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

View File

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

View File

@ -1,8 +1,7 @@
import {compose, EventEmitter} from '@avocado/core';
import {QuadTree, Rectangle, Vector} from '@avocado/math';
import {EntityCreatePacket} from '../packets/entity-create.packet';
import {EntityRemovePacket} from '../packets/entity-remove.packet';
import {EntityListUpdateEntityPacket} from '../packets/entity-list-update-entity.packet';
import {Entity} from '../entity.synchronized';
const decorate = compose(
@ -30,6 +29,18 @@ export class EntityList extends decorate(class {}) {
}
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) {
@ -58,6 +69,12 @@ export class EntityList extends decorate(class {}) {
this.emit('entityAdded', entity);
}
cleanPackets() {
for (let i = 0; i < this._flatEntities.length; i++) {
this._flatEntities[i].cleanPackets();
}
}
destroy() {
for (let i = 0; i < this._flatEntities.length; i++) {
this._flatEntities[i].destroy();
@ -86,6 +103,20 @@ export class EntityList extends decorate(class {}) {
packets(informed) {
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;
}

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

View File

@ -2,7 +2,7 @@ import {compose, EventEmitter} from '@avocado/core';
import {Vector} from '@avocado/math';
import {Trait} from '../trait';
import {TraitPositionedPacket} from '../packets/trait-positioned.packet';
import {TraitUpdatePositionedPositionPacket} from '../packets/trait-update-positioned-position.packet';
const decorate = compose(
EventEmitter,
@ -31,9 +31,10 @@ 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.x >> 2;
const y = this.state.y >> 2;
const x = this.state.x;
const y = this.state.y;
this._position = [x, y];
this._positionChanged = false;
this.entity.position[0] = x;
this.entity.position[1] = y;
if (AVOCADO_CLIENT) {
@ -44,6 +45,10 @@ export class Positioned extends decorate(Trait) {
}
}
cleanPackets() {
this._positionChanged = false;
}
destroy() {
this.off('_positionChanged', this.on_positionChanged);
if (AVOCADO_CLIENT) {
@ -52,9 +57,8 @@ 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;
if (packet instanceof TraitUpdatePositionedPositionPacket) {
[this.serverX, this.serverY] = packet.data.position;
}
}
@ -62,8 +66,9 @@ export class Positioned extends decorate(Trait) {
this.entity.position[0] = newPosition[0];
this.entity.position[1] = newPosition[1];
if (AVOCADO_SERVER) {
this.state.x = newPosition[0] << 2;
this.state.y = newPosition[1] << 2;
this.state.x = newPosition[0];
this.state.y = newPosition[1];
this._positionChanged = true;
}
this.entity.emit('positionChanged', oldPosition, newPosition);
}
@ -72,19 +77,17 @@ export class Positioned extends decorate(Trait) {
this.serverPositionDirty = true;
}
packetsForUpdate() {
packets(informed) {
const packets = [];
if (this.isDirty) {
if (this._positionChanged) {
// 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.
// The world bounds will (should!) keep things *eventually* correct.
const x = Math.max(0, this.state.x);
const y = Math.max(0, this.state.y);
const packed = (y << 16) | (x << 0);
packets.push(new TraitPositionedPacket({
position: packed,
packets.push(new TraitUpdatePositionedPositionPacket({
position: [x, y],
}, this.entity));
this.makeClean();
}
return packets;
}

View File

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

View File

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

View File

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

View File

@ -1,5 +1,10 @@
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 {ShapeList} from '@avocado/physics';
@ -50,6 +55,9 @@ export class Layer extends decorate(class {}) {
if (packet instanceof LayerUpdateTilesetUriPacket) {
this.tilesetUri = packet.data;
}
if (packet instanceof EntityListUpdateEntityPacket) {
this.entityList.acceptPacket(packet);
}
}
addEntity(entity) {
@ -100,6 +108,7 @@ export class Layer extends decorate(class {}) {
cleanPackets() {
this._tilesetUriChanged = false;
this.entityList.cleanPackets();
}
destroy() {
@ -202,6 +211,10 @@ export class Layer extends decorate(class {}) {
if (this._tilesetUriChanged) {
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;
}

View File

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