class

Tryst::EventSource

Inherits Reference < Object

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

CHECK_PROC = ->tryst_event_source_check(::Pointer(Void), LibC::Int)
DEFAULT_INTERVAL = 16.milliseconds

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.

SETUP_PROC = ->tryst_event_source_setup(::Pointer(Void), LibC::Int)

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

new(check : Check, data : Pointer(Void) = Pointer(Void).null, interval : Time::Span = DEFAULT_INTERVAL)

@api private - use Interp#register_event_source, which also ties the source's lifetime to the interpreter's.

Source

Instance methods

interval
Source
register

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.

Source
registered?
Source
unregister

Stops the source. Safe to call on one that is already stopped, and safe to call more than once.

Source

Nested types