flow: trait updates!

This commit is contained in:
cha0s 2019-09-30 01:36:02 -05:00
parent ddd9b1c8e8
commit 12a70ee94e
15 changed files with 230 additions and 40 deletions

View File

@ -38,3 +38,4 @@
- ❌ Target trait updates beyond just visible; e.g. others don't need to know - ❌ Target trait updates beyond just visible; e.g. others don't need to know
about inventory change about inventory change
- ❌ afterDestructionTickers shouldn't exist... destruction should be deferred - ❌ afterDestructionTickers shouldn't exist... destruction should be deferred
- ❌ Remove position pack check from trait and add to Vector

View File

@ -13,6 +13,7 @@ import {Resource} from '@avocado/resource';
import {EntityCreatePacket} from './packets/entity-create.packet'; import {EntityCreatePacket} from './packets/entity-create.packet';
import {hasTrait, lookupTrait} from './trait/registry'; import {hasTrait, lookupTrait} from './trait/registry';
import {EntityUpdateTraitPacket} from './packets/entity-update-trait.packet';
const debug = D('@avocado:entity:traits'); const debug = D('@avocado:entity:traits');
@ -107,6 +108,7 @@ export class Entity extends decorate(Resource) {
this.tick = this.tick.bind(this); this.tick = this.tick.bind(this);
// Fast props. // Fast props.
this.numericUid = numericUid++; this.numericUid = numericUid++;
this.instanceUuid = this.numericUid;
this.position = [0, 0]; this.position = [0, 0];
this.room = null; this.room = null;
this.visibleAabb = [0, 0, 0, 0]; this.visibleAabb = [0, 0, 0, 0];
@ -117,6 +119,15 @@ export class Entity extends decorate(Resource) {
} }
acceptPacket(packet) { acceptPacket(packet) {
if (packet instanceof EntityUpdateTraitPacket) {
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]);
}
}
}
} }
addTrait(type, json = {}) { addTrait(type, json = {}) {
@ -194,6 +205,12 @@ export class Entity extends decorate(Resource) {
return Object.keys(this._traits); return Object.keys(this._traits);
} }
cleanPackets() {
for (let i = 0; i < this._traitsFlat.length; i++) {
this._traitsFlat[i].cleanPackets();
}
}
fromJSON(json) { fromJSON(json) {
super.fromJSON(json); super.fromJSON(json);
if (json.instanceUuid) { if (json.instanceUuid) {
@ -262,6 +279,25 @@ export class Entity extends decorate(Resource) {
packets(informed) { packets(informed) {
const packets = []; const packets = [];
const updates = [];
for (const type in this._traits) {
const traitPackets = this._traits[type].packets(informed);
if (traitPackets.length > 0) {
updates.push({
type,
packets: traitPackets,
});
}
}
if (updates.length > 0) {
packets.push(new EntityUpdateTraitPacket({
synchronized: {
id: 0,
type: 0,
},
traits: updates,
}));
}
return packets; return packets;
} }
@ -335,6 +371,10 @@ export class Entity extends decorate(Resource) {
types.forEach((type) => this.removeTrait(type)); types.forEach((type) => this.removeTrait(type));
} }
synchronizationId() {
return this.instanceUuid;
}
tick(elapsed) { tick(elapsed) {
for (let i = 0; i < this._traitTickers.length; i++) { for (let i = 0; i < this._traitTickers.length; i++) {
this._traitTickers[i](elapsed); this._traitTickers[i](elapsed);
@ -348,7 +388,7 @@ export class Entity extends decorate(Resource) {
} }
return { return {
...super.toJSON(), ...super.toJSON(),
instanceUuid: this.numericUid, instanceUuid: this.instanceUuid,
traits: json, traits: json,
}; };
} }

View File

@ -4,6 +4,10 @@ export {EntityCreatePacket} from './packets/entity-create.packet';
export {EntityRemovePacket} from './packets/entity-remove.packet'; export {EntityRemovePacket} from './packets/entity-remove.packet';
export {EntityPacket} from './packets/entity.packet'; export {EntityPacket} from './packets/entity.packet';
export {
EntityListUpdateEntityPacket,
} from './packets/entity-list-update-entity.packet';
export {EntityList, EntityListView} from './list'; export {EntityList, EntityListView} from './list';
export { export {

View File

@ -1,8 +1,7 @@
import {compose, EventEmitter} from '@avocado/core'; import {compose, EventEmitter} from '@avocado/core';
import {QuadTree, Rectangle, Vector} from '@avocado/math'; import {QuadTree, Rectangle, Vector} from '@avocado/math';
import {EntityCreatePacket} from '../packets/entity-create.packet'; import {EntityListUpdateEntityPacket} from '../packets/entity-list-update-entity.packet';
import {EntityRemovePacket} from '../packets/entity-remove.packet';
import {Entity} from '../entity.synchronized'; import {Entity} from '../entity.synchronized';
const decorate = compose( const decorate = compose(
@ -30,6 +29,18 @@ export class EntityList extends decorate(class {}) {
} }
acceptPacket(packet) { acceptPacket(packet) {
if (packet instanceof EntityListUpdateEntityPacket) {
for (let i = 0; i < packet.data.length; i++) {
const {uuid, packets} = packet.data[i];
// WHY ?
if (!this._entities[uuid]) {
continue;
}
for (let j = 0; j < packets.length; j++) {
this._entities[uuid].acceptPacket(packets[j]);
}
}
}
} }
addEntity(entity) { addEntity(entity) {
@ -58,6 +69,12 @@ export class EntityList extends decorate(class {}) {
this.emit('entityAdded', entity); this.emit('entityAdded', entity);
} }
cleanPackets() {
for (let i = 0; i < this._flatEntities.length; i++) {
this._flatEntities[i].cleanPackets();
}
}
destroy() { destroy() {
for (let i = 0; i < this._flatEntities.length; i++) { for (let i = 0; i < this._flatEntities.length; i++) {
this._flatEntities[i].destroy(); this._flatEntities[i].destroy();
@ -86,6 +103,20 @@ export class EntityList extends decorate(class {}) {
packets(informed) { packets(informed) {
const packets = []; const packets = [];
const updates = [];
for (const uuid in this._entities) {
const entity = this._entities[uuid];
const entityPackets = entity.packets(informed);
if (entityPackets.length > 0) {
updates.push({
uuid: entity.instanceUuid,
packets: entityPackets,
});
}
}
if (updates.length > 0) {
packets.push(new EntityListUpdateEntityPacket(updates));
}
return packets; return packets;
} }

View File

@ -0,0 +1,35 @@
import {BundlePacket, Packet} from '@avocado/net';
export class EntityListUpdateEntityPacket extends Packet {
static pack(packet) {
const data = packet.data[1];
for (let i = 0; i < data.length; i++) {
data[i].packets = BundlePacket.packPacket(
new BundlePacket(data[i].packets)
);
}
return super.pack(packet);
}
static get schema() {
return {
...super.schema,
data: [
{
uuid: 'uint32',
packets: 'buffer',
},
],
};
}
static unpack(packet) {
const data = super.unpack(packet);
for (let i = 0; i < data.length; i++) {
data[i].packets = BundlePacket.unpack(data[i].packets);
}
return data;
}
}

View File

@ -0,0 +1,34 @@
import {BundlePacket, SynchronizedUpdatePacket} from '@avocado/net';
export class EntityUpdateTraitPacket extends SynchronizedUpdatePacket {
static pack(packet) {
const data = packet.data[1];
for (let i = 0; i < data.traits.length; i++) {
data.traits[i].packets = BundlePacket.packPacket(
new BundlePacket(data.traits[i].packets)
);
}
return super.pack(packet);
}
static get synchronizationSchema() {
return {
traits: [
{
type: 'string',
packets: 'buffer',
},
],
};
}
static unpack(packet) {
const data = super.unpack(packet);
for (let i = 0; i < data.traits.length; i++) {
data.traits[i].packets = BundlePacket.unpack(data.traits[i].packets);
}
return data;
}
}

View File

@ -1,11 +0,0 @@
import {EntityPacket} from './entity.packet';
export class TraitPositionedPacket extends EntityPacket {
static get schema() {
const schema = super.schema;
schema.data.position = 'uint32';
return schema;
}
}

View File

@ -0,0 +1,27 @@
import {Vector} from '@avocado/math';
import {Packet} from '@avocado/net';
export class TraitUpdatePositionedPositionPacket extends Packet {
static pack(packet) {
const data = packet.data[1];
data.position = Vector.packToUint32(data.position);
return super.pack(packet);
}
static get schema() {
return {
...super.schema,
data: {
position: 'uint32',
},
};
}
static unpack(packet) {
const data = super.unpack(packet);
data.position = Vector.unpackFromUint32(data.position);
return data;
}
}

View File

@ -1,7 +1,9 @@
import {compose, merge, mergeDiff, Property} from '@avocado/core'; import {compose, merge, mergeDiff, Property} from '@avocado/core';
import {Vector} from '@avocado/math'; import {Vector} from '@avocado/math';
import {SynchronizedMixin} from '@avocado/net';
const decorate = compose( const decorate = compose(
SynchronizedMixin,
); );
export class Trait extends decorate(class {}) { export class Trait extends decorate(class {}) {
@ -25,6 +27,9 @@ export class Trait extends decorate(class {}) {
acceptPacket(packet) { acceptPacket(packet) {
} }
cleanPackets() {
}
static contextType() { static contextType() {
return {}; return {};
} }
@ -106,6 +111,10 @@ export class Trait extends decorate(class {}) {
return packets; return packets;
} }
packetsAreIdempotent() {
return false;
}
toJSON() { toJSON() {
return { return {
params: this.params, params: this.params,

View File

@ -2,7 +2,7 @@ import {compose, EventEmitter} from '@avocado/core';
import {Vector} from '@avocado/math'; import {Vector} from '@avocado/math';
import {Trait} from '../trait'; import {Trait} from '../trait';
import {TraitPositionedPacket} from '../packets/trait-positioned.packet'; import {TraitUpdatePositionedPositionPacket} from '../packets/trait-update-positioned-position.packet';
const decorate = compose( const decorate = compose(
EventEmitter, EventEmitter,
@ -31,9 +31,10 @@ export class Positioned extends decorate(Trait) {
constructor(entity, params, state) { constructor(entity, params, state) {
super(entity, params, state); super(entity, params, state);
this.on('_positionChanged', this.on_positionChanged, this); this.on('_positionChanged', this.on_positionChanged, this);
const x = this.state.x >> 2; const x = this.state.x;
const y = this.state.y >> 2; const y = this.state.y;
this._position = [x, y]; this._position = [x, y];
this._positionChanged = false;
this.entity.position[0] = x; this.entity.position[0] = x;
this.entity.position[1] = y; this.entity.position[1] = y;
if (AVOCADO_CLIENT) { if (AVOCADO_CLIENT) {
@ -44,6 +45,10 @@ export class Positioned extends decorate(Trait) {
} }
} }
cleanPackets() {
this._positionChanged = false;
}
destroy() { destroy() {
this.off('_positionChanged', this.on_positionChanged); this.off('_positionChanged', this.on_positionChanged);
if (AVOCADO_CLIENT) { if (AVOCADO_CLIENT) {
@ -52,9 +57,8 @@ export class Positioned extends decorate(Trait) {
} }
acceptPacket(packet) { acceptPacket(packet) {
if (packet instanceof TraitPositionedPacket) { if (packet instanceof TraitUpdatePositionedPositionPacket) {
this.serverX = (packet.data.position & 0xFFFF) >> 2; [this.serverX, this.serverY] = packet.data.position;
this.serverY = (packet.data.position >> 16) >> 2;
} }
} }
@ -62,8 +66,9 @@ export class Positioned extends decorate(Trait) {
this.entity.position[0] = newPosition[0]; this.entity.position[0] = newPosition[0];
this.entity.position[1] = newPosition[1]; this.entity.position[1] = newPosition[1];
if (AVOCADO_SERVER) { if (AVOCADO_SERVER) {
this.state.x = newPosition[0] << 2; this.state.x = newPosition[0];
this.state.y = newPosition[1] << 2; this.state.y = newPosition[1];
this._positionChanged = true;
} }
this.entity.emit('positionChanged', oldPosition, newPosition); this.entity.emit('positionChanged', oldPosition, newPosition);
} }
@ -72,19 +77,17 @@ export class Positioned extends decorate(Trait) {
this.serverPositionDirty = true; this.serverPositionDirty = true;
} }
packetsForUpdate() { packets(informed) {
const packets = []; const packets = [];
if (this.isDirty) { if (this._positionChanged) {
// Physics slop can end us up with negatives. Don't allow them in the // Physics slop can end us up with negatives. Don't allow them in the
// packed representation, even though it means a slight loss in accuracy. // packed representation, even though it means a slight loss in accuracy.
// The world bounds will (should!) keep things *eventually* correct. // The world bounds will (should!) keep things *eventually* correct.
const x = Math.max(0, this.state.x); const x = Math.max(0, this.state.x);
const y = Math.max(0, this.state.y); const y = Math.max(0, this.state.y);
const packed = (y << 16) | (x << 0); packets.push(new TraitUpdatePositionedPositionPacket({
packets.push(new TraitPositionedPacket({ position: [x, y],
position: packed,
}, this.entity)); }, this.entity));
this.makeClean();
} }
return packets; return packets;
} }

View File

@ -115,8 +115,8 @@ export class Spawner extends decorate(Trait) {
if (!json.traits.positioned.state) { if (!json.traits.positioned.state) {
json.traits.positioned.state = {}; json.traits.positioned.state = {};
} }
json.traits.positioned.state.x = position[0] << 2; json.traits.positioned.state.x = position[0];
json.traits.positioned.state.y = position[1] << 2; json.traits.positioned.state.y = position[1];
return this.entity.spawn(key, json); return this.entity.spawn(key, json);
}, },

View File

@ -32,10 +32,8 @@ export class BundlePacket extends Packet {
packedPacket.copy(buffer, caret, 0); packedPacket.copy(buffer, caret, 0);
caret += packedPacket.length; caret += packedPacket.length;
} }
return this.builder.encode({ packet.data[1] = buffer;
_id: packet.data[0], return super.pack(packet);
data: buffer,
});
} }
static get schema() { static get schema() {
@ -46,16 +44,19 @@ export class BundlePacket extends Packet {
} }
static unpack(packet) { static unpack(packet) {
const {data} = this.builder.decode(packet); const data = super.unpack(packet)
const packets = []; const packets = [];
let caret = 0; let caret = 0;
while (caret < data.length) { while (caret < data.length) {
// Read packed length. // Read packed length.
const length = data.readUInt32LE(caret); const length = data.readUInt32LE(caret);
caret += 4; caret += 4;
// Read packed data. // Read packed data. TODO: manual blitting sucks...
const packedPacket = Buffer.from(data.buffer, caret, length); const packedPacket = Buffer.allocUnsafe(length);
caret += length; let i = 0;
while (i < length) {
packedPacket.writeUInt8(data.readUInt8(caret++), i++);
}
// Lookup packet. // Lookup packet.
const packetId = packedPacket.readUInt8(0); const packetId = packedPacket.readUInt8(0);
const Packet = packetFromId(packetId); const Packet = packetFromId(packetId);

View File

@ -17,7 +17,9 @@ export function SynchronizedMixin(Superclass) {
this.fromJSON(json); this.fromJSON(json);
} }
packets(informed) {} packets(informed) {
return [];
}
packetsAreIdempotent() { packetsAreIdempotent() {
return true; return true;

View File

@ -1,5 +1,10 @@
import {compose, EventEmitter, Property} from '@avocado/core'; import {compose, EventEmitter, Property} from '@avocado/core';
import {Entity, EntityCreatePacket, EntityList} from '@avocado/entity'; import {
Entity,
EntityCreatePacket,
EntityList,
EntityListUpdateEntityPacket,
} from '@avocado/entity';
import {Vector} from '@avocado/math'; import {Vector} from '@avocado/math';
import {ShapeList} from '@avocado/physics'; import {ShapeList} from '@avocado/physics';
@ -50,6 +55,9 @@ export class Layer extends decorate(class {}) {
if (packet instanceof LayerUpdateTilesetUriPacket) { if (packet instanceof LayerUpdateTilesetUriPacket) {
this.tilesetUri = packet.data; this.tilesetUri = packet.data;
} }
if (packet instanceof EntityListUpdateEntityPacket) {
this.entityList.acceptPacket(packet);
}
} }
addEntity(entity) { addEntity(entity) {
@ -100,6 +108,7 @@ export class Layer extends decorate(class {}) {
cleanPackets() { cleanPackets() {
this._tilesetUriChanged = false; this._tilesetUriChanged = false;
this.entityList.cleanPackets();
} }
destroy() { destroy() {
@ -202,6 +211,10 @@ export class Layer extends decorate(class {}) {
if (this._tilesetUriChanged) { if (this._tilesetUriChanged) {
packets.push(new LayerUpdateTilesetUriPacket(this.tilesetUri)); packets.push(new LayerUpdateTilesetUriPacket(this.tilesetUri));
} }
const entityListPackets = this.entityList.packets(informed);
for (let i = 0; i < entityListPackets.length; i++) {
packets.push(entityListPackets[i]);
}
return packets; return packets;
} }

View File

@ -14,6 +14,7 @@ export class LayersUpdateLayerPacket extends Packet {
static get schema() { static get schema() {
return { return {
...super.schema,
data: [ data: [
{ {
layerIndex: 'uint8', layerIndex: 'uint8',