Skip to content

Commit a8a4f06

Browse files
committed
Merge pull request #695 from ngokevin/waitload
have entities wait for child entities to load before emitting load
2 parents 561f317 + a5eed97 commit a8a4f06

File tree

14 files changed

+337
-191
lines changed

14 files changed

+337
-191
lines changed

src/components/camera.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ module.exports.Component = registerComponent('camera', {
1616
var camera = this.camera = new THREE.PerspectiveCamera();
1717
var el = this.el;
1818
el.object3D.add(camera);
19-
el.sceneEl.cameraEl = el;
19+
el.sceneEl.registerCamera(el);
2020
},
2121

2222
/**

src/components/look-at.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ module.exports.Component = registerComponent('look-at', {
5858
warn('"' + targetSelector + '" does not point to a valid entity to look-at');
5959
return;
6060
}
61-
if (!targetEl.object3D) {
61+
if (!targetEl.hasLoaded) {
6262
return targetEl.addEventListener('loaded', function () {
6363
self.beginTracking(targetEl);
6464
});

src/core/a-animation.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,11 @@ module.exports.AAnimation = registerElement('a-animation', {
7272
var el = self.el = self.parentNode;
7373

7474
if (el.isNode) {
75-
init();
75+
if (el.hasLoaded) {
76+
init();
77+
} else {
78+
el.addEventListener('loaded', init.bind(self));
79+
}
7680
} else {
7781
// To handle elements that are not yet `<a-entity>`s (e.g., templates).
7882
el.addEventListener('nodeready', init.bind(self));

src/core/a-assets.js

Lines changed: 14 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,15 @@
1-
/* global HTMLElement */
2-
var re = require('./a-register-element');
3-
var utils = require('../utils/');
4-
5-
var isNode = re.isNode;
6-
var registerElement = re.registerElement;
7-
8-
module.exports = registerElement(
9-
'a-assets',
10-
{
11-
prototype: Object.create(
12-
HTMLElement.prototype,
13-
{
14-
attachedCallback: {
15-
value: function () {
16-
this.attachEventListeners();
17-
}
18-
},
19-
20-
attachEventListeners: {
21-
value: function () {
22-
var self = this;
23-
var assetLoaded = this.assetLoaded.bind(this);
24-
this.assetsPending = 0;
25-
var children = this.querySelectorAll('*');
26-
Array.prototype.slice.call(children).forEach(countElement);
27-
28-
if (!this.assetsPending) {
29-
assetLoaded();
30-
}
31-
32-
function countElement (node) {
33-
if (!isNode(node)) { return; }
34-
if (!node.hasLoaded) {
35-
attachEventListener(node);
36-
self.assetsPending++;
37-
}
38-
}
39-
40-
function attachEventListener (node) {
41-
node.addEventListener('loaded', assetLoaded);
42-
}
43-
}
44-
},
45-
46-
assetLoaded: {
47-
value: function () {
48-
this.assetsPending--;
49-
if (this.assetsPending <= 0) {
50-
this.load();
51-
}
52-
}
53-
},
54-
55-
load: {
56-
value: function () {
57-
// To prevent emitting the loaded event more than once.
58-
if (this.hasLoaded) { return; }
59-
this.hasLoaded = true;
60-
var data = { bubbles: false, detail: {} };
61-
utils.fireEvent(this, 'loaded', data);
62-
}
63-
}
1+
var ANode = require('./a-node');
2+
var registerElement = require('./a-register-element').registerElement;
3+
4+
/**
5+
* TODO: Block on assets before loading.
6+
*/
7+
module.exports = registerElement('a-assets', {
8+
prototype: Object.create(ANode.prototype, {
9+
load: {
10+
value: function () {
11+
ANode.prototype.load.call(this);
6412
}
65-
)
66-
}
67-
);
13+
}
14+
})
15+
});

src/core/a-entity.js

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ var log = debug('core:a-entity');
1010
var error = debug('core:a-entity:error');
1111
var registerElement = re.registerElement;
1212

13+
var AEntity;
14+
1315
/**
1416
* Entity element definition.
1517
* Entities represent all elements that are part of the scene, and always have
@@ -25,7 +27,7 @@ var registerElement = re.registerElement;
2527
* @member {object} object3D - three.js object.
2628
* @member {array} states
2729
*/
28-
var proto = {
30+
var proto = Object.create(ANode.prototype, {
2931
defaults: {
3032
value: {
3133
position: '',
@@ -37,6 +39,7 @@ var proto = {
3739

3840
createdCallback: {
3941
value: function () {
42+
this.isEntity = true;
4043
this.states = [];
4144
this.components = {};
4245
this.object3D = new THREE.Mesh();
@@ -52,7 +55,9 @@ var proto = {
5255
attachedCallback: {
5356
value: function () {
5457
this.addToParent();
55-
this.load();
58+
if (!this.isScene) {
59+
this.load();
60+
}
5661
}
5762
},
5863

@@ -155,16 +160,18 @@ var proto = {
155160

156161
load: {
157162
value: function () {
158-
// To prevent calling load more than once
159163
if (this.hasLoaded) { return; }
160-
// Handle to the associated DOM element
161164
this.object3D.el = this;
162-
// It attaches itself to the threejs parent object3D
165+
166+
// Attach to parent object3D.
163167
this.addToParent();
164-
// Components initialization
165-
this.updateComponents();
166-
// Call the parent class
167-
ANode.prototype.load.call(this);
168+
169+
if (this.isScene) {
170+
ANode.prototype.load.call(this, this.updateComponents.bind(this));
171+
} else {
172+
ANode.prototype.load.call(this, this.updateComponents.bind(this),
173+
function (el) { return el.isEntity; });
174+
}
168175
},
169176
writable: window.debug
170177
},
@@ -175,6 +182,25 @@ var proto = {
175182
}
176183
},
177184

185+
/**
186+
* @returns {array} Direct children that are entities.
187+
*/
188+
getChildEntities: {
189+
value: function () {
190+
var children = this.children;
191+
var childEntities = [];
192+
193+
for (var i = 0; i < this.children.length; i++) {
194+
var child = children[i];
195+
if (child instanceof AEntity) {
196+
childEntities.push(child);
197+
}
198+
}
199+
200+
return childEntities;
201+
}
202+
},
203+
178204
/**
179205
* Check if a component is defined for an entity, including defaults and mixins.
180206
*
@@ -297,7 +323,6 @@ var proto = {
297323
newData = component.parse(newData);
298324
}
299325
// Component already initialized. Update component.
300-
// TODO: update component attribute more granularly.
301326
component.updateAttributes(newData);
302327
return;
303328
}
@@ -341,9 +366,10 @@ var proto = {
341366
value: function (attr, oldVal, newVal) {
342367
var component = components[attr];
343368
oldVal = oldVal || this.getAttribute(attr);
344-
// When creating objects programatically and setting attributes, the object is not part
345-
// of the scene until is inserted into the DOM.
346-
if (!this.hasLoaded) { return; }
369+
// When creating entities programatically and setting attributes, it is not part
370+
// of the scene until it is inserted into the DOM. This does not apply to scenes as
371+
// scenes depend on its child entities to load.
372+
if (!this.hasLoaded && !this.isScene) { return; }
347373
if (attr === 'mixin') {
348374
this.updateStateMixins(newVal, oldVal);
349375
this.updateComponents();
@@ -465,8 +491,9 @@ var proto = {
465491
return is;
466492
}
467493
}
468-
};
494+
});
469495

470-
module.exports = registerElement('a-entity', {
471-
prototype: Object.create(ANode.prototype, proto)
496+
AEntity = registerElement('a-entity', {
497+
prototype: proto
472498
});
499+
module.exports = AEntity;

src/core/a-node.js

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,16 @@ var utils = require('../utils/');
44

55
/**
66
* Base class for A-Frame that manages loading of objects.
7+
*
8+
* Nodes can be modified using mixins.
9+
* Nodes emit a `loaded` event when they and their children have initialized. Which children
10+
* to wait for can be customized using `loadChildrenFilter`.
711
*/
812
module.exports = registerElement('a-node', {
913
prototype: Object.create(HTMLElement.prototype, {
1014
createdCallback: {
1115
value: function () {
16+
this.hasLoaded = false;
1217
this.isNode = true;
1318
this.mixinEls = [];
1419
this.mixinObservers = {};
@@ -18,28 +23,61 @@ module.exports = registerElement('a-node', {
1823
attachedCallback: {
1924
value: function () {
2025
var mixins = this.getAttribute('mixin');
26+
2127
this.sceneEl = document.querySelector('a-scene');
2228
this.emit('nodeready', {}, false);
2329
if (mixins) { this.updateMixins(mixins); }
2430
}
2531
},
2632

27-
detachedCallback: {
28-
value: function () { /* no-op */ }
29-
},
30-
3133
attributeChangedCallback: {
3234
value: function (attr, oldVal, newVal) {
3335
if (attr === 'mixin') { this.updateMixins(newVal, oldVal); }
3436
}
3537
},
3638

39+
detachedCallback: {
40+
value: function () { /* no-op */ }
41+
},
42+
43+
/**
44+
* Wait for children to load, if any.
45+
* Then emit `loaded` event and set `hasLoaded`.
46+
*/
3747
load: {
48+
value: function (cb, childFilter) {
49+
var children;
50+
var childrenLoaded;
51+
var self = this;
52+
53+
if (self.hasLoaded) { return; }
54+
55+
// Default to waiting for all nodes.
56+
childFilter = childFilter || function (el) { return el.isNode; };
57+
58+
// Wait for children to load (if any), then load.
59+
children = this.getChildren();
60+
childrenLoaded = children.filter(childFilter).map(function (child) {
61+
return new Promise(function waitForLoaded (resolve) {
62+
child.addEventListener('loaded', resolve);
63+
});
64+
});
65+
66+
Promise.all(childrenLoaded).then(function emitLoaded () {
67+
if (cb) { cb(); }
68+
self.hasLoaded = true;
69+
self.emit('loaded', {}, false);
70+
});
71+
}
72+
},
73+
74+
getChildren: {
3875
value: function () {
39-
// To prevent emmitting the loaded event more than once
40-
if (this.hasLoaded) { return; }
41-
this.hasLoaded = true;
42-
this.emit('loaded', {}, false);
76+
var children = [];
77+
for (var i = 0; i < this.children.length; i++) {
78+
children.push(this.children[i]);
79+
}
80+
return children;
4381
}
4482
},
4583

0 commit comments

Comments
 (0)