next up previous contents
Next: Alternate types for time Up: A Discrete EVent system Previous: Simulation on multi-core computers   Contents

Models with Many Input/Output Types

It would be surprising if every component in a large model had the same input and output requirements. Some models can be satisfactorily constructed with a single type of input/output object and, if this is the case, it will simplify the design of your simulator. If not, you'll need to address this problem when you design your simulation program.

One solution to this problem is to establish a base class for all input and output types, and then to derive specific types from the common base. The simulator and all of its components exchange pointers to the base class and downcast objects as needed. The C++ dynamic_cast operator is particularly useful for this purpose. Although it is not without its problems, I have used this solution in many designs and it works well.

It is not always possible for every component in a model to share a common base class for its input and output type. This can happen if different sub-model have very different input and output needs or when models from earlier projects are reused. For example, to use a CellSpace model as a component of a Digraph model requires some means of converting CellEvent objects into the PortValue objects. A solution to this problem is to use the Simulator and EventListener classes to wrap a model with one input and output type inside of an atomic model with a different input and output type.

The ModelWrapper class is an Atomic model that encapsulates another model. The encapsulated model can be a Network or Atomic model. The ModelWrapper uses input/output objects of type ExternalType while the encapsulated class uses input/output objects of type InternalType. Two abstract methods are provided for converting objects with one type into objects with the other type. These methods are

void translateInput(const Bag<ExternalType>& external_input, Bag<Event<InternalType> >& internal_input)
void translateOutput(const Bag<Event<InternalType> >& internal_output, Bag<ExternalType>& external_output)

The cleanup of converted objects is managed with the gc_output method, which is inherited from the ModelWrapper's Atomic base class, plus a new gc_input method to cleanup objects created by the translateInput method: its signature is

void gc_input(Bag<Event<InternalType> >& g)

The model to encapsulate is passed to the constructor of the ModelWrapper. The ModelWrapper creates a Simulator for the model that is used to control its evolution. The ModelWrapper is a simulator inside of a model inside of a simulator! The ModelWrapper keeps track of the wrapped model's last event time, and it uses this information and the Simulator's nextEventTime method to compute its own time advance. Internal, external, and confluent events cause the WrappedModel to invoke its Simulator's computeNextState method and thereby advance the state of the wrapped model. Internal events are simplest. The computeNextState method is invoked with the wrapped model's next event time and an empty input bag.

The delta_conf and delta_ext methods must convert the incoming input events, which have the type ExternalType, into input events for the wrapped model, which have the type InternalEvent. This is accomplished with the translateInput method. The first argument to this method is the input bag passed to the ModelWrapper's delta_ext or delta_conf method. The second argument is an empty bag that the method implementation must fill. When the translateInput method returns this bag will be passed to the computeNextState method of the ModelWrapper's simulator. Notice that the internal_input argument is a Bag filled with Event objects. If the wrapped model is a Network then the translated events can be targeted at any of the network's components. The ModelWrapper invokes the gc_input method when it is done with the events in the internal_input bag. This gives you the opportunity to delete objects that you created when translateInput was called.

A similar process occurs when the ModelWrapper's output_func method is invoked, but in this case it is necessary to convert output objects from the wrapped model, which have type InternalType, to output objects from the ModelWrapper, which have type ExternalType. This is accomplished by invoking the translateOutput method. The method's first argument is the bag of output events produced collectively by all of the wrapped model's components. Notice that the contents of the internal_output bag are Event objects. The model field points to the component of the wrapped model (or the wrapped model itself) that produced the event and the value field contains an output object produced by that model. These Event objects must be converted to objects of type ExternalType and stored in the external_output bag. The external_output bag is, in fact, the bag passed to the wrapper's output_func, and so its contents become the output objects produced by the wrapper. The gc_output method is used in the usual way to clean up any objects created by this process.

The Wrapper class shown below illustrates how to use the WrapperModel class. The Wrapper is derived from the WrapperModel and implements its four virtual methods: translateInput, translateOutput, gc_input, and gc_output. This class wraps an Atomic model that uses int* objects for input and output. The Wrapper uses C strings for its input and output. The translation methods convert integers to strings and vice versa. The Wrapper can be used just like any Atomic model: it can be a component in a network model or simulated by itself. The behavior of the Wrapper is identical to the model it wraps. The only change is in the interface.

// This class converts between char* and int* event types.
class Wrapper: public adevs::ModelWrapper<char*,int*> {
        Wrapper(adevs::Atomic<int*>* model):
            // Pass the model to the base class constructor
        void translateInput(const adevs::Bag<char*>& external,
                adevs::Bag<adevs::Event<int*> >& internal) {
            // Iterate through the incoming events
            adevs::Bag<char*>::const_iterator iter;
            for (iter = external.begin(); iter != external.end(); iter++) {
                // Convert each one into an int* and send it to the
                // wrapped model
                adevs::Event<int*> event;
                // Set the event value
                event.value = new int(atoi(*iter));
                // Set the event target
                event.model = getWrappedModel();
                // Put it into the bag of translated objects
        void translateOutput(const adevs::Bag<adevs::Event<int*> >& internal,
                adevs::Bag<char*>& external) {
            // Iterate through the incoming events
            adevs::Bag<adevs::Event<int*> >::const_iterator iter;
            for (iter = internal.begin(); iter != internal.end(); iter++) {
                // Convert the incoming event value to a string
                char* str = new char[100];
                // Put it into the bag of translated objects
        void gc_output(adevs::Bag<char*>& g) {
            // Delete strings allocated in the translateOutput method
            adevs::Bag<char*>::iterator iter;
            for (iter = g.begin(); iter != g.end(); iter++)
                delete [] *iter;
        void gc_input(adevs::Bag<adevs::Event<int*> >& g) {
            // Delete integers allocated in the translateInput method
            adevs::Bag<adevs::Event<int*> >::iterator iter;;
            for (iter = g.begin(); iter != g.end(); iter++)
                delete (*iter).value;

next up previous contents
Next: Alternate types for time Up: A Discrete EVent system Previous: Simulation on multi-core computers   Contents
James J. Nutaro 2016-12-27