Replace RNG with xoshiro256+; update replay format

Support for writing older replay versions has been removed.
This commit is contained in:
Andrei Alexeyev 2019-03-09 18:19:42 +02:00
parent 4669ef291f
commit fc41ebf89c
No known key found for this signature in database
GPG key ID: 363707CD4C7FE8A4
7 changed files with 159 additions and 149 deletions

View file

@ -235,7 +235,7 @@ static void replayview_drawitem(void *n, int item, int cnt) {
int columns = sizeof(sizes)/sizeof(float), i, j;
float base_size = (SCREEN_W - 110.0) / columns;
time_t t = rpy->stages[0].seed;
time_t t = rpy->stages[0].start_time;
struct tm* timeinfo = localtime(&t);
for(i = 0; i < columns; ++i) {
@ -363,7 +363,7 @@ static int replayview_cmp(const void *a, const void *b) {
Replay *arpy = actx->replay;
Replay *brpy = bctx->replay;
return brpy->stages[0].seed - arpy->stages[0].seed;
return brpy->stages[0].start_time - arpy->stages[0].start_time;
}
static int fill_replayview_menu(MenuData *m) {

View file

@ -16,62 +16,67 @@
static RandomState *tsrand_current;
/*
* Complementary-multiply-with-carry algorithm
*/
uint64_t splitmix64(uint64_t *state) {
// from http://xoshiro.di.unimi.it/splitmix64.c
// CMWC engine
uint32_t tsrand_p(RandomState *rnd) {
assert(!rnd->locked);
uint64_t const a = 18782; // as Marsaglia recommends
uint32_t const m = 0xfffffffe; // as Marsaglia recommends
uint64_t t;
uint32_t x;
rnd->i = (rnd->i + 1) & (CMWC_CYCLE - 1);
t = a * rnd->Q[rnd->i] + rnd->c;
// Let c = t / 0xfffffff, x = t mod 0xffffffff
rnd->c = t >> 32;
x = t + rnd->c;
if(x < rnd->c) {
x++;
rnd->c++;
}
return rnd->Q[rnd->i] = m - x;
uint64_t z = (*state += 0x9e3779b97f4a7c15);
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
return z ^ (z >> 31);
}
void tsrand_seed_p(RandomState *rnd, uint32_t seed) {
static const uint32_t phi = 0x9e3779b9;
uint64_t makeseed(void) {
static uint64_t s;
return splitmix64(&s) ^ SDL_GetPerformanceCounter();
}
rnd->Q[0] = seed;
rnd->Q[1] = seed + phi;
rnd->Q[2] = seed + phi + phi;
static inline uint64_t rotl(uint64_t x, int k) {
return (x << k) | (x >> (64 - k));
}
for(int i = 3; i < CMWC_CYCLE; ++i) {
rnd->Q[i] = rnd->Q[i - 3] ^ rnd->Q[i - 2] ^ phi ^ i;
}
static uint64_t xoshiro256plus(uint64_t s[4]) {
// from http://xoshiro.di.unimi.it/xoshiro256plus.c
rnd->c = 0x8edc04;
rnd->i = CMWC_CYCLE - 1;
const uint64_t result_plus = s[0] + s[3];
const uint64_t t = s[1] << 17;
for(int i = 0; i < CMWC_CYCLE*16; ++i) {
tsrand_p(rnd);
}
s[2] ^= s[0];
s[3] ^= s[1];
s[1] ^= s[2];
s[0] ^= s[3];
s[2] ^= t;
s[3] = rotl(s[3], 45);
return result_plus;
}
uint32_t tsrand_p(RandomState *rnd) {
assert(!rnd->locked);
return xoshiro256plus(rnd->state) >> 32;
}
uint64_t tsrand64_p(RandomState *rnd) {
assert(!rnd->locked);
return xoshiro256plus(rnd->state);
}
void tsrand_seed_p(RandomState *rnd, uint64_t seed) {
rnd->state[0] = splitmix64(&seed);
rnd->state[1] = splitmix64(&seed);
rnd->state[2] = splitmix64(&seed);
rnd->state[3] = splitmix64(&seed);
}
void tsrand_switch(RandomState *rnd) {
tsrand_current = rnd;
}
void tsrand_init(RandomState *rnd, uint32_t seed) {
void tsrand_init(RandomState *rnd, uint64_t seed) {
memset(rnd, 0, sizeof(RandomState));
tsrand_seed_p(rnd, seed);
}
void tsrand_seed(uint32_t seed) {
void tsrand_seed(uint64_t seed) {
tsrand_seed_p(tsrand_current, seed);
}
@ -79,17 +84,26 @@ uint32_t tsrand(void) {
return tsrand_p(tsrand_current);
}
float frand(void) {
return (float)((double)tsrand()/(double)TSRAND_MAX);
uint64_t tsrand64(void) {
return tsrand64_p(tsrand_current);
}
float nfrand(void) {
static inline double makedouble(uint64_t x) {
// Range: [0..1)
return (x >> 11) * (1.0 / (UINT64_C(1) << 53));
}
double frand(void) {
return makedouble(tsrand64());
}
double nfrand(void) {
return frand() * 2.0 - 1.0;
}
// we use this to support multiple rands in a single statement without breaking replays across different builds
static uint32_t tsrand_array[TSRAND_ARRAY_LIMIT];
static uint64_t tsrand_array[TSRAND_ARRAY_LIMIT];
static int tsrand_array_elems;
static uint64_t tsrand_fillflags = 0;
@ -106,32 +120,32 @@ static void tsrand_error(const char *file, const char *func, uint line, const ch
#define TSRANDERR(...) tsrand_error(file, __func__, line, __VA_ARGS__)
void __tsrand_fill_p(RandomState *rnd, int amount, const char *file, uint line) {
void _tsrand_fill_p(RandomState *rnd, int amount, const char *file, uint line) {
if(tsrand_fillflags) {
TSRANDERR("Some indices left unused from the previous call");
return;
}
tsrand_array_elems = amount;
tsrand_fillflags = (1L << amount) - 1;
tsrand_fillflags = (UINT64_C(1) << amount) - 1;
for(int i = 0; i < amount; ++i) {
tsrand_array[i] = tsrand_p(rnd);
tsrand_array[i] = tsrand64_p(rnd);
}
}
void __tsrand_fill(int amount, const char *file, uint line) {
__tsrand_fill_p(tsrand_current, amount, file, line);
void _tsrand_fill(int amount, const char *file, uint line) {
_tsrand_fill_p(tsrand_current, amount, file, line);
}
uint32_t __tsrand_a(int idx, const char *file, uint line) {
uint64_t _tsrand64_a(int idx, const char *file, uint line) {
if(idx >= tsrand_array_elems || idx < 0) {
TSRANDERR("Index out of range (%i / %i)", idx, tsrand_array_elems);
return 0;
}
if(tsrand_fillflags & (1L << idx)) {
tsrand_fillflags &= ~(1L << idx);
if(tsrand_fillflags & (UINT64_C(1) << idx)) {
tsrand_fillflags &= ~(UINT64_C(1) << idx);
return tsrand_array[idx];
}
@ -139,12 +153,16 @@ uint32_t __tsrand_a(int idx, const char *file, uint line) {
return 0;
}
float __afrand(int idx, const char *file, uint line) {
return (float)((double)__tsrand_a(idx, file, line) / (double)TSRAND_MAX);
uint32_t _tsrand_a(int idx, const char *file, uint line) {
return _tsrand64_a(idx, file, line) >> 32;
}
float __anfrand(int idx, const char *file, uint line) {
return __afrand(idx, file, line) * 2 - 1;
double _afrand(int idx, const char *file, uint line) {
return makedouble(_tsrand64_a(idx, file, line));
}
double _anfrand(int idx, const char *file, uint line) {
return _afrand(idx, file, line) * 2 - 1;
}
void tsrand_lock(RandomState *rnd) {

View file

@ -11,48 +11,43 @@
#include "taisei.h"
#define CMWC_CYCLE 4096 // as Marsaglia recommends
#define CMWC_C_MAX 809430660 // as Marsaglia recommends
struct RandomState {
uint32_t Q[CMWC_CYCLE];
uint32_t c; // must be limited with CMWC_C_MAX
uint32_t i;
typedef struct RandomState {
uint64_t state[4];
bool locked;
};
} RandomState;
typedef struct RandomState RandomState;
uint64_t splitmix64(uint64_t *state);
uint64_t makeseed(void);
void tsrand_init(RandomState *rnd, uint32_t seed);
void tsrand_init(RandomState *rnd, uint64_t seed);
void tsrand_switch(RandomState *rnd);
void tsrand_seed_p(RandomState *rnd, uint32_t seed);
void tsrand_seed_p(RandomState *rnd, uint64_t seed);
uint32_t tsrand_p(RandomState *rnd);
uint64_t tsrand64_p(RandomState *rnd);
void tsrand_seed(uint32_t seed);
void tsrand_seed(uint64_t seed);
uint32_t tsrand(void);
uint64_t tsrand64(void);
void tsrand_lock(RandomState *rnd);
void tsrand_unlock(RandomState *rnd);
float frand(void);
float nfrand(void);
double frand(void);
double nfrand(void);
void __tsrand_fill_p(RandomState *rnd, int amount, const char *file, uint line);
void __tsrand_fill(int amount, const char *file, uint line);
uint32_t __tsrand_a(int idx, const char *file, uint line);
float __afrand(int idx, const char *file, uint line);
float __anfrand(int idx, const char *file, uint line);
void _tsrand_fill_p(RandomState *rnd, int amount, const char *file, uint line);
void _tsrand_fill(int amount, const char *file, uint line);
uint32_t _tsrand_a(int idx, const char *file, uint line);
uint64_t _tsrand64_a(int idx, const char *file, uint line);
double _afrand(int idx, const char *file, uint line);
double _anfrand(int idx, const char *file, uint line);
#define tsrand_fill_p(rnd,amount) __tsrand_fill_p(rnd, amount, __FILE__, __LINE__)
#define tsrand_fill(amount) __tsrand_fill(amount, __FILE__, __LINE__)
#define tsrand_a(idx) __tsrand_a(idx, __FILE__, __LINE__)
#define afrand(idx) __afrand(idx, __FILE__, __LINE__)
#define anfrand(idx) __anfrand(idx, __FILE__, __LINE__)
#define tsrand_fill_p(rnd,amount) _tsrand_fill_p(rnd, amount, __FILE__, __LINE__)
#define tsrand_fill(amount) _tsrand_fill(amount, __FILE__, __LINE__)
#define tsrand_a(idx) _tsrand_a(idx, __FILE__, __LINE__)
#define afrand(idx) _afrand(idx, __FILE__, __LINE__)
#define anfrand(idx) _anfrand(idx, __FILE__, __LINE__)
#define TSRAND_MAX UINT32_MAX
#define TSRAND_ARRAY_LIMIT 64
#define srand USE_tsrand_seed_INSTEAD_OF_srand
#define rand USE_tsrand_INSTEAD_OF_rand
#define TSRAND_ARRAY_LIMIT 16
#endif // IGUARD_random_h

View file

@ -24,7 +24,7 @@ void replay_init(Replay *rpy) {
log_debug("Replay at %p initialized for writing", (void*)rpy);
}
ReplayStage* replay_create_stage(Replay *rpy, StageInfo *stage, uint64_t seed, Difficulty diff, Player *plr) {
ReplayStage* replay_create_stage(Replay *rpy, StageInfo *stage, uint64_t start_time, uint64_t seed, Difficulty diff, Player *plr) {
ReplayStage *s;
rpy->stages = (ReplayStage*)realloc(rpy->stages, sizeof(ReplayStage) * (++rpy->numstages));
@ -37,7 +37,8 @@ ReplayStage* replay_create_stage(Replay *rpy, StageInfo *stage, uint64_t seed, D
s->events = (ReplayEvent*)malloc(sizeof(ReplayEvent) * s->capacity);
s->stage = stage->id;
s->seed = seed;
s->start_time = start_time;
s->rng_seed = seed;
s->diff = diff;
s->plr_pos_x = floor(creal(plr->pos));
@ -166,7 +167,7 @@ static uint32_t replay_calc_stageinfo_checksum(ReplayStage *stg, uint16_t versio
uint32_t cs = 0;
cs += stg->stage;
cs += stg->seed;
cs += stg->rng_seed;
cs += stg->diff;
cs += stg->plr_points;
cs += stg->plr_char;
@ -200,24 +201,15 @@ static uint32_t replay_calc_stageinfo_checksum(ReplayStage *stg, uint16_t versio
}
static bool replay_write_stage(ReplayStage *stg, SDL_RWops *file, uint16_t version) {
if(version >= REPLAY_STRUCT_VERSION_TS102000_REV1) {
SDL_WriteLE32(file, stg->flags);
}
assert(version >= REPLAY_STRUCT_VERSION_TS103000_REV2);
SDL_WriteLE32(file, stg->flags);
SDL_WriteLE16(file, stg->stage);
SDL_WriteLE32(file, stg->seed);
SDL_WriteLE64(file, stg->start_time);
SDL_WriteLE64(file, stg->rng_seed);
SDL_WriteU8(file, stg->diff);
if(version >= REPLAY_STRUCT_VERSION_TS103000_REV0) {
SDL_WriteLE64(file, stg->plr_points);
} else {
SDL_WriteLE32(file, stg->plr_points);
}
if(version >= REPLAY_STRUCT_VERSION_TS102000_REV1) {
SDL_WriteU8(file, stg->plr_continues_used);
}
SDL_WriteLE64(file, stg->plr_points);
SDL_WriteU8(file, stg->plr_continues_used);
SDL_WriteU8(file, stg->plr_char);
SDL_WriteU8(file, stg->plr_shot);
SDL_WriteLE16(file, stg->plr_pos_x);
@ -225,30 +217,12 @@ static bool replay_write_stage(ReplayStage *stg, SDL_RWops *file, uint16_t versi
SDL_WriteU8(file, stg->plr_focus);
SDL_WriteLE16(file, stg->plr_power);
SDL_WriteU8(file, stg->plr_lives);
if(version >= REPLAY_STRUCT_VERSION_TS103000_REV1) {
SDL_WriteLE16(file, stg->plr_life_fragments);
} else {
SDL_WriteU8(file, stg->plr_life_fragments);
}
SDL_WriteLE16(file, stg->plr_life_fragments);
SDL_WriteU8(file, stg->plr_bombs);
if(version >= REPLAY_STRUCT_VERSION_TS103000_REV1) {
SDL_WriteLE16(file, stg->plr_bomb_fragments);
} else {
SDL_WriteU8(file, stg->plr_bomb_fragments);
}
SDL_WriteLE16(file, stg->plr_bomb_fragments);
SDL_WriteU8(file, stg->plr_inputflags);
if(version >= REPLAY_STRUCT_VERSION_TS103000_REV0) {
SDL_WriteLE32(file, stg->plr_graze);
SDL_WriteLE32(file, stg->plr_point_item_value);
} else if(version >= REPLAY_STRUCT_VERSION_TS102000_REV2) {
SDL_WriteLE16(file, stg->plr_graze);
}
SDL_WriteLE32(file, stg->plr_graze);
SDL_WriteLE32(file, stg->plr_point_item_value);
SDL_WriteLE16(file, stg->numevents);
SDL_WriteLE32(file, 1 + ~replay_calc_stageinfo_checksum(stg, version));
@ -267,20 +241,20 @@ static void fix_flags(Replay *rpy) {
}
bool replay_write(Replay *rpy, SDL_RWops *file, uint16_t version) {
assert(version >= REPLAY_STRUCT_VERSION_TS103000_REV2);
uint16_t base_version = (version & ~REPLAY_VERSION_COMPRESSION_BIT);
bool compression = (version & REPLAY_VERSION_COMPRESSION_BIT);
SDL_RWwrite(file, replay_magic_header, sizeof(replay_magic_header), 1);
SDL_WriteLE16(file, version);
if(base_version >= REPLAY_STRUCT_VERSION_TS102000_REV0) {
TaiseiVersion v;
TAISEI_VERSION_GET_CURRENT(&v);
TaiseiVersion v;
TAISEI_VERSION_GET_CURRENT(&v);
if(taisei_version_write(file, &v) != TAISEI_VERSION_SIZE) {
log_error("Failed to write game version: %s", SDL_GetError());
return false;
}
if(taisei_version_write(file, &v) != TAISEI_VERSION_SIZE) {
log_error("Failed to write game version: %s", SDL_GetError());
return false;
}
void *buf;
@ -295,10 +269,7 @@ bool replay_write(Replay *rpy, SDL_RWops *file, uint16_t version) {
replay_write_string(vfile, config_get_str(CONFIG_PLAYERNAME), base_version);
fix_flags(rpy);
if(base_version >= REPLAY_STRUCT_VERSION_TS102000_REV1) {
SDL_WriteLE32(vfile, rpy->flags);
}
SDL_WriteLE32(vfile, rpy->flags);
SDL_WriteLE16(vfile, rpy->numstages);
for(int i = 0; i < rpy->numstages; ++i) {
@ -390,6 +361,7 @@ static bool replay_read_header(Replay *rpy, SDL_RWops *file, int64_t filesize, s
case REPLAY_STRUCT_VERSION_TS102000_REV2:
case REPLAY_STRUCT_VERSION_TS103000_REV0:
case REPLAY_STRUCT_VERSION_TS103000_REV1:
case REPLAY_STRUCT_VERSION_TS103000_REV2:
{
if(taisei_version_read(file, &rpy->game_version) != TAISEI_VERSION_SIZE) {
log_error("%s: Failed to read game version", source);
@ -447,7 +419,15 @@ static bool replay_read_meta(Replay *rpy, SDL_RWops *file, int64_t filesize, con
}
CHECKPROP(stg->stage = SDL_ReadLE16(file), u);
CHECKPROP(stg->seed = SDL_ReadLE32(file), u);
if(version >= REPLAY_STRUCT_VERSION_TS103000_REV2) {
CHECKPROP(stg->start_time = SDL_ReadLE64(file), u);
CHECKPROP(stg->rng_seed = SDL_ReadLE64(file), u);
} else {
stg->rng_seed = SDL_ReadLE32(file);
stg->start_time = stg->rng_seed;
}
CHECKPROP(stg->diff = SDL_ReadU8(file), u);
if(version >= REPLAY_STRUCT_VERSION_TS103000_REV0) {

View file

@ -46,13 +46,16 @@
// Taisei v1.3 revision 1: expands life and bomb fragments to 16bit
#define REPLAY_STRUCT_VERSION_TS103000_REV1 10
// Taisei v1.3 revision 2: RNG changed; seed separated from start time; time expanded to 64bit
#define REPLAY_STRUCT_VERSION_TS103000_REV2 11
/* END supported struct versions */
#define REPLAY_VERSION_COMPRESSION_BIT 0x8000
#define REPLAY_COMPRESSION_CHUNK_SIZE 4096
// What struct version to use when saving recorded replays
#define REPLAY_STRUCT_VERSION_WRITE (REPLAY_STRUCT_VERSION_TS103000_REV1 | REPLAY_VERSION_COMPRESSION_BIT)
#define REPLAY_STRUCT_VERSION_WRITE (REPLAY_STRUCT_VERSION_TS103000_REV2 | REPLAY_VERSION_COMPRESSION_BIT)
#define REPLAY_ALLOC_INITIAL 256
@ -88,8 +91,10 @@ typedef struct ReplayStage {
// initial game settings
uint16_t stage; // must match type of StageInfo.id in stage.h
uint32_t seed; // this also happens to be the game initiation time, and we currently use this property
// NOTE: this might change eventually
/* BEGIN REPLAY_STRUCT_VERSION_TS103000_REV2 and above */
uint64_t start_time;
/* END REPLAY_STRUCT_VERSION_TS103000_REV2 and above */
uint64_t rng_seed; // NOTE: before REPLAY_STRUCT_VERSION_TS103000_REV2: uint32_t, also specifies start_time
uint8_t diff;
// initial player settings
@ -219,7 +224,7 @@ typedef enum ReplayStageFlags {
} ReplayStageFlags;
void replay_init(Replay *rpy);
ReplayStage* replay_create_stage(Replay *rpy, StageInfo *stage, uint64_t seed, Difficulty diff, Player *plr);
ReplayStage* replay_create_stage(Replay *rpy, StageInfo *stage, uint64_t start_time, uint64_t seed, Difficulty diff, Player *plr);
void replay_destroy(Replay *rpy);
void replay_destroy_events(Replay *rpy);

View file

@ -783,19 +783,22 @@ void stage_loop(StageInfo *stage) {
stage_preload();
stage_draw_init();
uint32_t seed = (uint32_t)time(0);
tsrand_switch(&global.rand_game);
tsrand_seed_p(&global.rand_game, seed);
stage_start(stage);
if(global.replaymode == REPLAY_RECORD) {
global.replay_stage = replay_create_stage(&global.replay, stage, seed, global.diff, &global.plr);
uint64_t start_time = (uint64_t)time(0);
uint64_t seed = makeseed();
tsrand_seed_p(&global.rand_game, seed);
global.replay_stage = replay_create_stage(&global.replay, stage, start_time, seed, global.diff, &global.plr);
// make sure our player state is consistent with what goes into the replay
player_init(&global.plr);
replay_stage_sync_player_state(global.replay_stage, &global.plr);
log_debug("Random seed: %u", seed);
log_debug("Start time: %"PRIu64, start_time);
log_debug("Random seed: 0x%"PRIx64, seed);
StageProgress *p = stage_get_progress_from_info(stage, global.diff, true);
@ -812,10 +815,11 @@ void stage_loop(StageInfo *stage) {
assert(stg != NULL);
assert(stage_get(stg->stage) == stage);
log_debug("REPLAY_PLAY mode: %d events, stage: \"%s\"", stg->numevents, stage->title);
tsrand_seed_p(&global.rand_game, stg->rng_seed);
tsrand_seed_p(&global.rand_game, stg->seed);
log_debug("Random seed: %u", stg->seed);
log_debug("REPLAY_PLAY mode: %d events, stage: \"%s\"", stg->numevents, stage->title);
log_debug("Start time: %"PRIu64, stg->start_time);
log_debug("Random seed: 0x%"PRIx64, stg->rng_seed);
global.diff = stg->diff;
player_init(&global.plr);

View file

@ -67,6 +67,14 @@ char* getenv();
attr_deprecated("Use env_set instead")
int setenv();
#undef rand
attr_deprecated("Use tsrand instead")
int rand(void);
#undef srand
attr_deprecated("Use tsrand_seed instead")
void srand(uint);
PRAGMA(GCC diagnostic pop)
#endif // IGUARD_util_consideredharmful_h