1

Half a year ago I wrote a little multiplayer game in node.js and with socket.io. Now I'm trying to improve it and one of my improvements is breaking up the code into modules (btw. I'm using expressjs 4.4 and socket.io 1.0). I have a player queue which handles the order of the connected players. Everything went pretty fine until I got stuck with socket.io: I'd like to handle the (dis-)connection listeners in the player queue while I'd like to put all the game related listeners in my game module. I tried a couple of things including Using socket.io from a module and What's the correct way to pass a Socket.IO socket between modules? none of which I brought to work. Currently I'm passing the io object from:

io = require("socket.io")(server);

Am I missing something?

My code:

PlayerManager.js

var log = require("npmlog"),
    Player = require("./Model/Player").Player;

var playerIo;

var PlayerManager = (function() {
    var self;

    function PlayerManager(eventEmitter, io) {
        log.info("PlayerManager", "loading PlayerManager");

        self = this;

        this.eventEmitter = eventEmitter;
        this.io = io;
        this.playerQueue = [];
        this.players = [];

        this.setSocketListeners();
    }

    PlayerManager.prototype.setSocketListeners = function() {
        log.info("PlayerManager", "loading SocketListeners");

        playerIo = this.io.of("/player").on("connection", function(socket) {
            log.info("PlayerManager", "Player " + socket.id + " has connected");

            socket.on("disconnect", function() {
                log.info("PlayerManager", "Player " + socket.id + " has disconnected.");

                self.removePlayer(socket.id);
            });

            self.addPlayer(new Player(socket));
        });
    };

    PlayerManager.prototype.addPlayer = function(player) {
        log.info("PlayerManager", "Pushing Player " + player.socket.id + " to Queue. Current position: " + this.playerQueue.length);

        this.playerQueue.push(player);
    };

    PlayerManager.prototype.removePlayer = function(id) {
        log.info("PlayerManager", "Removing Player " + id + " from Queue");

        var player = this.getPlayerById(id);

        if (!player) {
            return;
        }

        this.playerQueue.splice(this.playerQueue.indexOf(player), 1);
    };

    PlayerManager.prototype.getPlayers = function() {
        log.info("PlayerManager", "Choosing players (length of queue: " + this.playerQueue.length + ")");

        this.players = [];

        if (this.playerQueue.length >= 1) { //should be changed to 2 in PROD
            var player;

            for (var i = 0; i < 4; ++i) {
                player = this.playerQueue.shift();

                if (player !== undefined && !player.socket.disconnected) {
                    player.socket.join("players");
                    this.players.push(player);
                }
            }

            return this.players;
        } else {
            return false;
        }
    };

    PlayerManager.prototype.onGameFinished = function() {
        var player;

        while (this.players && this.players.length > 0) {
            player = this.players.shift();

            player.socket.leave("players");

            log.info("GameManager", "players size: " + self.players.length);
        }
    };

    PlayerManager.prototype.getPlayerById = function(id) {
        for (var i = 0; i < this.playerQueue.length; ++i) {
            if (this.playerQueue[i].socket.id === id) {
                return this.playerQueue[i];
            }
        }

        return false;
    };

    return PlayerManager;
})();

exports = module.exports = PlayerManager;

Game.js

var log = require("npmlog");
var config = require("./config");
var Duck = require("./Model/Duck").Duck;

var Game = (function() {
    var self;

    function Game(eventEmitter, io, players) {
        log.info("Game", "loading ...");

        self = this;

        this.io = io;
        this.eventEmitter = eventEmitter;
        this.players = players;

        setInterval(function() {
            self.io.to("players").emit("blub");
        });

        // ...

    return Game;
})();

exports = module.exports = Game;

and for completeness:

index.js (the client)

define(["socketio"], function(io) {
    var socket = io("/player");

    socket.on("blub", function() {
        console.log("blub");
    });

    setInterval(function() {
        socket.emit("blub");
    }, 1000);
});
Community
  • 1
  • 1
Sphygmomanometer
  • 595
  • 5
  • 10
  • In the meantime I would do something different: Split your code into modules (you already might have done that) but instead of referencing other modules, communicate with them through messaging (e.g. with EventEmitter). This leads to low coupling between your modules. Then write a thin wrapper around your socket.io-stuff. For example: You put your socket.io-related code in module. When you receive something (`socket.on('whatever', fn);`) in the callback function of your socket.io handler you'd immediately forward it through your event emitter to the respective module. – Sphygmomanometer May 21 '15 at 07:00

1 Answers1

0

After one year it's time to clean up and share insights I've gained over that period.

Having socket.io-related code spread all over the project is bad design (or code from any other library for that matter). If you ever would have to change the wrapper library, you'd have to do so in many places in your code base. The better approach is to restrict usage of socket.io to a thin layer where all the communication to and from websockets is done. From there on you'd use another layer of messaging: your own. Node.js' own EventEmitter is everything you'd need for the job.

Sphygmomanometer
  • 595
  • 5
  • 10