behaviortree 0.7.4

A #![no_std] compatible behavior tree library similar to 'BehaviorTree.CPP'.
Documentation
// Copyright © 2025 Stephan Kunz
//! [`behaviortree`](crate) behavior module.

pub mod action;
pub mod behavior_data;
pub mod behavior_description;
pub mod condition;
pub mod control;
pub mod decorator;
pub mod error;
pub mod mock_behavior;
pub mod pre_post_conditions;
mod shared_queue;
mod simple_behavior;
mod sub_tree;

// flatten
pub use error::Error as BehaviorError;
pub use mock_behavior::{MockBehavior, MockBehaviorConfig};
use nanoserde::DeJson;
pub use shared_queue::SharedQueue;
pub use simple_behavior::{ComplexBhvrTickFn, SimpleBehavior, SimpleBhvrTickFn};
pub use sub_tree::SubTree;

// region:      --- modules
use crate::{
	ACTION, CONDITION, CONTROL, DECORATOR, FAILURE, IDLE, RUNNING, SKIPPED, SUBTREE, SUCCESS,
	behavior::{behavior_data::BehaviorData, behavior_description::BehaviorDescription, pre_post_conditions::Conditions},
	factory::BehaviorRegistry,
	port::PortList,
	tree::BehaviorTreeElementList,
};
use alloc::{boxed::Box, string::String};
use core::any::Any;
use databoard::{Databoard, RemappingList};
use tinyscript::SharedRuntime;
// endregion:   --- modules

// region:		--- types
/// Pointer to a behavior.
pub type BehaviorPtr = Box<dyn BehaviorExecution>;

/// Result type definition for behaviors.
pub type BehaviorResult<Output = BehaviorState> = Result<Output, BehaviorError>;

/// Type alias for a behavior creation function
pub type BehaviorCreationFn = dyn Fn() -> BehaviorPtr + Send + Sync;

/// [`BehaviorData`] state change callback signature.
///
/// This callback can be used to observe [`BehaviorData`] and manipulate the resulting [`BehaviorState`] of a tick.
pub type BehaviorTickCallback = dyn Fn(&BehaviorData, &mut BehaviorState) + Send + Sync;
// endregion:   --- types

// region:		--- BehaviorDataCollection
/// This struct is used to minimize the stack consumption during recursion of tree creation
/// and to transport the data collection between function calls.
pub(crate) struct BehaviorDataCollection<'a> {
	pub behavior_name: String,
	pub path: String,
	pub bhvr_desc: BehaviorDescription,
	pub blackboard: Databoard,
	pub bhvr: Box<dyn BehaviorExecution>,
	pub remappings: RemappingList,
	pub conditions: Conditions,
	pub uid: u16,
	pub registry: &'a BehaviorRegistry,
}
// endregion:	--- BehaviorDataCollection

// region:      --- BehaviorExecution
/// Supertrait for execution of a [`Behavior`].
/// The contained functions are generated by the derive macros.
pub trait BehaviorExecution: Any + Behavior {
	/// Dynamic downcasting.
	fn as_any(&self) -> &dyn Any;

	/// Mutable dynamic downcasting.
	fn as_any_mut(&mut self) -> &mut dyn Any;

	/// Provide the boxed creation function.
	#[deprecated(since = "0.7.3", note = "use <T>::create(...)")]
	fn creation_fn() -> Box<BehaviorCreationFn>
	where
		Self: Sized;

	/// Get the [`BehaviorKind`] of the behavior that shall become a node in a behavior (sub)tree.
	#[deprecated(since = "0.7.3")]
	fn kind() -> BehaviorKind
	where
		Self: Sized;

	/// Get the `static` list of defined ports.
	fn static_provided_ports(&self) -> PortList;
}
// endregion:   --- BehaviorExecution

// region:		--- Behavior
/// Defines the methods common to all behaviors.
/// These methods are available when traversing a behavior tree.
#[async_trait::async_trait]
pub trait Behavior: Send + Sync {
	/// Method called during stop/cancel/halt of a behavior,
	/// intended to reset the internal fields of your behavior.
	///
	/// It is not necessary to care about children and maintaining state,
	/// which at this point has already be done by the tree.
	///
	/// Default implementation does nothing.
	/// # Errors
	/// - if something prevents stopping the behavior properly
	#[inline]
	fn on_halt(&mut self) -> Result<(), BehaviorError> {
		Ok(())
	}

	/// Method is called before starting to tick a behavior,
	/// intended to do preliminary stuff for your behavior.
	///
	/// In general it is not necessary to care about children
	/// and maintaining state, which will be done by the
	/// default `start()` method.
	///
	/// Default implementation does nothing.
	/// # Errors
	/// - if something prevents starting the behavior properly
	#[inline]
	fn on_start(
		&mut self,
		_behavior: &mut BehaviorData,
		_children: &mut BehaviorTreeElementList,
		_runtime: &SharedRuntime,
	) -> Result<(), BehaviorError> {
		Ok(())
	}

	/// Method is called on first tick of a behavior instead of `tick()`.
	/// If this method returns [`BehaviorState::Running`], the
	/// behavior becomes asynchronous.
	/// # Errors
	/// - if something prevents starting the behavior properly
	#[inline]
	async fn start(
		&mut self,
		behavior: &mut BehaviorData,
		children: &mut BehaviorTreeElementList,
		runtime: &SharedRuntime,
	) -> BehaviorResult {
		self.on_start(behavior, children, runtime)?;
		self.tick(behavior, children, runtime).await
	}

	/// Method to tick a behavior.
	/// # Errors
	async fn tick(
		&mut self,
		behavior: &mut BehaviorData,
		children: &mut BehaviorTreeElementList,
		runtime: &SharedRuntime,
	) -> BehaviorResult;

	/// Method to halt a behavior.
	/// # Errors
	#[inline]
	fn halt(
		&mut self,
		_behavior: &mut BehaviorData,
		children: &mut BehaviorTreeElementList,
		runtime: &SharedRuntime,
	) -> BehaviorResult {
		children.halt(runtime)?;
		self.on_halt()?;
		Ok(BehaviorState::Idle)
	}

	/// Provide the list of defined ports.
	/// Default implementation returns an empty list.
	#[must_use]
	#[inline]
	fn provided_ports() -> PortList
	where
		Self: Sized,
	{
		PortList::default()
	}
}
// endregion:	--- Behavior

// region:		--- BehaviorKind
/// All types of behaviors usable in a behavior tree.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[repr(u8)]
pub enum BehaviorKind {
	/// Action
	#[default]
	Action,
	/// Condition
	Condition,
	/// Control
	Control,
	/// Decorator
	Decorator,
	/// Subtree
	SubTree,
}

impl core::fmt::Display for BehaviorKind {
	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
		write!(f, "{}", self.as_str())
	}
}

impl BehaviorKind {
	/// Provide kind as a static str reference.
	#[must_use]
	pub const fn as_str(&self) -> &'static str {
		match self {
			Self::Action => ACTION,
			Self::Condition => CONDITION,
			Self::Control => CONTROL,
			Self::Decorator => DECORATOR,
			Self::SubTree => SUBTREE,
		}
	}
}
// endregion:	--- BehaviorKind

// region:      --- BehaviorState

/// Behavior state.
///
/// Implementation is as in BehaviorTree.CPP to be able to
/// cooperate with Groot2.
/// IMPORTANT: Behaviors should NEVER return IDLE from a tick.
#[derive(Clone, Copy, Debug, Default, DeJson, PartialEq, Eq)]
#[repr(u8)]
pub enum BehaviorState {
	/// Behavior is not executing.
	#[default]
	Idle = 0,
	/// Behavior is still executing.
	Running = 1,
	/// Behavior finished with success.
	Success = 2,
	/// Behavior execution failed.
	Failure = 3,
	/// Behavior has been skipped.
	Skipped = 4,
}

impl BehaviorState {
	/// Check if state is signaling that the behavior is active
	#[must_use]
	pub const fn is_active(&self) -> bool {
		matches!(self, Self::Idle | Self::Skipped)
	}

	/// Check if state is signaling that the behavior is completed
	#[must_use]
	pub const fn is_completed(&self) -> bool {
		matches!(self, Self::Success | Self::Failure)
	}

	/// Provide kind as a static str reference.
	#[must_use]
	pub const fn as_str(&self) -> &'static str {
		match self {
			Self::Idle => IDLE,
			Self::Running => RUNNING,
			Self::Success => SUCCESS,
			Self::Failure => FAILURE,
			Self::Skipped => SKIPPED,
		}
	}
}

impl core::fmt::Display for BehaviorState {
	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
		write!(f, "{}", self.as_str())
	}
}

impl core::str::FromStr for BehaviorState {
	type Err = BehaviorError;

	fn from_str(s: &str) -> Result<Self, Self::Err> {
		let s = s.to_ascii_lowercase();
		let res = match s.as_ref() {
			"idle" => Self::Idle,
			"running" => Self::Running,
			"success" => Self::Success,
			"failure" => Self::Failure,
			"skipped" => Self::Skipped,
			_ => {
				return Err(BehaviorError::ParseError {
					value: s.into(),
					src: "BehaviorState::from_str()".into(),
				});
			}
		};
		Ok(res)
	}
}
// endregion:   --- BehaviorState

#[cfg(test)]
mod tests {
	use super::*;

	// check, that the auto traits are available
	const fn is_normal<T: Sized + Send + Sync>() {}

	#[test]
	const fn normal_types() {
		is_normal::<error::Error>();
		is_normal::<BehaviorData>();
		is_normal::<BehaviorDataCollection>();
		is_normal::<BehaviorDescription>();
	}
}