UNPKG

colyseus

Version:

Multiplayer Game Server for Node.js.

171 lines (130 loc) 5.05 kB
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 ]; } }