#![doc = include_str!("../readme.inc.md")]
#![deny(missing_docs)]
use std::{borrow::Cow, ops::RangeInclusive};
use bevy::prelude::*;
use ego_tree::*;
mod behave_trigger;
mod ctx;
mod dyn_bundle;
mod plugin;
#[cfg(test)]
mod tests;
use behave_trigger::*;
use ctx::*;
use dyn_bundle::prelude::*;
pub use ego_tree;
use plugin::TickCtx;
pub mod prelude {
pub use super::behave;
pub use super::behave_trigger::BehaveTrigger;
pub use super::ctx::*;
pub use super::plugin::*;
pub use super::{Behave, BehaveFinished};
pub use ego_tree::*;
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
enum BehaveNodeStatus {
Success,
Failure,
Running,
RunningTimer,
AwaitingTrigger,
PendingReset,
}
#[derive(Component, Reflect, Debug)]
pub struct BehaveFinished(pub bool);
#[derive(Clone)]
pub enum Behave {
Wait(f32),
DynamicEntity {
name: Cow<'static, str>,
dynamic_bundel: DynamicBundel,
},
Sequence,
Fallback,
Invert,
AlwaysSucceed,
AlwaysFail,
TriggerReq(DynamicTrigger),
Forever,
While,
IfThen,
}
impl std::fmt::Display for Behave {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Behave::While => write!(f, "While"),
Behave::Wait(secs) => write!(f, "Wait({secs}s)"),
Behave::DynamicEntity { name, .. } => write!(f, "Spawn({name})"),
Behave::Sequence => write!(f, "Sequence"),
Behave::Fallback => write!(f, "Fallback"),
Behave::Invert => write!(f, "Invert"),
Behave::AlwaysSucceed => write!(f, "AlwaysSucceed"),
Behave::AlwaysFail => write!(f, "AlwaysFail"),
Behave::TriggerReq(t) => write!(f, "Trigger({})", t.type_name()),
Behave::Forever => write!(f, "Forever"),
Behave::IfThen => write!(f, "IfThen"),
}
}
}
impl Behave {
pub fn spawn<T: Bundle + Clone>(bundle: T) -> Behave {
Behave::DynamicEntity {
name: "unnamed".into(),
dynamic_bundel: DynamicBundel::new(bundle),
}
}
pub fn spawn_named<T: Bundle + Clone>(name: impl Into<Cow<'static, str>>, bundle: T) -> Behave {
let name = name.into();
Behave::DynamicEntity {
name: name.clone(),
dynamic_bundel: DynamicBundel::new((Name::new(name), bundle)),
}
}
pub fn trigger<T: Clone + Send + Sync + 'static>(value: T) -> Self {
Behave::TriggerReq(DynamicTrigger::new(value))
}
pub(crate) fn permitted_children(&self) -> RangeInclusive<usize> {
match self {
Behave::Sequence => 0..=usize::MAX,
Behave::Fallback => 0..=usize::MAX,
Behave::Forever => 1..=usize::MAX,
Behave::While => 1..=2,
Behave::IfThen => 2..=3,
Behave::Invert => 1..=1,
Behave::Wait(_) => 0..=0,
Behave::TriggerReq(_) => 0..=0,
Behave::DynamicEntity { .. } => 0..=0,
Behave::AlwaysSucceed => 0..=0,
Behave::AlwaysFail => 0..=0,
}
}
}
#[derive(Clone)]
pub(crate) enum BehaveNode {
Forever {
status: Option<BehaveNodeStatus>,
},
Wait {
start_time: Option<f32>,
secs_to_wait: f32,
status: Option<BehaveNodeStatus>,
},
DynamicEntity {
task_status: EntityTaskStatus,
status: Option<BehaveNodeStatus>,
bundle: DynamicBundel,
name: Cow<'static, str>,
},
SequenceFlow {
status: Option<BehaveNodeStatus>,
},
FallbackFlow {
status: Option<BehaveNodeStatus>,
},
Invert {
status: Option<BehaveNodeStatus>,
},
AlwaysSucceed {
status: Option<BehaveNodeStatus>,
},
AlwaysFail {
status: Option<BehaveNodeStatus>,
},
TriggerReq {
status: Option<BehaveNodeStatus>,
task_status: TriggerTaskStatus,
trigger: DynamicTrigger,
},
While {
status: Option<BehaveNodeStatus>,
},
IfThen {
status: Option<BehaveNodeStatus>,
},
}
#[derive(Clone, Debug)]
enum EntityTaskStatus {
NotStarted,
Started(Entity),
Complete(bool),
}
#[derive(Clone, Debug)]
enum TriggerTaskStatus {
NotTriggered,
Triggered,
Complete(bool),
}
impl BehaveNode {
fn status(&self) -> &Option<BehaveNodeStatus> {
match self {
BehaveNode::Forever { status } => status,
BehaveNode::TriggerReq { status, .. } => status,
BehaveNode::Wait { status, .. } => status,
BehaveNode::DynamicEntity { status, .. } => status,
BehaveNode::SequenceFlow { status } => status,
BehaveNode::FallbackFlow { status } => status,
BehaveNode::Invert { status } => status,
BehaveNode::AlwaysSucceed { status } => status,
BehaveNode::AlwaysFail { status } => status,
BehaveNode::While { status } => status,
BehaveNode::IfThen { status } => status,
}
}
fn status_mut(&mut self) -> &mut Option<BehaveNodeStatus> {
match self {
BehaveNode::Forever { status } => status,
BehaveNode::TriggerReq { status, .. } => status,
BehaveNode::Wait { status, .. } => status,
BehaveNode::DynamicEntity { status, .. } => status,
BehaveNode::SequenceFlow { status } => status,
BehaveNode::FallbackFlow { status } => status,
BehaveNode::Invert { status } => status,
BehaveNode::AlwaysSucceed { status } => status,
BehaveNode::AlwaysFail { status } => status,
BehaveNode::While { status } => status,
BehaveNode::IfThen { status } => status,
}
}
}
impl std::fmt::Display for BehaveNode {
#[rustfmt::skip]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BehaveNode::Forever { .. } => write!(f, "Forever")?,
BehaveNode::TriggerReq { trigger, .. } => write!(f, "TriggerReq({})", trigger.type_name())?,
BehaveNode::Wait { secs_to_wait, .. } => write!(f, "Wait({secs_to_wait})")?,
BehaveNode::DynamicEntity {name, .. } => write!(f, "DynamicEntity({name})")?,
BehaveNode::SequenceFlow { .. } => write!(f, "SequenceFlow")?,
BehaveNode::FallbackFlow { .. } => write!(f, "FallbackFlow")?,
BehaveNode::Invert { .. } => write!(f, "Invert")?,
BehaveNode::AlwaysSucceed { .. } => write!(f, "AlwaysSucceed")?,
BehaveNode::AlwaysFail { .. } => write!(f, "AlwaysFail")?,
BehaveNode::While { .. } => write!(f, "While")?,
BehaveNode::IfThen { .. } => write!(f, "IfThen")?,
}
match self.status() {
Some(BehaveNodeStatus::Success) => write!(f, " --> ✅"),
Some(BehaveNodeStatus::Failure) => write!(f, " --> ❌"),
Some(BehaveNodeStatus::Running) => write!(f, " --> ⏳"),
Some(BehaveNodeStatus::RunningTimer) => write!(f, " --> ⏳"),
Some(BehaveNodeStatus::AwaitingTrigger) => write!(f, " --> ⏳"),
Some(BehaveNodeStatus::PendingReset) => write!(f, " --> 🔄"),
_ => Ok(()),
}
}
}
impl BehaveNode {
pub(crate) fn reset(&mut self) {
match self {
BehaveNode::Forever { status } => {
*status = None;
}
BehaveNode::TriggerReq {
status,
task_status,
..
} => {
*status = None;
*task_status = TriggerTaskStatus::NotTriggered;
}
BehaveNode::Wait {
status, start_time, ..
} => {
*status = None;
*start_time = None;
}
BehaveNode::DynamicEntity {
status,
task_status,
..
} => {
*status = None;
*task_status = EntityTaskStatus::NotStarted;
}
BehaveNode::SequenceFlow { status } => {
*status = None;
}
BehaveNode::FallbackFlow { status } => {
*status = None;
}
BehaveNode::Invert { status } => {
*status = None;
}
BehaveNode::AlwaysSucceed { status } => {
*status = None;
}
BehaveNode::AlwaysFail { status } => {
*status = None;
}
BehaveNode::While { status } => {
*status = None;
}
BehaveNode::IfThen { status } => {
*status = None;
}
}
}
pub(crate) fn new(behave: Behave) -> Self {
match behave {
Behave::Forever => Self::Forever { status: None },
Behave::TriggerReq(trig_fn) => Self::TriggerReq {
status: None,
task_status: TriggerTaskStatus::NotTriggered,
trigger: trig_fn,
},
Behave::Wait(secs_to_wait) => Self::Wait {
start_time: None,
secs_to_wait,
status: None,
},
Behave::DynamicEntity {
name,
dynamic_bundel: bundle,
} => Self::DynamicEntity {
task_status: EntityTaskStatus::NotStarted,
status: None,
bundle,
name,
},
Behave::While => Self::While { status: None },
Behave::Sequence => Self::SequenceFlow { status: None },
Behave::Fallback => Self::FallbackFlow { status: None },
Behave::Invert => Self::Invert { status: None },
Behave::AlwaysSucceed => Self::AlwaysSucceed { status: None },
Behave::AlwaysFail => Self::AlwaysFail { status: None },
Behave::IfThen => Self::IfThen { status: None },
}
}
}
fn reset_descendants(n: &mut NodeMut<BehaveNode>) {
n.value().reset();
if let Some(mut sibling) = n.next_sibling() {
reset_descendants(&mut sibling);
}
if let Some(mut child) = n.first_child() {
reset_descendants(&mut child);
}
}
fn tick_node(
n: &mut NodeMut<BehaveNode>,
commands: &mut Commands,
tick_ctx: &TickCtx,
) -> BehaveNodeStatus {
use BehaveNode::*;
let reset_needed = match n.value().status() {
Some(BehaveNodeStatus::Success) => return BehaveNodeStatus::Success,
Some(BehaveNodeStatus::Failure) => return BehaveNodeStatus::Failure,
Some(BehaveNodeStatus::PendingReset) => true,
_ => false,
};
if reset_needed {
*n.value().status_mut() = Some(BehaveNodeStatus::Running);
reset_descendants(n);
}
let task_node = n.id();
match n.value() {
While { .. } => {
*n.value().status_mut() = Some(BehaveNodeStatus::Running);
let mut first_child = n
.first_child()
.expect("While node first child must exist (the conditional)");
match tick_node(&mut first_child, commands, tick_ctx) {
BehaveNodeStatus::Success => {
*first_child.value().status_mut() = Some(BehaveNodeStatus::Success);
if let Some(mut second_child) = first_child.next_sibling() {
match tick_node(&mut second_child, commands, tick_ctx) {
BehaveNodeStatus::Success => {
*second_child.value().status_mut() =
Some(BehaveNodeStatus::Success);
*n.value().status_mut() = Some(BehaveNodeStatus::PendingReset);
BehaveNodeStatus::PendingReset
}
BehaveNodeStatus::PendingReset => BehaveNodeStatus::Running,
other => {
*n.value().status_mut() = Some(other);
other
}
}
} else {
*n.value().status_mut() = Some(BehaveNodeStatus::PendingReset);
BehaveNodeStatus::PendingReset
}
}
BehaveNodeStatus::PendingReset => BehaveNodeStatus::Running,
other => {
*first_child.value().status_mut() = Some(other);
*n.value().status_mut() = Some(other);
other
}
}
}
IfThen { .. } => {
*n.value().status_mut() = Some(BehaveNodeStatus::Running);
let mut conditional_child = n
.first_child()
.expect("IfThen node first child must exist (the 'if condition' child)");
match tick_node(&mut conditional_child, commands, tick_ctx) {
BehaveNodeStatus::Success => {
*conditional_child.value().status_mut() = Some(BehaveNodeStatus::Success);
let mut then_child = conditional_child
.next_sibling()
.expect("IfThen node second child must exist (the 'then' child)");
let then_result = tick_node(&mut then_child, commands, tick_ctx);
*n.value().status_mut() = Some(then_result);
then_result
}
BehaveNodeStatus::Failure => {
*conditional_child.value().status_mut() = Some(BehaveNodeStatus::Failure);
if let Some(mut else_child) = conditional_child
.next_sibling()
.expect("If nodes must have exactly two or three children")
.next_sibling()
{
let else_result = tick_node(&mut else_child, commands, tick_ctx);
*n.value().status_mut() = Some(else_result);
else_result
} else {
*n.value().status_mut() = Some(BehaveNodeStatus::Failure);
BehaveNodeStatus::Failure
}
}
BehaveNodeStatus::PendingReset => BehaveNodeStatus::Running,
other => {
*n.value().status_mut() = Some(other);
other
}
}
}
Forever { .. } => {
*n.value().status_mut() = Some(BehaveNodeStatus::Running);
let mut only_child = n.first_child().expect("Forever nodes must have a child");
if only_child.has_siblings() {
panic!("Forever nodes must have a single child, not multiple children");
}
match tick_node(&mut only_child, commands, tick_ctx) {
BehaveNodeStatus::Success | BehaveNodeStatus::Failure => {
*n.value().status_mut() = Some(BehaveNodeStatus::PendingReset);
BehaveNodeStatus::PendingReset
}
BehaveNodeStatus::PendingReset => BehaveNodeStatus::Running,
other => other,
}
}
TriggerReq {
task_status: task_status @ TriggerTaskStatus::NotTriggered,
status,
trigger,
} => {
let ctx = BehaveCtx::new_for_trigger(task_node, tick_ctx);
commands.dyn_trigger(trigger.clone(), ctx);
*task_status = TriggerTaskStatus::Triggered;
*status = Some(BehaveNodeStatus::Running);
BehaveNodeStatus::Running
}
#[rustfmt::skip]
TriggerReq {task_status: TriggerTaskStatus::Complete(true), status, ..} => {
*status = Some(BehaveNodeStatus::Success);
BehaveNodeStatus::Success
}
#[rustfmt::skip]
TriggerReq {task_status: TriggerTaskStatus::Complete(false), status, ..} => {
*status = Some(BehaveNodeStatus::Failure);
BehaveNodeStatus::Failure
}
#[rustfmt::skip]
TriggerReq {task_status: TriggerTaskStatus::Triggered, status, .. } => {
*status = Some(BehaveNodeStatus::AwaitingTrigger);
BehaveNodeStatus::AwaitingTrigger
}
Invert { .. } => {
let mut only_child = n.first_child().expect("Invert nodes must have a child");
if only_child.has_siblings() {
panic!("Invert nodes must have a single child, not multiple children");
}
let res = match tick_node(&mut only_child, commands, tick_ctx) {
BehaveNodeStatus::Success => BehaveNodeStatus::Failure, BehaveNodeStatus::Failure => BehaveNodeStatus::Success, BehaveNodeStatus::PendingReset => BehaveNodeStatus::Running,
other => other,
};
let Invert { status } = n.value() else {
unreachable!("Must be an Invert");
};
*status = Some(res);
res
}
AlwaysSucceed { status } => {
*status = Some(BehaveNodeStatus::Success);
BehaveNodeStatus::Success
}
AlwaysFail { status } => {
*status = Some(BehaveNodeStatus::Failure);
BehaveNodeStatus::Failure
}
Wait {
start_time: start_time @ None,
status,
..
} => {
*start_time = Some(tick_ctx.elapsed_secs);
*status = Some(BehaveNodeStatus::Running);
BehaveNodeStatus::Running
}
Wait {
start_time: Some(start_time),
secs_to_wait,
status,
} => {
let elapsed = tick_ctx.elapsed_secs - *start_time;
if elapsed > *secs_to_wait {
*status = Some(BehaveNodeStatus::Success);
return BehaveNodeStatus::Success;
}
BehaveNodeStatus::RunningTimer
}
DynamicEntity {
task_status: task_status @ EntityTaskStatus::NotStarted,
status,
bundle,
name: _,
} => {
let mut e = commands.spawn(());
e.insert(ChildOf(tick_ctx.bt_entity));
let ctx = BehaveCtx::new_for_entity(task_node, tick_ctx, e.id());
let id = e.dyn_insert(bundle.clone(), Some(ctx)).id();
*task_status = EntityTaskStatus::Started(id);
*status = Some(BehaveNodeStatus::Running);
BehaveNodeStatus::Running
}
#[rustfmt::skip]
DynamicEntity { task_status: EntityTaskStatus::Started(_), status: status @ Some(BehaveNodeStatus::Running), .. } => {
*status = Some(BehaveNodeStatus::AwaitingTrigger);
BehaveNodeStatus::AwaitingTrigger
}
#[rustfmt::skip]
DynamicEntity{ task_status: EntityTaskStatus::Started(_), .. } => unreachable!("Short circuit should prevent this while AwaitingTrigger"),
#[rustfmt::skip]
DynamicEntity {task_status: EntityTaskStatus::Complete(true), status, ..} => {
*status = Some(BehaveNodeStatus::Success);
BehaveNodeStatus::Success
}
#[rustfmt::skip]
DynamicEntity {task_status: EntityTaskStatus::Complete(false), status, ..} => {
*status = Some(BehaveNodeStatus::Failure);
BehaveNodeStatus::Failure
}
SequenceFlow { .. } => {
let Some(mut child) = n.first_child() else {
warn!("SequenceFlow with no children, returning success anyway");
return BehaveNodeStatus::Success;
};
let mut final_status;
loop {
match tick_node(&mut child, commands, tick_ctx) {
BehaveNodeStatus::Success => {
final_status = BehaveNodeStatus::Success;
if let Ok(next_child) = child.into_next_sibling() {
child = next_child;
continue;
} else {
break;
}
}
BehaveNodeStatus::PendingReset => {
final_status = BehaveNodeStatus::Running;
break;
}
other => {
final_status = other;
break;
}
}
}
let SequenceFlow { status, .. } = n.value() else {
unreachable!("Must be a SequenceFlow");
};
*status = Some(final_status);
final_status
}
FallbackFlow { .. } => {
let Some(mut child) = n.first_child() else {
warn!("FallbackFlow with no children, returning success anyway");
return BehaveNodeStatus::Success;
};
let mut final_status;
loop {
match tick_node(&mut child, commands, tick_ctx) {
BehaveNodeStatus::Failure => {
final_status = BehaveNodeStatus::Failure;
if let Ok(next_child) = child.into_next_sibling() {
child = next_child;
continue;
} else {
break;
}
}
BehaveNodeStatus::PendingReset => {
final_status = BehaveNodeStatus::Running;
break;
}
other => {
final_status = other;
break;
}
}
}
let FallbackFlow { status, .. } = n.value() else {
unreachable!("Must be a FallbackFlow");
};
*status = Some(final_status);
final_status
}
}
}
#[macro_export]
macro_rules! behave {
(@ $n:ident { @[ $children:expr ] $(, $($tail:tt)*)? }) => {{
for child in $children {
$n.append(child);
}
$( behave!(@ $n { $($tail)* }); )?
}};
(@ $n:ident { ... $children:expr $(, $($tail:tt)*)? }) => {{
for child in $children {
$n.append_subtree(child);
}
$( behave!(@ $n { $($tail)* }); )?
}};
(@ $n:ident { @ $subtree:expr $(, $($tail:tt)*)? }) => {{
$n.append_subtree($subtree);
$( behave!(@ $n { $($tail)* }); )?
}};
(@ $n:ident { $(,)? }) => { };
(@ $n:ident { $value:expr $(,)? }) => {{
$n.append($value);
}};
(@ $n:ident { $value:expr, $($tail:tt)* }) => {{
$n.append($value);
behave!(@ $n { $($tail)* });
}};
(@ $n:ident { $value:expr => $children:tt $(,)? }) => {{
let mut node = $n.append($value);
behave!(@ node $children);
}};
(@ $n:ident { $value:expr => $children:tt, $($tail:tt)* }) => {{
let mut node = $n.append($value);
behave!(@ node $children);
behave!(@ $n { $($tail)* });
}};
($root:expr $(,)?) => { $crate::ego_tree::Tree::new($root) };
($root:expr => $children:tt $(,)?) => {{
let mut tree = $crate::ego_tree::Tree::new($root);
{
#[allow(unused)]
let mut node = tree.root_mut();
behave!(@ node $children);
}
tree
}};
}