Tresjs / tres

Declarative ThreeJS using Vue Components
https://tresjs.org
MIT License
1.91k stars 85 forks source link

feat: 633 use loop #673

Closed alvarosabu closed 1 month ago

alvarosabu commented 2 months ago

Hopefully Closes #633

Based on the team discussion. This PR introduces a new API

useLoop

This composable allows you to execute a callback on every frame, similar to useRenderLoop but unique to each TresCanvas instance and with access to the context.

[!WARNING]
useLoop can only be used inside of a TresCanvas since this component acts as the provider for the context data.

Usage

Register update callbacks

The user can register update callbacks (such as animations, fbo, etc) using the onBeforeRender

<script setup>
import { useLoop } from '@tresjs/core'

const boxRef = ref()

const { onBeforeRender } = useLoop()

onBeforeRender(({ delta }) => {
  boxRef.value.rotation.y += delta
})
</script>

Take over the render loop

The user can take over the render loop callback by using the render method

const { render } = useLoop()

render(({ renderer, scene, camera }) => {
  renderer.render(scene, camera)
})

[!NOTE]
To run this example please run the playground and go to http://localhost:5173/advanced/take-over-loop

Register after render callbacks (ex physics calculations)

The user can also register after rendering callbacks using the onAfterRender

const { onAfterRender } = useLoop()

onAfterRender(({ renderer }) => {
  // Calculations
})

Priority mechanism

Both useBeforeRender and useAfteRender provide an optional priority index. This indexes could be any from [Number.NEGATIVE_INFINITY ... 0 ... Number.NEGATIVE_INFINITY]

const { onBeforeRender, pause, resume } = useLoop()

onBeforeRender((state) => {
  if (!sphereRef.value) { return }
  console.log('updating sphere')
  sphereRef.value.position.y += Math.sin(state.elapsed) * 0.01
})

onBeforeRender(() => {
   console.log('this should happen before updating the sphere')
}, -1)

For example, to use Frame Object Buffer (FBO) is convenient to use a very big number to ensure that all updates happen before the FBO callback and the last one just before the scene render.

[!NOTE]
To run this example (FBO) please run the playground and go to http://localhost:5173/advanced/fbo`

Pausing and resuming the loop

The end user can use pause and resume methods:

const { onBeforeRender, pause, resume } = useLoop()

onBeforeRender(({ elapse }) => {
  sphereRef.value.position.y += Math.sin(elapsed) * 0.01
})

pause() // This stops the clock, delta = 0 and freezes elapsed.

Diagram onLoop

Pausing and resuming the render

You can use pauseRender and resumeRender methods:

const { pauseRender, resumeRender } = useLoop()

onBeforeRender(({ elapse }) => {
  sphereRef.value.position.y += Math.sin(elapsed) * 0.01
})

pauseRender() // This will pause the renderer
resumeRender() // This will resume the renderer

Pending

alvarosabu commented 2 months ago

Hi @Tresjs/core team, this PR is the result of the latest meeting and the discussion on Discord, please let me know if there is any further questions or feedback, I will wait until final agreement on the contracts to create the documentation.

I added tests as well.

andretchen0 commented 2 months ago

@alvarosabu ,

Hi @Tresjs/core team, this PR is the result of the latest meeting and the discussion on Discord, please let me know if there is any further questions or feedback, I will wait until final agreement on the contracts to create the documentation.

You're looking for a review of code/demos? Or not yet?

alvarosabu commented 2 months ago

Hi @andretchen0 since we had the call only the 3 of us we wanted to have your opinion about this approach, I added examples for take over mode and fbo in the advance section.

I know also @Tinoooo wanted to discuss further the pause Render so I added the methods but didn't expose them yet.

andretchen0 commented 2 months ago

@Tinoooo 's explanation of the design is ok by me.

Glad to review whenever. Let me know.

alvarosabu commented 2 months ago

Hey @alvarosabu ,

This doesn't appear to implement the API from here. In particular, there doesn't seem to be a way to off callbacks. Is that right?

Let me confirm with him why he added an off method on that interface. We didn't discuss anything related to that haha that's why is not implemented šŸ˜…

alvarosabu commented 2 months ago

Ok @andretchen0 I just confirmed with @Tinoooo is a method to unsubscribe the callback inspired by https://vueuse.org/shared/createEventHook/

I will need to implement it.

andretchen0 commented 2 months ago

Ok @andretchen0 I just confirmed with @Tinoooo is a method to unsubscribe the callback inspired by https://vueuse.org/shared/createEventHook/

I will need to implement it.

If you'd like, you can do the following on your branch:

git fetch
git checkout fix/use-render-loop src/utils/createPriorityEventHook{.test,}.ts

Then you'll have src/utils/createPriorityEventHook.ts, which I'd written for the POC.

It works like createEventHook, including implementing off. It accepts a priority and sorts events according priority/insertion order.

It's got pretty good test coverage. I didn't write any documentation, but you can see examples in createPriorityEventHook.test.ts

If you like, you can use it in the place of the Maps.

Otherwise, no worries if you want to use a different implementation.

alvarosabu commented 1 month ago

Ok @andretchen0 I just confirmed with @Tinoooo is a method to unsubscribe the callback inspired by https://vueuse.org/shared/createEventHook/ I will need to implement it.

If you'd like, you can do the following on your branch:

git fetch
git checkout fix/use-render-loop src/utils/createPriorityEventHook{.test,}.ts

Then you'll have src/utils/createPriorityEventHook.ts, which I'd written for the POC.

It works like createEventHook, including implementing off. It accepts a priority and sorts events according priority/insertion order.

It's got pretty good test coverage. I didn't write any documentation, but you can see examples in createPriorityEventHook.test.ts

If you like, you can use it in the place of the Maps.

Otherwise, no worries if you want to use a different implementation.

Hi @andretchen0 this was really helpful thanks a lot for this, I just pushed a refactor that uses the utility you provided instead of the subscriber's map, works like a charm so no need for Array.from, I also updated the tests for callback registration similar to the ones on the createPriorityEventhook.test.ts

I also tested a few more things, like registering the same callback twice etc and everything seems to be covered, let me know if there is anything else pending.

alvarosabu commented 1 month ago

Question for you both @Tinoooo @andretchen0 , should I add the usage of off to the docs?

const { off, on, trigger, ...} = onBeforeRender()

I'm not sure we should expose on and trigger tho šŸ¤”

Tinoooo commented 1 month ago

onBeforeRender

Yes, it should be in the docs. I guess on and trigger should not be exposed. I cannot think of a use case that makes these useful.

andretchen0 commented 1 month ago

I agree with @Tinoooo . off should be documented. on and trigger shouldn't be returned.

alvarosabu commented 1 month ago

I agree with @Tinoooo . off should be documented. on and trigger shouldn't be returned.

Awesome @andretchen0 , I already made the changes on the return and documented the off method