Unity2018 ECS框架Entities源码解析(三)ComponentDataArray的实现

23 篇文章 4 订阅
4 篇文章 1 订阅

一个普通的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注入组件信息

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值