1

This is my first project using ECS design and I come from a RDMS background for reference.

I'm building a ecology simulation that spans multiple planets, with the following ECS structure:

Entities & Components

  • Planet (Components: Year)
  • Biome (Components: Planet (Link to planet this biome is in))
  • Species (Components: Population, Growth Rate, Biome (Link to biome they reside in))

Systems

  • TimeSystem (Adds +1 to the year component every N ticks)
  • PopulationGrowthSystem (Updates the Population component for each species if a year has passed on the planet)

Question:

I'm struggling with how the PopulationGrowthSystem should filter which Species Populations to update on each iteration.

Iterating through each Population component requires me to do a Species Entity->Biome Component->Planet Component lookup to check the year, which is terribly inefficient.

I've considered de-normalizing by putting something like year_last_updated in the Population Component, but it doesn't feel clean.

I've also considered just updating Population components every N-ticks, but Year needs to always be updated before the population is updated, and to my reading on proper ECS design, I shouldn't depend on the Systems being run in order.

I feel like I'm missing something obvious in how I'm approaching this.

Tyler
  • 113
  • 3
  • Some ECS architectures I have seen allow component which are shared between multiple entities. That means multiple Species entities can share the same Biome component and Planet component which are also shared by the biome entities and planet entities respectively. – Philipp Dec 22 '21 at 14:49
  • @Philipp Thanks for the suggestion. Guess it's not a huge issue to attach year directly to species to reduce cross-referencing – Tyler Dec 23 '21 at 15:21

1 Answers1

1

If I were to do this I would consider two approaches. It sounds like you considered the second option already but maybe the first one suits your design better.

  1. Time Manager takes responsibility

I would add EndYear() method to the PopulationGrowthSystem and update like this.

TimeManager::update()
{
  for (component : Components) 
    component.iterate();
  if (endYear) 
    for (populationComponent: PopulationComponents) 
      populationComponent.endYear();
}
  1. Components take responsibility

In this case the component would take a parameter with the world state. I would probably use a struct with all the data in the game and pass a parameter by reference.

TimeManager::update()
{
  g_worldData.endYear = endYear;
  for (component : Components) 
    component.iterate(g_worldData);
}

PopulationComponent::iterate(WorldData& data) { if (data.endYear) ... }

Jay
  • 810
  • 5
  • 14
  • Thank you. Solution #1 is an approach that I had ruled out for perhaps flawed thinking around trying to minimize the scope of each system. I guess it's reasonable to update multiple sets of components in a single system if they're interdependent? – Tyler Dec 23 '21 at 13:53
  • @Tyler yeah you can think of it being updated for different events. Eg, update for frame_tick, update for other_game_event, etc... – Jay Dec 24 '21 at 00:15