Currently we don't have the ability to deactivate or suppress a component from an entity temporarily. As this is a key advantage of an ECS I think it's something that should be added.
I feel that the best way to do this is to add two new methods to an entity. deactivateComponent and activateComponent. These would work in the same way as getComponent, taking a Class which if it exists on the entity would be activated or deactivated.
hasComponent would return false if the component is deactivated to ensure that behaviour is as expected, likewise getComponent will return null.
Implementing this would involve adding new methods to ComponentContainer (and those that implement it) to activate and deactivate components. A new store would be added to PoioEntityManager to keep track of the deactivated components and to allow for getComponent and related methods to be updated. This would in turn ensure that events are handled correctly as well.
I'd think the best implementation would be to add a DisabledComponentsComponent to the entity and move the component that is being disabled to a list on the DisabledComponentsComponent, it can be introspected easily and it properly and fully removes the component from the entity and system processing, and to move it back you can just re-activate it.
However, if someone tried to add a component to an entity that was already disabled, should a check be added to check for the DisabledComponentsComponent and iterate its list first to see if it disabled and reactivate it (with its original info?), or should it be ignored and the component has a new version of that component while the one on the DisabledComponentsComponent is either ignored or deleted? I think either way works but one method needs to be picked and stuck to in the engine. I'd personally vote on ignoring that DisabledComponentsComponent and adding the component regardless, if anything touches the DisabledComponentsComponent later that processing can clean out duplicates, or just ignore them (might be good for a history?)
I think adding another component to keep disabled components is a tad overthinking it. I'm sure we can add enough stuff on an entity to have proper "has" and "get"s to suffice any need (like have a bool parameter in these methods to search either the available or unavailable ones, or enum to have it search both as well). This way, when changing all methods to have the additional bool, we can comb through those which add a component if it doesn't exist so that they look through both sets, and leave the ones that just check for existing component with a "true" param.
As long as it does not leave the component 'on' the entity, that would suddenly require adding if(whateverComponentEnabled) checks everywhere, which though not hard, adds a bit of overhead that is entirely unnecessary as well as adding extra storage of at least 1 bit to hold whether a component is enabled or not unless a scan through a linear array or map or so is performed, which is even more non-free in cost.
That's why I mentioned the change. Having a bool or enum by which you check only for disable/enabled/both components of a certain class would mean no needing a new method. The component would have a saved bool to say if it's either enabled/disabled, and once that happens, you only need a base component method that returns that value (in case you got the component with "both" but still wanna check its state.
Having a bool or enum by which you check only for disable/enabled/both components of a certain class would mean no needing a new method.
But does mean a lot more conditional checks where there does not need to be, the ECS style already models this, if you want to 'disable' a component, you remove it (serializing its state elsewhere), there is no persist runtime cost for this either, only cost is the action of enabling or disabling the component itself, not on potentially every-single-component-access. Adding a boolean to the base component also inflates its size by a byte (on the Sun/Oracle JIT JVM, up to 9 bytes in others) for every single component needlessly. In addition you would have to be checking that boolean in a variety of areas and skip an operation if it is marked as disabled, which adds a conditional, which also slows down the code in potentially tight loops.
Some fictional use-cases:
HealthComponent and becomes immune. They player should not receive any damage but we could still want to count his health for global statistics.So the core problem is to disable to the functionality without loosing the data.
With the existing implementation, we could do this by adding an extra Component, which is then handled in the affected system, e.g. an ImmuneComponent or an TemporaryUnlitComponent.
This has the drawback of additional implementation effort on each system which has to disable it's effects. We also need an additional Component for each type of "ignore"-effect. This could get complex if there are a lot of ignore effects and effect systems.
A special logic for disabling a component could save some logc and classes but moves the extra implementation effort to all systems, which should always work, because they have to respect the active and disabled state. As @OvermindDL1 mentioned, this comes with an extra cost in the API, if we need additional checks when fetching components (which is probably one of our most-called functions).
We can shift the problem to a DisabledComponentsComponent and affected systems would then have to fetch two instead of one components (could be a conditional check). However this comes with another problem, as a Component field with the type List<Component> is probably not supported out of the box.
My personal preference would be to add extra components like ImmuneComponent and stick with the existing implementation. If we decide to do API changes and a general disabling system, I would like to discusse them on a PR whre the direct outcome is more visible.
A character has a HealthComponent and becomes immune. They player should not receive any damage but we could still want to count his health for global statistics.
The HealthComponent should only store the default health stats, say just maxHealth, there should also be a DamagedComponent that shows the amount of damage on the entity, when it is >= the HealthComponent.maxHealth then a system should run that kills the entity, when DamagedComponent hits <=0 it should be removed.
A character becomes immune for a short duration by using a potion. Afterwards the character becomes immune permanent by a command. After the potion effect is elapsed, the character should still be immune.
The system that applies damage should check for something like an ImmuneToDamageComponent, it could even have fields on it saying what kind of damage is immune, and maybe even for how long for auto-removal for each type, and once all immune types are gone then the component itself removed as well.
A torch is "blown off" and stops to emit light for a period of time. Afterwards it works as before.
Put on an AnimateEmittedLightComponent or so that changes the values in the existing EmitLightComponent to be a certain value (black for example), then have it flicker it back in on so-many-seconds until the animation cycle is complete and it is removed.
With the existing implementation, we could do this by adding an extra Component, which is then handled in the affected system, e.g. an ImmuneComponent or an TemporaryUnlitComponent.
This has the drawback of additional implementation effort on each system which has to disable it's effects. We also need an additional Component for each type of "ignore"-effect. This could get complex if there are a lot of ignore effects and effect systems.
More components is not a bad thing though, in my engine I tend to have many thousands to over ten thousand a few times different components (though many are just 'flag' components that only exist ethereally as they are optimized out to bitfields). But yes, a DisabledComponent could be useful, but it is not something I've ever used in my engines to date, rather I try to figure out a reason why I would want to disable something and see if I can invert it somehow (see health/damage above), add a component that can filter (like the immune one above, I have a few powerful immune component in my engine that handles armor, attack damages, types, time delays, etc...), or try to make it as generic as I can (in my engines I actually have a generic AnimateComponent that uses my internal dynamic lookup system to acquire a dynamically specific component to animate dynamically specified fields in a way that is fast but still easily serializable, could do it java too, just more costly there compared to C++).
We can shift the problem to a DisabledComponentsComponent and affected systems would then have to fetch two instead of one components (could be a conditional check).
Which is why the original one needs to be removed from the entity entirely. In an ECS system if an entity has a component then it is active, if it needs to be disabled then it needs to be removed. A trivial version of it might be to just have a DisabledComponent that holds an Entity ID (and creates that new entity on creation) and just moves the component from the main entity to the disabled entity (I have done this a few times in my engine I do admit, though I have a generic DisabledComponent that if it is on an entity gets it unregistered from all the other systems like it does not exist, and removing it re-registers it to all caches, convenient for such disabled entities).
My personal preference would be to add extra components like ImmuneComponent and stick with the existing implementation. If we decide to do API changes and a general disabling system, I would like to discusse them on a PR whre the direct outcome is more visible.
I'm also for this, the more generic it can be the better, but there should be no qualms of having tons of component types, in my engine I'm used to running 1k-10k component types with million+ entities in highly packed memory (gotta love C++).
Adding in some feedback from @immortius via Slack:
It is something I've mused about a bit. I wouldn't describe it as a fundamental feature of Entity Systems. It does provide a lot of convenience around removing/enabling features while retaining settings though.
Wouldn't be too hard to add to gestalt-es next time I pry myself free of other projects.
It might be simplest to add an "enabled" attribute to every component.
Which I can do since a lot of component details are code gen in gestalt-es.
Event system would ignore them.
Other things would have to check which isn't ideal.
Better is to have an active and inactive component collection for each entity.
And move components between them as needed.
Obviously need some methods to access the inactive store. And it needs to tie in to lifecycle events and persistence.
But pretty doable.
To compare to a mainstream ES (well... component system) - Unity doesn't all individual components to be disabled, only whole entities.
But then unlike Terasology the support for multi-entity constructs is pretty good in Unity.
So the base support for this might best go in the new v6 of Gestalt that includes the entity system in our external library, which still needs to be adopted for the Terasology engine. Unsure if that might push this to a v2.0.0 release due to backwards incompatible reasons. Should also include some doc updates and ideally another tutorial or two - comes with new features.
One use case I'm curious about: What if a player drinks an invulnerability potion, inactivating the health component, and then drinks a max health boost potion? Would that seek out a HealthComponent even if inactive to apply the effect for whenever invulnerability wears off? Or should gameplay logic dictate such a potion would have no effect if timed like that? Sounds like that might be too inflexible, so maybe checking for inactive would be right.
I really don't think disabling health would be a good approach to invulnerability for multiple reasons (e.g. if you have a health bar above an enemy, it would disappear when the enemy is invulnerable, which is not great).
Most helpful comment
I really don't think disabling health would be a good approach to invulnerability for multiple reasons (e.g. if you have a health bar above an enemy, it would disappear when the enemy is invulnerable, which is not great).