├── .github └── workflows │ └── rust.yml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── src ├── behavior.rs ├── lib.rs ├── macros.rs ├── node.rs ├── prelude.rs ├── testing.rs └── types.rs └── tests ├── action_test.rs ├── common └── mod.rs ├── cond_test.rs ├── select_test.rs ├── sequence_test.rs ├── stateful_action_test.rs ├── wait_test.rs ├── while_test.rs └── yesno_test.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "behavior-tree" 7 | version = "0.1.0" 8 | dependencies = [ 9 | "puffin", 10 | "rand", 11 | "tracing", 12 | ] 13 | 14 | [[package]] 15 | name = "byteorder" 16 | version = "1.4.3" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 19 | 20 | [[package]] 21 | name = "cfg-if" 22 | version = "1.0.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 25 | 26 | [[package]] 27 | name = "getrandom" 28 | version = "0.2.3" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 31 | dependencies = [ 32 | "cfg-if", 33 | "libc", 34 | "wasi", 35 | ] 36 | 37 | [[package]] 38 | name = "lazy_static" 39 | version = "1.4.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 42 | 43 | [[package]] 44 | name = "libc" 45 | version = "0.2.103" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" 48 | 49 | [[package]] 50 | name = "once_cell" 51 | version = "1.8.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" 54 | 55 | [[package]] 56 | name = "pin-project-lite" 57 | version = "0.2.7" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" 60 | 61 | [[package]] 62 | name = "ppv-lite86" 63 | version = "0.2.10" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 66 | 67 | [[package]] 68 | name = "proc-macro2" 69 | version = "1.0.29" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" 72 | dependencies = [ 73 | "unicode-xid", 74 | ] 75 | 76 | [[package]] 77 | name = "puffin" 78 | version = "0.7.0" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "88f91429f20196b1162c80f625d93dfb63ab68155e8f57878d4363e53bdb9b46" 81 | dependencies = [ 82 | "byteorder", 83 | "once_cell", 84 | ] 85 | 86 | [[package]] 87 | name = "quote" 88 | version = "1.0.9" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 91 | dependencies = [ 92 | "proc-macro2", 93 | ] 94 | 95 | [[package]] 96 | name = "rand" 97 | version = "0.8.4" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" 100 | dependencies = [ 101 | "libc", 102 | "rand_chacha", 103 | "rand_core", 104 | "rand_hc", 105 | ] 106 | 107 | [[package]] 108 | name = "rand_chacha" 109 | version = "0.3.1" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 112 | dependencies = [ 113 | "ppv-lite86", 114 | "rand_core", 115 | ] 116 | 117 | [[package]] 118 | name = "rand_core" 119 | version = "0.6.3" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 122 | dependencies = [ 123 | "getrandom", 124 | ] 125 | 126 | [[package]] 127 | name = "rand_hc" 128 | version = "0.3.1" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" 131 | dependencies = [ 132 | "rand_core", 133 | ] 134 | 135 | [[package]] 136 | name = "syn" 137 | version = "1.0.76" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84" 140 | dependencies = [ 141 | "proc-macro2", 142 | "quote", 143 | "unicode-xid", 144 | ] 145 | 146 | [[package]] 147 | name = "tracing" 148 | version = "0.1.27" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "c2ba9ab62b7d6497a8638dfda5e5c4fb3b2d5a7fca4118f2b96151c8ef1a437e" 151 | dependencies = [ 152 | "cfg-if", 153 | "pin-project-lite", 154 | "tracing-attributes", 155 | "tracing-core", 156 | ] 157 | 158 | [[package]] 159 | name = "tracing-attributes" 160 | version = "0.1.16" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "98863d0dd09fa59a1b79c6750ad80dbda6b75f4e71c437a6a1a8cb91a8bcbd77" 163 | dependencies = [ 164 | "proc-macro2", 165 | "quote", 166 | "syn", 167 | ] 168 | 169 | [[package]] 170 | name = "tracing-core" 171 | version = "0.1.20" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "46125608c26121c81b0c6d693eab5a420e416da7e43c426d2e8f7df8da8a3acf" 174 | dependencies = [ 175 | "lazy_static", 176 | ] 177 | 178 | [[package]] 179 | name = "unicode-xid" 180 | version = "0.2.2" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 183 | 184 | [[package]] 185 | name = "wasi" 186 | version = "0.10.2+wasi-snapshot-preview1" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 189 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "behavior-tree" 3 | version = "0.1.0" 4 | edition = "2018" 5 | license = "MIT" 6 | 7 | authors = ["Jakub Arnold "] 8 | description = "Yet another behavior tree library for rust!" 9 | documentation = "https://docs.rs/behavior-tree" 10 | repository = "https://github.com/darthdeus/behavior-tree" 11 | 12 | keywords = ["gamedev", "ai"] 13 | 14 | [lib] 15 | doctest = false 16 | 17 | [features] 18 | profiling = ["puffin"] 19 | 20 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 21 | 22 | [dependencies] 23 | tracing = "0.1" 24 | rand = "0.8.4" 25 | puffin = { version = "0.7.0", optional = true } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `behavior-tree` for Rust! 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/behavior-tree)](https://crates.io/crates/behavior-tree) 4 | [![Crates.io (latest)](https://img.shields.io/crates/d/behavior-tree)](https://crates.io/crates/behavior-tree) 5 | [![Crates.io](https://img.shields.io/crates/l/behavior-tree)](https://crates.io/crates/behavior-tree) 6 | [![docs.rs](https://img.shields.io/docsrs/behavior-tree)](https://docs.rs/behavior-tree) 7 | [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/darthdeus/behavior-tree/rust.yml)](https://github.com/darthdeus/behavior-tree/actions) 8 | [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/darthdeus/behavior-tree)](https://github.com/darthdeus/behavior-tree/commits/master) 9 | [![GitHub branch checks state](https://img.shields.io/github/checks-status/darthdeus/behavior-tree/master)](https://github.com/darthdeus/behavior-tree/actions) 10 | 11 | **USE AT YOUR OWN RISK. This crate is under heavy development at the moment and a lot of the APIs will change often and without any notice. Performance is also terrible right now.** 12 | 13 | Implemented nodes: 14 | 15 | - Sequence - execute child nodes in a sequence until one of them fails. 16 | - Select - execute child nodes in a sequence until one of them succeeds. 17 | - While - execute a child node only when a condition is true. 18 | - Wait - constant time delay. 19 | - RandomWait - random time delay with a defined max. 20 | - Action - generic user-defined action. 21 | - StatefulAction - generic user-defined action which manages its own state in addition to the tree-wide Blackboard. 22 | - Cond - checks a condition and executes either the `positive` or `negative` child. 23 | 24 | Almost all of the behaviors have test coverage including a few of the edge cases, but it is by no means exhaustive yet. 25 | 26 | **There are a few quirks that need to be figured out, especially with respect to debugging/visualization, which will be stabilized before version `0.1`.** 27 | 28 | ## Other behavior tree crates 29 | 30 | There are a few other crates that implement behavior trees (listed below). 31 | This library is inspired by all of them, as well as the [Behavior Tree Starter 32 | Kit from Game AI 33 | Pro](https://www.gameaipro.com/GameAIPro/GameAIPro_Chapter06_The_Behavior_Tree_Starter_Kit.pdf). 34 | 35 | - https://crates.io/crates/piston-ai_behavior 36 | - https://github.com/pistondevelopers/ai_behavior 37 | 38 | - https://crates.io/crates/aspen 39 | - https://gitlab.com/neachdainn/aspen 40 | 41 | - https://crates.io/crates/stackbt_behavior_tree 42 | - https://github.com/eaglgenes101/stackbt 43 | 44 | ## Who uses this? 45 | 46 | The code was originally extracted from [BITGUN](https://store.steampowered.com/app/1673940/BITGUN/), which continues to use it as it's being developed open source. If you're using this crate in your game [do open a PR](https://github.com/darthdeus/behavior-tree/pulls) so we can list your game here as well! 47 | -------------------------------------------------------------------------------- /src/behavior.rs: -------------------------------------------------------------------------------- 1 | use crate::maybe_profile_function; 2 | use crate::prelude::*; 3 | use std::{cell::RefCell, rc::Rc}; 4 | 5 | pub trait StatefulAction { 6 | fn tick(&mut self, data: &mut T) -> Status; 7 | fn reset(&mut self); 8 | } 9 | 10 | pub struct BehaviorTree { 11 | pub tree: Rc>>, 12 | } 13 | 14 | impl BehaviorTree { 15 | pub fn new(root: Node) -> Self { 16 | let root = Rc::new(RefCell::new(root)); 17 | 18 | Self { tree: root } 19 | } 20 | } 21 | 22 | pub enum Behavior { 23 | Wait { 24 | curr: f64, 25 | max: f64, 26 | }, 27 | 28 | RandomWait { 29 | curr: f64, 30 | curr_max: f64, 31 | max: f64, 32 | }, 33 | 34 | Cond( 35 | String, 36 | // Rc bool>, 37 | fn(&T) -> bool, 38 | Rc>>, 39 | Rc>>, 40 | ), 41 | 42 | Sequence(usize, Vec>>>), 43 | Select(usize, Vec>>>), 44 | 45 | Action(String, fn(&mut T) -> Status), 46 | ActionSuccess(String, fn(&mut T) -> ()), 47 | 48 | StatefulAction(String, Box>), 49 | // StatefulAction(String, fn(&mut T, &P) -> Status), 50 | 51 | // Invert(Rc>), 52 | // AlwaysSucceed(Rc>), 53 | // Condition(Rc bool>, Rc>), 54 | // WaitForever, 55 | // Action(T), 56 | While(Box bool>, Rc>>), 57 | } 58 | 59 | fn sequence( 60 | delta: f64, 61 | context: &mut T, 62 | is_sequence: bool, 63 | current: &mut usize, 64 | xs: &mut Vec>>>, 65 | ) -> Status { 66 | maybe_profile_function!(); 67 | 68 | let (status_positive, status_negative) = if is_sequence { 69 | (Status::Success, Status::Failure) 70 | } else { 71 | (Status::Failure, Status::Success) 72 | }; 73 | 74 | let mut repr_string = String::new(); 75 | let mut status = status_positive; 76 | 77 | let len = xs.len(); 78 | 79 | for i in 0..*current { 80 | if xs[i].borrow_mut().recheck_condition(context, is_sequence) { 81 | *current = i; 82 | for j in (i + 1)..len { 83 | if j < len { 84 | xs[j].borrow_mut().reset(); 85 | } 86 | } 87 | // TODO: add a test that verifies that the break is needed 88 | break; 89 | } 90 | } 91 | 92 | // Resetting state 93 | if *current == len { 94 | *current = 0; 95 | } 96 | 97 | while *current < len { 98 | let mut x = xs[*current].borrow_mut(); 99 | 100 | if x.status == Status::Success || x.status == Status::Failure { 101 | // *current += 1; 102 | // continue; 103 | x.reset(); 104 | } 105 | let res = x.tick(delta, context); 106 | 107 | if res == status_positive { 108 | *current += 1; 109 | repr_string += "+"; 110 | } else if res == status_negative { 111 | status = status_negative; 112 | repr_string += "-"; 113 | break; 114 | } else { 115 | status = Status::Running; 116 | repr_string += "."; 117 | break; 118 | } 119 | 120 | // match x.tick(delta, context) { 121 | // (Status::Success, repr) => { 122 | // } 123 | // (Status::Failure, repr) => { 124 | // // return (Status::Failure, DebugRepr::new("Sequence", Status::Failure)) 125 | // } 126 | // (Status::Running, repr) => { 127 | // // return (Status::Running, DebugRepr::new("Sequence", Status::Running)) 128 | // } 129 | // } 130 | } 131 | 132 | status 133 | } 134 | // 135 | // for (i, x) in xs.iter_mut().enumerate() { 136 | // match x.tick(delta, context) { 137 | // (Status::Success, repr) => { 138 | // if i < len - 1 { 139 | // index += 1; 140 | // } 141 | // repr_string += "+"; 142 | // child_repr = Some(repr); 143 | // } 144 | // (Status::Failure, repr) => { 145 | // status = Status::Failure; 146 | // repr_string += "-"; 147 | // child_repr = Some(repr); 148 | // break; 149 | // // return (Status::Failure, DebugRepr::new("Sequence", Status::Failure)) 150 | // } 151 | // (Status::Running, repr) => { 152 | // status = Status::Running; 153 | // repr_string += "."; 154 | // child_repr = Some(repr); 155 | // break; 156 | // // return (Status::Running, DebugRepr::new("Sequence", Status::Running)) 157 | // } 158 | // } 159 | // } 160 | 161 | impl Behavior { 162 | pub fn tick(&mut self, delta: f64, context: &mut T) -> Status { 163 | maybe_profile_function!(); 164 | 165 | let _status = match self { 166 | Behavior::Wait { 167 | ref mut curr, 168 | max: _, 169 | } => { 170 | *curr -= delta; 171 | let status = if *curr <= 0.0 { 172 | Status::Success 173 | } else { 174 | Status::Running 175 | }; 176 | 177 | return status; 178 | } 179 | 180 | Behavior::RandomWait { 181 | ref mut curr, 182 | curr_max: _, 183 | max: _, 184 | } => { 185 | *curr -= delta; 186 | let status = if *curr <= 0.0 { 187 | Status::Success 188 | } else { 189 | Status::Running 190 | }; 191 | 192 | return status; 193 | } 194 | 195 | Behavior::Cond(_, cond, a, b) => { 196 | let c = cond(context); 197 | 198 | let status = if c { 199 | a.borrow_mut().tick(delta, context) 200 | } else { 201 | b.borrow_mut().tick(delta, context) 202 | }; 203 | 204 | return status; 205 | } 206 | 207 | Behavior::Sequence(ref mut current, xs) => { 208 | return sequence(delta, context, true, current, xs) 209 | } 210 | 211 | Behavior::Select(ref mut current, xs) => { 212 | return sequence(delta, context, false, current, xs) 213 | } 214 | 215 | Behavior::Action(_, action) => { 216 | let status = action(context); 217 | return status; 218 | } 219 | 220 | Behavior::ActionSuccess(_, action) => { 221 | let _ = action(context); 222 | return Status::Success; 223 | } 224 | 225 | // TODO: state reset? 226 | Behavior::StatefulAction(_, action) => { 227 | return action.tick(context); 228 | } 229 | 230 | Behavior::While(cond, behavior) => { 231 | if cond(context) { 232 | return behavior.borrow_mut().tick(delta, context); 233 | } else { 234 | return Status::Failure; 235 | } 236 | } // Status::Success => Status::Failure, 237 | // Status::Failure => Status::Success, 238 | // Status::Running => Status::Running, 239 | // }, 240 | // Behavior::AlwaysSucceed(b) => match b.tick(delta, context).0 { 241 | // Status::Success | Status::Failure => Status::Success, 242 | // Status::Running => Status::Running, 243 | // }, 244 | 245 | // Behavior::Condition(cond, action) => { 246 | // if cond(delta, context) { 247 | // action.tick(delta, context).0 248 | // } else { 249 | // Status::Failure 250 | // } 251 | // } 252 | 253 | // Behavior::StatefulAction(_) => todo!(), 254 | // _ => todo!(), 255 | // Behavior::Select(xs) => { 256 | // for x in xs.iter_mut() { 257 | // match x.tick(delta, context).0 { 258 | // Status::Success => { 259 | // return (Status::Success, DebugRepr::new("Select", Status::Success)) 260 | // } 261 | // Status::Failure => { 262 | // return (Status::Running, DebugRepr::new("Select", Status::Running)) 263 | // } 264 | // Status::Running => {} 265 | // } 266 | // } 267 | // 268 | // Status::Failure 269 | // } 270 | }; 271 | 272 | // (status, DebugRepr::new("X", status)) 273 | } 274 | 275 | pub fn reset(&mut self) { 276 | maybe_profile_function!(); 277 | 278 | match self { 279 | Behavior::Wait { ref mut curr, max } => { 280 | *curr = *max; 281 | } 282 | Behavior::RandomWait { 283 | ref mut curr, 284 | ref mut curr_max, 285 | max, 286 | } => { 287 | *curr_max = rand::random::() * *max; 288 | *curr = *curr_max; 289 | } 290 | Behavior::Sequence(ref mut idx, nodes) => { 291 | *idx = 0; 292 | for node in nodes.iter_mut() { 293 | node.borrow_mut().reset(); 294 | } 295 | } 296 | Behavior::Select(ref mut idx, nodes) => { 297 | *idx = 0; 298 | for node in nodes.iter_mut() { 299 | node.borrow_mut().reset(); 300 | } 301 | } 302 | Behavior::StatefulAction(_name, ref mut state) => { 303 | state.reset(); 304 | } 305 | 306 | Behavior::While(_, node) => node.borrow_mut().reset(), 307 | _ => {} 308 | } 309 | } 310 | } 311 | 312 | impl core::fmt::Debug for Behavior { 313 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 314 | maybe_profile_function!(); 315 | 316 | match self { 317 | Behavior::Wait{curr, max} => { 318 | f.debug_struct("Wait").field("current", curr).field("max", max); 319 | } 320 | Behavior::Action(name, _fn) => { 321 | f.debug_struct("Action").field("name", name); 322 | } 323 | // Behavior::StatefulAction() => todo!(), 324 | 325 | _ => {} 326 | // Behavior::Invert(_) => todo!(), 327 | // Behavior::AlwaysSucceed(_) => todo!(), 328 | // Behavior::Cond(_, _, _) => todo!(), 329 | // Behavior::Sequence(_) => todo!(), 330 | // Behavior::Select(_) => todo!(), 331 | // Behavior::Condition(_, _) => todo!(), 332 | // Behavior::ActionSuccess(_) => todo!(), 333 | // Behavior::StatefulAction(_) => todo!(), 334 | }; 335 | 336 | Ok(()) 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use crate::behavior::*; 2 | pub use crate::macros::*; 3 | pub use crate::node::*; 4 | pub use crate::types::*; 5 | pub use crate::testing::*; 6 | 7 | // Only used internally 8 | mod prelude; 9 | 10 | mod behavior; 11 | mod macros; 12 | mod node; 13 | mod types; 14 | mod testing; 15 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! cond { 3 | ($cond:expr, $a:expr, $b:expr $(,)?) => { 4 | Node::cond(stringify!($cond), $cond, $a, $b) 5 | }; 6 | } 7 | 8 | #[macro_export] 9 | macro_rules! sequence { 10 | ($($x:expr),+ $(,)?) => { 11 | // Node::named_sequence(stringify!($($x),+), vec![$($x),+]) 12 | Node::sequence(vec![$($x),+]) 13 | } 14 | } 15 | 16 | #[macro_export] 17 | macro_rules! named_sequence { 18 | ($name:expr, $($x:expr),+ $(,)?) => { 19 | Node::named_sequence($name, vec![$($x),+]) 20 | } 21 | } 22 | 23 | #[macro_export] 24 | macro_rules! select { 25 | ($($x:expr),+ $(,)?) => { 26 | Node::named_select(stringify!($($x),+), vec![$($x),+]) 27 | } 28 | } 29 | 30 | #[macro_export] 31 | macro_rules! while_single { 32 | ($cond:expr, $child:expr $(,)?) => { 33 | Node::named_while_single(stringify!($cond), $cond, $child) 34 | }; 35 | } 36 | 37 | #[macro_export] 38 | macro_rules! maybe_profile_function { 39 | () => { 40 | #[cfg(feature = "puffin")] 41 | puffin::profile_function!(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/node.rs: -------------------------------------------------------------------------------- 1 | use crate::maybe_profile_function; 2 | use crate::prelude::*; 3 | use std::{cell::RefCell, rc::Rc}; 4 | 5 | pub struct Node { 6 | pub name: Option, 7 | pub behavior: Behavior, 8 | pub status: Status, 9 | pub collapse_as: Option, 10 | } 11 | 12 | impl Node { 13 | fn new(behavior: Behavior) -> Node { 14 | Node { 15 | name: None, 16 | behavior, 17 | status: Status::Initialized, 18 | collapse_as: None, 19 | } 20 | } 21 | 22 | pub fn new_named(name: String, behavior: Behavior) -> Node { 23 | Node { 24 | name: Some(name), 25 | behavior, 26 | status: Status::Initialized, 27 | collapse_as: None, 28 | } 29 | } 30 | 31 | pub fn action(name: &str, func: fn(&mut T) -> Status) -> Node { 32 | Self::new_named(name.to_owned(), Behavior::Action(name.to_owned(), func)) 33 | } 34 | 35 | pub fn action_success(name: &str, func: fn(&mut T) -> ()) -> Node { 36 | Self::new_named( 37 | name.to_owned(), 38 | Behavior::ActionSuccess(name.to_owned(), func), 39 | ) 40 | } 41 | 42 | pub fn stateful_action(name: &str, func: Box>) -> Node { 43 | Self::new_named( 44 | name.to_owned(), 45 | Behavior::StatefulAction(name.to_owned(), func), 46 | ) 47 | } 48 | 49 | pub fn sequence(nodes: Vec>) -> Node { 50 | Self::new(Behavior::Sequence( 51 | 0, 52 | nodes 53 | .into_iter() 54 | .map(|node| Rc::new(RefCell::new(node))) 55 | .collect(), 56 | )) 57 | } 58 | 59 | pub fn named_sequence(name: &str, nodes: Vec>) -> Node { 60 | Self::new_named( 61 | name.to_owned(), 62 | Behavior::Sequence( 63 | 0, 64 | nodes 65 | .into_iter() 66 | .map(|node| Rc::new(RefCell::new(node))) 67 | .collect(), 68 | ), 69 | ) 70 | } 71 | 72 | pub fn select(nodes: Vec>) -> Node { 73 | Self::new(Behavior::Select( 74 | 0, 75 | nodes 76 | .into_iter() 77 | .map(|node| Rc::new(RefCell::new(node))) 78 | .collect(), 79 | )) 80 | } 81 | 82 | // TODO: select -> success -> reset 83 | 84 | pub fn named_select(name: &str, nodes: Vec>) -> Node { 85 | Self::new_named( 86 | name.to_owned(), 87 | Behavior::Select( 88 | 0, 89 | nodes 90 | .into_iter() 91 | .map(|node| Rc::new(RefCell::new(node))) 92 | .collect(), 93 | ), 94 | ) 95 | } 96 | 97 | pub fn cond(name: &str, cond: fn(&T) -> bool, success: Node, failure: Node) -> Node { 98 | Self::new_named( 99 | name.to_owned(), 100 | Behavior::Cond( 101 | name.to_owned(), 102 | cond, 103 | Rc::new(RefCell::new(success)), 104 | Rc::new(RefCell::new(failure)), 105 | ), 106 | ) 107 | } 108 | 109 | pub fn wait(time: f64) -> Node { 110 | Self::new(Behavior::Wait { 111 | curr: time, 112 | max: time, 113 | }) 114 | } 115 | 116 | pub fn random_wait(time: f64) -> Node { 117 | let curr_max = rand::random::() * time; 118 | 119 | Self::new(Behavior::RandomWait { 120 | curr: curr_max, 121 | curr_max, 122 | max: time, 123 | }) 124 | } 125 | 126 | pub fn named_while_single( 127 | name: &str, 128 | cond: Box bool>, 129 | child: Node, 130 | ) -> Node { 131 | Self::new_named( 132 | name.to_owned(), 133 | Behavior::While(cond, Rc::new(RefCell::new(child))), 134 | ) 135 | } 136 | 137 | pub fn named_while_single_child( 138 | name: &str, 139 | cond: Box bool>, 140 | child: Rc>>, 141 | ) -> Node { 142 | Self::new_named(name.to_owned(), Behavior::While(cond, child)) 143 | } 144 | 145 | // pub fn while_single(cond: fn(&T) -> bool, child: Node) -> Node { 146 | // Self::new(Behavior::While(cond, Rc::new(RefCell::new(child)))) 147 | // } 148 | 149 | pub fn collapse(self, desc: &str) -> Node { 150 | Self { 151 | collapse_as: Some(desc.to_owned()), 152 | ..self 153 | } 154 | } 155 | 156 | pub fn reset(&mut self) { 157 | self.status = Status::Initialized; 158 | self.behavior.reset(); 159 | } 160 | 161 | pub fn tick(&mut self, delta: f64, context: &mut T) -> Status { 162 | maybe_profile_function!(); 163 | 164 | if self.status == Status::Success || self.status == Status::Failure { 165 | self.reset(); 166 | } 167 | 168 | self.status = self.behavior.tick(delta, context); 169 | self.status 170 | } 171 | 172 | pub fn children(&self) -> Vec>>> { 173 | match &self.behavior { 174 | Behavior::Wait { .. } => vec![], 175 | Behavior::RandomWait { .. } => vec![], 176 | Behavior::Cond(_, _, positive, negative) => vec![positive.clone(), negative.clone()], 177 | Behavior::Sequence(_, ref seq) => seq.clone(), 178 | Behavior::Select(_, ref seq) => seq.clone(), 179 | Behavior::Action(_, _) => vec![], 180 | Behavior::ActionSuccess(_, _) => vec![], 181 | Behavior::StatefulAction(_, _) => vec![], 182 | Behavior::While(_, item) => vec![item.clone()], 183 | } 184 | } 185 | 186 | pub fn name(&self) -> String { 187 | maybe_profile_function!(); 188 | 189 | match &self.collapse_as { 190 | Some(collapse_text) => collapse_text.clone(), 191 | None => { 192 | match &self.behavior { 193 | Behavior::Wait { curr, max } => format!("Wait {:.2}/{:.2}", curr, max), 194 | Behavior::RandomWait { 195 | curr, 196 | curr_max, 197 | max, 198 | } => format!("RandomWait {:.2}/{:.2} ({:.2})", curr, curr_max, max), 199 | Behavior::Cond(name, _cond, _a, _b) => format!("Cond {}", name), 200 | // TreeRepr::new("Cond", vec![a.borrow().to_debug(), b.borrow().to_debug()]) 201 | // .with_detail(name.clone()) 202 | Behavior::Sequence(_, _seq) => "Sequence".to_string(), 203 | // if let Some(ref name) = self.name { 204 | // format!("Sequence {}", name) 205 | // } else { 206 | // "Sequence".to_string() 207 | // }, 208 | // seq.iter().map(|x| x.borrow().to_debug()).collect(), 209 | Behavior::Select(_, _seq) => "Select".to_string(), 210 | Behavior::Action(name, _) => format!("Action {}", name), 211 | Behavior::ActionSuccess(name, _) => format!("ActionSuccess {}", name), 212 | Behavior::StatefulAction(name, _) => format!("StatefulAction {}", name), 213 | // Behavior::While(_, x) => TreeRepr::new("While", vec![x.to_debug()]), 214 | // TODO: add to detail 215 | Behavior::While(_, _x) => { 216 | format!( 217 | "While {}", 218 | self.name.as_ref().expect("While must have a name") 219 | ) 220 | } 221 | } 222 | } 223 | } 224 | } 225 | 226 | pub fn recheck_condition(&mut self, context: &T, is_sequence: bool) -> bool { 227 | maybe_profile_function!(); 228 | 229 | match &self.behavior { 230 | Behavior::While(cond, _) => cond(context) != is_sequence, 231 | _ => false, 232 | // Behavior::Sequence(_, _) => todo!(), 233 | // Behavior::Select(_, _) => todo!(), 234 | // Behavior::Action(_, _) => todo!(), 235 | // Behavior::ActionSuccess(_, _) => todo!(), 236 | // Behavior::StatefulAction(_, _) => todo!(), 237 | // Behavior::Wait { curr, max } => todo!(), 238 | // Behavior::Cond(_, _, _, _) => todo!(), 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::behavior::*; 2 | pub use crate::macros::*; 3 | pub use crate::node::*; 4 | pub use crate::types::*; 5 | pub use crate::testing::*; 6 | pub use tracing::*; 7 | -------------------------------------------------------------------------------- /src/testing.rs: -------------------------------------------------------------------------------- 1 | //! Nodes in this module are intended for testing. While they're being 2 | //! used to internally test this library they could also prove to be useful 3 | //! for testing custom node types in user code. 4 | use std::{cell::RefCell, rc::Rc}; 5 | 6 | use crate::prelude::*; 7 | 8 | /// Node that will panic upon being dropped if it hasn't ticked 9 | /// at least once. 10 | /// 11 | /// Inspired by https://github.com/danieleades/aspen/blob/master/src/std_nodes/testing.rs 12 | #[derive(Default)] 13 | pub struct YesTick { 14 | pub ticked: bool, 15 | } 16 | 17 | impl YesTick { 18 | pub fn action() -> Node { 19 | Node::stateful_action("yes", Box::new(YesTick::default())) 20 | } 21 | } 22 | 23 | impl StatefulAction for YesTick { 24 | fn tick(&mut self, _data: &mut T) -> Status { 25 | self.ticked = true; 26 | Status::Success 27 | } 28 | 29 | fn reset(&mut self) { 30 | // no-op 31 | } 32 | } 33 | 34 | impl Drop for YesTick { 35 | fn drop(&mut self) { 36 | assert!(self.ticked, "YesTick dropped without being ticked"); 37 | } 38 | } 39 | 40 | /// Node that will panic when it ticks. 41 | /// 42 | /// Inspired by https://github.com/danieleades/aspen/blob/master/src/std_nodes/testing.rs 43 | #[derive(Default)] 44 | pub struct NoTick; 45 | 46 | impl NoTick { 47 | pub fn action() -> Node { 48 | Node::stateful_action("no", Box::new(NoTick::default())) 49 | } 50 | } 51 | 52 | impl StatefulAction for NoTick { 53 | fn tick(&mut self, _data: &mut T) -> Status { 54 | panic!("NoTick node should never be ticked"); 55 | } 56 | 57 | fn reset(&mut self) { 58 | // no-op to allow testing reset on bigger trees 59 | } 60 | } 61 | 62 | /// Node that always runs. 63 | pub struct AlwaysRunning; 64 | 65 | impl AlwaysRunning { 66 | pub fn action() -> Node { 67 | Node::action("running", |_| Status::Running) 68 | } 69 | } 70 | 71 | /// Node that always returns a given status when ticked, but exposes the status 72 | /// to be modified from the outside. 73 | pub struct ConstAction { 74 | pub return_status: Rc>, 75 | } 76 | 77 | impl ConstAction { 78 | pub fn new(status: Status) -> Self { 79 | Self { 80 | return_status: Rc::new(RefCell::new(status)), 81 | } 82 | } 83 | } 84 | 85 | impl StatefulAction for ConstAction { 86 | fn tick(&mut self, _data: &mut T) -> Status { 87 | *self.return_status.borrow() 88 | } 89 | 90 | fn reset(&mut self) {} 91 | } 92 | // impl StatefulAction for AlwaysRunning { 93 | // fn tick(&mut self, _data: &mut T) -> Status { 94 | // Status::Running 95 | // } 96 | // 97 | // fn reset(&mut self) { 98 | // panic!("AlwaysRunning should never be reset"); 99 | // } 100 | // } 101 | 102 | #[derive(Default)] 103 | pub struct Counter { 104 | pub value: Rc>, 105 | resettable: bool, 106 | } 107 | 108 | impl Counter { 109 | pub fn action(resettable: bool) -> (Rc>>, Rc>) { 110 | let value = Rc::new(RefCell::new(0)); 111 | ( 112 | Rc::new(RefCell::new(Node::stateful_action( 113 | "counter", 114 | Box::new(Self { 115 | value: value.clone(), 116 | resettable, 117 | }), 118 | ))), 119 | value, 120 | ) 121 | } 122 | } 123 | 124 | impl StatefulAction for Counter { 125 | fn tick(&mut self, _data: &mut T) -> Status { 126 | *self.value.borrow_mut() += 1; 127 | Status::Success 128 | } 129 | 130 | fn reset(&mut self) { 131 | if self.resettable { 132 | *self.value.borrow_mut() = 0; 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 2 | pub enum Status { 3 | Initialized, 4 | Success, 5 | Failure, 6 | Running, 7 | } 8 | 9 | impl core::default::Default for Status { 10 | fn default() -> Self { 11 | Self::Initialized 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/action_test.rs: -------------------------------------------------------------------------------- 1 | use behavior_tree::*; 2 | 3 | use crate::common::*; 4 | mod common; 5 | 6 | #[test] 7 | fn test_simple_action() { 8 | let mut bt: Node = Node::action("inc_pingpong", inc_pingpong); 9 | 10 | let mut data = EvenCounter { value: 0 }; 11 | 12 | let status = bt.tick(0.0, &mut data); 13 | assert_eq!(status, Status::Running); 14 | assert_eq!(data.value, 1); 15 | 16 | let status = bt.tick(0.0, &mut data); 17 | assert_eq!(status, Status::Success); 18 | assert_eq!(data.value, 2); 19 | 20 | let status = bt.tick(0.0, &mut data); 21 | assert_eq!(status, Status::Running); 22 | assert_eq!(data.value, 3); 23 | 24 | let status = bt.tick(0.0, &mut data); 25 | assert_eq!(status, Status::Success); 26 | assert_eq!(data.value, 4); 27 | 28 | let status = bt.tick(0.0, &mut data); 29 | assert_eq!(status, Status::Running); 30 | assert_eq!(data.value, 5); 31 | 32 | let status = bt.tick(0.0, &mut data); 33 | assert_eq!(status, Status::Success); 34 | assert_eq!(data.value, 6); 35 | } 36 | -------------------------------------------------------------------------------- /tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | use behavior_tree::*; 2 | 3 | pub struct EvenCounter { 4 | pub value: i32, 5 | } 6 | 7 | impl StatefulAction<()> for EvenCounter { 8 | fn tick(&mut self, _data: &mut ()) -> Status { 9 | if self.value == 0 { 10 | self.value += 1; 11 | Status::Running 12 | } else { 13 | Status::Success 14 | } 15 | } 16 | 17 | fn reset(&mut self) { 18 | self.value = 0; 19 | } 20 | } 21 | 22 | pub fn inc_pingpong(data: &mut EvenCounter) -> Status { 23 | if data.value % 2 == 0 { 24 | data.value += 1; 25 | Status::Running 26 | } else { 27 | data.value += 1; 28 | Status::Success 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/cond_test.rs: -------------------------------------------------------------------------------- 1 | use behavior_tree::*; 2 | 3 | #[derive(Default)] 4 | struct Blackboard { 5 | is_foo: bool, 6 | result: String, 7 | } 8 | 9 | #[test] 10 | fn test_cond_simple_true() { 11 | let mut bt: Node = Node::cond( 12 | "is_foo", 13 | |data| data.is_foo, 14 | YesTick::action(), 15 | NoTick::action(), 16 | ); 17 | 18 | let mut bb = Blackboard::default(); 19 | bb.is_foo = true; 20 | bt.tick(1.0, &mut bb); 21 | } 22 | 23 | #[test] 24 | fn test_cond_simple_false() { 25 | let mut bt: Node = Node::cond( 26 | "is_foo", 27 | |data| data.is_foo, 28 | NoTick::action(), 29 | YesTick::action(), 30 | ); 31 | 32 | let mut bb = Blackboard::default(); 33 | bt.tick(1.0, &mut bb); 34 | } 35 | 36 | #[test] 37 | fn test_cond() { 38 | let mut bt: Node = Node::cond( 39 | "is_foo", 40 | |data| data.is_foo, 41 | Node::action_success("yes", |data| data.result = "yes".to_owned()), 42 | Node::action_success("no", |data| data.result = "no".to_owned()), 43 | ); 44 | 45 | let mut bb = Blackboard { 46 | is_foo: false, 47 | result: "".to_string(), 48 | }; 49 | 50 | let status = bt.tick(1.0, &mut bb); 51 | assert_eq!(bb.result, "no"); 52 | assert_eq!(status, Status::Success); 53 | 54 | bb.is_foo = true; 55 | 56 | let status = bt.tick(1.0, &mut bb); 57 | assert_eq!(bb.result, "yes"); 58 | assert_eq!(status, Status::Success); 59 | } 60 | -------------------------------------------------------------------------------- /tests/select_test.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use behavior_tree::*; 4 | 5 | #[test] 6 | fn test_simple_select() { 7 | let mut bt: Node<()> = Node::select(vec![ 8 | Node::action("fail", |_| Status::Failure), 9 | Node::action("fail", |_| Status::Failure), 10 | YesTick::action(), 11 | NoTick::action(), 12 | NoTick::action(), 13 | ]); 14 | 15 | let res = bt.tick(1.0, &mut ()); 16 | assert_eq!(res, Status::Success); 17 | 18 | assert_eq!(bt.status, Status::Success); 19 | if let Behavior::Select(_, ref nodes) = bt.behavior { 20 | assert_eq!(nodes[0].borrow().status, Status::Failure); 21 | assert_eq!(nodes[1].borrow().status, Status::Failure); 22 | assert_eq!(nodes[2].borrow().status, Status::Success); 23 | assert_eq!(nodes[3].borrow().status, Status::Initialized); 24 | assert_eq!(nodes[4].borrow().status, Status::Initialized); 25 | } else { 26 | panic!("Expected sequence") 27 | } 28 | 29 | bt.reset(); 30 | assert_eq!(bt.status, Status::Initialized); 31 | 32 | if let Behavior::Select(_, ref nodes) = bt.behavior { 33 | for node in nodes.iter() { 34 | assert_eq!(node.borrow().status, Status::Initialized); 35 | } 36 | } else { 37 | panic!("Expected sequence") 38 | } 39 | } 40 | 41 | #[test] 42 | fn test_simple_select_inv() { 43 | let mut bt: Node<()> = Node::select(vec![ 44 | Node::action("success", |_| Status::Success), 45 | NoTick::action(), 46 | NoTick::action(), 47 | NoTick::action(), 48 | NoTick::action(), 49 | ]); 50 | 51 | let res = bt.tick(1.0, &mut ()); 52 | assert_eq!(res, Status::Success); 53 | } 54 | 55 | #[test] 56 | fn test_simple_select_running() { 57 | let mut bt: Node<()> = Node::select(vec![ 58 | Node::action("fail", |_| Status::Failure), 59 | Node::action("fail", |_| Status::Failure), 60 | Node::action("fail", |_| Status::Failure), 61 | AlwaysRunning::action(), 62 | NoTick::action(), 63 | NoTick::action(), 64 | NoTick::action(), 65 | NoTick::action(), 66 | ]); 67 | 68 | let res = bt.tick(1.0, &mut ()); 69 | assert_eq!(res, Status::Running); 70 | } 71 | 72 | #[test] 73 | fn test_simple_select_fail() { 74 | let mut bt: Node<()> = Node::select(vec![ 75 | Node::action("fail", |_| Status::Failure), 76 | Node::action("fail", |_| Status::Failure), 77 | Node::action("fail", |_| Status::Failure), 78 | Node::action("fail", |_| Status::Failure), 79 | Node::action("fail", |_| Status::Failure), 80 | ]); 81 | 82 | let res = bt.tick(1.0, &mut ()); 83 | assert_eq!(res, Status::Failure); 84 | } 85 | 86 | #[test] 87 | fn test_condition_recheck() { 88 | let const_status = Rc::new(RefCell::new(Status::Failure)); 89 | 90 | let mut bt: Node<()> = Node::select(vec![ 91 | Node::stateful_action( 92 | "const", 93 | Box::new(ConstAction { 94 | return_status: const_status.clone(), 95 | }), 96 | ), 97 | Node::stateful_action("counter", Box::new(Counter::default())), 98 | AlwaysRunning::action(), 99 | ]); 100 | 101 | let status = bt.tick(1.0, &mut ()); 102 | assert_eq!(status, Status::Success); 103 | 104 | *const_status.borrow_mut() = Status::Success; 105 | 106 | let status = bt.tick(1.0, &mut ()); 107 | assert_eq!(status, Status::Success); 108 | 109 | *const_status.borrow_mut() = Status::Running; 110 | 111 | let status = bt.tick(1.0, &mut ()); 112 | assert_eq!(status, Status::Running); 113 | } 114 | -------------------------------------------------------------------------------- /tests/sequence_test.rs: -------------------------------------------------------------------------------- 1 | use crate::common::*; 2 | use behavior_tree::*; 3 | mod common; 4 | 5 | #[test] 6 | fn test_simple_sequence() { 7 | let mut bt: Node<()> = Node::sequence(vec![ 8 | Node::action("success", |_| Status::Success), 9 | YesTick::action(), 10 | Node::action("success", |_| Status::Success), 11 | Node::action("success", |_| Status::Success), 12 | YesTick::action(), 13 | ]); 14 | 15 | assert_eq!(bt.status, Status::Initialized); 16 | 17 | let res = bt.tick(1.0, &mut ()); 18 | assert_eq!(res, Status::Success); 19 | 20 | assert_eq!(bt.status, Status::Success); 21 | if let Behavior::Sequence(_, ref nodes) = bt.behavior { 22 | for node in nodes.iter() { 23 | assert_eq!(node.borrow().status, Status::Success); 24 | } 25 | } else { 26 | panic!("Expected sequence") 27 | } 28 | 29 | bt.reset(); 30 | assert_eq!(bt.status, Status::Initialized); 31 | 32 | if let Behavior::Sequence(_, ref nodes) = bt.behavior { 33 | for node in nodes.iter() { 34 | assert_eq!(node.borrow().status, Status::Initialized); 35 | } 36 | } else { 37 | panic!("Expected sequence") 38 | } 39 | } 40 | 41 | #[test] 42 | fn test_simple_sequence_inv() { 43 | let mut bt: Node<()> = Node::sequence(vec![ 44 | Node::action("failure", |_| Status::Failure), 45 | NoTick::action(), 46 | NoTick::action(), 47 | NoTick::action(), 48 | NoTick::action(), 49 | ]); 50 | 51 | assert_eq!(bt.status, Status::Initialized); 52 | 53 | let res = bt.tick(1.0, &mut ()); 54 | assert_eq!(res, Status::Failure); 55 | 56 | assert_eq!(bt.status, Status::Failure); 57 | } 58 | 59 | // Sequence 60 | // S S R ....... -> R 61 | // S S S S F ... -> F 62 | // S S S S S S S -> S 63 | 64 | // Select 65 | // F F R ....... -> R 66 | // F F F F S ... -> S 67 | // F F F F F F F -> F 68 | 69 | #[test] 70 | fn test_simple_running() { 71 | let mut bt: Node<()> = Node::sequence(vec![ 72 | Node::action("success", |_| Status::Success), 73 | YesTick::action(), 74 | AlwaysRunning::action(), 75 | NoTick::action(), 76 | ]); 77 | 78 | // Check that sequence doesn't step over running tasks 79 | for _ in 0..10 { 80 | let res = bt.tick(1.0, &mut ()); 81 | assert_eq!(res, Status::Running); 82 | } 83 | } 84 | 85 | #[test] 86 | fn test_simple_sequence_pingpong() { 87 | let mut bt = Node::sequence(vec![Node::action("inc_pingpong", inc_pingpong)]); 88 | 89 | // S 90 | // | 91 | // A 92 | 93 | let mut data = EvenCounter { value: 0 }; 94 | 95 | let status = bt.tick(0.0, &mut data); 96 | assert_eq!(status, Status::Running); 97 | assert_eq!(data.value, 1); 98 | 99 | let status = bt.tick(0.0, &mut data); 100 | assert_eq!(status, Status::Success); 101 | assert_eq!(data.value, 2); 102 | 103 | let status = bt.tick(0.0, &mut data); 104 | assert_eq!(status, Status::Running); 105 | assert_eq!(data.value, 3); 106 | } 107 | 108 | #[test] 109 | fn test_nested_sequence() { 110 | struct DoubleCounter { 111 | x: i32, 112 | y: i32, 113 | } 114 | 115 | fn inc_x(data: &mut DoubleCounter) -> Status { 116 | if data.x % 2 == 0 { 117 | data.x += 1; 118 | Status::Running 119 | } else { 120 | Status::Success 121 | } 122 | } 123 | 124 | fn inc_y(data: &mut DoubleCounter) -> Status { 125 | if data.y % 2 == 0 { 126 | data.y += 1; 127 | Status::Running 128 | } else { 129 | Status::Success 130 | } 131 | } 132 | 133 | let mut bt = Node::sequence(vec![ 134 | Node::action("inc_once_1", inc_x), 135 | Node::action("inc_once_2", inc_y), 136 | ]); 137 | 138 | // S 139 | // | 140 | // X -> Y 141 | // 142 | // sequence(start_attack, wait, stop_attack) 143 | 144 | let mut data = DoubleCounter { x: 0, y: 0 }; 145 | 146 | let status = bt.tick(0.0, &mut data); 147 | assert_eq!(status, Status::Running); 148 | assert_eq!(data.x, 1); 149 | assert_eq!(data.y, 0); 150 | 151 | let status = bt.tick(0.0, &mut data); 152 | assert_eq!(status, Status::Running); 153 | assert_eq!(data.x, 1); 154 | assert_eq!(data.y, 1); 155 | 156 | let status = bt.tick(0.0, &mut data); 157 | assert_eq!(data.x, 1); 158 | assert_eq!(data.y, 1); 159 | assert_eq!(status, Status::Success); 160 | 161 | let status = bt.tick(0.0, &mut data); 162 | assert_eq!(data.x, 1); 163 | assert_eq!(data.y, 1); 164 | assert_eq!(status, Status::Success); 165 | 166 | let status = bt.tick(0.0, &mut data); 167 | assert_eq!(data.x, 1); 168 | assert_eq!(data.y, 1); 169 | assert_eq!(status, Status::Success); 170 | 171 | // let status = bt.tick(0.0, &mut data); 172 | // assert_eq!(data.x, 4); 173 | // assert_eq!(data.y, 4); 174 | // assert_eq!(status, Status::Success); 175 | } 176 | -------------------------------------------------------------------------------- /tests/stateful_action_test.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use behavior_tree::*; 4 | 5 | #[test] 6 | fn test_stateful_action() { 7 | struct CounterWrap { 8 | value: Rc>, 9 | } 10 | 11 | impl StatefulAction<()> for CounterWrap { 12 | fn tick(&mut self, _data: &mut ()) -> Status { 13 | let mut value = self.value.borrow_mut(); 14 | 15 | if *value == 0 { 16 | *value += 1; 17 | Status::Running 18 | } else { 19 | Status::Success 20 | } 21 | } 22 | 23 | fn reset(&mut self) { 24 | *self.value.borrow_mut() = 0; 25 | } 26 | } 27 | 28 | let v1 = Rc::new(RefCell::new(0)); 29 | let v2 = Rc::new(RefCell::new(0)); 30 | 31 | let c1 = Box::new(CounterWrap { value: v1.clone() }); 32 | let c2 = Box::new(CounterWrap { value: v2.clone() }); 33 | 34 | let mut bt = Node::sequence(vec![ 35 | Node::stateful_action("inc_x", c1), 36 | Node::stateful_action("inc_y", c2) 37 | ]); 38 | 39 | // Player visible? 40 | // / \ 41 | // S Wait 42 | // | 43 | // X -> Y 44 | // 45 | // sequence(start_attack, wait, stop_attack) 46 | 47 | let mut data = (); 48 | assert_eq!(*v1.borrow(), 0); 49 | assert_eq!(*v2.borrow(), 0); 50 | 51 | let status = bt.tick(0.0, &mut data); 52 | assert_eq!(status, Status::Running); 53 | assert_eq!(*v1.borrow(), 1); 54 | assert_eq!(*v2.borrow(), 0); 55 | 56 | let status = bt.tick(0.0, &mut data); 57 | assert_eq!(status, Status::Running); 58 | assert_eq!(*v1.borrow(), 1); 59 | assert_eq!(*v2.borrow(), 1); 60 | 61 | let status = bt.tick(0.0, &mut data); 62 | assert_eq!(status, Status::Success); 63 | assert_eq!(*v1.borrow(), 1); 64 | assert_eq!(*v2.borrow(), 1); 65 | 66 | // TODO: check stateful action reset 67 | // let (status, debug_repr) = bt.tick(0.0, &mut data); 68 | // assert_eq!(debug_repr.cursor.index(), 0); 69 | // assert_eq!(status, Status::Running); 70 | } 71 | -------------------------------------------------------------------------------- /tests/wait_test.rs: -------------------------------------------------------------------------------- 1 | use behavior_tree::*; 2 | 3 | #[test] 4 | fn wait_behavior_test() { 5 | let mut tree: Node<()> = Node::wait(0.5); 6 | 7 | // Ticks bigger than the wait time get clamped to the wait time. 8 | let status = tree.tick(0.3, &mut ()); 9 | assert_eq!(status, Status::Running); 10 | let status = tree.tick(0.3, &mut ()); 11 | assert_eq!(status, Status::Success); 12 | let status = tree.tick(0.3, &mut ()); 13 | assert_eq!(status, Status::Running); 14 | let status = tree.tick(0.3, &mut ()); 15 | assert_eq!(status, Status::Success); 16 | let status = tree.tick(0.3, &mut ()); 17 | assert_eq!(status, Status::Running); 18 | let status = tree.tick(0.3, &mut ()); 19 | assert_eq!(status, Status::Success); 20 | 21 | let status = tree.tick(1.5, &mut ()); 22 | assert_eq!(status, Status::Success); 23 | 24 | let status = tree.tick(0.1, &mut ()); 25 | assert_eq!(status, Status::Running); 26 | let status = tree.tick(0.1, &mut ()); 27 | assert_eq!(status, Status::Running); 28 | let status = tree.tick(0.1, &mut ()); 29 | assert_eq!(status, Status::Running); 30 | let status = tree.tick(0.1, &mut ()); 31 | assert_eq!(status, Status::Running); 32 | 33 | // Now that 0.4 has passed the timer is at ~0.1 34 | match tree.behavior { 35 | Behavior::Wait { curr, max: _ } => { 36 | assert!((curr - 0.1).abs() < 1e-5); 37 | } 38 | _ => panic!("Unexpected behavior type."), 39 | } 40 | 41 | let status = tree.tick(0.12, &mut ()); 42 | assert_eq!(status, Status::Success); 43 | 44 | let status = tree.tick(0.1, &mut ()); 45 | assert_eq!(status, Status::Running); 46 | } 47 | -------------------------------------------------------------------------------- /tests/while_test.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use behavior_tree::*; 4 | 5 | struct Blackboard { 6 | cond: bool, 7 | } 8 | 9 | #[test] 10 | fn test_simple_while_positive() { 11 | let mut bb = Blackboard { cond: false }; 12 | let mut bt: Node = 13 | Node::named_while_single("test", Box::new(|data| data.cond), NoTick::action()); 14 | 15 | let status = bt.tick(1.0, &mut bb); 16 | assert_eq!(status, Status::Failure); 17 | let status = bt.tick(1.0, &mut bb); 18 | assert_eq!(status, Status::Failure); 19 | let status = bt.tick(1.0, &mut bb); 20 | assert_eq!(status, Status::Failure); 21 | } 22 | 23 | #[test] 24 | fn test_simple_while_negative() { 25 | let mut bb = Blackboard { cond: true }; 26 | let mut bt: Node = 27 | Node::named_while_single("test", Box::new(|data| data.cond), YesTick::action()); 28 | 29 | let status = bt.tick(1.0, &mut bb); 30 | assert_eq!(status, Status::Success); 31 | let status = bt.tick(1.0, &mut bb); 32 | assert_eq!(status, Status::Success); 33 | let status = bt.tick(1.0, &mut bb); 34 | assert_eq!(status, Status::Success); 35 | } 36 | 37 | #[test] 38 | fn test_simple_while_running() { 39 | let mut bb = Blackboard { cond: true }; 40 | let mut bt: Node = 41 | Node::named_while_single("test", Box::new(|data| data.cond), AlwaysRunning::action()); 42 | 43 | let status = bt.tick(1.0, &mut bb); 44 | assert_eq!(status, Status::Running); 45 | let status = bt.tick(1.0, &mut bb); 46 | assert_eq!(status, Status::Running); 47 | let status = bt.tick(1.0, &mut bb); 48 | assert_eq!(status, Status::Running); 49 | } 50 | 51 | #[test] 52 | fn test_while_sequence() { 53 | let mut bb = Blackboard { cond: true }; 54 | let mut bt: Node = Node::sequence(vec![ 55 | Node::named_while_single("test", Box::new(|data| data.cond), AlwaysRunning::action()), 56 | NoTick::action(), 57 | ]); 58 | 59 | let status = bt.tick(1.0, &mut bb); 60 | assert_eq!(status, Status::Running); 61 | let status = bt.tick(1.0, &mut bb); 62 | assert_eq!(status, Status::Running); 63 | let status = bt.tick(1.0, &mut bb); 64 | assert_eq!(status, Status::Running); 65 | } 66 | 67 | #[test] 68 | fn test_while_select() { 69 | let mut bb = Blackboard { cond: false }; 70 | let mut bt: Node = Node::select(vec![ 71 | Node::named_while_single("test", Box::new(|data| data.cond), NoTick::action()), 72 | YesTick::action(), 73 | ]); 74 | 75 | let status = bt.tick(1.0, &mut bb); 76 | assert_eq!(status, Status::Success); 77 | let status = bt.tick(1.0, &mut bb); 78 | assert_eq!(status, Status::Success); 79 | let status = bt.tick(1.0, &mut bb); 80 | assert_eq!(status, Status::Success); 81 | } 82 | 83 | #[test] 84 | fn test_while_select_recheck() { 85 | let (counter_action, counter) = Counter::action(false); 86 | 87 | let value = Rc::new(RefCell::new(true)); 88 | let v2 = value.clone(); 89 | 90 | let mut bt: Node<()> = Node::select(vec![ 91 | Node::named_while_single_child( 92 | "test", 93 | Box::new(move |_data| *v2.borrow()), 94 | counter_action, 95 | ), 96 | // Node::stateful_action( 97 | // "const", 98 | // Box::new(ConstAction { 99 | // return_status: const_status.clone(), 100 | // }), 101 | // ), 102 | // Node::stateful_action("counter", Box::new(Counter::default())), 103 | AlwaysRunning::action(), 104 | ]); 105 | 106 | let status = bt.tick(1.0, &mut ()); 107 | assert_eq!(status, Status::Success); 108 | // TODO: index 1? 109 | assert_eq!(*counter.borrow(), 1); 110 | 111 | let status = bt.tick(1.0, &mut ()); 112 | assert_eq!(status, Status::Success); 113 | assert_eq!(*counter.borrow(), 2); 114 | 115 | *value.borrow_mut() = false; 116 | 117 | let status = bt.tick(1.0, &mut ()); 118 | assert_eq!(status, Status::Running); 119 | assert_eq!(*counter.borrow(), 2); 120 | 121 | let status = bt.tick(1.0, &mut ()); 122 | assert_eq!(status, Status::Running); 123 | assert_eq!(*counter.borrow(), 2); 124 | 125 | *value.borrow_mut() = true; 126 | 127 | let status = bt.tick(1.0, &mut ()); 128 | assert_eq!(status, Status::Success); 129 | assert_eq!(*counter.borrow(), 3); 130 | 131 | // *const_status.borrow_mut() = Status::Success; 132 | // 133 | // let status = bt.tick(1.0, &mut ()); 134 | // assert_eq!(status, Status::Success); 135 | // 136 | // *const_status.borrow_mut() = Status::Running; 137 | // 138 | // let status = bt.tick(1.0, &mut ()); 139 | // assert_eq!(status, Status::Running); 140 | } 141 | -------------------------------------------------------------------------------- /tests/yesno_test.rs: -------------------------------------------------------------------------------- 1 | use behavior_tree::*; 2 | 3 | #[test] 4 | #[should_panic] 5 | fn test_yes_tick_panics_without_tick() { 6 | let _bt: Node<()> = YesTick::action(); 7 | } 8 | 9 | #[test] 10 | fn test_yes_tick_likes_being_ticked() { 11 | let mut bt: Node<()> = YesTick::action(); 12 | bt.tick(1.0, &mut ()); 13 | } 14 | 15 | 16 | #[test] 17 | fn test_no_tick_without_tick() { 18 | let _bt: Node<()> = NoTick::action(); 19 | } 20 | 21 | #[test] 22 | #[should_panic] 23 | fn test_no_tick_crash_with_tick() { 24 | let mut bt: Node<()> = NoTick::action(); 25 | bt.tick(1.0, &mut ()); 26 | } 27 | 28 | --------------------------------------------------------------------------------