APSchmidt 发表于 2022-10-2 17:38

MassSample的创建Entity

以MassSample项目为例,梳理创建Entity并且设置初始Fragments的流程。
一、创建Entity模板

创建Entity时需要一个创建模板类:FMassEntityTemplate。


1、通过UMassEntityConfigAsset创建FMassEntityTemplate类
Template = *MassEntityConfig->GetConfig().GetOrCreateEntityTemplate(*GameState,*MassEntityConfig);

首先是通过GetEntityTemplateInternal从缓存中取FMassEntityTemplate,具体的方式是UMassSpawnerSubsystem中成员变量UMassEntityTemplateRegistry中记录了一个Map,一个唯一id对应一个Template。如果缓存中没有,则首先调用Trait的两个默认函数,向FMassEntityTemplate里添加Fragment、Tag等组件或标签。然后再通过UMassEntityTemplateRegistry初始化Template。
void UMassEntityTemplateRegistry::InitializeEntityTemplate(FMassEntityTemplate& OutTemplate) const
{
        // expected to be ensured by the caller
        check(!OutTemplate.IsEmpty());

        UWorld* World = GetWorld();
        // find or create template
        UMassEntitySubsystem* EntitySys = UWorld::GetSubsystem<UMassEntitySubsystem>(World);
        check(EntitySys);

        // Sort anything there is to sort for later comparison purposes
        OutTemplate.Sort();

        const FMassArchetypeHandle ArchetypeHandle =
EntitySys->CreateArchetype(OutTemplate.GetCompositionDescriptor(), OutTemplate.GetSharedFragmentValues());
        OutTemplate.SetArchetype(ArchetypeHandle);
}CreateArchetype:首先从缓存中找有相同Fragment等参数的FMassArchetypeData,如果找不到,则创建一个FMassArchetypeData,并且添加到缓存中。FMassArchetypeData由一组唯一的fragment定义(没有重复),即一个组件的数组,拥有相同组件集合的Entity指向相同的Archetype,Archetype对应一个Chunk数组,Chunk是一个固定大小的混村空间,当一个Chunk空间用光了就多创建一个。FMassArchetypeData的初始化主要是初始化一些用来记录内存结构的变量。
二、创建Entity

通过UMassEntitySubsystem创建。这里涉及到双向添加,一是通过传进来的FMassArchetyoeHandle参数构造FEntityData,并添加到UMassEntitySubsystem的Entities数组中。另外也会把新创建出来的FMassEntityHandle添加到FMassArchetypeData中。
FMassArchetypeData::AddEntityInternal:1、找到可以添加的Chunk,如果有空的Chunk,就把空Chunk通过ChunkFragmentsTemplate(FMassArchetypeData的Fragments类型模板)重置一下,如果有没有填充满的Chunk,则取这个Chunk,并向这个Chunk添加实例。2、添加到EntityMap,EntityMap是的key和value都是int32类型,key值是FMassEntityHandle里的Index,这个Index是UMassEntitySubsystem在创建Entity时赋的值,和Entities的下标是一致的。Value是在所有Chunks里累计的Entity下标。3、把Entity添加到目标Chunk里面。这里向Chunk添加Entity的的方式注意一下,FMassArchetypeChunk的参数有5个:RawMemory一个指向Chunk的大小的指针,AllocSize是Chunk的大小,NumInstances是Chunk包含的Entity的个数,ChunkFragmentData是Chunk包含Fragment的类型。取对应下标的Entity是把RawMemory加上偏移转换成FMassEntityHandle的指针数组,然后取对应下标值。
DestinationChunk->GetEntityArrayElementRef(EntityListOffsetWithinChunk, IndexWithinChunk) = Entity;

// Returns the Entity array element at the specified index
FMassEntityHandle& GetEntityArrayElementRef(int32 ChunkBase, int32 IndexWithinChunk)
{
        return ((FMassEntityHandle*)(RawMemory + ChunkBase));
}

int32 FMassArchetypeData::AddEntityInternal(FMassEntityHandle Entity, const bool bInitializeFragments)
{
        int32 IndexWithinChunk = 0;
        int32 AbsoluteIndex = 0;
        int32 ChunkIndex = 0;
        int32 EmptyChunkIndex = INDEX_NONE;
        int32 EmptyAbsoluteIndex = INDEX_NONE;

        FMassArchetypeChunk* DestinationChunk = nullptr;
        // Check chunks for a free spot (trying to reuse the earlier ones first so later ones might get freed up)
        //@TODO: This could be accelerated to include a cached index to the first chunk with free spots or similar
        for (FMassArchetypeChunk& Chunk : Chunks)
        {
                if (Chunk.GetNumInstances() == 0)
                {
                        // Remember first empty chunk but continue looking for a chunk that has space and same group tag
                        if (EmptyChunkIndex == INDEX_NONE)
                        {
                                EmptyChunkIndex = ChunkIndex;
                                EmptyAbsoluteIndex = AbsoluteIndex;
                        }
                }
                else if (Chunk.GetNumInstances() < NumEntitiesPerChunk)
                {
                        IndexWithinChunk = Chunk.GetNumInstances();
                        AbsoluteIndex += IndexWithinChunk;

                        Chunk.AddInstance();

                        DestinationChunk = &Chunk;
                        break;
                }
                AbsoluteIndex += NumEntitiesPerChunk;
                ++ChunkIndex;
        }

        if (DestinationChunk == nullptr)
        {
                // Check if it is a recycled chunk
                if (EmptyChunkIndex != INDEX_NONE)
                {
                        DestinationChunk = &Chunks;
                        DestinationChunk->Recycle(ChunkFragmentsTemplate);
                        AbsoluteIndex = EmptyAbsoluteIndex;
                }
                else
                {
                        DestinationChunk = &Chunks.Emplace_GetRef(GetChunkAllocSize(), ChunkFragmentsTemplate);
                }

                check(DestinationChunk);
                DestinationChunk->AddInstance();
        }

        // Initialize the fragment memory
        if (bInitializeFragments)
        {
                for (const FMassArchetypeFragmentConfig& FragmentConfig : FragmentConfigs)
                {
                        void* FragmentPtr = FragmentConfig.GetFragmentData(DestinationChunk->GetRawMemory(), IndexWithinChunk);
                        FragmentConfig.FragmentType->InitializeStruct(FragmentPtr);
                }
        }

        // Add to the table and map
        EntityMap.Add(Entity.Index, AbsoluteIndex);
        DestinationChunk->GetEntityArrayElementRef(EntityListOffsetWithinChunk, IndexWithinChunk) = Entity;

        return AbsoluteIndex;
}三、给Entity设置Fragment

void UMassEntitySubsystem::SetEntityFragmentsValues(FMassEntityHandle Entity, TArrayView<const FInstancedStruct> FragmentInstanceList)
{
        CheckIfEntityIsActive(Entity);

        const FEntityData& EntityData = Entities;
        EntityData.CurrentArchetype->SetFragmentsData(Entity, FragmentInstanceList);
}从Chunks中取出对应类型的fragment,然后替换
void FMassArchetypeData::SetFragmentsData(const FMassEntityHandle Entity, TArrayView<const FInstancedStruct> FragmentInstances)
{
        FMassRawEntityInChunkData InternalIndex = MakeEntityHandle(Entity);

        for (const FInstancedStruct& Instance : FragmentInstances)
        {
                const UScriptStruct* FragmentType = Instance.GetScriptStruct();
                check(FragmentType);
                const int32 FragmentIndex = FragmentIndexMap.FindChecked(FragmentType);
                void* FragmentMemory = GetFragmentData(FragmentIndex, InternalIndex);
                // No UE::Mass::Core::bBitwiseRelocateFragments, this isn't a move fragment
                FragmentType->CopyScriptStruct(FragmentMemory, Instance.GetMemory());
        }
}
FORCEINLINE FMassRawEntityInChunkData MakeEntityHandle(int32 EntityIndex) const
{
      // Entity在所有Chunks里的下标
        const int32 AbsoluteIndex = EntityMap.FindChecked(EntityIndex);
      // 位于第几个chunk
        const int32 ChunkIndex = AbsoluteIndex / NumEntitiesPerChunk;
        // Entity所在Chunk的内存,Entity所在Chunk的下标
        return FMassRawEntityInChunkData(Chunks.GetRawMemory(), AbsoluteIndex % NumEntitiesPerChunk);
}

FORCEINLINE void* GetFragmentData(const int32 FragmentIndex, const FMassRawEntityInChunkData EntityIndex) const
{
        return FragmentConfigs.GetFragmentData(EntityIndex.ChunkRawMemory, EntityIndex.IndexWithinChunk);
}

void* GetFragmentData(uint8* ChunkBase, int32 IndexWithinChunk) const
{
      // 所在Chunk的首地址+在chunk的偏移+归属Entity的Index*Fragment的大小
        return ChunkBase + ArrayOffsetWithinChunk + (IndexWithinChunk * FragmentType->GetStructureSize());
}
页: [1]
查看完整版本: MassSample的创建Entity