use std::cell::RefCell;
use std::collections::HashSet;
use std::rc::Rc;
use arora_behavior::Status;
use arora_types::data::{prefix_covers, Slot};
use arora_types::value::Value;
use uuid::Uuid;
use crate::error::BehaviorTreeError;
use crate::nodes::SCATTER_WITHIN_PARAM_ID;
use crate::schema::{Expression, Node};
use crate::variable::{is_tree_local, VariableResolver};
use crate::{BehaviorTree, Outputs};
pub(crate) struct Scatter {
node: Uuid,
within: String,
resolver: Option<Rc<VariableResolver<'static>>>,
bound: RefCell<Bound>,
}
#[derive(Default)]
struct Bound {
keys: Option<Value>,
slots: Vec<Box<dyn Slot>>,
}
impl Scatter {
pub(crate) fn bind(node: &Node) -> Result<Self, BehaviorTreeError> {
let refuse = |what: String| BehaviorTreeError::InconsistentTreeError {
message: format!("Scatter node {}: {what}", node.id),
};
let within = match node.arguments.get(&SCATTER_WITHIN_PARAM_ID) {
Some(Expression::Value(Value::String(within))) => within,
other => {
return Err(refuse(format!(
"`within` is a key prefix fixed at lowering, a literal string, not {other:?}"
)))
}
};
if within.is_empty() || within.ends_with('/') || is_tree_local(within) {
return Err(refuse(format!(
"`within` '{within}' is not a key prefix: it is empty, ends with '/' or is \
tree-local"
)));
}
Ok(Scatter {
node: node.id,
within: within.clone(),
resolver: None,
bound: RefCell::default(),
})
}
pub(crate) fn write(&self, keys: Value, values: Value) -> Result<Status, BehaviorTreeError> {
let Ok(values) = values.into_elements() else {
return Ok(Status::Failure);
};
let mut bound = self.bound.borrow_mut();
if bound.keys.as_ref() != Some(&keys) {
let Some(names) = key_names(&keys) else {
return Ok(Status::Failure);
};
if names.len() != values.len() {
return Ok(Status::Failure);
}
*bound = Bound {
slots: self.resolve(&names)?,
keys: Some(keys),
};
}
if bound.slots.len() != values.len() {
return Ok(Status::Failure);
}
for (slot, value) in bound.slots.iter().zip(values) {
if value != Value::Unit {
let _ = slot.set(Some(value));
}
}
Ok(Status::Success)
}
fn resolve(&self, names: &[String]) -> Result<Vec<Box<dyn Slot>>, BehaviorTreeError> {
let refuse = |what: String| BehaviorTreeError::InconsistentTreeError {
message: format!("Scatter node {}: {what}", self.node),
};
let mut named = HashSet::with_capacity(names.len());
for name in names {
if !prefix_covers(&self.within, name) {
return Err(refuse(format!(
"the key '{name}' is not within '{}'",
self.within
)));
}
if !named.insert(name) {
return Err(refuse(format!("the key '{name}' is named twice")));
}
}
let resolver = self
.resolver
.as_ref()
.ok_or_else(|| refuse("the tree has no store to resolve keys in".to_string()))?;
names
.iter()
.map(|name| {
resolver(name).ok_or_else(|| refuse(format!("the key '{name}' has no store slot")))
})
.collect()
}
}
fn key_names(keys: &Value) -> Option<Vec<String>> {
match keys {
Value::ArrayString(names) => Some(names.clone()),
Value::ArrayValue(elements) => elements
.iter()
.map(|element| match element {
Value::String(name) => Some(name.clone()),
_ => None,
})
.collect(),
_ => None,
}
}
impl BehaviorTree {
pub fn with_key_resolver(mut self, resolver: Rc<VariableResolver<'static>>) -> Self {
for outputs in self.outputs.values_mut() {
if let Outputs::Keys(scatter) = outputs {
*scatter = Rc::new(Scatter {
node: scatter.node,
within: scatter.within.clone(),
resolver: Some(resolver.clone()),
bound: RefCell::default(),
});
}
}
self
}
}
#[cfg(test)]
mod tests;