diff options
| -rw-r--r-- | Makefile | 5 | ||||
| -rw-r--r-- | surfaces/main.c | 10 | ||||
| -rw-r--r-- | surfaces/screen.c | 20 | ||||
| -rw-r--r-- | surfaces/vega.h | 14 | ||||
| -rw-r--r-- | surfaces/web.c | 6 | ||||
| -rw-r--r-- | tests/check.py | 43 |
6 files changed, 76 insertions, 22 deletions
@@ -99,5 +99,6 @@ check-text: $(BUILD)/vega-symbols $(BUILD)/test-text $(BUILD)/test-layout check- check: $(BUILD)/vega $(BUILD)/vega-surface.so check-text $(MAKE) -C $(AUP) build/aup - $(MAKE) -C $(PURE) build/pure-serve - python3 tests/check.py --vega $(BUILD)/vega --symbols $(BUILD)/vega-symbols --aup $(AUP)/build/aup --serve $(PURE)/build/pure-serve + $(MAKE) -C $(PURE) build/pure-serve build/clock-back.so + python3 tests/check.py --vega $(BUILD)/vega --symbols $(BUILD)/vega-symbols --aup $(AUP)/build/aup --serve $(PURE)/build/pure-serve \ + --clock-back $(PURE)/build/clock-back.so diff --git a/surfaces/main.c b/surfaces/main.c index 6c7e368..70958e1 100644 --- a/surfaces/main.c +++ b/surfaces/main.c @@ -483,7 +483,7 @@ static int interactive(void) { VegaGrid grid = {0}; drawn = &grid; VegaTimers timers; - vega_timers_start(&timers, app.module, vega_now()); + vega_timers_start(&timers, app.module, vega_clock()); for (;;) { if (resized) { struct winsize size; @@ -494,7 +494,7 @@ static int interactive(void) { } /* 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); + long due = vega_timers_run(&timers, &app, vega_clock(), &ticked); char line[VEGA_TEXT + 4]; vega_draw(&grid, &app, &screen, line, shown_entry(line), typing); paint_terminal(&grid); @@ -505,7 +505,7 @@ static int interactive(void) { app.now = vega_now(); if (ready == 0) { if (screen.ticks) pure_app_frame(&app); - if (module_changed()) vega_timers_start(&timers, app.module, app.now); + if (module_changed()) vega_timers_start(&timers, app.module, vega_clock()); continue; } if (ready < 0) continue; /* a signal, such as a resize */ @@ -647,7 +647,7 @@ static int live(VegaInput *input, unsigned columns, unsigned rows, bool recorded signal(SIGTERM, on_stop); } VegaTimers timers; - vega_timers_start(&timers, app.module, vega_now()); + vega_timers_start(&timers, app.module, vega_clock()); char path[512] = ""; unsigned long differing = 0; bool open_input = true, printed = false; @@ -664,7 +664,7 @@ static int live(VegaInput *input, unsigned columns, unsigned rows, bool recorded bool dirty = true; while (!stopping && !(node && node->stopping(node->host))) { bool ticked = false; - long due = node ? -1 : vega_timers_run(&timers, &app, vega_now(), &ticked); + long due = node ? -1 : vega_timers_run(&timers, &app, vega_clock(), &ticked); PureSnapshot *snapshot = node ? node->read(node->host, &app) : NULL; dirty = dirty || ticked || snapshot || access_acted; access_acted = false; diff --git a/surfaces/screen.c b/surfaces/screen.c index fdecaf4..f079bd5 100644 --- a/surfaces/screen.c +++ b/surfaces/screen.c @@ -11,23 +11,29 @@ double vega_now(void) { return floor((double)t.tv_sec * 1000 + (double)t.tv_nsec / 1e6); } -void vega_timers_start(VegaTimers *timers, const PureModule *m, double now) { +double vega_clock(void) { + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + return floor((double)t.tv_sec * 1000 + (double)t.tv_nsec / 1e6); +} + +void vega_timers_start(VegaTimers *timers, const PureModule *m, double clock) { 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; + timers->next[i] = m->input[i].kind == PURE_KIND_TIMER ? clock + m->input[i].period : 0; } -long vega_timers_run(VegaTimers *timers, PureApp *app, double now, bool *ticked) { +long vega_timers_run(VegaTimers *timers, PureApp *app, double clock, 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; + if (timers->next[i] <= clock) { + app->now = vega_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; + timers->next[i] = clock + m->input[i].period; } - if (soonest < 0 || timers->next[i] - now < soonest) soonest = timers->next[i] - now; + if (soonest < 0 || timers->next[i] - clock < soonest) soonest = timers->next[i] - clock; } return soonest < 0 ? -1 : (long)ceil(soonest); } diff --git a/surfaces/vega.h b/surfaces/vega.h index ff880c5..0ab87dd 100644 --- a/surfaces/vega.h +++ b/surfaces/vega.h @@ -43,12 +43,16 @@ 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. */ +/* A clock that only goes on (milliseconds, from boot): when timers are due, + * which setting the wall clock back must not hold up. */ +double vega_clock(void); +/* When each timer input ticks next, on vega_clock. run ticks every one due + * at clock (a tick sees the wall clock, vega_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); +void vega_timers_start(VegaTimers *, const PureModule *, double clock); +long vega_timers_run(VegaTimers *, PureApp *, double clock, 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; diff --git a/surfaces/web.c b/surfaces/web.c index 7103c8f..a4759c4 100644 --- a/surfaces/web.c +++ b/surfaces/web.c @@ -418,11 +418,11 @@ int vega_web(PureApp *app, const VegaScreen *screen, const char *listen_on, unsi 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()); + vega_timers_start(&timers, app->module, vega_clock()); 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); + long due = vega_timers_run(&timers, app, vega_clock(), &ticked); struct pollfd fds[CLIENTS + 1]; unsigned slot[CLIENTS + 1], count = 1; fds[0] = (struct pollfd){listener, POLLIN, 0}; @@ -441,7 +441,7 @@ int vega_web(PureApp *app, const VegaScreen *screen, const char *listen_on, unsi if (ready == 0 && screen->ticks) changed = pure_app_frame(app) == PURE_OK; if (reload && reload()) { /* a new module: every page draws it */ changed = true; - vega_timers_start(&timers, app->module, app->now); + vega_timers_start(&timers, app->module, vega_clock()); } if (fds[0].revents & POLLIN) for (int fd; (fd = accept4(listener, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC)) >= 0;) { diff --git a/tests/check.py b/tests/check.py index af8ce62..efe5027 100644 --- a/tests/check.py +++ b/tests/check.py @@ -690,6 +690,46 @@ def timing(vega, module): return len(out), heard +BEATS_SPEC = """state beats = 0 +input beat = every 20 ms +on beat { beats = beats + 1 } +view count = beats +""" + + +def clock_set_back(vega, aup, work, clock_back): + """The terminal's timers wait on the monotonic clock: with the wall clock + set back an hour 0.2 s in (Pure's build/clock-back.so, LD_PRELOAD), a + timer of 20 ms goes on ticking (review 004: it stopped until the wall + clock came back to the tick it had waited for). The ticks are the + app's, in its store.""" + import pty, select, time + source, module, store = work / 'beats.aup', work / 'beats.pmod', work / 'beats-store' + source.write_text(BEATS_SPEC) + built = run(aup, 'build', source, '-o', module) + assert built.returncode == 0, built.stderr + pid, fd = pty.fork() + if pid == 0: + os.environ['LD_PRELOAD'] = str(clock_back) + os.execv(str(vega), [str(vega), 'tui', str(module), '--store', str(store)]) + end = time.time() + 0.8 + while time.time() < end: + if select.select([fd], [], [], 0.05)[0]: + try: + os.read(fd, 65536) + except OSError: + break + os.write(fd, b'\x03') + time.sleep(0.2) + _, status = os.waitpid(pid, 0) + os.close(fd) + assert os.WIFEXITED(status) and os.WEXITSTATUS(status) == 0, status + shown_lines = run(vega, 'cli', module, '--store', store, stdin='show\n').stdout.splitlines() + beats = next(int(line.split(':')[1]) for line in shown_lines if line.startswith('count')) + assert beats >= 20, shown_lines + return beats + + def hosts(vega, serve, module, work): """examples/board.aup, one app and one store for people and programs: the CLI adds and moves cards, pure-serve serves the same store as a JSON API @@ -1653,6 +1693,7 @@ def main(): parser.add_argument('--vega', type=Path, required=True) parser.add_argument('--aup', type=Path, required=True) parser.add_argument('--serve', type=Path, required=True) + parser.add_argument('--clock-back', type=Path, help="Pure's build/clock-back.so: the wall clock set back") parser.add_argument('--symbols', type=Path) parser.add_argument('--seeds', type=int, default=8) parser.add_argument('--rounds', type=int, default=3) @@ -1672,6 +1713,8 @@ def main(): report['tui_frames'] = tui(vega, modules['todo'], range(args.seeds), [(40, 12), (24, 9), (80, 30), (16, 6)]) report['table_lines'] = scores(vega, modules['scores']) report['timed_lines'], report['ticks_heard'] = timing(vega, modules['stopwatch']) + if args.clock_back: + report['beats_with_clock_set_back'] = clock_set_back(vega, aup, work, args.clock_back.resolve()) report['board_rows_across_hosts'] = hosts(vega, args.serve.resolve(), modules['board'], work) report['hot_swap_lines'] = hot_swap(vega, aup, work) report['split_list_lines'] = split(vega, modules['today'], work, range(args.seeds)) |
