diff --git a/packages/fiber/src/core/events.ts b/packages/fiber/src/core/events.ts index 23aa5ac20b..9253864bab 100644 --- a/packages/fiber/src/core/events.ts +++ b/packages/fiber/src/core/events.ts @@ -188,36 +188,38 @@ export function createEvents(store: UseBoundStore) { // Allow callers to eliminate event objects const eventsObjects = filter ? filter(state.internal.interaction) : state.internal.interaction // Reset all raycaster cameras to undefined - eventsObjects.forEach((obj) => { - const state = getRootState(obj) + for (let i = 0; i < eventsObjects.length; i++) { + const state = getRootState(eventsObjects[i]) if (state) { state.raycaster.camera = undefined! } - }) + } if (!state.previousRoot) { // Make sure root-level pointer and ray are set up state.events.compute?.(event, state) } + function handleRaycast(obj: THREE.Object3D) { + const state = getRootState(obj) + // Skip event handling when noEvents is set, or when the raycasters camera is null + if (!state || !state.events.enabled || state.raycaster.camera === null) return [] + + // When the camera is undefined we have to call the event layers update function + if (state.raycaster.camera === undefined) { + state.events.compute?.(event, state, state.previousRoot?.getState()) + // If the camera is still undefined we have to skip this layer entirely + if (state.raycaster.camera === undefined) state.raycaster.camera = null! + } + + // Intersect object by object + return state.raycaster.camera ? state.raycaster.intersectObject(obj, true) : [] + } + // Collect events let hits: THREE.Intersection>[] = eventsObjects // Intersect objects - .flatMap((obj) => { - const state = getRootState(obj) - // Skip event handling when noEvents is set, or when the raycasters camera is null - if (!state || !state.events.enabled || state.raycaster.camera === null) return [] - - // When the camera is undefined we have to call the event layers update function - if (state.raycaster.camera === undefined) { - state.events.compute?.(event, state, state.previousRoot?.getState()) - // If the camera is still undefined we have to skip this layer entirely - if (state.raycaster.camera === undefined) state.raycaster.camera = null! - } - - // Intersect object by object - return state.raycaster.camera ? state.raycaster.intersectObject(obj, true) : [] - }) + .flatMap(handleRaycast) // Sort by event priority and distance .sort((a, b) => { const aState = getRootState(a.object) @@ -317,7 +319,7 @@ export function createEvents(store: UseBoundStore) { ray: raycaster.ray, camera: camera, // Hijack stopPropagation, which just sets a flag - stopPropagation: () => { + stopPropagation() { // https://github.com/pmndrs/react-three-fiber/issues/596 // Events are not allowed to stop propagation if the pointer has been captured const capturesForPointer = 'pointerId' in event && internal.capturedMap.get(event.pointerId) @@ -359,7 +361,7 @@ export function createEvents(store: UseBoundStore) { function cancelPointer(intersections: Intersection[]) { const { internal } = store.getState() - Array.from(internal.hovered.values()).forEach((hoveredObj) => { + for (const hoveredObj of internal.hovered.values()) { // When no objects were hit or the the hovered object wasn't found underneath the cursor // we call onPointerOut and delete the object from the hovered-elements map if ( @@ -382,10 +384,17 @@ export function createEvents(store: UseBoundStore) { handlers.onPointerLeave?.(data as ThreeEvent) } } - }) + } } - const handlePointer = (name: string) => { + function pointerMissed(event: MouseEvent, objects: THREE.Object3D[]) { + for (let i = 0; i < objects.length; i++) { + const instance = (objects[i] as unknown as Instance).__r3f + instance?.handlers.onPointerMissed?.(event) + } + } + + function handlePointer(name: string) { // Deal with cancelation switch (name) { case 'onPointerLeave': @@ -404,17 +413,17 @@ export function createEvents(store: UseBoundStore) { } // Any other pointer goes here ... - return (event: DomEvent) => { + return function handleEvent(event: DomEvent) { const { onPointerMissed, internal } = store.getState() - //prepareRay(event) + // prepareRay(event) internal.lastEvent.current = event // Get fresh intersects const isPointerMove = name === 'onPointerMove' const isClickEvent = name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick' const filter = isPointerMove ? filterPointerEvents : undefined - //const hits = patchIntersects(intersect(filter), event) + // const hits = patchIntersects(intersect(filter), event) const hits = intersect(event, filter) const delta = isClickEvent ? calculateDistance(event) : 0 @@ -435,7 +444,7 @@ export function createEvents(store: UseBoundStore) { // Take care of unhover if (isPointerMove) cancelPointer(hits) - handleIntersects(hits, event, delta, (data: ThreeEvent) => { + function onIntersect(data: ThreeEvent) { const eventObject = data.eventObject const instance = (eventObject as unknown as Instance).__r3f const handlers = instance?.handlers @@ -485,14 +494,10 @@ export function createEvents(store: UseBoundStore) { } } } - }) - } - } + } - function pointerMissed(event: MouseEvent, objects: THREE.Object3D[]) { - objects.forEach((object: THREE.Object3D) => - (object as unknown as Instance).__r3f?.handlers.onPointerMissed?.(event), - ) + handleIntersects(hits, event, delta, onIntersect) + } } return { handlePointer } diff --git a/packages/fiber/src/core/loop.ts b/packages/fiber/src/core/loop.ts index 1d314ff5f7..eb2e0fa487 100644 --- a/packages/fiber/src/core/loop.ts +++ b/packages/fiber/src/core/loop.ts @@ -36,7 +36,9 @@ export const addTail = (callback: GlobalRenderCallback) => createSubs(callback, function run(effects: Set, timestamp: number) { if (!effects.size) return - effects.forEach(({ callback }) => callback(timestamp)) + for (const { callback } of effects.values()) { + callback(timestamp) + } } export type GlobalEffectType = 'before' | 'after' | 'tail' @@ -91,7 +93,7 @@ export function createLoop(roots: Map) { flushGlobalEffects('before', timestamp) // Render all roots - roots.forEach((root) => { + for (const root of roots.values()) { state = root.store.getState() // If the frameloop is invalidated, do not run another frame if ( @@ -101,7 +103,7 @@ export function createLoop(roots: Map) { ) { repeat += render(timestamp, state) } - }) + } // Run after-effects flushGlobalEffects('after', timestamp) @@ -136,7 +138,7 @@ export function createLoop(roots: Map) { frame?: THREE.XRFrame, ): void { if (runGlobalEffects) flushGlobalEffects('before', timestamp) - if (!state) roots.forEach((root) => render(timestamp, root.store.getState())) + if (!state) for (const root of roots.values()) render(timestamp, root.store.getState()) else render(timestamp, state, frame) if (runGlobalEffects) flushGlobalEffects('after', timestamp) } diff --git a/packages/fiber/src/core/utils.ts b/packages/fiber/src/core/utils.ts index 23cc8ae8be..c9c4810d44 100644 --- a/packages/fiber/src/core/utils.ts +++ b/packages/fiber/src/core/utils.ts @@ -254,7 +254,8 @@ export function applyProps(instance: Instance, data: InstanceProps | DiffSet) { // Prepare memoized props if (instance.__r3f) instance.__r3f.memoizedProps = memoized - changes.forEach(([key, value, isEvent, keys]) => { + for (let i = 0; i < changes.length; i++) { + let [key, value, isEvent, keys] = changes[i] let currentInstance = instance let targetProp = currentInstance[key] @@ -340,7 +341,7 @@ export function applyProps(instance: Instance, data: InstanceProps | DiffSet) { } invalidateInstance(instance) - }) + } if (localState.parent && rootState.internal && instance.raycast && prevHandlers !== localState.eventCount) { // Pre-emptively remove the instance from the interaction manager