[Discussion] ECS-based architecture
simonihmig opened this issue · 4 comments
Hey @Rich-Harris, congrats for this (pre-)release, excited to see you explore the space of declarative 3D further! 🚀
I have also been doing that a bit over the last year or two. Though I am not a Svelte user (rather long-time Ember). Nevertheless I wanted to share my thoughts here, which are more at an architectural level, so not that much related to the actual JS framework. So sorry for hijacking this repo’s issue tracker for this, as I didn’t have any better channel available (certainly better than Twitter), hope you don’t mind…
So the approach we followed for adding declarative and reactive 3D to our Ember apps was inspired by AFrame, who were AFAIK(?) the first ones to do that on the web. And they chose the ECS pattern already popular in games to do that. So we basically followed that, and came up with
- ecsy-babylon, an ECS implementation for Babylon.js, based on ecsy - Mozilla’s (meanwhile unmaintained) ECS library
- ember-ecsy-babylon, the Ember sauce on top of it, that adds the declarative and reactive parts (based on Ember’s component and reactivity model)
Note that ecsy, the ECS library chosen here, is basically unmaintained, due to the restructuring/layoffs at the Mozilla (Mixed Reality) team. Same goes for their take of a 3D binding with ecsy-three, which didn’t get very far. So my lib probably also needs to be refactored/rewritten to use a different ECS base. But as this is about the architectural approach, this should not matter now…
And that has served us quite well actually in a bunch of apps. Some advantages I see with this:
- ECS has a nice composability (over inheritance) story. For example it should be „easy“ to add a physics component/system that interacts with the already provided mesh system, without having to make the mesh part aware of any physics stuff.
- By introducing a separate layer between the UI components and the WebGL system (be it three.js or babylon.js), you are decoupling the JS framework part from the 3D specific stuff.
To elaborate on the second part: in our case ecsy-babylon
probably contains 95% of the code, and is totally unrelated to any JS framework. It could be used on its own, if you just want to use Babylon with the ECS pattern. But that is still very much imperative style. Then ember-ecsy-babylon
is a pretty lightweight layer on top, that does not need to know much about the underlying layer. So you could add new functionality to ecsy-babylon
(say physics) without even touching ember-ecsy-babylon
. Still you will get the new functionality, with the added declarative/reactivity sugar. At its core it basically provides two UI components:
- one to create/delete ECS entities
- and another to add/update/delete ECS components to those entities
Beware of the unfortunate but unavoidable ambiguous use of the term „component“ - that’s why I’ll prefix it with either „UI“ or „ECS“
If you want to see an example how such a ECS-based declarative template looks like, here is the addon’s demo (source). Hope this is halfway understandable despite the „emberisms“ there.
So it should be pretty easy to create e.g svelte-ecsy-babylon
, with just the same lightweight UI components to handle entities and ECS components, with all the heavy lifting still being done by ecsy-babylon
! (This is just an example to illustrate the benefit of having this middle layer, I am not actually suggesting that specific example in any way)
So instead of reinventing the wheel, by having to wrap the (quite massive) functionality of 3D frameworks like Babylon or Three by every JS Framework (Svelte, Vue, Ember or what else), this middle layer could provide the shared base that others could build on top (with rather little effort). Basically instead of UI components
> WebGL library
, you have UI component
> ECS library
> WebGL library
.
That’s basically the idea I would love to hear your (and others) thoughts on! 🙂
Some other notable aspects:

- ECS does provide lifecycle functionality, so you can properly manage and dispose (#1) instances when removed from the scene (example)

- it does require you to wrap the underlying (Babylon/Three) APIs explicitly, making the code somewhat boilerplatey and coupled to (changing) underlying APIs (see this worst-case example). But that’s also true for your approach AFAICT, right? I understand that this is something that R3F takes pride in not having to do, as you also touched in „Component library versus renderer“. But not having the ability to design your own API comes with its own set of drawbacks I guess (e.g. not a fan of this attach thing).
hmm interesting. where does svelte-cubed sit? What kind of abstractions would work well?
svelte is all the way over in the UI components
zone but three.js is your WebGL library
. At a side, not that much of stretch to see svelte do the same thing around other graphics libraries too.
I like the words of Don talking more about good features you could have though a compilation step.
I have no idea of any alternative to ECS. It is a staple 3D world abstraction that everyone gets.
At a side, not that much of stretch to see svelte do the same thing around other graphics libraries too.
Absolutely. And that's why this idea of a ECS-based middle layer came up. I only mentioned 3D here (hence WebGL library
), as that's what I am most interested in, and what's happening here. But you can just as well build ECS-based abstractions around other kinds of "renderers" (ecsy-two, ecsypixi, ...).
To give an example how this would scale up, say we have 5 major UI frameworks (React, Vue, Angular, Svelte, Ember) and 5 "renderers" (Three.js, Babylon, Pixi, Fabric, etc.). When each UI framework has to create a "binding" to the renderer, then you would end up with 25 (5x5) libraries. With a middle layer, you would more or less have 5 UI-to-ECS libs + 5 ECS-to-renderer libs, so 10. Of course with higher number, the middle layer version scales much better (m+n
vs. m*n
).
So my point is to be able to share the hard parts (here the ECS-to-renderer) across ecosystems, instead of reinventing the wheel.
Note that this is (admittedly too optimistically) assuming there is just one ECS library which all libs use. But even when there would have to be a UI binding for each UI framework and ECS-to-renderer combination (which would mean m*n+n
in the worst case), this would probably still be beneficial in terms of effort, as the majority of the work lies in wrapping the renderer (the ECS-to-renderer part), and that would only happen once per renderer (n
), and not per UI/renderer combination as it is now (m*n
)
I like the words of Don talking more about good features you could have though a compilation step.
Sounds interesting, but to me this seems orthogonal to the question here how the still needed runtime part is implemented (with our without that middle layer)
5 major UI frameworks (React, Vue, Angular, Svelte, Ember) and 5 "renderers" (Three.js, Babylon, Pixi, Fabric, etc.).
I do not know the ambition of this project nor will speak for anyone else. It seems the goal of this project is to promote Svelte's unique characteristics as a compiler for 3D modeling for declarative programming. So adding a ECS middle layer could just be adding complexity and scope creep.
I'm currently working with Aframe and 8thwall to do commercial webXR.
Recently I've taken up using Svelte and Aframe for a side-project and the performance is absolutely amazing.
It is faster to write functionality in Svelte that in vanilla Aframe because with Svelte's data binding you can avoid registering custom Aframe components and calling .setAttribute in your code.
In Svelte you can simply modify color
in handleClick:
<a-box position="0 0 -5" color={color} on:click={handleClick}> </a-box>
The same could be true for Svelte-Cubed. Svelte-Cubed props are almost exactly like the properties of an Aframe component schema ( minus Aframe's handy parsing ).
An entity component system benefits Aframe in that it allows the a-entity
style markup to abstract the 3D scene spatially and with Svelte-Cubed, I was excited to see the <SC.Group property>
syntax coming to do the same thing: represent 3D spatial scenes with markup. @Rich-Harris seems to be headed to replace the Aframe component lifecycle methods with a set of lifecycle utility functions which could potentially do the same thing, allow the dev to interact with the render cycle from multiple components at once, every frame. Using Svelte and Aframe together, the only thing I now need to register Aframe components for is to use those lifecycle methods.
I can't imagine really though mixing aframe and svelte-cubed. Not yet, anyway! I'm quite excited to play with it! It really looks slick and would be perfect for building out threejs projects that don't require the overhead of Aframe and could in the future come to support its own webXR implementation aside from Aframe.
Looking Forward, Loving Svelte so far!