EntitasLite源码分析(二)

IContexts

上文介绍了Context是EntitasLite框架中管理一切的管家,而Contexts则是负责创建和管理单个Context的大管家(从名字也能看出来)。

IContexts接口本身内容非常简单,只有一个记录所有Context的数组
在这里插入图片描述

Contexts

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

在框架中,Contexts以单例的形式使用:

		private static Contexts _sharedInstance;

		public static Contexts sharedInstance
		{
			get
			{
				if (_sharedInstance == null)
					_sharedInstance = new Contexts();
				return _sharedInstance;
			}
		}

创建单例时,会在构造方法中一次性创建所有的Context,之后,Context的数量和范围(包含的Component)就不再改变了。

		public Contexts()
		{
			InitAllContexts();
		}

在介绍 InitAllContexts 方法是如何创建所有Context之前,我们可以先想一下,一个游戏中会有多少个Context?不同的Context之间以什么作为标识?既然 InitAllContexts 方法能够一次性创建出所有的Context,就说明有哪些Context、每个Context包含哪些Component是在编码阶段就已经确定的,那又是通过什么来确定的呢?

答案就是ContextAttribute

	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true)]
	public abstract class ContextAttribute : Attribute 
	{
		//指定名字,该名字对应属于哪个Context
		public readonly string name;

		protected ContextAttribute() => name = GetName(this);


		private static string GetName(ContextAttribute c) => GetName(c.GetType());
		private static string GetName(Type type) => type.Name.RemoveSuffix("Attribute").RemoveSuffix("Context");

		public static string GetName<C>() where C : ContextAttribute => ContextAttributeNameCache<C>.Name;
		private static class ContextAttributeNameCache<C> where C: ContextAttribute 
		{
			public static readonly string Name = GetName(typeof(C));
			static ContextAttributeNameCache() {}
		}
	}

通过给Component添加ContextAttribute属性,并指定名字,就可以将Component分配给对应的Context,像下面这样:

	//指定 PositionComponent 组件属于名字为 Default 的Context
	[Default]
	public class PositionComponent : IComponent
	{
		public int x;
		public int y;
	}

而在Contexts单例中,也存在一个字典,记录着名字到Context实例的映射关系:

		/// <summary>
		/// ContextName -- Context
		/// </summary>
		private Dictionary<string, Context> _contextLookup;

通常一个项目中不会有太多的Context,比如框架中默认只提供了以下几种Context

在这里插入图片描述
有了ContextAttribute属性,就可以在程序启动时,遍历一遍所有的Component,根据Component上设置的ContextAttribute属性的名字,统计出项目中一共需要创建多少个Context、每个Context的名字是什么,以及每个Context内包含了哪些Component。这也就是Contexts中CollectAllComponents()方法做的事情。

		/// <summary>
		/// 收集程序域中的所有 IComponent
		/// 根据 IComponent 设置的 Context 属性
		/// 按照 Context属性的名字将IComponent进行分组
		/// </summary>
		/// <returns></returns>
		private static Dictionary<string, List<Type>> CollectAllComponents()
		{
			var defaultContextName = ContextAttribute.GetName<Default>();
			
			//收集程序域中的所有IComponent类型
			var compType = typeof(IComponent);
			var types = AppDomain.CurrentDomain.GetAssemblies()
								.SelectMany(s => s.GetTypes())
								.Where(p => p.IsClass && p.IsPublic && !p.IsAbstract &&
											compType.IsAssignableFrom(p));
			
			//key为context的名字,Value为这个context包含哪些类型(组件的type)
			var comps = new Dictionary<string, List<Type>>();
			//default比较特殊,所有没有设置 ContextAttribute 的Component都被认为是Default的
			comps[defaultContextName] = new List<Type>();

			var attrType = typeof(ContextAttribute);
			//遍历上面收集到的所有IComponent类型
			//如果没有设置过ContextAttribute,则默认归类于Default的Context
			//如果设置了ContextAttribute,则以ContextAttribute的Name作为该IComponent要归属的Context
			foreach (var t in types)
			{
				var attribs = t.GetCustomAttributes(attrType, false);

				if (attribs == null || attribs.Length <= 0)
				{
					//没有Context属性,放入默认Context
					CollectComponent(comps, defaultContextName, t);
				}
				else
				{
					foreach (var attr in attribs)
					{
						CollectComponent(comps, ((ContextAttribute)attr).name, t);
					}
				}
			}
			
			return comps;
		}

然后我们回到Contexts单例创建的时候,看看Contexts在构造时调用的InitAllContexts()方法。

		/// <summary>
		/// 梦开始的地方
		/// </summary>
		private void InitAllContexts()
		{
			var defaultContextName = ContextAttribute.GetName<Default>();

			// 统计出项目中需要创建哪些Context、每个Context的名字是什么、每个Context中包含哪些Component
			Dictionary<string, List<Type>> comps = CollectAllComponents();

			var contextList = new List<Context>();
			_contextLookup = new Dictionary<string, Context>();

			foreach (var cc in comps)
			{
				//context的名字
				var name = cc.Key;
				//这个context中包含哪些Component(Type)
				var list = cc.Value;
				//是否是Default的Context
				var isDefault = name == defaultContextName;

				list.Sort((x, y) => string.CompareOrdinal(x.FullName, y.FullName));
				
				//创建每个Context的实例
				//每个Context有哪些Component是固定的,totalComponents也是固定的,即list.Count
				//Context中CreateEntity()时为Entity分配的唯一ID,其初始值即来自这里的startCreationIndex
				//可见不同Context里Entity的ID是会重复的
				var c = new Context(list.Count, startCreationIndex, new ContextInfo(name, list.ToArray(), isDefault), GetAERC());

				//记录名字到Context实例的映射
				_contextLookup[name] = c;
				contextList.Add(c);
			}

			_defaultContext = _contextLookup[defaultContextName];
			_contextList = contextList.ToArray();
		}

最后,在创建Context时还传入了一个ContextInfo的实例,这个ContextInfo里存着该Context的基本初始信息,结构如下:
在这里插入图片描述
其中的componentTypes数组就是上文InitAllContexts()中new Context时传入的 list.ToArray(),记录了该Context包含的Component的实际类型,Find(Type type)方法传入的参数是Component的类型,通过与componentTypes数组中的元素做对比,找到该Component对应的数组下标返回,这个数组下标就是在框架中到处会用到的Component的Index。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值