How to Organize and Clarify Components and Services?

How are components organized so that developers know the intent of each component and system?

(Yes I’m using @moduledoc)

For example, I have the following components and services that all deal with the health of a character.

Components.TakeDamage,
Components.InstantHeal,
Components.IsDamaged,
Components.HealthPointRecovery,
Components.HealthPointRecoveryMilliseconds,
Components.MaxHealthPoints,
Components.HealthPoints
Systems.InstantHeal,
Systems.TakeDamage,
Systems.IsDamagedRemover,
Systems.HealthPointRecoveryScheduler,
Systems.HealthPointRecovery

I’m compelled to put them all in a namespace for organization and clarity purposes but if feels like that could an ECS anti-pattern. How should I organize components and systems?

2 Likes

Hi,

Here are some naming recommendations for Elixir: Naming conventions — Elixir v1.16.1
These recommendations are quite general.

I would recommend trying to use your current approach in naming, and see how it works. It seems, that ECSx examples use similar approach.

Any reason not to organize components and systems into namespaces (e.g. subfolders)?

Regarding naming.

Just want to make sure, that you have some application prefix before each of the modules.
I mean something like MyGame.Components.TakeDamage instead of Components.TakeDamage. This prefix allows to avoid conflicts with other packages in future.
Elixir (and Erlang) has flat structure for module names. I mean if you have Components as a prefix, it may clash with some library.

Regarding the organization of components and systems into namespaces. Why not? You absolutely can do this.

I have seen different ways to structure the code:

  1. Move systems to Systems namespace. Move components to Components namespace.
    You will easily identify where to search for each type. They will be perfectly separated.
    But you will need to have 2 open folders at a time. For example, you work on the system and want to use a component, so you will need to open another folder to look at it.

  2. You can collect related modules into the namespaces. I mean, everything related to the player (components, systems), will be closer. For example, if you work on the player, you will need to open only one folder and jump across the related files. The difficult part here is where to put cross-boundary components or systems. Here are some tips about boundaries for Elixir projects. Possibly this will give you some insights.

  3. Flat structure. Just put everything in one folder. This way you will not need to think where to put the module and focus on the code itself. As a project grows it can become difficult to work with and will look like opening a node_modules folder :slight_smile:

I do not know which approach fits your project better. As for me, I try to gather related modules closer (2), or split them by their type (1). Actually you can refactor your code later and adjust it to your needs. Trying to do everything right from the beginning may consume too much time. It may lead to analysis paralysis and writing no code at all. In my opinion it is better to focus on code itself and reflect about the structure or namespaces later. I have recently changed the namespace structure in the project, and it took only a couple of hours.

I remember how I thought about the architecture, naming and other things for my pet projects. It was interesting, but it gave no value. When I realize I spend too much time on such things, I start doing valuable things. Usually naming inspirations come after some time, when I already have the code. They are related to the patterns of my project. And they are so much better than imaginary ones.

This should be OK, as long as the module name is the same in both the Component module file, and the Manager components/0 callback. The generators will create MyApp.Components.MyCompName by default, but you are welcome to change it afterwards. Perhaps in the future we could add a flag to the generator to include a sub-namespace from the start.

Note: I mentioned Components above but the same should apply for Systems.

2 Likes

I moved a number of my components and systems into folders and equivalent namespaces. Yes. The code functions just fine.

The dashboard only shows the last bit of the module name so I’ll probably switch to a naming convention instead. (not asking for a change - just sharing my experience)

You can see in the screen shot that there’d be no way to know that the components belong together as highlighted. Further Status is used more than once so I don’t know which is which.

1 Like

Thanks for the report, we will look into this :slightly_smiling_face:

For example, I have the following components and services that all deal with the health of a character.

If I am understanding the core question correctly, are you asking if you should put them into a namespace such as Components.Character.XYZ because they all pertain to a Character? If so, then I would say that the answer is most likely not. The reason is that multiple different entities can have the same components, and therefore be acted upon by the same systems. A character and a wall, for example, could both have all of those components, and be acted upon by all of those systems. Having one system to handle damage against a wall and one to handle damage against a character means you are missing out on a core decentralization concept of ECS. The Systems should read the context for their actions out of components, care nothing for which entity type it is, and not have things hard coded.

For example, the HealthPointRecovery system should query the IsDamaged component for every entity id that is damaged, and then read from the HealthRecoveryPerTick component for each entity, and add that value to the HealthPoints component for that entity, and have absolutely no knowledge or need to care that one entity is a wall and one is a character.

1 Like

I may still have a ways to go in learning the appropriate architecture for ECS. That said…

I agree that Health Point management is a separable concept because characters, monsters, and breakable items could all use the same components and tags. Within Health Points management I have

  • Points component
  • MaxPoints component
  • IsDead tag
  • IsInjured tag
  • IsHealthy tag
    and I’d like to group these by name (or namespace…).

The idea being I could differentiate Health Points from Experience Points from Reputation Points. Does that make sense?

One way to avoid this might be simplification via deduplication of information. If you took those 5 items, and instead just had two components: HealthPoints and MaxHealthPoints, then you wouldn’t need the other three, because you could determine IsDead with simply HealthPoints == 0, and you could determine IsInjured and IsHealthy by comparing HealthPoints and MaxHealthPoints. As a half measure, you could have a third field called HealthStatus that was either dead, injured, or healthy. With only two or three components, the need for a namespace goes away.

1 Like

Agreed…and components have a couple of nice function such as search() and at_least() . I’m working through pulling out what’s easily computable and where to put those functions.