Created
June 30, 2014 20:46
-
-
Save anonymous/c81c0275212944ad9cbf to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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