sschmid / Entitas

Entitas is a super fast Entity Component System (ECS) Framework specifically made for C# and Unity
MIT License
7.16k stars 1.11k forks source link

Simplify single-member component access #926

Open ianeinman opened 4 years ago

ianeinman commented 4 years ago

I have a suggestion to simplify access to components that have only a single member. Let's say I have an entity that looks like this:

[Game]
public struct InterstellarPosition : IComponent {
   public double3 Position;

I need to access this value as follows:

entity.interstellarPosition.Position = newPosition;

But if the component only has one member, it would be nice if I could just do this:

entity.interstellarPosition = newPosition;

My suggestion is to add an optional attribute to the component which enables it to do this. Note that in this case, I think that the component is never actually created, it functions only as a template which describes what type the component is. So you could define a generic class and perhaps all they need to do is something like this:

[Game]
public struct InterstellarPosition : SingleMemberComponent<double3> {}
Arpple commented 4 years ago

there is workaround by using c# implicit keyword https://github.com/sschmid/Entitas-CSharp/pull/88

ianeinman commented 4 years ago

Excellent, thanks for the prompt response.

I do think an attribute would still be good though. The workaround just makes it less code I have to type, which is good. But underneath, there's still a component being created, and that operator is also being called.

The attribute would enable you to optimize the code generation to be more like the Atom proposal, where in this case the component is not actually created at all, but only the raw inner value is created.

ribbanya commented 4 years ago

I wrote this for my game, does something similar.

public abstract class ValueComponent<T> : Entitas.IComponent {
  public T value;

  public override string ToString() {
    return this.value.ToString();
  }

  // Allows "var (x) = (entity.xComponent)",
  // although you can also just do "T x = entity.xComponent"
  public void Deconstruct(out T value) {
    value = this.value;
  }

  public static implicit operator T(ValueComponent<T> component) {
    return component.value;
  }
}

I'll see if I can work up a code generator for something like this, when I have time.

ianeinman commented 4 years ago

Yes, the issue isn't too hard to work around, but I think it would be more performant if Entitas supported it directly. Let me show an example:

[Game]
public class HealthComponent : IComponent
{
   public int Health;
}

// Access as follows:
GameEntity entity = <from wherever>
string myName = entity.healthComponent.Health;

The workarounds mentioned above make it easier to access the value, by not needing to type Health. This is fine, but does not improve the performance. Underneath, as far as I know, there is still an array of "HealthComponent" references.

But if Entitas knows that this component has only a single value, it can instead make it reference an array of that type (in this case, an "int"). Particularly when the single type is a value type it would be fast since it would be a contiguous memory block. This is one of the performance benefits of Unity's ECS system, and I think it was at the core of the "Atom" prototype that @sschmid was talking about a few months ago.

I prefer Entitas over Unity's ECS for several reasons (it is more flexible). I like the fact that a component can contain anything and does not have to be value types. But it would be great if there was a performance benefit when you were using only value types and I think this is one way to do it.

Now, I could be wrong about how things are currently working underneath and maybe this suggestion wouldn't be of benefit, but I still wanted to put it out there because I thought it could be useful.