From 14b35ecd216fda58873b94cd2b211986f2b20e02 Mon Sep 17 00:00:00 2001 From: Vito Caputo Date: Wed, 5 Jul 2023 16:46:54 -0700 Subject: modules/rkt: introduce a rudimentary scenes editor This adds a BBS-style interface for creating new scenes in a live rkt session. It listens on tcp port 54321 on localhost by default, just use telnet to connect, the rest is fairly self-explanatory. This is still early days, but it's a whole lot more than nothing. --- src/modules/rkt/Makefile.am | 2 +- src/modules/rkt/rkt.c | 103 +++- src/modules/rkt/rkt.h | 23 +- src/modules/rkt/rkt_scener.c | 1221 ++++++++++++++++++++++++++++++++++++++++++ src/modules/rkt/rkt_scener.h | 14 + 5 files changed, 1352 insertions(+), 11 deletions(-) create mode 100644 src/modules/rkt/rkt_scener.c create mode 100644 src/modules/rkt/rkt_scener.h diff --git a/src/modules/rkt/Makefile.am b/src/modules/rkt/Makefile.am index 03cebe6..fd820ab 100644 --- a/src/modules/rkt/Makefile.am +++ b/src/modules/rkt/Makefile.am @@ -1,3 +1,3 @@ noinst_LTLIBRARIES = librkt.la -librkt_la_SOURCES = rkt.c rkt.h +librkt_la_SOURCES = rkt.c rkt.h rkt_scener.h rkt_scener.c librkt_la_CPPFLAGS = -I@top_srcdir@/src -I@top_srcdir@/src/libs -I@top_srcdir@/src/libs/rocket/rocket/lib diff --git a/src/modules/rkt/rkt.c b/src/modules/rkt/rkt.c index ee7ac0e..d8bab4b 100644 --- a/src/modules/rkt/rkt.c +++ b/src/modules/rkt/rkt.c @@ -18,6 +18,7 @@ #include "txt/txt.h" #include "rkt.h" +#include "rkt_scener.h" /* Copyright (C) 2023 - Vito Caputo */ @@ -237,6 +238,10 @@ static til_module_context_t * rkt_create_context(const til_module_t *module, til rkt_update_rocket(ctxt, ticks); + /* are we running the scener too? */ + if (s->scener_listen) + rkt_scener_startup(ctxt); + return &ctxt->til_module_context; } @@ -245,6 +250,8 @@ static void rkt_destroy_context(til_module_context_t *context) { rkt_context_t *ctxt = (rkt_context_t *)context; + rkt_scener_shutdown(ctxt); + if (ctxt->sync_device) sync_destroy_device(ctxt->sync_device); @@ -260,17 +267,19 @@ static void rkt_render_fragment(til_module_context_t *context, til_stream_t *str rkt_context_t *ctxt = (rkt_context_t *)context; rkt_update_rocket(ctxt, ticks); + /* this is deliberately done before scener, so scener may override the scene shown */ + ctxt->scene = (unsigned)sync_get_val(ctxt->scene_track, ctxt->rocket_row); + rkt_scener_update(ctxt); /* this drives our per-rocket-track updates, with the tracks registered as owner_foo on the pipes, respectively */ til_stream_for_each_pipe(stream, rkt_pipe_update, ctxt); { - unsigned scene; + unsigned scene = ctxt->scene; - scene = (unsigned)sync_get_val(ctxt->scene_track, ctxt->rocket_row); if (scene < ctxt->n_scenes) { til_module_render(ctxt->scenes[scene].module_ctxt, stream, ticks, fragment_ptr); - } else if (scene == 99999 && !((rkt_setup_t *)context->setup)->connect) { + } else if (scene == 99999 && !((rkt_setup_t *)context->setup)->connect && !ctxt->scener) { /* 99999 is treated as an "end of sequence" scene, but only honored when connect=off (player mode) */ til_stream_end(stream); } else { @@ -317,7 +326,7 @@ static void rkt_setup_free(til_setup_t *setup) static int rkt_setup(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup) { const til_settings_t *scenes_settings; - const char *connect_values[] = { + const char *bool_values[] = { "off", "on", NULL @@ -329,6 +338,9 @@ static int rkt_setup(const til_settings_t *settings, til_setting_t **res_setting const char *connect; const char *host; const char *port; + const char *listen; + const char *listen_address; + const char *listen_port; int r; /* This is largely taken from compose::layers, but might just go away when I add tables to rocket, @@ -444,11 +456,11 @@ static int rkt_setup(const til_settings_t *settings, til_setting_t **res_setting r = til_settings_get_and_describe_value(settings, &(til_setting_spec_t){ - .name = "Editor connection toggle", + .name = "RocketEditor connection toggle", .key = "connect", /* TODO: regex */ - .preferred = connect_values[1], - .values = connect_values, + .preferred = bool_values[1], + .values = bool_values, .annotations = NULL, }, &connect, @@ -487,6 +499,51 @@ static int rkt_setup(const til_settings_t *settings, til_setting_t **res_setting return r; } + r = til_settings_get_and_describe_value(settings, + &(til_setting_spec_t){ + .name = "Scene editor listen toggle", + .key = "listen", + /* TODO: regex */ + .preferred = bool_values[1], + .values = bool_values, + .annotations = NULL, + }, + &listen, + res_setting, + res_desc); + if (r) + return r; + + if (!strcasecmp(listen, "on")) { + r = til_settings_get_and_describe_value(settings, + &(til_setting_spec_t){ + .name = "Listen address", + .key = "listen_address", + .preferred = RKT_SCENER_DEFAULT_ADDRESS, + /* TODO: regex */ + .annotations = NULL, + }, + &listen_address, + res_setting, + res_desc); + if (r) + return r; + + r = til_settings_get_and_describe_value(settings, + &(til_setting_spec_t){ + .name = "Listen port", + .key = "listen_port", + .preferred = TIL_SETTINGS_STR(RKT_SCENER_DEFAULT_PORT), + /* TODO: regex */ + .annotations = NULL, + }, + &listen_port, + res_setting, + res_desc); + if (r) + return r; + } + if (res_setup) { size_t n_scenes = til_settings_get_count(scenes_settings); til_setting_t *scene_setting; @@ -497,6 +554,38 @@ static int rkt_setup(const til_settings_t *settings, til_setting_t **res_setting if (!setup) return -ENOMEM; + if (!strcasecmp(listen, "on")) { + setup->scener_listen = 1; + + setup->scener_address = strdup(listen_address); + if (!setup->scener_address) { + til_setup_free(&setup->til_setup); + + return -ENOMEM; + } + + sscanf(listen_port, "%hu", &setup->scener_port); /* FIXME parse errors */ + + /* XXX FIXME TODO: HACK ALERT: til_settings_t probably needs to be refcounted, + * and this should be taking a proper reference! The only reason this can + * _remotely_ work today is rototiller doesn't free its settings until exiting, + * and rkt is HERMETIC - so all these should persist unless _rkt_ replaces them + * (like when editing). But that seems like a rather fragile way to be, and + * the act of distinguishing the baked til_setup_t from til_settings_t has been + * specifically in part to allow releasing the latter's resources once the setup + * is baked. But in rkt's case, at least in creative mode, it needs to allow + * live editing of the setup - which isn't possible on the baked til_setup_t, only + * the string-oriented til_settings_t. + * + * So what's happening here is a bit of an impedance mismatch, and for now I'm just + * going to cast these to non-const and get things more fleshed out before trying + * to change the til_settings API to better accomodate these new uses. + * XXX FIXME TODO + */ + setup->settings = (til_settings_t *)settings; + setup->scenes_settings = (til_settings_t *)scenes_settings; + } + setup->n_scenes = n_scenes; for (size_t i = 0; til_settings_get_value_by_idx(scenes_settings, i, &scene_setting); i++) { diff --git a/src/modules/rkt/rkt.h b/src/modules/rkt/rkt.h index 2c41631..b06c6a4 100644 --- a/src/modules/rkt/rkt.h +++ b/src/modules/rkt/rkt.h @@ -3,6 +3,9 @@ #include "til.h" #include "til_module_context.h" +#include "til_settings.h" + +typedef struct rkt_scener_t rkt_scener_t; typedef struct rkt_scene_t { til_module_context_t *module_ctxt; @@ -11,6 +14,7 @@ typedef struct rkt_scene_t { typedef struct rkt_context_t { til_module_context_t til_module_context; + rkt_scener_t *scener; struct sync_device *sync_device; const struct sync_track *scene_track; double rows_per_ms; @@ -19,20 +23,33 @@ typedef struct rkt_context_t { unsigned paused:1; size_t n_scenes; rkt_scene_t *scenes; + unsigned scene; /* current scene (usually driven by the scene track data, + * but scener may override it to force showing a specific scene) + */ } rkt_context_t; typedef struct rkt_setup_scene_t { const til_module_t *module; - til_setup_t *setup; /* Baked setup as-configured via setup. */ + til_setup_t *setup; /* Baked setup as-configured via setup. */ } rkt_setup_scene_t; typedef struct rkt_setup_t { til_setup_t til_setup; + til_settings_t *settings; /* Settings instance used to produce rkt's root setup, + * which rkt grabs a reference to for serializing its + * entirety "as args". The per-scene setups also grab + * reference to their respective settings instances, for + * editability within their levels of the rkt settings + * heirarchy. + */ + til_settings_t *scenes_settings; + const char *base; double rows_per_ms; unsigned connect:1; - const char *host; - unsigned short port; + unsigned scener_listen:1; + const char *host, *scener_address; + unsigned short port, scener_port; size_t n_scenes; rkt_setup_scene_t scenes[]; } rkt_setup_t; diff --git a/src/modules/rkt/rkt_scener.c b/src/modules/rkt/rkt_scener.c new file mode 100644 index 0000000..c317642 --- /dev/null +++ b/src/modules/rkt/rkt_scener.c @@ -0,0 +1,1221 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "til_str.h" +#include "til_stream.h" + +#include "rkt.h" +#include "rkt_scener.h" + +/* Copyright (C) 2023 - Vito Caputo */ + +/* This implements a rudimentary BBS-style interface for manipulating the scenes in ctxt->scenes. + * + * Only a single connection is supported at this time. It's really intended just to get _something_ + * cross-platform usable for editing the available scenes "live" at runtime in a minimum of time/effort. + * + * A more "modern" approach to this would be a HTTP REST API, yikes. + */ + +#define RKT_SCENER_DEFAULT_MODULE "compose" + +typedef enum rkt_scener_fsm_t { + RKT_SCENER_FSM_LISTENING, /* port is listening, waiting for connection */ + RKT_SCENER_FSM_SENDING, /* sending output */ + RKT_SCENER_FSM_RECVING, /* reading input */ + RKT_SCENER_FSM_SEND_SETTINGS, /* send rkt's settings hierarchy, including current scenes state, as args */ + RKT_SCENER_FSM_SEND_SCENES, /* send main scenes list -> prompt */ + RKT_SCENER_FSM_RECV_SCENES, /* waiting/reading at main scenes prompt */ + RKT_SCENER_FSM_SEND_NEWSCENE, /* send create new scene dialog -> prompt */ + RKT_SCENER_FSM_RECV_NEWSCENE, /* waiting/reading at the new scene prompt, creating/setting up new scene on input */ + RKT_SCENER_FSM_SEND_NEWSCENE_SETUP, /* send whatever's necessary for next step of new_scene.settings setup */ + RKT_SCENER_FSM_SEND_NEWSCENE_SETUP_PROMPT, + RKT_SCENER_FSM_RECV_NEWSCENE_SETUP, /* waiting/reading at new scene setup setting prompt, finalizing and adding when complete */ + RKT_SCENER_FSM_SEND_EDITSCENE, /* send edit scene dialog for scene @ scener->scene -> prompt */ + RKT_SCENER_FSM_RECV_EDITSCENE, /* waiting/reading at the edit scene prompt, editing scene on input */ +} rkt_scener_fsm_t; + +typedef struct rkt_scener_t { + rkt_scener_fsm_t state, next_state; + unsigned scene; + unsigned pin_scene:1; + int listener; + int client; + til_str_t *input; + til_str_t *output; + size_t output_pos; + + struct { + /* used while constructing a new scene, otherwise NULL */ + til_settings_t *settings; + til_setting_t *cur_setting; + const til_setting_desc_t *cur_desc; + til_setting_t *cur_invalid; + } new_scene; +} rkt_scener_t; + + +int rkt_scener_startup(rkt_context_t *ctxt) +{ + rkt_scener_t *scener; + rkt_setup_t *setup; + int on = 1; + struct sockaddr_in addr; + + assert(ctxt); + + setup = ((rkt_setup_t *)ctxt->til_module_context.setup); + + scener = calloc(1, sizeof(rkt_scener_t)); + if (!scener) + return -ENOMEM; + + scener->listener = socket(AF_INET, SOCK_STREAM, 0); + if (scener->listener == -1) + goto _err_free; + + if (setsockopt(scener->listener, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on)) == -1) + goto _err_close; + + addr.sin_family = AF_INET; + addr.sin_port = htons(setup->scener_port); + inet_aton(setup->scener_address, (struct in_addr *)&addr.sin_addr.s_addr); + + if (bind(scener->listener, (struct sockaddr *)&addr, sizeof(addr)) == -1) + goto _err_close; + + if (listen(scener->listener, 1) == -1) + goto _err_close; + + if (fcntl(scener->listener, F_SETFL, (int)O_NONBLOCK) == -1) + goto _err_close; + + scener->client = -1; + ctxt->scener = scener; + + return 0; + +_err_close: + close(scener->listener); +_err_free: + free(scener); + + return -errno; +} + + +/* helper for sending output, entering next_state once sent */ +static int rkt_scener_send(rkt_scener_t *scener, til_str_t *output, rkt_scener_fsm_t next_state) +{ + assert(scener); + assert(!scener->output); /* catch entering send mid-send (or leaking output) */ + assert(output); + assert(next_state != RKT_SCENER_FSM_SENDING); + + /* we generally send after processing input, so cleaning up for the input handlers here + * is ergonomic, enabling such callers to simply 'return rkt_scener_send(...);' + */ + scener->input = til_str_free(scener->input); + + scener->output_pos = 0; + scener->output = output; + scener->next_state = next_state; + scener->state = RKT_SCENER_FSM_SENDING; + + return 0; +} + + +/* helper for receiving input, entering next_state once received (a line of text) */ +static int rkt_scener_recv(rkt_scener_t *scener, rkt_scener_fsm_t next_state) +{ + assert(scener); + assert(!scener->input); + assert(next_state != RKT_SCENER_FSM_RECVING); + + scener->next_state = next_state; + scener->state = RKT_SCENER_FSM_RECVING; + + return 0; +} + + +/* helper for reentering the listening state and returning -errno, for hard errors */ +static int rkt_scener_err_close(rkt_scener_t *scener, int err) +{ + assert(scener); + + if (err > 0) + err = -err; + + scener->state = RKT_SCENER_FSM_LISTENING; + + return err; +} + + +/* helper for sending a minimal strerror(errno) style message to the user before entering next_state */ +static int rkt_scener_send_error(rkt_scener_t *scener, int error, rkt_scener_fsm_t next_state) +{ + til_str_t *output; + + assert(scener); + + /* TODO: this should really use a static allocated output buffer to try work under ENOMEM */ + output = til_str_newf("Error: %s\n", strerror(error)); + if (!output) + return -ENOMEM; + + return rkt_scener_send(scener, output, next_state); +} + + +/* helper for sending "invalid input" message w/scener->input incorporated */ +static int rkt_scener_send_invalid_input(rkt_scener_t *scener, rkt_scener_fsm_t next_state) +{ + til_str_t *output; + + assert(scener); + assert(scener->input); + + output = til_str_new("\nInvalid input: "); + if (!output) + return rkt_scener_err_close(scener, ENOMEM); + + if (til_str_appendf(output, "\"%s\"\n\n", til_str_buf(scener->input, NULL)) < 0) + return rkt_scener_err_close(scener, ENOMEM); + + scener->input = til_str_free(scener->input); + + return rkt_scener_send(scener, output, next_state); +} + + +/* helper for sending simple messages */ /* TODO: make variadic */ +static int rkt_scener_send_message(rkt_scener_t *scener, const char *msg, rkt_scener_fsm_t next_state) +{ + til_str_t *output; + + output = til_str_new(msg); + if (!output) + return rkt_scener_err_close(scener, ENOMEM); + + return rkt_scener_send(scener, output, next_state); +} + + +/* send welcome message */ +static int rkt_scener_send_welcome(rkt_scener_t *scener, rkt_scener_fsm_t next_state) +{ + return rkt_scener_send_message(scener, + "\n\nWelcome to scener.\n" + "\n\n Long live the scene!\n\n", + next_state); +} + + +/* send goodbye message */ +static int rkt_scener_send_goodbye(rkt_scener_t *scener, rkt_scener_fsm_t next_state) +{ + return rkt_scener_send_message(scener, + "\n\n The scene is dead.\n\n", + next_state); +} + + +/* handle input from the main scenes prompt */ +static int rkt_scener_handle_input_scenes(rkt_context_t *ctxt) +{ + rkt_scener_t *scener; + size_t len, i; + const char *buf; + + assert(ctxt); + scener = ctxt->scener; + assert(scener); + assert(scener->input); + + buf = til_str_buf(scener->input, &len); + + /* skip any leading whitespace XXX maybe move to a helper? */ + for (i = 0; i < len; i++) { + if (buf[i] != '\t' && buf[i] != ' ') + break; + } + + switch (buf[i]) { + case '0' ... '9': { /* edit scene, parse uint */ + unsigned s = 0; + + for (;i < len; i++) { + if (buf[i] < '0' || buf[i] > '9') + break; + + s *= 10; + s += buf[i] - '0'; + } + + /* XXX: maybe skip trailing whitespace too? */ + if (i < len) + return rkt_scener_send_invalid_input(scener, RKT_SCENER_FSM_SEND_SCENES); + + if (s >= ctxt->n_scenes) + return rkt_scener_send_invalid_input(scener, RKT_SCENER_FSM_SEND_SCENES); + + scener->scene = s; + scener->state = RKT_SCENER_FSM_SEND_EDITSCENE; + break; + } + + case 'N': /* Add new scene */ + case 'n': + scener->state = RKT_SCENER_FSM_SEND_NEWSCENE; + break; + + case 'S': /* Serialize current settings for the entire rkt scenes state */ + case 's': + scener->state = RKT_SCENER_FSM_SEND_SETTINGS; + break; + + case 'Q': /* End scener session; disconnects, but leaves rkt/rototiller intact */ + case 'q': + /* TODO: it might make sense to dump the serialized settings on quit just as a safety-net, + * or ask if an export is desired (assuming track data exports from scener becomes a thing) + */ + return rkt_scener_send_goodbye(scener, RKT_SCENER_FSM_LISTENING); + + case '!': /* toggle pin_scene */ + scener->pin_scene = !scener->pin_scene; + scener->state = RKT_SCENER_FSM_SEND_SCENES; + break; + + case '\0': /* if you don't say anything to even quote as "invalid input", just go back to the scenes dialog */ + scener->state = RKT_SCENER_FSM_SEND_SCENES; + break; + + default: + return rkt_scener_send_invalid_input(scener, RKT_SCENER_FSM_SEND_SCENES); + } + + /* XXX: note it's important the above non-invalid-input cases break and not return, + * so we discard the input. + */ + scener->input = til_str_free(scener->input); + + return 0; +} + + +static int rkt_scener_handle_input_newscene(rkt_context_t *ctxt) +{ + rkt_scener_t *scener; + til_settings_t *scenes_settings; + til_settings_t *new_settings; + const char *buf; + size_t len; + + assert(ctxt); + scener = ctxt->scener; + assert(scener); + assert(scener->input); + assert(!scener->new_scene.settings); + + scenes_settings = ((rkt_setup_t *)ctxt->til_module_context.setup)->scenes_settings; + + /* XXX: this !len exception is to treat "" exceptionally and not add a "" bare-value @ idx 0, + * which will be looked up as the module name. It would be nice to not need this, maybe + * if the invalid input handler could just be graceful enough this could be removed and + * allow the "" invalid module name to then fall-through into a module re-select with + * the empty name in the setting... TODO + */ + buf = til_str_buf(scener->input, &len); + if (!len) + buf = NULL; + + new_settings = til_settings_new(NULL, scenes_settings, "WIP-new-scene", buf); + if (!new_settings) + return rkt_scener_err_close(scener, ENOMEM); + + scener->input = til_str_free(scener->input); + scener->new_scene.settings = new_settings; + scener->new_scene.cur_setting = NULL; + scener->new_scene.cur_desc = NULL; + scener->new_scene.cur_invalid = NULL; + scener->state = RKT_SCENER_FSM_SEND_NEWSCENE_SETUP; + + return 0; +} + + +static int rkt_scener_handle_input_newscene_setup(rkt_context_t *ctxt) +{ + rkt_scener_t *scener; + til_setting_t *setting; + const til_setting_desc_t *desc; + const til_setting_t *invalid; + size_t len; + const char *buf; + const char *value; + + assert(ctxt); + scener = ctxt->scener; + assert(scener); + assert(scener->input); + assert(scener->new_scene.settings); + + setting = scener->new_scene.cur_setting; + desc = scener->new_scene.cur_desc; + invalid = scener->new_scene.cur_invalid; + + if (invalid && setting == invalid && !desc) + desc = invalid->desc; + + assert(desc); + + /* we're adding a setting, the input may be a raw string, or + * it might be a subscript of an array of values, it all depends on + * scener->new_scene.cur_desc + * + * there should probably be some optional syntax supported for forcing + * raw values in the event you want to go outside what's in the values, + * bypassing any spec check in the process, relying entirely on the + * setup function's robustness to detect invalid input, which is + * going to break sometimes no doubt. But the values[] arrays are + * quite limited in some cases where we've basically just given a + * handful of presets for something where really any old float value + * would work. Maybe what's really necessary there is to just have + * something in the spec that says "arbitrary_ok" despite having + * values[]. There's certainly some strict scenarios where the list has + * the only valid options (eg drizzle::style={mask,map}), so blocking + * raw input for those seems sensible. TODO + */ + buf = til_str_buf(scener->input, &len); + if (!len) { + value = desc->spec.preferred; + } else { + til_str_t *output; + + if (desc->spec.values) { + unsigned i, j, found; + + /* multiple choice, map numeric input to values entry */ + if (sscanf(buf, "%u", &j) < 1) { + output = til_str_newf("Invalid input: \"%s\"\n", buf); + if (!output) + return -ENOMEM; + + return rkt_scener_send(scener, output, RKT_SCENER_FSM_SEND_NEWSCENE_SETUP); + } + + for (found = i = 0; desc->spec.values[i]; i++) { + if (i == j) { + value = desc->spec.values[i]; + found = 1; + break; + } + } + + if (!found) { + output = til_str_newf("Invalid option: %u outside of range [0-%u]\n", j, i - 1); + if (!output) + return -ENOMEM; + + return rkt_scener_send(scener, output, RKT_SCENER_FSM_SEND_NEWSCENE_SETUP); + } + + } else { + /* use typed input as setting, TODO: apply regex */ + value = buf; + } + } + + /* we might be fixing an invalid setting instead of adding, determine that here */ + if (invalid && setting == invalid) { + /* TODO: add til_setting_set_value() ? */ + free((void *)setting->value); + setting->value = strdup(value); + scener->new_scene.cur_invalid = NULL; /* try again */ + } else if (!til_settings_add_value(desc->container, desc->spec.key, value)) + return -ENOMEM; + + scener->input = til_str_free(scener->input); + scener->state = RKT_SCENER_FSM_SEND_NEWSCENE_SETUP; + + return 0; +} + + + +/* randomize the settings for ctxt->scenes[scene], keeping its current module */ +static int rkt_scener_randomize_scene_settings(rkt_context_t *ctxt, unsigned scene_idx) +{ + const til_module_t *module; + rkt_scener_t *scener; + rkt_scene_t *scene; + til_settings_t *scenes_settings; + til_setting_t *scene_setting; + til_settings_t *new_settings; + til_setup_t *setup; + char *label; + int r; + + assert(ctxt); + scener = ctxt->scener; + assert(scener); + + assert(scene_idx < ctxt->n_scenes); + + scenes_settings = ((rkt_setup_t *)ctxt->til_module_context.setup)->scenes_settings; + scene = &ctxt->scenes[scene_idx]; + module = scene->module_ctxt->module, + assert(module); + + til_settings_get_value_by_idx(scenes_settings, scene_idx, &scene_setting); + /* FIXME: this is all rather janky TODO clean up the api for these uses, helpers, whatever */ + r = til_settings_label_setting(scenes_settings, scene_setting, &label); + if (r < 0) + return r; + + new_settings = til_settings_new(NULL, scenes_settings, label, module->name); + free(label); + if (!new_settings) + return -ENOMEM; + + /* FIXME: seed reproducibility needs to be sorted out, maybe move seed into settings */ + til_module_setup_randomize(module, + new_settings, + rand_r(&ctxt->til_module_context.seed), + &setup, + NULL); /* TODO: errors! */ + + scene_setting->value_as_nested_settings = new_settings; +#if 0 + free((void *)scene_setting->value); + scene_setting->value = as_arg; /* XXX should I really overrite the original bare-value? maybe + * preserve the ability to go back to what it was by leaving + * this alone? printing the settings_as_arg ignores this anyways, + * when there's a non-NULL value_as_nested_settings... + * Maybe once scene editing gets implemented, this may become + * more of an issue with an obvious Right Way. + */ +#endif + scene->module_ctxt = til_module_context_free(scene->module_ctxt); + (void) til_module_create_context(module, + ctxt->til_module_context.stream, + rand_r(&ctxt->til_module_context.seed), + ctxt->til_module_context.last_ticks, + ctxt->til_module_context.n_cpus, + setup, + &scene->module_ctxt); /* TODO: errors */ + til_setup_free(setup); + + /* This will probably get more complicated once rkt starts getting more active about + * creating and destroying scene contexts only while they're in use. But today all + * contexts persist for the duration, so they always have a reference as long as they're + * extant in ctxt->scenes[]... + */ + til_stream_gc_module_contexts(ctxt->til_module_context.stream); + + return 0; +} + + +static int rkt_scener_handle_input_editscene(rkt_context_t *ctxt) +{ + rkt_scener_t *scener; + size_t len, i; + const char *buf; + + assert(ctxt); + scener = ctxt->scener; + assert(scener); + assert(scener->input); + + buf = til_str_buf(scener->input, &len); + + /* skip any leading whitespace XXX maybe move to a helper? */ + for (i = 0; i < len; i++) { + if (buf[i] != '\t' && buf[i] != ' ') + break; + } + + switch (buf[i]) { + case '0' ... '9': { /* edit scene */ + unsigned s = 0; + + /* XXX: move to function? */ + for (;i < len; i++) { + if (buf[i] < '0' || buf[i] > '9') + break; + + s *= 10; + s += buf[i] - '0'; + } + + /* XXX: maybe skip trailing whitespace too? */ + if (i < len) + return rkt_scener_send_invalid_input(scener, RKT_SCENER_FSM_SEND_EDITSCENE); + + if (s >= ctxt->n_scenes) + return rkt_scener_send_invalid_input(scener, RKT_SCENER_FSM_SEND_EDITSCENE); + + scener->scene = s; + scener->state = RKT_SCENER_FSM_SEND_EDITSCENE; + break; + } + + case 'E': /* edit scene settings */ + case 'e': + /* TODO run through a setup cycle seeded with current settings */ + /* so editing is very similar to creating a new scene... + * but the current scene's as_args are used to seed the create, + * instead of leaving it blank as in NEWSCENE. + * + * The main reason I haven't already done this is I want to have the + * input as_args settings show up as raw input at the initial New scene + * prompt, in a way that the user can then arrow-key/backspace over and + * change things in the same manner as if they has copy and pasted it into + * a text editor. But I'm not sure I can achieve that without going full ANSI + * BBS control codes up in here... still investigating. TODO + */ + return rkt_scener_send_message(scener, + "\n\nScene editing not _yet_ implemented, create a new one?\n" + "\n" + "Keep in mind, it's the Rocket 'scene' track that governs scene ordering/visibility,\n" + "think of these as more like Samples/Instruments in a tracker.\n" + "So you can relatively harmlessly have unused scenes in here.\n" + "\n" + "A temporary way to edit is copy and paste the settings shown above into a text editor,\n" + "make your tweaks (you can also just delete parts you want to revisit interactive setup for),\n" + "then paste it into the initial New scene raw input prompt (*discard* the quotes!).\n" + "\n" + "One advantage to this approach is you'll always have the old scene's settings for a retry.\n" + "\n", + RKT_SCENER_FSM_SEND_EDITSCENE); + + case 'R': /* randomize only the settings (keep existing module) */ + case 'r': { + int r; + + r = rkt_scener_randomize_scene_settings(ctxt, scener->scene); + if (r < 0) + return rkt_scener_send_error(scener, r, RKT_SCENER_FSM_SEND_EDITSCENE); + + scener->state = RKT_SCENER_FSM_SEND_EDITSCENE; + break; + } + + case 'N': + case 'n': + scener->state = RKT_SCENER_FSM_SEND_NEWSCENE; + break; + + case '!': /* toggle pin_scene */ + scener->pin_scene = !scener->pin_scene; + scener->state = RKT_SCENER_FSM_SEND_EDITSCENE; + break; + + case '\0': /* if you don't say anything to even quote as "invalid input", just go back to the scenes dialog */ + scener->state = RKT_SCENER_FSM_SEND_SCENES; + break; + + default: + return rkt_scener_send_invalid_input(scener, RKT_SCENER_FSM_SEND_EDITSCENE); + } + + /* XXX: note it's important the above non-invalid-input cases break and not return, + * so we discard the input. + */ + scener->input = til_str_free(scener->input); + + return 0; +} + + +/* The architecture here is kept simple; just one client is supported, the sockets are + * all put in non-blocking mode, so we just poll for accepts on the listener when not + * connected (listening), or poll for send buffer availability when sending, or poll for + * recv bytes when receiving. We're only doing one of those at a time WRT IO, per-update. + * + * When something is to be sent, it gets buffered entirely and placed in scener->output + * before entering a generic sending state which persists until the output is + * all sent. Once that happens, the queued "next state" gets entered. Since we're not going + * to be sending big binary streams of filesystem data, this is fine; it's basically a BBS UI. + * + * When something needs to be received, a "next state" is queued and the generic receiving + * state entered. The receiving state persists receiving bytes until a newline byte is + * received, making this fundamentally line-oriented. The received line is buffered and left + * in scener->input, for the queued "next state" to handle, which is transitioned to @ newline. + * Yes, this means scener will consume all memory if you just keep sending non-newline data to + * it, this isn't a public tcp service, it's a tool just for you to use - the safety guards are + * minimal if present at all. + * + * That's basically all there is... error situations are handled by reentering the listening + * state which will first close the client fd if valid, before resuming polling the listener fd + * w/accept. + * + * This update function is expected to be called regularly by rkt, probably every frame. It's + * kept in this "dumb polling" single-threaded synchronous fashion deliberately so scener can be + * relatively unconcerned about mucking with the scenes state and any other rkt state without + * introducing locks or other synchronization complexities. It's not the greatest perf-wise, but + * keep in mind this is strictly for the creative process, you wouldn't have this enabled when + * running a finished prod in anything resembling release mode. + * + * The return value is -errno on errors, 0 on "everything's normal". Some attempt + * has been made to be resilient to errors in that many -errno paths will just return + * the state to "listening" and may just recover. I'm not sure right now what rkt will + * do with the errors coming out of this function, for now they'll surely just be silently + * ignored... but at some point it might be useful to print something to stderr about their + * nature at least, in rkt that is, not here. + */ + +int rkt_scener_update(rkt_context_t *ctxt) +{ + rkt_scener_t *scener; + unsigned ctxt_scene; + + assert(ctxt); + + if (!ctxt->scener) + return 0; + + scener = ctxt->scener; + + ctxt_scene = ctxt->scene; + if (scener->pin_scene) + ctxt->scene = scener->scene; + + switch (scener->state) { + case RKT_SCENER_FSM_LISTENING: { + int fd, on = 1; + + /* any state can just resume listening anytime, which will close and free things */ + if (scener->client != -1) { + close(scener->client); + scener->client = -1; + } + + scener->output = til_str_free(scener->output); + scener->input = til_str_free(scener->input); + scener->new_scene.settings = til_settings_free(scener->new_scene.settings); + + fd = accept(scener->listener, NULL, NULL); + if (fd == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return 0; + + return -errno; + } + + if (fcntl(fd, F_SETFL, (int)O_NONBLOCK) == -1) { + close(fd); + return -errno; + } + + scener->client = fd; + (void) setsockopt(scener->client, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on)); + + return rkt_scener_send_welcome(scener, RKT_SCENER_FSM_SEND_SCENES); + } + + case RKT_SCENER_FSM_SENDING: { + const char *buf; + size_t len; + ssize_t ret; + + buf = til_str_buf(scener->output, &len); + + assert(scener->output_pos < len); + + ret = send(scener->client, &buf[scener->output_pos], len - scener->output_pos, MSG_NOSIGNAL); + if (ret == -1) { + if (errno == EAGAIN || EWOULDBLOCK) + return 0; + + return rkt_scener_err_close(scener, errno); + } + + scener->output_pos += ret; + if (scener->output_pos < len) + return 0; + + scener->output = til_str_free(scener->output); + scener->state = scener->next_state; + + return 0; + } + + case RKT_SCENER_FSM_RECVING: { + char b[2] = {}; + + for (;;) { + /* keep accumulating input until a newline, then transition to next_state */ + switch (recv(scener->client, &b, 1, 0)) { + case -1: + if (errno == EWOULDBLOCK || errno == EAGAIN) + return 0; + + return rkt_scener_err_close(scener, errno); + + case 0: /* client shutdown before finding \n, return to listening (discard partial input) */ + return rkt_scener_err_close(scener, 0); + + case 1: /* add byte to input buffer, transition on newline (TODO: one-byte recv()s are _slow_) */ + if (!scener->input) { + scener->input = til_str_new(b); + if (!scener->input) + return rkt_scener_err_close(scener, ENOMEM); + } else { + if (til_str_appendf(scener->input, "%c", b[0]) < 0) + return rkt_scener_err_close(scener, ENOMEM); + } + + if (b[0] == '\n') { /* TODO: maybe support escaping the newline for multiline input? */ + /* before transitioning. let's strip off the line delimiter. + * it's effectively encapsulation protocol and not part of the input buffer + */ + scener->input = til_str_chomp(scener->input); + scener->state = scener->next_state; + } + + return 0; + + default: + assert(0); + } + } + } + + case RKT_SCENER_FSM_SEND_SETTINGS: { + til_str_t *output; + char *as_arg; + + as_arg = til_settings_as_arg(((rkt_setup_t *)ctxt->til_module_context.setup)->settings); + if (!as_arg) + return rkt_scener_err_close(scener, ENOMEM); + + output = til_str_newf("\n--module='%s'\n", as_arg); + free(as_arg); + if (!output) + return rkt_scener_err_close(scener, ENOMEM); + + return rkt_scener_send(scener, output, RKT_SCENER_FSM_SEND_SCENES); + } + + case RKT_SCENER_FSM_SEND_SCENES: { + til_settings_t *scenes_settings; + til_str_t *output; + unsigned i; + + output = til_str_new("\n\n"); + if (!output) + return rkt_scener_err_close(scener, ENOMEM); + + scenes_settings = ((rkt_setup_t *)ctxt->til_module_context.setup)->scenes_settings; + if (til_settings_strprint_path(scenes_settings, output) < 0) + return rkt_scener_err_close(scener, ENOMEM); + + if (til_str_appendf(output, "/scenes:\n\n") < 0) + return rkt_scener_err_close(scener, ENOMEM); + + if (til_str_appendf(output, " +- Rocket\n" + " |+- Scener\n" + " ||+- Pinned by scener\n" + " |||\n") < 0) + return rkt_scener_err_close(scener, ENOMEM); + + for (i = 0; i < ctxt->n_scenes; i++) { + if (til_str_appendf(output, + " %c%c%c%s\n", + ctxt_scene == i ? '*' : ' ', + scener->scene == i ? '*' : ' ', + (scener->scene == i && scener->pin_scene) ? '!' : ' ', + ctxt->scenes[i].module_ctxt->setup->path) < 0) + return rkt_scener_err_close(scener, ENOMEM); + } + + if (til_str_appendf(output, "\n") < 0) + return rkt_scener_err_close(scener, ENOMEM); + + if (i) { + if (til_str_appendf(output, " [0-%u]", i - 1) < 0) + return rkt_scener_err_close(scener, ENOMEM); + } + + if (til_str_appendf(output, + " (N)ewScene (S)howSettings %s (Q)uit: ", + scener->pin_scene ? "Unpin(!)" : "Pin(!)") < 0) + return rkt_scener_err_close(scener, ENOMEM); + + return rkt_scener_send(scener, output, RKT_SCENER_FSM_RECV_SCENES); + } + + case RKT_SCENER_FSM_RECV_SCENES: + if (!scener->input) + return rkt_scener_recv(scener, scener->state); + + return rkt_scener_handle_input_scenes(ctxt); + + case RKT_SCENER_FSM_SEND_NEWSCENE: { + til_str_t *output; + + output = til_str_new("Input new scene \"module[,settings...]\" :\n"); + if (!output) + return rkt_scener_err_close(scener, ENOMEM); + + return rkt_scener_send(scener, output, RKT_SCENER_FSM_RECV_NEWSCENE); + } + + case RKT_SCENER_FSM_RECV_NEWSCENE: + if (!scener->input) + return rkt_scener_recv(scener, scener->state); + + return rkt_scener_handle_input_newscene(ctxt); + + case RKT_SCENER_FSM_SEND_NEWSCENE_SETUP: { + til_str_t *output; + int r; + + r = til_module_setup(scener->new_scene.settings, + &scener->new_scene.cur_setting, + &scener->new_scene.cur_desc, + NULL); /* res_setup deliberately left NULL for two reasons: + * 1. prevents finalizing (path is "...WIP-new-scene...") + * 2. disambiguates -EINVAL errors from those potentially + * returned while finalizing/baking into a til_setup_t. + * + * This way if such an error is returned, we can expect + * res_setting and res_desc to refer to /what's/ invalid, + * and correct it before retrying. + * + * It'd be rather obnoxious with how the setup funcs are + * implemented currently to try force returning the relevant + * setting+desc for errors found during baking... I still + * need to firm up and document setup_func error semantics, and + * ideally it would be nice if the finalizing could say what + * setting/desc is relevant when say.. doing an atoi(). + * That will require a bit of churn in the settings get_value + * API and changing all the setup_funcs to be setting-centric + * instead of the current char* value-centric mode TODO + */ + if (r < 0) { + /* something went wrong... */ + if (r != -EINVAL) /* just give up on everything non-EINVAL */ + return rkt_scener_err_close(scener, r); + + /* Invalid setting! go back to prompting for input */ + scener->new_scene.cur_invalid = scener->new_scene.cur_setting; + + return rkt_scener_send_error(scener, EINVAL, RKT_SCENER_FSM_SEND_NEWSCENE_SETUP_PROMPT); + + } else if (r > 0) { + til_setting_t *setting = scener->new_scene.cur_setting; + const til_setting_desc_t *desc = scener->new_scene.cur_desc; + til_setting_t *invalid = scener->new_scene.cur_invalid; + + assert(desc); + + if (setting && setting != invalid && !setting->desc) { + /* Apply override before, or after the spec_check()? unclear. + * TODO This probably also needs to move into a til_settings helper + */ + if (desc->spec.override) { + const char *o; + + o = desc->spec.override(setting->value); + if (!o) + return rkt_scener_err_close(scener, ENOMEM); + + if (o != setting->value) { + free((void *)setting->value); + setting->value = o; + } + } + + if (til_setting_spec_check(&desc->spec, setting->value) < 0) { + /* setting invalid! go back to prompting for input */ + scener->new_scene.cur_invalid = setting; + + return rkt_scener_send_error(scener, EINVAL, RKT_SCENER_FSM_SEND_NEWSCENE_SETUP_PROMPT); + } + + if (desc->spec.as_nested_settings && !setting->value_as_nested_settings) { + char *label = NULL; + + if (!desc->spec.key) { + /* generate a positional label for bare-value specs */ + r = til_settings_label_setting(desc->container, setting, &label); + if (r < 0) + return rkt_scener_err_close(scener, r); + } + + setting->value_as_nested_settings = til_settings_new(NULL, desc->container, desc->spec.key ? : label, setting->value); + free(label); + + if (!setting->value_as_nested_settings) { + /* FIXME: til_settings_new() seems like it should return an errno, since it can encounter parse errors too? */ + return rkt_scener_err_close(scener, ENOMEM); + }; + } + + setting->desc = desc; + + /* setting OK and described, stay in this state and do more setup */ + return 0; + } + + /* More settings needed! go back to prompting for input */ + scener->state = RKT_SCENER_FSM_SEND_NEWSCENE_SETUP_PROMPT; + + return 0; + } + + /* new_scene.settings appears to be complete, but it still needs to be finalized. + * Before that can happen scenes_settings need to be enlarged, then new_settings + * can get added under the new entry. At that point a proper path would result, + * so finalizing may proceed. + */ + + /* these *may* move into helpers, so scoping them as ad-hoc unnamed functions for now */ + { /* expand scenes settings */ + til_settings_t *scenes_settings; + til_setting_t *scene_setting; + char *label, *as_arg; + int r; + + /* now that we know settings is completely valid, expand scenes and get everything wired up */ + as_arg = til_settings_as_arg(scener->new_scene.settings); + if (!as_arg) + return rkt_scener_err_close(scener, ENOMEM); + + scenes_settings = ((rkt_setup_t *)ctxt->til_module_context.setup)->scenes_settings; + scene_setting = til_settings_add_value(scenes_settings, NULL, as_arg); + if (!scene_setting) + return rkt_scener_err_close(scener, ENOMEM); + + r = til_setting_desc_new(scenes_settings, + &(til_setting_spec_t){ + .as_nested_settings = 1, + }, + &scene_setting->desc); + if (r < 0) /* FIXME TODO we should probably drop the half-added value??? */ + return rkt_scener_err_close(scener, r); + + r = til_settings_label_setting(scenes_settings, scene_setting, &label); + if (r < 0) /* FIXME TODO we should probably drop the half-added value??? */ + return rkt_scener_err_close(scener, r); + + r = til_settings_set_label(scener->new_scene.settings, label); + free(label); + if (r < 0) /* FIXME TODO we should probably drop the half-added value??? */ + return rkt_scener_err_close(scener, r); + + scene_setting->value_as_nested_settings = scener->new_scene.settings; + } + + { /* expand context scenes, finalize setup, create context */ + const char *module_name; + const til_module_t *module; + til_module_context_t *module_ctxt; + til_setup_t *setup; + rkt_scene_t *new_scenes; + int r; + + /* Still haven't baked the setup. + * At this point the new scene's settings are inserted, + * and it's time to bake the setup and create the context, + * adding the rkt_scene_t instance corresponding to the settings. + */ + module_name = til_settings_get_value_by_idx(scener->new_scene.settings, 0, NULL); + if (!module_name) /* FIXME TODO we should probably un-add the scene from scenes_settings??? */ + return rkt_scener_err_close(scener, EINVAL); /* this really shouldn't happen */ + + module = til_lookup_module(module_name); + if (!module) /* FIXME TODO we should probably un-add the scene from scenes_settings??? */ + return rkt_scener_err_close(scener, EINVAL); /* this really shouldn't happen */ + + r = til_module_setup_finalize(module, scener->new_scene.settings, &setup); + if (r < 0) /* FIXME TODO we should probably un-add the scene from scenes_settings??? */ + return rkt_scener_err_close(scener, r); + + /* have baked setup @ setup, create context using it */ + r = til_module_create_context(module, + ctxt->til_module_context.stream, + rand_r(&ctxt->til_module_context.seed), /* FIXME TODO seeds need work (make reproducible) */ + ctxt->til_module_context.last_ticks, + ctxt->til_module_context.n_cpus, + setup, + &module_ctxt); + til_setup_free(setup); + if (r < 0) + return rkt_scener_err_close(scener, r); + + /* have context, almost there, enlarge scener->scenes, bump scener->n_scenes */ + new_scenes = realloc(ctxt->scenes, (ctxt->n_scenes + 1) * sizeof(*new_scenes)); + if (!new_scenes) { + til_module_context_free(module_ctxt); + + return rkt_scener_err_close(scener, r); + } + + new_scenes[ctxt->n_scenes].module_ctxt = module_ctxt; + ctxt->scenes = new_scenes; + ctxt->n_scenes++; + + /* added! */ + + scener->scene = ctxt->n_scenes - 1; + } + + scener->new_scene.settings = NULL; + + output = til_str_new("\n\nNew scene added successfully...\n"); + if (!output) + return rkt_scener_err_close(scener, ENOMEM); + + return rkt_scener_send(scener, output, RKT_SCENER_FSM_SEND_SCENES); + } + + case RKT_SCENER_FSM_SEND_NEWSCENE_SETUP_PROMPT: { + til_setting_t *setting = scener->new_scene.cur_setting; + const til_setting_desc_t *desc = scener->new_scene.cur_desc; + til_setting_t *invalid = scener->new_scene.cur_invalid; + til_str_t *output; + + if (invalid && setting == invalid && !desc) + desc = invalid->desc; + + assert(desc); + + /* construct a prompt based on cur_desc + * + * this was derived from setup_interactively(), but til_str_t-centric and + * decomposed further for the scener fsm integration. + */ + output = til_str_newf("\n", desc->spec.key); + if (!output) + return rkt_scener_err_close(scener, ENOMEM); + + if (desc->spec.values) { + unsigned i, preferred = 0; + int width = 0; + + for (i = 0; desc->spec.values[i]; i++) { + int len; + + len = strlen(desc->spec.values[i]); + if (len > width) + width = len; + } + + /* multiple choice */ + if (til_setting_desc_strprint_path(desc, output) < 0) + return rkt_scener_err_close(scener, ENOMEM); + + if (til_str_appendf(output, ":\n %s:\n", desc->spec.name) < 0) + return rkt_scener_err_close(scener, ENOMEM); + + for (i = 0; desc->spec.values[i]; i++) { + if (til_str_appendf(output, " %2u: %*s%s%s\n", i, width, desc->spec.values[i], + desc->spec.annotations ? ": " : "", + desc->spec.annotations ? desc->spec.annotations[i] : "") < 0) + return rkt_scener_err_close(scener, ENOMEM); + + if (!strcasecmp(desc->spec.preferred, desc->spec.values[i])) + preferred = i; + } + + if (til_str_appendf(output, " Enter a value 0-%u [%u (%s)]: ", + i - 1, preferred, desc->spec.preferred) < 0) + return rkt_scener_err_close(scener, ENOMEM); + + } else { + /* arbitrarily typed input */ + if (til_setting_desc_strprint_path(desc, output) < 0) + return rkt_scener_err_close(scener, ENOMEM); + + if (til_str_appendf(output, ":\n %s [%s]: ", desc->spec.name, desc->spec.preferred) < 0) + return rkt_scener_err_close(scener, ENOMEM); + } + + return rkt_scener_send(scener, output, RKT_SCENER_FSM_RECV_NEWSCENE_SETUP); + } + + case RKT_SCENER_FSM_RECV_NEWSCENE_SETUP: + if (!scener->input) + return rkt_scener_recv(scener, scener->state); + + return rkt_scener_handle_input_newscene_setup(ctxt); + + case RKT_SCENER_FSM_SEND_EDITSCENE: { + til_settings_t *scenes_settings = ((rkt_setup_t *)ctxt->til_module_context.setup)->scenes_settings; + til_setting_t *scene_setting; + char *as_arg; + til_str_t *output; + + if (!til_settings_get_value_by_idx(scenes_settings, scener->scene, &scene_setting)) + return rkt_scener_err_close(scener, ENOENT); + + as_arg = til_settings_as_arg(scene_setting->value_as_nested_settings); + if (!as_arg) + return rkt_scener_err_close(scener, ENOMEM); + + output = til_str_newf("\n" + "%s:\n\n" + " Visible: %s\n" + " Pinned: %s\n" + " Settings: \'%s\'\n" + "\n" + " (E)dit (R)andomizeSetup (N)ewScene %s: ", + ctxt->scenes[scener->scene].module_ctxt->setup->path, + scener->pin_scene ? "YES" : (ctxt_scene == scener->scene ? "YES" : "NO, PIN TO FORCE"), + scener->pin_scene ? "YES, (!) to UNPIN" : "NO, (!) TO PIN", + as_arg, + scener->pin_scene ? "Unpin(!)" : "Pin(!)"); + free(as_arg); + if (!output) + return rkt_scener_err_close(scener, ENOMEM); + + return rkt_scener_send(scener, output, RKT_SCENER_FSM_RECV_EDITSCENE); + } + + case RKT_SCENER_FSM_RECV_EDITSCENE: + if (!scener->input) + return rkt_scener_recv(scener, scener->state); + + return rkt_scener_handle_input_editscene(ctxt); + + default: + assert(0); + } + + return 0; +} + + +int rkt_scener_shutdown(rkt_context_t *ctxt) +{ + assert(ctxt); + + if (!ctxt->scener) + return 0; + + close(ctxt->scener->listener); + + if (ctxt->scener->client != -1) + close(ctxt->scener->client); + + til_str_free(ctxt->scener->input); + til_str_free(ctxt->scener->output); + + free(ctxt->scener); + ctxt->scener = NULL; + + return 0; +} diff --git a/src/modules/rkt/rkt_scener.h b/src/modules/rkt/rkt_scener.h new file mode 100644 index 0000000..ad21f0a --- /dev/null +++ b/src/modules/rkt/rkt_scener.h @@ -0,0 +1,14 @@ +#ifndef _RKT_SCENER_H +#define _RKT_SCENER_H + +/* these are made public for rkt's setup func */ +#define RKT_SCENER_DEFAULT_PORT 54321 +#define RKT_SCENER_DEFAULT_ADDRESS "127.0.0.1" + +typedef struct rkt_context_t rkt_context_t; + +int rkt_scener_startup(rkt_context_t *ctxt); +int rkt_scener_update(rkt_context_t *ctxt); +int rkt_scener_shutdown(rkt_context_t *ctxt); + +#endif -- cgit v1.2.3