ECS Entitas分析(二)__Context

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分析(一)___概括

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。