Reputation: 534
Intro:
I am building a website which one of its main feature is a whiteboard. You can open a session with another user and then whatever you paint on the board he will see and vice versa.
implementated using HTML and javascript, peer to peer.
The issue:
Right now two things can trigger a drawing on your board - you draw something or your partner in the session draws something. (both trigger a drawing event).
The problems begin when you draw something and in the same time your partner does too. (drawing is mixed, my input and the one received by my partner are mixed, can not parallelly deal with two drawings input)
The solution: (?)
Multithreading could have solved it. Dealing with the partner's drawing in a separate thread. However, JavaScript is not multithreaded. I read about the options of using 'worker'(HTML 5) but if i understand correctly it is very limited in working with variables from the main thread and cannot influence the HTML (change what is seen on the screen).
Any ideas?
Upvotes: 1
Views: 178
Reputation:
Multi-threading won't solve this for you. Web Workers are as mentioned already, limited and does not add any benefits in this context as that would require access to DOM etc. (you could use them to process pixels using transferable objects, ie. typed arrays, but you would have to pipe them back to your main JS thread after processing so you would be just as far).
Good news though, you can draw two different paintings at the same time. I assume you use canvas to achieve this.
Simply create two overlaying canvases and feed your local mouse/touch movements to one of the canvases while feeding the data over your web socket to the other canvas.
The other way is to draw each segment in an atomic manner - for each mouse move and for each data received over web socket representing a movement (you may need to iterate here), for example:
ctx.beginPath(); /// reset path for this segment
ctx.moveTo(oldX, oldY); /// of current client/user
ctx.lineTo(x, y); /// current position
ctx.strokeStyle = currentColor; /// the one drawing this line
ctx.stroke();
(oldX/Y would be initialized the first time by the mouse down event).
Doing this in a segmented way allows both to draw at the same time, albeit it's a bit more performance hungry. Personally I would go for a layered canvas solution if order of lines isn't important. As your solution would use events it would be asynchronous and would deal with an event queue which should work fine here.
Simply set up some arrays to hold the data (old position, styles etc.) for each user so you can use an ID/index for the current user/client.
Some pseudo code to give a simple overview for segmented approach:
var oldX = [],
oldY = [],
currentColor = [], ...;
...set up clients, arrays, mousedown/up etc.
canvas.onmousemove = function(e) {
var pos = getMousePos ...
if (isDrawing) drawSegment(0, pos.x, pos.y); /// f.ex. client 0 = local
}
function handleSocketData() {
... get x/y from data stream
drawSegment(1, x, y); /// f.ex. client 1 = web socket
}
function drawSegment(client, x, y) {
ctx.beginPath();
ctx.moveTo(oldX[client], oldY[client]);
ctx.lineTo(x, y);
ctx.strokeStyle = currentColor[client];
ctx.stroke();
oldX[client] = x;
oldY[client] = y;
}
This is of course simplified. You would need to store each point in a point array with the same state details as when drawn to be able to redraw the canvas if cleared, handle multiple segments on the socket and so forth but I think it gives an impression of how to implement the core principle of this method.
Hope this helps!
Upvotes: 1
Reputation: 61865
In this case the "race condition" is not related to or solved by threading - it would actually just make it more complicated to have threads.
Instead, the serialization schedule/order needs to be defined and followed when implementing which lines are drawn, and when - e.g. Who's lines go "on top"? And is it by-segment or by-path? Priority by pen-down (first) or pen-up (last)? This can be just as simply handled within context of the asynchronous model used by JavaScript.
The easiest approach is just to draw the lines based on the order in which they are received by a client; if there needs to be a consistent view then the algorithm/protocol needs to introduce synchronization such as a shared counter or "timestamp", perhaps injected by the server when it receives the updates.
The state for the different peers can be trivially maintained in objects (e.g. maps keyed to the peer) and is not hard to setup or maintain.
Web Workers are the closest thing that [browser] JavaScript has to threading.
Web Workers are more akin to BackgroundWorkers as are often used from UI frameworks such as Swing in Java or .NET WebForms. That is, the background task - be it a Web Worker or a Background Worker - is not allowed to modify the UI/HTML but must trigger some form of callback which is then processed on the "correct thread".
This actually works out much better in many cases than unrestricted threading and cross-thread access: no consistency or atomicity worries!
Upvotes: 0
Reputation: 967
I don't think multiple threads are required in this case. You can do all your drawing in single thread. Lets say you have a function which draws a line between two points. You can call this function on a local draw event (when user himself draws) and also when partner data is received. You can even animate partner drawing after a short delay.
Upvotes: 0