// ============================================================
// Event: Interrupt / Event-Driven Subtree for Behavior Trees
//
// EventBus allows nodes to emit named events and register
// handlers that fire when events are received. Combined with
// the InterruptNode, this enables reactive, event-driven
// behavior trees where an external condition can abort the
// currently running subtree and switch to an interrupt handler.
// ============================================================
///|
/// EventBus holds a set of pending events and registered handlers.
pub struct EventBus {
pending : Map[String, Bool]
handlers : Map[String, (Blackboard) -> Status]
}
///|
/// Create a new EventBus.
pub fn EventBus::new() -> EventBus {
{ pending: Map([]), handlers: Map([]) }
}
///|
/// Raise a named event so it will be consumed on the next tick.
pub fn EventBus::emit(self : EventBus, name : String) -> Unit {
self.pending.set(name, true)
}
///|
/// Register a handler closure for a named event.
/// When the event fires and the InterruptNode is ticked, this handler runs.
pub fn EventBus::on(
self : EventBus,
name : String,
handler : (Blackboard) -> Status,
) -> Unit {
self.handlers.set(name, handler)
}
///|
/// Dispatch a pending event to its registered handler exactly once.
/// Returns `None` when the event is not pending or has no handler.
pub fn EventBus::dispatch(
self : EventBus,
name : String,
bb : Blackboard,
) -> Status? {
if !self.has_event(name) {
return None
}
self.consume(name)
match self.handlers.get(name) {
Some(handler) => Some(handler(bb))
None => None
}
}
///|
/// Check if a named event is currently pending.
pub fn EventBus::has_event(self : EventBus, name : String) -> Bool {
self.pending.get(name).unwrap_or(false)
}
///|
/// Consume (clear) a named event after it has been handled.
pub fn EventBus::consume(self : EventBus, name : String) -> Unit {
self.pending.remove(name)
}
///|
/// Clear all pending events.
pub fn EventBus::clear_all(self : EventBus) -> Unit {
let keys : Array[String] = []
self.pending.each(fn(k, _) { keys.push(k) })
for k in keys {
self.pending.remove(k)
}
}
// ============================================================
// InterruptNode
//
// An InterruptNode monitors a named event on the provided EventBus.
// On each tick:
// - If the named event is pending, the interrupt handler fires
// (and the child subtree is reset), then the event is consumed.
// - Otherwise, the child subtree ticks normally.
// This allows external triggers (e.g. "enemy_spotted") to
// preempt an ongoing behavior.
// ============================================================
///|
/// interrupt_node creates a node that monitors a named event.
/// When the event is raised, `interrupt_handler` runs instead of `child`.
pub fn interrupt_node(
bus : EventBus,
event_name : String,
child : Node,
interrupt_handler : Node,
) -> Node {
let tick = fn(bb) {
if bus.has_event(event_name) {
bus.consume(event_name)
child.reset()
interrupt_handler.tick(bb)
} else {
child.tick(bb)
}
}
let reset = fn() {
child.reset()
interrupt_handler.reset()
}
Node::new(tick, reset)
}
// ============================================================
// ConditionGuard
//
// A ConditionGuard monitors a predicate on every tick. If the
// predicate turns false while the child is Running, it aborts
// the child (reset) and returns Failure immediately. This is
// the reactive variant of Condition, suitable for precondition
// checks that must hold throughout a long-running action.
// ============================================================
///|
/// condition_guard_node creates a reactive guard wrapper.
/// It ticks `child` only while `predicate(bb)` is true.
/// If the predicate fails mid-execution, the child is reset and Failure is returned.
pub fn condition_guard_node(
predicate : (Blackboard) -> Bool,
child : Node,
) -> Node {
let tick = fn(bb) {
if predicate(bb) {
child.tick(bb)
} else {
child.reset()
Status::BTFailure
}
}
let reset = fn() { child.reset() }
Node::new(tick, reset)
}