Browse Source

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

* fix(loop): take plain snapshots of ctx

* fix: types for useloop

* chore: lint
Alvaro Saburido 11 months ago
parent
commit
a41f532b0c

+ 66 - 0
playground/auto-imports.d.ts

@@ -0,0 +1,66 @@
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// noinspection JSUnusedGlobalSymbols
+// Generated by unplugin-auto-import
+export {}
+declare global {
+  const EffectScope: typeof import('vue')['EffectScope']
+  const computed: typeof import('vue')['computed']
+  const createApp: typeof import('vue')['createApp']
+  const customRef: typeof import('vue')['customRef']
+  const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
+  const defineComponent: typeof import('vue')['defineComponent']
+  const effectScope: typeof import('vue')['effectScope']
+  const getCurrentInstance: typeof import('vue')['getCurrentInstance']
+  const getCurrentScope: typeof import('vue')['getCurrentScope']
+  const h: typeof import('vue')['h']
+  const inject: typeof import('vue')['inject']
+  const isProxy: typeof import('vue')['isProxy']
+  const isReactive: typeof import('vue')['isReactive']
+  const isReadonly: typeof import('vue')['isReadonly']
+  const isRef: typeof import('vue')['isRef']
+  const markRaw: typeof import('vue')['markRaw']
+  const nextTick: typeof import('vue')['nextTick']
+  const onActivated: typeof import('vue')['onActivated']
+  const onBeforeMount: typeof import('vue')['onBeforeMount']
+  const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
+  const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
+  const onDeactivated: typeof import('vue')['onDeactivated']
+  const onErrorCaptured: typeof import('vue')['onErrorCaptured']
+  const onMounted: typeof import('vue')['onMounted']
+  const onRenderTracked: typeof import('vue')['onRenderTracked']
+  const onRenderTriggered: typeof import('vue')['onRenderTriggered']
+  const onScopeDispose: typeof import('vue')['onScopeDispose']
+  const onServerPrefetch: typeof import('vue')['onServerPrefetch']
+  const onUnmounted: typeof import('vue')['onUnmounted']
+  const onUpdated: typeof import('vue')['onUpdated']
+  const provide: typeof import('vue')['provide']
+  const reactive: typeof import('vue')['reactive']
+  const readonly: typeof import('vue')['readonly']
+  const ref: typeof import('vue')['ref']
+  const resolveComponent: typeof import('vue')['resolveComponent']
+  const shallowReactive: typeof import('vue')['shallowReactive']
+  const shallowReadonly: typeof import('vue')['shallowReadonly']
+  const shallowRef: typeof import('vue')['shallowRef']
+  const toRaw: typeof import('vue')['toRaw']
+  const toRef: typeof import('vue')['toRef']
+  const toRefs: typeof import('vue')['toRefs']
+  const toValue: typeof import('vue')['toValue']
+  const triggerRef: typeof import('vue')['triggerRef']
+  const unref: typeof import('vue')['unref']
+  const useAttrs: typeof import('vue')['useAttrs']
+  const useCssModule: typeof import('vue')['useCssModule']
+  const useCssVars: typeof import('vue')['useCssVars']
+  const useSlots: typeof import('vue')['useSlots']
+  const watch: typeof import('vue')['watch']
+  const watchEffect: typeof import('vue')['watchEffect']
+  const watchPostEffect: typeof import('vue')['watchPostEffect']
+  const watchSyncEffect: typeof import('vue')['watchSyncEffect']
+}
+// for type re-export
+declare global {
+  // @ts-ignore
+  export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
+  import('vue')
+}

+ 0 - 31
playground/components.d.ts

@@ -8,52 +8,21 @@ export {}
 declare module 'vue' {
   export interface GlobalComponents {
     AkuAku: typeof import('./src/components/AkuAku.vue')['default']
-    AnimatedModel: typeof import('./src/components/AnimatedModel.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']
-    Cameras: typeof import('./src/components/Cameras.vue')['default']
-    copy: typeof import('./src/components/TheBasic copy.vue')['default']
-    DanielTest: typeof import('./src/components/DanielTest.vue')['default']
-    DebugUI: typeof import('./src/components/DebugUI.vue')['default']
-    DeleteMe: typeof import('./src/components/DeleteMe.vue')['default']
     DirectiveSubComponent: typeof import('./src/components/DirectiveSubComponent.vue')['default']
     DynamicModel: typeof import('./src/components/DynamicModel.vue')['default']
     FBOCube: typeof import('./src/components/FBOCube.vue')['default']
-    FBXModels: typeof import('./src/components/FBXModels.vue')['default']
-    Gltf: typeof import('./src/components/gltf/index.vue')['default']
     GraphPane: typeof import('./src/components/GraphPane.vue')['default']
     LocalOrbitControls: typeof import('./src/components/LocalOrbitControls.vue')['default']
-    MeshWobbleMaterial: typeof import('./src/components/meshWobbleMaterial/index.vue')['default']
-    MultipleCanvas: typeof import('./src/components/MultipleCanvas.vue')['default']
-    PortalJourney: typeof import('./src/components/portal-journey/index.vue')['default']
-    RenderingLogger: typeof import('./src/components/RenderingLogger.vue')['default']
-    Responsiveness: typeof import('./src/components/Responsiveness.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
-    ShadersExperiment: typeof import('./src/components/shaders-experiment/index.vue')['default']
     TakeOverLoopExperience: typeof import('./src/components/TakeOverLoopExperience.vue')['default']
     TestSphere: typeof import('./src/components/TestSphere.vue')['default']
     Text3D: typeof import('./src/components/Text3D.vue')['default']
-    TheBasic: typeof import('./src/components/TheBasic.vue')['default']
     TheCameraOperator: typeof import('./src/components/TheCameraOperator.vue')['default']
-    TheConditional: typeof import('./src/components/TheConditional.vue')['default']
-    TheEnvironment: typeof import('./src/components/TheEnvironment.vue')['default']
-    TheEvents: typeof import('./src/components/TheEvents.vue')['default']
     TheExperience: typeof import('./src/components/TheExperience.vue')['default']
-    TheFireFlies: typeof import('./src/components/portal-journey/TheFireFlies.vue')['default']
-    TheFirstScene: typeof import('./src/components/TheFirstScene.vue')['default']
-    TheGizmos: typeof import('./src/components/TheGizmos.vue')['default']
-    TheGroups: typeof import('./src/components/TheGroups.vue')['default']
-    TheModel: typeof import('./src/components/gltf/TheModel.vue')['default']
-    TheParticles: typeof import('./src/components/TheParticles.vue')['default']
-    ThePortal: typeof import('./src/components/portal-journey/ThePortal.vue')['default']
-    TheSmallExperience: typeof import('./src/components/TheSmallExperience.vue')['default']
     TheSphere: typeof import('./src/components/TheSphere.vue')['default']
-    TheUSDZModel: typeof import('./src/components/udsz/TheUSDZModel.vue')['default']
-    TresLechesTest: typeof import('./src/components/TresLechesTest.vue')['default']
-    Udsz: typeof import('./src/components/udsz/index.vue')['default']
-    VectorSetProps: typeof import('./src/components/VectorSetProps.vue')['default']
   }
 }

+ 4 - 4
playground/src/components/AnimatedObjectUseUpdate.vue

@@ -1,19 +1,19 @@
 <!-- eslint-disable no-console -->
 <script setup lang="ts">
-import { useLoop } from '@tresjs/core'
+import { type LoopCallbackWithCtx, useLoop } from '@tresjs/core'
 import { useControls } from '@tresjs/leches'
 import { useThrottleFn } from '@vueuse/core'
 
 const sphereRef = ref()
 
-const log = useThrottleFn(() => console.log('updating sphere'), 3000)
+const log = useThrottleFn(state => console.log('updating sphere', state), 3000)
 const log2 = useThrottleFn(() => console.log('this should happen before updating the sphere'), 3000)
 
 const { onBeforeRender, pause, resume } = useLoop()
 
-const updateCallback = (state) => {
+const updateCallback = (state: LoopCallbackWithCtx) => {
   if (!sphereRef.value) { return }
-  log()
+  log(state)
   sphereRef.value.position.y += Math.sin(state.elapsed) * 0.01
 }
 

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

@@ -1,7 +1,7 @@
 <script setup lang="ts">
-import { vAlwaysLookAt, vLightHelper } from '@tresjs/core'
+import { vLightHelper } from '@tresjs/core'
 </script>
 
 <template>
-  <TresDirectionalLight v-light-helper v-always-look-at="[0, 0, 0]" :position="[3, 3, 3]" :intensity="1" />
+  <TresDirectionalLight v-light-helper :position="[3, 3, 3]" :intensity="1" />
 </template>

+ 9 - 2
playground/src/components/TakeOverLoopExperience.vue

@@ -40,11 +40,18 @@ watchEffect(() => {
     resumeRender()
   }
 })
+
+const showGrid = ref(true)
+
+setTimeout(() => {
+  showGrid.value = false
+}, 10000)
 </script>
 
 <template>
   <TresPerspectiveCamera :position="[3, 3, 3]" />
-  <OrbitControls />
+  <OrbitControls make-default />
   <AnimatedObjectUseUpdate />
-  <TresAmbientLight :intensity="1" /> />
+  <TresGridHelper v-if="showGrid" />
+  <TresAmbientLight :intensity="1" />
 </template>

+ 13 - 1
playground/src/pages/models/PrimitivesModel.vue

@@ -19,6 +19,18 @@ const gl = {
 }
 
 useControls('fpsgraph')
+
+const modelsPositions = ref([
+  {
+    position: [0, 2, 2],
+  },
+  {
+    position: [0, 3, 5],
+  },
+  {
+    position: [0, 1, 1],
+  },
+])
 </script>
 
 <template>
@@ -35,7 +47,7 @@ useControls('fpsgraph')
     <OrbitControls />
 
     <Suspense>
-      <DynamicModel />
+      <DynamicModel v-for="model in modelsPositions" :key="model" :position="model.position" />
     </Suspense>
     <TresAxesHelper :args="[1]" />
     <TresDirectionalLight

+ 9 - 11
src/composables/useLoop/index.ts

@@ -1,7 +1,5 @@
-import { unref } from 'vue'
-import type { Fn } from '@vueuse/core'
-import type { TresCamera } from '../../types'
 import { useTresContext } from '../useTresContextProvider'
+import type { LoopCallbackFn } from './../../core/loop'
 
 export function useLoop() {
   const {
@@ -17,24 +15,24 @@ export function useLoop() {
 
   // Pass context to loop
   loop.setContext({
-    camera: unref(camera) as TresCamera,
-    scene: unref(scene),
-    renderer: unref(renderer),
-    raycaster: unref(raycaster),
-    controls: unref(controls),
+    camera,
+    scene,
+    renderer,
+    raycaster,
+    controls,
     invalidate,
     advance,
   })
 
-  function onBeforeRender(cb: Fn, index = 0) {
+  function onBeforeRender(cb: LoopCallbackFn, index = 0) {
     return loop.register(cb, 'before', index)
   }
 
-  function render(cb: Fn) {
+  function render(cb: LoopCallbackFn) {
     return loop.register(cb, 'render')
   }
 
-  function onAfterRender(cb: Fn, index = 0) {
+  function onAfterRender(cb: LoopCallbackFn, index = 0) {
     return loop.register(cb, 'after', index)
   }
 

+ 40 - 16
src/core/loop.ts

@@ -1,8 +1,9 @@
 import type { Ref } from 'vue'
-import { ref } from 'vue'
+import { ref, unref } from 'vue'
+import type { Camera, EventDispatcher, Raycaster, Scene, WebGLRenderer } from 'three'
 import { Clock, MathUtils } from 'three'
 import type { Fn } from '@vueuse/core'
-import type { Callback, PriorityEventHookOn } from '../utils/createPriorityEventHook'
+import type { Callback } from '../utils/createPriorityEventHook'
 import { createPriorityEventHook } from '../utils/createPriorityEventHook'
 
 export type LoopStage = 'before' | 'render' | 'after'
@@ -13,15 +14,29 @@ export interface LoopCallback {
   clock: Clock
 }
 
+export interface LoopCallbackWithCtx extends LoopCallback {
+  camera: Camera
+  scene: Scene
+  renderer: WebGLRenderer
+  raycaster: Raycaster
+  controls: Ref<(EventDispatcher<object> & {
+    enabled: boolean
+  }) | null>
+  invalidate: Fn
+  advance: Fn
+}
+
+export type LoopCallbackFn = (params: LoopCallbackWithCtx) => void
+
 export interface RendererLoop {
   loopId: string
-  register: (callback: Fn, stage: LoopStage, index?: number) => Partial<PriorityEventHookOn<LoopCallback>>
-  start: () => void
-  stop: () => void
-  pause: () => void
-  resume: () => void
-  pauseRender: () => void
-  resumeRender: () => void
+  register: (callback: LoopCallbackFn, stage: LoopStage, index?: number) => { off: Fn }
+  start: Fn
+  stop: Fn
+  pause: Fn
+  resume: Fn
+  pauseRender: Fn
+  resumeRender: Fn
   isActive: Ref<boolean>
   isRenderPaused: Ref<boolean>
   setContext: (newContext: Record<string, any>) => void
@@ -33,10 +48,10 @@ export function createRenderLoop(): RendererLoop {
   const isRenderPaused = ref(false)
   let animationFrameId: number
   const loopId = MathUtils.generateUUID()
-  let defaultRenderFn: Callback<LoopCallback> | null = null
-  const subscribersBefore = createPriorityEventHook<LoopCallback>()
-  const subscriberRender = createPriorityEventHook<LoopCallback>()
-  const subscribersAfter = createPriorityEventHook<LoopCallback>()
+  let defaultRenderFn: Callback<LoopCallbackWithCtx> | null = null
+  const subscribersBefore = createPriorityEventHook<LoopCallbackWithCtx>()
+  const subscriberRender = createPriorityEventHook<LoopCallbackWithCtx>()
+  const subscribersAfter = createPriorityEventHook<LoopCallbackWithCtx>()
 
   // Context to be passed to callbacks
   let context: Record<string, any> = {}
@@ -45,7 +60,7 @@ export function createRenderLoop(): RendererLoop {
     context = newContext
   }
 
-  function registerCallback(callback: Callback<LoopCallback>, stage: 'before' | 'render' | 'after', index = 0): Partial<PriorityEventHookOn<LoopCallback>> {
+  function registerCallback(callback: LoopCallbackFn, stage: 'before' | 'render' | 'after', index = 0): { off: Fn } {
     switch (stage) {
       case 'before':
         return subscribersBefore.on(callback, index)
@@ -97,7 +112,16 @@ export function createRenderLoop(): RendererLoop {
   function loop() {
     const delta = clock.getDelta()
     const elapsed = clock.getElapsedTime()
-    const params = { delta, elapsed, clock, ...context }
+    const snapshotCtx = {
+      camera: unref(context.camera),
+      scene: unref(context.scene),
+      renderer: unref(context.renderer),
+      raycaster: unref(context.raycaster),
+      controls: unref(context.controls),
+      invalidate: context.invalidate,
+      advance: context.advance,
+    }
+    const params = { delta, elapsed, clock, ...snapshotCtx }
 
     if (isActive.value) {
       subscribersBefore.trigger(params)
@@ -123,7 +147,7 @@ export function createRenderLoop(): RendererLoop {
 
   return {
     loopId,
-    register: (callback: Fn, stage: 'before' | 'render' | 'after', index) => registerCallback(callback, stage, index),
+    register: (callback: LoopCallbackFn, stage: 'before' | 'render' | 'after', index) => registerCallback(callback, stage, index),
     start,
     stop,
     pause,

+ 1 - 0
src/index.ts

@@ -9,6 +9,7 @@ export * from './core/catalogue'
 export * from './components'
 export * from './types'
 export * from './directives'
+export * from './core/loop'
 
 export interface TresOptions {
   extends?: Record<string, unknown>