1
0

async_realtime_environment.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import asyncio
  2. from time import monotonic
  3. from typing import Any, Optional, Union
  4. from simpy.core import EmptySchedule, Environment, Infinity, SimTime, StopSimulation
  5. from simpy.events import URGENT, Event
  6. from simpy.rt import RealtimeEnvironment
  7. class AsyncRealtimeEnvironment(RealtimeEnvironment):
  8. """A real-time simulation environment that uses asyncio.
  9. The methods step and run are a 1-1 copy of the original methods from simpy.rt.RealtimeEnvironment,
  10. except that they are async and await asyncio.sleep instead of time.sleep.
  11. """
  12. async def step(self) -> None:
  13. """Process the next event after enough real-time has passed for the
  14. event to happen.
  15. The delay is scaled according to the real-time :attr:`factor`. With
  16. :attr:`strict` mode enabled, a :exc:`RuntimeError` will be raised, if
  17. the event is processed too slowly.
  18. """
  19. evt_time = self.peek()
  20. if evt_time is Infinity:
  21. raise EmptySchedule()
  22. real_time = self.real_start + (evt_time - self.env_start) * self.factor
  23. if self.strict and monotonic() - real_time > self.factor:
  24. # Events scheduled for time *t* may take just up to *t+1*
  25. # for their computation, before an error is raised.
  26. delta = monotonic() - real_time
  27. raise RuntimeError(
  28. f'Simulation too slow for real time ({delta:.3f}s).'
  29. )
  30. # Sleep in a loop to fix inaccuracies of windows (see
  31. # https://stackoverflow.com/a/15967564 for details) and to ignore
  32. # interrupts.
  33. while True:
  34. delta = real_time - monotonic()
  35. if delta <= 0:
  36. break
  37. await asyncio.sleep(delta)
  38. Environment.step(self)
  39. async def run(
  40. self, until: Optional[Union[SimTime, Event]] = None
  41. ) -> Optional[Any]:
  42. """Executes :meth:`step()` until the given criterion *until* is met.
  43. - If it is ``None`` (which is the default), this method will return
  44. when there are no further events to be processed.
  45. - If it is an :class:`~simpy.events.Event`, the method will continue
  46. stepping until this event has been triggered and will return its
  47. value. Raises a :exc:`RuntimeError` if there are no further events
  48. to be processed and the *until* event was not triggered.
  49. - If it is a number, the method will continue stepping
  50. until the environment's time reaches *until*.
  51. """
  52. if until is not None:
  53. if not isinstance(until, Event):
  54. # Assume that *until* is a number if it is not None and
  55. # not an event. Create a Timeout(until) in this case.
  56. at: SimTime
  57. if isinstance(until, int):
  58. at = until
  59. else:
  60. at = float(until)
  61. if at <= self.now:
  62. raise ValueError(
  63. f'until(={at}) must be > the current simulation time.'
  64. )
  65. # Schedule the event before all regular timeouts.
  66. until = Event(self)
  67. until._ok = True
  68. until._value = None
  69. self.schedule(until, URGENT, at - self.now)
  70. elif until.callbacks is None:
  71. # Until event has already been processed.
  72. return until.value
  73. until.callbacks.append(StopSimulation.callback)
  74. try:
  75. while True:
  76. await self.step()
  77. except StopSimulation as exc:
  78. return exc.args[0] # == until.value
  79. except EmptySchedule as e:
  80. if until is not None:
  81. assert not until.triggered
  82. raise RuntimeError(
  83. f'No scheduled events left but "until" event was not '
  84. f'triggered: {until}'
  85. ) from e
  86. return None