Skip to main content

behaviortree_core/tree/
tree.rs

1// Copyright © 2025 Stephan Kunz
2//! [`BehaviorTree`] implementation.
3
4#[cfg(feature = "groot")]
5use alloc::boxed::Box;
6use alloc::string::String;
7#[cfg(feature = "std")]
8use alloc::vec::Vec;
9
10use databoard::Databoard;
11#[cfg(feature = "groot")]
12use embassy_sync::{
13	blocking_mutex::raw::CriticalSectionRawMutex,
14	channel::{Channel, Sender},
15};
16#[cfg(feature = "std")]
17use libloading::Library;
18use tinyscript::SharedRuntime;
19#[cfg(feature = "std")]
20use uuid::Uuid;
21
22#[cfg(feature = "groot")]
23use crate::tree::observer::groot2::{GROOT_STATE, attach_groot_callback, connector_data::Groot2ConnectorData};
24use crate::{
25	Arc, BehaviorResult, Mutex,
26	behavior_state::BehaviorState,
27	behavior_traits::BehaviorRegistry,
28	tree::{
29		error::Error,
30		tree_element::{BehaviorTreeElement, TreeElementKind},
31		tree_iter::{TreeIter, TreeIterMut},
32	},
33};
34
35#[cfg(feature = "groot")]
36use super::CHANNEL_SIZE;
37
38/// Tree identifier - 16 raw bytes (UUID-wire-compatible).
39pub type TreeId = [u8; 16];
40
41/// Recursion function to print a (sub)tree recursively, limit is a tree-depth of 127
42/// # Errors
43/// - [`Error::RecursionLimit`] if limit of 127 for tree depth is exceeded
44fn print_recursively(level: i8, behavior: &BehaviorTreeElement) -> Result<(), Error> {
45	if level == i8::MAX {
46		return Err(Error::RecursionLimit {
47			behavior: behavior.name().clone(),
48		});
49	}
50
51	let next_level = level + 1;
52	let mut indentation = String::new();
53	for _ in 0..level {
54		indentation.push_str("  ");
55	}
56
57	#[cfg(feature = "std")]
58	std::println!("{indentation}{}", behavior.name());
59	for child in &**behavior.children() {
60		print_recursively(next_level, child)?;
61	}
62	Ok(())
63}
64
65#[cfg(feature = "groot")]
66#[derive(Clone, Default)]
67pub enum BehaviorTreeMessage {
68	#[default]
69	NothingToDo,
70	AddGrootCallback(Arc<Mutex<Groot2ConnectorData>>),
71	RemoveAllGrootHooks,
72}
73
74/// A Tree of [`BehaviorTreeElement`]s.
75/// A certain [`BehaviorTree`] can contain up to 65536 [`BehaviorTreeElement`]s.
76pub struct BehaviorTree {
77	/// The trees unique id
78	uuid: TreeId,
79	/// Root element of tree.
80	root: BehaviorTreeElement,
81	/// `runtime` is shared between elements.
82	runtime: SharedRuntime,
83	#[cfg(feature = "groot")]
84	channel: &'static Channel<CriticalSectionRawMutex, BehaviorTreeMessage, CHANNEL_SIZE>,
85	/// `libraries` stores a reference to the used shared libraries aka plugins.
86	/// This is necessary to avoid memory deallocation of libs while tree is in use.
87	#[cfg(feature = "std")]
88	_libraries: Vec<Arc<Library>>,
89}
90
91impl BehaviorTree {
92	/// create a Tree with reference to its libraries
93	#[must_use]
94	pub fn new(root: BehaviorTreeElement, registry: &impl BehaviorRegistry) -> Self {
95		// create a [`SharedRuntime`](https://docs.rs/tinyscript/latest/tinyscript/runtime/type.SharedRuntime.html)
96		// based on the current state of registriesscripting runtime
97		let runtime = Arc::new(Mutex::new(registry.runtime().clone()));
98		// clone the current state of registered libraries so that they are not deallocated while tree is running
99		#[cfg(feature = "std")]
100		let mut libraries = Vec::with_capacity(registry.libraries().capacity() + 1);
101		#[cfg(feature = "std")]
102		for lib in registry.libraries() {
103			libraries.push(lib.clone());
104		}
105
106		#[cfg(feature = "groot")]
107		let channel: &'static Channel<CriticalSectionRawMutex, BehaviorTreeMessage, CHANNEL_SIZE> =
108			Box::leak(Box::new(Channel::new()));
109		Self {
110			#[cfg(feature = "std")]
111			uuid: Uuid::new_v4().into_bytes(),
112			#[cfg(not(feature = "std"))]
113			uuid: [0u8; 16], // @TODO: replace with unique value
114			root,
115			runtime,
116			#[cfg(feature = "groot")]
117			channel,
118			#[cfg(feature = "std")]
119			_libraries: libraries,
120		}
121	}
122
123	/// Access the root blackboard of the tree.
124	#[must_use]
125	pub const fn blackboard(&self) -> &Databoard {
126		self.root.data().blackboard()
127	}
128
129	/// Access the root blackboard of the tree.
130	#[must_use]
131	pub const fn blackboard_mut(&mut self) -> &mut Databoard {
132		self.root.data_mut().blackboard_mut()
133	}
134
135	/// Pretty print the tree.
136	/// # Errors
137	/// - if tree depth exceeds 127 (sub)tree levels.
138	pub fn print(&self) -> Result<(), Error> {
139		print_recursively(0, &self.root)
140	}
141
142	/// Get a (sub)tree where index 0 is root tree.
143	/// # Errors
144	/// - if subtree is not found.
145	pub fn subtree(&self, index: usize) -> Result<&BehaviorTreeElement, Error> {
146		let mut i = 0_usize;
147		for element in self.iter() {
148			if matches!(element.kind(), TreeElementKind::SubTree) {
149				if i == index {
150					return Ok(element);
151				}
152				i += 1;
153			}
154		}
155		Err(Error::SubtreeNotFound { index })
156	}
157
158	/// Get the trees uuid.
159	#[must_use]
160	pub const fn uuid(&self) -> &TreeId {
161		&self.uuid
162	}
163
164	// /// Set the trees uuid.
165	// pub fn set_uuid(&mut self, uuid: TreeId) {
166	// 	self.uuid = uuid;
167	// }
168
169	/// Get a message sender.
170	/// This sender can be used to modify the tree while running.
171	#[cfg(feature = "groot")]
172	#[must_use]
173	pub fn sender(&self) -> Sender<'static, CriticalSectionRawMutex, BehaviorTreeMessage, CHANNEL_SIZE> {
174		self.channel.sender()
175	}
176
177	/// Get the trees total number of children.
178	#[must_use]
179	pub fn size(&self) -> u16 {
180		let mut count = 0;
181		let iter = self.iter();
182		for _ in iter {
183			count += 1;
184		}
185		count
186	}
187
188	/// Handle incoming message    
189	#[cfg(feature = "groot")]
190	fn handle_message(&mut self, message: BehaviorTreeMessage) {
191		match message {
192			BehaviorTreeMessage::RemoveAllGrootHooks => {
193				for element in self.iter_mut() {
194					element.remove_pre_state_change_callback(GROOT_STATE);
195				}
196			}
197			BehaviorTreeMessage::AddGrootCallback(data) => {
198				attach_groot_callback(self, data);
199			}
200			BehaviorTreeMessage::NothingToDo => {}
201		}
202	}
203
204	/// Ticks the tree exactly once.
205	/// # Errors
206	pub async fn tick_exactly_once(&mut self) -> BehaviorResult {
207		#[cfg(feature = "groot")]
208		while let Ok(message) = self.channel.try_receive() {
209			self.handle_message(message);
210		}
211		self.root.tick(&self.runtime).await
212	}
213
214	/// Ticks the tree once.
215	/// @TODO: The wakeup mechanism is not yet implemented
216	/// # Errors
217	pub async fn tick_once(&mut self) -> BehaviorResult {
218		#[cfg(feature = "groot")]
219		while let Ok(message) = self.channel.try_receive() {
220			self.handle_message(message);
221		}
222		self.root.tick(&self.runtime).await
223	}
224
225	/// Ticks the tree until it finishes either with [`BehaviorState::Success`] or [`BehaviorState::Failure`].
226	/// # Errors
227	pub async fn tick_while_running(&mut self) -> BehaviorResult {
228		let mut state = BehaviorState::Running;
229		while state == BehaviorState::Running || state == BehaviorState::Idle {
230			#[cfg(feature = "groot")]
231			while let Ok(message) = self.channel.try_receive() {
232				self.handle_message(message);
233			}
234			state = self.root.tick(&self.runtime).await?;
235
236			// Not implemented: Check for wake-up conditions and tick again if so
237			// Not sure if this is still necessary with real async
238			// @TODO!
239
240			// yield after every tick so other futures/tasks get scheduled
241			// (needed when behaviors complete synchronously without suspending)
242			#[cfg(feature = "std")]
243			tokio::task::yield_now().await;
244			#[cfg(not(feature = "std"))]
245			embassy_futures::yield_now().await;
246		}
247
248		// handle eventually pending messages
249		#[cfg(feature = "groot")]
250		while let Ok(message) = self.channel.try_receive() {
251			self.handle_message(message);
252		}
253		Ok(state)
254	}
255
256	/// Get an iterator over the tree.
257	pub fn iter(&self) -> impl Iterator<Item = &BehaviorTreeElement> {
258		TreeIter::new(&self.root)
259	}
260
261	/// Get a mutable iterator over the tree.
262	pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut BehaviorTreeElement> {
263		TreeIterMut::new(&mut self.root)
264	}
265
266	/// Reset tree to initial state.
267	/// # Errors
268	/// - if reset of children failed
269	pub fn reset(&mut self) -> Result<(), crate::error::Error> {
270		self.root.halt(&self.runtime)?;
271		self.runtime.lock().clear();
272		Ok(())
273	}
274}
275
276#[cfg(all(test, feature = "std"))]
277mod tests {
278	use super::*;
279
280	use core::ops::Range;
281
282	use databoard::Databoard;
283
284	use crate::{
285		BehaviorDescription,
286		behavior_data::BehaviorData,
287		behavior_state::BehaviorState,
288		behaviors::mock_behavior::{MockBehavior, MockBehaviorConfig},
289		tree::tree_element::{BehaviorTreeElement, TreeElementKind},
290	};
291
292	struct TestRegistry {
293		runtime: tinyscript::Runtime,
294		libraries: Vec<Arc<Library>>,
295	}
296
297	impl crate::behavior_traits::BehaviorRegistry for TestRegistry {
298		fn add_behavior(
299			&mut self,
300			_: BehaviorDescription,
301			_: alloc::boxed::Box<crate::BehaviorCreationFn>,
302		) -> Result<(), crate::error::Error> {
303			Ok(())
304		}
305		fn add_tree_defintion(
306			&mut self,
307			_: &str,
308			_: crate::ConstString,
309			_: Range<usize>,
310		) -> Result<(), crate::error::Error> {
311			Ok(())
312		}
313		fn libraries(&self) -> &Vec<Arc<Library>> {
314			&self.libraries
315		}
316		fn runtime(&self) -> &tinyscript::Runtime {
317			&self.runtime
318		}
319		fn register_enum_tuple(&mut self, _: &str, _: i8) -> Result<(), crate::error::Error> {
320			Ok(())
321		}
322	}
323
324	fn make_success_leaf() -> BehaviorTreeElement {
325		let config = MockBehaviorConfig {
326			return_state: BehaviorState::Success,
327			..Default::default()
328		};
329		let behavior: crate::BehaviorPtr = alloc::boxed::Box::new(MockBehavior::new(config));
330		let data = BehaviorData::create(0, Databoard::default(), BehaviorDescription::new("leaf", "leaf", false));
331		BehaviorTreeElement::create(TreeElementKind::Leaf, behavior, data)
332	}
333
334	/// Covers `BehaviorTree::new()` with a non-empty library list (lines 102-104).
335	/// Loads an actual shared library so the `for lib in registry.libraries()` body executes.
336	#[test]
337	fn new_with_library_clones_libraries() {
338		// Load a real shared library. SAFETY: Loading libc is safe in tests.
339		let lib = unsafe {
340			Library::new("/usr/lib/x86_64-linux-gnu/libc.so.6")
341				.or_else(|_| Library::new("libc.so.6"))
342				.or_else(|_| Library::new("libc.so"))
343		};
344		if let Ok(loaded_lib) = lib {
345			let registry = TestRegistry {
346				runtime: tinyscript::Runtime::default(),
347				libraries: alloc::vec![Arc::new(loaded_lib)],
348			};
349			// BehaviorTree::new clones the library into _libraries (covers lines 102-104).
350			let tree = BehaviorTree::new(make_success_leaf(), &registry);
351			assert_eq!(tree.size(), 1);
352		}
353		// If no shared library found, the test is skipped gracefully.
354	}
355
356	/// Covers the unused-but-required `BehaviorRegistry` trait methods on `TestRegistry`
357	/// (lines 290, 291, 294): `add_behavior`, `add_tree_defintion`, `register_enum_tuple`.
358	#[test]
359	fn test_registry_unused_trait_methods() {
360		let mut registry = TestRegistry {
361			runtime: tinyscript::Runtime::default(),
362			libraries: Vec::new(),
363		};
364		let desc = BehaviorDescription::new("x", "x", false);
365		let creation_fn: alloc::boxed::Box<crate::BehaviorCreationFn> =
366			alloc::boxed::Box::new(|_| alloc::boxed::Box::new(MockBehavior::new(MockBehaviorConfig::default())));
367		let _ = crate::behavior_traits::BehaviorRegistry::add_behavior(&mut registry, desc, creation_fn);
368		let _ = crate::behavior_traits::BehaviorRegistry::add_tree_defintion(&mut registry, "id", "xml".into(), 0..1);
369		let _ = crate::behavior_traits::BehaviorRegistry::register_enum_tuple(&mut registry, "E", 1);
370	}
371
372	/// Covers tick_while_running post-loop message drain (lines 246-247).
373	/// The spawned task enqueues a message; it runs during yield_now() (after the tick loop
374	/// exits but before the post-loop drain), so the post-loop drain picks it up.
375	#[tokio::test]
376	async fn tick_while_running_post_loop_drain() {
377		let registry = TestRegistry {
378			runtime: tinyscript::Runtime::default(),
379			libraries: Vec::new(),
380		};
381		let mut tree = BehaviorTree::new(make_success_leaf(), &registry);
382		let sender = tree.sender();
383
384		// Spawn: enqueues a message immediately, runs during yield_now() in tick_while_running.
385		tokio::spawn(async move {
386			let _ = sender.try_send(BehaviorTreeMessage::NothingToDo);
387		});
388
389		let state = tree.tick_while_running().await.unwrap();
390		assert_eq!(state, BehaviorState::Success);
391	}
392}