Skip to main content

behaviortree_core/behaviors/
fallbacks.rs

1// Copyright © 2025 Stephan Kunz
2//! `Fallback` & `AsyncFallback` [`Control`] implementations.
3
4use alloc::boxed::Box;
5
6use behaviortree_derive::Control;
7use tinyscript::SharedRuntime;
8
9use crate::{
10	self as behaviortree_core, BehaviorCreationFn, BehaviorResult, ConstString, behavior_data::BehaviorData,
11	behavior_description::BehaviorDescription, behavior_state::BehaviorState, error::Error, tree::BehaviorTreeElementList,
12};
13
14/// Kind of the Fallback
15#[derive(Clone, Copy, Debug)]
16pub enum FallbackKind {
17	/// Asynchronous
18	Async,
19	/// Reactive
20	Reactive,
21	/// Standard
22	Standard,
23}
24
25/// The `Fallback` behavior is used to try different strategies until one succeeds.
26///
27/// It implements 3 modes, which differ in how they handle a childs return value:
28/// - The synchronous mode will try to succeed within one tick from its parent.
29///   If any child returns [`BehaviorState::Running`], previous children will NOT be ticked again.
30///   - If a child returns [`BehaviorState::Failure`], continue to the next sibling.
31///     If all the children fail, than this node returns [`BehaviorState::Failure`].
32///   - If a child returns [`BehaviorState::Running`], this node returns [`BehaviorState::Running`]
33///     and continues with the same child at the next tick from parent.
34///   - If a child returns [`BehaviorState::Success`] this behavior returns [`BehaviorState::Success`].
35/// - The asynchronous mode will return the flow contol after a childs failure to its parent.
36///   If any child returns [`BehaviorState::Running`], previous children will NOT be ticked again.
37///   - If a the children returns [`BehaviorState::Failure`], this node returns [`BehaviorState::Running`].
38///     and continues with the next child at the next tick from parent.
39///     If all the children fail, than this node returns [`BehaviorState::Failure`].
40///   - If a child returns [`BehaviorState::Running`], this node returns [`BehaviorState::Running`]
41///     and continues with the same child at the next tick from parent.
42///   - If a child returns [`BehaviorState::Success`] this behavior returns [`BehaviorState::Success`].
43/// - The reactive mode always ticks all children from first to last:
44///   If any child returns [`BehaviorState::Running`], previous children will be ticked again.
45///   IMPORTANT: Having asynchronous children (aka children that return [`BehaviorState::Running`]) makes
46///   this behavior difficult to predict. Avoid having more than one asynchronous children!
47///   - If a child returns [`BehaviorState::Running`], continue to the next sibling.
48///   - If a child returns [`BehaviorState::Failure`], continue to the next sibling.
49///     If all the children fail, than this node returns [`BehaviorState::Failure`].
50///   - If a child returns [`BehaviorState::Success`], stop running child and return [`BehaviorState::Success`].
51///
52/// The variants are gated behind features `fallback`,`async_fallback` and `reactive_fallback` respectively.
53///
54/// Examples:
55///
56/// The synchronous variant is gated behind feature `fallback`.
57/// ```xml
58/// <Fallback>
59///    <Behavior1/>
60///    <Behavior2/>
61///    <Behavior3/>
62/// </Fallback>
63/// ```
64///
65/// The asynchronous variant is gated behind feature `async_fallback`.
66/// ```xml
67/// <AsyncFallback>
68///    <Behavior1/>
69///    <Behavior2/>
70///    <Behavior3/>
71/// </AsyncFallback>
72/// ```
73#[derive(Control, Debug)]
74#[behavior(groot2, no_create, no_register, no_register_with)]
75pub struct Fallbacks {
76	/// Kind of Fallback
77	kind: FallbackKind,
78	/// Current child
79	child_idx: usize,
80	/// Amount of skipped children
81	skipped: usize,
82	/// Running child
83	running_child_idx: i32,
84}
85
86#[async_trait::async_trait]
87impl crate::behavior_traits::Behavior for Fallbacks {
88	fn on_start(
89		&mut self,
90		behavior: &mut BehaviorData,
91		_children: &mut BehaviorTreeElementList,
92		_runtime: &SharedRuntime,
93	) -> Result<(), Error> {
94		self.child_idx = 0;
95		self.skipped = 0;
96		self.running_child_idx = -1;
97		behavior.set_state(BehaviorState::Running);
98		Ok(())
99	}
100
101	async fn tick(
102		&mut self,
103		_behavior: &mut BehaviorData,
104		children: &mut BehaviorTreeElementList,
105		runtime: &SharedRuntime,
106	) -> BehaviorResult {
107		let children_count = children.len();
108		// In the reactive mode start loop from the beginning with every tick
109		if matches!(self.kind, FallbackKind::Reactive) {
110			self.child_idx = 0;
111		}
112		while self.child_idx < children_count {
113			let child = &mut children[self.child_idx];
114			let prev_state = child.state();
115			let child_state = child.tick(runtime).await?;
116
117			match child_state {
118				BehaviorState::Failure => {
119					self.child_idx += 1;
120					match self.kind {
121						FallbackKind::Async => {
122							if (prev_state == BehaviorState::Idle) && (self.child_idx < children_count) {
123								return Ok(BehaviorState::Running);
124							}
125						}
126						FallbackKind::Reactive => self.running_child_idx = -1,
127						FallbackKind::Standard => {}
128					}
129				}
130				BehaviorState::Idle => {
131					let behavior: ConstString = match self.kind {
132						FallbackKind::Async => "AsnycFallback".into(),
133						FallbackKind::Reactive => "ReactiveFallback".into(),
134						FallbackKind::Standard => "Fallback".into(),
135					};
136					return Err(Error::State {
137						behavior,
138						state: child_state,
139					});
140				}
141				BehaviorState::Running => {
142					match self.kind {
143						FallbackKind::Async | FallbackKind::Standard => {}
144						#[allow(clippy::cast_possible_truncation)]
145						#[allow(clippy::cast_sign_loss)]
146						#[allow(clippy::cast_possible_wrap)]
147						FallbackKind::Reactive => {
148							// halt previously running child
149							if self.running_child_idx != (self.child_idx as i32) && self.running_child_idx != -1 {
150								children[self.running_child_idx as usize].halt_children(runtime)?;
151							}
152							self.running_child_idx = self.child_idx as i32;
153							if self.running_child_idx == -1 {
154								self.running_child_idx = self.child_idx as i32;
155							} else if self.running_child_idx != (self.child_idx as i32) {
156								// Multiple children running at the same time
157								return Err(Error::Composition {
158									txt: "[ReactiveFallback]: Only a single child can return Running.".into(),
159								});
160							}
161						}
162					}
163					return Ok(child_state);
164				}
165				BehaviorState::Success => {
166					children.reset(runtime)?;
167					children.halt(runtime)?;
168					self.child_idx = 0;
169					self.skipped = 0;
170					self.running_child_idx = -1;
171					return Ok(child_state);
172				}
173				BehaviorState::Skipped => {
174					self.child_idx += 1;
175					self.skipped += 1;
176				}
177			}
178		}
179
180		// loop ended without a success,
181		// so either all children failed or were skipped
182		let all_skipped = self.skipped == children_count;
183		if self.child_idx >= children_count {
184			children.reset(runtime)?;
185			self.child_idx = 0;
186			self.skipped = 0;
187			self.running_child_idx = -1;
188		}
189		if all_skipped {
190			Ok(BehaviorState::Skipped)
191		} else {
192			Ok(BehaviorState::Failure)
193		}
194	}
195}
196
197impl Fallbacks {
198	/// Returns a Fallback behavior with the given kind.
199	#[must_use]
200	pub const fn new(kind: FallbackKind) -> Self {
201		Self {
202			kind,
203			child_idx: 0,
204			skipped: 0,
205			running_child_idx: -1,
206		}
207	}
208
209	/// Creates a `creation_fn()` for `Fallback` with the given kind.
210	#[must_use]
211	pub fn create_fn(kind: FallbackKind) -> Box<BehaviorCreationFn> {
212		Box::new(move |_blackboard| Box::new(Self::new(kind)))
213	}
214
215	/// Registers the `Fallback` behavior in the factory.
216	/// # Errors
217	/// - if registration fails
218	pub fn register_with(
219		registry: &mut impl behaviortree_core::behavior_traits::BehaviorRegistry,
220		name: &str,
221		kind: FallbackKind,
222	) -> Result<(), Error> {
223		let bhvr_desc = BehaviorDescription::new(name, name, true);
224		let bhvr_creation_fn = Self::create_fn(kind);
225		registry.add_behavior(bhvr_desc, bhvr_creation_fn)
226	}
227}