UNPKG

colyseus

Version:

Multiplayer Game Server for Node.js.

166 lines (165 loc) 6.9 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); Object.defineProperty(exports, "__esModule", { value: true }); var msgpack = require("msgpack-lite"); var fossilDelta = require("fossil-delta"); var clock_timer_js_1 = require("clock-timer.js"); var events_1 = require("events"); var timeframe_1 = require("timeframe"); var Protocol_1 = require("./Protocol"); var Utils_1 = require("./Utils"); var Room = (function (_super) { __extends(Room, _super); function Room(options) { if (options === void 0) { options = {}; } var _this = _super.call(this) || this; _this.clock = new clock_timer_js_1.default(); _this.clients = []; _this.roomId = options.roomId; _this.roomName = options.roomName; _this.options = options; // Default patch rate is 20fps (50ms) _this.setPatchRate(1000 / 20); return _this; } Room.prototype.requestJoin = function (options) { return true; }; Room.prototype.setSimulationInterval = function (callback, delay) { var _this = this; if (delay === void 0) { delay = 1000 / 60; } // clear previous interval in case called setSimulationInterval more than once if (this._simulationInterval) clearInterval(this._simulationInterval); this._simulationInterval = setInterval(function () { _this.clock.tick(); callback(); }, delay); }; Room.prototype.setPatchRate = function (milliseconds) { // clear previous interval in case called setPatchRate more than once if (this._patchInterval) clearInterval(this._patchInterval); this._patchInterval = setInterval(this.broadcastPatch.bind(this), milliseconds); }; Room.prototype.useTimeline = function (maxSnapshots) { if (maxSnapshots === void 0) { maxSnapshots = 10; } this.timeline = timeframe_1.createTimeline(maxSnapshots); }; Room.prototype.setState = function (newState) { this.clock.start(); // ensure state is populated for `sendState()` method. this._previousState = Utils_1.toJSON(newState); this._previousStateEncoded = msgpack.encode(this._previousState); this.state = newState; if (this.timeline) { this.timeline.takeSnapshot(this.state); } }; Room.prototype.lock = function () { this.emit('lock'); }; Room.prototype.unlock = function () { this.emit('unlock'); }; Room.prototype.send = function (client, data) { client.send(msgpack.encode([Protocol_1.Protocol.ROOM_DATA, this.roomId, data]), { binary: true }, Utils_1.logError.bind(this)); }; Room.prototype.broadcast = function (data) { // no data given, try to broadcast patched state if (!data) { throw new Error("Room#broadcast: 'data' is required to broadcast."); } // encode all messages with msgpack if (!(data instanceof Buffer)) { data = msgpack.encode([Protocol_1.Protocol.ROOM_DATA, this.roomId, data]); } var numClients = this.clients.length; while (numClients--) { this.clients[numClients].send(data, { binary: true }, Utils_1.logError.bind(this)); } return true; }; Room.prototype.disconnect = function () { var i = this.clients.length; while (i--) { this._onLeave(this.clients[i]); } }; Room.prototype.sendState = function (client) { client.send(msgpack.encode([ Protocol_1.Protocol.ROOM_STATE, this.roomId, this._previousState, this.clock.currentTime, this.clock.elapsedTime, ]), { binary: true }, Utils_1.logError.bind(this)); }; Room.prototype.broadcastPatch = function () { if (!this._previousState) { throw new Error('trying to broadcast null state. you should call #setState on constructor or during user connection.'); } var currentState = Utils_1.toJSON(this.state); var currentStateEncoded = msgpack.encode(currentState); // skip if state has not changed. if (currentStateEncoded.equals(this._previousStateEncoded)) { return false; } var patches = fossilDelta.create(this._previousStateEncoded, currentStateEncoded); // take a snapshot of the current state if (this.timeline) { this.timeline.takeSnapshot(this.state, this.clock.elapsedTime); } this._previousState = currentState; this._previousStateEncoded = currentStateEncoded; // broadcast patches (diff state) to all clients, // even if nothing has changed in order to calculate PING on client-side return this.broadcast(msgpack.encode([Protocol_1.Protocol.ROOM_STATE_PATCH, this.roomId, patches])); }; Room.prototype._onJoin = function (client, options) { this.clients.push(client); // confirm room id that matches the room name requested to join client.send(msgpack.encode([Protocol_1.Protocol.JOIN_ROOM, this.roomId, this.roomName]), { binary: true }, Utils_1.logError.bind(this)); // send current state when new client joins the room if (this.state) { this.sendState(client); } if (this.onJoin) { this.onJoin(client, options); } }; Room.prototype._onLeave = function (client, isDisconnect) { if (isDisconnect === void 0) { isDisconnect = false; } // remove client from client list Utils_1.spliceOne(this.clients, this.clients.indexOf(client)); if (this.onLeave) this.onLeave(client); this.emit('leave', client, isDisconnect); if (!isDisconnect) { client.send(msgpack.encode([Protocol_1.Protocol.LEAVE_ROOM, this.roomId]), { binary: true }, Utils_1.logError.bind(this)); } // custom cleanup method & clear intervals if (this.clients.length == 0) { if (this.onDispose) this.onDispose(); if (this._patchInterval) clearInterval(this._patchInterval); if (this._simulationInterval) clearInterval(this._simulationInterval); this.emit('dispose'); } }; return Room; }(events_1.EventEmitter)); exports.Room = Room;