diff --git a/package.json b/package.json index 1bfca3c..b62ab46 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@avocado/timing": "1.x", "@avocado/topdown": "^1.0.0", "glob": "^7.1.3", + "immutablediff": "0.4.4", "is-plain-object": "2.0.4", "source-map-support": "^0.5.11" } diff --git a/server/game.js b/server/game.js index 6d683fc..1e7f33b 100644 --- a/server/game.js +++ b/server/game.js @@ -71,10 +71,10 @@ function createMainLoop() { lastTime = now; room.tick(elapsed); worldTime.tick(elapsed); - stateSynchronizer.tick(); - // All informed entities get their own slice. + stateSynchronizer.tick(elapsed); + // Inform entities of the new state. for (const entity of informables) { - entity.inform(stateSynchronizer); + entity.inform(stateSynchronizer.state); } } } diff --git a/traits/informed.js b/traits/informed.js index 9c6e000..2a4e26d 100644 --- a/traits/informed.js +++ b/traits/informed.js @@ -1,9 +1,10 @@ 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} from '@avocado/state'; +import {Packer, StateSynchronizer} from '@avocado/state'; const decorate = compose( ); @@ -11,11 +12,9 @@ const decorate = compose( export class Informed extends decorate(Trait) { initialize() { - this.entityReducer = this.createIndexedReducer(); - this._informState = I.Map(); this._packer = new Packer(); - this._sentSelf = false; this._socket = undefined; + this._state = I.Map(); } destroy() { @@ -25,73 +24,27 @@ export class Informed extends decorate(Trait) { } } - 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) { - 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, + 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 this.collapseStateDiff(diff); + return state; } get socket() { @@ -106,25 +59,21 @@ export class Informed extends decorate(Trait) { methods() { return { - inform: (stateSynchronizer) => { - // Take diff. - let diff = stateSynchronizer.diff(this._informState); - if (!this._sentSelf) { - diff.unshift({ - op: 'add', - path: '/selfEntity', - value: this.entity.instanceUuid, - }); - this._sentSelf = true; + 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; } - this._informState = stateSynchronizer.state; // Emit! - if (diff) { - this._socket.send({ - type: 'state-update', - payload: this._packer.pack(diff), - }); - } + const packed = this._packer.pack(steps); + this._socket.send({ + type: 'state-update', + payload: packed, + }); }, };