slightly better. table-changed events are now placed where the buffer's topological position is, so we know that whatever's left to the buffer has likely changed it.

tbh i'd also like to embed a high level step debugger. stepping through with gdb has too many low level details i'm not interested in.

#devlog #nudl

and i have my trace. it visualizes iteration boundary ("main"), rules executed (in green) and instant events for when changed buffers were reset, so i can track which data was changed.

#devlog #nudl

added a debug mode to generated binaries that switches to a live view of all updated tables in an iteration on Ctrl+C press, primarily so i can see when a process isn't exiting which updates are likely responsible.

but what i would _really_ like is a graph visualization with active rules and tables highlighted.

maybe the first step is to just generate a json trace file that can be fed into https://ui.perfetto.dev/ for instance. yes, as a compiler feature.

#devlog #nudl

Perfetto UI

after this change it became clear that this new deferred insertion pattern benefits greatly from a new table type, the delta table, which clears itself on each iteration, and effectively batches operations.

#devlog #nudl

here is for instance how the implementation of the option table type has improved by this change:

before: https://paste.sr.ht/~duangle/3d3931dbd491f24d279d0bcb63e2f9318c9105f4

after: https://paste.sr.ht/~duangle/cf1ed519a84adc1135a65f280e40196be357af88

#devlog #nudl

first example properly executes and runs with the perf improved cue. it *is* slightly more annoying, but only when you alter tables while you are reading them.

on the upside, the structure is more efficient and computationally reasonable, and optimally prepared for parallelization (though not in this very serial implementation of a fibonacci number generator.)

#devlog #nudl

the scheduler can be made to behave by inserting implicit edges from all triggers that directly depend on our trigger's cue target, to our cue target.

then case (a) causes a strong cycle, but case (b) doesn't, and also enforces correct topological order, running the top triggers first.

okay. i feel confident enough to implement this.

#devlog #nudl

however, if a cue is the only change that a trigger performs (and we could enforce this), and all of such triggers have no direct dependencies on each other (which we could also enforce), then all cue-triggers can be scheduled to happen at the end of the iteration.

it does force the program to utilize double buffering in some cases (e.g. (a) must be transformed to (b)), but significantly reduces implementation effort and memory/performance overhead.

#devlog #nudl

regular table insertion in nudl is performed with `then T args...`.

the language guarantees that all insertions on T are complete before all queries on T for one iteration.

to support cyclical changes, one can `cue T args...`. this is implemented by requiring tables to buffer these changes until the end of the iteration, where they are then committed as if performed in the next iteration.

but keeping this temporary store is inefficient, and possibly unnecessary.

#devlog #nudl

did the nudl bug first. those were several bugs:

* `&(*some_shared_ptr)` is no longer a legal operation in gcc-15 when the pointer is null. possibly never worked, and i didn't know. replaced with `some_shared_ptr.get()`.

* out of bounds std::vector access is now caught by an assertion. apparently this used to be assertion-free, and so i didn't notice.

so the stdlib got more robust and therefore i found more errors.

#devlog #nudl