一个普通的System大体是这样的:
public class SampleSystem : ComponentSystem
{
public struct Data
{
public readonly int Length;
public ComponentDataArray<A> As;
public ComponentDataArray<B> Bs;
}
[Inject] private Data m_Data;
protected override void OnUpdate()
{
for (int index = 0; index < m_Data.Length; ++index)
{
var aaa = m_Data.As[index].Value;
...
}
}
}
根据上章说的组件的内存布局,我们可以想象一下System调用Update前,要给m_Data这个字段注入满足条件的值所需要的信息,以上面的SampleSystem为例,其需要两个组件A和B,那么我们只需要知道当前所有的Archetype中有哪些包含有A,B组件的即可,然后注入时遍历这个满足条件的Archetype列表里所有的Chunk给CompoentDataArray使用。注意这里并不是把Chunk里所有的组件值拷贝到该ComponentDataArray中,这样就太低效了,我们只需要在访问具体值时跳转到对应的Chunk和值的指针就可以了,具体是由ComponentDataArray和ComponentChunkIterator类配合完成的,ComponentDataArray重写了索引符操作,即在m_Data.As[index]时就会触发:
public T this[int index]
{
get
{
if (index < m_Cache.CachedBeginIndex || index >= m_Cache.CachedEndIndex)
m_Iterator.MoveToEntityIndexAndUpdateCache(index, out m_Cache, false);
//相当于m_Cache.CachedPtr[index];
return UnsafeUtility.ReadArrayElement<T>(m_Cache.CachedPtr, index);
}
set{
//和上面差不多,先忽略
}
}
其中m_Cache.CachedPtr我们猜得出就是该组件类型在其Chunk的指针地址了 ,第index个就是相应entity的该组件的值,我们知道就算是同个组件类型,其值都不是必定存放在同一个Chunk里的,甚至不在同个Archetype里,所以就有第6,7行的处理,就是切换到第index个entity的组件所在的Chunk指针上。这些逻辑就由ComponentChunkIterator类负责了。
ComponentChunkIterator很简单,你告诉它总共有哪些个满足的Archetype就行了,实际就是其m_FirstMatchingArchetype字段,m_FirstMatchingArchetype是个链表,用完它后就用其Next字段搞下一个,具体代码和注解如下(经删减):
public void MoveToEntityIndexAndUpdateCache(int index, out ComponentChunkCache cache, bool isWriting)
{
//先找到index所在的Archetype和Chunk
MoveToEntityIndex(index);
//把找到的Chunk信息设置给cache参数给ComponentDataArray使用
UpdateCacheToCurrentChunk(out cache, isWriting, IndexInComponentGroup);
}
public void MoveToEntityIndex(int index)
{
//这个判定可以先不管,主要是Filter的功能处理,比如你可以设定某组件的值为x时才注入给System,我们以后再探讨Filter
if (!m_Filter.RequiresMatchesFilter)
{
//如果本次查询的index比上次查询的要小,那就要从头开始了,先把当前指定的Archetype指向m_FirstMatchingArchetype
//注意m_CurrentArchetypeEntityIndex的初始值为int.MaxValue,所以首次进入本方法时下面的判定会为真。
if (index < m_CurrentArchetypeEntityIndex)
{
m_CurrentMatchingArchetype = m_FirstMatchingArchetype;
m_CurrentArchetypeEntityIndex = 0;
m_CurrentChunk = (Chunk*) m_CurrentMatchingArchetype->Archetype->ChunkList.Begin;
m_CurrentChunkEntityIndex = 0;
}
//如果查询的index不存在当前的Archetype就跳到下一个Archetype看看,直到找到为止。
//其中m_CurrentArchetypeEntityIndex记录着当前index所在Archetype之前所有的Archetype的Entity数量之和;
//而m_CurrentChunkEntityIndex则保存当前index所在Chunk之前所有的同属一个Archetype的Chunk里的Entity数量之和。
//这两个变量是个优化项,用于计算偏移以让ComponentDataArray不需要每次查询都进入本方法,只有在跨Chunk时才需要进入,
//这个等下UpdateCacheToCurrentChunk方法会说到。
while (index >= m_CurrentArchetypeEntityIndex + m_CurrentMatchingArchetype->Archetype->EntityCount)
{
m_CurrentArchetypeEntityIndex += m_CurrentMatchingArchetype->Archetype->EntityCount;
m_CurrentMatchingArchetype = m_CurrentMatchingArchetype->Next;
m_CurrentChunk = (Chunk*) m_CurrentMatchingArchetype->Archetype->ChunkList.Begin;
m_CurrentChunkEntityIndex = 0;
}
//上面只是找到index所在的Archetype,别忘了一个Archetype里有可能有多个Chunk,
//所以还需要定位到对应的Chunk,逻辑和找对应Archetype时差不多
index -= m_CurrentArchetypeEntityIndex;
if (index < m_CurrentChunkEntityIndex)
{
m_CurrentChunk = (Chunk*) m_CurrentMatchingArchetype->Archetype->ChunkList.Begin;
m_CurrentChunkEntityIndex = 0;
}
while (index >= m_CurrentChunkEntityIndex + m_CurrentChunk->Count)
{
m_CurrentChunkEntityIndex += m_CurrentChunk->Count;
m_CurrentChunk = (Chunk*) m_CurrentChunk->ChunkListNode.Next;
}
}
else
{
//指定了Filter的情况,这里略过...
}
}
public void UpdateCacheToCurrentChunk(out ComponentChunkCache cache, bool isWriting, int indexInComponentGroup)
{
var archetype = m_CurrentMatchingArchetype->Archetype;
int indexInArchetype = m_CurrentMatchingArchetype->IndexInArchetype[indexInComponentGroup];
cache.CachedBeginIndex = m_CurrentChunkEntityIndex + m_CurrentArchetypeEntityIndex;
cache.CachedEndIndex = cache.CachedBeginIndex + m_CurrentChunk->Count;
cache.CachedSizeOf = archetype->SizeOfs[indexInArchetype];
//这个指针才是重点!指向了该组件类型在Chunk中的起始地址再减去在此之前的其它Chunk里组件数量之和*组件大小,具体看下面的例子
cache.CachedPtr = m_CurrentChunk->Buffer + archetype->Offsets[indexInArchetype] -
cache.CachedBeginIndex * cache.CachedSizeOf;
}
举个栗子:比如当前World中有三个Archetype分别包含的组件有:Arche1包含{A,B,C三组件},Arche2包含{G组件},Archetype3包含{A,G,B},那么满足条件的有两个Archetype1和3。所以m_FirstMatchingArchetype为Archetype1,m_FirstMatchingArchetype.Next为Archetype3。我们这里再假设Archetype1的EntityCount(Entity数量)为5个,Archetype3的为7个。
要知道ComponentDataArray在用CachedPtr时是直接用index的,该index前3个是在Archetype1的Chunk1上,第4~5个在其Chunk2上,6~9在Archetype3的Chunk1上,10~12在其Chunk2上。所以查询index 1时,这里的CachedPtr指向第一个Chunk[1]是可以的。但查询index 6时,CachedPtr指向的是Archetype3的Chunk1,CachedPtr[6]就会有问题了,因为Chunk1只有4个组件值(存放的是第6~9个组件的值共4个),所以CachedPtr指针地址需要减去该Chunk之前的所有Chunk包含的该类型组件之和*该组件的字节大小,以上述为例即Archetype1Chunk1和Archetype1Chunk2的EntityCount之和为(3+2)*sizeof(该组件类型),这样就可以对处理过的CachedPtr访问它的第"6"个元素了。
我们下一章再来看看m_FirstMatchingArchetype是怎么取得的:System注入组件信息