Skip to content

Commit

Permalink
[v9] experiment: extend factory overload (#2785)
Browse files Browse the repository at this point in the history
  • Loading branch information
CodyJasonBennett authored Sep 17, 2023
1 parent 795fa93 commit 642c0ca
Show file tree
Hide file tree
Showing 9 changed files with 50 additions and 38 deletions.
3 changes: 2 additions & 1 deletion packages/fiber/src/core/events.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as THREE from 'three'
import { getRootState, type Properties } from './utils'
import { getRootState } from './utils'
import type { Instance } from './reconciler'
import type { RootState, RootStore } from './store'
import type { Properties } from '../three-types'

export interface Intersection extends THREE.Intersection {
/** The event source (the object which registered the handler) */
Expand Down
3 changes: 1 addition & 2 deletions packages/fiber/src/core/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,7 @@ function loadingFn<T>(extensions?: Extensions<T>, onProgress?: (event: ProgressE
new Promise<LoaderResult<T>>((res, reject) =>
loader.load(
input,
(data) =>
res(data?.scene instanceof THREE.Object3D ? Object.assign(data, buildGraph(data.scene)) : data),
(data) => res(data?.scene instanceof THREE.Object3D ? Object.assign(data, buildGraph(data.scene)) : data),
onProgress,
(error) => reject(new Error(`Could not load ${input}: ${(error as ErrorEvent)?.message}`)),
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import * as THREE from 'three'
import * as React from 'react'
import Reconciler from 'react-reconciler'
import { ContinuousEventPriority, DiscreteEventPriority, DefaultEventPriority } from 'react-reconciler/constants'
import { unstable_IdlePriority as idlePriority, unstable_scheduleCallback as scheduleCallback } from 'scheduler'
import { diffProps, applyProps, invalidateInstance, attach, detach, prepare, globalScope, isObject3D } from './utils'
import type { RootStore } from './store'
import { removeInteractivity, type EventHandlers } from './events'
import type { ThreeElement } from '../three-types'

export interface Root {
fiber: Reconciler.FiberRoot
Expand All @@ -14,7 +16,7 @@ export interface Root {
export type AttachFnType<O = any> = (parent: any, self: O) => () => void
export type AttachType<O = any> = string | AttachFnType<O>

export type ConstructorRepresentation = new (...args: any[]) => any
export type ConstructorRepresentation<T = any> = new (...args: any[]) => T

export interface Catalogue {
[name: string]: ConstructorRepresentation
Expand Down Expand Up @@ -69,7 +71,23 @@ interface HostConfig {
}

export const catalogue: Catalogue = {}
export const extend = (objects: Partial<Catalogue>): void => void Object.assign(catalogue, objects)

let i = 0

export const extend = <T extends Catalogue | ConstructorRepresentation>(
objects: T,
): T extends ConstructorRepresentation ? React.ExoticComponent<ThreeElement<T>> : void => {
if (typeof objects === 'function') {
const Component = `${i++}`
catalogue[Component] = objects

// Returns a component whose name will be inferred in devtools
// @ts-ignore
return React.forwardRef({ [objects.name]: (props, ref) => <Component {...props} ref={ref} /> }[objects.name])
} else {
return void Object.assign(catalogue, objects) as any
}
}

function createInstance(type: string, props: HostConfig['props'], root: RootStore): HostConfig['instance'] {
// Get target from catalogue
Expand Down
4 changes: 1 addition & 3 deletions packages/fiber/src/core/renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as React from 'react'
import { ConcurrentRoot } from 'react-reconciler/constants'
import create from 'zustand'

import { ThreeElement } from '../three-types'
import type { Properties, ThreeElement } from '../three-types'
import {
Renderer,
createStore,
Expand Down Expand Up @@ -46,8 +46,6 @@ export const _roots = new Map<Canvas, Root>()

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]>

export type GLProps =
| Renderer
| ((canvas: Canvas) => Renderer)
Expand Down
5 changes: 0 additions & 5 deletions packages/fiber/src/core/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,6 @@ export type ColorManagementRepresentation = { enabled: boolean | never } | { leg
*/
export const getColorManagement = (): ColorManagementRepresentation | null => (catalogue as any).ColorManagement ?? null

export type NonFunctionKeys<P> = { [K in keyof P]-?: P[K] extends Function ? never : K }[keyof P]
export type Overwrite<P, O> = Omit<P, NonFunctionKeys<O>> & O
export type Properties<T> = Pick<T, NonFunctionKeys<T>>
export type Mutable<P> = { [K in keyof P]: P[K] | Readonly<P[K]> }

export type Act = <T = any>(cb: () => Promise<T>) => Promise<T>

/**
Expand Down
18 changes: 10 additions & 8 deletions packages/fiber/src/three-types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import type * as THREE from 'three'
import type { Args, EventHandlers, InstanceProps, ConstructorRepresentation } from './core'
import type { Mutable, Overwrite } from './core/utils'

export { Overwrite }
type NonFunctionKeys<P> = { [K in keyof P]-?: P[K] extends Function ? never : K }[keyof P]
export type Overwrite<P, O> = Omit<P, NonFunctionKeys<O>> & O
export type Properties<T> = Pick<T, NonFunctionKeys<T>>
export type Mutable<P> = { [K in keyof P]: P[K] | Readonly<P[K]> }

interface MathRepresentation {
export interface MathRepresentation {
set(...args: number[]): any
}
interface VectorRepresentation extends MathRepresentation {
export interface VectorRepresentation extends MathRepresentation {
setScalar(s: number): any
}

Expand All @@ -25,20 +27,20 @@ export type Layers = MathType<THREE.Layers>
export type Quaternion = MathType<THREE.Quaternion>
export type Euler = MathType<THREE.Euler>

type WithMathProps<P> = { [K in keyof P]: P[K] extends MathRepresentation | THREE.Euler ? MathType<P[K]> : P[K] }
export type WithMathProps<P> = { [K in keyof P]: P[K] extends MathRepresentation | THREE.Euler ? MathType<P[K]> : P[K] }

interface RaycastableRepresentation {
export interface RaycastableRepresentation {
raycast(raycaster: THREE.Raycaster, intersects: THREE.Intersection[]): void
}
type EventProps<P> = P extends RaycastableRepresentation ? Partial<EventHandlers> : {}
export type EventProps<P> = P extends RaycastableRepresentation ? Partial<EventHandlers> : {}

export interface ReactProps<P> {
children?: React.ReactNode
ref?: React.Ref<P>
key?: React.Key
}

type ElementProps<T extends ConstructorRepresentation, P = InstanceType<T>> = Partial<
export type ElementProps<T extends ConstructorRepresentation, P = InstanceType<T>> = Partial<
Overwrite<WithMathProps<P>, ReactProps<P> & EventProps<P>>
>

Expand Down
8 changes: 8 additions & 0 deletions packages/fiber/tests/__snapshots__/index.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ Array [
"Disposable",
"DomEvent",
"Dpr",
"ElementProps",
"Euler",
"EventHandlers",
"EventManager",
"EventProps",
"Events",
"Extensions",
"FilterFunction",
Expand All @@ -40,12 +42,16 @@ Array [
"Loader",
"LoaderProto",
"LoaderResult",
"MathRepresentation",
"MathType",
"Mutable",
"ObjectMap",
"Overwrite",
"Performance",
"Properties",
"Props",
"Quaternion",
"RaycastableRepresentation",
"ReactProps",
"ReactThreeFiber",
"ReconcilerRoot",
Expand All @@ -67,7 +73,9 @@ Array [
"Vector2",
"Vector3",
"Vector4",
"VectorRepresentation",
"Viewport",
"WithMathProps",
"XRManager",
"_roots",
"act",
Expand Down
6 changes: 6 additions & 0 deletions packages/fiber/tests/renderer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ describe('renderer', () => {
expect(scene.children.length).toBe(1)
expect(scene.children[0]).toBeInstanceOf(Mock)
expect(scene.children[0].name).toBe('mock')

const Component = extend(THREE.Mesh)
await act(async () => root.render(<Component />))

expect(scene.children.length).toBe(1)
expect(scene.children[0]).toBeInstanceOf(THREE.Mesh)
})

it('should render primitives', async () => {
Expand Down
19 changes: 2 additions & 17 deletions packages/fiber/tests/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as THREE from 'three'
import { extend, Instance, RootStore, ThreeElement } from '../src'
import { Instance, RootStore } from '../src'
import {
is,
dispose,
Expand All @@ -15,21 +15,6 @@ import {
updateCamera,
} from '../src/core/utils'

class TestElement {
public value: string
constructor() {
this.value = 'initial'
}
}

declare module '@react-three/fiber' {
interface ThreeElements {
testElement: ThreeElement<typeof TestElement>
}
}

extend({ TestElement })

// Mocks a Zustand store
const storeMock: RootStore = Object.assign(() => null!, {
getState: () => null!,
Expand Down Expand Up @@ -412,7 +397,7 @@ describe('applyProps', () => {
})

it('should not apply a prop if it is undefined', async () => {
const target = new TestElement()
const target = { value: 'initial' }
applyProps(target, { value: undefined })

expect(target.value).toBe('initial')
Expand Down

0 comments on commit 642c0ca

Please sign in to comment.