Ver código fonte

refactor!: removed useTresReady, added isReady to the renderer in the context

BREAKING CHANGE:

- useTresReady is no longer available, it has been replaced by isReady in the renderer in the context
- onTresReady is no longer available. renderer.isReady should be leveraged instead
Tino Koch 2 meses atrás
pai
commit
9880017e6c

+ 5 - 4
src/components/TresCanvas.vue

@@ -36,6 +36,7 @@ import { nodeOps } from '../core/nodeOps'
 
 import { disposeObject3D, kebabToCamel } from '../utils/'
 import { registerTresDevtools } from '../devtools'
+import { whenever } from '@vueuse/core'
 
 export interface TresCanvasProps
   extends Omit<WebGLRendererParameters, 'canvas'> {
@@ -244,14 +245,14 @@ onMounted(() => {
     )
   })
 
-  context.value.onReady(() => {
-    if (context.value) { emit('ready', context.value) }
-  })
-
   // HMR support
   if (import.meta.hot && context.value) { import.meta.hot.on('vite:afterUpdate', () => handleHMR(context.value as TresContext)) }
 })
 
+whenever(() => context.value?.renderer.isReady, () => {
+  if (context.value) { emit('ready', context.value) }
+}, { once: true })
+
 onUnmounted(unmountCanvas)
 </script>
 

+ 0 - 1
src/composables/index.ts

@@ -11,5 +11,4 @@ export * from './useRenderLoop'
 export * from './useTexture'
 export * from './useTresContextProvider'
 export * from './useTresEventManager'
-export { onTresReady } from './useTresReady'
 export { UseLoader, UseTexture }

+ 8 - 1
src/composables/useRenderer/useRendererManager.ts

@@ -11,7 +11,7 @@ import {
   useDevicePixelRatio,
 } from '@vueuse/core'
 import { ACESFilmicToneMapping, Color, WebGLRenderer } from 'three'
-import { computed, type MaybeRef, onUnmounted, ref, shallowRef, toValue, watch, watchEffect } from 'vue'
+import { computed, type MaybeRef, onUnmounted, readonly, ref, shallowRef, toValue, watch, watchEffect } from 'vue'
 
 // Solution taken from Thretle that actually support different versions https://github.com/threlte/threlte/blob/5fa541179460f0dadc7dc17ae5e6854d1689379e/packages/core/src/lib/lib/useRenderer.ts
 import { revision } from '../../core/revision'
@@ -196,9 +196,15 @@ export function useRendererManager(
     invalidateOnDemand()
   })
 
+  const isReady = ref(false)
+
   watch([sizes.width, sizes.height], () => {
     instance.value.setSize(sizes.width.value, sizes.height.value)
     invalidateOnDemand()
+
+    if (!isReady.value && sizes.width.value && sizes.height.value) {
+      isReady.value = true
+    }
   }, {
     immediate: true,
   })
@@ -305,6 +311,7 @@ export function useRendererManager(
   return {
     instance,
 
+    isReady: readonly(isReady),
     advance,
     onRender,
     invalidate,

+ 7 - 12
src/composables/useTresContextProvider/index.ts

@@ -12,8 +12,7 @@ import { useCamera } from '../useCamera'
 import { useRendererManager } from '../useRenderer/useRendererManager'
 import useSizes, { type SizesType } from '../useSizes'
 import { type TresEventManager, useTresEventManager } from '../useTresEventManager'
-import { useTresReady } from '../useTresReady'
-import { createEventHook, type EventHookOff } from '@vueuse/core'
+import { whenever } from '@vueuse/core'
 
 export interface PerformanceState {
   maxFrames: number
@@ -52,7 +51,6 @@ export interface TresContext {
   deregisterObjectAtPointerEventHandler?: (object: TresObject) => void
   registerBlockingObjectAtPointerEventHandler?: (object: TresObject) => void
   deregisterBlockingObjectAtPointerEventHandler?: (object: TresObject) => void
-  onReady: EventHookOff<TresContext> // TODO #980 consider removing this
 }
 
 export function useTresContextProvider({
@@ -88,8 +86,6 @@ export function useTresContextProvider({
     },
   )
 
-  const readyEventHook = createEventHook<TresContext>() // TODO #980 consider removing this
-
   const ctx: TresContext = {
     sizes,
     scene: localScene,
@@ -115,7 +111,6 @@ export function useTresContextProvider({
     setCameraActive,
     deregisterCamera,
     loop,
-    onReady: readyEventHook.on,
   }
 
   provide('useTres', ctx)
@@ -125,19 +120,19 @@ export function useTresContextProvider({
     root: ctx,
   }
 
-  const { on: onTresReady, cancel: cancelTresReady } = useTresReady(ctx)!
-
   ctx.loop.setReady(false)
   ctx.loop.start()
 
-  onTresReady(() => {
-    readyEventHook.trigger(ctx)
+  whenever(renderer.isReady, () => { // TODO #994 This does not belong here, see https://github.com/Tresjs/tres/issues/595
     ctx.loop.setReady(true)
-    useTresEventManager(scene, ctx)
+  }, {
+    once: true,
+    immediate: true,
   })
 
+  useTresEventManager(scene, ctx)
+
   onUnmounted(() => {
-    cancelTresReady()
     ctx.loop.stop()
   })
 

+ 0 - 187
src/composables/useTresReady/createReadyEventHook/createReadyHook.test.ts

@@ -1,187 +0,0 @@
-import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
-import { createReadyEventHook } from './index'
-
-describe('createReadyEventHook', () => {
-  beforeEach(() => {
-    vi.useFakeTimers()
-  })
-
-  afterEach(() => {
-    vi.restoreAllMocks()
-  })
-
-  describe('createReadyEventHook(getIsReady)', () => {
-    it('calls getIsReady when created', () => {
-      const getIsReady = vi.fn(() => true)
-      createReadyEventHook(getIsReady, null)
-      expect(getIsReady).toBeCalled()
-    })
-
-    it('calls getIsReady periodically', () => {
-      const fn = vi.fn(() => false)
-      createReadyEventHook(fn, null, 1)
-      vi.advanceTimersByTime(1000)
-      expect(fn).toHaveBeenCalledTimes(1000 + 1)
-    })
-
-    it('calls getIsReady periodically, but only until `getIsReady()` is truthy', () => {
-      let i = 0
-      const fn0 = () => {
-        i++
-        return i === 5
-      }
-      createReadyEventHook(fn0, null)
-      vi.advanceTimersByTime(1000)
-      expect(i).toBe(5)
-
-      i = -1
-      const fn1 = () => {
-        i++
-        return i
-      }
-      createReadyEventHook(fn1 as any, null)
-      vi.advanceTimersByTime(1000)
-      expect(i).toBe(1)
-    })
-
-    it('calls getIsReady periodically, but only while not cancelled', () => {
-      const fn = vi.fn(() => false)
-      const { cancel } = createReadyEventHook(fn, null, 1)
-      vi.advanceTimersByTime(99)
-      cancel()
-      vi.advanceTimersByTime(1000)
-      expect(fn).toHaveBeenCalledTimes(100)
-    })
-  })
-
-  describe('createReadyEventHook(getIsReady, intervalMs)', () => {
-    it('calls getIsReady at the provided interval', () => {
-      const fn = vi.fn(() => false)
-      createReadyEventHook(fn, null, 100)
-      expect(fn).toHaveBeenCalledTimes(1)
-      vi.advanceTimersByTime(99)
-      expect(fn).toHaveBeenCalledTimes(1)
-      vi.advanceTimersByTime(1000)
-      expect(fn).toHaveBeenCalledTimes(10 + 1)
-      vi.advanceTimersByTime(5000)
-      expect(fn).toHaveBeenCalledTimes(50 + 10 + 1)
-    })
-  })
-
-  describe('createReadyEventHook().on', () => {
-    it('registers a function and calls it once `getIsReady() === true`', () => {
-      const fn = vi.fn()
-      const { on } = createReadyEventHook(trueIfCalledNTimes(10), null)
-
-      on(fn)
-      vi.advanceTimersByTime(10000)
-
-      expect(fn).toHaveBeenCalledTimes(1)
-    })
-
-    it('calls registered functions with args', () => {
-      const fn0 = vi.fn()
-      const fn1 = vi.fn()
-      const arg0 = { foo: 'bar' }
-      const arg1 = { baz: 'boo' }
-      const { on } = createReadyEventHook(() => true, [arg0, arg1])
-
-      on(fn0)
-      on(fn1)
-
-      expect(fn0).toHaveBeenCalledWith([{ foo: 'bar' }, { baz: 'boo' }])
-      expect(fn1).toHaveBeenCalledWith([{ foo: 'bar' }, { baz: 'boo' }])
-    })
-
-    it('calls a function immediately if `getIsReady() === true`', () => {
-      const fn = vi.fn()
-      const { on } = createReadyEventHook(() => true, null)
-
-      on(fn)
-
-      expect(fn).toHaveBeenCalledTimes(1)
-    })
-
-    it('calls functions with arg immediately if `getIsReady() === true`', () => {
-      const fn0 = vi.fn()
-      const fn1 = vi.fn()
-      const arg = { foo: 'bar' }
-      const { on } = createReadyEventHook(() => true, arg)
-
-      on(fn0)
-      on(fn1)
-
-      expect(fn0).toHaveBeenCalledWith({ foo: 'bar' })
-      expect(fn1).toHaveBeenCalledWith({ foo: 'bar' })
-    })
-
-    it('can register many functions, one at a time', () => {
-      const fns = Array.from({ length: 100 })
-        .fill(0)
-        .map(_ => vi.fn())
-
-      const { on } = createReadyEventHook(trueIfCalledNTimes(10), null)
-      fns.forEach(fn => on(fn))
-      vi.advanceTimersByTime(10000)
-
-      for (const fn of fns) {
-        expect(fn).toHaveBeenCalledTimes(1)
-      }
-    })
-  })
-
-  describe('createReadyEventHook().off(fn)', () => {
-    it('unregisters a function', () => {
-      const fns = Array.from({ length: 100 })
-        .fill(0)
-        .map(_ => vi.fn())
-
-      const { on, off } = createReadyEventHook(trueIfCalledNTimes(10), null)
-      fns.forEach(fn => on(fn))
-
-      const offedFns = new Set()
-      fns.forEach((fn) => {
-        if (Math.random() < 0.5) {
-          offedFns.add(fn)
-          off(fn)
-        }
-      })
-      vi.advanceTimersByTime(10000)
-
-      fns.forEach((fn) => {
-        expect(fn).toHaveBeenCalledTimes(offedFns.has(fn) ? 0 : 1)
-      })
-    })
-  })
-
-  describe('createReadyEventHook().on(fn).off()', () => {
-    it('unregisters a function', () => {
-      const fns = Array.from({ length: 100 })
-        .fill(0)
-        .map(_ => vi.fn())
-
-      const { on } = createReadyEventHook(trueIfCalledNTimes(10), null)
-
-      const offedFns = new Set()
-      fns.forEach((fn) => {
-        const { off } = on(fn)
-        if (Math.random() < 0.5) {
-          offedFns.add(fn)
-          off()
-        }
-      })
-      vi.advanceTimersByTime(1000)
-
-      fns.forEach((fn) => {
-        expect(fn).toHaveBeenCalledTimes(offedFns.has(fn) ? 0 : 1)
-      })
-    })
-  })
-})
-
-function trueIfCalledNTimes(n: number) {
-  return () => {
-    n = Math.max(n - 1, 0)
-    return n === 0
-  }
-}

+ 0 - 87
src/composables/useTresReady/createReadyEventHook/index.ts

@@ -1,87 +0,0 @@
-import type { EventHook, EventHookOn, IsAny } from '@vueuse/core'
-import { createEventHook } from '@vueuse/core'
-
-type Callback<T> =
-  IsAny<T> extends true
-    ? (param: any) => void
-    : [T] extends [void]
-        ? () => void
-        : (param: T) => void
-
-export function createReadyEventHook<T>(
-  getIsReady: () => boolean,
-  triggerParams: T,
-  pollIntervalMs = 100,
-): EventHook<T> & { cancel: () => void } {
-  pollIntervalMs = pollIntervalMs <= 0 ? 100 : pollIntervalMs
-  const hook = createEventHook()
-  // NOTE: This hook will likely be long-lived and
-  // we don't want to interfere with garbage collection
-  // in the meantime.
-  // Keep a set of `offFns` and call them after `getIsReady`
-  // in order to remove them from the `hook`.
-  const offFns = new Set<() => void>()
-  let ready = false
-  let cancelled = false
-  let timeoutId: ReturnType<typeof setTimeout> | null = null
-
-  function doReadyTest() {
-    if (timeoutId) {
-      clearTimeout(timeoutId)
-    }
-    if (!cancelled && !ready && getIsReady()) {
-      hook.trigger(triggerParams)
-      offFns.forEach(offFn => offFn())
-      offFns.clear()
-      ready = true
-    }
-    else if (!cancelled && !ready) {
-      timeoutId = setTimeout(doReadyTest, pollIntervalMs)
-    }
-  }
-
-  function cancel() {
-    cancelled = true
-    if (timeoutId) {
-      clearTimeout(timeoutId)
-    }
-  }
-
-  if (import.meta.hot) {
-    import.meta.hot.on('vite:afterUpdate', () => {
-      ready = false
-      doReadyTest()
-    })
-  }
-
-  doReadyTest()
-
-  const triggerSingleCallback = (callback: Callback<T>, ...args: [T]) => {
-    callback(...args)
-  }
-
-  const onOrCall: EventHookOn<T> = (callback) => {
-    if (!ready) {
-      const onFn = hook.on(callback)
-
-      if (!import.meta.hot) {
-        // NOTE: We must keep callbacks around for HMR.
-        // But if it doesn't exist, remove callbacks.
-        offFns.add(onFn.off)
-      }
-      return hook.on(callback)
-    }
-    else {
-      triggerSingleCallback(callback as Callback<T>, triggerParams)
-      return { off: () => {} }
-    }
-  }
-
-  return {
-    on: onOrCall,
-    off: hook.off,
-    trigger: hook.trigger,
-    clear: hook.clear,
-    cancel,
-  }
-}

+ 0 - 53
src/composables/useTresReady/index.ts

@@ -1,53 +0,0 @@
-import type { TresContext } from '../useTresContextProvider'
-import { useTresContext } from '../useTresContextProvider'
-import { createReadyEventHook } from './createReadyEventHook'
-
-const ctxToUseTresReady = new WeakMap<
-  TresContext,
-  ReturnType<typeof createReadyEventHook<TresContext>>
->()
-
-export function useTresReady(ctx?: TresContext) {
-  ctx = ctx || useTresContext()
-  if (ctxToUseTresReady.has(ctx)) {
-    return ctxToUseTresReady.get(ctx)!
-  }
-
-  const MAX_READY_WAIT_MS = 100
-  const start = Date.now()
-
-  // NOTE: Consider Tres to be "ready" if either is true:
-  // - MAX_READY_WAIT_MS has passed (assume Tres is intentionally degenerate)
-  // - Tres is not degenerate
-  //     - A renderer exists
-  //     - A DOM element exists
-  //     - The DOM element's height/width is not 0
-  const getTresIsReady = () => {
-    if (Date.now() - start >= MAX_READY_WAIT_MS) {
-      return true
-    }
-    else {
-      const renderer = ctx.renderer.instance.value
-      const domElement = renderer?.domElement || { width: 0, height: 0 }
-      return !!(renderer && domElement.width > 0 && domElement.height > 0)
-    }
-  }
-
-  const args = ctx as TresContext
-  const result = createReadyEventHook(getTresIsReady, args)
-  ctxToUseTresReady.set(ctx, result)
-
-  return result
-}
-
-export function onTresReady(fn: (ctx: TresContext) => void) {
-  const ctx = useTresContext()
-  if (ctx) {
-    if (ctxToUseTresReady.has(ctx)) {
-      return ctxToUseTresReady.get(ctx)!.on(fn)
-    }
-    else {
-      return useTresReady(ctx).on(fn)
-    }
-  }
-}