I haven't gotten anything rendering with a worker version yet, but what I've found so far is that it's hard to make and API (around workers) that is as easy to use without knowing workers are in play. Maybe I'm starting at too high of a level to work down from?
I have some idea of what I'm gonna change about my current design though. At first I started with something similar to yours, where the whole scene is in a single worker. Then, when the user does
new Node on the UI side, that automatically creates a reciprocal
Node instance inside of the worker. It's the same class, with one instance on both sides, but some methods are UI-side methods, and some are worker-side methods, while some are hybrid and can be called on either side. I'm not sure of keeping this structure though. My goal is to provide a simple UI-side API, but in making it simple, the implementation is a lot more involved. One of my goals is also to prevent the user from hurting themselves (or their app) with the API, which makes things perhaps less flexible, but easier for a beginner to start using and easier for us to guarantee some level of performance without users worrying about it.
When a user simply writes
new Scene, behind the scenes that gets a ref to an
Engine singleton, associates the new Scene to the Engine (Engine is part of the private API), creates a scene worker if one doesn't exist yet, and finally a reciprocal creates Scene object in the scene worker. That all happens without the user knowing. The user only has to do
scene.add(new Node) to begin to build a scene graph, which behind the scenes causes a reciprocal worker Node to be instantiated in the scene worker.
I'm thinking about splitting the scene worker into separate workers, where on is merely a communication gateway, between the UI and other workers. There will be a worker who's sole job is to calculate world transforms from the scene graph, and separate workers for calculating (possible groups) of calculations that map to local transforms on Nodes. I'm not exactly sure what that'll look like yet though. Maybe you'll get some ideas from reading this.
After reading some articles (I forgot where they are :[ ), I'm not sure exposing direct
classes (Node/Scene/etc) as part of the public API is the best thing to do. I think it might be better to expose methods or functions for creating the pieces that the user needs, then that way (behind the scenes) it can be easier to separate certain logic from certain classes (for example, remove the creation of the Engine and SceneWorker singletons from the Scene class, and do those in a function that create the Scene instead:
let scene = Engine.createScene()
let scene = Scene.create()
The variable and method names are subject to change, but the idea is that we expose public methods/functions for creating the pieces we need, while internally they could be implemented with classes. The benefit is that people that use the public API don't have to worry about how to extend a class in order to build more things. Instead, users can make their own classes that use these creator/factory methods/functions, and the behavior of our class instances can then be guaranteed more easily. Of course, there's nothing stopping a user from importing the Node class directly, but that doesn't need to be part of the public API ("private" API could be toggled with a button in the UI and show a warning that this isn't the recommended way of using our library and is subject to change).
In the app that I'm currently working on (not using workers yet), I'm making UI components that use
Scene instances but never extend them; the UI components only ever contain instances of the engine's classes and do as needed with them. I'm trying it out to see what it's like not to extend anything from the library.
Some of the concepts of not extending are from guidelines I've read about React where some suggest to always use composition of components instead of component extension (except for the mandatory extending of React.Component to initially create components). I've found that, indeed, code becomes easier to reason because then extensions and modifications of the behavior of base classes does not become cognitive load that the developer has to keep in mind.
I've been using async/await with CSP using async-csp in my current project (look at some of the Channel methods like
Channel#pipe which might seem similar to methods like Famous 0.3 EventHandler#pipe). I'm wondering if any of these async/await+csp techniques might be handy in coordinating workers and other things in the engine...
So, that's where I'm at thought wise. I've gotta get something rendering though, then I'll have more ideas. :]