Tryst::EventSource
A callback Tcl runs on every pass of its event loop.
This is how a library with an event queue of its own - a GPU renderer, a game controller, a socket, a queue fed by another thread
- gets pumped from inside Tk's loop instead of fighting it for the main thread. Tcl calls the check function every time the notifier wakes, and the setup function caps how long it may sleep in between, so the pump runs at a predictable rate even when nothing is happening in the UI.
THE CALLBACK IS A PLAIN FUNCTION POINTER, not a block, and that is
the whole design. It runs constantly - many times a second, forever -
so it must not allocate, must not raise and must not do anything the
garbage collector has to know about. A capturing closure would drag
exactly that into the hot path, so one is refused outright rather
than accepted and regretted later. State reaches the callback through
the opaque data pointer instead:
fun pump_my_library(data : Void*)
MyLib.poll(data.as(MyLib::Context*))
end
source = interp.register_event_source(->pump_my_library(Void*), context.as(Void*))
# ...
source.unregister
Constants
How long the notifier may sleep before running the check function again. The default is a 60fps-ish pump, which is what a renderer or a controller poll wants; a source that only needs to be responsive rather than smooth can afford much more.
The two C callbacks, evaluated ONCE into constants. Deliberately - do not inline them back into the calls below.
Tcl_DeleteEventSource finds the source to remove by matching all three values it was created with: the setup pointer, the check pointer and the client data. If any one of them differs it matches nothing, removes nothing, and reports nothing.
The trap is that ->tryst_event_source_setup does not evaluate to
the address of that function. Crystal builds a small wrapper for
each -> expression and hands C the address of the wrapper, so the
same -> written twice gives two different addresses - measured at
20 bytes apart. Register with one and delete with the other and the
delete silently matches nothing, leaving a source that fires on
every pass of the event loop with no way left to stop it.
Constructors
@api private - use Interp#register_event_source, which also ties the source's lifetime to the interpreter's.
Instance methods
Starts the source. Called by the constructor; calling it again on a live source does nothing, since registering the same trio twice with Tcl would have it call the check function twice per pass.
Stops the source. Safe to call on one that is already stopped, and safe to call more than once.