Skip to main content

mecha10_behavior_patterns/
subsumption.rs

1//! Subsumption architecture for priority-based control
2//!
3//! This module implements the subsumption architecture pattern where higher priority
4//! behaviors can override lower priority ones. This is particularly useful for
5//! safety-critical systems where safety behaviors must take precedence over task behaviors.
6
7use async_trait::async_trait;
8use mecha10_behavior_runtime::{BehaviorNode, BoxedBehavior, NodeStatus};
9use mecha10_core::Context;
10use serde::{Deserialize, Serialize};
11use tracing::{debug, info};
12
13/// A layer in the subsumption architecture with a priority and behavior.
14#[derive(Debug)]
15pub struct SubsumptionLayer {
16    /// Priority level (higher = more important, executes first)
17    pub priority: u8,
18    /// The behavior to execute for this layer
19    pub behavior: BoxedBehavior,
20    /// Optional name for debugging
21    pub name: Option<String>,
22}
23
24impl SubsumptionLayer {
25    mecha10_core::new_with_defaults!(
26        /// Create a new subsumption layer.
27        pub fn new(priority: u8, behavior: BoxedBehavior) -> Self {
28            name: None
29        }
30    );
31
32    mecha10_core::builder_setter!(
33        /// Set the name of this layer.
34        into_some, with_name, name, impl Into<String>
35    );
36}
37
38/// Subsumption node for priority-based layered control.
39///
40/// This implements the subsumption architecture where behaviors are organized in layers
41/// with different priorities. Higher priority layers can override lower priority ones.
42///
43/// # Behavior
44///
45/// - Layers are executed in order of priority (highest first)
46/// - If a layer returns `Success` or `Running`, execution stops and that status is returned
47/// - If a layer returns `Failure`, the next lower priority layer is tried
48/// - If all layers fail, the node returns `Failure`
49///
50/// # Use Cases
51///
52/// - **Safety-critical systems**: Safety behaviors override task behaviors
53/// - **Reactive control**: Obstacle avoidance overrides navigation
54/// - **Hierarchical control**: High-level planning with low-level reactive layers
55///
56/// # Example
57///
58/// ```rust
59/// use mecha10_behavior_patterns::prelude::*;
60///
61/// # #[derive(Debug)]
62/// # struct EmergencyStopBehavior;
63/// # #[async_trait]
64/// # impl BehaviorNode for EmergencyStopBehavior {
65/// #     async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
66/// #         Ok(NodeStatus::Failure) // No emergency
67/// #     }
68/// # }
69/// # #[derive(Debug)]
70/// # struct AvoidObstacleBehavior;
71/// # #[async_trait]
72/// # impl BehaviorNode for AvoidObstacleBehavior {
73/// #     async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
74/// #         Ok(NodeStatus::Failure) // No obstacle
75/// #     }
76/// # }
77/// # #[derive(Debug)]
78/// # struct NavigateBehavior;
79/// # #[async_trait]
80/// # impl BehaviorNode for NavigateBehavior {
81/// #     async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
82/// #         Ok(NodeStatus::Running)
83/// #     }
84/// # }
85///
86/// let subsumption = SubsumptionNode::new()
87///     .add_layer(10, Box::new(EmergencyStopBehavior))    // Highest priority
88///     .add_layer(5, Box::new(AvoidObstacleBehavior))     // Medium priority
89///     .add_layer(1, Box::new(NavigateBehavior));         // Lowest priority
90/// ```
91#[derive(Debug)]
92pub struct SubsumptionNode {
93    pub layers: Vec<SubsumptionLayer>,
94}
95
96impl SubsumptionNode {
97    /// Create a new empty subsumption node.
98    pub fn new() -> Self {
99        Self { layers: Vec::new() }
100    }
101
102    /// Add a layer with the given priority and behavior.
103    ///
104    /// Layers are automatically sorted by priority (highest first).
105    pub fn add_layer(mut self, priority: u8, behavior: BoxedBehavior) -> Self {
106        self.layers.push(SubsumptionLayer::new(priority, behavior));
107        self.layers.sort_by_key(|b| std::cmp::Reverse(b.priority));
108        self
109    }
110}
111
112impl Default for SubsumptionNode {
113    fn default() -> Self {
114        Self::new()
115    }
116}
117
118#[async_trait]
119impl BehaviorNode for SubsumptionNode {
120    async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
121        if self.layers.is_empty() {
122            return Ok(NodeStatus::Failure);
123        }
124
125        for layer in &mut self.layers {
126            let layer_name = layer.name.as_deref().unwrap_or("unnamed");
127
128            debug!(
129                "Subsumption: trying layer '{}' (priority {})",
130                layer_name, layer.priority
131            );
132
133            let status = layer.behavior.tick(ctx).await?;
134
135            match status {
136                NodeStatus::Success | NodeStatus::Running => {
137                    info!(
138                        "Subsumption: layer '{}' (priority {}) taking control with status {}",
139                        layer_name, layer.priority, status
140                    );
141                    return Ok(status);
142                }
143                NodeStatus::Failure => {
144                    debug!("Subsumption: layer '{}' failed, trying next layer", layer_name);
145                    continue;
146                }
147            }
148        }
149
150        debug!("Subsumption: all layers failed");
151        Ok(NodeStatus::Failure)
152    }
153
154    fn name(&self) -> &str {
155        "subsumption"
156    }
157
158    mecha10_behavior_runtime::children_mut_via_wrapped_behavior!(layers, layer);
159}
160
161/// Configuration for a subsumption layer (for JSON deserialization).
162#[allow(dead_code)]
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct SubsumptionLayerConfig {
165    /// Priority level (higher = more important)
166    pub priority: u8,
167    /// Node type to instantiate
168    pub node: String,
169    /// Optional name for this layer
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub name: Option<String>,
172    /// Configuration for the node
173    #[serde(default)]
174    pub config: serde_json::Value,
175}