react-spring: @react-spring/three useSpring stops updating in XR session
🐛 Bug Report
Springs stop updating when starting an active XR session.
Not sure if this a bug related to @react-spring/three, @react-three/fiber, @react-three/xr or a combination of all three.
To Reproduce
- Load repro link below on an Oculus Quest 2
- The mesh changes scale on a timer ever 1s - this is animated with a spring
- Start a VR session
- The mesh stops animating scale and stays at the scale it was when the xr session was started
Expected behavior
The mesh should continue to animate scale inside the XR session.
Link to repro (highly encouraged)
https://codesandbox.io/s/react-xr-react-spring-bug-fkzt3?file=/src/index.tsx
Environment
@react-spring/three
>= v9.0.0@react-three/fiber
>= v6.0.3@react-three/xr
>= v2.1.0react
v17.x
Works as expected with the following old versions:
@react-spring/three
v9.0.0-rc.3react-three-fiber
v5.x.x@react-three/xr
v2.0.1
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Reactions: 1
- Comments: 16 (9 by maintainers)
Commits related to this issue
- fix: use new rafz property to let r3f drive animation frames resolves #1518 — committed to pmndrs/react-spring by joshuaellis 3 years ago
- fix: r3f not running frameloop & array props not correctly updated (#1551) * chore: depreciate three-v5 * chore: add three-demo * feat: move rafz to part of monorepo * fix: use new rafz prop... — committed to pmndrs/react-spring by joshuaellis 3 years ago
Also, great investigation so far! ⭐
I spent a few hours remote debugging my Oculus Quest 2 browser. This is what I could see:
No active XR session
FrameLoop.advance
FrameLoop.advance
SpringValue.advance
In active XR session
FrameLoop.advance
FrameLoop.advance
SpringValue.advance
I’m not familiar with the internals of react-spring so I’m not sure what that means. I was a bit surprised to see separate frame loop logic for each target tbh. And I’m not sure I understand where the shared FrameLoop gets called from the three FrameLoop.
Hope this helps nail down the issue. I will wait for someone who actually knows the internals of react-spring to comment 😅
Cool, so after some digging this weekend, it turns out that
r3f
was not driving the animation ofreact-spring
when using@react-spring/three
. So that made it easier to remove theFrameLoop
class from@react-spring/three
.I then looked at the code for
rafz
and there’s only two places we call the nativeRequestAnimationFrame
in the start function and the loop. Taking inspiration from the work I did withreact-three-fiber
aroundframeLoop
and when to call it, i’ve decided to pull this intorafz
so that@react-spring/three
will changerafz.frameLoop
todemand
, this will mean nothing usingrafz
will run unless we manually advance the timer, we can do this by adding an effect function tor3f
, sincer3f
chooses the correct loop function depending on if itsxr
or not, this should work. Keep eyes pealed for the PR intorafz
and the fix forreact-spring
!Edit: I want to say a real thank you to @ffdead, great research helped isolate the issue and wrote up incredibly clearly so I could think about how to solve it 🙌🏼
this has been released on v9.2.2 in theory it should all be working, but we can reopen if it’s not 🙏🏼
Thanks for the research! It’s very helpful ⭐ it’s also not easy to resolve (but when is it?).
So it feels like step1 is getting everything to use a set global AnimationLoop function e.g in the context of web this would be
rafz
. Step 2 would be to be able to swap this default global AnimationLoop function out for a func that can run as an effect in r3f…Ok, here’s what I think is going on:
The main issue
window.requestAnimationFrame
doesn’t run in an XR session on the Oculus Quest browser. This is expected as we need to useWebGLRenderer.setAnimationLoop
to run animations in XR.The three target’s FrameLoop is not being used at all currently. The solution is to let r3f drive the frame loop which uses
WebGLRenderer.setAnimationLoop
internally. The three target’s FrameLoop is hooked up to r3f viaaddEffect
properly, but the FrameLoop itself is not being used to drive the springs.Globals.assign({ frameLoop })
is a noop because:Globals
don’t actually contain an assign or export forframeLoop
import { frameLoop } from '@react-spring/shared'
instead of something likeGlobals.frameLoop
withAnimated
useimport { raf } from '@react-spring/shared'
directly without using the FrameLoop to schedule the frames.Temporary workaround
My temporary workaround is to monkey patch the three target to run
rafz
frames via r3faddEffect
. I didn’t find a way to request only 1 frame from r3f so I cache the current frame callback in a temporary variable.Workaround sandbox
Problems with the workaround
Only one frame callback is saved. This might cause issues if we have multiple
rafz
frames scheduled for each r3f frame, but hopefully, that’s not happening.r3f
invalidate
needs to be called manually while springs are active. It’s important that apps can stop the r3f render loop when not needed to preserve battery / performance by passing<Canvas frameloop="demand"/>
the
rafz
internal loop seem to run all the time which would cause r3f to invalidate continuously if we did something like this:Conclusions
rafz
directly since it relies on window.requestAnimationFrame.This feels like fundamental change so I suspect a core maintainer would be better suited to make the necessary changes.
Side note: a friend has one I was supposed to borrow a while back! annoying I never got it.
I’m really sorry, I simply can’t help actually look into this issue. I would be surprised if this were an issue with
react-spring
because it works fine with regular WebGL, i’m also happy to be proven completely wrong.You could open an issue in
react-xr
sure! Equally, if you were interested in looking into I’m happy to support you however I can // answer questions etc. 👍🏼