Skip to content

Commit 0bb83e6

Browse files
authored
Finny 0.1.0 (#6)
Reworked the library for Rust 2018 with a function builder API. Missing features: * Submachines * "Any" events * Docs & visualization * Inspection * Deferred events, shallow history for states
1 parent d70a641 commit 0bb83e6

File tree

129 files changed

+3017
-17286
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

129 files changed

+3017
-17286
lines changed

.github/workflows/ci.yml

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: Build
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
pull_request:
7+
branches: [ master ]
8+
9+
env:
10+
CARGO_TERM_COLOR: always
11+
12+
jobs:
13+
build:
14+
runs-on: ubuntu-latest
15+
16+
steps:
17+
- uses: actions/checkout@v2
18+
- name: Build
19+
run: cargo build --verbose
20+
- name: Run tests
21+
run: cargo test --verbose
22+
- name: Run no_std test
23+
run: cd finny_nostd_tests && cargo build && cargo run

.travis.yml

-9
This file was deleted.

Cargo.toml

+9-13
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
1-
[package]
2-
name = "fsm"
3-
version = "0.1.0"
4-
authors = ["Rudi Benkovic <rudi.benkovic@gmail.com>"]
1+
[workspace]
2+
members = [
3+
"finny/",
4+
"finny_derive/",
5+
"finny_tests/"
6+
]
57

6-
[dependencies]
7-
serde = { version = "1.0" }
8-
serde_derive = { version = "1.0", optional = true }
9-
10-
[features]
11-
default = ["std", "info_serializable"]
12-
std = []
13-
core_collections = []
14-
info_serializable = ["serde_derive"]
8+
exclude = [
9+
"finny_nostd_tests/"
10+
]

README.md

+55-81
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,64 @@
1-
# Hierarchical state machines for Rust
1+
# Finny - Finite State Machines for Rust
22

3-
Inspired by Boost MSM. Generates the transition tables using procedural macros. Requires at least Rust 1.16.
4-
5-
Consider this a preview, undocumented, alpha-level release.
6-
7-
[![Build Status](https://travis-ci.org/hashmismatch/fsm.rs.svg?branch=master)](https://travis-ci.org/hashmismatch/fsm.rs)
3+
![Build](https://github.com/hashmismatch/finny.rs/workflows/Build/badge.svg)
84

95
## Features
10-
11-
- Static states
12-
- Submachines with optional state preserving events
13-
- Orthogonal regions
14-
- Interrupt states
15-
- State guards
16-
- Queued events
17-
- Enum generation for event and state types
18-
- Internal state transitions that don't trigger the entry and exit events
19-
- Helpers for multiple entry states
20-
- Graphviz visualisation, the codegen generates a test that saves the graph file to the filesystem
21-
- Inspection trait for custom debugging
6+
* Declarative, builder API with a procedural function macro that generate the dispatcher
7+
* Compile-time transition graph validation
8+
* No run-time allocations required, `no_std` support
9+
* Support for generics within the shared context
10+
* Transition guards and actions
11+
* FSM regions, also known as orthogonal states
12+
* Event queueing and run-to-completition execution
2213

2314
## Example
2415

2516
```rust
26-
extern crate fsm;
27-
#[macro_use]
28-
extern crate fsm_codegen;
29-
30-
use fsm::*;
31-
32-
#[derive(Clone, PartialEq, Default, Debug)]
33-
pub struct Event1 { v: usize }
34-
impl FsmEvent for Event1 {}
35-
36-
#[derive(Clone, PartialEq, Default, Debug)]
37-
pub struct EventGo;
38-
impl FsmEvent for EventGo {}
39-
40-
#[derive(Clone, PartialEq, Default, Debug)]
41-
pub struct EventRestart;
42-
impl FsmEvent for EventRestart {}
43-
44-
#[derive(Clone, PartialEq, Default, Debug)]
45-
pub struct EventStart;
46-
impl FsmEvent for EventStart {}
47-
48-
#[derive(Clone, PartialEq, Default, Debug)]
49-
pub struct EventStop;
50-
impl FsmEvent for EventStop {}
51-
52-
#[derive(Clone, PartialEq, Default)]
53-
pub struct StateA { a: usize }
54-
impl FsmState<FsmMinTwo> for StateA { }
55-
56-
#[derive(Clone, PartialEq, Default)]
57-
pub struct StateB { b: usize }
58-
impl FsmState<FsmMinTwo> for StateB { }
59-
60-
#[derive(Clone, PartialEq, Default)]
61-
pub struct StateC { c: usize }
62-
impl FsmState<FsmMinTwo> for StateC { }
63-
64-
#[derive(Fsm)]
65-
struct FsmMinTwoDefinition(
66-
InitialState<FsmMinTwo, StateA>,
67-
68-
Transition < FsmMinTwo, StateA, EventStart, StateB, NoAction >,
69-
Transition < FsmMinTwo, StateB, EventStop, StateA, NoAction >,
70-
Transition < FsmMinTwo, StateB, EventGo, StateC, NoAction >,
71-
72-
Transition < FsmMinTwo, (StateA, StateB, StateC),
73-
EventRestart, StateA, NoAction >,
74-
75-
TransitionInternal < FsmMinTwo, StateA, Event1, NoAction >,
76-
77-
InterruptState < FsmMinTwo, StateB, EventStop >
78-
);
79-
80-
#[cfg(test)]
81-
#[test]
82-
fn test_fsm_min2() {
83-
let mut fsm = FsmMinTwo::new(());
84-
fsm.start();
85-
assert_eq!(FsmMinTwoStates::StateA, fsm.get_current_state());
17+
extern crate finny;
18+
use finny::{finny_fsm, FsmFactory, FsmResult, decl::{BuiltFsm, FsmBuilder}};
19+
20+
// The context is shared between all guards, actions and transitions. Generics are supported here!
21+
#[derive(Default)]
22+
pub struct MyContext { val: u32 }
23+
// The states are plain structs.
24+
#[derive(Default)]
25+
pub struct MyStateA { n: usize }
26+
#[derive(Default)]
27+
pub struct MyStateB;
28+
// The events are also plain structs. They can have fields.
29+
#[derive(Clone)]
30+
pub struct MyEvent;
31+
32+
// The FSM is generated by a procedural macro
33+
#[finny_fsm]
34+
fn my_fsm(mut fsm: FsmBuilder<MyFsm, MyContext>) -> BuiltFsm {
35+
// The FSM is described using a builder-style API
36+
fsm.state::<MyStateA>()
37+
.on_entry(|state, ctx| {
38+
state.n += 1;
39+
ctx.context.val += 1;
40+
})
41+
.on_event::<MyEvent>()
42+
.transition_to::<MyStateB>()
43+
.guard(|_ev, ctx| { ctx.context.val > 0 })
44+
.action(|_ev, ctx, state_a, state_b| { ctx.context.val += 1; });
45+
fsm.state::<MyStateB>();
46+
fsm.initial_state::<MyStateA>();
47+
fsm.build()
48+
}
8649

87-
fsm.process_event(FsmMinTwoEvents::EventStart(EventStart)).unwrap();
88-
assert_eq!(FsmMinTwoStates::StateB, fsm.get_current_state());
50+
// The FSM is built and tested.
51+
fn main() -> FsmResult<()> {
52+
let mut fsm = MyFsm::new(MyContext::default())?;
53+
assert_eq!(0, fsm.val);
54+
fsm.start()?;
55+
let state_a: &MyStateA = fsm.get_state();
56+
assert_eq!(1, state_a.n);
57+
assert_eq!(1, fsm.val);
58+
fsm.dispatch(MyEvent)?;
59+
assert_eq!(2, fsm.val);
60+
Ok(())
8961
}
90-
```
62+
```
63+
64+
License: MIT OR Apache-2.0

finny/Cargo.toml

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "finny"
3+
version = "0.1.0"
4+
authors = ["Rudi Benkovic <rudi.benkovic@gmail.com>"]
5+
edition = "2018"
6+
repository = "https://github.com/hashmismatch/finny.rs"
7+
description = "Finite State Machines with a procedural builder-style API and compile time transition checks."
8+
license = "MIT OR Apache-2.0"
9+
keywords = ["fsm", "state"]
10+
categories = ["Data structures", "Algorithms"]
11+
12+
[dependencies]
13+
derive_more = "0.99.11"
14+
finny_derive = { path = "../finny_derive", version = "0.1.0" }
15+
arraydeque = { version = "0.4", default-features = false }
16+
17+
[features]
18+
default = ["std"]
19+
std = ["arraydeque/std"]

finny/src/decl/event.rs

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use crate::lib::*;
2+
3+
use crate::{FsmBackend, fsm::EventContext};
4+
use super::{FsmQueueMock, FsmStateBuilder};
5+
6+
pub struct FsmEventBuilderState<'a, TFsm, TContext, TEvent, TState> {
7+
pub (crate) _state_builder: &'a FsmStateBuilder<TFsm, TContext, TState>,
8+
pub (crate) _event: PhantomData<TEvent>
9+
}
10+
11+
impl<'a, TFsm, TContext, TEvent, TState> FsmEventBuilderState<'a, TFsm, TContext, TEvent, TState> {
12+
/// An internal transition doesn't trigger the state's entry and exit actions, as opposed to self-transitions.
13+
pub fn internal_transition<'b>(&'b self) -> FsmEventBuilderTransition<'b, TFsm, TContext, TEvent, TState> {
14+
FsmEventBuilderTransition {
15+
_state_event_builder: self
16+
}
17+
}
18+
19+
/// A self transition triggers this state's entry and exit actions, while an internal transition does not.
20+
pub fn self_transition<'b>(&'b self) -> FsmEventBuilderTransition<'b, TFsm, TContext, TEvent, TState> {
21+
FsmEventBuilderTransition {
22+
_state_event_builder: self
23+
}
24+
}
25+
26+
/// Transition into this state. The transition can have a guard and an action.
27+
pub fn transition_to<'b, TStateTo>(&'b self) -> FsmEventBuilderTransitionFull<'b, TFsm, TContext, TEvent, TState, TStateTo> {
28+
FsmEventBuilderTransitionFull {
29+
_transition_from: self,
30+
_state_to: PhantomData::default()
31+
}
32+
}
33+
}
34+
35+
36+
pub struct FsmEventBuilderTransition<'a, TFsm, TContext, TEvent, TState> {
37+
_state_event_builder: &'a FsmEventBuilderState<'a, TFsm, TContext, TEvent, TState>
38+
}
39+
40+
impl<'a, TFsm, TContext, TEvent, TState> FsmEventBuilderTransition<'a, TFsm, TContext, TEvent, TState>
41+
where TFsm: FsmBackend
42+
{
43+
/// An action that happens when the currently active state receives this event. No transitions.
44+
pub fn action<TAction: Fn(&TEvent, &mut EventContext<'a, TFsm, FsmQueueMock<TFsm>>, &mut TState)>(&mut self, _action: TAction) -> &mut Self {
45+
self
46+
}
47+
48+
/// A guard for executing this action.
49+
pub fn guard<TGuard: Fn(&TEvent, &EventContext<'a, TFsm, FsmQueueMock<TFsm>>) -> bool>(&mut self, _guard: TGuard) -> &mut Self {
50+
self
51+
}
52+
}
53+
54+
55+
pub struct FsmEventBuilderTransitionFull<'a, TFsm, TContext, TEvent, TStateFrom, TStateTo> {
56+
_transition_from: &'a FsmEventBuilderState<'a, TFsm, TContext, TEvent, TStateFrom>,
57+
_state_to: PhantomData<TStateTo>
58+
}
59+
60+
impl<'a, TFsm, TContext, TEvent, TStateFrom, TStateTo> FsmEventBuilderTransitionFull<'a, TFsm, TContext, TEvent, TStateFrom, TStateTo>
61+
where TFsm: FsmBackend
62+
{
63+
/// An action that happens between the transitions from the two states.
64+
pub fn action<TAction: Fn(&TEvent, &mut EventContext<'a, TFsm, FsmQueueMock<TFsm>>, &mut TStateFrom, &mut TStateTo)>(&mut self, _action: TAction) -> &mut Self {
65+
self
66+
}
67+
68+
/// A guard for starting this transition from one state to another, including executing the action.
69+
pub fn guard<TGuard: Fn(&TEvent, &EventContext<'a, TFsm, FsmQueueMock<TFsm>>) -> bool>(&mut self, _guard: TGuard) -> &mut Self {
70+
self
71+
}
72+
}

finny/src/decl/fsm.rs

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use crate::lib::*;
2+
3+
use crate::FsmBackend;
4+
use super::FsmStateBuilder;
5+
6+
/// The main builder-API for defining your Finny state machine.
7+
#[derive(Default)]
8+
pub struct FsmBuilder<TFsm, TContext> {
9+
pub _fsm: PhantomData<TFsm>,
10+
pub _context: PhantomData<TContext>
11+
}
12+
13+
/// The consumed struct of the FSM, ensures that all of the builder's references are released.
14+
pub struct BuiltFsm;
15+
16+
impl<TFsm, TContext> FsmBuilder<TFsm, TContext>
17+
where TFsm: FsmBackend
18+
{
19+
/// Sets the initial state of the state machine. Required!
20+
pub fn initial_state<TSTate>(&mut self) {
21+
22+
}
23+
24+
/// Defines multiple initial states for multiple regions of the FSM. The type has to be a tuple
25+
/// of the initial states for each region.
26+
///
27+
/// Example : `fsm.initial_states<(StateA, StateX)>()`
28+
pub fn initial_states<TStates>(&mut self) {
29+
30+
}
31+
32+
/// Adds some information about a state.
33+
pub fn state<TState>(&mut self) -> FsmStateBuilder<TFsm, TContext, TState> {
34+
FsmStateBuilder {
35+
_state: PhantomData::default(),
36+
_fsm: PhantomData::default(),
37+
_context: PhantomData::default()
38+
}
39+
}
40+
41+
/// Builds the final machine. Has to be returned from the definition function.
42+
pub fn build(self) -> BuiltFsm {
43+
BuiltFsm
44+
}
45+
}

finny/src/decl/mod.rs

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//! The builder-style API structures for defining your Finny FSM. The procedural macro parses
2+
//! these method calls and generated the optimized implementation.
3+
4+
mod fsm;
5+
mod state;
6+
mod event;
7+
8+
pub use self::fsm::*;
9+
pub use self::state::*;
10+
pub use self::event::*;
11+
12+
#[cfg(feature = "std")]
13+
pub type FsmQueueMock<F> = crate::FsmEventQueueVec<F>;
14+
15+
#[cfg(not(feature = "std"))]
16+
pub type FsmQueueMock<F> = crate::FsmEventQueueNull<F>;

0 commit comments

Comments
 (0)