Skip to content

Instantly share code, notes, and snippets.

Created June 30, 2014 20:46
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 anonymous/c81c0275212944ad9cbf to your computer and use it in GitHub Desktop.
Save anonymous/c81c0275212944ad9cbf to your computer and use it in GitHub Desktop.
hits | code
-------+----------------------------------------------------------------------------------------------------------
130 | function S:interpret(options)
129 | self._delayedSend = { extraTime=0 }
|
| -- if not self:validate() then self:failWithError() end
129 | if not rawget(self,'_stateById') then self:expandScxmlSource() end
129 | self._configuration:clear()
129 | self._statesToInvoke = OrderedSet()
129 | self._internalQueue = Queue()
129 | self._externalQueue = Queue()
129 | self._historyValue = {}
|
129 | self._data = LXSC.Datamodel(self,options and options.data)
129 | self._data:_setSystem('_sessionid',LXSC.uuid4())
129 | self._data:_setSystem('_name',self.name or LXSC.uuid4())
129 | self._data:_setSystem('_ioprocessors',{})
129 | if self.binding == "early" then self._data:initAll() end
129 | self.running = true
129 | self:executeGlobalScriptElement()
129 | self:enterStates(self.initial.transitions)
129 | self:mainEventLoop()
| end
|
| -- ******************************************************************************************************
| -- ******************************************************************************************************
| -- ******************************************************************************************************
|
130 | function S:mainEventLoop()
| local anyTransition, enabledTransitions, macrostepDone, iterations
202 | while self.running do
153 | anyTransition = false -- (LXSC specific)
153 | iterations = 0 -- (LXSC specific)
153 | macrostepDone = false
|
| -- Here we handle eventless transitions and transitions
| -- triggered by internal events until macrostep is complete
440 | while self.running and not macrostepDone and iterations<S.MAX_ITERATIONS do
287 | enabledTransitions = self:selectEventlessTransitions()
287 | if enabledTransitions:isEmpty() then
186 | if self._internalQueue:isEmpty() then
51 | macrostepDone = true
| else
135 | logloglog("-- Internal Queue: "..self._internalQueue:inspect())
135 | local internalEvent = self._internalQueue:dequeue()
135 | self._data:_setSystem('_event',internalEvent)
135 | enabledTransitions = self:selectTransitions(internalEvent)
| end
| end
287 | if not enabledTransitions:isEmpty() then
231 | anyTransition = true
231 | self:microstep(enabledTransitions)
| end
287 | iterations = iterations + 1
| end
|
153 | 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…
153 | 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
93 | for _,state in ipairs(self._statesToInvoke) do for _,inv in ipairs(state._invokes) do self:invoke(inv) end end
49 | self._statesToInvoke:clear()
|
| -- Invoking may have raised internal error events; if so, we skip and iterate to handle them
49 | if self._internalQueue:isEmpty() then
49 | logloglog("-- External Queue: "..self._externalQueue:inspect())
49 | local externalEvent = self._externalQueue:dequeue()
49 | if externalEvent then -- (LXSC specific) The queue might be empty.
38 | if isCancelEvent(externalEvent) then
*****0 | self.running = false
| else
38 | self._data:_setSystem('_event',externalEvent)
77 | 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
38 | enabledTransitions = self:selectTransitions(externalEvent)
38 | if not enabledTransitions:isEmpty() then
37 | anyTransition = true
37 | self:microstep(enabledTransitions)
| end
| end
| end
|
| -- (LXSC specific) we stop iterating as soon as no transitions occur
49 | if not anyTransition then break end
| end
| end
|
| -- We re-check if we're running here because LXSC uses step-based processing;
| -- we may have exited the 'running' loop if there were no more events to process.
160 | if not self.running then self:exitInterpreter() end
| end
|
| -- ******************************************************************************************************
| -- ******************************************************************************************************
| -- ******************************************************************************************************
|
130 | function S:executeGlobalScriptElement()
129 | if rawget(self,'_script') then self:executeSingle(self._script) end
| end
|
130 | function S:exitInterpreter()
151 | local statesToExit = self._configuration:toList():sort(exitOrder)
302 | for _,s in ipairs(statesToExit) do
151 | for _,content in ipairs(s._onexits) do self:executeContent(content) end
151 | 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)
|
151 | if isFinalState(s) and isScxmlState(s.parent) then
151 | self:returnDoneEvent(self:donedata(s))
| end
| end
| end
|
130 | function S:selectEventlessTransitions()
287 | startfunc('selectEventlessTransitions()')
287 | local enabledTransitions = OrderedSet()
287 | local atomicStates = self._configuration:toList():filter(isAtomicState):sort(entryOrder)
609 | for _,state in ipairs(atomicStates) do
322 | self:addEventlessTransition(state,enabledTransitions)
| end
287 | enabledTransitions = self:removeConflictingTransitions(enabledTransitions)
287 | closefunc('-- selectEventlessTransitions result: '..enabledTransitions:inspect())
287 | return enabledTransitions
| end
| -- (LXSC specific) we use this function since Lua cannot break out of a nested loop
130 | function S:addEventlessTransition(state,enabledTransitions)
940 | for _,s in ipairs(state.selfAndAncestors) do
735 | for _,t in ipairs(s._eventlessTransitions) do
117 | if t:conditionMatched(self._data) then
105 | enabledTransitions:add(t)
105 | return
| end
| end
| end
| end
|
130 | function S:selectTransitions(event)
173 | startfunc('selectTransitions( '..event:inspect()..' )')
173 | local enabledTransitions = OrderedSet()
173 | local atomicStates = self._configuration:toList():filter(isAtomicState):sort(entryOrder)
372 | for _,state in ipairs(atomicStates) do
199 | self:addTransitionForEvent(state,event,enabledTransitions)
| end
173 | enabledTransitions = self:removeConflictingTransitions(enabledTransitions)
173 | closefunc('-- selectTransitions result: '..enabledTransitions:inspect())
173 | return enabledTransitions
| end
| -- (LXSC specific) we use this function since Lua cannot break out of a nested loop
130 | function S:addTransitionForEvent(state,event,enabledTransitions)
328 | for _,s in ipairs(state.selfAndAncestors) do
379 | for _,t in ipairs(s._eventedTransitions) do
250 | if t:matchesEvent(event) and t:conditionMatched(self._data) then
183 | enabledTransitions:add(t)
183 | return
| end
| end
| end
| end
|
130 | function S:removeConflictingTransitions(enabledTransitions)
460 | startfunc('removeConflictingTransitions( enabledTransitions:'..enabledTransitions:inspect()..' )')
460 | local filteredTransitions = OrderedSet()
737 | for _,t1 in ipairs(enabledTransitions) do
277 | local t1Preempted = false
277 | local transitionsToRemove = OrderedSet()
288 | 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
|
277 | if not t1Preempted then
276 | for _,t3 in ipairs(transitionsToRemove) do
1 | filteredTransitions:delete(t3)
| end
275 | filteredTransitions:add(t1)
| end
| end
|
460 | closefunc('-- removeConflictingTransitions result: '..filteredTransitions:inspect())
460 | return filteredTransitions
| end
|
130 | function S:microstep(enabledTransitions)
268 | startfunc('microstep( enabledTransitions:'..enabledTransitions:inspect()..' )')
|
268 | self:exitStates(enabledTransitions)
268 | self:executeTransitionContent(enabledTransitions)
268 | self:enterStates(enabledTransitions)
|
268 | if rawget(self,'onEnteredAll') then self.onEnteredAll() end
|
268 | closefunc()
| end
|
130 | function S:exitStates(enabledTransitions)
268 | startfunc('exitStates( enabledTransitions:'..enabledTransitions:inspect()..' )')
|
268 | local statesToExit = self:computeExitSet(enabledTransitions)
647 | for _,s in ipairs(statesToExit) do self._statesToInvoke:delete(s) end
268 | statesToExit = statesToExit:toList():sort(exitOrder)
|
| -- Record history for states being exited
647 | for _,s in ipairs(statesToExit) do
666 | for _,h in ipairs(s.states) do
287 | if h._kind=='history' then
28 | self._historyValue[h.id] = self._configuration:toList():filter(function(s0)
42 | if h.type=='deep' then
15 | return isAtomicState(s0) and isDescendant(s0,s)
| else
27 | return s0.parent==s
| end
28 | end)
| end
| end
| end
|
| -- Exit the states
647 | for _,s in ipairs(statesToExit) do
379 | if self.onBeforeExit then self.onBeforeExit(s.id,s._kind,s.isAtomic) end
416 | for _,content in ipairs(s._onexits) do
37 | self:executeContent(content)
| end
379 | for _,inv in ipairs(s._invokes) do self:cancelInvoke(inv) end
379 | self._configuration:delete(s)
379 | logloglog(string.format("-- removed %s from the configuration; config is now {%s}",s:inspect(),table.concat(self:activeStateIds(),', ')))
| end
|
268 | closefunc()
| end
|
130 | function S:computeExitSet(transitions)
294 | startfunc('computeExitSet( transitions:'..transitions:inspect()..' )')
294 | local statesToExit = OrderedSet()
594 | for _,t in ipairs(transitions) do
300 | if t.targets then
280 | local domain = self:getTransitionDomain(t)
827 | for _,s in ipairs(self._configuration) do
547 | if isDescendant(s,domain) then
452 | statesToExit:add(s)
| end
| end
| end
| end
294 | closefunc('-- computeExitSet result '..statesToExit:inspect())
294 | return statesToExit
| end
|
130 | function S:executeTransitionContent(enabledTransitions)
268 | startfunc('executeTransitionContent( enabledTransitions:'..enabledTransitions:inspect()..' )')
542 | for _,t in ipairs(enabledTransitions) do
274 | if self.onTransition then self.onTransition(t) end
308 | for _,executable in ipairs(t._exec) do
34 | if not self:executeSingle(executable) then break end
| end
| end
268 | closefunc()
| end
|
130 | function S:enterStates(enabledTransitions)
397 | startfunc('enterStates( enabledTransitions:'..enabledTransitions:inspect()..' )')
|
397 | local statesToEnter = OrderedSet()
397 | local statesForDefaultEntry = OrderedSet()
397 | local defaultHistoryContent = {} -- temporary table for default content in history states
397 | self:computeEntrySet(enabledTransitions,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
|
906 | for _,s in ipairs(statesToEnter:toList():sort(entryOrder)) do
509 | self._configuration:add(s)
509 | logloglog(string.format("-- added %s '%s' to the configuration; config is now <%s>",s._kind,s.id,table.concat(self:activeStateIds(),', ')))
509 | if isScxmlState(s) then error("Added SCXML to configuration.") end
509 | self._statesToInvoke:add(s)
|
509 | if self.binding=="late" then
| -- The LXSC datamodel ensures this happens only once per state
6 | self._data:initState(s)
| end
|
664 | for _,content in ipairs(s._onentrys) do
155 | self:executeContent(content)
| end
509 | if self.onAfterEnter then self.onAfterEnter(s.id,s._kind,s.isAtomic) end
|
509 | if statesForDefaultEntry:isMember(s) then
98 | for _,t in ipairs(s.initial.transitions) do
52 | for _,executable in ipairs(t._exec) do
3 | if not self:executeSingle(executable) then break end
| end
| end
| end
|
509 | if defaultHistoryContent[s.id] then
4 | logloglog("-- executing defaultHistoryContent for "..s.id)
5 | for _,executable in ipairs(defaultHistoryContent[s.id]) do
1 | if not self:executeSingle(executable) then break end
| end
| end
|
509 | if isFinalState(s) then
141 | local parent = s.parent
141 | if isScxmlState(parent) then
127 | 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
|
397 | closefunc()
| end
|
130 | function S:computeEntrySet(transitions,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
397 | startfunc('computeEntrySet( transitions:'..transitions:inspect()..', ... )')
|
800 | for _,t in ipairs(transitions) do
403 | if t.targets then
788 | for _,s in ipairs(t.targets) do
395 | self:addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
| end
| end
| -- logloglog('-- after adding descendants statesToEnter is: '..statesToEnter:inspect())
|
403 | local ancestor = self:getTransitionDomain(t)
798 | for _,s in ipairs(self:getEffectiveTargetStates(t)) do
395 | self:addAncestorStatesToEnter(s,ancestor,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
| end
| end
397 | logloglog('-- computeEntrySet result statesToEnter: '..statesToEnter:inspect())
397 | logloglog('-- computeEntrySet result statesForDefaultEntry: '..statesForDefaultEntry:inspect())
397 | closefunc()
| end
|
130 | function S:addDescendantStatesToEnter(state,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
491 | startfunc("addDescendantStatesToEnter( state:"..state:inspect()..", ... )")
491 | if isHistoryState(state) then
|
8 | if self._historyValue[state.id] then
8 | for _,s in ipairs(self._historyValue[state.id]) do
4 | self:addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
4 | self:addAncestorStatesToEnter(s,state.parent,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
| end
| else
4 | defaultHistoryContent[state.parent.id] = state.transitions[1]._exec
4 | logloglog("-- defaultHistoryContent['"..state.parent.id.."'] = "..(#state.transitions[1]._exec).." executables")
8 | for _,t in ipairs(state.transitions) do
4 | if t.targets then
8 | for _,s in ipairs(t.targets) do
4 | self:addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
4 | self:addAncestorStatesToEnter(s,state.parent,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
| end
| end
| end
| end
|
| else
|
483 | statesToEnter:add(state)
483 | logloglog("statesToEnter:add( "..state:inspect().." )")
|
483 | if isCompoundState(state) then
49 | statesForDefaultEntry:add(state)
98 | for _,t in ipairs(state.initial.transitions) do
100 | for _,s in ipairs(t.targets) do
51 | self:addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
51 | self:addAncestorStatesToEnter(s,state,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
| end
| end
434 | elseif isParallelState(state) then
38 | for _,child in ipairs(getChildStates(state)) do
84 | if not statesToEnter:some(function(s) return isDescendant(s,child) end) then
26 | self:addDescendantStatesToEnter(child,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
| end
| end
| end
|
| end
|
491 | closefunc()
| end
|
130 | function S:addAncestorStatesToEnter(state,ancestor,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
454 | startfunc("addAncestorStatesToEnter( state:"..state:inspect()..", ancestor:"..ancestor:inspect()..", ... )")
|
496 | for anc in state:ancestorsUntil(ancestor) do
42 | statesToEnter:add(anc)
42 | logloglog("statesToEnter:add( "..anc:inspect().." )")
42 | if isParallelState(anc) then
38 | for _,child in ipairs(getChildStates(anc)) do
89 | if not statesToEnter:some(function(s) return isDescendant(s,child) end) then
11 | self:addDescendantStatesToEnter(child,statesToEnter,statesForDefaultEntry,defaultHistoryContent)
| end
| end
| end
| end
|
454 | closefunc()
| end
|
130 | 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
|
130 | function S:getTransitionDomain(t)
683 | startfunc('getTransitionDomain( t:'..t:inspect()..' )' )
| local result
683 | local tstates = self:getEffectiveTargetStates(t)
683 | if not tstates then
*****0 | result = nil
687 | 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
681 | result = findLCCA(t.source,t.targets or emptyList)
| end
683 | closefunc('-- getTransitionDomain result: '..tostring(result and result.id))
683 | return result
| end
|
130 | function S:getEffectiveTargetStates(transition)
1093 | startfunc('getEffectiveTargetStates( transition:'..transition:inspect()..' )')
1093 | local targets = OrderedSet()
1093 | if transition.targets then
2150 | for _,s in ipairs(transition.targets) do
1077 | if isHistoryState(s) then
15 | if self._historyValue[s.id] then
8 | 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
1062 | targets:add(s)
| end
| end
| end
1093 | closefunc('-- getEffectiveTargetStates result: '..targets:inspect())
1093 | return targets
| end
|
130 | function S:expandScxmlSource()
129 | self:convertInitials()
129 | self._stateById = {}
733 | for _,s in ipairs(self.states) do s:cacheReference(self._stateById) end
129 | self:resolveReferences(self._stateById)
| end
|
130 | function S:returnDoneEvent(donedata)
| -- TODO: implement
| end
|
130 | function S:invoke(invoke)
| -- TODO: implement <invoke>
2 | error("Invoke not supported.")
| end
|
130 | function S:donedata(state)
165 | local c = state._donedatas[1]
165 | 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
|
130 | function S:fireEvent(name,data,eventValues)
234 | eventValues = eventValues or {}
234 | eventValues.type = eventValues.type or 'platform'
234 | local event = LXSC.Event(name,data,eventValues)
234 | logloglog(string.format("-- queued %s event '%s'",event.type,event.name))
234 | if rawget(self,'onEventFired') then self.onEventFired(event) end
234 | self[eventValues.type=='external' and "_externalQueue" or "_internalQueue"]:enqueue(event)
234 | return event
| end
|
| -- Sensible aliases
130 | S.start = S.interpret
130 | S.restart = S.interpret
|
130 | function S:step()
33 | self:processDelayedSends()
33 | self:mainEventLoop()
| end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment