Expand description
The dispatcher — node registration, subscription, wave engine.
Mirrors ~/src/graphrefly-ts/src/__experiments__/handle-core/core.ts
(the Phase 13.6 brainstorm prototype, ~370 lines, 22 invariant tests).
§Scope (M1 dispatcher + Slice A+B parity, closed 2026-05-05)
- State + derived + dynamic node registration.
- Subscribe / unsubscribe with push-on-subscribe (R1.2.3).
- RAII [
Subscription] with Drop-based deregister (§10.12). - DIRTY → DATA / RESOLVED ordering (R1.3.1.b two-phase push).
- Equals-substitution (R1.3.2): identity is zero-FFI; custom crosses boundary.
- First-run gate (R2.5.3) — fn does not fire until every dep has a handle.
- Diamond resolution — one fn fire per wave even with shared upstream.
set_deps()atomic dep mutation with cycle detection + Phase 13.8 Q1 terminal-rejection policy (R3.3.1).- PAUSE / RESUME with lockId set + replay buffer (R1.2.6, R2.6, §10.2).
- INVALIDATE broadcast + cascade with R1.4 idempotency.
- COMPLETE / ERROR cascade + Lock 2.B auto-cascade gating (ERROR dominates COMPLETE; first error wins).
- TEARDOWN auto-precedes COMPLETE (R2.6.4 / Lock 6.F) +
has_received_teardownidempotency. - Meta TEARDOWN ordering (R1.3.9.d) — companions tear down before parent.
- Resubscribable terminal lifecycle (R2.2.7, R2.5.3) — late subscribe to a resubscribable terminal node resets lifecycle, except after TEARDOWN (per F3 audit guard: TEARDOWN is permanent).
§Module split (Slice C-1, 2026-05-05)
Wave-engine internals (drain loop, fire selection, emission commit, sink
dispatch) live in [crate::batch]. The split is purely organizational —
the methods are still on Core. See batch.rs for the wave-engine
entry points (run_wave, drain_and_flush, commit_emission,
queue_notify, deliver_data_to_consumer).
§Out of scope (later slices / milestones)
- Deactivation cleanup (RAM nodes clear cache when sink count → 0) — M2.
See migration-status.md for the
milestone tracker and porting-deferred.md
for surfaced concerns deferred to evidence-driven slices.
§Re-entrance discipline (Slice A close, M1: fully lock-released)
- Wave-end sink fires drop the state lock first. A subscriber’s sink
that calls back into
Core::emit/pause/resume/invalidate/complete/error/teardownre-acquires the lock cleanly and runs a nested wave (the one-Core-per-OS-threadin_tickownership slot is cleared by the owningBatchGuard::dropbefore the deferred-fire phase). BindingBoundary::invoke_fnfires lock-released. The wave engine acquires + drops the state lock per fn-fire iteration around theinvoke_fncallback. User fns may re-enterCore::emit/pause/ etc. and run a nested wave.BindingBoundary::custom_equalsfires lock-released.commit_emissionbrackets the equals check around a lock release; custom equals oracles may re-enter Core safely.- Subscribe-time handshake also fires lock-released.
Core::subscribeinstalls the sink under the state lock, drops it, then fires the per-tier handshake ([Start]/[Data(cache)]?/[Complete]?/[Error(h)]?/[Teardown]?per R1.3.5.a) lock-released. A handshake-time sink callback may re-enter Core via the owner-side mailbox/DeferQueueseam (emit/complete/error/subscribe); the owner drain applies it as a nested wave. Post-S4Coreis single-owner!Send + !Sync— there is nowave_ownermutex and no cross-thread block; R1.3.5.a happens-after ordering holds because the one owner thread installs the sink (state lock) before any subsequent emit’s flush observes it (subscribers_revisionfreeze, Slice X4/D2).
Structs§
- Core
- The handle-protocol Core dispatcher.
- Core
Shared - Core-global state with NO per-scheduling-group partition
(Slice B-2 Step 1, D220-EXEC). Hoisted out of
CoreStateso that when Step 2 shardsnodes/childrenperShardKey, THESE remain singular: monotonic id counters, the topology-sink registry, the cross-shard-visiblecurrently_firingreentrancy stack (P13 / /qa F2 — the load-bearing cross-thread-visibility property), the two Core-global caps, and the Core-shutdown scratch-release queue. - Core
State - Per-shard mutable Core state (Step 2a, D220-EXEC: still exactly ONE
shard — behaviour-identical with the pre-B-2 single
CoreState; Step 2b keys a per-[crate::state_cell::ShardKey] map of these). All mutable Core state, behind oneparking_lot::Mutex. - Node
Opts - Per-kind opts for
Core::register. Cross-kind config knobs live here; per-kind specifics (deps, fn_or_op) live onNodeRegistration. - Node
Registration - Unified node-registration descriptor (D030, Slice D).
- Operator
Opts - Registration options for
Core::register_operator. - Partition
Order Violation - Error returned when a same-thread partition acquire violates ascending order. Phase H+ STRICT variant (D115).
- Resume
Report - Reported back from
Core::resumewhen the final lock releases. - Subscription
Id - Public identifier for a single subscription (S2b / D225: promoted
from
pub(crate)). Returned byCore::subscribe/Core::try_subscribe; pair it with thenode_idyou subscribed and pass both toCore::unsubscribeto deregister.
Enums§
- Deferred
Producer Op - A producer-pattern operation deferred because it would have
violated the ascending partition-order protocol (Phase H+ STRICT,
D115). Drained by
BatchGuard::dropafter wave_guards are released (no partitions held → safe to acquire any partition). - Equals
Mode - Equality mode for a node’s outgoing emissions.
- Node
FnOr Op - Closure-form fn id OR typed operator discriminant — the two dispatch
paths a node can use. State / passthrough nodes pass
NonetoCore::register(no fn at all). - Node
Kind - Node kind discriminant — derived metadata computed from
[
NodeRecord]’s field shape (D030 unification, Slice D). - Operator
Op - Built-in operator discriminant. Selects the per-operator dispatch path
in
fire_operator(crates/graphrefly-core/src/batch.rs). Each variant carries the binding-side closure ids (and seed handle for stateful folders) needed for the wave-execution path; Core stores no user values itself per the handle-protocol cleaving plane. - Pausable
Mode - Pause behavior mode (canonical-spec §2.6 — three modes shipped in TS; Slice F audit, 2026-05-07 — closed the Rust port gap).
- Pause
Error - Errors returnable by
Core::pauseandCore::resume. - Register
Error - Errors returnable by
Core::registerand its sugar wrappers (Core::register_state,Core::register_producer,Core::register_derived,Core::register_dynamic,Core::register_operator). - SetDeps
Error - Errors returnable by
Core::set_deps. - SetPausable
Mode Error - Errors returnable by
Core::set_pausable_mode. - Subscribe
Error - Errors returnable by
Core::try_subscribe. - Terminal
Kind - Terminal-lifecycle state — once set on a node, the node will not emit further DATA; per-dep slots on consumers also use this to track which upstreams have terminated (R1.3.4 / Lock 2.B).
- UpError
- Errors returnable by
Core::up(canonical R1.4.1).
Traits§
- Core
Full - Object-safe full-
Corere-entry surface (S2b / D233) — the methods a producer sink’s owner-sidecrate::mailbox::MailboxOp::Deferclosure needs, byNodeId/HandleId/Sink/id only (noC/T), blanket-impl’d for everyCore. Lets windowing / higher-order-operator sinks perform value-returning topology mutation (register_*/subscribe) in-wave without naming the cell type: theBatchGuarddrain-to-quiescence loop callsf(self as &dyn CoreFull)while it holds the owner&Core.
Type Aliases§
- Sink
- A subscriber callback.
Send + Syncso the Core can fire it from any thread;Fn(notFnMut) so multiple references coexist — capture mutable state inMutex<T>or atomics on the binding side.