Created
March 17, 2018 17:58
-
-
Save diericx/085738b1510c7a9c7fc8c74da4e8874a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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