From 0b23445763ab63e1ab506b24e490a030f5230afa Mon Sep 17 00:00:00 2001 From: Martin Broers Date: Sun, 30 Jun 2024 17:26:11 +0200 Subject: [PATCH] Added transition_callback This patch adds a transition_callback function. The function, if not used, will be optimized away by the compiler. Signed-off-by: Martin Broers --- CHANGELOG.md | 5 ++++ README.md | 6 ++++ examples/transition_callback.rs | 50 +++++++++++++++++++++++++++++++++ macros/src/codegen.rs | 10 +++++-- 4 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 examples/transition_callback.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 78ad64d..e003991 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added + +- Add transition callback. A function which is called for every transition. It has default empty +implementation. + ## [0.7.0] - 2024-07-03 ### Added diff --git a/README.md b/README.md index ff2e543..f61f826 100644 --- a/README.md +++ b/README.md @@ -311,6 +311,12 @@ The statemachine will create for all states an `on_entry_` and `on_exit_` functi If the are not used, they will be optimized away by the compiler. An example be found in `on_entry_on_exit_generic`. +### Transition callback + +The statemachine will call for every transition a transition callback. This function +is called with both the old state and new state as arguments. An example can be found +in `transition_callback`. + ## Helpers ### Auto-derive certain traits for states and events diff --git a/examples/transition_callback.rs b/examples/transition_callback.rs new file mode 100644 index 0000000..3a7386c --- /dev/null +++ b/examples/transition_callback.rs @@ -0,0 +1,50 @@ +//! An example of using state data to propagate events (See issue-17) + +#![deny(missing_docs)] + +use std::rc::Rc; +use std::sync::Mutex; + +use smlang::statemachine; + +statemachine! { + derive_states: [Debug, Copy, Clone ], + transitions: { + *D0 + ToD1 = D1, + D1 + ToD2 = D2, + }, +} + +/// Context +pub struct Context { + transition_called: Rc>, + state_exited: Rc>>, + state_entered: Rc>>, +} + +impl StateMachineContext for Context { + fn transition_callback(&self, exit: &States, entry: &States) { + *self.transition_called.lock().unwrap() = true; + *self.state_exited.lock().unwrap() = Some(*exit); + *self.state_entered.lock().unwrap() = Some(*entry); + } +} + +fn main() { + let mut sm = StateMachine::new(Context { + transition_called: Rc::new(Mutex::new(false)), + state_exited: Rc::new(Mutex::new(None)), + state_entered: Rc::new(Mutex::new(None)), + }); + + // first event starts the dominos + let _ = sm.process_event(Events::ToD1).unwrap(); + + assert!(matches!(sm.state(), &States::D1)); + assert!(*sm.context().transition_called.lock().unwrap()); + assert_eq!(*sm.context().state_exited.lock().unwrap(), Some(States::D0)); + assert_eq!( + *sm.context().state_entered.lock().unwrap(), + Some(States::D1) + ); +} diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index f754a03..8d35b45 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -411,7 +411,6 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { let streams: Vec = guard.iter() .zip(action.iter().zip(out_state)).map(|(guard, (action,out_state))| { - let binding = out_state.to_string(); let out_state_string = &binding.split('(').next().unwrap(); let binding = in_state.to_string(); @@ -441,7 +440,6 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { self.context.#guard_ident(#temporary_context_call #guard_params) #guard_await .map_err(#error_type_name::GuardFailed)? } }); - quote! { // This #guard_expression contains a boolean expression of guard functions // Each guard function has Result return type. @@ -458,17 +456,18 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { let out_state = #states_type_name::#out_state; self.context.log_state_change(&out_state); #entry_exit_states + self.context().transition_callback(&self.state(), &out_state); self.state = out_state; return Ok(&self.state); } } } else { // Unguarded transition - quote!{ #action_code let out_state = #states_type_name::#out_state; self.context.log_state_change(&out_state); #entry_exit_states + self.context().transition_callback(&self.state(), &out_state); self.state = out_state; return Ok(&self.state); } @@ -569,6 +568,11 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { /// `process_event()`. No-op by default but can be overridden in implementations /// of a state machine's `StateMachineContext` trait. fn log_state_change(&self, new_state: & #states_type_name) {} + + /// Called when transitioning to a new state as a result of an event passed to + /// `process_event()`. No-op by default but can be overridden in implementations + /// of a state machine's `StateMachineContext` trait. + fn transition_callback(&self, old_state: & #states_type_name, new_state: & #states_type_name) {} } /// List of auto-generated states.