import * as I from 'immutable'; import immutablediff from 'immutablediff'; import isPlainObject from 'is-plain-object'; import {compose} from '@avocado/core'; import {Trait} from '@avocado/entity'; import {Packer, Synchronizer} from '@avocado/state'; const decorate = compose( ); export class Informed extends decorate(Trait) { initialize() { this._packer = new Packer(); this._socket = undefined; this._state = I.Map(); } destroy() { if (this._socket) { delete this._socket.entity; delete this._socket; } } reduceState(state) { // Set client's self entity. state = state.set('selfEntity', this.entity.instanceUuid); // Reduce entity list to visible. const room = this.entity.room; const camera = this.entity.camera; for (const {index, layer} of room.layers) { const visibleEntities = layer.visibleEntities( camera.rectangle ); let reducedEntityList = I.Map(); for (const entity of visibleEntities) { reducedEntityList = reducedEntityList.set( entity.instanceUuid, entity.state, ); } const entityListPath = ['room', 'layers', index, 'entityList']; state = state.setIn(entityListPath, reducedEntityList); } return state; } get socket() { return this._socket; } set socket(socket) { socket.entity = this.entity; this._socket = socket; } methods() { return { inform: (state) => { // Reduce state. const reducedState = this.reduceState(state); // Take a pure JS diff. const steps = immutablediff(this._state, reducedState).toJS(); this._state = reducedState; if (0 === steps.length) { return; } // Emit! const keys = this._packer.computeNewKeys(steps); if (0 !== keys[0].length) { this._socket.send({ type: 'keys', payload: keys, }); } const packed = this._packer.pack(steps); this._socket.send(packed); }, }; } }