behaviortree 0.7.4

A #![no_std] compatible behavior tree library similar to 'BehaviorTree.CPP'.
Documentation
// Copyright © 2025 Stephan Kunz
//! [`SequenceWithMemory`] [`Control`] implementation.

// region:      --- modules
use crate::{
	self as behaviortree, Control,
	behavior::{Behavior, BehaviorData, BehaviorError, BehaviorResult, BehaviorState},
	tree::BehaviorTreeElementList,
};
use alloc::boxed::Box;
use tinyscript::SharedRuntime;
// endregion:   --- modules

// region:      --- SequenceWithMemory
/// A [`SequenceWithMemory`] ticks its children in an ordered sequence from first to last.
/// If any child returns [`BehaviorState::Running`], previous children are not ticked again.
/// - If all the children return [`BehaviorState::Success`], this behavior returns [`BehaviorState::Success`].
/// - If a child returns [`BehaviorState::Running`], this behavior returns [`BehaviorState::Running`].
///   The loop is NOT restarted, the same running child will be ticked again.
/// - If a child returns [`BehaviorState::Failure`], the loop stops and the behavior returns [`BehaviorState::Failure`].
///
/// The behavior is gated behind feature `sequence_with_memory`.
///
/// Example:
///
/// ```xml
/// <SequenceWithMemory>
///    <Behavior1/>
///    <Behavior2/>
///    <Behavior3/>
/// </SequenceWithMemory>
/// ```
#[derive(Control, Debug)]
#[behavior(groot2)]
pub struct SequenceWithMemory {
	/// Defaults to '0'
	child_idx: usize,
	/// Defaults to 'true'
	all_skipped: bool,
}

impl Default for SequenceWithMemory {
	fn default() -> Self {
		Self {
			child_idx: 0,
			all_skipped: true,
		}
	}
}

#[async_trait::async_trait]
impl Behavior for SequenceWithMemory {
	#[inline]
	fn on_halt(&mut self) -> Result<(), BehaviorError> {
		self.child_idx = 0;
		self.all_skipped = true;
		Ok(())
	}

	#[inline]
	fn on_start(
		&mut self,
		behavior: &mut BehaviorData,
		_children: &mut BehaviorTreeElementList,
		_runtime: &SharedRuntime,
	) -> Result<(), BehaviorError> {
		behavior.set_state(BehaviorState::Running);
		Ok(())
	}

	async fn tick(
		&mut self,
		_behavior: &mut BehaviorData,
		children: &mut BehaviorTreeElementList,
		runtime: &SharedRuntime,
	) -> BehaviorResult {
		while self.child_idx < children.len() {
			let child = &mut children[self.child_idx];
			let new_state = child.tick(runtime).await?;

			self.all_skipped &= new_state == BehaviorState::Skipped;

			match new_state {
				BehaviorState::Failure => {
					// Do NOT reset children on failure
					// Halt children at and after current index
					children.halt_from(self.child_idx, runtime)?;
					return Ok(BehaviorState::Failure);
				}
				BehaviorState::Idle => {
					return Err(BehaviorError::State {
						behavior: "SequenceWithMemory".into(),
						state: new_state,
					});
				}
				BehaviorState::Running => return Ok(BehaviorState::Running),
				BehaviorState::Skipped | BehaviorState::Success => {
					self.child_idx += 1;
				}
			}
		}

		// All children returned Success
		if self.child_idx >= children.len() {
			// Reset children
			children.halt(runtime)?;
			self.child_idx = 0;
		}

		if self.all_skipped {
			Ok(BehaviorState::Skipped)
		} else {
			Ok(BehaviorState::Success)
		}
	}
}
// endregion:   --- SequenceWithMemory