EntitasLite源码分析(三)

IEntity

Entity作为ECS的基础个体,是整个框架中最大的类。同样,通过对应的 IEntity 接口类定义了Entity的基本能力。IEntity的结构如下:
在这里插入图片描述
虽然内容多,但由于在ECS中Entity并不承载逻辑,只是Component的容器,所以可以发现,IEntity的结构还是比较简单的,基本只包含了以下四块:

  1. 引用计数器IAERC,负责记录当前Entity被外部哪些对象引用
  2. 一些生命周期函数,如初始化、销毁等
  3. 一系列对自身Component的增删改查方法
  4. 通过几个event,在特殊事件节点回调至外部结构(主要是Context),触发特定逻辑,如前文提到Component添加移除时触发Context中对Group的更新。

涉及到的委托形式为:
在这里插入图片描述

Entity

Entity是IEntity接口的实现类,内容如下:
在这里插入图片描述
在这里插入图片描述

创建和销毁

从Context里我们知道,Entity生命周期结束后不会真的销毁,而是被Context回收等待复用。那就需要区分开哪些内容是Entity结构统一的,哪些内容是单个Entity实例特异的。结构统一的内容只需要在第一次创建Entity时进行一次初始化,而单个实例特异的则需要每次复用的时候重新赋值。

结构统一的内容包括:

  • _contextInfo - 所属Context的基础信息,由Context传入一次自身ContextInfo的引用即可
  • _totalComponents - 最多可能会存在多少种Component,其实就是所属Context一共有多少种Component,由Context传入一次即可
  • _componentPools - 每种Component的对象池,由Context传入一次自身的组件对象池数组即可
  • _aerc - 引用计数器,第一次创建时由Context分配一次即可
  • _components - 记录当前Entity实例实际添加了哪些Component,这个数组的长度固定为_totalComponents,然后每添加一个Component,就用Component的Index作为下标,记录到该数组种,因此,只需要分配一次内存空间,只要在Entity回收前清空数组即可

实例特异的内容包括:

  • _creationIndex - 实例的唯一ID,需要每次复用时都重新分配
  • _isEnabled - 实例当前是否处于激活状态,复用时需要重新激活

Entity种提供了Initialize和Reactive两个方法,分别在第一次创建时和被复用时调用,Reactive方法就是对实例特异的内容进行重新赋值,而Initialize方法负责对结构统一的内容进行第一次赋值,同时又在内部调用了Reactive。

		/// <summary>
        /// 第一次创建时调用
        /// </summary>
        /// <param name="creationIndex"></param>
        /// <param name="totalComponents"></param>
        /// <param name="componentPools"></param>
        /// <param name="contextInfo"></param>
        /// <param name="aerc"></param>
        public void Initialize(int creationIndex, int totalComponents, Stack<IComponent>[] componentPools, ContextInfo contextInfo = null, IAERC aerc = null) {
            
            Reactivate(creationIndex);

            _totalComponents = totalComponents;
            _components = new IComponent[totalComponents];
            _componentPools = componentPools;

            _contextInfo = contextInfo ?? createDefaultContextInfo();
            _aerc = aerc ?? new SafeAERC(this);
        }
		/// <summary>
        /// 激活Entity
        /// 第一次创建时由Initialize方法调用
        /// 后续复用时由Context直接调用
        /// </summary>
        /// <param name="creationIndex"></param>
        public void Reactivate(int creationIndex) 
        {
            _creationIndex = creationIndex;
            _isEnabled = true;
        }

Entity向外提供了一个主动销毁自身的接口方法Destroy()

		/// <summary>
        /// Entity对外提供的销毁自己的接口
        /// 这个接口的作用是通知Entity我要销毁你了
        /// 并不是Entity销毁时做清理工作的接口
        /// 调用时会通知Context,由Context执行销毁流程
        /// 在销毁流程中回调到Entity的 InternalDestroy() 方法,执行清理
        /// </summary>
        /// <exception cref="EntityIsNotEnabledException"></exception>
        public void Destroy() 
        {
            if (!_isEnabled) 
            {
                throw new EntityIsNotEnabledException("Cannot destroy " + this + "!");
            }

            if (OnDestroyEntity != null) 
            {
                OnDestroyEntity(this);
            }
        }

这个方法只负责触发起Entity的销毁,但Entity本身并不负责真正的销毁流程,而是通过 OnDestroyEntity 事件回调至Context的DetroyEntity方法中进行实际销毁,并在销毁流程中调用Entity的InternalDestroy() 方法执行Entity自身的销毁清理操作。当前Entity实例已经添加的组件也是在这里清空的。

		/// <summary>
        /// Entity自身的销毁清理操作
        /// Context在DestroyEntity方法中,于销毁流程的适当时机下调用
        /// 当前Entity实例添加的组件也是在这里清空的
        /// </summary>
        public void InternalDestroy() 
        {
			_isEnabled = false;
			_name = null;
			_toStringCache = null;

			RemoveAllComponents();

			OnComponentAdded = null;
            OnComponentReplaced = null;
            OnComponentRemoved = null;
            OnDestroyEntity = null;
		}

引用和释放

除了Context会引用Entity,Entity还可能被添加进其他结构中,如Group、Collector等。通过引用计数器,确保Entity实例已经完全没有被引用后,才会由Context回收,以免错误的复用导致数据错乱。

		/// <summary>
        /// 当前引用计数
        /// </summary>
        public int retainCount => _aerc.retainCount;

        /// <summary>
        /// 添加引用
        /// </summary>
        /// <param name="owner"></param>
        public void Retain(object owner) 
        {
            _aerc.Retain(owner);
        }

        /// <summary>
        /// 释放引用
        /// 引用计数归0时,触发OnEntityReleased事件
        /// OnEntityReleased 即调用至Context的onEntityReleased方法,放入对象池等待复用
        /// </summary>
        /// <param name="owner"></param>
        public void Release(object owner) 
        {
            _aerc.Release(owner);
            //_toStringCache = null;

            if (_aerc.retainCount == 0) 
            {
                if (OnEntityReleased != null) OnEntityReleased(this);
            }
        }

Component操作

Component操作无非就是对Component的增删改查。并且都是以Component的Index作为key来进行。

添加组件很简单,主要使用Add方法,以index为下标,记录到数组中,并触发 OnComponentAdded 事件,回调至Context中更新Group。

		/// <summary>
        /// 添加Component
        /// </summary>
        /// <param name="index">Component的唯一ID(类型ID)</param>
        /// <param name="component"></param>
        /// <exception cref="EntityIsNotEnabledException"></exception>
        /// <exception cref="EntityAlreadyHasComponentException"></exception>
        public void AddComponent(int index, IComponent component) 

移除组件同样以index为key

		/// <summary>
        /// 移除Component
        /// </summary>
        /// <param name="index"></param>
        /// <exception cref="EntityIsNotEnabledException"></exception>
        /// <exception cref="EntityDoesNotHaveComponentException"></exception>
        public void RemoveComponent(int index) 

另外,Entity还提供了替换组件的接口:

		/// <summary>
        /// 替换Component
        /// 没有的话会添加
        /// </summary>
        /// <param name="index"></param>
        /// <param name="component"></param>
        /// <exception cref="EntityIsNotEnabledException"></exception>
        public void ReplaceComponent(int index, IComponent component) 
        {
            if (!_isEnabled) 
            {
                throw new EntityIsNotEnabledException(
                    "Cannot replace component '" +
                    _contextInfo.componentNames[index] + "' on " + this + "!"
                );
            }

            if (HasComponent(index)) 
            {
                replaceComponent(index, component);
            } 
            else if (component != null) 
            {
                AddComponent(index, component);
            }
        }

这是一个相对于单纯增删通用性更强的方法,其内部会先判断要替换的下标当前是否已经有记录,没有的话视为添加组件,如果有,才会走私有的替换方法:

		/// <summary>
        /// 替换Component的实际执行
        /// 存在三种情况
        ///     1.同一个Component的引用,整体刷一遍:
        ///                         不需要重新设置_components数组
        ///                         直接走OnComponentReplaced回调
        ///     2.null替换掉旧的Component:
        ///                         将_components数组index下标位置设置为null
        ///                         这个方法只在内部调用,且在调用前都提前进行了判断,不存在旧的Component为null的情况
        ///                         相当于Remove,只走OnComponentRemoved回调,不走OnComponentReplaced回调
        ///                         旧的Component走Reset方法(如果实现了IResetable接口),放回池里
        ///     3.同一类型的一个新的Component引用:
        ///                         将_components数组index下标位置设置为新的引用
        ///                         走OnComponentReplaced回调
        ///                         旧的Component走Reset方法(如果实现了IResetable接口),放回池里
        /// </summary>
        /// <param name="index"></param>
        /// <param name="replacement"></param>
        void replaceComponent(int index, IComponent replacement) 
        {
            //_toStringCache = null;
            var previousComponent = _components[index];
            if (replacement != previousComponent) 
            {
                _components[index] = replacement;
                _componentsCache = null;
                if (replacement != null) 
                {
                    if (OnComponentReplaced != null) 
                    {
                        OnComponentReplaced(this, index, previousComponent, replacement);
                    }
                } 
                else 
                {
                    _componentIndicesCache = null;
                    if (OnComponentRemoved != null) 
                    {
                        OnComponentRemoved(this, index, previousComponent);
                    }
                }

				// 旧的Component调用Reset方法,并清空回池
				var resetablePrevComponent = previousComponent as IResetable;
				if (resetablePrevComponent != null) resetablePrevComponent.Reset();
				var entityIdRef = previousComponent as IEntityIdRef;
				if (entityIdRef != null) entityIdRef.entityId = 0;
				var modifiable = previousComponent as IModifiable;
				if (modifiable != null) modifiable.modified = false;
				GetComponentPool(index).Push(previousComponent);
            } 
            else 
            {
                if (OnComponentReplaced != null) 
                {
                    OnComponentReplaced(this, index, previousComponent, replacement);
                }
            }
        }

上面注释里已经对这个方法的内容写的很清楚了,只多提一句,其实RemoveComponent方法内部也是调用的ReplaceComponent,只不过是将指定index替换为null。

最后,Entity还提供了几个查询当前Entity实例是否添加了指定Component的方法,除了供业务逻辑查询使用,也是Matcher进行筛选时调用的方法。

		/// <summary>
        /// 是否已经添加了指定类型的Component
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public bool HasComponent(int index) 

		/// <summary>
        /// 是否已经添加了所有指定类型的Component
        /// </summary>
        /// <param name="indices"></param>
        /// <returns></returns>
        public bool HasComponents(int[] indices) 

		/// <summary>
        /// 是否已经添加了指定类型的Component之一
        /// </summary>
        /// <param name="indices"></param>
        /// <returns></returns>
        public bool HasAnyComponent(int[] indices) 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值