use crate::arora_generated::behavior_tree::status::Status;
use crate::load_behavior_tree_yaml;
use crate::nodes;
use crate::tree_node::TreeNode;
use crate::{run_behavior_tree, BehaviorTree, BehaviorTreeRuntime, ModuleFunction};
use anyhow::Result;
use arora_types::call::{Call, CallBridge, CallError, CallResult, Callable, CallableId};
use arora_types::record::module::frozen::Function;
use arora_types::record::ty::{FrozenTy, PrimitiveKind};
use arora_types::value::Value;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use uuid::Uuid;
#[test]
pub fn load_parse_error() -> Result<()> {
let tree_yaml = "I'm singing in the rain...";
assert!(load_behavior_tree_yaml(tree_yaml).is_err());
Ok(())
}
#[test]
pub fn load_simple_tree() -> Result<()> {
let tree_yaml = &crate::schema::tests::SIMPLE_TREE_YAML;
load_behavior_tree_yaml(tree_yaml)?;
Ok(())
}
#[test]
fn shared_variable_id_resolves_to_one_cell() {
use crate::schema::{Expression, NodeParameterId};
use crate::variable::VariableCell;
let var_id = Uuid::new_v4();
let mut variables: HashMap<Uuid, VariableCell> = HashMap::new();
let mut node_parameters: HashMap<NodeParameterId, VariableCell> = HashMap::new();
let param_a = NodeParameterId {
node: Uuid::new_v4(),
parameter: Uuid::new_v4(),
};
let param_b = NodeParameterId {
node: Uuid::new_v4(),
parameter: Uuid::new_v4(),
};
let cell_a = crate::setup_node_parameter_variable(
¶m_a,
&Expression::VariableId(var_id),
&mut variables,
&mut node_parameters,
&|_| None,
&HashMap::new(),
)
.unwrap();
let cell_b = crate::setup_node_parameter_variable(
¶m_b,
&Expression::VariableId(var_id),
&mut variables,
&mut node_parameters,
&|_| None,
&HashMap::new(),
)
.unwrap();
cell_a.set(Value::Boolean(true));
assert_eq!(
cell_b.get_or_unit(),
Value::Boolean(true),
"two parameters bound to the same variable id must share one cell"
);
}
#[test]
fn resolved_variable_is_store_backed() {
use crate::schema::{Expression, NodeParameterId};
use crate::variable::VariableCell;
use arora_simple_data_store::SimpleDataStore;
use arora_types::data::{DataStore, Key, StateChange};
let store = SimpleDataStore::new();
let resolver = {
let store = store.clone();
move |name: &str| Some(store.slot(&Key::from(name)))
};
let var_id = Uuid::new_v4();
let names = HashMap::from([(var_id, "battery.level".to_string())]);
let mut variables: HashMap<Uuid, VariableCell> = HashMap::new();
let mut node_parameters: HashMap<NodeParameterId, VariableCell> = HashMap::new();
let param = NodeParameterId {
node: Uuid::new_v4(),
parameter: Uuid::new_v4(),
};
let cell = crate::setup_node_parameter_variable(
¶m,
&Expression::VariableId(var_id),
&mut variables,
&mut node_parameters,
&resolver,
&names,
)
.unwrap();
cell.set(Value::from(1.0_f64));
assert_eq!(
store.read(&[Key::from("battery.level")]),
vec![Some(Value::from(1.0_f64))],
"a write through the resolved cell must reach the store key"
);
store
.write(StateChange::set("battery.level", Value::from(2.0_f64)))
.unwrap();
assert_eq!(
cell.get(),
Some(Value::from(2.0_f64)),
"a write through the store key must be visible through the resolved cell"
);
}
type LeafStatuses = Rc<RefCell<HashMap<Uuid, Status>>>;
type LeafTicks = Rc<RefCell<HashMap<Uuid, u32>>>;
struct TestBridge {
registered: HashMap<u64, Rc<dyn Callable>>,
next_id: u64,
leaf_statuses: LeafStatuses,
leaf_ticks: LeafTicks,
}
impl TestBridge {
fn new(leaf_statuses: LeafStatuses, leaf_ticks: LeafTicks) -> Self {
Self {
registered: HashMap::new(),
next_id: 0,
leaf_statuses,
leaf_ticks,
}
}
fn empty() -> Self {
Self::new(
Rc::new(RefCell::new(HashMap::new())),
Rc::new(RefCell::new(HashMap::new())),
)
}
}
impl CallBridge for TestBridge {
fn arora_call(&mut self, _module: &Uuid, call: Call) -> Result<CallResult, CallError> {
*self.leaf_ticks.borrow_mut().entry(call.id).or_insert(0) += 1;
let status = self
.leaf_statuses
.borrow()
.get(&call.id)
.cloned()
.ok_or(CallError::FunctionNotFound { id: call.id })?;
Ok(CallResult {
ret: status.into(),
mutated: Vec::new(),
})
}
fn arora_register_callable(&mut self, callable: Rc<dyn Callable>) -> CallableId {
let id = self.next_id;
self.next_id += 1;
self.registered.insert(id, callable);
CallableId { id }
}
fn arora_unregister_callable(&mut self, callable_id: &CallableId) {
self.registered.remove(&callable_id.id);
}
fn arora_call_indirect(&mut self, callable_id: &CallableId) -> Result<Value, CallError> {
let callable = self
.registered
.get(&callable_id.id)
.cloned()
.ok_or(CallError::Generic {
message: format!("unknown callable {}", callable_id.id),
})?;
callable.call(self)
}
}
fn scripted_leaf(function: Uuid) -> TreeNode {
TreeNode {
function,
children: None,
parameters: HashMap::new(),
}
}
fn scripted_leaf_function(function: Uuid) -> ModuleFunction {
ModuleFunction {
module_id: Uuid::nil(),
function_id: function,
function_name: "scripted_leaf".to_string(),
function: Function {
parameters: HashMap::new(),
parameter_ordering: Vec::new(),
return_ty: FrozenTy::from(PrimitiveKind::U8),
},
}
}
fn build(node: TreeNode) -> BehaviorTree {
node.try_into().expect("tree builds")
}
fn tick_once(tree: &BehaviorTree) -> Status {
let mut bridge = TestBridge::empty();
let mut runtime = BehaviorTreeRuntime::setup(tree, Rc::new(HashMap::new()), &mut bridge, false)
.expect("runtime sets up");
runtime.tick().expect("tick succeeds")
}
fn run(tree: &BehaviorTree) -> Status {
let mut bridge = TestBridge::empty();
run_behavior_tree(tree, Rc::new(HashMap::new()), &mut bridge, false).expect("run succeeds")
}
#[test]
fn seq_all_success_is_success() {
let tree = build(nodes::seq(vec![nodes::succeed(), nodes::succeed()]));
assert_eq!(run(&tree), Status::Success);
}
#[test]
fn seq_first_failure_is_failure() {
let later = Uuid::from_u128(0xA1);
let tree = build(nodes::seq(vec![nodes::fail(), scripted_leaf(later)]));
assert_eq!(tick_once(&tree), Status::Failure);
}
#[test]
fn seq_running_child_is_running() {
let tree = build(nodes::seq(vec![nodes::succeed(), nodes::run()]));
assert_eq!(tick_once(&tree), Status::Running);
}
#[test]
fn fallback_first_success_is_success() {
let tree = build(nodes::fallback(vec![nodes::succeed(), nodes::fail()]));
assert_eq!(run(&tree), Status::Success);
}
#[test]
fn fallback_all_failure_is_failure() {
let tree = build(nodes::fallback(vec![nodes::fail(), nodes::fail()]));
assert_eq!(run(&tree), Status::Failure);
}
#[test]
fn fallback_empty_is_success() {
let tree = build(nodes::fallback(vec![]));
assert_eq!(run(&tree), Status::Success);
}
#[test]
fn fallback_running_child_is_running() {
let tree = build(nodes::fallback(vec![nodes::run(), nodes::succeed()]));
assert_eq!(tick_once(&tree), Status::Running);
}
#[test]
fn parallel_all_success_is_success() {
let tree = build(nodes::parallel(vec![nodes::succeed(), nodes::succeed()]));
assert_eq!(run(&tree), Status::Success);
}
#[test]
fn parallel_any_failure_is_failure() {
let tree = build(nodes::parallel(vec![
nodes::succeed(),
nodes::fail(),
nodes::succeed(),
]));
assert_eq!(run(&tree), Status::Failure);
}
#[test]
fn parallel_mixed_running_is_running() {
let tree = build(nodes::parallel(vec![nodes::succeed(), nodes::run()]));
assert_eq!(tick_once(&tree), Status::Running);
}
#[test]
fn seq_star_resumes_and_resets() {
let first = Uuid::from_u128(0x1);
let second = Uuid::from_u128(0x2);
let third = Uuid::from_u128(0x3);
let statuses: LeafStatuses = Rc::new(RefCell::new(HashMap::from([
(first, Status::Success),
(second, Status::Running),
(third, Status::Success),
])));
let ticks: LeafTicks = Rc::new(RefCell::new(HashMap::new()));
let function_index = Rc::new(HashMap::from([
(first, scripted_leaf_function(first)),
(second, scripted_leaf_function(second)),
(third, scripted_leaf_function(third)),
]));
let tree = build(nodes::seq_star(vec![
scripted_leaf(first),
scripted_leaf(second),
scripted_leaf(third),
]));
let mut bridge = TestBridge::new(statuses.clone(), ticks.clone());
let mut runtime = BehaviorTreeRuntime::setup(&tree, function_index.clone(), &mut bridge, false)
.expect("setup");
assert_eq!(runtime.tick().expect("tick"), Status::Running);
let _ = runtime;
assert_eq!(*ticks.borrow().get(&first).unwrap_or(&0), 1);
assert_eq!(*ticks.borrow().get(&second).unwrap_or(&0), 1);
assert_eq!(*ticks.borrow().get(&third).unwrap_or(&0), 0);
statuses.borrow_mut().insert(second, Status::Success);
let mut runtime = BehaviorTreeRuntime::setup(&tree, function_index.clone(), &mut bridge, false)
.expect("setup");
assert_eq!(runtime.tick().expect("tick"), Status::Success);
let _ = runtime;
assert_eq!(
*ticks.borrow().get(&first).unwrap_or(&0),
1,
"first not re-ticked"
);
assert_eq!(*ticks.borrow().get(&second).unwrap_or(&0), 2);
assert_eq!(*ticks.borrow().get(&third).unwrap_or(&0), 1);
let mut runtime = BehaviorTreeRuntime::setup(&tree, function_index.clone(), &mut bridge, false)
.expect("setup");
assert_eq!(runtime.tick().expect("tick"), Status::Success);
let _ = runtime;
assert_eq!(
*ticks.borrow().get(&first).unwrap_or(&0),
2,
"first re-ticked after reset"
);
}