// ============================================================
// Advanced Decorators
//
// In addition to the core decorators (Inverter, Succeeder,
// Repeater, Limiter) this module provides:
//
// - retry_node: retry the child up to N times on Failure
// - until_success_node: repeat child until it returns Success
// - until_failure_node: repeat child until it returns Failure
// - timeout_node: fail the child after max_ticks ticks Running
// - semaphore_node: allow at most N concurrent Running nodes
// ============================================================
// Inverter Decorator
// Inverts the child's Status: Success -> Failure, Failure -> Success.
// Running is passed through unchanged.
///|
pub fn inverter_node(child : Node) -> Node {
let tick = fn(bb) {
let status = child.tick(bb)
match status {
Status::BTSuccess => Status::BTFailure
Status::BTFailure => Status::BTSuccess
Status::BTRunning => Status::BTRunning
}
}
let reset = fn() { child.reset() }
Node::new(tick, reset)
}
// Succeeder Decorator
// Always returns Success (or Running if child is Running).
///|
pub fn succeeder_node(child : Node) -> Node {
let tick = fn(bb) {
let status = child.tick(bb)
match status {
Status::BTRunning => Status::BTRunning
_ => Status::BTSuccess
}
}
let reset = fn() { child.reset() }
Node::new(tick, reset)
}
// Repeater Decorator
// Ticks the child up to `max_repeats` times. Returns Running while repeating.
// Returns Success when the count is reached, Failure if the child fails.
// If max_repeats <= 0, repeats infinitely until child fails.
///|
pub fn repeater_node(child : Node, max_repeats : Int) -> Node {
let current_count : Ref[Int] = Ref::new(0)
let tick = fn(bb) {
if max_repeats > 0 && current_count.get() >= max_repeats {
current_count.set(0)
return Status::BTSuccess
}
let status = child.tick(bb)
match status {
Status::BTRunning => Status::BTRunning
Status::BTFailure => {
current_count.set(0)
Status::BTFailure
}
Status::BTSuccess => {
current_count.set(current_count.get() + 1)
if max_repeats > 0 && current_count.get() >= max_repeats {
current_count.set(0)
Status::BTSuccess
} else {
Status::BTRunning
}
}
}
}
let reset = fn() {
current_count.set(0)
child.reset()
}
Node::new(tick, reset)
}
// Limiter Decorator
// Guards the child from running again for `min_ticks` ticks after it completes.
// Returns Failure during the cooldown window.
///|
pub fn limiter_node(child : Node, min_ticks : Int) -> Node {
let tick_counter : Ref[Int] = Ref::new(min_ticks)
let tick = fn(bb) {
let current = tick_counter.get()
if current < min_ticks {
tick_counter.set(current + 1)
return Status::BTFailure
}
let status = child.tick(bb)
match status {
Status::BTSuccess | Status::BTFailure => tick_counter.set(0)
Status::BTRunning => ()
}
status
}
let reset = fn() {
tick_counter.set(min_ticks)
child.reset()
}
Node::new(tick, reset)
}
// Retry Decorator
// Retries the child on Failure up to `max_retries` times per activation.
// Returns Running while retrying, Success when the child succeeds,
// and Failure once all retries are exhausted.
///|
pub fn retry_node(child : Node, max_retries : Int) -> Node {
let attempts : Ref[Int] = Ref::new(0)
let tick = fn(bb) {
let status = child.tick(bb)
match status {
Status::BTSuccess => {
attempts.set(0)
Status::BTSuccess
}
Status::BTRunning => Status::BTRunning
Status::BTFailure => {
let a = attempts.get() + 1
attempts.set(a)
if a >= max_retries {
attempts.set(0)
Status::BTFailure
} else {
child.reset()
Status::BTRunning
}
}
}
}
let reset = fn() {
attempts.set(0)
child.reset()
}
Node::new(tick, reset)
}
// UntilSuccess Decorator
// Ticks the child repeatedly until it returns Success.
// Returns Running while the child is Failing or Running.
// On child Success, returns Success and resets.
///|
pub fn until_success_node(child : Node) -> Node {
let tick = fn(bb) {
let status = child.tick(bb)
match status {
Status::BTSuccess => {
child.reset()
Status::BTSuccess
}
_ => {
child.reset()
Status::BTRunning
}
}
}
let reset = fn() { child.reset() }
Node::new(tick, reset)
}
// UntilFailure Decorator
// Ticks the child repeatedly until it returns Failure.
// Returns Running while the child is Succeeding or Running.
// On child Failure, returns Success and resets.
///|
pub fn until_failure_node(child : Node) -> Node {
let tick = fn(bb) {
let status = child.tick(bb)
match status {
Status::BTFailure => {
child.reset()
Status::BTSuccess
}
_ => {
child.reset()
Status::BTRunning
}
}
}
let reset = fn() { child.reset() }
Node::new(tick, reset)
}
// Timeout Decorator
// Ticks the child normally but fails it if it remains Running
// for more than `max_ticks` consecutive ticks.
///|
pub fn timeout_node(child : Node, max_ticks : Int) -> Node {
let elapsed : Ref[Int] = Ref::new(0)
let tick = fn(bb) {
if elapsed.get() >= max_ticks {
elapsed.set(0)
child.reset()
return Status::BTFailure
}
let status = child.tick(bb)
match status {
Status::BTRunning => {
elapsed.set(elapsed.get() + 1)
Status::BTRunning
}
other => {
elapsed.set(0)
other
}
}
}
let reset = fn() {
elapsed.set(0)
child.reset()
}
Node::new(tick, reset)
}