#[cfg(feature = "groot")]
use alloc::boxed::Box;
use alloc::string::String;
#[cfg(feature = "std")]
use alloc::vec::Vec;
use databoard::Databoard;
#[cfg(feature = "groot")]
use embassy_sync::{
blocking_mutex::raw::CriticalSectionRawMutex,
channel::{Channel, Sender},
};
#[cfg(feature = "std")]
use libloading::Library;
use tinyscript::SharedRuntime;
#[cfg(feature = "std")]
use uuid::Uuid;
#[cfg(feature = "groot")]
use crate::tree::observer::groot2::{GROOT_STATE, attach_groot_callback, connector_data::Groot2ConnectorData};
use crate::{
Arc, BehaviorResult, Mutex,
behavior_state::BehaviorState,
behavior_traits::BehaviorRegistry,
tree::{
error::Error,
tree_element::{BehaviorTreeElement, TreeElementKind},
tree_iter::{TreeIter, TreeIterMut},
},
};
#[cfg(feature = "groot")]
use super::CHANNEL_SIZE;
pub type TreeId = [u8; 16];
fn print_recursively(level: i8, behavior: &BehaviorTreeElement) -> Result<(), Error> {
if level == i8::MAX {
return Err(Error::RecursionLimit {
behavior: behavior.name().clone(),
});
}
let next_level = level + 1;
let mut indentation = String::new();
for _ in 0..level {
indentation.push_str(" ");
}
#[cfg(feature = "std")]
std::println!("{indentation}{}", behavior.name());
for child in &**behavior.children() {
print_recursively(next_level, child)?;
}
Ok(())
}
#[cfg(feature = "groot")]
#[derive(Clone, Default)]
pub enum BehaviorTreeMessage {
#[default]
NothingToDo,
AddGrootCallback(Arc<Mutex<Groot2ConnectorData>>),
RemoveAllGrootHooks,
}
pub struct BehaviorTree {
uuid: TreeId,
root: BehaviorTreeElement,
runtime: SharedRuntime,
#[cfg(feature = "groot")]
channel: &'static Channel<CriticalSectionRawMutex, BehaviorTreeMessage, CHANNEL_SIZE>,
#[cfg(feature = "std")]
_libraries: Vec<Arc<Library>>,
}
impl BehaviorTree {
#[must_use]
pub fn new(root: BehaviorTreeElement, registry: &impl BehaviorRegistry) -> Self {
let runtime = Arc::new(Mutex::new(registry.runtime().clone()));
#[cfg(feature = "std")]
let mut libraries = Vec::with_capacity(registry.libraries().capacity() + 1);
#[cfg(feature = "std")]
for lib in registry.libraries() {
libraries.push(lib.clone());
}
#[cfg(feature = "groot")]
let channel: &'static Channel<CriticalSectionRawMutex, BehaviorTreeMessage, CHANNEL_SIZE> =
Box::leak(Box::new(Channel::new()));
Self {
#[cfg(feature = "std")]
uuid: Uuid::new_v4().into_bytes(),
#[cfg(not(feature = "std"))]
uuid: [0u8; 16], root,
runtime,
#[cfg(feature = "groot")]
channel,
#[cfg(feature = "std")]
_libraries: libraries,
}
}
#[must_use]
pub const fn blackboard(&self) -> &Databoard {
self.root.data().blackboard()
}
#[must_use]
pub const fn blackboard_mut(&mut self) -> &mut Databoard {
self.root.data_mut().blackboard_mut()
}
pub fn print(&self) -> Result<(), Error> {
print_recursively(0, &self.root)
}
pub fn subtree(&self, index: usize) -> Result<&BehaviorTreeElement, Error> {
let mut i = 0_usize;
for element in self.iter() {
if matches!(element.kind(), TreeElementKind::SubTree) {
if i == index {
return Ok(element);
}
i += 1;
}
}
Err(Error::SubtreeNotFound { index })
}
#[must_use]
pub const fn uuid(&self) -> &TreeId {
&self.uuid
}
#[cfg(feature = "groot")]
#[must_use]
pub fn sender(&self) -> Sender<'static, CriticalSectionRawMutex, BehaviorTreeMessage, CHANNEL_SIZE> {
self.channel.sender()
}
#[must_use]
pub fn size(&self) -> u16 {
let mut count = 0;
let iter = self.iter();
for _ in iter {
count += 1;
}
count
}
#[cfg(feature = "groot")]
fn handle_message(&mut self, message: BehaviorTreeMessage) {
match message {
BehaviorTreeMessage::RemoveAllGrootHooks => {
for element in self.iter_mut() {
element.remove_pre_state_change_callback(GROOT_STATE);
}
}
BehaviorTreeMessage::AddGrootCallback(data) => {
attach_groot_callback(self, data);
}
BehaviorTreeMessage::NothingToDo => {}
}
}
pub async fn tick_exactly_once(&mut self) -> BehaviorResult {
#[cfg(feature = "groot")]
while let Ok(message) = self.channel.try_receive() {
self.handle_message(message);
}
self.root.tick(&self.runtime).await
}
pub async fn tick_once(&mut self) -> BehaviorResult {
#[cfg(feature = "groot")]
while let Ok(message) = self.channel.try_receive() {
self.handle_message(message);
}
self.root.tick(&self.runtime).await
}
pub async fn tick_while_running(&mut self) -> BehaviorResult {
let mut state = BehaviorState::Running;
while state == BehaviorState::Running || state == BehaviorState::Idle {
#[cfg(feature = "groot")]
while let Ok(message) = self.channel.try_receive() {
self.handle_message(message);
}
state = self.root.tick(&self.runtime).await?;
#[cfg(feature = "std")]
tokio::task::yield_now().await;
#[cfg(not(feature = "std"))]
embassy_futures::yield_now().await;
}
#[cfg(feature = "groot")]
while let Ok(message) = self.channel.try_receive() {
self.handle_message(message);
}
Ok(state)
}
pub fn iter(&self) -> impl Iterator<Item = &BehaviorTreeElement> {
TreeIter::new(&self.root)
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut BehaviorTreeElement> {
TreeIterMut::new(&mut self.root)
}
pub fn reset(&mut self) -> Result<(), crate::error::Error> {
self.root.halt(&self.runtime)?;
self.runtime.lock().clear();
Ok(())
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
use core::ops::Range;
use databoard::Databoard;
use crate::{
BehaviorDescription,
behavior_data::BehaviorData,
behavior_state::BehaviorState,
behaviors::mock_behavior::{MockBehavior, MockBehaviorConfig},
tree::tree_element::{BehaviorTreeElement, TreeElementKind},
};
struct TestRegistry {
runtime: tinyscript::Runtime,
libraries: Vec<Arc<Library>>,
}
impl crate::behavior_traits::BehaviorRegistry for TestRegistry {
fn add_behavior(
&mut self,
_: BehaviorDescription,
_: alloc::boxed::Box<crate::BehaviorCreationFn>,
) -> Result<(), crate::error::Error> {
Ok(())
}
fn add_tree_defintion(
&mut self,
_: &str,
_: crate::ConstString,
_: Range<usize>,
) -> Result<(), crate::error::Error> {
Ok(())
}
fn libraries(&self) -> &Vec<Arc<Library>> {
&self.libraries
}
fn runtime(&self) -> &tinyscript::Runtime {
&self.runtime
}
fn register_enum_tuple(&mut self, _: &str, _: i8) -> Result<(), crate::error::Error> {
Ok(())
}
}
fn make_success_leaf() -> BehaviorTreeElement {
let config = MockBehaviorConfig {
return_state: BehaviorState::Success,
..Default::default()
};
let behavior: crate::BehaviorPtr = alloc::boxed::Box::new(MockBehavior::new(config));
let data = BehaviorData::create(0, Databoard::default(), BehaviorDescription::new("leaf", "leaf", false));
BehaviorTreeElement::create(TreeElementKind::Leaf, behavior, data)
}
#[test]
fn new_with_library_clones_libraries() {
let lib = unsafe {
Library::new("/usr/lib/x86_64-linux-gnu/libc.so.6")
.or_else(|_| Library::new("libc.so.6"))
.or_else(|_| Library::new("libc.so"))
};
if let Ok(loaded_lib) = lib {
let registry = TestRegistry {
runtime: tinyscript::Runtime::default(),
libraries: alloc::vec![Arc::new(loaded_lib)],
};
let tree = BehaviorTree::new(make_success_leaf(), ®istry);
assert_eq!(tree.size(), 1);
}
}
#[test]
fn test_registry_unused_trait_methods() {
let mut registry = TestRegistry {
runtime: tinyscript::Runtime::default(),
libraries: Vec::new(),
};
let desc = BehaviorDescription::new("x", "x", false);
let creation_fn: alloc::boxed::Box<crate::BehaviorCreationFn> =
alloc::boxed::Box::new(|_| alloc::boxed::Box::new(MockBehavior::new(MockBehaviorConfig::default())));
let _ = crate::behavior_traits::BehaviorRegistry::add_behavior(&mut registry, desc, creation_fn);
let _ = crate::behavior_traits::BehaviorRegistry::add_tree_defintion(&mut registry, "id", "xml".into(), 0..1);
let _ = crate::behavior_traits::BehaviorRegistry::register_enum_tuple(&mut registry, "E", 1);
}
#[tokio::test]
async fn tick_while_running_post_loop_drain() {
let registry = TestRegistry {
runtime: tinyscript::Runtime::default(),
libraries: Vec::new(),
};
let mut tree = BehaviorTree::new(make_success_leaf(), ®istry);
let sender = tree.sender();
tokio::spawn(async move {
let _ = sender.try_send(BehaviorTreeMessage::NothingToDo);
});
let state = tree.tick_while_running().await.unwrap();
assert_eq!(state, BehaviorState::Success);
}
}