diff --git a/docs/modding_lua.txt b/docs/modding_lua.txt index 93924c0..152bdd7 100644 --- a/docs/modding_lua.txt +++ b/docs/modding_lua.txt @@ -738,6 +738,8 @@ common.prng_new(seed, stream) @ - random(max) - random(min, max) - returns a random number between min and max, defaulting to values of 0 and 1 + - jump(step) + - jump ahead by step amount, can be negative str = common.net_pack(fmt, ...) @ diff --git a/include/common.h b/include/common.h index ad644a5..55d1b34 100644 --- a/include/common.h +++ b/include/common.h @@ -698,6 +698,7 @@ void prng_seed(prng_t *rng, uint64_t seed, uint64_t stream); uint32_t prng_random(prng_t *rng); double prng_random_double(prng_t *rng); double prng_random_double_range(prng_t *rng, double minimum, double maximum); +void prng_jump(prng_t *rng, uint64_t step); // vecmath.c vec4f_t mtx_apply_vec(matrix_t *mtx, vec4f_t *vec); diff --git a/src/lua_random.h b/src/lua_random.h index 91fab0e..5b3198e 100644 --- a/src/lua_random.h +++ b/src/lua_random.h @@ -21,6 +21,12 @@ // taken, but without a way to take arbitrary steps, this is pretty useless. // It would also require storing more state, but it's only 128 bits at the // moment, which is already pretty small for a PRNG. +// * We have jump now, but if we allow seeding with strings, then we could get +// initial seeds that can't fit in Lua anyway. +// * We could return a hex string or something, but that would require us to +// accept hex strings only. How would we deal with non-hex strings? Separate +// function for hex strings? get_state/set_state, but allow arbitrary strings +// (and hash them somehow) in seed? // * We could add a clone function that creates a new one with the same state. // Useless for networking though. @@ -67,6 +73,15 @@ int icelua_fn_cl_prng_random(lua_State *L) return 1; } +int icelua_fn_cl_prng_jump(lua_State *L) +{ + int top = icelua_assert_stack(L, 1, 1); + prng_t *rng = lua_touserdata(L, lua_upvalueindex(1)); + uint64_t step = (uint64_t)lua_tonumber(L, 1); + prng_jump(rng, step); + return 0; +} + int icelua_fn_common_prng_new(lua_State *L) { int top = icelua_assert_stack(L, 0, 2); @@ -96,6 +111,11 @@ int icelua_fn_common_prng_new(lua_State *L) lua_pushcclosure(L, &icelua_fn_cl_prng_random, 1); // Create closure, 1 upvalue (the RNG state) lua_settable(L, -4); // Insert closure into table + lua_pushstring(L, "jump"); // Function name + lua_pushvalue(L, -2); // Duplicate RNG reference to top of stack + lua_pushcclosure(L, &icelua_fn_cl_prng_jump, 1); // Create closure, 1 upvalue (the RNG state) + lua_settable(L, -4); // Insert closure into table + // Pop the RNG state off the stack. lua_pop(L, 1); diff --git a/src/random.c b/src/random.c index 29c940e..5d94a12 100644 --- a/src/random.c +++ b/src/random.c @@ -20,8 +20,9 @@ // Basic implementation of the PCG psuedorandom number generator. // Specifically, PCG-XSH-RR, as that's what the paper recommends for general use. // This may diverge from the official implementations, but I wanted to give credit. -// TODO: We could drop the stream part and use a set value. We have no real use -// for it, and it complicates the API. It does give us more possible sequences though. +// TODO: We could drop the stream part and use a set value. It's useful for seeding +// a new PRNG from an existing one's output, but if people just want random seeds, +// we can seed from time. // On the other hand, using a set value does allow us to ensure that a relatively // good value is chosen (co-primes, etc.). @@ -64,3 +65,21 @@ double prng_random_double_range(prng_t *rng, double minimum, double maximum) { double d = (double)random / UINT32_MAX; return minimum + d * (maximum - minimum); } + +// Magical maths thanks to Forrest B. Brown, "Random number Generation with Arbitrary Strides" +void prng_jump(prng_t *rng, uint64_t step) { + uint64_t m_result = 1; + uint64_t c_result = 0; + uint64_t stream = rng->stream; + uint64_t multiplier = PRNG_MULTIPLIER; + while (step > 0) { + if (step & 1) { + m_result *= multiplier; + c_result = c_result * multiplier + stream; + } + stream = (multiplier + 1) * stream; + multiplier *= multiplier; + step >>= 1; + } + rng->state = m_result * rng->state + c_result; +}