Skip to content

Commit 1aa74af

Browse files
authored
Return None if ticking BT after it finishes. (#45)
Today, ticking a behavior tree after it has finished has confusing results. Looking at the code, it seems the intent was that once a `State` finishes, it is not expected to tick again. For example, if you tick a Sequence/Select after it has finished, it always returns `Running`. Here's an example test that shows this strange behavior: ```rust #[test] fn weird_behavior() { let a = 4; let mut bt = BT::new( Select(vec![ Invert(Box::new(Action(Dec))), Invert(Box::new(Action(Dec))), Invert(Box::new(Action(Dec))), ]), (), ); let (a, s, _) = tick(a, 0.1, &mut bt); assert_eq!(a, 1); assert_eq!(s, Failure); let (a, s, _) = tick(a, 0.1, &mut bt); assert_eq!(a, 1); assert_eq!(s, Running); let (a, s, _) = tick(a, 0.1, &mut bt); assert_eq!(a, 1); assert_eq!(s, Running); } ``` This is clearly wrong. We could fix this particular issue (make Sequence/Select return a finished status if you tick it again), but it's not clear that ticking a finished behavior makes sense at all - so perhaps we should stop that. This PR solves this by just keeping track of the BT returns a Success or Failure status and then prevents ticking in those cases (returning an Option)
1 parent d49d673 commit 1aa74af

11 files changed

Lines changed: 163 additions & 140 deletions

File tree

bonsai/src/bt.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ pub struct BT<A, B> {
1818
/// referred to as a "blackboard". State is written to and read from a
1919
/// blackboard, allowing nodes to share state and communicate each other.
2020
bb: B,
21+
/// Whether the tree has been finished before.
22+
finished: bool,
2123
}
2224

2325
impl<A: Clone, B> BT<A, B> {
@@ -29,10 +31,13 @@ impl<A: Clone, B> BT<A, B> {
2931
state: bt,
3032
initial_behavior: backup_behavior,
3133
bb: blackboard,
34+
finished: false,
3235
}
3336
}
3437

35-
/// Updates the cursor that tracks an event.
38+
/// Updates the cursor that tracks an event. Returns [`None`] if attempting
39+
/// to tick after this tree has already returned [`Status::Success`] or
40+
/// [`Status::Failure`].
3641
///
3742
/// The action need to return status and remaining delta time.
3843
/// Returns status and the remaining delta time.
@@ -45,12 +50,21 @@ impl<A: Clone, B> BT<A, B> {
4550
/// it actually took to complete the traversal and propagate the
4651
/// results back up to the root node
4752
#[inline]
48-
pub fn tick<E, F>(&mut self, e: &E, f: &mut F) -> (Status, f64)
53+
pub fn tick<E, F>(&mut self, e: &E, f: &mut F) -> Option<(Status, f64)>
4954
where
5055
E: UpdateEvent,
5156
F: FnMut(ActionArgs<E, A>, &mut B) -> (Status, f64),
5257
{
53-
self.state.tick(e, &mut self.bb, f)
58+
if self.finished {
59+
return None;
60+
}
61+
match self.state.tick(e, &mut self.bb, f) {
62+
result @ (Status::Success | Status::Failure, _) => {
63+
self.finished = true;
64+
Some(result)
65+
}
66+
result => Some(result),
67+
}
5468
}
5569

5670
/// Retrieve a mutable reference to the blackboard for
@@ -72,7 +86,14 @@ impl<A: Clone, B> BT<A, B> {
7286
/// PS! invoking reset_bt does not reset the Blackboard.
7387
pub fn reset_bt(&mut self) {
7488
let initial_behavior = self.initial_behavior.to_owned();
75-
self.state = State::new(initial_behavior)
89+
self.state = State::new(initial_behavior);
90+
self.finished = false;
91+
}
92+
93+
/// Whether this behavior tree is in a completed state (the last tick returned
94+
/// [`Status::Success`] or [`Status::Failure`]).
95+
pub fn is_finished(&self) -> bool {
96+
self.finished
7697
}
7798
}
7899

bonsai/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
//! acc -= 1;
6161
//! (Success, args.dt)
6262
//! }
63-
//! });
63+
//! }).unwrap();
6464
//!
6565
//! // update counter in blackboard
6666
//! let bb = bt.get_blackboard();

bonsai/src/visualizer.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -154,16 +154,18 @@ mod tests {
154154
// A test state machine that can increment and decrement.
155155
fn tick(mut acc: i32, dt: f64, bt: &mut BT<TestActions, HashMap<String, i32>>) -> (i32, Status, f64) {
156156
let e: Event = UpdateArgs { dt }.into();
157-
let (s, t) = bt.tick(&e, &mut |args, blackboard| match args.action {
158-
TestActions::Inc => {
159-
acc += 1;
160-
(Success, args.dt)
161-
}
162-
TestActions::Dec => {
163-
acc -= 1;
164-
(Success, args.dt)
165-
}
166-
});
157+
let (s, t) = bt
158+
.tick(&e, &mut |args, blackboard| match args.action {
159+
TestActions::Inc => {
160+
acc += 1;
161+
(Success, args.dt)
162+
}
163+
TestActions::Dec => {
164+
acc -= 1;
165+
(Success, args.dt)
166+
}
167+
})
168+
.unwrap();
167169
(acc, s, t)
168170
}
169171

bonsai/tests/behavior_tests.rs

Lines changed: 65 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -21,36 +21,38 @@ enum TestActions {
2121
fn tick(mut acc: i32, dt: f64, state: &mut BT<TestActions, ()>) -> (i32, bonsai_bt::Status, f64) {
2222
let e: Event = UpdateArgs { dt }.into();
2323
println!("acc {}", acc);
24-
let (s, t) = state.tick(&e, &mut |args: ActionArgs<Event, TestActions>, _| match *args.action {
25-
Inc => {
26-
acc += 1;
27-
(Success, args.dt)
28-
}
29-
Dec => {
30-
acc -= 1;
31-
(Success, args.dt)
32-
}
33-
LessThan(v) => {
34-
println!("inside less than with acc: {}", acc);
35-
if acc < v {
36-
println!("success {}<{}", acc, v);
24+
let (s, t) = state
25+
.tick(&e, &mut |args: ActionArgs<Event, TestActions>, _| match *args.action {
26+
Inc => {
27+
acc += 1;
3728
(Success, args.dt)
38-
} else {
39-
println!("failure {}>={}", acc, v);
40-
(Failure, args.dt)
4129
}
42-
}
43-
TestActions::LessThanRunningSuccess(v) => {
44-
println!("inside LessThanRunningSuccess with acc: {}", acc);
45-
if acc < v {
46-
println!("success {}<{}", acc, v);
47-
(Running, args.dt)
48-
} else {
49-
println!("failure {}>={}", acc, v);
30+
Dec => {
31+
acc -= 1;
5032
(Success, args.dt)
5133
}
52-
}
53-
});
34+
LessThan(v) => {
35+
println!("inside less than with acc: {}", acc);
36+
if acc < v {
37+
println!("success {}<{}", acc, v);
38+
(Success, args.dt)
39+
} else {
40+
println!("failure {}>={}", acc, v);
41+
(Failure, args.dt)
42+
}
43+
}
44+
TestActions::LessThanRunningSuccess(v) => {
45+
println!("inside LessThanRunningSuccess with acc: {}", acc);
46+
if acc < v {
47+
println!("success {}<{}", acc, v);
48+
(Running, args.dt)
49+
} else {
50+
println!("failure {}>={}", acc, v);
51+
(Success, args.dt)
52+
}
53+
}
54+
})
55+
.unwrap();
5456
println!("status: {:?} dt: {}", s, t);
5557

5658
(acc, s, t)
@@ -60,17 +62,19 @@ fn tick(mut acc: i32, dt: f64, state: &mut BT<TestActions, ()>) -> (i32, bonsai_
6062
fn tick_with_ref(acc: &mut i32, dt: f64, state: &mut BT<TestActions, ()>) {
6163
let e: Event = UpdateArgs { dt }.into();
6264

63-
state.tick(&e, &mut |args: ActionArgs<Event, TestActions>, _| match *args.action {
64-
Inc => {
65-
*acc += 1;
66-
(Success, args.dt)
67-
}
68-
Dec => {
69-
*acc -= 1;
70-
(Success, args.dt)
71-
}
72-
TestActions::LessThanRunningSuccess(_) | LessThan(_) => todo!(),
73-
});
65+
state
66+
.tick(&e, &mut |args: ActionArgs<Event, TestActions>, _| match *args.action {
67+
Inc => {
68+
*acc += 1;
69+
(Success, args.dt)
70+
}
71+
Dec => {
72+
*acc -= 1;
73+
(Success, args.dt)
74+
}
75+
TestActions::LessThanRunningSuccess(_) | LessThan(_) => todo!(),
76+
})
77+
.unwrap();
7478
}
7579

7680
// Each action that terminates immediately
@@ -85,8 +89,12 @@ fn test_immediate_termination() {
8589
let mut state = BT::new(seq, ());
8690
tick_with_ref(&mut a, 0.0, &mut state);
8791
assert_eq!(a, 2);
92+
assert!(state.is_finished());
93+
state.reset_bt();
8894
tick_with_ref(&mut a, 1.0, &mut state);
89-
assert_eq!(a, 2)
95+
assert_eq!(a, 4);
96+
assert!(state.is_finished());
97+
state.reset_bt();
9098
}
9199

92100
// Tree terminates after 2.001 seconds
@@ -218,14 +226,17 @@ fn test_if_less_than() {
218226
let (a, s, _) = tick(a, 0.1, &mut state);
219227
assert_eq!(a, 2);
220228
assert_eq!(s, Success);
229+
state.reset_bt();
221230
let (a, s, _) = tick(a, 0.1, &mut state);
222231
assert_eq!(a, 1);
223232
assert_eq!(s, Success);
233+
state.reset_bt();
224234
let (a, s, _) = tick(a, 0.1, &mut state);
225235
assert_eq!(a, 0);
226236
assert_eq!(s, Success);
237+
state.reset_bt();
227238
let (a, s, _) = tick(a, 0.1, &mut state);
228-
assert_eq!(a, -1);
239+
assert_eq!(a, 1);
229240
assert_eq!(s, Success);
230241
}
231242

@@ -270,50 +281,29 @@ fn test_select_succeed_on_first() {
270281

271282
let (a, _, _) = tick(a, 0.1, &mut state);
272283
assert_eq!(a, 1);
284+
state.reset_bt();
273285
let (a, _, _) = tick(a, 0.1, &mut state);
274286
assert_eq!(a, 2);
275287
}
276288

277289
#[test]
278-
fn test_select_no_state_reset() {
279-
let a: i32 = 3;
280-
let sel = Select(vec![Action(LessThan(1)), Action(Dec), Action(Inc)]);
281-
let mut state = BT::new(sel, ());
282-
283-
let (a, s, _) = tick(a, 0.1, &mut state);
284-
assert_eq!(a, 2);
285-
assert_eq!(s, Success);
286-
let (a, s, _) = tick(a, 0.1, &mut state);
287-
assert_eq!(a, 1);
288-
assert_eq!(s, Success);
289-
let (a, s, _) = tick(a, 0.1, &mut state);
290-
assert_eq!(a, 0);
291-
assert_eq!(s, Success);
292-
let (a, s, _) = tick(a, 0.1, &mut state);
293-
assert_eq!(a, -1);
294-
assert_eq!(s, Success);
295-
}
296-
297-
#[test]
298-
fn test_select_with_state_reset() {
290+
fn test_select_needs_reset() {
299291
let a: i32 = 3;
300292
let sel = Select(vec![Action(LessThan(1)), Action(Dec), Action(Inc)]);
301-
let sel_clone = sel.clone();
302293
let mut state = BT::new(sel, ());
303294

304295
let (a, s, _) = tick(a, 0.1, &mut state);
305296
assert_eq!(a, 2);
306297
assert_eq!(s, Success);
298+
state.reset_bt();
307299
let (a, s, _) = tick(a, 0.1, &mut state);
308300
assert_eq!(a, 1);
309301
assert_eq!(s, Success);
302+
state.reset_bt();
310303
let (a, s, _) = tick(a, 0.1, &mut state);
311304
assert_eq!(a, 0);
312305
assert_eq!(s, Success);
313-
314-
// reset state
315-
state = BT::new(sel_clone, ());
316-
306+
state.reset_bt();
317307
let (a, s, _) = tick(a, 0.1, &mut state);
318308
assert_eq!(a, 0);
319309
assert_eq!(s, Success);
@@ -338,26 +328,28 @@ fn test_select_and_when_all() {
338328
fn test_select_and_invert() {
339329
let a: i32 = 3;
340330
let sel = Invert(Box::new(Select(vec![Action(LessThan(1)), Action(Dec), Action(Inc)])));
341-
let whenall = WhenAll(vec![Wait(0.35), sel]);
342-
let mut state = BT::new(whenall, ());
331+
let mut state = BT::new(sel, ());
343332

344333
// Running + Failure = Failure
345334
let (a, s, _) = tick(a, 0.1, &mut state);
346335
assert_eq!(a, 2);
347336
assert_eq!(s, Failure);
337+
state.reset_bt();
348338
let (a, s, _) = tick(a, 0.3, &mut state);
349339
assert_eq!(a, 1);
350340
assert_eq!(s, Failure);
341+
state.reset_bt();
351342
let (a, s, _) = tick(a, 0.1, &mut state);
352343
assert_eq!(a, 0);
353344
assert_eq!(s, Failure);
345+
state.reset_bt();
354346
let (a, s, _) = tick(a, 0.1, &mut state);
355-
assert_eq!(a, -1);
347+
assert_eq!(a, 0);
356348
assert_eq!(s, Failure);
357349
}
358350

359351
#[test]
360-
fn test_allways_succeed() {
352+
fn test_always_succeed() {
361353
let a: i32 = 3;
362354
let sel = Sequence(vec![
363355
Wait(0.5),
@@ -375,12 +367,14 @@ fn test_allways_succeed() {
375367
let (a, s, _) = tick(a, 0.7, &mut state);
376368
assert_eq!(a, 3);
377369
assert_eq!(s, Success);
378-
let (a, s, _) = tick(a, 0.4, &mut state);
370+
state.reset_bt();
371+
let (a, s, _) = tick(a, 0.5, &mut state);
379372
assert_eq!(a, 3);
380373
assert_eq!(s, Success);
374+
state.reset_bt();
381375
let (a, s, _) = tick(a, 0.1, &mut state);
382376
assert_eq!(a, 3);
383-
assert_eq!(s, Success);
377+
assert_eq!(s, Running);
384378
}
385379

386380
#[test]

bonsai/tests/blackboard_tests.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,18 @@ pub enum TestActions {
1717
fn tick(mut acc: i32, dt: f64, bt: &mut BT<TestActions, HashMap<String, i32>>) -> i32 {
1818
let e: Event = UpdateArgs { dt }.into();
1919

20-
let (_s, _t) = bt.tick(&e, &mut |args, _| match *args.action {
21-
Inc => {
22-
acc += 1;
23-
(Success, args.dt)
24-
}
25-
Dec => {
26-
acc -= 1;
27-
(Success, args.dt)
28-
}
29-
});
20+
let (_s, _t) = bt
21+
.tick(&e, &mut |args, _| match *args.action {
22+
Inc => {
23+
acc += 1;
24+
(Success, args.dt)
25+
}
26+
Dec => {
27+
acc -= 1;
28+
(Success, args.dt)
29+
}
30+
})
31+
.unwrap();
3032

3133
// update counter in blackboard
3234
let bb = bt.get_blackboard();

0 commit comments

Comments
 (0)