فهرست منبع

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

alvarosabu 1 سال پیش
والد
کامیت
829e81e48b

+ 7 - 7
docs/api/composables.md

@@ -62,12 +62,12 @@ const context = useTresContext()
 | **advance** | a method to advance the render loop. This is only required if you set the `render-mode` prop to `manual`. |
 | **loop** | the renderer loop |
 
-### useFrame <Badge text="v4.1.0" />
+### useUpdate <Badge text="v4.1.0" />
 
 This composable allows you to execute a callback on every rendered frame, similar to `useRenderLoop` but unique to each `TresCanvas` instance and with access to the [context](#usetrescontext).
 
 ::: warning
-`useFrame` can be only be used inside of a `TresCanvas` since this component acts as the provider for the context data.
+`useUpdate` can be only be used inside of a `TresCanvas` since this component acts as the provider for the context data.
 :::
 
 ::: code-group
@@ -87,11 +87,11 @@ import AnimatedBox from './AnimatedBox.vue'
 
 ```vue [AnimatedBox.vue]
 <script setup>
-import { useFrame } from '@tresjs/core'
+import { useUpdate } from '@tresjs/core'
 
 const boxRef = ref()
 
-useFrame(({ delta }) => {
+useUpdate(({ delta }) => {
   boxRef.value.rotation.y += 0.01
 })
 </script>
@@ -110,14 +110,14 @@ Your callback function will be triggered just before a frame is rendered and it
 
 #### Render priority
 
-The `useFrame` composable accepts a second argument `index` which is used to determine the order in which the loop functions are executed. The default value is `0` which means the function will be executed after the renderer updates the scene. If you set the value to `-1`, the function will be executed before the renderer updates the scene.
+The `useUpdate` composable accepts a second argument `index` which is used to determine the order in which the loop functions are executed. The default value is `0` which means the function will be executed after the renderer updates the scene. If you set the value to `-1`, the function will be executed before the renderer updates the scene.
 
 ```ts
-useFrame(() => {
+useUpdate(() => {
   console.count('before renderer')
 }, -1)
 
-useFrame(() => {
+useUpdate(() => {
   console.count('after renderer')
 }, 1)
 ```

+ 2 - 0
playground/components.d.ts

@@ -10,6 +10,8 @@ declare module 'vue' {
     AkuAku: typeof import('./src/components/AkuAku.vue')['default']
     AnimatedModel: typeof import('./src/components/AnimatedModel.vue')['default']
     AnimatedObjectUseFrame: typeof import('./src/components/AnimatedObjectUseFrame.vue')['default']
+    AnimatedObjectuseUpdate: typeof import('./src/components/AnimatedObjectuseUpdate.vue')['default']
+    AnimatedObjectUseUpdate: typeof import('./src/components/AnimatedObjectUseUpdate.vue')['default']
     BlenderCube: typeof import('./src/components/BlenderCube.vue')['default']
     Box: typeof import('./src/components/Box.vue')['default']
     CameraOperator: typeof import('./src/components/CameraOperator.vue')['default']

+ 18 - 11
playground/src/components/AnimatedObjectUseFrame.vue → playground/src/components/AnimatedObjectUseUpdate.vue

@@ -1,32 +1,40 @@
 <!-- eslint-disable no-console -->
 <script setup lang="ts">
-import { useFrame } from '@tresjs/core'
+import { useUpdate } from '@tresjs/core'
+import { useControls } from '@tresjs/leches'
 
 const sphereRef = ref()
-const { pause, isActive, resume } = useFrame((state) => {
+const { pause, resume } = useUpdate((state) => {
   if (!sphereRef.value) { return }
   sphereRef.value.position.y += Math.sin(state.elapsed) * 0.01
 })
 
-function onSphereClick() {
-  console.log('sphere clicked', { isActive })
+const { areUpdatesPaused } = useControls({
+  areUpdatesPaused: {
+    value: false,
+    type: 'boolean',
+    label: 'Pause Updates',
+  },
+})
 
-  if (isActive.value) {
+watchEffect(() => {
+  if (areUpdatesPaused.value) {
     pause()
   }
   else {
     resume()
   }
-}
-/* useFrame(() => {
+})
+
+/* useUpdate(() => {
   console.count('before renderer')
 }, -1)
 
-useFrame(() => {
+useUpdate(() => {
   console.log('this should be just before render')
-}, 1)
+})
 
-useFrame((state) => {
+useUpdate((state) => {
   if (!sphereRef.value) { return }
   console.count('after renderer')
   sphereRef.value.position.y += Math.sin(state.elapsed) * 0.01
@@ -39,7 +47,6 @@ useFrame((state) => {
     :position="[2, 2, 0]"
     name="sphere"
     cast-shadow
-    @click="onSphereClick"
   >
     <TresSphereGeometry />
     <TresMeshToonMaterial color="#FBB03B" />

+ 21 - 4
playground/src/components/TakeOverLoopExperience.vue

@@ -1,17 +1,34 @@
 <script setup lang="ts">
 import { useRender } from '@tresjs/core'
 import { OrbitControls } from '@tresjs/cientos'
+import { useControls } from '@tresjs/leches'
 
-useRender(({ renderer, scene, camera }) => {
-  /* console.log('this should replace the renderer', renderer.value) */
+const { pause, resume } = useRender(({ renderer, scene, camera }) => {
   renderer.value.render(scene.value, camera.value)
 })
+
+const { isRenderPaused } = useControls({
+  isRenderPaused: {
+    value: false,
+    type: 'boolean',
+    label: 'Pause Render',
+  },
+})
+
+watchEffect(() => {
+  if (isRenderPaused.value) {
+    pause()
+  }
+  else {
+    resume()
+  }
+})
 </script>
 
 <template>
   <TresPerspectiveCamera :position="[3, 3, 3]" />
   <OrbitControls />
-  <AnimatedObjectUseFrame />
-  <TresAmbientLight :intensity="1" />
+  <AnimatedObjectUseUpdate />
+  <TresAmbientLight :intensity="1" /> />
   <TresGridHelper />
 </template>

+ 2 - 2
playground/src/components/TheSphere.vue

@@ -1,9 +1,9 @@
 <!-- eslint-disable no-console -->
 <script setup lang="ts">
-import { useFrame } from '@tresjs/core'
+import { useUpdate } from '@tresjs/core'
 
 const sphereRef = ref()
-useFrame((state) => {
+useUpdate((state) => {
   if (!sphereRef.value) { return }
   sphereRef.value.position.y += Math.sin(state.elapsed) * 0.01
 })

+ 5 - 0
playground/src/pages/advanced/TakeOverLoop.vue

@@ -1,6 +1,8 @@
 <script setup lang="ts">
 import { TresCanvas } from '@tresjs/core'
 import { BasicShadowMap, NoToneMapping, SRGBColorSpace } from 'three'
+import { TresLeches, useControls } from '@tresjs/leches'
+import '@tresjs/leches/styles'
 
 const gl = {
   clearColor: '#82DBC5',
@@ -10,9 +12,12 @@ const gl = {
   outputColorSpace: SRGBColorSpace,
   toneMapping: NoToneMapping,
 }
+
+useControls('fpsgraph')
 </script>
 
 <template>
+  <TresLeches />
   <TresCanvas v-bind="gl">
     <TakeOverLoopExperience />
   </TresCanvas>

+ 11 - 0
playground/src/pages/basic/example.vue

@@ -0,0 +1,11 @@
+<script setup lang="ts">
+import { TresCanvas } from '@tresjs/core'
+</script>
+
+<template>
+  <TresCanvas clear-color="#c0ffee" window-size>
+    <TresPerspectiveCamera :position="[3, 3, 3]" />
+    <TresGridHelper :size="10" />
+    <TresAmbientLight :intensity="1" />
+  </TresCanvas>
+</template>

+ 1 - 1
src/composables/index.ts

@@ -8,6 +8,6 @@ export * from './useLogger'
 export * from './useSeek'
 export * from './usePointerEventHandler'
 export * from './useTresContextProvider'
-export * from './useFrame'
+export * from './useUpdate'
 export * from './useRender'
 export * from './useTresEventManager'

+ 2 - 2
src/composables/useRender/index.ts

@@ -15,8 +15,8 @@ export function useRender(cb: (arg0: any) => void) {
   }
   loop.onLoop(wrappedCallback as Fn, 1)
   return {
-    pause: loop.pause,
-    resume: loop.resume,
+    pause: loop.pauseRender,
+    resume: loop.resumeRender,
     isActive: loop.isActive,
   }
 }

+ 3 - 0
src/composables/useTresContextProvider/index.ts

@@ -1,3 +1,4 @@
+import type { Fn } from '@vueuse/core'
 import { useFps, useMemory, useRafFn } from '@vueuse/core'
 import { computed, inject, onUnmounted, provide, readonly, ref, shallowRef } from 'vue'
 import type { Camera, EventDispatcher, WebGLRenderer } from 'three'
@@ -60,6 +61,7 @@ export interface TresContext {
   perf: PerformanceState
   render: RenderState
   // Loop
+  subscribers: Map<number, Fn[]>
   loop: RendererLoop
   /**
    * Invalidates the current frame when renderMode === 'on-demand'
@@ -180,6 +182,7 @@ export function useTresContextProvider({
     registerCamera,
     setCameraActive,
     deregisterCamera,
+    subscribers: new Map(),
     loop: createRenderLoop(),
   }
 

+ 2 - 2
src/composables/useFrame/index.ts → src/composables/useUpdate/index.ts

@@ -1,9 +1,9 @@
 import type { Fn } from '@vueuse/core'
 import { useTresContext } from '../useTresContextProvider'
 
-export function useFrame(cb: (arg0: any) => void, index = 0) {
+export function useUpdate(cb: (arg0: any) => void, index = 0) {
   // We force users to use `useRender` to take over the main loop, so if anyone uses
-  // `useFrame` with index 1, we will automatically change it to index 2 so
+  // `useUpdate` with index 1, we will automatically change it to index 2 so
   // it doesn't interfere with the main loop
 
   const priority = index === 1 ? 2 : index

+ 23 - 3
src/core/loop.ts

@@ -11,13 +11,15 @@ export interface RendererLoop {
   stop: () => void
   pause: () => void
   resume: () => void
+  pauseRender: () => void
+  resumeRender: () => void
   isActive: Ref<boolean>
 }
 
 export function createRenderLoop(): RendererLoop {
   const clock = new Clock(false)
   const isActive = ref(false)
-
+  const isRenderPaused = ref(true)
   let animationFrameId: number
   const loopId = MathUtils.generateUUID()
 
@@ -71,10 +73,19 @@ export function createRenderLoop(): RendererLoop {
     isActive.value = true
   }
 
+  function pauseRender() {
+    pause()
+    isRenderPaused.value = false
+  }
+
+  function resumeRender() {
+    resume()
+    isRenderPaused.value = true
+  }
+
   function loop() {
     const delta = clock.getDelta()
     const elapsed = clock.getElapsedTime()
-    /* console.log('loop', animationFrameId) */
 
     // Sort and execute callbacks based on index
     Array.from(subscribers.keys())
@@ -82,7 +93,14 @@ export function createRenderLoop(): RendererLoop {
       .forEach((index) => {
         subscribers.get(index).forEach((callback: LoopCallback) => {
           if (index !== 1 && !isActive.value) { return }
-          callback({ delta, elapsed, clock })
+          if (index === 1) {
+            if (isRenderPaused.value) {
+              callback({ delta, elapsed, clock })
+            }
+          }
+          else {
+            callback({ delta, elapsed, clock })
+          }
         })
       })
 
@@ -96,6 +114,8 @@ export function createRenderLoop(): RendererLoop {
     stop,
     pause,
     resume,
+    pauseRender,
+    resumeRender,
     onLoop: (callback: LoopCallback, index = 0) => registerCallback(callback, index),
     isActive,
   }

+ 2 - 2
src/directives/vAlwaysLookAt.ts

@@ -2,7 +2,7 @@ import type { Object3D } from 'three'
 import type { Ref } from 'vue'
 import { extractBindingPosition } from '../utils'
 import type { TresVector3 } from '../types'
-import { useFrame, useLogger } from '../composables'
+import { useLogger, useUpdate } from '../composables'
 
 const { logWarning } = useLogger()
 
@@ -13,7 +13,7 @@ export const vAlwaysLookAt = {
       logWarning(`v-always-look-at: problem with binding value: ${binding.value}`)
       return
     }
-    useFrame(() => {
+    useUpdate(() => {
       el.lookAt(observer)
     })
   },

+ 2 - 2
src/directives/vRotate.ts

@@ -1,7 +1,7 @@
 import { ref } from 'vue'
 import { Quaternion, Vector3 } from 'three'
 import type { TresObject } from '../types'
-import { useFrame, useLogger } from '../composables'
+import { useLogger, useUpdate } from '../composables'
 
 const { logWarning } = useLogger()
 
@@ -31,7 +31,7 @@ export const vRotate = {
     const quaternion = new Quaternion().setFromAxisAngle(new Vector3(x.value, y.value, z.value)
       .normalize(), radiansPerFrame)
 
-    useFrame(() => {
+    useUpdate(() => {
       el.applyQuaternion(quaternion)
     })
   },