Over at Automattic, we have a fair few projects that use WebSockets. I knew what they were, but I’d never really used them for anything serious.
If you haven’t heard of WebSockets, they’re an easy way for a webpage and server to send messages to each other, without having to mess about with forms, or individual requests, or reloading pages.
My usual learning style is that I have to do something useful with a tech before I really get it. Sure, I can hold abstract concepts in my head, but it doesn’t really settle in until I do a project using them.
So I did this: http://draw.notnownikki.com/ – a 16×16 grid of pixels that anyone can draw on. You see everyone’s drawing happen in (pretty much) real time, and it works on most modern mobile devices too.
I learned a lot from doing this, both from a technical and personal perspective.
The technical stuff.
WebSockets are easy! Start a server, have the client open a connection, and send messages back and forth!
Managing events and making sure all your clients have the correct, up-to-date data, is hard.
When the user clicks on a pixel, a message gets sent to the server, saying, “Hi! We’re coloring in pixel X with color Y!”
Then, the server has to notify all the connected clients that the pixel has a new color, so a message gets sent out saying, “Someone just colored pixel X with color Y!” and everyone’s screen updates.
It would be if we only had one user. And if networks were all the same speed and everyone always got every message in the same order. But, we don’t, they’re not, and they don’t.
Let’s say that Emma and Nikki both go to color the same pixel at the same time. Emma used pink and Nikki used purple. The server gets these two messages, and sends the notification out to all the connected users. The server is going to do send out the “color it pink!” and “color it purple!” at pretty much the same time, and there’s no guarantee that connected users would get them in the order they happened. That means, some of the people might see pink as the last color applied to that pixel, and others might see purple.
That would be wrong and bad.
There were a few solutions I considered.
The next one I considered was to lock the entire image, update it, and send out the new data. This works, but means that every change is blocked when any change is getting processed.
What I finally settled for was locking individual pixels during a change. That meant that other bits of coloring could go on unblocked, but we’d be sure that the data we were changing wasn’t going to change underneath us when we were half-way done updating it.
Still, there was the issue of two changes going through, and not arriving in sequence to all the connected users.
See, while the state of the pixels was guaranteed to be consistent on the server side, the messages going out to connected users might not arrive in the order they were processed.
Let’s say we have 3 users. We get two changes to the same pixel, Emma sets it pink, and then Nikki sets it purple. Now those changes have to go out to the users. While both changes are getting sent out, the pink change might stall a little sending to the first user, because of some system conditions beyond our control. That means we could have finished sending out the purple change to all three users, when the pink change had not yet been sent. What happens then? The pink change carries on sending after the purple change had finished sending, and we have users seeing a pink pixel when they should be seeing purple!
We can’t have that, can we!
The solution here was a serial number for pixel changes.
When we set a pixel, we increment a serial number for it, so we can say “Change 5 to pixel X was to set it pink”, and, “Change 6 to pixel X was to set it purple.”
Now when clients get a message to set a pixel’s color, it can check if it has a more up to date change by comparing the serial number of the change. If change 6 (setting it purple) had been applied, and then we get a message saying to set it pink with a serial number of 5, we know that change is out of date and can be discarded.
And, it works!
Although there are scaling issues (it’s limited to a single nodejs process at the moment) these issues are solvable with a solution like Redis. But I doubt I’m going to need that with this application 🙂
The personal side.
I learned that, even though the canvas is a shared environment, I feel really strange going over stuff other people have drawn. It’s like I don’t want them to be offended if I add shading, or change it somehow!
I found I wasn’t alone in this either.
I also learned that the people on my twitter feed are lovely, and in a few hours of me tweeting out the address, we had collectively drawn this!
I plan to increase the resolution of the canvas and make the color palette nicer next, and hopefully get some of my pixel-arting friends to show what can really be done when people with talent collaborate!