© 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_8

8. Integrating the Application and Service Domains

Leon Starr, Andrew Mangogna2 and Stephen Mellor1

(1)San Francisco, California, USA

(2)Nipomo, California, USA

In this chapter, we discuss how to manage interactions among domains. Because we are bridging the gap from the semantics of one domain to that of another, we call this topic bridging. From a modeler’s perspective, bridging two domains can be relatively simple. This assumes that no semantic content is missing between the two domains. For example, it would be difficult to bridge a video game application directly to the rendering hardware without having at least an intermediate draw engine. We have avoided this kind of trouble by carefully constructing the domain chart for the ALS example.

Efficient implementation of a bridge, on the other hand, can pose its own set of problems. Pycca provides support for bridging, but a certain amount of code does have to be written. Fortunately, this can be done systematically with pycca providing help to ease the process.

Before diving into implementation, as always, we need a clear and detailed vision of what must be accomplished. The first part of this chapter presents useful building blocks for visualizing and specifying the data necessary to define a bridge. We apply this to a few key interactions between the Lubrication and SIO domains. Then, in the second part of this chapter, we show how to specify this data by using pycca-generated constants in conjunction with handwritten C code. Finally, we show some of the details of how pycca supports performing model-level actions from outside a domain.

Summary of Domain Benefits

As we proceed into all the details of bridging, you may wonder why we are going to all this effort to preserve the semantic integrity of our domains. So let’s first take stock of why domains are important. A key goal of domain engineering is to promote reuse of domains. If a domain is redesigned but provides the same specified services, it should have minimal, preferably zero, impact on the models of any domains with which it interacts.

For example, if we (or someone else) devise a completely different way of managing SIO, it should make no difference to the Lubrication domain. As long as the Above injection pressure event gets delivered, under the proper physical conditions, the Lubrication domain’s needs are satisfied. The logic of the Lubrication domain isn’t aware of and doesn’t care about how the event is delivered.

More benefits than reusability are at stake. By keeping the models separate, it becomes possible to develop them in parallel without a lot of coordination overhead. The expertise required to build one domain is distinct from that required to build the other. This means that you can employ entirely different specialists to develop the models of each domain. Complexity is reduced because each domain model can be built without having to worry about the details of how the other works. This outcome derives from applying the principle of separation of concerns, as we discussed in the first part of Chapter 7.

Each Domain Is a Black Box

To preserve these benefits, the models in two bridged domains view one another as black boxes. SIO doesn’t model anything about Lubrication, and Lubrication doesn’t model anything about SIO. The Lubrication domain does not know which events, if any, ping around in the SIO domain. It knows nothing of SIO’s internal states or actions. You can rename and reorganize the model content in one domain all you want without breaking anything in the other domain. But this opacity principle applies only to the model structures, not necessarily the data they contain! For example, an instance of the SIO Point Threshold class can have its Limit attribute set equivalent to the Max delivery pressure of a corresponding instance of Injector Design in the Lubrication domain. Or some instances of SIO’s Continuous Input Point class may correspond to specific instances of Injector in the Lubrication domain. Depending on usage, this data may be glued together at model time, compile time, initialization time, or dynamically at normal runtime.

Figure 8-1 depicts this mystery box principle.

A421575_1_En_8_Fig1_HTML.gif
Figure 8-1. Lubrication sees SIO as a black box

We can see that the Lubrication domain assumes the existence of an SIO domain that triggers sensor-based events, method calls, and attribute values. It also assumes that SIO can manage control directed at the physical world. But Lubrication has no idea how any of this is accomplished or what structures (modeled or nonmodeled) are involved in SIO.

The Lubrication domain has an external entity named SIO that serves as a proxy for these assumptions. Keeping the name of the proxy external entity and the actual domain the same is a convenient mnemonic, but it is important to remember that the SIO external entity is not the same thing as the SIO domain. The SIO external entity is the Lubrication domain's view of how it has delegated functionality outside its scope to be accomplished elsewhere. The Lubrication domain uses the external entity as a proxy so that it can express its requirements for actuator and sensor service in a way that is consistent with the structure and activities of the domain. If the SIO external entity was in fact the same as the SIO domain, we would expect to be able to connect the external entity operations arising in Lubrication directly to domain operations targeted at SIO, and there would be no semantic gap to bridge. This is definitely not the situation we want. If there were no semantic gap between the domains, the SIO domain would have to know the details of how the Lubrication domains works. We would have sacrificed reducing complexity by separating the concerns of the domains and abandoned any chance of reusing the SIO domain in another application.

Figure 8-2 shows the view looking back from SIO’s perspective. We get a similar picture.

A421575_1_En_8_Fig2_HTML.gif
Figure 8-2. SIO sees its clients as black boxes

SIO has no idea what Inject means. But it does know how to write a value out to a hardware register with the Write point domain operation. Similarly, other SIO services are defined in a vocabulary that is consistent with the view SIO has of its subject matter. SIO is not completely unconstrained in what it does. The services provided are intended to be sufficient for a client domain such as Lubrication to interact with the physical world, and the Lubrication domain does make known the specific services it needs.

Despite the black-box view that each domain takes of the other, when we bridge the Lubrication and SIO domains together, we must provide data values to certain SIO classes that account for the specific behavior the Lubrication domain requires. Those SIO classes were parameterized by having attributes whose values control the processing details. For example, the SIO domain has attributes that specify a value range limit as part of its service to provide its clients with alerts. We provide values for these types of attributes with data gathered from the models and instance populations of the Lubrication domain, or any other client domain that is serviced by SIO.

Note also that while the Lubrication domain needs SIO in order to function, SIO doesn’t really need anything from Lubrication to perform its duty. This is the nature of a client/service domain bridge. The client imposes requirements on the service, and the service is populated and bridged to fulfill them. That explains the direction of the dependency arrows on the domain chart; these arrows are sometimes misinterpreted as indicating some type of flow of control or data between domains. They are instead intended to represent the flow of requirements or dependency between the domains. The actual control or data flow between domains is determined later, as part of the bridging effort.

Marking and Mapping

Now let’s take a look at what data is required to connect one domain to another across a bridge. We need a way to define how certain data and activity in one domain maps to corresponding data in the other domain. This is done using two methods: marking and mapping.

Marking is the process of identifying and classifying model elements in a client domain that can be used to configure properties in a service domain. For example, we mark attributes in the Lubrication domain whose values are sensor driven. Mapping is the process of defining the correspondence between marked elements in the client domain and supporting model elements in a service domain. For example, a Pressure attribute in Lubrication will correspond to the sampled and scaled value of an SIO Continuous Input Point. There must then be a way to map each instance of Injector to its counterpart instance of Continuous Input Point in SIO.

It is critical that we do this in such a way that is complete, but doesn’t specify any particular implementation. That way, when we implement, we have all the marking and mapping data we need to make the system work. We also have the freedom to implement the bridge efficiently, given our particular platform technology.

Look at Figure 8-3, a snippet from the Injector state model in the Lubrication domain.

A421575_1_En_8_Fig3_HTML.gif
Figure 8-3. External entity operations

On the right-hand side, we see the external entity acting as a proxy for the SIO domain. We have marked two Injector state actions that are special in that they trigger control in the physical environment via some sort of actuator. So we declare them to be Actuator controls. They also happen to be external entity operations. They invoke operations defined on an external entity that represents the SIO domain. An external entity operation can be invoked with parameters and return a value. Neither operation in this example specifies any parameters or expects any return value. Our action language presumes that the ID of the calling instance is implicitly available via any external entity operation, so it is not necessary to explicitly supply the Injector ID.

Figure 8-4 shows what the SIO domain must do in response to the operations on the SIO external entity.

A421575_1_En_8_Fig4_HTML.gif
Figure 8-4. A domain operation

The SIO domain provides the Write point domain operation. This operation takes an I/O Point parameter, which is the value of an identifier of the target I/O Point and a Value to write. If 1 is written, injection is started, and if 0 is written, injection is stopped. Note that the Injector and I/O Point parameters have been highlighted to indicate that each carries an ID value corresponding to an instance. They are handled a bit differently than the Value parameter, as you will see.

So our task is to map the marked external entity operations in the Lubrication domain onto the domain operation in the SIO domain. To accomplish this, we will use something called half tables. You put two of them together to create a bridge table. Figure 8-5 shows an example of how it works.

A421575_1_En_8_Fig5_HTML.gif
Figure 8-5. Inject operation half tables

The first step is to map an external entity operation in the Lubrication domain to a domain operation in the SIO domain. For the moment, we won’t consider how to identify the instances involved because, just now, we are concerned only with how the domain models connect together. We then construct the half table for each side. Joining the two halves together yields a bridge table. With the bridge table in hand, we can fill in the values on each side corresponding to the two domains. The result shows exactly how the semantics of Inject and Stop Injecting in the Lubrication domain are made manifest by writing specific values to an I/O Point.

But we’re not done yet. The preceding table shows how the semantic gap is bridged, but operations happen on instances. We know that the Write point operation is called when the Lubrication domain invokes an Inject or Stop injecting operation. But the method must be invoked on some instance of I/O Point.

To determine the instances involved in the operations, a further mapping is required, this time between instances of Injector and instances of I/O Point. Again, we use half tables, as shown in Figure 8-6.

A421575_1_En_8_Fig6_HTML.gif
Figure 8-6. Inject instance half tables

We are making a correspondence between an instance of an Injector and an instance of an I/O Point. The Injector class has a single identifying attribute named ID, and the same is true of the I/O Point class. So the half tables, coincidentally, have the same column headings. To obtain the bridge table, we join the two instance half tables.

The instance populations of Injector and I/O Point are static during runtime. During the running of the system, we do not create or delete Injector instances, nor do we create or delete I/O Point instances. Consequently, we can fill in the table when we are ready to build the system. If either or both populations were dynamic, we would need to map create and delete actions in each domain to one another so that the values in the instance mapping table could be maintained during runtime.

There is one final detail about the bridge that must be specified. The Inject and Stop injecting external entity operations have a formal parameter named Injector whose value determines which Injector class instance is to be started or stopped. Similarly, the Write pointoperation in SIO has a formal parameter named I/O Point whose value determines to which I/O Point class instance the write operation pertains (in addition to the parameter that specifies the value to write). We must say where we get the argument values for these formal parameters. We want to say that the Injector parameter in the Lubrication operations represents an instance of the Injector class and that this maps to the I/O Point parameter that represents a corresponding instance of the I/O Point class. Again, we specify a bridge table by using half tables, mapping an ID parameter in an external entity operation to an ID parameter in a domain operation. Figure 8-7 shows the resulting bridge table.

A421575_1_En_8_Fig7_HTML.gif
Figure 8-7. Mapping ID parameters

These three bridge tables precisely define the way the semantic gap for starting and stopping injection between the Lubrication domain and the SIO domain is realized. Tracing through the bridge tables, we can see the complete logic of the bridge mapping.

  • When the Inject external entity operation is invoked in the Lubrication domain, the inject operation bridge table, Figure 8-5, states that we must realize the Lubrication domain’s intent by having the bridge code invoke the Write point domain operation in the SIO domain and that passing 1 as the Value argument in that operation corresponds to starting the injection.

  • The ID parameter bridge table, Figure 8-7, states that the Injector parameter value, given as an argument in the Inject invocation, is an identifier of an Injector class instance and that the Write point operation requires the identifier of a corresponding I/O Point class instance.

  • The instance bridge table, Figure 8-6, provides the correspondence between the value of an Injector identifier, as obtained from the Inject operation ID parameter and the value of an I/O Point identifier that is supplied to the Write point ID parameter.

In this way, the bridge can determine not only what domain operation in SIO satisfies the needs of the Lubrication domain, but precisely what values have to be passed to the Write point operation when it is invoked in the bridge code.

You may notice that the columns of the half tables always refer to generic model elements (operations, instances, and so forth). Mapping across domains involves the general idea of creating a correspondence between one kind of model element in a domain to another (or possibly the same) type of element in another domain.

After we build our tables, we are in an excellent position to move forward with our pycca implementation. Without them, things can get rather confusing. But before jumping into implementing the bridge, we show other bridge tables to demonstrate the variety of mappings that can arise, particularly when asynchronous operations such as event signaling are involved.

Start and Stop Monitoring Pressure

Figure 8-8 shows two asynchronous signals, Start and Stop monitoring, which are directed at the SIO external entity. They are asynchronous because Lubrication wants to signal SIO and expects feedback in the future, but needs to go on about its business in the meantime.

A421575_1_En_8_Fig8_HTML.gif
Figure 8-8. Signals to an external entity

It’s a bit difficult to classify these as anything other than services we need, so we’ll just mark them as Service. These signals correspond to the Sample and Stop events in SIO, as shown in Figure 8-9.

A421575_1_En_8_Fig9_HTML.gif
Figure 8-9. Events in SIO

In this case, we are able to leave the Injector.ID implicit in the sent signal. So neither signal carries an explicit ID parameter. It is always assumed that the source of a signal is available to the model execution architecture even if it isn’t explicitly sent.

Now let’s build the tables. Figure 8-10 shows the result.

A421575_1_En_8_Fig10_HTML.gif
Figure 8-10. Monitor start stop bridge tables

First we map an external entity signal specification to an event specification. We use the term specification here to be clear that we are not talking about a particular signal flying around during runtime, but rather the signature or specification of the signal/event structure.

In the second table, we map instances of Injector to SIO instances of Conversion Group. But we have already seen this table when we mapped instances for the injection control. The table heading is the same, so we can just add rows for the instance mapping from Injectors to Conversion Groups. We have now specified all the information necessary to work out which event to trigger in the SIO domain and to which instance it should be addressed.

Update Pressure

Bridging is not limited to actions. If we look at the Injector class, we see that there is one special attribute, Pressure. It is special in that the Lubrication domain assumes that it can read the attribute and obtain the current lubricant pressure of the actual physical Injector. This implies that SIO is delegated the responsibility to keep the pressure value up-to-date. In Figure 8-11, we mark it as a sensor attribute. In a domain such as Lubrication, many such attributes are often scattered around. The SIO notion of a sensor is realized as a Continuous Input Point. For each pressure value in Lubrication, there is a Continuous Input Point instance in SIO. After the raw device value has been converted and scaled to a meaningful pressure value, it must be made available somehow when an Injector reads its pressure. We’ll get to the somehow later in the chapter, but for now it is enough to say that the attribute values are mapped together.

A421575_1_En_8_Fig11_HTML.jpg
Figure 8-11. Marking a sensor attribute

Figure 8-12 shows the specification of the bridge tables.

A421575_1_En_8_Fig12_HTML.gif
Figure 8-12. Attribute mapping bridge tables

In the instance-to-instance table, we map the instances of Injector to corresponding instances of Continuous Input Point. This table also has the same heading as you have seen before. We can just extend that table with the new instance mappings for the Continuous Input Points associated to the injector lubricant pressure. Note that multiple SIO model elements are required to capture the services required by a single Injector. This is not an unusual circumstance.

The correlation between the Injector.Pressure attribute and the Continuous Input Point.Value is straightforward. If we had other sensor-based attributes, such as Temperature or Vibration, we would add rows to the table, filling in the appropriate class and attribute names on the left half, and filling in the appropriate Continuous Input Point ID Value on the right half.

We now know which data is mapped across the bridge, but we haven’t yet made any implementation decisions about the mechanism used to transport the data. Are the values mapped to the same memory location? Are values pushed or pulled between domains? This can be determined when the bridge is implemented. From a model perspective, the requirement is that when an Injector.Pressure value is read, it must be up-to-date and reflect the actual lubricant pressure seen at the real-world injector. From a bridge perspective, we must ensure that the Injector.Pressure value is the same as its corresponding Continuous Input Point.Value attribute value. From an implementation perspective, there are numerous means, each with different performance trade-offs, that accomplish the goal.

Injector Pressure Alerts

This last example is a bit more complex, but we will use the same process. We mark elements of the Lubrication domain and then figure out how to map them onto corresponding elements of SIO.

During the period when injector pressure is monitored, the Lubrication domain expects SIO to notify an Injector if one of four conditions occurs:

  1. The pressure is above the minimum required for injecting lubricant.

  2. The pressure is below the minimum required for injecting lubricant.

  3. The pressure is above the minimum allowed when dissipating (that is, when lubricant is not being injected).

  4. The pressure is greater than the allowed maximum.

Injectors are characterized by their associated Injector Designs. Three attributes of Injector Design can be marked as Threshold attributes. These markings show the Lubrication domain’s intent that the attributes be used as part of the required pressure alerts for the injector pressure. Our task is to realize this marking as elements of SIO. Max delivery pressure, Max system pressure, and Max dissipation pressure must correspond to instances of Point Threshold whose Limit value is equal to the Injector Design attribute values. Figure 8-13 shows the correspondence.

A421575_1_En_8_Fig13_HTML.jpg
Figure 8-13. Marking threshold attributes

Looking again at the Injector model, we see that three events and a class method correspond to the preceding conditions, and these must be triggered by SIO at the appropriate times. We can think of these as being marked as Alert events and an Alert method.

The SIO domain provides the capability to compare values of Continuous Input Points against limits provided by a Point Threshold and determine whether a point value is in range or out of range with respect to the Point Threshold. This comparison operation is captured in the life cycle of the Range Limitation class. The association between Continuous Input Points and Point Thresholds, as mediated by the Range Limitation class, allows a point to be compared against multiple thresholds and a threshold to be applied to multiple points. So to configure the SIO domain to monitor for pressure alerts means that we must carefully populate the Point Threshold and Range Limitation instances to match the expectations of the Injectors. We already have seen, as part of updating the Injector.Pressure value, that three Continuous Input Points are populated in SIO that correspond to the injector pressure. The values of those points are compared against limits to decide whether there is a pressure alert. Figure 8-14 shows the correspondence between Lubrication alert events and SIO range limits.

A421575_1_En_8_Fig14_HTML.jpg
Figure 8-14. Marking alert events

Knowing which Continuous Input Points refer to injector pressure and knowing which Point Thresholds we need to apply to satisfy the pressure alerts, we can populate the instances of Range Limitation. Consider a single Injector, IN2, which happens to be a model IHN4 injector. Its related Injector Design specifies the following values:

  • Min delivery pressure ⇒ 19 MPa

  • Max system pressure ⇒ 35 MPa

  • Max dissipation pressure ⇒ 32 MPa

This means that there are three corresponding instances of Point Threshold called PT1, PT2, and PT3. We already know from the bridge that updates the Injector.Pressure attribute, IN2 maps to the Continuous Input Point IOP2. Three instances of Range Limitation must be created in SIO to monitor IOP2, one for each Point Threshold that reflects the marked Threshold attributes of the Injector Design. These are identified in SIO as PT1-IOP2, PT2-IOP2, and PT3-IOP2. In total, we have nine instances of Range Limitation for the three Continuous Input Points, and the three distinct Point Thresholds corresponding to each Injector Design.

For each newly acquired point value, each associated instance of Range Limitation compares the value to its corresponding threshold limit. When the threshold is crossed, an In range or Out of range signal is sent to the NOTIFY external entity in SIO. The task is to ensure that this signal triggers the appropriate event or method in Lubrication and directs it to the correct instance of Injector. Because the Lubrication domain expects the alerts to be delivered as either a domain operation or as an event signaled to an Injector instance, we’ll need two bridge tables to accomplish the mapping, as shown in Figure 8-15. One maps to the Injector max pressure domain operation, and the other maps to our alert events.

A421575_1_En_8_Fig15_HTML.gif
Figure 8-15. Mapping range limits

We are doing something a bit different in these tables. Because we are handling an external entity signal that originates in SIO, we are mapping from SIO to Lubrication this time. The table column order makes no difference, but it is a bit easier to explain from the direction of the signal to its destination. Also in this case, the half tables is a combination of two SIO model elements: an external entity signal and a parameter value. That’s because any Continuous Input Point may have multiple thresholds defined for it, and we need a parameter to distinguish which threshold is being signaled. Each of the six thresholds can be signaled as either In range or Out of range. Notice also that of the twelve possible entries in the two tables, only eight are populated with values. This is because the Lubrication domain is not interested in the cases where the maximum injector pressure limit goes In range nor when the dissipation pressure limit is In range. There are no alert events or alert methods for these cases. Consequently, when those external entity signals are generated, they are not found in the bridge tables, and no bridge action is taken. Going back to our example, consider when the value of Continuous Input Point, IOP2, exceeds the limit for Point Threshold, PT2. This would be detected by the PT2-IOP2 instance of Range Limitation. If the Out of range signal is sent to the NOTIFY external entity, and the Threshold ID is given as PT2, then our table shows that the bridge invokes the Injector max pressure domain operation. Alternatively, if the Threshold ID value had been PT1, consulting the first table would fail to find a match; but consulting the second table, we find that we must signal the Above inject pressure event to an Injector.

Proceeding as before, we must build half tables that correlate the Continuous Input Point instances in SIO to Injector instances in Lubrication. The Continuous Input Point identifiers here are the same ones that are used for updating the Injector.Pressure value. It is that same value against which the threshold limit is compared. Figure 8-16 shows the populated bridge table.

A421575_1_En_8_Fig16_HTML.gif
Figure 8-16. Mapping input points

Finally, we must specify from where the ID parameters come. When we invoke the Injector max pressure domain operation, the external entity ID parameter is a Continuous Input Point ID, and the domain operation ID parameter is an Injector ID. Similarly, if we are signaling an alert event out of the bridge, the instance to which the signal is sent is an Injector. Figure 8-17 shows the identifier mappings.

A421575_1_En_8_Fig17_HTML.gif
Figure 8-17. Mapping identifier parameters

This last example of constructing the bridge tables is certainly more complicated than the others. It requires populating several different classes in SIO to configure it appropriately to meet the Lubrication domain’s needs. Once that is done, the bridge tables are built, following the techniques we have shown. It serves as a good reminder that bridging is an abstract undertaking, requiring that we make correspondences between multiple domains; we need to apply the marking and mapping techniques diligently to avoid getting tangled up.

Implementing Bridges in Pycca

In the previous section, we showed how to bridge the assumptions and dependencies of one domain onto the services provided by another domain. In this section, we turn our attention to creating the code necessary to implement the bridge between the domains.

In our example, the Lubrication domain uses the SIO external entity as a proxy to express how it has delegated actions. It invokes operations on the SIO external entity at the correct point in its processing when those actions need to take place. We distinguished between external entity operations as synchronous and asynchronous. This distinction is important to the Lubrication domain, as it expresses the domain’s expectation for fulfillment of the service. When the Inject or Stop injecting operations are invoked, the Lubrication domain assumes that, however it may happen, by the time the operation has completed, the injector solenoids have been engaged or disengaged from applying lubricant. Contrast that with the assumptions the Lubrication domain has when the Start monitoring and Stop monitoring operations are invoked. In that case, the Lubrication domain assumes that it can continue with its processing, and events will arrive later to indicate the results of monitoring the injector pressure.

For pycca-generated domains, each external entity operation, whether synchronous or asynchronous in its nature, is converted into an external declaration of an ordinary C function. When the operation is invoked, it executes as any other ordinary C function, potentially accepting arguments and returning a value. The model-level concepts of synchronous vs. asynchronous become lost in the conversion to an ordinary function but are still present in the interactions of the bridge.

To bridge pycca domains, it is necessary to supply a definition for the external entity functions. Pycca uses a naming convention for the external entity functions:

 eop_<domain name>_<external operation name>
  • <domain name> is the name of the domain in which the external entity was defined.

  • < external operation name > is the name given to the external entity operation when it was declared by the external operation declaration in the pycca source.

The bridge implementation consists of a function, named as just described, for each of the external entity operations defined by a domain. Then, when the domain code files along with the bridge code files are compiled and linked, all the external symbol references are resolved, and we obtain a runnable program.

Pycca Facilities for Implementing Bridge Code

In this section, we discuss the help that pycca provides in constructing bridges.

The bridging code for pycca-generated domains is manually written. We do not have any great expectations that bridge code can be reused in the same way that we wish to reuse domains. Bridges are specific to the usage of particular domains, populated with specific instances for a given application context. As you saw previously, the population of the bridge tables depends on the initial population of class instances in both Lubrication and SIO. For our example, the data values of SIO attributes have been chosen specifically to match corresponding attribute values in Lubrication. Reusing the SIO domain in a different application requires a different instance population and different bridge tables. Because we expect little reuse of bridge code, manually writing such code is the most direct way to glue domains together. Although there is no support in pycca for generating the bridge code itself, pycca does provide other essential support.

The Domain Portal

As we saw previously in the bridge tables that map an external entity operation onto model elements of a service domain, there are several types of actions we might wish to perform in the service domain. Sometimes it is as simple as invoking a domain operation on the service domain. Other times, we may need to signal an event to a class instance or update an attribute value in the service domain.

When pycca generates a domain, the only symbols visible outside the domain are the domain operation names. For each domain operation statement, pycca generates an ordinary C function of external scope whose name is of the form <do-main name>_<domain operation name>. All other C identifiers in the generated code file have file static scope, and so their symbols are not available outside the code file for the domain. The domain is well encapsulated, and there is little probability of a symbol name conflict when linking multiple domains into an application. Typically, the external entity functions for a domain are placed in a separate C source file, but other organizations are possible. For simple cases, such as ours, we can place the external entity functions for both domains in a single source file.

To help create the external entity functions, pycca supplies a means to accomplish simpler, common model-level actions from outside the domain. Upon request, pycca creates a portal into the domain. The portal consists of an initialized variable along with a set of C functions that operate over the data values in the portal variable to perform common model-level operations. The name of the portal variable follows the convention of <domain name>_portal, and this name is also of external scope. The C functions of the portal code support the following operations:

  • Creating and destroying class instances

  • Reading and updating instance attribute values

  • Signaling events

  • Signaling and canceling delayed events

The ability to access attributes and signal events eliminates the need to create domain operations that perform only simple, common, model-level operations. Any bridge operation that can be accomplished with the portal functions does not require the domain to provide a domain operation. Domains must provide domain operations when the required model-level operations are more complicated, such as traversing relationships or searching for instances based on attribute values.

The pycca approach is a compromise between opening the domain to arbitrary processing from outside and having each model-level operation required by a bridging operation implemented as a domain operation. The portal breaks the encapsulation of a domain, but in a limited way. In practice, the compromise works well for many cases and achieves the goal of enhancing domain reuse by minimizing the number of explicit domain operations that might have to be created when a domain is used in a particular application context.

Identifying Domain Elements

When pycca is requested to generate a portal for the domain, it also includes in the generated header file for the domain a set of C preprocessor define statements that constitute a numerical encoding for the model-level elements of the domain. The definitions include the following:

  • A number to uniquely identify each class in the domain

  • The total number of classes in the domain

  • A number to uniquely identify each attribute within a class

  • The total number of attributes of a class

  • A number to uniquely identify each instance within a class

  • The total number of instances of a class

  • The numerical encoding of the state model events for a class

This encoding gives symbolic names to a set of ordinary integers. The values are used as arguments to the portal functions to specify which class or attribute is intended and to identify any instances that are part of the initial instance population. For example, to send an event to an instance, you must supply the class number, instance number, and event number to the portal function. This is done with the define statements supplied by pycca in the generated header file for the domain. We show how these symbols are used in implementing bridge mappings and how the portal itself operates.

Lubrication Domain External Entity Functions

Four external entity functions are defined by the Lubrication domain. Two control the lubricant injection, and two monitor injector lubricant pressure.

Implementing Injection Control Functions

We now show the implementation of the external entity functions for starting and stopping lubricant injection. Figure 8-6 showed the bridge tables that map between Lubrication domain Injector instances and SIO domain I/O Point instances. We could design the external entity function by directly encoding the table as shown. Looking closely, however, we see that the only two columns that vary are the ID Value on the Lubrication domain side and the ID Value on the SIO domain side. The instance bridge table maps a Lubrication domain Injector ID attribute value to an SIO I/O Point ID attribute value. This suggests an optimization that yields a smaller table. We need only implement a mapping for the varying parts of the bridge tables. We further observe that every Injector instance in the Lubrication domain appears in the bridge table. This means that we can implement the bridge table search as a simple array-indexing operation using the numerical encoding of instances supplied by pycca. The pycca encoding for instance identifiers consists of zero-based consecutive integers that are intended to be usable as array indices. The table to map Injectors to I/O Points that control the injector solenoids can be reduced to the following:

static sio_Point_ID const injToPointMap[LUBE_INJECTOR_INST_COUNT] = {
    [LUBE_INJECTOR_IN1_INST_ID] = SIO_IO_POINT_IOP1_INST_ID,     // ❶
    [LUBE_INJECTOR_IN2_INST_ID] = SIO_IO_POINT_IOP2_INST_ID,
    [LUBE_INJECTOR_IN3_INST_ID] = SIO_IO_POINT_IOP3_INST_ID,
} ;
  • ❶ Note the use of C99 designated initializers in the array definition. This allows us to use the symbolic name of the injector instance number without regard for its value, and the ordering of the elements of the array in memory are placed correctly by the compiler. Along with making the mapping clearer, this technique avoids many potential errors, especially because the values for the index symbols are automatically generated.

So, the bridge table from Figure 8-6 is implemented as an initialized C array, indexed by the Injector instance identifier generated by pycca and whose element values are the SIO I/O Point instance numbers corresponding to the Discrete Points controlling the injector solenoids. Notice that the symbol names generated by pycca contain enough additional information to ensure that the symbols do not conflict with those from other domains. Also notice that we have chosen names for the initial instances that reflect their usage in the application and this text. Careful name selection can help avoid confusion when putting together bridge-mapping data.

Figure 8-5 showed the bridge table that maps the Inject and Stop injecting external entity operation from the Lubrication domain onto the Write point domain operation of the SIO domain. Again, if we examine the table closely, we see that the only two columns that vary are Operation on the Lubrication domain side and Value on the SIO domain side. This suggests another optimization. We implement the bridge mapping as a C function that performs the instance mapping and parameterizes the I/O Point value depending on whether we are starting or stopping the injection:

static void
controlInjector(
    InstId_t injectorId,
    bool starting)
{
    assert(injectorId < LUBE_INJECTOR_INST_COUNT) ;


    sio_Write_point(injToPointMap[injectorId], starting ? 1 : 0) ;
}

This function maps an Injector instance to an I/O Point instance, using the injToPointMap array, and invokes the SIO domain operation to write a value to the point. The starting argument is true if we want to start injecting, and false otherwise. Starting and stopping are mapped to 1 and 0, respectively, as the required values of the Discrete Point to start and stop lubricant injection.

The external entity functions need only invoke the common controlInjector function with the appropriate value for the starting argument:

void
eop_lube_SIO_Inject(
    InstId_t injectorId)
{
    controlInjector(injectorId, true) ;
}


void
eop_lube_SIO_Stop_injecting(
    InstId_t injectorId)
{
    controlInjector(injectorId, false) ;
}

Once distilled down to its essentials, the bridge to implement starting and stopping injection is quite small. However, we want to emphasize that the path to this small implementation starts with designing the bridge, using the ideas discussed in the first part of this chapter. Larger and more complicated bridges will require larger and more complicated external entity function implementations, but those functions can be derived using the design process we have shown.

Implementing Injector Pressure Monitoring Functions

The two other external entity functions for the Lubrication domain involve monitoring the injection pressure. The Lubrication domain invokes Start monitoring when it has decided that the injection lubricant pressure needs to be monitored. It invokes Stop monitoring when the Injector is at a point in its life cycle where monitoring is no longer needed. The design of the bridge is shown in Figure 8-10. In this case, the Lubrication domain maps an asynchronous signaling operation onto signaling an event to an instance in the SIO domain. The instance mapping is from Injector instances in the Lubrication domain to Conversion Group instances in the SIO domain.

Using the same reasoning as in the previous external entity function, we can construct an initialized C array variable to perform the instance mapping:

static sio_Point_ID const presToConvGrpMap[LUBE_INJECTOR_INST_COUNT] = {
    [LUBE_INJECTOR_IN1_INST_ID] = SIO_CONVERSION_GROUP_INJ1_CG_INST_ID,
    [LUBE_INJECTOR_IN2_INST_ID] = SIO_CONVERSION_GROUP_INJ2_CG_INST_ID,
    [LUBE_INJECTOR_IN3_INST_ID] = SIO_CONVERSION_GROUP_INJ3_CG_INST_ID,
} ;

Again, because the set of Injector instances in the implementation of the mapping bridge table is the complete set of instances for the domain, we can perform the search for the corresponding Conversion Group by using array indexing.

Examining the bridge table in Figure 8-10 that maps the external entity signal to an event, we see that the arrangement is similar to that for starting and stopping injection. In this case, the varying parts of the bridge table are the Signal column from the Lubrication domain and the Event column from the SIO domain. We exploit this arrangement by constructing a function where the SIO event, encoded as a number by pycca, is a parameter:

static void
signalConversionGroup(
    InstId_t injectorId,
    EventCode event)
{
    assert(injectorId < LUBE_INJECTOR_INST_COUNT) ;


    int pcode = pycca_generate_event(&sio_portal,
        SIO_CONVERSION_GROUP_CLASS_ID,
        presToConvGrpMap[injectorId],
        NormalEvent,
        event,
        NULL) ;


    assert(pcode == 0) ;
    (void)pcode ;                             // ❶
}
  • ❶ This eliminates compiler warnings about pcode being unused when the assertions are removed.

The controlConvertionGroup function uses the pycca portal function pycca_generate_event to signal an event to a Conversion Group instance from outside the SIO domain. The arguments to pycca_generate_event are as follows:

  • sio_portal, which tells the function which domain is involved.

  • SIO_CONVERSION_GROUP_CLASS_ID, which gives the class of the signaled instance. This symbol is generated by pycca.

  • The Conversion Group instance number obtained from the instance mapping array shown previously. The initializer values in this array are symbols generated by pycca.

  • The type of the event to signal.

  • The number of the event to signal, passed in as a parameter.

  • A pointer to the event parameters. In this case, there are none.

Using the preceding function, we can code the Lubrication domain external entity functions for starting and stopping pressure monitoring.

void
eop_lube_SIO_Start_monitoring(
    InstId_t injectorId)
{
    signalConversionGroup(injectorId, SIO_CONVERSION_GROUP_SAMPLE_EVENT_ID) ;
}


void
eop_lube_SIO_Stop_monitoring(
    InstId_t injectorId)
{
    signalConversionGroup(injectorId, SIO_CONVERSION_GROUP_STOP_EVENT_ID) ;
}

SIO Domain External Entity Function

In this section, we show the external entity functions for the SIO domain. We show only four of the functions here. The remaining function not covered here is shown in the online materials for the book. Its implementation pattern is similar to ones you have already seen.

Updating Injector Pressure Attribute

After the Lubrication domain has invoked Start monitoring, its expectation is that the value of the Pressure attribute of the Injector instances always contains the latest measured value. You saw how monitoring injector pressure was mapped to an event to an instance of Conversion Group in the SIO domain. Each time a Continuous Input Point is sampled, it is scaled to engineering units, and the New point value operation of the NOTIFY external entity is invoked. The asynchronous nature of the bridge from the Lubrication domain perspective now shows up as an external entity operation invoked by the SIO domain.

The external entity function for New point value is responsible for updating, in the Lubrication domain, the value of the Pressure attribute for the Injector instance corresponding to the Continuous Input Point. Figure 8-12 shows the bridge table. From the point of view of the bridge, Figure 8-12 extends the bridge table used in other parts of the bridge. However, from the point of view of our implementation, we are unable to reuse the instance mapping created for starting and stopping injection. This is because we simplified the instance mapping bridge table to exclude parts that did not vary when the mapping was used for controlling injection. That “optimization” allowed us to use array indexing as the mapping search mechanism, but prevents us from using the mapping array for any other external entity functions. It is a trade-off we accept to simplify the instance-mapping search, but it requires us to build a different instance-mapping implementation for this external entity function.

Unlike our previous maps, here we are mapping from I/O Point in the SIO domain onto Injectors in the Lubrication domain. There may many more I/O Points in the SIO domain than there are Injectors in the Lubrication domain, so our previous strategy of using a simple array index doesn’t work well. For this mapping, we choose to build and search a table that explicitly maps I/O Point identifiers corresponding to the Injector pressure values to the Injector instances’ IDs in the Lubrication domain.

To accomplish the mapping, we need a data structure to hold the necessary data:

typedef struct {
    InstId_t fromInst ;
    InstId_t toInst ;
} BridgeIDMap ;

Given an array of such structures, we code a simple linear search to find the corresponding ID value:

static BridgeIDMap const *
mapIOPoint(
    BridgeIDMap const *mapping,
    int numMappings,
    InstId_t from)
{
    for ( ; numMappings > 0 ; numMappings--, mapping++) {
        if (mapping->fromInst == from) {
            return mapping ;
        }
    }


    return NULL ;    // ❶
}
  • ❶ We use NULL to indicate that the mapping failed.

We have chosen a simple search implementation. Our instance populations are small, and so our mapping tables are also small. Larger populations would warrant a more sophisticated search technique such as a hash table.

The mapping from I/O Point instances that correspond to Injector pressure values to the Lubrication domain Injector instances is held in an initialized array variable:

static BridgeIDMap const presToInjMap[LUBE_INJECTOR_INST_COUNT] = {
    {
        .fromInst = SIO_IO_POINT_IOP1_INST_ID,
        .toInst = LUBE_INJECTOR_IN1_INST_ID,
    },
    {
        .fromInst = SIO_IO_POINT_IOP2_INST_ID,
        .toInst = LUBE_INJECTOR_IN2_INST_ID,
    },
    {
        .fromInst = SIO_IO_POINT_IOP3_INST_ID,
        .toInst = LUBE_INJECTOR_IN3_INST_ID,
    },
} ;

Because we are searching the array for the identifier of an I/O Point, the order of the array elements is arbitrary.

The code for the SIO external entity function uses a portal function to update the Injector pressure attribute in the Lubrication domain:

<<sio external operations>>= void
eop_sio_NOTIFY_New_point_value(
    sio_Point_ID point,
    sio_Point_Value value)
{
    assert(point < SIO_IO_POINT_INST_COUNT) ;


    BridgeIDMap const *pointMap =
            mapIOPoint(presToInjMap, COUNTOF(presToInjMap), point) ; // ❶


    assert(pointMap != NULL)  ;                                      // ❷
    if (pointMap == NULL) {
        return ;
    }
    int pcode = pycca_update_attr(&lube_portal,                      // ❸
        LUBE_INJECTOR_CLASS_ID,
        pointMap->toInst,
        LUBE_INJECTOR_PRESSURE_ATTR_ID,
        &value,
        sizeof(value)) ;
    assert(pcode > 0) ;
    (void)pcode ;
}
  • ❶ The COUNTOF macro computes the number of elements in its argument array.

  • ❷ We use assert to catch bad mappings during development. There should be none. However, when the assertions are gone in a release build, we have decided to protect ourselves against dereferencing the NULL pointer. Just because we expect no failures in the mapping does not mean one won’t happen in reality.

  • ❸ This portal function updates the value of an attribute. In this case, the attribute is the Pressure for an Injector instance in the Lube domain. Note the use of pycca-generated symbols as argument values to the portal function.

You can think of this bridge implementation as a push strategy for transporting the injector pressure values across to the Lubrication domain. The Pressure attribute of each Injector instance is updated at the same frequency that the corresponding Conversion Group is sampled. So, the activities of the Lubrication domain always pick up the latest pressure value when it is needed. You can contrast this technique with a pull technique, in which the pressure values for the Injectors would be read from the Signal IO domain when needed.

Signaling Pressure Alerts

The second part of the lubricant pressure monitoring bridge is to alert an Injector in the Lubrication domain when the lubricant pressure falls outside specified range boundaries. When SIO obtains a newly sampled value for a Continuous Input Point, it checks to see whether that point has one or more Range Limitations associated with it. The Range Limitation instances compare the point value against its Point Threshold limit and invoke the external entity operations of Out of range or In range, depending on the outcome of the comparison.

Figure 8-15 shows the bridge table. To external entity functions must be supplied. The bridge mapping of the external entity signal has one of three outcomes:

  • A class method is invoked on an Injector.

  • An event is signaled to an Injector.

  • The threshold notification is not needed by Lubrication, and nothing happens.

The choice of three outcomes adds complexity to the external entity function implementations, but following our previous pattern, we factor out a small piece of code to perform the signaling operation. We use a pycca portal function and take the injector instance and event number as parameters:

static void
signalInjector(
    InstId_t injectorId,
    EventCode event)
{
    assert(injectorId < LUBE_INJECTOR_INST_COUNT) ;


    int pcode = pycca_generate_event(&lube_portal,
        LUBE_INJECTOR_CLASS_ID,
        injectorId,
        NormalEvent,
        event,
        NULL) ;


    assert(pcode == 0) ;
    (void)pcode ;
}

The external entity function to notify Injectors when a pressure threshold is out of range tests the threshold ID to determine the manner of notification:

void
eop_sio_NOTIFY_Out_of_range(
    sio_Point_ID point,
    sio_Threshold_ID threshold)
{
    assert(point < SIO_IO_POINT_INST_COUNT) ;
    assert(threshold < SIO_POINT_THRESHOLD_INST_COUNT) ;


    BridgeIDMap const *pointMap = mapIOPoint(presToInjMap, COUNTOF(presToInjMap), point) ;

    assert(pointMap != NULL) ;
    if (pointMap == NULL) {
        return ;
    }


    switch (threshold) {
    case SIO_POINT_THRESHOLD_IX77B_ABOVE_INJ_INST_ID:// fall through
    case SIO_POINT_THRESHOLD_IHN4_ABOVE_INJ_INST_ID:
        signalInjector(pointMap->toInst,
                LUBE_INJECTOR_ABOVE_INJECT_PRESSURE_EVENT_ID) ;
        break ;


    case SIO_POINT_THRESHOLD_IX77B_ABOVE_DISP_INST_ID:// fall through
    case SIO_POINT_THRESHOLD_IHN4_ABOVE_DISP_INST_ID:
        signalInjector(pointMap->toInst,
                LUBE_INJECTOR_ABOVE_DISSIPATION_PRESSURE_EVENT_ID) ;
        break ;


    case SIO_POINT_THRESHOLD_IX77B_MAX_PRES_INST_ID:// fall through
    case SIO_POINT_THRESHOLD_IHN4_MAX_PRES_INST_ID:
        lube_Injector_max_pressure(pointMap->toInst)   ;
        break ;


    /*
     * N.B. no default case.
     * Unexpected Range Limitation instances are silently ignored.
     */


     }
}

Like the preceding external entity functions, the In range external entity function also tests the threshold ID. Because there is no Below dissipation pressure event and no Below max pressure method for an Injector, some of the Range Limitation threshold values have no mapping in the bridge. The only threshold for which In range notifications are meaningful is the lubrication injection pressure:

void
eop_sio_NOTIFY_In_range(
    sio_Point_ID point,
    sio_Threshold_ID  threshold)
{
    assert(point < SIO_IO_POINT_INST_COUNT) ;
    assert(threshold < SIO_POINT_THRESHOLD_INST_COUNT) ;


    BridgeIDMap const *pointMap = mapIOPoint(presToInjMap, COUNTOF(presToInjMap), point) ;

    assert(pointMap != NULL) ;
    if (pointMap == NULL) {
        return ;
    }


    switch (threshold) {
    case SIO_POINT_THRESHOLD_IX77B_ABOVE_INJ_INST_ID:// fall through
    case SIO_POINT_THRESHOLD_IHN4_ABOVE_INJ_INST_ID:
        signalInjector(pointMap->toInst,
            LUBE_INJECTOR_BELOW_INJECT_PRESSURE_EVENT_ID) ; // ❶
        break ;


    /*
     * N.B. no default case.
     * Unexpected Range Limitation instances are silently ignored.
     */
    }
}
  • ❶ Notice that we signal Below inject pressure when the minimum injection pressure threshold returns to being In range.

Both of these external entity functions could have been implemented differently. The implementation given is convenient for a small number of Point Threshold instances and makes clear which thresholds cause which action, either signaling an event or invoking a domain operation. But say there were 100 Point Threshold instances. A switch statement implementation would not scale well, and we would probably choose to encode the Point Threshold mapping in data and write code to search the data for the correct action to perform. Again we stress that the logic of the bridge and its mapping of semantics between domains stands apart from the implementation of the bridge code itself.

How the Portal Works

We have shown how the Lubrication and SIO domains can be bridged together by supplying code for the external entity functions of each domain. The code for the external entity functions mapped class instances between the domains and invoked either a domain operation or a pycca portal function to fulfill the expectations of the bridge. What remains is to show how the pycca portal functions themselves work.

The portal functions are data driven by the values contained in the portal variable generated by pycca. The portal variable has the following type:

struct pycca_domain_portal {
    struct pycca_class_portal const *classes ;
    ClassId_t numClasses ;
} ;

The portal variable contains a pointer to an array of class descriptive information and a count providing the number of elements in the class description array. For the Lubrication domain, the portal variable generated by pycca is as follows:

struct pycca_domain_portal const lube_portal = {
    .classes  =  lube_class_portal,
    .numClasses  =  6
} ;

As we stated before, pycca also emits a series of preprocessor defines that encode, as consecutive zero-based integers, a set of identifiers for the instances of a class. These identifiers can be used directly as an array index into the class instance storage array. Pycca also generates definitions that serve as identifiers for the classes and attributes of the domain. All of these preprocessor symbols follow naming conventions to make the symbol values unique. You saw examples of the symbol names in the preceding code. The following data types are also placed in the generated header file of the domain:

typedef unsigned short ClassId_t ;
typedef unsigned short InstId_t ;
typedef unsigned short AttrId_t ;
typedef unsigned short AttrOffset_t ;
typedef unsigned short AttrSize_t ;

The class information for the portal has the following structure:

struct pycca_class_portal {
    void *storage ;
    struct pycca_attr_portal const *attrs ;
    struct mechclass const *mechClass ;
    AttrId_t numAttrs ;
    InstId_t numInsts ;
    size_t instSize ;
    size_t instOffset ;
    bool isConst ;
    bool hasCommon ;
    StateCode initialState ;
} ;

The members of the portal class description are as follows:

storage

A pointer to the storage array for the class instances.

attrs

A pointer to an array of attribute descriptors.

mechClass

A pointer to the descriptor for the class.

numAttrs

The number of attributes of the class. This is the number of elements pointed to by the attrs member.

numInst

The maximum number of instances of the class.

instSize

The number of bytes occupied by an instance of the class.

instOffset

The offset in bytes from the beginning of the storage for an instance, where the instance data begins. This member is nonzero only for union-based superclasses. Because the subclass is stored directly contained within the superclass for union-based generalizations, this value gives the offset to the subclass storage.

isConst

A Boolean value that indicates whether the instance storage has been placed in read-only memory.

hasCommon

A Boolean value that indicates whether the class structure definition contains the common_member. For classes with static instance populations and no state model, pycca does not define the common_member for the class structure and saves the memory that would otherwise be filled with NULL pointers.

initialState

The state number for the initial state of an instance. For classes that do not have a state model, the value is ignored.

We show part of the class descriptive information, in this case for the Injector class:

static struct pycca_class_portal const lube_class_portal[] = {
    // .... omitted class descriptive information


    {
        .storage = Injector_storage,
        .attrs = Injector_attr_portal,
        .mechClass = &Injector_class,
        .numAttrs = 3,
        .numInsts = 3,
        .instSize = sizeof(struct Injector),
        .instOffset = 0,
        .isConst = 0,
        .hasCommon = 1,
        .initialState = Injector_INITIAL_STATE
    },


    // .... omitted class descriptive information
} ;

Each attribute of the class is described by the following data structure:

struct pycca_attr_portal {
    AttrOffset_t offset ;
    AttrSize_t size ;
} ;

The portal functions work primarily by using the pycca-generated symbols as indices into arrays generated by pycca when the portal is requested. The data elements in the arrays contain pointer values to internal domain data that can be passed as arguments to the runtime code. This arrangement provides a level of indirection to prevent exposing the domain internals and provides some control over the operations that may be performed on a domain externally. Because this is C, such protections are not firmly enforced by the compiler, and knowing the structure of the portal data means that it can be abused. We assume some level of good intentions and professionalism.

We show one example of a portal function, pycca_update_attr. All portal functions return an int value. A return value less than zero indicates an error, whereas a return value greater than or equal to zero indicates success. The pycca_update_attr function updates the value of an attribute in the given instance of the given class. Here, a successful return value indicates the number of bytes copied into the attribute:

int
pycca_update_attr(
    struct pycca_domain_portal const *portal,
    ClassId_t class,
    InstId_t inst,
    AttrId_t attr,
    void const *src,
    AttrSize_t size)
{
    int result ;


    if (class < portal->numClasses) {
        struct pycca_class_portal const *classes = portal->classes + class ;     // ❶
        if (inst < classes->numInsts) {
            MechInstance instRef = (MechInstance)((char *)classes->storage +                      classes->instSize * inst + classes->instOffset) ;          // ❷
            if (attr < classes->numAttrs) {
                struct pycca_attr_portal const *attrs = classes->attrs + attr ;  // ❸
                if (classes->hasCommon == false || instRef->alloc != 0) {        // ❹
                    if (!classes->isConst) {                                     // ❺
                        void *dst = (void *)((char *)instRef + attrs->offset) ;
                        result = size < attrs->size ? size : attrs->size ;       // ❻
                        memcpy(dst, src, result) ;
                    } else {
                        result = PYCCA_PORTAL_NO_UPDATE ;
                    }
                } else {
                    result = PYCCA_PORTAL_UNALLOC ;
                }
            } else {
                result = PYCCA_PORTAL_NO_ATTR ;
            }
        } else {
            result = PYCCA_PORTAL_NO_INST ;
        }
    } else {
        result = PYCCA_PORTAL_NO_CLASS ;
    }


    return result ;
}
  • ❶ Index to the class descriptor.

  • ❷ Compute a pointer to the class instance.

  • ❸ Index into the attribute descriptor.

  • ❹ Check whether the instance storage slot is in use.

  • ❺ Check whether we are trying to write to constant memory.

  • ❻ Copy only the lesser of the size of the attribute storage and the size of the updated value storage. Note that this works properly only for processors that store multibyte quantities in little endian order.

Most of the code is spent validating input arguments in a way that allows us to give specific error codes on failure. The successful path through the code uses the class, inst, and attr arguments as indices into the description data contained in the portal variable. Finally, the value is copied into the proper memory location.

The other portal functions follow a similar pattern. Argument values are validated and used as indices into descriptive data. The descriptive data is used to compute a pointer to a class instance. Given an instance pointer, the usual runtime functions may be invoked on it.

Summary

In this chapter, we showed how to bridge the semantic gap between two domains by using our example Lubrication and SIO domains. Bridges were specified through marking and mapping. Marking designates domain elements in the client that have a correspondence in a service domain. Marking is also used to identify and populate class instances in the service domain. The service domain instance population is configured to meet the requirements of its client. Mapping makes the correspondence between domain elements explicit by recording data in bridge tables. Bridge tables are composed of half tables. Each domain contributes columns to its half table to describe the elements of the domain that are involved in the bridge. By joining half tables, we generate a bridge table that is then populated with rows for the instances and parameters having a correspondence in the bridge.

The bridge code is implemented by manually writing code for each external entity function. Pycca supplies a portal into a domain that is used in the external entity functions to perform common fundamental model-level operations such as signaling an event. Pycca also supplies a symbolic encoding of model-level elements such as classes, attributes, and instances. These symbols are useful in coding the external entity functions and are used as arguments to the portal functions. Finally, we showed how the portal and its functions work. The portal consists of an initialized variable and a set of C functions. The structure of the portal variable was shown along with some of the initializers for the Lubrication domain. Finally, we showed the C code for the portal function that updates the value of an attribute.

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

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