Browse Source

AfterTrigger is stateless again (as it should be)

Joeri Exelmans 5 years ago
parent
commit
bb3cd2a13f

+ 12 - 2
src/sccd/execution/statechart_state.py

@@ -34,6 +34,11 @@ class StatechartState:
     # mapping from history state id to states to enter if history is target of transition
     self.history_values = {}
 
+    # For each AfterTrigger in the statechart tree, we keep an expected 'id' that is
+    # a parameter to a future 'after' event. This 'id' is incremented each time a timer
+    # is started, so we only respond to the most recent one.
+    self.timer_ids = [-1] * len(statechart.tree.after_triggers)
+
     # output events accumulate here until they are collected
     self.output = []
 
@@ -112,7 +117,7 @@ class StatechartState:
       # Special case: after trigger
       if isinstance(t.trigger, AfterTrigger):
         e = [e for e in events if e.id == t.trigger.id][0]
-        if t.trigger.expected_id != e.parameters[0]:
+        if self.timer_ids[t.trigger.after_id] != e.parameters[0]:
           return False
 
       if t.guard is None:
@@ -141,12 +146,17 @@ class StatechartState:
   def _start_timers(self, triggers: List[AfterTrigger]):
       for after in triggers:
           delay: Duration = after.delay.eval(self, [], self.gc_memory)
+          timer_id = self._next_timer_id(after)
           self.gc_memory.flush_temp()
           self.output.append(OutputEvent(
-              Event(id=after.id, name=after.name, parameters=[after.nextTimerId()]),
+              Event(id=after.id, name=after.name, parameters=[timer_id]),
               target=InstancesTarget([self.instance]),
               time_offset=delay))
 
+  def _next_timer_id(self, trigger: AfterTrigger):
+    self.timer_ids[trigger.after_id] += 1
+    return self.timer_ids[trigger.after_id]
+
   # Return whether the current configuration includes ALL the states given.
   def in_state(self, state_strings: List[str]) -> bool:
       state_ids_bitmap = Bitmap.from_list((self.model.tree.state_dict[state_string].gen.state_id for state_string in state_strings))

+ 1 - 1
src/sccd/legacy/xml_loader.py

@@ -181,7 +181,7 @@ def load_statechart(scxml_node, namespace: Namespace) -> Statechart:
       next_after_id += 1
       trigger = AfterTrigger(namespace.assign_event_id(event), event, IntLiteral(int(after)))
     elif event is not None:
-      trigger = Trigger(namespace.assign_event_id(event), event, port)
+      trigger = EventTrigger(namespace.assign_event_id(event), event, port)
     else:
       trigger = None
     transition.setTrigger(trigger)

+ 2 - 2
src/sccd/parser/statechart_parser.py

@@ -326,12 +326,12 @@ class TreeParser(StateParser):
               msg += "\n Hint: Did you forget a duration unit sufix? ('s', 'ms', ...)"
             raise Exception(msg)
           event = "_after%d" % next_after_id # transition gets unique event name
+          trigger = AfterTrigger(globals.events.assign_id(event), event, next_after_id, after_expr)
           next_after_id += 1
-          trigger = AfterTrigger(globals.events.assign_id(event), event, after_expr)
         except Exception as e:
           self._raise(t_el, "after=\"%s\": %s" % (after, str(e)), e)
       elif event is not None:
-        trigger = Trigger(globals.events.assign_id(event), event, port)
+        trigger = EventTrigger(globals.events.assign_id(event), event, port)
         globals.inports.assign_id(port)
       else:
         trigger = None

+ 8 - 13
src/sccd/syntax/tree.py

@@ -81,7 +81,7 @@ class ParallelState(State):
         return targets
 
 @dataclass
-class Trigger:
+class EventTrigger:
     id: int # event ID
     name: str # event name
     port: str
@@ -92,21 +92,14 @@ class Trigger:
         else:
             return self.name
 
-class AfterTrigger(Trigger):
+class AfterTrigger(EventTrigger):
     # id: unique within the statechart
-    def __init__(self, id: int, name: str, delay: Expression):
+    def __init__(self, id: int, name: str, after_id: int, delay: Expression):
+
         super().__init__(id=id, name=name, port="")
+        self.after_id = after_id # unique ID for AfterTrigger
         self.delay = delay
 
-        # Stateful variable, incremented each time a new future 'after' event is scheduled.
-        # This is to distinguish multiple scheduled future events for the same after-transition.
-        # Only one scheduled event should be responded to, i.e. the latest one.
-        self.expected_id = -1
-
-    def nextTimerId(self) -> int:
-        self.expected_id += 1
-        return self.expected_id
-
     def render(self) -> str:
         return "after("+self.delay.render()+")"
 
@@ -118,7 +111,7 @@ class Transition:
 
     guard: Optional[Expression] = None
     actions: List[Action] = field(default_factory=list)
-    trigger: Optional[Trigger] = None
+    trigger: Optional[EventTrigger] = None
 
     gen: Optional['TransitionOptimization'] = None        
                     
@@ -140,6 +133,7 @@ class StateTree:
         self.state_dict = {} # mapping from 'full name' to State
         self.state_list = [] # depth-first list of states
         self.transition_list = [] # all transitions in the tree, sorted by source state, depth-first
+        self.after_triggers = []
 
         next_id = 0
 
@@ -170,6 +164,7 @@ class StateTree:
                     has_eventless_transitions = True
                 elif isinstance(t.trigger, AfterTrigger):
                     after_triggers.append(t.trigger)
+                    self.after_triggers.append(t.trigger)
 
             for c in state.children:
                 init_state(c, full_name, [state] + ancestors)