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源码分析(一)___概括
<下一篇>ECS Entitas源码分析(三)___Component

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
 一: 什么是ECS?       ECS是一种新的架构模式(当然是针对Unity本身来说),这是一个取代GameObject / Component 的模式。 其模式遵循组合优于继承原则,游戏内的每一个基本单元都是一个实体,每个实体又由一个或多个组件构成,每个组件仅仅包含代表其特性的数据(即在组件中没有任何方法)。系统便是来处理拥有一个或多个相同组件的实体集合的工具,其只拥有行为(即在系统中没有任何数据)。       ECS旨在比GameObject / MonoBehaviour更容易处理大量物体。ECS由于是面向数据的设计,所以很容易并行高速处理,以及与Unity开发的C#JobSystem/Burst Compiler一起工作,从而获取更高的项目运行性能。ECS总体发展历史       目前Unity公司由Mike Acton 大佬主持DOD(Data Oriented Design 面向数据设计)的全面改革与推进工作,目的是旨在进一步推进Unity系统本身与Unity项目的执行效率。    ECS总体发展:    A: 在Unity2018引入Entities之前,其实ECS框架早已经出现,但是ECS还是因为守望先锋采用了这个框架才被我们所熟知,后来Git上面出现了一个Entitas的插件可以直接用在Unity上面。Entitas-Unity(有多个语言的port版本,Entitas-Unity下统一称为Entitas) 是 Unity的一个ECS(Entity/Component/System)框架,是在github上面的一个开源项目。    B: 经过Unity公司的认可与改造,目前Unity2018.x 版本已经通过 Package Manager 提供了Unity版本的ECS插件,名称为:“Entities”。    C: Unity2019.x 版本,对“Entities”插件的API进行了进一步改造与完善,以更规范的API实现更加高效的与规范的编程模式。 三:ECS(一)轻松入门篇       本“ECS入门篇”旨在全面讲解ecs 的相关理论与简单Hello World 的编程模式,且通过Unity2018.x与Unity2019.x 两个不同API的编程模式,初步讲解ECS的编程规范与模式规范。        需要进一步学习ECS的小伙伴们,可以关注后续“ECS()小试牛刀”篇的进一步讲解。   《Unity ECS() 小试牛刀》https://edu.csdn.net/course/detail/27096 

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值