-
-
Notifications
You must be signed in to change notification settings - Fork 4k
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
enable non-recursive raycaster (fixes #2329) #2331
Conversation
We need to fix the tests |
this is turning out to be more insidious than expected... still working on it i can see now why the implementation did what it did, in that once the change makes entities require geometry in order to intersect, a slew of the tests break because they never actually cared, and doing it correctly is not as simple as one would expect. (frustrating, because functionally things work correctly...) |
looks like there is an issue with refreshObjects() happening before geometry is attached (and thus there are no children of the entity's object3D to add yet). if there is some better way to understand when refreshObjects() can be invoked correctly, that would be most welcome... |
ok now I am a little baffled, as |
Probably just some race condition or timing issue. |
Codecov Report@@ Coverage Diff @@
## master #2331 +/- ##
=========================================
+ Coverage 85.97% 86% +0.03%
=========================================
Files 109 109
Lines 4519 4538 +19
=========================================
+ Hits 3885 3903 +18
- Misses 634 635 +1
Continue to review full report at Codecov.
|
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.
T< TIME> [FATAL] AppErrorHandler: Error #1132: Invalid JSON parse input.
SyntaxError: Error #1132: Invalid JSON parse input.
at Error$/throwError()
at JSON$/parse()
at com.vmware.vsphere.client.model.vm.config.devices.connection::DeviceControllerServerConnection$/getConnectedCdroms()
at com.vmware.vsphere.client.views.vm.config.devices.connection::DeviceSummaryEntry/onMenuButtonClick()
Logger Stack Trace:
at com.vmware.vsphere.client.util::AppErrorHandler/onUncaughtError()
latest PR incorporates this...
|
5d719c1
to
7aac032
Compare
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.
?/?
Any update about this fix ? |
all the tests work, the latest version of PR uses strategy suggested, I believe the raycaster interval fix was merged into 0.5.0 separately, |
7aac032
to
ae0e455
Compare
What did we think about just removing the |
it can be significantly more expensive to do recursive raycast where non-recursive suffices as well, but perhaps @maissani and others can comment |
@machenmusik did you still want to get this merged? |
if it still works, sure... @maissani @donmccurdy is this PR still useful to you? |
I've forgotten the context here a bit, but as far as I remember: With
|
src/components/raycaster.js
Outdated
el.removeEventListener('loaded', nowRefresh); | ||
self.refreshObjects(); | ||
}); | ||
} |
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.
This is waiting for the current element, presumably the cursor, to load, right? Seems like a flaky indicator for the state of the rest of the scene. Plausible alternatives:
- Refresh on first tick.
- Refresh on scene load.
- Refresh every time the scene has a loaded event, to pick up new entities.
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.
wow this was from a long time ago... refreshing my memory as well...
I think this was supposed to be the last one, but listener should be on sceneEl not el
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.
looking at it a little more closely now, shouldn't this happen when children are attached somewhere in the scene?
this.el.sceneEl.addEventListener('child-attached', this.refreshOnceChildLoaded);
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 think I originally had some changes that would defer this work until next actual raycast (to coalesce / avoid spurious invocations) - but that seems to have gone, should we bring it back?
src/components/raycaster.js
Outdated
return; | ||
// If objects not defined, intersect with everything. | ||
if (objectsAreEls) { | ||
objects = this.el.sceneEl.querySelectorAll(data.objects); |
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.
IMO this code would be more readable if, by the end of this first if/else block, the objects
array is guaranteed to contain only Object3Ds. And, we can avoid calling objects.length
on a NodeList in a loop; that's O(n²). Instead:
objects = this.el.sceneEl.querySelectorAll(data.objects);
for (var i = 0, len = objects.length; i < len; i++) {
this.objects.push(objectEls[i].object3D);
}
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.
had been assuming JIT would hoist the length call, but you're right that it can be explicit.
src/components/raycaster.js
Outdated
// If there aren't any children, then until a refresh after geometry loads, | ||
// raycast won't see this object... but that should happen automatically. | ||
} | ||
} |
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.
With the change above, this block reduces to:
for (i = 0; i < objects.length; i++) {
var children = objects[i].children;
if (children) {
this.objects.push.apply(this.objects, children);
}
}
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.
(presumably this wants the explicit length hoisting described above)
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.
Actually not here, on the explicit length hoisting. With normal arrays, doing i = 0; i < array.length; i++
is completely fine, clean, and fast.
But if you have a NodeList instead of an array (the result of querySelectorAll()
is a NodeList, not an array) then calling .length
is slower, as described here.
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.
Heh, ok it's more complicated than I thought, there are "live" and "static" NodeLists, and the result of querySelectorAll() is the latter, which means it can probably be hoisted anyway. Live NodeLists are more trouble. Feel free to ignore my concern and I'll just patch it if I actually run into a noticeable performance issue. :)
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.
ok, that's how I thought it worked, but I already proactively patched since code is still quite readable that way
since there appears to be a wrapper around an entity's object3D, use its children instead
… box add geometry so that raycaster actually adds something wait until next tick to satisfy Firefox; remove extra tick
ae0e455
to
b2755b1
Compare
(also rebased against something more recent) |
@donmccurdy @dmarcos @ngokevin anything else on this one? |
src/components/raycaster.js
Outdated
*/ | ||
refreshOnceChildLoaded: function (evt) { | ||
var self = this; | ||
var el = evt.detail.el; |
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 use childEl
to not confuse with the raycaster.
src/components/raycaster.js
Outdated
var self = this; | ||
var el = evt.detail.el; | ||
if (!el) { return; } | ||
if (el.hasLoaded) { this.refreshObjects(); } else { |
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.
Let's put else
blocks on their own level.
if (clause) {
// ...
} else {
// ...
}
src/components/raycaster.js
Outdated
var objectEls; | ||
var len; | ||
var objects; | ||
var objectsAreEls = data.objects ? this.el.sceneEl.querySelectorAll(data.objects) : null; |
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.
Why the logic change here? Previously, we had:
if (data.objects) {
// Run query selector.
} else {
// Intersect with everything.
}
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.
see @donmccurdy recommendations above -- basically, this.objects should have only object3D when done, so the first part is exactly that, and the second part grabs the child object3D values because the top-level group one is useless.
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: objectsAreEls
sounds like a boolean to me. Maybe call it objectEls
?
src/components/raycaster.js
Outdated
for (i = 0; i < objectEls.length; i++) { | ||
this.objects.push(objectEls[i].object3D); | ||
if (objectsAreEls) { | ||
for (objects = [], i = 0, len = objectsAreEls.length; i < len; i++) { |
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 objects = []
assignment in the loop is a bit hard to follow. We also don't need len
variable for a for loop for (i = 0; i < objectEls.length; i++)
.
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.
len came from @donmccurdy request - recanted after already done - be easier if y'all would agree with each other first ;-)
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 see, sounds fine. We can move the assignment out though
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.
Haha yeah sorry. My worry about objectsAreEls.length
performance turned out to be unnecessary. This loop can just use normal syntax. :)
src/components/raycaster.js
Outdated
} | ||
|
||
// If objects not defined, intersect with everything. | ||
this.objects = this.el.sceneEl.object3D.children; | ||
for (this.objects = [], i = 0, len = objects.length; i < len; i++) { |
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.
Isn't this only necessary for non-recursive raycasting?
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.
see @donmccurdy recommendations above -- basically, this.objects should have only object3D when done, so we always do this second step 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.
Right, my preference would be to give objects
a consistent type Array<Object3D>
as early as possible, rather than checking type in later loops.
And yes — this entire loop is only necessary for non-recursive raycasting. Maybe saves the raycaster some very trivial amount of work, too.
tests/components/raycaster.test.js
Outdated
sceneEl.appendChild(newEl); | ||
|
||
function eventuallyDoAssert () { | ||
if (newEl.hasLoaded) { doAssert(); } else { newEl.addEventListener('loaded', doAssert); } |
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.
Here as well:
if (clause) {
} else {
}
tests/components/raycaster.test.js
Outdated
targetEl.setAttribute('position', '0 0 -1'); | ||
targetEl.addEventListener('loaded', function finishSetup () { | ||
el.sceneEl.appendChild(targetEl); | ||
// `npm run test:forefox` needs the timeout for the tests to succeed. |
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.
Probably not worth mentioning, appendChild is asynchronous so it's a good idea to have always.
examples/test/raycaster/simple.html
Outdated
</a-scene> | ||
|
||
<script> | ||
var el = document.createElement('a-entity'); |
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.
Within an ad-hoc component would be cool to contain the logic
examples/test/raycaster/simple.html
Outdated
el.setAttribute('position', '0 0 -2'); | ||
document.querySelector('a-scene').appendChild(el); | ||
|
||
document.querySelector('a-entity[geometry]').addEventListener('raycaster-intersected', function (evt) { |
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.
query selector needed? this is el
, right?
examples/test/raycaster/simple.html
Outdated
document.querySelector('a-scene').appendChild(el); | ||
|
||
document.querySelector('a-entity[geometry]').addEventListener('raycaster-intersected', function (evt) { | ||
// getting two intersection events per tick; same element, different faces... |
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 does this comment mean? A bug, or what we expect?
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 think the behavior is permitted.
If we choose to keep the But I don't have a strong opinion about whether that option should exist, one way or another. |
@dmarcos @ngokevin - @donmccurdy and I think this one is done, did we want to merge or close? |
Thanks! |
fixes #2329