IIRC this is a performance issue. Said rapidly added and removed components tend to be “message” or “status” components. For example, a soldier entity with a ton of components can acquire a GetReadyToShoot component that only lasts 1 frame. Or maybe a soldier holds on to a Suppressed component for only a few dozen frames. In any case these constantly changing archetypes would waste chunk space and induce latency. Would the solution be to have a sort of “status entity” where the parent entity is comprised of rarely changed components, and the status entity is comprised of fewer but transient components? A StatusEntity component for the parent entity and AffectedEntity component for the status entity would be used to link the two.
You’re right, that can cause poor performance. Best to avoid archetype changes entirely, when possible.
This is something I struggled to know how best to approach. But after a few weeks of testing different approaches, I decided to commit to the following rules:
- Entities never change archetypes. Once I create an entity, it will never change its archetype until it’s destroyed. That means no adding or removing components.
P.S. This also means to always create entities from prefabs, instantiation, or by using EntityArchetype variables. Creating an entity from scratch and then adding components to it one by one causes a lot of archetype thrash.
-
Use separate, tiny entities for events or messages. These generally have one component, and are destroyed within a single frame (usually later in that same update loop).
-
Avoid #2 as much as possible. Creating an event entity still requires you to use an EntityCommandBuffer, which requires a sync point. In many cases when you might want to create a message or entity component, there end up being other ways to do what you’d like that avoid creating new entities.
A tip that’s helped me reach #1 on that list:
I tend to define behaviors (like “GetReadyToShoot”) as IBufferElementData-s, and store them in DynamicBuffers on my entities. That way, I can write an EntityQuery with a change filter for that Buffer type, and early out if the DB is empty. If it’s not, then I process all the behaviors inside.
Anyhow, at the end of the day, avoiding constant thrash from archetype changes and chunk allocation is more performant. and there are usually ways to achieve it.
Great advice, I have a couple of things to add.
#1 I try to strive for this, but do allow archetypes to change if it’s only at max every few seconds.
#2/3 I wrote a somewhat popular event system to do this without EntityCommandBuffer and instead using batch operations while managing the life spam automatically.
https://discussions.unity.com/t/724023/30
Taking my old benchmark it’s about 10x faster.
That’s 100k events/frame on a really old cpu.
This solution isn’t for everyone, but does go to show the benefits of avoiding ECB and moving to batch operations instead if possible.
Your idea of representing messages as IBufferElementData is interesting. I’ll definitely use messages this way. Thanks!
I remember this from January! I’d consider using your Event system. Fortunately I’ve only been coding constantly active systems so far and can now evaluate different ways of triggering temporary systems
Interesting, How would you handle CreateAdditionalEntity() inside a GameObjectConversionSystem. Im not sure of a way to get around creating an entity from scratch and then adding components to it one by one. Or is ok to take the one off hit ?
Good catch. I have made exceptions before for one-off entities created during game start up. Certainly for the Game Object Conversion System, but also for some cases where I’ve created highly custom, persistent entities in a System’s Create() method.
but in truth, that’s me being lazy. My first case could usually be avoided using SubScenes to avoid runtime GameObject conversion. And to avoid my second case, one could use EntityManager.CreateArchetype(), and then use the resulting archetype to spawn the custom entity.
Got it thanks for the reply
For the OP, you can also use Tags to minimize the cost of chunk data movement:
public struct IsOnCooldown : IComponentData { }
public struct CooldownInfo : IComponentData {
public float timeRemaining;
}
So instead of adding/removing CooldownInfo
, you use an extra tag IsOnCooldown
, and only add/remove/query that.
@PublicEnumE @tertle I’ve settled on having a IComponentData containing only a bool isActive, and having relevant entities fetched through an EntityQuery with a SetFilterChanged on that component, processing only when isActive is true. However, this still fetches entities with isActive set to false. This is easily avoidable through an early exit, but would a cleaner/more flexible way be to have each “message” component be an ISharedComponentData with a bool isActive? In this case, only entities with isActive set to true would be processed.
Hear you loud and clear. It’s difficult to speak to your specific case without being more familiar, but:
One thing about ECS: Because of the way CPUs work, what ends up being most performant in the end isn’t always immediately intuitive. We’re traditionally trained to look for a mathematically most performant solution which seems to eliminate all possible waste (like ”only iterate over the specific components which have changed.”).
But in ECS this isn’t always the most performant thing to do. Since all component data is stored in chunks, and since an entire chunk is loaded into the cache to edit any of its component data, and since a CPU reading a bool from its own cache is blazingly fast: iterating over several false bools in a chunk to find the one bool that’s true might end up being the fastest of all possible options we have available (and damned fast, at that).
Since the ins and out of ECS performance are still being worked out, a lot of this community has taken this approach: When starting out, try a few different approaches, and crank them up to a large scale to do performance tests. Don’t assume that just because a solution contains some waste that it’s not going to end up being the fastest available solution. Test and see. And learn what’s out there about the inner workings of ECS. The new rules about what’s likely to be most performant will become easier to predict.
Thanks! I forgot that premature optimization/assumptions are the root of all evil. I’ll try running some tests and maybe share what I find
This is my struggle as well. Adding components so that they can be filtered in some systems is the most idiomatic way in ECS, but because of the way the data is handled, this may not be the best course.
What is Unity devs advice on this?
I’ve settled on having a single component with a single bool named isDisabled and just work with that, a bit like @Abbrew s solution.
I tried different approaches myself as I like tinkering with stuff, and that is by far the best solution in terms of both performance and ease of coding together. However, it does go against ecs style so it would indeed be nice to have a unity dev let us know if this should be the ‘correct’ way of doing things or not.