Skip to content

Commit

Permalink
fix: 686 useloop callback state missing controls (#687)
Browse files Browse the repository at this point in the history
* fix(loop): take plain snapshots of ctx

* fix: types for useloop

* chore: lint
  • Loading branch information
alvarosabu committed May 22, 2024
1 parent 0720d18 commit a41f532
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 67 deletions.
66 changes: 66 additions & 0 deletions playground/auto-imports.d.ts
Original file line number Diff line number Diff line change
@@ -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')
}
31 changes: 0 additions & 31 deletions playground/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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']
}
}
8 changes: 4 additions & 4 deletions playground/src/components/AnimatedObjectUseUpdate.vue
Original file line number Diff line number Diff line change
@@ -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
}
Expand Down
4 changes: 2 additions & 2 deletions playground/src/components/DirectiveSubComponent.vue
Original file line number Diff line number Diff line change
@@ -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>
11 changes: 9 additions & 2 deletions playground/src/components/TakeOverLoopExperience.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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>
14 changes: 13 additions & 1 deletion playground/src/pages/models/PrimitivesModel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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
Expand Down
20 changes: 9 additions & 11 deletions src/composables/useLoop/index.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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)
}

Expand Down
56 changes: 40 additions & 16 deletions src/core/loop.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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
Expand All @@ -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> = {}
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down

0 comments on commit a41f532

Please sign in to comment.