Skip to content

Commit

Permalink
fsm: replace error-prone lack of transition f assert by returnin 0 ST…
Browse files Browse the repository at this point in the history
…ATE, active-object: replace emptyQueueCb by adding implicit HasEmptyQueue, move basicTransition f to fsm
  • Loading branch information
polesskiy-dev committed Aug 23, 2023
1 parent c2b2996 commit af49a48
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 40 deletions.
30 changes: 21 additions & 9 deletions src/active-object/active_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@
* @tparam id ID value
* @tparam maxQueueCapacity Queue capacity value
*
* @example DECLARE_ACTIVE_OBJECT(MY_ACTIVE_OBJECT, MY_EVENT, MY_STATE, MY_FIELDS, 16)
* ###Example:
* @code
* DECLARE_ACTIVE_OBJECT(MY_ACTIVE_OBJECT, MY_EVENT, MY_STATE, MY_FIELDS, 16)
* @endcode
*/
#define DECLARE_ACTIVE_OBJECT(ACTIVE_OBJECT_T, EVENT_T, STATE_T, FIELDS_T, maxQueueCapacity) \
DECLARE_QUEUE(EVENT_T, maxQueueCapacity); \
Expand All @@ -67,8 +70,9 @@
\
void ACTIVE_OBJECT_T##_Ctor(ACTIVE_OBJECT_T *const self, uint8_t id, STATE_T initialState, FIELDS_T fields); \
void ACTIVE_OBJECT_T##_Dispatch(ACTIVE_OBJECT_T *const self, EVENT_T event); \
bool ACTIVE_OBJECT_T##_HasEmptyQueue(ACTIVE_OBJECT_T *const self); \
bool ACTIVE_OBJECT_T##_basicTransitionToNextState(ACTIVE_OBJECT_T *const self, STATE_T nextState); \
void ACTIVE_OBJECT_T##_ProcessQueue(ACTIVE_OBJECT_T *const self, EVENT_T##_HANDLER_F eventHandlerCb, STATE_T##_TRANSITION_F transitionToNextStateCb, ACTIVE_OBJECT_T##HAS_EMPTY_QUEUE_F hasEmptyQueueCb);
void ACTIVE_OBJECT_T##_ProcessQueue(ACTIVE_OBJECT_T *const self, EVENT_T##_HANDLER_F eventHandlerCb, STATE_T##_TRANSITION_F transitionToNextStateCb);

/**
* @struct ACTIVE_OBJECT_T
Expand All @@ -90,7 +94,7 @@
* @param initialState Initial state of the Active Object
* @param fields User-defined fields
*
* ####Example:
* ###Example:
* @code
* // Create a new active object:
* MY_ACTIVE_OBJECT myObject;
Expand All @@ -105,7 +109,7 @@ void ACTIVE_OBJECT_T_Ctor(ACTIVE_OBJECT_T *const self, uint8_t id, STATE_T initi
* @param self Pointer to the Active Object
* @param event Event to be dispatched
*
* ####Example:
* ###Example:
* @code
* // Dispatching an event to the active object:
* MY_EVENT myEvent = { .sig=SOME_SIG };
Expand All @@ -114,6 +118,15 @@ void ACTIVE_OBJECT_T_Ctor(ACTIVE_OBJECT_T *const self, uint8_t id, STATE_T initi
*/
void ACTIVE_OBJECT_T##_Dispatch(ACTIVE_OBJECT_T *const self, EVENT_T event);

/**
* @brief Check Active Object queue empty
*
* @param self Pointer to the Active Object
*
* @return is queue empty
*/
bool ACTIVE_OBJECT_T##_HasEmptyQueue(ACTIVE_OBJECT_T *const self);

/**
* @brief Transition to the next state of the Active Object
*
Expand All @@ -122,7 +135,7 @@ void ACTIVE_OBJECT_T##_Dispatch(ACTIVE_OBJECT_T *const self, EVENT_T event);
*
* TODO should be improved, temporary inline, handle here exit -> enter -> traverse for states, implementing Mealy&Moore Hybrid FSM
*
* ####Example:
* ###Example:
* @code
* // Transition to a new state:
* MY_STATE newState = ...;
Expand All @@ -137,15 +150,14 @@ bool ACTIVE_OBJECT_T_basicTransitionToNextState(ACTIVE_OBJECT_T *const self, STA
* @param self Pointer to the Active Object
* @param eventHandlerCb Callback to handle queue event, returns next state
* @param transitionToNextStateCb Callback to transition to the next state
* @param hasEmptyQueueCb Callback to handle empty queue
*
* ####Example:
* ###Example:
* @code
* // Processing the event queue:
* MY_ACTIVE_OBJECT_ProcessQueue(&myObject, myEventHandler, myTransitionCb, myEmptyQueueHandler); \
* if (!MY_ACTIVE_OBJECT_HasEmptyQueue(&myObject)) MY_ACTIVE_OBJECT_ProcessQueue(&myObject, myEventHandler, myTransitionCb); \
* @endcode
*/
void ACTIVE_OBJECT_T_ProcessQueue(ACTIVE_OBJECT_T *const self, EVENT_T##_HANDLER_F eventHandlerCb, STATE_T_TRANSITION_F transitionToNextStateCb, ACTIVE_OBJECT_T_HAS_EMPTY_QUEUE_F hasEmptyQueueCb)
void ACTIVE_OBJECT_T_ProcessQueue(ACTIVE_OBJECT_T *const self, EVENT_T##_HANDLER_F eventHandlerCb, STATE_T_TRANSITION_F transitionToNextStateCb)

#endif

Expand Down
13 changes: 8 additions & 5 deletions src/active-object/active_object_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,17 @@
return true; \
} \
\
bool ACTIVE_OBJECT_T##_HasEmptyQueue(ACTIVE_OBJECT_T *const self) {\
return EMPTY_QUEUE == QUEUE_##EVENT_T##_GetSize(&self->queue); \
}\
\
void ACTIVE_OBJECT_T##_ProcessQueue( \
ACTIVE_OBJECT_T *const self, \
EVENT_T##_HANDLER_F eventHandlerCb, \
STATE_T##_TRANSITION_F transitionToNextStateCb, \
ACTIVE_OBJECT_T##HAS_EMPTY_QUEUE_F hasEmptyQueueCb) { \
bool isEmptyQueue = EMPTY_QUEUE == QUEUE_##EVENT_T##_GetSize(&self->queue); \
/* TODO implement check for hasEmptyQueueCb existance */ \
if (isEmptyQueue) return hasEmptyQueueCb(self); \
STATE_T##_TRANSITION_F transitionToNextStateCb) { \
/* TODO debug log it, just double check to no dequeue empty queue */ \
if (ACTIVE_OBJECT_T##_HasEmptyQueue(self)) return; \
\
EVENT_T e = QUEUE_##EVENT_T##_Dequeue(&self->queue); \
STATE_T nextState = eventHandlerCb(self, e); \
transitionToNextStateCb(self, nextState); \
Expand Down
32 changes: 30 additions & 2 deletions src/fsm/fsm.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ STATE_T ACTIVE_OBJECT_T##_FSM_ProcessEventToNextState(\
EVENT_T event, \
EVENT_T##_HANDLE_F transitionTable[statesMax][eventsMax], \
STATE_T##_HANDLE_FUNCTIONS stateHandlersList[statesMax] \
);
);\
bool ACTIVE_OBJECT_T##_FSM_BasicTransitionToNextState(ACTIVE_OBJECT_T *const activeObject, STATE_T nextState);\

/**
* @def DECLARE_GUARD(ACTIVE_OBJECT_T, EVENT_T, CONDITION_FUNCTION, ON_TRUE_FUNCTION, ON_FALSE_FUNCTION)
Expand All @@ -67,6 +68,11 @@ STATE_T ACTIVE_OBJECT_T##_FSM_ProcessEventToNextState(\
* @tparam CONDITION_FUNCTION The function to evaluate the guard condition.
* @tparam ON_TRUE_FUNCTION The function to be called if the guard condition evaluates to true.
* @tparam ON_FALSE_FUNCTION The function to be called if the guard condition evaluates to false.
*
* ###Example:
* @code
* // TODO
* @endcode
*/
#define DECLARE_GUARD(ACTIVE_OBJECT_T, EVENT_T, CONDITION_FUNCTION, ON_TRUE_FUNCTION, ON_FALSE_FUNCTION) \
REQUEST_STATE GUARD_##CONDITION_FUNCTION##_##ON_TRUE_FUNCTION##_##ON_FALSE_FUNCTION(ACTIVE_OBJECT_T *const activeObject, EVENT_T event) { \
Expand All @@ -79,6 +85,11 @@ STATE_T ACTIVE_OBJECT_T##_FSM_ProcessEventToNextState(\
* @def GUARD(CONDITION_FUNCTION, ON_TRUE_FUNCTION, ON_FALSE_FUNCTION)
* @brief Convenience macro to represent a specific guard function.
* @details This macro simplifies the invocation of the declared guard function based on the provided CONDITION_FUNCTION, ON_TRUE_FUNCTION, and ON_FALSE_FUNCTION.
*
* ###Example:
* @code
* // TODO
* @endcode
*/
#define GUARD(CONDITION_FUNCTION, ON_TRUE_FUNCTION, ON_FALSE_FUNCTION) (GUARD_##CONDITION_FUNCTION##_##ON_TRUE_FUNCTION##_##ON_FALSE_FUNCTION)

Expand All @@ -92,10 +103,27 @@ STATE_T ACTIVE_OBJECT_T##_FSM_ProcessEventToNextState(\
* @param event[in] - new event
* @param transitionTable[in] - event handlers functions 2D array
* @param stateHandleFunctionsList[in] - state handle functions array
* @return next state
* @return next state or 0 in case of state handler lack in transitionTable
*/
STATE_T ACTIVE_OBJECT_T_FSM_ProcessEventToNextState(ACTIVE_OBJECT_T *const activeObject, EVENT_T event, EVENT_T_HANDLE_F transitionTable[statesMax][eventsMax])

/**
* @brief Transition Active Object to the next state
*
* @param self Pointer to the Active Object
* @param nextState Next state to transition to
*
* @return whether transition succeed
*
* ###Example:
* @code
* // Transition to a new state:
* MY_STATE newState = ...;
* MY_ACTIVE_OBJECT_FSM_BasicTransitionToNextState(&myObject, newState);
* @endcode
*/
bool ACTIVE_OBJECT_T##_FSM_BasicTransitionToNextState(ACTIVE_OBJECT_T *const activeObject, STATE_T nextState);

#endif

#endif //FSM_H
5 changes: 3 additions & 2 deletions src/fsm/fsm_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ STATE_T ACTIVE_OBJECT_T##_FSM_ProcessEventToNextState(\
) { \
STATE_T currState = activeObject->state; \
EVENT_T##_HANDLE_F stateHandler = transitionTable[currState][event.sig]; \
\
assert(NULL != stateHandler); \
\
/* try to avoid lack of state handlers, returns 0 (NO_STATE) */ \
if(NULL == stateHandler) return (STATE_T){0}; \
\
STATE_T nextState = stateHandler(activeObject, event); \
\
Expand Down
16 changes: 8 additions & 8 deletions src/queue/queue.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* It utilizes a fixed-size array and employs the concept of wrapping around the indices to achieve a circular behavior.
* The circular nature allows efficient utilization of space without wasting memory.
*
* * ###Example:
* ### Example:
* @code
* #include "queue.h"
* #include "queue_impl.h"
Expand Down Expand Up @@ -40,7 +40,7 @@
* @tparam T The data type of the elements in the queue.
* @tparam maxQueueCapacity max queue capacity (size), should always be a power of 2, not more than 128 (int8)
*
* ####Example:
* ### Example:
* @code
* // Declare a queue for integers with a maximum capacity of 10
* DECLARE_QUEUE(int, 10);
Expand Down Expand Up @@ -76,7 +76,7 @@
*
* @param q Pointer to the queue to be initialized.
*
* ####Example:
* #### Example:
* @code
* QUEUE_int q;
* QUEUE_int_Ctor(&q);
Expand All @@ -92,7 +92,7 @@ void QUEUE_T_Ctor(QUEUE_T *const q);
*
* @return `true` if element enqueued (queue wasn't full), `false` otherwise.
*
* ####Example:
* ### Example:
* @code
* QUEUE_int q;
* QUEUE_int_Ctor(&q);
Expand All @@ -107,7 +107,7 @@ bool QUEUE_T_Enqueue(QUEUE_T *const q, T item);
* @param q Pointer to the queue.
* @return The dequeued item or NULL
*
* ####Example:
* ### Example:
* @code
* QUEUE_int q;
* QUEUE_int_Ctor(&q);
Expand All @@ -123,7 +123,7 @@ T QUEUE_T_Dequeue(QUEUE_T *const q);
* @param q Pointer to the queue.
* @return The current size of the queue.
*
* ####Example:
* ### Example:
* @code
* QUEUE_int q;
* QUEUE_int_Ctor(&q);
Expand All @@ -139,7 +139,7 @@ int QUEUE_T_GetSize(QUEUE_T *const q);
* @param q Pointer to the queue.
* @return The element at the front of the queue or NULL
*
* ####Example:
* #### Example:
* @code
* QUEUE_int q;
* QUEUE_int_Ctor(&q);
Expand All @@ -155,7 +155,7 @@ T QUEUE_T_Peek(QUEUE_T *const q)
* @param q Pointer to the queue.
* @return `true` if the queue is full, `false` otherwise.
*
* ####Example:
* ### Example:
* @code
* QUEUE_int q;
* QUEUE_int_Ctor(&q);
Expand Down
26 changes: 12 additions & 14 deletions test/active-object/active_object.test.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,41 +62,39 @@ void test_dispatchAndProcessQueue(void) {

// Dispatch TEST_EVENT_A and process it
TestActiveObject_Dispatch(&obj, TEST_EVENT_A);
TestActiveObject_ProcessQueue(&obj, handleEvent, transitionToState, handleEmptyQueue);
TestActiveObject_ProcessQueue(&obj, handleEvent, transitionToState);

TEST_ASSERT_EQUAL(TEST_STATE_RUNNING, obj.state);

// Dispatch TEST_EVENT_B and process it
TestActiveObject_Dispatch(&obj, TEST_EVENT_B);
TestActiveObject_ProcessQueue(&obj, handleEvent, transitionToState, handleEmptyQueue);
TestActiveObject_ProcessQueue(&obj, handleEvent, transitionToState);

TEST_ASSERT_EQUAL(TEST_STATE_PAUSED, obj.state);
}

void test_basicTransitionToNextState(void) {
void test_hasEmptyQueue(void) {
TestActiveObject obj;
TestFields fields = { .value = 10 };
TestActiveObject_Ctor(&obj, 1, TEST_STATE_IDLE, fields);

// Assert initial state
TEST_ASSERT_EQUAL(TEST_STATE_IDLE, obj.state);
// Initially the queue should be empty
TEST_ASSERT_TRUE(TestActiveObject_HasEmptyQueue(&obj));

// Test transitioning to TEST_STATE_RUNNING
bool transitioned = TestActiveObject_basicTransitionToNextState(&obj, TEST_STATE_RUNNING);
TEST_ASSERT_TRUE(transitioned);
TEST_ASSERT_EQUAL(TEST_STATE_RUNNING, obj.state);
// Add an item to the queue and check again
TestActiveObject_Dispatch(&obj, TEST_EVENT_A);
TEST_ASSERT_FALSE(TestActiveObject_HasEmptyQueue(&obj));

// Test transitioning to TEST_STATE_PAUSED
transitioned = TestActiveObject_basicTransitionToNextState(&obj, TEST_STATE_PAUSED);
TEST_ASSERT_TRUE(transitioned);
TEST_ASSERT_EQUAL(TEST_STATE_PAUSED, obj.state);
// Process the queue, it should become empty again
TestActiveObject_ProcessQueue(&obj, handleEvent, transitionToState);
TEST_ASSERT_TRUE(TestActiveObject_HasEmptyQueue(&obj));
}

int main(void) {
UNITY_BEGIN();
RUN_TEST(test_constructor);
RUN_TEST(test_dispatchAndProcessQueue);
RUN_TEST(test_basicTransitionToNextState);
RUN_TEST(test_hasEmptyQueue);
UNITY_END();

return 0;
Expand Down

0 comments on commit af49a48

Please sign in to comment.