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,
}
}
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
}
#[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(|a, b| b.priority.cmp(&a.priority));
self
}
pub fn add_named_layer(mut self, priority: u8, name: impl Into<String>, behavior: BoxedBehavior) -> Self {
self.layers
.push(SubsumptionLayer::new(priority, behavior).with_name(name));
self.layers.sort_by(|a, b| b.priority.cmp(&a.priority));
self
}
pub fn layer_count(&self) -> usize {
self.layers.len()
}
}
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)
}
async fn reset(&mut self) -> anyhow::Result<()> {
for layer in &mut self.layers {
layer.behavior.reset().await?;
}
Ok(())
}
async fn on_init(&mut self, ctx: &Context) -> anyhow::Result<()> {
for layer in &mut self.layers {
layer.behavior.on_init(ctx).await?;
}
Ok(())
}
async fn on_terminate(&mut self, ctx: &Context) -> anyhow::Result<()> {
for layer in &mut self.layers {
layer.behavior.on_terminate(ctx).await?;
}
Ok(())
}
fn name(&self) -> &str {
"subsumption"
}
}
#[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,
}