Created
March 17, 2018 17:59
-
-
Save diericx/adfc3ff53910804f79b815a96091ad14 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 * as msgpack from "notepack.io"; | |
import * as fossilDelta from "fossil-delta"; | |
import * as shortid from "shortid"; | |
import { Client } from "./index"; | |
import { Protocol, send } from "./Protocol"; | |
import { logError, spliceOne } from "./Utils"; | |
import { debugPatch, debugPatchData } from "./Debug"; | |
import {Room} from "./Room"; | |
import * as jsonPatch from "fast-json-patch"; // this is only used for debugging patches | |
export abstract class RoomAll<T=any> extends Room { | |
public clients: Client[] = []; | |
public state: T; | |
// when a new user connects, it receives the '_previousState', which holds | |
// the last binary snapshot other users already have, therefore the patches | |
// that follow will be the same for all clients. | |
protected _previousState: any; | |
protected _previousStateEncoded: any; | |
constructor () { | |
super(); | |
if (arguments.length > 0) { | |
console.warn("DEPRECATION WARNING: use 'onInit(options)' instead of 'constructor(options)' to initialize the room."); | |
} | |
this.setPatchRate(this.patchRate); | |
} | |
public getNumClients(clients) { | |
return clients.length; | |
} | |
public mapClients(clients, fun) { | |
for (let i = 0; i < clients.length; i++) { | |
fun(clients[i]) | |
} | |
} | |
protected addClient(client: Client) { | |
this.clients.push( client ); | |
} | |
protected removeClient(client: Client): boolean { | |
return spliceOne(this.clients, this.clients.indexOf(client)) | |
} | |
public setState (newState) { | |
this.clock.start(); | |
this._previousState = newState; | |
// ensure state is populated for `sendState()` method. | |
this._previousStateEncoded = msgpack.encode( this._previousState ); | |
this.state = newState; | |
if ( this.timeline ) { | |
this.timeline.takeSnapshot( this.state ); | |
} | |
} | |
protected sendState (client: Client): void { | |
send(client, [ | |
Protocol.ROOM_STATE, | |
this.roomId, | |
this._previousStateEncoded, | |
this.clock.currentTime, | |
this.clock.elapsedTime, | |
]); | |
} | |
protected broadcast (data: any): boolean { | |
// 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.ROOM_DATA, this.roomId, data]); | |
} | |
let numClients = this.clients.length; | |
while (numClients--) { | |
this.clients[ numClients ].send(data, { binary: true }, logError.bind(this) ); | |
} | |
return true; | |
} | |
protected broadcastPatch (): boolean { | |
if ( !this._previousState ) { | |
debugPatch('trying to broadcast null state. you should call #setState on constructor or during user connection.'); | |
return false; | |
} | |
let currentState = this.state; | |
let currentStateEncoded = msgpack.encode( currentState ); | |
// skip if state has not changed. | |
if ( currentStateEncoded.equals( this._previousStateEncoded ) ) { | |
return false; | |
} | |
let patches = fossilDelta.create( this._previousStateEncoded, currentStateEncoded ); | |
// take a snapshot of the current state | |
if (this.timeline) { | |
this.timeline.takeSnapshot( this.state, this.clock.elapsedTime ); | |
} | |
// | |
// debugging | |
// | |
if (debugPatch.enabled) { | |
debugPatch(`"%s" (roomId: "%s") is sending %d bytes:`, this.roomName, this.roomId, patches.length); | |
} | |
if (debugPatchData.enabled) { | |
debugPatchData(jsonPatch.compare(this._previousState, currentState)); | |
} | |
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.ROOM_STATE_PATCH, this.roomId, patches ]) ); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment