-
Notifications
You must be signed in to change notification settings - Fork 26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add entity/component system? #154
Comments
Data-oriented ECS systemThis approach which I've drafted above is not really an ECS in its true sense as in GitHub projects listed above or described on wiki because:
The overhead cost of ECS here is going through the list of all entities and finding those matching the required component set. The https://github.com/soulfoam/ecs solves it by defining the component mask for each entity. Matching is done by simple ANDing with specific mask combination. The other approach is to have struct of This could work and should be integrable with ACE just fine at the present point - it doesn't affect the ACE architecture in any way, because only game logic elements use ECS and the remaining engine code is written as-is. There are some concerns which I have:
One major upside - when organizing as: for(auto &Entity: Entities) {
if(Entity.pBob != null) {
Entity.pBob->undraw(Entity);
}
}
for(auto &Entity: Entities) {
if(Entity.pBob != null) {
Entity.pBob->saveBg(Entity);
}
}
for(auto &Entity: Entities) {
if(Entity.pSteerLogic != null) {
// Can be class derived from tSteerLogic with virtual process()
Entity.pSteerLogic->process(Entity);
}
}
for(auto &Entity: Entities) {
if(Entity.pBob != null) {
Entity.pBob->draw(Entity);
}
} it makes code quite clean. The downside is that this approach discourages interleaving blitter and CPU work - e.g. requesting drawing of object as soon as its steer logic is processed so that it could be drawn while other one is being calculated. Again, this could be solved with cached lists of entities which are processed by same systems. Since this system is for game logic only, it doesn't have to be integrated with ACE - and I would skip doing so because falling back to generic approach misses opportunity to optimize for given game project. |
Just stumbled across this. Having had some discussions at Uni with someone who is doing research in that space, I came away with the feeling that ECS systems really do not play to the strengths of the Amiga's hardware - like you say, they don't really encourage you interleave coprocessor operations with other CPU-handled component operations, for example. I guess it can be done, just that it really would need some thought to make sure not to waste cycles. |
I'm writing this as a reaction to Nivrig's and Kwahu's discussion on Amiga Game Dev discord. Some points of reference for ECS systems:
The unity-like gameobject system
This thing is better written in OOP languages, so it would need #153
Basically the thing used by Unity and most game engines nowadays. The most basic ECS is something like this:
along with constructors and destructors for them. Now, all ACE managers are already written to have create/process/destroy fns and are sometimes nested and cross-referencing each other so they could be converted to such components.
This is my quick and dirty proposal, I'm not a big fan of it at this point, so it definitely needs more work and thoughts. There are some problems to solve and I'd like to hear some more input about it.
Components of components
If you look at the ACE view/viewport system, it already looks like ECS. Each view has viewports and are processed on the list, so they could be treated as view's components. Then, each viewport has viewport managers - be it camera, simplebuffer, etc. So that would mean that... each viewport can be treated as an entity and have the viewport managers as components. This deep nesting breaks ECS pattern, so something else would be needed.
Instead, it could be solved by limiting it to component system. That means, ACE would expose primitives to manage and process list of components. This way, each class could have its components and those could be nested indefinitely. The code could look something like this:
then, the view could be a component of the game state and insideGameState() could be just done with
gameState.components.process()
.The
tComponentList
could expose following functionality:process()
methodgetComponentById()
whereid
is an unique string or enum or int - could be sped up by usingstd::map<id, tComponent>
instead of plain array orstd::vector
or having both for those two access types, or make it use a vector but emphase that searching for references is slow and should be done in the component constructor or post-construct stepgetComponentByType(lastSearchPos, type)
- thetSimpleBufferManager
looks for already presenttCameraManager
and creates the new one if it isn't found. Unity ECS enforces components to have unique type, but since my proposal skips entity lists, we'd have to work around this limitation, so making this function re-entrant to get next result is a must.tVPort &
arg in its constructor and work from there to the view on its own.The post-construct step
Finding reference to other components brings another problem - when done in constructors, it will only work with already-constructed components. There would have to be another step to doing post-init jobs like finding references. This could be skipped by making ref search inside
process()
function very fast, but perhaps it would expose more unexpected problems of same nature later on.Perhaps the component constructors should to as little as possible and have the dedicated
Construct()
method which would be called after all lists are filled.Zero-cost abstractions
The first thing which comes to mind is that
tComponentList
should hold something following and just call theprocess()
method:although deriving all components from such class allows for putting different component types into same list, this would introduce some overhead in scenarios where they are all the same (think of viewports inside a view):
process()
functions are all the same and can be inlined and optimized furtherThe solution would be to have the whole component system templated:
this would result in calling a same
process()
function in a loop with different objects passed in an arg - perhaps even giving optimization opportunity to unroll it. Which shows that maybe even component storage could be passed to the template - either usestd::vector
orstd::array
of predefined size for extra optimizing lists of constant size.Order of execution is crucial
Currently, view must execute viewports in order of their appearance. Also, there is a strict order for viewport managers execution or else everything explodes. Also, when writing gamestates for most of the games, it quickly becomes apparent that most operations must be done in strict order, so component list must be ordered. They probably could store an ordinal number and be sorted with each addition of component, so that the process() wouldn't have to waste time on determining the order of execution. ACE could define them in hundred- or thousand-increments to allow users adding custom logic in between.
That solves the display, input and other core components, but doesn't solve the game logic components, should ACE bundle them. Their order may vary between projects. Perhaps those should accept ordinal number as a parameter in constructor but that adds a bit of tedious manual management.
Another option would be setting the components when instantiating the list, e.g.
but that prevents instantiating additional components by others (e.g.
tSimplebuffer
/tScrollbuffer
instantiatestCamera
). Perhaps someone would like to have enemies or other entities dynamically spawned and managed as a list of components. What then?Changing which components are executed and reordering them
Some components may need to be occasionally disabled (enemy component is dead, projectile has ended its lifespan and is ready for reuse). Skipping components which have
m_isDisabled
set to true is a one way to solve this, but having lots of those lists introduces significant overhead in checking if any of them should be skipped. Perhaps there should be a list of enabled components acting as a cache, but updating it should be done on-demand and e.g. once per frame.Also, in case someone is mad enough to do single-buffered game and have the blittable entities as components, there should be a way to Y-sort the relevant component list so that blits are always done from top to bottom.
Forcing a code layout
This one is big. Shifting ACE into component system would enforce that code layout on all projects instead of allowing each project to have their own less or more hardcoded systems. I don't know how I feel about taking away that degree of freedom from ACE users. This would drastically shift ACE from being a mostly-library framework-ish to being full-blown engine/framework.
Blitter concurrency
I thought this might be a problem, but then if the components are in fixed order then it is possible to have blits scattered across the code, preventing prolonged blitter idle times. Other blitter management methods exist, but I prefer to not prevent any of them as ACE should be as versatile and not limiting tool as possible.
The text was updated successfully, but these errors were encountered: