A very basic Federation : Lesson 3

Previous posts:

Lesson 1 – Lesson 2

Now I will explain in details the implementation of the TankClass class.

This class will be responsible for manage all Tank objects and attributes. You may see sometimes this code merged with the Federate code ( in our case, in the Main class ). I like to do this way to reuse this code in other Federates to help me when I need to subscribe to Tank attributes and to control all discovered Tanks.

Let’s start with the class attributes.

public class TankClass {
  // The RTI Ambassador - So we can talk to the RTI from here.
  private RTIambassador rtiamb;
  // We must hold the handle of this class 
  private ObjectClassHandle tankHandle;
  // A list of Tank objects we will instantiate
  private List<TankObject> instances;
  // An encoder helper
  private EncoderDecoder encoder;
  // We must hold the handle for all attributes of this class
  // Just Model for now.
  private AttributeHandle modelHandle;
  // Hold all our registered attributes  
  private AttributeHandleSet attributes;

        ...
}

We will need to store the RTIAmbassador to communicate with it. The tankHandle will store the class handle for the Tank class. This handle is assigned by the RTI when the SOM is read.

We must hold all our Tanks too. I will do this using the instances attribute. The encoder is a helper to convert Java date types to the RTI data types. We cannot send things like String, double or boolean directly to the RTI and the incoming data also need to be converted to Java types.

The modelHandle attribute will hold the handle of the Model attribute, in same way as the Tank class handle. You must hold all handles for each attribute of your class. The AttributeHandleSet  is a kind of package to send attribute updates to the RTI.

Here is the contructor of our controller:

public TankClass( RTIambassador rtiamb ) throws Exception {
  // Get the RTIAmbassador. 
  this.rtiamb = rtiamb;
  // Ask the RTI for the class handle of our Tank.
  this.tankHandle = rtiamb.getObjectClassHandle( "HLAobjectRoot.Tank" );
  // Get the class handle for the Model attribute of the Tank
  this.modelHandle = rtiamb.getAttributeHandle( tankHandle, "Model" );
  // Create a list to store all attribute handles of the Tank
  // just to avoid to create again when publish / subscribe
  // but you may want to publish and subscribe to different attributes.
  // Why? Because this Java Class ( TankClass.java ) can be used
  // in others Federates to subscribe to Tank attributes and to store
  // discovered Tanks.
  // Ex: A Radar Federate to subscribe to Tank attributes and found
  // some Tanks around.
  this.attributes = rtiamb.getAttributeHandleSetFactory().create();
  attributes.add( modelHandle );
  // Our Tank list ( created by us or discovered )
  instances = new ArrayList<TankObject>();
  // Our encoder helper.
  encoder = new EncoderDecoder();
}

An object of this class will need the RTIAmbassador reference.  Next, we must get the RTI class reference to the Tank class.

this.tankHandle = rtiamb.getObjectClassHandle( "HLAobjectRoot.Tank" );

I’ll say again, this is the reference to the Tank class. There is no Tank object yet. Next, we need to get all attributes handles too. Our Tank have only one attribute called “Model”. To get the correct attribute handle, we must give the class handle of the Tank ( because other class could have a Model attribute too ).

this.modelHandle = rtiamb.getAttributeHandle( tankHandle, "Model" );

I’ll show you the Tank SOM again.

<objectClass>
    <name>HLAobjectRoot</name>
      
    <objectClass>
        <name>Tank</name>
        <sharing>PublishSubscribe</sharing>
        <semantics>A Tank</semantics>
        <attribute>
            <name>Model</name>
            <dataType>HLAunicodeString</dataType>
            <updateType>Static</updateType>
            <updateCondition>NA</updateCondition>
            <ownership>NoTransfer</ownership>
            <sharing>PublishSubscribe</sharing>
            <dimensions/>
            <transportation>HLAreliable</transportation>
            <order>Receive</order>
            <semantics>The Tank Model</semantics>
        </attribute>                
    </objectClass>
      
</objectClass>

Next we must put the reference of the Model attribute in the AttributeHandleSet. I do this way just because  I want. It will be easy to me to publish or subscribe to all attributes and I don’t want to write all code in both methods.

this.attributes = rtiamb.getAttributeHandleSetFactory().create();
attributes.add( modelHandle );

Time to show the publish and subscribe methods. I am just looking for reusing code. If you are using this class in the Tank Federate then you may want to publish. But if you are using this class in other Federates then you may want to subscribe to its attributes (unless you have two or more Tank Federates running and need to interact with each other). I’ll just make a brief comment about it: you might not want to use all the attributes at once, so you must decide what to put in the AttributeHandleSet list and where (right in the publish/subscribe methods or in the constructor ) by your own.

// Publish all Tank attributes you want ( the Tank Federate will do this ).
public void publish() throws RTIexception {
  rtiamb.publishObjectClassAttributes( tankHandle, attributes );
}

// If you are not the Tank Federate, you can subscribe to the Tank attributes.
public void subscribe() throws RTIexception {
  rtiamb.subscribeObjectClassAttributes( tankHandle, attributes );		
}

To publish or subscribe, we must give the handle of the Tank class so the RTI will know what attributes you’re talking about.

When the RTI needs to comunicate with our Federate, it will use the Federate Ambassador. Sometimes the RTI  will tell us something about Tanks. So it will pass an reference to the object of the event. We need a method in our class to decide if an object handle is a Tank.

// Check if a given object handle is one of mine objects
// ( a handle of a Tank object )
public boolean isATank( ObjectInstanceHandle objHandle ) {
  for ( TankObject tankObj : instances  ) {
    if ( tankObj.getHandle().equals( objHandle ) ) {
      return true;
    }
  }
  return false;
}

It will search inside all our Tank objects ( discovered or created ) to see if the object in focus is one of our Tanks.

The next method is to send an update from all our Tanks attributes to the RTI. This method is a little pessoal because may vary from case to case. I decide to send all updates from all Tanks at once.

// Here you will send to the RTI the attribute changes of all Tanks
// or some Tanks if you want. Do this only if you are the Tank Federate ( Tank's owner )
public void updateAttributeValues() throws RTIexception {
  // I will send updates for all Tanks 
  for ( TankObject tank : instances  ) {
    // Convert Java String to the RTI String 
    HLAunicodeString modelValue = encoder.createHLAunicodeString( tank.getModel() );
    // Create a package to send all to the RTI
    // We will reserve space for one element ( create(1) ) but it may grow
    // if more is added.
    AttributeHandleValueMap attributes = rtiamb.getAttributeHandleValueMapFactory().create(1);
    // We must tell the attribute handle of this attribute value so the RTI can identify it.
    attributes.put( modelHandle, modelValue.toByteArray() );
    // When send the attributes to update, we must tell the RTI what specific object is it owner.
    // we must give the object handle of this Tank. We can send a tag to track this operation
    rtiamb.updateAttributeValues( tank.getHandle(), attributes, "The Tag I Like".getBytes() );
  }
}

As you can see, every time we talk to the RTI we must pass the handle of the Tank class, but when send attribute updates we must pass the handle of the Tank object.

rtiamb.updateAttributeValues( tank.getHandle(), attributes, "The Tag I Like".getBytes() );

The next method will be used if this class is in use by other Federate not the Tank. Again, I just want to reuse code. This method is called by the RTI Ambassador when a attribute update was received, so it will not make sense if we are the Tank ( unless you have many Tank Federates in your Federation ).

// When the Tank Federate sends the updates of its attributes, we must search
// in our list of discovered Tanks to see if this specific Tank is in there.
// If so, update its attributes. Do this only if you are NOT the Tank owner ( since you
// haven't subscribed to your own attribute changes! )
public TankObject update( AttributeHandleValueMap theAttributes, ObjectInstanceHandle theObject ) {
  // Find the Tank instance
  for ( TankObject tank : instances ) {
    if( tank.getHandle().equals( theObject) ) {
      // Update its attributes.
      for( AttributeHandle attributeHandle : theAttributes.keySet() )	{
        // Is the attribute the Tank's Model?
        if( attributeHandle.equals( modelHandle) ) {
          // Decode the RTI String type ( HLAunicodeString ) to Java String
          // Set the attribute value in the Tank
          tank.setModel( encoder.toString( theAttributes.get(attributeHandle) ) );
        }
      }
      return tank;
    }
  }
  return null;
}

The RTI will notify us about attribute changes by telling us the list of attributes ( AttributeHandleValueMap ) and the handle of the owner object ( ObjectInstanceHandle ). As you remember, that’s not what we sent in the updateAttributeValues method?

We just search in our Tank object list if this object handle is a Tank. If so, we will check every attribute inside the package to see each attribute type and update the actual value in the Tank Java object.

tank.setModel( encoder.toString( theAttributes.get(attributeHandle) ) );

Next, we have two methods to create new Tank objects. One to be used if we are the Tank Federate ( so will be a new Tank object inside the RTI ) and other to be used if we are other Federate ( so the RTI is telling us we have found a new Tank created by the Tank Federate ). Once more, is just to reuse code.

// Use this method if you are using this Java class ( TankClass.java ) by other
// Federate ( Ex. a Radar ) . So when the RTI tells to your Radar Federate that 
// there is a Tank nearby, it will give you the Tank's object handle then you can
// store all tanks your Radar found. The two createNew() methods are exclusives:
// This is used by Federates that not own the Tank object.
public ObjectInstanceHandle createNew( ObjectInstanceHandle tankObjectHandle ) throws RTIexception {
  instances.add( new TankObject(tankObjectHandle) );
  return coreObjectHandle;
}

This method is to be used by other Federates. The RTI will give the object handle of the new Tank, so we just need to create a new Java Tank object using this handle. At first time, it will have the default attributes until the attributes update arrives so be careful with NPE.

// Create a new Tank and register. Store it in our list of objects. 
// Only the object owner can registerObjectInstance() of an object.
// This method is used only by the Tank Federate.
public ObjectInstanceHandle createNew() throws RTIexception {
  ObjectInstanceHandle tankObjectHandle = rtiamb.registerObjectInstance( tankHandle );
  TankObject to = new TankObject(tankObjectHandle);
  log("Tank " + to.getModel() + " created.");
  instances.add( to );
  return coreObjectHandle;
}

… and this will be used by the Tank Federate. As you can see, we’re registering a new Tank in the RTI, so we must give the Tank class handle and the RTI will give back the object class handle of the new Tank.

ObjectInstanceHandle coreObjectHandle = rtiamb.registerObjectInstance( tankHandle );

Next step is to store our new Tank in the Tank list.

In the next post I’ll cover the “TankObject”.

Add Comment