Skip to content
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

feat: 633 use loop #673

Merged
merged 53 commits into from
May 14, 2024
Merged

feat: 633 use loop #673

merged 53 commits into from
May 14, 2024

Conversation

alvarosabu
Copy link
Member

@alvarosabu alvarosabu commented May 4, 2024

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

  • Tests
  • Documentation

@alvarosabu alvarosabu added feature p3-downstream-blocker Blocking the downstream ecosystem to work properly (priority) p3-significant High-priority enhancement (priority) labels May 4, 2024
@alvarosabu alvarosabu self-assigned this May 4, 2024
@alvarosabu alvarosabu requested a review from Tinoooo May 4, 2024 15:14
@alvarosabu alvarosabu marked this pull request as draft May 4, 2024 15:14
@alvarosabu alvarosabu requested a review from Tinoooo May 8, 2024 15:08
Copy link
Member

@JaimeTorrealba JaimeTorrealba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see problems, thanks for this big work Alvaro

@andretchen0
Copy link
Contributor

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

@alvarosabu
Copy link
Member Author

alvarosabu commented May 9, 2024

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

docs/api/composables.md Outdated Show resolved Hide resolved
docs/api/composables.md Outdated Show resolved Hide resolved
src/components/TresCanvas.vue Outdated Show resolved Hide resolved
Comment on lines 50 to 52
const wrappedCallback = (params: LoopCallback) => {
cb({ ...params, camera: unref(camera) as TresCamera, scene: unref(scene), renderer: unref(renderer), raycaster: unref(raycaster), controls: unref(controls), invalidate, advance })
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: I suggest creating a method that creates this method to avoid code duplication like mentioned here

@@ -185,6 +193,31 @@ export function useTresContextProvider({
root: ctx,
}

// The loop

ctx.loop.register(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Can this be moved to useRenderer? This would be beneficial for the seperation of concerns and the dependencies would be more obvious.

Copy link
Contributor

@andretchen0 andretchen0 May 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I had commented the opposite.

Making this case for that:

If we move the question "should the scene be rendered?" to useRenderer that means:

  • useRenderer has to be passed render, invalidate, advance, and renderMode – 4 dependencies it otherwise doesn't require
  • it has to know and implement the update policies, which are orthogonal to what WebGLRenderer otherwise does.

If we want to keep the update in useRenderer, I think it'd be better off encapulated in a single dependency like onRender() – which it would simply call. But I'd personally prefer that it be removed from useRenderer altogether. The render policy can be part of the loop:

if (renderPolicy.shouldRender()) {
    renderer.render()
}

In this way, we eliminate the dependencies in useRenderer altogether and put them behind an interface – nothing else needs to muck around with the implementation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already moved it.... can we please agree on one way @Tinoooo @andretchen0 ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alvarosabu

I'd prefer to reduce dependencies in useRenderer, but I won't block the PR because of it. It's your call.

src/composables/useTresContextProvider/index.ts Outdated Show resolved Hide resolved
@@ -185,6 +193,31 @@ export function useTresContextProvider({
root: ctx,
}

// The loop

ctx.loop.register(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Can/Should we make sure that this is resored once a user replaced the rendering with a custom callback and removed it afterwards via off?

@@ -2,6 +2,10 @@

With the new directive v-always-look-at provided by **TresJS**, you can add easily command an [Object3D](https://threejs.org/docs/index.html?q=object#api/en/core/Object3D) to always look at a specific position, this could be passed as a Vector3 or an Array.

::: warning
This directive can be only be used inside of a `TresCanvas` since this component acts as the provider for the context data and it uses `onLoop` under the hood.
Copy link
Contributor

@andretchen0 andretchen0 May 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work? I'm getting

Error: useTresContext must be used together with useTresContextProvider

vAlwaysLookAt only runs if updated – I think that's a bug – so you'll have to update it in order to get the error to throw.

<script setup lang="ts">
import { shallowRef } from 'vue'
import { vAlwaysLookAt, vLightHelper } from '@tresjs/core'

const other = shallowRef([1,1,1])

setTimeout(() => other.value = [2,1,1], 1000)
</script>

<template>
  <TresDirectionalLight v-light-helper v-always-look-at="other" :position="[3, 3, 3]" :intensity="1" />
</template>

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm gonna remove the directives with loop. They are not adding enough value for the troubles


function wrapCallback(cb: Fn) {
return (params: LoopCallback) => {
cb({ ...params, camera: unref(camera) as TresCamera, scene: unref(scene), renderer: unref(renderer), raycaster: unref(raycaster), controls: unref(controls), invalidate, advance })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible, it's probably be best to avoid wrapping the callback.

Currently, for every callback function in every frame, this code is recreating the argument object.

The argument object only needs to be created once.

Copy link
Member Author

@alvarosabu alvarosabu May 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andretchen0 how do we get the latest context if we are returning the plain values? if we called once on the onLoop is only going to return the snapshot of the context at that specific moment.

@@ -274,13 +250,33 @@ export function useRenderer(
}
})

// Register loop
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For separation of concerns, I'd move this somewhere else.

Almost all core code has access to the renderer, so this could live just about anywhere.

On the other hand, useRenderer has to be passed render, loop, invalidate, advance, in order to do loop.register.

function render(cb: Fn) {
const wrappedCallback = wrapCallback(cb)
const { off } = loop.register(wrappedCallback, 'render')
return { off }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can't be offed.

playground/src/components/TakeOverLoopExperience.vue

<script setup lang="ts">
import { useLoop } from '@tresjs/core'

import { OrbitControls } from '@tresjs/cientos'
import { useControls } from '@tresjs/leches'

const { render, pauseRender, resumeRender } = useLoop()

const off = render(({ renderer, scene, camera }) => {
  console.log('on')
  renderer.render(scene, camera)
}).off

setTimeout(() => {off(); console.log('offing')}, 1000)

const { isRenderPaused } = useControls({
  isRenderPaused: {
    value: false,
    type: 'boolean',
    label: 'Pause Render',
  },
})

watchEffect(() => {
  if (isRenderPaused.value) {
    pauseRender()
  }
  else {
    resumeRender()
  }
})
</script>

<template>
  <TresPerspectiveCamera :position="[3, 3, 3]" />
  <OrbitControls />
  <AnimatedObjectUseUpdate />
  <TresAmbientLight :intensity="1" /> />
  <TresGridHelper />
</template>

Copy link
Member Author

@alvarosabu alvarosabu May 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @andretchen0 please check the discord discussion

BREAKING_CHANGE: Directives `vAlwaysLookAt` and `vRotate` due incompatibility with new `useLoop` and the refactor of the render loop logic.
@alvarosabu alvarosabu merged commit 1b2fa70 into v4 May 14, 2024
3 checks passed
@alvarosabu alvarosabu deleted the feature/633-use-loop-proposal-2 branch May 14, 2024 15:54
alvarosabu added a commit that referenced this pull request May 30, 2024
* feat: 474 vue chrome devtools plugin (#479)

* feat: vue chrome devtools

* feat: editable scenes from devtools

* chore(lint): fix lint errors

* feat: highlight material

* chore(lint): fix

* chore: release v4.0.0-next.0

* feat: update to three `v160` and vue `v3.4` (#488)

* fix(types): added `Object3DEventMap` to `Object3D` generics for point event handling (#491)

* feat: 140 on demand rendering (#497)

* feat: conditional rendering

* chore: remove subscribe system

* feat: on-demand automatic invalidation with prop changes

* feat: invalidate once first when is `renderMode !== 'always'`

* docs: performance page, on-demand rendering

* chore: fix windowsize issue

* chore(lint): fix maximum line length issues

* feat: invalidate on-demand on window resize

* feat: add advance method for manual mode

* feat: fix manual first render with advance

* docs: performance manual mode

* docs: add badge with version

* chore: correct typos and PR suggestions

* chore: tell dont ask fix

* feat: render state instead of internal

* feat: remove default camera warning (#499)

* feat: remove annoying defautl camera warning

* chore: remove logWarning

* feat: 492 set tone mapping default to acesfilmictonemapping (#498)

* feat: set ACESFilmicToneMapping as default toneMapping

* chore: usage of nullish coealescing operator instead of ternaries

* feat: 516 localstate for custom renderer node instances instead of userdata (#522)

* feat: conditional rendering

* chore: remove subscribe system

* feat: on-demand automatic invalidation with prop changes

* feat: invalidate once first when is `renderMode !== 'always'`

* docs: performance page, on-demand rendering

* chore: fix windowsize issue

* chore(lint): fix maximum line length issues

* feat: invalidate on-demand on window resize

* feat: add advance method for manual mode

* feat: fix manual first render with advance

* docs: performance manual mode

* docs: add badge with version

* chore: correct typos and PR suggestions

* chore: tell dont ask fix

* feat: render state instead of internal

* feat: add __tres local state to nodeOps instances

* feat: add context to root on instances localstate

* feat: camera registration ops from node local state ctx

* feat: event handling registration from localState of nodes

* feature: disposable flag on node localstate

* feat: remove userData from types

* chore: remove unused import

* fix(test): fake localstate `.__tres` on tests

* fix(types): fix nodeOps instances localstate type

* fix: camera aspect

* Update orthographic camera aspect when screen size updates
* Give user a "manual" flag to keep Tres from updating camera

* feat: 503 conditional rendering of primitives (#514)

* feat(nodeOps): switch instance logic for reactive `object` prop

* chore: playground primitives with models

* chore: fix linter

* chore: fix tests and linters, primitive object is now reactive

* chore: refactor instance swaping logic to overwrite set and copy properties

* chore: tests

* chore: remove console.log

* chore: remove unused import watch

* feat: add primitive conditional to patch object prop

* fix: `nodeOps` is now a function (#579)

* fix: `nodeOps` is now a function

* chore(test): updated tests for `nodeOps`

* chore: next package json version

* chore: release v4.0.0-next.1

* fix: refactor nodeOps to return methods at the end of the function (#602)

* fix: refactor nodeOps to return methods at the end of the function

* chore: fix lint

* chore: internal playground organisation (#601)

* chore: new internal playground org and testing pages

* chore: fix lint

* chore: better styling of playground landing page

* chore: lint

* chore: deps update

* chore: internal primitive model test playground

* chore: fix lint

* chore: release v4.0.0-next.2

* chore: misc routes

* fix: do not change pierced props case (#608)

* chore: lint fix

* chore: problem with package version

* chore: fix lint

* chore: rebuild pnpm-lock

* test(nodeOps): clean up tests

* test(nodeOps): organize tests

* test: add coverage plugin

* test: add coverage to package.json script

* test(nodeOps): improve test coverage

* feat: devtools renderer improvements (#614)

* feat: renderer programs when selecting scene on devtools

* feat: renderer.info

* chore: fix lint

* docs: devtools update

* chore: fix lint issues

* feat(events)!: pointerevents manager and state (#529)

* new file:   playground/src/components/Box.vue
	new file:   playground/src/pages/raycaster/Propogation.vue
	  * Started work on interactive Event Propogation playground example
	modified:   src/components/TresCanvas.vue
	  * Import and use `useEventStore`
	  * defineEmits for all expected pointer events so we may emit propogated events off of the canvasa
	modified:   src/composables/index.ts
	new file:   src/composables/useEventStore/index.ts
	  * Started work on an event store. I'm not sure this counts as a store just yet
	  * Wired up majority of pointer events
	  * Added event propogation
	  * Does not require using userData scene props or nodeOps for registering objects to scene
	modified:   src/composables/useRaycaster/index.ts
	  * Added new event listeners to power newly supported pointer events. We now check whole scene/children when calling intersectObjects.
	  * Created new EventHooks for new events
	  * Added `forceUpdate` function that allows for pointer-move events to work without mouth movement (good for when camera is moving but mouse is not)

	modified:   src/core/nodeOps.ts
	  * Added supported events to array so they don't get received as props
	  * (temporarily) unhook current pointer event solution to iterate on useEventStore
	modified:   src/utils/index.ts
	  * Added Camel-to-kebab case util

* Support multiple event listeners, add support for .stop event modifier

* Set stopProgation variable to false by default, whoops

* fix typo

* fix: remove `createGlobalState` from `useEventStore`, allowing events to work while multiple TresCanvas' are being used

* fix(perf): remove extraneous intersectObjects/getIntersects calls by moving intersects into a ref that is updated on pointer-move

* chore(lint): fix lint issues

* feat: enhance events manager to include duplicates checking, pointer-missed support, and forced updating

Per file changelog:
	modified:   playground/src/components/Box.vue
	  * Added a pointer-missed handler for testing
	modified:   playground/src/pages/TheBasic.vue
	  * uses forceUpdate from EventManager to fire events even when the mouse hasn't moved
	modified:   playground/src/pages/raycaster/Propagation.vue
	  * Didn't mean to undo the lint changes, adds a pointer-missed event on the canvas 		for extra testing
	modified:   src/components/TresCanvas.vue
	  * Adds `pointer-missed` as possible event for canvas emits
	modified:   src/composables/index.ts
	  * Update export
	deleted:    src/composables/useEventStore/index.ts
	  * Rename `useEventStore` to `useTresEventManager`
	modified:   src/composables/useRaycaster/index.ts
	  * Check for empty intersects on hit test, wire up pointerMissed events eventHook
	  * Fix forceUpdate to call onPointerMove instead of triggering an EventHook
	modified:   src/composables/useTresContextProvider/index.ts
	  * Add TresEventManager type
	new file:   src/composables/useTresEventManager/index.ts
	  * add onPointerMissed
	  * create (de)registerPointerMissedObj methods so we can track objects in the scene listening to this event
	  * Note: These are passed to nodeOps via TresContext
	  * Implement duplicates checking for eventPropogation
	modified:   src/core/nodeOps.ts
	  * register/deregister pointerMissed objects

* chore: lint

* docs: new event docs

* chore: fix lint

* feat: enhance event object details and use in Box example to change material color. Add ability to force event system updates even when mouse hasn't moved. Enhance pointer-enter/leave events. Update types

  Box.vue
    * Added pointer-missed handler
    * set the materials flash color using the object coming off of the event instead of a ref
  UseRaycaster
    * Flesh out event details to include
      * all mouse event properties
      * intersections
      * tres camera
      * camera raycaster
      * source event
      * mouse position delta
      * stopPropagating stub
      * and unprojectedPoint (this needs work, cant get the math to work)
  UseTresContextProvider
    * Add TresEventManager type to TresContext
  useTresEventManager
    * Add forceUpdate method to allow apps to force an event system update even when the mouse hasnt moved
    * Add pointerMissed event
    * Properly implement pointer-enter/pointer-leave events
      * Before now, pointer-enter | leave were only called on first object in intersection, now we execute the events for all entered/left objects
    * Use stopPropagating property included on event object

* chore: lint

* chore: fix lint issues

---------

Co-authored-by: alvarosabu <[email protected]>

* feat: 499 better memory management (#606)

* chore: memory management playground

* feat: recursively free cpu and gpu memory allocation on remove

* chore: clumsy attempt to dispose on unmount

* chore: lint fix

* feat: remove scene root on disposal

* chore: fix lint

* docs: added disposal guide on `performance` docs

* chore: fix lint

* chore: type issues (#663)

* fix: fix some internal types

* chore: fix linters

* fix: typescript issues on event manager

* chore: release v4.0.0-rc.0

* fix: make on* callbacks settable (#672)

* fix: make on- callbacks settable

* test: test setting not calling

* feat: 633 use loop  (#673)

* feat: createRenderLoop unique to context

* feat: onLoop returns current state

* feat: ensuring callback excecution with index order

* feat: take control of render loop logic

* docs: updated composable docs

* feat: change error to deprecation warning towards v5

* chore: add link to new composable docs on deprecation warning

* chore: remove depcreation warning of existing useRenderLoop

* feat: `useFrame` and `useRender` instead of `onLoop`

* chore: fix lint

* feat: applied useFrame to directives

* chore: fix lint

* feat: `useUpdate` instead of `useFrame` and useRender pausing.

* chore: testing fbo

* feat: reserve index 1 for late-updates

* chore: fix lint

* feat: useLoop composable for the win

* chore: change onLoop name for register

* chore: unit tests for loop

* chore: change order for registration to make index optional

* chore: fix lint

* feat: pauseRender and resumeRender

* docs: useLoop guide

* docs: updated basic animations recipe to `useLoop`

* docs: correct pause render methods on docs

* Update docs/api/composables.md

Co-authored-by: Tino Koch <[email protected]>

* Update docs/api/composables.md

Co-authored-by: Tino Koch <[email protected]>

* Update docs/api/composables.md

Co-authored-by: Tino Koch <[email protected]>

* Update docs/api/composables.md

Co-authored-by: Tino Koch <[email protected]>

* Update docs/api/composables.md

Co-authored-by: Tino Koch <[email protected]>

* Update docs/api/composables.md

Co-authored-by: Tino Koch <[email protected]>

* Update docs/api/composables.md

Co-authored-by: Tino Koch <[email protected]>

* chore: refactor subscribers to `priorityEventHooks`

* Update docs/api/composables.md

Co-authored-by: Tino Koch <[email protected]>

* feat: just return `off` on the loop registration methods

* docs: update docs to add `off` unregister callback method

* feat: remove `v-rotate`

* docs: added context warning for `v-always-look-at`

* Update docs/api/composables.md

Co-authored-by: Tino Koch <[email protected]>

* Update docs/api/composables.md

Co-authored-by: Tino Koch <[email protected]>

* chore: remove leftover of isntance.provide

* chore: remove subscribers from context

* chore: abstract `wrapCallback`  and move render loop register to `useRender`

* chore: fix lint

* chore: testing off

* Revert "chore: abstract `wrapCallback`  and move render loop register to `useRender`"

This reverts commit 24cec65.

* chore: return bound `off` method and use createPriorityEvent for render with defaultFn fallback

* feat: deprecate and remove `vAlwaysLookAt` and `vRotate`

BREAKING_CHANGE: Directives `vAlwaysLookAt` and `vRotate` due incompatibility with new `useLoop` and the refactor of the render loop logic.

* feat: set context to loop to avoid wrapping the callbacks

* feat: dispose render hook before taking over

---------

Co-authored-by: Tino Koch <[email protected]>

* chore(playground): adding missing import and removing the directives that were deprecated

* chore(playground): use new composable on animations

* fix(utils): reorder object disposal to avoid issue with Helper `dispose` methods (#683)

* chore: updated deps

* chore: release v4.0.0-rc.1

* fix: manual rendering blank (#685)

* fix: increate time to advance on manual mode

* chore: correct playground

* fix: 686 useloop callback state missing controls (#687)

* fix(loop): take plain snapshots of ctx

* fix: types for useloop

* chore: lint

* docs: add RectAreaLightHelper to vLightHelper docs

* chore(deps): update deps 24-0-2024

* chore: release v4.0.0-rc.2

* fix: start loop if user calls useRenderLoop (#695)

* docs: change motivation

* chore(deps): last update before release

---------

Co-authored-by: Peter <[email protected]>
Co-authored-by: Garrett Walker <[email protected]>
Co-authored-by: Tino Koch <[email protected]>
Co-authored-by: Jaime Torrealba <[email protected]>
Co-authored-by: Jaime A Torrealba C <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature p3-downstream-blocker Blocking the downstream ecosystem to work properly (priority) p3-significant High-priority enhancement (priority)
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

4 participants