statechart_execution.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. from typing import *
  2. from sccd.statechart.static.statechart import *
  3. from sccd.statechart.dynamic.event import *
  4. from sccd.util.debug import print_debug
  5. from sccd.util.bitmap import *
  6. from sccd.action_lang.static.scope import *
  7. from sccd.action_lang.dynamic.exceptions import *
  8. from sccd.util import timer
  9. # Set of current states etc.
  10. class StatechartExecution:
  11. def __init__(self, statechart: Statechart, instance):
  12. self.statechart = statechart
  13. self.instance = instance
  14. self.gc_memory = None
  15. self.rhs_memory = None
  16. self.raise_internal = None
  17. self.raise_next_bs = None
  18. # these 2 fields have the same information
  19. self.configuration: List[State] = []
  20. self.configuration_bitmap: Bitmap = Bitmap()
  21. self.eventless_states = 0 # number of states in current configuration that have at least one eventless outgoing transition.
  22. # mapping from configuration_bitmap (=int) to configuration (=List[State])
  23. self.config_mem = {}
  24. # mapping from history state id to states to enter if history is target of transition
  25. self.history_values: Dict[int, List[State]] = {}
  26. # For each AfterTrigger in the statechart tree, we keep an expected 'id' that is
  27. # a parameter to a future 'after' event. This 'id' is incremented each time a timer
  28. # is started, so we only respond to the most recent one.
  29. self.timer_ids = [-1] * len(statechart.tree.after_triggers)
  30. # output events accumulate here until they are collected
  31. self.output = []
  32. # enter default states
  33. def initialize(self):
  34. states = self.statechart.tree.root.target_states(self, True)
  35. self.configuration.extend(self.statechart.tree.state_list[id] for id in states.items())
  36. self.configuration_bitmap = states
  37. ctx = EvalContext(current_state=self, events=[], memory=self.rhs_memory)
  38. if self.statechart.datamodel is not None:
  39. self.statechart.datamodel.exec(self.rhs_memory)
  40. for state in self.configuration:
  41. print_debug(termcolor.colored(' ENTER %s'%state.gen.full_name, 'green'))
  42. self.eventless_states += state.gen.has_eventless_transitions
  43. self._perform_actions(ctx, state.enter)
  44. self._start_timers(state.gen.after_triggers)
  45. self.rhs_memory.flush_transition()
  46. self.rhs_memory.flush_round()
  47. self.gc_memory.flush_round()
  48. # events: list SORTED by event id
  49. def fire_transition(self, events: List[Event], t: Transition):
  50. try:
  51. timer.start("transition")
  52. timer.start("exit states")
  53. # Exit set is the intersection between self.configuration and t.gen.arena.descendants.
  54. # The following was found to have better performance than reverse-iterating and filtering self.configuration or t.arena.gen.descendants lists, despite the fact that Bitmap.reverse_items() isn't very efficient.
  55. exit_ids = self.configuration_bitmap & t.gen.arena.gen.descendants_bitmap
  56. exit_set = (self.statechart.tree.state_list[id] for id in exit_ids.reverse_items())
  57. # Alternative implementation:
  58. # if len(self.configuration) < len(t.gen.arena.gen.descendants):
  59. # exit_set = (s for s in self.configuration if s.gen.state_id_bitmap & t.gen.arena.gen.descendants_bitmap)
  60. # else:
  61. # exit_set = (s for s in t.gen.arena.gen.descendants if s.gen.state_id_bitmap & self.configuration_bitmap)
  62. timer.stop("exit states")
  63. timer.start("enter states")
  64. # Enter path is the intersection between:
  65. # 1) the transitions target + the target's ancestors and
  66. # 2) the arena's descendants
  67. enter_path = (t.targets[0].gen.ancestors_bitmap | t.targets[0].gen.state_id_bitmap) & t.gen.arena.gen.descendants_bitmap
  68. # Now, along the enter path, there may be AND-states whose children we don't explicitly enter, but should enter.
  69. # That's why we call 'target_states' on every state on the path and join the results.
  70. items = enter_path.items()
  71. shifted = itertools.chain(enter_path.items(), [-1])
  72. next(shifted) # throw away first value
  73. pairwise = zip(items, shifted)
  74. enter_ids = Bitmap()
  75. for state_id, next_state_id in pairwise:
  76. enter_ids |= self.statechart.tree.state_list[state_id].target_states(self, next_state_id == -1)
  77. enter_set = (self.statechart.tree.state_list[id] for id in enter_ids.items())
  78. timer.stop("enter states")
  79. ctx = EvalContext(current_state=self, events=events, memory=self.rhs_memory)
  80. print_debug("fire " + str(t))
  81. timer.start("exit states")
  82. # exit states...
  83. for s in exit_set:
  84. # remember which state(s) we were in if a history state is present
  85. for h in s.gen.history:
  86. if isinstance(h, DeepHistoryState):
  87. f = lambda s0: not s0.gen.descendants_bitmap and s0.gen.state_id_bitmap & s.gen.descendants_bitmap
  88. else:
  89. f = lambda s0: s0.gen.ancestors_bitmap and s0.parent == s
  90. self.history_values[h.gen.state_id] = list(filter(f, self.configuration))
  91. print_debug(termcolor.colored(' EXIT %s' % s.gen.full_name, 'green'))
  92. self.eventless_states -= s.gen.has_eventless_transitions
  93. # execute exit action(s)
  94. self._perform_actions(ctx, s.exit)
  95. # self.rhs_memory.pop_local_scope(s.scope)
  96. self.configuration_bitmap &= ~s.gen.state_id_bitmap
  97. timer.stop("exit states")
  98. # execute transition action(s)
  99. timer.start("actions")
  100. self.rhs_memory.push_frame(t.scope) # make room for event parameters on stack
  101. if t.trigger:
  102. t.trigger.copy_params_to_stack(ctx)
  103. self._perform_actions(ctx, t.actions)
  104. self.rhs_memory.pop_frame()
  105. timer.stop("actions")
  106. timer.start("enter states")
  107. # enter states...
  108. for s in enter_set:
  109. print_debug(termcolor.colored(' ENTER %s' % s.gen.full_name, 'green'))
  110. self.eventless_states += s.gen.has_eventless_transitions
  111. self.configuration_bitmap |= s.gen.state_id_bitmap
  112. # execute enter action(s)
  113. self._perform_actions(ctx, s.enter)
  114. self._start_timers(s.gen.after_triggers)
  115. timer.stop("enter states")
  116. try:
  117. self.configuration = self.config_mem[self.configuration_bitmap]
  118. except KeyError:
  119. self.configuration = self.config_mem[self.configuration_bitmap] = [s for s in self.statechart.tree.state_list if self.configuration_bitmap & s.gen.state_id_bitmap]
  120. self.rhs_memory.flush_transition()
  121. timer.stop("transition")
  122. except SCCDRuntimeException as e:
  123. e.args = ("During execution of transition %s:\n" % str(t) +str(e),)
  124. raise
  125. def check_guard(self, t, events) -> bool:
  126. try:
  127. # Special case: after trigger
  128. if isinstance(t.trigger, AfterTrigger):
  129. e = [e for e in events if bit(e.id) & t.trigger.enabling_bitmap][0] # it's safe to assume the list will contain one element cause we only check a transition's guard after we know it may be enabled given the set of events
  130. if self.timer_ids[t.trigger.after_id] != e.params[0]:
  131. return False
  132. if t.guard is None:
  133. return True
  134. else:
  135. ctx = EvalContext(current_state=self, events=events, memory=self.gc_memory)
  136. self.gc_memory.push_frame(t.scope)
  137. # Guard conditions can also refer to event parameters
  138. if t.trigger:
  139. t.trigger.copy_params_to_stack(ctx)
  140. result = t.guard.eval(self.gc_memory)
  141. self.gc_memory.pop_frame()
  142. return result
  143. except SCCDRuntimeException as e:
  144. e.args = ("While checking guard of transition %s:\n" % str(t) +str(e),)
  145. raise
  146. def check_source(self, t) -> bool:
  147. return self.configuration_bitmap & t.source.gen.state_id_bitmap
  148. @staticmethod
  149. def _perform_actions(ctx: EvalContext, actions: List[Action]):
  150. for a in actions:
  151. a.exec(ctx)
  152. def _start_timers(self, triggers: List[AfterTrigger]):
  153. for after in triggers:
  154. delay: Duration = after.delay.eval(
  155. EvalContext(current_state=self, events=[], memory=self.gc_memory))
  156. timer_id = self._next_timer_id(after)
  157. self.raise_next_bs(Event(id=after.id, name=after.name, params=[timer_id]), delay)
  158. def _next_timer_id(self, trigger: AfterTrigger):
  159. self.timer_ids[trigger.after_id] += 1
  160. return self.timer_ids[trigger.after_id]
  161. # Return whether the current configuration includes ALL the states given.
  162. def in_state(self, state_strings: List[str]) -> bool:
  163. state_ids_bitmap = Bitmap.from_list((self.statechart.tree.state_dict[state_string].gen.state_id for state_string in state_strings))
  164. in_state = self.configuration_bitmap.has_all(state_ids_bitmap)
  165. # if in_state:
  166. # print_debug("in state"+str(state_strings))
  167. # else:
  168. # print_debug("not in state"+str(state_strings))
  169. return in_state
  170. def collect_output(self) -> List[OutputEvent]:
  171. output = self.output
  172. self.output = []
  173. return output