From 12da756d408383d98db6525a6000b9435e680214 Mon Sep 17 00:00:00 2001 From: nhjschulz Date: Sat, 17 Feb 2024 11:31:10 +0100 Subject: [PATCH] CFSM: Add user instance data Extend CFSM context to include a instance data pointer. This is used to run multiple instances of the same FSM. Renamed context from cfsm_Cfsm to cfsm_Ctx. This better reflects now what it is. Side: Compiler warning removal. --- CMakeLists.txt | 17 ++++ README.md | 53 +++++++---- doc/mario_classdiagram.puml | 6 +- examples/mario/main.c | 6 +- examples/mario/mario.c | 4 + examples/mario/states/cape_mario.c | 17 ++-- examples/mario/states/cape_mario.h | 2 +- examples/mario/states/dead_mario.c | 7 +- examples/mario/states/dead_mario.h | 2 +- examples/mario/states/fire_mario.c | 18 ++-- examples/mario/states/fire_mario.h | 2 +- examples/mario/states/small_mario.c | 19 ++-- examples/mario/states/small_mario.h | 2 +- examples/mario/states/super_mario.c | 18 ++-- examples/mario/states/super_mario.h | 2 +- src/c_fsm.c | 16 ++-- src/c_fsm.h | 71 ++++++++++----- tests/test_c_fsm.c | 135 ++++++++++++++++------------ 18 files changed, 251 insertions(+), 146 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 463a3c6..c14512d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,10 @@ project(CFSM HOMEPAGE_URL "https://github.com/nhjschulz/cfsm" ) +# ****************************************************************************** +# Build CFSM as a static link library which is used by example code. +# ****************************************************************************** + add_library(cfsm src/c_fsm.c ) @@ -22,6 +26,19 @@ target_include_directories(cfsm PUBLIC "src" ) +# ****************************************************************************** +# Enable strict compiler warnings +# ****************************************************************************** + +if (MSVC) + # warning level 4 + add_compile_options(/W4) +else() + # additional warnings + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + + set (CFSM_EXAMPLE_MARIO_SRC "examples/mario/main.c" "examples/mario/mario.c" diff --git a/README.md b/README.md index 3243b06..913fcf4 100644 --- a/README.md +++ b/README.md @@ -58,25 +58,34 @@ There are operations to execute Each of these operations is represented as a function pointer in the CFSM context data structure. CFSM takes care about calling leave and enter operations during a state transition. The cyclic process and event signaling -gets triggered by the application through CFSM public API. The CFSM context -structure definition is: +gets triggered by the application through CFSM public API. + +A void pointer called ```ctxPtr``` is part of the context in addition to +the operation function pointers. This pointer is set by ```cfm_init()``` and +can be used in an application specific way. FSM does not use it by itself. +The ```ctxPtr``` is intended to support multiple FSM instances with the same +handler functions. The handlers can use the pointer to access instance +specific data. + +The CFSM context structure definition is: ```c -typedef void (*cfsm_TransitionFunction)(struct cfsm_Fsm * fsm); -typedef void (*cfsm_EventFunction)(struct cfsm_Fsm * fsm, int eventId); -typedef void (*cfsm_ProcessFunction)(struct cfsm_Fsm * fsm); +typedef void (*cfsm_TransitionFunction)(struct cfsm_Ctx * fsm); +typedef void (*cfsm_EventFunction)(struct cfsm_Ctx * fsm, int eventId); +typedef void (*cfsm_ProcessFunction)(struct cfsm_Ctx * fsm); +typedef void *cfsm_InstanceDataPtr; /** CFSM context operations */ -typedef struct cfsm_Fsm { +typedef struct cfsm_Ctx { + cfsm_InstanceDataPtr ctxPtr; /**< context instance data */ cfsm_TransitionFunction onEnter; /**< operation run on enter */ cfsm_TransitionFunction onLeave; /**< operation run on leave */ cfsm_ProcessFunction onProcess; /**< cyclic operations */ cfsm_EventFunction onEvent; /**< report event to the state */ -} cfsm_Fsm; +} cfsm_Ctx; ``` - Notes: * All operations in a state are optional, with the exception of the enter operation. A state that cannot be entered is pointless. @@ -98,12 +107,12 @@ to also update the context function pointers. Below is an example of a set of function that define a SuperMario state: ```c -static void SuperMario_onProcess(cfsm_Fsm * fsm) { /* ... */} -static void SuperMario_onLeave(cfsm_Fsm * fsm) { /* ... */} -static void SuperMario_onEvent(cfsm_Fsm * fsm, int eventId) { /* ...*/} +static void SuperMario_onProcess(cfsm_Ctx * fsm) { /* ... */} +static void SuperMario_onLeave(cfsm_Ctx * fsm) { /* ... */} +static void SuperMario_onEvent(cfsm_Ctx * fsm, int eventId) { /* ...*/} -void SuperMario_onEnter(cfsm_Fsm * fsm) +void SuperMario_onEnter(cfsm_Ctx * fsm) { fsm->onProcess = SuperMario_onProcess; fsm->onEvent = SuperMario_onEvent; @@ -189,7 +198,7 @@ This is the example application component design: ### The Main function The main function implements the game simulation loop. It owns -a CFSM instance as a local cfsm_Fsm structure called ``marioFsm``. +a CFSM instance as a local cfsm_Ctx structure called ``marioFsm``. The CFSM setup phase consists of initializing ``marioFsm`` and then transition to Mario's start state "SmallMario". The simplified @@ -201,14 +210,20 @@ codes looks like this: int main(int argc, char **argv) { - cfsm_Fsm marioFsm; + cfsm_Ctx marioFsm; - cfsm_init(&marioFsm); + cfsm_init(&marioFsm, NULL); cfsm_transition(&marioFsm, SmallMario_onEnter); /* ... */ ``` +This example doesn't use instance data and passes NULL for it +in the call to ```cfsm_init()```. Instance data could be used to +extend the game to support multiple players by adding +a Luigi. We can then run two FSMs in parallel with the same +handlers. + Transitioning to the start state is done by providing the enter operation handler for this state to the API function ``cfsm_transition()``. CFSM then calls the leave operation @@ -307,7 +322,7 @@ and the only one that needs to be public. This is necessary to allow other modules to transition into it. ```c - void SmallMario_onEnter(cfsm_Fsm * fsm) + void SmallMario_onEnter(cfsm_Ctx * fsm) { puts("SmallMario_onEnter()..."); @@ -346,7 +361,7 @@ according to the Mario state machine. We just print a line to indicate to the user that we got called. ```c -static void SmallMario_onLeave(cfsm_Fsm * fsm) +static void SmallMario_onLeave(cfsm_Ctx * fsm) { puts("SmallMario_onLeave() ..."); } @@ -364,7 +379,7 @@ the event operation handler. If your logic follows a polling model, you likely implement this in the processing operation instead. ```c -static void SmallMario_onProcess(cfsm_Fsm * fsm) +static void SmallMario_onProcess(cfsm_Ctx * fsm) { puts("SmallMario_onProces(): It's me, Mario!"); } @@ -387,7 +402,7 @@ monster case. Here we also need to decrease the number of lives and eventually transition into dead Mario if no more are left. ```c -static void SmallMario_onEvent(cfsm_Fsm * fsm, int eventId) +static void SmallMario_onEvent(cfsm_Ctx * fsm, int eventId) { mario_updateCoins(eventId); diff --git a/doc/mario_classdiagram.puml b/doc/mario_classdiagram.puml index ced4e31..31bdcb3 100644 --- a/doc/mario_classdiagram.puml +++ b/doc/mario_classdiagram.puml @@ -42,7 +42,7 @@ package "Mario Example" { } package "CFSM" { - class cfsm_Fsm { + class cfsm_Ctx { +cfsm_init() +cfsm_transition( enterFunc: cfsm_TransitionFunction) +cfsm_process() @@ -57,7 +57,7 @@ package "CFSM" { } } -cfsm_Fsm -- Operations +cfsm_Ctx -- Operations SuperMario .u-|> Operations CapeMario .u-|> Operations FireMario .u-|> Operations @@ -67,7 +67,7 @@ SmallMario .u-|> Operations states .> Mario : update SmallMario <.. Main -Main ..> cfsm_Fsm : init as SmallMario\nprocessing and\n event signalling +Main ..> cfsm_Ctx : init as SmallMario\nprocessing and\n event signalling MarioVariant .l. MarioData MarioEvent .r. MarioData diff --git a/examples/mario/main.c b/examples/mario/main.c index fecc05c..d61ef0d 100644 --- a/examples/mario/main.c +++ b/examples/mario/main.c @@ -65,12 +65,14 @@ int main(int argc, char **argv) { + (void)argc; + (void)argv; - cfsm_Fsm marioFsm; + cfsm_Ctx marioFsm; puts ("Mario cfsm example: \n"); - cfsm_init(&marioFsm); + cfsm_init(&marioFsm, NULL); cfsm_transition(&marioFsm, SmallMario_onEnter); for(;;) diff --git a/examples/mario/mario.c b/examples/mario/mario.c index 134baf6..39280bd 100644 --- a/examples/mario/mario.c +++ b/examples/mario/mario.c @@ -115,6 +115,10 @@ void mario_updateCoins(MarioEvent e) case MONSTER: break; + + case QUIT: + case NOP: + break; } if (mario.coins > 5000) diff --git a/examples/mario/states/cape_mario.c b/examples/mario/states/cape_mario.c index 2ebe301..650770a 100644 --- a/examples/mario/states/cape_mario.c +++ b/examples/mario/states/cape_mario.c @@ -58,9 +58,9 @@ * Prototypes *****************************************************************************/ -static void CapeMario_onEvent(cfsm_Fsm * state, int eventId); -static void CapeMario_onProcess(cfsm_Fsm * state); -static void CapeMario_onLeave(cfsm_Fsm * state); +static void CapeMario_onEvent(cfsm_Ctx * state, int eventId); +static void CapeMario_onProcess(cfsm_Ctx * state); +static void CapeMario_onLeave(cfsm_Ctx * state); /****************************************************************************** * Variables @@ -70,7 +70,7 @@ static void CapeMario_onLeave(cfsm_Fsm * state); * External functions *****************************************************************************/ -void CapeMario_onEnter(cfsm_Fsm * fsm) +void CapeMario_onEnter(cfsm_Ctx * fsm) { puts("CapeMario_onEnter()..."); @@ -85,8 +85,9 @@ void CapeMario_onEnter(cfsm_Fsm * fsm) * Local functions *****************************************************************************/ -static void CapeMario_onEvent(cfsm_Fsm * fsm, int eventId) +static void CapeMario_onEvent(cfsm_Ctx * fsm, int eventId) { + (void)fsm; mario_updateCoins(eventId); switch(eventId) @@ -109,13 +110,15 @@ static void CapeMario_onEvent(cfsm_Fsm * fsm, int eventId) } } -static void CapeMario_onProcess(cfsm_Fsm * fsm) +static void CapeMario_onProcess(cfsm_Ctx * fsm) { + (void)fsm; puts("CapeMario_onProces(): Look, I can fly!"); } -static void CapeMario_onLeave(cfsm_Fsm * fsm) +static void CapeMario_onLeave(cfsm_Ctx * fsm) { + (void)fsm; puts("CapeMario_onLeave() ..."); } diff --git a/examples/mario/states/cape_mario.h b/examples/mario/states/cape_mario.h index 4679525..8eda47c 100644 --- a/examples/mario/states/cape_mario.h +++ b/examples/mario/states/cape_mario.h @@ -55,7 +55,7 @@ /****************************************************************************** * Functions *****************************************************************************/ -void CapeMario_onEnter(cfsm_Fsm * state); +void CapeMario_onEnter(cfsm_Ctx * state); #endif /* SUPER_MARIO_H */ diff --git a/examples/mario/states/dead_mario.c b/examples/mario/states/dead_mario.c index 7bf01a8..5c2706c 100644 --- a/examples/mario/states/dead_mario.c +++ b/examples/mario/states/dead_mario.c @@ -54,7 +54,7 @@ * Prototypes *****************************************************************************/ -static void DeadMario_onProcess(cfsm_Fsm * state); +static void DeadMario_onProcess(cfsm_Ctx * state); /****************************************************************************** * Variables @@ -64,7 +64,7 @@ static void DeadMario_onProcess(cfsm_Fsm * state); * External functions *****************************************************************************/ -void DeadMario_onEnter(cfsm_Fsm * fsm) +void DeadMario_onEnter(cfsm_Ctx * fsm) { puts("DeadMario_onEnter()..."); @@ -77,8 +77,9 @@ void DeadMario_onEnter(cfsm_Fsm * fsm) * Local functions *****************************************************************************/ -static void DeadMario_onProcess(cfsm_Fsm * fsm) +static void DeadMario_onProcess(cfsm_Ctx * fsm) { + (void)fsm; puts("DeadMario_onProces(): He's dead Jim!"); } diff --git a/examples/mario/states/dead_mario.h b/examples/mario/states/dead_mario.h index 6430bbc..7ed7456 100644 --- a/examples/mario/states/dead_mario.h +++ b/examples/mario/states/dead_mario.h @@ -55,7 +55,7 @@ /****************************************************************************** * Functions *****************************************************************************/ -void DeadMario_onEnter(cfsm_Fsm * state); +void DeadMario_onEnter(cfsm_Ctx * state); #endif /* DEAD_MARIO_H */ diff --git a/examples/mario/states/fire_mario.c b/examples/mario/states/fire_mario.c index 87c2f6b..b444ff8 100644 --- a/examples/mario/states/fire_mario.c +++ b/examples/mario/states/fire_mario.c @@ -60,9 +60,9 @@ * Prototypes *****************************************************************************/ -static void FireMario_onEvent(cfsm_Fsm * state, int eventId); -static void FireMario_onProcess(cfsm_Fsm * state); -static void FireMario_onLeave(cfsm_Fsm * state); +static void FireMario_onEvent(cfsm_Ctx * state, int eventId); +static void FireMario_onProcess(cfsm_Ctx * state); +static void FireMario_onLeave(cfsm_Ctx * state); /****************************************************************************** * Variables @@ -72,7 +72,7 @@ static void FireMario_onLeave(cfsm_Fsm * state); * External functions *****************************************************************************/ -void FireMario_onEnter(cfsm_Fsm * fsm) +void FireMario_onEnter(cfsm_Ctx * fsm) { puts("FireMario_onEnter()..."); @@ -87,7 +87,7 @@ void FireMario_onEnter(cfsm_Fsm * fsm) * Local functions *****************************************************************************/ -void FireMario_onEvent(cfsm_Fsm * fsm, int eventId) +void FireMario_onEvent(cfsm_Ctx * fsm, int eventId) { mario_updateCoins(eventId); @@ -111,13 +111,17 @@ void FireMario_onEvent(cfsm_Fsm * fsm, int eventId) } } -void FireMario_onProcess(cfsm_Fsm * fsm) +void FireMario_onProcess(cfsm_Ctx * fsm) { + (void)fsm; + puts("FireMario_onProces(): I throw fire balls!"); } -void FireMario_onLeave(cfsm_Fsm * fsm) +void FireMario_onLeave(cfsm_Ctx * fsm) { + (void)fsm; + puts("FireMario_onLeave() ..."); } diff --git a/examples/mario/states/fire_mario.h b/examples/mario/states/fire_mario.h index b3d1d5f..a4b6302 100644 --- a/examples/mario/states/fire_mario.h +++ b/examples/mario/states/fire_mario.h @@ -55,7 +55,7 @@ /****************************************************************************** * Functions *****************************************************************************/ -void FireMario_onEnter(cfsm_Fsm * state); +void FireMario_onEnter(cfsm_Ctx * state); #endif /* FIRE_MARIO_H */ diff --git a/examples/mario/states/small_mario.c b/examples/mario/states/small_mario.c index 7c42772..0a0fae3 100644 --- a/examples/mario/states/small_mario.c +++ b/examples/mario/states/small_mario.c @@ -58,9 +58,9 @@ * Prototypes *****************************************************************************/ -static void SmallMario_onEvent(cfsm_Fsm * state, int eventId); -static void SmallMario_onProcess(cfsm_Fsm * state); -static void SmallMario_onLeave(cfsm_Fsm * state); +static void SmallMario_onEvent(cfsm_Ctx * state, int eventId); +static void SmallMario_onProcess(cfsm_Ctx * state); +static void SmallMario_onLeave(cfsm_Ctx * state); /****************************************************************************** * Variables @@ -70,7 +70,7 @@ static void SmallMario_onLeave(cfsm_Fsm * state); * External functions *****************************************************************************/ -void SmallMario_onEnter(cfsm_Fsm * fsm) +void SmallMario_onEnter(cfsm_Ctx * fsm) { puts("SmallMario_onEnter()..."); @@ -85,8 +85,10 @@ void SmallMario_onEnter(cfsm_Fsm * fsm) * Local functions *****************************************************************************/ -static void SmallMario_onEvent(cfsm_Fsm * fsm, int eventId) +static void SmallMario_onEvent(cfsm_Ctx * fsm, int eventId) { + (void)fsm; + mario_updateCoins(eventId); switch(eventId) @@ -112,13 +114,16 @@ static void SmallMario_onEvent(cfsm_Fsm * fsm, int eventId) } } -static void SmallMario_onProcess(cfsm_Fsm * fsm) +static void SmallMario_onProcess(cfsm_Ctx * fsm) { + (void)fsm; + puts("SmallMario_onProces(): It's me, Mario!"); } -static void SmallMario_onLeave(cfsm_Fsm * fsm) +static void SmallMario_onLeave(cfsm_Ctx * fsm) { + (void)fsm; puts("SmallMario_onLeave() ..."); } diff --git a/examples/mario/states/small_mario.h b/examples/mario/states/small_mario.h index 186a078..bbc982e 100644 --- a/examples/mario/states/small_mario.h +++ b/examples/mario/states/small_mario.h @@ -55,7 +55,7 @@ /****************************************************************************** * Functions *****************************************************************************/ -void SmallMario_onEnter(cfsm_Fsm * state); +void SmallMario_onEnter(cfsm_Ctx * state); #endif /* SMALL_MARIO_H */ diff --git a/examples/mario/states/super_mario.c b/examples/mario/states/super_mario.c index 5f951d8..47f295f 100644 --- a/examples/mario/states/super_mario.c +++ b/examples/mario/states/super_mario.c @@ -58,9 +58,9 @@ * Prototypes *****************************************************************************/ -static void SuperMario_onEvent(cfsm_Fsm * state, int eventId); -static void SuperMario_onProcess(cfsm_Fsm * state); -static void SuperMario_onLeave(cfsm_Fsm * state); +static void SuperMario_onEvent(cfsm_Ctx * state, int eventId); +static void SuperMario_onProcess(cfsm_Ctx * state); +static void SuperMario_onLeave(cfsm_Ctx * state); /****************************************************************************** * Variables @@ -70,7 +70,7 @@ static void SuperMario_onLeave(cfsm_Fsm * state); * External functions *****************************************************************************/ -void SuperMario_onEnter(cfsm_Fsm * fsm) +void SuperMario_onEnter(cfsm_Ctx * fsm) { puts("SuperMario_onEnter()..."); @@ -85,7 +85,7 @@ void SuperMario_onEnter(cfsm_Fsm * fsm) * Local functions *****************************************************************************/ -static void SuperMario_onEvent(cfsm_Fsm * fsm, int eventId) +static void SuperMario_onEvent(cfsm_Ctx * fsm, int eventId) { mario_updateCoins(eventId); @@ -109,13 +109,17 @@ static void SuperMario_onEvent(cfsm_Fsm * fsm, int eventId) } } -static void SuperMario_onProcess(cfsm_Fsm * fsm) +static void SuperMario_onProcess(cfsm_Ctx * fsm) { + (void)fsm; + puts("SuperMario_onProces(): It's me, SUPER Mario!"); } -static void SuperMario_onLeave(cfsm_Fsm * fsm) +static void SuperMario_onLeave(cfsm_Ctx * fsm) { + (void)fsm; + puts("SuperMario_onLeave() ..."); } diff --git a/examples/mario/states/super_mario.h b/examples/mario/states/super_mario.h index 19efdc0..c08d58d 100644 --- a/examples/mario/states/super_mario.h +++ b/examples/mario/states/super_mario.h @@ -55,7 +55,7 @@ /****************************************************************************** * Functions *****************************************************************************/ -void SuperMario_onEnter(cfsm_Fsm * state); +void SuperMario_onEnter(cfsm_Ctx * state); #endif /* SUPER_MARIO_H */ diff --git a/src/c_fsm.c b/src/c_fsm.c index 7766416..6698904 100644 --- a/src/c_fsm.c +++ b/src/c_fsm.c @@ -60,13 +60,12 @@ * External functions *****************************************************************************/ -void cfsm_init(struct cfsm_Fsm * fsm) +void cfsm_init(struct cfsm_Ctx * fsm, cfsm_InstanceDataPtr instanceData) { - *fsm = (cfsm_Fsm) {0, 0, 0, 0}; + *fsm = (cfsm_Ctx) {instanceData, 0, 0, 0, 0}; } - -void cfsm_transition(struct cfsm_Fsm * fsm, cfsm_TransitionFunction enterFunc) +void cfsm_transition(struct cfsm_Ctx * fsm, cfsm_TransitionFunction enterFunc) { /* Call former state leave operations if present. */ if ((cfsm_TransitionFunction)0 != fsm->onLeave) @@ -77,7 +76,10 @@ void cfsm_transition(struct cfsm_Fsm * fsm, cfsm_TransitionFunction enterFunc) /* Set enter function pointer and clear all other handler. They * get set by the enter function if needed. */ - *fsm = (cfsm_Fsm) { enterFunc, 0, 0, 0 }; + fsm->onEnter = enterFunc; + fsm->onEvent = (cfsm_EventFunction)0; + fsm->onLeave = (cfsm_TransitionFunction)0; + fsm->onProcess= (cfsm_ProcessFunction)0; /* Call enter function NULL checked. It might be NULL to "disable" * all FSM operations. @@ -88,7 +90,7 @@ void cfsm_transition(struct cfsm_Fsm * fsm, cfsm_TransitionFunction enterFunc) } } -void cfsm_process(struct cfsm_Fsm * fsm) +void cfsm_process(struct cfsm_Ctx * fsm) { /* Delegate to state processing operation if handler is defined. */ if ((cfsm_ProcessFunction)0 != fsm->onProcess) @@ -97,7 +99,7 @@ void cfsm_process(struct cfsm_Fsm * fsm) } } -void cfsm_event(struct cfsm_Fsm * fsm, int eventId) +void cfsm_event(struct cfsm_Ctx * fsm, int eventId) { /* Delegate to state event processing if handler is defined. */ if ((cfsm_EventFunction)0 != fsm->onEvent) diff --git a/src/c_fsm.h b/src/c_fsm.h index 90873b1..4121e7f 100644 --- a/src/c_fsm.h +++ b/src/c_fsm.h @@ -27,7 +27,7 @@ /** * @brief CFSM Header file * - * This file defines the types and functions used to implement the cfsm + * This file defines the types and functions used to implement the CFSM * pattern for finite state machines in C-language. * * Repository: https://github.com/nhjschulz/cfsm @@ -60,20 +60,42 @@ extern "C" { * Types and Classes *****************************************************************************/ -struct cfsm_Fsm; +/* forward declation for typedefs */ +struct cfsm_Ctx; -typedef void (*cfsm_TransitionFunction)(struct cfsm_Fsm * fsm); -typedef void (*cfsm_EventFunction)(struct cfsm_Fsm * fsm, int eventId); -typedef void (*cfsm_ProcessFunction)(struct cfsm_Fsm * fsm); +/** + * @brief Function pointer type for enter/leave operations. + * + */ +typedef void (*cfsm_TransitionFunction)(struct cfsm_Ctx * fsm); + +/** + * @brief Function pointer type for event signal operation. + * + */ +typedef void (*cfsm_EventFunction)(struct cfsm_Ctx * fsm, int eventId); + +/** + * @brief * @brief Function pointer type for cyclic process operation. + * + */ +typedef void (*cfsm_ProcessFunction)(struct cfsm_Ctx * fsm); + +/** + * @brief Instance data pointer as void * to accept any pointer typ. + * + */ +typedef void *cfsm_InstanceDataPtr; -/** CFSM context operations */ -typedef struct cfsm_Fsm { +/** The CFSM context data structure +*/ +typedef struct cfsm_Ctx { + cfsm_InstanceDataPtr ctxPtr; /**< context instance data */ cfsm_TransitionFunction onEnter; /**< operation run on enter */ cfsm_TransitionFunction onLeave; /**< operation run on leave */ cfsm_ProcessFunction onProcess; /**< cyclic operations */ cfsm_EventFunction onEvent; /**< report event to the state */ -} cfsm_Fsm; - +} cfsm_Ctx; /****************************************************************************** * Functions @@ -81,26 +103,33 @@ typedef struct cfsm_Fsm { /** * @brief Initialize the given fsm. - * - * @param fsm The fsm data structure + * + * Initialize a cfsm context structure by setting all handlers to NULL + * and update the instance data pointer with instanceData. Instance data + * is used if the same operation handlers are used in multiple FSM instances. + * The handlers can then access the instance data to operate on the actual + * context. + * + * @param fsm The fsm data structure to initialize. + * @param instanceData Pointer to instance data (may be NULL if unneeded). * @since 0.1.0 */ -void cfsm_init(struct cfsm_Fsm * fsm); +void cfsm_init(cfsm_Ctx * fsm, cfsm_InstanceDataPtr instanceData); /** * @brief Transition given fsm to a new state * * Perform a state transition be calling the function given by enterFunc. - * The called function is expected to update the needed state handlers in + * The called function is expected to update the state handlers in * the state structure. Unused handlers needs not to be set. * Passing NULL as enterfunc triggers the leave handler for the current - * state and clears all handlers. + * state and clears all handler which stops the FSM from doing anything. * * @param fsm The fsm data structure * @param enterFunc The enter function for the new fsm state (may be NULL) * @since 0.1.0 */ -void cfsm_transition(struct cfsm_Fsm * fsm, cfsm_TransitionFunction enterFunc); +void cfsm_transition(struct cfsm_Ctx * fsm, cfsm_TransitionFunction enterFunc); /** * @brief Execute a process cycle to the current fsm state @@ -113,18 +142,18 @@ void cfsm_transition(struct cfsm_Fsm * fsm, cfsm_TransitionFunction enterFunc); * @param fsm The fsm data structure * @since 0.1.0 */ -void cfsm_process(struct cfsm_Fsm * fsm); +void cfsm_process(struct cfsm_Ctx * fsm); /** * @brief Signal an event to the current fsm state. * * Call the onEvent handler of the current fsm state. This - * function is expected to be called when an even shall be + * function is expected to be called when an event shall be * signaled to the current state. An event is just an * application defined integer id. It has no meaning to - * the fsm itself. Events provide a method to react to - * application events when they occure instead of polling - * for them during process cycles. + * the FSM itself. Events provide a method to react to + * application events when they occure, instead of polling + * for them during process cycles. * * An example for an event id could be a UI button press * to trigger a state dependend reaction. @@ -133,7 +162,7 @@ void cfsm_process(struct cfsm_Fsm * fsm); * @param eventId An application defined ID to identify the event. * @since 0.1.0 */ -void cfsm_event(struct cfsm_Fsm * fsm, int eventId); +void cfsm_event(struct cfsm_Ctx * fsm, int eventId); #ifdef __cplusplus } diff --git a/tests/test_c_fsm.c b/tests/test_c_fsm.c index 1c53f72..3334020 100644 --- a/tests/test_c_fsm.c +++ b/tests/test_c_fsm.c @@ -60,26 +60,26 @@ typedef struct StateOperationCounter_ /****************************************************************************** * Prototypes *****************************************************************************/ -static void State_only_onEnter(cfsm_Fsm * fsm); +static void State_only_onEnter(cfsm_Ctx * fsm); -static void State_A_onEvent(cfsm_Fsm * fsm, int eventId); -static void State_A_onProcess(cfsm_Fsm * fsm); -static void State_A_onLeave(cfsm_Fsm * fsm); -static void State_A_onEnter(cfsm_Fsm * fsm); +static void State_A_onEvent(cfsm_Ctx * fsm, int eventId); +static void State_A_onProcess(cfsm_Ctx * fsm); +static void State_A_onLeave(cfsm_Ctx * fsm); +static void State_A_onEnter(cfsm_Ctx * fsm); -static void State_B_onEvent(cfsm_Fsm * fsm, int eventId); -static void State_B_onProcess(cfsm_Fsm * fsm); -static void State_B_onLeave(cfsm_Fsm * fsm); -static void State_B_onEnter(cfsm_Fsm * fsm); +static void State_B_onEvent(cfsm_Ctx * fsm, int eventId); +static void State_B_onProcess(cfsm_Ctx * fsm); +static void State_B_onLeave(cfsm_Ctx * fsm); +static void State_B_onEnter(cfsm_Ctx * fsm); /****************************************************************************** * Variables *****************************************************************************/ -static cfsm_Fsm fsm; /**< fsm instance used in tests */ +static cfsm_Ctx fsmInstance; /**< fsm instance used in tests */ static StateOperationCounter state_A; static StateOperationCounter state_B; - +static uint8_t dummyInstanceData = 42u; /****************************************************************************** * External functions @@ -87,7 +87,7 @@ static StateOperationCounter state_B; void setUp(void) { - cfsm_init(&fsm); + cfsm_init(&fsmInstance, NULL); memset(&state_A, 0, sizeof(state_A)); memset(&state_B, 0, sizeof(state_A)); @@ -104,38 +104,42 @@ void test_cfsm_version() TEST_ASSERT_EQUAL(1, CFSM_VER_PATCH); } -void test_cfsm_init_should_clear_handler() +void test_cfsm_init_should_clear_handler(void) { - memset(&fsm, -1, sizeof(fsm)); /* corrupt content */ + memset(&fsmInstance, -1, sizeof(fsmInstance)); /* corrupt content */ + + cfsm_init(&fsmInstance, &dummyInstanceData); + + TEST_ASSERT_EQUAL_PTR(NULL, fsmInstance.onEnter); + TEST_ASSERT_EQUAL_PTR(NULL, fsmInstance.onEvent); + TEST_ASSERT_EQUAL_PTR(NULL, fsmInstance.onProcess); + TEST_ASSERT_EQUAL_PTR(NULL, fsmInstance.onLeave); + + TEST_ASSERT_EQUAL_PTR(&dummyInstanceData, fsmInstance.ctxPtr); - cfsm_init(&fsm); - TEST_ASSERT_EQUAL_PTR(fsm.onEnter, NULL); - TEST_ASSERT_EQUAL_PTR(fsm.onEvent, NULL); - TEST_ASSERT_EQUAL_PTR(fsm.onProcess, NULL); - TEST_ASSERT_EQUAL_PTR(fsm.onLeave, NULL); } -void test_cfsm_init_is_safe_to_use() +void test_cfsm_init_is_safe_to_use(void) { - cfsm_init(&fsm); + cfsm_init(&fsmInstance, NULL); /* should not crash */ - cfsm_process(&fsm); - cfsm_event(&fsm, 0x12345678); + cfsm_process(&fsmInstance); + cfsm_event(&fsmInstance, 0x12345678); } -void test_cfsm_transition_should_set_enter_handler_only() +void test_cfsm_transition_should_set_enter_handler_only(void) { - cfsm_transition(&fsm, State_only_onEnter); - TEST_ASSERT_EQUAL_PTR(fsm.onEnter, State_only_onEnter); - TEST_ASSERT_EQUAL_PTR(fsm.onEvent, NULL); - TEST_ASSERT_EQUAL_PTR(fsm.onProcess, NULL); - TEST_ASSERT_EQUAL_PTR(fsm.onLeave, NULL); + cfsm_transition(&fsmInstance, State_only_onEnter); + TEST_ASSERT_EQUAL_PTR(fsmInstance.onEnter, State_only_onEnter); + TEST_ASSERT_EQUAL_PTR(fsmInstance.onEvent, NULL); + TEST_ASSERT_EQUAL_PTR(fsmInstance.onProcess, NULL); + TEST_ASSERT_EQUAL_PTR(fsmInstance.onLeave, NULL); } void test_cfs_process() { - cfsm_transition(&fsm, State_A_onEnter); + cfsm_transition(&fsmInstance, State_A_onEnter); TEST_ASSERT_EQUAL_INT(state_A.enterCalls, 1); TEST_ASSERT_EQUAL_INT(state_A.leaveCalls, 0); @@ -145,7 +149,7 @@ void test_cfs_process() for (int i = 0; i < 10; ++i) { - cfsm_process(&fsm); + cfsm_process(&fsmInstance); TEST_ASSERT_EQUAL_INT(state_A.lastEventId, i); } TEST_ASSERT_EQUAL_INT(state_A.enterCalls, 1); @@ -156,7 +160,7 @@ void test_cfs_process() void test_cfs_signalEvent() { - cfsm_transition(&fsm, State_A_onEnter); + cfsm_transition(&fsmInstance, State_A_onEnter); TEST_ASSERT_EQUAL_INT(state_A.enterCalls, 1); TEST_ASSERT_EQUAL_INT(state_A.leaveCalls, 0); @@ -165,7 +169,7 @@ void test_cfs_signalEvent() for (int i = 0; i < 10; ++i) { - cfsm_event(&fsm, i); + cfsm_event(&fsmInstance, i); } TEST_ASSERT_EQUAL_INT(state_A.enterCalls, 1); TEST_ASSERT_EQUAL_INT(state_A.leaveCalls, 0); @@ -173,14 +177,14 @@ void test_cfs_signalEvent() TEST_ASSERT_EQUAL_INT(state_A.eventCalls, 10); } -void test_cfs_transition_A_B_A() +void test_cfs_transition_A_B_A(void) { - cfsm_transition(&fsm, State_A_onEnter); + cfsm_transition(&fsmInstance, State_A_onEnter); - TEST_ASSERT_EQUAL_PTR(fsm.onEnter, State_A_onEnter); - TEST_ASSERT_EQUAL_PTR(fsm.onEvent, State_A_onEvent); - TEST_ASSERT_EQUAL_PTR(fsm.onProcess, State_A_onProcess); - TEST_ASSERT_EQUAL_PTR(fsm.onLeave, State_A_onLeave); + TEST_ASSERT_EQUAL_PTR(fsmInstance.onEnter, State_A_onEnter); + TEST_ASSERT_EQUAL_PTR(fsmInstance.onEvent, State_A_onEvent); + TEST_ASSERT_EQUAL_PTR(fsmInstance.onProcess, State_A_onProcess); + TEST_ASSERT_EQUAL_PTR(fsmInstance.onLeave, State_A_onLeave); TEST_ASSERT_EQUAL_INT(state_A.enterCalls, 1); TEST_ASSERT_EQUAL_INT(state_A.leaveCalls, 0); @@ -192,12 +196,12 @@ void test_cfs_transition_A_B_A() TEST_ASSERT_EQUAL_INT(state_B.processCalls, 0); TEST_ASSERT_EQUAL_INT(state_B.eventCalls, 0); - cfsm_transition(&fsm, State_B_onEnter); + cfsm_transition(&fsmInstance, State_B_onEnter); - TEST_ASSERT_EQUAL_PTR(fsm.onEnter, State_B_onEnter); - TEST_ASSERT_EQUAL_PTR(fsm.onEvent, State_B_onEvent); - TEST_ASSERT_EQUAL_PTR(fsm.onProcess, State_B_onProcess); - TEST_ASSERT_EQUAL_PTR(fsm.onLeave, State_B_onLeave); + TEST_ASSERT_EQUAL_PTR(fsmInstance.onEnter, State_B_onEnter); + TEST_ASSERT_EQUAL_PTR(fsmInstance.onEvent, State_B_onEvent); + TEST_ASSERT_EQUAL_PTR(fsmInstance.onProcess, State_B_onProcess); + TEST_ASSERT_EQUAL_PTR(fsmInstance.onLeave, State_B_onLeave); TEST_ASSERT_EQUAL_INT(state_A.enterCalls, 1); TEST_ASSERT_EQUAL_INT(state_A.leaveCalls, 1); @@ -209,12 +213,12 @@ void test_cfs_transition_A_B_A() TEST_ASSERT_EQUAL_INT(state_B.processCalls, 0); TEST_ASSERT_EQUAL_INT(state_B.eventCalls, 0); - cfsm_transition(&fsm, State_A_onEnter); + cfsm_transition(&fsmInstance, State_A_onEnter); - TEST_ASSERT_EQUAL_PTR(fsm.onEnter, State_A_onEnter); - TEST_ASSERT_EQUAL_PTR(fsm.onEvent, State_A_onEvent); - TEST_ASSERT_EQUAL_PTR(fsm.onProcess, State_A_onProcess); - TEST_ASSERT_EQUAL_PTR(fsm.onLeave, State_A_onLeave); + TEST_ASSERT_EQUAL_PTR(fsmInstance.onEnter, State_A_onEnter); + TEST_ASSERT_EQUAL_PTR(fsmInstance.onEvent, State_A_onEvent); + TEST_ASSERT_EQUAL_PTR(fsmInstance.onProcess, State_A_onProcess); + TEST_ASSERT_EQUAL_PTR(fsmInstance.onLeave, State_A_onLeave); TEST_ASSERT_EQUAL_INT(state_A.enterCalls, 2); TEST_ASSERT_EQUAL_INT(state_A.leaveCalls, 1); @@ -242,11 +246,12 @@ int main(void) /****************************************************************************** * Local functions *****************************************************************************/ -static void State_only_onEnter(cfsm_Fsm * fsm) +static void State_only_onEnter(cfsm_Ctx * fsm) { + (void)fsm; } -static void State_A_onEnter(cfsm_Fsm * fsm) +static void State_A_onEnter(cfsm_Ctx * fsm) { fsm->onEvent = State_A_onEvent; fsm->onLeave = State_A_onLeave; @@ -255,22 +260,29 @@ static void State_A_onEnter(cfsm_Fsm * fsm) state_A.enterCalls++; } -static void State_A_onEvent(cfsm_Fsm * fsm, int eventId) +static void State_A_onEvent(cfsm_Ctx * fsm, int eventId) { + (void)fsm; + (void)eventId; + state_A.eventCalls++; } -static void State_A_onProcess(cfsm_Fsm * fsm) +static void State_A_onProcess(cfsm_Ctx * fsm) { + (void)fsm; + state_A.processCalls++; } -static void State_A_onLeave(cfsm_Fsm * fsm) +static void State_A_onLeave(cfsm_Ctx * fsm) { + (void)fsm; + state_A.leaveCalls++; } -static void State_B_onEnter(cfsm_Fsm * fsm) +static void State_B_onEnter(cfsm_Ctx * fsm) { fsm->onEvent = State_B_onEvent; fsm->onLeave = State_B_onLeave; @@ -278,17 +290,24 @@ static void State_B_onEnter(cfsm_Fsm * fsm) state_B.enterCalls++; } -static void State_B_onEvent(cfsm_Fsm * fsm, int eventId) +static void State_B_onEvent(cfsm_Ctx * fsm, int eventId) { + (void)fsm; + (void)eventId; + state_B.eventCalls++; } -static void State_B_onProcess(cfsm_Fsm * fsm) +static void State_B_onProcess(cfsm_Ctx * fsm) { + (void)fsm; + state_B.processCalls++; } -static void State_B_onLeave(cfsm_Fsm * fsm) +static void State_B_onLeave(cfsm_Ctx * fsm) { + (void)fsm; + state_B.leaveCalls++; }