Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v9] refactor!: instance descriptors, dynamically map ThreeElements #2465

Merged
merged 68 commits into from
Sep 18, 2022
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
40d36bb
refactor: use instance descriptors
CodyJasonBennett Aug 27, 2022
5923e1f
fix: remove duplicate args check, align text warning
CodyJasonBennett Aug 27, 2022
816d231
fix: don't overwrite containers in prepare
CodyJasonBennett Aug 27, 2022
cd36cfb
fix: type applyProps, flatten reconciler
CodyJasonBennett Aug 28, 2022
0fdf940
fix: don't recursively remove on swap
CodyJasonBennett Aug 28, 2022
dc4e3b1
refactor(types): automate ThreeElements
CodyJasonBennett Aug 28, 2022
16092cf
fix: don't overwrite dispose
CodyJasonBennett Aug 28, 2022
98d53e2
fix: don't leak react internals into instance props
CodyJasonBennett Aug 29, 2022
1a31024
fix: also exclude primitive props
CodyJasonBennett Aug 29, 2022
5799b6b
chore: cleanup event types
CodyJasonBennett Aug 29, 2022
53c24d1
fix: add back three-types, export ThreeElements as interface
CodyJasonBennett Aug 29, 2022
ab97027
chore(types): cleanup
CodyJasonBennett Aug 29, 2022
3ef4354
fix: check obj type in dispose, reconstruct with args length
CodyJasonBennett Aug 29, 2022
a8e9225
fix(types): move primitive to ThreeElements
CodyJasonBennett Aug 29, 2022
8ffad25
chore(types): cleanup helper signatures, require object in primitives
CodyJasonBennett Aug 30, 2022
02af69a
chore(docs): update onUpdate, TS sections
CodyJasonBennett Aug 30, 2022
903392e
chore(types): cleanup
CodyJasonBennett Aug 30, 2022
cb1c6b9
fix(types): ensure instanceprops override nodeprops
CodyJasonBennett Aug 31, 2022
90fefb9
fix(types): don't exclude functions from JSX
CodyJasonBennett Aug 31, 2022
c360b3b
Merge branch 'v9' into refactor/instance-descriptors
CodyJasonBennett Sep 1, 2022
f3c1320
fix: unlink instances on unmount, null-check unmounted containers
CodyJasonBennett Sep 1, 2022
d415383
refactor(RTTR): use Instance type, drop `is`
CodyJasonBennett Sep 1, 2022
6425146
chore(RTTR): remove any cast
CodyJasonBennett Sep 1, 2022
18f9e2b
Merge branch 'v9' into refactor/instance-descriptors
CodyJasonBennett Sep 1, 2022
b59c9c8
chore(types): prefer InstanceType, node must extend constructor signa…
CodyJasonBennett Sep 1, 2022
830c101
fix(types): allow unknown props in instanceprops
CodyJasonBennett Sep 2, 2022
ec08f4a
fix(types): pass prototype to instance props
CodyJasonBennett Sep 2, 2022
bcf1450
[v9] fix: handle container effects on completed trees, track instance…
CodyJasonBennett Sep 2, 2022
e62c4fd
Merge branch 'v9' into refactor/instance-descriptors
CodyJasonBennett Sep 9, 2022
a9ec8ed
Merge branch 'v9' into refactor/instance-descriptors
CodyJasonBennett Sep 9, 2022
f47366d
Merge branch 'v9' into refactor/instance-descriptors
CodyJasonBennett Sep 9, 2022
9b4ab11
Merge branch 'v9' into refactor/instance-descriptors
CodyJasonBennett Sep 9, 2022
2941762
chore: fix conflicts
CodyJasonBennett Sep 9, 2022
ba41289
Merge branch 'v9' into refactor/instance-descriptors
CodyJasonBennett Sep 9, 2022
562bdf1
chore: fix conflict
CodyJasonBennett Sep 9, 2022
1ea33c7
fix(useInstanceHandle): update types to reflect descriptors
CodyJasonBennett Sep 9, 2022
66206d1
refactor(types)!: Node => ThreeElement
CodyJasonBennett Sep 9, 2022
21a98c8
refactor(types)!: remove ReactThreeFiber namespace
CodyJasonBennett Sep 9, 2022
ea8da68
fix(applyProps): overwrite atomic properties if incompatible
CodyJasonBennett Sep 9, 2022
9a1519b
chore(tests): add resolve and applyProps cases
CodyJasonBennett Sep 9, 2022
bd6c9d1
chore: fix conflicts
CodyJasonBennett Sep 9, 2022
5712ec1
chore(tests): add utils coverage
CodyJasonBennett Sep 9, 2022
a5c53ad
chore(tests): mock Zustand store in utils
CodyJasonBennett Sep 9, 2022
2e7a951
chore(utils): cleanup
CodyJasonBennett Sep 9, 2022
fd3ea46
chore(tests): harden renderer lifecycle tests
CodyJasonBennett Sep 10, 2022
f357028
chore: mock scheduler/unstable_scheduleCallback
CodyJasonBennett Sep 10, 2022
9289a30
fix(renderer): don't mutate while removing recursively
CodyJasonBennett Sep 10, 2022
e58afbb
chore(tests): remove scheduler workaround
CodyJasonBennett Sep 10, 2022
c6aa939
fix(core): unlink on no preference
CodyJasonBennett Sep 10, 2022
690f112
chore(tests): harden primitive children dispose case
CodyJasonBennett Sep 10, 2022
7d1db95
fix(applyProps): don't try to construct literals on HMR
CodyJasonBennett Sep 10, 2022
972106c
refactor(applyProps): pick from root obj on HMR
CodyJasonBennett Sep 10, 2022
5cb947b
fix(types): primitive accepts truthy non-literal types
CodyJasonBennett Sep 10, 2022
eeaebd3
fix(renderer): revalidate args on prop change
CodyJasonBennett Sep 10, 2022
556da62
fix(core): don't mutate on swap, de-dup events
CodyJasonBennett Sep 10, 2022
d09a5d6
fix(applyProps): handle event handler HMR
CodyJasonBennett Sep 10, 2022
de5c80f
chore(tests): prop diffing & updates, harden reconstruct, isolate rec…
CodyJasonBennett Sep 10, 2022
d51a426
chore(tests): cleanup
CodyJasonBennett Sep 10, 2022
78f0c53
chore: cleanup removeChild
CodyJasonBennett Sep 10, 2022
23f250d
fix(applyProps): loosen literal check
CodyJasonBennett Sep 10, 2022
e311e86
chore(applyProps): cleanup
CodyJasonBennett Sep 10, 2022
54319d2
fix(applyProps): validate scalar type
CodyJasonBennett Sep 10, 2022
870b6db
chore(tests): add undefined, layers applyProps cases
CodyJasonBennett Sep 10, 2022
b5081a6
refactor(utils): move HMR to diffProps
CodyJasonBennett Sep 12, 2022
817f427
fix(core): replace old instance on reconstruct
CodyJasonBennett Sep 12, 2022
a969026
Merge branch 'v9' into refactor/instance-descriptors
CodyJasonBennett Sep 13, 2022
ab801cc
Merge branch 'v9' into refactor/instance-descriptors
CodyJasonBennett Sep 13, 2022
ee6bf62
Merge branch 'v9' into refactor/instance-descriptors
CodyJasonBennett Sep 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions docs/API/events.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ nav: 8

`three.js` objects that implement their own `raycast` method (meshes, lines, etc) can be interacted with by declaring events on them. We support pointer events, clicks and wheel-scroll. Events contain the browser event as well as the `three.js` event data (object, point, distance, etc). You may want to [polyfill](https://github.com/jquery/PEP) them, if that's a concern.

Additionally, there's a special `onUpdate` that is called every time the object gets fresh props, which is good for things like `self => (self.verticesNeedUpdate = true)`.

Also notice the `onPointerMissed` on the canvas element, which fires on clicks that haven't hit _any_ meshes.

```jsx
Expand All @@ -24,7 +22,6 @@ Also notice the `onPointerMissed` on the canvas element, which fires on clicks t
onPointerLeave={(e) => console.log('leave')} // see note 1
onPointerMove={(e) => console.log('move')}
onPointerMissed={() => console.log('missed')}
onUpdate={(self) => console.log('props have been updated')}
/>
```

Expand Down
1 change: 0 additions & 1 deletion docs/tutorials/events-and-interaction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ Any mesh in React Three Fiber has a large number of events, 13 to be more precis
onPointerLeave={(e) => console.log('leave')}
onPointerMove={(e) => console.log('move')}
onPointerMissed={() => console.log('missed')}
onUpdate={(self) => console.log('props have been updated')}
/>
```

Expand Down
18 changes: 3 additions & 15 deletions docs/tutorials/typescript.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -89,26 +89,14 @@ You can then declaratively create custom elements with primitives, but TypeScrip
<customElement />
```

### Node Helpers

react-three-fiber exports helpers that you can use to define different types of nodes. These nodes will type an element that we'll attach to the global JSX namespace.

```tsx
Node
Object3DNode
BufferGeometryNode
MaterialNode
LightNode
```

### Extending ThreeElements

Since our custom element is an object, we'll use `Object3DNode` to define it.
To define our element in JSX, we'll use the `Node` interface to extend `ThreeElements`. This interface describes three.js classes that are available in the R3F catalog and can be used as native elements.

```tsx
import { useRef, useEffect } from 'react'
import { GridHelper } from 'three'
import { extend, Object3DNode } from '@react-three/fiber'
import { extend, Node } from '@react-three/fiber'

// Create our custom element
class CustomElement extends GridHelper {}
Expand All @@ -119,7 +107,7 @@ extend({ CustomElement })
// Add types to ThreeElements elements so primitives pick up on it
declare module '@react-three/fiber' {
interface ThreeElements {
customElement: Object3DNode<CustomElement, typeof CustomElement>
customElement: Node<typeof CustomElement>
}
}

Expand Down
5 changes: 2 additions & 3 deletions example/typings/global.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { ReactThreeFiber } from '@react-three/fiber'
import { OrbitControls } from 'three-stdlib'

import { DotMaterial } from '../src/demos/Pointcloud'

declare module '@react-three/fiber' {
interface ThreeElements {
orbitControls: ReactThreeFiber.Node<OrbitControls, typeof OrbitControls>
dotMaterial: ReactThreeFiber.MaterialNode<DotMaterial, typeof DotMaterial>
orbitControls: ReactThreeFiber.Node<typeof OrbitControls>
dotMaterial: ReactThreeFiber.Node<typeof DotMaterial>
}
}
17 changes: 10 additions & 7 deletions packages/fiber/src/core/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,8 @@ export function createEvents(store: UseBoundStore<RootState>) {
function filterPointerEvents(objects: THREE.Object3D[]) {
return objects.filter((obj) =>
['Move', 'Over', 'Enter', 'Out', 'Leave'].some(
(name) => (obj as unknown as Instance).__r3f?.handlers[('onPointer' + name) as keyof EventHandlers],
(name) =>
(obj as Instance<THREE.Object3D>['object']).__r3f?.handlers[('onPointer' + name) as keyof EventHandlers],
),
)
}
Expand Down Expand Up @@ -239,7 +240,8 @@ export function createEvents(store: UseBoundStore<RootState>) {
let eventObject: THREE.Object3D | null = hit.object
// Bubble event up
while (eventObject) {
if ((eventObject as unknown as Instance).__r3f?.eventCount) intersections.push({ ...hit, eventObject })
if ((eventObject as Instance<THREE.Object3D>['object']).__r3f?.eventCount)
intersections.push({ ...hit, eventObject })
eventObject = eventObject.parent
}
}
Expand Down Expand Up @@ -369,10 +371,10 @@ export function createEvents(store: UseBoundStore<RootState>) {
)
) {
const eventObject = hoveredObj.eventObject
const instance = (eventObject as unknown as Instance).__r3f
const handlers = instance?.handlers
const instance = (eventObject as Instance<THREE.Object3D>['object']).__r3f
internal.hovered.delete(makeId(hoveredObj))
if (instance?.eventCount) {
const handlers = instance.handlers
// Clear out intersects, they are outdated by now
const data = { ...hoveredObj, intersections }
handlers.onPointerOut?.(data as ThreeEvent<PointerEvent>)
Expand Down Expand Up @@ -434,10 +436,11 @@ export function createEvents(store: UseBoundStore<RootState>) {

handleIntersects(hits, event, delta, (data: ThreeEvent<DomEvent>) => {
const eventObject = data.eventObject
const instance = (eventObject as unknown as Instance).__r3f
const handlers = instance?.handlers
const instance = (eventObject as Instance<THREE.Object3D>['object']).__r3f

// Check presence of handlers
if (!instance?.eventCount) return
const handlers = instance.handlers

if (isPointerMove) {
// Move event ...
Expand Down Expand Up @@ -488,7 +491,7 @@ export function createEvents(store: UseBoundStore<RootState>) {

function pointerMissed(event: MouseEvent, objects: THREE.Object3D[]) {
objects.forEach((object: THREE.Object3D) =>
(object as unknown as Instance).__r3f?.handlers.onPointerMissed?.(event),
(object as Instance<THREE.Object3D>['object']).__r3f?.handlers.onPointerMissed?.(event),
)
}

Expand Down
22 changes: 11 additions & 11 deletions packages/fiber/src/core/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import {
FrameloopLegacy,
Frameloop,
} from './store'
import { createRenderer, extend, Root } from './renderer'
import { reconciler, extend, Root } from './renderer'
import { createLoop, addEffect, addAfterEffect, addTail } from './loop'
import { getEventPriority, EventManager, ComputeFunction } from './events'
import { EventManager, ComputeFunction } from './events'
import {
is,
dispose,
Expand All @@ -31,14 +31,14 @@ import {
useIsomorphicLayoutEffect,
Camera,
updateCamera,
applyProps,
} from './utils'
import { useStore } from './hooks'
import { Stage, Lifecycle, Stages } from './stages'
import { OffscreenCanvas } from 'three'

const roots = new Map<Element, Root>()
const { invalidate, advance } = createLoop(roots)
const { reconciler, applyProps } = createRenderer(roots, getEventPriority)
const shallowLoose = { objects: 'shallow', strict: false } as EquConfig

type Properties<T> = Pick<T, { [K in keyof T]: T[K] extends (_: any) => any ? never : K }[keyof T]>
Expand Down Expand Up @@ -88,9 +88,9 @@ export type RenderProps<TCanvas extends Element> = {
camera?: (
| Camera
| Partial<
ReactThreeFiber.Object3DNode<THREE.Camera, typeof THREE.Camera> &
ReactThreeFiber.Object3DNode<THREE.PerspectiveCamera, typeof THREE.PerspectiveCamera> &
ReactThreeFiber.Object3DNode<THREE.OrthographicCamera, typeof THREE.OrthographicCamera>
ReactThreeFiber.Node<typeof THREE.Camera> &
ReactThreeFiber.Node<typeof THREE.PerspectiveCamera> &
ReactThreeFiber.Node<typeof THREE.OrthographicCamera>
>
) & {
/** Flags the camera as manual, putting projection into your own hands */
Expand Down Expand Up @@ -242,9 +242,9 @@ function createRoot<TCanvas extends Element>(canvas: TCanvas): ReconcilerRoot<TC

// Set raycaster options
const { params, ...options } = raycastOptions || {}
if (!is.equ(options, raycaster, shallowLoose)) applyProps(raycaster as any, { ...options })
if (!is.equ(options, raycaster, shallowLoose)) applyProps(raycaster, { ...options } as any)
if (!is.equ(params, raycaster.params, shallowLoose))
applyProps(raycaster as any, { params: { ...raycaster.params, ...params } })
applyProps(raycaster, { params: { ...raycaster.params, ...params } } as any)

// Create default camera (one time only!)
if (!state.camera) {
Expand All @@ -256,7 +256,7 @@ function createRoot<TCanvas extends Element>(canvas: TCanvas): ReconcilerRoot<TC
: new THREE.PerspectiveCamera(75, 0, 0.1, 1000)
if (!isCamera) {
camera.position.z = 5
if (cameraOptions) applyProps(camera as any, cameraOptions as any)
if (cameraOptions) applyProps(camera, cameraOptions as any)
// Always look at center by default
if (!cameraOptions?.rotation) camera.lookAt(0, 0, 0)
}
Expand Down Expand Up @@ -326,7 +326,7 @@ function createRoot<TCanvas extends Element>(canvas: TCanvas): ReconcilerRoot<TC

// Set gl props
if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, gl, shallowLoose))
applyProps(gl as any, glConfig as any)
applyProps(gl, glConfig as any)
// Store events internally
if (events && !state.events.handlers) state.set({ events: events(store) })
// Check pixelratio
Expand Down Expand Up @@ -422,7 +422,7 @@ function unmountComponentAtNode<TElement extends Element>(canvas: TElement, call
state.gl?.renderLists?.dispose?.()
state.gl?.forceContextLoss?.()
if (state.gl?.xr) state.xr.disconnect()
dispose(state)
dispose(state.scene)
roots.delete(canvas)
if (callback) callback(canvas)
} catch (e) {
Expand Down
Loading