refactor: immutablen't
This commit is contained in:
parent
2ccd527884
commit
3631a7f669
11
TODO.md
11
TODO.md
|
@ -20,13 +20,20 @@
|
|||
- ❌ entityList.fromJSON()
|
||||
- ❌ Socket WebWorker can't connect in Firefox
|
||||
- ✔ Don't run emitter destruction tickers on server
|
||||
- ❌ Investigate unrolling equalsClose
|
||||
- ✔ Investigate unrolling equalsClose
|
||||
- ✔ Bitshifts for on_positionChanged x/y boxing
|
||||
- ✔ Memoize Object.getOwnPropertyNames results per trait constructor
|
||||
- ✔ EE optimizations (lookupEmitListeners)
|
||||
- ✔ Core.fastApply, search for /\(.../
|
||||
- ✔ Core.fastApply, search for /\(\.\.\./
|
||||
- ✔ Rename visibleBoundingBox(es)? to visibleAabb(s)?
|
||||
- ❌ Property.fastAccess to skip getter, this.entity.currentAnimation
|
||||
- ✔ Trait::isDirty should be flat
|
||||
- ✔ Trait params fromJS is super slow
|
||||
- ✔ Entity::is is slow?
|
||||
- ❌ Manual state sync
|
||||
- ✔ Synchronized provides packet updates
|
||||
- ✔ Phase out EntityPacketSynchronizer
|
||||
- ❌ Implement ALL trait state update packets
|
||||
- ❌ Implement Entity remember packets
|
||||
- ✔ Implement Entity remove packets
|
||||
- ❌ Save state/param extensions separately on create instead of only merging
|
||||
|
|
|
@ -35,7 +35,7 @@ export class Behaved extends decorate(Trait) {
|
|||
const routinesJSON = this.params.routines;
|
||||
this._routines = (new Routines()).fromJSON(routinesJSON);
|
||||
this._routines.context = this._context;
|
||||
this.updateCurrentRoutine(this.state.get('currentRoutine'));
|
||||
this.updateCurrentRoutine(this.state.currentRoutine);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import {Packet} from '@avocado/net';
|
||||
|
||||
export class EntityPacket extends Packet {
|
||||
|
||||
static get schema() {
|
||||
return {
|
||||
...super.schema,
|
||||
data: [{
|
||||
uuid: 'string',
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
bundleWith(other) {
|
||||
this.data.push(other.data[0]);
|
||||
}
|
||||
|
||||
forEachData(fn) {
|
||||
for (let i = 0; i < this.data.length; i++) {
|
||||
const data = this.data[i];
|
||||
fn(data);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -6,6 +6,7 @@ import {compose, EventEmitter, fastApply} from '@avocado/core';
|
|||
import {Resource} from '@avocado/resource';
|
||||
import {Synchronized} from '@avocado/state';
|
||||
|
||||
import {EntityCreatePacket} from './packets/entity-create.packet';
|
||||
import {hasTrait, lookupTrait} from './trait/registry';
|
||||
|
||||
const debug = D('@avocado:entity:traits');
|
||||
|
@ -86,7 +87,6 @@ export class Entity extends decorate(Resource) {
|
|||
super();
|
||||
this._hooks = {};
|
||||
this._traits = {};
|
||||
this._traitsTypesDirty = [];
|
||||
this._traitsFlat = [];
|
||||
this._traitTickers = [];
|
||||
this._traitRenderTickers = [];
|
||||
|
@ -158,11 +158,6 @@ export class Entity extends decorate(Resource) {
|
|||
type: Trait.type(),
|
||||
});
|
||||
}
|
||||
// Add state.
|
||||
this.state = this.state.set(type, I.Map({
|
||||
params: instance.params,
|
||||
state: instance.state,
|
||||
}))
|
||||
// Track trait.
|
||||
this._traits[type] = instance;
|
||||
this._traitsFlat.push(instance);
|
||||
|
@ -201,8 +196,7 @@ export class Entity extends decorate(Resource) {
|
|||
hydrate() {
|
||||
const promises = [];
|
||||
for (let i = 0; i < this._traitsFlat.length; i++) {
|
||||
const instance = this._traitsFlat[i];
|
||||
promises.push(instance.hydrate());
|
||||
promises.push(this._traitsFlat[i].hydrate());
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
@ -231,26 +225,21 @@ export class Entity extends decorate(Resource) {
|
|||
return type in this._traits;
|
||||
}
|
||||
|
||||
patchStateStep(type, step) {
|
||||
let instance = this._traits[type];
|
||||
// New trait requested?
|
||||
if (!this._traits[type]) {
|
||||
// Doesn't exist?
|
||||
if (!hasTrait(type)) {
|
||||
return;
|
||||
}
|
||||
if ('params' in step.value) {
|
||||
step.value.params = step.value.params.toJS();
|
||||
}
|
||||
this.addTrait(type, step.value);
|
||||
instance = this._traits[type];
|
||||
this.state = this.state.setIn([type, 'params'], instance.params);
|
||||
packetsForUpdate(force = false) {
|
||||
const packets = [];
|
||||
if (force) {
|
||||
const packet = new EntityCreatePacket(this.toJSON(), this);
|
||||
packets.push(packet);
|
||||
}
|
||||
else {
|
||||
// Accept state.
|
||||
instance.patchState([step]);
|
||||
for (let i = 0; i < this._traitsFlat.length; i++) {
|
||||
const traitPackets = this._traitsFlat[i].packetsForUpdate();
|
||||
for (let j = 0; j < traitPackets.length; j++) {
|
||||
packets.push(traitPackets[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.state = this.state.setIn([type, 'state'], instance.state);
|
||||
return packets;
|
||||
}
|
||||
|
||||
renderTick(elapsed) {
|
||||
|
@ -296,8 +285,6 @@ export class Entity extends decorate(Resource) {
|
|||
this.off(eventName, listeners[eventName]);
|
||||
}
|
||||
instance._memoizedListeners = {};
|
||||
// Remove state.
|
||||
this.state = this.state.delete(type);
|
||||
// Remove instance.
|
||||
delete this._traits[type];
|
||||
this._traitsFlat.splice(this._traitsFlat.indexOf(instance), 1);
|
||||
|
@ -325,34 +312,10 @@ export class Entity extends decorate(Resource) {
|
|||
types.forEach((type) => this.removeTrait(type));
|
||||
}
|
||||
|
||||
setTraitDirty(type) {
|
||||
this._traitsTypesDirty.push(type);
|
||||
if (this.is('listed')) {
|
||||
this.list && this.list.markEntityDirty(this);
|
||||
}
|
||||
}
|
||||
|
||||
tick(elapsed) {
|
||||
for (let i = 0; i < this._traitTickers.length; i++) {
|
||||
this._traitTickers[i](elapsed);
|
||||
}
|
||||
if (AVOCADO_SERVER) {
|
||||
this.tickMutateState();
|
||||
}
|
||||
}
|
||||
|
||||
tickMutateState() {
|
||||
if (0 === this._traitsTypesDirty.length) {
|
||||
return;
|
||||
}
|
||||
this.state = this.state.withMutations((state) => {
|
||||
for (let i = 0; i < this._traitsTypesDirty.length; i++) {
|
||||
const type = this._traitsTypesDirty[i];
|
||||
const instance = this._traits[type];
|
||||
state.setIn([type, 'state'], instance.state);
|
||||
}
|
||||
});
|
||||
this._traitsTypesDirty = [];
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
|
@ -368,12 +331,12 @@ export class Entity extends decorate(Resource) {
|
|||
|
||||
}
|
||||
|
||||
export {EntityPacket} from './entity.packet';
|
||||
export {EntityCreatePacket} from './packets/entity-create.packet';
|
||||
export {EntityRemovePacket} from './packets/entity-remove.packet';
|
||||
export {EntityPacket} from './packets/entity.packet';
|
||||
|
||||
export {EntityList, EntityListView} from './list';
|
||||
|
||||
export {EntityPacketSynchronizer} from './packet-synchronizer';
|
||||
|
||||
export {
|
||||
hasTrait,
|
||||
lookupTrait,
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import * as I from 'immutable';
|
||||
import mapValues from 'lodash.mapvalues';
|
||||
|
||||
import {compose, EventEmitter} from '@avocado/core';
|
||||
import {QuadTree, Rectangle, Vector} from '@avocado/math';
|
||||
import {Synchronized} from '@avocado/state';
|
||||
|
||||
import {EntityCreatePacket} from '../packets/entity-create.packet';
|
||||
import {EntityRemovePacket} from '../packets/entity-remove.packet';
|
||||
import {Entity} from '../index';
|
||||
|
||||
const decorate = compose(
|
||||
|
@ -17,8 +16,9 @@ export class EntityList extends decorate(class {}) {
|
|||
constructor() {
|
||||
super();
|
||||
this._afterDestructionTickers = [];
|
||||
this._dirtyEntities = [];
|
||||
this._entities = {};
|
||||
this._entitiesJustAdded = [];
|
||||
this._entitiesJustRemoved = [];
|
||||
this._entityTickers = []
|
||||
this._flatEntities = [];
|
||||
this._quadTree = new QuadTree();
|
||||
|
@ -31,12 +31,22 @@ export class EntityList extends decorate(class {}) {
|
|||
}
|
||||
}
|
||||
|
||||
acceptPacket(packet) {
|
||||
if (packet instanceof EntityCreatePacket) {
|
||||
const entity = new Entity(packet.data);
|
||||
entity.instanceUuid = packet.data.uuid;
|
||||
this.addEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
addEntity(entity) {
|
||||
const uuid = entity.instanceUuid;
|
||||
this._entities[uuid] = entity;
|
||||
this._flatEntities.push(entity);
|
||||
this._entityTickers.push(entity.tick);
|
||||
this.state = this.state.set(uuid, entity.state);
|
||||
if (AVOCADO_SERVER) {
|
||||
this._entitiesJustAdded.push(entity);
|
||||
}
|
||||
entity.setIntoList(this);
|
||||
entity.once('destroy', () => {
|
||||
this.removeEntity(entity);
|
||||
|
@ -52,8 +62,8 @@ export class EntityList extends decorate(class {}) {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
for (const entity of this) {
|
||||
entity.destroy();
|
||||
for (let i = 0; i < this._flatEntities.length; i++) {
|
||||
this._flatEntities[i].destroy();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,40 +73,27 @@ export class EntityList extends decorate(class {}) {
|
|||
}
|
||||
}
|
||||
|
||||
markEntityDirty(entity) {
|
||||
if (AVOCADO_CLIENT) {
|
||||
return;
|
||||
}
|
||||
if (-1 === this._dirtyEntities.indexOf(entity)) {
|
||||
this._dirtyEntities.push(entity);
|
||||
}
|
||||
}
|
||||
|
||||
patchStateStep(uuid, step) {
|
||||
const entity = this._entities[uuid];
|
||||
if ('/' === step.path) {
|
||||
switch (step.op) {
|
||||
case 'add':
|
||||
// New entity. Create with patch as traits.
|
||||
const newEntity = new Entity({
|
||||
traits: step.value,
|
||||
});
|
||||
newEntity.instanceUuid = uuid;
|
||||
this.addEntity(newEntity);
|
||||
break;
|
||||
case 'remove':
|
||||
// Maybe already destroyed on the client?
|
||||
if (entity && entity.is('existent')) {
|
||||
entity.destroy();
|
||||
}
|
||||
break;
|
||||
packetsForUpdate(force = false) {
|
||||
const packets = [];
|
||||
if (!force) {
|
||||
for (let i = 0; i < this._entitiesJustAdded.length; i++) {
|
||||
const entity = this._entitiesJustAdded[i];
|
||||
packets.push(new EntityCreatePacket(entity.toJSON(), entity));
|
||||
}
|
||||
return;
|
||||
this._entitiesJustAdded = [];
|
||||
for (let i = 0; i < this._entitiesJustRemoved.length; i++) {
|
||||
const entity = this._entitiesJustRemoved[i];
|
||||
packets.push(new EntityRemovePacket({}, entity));
|
||||
}
|
||||
this._entitiesJustRemoved = [];
|
||||
}
|
||||
if ('replace' === step.op && entity) {
|
||||
// Exists; patch.
|
||||
entity.patchState([step]);
|
||||
for (let i = 0; i < this._flatEntities.length; i++) {
|
||||
const entityPackets = this._flatEntities[i].packetsForUpdate(force);
|
||||
for (let j = 0; j < entityPackets.length; j++) {
|
||||
packets.push(entityPackets[j]);
|
||||
}
|
||||
}
|
||||
return packets;
|
||||
}
|
||||
|
||||
get quadTree() {
|
||||
|
@ -108,10 +105,12 @@ export class EntityList extends decorate(class {}) {
|
|||
if (!(uuid in this._entities)) {
|
||||
return;
|
||||
}
|
||||
if (AVOCADO_SERVER) {
|
||||
this._entitiesJustRemoved.push(entity);
|
||||
}
|
||||
delete this._entities[uuid];
|
||||
this._flatEntities.splice(this._flatEntities.indexOf(entity), 1);
|
||||
this._entityTickers.splice(this._entityTickers.indexOf(entity.tick), 1);
|
||||
this.state = this.state.delete(uuid);
|
||||
this.emit('entityRemoved', entity);
|
||||
}
|
||||
|
||||
|
@ -122,10 +121,6 @@ export class EntityList extends decorate(class {}) {
|
|||
}
|
||||
// Run normal tickers.
|
||||
this.tickEntities(elapsed)
|
||||
// Update state.
|
||||
if (AVOCADO_SERVER) {
|
||||
this.tickMutateState();
|
||||
}
|
||||
}
|
||||
|
||||
tickAfterDestructionTickers(elapsed) {
|
||||
|
@ -149,19 +144,6 @@ export class EntityList extends decorate(class {}) {
|
|||
}
|
||||
}
|
||||
|
||||
tickMutateState(state) {
|
||||
if (0 === this._dirtyEntities.length) {
|
||||
return;
|
||||
}
|
||||
this.state = this.state.withMutations((state) => {
|
||||
for (let i = 0; i < this._dirtyEntities.length; i++) {
|
||||
const entity = this._dirtyEntities[i];
|
||||
state.set(entity.$$avocado_property_instanceUuid, entity.state);
|
||||
}
|
||||
});
|
||||
this._dirtyEntities = [];
|
||||
}
|
||||
|
||||
visibleEntities(query) {
|
||||
const entities = [];
|
||||
const entitiesChecked = [];
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
import {EntityPacket} from '@avocado/entity';
|
||||
|
||||
export class EntityPacketSynchronizer {
|
||||
|
||||
constructor() {
|
||||
this.packetsToSynchronize = new Map();
|
||||
this.trackedEntities = new Map();
|
||||
}
|
||||
|
||||
acceptPacket(packet) {
|
||||
if (!(packet instanceof EntityPacket)) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < packet.data.length; i++) {
|
||||
const data = packet.data[i];
|
||||
const packetEntity = this.trackedEntities.get(data.uuid);
|
||||
if (!packetEntity) {
|
||||
continue;
|
||||
}
|
||||
const Packet = packet.constructor;
|
||||
packetEntity.acceptPacket(new Packet([data]));
|
||||
}
|
||||
}
|
||||
|
||||
flushPackets(flusher) {
|
||||
const flushed = new Map();
|
||||
const it = this.packetsToSynchronize.entries();
|
||||
for (let value = it.next(); !value.done; value = it.next()) {
|
||||
const entity = value.value[0];
|
||||
const packets = value.value[1];
|
||||
const mergedPackets = this._mergePackets(packets);
|
||||
flushed.set(entity, mergedPackets);
|
||||
}
|
||||
this.packetsToSynchronize.clear();
|
||||
return flushed;
|
||||
}
|
||||
|
||||
_mergePackets(packets) {
|
||||
const mergedPackets = new Map();
|
||||
for (let i = 0; i < packets.length; i++) {
|
||||
const packet = packets[i];
|
||||
const Packet = packet.constructor;
|
||||
if (!mergedPackets.has(Packet)) {
|
||||
mergedPackets.set(Packet, packet);
|
||||
}
|
||||
else {
|
||||
mergedPackets.get(Packet).mergeWith(packet);
|
||||
}
|
||||
}
|
||||
return mergedPackets;
|
||||
}
|
||||
|
||||
_queuePacketFor(entity, packet) {
|
||||
if (!this.packetsToSynchronize.has(entity)) {
|
||||
this.packetsToSynchronize.set(entity, []);
|
||||
}
|
||||
this.packetsToSynchronize.get(entity).push(packet);
|
||||
}
|
||||
|
||||
trackEntity(entity) {
|
||||
this.trackedEntities.set(entity.instanceUuid, entity);
|
||||
entity.once('destroy', () => {
|
||||
this.trackedEntities.delete(entity.instanceUuid);
|
||||
});
|
||||
this._watchEntityPackets(entity);
|
||||
}
|
||||
|
||||
_watchEntityPackets(entity) {
|
||||
const onSendPacket = (packet) => {
|
||||
this._queuePacketFor(entity, packet);
|
||||
};
|
||||
entity.on('sendPacket', onSendPacket);
|
||||
entity.once('destroy', () => {
|
||||
this.packetsToSynchronize.delete(entity);
|
||||
entity.off('sendPacket', onSendPacket);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
35
packages/entity/packets/entity-create.packet.js
Normal file
35
packages/entity/packets/entity-create.packet.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
import msgpack from 'msgpack-lite';
|
||||
|
||||
import {EntityPacket} from './entity.packet';
|
||||
|
||||
export class EntityCreatePacket extends EntityPacket {
|
||||
|
||||
constructor(data, entity) {
|
||||
if ('undefined' !== typeof entity) {
|
||||
data.uuid = entity.instanceUuid;
|
||||
data.layer = entity.layer.index;
|
||||
}
|
||||
super(data);
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
static pack(packet) {
|
||||
return this.builder.encode({
|
||||
_id: packet.data[0],
|
||||
data: msgpack.encode(packet.data[1]),
|
||||
})
|
||||
}
|
||||
|
||||
static get schema() {
|
||||
return {
|
||||
...super.schema,
|
||||
data: 'buffer',
|
||||
};
|
||||
}
|
||||
|
||||
static unpack(packet) {
|
||||
const {data} = this.builder.decode(packet);
|
||||
return msgpack.decode(data);
|
||||
}
|
||||
|
||||
}
|
0
packages/entity/packets/entity-remember.packet.js
Normal file
0
packages/entity/packets/entity-remember.packet.js
Normal file
3
packages/entity/packets/entity-remove.packet.js
Normal file
3
packages/entity/packets/entity-remove.packet.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import {EntityPacket} from './entity.packet';
|
||||
|
||||
export class EntityRemovePacket extends EntityPacket {}
|
22
packages/entity/packets/entity.packet.js
Normal file
22
packages/entity/packets/entity.packet.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
import {Packet} from '@avocado/net';
|
||||
|
||||
export class EntityPacket extends Packet {
|
||||
|
||||
constructor(data, entity) {
|
||||
if ('undefined' !== typeof entity) {
|
||||
data.uuid = entity.instanceUuid;
|
||||
}
|
||||
super(data);
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
static get schema() {
|
||||
return {
|
||||
...super.schema,
|
||||
data: {
|
||||
uuid: 'string',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
}
|
11
packages/entity/packets/trait-directional.packet.js
Normal file
11
packages/entity/packets/trait-directional.packet.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import {EntityPacket} from './entity.packet';
|
||||
|
||||
export class TraitDirectionalPacket extends EntityPacket {
|
||||
|
||||
static get schema() {
|
||||
const schema = super.schema;
|
||||
schema.data.direction = 'uint8';
|
||||
return schema;
|
||||
}
|
||||
|
||||
}
|
11
packages/entity/packets/trait-positioned.packet.js
Normal file
11
packages/entity/packets/trait-positioned.packet.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import {EntityPacket} from './entity.packet';
|
||||
|
||||
export class TraitPositionedPacket extends EntityPacket {
|
||||
|
||||
static get schema() {
|
||||
const schema = super.schema;
|
||||
schema.data.position = 'uint32';
|
||||
return schema;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
import * as I from 'immutable';
|
||||
import merge from 'lodash.merge';
|
||||
|
||||
import {compose, Property} from '@avocado/core';
|
||||
|
@ -18,7 +17,8 @@ export class Trait extends decorate(class {}) {
|
|||
const ctor = this.constructor;
|
||||
this._memoizedListeners = undefined;
|
||||
this.params = Object.assign({}, ctor.defaultParams(), params);
|
||||
this.state = I.fromJS(ctor.defaultState()).merge(I.fromJS(state));
|
||||
this.state = Object.assign({}, ctor.defaultState(), state);
|
||||
this.previousState = JSON.parse(JSON.stringify(this.state));
|
||||
if (this.tick) {
|
||||
this.tick = this.tick.bind(this);
|
||||
}
|
||||
|
@ -56,24 +56,10 @@ export class Trait extends decorate(class {}) {
|
|||
return {};
|
||||
}
|
||||
|
||||
patchStateStep(key, step) {
|
||||
if ('state' !== key) {
|
||||
return;
|
||||
}
|
||||
const stateKey = step.path.substr(1);
|
||||
const value = this.transformPatchValue(stateKey, step.value);
|
||||
if (stateKey in this.entity) {
|
||||
this.entity[stateKey] = value;
|
||||
}
|
||||
else {
|
||||
this.state = this.state.set(stateKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
params: this.params,
|
||||
state: this.state.toJS(),
|
||||
state: this.state,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -112,15 +98,14 @@ export function StateProperty(key, meta = {}) {
|
|||
this.entity.emit(...args);
|
||||
};
|
||||
meta.initialize = meta.initialize || function() {
|
||||
this[transformedProperty] = this.state.get(key);
|
||||
this[transformedProperty] = this.state[key];
|
||||
}
|
||||
meta.get = meta.get || new Function(`
|
||||
return this.${transformedProperty};
|
||||
`);
|
||||
meta.set = meta.set || new Function('value', `
|
||||
this.entity.setTraitDirty(this.constructor.type());
|
||||
this.${transformedProperty} = value;
|
||||
this.state = this.state.set('${key}', value);
|
||||
this.state['${key}'] = value;
|
||||
`);
|
||||
return Property(key, meta)(Superclass);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import {compose} from '@avocado/core';
|
|||
import {Vector} from '@avocado/math';
|
||||
|
||||
import {StateProperty, Trait} from '../trait';
|
||||
import {TraitDirectionalPacket} from '../packets/trait-directional.packet';
|
||||
|
||||
const decorate = compose(
|
||||
StateProperty('direction', {
|
||||
|
@ -33,6 +34,23 @@ export class Directional extends decorate(Trait) {
|
|||
this.directionCount = this.params.directionCount;
|
||||
}
|
||||
|
||||
acceptPacket(packet) {
|
||||
if (packet instanceof TraitDirectionalPacket) {
|
||||
this.entity.direction = packet.data.direction;
|
||||
}
|
||||
}
|
||||
|
||||
packetsForUpdate() {
|
||||
const packets = [];
|
||||
if (this.state.direction !== this.previousState.direction) {
|
||||
packets.push(new TraitDirectionalPacket({
|
||||
direction: this.state.direction,
|
||||
}, this.entity));
|
||||
this.previousState.direction = this.state.direction;
|
||||
}
|
||||
return packets;
|
||||
}
|
||||
|
||||
listeners() {
|
||||
const listeners = {};
|
||||
if (this.params.trackMovement) {
|
||||
|
|
|
@ -2,6 +2,7 @@ import {compose, EventEmitter} from '@avocado/core';
|
|||
import {Vector} from '@avocado/math';
|
||||
|
||||
import {Trait} from '../trait';
|
||||
import {TraitPositionedPacket} from '../packets/trait-positioned.packet';
|
||||
|
||||
const decorate = compose(
|
||||
EventEmitter,
|
||||
|
@ -30,8 +31,8 @@ export class Positioned extends decorate(Trait) {
|
|||
constructor(entity, params, state) {
|
||||
super(entity, params, state);
|
||||
this.on('_positionChanged', this.on_positionChanged, this);
|
||||
const x = this.state.get('x') >> 2;
|
||||
const y = this.state.get('y') >> 2;
|
||||
const x = this.state.x >> 2;
|
||||
const y = this.state.y >> 2;
|
||||
this._position = [x, y];
|
||||
this.entity.position[0] = x;
|
||||
this.entity.position[1] = y;
|
||||
|
@ -50,16 +51,19 @@ export class Positioned extends decorate(Trait) {
|
|||
}
|
||||
}
|
||||
|
||||
acceptPacket(packet) {
|
||||
if (packet instanceof TraitPositionedPacket) {
|
||||
this.serverX = (packet.data.position & 0xFFFF) >> 2;
|
||||
this.serverY = (packet.data.position >> 16) >> 2;
|
||||
}
|
||||
}
|
||||
|
||||
on_positionChanged(oldPosition, newPosition) {
|
||||
this.entity.position[0] = newPosition[0];
|
||||
this.entity.position[1] = newPosition[1];
|
||||
if (AVOCADO_SERVER) {
|
||||
const x = newPosition[0] << 2;
|
||||
const y = newPosition[1] << 2;
|
||||
this.state = this.state.withMutations((state) => {
|
||||
state.set('x', x).set('y', y);
|
||||
});
|
||||
this.entity.setTraitDirty('positioned');
|
||||
this.state.x = newPosition[0] << 2;
|
||||
this.state.y = newPosition[1] << 2;
|
||||
}
|
||||
this.entity.emit('positionChanged', oldPosition, newPosition);
|
||||
}
|
||||
|
@ -68,36 +72,26 @@ export class Positioned extends decorate(Trait) {
|
|||
this.serverPositionDirty = true;
|
||||
}
|
||||
|
||||
patchStateStep(key, step) {
|
||||
if ('state' !== key) {
|
||||
return;
|
||||
}
|
||||
const stateKey = step.path.substr(1);
|
||||
const value = this.transformPatchValue(stateKey, step.value);
|
||||
switch (stateKey) {
|
||||
case 'x':
|
||||
this.serverX = value;
|
||||
break;
|
||||
case 'y':
|
||||
this.serverY = value;
|
||||
break;
|
||||
default:
|
||||
super.patchStateStep(key, step);
|
||||
break;
|
||||
packetsForUpdate() {
|
||||
const packets = [];
|
||||
if (
|
||||
this.state.x !== this.previousState.x
|
||||
|| this.state.y !== this.previousState.y
|
||||
) {
|
||||
const packed = ((this.state.y) << 16) | (this.state.x << 0);
|
||||
packets.push(new TraitPositionedPacket({
|
||||
position: packed,
|
||||
}, this.entity));
|
||||
this.previousState.x = this.state.x;
|
||||
this.previousState.y = this.state.y;
|
||||
}
|
||||
return packets;
|
||||
}
|
||||
|
||||
set relaxServerPositionConstraintIfNearerThan(nearerThan) {
|
||||
this._relaxServerPositionConstraintIfNearerThan = nearerThan;
|
||||
}
|
||||
|
||||
transformPatchValue(key, value) {
|
||||
if (-1 !== ['x', 'y'].indexOf(key)) {
|
||||
return value / 4;
|
||||
}
|
||||
super.transformPatchValue(key, value);
|
||||
}
|
||||
|
||||
listeners() {
|
||||
return {
|
||||
|
||||
|
|
|
@ -54,14 +54,14 @@ export class Visible extends decorate(Trait) {
|
|||
if (filter) {
|
||||
this._container.setFilter(filter);
|
||||
}
|
||||
this._container.isVisible = this.state.get('isVisible');
|
||||
this._container.isVisible = this.state.isVisible;
|
||||
}
|
||||
this._rawVisibleAabb = [0, 0, 0, 0];
|
||||
this.scheduledBoundingBoxUpdate = true;
|
||||
this.trackPosition = this.params.trackPosition;
|
||||
const scale = this.state.get('visibleScale');
|
||||
this._visibleScale = [scale.get(0), scale.get(1)];
|
||||
this.onZIndexChanged(this.state.get('zIndex'));
|
||||
const scale = this.state.visibleScale;
|
||||
this._visibleScale = [scale[0], scale[1]];
|
||||
this.onZIndexChanged(this.state.zIndex);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
@ -88,7 +88,7 @@ export class Visible extends decorate(Trait) {
|
|||
}
|
||||
|
||||
set rawVisibleScale(scale) {
|
||||
this.entity.visibleScale = I.List(scale);
|
||||
this.entity.visibleScale = scale;
|
||||
}
|
||||
|
||||
synchronizePosition() {
|
||||
|
@ -107,7 +107,7 @@ export class Visible extends decorate(Trait) {
|
|||
}
|
||||
|
||||
get visibleScaleX() {
|
||||
return this.entity.visibleScale.get(0);
|
||||
return this.state.visibleScale[0];
|
||||
}
|
||||
|
||||
set visibleScaleX(x) {
|
||||
|
@ -115,7 +115,7 @@ export class Visible extends decorate(Trait) {
|
|||
}
|
||||
|
||||
get visibleScaleY() {
|
||||
return this.entity.visibleScale.get(1);
|
||||
return this.state.visibleScale[1];
|
||||
}
|
||||
|
||||
set visibleScaleY(y) {
|
||||
|
@ -155,7 +155,7 @@ export class Visible extends decorate(Trait) {
|
|||
|
||||
visibleScaleChanged: () => {
|
||||
const scale = this.entity.visibleScale;
|
||||
this._visibleScale = [scale.get(0), scale.get(1)];
|
||||
this._visibleScale = [scale[0], scale[1]];
|
||||
if (this._container) {
|
||||
this._container.scale = this._visibleScale;
|
||||
}
|
||||
|
|
|
@ -11,105 +11,11 @@ const decorate = compose(
|
|||
export function Synchronized(Superclass) {
|
||||
return class Synchronized extends decorate(Superclass) {
|
||||
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.state = I.Map();
|
||||
this._childrenNeedInitialization = true;
|
||||
this._childrenWithState = []
|
||||
this._childrenWithoutState = [];
|
||||
this._childrenTickers = [];
|
||||
}
|
||||
acceptPacket(packet) {}
|
||||
|
||||
ensureSynchronizedChildren() {
|
||||
if (!this._childrenNeedInitialization) {
|
||||
return;
|
||||
}
|
||||
this._childrenNeedInitialization = false;
|
||||
const synchronizedChildren = this.synchronizedChildren();
|
||||
for (let i = 0; i < synchronizedChildren.length; ++i) {
|
||||
const key = synchronizedChildren[i];
|
||||
if (
|
||||
'undefined' !== typeof this[key]
|
||||
&& 'undefined' !== typeof this[key].tick
|
||||
) {
|
||||
this._childrenTickers.push(this[key].tick.bind(this[key]));
|
||||
}
|
||||
if (
|
||||
'undefined' !== typeof this[key]
|
||||
&& 'undefined' !== typeof this[key].state
|
||||
) {
|
||||
this._childrenWithState.push(key);
|
||||
}
|
||||
else {
|
||||
this._childrenWithoutState.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
patchState(patch) {
|
||||
for (const step of patch) {
|
||||
const {op, path, value} = step;
|
||||
if ('/' === path) {
|
||||
for (const key in value) {
|
||||
this.patchStateStep(key, {
|
||||
op,
|
||||
path,
|
||||
value: value[key],
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
const [key, substep] = nextStep(step);
|
||||
this.patchStateStep(key, {
|
||||
op: substep.op,
|
||||
path: substep.path,
|
||||
value: substep.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
patchStateStep(key, step) {
|
||||
if (!(key in this)) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
'undefined' !== typeof this[key]
|
||||
&& 'undefined' !== typeof this[key].patchState
|
||||
) {
|
||||
this[key].patchState([step]);
|
||||
}
|
||||
else {
|
||||
this[key] = step.value;
|
||||
}
|
||||
}
|
||||
|
||||
synchronizedChildren() {
|
||||
packetsForUpdate(force = false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
tickSynchronized(elapsed) {
|
||||
this.ensureSynchronizedChildren();
|
||||
for (let i = 0; i < this._childrenTickers.length; ++i) {
|
||||
this._childrenTickers[i](elapsed);
|
||||
}
|
||||
if (AVOCADO_SERVER) {
|
||||
this.tickMutateState();
|
||||
}
|
||||
}
|
||||
|
||||
tickMutateState() {
|
||||
this.state = this.state.withMutations((state) => {
|
||||
for (let i = 0; i < this._childrenWithState.length; ++i) {
|
||||
const key = this._childrenWithState[i];
|
||||
state.set(key, this[key].state);
|
||||
}
|
||||
for (let i = 0; i < this._childrenWithoutState.length; ++i) {
|
||||
const key = this._childrenWithoutState[i];
|
||||
state.set(key, this[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,55 +4,25 @@ import {nextStep} from './next-step';
|
|||
|
||||
export class Synchronizer {
|
||||
|
||||
constructor(statefuls) {
|
||||
this._state = I.Map();
|
||||
this._statefuls = statefuls;
|
||||
this.updateState();
|
||||
constructor(children) {
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
patchState(patch) {
|
||||
const stepMap = {};
|
||||
for (const step of patch) {
|
||||
const [key, substep] = nextStep(step)
|
||||
if (!stepMap[key]) {
|
||||
stepMap[key] = [];
|
||||
acceptPacket(packet) {
|
||||
for (let i = 0; i < this.children.length; i++) {
|
||||
this.children[i].acceptPacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
packetsForUpdate(force = false) {
|
||||
const packetsForUpdate = [];
|
||||
for (let i = 0; i < this.children.length; i++) {
|
||||
const childPacketsForUpdate = this.children[i].packetsForUpdate(force);
|
||||
for (let j = 0; j < childPacketsForUpdate.length; j++) {
|
||||
packetsForUpdate.push(childPacketsForUpdate[j]);
|
||||
}
|
||||
stepMap[key].push(substep);
|
||||
}
|
||||
for (const key in stepMap) {
|
||||
const stateful = this._statefuls[key];
|
||||
if (!stateful) {
|
||||
continue;
|
||||
}
|
||||
stateful.patchState(stepMap[key]);
|
||||
}
|
||||
}
|
||||
|
||||
setStateful(key, stateful) {
|
||||
this._statefuls[key] = stateful;
|
||||
}
|
||||
|
||||
get state() {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
tick(elapsed) {
|
||||
for (const key in this._statefuls) {
|
||||
const stateful = this._statefuls[key];
|
||||
stateful.tick(elapsed);
|
||||
}
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
updateState() {
|
||||
if (AVOCADO_SERVER) {
|
||||
this._state = this._state.withMutations((state) => {
|
||||
for (const key in this._statefuls) {
|
||||
const stateful = this._statefuls[key];
|
||||
state.set(key, stateful.state);
|
||||
}
|
||||
});
|
||||
}
|
||||
return packetsForUpdate;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
12
packages/timing/packets/trait-animated.packet.js
Normal file
12
packages/timing/packets/trait-animated.packet.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import {EntityPacket} from '@avocado/entity';
|
||||
|
||||
export class TraitAnimatedPacket extends EntityPacket {
|
||||
|
||||
static get schema() {
|
||||
const schema = super.schema;
|
||||
schema.data.currentAnimation = 'string';
|
||||
schema.data.isAnimating = 'bool';
|
||||
return schema;
|
||||
}
|
||||
|
||||
}
|
|
@ -4,6 +4,7 @@ import {Rectangle, Vector} from '@avocado/math';
|
|||
|
||||
import {Animation} from '../animation';
|
||||
import {AnimationView} from '../animation-view';
|
||||
import {TraitAnimatedPacket} from '../packets/trait-animated.packet';
|
||||
|
||||
const decorate = compose(
|
||||
StateProperty('currentAnimation', {
|
||||
|
@ -40,7 +41,7 @@ export class Animated extends decorate(Trait) {
|
|||
this.animationViews = undefined;
|
||||
this.animationsPromise = undefined;
|
||||
this._cachedAabbs = {};
|
||||
this._currentAnimation = this.state.get('currentAnimation');
|
||||
this._currentAnimation = this.state.currentAnimation;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
@ -61,6 +62,13 @@ export class Animated extends decorate(Trait) {
|
|||
this._cachedAabbs = {};
|
||||
}
|
||||
|
||||
acceptPacket(packet) {
|
||||
if (packet instanceof TraitAnimatedPacket) {
|
||||
this.entity.currentAnimation = packet.data.currentAnimation;
|
||||
this.entity.isAnimating = packet.data.isAnimating;
|
||||
}
|
||||
}
|
||||
|
||||
hideAnimation(key) {
|
||||
if (!this.animationViews) {
|
||||
return;
|
||||
|
@ -142,6 +150,22 @@ export class Animated extends decorate(Trait) {
|
|||
return this._animations[key].offset;
|
||||
}
|
||||
|
||||
packetsForUpdate() {
|
||||
const packets = [];
|
||||
if (
|
||||
this.state.currentAnimation !== this.previousState.currentAnimation
|
||||
|| this.state.isAnimating !== this.previousState.isAnimating
|
||||
) {
|
||||
packets.push(new TraitAnimatedPacket({
|
||||
currentAnimation: this.state.currentAnimation,
|
||||
isAnimating: this.state.isAnimating,
|
||||
}, this.entity));
|
||||
this.previousState.currentAnimation = this.state.currentAnimation;
|
||||
this.previousState.isAnimating = this.state.isAnimating;
|
||||
}
|
||||
return packets;
|
||||
}
|
||||
|
||||
setSpriteScale() {
|
||||
if (!this.animationViews) {
|
||||
return;
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import * as I from 'immutable';
|
||||
|
||||
import {compose, EventEmitter, Property} from '@avocado/core';
|
||||
import {Entity, EntityList} from '@avocado/entity';
|
||||
import {Entity, EntityCreatePacket, EntityList} from '@avocado/entity';
|
||||
import {Vector} from '@avocado/math';
|
||||
import {ShapeList} from '@avocado/physics';
|
||||
import {Synchronized} from '@avocado/state';
|
||||
|
||||
import {Tiles} from './tiles';
|
||||
import {Tileset} from './tileset';
|
||||
import {LayerCreatePacket} from './packets/layer-create.packet';
|
||||
import {TileUpdatePacket} from './packets/tile-update.packet';
|
||||
|
||||
const decorate = compose(
|
||||
EventEmitter,
|
||||
|
@ -25,9 +25,10 @@ const decorate = compose(
|
|||
|
||||
export class Layer extends decorate(class {}) {
|
||||
|
||||
constructor() {
|
||||
constructor(json) {
|
||||
super();
|
||||
this.entityList = new EntityList();
|
||||
this.index = -1;
|
||||
this.tileGeometry = [];
|
||||
this.tiles = new Tiles();
|
||||
// Listeners.
|
||||
|
@ -36,8 +37,19 @@ export class Layer extends decorate(class {}) {
|
|||
this.tiles.on('dataChanged', this.onTileDataChanged, this);
|
||||
this.on('tilesetChanged', this.onTilesetChanged, this);
|
||||
this.on('tilesetUriChanged', this.onTilesetUriChanged, this);
|
||||
this.onTilesetUriChanged();
|
||||
this.on('worldChanged', this.onWorldChanged, this);
|
||||
if ('undefined' !== typeof json) {
|
||||
this.fromJSON(json);
|
||||
}
|
||||
}
|
||||
|
||||
acceptPacket(packet) {
|
||||
if (packet instanceof TileUpdatePacket) {
|
||||
this.tiles.acceptPacket(packet);
|
||||
}
|
||||
if (packet instanceof EntityCreatePacket) {
|
||||
this.entityList.acceptPacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
addEntity(entity) {
|
||||
|
@ -92,10 +104,9 @@ export class Layer extends decorate(class {}) {
|
|||
|
||||
fromJSON(json) {
|
||||
if (json.entities) {
|
||||
json.entities.forEach((entityJSON) => {
|
||||
const entity = new Entity(entityJSON);
|
||||
this.entityList.addEntity(entity);
|
||||
});
|
||||
for (let i = 0; i < json.entities.length; i++) {
|
||||
this.entityList.addEntity(new Entity(json.entities[i]));
|
||||
}
|
||||
}
|
||||
if (json.tiles) {
|
||||
this.tiles.fromJSON(json.tiles)
|
||||
|
@ -156,6 +167,19 @@ export class Layer extends decorate(class {}) {
|
|||
}
|
||||
}
|
||||
|
||||
packetsForUpdate(force = false) {
|
||||
const packets = [];
|
||||
// Create layer during a force.
|
||||
if (force) {
|
||||
packets.push(new LayerCreatePacket(this.toJSON()));
|
||||
}
|
||||
const entityListPackets = this.entityList.packetsForUpdate(force);
|
||||
for (let i = 0; i < entityListPackets.length; i++) {
|
||||
packets.push(entityListPackets[i]);
|
||||
}
|
||||
return packets;
|
||||
}
|
||||
|
||||
removeEntity(entity) {
|
||||
this.entityList.removeEntity(entity);
|
||||
}
|
||||
|
@ -177,7 +201,14 @@ export class Layer extends decorate(class {}) {
|
|||
}
|
||||
|
||||
tick(elapsed) {
|
||||
this.tickSynchronized(elapsed);
|
||||
this.entityList.tick(elapsed);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
tilesetUri: this.tilesetUri,
|
||||
tiles: this.tiles.toJSON(),
|
||||
};
|
||||
}
|
||||
|
||||
visibleEntities(query) {
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import * as I from 'immutable';
|
||||
|
||||
import {arrayUnique, compose, EventEmitter, flatten} from '@avocado/core';
|
||||
import {EntityCreatePacket} from '@avocado/entity';
|
||||
import {Synchronized} from '@avocado/state';
|
||||
|
||||
import {Layer} from './layer';
|
||||
import {LayerCreatePacket} from './packets/layer-create.packet';
|
||||
import {TileUpdatePacket} from './packets/tile-update.packet';
|
||||
|
||||
const decorate = compose(
|
||||
EventEmitter,
|
||||
|
@ -12,15 +15,29 @@ const decorate = compose(
|
|||
|
||||
export class Layers extends decorate(class {}) {
|
||||
|
||||
constructor() {
|
||||
constructor(json) {
|
||||
super();
|
||||
this.layers = {};
|
||||
this.layers = [];
|
||||
if ('undefined' !== typeof json) {
|
||||
this.fromJSON(json);
|
||||
}
|
||||
}
|
||||
|
||||
*[Symbol.iterator]() {
|
||||
for (const index in this.layers) {
|
||||
const layer = this.layers[index];
|
||||
yield {index, layer};
|
||||
for (let i = 0; i < this.layers.length; i++) {
|
||||
yield this.layers[i];
|
||||
}
|
||||
}
|
||||
|
||||
acceptPacket(packet) {
|
||||
if (packet instanceof LayerCreatePacket) {
|
||||
this.addLayer(new Layer(packet.data));
|
||||
}
|
||||
if (packet instanceof TileUpdatePacket) {
|
||||
this.layers[packet.data.layer].acceptPacket(packet);
|
||||
}
|
||||
if (packet instanceof EntityCreatePacket) {
|
||||
this.layers[packet.data.layer].acceptPacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,34 +49,36 @@ export class Layers extends decorate(class {}) {
|
|||
layer.addEntity(entity);
|
||||
}
|
||||
|
||||
addLayer(index, layer) {
|
||||
addLayer(layer) {
|
||||
layer.on('entityAdded', this.onEntityAddedToLayers, this);
|
||||
layer.on('entityRemoved', this.onEntityRemovedFromLayers, this);
|
||||
this.layers[index] = layer;
|
||||
this.emit('layerAdded', layer, index);
|
||||
this.layers.push(layer);
|
||||
layer.index = this.layers.length - 1;
|
||||
this.emit('layerAdded', layer);
|
||||
}
|
||||
|
||||
allEntities() {
|
||||
let allEntities = [];
|
||||
for (const index in this.layers) {
|
||||
const layer = this.layers[index];
|
||||
allEntities = allEntities.concat(layer.allEntities());
|
||||
for (let i = 0; i < this.layers.length; i++) {
|
||||
const layerEntities = this.layers[i].allEntities();
|
||||
for (let j = 0; j < layerEntities.length; j++) {
|
||||
allEntities.push(layerEntities[j]);
|
||||
}
|
||||
}
|
||||
return allEntities;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
for (const index in this.layers) {
|
||||
const layer = this.layers[index];
|
||||
this.removeLayer(index);
|
||||
for (let i = 0; i < this.layers.length; i++) {
|
||||
const layer = this.layers[i];
|
||||
this.removeLayer(layer);
|
||||
layer.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
findEntity(uuid) {
|
||||
for (const index in this.layers) {
|
||||
const layer = this.layers[index];
|
||||
const foundEntity = layer.findEntity(uuid);
|
||||
for (let i = 0; i < this.layers.length; i++) {
|
||||
const foundEntity = this.layers[i].findEntity(uuid);
|
||||
if (foundEntity) {
|
||||
return foundEntity;
|
||||
}
|
||||
|
@ -67,13 +86,8 @@ export class Layers extends decorate(class {}) {
|
|||
}
|
||||
|
||||
fromJSON(json) {
|
||||
if (json) {
|
||||
for (const index in json) {
|
||||
const layerJSON = json[index];
|
||||
const layer = new Layer()
|
||||
this.addLayer(index, layer);
|
||||
layer.fromJSON(layerJSON);
|
||||
}
|
||||
for (let i = 0; i < json.length; i++) {
|
||||
this.addLayer(new Layer(json[i]));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
@ -90,12 +104,15 @@ export class Layers extends decorate(class {}) {
|
|||
this.emit('entityRemoved', entity);
|
||||
}
|
||||
|
||||
patchStateStep(index, step) {
|
||||
const layer = this.layers[index] ? this.layers[index] : new Layer();
|
||||
if (!this.layers[index]) {
|
||||
this.addLayer(index, layer);
|
||||
packetsForUpdate(force = false) {
|
||||
const packets = [];
|
||||
for (let i = 0; i < this.layers.length; i++) {
|
||||
const layerPacketsForUpdate = this.layers[i].packetsForUpdate(force);
|
||||
for (let j = 0; j < layerPacketsForUpdate.length; j++) {
|
||||
packets.push(layerPacketsForUpdate[j]);
|
||||
}
|
||||
}
|
||||
layer.patchState([step]);
|
||||
return packets;
|
||||
}
|
||||
|
||||
removeEntityFromLayer(entity, layerIndex) {
|
||||
|
@ -106,41 +123,30 @@ export class Layers extends decorate(class {}) {
|
|||
layer.removeEntity(entity);
|
||||
}
|
||||
|
||||
removeLayer(index) {
|
||||
const layer = this.layers[index];
|
||||
if (!layer) {
|
||||
// TODO: fix index for remaining layers
|
||||
removeLayer(layer) {
|
||||
const index = this.layers.indexOf(layer);
|
||||
if (-1 === index) {
|
||||
return;
|
||||
}
|
||||
layer.off('entityAdded', this.onEntityAddedToLayers);
|
||||
layer.off('entityRemoved', this.onEntityRemovedFromLayers);
|
||||
delete this.layers[index];
|
||||
this.emit('layerRemoved', layer, index);
|
||||
this.layers.splice(index, 1);
|
||||
this.emit('layerRemoved', layer);
|
||||
}
|
||||
|
||||
tick(elapsed) {
|
||||
if (this.layers) {
|
||||
for (const index in this.layers) {
|
||||
const layer = this.layers[index];
|
||||
layer.tick(elapsed);
|
||||
}
|
||||
if (AVOCADO_SERVER) {
|
||||
this.state = this.state.withMutations((state) => {
|
||||
for (const index in this.layers) {
|
||||
const layer = this.layers[index];
|
||||
state.set(index, layer.state);
|
||||
}
|
||||
});
|
||||
}
|
||||
for (let i = 0; i < this.layers.length; i++) {
|
||||
this.layers[i].tick(elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
visibleEntities(query) {
|
||||
const entities = [];
|
||||
for (const index in this.layers) {
|
||||
const layer = this.layers[index];
|
||||
const layerVisibleEntities = layer.visibleEntities(query);
|
||||
for (let i = 0; i < layerVisibleEntities.length; i++) {
|
||||
const layerVisibleEntity = layerVisibleEntities[i];
|
||||
for (let i = 0; i < this.layers.length; i++) {
|
||||
const layerVisibleEntities = this.layers[i].visibleEntities(query);
|
||||
for (let j = 0; j < layerVisibleEntities.length; j++) {
|
||||
const layerVisibleEntity = layerVisibleEntities[j];
|
||||
entities.push(layerVisibleEntity);
|
||||
}
|
||||
}
|
||||
|
|
18
packages/topdown/packets/layer-create.packet.js
Normal file
18
packages/topdown/packets/layer-create.packet.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import {Packet} from '@avocado/net';
|
||||
|
||||
export class LayerCreatePacket extends Packet {
|
||||
|
||||
static get schema() {
|
||||
return {
|
||||
...super.schema,
|
||||
data: {
|
||||
tilesetUri: 'string',
|
||||
tiles: {
|
||||
size: ['uint16'],
|
||||
data: ['uint16'],
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
}
|
12
packages/topdown/packets/room-size-update.packet.js
Normal file
12
packages/topdown/packets/room-size-update.packet.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import {Packet} from '@avocado/net';
|
||||
|
||||
export class RoomSizeUpdatePacket extends Packet {
|
||||
|
||||
static get schema() {
|
||||
return {
|
||||
...super.schema,
|
||||
data: 'uint32',
|
||||
};
|
||||
}
|
||||
|
||||
}
|
12
packages/topdown/packets/tile-update.packet.js
Normal file
12
packages/topdown/packets/tile-update.packet.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import {Packet} from '@avocado/net';
|
||||
|
||||
export class TileUpdatePacket extends Packet {
|
||||
|
||||
static get schema() {
|
||||
return {
|
||||
...super.schema,
|
||||
data: ['uint16'],
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -1,11 +1,15 @@
|
|||
import * as I from 'immutable';
|
||||
|
||||
import {compose, EventEmitter, Property} from '@avocado/core';
|
||||
import {EntityCreatePacket, EntityPacket, EntityRemovePacket} from '@avocado/entity';
|
||||
import {Vector} from '@avocado/math';
|
||||
import {RectangleShape} from '@avocado/physics';
|
||||
import {Synchronized} from '@avocado/state';
|
||||
|
||||
import {Layers} from './layers';
|
||||
import {LayerCreatePacket} from './packets/layer-create.packet';
|
||||
import {RoomSizeUpdatePacket} from './packets/room-size-update.packet';
|
||||
import {TileUpdatePacket} from './packets/tile-update.packet';
|
||||
|
||||
const ROOM_BOUND_SIZE = 64;
|
||||
const HALF_ROOM_BOUND_SIZE = ROOM_BOUND_SIZE / 2;
|
||||
|
@ -36,6 +40,34 @@ export class Room extends decorate(class {}) {
|
|||
this.on('worldChanged', this.onWorldChanged, this);
|
||||
}
|
||||
|
||||
acceptPacket(packet) {
|
||||
if (
|
||||
packet instanceof LayerCreatePacket
|
||||
|| packet instanceof TileUpdatePacket
|
||||
|| packet instanceof EntityCreatePacket
|
||||
) {
|
||||
this.layers.acceptPacket(packet);
|
||||
}
|
||||
if (packet instanceof EntityRemovePacket) {
|
||||
const entity = this.findEntity(packet.data.uuid);
|
||||
if (entity) {
|
||||
entity.destroy();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (packet instanceof EntityPacket) {
|
||||
const entity = this.findEntity(packet.data.uuid);
|
||||
if (entity) {
|
||||
entity.acceptPacket(packet);
|
||||
}
|
||||
}
|
||||
if (packet instanceof RoomSizeUpdatePacket) {
|
||||
const x = packet.data & 0xFFFF;
|
||||
const y = packet.data >> 16;
|
||||
this.size = [x, y];
|
||||
}
|
||||
}
|
||||
|
||||
addEntityToLayer(entity, layerIndex = 0) {
|
||||
this.layers.addEntityToLayer(entity, layerIndex);
|
||||
}
|
||||
|
@ -78,7 +110,7 @@ export class Room extends decorate(class {}) {
|
|||
this.emit('entityRemoved', entity);
|
||||
}
|
||||
|
||||
onLayerAdded(layer, index) {
|
||||
onLayerAdded(layer) {
|
||||
layer.world = this.world;
|
||||
}
|
||||
|
||||
|
@ -88,7 +120,7 @@ export class Room extends decorate(class {}) {
|
|||
|
||||
onWorldChanged() {
|
||||
const world = this.world;
|
||||
for (const {layer} of this.layers) {
|
||||
for (const layer of this.layers) {
|
||||
layer.world = world;
|
||||
for (const entity of layer.entityList) {
|
||||
if (entity.is('physical')) {
|
||||
|
@ -100,16 +132,22 @@ export class Room extends decorate(class {}) {
|
|||
this.updateBounds();
|
||||
}
|
||||
|
||||
removeEntityFromLayer(entity, layerIndex = 0) {
|
||||
this.layers.removeEntityFromLayer(entity, layerIndex);
|
||||
packetsForUpdate(force = false) {
|
||||
const packets = [];
|
||||
if (force) {
|
||||
const packed = (this.height << 16) | (this.width);
|
||||
packets.push(new RoomSizeUpdatePacket(packed));
|
||||
}
|
||||
// Layers packets.
|
||||
const layersPacketsForUpdate = this.layers.packetsForUpdate(force);
|
||||
for (let i = 0; i < layersPacketsForUpdate.length; i++) {
|
||||
packets.push(layersPacketsForUpdate[i]);
|
||||
}
|
||||
return packets;
|
||||
}
|
||||
|
||||
synchronizedChildren() {
|
||||
return [
|
||||
'width',
|
||||
'height',
|
||||
'layers',
|
||||
];
|
||||
removeEntityFromLayer(entity, layerIndex) {
|
||||
this.layers.removeEntityFromLayer(entity, layerIndex);
|
||||
}
|
||||
|
||||
updateBounds() {
|
||||
|
@ -152,7 +190,7 @@ export class Room extends decorate(class {}) {
|
|||
}
|
||||
|
||||
tick(elapsed) {
|
||||
this.tickSynchronized(elapsed);
|
||||
this.layers.tick(elapsed);
|
||||
if (this.world) {
|
||||
this.world.tick(elapsed);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import {compose, EventEmitter} from '@avocado/core';
|
|||
import {Rectangle, Vector} from '@avocado/math';
|
||||
import {Synchronized} from '@avocado/state';
|
||||
|
||||
import {TileUpdatePacket} from './packets/tile-update.packet';
|
||||
|
||||
const decorate = compose(
|
||||
EventEmitter,
|
||||
Vector.Mixin('size', 'width', 'height', {
|
||||
|
@ -14,9 +16,17 @@ const decorate = compose(
|
|||
|
||||
export class Tiles extends decorate(class {}) {
|
||||
|
||||
constructor() {
|
||||
constructor(json) {
|
||||
super();
|
||||
this.data = I.List();
|
||||
this.data = [];
|
||||
if ('undefined' !== typeof json) {
|
||||
this.fromJSON(json);
|
||||
}
|
||||
}
|
||||
|
||||
acceptPacket(packet) {
|
||||
if (packet instanceof TileUpdatePacket) {
|
||||
}
|
||||
}
|
||||
|
||||
forEachTile(fn) {
|
||||
|
@ -25,7 +35,7 @@ export class Tiles extends decorate(class {}) {
|
|||
let i = 0;
|
||||
for (let k = 0; k < height; ++k) {
|
||||
for (let j = 0; j < width; ++j) {
|
||||
fn(this.data.get(i), x, y, i);
|
||||
fn(this.data[i], x, y, i);
|
||||
++i;
|
||||
++x;
|
||||
}
|
||||
|
@ -39,26 +49,11 @@ export class Tiles extends decorate(class {}) {
|
|||
this.size = json.size;
|
||||
}
|
||||
if (json.data) {
|
||||
this.data = I.fromJS(json.data);
|
||||
this.data = json.data.slice(0);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
patchStateStep(key, step) {
|
||||
if ('data' === key) {
|
||||
const oldData = this.data;
|
||||
for (const i in step.value) {
|
||||
const index = parseInt(i);
|
||||
this.data = this.data.set(index, step.value[i]);
|
||||
}
|
||||
if (oldData !== this.data) {
|
||||
this.emit('dataChanged');
|
||||
}
|
||||
return;
|
||||
}
|
||||
super.patchStateStep(key, step);
|
||||
}
|
||||
|
||||
get rectangle() {
|
||||
return Rectangle.compose([0, 0], this.size);
|
||||
}
|
||||
|
@ -69,10 +64,10 @@ export class Tiles extends decorate(class {}) {
|
|||
return;
|
||||
}
|
||||
const index = y * this.width + x;
|
||||
if (index < 0 || index >= this.data.size) {
|
||||
if (index < 0 || index >= this.data.length) {
|
||||
return;
|
||||
}
|
||||
this.data = this.data.set(index, tile);
|
||||
this.data[index] = tile;
|
||||
this.emit('dataChanged');
|
||||
}
|
||||
|
||||
|
@ -94,7 +89,7 @@ export class Tiles extends decorate(class {}) {
|
|||
const slice = new Array(sliceWidth * sliceHeight);
|
||||
for (let j = 0; j < sliceHeight; ++j) {
|
||||
for (let i = 0; i < sliceWidth; ++i) {
|
||||
slice[sliceRow + x] = this.data.get(dataRow + x);
|
||||
slice[sliceRow + x] = this.data[dataRow + x];
|
||||
x++;
|
||||
}
|
||||
sliceRow += sliceWidth;
|
||||
|
@ -104,26 +99,14 @@ export class Tiles extends decorate(class {}) {
|
|||
return slice;
|
||||
}
|
||||
|
||||
synchronizedChildren() {
|
||||
return [
|
||||
'width',
|
||||
'height',
|
||||
'data',
|
||||
];
|
||||
}
|
||||
|
||||
tick(elapsed) {
|
||||
this.tickSynchronized(elapsed);
|
||||
}
|
||||
|
||||
tileAt(x, y) {
|
||||
return this.data.get(y * this.width + x);
|
||||
return this.data[y * this.width + x];
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
size: [...this.size],
|
||||
data: [...this.data.toJS()],
|
||||
size: Vector.copy(this.size),
|
||||
data: this.data.slice(0),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user