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

solidified solid-three #15

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
Draft

solidified solid-three #15

wants to merge 11 commits into from

Conversation

bigmistqke
Copy link

Implementation of proxy-based threejs renderer, based on the exploration done in the zustand-to-store branch.

This branch deviates from the previous goal of staying close to r3f's codebase in favor of simplification and solidification

Similarities

  • Props management
  • Children/scene graph management

Differences

  • Custom event handling.
  • We only store the props of the component in the threejs instance (behind $S3C symbol)
  • No central store.
  • useThree returns canvas, threejs' webgl-renderer, camera, raycaster and scene.
  • useLoader is a very minimal wrapper around createResource.
  • Current implementation defaults the catalogue to {}.
    • extend(THREE) if you do not want to think about treeshaking.
    • I am working on a babel-plugin to create automatic extend calls.

Implementation of proxy-based threejs renderer, based on the exploration done in the zustand-to-store branch: see #12

This branch deviates from the previous goal of staying close to r3f's codebase in favor of simplification and 'solidification'.

How the props are managed (= applying jsx-props to three-instand and managing of the scene graph) and the Primitive-component are copied from the zustand-to-store branch.

differences:
- Custom event handling
- Augment the threejs instance with far less in comparison then r3f: we only store the props of the component
@vorth
Copy link
Member

vorth commented Apr 12, 2024

Are these similarities and differences comparing this branch with zustand-to-store, or with main? The latter is more valuable.

I really don't have a clue how to assess the merits of this PR, sorry. We don't have a test suite, so I can't really do it empirically. There's not enough information in the PR to really understand the changes and any tradeoffs they imply.

We really need user documentation, or at least LOTS of sample code. I think we're still in the unfortunate situation that someone can't really predict how to use the library without understanding both its implementation AND the tooling transformation it requires.

Also, if you're still using "T.whatever", we don't need any custom tooling, and I'd prefer to keep it that way. Just give me sample code for the registrations.

BTW you should change the version number from "0.2.0" to anything else.

Prior
- our event system was not bubbling events down the scene graph. 
- `event.stopPropagation()` prevented events from reaching objects behind the `Object3D` that called this event.

After
- adds event bubbling 
- adapts event.stopPropagation to stop event bubbling, similar to the DOM/react-three-fiber.
- adds pointer and setPointer to ThreeContext

The event propagation deviates from react-three-fiber regarding derived events: `on{Pointer|Mouse}Enter` and `on{Point|Mouse}Leave` are derived from `window.on{Pointer|Mouse}Move`.

In react-three-fiber stopping propagation on a move-event has an impact on leave/enter-events: all the objects that are currently 'hovered' are 'unhovered' and their leave-eventhandlers are called. This includes the object that called event.stopPropagation().

See https://docs.pmnd.rs/react-three-fiber/api/events#event-propagation-(bubbling) for documentation.

This is not an intuitive approach in my opinion.

In the current implementation move events and derived events are separated from each other, stopping propagation move does not impact the object's hovered state, nor does it call the leave-eventhandler.

Stopping propagation inside a move or derived eventhandler stops only the bubbling down of the specific eventhandler.
This commit ports react-three-fiber's tests to solid-three and adds testing utilities and components to a new entry: `@solid-three/testing`.

The r3f tests are ported from `jest` and `testing-library/react` to `vitest` and `@solid-js/testing-library`. These tests are located at `./tests`.

r3f has a package `react-three-fiber/test-renderer`. I took their WebGL2RenderingContext- and canvas-mock implementation and moved it to `src/testing`.

Exports of `solid-three/testing`:
- test() 
    - A utility function to setup a testing environment.
    - Accepts as arguments
        - Accessor<ThreeComponent>
        - CanvasProps
    - Returns TestAPI
      = ThreeContext augmented with the eventRegistry-object and utility functions to assist with testing: unmount() and waitTillNextFrame().
- <TestCanvas/>
    - a mock of <Canvas/> to be used in non-DOM environments.

Currently we have 1 standing bug. Most of the missing API has been re-implemented (see Fixes and Features).

Missing API:
- PointerCapture
- onPointerMissed-event
- Canvas-prop performance

Bug:
- Order of ThreeElement's children is not synchronised with the order of ThreeComponent's children-prop
    - see "can swap 4 array primitives"

Fixes:
- Update sub-properties when a property changes:
    -  p.ex in <T.Mesh position={} position-x={}/>
      If props.position updates, position-x will be applied to it immediately after.
    - see "invalidates pierced props when root is changed"

Features:
- <Portal element={Object3D}/>
  = Portal accepts now an Object3D as optional element-prop. If it is defined it will render into that element instead of the scene.
- Portal overwrite the ThreeContext's scene with props.element if it exists.
- Config threeContext's gl, camera, scene and raycaster with props on <Canvas/>
- Canvas' gl-prop accepts a callback: (canvas: HTMLCanvasElement) => THREE.WebGLRenderer.
- Attach-prop accepts a callback, besides "string"
    - see "attaches Object3D children that use attachFns"
- Extensible typing for T with namespace SolidThree. 
    - extend() now only accepts SolidThree.Elements.
- Allow Accessor<string[]> in useLoader: 
    - p.ex useLoader(GLFLoader, () => ["string", "string"])
      Returns Resource<readonly [GTLF, GLTF]>
    - see "can handle useLoader hook with an array of strings"
- Cache results of useLoader
- Optional setup-function in useLoader
    - see "can handle useLoader with a loader extension"
- Add onUpdate-event: gets called on every prop-update.
    - see "onUpdate shouldn't update itself"
- Initial xr-support. Running the tests is not sufficient to know if this is actually working.
- <Canvas/> configuration/props:
    - Shadow-prop: default shadow map
    - Flat- and linear-prop: tone-mapping and color-management
    - Frameloop-prop: "always" | "never" | "demand"
      we expose a render and requestRender-function from useThree()

Other changes:
- Use tsup instead of vite for building
- Cleanup vite.config.ts
- Bump version number to 0.3.0
@bigmistqke
Copy link
Author

bigmistqke commented Apr 17, 2024

Are these similarities and differences comparing this branch with zustand-to-store, or with main? The latter is more valuable.

In comparison with zustand-to-store. This initial commit was a very barebones implementation of a three-renderer for solid. I have since re-implemented a lot of the API react-three-fiber provides.

I really don't have a clue how to assess the merits of this PR, sorry. We don't have a test suite, so I can't really do it empirically.

I created testing utilities for solid-three (see ./src/testing) and ported the tests from react-three-fiber.
We have some tests failing due to missing API, but I would consider these non-essential.

There's not enough information in the PR to really understand the changes and any tradeoffs they imply.

From userland there are currently little changes, now that I re-implemented most of the API, except from some internals

  • useThree-value is more minimal
  • event-objects/event-handlers are simpler. react-three-fiber makes a lot of objects during event-handling, I don't know if we should reproduce this.
  • missing API:
    • onPointerMissed
    • PointerCapture
    • performance-prop in canvas

We really need user documentation, or at least LOTS of sample code. I think we're still in the unfortunate situation that someone can't really predict how to use the library without understanding both its implementation AND the tooling transformation it requires.

Yes, I agree. I (and chatgpt) provided jsdoc comments for most of the functions, also internal ones. But we need documentation/examples, for sure. A REPL would be very handy.

LinearEncoding and sRGBEncoding is not included anymore in later versions of threejs. react-three-fiber hardcoded these enums.

see pmndrs/react-three-fiber#2829
- remove `@types/three` and `zustand`  from dependencies
- add `@types/three` to peer-dependencies
- update `three`, `tsup` and `solid-js`in dev-dependencies

update pnpm-lock.yaml
This prevents flashes.
before
- createThree and other initialisation code was called immediately after declaring canvas
- this caused issues with binding pointer-events on macOS chrome 125.0.6422.78 (arm64)

after
- createThree and other initialisation code is wrapped in onMount
- add comments
- extract type-utilities into ./type-utils.ts
- export under the `S3` namespace
- (fix) infer THREE.Color correctly
- simplify types
- remove types from jsdoc comments
before
- `solid-three` does not expose api to set the core elements from `useThree`.
- In `react-three-fiber` you can set core elements from `useThree` with zustand: you directly set the element in the store and it is the responsibility of the caller to manage proper cleanup.
- an example of how this clean-up looks like:
createEffect(() => {
  const oldCam = untrack(() => store.camera)
  store.set({ camera })
  onCleanup(() => {
    store.set({ camera: oldCam })
  })
})
- this is bug-prone since it relies on there only being a single camera that overwrites the default camera. When there are multiple cameras, the order of unmounting could create unwanted side-effects.

after
- use stack-like data-structure to manage all the core-elements: `Stack` and `AugmentedStack`.
- the top element is being used as element in the store with `stack.peek()`.
- push returns a cleanup-function
- pushed element is automatically removed onCleanup
- a warning is given in dev-mode if 
    - There are more then 2 elements in the stack (the default and an overriding element), since this still could lead to unexpected behavior.
    - An element is added to the stack outside of a `createRoot` or `render`, since this will require manual cleanup.
    - TODO: improve warning messages.

also
- adjusted implementation `withMultipleContext`.
- return-type `withContext<T>()` and `withMultipleContext<T>()` is  `T` instead of `Accessor<T>`.
- wheel-event is passive in `createEvents`.
remove packageManager-property from package.json
@bigmistqke
Copy link
Author

bigmistqke commented Jul 15, 2024

Following api still needs to be implemented:

  • context.clock
  • context.size
  • context.viewport
  • second argument to useFrame to schedule
  • key-property to force re-creation of three-element

Following api might be implemented (let's discuss)

  • context.control
    • This value is not used in solid-three internals but is a convention by react-three-fiber so that other components can react to it. It is a bit awkward that it is purely by convention and cannot be enforced.
  • context.performance
    • Again not a value that is used internally by solid-three, but instead is a value that can be used to orchestrate a performance downgrade from the consuming code. I am not sure if this is something that we need to provide, as it can be easily implemented userside with a simple context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants