Skip to content

Commit 79ffd5d

Browse files
committed
Refactors component updates to minimize the number of string parsings and component data stringifications
1 parent 8e3dd32 commit 79ffd5d

28 files changed

+378
-203
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Animation</title>
6+
<meta name="description" content="Animation - A-Frame">
7+
<script src="../../dist/aframe.js"></script>
8+
</head>
9+
<body>
10+
<a-scene stats="true">
11+
<a-assets>
12+
<a-mixin id="ball" geometry="primitive: sphere; radius:1" material="shader: flat; color: red">
13+
</a-mixin>
14+
<a-mixin id="opacity" attribute="material.opacity" dur="2000" direction="alternate" fill="forwards"
15+
ease="linear" repeat="indefinite" from="0.3" to="1.0"><a-mixin>
16+
<a-mixin id="spin" attribute="rotation" dur="4000"
17+
repeat="indefinite" easing="linear" dur="3000" to="0 0 360"><a-mixin>
18+
</a-assets>
19+
20+
<a-entity position="0 0 80">
21+
<a-entity camera look-controls wasd-controls></a-entity>
22+
</a-entity>
23+
24+
<a-entity id="fans"></a-entity>
25+
26+
</a-scene>
27+
</body>
28+
<script>
29+
var i;
30+
var fansEl = document.querySelector('#fans');
31+
var numRows = 4;
32+
var numFans = 3
33+
var numFanRows = 3;
34+
var numBalls = 11;
35+
var ball =
36+
'<a-entity>' +
37+
'<a-entity mixin="ball">' +
38+
'<a-animation mixin="opacity"></a-animation>' +
39+
'</a-entity>' +
40+
'</a-entity>';
41+
var ballEl;
42+
var rowEl;
43+
var ballInitX = -15;
44+
var fanInitX = -60;
45+
var fanInitY = -40;
46+
var offset;
47+
48+
for (var n = 0; n < numFanRows; ++n) {
49+
var fanRow = document.createElement('a-entity');
50+
fanRow.setAttribute('position', { x: 0, y: fanInitY - (n * fanInitY), z: 0 });
51+
for (var k = 0; k < numFans; ++k) {
52+
var fanEl = document.createElement('a-entity');
53+
fanEl.setAttribute('position', { x: fanInitX - (k * fanInitX), y: 0, z: 0});
54+
var animationEl = document.createElement('a-animation');
55+
animationEl.setAttribute('mixin', 'spin');
56+
fanEl.appendChild(animationEl);
57+
for (var i = 0; i < numRows; ++i) {
58+
var rowEl = document.createElement('a-entity');
59+
rowEl.setAttribute('rotation', { x: 0, y: 0, z: i * (180 / numRows) });
60+
for (var j = 0; j < numBalls; ++j) {
61+
offset = ballInitX + 3 * j;
62+
if (offset === 0) { continue; }
63+
var ballEl = document.createElement('a-entity');
64+
ballEl.setAttribute('position', { x: ballInitX + 3 * j, y: 0, z: 0 });
65+
ballEl.innerHTML = ball;
66+
animationEl = ballEl.querySelector('a-animation');
67+
animationEl.setAttribute('begin', Math.random() * 2000);
68+
rowEl.appendChild(ballEl)
69+
}
70+
fanEl.appendChild(rowEl);
71+
}
72+
fanRow.appendChild(fanEl);
73+
}
74+
fansEl.appendChild(fanRow);
75+
}
76+
</script>
77+
</html>

‎src/components/camera.js‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ module.exports.Component = registerComponent('camera', {
1919
*/
2020
init: function () {
2121
var camera = this.camera = new THREE.PerspectiveCamera();
22+
this.system.addCamera(this.el);
2223
this.el.setObject3D('camera', camera);
2324
},
2425

2526
/**
2627
* Remove camera on remove (callback).
2728
*/
2829
remove: function () {
30+
this.system.removeCamera(this.el);
2931
this.el.removeObject3D('camera');
3032
},
3133

@@ -51,7 +53,7 @@ module.exports.Component = registerComponent('camera', {
5153
// If `active` property changes, or first update, handle active camera with system.
5254
if (data.active && system.activeCameraEl !== this.el) {
5355
// Camera enabled. Set camera to this camera.
54-
system.setActiveCamera(el, camera);
56+
system.setActiveCamera(el);
5557
} else if (!data.active && system.activeCameraEl === this.el) {
5658
// Camera disabled. Set camera to another camera.
5759
system.disableActiveCamera();

‎src/components/geometry.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ function getGeometry (data, schema) {
132132
}
133133
case 'torusKnot': {
134134
return new THREE.TorusKnotGeometry(
135-
data.radius, data.radiusTubular * 2, data.segmentsTubular, data.segmentsRadial,
135+
data.radius, data.radiusTubular * 2, data.segmentsRadial, data.segmentsTubular,
136136
data.p, data.q);
137137
}
138138
default: {

‎src/components/obj-model.js‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* global HTMLElement */
21
var debug = require('../utils/debug');
32
var registerComponent = require('../core/component').registerComponent;
43
var THREE = require('../lib/three');
@@ -39,7 +38,7 @@ module.exports.Component = registerComponent('obj-model', {
3938

4039
if (mtlUrl) {
4140
// .OBJ with an .MTL.
42-
if (HTMLElement.prototype.getAttribute.call(el, 'material')) {
41+
if (el.components.material && el.components.material.attr !== undefined) {
4342
warn('Material component properties are ignored when a .MTL is provided');
4443
}
4544
mtlLoader.setBaseUrl(mtlUrl.substr(0, mtlUrl.lastIndexOf('/') + 1));

‎src/core/a-entity.js‎

Lines changed: 90 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ var AEntity;
99
var isNode = re.isNode;
1010
var debug = utils.debug('core:a-entity:debug');
1111
var registerElement = re.registerElement;
12-
var styleParser = utils.styleParser;
1312

1413
/**
1514
* Entity is a container object that components are plugged into to comprise everything in
@@ -58,7 +57,7 @@ var proto = Object.create(ANode.prototype, {
5857
this.addToParent();
5958
if (!this.isScene) {
6059
this.load();
61-
if (!this.parentNode.paused) { this.play(); }
60+
if (this.parentEl.isPlaying) { this.play(); }
6261
}
6362
}
6463
},
@@ -246,33 +245,29 @@ var proto = Object.create(ANode.prototype, {
246245
* Initialize component.
247246
*/
248247
initComponent: {
249-
value: function (name, isDependency) {
248+
value: function (name, isDependency, attr) {
250249
var component;
251-
var isComponentDefined = checkComponentDefined(this, name);
250+
var isComponentDefined = checkComponentDefined(this, name) || attr !== undefined;
252251

253-
// Check if component is registered and whether component should be iniitalized.
252+
// Check if component is registered and whether component should be initalized.
254253
if (!components[name] || (!isComponentDefined && !isDependency)) {
255254
return;
256255
}
257256

258257
// Initialize dependencies.
259258
this.initComponentDependencies(name);
260259

261-
if (isDependency && !isComponentDefined) {
262-
// Add component if it is a dependency and not yet defined.
263-
this.setAttribute(name, '');
264-
} else {
265-
if (this.isScene && !this.hasAttribute(name) && name in this.defaultComponents) {
266-
// For scene default components, expose them in the DOM.
267-
HTMLElement.prototype.setAttribute.call(this, name, this.defaultComponents[name]);
268-
}
260+
// if (this.isScene && !this.hasAttribute(name) && name in this.defaultComponents) {
261+
// For scene default components, expose them in the DOM.
262+
// HTMLElement.prototype.setAttribute.call(this, name, this.defaultComponents[name]);
263+
// }
269264

270-
// Check if component already initialized.
271-
if (name in this.components) { return; }
265+
// Check if component already initialized.
266+
if (name in this.components) { return; }
267+
268+
component = this.components[name] = new components[name].Component(this, attr);
269+
if (this.isPlaying) { playComponent(component, this.sceneEl); }
272270

273-
component = this.components[name] = new components[name].Component(this);
274-
if (this.isPlaying) { playComponent(component, this.sceneEl); }
275-
}
276271
debug('Component initialized: %s', name);
277272
},
278273
writable: window.debug
@@ -301,14 +296,56 @@ var proto = Object.create(ANode.prototype, {
301296
}
302297
},
303298

299+
getMixedInComponents: {
300+
value: function () {
301+
var mixinEls = this.mixinEls;
302+
var i;
303+
var components = [];
304+
for (i = 0; i < mixinEls.length; ++i) {
305+
var keys = Object.keys(mixinEls[i].attrs);
306+
keys.forEach(function (key) {
307+
components.push(key);
308+
});
309+
}
310+
return components;
311+
}
312+
},
313+
304314
updateComponents: {
305315
value: function () {
306316
var self = this;
307-
var allComponents = Object.keys(components);
308-
allComponents.forEach(updateComponent);
309-
function updateComponent (name) {
310-
var elValue = self.getAttribute(name);
311-
self.updateComponent(name, elValue);
317+
var elValue;
318+
var attrs = {};
319+
var componentName;
320+
var i;
321+
if (!this.hasLoaded) { return; }
322+
var elComponents = Object.keys(this.components);
323+
for (i = 0; i < elComponents.length; ++i) {
324+
attrs[elComponents[i]] = true;
325+
}
326+
var attributes = this.attributes;
327+
for (i = 0; i < attributes.length; ++i) {
328+
if (!components[attributes[i].name]) { continue; }
329+
attrs[attributes[i].name] = true;
330+
}
331+
var mixedInComponents = this.getMixedInComponents();
332+
mixedInComponents.forEach(function (key) {
333+
attrs[key] = true;
334+
});
335+
var defaultComponents = Object.keys(this.defaultComponents);
336+
for (i = 0; i < defaultComponents.length; ++i) {
337+
componentName = defaultComponents[i];
338+
delete attrs[componentName];
339+
elValue = self.getAttribute(componentName) || undefined;
340+
if (components[componentName]) {
341+
this.updateComponent(componentName, elValue);
342+
}
343+
}
344+
attrs = Object.keys(attrs);
345+
for (i = 0; i < attrs.length; ++i) {
346+
componentName = attrs[i];
347+
elValue = self.getAttribute(componentName);
348+
this.updateComponent(componentName, elValue);
312349
}
313350
}
314351
},
@@ -324,15 +361,19 @@ var proto = Object.create(ANode.prototype, {
324361
updateComponent: {
325362
value: function (name, newData) {
326363
var component = this.components[name];
327-
var isDefault = name in this.defaultComponents;
364+
var defaultComponents = this.defaultComponents;
365+
var isDefault = name in defaultComponents;
328366
var isMixedIn = isComponentMixedIn(name, this.mixinEls);
367+
var elValue = this.getAttribute(defaultComponents[name]);
368+
329369
if (component) {
330370
// Attribute was removed, remove component if:
331371
// 1. If component not defined in the defaults/mixins/attribute.
332372
// 2. If new data is null, then not a default component and component is not defined
333373
// via mixins
334-
if (!checkComponentDefined(this, name) ||
335-
newData === null && !isDefault && !isMixedIn) {
374+
if (!checkComponentDefined(this, name) && !elValue ||
375+
newData === null) {
376+
if (isDefault || isMixedIn) { return; }
336377
this.removeComponent(name);
337378
return;
338379
}
@@ -341,7 +382,7 @@ var proto = Object.create(ANode.prototype, {
341382
return;
342383
}
343384
// Component not yet initialized. Initialize component.
344-
this.initComponent(name);
385+
this.initComponent(name, false, newData);
345386
}
346387
},
347388

@@ -356,8 +397,9 @@ var proto = Object.create(ANode.prototype, {
356397
var component = components[attr];
357398
if (component) {
358399
this.setEntityAttribute(attr, undefined, null);
400+
} else {
401+
HTMLElement.prototype.removeAttribute.call(this, attr);
359402
}
360-
HTMLElement.prototype.removeAttribute.call(this, attr);
361403
}
362404
},
363405

@@ -372,7 +414,7 @@ var proto = Object.create(ANode.prototype, {
372414
var sceneEl = this.sceneEl;
373415

374416
// Already playing.
375-
if (this.isPlaying) { return; }
417+
if (this.isPlaying || !this.hasLoaded) { return; }
376418
this.isPlaying = true;
377419

378420
// Wake up all components.
@@ -427,18 +469,15 @@ var proto = Object.create(ANode.prototype, {
427469
*/
428470
setEntityAttribute: {
429471
value: function (attr, oldVal, newVal) {
430-
var component = components[attr];
431-
oldVal = oldVal || this.getAttribute(attr);
432-
// When creating entities programatically and setting attributes, it is not part
433-
// of the scene until it is inserted into the DOM. This does not apply to scenes as
434-
// scenes depend on its child entities to load.
435-
if (!this.hasLoaded && !this.isScene) { return; }
472+
if (components[attr]) {
473+
this.updateComponent(attr, newVal);
474+
return;
475+
}
436476
if (attr === 'mixin') {
437477
this.updateStateMixins(newVal, oldVal);
438478
this.updateComponents();
439479
return;
440480
}
441-
if (component) { this.updateComponent(attr, newVal); }
442481
}
443482
},
444483

@@ -464,25 +503,25 @@ var proto = Object.create(ANode.prototype, {
464503
var self = this;
465504
var component = this.components[attr] || components[attr];
466505
var partialComponentData;
467-
value = value === undefined ? '' : value;
468506
var componentObj = value; // Deserialized value to send to the component.
469507
var componentStr = value; // Serialized value to send to the DOM.
470-
var oldValue;
508+
var oldValue = {};
471509

472510
if (component) {
473511
if (typeof value === 'string' && componentPropValue !== undefined) {
474512
// Update currently-defined component data with the new property value.
475513
// Use native setAttribute in order not to double-parse properties.
476-
partialComponentData = styleParser.parse(
477-
HTMLElement.prototype.getAttribute.call(this, attr)) || {};
514+
partialComponentData = component.attr || {};
478515
partialComponentData[value] = componentPropValue;
479516
componentObj = partialComponentData;
480517
}
518+
oldValue = utils.extend({}, component.attr);
481519
componentStr = component.stringify(componentObj);
520+
self.setEntityAttribute(attr, oldValue, componentObj);
521+
} else {
522+
ANode.prototype.setAttribute.call(self, attr, componentStr);
523+
if (attr === 'mixin') { self.setEntityAttribute('mixin', undefined, componentObj); }
482524
}
483-
oldValue = this.getAttribute(attr);
484-
ANode.prototype.setAttribute.call(self, attr, componentStr);
485-
self.setEntityAttribute(attr, oldValue, componentObj);
486525
},
487526
writable: window.debug
488527
},
@@ -500,10 +539,12 @@ var proto = Object.create(ANode.prototype, {
500539
*/
501540
getAttribute: {
502541
value: function (attr) {
503-
var component = this.components[attr] || components[attr];
504-
var value = HTMLElement.prototype.getAttribute.call(this, attr);
505-
if (!component || typeof value !== 'string') { return value; }
506-
return component.parse(value, true);
542+
var component = this.components[attr];
543+
// If there's a cached value we just return it
544+
if (component && component.attr !== undefined) {
545+
return component.attr;
546+
}
547+
return HTMLElement.prototype.getAttribute.call(this, attr);
507548
},
508549
writable: window.debug
509550
},
@@ -570,6 +611,8 @@ function checkComponentDefined (el, name) {
570611
// Check if element contains the component.
571612
if (el.hasAttribute(name)) { return true; }
572613

614+
if (el.components[name]) { return true; }
615+
573616
return isComponentMixedIn(name, el.mixinEls);
574617
}
575618

0 commit comments

Comments
 (0)