From fbe0776543e218108d1da8276ee388cb0f370975 Mon Sep 17 00:00:00 2001 From: Kristoffer Solberg Rakstad Date: Tue, 8 Oct 2024 16:04:43 +0200 Subject: [PATCH] refactor adding WhileAll behavior (#36) **In this MR** * Rename behavior `RepeatSequence` to `WhileAll` * Improve readme slightly for While and WhileAll behavior * Bump rust version * Bump lib version --- .gitignore | 2 ++ README.md | 6 +++--- bonsai/Cargo.toml | 4 ++-- bonsai/src/behavior.rs | 11 ++++++++--- bonsai/src/lib.rs | 1 + bonsai/src/state.rs | 12 ++++++------ bonsai/src/visualizer.rs | 6 +++--- bonsai/tests/behavior_tests.rs | 26 +++++++++++--------------- examples/src/3d/main.rs | 4 +++- examples/src/async_drone/jobs.rs | 9 +++++---- examples/src/simple_npc_ai/main.rs | 12 ++++++------ 11 files changed, 50 insertions(+), 43 deletions(-) diff --git a/.gitignore b/.gitignore index 088ba6b..cbed4d7 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk + +.idea/ diff --git a/README.md b/README.md index dd25951..bd2591b 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![Build Status](https://github.com/Sollimann/bonsai/actions/workflows/rust-ci.yml/badge.svg)](https://github.com/Sollimann/bonsai/actions) [![Bonsai crate](https://img.shields.io/crates/v/bonsai-bt.svg)](https://crates.io/crates/bonsai-bt) -[![minimum rustc 1.56](https://img.shields.io/badge/rustc-1.56+-blue.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html) +[![minimum rustc 1.72](https://img.shields.io/badge/rustc-1.56+-blue.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html) [![Docs](https://docs.rs/bonsai-bt/badge.svg)](https://docs.rs/bonsai-bt) [![codecov](https://codecov.io/gh/Sollimann/bonsai/branch/main/graph/badge.svg?token=JX8JBPWORV)](https://codecov.io/gh/Sollimann/bonsai) [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://GitHub.com/Sollimann/bonsai/graphs/commit-activity) @@ -47,12 +47,12 @@ A Behavior Tree forms a tree structure where each node represents a process. Whe For example, if you have a state `A` and a state `B`: - Move from state `A` to state `B` if `A` succeeds: `Sequence([A, B])` -- Move from state `A` to sequence of states `[B]` if `A` is running. If all states in the sequence `[B]` succeed in order, check if `A` is still running and repeat. Stop if `A` succeeds or any of the states fail: `RepeatSequence(A, [B])` - Try `A` first and then try `B` if `A` fails: `Select([A, B])` - If `condition` succeedes do `A`, else do `B` : `If(condition, A, B)` - If `A` succeeds, return failure (and vice-versa): `Invert(A)` -- Do `B` repeatedly while `A` runs: `While(A, [B])` +- Do `A`, `B` repeatedly while `LoopCondition` runs: `While(LoopCondition, [A, B])`. Checks condition node between nodes `A`, `B`. - Do `A`, `B` forever: `While(WaitForever, [A, B])` +- Do `A`, `B` repeatedly while `LoopCondition` runs: `WhileAll(LoopCondition, [A, B])`. After *All* nodes `A`, `B` are completed successfully, check the condition node. - Run `A` and `B` in parallell and wait for both to succeed: `WhenAll([A, B])` - Run `A` and `B` in parallell and wait for any to succeed: `WhenAny([A, B])` - Run `A` and `B` in parallell, but `A` has to succeed before `B`: `After([A, B])` diff --git a/bonsai/Cargo.toml b/bonsai/Cargo.toml index 2dbeec2..3a1b898 100644 --- a/bonsai/Cargo.toml +++ b/bonsai/Cargo.toml @@ -11,8 +11,8 @@ license = "MIT" name = "bonsai-bt" readme = "../README.md" repository = "https://github.com/sollimann/bonsai.git" -rust-version = "1.60.0" -version = "0.6.3" +rust-version = "1.80.0" +version = "0.7.0" [lib] name = "bonsai_bt" diff --git a/bonsai/src/behavior.rs b/bonsai/src/behavior.rs index c60aa68..173748b 100644 --- a/bonsai/src/behavior.rs +++ b/bonsai/src/behavior.rs @@ -43,10 +43,15 @@ pub enum Behavior { /// Succeeds if the conditional behavior succeeds. /// Fails if the conditional behavior fails, /// or if any behavior in the loop body fails. + /// + /// # Panics + /// + /// Panics if the given behavior sequence is empty. While(Box>, Vec>), /// Runs a sequence on repeat as long as a conditional behavior /// that precedes the sequence is running. + /// /// Conditional behavior is **only** checked before the sequence runs and /// not during the sequence. /// @@ -61,14 +66,14 @@ pub enum Behavior { /// /// ``` /// - ///use bonsai_bt::{BT, Running, Failure, Success, Action, UpdateArgs, Behavior::RepeatSequence, ActionArgs}; + ///use bonsai_bt::{BT, Running, Failure, Success, Action, UpdateArgs, Behavior::WhileAll, ActionArgs}; ///use bonsai_bt::Event; /// ///#[derive(Clone, Debug)] /// ///enum Ex { A, B, C } /// - ///let rs = RepeatSequence( + ///let rs = WhileAll( /// Box::new(Action(Ex::A)), /// vec![Action(Ex::B), Action(Ex::C)], ///); @@ -101,7 +106,7 @@ pub enum Behavior { ///}); ///assert!(i == 4); /// ``` - RepeatSequence(Box>, Vec>), + WhileAll(Box>, Vec>), /// Runs all behaviors in parallel until all succeeded. /// /// Succeeds if all behaviors succeed. diff --git a/bonsai/src/lib.rs b/bonsai/src/lib.rs index 5ad1765..1e710af 100644 --- a/bonsai/src/lib.rs +++ b/bonsai/src/lib.rs @@ -120,6 +120,7 @@ pub use behavior::Behavior::{ self, Action, After, AlwaysSucceed, If, Invert, Select, Sequence, Wait, WaitForever, WhenAll, WhenAny, While, + WhileAll, }; pub use bt::BT; diff --git a/bonsai/src/state.rs b/bonsai/src/state.rs index 19fe411..0e75c22 100644 --- a/bonsai/src/state.rs +++ b/bonsai/src/state.rs @@ -53,8 +53,8 @@ pub enum State { SequenceState(Vec>, usize, Box>), /// Keeps track of a `While` behavior. WhileState(Box>, Vec>, usize, Box>), - /// Keeps track of a `RepeatSequence` behavior. - RepeatSequenceState(Box>, Vec>, usize, bool, Box>), + /// Keeps track of a `WhileAll` behavior. + WhileAllState(Box>, Vec>, usize, bool, Box>), /// Keeps track of a `WhenAll` behavior. WhenAllState(Vec>>), /// Keeps track of a `WhenAny` behavior. @@ -97,13 +97,13 @@ impl State { Behavior::WhenAll(all) => State::WhenAllState(all.into_iter().map(|ev| Some(State::new(ev))).collect()), Behavior::WhenAny(any) => State::WhenAnyState(any.into_iter().map(|ev| Some(State::new(ev))).collect()), Behavior::After(after_all) => State::AfterState(0, after_all.into_iter().map(State::new).collect()), - Behavior::RepeatSequence(ev, rep) => { + Behavior::WhileAll(ev, rep) => { let state = State::new( rep.first() - .expect("RepeatSequence's sequence of behaviors to run cannot be empty!") + .expect("WhileAll's sequence of behaviors to run cannot be empty!") .clone(), ); - State::RepeatSequenceState(Box::new(State::new(*ev)), rep, 0, true, Box::new(state)) + State::WhileAllState(Box::new(State::new(*ev)), rep, 0, true, Box::new(state)) } } } @@ -322,7 +322,7 @@ impl State { } ( _, - &mut RepeatSequenceState( + &mut WhileAllState( ref mut condition_behavior, ref all_sequence_behaviors, ref mut cur_seq_idx, diff --git a/bonsai/src/visualizer.rs b/bonsai/src/visualizer.rs index 177d0d3..0c267ef 100644 --- a/bonsai/src/visualizer.rs +++ b/bonsai/src/visualizer.rs @@ -14,7 +14,7 @@ pub(crate) enum NodeType { Select, If, Sequence, - RepeatSequence, + WhileAll, While, WhenAll, WhenAny, @@ -92,8 +92,8 @@ impl BT { let right = Sequence(seq); Self::dfs_recursive(graph, right, node_id) } - Behavior::RepeatSequence(ev, seq) => { - let node_id = graph.add_node(NodeType::RepeatSequence); + Behavior::WhileAll(ev, seq) => { + let node_id = graph.add_node(NodeType::WhileAll); graph.add_edge(parent_node, node_id, 1); // left diff --git a/bonsai/tests/behavior_tests.rs b/bonsai/tests/behavior_tests.rs index 9cf84b8..543e14e 100644 --- a/bonsai/tests/behavior_tests.rs +++ b/bonsai/tests/behavior_tests.rs @@ -1,11 +1,7 @@ use crate::behavior_tests::TestActions::{Dec, Inc, LessThan, LessThanRunningSuccess}; -use bonsai_bt::Behavior::RepeatSequence; use bonsai_bt::{ - Action, ActionArgs, - Behavior::{After, AlwaysSucceed, If, Invert, Select}, - Event, Failure, Sequence, State, - Status::Running, - Success, UpdateArgs, Wait, WaitForever, WhenAll, While, + Action, ActionArgs, After, AlwaysSucceed, Event, Failure, If, Invert, Select, Sequence, State, Status::Running, + Success, UpdateArgs, Wait, WaitForever, WhenAll, While, WhileAll, }; /// Some test actions. @@ -437,7 +433,7 @@ fn test_after_all_succeed_out_of_order() { fn test_repeat_sequence() { { let a: i32 = 0; - let after = RepeatSequence(Box::new(Action(LessThanRunningSuccess(5))), vec![Action(Inc)]); + let after = WhileAll(Box::new(Action(LessThanRunningSuccess(5))), vec![Action(Inc)]); let mut state = State::new(after); @@ -454,7 +450,7 @@ fn test_repeat_sequence() { /// running initially, then the condition behavior cannot run more than once until the whole /// sequence has succeeded. fn test_repeat_sequence_double_running() { - let after = RepeatSequence( + let after = WhileAll( Box::new(Action(LessThanRunningSuccess(5))), // running... vec![ Action(LessThanRunningSuccess(5)), // running... until current value is 5 @@ -484,7 +480,7 @@ fn test_repeat_sequence_double_running() { #[test] fn test_repeat_sequence2() { - let after = RepeatSequence( + let after = WhileAll( Box::new(Action(LessThanRunningSuccess(5))), // running... vec![ Action(LessThanRunningSuccess(10)), // running... until current value is 5 @@ -516,7 +512,7 @@ fn test_repeat_sequence2() { #[test] fn test_repeat_sequence3() { - let after = RepeatSequence( + let after = WhileAll( Box::new(Action(LessThanRunningSuccess(2))), vec![ Action(LessThanRunningSuccess(10)), @@ -553,9 +549,9 @@ fn test_repeat_sequence_nested() { let dec2 = Sequence(vec![Action(Dec), Action(Dec)]); let inc1 = Sequence(vec![Action(Inc)]); - let nested = RepeatSequence(Box::new(Action(LessThanRunningSuccess(5))), vec![Action(Inc), inc1]); + let nested = WhileAll(Box::new(Action(LessThanRunningSuccess(5))), vec![Action(Inc), inc1]); - let after = RepeatSequence( + let after = WhileAll( Box::new(Action(LessThanRunningSuccess(1))), vec![ nested, // inc to 6 @@ -586,7 +582,7 @@ fn test_repeat_sequence_nested() { fn test_repeat_sequence_fail() { { let a: i32 = 4; - let after = RepeatSequence( + let after = WhileAll( Box::new(Action(LessThanRunningSuccess(5))), vec![Action(Dec), Action(LessThan(0))], ); @@ -603,7 +599,7 @@ fn test_repeat_sequence_timed() { let a: i32 = 0; let time_step = 0.1; let steps = 5; - let after = RepeatSequence( + let after = WhileAll( Box::new(Action(LessThanRunningSuccess(steps))), vec![Wait(time_step), Action(Inc)], ); @@ -624,7 +620,7 @@ fn test_repeat_sequence_timed() { #[test] #[should_panic] fn test_repeat_sequence_empty() { - let after = RepeatSequence(Box::new(Action(LessThanRunningSuccess(0))), vec![]); + let after = WhileAll(Box::new(Action(LessThanRunningSuccess(0))), vec![]); // panics because no behaviors... let _state = State::new(after); } diff --git a/examples/src/3d/main.rs b/examples/src/3d/main.rs index eeca028..7e42401 100644 --- a/examples/src/3d/main.rs +++ b/examples/src/3d/main.rs @@ -64,11 +64,13 @@ fn write_to_screen(txt: String, w: &mut Window) { &Point3::new(1.0, 0.0, 1.0), ); } + +#[allow(clippy::doc_lazy_continuation)] /// This method ticks the behavior tree for a given duration 'dt' to move the /// behavior tree forward in time. Note that a tick - basically a depth-first traversal /// - of the tree is intended to return instantly, so it is important that the action /// callbacks return instantly. Long-running tasks/actions might take many ticks to complete -/// , where you update and monitor the task on a tick-basis. +/// where you update and monitor the task on a tick-basis. /// /// The ticks to execute for as long as the specified time 'dt'. fn game_tick( diff --git a/examples/src/async_drone/jobs.rs b/examples/src/async_drone/jobs.rs index b38a909..ad59af5 100644 --- a/examples/src/async_drone/jobs.rs +++ b/examples/src/async_drone/jobs.rs @@ -4,11 +4,12 @@ use tokio::time::{sleep, Duration}; struct PrintOnDrop(&'static str); -#[derive(Clone, Debug)] +#[allow(dead_code)] +#[derive(Debug)] pub struct Point { - pub x: f32, - pub y: f32, - pub z: f32, + x: f32, + y: f32, + z: f32, } impl Point { diff --git a/examples/src/simple_npc_ai/main.rs b/examples/src/simple_npc_ai/main.rs index 8a333cc..3487675 100644 --- a/examples/src/simple_npc_ai/main.rs +++ b/examples/src/simple_npc_ai/main.rs @@ -1,4 +1,4 @@ -use bonsai_bt::Behavior::RepeatSequence; +use bonsai_bt::Behavior::WhileAll; use bonsai_bt::{Behavior::Action, Event, Failure, Running, Status, Success, UpdateArgs, BT}; #[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq)] @@ -103,11 +103,11 @@ impl EnemyNPCState { } } -/// Demonstrates a usage of [RepeatSequence] behavior with +/// Demonstrates a usage of [WhileAll] behavior with /// a simple NPC simulation. /// -/// The NPC AI first enters a higher [RepeatSequence] that -/// checks if the NPC is dead, then it succeeds to inner [RepeatSequence] +/// The NPC AI first enters a higher [WhileAll] that +/// checks if the NPC is dead, then it succeeds to inner [WhileAll] /// where the NPC performs actions until it is determined that /// no action points are left to consume. Then the AI control flow returns /// to the previous higher sequence where the executions continues and the NPC rests @@ -138,11 +138,11 @@ impl EnemyNPCState { /// /// fn main() { - let run_and_shoot_ai = RepeatSequence( + let run_and_shoot_ai = WhileAll( Box::new(Action(EnemyNPC::HasActionPointsLeft)), vec![Action(EnemyNPC::Run), Action(EnemyNPC::Shoot)], ); - let top_ai = RepeatSequence( + let top_ai = WhileAll( Box::new(Action(EnemyNPC::IsDead)), vec![run_and_shoot_ai.clone(), Action(EnemyNPC::Rest), Action(EnemyNPC::Die)], );