Skip to content

Commit

Permalink
Introduce sceneOnly flag for components (#5465)
Browse files Browse the repository at this point in the history
* Introduce sceneOnly flag for components

* Move sceneOnly and multiplicity checks into Component constructor

---------

Co-authored-by: Noeri Huisman <mrxz@users.noreply.github.com>
  • Loading branch information
mrxz and mrxz committed Feb 17, 2024
1 parent 093fcb8 commit 807b1de
Show file tree
Hide file tree
Showing 19 changed files with 123 additions and 14 deletions.
16 changes: 16 additions & 0 deletions docs/core/component.md
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,22 @@ AFRAME.registerComponent('foo', {
});
```
### `sceneOnly`
The `sceneOnly` flag indicates if a component can only be applied to the scene
entity. Since `sceneOnly` is set to `false` by default, the component can be added
to any entity. For example, any entity could have a geometry component.
But if a component has `sceneOnly` set to `true`, then the component can only be
applied to `<a-scene>`:
```js
AFRAME.registerComponent('foo', {
sceneOnly: true,
// ...
});
```
### `events`
The `events` object allows for conveniently defining event handlers that get
Expand Down
2 changes: 2 additions & 0 deletions src/components/scene/ar-hit-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ module.exports.Component = register('ar-hit-test', {
}
},

sceneOnly: true,

init: function () {
this.hitTest = null;
this.imageDataArray = new Uint8ClampedArray(512 * 512 * 4);
Expand Down
1 change: 1 addition & 0 deletions src/components/scene/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module.exports.Component = register('background', {
color: { type: 'color', default: 'black' },
transparent: { default: false }
},
sceneOnly: true,
update: function () {
var data = this.data;
var object3D = this.el.object3D;
Expand Down
3 changes: 2 additions & 1 deletion src/components/scene/debug.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
var register = require('../../core/component').registerComponent;

module.exports.Component = register('debug', {
schema: {default: true}
schema: {default: true},
sceneOnly: true
});
2 changes: 2 additions & 0 deletions src/components/scene/device-orientation-permission-ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ module.exports.Component = registerComponent('device-orientation-permission-ui',
cancelButtonText: {default: 'Cancel'}
},

sceneOnly: true,

init: function () {
var self = this;

Expand Down
2 changes: 2 additions & 0 deletions src/components/scene/embedded.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ module.exports.Component = registerComponent('embedded', {

schema: {default: true},

sceneOnly: true,

update: function () {
var sceneEl = this.el;
var enterVREl = sceneEl.querySelector('.a-enter-vr');
Expand Down
7 changes: 2 additions & 5 deletions src/components/scene/fog.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,13 @@ module.exports.Component = register('fog', {
type: {default: 'linear', oneOf: ['linear', 'exponential']}
},

sceneOnly: true,

update: function () {
var data = this.data;
var el = this.el;
var fog = this.el.object3D.fog;

if (!el.isScene) {
warn('Fog component can only be applied to <a-scene>');
return;
}

// (Re)create fog if fog doesn't exist or fog type changed.
if (!fog || data.type !== fog.name) {
el.object3D.fog = getFog(data);
Expand Down
2 changes: 2 additions & 0 deletions src/components/scene/inspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ module.exports.Component = registerComponent('inspector', {
url: {default: INSPECTOR_URL}
},

sceneOnly: true,

init: function () {
this.firstPlay = true;
this.onKeydown = this.onKeydown.bind(this);
Expand Down
2 changes: 2 additions & 0 deletions src/components/scene/keyboard-shortcuts.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ module.exports.Component = registerComponent('keyboard-shortcuts', {
exitVR: {default: true}
},

sceneOnly: true,

init: function () {
this.onKeyup = this.onKeyup.bind(this);
},
Expand Down
2 changes: 2 additions & 0 deletions src/components/scene/pool.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ module.exports.Component = registerComponent('pool', {
dynamic: {default: false}
},

sceneOnly: true,

multiple: true,

initPool: function () {
Expand Down
2 changes: 2 additions & 0 deletions src/components/scene/real-world-meshing.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ module.exports.Component = register('real-world-meshing', {
planeMixin: {default: ''}
},

sceneOnly: true,

init: function () {
var webxrData = this.el.getAttribute('webxr');
var requiredFeaturesArray = webxrData.requiredFeatures;
Expand Down
1 change: 1 addition & 0 deletions src/components/scene/reflection.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module.exports.Component = register('reflection', {
schema: {
directionalLight: { type: 'selector' }
},
sceneOnly: true,
init: function () {
var self = this;
this.cubeRenderTarget = new THREE.WebGLCubeRenderTarget(16);
Expand Down
2 changes: 2 additions & 0 deletions src/components/scene/screenshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ module.exports.Component = registerComponent('screenshot', {
camera: {type: 'selector'}
},

sceneOnly: true,

setup: function () {
var el = this.el;
if (this.canvas) { return; }
Expand Down
2 changes: 2 additions & 0 deletions src/components/scene/stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ var ThreeStats = window.threeStats;
module.exports.Component = registerComponent('stats', {
schema: {default: true},

sceneOnly: true,

init: function () {
var scene = this.el;

Expand Down
2 changes: 2 additions & 0 deletions src/components/scene/xr-mode-ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ module.exports.Component = registerComponent('xr-mode-ui', {
XRMode: {default: 'vr', oneOf: ['vr', 'ar', 'xr']}
},

sceneOnly: true,

init: function () {
var self = this;
var sceneEl = this.el;
Expand Down
7 changes: 1 addition & 6 deletions src/core/a-entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,12 +310,7 @@ class AEntity extends ANode {
// Initialize dependencies first
this.initComponentDependencies(componentName);

// If component name has an id we check component type multiplicity.
if (componentId && !COMPONENTS[componentName].multiple) {
throw new Error('Trying to initialize multiple ' +
'components of type `' + componentName +
'`. There can only be one component of this type per entity.');
}
// Initialize component
component = new COMPONENTS[componentName].Component(this, data, componentId);
if (this.isPlaying) { component.play(); }

Expand Down
14 changes: 14 additions & 0 deletions src/core/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ var emptyInitialOldData = Object.freeze({});
*/
var Component = module.exports.Component = function (el, attrValue, id) {
var self = this;

// If component is sceneOnly check the entity is the scene element
if (this.sceneOnly && !el.isScene) {
throw new Error('Component `' + this.name + '` can only be applied to <a-scene>');
}

// If component name has an id we check component type multiplicity.
if (id && !this.multiple) {
throw new Error('Trying to initialize multiple ' +
'components of type `' + this.name +
'`. There can only be one component of this type per entity.');
}

this.el = el;
this.id = id;
this.attrName = this.name + (id ? '__' + id : '');
Expand Down Expand Up @@ -699,6 +712,7 @@ module.exports.registerComponent = function (name, definition) {
isSinglePropertyObject: NewComponent.prototype.isSinglePropertyObject,
isObjectBased: NewComponent.prototype.isObjectBased,
multiple: NewComponent.prototype.multiple,
sceneOnly: NewComponent.prototype.sceneOnly,
name: name,
parse: NewComponent.prototype.parse,
parseAttrValueForCache: NewComponent.prototype.parseAttrValueForCache,
Expand Down
9 changes: 7 additions & 2 deletions tests/components/scene/fog.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ suite('fog', function () {
setup(function (done) {
this.entityEl = entityFactory();
var el = this.el = this.entityEl.parentNode;
var self = this;

el.addEventListener('loaded', function () {
// Stub scene load to avoid WebGL code.
Expand All @@ -18,8 +17,14 @@ suite('fog', function () {

test('does not set fog for entities', function () {
var entityEl = this.entityEl;
entityEl.setAttribute('fog', '');
try {
entityEl.setAttribute('fog', '');
assert.fail();
} catch (e) {
assert.equal(e.message, 'Component `fog` can only be applied to <a-scene>');
}
assert.notOk(entityEl.object3D.fog);
assert.notOk(entityEl.components['fog']);
});

suite('update', function () {
Expand Down
59 changes: 59 additions & 0 deletions tests/core/component.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,65 @@ suite('Component', function () {
});
});

suite('sceneOnly components', function () {
var el;
var TestComponent;
setup(function () {
el = entityFactory();
delete components.test;
TestComponent = registerComponent('test', { sceneOnly: true });
});

test('allows instantiating sceneOnly component on scene element', function () {
el.sceneEl.setAttribute('test', '');
assert.ok(el.sceneEl.components['test']);
});

test('throws error when instantiating sceneOnly component on entities', function () {
try {
el.setAttribute('test', '');
assert.fail();
} catch (e) {
assert.equal(e.message, 'Component `test` can only be applied to <a-scene>');
}
assert.notOk(el.components['test']);
});
});

suite('multiple components', function () {
var el;
setup(function () {
el = entityFactory();
delete components.test;
});

test('allows multiple instances on element with ids when component has multiple flag', function () {
var TestComponent = registerComponent('test', { multiple: true });
var first = new TestComponent(el, 'data', 'first');
var second = new TestComponent(el, 'data', 'second');
assert.notOk(el.components['test']);
assert.equal(el.components['test__first'], first);
assert.equal(el.components['test__second'], second);
});

test('allows instance without id even when component has multiple flag', function () {
var TestComponent = registerComponent('test', { multiple: true });
var component = new TestComponent(el, 'data', '');
assert.equal(el.components['test'], component);
});

test('throws error when instantiating with id when component does not have multiple flag', function () {
var TestComponent = registerComponent('test', { multiple: false });
try {
var component = new TestComponent(el, 'data', 'some-id');
assert.fail();
} catch (e) {
assert.equal(e.message, 'Trying to initialize multiple components of type `test`. ' +
'There can only be one component of this type per entity.');
}
});
});

suite('third-party components', function () {
var el;
setup(function (done) {
Expand Down

0 comments on commit 807b1de

Please sign in to comment.