When should something be an entity?

As the title suggests. My current understanding is that the only case where something should be an entity is when it needs to be iterated by a system or serve a singleton to be accessed. However, I’m not super sure about the iteration part:

For example, you could keep all your trees as an array is struct of 2 floats that represent growth level and fruit ripeness level and a float3 position. Then, a tree growth system will go through the entire array, if growth is < 1.0 add to it, then do the same with ripeness and spawn a fruit and reset when it’s fully ripe.

However, the same could also be achieved with entities, which do basically what I described above, but under the hood and are easier to parallelize with jobs? On the contrary, such a simple data struct might prove to be horribly utilized by the chunk system, as the chunk is limited in number of entities and the entity itself occupied very little memory.

So how do you reliably determine whether something should be an entity or not?

when things need to interact with and be affected by entities in the simulation is a good case for making them entities.

So if two things need interacting, a rule of thumb is to consider making both of them entities, got it. This would mean that if my example remains as-is, there’s no point of making the trees into entities?

Chunks are limited to 128 entities and 16kb of memory, which shouldn’t impose any performance limitation. Worst case scenario is a “very fat” entity, where only a few entities fit in each chunk.

When you make them into entities you can easily:

  • add, change and remove components as you iterate on the code (fruit data? growth rate? tree types? visuals?)
  • use baking and scenes for authoring
  • split logic into self-contained systems that have structured access to tree data (tree spawning, growth, fruit spawning, saving, etc)

I also tend to assume that storing all of your game state in a single “database” (an entity world) is of value in itself. The code is simpler that way.

For me this seems like an unnecessary decision. Everything is an entity unless there are clear reasons why it shouldn’t be one. Long-lived objects tied to gameplay mechanics and/or visuals seem like a good candidate.

2 Likes

Worst case scenario is a “very fat” entity, where only a few entities fit in each chunk.

My concern is actually the opposite: My tree entity is basically 5 floats (3 for position, 1 for growth and 1 for fruit), which means 20 bytes per entity. 128x20=2,560 bytes maximum per chunk, or 2.56 kilobytes. This means only about 15% of chunk is utilized, due to this weird restriction of only 128 entities per chunk, but unity will still allocate all 16KB, most of which be unused… Correct me if my math is wrong.

I know I’m probably prematurely optimizing but I’d like to know whether there’s a way to modify entity cap per chunk (I should be able to modify the package code directly? Though it will probably break something if it’s hardcoded) or an alternative to better utilize memory.

I do like the ability to easily add/remove components to diversify the gameplay but if I recall correctly the documentation states that this is highly discouraged as it creates archetypes that perform exponentially worse with amount.

That’s a valid concern, but in most cases it seems like a negligible edge case. It doesn’t affect performance unless the user device runs out of memory.

In your example, one million trees takes up ~125MB of RAM. Let’s even round it up to 300MB to account for chunk meta entities and other unplanned inefficiencies. Is that a lot for modern hardware? Will your game contain millions of entities? (I’ve shipped a game with similar runtime entity counts, and fairly bad chunk utilization, and among the many things we had to optimize, I don’t think chunk memory usage came up as an issue.)

Not currently an option AFAIK but maybe you can hack the entities package if you really need to. This has been one of the most requested features since the early days, though, so one day, perhaps.

That’s a shame. Hopefully in the future it’s possible to adjust the cap.
Also, something I wanted to make clear as well: instantiating/removing trees as simple struct is almost free from what I can tell, as it’s simple data. On the other hand, entities documentation seems to be very specific about the process of creating/destroying entities saying that it’s a very expensive process that causes a structural change and should be avoided as much as possible. Would performance take a major hit if I instantiate and destroy a high amount of tree entities, even if I try to centralize them?

1 Like

Math is slightly wrong. You forgot about the 8 bytes to store the entity itself, which is also stored as an array in a chunk.

However, I question your example, because it would imply that your tree entity has no presentation representation. If you rendered it with Entities Graphics, you’ll easily use up the rest of the chunk. And if you use a separate resource for rendering, you at least will want another integer to reference that external resource. In the worst case, you’ll have 25% chunk utilization. But I suspect that you may find some “temporary value” components you can add to your trees to speed up the logic.

1 Like

I’d say you could get away with keeping the representation description out of the entity: a system could simply be rendering the same tree mesh for each tree, scaling it according to growth, rendering instead another variation of it when there’s a fruit on the said tree. That way, it’s not necessary to keep any representation info in the component as it will never change nor differ between entities. Alternatively, just bare minimum, a tree type can be a single byte that is then used to lookup tree representation data (mesh, name, material)

I understand what you mean though. Realistically a case like this is very unlikely to happen. More often than not you’d have additional data that will improve chunk utilization. However, I still believe it would be a good feature to allow adjusting of the entity cap per chunk.

[roots, branches, leaves, fruit / seeds] all “grow” over time, those all can essentially be described as growthProgress:

public struct PlantGrowthComponent : IDataComponent
{
    public UInt16 growthAmount;
    public UInt16 growthProgress;
}

As for “When to use entities” really it all depends on a number of factors that only you and your project specs can answer.

Is it a necessity? Do you plan on having 6+ Million data objects that you will perform logic on every frame? Or thousands of render components (GPU Batching still exists).

Do you want to learn it?

Will you actually benefit from a mobile game where you’re saving those resources?

I personally think it’s good to understand, but both small and large projects suffer from the extended development time when first using them.

This would imply that you are reuploading the array of positions and other attributes to the GPU every frame. That’s stupid. You waste the GPU’s time by making it wait for the memory transfer. And many ECS games get easily GPU bottlenecked. Don’t do that. Use some components to do bookkeeping of GPU resources per instance.

You can set the global chunk size by modifying the Entities package. It is a single constant that all the other size constants are dependent on. But you should only consider doing this very late into your project when you can actually profile the optimal chunk size for the full game.

Really, I think the true solution to this is a hybrid chunk representation where you have the normal 16 kB chunks as “archetype leaders”, and if one of those fill up, all the remaining chunks in the archetype start to fill up mega chunks, where a chunk inside a mega chunk spans for 128 entities across all the component arrays. Every archetype would then have two different memory layouts, one for leader chunks and one for mega chunks. However, this is far from the lowest-hanging fruit performance-wise in Unity.

1 Like

That’s a good point. I was so concerned about CPU and memory utilization I forgot about GPU bottlenecks. Looks like 128 entities might really be very close to an ideally balanced amount, though it’s nice to know it’s possible to adjust the cap.

I’m still yet to fully understand the drawbacks of using entities for things that may be frequently spawned and destroyed, but at the very least it’s clear that they’re a good fit for persistent things that require simulation. Ty for advice.