Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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:
pnpm run playground
You should see this:
Non-breaking
Afaik, this change is non-breaking.
User-facing API
Plugins are currently imported like
... and passed to
<TresCanvas />
likeAfter that, whatever functionality the plugin provides is available to the user.
pluginCore
is loaded by default if the user doesn't specify aplugins
prop. This plugin includes Tres'RendererOptions
formerly insrc/core/nodeOps.ts
, e.g., turning<TresMesh />
intoTHREE.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 withRendererOptions
, that lends itself to a nice decoupling:type Plugin
), and when to call their methods.On the downside, it's an additional layer of abstraction.
As the coordinator, the plugin system's main job is to:
context
(i.e., call the plugin withcontext
)RendererOptions
methods) byweight
RendererOptions
calls to the proper plugin entries using the returnedRendererOptions
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 whenTresCanvas
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'sinsert
, 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
methodsPlugins can have a few
filter
s that the coordinator runs before forwarding a lifecycle call to the plugin. The filters are in the form of:... 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'sinsert
method: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
andRendererElement
from Vue'sRendererOptions
. The distinction is irrelevant for the Tres core, which sets both types toTresObject
. 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 aTresContext
.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 extendcontext
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 offeruse*
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 />
intoTHREE.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 thecontext
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
andbeforeAll
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 formernodeOps.ts
tocore/plugins/tres.nodeOps.plugin.ts
, and git didn't figure it out.It's mostly untouched, except for
createElement
wasn't assigningposition
androtation
to theObject3D
, but the physics plugin needs those values to be set on creation, due to theRendererOptions
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