// ============================================================
// Event queue router
//
// EventQueue stores ordered messages, while EventBus stores one-shot flags.
// EventRouter is the integration layer for applications that need a bounded
// amount of message work per frame. Handlers receive the original event,
// including its typed payload, and a Running handler remains active across
// frames without losing the message.
// ============================================================
///|
/// A named event handler used by EventRouter.
pub struct EventRoute {
name : String
handler : (Blackboard, EventRecord) -> Status
}
///|
/// Register a handler for an event name.
pub fn EventRoute::new(
name : String,
handler : (Blackboard, EventRecord) -> Status,
) -> EventRoute {
{ name, handler }
}
///|
/// Return the event name handled by this route.
pub fn EventRoute::name(self : EventRoute) -> String {
self.name
}
///|
/// A bounded router tick report for monitoring and backpressure decisions.
pub struct EventRouterReport {
processed : Int
succeeded : Int
failed : Int
running : Bool
pending : Int
status : Status
}
///|
/// Number of events delivered during this router tick.
pub fn EventRouterReport::processed(self : EventRouterReport) -> Int {
self.processed
}
///|
/// Number of events that completed successfully during this tick.
pub fn EventRouterReport::succeeded(self : EventRouterReport) -> Int {
self.succeeded
}
///|
/// Number of events that failed during this tick.
pub fn EventRouterReport::failed(self : EventRouterReport) -> Int {
self.failed
}
///|
/// Whether a handler is still running across frames.
pub fn EventRouterReport::is_running(self : EventRouterReport) -> Bool {
self.running
}
///|
/// Number of queued or active events after this tick.
pub fn EventRouterReport::pending(self : EventRouterReport) -> Int {
self.pending
}
///|
/// Overall status for the router tick.
pub fn EventRouterReport::status(self : EventRouterReport) -> Status {
self.status
}
///|
/// Format the report for a log line or metrics sample.
pub fn EventRouterReport::to_line(self : EventRouterReport) -> String {
"processed=\{self.processed};succeeded=\{self.succeeded};failed=\{self.failed};running=\{self.running};pending=\{self.pending};status=\{self.status.to_string()}"
}
///|
/// A bounded event-to-handler dispatcher.
pub struct EventRouter {
queue : EventQueue
routes : Array[EventRoute]
fallback : Ref[EventRoute?]
max_events : Ref[Int]
active : Ref[EventRecord?]
}
///|
/// Create a router over an existing FIFO queue.
pub fn EventRouter::new(queue : EventQueue, max_events : Int) -> EventRouter {
{
queue,
routes: [],
fallback: Ref::new(None),
max_events: Ref::new(if max_events > 0 { max_events } else { 1 }),
active: Ref::new(None),
}
}
///|
/// Add or replace a named route. Returns true when a route was inserted.
pub fn EventRouter::on(
self : EventRouter,
name : String,
handler : (Blackboard, EventRecord) -> Status,
) -> Bool {
let route = EventRoute::new(name, handler)
let mut i = 0
while i < self.routes.length() {
if self.routes[i].name == route.name {
self.routes[i] = route
return false
}
i = i + 1
}
self.routes.push(route)
true
}
///|
/// Remove one named route. Returns whether a route was removed.
pub fn EventRouter::remove(self : EventRouter, name : String) -> Bool {
let mut i = 0
while i < self.routes.length() {
if self.routes[i].name == name {
let _ = self.routes.remove(i)
return true
}
i = i + 1
}
false
}
///|
/// Return whether a named route exists.
pub fn EventRouter::has_route(self : EventRouter, name : String) -> Bool {
for route in self.routes {
if route.name == name {
return true
}
}
false
}
///|
/// Number of registered routes.
pub fn EventRouter::route_count(self : EventRouter) -> Int {
self.routes.length()
}
///|
/// Install a handler for events without a named route.
pub fn EventRouter::set_fallback(
self : EventRouter,
handler : (Blackboard, EventRecord) -> Status,
) -> Unit {
self.fallback.set(Some(EventRoute::new("*", handler)))
}
///|
/// Remove the fallback handler.
pub fn EventRouter::clear_fallback(self : EventRouter) -> Unit {
self.fallback.set(None)
}
///|
/// Set the maximum number of new events processed in one tick.
pub fn EventRouter::set_max_events(
self : EventRouter,
max_events : Int,
) -> Unit {
self.max_events.set(if max_events > 0 { max_events } else { 1 })
}
///|
/// Current per-tick event budget.
pub fn EventRouter::max_events(self : EventRouter) -> Int {
self.max_events.get()
}
///|
/// Return the queue used by this router.
pub fn EventRouter::queue(self : EventRouter) -> EventQueue {
self.queue
}
///|
/// Return the active event being resumed, if a handler returned Running.
pub fn EventRouter::active_event(self : EventRouter) -> EventRecord? {
self.active.get()
}
///|
/// Return whether a handler is waiting for another frame.
pub fn EventRouter::is_running(self : EventRouter) -> Bool {
match self.active.get() {
Some(_) => true
None => false
}
}
///|
/// Find a named route without changing the registration order.
fn find_event_route(routes : Array[EventRoute], name : String) -> EventRoute? {
for route in routes {
if route.name == name {
return Some(route)
}
}
None
}
///|
/// Process at most the configured number of events.
///
/// A Running handler keeps the current EventRecord active. The event is not
/// polled from the queue again, so handlers can safely perform multi-frame
/// animation, network retry, or resource loading work.
pub fn EventRouter::tick(
self : EventRouter,
bb : Blackboard,
) -> EventRouterReport {
let mut processed = 0
let mut succeeded = 0
let mut failed = 0
let limit = self.max_events.get()
while processed < limit {
let event = match self.active.get() {
Some(active) => active
None =>
match self.queue.poll() {
None => break
Some(next) => {
self.active.set(Some(next))
next
}
}
}
let status = match find_event_route(self.routes, event.name()) {
Some(route) => (route.handler)(bb, event)
None =>
match self.fallback.get() {
Some(route) => (route.handler)(bb, event)
None => Status::BTFailure
}
}
processed = processed + 1
match status {
Status::BTRunning => break
Status::BTSuccess => {
succeeded = succeeded + 1
self.active.set(None)
}
Status::BTFailure => {
failed = failed + 1
self.active.set(None)
}
}
}
let running = self.is_running()
let active_count = if running { 1 } else { 0 }
let pending = self.queue.size() + active_count
let overall = if running || pending > 0 {
Status::BTRunning
} else if failed > 0 {
Status::BTFailure
} else {
Status::BTSuccess
}
{ processed, succeeded, failed, running, pending, status: overall }
}
///|
/// Cancel the active handler without consuming queued events.
pub fn EventRouter::cancel_active(self : EventRouter) -> Unit {
self.active.set(None)
}
///|
/// Clear queued and active work, preserving route registrations.
pub fn EventRouter::reset(self : EventRouter) -> Unit {
self.queue.clear()
self.active.set(None)
}
///|
/// Return the number of queued events, excluding an active event.
pub fn EventRouter::queued(self : EventRouter) -> Int {
self.queue.size()
}
///|
/// Return all registered route names in declaration order.
pub fn EventRouter::route_names(self : EventRouter) -> Array[String] {
let names : Array[String] = []
for route in self.routes {
names.push(route.name)
}
names
}
///|
/// Deliver one event immediately through a named route without queueing it.
pub fn EventRouter::deliver(
self : EventRouter,
name : String,
bb : Blackboard,
payload : Value?,
) -> Status {
let event = EventRecord::new(-1, name, payload)
match find_event_route(self.routes, event.name()) {
Some(route) => (route.handler)(bb, event)
None =>
match self.fallback.get() {
Some(route) => (route.handler)(bb, event)
None => Status::BTFailure
}
}
}