denniskaselow / dartemis

A Dart port of the Artemis Entity System Framework
BSD 2-Clause "Simplified" License
49 stars 6 forks source link

Improvement: Make manual creation of Mappers obsolete #64

Closed Marty closed 7 months ago

Marty commented 1 year ago

I made the manual creation of Mappers in my project obsolete.
It works nicely for me that way but it would be better suited in the library itself.

The MapperSystem holds a Map with the mappers by type.
This does not need to be a system and could be integrated in World.

class MapperSystem extends VoidEntitySystem  {
  final Map<Type, Mapper> _mappers = {};

  Mapper<T> mapper<T extends Component>() {
    return _mappers.putIfAbsent(T, () => Mapper<T>(world)) as Mapper<T>;
  }

  @override
  void initialize() {
  }

  @override
  void processSystem() {
  }
}

To access this conveniently, I have two extensions:

extension MapperSystemExtension on EntitySystem {
  T? getComponent<T extends Component>(int entity) {
    return world.mapper<T>().getSafe(entity);
  }

  bool hasComponent<T extends Component>(int entity) {
    return world.mapper<T>().has(entity);
  }
}

extension MapperWorldExtension on World {
  Mapper<T> mapper<T extends Component>() {
    return getSystem<MapperSystem>().mapper<T>();
  }

  T? getComponent<T extends Component>(int entity) {
    return mapper<T>().getSafe(entity);
  }
}

It can then be used like this:

var position = getComponent<Position>(entity)!;

A nice sideeffect that I did not think of prior is, that you can easily navigate to the Component in question (in this case Position) without jumping to the definition of the mapper first. I find myself using that quite often.

Do you see any downsides to this?
I'd be happy to create a PR :)

denniskaselow commented 1 year ago

Hot damn. That looks great.

If you can get the tests to work despite #60 then go ahead with your PR.

Though I would prefer it if the getComponent method returns a T instead of a T? and used the [] operator instead of getSafe. And return T? with a getOptionalComponent that uses OptionalMapper. Or is there a reason you are doing it your way?

The only downsides I can think of at the moment (but they are negligible because you can still use the manual/generated way depending on your personal preferences):

I am so used to dartemis_builder I never create my mappers manually anyway. I don't know if you have looked at it, but with it a MovementSystem would look like this, and there would be no need to create or access mappers:

@Generate(
  EntityProcessingSystem,
  allOf: [
    Position,
    Velocity,
  ],
)
class SimpleMovementSystem extends _$SimpleMovementSystem {
  @override
  void processEntity(int entity, Position position, Velocity velocity) {    
    position
      ..x += velocity.x * world.delta
      ..y += velocity.y * world.delta;
  }
}

EDIT: Oh, I see I need to update the dartemis readme with this example.