Skip to content

Commit

Permalink
Merge pull request aframevr#2679 from dmarcos/cachObjectSetAttribute
Browse files Browse the repository at this point in the history
Skip type checking if an object is passed through setAttribute twice
  • Loading branch information
ngokevin authored May 23, 2017
2 parents a2a241f + 1a45e04 commit 9eacbb6
Show file tree
Hide file tree
Showing 10 changed files with 339 additions and 140 deletions.
13 changes: 13 additions & 0 deletions docs/introduction/best-practices.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,19 @@ ways to help improve performance of an A-Frame scene:
handlers to hook into the global render loop. Use utilities such as
`AFRAME.utils.throttleTick` to limit the number of times the `tick` handler
is run if appropriate.
- Avoid instantiating variables in the `tick` handler to minimize garbage collection cycles. If you are going to continously modify a component in the tick, make sure to pass the same object with updated properties. A-Frame will keep track of the latest passed object and disable type checking on subsequent calls for an extra speed boost. This is an example of a recommended tick function that modifies the rotation on every tick:
```js
AFRAME.registerComponent('foo', {
tick: function () {
var rotationTmp = this.rotationTmp = this.rotationTmp || {x: 0, y: 0, z: 0};
var rotation = el.getAttribute('rotation');
rotationTmp.x = rotation.x + 0.1;
rotationTmp.y = rotation.y + 0.1;
rotationTmp.z = rotation.z + 0.1;
el.setAttribute('rotation', rotationTmp);
}
});
```

### `tick` Handlers

Expand Down
72 changes: 72 additions & 0 deletions examples/performance/cubes/components.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* global AFRAME, THREE */
function randomIncRad (multiplier) {
return multiplier * Math.random();
}

function randomIncDeg (multiplier) {
return randomIncRad(multiplier) * THREE.Math.RAD2DEG;
}

// COMPONENTS
// ------------------
AFRAME.registerComponent('rotate-obj3d', {
tick: function () {
var el = this.el;
el.object3D.rotation.x += randomIncRad(0.1);
el.object3D.rotation.y += randomIncRad(0.2);
el.object3D.rotation.z += randomIncRad(0.3);
}
});

AFRAME.registerComponent('rotate-get-obj3d', {
tick: function () {
var el = this.el;
var rotation = el.getAttribute('rotation');

rotation.x += randomIncRad(0.1);
rotation.y += randomIncRad(0.2);
rotation.z += randomIncRad(0.3);

el.object3D.rotation.x = rotation.x;
el.object3D.rotation.y = rotation.y;
el.object3D.rotation.z = rotation.z;
}
});

AFRAME.registerComponent('rotate-obj3d-set', {
tick: function () {
var el = this.el;
var rotation = el.getAttribute('rotation');

rotation.x += randomIncDeg(0.1);
rotation.y += randomIncDeg(0.2);
rotation.z += randomIncDeg(0.3);

el.setAttribute('rotation', rotation);
}
});

AFRAME.registerComponent('rotate-get-set', {
tick: function () {
var el = this.el;
var rotationAux = this.rotationAux = this.rotationAux || {x: 0, y: 0, z: 0};
var rotation = el.getAttribute('rotation');
rotationAux.x = rotation.x + randomIncDeg(0.1);
rotationAux.y = rotation.y + randomIncDeg(0.2);
rotationAux.z = rotation.z + randomIncDeg(0.3);
el.setAttribute('rotation', rotationAux);
}
});

AFRAME.registerComponent('rotate-get-settext', {
tick: function () {
var el = this.el;
var rotation = el.getAttribute('rotation');

rotation.x += randomIncDeg(0.1);
rotation.y += randomIncDeg(0.2);
rotation.z += randomIncDeg(0.3);

el.setAttribute('rotation', rotation.x + ' ' + rotation.y + ' ' + rotation.z);
}
});
45 changes: 45 additions & 0 deletions examples/performance/cubes/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<!DOCTYPE html>
<html>
<head>
<title>Rotating Cubes Performance Test - A-Frame</title>
<meta name="description" content="">
<script src="../../../dist/aframe-master.js"></script>
<script src="utils.js"></script>
<script src="components.js"></script>
<script src="main.js"></script>
</head>
<style>
body {
color: #cccccc;
font-family: Monospace;
font-size: 13px;
text-align: center;
margin: 0px;
overflow: hidden;
}

#info {
background-color: #000;
position: absolute;
top: 0px;
width: 100%;
padding: 5px;
z-index: 9999;
}
#info a {
color: #fff;
}
</style>
<body>
<div id="info">
<a href="?numobjects=5000">none</a>
<a href="?component=rotate-obj3d&numobjects=5000">rotate-obj3d</a>
<a href="?component=rotate-get-obj3d&numobjects=5000">rotate-get-obj3d</a>
<a href="?component=rotate-obj3d-set&numobjects=5000">rotate-obj3d-set</a>
<a href="?component=rotate-get-set&numobjects=5000">rotate-get-set</a>
<a href="?component=rotate-get-settext&numobjects=5000">rotate-get-settext</a>
</div>
<a-scene stats main></a-scene>
</body>
</html>

49 changes: 49 additions & 0 deletions examples/performance/cubes/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/* global AFRAME */
AFRAME.registerComponent('main', {
init: function () {
var urlParams = this.getUrlParams();
var defaultNumObjects = 5000;
var numObjects = urlParams.numobjects || defaultNumObjects;
this.cubeDistributionWidth = 100;
for (var i = 0; i < numObjects; i++) {
var cubeEl = document.createElement('a-entity');

if (urlParams.component) {
cubeEl.setAttribute(urlParams.component, '');
}
cubeEl.setAttribute('position', this.getRandomPosition());
cubeEl.setAttribute('geometry', {primitive: 'box'});
cubeEl.setAttribute('material', {color: this.getRandomColor(), shader: 'flat'});
this.el.sceneEl.appendChild(cubeEl);
}
},

getUrlParams: function () {
var match;
var pl = /\+/g; // Regex for replacing addition symbol with a space
var search = /([^&=]+)=?([^&]*)/g;
var decode = function (s) { return decodeURIComponent(s.replace(pl, ' ')); };
var query = window.location.search.substring(1);
var urlParams = {};

match = search.exec(query);
while (match) {
urlParams[decode(match[1])] = decode(match[2]);
match = search.exec(query);
}
return urlParams;
},

getRandomPosition: function () {
var cubeDistributionWidth = this.cubeDistributionWidth;
return {
x: Math.random() * cubeDistributionWidth - cubeDistributionWidth / 2,
y: Math.random() * cubeDistributionWidth - cubeDistributionWidth / 2,
z: Math.random() * cubeDistributionWidth - cubeDistributionWidth
};
},

getRandomColor: function () {
return '#' + ('000000' + Math.random().toString(16).slice(2, 8).toUpperCase()).slice(-6);
}
});
38 changes: 8 additions & 30 deletions src/core/a-entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -511,9 +511,10 @@ var proto = Object.create(ANode.prototype, {
*
* @param {string} attr - Component name.
* @param {object} attrValue - The value of the DOM attribute.
* @param {bollean} clobber - if the new attrValue will ccompletely replace the previous properties
*/
updateComponent: {
value: function (attr, attrValue) {
value: function (attr, attrValue, clobber) {
var component = this.components[attr];
var isDefault = attr in this.defaultComponents;
if (component) {
Expand All @@ -522,7 +523,7 @@ var proto = Object.create(ANode.prototype, {
return;
}
// Component already initialized. Update component.
component.updateProperties(attrValue);
component.updateProperties(attrValue, clobber);
return;
}
// Component not yet initialized. Initialize component.
Expand Down Expand Up @@ -682,6 +683,8 @@ var proto = Object.create(ANode.prototype, {
*/
setAttribute: {
value: function (attrName, arg1, arg2) {
var arg1Type = typeof arg1;
var clobber;
var componentName;
var isDebugMode;

Expand All @@ -690,12 +693,11 @@ var proto = Object.create(ANode.prototype, {

// Determine which type of setAttribute to call based on the types of the arguments.
if (COMPONENTS[componentName]) {
if (typeof arg1 === 'string' && typeof arg2 !== 'undefined') {
if (arg1Type === 'string' && typeof arg2 !== 'undefined') {
singlePropertyUpdate(this, attrName, arg1, arg2);
} else if (typeof arg1 === 'object' && arg2 === true) {
multiPropertyClobber(this, attrName, arg1);
} else {
componentUpdate(this, attrName, arg1);
clobber = arg1Type !== 'object' || (arg1Type === 'object' && arg2 === true);
this.updateComponent(attrName, arg1, clobber);
}

// In debug mode, write component data up to the DOM.
Expand All @@ -714,30 +716,6 @@ var proto = Object.create(ANode.prototype, {
el.updateComponentProperty(componentName, propName, propertyValue);
}

/**
* Just update multiple component properties at once for a multi-property component.
* >> setAttribute('foo', {bar: 'baz'})
*/
function componentUpdate (el, componentName, propValue) {
var component = el.components[componentName];
if (component && typeof propValue === 'object') {
// Extend existing component attribute value.
el.updateComponent(
componentName,
utils.extendDeep(utils.extendDeep({}, component.attrValue), propValue));
} else {
el.updateComponent(componentName, propValue);
}
}

/**
* Pass in complete data set for a multi-property component.
* >> setAttribute('foo', {bar: 'baz'}, true)
*/
function multiPropertyClobber (el, componentName, propObject) {
el.updateComponent(componentName, propObject);
}

/**
* Just update one of the component properties.
* >> setAttribute('id', 'myEntity')
Expand Down
Loading

0 comments on commit 9eacbb6

Please sign in to comment.