use crate::prelude::*;
#[test]
fn test_empty_control_flow() {
let tree = behave! {
Behave::Sequence => {
@[[]]
}
};
assert!(BehaveTree::verify(&tree));
assert_tree("Sequence", tree);
let tree = behave! {
Behave::Fallback => {
}
};
assert!(BehaveTree::verify(&tree));
assert_tree("Fallback", tree);
}
#[test]
fn test_at_list() {
let behaviours = [Behave::Wait(1.0), Behave::Wait(2.0), Behave::Wait(3.0)];
let tree = behave! {
Behave::Sequence => {
Behave::Wait(5.0),
@[ behaviours ]
}
};
assert_tree(
"Sequence
├── Wait(5s)
├── Wait(1s)
├── Wait(2s)
└── Wait(3s)",
tree,
);
}
#[test]
fn test_at_tree() {
let subtree = behave! {
Behave::Sequence => {
Behave::Wait(1.0),
Behave::Wait(2.0),
}
};
let tree = behave! {
Behave::Sequence => {
Behave::Wait(5.0),
@ subtree
}
};
assert_tree(
"Sequence
├── Wait(5s)
└── Sequence
├── Wait(1s)
└── Wait(2s)",
tree,
);
}
#[test]
fn test_ego_tree_api() {
let trees = [
behave! {
Behave::Wait(1.0),
},
behave! {
Behave::Sequence => {
Behave::Wait(1.0),
Behave::Wait(2.0),
}
},
];
let mut tree = ego_tree::Tree::new(Behave::Sequence);
let mut root = tree.root_mut();
root.append(Behave::Wait(0.1));
for subtree in trees.clone() {
root.append_subtree(subtree);
}
assert_tree(
"Sequence
├── Wait(0.1s)
├── Wait(1s)
└── Sequence
├── Wait(1s)
└── Wait(2s)",
tree.clone(),
);
let t2 = behave! {
Behave::Sequence => {
Behave::Wait(0.1),
... trees,
}
};
assert_eq!(tree.to_string(), t2.to_string());
}
#[test]
fn test_root_ancestor_with_nested_trees() {
use crate::prelude::*;
use bevy::prelude::*;
let mut app = App::new();
app.add_plugins(MinimalPlugins);
app.add_plugins(BehavePlugin::default());
app.add_plugins(bevy::log::LogPlugin::default());
app.add_systems(Startup, |mut commands: Commands| {
let tree = behave! {
Behave::Sequence => {
Behave::Wait(1.0),
Behave::spawn_named("Nested tree", BehaveTree::new(behave! { Behave::Wait(2.0) }).with_logging(true)),
}
};
let id = commands.spawn(BehaveTree::new(tree).with_logging(true)).id();
info!("spawned tree with id: {}", id);
});
app.add_observer(
|t: On<Add, BehaveFinished>,
q: Query<(&BehaveFinished, &BehaveCtx)>,
mut exit: MessageWriter<AppExit>,
mut commands: Commands| {
let Ok((finished, ctx)) = q.get(t.event().entity) else {
exit.write(AppExit::Success);
return;
};
if finished.0 {
commands.trigger(ctx.success());
} else {
commands.trigger(ctx.failure());
}
},
);
app.run();
}
#[test]
fn test_frame_delays_async() {
run_frame_delays(false, 5);
}
#[test]
fn test_frame_delays_sync() {
run_frame_delays(true, 1);
}
#[test]
fn test_while_node() {
use crate::prelude::*;
use bevy::prelude::*;
#[derive(Event, Clone)]
struct RunAssert;
#[derive(Event, Clone)]
struct Count;
fn should_only_run_once(
trigger: On<BehaveTrigger<RunAssert>>,
mut cmd: Commands,
mut count: Local<u32>,
) {
*count += 1;
assert_eq!(*count, 1);
cmd.trigger(trigger.ctx.success());
}
fn run_twice(
trigger: On<BehaveTrigger<Count>>,
mut cmd: Commands,
mut count: Local<u32>,
mut exit: MessageWriter<AppExit>,
) {
*count += 1;
cmd.trigger(trigger.ctx.success());
if *count == 2 {
exit.write(AppExit::Success);
}
}
let mut app = App::new();
let app = app
.add_plugins((
BehavePlugin::default(),
MinimalPlugins,
bevy::log::LogPlugin::default(),
))
.add_observer(should_only_run_once)
.add_observer(run_twice)
.add_systems(Startup, |mut cmd: Commands| {
let tree = behave! {
Behave::Forever => {
Behave::Sequence => {
Behave::trigger(RunAssert),
Behave::While => {
Behave::AlwaysSucceed,
Behave::trigger(Count),
}
}
}
};
cmd.spawn(BehaveTree::new(tree).with_logging(true));
});
app.run();
}
fn run_frame_delays(sync: bool, expected_final_frame: u32) {
use crate::prelude::*;
use bevy::prelude::*;
#[derive(Resource)]
struct ExpectedFinalFrame(u32);
#[derive(Resource, Default)]
struct Frame(u32);
fn tick_frame(mut frame: ResMut<Frame>) {
frame.0 += 1;
info!("Frame -> {}", frame.0);
}
#[derive(Clone)]
struct CheckTrigger(bool);
fn on_check_trigger(
t: On<BehaveTrigger<CheckTrigger>>,
mut commands: Commands,
frame: Res<Frame>,
) {
let CheckTrigger(success) = t.inner();
info!("CheckTrigger @ {} returning {}", frame.0, success);
if *success {
commands.trigger(t.ctx().success());
} else {
commands.trigger(t.ctx().failure());
}
}
fn final_tree_finished_checker(
_t: On<Add, BehaveFinished>,
mut exit: MessageWriter<AppExit>,
frame: Res<Frame>,
expected_final_frame: Res<ExpectedFinalFrame>,
) {
info!("Finished @ {}", frame.0);
assert_eq!(
frame.0, expected_final_frame.0,
"Mismatch on final frame number"
);
exit.write(AppExit::Success);
}
let mut app = App::new();
app.init_resource::<Frame>();
app.insert_resource(ExpectedFinalFrame(expected_final_frame));
app.add_observer(on_check_trigger);
app.add_plugins(MinimalPlugins);
if sync {
app.add_plugins(BehavePlugin::default().with_synchronous());
} else {
app.add_plugins(BehavePlugin::default());
}
app.add_plugins(bevy::log::LogPlugin::default());
app.add_systems(FixedPreUpdate, tick_frame.before(BehaveSet));
app.add_systems(Startup, |mut commands: Commands| {
let tree = behave! {
Behave::Sequence => {
Behave::trigger(CheckTrigger(true)),
Behave::spawn_named("Nested tree", BehaveTree::new(behave! { Behave::trigger(CheckTrigger(true)), }).with_logging(true)),
}
};
let id = commands.spawn(BehaveTree::new(tree).with_logging(true))
.observe(final_tree_finished_checker)
.id();
info!("spawned tree with id: {}", id);
});
app.add_observer(
|t: On<Add, BehaveFinished>,
q: Query<(&BehaveFinished, &BehaveCtx)>,
mut commands: Commands| {
let Ok((finished, ctx)) = q.get(t.event().entity) else {
return;
};
if finished.0 {
commands.trigger(ctx.success());
} else {
commands.trigger(ctx.failure());
}
},
);
app.run();
}
fn assert_tree(s: &str, tree: Tree<Behave>) {
let leading_spaces = s
.lines()
.find(|line| !line.trim().is_empty() && line.starts_with(' '))
.map(|line| line.len() - line.trim_start().len())
.unwrap_or(0);
let mut expected = s
.lines()
.map(|line| {
if line.len() >= leading_spaces {
&line[leading_spaces..]
} else {
line
}
})
.collect::<Vec<_>>()
.join("\n");
expected.push('\n');
assert_eq!(tree.to_string(), expected);
}