Skip to content

Commit

Permalink
Use a plain (sorted) list as event queue, for convenience
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexandreDecan committed Aug 21, 2018
1 parent f4d50d7 commit ca205b0
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 98 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ Unreleased
- (Added) An ``unbind`` method for an ``Interpreter``.
- (Added) A ``remove`` method on ``EventQueue`` and a corresponding ``cancel`` method on ``Interpreter``.
- (Changed) Meta-Event *step started* has a ``time`` attribute.
- (Changed) The current event queue can be consulted as a list using ``interpreter._event_queue``.
- (Fixed) Hook-errors reported by ``sismic-bdd`` CLI are a little bit more verbose (`#81 <https://github.com/AlexandreDecan/sismic/issues/81>`__).
- (Fixed) Optional transition for ``testing.transition_is_processed``, as previously promised by its documentation but not implemented.

- (Removed) Internal module ``sismic.interpreter.queue``.


1.3.0 (2018-07-06)
------------------
Expand Down
22 changes: 21 additions & 1 deletion docs/execution.rst
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,30 @@ For convenience, :py:meth:`~sismic.interpreter.Interpreter.queue` returns the in

.. testcode:: interpreter

interpreter.queue('click', 'clack').execute_once()
interpreter.queue('click', 'clack', 'clock').execute_once()

Notice that :py:meth:`~sismic.interpreter.Interpreter.execute_once` consumes at most one event at a time.
In the above example, the *clack* event is not yet processed.
This can be checked by looking at the current event queue of the interpreter.

.. testcode:: interpreter

for time, event in interpreter._event_queue:
print(event.name)

.. testoutput:: interpreter

clack
clock

Queued events can be removed from the queue by calling the :py:meth:`~sismic.interpreter.cancel` method of
the interpreter. This method accepts both the name of an event or an event instance, and remove the
first corresponding event from the queue.

.. testcode:: interpreter

interpreter.cancel('clock')
assert interpreter._event_queue == [(0, Event('clack'))]

To process all events **at once**, one can repeatedly call :py:meth:`~sismic.interpreter.Interpreter.execute_once` until
it returns a ``None`` value, meaning that nothing happened during the last call. For instance:
Expand Down
51 changes: 41 additions & 10 deletions sismic/interpreter/default.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import abc
import bisect
import warnings

from itertools import combinations
from typing import (Any, Callable, Dict, Iterable, List, Mapping, Optional,
Set, Tuple, Union, cast)
Expand All @@ -13,11 +16,22 @@
FinalState, InternalEvent, MacroStep, MetaEvent,
MicroStep, OrthogonalState, ShallowHistoryState,
Statechart, StateMixin, Transition)
from .queue import EventQueue

__all__ = ['Interpreter']


class _KeyifyList():
def __init__(self, inner, key):
self.inner = inner
self.key = key

def __len__(self):
return len(self.inner)

def __getitem__(self, k):
return self.key(self.inner[k])


class Interpreter:
"""
A discrete interpreter that executes a statechart according to a semantic close to SCXML
Expand Down Expand Up @@ -55,8 +69,8 @@ def __init__(self, statechart: Statechart, *,
# Set of active states
self._configuration = set() # type: Set[str]

# Event queue, contains (time, is_external, n, event) where n is tie-breaker
self._event_queue = EventQueue()
# Event queue
self._event_queue = [] # type: List[Tuple[float, Event]]

# Bound callables
self._bound = [] # type: List[Callable[[Event], Any]]
Expand Down Expand Up @@ -190,7 +204,7 @@ def queue(self, event_or_name: Union[str, Event], *events_or_names: Union[str, E
if isinstance(event, InternalEvent):
raise ValueError('Internal event cannot be queued, use Event or DelayedEvent instead.')
elif isinstance(event, Event):
self._event_queue.push(self.clock.time, event)
self._queue_event(event)
else:
raise ValueError('{} is not a string nor an Event instance.'.format(event))

Expand All @@ -204,7 +218,11 @@ def cancel(self, event_or_name: Union[str, Event]) -> bool:
:return: True if the event was found and removed, False otherwise.
"""
event = Event(event_or_name) if isinstance(event_or_name, str) else event_or_name
return self._event_queue.remove(event) is not None
for i, (time, queued_event) in enumerate(self._event_queue):
if queued_event == event:
self._event_queue.pop(i)
return True
return False

def execute(self, max_steps: int = -1) -> List[MacroStep]:
"""
Expand Down Expand Up @@ -281,6 +299,19 @@ def execute_once(self) -> Optional[MacroStep]:

return macro_step

def _queue_event(self, event: Event):
"""
Convenient helper to queue events to the current event queue.
:param event: Event to queue.
"""
time = self._time + (event.delay if isinstance(event, DelayedEvent) else 0)
position = bisect.bisect_right( # type: ignore
_KeyifyList(self._event_queue, lambda t: (t[0], not isinstance(t[1], InternalEvent))),
(time, not isinstance(event, InternalEvent))
)
self._event_queue.insert(position, (time, event))

def _raise_event(self, event: Union[InternalEvent, MetaEvent]) -> None:
"""
Raise an event from the statechart.
Expand All @@ -297,7 +328,7 @@ def _raise_event(self, event: Union[InternalEvent, MetaEvent]) -> None:
:param event: event to be sent by the statechart.
"""
if isinstance(event, InternalEvent):
self._event_queue.push(self.time, event)
self._queue_event(event)

if isinstance(event, DelayedEvent):
external_event = DelayedEvent(event.name, event.delay, **event.data)
Expand Down Expand Up @@ -352,11 +383,11 @@ def _select_event(self, *, consume: bool) -> Optional[Event]:
:param consume: Indicates whether event should be consumed.
:return: An instance of Event or None if no event is available
"""
if not self._event_queue.empty:
time, event = self._event_queue.first
if len(self._event_queue) > 0:
time, event = self._event_queue[0]
if time <= self.time:
if consume:
self._event_queue.pop()
if consume:
self._event_queue.pop(0)
return event
return None

Expand Down
86 changes: 0 additions & 86 deletions sismic/interpreter/queue.py

This file was deleted.

0 comments on commit ca205b0

Please sign in to comment.