refactor: state reducer

This commit is contained in:
cha0s 2019-04-07 15:59:55 -04:00
parent 2f3988cd55
commit 6b4a12e924
3 changed files with 38 additions and 88 deletions

View File

@ -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"
}

View File

@ -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);
}
}
}

View File

@ -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,
});
},
};