refactor: state update

This commit is contained in:
cha0s 2019-04-05 23:14:29 -04:00
parent 8f3a8d9513
commit 6eb7d6bad9
8 changed files with 255 additions and 101 deletions

View File

@ -4,6 +4,7 @@ import mapValues from 'lodash.mapvalues';
import {arrayUnique, compose} from '@avocado/core'; import {arrayUnique, compose} from '@avocado/core';
import {QuadTree, Rectangle, Vector} from '@avocado/math'; import {QuadTree, Rectangle, Vector} from '@avocado/math';
import {EventEmitter} from '@avocado/mixins'; import {EventEmitter} from '@avocado/mixins';
import {StateSynchronizer} from '@avocado/state';
import {create} from './index'; import {create} from './index';
@ -56,29 +57,63 @@ export class EntityList extends decorate(class {}) {
} }
patchState(patch) { patchState(patch) {
for (const uuid in patch) { for (const step of patch) {
const {op, path, value} = step;
if ('/' === path) {
for (const uuid in value) {
const localUuid = this.uuidMap_PRIVATE[uuid]; const localUuid = this.uuidMap_PRIVATE[uuid];
const entity = this.entities_PRIVATE[localUuid]; const entity = this.entities_PRIVATE[localUuid];
if (entity) { if (entity) {
if (false === patch[uuid]) { if (false === value[uuid]) {
// Entity removed. // Entity removed.
this.removeEntity(entity); this.removeEntity(entity);
} }
else { else {
entity.patchState(patch[uuid]); entity.patchState([
{
op: 'replace',
path: '/',
value: value[uuid],
}
]);
this.state_PRIVATE = this.state_PRIVATE.set(localUuid, entity.state); this.state_PRIVATE = this.state_PRIVATE.set(localUuid, entity.state);
} }
} }
else { else {
// New entity. Create with patch as traits. // New entity. Create with patch as traits.
const newEntity = create().fromJSON({ const newEntity = create().fromJSON({
traits: patch[uuid], traits: value[uuid],
}); });
this.uuidMap_PRIVATE[uuid] = newEntity.instanceUuid; this.uuidMap_PRIVATE[uuid] = newEntity.instanceUuid;
this.addEntity(newEntity); this.addEntity(newEntity);
} }
} }
} }
else {
const [uuid, substep] = StateSynchronizer.forwardStep(step);
const localUuid = this.uuidMap_PRIVATE[uuid];
const entity = this.entities_PRIVATE[localUuid];
if (entity) {
if (false === substep.value[uuid]) {
// Entity removed.
this.removeEntity(entity);
}
else {
entity.patchState([substep]);
this.state_PRIVATE = this.state_PRIVATE.set(localUuid, entity.state);
}
}
else {
// New entity. Create with patch as traits.
const newEntity = create().fromJSON({
traits: substep.value[uuid],
});
this.uuidMap_PRIVATE[uuid] = newEntity.instanceUuid;
this.addEntity(newEntity);
}
}
}
}
get quadTree() { get quadTree() {
return this._quadTree; return this._quadTree;

View File

@ -3,6 +3,7 @@ import * as I from 'immutable';
import {Vector} from '@avocado/math'; import {Vector} from '@avocado/math';
import {Property} from '@avocado/mixins'; import {Property} from '@avocado/mixins';
import {Resource} from '@avocado/resource'; import {Resource} from '@avocado/resource';
import {StateSynchronizer} from '@avocado/state';
export class Trait { export class Trait {
@ -38,12 +39,15 @@ export class Trait {
} }
patchState(patch) { patchState(patch) {
if (!patch.state) { for (const step of patch) {
return; const {op, path, value} = step;
if ('/' === path) {
if (!value.state) {
continue;
} }
const undefinedProperties = {}; const undefinedProperties = {};
for (const key in patch.state) { for (const key in value.state) {
const value = patch.state[key]; const value = value.state[key];
if (key in this.entity) { if (key in this.entity) {
this.entity[key] = value; this.entity[key] = value;
} }
@ -53,6 +57,22 @@ export class Trait {
} }
this.state = this.state.merge(undefinedProperties); this.state = this.state.merge(undefinedProperties);
} }
else {
const [stepKey, substep] = StateSynchronizer.forwardStep(step);
if ('state' === stepKey) {
const key = substep.path.substr(1);
const undefinedProperties = {};
if (key in this.entity) {
this.entity[key] = substep.value;
}
else {
undefinedProperties[key] = substep.value;
}
this.state = this.state.merge(undefinedProperties);
}
}
}
}
toJSON() { toJSON() {
return { return {

View File

@ -3,6 +3,7 @@ import without from 'lodash.without';
import * as I from 'immutable'; import * as I from 'immutable';
import {Resource} from '@avocado/resource'; import {Resource} from '@avocado/resource';
import {StateSynchronizer} from '@avocado/state';
import {hasTrait, lookupTrait, registerTrait} from './trait-registry'; import {hasTrait, lookupTrait, registerTrait} from './trait-registry';
@ -184,7 +185,10 @@ export class Traits {
} }
patchState(patch) { patchState(patch) {
for (const type in patch) { for (const step of patch) {
const {op, path, value} = step;
if ('/' === path) {
for (const type in value) {
let instance = this.traits_PRIVATE[type]; let instance = this.traits_PRIVATE[type];
// New trait requested? // New trait requested?
if (!this.traits_PRIVATE[type]) { if (!this.traits_PRIVATE[type]) {
@ -192,16 +196,42 @@ export class Traits {
if (!hasTrait(type)) { if (!hasTrait(type)) {
continue; continue;
} }
this.addTrait(type, patch[type]); this.addTrait(type, value[type]);
instance = this.traits_PRIVATE[type]; instance = this.traits_PRIVATE[type];
} }
else { else {
// Accept state. // Accept state.
instance.patchState(patch[type]); instance.patchState([
{
op: 'replace',
path: '/',
value: patch[type],
}
]);
} }
this._setInstanceState(type, instance); this._setInstanceState(type, instance);
} }
} }
else {
const [type, substep] = StateSynchronizer.forwardStep(step);
let instance = this.traits_PRIVATE[type];
// New trait requested?
if (!this.traits_PRIVATE[type]) {
// Doesn't exist?
if (!hasTrait(type)) {
continue;
}
this.addTrait(type, substep.value[type]);
instance = this.traits_PRIVATE[type];
}
else {
// Accept state.
instance.patchState([substep]);
}
this._setInstanceState(type, instance);
}
}
}
removeAllTraits() { removeAllTraits() {
const types = this.allTypes(); const types = this.allTypes();

View File

@ -12,33 +12,19 @@ export class StateSynchronizer {
diff(previousState) { diff(previousState) {
// Take a pure JS diff. // Take a pure JS diff.
const steps = immutablediff(previousState, this._state).toJS(); const steps = immutablediff(previousState, this._state).toJS();
const updateSteps = steps.filter(StateSynchronizer.isStepUpdate); return steps;
return StateSynchronizer.hydratePathValues(updateSteps);
} }
static hydratePathValues(pathValues) { static forwardStep(step) {
let accumulated = {}; const {path, op, value} = step;
for (const {path, value} of pathValues) {
if ('/' === path) {
accumulated = value;
}
else {
const parts = path.split('/'); const parts = path.split('/');
parts.shift(); const [key] = parts.splice(1, 1);
let walk = accumulated; const subpath = parts.join('/');
for (let i = 0; i < parts.length; ++i) { return [key, {
const part = parts[i]; op,
walk[part] = walk[part] || {}; path: subpath ? subpath : '/',
if (i === parts.length - 1) { value,
walk[part] = value; }];
}
else {
walk = walk[part];
}
}
}
}
return accumulated;
} }
static isStepUpdate(step) { static isStepUpdate(step) {
@ -46,12 +32,26 @@ export class StateSynchronizer {
} }
patchState(patch) { patchState(patch) {
for (const key in patch) { const stepMap = {};
for (const {op, path, value} of patch) {
const parts = path.split('/');
const [key] = parts.splice(1, 1);
if (!stepMap[key]) {
stepMap[key] = [];
}
const subpath = parts.join('/');
stepMap[key].push({
op,
path: subpath ? subpath : '/',
value,
});
}
for (const key in stepMap) {
const stateful = this._statefuls[key]; const stateful = this._statefuls[key];
if (!stateful) { if (!stateful) {
continue; continue;
} }
stateful.patchState(patch[key]); stateful.patchState(stepMap[key]);
} }
} }

View File

@ -3,6 +3,7 @@ import * as I from 'immutable';
import {compose} from '@avocado/core'; import {compose} from '@avocado/core';
import {create as createEntity, EntityList} from '@avocado/entity'; import {create as createEntity, EntityList} from '@avocado/entity';
import {EventEmitter, Property} from '@avocado/mixins'; import {EventEmitter, Property} from '@avocado/mixins';
import {StateSynchronizer} from '@avocado/state';
import {Tiles} from './tiles'; import {Tiles} from './tiles';
@ -81,14 +82,40 @@ export class Layer extends decorate(class {}) {
} }
patchState(patch) { patchState(patch) {
if (patch.entityList) { for (const step of patch) {
this.entityList.patchState(patch.entityList); const {op, path, value} = step;
if ('/' === path) {
if (value.entityList) {
this.entityList.patchState([
{
op: 'replace',
path: '/',
value: value.entityList
}
]);
}
if (value.tilesetUri) {
this.tilesetUri = value.tilesetUri;
}
if (value.tiles) {
this.tiles.patchState([
{
op: 'replace',
path: '/',
value: value.tiles
}
]);
}
}
else {
const [key, substep] = StateSynchronizer.forwardStep(step);
if ('entityList' === key) {
this.entityList.patchState([substep]);
}
if ('tiles' === key) {
this.tiles.patchState([substep]);
} }
if (patch.tilesetUri) {
this.tilesetUri = patch.tilesetUri;
} }
if (patch.tiles) {
this.tiles.patchState(patch.tiles);
} }
} }

View File

@ -2,6 +2,7 @@ import * as I from 'immutable';
import {compose} from '@avocado/core'; import {compose} from '@avocado/core';
import {EventEmitter} from '@avocado/mixins'; import {EventEmitter} from '@avocado/mixins';
import {StateSynchronizer} from '@avocado/state';
import {Layer} from './layer'; import {Layer} from './layer';
@ -84,12 +85,31 @@ export class Layers extends decorate(class {}) {
} }
patchState(patch) { patchState(patch) {
for (const index in patch) { for (const step of patch) {
const {op, path, value} = step;
if ('/' === path) {
for (const index in value) {
const layer = this.layers[index] ? this.layers[index] : new Layer(); const layer = this.layers[index] ? this.layers[index] : new Layer();
if (!this.layers[index]) { if (!this.layers[index]) {
this.addLayer(index, layer); this.addLayer(index, layer);
} }
layer.patchState(patch[index]); layer.patchState([
{
op: 'replace',
path: '/',
value: value[index],
}
]);
}
}
else {
const [index, substep] = StateSynchronizer.forwardStep(step);
const layer = this.layers[index] ? this.layers[index] : new Layer();
if (!this.layers[index]) {
this.addLayer(index, layer);
}
layer.patchState([substep]);
}
} }
} }

View File

@ -4,6 +4,7 @@ import {compose} from '@avocado/core';
import {Vector} from '@avocado/math'; import {Vector} from '@avocado/math';
import {EventEmitter, Property} from '@avocado/mixins'; import {EventEmitter, Property} from '@avocado/mixins';
import {RectangleShape} from '@avocado/physics'; import {RectangleShape} from '@avocado/physics';
import {StateSynchronizer} from '@avocado/state';
import {Layers} from './layers'; import {Layers} from './layers';
@ -96,14 +97,31 @@ export class Room extends decorate(class {}) {
} }
patchState(patch) { patchState(patch) {
if (patch.width) { for (const step of patch) {
this.width = patch.width; const {op, path, value} = step;
if ('/' === path) {
if (value.width) {
this.width = value.width;
}
if (value.height) {
this.height = value.height;
}
if (value.layers) {
this.layers.patchState([
{
op: 'replace',
path: '/',
value: value.layers,
}
])
}
}
else {
const [key, substep] = StateSynchronizer.forwardStep(step);
if ('layers' === key) {
this.layers.patchState([substep]);
} }
if (patch.height) {
this.height = patch.height;
} }
if (patch.layers) {
this.layers.patchState(patch.layers);
} }
} }

View File

@ -20,23 +20,27 @@ export class Tiles extends decorate(class {}) {
} }
patchState(patch) { patchState(patch) {
if (patch.width) { for (const {op, path, value} of patch) {
this.width = patch.width; if ('/' === path) {
if (value.width) {
this.width = value.width;
} }
if (patch.height) { if (value.height) {
this.height = patch.height; this.height = value.height;
} }
if (patch.data) { if (value.data) {
const oldData = this.data; const oldData = this.data;
for (const i in patch.data) { for (const i in value.data) {
const index = parseInt(i); const index = parseInt(i);
this.data = this.data.set(index, patch.data[i]); this.data = this.data.set(index, value.data[i]);
} }
if (oldData !== this.data) { if (oldData !== this.data) {
this.emit('dataChanged'); this.emit('dataChanged');
} }
} }
} }
}
}
fromJSON(json) { fromJSON(json) {
if (json.size) { if (json.size) {