behaviortree-core 0.1.0

Core implementaion of behaviortree
Documentation
// Copyright © 2025 Stephan Kunz
//! `Sequence`, `AsyncSequence` & `SequenceWithMemory` [`Control`] implementations.

use alloc::boxed::Box;

use behaviortree_derive::Control;
use tinyscript::SharedRuntime;

use crate::{
	self as behaviortree_core, BehaviorCreationFn, BehaviorResult, ConstString, behavior_data::BehaviorData,
	behavior_description::BehaviorDescription, behavior_state::BehaviorState, error::Error, tree::BehaviorTreeElementList,
};

/// Kind of the Sequence
#[derive(Clone, Copy, Debug)]
pub enum SequenceKind {
	/// Asynchronous
	Async,
	/// Reactive
	Reactive,
	/// Standard
	Standard,
	/// With memory
	WithMemory,
}

/// A `Sequence` ticks its children in an ordered sequence from first to last.
///
/// It implements 4 modes, which differ in how they handle a childs return value:
/// - The synchronous mode will try to succeed within one tick from its parent.
///   If any child returns [`BehaviorState::Running`], previous children will NOT be ticked again.
///   - If a child returns [`BehaviorState::Failure`] the sequence returns [`BehaviorState::Failure`].
///   - If a child returns [`BehaviorState::Running`] the sequence returns [`BehaviorState::Running`]
///     and continues with the same child at the next tick from parent.
///   - If a child returns [`BehaviorState::Success`], continue to the next sibling.
///     If all the children succeed, than this node returns [`BehaviorState::Success`].
/// - The asynchronous mode will return the flow contol after a childs success to its parent.
///   If any child returns [`BehaviorState::Running`], previous children will NOT be ticked again.
///   - If a child returns [`BehaviorState::Failure`] the sequence returns [`BehaviorState::Failure`].
///   - If a child returns [`BehaviorState::Running`], this node returns [`BehaviorState::Running`]
///     and continues with the same child at the next tick from parent.
///   - If a child returns [`BehaviorState::Success`], this node returns [`BehaviorState::Running`].
///     and continues with the next child at the next tick from parent.
///     If all children returned [`BehaviorState::Success`] the sequence returns [`BehaviorState::Success`].
/// - The reactive mode always ticks all children from first to last:
///   If any child returns [`BehaviorState::Running`], previous children will be ticked again.
///   IMPORTANT: Having asynchronous children (aka children that return [`BehaviorState::Running`]) makes
///   this behavior difficult to predict. Avoid having more than one asynchronous children!
///   - If a child returns [`BehaviorState::Failure`] the sequence returns [`BehaviorState::Failure`].
///   - If a child returns [`BehaviorState::Running`] halt the remaining siblings in the sequence
///     and return [`BehaviorState::Running`].
///   - If a child returns [`BehaviorState::Success`], tick the next child.
///     If all children returned [`BehaviorState::Success`] the sequence returns [`BehaviorState::Success`].
/// - The with memory mode remembers the previously handled children:
///   If any child returns [`BehaviorState::Running`], previous children are not ticked again.
///   - 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`].
///   - If a child returns [`BehaviorState::Success`], tick the next child.
///     If all children returned [`BehaviorState::Success`] the sequence returns [`BehaviorState::Success`].
///
/// The variants are gated behind features `sequence`, `async_sequence`, `reactive_sequence` and `sequence_with_memory` respectively.
///
/// Examples:
///
/// The synchronous variant is gated behind feature `sequence`.
/// ```xml
/// <Sequence>
///    <Behavior1/>
///    <Behavior2/>
///    <Behavior3/>
/// </Sequence>
/// ```
///
/// The asynchronous variant is gated behind feature `async_sequence`.
/// ```xml
/// <AsyncSequence>
///    <Behavior1/>
///    <Behavior2/>
///    <Behavior3/>
/// </AsyncSequence>
/// ```
///
/// The reactive variant is gated behind feature `reactive_sequence`.
/// ```xml
/// <ReactiveSequence>
///    <Behavior1/>
///    <Behavior2/>
///    <Behavior3/>
/// </ReactiveSequence>
/// ```
///
/// The variant with memory is gated behind feature `sequence_with_memory`.
/// ```xml
/// <SequenceWithMemory>
///    <Behavior1/>
///    <Behavior2/>
///    <Behavior3/>
/// </SequenceWithMemory>
/// ```
#[derive(Control, Debug)]
#[behavior(groot2, no_create, no_register, no_register_with)]
pub struct Sequences {
	/// Kind of Sequence
	kind: SequenceKind,
	/// Current child
	child_idx: usize,
	/// Amount of skipped children
	skipped: usize,
	/// Running child
	running_child_idx: i32,
}

#[async_trait::async_trait]
impl crate::behavior_traits::Behavior for Sequences {
	fn on_start(
		&mut self,
		behavior: &mut BehaviorData,
		_children: &mut BehaviorTreeElementList,
		_runtime: &SharedRuntime,
	) -> Result<(), Error> {
		self.child_idx = 0;
		self.skipped = 0;
		self.running_child_idx = -1;
		behavior.set_state(BehaviorState::Running);
		Ok(())
	}

	async fn tick(
		&mut self,
		_behavior: &mut BehaviorData,
		children: &mut BehaviorTreeElementList,
		runtime: &SharedRuntime,
	) -> BehaviorResult {
		let children_count = children.len();
		// In the reactive mode start loop from the beginning with every tick
		if matches!(self.kind, SequenceKind::Reactive) {
			self.child_idx = 0;
		}
		while self.child_idx < children_count {
			let child = &mut children[self.child_idx];
			let prev_state = child.state();
			let child_state = child.tick(runtime).await?;

			match child_state {
				BehaviorState::Failure => {
					match self.kind {
						SequenceKind::Async | SequenceKind::Reactive | SequenceKind::Standard => {
							children.reset(runtime)?;
							self.child_idx = 0;
							self.running_child_idx = -1;
						}
						SequenceKind::WithMemory => {
							// Do NOT reset children on failure
							// Halt children at and after current index
							children.halt_from(self.child_idx, runtime)?;
						}
					}
					return Ok(child_state);
				}
				BehaviorState::Idle => {
					let behavior: ConstString = match self.kind {
						SequenceKind::Async => "AsnycSequence".into(),
						SequenceKind::Reactive => "ReactiveSequence".into(),
						SequenceKind::Standard => "Sequence".into(),
						SequenceKind::WithMemory => "SequenceWithMemory".into(),
					};

					return Err(Error::State {
						behavior,
						state: child_state,
					});
				}
				BehaviorState::Running => {
					match self.kind {
						SequenceKind::Async | SequenceKind::Standard | SequenceKind::WithMemory => {}
						#[allow(clippy::cast_possible_truncation)]
						#[allow(clippy::cast_sign_loss)]
						#[allow(clippy::cast_possible_wrap)]
						SequenceKind::Reactive => {
							// halt previously running child
							if self.running_child_idx != (self.child_idx as i32) && self.running_child_idx != -1 {
								children[self.running_child_idx as usize].halt_children(runtime)?;
								self.running_child_idx = -1;
							}
							if self.running_child_idx == -1 {
								self.running_child_idx = self.child_idx as i32;
							} else if self.running_child_idx != (self.child_idx as i32) {
								// Multiple children running at the same time
								return Err(Error::Composition {
									txt: "[ReactiveSequence]: Only a single child can return Running.".into(),
								});
							}
						}
					}
					return Ok(child_state);
				}
				BehaviorState::Skipped => {
					if !matches!(self.kind, SequenceKind::WithMemory) {
						// halt current child
						child.halt_children(runtime)?;
					}

					self.child_idx += 1;
					self.skipped += 1;
					self.running_child_idx = -1;
				}
				BehaviorState::Success => {
					self.child_idx += 1;
					match self.kind {
						SequenceKind::Async => {
							if (prev_state == BehaviorState::Idle) && (self.child_idx < children_count) {
								return Ok(BehaviorState::Running);
							}
						}
						SequenceKind::Reactive => self.running_child_idx = -1,
						SequenceKind::Standard | SequenceKind::WithMemory => {}
					}
				}
			}
		}

		// All children returned Success or were skipped
		let all_skipped = self.skipped == children_count;
		if self.child_idx >= children_count {
			children.reset(runtime)?;
			self.child_idx = 0;
			self.skipped = 0;
			self.running_child_idx = -1;
		}
		if all_skipped {
			Ok(BehaviorState::Skipped)
		} else {
			Ok(BehaviorState::Success)
		}
	}
}

impl Sequences {
	/// Returns a Sequence behavior with the given kind.
	#[must_use]
	pub const fn new(kind: SequenceKind) -> Self {
		Self {
			kind,
			child_idx: 0,
			skipped: 0,
			running_child_idx: -1,
		}
	}

	/// Creates a `creation_fn()` for `Sequence` with the given kind.
	#[must_use]
	pub fn create_fn(kind: SequenceKind) -> Box<BehaviorCreationFn> {
		Box::new(move |_blackboard| Box::new(Self::new(kind)))
	}

	/// Registers the `Sequence` behavior in the factory.
	/// # Errors
	/// - if registration fails
	pub fn register_with(
		registry: &mut impl behaviortree_core::behavior_traits::BehaviorRegistry,
		name: &str,
		kind: SequenceKind,
	) -> Result<(), Error> {
		let bhvr_desc = BehaviorDescription::new(name, name, true);
		let bhvr_creation_fn = Self::create_fn(kind);
		registry.add_behavior(bhvr_desc, bhvr_creation_fn)
	}
}