33 createEffect ,
44 createMemo ,
55 createRenderEffect ,
6+ createRoot ,
67 mergeProps ,
78 onCleanup ,
89} from "solid-js"
@@ -29,9 +30,10 @@ import { frameContext, threeContext } from "./hooks.ts"
2930import { eventContext } from "./internal-context.ts"
3031import { manageSceneGraph , useProps } from "./props.ts"
3132import { CursorRaycaster , type EventRaycaster } from "./raycasters.tsx"
32- import type { CameraKind , Context } from "./types.ts"
33+ import type { CameraKind , Context , FrameListener , FrameListenerCallback } from "./types.ts"
3334import {
3435 augment ,
36+ binarySearch ,
3537 defaultProps ,
3638 getCurrentViewport ,
3739 removeElementFromArray ,
@@ -54,15 +56,55 @@ export function createThree(canvas: HTMLCanvasElement, props: CanvasProps) {
5456 /* */
5557 /**********************************************************************************/
5658
57- type FrameListener = ( context : Context , delta : number , frame ?: XRFrame ) => void
59+ const frameListeners = {
60+ before : {
61+ map : new Map < number , FrameListenerCallback [ ] > ( ) ,
62+ priorities : [ ] as number [ ] , // Keep this sorted
63+ } ,
64+ after : {
65+ map : new Map < number , FrameListenerCallback [ ] > ( ) ,
66+ priorities : [ ] as number [ ] ,
67+ } ,
68+ }
69+
70+ const addFrameListener : FrameListener = ( callback , options ) => {
71+ return createRoot ( dispose => {
72+ createRenderEffect ( ( ) => {
73+ const { stage = "before" , priority = 0 } = options ?? { }
74+
75+ const listeners = frameListeners [ stage ]
76+
77+ let array = listeners . map . get ( priority )
78+
79+ if ( ! array ) {
80+ array = [ ]
81+ listeners . map . set ( priority , array )
82+ const index = binarySearch ( listeners . priorities , priority )
83+ listeners . priorities . splice ( index , 0 , priority )
84+ }
85+
86+ array . push ( callback )
5887
59- const frameListeners : FrameListener [ ] = [ ]
60- // Adds a callback to be called on each frame
61- function addFrameListener ( callback : FrameListener ) {
62- frameListeners . push ( callback )
63- const cleanup = ( ) => removeElementFromArray ( frameListeners , callback )
64- onCleanup ( cleanup )
65- return cleanup
88+ onCleanup ( ( ) => {
89+ removeElementFromArray ( array , callback )
90+ if ( array . length === 0 ) {
91+ listeners . map . delete ( priority )
92+ listeners . priorities . splice ( listeners . priorities . indexOf ( priority ) , 1 )
93+ }
94+ } )
95+ } )
96+
97+ return dispose
98+ } )
99+ }
100+
101+ function updateFrameListeners ( stage : "before" | "after" , delta : number , frame ?: XRFrame ) {
102+ for ( const priority of frameListeners [ stage ] . priorities ) {
103+ const callbacks = frameListeners [ stage ] . map . get ( priority ) !
104+ for ( const callback of callbacks ) {
105+ callback ( context , delta , frame )
106+ }
107+ }
66108 }
67109
68110 /**********************************************************************************/
@@ -100,6 +142,7 @@ export function createThree(canvas: HTMLCanvasElement, props: CanvasProps) {
100142 /**********************************************************************************/
101143
102144 let pendingRenderRequest : number | undefined
145+
103146 function render ( timestamp : number , frame ?: XRFrame ) {
104147 if ( ! context . gl ) {
105148 return
@@ -108,8 +151,11 @@ export function createThree(canvas: HTMLCanvasElement, props: CanvasProps) {
108151 context . clock . elapsedTime = timestamp
109152 }
110153 pendingRenderRequest = undefined
154+
155+ const delta = context . clock . getDelta ( )
156+ updateFrameListeners ( "before" , delta , frame )
111157 context . gl . render ( context . scene , context . currentCamera )
112- frameListeners . forEach ( listener => listener ( context , context . clock . getDelta ( ) , frame ) )
158+ updateFrameListeners ( "after" , delta , frame )
113159 }
114160 function requestRender ( ) {
115161 if ( pendingRenderRequest ) return
0 commit comments