-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Suggestion: Don't create default camera, scene, and render loop #61
Comments
you don't have to use the default camera, you can useRender(..., true) and render your own scene and your own camera, at that point the defaults dont matter: https://github.com/drcmda/react-three-fiber#heads-up-display-rendering-multiple-scenes Also the frameloop is optional, even the renderer: https://codesandbox.io/s/yq90n32zmx But, this isn't what most people would go for. Auto frameloop is far more effective and it meshes well with other libs like react-spring, etc. I couldn't imagine a single reason why you'd want to be the one that calls requestAnimationLoop. There's no benefit, just downsides. useRender(..., true) essentially empties everything that would be done internally, letting you define your own update effects (camera.update(), etc) and render calls. But you could of course skip all of this and just use render/unmountComponentAtNode. The thing about useRender is that it allows components to affect the render cycle in a uniform way, so that you can compose. This isn't easily possible in Threejs. Even simple stuff like function Effects({ factor }) {
const { gl, scene, camera, size } = useThree()
const composer = useRef()
useEffect(() => void composer.current.setSize(size.width, size.height), [size])
// This takes over as the main render-loop (when 2nd arg is set to true)
useRender(() => composer.current.render(), true)
return (
<effectComposer ref={composer} args={[gl]}>
<renderPass attachArray="passes" args={[scene, camera]} />
<glitchPass attachArray="passes" factor={factor} renderToScreen />
</effectComposer>
)
}
// Mount effects conditionally
{effectsActive && <Effects /> would mean so much complexity in Threejs. But here the effects component is self managed and controls its own render logic. But once it's unmounted everything goes back to normal. In Threejs your render loop would have to know about effects in order to switch it on/off, which creates a dependency you don't want to have. |
You don't need to imagine it, it's what I already do! 😃 I pause my animation loop on window blur, and on focus, start it up again and track the elapsed time delta. I also use the value passed to the rAF callback, which I can't do with the current loop because it's abstracted away. In your example of manual looping, you have to drop into more imperative code to achieve it, which I'd prefer not to do if there's a declarative component that can do it (but that doesn't introduce default behavior). It also requires exposing the guts of the library to the user. React-three-render achieved this with a parameter to a trigger created callback. |
But can't you do this with the invalidate function? Im not so familiar with three-renderer, but that trigger looks familar. <Canvas invalidateFrameloop={state.paused}>
<SomeComponent />
</Canvas>
function SomeComponent() {
const { invalidate } = useThree()
useEffect(() => {
// trigger a single frame, if invalidateFrameloop is true rendering is manual
invalidate()
}, [])
return ...
} There's also a universal one, which would render all canvases (if you had multiple) for a single frame. import { invalidate } from 'react-three-fiber'
invalidate() Another (perhaps cleaner?) way would be: useRender(({ gl, camera, scene }) => {
if (!state.pause) gl.render(camera.scene)
}, true)
That's no problem, i'll add it. |
The timetag is merged, next patch you can get it like this: useRender((state, t) => ...) |
I want to rAF outside of the canvas component because I need to update many UI elements surrounding the canvas as part of my game state, like overlayed vanilla html menus. I have a big game state object, I run it through one frame (like physics), then I pass all that data down to UI and I tried to recreate this scenario in a codesandbox, where |
You can try using the global invalidate, don't need to pass the local one up. Maybe that makes it easier.
Here's the codesandbox with global invalidate: https://codesandbox.io/s/zn7o3z0k33 What i don't like about the current solution is that it seems to put React through the update loop with that useState trigger, it re-renders the Game component 60fps. I would avoid that at all cost. Even if some UI elements have to reflect something, can't this be reduced? Or written outside of React? Running React in a game loop is a bit controversial i think. That's the whole purpose, if the rotation would be in useRender with a clean ongoing game loop it would be butter smooth. But "animating" that rotation through React will kill performance. Personally i would prefer the loop to be in canvas. I'd communicate change to the surrounding UI in there, probably via animatable values (react-spring), because they don't re-render anything. |
I'll try the global invalidate, I generally prefer to keep things "in the tree" so I'll also try to figure out why my demo isn't working. What I do now for Charisma is steps a physics engine (p2.js) which updates all in-game positions, then I pass down those positions to the respective components as props. I loop over all "dynamic" entity data in one component and re-render each child, like |
no, if you need realtime it will come down to prop passing. i just hope physics won't take too long to computer, because three + p2 + react + dom paint all in a 15ms window isn't much time to do things. If you use react-spring for positioning, it will at least be outside of react, you would pass down props once and change them in the parent, which informs the views that cling to it. |
I've found two things: My setup doesn't work if <Canvas>
<ExposeContextToParent
setRenderTrigger={setRenderTrigger}
/>
<scene ref={sceneRef}> but this doesn't: <Canvas>
<scene ref={sceneRef}>
<ExposeContextToParent
setRenderTrigger={setRenderTrigger}
/> Secondly, there doesn't appear to be a way to fully control the render loop? If I use Are either of these bugs? |
invalidateFrameLoop means that it only renders on prop changes or manual trigger (by calling invalidate). if you need a fully manual loop, perhaps we can expose some of the code around canvas and make it re-usable so that people can build whatever they like around the basic render/unmountComponentAtNode reconciler functionality. can i interest you in looking into it, making a PR? |
I'm still struggling with this. I put my game state on the But now I want to click on a Do you have a suggestion of how to approach this? Is there a way to move the |
You could use a global effect. Check the exports, it’s among them. This can be done outside of canvas. |
Why is this bad? How to redraw things if you don't rerender react component? I also need a better way to do this. I'm writing a binding to an ECS framework, upon drawing, I sync components to react, and trigger rerender: I can see reconciler takes a long time, while gl drawing is very fast. |
See: https://twitter.com/0xca0a/status/1135465164504031233 And: https://twitter.com/0xca0a/status/1133329007800397824 (this one's using three-fiber) It all depends on how big the rendering effort is. In edge cases you can use useRender, react-spring, or something like zustand. All three go outside of React. |
Thanks, your example helps, I've already got the reference to component (the component in ECS) which gets transient updates by ECS framework (I guess "transient updates" means the reference to the data object is not changed so it won't trigger rerender, but the data inside object change rapidly, am I right?) The key here is Also, hope you can expand https://github.com/react-spring/zustand#transient-updates-for-often-occuring-state-changes to describe what does "transient updates" means... |
well, yes, it means what you said. you are basically notified of a state change via callback and now you can apply updates to the component by directly mutating the view "transiently". react-spring solves this in a purely declarative way. but it's focussed on animation, which probably isn't what you need. so zustand + useRender cuts a good middleground, because the api is formed in a way that suits useEffect, so everything gets cleaned up after the component unmounts. also interesting, react will get something like this officially. dan abramov has mentioned this on twitter. something like a fast path-through mode where, if you agree to not change the structure of the view, you can blow props changes through React without diffing, which would be as fast as applying them natively. |
I don't think this library should hard code a scene, camera, or render loop, for example hard coding the default camera to a perspectivecamera at position 0,0,5. I think this library should be more general than that. A Three setup can have 0 to many cameras of different types, 0 to many scenes, and itself doesn't start a requestAnimationFrame loop. These seem anti-React too, to hard code components into a library.
The text was updated successfully, but these errors were encountered: