ecsx-framework/ECSx

Component fields order matters.

Closed this issue · 4 comments

Hi Andrew! @APB9785

Having a blast trying this out.

I'm trying to make a little factory simulator, and I noticed that the order of atoms in the fields param for Component.add/3 seems to matter.

For example, I have a Producing aspect.

defmodule Factory.Aspects.Producing do
  @moduledoc """
  Documentation for SampleAspect components.
  """
  use ECSx.Aspect,
    schema: {:entity_id, :type, :per_minute, :to}
end

Then when I add a component.

    ECSx.Component.add(Producing, [id: id, to: to, type: :iron, per_minute: 30], [
      :type,
      :id,
      :per_minute,
      :to
    ])

The order of fields changes which values are stored in which field. For example I'm logging all Producing components:

    ECSx.Component.get_all(Factory.Aspects.Producing, [:id, :type, :per_minute, :to])
    |> IO.inspect()

Which prints the following. Notice my fields and values are incorrect.

[%{id: :iron, per_minute: 30, to: 4, type: 1}]

Perhaps I'm doing something wrong, or maybe this is a bug? Either way, I wanted to get your input on the fields params. I wonder if it would be cleaner to exclude fields from the function signature entirely?

i.e.

ECSx.Component.add(:aspect, [key: "value"])

Or am I misunderstanding, and there's a reason to include the fields param?

Thank you again for making this and letting me try it out!

Ah yes, I thought this might come up. The Components module isn't supposed to be used as the primary API for adding components. I'm still on the fence about having those functions be public at all, but I thought it could be helpful for some advanced case. The way you're supposed to add a Producing component is to call Producing.add_component(entity_id: 123, other_field: :iron, per_minute: 30 ...), which in the end does delegate to Component.add but it will automatically handle the fields in order, without having to specify them.

The presence of the fields is necessary because the output of that function is a map, and we need to know what keys to use for the map - the ordering is necessary because we are storing the components in ETS according to a certain ordering, and we need to know which element of the tuple corresponds to which key in the resulting map. It's probably possible to find a fully compile-time solution for this, right now the fields are being saved in a module attribute at compile time, which is then automatically accessed whenever one of the Aspect functions is called.

Another note is that I recommend having the first field of each component be entity_id so you can easily lookup whether an entity has that aspect or not.

Thanks for testing this out, @BrooklinJazz - I already see some important documentation that needs to be added.

Documentation updated