Expand description
Host-generic ECS driver: dispatchers, derivation fixed-point, mirror
sync, timers, HTTP fetch, and runtime::ScriptPlugin. Formerly the
lumen-script-runtime crate; folded in with its flat surface
re-exported at the crate root for source compatibility.
Host-generic script runtime.
Everything in the scripting contract that is not compilation, value
conversion, or invocation lives here, parameterized over
H: ScriptHost: the 18-event dispatch surface with per-id handler
routing, the derivation fixed-point driver, the store->mirror sync
policy driver, timers, HTTP fetch plumbing, the load-failure banner
protocol, and the ScriptPlugin that wires it all into the tick.
Concrete hosts, each a runtime module (lumen-candela and the others
under std/), provide engine + builtins + conversion and hand a built
host to ScriptPlugin::new.
§Ordering contract (7bfc0f2 - do not regress)
Systems:
1. control pushes (push_toggle/slider/textinput/scroll_to_signal)
2. sync_signals_into_host .after(all pushes) - type-preserving parse-back
3. tick_script .after(sync) + all event dispatchers
4. embedder's apply_script_commands .after(dispatchers) .before(derivations, readers)
5. apply_derivations .after(sync) - fixed point, <= 32 passes
6. dirty-gated binding readers
A11ySync: clear_property_store_dirtyDirty flags live exactly one tick; every consumer of “what changed” must be ordered inside that window.
§Several hosts in one app
An app can run more than one host at a time (one per script language it
ships). Each host is its own Resource, so every ::<H> system above is
monomorphised once per active host. Embedders must therefore order against
ScriptSet rather than against a concrete system::<H>: a .before( apply_derivations::<Rhai>) edge says nothing about the Lua host’s
derivation pass, and the dirty-window guarantees collapse for every host it
does not name. The sets below hold the same systems for every active host,
so one edge covers all of them.
Hosts share the world’s PropertyStore, so a signal written by one is read
by the others on the same tick. Lifecycle and event callbacks (on_start,
on_ready, on_click, on_timer, …) are delivered to every active host;
a host that does not define the handler ignores the call.
Structs§
- DueFrame
- The step handed to
on_framethis tick, orNonewhen no frame is due. Rewritten every tick byretire_frame_requestand read byfire_frame_callbackson each active host. - DueTimers
- Names of the timers due this tick, in sorted order. Rewritten every tick by
retire_due_timersand read byfire_due_timerson each active host. - Fetch
Registry - HTTP plumbing: start requests away from the world thread, marshal the completed reply back onto the ECS/UI thread, and surface it to the script.
- Frame
Hook - The state behind
request_frame()/on_frame(dt). - Http
Hooks - The
HttpHooks every script request passes through, in the order they were added. - OnReady
Fired - Latch guarding
fire_on_ready: holds theScriptHost::langof every host that has already dispatchedon_ready, so each active host fires once per mount. Hot reload clears the set after respawning the tree, so a script that builds DOM inon_readyrebuilds it on the fresh mount. - Pending
Fetch Replies - HTTP replies that finished this tick, waiting to be delivered to every
active host. Filled by
collect_fetch_repliesand emptied byclear_fetch_replies. - Pending
Plugin Events - Handler calls pushed from outside a script call and delivered this tick,
mirroring
PendingFetchReplies: filled bycollect_plugin_events, offered to every active host byfire_plugin_events, and emptied byclear_plugin_events. - Script
Command Event - One
ScriptCommandflowing through the ECS message bus so app systems can read it viaMessageReader<ScriptCommandEvent>. - Script
Load Failure - Inserted by
ScriptPlugin::buildwhen the initial script failed to compile or evaluate. Embedders (lumenc’srun) surface it prominently - e.g. through the in-window error banner - so a dead script never masquerades as a healthy app that just ignores clicks. Absent when the script loaded cleanly. - Script
Plugin - Generic script plugin: loads
sourceinto the supplied host, fireson_start(re-stashing its commands into the sink so they flow through the first tick’s normal drain), installs the host as aResource, and registers the full dispatcher / derivation / timer / fetch system set. - Script
Started At - Wall-clock moment the script plugin was installed. Kept for embedders
that want elapsed-since-install (the FFI surface reads it); the
runtime itself is reactive-only and no longer computes a per-frame
t. - Timer
Registry - Active timers keyed by name. Names are unique - setting a timer with the same name replaces the previous one (matches set_interval / set_timeout semantics in browsers).
Enums§
- Script
Set - Ordering anchors for the host-generic script systems.
Constants§
- MAX_
FRAME_ DT - The longest step
on_frameis ever handed, in seconds.
Functions§
- apply_
derivations - Re-evaluate every computed signal whose deps changed this tick.
- clear_
fetch_ replies - Drop this tick’s delivered HTTP replies. Runs after every host’s
fire_fetched_responses. - clear_
plugin_ events - Drop this tick’s delivered plugin events. Runs after every host’s
fire_plugin_events. - collect_
fetch_ replies - Move finished HTTP replies (marshalled back from the worker threads) off the
channel into
PendingFetchReplies, and record each for the devtools network pane. Host-neutral and registered once, so a reply is offered to every active host rather than taken by whichever ran first. - collect_
plugin_ events - Move the events plugins and capabilities pushed into
PendingPluginEvents. Host-neutral and registered once, so a handler call is offered to every active host rather than taken by whichever ran first; aPluginEvent::Commandsbatch goes straight onto the command bus here instead, so it applies once however many hosts run. - dispatch_
clicks_ and_ doubles - Forward this tick’s
ClickEvent/DoubleClickEventmessages to the script’son_click(id)/on_double_click(id)handlers. - dispatch_
close_ to_ script - Forward the window backend’s
lumen_core::input::CloseRequestto the script’son_close()lifecycle hook - before the backend tears anything down, so scripts get a last chance to persist state. - dispatch_
dialog_ closes_ to_ script - Forward
lumen_core::input::DialogClosed(W5 dialog contract) to the script:accepted = trueroutes ason_dialog_accepted(id)/on("dialog_accepted", id, fn),accepted = falseason_dialog_rejected(id)/on("dialog_rejected", id, fn). The emitter guarantees exactly one message per open->close cycle. - dispatch_
file_ drops_ to_ script - Forward
FileDroppedason_file_dropped(id, path). - dispatch_
long_ press_ to_ script - Forward
LongPressEventason_long_press(id). - dispatch_
menu_ clicks_ to_ script - Forward
lumen_core::input::MenuClickedto the script ason_menu(id), with the per-idon("menu", id, fn)router applying first. - dispatch_
slider_ to_ script - Forward
lumen_primitives::SliderChangedason_slider(id, value)(f64), with per-id handler routing viaon("slider", "<id>", "<fn>"). - dispatch_
text_ input_ to_ script - Forward text changes as
on_text_input(id, text). - dispatch_
toggle_ to_ script - Forward
lumen_primitives::ToggleChangedason_toggle(id, checked)(bool), with per-id handler routing viaon("toggle", "<id>", "<fn>"). - drain_
fetch_ commands - Read
Fetch/Httpcommands the script emitted this tick and hand each to the registry’s dispatcher. Other variants are no-ops here (they flow to apply_script_commands and timer drains separately). - drain_
frame_ requests - Read the
RequestFramecommands emitted this tick and arm the hook. - drain_
timer_ commands - Read
SetTimer/CancelTimercommands emitted by the script’s builtins this tick and update theTimerRegistryaccordingly, then ask the loop to wake at the earliest pending deadline. Without that request an idle loop parks on events alone and a timer armed from a click fires only when some unrelated input ticks the app. - fill_
components - Fill every use site the build could not finish by calling the function it names and putting the node that call returns in the marker’s place.
- fire_
due_ timers - Fire
on_timer(name)for every timerretire_due_timerscollected this tick. A host that defines no matching handler ignores the call, so in an app running several languages the timer reaches whichever host declared it. - fire_
fetched_ responses - Invoke the script’s completion handler once for each reply
collect_fetch_repliesgathered. This is the only place a network reply crosses into script/signal land - the worker never touches the world, mirroring Slint’sinvoke_from_event_loop. - fire_
frame_ callbacks - Call
on_frame(dt)on this host whenretire_frame_requestfound a frame due. A host that declares noon_frameis silent, so in an app running several languages the callback reaches whichever host declared it. - fire_
on_ ready - Dispatch the script’s optional
on_ready()once per mount, on the first tick after the DOM index is published. - fire_
plugin_ events - Invoke the script’s handler once for each call
collect_plugin_eventsgathered. Routing matches the fetch pipeline exactly: a per-keyon(event, key, fn)registration wins, else the event’s fallback, and the key rides as the handler’s first argument ahead of the event’s own. - panic_
payload_ text - Best-effort text for a caught panic payload.
- register_
plugin_ event_ message - Register
PluginEventas a message, so a main-thread system can write one throughMessageWriter<PluginEvent>. Idempotent:ScriptPlugincalls it, and so does a capability that writes events, whichever installs first. - register_
script_ commands - Make sure the
ScriptCommandEventstream exists inworld. - reload_
script - Hot-reload orchestration entry: swap the loaded program on the live
host resource via
ScriptHost::replace(compile-first, atomic, full rollback on eval failure - the host owns the atomicity). ReturnsNonewhen no host is installed (script-less app). - retire_
due_ timers - Collect the timers whose deadline has passed, reschedule the repeating ones, and drop the one-shots. Host-neutral and registered once, so each active host is offered the same due list instead of the first one to run taking it.
- retire_
frame_ request - Decide whether a frame callback is due this tick and what step it gets.
- sync_
signals_ into_ host - Mirror the ECS-side global
lumen_core::property_store::PropertyStorecells into the host’s rich-typed local mirror at the start of every tick, walking only the keys dirtied this tick. Without this, the script’s signal reads see stale values when another system (a<for>reconciler, a two-way bind push, …) wrote to the property store between the previous tick and this one. - tick_
script - Per-tick driver that drains the host’s command sink and forwards
every
ScriptCommandonto theScriptCommandEventmessage bus. On the first tick this also flushes the commands re-stashed byon_start(seeScriptPlugin::build).