> Device motion + websockets demo

Socket balls

iOS 4.2 is finally out and it adds a couple of new important tools to the web developer’s arsenal: websockets and devicemotion. The former makes client/server communication a snap, the latter brings accelerometer support to the browser. I wanted to test both technologies and see what problems might arise. It has been a very interesting experience, here’re my impressions.

The project is called Socket balls and you can grab/fork it from github (do whatever you want with it, it’s MIT licensed). I’ve also set up a test server at cubiq.org/dropbox/sb, but won’t be always up, sorry.

As soon as you connect to the demo page, you’ll see a red ball that you can control by tilting the iPad. You need iPad 4.2 of course and you should lock device rotation. The ball is handled by the devicemotion event and the responsiveness of this thing pretty much impressed me.

If other users are connected to the application, you’ll see them as grey balls. Spheres position is updated between clients (almost) in realtime with the help of Node.js and Socket.IO plugin.

If you want to replicate the installation locally, install Node.js and the latest Socket.IO from git repo. Copy the Socket.IO client into the project folder in a subfolder called socket.io, and execute the server side JS (node server.js).

onDeviceMotion Event

Listening to the accelerometer is ridiculously easy:

window.addEventListener('devicemotion', function (e) {
	ax = e.accelerationIncludingGravity.x * sensitivity;
	ay = -e.accelerationIncludingGravity.y * sensitivity;
}, false);

Here we store the acceleration on both axis in a couple of global variables for later use. sensitivity variable reduces the otherwise ultra-susceptible accelerometer. Initially I wanted to place the ball movement directly inside the devicemotion event function, but the event polling seems inconsistent or at least laggy. The smartest thing I could come out with was to put the ball translation inside a zero-setInterval:

mainLoop = setInterval(moveMe);

The moveMe() function takes care of moving the user’s sphere.

function moveMe () {
	me.move();
	buffer[buffer.length] = { x: me.x, y: me.y };
	if (buffer.length > 10) buffer.shift();
	sendPosition();
}

The interesting part of it is that I’m using a buffer to store ball position. We will come back to this later when talking about socket connection. All you need to know right now is that sendPosition() is responsible of sending the ball position to the server.

Server Side Javascript (server.js)

I used “Node.js”, the buzz word has been pronounced, I’m now a better developer.

40-lines-of-code handles the whole coordinates distribution between clients.

socket.on('connection', function (client) {
	client.send({ type: 'playerslist', list: players });

	players[client.sessionId] = { x:0, y:0 }

	socket.broadcast({ type: 'new', id: client.sessionId }, [client.sessionId]);

	client.on('message', function (message) {
		if (message.type != 'position') {
			return;
		}

		players[message.id] = { x: message.x, y: message.y };
		client.send({ type: 'position', list: players });
	});
	
	client.on('disconnect', function () {
		delete players[this.sessionId];
		socket.broadcast({ type: 'leave', id: this.sessionId });
	});
});

The code is self explanatory. Node is all event based. When an user connects the connection event is fired and message and disconnect events are registered.

Upon connection we send the list of already logged in users to the new client (client.send({ type: 'playerslist', list: players });). The message event is executed each time the client sends a message through the socket. In our case the client sends the ball x/y position, the server stores that data in a variable and sends to the same client the position of all other clients.

The server does not broadcast the user’s new location to all clients, but it waits that each client is ready to receive data. This is important to prevent network congestion.

On disconnect we remove the user from the client list and send to all clients a ping to inform them that the user has logged out (and the ball is removed from the DOM).

Client Side (client.js)

Client.js file takes care of handling the messages coming from the Node server. Again it’s event based.

First of all we try to connect (var connection = socket.connect()) and we set a 3 seconds timeout to check if the connection has been really established.

socket.on('connect', function () {
	clearTimeout(connectTimeout);
	sessionid = this.transport.sessionid;
	me = new Player(sessionid, 'player');
	mainLoop = setInterval(moveMe);
});

On connect we create the red ball and we start the mainLoop (inside a zero-setInterval, as mentioned before). The main loop both moves the red ball and sends its position to the server (function sendPosition()).

function sendPosition () {
	if (ready) {
		ready = false;

		var pos = buffer.length ? buffer[0] : { x:me.x, y:me.y };
		buffer.shift();

		socket.send({ type:'position', id:me.id, x:pos.x, y:pos.y });
	}
}

This is very important as we are using the sendPosition function as a heartbeat. Data through the socket is send only if the client is ready. The ready global is set to true only when the server has received the previous packet of data.

If the server received the user location, and the user received the other clients position, the ready global is set back to true and the cycle restart. This means that if the connection is slow, or the server is too busy, we will be loosing cycles, that’s why we are saving the spheres location inside a buffer. The higher the buffer (I set it to 10 in the demo, which is very low) the smoother will be the balls movements, but also the higher will be the lag. In a real time game you want low lag, so you probably don’t want to use a buffer, but of course the spheres will be moving all around the screen in a very jumpy way (no fluid movement). That’s why you need both a fast server and a fast connection.

In a less stressful environment (ie: not a game) you can instead use a very high buffer, so no data is lost.

What I’ve learned

Floods! You have to take care of how data is sent through the socket, there are no automatic checks nor buffers. If you send too much data the connection just drops or even worse the browser crashes.

Stability. Connection to the server is sometime lost without a reason. You have to check if the user disconnected and act accordingly. Also the combination of devicemotion and websockets seems still a bit bleeding edge. The browser sometimes crashed in my tests, especially when resuming from standby (I know more checks are needed, my bad). I noticed that the debug console higher the crash count. So try your application with and without it.

Battery life. Persistent connections + accelerometer suck up all your battery life. But this is also true for native applications that use similar technologies.

Node.js is light on the server. I did not stress test the application, but the server load has always been at 0.00 during my tests.

Conclusions

I think that we are not still there for a real-time action multiplayer browser-games. The technology is still dodgy and the browser crashes a bit too much. But for more relaxed applications the iOS4.2 powered iPad is an impressive beast. And I’m looking forward to seeing what the Android reply will be. It’s an amazing time for being a web developer.

/Share the joy

/Reactions

    • Author: @bdufresne
    • Posted on: 2010/11/29
    • At: 15:48

    Another amazing project by Matteo S.

    • Author: Dennis K
    • Posted on: 2010/12/06
    • At: 14:33

    Is there a solution to lock the device orientation?

    Canvas based Games would be so cool if we can lock the device from within Safari.

    • You have to check for orientation and show the user a message suggesting to rotate the device

    • Author: Preston
    • Posted on: 2010/12/20
    • At: 05:07

    Very cool work – I couldn’t find any demo’s of integrating these two features – so built my own demo, then finally found this page after the fact. Great stuff – here is my demo (source still too sloppy to share, hopefully will clean up)

    http://www.youtube.com/watch?v=KL9cdQxsNRM

    -P

  • Alas, I was the only one… the red ball bounces alone.

    • Author: neil stein
    • Posted on: 2011/02/04
    • At: 00:51

    i have been playing with your sample code, all seems ok- I get the connection, and then it is immediately dropped- any direction?

    • it’s most likely a socket.io problem, use the latest version on github (not the one on NPM)

    • Author: Max
    • Posted on: 2011/04/18
    • At: 18:20

    Hello!

    I’ve installed node.js and socket.io and I get the Hello World example to work, but I can’t this to work at all.

    The server is running

    18 Apr 17:57:58 – socket.io ready – accepting connections

    But when i visit the pag eon my ipad the screen is just blank.

    This is on a hosted debian-server, ip @ xx.xx.xxx.xxx and this project is the index.html file. any idea whta im doing wrong?

    thank you

    • no errors in the debug console on ipad? can you connect with telnet to the 8008 port?

    • Author: max
    • Posted on: 2011/04/19
    • At: 11:01

    Him I got it working when downloading the correct socket.io lib, i used an old version or something i guess, because now it works. Could you please remove the IP i posted in a haste? Would be great, thank you for a great example, really gave me some cool ideas.

    • keep me posted about your project/ideas! ;)

    • Author: Yang
    • Posted on: 2012/01/26
    • At: 14:04

    Hi, I’m quite curious about the issue of floods. Could you elaborate on how you handled it?

  • It?s in point of fact a great and helpful piece of information. I am satisfied that you shared this helpful info with us. Please keep us informed like this. Thank you for sharing.

    • Author: Anil
    • Posted on: 2012/03/26
    • At: 19:26

    Have a look at a similar implementation – Iphone as a controller to play space invaders http://www.webdigi.co.uk/fun/space/