Skip to main content

magicstatemachines/shared/
guard.rs

1use super::{SharedStateError, SharedStorageView, SharedValue, WrongStateError};
2use crate::{
3    InnerInference, MayTransition, SMut, SRef, State, StateMachineImpl, StateStorage, StateTrait,
4    StateUnionRuntime, StateUnionState, Transition, TransitionCallsite,
5    state_trait::{self, ErasedState},
6};
7use core::marker::PhantomData;
8use core::ops::{Deref, DerefMut};
9
10/// Immutable shared-state read guard used as [`State`] storage.
11///
12/// This is the inner value for [`StorageStateRef`]. Public shared immutable
13/// borrows return `State<StorageStateRef<...>, T, S>`, not this type directly.
14/// The storage implements [`SRef`] but deliberately does not implement
15/// [`SMut`], so read-only arbitrary-self methods can run while generated
16/// transition calls cannot complete.
17///
18/// ```ignore
19/// let connected = shared.borrow::<Connected>()?;
20/// assert_eq!(connected.endpoint(), "localhost:8080"); // `self: &State<impl SRef, ...>`
21///
22/// // Union borrows work too; the runtime marker is checked against the union.
23/// let online = shared.borrow::<Online>()?;
24/// ```
25pub struct StateRef<G, T, S> {
26    guard: G,
27    marker: PhantomData<fn() -> (T, S)>,
28}
29
30impl<G, T, S> StateRef<G, T, S>
31where
32    G: Deref<Target = SharedValue<T>>,
33    S: SharedBorrowState,
34{
35    pub(super) fn from_guard<StorageError>(
36        guard: G,
37    ) -> Result<Self, SharedStateError<StorageError>> {
38        S::ensure_state(&guard.state)?;
39        Ok(Self {
40            guard,
41            marker: PhantomData,
42        })
43    }
44}
45
46impl<G, T, S> Deref for StateRef<G, T, S>
47where
48    G: Deref<Target = SharedValue<T>>,
49{
50    type Target = T;
51
52    fn deref(&self) -> &Self::Target {
53        &self.guard.value
54    }
55}
56
57/// Generic [`State`] backend for an immutable shared-state guard.
58///
59/// This storage can expose `&T` through [`SRef`], but it cannot expose `&mut T`
60/// and therefore does not implement [`SMut`]. A `State<StorageStateRef<...>>`
61/// is useful for read-only methods such as
62/// `fn endpoint(self: &State<impl SRef, Self, impl InOnline>) -> &str`, but it
63/// cannot be transitioned by the generated [`transition!`](macro@crate::transition)
64/// macro.
65pub struct StorageStateRef<'a, Backend>(PhantomData<&'a Backend>);
66
67impl<'a, Backend> StateStorage for StorageStateRef<'a, Backend>
68where
69    Backend: SharedStorageView + 'a,
70{
71    type Inference = InnerInference;
72
73    type Inner<T, S>
74        = StateRef<Backend::ReadGuard<'a, T>, T, S>
75    where
76        T: StateMachineImpl;
77    type Machine<T>
78        = T
79    where
80        T: StateMachineImpl;
81
82    fn retag<T, From, To>(inner: Self::Inner<T, From>) -> Self::Inner<T, To>
83    where
84        T: StateMachineImpl,
85    {
86        StateRef {
87            guard: inner.guard,
88            marker: PhantomData,
89        }
90    }
91
92    fn inferred_state<T, S>(inner: &Self::Inner<T, S>) -> ErasedState
93    where
94        T: StateMachineImpl,
95        S: StateTrait,
96    {
97        state_trait::clone_erased(&inner.guard.state)
98    }
99}
100
101impl<'a, Backend> SRef for StorageStateRef<'a, Backend>
102where
103    Backend: SharedStorageView + 'a,
104{
105    fn s_ref<T, S>(inner: &Self::Inner<T, S>) -> &T
106    where
107        T: StateMachineImpl,
108    {
109        inner
110    }
111}
112
113/// Mutable typed view into shared state.
114///
115/// Transitions performed through this guard update an internal pending state.
116/// When the guard is dropped, that pending state is committed back to the
117/// shared container.
118///
119/// The returned guard is used through the generic [`State`] alias
120/// [`SMutView`](crate::SMutView), so implementation methods can keep the same
121/// `State<S, Self, Current>` receiver shape they use for owned values:
122///
123/// ```ignore
124/// {
125///     let connected = shared.borrow_mut::<Connected>()?;
126///     let authenticated = magicstatemachines::transition!(
127///         connected,
128///         "alice".to_owned(),
129///     );
130///     drop(authenticated); // shared state is now `Authenticated`
131/// }
132/// ```
133///
134/// The commit happens when the final guard state is dropped. Constructing a
135/// transition call object is not a commit by itself.
136pub struct StateMut<G, T, S>
137where
138    G: DerefMut<Target = SharedValue<T>>,
139{
140    guard: Option<G>,
141    pending: ErasedState,
142    marker: PhantomData<fn() -> (T, S)>,
143}
144
145/// Generic [`State`] backend for a mutable shared-state guard.
146pub struct StorageStateMut<'a, Backend>(PhantomData<&'a Backend>);
147
148impl<'a, Backend> StateStorage for StorageStateMut<'a, Backend>
149where
150    Backend: SharedStorageView + 'a,
151{
152    type Inference = InnerInference;
153
154    type Inner<T, S>
155        = StateMut<Backend::WriteGuard<'a, T>, T, S>
156    where
157        T: StateMachineImpl;
158    type Machine<T>
159        = T
160    where
161        T: StateMachineImpl;
162
163    fn retag<T, From, To>(mut inner: Self::Inner<T, From>) -> Self::Inner<T, To>
164    where
165        T: StateMachineImpl,
166    {
167        StateMut {
168            guard: inner.guard.take(),
169            pending: state_trait::clone_erased(&inner.pending),
170            marker: PhantomData,
171        }
172    }
173
174    fn inferred_state<T, S>(inner: &Self::Inner<T, S>) -> ErasedState
175    where
176        T: StateMachineImpl,
177        S: StateTrait,
178    {
179        state_trait::clone_erased(&inner.pending)
180    }
181}
182
183impl<'a, Backend> MayTransition for StorageStateMut<'a, Backend>
184where
185    Backend: SharedStorageView + 'a,
186{
187    fn complete_transition<T, From, To, Args>(
188        mut state: State<Self, T, From>,
189        _args: Args,
190        _callsite: TransitionCallsite,
191    ) -> State<Self, T, To>
192    where
193        T: StateMachineImpl,
194        From: StateTrait,
195        To: crate::ConcreteStateTrait,
196        T::Standin: Transition<From, To>,
197        <T::Standin as Transition<From, To>>::F: crate::TransitionSignature<Args>,
198    {
199        State {
200            inner: StateMut {
201                guard: state.inner.guard.take(),
202                pending: state_trait::erased_state::<To>(),
203                marker: PhantomData,
204            },
205            marker: PhantomData,
206        }
207    }
208
209    fn complete_transition_after_effect<T, From, To>(
210        mut state: State<Self, T, From>,
211        _callsite: TransitionCallsite,
212    ) -> State<Self, T, To>
213    where
214        T: StateMachineImpl,
215        From: StateTrait,
216        To: crate::ConcreteStateTrait,
217    {
218        State {
219            inner: StateMut {
220                guard: state.inner.guard.take(),
221                pending: state_trait::erased_state::<To>(),
222                marker: PhantomData,
223            },
224            marker: PhantomData,
225        }
226    }
227}
228
229impl<'a, Backend> SRef for StorageStateMut<'a, Backend>
230where
231    Backend: SharedStorageView + 'a,
232{
233    fn s_ref<T, S>(inner: &Self::Inner<T, S>) -> &T
234    where
235        T: StateMachineImpl,
236    {
237        inner
238    }
239}
240
241impl<'a, Backend> SMut for StorageStateMut<'a, Backend>
242where
243    Backend: SharedStorageView + 'a,
244{
245    fn s_mut<T, S>(inner: &mut Self::Inner<T, S>) -> &mut T
246    where
247        T: StateMachineImpl,
248    {
249        inner
250    }
251}
252
253impl<G, T, S> StateMut<G, T, S>
254where
255    G: DerefMut<Target = SharedValue<T>>,
256    S: SharedBorrowState,
257{
258    pub(super) fn from_guard<StorageError>(
259        guard: G,
260    ) -> Result<Self, SharedStateError<StorageError>> {
261        S::ensure_state(&guard.state)?;
262        let pending = S::initial_pending(&guard.state);
263        Ok(Self {
264            guard: Some(guard),
265            pending,
266            marker: PhantomData,
267        })
268    }
269}
270
271/// Creates a callable transition for a mutable shared-state guard.
272///
273/// This is the guarded-state counterpart to [`crate::transition()`].
274#[must_use]
275pub fn transition_mut<G, T, S, Next>(
276    state: StateMut<G, T, S>,
277    _token: T::TransitionToken,
278) -> StateMutTransitionCall<G, T, S, Next>
279where
280    G: DerefMut<Target = SharedValue<T>>,
281    T: StateMachineImpl,
282    T::Standin: Transition<S, Next>,
283{
284    StateMutTransitionCall {
285        state,
286        to: PhantomData,
287    }
288}
289
290impl<G, T, S> Deref for StateMut<G, T, S>
291where
292    G: DerefMut<Target = SharedValue<T>>,
293{
294    type Target = T;
295
296    fn deref(&self) -> &Self::Target {
297        &self.guard.as_ref().expect("guard is present").value
298    }
299}
300
301impl<G, T, S> DerefMut for StateMut<G, T, S>
302where
303    G: DerefMut<Target = SharedValue<T>>,
304{
305    fn deref_mut(&mut self) -> &mut Self::Target {
306        &mut self.guard.as_mut().expect("guard is present").value
307    }
308}
309
310impl<G, T, S> Drop for StateMut<G, T, S>
311where
312    G: DerefMut<Target = SharedValue<T>>,
313{
314    fn drop(&mut self) {
315        if let Some(guard) = self.guard.as_mut() {
316            guard.state = state_trait::clone_erased(&self.pending);
317        }
318    }
319}
320
321/// Low-level transition call object for [`StateMut`].
322#[doc(hidden)]
323pub struct StateMutTransitionCall<G, T, From, To>
324where
325    G: DerefMut<Target = SharedValue<T>>,
326{
327    state: StateMut<G, T, From>,
328    to: PhantomData<fn() -> To>,
329}
330
331impl<G, T, From, To> StateMutTransitionCall<G, T, From, To>
332where
333    G: DerefMut<Target = SharedValue<T>>,
334{
335    pub fn call<Args>(mut self, _args: Args) -> StateMut<G, T, To>
336    where
337        T: StateMachineImpl,
338        T::Standin: Transition<From, To>,
339        <T::Standin as Transition<From, To>>::F: crate::TransitionSignature<Args>,
340        To: crate::ConcreteStateTrait,
341    {
342        StateMut {
343            guard: self.state.guard.take(),
344            pending: state_trait::erased_state::<To>(),
345            marker: PhantomData,
346        }
347    }
348}
349
350/// State marker that can validate a shared-storage runtime marker.
351#[doc(hidden)]
352pub trait SharedBorrowState: StateTrait {
353    fn ensure_state(actual: &ErasedState) -> Result<(), WrongStateError>;
354    fn initial_pending(actual: &ErasedState) -> ErasedState;
355}
356
357pub auto trait ExactSharedBorrowState {}
358
359impl<Marker> !ExactSharedBorrowState for StateUnionState<Marker> {}
360
361impl<S> SharedBorrowState for S
362where
363    S: crate::ConcreteStateTrait + ExactSharedBorrowState,
364{
365    fn ensure_state(actual: &ErasedState) -> Result<(), WrongStateError> {
366        if state_trait::is_state::<S>(actual) {
367            Ok(())
368        } else {
369            Err(WrongStateError {
370                expected: core::any::type_name::<S>(),
371                actual: actual.type_name(),
372            })
373        }
374    }
375
376    fn initial_pending(_actual: &ErasedState) -> ErasedState {
377        state_trait::erased_state::<S>()
378    }
379}
380
381impl<Marker> SharedBorrowState for StateUnionState<Marker>
382where
383    Marker: StateUnionRuntime + 'static,
384    StateUnionState<Marker>: StateTrait,
385{
386    fn ensure_state(actual: &ErasedState) -> Result<(), WrongStateError> {
387        if Marker::contains(&**actual) {
388            Ok(())
389        } else {
390            Err(WrongStateError {
391                expected: Marker::expected_type_name(),
392                actual: actual.type_name(),
393            })
394        }
395    }
396
397    fn initial_pending(actual: &ErasedState) -> ErasedState {
398        state_trait::clone_erased(actual)
399    }
400}