首页 > 编程语言 >EntitasLite源码分析(五)

EntitasLite源码分析(五)

时间:2024-12-01 19:29:43浏览次数:8  
标签:分析 单例 contextInfo Component Entity 源码 Context EntitasLite public

IComponent

在ECS结构中,Component并不实际承载逻辑,而是负责为逻辑提供所需的数据,因此Component只是一个负责记录数据的容器。

EntitasLite框架中,几乎所有类型都维持着一个接口类对应一个接口实现类的形式,如 IContext 和 Context、IEntity 和 Entity。Component则不同,Component存在的意义是为了使数据有个存取的地方,以供实际的业务逻辑使用,其本身是没有任何特性的。而不同的业务逻辑需要关心的数据往往是不同且隔离的,因此我们通常会根据业务逻辑来规划Component,即为单个业务逻辑提供一个对应的Component(当然有时候也会涉及到多个Component)来维护数据。也就是说Component是使用者根据自身需要大量创建的一个类型,因此,在框架中只提供了一个空的IComponent的接口类来定义组件身份,当我们需要增加一个新组建时,只需要实现这个接口即可。

public interface IComponent {}

[Game]
public sealed class MovableComponent : IComponent {
}

ComponentIndex

之前我们说过,在EntitasLite中,对于Component的增删改查都会以Index作为Component的唯一标识,那这个Index到底是什么,又从哪里可以获得呢?这点可以从 ComponentIndex<T> 泛型类中找到答案。

public class ComponentIndex<T> where T : IComponent

这是一个工具类,用于获得指定Component T 在某个Context中的Index,可见,与Entity一样,Component的Index也是在单个Context中唯一的。

ComponentIndex<T>中缓存了之前的查找结果:

private static int _cachedDefaultIndex = -1;
private static Dictionary<ContextInfo, int> _cachedLookup = new Dictionary<ContextInfo, int>();

然后向外提供了三个静态查找方法:

public static int FindIn<C>() where C : ContextAttribute

public static int FindIn(Context _context)

public static int FindIn(ContextInfo _contextInfo)

前两个方法最终都会调到最后这个以ContextInfo为参数的方法中来:

		public static int FindIn(ContextInfo _contextInfo)
		{
			if (_contextInfo == null) return -1;

			if (_contextInfo.isDefault)
			{
				//默认ContextInfo都是一样的,T 的type对应的 componentTypes 下标应该是一样的
				//因此可以用static字段记录,只需要找一遍就可以
				if (_cachedDefaultIndex < 0)
				{
					_cachedDefaultIndex = _contextInfo.Find<T>();
					if (_cachedDefaultIndex < 0)
						throw new ComponentIsNotInContextException(typeof(T), _contextInfo);
				}

				return _cachedDefaultIndex;
			}
			else
			{
				//非默认的ContextInfo,可以用字典缓存起来
				//避免每次都要重新找
				int val = -1;
				if (!_cachedLookup.TryGetValue(_contextInfo, out val))
				{
					//通过ContextInfo的Find<T>方法来返回实际的Index
					val = _contextInfo.Find<T>();
					if (val < 0)
						throw new ComponentIsNotInContextException(typeof(T), _contextInfo);

					_cachedLookup[_contextInfo] = val;
				}

				return val;
			}
		}

之前在EntitasLite源码分析(二)中我们简单提到了ContexInfo记录着Context的基本信息,这里正好来回顾一下。

Contexts在构造时会收集程序中所有的IComponent,并根据它们的ContextAttribute统计一共有哪些Context,以及每个Context中都有哪些IComponent,然后创建Context,并为每个Context都生成一个对应的ContextInfo。ContextInfo里记录着该Context的名字等基本信息,其中最重要的,是通过一个数组,记录着这个Context所包含的每个Component的程序类型Type.

public readonly Type[] componentTypes;

ComponentIndex<T> 获取Index时,最终调用了ContextInfo的Find<T>方法得到结果,该方法如下:

		internal int Find<T>() where T : IComponent 
		{
			return Find(typeof(T));
		}

		internal int Find(Type type) 
		{
			return Array.IndexOf(componentTypes, type);
		}

可见,Component的Index,其实就是它是该Context中的第几个组件。

单例Component

在EntitasLite框架本身,并没有SingletonEntity或者SingletonComponent这种东西,但是可以通过实现 IUnique 接口使Component成为一个单例组件。

public interface IUnique {}

[Obsolete("Use IUnique instead")]
public interface IUniqueComponent : IComponent, IUnique	{}

单例组件在Context中只允许存在一个(同样的,单例也是针对单个Context而言的),也就是说,整个Context中最多只会有一个Entity身上添加了这个组件,而添加了这个组件的Entity,就被成为这个Component的单例Entity。这点可以通过Context的 AddUnique<T> 方法看出来:

		public T AddUnique<T>(bool useExisted = true) where T : IComponent, IUnique, new() 
		{
			int componentIndex = ComponentIndex<T>.FindIn(this.contextInfo);

			Entity entity = GetSingleEntity(componentIndex);
			if (entity != null) 
			{
				//单例组件只允许存在一个,也就是说,Context中只允许有一个Entity带有这个组件
				if (!useExisted)
					throw new EntityAlreadyHasComponentException(
					   componentIndex, "Cannot add component '" +
					   _contextInfo.componentNames[componentIndex] + "' to " + entity + "!",
					   "You should check if an entity already has the component."
					);
				return (T)ModifyUniqueComponent(componentIndex);
			}

			entity = CreateEntity(typeof(T).Name);
			T component = entity.CreateComponent<T>(componentIndex);
			entity.AddComponent(componentIndex, component);

			return component;
		}

当我们试图添加一个单例组件时,会先查找当前有没有该组件的单例Entity,内容如下:

		public Entity GetSingleEntity(int componentIndex) 
		{
			IGroup group = _groupForSingle[componentIndex];

			if (group == null) 
			{
				group = GetGroup(Matcher.AllOf(componentIndex).SetComponentNames(this.contextInfo.componentNames));
				_groupForSingle[componentIndex] = group;
			}

			return group.GetSingleEntity();
		}

这个过程会为要查找的单例Component创建一个Group(如果不存在的话),然后调用Group的GetSingleEntity方法:

		/// <summary>
        /// 这个Group里应该只有一个Entity(或者没有)
        /// 没有返回null
        /// 多于1个抛异常
        /// </summary>
        /// <returns></returns>
        /// <exception cref="GroupSingleEntityException"></exception>
        public Entity GetSingleEntity()
        {
            if (_singleEntityCache == null) 
            {
                var c = _entities.Count;
                if (c == 1) 
                {
                    using (var enumerator = _entities.GetEnumerator()) 
                    {
                        enumerator.MoveNext();
                        _singleEntityCache = enumerator.Current;
                    }
                } 
                else if (c == 0) 
                {
                    return null;
                } 
                else 
                {
                    throw new GroupSingleEntityException(this);
                }
            }

            return _singleEntityCache;
        }

由于这个Group是为单例Component创建的,而Context中最多只会有一个Entity带有这个单例组件,那也就是说,这个Group中最多只会有一个Entity,所以方法里对Entity的数量进行了检查,如果多于1个会抛出异常,如果没有会返回null,如果正好有1个,OK,就他了。

除了添加单例Component,Context还提供了获取单例Component和获取单例Entity的方法:

public Entity GetSingleEntity<T>() where T : IComponent, IUnique

private IComponent GetUniqueComponent(int componentIndex)

而所谓的单例Entity,其实指的是Context中唯一携带某个单例Component的Entity,自然也不会有单独添加单例Entity的操作。

标签:分析,单例,contextInfo,Component,Entity,源码,Context,EntitasLite,public
From: https://blog.csdn.net/zhiai315/article/details/144108123

相关文章

  • EntitasLite源码分析(六)
    IMatcherMatcher是EntitasLite提供的一个匹配器,也可以叫筛选器,通过定义一系列Component规则来对Entity进行匹配或筛选。基本结构由IMatcher接口类定义:这一堆的IMatcher接口乍一看有点乱,仔细一看,还真是有点乱。没关系,我们继续看实现类Matcher。MatcherMatcher是IMatche......
  • flask框架活动信息管理(毕设源码+论文)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、选题背景关于工程教育专业认证相关研究,现有研究主要以工程教育专业认证的理念、标准和实践经验为主,专门针对工程教育专业认证网站建设的研究较......
  • springboot361招生宣传管理系统(论文+源码)_kaic
     毕业设计(论文)题目:招生宣传管理系统      摘 要使用旧方法对招生宣传管理系统的信息进行系统化管理已经不再让人们信赖了,把现在的网络信息技术运用在招生宣传管理系统的管理上面可以解决许多信息管理上面的难题,比如处理数据时间很长,数据存在错误不能......
  • flask框架共享单车系统(毕设源码+论文)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、选题背景关于共享单车系统的研究,现有研究多聚焦于共享单车的商业模式、市场推广以及宏观层面的发展战略等方面,专门针对共享单车系统内部各个功......
  • 【数据分析生命周期全揭秘】从零开始打造你的数据科学项目 | 最全指南
    文章目录前言一、数据分析生命周期是什么?二、第一阶段:发现(Discovery)三、第二阶段:数据准备(DataPreparation)四、第三阶段:模型规划(ModelPlanning)五、第四阶段:模型构建(ModelBuilding)六、第五阶段:结果沟通(CommunicateResults)七、第六阶段:部署运营(Operationalize)......
  • springboot基于Android的个人健康管理系统设计与实现(源码+vue+uinapp+部署文档等)
    文章目录详细视频演示项目介绍技术介绍功能介绍核心代码数据库参考系统效果图源码获取详细视频演示文章底部名片,获取项目的完整演示视频,免费解答技术疑问项目介绍  如今的信息时代,对信息的共享性,信息的流通性有着较高要求,因此传统管理方式就不适合。为了让管理......
  • Economic-Statistics-Investment-Analysis-: 美国上市公司的财务报表分析: 净利润率 +
    财报:人口分布非常重要,特别是“工龄期”与“高教期”的人口占比;-教育任务:现代社会,都要经过国家教育组织及社会化生活的培养,成长为独立社会人(社会生产力是角色之一)-生活任务:生产、婚育、旺盛期、退休、老年期、-生产任务:人类的生命周期:孕育、出生、学龄前、K12......
  • 【开题报告】基于Springboot+vue学业预警帮扶系统(程序+源码+论文) 计算机毕业设计
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景在当今高等教育日益普及的背景下,学生群体的多样性和复杂性不断增加,学业困难问题日益凸显。许多学生在面对繁重的学业压力、复杂的课程内容以及个人生......
  • 【开题报告】基于Springboot+vue宿舍管理系统的设计与实现(程序+源码+论文) 计算机毕业
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着高校规模的不断扩大和学生人数的持续增长,宿舍管理成为高校日常管理中不可或缺的一环。传统的宿舍管理方式,如人工登记、纸质记录等,已难以满足现代......
  • 【开题报告】基于Springboot+vue休闲农场管理系统(程序+源码+论文) 计算机毕业设计
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着城市化进程的加速,人们日益向往回归自然、体验田园生活的休闲方式。休闲农场作为一种新兴的农业旅游模式,不仅为人们提供了亲近自然、放松身心的场......