How to write beWeeVee enabled software – Part 3
Reading time: 8 - 14 minutes
In the first two articles we explained how beWeeVee works under the hood [1] to create a seamlessly experience developing applications that can synchronize changes on data structures preserving positional intention [2].
We also introduced the ITextView and IElementView<T> client APIs to easy interact with linear data structures (aka Lists of stuff). Linear structures are more common that one may think at first though, so today we will present an example that shows how a linear structure can accomodate pretty interesting behaviors. All these examples, complete with source code will be released at part of the SDK of beWeeVee. We have already send some copies of a pre-CTP to some interested parties, if for some reason you think that it may be interest to you, and want early access to it; just let us know and we can arrange for an sneak peek.
The sketcher sample explained
The ability to handle text is pretty understandable in itself (even though you can work on more structured things like XML), so we will focus on something a little bit different. We would like to create an application that will be able to show how beWeeVee can handle other not so common and simple scenarios.
Co-operation (aka Real-time Collaboration) is becoming pretty common in web environments, but with beWeeVee all applications can make use of those features; games have been doing it for years in the restricted model provided by the rules of the objects they use. Our rules are going to be simple:
1. When I draw something I want my other party to see the same thing.
2. But if the two of us draw at the same time we want everybody to be able to see exactly the same and be able to operate over it in exactly the same way; there is no room for inconsistency.
With beWeeVee we are able to achieve Convergence and that is the utility of the approach, when Convergence is achieved both of us will see exactly the same outcome up to the underlying representation level in a consistent way.
So if our representation is at the stroke level, we want to ensure that we are always drawing our strokes in the appropriate depth level. As noted before the data structure has to be linearizable, if we treat every stroke as a simple element (note that we can lift this requirement also
) and put the strokes in a list and use the index of the list as the depth cue, we are simply creating a list of strokes.
We have seen before a way to achieve this using the following code:
public class SynchronizableStrokeCollection : ElementView<SerializableStroke>
{
public SyncronizableStrokeCollection()
{
[...]
}public StrokeCollection Strokes { get; set; }
[...]
}
It is pretty simple to see that the SerializableStroke is our underlying representation, while the StrokeCollection is just a wrapper usable by WPF or Silverlight to perform the drawing on the screen.
But what is a SerializableStroke anyways? An Stroke is defined as a list of points in a 2D space so we can define it as:
public struct SerializableStylusPoint
{
public double X { get; set; }
public double Y { get; set; }
}public class SerializableStroke
{
public SerializableStroke()
{
StylusPoints = new List<SerializableStylusPoint>();
}
public IList<SerializableStylusPoint> StylusPoints { get; set; }
}
Adding and removing strokes is as simple as defining Add and Remove methods. We are going to concentrate on the adding because it is the only method provided in the Sample. But removing items is just selecting the appropriate object and well call Remove
We decided to use an InkPresenter that is going to provide use with points created from the mouse.
When we find a MouseDown event we start to create an stroke (a list of points) and when we received the MouseUp we are done with the Stroke.
As we can see in the extract we are providing methods to compose a stroke internally until it is ready to be put in the beWeeVee managed collection.
public class SynchronizableStrokeCollection : ElementView<SerializableStroke>
{
private Stroke stroke;
[...]public void StartStroke()
{
// start a new stroke
stroke = new Stroke();
// adds the stroke to the stroke collection
Strokes.Add(stroke);
}
public void AddPointToStroke(StylusPointCollection point)
{
// checks if a stroke is being created
if (stroke != null)
{
// add a new point to the stroke
stroke.StylusPoints.Add(point);
}
}
public void EndStroke()
{
if (stroke != null)
{
// inserts the stroke on the sync list
End.Insert(stroke.ToSerializableStroke());
// create the stroke positions
stroke = null;
}
}
}
The most important part in the code is the: End.Insert(stroke.ToSerializableStroke()) statement. In there you can see that we are getting the End Range (a 0 length tracking range that always point to the end of the list) and inserting at the end a Serializable Stroke created from the real WPF/Silverlight constructed Stroke.
When you are adding the element into the stroke you are ensuring that beWeeVee is already tracking the positional information for that element into that position. However there is a very important thing that we haven’t discussed before, how to send out a message to other beWeeVee instances.
BeWeeVee itself prefers to know nothing about how you prefer to communicate with other instances; however, it provides a reference implementation using Protocol Buffers that can be embedded in almost any method of communication; we also provide the ability to serialize with standard binary encoding too (when available).
In this particular example we are using beWeeVee to synchronize 2 copies in the same process so it is as easy as to send the operation into the other instance. This is a pretty simple way to perform demos but a pretty unrealistic scenario; you can find a WCF implementation to the same idea in the SketcherWithDuplexSample with a very simple server. For high throughput you should use TCP sockets implementations or P2P (PeerNetworking) if in Desktop environments for better performance and low latency response. Don’t worry we will post about it soon, and yes, we have a sample covering P2P technologies too. BeWeeVee after all is designed to handle both standard Client/Server and PeerToPeer scenarios.
The code to perform the reception is the following:
// creates synchronizable collections
var ssc1 = new SynchronizableStrokeCollection();
var ssc2 = new SynchronizableStrokeCollection();// wires up the synchronization
ssc1.Controller.OnBroadcast += (operationSender, args) => ssc2.Controller.ReceiveOperation(args.Operation);
ssc2.Controller.OnBroadcast += (operationSender, args) => ssc1.Controller.ReceiveOperation(args.Operation);
It is pretty simple to notice that each SynchronizableStrokeCollection has also inherited its own ConcurrencyController. What we do is to cross wire them, when the Controller expects to broadcast an operation we are telling the other one to receive it.
If we want to use WCF instead of memory copy we would be doing something pretty similar. As in our example we are deciding that both parties are running in the same browser instance (for simplicity only) then we have 2 InkPresenters so we are sending and receiving from the Server in different proxies:
client1 = new ServerClient(binding, address);
client1.ReceivedReceived += (s, arg) => ssc2.Controller.ReceiveOperation(DeserializeOperation(arg.operation));client2 = new ServerClient(binding, address);
client2.ReceivedReceived += (s, arg) => ssc1.Controller.ReceiveOperation(DeserializeOperation(arg.operation));// wires up the syncronization
ssc1.Controller.OnBroadcast += (operationSender, args) =>
{
var buffer = SerializeOperation(sessionId, args.Operation);
client1.ReceiveAsync(sessionId, buffer);
};
ssc2.Controller.OnBroadcast += (operationSender, args) =>
{
var buffer = SerializeOperation(sessionId, args.Operation);
client2.ReceiveAsync(sessionId, buffer);
};
Up to this moment we have seen how to receive an operation and how to broadcast the operation to the other parties, however, nothing have been done to act when remote operations come from the other sites.
To that we are going to go back again to the SynchronizableStrokeCollection as it has the Controller that should be able to act when operations are sent from the outside. The ElementView<T> is already wired to handle the remote operations, but as we want to also perform those changes into the local stroke collection we also need to wire up the OnRemoteOperationReceived event to execute the operation in the StrokeCollection.
public SynchronizableStrokeCollection()
{
// stroke collection creation
Strokes = new StrokeCollection();
// wires up the operation received event
Controller.OnRemoteOperationReceived += (sender, args) =>
args.Operation.Execute(Strokes, serializableStroke => serializableStroke.FromSerializableStroke());
}
As seen in this code extract that is pretty simple, however as we are using SerializableStrokes to bypass the serializable restriction, we must be able to execute the operation on different type of list. For that, we use a special version of the Execute Command that allows us to convert from a serializable version into a WPF/Silverlight native version.
With that in mind you have all the required building blocks to start synchronizing your linearizable data structures.
Special thanks to Daniel Iglesias from Huddle Group because he programmed the first sketcher demo. Moreover if you are here in Argentina, we will be showing an extended version of this example at Buenos Aires’ CodeCamp 2009 next saturday (26th of September). We will start from the classic “New Project” on Visual Studio up to a WCF Server aware implementation running with a custom server. All, except the server, coded step-by-step in 1 hour using Silverlight 3, Model-View-ViewModel and WCF’ duplex channels. So see you there if you are around.


Recent Comments