• Aucun résultat trouvé

T echnical D esign A nd I mplementation

4.2 The Architecture - The Herd Framework

4.2.3 Core Plugin Library

4.2.3.3 Publish-subscribe data sharing node

The published-subscribe data sharing allows for easy synchronization between data models. Al-though the focus is on remote rendering and adapting towards the client, it became clear that there is more data other than the rendering that can benefit the adaptation. For example for localized rendering mixed together with remote rendering providing a hybrid solution. In order to make it generic, we simply looked at what other data we have and how this is useful for shar-ing. For collaboration it becomes very important where the users are working together virtually and how where physically this virtual workspace is hosted, either at a central system, and every view is streamed to each end-user or locally and modifications are propagated to each other.

This type of synchronization however is dependent on the type of connection either one user since his modification to all other users (peer-to-peer) or to a central server that then takes care of updating the other users (client-server). Our approach is capable of handling both situations by breaking down the relation in generic terms of subscriber and publisher. A client for example can be a subscriber and publisher. This however involves some management of data-transfer between the subscriber and publisher within the client application. We can define three spaces where data is residing, outside somewhere on the network, internally as a generic data model and specialised data models optimized for specific routines that use the data. This is illustrated in figure 4.17. Where the publisher is on the outside and communicates directly with the sub-scriber which is internal and upon the subsub-scriber we have listeners that translate the data into the specialized data models.

DataModel Data

Model

Rendering Interface Functionality Adaptation

Compression Synchronization

Conversion Synchronization

End Device Publisher Subscriber Listener

Figure 4.17: Flow of data from the cloud to the local data model and to application data. The cloud data model is the Publisher, the local data model the Subscriber and the application data the listener.

Thepublisherandsubscriberare similar in functionality, as both maintain a tree-based data struc-ture of caches and objects, which is being kept synchronised. The data stored on both sides may not be identical due to the adaptation rules being set for the given session. From figure 4.11 we

can extract three important components, the MemManager from which both the publisher and subscriber inherit, the MemObject which will contain the data and MemCachewill inherit and third the MemListener. In addition there is a protocol extending the base protocol ( code: 4.7 ) that provides the necessary identifiers for handling the communication between the internal MemManagerand externalMemManager, but also the internal communication fromMemManager to the listener.

Figure 4.18:Abstract class diagram of the MemManager, Publisher and Subscriber.

The publisher only handles the HerdDataPacket objects, where thesubscriber extends on this by also providing similar function for direct access as shown in figure 4.18. Which is also reflected in the encapsulating node classes. An example of creating a subscriber and adding data is given in code 4.8.

1autonode=kernel−>createNode("HerdNodeLibCore","HerdSubscriberNode");

2subscriber=static_cast<HerdSubscriberNode*>(node);

3subscriber−>run();

4subscriber−>subscribe(0);

5autoparentCache=m_subscriber−>getCache(0);

6

7MemCachePtrcache(new MemCache("Test cache"));

8subscriber−>addCache(cache,parentCache);

9

10MemObjectPtrmemObject(new MemObject());

11memObject−>data()−>addField("This is some test data.");

12memObject−>data()>addField("Another string.");

13memObject−>data()−>addField(42);

14memObject−>data()>addField(1024.0f);

15subscriber−>addObject(memObject,cache);

Code 4.8:Creating the subscriber and add data.

First through the kernelthe required node is created. In this example we assume that there is already a publisher node active on the system and the subscriber is able to connect directly to it upon calling therunfunction. The subscribersubscribesto theroot, which always has identifier 0.

We then can get theroot cachewhich is used as the parent cache for a new cache to be created, at line 7. Using theaddCachefunction we tell the subscriber to add the cache as a child to the root cache. After that a new object is created, at line 10, which is child of the previously created cache.

By calling thedatafunction we get access to the underlyingHerdDataPacketand can add various data. Using theaddObjectfunction the publisher is informed about the new object and is added to the data tree. Any other subsciber is directly notified of these changes, but only those that are subscribed to the cache. Figure 4.19 shows that aMemCachehas a set of subscribers (which is a list of TCP session identifiers and not the instance or pointer of MemSubscriber) and a set of MemObjectinstances. TheMemCacheitself is also of base typeMemObjectand can therefore, aside from a string typed name also contain various data.

1addCachey=

Figure 4.19: Abstract class diagram of the MemObject, MemCache and MemListener.

The whole concept of this approach is to avoid complexity and keeping it minimal, having a singular connection publish/subscribe, a data tree of containers called MemCache and leaves called MemObject. Without diving into constraints and type limitations which is often seen in other data formats. This however does bring limitations in accessing the data. Each object does have an identifier, and subscribing to a cache is best done by its identifier. But a subscriber that just connects is not aware of what is already existing. It can therefore subscribe to a cache without traversing the whole tree and the same applies for unsubscribe. Also the subscriber provides a functionsubscribeCacheByName, however this will make a subscription to the first cache with the given name within the list of children of the given parent cache, since it is possible to simply add caches with the same name. This is currently left for the developer to take care of. A default approach is to first always subscribe to the root, as this is a mandatory cache, without traversing it. From there look at its children cache names and then decide whether to create a new cache or subscribe to an existing one.

Until now we only discussed the direct connection between subscriber and publisher, but not the relation to its listeners and what these listeners exactly are. Whenever the publisher is pushing an update to its subscribers, the subscribers will update their data model accordingly, however the higher level application has no direct notion of this happening, since it happens in its own thread

and is therefore separated. Although it is possible to directly link the data from theMemObject by pointer and therefore any update is given, albeit not really thread safe, but in some cases it can be used. For example we can store the mouse cursor position of each user in a single cache and the client application is directly linked to the data object and uses the data as coordinates for drawing cursors for each user. Whenever the data is more sensitive and does not allow for errors to occur or when there is a conversion needed a more sophisticated method is needed, which is provided by the listeners. For example a listener is needed when we want to share a 3D model’s vertex data, to render this data we can use vertex buffer objects (VBOs). Since we only need to update the VBO whenever there is an actual update from the publisher, we need to be informed about this, upon which we can copy the data from main memory into the graphic cards memory using OpenGL commands5and possibly some pre-processing might be needed (e.g. calculating new normal and tangent vectors). The listeners are providing these functions. Depending on the listener it either contains aMemCacheorMemObjectand is used at a higher level for inheritance.

A class inherits from a listener type (cache or object) and can overwrite certain event functions which are shown in figure 4.20

+setCache(cacheU:UMemCache)U:Uvoid +getCache()U:UMemCache +onAddCache(cacheU:UMemCache) +onRemoveCache(cacheU:UMemCache) +onRenameCache()

+onAddObject(objectU:UMemObject) +onRemoveObject(objectU:UMemObject) +onAddCacheReply()

+onSubscribe()

+onSubscribeChild(cacheU:UMemCache) +onUnsubscribe()

+validate() +updateModify()

MemCacheListener

+validate() +setObject() +getObject() +onModifyObject()

+onAddObjectReply(objectU:UMemObject) +updateModify()

MemObjectListener

Figure 4.20:Abstract class diagram of MemObjectListener and MemCacheListener.

This provides the communication from the subscriber to the listener, but not from listener to subscriber. Modifying any of the data objects doesn’t invoke any updates towards the publisher.

For this to happen theupdateModifyfunction is provided. This function can also be overwritten since conversion of data might be needed again (just in reversed direction). Ultimately the updateModifyfunction in theMemSubscriber class is called which takes the full data object (thus theHerdDataPacket) and places this into a newHerdDataPacket, this is shown in code 4.9

5VBO informationhttp://www.opengl.org/wiki/Vertex_Specification#Vertex_Buffer_Object

1 if(!_object−>id()) {

2 std::cerr<<"MemSubscriber::modifyObject - Unable to modify publisher object, local object has no valid id"<<std::endl;

3 return;