perf: much more care around hidden classes
This commit is contained in:
parent
fa939ac3fc
commit
d289b9c36a
|
@ -26,7 +26,8 @@ export class Behaved extends decorate(Trait) {
|
|||
return 'behaved';
|
||||
}
|
||||
|
||||
initialize() {
|
||||
constructor(entity, params, state) {
|
||||
super(entity, params, state);
|
||||
this._context = createContext();
|
||||
this._context.add('entity', this.entity);
|
||||
this._currentRoutine = undefined;
|
||||
|
@ -34,7 +35,7 @@ export class Behaved extends decorate(Trait) {
|
|||
const routinesJSON = this.params.routines;
|
||||
this._routines = (new Routines()).fromJSON(routinesJSON);
|
||||
this._routines.context = this._context;
|
||||
this.updateCurrentRoutine();
|
||||
this.updateCurrentRoutine(this.state.get('currentRoutine'));
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
@ -51,17 +52,15 @@ export class Behaved extends decorate(Trait) {
|
|||
this._isBehaving = isBehaving;
|
||||
}
|
||||
|
||||
updateCurrentRoutine() {
|
||||
this._currentRoutine = this._routines.routine(
|
||||
this.entity.currentRoutine
|
||||
);
|
||||
updateCurrentRoutine(currentRoutine) {
|
||||
this._currentRoutine = this._routines.routine(currentRoutine);
|
||||
}
|
||||
|
||||
listeners() {
|
||||
return {
|
||||
|
||||
currentRoutineChanged: () => {
|
||||
this.updateCurrentRoutine();
|
||||
currentRoutineChanged: (old, currentRoutine) => {
|
||||
this.updateCurrentRoutine(currentRoutine);
|
||||
},
|
||||
|
||||
dying: () => {
|
||||
|
|
|
@ -72,7 +72,7 @@ export function EventEmitterMixin(Superclass) {
|
|||
if ('function' !== typeof fn) {
|
||||
// Only type.
|
||||
if (type in this.$$events) {
|
||||
delete this.$$events[type];
|
||||
this.$$events[type] = [];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -85,9 +85,6 @@ export function EventEmitterMixin(Superclass) {
|
|||
listeners[type] = listeners[type].filter((listener) => {
|
||||
return listener.fn !== fn;
|
||||
});
|
||||
if (0 === listeners[type].length) {
|
||||
delete listeners[type];
|
||||
}
|
||||
}
|
||||
|
||||
on(typesOrType, fn, that = undefined) {
|
||||
|
|
|
@ -10,40 +10,66 @@ import {hasTrait, lookupTrait} from './trait/registry';
|
|||
|
||||
const debug = D('@avocado:entity:traits');
|
||||
|
||||
function copyTraitProperties(from, to, instance) {
|
||||
do {
|
||||
Object.getOwnPropertyNames(from).forEach((property) => {
|
||||
// Nooope.
|
||||
switch (property) {
|
||||
case 'state':
|
||||
return;
|
||||
const blacklistedAccessorKeys = [
|
||||
'state',
|
||||
];
|
||||
|
||||
// This really seems like a whole lot of complicated nonsense, but it's an
|
||||
// unfortunate consequence of V8 (and maybe others) not optimizing mutable
|
||||
// accessors in fast hidden classes.
|
||||
const traitAccessorForPropertyMap = {};
|
||||
function traitAccessorForProperty(type, property) {
|
||||
if (!(type in traitAccessorForPropertyMap)) {
|
||||
traitAccessorForPropertyMap[type] = {};
|
||||
}
|
||||
if (!(property in traitAccessorForPropertyMap[type])) {
|
||||
traitAccessorForPropertyMap[type][property] = {
|
||||
get: function() {
|
||||
return this._traits[type][property];
|
||||
},
|
||||
set: function(value) {
|
||||
this._traits[type][property] = value;
|
||||
}
|
||||
// Make sure it's actually a property.
|
||||
let descriptor = Object.getOwnPropertyDescriptor(from, property);
|
||||
};
|
||||
}
|
||||
return traitAccessorForPropertyMap[type][property];
|
||||
}
|
||||
function defineTraitAccessors(from, to, instance) {
|
||||
const type = instance.constructor.type();
|
||||
do {
|
||||
Object.getOwnPropertyNames(from).forEach((accessorKey) => {
|
||||
if (-1 !== blacklistedAccessorKeys.indexOf(accessorKey)) {
|
||||
return;
|
||||
}
|
||||
let descriptor = Object.getOwnPropertyDescriptor(from, accessorKey);
|
||||
// Make sure it's actually an accessor.
|
||||
if (!descriptor.get && !descriptor.set) {
|
||||
return;
|
||||
}
|
||||
// Bind properties to trait instance.
|
||||
const accessor = traitAccessorForProperty(type, accessorKey);
|
||||
if (descriptor.get) {
|
||||
descriptor.get = descriptor.get.bind(instance);
|
||||
descriptor.get = accessor.get;
|
||||
}
|
||||
if (descriptor.set) {
|
||||
descriptor.set = descriptor.set.bind(instance);
|
||||
descriptor.set = accessor.set;
|
||||
}
|
||||
Object.defineProperty(to, property, descriptor);
|
||||
Object.defineProperty(to, accessorKey, descriptor);
|
||||
});
|
||||
} while (Object.prototype !== (from = Object.getPrototypeOf(from)));
|
||||
}
|
||||
|
||||
function enumerateTraitProperties(prototype) {
|
||||
function enumerateTraitAccessorKeys(prototype) {
|
||||
const keys = [];
|
||||
do {
|
||||
Object.getOwnPropertyNames(prototype).forEach((property) => {
|
||||
let descriptor = Object.getOwnPropertyDescriptor(prototype, property);
|
||||
Object.getOwnPropertyNames(prototype).forEach((accessorKey) => {
|
||||
if (-1 !== blacklistedAccessorKeys.indexOf(accessorKey)) {
|
||||
return;
|
||||
}
|
||||
let descriptor = Object.getOwnPropertyDescriptor(prototype, accessorKey);
|
||||
// Make sure it's actually an accessor.
|
||||
if (!descriptor.get && !descriptor.set) {
|
||||
return;
|
||||
}
|
||||
keys.push(property);
|
||||
keys.push(accessorKey);
|
||||
});
|
||||
} while (Object.prototype !== (prototype = Object.getPrototypeOf(prototype)));
|
||||
return keys;
|
||||
|
@ -109,9 +135,7 @@ export class Entity extends decorate(Resource) {
|
|||
const {params, state} = json;
|
||||
const instance = new Trait(this, params, state);
|
||||
// Proxy properties.
|
||||
copyTraitProperties(Trait.prototype, this, instance);
|
||||
// Let the Trait do its initialization.
|
||||
instance.initialize();
|
||||
defineTraitAccessors(Trait.prototype, this, instance);
|
||||
// Attach listeners.
|
||||
const listeners = instance.memoizedListeners();
|
||||
for (const eventName in listeners) {
|
||||
|
@ -248,10 +272,13 @@ export class Entity extends decorate(Resource) {
|
|||
}
|
||||
const hooks = instance.hooks();
|
||||
for (const key in hooks) {
|
||||
delete this._hooks[key];
|
||||
const implementation = this._hooks[key].find(({type: hookType}) => {
|
||||
return hookType === type;
|
||||
});
|
||||
this._hooks[key].splice(this._hooks[key].indexOf(implementation), 1);
|
||||
}
|
||||
const Trait = lookupTrait(type);
|
||||
const properties = enumerateTraitProperties(Trait.prototype);
|
||||
const properties = enumerateTraitAccessorKeys(Trait.prototype);
|
||||
for (let i = 0; i < properties.length; ++i) {
|
||||
const property = properties[i];
|
||||
delete this[property];
|
||||
|
@ -317,7 +344,7 @@ export class Entity extends decorate(Resource) {
|
|||
}
|
||||
instance.isDirty = false;
|
||||
if (this.is('listed')) {
|
||||
this.list.markEntityDirty(this);
|
||||
this.list && this.list.markEntityDirty(this);
|
||||
}
|
||||
this._setInstanceState(state, instance.constructor.type(), instance);
|
||||
}
|
||||
|
|
|
@ -38,7 +38,6 @@ export class EntityList extends decorate(class {}) {
|
|||
this._flatEntities.push(entity);
|
||||
this._entityTickers.push(entity.tick);
|
||||
this.state = this.state.set(uuid, entity.state);
|
||||
entity.addTrait('listed');
|
||||
entity.setIntoList(this);
|
||||
entity.once('destroy', () => {
|
||||
this.removeEntity(entity);
|
||||
|
@ -112,9 +111,6 @@ export class EntityList extends decorate(class {}) {
|
|||
this._entityTickers.splice(this._entityTickers.indexOf(entity.tick), 1);
|
||||
this.state = this.state.delete(uuid);
|
||||
this.emit('entityRemoved', entity);
|
||||
if (entity.is('listed')) {
|
||||
entity.removeTrait('listed');
|
||||
}
|
||||
}
|
||||
|
||||
tick(elapsed) {
|
||||
|
|
|
@ -39,8 +39,6 @@ export class Trait extends decorate(class {}) {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
initialize() {}
|
||||
|
||||
label() {
|
||||
return this.constructor.name;
|
||||
}
|
||||
|
|
|
@ -28,7 +28,8 @@ export class Directional extends decorate(Trait) {
|
|||
return 'directional';
|
||||
}
|
||||
|
||||
initialize() {
|
||||
constructor(entity, params, state) {
|
||||
super(entity, params, state);
|
||||
this.directionCount = this.params.directionCount;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,8 @@ export class Existent extends decorate(Trait) {
|
|||
return 'existent';
|
||||
}
|
||||
|
||||
initialize() {
|
||||
constructor(entity, params, state) {
|
||||
super(entity, params, state);
|
||||
this._isTicking = this.params.isTicking;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,8 @@ export class Listed extends Trait {
|
|||
return 'listed';
|
||||
}
|
||||
|
||||
initialize() {
|
||||
constructor(entity, params, state) {
|
||||
super(entity, params, state);
|
||||
this.entity.list = undefined;
|
||||
this.quadTreeAabb = [];
|
||||
this.quadTreeNodes = [];
|
||||
|
|
|
@ -21,7 +21,8 @@ export class Mobile extends decorate(Trait) {
|
|||
return 'mobile';
|
||||
}
|
||||
|
||||
initialize() {
|
||||
constructor(entity, params, state) {
|
||||
super(entity, params, state);
|
||||
this.requestedMovement = [0, 0];
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,8 @@ export class Positioned extends decorate(Trait) {
|
|||
return 'positioned';
|
||||
}
|
||||
|
||||
initialize() {
|
||||
constructor(entity, params, state) {
|
||||
super(entity, params, state);
|
||||
this.on('_positionChanged', this.on_positionChanged, this);
|
||||
const x = this.state.get('x') >> 2;
|
||||
const y = this.state.get('y') >> 2;
|
||||
|
|
|
@ -36,7 +36,8 @@ export class Spawner extends decorate(Trait) {
|
|||
this.children = [];
|
||||
}
|
||||
|
||||
initialize() {
|
||||
constructor(entity, params, state) {
|
||||
super(entity, params, state);
|
||||
this.children = [];
|
||||
this.spawnJSONs = this.params.spawns;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@ const decorate = compose(
|
|||
|
||||
export class Emitter extends decorate(Trait) {
|
||||
|
||||
initialize() {
|
||||
constructor(entity, params, state) {
|
||||
super(entity, params, state);
|
||||
this.emitters = {};
|
||||
if (AVOCADO_CLIENT) {
|
||||
this.ticker = new Ticker(1 / 10);
|
||||
|
|
|
@ -29,7 +29,8 @@ export class Pictured extends decorate(Trait) {
|
|||
return 'pictured';
|
||||
}
|
||||
|
||||
initialize() {
|
||||
constructor(entity, params, state) {
|
||||
super(entity, params, state);
|
||||
this._cachedAabbs = {};
|
||||
this._images = this.params.images;
|
||||
this.sprites = undefined;
|
||||
|
|
|
@ -53,21 +53,22 @@ export class Visible extends decorate(Trait) {
|
|||
return 'visible';
|
||||
}
|
||||
|
||||
initialize() {
|
||||
constructor(entity, params, state) {
|
||||
super(entity, params, state);
|
||||
if (hasGraphics) {
|
||||
this._container = new Container();
|
||||
const filter = this.params.filter;
|
||||
if (filter) {
|
||||
this._container.setFilter(filter);
|
||||
}
|
||||
this._container.isVisible = this.entity.isVisible;
|
||||
this._container.isVisible = this.state.get('isVisible');
|
||||
}
|
||||
this._rawVisibleAabb = [0, 0, 0, 0];
|
||||
this.scheduledBoundingBoxUpdate = true;
|
||||
this.trackPosition = this.params.trackPosition;
|
||||
const scale = this.entity.visibleScale;
|
||||
const scale = this.state.get('visibleScale');
|
||||
this._visibleScale = [scale.get(0), scale.get(1)];
|
||||
this.onZIndexChanged();
|
||||
this.onZIndexChanged(this.state.get('zIndex'));
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
@ -80,8 +81,7 @@ export class Visible extends decorate(Trait) {
|
|||
return this._container;
|
||||
}
|
||||
|
||||
onZIndexChanged() {
|
||||
const zIndex = this.entity.zIndex;
|
||||
onZIndexChanged(zIndex) {
|
||||
this._usingAutoZIndex = AUTO_ZINDEX === zIndex;
|
||||
if (!this._usingAutoZIndex) {
|
||||
if (this._container) {
|
||||
|
@ -168,8 +168,8 @@ export class Visible extends decorate(Trait) {
|
|||
}
|
||||
},
|
||||
|
||||
zIndexChanged: () => {
|
||||
this.onZIndexChanged();
|
||||
zIndexChanged: (old, zIndex) => {
|
||||
this.onZIndexChanged(zIndex);
|
||||
},
|
||||
|
||||
};
|
||||
|
|
|
@ -27,7 +27,8 @@ export class Collider extends decorate(Trait) {
|
|||
return 'collider';
|
||||
}
|
||||
|
||||
initialize() {
|
||||
constructor(entity, params, state) {
|
||||
super(entity, params, state);
|
||||
this._collisionGroup = this.params.collisionGroup;
|
||||
this._collidesWithGroups = this.params.collidesWithGroups;
|
||||
this._isCollidingWith = [];
|
||||
|
|
|
@ -22,7 +22,8 @@ export class Physical extends decorate(Trait) {
|
|||
return 'physical';
|
||||
}
|
||||
|
||||
initialize() {
|
||||
constructor(entity, params, state) {
|
||||
super(entity, params, state);
|
||||
this._body = undefined;
|
||||
this.bodyView = undefined;
|
||||
this._world = undefined;
|
||||
|
|
|
@ -19,7 +19,8 @@ export class Shaped extends decorate(Trait) {
|
|||
return 'shaped';
|
||||
}
|
||||
|
||||
initialize() {
|
||||
constructor(entity, params, state) {
|
||||
super(entity, params, state);
|
||||
this._shape = shapeFromJSON(this.params.shape);
|
||||
this.shapeView = undefined;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,8 @@ export class Audible extends Trait {
|
|||
this.loadSounds();
|
||||
}
|
||||
|
||||
initialize() {
|
||||
constructor(entity, params, state) {
|
||||
super(entity, params, state);
|
||||
this._sounds = this.params.sounds;
|
||||
this.sounds = {};
|
||||
}
|
||||
|
|
|
@ -33,13 +33,14 @@ export class Animated extends decorate(Trait) {
|
|||
return 'animated';
|
||||
}
|
||||
|
||||
initialize() {
|
||||
constructor(entity, params, state) {
|
||||
super(entity, params, state);
|
||||
this._animations = this.params.animations;
|
||||
this.animations = {};
|
||||
this.animationViews = undefined;
|
||||
this.animationsPromise = undefined;
|
||||
this._cachedAabbs = {};
|
||||
this._currentAnimation = this.entity.currentAnimation;
|
||||
this._currentAnimation = this.state.get('currentAnimation');
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
|
|
@ -108,15 +108,11 @@ export class Layer extends decorate(class {}) {
|
|||
}
|
||||
|
||||
onEntityAddedToLayer(entity) {
|
||||
entity.addTrait('layered');
|
||||
entity.layer = this;
|
||||
this.emit('entityAdded', entity)
|
||||
}
|
||||
|
||||
onEntityRemovedFromLayer(entity) {
|
||||
if (entity.is('layered')) {
|
||||
entity.removeTrait('layered');
|
||||
}
|
||||
this.emit('entityRemoved', entity);
|
||||
}
|
||||
|
||||
|
|
|
@ -71,15 +71,11 @@ export class Room extends decorate(class {}) {
|
|||
}
|
||||
|
||||
onEntityAddedToRoom(entity) {
|
||||
entity.addTrait('roomed');
|
||||
entity.room = this;
|
||||
this.emit('entityAdded', entity)
|
||||
}
|
||||
|
||||
onEntityRemovedFromRoom(entity) {
|
||||
if (entity.is('roomed')) {
|
||||
entity.removeTrait('roomed');
|
||||
}
|
||||
this.emit('entityRemoved', entity);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,8 @@ export class Followed extends Trait {
|
|||
return 'followed';
|
||||
}
|
||||
|
||||
initialize() {
|
||||
constructor(entity, params, state) {
|
||||
super(entity, params, state);
|
||||
const camera = new Camera();
|
||||
camera.viewSize = this.params.viewSize;
|
||||
this._camera = camera;
|
||||
|
@ -24,10 +25,11 @@ export class Followed extends Trait {
|
|||
|
||||
destroy() {
|
||||
if (!this.entity.is('roomed')) {
|
||||
const room = this.entity.room;
|
||||
if (room) {
|
||||
room.off('sizeChanged', this.onRoomSizeChanged);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const room = this.entity.room;
|
||||
if (room) {
|
||||
room.off('sizeChanged', this.onRoomSizeChanged);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,7 +41,10 @@ export class Followed extends Trait {
|
|||
if (!this.entity.is('roomed')) {
|
||||
return;
|
||||
}
|
||||
this._camera.areaSize = this.entity.room.size;
|
||||
const room = this.entity.room;
|
||||
if (room) {
|
||||
this._camera.areaSize = room.size;
|
||||
}
|
||||
}
|
||||
|
||||
updatePosition() {
|
||||
|
|
|
@ -6,7 +6,8 @@ export class Layered extends Trait {
|
|||
return 'layered';
|
||||
}
|
||||
|
||||
initialize() {
|
||||
constructor(entity, params, state) {
|
||||
super(entity, params, state);
|
||||
this._layer = undefined;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,8 @@ export class Roomed extends Trait {
|
|||
return 'roomed';
|
||||
}
|
||||
|
||||
initialize() {
|
||||
constructor(entity, params, state) {
|
||||
super(entity, params, state);
|
||||
this._room = undefined;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user