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] experiment: extend factory overload #2785

Merged
merged 10 commits into from
Sep 17, 2023
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
}
}
CodyJasonBennett marked this conversation as resolved.
Show resolved Hide resolved

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