1======
2Events
3======
4
5.. currentmodule:: simpy.events
6
7SimPy includes an extensive set of event types for various purposes. All of
8them inherit :class:`simpy.events.Event`. The listing below shows the
9hierarchy of events built into SimPy::
10
11   events.Event
12   |
13   +— events.Timeout
14   |
15   +— events.Initialize
16   |
17   +— events.Process
18   |
19   +— events.Condition
20   |  |
21   |  +— events.AllOf
22   |  |
23   |  +— events.AnyOf
24   .
25   .
26   .
27
28This is the set of basic events. Events are extensible and resources, for
29example, define additional events. In this guide, we'll focus on the events in
30the :mod:`simpy.events` module. The :doc:`guide to resources <resources>`
31describes the various resource events.
32
33
34Event basics
35============
36
37SimPy events are very similar – if not identical — to deferreds, futures or
38promises. Instances of the class :class:`Event` are used to describe any kind
39of events. Events can be in one of the following states. An event
40
41- might happen (not triggered),
42- is going to happen (triggered) or
43- has happened (processed).
44
45They traverse these states exactly once in that order. Events are also tightly
46bound to time and time causes events to advance their state.
47
48Initially, events are not triggered and just objects in memory.
49
50If an event gets triggered, it is scheduled at a given time and inserted into
51SimPy's event queue. The property :attr:`Event.triggered` becomes ``True``.
52
53As long as the event is not *processed*, you can add *callbacks* to an event.
54Callbacks are callables that accept an event as parameter and are stored in the
55:attr:`Event.callbacks` list.
56
57An event becomes *processed* when SimPy pops it from the event queue and
58calls all of its callbacks. It is now no longer possible to add callbacks. The
59property :attr:`Event.processed` becomes ``True``.
60
61Events also have a *value*. The value can be set before or when the event is
62triggered and can be retrieved via :attr:`Event.value` or, within a process, by
63yielding the event (``value = yield event``).
64
65
66Adding callbacks to an event
67----------------------------
68
69"What? Callbacks? I've never seen no callbacks!", you might think if you have
70worked your way through the :doc:`tutorial <../simpy_intro/index>`.
71
72That's on purpose. The most common way to add a callback to an event is
73yielding it from your process function (``yield event``). This will add the
74process' *_resume()* method as a callback. That's how your process gets resumed
75when it yielded an event.
76
77However, you can add any callable object (function) to the list of callbacks
78as long as it accepts an event instance as its single parameter:
79
80.. code-block:: python
81
82   >>> import simpy
83   >>>
84   >>> def my_callback(event):
85   ...     print('Called back from', event)
86   ...
87   >>> env = simpy.Environment()
88   >>> event = env.event()
89   >>> event.callbacks.append(my_callback)
90   >>> event.callbacks
91   [<function my_callback at 0x...>]
92
93If an event has been *processed*, all of its :attr:`Event.callbacks` have been
94executed and the attribute is set to ``None``. This is to prevent you from
95adding more callbacks – these would of course never get called because the
96event has already happened.
97
98Processes are smart about this, though. If you yield a processed event,
99*_resume()* will immediately resume your process with the value of the event
100(because there is nothing to wait for).
101
102
103Triggering events
104-----------------
105
106When events are triggered, they can either *succeed* or *fail*. For example, if
107an event is to be triggered at the end of a computation and everything works
108out fine, the event will *succeed*. If an exception occurs during that
109computation, the event will *fail*.
110
111To trigger an event and mark it as successful, you can use
112``Event.succeed(value=None)``. You can optionally pass a *value* to it (e.g.,
113the results of a computation).
114
115To trigger an event and mark it as failed, call ``Event.fail(exception)``
116and pass an :class:`Exception` instance to it (e.g., the exception you caught
117during your failed computation).
118
119There is also a generic way to trigger an event: ``Event.trigger(event)``.
120This will take the value and outcome (success or failure) of the event passed
121to it.
122
123All three methods return the event instance they are bound to. This allows you
124to do things like ``yield Event(env).succeed()``.
125
126
127Example usages for ``Event``
128============================
129
130The simple mechanics outlined above provide a great flexibility in the way
131events (even the basic :class:`Event`) can be used.
132
133One example for this is that events can be shared. They can be created by a
134process or outside of the context of a process. They can be passed to other
135processes and chained:
136
137.. code-block:: python
138
139    >>> class School:
140    ...     def __init__(self, env):
141    ...         self.env = env
142    ...         self.class_ends = env.event()
143    ...         self.pupil_procs = [env.process(self.pupil()) for i in range(3)]
144    ...         self.bell_proc = env.process(self.bell())
145    ...
146    ...     def bell(self):
147    ...         for i in range(2):
148    ...             yield self.env.timeout(45)
149    ...             self.class_ends.succeed()
150    ...             self.class_ends = self.env.event()
151    ...             print()
152    ...
153    ...     def pupil(self):
154    ...         for i in range(2):
155    ...             print(r' \o/', end='')
156    ...             yield self.class_ends
157    ...
158    >>> school = School(env)
159    >>> env.run()
160     \o/ \o/ \o/
161     \o/ \o/ \o/
162
163This can also be used like the *passivate / reactivate* known from SimPy 2.
164The pupils *passivate* when class begins and are *reactivated* when the bell
165rings.
166
167
168Let time pass by: the ``Timeout``
169=================================
170
171To actually let time pass in a simulation, there is the *timeout* event.
172A timeout has two parameters: a *delay* and an optional *value*:
173``Timeout(delay, value=None)``. It triggers itself during its creation and
174schedules itself at ``now + delay``. Thus, the ``succeed()`` and ``fail()``
175methods cannot be called again and you have to pass the event value to it when
176you create the timeout.
177
178The delay can be any kind of number, usually an *int* or *float* as long as it
179supports comparison and addition.
180
181
182Processes are events, too
183=========================
184
185SimPy processes (as created by :class:`Process` or ``env.process()``) have the
186nice property of being events, too.
187
188That means, that a process can yield another process. It will then be resumed
189when the other process ends. The event's value will be the return value of that
190process:
191
192.. code-block:: python
193
194    >>> def sub(env):
195    ...     yield env.timeout(1)
196    ...     return 23
197    ...
198    >>> def parent(env):
199    ...     ret = yield env.process(sub(env))
200    ...     return ret
201    ...
202    >>> env.run(env.process(parent(env)))
203    23
204
205The example above will only work in Python >= 3.3. As a workaround for older
206Python versions, you can use ``env.exit(23)`` with the same effect.
207
208When a process is created, it schedules an :class:`Initialize` event which will
209start the execution of the process when triggered. You usually won't have to
210deal with this type of event.
211
212If you don't want a process to start immediately but after a certain delay, you
213can use :func:`simpy.util.start_delayed()`. This method returns a helper
214process that uses a *timeout* before actually starting a process.
215
216The example from above, but with a delayed start of ``sub()``:
217
218.. code-block:: python
219
220    >>> from simpy.util import start_delayed
221    >>>
222    >>> def sub(env):
223    ...     yield env.timeout(1)
224    ...     return 23
225    ...
226    >>> def parent(env):
227    ...     sub_proc = yield start_delayed(env, sub(env), delay=3)
228    ...     ret = yield sub_proc
229    ...     return ret
230    ...
231    >>> env.run(env.process(parent(env)))
232    23
233
234Pay attention to the additional ``yield`` needed for the helper process.
235
236.. _waiting_for_multiple_events_at_once:
237
238Waiting for multiple events at once
239===================================
240
241Sometimes, you want to wait for more than one event at the same time. For
242example, you may want to wait for a resource, but not for an unlimited amount
243of time. Or you may want to wait until a set of events has happened.
244
245SimPy therefore offers the :class:`AnyOf` and :class:`AllOf` events which both
246are a :class:`Condition` event.
247
248Both take a list of events as an argument and are triggered when any (at least one) or
249all of them are triggered.
250
251.. code-block:: python
252
253    >>> from simpy.events import AnyOf, AllOf, Event
254    >>> events = [Event(env) for i in range(3)]
255    >>> a = AnyOf(env, events)  # Triggers if at least one of "events" is triggered.
256    >>> b = AllOf(env, events)  # Triggers if all each of "events" is triggered.
257
258The value of a condition event is an ordered dictionary with an entry for every
259triggered event. In the case of ``AllOf``, the size of that dictionary will
260always be the same as the length of the event list. The value dict of ``AnyOf``
261will have at least one entry. In both cases, the event instances are used as
262keys and the event values will be the values.
263
264As a shorthand for ``AllOf`` and ``AnyOf``, you can also use the logical
265operators ``&`` (and) and ``|`` (or):
266
267.. code-block:: python
268
269    >>> def test_condition(env):
270    ...     t1, t2 = env.timeout(1, value='spam'), env.timeout(2, value='eggs')
271    ...     ret = yield t1 | t2
272    ...     assert ret == {t1: 'spam'}
273    ...
274    ...     t1, t2 = env.timeout(1, value='spam'), env.timeout(2, value='eggs')
275    ...     ret = yield t1 & t2
276    ...     assert ret == {t1: 'spam', t2: 'eggs'}
277    ...
278    ...     # You can also concatenate & and |
279    ...     e1, e2, e3 = [env.timeout(i) for i in range(3)]
280    ...     yield (e1 | e2) & e3
281    ...     assert all(e.processed for e in [e1, e2, e3])
282    ...
283    >>> proc = env.process(test_condition(env))
284    >>> env.run()
285
286The order of condition results is identical to the order in which the condition
287events were specified. This allows the following idiom for conveniently
288fetching the values of multiple events specified in an *and* condition
289(including ``AllOf``):
290
291.. code-block:: python
292
293    >>> def fetch_values_of_multiple_events(env):
294    ...     t1, t2 = env.timeout(1, value='spam'), env.timeout(2, value='eggs')
295    ...     r1, r2 = (yield t1 & t2).values()
296    ...     assert r1 == 'spam' and r2 == 'eggs'
297    ...
298    >>> proc = env.process(fetch_values_of_multiple_events(env))
299    >>> env.run()
300