0

I'm writing a game in NodeJS with Socket.io. I have a Player class where users are able to log in and out of the game. However when a client logs out of the game, and re-joins their old event listeners are firing twice.

I tried looking at this question, but it doesn't seem to work as it still produces duplicate output.

My code:

Output:

[+] Player [OZYaW0Ncrfg21NJDAAAB] has logged in.
[+] Player moved to [10,111]
[-] Player [OZYaW0Ncrfg21NJDAAAB] has logged out.
[+] Player moved to [10,111]
[+] Player [OZYaW0Ncrfg21NJDAAAB] has logged in.
[+] Player moved to [10,111]
[+] Player moved to [10,111]                     <-- this should not happen!
[-] Player [OZYaW0Ncrfg21NJDAAAB] has logged out.

client.js

var socket = io('http://localhost:8080/');
socket.emit('player.login');
socket.emit('player.move', [10, 111]);
socket.emit('player.logout');
socket.emit('player.move', [10, 111]);
socket.emit('player.login');
socket.emit('player.move', [10, 111]);
socket.emit('player.logout');

main.js

var io = require('socket.io')(8080);
var Player = require('./Player');

// All players
var players = {};

io.on('connection', function(socket) {

    // On login
    socket.on('player.login', function() {
        players[socket.id] = new Player(socket);
        console.log('[+] Player [' + socket.id + '] has logged in.');
    });

    // On logout
    socket.on('player.logout', function() {
        delete players[socket.id];
        console.log('[-] Player [' + socket.id + '] has logged out.');
    });
});

Player.js

/**
 * Player class
 * @param socket
 * @constructor
 */
function Player(socket) {
    this.socket = socket;
    this.position = {x : 0, y : 0};
    this.__bind();
}

/**
 * Move a player
 * @param {Array} position
 */
Player.prototype.move = function(position) {
    this.position.x = position[0];
    this.position.y = position[1];
    console.log("[+] Player moved to [" + position[0] + ',' + position[1] + ']');
};

/**
 * Add event listeners
 * @private
 */
Player.prototype.__bind = function() {
    this.socket.on('player.move', this.move.bind(this));
    this.socket.on('player.logout', this.__unbind.bind(this));
};

/**
 * Remove event listeners
 * @private
 */
Player.prototype.__unbind = function() {
    this.socket.removeListener('player.move', this.move);
};

// Exports
module.exports = Player;
Community
  • 1
  • 1
Paradoxis
  • 4,471
  • 7
  • 32
  • 66

2 Answers2

4

The trouble is that EventEmitter.removeListener() requires a reference to the listener function. You passed this.move, but the actual listener function was this.move.bind(this). You didn't keep a reference to that function, so you can't remove the listener with removeListener(). You have a couple of options:

Use EventEmitter.removeAllListeners()

You don't need a function reference with this method. All listeners for the event are removed. If that's ok, this is the simplest solution.

Player.prototype.__unbind = function() {
    this.socket.removeAllListeners('player.move');
};

Keep a reference to the listener

If you don't want to remove all listeners, you'll have to keep a reference to the handler function. Assign a property in the constructor that calls .bind(this) on the prototype method.

function Player(socket) {
    this.socket = socket;
    this.position = {x : 0, y : 0};
    this.move = this.__move.bind(this)
    this.__bind();
}

Player.prototype.__move = function(position) {
    this.position.x = position[0];
    this.position.y = position[1];
    console.log("[+] Player moved to [" + position[0] + ',' + position[1] + ']');
};

Player.prototype.__bind = function() {
    this.socket.on('player.move', this.move); // already bound!
    this.socket.on('player.logout', this.__unbind.bind(this));
};
gilly3
  • 87,962
  • 25
  • 144
  • 176
0

Try this:

/**
 * Player class
 * @param socket
 * @constructor
 */
function Player(socket) {
    this.socket = socket;
    this.position = {x : 0, y : 0};
    this.__bind();
}

/**
 * Move a player
 * @param {Array} position
 */
Player.prototype.move = function(position) {
    this.position.x = position[0];
    this.position.y = position[1];
    console.log("[+] Player moved to [" + position[0] + ',' + position[1] + ']');
};

/**
 * Add event listeners
 * @private
 */
Player.prototype.__bind = function() {
    var self = this;
    this.socket.on('player.move', function(position){
      self.move(position);
    });
    this.socket.on('player.logout', function() {
      self.__unbind();
    });
};

/**
 * Remove event listeners
 * @private
 */
Player.prototype.__unbind = function() {
    this.socket.on('player.move', function(position) {
      console.log('[!] Cannot move to position ['+positon[0]+','+position[1]+']. Reason: loged out');
    });
};

// Exports
module.exports = Player;
num8er
  • 18,604
  • 3
  • 43
  • 57
  • 2
    Please explain in words what you changed and why that fixes the problem. – jfriend00 Aug 28 '15 at 00:39
  • first of all I've changed the way You bind event using closures, second I've created "self" variable to avoid conflicts with "this", third I've user to rewrite event handler with empty closure than removing it. – num8er Aug 28 '15 at 01:50
  • 1
    Please add this explanation to your actual answer. – jfriend00 Aug 28 '15 at 04:18