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 |
Available functions:
All functions have to be called according to the ESI standard, using colons, e.g. lib:FUNCTIONNAME(params)
Documentation
STATE:INIT
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 } |
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:ON_ENTER
STATE:ON_LEAVE
STATE:IN_STATE
TRANSITION:INIT
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 } |
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:CONDITION
Description
TRANSITION:CONDITION callback
this function is evaluated in order to decide if a state transition is possible.
TRANSITION:ON_TRANSITION
STATEMACHINE:INIT
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. } |
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: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.
STATEMACHINE:CLEANUP
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.
STATEMACHINE:RESTART
STATEMACHINE:TO_MERMAID
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)