1 module wren.optional.random;
2 import wren.vm;
3 
4 private static const(char)[] randomModuleSource = import("optional/wren_opt_random.wren");
5 
6 // Implements the well equidistributed long-period linear PRNG (WELL512a).
7 //
8 // https://en.wikipedia.org/wiki/Well_equidistributed_long-period_linear
9 struct Well512
10 {
11     uint[32] state;
12     uint index;
13 }
14 
15 // Code from: http://www.lomont.org/Math/Papers/2008/Lomont_PRNG_2008.pdf
16 static uint advanceState(Well512* well) @nogc
17 {
18     uint a, b, c, d;
19     a = well.state[well.index];
20     c = well.state[(well.index + 13) & 15];
21     b =  a ^ c ^ (a << 16) ^ (c << 15);
22     c = well.state[(well.index + 9) & 15];
23     c ^= (c >> 11);
24     a = well.state[well.index] = b ^ c;
25     d = a ^ ((a << 5) & 0xda442d24U);
26 
27     well.index = (well.index + 15) & 15;
28     a = well.state[well.index];
29     well.state[well.index] = a ^ b ^ d ^ (a << 2) ^ (b << 18) ^ (c << 28);
30     return well.state[well.index];
31 }
32 
33 static void randomAllocate(WrenVM* vm) @nogc
34 {
35     Well512* well = cast(Well512*)wrenSetSlotNewForeign(vm, 0, 0, Well512.sizeof);
36     well.index = 0;
37 }
38 
39 static void randomSeed0(WrenVM* vm) @nogc
40 {
41     import core.stdc.stdlib : srand, rand;
42     import core.stdc.time : time;
43 
44     Well512* well = cast(Well512*)wrenGetSlotForeign(vm, 0);
45 
46     srand(cast(uint)time(null));
47     for (int i = 0; i < 16; i++)
48     {
49         well.state[i] = rand();
50     }
51 }
52 
53 static void randomSeed1(WrenVM* vm) @nogc
54 {
55     import core.stdc.stdlib : srand, rand;
56     Well512* well = cast(Well512*)wrenGetSlotForeign(vm, 0);
57 
58     srand(cast(uint)wrenGetSlotDouble(vm, 1));
59     for (int i = 0; i < 16; i++)
60     {
61         well.state[i] = rand();
62     }
63 }
64 
65 static void randomSeed16(WrenVM* vm) @nogc
66 {
67     Well512* well = cast(Well512*)wrenGetSlotForeign(vm, 0);
68 
69     for (int i = 0; i < 16; i++)
70     {
71         well.state[i] = cast(uint)wrenGetSlotDouble(vm, i + 1);
72     }
73 }
74 
75 static void randomFloat(WrenVM* vm) @nogc
76 {
77     Well512* well = cast(Well512*)wrenGetSlotForeign(vm, 0);
78 
79     // A double has 53 bits of precision in its mantissa, and we'd like to take
80     // full advantage of that, so we need 53 bits of random source data.
81 
82     // First, start with 32 random bits, shifted to the left 21 bits.
83     double result = cast(double)advanceState(well) * (1 << 21);
84 
85     // Then add another 21 random bits.
86     result += cast(double)(advanceState(well) & ((1 << 21) - 1));
87 
88     // Now we have a number from 0 - (2^53). Divide be the range to get a double
89     // from 0 to 1.0 (half-inclusive).
90     result /= 9007199254740992.0;
91 
92     wrenSetSlotDouble(vm, 0, result);
93 }
94 
95 static void randomInt0(WrenVM* vm) @nogc
96 {
97     Well512* well = cast(Well512*)wrenGetSlotForeign(vm, 0);
98 
99     wrenSetSlotDouble(vm, 0, cast(double)advanceState(well));
100 }
101 
102 const(char)[] wrenRandomSource() @nogc
103 {
104     return randomModuleSource;
105 }
106 
107 WrenForeignClassMethods wrenRandomBindForeignClass(WrenVM* vm,
108                                                     const(char)* module_,
109                                                     const(char)* className) @nogc
110 {
111     import core.stdc.string : strcmp;
112     assert(strcmp(className, "Random") == 0, "Should be in Random class.");
113     WrenForeignClassMethods methods;
114     methods.allocate = &randomAllocate;
115     methods.finalize = null;
116     return methods;
117 }
118 
119 WrenForeignMethodFn wrenRandomBindForeignMethod(WrenVM* vm,
120                                                 const(char)* className,
121                                                 bool isStatic,
122                                                 const(char)* signature) @nogc
123 {
124     import core.stdc.string : strcmp;
125     assert(strcmp(className, "Random") == 0, "Should be in Random class.");
126 
127     if (strcmp(signature, "<allocate>") == 0) return &randomAllocate;
128     if (strcmp(signature, "seed_()") == 0) return &randomSeed0;
129     if (strcmp(signature, "seed_(_)") == 0) return &randomSeed1;
130         
131     if (strcmp(signature, "seed_(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)") == 0)
132     {
133         return &randomSeed16;
134     }
135     
136     if (strcmp(signature, "float()") == 0) return &randomFloat;
137     if (strcmp(signature, "int()") == 0) return &randomInt0;
138 
139     assert(0, "Unknown method.");
140 }