playcanvas / playcanvas/engine
[RFC] Async/Lazy Component Systems
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 16.8k
- Forks
- 2k
- Avg merge
- 4h 32m
- Merged PRs (30d)
- 222
Description
Before you can add a component to an entity, you currently need to register the component system with the app.
opts.componentSystems = [RenderComponentSystem]
const app = new AppBase(canvas);
app.init(opts);
app.start();
new Entity('cube').addComponent('render');
This has a couple of friction points:
- Creates unnecessary boilerplate. Engine users are required to manage a list of component system. This either means that systems are included when not needed, or worse, components are added without systems, causing errors and friction for developers
- In editor projects, every component systems is added by default, regardless of whether they're used or not.
- In both cases neither can be tree-shaken, as they're instantiated by the App
An optimal solution would;
- Only instantiate components systems when necessary
- Not require manually maintaining a list of component systems.
- Support tree-shaking
Proposal
Update the addComponent() method to resolve to a lazily loaded component system:
const createComponentSystem = async path => {
const class = await import(path);
return new class();
}
addComponent(type, data) {
let system = this._app.systems[type];
if (!system) {
switch(type) {
case 'camera' : await createComponentSystem('./CameraComponentSystem.js');
case 'render' : await createComponentSystem('./RenderComponentSystem.js');
}
}
// ...
}
Benefits of this approach
- Maintains similar api
addComponent('camera') - Component Systems are lazily loaded.
- Simplifies engine only startup by deprecating the need for specifying
systemsarray. - Better supports tree-shaking.
- Editor projects only load necessary components.
Cons
Modifies the addComponent() to become async. Strictly speaking this is a breaking change as it requires await addComponent() which means a semantic major version bump.
Other options
An alternative non breaking change would be to use statically imported components which are not async
import CameraComponentSystem from './CameraComponentSystem'
addComponent(type) {
// ...
switch(type) {
case 'camera' : CameraComponentSystem;
....
}
}
This still has the benefit of a not requiring specifying a component system array, but is harder to tree-shake.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading src/framework/app-base.js at the component-system initialization referenced in the issue, then trace addComponent() and the systems list. Compare the asynchronous and static-import proposals, and define how lazy instantiation, tree-shaking, editor behavior, and the breaking API change would be validated before implementation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- game-dev
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100