flow: massive sync update

This commit is contained in:
cha0s 2019-09-29 13:19:57 -05:00
parent dfd2dc2a41
commit ddd9b1c8e8
26 changed files with 482 additions and 309 deletions

View File

@ -117,10 +117,6 @@ export class Entity extends decorate(Resource) {
}
acceptPacket(packet) {
for (let i = 0; i < this._traitsAcceptingPackets.length; i++) {
const instance = this._traitsAcceptingPackets[i];
instance.acceptPacket(packet);
}
}
addTrait(type, json = {}) {
@ -200,6 +196,9 @@ export class Entity extends decorate(Resource) {
fromJSON(json) {
super.fromJSON(json);
if (json.instanceUuid) {
this.instanceUuid = json.instanceUuid;
}
this.addTraits(json.traits);
return this;
}
@ -261,20 +260,8 @@ export class Entity extends decorate(Resource) {
return mergeDiff(this._json, this.toJSON());
}
packetsForUpdate(force = false) {
packets(informed) {
const packets = [];
if (force) {
const packet = new EntityCreatePacket(this.mergeDiff(), this);
packets.push(packet);
}
else {
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]);
}
}
}
return packets;
}
@ -361,6 +348,7 @@ export class Entity extends decorate(Resource) {
}
return {
...super.toJSON(),
instanceUuid: this.numericUid,
traits: json,
};
}

View File

@ -30,14 +30,6 @@ export class EntityList extends decorate(class {}) {
}
acceptPacket(packet) {
if (packet instanceof EntityCreatePacket) {
if (!this.findEntity(packet.data.uuid)) {
Entity.loadOrInstance(packet.data).then((entity) => {
entity.instanceUuid = packet.data.uuid;
this.addEntity(entity);
});
}
}
}
addEntity(entity) {
@ -81,7 +73,7 @@ export class EntityList extends decorate(class {}) {
fromJSON(json) {
for (let i = 0; i < json.length; i++) {
const entityJSON = json[i];
if ('undefined' !== entityJSON.uri) {
if (entityJSON.uri) {
Entity.read(entityJSON.uri).then((readJSON) => {
this.addEntity(new Entity(readJSON, entityJSON));
});
@ -92,28 +84,8 @@ export class EntityList extends decorate(class {}) {
}
}
packetsForUpdate(force = false) {
packets(informed) {
const packets = [];
if (!force) {
for (let i = 0; i < this._entitiesJustAdded.length; i++) {
const entity = this._entitiesJustAdded[i];
packets.push(new EntityCreatePacket(entity.mergeDiff(), entity));
}
this._entitiesJustAdded = [];
}
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]);
}
}
if (!force) {
for (let i = 0; i < this._entitiesJustRemoved.length; i++) {
const entity = this._entitiesJustRemoved[i];
packets.push(new EntityRemovePacket({}, entity));
}
this._entitiesJustRemoved = [];
}
return packets;
}
@ -166,6 +138,14 @@ export class EntityList extends decorate(class {}) {
}
}
toJSON() {
const json = [];
for (let i = 0; i < this._flatEntities.length; i++) {
json.push(this._flatEntities[i].mergeDiff());
}
return json;
}
visibleEntities(query) {
const entities = [];
const entitiesChecked = [];

View File

@ -22,6 +22,13 @@ export class Trait extends decorate(class {}) {
}
}
acceptPacket(packet) {
}
static contextType() {
return {};
}
createTraitPacketUpdates(Packet) {
const packets = [];
if (this.isDirty) {
@ -31,6 +38,25 @@ export class Trait extends decorate(class {}) {
return packets;
}
static defaultJSON() {
return {
params: this.defaultParams(),
state: this.defaultState(),
};
}
static defaultParams() {
return {};
}
static defaultState() {
return {};
}
static dependencies() {
return [];
}
destroy() {}
hooks() {
@ -75,6 +101,11 @@ export class Trait extends decorate(class {}) {
return {};
}
packets(informed) {
const packets = [];
return packets;
}
toJSON() {
return {
params: this.params,
@ -86,29 +117,6 @@ export class Trait extends decorate(class {}) {
return value;
}
static contextType() {
return {};
}
static defaultJSON() {
return {
params: this.defaultParams(),
state: this.defaultState(),
};
}
static defaultParams() {
return {};
}
static defaultState() {
return {};
}
static dependencies() {
return [];
}
}
export function StateProperty(key, meta = {}) {

View File

@ -1,29 +0,0 @@
import * as Matrix from './matrix'
describe 'Matrix', ->
it 'can inspect size', ->
matrix = [[0, 0], [0, 0], [0, 0], [0, 0]]
expect(Matrix.size matrix).toBe 8
expect(Matrix.sizeVector matrix).toEqual [2, 4]
it 'can test equality', ->
l = [[0, 0], [0, 0], [0, 0], [0, 0]]
r = [[0, 0], [0, 0], [0, 0], [0, 0]]
expect(Matrix.equals l, r).toBe true
it 'can make deep copies', ->
matrix = [[1], [2], [3]]
matrix2 = Matrix.copy matrix
expect(matrix).toEqual matrix2
matrix[0][0] = 4
expect(matrix).not.toEqual matrix2

View File

@ -1,3 +1,4 @@
import {idFromPacket} from './registry';
import schemapack from 'schemapack';
export class Packet {
@ -20,6 +21,13 @@ export class Packet {
});
}
static packPacket(packet) {
const id = idFromPacket(packet.constructor);
return packet.constructor.pack({
data: [id, packet.data],
});
}
static get schema() {
return {
_id: 'uint8',
@ -32,4 +40,3 @@ export class Packet {
}
}

View File

@ -10,9 +10,7 @@ export class ClientSynchronizer {
this._synchronized = {};
}
acceptPackets(packets) {
for (let i = 0; i < packets.length; i++) {
const packet = packets[i];
acceptPacket(packet) {
if ((packet instanceof SynchronizedCreatePacket)) {
const json = packet.data.spec;
const {id, type} = packet.data.synchronized;
@ -21,7 +19,7 @@ export class ClientSynchronizer {
this._synchronized[type] = {};
}
if (this._synchronized[type][id]) {
this._synchronized[type][id].fromJSON(json);
this._synchronized[type][id].fromNetwork(json);
}
else {
this._synchronized[type][id] = new Synchronized(json);
@ -41,7 +39,6 @@ export class ClientSynchronizer {
this._synchronized[type][id] = null;
}
}
}
addSynchronized(synchronized) {
const type = idFromSynchronized(synchronized.constructor);
@ -55,4 +52,11 @@ export class ClientSynchronizer {
this._synchronized[type][synchronizationId] = synchronized;
}
synchronized(type, id) {
if (!this._synchronized[type]) {
return;
}
return this._synchronized[type][id];
}
}

View File

@ -60,7 +60,7 @@ export class ServerSynchronizer {
id,
type,
},
spec: synchronized.toJSON(),
spec: synchronized.toNetwork(informed),
});
payload.push(packet);
}

View File

@ -13,6 +13,10 @@ export function SynchronizedMixin(Superclass) {
destroy() {}
fromNetwork(json) {
this.fromJSON(json);
}
packets(informed) {}
packetsAreIdempotent() {
@ -38,6 +42,10 @@ export function SynchronizedMixin(Superclass) {
return 0;
}
toNetwork(informed) {
return this.toJSON();
}
}
}

View File

@ -15,8 +15,10 @@ export class Audible extends Trait {
}
hydrate() {
if (AVOCADO_CLIENT) {
this.loadSounds();
}
}
constructor(entity, params, state) {
super(entity, params, state);

View File

@ -80,6 +80,10 @@ export class Animated extends decorate(Trait) {
this.entity.container.removeChild(animationView);
}
hydrate() {
return this.promiseAnimations();
}
jitterFor(key) {
if (!(key in this._animations) || !this._animations[key].jitter) {
return 0;
@ -91,17 +95,7 @@ export class Animated extends decorate(Trait) {
if (this.animationsPromise) {
return;
}
const animationPromises = [];
// Load all animations.
for (const key in this._animations) {
const {uri} = this._animations[key];
const promise = Animation.load(uri).then((animation) => {
// Zip with key to make populating animations and views trivial.
return {animation, key};
});
animationPromises.push(promise);
}
this.animationsPromise = Promise.all(animationPromises);
this.promiseAnimations();
// Store keyed animations.
this.animations = {};
this.animationsPromise.then((animations) => {
@ -154,6 +148,23 @@ export class Animated extends decorate(Trait) {
return this.createTraitPacketUpdates(TraitAnimatedPacket);
}
promiseAnimations() {
if (!this.animationsPromise) {
const animationPromises = [];
// Load all animations.
for (const key in this._animations) {
const {uri} = this._animations[key];
const promise = Animation.load(uri).then((animation) => {
// Zip with key to make populating animations and views trivial.
return {animation, key};
});
animationPromises.push(promise);
}
this.animationsPromise = Promise.all(animationPromises);
}
return this.animationsPromise;
}
setSpriteScale() {
if (!this.animationViews) {
return;

View File

@ -18,8 +18,11 @@ export class LayerView extends Container {
this.tileGeometryViews = [];
// Handle entity add/remove.
layer.on('tileDataChanged', this.onLayerTileDataChanged, this);
this.onLayerTileDataChanged();
layer.on('tileGeometryChanged', this.onLayerTileGeometryChanged, this);
this.onLayerTileGeometryChanged();
layer.on('tilesetChanged', this.onLayerTilesetChanged, this);
this.onLayerTilesetChanged(layer.tileset, undefined);
this.addChild(this.layerContainer);
this.addChild(this.tileGeometryContainer);
this.addChild(this.entityListView);

View File

@ -3,14 +3,17 @@ import {Entity, EntityCreatePacket, EntityList} from '@avocado/entity';
import {Vector} from '@avocado/math';
import {ShapeList} from '@avocado/physics';
import {
LayerUpdateTilesetUriPacket,
} from './packets/layer-update-tileset-uri.packet';
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,
Property('tilesetUri', {
default: '',
track: true,
}),
Property('tileset', {
@ -30,6 +33,7 @@ export class Layer extends decorate(class {}) {
this.tileEntities = {};
this.tileGeometry = [];
this.tiles = new Tiles();
this._tilesetUriChanged = false;
// Listeners.
this.entityList.on('entityAdded', this.onEntityAddedToLayer, this);
this.entityList.on('entityRemoved', this.onEntityRemovedFromLayer, this);
@ -43,11 +47,8 @@ export class Layer extends decorate(class {}) {
}
acceptPacket(packet) {
if (packet instanceof TileUpdatePacket) {
this.tiles.acceptPacket(packet);
}
if (packet instanceof EntityCreatePacket) {
this.entityList.acceptPacket(packet);
if (packet instanceof LayerUpdateTilesetUriPacket) {
this.tilesetUri = packet.data;
}
}
@ -66,6 +67,7 @@ export class Layer extends decorate(class {}) {
}
addTileGeometry() {
return false;
const tileset = this.tileset;
if (!tileset) {
return false;
@ -96,6 +98,10 @@ export class Layer extends decorate(class {}) {
return Array.from(this.entityList);
}
cleanPackets() {
this._tilesetUriChanged = false;
}
destroy() {
this.entityList.destroy();
this.entityList.off('entityAdded', this.onEntityAddedToLayer);
@ -168,6 +174,7 @@ export class Layer extends decorate(class {}) {
}
onTilesetUriChanged() {
this._tilesetUriChanged = true;
if (!this.tilesetUri) {
return;
}
@ -190,22 +197,10 @@ export class Layer extends decorate(class {}) {
}
}
packetsForUpdate(force = false) {
packets(informed) {
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]);
}
if (!force) {
const tilesPackets = this.tiles.packetsForUpdate();
for (let i = 0; i < tilesPackets.length; ++i) {
tilesPackets[i].data.layer = this.index;
packets.push(tilesPackets[i]);
}
if (this._tilesetUriChanged) {
packets.push(new LayerUpdateTilesetUriPacket(this.tilesetUri));
}
return packets;
}
@ -251,11 +246,25 @@ export class Layer extends decorate(class {}) {
toJSON() {
return {
entities: this.entityList.toJSON(),
tilesetUri: this.tilesetUri,
tiles: this.tiles.toJSON(),
};
}
toNetwork(informed) {
const {areaToInform} = informed;
const visibleEntities = this.visibleEntities(areaToInform);
const visibleEntitiesDiffs = visibleEntities.map((visibleEntity) => {
return visibleEntity.mergeDiff();
});
return {
entities: visibleEntitiesDiffs,
tilesetUri: this.tilesetUri,
tiles: this.tiles.toNetwork(informed),
};
}
visibleEntities(query) {
return this.entityList.visibleEntities(query);
}

View File

@ -4,8 +4,7 @@ import {arrayUnique, compose, EventEmitter, flatten} from '@avocado/core';
import {EntityCreatePacket} from '@avocado/entity';
import {Layer} from './layer';
import {LayerCreatePacket} from './packets/layer-create.packet';
import {TileUpdatePacket} from './packets/tile-update.packet';
import {LayersUpdateLayerPacket} from './packets/layers-update-layer.packet';
const decorate = compose(
EventEmitter,
@ -23,19 +22,21 @@ export class Layers extends decorate(class {}) {
*[Symbol.iterator]() {
for (let i = 0; i < this.layers.length; i++) {
yield this.layers[i];
yield {
index: i,
layer: this.layers[i],
};
}
}
acceptPacket(packet) {
if (packet instanceof LayerCreatePacket) {
this.addLayer(new Layer(packet.data));
if (packet instanceof LayersUpdateLayerPacket) {
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]);
}
if (packet instanceof TileUpdatePacket) {
this.layers[packet.data.layer].acceptPacket(packet);
}
if (packet instanceof EntityCreatePacket) {
this.layers[packet.data.layer].acceptPacket(packet);
}
}
@ -66,6 +67,12 @@ export class Layers extends decorate(class {}) {
return allEntities;
}
cleanPackets() {
for (let i = 0; i < this.layers.length; i++) {
this.layers[i].cleanPackets();
}
}
destroy() {
for (let i = 0; i < this.layers.length; i++) {
const layer = this.layers[i];
@ -102,13 +109,21 @@ export class Layers extends decorate(class {}) {
this.emit('entityRemoved', entity);
}
packetsForUpdate(force = false) {
packets(informed) {
const packets = [];
const updates = [];
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]);
const layerPackets = this.layers[i].packets(informed);
if (0 === layerPackets.length) {
continue;
}
updates.push({
layerIndex: i,
layerPackets,
});
}
if (updates.length > 0) {
packets.push(new LayersUpdateLayerPacket(updates));
}
return packets;
}
@ -139,6 +154,14 @@ export class Layers extends decorate(class {}) {
}
}
toJSON() {
return this.layers.map((layer) => layer.toJSON());
}
toNetwork(informed) {
return this.layers.map((layer) => layer.toNetwork(informed));
}
visibleEntities(query) {
const entities = [];
for (let i = 0; i < this.layers.length; i++) {

View File

@ -1,18 +0,0 @@
import {Packet} from '@avocado/net';
export class LayerCreatePacket extends Packet {
static get schema() {
return {
...super.schema,
data: {
tilesetUri: 'string',
tiles: {
size: ['uint16'],
data: ['uint16'],
},
},
};
}
}

View File

@ -0,0 +1,11 @@
import {Packet} from '@avocado/net';
export class LayerUpdateTilesetUriPacket extends Packet {
static get schema() {
return {
data: 'string',
};
}
}

View File

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

View File

@ -1,12 +0,0 @@
import {Packet} from '@avocado/net';
export class RoomSizeUpdatePacket extends Packet {
static get schema() {
return {
...super.schema,
data: 'uint32',
};
}
}

View File

@ -0,0 +1,26 @@
import {BundlePacket} from '@avocado/net';
import {SynchronizedUpdatePacket} from '@avocado/net';
export class RoomUpdateLayersPacket extends SynchronizedUpdatePacket {
static pack(packet) {
const data = packet.data[1];
data.layersPackets = BundlePacket.packPacket(
new BundlePacket(data.layersPackets)
);
return super.pack(packet);
}
static get synchronizationSchema() {
return {
layersPackets: 'buffer',
};
}
static unpack(packet) {
const data = super.unpack(packet);
data.layersPackets = BundlePacket.unpack(data.layersPackets);
return data;
}
}

View File

@ -0,0 +1,24 @@
import {Vector} from '@avocado/math';
import {SynchronizedUpdatePacket} from '@avocado/net';
export class RoomUpdateSizePacket extends SynchronizedUpdatePacket {
static pack(packet) {
const data = packet.data[1];
data.size = Vector.packToUint32(data.size);
return super.pack(packet);
}
static get synchronizationSchema() {
return {
size: 'uint32',
};
}
static unpack(packet) {
const data = super.unpack(packet);
data.size = Vector.unpackFromUint32(data.size);
return data;
}
}

View File

@ -6,7 +6,7 @@ export class TileUpdatePacket extends Packet {
return {
...super.schema,
data: {
layer: 'uint8',
...super.schema.data,
position: 'uint32',
tile: 'uint16',
},

View File

@ -3,19 +3,20 @@ 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 {SynchronizedMixin} from '@avocado/net';
import {RectangleShape} from '@avocado/physics';
import {Resource} from '@avocado/resource';
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';
import {RoomUpdateSizePacket} from './packets/room-update-size.packet';
import {RoomUpdateLayersPacket} from './packets/room-update-layers.packet';
const ROOM_BOUND_SIZE = 64;
const HALF_ROOM_BOUND_SIZE = ROOM_BOUND_SIZE / 2;
const decorate = compose(
EventEmitter,
SynchronizedMixin,
Property('world', {
track: true,
}),
@ -25,18 +26,26 @@ const decorate = compose(
}),
);
let synchronizationId = 1;
export class Room extends decorate(Resource) {
constructor(json) {
super();
this.bounds = [];
this._sizeChanged = false;
this._synchronizationId = synchronizationId++;
const layerJSON = 'undefined' !== typeof json ? json.layers : undefined;
this.layers = new Layers(layerJSON);
this.queuedEntityPackets = {};
// Listeners.
this.layers.on('entityAdded', this.onEntityAddedToRoom, this);
this.layers.on('entityRemoved', this.onEntityRemovedFromRoom, this);
this.layers.on('layerAdded', this.onLayerAdded, this);
const allEntities = this.allEntities();
for (let i = 0; i < allEntities.length; i++) {
this.onEntityAddedToRoom(allEntities[i]);
}
this.on('sizeChanged', this.onSizeChanged, this);
this.on('worldChanged', this.onWorldChanged, this);
if ('undefined' !== typeof json && json.size) {
@ -45,39 +54,16 @@ export class Room extends decorate(Resource) {
}
acceptPacket(packet) {
if (packet instanceof EntityCreatePacket) {
this.queuedEntityPackets[packet.data.uuid] = [];
this.layers.acceptPacket(packet);
// Size update.
if (packet instanceof RoomUpdateSizePacket) {
this.size = packet.data.size;
}
if (
packet instanceof LayerCreatePacket
|| packet instanceof TileUpdatePacket
) {
this.layers.acceptPacket(packet);
// Layer updates.
if (packet instanceof RoomUpdateLayersPacket) {
const {layersPackets} = packet.data;
for (let i = 0; i < layersPackets.length; ++i) {
this.layers.acceptPacket(layersPackets[i]);
}
if (packet instanceof EntityRemovePacket) {
const entity = this.findEntity(packet.data.uuid);
if (entity) {
entity.destroyGently();
}
return;
}
if (packet instanceof EntityPacket) {
const entity = this.findEntity(packet.data.uuid);
if (entity) {
entity.acceptPacket(packet);
}
else {
const queuedPackets = this.queuedEntityPackets[packet.data.uuid];
if (queuedPackets) {
queuedPackets.push(packet);
}
}
}
if (packet instanceof RoomSizeUpdatePacket) {
const x = packet.data & 0xFFFF;
const y = packet.data >> 16;
this.size = [x, y];
}
}
@ -89,7 +75,14 @@ export class Room extends decorate(Resource) {
return this.layers.allEntities();
}
cleanPackets() {
super.cleanPackets();
this._sizeChanged = false;
this.layers.cleanPackets();
}
destroy() {
super.destroy();
this.layers.destroy();
this.layers.off('entityAdded', this.onEntityAddedToRoom);
this.layers.off('entityRemoved', this.onEntityRemovedFromRoom);
@ -116,15 +109,6 @@ export class Room extends decorate(Resource) {
onEntityAddedToRoom(entity) {
entity.setIntoRoom(this);
if (AVOCADO_CLIENT) {
const queuedPackets = this.queuedEntityPackets[entity.instanceUuid];
if (queuedPackets) {
for (let i = 0; i < queuedPackets.length; i++) {
entity.acceptPacket(queuedPackets[i]);
}
}
this.queuedEntityPackets[entity.instanceUuid] = undefined;
}
this.emit('entityAdded', entity)
}
@ -138,12 +122,13 @@ export class Room extends decorate(Resource) {
}
onSizeChanged() {
this._sizeChanged = true;
this.updateBounds();
}
onWorldChanged() {
const world = this.world;
for (const layer of this.layers) {
for (const {index, layer} of this.layers) {
layer.world = world;
for (const entity of layer.entityList) {
if (entity.is('physical')) {
@ -155,24 +140,56 @@ export class Room extends decorate(Resource) {
this.updateBounds();
}
packetsForUpdate(force = false) {
const packets = [];
if (force) {
const packed = (this.height << 16) | (this.width);
packets.push(new RoomSizeUpdatePacket(packed));
packets(informed) {
const payload = [];
if (this._sizeChanged) {
payload.push(new RoomUpdateSizePacket({
size: this.size,
}));
}
// Layers packets.
const layersPacketsForUpdate = this.layers.packetsForUpdate(force);
for (let i = 0; i < layersPacketsForUpdate.length; i++) {
packets.push(layersPacketsForUpdate[i]);
// Layer updates.
const layersPackets = this.layers.packets(informed);
if (layersPackets.length > 0) {
payload.push(new RoomUpdateLayersPacket({
layersPackets,
}));
}
return packets;
return payload;
}
packetsAreIdempotent() {
return false;
}
removeEntityFromLayer(entity, layerIndex) {
this.layers.removeEntityFromLayer(entity, layerIndex);
}
synchronizationId() {
return this._synchronizationId;
}
tick(elapsed) {
this.layers.tick(elapsed);
if (this.world) {
this.world.tick(elapsed);
}
}
toNetwork(informed) {
return {
layers: this.layers.toNetwork(informed),
size: this.size,
};
}
toJSON() {
return {
layer: this.layers.toJSON(),
size: this.size,
};
}
updateBounds() {
const world = this.world;
if (!world) {
@ -212,13 +229,6 @@ export class Room extends decorate(Resource) {
});
}
tick(elapsed) {
this.layers.tick(elapsed);
if (this.world) {
this.world.tick(elapsed);
}
}
visibleEntities(query) {
return this.layers.visibleEntities(query);
}

View File

@ -5,6 +5,8 @@ import {Rectangle, Vector} from '@avocado/math';
import {TileUpdatePacket} from './packets/tile-update.packet';
const CHUNK_AXIS = 10;
const decorate = compose(
EventEmitter,
Vector.Mixin('size', 'width', 'height', {
@ -16,57 +18,106 @@ export class Tiles extends decorate(class {}) {
constructor(json) {
super();
// this.chunks = [];
this.data = [];
this.updatePackets = [];
if ('undefined' !== typeof json) {
this.fromJSON(json);
}
}
acceptPacket(packet) {
if (packet instanceof TileUpdatePacket) {
const unpackedPosition = [
packet.data.position & 0xFFFF,
packet.data.position >> 16,
];
this.setTileAt(unpackedPosition, packet.data.tile);
}
// if (packet instanceof TileUpdatePacket) {
// const unpackedPosition = [
// packet.data.position & 0xFFFF,
// packet.data.position >> 16,
// ];
// this.setTileAt(unpackedPosition, packet.data.tile);
// }
}
forEachTile(fn) {
let [x, y] = [0, 0];
let [width, height] = this.size;
let i = 0;
for (let k = 0; k < height; ++k) {
for (let j = 0; j < width; ++j) {
fn(this.data[i], x, y, i);
++i;
++x;
}
x = 0;
++y;
}
}
// chunkIndexAtPosition(position) {
// const chunkCount = Vector.ceil(Vector.scale(this.size, 1 / CHUNK_AXIS));
// const chunkPosition = Vector.floor(Vector.scale(position, 1 / CHUNK_AXIS));
// const chunkIndex = chunkPosition[1] * chunkCount[0] + chunkPosition[0];
// return chunkIndex;
// }
// static createChunkFromData(position, size, data) {
// const chunk = Array(CHUNK_AXIS * CHUNK_AXIS).fill(0);
// let chunkIndex = 0;
// const chunkPosition = Vector.scale(position, CHUNK_AXIS);
// let dataIndex = size[0] * chunkPosition[1] + chunkPosition[0];
// for (let y = 0; y < CHUNK_AXIS; ++y) {
// for (let x = 0; x < CHUNK_AXIS; ++x) {
// if (chunkPosition[0] < size[0] && chunkPosition[1] < size[1]) {
// chunk[chunkIndex] = data[dataIndex];
// }
// chunkIndex += 1;
// chunkPosition[0] += 1;
// dataIndex += 1;
// }
// chunkPosition[0] -= CHUNK_AXIS;
// chunkPosition[1] += 1;
// dataIndex -= CHUNK_AXIS;
// dataIndex += size[0];
// }
// return chunk;
// }
// forEachTile(fn) {
// let [x, y] = [0, 0];
// let [width, height] = this.size;
// let i = 0;
// for (let k = 0; k < height; ++k) {
// for (let j = 0; j < width; ++j) {
// fn(this.data[i], x, y, i);
// ++i;
// ++x;
// }
// x = 0;
// ++y;
// }
// }
fromJSON(json) {
if (json.size) {
this.size = json.size;
super.size = json.size;
}
if (json.data) {
// this.chunks = [];
// const chunkCount = Vector.ceil(Vector.scale(this.size, 1 / CHUNK_AXIS));
// if (json.data && json.data.length > 0) {
// for (let y = 0; y < chunkCount[1]; ++y) {
// for (let x = 0; x < chunkCount[0]; ++x) {
// this.chunks.push(Tiles.createChunkFromData(
// [x, y],
// this.size,
// json.data
// ));
// }
// }
// }
// if (json.chunks && json.chunks.length > 0) {
// }
if (json.data && json.data.length > 0) {
this.data = json.data.slice(0);
}
// Instantiate blank tiles if we got size but no data.
else {
this.data = Array(Vector.area(this.size)).fill(0);
}
return this;
}
indexAt(position) {
return this.width * position[1] + position[0];
}
// indexAt(position) {
// return this.width * position[1] + position[0];
// }
packetsForUpdate() {
const packetsForUpdate = this.updatePackets;
this.updatePackets = [];
return packetsForUpdate;
}
// packetsForUpdate() {
// const packetsForUpdate = this.updatePackets;
// this.updatePackets = [];
// return packetsForUpdate;
// }
get rectangle() {
return Rectangle.compose([0, 0], this.size);
@ -83,13 +134,32 @@ export class Tiles extends decorate(class {}) {
}
this.data[index] = tile;
const packedPosition = position[1] << 16 | position[0];
this.updatePackets.push(new TileUpdatePacket({
position: packedPosition,
tile,
}));
// this.updatePackets.push(new TileUpdatePacket({
// position: packedPosition,
// tile,
// }));
this.emit('dataChanged');
}
get size() {
return super.size;
}
set size(size) {
super.size = size;
// const newChunks = [];
// const chunkCount = Vector.ceil(Vector.div(size, [CHUNK_AXIS, CHUNK_AXIS]));
// const chunkLinearCount = Vector.area(chunkCount);
// for (let i = 0; i < chunkLinearCount; ++i) {
// newChunks.
// }
// for (let y = 0; y < chunkCount[1]; y++) {
// for (let x = 0; x < chunkCount[0]; x++) {
// newChunks[y][x]
// }
// }
}
slice(rectangle) {
const tilesRectangle = this.rectangle;
// Get intersection.
@ -119,13 +189,24 @@ export class Tiles extends decorate(class {}) {
}
tileAt(position) {
return this.data[this.indexAt(position)];
const index = this.indexAt(position);
if (index < 0 || index >= this.data.length) {
return;
}
return this.data[index];
}
toJSON() {
return {
size: Vector.copy(this.size),
data: this.data.slice(0),
size: this.size,
data: this.data,
};
}
toNetwork(informed) {
return {
size: this.size,
data: this.data,
};
}

View File

@ -67,6 +67,7 @@ export class Followed extends Trait {
},
traitAdded: (type) => {
this.onRoomSizeChanged();
if (-1 === [
'positioned',
'followed',