Skip to content

Commit

Permalink
BREAKING CHANGE: State hooks onEnter, onExit now calls only if nextSt…
Browse files Browse the repository at this point in the history
…ate differs from currentState
  • Loading branch information
polesskiy-dev committed Sep 18, 2023
1 parent c745cde commit e70712e
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 69 deletions.
16 changes: 8 additions & 8 deletions src/active_object/active_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* ### Example:
* @code
* // Active Object inheritance
* typedef enum { NO_STATE, STATE_1 = 1, STATES_MAX } TEST_STATE; // state names
* typedef enum { INVALID_STATE = -1, NO_STATE, STATE_1 = 1, STATES_MAX } TEST_STATE; // state names
* typedef enum { NO_SIG, EVENT_SIG_1 = 1, EVENTS_MAX } TEST_EVENT_SIG; // event signals names
* TEvent events[QUEUE_MAX_SIZE]; // events list for the queue
*
Expand Down Expand Up @@ -66,17 +66,17 @@ typedef bool (*TStateHook)(TActiveObject *const activeObject, void *const ctx);

/** @brief Struct representing a single state of an active object. */
typedef struct {
int32_t name;
TStateHook onEnter;
TStateHook onTraverse;
TStateHook onExit;
int name; /**< State name. */
TStateHook onEnter; /**< State onEnter hook. If the next state is the same as the current state, this hook won't be called. */
TStateHook onTraverse; /**< State onTraverse hook. If next state is the same as current state, this hook will be called. */
TStateHook onExit; /**< State onExit hook. If the next state is the same as the current state, this hook won't be called. */
} TState;

/** @brief Struct representing an active object. */
struct TActiveObject {
uint8_t id;
const TState *state;
TEventQueue queue;
uint8_t id; /**< Object ID. */
const TState *state; /**< Pointer to the current state. */
TEventQueue queue; /**< Event queue. */
};

/** @brief Initialize an active object.
Expand Down
1 change: 0 additions & 1 deletion src/event_queue/event_queue.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
#include <stdbool.h>
#include <stddef.h>


/**
* @brief Event structure containing a signal and payload
*/
Expand Down
88 changes: 44 additions & 44 deletions src/fsm/fsm.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,32 @@ inline bool FSM_IsValidState(const TState *const state) {
};

const TState *FSM_ProcessEventToNextState(
TActiveObject *const activeObject,
TEvent event,
uint32_t statesMax,
uint32_t eventsMax,
const TState statesList[statesMax],
const TEventHandler transitionTable[statesMax][eventsMax]) {
TActiveObject *const activeObject,
TEvent event,
uint32_t statesMax,
uint32_t eventsMax,
const TState statesList[statesMax],
const TEventHandler transitionTable[statesMax][eventsMax]) {

/* Validate input args */
/* Validate input args */

// Validate Active object
if (NULL == activeObject
|| NULL == statesList
|| NULL == transitionTable) return &invalidState; // Handle null pointers as needed
if (NULL == activeObject
|| NULL == statesList
|| NULL == transitionTable)
return &invalidState; // Handle null pointers as needed

const TState *const currState = activeObject->state;

// Validate current state
if (currState->name < 0 || currState->name >= statesMax) return &invalidState;

// Validate current event
if (event.sig < 0 || event.sig >= eventsMax) return &invalidState;

// Lookup transition table to find the handler for the current state and event
const TEventHandler eventHandler = transitionTable[currState->name][event.sig];

// Call the handler to get the next state and make side effects
if (eventHandler) {
const TState *nextState = eventHandler(activeObject, event);
Expand All @@ -46,49 +47,48 @@ const TState *FSM_ProcessEventToNextState(
}

// Return empty state if no transition exists
return &emptyState;
return &emptyState;
};

bool FSM_TraverseNextState(
TActiveObject *const activeObject,
const TState *const nextState) {
// Execute a state hook and return its status.
bool _executeHook(TStateHook hook, TActiveObject *activeObject) {
if (hook) {
return hook(activeObject, NULL);
}
return true; // No hook to execute, so consider it successful
}

// Null args checks
if (NULL == activeObject
|| NULL == nextState) {
bool FSM_TraverseNextState(
TActiveObject *const activeObject,
const TState *const nextState) {
// Null args checks
if (!activeObject || !nextState) {
return false;
};
}

// Validate next state
if (!FSM_IsValidState(nextState)) {
return false;
};

// Call onExit of current state, if any
if (NULL != activeObject->state->onExit) {
bool isSuccessfulHook = activeObject->state->onExit(activeObject, NULL);
if (!isSuccessfulHook) {
return false; // Hook failed
}
}

// Update the state
activeObject->state = (TState *)nextState;

// Call onEnter of next state, if any
if (activeObject->state->onEnter) {
bool isSuccessfulHook = activeObject->state->onEnter(activeObject, NULL);
if (!isSuccessfulHook) {
return false; // Hook failed
}
const TState *currState = &(*activeObject->state);

// If the next state is the same as the current state
if (FSM_IsEqualStates(currState, nextState)) {
return _executeHook(currState->onTraverse, activeObject);
}

// If the next state is different, execute hooks for transitioning
if (!_executeHook(currState->onExit, activeObject)) {
return false;
}

// Finally, call onTraverse of next state, if any
if (activeObject->state->onTraverse) {
bool isSuccessfulHook = activeObject->state->onTraverse(activeObject, NULL);
if (!isSuccessfulHook) {
return false; // Hook failed
}
// Update the state
activeObject->state = (TState *) nextState;

if (!_executeHook(activeObject->state->onEnter, activeObject) ||
!_executeHook(activeObject->state->onTraverse, activeObject)) {
return false;
}

return true;
Expand Down
38 changes: 26 additions & 12 deletions src/fsm/fsm.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ typedef const TState* (*TEventHandler)(TActiveObject *const activeObject, TEvent
* @returns -1 (INVALID_STATE) in case of invalid input args
*/
const TState *FSM_ProcessEventToNextState(
TActiveObject *const activeObject,
TEvent event,
uint32_t statesMax,
uint32_t eventsMax,
const TState statesList[statesMax],
const TEventHandler transitionTable[statesMax][eventsMax]);
TActiveObject *const activeObject, /**< The active object. */
TEvent event, /**< The incoming event. */
uint32_t statesMax, /**< The maximum number of states. */
uint32_t eventsMax, /**< The maximum number of events. */
const TState statesList[statesMax], /**< The list of states. */
const TEventHandler transitionTable[statesMax][eventsMax]); /**< The transition table for state-event. */

/**
* @brief Transitions the active object to the next state
Expand All @@ -48,8 +48,6 @@ const TState *FSM_ProcessEventToNextState(
*
* @param[in,out] activeObject The active object.
* @param[in] nextState The next state to transition to.
* @param[in] statesMax The maximum number of states.
* @param[in] statesList The list of states.
*
* @return True if the transition is successful, false otherwise.
*/
Expand All @@ -63,12 +61,28 @@ bool FSM_IsEqualStates(const TState *const stateA, const TState *const stateB);
/** @brief Checks if a state is valid (i.e., not an empty or invalid state). */
bool FSM_IsValidState(const TState *const state);

#define DECLARE_GUARD(CONDITION_FUNCTION, ON_TRUE_FUNCTION) \
const TState* GUARD_##CONDITION_FUNCTION##_##ON_TRUE_FUNCTION(TActiveObject *const activeObject, TEvent event) { \
if (CONDITION_FUNCTION(activeObject, event)) return ON_TRUE_FUNCTION(activeObject, event); \
/**
* @brief Guard function declaration macro
* @note should be invoked before using appropriate GUARD macro in transition table
* @details Creates a guard function for a given condition function and two event handler functions.
* For true condition result the first event handler function will be called, for false - the second one.
*
* ### Example
* @code
* TODO make an example
* @endcode
*/
#define DECLARE_GUARD(CONDITION_FUNCTION, ON_TRUE_FUNCTION, ON_FALSE_FUNCTION) \
const TState* GUARD_##CONDITION_FUNCTION##_##ON_TRUE_FUNCTION##_##ON_FALSE_FUNCTION(TActiveObject *const activeObject, TEvent event) { \
if (CONDITION_FUNCTION(activeObject, event)) return ON_TRUE_FUNCTION(activeObject, event); \
return ON_FALSE_FUNCTION(activeObject, event); \
return (const TState*)NULL; \
};

#define GUARD(CONDITION_FUNCTION, ON_TRUE_FUNCTION) (GUARD_##CONDITION_FUNCTION##_##ON_TRUE_FUNCTION)
/**
* @brief Guard function macro
* @note DECLARE_GUARD should be invoked before using appropriate GUARD macro in transition table
*/
#define GUARD(CONDITION_FUNCTION, ON_TRUE_FUNCTION, ON_FALSE_FUNCTION) GUARD_##CONDITION_FUNCTION##_##ON_TRUE_FUNCTION##_##ON_FALSE_FUNCTION

#endif //FSM_H
8 changes: 4 additions & 4 deletions test/fsm/fsm.test.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
typedef enum { NO_STATE, EMPTY_HOOKS_ST, SUCCESS_HOOKS_ST, FAILURE_HOOKS_ST, STATES_MAX } STATES_NAMES; // state names
typedef enum { NO_SIG, GO_EMPTY_HOOKS_ST, GO_SUCCESS_HOOKS_ST, GO_FAILURE_HOOKS_ST, EVENTS_MAX } EVENT_SIGS; // events signals names

bool _successHook(void *const activeObject, void *const ctx) { return true; };
bool _failureHook(void *const activeObject, void *const ctx) { return false; };
bool _successHook(TActiveObject *const AO, void *const ctx) { return true; };
bool _failureHook(TActiveObject *const AO, void *const ctx) { return false; };

const TState statesList[STATES_MAX] = {
[NO_STATE] = {.name = NO_STATE},
Expand All @@ -24,12 +24,12 @@ const TState* _goToFailureHooksState(TActiveObject *const activeObject, TEvent e

bool _onTrue(TActiveObject *const activeObject, TEvent event) { return true; };

DECLARE_GUARD(_onTrue, _goToFailureHooksState);
DECLARE_GUARD(_onTrue, _goToFailureHooksState, _goToSuccessHooksState);

const TEventHandler transitionTable[STATES_MAX][EVENTS_MAX] = {
[NO_STATE] = { [GO_EMPTY_HOOKS_ST] = _goToEmmptyHooksState },
[EMPTY_HOOKS_ST] = { [GO_SUCCESS_HOOKS_ST] = _goToSuccessHooksState },
[SUCCESS_HOOKS_ST] = { [GO_FAILURE_HOOKS_ST] = GUARD(_onTrue, _goToFailureHooksState) },
[SUCCESS_HOOKS_ST] = { [GO_FAILURE_HOOKS_ST] = GUARD(_onTrue, _goToFailureHooksState, _goToSuccessHooksState) },
[FAILURE_HOOKS_ST] = { [GO_SUCCESS_HOOKS_ST] = _goToSuccessHooksState },
};

Expand Down

0 comments on commit e70712e

Please sign in to comment.