Skip to content

Commit

Permalink
fix re-parenting, useframe not working properly in portals, attach crash
Browse files Browse the repository at this point in the history
  • Loading branch information
drcmda committed Mar 31, 2022
1 parent 7dbf70b commit 549a6bf
Show file tree
Hide file tree
Showing 9 changed files with 46 additions and 32 deletions.
2 changes: 1 addition & 1 deletion example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Global, Loading, Page, DemoPanel, Dot, Error } from './styles'

import * as demos from './demos'

const DEFAULT_COMPONENT_NAME = 'MagicMirror'
const DEFAULT_COMPONENT_NAME = 'Portals'
const visibleComponents: any = Object.entries(demos).reduce((acc, [name, item]) => ({ ...acc, [name]: item }), {})

function ErrorBoundary({ children, fallback, name }: any) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function useHover() {
return [hovered, { onPointerOver: (e: any) => (e.stopPropagation(), hover(true)), onPointerOut: () => hover(false) }]
}

function MagicMirror({ children, scale = [1, 1, 1], clearColor = 'white', ...props }: any) {
function Portal({ children, scale = [1, 1, 1], clearColor = 'white', ...props }: any) {
const ref = useRef<THREE.Mesh>(null!)
const fbo = useFBO()
const { events } = useThree()
Expand Down Expand Up @@ -108,19 +108,19 @@ const App = () => (
<group position={[0, -1, 0]}>
<Lights />
{/* First layer, a portal */}
<MagicMirror scale={[4, 5, 1]} position={[0, 2.5, 0]} rotation={[0, 0, 0]}>
<Portal scale={[4, 5, 1]} position={[0, 2.5, 0]} rotation={[0, 0, 0]}>
<Lights />
<Farm scale={10} rotation={[0, 0, 0]} position={[-1, -2, -10]} />
<Soda scale={5} position={[2, -2, -1.5]} />
{/* Second layer, a portal inside the portal */}
<MagicMirror scale={[4, 5, 1]} position={[2, 0, -5]} rotation={[0, 0, 0]}>
<Portal scale={[4, 5, 1]} position={[2, 0, -5]} rotation={[0, 0, 0]}>
<Lights />
<Soda scale={8} position={[0, -2, -1.5]} />
{/* Inside here we are in a state mirror, hence all eco system packages work.
Environment for instance only affects the portal, nothing leaks out */}
<Environment preset="city" background="only" />
</MagicMirror>
</MagicMirror>
</Portal>
</Portal>
<Ramen scale={4} position={[-2, 0, 2]} />
<Soda scale={5} position={[1.5, 0, 3]} />
</group>
Expand Down
24 changes: 12 additions & 12 deletions example/src/demos/Reparenting.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useState, useEffect, useCallback } from 'react'
import { Canvas, createPortal } from '@react-three/fiber'
import { useReducer } from 'react'

function Icosahedron() {
const [active, set] = useState(false)
Expand All @@ -13,17 +14,12 @@ function Icosahedron() {
}

function RenderToPortal({ targets }: any) {
const [target, set] = useState(targets[0])
useEffect(() => void setTimeout(() => set(targets[1]), 500), [targets])
return (
<>
<mesh position={[-2, 0, 0]}>
<sphereGeometry args={[0.5, 16, 16]} />
<meshNormalMaterial />
</mesh>
{createPortal(<Icosahedron />, target)}
</>
)
const [target, toggle] = useReducer((state) => (state + 1) % targets.length, 0)
useEffect(() => {
const interval = setInterval(toggle, 1000)
return () => clearInterval(interval)
}, [targets])
return <>{createPortal(<Icosahedron />, targets[target])}</>
}

export default function Group() {
Expand All @@ -32,7 +28,11 @@ export default function Group() {
return (
<Canvas onCreated={() => console.log('onCreated')}>
<group>
<group ref={set1 as any} position={[0, 0, 0]} />
<group ref={set1 as any} position={[-2, 0, 0]} />
<mesh position={[0, 0, 0]}>
<sphereGeometry args={[0.5, 16, 16]} />
<meshNormalMaterial />
</mesh>
<group ref={set2 as any} position={[2, 0, 0]} />
{ref1 && ref2 && <RenderToPortal targets={[ref1, ref2]} />}
</group>
Expand Down
4 changes: 2 additions & 2 deletions example/src/demos/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const SuspenseMaterial = { Component: lazy(() => import('./SuspenseMaterial')) }
const SVGRenderer = { Component: lazy(() => import('./SVGRenderer')) }
const Test = { Component: lazy(() => import('./Test')) }
const Viewcube = { Component: lazy(() => import('./Viewcube')) }
const MagicMirror = { Component: lazy(() => import('./MagicMirror')) }
const Portals = { Component: lazy(() => import('./Portals')) }
const ViewTracking = { Component: lazy(() => import('./ViewTracking')) }

export {
Expand All @@ -48,6 +48,6 @@ export {
Test,
Viewcube,
MultiView,
MagicMirror,
Portals,
ViewTracking,
}
5 changes: 3 additions & 2 deletions packages/fiber/src/core/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@ export function useThree<T = RootState>(
}

export function useFrame(callback: RenderCallback, renderPriority: number = 0): null {
const subscribe = useStore().getState().internal.subscribe
const store = useStore()
const subscribe = store.getState().internal.subscribe
// Update ref
const ref = React.useRef<RenderCallback>(callback)
React.useLayoutEffect(() => void (ref.current = callback), [callback])
// Subscribe on mount, unsubscribe on unmount
React.useLayoutEffect(() => subscribe(ref, renderPriority), [renderPriority, subscribe])
React.useLayoutEffect(() => subscribe(ref, renderPriority, store), [renderPriority, subscribe, store])
return null
}

Expand Down
2 changes: 1 addition & 1 deletion packages/fiber/src/core/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ export type InjectState = Partial<
>

function createPortal(children: React.ReactNode, container: THREE.Object3D, state?: InjectState): React.ReactNode {
return <Portal children={children} container={container} state={state} />
return <Portal key={container.uuid} children={children} container={container} state={state} />
}

function Portal({
Expand Down
6 changes: 5 additions & 1 deletion packages/fiber/src/core/loop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ function run(effects: GlobalRenderCallback[], timestamp: number) {
}

let subscribers: Subscription[]
let subscription: Subscription
function render(timestamp: number, state: RootState, frame?: THREE.XRFrame) {
// Run local effects
let delta = state.clock.getDelta()
Expand All @@ -34,7 +35,10 @@ function render(timestamp: number, state: RootState, frame?: THREE.XRFrame) {
}
// Call subscribers (useFrame)
subscribers = state.internal.subscribers
for (i = 0; i < subscribers.length; i++) subscribers[i].ref.current(state, delta, frame)
for (i = 0; i < subscribers.length; i++) {
subscription = subscribers[i]
subscription.ref.current(subscription.store.getState(), delta, frame)
}
// Render content
if (!state.internal.priority && state.gl.render) state.gl.render(state.scene, state.camera)
// Decrease frame count
Expand Down
10 changes: 5 additions & 5 deletions packages/fiber/src/core/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ function createRenderer<TCanvas>(roots: Map<TCanvas, Root>, getEventPriority?: (
let added = false
if (child) {
// The attach attribute implies that the object attaches itself on the parent
if (child.__r3f.attach) {
if (child.__r3f?.attach) {
attach(parentInstance, child, child.__r3f.attach)
} else if (child.isObject3D && parentInstance.isObject3D) {
// add in the usual parent-child way
Expand All @@ -128,7 +128,7 @@ function createRenderer<TCanvas>(roots: Map<TCanvas, Root>, getEventPriority?: (
}
// This is for anything that used attach, and for non-Object3Ds that don't get attached to props;
// that is, anything that's a child in React but not a child in the scenegraph.
if (!added) parentInstance.__r3f.objects.push(child)
if (!added) parentInstance.__r3f?.objects.push(child)
if (!child.__r3f) prepare(child, {})
child.__r3f.parent = parentInstance
updateInstance(child)
Expand All @@ -139,7 +139,7 @@ function createRenderer<TCanvas>(roots: Map<TCanvas, Root>, getEventPriority?: (
function insertBefore(parentInstance: Instance, child: Instance, beforeChild: Instance) {
let added = false
if (child) {
if (child.__r3f.attach) {
if (child.__r3f?.attach) {
attach(parentInstance, child, child.__r3f.attach)
} else if (child.isObject3D && parentInstance.isObject3D) {
child.parent = parentInstance as unknown as THREE.Object3D
Expand All @@ -150,7 +150,7 @@ function createRenderer<TCanvas>(roots: Map<TCanvas, Root>, getEventPriority?: (
added = true
}

if (!added) parentInstance.__r3f.objects.push(child)
if (!added) parentInstance.__r3f?.objects.push(child)
if (!child.__r3f) prepare(child, {})
child.__r3f.parent = parentInstance
updateInstance(child)
Expand All @@ -170,7 +170,7 @@ function createRenderer<TCanvas>(roots: Map<TCanvas, Root>, getEventPriority?: (
if (parentInstance.__r3f?.objects)
parentInstance.__r3f.objects = parentInstance.__r3f.objects.filter((x) => x !== child)
// Remove attachment
if (child.__r3f.attach) {
if (child.__r3f?.attach) {
detach(parentInstance, child, child.__r3f.attach)
} else if (child.isObject3D && parentInstance.isObject3D) {
parentInstance.remove(child)
Expand Down
15 changes: 12 additions & 3 deletions packages/fiber/src/core/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface Intersection extends THREE.Intersection {
export type Subscription = {
ref: React.MutableRefObject<RenderCallback>
priority: number
store: UseBoundStore<RootState, StoreApi<RootState>>
}

export type Dpr = number | [min: number, max: number]
Expand Down Expand Up @@ -63,7 +64,11 @@ export type InternalState = {
capturedMap: Map<number, Map<THREE.Object3D, PointerCaptureTarget>>
initialClick: [x: number, y: number]
initialHits: THREE.Object3D[]
subscribe: (callback: React.MutableRefObject<RenderCallback>, priority?: number) => () => void
subscribe: (
callback: React.MutableRefObject<RenderCallback>,
priority: number,
store: UseBoundStore<RootState, StoreApi<RootState>>,
) => () => void
}

export type RootState = {
Expand Down Expand Up @@ -288,7 +293,11 @@ const createStore = (
initialHits: [],
capturedMap: new Map(),

subscribe: (ref: React.MutableRefObject<RenderCallback>, priority = 0) => {
subscribe: (
ref: React.MutableRefObject<RenderCallback>,
priority: number,
store: UseBoundStore<RootState, StoreApi<RootState>>,
) => {
set(({ internal }) => ({
internal: {
...internal,
Expand All @@ -299,7 +308,7 @@ const createStore = (
priority: internal.priority + (priority > 0 ? 1 : 0),
// Register subscriber and sort layers from lowest to highest, meaning,
// highest priority renders last (on top of the other frames)
subscribers: [...internal.subscribers, { ref, priority }].sort((a, b) => a.priority - b.priority),
subscribers: [...internal.subscribers, { ref, priority, store }].sort((a, b) => a.priority - b.priority),
},
}))
return () => {
Expand Down

0 comments on commit 549a6bf

Please sign in to comment.