Skip to content

Instantly share code, notes, and snippets.

@Phrogz
Created June 29, 2014 03:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Phrogz/9e7180b6c37ee0643260 to your computer and use it in GitHub Desktop.
Save Phrogz/9e7180b6c37ee0643260 to your computer and use it in GitHub Desktop.
-------+-------------------------------------------------------------------------------
-- ct | code -------------------------------------------------------------------------
-------+-------------------------------------------------------------------------------
120 local LXSC = require 'lib/lxsc';
*****0 (function(S)
120 S.MAX_ITERATIONS = 1000
120 local OrderedSet,Queue,List = LXSC.OrderedSet, LXSC.Queue, LXSC.List
-- ****************************************************************************
410 local function entryOrder(a,b) return a._order < b._order end
324 local function exitOrder(a,b) return b._order < a._order end
1469 local function isDescendant(a,b) return a:descendantOf(b) end
157 local function isCancelEvent(e) return e.name=='quit.lxsc' end
755 local function isFinalState(s) return s._kind=='final' end
905 local function isScxmlState(s) return s._kind=='scxml' end
1641 local function isHistoryState(s) return s._kind=='history' end
582 local function isParallelState(s) return s._kind=='parallel' end
1330 local function isCompoundState(s) return s.isCompound end
901 local function isAtomicState(s) return s.isAtomic end
150 local function getChildStates(s) return s.reals end
local function findLCCA(first,rest) -- least common compound ancestor
743 for _,anc in ipairs(first.ancestors) do
743 if isCompoundState(anc) or isScxmlState(anc) then
1414 if rest:every(function(s) return isDescendant(s,anc) end) then
644 return anc
end
end
end
end
120 local emptyList = List()
120 local depth=0
local function logloglog(s)
-- print(string.rep(' ',depth)..tostring(s))
end
5386 local function startfunc(s) logloglog(s) depth=depth+1 end
5386 local function closefunc(s) if s then logloglog(s) end depth=depth-1 end
-- ****************************************************************************
120 function S:interpret(options)
120 self._delayedSend = { extraTime=0 }
-- if not self:validate() then self:failWithError() end
120 if not rawget(self,'_stateById') then self:expandScxmlSource() end
120 self._configuration:clear()
120 self._statesToInvoke = OrderedSet() -- TODO: implement <invoke>
120 self._internalQueue = Queue()
120 self._externalQueue = Queue()
120 self._historyValue = {}
120 self._data = LXSC.Datamodel(self,options and options.data)
120 self._data:_setSystem('_sessionid',LXSC.uuid4())
120 self._data:_setSystem('_name',self.name or LXSC.uuid4())
120 self._data:_setSystem('_ioprocessors',{})
120 if self.binding == "early" then self._data:initAll() end
120 self.running = true
120 self:executeGlobalScriptElement()
120 self:enterStates(self.initial.transitions)
120 self:mainEventLoop()
end
-- ******************************************************************************************************
-- ******************************************************************************************************
-- ******************************************************************************************************
120 function S:mainEventLoop()
local anyTransition, enabledTransitions, macrostepDone, iterations
190 while self.running do
145 anyTransition = false -- (LXSC specific)
145 iterations = 0 -- (LXSC specific)
145 macrostepDone = false
-- Here we handle eventless transitions and transitions
-- triggered by internal events until macrostep is complete
416 while self.running and not macrostepDone and iterations<S.MAX_ITERATIONS do
271 enabledTransitions = self:selectEventlessTransitions()
271 if enabledTransitions:isEmpty() then
176 if self._internalQueue:isEmpty() then
48 macrostepDone = true
else
128 logloglog("-- Internal Queue: "..self._internalQueue:inspect())
128 local internalEvent = self._internalQueue:dequeue()
128 self._data:_setSystem('_event',internalEvent)
128 enabledTransitions = self:selectTransitions(internalEvent)
end
end
271 if not enabledTransitions:isEmpty() then
218 anyTransition = true
218 self:microstep(enabledTransitions)
end
271 iterations = iterations + 1
end
145 if iterations>=S.MAX_ITERATIONS then print(string.format("Warning: stopped unstable system after %d internal iterations",S.MAX_ITERATIONS)) end
-- Either we're in a final state, and we break out of the loop…
145 if not self.running then break end
-- …or we've completed a macrostep, so we start a new macrostep by waiting for an external event
-- Here we invoke whatever needs to be invoked. The implementation of 'invoke' is platform-specific
86 for _,state in ipairs(self._statesToInvoke) do for _,inv in ipairs(state._invokes) do self:invoke(inv) end end
48 self._statesToInvoke:clear()
-- Invoking may have raised internal error events; if so, we skip and iterate to handle them
48 if self._internalQueue:isEmpty() then
48 logloglog("-- External Queue: "..self._externalQueue:inspect())
48 local externalEvent = self._externalQueue:dequeue()
48 if externalEvent then -- (LXSC specific) The queue might be empty.
37 if isCancelEvent(externalEvent) then
*****0 self.running = false
else
37 self._data:_setSystem('_event',externalEvent)
76 for _,state in ipairs(self._configuration) do
39 for _,inv in ipairs(state._invokes) do
*****0 if inv.invokeid == externalEvent.invokeid then self:applyFinalize(inv, externalEvent) end
*****0 if inv.autoforward then self:send(inv.id, externalEvent) end
end
end
37 enabledTransitions = self:selectTransitions(externalEvent)
37 if not enabledTransitions:isEmpty() then
36 anyTransition = true
36 self:microstep(enabledTransitions)
end
end
end
-- (LXSC specific) we stop iterating as soon as no transitions occur
48 if not anyTransition then break end
end
end
-- We re-check if we're running here because we use step-based processing;
-- we may have exited the 'running' loop if there were no more events to process.
151 if not self.running then self:exitInterpreter() end
end
-- ******************************************************************************************************
-- ******************************************************************************************************
-- ******************************************************************************************************
120 function S:executeGlobalScriptElement()
120 if rawget(self,'_script') then self:executeSingle(self._script) end
end
120 function S:exitInterpreter()
142 local statesToExit = self._configuration:toList():sort(exitOrder)
284 for _,s in ipairs(statesToExit) do
142 for _,content in ipairs(s._onexits) do self:executeContent(content) end
142 for _,inv in ipairs(s._invokes) do self:cancelInvoke(inv) end
-- (LXSC specific) We do not delete the configuration on exit so that it may be examined later.
-- self._configuration:delete(s)
142 if isFinalState(s) and isScxmlState(s.parent) then
142 self:returnDoneEvent(self:donedata(s))
end
end
end
120 function S:selectEventlessTransitions()
271 startfunc('selectEventlessTransitions()')
271 local enabledTransitions = OrderedSet()
271 local atomicStates = self._configuration:toList():filter(isAtomicState):sort(entryOrder)
574 for _,state in ipairs(atomicStates) do
303 self:addEventlessTransition(state,enabledTransitions)
end
271 enabledTransitions = self:removeConflictingTransitions(enabledTransitions)
271 closefunc('-- selectEventlessTransitions result: '..enabledTransitions:inspect())
271 return enabledTransitions
end
-- (LXSC specific) we use this function since Lua cannot break out of a nested loop
120 function S:addEventlessTransition(state,enabledTransitions)
889 for _,s in ipairs(state.selfAndAncestors) do
690 for _,t in ipairs(s._eventlessTransitions) do
104 if t:conditionMatched(self._data) then
99 enabledTransitions:add(t)
99 return
end
end
end
end
120 function S:selectTransitions(event)
165 startfunc('selectTransitions( '..event:inspect()..' )')
165 local enabledTransitions = OrderedSet()
165 local atomicStates = self._configuration:toList():filter(isAtomicState):sort(entryOrder)
356 for _,state in ipairs(atomicStates) do
191 self:addTransitionForEvent(state,event,enabledTransitions)
end
165 enabledTransitions = self:removeConflictingTransitions(enabledTransitions)
165 closefunc('-- selectTransitions result: '..enabledTransitions:inspect())
165 return enabledTransitions
end
-- (LXSC specific) we use this function since Lua cannot break out of a nested loop
120 function S:addTransitionForEvent(state,event,enabledTransitions)
320 for _,s in ipairs(state.selfAndAncestors) do
370 for _,t in ipairs(s._eventedTransitions) do
241 if t:matchesEvent(event) and t:conditionMatched(self._data) then
175 enabledTransitions:add(t)
175 return
end
end
end
end
120 function S:removeConflictingTransitions(enabledTransitions)
436 startfunc('removeConflictingTransitions( enabledTransitions:'..enabledTransitions:inspect()..' )')
436 local filteredTransitions = OrderedSet()
699 for _,t1 in ipairs(enabledTransitions) do
263 local t1Preempted = false
263 local transitionsToRemove = OrderedSet()
274 for _,t2 in ipairs(filteredTransitions) do
13 if self:computeExitSet(List(t1)):hasIntersection(self:computeExitSet(List(t2))) then
3 if isDescendant(t1.source,t2.source) then
1 transitionsToRemove:add(t2)
else
2 t1Preempted = true
2 break
end
end
end
263 if not t1Preempted then
262 for _,t3 in ipairs(transitionsToRemove) do
1 filteredTransitions:delete(t3)
end
261 filteredTransitions:add(t1)
end
end
436 closefunc('-- removeConflictingTransitions result: '..filteredTransitions:inspect())
436 return filteredTransitions
end
120 function S:microstep(enabledTransitions)
254 startfunc('microstep( enabledTransitions:'..enabledTransitions:inspect()..' )')
254 self:exitStates(enabledTransitions)
254 self:executeTransitionContent(enabledTransitions)
254 self:enterStates(enabledTransitions)
254 if rawget(self,'onEnteredAll') then self.onEnteredAll() end
254 closefunc()
end
120 function S:exitStates(enabledTransitions)
254 startfunc('exitStates( enabledTransitions:'..enabledTransitions:inspect()..' )')
254 local statesToExit = self:computeExitSet(enabledTransitions)
611 for _,s in ipairs(statesToExit) do self._statesToInvoke:delete(s) end
254 statesToExit = statesToExit:toList():sort(exitOrder)
-- Record history for states being exited
611 for _,s in ipairs(statesToExit) do
621 for _,h in ipairs(s.states) do
264 if h._kind=='history' then
22 self._historyValue[h.id] = self._configuration:toList():filter(function(s0)
32 if h.type=='deep' then
15 return isAtomicState(s0) and isDescendant(s0,s)
else
17 return s0.parent==s
end
22 end)
end
end
end
-- Exit the states
611 for _,s in ipairs(statesToExit) do
357 if self.onBeforeExit then self.onBeforeExit(s.id,s._kind,s.isAtomic) end
391 for _,content in ipairs(s._onexits) do
34 self:executeContent(content)
end
357 for _,inv in ipairs(s._invokes) do self:cancelInvoke(inv) end
357 self._configuration:delete(s)
357 logloglog(string.format("-- removed %s from the configuration; config is now {%s}",s:inspect(),table.concat(self:activeStateIds(),', ')))
end
254 closefunc()
end
120 function S:computeExitSet(transitions)
280 startfunc('computeExitSet( transitions:'..transitions:inspect()..' )')
280 local statesToExit = OrderedSet()
566 for _,t in ipairs(transitions) do
286 if t.targets then
266 local domain = self:getTransitionDomain(t)
786 for _,s in ipairs(self._configuration) do
520 if isDescendant(s,domain) then
430 statesToExit:add(s)
end
end
end
end
280 closefunc('-- computeExitSet result '..statesToExit:inspect())
280 return statesToExit
end
120 function S:executeTransitionContent(enabledTransitions)
254 startfunc('executeTransitionContent( enabledTransitions:'..enabledTransitions:inspect()..' )')
514 for _,t in ipairs(enabledTransitions) do
260 if self.onTransition then self.onTransition(t) end
291 for _,executable in ipairs(t._exec) do
31 if not self:executeSingle(executable) then break end
end
end
254 closefunc()
end
120 function S:enterStates(enabledTransitions)
374 startfunc('enterStates( enabledTransitions:'..enabledTransitions:inspect()..' )')
374 local statesToEnter = OrderedSet()
374 local statesForDefaultEntry = OrderedSet()
374 local defaultHistoryContent = {} -- temporary table for default content in history states
374 self:computeEntrySet(enabledTransitions,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
851 for _,s in ipairs(statesToEnter:toList():sort(entryOrder)) do
477 self._configuration:add(s)
477 logloglog(string.format("-- added %s '%s' to the configuration; config is now <%s>",s._kind,s.id,table.concat(self:activeStateIds(),', ')))
477 if isScxmlState(s) then error("Added SCXML to configuration.") end
477 self._statesToInvoke:add(s)
477 if self.binding=="late" then
-- The LXSC datamodel ensures this happens only once per state
3 self._data:initState(s)
end
620 for _,content in ipairs(s._onentrys) do
143 self:executeContent(content)
end
477 if self.onAfterEnter then self.onAfterEnter(s.id,s._kind,s.isAtomic) end
477 if statesForDefaultEntry:isMember(s) then
90 for _,t in ipairs(s.initial.transitions) do
47 for _,executable in ipairs(t._exec) do
2 if not self:executeSingle(executable) then break end
end
end
end
477 if defaultHistoryContent[s.id] then
2 logloglog("-- executing defaultHistoryContent for "..s.id)
2 for _,executable in ipairs(defaultHistoryContent[s.id]) do
*****0 if not self:executeSingle(executable) then break end
end
end
477 if isFinalState(s) then
134 local parent = s.parent
134 if isScxmlState(parent) then
120 self.running = false
else
14 local grandparent = parent.parent
14 self:fireEvent( "done.state."..parent.id, self:donedata(s), {type='internal'} )
14 if isParallelState(grandparent) then
4 local allAreInFinal = true
10 for _,child in ipairs(grandparent.reals) do
8 if not self:isInFinalState(child) then
2 allAreInFinal = false
2 break
end
end
4 if allAreInFinal then
2 self:fireEvent( "done.state."..grandparent.id, nil, {type='internal'} )
end
end
end
end
end
374 closefunc()
end
120 function S:computeEntrySet(transitions,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
374 startfunc('computeEntrySet( transitions:'..transitions:inspect()..', ... )')
754 for _,t in ipairs(transitions) do
380 if t.targets then
742 for _,s in ipairs(t.targets) do
372 self:addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
end
end
-- logloglog('-- after adding descendants statesToEnter is: '..statesToEnter:inspect())
380 local ancestor = self:getTransitionDomain(t)
752 for _,s in ipairs(self:getEffectiveTargetStates(t)) do
372 self:addAncestorStatesToEnter(s,ancestor,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
end
end
374 logloglog('-- computeEntrySet result statesToEnter: '..statesToEnter:inspect())
374 logloglog('-- computeEntrySet result statesForDefaultEntry: '..statesForDefaultEntry:inspect())
374 closefunc()
end
120 function S:addDescendantStatesToEnter(state,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
457 startfunc("addDescendantStatesToEnter( state:"..state:inspect()..", ... )")
457 if isHistoryState(state) then
4 if self._historyValue[state.id] then
4 for _,s in ipairs(self._historyValue[state.id]) do
2 self:addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
2 self:addAncestorStatesToEnter(s,state.parent,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
end
else
2 defaultHistoryContent[state.parent.id] = state.transitions[1]._exec
2 logloglog("-- defaultHistoryContent['"..state.parent.id.."'] = "..(#state.transitions[1]._exec).." executables")
4 for _,t in ipairs(state.transitions) do
2 if t.targets then
4 for _,s in ipairs(t.targets) do
2 self:addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
2 self:addAncestorStatesToEnter(s,state.parent,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
end
end
end
end
else
453 statesToEnter:add(state)
453 logloglog("statesToEnter:add( "..state:inspect().." )")
453 if isCompoundState(state) then
45 statesForDefaultEntry:add(state)
90 for _,t in ipairs(state.initial.transitions) do
92 for _,s in ipairs(self:getEffectiveTargetStates(t)) do
47 self:addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
47 self:addAncestorStatesToEnter(s,state,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
end
end
408 elseif isParallelState(state) then
35 for _,child in ipairs(getChildStates(state)) do
79 if not statesToEnter:some(function(s) return isDescendant(s,child) end) then
24 self:addDescendantStatesToEnter(child,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
end
end
end
end
457 closefunc()
end
120 function S:addAncestorStatesToEnter(state,ancestor,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
423 startfunc("addAncestorStatesToEnter( state:"..state:inspect()..", ancestor:"..ancestor:inspect()..", ... )")
463 for anc in state:ancestorsUntil(ancestor) do
40 statesToEnter:add(anc)
40 logloglog("statesToEnter:add( "..anc:inspect().." )")
40 if isParallelState(anc) then
35 for _,child in ipairs(getChildStates(anc)) do
83 if not statesToEnter:some(function(s) return isDescendant(s,child) end) then
10 self:addDescendantStatesToEnter(child,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
end
end
end
end
423 closefunc()
end
120 function S:isInFinalState(s)
8 if isCompoundState(s) then
24 return getChildStates(s):some(function(s) return isFinalState(s) and self._configuration:isMember(s) end)
*****0 elseif isParallelState(s) then
*****0 return getChildStates(s):every(function(s) self:isInFinalState(s) end)
else
*****0 return false
end
end
120 function S:getTransitionDomain(t)
646 startfunc('getTransitionDomain( t:'..t:inspect()..' )' )
local result
646 local tstates = self:getEffectiveTargetStates(t)
646 if not tstates then
*****0 result = nil
650 elseif t.type=='internal' and isCompoundState(t.source) and tstates:every(function(s) return isDescendant(s,t.source) end) then
2 result = t.source
else
644 result = findLCCA(t.source,t.targets or emptyList)
end
646 closefunc('-- getTransitionDomain result: '..tostring(result and result.id))
646 return result
end
120 function S:getEffectiveTargetStates(transition)
1078 startfunc('getEffectiveTargetStates( transition:'..transition:inspect()..' )')
1078 local targets = OrderedSet()
1078 if transition.targets then
2122 for _,s in ipairs(transition.targets) do
1064 if isHistoryState(s) then
13 if self._historyValue[s.id] then
6 targets:union(self._historyValue[s.id])
else
-- History states can only have one transition, so we hard-code that here.
7 targets:union(self:getEffectiveTargetStates(s.transitions[1]))
end
else
1051 targets:add(s)
end
end
end
1078 closefunc('-- getEffectiveTargetStates result: '..targets:inspect())
1078 return targets
end
120 function S:expandScxmlSource()
120 self:convertInitials()
120 self._stateById = {}
691 for _,s in ipairs(self.states) do s:cacheReference(self._stateById) end
120 self:resolveReferences(self._stateById)
end
120 function S:returnDoneEvent(donedata)
-- TODO: implement
end
120 function S:donedata(state)
156 local c = state._donedatas[1]
156 if c then
8 if c._kind=='content' then
4 local wrapper = {}
4 self:executeSingle(c,wrapper)
4 return wrapper.content
else
4 local map = {}
8 for _,p in ipairs(state._donedatas) do
4 local val = p.location and self._data:get(p.location) or p.expr and self._data:eval(p.expr)
4 if val == LXSC.Datamodel.INVALIDLOCATION then
2 self:fireEvent("error.execution.invalid-param-value","There was an error determining the value for a <param> inside a <donedata>")
2 elseif val ~= LXSC.Datamodel.EVALERROR then
1 if p.name==nil or p.name=="" then
*****0 self:fireEvent("error.execution.invalid-param-name","Unsupported <param> name '"..tostring(p.name).."'")
else
1 map[p.name] = val
end
end
end
4 return next(map) and map
end
end
end
120 function S:fireEvent(name,data,eventValues)
221 eventValues = eventValues or {}
221 eventValues.type = eventValues.type or 'platform'
221 local event = LXSC.Event(name,data,eventValues)
221 logloglog(string.format("-- queued %s event '%s'",event.type,event.name))
221 if rawget(self,'onEventFired') then self.onEventFired(event) end
221 self[eventValues.type=='external' and "_externalQueue" or "_internalQueue"]:enqueue(event)
221 return event
end
-- Sensible aliases
120 S.start = S.interpret
120 S.restart = S.interpret
120 function S:step()
31 self:processDelayedSends()
31 self:mainEventLoop()
end
240 end)(LXSC.SCXML)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment