refactor: state update
This commit is contained in:
parent
8f3a8d9513
commit
6eb7d6bad9
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user