use crate::integration::error::Result as IntegrationResult;
use crate::{IntoEvent, Machine};
use std::sync::Arc;
use tokio::sync::Mutex;
#[derive(Clone)]
pub struct SharedMachineRef {
machine: Arc<Mutex<Machine>>,
name: String,
}
impl SharedMachineRef {
pub fn new(machine: Machine) -> Self {
let name = machine.name.clone();
Self {
machine: Arc::new(Mutex::new(machine)),
name,
}
}
pub async fn send_event<E: IntoEvent + Send>(&self, event: E) -> IntegrationResult<bool> {
let event = event.into_event();
let mut machine = self.machine.lock().await;
Ok(machine.send(event).await?)
}
pub async fn is_in_state(&self, state_id: &str) -> IntegrationResult<bool> {
let machine = self.machine.lock().await;
Ok(machine.is_in(&state_id.to_string()))
}
pub fn name(&self) -> &str {
&self.name
}
}
#[async_trait::async_trait]
pub trait EventForwarder {
async fn forward_event<E: IntoEvent + Send>(&self, event: E) -> IntegrationResult<bool>;
}
#[async_trait::async_trait]
impl EventForwarder for SharedMachineRef {
async fn forward_event<E: IntoEvent + Send>(&self, event: E) -> IntegrationResult<bool> {
self.send_event(event).await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::StateError;
use crate::{
Action, Context, Event, Machine, MachineBuilder, State, Transition, TransitionType,
};
use futures::FutureExt;
use std::sync::Arc;
#[tokio::test]
async fn test_event_forwarding() -> IntegrationResult<()> {
let child = create_child_machine().await;
let shared_child = Arc::new(Mutex::new(SharedMachineRef::new(child)));
let child_for_parent = shared_child.clone();
let parent = create_parent_machine(child_for_parent).await?; let shared_parent = SharedMachineRef::new(parent);
println!("Debug: Sending ACTIVATE event directly to child");
let direct_result = {
let child_guard = shared_child.lock().await;
child_guard.send_event(Event::from("ACTIVATE")).await
};
println!("Debug: Direct child event result: {:?}", direct_result);
let result = shared_parent.send_event(Event::from("PARENT_EVENT")).await;
println!("Debug: Parent event result: {:?}", result);
let is_activated = {
let child_guard = shared_child.lock().await;
child_guard.is_in_state("activated").await?
};
println!("Debug: Child is in activated state: {:?}", is_activated);
assert!(is_activated);
Ok(())
}
async fn create_child_machine() -> Machine<Context, Event, String> {
let initial = State::new("initial".to_string());
let activated = State::new_final("activated".to_string());
let activate = Transition::new(
"initial".to_string(),
Some("activated".to_string()),
Some(Event::from("ACTIVATE")),
None,
vec![],
TransitionType::External,
);
MachineBuilder::new("childMachine".to_string(), "initial".to_string())
.state(initial)
.state(activated)
.transition(activate)
.build()
.await
.expect("Child machine build failed")
}
async fn create_parent_machine(
child_ref: Arc<tokio::sync::Mutex<SharedMachineRef>>,
) -> IntegrationResult<Machine<Context, Event, String>> {
let initial = State::new("initial".to_string());
let processing = State::new("processing".to_string());
let done = State::new_final("done".to_string());
let forward_action: Action<Context, Event> = Action::from_fn(
move |_ctx: Arc<tokio::sync::RwLock<Context>>, evt: &Event| {
let child_clone: Arc<tokio::sync::Mutex<SharedMachineRef>> = Arc::clone(&child_ref);
let event_to_forward = evt.clone();
async move {
let child_guard = child_clone.lock().await;
match child_guard.send_event(event_to_forward).await {
Ok(_) => Ok(()),
Err(e) => Err(StateError::ActionFailed(format!("Forward failed: {:?}", e))),
}
}
.boxed()
},
);
let process = Transition::new(
"initial".to_string(),
Some("processing".to_string()),
Some(Event::from("PROCESS")),
None,
vec![forward_action],
TransitionType::External,
);
let complete = Transition::new(
"processing".to_string(),
Some("done".to_string()),
Some(Event::from("CHILD_DONE")),
None,
vec![],
TransitionType::External,
);
Ok(
MachineBuilder::new("parentMachine".to_string(), "initial".to_string())
.state(initial)
.state(processing)
.state(done)
.transition(process)
.transition(complete)
.build()
.await?,
)
}
}