美文网首页
ECS Entitas分析(二)__Context

ECS Entitas分析(二)__Context

作者: zzqlb | 来源:发表于2020-07-16 00:29 被阅读0次

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

    相关文章

      网友评论

          本文标题:ECS Entitas分析(二)__Context

          本文链接:https://www.haomeiwen.com/subject/qjjncktx.html