Skip to main content

magicstatemachines/shared/
guard.rs

1use super::{SharedStateError, SharedStorage, SharedValue, WrongStateError};
2use crate::{
3    InnerInference, 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: SharedStorage + '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 complete_transition<T, From, To, Args>(
93        _state: State<Self, T, From>,
94        _args: Args,
95        _callsite: TransitionCallsite,
96    ) -> State<Self, T, To>
97    where
98        T: StateMachineImpl,
99        From: StateTrait,
100        To: crate::ConcreteStateTrait,
101        T::Standin: Transition<From, To>,
102        <T::Standin as Transition<From, To>>::F: crate::TransitionSignature<Args>,
103    {
104        panic!("immutable shared-state views cannot transition")
105    }
106
107    fn complete_transition_after_effect<T, From, To>(
108        _state: State<Self, T, From>,
109        _callsite: TransitionCallsite,
110    ) -> State<Self, T, To>
111    where
112        T: StateMachineImpl,
113        From: StateTrait,
114        To: crate::ConcreteStateTrait,
115    {
116        panic!("immutable shared-state views cannot transition")
117    }
118
119    fn inferred_state<T, S>(inner: &Self::Inner<T, S>) -> ErasedState
120    where
121        T: StateMachineImpl,
122        S: StateTrait,
123    {
124        state_trait::clone_erased(&inner.guard.state)
125    }
126}
127
128impl<'a, Backend> SRef for StorageStateRef<'a, Backend>
129where
130    Backend: SharedStorage + 'a,
131{
132    fn s_ref<T, S>(inner: &Self::Inner<T, S>) -> &T
133    where
134        T: StateMachineImpl,
135    {
136        inner
137    }
138}
139
140/// Mutable typed view into shared state.
141///
142/// Transitions performed through this guard update an internal pending state.
143/// When the guard is dropped, that pending state is committed back to the
144/// shared container.
145///
146/// The returned guard is used through the generic [`State`] alias
147/// [`SMutView`](crate::SMutView), so implementation methods can keep the same
148/// `State<S, Self, Current>` receiver shape they use for owned values:
149///
150/// ```ignore
151/// {
152///     let connected = shared.borrow_mut::<Connected>()?;
153///     let authenticated = magicstatemachines::transition!(
154///         connected,
155///         "alice".to_owned(),
156///     );
157///     drop(authenticated); // shared state is now `Authenticated`
158/// }
159/// ```
160///
161/// The commit happens when the final guard state is dropped. Constructing a
162/// transition call object is not a commit by itself.
163pub struct StateMut<G, T, S>
164where
165    G: DerefMut<Target = SharedValue<T>>,
166{
167    guard: Option<G>,
168    pending: ErasedState,
169    marker: PhantomData<fn() -> (T, S)>,
170}
171
172/// Generic [`State`] backend for a mutable shared-state guard.
173pub struct StorageStateMut<'a, Backend>(PhantomData<&'a Backend>);
174
175impl<'a, Backend> StateStorage for StorageStateMut<'a, Backend>
176where
177    Backend: SharedStorage + 'a,
178{
179    type Inference = InnerInference;
180
181    type Inner<T, S>
182        = StateMut<Backend::WriteGuard<'a, T>, T, S>
183    where
184        T: StateMachineImpl;
185    type Machine<T>
186        = T
187    where
188        T: StateMachineImpl;
189
190    fn retag<T, From, To>(mut inner: Self::Inner<T, From>) -> Self::Inner<T, To>
191    where
192        T: StateMachineImpl,
193    {
194        StateMut {
195            guard: inner.guard.take(),
196            pending: state_trait::clone_erased(&inner.pending),
197            marker: PhantomData,
198        }
199    }
200
201    fn complete_transition<T, From, To, Args>(
202        mut state: State<Self, T, From>,
203        _args: Args,
204        _callsite: TransitionCallsite,
205    ) -> State<Self, T, To>
206    where
207        T: StateMachineImpl,
208        From: StateTrait,
209        To: crate::ConcreteStateTrait,
210        T::Standin: Transition<From, To>,
211        <T::Standin as Transition<From, To>>::F: crate::TransitionSignature<Args>,
212    {
213        State {
214            inner: StateMut {
215                guard: state.inner.guard.take(),
216                pending: state_trait::erased_state::<To>(),
217                marker: PhantomData,
218            },
219            marker: PhantomData,
220        }
221    }
222
223    fn complete_transition_after_effect<T, From, To>(
224        mut state: State<Self, T, From>,
225        _callsite: TransitionCallsite,
226    ) -> State<Self, T, To>
227    where
228        T: StateMachineImpl,
229        From: StateTrait,
230        To: crate::ConcreteStateTrait,
231    {
232        State {
233            inner: StateMut {
234                guard: state.inner.guard.take(),
235                pending: state_trait::erased_state::<To>(),
236                marker: PhantomData,
237            },
238            marker: PhantomData,
239        }
240    }
241
242    fn inferred_state<T, S>(inner: &Self::Inner<T, S>) -> ErasedState
243    where
244        T: StateMachineImpl,
245        S: StateTrait,
246    {
247        state_trait::clone_erased(&inner.pending)
248    }
249}
250
251impl<'a, Backend> SRef for StorageStateMut<'a, Backend>
252where
253    Backend: SharedStorage + 'a,
254{
255    fn s_ref<T, S>(inner: &Self::Inner<T, S>) -> &T
256    where
257        T: StateMachineImpl,
258    {
259        inner
260    }
261}
262
263impl<'a, Backend> SMut for StorageStateMut<'a, Backend>
264where
265    Backend: SharedStorage + 'a,
266{
267    fn s_mut<T, S>(inner: &mut Self::Inner<T, S>) -> &mut T
268    where
269        T: StateMachineImpl,
270    {
271        inner
272    }
273}
274
275impl<G, T, S> StateMut<G, T, S>
276where
277    G: DerefMut<Target = SharedValue<T>>,
278    S: SharedBorrowState,
279{
280    pub(super) fn from_guard<StorageError>(
281        guard: G,
282    ) -> Result<Self, SharedStateError<StorageError>> {
283        S::ensure_state(&guard.state)?;
284        let pending = S::initial_pending(&guard.state);
285        Ok(Self {
286            guard: Some(guard),
287            pending,
288            marker: PhantomData,
289        })
290    }
291}
292
293/// Creates a callable transition for a mutable shared-state guard.
294///
295/// This is the guarded-state counterpart to [`crate::transition()`].
296#[must_use]
297pub fn transition_mut<G, T, S, Next>(
298    state: StateMut<G, T, S>,
299    _token: T::TransitionToken,
300) -> StateMutTransitionCall<G, T, S, Next>
301where
302    G: DerefMut<Target = SharedValue<T>>,
303    T: StateMachineImpl,
304    T::Standin: Transition<S, Next>,
305{
306    StateMutTransitionCall {
307        state,
308        to: PhantomData,
309    }
310}
311
312impl<G, T, S> Deref for StateMut<G, T, S>
313where
314    G: DerefMut<Target = SharedValue<T>>,
315{
316    type Target = T;
317
318    fn deref(&self) -> &Self::Target {
319        &self.guard.as_ref().expect("guard is present").value
320    }
321}
322
323impl<G, T, S> DerefMut for StateMut<G, T, S>
324where
325    G: DerefMut<Target = SharedValue<T>>,
326{
327    fn deref_mut(&mut self) -> &mut Self::Target {
328        &mut self.guard.as_mut().expect("guard is present").value
329    }
330}
331
332impl<G, T, S> Drop for StateMut<G, T, S>
333where
334    G: DerefMut<Target = SharedValue<T>>,
335{
336    fn drop(&mut self) {
337        if let Some(guard) = self.guard.as_mut() {
338            guard.state = state_trait::clone_erased(&self.pending);
339        }
340    }
341}
342
343/// Low-level transition call object for [`StateMut`].
344#[doc(hidden)]
345pub struct StateMutTransitionCall<G, T, From, To>
346where
347    G: DerefMut<Target = SharedValue<T>>,
348{
349    state: StateMut<G, T, From>,
350    to: PhantomData<fn() -> To>,
351}
352
353impl<G, T, From, To> StateMutTransitionCall<G, T, From, To>
354where
355    G: DerefMut<Target = SharedValue<T>>,
356{
357    pub fn call<Args>(mut self, _args: Args) -> StateMut<G, T, To>
358    where
359        T: StateMachineImpl,
360        T::Standin: Transition<From, To>,
361        <T::Standin as Transition<From, To>>::F: crate::TransitionSignature<Args>,
362        To: crate::ConcreteStateTrait,
363    {
364        StateMut {
365            guard: self.state.guard.take(),
366            pending: state_trait::erased_state::<To>(),
367            marker: PhantomData,
368        }
369    }
370}
371
372/// State marker that can validate a shared-storage runtime marker.
373#[doc(hidden)]
374pub trait SharedBorrowState: StateTrait {
375    fn ensure_state(actual: &ErasedState) -> Result<(), WrongStateError>;
376    fn initial_pending(actual: &ErasedState) -> ErasedState;
377}
378
379pub auto trait ExactSharedBorrowState {}
380
381impl<Marker> !ExactSharedBorrowState for StateUnionState<Marker> {}
382
383impl<S> SharedBorrowState for S
384where
385    S: crate::ConcreteStateTrait + ExactSharedBorrowState,
386{
387    fn ensure_state(actual: &ErasedState) -> Result<(), WrongStateError> {
388        if state_trait::is_state::<S>(actual) {
389            Ok(())
390        } else {
391            Err(WrongStateError {
392                expected: core::any::type_name::<S>(),
393                actual: actual.type_name(),
394            })
395        }
396    }
397
398    fn initial_pending(_actual: &ErasedState) -> ErasedState {
399        state_trait::erased_state::<S>()
400    }
401}
402
403impl<Marker> SharedBorrowState for StateUnionState<Marker>
404where
405    Marker: StateUnionRuntime + 'static,
406    StateUnionState<Marker>: StateTrait,
407{
408    fn ensure_state(actual: &ErasedState) -> Result<(), WrongStateError> {
409        if Marker::contains(&**actual) {
410            Ok(())
411        } else {
412            Err(WrongStateError {
413                expected: Marker::expected_type_name(),
414                actual: actual.type_name(),
415            })
416        }
417    }
418
419    fn initial_pending(actual: &ErasedState) -> ErasedState {
420        state_trait::clone_erased(actual)
421    }
422}