ECS Collector简介

460 阅读4分钟

Collector创建

Collector可以用来监听group中的entity。我们可以通过如下方式创建:

var group = gameContext.GetGroup(GameMatcher.Position);
var colloect = group.CreateCollector(GroupEvent.Removed);

这段代码表示当group中的entity移除Position组件时将会被添加到collect中。 不同行为通过GroupEvent定义,默认为GroupEvent.Add。

  • GroupEvent.Add:表示entity添加Position组件,进入group的同时,也会添加到collect中。
  • GroupEvent.Removed:表示entity移除Position组件时,会进入collect中。
  • GroupEvent.AddOrRemoved:表示移除或添加Position组件都会进入collect。

Collector原理

以下面这段代码为例,解释Collecter的原理。

var group = gameContext.GetGroup(GameMatcher.Position);
var colloect = group.CreateCollector(GroupEvent.Removed);

首先跟踪一下CreateCollector()方法,如下:

public static ICollector<TEntity> CreateCollector<TEntity>(this IGroup<TEntity> group, GroupEvent groupEvent = GroupEvent.Added) where TEntity : class, IEntity
{
    return (ICollector<TEntity>) new Collector<TEntity>(group, groupEvent);
}

该方法为拓展方法,只是new了一个Collect的实例。在查看一下Collector的构造函数:

//Collector.cs
public Collector(IGroup<TEntity>[] groups, GroupEvent[] groupEvents)
{
  this._groups = groups;
  this._collectedEntities = new HashSet<TEntity>(EntityEqualityComparer<TEntity>.comparer);
  this._groupEvents = groupEvents;
  if (groups.Length != groupEvents.Length)
    throw new CollectorException("Unbalanced count with groups (" + (object) groups.Length + ") and group events (" + (object) groupEvents.Length + ").", "Group and group events count must be equal.");
  this._addEntityCache = new GroupChanged<TEntity>(this.addEntity);
  this.Activate();
}

这段代码最关键的为这两句代码,_addEntityCache为一个事件,对应的方法签名为addEntity。所以接下来分别查看一下addEntity方法,和Activate方法。

this._addEntityCache = new GroupChanged<TEntity>(this.addEntity);
this.Activate();

首先是addEntity方法

//Collector.cs
private void addEntity(IGroup<TEntity> group, TEntity entity, int index, IComponent component)
{
  if (!this._collectedEntities.Add(entity))
    return;
  entity.Retain((object) this);
}

_collectedEntities为HashSet,保证了不会包含重复的entity,所以,执行_addEntityCache事件时,就会将entity添加到名为_collectedEntities的HashSet集合中。接下来为Activate方法:

//Collector.cs
public void Activate()
{
  for (int index = 0; index < this._groups.Length; ++index)
  {
    IGroup<TEntity> group = this._groups[index];
    switch (this._groupEvents[index])
    {
      case GroupEvent.Added:
        group.OnEntityAdded -= this._addEntityCache;
        group.OnEntityAdded += this._addEntityCache;
        break;
      case GroupEvent.Removed:
        group.OnEntityRemoved -= this._addEntityCache;
        group.OnEntityRemoved += this._addEntityCache;
        break;
      case GroupEvent.AddedOrRemoved:
        group.OnEntityAdded -= this._addEntityCache;
        group.OnEntityAdded += this._addEntityCache;
        group.OnEntityRemoved -= this._addEntityCache;
        group.OnEntityRemoved += this._addEntityCache;
        break;
    }
  }
}

从上可以看出,OnEntityRemoved也是一个事件,并且触发时,与_addEntityCache的效果一样,接下来从Group的实现代码中看一下OnEntityRemoved的触发情况。

//Group.cs
public void UpdateEntity(TEntity entity, int index, IComponent previousComponent,
 IComponent newComponent)
{
  if (!this._entities.Contains(entity))
    return;
  // ISSUE: reference to a compiler-generated field
  if (this.OnEntityRemoved != null)
  {
    // ISSUE: reference to a compiler-generated field
    this.OnEntityRemoved((IGroup<TEntity>) this, entity, index, previousComponent);
  }
  // ISSUE: reference to a compiler-generated field
  if (this.OnEntityAdded != null)
  {
    // ISSUE: reference to a compiler-generated field
    this.OnEntityAdded((IGroup<TEntity>) this, entity, index, newComponent);
  }
  // ISSUE: reference to a compiler-generated field
  if (this.OnEntityUpdated == null)
    return;
  // ISSUE: reference to a compiler-generated field
  this.OnEntityUpdated((IGroup<TEntity>) this, entity, index, previousComponent, newComponent);
}

再来查看那个位置调用了UpdateEntity,代码如下:

//Context.cs
private void updateGroupsComponentReplaced(IEntity entity, int index, IComponent previousComponent, IComponent newComponent)
{
  List<IGroup<TEntity>> groupList = this._groupsForIndex[index];
  if (groupList == null)
    return;
  TEntity entity1 = (TEntity) entity;
  for (int index1 = 0; index1 < groupList.Count; ++index1)
    groupList[index1].UpdateEntity(entity1, index, previousComponent, newComponent); 
}

接下来在查找updateGroupsComponentReplaced方法使用位置,代码如下:

//Context.cs
public Context(int totalComponents, int startCreationIndex, ContextInfo contextInfo, Func<IEntity, IAERC> aercFactory, Func<TEntity> entityFactory)
{
 //····省略前面代码
  this._cachedComponentReplaced = new EntityComponentReplaced(this.updateGroupsComponentReplaced);
  this._cachedEntityReleased = new EntityEvent(this.onEntityReleased);
  this._cachedDestroyEntity = new EntityEvent(this.onDestroyEntity);
}

接下来可以追踪_cachedComponentReplaced事件使用位置,代码如下:

//Context.cs
public TEntity CreateEntity()
{
  // ·····省略前面的代码
  entity.OnComponentReplaced += this._cachedComponentReplaced;
  entity.OnEntityReleased += this._cachedEntityReleased;
  entity.OnDestroyEntity += this._cachedDestroyEntity;
  // ISSUE: reference to a compiler-generated field
  if (this.OnEntityCreated != null)
  {
    // ISSUE: reference to a compiler-generated field
    this.OnEntityCreated((IContext) this, (IEntity) entity);
  }
  return entity;
}

可以发现在创建entity时,赋值给了entity的OnComponentReplaced事件。接下俩查看OnComponentReplaced事件的引用位置。代码如下:

//Enity.cs
private void replaceComponent(int index, IComponent replacement)
{
  IComponent component = this._components[index];
  if (replacement != component)
  {
    this._components[index] = replacement;
    this._componentsCache = (IComponent[]) null;
    if (replacement != null)
    {
      // ISSUE: reference to a compiler-generated field
      if (this.OnComponentReplaced != null)
      {
        // ISSUE: reference to a compiler-generated field
        this.OnComponentReplaced((IEntity) this, index, component, replacement);
      }
    }
    else
    {
      this._componentIndicesCache = (int[]) null;
      this._toStringCache = (string) null;
      // ISSUE: reference to a compiler-generated field
      if (this.OnComponentRemoved != null)
      {
        // ISSUE: reference to a compiler-generated field
        this.OnComponentRemoved((IEntity) this, index, component);
      }
    }
    this.GetComponentPool(index).Push(component);
  }
  else
  {
    // ISSUE: reference to a compiler-generated field
    if (this.OnComponentReplaced == null)
      return;
    // ISSUE: reference to a compiler-generated field
    this.OnComponentReplaced((IEntity) this, index, component, replacement);
  }
}

这段代码的意思是,先查看该entity是否存在有该component,若存在,则触发OnComponentReplaced事件,否则将触发OnComponentRemoved事件。 接下来查找一下replaceComponent的使用位置。

//Enity.cs
ublic void ReplaceComponent(int index, IComponent component)
{
  if (!this._isEnabled)
    throw new EntityIsNotEnabledException("Cannot replace component '" + this._contextInfo.componentNames[index] + "' on " + (object) this + "!");
  if (this.HasComponent(index))
  {
    this.replaceComponent(index, component);
  }
  else
  {
    if (component == null)
      return;
    this.AddComponent(index, component);
  }
}

ReplaceComponent方法是不是很熟悉,随便打开一个带参数的component的生成代码,都可以发现此方法。例如:

public partial class GameEntity {
    //····省略前面代码
    public void ReplaceAssetName(string newAsset_name) {
        var index = GameComponentsLookup.AssetName;
        var component = (AssetNameComponent)CreateComponent(index, typeof(AssetNameComponent));
        component.asset_name = newAsset_name;
        ReplaceComponent(index, component);
    }
    //····省略后面代码
}

所以当调用entity.ReplaceAssetName("22")时,就会触发系列的反应,该entity也会被收集起来。 接着调用colloect.collectedEntities便可以获得收集到的entities.

总结

从以上流程也可以发现,Collector只可以添加,也就意味着,假如我们收集的是添加Position Component,但是Position Component移除后,entity并不会从Collector中除去,所以要小心使用,但是ReactiveSystem很好的解决了这个问题,请看下一篇解析。