Skip to content

Commit

Permalink
Merge pull request #72 from dkumsh/master
Browse files Browse the repository at this point in the history
Support multiple guarded transitions triggered by the same event
  • Loading branch information
ryan-summers authored Jun 27, 2024
2 parents e5fc0e7 + 3bbdc6b commit 4291514
Show file tree
Hide file tree
Showing 28 changed files with 886 additions and 385 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ Cargo.lock
*.gv
*.svg
.vscode
.idea/
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Add name to statemachine and make dot output stable and unique ([issue-62](https://github.com/korken89/smlang-rs/pull/62))
- Add derive macros to states and events ([issue-62](https://github.com/korken89/smlang-rs/pull/62))
- Add hooks to `StateMachineContext` for logging events, guards, actions, and state changes
- Add support multiple guarded transitions for a triggering event
- Add support for guard boolean expressions in the state machine declaration

### Fixed

Expand All @@ -24,6 +26,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- [breaking] `state()` now returns a `Result`
- `StateMachine::new` and `StateMachine::new_with_state` are now const functions
- Fixed clippy warnings
- [breaking] Changed guard functions return type from Result<(),_> to Result<bool,_>

## [v0.6.0] - 2022-11-02

Expand Down
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,30 @@ statemachine!{
// ...
}
```

See example `examples/input_state_pattern_match.rs` for a usage example.

#### Guard expressions

Guard expression in square brackets [] allows to define a boolean expressions of multiple guard functions.
For example:
```rust
statemachine! {
transitions: {
*Init + Login(Entry) [valid_entry] / attempt = LoggedIn,
Init + Login(Entry) [!valid_entry && !too_many_attempts] / attempt = Init,
Init + Login(Entry) [!valid_entry && too_many_attempts] / attempt = LoginDenied,
LoggedIn + Logout / reset = Init,
}
}
```
Guard expressions may consist of guard function names, and their combinations with &&, || and ! operations.

#### Multiple guarded transitions for the same state and triggering event
Multiple guarded transitions for the same state and triggering event are supported (see the example above).
It is assumed that only one guard is enabled in such a case to avoid a conflict over which transition should be selected.
However, if there is a conflict and more than one guard is enabled, the first enabled transition,
in the order they appear in the state machine definition, will be selected.

### State machine context

The state machine needs a context to be defined.
Expand Down
29 changes: 17 additions & 12 deletions examples/async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use smlang::{async_trait, statemachine};
statemachine! {
transitions: {
*State1 + Event1 [guard1] / async action1 = State2,
State2 + Event2 [async guard2] / async action2 = State3,
State2 + Event2 [async guard2 && guard3] / async action2 = State3,
State3 + Event3 / action3 = State4(bool),
}
}
Expand All @@ -22,22 +22,21 @@ pub struct Context {

#[async_trait]
impl StateMachineContext for Context {
fn guard1(&mut self) -> Result<(), ()> {
println!("`guard1` called from sync context");
Ok(())
}

async fn action1(&mut self) -> () {
println!("`action1` called from async context");
let mut lock = self.lock.write().await;
*lock = true;
fn guard3(&mut self) -> Result<bool, ()> {
println!("`guard3` called from async context");
Ok(true)
}

async fn guard2(&mut self) -> Result<(), ()> {
async fn guard2(&mut self) -> Result<bool, ()> {
println!("`guard2` called from async context");
let mut lock = self.lock.write().await;
*lock = false;
Ok(())
Ok(true)
}

fn guard1(&mut self) -> Result<bool, ()> {
println!("`guard1` called from sync context");
Ok(true)
}

async fn action2(&mut self) -> () {
Expand All @@ -47,6 +46,12 @@ impl StateMachineContext for Context {
}
}

async fn action1(&mut self) -> () {
println!("`action1` called from async context");
let mut lock = self.lock.write().await;
*lock = true;
}

fn action3(&mut self) -> bool {
println!("`action3` called from sync context, done = `{}`", self.done);
self.done
Expand Down
10 changes: 3 additions & 7 deletions examples/event_with_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,8 @@ statemachine! {
pub struct Context;

impl StateMachineContext for Context {
fn guard(&mut self, event_data: &MyEventData) -> Result<(), ()> {
if event_data == &MyEventData(42) {
Ok(())
} else {
Err(())
}
fn guard(&mut self, event_data: &MyEventData) -> Result<bool, ()> {
Ok(event_data == &MyEventData(42))
}

fn action(&mut self, event_data: MyEventData) {
Expand All @@ -38,7 +34,7 @@ fn main() {
let mut sm = StateMachine::new(Context);
let result = sm.process_event(Events::Event1(MyEventData(1))); // Guard will fail

assert!(matches!(result, Err(Error::GuardFailed(()))));
assert!(matches!(result, Err(Error::TransitionsFailed)));

let result = sm.process_event(Events::Event1(MyEventData(42))); // Guard will pass

Expand Down
4 changes: 2 additions & 2 deletions examples/event_with_mutable_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ statemachine! {
pub struct Context;

impl StateMachineContext for Context {
fn guard(&mut self, event_data: &mut MyEventData) -> Result<(), ()> {
fn guard(&mut self, event_data: &mut MyEventData) -> Result<bool, ()> {
event_data.0 = 55;
Ok(())
Ok(true)
}

fn action(&mut self, event_data: &mut MyEventData) {
Expand Down
20 changes: 6 additions & 14 deletions examples/event_with_reference_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,17 @@ statemachine! {
pub struct Context;

impl StateMachineContext for Context {
fn guard1(&mut self, event_data: &[u8]) -> Result<(), ()> {
fn guard1(&mut self, event_data: &[u8]) -> Result<bool, ()> {
// Only ok if the slice is not empty
if !event_data.is_empty() {
Ok(())
} else {
Err(())
}
Ok(!event_data.is_empty())
}

fn action1(&mut self, event_data: &[u8]) {
println!("Got valid Event Data = {:?}", event_data);
}

fn guard2(&mut self, event_data: &MyReferenceWrapper) -> Result<(), ()> {
if *event_data.0 > 9000 {
Ok(())
} else {
Err(())
}
fn guard2(&mut self, event_data: &MyReferenceWrapper) -> Result<bool, ()> {
Ok(*event_data.0 > 9000)
}

fn action2(&mut self, event_data: MyReferenceWrapper) {
Expand All @@ -51,13 +43,13 @@ fn main() {
let mut sm = StateMachine::new(Context);

let result = sm.process_event(Events::Event1(&[])); // Guard will fail
assert!(matches!(result, Err(Error::GuardFailed(()))));
assert!(matches!(result, Err(Error::TransitionsFailed)));
let result = sm.process_event(Events::Event1(&[1, 2, 3])); // Guard will pass
assert!(matches!(result, Ok(&States::State2)));

let r = 42;
let result = sm.process_event(Events::Event2(MyReferenceWrapper(&r))); // Guard will fail
assert!(matches!(result, Err(Error::GuardFailed(()))));
assert!(matches!(result, Err(Error::TransitionsFailed)));

let r = 9001;
let result = sm.process_event(Events::Event2(MyReferenceWrapper(&r))); // Guard will pass
Expand Down
10 changes: 5 additions & 5 deletions examples/ex3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ statemachine! {
pub struct Context;

impl StateMachineContext for Context {
fn guard(&mut self) -> Result<(), ()> {
fn guard(&mut self) -> Result<bool, ()> {
// Always ok
Ok(())
Ok(true)
}

fn guard_fail(&mut self) -> Result<(), ()> {
fn guard_fail(&mut self) -> Result<bool, ()> {
// Always fail
Err(())
Ok(false)
}

fn action1(&mut self) {
Expand Down Expand Up @@ -53,7 +53,7 @@ fn main() {

// The action will never run as the guard will fail
let r = sm.process_event(Events::Event2);
assert!(matches!(r, Err(Error::GuardFailed(()))));
assert!(matches!(r, Err(Error::TransitionsFailed)));

println!("After action 2");

Expand Down
4 changes: 2 additions & 2 deletions examples/guard_action_syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub struct Context;

impl StateMachineContext for Context {
// Guard1 has access to the data from Event1
fn guard1(&mut self, _event_data: &MyEventData) -> Result<(), ()> {
fn guard1(&mut self, _event_data: &MyEventData) -> Result<bool, ()> {
todo!()
}

Expand All @@ -37,7 +37,7 @@ impl StateMachineContext for Context {
}

// Guard2 has access to the data from State2
fn guard2(&mut self, _state_data: &MyStateData) -> Result<(), ()> {
fn guard2(&mut self, _state_data: &MyStateData) -> Result<bool, ()> {
todo!()
}

Expand Down
8 changes: 4 additions & 4 deletions examples/guard_action_syntax_with_temporary_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ pub struct Context;

impl StateMachineContext for Context {
// Guard1 has access to the data from Event1
fn guard1(&mut self, temp_context: &mut u16, _event_data: &MyEventData) -> Result<(), ()> {
fn guard1(&mut self, temp_context: &mut u16, _event_data: &MyEventData) -> Result<bool, ()> {
*temp_context += 1;

Ok(())
Ok(true)
}

// Action1 has access to the data from Event1, and need to return the state data for State2
Expand All @@ -42,10 +42,10 @@ impl StateMachineContext for Context {
}

// Guard2 has access to the data from State2
fn guard2(&mut self, temp_context: &mut u16, _state_data: &MyStateData) -> Result<(), ()> {
fn guard2(&mut self, temp_context: &mut u16, _state_data: &MyStateData) -> Result<bool, ()> {
*temp_context += 1;

Ok(())
Ok(true)
}

// Action2 has access to the data from State2
Expand Down
8 changes: 3 additions & 5 deletions examples/guard_custom_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,8 @@ statemachine! {
pub struct Context;

impl StateMachineContext for Context {
type GuardError = GuardError;

// Guard1 has access to the data from Event1
fn guard1(&mut self, _event_data: &MyEventData) -> Result<(), GuardError> {
type GuardError = GuardError; // Guard1 has access to the data from Event1
fn guard1(&mut self, _event_data: &MyEventData) -> Result<bool, GuardError> {
Err(GuardError::Custom)
}

Expand All @@ -47,7 +45,7 @@ impl StateMachineContext for Context {
}

// Guard2 has access to the data from State2
fn guard2(&mut self, _state_data: &MyStateData) -> Result<(), GuardError> {
fn guard2(&mut self, _state_data: &MyStateData) -> Result<bool, GuardError> {
todo!()
}

Expand Down
24 changes: 12 additions & 12 deletions examples/named_async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,16 @@ pub struct Context {

#[async_trait]
impl AsyncSimpleStateMachineContext for Context {
fn guard1(&mut self) -> Result<(), ()> {
fn guard1(&mut self) -> Result<bool, ()> {
println!("`guard1` called from sync context");
Ok(())
Ok(true)
}

async fn guard2(&mut self) -> Result<bool, ()> {
println!("`guard2` called from async context");
let mut lock = self.lock.write().await;
*lock = false;
Ok(true)
}

async fn action1(&mut self) -> () {
Expand All @@ -34,11 +41,9 @@ impl AsyncSimpleStateMachineContext for Context {
*lock = true;
}

async fn guard2(&mut self) -> Result<(), ()> {
println!("`guard2` called from async context");
let mut lock = self.lock.write().await;
*lock = false;
Ok(())
fn action3(&mut self) -> bool {
println!("`action3` called from sync context, done = `{}`", self.done);
self.done
}

async fn action2(&mut self) -> () {
Expand All @@ -47,11 +52,6 @@ impl AsyncSimpleStateMachineContext for Context {
self.done = true;
}
}

fn action3(&mut self) -> bool {
println!("`action3` called from sync context, done = `{}`", self.done);
self.done
}
}

fn main() {
Expand Down
10 changes: 5 additions & 5 deletions examples/named_ex3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ statemachine! {
pub struct Context;

impl LoopingWithGuardsStateMachineContext for Context {
fn guard(&mut self) -> Result<(), ()> {
fn guard(&mut self) -> Result<bool, ()> {
// Always ok
Ok(())
Ok(true)
}

fn guard_fail(&mut self) -> Result<(), ()> {
fn guard_fail(&mut self) -> Result<bool, ()> {
// Always fail
Err(())
Ok(false)
}

fn action1(&mut self) {
Expand Down Expand Up @@ -54,7 +54,7 @@ fn main() {

// The action will never run as the guard will fail
let r = sm.process_event(LoopingWithGuardsEvents::Event2);
assert!(matches!(r, Err(LoopingWithGuardsError::GuardFailed(()))));
assert!(matches!(r, Err(LoopingWithGuardsError::TransitionsFailed)));

println!("After action 2");

Expand Down
Loading

0 comments on commit 4291514

Please sign in to comment.