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