Context介绍
Context是Entitas中的上下文环境,主要用于管理当前环境下的所有Entity以及Group的创建与回收。可以同时存在多个Context,Context之间互不影响。所有的Context由Contexts单例管理。
一、Contexts
Contexts继承自IContexts,主要用于创建和保存当前所有的Context。Contexts类由Entitias的代码生成器生成,无须我们手动实现。在Entitas系统外部通过Contexts.sharedInstance单例对象访问.
public partial class Contexts : Entitas.IContexts {
public static Contexts sharedInstance {
get {
if (_sharedInstance == null) {
_sharedInstance = new Contexts();
}
return _sharedInstance;
}
set { _sharedInstance = value; }
}
static Contexts _sharedInstance;
public GameContext game { get; set; }
public InputContext input { get; set; }
public Entitas.IContext[] allContexts { get { return new Entitas.IContext [] { game, input }; } }
public Contexts() {
game = new GameContext();
input = new InputContext();
}
}
二、ContextInfo
ContextInfo是Context的信息类,是Context初始化参数之一.主要包含了Context的名字,Context所有组件名字数组以及所有组件类型数组,而这些信息最终也是用来初始化Entity的.
public ContextInfo(string name, string[] componentNames, Type[] componentTypes) {
this.name = name;
this.componentNames = componentNames;
this.componentTypes = componentTypes;
}
三、Context
Context继承自IContext,用来管理当前上下文中的Entity以及Group的.我们所有用到的Enity与Group都应该通过Context的CreateEntity()
与GetGroup(IMatcher
方法创建与获取.因为在Context中会对所有的Entity与Group进行缓存,回收利用.
3.1、Context创建
Context无需我们手动创建,在Entitas的generate时,代码生成器会为我们自动生成对应的Context.下面的代码为自动生成的代码.
public sealed partial class GameContext : Entitas.Context {
public GameContext()
: base(
GameComponentsLookup.TotalComponents,
0,
new Entitas.ContextInfo(
"Game",
GameComponentsLookup.componentNames,
GameComponentsLookup.componentTypes
),
(entity) =>
#if (ENTITAS_FAST_AND_UNSAFE)
new Entitas.UnsafeAERC(),
#else
new Entitas.SafeAERC(entity),
#endif
() => new GameEntity()) {
}
}
我们再来看看Context构造函数的实现:
public Context(int totalComponents, int startCreationIndex, ContextInfo contextInfo, Func aercFactory, Func entityFactory) {
//当前环境中组件的类型的数量,主要用来初始化组件列表的初始化长度
_totalComponents = totalComponents;
//当前环境中Entity的起始ID
_creationIndex = startCreationIndex;
if (contextInfo != null) {
_contextInfo = contextInfo;
if (contextInfo.componentNames.Length != totalComponents) {
throw new ContextInfoException(this, contextInfo);
}
} else {
_contextInfo = createDefaultContextInfo();
}
//创建Entity的引用计数的工厂方法
_aercFactory= aercFactory ?? (entity => new SafeAERC(entity));
//创建Entity的工厂方法
_entityFactory = entityFactory;
//初始化各种容器
_groupsForIndex = new List>[totalComponents];
_componentPools = new Stack[totalComponents];
_entityIndices = new Dictionary();
_groupChangedListPool = new ObjectPool>>(
() => new List>(),
list => list.Clear()
);
//缓存delegates避免gc分配
//主要时在组件或者实体发生改变时,在代理方法中去改变对应Group中所包含的内容
//这也是Entitas处理大量同类型数据比较快时原因之一,避免的大量的循环遍历
_cachedEntityChanged = updateGroupsComponentAddedOrRemoved;
_cachedComponentReplaced = updateGroupsComponentReplaced;
_cachedEntityReleased = onEntityReleased;
_cachedDestroyEntity = onDestroyEntity;
3.2、Entity创建
每个Context中都有一个保存当前环境Entity的对象池:_reusableEntities
,以及当前所有活跃的Entity的集合_entities
。记住,所有的Entity创建都需要通过Context的CreateEntity()
方法。这样通过对象池可以减少大量的创建Entity的时间开销,同时也避免频繁的创建销毁Entity所带来的内存碎片,提高内存使用率。
public TEntity CreateEntity() {
TEntity entity;
//如果对象池中有对象,则取出对象,并重新激活
//如果没有,则使用工厂方法创建一个新的Entity,并初始化
if (_reusableEntities.Count > 0) {
entity = _reusableEntities.Pop();
entity.Reactivate(_creationIndex++);
} else {
entity = _entityFactory();
entity.Initialize(_creationIndex++, _totalComponents, _componentPools, _contextInfo, _aercFactory(entity));
}
//加入活跃列表中,并增加引用计数
_entities.Add(entity);
entity.Retain(this);
_entitiesCache = null;
//给Entity的变化添加代理方法,用于更新Group
entity.OnComponentAdded += _cachedEntityChanged;
entity.OnComponentRemoved += _cachedEntityChanged;
entity.OnComponentReplaced += _cachedComponentReplaced;
entity.OnEntityReleased += _cachedEntityReleased;
entity.OnDestroyEntity += _cachedDestroyEntity;
if (OnEntityCreated != null) {
OnEntityCreated(this, entity);
}
return entity;
}
3.3、Group的创建
Context中保存这当前环境中的所有Group,同时通过代理,在Entity发生变化时,更新对应的Group。保存所有Matcher与对应Group的字典Dictionary
,主要用于获取Group,主要用于Component变化时更新Group。
public IGroup GetGroup(IMatcher matcher) {
IGroup group;
//查看_groups中是否已存在该matcher的Group,有则返回,没有就创建新的Group
if (!_groups.TryGetValue(matcher, out group)) {
group = new Group(matcher);
var entities = GetEntities();
//遍历所有的Entity,将匹配的加入到Group中
for (int i = 0; i < entities.Length; i++) {
group.HandleEntitySilently(entities[i]);
}
_groups.Add(matcher, group);
//遍历这个matcher中的所有Component序号,将Group加入到对应的_groupsForIndex中
for (int i = 0; i < matcher.indices.Length; i++) {
var index = matcher.indices[i];
if (_groupsForIndex[index] == null) {
_groupsForIndex[index] = new List>();
}
_groupsForIndex[index].Add(group);
}
if (OnGroupCreated != null) {
OnGroupCreated(this, group);
}
}
return group;
}
3.4、其他
还有一些其他的方法,例如Entity上组件添加或修改时的代理方法updateGroupsComponentAddedOrRemoved
,组件删除时的代理方法onDestroyEntity
等这些大家可以通过查看Context的源码进行了解。
四、总结
Context是一个独立的环境,使用对象池管理当前环境中所有的Entity的创建以及回收。缓存着所有的Group,同时通过设置Entity改变时的一系列的代理方法,更新当前环境中对应的Group。
这样做可以减少Entity创建的开销,减少因为Entity频繁的创建与消耗带来的内存碎片,提高内存使用率。同时减少了我们需要某些数据集合时,通过遍历带来的一次集中性的大的开销,将这些分散到各个Entity变化的时刻。
<上一篇> ECS Entitas分析(一)___概括