Quellcode durchsuchen

Various bug fixes + improvements to digital watch model.

Bug fix
Joeri Exelmans vor 5 Jahren
Ursprung
Commit
2ff5a98dc8

+ 6 - 2
examples/digitalwatch/DigitalWatchGUI.py

@@ -150,7 +150,10 @@ class DigitalWatchGUI_Controller:
     # Check if time = alarm set time
     def checkTime(self):
         if self.GUI.getTime()[0] == self.GUI.getAlarm()[0] and self.GUI.getTime()[1] == self.GUI.getAlarm()[1] and self.GUI.getTime()[2] == self.GUI.getAlarm()[2]:
-            self.alarm()
+            # this method is called by the statechart during transition execution,
+            # do not send an event direcly to the statechart!
+            # do it later on, from tk's event loop, asap:
+            self.GUI.parent.after(0, self.alarm)
             return True
         else:
             return False
@@ -174,7 +177,8 @@ class DigitalWatchGUI_Static(Frame):
         self.curDate = [tmpDate[1], tmpDate[2], int(str(tmpDate[0])[3:]) ] 
         self.dateTag = None
         
-        self.curTime = list(localtime()[3:6])
+        # self.curTime = list(localtime()[3:6])
+        self.curTime = [11, 59, 55]
         self.timeTag = None
         
         self.curAlarm = [12, 0, 0]

Datei-Diff unterdrückt, da er zu groß ist
+ 437 - 392
examples/digitalwatch/model_digitalwatch.svg


+ 90 - 71
examples/digitalwatch/model_digitalwatch.xml

@@ -2,7 +2,11 @@
   <delta>1 ms</delta>
 
   <statechart>
-    <semantics big_step_maximality="take_one"/>
+    <semantics
+      big_step_maximality="take_one"
+      internal_event_lifeline="next_small_step"
+      input_event_lifeline="whole"
+    />
 
     <inport name="in">
       <event name="bottomLeftPressed"/>
@@ -34,11 +38,54 @@
       <event name="setAlarm"/>
     </outport>
 
-    <outport name="out">
-    </outport>
-
     <root>
       <parallel id="P">
+        <state id="Alarm" initial="Off">
+          <state id="Off">
+            <transition event="bottomLeftPressed" cond='INSTATE(["/P/Display/TimeUpdate"])' target="../On"/>
+          </state>
+
+          <state id="On" initial="NotBlinking">
+            <onentry>
+              <raise event="setAlarm"/>
+            </onentry>
+            <onexit>
+              <raise event="setAlarm"/>
+            </onexit>
+
+            <state id="NotBlinking">
+              <transition event="alarmStart" target="../Blinking"/>
+              <transition event="bottomLeftPressed" cond='INSTATE(["/P/Display/TimeUpdate"])' target="../../Off"/>
+            </state>
+
+            <state id="Blinking" initial="On">
+              <onexit>
+                <raise event="unsetIndiglo"/>
+              </onexit>
+              <state id="On">
+                <onentry>
+                  <raise event="setIndiglo"/>
+                </onentry>
+                <transition after="500 ms" target="../Off"/>
+              </state>
+              <state id="Off">
+                <onentry>
+                  <raise event="unsetIndiglo"/>
+                </onentry>
+                <transition after="500 ms" target="../On"/>
+              </state>
+              <!-- user interrupt alarm: also toggle off alarm -->
+              <transition event="topRightPressed" target="../../Off"/>
+              <transition event="topLeftPressed" target="../../Off"/>
+              <transition event="bottomRightPressed" target="../../Off"/>
+              <transition event="bottomLeftPressed" target="../../Off"/>
+              <!-- alarm over, everything remains like it was -->
+              <transition after="4 s" target="../NotBlinking"/>
+            </state>
+          </state>
+        </state>
+
+
         <state id="Indiglo" initial="Off">
           <state id="Off">
             <transition event="topRightPressed" target="../Pushed">
@@ -58,28 +105,35 @@
           </state>
         </state>
 
-        <state id="Chrono" initial="Stopped">
-          <state id="Stopped">
-            <transition event="bottomLeftPressed" cond='INSTATE(["/P/Display/ChronoUpdate"])' target=".">
-              <raise event="resetChrono"/>
-            </transition>
-            <transition event="bottomRightPressed" cond='INSTATE(["/P/Display/ChronoUpdate"])' target="../Running"/>
-          </state>
 
-          <state id="Running">
-            <transition after="10 ms" target=".">
-              <raise event="increaseChronoByOne"/>
+        <state id="ChronoWrapper">
+          <state id="Chrono" initial="Stopped">
+            <state id="Stopped">
+              <transition event="bottomRightPressed" cond='INSTATE(["/P/Display/ChronoUpdate"])' target="../Running"/>
+            </state>
+
+            <state id="Running">
+              <transition after="10 ms" target=".">
+                <raise event="increaseChronoByOne"/>
+                <raise event="int_refresh_chrono"/>
+              </transition>
+              <transition event="bottomRightPressed" cond='INSTATE(["/P/Display/ChronoUpdate"])' target="../Stopped"/>
+            </state>
+
+            <transition event="bottomLeftPressed" cond='INSTATE(["/P/Display/ChronoUpdate"])' target="Stopped">
+                <raise event="resetChrono"/>
+                <raise event="int_refresh_chrono"/>
             </transition>
-            <transition event="bottomRightPressed" cond='INSTATE(["/P/Display/ChronoUpdate"])' target="../Stopped"/>
           </state>
         </state>
 
+
         <state id="Display" initial="TimeUpdate">
           <state id="TimeUpdate">
             <onentry>
               <raise event="refreshTimeDisplay"/>
             </onentry>
-            <transition after="1 s" target="."/>
+            <transition event="int_refresh_time" target="."/>
             <transition event="topLeftPressed" target="../ChronoUpdate"/>
             <transition event="bottomRightPressed" target="../WaitingToEdit"/>
             <transition event="bottomLeftPressed" target="../WaitingForAlarm"/>
@@ -92,6 +146,16 @@
             <transition event="bottomRightReleased" target="../TimeUpdate"/>
           </state>
 
+          <state id="WaitingForAlarm">
+            <onentry>
+              <raise event="refreshAlarmDisplay"/>
+            </onentry>
+            <transition after="1500 ms" target="../EditingTime">
+              <raise event="alarm_edit"/>
+            </transition>
+            <transition event="bottomLeftReleased" target="../TimeUpdate"/>
+          </state>
+
           <state id="EditingTime" initial="Waiting">
             <onentry>
               <raise event="startSelection"/>
@@ -121,76 +185,31 @@
               <onentry>
                 <raise event="increaseSelection"/>
               </onentry>
-              <transition after="300 ms" target="."/>
+              <transition after="100 ms" target="."/>
               <transition event="bottomLeftReleased" target="../Waiting"/>
             </state>
           </state>
 
           <state id="ChronoUpdate">
-            <transition event="topLeftPressed" target="../TimeUpdate"/>
-            <transition after="10 ms" target=".">
-              <raise event="refreshChronoDisplay"/>
-            </transition>
-          </state>
-
-          <state id="WaitingForAlarm">
             <onentry>
-              <raise event="refreshAlarmDisplay"/>
+              <raise event="refreshChronoDisplay"/>
             </onentry>
-            <transition after="1500 ms" target="../EditingTime">
-              <raise event="alarm_edit"/>
-            </transition>
-            <transition event="bottomLeftReleased" target="../TimeUpdate"/>
+            <transition event="topLeftPressed" target="../TimeUpdate"/>
+            <transition event="int_refresh_chrono" target="."/>
           </state>
         </state>
 
-        <state id="Alarm" initial="Off">
-          <state id="Off">
-            <transition event="bottomLeftPressed" cond='INSTATE(["/P/Display/TimeUpdate"]) or INSTATE(["/P/Display/WaitingForAlarm"])' target="../On">
-              <raise event="setAlarm"/>
-            </transition>
-          </state>
-
-          <state id="On">
-            <onentry>
-              <raise event="checkTime"/>
-            </onentry>
-            <transition event="bottomLeftPressed" cond='INSTATE(["/P/Display/TimeUpdate"]) or INSTATE(["/P/Display/WaitingForAlarm"])' target="../Off">
-              <raise event="setAlarm"/>
-            </transition>
-            <transition after="1 s" target="."/>
-            <transition event="alarmStart" target="../Blinking"/>
-          </state>
-
-          <state id="Blinking" initial="On">
-            <onexit>
-              <raise event="setAlarm"/>
-              <raise event="unsetIndiglo"/>
-            </onexit>
-            <state id="On">
-              <onentry>
-                <raise event="setIndiglo"/>
-              </onentry>
-              <transition after="500 ms" target="../Off"/>
-            </state>
-            <state id="Off">
-              <onentry>
-                <raise event="unsetIndiglo"/>
-              </onentry>
-              <transition after="500 ms" target="../On"/>
-            </state>
-
-            <transition event="topRightPressed" target="../Off"/>
-            <transition event="topLeftPressed" target="../Off"/>
-            <transition event="bottomRightPressed" target="../Off"/>
-            <transition event="bottomLeftPressed" target="../Off"/>
-          </state>
-        </state>
 
         <state id="Time" initial="Increasing">
           <state id="Increasing">
+            <transition after="1 s" cond='INSTATE(["/P/Alarm/On"])' target=".">
+              <raise event="increaseTimeByOne"/>
+              <raise event="checkTime"/>
+              <raise event="int_refresh_time"/>
+            </transition>
             <transition after="1 s" target=".">
               <raise event="increaseTimeByOne"/>
+              <raise event="int_refresh_time"/>
             </transition>
             <transition event="time_edit" target="../Editing"/>
           </state>

+ 2 - 4
examples/digitalwatch/run.py

@@ -3,9 +3,6 @@ from sccd.realtime.tkinter import TkInterImplementation
 from sccd.cd.parser.xml import *
 
 def main():
-    # Load statechart
-    cd = load_cd("model_digitalwatch.xml")
-
     import tkinter
     from tkinter.constants import NO
     from DigitalWatchGUI import DigitalWatchGUI
@@ -31,7 +28,8 @@ def main():
             method = getattr(gui.controller, event.name)
             method()
 
-    controller = Controller(cd, on_output)
+    cd = load_cd("model_digitalwatch.xml")
+    controller = Controller(cd, output_callback=on_output)
     eventloop = EventLoop(controller, TkInterImplementation(tk))
 
     eventloop.start()

+ 10 - 7
src/sccd/controller/controller.py

@@ -22,8 +22,11 @@ class Controller:
         event: InternalEvent
         targets: List[Instance]
 
+        def __repr__(self):
+            return "QueueEntry("+str(self.event)+")"
 
-    def __init__(self, cd: AbstractCD, output_callback: Callable[[List[OutputEvent]],None] = _dummy_output_callback):
+
+    def __init__(self, cd: AbstractCD, output_callback: Callable[[OutputEvent],None] = _dummy_output_callback):
         cd.globals.assert_ready()
         self.cd = cd
 
@@ -52,10 +55,10 @@ class Controller:
     def get_model_delta(self) -> Duration:
         return self.cd.globals.delta
 
-    def _schedule(self, timestamp: int, event: InternalEvent, instances: List[Instance]):
+    def schedule(self, timestamp: int, event: InternalEvent, instances: List[Instance]):
         self.queue.add(timestamp, Controller.EventQueueEntry(event, instances))
 
-    def _inport_to_instances(self, port: str) -> List[Instance]:
+    def inport_to_instances(self, port: str) -> List[Instance]:
         try:
             self.cd.globals.inports.get_id(port)
         except KeyError as e:
@@ -72,10 +75,10 @@ class Controller:
         except KeyError as e:
             raise Exception("No such event: '%s'" % event_name) from e
 
-        instances = self._inport_to_instances(port)
+        instances = self.inport_to_instances(port)
         event = InternalEvent(event_id, event_name, params)
 
-        self._schedule(timestamp, event, instances)
+        self.schedule(timestamp, event, instances)
 
     # Get timestamp of next entry in event queue
     def next_wakeup(self) -> Optional[int]:
@@ -106,9 +109,9 @@ class Controller:
                 self.simulated_time = timestamp
                 if DEBUG:
                     print("\ntime is now %s" % str(self.cd.globals.delta * self.simulated_time))
+            # print("popped", entry)
+            # print("remaining", self.queue)
             # run all instances for whom there are events
             for instance in entry.targets:
                 instance.big_step([entry.event])
                 # print_debug("completed big step (time = %s)" % str(self.cd.globals.delta * self.simulated_time))
-
-        self.simulated_time = now

+ 26 - 24
src/sccd/controller/event_queue.py

@@ -14,50 +14,52 @@ class EventQueue(Generic[Timestamp, Item]):
         self.queue: List[Tuple[Timestamp, int, Item]] = []
         self.counters = {} # mapping from timestamp to number of items at timestamp
         self.removed: Set[Item] = set()
-    
+
     def __str__(self):
-        return str([entry for entry in self.queue if entry[2] not in self.removed])
-    
-    def is_empty(self) -> bool:
-        return not [item for item in self.queue if not item[2] in self.removed]
-    
+        return str(sorted([tup for tup in self.queue if tup[2] not in self.removed]))
+
     def earliest_timestamp(self) -> Optional[Timestamp]:
-        while self.queue and (self.queue[0] in self.removed):
-            item = heappop(self.queue)
-            self.removed.remove(item[2])
-        try:
-            return self.queue[0][0]
-        except IndexError:
-            return None
-    
+        with timer.Context("event_queue"):
+            while self.queue and (self.queue[0][2] in self.removed):
+                tup = heappop(self.queue)
+                self.removed.remove(tup[2])
+            try:
+                return self.queue[0][0]
+            except IndexError:
+                return None
+
     def add(self, timestamp: Timestamp, item: Item):
+        # print("add", item)
         with timer.Context("event_queue"):
             self.counters[timestamp] = self.counters.setdefault(timestamp, 0) + 1
             def_event = (timestamp, self.counters[timestamp], item)
             heappush(self.queue, def_event)
-    
+
     def remove(self, item: Item):
+        # print("remove", item)
         with timer.Context("event_queue"):
             self.removed.add(item)
             if len(self.removed) > 100:
-                self.queue = [x for x in self.queue if x not in self.removed]
+                self.queue = [x for x in self.queue if x[2] not in self.removed]
+                # print("heapify")
                 heapify(self.queue)
-                self.removed = set()
+                self.removed.clear()
 
     # Raises exception if called on empty queue
     def pop(self) -> Tuple[Timestamp, Item]:
         with timer.Context("event_queue"):
             while 1:
-                item = heappop(self.queue)
-                timestamp = item[0]
-                self.counters[timestamp] -= 1
-                if not self.counters[timestamp]:
+                timestamp, n, item = heappop(self.queue)
+                if self.counters[timestamp] == n:
                     del self.counters[timestamp]
-                if item[2] not in self.removed:
-                    return (timestamp, item[2])
+                if item not in self.removed:
+                    return (timestamp, item)
 
     def is_due(self, timestamp: Optional[Timestamp]) -> bool:
-        return len(self.queue) and (timestamp == None or self.queue[0][0] <= timestamp)
+        earliest = self.earliest_timestamp()
+        # print("is_due", earliest, timestamp, earliest is not None and (timestamp is None or earliest <= timestamp))
+
+        return earliest is not None and (timestamp is None or earliest <= timestamp)
 
     # Safe to call on empty queue
     # Safe to call other methods on the queue while the returned generator exists

+ 4 - 4
src/sccd/controller/object_manager.py

@@ -15,12 +15,12 @@ class ObjectManager(Instance):
         self.schedule_callback = schedule_callback
         self.cancel_callback = cancel_callback
 
-        # set of all instances in the runtime
-        # we need to maintain this set in order to do broadcasts
-        self.instances = [self] # object manager is an instance too!
 
         i = StatechartInstance(cd.get_default_class(), self, self.output_callback, self.schedule_callback, self.cancel_callback)
-        self.instances.append(i)
+        
+        # set of all instances in the runtime
+        # we need to maintain this set in order to do broadcasts
+        self.instances = [i]
 
     def _create(self, class_name) -> StatechartInstance:
         statechart_model = self.cd.classes[class_name]

+ 48 - 70
src/sccd/controller/test_event_queue.py

@@ -1,83 +1,61 @@
 import unittest
-from event_queue import EventQueue, EventQueueDeque
-
-def add_pop(q, unit):
-  unit.assertEqual(q.earliest_timestamp(), None)
-  q.add(10, 'a')
-  q.add(11, 'b')
-  q.add(11, 'c')
-  q.add(11, 'd')
-  q.add(11, 'e')
-  q.remove('c')
-  q.add(11, 'f')
-  q.add(11, 'g')
-  unit.assertEqual(q.earliest_timestamp(), 10)
-  unit.assertEqual(q.pop(), (10,'a'))
-  unit.assertEqual(q.pop(), (11,'b'))
-  unit.assertEqual(q.pop(), (11,'d'))
-  unit.assertEqual(q.pop(), (11,'e'))
-  unit.assertEqual(q.pop(), (11,'f'))
-  unit.assertEqual(q.pop(), (11,'g'))
-  unit.assertEqual(q.earliest_timestamp(), None)
-
-def add_remove(q, unit):
-  class X:
-    n = 0
-    def __init__(self):
-      self.x = X.n
-      X.n += 1
-    def __repr__(self):
-      return "x%d"%self.x
-  def testrange(N):
-    Xs = []
-    for i in range(10):
-      x = X()
-      Xs.append(x)
-      q.add(i, x)
-    unit.assertFalse(q.is_empty())
-    for x in Xs:
-      q.remove(x)
-    unit.assertTrue(q.is_empty())
-  testrange(1)
-  testrange(10)
-  testrange(100)
-  testrange(1000)
-
-def due(q, unit):
-  q.add(10, 'a')
-  q.add(11, 'b')
-  q.add(12, 'c')
-  q.add(20, 'd')
-  ctr = 0
-  for x in q.due(15):
-    ctr += 1
-  unit.assertEqual(ctr, 3)
+from event_queue import EventQueue
   
 
 class TestEventQueue(unittest.TestCase):
 
   def test_add_pop(self):
     q = EventQueue()
-    add_pop(q, self)
+    self.assertEqual(q.earliest_timestamp(), None)
+    q.add(10, 'a')
+    q.add(11, 'b')
+    q.add(11, 'c')
+    q.add(11, 'd')
+    q.add(11, 'e')
+    q.remove('c')
+    q.add(11, 'f')
+    q.add(11, 'g')
+    self.assertEqual(q.earliest_timestamp(), 10)
+    self.assertEqual(q.pop(), (10,'a'))
+    self.assertEqual(q.pop(), (11,'b'))
+    self.assertEqual(q.pop(), (11,'d'))
+    self.assertEqual(q.pop(), (11,'e'))
+    self.assertEqual(q.pop(), (11,'f'))
+    self.assertEqual(q.pop(), (11,'g'))
+    self.assertEqual(q.earliest_timestamp(), None)
 
   def test_add_remove(self):
     q = EventQueue()
-    add_remove(q, self)
-
-  def test_due(self):
-    q = EventQueue()
-    due(q, self)
 
-class TestEventQueueDeque(unittest.TestCase):
-
-  def test_add_pop(self):
-    q = EventQueueDeque()
-    add_pop(q, self)
-
-  def test_add_remove(self):
-    q = EventQueueDeque()
-    add_remove(q, self)
+    class X:
+      n = 0
+      def __init__(self):
+        self.x = X.n
+        X.n += 1
+      def __repr__(self):
+        return "x%d"%self.x
+
+    def testrange(N):
+      Xs = []
+      for i in range(10):
+        x = X()
+        Xs.append(x)
+        q.add(i, x)
+      for x in Xs:
+        q.remove(x)
+
+    testrange(1)
+    testrange(10)
+    testrange(100)
+    testrange(1000)
 
   def test_due(self):
-    q = EventQueueDeque()
-    due(q, self)
+    q = EventQueue()
+    q.add(10, 'a')
+    q.add(11, 'b')
+    q.add(12, 'c')
+    q.add(20, 'd')
+    ctr = 0
+    for x in q.due(15):
+      ctr += 1
+    self.assertEqual(ctr, 3)

+ 2 - 4
src/sccd/realtime/eventloop.py

@@ -20,16 +20,14 @@ class EventLoopImplementation(ABC):
 
 
 class EventLoop:
-    # def __init__(self, cd: AbstractCD, event_loop: EventLoopImplementation, output_callback: Callable[[List[Event]],None], time_impl: TimeImplementation = DefaultTimeImplementation):
     def __init__(self, controller: Controller, event_loop: EventLoopImplementation, time_impl: TimeImplementation = DefaultTimeImplementation):
         delta = controller.get_model_delta()
         self.timer = Timer(time_impl, unit=delta) # will give us timestamps in model unit
-        # self.controller = Controller(cd)
         self.controller = controller
         self.event_loop = event_loop
 
         # got to convert from model unit to eventloop native unit for scheduling
-        self.event_loop_convert = lambda x: int(get_conversion_f(delta, event_loop.time_unit())(x))
+        self.to_event_loop_unit = lambda x: int(get_conversion_f(delta, event_loop.time_unit())(x))
 
         self.scheduled = None
 
@@ -42,7 +40,7 @@ class EventLoop:
         if next_wakeup:
             # (next_wakeup - now) is negative, we are running behind
             # not much we can do about it though
-            sleep_duration = max(0, self.event_loop_convert(next_wakeup - now))
+            sleep_duration = max(0, self.to_event_loop_unit(next_wakeup - now))
             self.scheduled = self.event_loop.schedule(sleep_duration, self._wakeup)
         else:
             self.scheduled = None

+ 0 - 55
src/sccd/statechart/dynamic/event.py

@@ -45,58 +45,3 @@ class OutputEvent:
         return termcolor.colored(s, 'yellow')
 
     __repr__ = __str__
-
-# # A raised event.
-# class Event:
-#     __slots__ = ["id", "name", "port", "params"]
-
-#     def __init__(self, id, name, port = "", params = []):
-#         self.id: int = id
-#         self.name: str = name
-#         self.port: str = port
-#         self.params: List[Any] = params
-
-#     def __eq__(self, other):
-#         return self.id == other.id and self.port == other.port and self.params == other.params
-
-#     def __str__(self):
-#         if self.port:
-#             s = "Event("+self.port+"."+self.name
-#         else:
-#             s = "Event("+self.name
-#         if self.params:
-#             s += str(self.params)
-#         s += ")"
-#         return termcolor.colored(s, 'yellow')
-
-#     def __repr__(self):
-#         return self.__str__()
-
-# # Abstract class.
-# class EventTarget(ABC):
-#     __slots__ = []
-
-#     @abstractmethod
-#     def __init__(self):
-#         pass
-
-# # A raised output event with a target and a time offset.
-# class OutputEvent:
-#     __slots__ = ["event", "target", "time_offset"]
-
-#     def __init__(self, event: Event, target: EventTarget, time_offset: int = (0)):
-#         self.event = event
-#         self.target = target
-#         self.time_offset = time_offset
-
-# class OutputPortTarget(EventTarget):
-#     __slots__ = ["outport"]
-
-#     def __init__(self, outport: str):
-#         self.outport = outport
-
-# class InstancesTarget(EventTarget):
-#     __slots__ = ["instances"]
-
-#     def __init__(self, instances):
-#         self.instances = instances

+ 8 - 0
src/sccd/statechart/dynamic/round.py

@@ -117,6 +117,10 @@ class Round(ABC):
                 print_debug("completed "+self.name)
             return (changed, stable)
 
+    def reset(self):
+        self.remainder_events = []
+        self.next_events = []
+
     @abstractmethod
     def _run(self, forbidden_arenas: Bitmap) -> RoundResult:
         pass
@@ -168,6 +172,10 @@ class SuperRound(Round):
     def __repr__(self):
         return self.name + " > " + self.subround.__repr__()
 
+    def reset(self):
+        super().reset()
+        self.subround.reset()
+
     def _run(self, forbidden_arenas: Bitmap) -> RoundResult:
         arenas_changed = Bitmap()
         arenas_stabilized = Bitmap()

+ 3 - 1
src/sccd/statechart/dynamic/statechart_execution.py

@@ -137,7 +137,9 @@ class StatechartExecution:
 
     def _cancel_timers(self, triggers: List[AfterTrigger]):
         for after in triggers:
-            self.cancel_callback(self.timer_ids[after.after_id])
+            if self.timer_ids[after.after_id] is not None:
+                self.cancel_callback(self.timer_ids[after.after_id])
+                self.timer_ids[after.after_id] = None
 
     # Return whether the current configuration includes ALL the states given.
     def in_state(self, state_strings: List[str]) -> bool:

+ 1 - 0
src/sccd/statechart/dynamic/statechart_instance.py

@@ -144,6 +144,7 @@ class StatechartInstance(Instance):
     # perform a big step. generating a set of output events
     def big_step(self, input_events: List[InternalEvent]):
         # print_debug('attempting big step, input_events='+str(input_events))
+        self._big_step.reset()
         self.set_input(input_events)
         self._big_step.run_and_cycle_events()
 

+ 4 - 1
src/sccd/statechart/parser/xml.py

@@ -230,7 +230,10 @@ def statechart_parser_rules(globals, path, load_external = True, parse_f = parse
             # after-events should only be generated by the runtime
             # by putting a '+' in the event name (which isn't an allowed character in the parser),
             # we can be certain that the user will never generate a valid after-event.
-            event_name = "+after%d" % after_id # transition gets unique event name
+            if DEBUG:
+              event_name = "+%d_%s_%s" % (after_id, parent.short_name, target_string) # transition gets unique event name
+            else:
+              event_name = "+%d" % after_id
             transition.trigger = AfterTrigger(globals.events.assign_id(event_name), event_name, after_id, after_expr)
             after_id += 1
 

+ 7 - 0
src/sccd/statechart/static/statechart.py

@@ -62,6 +62,13 @@ class SemanticConfiguration:
   priority: SemanticChoice[Priority] = Priority.SOURCE_PARENT
   concurrency: SemanticChoice[Concurrency] = Concurrency.SINGLE
 
+  def __str__(self):
+    s = ""
+    for f in fields(self):
+      s += "\n  %s: %s" % (f.name, getattr(self, f.name))
+    return s
+
+
   @classmethod
   def get_fields(cls) -> Iterator[Tuple[str, SemanticAspect]]:
     return ((f.name, type(f.default)) for  f in fields(cls))

+ 1 - 1
src/sccd/test/run.py

@@ -57,7 +57,7 @@ class Test(unittest.TestCase):
       controller = Controller(test.cd, on_output)
 
       for i in test.input:
-        controller._schedule(i.timestamp.eval(None), i.event, controller._inport_to_instances(i.port))
+        controller.schedule(i.timestamp.eval(None), i.event, controller.inport_to_instances(i.port))
 
       def controller_thread():
         try:

+ 1 - 2
src/sccd/test/xml.py

@@ -81,8 +81,7 @@ def test_parser_rules(statechart_parser_rules):
         if not variant:
           return ""
         text = "Semantic variant %d of %d:" % (i+1, len(variants))
-        for f in fields(variant):
-          text += "\n  %s: %s" % (f.name, getattr(variant, f.name))
+        text += str(variant)
         return text
 
       return [TestVariant(

+ 1 - 1
src/sccd/util/timer.py

@@ -27,7 +27,7 @@ if TIMINGS:
 
   def _print_stats():
       print("\ntimings:")
-      for key,val in timings.items():
+      for key,val in sorted(timings.items()):
         print("  %s: %.2f ms / %d = %.3f ms" % (key,val*1000,counts[key],val*1000/counts[key]))
 
   atexit.register(_print_stats)

+ 7 - 0
test/test_files/semantics/event_lifeline/statechart_flat.xml

@@ -8,6 +8,7 @@
   <outport name="out">
     <event name="in_b"/>
     <event name="in_c"/>
+    <event name="in_d"/>
   </outport>
 
   <root initial="a">
@@ -26,6 +27,12 @@
       <onentry>
         <raise event="in_c"/>
       </onentry>
+      <transition event="f" target="/d"/>
+    </state>
+    <state id="d">
+      <onentry>
+        <raise event="in_d"/>
+      </onentry>
     </state>
   </root>
 </statechart>

+ 1 - 1
test/test_files/semantics/event_lifeline/test_flat_nextbs.xml

@@ -4,7 +4,7 @@
     <override_semantics
       big_step_maximality="take_one, take_many"
       internal_event_lifeline="queue"
-      input_event_lifeline="*"/>
+      input_event_lifeline="first_small_step, first_combo_step"/>
   </statechart>
   <input>
       <event name="e" port="in" time="0 d"/>

+ 21 - 0
test/test_files/semantics/event_lifeline/test_flat_queue_whole_takemany.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" ?>
+<test>
+  <statechart src="statechart_flat.xml">
+    <override_semantics
+      big_step_maximality="take_many"
+      internal_event_lifeline="queue"
+      input_event_lifeline="whole"/>
+  </statechart>
+  <input>
+      <event name="e" port="in" time="0 d"/>
+  </input>
+  <output>
+    <big_step>
+      <event name="in_b" port="out"/>
+    </big_step>
+    <big_step>
+      <event name="in_c" port="out"/>
+      <event name="in_d" port="out"/>
+    </big_step>
+  </output>
+</test>

+ 19 - 0
test/test_files/semantics/event_lifeline/test_flat_remainder.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" ?>
+<test>
+  <statechart src="statechart_flat.xml">
+    <override_semantics
+      big_step_maximality="take_many"
+      internal_event_lifeline="remainder"
+      input_event_lifeline="*"/>
+  </statechart>
+  <input>
+      <event name="e" port="in" time="0 d"/>
+  </input>
+  <output>
+    <big_step>
+      <event name="in_b" port="out"/>
+      <event name="in_c" port="out"/>
+      <event name="in_d" port="out"/>
+    </big_step>
+  </output>
+</test>

test/test_files/semantics/event_lifeline/test_ortho_nextbs.xml → test/test_files/semantics/event_lifeline/test_ortho_queue.xml