colyseus
Version:
Multiplayer Game Server for Node.js.
171 lines (130 loc) • 5.05 kB
text/typescript
import * as http from "http";
import { EventEmitter } from "events";
import { Server as WebSocketServer, IServerOptions } from "uws";
import { Protocol } from "./Protocol";
import { MatchMaker } from "./MatchMaker";
import { spliceOne } from "./Utils";
import { Room } from "./Room";
import { Client } from "./index";
import * as shortid from "shortid";
import * as msgpack from "msgpack-lite";
export type ServerOptions = IServerOptions & {
ws?: WebSocketServer
};
// // memory debugging
// setInterval(function() { console.log(require('util').inspect(process.memoryUsage())); }, 1000)
export class Server extends EventEmitter {
protected server: WebSocketServer;
protected matchMaker: MatchMaker = new MatchMaker();
// room references by client id
protected clients: {[id: string]: Room<any>[]} = {};
constructor (options?: ServerOptions) {
super();
if (options) {
this.attach(options);
}
}
/**
* Attaches Colyseus server to a server or port.
*/
public attach (options: ServerOptions) {
if (options.server || options.port) {
this.server = new WebSocketServer(options);
} else {
this.server = options.ws;
}
this.server.on('connection', this.onConnect);
}
/**
* @example Registering with room name + class handler
* server.register("room_name", RoomHandler)
*
* @example Registering with room name + class handler + custom options
* server.register("area_1", AreaHandler, { map_file: "area1.json" })
* server.register("area_2", AreaHandler, { map_file: "area2.json" })
* server.register("area_3", AreaHandler, { map_file: "area3.json" })
*/
public register (name: string, handler: Function, options?: any) {
this.matchMaker.addHandler(name, handler, options);
}
private onConnect = (client: Client) => {
let clientId = shortid.generate();
client.id = clientId;
client.send( msgpack.encode([ Protocol.USER_ID, clientId ]), { binary: true } );
client.on('message', this.onMessage.bind(this, client));
client.on('error', this.onError.bind(this, client));
client.on('close', this.onDisconnect.bind(this, client));
this.clients[ clientId ] = [];
this.emit('connect', client);
}
private onError (client: Client, e: any) {
console.error("[ERROR]", client.id, e);
}
private onMessage (client: Client, data: any) {
let message;
// try to decode message received from client
try {
message = msgpack.decode(Buffer.from(data));
} catch (e) {
console.error("Couldn't decode message:", data, e.stack);
return;
}
this.emit('message', client, message);
if (typeof(message[0]) === "number" && message[0] == Protocol.JOIN_ROOM) {
this.onJoinRoomRequest(client, message[1], message[2], (err: string, room: Room<any>) => {
if (err) {
let roomId = (room) ? room.roomId : message[1];
client.send(msgpack.encode([Protocol.JOIN_ERROR, roomId, err]), { binary: true });
if (room) (<any>room)._onLeave(client);
}
});
} else if (typeof(message[0]) === "number" && message[0] == Protocol.LEAVE_ROOM) {
// trigger onLeave directly to specific room
let room = this.matchMaker.getRoomById( message[1] );
if (room) (<any>room)._onLeave(client);
} else if (typeof(message[0]) === "number" && message[0] == Protocol.ROOM_DATA) {
// send message directly to specific room
let room = this.matchMaker.getRoomById( message[1] );
if (room) room.onMessage(client, message[2]);
} else {
this.clients[ client.id ].forEach(room => room.onMessage(client, message));
}
}
private onJoinRoomRequest ( client: Client, roomToJoin: number | string, clientOptions: any, callback: (err: string, room: Room<any>) => any): void {
var room: Room<any>;
let err: string;
if (typeof(roomToJoin)==="string") {
room = this.matchMaker.joinOrCreateByName(client, roomToJoin, clientOptions || {});
} else {
room = this.matchMaker.joinById(client, roomToJoin, clientOptions);
}
if ( room ) {
try {
(<any>room)._onJoin(client, clientOptions);
} catch (e) {
console.error(room.roomName, "onJoin:", e.stack);
err = e.message;
}
room.once('leave', this.onClientLeaveRoom.bind(this, room));
this.clients[ client.id ].push( room );
} else {
err = "join_request_fail";
}
callback(err, room);
}
private onClientLeaveRoom = (room: Room<any>, client: Client, isDisconnect: boolean): boolean => {
if (isDisconnect) {
return true;
}
var roomIndex = this.clients[ client.id ].indexOf(room);
if (roomIndex >= 0) {
spliceOne(this.clients[ client.id ], roomIndex);
}
}
private onDisconnect (client) {
this.emit('disconnect', client);
// send leave message to all connected rooms
this.clients[ client.id ].forEach(room => (<any>room)._onLeave(client, true));
delete this.clients[ client.id ];
}
}