// ============================================================
// Execution safety policies
// ============================================================
///|
/// Limit the total number of child ticks during one activation.
/// This protects service loops from accidental infinite-running subtrees.
pub fn execution_budget_node(child : Node, max_ticks : Int) -> Node {
let used : Ref[Int] = Ref::new(0)
let budget = if max_ticks > 0 { max_ticks } else { 1 }
let tick = fn(bb) {
if used.get() >= budget {
child.reset()
used.set(0)
Status::BTFailure
} else {
used.set(used.get() + 1)
match child.tick(bb) {
Status::BTSuccess => {
used.set(0)
Status::BTSuccess
}
Status::BTFailure => {
used.set(0)
Status::BTFailure
}
Status::BTRunning => Status::BTRunning
}
}
}
let reset = fn() {
used.set(0)
child.reset()
}
Node::new(tick, reset)
}
///|
/// Fail when a predicate is false, otherwise delegate to the child.
pub fn require_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)
}
///|
/// Execute a child at most once per activation and remember its outcome.
pub fn once_node(child : Node) -> Node {
let completed : Ref[Status?] = Ref::new(None)
let tick = fn(bb) {
match completed.get() {
Some(status) => status
None => {
let status = child.tick(bb)
match status {
Status::BTSuccess | Status::BTFailure => completed.set(Some(status))
Status::BTRunning => ()
}
status
}
}
}
let reset = fn() {
completed.set(None)
child.reset()
}
Node::new(tick, reset)
}