While the syntax for raising events did not change the differences address the execution semantics. The main difference is that from release 4 event buffering is completely supported for event driven and cycle based state machines it is not available for the cycle based case in 3.x releases. This implies some constraints which must be considered for 3.x.
When a reaction raises an event, the subsequent behaviour of the state machine is highly dependent on the execution scheme, selected by either the @CycleBased (default) or the @EventDriven annotation. In short:
The following sections will give a more in-depth view on these different behaviours.
As explained above, an event that has been raised using the raise statement exists in the current run-to-completion step only and thus can be dealt with during the rest of that particular run-to-completion step only.
Please also note that orthogonal regions are executed sequentially and in a deterministic order.
This has remarkable consequences on where an event is “visible” and where it is not. Let’s say we have a state machine at a certain point in time where n orthogonal regions are active. The regions ri have a defined execution order: r1 → r2 → … → rn-1 → rn.
If an event e is raised in region rm, then e is available only in those regions that have not been processed yet, i. e. in ri where i > m and i ≤ n.
The following example illustrates this. When the state machine is started, the situation is as shown in figure "Raising an event [1]": A and X in the parallel regions r1 and r2 are active states. Important: Region r1 is executed before r2.
Raising an event [1]
Now event
e1 occurs. It triggers transition
A → B in
r1. This transition’s effect
raise e2
causes event
e2 to be raised. This event triggers transition
X → Y in
r2 to fire
within the same RTC.
After that, the situation is now as can be seen in figure "Raising an event [2]": substates B and Y are now active states. However, the transition B → C will not be taken, because that would have to happen in another RTC. But by then the particular e2 instance raised by A → B will be long gone, since it was restricted to the RTC it has been raised in.
Raising an event [2]
Orthogonal regions within a composite state are always executed from left to right or from top to bottom, respectively. On the top level, where orthogonal regions can be graphically arranged at will, the execution order is defined by the statechart model’s property region priority. To inspect or change it, open the properties view and select the statechart’s definition section.
The region priorities of the example above are shown in figure "Region priorities". Change the execution order and the example will behave differently than described.
Region priorities
Not being able to raise an event and process it in “earlier” regions is a restriction you can overcome using variables. The idea is to set a “communication variable” to a certain value in a region that is processed “later”. During the next RTC the earlier region could act on the variables value. Another option – and in many cases the better on, is to change to the event-driven execution scheme.
As explained above, an event that has been raised using the raise statement is appended to an event queue, and thus it is not visible in the current run-to-completion step (RTC). Since in the event-driven execution scheme an RTC always processes a single event at a time, only the event that caused the RTC to execute in the first place remains visible “downstream” in the current RTC.
The raised event will, however, trigger an RTC on its own. In that RTC, it will be visible to all regions, no matter of their execution order. The execution order as explained in the preceeding section still applies, but it is of less importance in the event-driven approach.
Events raised internally by the raise statement in the reaction of a state or transition take precedence over events that entered the state machine from the outside. This convention ensures that an external event together with all effects it causes – and in particular with all events raised internally – are processed completely, before the next external event can take charge.
Consider, for example, events a and b having entered, in that order, a state machine that is executing in event-driven mode. Since a entered the state machine first, it is processed first in an RTC. Event b is put on a queue for external events, where it has to wait until a is done. Processing of a, however, leads to another event c being raised via the raise statement. Since a is still being processed, c is, similar to b, also queued.
However, c is put on a different event queue, a queue that is reserved for internal events only. Events in the internal event queue are always processed first. The external event queue will be considered only if the internal event queue is empty. As a consequence for our example, the order of events being processed by successive run-to-completion steps is a, c, b.
Please note: There is only a single internal event queue, and all internally-raised events are appended to it. Let’s consider the example above again and let’s assume that processing of a not only raised the internal event c, but also an additional event d. Since both c and d are going to the internal event queue, the overall processing sequence is a, c, d, b. Now let’s also assume that in the course of processing c yet another event e is raised. Like all internally-raised events, it will be appended to the end of the internal event queue, i.e., behind d, not behind c, resulting in an overall execution sequence of a, c, d, e, b. In other words, the state machine is taking a breadth-first approach on internally raised events, in lieu of depth-first.
Please also note: In event-driven execution mode, there’s a looming risk of endless event processing you should be aware of. In the example above, imagine processing of event c would always raise another instance of c. In that case, the internal event queue would never get empty, the state machine would be busily processing one c after the other, without ever ending and without ever getting to b.