use core::{any::Any, time::Duration};
use alloc::boxed::Box;
use dataport::{PortCollection, PortCollectionAccessors, PortCollectionProvider, PortList, PortVec};
#[cfg(feature = "std")]
use std::time::Instant;
use tinyscript::SharedRuntime;
use crate::{
self as behaviortree_core, Arc, BehaviorCreationFn, BehaviorResult, ConstString, behavior_data::BehaviorData,
behavior_description::BehaviorDescription, behavior_kind::BehaviorKind, behavior_state::BehaviorState,
behavior_traits::BehaviorExecution, error::Error, tree::BehaviorTreeElementList,
};
#[derive(Clone, Default)]
pub struct MockBehaviorConfig {
pub return_state: BehaviorState,
pub kind: BehaviorKind,
pub success_script: Option<ConstString>,
pub failure_script: Option<ConstString>,
pub post_script: Option<ConstString>,
pub async_delay: Option<Duration>,
pub complete_func: Option<Arc<Box<dyn Fn() -> BehaviorState + Send + Sync>>>,
}
impl core::fmt::Debug for MockBehaviorConfig {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("MockBehaviorConfig")
.field("return_state", &self.return_state)
.field("success_script", &self.success_script)
.field("failure_script", &self.failure_script)
.field("post_script", &self.post_script)
.field("async_delay", &self.async_delay)
.finish_non_exhaustive()
}
}
impl MockBehaviorConfig {
#[must_use]
pub fn new(return_state: BehaviorState, kind: BehaviorKind) -> Self {
Self {
return_state,
kind,
..Default::default()
}
}
}
pub struct MockBehavior {
config: MockBehaviorConfig,
#[cfg(feature = "std")]
start_time: Option<Instant>,
portlist: PortVec,
}
impl BehaviorExecution for MockBehavior {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn kind(&self) -> BehaviorKind {
self.config.kind
}
fn portlist(&self) -> &dyn PortList {
&self.portlist
}
fn portlist_mut(&mut self) -> &mut dyn PortList {
&mut self.portlist
}
}
#[async_trait::async_trait]
impl crate::behavior_traits::Behavior for MockBehavior {
fn on_halt(&mut self) -> Result<(), Error> {
#[cfg(feature = "std")]
{
self.start_time = None;
}
Ok(())
}
async fn start(
&mut self,
behavior: &mut BehaviorData,
_children: &mut BehaviorTreeElementList,
runtime: &SharedRuntime,
) -> BehaviorResult {
if self.config.return_state == BehaviorState::Idle {
return Err(Error::Composition {
txt: "MockBehavior may not return IDLE".into(),
});
}
if self.config.async_delay.is_some() {
#[cfg(feature = "std")]
{
self.start_time = Some(Instant::now());
Ok(BehaviorState::Running)
}
#[cfg(not(feature = "std"))]
{
self.completed(behavior, runtime)
}
} else {
self.completed(behavior, runtime)
}
}
async fn tick(
&mut self,
behavior: &mut BehaviorData,
_children: &mut BehaviorTreeElementList,
runtime: &SharedRuntime,
) -> BehaviorResult {
#[cfg(feature = "std")]
if let Some(delay) = &self.config.async_delay
&& let Some(start) = &self.start_time
{
if Instant::now().duration_since(*start) > *delay {
self.start_time = None;
self.completed(behavior, runtime)
} else {
Ok(BehaviorState::Running)
}
} else {
self.completed(behavior, runtime)
}
#[cfg(not(feature = "std"))]
self.completed(behavior, runtime)
}
}
impl PortCollectionProvider for MockBehavior {
fn provided_ports(&self) -> &impl PortCollectionAccessors {
&self.portlist
}
fn provided_ports_mut(&mut self) -> &mut impl PortCollectionAccessors {
&mut self.portlist
}
fn port_collection(&self) -> &impl PortCollection {
&self.portlist
}
}
impl MockBehavior {
#[must_use]
pub fn new(config: MockBehaviorConfig) -> Self {
Self {
config,
#[cfg(feature = "std")]
start_time: None,
portlist: PortVec::default(),
}
}
pub const fn set_state(&mut self, state: BehaviorState) {
self.config.return_state = state;
}
#[allow(clippy::unnecessary_wraps)]
fn completed(&self, behavior: &mut BehaviorData, runtime: &SharedRuntime) -> BehaviorResult {
let state = self
.config
.complete_func
.as_ref()
.map_or(self.config.return_state, |func| (func.as_ref())());
if state == BehaviorState::Success
&& let Some(script) = &self.config.success_script
{
let _result = runtime
.lock()
.run(script, behavior.blackboard_mut())?;
} else if state == BehaviorState::Failure
&& let Some(script) = &self.config.failure_script
{
let _result = runtime
.lock()
.run(script, behavior.blackboard_mut())?;
}
if let Some(script) = &self.config.post_script {
let _result = runtime
.lock()
.run(script, behavior.blackboard_mut())?;
}
Ok(state)
}
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn create_fn(config: MockBehaviorConfig) -> Box<BehaviorCreationFn> {
Box::new(move |_blackboard| {
Box::new(Self {
config: config.clone(),
#[cfg(feature = "std")]
start_time: None,
portlist: PortVec::default(),
})
})
}
pub fn register_with(
registry: &mut impl behaviortree_core::behavior_traits::BehaviorRegistry,
name: &str,
config: MockBehaviorConfig,
groot2: bool,
) -> Result<(), Error> {
let bhvr_desc = BehaviorDescription::new(name, name, groot2);
let bhvr_creation_fn = Self::create_fn(config);
registry.add_behavior(bhvr_desc, bhvr_creation_fn)
}
}