当前位置: 首页 > article >正文

EntitasLite源码分析(一)

EntitasLite是一个轻量级的ECS框架,代码量少,结构清晰,非常适合扩展和封装,创建适合自身项目实际情况的ECS系统。源码地址:https://github.com/rocwood/Entitas-Lite.git

IContext

在EntitasLite中,Context是一个非常重要的基础概念。Context可以理解为是一个划定一系列Component的范围,也可以理解成一系列业务逻辑发生的域。

IContext定义了Context的能力,其主要内容如下:
在这里插入图片描述
在这里插入图片描述
可以看出,IContext中主要包含三部分内容:Component、Entity、Group

Component决定了Context包含的数据范围,因而也决定了Context能够支持的业务范围。一个Context内有哪些Component在创建Context的时候即已确定,因此totalComponents字段的值是固定的,可以以此值作为数组长度,为该Context中的每种Component创建一个对象池,供后续复用。

Entity的管理也由Context负责,通过CreateEntity()方法创建Entity实例,并为该实例分配一个CreationIndex作为唯一ID。这个唯一ID是Context内的一个自增ID,初始值在创建Context的时候由外部指定,既没有分段也非引用传递,因此Entity的这个唯一ID只在Context内部唯一,也能反映出框架的思想就是通过Context来管理一切。除了创建销毁,Context对其内部的Entity同样做了池化管理。

Group可以理解为Context的子集,一个Group关心Context内的一部分Component,具体关心哪些Component则由Matcher指定,因此Matcher和Group是一一对应的Key-Value关系。当发生Component的添加和移除行为时,关心该Component的Group会根据自身Matcher的规则决定是否需要将此次发生Component改变的Entity添加进自身记录(或从自身记录中移除)。框架借此得以维护更新自身的内容视图。

Context

Context是IContext的具体实现类,其主要内容如下:
在这里插入图片描述

几个重要的方法

		/// <summary>
		/// 供外部调用的创建Entity的接口
		/// </summary>
		public Entity CreateEntity()
		{
			Entity entity;
			
			//从池中POP出来复用的Entity只需要Reactive重新激活
			//第一次创建的Entity则需要走Initialize方法进行初始化
			//其中的原因在看到Entity的源码时自然会了解
			//但无论是哪一种,都会通过 _creationIndex++ 重新分配唯一ID
			if (_reusableEntities.Count > 0)
			{
				entity = _reusableEntities.Pop();
				entity.Reactivate(_creationIndex++);
			}
			else
			{
				entity = (Entity)Activator.CreateInstance(typeof(Entity));
				entity.Initialize(_creationIndex++, _totalComponents, _componentPools, _contextInfo, _aercFactory(entity));
			}

			//添加新创建(或复用)的Entity到自身的HashSet记录中
			_entities.Add(entity);
			entity.Retain(this);
			_entitiesCache = null;

			//三个组件回调,当该Entity发生组件变化时触发
			//回调至当前Context的对应方法中,用以更新Group,维护视图
			entity.OnComponentAdded += _cachedEntityChanged;
			entity.OnComponentRemoved += _cachedEntityChanged;
			entity.OnComponentReplaced += _cachedComponentReplaced;
			
			//两个Entity生命函数回调
			//当Entity的引用计数清0时触发OnEntityReleased,回调至Context,由Context对Entity真正执行自动销毁等操作
			//当Entity向外部提供的手动Destroy接口被调用时触发OnDestroyEntity,回调至Context,由Context真正执行销毁流程
			entity.OnEntityReleased += _cachedEntityReleased;
			entity.OnDestroyEntity += _cachedDestroyEntity;

			if (OnEntityCreated != null) 
				OnEntityCreated(this, entity);

			return entity;
		}

可以看到,当Entity添加和移除Component时,都会触发 _cachedEntityChanged 委托,该委托实际方法如下:

		/// <summary>
		/// entity发生组件的添加和移除时调用
		/// </summary>
		/// <param name="entity"></param>
		/// <param name="index"></param>
		/// <param name="component"></param>
		void updateGroupsComponentAddedOrRemoved(IEntity entity, int index, IComponent component)
		{
			//参数中的index是Component的类型ID,一个index就代表一种Component
			//找出所有关心这个Component的Group
			List<IGroup> _IGroupList = _groupsForIndex[index];
			if (null == _IGroupList) return;
			
			List<GroupChanged> _groupChangedDelList = _groupChangedListPool.Get();
			Entity tEntity = (Entity)entity;

			//对每个相关的 Group 依次尝试添加或移除 entity的操作
			//这个操作在Group的HandleEntity中由Group实现
			//如果操作成功,意味着该 Group 发生变化
			//当 Group 发生变化时,收集本次变化具体触发了什么委托(OnEntityAdded或 OnEntityRemoved)
			for (int i = 0; i < _IGroupList.Count; i++)
			{
				_groupChangedDelList.Add(_IGroupList[i].HandleEntity(tEntity));
			}

			//依次执行本次Component增减所触发的Group委托
			for (int i = 0; i < _groupChangedDelList.Count; i++)
			{
				GroupChanged groupChangedEvent = _groupChangedDelList[i];
				if (null == groupChangedEvent) continue;
				
				groupChangedEvent(_IGroupList[i], tEntity, index, component);
			}

			_groupChangedListPool.Push(_groupChangedDelList);
		}

上面这段代码里涉及到一个 _groupsForIndex 数组,这个数组用Component的Index(也就是Component的类型)作为下标索引,记录着都有哪些Group关心该类型的Component,在创建Group时即将Group添加进了该数组的元素中,发生的地方在下面这个方法中:

		/// <summary>
		/// 通过Matcher获取Group
		/// </summary>
		/// <param name="matcher"></param>
		/// <returns></returns>
		public IGroup GetGroup(IMatcher matcher)
		{
			IGroup group;
			if (!_groups.TryGetValue(matcher, out group))
			{
				//如果Matcher对应的Group不存在,则立刻创建一个新的Group
				group = new Group(matcher);
				//先对当前已经存在的所有Entity做一遍过滤,看哪些Entity应该被包含进新Group中
				var entities = GetEntities();
				for (int i = 0; i < entities.Length; i++)
				{
					group.HandleEntitySilently(entities[i]);
				}
				_groups.Add(matcher, group);

				//对 Matcher 涉及到的所有 Component,都标记为 Group 相关
				//原因是一旦Entity发生了这些Component的改变时,都有可能引起该 Group 的变化
				//Group就是在这时候被添加进 _groupsForIndex 数组的元素中的
				for (int i = 0; i < matcher.indices.Length; i++)
				{
					int index = matcher.indices[i];
					if (_groupsForIndex[index] == null)
					{
						_groupsForIndex[index] = new List<IGroup>();
					}
					_groupsForIndex[index].Add(group);
				}

				//调用新 group 创建的回调
				if (OnGroupCreated != null)
				{
					OnGroupCreated(this, group);
				}
			}

			return group;
		}

Entity提供了一个供外部调用进行手动销毁的接口方法Destroy(),该方法只是用来供外部通知Entity要进行销毁,但就像Entity的创建由Context管理一样,Entity的销毁也不由本身负责,同样通过委托回调至Context中,由Context真正执行,并在销毁流程中调用Entity的清理方法。

		/// <summary>
		/// Entity的Destroy方法被调用时调用
		/// Destroy方法是Entity向外提供的主动销毁Entity的接口
		/// 当外部调用这个接口时,Entity会从这里通知Context
		/// 由Context执行 DestroyEntity((Entity)entity) 方法真正执行销毁流程
		/// 销毁流程里除了移除Context的各种记录外,还会调用Entity的InternalDestroy方法,执行清理
		/// </summary>
		/// <param name="entity"></param>
		void onDestroyEntity(IEntity entity)
		{
			DestroyEntity((Entity)entity);
		}
		/// <summary>
		/// 对应CreateEntity方法,是真正执行Entity销毁流程的方法
		/// OnEntityWillBeDestroyed -> entity.InternalDestroy -> OnEntityDestroyed
		/// </summary>
		/// <param name="entity"></param>
		/// <exception cref="ContextDoesNotContainEntityException"></exception>		
		private void DestroyEntity(Entity entity)
		{
			//从当前Context的HashSet记录中移除该Entity
			var removed = _entities.Remove(entity);
			if (!removed)
			{
				throw new ContextDoesNotContainEntityException(
					"'" + this + "' cannot destroy " + entity + "!",
					"This cannot happen!?!"
				);
			}
			_entitiesCache = null;

			//Entity清理前触发点
			if (OnEntityWillBeDestroyed != null)
			{
				OnEntityWillBeDestroyed(this, entity);
			}

			//调用Entity的清理方法
			//Entity在这里会清空自身的Component
			entity.InternalDestroy();

			//Entity清理后触发点
			if (OnEntityDestroyed != null)
			{
				OnEntityDestroyed(this, entity);
			}

			if (entity.retainCount == 1)
			{
				//entity没有别的owner了
				//释放当前Context对Entity的引用
				//放回复用池里
				entity.OnEntityReleased -= _cachedEntityReleased;
				_reusableEntities.Push(entity);
				entity.Release(this);
				entity.RemoveAllOnEntityReleasedHandlers();
			}
			else
			{
				//释放当前Context对Entity的引用
				//entity的引用计数还没有清0
				//此时不可以放回池里
				//需要放到 Retained的Set里进行记录
				_retainedEntities.Add(entity);
				entity.Release(this);
			}
		}

除此之外,Context还扩展了三个创建Collector的方法,具体Collector是啥会在后文中介绍。
在这里插入图片描述


http://www.kler.cn/a/411018.html

相关文章:

  • JSON数据转化为Excel及数据处理分析
  • 政安晨【零基础玩转各类开源AI项目】探索Cursor-AI Coder的应用实例
  • EXTI配置流程 含中断延时消抖点亮小灯
  • 使用 前端技术 创建 QR 码生成器 API1
  • 利用开源图床的技巧与实践
  • redis数据类型详解
  • Java中的JSONObject详解
  • Day3 洛谷Day3 1161+1179+1200+1304
  • 【AI系统】昇腾 AI 架构介绍
  • 直接抄作业!Air780E模组LuatOS开发:位运算(bit)示例
  • 【MyBatis】全局配置文件—mybatis.xml 创建xml模板
  • 【设计模式】【结构型模式(Structural Patterns)】之组合模式(Composite Pattern)
  • HOW - React 状态模块化管理和按需加载(二) - jotai
  • Java ArrayList 与顺序表:在编程海洋中把握数据结构的关键之锚
  • Java学习笔记--继承的介绍,基本使用,成员变量和成员方法访问特点
  • MySQL 与 MongoDB 存储差异分析
  • 【优先算法学习】双指针--结合题目讲解学习
  • 深入了解决策树---机器学习中的经典算法
  • 排除杂音噪音,手机录音去除杂音软件如何办到?
  • vscode 如何鼠标双击时选择带有-的
  • 【北京迅为】iTOP-4412全能版使用手册-第十一章 设备树Linux系统编译
  • uniapp 开发微信小程序笔记
  • 计算机网络----基本概念
  • 华为云开发的入门介绍
  • 相亲交友小程序项目介绍
  • 【在Linux世界中追寻伟大的One Piece】多线程(二)