YoYoGames / GameMaker-Bugs

Public tracking for GameMaker bugs
24 stars 8 forks source link

Individual Random Number Generator Objects/States #5695

Open coyo-t opened 6 months ago

coyo-t commented 6 months ago

Is your feature request related to a problem?

its not easy to have fine control over RNG in gamemaker. you cant get the current RNG state or push/pop it; you can only get and set the original seed in addition to it being one global RNG state. this is a problem if the current random state needs to be saved for a later time (IE level gen, but some graphical stuff needs random numbers too, which shouldnt affect the levelgen outcome. or demo playback which needs the exact random state to play back the demo without desyncs)

Describe the solution you'd like

functions which create a random "object"/"state" (random_create(seed), random_destroy(rng)). as well, a function that takes a random object and makes it the current global random state (random_get_state() random_set_state(rng)). this way the existing random_* funcs would be kept in-place as they are without needing to change them to take in a random object

Describe alternatives you've considered

creating my own rng structs based on Java's. while this "works" im not happy with it (obviously not as performant as the native random functions, less portable across projects, ect)

Additional context

No response

TheouAegis commented 5 months ago

If your own RNG is less performant than GM's, it just means yours is better, or just overly complex. The GM RNG sets the seed at the start (or when randomize() or random_set_seed() is called), and every time you call a random function, it increments a hidden counter by the same amount every time. It performs some calculation using that counter and the seed. Whenever you call randomize() or random_set_seed(), the hidden counter resets. It's a very simplistic RNG. Just using something like get_timer() & 15 will give you a fairly random 1:16 chance with minimal processing power. I support GM adding a secondary set of RNG, though.

rwkay commented 5 months ago

errr,... the random number generator is nothing like the description above it is a full psuedo random number generator an implementation of https://web.archive.org/web/20181215122620/http://www.lomont.org/Math/Papers/2008/Lomont_PRNG_2008.pdf

the random seed is changed as part of the algorithm (ready for the next iteration)

randomize just choses a random seed.

other reading http://stackoverflow.com/questions/1046714/what-is-a-good-random-number-generator-for-a-game

the full C++ code we use is (we have published this before)

unsigned int YYRandom( void )
{
//  return ((float)rand() * (1.0f/((float)RAND_MAX+1.0f)));

   // PRNG from http://www.lomont.org/Math/Papers/2008/Lomont_PRNG_2008.pdf
   // other reading http://stackoverflow.com/questions/1046714/what-is-a-good-random-number-generator-for-a-game
   unsigned int a, b, c, d;
   a = state[g_RndIndex];
   c = state[(g_RndIndex+13)&15];
   b = a^c^(a<<16)^(c<<15);
   c = state[(g_RndIndex+9)&15];
   c ^= (c>>11);
   a = state[g_RndIndex] = b^c;
   d = a^((a<<5)&s_nRandomPoly);
   g_RndIndex = (g_RndIndex + 15)&15;
    // RK :: clang had an optimisation bug, caused when the following lines used to be
    // RK :: a = state[g_RndIndex];
    // RK :: state[g_RndIndex] = a^b^d^(a<<2)^(b<<18)^(c<<28);
    // RK :: where it seemed to think that it was OK to take d and substitiute in a^((a<<5)&s_nRandomPoly); into the expr a^b^d^(a<<2)^(b<<18)^(c<<28)
    // RK :: but a had been changed completely by the time that expr happened so it got the wrong answer
    // RK :: originally we switched off optimisations on this file (but that cannot be done reliably on all build systems, so changing this
    // RK :: to a different volatile variable seems to work
   volatile unsigned int e = state[g_RndIndex];
   state[g_RndIndex] = e^b^d^(e<<2)^(b<<18)^(c<<28);
   return state[g_RndIndex];
}