refactor: synchronization
This commit is contained in:
parent
8afa52fc2e
commit
6bdbe70fec
|
@ -118,6 +118,7 @@ export default (latus) => class Behaved extends decorate(Trait) {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
this.#context.destroy();
|
||||
this.#currentRoutine = undefined;
|
||||
this.#routines = undefined;
|
||||
|
|
|
@ -85,8 +85,8 @@ export default (Trait, latus) => class DialogInitiator extends Trait {
|
|||
};
|
||||
}
|
||||
|
||||
packets(informed) {
|
||||
return super.packets(informed).concat(
|
||||
packetsFor(informed) {
|
||||
return super.packetsFor(informed).concat(
|
||||
informed === this.entity
|
||||
? this.#dialogs.map((dialog) => ['OpenDialog', dialog])
|
||||
: [],
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const blacklistedAccessorKeys = [
|
||||
's13nId',
|
||||
'state',
|
||||
'uri',
|
||||
'instanceUuid',
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
import {Packet} from '@latus/socket';
|
||||
|
||||
export default (latus) => class EntityListUpdateEntityPacket extends Packet {
|
||||
|
||||
static pack(data) {
|
||||
const {Bundle} = latus.get('%packets');
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
data[i].packets = Bundle.encode(data[i].packets);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static get data() {
|
||||
return [
|
||||
{
|
||||
uuid: 'uint32',
|
||||
packets: 'buffer',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
static unpack(data) {
|
||||
const {Bundle} = latus.get('%packets');
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
data[i].packets = Bundle.decode(data[i].packets);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
};
|
|
@ -1,44 +0,0 @@
|
|||
import {SynchronizedUpdatePacket} from '@avocado/s13n';
|
||||
|
||||
export default (latus) => class EntityUpdateTraitPacket extends SynchronizedUpdatePacket {
|
||||
|
||||
static pack(data) {
|
||||
const {Bundle} = latus.get('%packets');
|
||||
const Traits = latus.get('%traits');
|
||||
for (let i = 0; i < data.traits.length; i++) {
|
||||
const {[data.traits[i].type]: Trait} = Traits;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
data.traits[i] = {
|
||||
type: Trait.id,
|
||||
packets: Bundle.encode(data.traits[i].packets),
|
||||
};
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static get s13nSchema() {
|
||||
return {
|
||||
traits: [
|
||||
{
|
||||
type: 'uint8',
|
||||
packets: 'buffer',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
static unpack(data) {
|
||||
const {Bundle} = latus.get('%packets');
|
||||
const Traits = latus.get('%traits');
|
||||
for (let i = 0; i < data.traits.length; i++) {
|
||||
const {[data.traits[i].type]: Trait} = Traits;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
data.traits[i] = {
|
||||
type: Trait.type,
|
||||
packets: Bundle.decode(data.traits[i].packets),
|
||||
};
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
};
|
|
@ -2,13 +2,14 @@ import {compile, Context} from '@avocado/behavior';
|
|||
import {compose, EventEmitter} from '@latus/core';
|
||||
import {QuadTree, Rectangle} from '@avocado/math';
|
||||
import {JsonResource} from '@avocado/resource';
|
||||
import {Serializer} from '@avocado/s13n';
|
||||
import {Synchronized} from '@avocado/s13n';
|
||||
|
||||
export default (latus) => {
|
||||
const decorate = compose(
|
||||
EventEmitter,
|
||||
Synchronized(latus),
|
||||
);
|
||||
|
||||
export default (latus) => class EntityList extends decorate(JsonResource) {
|
||||
return class EntityList extends decorate(JsonResource) {
|
||||
|
||||
#afterDestructionTickers = [];
|
||||
|
||||
|
@ -22,46 +23,13 @@ export default (latus) => class EntityList extends decorate(JsonResource) {
|
|||
|
||||
#quadTree = new QuadTree();
|
||||
|
||||
#serializer = new Serializer();
|
||||
|
||||
async acceptPacket(packet) {
|
||||
if ('EntityListUpdateEntity' === packet.constructor.type) {
|
||||
for (let i = 0; i < packet.data.length; i++) {
|
||||
const {uuid, packets} = packet.data[i];
|
||||
for (let j = 0; j < packets.length; j++) {
|
||||
if (this.#entities[uuid]) {
|
||||
this.#entities[uuid].acceptPacket(packets[j]);
|
||||
}
|
||||
else {
|
||||
this.#serializer.later(uuid, (entity) => {
|
||||
if (entity) {
|
||||
entity.acceptPacket(packets[j]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const {constructor: {s13nType}} = packet;
|
||||
switch (s13nType) {
|
||||
case 'create': {
|
||||
const uuid = packet.data.synchronized.id;
|
||||
await super.acceptPacket(packet);
|
||||
const {s13nType} = packet;
|
||||
if ('create' === s13nType) {
|
||||
const {Entity} = latus.get('%resources');
|
||||
this.#serializer.create(uuid, Entity.load(packet.data.spec));
|
||||
this.#serializer.later(uuid, (entity) => {
|
||||
this.addEntity(entity);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'destroy': {
|
||||
const uuid = packet.data.synchronized.id;
|
||||
this.#serializer.cancelIfPending(uuid);
|
||||
if (this.#entities[uuid]) {
|
||||
this.#entities[uuid].destroy();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
const {id} = packet.data.synchronized;
|
||||
this.addEntity(this.synchronized(Entity.resourceId, id));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,17 +42,19 @@ export default (latus) => class EntityList extends decorate(JsonResource) {
|
|||
this.#entities[uuid] = entity;
|
||||
this.#flatEntities.push(entity);
|
||||
this.#entityTickers.push(entity.tick);
|
||||
if ('client' !== process.env.SIDE) {
|
||||
this.#informedEntities.set(entity, []);
|
||||
}
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
entity.list = this;
|
||||
entity.emit('addedToList');
|
||||
entity.once('destroying', () => this.onEntityDestroying(entity));
|
||||
this.emit('entityAdded', entity);
|
||||
if ('client' !== process.env.SIDE) {
|
||||
this.#informedEntities.set(entity, []);
|
||||
}
|
||||
this.startSynchronizing(entity);
|
||||
}
|
||||
|
||||
cleanPackets() {
|
||||
super.cleanPackets();
|
||||
for (let i = 0; i < this.#flatEntities.length; i++) {
|
||||
this.#flatEntities[i].cleanPackets();
|
||||
}
|
||||
|
@ -126,13 +96,13 @@ export default (latus) => class EntityList extends decorate(JsonResource) {
|
|||
}
|
||||
}
|
||||
|
||||
packets(informed) {
|
||||
packetsFor(informed) {
|
||||
const packets = [];
|
||||
const {Entity} = latus.get('%resources');
|
||||
// Visible entities.
|
||||
const {areaToInform} = informed;
|
||||
const previousVisibleEntities = this.#informedEntities.get(informed);
|
||||
const visibleEntities = this.visibleEntities(areaToInform);
|
||||
const updates = [];
|
||||
for (let i = 0; i < visibleEntities.length; i++) {
|
||||
const entity = visibleEntities[i];
|
||||
// Newly visible entity.
|
||||
|
@ -143,22 +113,22 @@ export default (latus) => class EntityList extends decorate(JsonResource) {
|
|||
else {
|
||||
previousVisibleEntities.splice(index, 1);
|
||||
}
|
||||
const entityPackets = entity.packets(informed);
|
||||
const entityPackets = entity.packetsFor(informed);
|
||||
if (entityPackets.length > 0) {
|
||||
updates.push({
|
||||
uuid: entity.instanceUuid,
|
||||
packets.push([
|
||||
'SynchronizedUpdate',
|
||||
{
|
||||
packets: entityPackets,
|
||||
});
|
||||
synchronized: {
|
||||
id: entity.s13nId,
|
||||
type: Entity.resourceId,
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
// Send updates.
|
||||
this.#informedEntities.set(informed, visibleEntities);
|
||||
if (updates.length > 0) {
|
||||
packets.push([
|
||||
'EntityListUpdateEntity',
|
||||
updates,
|
||||
]);
|
||||
}
|
||||
// Send destroys.
|
||||
for (let i = 0; i < previousVisibleEntities.length; i++) {
|
||||
const entity = previousVisibleEntities[i];
|
||||
|
@ -204,6 +174,7 @@ export default (latus) => class EntityList extends decorate(JsonResource) {
|
|||
if (!(uuid in this.#entities)) {
|
||||
return;
|
||||
}
|
||||
this.stopSynchronizing(entity);
|
||||
if ('client' !== process.env.SIDE) {
|
||||
this.#informedEntities.delete(entity);
|
||||
}
|
||||
|
@ -299,3 +270,4 @@ export default (latus) => class EntityList extends decorate(JsonResource) {
|
|||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
|
|
@ -14,14 +14,14 @@ import BaseEntity from '../base-entity';
|
|||
|
||||
const debug = D('@avocado/entity');
|
||||
|
||||
const decorate = compose(
|
||||
EventEmitter,
|
||||
Synchronized,
|
||||
);
|
||||
|
||||
let numericUid = 'client' !== process.env.SIDE ? 1 : 1000000000;
|
||||
|
||||
export default (latus) => class Entity extends decorate(BaseEntity) {
|
||||
export default (latus) => {
|
||||
const decorate = compose(
|
||||
EventEmitter,
|
||||
Synchronized(latus),
|
||||
);
|
||||
return class Entity extends decorate(BaseEntity) {
|
||||
|
||||
#hooks = {};
|
||||
|
||||
|
@ -60,18 +60,6 @@ export default (latus) => class Entity extends decorate(BaseEntity) {
|
|||
this.visibleAabb = [0, 0, 0, 0];
|
||||
}
|
||||
|
||||
acceptPacket(packet) {
|
||||
if ('EntityUpdateTrait' === packet.constructor.type) {
|
||||
const {traits} = packet.data;
|
||||
for (let i = 0; i < traits.length; i++) {
|
||||
const {type, packets} = traits[i];
|
||||
for (let j = 0; j < packets.length; j++) {
|
||||
this.#traits[type].acceptPacket(packets[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addTickingPromise(tickingPromise) {
|
||||
const ticker = tickingPromise.tick.bind(tickingPromise);
|
||||
this.#tickingPromisesTickers.push(ticker);
|
||||
|
@ -150,6 +138,7 @@ export default (latus) => class Entity extends decorate(BaseEntity) {
|
|||
if ('acceptPacket' in trait) {
|
||||
this.#traitsAcceptingPackets.push(trait);
|
||||
}
|
||||
this.startSynchronizing(trait);
|
||||
this.emit('traitAdded', type, trait);
|
||||
return trait;
|
||||
}
|
||||
|
@ -186,6 +175,7 @@ export default (latus) => class Entity extends decorate(BaseEntity) {
|
|||
}
|
||||
|
||||
cleanPackets() {
|
||||
super.cleanPackets();
|
||||
if (!this.#markedAsDirty) {
|
||||
return;
|
||||
}
|
||||
|
@ -255,32 +245,25 @@ export default (latus) => class Entity extends decorate(BaseEntity) {
|
|||
this.#markedAsDirty = true;
|
||||
}
|
||||
|
||||
packets(informed) {
|
||||
packetsFor(informed) {
|
||||
const packets = [];
|
||||
const updates = [];
|
||||
const traits = Object.entries(this.#traits);
|
||||
const traits = Object.values(this.#traits);
|
||||
for (let i = 0; i < traits.length; i++) {
|
||||
const [type, trait] = traits[i];
|
||||
const traitPackets = trait.packets(informed);
|
||||
const trait = traits[i];
|
||||
const traitPackets = trait.packetsFor(informed);
|
||||
if (traitPackets.length > 0) {
|
||||
updates.push({
|
||||
type,
|
||||
packets: traitPackets,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (updates.length > 0) {
|
||||
packets.push([
|
||||
'EntityUpdateTrait',
|
||||
'SynchronizedUpdate',
|
||||
{
|
||||
packets: traitPackets,
|
||||
synchronized: {
|
||||
id: 0,
|
||||
type: 0,
|
||||
id: trait.s13nId,
|
||||
type: trait.constructor.resourceId,
|
||||
},
|
||||
traits: updates,
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
return packets;
|
||||
}
|
||||
|
||||
|
@ -302,7 +285,7 @@ export default (latus) => class Entity extends decorate(BaseEntity) {
|
|||
}
|
||||
// Destroy instance.
|
||||
const instance = this.#traits[type];
|
||||
instance.destroy();
|
||||
this.stopSynchronizing(instance);
|
||||
// Remove methods, hooks, and properties.
|
||||
const methods = instance.methods();
|
||||
const keys = Object.keys(methods);
|
||||
|
@ -327,7 +310,7 @@ export default (latus) => class Entity extends decorate(BaseEntity) {
|
|||
const [event, listener] = listeners[i];
|
||||
this.off(event, listener);
|
||||
}
|
||||
instance._memoizedListeners = {};
|
||||
instance.destroy();
|
||||
// Remove instance.
|
||||
delete this.#traits[type];
|
||||
this.#traitsFlat.splice(this.#traitsFlat.indexOf(instance), 1);
|
||||
|
@ -349,7 +332,7 @@ export default (latus) => class Entity extends decorate(BaseEntity) {
|
|||
types.forEach((type) => this.removeTrait(type));
|
||||
}
|
||||
|
||||
s13nId() {
|
||||
get s13nId() {
|
||||
return this.instanceUuid;
|
||||
}
|
||||
|
||||
|
@ -439,3 +422,4 @@ export default (latus) => class Entity extends decorate(BaseEntity) {
|
|||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
|
|
@ -112,6 +112,7 @@ export default (latus) => class Alive extends decorate(Trait) {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
this.#context.destroy();
|
||||
}
|
||||
|
||||
|
@ -193,7 +194,7 @@ export default (latus) => class Alive extends decorate(Trait) {
|
|||
};
|
||||
}
|
||||
|
||||
packets() {
|
||||
packetsFor() {
|
||||
const packets = this.#packets.concat();
|
||||
const {life, maxLife} = this.stateDifferences();
|
||||
if (life || maxLife) {
|
||||
|
|
|
@ -90,7 +90,7 @@ export default () => class Directional extends decorate(Trait) {
|
|||
};
|
||||
}
|
||||
|
||||
packets() {
|
||||
packetsFor() {
|
||||
const {direction} = this.stateDifferences();
|
||||
if (direction) {
|
||||
return [[
|
||||
|
|
|
@ -37,6 +37,7 @@ export default () => class DomNode extends decorate(Trait) {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
super.parentNode?.removeChild(this.entity.node);
|
||||
}
|
||||
|
||||
|
|
|
@ -224,7 +224,7 @@ export default () => class Mobile extends decorate(Trait) {
|
|||
};
|
||||
}
|
||||
|
||||
packets() {
|
||||
packetsFor() {
|
||||
const {isMobile, speed} = this.stateDifferences();
|
||||
if (isMobile || speed) {
|
||||
return [[
|
||||
|
|
|
@ -75,6 +75,7 @@ export default () => class Positioned extends decorate(Trait) {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
this.off('trackedPositionChanged', this.ontrackedPositionChanged);
|
||||
if ('client' === process.env.SIDE) {
|
||||
this.off('serverPositionChanged', this.onServerPositionChanged);
|
||||
|
@ -107,7 +108,7 @@ export default () => class Positioned extends decorate(Trait) {
|
|||
this.serverPositionDirty = true;
|
||||
}
|
||||
|
||||
packets() {
|
||||
packetsFor() {
|
||||
const {x, y} = this.stateDifferences();
|
||||
if (x || y) {
|
||||
return [[
|
||||
|
|
|
@ -143,6 +143,7 @@ export default (latus) => class Spawner extends decorate(Trait) {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
while (this.#children.length > 0) {
|
||||
const child = this.#children.pop();
|
||||
if (child) {
|
||||
|
|
|
@ -112,7 +112,7 @@ describe('Alive', () => {
|
|||
it('generates and accepts life packets', async () => {
|
||||
entity.life = 80;
|
||||
entity.maxLife = 90;
|
||||
const packets = entity.trait('Alive').packets();
|
||||
const packets = entity.trait('Alive').packetsFor();
|
||||
expect(packets).to.have.lengthOf(1);
|
||||
expect(packets[0][0]).to.equal('TraitUpdateAlive');
|
||||
expect(packets[0][1]).to.deep.equal({life: 80, maxLife: 90});
|
||||
|
@ -124,7 +124,7 @@ describe('Alive', () => {
|
|||
it('generates and accepts death packets', async () => {
|
||||
entity.life = 0;
|
||||
entity.tick();
|
||||
const packets = entity.trait('Alive').packets();
|
||||
const packets = entity.trait('Alive').packetsFor();
|
||||
expect(packets).to.have.lengthOf(2);
|
||||
expect(packets[0][0]).to.equal('Died');
|
||||
expect(packets[1][0]).to.equal('TraitUpdateAlive');
|
||||
|
|
|
@ -42,7 +42,7 @@ describe('Directional', () => {
|
|||
});
|
||||
it('generates and accepts direction packets', async () => {
|
||||
entity.direction = 2;
|
||||
const packets = entity.trait('Directional').packets();
|
||||
const packets = entity.trait('Directional').packetsFor();
|
||||
expect(packets).to.have.lengthOf(1);
|
||||
expect(packets[0][0]).to.equal('TraitUpdateDirectionalDirection');
|
||||
expect(packets[0][1]).to.equal(2);
|
||||
|
|
|
@ -30,7 +30,7 @@ describe('Positioned', () => {
|
|||
if ('client' !== process.env.SIDE) {
|
||||
it('generates and accepts movement packets', async () => {
|
||||
entity.setPosition([1, 1]);
|
||||
const packets = entity.trait('Positioned').packets();
|
||||
const packets = entity.trait('Positioned').packetsFor();
|
||||
expect(packets).to.have.lengthOf(1);
|
||||
expect(packets[0][0]).to.equal('TraitUpdatePositionedPosition');
|
||||
expect(packets[0][1]).to.deep.equal([1, 1]);
|
||||
|
|
|
@ -62,6 +62,7 @@ export default (latus) => class Pictured extends decorate(Trait) {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
if (this.#sprites) {
|
||||
const sprites = Object.entries(this.#sprites);
|
||||
for (let i = 0; i < sprites.length; i++) {
|
||||
|
|
|
@ -35,6 +35,7 @@ export default () => class Rastered extends Trait {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
if (this.#container) {
|
||||
this.#container.destroy();
|
||||
}
|
||||
|
|
|
@ -98,6 +98,7 @@ export default () => class Visible extends decorate(Trait) {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
this.removeFromQuadTree(this.entity.list);
|
||||
}
|
||||
|
||||
|
@ -167,7 +168,7 @@ export default () => class Visible extends decorate(Trait) {
|
|||
};
|
||||
}
|
||||
|
||||
packets() {
|
||||
packetsFor() {
|
||||
const {isVisible, opacity, rotation} = this.stateDifferences();
|
||||
if (isVisible || opacity || rotation) {
|
||||
return [[
|
||||
|
|
|
@ -26,6 +26,7 @@ export default () => class Controllable extends Trait {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
this.#actionRegistry.stopListening();
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ export default (latus) => class Interactive extends decorate(Trait) {
|
|||
};
|
||||
}
|
||||
|
||||
packets() {
|
||||
packetsFor() {
|
||||
const {isInteractive} = this.stateDifferences();
|
||||
if (isInteractive) {
|
||||
return [[
|
||||
|
|
|
@ -126,6 +126,7 @@ export default (latus) => class Collider extends decorate(Trait) {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
this.releaseAllCollisions();
|
||||
}
|
||||
|
||||
|
|
|
@ -322,7 +322,7 @@ export default (latus) => class Emitter extends decorate(Trait) {
|
|||
/* eslint-enable no-param-reassign */
|
||||
}
|
||||
|
||||
packets() {
|
||||
packetsFor() {
|
||||
return this.#emitting.length > 0
|
||||
? [['EmitParticles', this.#emitting]]
|
||||
: [];
|
||||
|
|
|
@ -93,6 +93,7 @@ export default () => class Physical extends decorate(Trait) {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
this.world = undefined;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ export default () => class Shaped extends decorate(Trait) {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
this.#shape.destroy();
|
||||
if (this.#shapeView) {
|
||||
this.#shapeView.destroy();
|
||||
|
|
|
@ -4,19 +4,14 @@ import {
|
|||
SynchronizedUpdatePacket,
|
||||
} from './packets';
|
||||
|
||||
export * from './packets';
|
||||
|
||||
export {default as ReceiverSynchronizer} from './receiver-synchronizer';
|
||||
export {default as SenderSynchronizer} from './sender-synchronizer';
|
||||
export {default as Serializer} from './serializer';
|
||||
export {default as Synchronized, synchronized} from './synchronized';
|
||||
export {default as Synchronized} from './synchronized';
|
||||
|
||||
export default {
|
||||
hooks: {
|
||||
'@latus/socket/packets': () => ({
|
||||
'@latus/socket/packets': (latus) => ({
|
||||
SynchronizedCreate: SynchronizedCreatePacket,
|
||||
SynchronizedDestroy: SynchronizedDestroyPacket,
|
||||
SynchronizedUpdate: SynchronizedUpdatePacket,
|
||||
SynchronizedUpdate: SynchronizedUpdatePacket(latus),
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,9 +1,29 @@
|
|||
import SynchronizedPacket from './synchronized';
|
||||
|
||||
export default class SynchronizedUpdatePacket extends SynchronizedPacket {
|
||||
export default (latus) => class SynchronizedUpdatePacket extends SynchronizedPacket {
|
||||
|
||||
static pack(data) {
|
||||
const {Bundle} = latus.get('%packets');
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
data.packets = Bundle.encode(data.packets);
|
||||
return data;
|
||||
}
|
||||
|
||||
static get s13nSchema() {
|
||||
return {
|
||||
packets: 'buffer',
|
||||
};
|
||||
}
|
||||
|
||||
static get s13nType() {
|
||||
return 'update';
|
||||
}
|
||||
|
||||
static unpack(data) {
|
||||
const {Bundle} = latus.get('%packets');
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
data.packets = Bundle.decode(data.packets);
|
||||
return data;
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -16,6 +16,10 @@ export default class SynchronizedPacket extends Packet {
|
|||
return {};
|
||||
}
|
||||
|
||||
get s13nType() {
|
||||
return this.constructor.s13nType;
|
||||
}
|
||||
|
||||
static get s13nType() {
|
||||
return 'none';
|
||||
}
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
import {Class, compose, EventEmitter} from '@latus/core';
|
||||
|
||||
import Serializer from './serializer';
|
||||
|
||||
const decorate = compose(
|
||||
EventEmitter,
|
||||
);
|
||||
|
||||
export default class ReceiverSynchronizer extends decorate(Class) {
|
||||
|
||||
#serializer = new Serializer();
|
||||
|
||||
#synchronized = {};
|
||||
|
||||
constructor(latus) {
|
||||
super();
|
||||
this.latus = latus;
|
||||
}
|
||||
|
||||
async acceptPacket(packet) {
|
||||
const {constructor: {s13nType}} = packet;
|
||||
if (!s13nType) {
|
||||
return;
|
||||
}
|
||||
const {id, type} = packet.data.synchronized;
|
||||
switch (s13nType) {
|
||||
case 'create': {
|
||||
this.createSynchronized(type, id, packet.data.spec);
|
||||
break;
|
||||
}
|
||||
case 'destroy': {
|
||||
this.deleteSynchronized(type, id);
|
||||
break;
|
||||
}
|
||||
case 'update': {
|
||||
this.#serializer.later(`${type}:${id}`, (resource) => {
|
||||
resource.acceptPacket(packet);
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
addSynchronized(synchronized) {
|
||||
const {resourceId: type} = synchronized.constructor;
|
||||
if (!(type in this.#synchronized)) {
|
||||
this.#synchronized[type] = {};
|
||||
}
|
||||
const id = synchronized.s13nId();
|
||||
this.#synchronized[type][id] = synchronized;
|
||||
}
|
||||
|
||||
async createSynchronized(type, id, json) {
|
||||
const {[type]: Resource} = this.latus.get('%resources');
|
||||
if (!(type in this.#synchronized)) {
|
||||
this.#synchronized[type] = {};
|
||||
}
|
||||
if (this.#synchronized[type][id]) {
|
||||
await this.#synchronized[type][id].load(json);
|
||||
}
|
||||
else {
|
||||
this.#serializer.create(`${type}:${id}`, Resource.load(json));
|
||||
await this.#serializer.later(`${type}:${id}`, (resource) => {
|
||||
this.#synchronized[type][id] = resource;
|
||||
});
|
||||
}
|
||||
this.emit('created', type, this.#synchronized[type][id], id);
|
||||
}
|
||||
|
||||
async deleteSynchronized(type, id) {
|
||||
if (!this.hasSynchronized(type, id)) {
|
||||
return;
|
||||
}
|
||||
this.#serializer.cancelIfPending(`${type}:${id}`);
|
||||
const resource = await this.#synchronized[type][id];
|
||||
if (resource) {
|
||||
await resource.destroy();
|
||||
delete this.#synchronized[type][id];
|
||||
}
|
||||
}
|
||||
|
||||
hasSynchronized(type, id) {
|
||||
return !!this.#synchronized[type][id];
|
||||
}
|
||||
|
||||
synchronized(type, id) {
|
||||
return this.synchronizedOfType(type)[id];
|
||||
}
|
||||
|
||||
synchronizedOfType(type) {
|
||||
return this.#synchronized[type] || {};
|
||||
}
|
||||
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
export default class SenderSynchronizer {
|
||||
|
||||
#added = [];
|
||||
|
||||
#nextSyncPackets = [];
|
||||
|
||||
#queuedPackets = [];
|
||||
|
||||
#removed = [];
|
||||
|
||||
#synchronized = {};
|
||||
|
||||
#synchronizedFlat = [];
|
||||
|
||||
constructor(latus) {
|
||||
this.latus = latus;
|
||||
}
|
||||
|
||||
addSynchronized(synchronized) {
|
||||
if (this.hasSynchronized(synchronized)) {
|
||||
return;
|
||||
}
|
||||
this.#added.push(synchronized);
|
||||
const {resourceId: type} = synchronized.constructor;
|
||||
if (!(type in this.#synchronized)) {
|
||||
this.#synchronized[type] = {};
|
||||
}
|
||||
this.#synchronizedFlat.push(synchronized);
|
||||
const id = synchronized.s13nId();
|
||||
this.#synchronized[type][id] = synchronized;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.#queuedPackets = [];
|
||||
this.#synchronized = {};
|
||||
}
|
||||
|
||||
hasSynchronized(synchronized) {
|
||||
return -1 !== this.#synchronizedFlat.indexOf(synchronized);
|
||||
}
|
||||
|
||||
packetsFor(informed) {
|
||||
const payload = [];
|
||||
for (let i = 0; i < this.#synchronizedFlat.length; i++) {
|
||||
const synchronized = this.#synchronizedFlat[i];
|
||||
if (-1 !== this.#added.indexOf(synchronized)) {
|
||||
payload.push(synchronized.createPacket(informed));
|
||||
}
|
||||
else if (-1 !== this.#removed.indexOf(synchronized)) {
|
||||
payload.push(synchronized.destroyPacket(informed));
|
||||
}
|
||||
else {
|
||||
const packets = synchronized.packetsFor(this.latus, informed);
|
||||
for (let j = 0; j < packets.length; j++) {
|
||||
payload.push(packets[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.#added = [];
|
||||
this.#removed = [];
|
||||
return payload;
|
||||
}
|
||||
|
||||
removeSynchronized(synchronized) {
|
||||
if (!this.hasSynchronized(synchronized)) {
|
||||
return;
|
||||
}
|
||||
this.#removed.push(synchronized);
|
||||
const index = this.#synchronizedFlat.indexOf(synchronized);
|
||||
this.#synchronizedFlat.splice(index, 1);
|
||||
const {resourceId: type} = synchronized.constructor;
|
||||
const id = synchronized.s13nId();
|
||||
delete this.#synchronized[type][id];
|
||||
}
|
||||
|
||||
async send(socket, informed) {
|
||||
const synchronizerPackets = this.packetsFor(informed);
|
||||
for (let i = 0; i < synchronizerPackets.length; i++) {
|
||||
this.#nextSyncPackets.push(synchronizerPackets[i]);
|
||||
}
|
||||
for (let i = 0; i < this.#queuedPackets.length; i++) {
|
||||
this.#nextSyncPackets.push(this.#queuedPackets[i]);
|
||||
}
|
||||
this.#queuedPackets = [];
|
||||
if (socket && this.#nextSyncPackets.length > 0) {
|
||||
const nextSyncPackets = this.#nextSyncPackets;
|
||||
this.#nextSyncPackets = [];
|
||||
await socket.send(['Bundle', nextSyncPackets]);
|
||||
}
|
||||
}
|
||||
|
||||
queuePacket(packet) {
|
||||
this.#queuedPackets.push(packet);
|
||||
}
|
||||
|
||||
}
|
|
@ -7,13 +7,6 @@ export default class Serializer {
|
|||
|
||||
#promises = new Map();
|
||||
|
||||
cancelIfPending(id) {
|
||||
const pending = this.#pending.get(id);
|
||||
if (pending) {
|
||||
pending.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
create(id, creator) {
|
||||
const promise = creator.then(async (resource) => {
|
||||
if (!this.#pending.has(id)) {
|
||||
|
@ -27,13 +20,18 @@ export default class Serializer {
|
|||
throw error;
|
||||
}
|
||||
});
|
||||
this.#pending.set(id, {
|
||||
cancel: () => this.#pending.delete(id),
|
||||
});
|
||||
this.#pending.set(id, () => this.#pending.delete(id));
|
||||
this.#promises.set(id, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
destroy(id) {
|
||||
// Cancel...
|
||||
this.#pending.get(id)?.();
|
||||
this.#pending.delete(id);
|
||||
this.#promises.delete(id);
|
||||
}
|
||||
|
||||
later(id, fn) {
|
||||
const promise = this.#promises.get(id);
|
||||
if (!promise) {
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
export const synchronized = ({config}) => config['%synchronized'];
|
||||
|
||||
export default function SynchronizedMixin(Superclass) {
|
||||
|
||||
return class Synchronized extends Superclass {
|
||||
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this._idempotentPackets = [];
|
||||
}
|
||||
|
||||
cleanPackets() {
|
||||
this._idempotentPackets = [];
|
||||
}
|
||||
|
||||
createPacket(informed) {
|
||||
const id = this.s13nId();
|
||||
return [
|
||||
'SynchronizedCreate',
|
||||
{
|
||||
synchronized: {
|
||||
id,
|
||||
type: this.constructor.resourceId,
|
||||
},
|
||||
spec: this.toNetwork(informed),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
destroy() {}
|
||||
|
||||
destroyPacket() {
|
||||
const id = this.s13nId();
|
||||
return [
|
||||
'SynchronizedDestroy',
|
||||
{
|
||||
synchronized: {
|
||||
id,
|
||||
type: this.constructor.resourceId,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
fromNetwork() {
|
||||
throw new ReferenceError(
|
||||
`${this.constructor.resourceType || this.constructor.name}::fromNetwork is undefined`,
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
packets() {
|
||||
return [];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
packetsAreIdempotent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
packetsFor(latus, informed) {
|
||||
if (this._idempotentPackets.length > 0) {
|
||||
return this._idempotentPackets;
|
||||
}
|
||||
const packets = this.packets(informed);
|
||||
// Embed synchronization info.
|
||||
const id = this.s13nId();
|
||||
for (let i = 0; i < packets.length; i++) {
|
||||
packets[i][1].synchronized = {
|
||||
id,
|
||||
type: this.constructor.resourceId,
|
||||
};
|
||||
}
|
||||
if (this.packetsAreIdempotent()) {
|
||||
this._idempotentPackets = packets;
|
||||
}
|
||||
return packets;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
s13nId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
toNetwork() {
|
||||
return this.toJSON();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
107
packages/s13n/src/synchronized/client.js
Normal file
107
packages/s13n/src/synchronized/client.js
Normal file
|
@ -0,0 +1,107 @@
|
|||
import Serializer from '../serializer';
|
||||
|
||||
export default (latus) => (Resource) => (
|
||||
class SynchronizedResource extends Resource {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._serializer = new Serializer();
|
||||
this._synchronized = {};
|
||||
}
|
||||
|
||||
async acceptPacket(packet) {
|
||||
const {s13nType} = packet;
|
||||
// eslint-disable-next-line max-len
|
||||
if (!s13nType) {
|
||||
return;
|
||||
}
|
||||
const {id, type} = packet.data.synchronized;
|
||||
switch (s13nType) {
|
||||
case 'create':
|
||||
await this.createSynchronized(type, id, packet.data.spec);
|
||||
break;
|
||||
case 'destroy':
|
||||
await this.destroySynchronized(type, id);
|
||||
break;
|
||||
case 'update':
|
||||
if (this._synchronized[type]?.[id]) {
|
||||
const promises = [];
|
||||
for (let i = 0; i < packet.data.packets.length; i++) {
|
||||
promises.push(this._synchronized[type][id].acceptPacket(packet.data.packets[i]));
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
else {
|
||||
await this._serializer.later(
|
||||
`${type}:${id}`,
|
||||
(resource) => resource.acceptPacket(packet),
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
async createSynchronized(type, id, json) {
|
||||
const {[type]: Resource} = latus.get('%resources');
|
||||
if (!(type in this._synchronized)) {
|
||||
this._synchronized[type] = {};
|
||||
}
|
||||
if (this._synchronized[type][id]) {
|
||||
await this._synchronized[type][id].load(json);
|
||||
}
|
||||
else {
|
||||
this._serializer.create(`${type}:${id}`, Resource.load(json));
|
||||
await this._serializer.later(`${type}:${id}`, (resource) => {
|
||||
this._synchronized[type][id] = resource;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
destroy() {}
|
||||
|
||||
async destroySynchronized(type, id) {
|
||||
if (!this._synchronized[type]?.[id]) {
|
||||
return;
|
||||
}
|
||||
this._serializer.destroy(`${type}:${id}`);
|
||||
const resource = await this._synchronized[type][id];
|
||||
if (resource) {
|
||||
await resource.destroy();
|
||||
delete this._synchronized[type][id];
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
get s13nId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
startSynchronizing(resource) {
|
||||
const id = resource.s13nId;
|
||||
const type = resource.constructor.resourceId;
|
||||
if (this._synchronized[type]?.[id]) {
|
||||
return;
|
||||
}
|
||||
if (!(type in this._synchronized)) {
|
||||
this._synchronized[type] = {};
|
||||
}
|
||||
this._synchronized[type][id] = resource;
|
||||
}
|
||||
|
||||
stopSynchronizing(resource) {
|
||||
const id = resource.s13nId;
|
||||
const type = resource.constructor.resourceId;
|
||||
if (!this._synchronized[type]?.[id]) {
|
||||
return;
|
||||
}
|
||||
delete this._synchronized[type][id];
|
||||
}
|
||||
|
||||
synchronized(type, id) {
|
||||
return this._synchronized[type]?.[id];
|
||||
}
|
||||
|
||||
}
|
||||
);
|
4
packages/s13n/src/synchronized/index.js
Normal file
4
packages/s13n/src/synchronized/index.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
import Client from './client';
|
||||
import Server from './server';
|
||||
|
||||
export default process.env.SIDE === 'client' ? Client : Server;
|
123
packages/s13n/src/synchronized/server.js
Normal file
123
packages/s13n/src/synchronized/server.js
Normal file
|
@ -0,0 +1,123 @@
|
|||
export default () => (Resource) => (
|
||||
class SynchronizedResource extends Resource {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._added = [];
|
||||
this._removed = [];
|
||||
this._synchronized = [];
|
||||
}
|
||||
|
||||
cleanPackets() {
|
||||
this._added = [];
|
||||
this._removed = [];
|
||||
for (let i = 0; i < this._synchronized.length; i++) {
|
||||
this._synchronized[i].cleanPackets();
|
||||
}
|
||||
}
|
||||
|
||||
createPacket(informed) {
|
||||
return [
|
||||
'SynchronizedCreate',
|
||||
{
|
||||
synchronized: {
|
||||
id: this.s13nId,
|
||||
type: this.constructor.resourceId,
|
||||
},
|
||||
spec: this.toNetwork(informed),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
this._added = [];
|
||||
this._removed = [];
|
||||
this._synchronized = [];
|
||||
}
|
||||
|
||||
destroyPacket() {
|
||||
return [
|
||||
'SynchronizedDestroy',
|
||||
{
|
||||
synchronized: {
|
||||
id: this.s13nId,
|
||||
type: this.constructor.resourceId,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
isSynchronizing(resource) {
|
||||
return -1 !== this._synchronized.indexOf(resource);
|
||||
}
|
||||
|
||||
packetsFor(informed) {
|
||||
const packets = [];
|
||||
for (let i = 0; i < this._synchronized.length; i++) {
|
||||
const synchronized = this._synchronized[i];
|
||||
if (-1 !== this._added.indexOf(synchronized)) {
|
||||
packets.push(synchronized.createPacket(informed));
|
||||
}
|
||||
else if (-1 !== this._removed.indexOf(synchronized)) {
|
||||
packets.push(synchronized.destroyPacket(informed));
|
||||
}
|
||||
else {
|
||||
const updatePacket = synchronized.updatePacket(informed);
|
||||
if (updatePacket) {
|
||||
packets.push(updatePacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
return packets;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
get s13nChildren() {
|
||||
return [];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
get s13nId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
startSynchronizing(synchronized) {
|
||||
if (this.isSynchronizing(synchronized)) {
|
||||
return;
|
||||
}
|
||||
this._added.push(synchronized);
|
||||
this._synchronized.push(synchronized);
|
||||
}
|
||||
|
||||
stopSynchronizing(synchronized) {
|
||||
if (!this.isSynchronizing(synchronized)) {
|
||||
return;
|
||||
}
|
||||
this._removed.push(synchronized);
|
||||
this._synchronized.splice(this._synchronized.indexOf(synchronized), 1);
|
||||
}
|
||||
|
||||
toNetwork() {
|
||||
return this.toJSON();
|
||||
}
|
||||
|
||||
updatePacket(informed) {
|
||||
const packets = this.packetsFor(informed);
|
||||
if (0 === packets.length) {
|
||||
return undefined;
|
||||
}
|
||||
return [
|
||||
'SynchronizedUpdate',
|
||||
{
|
||||
packets,
|
||||
synchronized: {
|
||||
id: this.s13nId,
|
||||
type: this.constructor.resourceId,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
);
|
|
@ -68,6 +68,7 @@ export default (latus) => class Audible extends Trait {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
Object.values(this.#sounds).forEach((sound) => {
|
||||
Promise.resolve(sound).then((sound) => {
|
||||
sound.destroy();
|
||||
|
@ -122,7 +123,7 @@ export default (latus) => class Audible extends Trait {
|
|||
}));
|
||||
}
|
||||
|
||||
packets() {
|
||||
packetsFor() {
|
||||
return Object.keys(this.#playing).map((key) => ['PlaySound', {sound: key}]);
|
||||
}
|
||||
|
||||
|
|
|
@ -89,6 +89,7 @@ export default (latus) => class Animated extends decorate(Trait) {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
if (this.#animationViews) {
|
||||
const animationViews = Object.entries(this.#animationViews);
|
||||
for (let i = 0; i < animationViews.length; i++) {
|
||||
|
@ -246,7 +247,7 @@ export default (latus) => class Animated extends decorate(Trait) {
|
|||
return this.params.animations[key].offset;
|
||||
}
|
||||
|
||||
packets() {
|
||||
packetsFor() {
|
||||
const {currentAnimation, isAnimating} = this.stateDifferences();
|
||||
if (this.#forceUpdate || currentAnimation || isAnimating) {
|
||||
this.#forceUpdate = false;
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
import {Packet} from '@latus/socket';
|
||||
|
||||
export default (latus) => class LayersUpdateLayerPacket extends Packet {
|
||||
|
||||
static pack(data) {
|
||||
const {Bundle} = latus.get('%packets');
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
data[i].layerPackets = Bundle.encode(data[i].layerPackets);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static get data() {
|
||||
return [
|
||||
{
|
||||
layerIndex: 'uint8',
|
||||
layerPackets: 'buffer',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
static unpack(data) {
|
||||
const {Bundle} = latus.get('%packets');
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
data[i].layerPackets = Bundle.decode(data[i].layerPackets);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
};
|
|
@ -1,25 +0,0 @@
|
|||
import {SynchronizedUpdatePacket} from '@avocado/s13n';
|
||||
|
||||
export default (latus) => class RoomUpdateLayers extends SynchronizedUpdatePacket {
|
||||
|
||||
static pack(data) {
|
||||
const {Bundle} = latus.get('%packets');
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
data.layersPackets = Bundle.encode(data.layersPackets);
|
||||
return data;
|
||||
}
|
||||
|
||||
static get s13nSchema() {
|
||||
return {
|
||||
layersPackets: 'buffer',
|
||||
};
|
||||
}
|
||||
|
||||
static unpack(data) {
|
||||
const {Bundle} = latus.get('%packets');
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
data.layersPackets = Bundle.decode(data.layersPackets);
|
||||
return data;
|
||||
}
|
||||
|
||||
};
|
|
@ -1,15 +1,19 @@
|
|||
import {Property} from '@avocado/core';
|
||||
import {JsonResource} from '@avocado/resource';
|
||||
import {Synchronized} from '@avocado/s13n';
|
||||
import {compose, EventEmitter} from '@latus/core';
|
||||
|
||||
export default (latus) => {
|
||||
const decorate = compose(
|
||||
EventEmitter,
|
||||
Property('tileset', {
|
||||
track: true,
|
||||
}),
|
||||
Synchronized(latus),
|
||||
);
|
||||
return class Layer extends decorate(JsonResource) {
|
||||
|
||||
export default (latus) => class Layer extends decorate(JsonResource) {
|
||||
#s13nId;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
@ -20,26 +24,6 @@ export default (latus) => class Layer extends decorate(JsonResource) {
|
|||
this.setTiles(new Tiles());
|
||||
}
|
||||
|
||||
acceptPacket(packet) {
|
||||
const {constructor: {s13nType}} = packet;
|
||||
switch (s13nType) {
|
||||
case 'create':
|
||||
case 'destroy':
|
||||
this.entityList.acceptPacket(packet);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
switch (packet.constructor.type) {
|
||||
case 'EntityListUpdateEntity':
|
||||
this.entityList.acceptPacket(packet);
|
||||
break;
|
||||
case 'TilesUpdate':
|
||||
this.tiles.acceptPacket(packet);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
addEntity(entity) {
|
||||
this.entityList.addEntity(entity);
|
||||
}
|
||||
|
@ -55,6 +39,7 @@ export default (latus) => class Layer extends decorate(JsonResource) {
|
|||
}
|
||||
|
||||
cleanPackets() {
|
||||
super.cleanPackets();
|
||||
this.entityList.cleanPackets();
|
||||
this.tiles.cleanPackets();
|
||||
}
|
||||
|
@ -131,19 +116,6 @@ export default (latus) => class Layer extends decorate(JsonResource) {
|
|||
this.emit('tileDataChanged');
|
||||
}
|
||||
|
||||
packets(informed) {
|
||||
const packets = [];
|
||||
const entityListPackets = this.entityList.packets(informed);
|
||||
for (let i = 0; i < entityListPackets.length; i++) {
|
||||
packets.push(entityListPackets[i]);
|
||||
}
|
||||
const tilesPackets = this.tiles.packets(informed);
|
||||
for (let i = 0; i < tilesPackets.length; i++) {
|
||||
packets.push(tilesPackets[i]);
|
||||
}
|
||||
return packets;
|
||||
}
|
||||
|
||||
removeEntity(entity) {
|
||||
this.entityList.removeEntity(entity);
|
||||
}
|
||||
|
@ -159,8 +131,17 @@ export default (latus) => class Layer extends decorate(JsonResource) {
|
|||
this.tileEntities[index].splice(entityIndex, 1);
|
||||
}
|
||||
|
||||
get s13nId() {
|
||||
return this.#s13nId;
|
||||
}
|
||||
|
||||
set s13nId(s13nId) {
|
||||
this.#s13nId = s13nId;
|
||||
}
|
||||
|
||||
setEntityList(entityList) {
|
||||
if (this.entityList) {
|
||||
this.stopSynchronizing(this.entityList);
|
||||
Object.values(this.entityList.entities).forEach((entity) => {
|
||||
this.onEntityRemovedFromLayer(entity);
|
||||
});
|
||||
|
@ -173,6 +154,7 @@ export default (latus) => class Layer extends decorate(JsonResource) {
|
|||
Object.values(this.entityList.entities).forEach((entity) => {
|
||||
this.onEntityAddedToLayer(entity);
|
||||
});
|
||||
this.startSynchronizing(this.entityList);
|
||||
}
|
||||
|
||||
setTileAt(position, tile) {
|
||||
|
@ -181,10 +163,12 @@ export default (latus) => class Layer extends decorate(JsonResource) {
|
|||
|
||||
setTiles(tiles) {
|
||||
if (this.tiles) {
|
||||
this.stopSynchronizing(this.tiles);
|
||||
this.tiles.off('dataChanged', this.onTileDataChanged);
|
||||
}
|
||||
this.tiles = tiles;
|
||||
this.tiles.on('dataChanged', this.onTileDataChanged, this);
|
||||
this.startSynchronizing(tiles);
|
||||
}
|
||||
|
||||
tick(elapsed) {
|
||||
|
@ -228,3 +212,4 @@ export default (latus) => class Layer extends decorate(JsonResource) {
|
|||
// }
|
||||
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,28 +1,19 @@
|
|||
import {compose, EventEmitter} from '@latus/core';
|
||||
import {JsonResource} from '@avocado/resource';
|
||||
import {Synchronized} from '@avocado/s13n';
|
||||
|
||||
export default (latus) => {
|
||||
const decorate = compose(
|
||||
EventEmitter,
|
||||
Synchronized(latus),
|
||||
);
|
||||
|
||||
export default (latus) => class Layers extends decorate(JsonResource) {
|
||||
return class Layers extends decorate(JsonResource) {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.layers = [];
|
||||
}
|
||||
|
||||
acceptPacket(packet) {
|
||||
if ('LayersUpdateLayer' === packet.constructor.type) {
|
||||
for (let i = 0; i < packet.data.length; i++) {
|
||||
const {layerIndex, layerPackets} = packet.data[i];
|
||||
for (let j = 0; j < layerPackets.length; j++) {
|
||||
this.layers[layerIndex].acceptPacket(layerPackets[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addEntityToLayer(entity, layerIndex) {
|
||||
const layer = this.layers[layerIndex];
|
||||
if (!layer) {
|
||||
|
@ -32,6 +23,9 @@ export default (latus) => class Layers extends decorate(JsonResource) {
|
|||
}
|
||||
|
||||
addLayer(layer) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
layer.s13nId = this.layers.length;
|
||||
this.startSynchronizing(layer);
|
||||
layer.on('entityAdded', this.onEntityAddedToLayers, this);
|
||||
layer.on('entityRemoved', this.onEntityRemovedFromLayers, this);
|
||||
this.layers.push(layer);
|
||||
|
@ -39,6 +33,7 @@ export default (latus) => class Layers extends decorate(JsonResource) {
|
|||
}
|
||||
|
||||
cleanPackets() {
|
||||
super.cleanPackets();
|
||||
for (let i = 0; i < this.layers.length; i++) {
|
||||
this.layers[i].cleanPackets();
|
||||
}
|
||||
|
@ -96,27 +91,6 @@ export default (latus) => class Layers extends decorate(JsonResource) {
|
|||
this.emit('entityRemoved', entity);
|
||||
}
|
||||
|
||||
packets(informed) {
|
||||
const packets = [];
|
||||
const updates = [];
|
||||
for (let i = 0; i < this.layers.length; i++) {
|
||||
const layerPackets = this.layers[i].packets(informed);
|
||||
if (layerPackets.length > 0) {
|
||||
updates.push({
|
||||
layerIndex: i,
|
||||
layerPackets,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (updates.length > 0) {
|
||||
packets.push([
|
||||
'LayersUpdateLayer',
|
||||
updates,
|
||||
]);
|
||||
}
|
||||
return packets;
|
||||
}
|
||||
|
||||
removeAllLayers() {
|
||||
for (let i = 0; i < this.layers.length; i++) {
|
||||
this.removeLayer(this.layers[i]);
|
||||
|
@ -140,6 +114,7 @@ export default (latus) => class Layers extends decorate(JsonResource) {
|
|||
layer.off('entityRemoved', this.onEntityRemovedFromLayers);
|
||||
this.layers.splice(index, 1);
|
||||
this.emit('layerRemoved', layer);
|
||||
this.stopSynchronizing(layer);
|
||||
}
|
||||
|
||||
tick(elapsed) {
|
||||
|
@ -169,3 +144,4 @@ export default (latus) => class Layers extends decorate(JsonResource) {
|
|||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
|
|
@ -3,17 +3,17 @@ import {JsonResource} from '@avocado/resource';
|
|||
import {Synchronized} from '@avocado/s13n';
|
||||
import {compose, EventEmitter} from '@latus/core';
|
||||
|
||||
let s13nId = 1;
|
||||
|
||||
export default (latus) => {
|
||||
const decorate = compose(
|
||||
EventEmitter,
|
||||
Synchronized,
|
||||
Vector.Mixin('size', 'width', 'height', {
|
||||
default: [0, 0],
|
||||
}),
|
||||
Synchronized(latus),
|
||||
);
|
||||
|
||||
let s13nId = 1;
|
||||
|
||||
export default (latus) => class Room extends decorate(JsonResource) {
|
||||
return class Room extends decorate(JsonResource) {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
@ -22,25 +22,10 @@ export default (latus) => class Room extends decorate(JsonResource) {
|
|||
this.setLayers(new Layers());
|
||||
}
|
||||
|
||||
acceptPacket(packet) {
|
||||
// Layer updates.
|
||||
if ('RoomUpdateLayers' === packet.constructor.type) {
|
||||
const {layersPackets} = packet.data;
|
||||
for (let i = 0; i < layersPackets.length; ++i) {
|
||||
this.layers.acceptPacket(layersPackets[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addEntityToLayer(entity, layerIndex = 0) {
|
||||
this.layers.addEntityToLayer(entity, layerIndex);
|
||||
}
|
||||
|
||||
cleanPackets() {
|
||||
super.cleanPackets();
|
||||
this.layers.cleanPackets();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
this.layers.destroy();
|
||||
|
@ -88,32 +73,13 @@ export default (latus) => class Room extends decorate(JsonResource) {
|
|||
this.emit('entityRemoved', entity);
|
||||
}
|
||||
|
||||
packets(informed) {
|
||||
const payload = [];
|
||||
// Layer updates.
|
||||
const layersPackets = this.layers.packets(informed);
|
||||
if (layersPackets.length > 0) {
|
||||
payload.push([
|
||||
'RoomUpdateLayers',
|
||||
{
|
||||
layersPackets,
|
||||
},
|
||||
]);
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
packetsAreIdempotent() {
|
||||
return false;
|
||||
}
|
||||
|
||||
removeEntityFromLayer(entity, layerIndex) {
|
||||
this.layers.removeEntityFromLayer(entity, layerIndex);
|
||||
}
|
||||
|
||||
setLayers(layers) {
|
||||
if (this.layers) {
|
||||
this.stopSynchronizing(this.layers);
|
||||
const entities = Object.values(this.layers.entities);
|
||||
for (let i = 0; i < entities.length; i++) {
|
||||
this.onEntityRemovedFromRoom(entities[i]);
|
||||
|
@ -128,9 +94,10 @@ export default (latus) => class Room extends decorate(JsonResource) {
|
|||
for (let i = 0; i < entities.length; i++) {
|
||||
this.onEntityAddedToRoom(entities[i]);
|
||||
}
|
||||
this.startSynchronizing(this.layers);
|
||||
}
|
||||
|
||||
s13nId() {
|
||||
get s13nId() {
|
||||
return this._s13nId;
|
||||
}
|
||||
|
||||
|
@ -157,3 +124,4 @@ export default (latus) => class Room extends decorate(JsonResource) {
|
|||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import {Rectangle, Vector} from '@avocado/math';
|
||||
import {Class, compose, EventEmitter} from '@latus/core';
|
||||
import {Synchronized} from '@avocado/s13n';
|
||||
|
||||
export default (latus) => {
|
||||
const decorate = compose(
|
||||
EventEmitter,
|
||||
Vector.Mixin('size', 'width', 'height', {
|
||||
default: [0, 0],
|
||||
}),
|
||||
Synchronized(latus),
|
||||
);
|
||||
|
||||
export default () => class Tiles extends decorate(Class) {
|
||||
return class Tiles extends decorate(Class) {
|
||||
|
||||
constructor({data, size} = {}) {
|
||||
super();
|
||||
|
@ -48,7 +50,7 @@ export default () => class Tiles extends decorate(Class) {
|
|||
return this.width * position[1] + position[0];
|
||||
}
|
||||
|
||||
packets() {
|
||||
packetsFor() {
|
||||
return this._packets;
|
||||
}
|
||||
|
||||
|
@ -120,3 +122,4 @@ export default () => class Tiles extends decorate(Class) {
|
|||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
|
|
@ -28,6 +28,7 @@ export default () => class Followed extends Trait {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
const {room} = this.entity;
|
||||
if (room) {
|
||||
room.off('sizeChanged', this.onRoomSizeChanged);
|
||||
|
|
|
@ -23,6 +23,7 @@ export default () => class Layered extends Trait {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
this.detachFromLayer(this.entity.layer);
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ export default () => class TileEntity extends decorate(Trait) {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
this.removeTileEntity(this.entity.tileIndex);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
import {JsonResource} from '@avocado/resource';
|
||||
import {Synchronized} from '@avocado/s13n';
|
||||
import {compose} from '@latus/core';
|
||||
|
||||
const decorate = compose(
|
||||
Synchronized,
|
||||
);
|
||||
|
||||
export default class Trait extends decorate(JsonResource) {
|
||||
export default class Trait extends JsonResource {
|
||||
|
||||
#markedAsDirty = true;
|
||||
|
||||
|
@ -69,8 +63,9 @@ export default class Trait extends decorate(JsonResource) {
|
|||
return [];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
destroy() {}
|
||||
destroy() {
|
||||
this.#memoizedListeners = undefined;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
hooks() {
|
||||
|
@ -120,14 +115,18 @@ export default class Trait extends decorate(JsonResource) {
|
|||
return {};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this, no-unused-vars
|
||||
packets(informed) {
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
packetsFor() {
|
||||
return [];
|
||||
}
|
||||
|
||||
static get resourceId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
packetsAreIdempotent() {
|
||||
return false;
|
||||
get s13nId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
stateDifferences() {
|
||||
|
@ -145,6 +144,10 @@ export default class Trait extends decorate(JsonResource) {
|
|||
return differences;
|
||||
}
|
||||
|
||||
toNetwork() {
|
||||
return this.toJSON();
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
params: this.params,
|
||||
|
|
Loading…
Reference in New Issue
Block a user