hmans / miniplex

A 👩‍💻 developer-friendly entity management system for 🕹 games and similarly demanding applications, based on 🛠 ECS architecture.
MIT License
849 stars 39 forks source link

[2.0] Update entities, mark as dirty, reindex #220

Open hmans opened 2 years ago

hmans commented 2 years ago

Miniplex 2.0 supports predicate-based queries, which allows the user to also check for component values (instead of just presence/absence). For this to work, there also needs to be a mechanism that allows the user to mark an entity as dirty, so that it will be reindexed (because it's important that Miniplex mutates entities directly, instead of treating them like microstores.)

Suggested API (naming TBD):

/* Given a bucket containing entities with low health: */
const lowHealth = world.where((e) => e.health < 10);

/* Mutate the entity directly, as usual */
entity.health -= 10;

/* Mark the entity as dirty */
world.dirty(entity);

/* Regulary -- eg. once per tick -- reindex the world. At this point the entity might show up in the `lowHealth` bucket. */
world.flushDirty();

Changed events?

There's an opportunity here to implement an onEntityChanged event (or similar) that may help with #154. However, since entities are mutated directly (by design), it'll be difficult to impossible to provide the same sort of information (or even API fidelity) as eg. a reactive store solution (at least not without creating a potentially high number of new objects every frame.) There is a big potential for a significant divide between user expectations and what this library could realistically deliver.

mikecann commented 1 year ago

Interesting idea! I assume you have already considered Proxy's to do this... Probably too much of an overhead (might be worth a perf test or two tho)

Another potential option that I have used in the past is to have a "Mutable" type:

export class Mutable<T> {
  public onChange = new Signal1<T>();
  private _changed = true;
  private _value: T;
  private equalityChecker: EqualityChecker<T>;

  constructor(_value: T, equalityChecker: EqualityChecker<T> = shallowIsEqual) {
    this._value = _value;
    this.equalityChecker = equalityChecker;
  }

  get value() {
    return this._value;
  }

  set value(v: T) {
    if (this.equalityChecker(this._value, v)) return;
    this._value = v;
    this._changed = true;
    this.onChange.dispatch(v);
  }

  get changed() {
    return this._changed;
  }

  resetChanged() {
    this._changed = false;
  }
}

Then usage becomes:

/* Given a bucket containing entities with low health: */
const lowHealth = world.where((e) => e.health.value < 10);

/* Mutate the entity directly, as usual */
entity.health.value -= 10;

Something like that.