VR-Exchange 2.7 API Documentation
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
3 - The Portal API

Table of Contents

Brokers exchange data with the Portal through the Portal API.

Portal viewers, such as vrxMessageDump and the VR-Exchange Portal application use the Portal API to receive data.

3.1 Connecting to the Portal

Applications that want to receive messages from the Portal (brokers and Portal viewers) must create an instance of DtPortalConnection (in pConnection.h). This class opens a connection to the internal message queues and creates an instance of the message factory. To create a Portal connection do the following:

myPortalConnection = new DtPortalConnection(myBrokerName, myBrokerId, myControlQueueName, myInteractionQueueName, myObjectQueueName, myTcpPort);

The names of the Control, Interaction and Object queue are text strings that identify the shared memory queues to be used by the DtPortalConnection instance. If you want to run multiple copies of VR-Exchange on a single machine, each copy must use unique queue names. The queue names are set in the connection configuration files. The default values are:

Though brokers can connect to the Portal using this member function, it is much easier to use a DtBroker instance, which both creates the DtPortalConnection instance, and also registers callbacks to shut down the broker.

3.2 Receiving Interactions and Control Messages

A broker can receive three types of messages from the Portal: object updates, control messages, and interactions. Control messages (messages specific to the operation of VR-Exchange) and interactions are handled using callbacks. Object updates are handled using reflected objects. Interaction messages derive from DtPortalInteraction (pInteraction.h). Control messages derive from DtPortalControlMessage (pControlMessage.h). Object messages, control messages, and interaction messages have separate message queues. Control messages receive the highest priority.

To receive a message, a broker can subscribe to it via the message class. For example, a broker must subscribe to the DtPortalBrokerShutdownRequestMessage. This message is delivered to the broker when VR-Exchange exits. To subscribe to a message, do the following:

DtPortalBrokerShutdownRequestMessage::addCallback(myPortalConnection, shutdownRequest,0);

where shutdownRequest() is a function that shuts down the broker in an orderly way.

You can subscribe to interactions in much the same way:

DtPortalDetonationInteraction::addCallback(myPortalConnection, receiveDetonation, 0);

3.3 Sending Interaction and Control Messages

To send a control or interaction message to the Portal, create a message, fill it in, and then send it to the DtPortalConnection. For example, to send a detonation interaction, do the following:

DtPortalDetonationInteraction out;
// "in" is a structure containing detonation information
out.setAttackerId(in->attackerId());
out.setTargetId(in->targetId());
out.setMunitionId(in->munitionId());
out.setEventId(in->eventId().string());
myPortalConnection->send(out);

The syntax is the same for control messages.

3.4 Creating Interaction and Control Message Types

The procedure for creating interaction message types and control message types is the same, except that you derive them from different classes. Control messages are derived from DtPortalControlMessage; interaction messages are derived from DtPortalInteraction. Each message type has a unique message kind. The message kind is an enumeration that is a positive integer value. All predefined message kinds are defined in pMessageKinds.h.

Note
If you create new message types for a user-developed broker and you use that broker in an exercise with other user-developed brokers, you are responsible for ensuring that there are no conflicts in message kinds.

As a service to our customers, if you create new message types and send a list of your message kind values to suppo.nosp@m.rt@m.nosp@m.ak.co.nosp@m.m, we will add your message kinds to pMessageKinds.h so that future releases of VR-Exchange will show that those values are in use.

Because it is the type of the message that makes a message unique, and not the attributes of the message, there are two ways to make a new message type - a simple member function for messages that do not have attributes, and a more complex member function for messages that have attributes.

3.4.1 Creating a Message that Does Not Have Attributes

Control messages usually do not have attributes.

To create a message that has no attributes:

  1. Choose a message kind. Select an integer that is not already defined in pMessageKinds.h.
  2. Give the new message a name and define it as follows (assuming message kind 200 and a new message called PauseSimulation):
typedef DtPortalControlMessageTemplate<200> PauseSimulation;

The template class registers this message type with the factory and the DtPortalConnection instance will know how to use it. You can then use this type as you would any other control message type.

3.4.2 Creating a Message that has Attributes

Interaction messages usually have attributes. Some control messages have attributes. The modPortalApp example shows how to create a message that has attributes.

To create a message type that has attributes.

  1. Choose a message kind. Select an integer that is not already defined in pMessageKinds.h.
  2. If you are creating a control message, subclass DtPortalControlMessage. If you are creating an interaction message, subclass DtPortalInteraction.
  3. Add data storage to your message for all attributes of the message. There is no required way to do this, however the built-in messages follow the VR-Link way of writing accessors and mutators. Accessors use the format:

    int attributeName() const

    Mutators are preceded with set, for example:

    void setAttributeName(int val);

  4. For interactions, you must provide implementations for the following member functions:

    • Kind()
    • interactionKindName()
    • netSize()
    • writeSelfToBuffer()
    • readSelfFromBuffer()
    • printDataToStream().

    A full class header might look like the following:

    class Foo: public DtPortalInteraction
    {
    public:
    ...
    // required to return your unique kind
    virtual DtPortalMessageKind kind() const { return 200;}
    // required to return a string unique to this type.
    // (for interactions only)
    virtual const char *interactionKindName() const {return "foo";}
    // required - returns the number of bytes required to pack
    // this message in the network.
    virtual unsigned int netSize() const;
    // required - called to write message to buffer of size netSize()
    virtual void writeSelfToBuffer(void *buffer) const;
    // required - called to decode network buffer
    virtual void readSelfFromBuffer(const void *buffer);
    // required - prints the internal state of the message to str
    virtual std::ostream &printDataToStream(std::ostream &str) const;
    // Attributes
    DtU32 height()const { return myHeight;}
    void setHeight(DtU32 val){ myHeight = val;};
    DtString string()const { return myString;}
    void setString(const DtString& val) { myString = val;}
    protected:
    DtU32 myHeight;
    DtString myString;
    };

  5. To help implement the derived class, VR-Exchange includes encoder and decoder functions for basic types, as follows:

    unsigned int Foo::netSize() const
    {
    // get the size of the base message type
    unsigned int size = DtPortalInteraction::netSize();
    // append our attributes
    size += getSize(myHeight);
    size += getSize(myString);
    return size;
    }
    void Foo::writeSelfToBuffer(void *buffer) const
    {
    // encode the base class
    // advance the pointer
    char *ptr = (char *)buffer + DtPortalInteraction::netSize();
    // encode our attributes
    ptr = encode(myHeight, ptr);
    ptr = encode(myString, ptr);
    }
    void Foo::readSelfFromBuffer(const void *buffer)
    {
    const char *ptr = (const char *)buffer +
    ptr = decode(myHeight, ptr);
    ptr = decode(myString, ptr);
    }
    std::ostream &Foo::printDataToStream(std::ostream &str) const
    {
    DtPrintAttribute(str, "Height" , myHeight );
    DtPrintAttribute(str, "String" , myString );
    return str;
    }

  6. For interaction messages, add addCallback() and removeCallback() member functions. Although they are not required, because you can register callbacks on the PortalConnection, they provide added convenience. The line that begins connection->messageFactory() is particularly important, because it tells the Portal connection how to create a valid message.

    typedef void (*CallbackType) (const Foo& interaction,
    void* callingObject);
    void Foo::addCallback( DtPortalConnection* connection,
    CallbackType function, void* callingObject )
    {
    // Create a factory for this class and tell the message factory
    // to use it. This can be done in the broker, but it is harder to
    // forget when it is done here.
    connection->messageFactory()->registerCreator( Foo() );
    connection->addMessageCallback( 200,
    (DtPortalConnection::MessageCbFcn) function, callingObject);
    }
    void Foo::removeCallback( DtPortalConnection* connection,
    CallbackType function, void* callingObject )
    {
    connection->removeMessageCallback( 200,
    (DtPortalConnection::MessageCbFcn) function, callingObject );
    }

3.5 Reflecting Objects

Object updates are sent as messages and can be received and transmitted the same way as control messages and interactions. However, because objects have state, they require a different API. The concept of receiving object updates is called object reflection.

To receive updates for a particular type of object from the Portal, you create an instance of a Portal Object Manager. For example, entities are a type of object, as are aggregates. If you want to receive entity updates, create a DtPortalReflectedEntityManager instance (pReflectedObjectManagerTypes.h). The code looks like this:

DtPortalReflectedEntityManager manager(myPortalConnection);

You can then register callbacks for when objects are created or deleted, as follows:

manager.addRemovalCallback(deletedCallback, 0);
manager.addAdditionCallback(addedCallback, 0);

When an entity object is created, your function addedCallback() will be called. The broker will have access to a DtPortalReflectedEntity at that time. DtPortalReflectedEntity instances contain a pointer to a state repository, which keeps track of all the attributes of the object. They also contain a callback mechanism to notify the broker of object state changes. This way the objects do not need to be polled for changes each frame. To register for an update callback the broker does the following:

void addedCallback(const DtPortalReflectedEntity &reflectedObject)
{
...
reflectedObj.addUpdateCallback(updatedCallback, 0);
}

3.6 Publishing Objects

Sending object updates is called publishing. Each object type has a corresponding publisher type. To create an entity publisher and fill it with basic data, the code would look like this:

publisher = new DtPortalEntityPublisher(myPortalConnection);
// Set the required, unique ID
publisher->sr()->setIdentifier("Object2-F18");
// Fill In basic information
publisher->sr()->setLocation(23.3,45.3,45.2);
publisher->sr()->setOrientation(1.3,3.2,10.3);

Each publisher must be ticked every frame:

publisher->tick();

During a tick, the publisher looks at its internal state and decides if data has changed. If it has changed, an update message is sent to the Portal. If the object has not changed, the tick() member function does nothing. The tick() member function also sends updates for late-joining brokers.

The object is destroyed when the publisher goes out of scope. All objects have a final message, which is sent when the publisher is destroyed.

3.7 Creating New Object Types

Creating a new object type is much like creating an interaction or control message.

To create an object type:

  1. Select an integer that is not already defined in pMessageKinds.h.
  2. Create a new class, that derives from DtPortalObject.
  3. Add the attributes associated with the object. Do this the same way that you would add attributes for an interaction.
  4. Once this class is written, you must create a publisher, a reflected object, and a reflected object manager. These classes are created through templates, as follows:

    // Given this class declaration
    class PortalFruit : public DtPortalObject {};
    // Create a Publisher
    typedef DtPortalObjectPublisherTemplate<PortalFruit> PortalFruitPublisher;
    // Create a Reflected Object
    typedef DtPortalReflectedObjectTemplate<PortalFruit> PortalReflectedFruit;
    // Create a Reflected Fruit Manager
    typedef DtPortalReflectedObjectManager<PortalReflectedFruit> PortalReflectedFruitManager;

These typedefs let you create an object and use it like the built-in types. The modPortalApp example has a full implementation of the DtPortalFruit class.

[<< The Portal Language] [Home] [Top of Page] [The Broker API >>]


Document ID: Generated on Mon Apr 19 15:55:22 EDT 2021 from SVN revision 227909
Copyright © 2005-2021 MAK Technologies. All Rights Reserved (www.mak.com)