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