#[cfg(not(feature = "tracing"))]
use crate::StateCopy;
#[cfg(feature = "decompose")]
use crate::{DecomposedData, DecomposedState, RecomposeError};
use crate::{Initial, StateClone, StateMachineImpl, Transition};
#[cfg(feature = "tracing")]
use alloc::vec::Vec;
use core::marker::PhantomData;
use core::ops::{Deref, DerefMut};
#[cfg(feature = "tracing")]
use core::panic::Location;
use core::pin::Pin;
#[cfg_attr(not(feature = "tracing"), repr(transparent))]
pub struct StateOwned<T, S> {
pub(crate) value: T,
pub(crate) state: PhantomData<fn() -> S>,
#[cfg(feature = "tracing")]
pub(crate) trace: Vec<crate::TraceEntry>,
}
pub type SPin<T, S> = StateOwned<Pin<T>, S>;
#[doc(hidden)]
pub struct TransitionCall<T, From, To> {
state: StateOwned<T, From>,
#[cfg(feature = "tracing")]
callsite: &'static Location<'static>,
to: PhantomData<fn() -> To>,
}
#[must_use]
#[track_caller]
pub fn transition<T, S, Next>(
state: StateOwned<T, S>,
_token: T::TransitionToken,
) -> TransitionCall<T, S, Next>
where
T: StateMachineImpl,
T::Standin: Transition<S, Next>,
{
TransitionCall {
state,
#[cfg(feature = "tracing")]
callsite: Location::caller(),
to: PhantomData,
}
}
#[cfg(not(feature = "tracing"))]
impl<T, From, To> TransitionCall<T, From, To>
where
T: StateMachineImpl,
{
#[doc(hidden)]
pub fn call<Args>(self, _args: Args) -> StateOwned<T, To>
where
T::Standin: Transition<From, To>,
<T::Standin as Transition<From, To>>::F: crate::TransitionSignature<Args>,
{
StateOwned {
value: self.state.value,
state: PhantomData,
}
}
}
#[cfg(feature = "tracing")]
impl<T, From, To> TransitionCall<T, From, To>
where
T: StateMachineImpl,
{
#[doc(hidden)]
pub fn call<Args>(self, _args: Args) -> StateOwned<T, To>
where
T::Standin: Transition<From, To>,
<T::Standin as Transition<From, To>>::F: crate::TransitionSignature<Args>,
From: crate::StateTrait,
To: crate::ConcreteStateTrait,
{
let mut trace = self.state.trace;
trace.push(crate::TraceEntry::new::<From, To>(self.callsite));
StateOwned {
value: self.state.value,
state: PhantomData,
trace,
}
}
}
impl<T, S> StateOwned<T, S> {
#[cfg(feature = "decompose")]
#[must_use]
pub fn decompose(self) -> (DecomposedState<S>, DecomposedData<T>) {
let uid = decompose_uid();
(
DecomposedState {
uid,
state: PhantomData,
#[cfg(feature = "tracing")]
trace: self.trace,
},
DecomposedData {
uid,
value: self.value,
},
)
}
#[cfg(feature = "decompose")]
pub fn recompose(
state: DecomposedState<S>,
data: DecomposedData<T>,
) -> Result<Self, RecomposeError> {
if state.uid != data.uid {
return Err(RecomposeError);
}
Ok(Self {
value: data.value,
state: PhantomData,
#[cfg(feature = "tracing")]
trace: state.trace,
})
}
#[cfg(feature = "tracing")]
#[must_use]
pub fn trace(&self) -> &[crate::TraceEntry] {
&self.trace
}
}
#[cfg(all(feature = "decompose", feature = "nightly-random", feature = "std"))]
fn decompose_uid() -> u64 {
std::random::random(..)
}
#[cfg(all(
feature = "decompose",
feature = "nightly-random",
not(feature = "std")
))]
compile_error!("feature `nightly-random` requires `std`; use `decompose-rand` for no-std builds");
#[cfg(all(
feature = "decompose",
not(feature = "nightly-random"),
feature = "decompose-rand"
))]
fn decompose_uid() -> u64 {
use rand::TryRngCore;
rand::rngs::OsRng
.try_next_u64()
.expect("OS random source failed while decomposing state")
}
#[cfg(all(
feature = "decompose",
not(feature = "nightly-random"),
not(feature = "decompose-rand")
))]
compile_error!(
"feature `decompose` requires a random backend: enable `nightly-random` or `decompose-rand`"
);
impl<T, S> StateOwned<T, S>
where
T: StateMachineImpl,
T::Standin: Initial<S>,
{
#[must_use]
pub const fn new(value: T) -> Self {
Self {
value,
state: PhantomData,
#[cfg(feature = "tracing")]
trace: Vec::new(),
}
}
}
impl<T, S> Deref for StateOwned<T, S> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl<T, S> DerefMut for StateOwned<T, S> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.value
}
}
impl<T, S> Clone for StateOwned<T, S>
where
T: Clone,
S: StateClone,
{
fn clone(&self) -> Self {
Self {
value: self.value.clone(),
state: PhantomData,
#[cfg(feature = "tracing")]
trace: self.trace.clone(),
}
}
}
#[cfg(not(feature = "tracing"))]
impl<T, S> Copy for StateOwned<T, S>
where
T: Copy,
S: StateClone + StateCopy,
{
}
impl<T: core::fmt::Debug, S> core::fmt::Debug for StateOwned<T, S> {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.value.fmt(formatter)
}
}
#[cfg(not(feature = "tracing"))]
pub(super) fn complete_transition<T, From, To>(
state: StateOwned<T, From>,
_callsite: super::TransitionCallsite,
) -> StateOwned<T, To> {
StateOwned {
value: state.value,
state: PhantomData,
}
}
#[cfg(feature = "tracing")]
pub(super) fn complete_transition<T, From, To>(
state: StateOwned<T, From>,
callsite: super::TransitionCallsite,
) -> StateOwned<T, To>
where
From: crate::StateTrait,
To: crate::ConcreteStateTrait,
{
let mut trace = state.trace;
trace.push(crate::TraceEntry::new::<From, To>(callsite));
StateOwned {
value: state.value,
state: PhantomData,
trace,
}
}