VRPN Shared Object
VRPN Shared Object is a set of classes in VRPN that provide very fined-grained object sharing. Updates to any instance of a VRPN Shared Object will be reflected in the state of all of the instances of that VRPN Shared Object - all VRPN Shared Objects with the same type and the same name. Callbacks can be registered on any instance, which will be triggered when a state update is recieved at that instance. The type of a VRPN Shared Object is fixed at compile time and is currently one of int32, float64, or String (a null-terminated array of no more than 1000 characters; this is an implementation detail and can be changed). The name of a VRPN Shared Object is a constructor argument, a null-terminated array of no more than 82 characters (this implementation limit is deeply imbedded in VRPN and will not be easy to change).
The semantics of VRPN Shared Objects on multicast connections is undefined. It is assumed that there are no more than two instances, that one is a Server, and that the other is a Remote.
By default, the distributed algorithm is completely symmetric and uses optimistic concurrency control. Calling operator = () (or set()) on either instance of the object results in an immediate update of that instance's state, immediate triggering of registered callbacks, and transmission of an update message across the network to the peer instance.
If you pass the flag VRPN_SO_IGNORE_IDEMPOTENT to the constructor, the instance created ignores all calls to operator = () or set() that attempt to set it to the same value it already has.
VRPN Shared Objects keep track of the time at which an update was made. All instances of a given Shared Object have the same timestamp, which is the time at which operator = () was first called with that value, or the timestamp passed in to set().
This timestamp is not guaranteed to be updated with the clock offset maintained by VRPN Synchronized Connection, so it may be in the originating system's frame of reference rather than the local frame of reference.
If you pass the flag VRPN_SO_IGNORE_OLD to the constructor, the instance created ignores all calls to operator = () or set() that would yield a timestamp earlier than or equal to the current timestamp.
(diagram of normal
operation showing that the time at which an update was made (operator
= () was called) is propagated and tracked across all instances)
If you pass the flag VRPN_SO_DEFER_UPDATES to the constructor, Server instances function normally. However, Remote instances do not update their values or call their callbacks when set() or operator = () are called on them. Instead, they send an update message to their Server. The Server updates its values, calls its callbacks, and sends an update message back to the Remote, which then finally sets its value and calls its callbacks.
VRPN_SO_DEFER_UPDATES guarantees serialization, an important property for distributed systems. We may need it in callback-based programming, since it guarantees that the same callbacks are called on each system in the same order. However, serialization adds to the latency seen by Remote instances, since they must wait for an entire network round-trip before updating.
(diagram of deferred
update mode showing that nothing seems different from the server-side)
Using setSerializerPolicy(), a user can specify more precisely what happens at the serializer. The default policy in effect is ACCEPT: all updates sent to the serializer are accepted. The two alternative policies currently implemented are DENY (which gives the serializer a "lock" on the shared variable) and CALLBACK (which allows a user-specified callback function to return ACCEPT or DENY given the new value, its timestamp, and arbitrary user data).
The constructor of a VRPN Shared Object takes a name, an optional default value, and an optional mode specifier that selects among the modes above. Before the Shared Object can be used, you must call bindConnection() to give it a vrpn Connection pointer. (This is deferred from the constructor so that Shared Objects can be created in global memory or at other times before a Connection has been opened.)
IMPLEMENTATION AND EXTENSION NOTES:
We should switch to using STL strings. (Jeff Juliano vouches for their quality.) This would let us do away with the code replication and switch to a single hierarchy that uses templates; that might let us share nearly arbitrary more-complex objects. (But how do we guarantee that things support serialization in C++?) (Maybe we want to make sure that the user doesn't try to reuse the templates with other objects, since they quite likely won't serialize well? But it would get rid of most of my concerns about this whole approach being too fine-grained.)
It would probably be good to use the Strategy pattern ("Design Patterns", Gamma, Helm, Johnson, Vlissides 1995, pg 315) to replace d_mode and most of the if statements in shouldAcceptUpdate()/shouldSendUpdate(). This would make it easier to extend with new requirements. The actual mechanics look nasty, though, since objects of the strategy hierarchy would have to have a lot of coupling with the shared objects themselves.
I'd like to support migration of the synchronizer role between the Server and the Remote, depending on where the hotspot is, but it looks like that needs three-phase commit. The infrastructure is there, and I'll probably end up implementing it once I'm convinced that my belief in three-phase is justified.
Switching setSerializerPolicy() to run at times when the instance isn't being used as a serializer is too scary to consider. Way too easy for the instances to get out of sync. The user could set an update policy that specifies that after denying an update the new value should be sent back to the originator, but it would be really easy to set up a loop that way.
Page maintained by Tom Hudson. Last updated 23 November 1999.