Check in code for new random number generator

This commit is contained in:
2022-03-31 17:15:15 -04:00
parent 7fc6263e37
commit c48e02642a
9 changed files with 291 additions and 8 deletions

View File

@@ -1,6 +1,8 @@
#include "world.hpp"
#include "pprint.hpp"
#include <cmath>
#include <iostream>
static void tangible_getall(LuaStack &LS0, LuaSlot list, const util::IdVector &idv) {
LuaVar tangibles, tan;
@@ -311,6 +313,161 @@ LuaDefine(tangible_nopredict, "",
}
}
LuaDefine(math_random, "(args...)",
"|Generate random numbers."
"|"
"|What it generates depends on the arguments:"
"|"
"| () - a float in range [0.0, 1.0)"
"| (high) - an int between 1 and high inclusive"
"| (low, high) - an int between low and high inclusive"
"|"
"|math.random tries to cooperate with predictive"
"|reexecution to be as predictable as possible."
"|To achieve predictability, we used an ad-hoc"
"|random number generator. It passes a variety of"
"|statistical tests, but it's not well-studied."
"|"
"|If you want actually want nonpredictability, or"
"|if you need the assurance of a well-studied random"
"|number generator, use math.mtrandom or"
"|math.cryptrandom instead.") {
// Parse the arguments.
// This is hairy because there's a lot of possibilities.
bool passed_in_randomstate = false;
int arg = 1;
if ((lua_gettop(L) >= arg) && (lua_istable(L, arg))) {
passed_in_randomstate = true;
arg += 1;
}
bool have_range = false;
int64_t low, high;
if ((lua_gettop(L) >= arg) && (lua_type(L, arg) == LUA_TNUMBER)) {
double lowf, highf;
if ((lua_gettop(L) >= arg+1) && (lua_type(L, arg+1) == LUA_TNUMBER)) {
lowf = std::floor(lua_tonumber(L, arg));
highf = std::floor(lua_tonumber(L, arg + 1));
arg += 2;
} else {
lowf = 1;
highf = std::floor(lua_tonumber(L, arg));
arg += 1;
}
if ((lowf < -LuaStack::MAXINT) || (highf > LuaStack::MAXINT)) {
luaL_error(L, "math.random range exceeds MAXINT");
return 0;
}
if (lowf > highf) {
luaL_error(L, "math.random range low > high");
return 0;
}
low = int64_t(lowf);
high = int64_t(highf);
have_range = true;
}
if (lua_gettop(L) >= arg) {
luaL_error(L, "math.random accepts an optional randomstate and an optional range");
return 0;
}
// Generate the seed, count, and salt.
// The salt prevents accidental duplication between user-specified
// seeds and system-generated seeds.
uint64_t seed, count, salt;
if (passed_in_randomstate) {
lua_pushstring(L, "seed");
lua_rawget(L, 1);
lua_pushstring(L, "count");
lua_rawget(L, 1);
if ((lua_type(L, -1) != LUA_TNUMBER) ||
(lua_type(L, -2) != LUA_TNUMBER)) {
luaL_error(L, "Not a valid randomstate table");
return 0;
}
double dseed = lua_tonumber(L, -2);
double dcount = lua_tonumber(L, -1);
seed = uint64_t(dseed) & LuaStack::MAXINT;
count = uint64_t(dcount) & LuaStack::MAXINT;
if (dseed < 0) {
salt = 0x35c9a6082a097ade;
} else {
salt = 0x4785d086ead90c20;
}
lua_pop(L, 2);
lua_pushstring(L, "count");
lua_pushnumber(L, double((count + 1) & LuaStack::MAXINT));
lua_rawset(L, 1);
} else {
World *w = World::fetch_global_pointer(L);
if (w->lthread_use_ppool_) {
Tangible *actor = w->tangible_get(w->lthread_actor_id_);
seed = w->lthread_actor_id_;
count = actor->id_player_pool_.get_seqno();
salt = 0x3ab0fb84aedc3764;
} else {
// TODO: maybe throw in a 'donotpredict' here.
seed = 123456;
count = w->id_global_pool_.get_seqno();
salt = 0x6f493c90faf0139d;
}
}
// Generate the hash and convert to a lua_Number.
uint64_t hash = util::hash_ints(seed, count, salt, 456);
if (!have_range) {
double result = (hash & LuaStack::MAXINT) * 0x1p-53;
lua_pushnumber(L, result);
} else {
uint64_t range = (high - low) + 1;
uint64_t offset = (hash & 0x7FFFFFFFFFFFFFFF) % range;
int64_t result = low + int64_t(offset);
lua_pushnumber(L, result);
}
return 1;
}
LuaDefine(math_randomstate, "(seed)",
"|Create and return a randomstate table."
"|This is a lua table that stores the state for a random"
"|number generator. A randomstate table can be passed"
"|to math.random."
"|"
"|You can optionally omit the seed, in which case it will"
"|pick a seed randomly. Automatically-generated seeds are"
"|guaranteed never to be the same as user-specified seeds.") {
double seed;
if (lua_gettop(L) == 0) {
World *w = World::fetch_global_pointer(L);
int64_t iseed = (w->id_global_pool_.get_seqno() & LuaStack::MAXINT) + 1;
seed = -iseed;
} else if (lua_gettop(L) == 1) {
if (lua_type(L, 1) != LUA_TNUMBER) {
luaL_error(L, "math.randomstate takes an optional integer seed");
return 0;
}
seed = lua_tonumber(L, 1);
if ((seed < 0.0) || (seed > LuaStack::MAXINT) || (std::floor(seed) != seed)) {
luaL_error(L, "math.randomstate seed must be an integer 0-MAXINT");
return 0;
}
} else {
luaL_error(L, "math.randomstate takes an optional integer seed");
return 0;
}
lua_newtable(L);
lua_pushstring(L, "seed");
lua_pushnumber(L, seed);
lua_rawset(L, -3);
lua_pushstring(L, "count");
lua_pushnumber(L, 0);
lua_rawset(L, -3);
return 1;
}
LuaSandboxBuiltin(math_randomseed, "", "");
LuaDefine(pprint, "obj1,obj2,...",
"|Pretty-print object or objects.") {
World *w = World::fetch_global_pointer(L);