@Steveblue Can you describe where/why the bottle necks happens in worker vs non-worker?
I think the latency of the messaging back and forth between window and worker is hindering performance. The latency may only be ms but it has to be below 16ms to fully operate at 60fps. Perhaps my method of running requestAnimationFrame on the window is flawed and writing a polyfill for requestAnimationFrame on the Worker is the answer, but it is my understanding after doing some research that none of the existing polyfills perform better than 30fps. Right now boxer runs 60fps on the window, on every frame a message is sent to the worker to update, while at the same time the worker may be messaging the window to update properties tied to DOMComponent. Possibly making all messaging unidirectional will solve some performance issues.
@joe Have you made any progress trying to use a Worker? What have you found?
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 class
es (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()
scene.add(Engine.createNode())
or
let scene = Scene.create()
scene.add(Node.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 Node
and 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. :]