esi-state-machine

esi-state-machine

Description

Overview

This class provides a simple base class for the creation of statemachines. The state machine includes an "PREPARE" callback, which is intended to collect the required data or do other tidiing up prior to the state transition decision. Furthermore the state machine holds a list of available states ( all of type esi-state ) and a list of transtions between these state.

States

Eache State must at least have a name and can have an "ON_ENTER", "ON_LEAVE" or "IN_STATE" methode. additional data of the state can be stored in the "data" proeprty. State names must be unique. The Callbacks are called with the STATE object (self) as well as with the calling statemachine.

Transitions

Each Transition must have a "from" and a "to" entry, which shall be state names and an "CONDITION" callback which must return true, if all conditions for a state transitions are meat. Additionally the Tranistion can have an "ON_TRANSITION" callback, which is called when the transition is executed. The prototypes of all these callbacks are documented below.

RUN calls

In order to execute a step of the state machine, the "RUN" method must be called. this also allows to pass parameters on to the "PREPARE" method. Additionally, the state machine can have a "CLEANUP" method, which is called at the end of each "RUN".

Inheritance of Instanciation

State machines can be implemented either by creating a child class of the State Machine and to use a instance of that, or the required Methods can just be overwritten in the object, both is possible.

Persistence

The State Machine does not have its own persistance methods, but it supports an external persistance by: exporting the .data section, the .results section and the name of the current state from the statemachine itself, as well as exporting the .data section and the .results section of all states as JSON. And allowing to import the same data during the re-initialzation of the statemachien object.

JSON configuration

Originally the whole state machine was defined to be object oriented and to be configured by object and functions, as well as data and configuration. But as this state machine shall also be usable as a configurabel command executor, it shall be possible to create a statemachine from a JSON configuration.

Configuring States

So instead of initializing a STATE object with "ON_ENTER", "IN_STATE" and "ON_LEAVE" functions, it is also possible to provide a table from which a FUNCTOR is generated behind the scenes. the table must have the following form:

{
    lib= <name ob library to require>,
    func= <name of function in that library>,
    arg= {arg1, arg2, arg3, ...} -- list of arguments to pass to the function
}

In order to allow more flexible arguments, there is a "lookup" feature included, which is explained later.

Configuration Transitions

In the same fassion the "CONDITION" and "ON_TRANSITION" function of a "TRANSITION" can be defined. BUT for the "CONDITION" there is also another possibility, instead of configuring a Functor as described above, the Lua expression library can be used, if a tbale of the following form is used:

{
    ["$luaxp"]="< expression according to luaxp expression>",
    arg = {arg1, arg2, arg3, ...} -- list of arguments to pass to the function
}

The expression is evaluated in context described below.

lookup, expressions and context

The lookup feature works very straight forward. It scans each argument for tables of the form:

{ ["$lookup"]={arg1, arg2, arg3,...}}

and replaces this table with the result of the lookup. the arguments are interpreted consecutively as a path within the context, so that {"config", "main", 1 } translates to context["config"]["main"][1]. If the given number is negative, the it is counted from the end, so that -1 translates to main[#main] and -2 to main[#main-1]. The context for the lookup, as well as for the luaxp function is the same:

{
    data= statemachine.data,
    config=statemachine.config,
    currentState = statemachine.currentState,
    arg= arg,               -- only present and relevant for luaxp
    results= statemachine.results,
    lastResult= statemachine.results[#statemachine.results]
}

Example1 (json config)

-- state machine definition via a JSON string
local JSON= require("rapidjson")
local ESM = require("esi-state-machine")
local jsonString = JSON.encode( {
        options= {resetResultsOnPrepare= false},
        config= {setting1= true},
        data = {value1 = false},
        startState= "stateOne",
        states= {
                    {
                        name= "stateOne",
                    },
                    {
                        name= "stateTwo",
                        IN_STATE=  {
                            lib= "syslib",
                            func= "getcorepath",
                            arg= {}  -- this must be a list!
                        },
                        ON_LEAVE= {
                            lib = "table",
                            func = "concat",
                            arg= { { {["$lookup"]= {"results", -1, "v"}} , ".ObjectName"}, ""}
                        }
                    },
                    {
                        name= "lastState",
                        ON_ENTER= {
                            lib= "syslib",
                            func= "getvalue",
                            arg= { {["$lookup"]= {"results", -2, "v"}}  }
                        }
                    },
                },
        transitions= {
            {
                from= "stateOne", to= "stateTwo",
                CONDITION= {
                            ["$luaxp"]="(currentState.name == \"stateOne\") && (arg.argument1==false)",
                            arg = {argument1=false}
                        },
            },
            {
                from= "stateTwo", to= "lastState",
                CONDITION= {
                            ["$luaxp"]="(currentState.name == \"stateTwo\") && (arg.corepath != arg.corepath2)",
                            arg= {corepath=syslib.getcorepath(),
                                    corepath2= syslib.getcorepath().." "}
                                }
            }
        }
    })
local esm = ESM( JSON.decode(jsonString))
for i=1,10 do
    esm:RUN()
end
return esm.results

Example2 (definition via lua objects)

-- state machine definition via lua object
-- this example has the same functionality as the example1 above
local JSON= require("rapidjson")
local ESM = require("esi-state-machine")
local config = {
        options= {resetResultsOnPrepare= false},
        config= {setting1= true},
        data = {value1 = false},
        startState= "stateOne",
        states= {
                    {
                        name= "stateOne",
                    },
                    {
                        name= "stateTwo",
                        IN_STATE=  function(state, sm) -- state := self
                            return syslib.getcorepath()
                        end,
                        ON_LEAVE= function(state, sm) -- state:=self
                            return table.concat( {sm.results[#sm.results].v, ".ObjectName"}, "")
                        end
                    },
                    {
                        name= "lastState",
                        ON_ENTER= function(state, sm)
                            return syslib.getvalue(sm.results[#sm.results-1].v)
                        end
                    },
                },
        transitions= {
            {
                from= "stateOne", to= "stateTwo",
                CONDITION= function(self, sm)
                        return sm.currentState.name == "stateOne"
                end
            },
            {
                from= "stateTwo", to= "lastState",
                CONDITION= function(self, sm)
                    return sm.currentState.name == "stateTwo"
                end
            }
        }
    }
local esm = ESM( config )
for i=1,10 do
    esm:RUN()
end
return esm.results

Dependencies

library version inmation core library

rapidjson

5.3.0

yes

esi-class

2.0.0

yes

esi-subtype

1.0.0

yes

luaxp

0.9.7

yes

Documentation

STATE:INIT

Description

STATE(template)

this is the constructor for the STATE object

Parameters

Name Description

template

this is the template for object creation. This template must follow the form: { name= <name of the state>, description= <description of the state>, --string, optional data= <table to hold state data>, --optional ON_ENTER= <function executed when entering the state>, --optional IN_STATE= <function called every time the stateMachine runs in this state>, --optional ON_LEAVE= <function called, when this state is left>, --optional }

Returns

returns the STATE object

Examples

local ESM = require("esi-state-machine")+
-- this transition automatically changes from state1 to state2+
local trans = ESM.STATE({+
                  name="state1",+
                  data = {inStateCnt = 0},+
                  ON_ENTER = function(self, stateMachine) -- called on enter+
                      return "state "..self.name.. " entered."+
                  end,+
                  IN_STATE = function(self, stateMachine) -- called for each run, while in the state+
                     self.data.inStateCnt = self.data.inStateCnt +1+
                     return "inState "..self.name..": called "..self.data.inStateCnt.." times."+
                  end,+
                  ON_LEAVE = function(self, stateMachine) -- called on leave+
                      return "state "..self.name.." left."+
                  end+
  })+

STATE:TO_MERMAID

Description

STATE:TO_MERMAID

this function generates a mermade subgraph from the transition

Parameters

Name Description

self

the object to process

id

the id of the state

Returns

returns mermade subgraph as string.

STATE:ON_ENTER

Description

STATE:ON_ENTER callback of the STATE helper class

This callback is called, whenever a state is entered. this is executed after the transition was executed

Parameters

Name Description

self

the STATE object itself

stateMachine

the calling statemachine

Returns

nil, no return value needed.

STATE:ON_LEAVE

Description

STATE:ON_LEAVE callback of the STATE helper class

This callback is called, whenever a state is left. the result of this function is written to the results array prior to the entry/entries from the transition

Parameters

Name Description

self

the STATE object itself

stateMachine

the calling statemachine

Returns

nil, no return value needed.

STATE:IN_STATE

Description

STATE:IN_STATE callback of the STATE helper class

This callback is called, whenever the state machine is in a specific state. the result of this function is written to the results array.

Parameters

Name Description

self

the STATE object itself

stateMachine

the calling statemachine

Returns

nil, no return value needed.

TRANSITION:INIT

Description

TRANSITION(template)

this is the constructor for the TRANSITION object

Parameters

Name Description

template

this is the template for object creation. the template must have the following form: { from= <name of the old state>, to = <name of the new state>, CONDITION = < function which must return true, if transition shall be taken >, ON_TRANSITION = < function executed during the transition >, --optional }

Returns

returns the TRANSITION object

Examples

local ESM = require("esi-state-machine")+
-- this transition automatically changes from state1 to state2+
local trans = ESM.TRANSITION({+
                  from = "state1",+
                  to =   "state2",+
                  CONDITION = function(self, stateMachine)+
                         stateMachine.currentState.name == "state1"+
                  end,+
                  ON_TRANSITION = function(self, stateMachine)+
                     retrun "Transition from state1 to state2"+
                  end+
  })+

TRANSITION:TO_MERMAID

Description

TRANSITION:TO_MERMAID

this function generates a mermade subgraph from the transition

Returns

returns mermade subgraph as string.

TRANSITION:CONDITION

Description

TRANSITION:CONDITION callback

this function is evaluated in order to decide if a state transition is possible.

Parameters

Name Description

self

the transition object itself.

stateMachine

the esi-state-machine calling this condition evaluation.

Returns

true if the condition for a state transition is meat, false otherwise.

TRANSITION:ON_TRANSITION

Description

TRANSITION:ON_TRANSITION callback of the Transtion helper class

This callback is called, whenever a Transition is executed.

Parameters

Name Description

self

the TRANSITION object itself

stateMachine

the calling statemachine

Returns

nil, no return value needed.

STATEMACHINE:INIT

Description

STATE_MACHINE(template)

this is the constructor for the STATE_MACHINE object

Parameters

Name Description

template

this is the template for object creation. this template should be of the form: { PREPARE= <prepare function> CLEANUP= <cleanup function> data= {}  — a lua table holding data (can be persisted) config= {}, — a lua table holding configuration (not persisted, should not change) states= {<STATE>, …​ }, — list of STATE object or lua tables  — if lua tables are used, then the STATE objects are created the table as template startState= <START>, — the start state, either a STATE object or the name of a STATE (string) transitions= {<TRANSITION>, …​} — list of TRANSITION objects or lua tables  — if lua tables are used, then the TRANSITION objects are created the table as template options= {resetResultsOnPrepare=true} — defines if results should be discarded before PREPARE. }

Returns

returns the STATE_MACHINE object

Examples

local ESM = require("esi-state-machine")+
-- this transition automatically changes from state1 to state2+
local esm = ESM({+
                  PREPARE= function(self) return "prepare called" end,+
                  CLEANUP= function(self) return "cleanup called" end,+
                  states = {+
                     ESM.STATE({name="state1"}), -- no ON_ENTER, IN_STATE or ON_LEAVE activities+
                     ESM.STATE({name="state2"}), -- no ON_ENTER, IN_STATE or ON_LEAVE activities+
                  },+
                  startState = "state1",+
                  transitions = {+
                     -- state2 has no outgoing transition, so it is a terminating state+
                     ESM.TRANSITION({+
                          from= "state1",+
                          to =  "state2",+
                          CONDITION = function()+
                              return true --immediately change from state1 to state2+
                          end+
                     })+
                  }+
})+
for i=1,10 do+
   esm:RUN()+
end+
return esm.results+

STATEMACHINE:NOP

Description

NOP no operation

does nothing

Parameters

Name Description

_

no parameters needed, but self can be provided

Returns

returns nil

STATEMACHINE:PREPARE

Description

PREPARE callback e.g. for input procesing

default implementation setting runTime and triggerTime to now If this callback should be overwritte with each instance of the esi-state-machine class. at least runTime and triggerTime must be set.

Parameters

Name Description

…​

a flexible number of inputs is assumed. This can be used in the overwriting callback as needed.

Returns

returns nil, not return value needed.

STATEMACHINE:CLEANUP

Description

CLEANUP callback e.g. for ouput generation

this callback is optional and can be used to set ouput parameters or gather debug information etc.

Parameters

Name Description

none

no parameters needed.

Returns

returns nil, not return value needed.

STATEMACHINE:RUN

Description

STATE_MACHINE:RUN function of the statemachine

for each iterration this function must be called one. this function will perform at most one state transition. if a final state (a state with no outgoing transitions) is reached, then no transitions and no stateHooks are executed and the currentState, false is returned. otherwise: if resetResultsOnPrepare is set, then all results are cleared if .PREPARE is present, this hook is called. Prepare does not store any results. Then all available transitions are checked for their condition. On the first match a the transition is triggered. this calls the OnLeave Hook (and record the results in the state-machine as well as in the state leaft.) Then the OnTransition Hook is called (and record the results in the state-machine) Then the OnEnter Hook is called ( and records the results in the state-machine, as well as the new state.) after a transition or if no transition took place, the InState Hook is called on the current state. at the end the CLEANUP Hook is called. and the current state and "true" is returned HINT: stateResults are cleared, whenever a state is entered. HINT: is a transition is taken from state a back to state a, HINT: this is lead to the same actions as other state transitions: calling ON_LEAVE, calling ON_TRANSITION and calling ON_ENTER, as well as reseting the stateResults. is no transition is taken the StateMachine stays in its current state.

Parameters

Name Description

…​

all inputs to the run methode will be passed on to the PREPARE methode.

Returns

returns the current state ( e.g. after the transition), false (if a final state was reached)

STATEMACHINE:RESTART

Description

STATE_MACHINE:RESTART restart the state machine

resets the statemachine to the intial state, restes all result entries. keeps all data and config entries. no params needed (apart from self)

Returns

bollean true on success

STATEMACHINE:TO_MERMAID

Description

STATE_MACHINE:TO_MERMAID create a mermaid diagram from the state machine

Parameters

Name Description

inclDetails

if set, more details are included

exclWildCardTrans

if set, then tranistions to any state (*) are excluded

Returns

string with Mermaid content

STATEMACHINE:TO_JSON

Description

STATE_MACHINE:TO_JSON convert data and currentState to json

This json can be stored on a variable etc. this exports the .data section of the statemachine, as well as each state this exports the .results section of the statemachine, as well as each state this exports the currentState name config settings are not included, as they should be static for most use cases. no parameters needed (apart from self)

Returns

returns a json string