Skip to content

Commit

Permalink
Merge pull request #14 from hashmismatch/finny_timers
Browse files Browse the repository at this point in the history
Finny timers
  • Loading branch information
rudib authored Sep 16, 2021
2 parents 9c7c82f + 8fef1d7 commit 0c592d3
Show file tree
Hide file tree
Showing 38 changed files with 1,894 additions and 146 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,4 @@ fn main() -> FsmResult<()> {
}
```

License: MIT OR Apache-2.0
License: MIT OR Apache-2.0
7 changes: 5 additions & 2 deletions finny/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ finny_derive = { path = "../finny_derive", version = "0.2.0" }
arraydeque = { version = "0.4", default-features = false }
slog = { version = "2.7.0", optional = true }


[features]
default = ["std", "inspect_slog"]
default = ["std", "inspect_slog", "timers_std"]
std = ["arraydeque/std"]
inspect_slog = ["slog"]
inspect_slog = ["slog"]
timers_std = []
generate_plantuml = ["finny_derive/generate_plantuml"]
10 changes: 10 additions & 0 deletions finny/src/decl/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ impl<'a, TFsm, TContext, TEvent, TState> FsmEventBuilderTransition<'a, TFsm, TCo
pub fn guard<TGuard: Fn(&TEvent, &EventContext<'a, TFsm, FsmQueueMock<TFsm>>, &<TFsm as FsmBackend>::States) -> bool>(&mut self, _guard: TGuard) -> &mut Self {
self
}

/// A type for this transition. The struct for the transition will be generated.
pub fn with_transition_ty<TTransition>(&mut self) -> &mut Self {
self
}
}


Expand All @@ -69,4 +74,9 @@ impl<'a, TFsm, TContext, TEvent, TStateFrom, TStateTo> FsmEventBuilderTransition
pub fn guard<TGuard: Fn(&TEvent, &EventContext<'a, TFsm, FsmQueueMock<TFsm>>, &<TFsm as FsmBackend>::States) -> bool>(&mut self, _guard: TGuard) -> &mut Self {
self
}

/// A type for this transition. The struct for the transition will be generated.
pub fn with_transition_ty<TTransition>(&mut self) -> &mut Self {
self
}
}
28 changes: 27 additions & 1 deletion finny/src/decl/state.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::lib::*;
use crate::{TimerFsmSettings, lib::*};

use crate::{EventContext, FsmBackend};
use super::{FsmQueueMock, event::FsmEventBuilderState};
Expand Down Expand Up @@ -29,4 +29,30 @@ impl<TFsm, TContext, TState> FsmStateBuilder<TFsm, TContext, TState>
_event: PhantomData::default()
}
}

/// Start a new timer when entering this state. The timer should be unit struct with a implemented
/// Default trait. The timer is setup within a closure and the trigger is another closure
/// that returns an event to be enqueued in the FSM.
pub fn on_entry_start_timer<FSetup, FTrigger>(&self, _setup: FSetup, _trigger: FTrigger) -> FsmStateTimerBuilder<TFsm, TContext, TState>
where
FSetup: Fn(&mut TContext, &mut TimerFsmSettings),
FTrigger: Fn(&TContext, &TState) -> Option< <TFsm as FsmBackend>::Events >
{
FsmStateTimerBuilder {
_state: self
}
}
}

pub struct FsmStateTimerBuilder<'a, TFsm, TContext, TState> {
_state: &'a FsmStateBuilder<TFsm, TContext, TState>
}

impl<'a, TFsm, TContext, TState> FsmStateTimerBuilder<'a, TFsm, TContext, TState>
where TFsm: FsmBackend
{
/// Assign this type to the timer. The struct for it will be auto-generated.
pub fn with_timer_ty<TTimer>(self) {

}
}
42 changes: 27 additions & 15 deletions finny/src/fsm/dispatch.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
use crate::lib::*;
use crate::{FsmTimers, FsmTimersSub, lib::*};
use crate::{EventContext, FsmBackend, FsmBackendImpl, FsmEvent, FsmEventQueue, FsmEventQueueSub, FsmRegionId, FsmResult, Inspect};

pub struct DispatchContext<'a, 'b, 'c, F, Q, I>
pub struct DispatchContext<'a, 'b, 'c, F, Q, I, T>
where F: FsmBackend,
Q: FsmEventQueue<F>,
I: Inspect
I: Inspect,
T: FsmTimers<F>
{
pub queue: &'a mut Q,
pub inspect: &'b mut I,
pub backend: &'c mut FsmBackendImpl<F>
pub backend: &'c mut FsmBackendImpl<F>,
pub timers: &'a mut T
}

impl<'a, 'b, 'c, F, Q, I> DispatchContext<'a, 'b, 'c, F, Q, I>
impl<'a, 'b, 'c, F, Q, I, T> DispatchContext<'a, 'b, 'c, F, Q, I, T>
where F: FsmBackend,
Q: FsmEventQueue<F>,
I: Inspect
I: Inspect,
T: FsmTimers<F>
{

pub fn to_event_context(&'a mut self, region: FsmRegionId) -> EventContext<'a, F, Q>
Expand All @@ -24,20 +27,22 @@ where F: FsmBackend,
queue: self.queue,
region
}
}
}
}

/// Used to funnel the event down to the sub-machine.
pub fn dispatch_to_submachine<'a, 'b, 'c, TFsm, TSubMachine, TEvent, Q, I>(ctx: &mut DispatchContext<'a, 'b, 'c, TFsm, Q, I>, ev: &TEvent, inspect_event_ctx: &mut I)
pub fn dispatch_to_submachine<'a, 'b, 'c, TFsm, TSubMachine, Q, I, T>(ctx: &mut DispatchContext<'a, 'b, 'c, TFsm, Q, I, T>,
ev: FsmEvent<<TSubMachine as FsmBackend>::Events, <TSubMachine as FsmBackend>::Timers>, inspect_event_ctx: &mut I)
-> FsmResult<()>
where
TFsm: FsmBackend,
<TFsm as FsmBackend>::States: AsMut<TSubMachine>,
TSubMachine: FsmBackend + DerefMut<Target = FsmBackendImpl<TSubMachine>> + FsmBackend<Events = TEvent>,
<TFsm as FsmBackend>::States: AsMut<TSubMachine>,
<TFsm as FsmBackend>::Events: From<<TSubMachine as FsmBackend>::Events>,
<TFsm as FsmBackend>::Timers: From<<TSubMachine as FsmBackend>::Timers>,
TSubMachine: FsmBackend + DerefMut<Target = FsmBackendImpl<TSubMachine>>,
Q: FsmEventQueue<TFsm>,
I: Inspect,
<TFsm as FsmBackend>::Events: From<<TSubMachine as FsmBackend>::Events>,
TEvent: Clone
T: FsmTimers<TFsm>,
{
let sub_fsm: &mut TSubMachine = ctx.backend.states.as_mut();

Expand All @@ -47,13 +52,20 @@ pub fn dispatch_to_submachine<'a, 'b, 'c, TFsm, TSubMachine, TEvent, Q, I>(ctx:
_sub_fsm: core::marker::PhantomData::<TSubMachine>::default()
};

let mut timers_adapter = FsmTimersSub {
parent: ctx.timers,
_parent_fsm: core::marker::PhantomData::<TFsm>::default(),
_sub_fsm: core::marker::PhantomData::<TSubMachine>::default()
};

let mut inspect = inspect_event_ctx.for_sub_machine::<TSubMachine>();

let sub_dispatch_ctx = DispatchContext {
backend: sub_fsm,
inspect: &mut inspect,
queue: &mut queue_adapter
queue: &mut queue_adapter,
timers: &mut timers_adapter
};

<TSubMachine>::dispatch_event(sub_dispatch_ctx, FsmEvent::Event(ev.clone()))
<TSubMachine>::dispatch_event(sub_dispatch_ctx, ev)
}
32 changes: 28 additions & 4 deletions finny/src/fsm/events.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,62 @@
use crate::{FsmBackend, FsmEventQueue, FsmEventQueueSender, lib::*};

/// The internal event type that also allows stopping or starting the machine.
pub enum FsmEvent<E> {
#[derive(Clone)]
pub enum FsmEvent<E, T> {
Start,
Stop,
Timer(T),
Event(E)
}

impl<E> From<E> for FsmEvent<E> {
impl<E, T> From<E> for FsmEvent<E, T> {
fn from(event: E) -> Self {
FsmEvent::Event(event)
}
}

impl<E> Debug for FsmEvent<E> where E: Debug {
impl<E, T> Debug for FsmEvent<E, T> where E: Debug, T: Debug {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FsmEvent::Start => f.write_str("Fsm::Start"),
FsmEvent::Stop => f.write_str("Fsm::Stop"),
FsmEvent::Timer(t) => f.write_fmt(format_args!("Fsm::Timer({:?})", t)),
FsmEvent::Event(ev) => ev.fmt(f)
}
}
}

impl<E> AsRef<str> for FsmEvent<E> where E: AsRef<str> {
impl<E, T> AsRef<str> for FsmEvent<E, T> where E: AsRef<str> {
fn as_ref(&self) -> &str {
match self {
FsmEvent::Start => "Fsm::Start",
FsmEvent::Stop => "Fsm::Stop",
FsmEvent::Timer(_) => "Fsm::Timer",
FsmEvent::Event(e) => e.as_ref()
}
}
}

impl<E, T> FsmEvent<E, T> {
pub fn to_sub_fsm<FSub>(self) -> FsmEvent<<FSub as FsmBackend>::Events, <FSub as FsmBackend>::Timers>
where FSub: FsmBackend,
<FSub as FsmBackend>::Timers: From<T>,
<FSub as FsmBackend>::Timers: From<E>
{
match self {
FsmEvent::Start => FsmEvent::Start,
FsmEvent::Stop => FsmEvent::Stop,
FsmEvent::Timer(t) => {
FsmEvent::Timer(t.into())
}
FsmEvent::Event(ev) => {
FsmEvent::Timer(ev.into())
}
}
}
}


pub type FsmRegionId = usize;

/// The context that is given to all of the guards and actions.
Expand Down
18 changes: 10 additions & 8 deletions finny/src/fsm/fsm_factory.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{FsmBackend, FsmBackendImpl, FsmEventQueue, FsmFrontend, FsmResult, Inspect, InspectNull};
use crate::{FsmBackend, FsmBackendImpl, FsmEventQueue, FsmFrontend, FsmResult, FsmTimers, FsmTimersNull, Inspect, InspectNull};

#[cfg(feature="std")]
use crate::FsmEventQueueVec;
use crate::{FsmEventQueueVec, timers::std::TimersStd};

/// Builds a frontend for running your FSM.
pub trait FsmFactory {
Expand All @@ -11,25 +11,27 @@ pub trait FsmFactory {
fn new_submachine_backend(backend: FsmBackendImpl<Self::Fsm>) -> FsmResult<Self> where Self: Sized;

/// Build a new frontend for the FSM with all the environmental services provided by the caller.
fn new_with<Q, I>(context: <Self::Fsm as FsmBackend>::Context, queue: Q, inspect: I) -> FsmResult<FsmFrontend<Self::Fsm, Q, I>>
where Q: FsmEventQueue<Self::Fsm>, I: Inspect
fn new_with<Q, I, T>(context: <Self::Fsm as FsmBackend>::Context, queue: Q, inspect: I, timers: T) -> FsmResult<FsmFrontend<Self::Fsm, Q, I, T>>
where Q: FsmEventQueue<Self::Fsm>, I: Inspect, T: FsmTimers<Self::Fsm>
{
let frontend = FsmFrontend {
queue,
inspect,
backend: FsmBackendImpl::new(context)?
backend: FsmBackendImpl::new(context)?,
timers
};

Ok(frontend)
}

/// Build a new frontend for the FSM with a `FsmEventQueueVec` queue and no logging.
/// Build a new frontend for the FSM with a `FsmEventQueueVec` queue, `TimersStd` for timers and no logging.
#[cfg(feature="std")]
fn new(context: <Self::Fsm as FsmBackend>::Context) -> FsmResult<FsmFrontend<Self::Fsm, FsmEventQueueVec<Self::Fsm>, InspectNull>> {
fn new(context: <Self::Fsm as FsmBackend>::Context) -> FsmResult<FsmFrontend<Self::Fsm, FsmEventQueueVec<Self::Fsm>, InspectNull, TimersStd<Self::Fsm>>> {
let frontend = FsmFrontend {
queue: FsmEventQueueVec::new(),
backend: FsmBackendImpl::new(context)?,
inspect: InspectNull::new()
inspect: InspectNull::new(),
timers: TimersStd::new(),
};

Ok(frontend)
Expand Down
69 changes: 53 additions & 16 deletions finny/src/fsm/fsm_impl.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{DispatchContext, Inspect, lib::*};
use crate::{DispatchContext, FsmTimers, Inspect, lib::*};
use crate::{FsmBackend, FsmEvent, FsmEventQueue, FsmResult, FsmStates};

use super::FsmStateFactory;
Expand Down Expand Up @@ -49,24 +49,46 @@ impl<F: FsmBackend> Deref for FsmBackendImpl<F> {
}
}

impl<F: FsmBackend> DerefMut for FsmBackendImpl<F> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.context
}
}


/// The frontend of a state machine which also includes environmental services like queues
/// and inspection. The usual way to use the FSM.
pub struct FsmFrontend<F, Q, I>
where F: FsmBackend, Q: FsmEventQueue<F>, I: Inspect
pub struct FsmFrontend<F, Q, I, T>
where F: FsmBackend, Q: FsmEventQueue<F>, I: Inspect, T: FsmTimers<F>
{
pub backend: FsmBackendImpl<F>,
pub queue: Q,
pub inspect: I
pub inspect: I,
pub timers: T
}

impl<F, Q, I> FsmFrontend<F, Q, I>
where F: FsmBackend, Q: FsmEventQueue<F>, I: Inspect
impl<F, Q, I, T> FsmFrontend<F, Q, I, T>
where F: FsmBackend, Q: FsmEventQueue<F>, I: Inspect, T: FsmTimers<F>
{
/// Start the FSM, initiates the transition to the initial state.
pub fn start(&mut self) -> FsmResult<()> {
Self::dispatch_single_event(self, FsmEvent::Start)
}

/// Dispatch any pending timer events into the queue, then run all the
/// events from the queue until completition.
pub fn dispatch_timer_events(&mut self) -> FsmResult<()> {
loop {
if let Some(timer_id) = self.timers.get_triggered_timer() {
self.dispatch_single_event(FsmEvent::Timer(timer_id))?;
} else {
break;
}
}

self.dispatch_queue()
}

/// Dispatch this event and run it to completition.
pub fn dispatch<E>(&mut self, event: E) -> FsmResult<()>
where E: Into<<F as FsmBackend>::Events>
Expand All @@ -75,32 +97,47 @@ impl<F, Q, I> FsmFrontend<F, Q, I>
let ev = FsmEvent::Event(ev);
Self::dispatch_single_event(self, ev)?;

while let Some(ev) = self.queue.dequeue() {
let ev: <F as FsmBackend>::Events = ev.into();
Self::dispatch(self, ev)?;
}

Ok(())
self.dispatch_queue()
}

/// Dispatch only this event, do not run it to completition.
pub fn dispatch_single_event(&mut self, event: FsmEvent<<F as FsmBackend>::Events>) -> FsmResult<()> {
pub fn dispatch_single_event(&mut self, event: FsmEvent<<F as FsmBackend>::Events, <F as FsmBackend>::Timers>) -> FsmResult<()> {
let dispatch_ctx = DispatchContext {
backend: &mut self.backend,
inspect: &mut self.inspect,
queue: &mut self.queue
queue: &mut self.queue,
timers: &mut self.timers
};

F::dispatch_event(dispatch_ctx, event)
}

/// Dispatch the entire event queue and run it to completition.
pub fn dispatch_queue(&mut self) -> FsmResult<()> {
while let Some(ev) = self.queue.dequeue() {
let ev: <F as FsmBackend>::Events = ev.into();
// todo: log?
Self::dispatch(self, ev);
}

Ok(())
}
}

impl<F, Q, I> Deref for FsmFrontend<F, Q, I>
where F: FsmBackend, Q: FsmEventQueue<F>, I: Inspect
impl<F, Q, I, T> Deref for FsmFrontend<F, Q, I, T>
where F: FsmBackend, Q: FsmEventQueue<F>, I: Inspect, T: FsmTimers<F>
{
type Target = FsmBackendImpl<F>;

fn deref(&self) -> &Self::Target {
&self.backend
}
}

impl<F, Q, I, T> DerefMut for FsmFrontend<F, Q, I, T>
where F: FsmBackend, Q: FsmEventQueue<F>, I: Inspect, T: FsmTimers<F>
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.backend
}
}
Loading

0 comments on commit 0c592d3

Please sign in to comment.