General concepts of the state machine code

Execution schemes

The generated state machine code implements one of two execution schemes:

  • In the cycle-based execution scheme, the state machine will first collect events and later process them in a run-to-completion step. Run-to-completion steps are typically executed in regular time intervals.
  • In the event-driven execution scheme, the state machine becomes active each time an event arrives and processes it right away in a run-to-completion step.

You can select the execution scheme via the @CycleBased or @EventDriven annotations. Write the appropriate annotation to the top of your statechart’s definition section, see sections "@CycleBased" and "@EventDriven".

Cycle-based execution scheme

In the cycle-based execution scheme, each run cycle consists of two different phases:

  1. In the first phase, incoming events and time events are collected.
  2. In the second phase, runCycle() is executed and processes the events collected in the first phase.

This approach allows for explicitly processing several events at the same time as well as for checking combinations of events, e.g., a guard condition like [eventA && eventB]. This is very useful for systems that are close to hardware and input signals. Basically it is the input-process-output (IPO) model.

Example: The sample client code below first raises events eventA and eventB in the state machine. These events are lingering in the state machine until the client code calls runCycle() to actually process the events.


Please note: In the cycle-based execution scheme, an event that has been raised internally using the raise statement is visible in the run cycle „downstream” only, i.e., in such regions and the states therein that have not yet been processed in the current run cycle. Everything that is „upstream” in the run cycle cannot „see” this event. This is semantically different from the event-driven execution scheme. Read more on this topic in section "Raising and processing an event".

Event-driven execution scheme

In the event-driven execution scheme, each incoming event or time event immediately triggers the execution of a run-to-completion step. As a consequence, the state machine will never process two events simultaneously, and a guard condition like [eventA && eventB] will never become true.

Example: The sample client code below raises the events eventA and eventB. The state machine processes each event immediately as it arrives, triggered by the respective raise…() method. Thus the client code does not need to call runCycle().


Please note: In the event-driven execution scheme, an event that is raised internally in a run-to-completion step using the raise statement will not be acted upon by any active state „downstream” in the event cycle. The reason is that only a single event can be processed at a time, and this is the event that caused the current run cycle to execute in the first place. The internally-raised event will trigger its own run-to-completion step subsequently. Thus it will be visible to all active states in that RTC. This is semantically different from the cycle-based execution scheme. Read more on this topic in section "Raising and processing an event".

Responsibilities of generated code

The generated source code supports a basic statechart execution model that can be used to implement different variants. It is important to understand the responsibilities of the generated code, i.e., what it cares about and what it doesn’t. The remaining issues are out of scope and must be dealt with in the client code the developer has to provide.

The generated code basically takes care about the following:

  • It provides the interfaces to access state machine variables and events.
  • It initializes the state machine.
  • It implements the execution semantics within a run-to-completion (RTC) step. This is everything that is happening within the runCycle() function.

There are some predefined constraints:

  • The implementation is not thread-safe. Therefore the runCycle() method must never be called in parallel by different threads.
  • All events for a run-to-completion step will be consumed.

Out of scope are:

  • Scheduling: The code generator cannot make any general assumptions about scheduling mechanisms and constraints.
  • Timers: The code generator does not know how the system handles times. If any specific time-dependend behavior is required, the client code has to contribute it.
  • Event handling: The code generator cannot know and does not prescribe how events are entering the system. Therefore deciding when events will be delivered to the state machine is an outside (client code) responsibility.
  • Concurrency: The client code has to care about any thread synchronization.
  • Memory management: The generated state machine code will never allocate any memory. Allocating the memory needed for the statechart structure is a responsibility of the client code.

All these things are out of the generated code’s scope, since the code generators coming with YAKINDU Statechart Tools cannot provide tailored solutions without knowing any details about a respective specific environment, like runtime system, libraries, etc.

In order to have a better code generator support for specific usage patterns, you would have to implement corresponding code generator extensions.

Thread-safe execution

In order to circumvent the missing thread-safety of the runCycle() method, the Java code generator has an option for generating a so-called runnable wrapper, providing event queueing and multi-threading support. For details, see section "RunnableWrapper". It is easy to implement this as a general solution for Java, since the programming language provides corresponding standard features that by definition are available everywhere and can simply be used.

The C programming language, however, does not have any standardized threading features. Therefore a general solution cannot be given. Specific solutions for specific threading libraries would require the implementation of a suitable code generator extension.