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}