Instantly share code, notes, and snippets.

@diericx diericx/Room.ts
Created Mar 17, 2018

Embed
What would you like to do?
import { Client } from "./index";
import { EventEmitter } from "events";
import Clock from "@gamestdio/timer";
import { createTimeline, Timeline } from "@gamestdio/timeline";
import { Protocol, send } from "./Protocol";
import { logError, spliceOne } from "./Utils";
export abstract class Room<T=any> extends EventEmitter {
// Abstract methods
abstract onMessage (client: Client, data: any): void;
abstract getNumClients(clients): number;
abstract mapClients(clients, fun);
abstract setState (newState);
protected abstract sendState (client: Client): void
protected abstract broadcastPatch (): boolean;
protected abstract addClient(client);
protected abstract removeClient(client): boolean;
protected abstract broadcast(data: any, ...args): boolean;
// vars that can be anything but NEED to be implemented
abstract state: any;
abstract clients: any;
// protected vars
protected _simulationInterval: NodeJS.Timer;
protected _patchInterval: NodeJS.Timer;
// Optional abstract methods
onInit? (options: any): void;
onJoin? (client: Client, options?: any): void | Promise<any>;
onLeave? (client: Client): void | Promise<any>;
onDispose? (): void | Promise<any>;
public clock: Clock = new Clock();
public timeline?: Timeline;
public roomId: string;
public roomName: string;
public maxClients: number = Infinity;
public patchRate: number = 1000 / 20; // Default patch rate is 20fps (50ms)
public autoDispose: boolean = true;
// holds a list of clients with clientOptions during handshake
public connectingClients: {[clientId: string]: any} = {};
public get maxClientsReached () {
return this.getNumClients(this.clients) + Object.keys(this.connectingClients).length >= this.maxClients;
}
public requestJoin (options: any, isNew?: boolean): number | boolean {
return 1;
}
public verifyClient (client: Client, options: any): boolean | Promise<any> {
return Promise.resolve(true);
}
public useTimeline ( maxSnapshots: number = 10 ): void {
this.timeline = createTimeline( maxSnapshots );
}
public lock (): void {
this.emit('lock');
}
public unlock (): void {
this.emit('unlock');
}
public send (client: Client, data: any): void {
send(client, [ Protocol.ROOM_DATA, this.roomId, data ]);
}
public setPatchRate ( milliseconds: number ): void {
// clear previous interval in case called setPatchRate more than once
if ( this._patchInterval ) clearInterval(this._patchInterval);
this._patchInterval = setInterval( this.broadcastPatch.bind(this), milliseconds );
}
public setSimulationInterval ( callback: Function, delay: number = 1000 / 60 ): void {
// clear previous interval in case called setSimulationInterval more than once
if ( this._simulationInterval ) clearInterval( this._simulationInterval );
this._simulationInterval = setInterval( () => {
this.clock.tick();
callback();
}, delay );
}
public disconnect (): Promise<any> {
let promises = [];
this.mapClients(this.clients, (client) => promises.push( this._onLeave(client) ))
return Promise.all(promises);
}
private _onJoin (client: Client, options?: any): void {
this.addClient(client);
// confirm room id that matches the room name requested to join
send(client, [ Protocol.JOIN_ROOM, client.sessionId ]);
// send current state when new client joins the room
if (this.state) {
this.sendState(client);
}
if (this.onJoin) {
this.onJoin(client, options);
}
}
private _onLeave (client: Client, isDisconnect: boolean = false): void | Promise<any> {
let userReturnData;
//
// call abstract 'onLeave' method only if the client has been successfully accepted.
//
// the '_onLeave' method may be called before 'verifyClient' succeeds,
// before the client is appended to `this.clients`
//
if (this.removeClient(client) && this.onLeave) {
userReturnData = this.onLeave(client);
}
this.emit('leave', client, isDisconnect);
//
// TODO: force disconnect from server.
//
// need to check why the connection is being re-directed to MatchMaking
// process after calling `client.close()` here
//
if (!isDisconnect) {
send(client, [ Protocol.LEAVE_ROOM, this.roomId ]);
}
// custom cleanup method & clear intervals
if ( this.autoDispose ) {
this._disposeIfEmpty();
}
return userReturnData || Promise.resolve();
}
protected _dispose (): Promise<any> {
let userReturnData;
if ( this.onDispose ) userReturnData = this.onDispose();
if ( this._patchInterval ) clearInterval( this._patchInterval );
if ( this._simulationInterval ) clearInterval( this._simulationInterval );
// clear all timeouts/intervals + force to stop ticking
this.clock.clear();
this.clock.stop();
return userReturnData || Promise.resolve();
}
protected _disposeIfEmpty () {
if ( this.getNumClients(this.clients) == 0 ) {
this._dispose();
this.emit('dispose');
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment