-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Skip type checking if an object is passed through setAttribute twice #2679
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
Conversation
|
Doesn't the closure pattern cause objects to get shared across different instances of the same type of component? We've ran into issues with that before. Sometimes I wish for some of the advantages of closure-based component definitions. |
src/core/component.js
Outdated
| * @return {object} The component data | ||
| */ | ||
| function buildData (el, name, attrName, schema, elData, silent) { | ||
| function buildData (el, name, attrName, schema, elData, oldElData, silent, skipTypeChecking) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should just plop this function back into the component prototype as a method now, too many arguments.
|
You should not use the closure pattern to store state of the component. In this case is just an auxiliary variable to make a temporary computation so it's fine. |
|
30-40% improvements here in the 5k cubes scene |
c9c8cf3 to
c0f190e
Compare
examples/performance/cubes/setup.js
Outdated
| @@ -0,0 +1,46 @@ | |||
| var URL_PARAMS = getUrlParams(); | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be nice to encapsulate this in a main component <a-scene main>
| */ | ||
| updateComponent: { | ||
| value: function (attr, attrValue) { | ||
| value: function (attr, attrValue, clobber) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd doc-string this to note .setAttribute passes this in
src/core/component.js
Outdated
| var el = this.el; | ||
| var isSinglePropSchema = isSingleProp(this.schema); | ||
| var oldData = extendProperties({}, this.data, isSinglePropSchema); | ||
| var skipTypeChecking = typeof this.previousAttrValue === 'object' && attrValue === this.previousAttrValue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd comment the conditions of type checking (If the passed data is an object and the object has not changed)
src/core/component.js
Outdated
| var isSinglePropSchema = isSingleProp(this.schema); | ||
| var oldData = extendProperties({}, this.data, isSinglePropSchema); | ||
| var skipTypeChecking = typeof this.previousAttrValue === 'object' && attrValue === this.previousAttrValue; | ||
| var currentAttrValue = !clobber && this.attrValue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's !clobber && this.attrValue mean? Can you add a comment
src/core/component.js
Outdated
| var oldData = extendProperties({}, this.data, isSinglePropSchema); | ||
| var skipTypeChecking = typeof this.previousAttrValue === 'object' && attrValue === this.previousAttrValue; | ||
| var currentAttrValue = !clobber && this.attrValue; | ||
| this.previousAttrValue = attrValue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can define this in the constructor as well this.previousAttrValue = undefined
| this.previousAttrValue = attrValue; | ||
|
|
||
| if (attrValue !== undefined) { this.updateCachedAttrValue(attrValue); } | ||
| if (!el.hasLoaded) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you comment this clause?
src/core/component.js
Outdated
| this.updateSchema(this.buildData(attrValue, currentAttrValue, true)); | ||
| } | ||
| this.data = buildData(el, this.name, this.attrName, this.schema, this.attrValue); | ||
| this.data = this.buildData(attrValue, currentAttrValue, false, skipTypeChecking); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could have buildData's third argument be an options object so we can be explicit vs passing in bools
buildData(attrValue, currentAttrValue, {skipTypeChecking: true, silent: false})
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's maybe premature if we only have two flags? It's also not a pattern we have in other parts of the code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not concerned about the number of flags, but being explicit, because calling a function build(value, oldValue, true, false), hard to know what true/false does in that order.
But maybe not so we don't create another object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
buildData is not part of the public API and only used in two places in the whole code base so the chances of misuse are small. And also what you said. It's called only in two places but invoked on every component update so better to avoid new object instantations.
src/core/component.js
Outdated
| this.data = buildData(el, this.name, this.attrName, this.schema, this.attrValue); | ||
| this.data = this.buildData(attrValue, currentAttrValue, false, skipTypeChecking); | ||
|
|
||
| if (attrValue !== undefined) { this.updateCachedAttrValue(attrValue); } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious when would it be undefined?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when the component is defined via mixins for example. there's not an attrValue on the entity
src/core/component.js
Outdated
| * @param {boolean} skipTypeChecking - Skip type checking and cohercion. | ||
| * @return {object} The component data | ||
| */ | ||
| buildData: function (elData, oldElData, silent, skipTypeChecking) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: half the code calls it elData and half calls it attrValue, should we normalize?
| el.updateComponentProperty(componentName, propName, propertyValue); | ||
| } | ||
|
|
||
| /** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic here and below are handled by which pieces now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a lot of redundancy in that logic. The flag clobber basically replaces all of it
a8f06b3 to
25ca595
Compare
25ca595 to
1a45e04
Compare
I'm trying to find ways to optimize
setAttribute/getAttributecalls specially when they're called once per frame like for example in a component'stickmethod.My first attempt is to disable type checking when
setAttributeis called twice with the same object. Types are checked the first time and then the object is considered trusted. This would allow to construct yourtickmethod in the following way:This aligns with the good practice of avoiding object allocations inside your render loop. This optimization yields a 30%-40% percent FPS improvement with respect to master when rotating 2500 cubes using a tick like the above one.