use async_trait::async_trait;
use mecha10_behavior_runtime::{BehaviorNode, BoxedBehavior, NodeStatus};
use mecha10_core::Context;
use serde::{Deserialize, Serialize};
use tracing::{debug, info};
#[derive(Debug)]
pub struct SubsumptionLayer {
pub priority: u8,
pub behavior: BoxedBehavior,
pub name: Option<String>,
}
impl SubsumptionLayer {
pub fn new(priority: u8, behavior: BoxedBehavior) -> Self {
Self {
priority,
behavior,
name: None,
}
}
mecha10_core::builder_setter!(
into_some, with_name, name, impl Into<String>
);
}
#[derive(Debug)]
pub struct SubsumptionNode {
pub layers: Vec<SubsumptionLayer>,
}
impl SubsumptionNode {
pub fn new() -> Self {
Self { layers: Vec::new() }
}
pub fn add_layer(mut self, priority: u8, behavior: BoxedBehavior) -> Self {
self.layers.push(SubsumptionLayer::new(priority, behavior));
self.layers.sort_by_key(|b| std::cmp::Reverse(b.priority));
self
}
}
impl Default for SubsumptionNode {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl BehaviorNode for SubsumptionNode {
async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
if self.layers.is_empty() {
return Ok(NodeStatus::Failure);
}
for layer in &mut self.layers {
let layer_name = layer.name.as_deref().unwrap_or("unnamed");
debug!(
"Subsumption: trying layer '{}' (priority {})",
layer_name, layer.priority
);
let status = layer.behavior.tick(ctx).await?;
match status {
NodeStatus::Success | NodeStatus::Running => {
info!(
"Subsumption: layer '{}' (priority {}) taking control with status {}",
layer_name, layer.priority, status
);
return Ok(status);
}
NodeStatus::Failure => {
debug!("Subsumption: layer '{}' failed, trying next layer", layer_name);
continue;
}
}
}
debug!("Subsumption: all layers failed");
Ok(NodeStatus::Failure)
}
fn name(&self) -> &str {
"subsumption"
}
fn children_mut(&mut self) -> Vec<&mut dyn BehaviorNode> {
self.layers
.iter_mut()
.map(|layer| layer.behavior.as_mut() as &mut dyn BehaviorNode)
.collect()
}
}
#[allow(dead_code)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubsumptionLayerConfig {
pub priority: u8,
pub node: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default)]
pub config: serde_json::Value,
}