Skip to content

Commit

Permalink
Add client dispatcher mode and server metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
sork committed Mar 9, 2012
1 parent 700c080 commit 3309305
Show file tree
Hide file tree
Showing 16 changed files with 202 additions and 474 deletions.
Binary file added .DS_Store
Binary file not shown.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ node_modules
client-build
build.txt
config_build.json
config_local.json
config_local*
11 changes: 9 additions & 2 deletions client/js/game.js
Original file line number Diff line number Diff line change
Expand Up @@ -707,8 +707,15 @@ function(InfoManager, BubbleManager, Renderer, Map, Animation, Sprite, AnimatedT
var self = this;

this.client = new GameClient(this.host, this.port);
this.client.connect();

this.client.connect(true);

this.client.onDispatched(function(host, port) {
self.client.host = host;
self.client.port = port;
alert("host:"+host+ " port:"+port);
self.client.connect(); // connect to actual game server
});

this.client.onConnected(function() {
log.info("Connected to server "+self.client.host+":"+self.client.port);

Expand Down
58 changes: 38 additions & 20 deletions client/js/gameclient.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ define(['player', 'entityfactory', 'lib/bison'], function(Player, EntityFactory,
disable: function() {
this.isListening = false;
},

connect: function() {
connect: function(dispatcherMode) {
var url = "ws://"+ this.host +":"+ this.port +"/",
self = this;

Expand All @@ -55,25 +55,39 @@ define(['player', 'entityfactory', 'lib/bison'], function(Player, EntityFactory,
} else {
this.connection = new WebSocket(url);
}

this.connection.onopen = function(e) {
if(self.connected_callback) {
self.connected_callback();
}
};

if(dispatcherMode) {
this.connection.onmessage = function(e) {
var reply = JSON.parse(e.data);

this.connection.onmessage = function(e) {
self.receiveMessage(e.data);
};

this.connection.onerror = function(e) {
log.error(e, true);
};

this.connection.onclose = function() {
log.debug("Connection closed");
$('#container').addClass('error');
};
if(reply.status === 'OK') {
self.dispatched_callback(reply.host, reply.port);
} else if(reply.status === 'FULL') {
alert("BrowserQuest is currently at maximum player population. Please retry later.");
} else {
alert("Unknown error while connecting to BrowserQuest.");
}
};
} else {
this.connection.onopen = function(e) {
if(self.connected_callback) {
self.connected_callback();
}
};

this.connection.onmessage = function(e) {
self.receiveMessage(e.data);
};

this.connection.onerror = function(e) {
log.error(e, true);
};

this.connection.onclose = function() {
log.debug("Connection closed");
$('#container').addClass('error');
};
}
},

sendMessage: function(json) {
Expand Down Expand Up @@ -339,6 +353,10 @@ define(['player', 'entityfactory', 'lib/bison'], function(Player, EntityFactory,
this.blink_callback(id);
}
},

onDispatched: function(callback) {
this.dispatched_callback = callback;
},

onConnected: function(callback) {
this.connected_callback = callback;
Expand Down
3 changes: 2 additions & 1 deletion server/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"debug_level": "info",
"nb_players_per_world": 200,
"nb_worlds": 5,
"map_filepath": "./server/maps/world_server.json"
"map_filepath": "./server/maps/world_server.json",
"metrics_enabled": false
}
3 changes: 2 additions & 1 deletion server/config_local.json-dist
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"debug_level": "debug",
"nb_players_per_world": 200,
"nb_worlds": 5,
"map_filepath": "./server/maps/world_server.json"
"map_filepath": "./server/maps/world_server.json",
"metrics_enabled": false
}
73 changes: 61 additions & 12 deletions server/js/main.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

var fs = require('fs');
var fs = require('fs'),
Metrics = require('./metrics');


function main(config) {
Expand All @@ -8,6 +9,7 @@ function main(config) {
Log = require('log'),
_ = require('underscore'),
server = new ws.MultiVersionWebsocketServer(config.port),
metrics = config.metrics_enabled ? new Metrics(config) : null;
worlds = [];

switch(config.debug_level) {
Expand All @@ -19,32 +21,71 @@ function main(config) {
log = new Log(Log.INFO); break;
};

log.info("Starting BrowserQuest game server...");

server.onConnect(function(connection) {
var world = _.detect(worlds, function(world) {
return world.playerCount < config.nb_players_per_world;
});
if(world) {
world.connect_callback(new Player(connection, world));
var world, // the one in which the player will be spawned
connect = function() {
if(world) {
world.connect_callback(new Player(connection, world));
connection.send("go");
}
};

if(config.metrics_enabled) {
metrics.getOpenWorldCount(function(open_world_count) {
// choose the least populated world among open worlds
world = _.min(_.first(worlds, open_world_count), function(w) { return w.playerCount; });
connect();
});
}
else {
// simply fill each world sequentially until they are full
world = _.detect(worlds, function(world) {
return world.playerCount < config.nb_players_per_world;
});
connect();
}
});

server.onError(function() {
log.error(Array.prototype.join.call(arguments, ", "));
});

var onPopulationChange = function() {
metrics.updatePlayerCounters(worlds);
metrics.updateWorldDistribution(getWorldDistribution(worlds));
};

_.each(_.range(config.nb_worlds), function(i) {
var world = new WorldServer('world'+ (i+1), config.nb_players_per_world, server);
world.run(config.map_filepath);
worlds.push(world);

if(config.metrics_enabled) {
world.onPlayerAdded(onPopulationChange);
world.onPlayerRemoved(onPopulationChange);
}
});

server.onRequestStatus(function() {
var status = [];
_.each(worlds, function(world) {
status.push(world.playerCount);
return JSON.stringify(getWorldDistribution(worlds));
});

if(config.metrics_enabled) {
metrics.ready(function() {
onPopulationChange(); // initialize all counters to 0 when the server starts
});
return JSON.stringify(status);
}
}

function getWorldDistribution(worlds) {
var distribution = [];

_.each(worlds, function(world) {
distribution.push(world.playerCount);
});
return distribution;
}

function getConfigFile(path, callback) {
Expand All @@ -58,9 +99,17 @@ function getConfigFile(path, callback) {
});
}

var defaultConfigPath = './server/config.json',
customConfigPath = './server/config_local.json';

process.argv.forEach(function (val, index, array) {
if(index === 2) {
customConfigPath = val;
}
});

getConfigFile('./server/config.json', function(defaultConfig) {
getConfigFile('./server/config_local.json', function(localConfig) {
getConfigFile(defaultConfigPath, function(defaultConfig) {
getConfigFile(customConfigPath, function(localConfig) {
if(localConfig) {
main(localConfig);
} else if(defaultConfig) {
Expand Down
66 changes: 66 additions & 0 deletions server/js/metrics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@

var cls = require("./lib/class"),
_ = require("underscore"),
memcache = require("memcache");

module.exports = Metrics = Class.extend({
init: function(config) {
var self = this;

this.config = config;
this.client = new memcache.Client(config.memcached_port, config.memcached_host),
this.client.connect();
this.isReady = false;

this.client.on('connect', function() {
log.info("Metrics enabled: memcached client connected to "+config.memcached_host+":"+config.memcached_port);
self.isReady = true;
if(self.ready_callback) {
self.ready_callback();
}
});
},

ready: function(callback) {
this.ready_callback = callback;
},

updatePlayerCounters: function(worlds) {
var self = this,
config = this.config,
numServers = _.size(config.game_servers),
playerCount = _.reduce(worlds, function(sum, world) { return sum + world.playerCount; }, 0);

if(this.isReady) {
// Set the number of players on this server
this.client.set('player_count_'+config.server_name, playerCount, function() {
var total_players = 0;

// Recalculate the total number of players and set it
_.each(config.game_servers, function(server) {
self.client.get('player_count_'+server.name, function(error, result) {
var count = result ? parseInt(result) : 0;

total_players += count;
numServers -= 1;
if(numServers === 0) {
self.client.set('total_players', total_players);
}
});
});
});
} else {
log.error("Memcached client not connected");
}
},

updateWorldDistribution: function(worlds) {
this.client.set('world_distribution_'+this.config.server_name, worlds);
},

getOpenWorldCount: function(callback) {
this.client.get('world_count_'+this.config.server_name, function(error, result) {
callback(result);
});
}
});
3 changes: 0 additions & 3 deletions server/js/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ module.exports = Player = Character.extend({
this.lastCheckpoint = null;
this.formatChecker = new FormatChecker();

this.server.incrementPlayerCount();

this.connection.listen(function(message) {
var action = parseInt(message[0]);

Expand Down Expand Up @@ -206,7 +204,6 @@ module.exports = Player = Character.extend({
if(self.exit_callback) {
self.exit_callback();
}
self.server.decrementPlayerCount();
});
},

Expand Down
26 changes: 23 additions & 3 deletions server/js/worldserver.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@

var sys = require("sys"),
cls = require("./lib/class"),
var cls = require("./lib/class"),
_ = require("underscore"),
Log = require('log'),
Entity = require('./entity'),
Expand Down Expand Up @@ -61,7 +60,11 @@ module.exports = World = cls.Class.extend({
});

this.onPlayerEnter(function(player) {
log.info(player.name + " has joined "+ this.id);
log.info(player.name + " has joined "+ self.id);

if(!player.hasEnteredGame) {
self.incrementPlayerCount();
}

// Number of players in this world
self.pushToPlayer(player, new Messages.Population(self.playerCount));
Expand Down Expand Up @@ -108,7 +111,16 @@ module.exports = World = cls.Class.extend({
player.onExit(function() {
log.info(player.name + " has left the game.");
self.removePlayer(player);
self.decrementPlayerCount();

if(self.removed_callback) {
self.removed_callback();
}
});

if(self.added_callback) {
self.added_callback();
}
});

// Called when an entity is attacked by another entity
Expand Down Expand Up @@ -209,6 +221,14 @@ module.exports = World = cls.Class.extend({
this.enter_callback = callback;
},

onPlayerAdded: function(callback) {
this.added_callback = callback;
},

onPlayerRemoved: function(callback) {
this.removed_callback = callback;
},

onRegenTick: function(callback) {
this.regen_callback = callback;
},
Expand Down
Loading

0 comments on commit 3309305

Please sign in to comment.