aper_stateroom/
state_program.rs

1use crate::IntentEvent;
2use aper::{Aper, AperSync, Store, StoreHandle};
3use serde::{de::DeserializeOwned, Serialize};
4
5/// This trait can be added to a [StateMachine] which takes a [TransitionEvent] as
6/// its transition. Only state machines with this trait can be used directly with
7/// the aper client/server infrastructure.
8pub trait StateProgram: Aper<Intent = IntentEvent<Self::T>> + Send + Sync + 'static
9where
10    <Self as StateProgram>::T: Unpin + Send + Sync,
11{
12    type T: Serialize + DeserializeOwned + Clone + PartialEq;
13
14    /// A state machine may "suspend" an event which occurs at a specific time in the future.
15    /// This is useful for ensuring that the state is updated at a future time regardless of
16    /// a user-initiated state change before then. State machines that only change state as a
17    /// result of user-initiated events can ignore this method, as the default implementation
18    /// is to never suspend an event.
19    ///
20    /// This method is called by the server once after every call to `process_event`. If it
21    /// returns `None`, no event is suspended, and any previously suspended event is canceled.
22    /// If it returns `Some`, the provided event becomes the "suspended" event, replacing the
23    /// prior suspended event if there was one.
24    ///
25    /// Only one event can be suspended at a time. If a state machine wants to be triggered for
26    /// multiple events in the future, it is up to that state machine to return the
27    /// (chronologically) next event each time this method is called.
28    ///
29    /// Currently, only the state machine running on the server ever has this method called.
30    ///
31    /// Since they are not associated with a particular player, suspended events trigger
32    /// `process_event` with a `None` as the player in the [TransitionEvent].
33    fn suspended_event(&self) -> Option<IntentEvent<Self::T>> {
34        None
35    }
36
37    fn new() -> Self;
38}
39
40/// A [StateProgram] implementation that can be built from any [StateMachine]. Transitions
41/// are stripped of their metadata and passed down to the underlying state machine.
42pub struct StateMachineContainerProgram<SM>(pub SM)
43where
44    SM: Aper + Send + Sync + 'static,
45    <SM as Aper>::Intent: Send;
46
47impl<SM> AperSync for StateMachineContainerProgram<SM>
48where
49    SM: Aper + Send + Sync + 'static,
50    SM::Intent: Send,
51{
52    fn attach(store: StoreHandle) -> Self {
53        StateMachineContainerProgram(SM::attach(store))
54    }
55}
56
57impl<SM> Aper for StateMachineContainerProgram<SM>
58where
59    SM: Aper + Send + Sync + 'static,
60    <SM as Aper>::Intent: Send + Unpin + Sync + 'static,
61{
62    type Intent = IntentEvent<SM::Intent>;
63    type Error = SM::Error;
64
65    fn apply(&mut self, intent: &Self::Intent) -> Result<(), Self::Error> {
66        self.0.apply(&intent.intent)?;
67        Ok(())
68    }
69}
70
71impl<SM> StateProgram for StateMachineContainerProgram<SM>
72where
73    SM: Aper + Send + Sync + 'static,
74    <SM as Aper>::Intent: Send + Unpin + Sync + 'static,
75{
76    type T = SM::Intent;
77
78    fn new() -> Self {
79        let store = Store::default();
80        StateMachineContainerProgram(SM::attach(store.handle()))
81    }
82}