use alloc::boxed::Box;
use behaviortree_derive::Control;
use tinyscript::SharedRuntime;
use crate::{
self as behaviortree_core, BehaviorCreationFn, BehaviorResult, ConstString, behavior_data::BehaviorData,
behavior_description::BehaviorDescription, behavior_state::BehaviorState, error::Error, tree::BehaviorTreeElementList,
};
#[derive(Clone, Copy, Debug)]
pub enum FallbackKind {
Async,
Reactive,
Standard,
}
#[derive(Control, Debug)]
#[behavior(groot2, no_create, no_register, no_register_with)]
pub struct Fallbacks {
kind: FallbackKind,
child_idx: usize,
skipped: usize,
running_child_idx: i32,
}
#[async_trait::async_trait]
impl crate::behavior_traits::Behavior for Fallbacks {
fn on_start(
&mut self,
behavior: &mut BehaviorData,
_children: &mut BehaviorTreeElementList,
_runtime: &SharedRuntime,
) -> Result<(), Error> {
self.child_idx = 0;
self.skipped = 0;
self.running_child_idx = -1;
behavior.set_state(BehaviorState::Running);
Ok(())
}
async fn tick(
&mut self,
_behavior: &mut BehaviorData,
children: &mut BehaviorTreeElementList,
runtime: &SharedRuntime,
) -> BehaviorResult {
let children_count = children.len();
if matches!(self.kind, FallbackKind::Reactive) {
self.child_idx = 0;
}
while self.child_idx < children_count {
let child = &mut children[self.child_idx];
let prev_state = child.state();
let child_state = child.tick(runtime).await?;
match child_state {
BehaviorState::Failure => {
self.child_idx += 1;
match self.kind {
FallbackKind::Async => {
if (prev_state == BehaviorState::Idle) && (self.child_idx < children_count) {
return Ok(BehaviorState::Running);
}
}
FallbackKind::Reactive => self.running_child_idx = -1,
FallbackKind::Standard => {}
}
}
BehaviorState::Idle => {
let behavior: ConstString = match self.kind {
FallbackKind::Async => "AsnycFallback".into(),
FallbackKind::Reactive => "ReactiveFallback".into(),
FallbackKind::Standard => "Fallback".into(),
};
return Err(Error::State {
behavior,
state: child_state,
});
}
BehaviorState::Running => {
match self.kind {
FallbackKind::Async | FallbackKind::Standard => {}
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
#[allow(clippy::cast_possible_wrap)]
FallbackKind::Reactive => {
if self.running_child_idx != (self.child_idx as i32) && self.running_child_idx != -1 {
children[self.running_child_idx as usize].halt_children(runtime)?;
}
self.running_child_idx = self.child_idx as i32;
if self.running_child_idx == -1 {
self.running_child_idx = self.child_idx as i32;
} else if self.running_child_idx != (self.child_idx as i32) {
return Err(Error::Composition {
txt: "[ReactiveFallback]: Only a single child can return Running.".into(),
});
}
}
}
return Ok(child_state);
}
BehaviorState::Success => {
children.reset(runtime)?;
children.halt(runtime)?;
self.child_idx = 0;
self.skipped = 0;
self.running_child_idx = -1;
return Ok(child_state);
}
BehaviorState::Skipped => {
self.child_idx += 1;
self.skipped += 1;
}
}
}
let all_skipped = self.skipped == children_count;
if self.child_idx >= children_count {
children.reset(runtime)?;
self.child_idx = 0;
self.skipped = 0;
self.running_child_idx = -1;
}
if all_skipped {
Ok(BehaviorState::Skipped)
} else {
Ok(BehaviorState::Failure)
}
}
}
impl Fallbacks {
#[must_use]
pub const fn new(kind: FallbackKind) -> Self {
Self {
kind,
child_idx: 0,
skipped: 0,
running_child_idx: -1,
}
}
#[must_use]
pub fn create_fn(kind: FallbackKind) -> Box<BehaviorCreationFn> {
Box::new(move |_blackboard| Box::new(Self::new(kind)))
}
pub fn register_with(
registry: &mut impl behaviortree_core::behavior_traits::BehaviorRegistry,
name: &str,
kind: FallbackKind,
) -> Result<(), Error> {
let bhvr_desc = BehaviorDescription::new(name, name, true);
let bhvr_creation_fn = Self::create_fn(kind);
registry.add_behavior(bhvr_desc, bhvr_creation_fn)
}
}