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。