As described in the Overview, the main goal of the QP Framework is to provide a lightweight and efficient implementation of the Active Object model of computation with the specific focus on deeply embedded real-time systems, such as single-chip microcontroller units (MCUs).
The diagram below shows a QP Application consisting of multiple, collaborating Active Objects that collectively deliver the desired functionality:
Each Active Object has its own event queue and receives all events exclusively through that queue. On the other hand, events can be produced not only by Active Objects, but also interrupts (ISRs), or other software components. The Active Object infrastructure, such as the QP Framework in this case, is responsible for delivering and queuing the events in a deterministic and thread-safe manner.
Perhaps the most important characteristic of Active Objects, from which Active Objects actually derive their name, is their strict encapsulation. Encapsulation means that Active Objects don't share data or any other resources. Figure 210 illustrates this aspect by a thick, opaque encapsulation shell around each Active Object and by showing the internal state machines in gray, since they are really not supposed to be visible from the outside.
All events are delivered to Active Objects asynchronously, meaning that an event producer merely posts an event to the event queue of the recipient Active Object but doesn't wait in line for the actual processing of the event.
The QP Framework makes no distinction between external events generated from interrupts and internal events originating from Active Objects. As shown in Figure 210, an Active Object can post events to any other Active Object, including to self. All events are treated uniformly, regardless of their origin.
Each Active Object processes events in run-to-completion (RTC) fashion, which must be guaranteed by the underlying QP Framework. RTC means that Active Objects process the events one at a time and the next event can only be processed after the previous event has been processed completely. RTC event processing is the essential requirement for proper execution of state machines.
Most traditional operating systems manage the threads and all inter-thread communication based on blocking, such as waiting on a time-delay or a semaphore. However, blocking (as in the middle of the RTC step) is incompatible with the RTC event processing requirement. This is because every blocking call is really another way to deliver an event (event is delivered by unblocking and return from a blocking call). Such "backdoor" event delivery happening in the middle of the RTC step violates the RTC semantics, because after unblocking the Active Object needs to process two events at a time (the original one and the new one delivered by unblocking).
Another detrimental consequence of blocking (or polling for events) inside RTC steps is that Active Objects become unresponsive to events delivered to their event queues. This, in turn, can cause Active Objects to miss their hard-real time deadlines and also can cause overflow of the event queue.
Finally, blocking (or polling for events) means that the expected sequences of events are hard-coded, which is inherently inflexible and not extensible, especially if new events need to be added or the system must handle multiple event sequences.
In the UML, Active Object is defined as: "the object having its own thread of control" [UML 2.5]. This might indeed be the case if the QP Framework runs on top of a traditional multithreading kernel (e.g., traditional RTOS or general-purpose OS). However, the correct Active Object execution really requires the provision of a thread only during each RTC (Run-to-Completion) step. An Active Object that is merely waiting for event(s) does not need a thread at all. This opens up possibilities of using Active Objects with schedulers that don't support the notion of traditional threads. In that case Active Objects can collaborate in gaining access to the CPU only when needed.
In real-time applications it is very useful to prioritize work by assigning priorities to threads, for example. QP Framework takes it a bit further in that every Active Object is required to have a unique priority assigned to it. The Active Object priorities are then used as compact, unique identifiers of Active Objects.
QP Framework shall implement the Active Object design pattern