diff options
author | Vito Caputo <vcaputo@pengaru.com> | 2022-07-17 17:34:25 -0700 |
---|---|---|
committer | Vito Caputo <vcaputo@pengaru.com> | 2022-07-18 01:05:58 -0700 |
commit | 209b11f99c801141b79802cd6c23a2b568286f75 (patch) | |
tree | b8d03651c118bdbdcceb3ca1deca4696952d8afe | |
parent | b699465ca96070557feb9e69b898ddb0d4dca2dc (diff) |
til_args: add --seed= explicit PRNG seeding support
This enables reproducible yet pseudo-randomized visuals, at least
for the fully procedural modules.
The modules that are more simulation-y like sparkler and swarm
will still have runtime variations since they are dependent on
how much the simulation can run and there's been a lot of
sloppiness surrounding delta-t correctness and such.
But still, in a general sense, you'll find more or less similar
results even when doing randomized things like
module=rtv,channels=compose using the same seed value.
For the moment it only accepts a hexadecimal value, the leading
0x is optional.
e.g. these are all valid:
--seed=0xdeadbeef
--seed=0xdEAdBeFf
--seed=0x (produces 0)
--seed=0xff
--seed=deadbeef
--seed=ff
--seed= (produces 0)
--seed=0 (produces 0)
when you exceed the natural word size of an unsigned int on your
host architecture, an overflow error will be returned.
there are remaining issues to be fixed surrounding PRNG
reproducibility, in that things like til_module_randomize_setup()
doesn't currently accept a seed value. However it doesn't even
use rand_r() currently, but when it invokes desc->random() the
module's random() implementation should be able to use rand_r()
and needs to be fed the seed. So that all still needs wiring up
to propagate the root seed down everywhere it may be relevant.
-rw-r--r-- | src/main.c | 76 | ||||
-rw-r--r-- | src/til.c | 8 | ||||
-rw-r--r-- | src/til_args.c | 7 | ||||
-rw-r--r-- | src/til_args.h | 1 |
4 files changed, 81 insertions, 11 deletions
@@ -66,6 +66,7 @@ typedef struct setup_t { til_setup_t *module_setup; til_settings_t *video_settings; til_setup_t *video_setup; + unsigned seed; } setup_t; /* FIXME: this is unnecessarily copy-pasta, i think modules should just be made @@ -136,13 +137,84 @@ static int setup_video(til_settings_t *settings, til_setting_t **res_setting, co } +/* TODO: move to til.c */ +/* parse a hexadecimal seed with an optional leading 0x prefix into a libc srand()-appropriate machine-dependent sized unsigned int */ +/* returns -errno on any failure (including overflow), 0 on success. */ +static int parse_seed(const char *in, unsigned *res_seed) +{ + unsigned seed = 0; + + assert(in); + assert(res_seed); + + if (in[0] == '0' && (in[1] == 'x' || in[1] == 'X')) /* accept and ignore leading "0[xX]" */ + in += 2; + + for (int i = 0; *in && i < sizeof(*res_seed) * 2;) { + uint8_t h = 0; + + seed <<= 8; + + for (int j = 0; *in && j < 2; in++, j++, i++) { + h <<= 4; + + switch (*in) { + case '0'...'9': + h |= (*in) - '0'; + break; + + case 'a'...'f': + h |= (*in) - 'a' + 10; + break; + + case 'A'...'F': + h |= (*in) - 'A' + 10; + break; + + default: + return -EINVAL; + } + } + + seed |= h; + } + + if (*in) + return -EOVERFLOW; + + *res_seed = seed; + + return 0; +} + + +/* TODO: move to til.c, setup_t in general should just become til_setup_t. + * the sticking point is setup_interactively() is very rototiller-specific, so it needs + * to be turned into a caller-supplied callback or something. + */ /* turn args into settings, automatically applying defaults if appropriate, or interactively if appropriate. */ /* returns negative value on error, 0 when settings unchanged from args, 1 when changed */ /* on error, *res_failed_desc _may_ be assigned with something useful. */ static int setup_from_args(til_args_t *args, setup_t *res_setup, const til_setting_desc_t **res_failed_desc) { int r = -ENOMEM, changes = 0; - setup_t setup = {}; + setup_t setup = { .seed = time(NULL) + getpid() }; + + assert(args); + assert(res_setup); + + if (args->seed) { + r = parse_seed(args->seed, &setup.seed); + if (r < 0) + goto _err; + } + + /* FIXME TODO: this is gross! but we want to seed the PRNG before we do any actual setup + * in case we're randomizing settings. + * Maybe it makes more sense to just add a TIL_SEED env variable and let til_init() getenv("TIL_SEED") + * and do all this instead of setup_from_args(). This'll do for now. + */ + srand(setup.seed); setup.module_settings = til_settings_new(args->module); if (!setup.module_settings) @@ -296,7 +368,7 @@ int main(int argc, const char *argv[]) gettimeofday(&rototiller.start_tv, NULL); exit_if((r = til_module_create_context( - rototiller.module, 0, + rototiller.module, setup.seed, get_ticks(&rototiller.start_tv, &rototiller.start_tv, rototiller.ticks_offset), @@ -80,14 +80,6 @@ static const til_module_t *modules[] = { /* initialize rototiller (create rendering threads) */ int til_init(void) { - /* Various modules seed srand(), just do it here so they don't need to. - * At some point in the future this might become undesirable, if reproducible - * pseudo-randomized output is actually desirable. But that should probably be - * achieved using rand_r() anyways, since modules can't prevent others from playing - * with srand(). - */ - srand(time(NULL) + getpid()); - if (!(til_threads = til_threads_create())) return -errno; diff --git a/src/til_args.c b/src/til_args.c index e15150f..8969d89 100644 --- a/src/til_args.c +++ b/src/til_args.c @@ -9,7 +9,9 @@ * ./rototiller --video=drm,dev=/dev/dri/card3,connector=VGA-1,mode=640x480@60 * ./rototiller --video=sdl,size=640x480 * ./rototiller --module=roto,foo=bar,module=settings - * ./rototiller --defaults + * ./rototiller --defaults // use default settings where unspecified + * ./rototiller --go // don't show args and wait for user input before proceeding + * ./rototiller --seed=0xdeadbeef // explicitly set global random seed instead of generating one * * unrecognized arguments trigger an -EINVAL error, unless res_{argc,argv} are non-NULL * where a new argv will be allocated and populated with the otherwise invalid arguments @@ -44,6 +46,8 @@ static int args_parse(int argc, const char *argv[], til_args_t *res_args, int *r res_args->video = &argv[i][8]; } else if (!strncasecmp("--module=", argv[i], 9)) { res_args->module = &argv[i][9]; + } else if (!strncasecmp("--seed=", argv[i], 7)) { + res_args->seed = &argv[i][7]; } else if (!strcasecmp("--defaults", argv[i])) { res_args->use_defaults = 1; } else if (!strcasecmp("--help", argv[i])) { @@ -84,6 +88,7 @@ int til_args_help(FILE *out) " --go start rendering immediately upon fulfilling all required settings\n" " --help this help\n" " --module= module settings\n" + " --seed= seed to use for all PRNG in hexadecimal (e.g. 0xdeadbeef)\n" " --video= video settings\n" ); } diff --git a/src/til_args.h b/src/til_args.h index efe1e3c..d45b619 100644 --- a/src/til_args.h +++ b/src/til_args.h @@ -6,6 +6,7 @@ typedef struct til_args_t { const char *module; const char *video; + const char *seed; unsigned use_defaults:1; unsigned help:1; |