© Leon Starr, Andrew Mangogna and Stephen Mellor 2017

Leon Starr, Andrew Mangogna and Stephen Mellor, Models to Code, 10.1007/978-1-4842-2217-1_9

9. Event Polymorphism

Leon Starr, Andrew Mangogna2 and Stephen Mellor1

(1)San Francisco, California, USA

(2)Nipomo, California, USA

In programming, polymorphism is the idea that a function interface remains fixed, but the choice of a particular implementation of that function is done at runtime. Many types of polymorphism are defined in modern programming languages. Here we are referring to simple dynamic polymorphism. At the model level, polymorphism arises in the signaling of events to subclasses of a generalization.

Generalization and Set Partitioning

Polymorphic events are applicable only in the context of a generalization relationship in which each subclass has a state model and some events are shared across those state models. We must reiterate that the xUML interpretation of a generalization relationship is more restrictive than the many forms expressed in conventional UML. In xUML, the only generalizations permitted are those tagged as complete and disjoint. The complete tag means that every instance in the superclass has a corresponding instance in a subclass. The disjoint tag means that the corresponding instance is in exactly one subclass. So, in a generalization in which Aircraft is either Rotary Wing or Fixed Wing (a helicopter or an airplane), a given Aircraft must be one or the other, and not both, and not neither. Put another way, xUML generalization represents set partitioning rather than type inheritance. The properties of a disjoint set partitioning are pertinent to our discussion. Because it is understood that all xUML generalizations carry the same set partitioning constraints, we usually omit the tags to reduce diagram clutter.

Routing Polymorphic Events

Consider the generalization shown in Figure 9-1.

A421575_1_En_9_Fig1_HTML.gif
Figure 9-1. A simple generalization

We assume that each of the three subclasses of Vehicle has a state model and that there is a common set of events to which each state model responds. Let’s say the common events are Park and Pass (pass another vehicle). If a state activity of a class outside the generalization wishes to signal a Park event to either Car, Truck, or Bus, that activity would need to locate a particular instance of Car, Truck, or Bus. Usually, it navigates a relationship to an instance of Vehicle and then continues across R1 to find which particular instance is currently related to that instance of Vehicle. Having found the related subclass instance, we can proceed as we would for any event and signal the class instance.

Conceptually, determining which subclass is related to a particular instance of a superclass is not difficult. You just traverse the relationship from the superclass to each of its subclasses. An empty instance reference is obtained on navigation to a subclass to which an instance of Vehicle is not related. The properties of a generalization relationship guarantee that exactly one related instance will be found among the subclasses.

This solution has two notable problems. Navigating from the superclass to the subclass sprinkles a large amount of generic and almost identical action language throughout the state activities of the subclass signalers. Also, the action language is fragile, because adding or removing a subclass from the generalization requires reworking the superclass-to-subclass queries for each event signaled to one of the subclasses.

Because the model enumerates the subclasses of a generalization and the events to which any state model responds, we would prefer to address the event to the superclass instance and then let the model execution domain route the event to the appropriate subclass instance. To accomplish this, we designate polymorphic events that are signaled to a superclass but handled in one of the subclasses of the generalization.

A polymorphic event gives the signaling state activity the illusion that the superclass is signaled. However, the model execution domain performs the required work, at runtime, to find the currently related subclass instance, map the polymorphic event onto the event set of the related subclass instance, and signal the event. Because nothing is happening in the dispatch of polymorphic events that could not otherwise be handled in the action language of a state activity, polymorphic events can be considered, strictly speaking, an optimization, albeit a convenient and significant one. You can also think of event polymorphism as delegating to the MX domain the task of dispatching events across a generalization when those events have the same meaning in all the subclasses.

Routing for each Form of Generalization

The basic generalization form illustrated with the Vehicle scenario just described is the most common, but it is not the whole story. Generalization relationships can be composed into more complex forms.

Repeated specialization is characterized by a subclass in one generalization acting as a superclass in a different generalization. Figure 9-2 shows an example of this type of generalization. Polymorphic events may be defined on both superclasses. Those defined at the topmost superclass are delegated to the first level of subclasses. The subclass that is also a superclass of the next-lower generalization may further delegate those same events to its subclasses. It may also define new polymorphic events that are delegated to the second level of subclasses and not visible above the second-level superclass.

A421575_1_En_9_Fig2_HTML.gif
Figure 9-2. A repeated generalization

Multiple generalization features a class that participates as a subclass in more than one independent generalization. Figure 9-3 shows this case. Our interpretation of generalization does not allow a subclass to participate in multiple generalizations that ultimately have a common superclass ancestor. Such an arrangement violates the disjoint set interpretation of a generalization relationship. The spanning subclass must respond to any polymorphic events directed at each of its superclasses.

A421575_1_En_9_Fig3_HTML.gif
Figure 9-3. A multiple generalization

Compound generalization is a pattern in which a single class plays the role of a superclass in more than one generalization relationship. Figure 9-4 shows this case. An event signaled to the superclass must be dispatched to all generalizations for which the class serves as a superclass.

A421575_1_En_9_Fig4_HTML.gif
Figure 9-4. A compound generalization

We should note that many of the composed forms involving multiple generalizations may have dubious uses in modeling real-world problems. In particular, compound generalizations impose a strict set of constraints that real-world problems often do not meet. But our concern here is translation, and we must be prepared to render polymorphic event dispatch correctly across any composition of generalizations we are given.

Other implications of polymorphic events impact how they are translated:

  • A polymorphic event defined on a superclass does not affect the behavior of that particular superclass. A superclass may have a state model that responds to ordinary, nonpolymorphic events.

  • Classes that are repeatedly specialized and intermediate in a generalization hierarchy may or may not have a state model. If they don’t have a state model, any polymorphic events are passed along to the subordinate generalization. If they do have a state model, then some, all, or none of the polymorphic events may be consumed by that state model. Any polymorphic events not consumed by the intermediate state model are passed to the subordinate generalization. Any polymorphic events that are consumed are not available to the subordinate generalization.

  • A leaf subclass (a subclass that is not subject to further specialization) must process any events delegated to it or defined on it. If any polymorphic events have been delegated along the generalization, the leaf subclass must have a state model. Ultimately, all polymorphic events must be mapped to nonpolymorphic events and be accounted for in a state model. This is another way of saying that there is nothing special about a polymorphic event after it has been delegated to a leaf subclass.

Torpedo Launch Example

To illustrate how polymorphic events arise in models and to show how they are translated, we present a small example. This example is an excerpt of a submarine simulation that focuses on the behavior of torpedoes. As with all our examples, we do not imply that this is how a real submarine torpedo would be controlled. The model shown is strictly for pedagogical purposes.

The problem we consider is how torpedoes are stored, made ready, and eventually launched. Torpedoes are initially organized in storage racks. A torpedo is deployed for potential firing by taking it from its storage rack and loading it into a torpedo tube.

Two types of torpedo design determine how torpedoes are launched. One type of torpedo has a motor designed to propel the torpedo out of an open tube. The other torpedo type is launched using an air system that propels both torpedo and water out of the tube.

One additional complication exists. Under rare conditions, it is necessary to recall a torpedo such that there is no attempt to deploy or explode it. A torpedo could be recalled because of a late-discovered design flaw, or the need to reconfigure the torpedo for some reason. If a torpedo is sitting in its storage rack, it is easily recalled for maintenance or disposal. If it is already loaded into a tube when recalled, we must prevent it from firing and remove it from the tube. If the torpedo is already launched, we must be able to disarm a torpedo in flight before it explodes.

As always, we use a class model to capture the relevant abstractions, and it is shown in Figure 9-5. It is helpful to understand the high-level behavior intended for the classes before diving into the details of the individual state models, so we provide an overview of the class diagram.

A421575_1_En_9_Fig5_HTML.gif
Figure 9-5. Torpedo class model

The R1 generalization captures the three conditions of a Torpedo as being either a Stored, Loaded, or Fired Torpedo. The R5 association between Torpedo and Torpedo Spec provides the Launch type attribute that tells us how the Torpedo is to be launched. The attribute can have a value of Active or Passive.

Initially, a Torpedo is created as a Stored Torpedo and assigned to a Storage Rack. A Load event will cause a Stored Torpedo to migrate to being an instance of Loaded Torpedo. The Torpedo Spec.Launch type attribute is consulted to determine whether the Passive or Active Launch Torpedo subclass must be instantiated. The Storage Rack association is discarded, and R4 is established to register the Tube into which the Torpedo is loaded.

When the Fire event is received by a Loaded Torpedo, it will escape its Tube and migrate to an instance of Fired Torpedo. The R4 association is discarded because the Torpedo is no longer in a Tube.

Note that the polymorphic events have been annotated on the class diagram. This is purely for illustration and is not considered part of the class diagram. At the top, the Recall event is shown next to Torpedo. The asterisk (*) symbol is an informal way of indicating that this event is polymorphic and may be signaled to an instance of Torpedo, but is processed in one of the subclasses.

As you can see, the Recall event is noted next to each Torpedo subclass. In Executable UML, every subclass must define a way to handle the polymorphic event of the superclass in a given generalization relationship. In other words, if an instance of Torpedo receives a Recall event, a response to that event must be defined regardless of which subclass characterizes the instance of Torpedo at a given moment.

An * remains on the Recall event for the Loaded Torpedo class, indicating that we intend to further delegate the event to all of the subclasses on R2. The * is not present on the Recall event shown next to the Passive and Active Launch Torpedo subclasses. These subclasses are leaves in the generalization and so must have state models that react to the event.

Similarly the Fire and Cleared tube events directed at Loaded Torpedo are polymorphic and delegated to the subclasses on R2. These events represent new polymorphic events introduced on the R2 generalization and have no effect on the other subclasses of R1. Regardless of whether an instance of Loaded Torpedo is Passive or Active, for example, it must define a response for both the Fire and Cleared tube events. Considering polymorphic events defined for both R1 and R2, Passive Launch Torpedo and Active Launch Torpedo must respond to Recall, Fire, and Cleared tube.

Now let’s see how event polymorphism appears in the state models. We have decided to build a separate state diagram for each of the leaf subclasses. We start with the Stored Torpedo state model shown in Figure 9-6.

A421575_1_En_9_Fig6_HTML.gif
Figure 9-6. Stored Torpedo state model

This state model depicts the life cycle of a Stored Torpedo from the time it is initially created via assignment to a Storage Rack to the point at which it is either recalled or loaded into a torpedo Tube.

When the Load( Tube ) event is received, reclassification into a new instance of Loaded Tube occurs in the Migrating to Loaded state. Here the association to the Storage Rack is broken, the appropriate instances of Passive or Active Torpedo are created, along with an instance of Loaded Torpedo linked together along R2. Finally, the Tube specified in the event in.Tube is selected and linked to the Loaded Torpedo instance. From there, the final state symbol indicates that the Stored Torpedo instance is deleted.

The polymorphic event Recall appears as an ordinary event that triggers a transition in the STORED and MAINTENANCE states. It is safely ignored in the other states, which are all transitory (exited on self-directed events).

There is no Loaded Torpedo state model, by choice, and instead a state model has been defined on each of its subclasses. Let’s start with the Passive Launch Torpedo shown in Figure 9-7.

A421575_1_En_9_Fig7_HTML.gif
Figure 9-7. Passive Launch Torpedo state model

As the Stored Torpedo instance expires, a newly created instance of Passive Launch Torpedo is created in the LOADED state. The link to the Tube instance has already been established, and the Torpedo waits to be fired. The Fire event has been designated as polymorphic, so it can be signaled to any instance of Loaded Torpedo without regard to whether it is Passive or Active.

When a Fire event is signaled to an instance of Loaded Torpedo that is specialized as an instance of Passive Launch Torpedo, the event is delivered to the subclass instance in the Passive Launch Torpedo state model. This will cause a transition to the EXPELLING state, where the torpedo is pushed out of its Tube.

When the Cleared tube event (also polymorphic) is received, we know that the Torpedo is outside the submarine. It transitions to the Migrating to Fired state, where it disassociates itself from its Tube and becomes an instance of Fired Torpedo.

If the Recall event occurs while in the LOADED state, a software lock is placed on the torpedo such that it will ignore any subsequent Fire events. Otherwise, the event is ignored in the EXPELLING state because it is kind of hard to recall a Torpedo halfway out of the Tube! No worries, Figure 9-8 shows that action can be taken in the Fired Torpedo state model.

A421575_1_En_9_Fig8_HTML.gif
Figure 9-8. Fired Torpedo state model

So now we have a Torpedo that has left the sub on its way, presumably to a target. At this point, the Recall event will prevent the Torpedo from being armed. At a safe distance from the sub, the Arming distance event will be detected.

There is a danger, however, that a Recall event would have been ignored during the Passive or Active Launch Torpedo EXPELLING or ESCAPING TUBE states. And once ignored, an event is lost forever. This is why a class method named Recall() has been defined on the Torpedo class. To recall a torpedo, this method is called. It does two things. First, it sets the Torpedo.Recalled attribute to true. Second, it fires off the Recall polymorphic event directed at itself, which is then delegated appropriately. This means that even though the event may be discarded, the active recall status is remembered in the attribute value, which can be checked and cleared in a subsequent state.

In the Arming state, the Torpedo.Recalled value is consulted before arming the Torpedo, just in case the Recall event occurred in an earlier state, where no productive action could have been taken.

The state model for the Active Launch Torpedo subclass is similar to that of the Passive Launch Torpedo, with the exception of the ESCAPING TUBE state. It is shown in Figure 9-9.

A421575_1_En_9_Fig9_HTML.gif
Figure 9-9. Active Torpedo state model

The important thing to note here is that the polymorphic events Recall, Fire, and Cleared tube are all processed in the state models of the subclasses. We reiterate that there is nothing special about polymorphic events when they are eventually consumed. We have delegated to the model execution domain only the tasks of navigating the generalization from the superclass instance to the subclass instance, and mapping the event onto the event set of the receiving instance. After the event is received, it acts as any other event (it causes a transition, is ignored, or creates an error situation). The delegation of the runtime dispatch to the Model Execution domain allows us to signal the polymorphic event to the superclass instance without knowing which corresponding subclass is instantiated. As this example shows, polymorphic events, along with migrating subclasses, can be used to specify complicated multimodal behavior by using only the state models of individual classes.

Translating Polymorphic Events with Pycca

In this section, we show how the torpedo launch model is translated using pycca. As usual, we show only those parts of the translation that involve constructs you have not already seen and refer you to the fully worked-out example available as part of the online materials.

First, we must specify which events are polymorphic. Two classes define and delegate polymorphic events, Torpedo and Loaded Torpedo:

class Torpedo
    attribute (Torpedo_ID ID)
    attribute (bool Recalled) default {false}
    reference R5 -> Torpedo_Spec


    polymorphic event                                                # ❶
        Recall
    end


    subtype R1 reference                                             # ❷
        Stored_Torpedo
        Loaded_Torpedo
        Fired_Torpedo
    end


    instance operation
    Recall()
    {
        self->Recalled = true ;
        PYCCA_generatePolymorphic(Recall, Torpedo, self, self) ;    // ❸
    }


    population dynamic                                               # ❹
    slots 20
end
  • ❶ The polymorphic event statement introduces the names of the polymorphic events that may be signaled to the Torpedo class. Note that we use an asterisk (*) in the preceding code as an informal annotation on the class diagram to illustrate how polymorphic events are mapped across the generalization. Pycca does not use that notation. Events in the consuming state model need only have the same name as the ones declared as polymorphic.

  • ❷ Here we declare R1 to use pointer references to implement the generalization. This choice is arbitrary, and we use the reference implementation here to demonstrate its use. Polymorphic events would have worked the same if we had used the union style of implementation as we did in other models.

  • ❸ The instance operation shows how polymorphic events (with no event parameters) are signaled. Note that you have to use a specific macro, PYCCA_generatePolymorphic() to signal polymorphic events.

  • ❹ Because the number of Torpedoes can vary, we declare the population to be dynamic and allocate enough space for 20 instances. Remember, all storage is allocated at compile time, and here we state that we have at most 20 torpedoes in our system. Also note that because we have used the reference implementation in this case, storage will be allocated for each Torpedo subclass. With the reference implementation of a generalization, the subclass instances are stored separately from the superclass instances.

The second class to define polymorphic events is the Loaded Torpedo class:

class Loaded_Torpedo
    reference R1 -> Torpedo     # ❶
    reference R4 -> Tube


    polymorphic event           # ❷
        Fire
        Cleared_tube
    end


    subtype R2 reference
        Passive_Launch_Torpedo
        Active_Launch_Torpedo
    end


    population dynamic          # ❸
    slots 8
end
  • ❶ As a subclass in a generalization implemented using a reference, we will need a reference to our related superclass instance, as we need to traverse R1 from subclass to superclass. This back reference is necessary only if we have to navigate R1 from the subclass to the superclass. In this example, we need to perform that navigation. If the activities of Loaded Torpedo never navigate R1 to Torpedo, this reference can be omitted.

  • ❷ Two new polymorphic events are introduced. They are polymorphic across all the generalizations in which Loaded Torpedo participates, which happens to be only R2 in this case. This means that Fire and Cleared_tube must be handled by the state models of Passive Launch Torpedo and Active Launch Torpedo.

  • ❸ The instances of Loaded Torpedo are also dynamic. We have allocated space for only eight Loaded Torpedo instances. Assuming that our submarine has eight torpedo tubes, four forward and four aft, there can never be more than eight torpedoes loaded at a time on a submarine. Because some torpedoes are stored and others have been fired at any given point in time, the storage space allocated here need not be the same as for the Torpedo class instances.

We show the state model for Stored Torpedo implemented in pycca. We have omitted the C code for the state activities so we can focus on the structure of the state model itself:

class Stored_Torpedo
    reference R1 -> Torpedo


    reference R3 -> Storage_Rack
    machine
        state Installing_in_Rack(uint8_t rack) {
            // C code for the state activity.
        }
        transition . - Assigned -> Installing_in_Rack               # ❶
        transition Installing_in_Rack - Installed -> STORED


        state STORED() {
            // C code for the state activity.
        }
        transition STORED - Load -> Migrating_to_Loaded
        transition STORED - Recall -> Cancel_maintenance_interval   # ❷
        transition STORED - Request_maintenance_check -> MAINTENANCE


        state MAINTENANCE() {
            // C code for the state activity.
        }
        transition MAINTENANCE - Checks_out_ok -> STORED
        transition MAINTENANCE - Failed_maintenance -> Removing
        transition MAINTENANCE - Recall -> Removing                 # ❸


        state Removing() {
            // C code for the state activity.
        }
        final state Removing                                        # ❹


        state Migrating_to_Loaded(Tube_number Number) {
            // C code for the state activity.
        }
        final state Migrating_to_Loaded                             # ❺


        state Cancel_maintenance_interval() {
            // C code for the state activity.
        }
        transition Cancel_maintenance_interval - Maintenance_canceled -> Removing
    end


    population dynamic
    slots 20
end
  • ❶ Stored Torpedo instances are created asynchronously (via a creation event) using the Assigned event. The period character denotes a transition from the initial pseudo-state.

  • , ❸ The Recall event shows up as any other event would. Pycca will recognize that Stored Torpedo is a subclass of Torpedo along R1 and that Recall was defined as polymorphic along R1. So when Recall is signaled as a polymorphic event to Torpedo, it is dispatched as a normal event to Stored Torpedo. Exactly how polymorphic event dispatch happens is discussed in a separate section.

  • , ❺ Both the Removing and Migrating to Loaded states are shown as final states in the state model. When a Stored Torpedo is recalled, it is removed from the system and no longer exists. When a Stored Torpedo is loaded, we create a new instance of Loaded Torpedo and therefore must delete the instance of Stored Torpedo to maintain the R1 generalization constraints.

Finally, we show the state activity for the Migrating to Loaded state of Stored Torpedo. This example shows how the subclass migration from a Stored Torpedo to a Loaded Torpedo is implemented in pycca. Let’s first refer back to the action language for the state activity:

!& /R3/Storage Rack //unlink rack
switch (/R1/R5/Torpedo Spec.Launch type)
.Passive : torp .= migrate to Passive Launch Torpedo
.Active : torp .= migrate to Active Launch Torpedo
// link tube
torp/R2/Loaded Torpedo &R4 Tube( Number: in.Tube )

Because we have chosen to implement both R1 and R2 by using reference generalizations, we need to manage the instance creation and deletion explicitly. Migrating implies that an instance of the new subclass is created and related back to the superclass and that the instance of the old subclass is deleted. This processing would be different if we had chosen to implement the generalizations by using the union mechanism. Because with the union implementation, subclass storage is included as part of the superclass storage, migration simply modifies things in place without creating and deleting instances. Both generalization implementation mechanisms have their uses and trade-offs.

state Migrating_to_Loaded(Tube_number Number) {
    // Since this is a final state, the Stored Torpedo instance is deleted
    // automatically at the end of this activity and since there were no
    // references in Storage Rack back to the Stored Torpedo, we don’t have to
    // deal with the R3 association.


        // Get a reference to the superclass instance.
    ClassRefVar(Torpedo, torp) = self->R1 ;
        // Create an instance of Loaded Torpedo
    ClassRefVar(Loaded_Torpedo, ltorp) = PYCCA_newInstance(Loaded_Torpedo) ;
        // Relate the Loaded Torpedo to the Torpedo across R1.
    PYCCA_relateSubtypeByRef(torp, Torpedo, R1, ltorp, Loaded_Torpedo) ;   // ❶
    ltorp->R1 = torp ;
        // Relate the Loaded Torpedo to the Tube.
    ltorp->R4 = PYCCA_refOfId(Tube, rcvd_evt->Number) ;


        // Now handle R2
    switch (torp->R5->Launch_type) {                                       // ❷
        case Active: {
                // For Active Launch Torpedos, create a new instance.
            ClassRefVar(Active_Launch_Torpedo, altorp) =
                PYCCA_newInstance(Active_Launch_Torpedo) ;
                // Relate the Active Launch Torpedo across R2.
            PYCCA_relateSubtypeByRef(ltorp, Loaded_Torpedo, R2, altorp,
                    Active_Launch_Torpedo) ;                               // ❸
            altorp->R2 = ltorp ;
        }
        break ;
        case Passive: {
                // For Passive Launch Torpedos, create a new instance.
            ClassRefVar(Passive_Launch_Torpedo, pltorp) =
                PYCCA_newInstance(Passive_Launch_Torpedo) ;
                // Relate the Passive Launch Torpedo across R2.
            PYCCA_relateSubtypeByRef(ltorp, Loaded_Torpedo, R2, pltorp,
                    Passive_Launch_Torpedo) ;
            pltorp->R2 = ltorp ;
        }
        break ;
    }
}
  • ❶ Relating the superclass and subclass across R1 is a two-step undertaking. First, we update the pointer in the Torpedo superclass to the newly created subclass instance, including the type of subclass (Loaded Torpedo) to which the superclass points. Second, we must set up the reference from the subclass to the Torpedo superclass.

  • ❷ The Launch type attribute of Torpedo Spec determines which subclass we must create for R2.

  • ❸ The same process is used to relate superclass and subclass instances across R2.

How Polymorphic Events Are Signaled

In Chapter 5, we showed how normal events are signaled and dispatched. Signaling requires obtaining an event control block (ECB), filling it in with the proper data, and posting the ECB to the event queue. The same basic set of operations are required to signal a polymorphic event.

Pycca provides a macro to interface to the runtime code. For example, sending the Fire event to a Loaded Torpedo might appear in a state activity as follows:

PYCCA_generatePolymorphic(Fire, Loaded_Torpedo, ltorp, self) ;

This signals the Fire polymorphic event to the ltorp instance of the Loaded Torpedo class.

If the polymorphic event has parameters, you would need to obtain the ECB, fill in the parameters, and then post the event in the same way as is done for normal events. The only difference is that there is a macro that obtains an ECB that has been marked as polymorphic. For some hypothetical event, Reload, that takes a parameter named When, signaling the event might appear as follows:

MechEcb polyecb = PYCCA_newPolymorphicEvent(Reload, Torpedo, torp, self) ;
PYCCA_eventParam(polyecb, Torpedo, When) = 20 ;
PYCCA_postEvent(polyecb) ;

Pycca encodes the polymorphic event numbers as zero-based consecutive integers. This numeric encoding is distinct from that used for normal events. Some classes will have both a set of normal events and a set of polymorphic events. To distinguish the different event sets, an enumeration is defined, and each ECB carries a value of this enumeration to indicate what must be done to dispatch each different event type:

typedef enum {
    NormalEvent,
    PolymorphicEvent,
    CreationEvent
} MechEventType ;

How Polymorphic Events Are Dispatched

In Chapter 5, you saw how a normal, nonpolymorphic event is signaled and later dispatched. To signal, an ECB is filled out and queued. The dispatch of a normal event uses the ECB and the object dispatch block of the receiving instance’s class to compute the transition to a new state and invoke the associated activity. Similarly, dispatching a polymorphic event requires data from the ECB combined with data from a polymorphic dispatch block (PDB).

Additionally, the dispatch of a polymorphic event involves navigating the generalization relationship from the superclass instance to find the related subclass instance. Recall that pycca uses two strategies for storing generalization relationship information:

  • Generalizations stored as references include, in the superclass instance structure, a pointer to the subclass. Because each subclass has a different data type, the superclass pointer is typed as MechInstance (as a pointer to a generic instance).

  • Generalizations stored as unions include, in the superclass instance structure, a union of the data structures for all the subclasses of the generalization.

This difference impacts the way the related subclass instance reference is computed. An enumeration is used to discriminate the two cases:

typedef enum {
    PolyReference,
    PolyUnion
} PolyStorageType ;

Regardless of the way the generalization is stored, pycca defines a member in the superclass structure that encodes the type of the subclass to which the superclass instance is currently related. Pycca generates this encoding as consecutive integers starting at zero, which we use as array indices.

After an event is removed from the event queue, the eventType member of the ECB determines whether the event to dispatch is polymorphic. If it is, we can find the polymorphic dispatch block by following the targetInst member of the ECB, which points to the instance receiving the polymorphic event. Like all instances with dynamic behavior, its instClass member holds the value of a pointer to its class information. For classes that have defined polymorphic events, the class information contains a non-NULL value for the pdb member that points to a polymorphic dispatch block:

typedef struct polydispatchblock {
    DispatchCount eventCount ;
    DispatchCount hierCount ;
    HierarchyDispatch hierarchy ;
} const *PolyDispatchBlock ;

eventCount

The number of polymorphic events defined for or delegated to this class and handled in a subclass.

hierCount

The number of generalization hierarchies in which the class participates. This value is usually 1, but superclasses in a compound generalization will have a hierCount greater than 1.

hierarchy

A pointer to an array of hierarchy dispatch blocks. The array has hierCount number of elements.

If the superclass participates in a compound generalization, multiple elements will be in the hierarchy array. The polymorphic event is dispatched down each generalization relationship in which the superclass participates, so the dispatch code iterates over the hierarchy array. Commonly, there is only a single generalization.

Two operations are required to dispatch a polymorphic event. First, we must navigate the generalization from the superclass to the subclass. Second, we need to map the polymorphic event onto the event set of the subclass. The hierarchy dispatch block provides the information needed for both of these operations:

typedef struct hierarchydispatch {
    PolyStorageType refStorage ;
    AttributeOffset subCodeOffset ;
    AttributeOffset subInstOffset ;
    DispatchCount subtypeCount ;
    struct polyeventmap const *eventMap ;
} const *HierarchyDispatch ;

refStorage

Records whether the generalization relationship is stored in reference form or in union form.

subCodeOffset

Holds the byte offset from the beginning of the instance structure to where the type encoding for the currently related subclass is held. (The encoding is the structure element typed SubtypeCode described in Chapter 5.)

subInstOffset

Holds the byte offset from the beginning of the instance structure to the location of either a pointer to a subclass instance or to the union of the subclass structures.

subtypeCount

Holds the number of distinct subclasses that exist for this generalization relationship. This value is used for runtime checks.

eventMap

A pointer to the polymorphic event mapping for the hierarchy. This mapping is indexed in row order by subtype code and in column order by polymorphic event number. Each entry in the mapping array is of the following type.

typedef struct polyeventmap {
    EventCode event ;
    MechEventType eventType ;
} const *PolyEventMap ;

To compute a reference to the subclass instance, the subInstOffset member is added to the beginning of the superclass instance pointer. Depending on the value of the refStorage member, the resulting address is either a pointer to the subclass instances (union storage) or a pointer to a member that is in turn a pointer to the instance (reference storage).

The type of the subclass is determined by using the subCodeOffset to compute the location in the superclass where the encoded subclass type is stored. Using the subclass code value and the polymorphic event number, we can index into the eventMap. To make the data type of the event map the same for all classes, we store it as a one-dimensional array and then compute the index by using the subtype code as a row number and the polymorphic event number as a column number. This is the same tactic we used when dispatching normal events from a transition table.

The event mapping consists of both an event and an eventType. Because polymorphic events can pass down multiple levels in a repeated specialization hierarchy, a polymorphic event may map to yet another polymorphic event, and so we need to know the event type. Given the event type, the event number, and the subclass instance pointer, we can dispatch the event recursively. Figure 9-10 shows the data involved in dispatching a polymorphic event.

A421575_1_En_9_Fig10_HTML.gif
Figure 9-10. Data used in dispatching a polymorphic event

Figure 9-11 is an illustration of a scenario in which the polymorphic Fire event is signaled to a Loaded Torpedo instance.

A421575_1_En_9_Fig11_HTML.gif
Figure 9-11. Signaling the Fire polymorphic event

An ECB has been filled out and queued. The only element that distinguishes this from any normal event ECB is the eventType field filled out as a PolyEvent. Once dequeued for dispatch, the polymorphic dispatch block must be found. Figure 9-12 illustrates how the PDB is located.

A421575_1_En_9_Fig12_HTML.jpg
Figure 9-12. Locating the PDB

Finally, Figure 9-13 shows how the polymorphic event map is indexed to determine which normal event to send.

A421575_1_En_9_Fig13_HTML.gif
Figure 9-13. Indexing into the event map

Returning to our torpedo launch example, here is the code generated by pycca to support the polymorphic event dispatch for the Torpedo and Loaded Torpedo classes:

/*
 * PDB for Class, "Torpedo"
 */
static struct polyeventmap const Torpedo_R1_pem[] = {
    {.event = 6, .eventType = NormalEvent},
    {.event = 2, .eventType = PolymorphicEvent},             // ❶
    {.event = 6, .eventType = NormalEvent}
} ;
static struct hierarchydispatch const Torpedo_hdb[] = {
    {.refStorage = PolyReference,
    .subCodeOffset = offsetof(struct Torpedo, R1__code),
    .subInstOffset = offsetof(struct Torpedo, R1),
    .subtypeCount = 3,                                       // ❷
    .eventMap = Torpedo_R1_pem}
} ;
static struct polydispatchblock const Torpedo_pdb = {
    .eventCount = 1,
    .hierCount = 1,
    .hierarchy = Torpedo_hdb
} ;
/*
 * PDB for Class, "Loaded_Torpedo"
 */
static struct polyeventmap const Loaded_Torpedo_R2_pem[] = {
    {.event = 0, .eventType = NormalEvent},
    {.event = 1, .eventType = NormalEvent},
    {.event = 2, .eventType = NormalEvent},
    {.event = 0, .eventType = NormalEvent},
    {.event = 1, .eventType = NormalEvent},
    {.event = 2, .eventType = NormalEvent}
} ;
static struct hierarchydispatch const Loaded_Torpedo_hdb[] = {
    {.refStorage = PolyReference,
    .subCodeOffset = offsetof(struct Loaded_Torpedo, R2__code),
    .subInstOffset = offsetof(struct Loaded_Torpedo, R2),
    .subtypeCount = 2,
    .eventMap = Loaded_Torpedo_R2_pem}
} ;
static struct polydispatchblock const Loaded_Torpedo_pdb = {
    .eventCount = 3,
    .hierCount = 1,
    .hierarchy = Loaded_Torpedo_hdb
} ;


/*
 * Class Structure for, "Torpedo"
 */
static struct mechclass const Torpedo_class = {
    .iab = &Torpedo_iab,
    .odb = NULL,
    .pdb = &Torpedo_pdb                                     // ❸
} ;


/*
 * Class Structure for, "Loaded_Torpedo"
 */
static struct mechclass const Loaded_Torpedo_class = {
    .iab = &Loaded_Torpedo_iab,
    .odb = NULL,
    .pdb = &Loaded_Torpedo_pdb
} ;
  • ❶ Here is a case where a polymorphic event is mapped to another polymorphic event. In this case, it is the Recall event from R1 being propagated down the R2 generalization. It maps to a polymorphic event on Loaded Torpedo and to a normal event in both the Stored and Fired Torpedo subclasses.

  • ❷ The number of elements in the event map (Torpedo_R1_pem) is always equal to the subtypeCount value times the eventCount value from the PDB (Torpedo_pdb).

  • ❸ Here the PDB shows up as part of the class information. Notice that neither Torpedo nor Loaded Torpedo responds to normal events, and so pycca sets their odb members to NULL.

In keeping with our theme to expose all the details of how a translated application runs, we show the code that performs polymorphic event dispatch. At this point, the ECB has been removed from the event queue, and it has been determined that a polymorphic event must be dispatched:

static void
dispatchPolyEvent(
    MechEcb ecb)
{
    PolyDispatchBlock pdb = ecb->instOrClass.targetInst->instClass->pdb ;


    assert(pdb != NULL) ;
    assert(ecb->eventNumber < pdb->eventCount) ;
    assert(pdb->hierCount > 0) ;
    /*
     * Each generalization hierarchy that originates at the supertype has an
     * event generated down that hierarchy to one of the subtypes.
     */
    HierarchyDispatch hd = pdb->hierarchy ;
    for (unsigned hnum = 0 ; hnum < pdb->hierCount ; ++hnum) {
        /*
         * The most common case is to dispatch along a single hierarchy. In any
         * case, we can modify in place the input ECB on the last dispatched
         * event.
         */
        MechEcb newEcb ;
        if (hnum == pdb->hierCount - 1) {
            newEcb = ecb ;
        } else {
            newEcb = mechEventAlloc() ;
            /*
             * We set the source as the original sender.
             */
            newEcb->srcInst = ecb->srcInst ;
            /*
             * Copy event parameters.
             */
            newEcb->eventParameters = ecb->eventParameters ;
        }
        SubtypeCode type = *(SubtypeCode *)
                ((char *)ecb->instOrClass.targetInst + hd->subCodeOffset) ; // ❶


        assert(type < hd->subtypeCount) ;
        PolyEventMap pem = hd->eventMap +
                (type * pdb->eventCount + ecb->eventNumber) ;               // ❷
#           ifdef MECH_SM_TRACE
        /*
         * Trace the transition.
         */
        tracePolyEvent(ecb->eventNumber, ecb->srcInst,
                ecb->instOrClass.targetInst, type, hnum,
                pem->event, pem->eventType) ;
#           endif /* MECH_SM_TRACE */
        newEcb->eventNumber = pem->event ;
        newEcb->eventType = pem->eventType ;


        void *subTypeRef = (char *)ecb->instOrClass.targetInst + hd->subInstOffset ;
        newEcb->instOrClass.targetInst = hd->refStorage == PolyReference ?
            /*
             * When the generalization is implemented via a pointer, we need an
             * extra level of indirection to fetch the address of the subtype.
             */
            *(MechInstance *)subTypeRef :
            /*
             * When the generalization is implemented by a union, we need only
             * point to the address of the subtype as it is contained in the
             * supertype.
             */
            (MechInstance)subTypeRef ;


        if (newEcb->eventType == NormalEvent) {
            newEcb->alloc = newEcb->instOrClass.targetInst->alloc ;
            assert(newEcb->alloc != 0) ;
        }


        mechDispatch(newEcb) ;
        ++hd ;
    }
}
  • ❶ This bit of address arithmetic computes the location of the member of the instance structure that stores the encoded value of the currently related subclass and fetches the value located there. For example, if we were dispatching a polymorphic event to an instance of Torpedo, this expression would compute the address of the R1__code member and fetch the value from that location.

  • ❷ This expression indexes into the polymorphic event map. Because the map is stored as a one-dimensional array, we have to undertake the row/column indexing ourselves. The eventCount tells us the number of elements that are in a row. Pycca ensures that the elements in the polymorphic event map are stored in the proper order so this indexing expression fetches the correct event mapping.

Again, distilled to its essence, the code to dispatch a polymorphic event is quite short. It derives most of its “intelligence” from the values of the data structures provided by pycca.

Summary

In this chapter, we showed how polymorphism at the model level is specified and implemented. Polymorphic events may be defined for the superclass in a generalization relationship. At runtime, polymorphic events signaled to superclass instances are dispatched across the generalization to state machines in the subclasses. We showed how to specify polymorphic events to pycca, the data pycca generates to support the polymorphic event dispatch, and exactly how the ST/MX domain performs such event dispatch.

All xUML generalizations represent a set partitioning rather than inheritance. For each superclass, there is exactly one subclass instance, and this instance is in exactly one subclass.

Generalizations may be interconnected to yield the following forms:

  • Repeated specialization: A subclass of one generalization is also a superclass of another generalization.

  • Multiple generalization: A single subclass participates in more than one generalization with a superclass in each.

  • Compound generalization: A single class plays the role of a superclass in more than one generalization.

It is important that polymorphic events are sensibly dispatched in each form.

A superclass may delegate events so that they are handled in each subclass rather than by a state model on the superclass. A subclass may then either handle an event or, if it is also the superclass of another generalization, further delegate it. Each delegated event must be handled by a subclass at the current level or at a deeper level of specialization. A received event is always processed in a class.

Each polymorphic event is defined on a superclass and designated in the pycca model script. An event delegated to a subclass may be used by that subclass, just like any other event in the subclass’s state machine section of the model script.

Distinct pycca macros are provided to support the signaling of a polymorphic event with and without parameters.

Polymorphic event dispatch is handled differently, based on whether the generalization was implemented using the union or reference code pattern. The event control block used for normal events is also used for dispatching polymorphic events. The key difference is that an additional polymorphic dispatch block is referenced to navigate the generalization’s dispatch hierarchy and identify the corresponding subclass event that is processed.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset