use std::cell::{Cell, RefCell};
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
use arora_behavior::FunctionIndex;
use arora_types::call::{CallBridge, CallError};
use arora_types::value::Value;
use uuid::Uuid;
use crate::display::{stated_id, Names};
use crate::error::BehaviorTreeError;
use crate::nodes::{
REGEX_MATCH_FIRST_MATCH_PARAM_ID, REGEX_MATCH_FUNCTION_ID, RUN_STATUS_FUNCTION_ID,
RUN_STATUS_LATCH_PARAM_ID, RUN_STATUS_OUT_PARAM_ID, SEQ_STAR_CURRENT_INDEX_PARAM_ID,
SEQ_STAR_FUNCTION_ID, SET_STR_FUNCTION_ID, SET_STR_VARIABLE_PARAM_ID, UNPACK_FUNCTION_ID,
UNPACK_OUT_PARAM_IDS, UNSET_STR_FUNCTION_ID, UNSET_STR_VARIABLE_PARAM_ID,
};
use crate::schema::{Expression, Node, NodeParameterId, _RET_PARAM_ID};
use crate::variable::VariableCell;
use crate::{
argument_value, compute_expression, expression_call_signatures, is_interpreter_function_id,
is_native, is_optional, write_back, Scope,
};
#[derive(Debug, Default, Clone, PartialEq)]
pub struct DetachedPlan {
pub every_tick: Vec<Uuid>,
pub on_demand: Vec<Uuid>,
pub unused: Vec<Uuid>,
}
pub(crate) struct Detached {
nodes: Vec<Rc<Node>>,
ids: crate::id_hash::IdMap<Uuid, usize>,
bindings: crate::id_hash::IdMap<NodeParameterId, VariableCell>,
lowered: RefCell<Lowered>,
caches: RefCell<Vec<Cache>>,
tick: Cell<u64>,
observed: RefCell<HashSet<Uuid>>,
stack: RefCell<Vec<(usize, usize)>>,
}
#[derive(Default)]
struct Lowered {
signatures: FunctionIndex,
reads: Vec<Vec<usize>>,
arguments: Vec<Vec<Argument>>,
modules: Vec<Option<Uuid>>,
rewrites: Vec<bool>,
outputs: Vec<Vec<Output>>,
cells_only: Vec<bool>,
sinks: Vec<usize>,
observed: Vec<usize>,
pulls: HashMap<Uuid, Rc<[usize]>>,
plan: DetachedPlan,
}
struct Argument {
parameter: Uuid,
optional: bool,
source: Source,
}
enum Source {
Cell(Option<VariableCell>),
Computed(Expression),
}
#[derive(Default, Clone)]
struct Cache {
tick: u64,
arguments: Option<Vec<Value>>,
outputs: Vec<(Uuid, Value)>,
calls: u64,
failed: Option<String>,
}
fn inconsistent(message: String) -> BehaviorTreeError {
BehaviorTreeError::InconsistentTreeError { message }
}
fn cell_id(cell: &VariableCell) -> usize {
match cell {
VariableCell::Local(cell) => Rc::as_ptr(cell) as *const () as usize,
VariableCell::Stored(slot) => Rc::as_ptr(slot) as *const () as usize,
}
}
fn cells_read(
expression: &Expression,
variables: &HashMap<Uuid, VariableCell>,
node_parameters_variables: &crate::id_hash::IdMap<NodeParameterId, VariableCell>,
found: &mut Vec<VariableCell>,
) {
match expression {
Expression::Variable(cell) => found.push(cell.clone()),
Expression::VariableId(id) => found.extend(variables.get(id).cloned()),
Expression::NodeArgument(source) => {
found.extend(node_parameters_variables.get(source).cloned())
}
Expression::Select { source, .. } => {
cells_read(source, variables, node_parameters_variables, found)
}
Expression::Call(call) => {
let parts = [&call.module, &call.function].into_iter().chain(
call.arguments
.iter()
.flat_map(|(parameter, argument)| [parameter, argument]),
);
for part in parts {
cells_read(part, variables, node_parameters_variables, found);
}
}
Expression::Value(_) | Expression::Uuid(_) => {}
}
}
impl Detached {
pub(crate) fn select(nodes: &[Rc<Node>], attached: &HashSet<Uuid>) -> Vec<Rc<Node>> {
nodes
.iter()
.filter(|node| {
!attached.contains(&node.id)
&& !is_native(node.function)
&& node
.children
.as_ref()
.is_none_or(|children| children.is_empty())
})
.cloned()
.collect()
}
pub(crate) fn new(
nodes: Vec<Rc<Node>>,
bindings: crate::id_hash::IdMap<NodeParameterId, VariableCell>,
) -> Self {
let caches = RefCell::new(vec![Cache::default(); nodes.len()]);
Self {
ids: nodes
.iter()
.enumerate()
.map(|(at, node)| (node.id, at))
.collect(),
nodes,
bindings,
lowered: RefCell::default(),
caches,
tick: Cell::new(1),
observed: RefCell::default(),
stack: RefCell::default(),
}
}
pub(crate) fn empty() -> Self {
Self {
nodes: Vec::new(),
ids: crate::id_hash::IdMap::default(),
bindings: crate::id_hash::IdMap::default(),
lowered: RefCell::default(),
caches: RefCell::default(),
tick: Cell::new(1),
observed: RefCell::default(),
stack: RefCell::default(),
}
}
pub(crate) fn observe(&self, ids: impl IntoIterator<Item = Uuid>) {
self.observed
.borrow_mut()
.extend(ids.into_iter().filter(|id| self.ids.contains_key(id)));
}
pub(crate) fn output(&self, id: Uuid, port: Uuid) -> Option<Value> {
let at = *self.ids.get(&id)?;
let caches = self.caches.borrow();
caches[at].arguments.as_ref()?;
caches[at]
.outputs
.iter()
.find(|(output, _)| *output == port)
.map(|(_, value)| value.clone())
}
pub(crate) fn outputs(&self, id: Uuid) -> Option<HashMap<Uuid, Value>> {
let at = *self.ids.get(&id)?;
let caches = self.caches.borrow();
caches[at]
.arguments
.is_some()
.then(|| caches[at].outputs.iter().cloned().collect())
}
pub(crate) fn nodes(&self) -> &[Rc<Node>] {
&self.nodes
}
pub(crate) fn plan(&self) -> DetachedPlan {
self.lowered.borrow().plan.clone()
}
pub(crate) fn calls(&self, id: Uuid) -> Option<u64> {
let at = *self.ids.get(&id)?;
Some(self.caches.borrow()[at].calls)
}
pub(crate) fn pulls(&self, node: &Uuid) -> Option<Rc<[usize]>> {
self.lowered.borrow().pulls.get(node).cloned()
}
pub(crate) fn lower(
&self,
functions: &FunctionIndex,
attached: &[Rc<Node>],
variables: &HashMap<Uuid, VariableCell>,
node_parameters_variables: &crate::id_hash::IdMap<NodeParameterId, VariableCell>,
) -> Result<(), BehaviorTreeError> {
let names = Names::functions(functions);
let mut signatures = FunctionIndex::new();
let mut arguments = Vec::with_capacity(self.nodes.len());
let mut modules = Vec::with_capacity(self.nodes.len());
let mut rewrites = Vec::with_capacity(self.nodes.len());
let mut positions: crate::id_hash::IdMap<Uuid, crate::id_hash::IdMap<Uuid, usize>> =
crate::id_hash::IdMap::default();
let mut outputs = Vec::with_capacity(self.nodes.len());
let mut states: Vec<HashSet<usize>> = Vec::with_capacity(self.nodes.len());
let mut writers: HashMap<usize, (Option<usize>, Uuid)> = HashMap::new();
let mut claim = |cell: &VariableCell,
writer: Option<usize>,
node: Uuid|
-> Result<(), BehaviorTreeError> {
if matches!(cell, VariableCell::Stored(_)) {
return Ok(());
}
match writers.insert(cell_id(cell), (writer, node)) {
Some((previous, other))
if other != node && (previous.is_some() || writer.is_some()) =>
{
Err(inconsistent(format!(
"node {node} and node {other} write one tree-local cell, which a \
detached node writes: it has one writer"
)))
}
_ => Ok(()),
}
};
let output_cell = |node: Uuid, port: Uuid| {
node_parameters_variables
.get(&NodeParameterId {
node,
parameter: port,
})
.cloned()
};
for (at, node) in self.nodes.iter().enumerate() {
let refuse = |why: String| {
Err(inconsistent(format!(
"detached node {}: {} {why}",
node.id,
node.display(names)
)))
};
signatures.extend(expression_call_signatures(
|| format!("detached node {}", node.id),
node.arguments.values(),
functions,
)?);
if is_interpreter_function_id(node.function) {
return refuse("calls the interpreter module, which a tree does not call".into());
}
let signature = functions.get(&node.function);
let ordered = match signature {
Some(signature) => {
let positions = positions.entry(node.function).or_insert_with(|| {
signature
.function
.parameter_ordering
.iter()
.enumerate()
.map(|(at, id)| (*id, at))
.collect()
});
crate::display::ordered_by(&node.arguments, positions)
}
None => crate::display::ordered(&node.arguments, None),
};
let mut ports = vec![_RET_PARAM_ID];
if let Some(signature) = signature {
for &(parameter, argument) in &ordered {
if parameter == _RET_PARAM_ID {
continue;
}
let Some(declared) = signature.function.parameters.get(¶meter) else {
return refuse(format!(
"names the parameter {parameter}, which {} does not have",
signature.function_name
));
};
if declared.mutable {
if matches!(argument, Expression::Select { .. } | Expression::Call(_)) {
return refuse(format!(
"feeds the mutable parameter `{}` with {}: what the function \
writes back needs a literal or a variable to hold it",
declared.name,
argument.display(names)
));
}
ports.push(parameter);
}
}
signatures
.entry(node.function)
.or_insert_with(|| signature.clone());
}
let mut written = Vec::new();
let mut state = HashSet::new();
for port in ports {
if let Some(cell) = output_cell(node.id, port) {
claim(&cell, Some(at), node.id)?;
if port != _RET_PARAM_ID {
state.insert(cell_id(&cell));
}
let publish = self
.bindings
.get(&NodeParameterId {
node: node.id,
parameter: port,
})
.cloned();
written.push(Output {
port,
cell,
publish,
optional: output_optional(signature, &port),
});
}
}
let mut nested_writes = false;
for cell in call_writes(node, functions, variables, node_parameters_variables) {
nested_writes = true;
if matches!(cell, VariableCell::Stored(_)) {
return refuse(
"writes a store key back from a call in its arguments: a detached \
node writes the store only through its own outputs, after the tree \
ticks"
.into(),
);
}
claim(&cell, Some(at), node.id)?;
state.insert(cell_id(&cell));
}
states.push(state);
rewrites.push(
nested_writes
|| written
.iter()
.any(|output| output.port != _RET_PARAM_ID && output.publish.is_some()),
);
outputs.push(written);
arguments.push(
ordered
.into_iter()
.filter(|(parameter, _)| *parameter != _RET_PARAM_ID)
.map(|(parameter, argument)| {
let at = NodeParameterId {
node: node.id,
parameter,
};
Argument {
parameter,
optional: signature
.is_some_and(|f| is_optional(&f.function, ¶meter)),
source: match argument {
Expression::Call(_) | Expression::Select { .. } => {
Source::Computed(argument.clone())
}
_ => Source::Cell(
self.bindings
.get(&at)
.or_else(|| node_parameters_variables.get(&at))
.cloned(),
),
},
}
})
.collect::<Vec<_>>(),
);
modules.push(signature.map(|signature| signature.module_id));
}
for node in attached {
claim_attached(node, functions, &output_cell, &mut claim)?;
for cell in call_writes(node, functions, variables, node_parameters_variables) {
claim(&cell, None, node.id)?;
}
}
for (at, node) in self.nodes.iter().enumerate() {
for Output { port, .. } in &outputs[at] {
let Some(Expression::NodeArgument(input)) = node.arguments.get(port) else {
continue;
};
let refuse = |why: &str| {
Err(inconsistent(format!(
"detached node {}: {} binds an output to {}, {why}",
node.id,
node.display(names),
Expression::NodeArgument(input.clone()).display(names)
)))
};
let argument_of = |port: &NodeParameterId| {
self.nodes
.iter()
.chain(attached)
.find(|reader| reader.id == port.node)
.and_then(|reader| reader.arguments.get(&port.parameter))
};
let mut target = input;
let mut computed = false;
for _ in 0..=self.nodes.len() + attached.len() {
match argument_of(target) {
Some(Expression::NodeArgument(aliased)) => target = aliased,
Some(Expression::Call(_) | Expression::Select { .. }) => {
computed = true;
break;
}
_ => break,
}
}
if computed {
return refuse("an input a call or a selection computes, with no cell to feed");
}
let store_bound = [
&NodeParameterId {
node: node.id,
parameter: *port,
},
input,
target,
]
.into_iter()
.any(|bound| self.bindings.contains_key(bound));
if *port != _RET_PARAM_ID && store_bound {
return refuse(
"an input that names a store key: bind both to a tree-local variable",
);
}
}
}
for node in self.nodes.iter().chain(attached) {
let signature = functions.get(&node.function);
for (parameter, argument) in &node.arguments {
let mutable = signature
.and_then(|signature| signature.function.parameters.get(parameter))
.is_some_and(|parameter| parameter.mutable);
let writes = *parameter == _RET_PARAM_ID
|| (mutable && matches!(argument, Expression::NodeArgument(_)));
if writes {
continue;
}
let mut ports = Vec::new();
ports_named(argument, &mut ports);
for port in ports {
let Some(source) = self.ids.get(&port.node).map(|at| &self.nodes[*at]) else {
continue;
};
let Some(signature) = functions.get(&source.function) else {
continue;
};
let is_port = port.parameter == _RET_PARAM_ID
|| signature
.function
.parameters
.get(&port.parameter)
.is_some_and(|parameter| parameter.mutable);
if !is_port {
return Err(inconsistent(format!(
"{} is read, and {} has no such output: an output is its return \
value or a mutable parameter",
argument.display(names),
signature.function_name
)));
}
}
}
}
let reads_of = |node: &Node, own: Option<usize>| {
let mut cells = Vec::new();
for (parameter, argument) in &node.arguments {
if *parameter != _RET_PARAM_ID {
cells_read(argument, variables, node_parameters_variables, &mut cells);
cells.extend(
node_parameters_variables
.get(&NodeParameterId {
node: node.id,
parameter: *parameter,
})
.cloned(),
);
}
}
let mut read = Vec::new();
for cell in &cells {
let id = cell_id(cell);
let Some((Some(writer), _)) = writers.get(&id) else {
continue;
};
if Some(*writer) != own {
read.push(*writer);
} else if !states[*writer].contains(&id) {
return Err(inconsistent(format!(
"detached node {}: {} reads its own output: feedback goes through a \
store key",
node.id,
node.display(names)
)));
}
}
read.sort_unstable();
read.dedup();
Ok(read)
};
let reads: Vec<Vec<usize>> = self
.nodes
.iter()
.enumerate()
.map(|(at, node)| reads_of(node, Some(at)))
.collect::<Result<_, _>>()?;
let mut pulls = HashMap::new();
let mut tree_reads = Vec::new();
for node in attached {
let read = reads_of(node, None)?;
if !read.is_empty() {
tree_reads.extend(read.iter().copied());
pulls.insert(node.id, Rc::from(read));
}
}
let fed = self
.nodes
.iter()
.enumerate()
.flat_map(|(at, node)| {
node.arguments
.keys()
.filter(|parameter| **parameter != _RET_PARAM_ID)
.map(move |parameter| (at, node.id, *parameter))
})
.filter_map(|(at, node, parameter)| {
let np = NodeParameterId { node, parameter };
let cell = node_parameters_variables.get(&np)?;
match writers.get(&cell_id(cell)) {
Some((Some(writer), _)) if *writer != at => Some(np),
_ => None,
}
})
.collect::<crate::id_hash::IdSet<NodeParameterId>>();
for (node, arguments) in self.nodes.iter().zip(&mut arguments) {
for argument in arguments.iter_mut() {
let np = NodeParameterId {
node: node.id,
parameter: argument.parameter,
};
if fed.contains(&np) && matches!(argument.source, Source::Cell(_)) {
argument.source = Source::Cell(node_parameters_variables.get(&np).cloned());
}
}
}
let order = sorted(&reads, &self.nodes)?;
let sink = |at: usize| outputs[at].iter().any(|output| output.publish.is_some());
let closure = |starts: Vec<usize>| {
let mut reached = HashSet::new();
let mut stack = starts;
while let Some(at) = stack.pop() {
if reached.insert(at) {
stack.extend(reads[at].iter().copied());
}
}
reached
};
let observed_ids = self.observed.borrow();
let observed_at = |at: usize| observed_ids.contains(&self.nodes[at].id);
let every_tick = closure(
(0..self.nodes.len())
.filter(|at| sink(*at) || observed_at(*at))
.collect(),
);
let on_demand = closure(tree_reads);
let mut plan = DetachedPlan::default();
for at in &order {
let id = self.nodes[*at].id;
if every_tick.contains(at) {
plan.every_tick.push(id);
} else if on_demand.contains(at) {
plan.on_demand.push(id);
} else {
plan.unused.push(id);
}
}
let sinks = (0..self.nodes.len()).filter(|at| sink(*at)).collect();
let observed = order
.iter()
.copied()
.filter(|at| observed_at(*at) && !sink(*at))
.collect();
*self.lowered.borrow_mut() = Lowered {
signatures,
reads,
cells_only: arguments
.iter()
.map(|arguments: &Vec<Argument>| {
arguments
.iter()
.all(|argument| matches!(argument.source, Source::Cell(_)))
})
.collect(),
arguments,
modules,
rewrites,
outputs,
sinks,
observed,
pulls,
plan,
};
Ok(())
}
pub(crate) fn begin_tick(&self) {
self.tick.set(self.tick.get() + 1);
}
pub(crate) fn pull(
&self,
nodes: &[usize],
scope: Scope,
caller: &mut dyn CallBridge,
) -> Result<(), BehaviorTreeError> {
for at in nodes {
self.bring_up_to_date(*at, scope, caller)?;
}
Ok(())
}
pub(crate) fn tick_sinks(
&self,
scope: Scope,
caller: &mut dyn CallBridge,
) -> Result<(), BehaviorTreeError> {
let sinks = self.lowered.borrow().sinks.clone();
let mut failure = None;
for at in &sinks {
if let Err(error) = self.bring_up_to_date(*at, scope, caller) {
failure.get_or_insert(error);
}
}
{
let lowered = self.lowered.borrow();
let caches = self.caches.borrow();
for at in sinks.into_iter().filter(|at| caches[*at].failed.is_none()) {
for output in &lowered.outputs[at] {
let value = caches[at]
.outputs
.iter()
.find(|(port, _)| *port == output.port)
.map(|(_, value)| value);
if let (Some(key), Some(value)) = (&output.publish, value) {
write_back(key, value.clone(), output.optional);
}
}
}
}
let observed = self.lowered.borrow().observed.clone();
for at in observed {
if let Err(error) = self.bring_up_to_date(at, scope, caller) {
failure.get_or_insert(error);
}
}
failure.map_or(Ok(()), Err)
}
fn bring_up_to_date(
&self,
at: usize,
scope: Scope,
caller: &mut dyn CallBridge,
) -> Result<(), BehaviorTreeError> {
if let Some(outcome) = self.this_tick(at) {
return outcome;
}
let lowered = self.lowered.borrow();
let mut stack = std::mem::take(&mut *self.stack.borrow_mut());
stack.clear();
stack.push((at, 0));
let outcome = self.bring_up(&mut stack, &lowered, scope, caller);
*self.stack.borrow_mut() = stack;
outcome
}
fn bring_up(
&self,
stack: &mut Vec<(usize, usize)>,
lowered: &Lowered,
scope: Scope,
caller: &mut dyn CallBridge,
) -> Result<(), BehaviorTreeError> {
while let Some((node, next)) = stack.last_mut() {
let node = *node;
let failure = if let Some(read) = lowered.reads[node].get(*next).copied() {
*next += 1;
match self.this_tick(read) {
None => {
stack.push((read, 0));
continue;
}
Some(Ok(())) => continue,
Some(Err(failure)) => failure,
}
} else {
match self.evaluate(node, lowered, scope, caller) {
Ok(()) => {
stack.pop();
self.mark(node, None);
continue;
}
Err(failure) => failure,
}
};
let why = failure.to_string();
for (waiting, _) in stack.iter() {
self.mark(*waiting, Some(why.clone()));
}
return Err(failure);
}
Ok(())
}
fn this_tick(&self, at: usize) -> Option<Result<(), BehaviorTreeError>> {
let caches = self.caches.borrow();
let cache = &caches[at];
(cache.tick == self.tick.get()).then(|| match &cache.failed {
None => Ok(()),
Some(why) => Err(BehaviorTreeError::CallError(CallError::Generic {
message: format!(
"detached node {} failed this tick: {why}",
self.nodes[at].id
),
})),
})
}
fn mark(&self, at: usize, failed: Option<String>) {
let mut caches = self.caches.borrow_mut();
caches[at].tick = self.tick.get();
caches[at].failed = failed;
}
fn evaluate(
&self,
at: usize,
lowered: &Lowered,
scope: Scope,
caller: &mut dyn CallBridge,
) -> Result<(), BehaviorTreeError> {
let scope = Scope {
calls: &lowered.signatures,
..scope
};
let node = &self.nodes[at];
let outputs = &lowered.outputs[at];
for output in outputs {
if let (true, Some(key)) = (output.port != _RET_PARAM_ID, &output.publish) {
output.cell.set_opt(key.get());
}
}
let mut known_changed = false;
if lowered.cells_only[at] {
let unchanged = match &self.caches.borrow()[at].arguments {
Some(cached) => {
let unchanged = cached.len() == lowered.arguments[at].len()
&& lowered.arguments[at]
.iter()
.zip(cached)
.all(|(argument, cached)| {
let Source::Cell(cell) = &argument.source else {
return false;
};
holds_argument(cell.as_ref(), cached, argument.optional)
});
known_changed = !unchanged;
unchanged
}
None => false,
};
if unchanged {
if lowered.rewrites[at] {
self.rewrite(at, outputs);
}
return Ok(());
}
}
let mut values = Vec::with_capacity(lowered.arguments[at].len());
for argument in &lowered.arguments[at] {
let value = match &argument.source {
Source::Computed(expression) => {
let np = NodeParameterId {
node: node.id,
parameter: argument.parameter,
};
Some(compute_expression(scope, expression, caller, &np)?)
.filter(|value| *value != Value::Unit)
}
Source::Cell(cell) => cell.as_ref().and_then(VariableCell::get),
};
values.push(argument_value(value, argument.optional));
}
let changed = known_changed || self.caches.borrow()[at].arguments.as_ref() != Some(&values);
if !changed {
if lowered.rewrites[at] {
self.rewrite(at, outputs);
}
return Ok(());
}
let module_id = lowered.modules[at].ok_or(BehaviorTreeError::CallError(
CallError::FunctionNotFound { id: node.function },
))?;
let args: Vec<(Uuid, &Value)> = lowered.arguments[at]
.iter()
.zip(&values)
.map(|(argument, value)| (argument.parameter, value))
.collect();
self.caches.borrow_mut()[at].calls += 1;
let result = caller
.arora_call_borrowed(Some(module_id), node.function, &args)
.map_err(BehaviorTreeError::CallError)?;
let mut results: Vec<(Uuid, Value)> =
Vec::with_capacity(outputs.len().max(1 + result.mutated.len()));
if outputs.len() > 1 {
for (argument, value) in lowered.arguments[at].iter().zip(&values) {
if outputs
.iter()
.any(|output| output.port == argument.parameter)
{
results.push((argument.parameter, value.clone()));
}
}
}
results.push((_RET_PARAM_ID, result.ret));
for mutated in result.mutated {
match results.iter_mut().find(|(port, _)| *port == mutated.id) {
Some((_, value)) => *value = *mutated.value,
None => results.push((mutated.id, *mutated.value)),
}
}
for output in outputs {
if let Some((_, value)) = results.iter().find(|(port, _)| *port == output.port) {
write_back(&output.cell, value.clone(), output.optional);
}
}
let mut caches = self.caches.borrow_mut();
caches[at].arguments = Some(values);
caches[at].outputs = results;
Ok(())
}
fn rewrite(&self, at: usize, outputs: &[Output]) {
let caches = self.caches.borrow();
for output in outputs {
if let Some((_, value)) = caches[at]
.outputs
.iter()
.find(|(port, _)| *port == output.port)
{
write_back(&output.cell, value.clone(), output.optional);
}
}
}
}
struct Output {
port: Uuid,
cell: VariableCell,
publish: Option<VariableCell>,
optional: bool,
}
fn holds_argument(cell: Option<&VariableCell>, cached: &Value, optional: bool) -> bool {
let matches = |value: Option<&Value>| match value {
Some(value @ Value::Option(_)) => cached == value,
Some(value) if optional => {
matches!(cached, Value::Option(Some(inner)) if **inner == *value)
}
Some(value) => cached == value,
None if optional => matches!(cached, Value::Option(None)),
None => matches!(cached, Value::Unit),
};
match cell {
Some(cell) => cell.with(matches),
None => matches(None),
}
}
fn output_optional(signature: Option<&arora_behavior::ModuleFunction>, port: &Uuid) -> bool {
signature.is_some_and(|signature| {
if *port == _RET_PARAM_ID {
signature.function.return_ty.is_option()
} else {
is_optional(&signature.function, port)
}
})
}
fn claim_attached(
node: &Node,
functions: &FunctionIndex,
output_cell: &impl Fn(Uuid, Uuid) -> Option<VariableCell>,
claim: &mut impl FnMut(&VariableCell, Option<usize>, Uuid) -> Result<(), BehaviorTreeError>,
) -> Result<(), BehaviorTreeError> {
let mut ports = Vec::new();
if node.arguments.contains_key(&_RET_PARAM_ID) {
ports.push(_RET_PARAM_ID);
}
ports.extend(native_writes(node.function).iter().copied());
ports.extend(
node.arguments
.iter()
.filter(|(_, argument)| matches!(argument, Expression::Call(_)))
.map(|(parameter, _)| *parameter),
);
if let Some(signature) = functions.get(&node.function) {
ports.extend(
node.arguments
.keys()
.filter(|parameter| {
signature
.function
.parameters
.get(parameter)
.is_some_and(|parameter| parameter.mutable)
})
.copied(),
);
}
for port in ports {
if let Some(cell) = output_cell(node.id, port) {
claim(&cell, None, node.id)?;
}
}
Ok(())
}
pub(crate) fn native_writes(function: Uuid) -> &'static [Uuid] {
match function {
UNPACK_FUNCTION_ID => &UNPACK_OUT_PARAM_IDS,
SEQ_STAR_FUNCTION_ID => &[SEQ_STAR_CURRENT_INDEX_PARAM_ID],
RUN_STATUS_FUNCTION_ID => &[RUN_STATUS_OUT_PARAM_ID, RUN_STATUS_LATCH_PARAM_ID],
SET_STR_FUNCTION_ID => &[SET_STR_VARIABLE_PARAM_ID],
UNSET_STR_FUNCTION_ID => &[UNSET_STR_VARIABLE_PARAM_ID],
REGEX_MATCH_FUNCTION_ID => &[REGEX_MATCH_FIRST_MATCH_PARAM_ID],
_ => &[],
}
}
fn call_writes(
node: &Node,
functions: &FunctionIndex,
variables: &HashMap<Uuid, VariableCell>,
node_parameters_variables: &crate::id_hash::IdMap<NodeParameterId, VariableCell>,
) -> Vec<VariableCell> {
fn walk(
expression: &Expression,
functions: &FunctionIndex,
variables: &HashMap<Uuid, VariableCell>,
node_parameters_variables: &crate::id_hash::IdMap<NodeParameterId, VariableCell>,
found: &mut Vec<VariableCell>,
) {
match expression {
Expression::Select { source, .. } => walk(
source,
functions,
variables,
node_parameters_variables,
found,
),
Expression::Call(call) => {
let signature = stated_id(&call.function).and_then(|id| functions.get(&id));
for identifier in [&call.module, &call.function] {
walk(
identifier,
functions,
variables,
node_parameters_variables,
found,
);
}
for (parameter, argument) in &call.arguments {
let mutable = match (signature, stated_id(parameter)) {
(Some(signature), Some(id)) => signature
.function
.parameters
.get(&id)
.is_some_and(|parameter| parameter.mutable),
_ => true,
};
walk(
parameter,
functions,
variables,
node_parameters_variables,
found,
);
if mutable {
found.extend(match argument {
Expression::Variable(cell) => Some(cell.clone()),
Expression::VariableId(id) => variables.get(id).cloned(),
Expression::NodeArgument(at) => {
node_parameters_variables.get(at).cloned()
}
_ => None,
});
}
walk(
argument,
functions,
variables,
node_parameters_variables,
found,
);
}
}
_ => {}
}
}
let mut found = Vec::new();
for argument in node.arguments.values() {
walk(
argument,
functions,
variables,
node_parameters_variables,
&mut found,
);
}
found
}
fn ports_named(expression: &Expression, found: &mut Vec<NodeParameterId>) {
match expression {
Expression::NodeArgument(port) => found.push(port.clone()),
Expression::Select { source, .. } => ports_named(source, found),
Expression::Call(call) => {
ports_named(&call.module, found);
ports_named(&call.function, found);
for (parameter, argument) in &call.arguments {
ports_named(parameter, found);
ports_named(argument, found);
}
}
_ => {}
}
}
fn sorted(reads: &[Vec<usize>], nodes: &[Rc<Node>]) -> Result<Vec<usize>, BehaviorTreeError> {
let mut sorted = Vec::with_capacity(nodes.len());
let mut done = vec![false; nodes.len()];
let mut on_path = vec![false; nodes.len()];
let mut path: Vec<(usize, usize)> = Vec::new();
for start in 0..nodes.len() {
if done[start] {
continue;
}
path.push((start, 0));
on_path[start] = true;
while let Some((at, next)) = path.last_mut() {
let at = *at;
if let Some(read) = reads[at].get(*next).copied() {
*next += 1;
if done[read] {
continue;
}
if on_path[read] {
let from = path
.iter()
.position(|(on, _)| *on == read)
.expect("a node on the path is in it");
let cycle: Vec<String> = path[from..]
.iter()
.map(|(at, _)| nodes[*at].id.to_string())
.collect();
return Err(inconsistent(format!(
"the detached nodes {} read each other in a cycle: feed a value back \
through a store key",
cycle.join(", ")
)));
}
on_path[read] = true;
path.push((read, 0));
} else {
path.pop();
on_path[at] = false;
done[at] = true;
sorted.push(at);
}
}
}
Ok(sorted)
}