Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API for restoring Pseudorandom and PcgRandom state #14123

Merged
merged 3 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions builtin/game/features.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ core.features = {
sound_params_start_time = true,
physics_overrides_v2 = true,
hud_def_type_field = true,
random_state_restore = true,
}

function core.has_feature(arg)
Expand Down
10 changes: 9 additions & 1 deletion doc/lua_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -5284,6 +5284,10 @@ Utilities
physics_overrides_v2 = true,
-- In HUD definitions the field `type` is used and `hud_elem_type` is deprecated (5.9.0)
hud_def_type_field = true,
-- PseudoRandom and PcgRandom state is restorable
-- PseudoRandom has get_state method
-- PcgRandom has get_state and set_state methods (5.9.0)
random_state_restore = true,
}
```

Expand Down Expand Up @@ -8056,7 +8060,7 @@ child will follow movement and rotation of that bone.
* `get_lighting()`: returns the current state of lighting for the player.
* Result is a table with the same fields as `light_definition` in `set_lighting`.
* `respawn()`: Respawns the player using the same mechanism as the death screen,
including calling on_respawnplayer callbacks.
including calling `on_respawnplayer` callbacks.

`PcgRandom`
-----------
Expand All @@ -8079,6 +8083,8 @@ offering very strong randomness.
* `mean = (max - min) / 2`, and
* `variance = (((max - min + 1) ^ 2) - 1) / (12 * num_trials)`
* Increasing `num_trials` improves accuracy of the approximation
* `get_state()`: return generator state encoded in string
* `set_state(state_string)`: restore generator state from encoded string

`PerlinNoise`
-------------
Expand Down Expand Up @@ -8171,6 +8177,8 @@ Uses a well-known LCG algorithm introduced by K&R.
* `next(min, max)`: return next integer random number [`min`...`max`]
* Either `max - min == 32767` or `max - min <= 6553` must be true
due to the simple implementation making a bad distribution otherwise.
* `get_state()`: return state of pseudorandom generator as number
* use returned number as seed in PseudoRandom constructor to restore

`Raycast`
---------
Expand Down
1 change: 1 addition & 0 deletions games/devtest/.luacheckrc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ read_globals = {
"Settings",
"check",
"PseudoRandom",
"PcgRandom",

string = {fields = {"split", "trim"}},
table = {fields = {"copy", "getn", "indexof", "insert_all"}},
Expand Down
42 changes: 34 additions & 8 deletions games/devtest/mods/unittests/misc.lua
Original file line number Diff line number Diff line change
@@ -1,15 +1,41 @@
local function test_random()
local function test_pseudo_random()
-- We have comprehensive unit tests in C++, this is just to make sure the API code isn't messing up
local pr = PseudoRandom(13)
assert(pr:next() == 22290)
assert(pr:next() == 13854)
local gen1 = PseudoRandom(13)
assert(gen1:next() == 22290)
assert(gen1:next() == 13854)

local pr2 = PseudoRandom(-101)
assert(pr2:next(0, 100) == 35)
for n = 2, 128 do
gen1:next()
end

local gen2 = PseudoRandom(gen1:get_state())

for n = 128, 256 do
assert(gen1:next() == gen2:next())
end

sfence marked this conversation as resolved.
Show resolved Hide resolved
local pr3 = PseudoRandom(-101)
assert(pr3:next(0, 100) == 35)
-- unusual case that is normally disallowed:
assert(pr2:next(10000, 42767) == 12485)
assert(pr3:next(10000, 42767) == 12485)
end
unittests.register("test_pseudo_random", test_pseudo_random)

local function test_pcg_random()
local gen1 = PcgRandom(55)

for n = 0, 128 do
gen1:next()
end

local gen2 = PcgRandom(26)
gen2:set_state(gen1:get_state())

for n = 128, 256 do
assert(gen1:next() == gen2:next())
end
end
unittests.register("test_random", test_random)
unittests.register("test_pcg_random", test_pcg_random)

local function test_dynamic_media(cb, player)
if core.get_player_information(player:get_player_name()).protocol_version < 40 then
Expand Down
12 changes: 12 additions & 0 deletions src/noise.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,18 @@ s32 PcgRandom::randNormalDist(s32 min, s32 max, int num_trials)
return myround((float)accum / num_trials);
}

void PcgRandom::getState(u64 state[2]) const
{
state[0] = m_state;
state[1] = m_inc;
sfence marked this conversation as resolved.
Show resolved Hide resolved
}

void PcgRandom::setState(const u64 state[2])
{
m_state = state[0];
m_inc = state[1];
}

///////////////////////////////////////////////////////////////////////////////

float noise2d(int x, int y, s32 seed)
Expand Down
8 changes: 8 additions & 0 deletions src/noise.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ class PseudoRandom {
return (next() % (max - min + 1)) + min;
}

// Allow save and restore of state
inline int getState() const
sfence marked this conversation as resolved.
Show resolved Hide resolved
{
return m_next;
}
private:
s32 m_next;
};
Expand All @@ -94,6 +99,9 @@ class PcgRandom {
void bytes(void *out, size_t len);
s32 randNormalDist(s32 min, s32 max, int num_trials=6);

// Allow save and restore of state
void getState(u64 state[2]) const;
void setState(const u64 state[2]);
private:
u64 m_state;
u64 m_inc;
Expand Down
54 changes: 54 additions & 0 deletions src/script/lua_api/l_noise.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,17 @@ int LuaPseudoRandom::l_next(lua_State *L)
return 1;
}

int LuaPseudoRandom::l_get_state(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;

LuaPseudoRandom *o = checkObject<LuaPseudoRandom>(L, 1);
PseudoRandom &pseudo = o->m_pseudo;
int val = pseudo.getState();
lua_pushinteger(L, val);
return 1;
}


int LuaPseudoRandom::create_object(lua_State *L)
{
Expand Down Expand Up @@ -462,6 +473,7 @@ void LuaPseudoRandom::Register(lua_State *L)
const char LuaPseudoRandom::className[] = "PseudoRandom";
const luaL_Reg LuaPseudoRandom::methods[] = {
luamethod(LuaPseudoRandom, next),
luamethod(LuaPseudoRandom, get_state),
{0,0}
};

Expand Down Expand Up @@ -496,6 +508,46 @@ int LuaPcgRandom::l_rand_normal_dist(lua_State *L)
return 1;
}

int LuaPcgRandom::l_get_state(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;

LuaPcgRandom *o = checkObject<LuaPcgRandom>(L, 1);

u64 state[2];
o->m_rnd.getState(state);

std::stringstream s_state;

s_state << std::hex << std::setw(16) << std::setfill('0') << state[0];
s_state << std::hex << std::setw(16) << std::setfill('0') << state[1];

lua_pushstring(L, s_state.str().c_str());
sfence marked this conversation as resolved.
Show resolved Hide resolved
return 1;
}

int LuaPcgRandom::l_set_state(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;

LuaPcgRandom *o = checkObject<LuaPcgRandom>(L, 1);

std::string l_string = readParam<std::string>(L, 2);
if (l_string.size() != 32) {
throw LuaError("PcgRandom:set_state: Expected hex string of 32 characters");
}

std::istringstream s_state_0(l_string.substr(0, 16));
std::istringstream s_state_1(l_string.substr(16, 16));

u64 state[2];
s_state_0 >> std::hex >> state[0];
s_state_1 >> std::hex >> state[1];

o->m_rnd.setState(state);

return 0;
}

int LuaPcgRandom::create_object(lua_State *L)
{
Expand Down Expand Up @@ -536,6 +588,8 @@ const char LuaPcgRandom::className[] = "PcgRandom";
const luaL_Reg LuaPcgRandom::methods[] = {
luamethod(LuaPcgRandom, next),
luamethod(LuaPcgRandom, rand_normal_dist),
luamethod(LuaPcgRandom, get_state),
luamethod(LuaPcgRandom, set_state),
{0,0}
};

Expand Down
5 changes: 5 additions & 0 deletions src/script/lua_api/l_noise.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ class LuaPseudoRandom : public ModApiBase
// next(self, min=0, max=32767) -> get next value
static int l_next(lua_State *L);

// save state
static int l_get_state(lua_State *L);
public:
LuaPseudoRandom(s32 seed) : m_pseudo(seed) {}

Expand Down Expand Up @@ -150,6 +152,9 @@ class LuaPcgRandom : public ModApiBase
// get next normally distributed random value
static int l_rand_normal_dist(lua_State *L);

// save and restore state
static int l_get_state(lua_State *L);
static int l_set_state(lua_State *L);
public:
LuaPcgRandom(u64 seed) : m_rnd(seed) {}
LuaPcgRandom(u64 seed, u64 seq) : m_rnd(seed, seq) {}
Expand Down
44 changes: 31 additions & 13 deletions src/unittest/test_random.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,26 @@ void TestRandom::testPseudoRandom()
{
PseudoRandom pr(814538);

for (u32 i = 0; i != 256; i++)
UASSERTEQ(s32, pr.next(), expected_pseudorandom_results[i]);

PseudoRandom pr2(0);
UASSERTEQ(int, pr2.next(), 0);
UASSERTEQ(int, pr2.next(), 21469);
UASSERTEQ(int, pr2.next(), 9989);

PseudoRandom pr3(-101);
UASSERTEQ(int, pr3.next(), 3267);
UASSERTEQ(int, pr3.next(), 2485);
UASSERTEQ(int, pr3.next(), 30057);
for (u32 i = 0; i != 128; i++)
UASSERTEQ(int, pr.next(), expected_pseudorandom_results[i]);
sfence marked this conversation as resolved.
Show resolved Hide resolved

int state = pr.getState();
sfence marked this conversation as resolved.
Show resolved Hide resolved
PseudoRandom pr2(state);

for (u32 i = 128; i != 256; i++) {
UASSERTEQ(int, pr.next(), expected_pseudorandom_results[i]);
UASSERTEQ(int, pr2.next(), expected_pseudorandom_results[i]);
sfence marked this conversation as resolved.
Show resolved Hide resolved
}

PseudoRandom pr3(0);
UASSERTEQ(int, pr3.next(), 0);
UASSERTEQ(int, pr3.next(), 21469);
UASSERTEQ(int, pr3.next(), 9989);

PseudoRandom pr4(-101);
UASSERTEQ(int, pr4.next(), 3267);
UASSERTEQ(int, pr4.next(), 2485);
UASSERTEQ(int, pr4.next(), 30057);
}


Expand Down Expand Up @@ -101,8 +109,18 @@ void TestRandom::testPcgRandom()
{
PcgRandom pr(814538, 998877);

for (u32 i = 0; i != 256; i++)
for (u32 i = 0; i != 128; i++)
UASSERTEQ(u32, pr.next(), expected_pcgrandom_results[i]);

PcgRandom pr2(0, 0);
u64 state[2];
pr.getState(state);
pr2.setState(state);

for (u32 i = 128; i != 256; i++) {
UASSERTEQ(u32, pr.next(), expected_pcgrandom_results[i]);
UASSERTEQ(u32, pr2.next(), expected_pcgrandom_results[i]);
}
}


Expand Down