use crate::{
self as behaviortree, BehaviorDescription, BehaviorKind, BehaviorTreeFactory, ConstString, Control, EMPTY_STR,
behavior::{Behavior, BehaviorCreationFn, BehaviorData, BehaviorError, BehaviorResult, BehaviorState},
input_port,
port::PortList,
tree::BehaviorTreeElementList,
};
use alloc::string::String;
use alloc::vec::Vec;
use alloc::{boxed::Box, string::ToString};
use tinyscript::SharedRuntime;
const CASES: [&str; 6] = [
"case_1", "case_2", "case_3", "case_4", "case_5", "case_6",
];
const VARIABLE: &str = "variable";
#[derive(Control, Debug)]
#[behavior(no_create, no_register, no_register_with)]
pub struct Switch<const T: u8> {
cases: u8,
running_child_index: i32,
var: ConstString,
}
impl<const T: u8> Default for Switch<T> {
fn default() -> Self {
Self {
cases: T,
running_child_index: -1,
var: EMPTY_STR.into(),
}
}
}
#[async_trait::async_trait]
impl<const T: u8> Behavior for Switch<T> {
fn on_halt(&mut self) -> Result<(), BehaviorError> {
self.cases = T;
self.running_child_index = -1;
Ok(())
}
fn on_start(
&mut self,
behavior: &mut BehaviorData,
children: &mut BehaviorTreeElementList,
_runtime: &SharedRuntime,
) -> Result<(), BehaviorError> {
self.running_child_index = -1;
if children.len() != (self.cases + 1) as usize {
return Err(BehaviorError::Composition {
txt: "Wrong number of children in Switch behavior: must be (num_cases + 1)!".into(),
});
}
match behavior.remappings().find(VARIABLE) {
databoard::RemappingTarget::BoardPointer(board_pointer)
| databoard::RemappingTarget::LocalPointer(board_pointer)
| databoard::RemappingTarget::RootPointer(board_pointer) => self.var = board_pointer.to_string().into(),
databoard::RemappingTarget::StringAssignment(_) => {
return Err(BehaviorError::Composition {
txt: "port [variable] must be a Blackboard pointer".into(),
});
}
databoard::RemappingTarget::None(_) => {
return Err(BehaviorError::Composition {
txt: "port [variable] must be defined".into(),
});
}
}
behavior.set_state(BehaviorState::Running);
Ok(())
}
async fn tick(
&mut self,
behavior: &mut BehaviorData,
children: &mut BehaviorTreeElementList,
runtime: &SharedRuntime,
) -> BehaviorResult {
fn inner_tick(
behavior: &BehaviorData,
runtime: &SharedRuntime,
default_index: u8,
var: &ConstString,
) -> Result<i32, BehaviorError> {
let mut match_index = i32::from(default_index);
let var = behavior.get::<String>(var)?;
for i in 0..default_index {
let case = behavior.get::<String>(CASES[i as usize])?;
if var == case {
match_index = i32::from(i);
break;
}
let guard = runtime.lock();
if let Some(c_val) = guard.enum_discriminant(&case) {
if let Ok(v_val) = var.parse::<i8>()
&& c_val == v_val
{
match_index = i32::from(i);
break;
} else if let Some(v_val) = guard.enum_discriminant(&var)
&& c_val == v_val
{
match_index = i32::from(i);
break;
}
}
drop(guard);
if let Ok(v_val) = var.parse::<i64>()
&& let Ok(c_val) = case.parse::<i64>()
&& c_val == v_val
{
match_index = i32::from(i);
break;
}
if let Ok(c_val) = case.parse::<f64>()
&& let Ok(v_val) = var.parse::<f64>()
{
let delta = f64::abs(v_val - c_val);
if delta <= 0.000_000_000_000_002 {
match_index = i32::from(i);
break;
}
}
}
Ok(match_index)
}
let default_index = i32::from(T);
let match_index = inner_tick(behavior, runtime, T, &self.var)?;
if self.running_child_index > 0 && match_index != self.running_child_index && match_index <= default_index {
#[allow(clippy::cast_sign_loss)]
children[self.running_child_index as usize].halt_children(runtime)?;
}
#[allow(clippy::cast_sign_loss)]
let state = children[match_index as usize]
.tick(runtime)
.await?;
if state == BehaviorState::Skipped {
self.running_child_index = -1;
} else if state == BehaviorState::Idle {
return Err(BehaviorError::State {
behavior: "Switch".into(),
state,
});
} else if state == BehaviorState::Running {
self.running_child_index = match_index;
} else {
children.halt(runtime)?;
self.running_child_index = -1;
}
Ok(state)
}
fn provided_ports() -> PortList {
create_port_list(T)
}
}
impl<const T: u8> Switch<T> {
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn create_fn() -> Box<BehaviorCreationFn> {
Box::new(move || {
Box::new(Self {
cases: T,
running_child_index: 0,
var: EMPTY_STR.into(),
})
})
}
pub fn register(
factory: &mut BehaviorTreeFactory,
name: &str,
groot2: bool,
) -> Result<(), crate::factory::error::Error> {
let bhvr_desc = BehaviorDescription::new(name, name, BehaviorKind::Control, groot2, Self::provided_ports());
let bhvr_creation_fn = Self::create_fn();
factory
.registry_mut()
.add_behavior(bhvr_desc, bhvr_creation_fn)
}
}
#[allow(clippy::expect_used)]
fn create_port_list(size: u8) -> PortList {
let mut ports = PortList(Vec::with_capacity(size as usize));
let port = input_port!(String, VARIABLE);
ports
.add(port)
.expect("providing port [variable] failed in behavior [Switch<T>]");
for i in 0..size {
let port = input_port!(String, CASES[i as usize]);
ports
.add(port)
.expect("providing port [case_T] failed in behavior [Switch<T>]");
}
ports
}