Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow components ordering #6648

Merged
merged 20 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/core/sort.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,27 @@
*/
const cmpPriority = (a, b) => a.priority - b.priority;


/**
* @param {{order: number}} a - First object with `order` property.
LeXXik marked this conversation as resolved.
Show resolved Hide resolved
* @param {{order: number}} b - Second object with `order` property.
* @returns {number} A number indicating the relative position.
* @ignore
*/
const cmpStaticOrder = (a, b) => a.constructor.order - b.constructor.order;

/**
* @param {Array<{priority: number}>} arr - Array to be sorted in place where each element contains
* an object with at least a priority property.
* @returns {Array<{priority: number}>} In place sorted array.
* @ignore
*/
export const sortPriority = arr => arr.sort(cmpPriority);

/**
* @param {Array<{order: number}>} arr - Array to be sorted in place where each element contains
* an object with a static `order` property.
* @returns {Array<{order: number}>} In place sorted array.
* @ignore
*/
export const sortStaticOrder = arr => arr.sort(cmpStaticOrder);
9 changes: 9 additions & 0 deletions src/framework/components/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ import { EventHandler } from '../../core/event-handler.js';
* events each frame, and expose properties to the PlayCanvas Editor.
*/
class Component extends EventHandler {
/**
* Component order. When an entity with multiple components gets enabled, this order specifies
* in which order the components get enabled. The lowest number gets enabled first.
*
* @type {number} - Component order number.
* @private
*/
static order = 0;

/**
* The ComponentSystem used to create this Component.
*
Expand Down
2 changes: 2 additions & 0 deletions src/framework/components/rigid-body/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ class RigidBodyComponent extends Component {
*/
static EVENT_TRIGGERLEAVE = 'triggerleave';

static order = -1;

/** @private */
_angularDamping = 0;

Expand Down
7 changes: 4 additions & 3 deletions src/framework/components/system.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,16 @@ class ComponentSystem extends EventHandler {
* @ignore
*/
removeComponent(entity) {
const id = this.id;
const record = this.store[entity.getGuid()];
const component = entity.c[this.id];
const component = entity.c[id];

this.fire('beforeremove', entity, component);

delete this.store[entity.getGuid()];

entity[this.id] = undefined;
delete entity.c[this.id];
entity[id] = undefined;
delete entity.c[id];

this.fire('remove', entity, record.data);
}
Expand Down
53 changes: 38 additions & 15 deletions src/framework/entity.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Debug } from '../core/debug.js';
import { guid } from '../core/guid.js';
import { sortStaticOrder } from '../core/sort.js';

import { GraphNode } from '../scene/graph-node.js';

Expand All @@ -11,6 +12,12 @@ import { getApplication } from './globals.js';
*/
const _enableList = [];

/**
* @type {Array<import('./components/component.js').Component>}
* @ignore
*/
const _sortedArray = [];

/**
* The Entity is the core primitive of a PlayCanvas game. Generally speaking an object in your game
* will consist of an {@link Entity}, and a set of {@link Component}s which are managed by their
Expand Down Expand Up @@ -503,17 +510,14 @@ class Entity extends GraphNode {
_onHierarchyStateChanged(enabled) {
super._onHierarchyStateChanged(enabled);

// enable / disable all the components
const components = this.c;
for (const type in components) {
if (components.hasOwnProperty(type)) {
const component = components[type];
if (component.enabled) {
if (enabled) {
component.onEnable();
} else {
component.onDisable();
}
const components = this._getSortedComponents();
for (let i = 0; i < components.length; i++) {
const component = components[i];
if (component.enabled) {
if (enabled) {
component.onEnable();
} else {
component.onDisable();
}
}
}
Expand All @@ -522,10 +526,9 @@ class Entity extends GraphNode {
/** @private */
_onHierarchyStatePostChanged() {
// post enable all the components
const components = this.c;
for (const type in components) {
if (components.hasOwnProperty(type))
components[type].onPostStateChange();
const components = this._getSortedComponents();
for (let i = 0; i < components.length; i++) {
components[i].onPostStateChange();
}
}

Expand Down Expand Up @@ -601,6 +604,26 @@ class Entity extends GraphNode {
return clone;
}

_getSortedComponents() {
_sortedArray.length = 0;

const components = this.c;
let needSort = 0;
for (const type in components) {
if (components.hasOwnProperty(type)) {
const component = components[type];
needSort |= component.constructor.order !== 0;
_sortedArray.push(component);
}
}

if (needSort && _sortedArray.length > 1) {
sortStaticOrder(_sortedArray);
}

return _sortedArray;
}

/**
* @param {Object<string, Entity>} duplicatedIdsMap - A map of original entity GUIDs to cloned
* entities.
Expand Down
63 changes: 63 additions & 0 deletions test/framework/entity.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { DummyComponentSystem } from './test-component/system.mjs';
import { HTMLCanvasElement } from '@playcanvas/canvas-mock';

import { expect } from 'chai';
import { stub } from 'sinon';

describe('Entity', function () {

Expand Down Expand Up @@ -121,6 +122,68 @@ describe('Entity', function () {
});
}

it('respects components order on disable', function () {
const entity = new Entity();
entity.enabled = true;

entity.addComponent('collision');
entity.addComponent('rigidbody');

const colOnDisable = stub();
const rbOnDisable = stub();
let disableOrder = 0;

entity.collision.onDisable = colOnDisable;
entity.rigidbody.onDisable = rbOnDisable;

colOnDisable.onFirstCall().callsFake(() => {
disableOrder = 2;
});
rbOnDisable.onFirstCall().callsFake(() => {
disableOrder = 1;
});

entity.enabled = false;

expect(disableOrder).to.equal(2);

entity.destroy();
});

it('respects components order on enable', function () {
const entity = new Entity('Child');
const parent = new Entity('Parent');

parent.addChild(entity);
parent._enabled = true;
parent._enabledInHierarchy = true;

entity.addComponent('collision');
entity.addComponent('rigidbody');

entity.enabled = false;

const rbOnEnable = stub();
const colOnEnable = stub();
let enableOrder = 0;

entity.collision.onEnable = colOnEnable;
entity.rigidbody.onEnable = rbOnEnable;

colOnEnable.onFirstCall().callsFake(() => {
enableOrder = 2;
});
rbOnEnable.onFirstCall().callsFake(() => {
enableOrder = 1;
});

entity.enabled = true;

expect(enableOrder).to.equal(2);

parent.destroy();
});

});

const createSubtree = () => {
Expand Down