diff options
| author | eunhhu <[email protected]> | 2026-09-26 17:58:26 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-26 17:58:26 +0100 |
| commit | b466603358f8fa5a74551a9819fdd54ad124ef0c (patch) | |
| tree | 6f34265f30455589201222521c1a452274a0601d /src | |
| parent | 15c4616e19d997c64a983e39501289c7d9f50d0a (diff) | |
| download | vega-b466603358f8fa5a74551a9819fdd54ad124ef0c.tar.gz vega-b466603358f8fa5a74551a9819fdd54ad124ef0c.zip | |
Give live surfaces a clock and run timers; add a stopwatch
The terminal and the web page set the time events see to the wall clock
and tick an app's timers on time: each round ticks what is due, draws,
then waits for input, a frame or the next tick, and a tick is news to
every open page at once. Scripts have no clock: time MS sets the time
the next events see and tick TIMER ticks, so an app that uses time
replays exactly. A module swap reschedules the timers.
examples/stopwatch.aup runs on every surface; the checks drive it by
script, on a pseudo-terminal by the clock, and on the web page.
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.c | 34 | ||||
| -rw-r--r-- | src/screen.c | 29 | ||||
| -rw-r--r-- | src/vega.h | 10 | ||||
| -rw-r--r-- | src/web.c | 17 |
4 files changed, 82 insertions, 8 deletions
@@ -25,12 +25,13 @@ * written again replaces the running one and states keep their values by * name: aup watch app.aup -o app.pmod beside it makes a saved edit show at once. * Script commands, one per line: key KEYNAME | text HEX|- | type HEX | enter | - * tab | frame [N] | point VIEW ROW | click LINE COLUMN | swap MODULE | show. key presses and + * tab | frame [N] | point VIEW ROW | click LINE COLUMN | time MS | tick TIMER | swap MODULE | show. key presses and * releases the input bound to that key; text commits text to the screen's text * input; type, enter and tab act on the TUI's input line; point focuses a row * (0 first) of a list or table; click is a mouse click on the last shown TUI - * frame (1 first, as terminals report it); swap replaces the module as --watch - * does. */ + * frame (1 first, as terminals report it); time sets the time the next + * events see (live surfaces use the clock) and tick ticks a timer (live + * surfaces tick them on time); swap replaces the module as --watch does. */ static PureModule modules[2]; /* the running one (app.module) and the next */ static unsigned current; @@ -405,6 +406,8 @@ static int interactive(void) { signal(SIGWINCH, on_resize); VegaGrid grid = {0}; drawn = &grid; + VegaTimers timers; + vega_timers_start(&timers, app.module, vega_now()); for (;;) { if (resized) { struct winsize size; @@ -413,13 +416,19 @@ static int interactive(void) { vega_grid(&grid, size.ws_col, size.ws_row); else vega_grid(&grid, 80, 24); } + /* Tick what is due, draw, then wait for input, a frame or the next tick. */ + bool ticked = false; + long due = vega_timers_run(&timers, &app, vega_now(), &ticked); vega_draw(&grid, &app, &screen, entry, entry_length, typing); paint_terminal(&grid); struct pollfd wait = {STDIN_FILENO, POLLIN, 0}; - int ready = poll(&wait, 1, screen.ticks ? 16 : watched ? 200 : -1); + int timeout = screen.ticks ? 16 : watched ? 200 : -1; + if (due >= 0 && (timeout < 0 || due < timeout)) timeout = (int)due; + int ready = poll(&wait, 1, timeout); + app.now = vega_now(); if (ready == 0) { if (screen.ticks) pure_app_frame(&app); - module_changed(); + if (module_changed()) vega_timers_start(&timers, app.module, app.now); continue; } if (ready < 0) continue; /* a signal, such as a resize */ @@ -480,6 +489,21 @@ static int script(Surface surface, unsigned width, unsigned height, bool paintin if (surface != SURFACE_TUI) { fputs("vega: click needs the tui\n", stderr); continue; } acted = sscanf(line, "%*s %u %u", &at_line, &at_column) == 2 && at_line && shown; ok = acted && mouse(0, at_line - 1); + } else if (!strcmp(command, "time")) { + /* The time the next events see: scripts replay any. */ + char *end; + double now = strtod(argument, &end); + acted = false; + if (!*argument || *end || !(now >= 0 && now < 1e15)) fprintf(stderr, "vega: time takes milliseconds\n"); + else app.now = now; + } else if (!strcmp(command, "tick")) { + input = pure_module_input(app.module, argument); + verb = "tick"; + if (input < 0 || app.module->input[input].kind != PURE_KIND_TIMER) { + fprintf(stderr, "vega: no timer %s\n", argument); + input = -1; + ok = false; + } else ok = pure_app_tick(&app, (uint32_t)input) == PURE_OK; } else if (!strcmp(command, "swap")) { acted = false; if (!swap(argument, read_module(argument))) fprintf(stderr, "vega: kept the running module\n"); diff --git a/src/screen.c b/src/screen.c index 56c2cf9..fdecaf4 100644 --- a/src/screen.c +++ b/src/screen.c @@ -1,7 +1,36 @@ +#define _POSIX_C_SOURCE 200809L #include "vega.h" #include "module-text.h" #include <math.h> #include <string.h> +#include <time.h> + +double vega_now(void) { + struct timespec t; + clock_gettime(CLOCK_REALTIME, &t); + return floor((double)t.tv_sec * 1000 + (double)t.tv_nsec / 1e6); +} + +void vega_timers_start(VegaTimers *timers, const PureModule *m, double now) { + for (uint32_t i = 0; i < m->inputs; ++i) + timers->next[i] = m->input[i].kind == PURE_KIND_TIMER ? now + m->input[i].period : 0; +} + +long vega_timers_run(VegaTimers *timers, PureApp *app, double now, bool *ticked) { + const PureModule *m = app->module; + double soonest = -1; + for (uint32_t i = 0; i < m->inputs; ++i) { + if (m->input[i].kind != PURE_KIND_TIMER) continue; + if (timers->next[i] <= now) { + app->now = now; + pure_app_tick(app, i); /* a failed tick changes nothing, as any failed event */ + *ticked = true; + timers->next[i] = now + m->input[i].period; + } + if (soonest < 0 || timers->next[i] - now < soonest) soonest = timers->next[i] - now; + } + return soonest < 0 ? -1 : (long)ceil(soonest); +} static unsigned long evaluations; @@ -36,6 +36,16 @@ typedef struct { } VegaScreen; void vega_screen(VegaScreen *, const PureModule *); + +/* The wall-clock time events see on a live surface: milliseconds since 1970 + * (UTC). Scripts set their own (time MS). */ +double vega_now(void); +/* When each timer input ticks next. run ticks every one due at now (a tick + * sees now) and gives the milliseconds until the next, or -1 without timers; + * ticked says whether any did. Missed ticks are not made up. */ +typedef struct { double next[PURE_MODULE_INPUTS]; } VegaTimers; +void vega_timers_start(VegaTimers *, const PureModule *, double now); +long vega_timers_run(VegaTimers *, PureApp *, double now, bool *ticked); /* What a module seems to mean but does not meet: a list label that is not * text, a _focus view without its table, a column a list never shows, a * second text input. Each finding names the view or input it is about; @@ -382,7 +382,12 @@ int vega_web(PureApp *app, const VegaScreen *screen, const char *listen_on, unsi signal(SIGPIPE, SIG_IGN); static Client clients[CLIENTS]; for (unsigned i = 0; i < CLIENTS; ++i) clients[i].fd = -1; + VegaTimers timers; + vega_timers_start(&timers, app->module, vega_now()); while (!stopping) { + /* Tick what is due; every page hears of it this round, without waiting. */ + bool ticked = false; + long due = vega_timers_run(&timers, app, vega_now(), &ticked); struct pollfd fds[CLIENTS + 1]; unsigned slot[CLIENTS + 1], count = 1; fds[0] = (struct pollfd){listener, POLLIN, 0}; @@ -392,11 +397,17 @@ int vega_web(PureApp *app, const VegaScreen *screen, const char *listen_on, unsi fds[count] = (struct pollfd){c->fd, (short)(POLLIN | (c->out_at < c->out.used ? POLLOUT : 0)), 0}; slot[count++] = i; } - int ready = poll(fds, count, screen->ticks ? 33 : reload ? 200 : 1000); + int timeout = ticked ? 0 : screen->ticks ? 33 : reload ? 200 : 1000; + if (due >= 0 && due < timeout) timeout = (int)due; + int ready = poll(fds, count, timeout); if (ready < 0 && errno != EINTR) break; - bool changed = false; + bool changed = ticked; + app->now = vega_now(); /* what the events of this round see */ if (ready == 0 && screen->ticks) changed = pure_app_frame(app) == PURE_OK; - if (reload && reload()) changed = true; /* a new module: every page draws it */ + if (reload && reload()) { /* a new module: every page draws it */ + changed = true; + vega_timers_start(&timers, app->module, app->now); + } if (fds[0].revents & POLLIN) for (int fd; (fd = accept4(listener, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC)) >= 0;) { unsigned free_slot = CLIENTS; |
