ECS Entitas分析(二)__Context

2020-07-16  本文已影响0人  zzqlb

Context介绍

Context是Entitas中的上下文环境,主要用于管理当前环境下的所有Entity以及Group的创建与回收。可以同时存在多个Context,Context之间互不影响。所有的Context由Contexts单例管理。

一、Contexts

Contexts继承自IContexts,主要用于创建和保存当前所有的Context。Contexts类由Entitias的代码生成器生成,无须我们手动实现。在Entitas系统外部通过Contexts.sharedInstance单例对象访问.

public partial class Contexts : Entitas.IContexts {
    public static Contexts sharedInstance {
        get {
            if (_sharedInstance == null) {
                _sharedInstance = new Contexts();
            }
             return _sharedInstance;
        }
        set { _sharedInstance = value; }
    }
    
    static Contexts _sharedInstance;
    
    public GameContext game { get; set; }
    
    public InputContext input { get; set; }
    
    public Entitas.IContext[] allContexts { get { return new Entitas.IContext [] { game, input }; } }

    public Contexts() {
        game = new GameContext();
        input = new InputContext();
    }
}

二、ContextInfo

ContextInfo是Context的信息类,是Context初始化参数之一.主要包含了Context的名字,Context所有组件名字数组以及所有组件类型数组,而这些信息最终也是用来初始化Entity的.

public ContextInfo(string name, string[] componentNames, Type[] componentTypes) {
    this.name = name;
    this.componentNames = componentNames;
    this.componentTypes = componentTypes;
}

三、Context

Context继承自IContext,用来管理当前上下文中的Entity以及Group的.我们所有用到的Enity与Group都应该通过Context的CreateEntity()GetGroup(IMatcher<TEntity> matcher)方法创建与获取.因为在Context中会对所有的Entity与Group进行缓存,回收利用.

3.1、Context创建

Context无需我们手动创建,在Entitas的generate时,代码生成器会为我们自动生成对应的Context.下面的代码为自动生成的代码.

public sealed partial class GameContext : Entitas.Context<GameEntity> {
    public GameContext()
        : base(
            GameComponentsLookup.TotalComponents,
            0,
            new Entitas.ContextInfo(
                "Game",
                GameComponentsLookup.componentNames,
                GameComponentsLookup.componentTypes
            ),
            (entity) =>
    #if (ENTITAS_FAST_AND_UNSAFE)
        new Entitas.UnsafeAERC(),
    #else
        new Entitas.SafeAERC(entity),
    #endif
        () => new GameEntity()) {  
   }
}

我们再来看看Context构造函数的实现:

public Context(int totalComponents, int startCreationIndex, ContextInfo contextInfo, Func<IEntity, IAERC> aercFactory, Func<TEntity> entityFactory) {
    //当前环境中组件的类型的数量,主要用来初始化组件列表的初始化长度
    _totalComponents = totalComponents;
    //当前环境中Entity的起始ID
    _creationIndex = startCreationIndex;

    if (contextInfo != null) {
        _contextInfo = contextInfo;
        if (contextInfo.componentNames.Length != totalComponents) {
            throw new ContextInfoException(this, contextInfo);
        }
    } else {
        _contextInfo = createDefaultContextInfo();
    }

    //创建Entity的引用计数的工厂方法
     _aercFactory= aercFactory ?? (entity => new SafeAERC(entity));
     //创建Entity的工厂方法
     _entityFactory = entityFactory;
     
     //初始化各种容器
     _groupsForIndex = new List<IGroup<TEntity>>[totalComponents];
     _componentPools = new Stack<IComponent>[totalComponents];
     _entityIndices = new Dictionary<string, IEntityIndex>();
     _groupChangedListPool = new ObjectPool<List<GroupChanged<TEntity>>>(
         () => new List<GroupChanged<TEntity>>(),
         list => list.Clear()
     );
     
     //缓存delegates避免gc分配
     //主要时在组件或者实体发生改变时,在代理方法中去改变对应Group中所包含的内容
     //这也是Entitas处理大量同类型数据比较快时原因之一,避免的大量的循环遍历
     _cachedEntityChanged = updateGroupsComponentAddedOrRemoved;
     _cachedComponentReplaced = updateGroupsComponentReplaced;
     _cachedEntityReleased = onEntityReleased;
     _cachedDestroyEntity = onDestroyEntity;
3.2、Entity创建

每个Context中都有一个保存当前环境Entity的对象池:_reusableEntities,以及当前所有活跃的Entity的集合_entities。记住,所有的Entity创建都需要通过Context的CreateEntity()方法。这样通过对象池可以减少大量的创建Entity的时间开销,同时也避免频繁的创建销毁Entity所带来的内存碎片,提高内存使用率。

public TEntity CreateEntity() {
    TEntity entity;
    //如果对象池中有对象,则取出对象,并重新激活
    //如果没有,则使用工厂方法创建一个新的Entity,并初始化
    if (_reusableEntities.Count > 0) {
        entity = _reusableEntities.Pop();
        entity.Reactivate(_creationIndex++);
    } else {
        entity = _entityFactory();
        entity.Initialize(_creationIndex++, _totalComponents, _componentPools, _contextInfo, _aercFactory(entity));
    }
   
   //加入活跃列表中,并增加引用计数
    _entities.Add(entity);
    entity.Retain(this);
    _entitiesCache = null;

    //给Entity的变化添加代理方法,用于更新Group
    entity.OnComponentAdded += _cachedEntityChanged;
    entity.OnComponentRemoved += _cachedEntityChanged;
    entity.OnComponentReplaced += _cachedComponentReplaced;
    entity.OnEntityReleased += _cachedEntityReleased;
    entity.OnDestroyEntity += _cachedDestroyEntity;

    if (OnEntityCreated != null) {
        OnEntityCreated(this, entity);
    }
    return entity;
}
3.3、Group的创建

Context中保存这当前环境中的所有Group,同时通过代理,在Entity发生变化时,更新对应的Group。保存所有Matcher与对应Group的字典Dictionary<IMatcher<TEntity>, IGroup<TEntity>> _groups,主要用于获取Group,主要用于Component变化时更新Group。

public IGroup<TEntity> GetGroup(IMatcher<TEntity> matcher) {
    IGroup<TEntity> group;
    //查看_groups中是否已存在该matcher的Group,有则返回,没有就创建新的Group
    if (!_groups.TryGetValue(matcher, out group)) {
        group = new Group<TEntity>(matcher);
        var entities = GetEntities();
        //遍历所有的Entity,将匹配的加入到Group中
        for (int i = 0; i < entities.Length; i++) {
            group.HandleEntitySilently(entities[i]);
        }
        
        _groups.Add(matcher, group);
        
        //遍历这个matcher中的所有Component序号,将Group加入到对应的_groupsForIndex中
        for (int i = 0; i < matcher.indices.Length; i++) {
            var index = matcher.indices[i];
            if (_groupsForIndex[index] == null) {
                _groupsForIndex[index] = new List<IGroup<TEntity>>();
            }
             _groupsForIndex[index].Add(group);
        }
        
        if (OnGroupCreated != null) {
            OnGroupCreated(this, group);
        }
    }
    
     return group;
}
3.4、其他

还有一些其他的方法,例如Entity上组件添加或修改时的代理方法updateGroupsComponentAddedOrRemoved,组件删除时的代理方法onDestroyEntity等这些大家可以通过查看Context的源码进行了解。

四、总结

Context是一个独立的环境,使用对象池管理当前环境中所有的Entity的创建以及回收。缓存着所有的Group,同时通过设置Entity改变时的一系列的代理方法,更新当前环境中对应的Group。
这样做可以减少Entity创建的开销,减少因为Entity频繁的创建与消耗带来的内存碎片,提高内存使用率。同时减少了我们需要某些数据集合时,通过遍历带来的一次集中性的大的开销,将这些分散到各个Entity变化的时刻。

<上一篇> ECS Entitas分析(一)___概括

上一篇下一篇

猜你喜欢

热点阅读