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

feat: add plugin capability #588

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

Conversation

andretchen0
Copy link
Contributor

@andretchen0 andretchen0 commented Mar 14, 2024

As mentioned on Discord and here, I've been working on a POC to give Tres the capability to integrate plugins that use "nodeOps" (Vue's RendererOptions).

Here's a draft PR with the current state of affairs.

Sorry for the brain dump below. There were a lot of design decisions involved in getting the system up and running. I want to let you know what those are below.

But first ...

Run it

There's currently one playground example that shows off 2 (POC) plugins: Cannon physics and on-screen text. The playground file is at playground/src/components/TheExperience.vue.

To view it:

You should see this:

Screenshot 2024-03-14 at 16 32 37

Non-breaking

Afaik, this change is non-breaking.

User-facing API

Plugins are currently imported like

import { TresCanvas, pluginCore, pluginCannon, pluginText } from '@tresjs/core'

... and passed to <TresCanvas /> like

<TresCanvas :plugins="[pluginCore, pluginCannon, pluginText]">

After that, whatever functionality the plugin provides is available to the user.

pluginCore is loaded by default if the user doesn't specify a plugins prop. This plugin includes Tres' RendererOptions formerly in src/core/nodeOps.ts, e.g., turning <TresMesh /> into THREE.Mesh.

Design

Like Vue vis-a-vis RendererOptions, the idea here is to let the plugins define their behavior, while the system acts as a coordinator. Like with RendererOptions, that lends itself to a nice decoupling:

  • the coordinator doesn't know anything about plugins except their "shape" (type Plugin), and when to call their methods.
  • the plugins don't need to reach out and coordinate with the larger system.

On the downside, it's an additional layer of abstraction.

As the coordinator, the plugin system's main job is to:

  • bind the plugins to context (i.e., call the plugin with context)
  • order the plugin entries (RendererOptions methods) by weight
  • forward Vue RendererOptions calls to the proper plugin entries using the returned RendererOptions
  • call any other lifecycle methods – currently only dispose

Implementation

Closures

Plugins are currently functions that close over their state. So a plugin shouldn't leak unless the plugin author makes it leak.

setup / dispose

Plugins are functions. Setup happens when the plugin is called. A plugin can provide a dispose function that the system should call when TresCanvas is unmounted.

Not reactive

The plugins prop added to <TresCanvas /> will (probably?) not be reactive.

The plugin system itself could be written to allow plugins to be added/removed, but the nature of RendererOptions makes this difficult conceptually when authoring plugins – a plugin will usually manage a component's entire lifecycle, and if a plugin isn't around for e.g., a component's insert, it misses that lifecycle event.

And since we don't really want the plugin to ask the system – "What's your current status? Did you create any components I care about before I was turned on?" – plugins really need to be around for the entire lifetime of a <TresCanvas />

Types in RendererOptions methods

Plugins can have a few filters that the coordinator runs before forwarding a lifecycle call to the plugin. The filters are in the form of:

(a: any): a is T => boolean

... so that means there's less type checking to do in a few of the RendererOptions methods, as they can be typed from the start. E.g., here's the Cannon plugin's insert method:

insert: (body: Body, parent) => {

Generics

Again, like Vue RendererOptions, the plugin system core (src/core/nodeOps.ts) is written generically. It doesn't "know" it's part of Tres.

The plugin system uses RendererNode and RendererElement from Vue's RendererOptions. The distinction is irrelevant for the Tres core, which sets both types to TresObject. But in case a plugin needs to make the distinction, it's been kept.

To those two, the system adds a third generic called HostContext – the context that's bound to the plugins on setup. Currently, this is a TresContext.

Moving forward

Sharing: store?/provide

In another thread, I asked about including Pinia or another store. Pinia probably isn't what we want, but I do find myself wanting an established way for plugins to share their data.

All plugins get access to context when they're bound. I think it'd be natural to extend context to allow plugins to put getters/actions on it, maybe as their own "substores"?

Maybe we also allow plugins to provide so that they can offer use* directives that will work in child components.

Move context creation to plugin?

The former core RendererOptions (src/core/nodeOps.ts) that turns e.g., <TresMesh /> into THREE.Mesh is now a plugin itself (src/plugins/tres.nodeOps.plugin.ts).

It seems like a natural fit to me to allow that plugin to create context during its setup. Or maybe populate the context argument that it's passed.

File organization

Maybe put plugins in a separate project? Otherwise e.g., physics libraries will be included in the core's package.json.

Types in editor

Plugin-defined types aren't currently available in the editor. Pointers for adding them would be welcome!

Meta

Tests

Existing tests pass. Just FYI: nodeOpts.test.ts was moved to /src/plugins/tres.nodeOps.plugin.test and beforeAll in this file was changed as the signature of the nodeOps function has changed.

Some unit and implementation tests for the system were added at src/core/nodeOps.test.ts. That might be a good place to look for some very simple plugin examples.

Commit notes

I forgot to git mv the former nodeOps.ts to core/plugins/tres.nodeOps.plugin.ts, and git didn't figure it out.

It's mostly untouched, except for

  • changing it to the new plugin format
  • patching a small lifecycle problem: createElement wasn't assigning position and rotation to the Object3D, but the physics plugin needs those values to be set on creation, due to the RendererOptions lifecycle ordering. Without the change, colliders are all instantiated at [0,0,0], meaning they all collide. Bad.

Physics: why not rapier?

Rapier is a wasm module. Vue currently can't deal with those without a third-party plugin.

Feedback welcome!

@alvarosabu @Tinoooo @JaimeTorrealba

Copy link

netlify bot commented Mar 14, 2024

Deploy Preview for tresjs-docs ready!

Name Link
🔨 Latest commit f6c4c02
🔍 Latest deploy log https://app.netlify.com/sites/tresjs-docs/deploys/65f32a3bc0ab690008f699d5
😎 Deploy Preview https://deploy-preview-588--tresjs-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@andretchen0 andretchen0 marked this pull request as ready for review March 17, 2024 15:54
@andretchen0 andretchen0 marked this pull request as draft March 17, 2024 16:39
@alvarosabu alvarosabu added experimental Experimental feature investigation labels Mar 31, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
experimental Experimental feature investigation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants