controller.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import queue
  2. import dataclasses
  3. from typing import Dict, List, Optional
  4. from sccd.controller.event_queue import *
  5. from sccd.statechart.dynamic.event import *
  6. from sccd.controller.object_manager import *
  7. from sccd.util.debug import print_debug
  8. from sccd.cd.cd import *
  9. def _dummy_output_callback(output_event):
  10. pass
  11. # The Controller class is a primitive that can be used to build backends of any kind:
  12. # Threads, integration with existing event loop, game loop, test framework, ...
  13. # The Controller class itself is NOT thread-safe.
  14. class Controller:
  15. __slots__ = ["cd", "object_manager", "queue", "simulated_time", "run_until"]
  16. @dataclasses.dataclass(eq=False, frozen=True)
  17. class EventQueueEntry:
  18. __slots__ = ["event", "targets"]
  19. event: InternalEvent
  20. targets: List[Instance]
  21. def __init__(self, cd: AbstractCD, output_callback: Callable[[List[OutputEvent]],None] = _dummy_output_callback):
  22. cd.globals.assert_ready()
  23. self.cd = cd
  24. self.simulated_time = 0 # integer
  25. def schedule_after(after, event, instances):
  26. entry = Controller.EventQueueEntry(event, instances)
  27. self.queue.add(self.simulated_time + after, entry)
  28. return entry
  29. def cancel_after(entry):
  30. self.queue.remove(entry)
  31. self.object_manager = ObjectManager(cd, output_callback, schedule_after, cancel_after)
  32. self.queue: EventQueue[int, EventQueueEntry] = EventQueue()
  33. if DEBUG:
  34. self.cd.print()
  35. print("Model delta is %s" % str(self.cd.globals.delta))
  36. # First call to 'run_until' method initializes
  37. self.run_until = self._run_until_w_initialize
  38. def get_model_delta(self) -> Duration:
  39. return self.cd.globals.delta
  40. def _schedule(self, timestamp: int, event: InternalEvent, instances: List[Instance]):
  41. self.queue.add(timestamp, Controller.EventQueueEntry(event, instances))
  42. def _inport_to_instances(self, port: str) -> List[Instance]:
  43. try:
  44. self.cd.globals.inports.get_id(port)
  45. except KeyError as e:
  46. raise Exception("No such port: '%s'" % port) from e
  47. # For now, we just broadcast all input events.
  48. # We don't even check if the event is allowed on the input port.
  49. # TODO: multicast event only to instances that subscribe to this port.
  50. return self.object_manager.instances
  51. def add_input(self, timestamp: int, port: str, event_name: str, params = []):
  52. try:
  53. event_id = self.cd.globals.events.get_id(event_name)
  54. except KeyError as e:
  55. raise Exception("No such event: '%s'" % event_name) from e
  56. instances = self._inport_to_instances(port)
  57. event = InternalEvent(event_id, event_name, params)
  58. self._schedule(timestamp, event, instances)
  59. # Get timestamp of next entry in event queue
  60. def next_wakeup(self) -> Optional[int]:
  61. return self.queue.earliest_timestamp()
  62. def _run_until_w_initialize(self, now: Optional[int]):
  63. # first run...
  64. # initialize the object manager, in turn initializing our default class
  65. # and adding the generated events to the queue
  66. for i in self.object_manager.instances:
  67. i.initialize()
  68. if DEBUG:
  69. print("initialized.")
  70. # Next call to 'run_until' will call '_run_until'
  71. self.run_until = self._run_until
  72. # Let's try it out :)
  73. self.run_until(now)
  74. # Run until the event queue has no more due events wrt given timestamp and until all instances are stable.
  75. # If no timestamp is given (now = None), run until event queue is empty.
  76. def _run_until(self, now: Optional[int]):
  77. # Actual "event loop"
  78. for timestamp, entry in self.queue.due(now):
  79. if timestamp != self.simulated_time:
  80. # make time leap
  81. self.simulated_time = timestamp
  82. if DEBUG:
  83. print("\ntime is now %s" % str(self.cd.globals.delta * self.simulated_time))
  84. # run all instances for whom there are events
  85. for instance in entry.targets:
  86. instance.big_step([entry.event])
  87. # print_debug("completed big step (time = %s)" % str(self.cd.globals.delta * self.simulated_time))
  88. self.simulated_time = now