Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Node.js and Mongodb Building Blocks for Your Next HTML5 game

mongodb
April 04, 2012

Node.js and Mongodb Building Blocks for Your Next HTML5 game

Christian Kvalheim, Software Engineer, 10gen

MongoDB Berlin 2012

mongodb

April 04, 2012
Tweet

More Decks by mongodb

Other Decks in Technology

Transcript

  1. Gaming and HTML5 •Space is moving fast •Canvas/WebGL getting better

    and better •Lot’s of libraries but Zynga and Disney keeps buying the good ones
  2. Technology •Akihabara for game engine •SoundJS for the audio •websocket

    npm package •Socket.IO was to slow •Binary frames only on Chrome +Firefox 11 •Mongo capped collections •BSON parser in the browser
  3. Akihabara •Retro 2D game engine •“Simple” to adapt example game

    to Mongoman •Uses canvas only so not the fastest game engine out there but plenty good enough :)
  4. Mongo Capped collections •Positives •In memory only •Fifo based limiting

    memory exposure •Predictable time to write/read from collection •Supports Indexes •Tailable (like tail -f)
  5. Mongo Capped collections •Negatives •Documents cannot grow so need to

    prime each document •No single server persistence, server dies game gone.
  6. BSON Parser •Initial version (wait to use) •Supports node.js Buffers

    •Support Browser Uint8Array and Array •Plenty fast deserialization
  7. Node.JS •Express serves up the basic html pages and handles

    the hiscore list. •Organized using the cluster api in Node.js 0.6.X •1 parent process and X child processes handling game •If a child dies get’s restarted
  8. Cluster code // Fork workers (one pr. cpu), the web

    workers handle the websockets for (var i = 0; i < numCPUs; i++) { var worker = cluster.fork(); worker.on('message', function(msg) { if(msg != null && msg['cmd'] == 'online') { console.log("================================ worker online"); console.dir(msg); } }); } // If the worker thread dies just print it to the console and for a new one cluster.on('death', function(worker) { console.log('worker ' + worker.pid + ' died'); cluster.fork(); });
  9. Session workaround •Could not use express sessions as they don’t

    work with the websocket module •Wrote own session handling using capped collection •A bit HACKY bit works
  10. Websocket session grab code // Parse the cookie var cookie

    = connectUtils.parseCookie(request.httpRequest.headers['cookie']); // Grab the session id var sessionId = cookie['connect.sid']; // Grab the username based on the session id and initialize the board state.sessionsCollection.findOne({id:sessionId}, function(err, session) { if(err) throw err; session = typeof session == 'undefined' || session == null ? {} : session; initializeBoard(state, session, self); }) •Uses connect npm package for nice to have cookie parser functions •Using capped collection aswell
  11. .findAndModify( {number_of_players: {$lt:5}, pid: process.pid}, [], {$inc: {number_of_players: 1}, $push:{players:connection.connectionId}},

    {new:true, upsert:false}, function(err, board) { // Code to deal with the board or the non existance of the board } Initializing a board •Using findAndModify to pick a board •Atomic operation to add player to existing board •Process bound
  12. One for all, all for one •Websocket connections are bound

    to a specific process •Avoids overhead •Would need separate IO queues
  13. Message passing •Non-frequent messages in JSON •intialize •ghost died •mongoman

    dies •game over •Frequent messages in Binary •Movement information
  14. // Binary message update player position state.gameCollection.update({id: self.connectionId}, message.binaryData); //

    Let's grab the record state.gameCollection.findOne({id: self.connectionId, state:'n'}, {raw:true}, function(err, rawDoc) { if(rawDoc) { // Retrieve the board by id from cache var boardId = state.boardIdByConnections[self.connectionId]; var board = state.connectionsByBoardId[boardId]; // Send the data to all the connections expect the originating connection for(var i = 0; i < board.length; i++) { if(board[i] != self.connectionId) { if(state.connections[board[i]] != null) state.connections[board [i]].sendBytes(rawDoc); } } } }); Binary message passing •BSON de/serialization in the browser for movement. Raw mode queries.
  15. Client side processing •Using BSON parser •Available soon, Uint8Array and

    Array support. // Serialize a message BSON.serialize({'$set': {'pos': updateObject}}, false, true, false) // Handle the web socket messages GameCommunication.prototype.handleWebsocketMessage = function(message) { // Let's handle the message, deserializing it as needed if(message.data instanceof ArrayBuffer) { this.callback(BSON.deserialize(new Uint8Array(message.data))); } else { this.callback(JSON.parse(message.data)); } }
  16. Justification •Decrease latency by avoiding •Deserialize BSON on Server •Serialize

    BSON to JSON on Server •Deserialize JSON to Object on Client •Just Deserialize once on client •Less time spent in the EventLoop
  17. Light speed that #@!@#$ •Your max resolution is 2X times

    the ping time to the server •20ms = 40 ms delivery = 25 fps •135 = 270 ms delivery = 4 fps •No you know why it’s a WOW EU server or US server
  18. •Websockets still fresh •Few browsers support binary •Latency is the

    mind killer •If you users are in Germany have a local server for Germany •Hard to test games, time consuming •Anybody know a good test framework tell me :)
  19. What’s coming for you •Finishing up BSON NPM package •Making

    single BSON file both in normal and minified version for browsers •Some way to link directly to the latest one like JQuery.
  20. Demo game •Join in the game, DDOS My laptop point

    your Chrome or Firefox 11+ browser to •165.225.128.82:3000
  21. @mongodb conferences, appearances, and meetups http://www.10gen.com/events http://bit.ly/mongofb Facebook | Twitter

    | LinkedIn http://linkd.in/joinmongo download at mongodb.org support, training, and this talk brought to you by