import * as I from 'immutable'; import isPlainObject from 'is-plain-object'; import {compose} from '@avocado/core'; import {Trait} from '@avocado/entity'; const decorate = compose( ); export class Informed extends decorate(Trait) { initialize() { this.entityReducer = this.createIndexedReducer(); this._informState = I.Map(); this._sentSelf = false; this._socket = undefined; this.lastNearby = I.Set(); } destroy() { if (this._socket) { delete this._socket.entity; delete this._socket; } } collapseStateDiff(diff) { for (const index in diff) { const value = diff[index]; if (isPlainObject(value)) { if (!this.collapseStateDiff(value)) { delete diff[index]; } } } if (0 === Object.keys(diff).length) { return null; } return diff; } createIndexedReducer() { let previous = {}; return (diff, indexed) => { const reducedDiff = {}; const current = {}; for (const index in indexed) { const item = indexed[index]; current[index] = item; // Diff. if (previous[index]) { if (diff[index]) { reducedDiff[index] = diff[index]; } } // Added. else { reducedDiff[index] = item.state.toJS(); } } // Removed. for (const index in previous) { if (!current[index]) { reducedDiff[index] = false; } } previous = current; return reducedDiff; }; } reduceStateDiff(diff) { if (!diff || !diff.room || !diff.room.layers) { return diff } const room = this.entity.room; const camera = this.entity.camera; for (const index in diff.room.layers) { // Index entities. const layer = room.layer(index); const visibleEntities = layer.visibleEntities( camera.rectangle ); const indexedEntities = {}; for (const entity of visibleEntities) { indexedEntities[entity.instanceUuid] = entity; } // Reduce entities. diff.room.layers[index].entityList = this.entityReducer( diff.room.layers[index].entityList || {}, indexedEntities, ); } return this.collapseStateDiff(diff); } get socket() { return this._socket; } set socket(socket) { socket.entity = this.entity; this._socket = socket; } methods() { return { inform: (stateSynchronizer) => { // Take diff. let diff = stateSynchronizer.diff(this._informState); diff = this.reduceStateDiff(diff); diff = diff ? diff : {}; this._informState = stateSynchronizer.state; // Let the client know who they are. if (!this._sentSelf) { diff.selfEntity = this.entity.instanceUuid; this._sentSelf = true; } // Remove dead updates. if (0 === Object.keys(diff).length) { return; } // Emit! this._socket.send({type: 'state-update', payload: diff}); }, }; } }