use std::cell::RefCell;
use std::rc::Rc;
#[cfg(any(feature = "thread-safe", feature = "async"))]
use std::sync::Arc;
#[cfg(any(feature = "thread-safe", feature = "async"))]
use parking_lot::Mutex;
use crate::Context;
#[cfg(feature = "async")]
use crate::async_context::{
AsyncComputeContext, AsyncContext, AsyncEffectHandle, AsyncSignalHandle, AsyncSource,
};
use crate::cell::Computed;
use crate::cell::Source;
use crate::effect::Effect;
#[cfg(feature = "thread-safe")]
use crate::thread_safe::{ThreadSafeContext, ThreadSafeSignalHandle};
type TransitionFn<S, E> = dyn Fn(&S, &E) -> Option<S>;
#[cfg(any(feature = "thread-safe", feature = "async"))]
type ThreadSafeTransitionFn<S, E> = dyn Fn(&S, &E) -> Option<S> + Send + Sync;
pub struct StateMachine<S, E>
where
S: PartialEq + Clone + 'static,
E: 'static,
{
state: Source<S>,
transition: Rc<TransitionFn<S, E>>,
}
impl<S, E> StateMachine<S, E>
where
S: PartialEq + Clone + 'static,
E: 'static,
{
pub fn new<F>(ctx: &Context, initial: S, transition: F) -> Self
where
F: Fn(&S, &E) -> Option<S> + 'static,
{
Self {
state: ctx.source(initial),
transition: Rc::new(transition),
}
}
pub fn send(&self, ctx: &Context, event: E) -> bool {
let current = ctx.get(&self.state);
match (self.transition)(¤t, &event) {
Some(next) => {
ctx.set(&self.state, next);
true
}
None => false,
}
}
pub fn state(&self, ctx: &Context) -> S {
ctx.get(&self.state)
}
pub fn state_handle(&self) -> Source<S> {
self.state
}
pub fn on_transition<F>(&self, ctx: &Context, handler: F) -> Effect
where
F: Fn(&S, &S) + 'static,
{
let state = self.state;
let prev: Rc<RefCell<Option<S>>> = Rc::new(RefCell::new(None));
let handler = Rc::new(handler);
ctx.effect(move |ctx| {
let current = ctx.get(&state);
let mut prev_ref = prev.borrow_mut();
if let Some(ref old) = *prev_ref
&& old != ¤t
{
handler(old, ¤t);
}
*prev_ref = Some(current);
})
}
pub fn state_is(&self, ctx: &Context, target: S) -> Computed<bool> {
let state = self.state;
ctx.signal(move |ctx| ctx.get(&state) == target)
}
}
#[cfg(feature = "thread-safe")]
pub struct ThreadSafeStateMachine<S, E>
where
S: PartialEq + Clone + Send + Sync + 'static,
E: Send + Sync + 'static,
{
state: Source<S>,
transition: Arc<ThreadSafeTransitionFn<S, E>>,
}
#[cfg(feature = "thread-safe")]
impl<S, E> Clone for ThreadSafeStateMachine<S, E>
where
S: PartialEq + Clone + Send + Sync + 'static,
E: Send + Sync + 'static,
{
fn clone(&self) -> Self {
Self {
state: self.state,
transition: Arc::clone(&self.transition),
}
}
}
#[cfg(feature = "thread-safe")]
impl<S, E> ThreadSafeStateMachine<S, E>
where
S: PartialEq + Clone + Send + Sync + 'static,
E: Send + Sync + 'static,
{
pub fn new<F>(ctx: &ThreadSafeContext, initial: S, transition: F) -> Self
where
F: Fn(&S, &E) -> Option<S> + Send + Sync + 'static,
{
Self {
state: ctx.source(initial),
transition: Arc::new(transition),
}
}
pub fn send(&self, ctx: &ThreadSafeContext, event: E) -> bool {
let current = ctx.get(&self.state);
match (self.transition)(¤t, &event) {
Some(next) => {
ctx.set(&self.state, next);
true
}
None => false,
}
}
pub fn state(&self, ctx: &ThreadSafeContext) -> S {
ctx.get(&self.state)
}
pub fn state_handle(&self) -> Source<S> {
self.state
}
pub fn on_transition<F>(&self, ctx: &ThreadSafeContext, handler: F) -> Effect
where
F: Fn(&S, &S) + Send + Sync + 'static,
{
let state = self.state;
let prev: Arc<Mutex<Option<S>>> = Arc::new(Mutex::new(None));
let handler = Arc::new(handler);
ctx.effect(move |ctx: &ThreadSafeContext| {
let current = ctx.get(&state);
let mut prev_ref = prev.lock();
if let Some(ref old) = *prev_ref
&& old != ¤t
{
handler(old, ¤t);
}
*prev_ref = Some(current);
})
}
pub fn state_is(&self, ctx: &ThreadSafeContext, target: S) -> ThreadSafeSignalHandle<bool> {
let state = self.state;
ctx.signal(move |ctx: &ThreadSafeContext| ctx.get(&state) == target)
}
}
#[cfg(feature = "async")]
pub struct AsyncStateMachine<S, E>
where
S: PartialEq + Clone + Send + Sync + 'static,
E: Send + Sync + 'static,
{
state: AsyncSource<S>,
transition: Arc<ThreadSafeTransitionFn<S, E>>,
}
#[cfg(feature = "async")]
impl<S, E> Clone for AsyncStateMachine<S, E>
where
S: PartialEq + Clone + Send + Sync + 'static,
E: Send + Sync + 'static,
{
fn clone(&self) -> Self {
Self {
state: self.state,
transition: Arc::clone(&self.transition),
}
}
}
#[cfg(feature = "async")]
impl<S, E> AsyncStateMachine<S, E>
where
S: PartialEq + Clone + Send + Sync + 'static,
E: Send + Sync + 'static,
{
pub fn new<F>(ctx: &AsyncContext, initial: S, transition: F) -> Self
where
F: Fn(&S, &E) -> Option<S> + Send + Sync + 'static,
{
Self {
state: ctx.source(initial),
transition: Arc::new(transition),
}
}
pub fn send(&self, ctx: &AsyncContext, event: E) -> bool {
let current = ctx.get(&self.state);
match (self.transition)(¤t, &event) {
Some(next) => {
ctx.set(&self.state, next);
true
}
None => false,
}
}
pub fn state(&self, ctx: &AsyncContext) -> S {
ctx.get(&self.state)
}
pub fn state_handle(&self) -> AsyncSource<S> {
self.state
}
pub fn on_transition<F>(&self, ctx: &AsyncContext, handler: F) -> AsyncEffectHandle
where
F: Fn(&S, &S) + Send + Sync + 'static,
{
let state = self.state;
let prev: Arc<Mutex<Option<S>>> = Arc::new(Mutex::new(None));
let handler = Arc::new(handler);
ctx.effect_async(move |compute_ctx: AsyncComputeContext| {
let current = compute_ctx.get(&state);
let prev = prev.clone();
let handler = handler.clone();
async move {
let mut prev_ref = prev.lock();
if let Some(ref old) = *prev_ref
&& old != ¤t
{
handler(old, ¤t);
}
*prev_ref = Some(current);
None::<fn()>
}
})
}
pub fn state_is(&self, ctx: &AsyncContext, target: S) -> AsyncSignalHandle<bool> {
let state = self.state;
ctx.signal_async(move |compute_ctx: AsyncComputeContext| {
let t = target.clone();
async move { compute_ctx.get(&state) == t }
})
}
}