Skip to content

Commit

Permalink
Generic blackboard implementation (#26)
Browse files Browse the repository at this point in the history
Generalize blackboard. 

From user's perspective, in addition to being able to use generic
blackboard type, the user must now use tick directly with `bt.tick(...)`
instead of `bt.state.tick(...)` This change is required so that we can
pass a mutable reference of the blackboard to the user's tick callback
under the hood.
  • Loading branch information
kaphula committed Dec 18, 2023
1 parent 55b280e commit ec272b3
Show file tree
Hide file tree
Showing 16 changed files with 225 additions and 134 deletions.
2 changes: 1 addition & 1 deletion bonsai/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ name = "bonsai-bt"
readme = "../README.md"
repository = "https://github.com/sollimann/bonsai.git"
rust-version = "1.60.0"
version = "0.5.0"
version = "0.6.0"

[lib]
name = "bonsai_bt"
Expand Down
43 changes: 32 additions & 11 deletions bonsai/src/bt.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use std::collections::HashMap;
use std::fmt::Debug;

use petgraph::dot::{Config, Dot};
use petgraph::{stable_graph::NodeIndex, Graph};

use crate::visualizer::NodeType;
use crate::{Behavior, State};
use crate::{ActionArgs, Behavior, State, Status, UpdateEvent};

/// A "blackboard" is a simple key/value storage shared by all the nodes of the Tree.
///
Expand All @@ -16,32 +15,32 @@ use crate::{Behavior, State};
///
/// An "entry" of the Blackboard is a key/value pair.
#[derive(Clone, Debug)]
pub struct BlackBoard<K, V>(HashMap<K, V>);
pub struct BlackBoard<K>(K);

impl<K, V> BlackBoard<K, V> {
pub fn get_db(&mut self) -> &mut HashMap<K, V> {
impl<K> BlackBoard<K> {
pub fn get_db(&mut self) -> &mut K {
&mut self.0
}
}

/// The BT struct contains a compiled (immutable) version
/// of the behavior and a blackboard key/value storage
#[derive(Clone, Debug)]
pub struct BT<A, K, V> {
pub struct BT<A, K> {
/// constructed behavior tree
pub state: State<A>,
/// keep the initial state
initial_behavior: Behavior<A>,
/// blackboard
bb: BlackBoard<K, V>,
bb: BlackBoard<K>,
/// Tree formulated as PetGraph
pub(crate) graph: Graph<NodeType<A>, u32, petgraph::Directed>,
/// root node
root_id: NodeIndex,
}

impl<A: Clone + Debug, K: Debug, V: Debug> BT<A, K, V> {
pub fn new(behavior: Behavior<A>, blackboard: HashMap<K, V>) -> Self {
impl<A: Clone + Debug, K: Debug> BT<A, K> {
pub fn new(behavior: Behavior<A>, blackboard: K) -> Self {
let backup_behavior = behavior.clone();
let bt = State::new(behavior);

Expand All @@ -58,6 +57,28 @@ impl<A: Clone + Debug, K: Debug, V: Debug> BT<A, K, V> {
}
}

/// Updates the cursor that tracks an event.
///
/// The action need to return status and remaining delta time.
/// Returns status and the remaining delta time.
///
/// Passes event, delta time in seconds, action and state to closure.
/// The closure should return a status and remaining delta time.
///
/// return: (Status, f64)
/// function returns the result of the tree traversal, and how long
/// it actually took to complete the traversal and propagate the
/// results back up to the root node
#[inline]
pub fn tick<E, F>(&mut self, e: &E, f: &mut F) -> (Status, f64)
where
E: UpdateEvent,
F: FnMut(ActionArgs<E, A>, &mut BlackBoard<K>) -> (Status, f64),
A: Debug,
{
self.state.tick(e, &mut self.bb, f)
}

/// Compile the behavior tree into a [graphviz](https://graphviz.org/) compatible [DiGraph](https://docs.rs/petgraph/latest/petgraph/graph/type.DiGraph.html).
///
/// ```rust
Expand Down Expand Up @@ -96,13 +117,13 @@ impl<A: Clone + Debug, K: Debug, V: Debug> BT<A, K, V> {

/// Retrieve a mutable reference to the blackboard for
/// this Behavior Tree
pub fn get_blackboard(&mut self) -> &mut BlackBoard<K, V> {
pub fn get_blackboard(&mut self) -> &mut BlackBoard<K> {
&mut self.bb
}

/// Retrieve a mutable reference to the internal state
/// of the Behavior Tree
pub fn get_state(bt: &mut BT<A, K, V>) -> &mut State<A> {
pub fn get_state(bt: &mut BT<A, K>) -> &mut State<A> {
&mut bt.state
}

Expand Down
8 changes: 4 additions & 4 deletions bonsai/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
//!
//! ```rust
//! use bonsai_bt::{Event, Success, UpdateArgs, BT};
//!
//! use std::collections::HashMap;
//! // Some test actions.
//! #[derive(Clone, Debug, Copy)]
//! pub enum Actions {
Expand All @@ -48,10 +48,10 @@
//! }
//!
//! // A test state machine that can increment and decrement.
//! fn tick(mut acc: i32, dt: f64, bt: &mut BT<Actions, String, i32>) -> i32 {
//! let e: Event = UpdateArgs { dt }.into();
//! fn tick(mut acc: i32, dt: f64, bt: &mut BT<Actions, HashMap<String, i32>>) -> i32 {
//! let e: Event = UpdateArgs { dt }.into();
//!
//! let (_status, _dt) = bt.state.tick(&e, &mut |args| match *args.action {
//! let (_status, _dt) = bt.tick(&e, &mut |args, blackboard| match *args.action {
//! Actions::Inc => {
//! acc += 1;
//! (Success, args.dt)
Expand Down
34 changes: 24 additions & 10 deletions bonsai/src/sequence.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
use crate::status::Status::*;
use crate::{event::UpdateEvent, ActionArgs, Behavior, State, Status, RUNNING};
use std::fmt::Debug;
pub(crate) struct SequenceArgs<'a, A, E, F, B> {
pub select: bool,
pub upd: Option<f64>,
pub seq: &'a [Behavior<A>],
pub i: &'a mut usize,
pub cursor: &'a mut Box<State<A>>,
pub e: &'a E,
pub blackboard: &'a mut B,
pub f: &'a mut F,
}

// `Sequence` and `Select` share same algorithm.
//
// `Sequence` fails if any fails and succeeds when all succeeds.
// `Select` succeeds if any succeeds and fails when all fails.
pub fn sequence<A, E, F>(
select: bool,
upd: Option<f64>,
seq: &[Behavior<A>],
i: &mut usize,
cursor: &mut Box<State<A>>,
e: &E,
f: &mut F,
) -> (Status, f64)
pub fn sequence<A, E, F, B>(args: SequenceArgs<A, E, F, B>) -> (Status, f64)
where
A: Clone,
E: UpdateEvent,
F: FnMut(ActionArgs<E, A>) -> (Status, f64),
F: FnMut(ActionArgs<E, A>, &mut B) -> (Status, f64),
A: Debug,
{
let SequenceArgs {
select,
upd,
seq,
i,
cursor,
e,
blackboard,
f,
} = args;

let (status, inv_status) = if select {
// `Select`
(Status::Failure, Status::Success)
Expand All @@ -39,6 +52,7 @@ where
}
_ => e,
},
blackboard,
f,
) {
(Running, _) => {
Expand Down
64 changes: 45 additions & 19 deletions bonsai/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use std::fmt::Debug;

use crate::event::UpdateEvent;
use crate::sequence::sequence;
use crate::sequence::{sequence, SequenceArgs};
use crate::state::State::*;
use crate::status::Status::*;
use crate::when_all::when_all;
use crate::{Behavior, Status};
use std::fmt::Debug;

// use serde_derive::{Deserialize, Serialize};

/// The action is still running, and thus the action consumes
Expand Down Expand Up @@ -118,10 +120,10 @@ impl<A: Clone> State<A> {
/// function returns the result of the tree traversal, and how long
/// it actually took to complete the traversal and propagate the
/// results back up to the root node
pub fn tick<E, F>(&mut self, e: &E, f: &mut F) -> (Status, f64)
pub fn tick<E, F, B>(&mut self, e: &E, blackboard: &mut B, f: &mut F) -> (Status, f64)
where
E: UpdateEvent,
F: FnMut(ActionArgs<E, A>) -> (Status, f64),
F: FnMut(ActionArgs<E, A>, &mut B) -> (Status, f64),
A: Debug,
{
let upd = e.update(|args| Some(args.dt)).unwrap_or(None);
Expand All @@ -130,23 +132,26 @@ impl<A: Clone> State<A> {
match (upd, self) {
(_, &mut ActionState(ref action)) => {
// println!("In ActionState: {:?}", action);
f(ActionArgs {
event: e,
dt: upd.unwrap_or(0.0),
action,
})
f(
ActionArgs {
event: e,
dt: upd.unwrap_or(0.0),
action,
},
blackboard,
)
}
(_, &mut InvertState(ref mut cur)) => {
// println!("In InvertState: {:?}", cur);
match cur.tick(e, f) {
match cur.tick(e, blackboard, f) {
(Running, dt) => (Running, dt),
(Failure, dt) => (Success, dt),
(Success, dt) => (Failure, dt),
}
}
(_, &mut AlwaysSucceedState(ref mut cur)) => {
// println!("In AlwaysSucceedState: {:?}", cur);
match cur.tick(e, f) {
match cur.tick(e, blackboard, f) {
(Running, dt) => (Running, dt),
(_, dt) => (Success, dt),
}
Expand All @@ -170,7 +175,7 @@ impl<A: Clone> State<A> {
// remaining delta time after condition.
loop {
*status = match *status {
Running => match state.tick(e, f) {
Running => match state.tick(e, blackboard, f) {
(Running, dt) => {
return (Running, dt);
}
Expand All @@ -194,6 +199,7 @@ impl<A: Clone> State<A> {
}
_ => e,
},
blackboard,
f,
);
}
Expand All @@ -203,17 +209,35 @@ impl<A: Clone> State<A> {
(_, &mut SelectState(ref seq, ref mut i, ref mut cursor)) => {
// println!("In SelectState: {:?}", seq);
let select = true;
sequence(select, upd, seq, i, cursor, e, f)
sequence(SequenceArgs {
select,
upd,
seq,
i,
cursor,
e,
f,
blackboard,
})
}
(_, &mut SequenceState(ref seq, ref mut i, ref mut cursor)) => {
// println!("In SequenceState: {:?}", seq);
let select = false;
sequence(select, upd, seq, i, cursor, e, f)
sequence(SequenceArgs {
select,
upd,
seq,
i,
cursor,
e,
f,
blackboard,
})
}
(_, &mut WhileState(ref mut ev_cursor, ref rep, ref mut i, ref mut cursor)) => {
// println!("In WhileState: {:?}", ev_cursor);
// If the event terminates, do not execute the loop.
match ev_cursor.tick(e, f) {
match ev_cursor.tick(e, blackboard, f) {
(Running, _) => {}
x => return x,
};
Expand All @@ -229,6 +253,7 @@ impl<A: Clone> State<A> {
}
_ => e,
},
blackboard,
f,
) {
(Failure, x) => return (Failure, x),
Expand Down Expand Up @@ -257,19 +282,19 @@ impl<A: Clone> State<A> {
(_, &mut WhenAllState(ref mut cursors)) => {
// println!("In WhenAllState: {:?}", cursors);
let any = false;
when_all(any, upd, cursors, e, f)
when_all(any, upd, cursors, e, f, blackboard)
}
(_, &mut WhenAnyState(ref mut cursors)) => {
// println!("In WhenAnyState: {:?}", cursors);
let any = true;
when_all(any, upd, cursors, e, f)
when_all(any, upd, cursors, e, f, blackboard)
}
(_, &mut AfterState(ref mut i, ref mut cursors)) => {
// println!("In AfterState: {}", i);
// Get the least delta time left over.
let mut min_dt = f64::MAX;
for (j, item) in cursors.iter_mut().enumerate().skip(*i) {
match item.tick(e, f) {
match item.tick(e, blackboard, f) {
(Running, _) => {
min_dt = 0.0;
}
Expand Down Expand Up @@ -303,7 +328,7 @@ impl<A: Clone> State<A> {
// Only check the condition when the sequence starts.
if *i == 0 {
// If the event terminates, stop.
match ev_cursor.tick(e, f) {
match ev_cursor.tick(e, blackboard, f) {
(Running, _) => {}
x => return x,
};
Expand All @@ -317,6 +342,7 @@ impl<A: Clone> State<A> {
}
_ => e,
},
blackboard,
f,
) {
(Failure, x) => return (Failure, x),
Expand Down
9 changes: 5 additions & 4 deletions bonsai/src/visualizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub(crate) enum NodeType<A> {
After,
}

impl<A: Clone + Debug, K: Debug, V: Debug> BT<A, K, V> {
impl<A: Clone + Debug, K: Debug> BT<A, K> {
pub(crate) fn dfs_recursive(&mut self, behavior: Behavior<A>, parent_node: NodeIndex) {
match behavior {
Behavior::Action(action) => {
Expand Down Expand Up @@ -128,12 +128,13 @@ impl<A: Clone + Debug, K: Debug, V: Debug> BT<A, K, V> {
#[cfg(test)]
mod tests {
use super::*;
use crate::bt::BlackBoard;
use crate::visualizer::tests::TestActions::{Dec, Inc};
use crate::Behavior::{
Action, After, AlwaysSucceed, If, Invert, Select, Sequence, Wait, WaitForever, WhenAll, WhenAny, While,
};
use crate::Status::{self, Success};
use crate::{Event, UpdateArgs};
use crate::{ActionArgs, Event, UpdateArgs};
use petgraph::dot::{Config, Dot};
use petgraph::Graph;
use std::collections::HashMap;
Expand All @@ -148,9 +149,9 @@ mod tests {
}

// A test state machine that can increment and decrement.
fn tick(mut acc: i32, dt: f64, bt: &mut BT<TestActions, String, i32>) -> (i32, Status, f64) {
fn tick(mut acc: i32, dt: f64, bt: &mut BT<TestActions, HashMap<String, i32>>) -> (i32, Status, f64) {
let e: Event = UpdateArgs { dt }.into();
let (s, t) = bt.state.tick(&e, &mut |args| match args.action {
let (s, t) = bt.tick(&e, &mut |args, blackboard| match args.action {
TestActions::Inc => {
acc += 1;
(Success, args.dt)
Expand Down
Loading

0 comments on commit ec272b3

Please sign in to comment.