#ifndef FUNCTOR_SEQUENCER_H #define FUNCTOR_SEQUENCER_H #include #include // // Functor Sequencer v1.0 // // Sychov A., jul/2019 // // The functor sequencer. // This module implements the model of abstract programming (behaviour) that // implying a set of abstract states are switching sequently using so called // functors. The object that involves an user-defined context, user-defined // set of abstract states is called "sequencer". Each functor represents either // the only user-defined function with user context, or a set of such functions. // The set (@fFSeqEntry_t) consist of the user-defined function with user // context and two special user-defined functions - "enter" and "leave" - // that are called on the state changes. The state can be changed in two ways: // internally by the function itself and externally by calling module function // "Startup". The first way is the main and the only correct method of chaning // state of the running sequencer. The other method is should only used to start // up the sequencer with entering the specified state after constructing of a // sequencer object. To switch of running sequencer the "Shutdown" method is used. // The method "Dispatch" provides the sequencer operation and must be called // externally to make the sequencer to perform it's function. // The "entry" and "exit" functions are called automatically by the sequencer // if the functions are enabled by setting @FSEQ_ENTRY_EXIT_SUPPORT to 1. // The "entry" function has a prototype @fSeqEnter_t. // The "leave" function has a prototype @fSeqLeave_t. #define FSEQ_ENTRY_EXIT_SUPPORT 1 // @FSEQ_ALT_INHERIT_METHOD_SUPPORT // Each state may have it's own private context. This macro chooses the method // of accessing to the private context. // If @FSEQ_ALT_INHERIT_METHOD_SUPPORT is zero (Method #0): // the state function body can get access to the state private context using casting // the pointer @fFSeqEntry_t* to the pointer to container: // const struct { fFSeqEntry_t state; void * ctx_ref_ven; } stateContainer; // This method uses the @ctx_ref_ven constant pointer a.k.a veneer, that refers // to the actual private context: // const fFSeqEntry_t * stateFunction( const fFSeqEntry_t * this, // tFSeqCtx_t ctx, const fFSeqEntry_t * * pnext ) // { // // get access to the @ctx_ref_ven field from @stateContainer structure // const void * const_ctx_ref_ven = (const void*)(&this[1]); // ... // void * private_ctx = (void*)const_ctx_ref; // cast the pointer // ... use @private_ctx // } // Advantage: the @ctx_ref_ven is constant and refers to the private context directly, // easy to manage the private context for each state, easy code support, // you do not need to keep the state pointer into the RAM. // Disadvantage: the same, the @ctx_ref_ven is constant, and if you need multiple instances // of the the same state, you need to create multiply copies of the state entry, // due to you need to change @ctx_ref_ven field. // // If @FSEQ_ALT_INHERIT_METHOD_SUPPORT is non-zero (Method #1): // Alternative method. The state function body can get access to the state private context // the same way, using casting the pointer @fFSeqEntry_t* to the pointer to container, but // the container must be declared in the RAM: // typedef struct { ... } tPrivateContextData_t; // struct { fFSeqEntry_t state; tPrivateContextData_t data; } stateContainer; // // ... // const fFSeqEntry_t * stateFunction( const fFSeqEntry_t * this, // tFSeqCtx_t ctx, const fFSeqEntry_t * * pnext ) // { // // get access to the actual private context @private_ctx (field from @stateContainer structure) // tPrivateContextData_t * private_ctx_data = (void*)(&this[1]); // ... // ... use @private_ctx_data // } // // Advantage: since the @stateContainer is located in the RAM, you don't need a veneer, // the private fields are located directly "after" the state entry in the memory, // you can easily to add new instances of the state by creating new container. // Disadvantage: each container for state instance must contain the state entry pointer, that // is located in the RAM, also the code support is more complicated due to // you need to link the dynamic container and the static state entry. // // Internally, the method #0 is quite differ than the method #1, because the state entry actually // does not contain any state functions in the method #1 but only the virtual table pointer, // and in the method #0 the state entry consist of the state functions itself. It is a important // point to understand how it works and how to inherit and embedd the state entry in your application. // But if you do not need a private context, it is does not matter which method you choose due to // the interface of the module does not depend on this issue. #define FSEQ_ALT_INHERIT_METHOD_SUPPORT 1 // User is allowed to pass some user-dependent context via special parameter // of type "tFSeqCtx_t". This argument must be set on constructing the // sequencer object during the constructing function call (@fSeqConstruct). typedef void * tFSeqCtx_t; // user context // @fFSeqEntry_t (forward declaration) // The set of user-defined functions (see below) struct fFSeqEntry_t; typedef const struct fFSeqEntry_t * ( * fSeq_t)( const struct fFSeqEntry_t * this, tFSeqCtx_t ctx, const struct fFSeqEntry_t * * pnext ); #if FSEQ_ENTRY_EXIT_SUPPORT typedef void ( * fSeqEnter_t )( const struct fFSeqEntry_t * this, tFSeqCtx_t ctx ); typedef void ( * fSeqLeave_t )( const struct fFSeqEntry_t * this, tFSeqCtx_t ctx ); #endif #if FSEQ_ALT_INHERIT_METHOD_SUPPORT // The user-defined state methods. // Is only used when FSEQ_ALT_INHERIT_METHOD_SUPPORT is enabled. // The structure represents the state virtual table methods. typedef struct fFSeqVTable_t { fSeq_t f; // function body #if FSEQ_ENTRY_EXIT_SUPPORT // fSeqEnter_t enter; // entry-function fSeqLeave_t leave; // exit-function #endif } fFSeqVTable_t; #endif // @fFSeqEntry_t // The user-defined state entry. // Can either contain the user-defined methods itself, or // contain only reference to the methods table (see @FSEQ_ALT_INHERIT_METHOD_SUPPORT) // User can use "inheritance" method to declare some // additional state-dependent context, see the example below. typedef struct fFSeqEntry_t { #if FSEQ_ALT_INHERIT_METHOD_SUPPORT const fFSeqVTable_t * vtbl; // state virtual methods table reference #else fSeq_t f; // function body #if FSEQ_ENTRY_EXIT_SUPPORT // fSeqEnter_t enter; // entry-function fSeqLeave_t leave; // exit-function #endif #endif } fFSeqEntry_t; #if FSEQ_ALT_INHERIT_METHOD_SUPPORT == 0 // @DECLARE_FSEQ_STATE_ENTRY_WITH_CONTEXT // Declares user-defined state for sequencer // @name - the name of the state; // @ctx_var - user context variable (structure) // @... - va_arg arguments: state functions list (body, entry, exit) #define DECLARE_FSEQ_STATE_ENTRY_WITH_CONTEXT(statename, ctx_var, ...)\ const struct {\ fFSeqEntry_t stateEntry;\ void * ctx_ref;\ } statename = {\ .stateEntry = { __VA_ARGS__ },\ .ctx_ref = &ctx_var\ }; #define DECLARE_FSEQ_STATE_CONTEXT_BEGIN( statename, ctx_var )\ struct { #define DECLARE_FSEQ_STATE_CONTEXT_END( statename, ctx_var )\ } ctx_var; #define DECLARE_FSEQ_STATE_CONTEXT_END_INIT_BEGIN( statename, ctx_var )\ } ctx_var = { #define DECLARE_FSEQ_STATE_CONTEXT_END_INIT_END( statename, ctx_var )\ }; #else #define DECLARE_FSEQ_STATE_ENTRY_WITH_CONTEXT(statename, ctx_var, ...)\ const fFSeqVTable_t statename##_vtbl = { __VA_ARGS__ }; #define DECLARE_FSEQ_STATE_CONTEXT_BEGIN( statename, ctx_var )\ extern const fFSeqVTable_t statename##_vtbl;\ const struct {\ fFSeqEntry_t stateEntry;\ #define DECLARE_FSEQ_STATE_CONTEXT_END( statename, ctx_var )\ } ctx_var = { \ .stateEntry = { .vtbl = &statename##_vtbl },\ }; #define DECLARE_FSEQ_STATE_CONTEXT_END_INIT_BEGIN( statename, ctx_var )\ } ctx_var = { \ .stateEntry = { .vtbl = &statename##_vtbl }, #define DECLARE_FSEQ_STATE_CONTEXT_END_INIT_END( statename, ctx_var )\ }; #endif /* // ------------------------------------------------------------------------------------------------ // Inheritance example (Method #0, see @FSEQ_ALT_INHERIT_METHOD_SUPPORT): xFSeqObj_t seq; // sequencer // ... // first state vtable method prototypes: const fFSeqEntry_t * firstStateFunction( const fFSeqEntry_t *, tFSeqCtx_t, const fFSeqEntry_t * * ); void firstStateEnter( const fFSeqEntry_t *, tFSeqCtx_t ); void firstStateLeave( const fFSeqEntry_t *, tFSeqCtx_t ); // ... struct { int publicVar; } GlobalContext; // no comments // ... typedef struct { int privateVar; } tFirstStatePrivateContext; tFirstStatePrivateContext firstStatePrivateDynamicContext; // the dynamic private context of first state (RAM) // ... // The state user-structure, incuding the state entry itself (firstState) and the const pointer to the another object. const struct { fFSeqEntry_t firstState; void * ctx_ref; } firstStateExtended = { .firstState = { firstStateFunction, firstStateEnter, firstStateLeave }, .ctx_ref = &firstStatePrivateDynamicContext // link the const pointer with the dynamic object }; // ... void foo() // some user function { fSeqConstruct( &seq, &firstStateExtended.firstState, &GlobalContext ); // consturct sequencer and link the global context } // ... // @firstStateFunction // firstState function body const fFSeqEntry_t * firstStateFunction( const fFSeqEntry_t * this, tFSeqCtx_t ctx, const fFSeqEntry_t * * pnext ) { const void * const_ctx_ref = (const void*)(&this[1]); // get access to the @ctx_ref field from @firstStateExtended structure // ... tFirstStatePrivateContext * private_ctx = (void*)const_ctx_ref; // cast the pointer // ... private_ctx->privateVar = 1; // use private context } // ------------------------------------------------------------------------------------------------ */ /* // ------------------------------------------------------------------------------------------------ // Inheritance example (Method #1, see @FSEQ_ALT_INHERIT_METHOD_SUPPORT): xFSeqObj_t seq; // sequencer // ... // first state vtable method prototypes: const fFSeqEntry_t * firstStateFunction( const fFSeqEntry_t *, tFSeqCtx_t, const fFSeqEntry_t * * ); void firstStateEnter( const fFSeqEntry_t *, tFSeqCtx_t ); void firstStateLeave( const fFSeqEntry_t *, tFSeqCtx_t ); // ... struct { int publicVar; } GlobalContext; // no comments // ... typedef struct { int privateVar; } tFirstStatePrivateContext; // ... typedef struct { fFSeqEntry_t firstState; tFirstStatePrivateContext privateContext; } tFirstStateAndPrivateContext; // the structure of state methods: const fFSeqVTable_t firstStateVTable = { firstStateFunction, firstStateEnter, firstStateLeave }; // The state user-structure, incuding the state entry, and the private context itself: tFirstStateAndPrivateContext firstStateExtended = { // the dynamic private context of first state (RAM) .firstState.vtbl = &firstStateVTable, .privateContext = { .privateVar = 0, // some values... } }; // ... // ... void foo() // some user function { fSeqConstruct( &seq, &firstStateExtended.firstState, &GlobalContext ); // consturct sequencer and link the global context } // ... // @firstStateFunction // firstState function body const fFSeqEntry_t * firstStateFunction( const fFSeqEntry_t * this, tFSeqCtx_t ctx, const fFSeqEntry_t * * pnext ) { tFirstStatePrivateContext * private_ctx = (tFirstStatePrivateContext*)(&this[1]); // get access to the private context // ... private_ctx->privateVar = 1; // use private context } // ------------------------------------------------------------------------------------------------ */ // @xFSeqObj_t // The sequencer object, contains some private fields // that must not be accessed by user. typedef struct { const void * private[2]; } xFSeqObj_t; // @fSeqConstruct // Construct the sequencer object, but not run it. // @fSeqObj - the object to construct // @first - the main state of the object, can not be NULL // @ctx - user-defined context, can be NULL void fSeqConstruct( xFSeqObj_t * fSeqObj, const fFSeqEntry_t * first, tFSeqCtx_t ctx ); // @fSeqStartup // Starts the constructed sequencer. // @fSeqObj - the sequencer object to start // @first - the state to start from, can be NULL // If @first is NULL, the value @first from the @fSeqConstruct() call will be used. void fSeqStartup( xFSeqObj_t * fSeqObj, const fFSeqEntry_t * first ); // @fSeqDispatch // Provides the sequencer work // Must be called to make the sequencer call the state functions // @fSeqObj - the sequencer object to operate with void fSeqDispatch( xFSeqObj_t * fSeqObj ); // @fSeqShutdown // Shutdowns the running sequencer // @fSeqObj - the sequencer object to shutdown void fSeqShutdown( xFSeqObj_t * fSeqObj ); /* Macro using example: const fFSeqEntry_t * state1Function( const fFSeqEntry_t *, tFSeqCtx_t, const fFSeqEntry_t * * ); void state1Enter( const fFSeqEntry_t *, tFSeqCtx_t ); void state1Leave( const fFSeqEntry_t *, tFSeqCtx_t ); DECLARE_FSEQ_STATE_CONTEXT_BEGIN(seqState1, seqState1Context) struct { int state1_var; }; DECLARE_FSEQ_STATE_CONTEXT_END(seqState1, seqState1Context) DECLARE_FSEQ_STATE_ENTRY_WITH_CONTEXT( seqState1, seqState1Context, state1Function, state1Enter, state1Leave ); */ #endif