use databoard::Databoard;
use tinyscript::SharedRuntime;
use crate::{
BehaviorPtr, BehaviorResult, BehaviorState, ConstString, FAILURE_IF, ON_FAILURE, ON_SUCCESS, POST, SKIP_IF, SUCCESS_IF,
WHILE,
behavior_data::BehaviorData,
error::Error,
pre_post_conditions::{Conditions, PostConditions, PreConditions},
tree::{
tree_element_list::BehaviorTreeElementList,
tree_iter::{TreeIter, TreeIterMut},
},
};
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TreeElementKind {
Leaf,
Node,
SubTree,
}
pub struct BehaviorTreeElement {
kind: TreeElementKind,
behavior: BehaviorPtr,
data: BehaviorData,
children: BehaviorTreeElementList,
conditions: Conditions,
}
impl BehaviorTreeElement {
#[must_use]
pub fn create(kind: TreeElementKind, behavior: BehaviorPtr, data: BehaviorData) -> Self {
Self {
kind,
behavior,
data,
children: BehaviorTreeElementList::default(),
conditions: Conditions::default(),
}
}
#[must_use]
pub fn new(
kind: TreeElementKind,
behavior: BehaviorPtr,
data: BehaviorData,
children: BehaviorTreeElementList,
conditions: Conditions,
) -> Self {
Self {
kind,
behavior,
data,
children,
conditions,
}
}
#[must_use]
pub const fn uid(&self) -> u16 {
self.data.uid()
}
#[must_use]
pub const fn id(&self) -> &ConstString {
self.data.description().id()
}
#[must_use]
pub const fn name(&self) -> &ConstString {
self.data.description().name()
}
#[must_use]
pub const fn data(&self) -> &BehaviorData {
&self.data
}
#[must_use]
pub const fn data_mut(&mut self) -> &mut BehaviorData {
&mut self.data
}
#[must_use]
pub const fn behavior(&self) -> &BehaviorPtr {
&self.behavior
}
#[must_use]
pub const fn behavior_mut(&mut self) -> &mut BehaviorPtr {
&mut self.behavior
}
#[must_use]
pub const fn blackboard(&self) -> &Databoard {
self.data().blackboard()
}
#[must_use]
pub const fn blackboard_mut(&mut self) -> &mut Databoard {
self.data_mut().blackboard_mut()
}
#[must_use]
pub const fn children(&self) -> &BehaviorTreeElementList {
&self.children
}
#[must_use]
pub const fn children_mut(&mut self) -> &mut BehaviorTreeElementList {
&mut self.children
}
#[must_use]
pub const fn pre_conditions(&self) -> &PreConditions {
&self.conditions.pre
}
#[must_use]
pub const fn pre_conditions_mut(&mut self) -> &mut PreConditions {
&mut self.conditions.pre
}
#[must_use]
pub const fn post_conditions(&self) -> &PostConditions {
&self.conditions.post
}
#[must_use]
pub const fn post_conditions_mut(&mut self) -> &mut PostConditions {
&mut self.conditions.post
}
pub fn halt(&mut self, runtime: &SharedRuntime) -> Result<(), Error> {
if self.data.state() != BehaviorState::Idle {
let state = self
.behavior
.halt(&mut self.data, &mut self.children, runtime)?;
self.data.set_state(state);
if let Some(script) = self.conditions.post.get("_onHalted") {
let _ = runtime
.lock()
.run(script, self.data.blackboard_mut())?;
}
}
Ok(())
}
pub async fn tick(&mut self, runtime: &SharedRuntime) -> BehaviorResult {
let old_state = self.data.state();
let state = if let Some(result) = self.check_pre_conditions(runtime)? {
result
} else if old_state == BehaviorState::Idle {
self.behavior
.start(&mut self.data, &mut self.children, runtime)
.await?
} else {
self.behavior
.tick(&mut self.data, &mut self.children, runtime)
.await?
};
self.check_post_conditions(state, runtime)?;
if state != BehaviorState::Skipped {
self.data.set_state(state);
}
Ok(state)
}
pub fn halt_child_at(&mut self, index: usize, runtime: &SharedRuntime) -> Result<(), Error> {
self.children.halt_at(index, runtime)
}
pub fn halt_children_from(&mut self, index: usize, runtime: &SharedRuntime) -> Result<(), Error> {
self.children.halt_from(index, runtime)
}
pub fn halt_children(&mut self, runtime: &SharedRuntime) -> Result<(), Error> {
self.children.halt(runtime)
}
pub fn reset_state(&mut self) {
self.data.set_state(BehaviorState::Idle);
}
pub fn add_pre_state_change_callback<T>(&mut self, name: ConstString, callback: T)
where
T: Fn(&BehaviorData, &mut BehaviorState) + Send + Sync + 'static,
{
self.data
.add_pre_state_change_callback(name, callback);
}
pub fn remove_pre_state_change_callback(&mut self, name: &str) {
self.data.remove_pre_state_change_callback(name);
}
#[must_use]
pub fn children_iter(&self) -> impl DoubleEndedIterator<Item = &Self> {
self.children().iter()
}
#[must_use]
pub fn children_iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut Self> {
self.children_mut().iter_mut()
}
pub fn iter(&self) -> impl Iterator<Item = &Self> {
TreeIter::new(self)
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Self> {
TreeIterMut::new(self)
}
pub const fn kind(&self) -> TreeElementKind {
self.kind
}
fn check_pre_conditions(&mut self, runtime: &SharedRuntime) -> Result<Option<BehaviorState>, Error> {
if self.conditions.pre.is_some() {
if self.data.state() == BehaviorState::Idle || self.data.state() == BehaviorState::Skipped {
if let Some(script) = self.conditions.pre.get(FAILURE_IF) {
let res = runtime
.lock()
.run(script, self.data.blackboard_mut())?;
if bool::try_from(res)? {
return Ok(Some(BehaviorState::Failure));
}
}
if let Some(script) = self.conditions.pre.get(SUCCESS_IF) {
let res = runtime
.lock()
.run(script, self.data.blackboard_mut())?;
if bool::try_from(res)? {
return Ok(Some(BehaviorState::Success));
}
}
if let Some(script) = self.conditions.pre.get(SKIP_IF) {
let res = runtime
.lock()
.run(script, self.data.blackboard_mut())?;
if bool::try_from(res)? {
return Ok(Some(BehaviorState::Skipped));
}
}
if let Some(script) = self.conditions.pre.get(WHILE) {
let res = runtime
.lock()
.run(script, self.data.blackboard_mut())?;
if bool::try_from(res)? {
return Ok(Some(BehaviorState::Skipped));
}
}
} else
if self.data.state() == BehaviorState::Running
&& let Some(script) = self.conditions.pre.get(WHILE)
{
let res = runtime
.lock()
.run(script, self.data.blackboard_mut())?;
if bool::try_from(res)? {
let _res = self.halt(runtime);
return Ok(Some(BehaviorState::Skipped));
}
}
}
Ok(None)
}
fn check_post_conditions(&mut self, state: BehaviorState, runtime: &SharedRuntime) -> Result<(), Error> {
if self.conditions.post.is_some() {
match state {
BehaviorState::Failure => {
if let Some(script) = self.conditions.post.get(ON_FAILURE) {
let _ = runtime
.lock()
.run(script, self.data.blackboard_mut())?;
}
}
BehaviorState::Success => {
if let Some(script) = self.conditions.post.get(ON_SUCCESS) {
let _ = runtime
.lock()
.run(script, self.data.blackboard_mut())?;
}
}
_ => {}
}
if let Some(script) = self.conditions.post.get(POST) {
let _ = runtime
.lock()
.run(script, self.data.blackboard_mut())?;
}
}
Ok(())
}
#[must_use]
pub const fn groot2_path(&self) -> &ConstString {
self.data.description().groot2_path()
}
#[must_use]
pub const fn state(&self) -> BehaviorState {
self.data.state()
}
pub fn add_child(&mut self, child: Self) {
self.children.push(child);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
behavior_data::BehaviorData,
behavior_description::BehaviorDescription,
behavior_state::BehaviorState,
behaviors::mock_behavior::{MockBehavior, MockBehaviorConfig},
pre_post_conditions::Conditions,
};
use databoard::Databoard;
#[test]
fn new_constructor_covers_all_lines() {
let behavior: BehaviorPtr = alloc::boxed::Box::new(MockBehavior::new(MockBehaviorConfig {
return_state: BehaviorState::Success,
..Default::default()
}));
let data = BehaviorData::create(
42,
Databoard::default(),
BehaviorDescription::new("test_node", "test_node", false),
);
let children = BehaviorTreeElementList::default();
let conditions = Conditions::default();
let element = BehaviorTreeElement::new(TreeElementKind::Node, behavior, data, children, conditions);
assert_eq!(element.uid(), 42);
assert_eq!(element.name().as_ref(), "test_node");
assert!(matches!(element.kind(), TreeElementKind::Node));
assert_eq!(element.children().len(), 0);
}
}