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<TEntity> matcher)
方法創建與獲取.因為在Context中會對所有的Entity與Group進行緩存,回收利用.
3.1、Context創建
Context無需我們手動創建,在Entitas的generate時,代碼生成器會為我們自動生成對應的Context.下面的代碼為自動生成的代碼.
public sealed partial class GameContext : Entitas.Context<GameEntity> {
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<IEntity, IAERC> aercFactory, Func<TEntity> 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<IGroup<TEntity>>[totalComponents];
_componentPools = new Stack<IComponent>[totalComponents];
_entityIndices = new Dictionary<string, IEntityIndex>();
_groupChangedListPool = new ObjectPool<List<GroupChanged<TEntity>>>(
() => new List<GroupChanged<TEntity>>(),
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<IMatcher<TEntity>, IGroup<TEntity>> _groups
,主要用于獲取Group,主要用于Component變化時更新Group。
public IGroup<TEntity> GetGroup(IMatcher<TEntity> matcher) {
IGroup<TEntity> group;
//查看_groups中是否已存在該matcher的Group,有則返回,沒有就創建新的Group
if (!_groups.TryGetValue(matcher, out group)) {
group = new Group<TEntity>(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<IGroup<TEntity>>();
}
_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分析(一)___概括