sein 0.1.2

State machine experiment
Documentation
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use slotmap::{new_key_type, SlotMap};
use std::collections::HashMap;
use std::any::Any;

new_key_type! {
    struct NodeKey;
}

// Simplified control flow without exposing NodeKey to user code
pub enum EngineControl {
    Continue,
    JumpToNode(String),  // Use string identifiers instead of NodeKey
    AddNode(String, Box<dyn AnyNode>),  // (node_name, node)
    End,
}

// Node trait doesn't need to be generic over context type anymore
// Added Send bound to the Any trait to ensure thread safety
#[async_trait]
pub trait AnyNode: Send + Sync {
    async fn enter(&self, ctx: &mut (dyn Any + Send)) -> Result<EngineControl>;
}

// Type-safe wrapper for node implementation with specific context
#[async_trait]
pub trait NodeTrait<C: Send>: Send + Sync {
    async fn enter(&self, ctx: &mut C) -> Result<EngineControl>;
}

// Make NodeWrapper public and add a constructor
pub struct NodeWrapper<N, C> {
    node: N,
    _phantom: std::marker::PhantomData<C>,
}

impl<N, C> NodeWrapper<N, C> {
    pub fn new(node: N) -> Self {
        Self {
            node,
            _phantom: std::marker::PhantomData,
        }
    }
}

#[async_trait]
impl<N, C> AnyNode for NodeWrapper<N, C> 
where
    N: NodeTrait<C> + Send + Sync,
    C: Send + Sync + 'static + Any,
{
    async fn enter(&self, ctx: &mut (dyn Any + Send)) -> Result<EngineControl> {
        let ctx = ctx.downcast_mut::<C>().ok_or_else(|| {
            anyhow!("Context type mismatch in node execution")
        })?;
        self.node.enter(ctx).await
    }
}

// Engine now manages the mapping between string IDs and nodes
pub struct Engine<C: Send + Sync> {
    nodes: SlotMap<NodeKey, Box<dyn AnyNode>>,
    node_names: HashMap<String, NodeKey>,
    current: Option<NodeKey>,
    pub ctx: C,
}

impl<C: Send + Sync + 'static> Engine<C> {
    pub fn new(ctx: C) -> Self {
        Self {
            nodes: SlotMap::with_key(),
            node_names: HashMap::new(),
            current: None,
            ctx,
        }
    }

    // Add a node with a name
    pub fn add_node<N>(&mut self, name: &str, node: N) -> Result<()>
    where
        N: NodeTrait<C> + 'static + Send + Sync,
    {
        if self.node_names.contains_key(name) {
            return Err(anyhow!("Node with name '{}' already exists", name));
        }
        
        let wrapper = NodeWrapper {
            node,
            _phantom: std::marker::PhantomData,
        };
        
        let key = self.nodes.insert(Box::new(wrapper));
        self.node_names.insert(name.to_string(), key);
        Ok(())
    }

    // Set the starting node by name
    pub fn set_start_node(&mut self, name: &str) -> Result<()> {
        let key = self.node_names.get(name)
            .ok_or_else(|| anyhow!("Node '{}' not found", name))?;
        self.current = Some(*key);
        Ok(())
    }

    pub async fn run(&mut self) -> Result<()> {
        while let Some(curr_key) = self.current {
            let node = self.nodes.get(curr_key)
                .ok_or_else(|| anyhow!("Node key not found in engine"))?;

            let ctrl = node.enter(&mut self.ctx as &mut (dyn Any + Send)).await?;

            match ctrl {
                EngineControl::Continue => {
                    // Continue with the same node
                }
                EngineControl::JumpToNode(target_name) => {
                    let target_key = self.node_names.get(&target_name)
                        .ok_or_else(|| anyhow!("Target node '{}' not found", target_name))?;
                    self.current = Some(*target_key);
                }
                EngineControl::AddNode(name, new_node) => {
                    if self.node_names.contains_key(&name) {
                        return Err(anyhow!("Node with name '{}' already exists", name));
                    }
                    let key = self.nodes.insert(new_node);
                    self.node_names.insert(name, key);
                }
                EngineControl::End => {
                    self.current = None;
                }
            }
        }
        Ok(())
    }
}