[TSM.ID].[11031972] PXE : 10 Template/Kosong -> REAL Implementation (3Z Complete)
This commit is contained in:
@@ -1,104 +1,112 @@
|
||||
//! [TSM.ID].[11031972] — Platform X Ecosystem
|
||||
//! xcu-state-machine — Hierarchical state machine with hot-reload
|
||||
#![deny(warnings)]
|
||||
|
||||
//! [TSM.ID].[11031972] -- Platform X Ecosystem
|
||||
//! xcu-state-machine -- Hierarchical State Machine with hot-reload
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum XcuError {
|
||||
InitFailed(String),
|
||||
InvalidConfig(String),
|
||||
OperationFailed(String),
|
||||
ResourceExhausted,
|
||||
NotFound(String),
|
||||
Timeout,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for XcuError {
|
||||
pub enum SmError { InvalidTransition(String), StateNotFound(String), GuardFailed(String) }
|
||||
impl std::fmt::Display for SmError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::InitFailed(e) => write!(f, "Init failed: {e}"),
|
||||
Self::InvalidConfig(e) => write!(f, "Invalid config: {e}"),
|
||||
Self::OperationFailed(e) => write!(f, "Operation failed: {e}"),
|
||||
Self::ResourceExhausted => write!(f, "Resource exhausted"),
|
||||
Self::NotFound(e) => write!(f, "Not found: {e}"),
|
||||
Self::Timeout => write!(f, "Operation timed out"),
|
||||
match self { Self::InvalidTransition(e) => write!(f, "Invalid: {e}"), Self::StateNotFound(e) => write!(f, "State: {e}"), Self::GuardFailed(e) => write!(f, "Guard: {e}") }
|
||||
}
|
||||
}
|
||||
impl std::error::Error for SmError {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct State { pub name: String, pub parent: Option<String>, pub is_final: bool }
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Transition { pub from: String, pub to: String, pub event: String, pub guard: Option<String> }
|
||||
|
||||
pub struct StateMachine {
|
||||
states: HashMap<String, State>,
|
||||
transitions: Vec<Transition>,
|
||||
current: String,
|
||||
history: Vec<(String, String, String)>, // (from, event, to)
|
||||
guards: HashMap<String, Box<dyn Fn() -> bool + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl StateMachine {
|
||||
pub fn new(initial: &str) -> Self {
|
||||
let mut states = HashMap::new();
|
||||
states.insert(initial.into(), State { name: initial.into(), parent: None, is_final: false });
|
||||
Self { states, transitions: Vec::new(), current: initial.into(), history: Vec::new(), guards: HashMap::new() }
|
||||
}
|
||||
|
||||
pub fn add_state(&mut self, state: State) { self.states.insert(state.name.clone(), state); }
|
||||
pub fn add_transition(&mut self, t: Transition) { self.transitions.push(t); }
|
||||
pub fn add_guard<F>(&mut self, name: &str, f: F) where F: Fn() -> bool + Send + Sync + 'static { self.guards.insert(name.into(), Box::new(f)); }
|
||||
|
||||
pub fn fire(&mut self, event: &str) -> Result<&str, SmError> {
|
||||
let matching: Vec<&Transition> = self.transitions.iter()
|
||||
.filter(|t| t.from == self.current && t.event == event).collect();
|
||||
if matching.is_empty() {
|
||||
return Err(SmError::InvalidTransition(format!("No transition from '{}' on '{}'", self.current, event)));
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::error::Error for XcuError {}
|
||||
pub type Result<T> = std::result::Result<T, XcuError>;
|
||||
|
||||
pub struct Config {
|
||||
pub params: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new() -> Self { Self { params: HashMap::new() } }
|
||||
pub fn set(&mut self, key: &str, val: &str) -> &mut Self {
|
||||
self.params.insert(key.to_string(), val.to_string()); self
|
||||
}
|
||||
pub fn get(&self, key: &str) -> Result<&str> {
|
||||
self.params.get(key).map(|s| s.as_str()).ok_or_else(|| XcuError::NotFound(key.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self { Self::new() }
|
||||
}
|
||||
|
||||
pub struct Engine {
|
||||
config: Config,
|
||||
state: Arc<Mutex<EngineState>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum EngineState {
|
||||
Idle,
|
||||
Running,
|
||||
Paused,
|
||||
ShuttingDown,
|
||||
Stopped,
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
pub fn new(config: Config) -> Result<Self> {
|
||||
Ok(Self { config, state: Arc::new(Mutex::new(EngineState::Idle)) })
|
||||
for t in &matching {
|
||||
if let Some(ref guard_name) = t.guard {
|
||||
if let Some(guard_fn) = self.guards.get(guard_name) {
|
||||
if !guard_fn() { continue; }
|
||||
} else { return Err(SmError::GuardFailed(format!("Guard '{guard_name}' not found"))); }
|
||||
}
|
||||
let from = self.current.clone();
|
||||
self.current = t.to.clone();
|
||||
self.history.push((from, event.into(), t.to.clone()));
|
||||
return Ok(&self.current);
|
||||
}
|
||||
Err(SmError::GuardFailed(format!("All guards failed for '{event}'")))
|
||||
}
|
||||
|
||||
pub fn start(&self) -> Result<()> {
|
||||
let mut s = self.state.lock().map_err(|e| XcuError::OperationFailed(e.to_string()))?;
|
||||
*s = EngineState::Running;
|
||||
pub fn current(&self) -> &str { &self.current }
|
||||
pub fn is_final(&self) -> bool { self.states.get(&self.current).map(|s| s.is_final).unwrap_or(false) }
|
||||
pub fn history(&self) -> &[(String, String, String)] { &self.history }
|
||||
|
||||
/// Serialize state for hot-reload
|
||||
pub fn snapshot(&self) -> (String, Vec<(String, String, String)>) {
|
||||
(self.current.clone(), self.history.clone())
|
||||
}
|
||||
/// Restore from snapshot
|
||||
pub fn restore(&mut self, current: &str, history: Vec<(String, String, String)>) -> Result<(), SmError> {
|
||||
if !self.states.contains_key(current) { return Err(SmError::StateNotFound(current.into())); }
|
||||
self.current = current.into();
|
||||
self.history = history;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stop(&self) -> Result<()> {
|
||||
let mut s = self.state.lock().map_err(|e| XcuError::OperationFailed(e.to_string()))?;
|
||||
*s = EngineState::ShuttingDown;
|
||||
// graceful shutdown logic
|
||||
*s = EngineState::Stopped;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn state(&self) -> Result<EngineState> {
|
||||
let s = self.state.lock().map_err(|e| XcuError::OperationFailed(e.to_string()))?;
|
||||
Ok(s.clone())
|
||||
}
|
||||
|
||||
pub fn config(&self) -> &Config { &self.config }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
fn call_sm() -> StateMachine {
|
||||
let mut sm = StateMachine::new("idle");
|
||||
sm.add_state(State { name: "ringing".into(), parent: None, is_final: false });
|
||||
sm.add_state(State { name: "connected".into(), parent: None, is_final: false });
|
||||
sm.add_state(State { name: "ended".into(), parent: None, is_final: true });
|
||||
sm.add_transition(Transition { from: "idle".into(), to: "ringing".into(), event: "call".into(), guard: None });
|
||||
sm.add_transition(Transition { from: "ringing".into(), to: "connected".into(), event: "answer".into(), guard: None });
|
||||
sm.add_transition(Transition { from: "connected".into(), to: "ended".into(), event: "hangup".into(), guard: None });
|
||||
sm.add_transition(Transition { from: "ringing".into(), to: "ended".into(), event: "reject".into(), guard: None });
|
||||
sm
|
||||
}
|
||||
#[test]
|
||||
fn test_engine_lifecycle() {
|
||||
let engine = Engine::new(Config::new()).unwrap();
|
||||
assert_eq!(engine.state().unwrap(), EngineState::Idle);
|
||||
engine.start().unwrap();
|
||||
assert_eq!(engine.state().unwrap(), EngineState::Running);
|
||||
engine.stop().unwrap();
|
||||
assert_eq!(engine.state().unwrap(), EngineState::Stopped);
|
||||
fn test_transitions() {
|
||||
let mut sm = call_sm();
|
||||
sm.fire("call").unwrap();
|
||||
assert_eq!(sm.current(), "ringing");
|
||||
sm.fire("answer").unwrap();
|
||||
assert_eq!(sm.current(), "connected");
|
||||
sm.fire("hangup").unwrap();
|
||||
assert!(sm.is_final());
|
||||
}
|
||||
#[test]
|
||||
fn test_invalid() { let mut sm = call_sm(); assert!(sm.fire("hangup").is_err()); }
|
||||
#[test]
|
||||
fn test_snapshot_restore() {
|
||||
let mut sm = call_sm();
|
||||
sm.fire("call").unwrap();
|
||||
let (state, hist) = sm.snapshot();
|
||||
let mut sm2 = call_sm();
|
||||
sm2.restore(&state, hist).unwrap();
|
||||
assert_eq!(sm2.current(), "ringing");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user