// Copyright Epic Games, Inc. All Rights Reserved. #include "MassEntityManager.h" #include "MassEntityManagerConstants.h" #include "MassArchetypeData.h" #include "MassCommandBuffer.h" #include "MassEntityManagerStorage.h" #include "Engine/World.h" #include "UObject/UObjectIterator.h" #include "VisualLogger/VisualLogger.h" #include "MassExecutionContext.h" #include "MassDebugger.h" #include "Misc/Fork.h" #include "Misc/CoreDelegates.h" #include "Algo/Find.h" #include "MassEntityUtils.h" #include "MassEntityBuilder.h" #include "MassTypeManager.h" #include "MassProcessingContext.h" #include "MassObserverNotificationTypes.h" #define CHECK_SYNC_API() testableCheckf(IsProcessing() == false, TEXT("Synchronous API function %hs called during mass processing. Use asynchronous API instead."), __FUNCTION__) #define CHECK_SYNC_API_RETURN(ReturnValue) testableCheckfReturn(IsProcessing() == false, return ReturnValue, TEXT("Synchronous API function %hs called during mass processing. Use asynchronous API instead."), __FUNCTION__) const FMassEntityHandle FMassEntityManager::InvalidEntity; namespace UE::Mass::Private { // note: this function doesn't set EntityHandle.SerialNumber void ConvertArchetypelessSubchunksIntoEntityHandles(FMassArchetypeEntityCollection::FConstEntityRangeArrayView Subchunks, TArray& OutEntityHandles) { int32 TotalCount = 0; for (const FMassArchetypeEntityCollection::FArchetypeEntityRange& Subchunk : Subchunks) { TotalCount += Subchunk.Length; } int32 Index = OutEntityHandles.Num(); OutEntityHandles.AddDefaulted(TotalCount); for (const FMassArchetypeEntityCollection::FArchetypeEntityRange& Subchunk : Subchunks) { for (int i = Subchunk.SubchunkStart; i < Subchunk.SubchunkStart + Subchunk.Length; ++i) { OutEntityHandles[Index++].Index = i; } } } } //----------------------------------------------------------------------------- // FMassEntityManager //----------------------------------------------------------------------------- FMassEntityManager::FMassEntityManager(UObject* InOwner) : ObserverManager(*this) , TypeManager(new UE::Mass::FTypeManager(*this)) , Owner(InOwner) { #if WITH_MASSENTITY_DEBUG DebugName = InOwner ? (InOwner->GetName() + TEXT("_EntityManager")) : TEXT("Unset"); #endif } FMassEntityManager::~FMassEntityManager() { if (InitializationState == EInitializationState::Initialized) { Deinitialize(); } } void FMassEntityManager::GetResourceSizeEx(FResourceSizeEx& CumulativeResourceSize) { SIZE_T MyExtraSize = (InitializationState == EInitializationState::Initialized ? GetEntityStorageInterface().GetAllocatedSize() : 0) + FragmentHashToArchetypeMap.GetAllocatedSize() + FragmentTypeToArchetypeMap.GetAllocatedSize(); for (const TSharedPtr& CommandBuffer : DeferredCommandBuffers) { MyExtraSize += (CommandBuffer ? CommandBuffer->GetAllocatedSize() : 0); } CumulativeResourceSize.AddDedicatedSystemMemoryBytes(MyExtraSize); for (const auto& KVP : FragmentHashToArchetypeMap) { for (const TSharedPtr& ArchetypePtr : KVP.Value) { CumulativeResourceSize.AddDedicatedSystemMemoryBytes(ArchetypePtr->GetAllocatedSize()); } } } void FMassEntityManager::AddReferencedObjects(FReferenceCollector& Collector) { if (InitializationState == EInitializationState::Uninitialized) { UE_VLOG_UELOG(GetOwner(), LogMass, Log, TEXT("AddReferencedObjects called before Initialize call")); return; } if (InitializationState == EInitializationState::Deinitialized) { // this is AddReferencedObjects called after Deinitialize call, which means we don't want to retain any object refs // since this FMassEntityManager instance is going away even if it's kept alive by some stored shared refs at the moment. return; } for (FConstSharedStruct& Struct : ConstSharedFragmentsContainer.GetAllInstances()) { Struct.AddStructReferencedObjects(Collector); } for (FSharedStruct& Struct : SharedFragmentsContainer.GetAllInstances()) { Struct.AddStructReferencedObjects(Collector); } const class UScriptStruct* ScriptStruct = FMassObserverManager::StaticStruct(); TWeakObjectPtr ScriptStructPtr{ScriptStruct}; Collector.AddReferencedObjects(ScriptStructPtr, &ObserverManager); } void FMassEntityManager::Initialize() { FMassEntityManagerStorageInitParams InitializationParams; InitializationParams.Emplace(); Initialize(InitializationParams); } namespace UE::Mass::Private { struct FEntityStorageInitializer { void operator()(const FMassEntityManager_InitParams_SingleThreaded& Params) { EntityStorage->Emplace(); EntityStorage->Get().Initialize(Params); } void operator()(const FMassEntityManager_InitParams_Concurrent& Params) { #if WITH_MASS_CONCURRENT_RESERVE EntityStorage->Emplace(); EntityStorage->Get().Initialize(Params); #else checkf(false, TEXT("Mass does not support this storage backend")); #endif } FMassEntityManager::FEntityStorageContainerType* EntityStorage = nullptr; }; } void FMassEntityManager::Initialize(const FMassEntityManagerStorageInitParams& InitializationParams) { if (InitializationState == EInitializationState::Initialized) { UE_VLOG_UELOG(GetOwner(), LogMass, Log, TEXT("Calling %hs on already initialized entity manager owned by %s") , __FUNCTION__, *GetNameSafe(Owner.Get())); return; } LLM_SCOPE_BYNAME(TEXT("Mass/EntityManager")); Visit(UE::Mass::Private::FEntityStorageInitializer{&EntityStorage}, InitializationParams); #if WITH_MASSENTITY_DEBUG DebugEntityStoragePtr = &DebugGetEntityStorageInterface(); #endif // WITH_MASSENTITY_DEBUG for (TSharedPtr& CommandBuffer : DeferredCommandBuffers) { CommandBuffer = MakeShareable(new FMassCommandBuffer()); } // if we get forked we need to update the command buffer's CurrentThreadID if (FForkProcessHelper::IsForkRequested()) { OnPostForkHandle = FCoreDelegates::OnPostFork.AddSP(AsShared(), &FMassEntityManager::OnPostFork); } // creating these bitset instances to populate respective bitset types' StructTrackers FMassFragmentBitSet Fragments; FMassTagBitSet Tags; FMassChunkFragmentBitSet ChunkFragments; FMassSharedFragmentBitSet LocalSharedFragments; FMassConstSharedFragmentBitSet LocalConstSharedFragments; for (TObjectIterator StructIt; StructIt; ++StructIt) { if (UE::Mass::IsA(*StructIt)) { if (*StructIt != FMassFragment::StaticStruct()) { Fragments.Add(**StructIt); } } else if (UE::Mass::IsA(*StructIt)) { if (*StructIt != FMassTag::StaticStruct()) { Tags.Add(**StructIt); } } else if (UE::Mass::IsA(*StructIt)) { if (*StructIt != FMassChunkFragment::StaticStruct()) { ChunkFragments.Add(**StructIt); } } else if (UE::Mass::IsA(*StructIt)) { if (*StructIt != FMassSharedFragment::StaticStruct()) { LocalSharedFragments.Add(**StructIt); } } else if (UE::Mass::IsA(*StructIt)) { if (*StructIt != FMassConstSharedFragment::StaticStruct()) { LocalConstSharedFragments.Add(**StructIt); } } } #if WITH_MASSENTITY_DEBUG RequirementAccessDetector.Initialize(); FMassDebugger::RegisterEntityManager(*this); #endif // WITH_MASSENTITY_DEBUG InitializationState = EInitializationState::Initialized; bFirstCommandFlush = true; } void FMassEntityManager::PostInitialize() { ensureMsgf(InitializationState == EInitializationState::Initialized, TEXT("This needs to be done after all the subsystems have been initialized since some processors might want to access" " them during processors' initialization")); ObserverManager.Initialize(); } void FMassEntityManager::Deinitialize() { if (InitializationState == EInitializationState::Initialized) { FCoreDelegates::OnPostFork.Remove(OnPostForkHandle); // closing down so no point in actually flushing commands, but need to clean them up to avoid warnings on destruction for (TSharedPtr& CommandBuffer : DeferredCommandBuffers) { if (CommandBuffer) { CommandBuffer->CleanUp(); } } #if WITH_MASSENTITY_DEBUG FMassDebugger::UnregisterEntityManager(*this); #endif // WITH_MASSENTITY_DEBUG EntityStorage.Emplace(); ObserverManager.DeInitialize(); InitializationState = EInitializationState::Deinitialized; } else { UE_VLOG_UELOG(GetOwner(), LogMass, Log, TEXT("Calling %hs on already deinitialized entity manager owned by %s") , __FUNCTION__, *GetNameSafe(Owner.Get())); } } void FMassEntityManager::OnPostFork(EForkProcessRole Role) { if (Role == EForkProcessRole::Child) { LLM_SCOPE_BYNAME(TEXT("Mass/EntityManager")); for (TSharedPtr& CommandBuffer : DeferredCommandBuffers) { if (CommandBuffer) { CommandBuffer->ForceUpdateCurrentThreadID(); } else { CommandBuffer = MakeShareable(new FMassCommandBuffer()); } } ObserverManager.OnPostFork(Role); } } FMassArchetypeHandle FMassEntityManager::CreateArchetype(TConstArrayView FragmentsAndTagsList, const FMassArchetypeCreationParams& CreationParams) { FMassArchetypeCompositionDescriptor Composition; InternalAppendFragmentsAndTagsToArchetypeCompositionDescriptor(Composition, FragmentsAndTagsList); return CreateArchetype(Composition, CreationParams); } FMassArchetypeHandle FMassEntityManager::CreateArchetype(FMassArchetypeHandle SourceArchetype, TConstArrayView FragmentsAndTagsList) { const FMassArchetypeData& ArchetypeData = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(SourceArchetype); return CreateArchetype(SourceArchetype, FragmentsAndTagsList, FMassArchetypeCreationParams(ArchetypeData)); } FMassArchetypeHandle FMassEntityManager::CreateArchetype(FMassArchetypeHandle SourceArchetype, TConstArrayView FragmentsAndTagsList, const FMassArchetypeCreationParams& CreationParams) { const FMassArchetypeData& ArchetypeData = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(SourceArchetype); FMassArchetypeCompositionDescriptor Composition = ArchetypeData.GetCompositionDescriptor(); InternalAppendFragmentsAndTagsToArchetypeCompositionDescriptor(Composition, FragmentsAndTagsList); return CreateArchetype(Composition, CreationParams); } FMassArchetypeHandle FMassEntityManager::CreateArchetype(const TSharedPtr& SourceArchetype, const FMassFragmentBitSet& AddedFragments) { return CreateArchetype(SourceArchetype, AddedFragments, FMassArchetypeCreationParams(*SourceArchetype)); } FMassArchetypeHandle FMassEntityManager::CreateArchetype(const TSharedPtr& SourceArchetype, const FMassFragmentBitSet& AddedFragments, const FMassArchetypeCreationParams& CreationParams) { check(SourceArchetype.IsValid()); checkf(AddedFragments.IsEmpty() == false, TEXT("%hs Adding an empty fragment list to an archetype is not supported."), __FUNCTION__); const FMassArchetypeCompositionDescriptor Composition(AddedFragments + SourceArchetype->GetFragmentBitSet() , SourceArchetype->GetTagBitSet() , SourceArchetype->GetChunkFragmentBitSet() , SourceArchetype->GetSharedFragmentBitSet() , SourceArchetype->GetConstSharedFragmentBitSet()); return CreateArchetype(Composition, CreationParams); } FMassArchetypeHandle FMassEntityManager::GetOrCreateSuitableArchetype(const FMassArchetypeHandle& ArchetypeHandle , const FMassSharedFragmentBitSet& SharedFragmentBitSet , const FMassConstSharedFragmentBitSet& ConstSharedFragmentBitSet , const FMassArchetypeCreationParams& CreationParams) { const FMassArchetypeData& ArchetypeData = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(ArchetypeHandle); if (SharedFragmentBitSet != ArchetypeData.GetSharedFragmentBitSet() || ConstSharedFragmentBitSet != ArchetypeData.GetConstSharedFragmentBitSet()) { FMassArchetypeCompositionDescriptor NewDescriptor = ArchetypeData.GetCompositionDescriptor(); NewDescriptor.SharedFragments = SharedFragmentBitSet; NewDescriptor.ConstSharedFragments = ConstSharedFragmentBitSet; return CreateArchetype(NewDescriptor, CreationParams); } return ArchetypeHandle; } FMassArchetypeHandle FMassEntityManager::CreateArchetype(const FMassArchetypeCompositionDescriptor& Composition, const FMassArchetypeCreationParams& CreationParams) { LLM_SCOPE_BYNAME(TEXT("Mass/EntityManager")); const uint32 TypeHash = HashCombine(Composition.CalculateHash(), GetTypeHash(UE::Mass::FArchetypeGroups())); TArray>& HashRow = FragmentHashToArchetypeMap.FindOrAdd(TypeHash); TSharedPtr ArchetypeDataPtr; for (const TSharedPtr& Ptr : HashRow) { if (Ptr->IsEquivalent(Composition, /*Groups=*/{})) { #if WITH_MASSENTITY_DEBUG // Keep track of all names for this archetype. if (!CreationParams.DebugName.IsNone()) { Ptr->AddUniqueDebugName(CreationParams.DebugName); } #endif // WITH_MASSENTITY_DEBUG if (CreationParams.ChunkMemorySize > 0 && CreationParams.ChunkMemorySize != Ptr->GetChunkAllocSize()) { UE_LOG(LogMass, Warning, TEXT("Reusing existing Archetype, but the requested ChunkMemorySize is different. Requested %d, existing: %llu") , CreationParams.ChunkMemorySize, Ptr->GetChunkAllocSize()); } ArchetypeDataPtr = Ptr; break; } } if (!ArchetypeDataPtr.IsValid()) { // Important to pre-increment the version as the queries will use this value to do incremental updates ++ArchetypeDataVersion; // Create a new archetype FMassArchetypeData* NewArchetype = new FMassArchetypeData(CreationParams); NewArchetype->Initialize(*this, Composition, ArchetypeDataVersion); ArchetypeDataPtr = HashRow.Add_GetRef(MakeShareable(NewArchetype)); AllArchetypes.Add(ArchetypeDataPtr); ensure(AllArchetypes.Num() == ArchetypeDataVersion); for (const FMassArchetypeFragmentConfig& FragmentConfig : NewArchetype->GetFragmentConfigs()) { checkSlow(FragmentConfig.FragmentType) FragmentTypeToArchetypeMap.FindOrAdd(FragmentConfig.FragmentType).Add(ArchetypeDataPtr); } OnNewArchetypeEvent.Broadcast(FMassArchetypeHandle(ArchetypeDataPtr)); UE_TRACE_MASS_ARCHETYPE_CREATED(ArchetypeDataPtr) } return FMassArchetypeHelper::ArchetypeHandleFromData(ArchetypeDataPtr); } FMassArchetypeHandle FMassEntityManager::InternalCreateSimilarArchetype(const TSharedPtr& SourceArchetype, const FMassTagBitSet& OverrideTags) { checkSlow(SourceArchetype.IsValid()); const FMassArchetypeData& SourceArchetypeRef = *SourceArchetype.Get(); FMassArchetypeCompositionDescriptor NewComposition(SourceArchetypeRef.GetFragmentBitSet() , OverrideTags , SourceArchetypeRef.GetChunkFragmentBitSet() , SourceArchetypeRef.GetSharedFragmentBitSet() , SourceArchetypeRef.GetConstSharedFragmentBitSet()); return InternalCreateSimilarArchetype(SourceArchetypeRef, MoveTemp(NewComposition), SourceArchetypeRef.GetGroups()); } FMassArchetypeHandle FMassEntityManager::InternalCreateSimilarArchetype(const TSharedPtr& SourceArchetype, const FMassFragmentBitSet& OverrideFragments) { checkSlow(SourceArchetype.IsValid()); const FMassArchetypeData& SourceArchetypeRef = *SourceArchetype.Get(); FMassArchetypeCompositionDescriptor NewComposition(OverrideFragments , SourceArchetypeRef.GetTagBitSet() , SourceArchetypeRef.GetChunkFragmentBitSet() , SourceArchetypeRef.GetSharedFragmentBitSet() , SourceArchetypeRef.GetConstSharedFragmentBitSet()); return InternalCreateSimilarArchetype(SourceArchetypeRef, MoveTemp(NewComposition), SourceArchetypeRef.GetGroups()); } FMassArchetypeHandle FMassEntityManager::InternalCreateSimilarArchetype(const TSharedPtr& SourceArchetype, const UE::Mass::FArchetypeGroups& GroupsOverride) { checkSlow(SourceArchetype.IsValid()); const FMassArchetypeData& SourceArchetypeRef = *SourceArchetype.Get(); FMassArchetypeCompositionDescriptor NewComposition = SourceArchetype->GetCompositionDescriptor(); return InternalCreateSimilarArchetype(SourceArchetypeRef, MoveTemp(NewComposition), GroupsOverride); } FMassArchetypeHandle FMassEntityManager::InternalCreateSimilarArchetype(const FMassArchetypeData& SourceArchetypeRef, FMassArchetypeCompositionDescriptor&& NewComposition, const UE::Mass::FArchetypeGroups& Groups) { LLM_SCOPE_BYNAME(TEXT("Mass/EntityManager")); // we require Groups to be already shrunk. Shrinking is required to remove any trailing, invalid group IDs that would // be there if IDs were added and removed to this specific Groups container instance checkf(Groups.IsShrunk(), TEXT("A group container with invalid trailing IDs has been passed to archetype creation - this is not expected and will cause issues. Make sure to Shrink your Groups before passing to %hs"), __FUNCTION__); const uint32 TypeHash = HashCombine(NewComposition.CalculateHash(), GetTypeHash(Groups)); TArray>& HashRow = FragmentHashToArchetypeMap.FindOrAdd(TypeHash); TSharedPtr ArchetypeDataPtr; for (const TSharedPtr& Ptr : HashRow) { if (Ptr->IsEquivalent(NewComposition, Groups)) { ArchetypeDataPtr = Ptr; break; } } if (!ArchetypeDataPtr.IsValid()) { // Important to pre-increment the version as the queries will use this value to do incremental updates ++ArchetypeDataVersion; // Create a new archetype FMassArchetypeData* NewArchetype = new FMassArchetypeData(FMassArchetypeCreationParams(SourceArchetypeRef)); NewArchetype->InitializeWithSimilar(*this, SourceArchetypeRef, MoveTemp(NewComposition), Groups, ArchetypeDataVersion); NewArchetype->CopyDebugNamesFrom(SourceArchetypeRef); ArchetypeDataPtr = HashRow.Add_GetRef(MakeShareable(NewArchetype)); AllArchetypes.Add(ArchetypeDataPtr); ensure(AllArchetypes.Num() == ArchetypeDataVersion); for (const FMassArchetypeFragmentConfig& FragmentConfig : NewArchetype->GetFragmentConfigs()) { checkSlow(FragmentConfig.FragmentType) FragmentTypeToArchetypeMap.FindOrAdd(FragmentConfig.FragmentType).Add(ArchetypeDataPtr); } OnNewArchetypeEvent.Broadcast(FMassArchetypeHandle(ArchetypeDataPtr)); UE_TRACE_MASS_ARCHETYPE_CREATED(ArchetypeDataPtr) } return FMassArchetypeHelper::ArchetypeHandleFromData(ArchetypeDataPtr); } void FMassEntityManager::InternalAppendFragmentsAndTagsToArchetypeCompositionDescriptor( FMassArchetypeCompositionDescriptor& InOutComposition, TConstArrayView FragmentsAndTagsList) const { for (const UScriptStruct* Type : FragmentsAndTagsList) { if (UE::Mass::IsA(Type)) { InOutComposition.Fragments.Add(*Type); } else if (UE::Mass::IsA(Type)) { InOutComposition.Tags.Add(*Type); } else if (UE::Mass::IsA(Type)) { InOutComposition.ChunkFragments.Add(*Type); } else { UE_LOG(LogMass, Warning, TEXT("%hs: %s is not a valid fragment nor tag type. Ignoring.") , __FUNCTION__, *GetNameSafe(Type)); } } } FMassArchetypeHandle FMassEntityManager::GetArchetypeForEntity(FMassEntityHandle Entity) const { if (IsEntityValid(Entity)) { return FMassArchetypeHelper::ArchetypeHandleFromData(GetEntityStorageInterface().GetArchetypeAsShared(Entity.Index)); } return FMassArchetypeHandle(); } FMassArchetypeHandle FMassEntityManager::GetArchetypeForEntityUnsafe(FMassEntityHandle Entity) const { check(GetEntityStorageInterface().IsValidIndex(Entity.Index)); return FMassArchetypeHelper::ArchetypeHandleFromData(GetEntityStorageInterface().GetArchetypeAsShared(Entity.Index)); } void FMassEntityManager::GetMatchingArchetypes(const FMassFragmentRequirements& Requirements, TArray& OutValidArchetypes) const { GetMatchingArchetypes(Requirements, OutValidArchetypes, 0); } void FMassEntityManager::ForEachArchetypeFragmentType(const FMassArchetypeHandle& ArchetypeHandle, TFunction< void(const UScriptStruct* /*FragmentType*/)> Function) { const FMassArchetypeData& ArchetypeData = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(ArchetypeHandle); ArchetypeData.ForEachFragmentType(Function); } void FMassEntityManager::DoEntityCompaction(const double TimeAllowed) { int32 TotalEntitiesMoved = 0; const double TimeAllowedEnd = FPlatformTime::Seconds() + TimeAllowed; bool bReachedTimeLimit = false; for (const auto& KVP : FragmentHashToArchetypeMap) { for (const TSharedPtr& ArchetypePtr : KVP.Value) { const double TimeAllowedLeft = TimeAllowedEnd - FPlatformTime::Seconds(); bReachedTimeLimit = TimeAllowedLeft <= 0.0; if (bReachedTimeLimit) { break; } TotalEntitiesMoved += ArchetypePtr->CompactEntities(TimeAllowedLeft); } if (bReachedTimeLimit) { break; } } UE_CVLOG(TotalEntitiesMoved, GetOwner(), LogMass, Verbose, TEXT("Entity Compaction: moved %d entities"), TotalEntitiesMoved); } FMassEntityHandle FMassEntityManager::CreateEntity(const FMassArchetypeHandle& ArchetypeHandle, const FMassArchetypeSharedFragmentValues& SharedFragmentValues) { CHECK_SYNC_API_RETURN({}); check(ArchetypeHandle.IsValid()); const FMassEntityHandle Entity = ReserveEntity(); InternalBuildEntity(Entity , GetOrCreateSuitableArchetype(ArchetypeHandle, SharedFragmentValues.GetSharedFragmentBitSet(), SharedFragmentValues.GetConstSharedFragmentBitSet()) , SharedFragmentValues); return Entity; } FMassEntityHandle FMassEntityManager::CreateEntity(TConstArrayView FragmentInstanceList, const FMassArchetypeSharedFragmentValues& SharedFragmentValues, const FMassArchetypeCreationParams& CreationParams) { CHECK_SYNC_API_RETURN({}); check(FragmentInstanceList.Num() > 0); const FMassArchetypeHandle& ArchetypeHandle = CreateArchetype(FMassArchetypeCompositionDescriptor(FragmentInstanceList, FMassTagBitSet(), FMassChunkFragmentBitSet(), FMassSharedFragmentBitSet(), FMassConstSharedFragmentBitSet()), CreationParams); check(ArchetypeHandle.IsValid()); const FMassEntityHandle Entity = ReserveEntity(); // Using a creation context to prevent InternalBuildEntity from notifying observers before we set fragments data const TSharedRef CreationContext = ObserverManager.GetOrMakeCreationContext(); InternalBuildEntity(Entity, ArchetypeHandle, SharedFragmentValues); FMassArchetypeData* CurrentArchetype = GetEntityStorageInterface().GetArchetype(Entity.Index); check(CurrentArchetype); CurrentArchetype->SetFragmentsData(Entity, FragmentInstanceList); return Entity; } FMassEntityHandle FMassEntityManager::ReserveEntity() { FMassEntityHandle Result = GetEntityStorageInterface().AcquireOne(); return Result; } void FMassEntityManager::ReleaseReservedEntity(FMassEntityHandle Entity) { checkf(!IsEntityBuilt(Entity), TEXT("Entity is already built, use DestroyEntity() instead")); InternalReleaseEntity(Entity); } void FMassEntityManager::BuildEntity(FMassEntityHandle Entity, const FMassArchetypeHandle& ArchetypeHandle, const FMassArchetypeSharedFragmentValues& SharedFragmentValues) { CHECK_SYNC_API(); checkf(!IsEntityBuilt(Entity), TEXT("Expecting an entity that is not already built")); check(ArchetypeHandle.IsValid()); InternalBuildEntity(Entity, ArchetypeHandle, SharedFragmentValues); } void FMassEntityManager::BuildEntity(FMassEntityHandle Entity, TConstArrayView FragmentInstanceList, const FMassArchetypeSharedFragmentValues& SharedFragmentValues) { CHECK_SYNC_API(); check(FragmentInstanceList.Num() > 0); checkf(!IsEntityBuilt(Entity), TEXT("Expecting an entity that is not already built")); checkf(SharedFragmentValues.IsSorted(), TEXT("Expecting shared fragment values to be previously sorted")); FMassArchetypeCompositionDescriptor Composition(FragmentInstanceList, FMassTagBitSet(), FMassChunkFragmentBitSet(), FMassSharedFragmentBitSet(), FMassConstSharedFragmentBitSet()); for (const FConstSharedStruct& SharedFragment : SharedFragmentValues.GetConstSharedFragments()) { Composition.ConstSharedFragments.Add(*SharedFragment.GetScriptStruct()); } for (const FSharedStruct& SharedFragment : SharedFragmentValues.GetSharedFragments()) { Composition.SharedFragments.Add(*SharedFragment.GetScriptStruct()); } const FMassArchetypeHandle& ArchetypeHandle = CreateArchetype(Composition); check(ArchetypeHandle.IsValid()); // Using a creation context to prevent InternalBuildEntity from notifying observers before we set fragments data const TSharedRef CreationContext = ObserverManager.GetOrMakeCreationContext(); InternalBuildEntity(Entity, ArchetypeHandle, SharedFragmentValues); FMassArchetypeData* CurrentArchetype = GetEntityStorageInterface().GetArchetype(Entity.Index); check(CurrentArchetype); CurrentArchetype->SetFragmentsData(Entity, FragmentInstanceList); } TConstArrayView FMassEntityManager::BatchReserveEntities(const int32 Count, TArray& InOutEntities) { const int32 Index = InOutEntities.Num(); const int32 NumAdded = GetEntityStorageInterface().Acquire(Count, InOutEntities); ensureMsgf(NumAdded == Count, TEXT("Failed to reserve %d entities, was able to only reserve %d"), Count, NumAdded); return MakeArrayView(InOutEntities.GetData() + Index, NumAdded); } int32 FMassEntityManager::BatchReserveEntities(TArrayView InOutEntities) { return GetEntityStorageInterface().Acquire(InOutEntities); } TSharedRef FMassEntityManager::BatchBuildEntities(const FMassArchetypeEntityCollectionWithPayload& EncodedEntitiesWithPayload , const FMassFragmentBitSet& FragmentsAffected, const FMassArchetypeSharedFragmentValues& SharedFragmentValues, const FMassArchetypeCreationParams& CreationParams) { CHECK_SYNC_API_RETURN(FMassObserverManager::FCreationContext::DebugCreateDummyCreationContext()); check(SharedFragmentValues.IsSorted()); FMassArchetypeCompositionDescriptor Composition(FragmentsAffected, FMassTagBitSet(), FMassChunkFragmentBitSet(), FMassSharedFragmentBitSet(), FMassConstSharedFragmentBitSet()); for (const FConstSharedStruct& SharedFragment : SharedFragmentValues.GetConstSharedFragments()) { Composition.ConstSharedFragments.Add(*SharedFragment.GetScriptStruct()); } for (const FSharedStruct& SharedFragment : SharedFragmentValues.GetSharedFragments()) { Composition.SharedFragments.Add(*SharedFragment.GetScriptStruct()); } return BatchBuildEntities(EncodedEntitiesWithPayload, MoveTemp(Composition), SharedFragmentValues, CreationParams); } TSharedRef FMassEntityManager::BatchBuildEntities(const FMassArchetypeEntityCollectionWithPayload& EncodedEntitiesWithPayload , const FMassArchetypeCompositionDescriptor& Composition , const FMassArchetypeSharedFragmentValues& SharedFragmentValues, const FMassArchetypeCreationParams& CreationParams) { CHECK_SYNC_API_RETURN(FMassObserverManager::FCreationContext::DebugCreateDummyCreationContext()); TRACE_CPUPROFILER_EVENT_SCOPE(Mass_BatchBuildEntities); FMassArchetypeEntityCollection::FEntityRangeArray TargetArchetypeEntityRanges; // "built" entities case, this is verified during FMassArchetypeEntityCollectionWithPayload construction FMassArchetypeHandle TargetArchetypeHandle = CreateArchetype(Composition, CreationParams); check(TargetArchetypeHandle.IsValid()); // there are some extra steps in creating EncodedEntities from the original given entity handles and then back // to handles here, but this way we're consistent in how stuff is handled, and there are some slight benefits // to having entities ordered by their index (like accessing the Entities data below). TArray EntityHandles; UE::Mass::Private::ConvertArchetypelessSubchunksIntoEntityHandles(EncodedEntitiesWithPayload.GetEntityCollection().GetRanges(), EntityHandles); // since the handles encoded via FMassArchetypeEntityCollectionWithPayload miss the SerialNumber we need to update it // before passing over the new archetype. Thankfully we need to iterate over all the entity handles anyway // to update the manager's information on these entities (stored in FMassEntityManager::Entities) for (FMassEntityHandle& Entity : EntityHandles) { check(GetEntityStorageInterface().IsValidIndex(Entity.Index)); const UE::Mass::IEntityStorageInterface::EEntityState EntityState = GetEntityStorageInterface().GetEntityState(Entity.Index); checkf(EntityState == UE::Mass::IEntityStorageInterface::EEntityState::Reserved, TEXT("Trying to build entities that are not reserved. Check all handles are reserved or consider using BatchCreateEntities")); const int32 SerialNumber = GetEntityStorageInterface().GetSerialNumber(Entity.Index); Entity.SerialNumber = SerialNumber; GetEntityStorageInterface().SetArchetypeFromShared(Entity.Index, TargetArchetypeHandle.DataPtr); } TargetArchetypeHandle.DataPtr->BatchAddEntities(EntityHandles, SharedFragmentValues, TargetArchetypeEntityRanges); UE_TRACE_MASS_ENTITIES_CREATED(EntityHandles, *TargetArchetypeHandle.DataPtr.Get()) if (EncodedEntitiesWithPayload.GetPayload().IsEmpty() == false) { // at this point all the entities are in the target archetype, we can set the values // note that even though the "subchunk" information could have changed the order of entities is the same and // corresponds to the order in FMassArchetypeEntityCollectionWithPayload's payload TargetArchetypeHandle.DataPtr->BatchSetFragmentValues(TargetArchetypeEntityRanges, EncodedEntitiesWithPayload.GetPayload()); } // With this call we're either creating a fresh context populated with EntityHandles, or it will append // EntityHandles to active context. // Not creating the context sooner since we want to reuse TargetArchetypeEntityRanges by moving it over to the context. // Note that we can afford to create this context so late since all previous operations were on the archetype level // and as such won't cause observers triggering (which usually is prevented by context's existence), and that we // strongly assume the all entity creation/building (not to be mistaken with "reserving") takes place in a single thread // @todo add checks/ensures enforcing the assumption mentioned above. return ObserverManager.GetOrMakeCreationContext(EntityHandles, FMassArchetypeEntityCollection(TargetArchetypeHandle, MoveTemp(TargetArchetypeEntityRanges))); } TSharedRef FMassEntityManager::BatchCreateReservedEntities(const FMassArchetypeHandle& ArchetypeHandle , const FMassArchetypeSharedFragmentValues& SharedFragmentValues, TConstArrayView ReservedEntities) { CHECK_SYNC_API_RETURN(FMassObserverManager::FCreationContext::DebugCreateDummyCreationContext()); checkf(!ReservedEntities.IsEmpty(), TEXT("No reserved entities given to batch create.")); TRACE_CPUPROFILER_EVENT_SCOPE(Mass_BatchCreateReservedEntities); return InternalBatchCreateReservedEntities( GetOrCreateSuitableArchetype(ArchetypeHandle, SharedFragmentValues.GetSharedFragmentBitSet(), SharedFragmentValues.GetConstSharedFragmentBitSet()) , SharedFragmentValues, ReservedEntities); } TSharedRef FMassEntityManager::BatchCreateEntities(const FMassArchetypeHandle& ArchetypeHandle , const FMassArchetypeSharedFragmentValues& SharedFragmentValues, const int32 Count, TArray& InOutEntities) { CHECK_SYNC_API_RETURN(FMassObserverManager::FCreationContext::DebugCreateDummyCreationContext()); testableCheckfReturn(ArchetypeHandle.IsValid(), return FMassObserverManager::FCreationContext::DebugCreateDummyCreationContext() , TEXT("%hs expecting a valid ArchetypeHandle"), __FUNCTION__); TRACE_CPUPROFILER_EVENT_SCOPE(Mass_BatchCreateEntities); TConstArrayView ReservedEntities = BatchReserveEntities(Count, InOutEntities); return InternalBatchCreateReservedEntities( GetOrCreateSuitableArchetype(ArchetypeHandle, SharedFragmentValues.GetSharedFragmentBitSet(), SharedFragmentValues.GetConstSharedFragmentBitSet()) , SharedFragmentValues, ReservedEntities); } TSharedRef FMassEntityManager::InternalBatchCreateReservedEntities(const FMassArchetypeHandle& ArchetypeHandle , const FMassArchetypeSharedFragmentValues& SharedFragmentValues, TConstArrayView ReservedEntities) { // Functions calling into this one are required to verify that the archetype handle is valid FMassArchetypeData* ArchetypeData = FMassArchetypeHelper::ArchetypeDataFromHandle(ArchetypeHandle); checkf(ArchetypeData, TEXT("Functions calling into this one are required to verify that the archetype handle is valid")); for (FMassEntityHandle Entity : ReservedEntities) { check(IsEntityValid(Entity)); const UE::Mass::IEntityStorageInterface::EEntityState EntityState = GetEntityStorageInterface().GetEntityState(Entity.Index); checkf(EntityState == UE::Mass::IEntityStorageInterface::EEntityState::Reserved, TEXT("Trying to build entities that are not reserved. Check all handles are reserved or consider using BatchCreateEntities")); GetEntityStorageInterface().SetArchetypeFromShared(Entity.Index, ArchetypeHandle.DataPtr); } FMassArchetypeEntityCollection::FEntityRangeArray TargetArchetypeEntityRanges; ArchetypeData->BatchAddEntities(ReservedEntities, SharedFragmentValues, TargetArchetypeEntityRanges); UE_TRACE_MASS_ENTITIES_CREATED(ReservedEntities, *ArchetypeData) return ObserverManager.GetOrMakeCreationContext(ReservedEntities, FMassArchetypeEntityCollection(ArchetypeHandle, MoveTemp(TargetArchetypeEntityRanges))); } void FMassEntityManager::DestroyEntity(FMassEntityHandle Entity) { CHECK_SYNC_API(); CheckIfEntityIsActive(Entity); FMassArchetypeData* Archetype = GetEntityStorageInterface().GetArchetype(Entity.Index); if (Archetype) { ObserverManager.OnPreEntityDestroyed(Archetype->GetCompositionDescriptor(), Entity); Archetype->RemoveEntity(Entity); } UE_TRACE_MASS_ENTITY_DESTROYED(Entity) InternalReleaseEntity(Entity); } void FMassEntityManager::BatchDestroyEntities(TConstArrayView InEntities) { CHECK_SYNC_API(); checkf(ObserverManager.IsLocked() == false, TEXT("%hs: Trying to destroy entities while observers are locked - remove-observers won't get triggered in time to read fragments being removed."), __FUNCTION__); TRACE_CPUPROFILER_EVENT_SCOPE(Mass_BatchDestroyEntities); for (const FMassEntityHandle Entity : InEntities) { if (GetEntityStorageInterface().IsValidIndex(Entity.Index) == false) { continue; } const int32 SerialNumber = GetEntityStorageInterface().GetSerialNumber(Entity.Index); if (SerialNumber != Entity.SerialNumber) { continue; } if (FMassArchetypeData* Archetype = GetEntityStorageInterface().GetArchetype(Entity.Index)) { ObserverManager.OnPreEntityDestroyed(Archetype->GetCompositionDescriptor(), Entity); Archetype->RemoveEntity(Entity); } // else it's a "reserved" entity so it has not been assigned to an archetype yet, no archetype nor observers to notify } UE_TRACE_MASS_ENTITIES_DESTROYED(InEntities) GetEntityStorageInterface().Release(InEntities); } void FMassEntityManager::BatchDestroyEntityChunks(const FMassArchetypeEntityCollection& EntityCollection) { BatchDestroyEntityChunks(MakeArrayView(&EntityCollection, 1)); } void FMassEntityManager::BatchDestroyEntityChunks(TConstArrayView Collections) { CHECK_SYNC_API(); checkf(ObserverManager.IsLocked() == false, TEXT("%hs: Trying to destroy entities while observers are locked - remove-observers won't get triggered in time to read fragments being removed."), __FUNCTION__); TRACE_CPUPROFILER_EVENT_SCOPE(Mass_BatchDestroyEntityChunks); TArray EntitiesRemoved; FMassProcessingContext ProcessingContext(*this); for (const FMassArchetypeEntityCollection& EntityCollection : Collections) { EntitiesRemoved.Reset(); if (ensureMsgf(EntityCollection.GetArchetype().IsValid() && EntityCollection.IsUpToDate(), TEXT("Provided collection is out of data"))) { ObserverManager.OnPreEntitiesDestroyed(ProcessingContext, EntityCollection); checkf(EntityCollection.IsUpToDate(), TEXT("Remove-type observers resulted in additional mutating of entity composition. This is not allowed.")) FMassArchetypeData& ArchetypeData = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(EntityCollection.GetArchetype()); ArchetypeData.BatchDestroyEntityChunks(EntityCollection.GetRanges(), EntitiesRemoved); GetEntityStorageInterface().Release(EntitiesRemoved); } else { UE::Mass::Private::ConvertArchetypelessSubchunksIntoEntityHandles(EntityCollection.GetRanges(), EntitiesRemoved); GetEntityStorageInterface().ForceRelease(EntitiesRemoved); } } } void FMassEntityManager::BatchGroupEntities(const UE::Mass::FArchetypeGroupHandle GroupHandle, TConstArrayView Collections) { CHECK_SYNC_API(); if (GroupHandle.IsValid() == false) { UE_LOG(LogMass, Warning, TEXT("%hs called with an invalid GroupHandle"), __FUNCTION__); return; } TArray EntitiesBeingMoved; for (const FMassArchetypeEntityCollection& EntityCollection : Collections) { if (EntityCollection.GetArchetype().IsValid()) { FMassArchetypeData& CurrentArchetype = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(EntityCollection.GetArchetype()); if (CurrentArchetype.IsInGroup(GroupHandle) == false) { UE::Mass::FArchetypeGroups NewGroups = CurrentArchetype.GetGroups(); NewGroups.Add(GroupHandle); const FMassArchetypeHandle NewArchetypeHandle = InternalCreateSimilarArchetype(EntityCollection.GetArchetype().DataPtr, MoveTemp(NewGroups)); EntitiesBeingMoved.Reset(); CurrentArchetype.BatchMoveEntitiesToAnotherArchetype(EntityCollection, *NewArchetypeHandle.DataPtr.Get(), EntitiesBeingMoved // we need something like the following to support observers //, bTagsAddedAreObserved ? &NewArchetypeEntityRanges : nullptr ); for (const FMassEntityHandle& Entity : EntitiesBeingMoved) { check(GetEntityStorageInterface().IsValidIndex(Entity.Index)); GetEntityStorageInterface().SetArchetypeFromShared(Entity.Index, NewArchetypeHandle.DataPtr); } } } } } void FMassEntityManager::BatchGroupEntities(const UE::Mass::FArchetypeGroupHandle GroupHandle, TConstArrayView InEntities) { TArray Collections; UE::Mass::Utils::CreateEntityCollections(*this, InEntities, FMassArchetypeEntityCollection::FoldDuplicates, Collections); BatchGroupEntities(GroupHandle, Collections); } void FMassEntityManager::RemoveEntityFromGroupType(FMassEntityHandle EntityHandle, UE::Mass::FArchetypeGroupType GroupType) { CHECK_SYNC_API(); const FMassArchetypeHandle CurrentArchetypeHandle = GetArchetypeForEntity(EntityHandle); if (FMassArchetypeData* CurrentArchetype = CurrentArchetypeHandle.DataPtr.Get()) { if (CurrentArchetype->IsInGroupOfType(GroupType)) { const FMassArchetypeHandle NewArchetypeHandle = InternalCreateSimilarArchetype(CurrentArchetypeHandle.DataPtr, CurrentArchetype->GetGroups().Remove(GroupType)); CurrentArchetype->MoveEntityToAnotherArchetype(EntityHandle, *NewArchetypeHandle.DataPtr.Get()); GetEntityStorageInterface().SetArchetypeFromShared(EntityHandle.Index, NewArchetypeHandle.DataPtr); } } } UE::Mass::FArchetypeGroupHandle FMassEntityManager::GetGroupForEntity(FMassEntityHandle EntityHandle, UE::Mass::FArchetypeGroupType GroupType) const { if (FMassArchetypeData* CurrentArchetype = GetArchetypeForEntity(EntityHandle).DataPtr.Get()) { return UE::Mass::FArchetypeGroupHandle(GroupType, CurrentArchetype->GetGroups().GetID(GroupType)); } return UE::Mass::FArchetypeGroupHandle(); } UE::Mass::FArchetypeGroupType FMassEntityManager::FinOrAddArchetypeGroupType(const FName GroupName) { LLM_SCOPE_BYNAME(TEXT("Mass/EntityManager")); const int32* FoundGroupIndex = GroupNameToTypeIndex.Find(GroupName); if (LIKELY(FoundGroupIndex)) { return UE::Mass::FArchetypeGroupType(*FoundGroupIndex); } const int32 NewGroupIndex = GroupTypes.Add(GroupName); GroupNameToTypeIndex.Add(GroupName, NewGroupIndex); return UE::Mass::FArchetypeGroupType(NewGroupIndex); } const UE::Mass::FArchetypeGroups& FMassEntityManager::GetGroupsForArchetype(const FMassArchetypeHandle& ArchetypeHandle) const { if (ArchetypeHandle.IsValid() == false) { static UE::Mass::FArchetypeGroups DummyGroups; return DummyGroups; } return ArchetypeHandle.DataPtr->GetGroups(); } void FMassEntityManager::AddFragmentToEntity(FMassEntityHandle Entity, const UScriptStruct* FragmentType) { CHECK_SYNC_API(); checkf(FragmentType, TEXT("Null fragment type passed in to %hs"), __FUNCTION__); CheckIfEntityIsActive(Entity); const FMassArchetypeCompositionDescriptor Descriptor(InternalAddFragmentListToEntityChecked(Entity, FMassFragmentBitSet(*FragmentType))); ObserverManager.OnPostCompositionAdded(Entity, Descriptor); } void FMassEntityManager::AddFragmentToEntity(FMassEntityHandle Entity, const UScriptStruct* FragmentType, const FStructInitializationCallback& Initializer) { CHECK_SYNC_API(); checkf(FragmentType, TEXT("Null fragment type passed in to %hs"), __FUNCTION__); CheckIfEntityIsActive(Entity); FMassFragmentBitSet Fragments = InternalAddFragmentListToEntityChecked(Entity, FMassFragmentBitSet(*FragmentType)); FMassArchetypeData* CurrentArchetype = GetEntityStorageInterface().GetArchetype(Entity.Index); check(CurrentArchetype); void* FragmentData = CurrentArchetype->GetFragmentDataForEntity(FragmentType, Entity.Index); Initializer(FragmentData, *FragmentType); const FMassArchetypeCompositionDescriptor Descriptor(MoveTemp(Fragments)); ObserverManager.OnPostCompositionAdded(Entity, Descriptor); } void FMassEntityManager::AddFragmentListToEntity(FMassEntityHandle Entity, TConstArrayView FragmentList) { CHECK_SYNC_API(); CheckIfEntityIsActive(Entity); const FMassArchetypeCompositionDescriptor Descriptor(InternalAddFragmentListToEntityChecked(Entity, FMassFragmentBitSet(FragmentList))); ObserverManager.OnPostCompositionAdded(Entity, Descriptor); } void FMassEntityManager::AddCompositionToEntity_GetDelta(FMassEntityHandle Entity, FMassArchetypeCompositionDescriptor& InOutDescriptor, const FMassArchetypeSharedFragmentValues* AddedSharedFragmentValues) { CHECK_SYNC_API(); CheckIfEntityIsActive(Entity); FMassArchetypeData* OldArchetype = GetEntityStorageInterface().GetArchetype(Entity.Index); check(OldArchetype); InOutDescriptor.Remove(OldArchetype->GetCompositionDescriptor()); ensureMsgf(InOutDescriptor.ChunkFragments.IsEmpty(), TEXT("Adding new chunk fragments is not supported")); ensureMsgf(InOutDescriptor.SharedFragments.IsEmpty() || (AddedSharedFragmentValues && AddedSharedFragmentValues->DoesMatchComposition(InOutDescriptor)) , TEXT("When adding new shared fragments it's required to provide values for said fragments")); if (InOutDescriptor.IsEmpty() == false) { FMassArchetypeCompositionDescriptor NewDescriptor = OldArchetype->GetCompositionDescriptor(); NewDescriptor.Append(InOutDescriptor); const FMassArchetypeHandle NewArchetypeHandle = CreateArchetype(NewDescriptor, FMassArchetypeCreationParams(*OldArchetype)); if (ensure(NewArchetypeHandle.DataPtr.Get() != OldArchetype)) { // Move the entity over FMassArchetypeData& NewArchetype = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(NewArchetypeHandle); NewArchetype.CopyDebugNamesFrom(*OldArchetype); if (AddedSharedFragmentValues) { // we need to merge AddedSharedFragmentValues with OldArchetype's shared fragments FMassArchetypeSharedFragmentValues CurrentSharedFragment = OldArchetype->GetSharedFragmentValues(Entity); CurrentSharedFragment.Append(*AddedSharedFragmentValues); CurrentSharedFragment.Sort(); OldArchetype->MoveEntityToAnotherArchetype(Entity, NewArchetype, &CurrentSharedFragment); } else { OldArchetype->MoveEntityToAnotherArchetype(Entity, NewArchetype); } GetEntityStorageInterface().SetArchetypeFromShared(Entity.Index, NewArchetypeHandle.DataPtr); ObserverManager.OnPostCompositionAdded(Entity, InOutDescriptor); } } } void FMassEntityManager::RemoveCompositionFromEntity(FMassEntityHandle Entity, const FMassArchetypeCompositionDescriptor& InDescriptor) { CHECK_SYNC_API(); CheckIfEntityIsActive(Entity); if(InDescriptor.IsEmpty() == false) { FMassArchetypeData* OldArchetype = GetEntityStorageInterface().GetArchetype(Entity.Index); check(OldArchetype); FMassArchetypeCompositionDescriptor NewDescriptor = OldArchetype->GetCompositionDescriptor(); NewDescriptor.Remove(InDescriptor); ensureMsgf(InDescriptor.ChunkFragments.IsEmpty(), TEXT("Removing chunk fragments is not supported")); if (NewDescriptor.IsEquivalent(OldArchetype->GetCompositionDescriptor()) == false) { ObserverManager.OnPreCompositionRemoved(Entity, InDescriptor); const FMassArchetypeHandle NewArchetypeHandle = CreateArchetype(NewDescriptor, FMassArchetypeCreationParams(*OldArchetype)); if (ensure(NewArchetypeHandle.DataPtr.Get() != OldArchetype)) { // Move the entity over FMassArchetypeData& NewArchetype = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(NewArchetypeHandle); NewArchetype.CopyDebugNamesFrom(*OldArchetype); if (InDescriptor.SharedFragments.IsEmpty() && InDescriptor.ConstSharedFragments.IsEmpty()) { OldArchetype->MoveEntityToAnotherArchetype(Entity, NewArchetype); } else { // we need to remove the shared fragment values to match the new composition FMassArchetypeSharedFragmentValues CurrentSharedFragment = OldArchetype->GetSharedFragmentValues(Entity); CurrentSharedFragment.Remove(InDescriptor); CurrentSharedFragment.Sort(); OldArchetype->MoveEntityToAnotherArchetype(Entity, NewArchetype, &CurrentSharedFragment); } GetEntityStorageInterface().SetArchetypeFromShared(Entity.Index, NewArchetypeHandle.DataPtr); } } } } const FMassArchetypeCompositionDescriptor& FMassEntityManager::GetArchetypeComposition(const FMassArchetypeHandle& ArchetypeHandle) const { const FMassArchetypeData& ArchetypeData = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(ArchetypeHandle); return ArchetypeData.GetCompositionDescriptor(); } void FMassEntityManager::InternalBuildEntity(FMassEntityHandle Entity, const FMassArchetypeHandle& ArchetypeHandle, const FMassArchetypeSharedFragmentValues& SharedFragmentValues) { const TSharedPtr& NewArchetype = ArchetypeHandle.DataPtr; GetEntityStorageInterface().SetArchetypeFromShared(Entity.Index, ArchetypeHandle.DataPtr); NewArchetype->AddEntity(Entity, SharedFragmentValues); UE_TRACE_MASS_ENTITY_CREATED(Entity, *NewArchetype) ObserverManager.OnPostEntityCreated(Entity, NewArchetype->GetCompositionDescriptor()); } void FMassEntityManager::InternalReleaseEntity(FMassEntityHandle Entity) { // Using force release by bypass serial number check since we have verified the validity of the handle earlier. GetEntityStorageInterface().ForceReleaseOne(Entity); } FMassFragmentBitSet FMassEntityManager::InternalAddFragmentListToEntityChecked(FMassEntityHandle Entity, const FMassFragmentBitSet& InFragments) { TSharedPtr& OldArchetype = GetEntityStorageInterface().GetArchetypeAsShared(Entity.Index); check(OldArchetype); UE_CLOG(OldArchetype->GetFragmentBitSet().HasAny(InFragments), LogMass, Log , TEXT("Trying to add a new fragment type to an entity, but it already has some of them. (%s)") , *InFragments.GetOverlap(OldArchetype->GetFragmentBitSet()).DebugGetStringDesc()); FMassFragmentBitSet NewFragments = InFragments - OldArchetype->GetFragmentBitSet(); if (NewFragments.IsEmpty() == false) { InternalAddFragmentListToEntity(Entity, NewFragments); } return MoveTemp(NewFragments); } void FMassEntityManager::InternalAddFragmentListToEntity(FMassEntityHandle Entity, const FMassFragmentBitSet& InFragments) { checkf(InFragments.IsEmpty() == false, TEXT("%hs is intended for internal calls with non empty NewFragments parameter"), __FUNCTION__); check(GetEntityStorageInterface().IsValidIndex(Entity.Index)); TSharedPtr& OldArchetype = GetEntityStorageInterface().GetArchetypeAsShared(Entity.Index); check(OldArchetype.IsValid()); // fetch or create the new archetype const FMassArchetypeHandle NewArchetypeHandle = CreateArchetype(OldArchetype, InFragments); checkf(NewArchetypeHandle.DataPtr != OldArchetype, TEXT("%hs is intended for internal calls with non overlapping fragment list."), __FUNCTION__); // Move the entity over FMassArchetypeData& NewArchetype = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(NewArchetypeHandle); NewArchetype.CopyDebugNamesFrom(*OldArchetype); OldArchetype->MoveEntityToAnotherArchetype(Entity, NewArchetype); GetEntityStorageInterface().SetArchetypeFromShared(Entity.Index, NewArchetypeHandle.DataPtr); } void FMassEntityManager::AddFragmentInstanceListToEntity(FMassEntityHandle Entity, TConstArrayView FragmentInstanceList) { CHECK_SYNC_API(); CheckIfEntityIsActive(Entity); checkf(FragmentInstanceList.Num() > 0, TEXT("Need to specify at least one fragment instances for this operation")); const FMassArchetypeCompositionDescriptor Descriptor(InternalAddFragmentListToEntityChecked(Entity, FMassFragmentBitSet(FragmentInstanceList))); FMassArchetypeData* CurrentArchetype = GetEntityStorageInterface().GetArchetype(Entity.Index); check(CurrentArchetype); CurrentArchetype->SetFragmentsData(Entity, FragmentInstanceList); ObserverManager.OnPostCompositionAdded(Entity, Descriptor); } void FMassEntityManager::RemoveFragmentFromEntity(FMassEntityHandle Entity, const UScriptStruct* FragmentType) { RemoveFragmentListFromEntity(Entity, MakeArrayView(&FragmentType, 1)); } void FMassEntityManager::RemoveFragmentListFromEntity(FMassEntityHandle Entity, TConstArrayView FragmentList) { CHECK_SYNC_API(); CheckIfEntityIsActive(Entity); FMassArchetypeData* OldArchetype = GetEntityStorageInterface().GetArchetype(Entity.Index); check(OldArchetype); const FMassFragmentBitSet FragmentsToRemove(FragmentList); if (OldArchetype->GetFragmentBitSet().HasAny(FragmentsToRemove)) { // If all the fragments got removed this will result in fetching of the empty archetype const FMassArchetypeCompositionDescriptor NewComposition(OldArchetype->GetFragmentBitSet() - FragmentsToRemove , OldArchetype->GetTagBitSet() , OldArchetype->GetChunkFragmentBitSet() , OldArchetype->GetSharedFragmentBitSet() , OldArchetype->GetConstSharedFragmentBitSet()); const FMassArchetypeHandle NewArchetypeHandle = CreateArchetype(NewComposition, FMassArchetypeCreationParams(*OldArchetype)); // Find overlap. It isn't guaranteed that the old archetype has all of the fragments being removed. FMassArchetypeCompositionDescriptor CompositionDelta(OldArchetype->GetFragmentBitSet().GetOverlap(FragmentsToRemove)); ObserverManager.OnPreCompositionRemoved(Entity, CompositionDelta); // Move the entity over FMassArchetypeData& NewArchetype = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(NewArchetypeHandle); NewArchetype.CopyDebugNamesFrom(*OldArchetype); OldArchetype->MoveEntityToAnotherArchetype(Entity, NewArchetype); GetEntityStorageInterface().SetArchetypeFromShared(Entity.Index, NewArchetypeHandle.DataPtr); } } void FMassEntityManager::SwapTagsForEntity(FMassEntityHandle Entity, const UScriptStruct* OldTagType, const UScriptStruct* NewTagType) { CHECK_SYNC_API(); CheckIfEntityIsActive(Entity); checkf(UE::Mass::IsA(OldTagType), TEXT("%hs works only with tags while '%s' is not one."), __FUNCTION__, *GetPathNameSafe(OldTagType)); checkf(UE::Mass::IsA(NewTagType), TEXT("%hs works only with tags while '%s' is not one."), __FUNCTION__, *GetPathNameSafe(NewTagType)); TSharedPtr& CurrentArchetype = GetEntityStorageInterface().GetArchetypeAsShared(Entity.Index); check(CurrentArchetype); FMassTagBitSet NewTagBitSet = CurrentArchetype->GetTagBitSet(); NewTagBitSet.Remove(*OldTagType); NewTagBitSet.Add(*NewTagType); if (NewTagBitSet != CurrentArchetype->GetTagBitSet()) { const FMassArchetypeHandle NewArchetypeHandle = InternalCreateSimilarArchetype(CurrentArchetype, NewTagBitSet); checkSlow(NewArchetypeHandle.IsValid()); // Move the entity over CurrentArchetype->MoveEntityToAnotherArchetype(Entity, *NewArchetypeHandle.DataPtr.Get()); GetEntityStorageInterface().SetArchetypeFromShared(Entity.Index, NewArchetypeHandle.DataPtr); } } void FMassEntityManager::AddTagToEntity(FMassEntityHandle Entity, const UScriptStruct* TagType) { CHECK_SYNC_API(); checkf(UE::Mass::IsA(TagType), TEXT("%hs works only with tags while '%s' is not one."), __FUNCTION__, *GetPathNameSafe(TagType)); CheckIfEntityIsActive(Entity); TSharedPtr& CurrentArchetype = GetEntityStorageInterface().GetArchetypeAsShared(Entity.Index); check(CurrentArchetype); if (CurrentArchetype->HasTagType(TagType) == false) { FMassTagBitSet NewTags = CurrentArchetype->GetTagBitSet(); NewTags.Add(*TagType); const FMassArchetypeHandle NewArchetypeHandle = InternalCreateSimilarArchetype(CurrentArchetype, NewTags); checkSlow(NewArchetypeHandle.IsValid()); // Move the entity over CurrentArchetype->MoveEntityToAnotherArchetype(Entity, *NewArchetypeHandle.DataPtr.Get()); GetEntityStorageInterface().SetArchetypeFromShared(Entity.Index, NewArchetypeHandle.DataPtr); ObserverManager.OnPostCompositionAdded(Entity, FMassArchetypeCompositionDescriptor(FMassTagBitSet(*TagType))); } } void FMassEntityManager::RemoveTagFromEntity(FMassEntityHandle Entity, const UScriptStruct* TagType) { CHECK_SYNC_API(); checkf(UE::Mass::IsA(TagType), TEXT("%hs works only with tags while '%s' is not one."), __FUNCTION__, *GetPathNameSafe(TagType)); CheckIfEntityIsActive(Entity); TSharedPtr& CurrentArchetype = GetEntityStorageInterface().GetArchetypeAsShared(Entity.Index); check(CurrentArchetype); if (CurrentArchetype->HasTagType(TagType)) { FMassTagBitSet TagDelta(*TagType); const FMassTagBitSet NewTagComposition = CurrentArchetype->GetTagBitSet() - TagDelta; ObserverManager.OnPreCompositionRemoved(Entity, FMassArchetypeCompositionDescriptor(MoveTemp(TagDelta))); const FMassArchetypeHandle NewArchetypeHandle = InternalCreateSimilarArchetype(CurrentArchetype, NewTagComposition); checkSlow(NewArchetypeHandle.IsValid()); // Move the entity over CurrentArchetype->MoveEntityToAnotherArchetype(Entity, *NewArchetypeHandle.DataPtr.Get()); GetEntityStorageInterface().SetArchetypeFromShared(Entity.Index, NewArchetypeHandle.DataPtr); } } bool FMassEntityManager::AddConstSharedFragmentToEntity(const FMassEntityHandle Entity, const FConstSharedStruct& InConstSharedFragment) { CHECK_SYNC_API_RETURN(false); if (!ensureMsgf(InConstSharedFragment.IsValid(), TEXT("%hs parameter Fragment is expected to be valid"), __FUNCTION__)) { return false; } CheckIfEntityIsActive(Entity); FMassArchetypeData* CurrentArchetype = GetEntityStorageInterface().GetArchetypeAsShared(Entity.Index).Get(); check(CurrentArchetype); const UScriptStruct* StructType = InConstSharedFragment.GetScriptStruct(); CA_ASSUME(StructType); if (CurrentArchetype->GetCompositionDescriptor().ConstSharedFragments.Contains(*StructType)) { const FMassArchetypeSharedFragmentValues& SharedFragmentValues = CurrentArchetype->GetSharedFragmentValues(Entity); FConstSharedStruct ExistingConstSharedStruct = SharedFragmentValues.GetConstSharedFragmentStruct(StructType); if (ExistingConstSharedStruct == InConstSharedFragment || ExistingConstSharedStruct.CompareStructValues(InConstSharedFragment)) { // nothing to do return true; } UE_LOG(LogMass, Warning, TEXT("Changing shared fragment value of entities is not supported")); return false; } FMassArchetypeCompositionDescriptor NewComposition(CurrentArchetype->GetCompositionDescriptor()); NewComposition.ConstSharedFragments.Add(*StructType); const FMassArchetypeHandle NewArchetypeHandle = CreateArchetype(NewComposition, FMassArchetypeCreationParams(*CurrentArchetype)); check(NewArchetypeHandle.IsValid()); FMassArchetypeData* NewArchetype = NewArchetypeHandle.DataPtr.Get(); check(NewArchetype); const FMassArchetypeSharedFragmentValues& OldSharedFragmentValues = CurrentArchetype->GetSharedFragmentValues(Entity.Index); check(!OldSharedFragmentValues.ContainsType(StructType)); FMassArchetypeSharedFragmentValues NewSharedFragmentValues(OldSharedFragmentValues); NewSharedFragmentValues.Add(InConstSharedFragment); NewSharedFragmentValues.Sort(); CurrentArchetype->MoveEntityToAnotherArchetype(Entity, *NewArchetype, &NewSharedFragmentValues); // Change the entity archetype GetEntityStorageInterface().SetArchetypeFromShared(Entity.Index, NewArchetypeHandle.DataPtr); return true; } bool FMassEntityManager::RemoveConstSharedFragmentFromEntity(const FMassEntityHandle Entity, const UScriptStruct& ConstSharedFragmentType) { CHECK_SYNC_API_RETURN(false); if (!ensureMsgf(UE::Mass::IsA(&ConstSharedFragmentType), TEXT("%hs parameter ConstSharedFragmentType is expected to be a FMassConstSharedFragment"), __FUNCTION__)) { return false; } CheckIfEntityIsActive(Entity); FMassArchetypeData* CurrentArchetype = GetEntityStorageInterface().GetArchetypeAsShared(Entity.Index).Get(); check(CurrentArchetype); if (!CurrentArchetype->GetCompositionDescriptor().ConstSharedFragments.Contains(ConstSharedFragmentType)) { // Nothing to do. Returning false to indicate nothing has been removed, as per function's documentation return false; } FMassArchetypeCompositionDescriptor NewComposition(CurrentArchetype->GetCompositionDescriptor()); NewComposition.ConstSharedFragments.Remove(ConstSharedFragmentType); const FMassArchetypeHandle NewArchetypeHandle = CreateArchetype(NewComposition); check(NewArchetypeHandle.IsValid()); FMassArchetypeData* NewArchetype = NewArchetypeHandle.DataPtr.Get(); check(NewArchetype); const FMassArchetypeSharedFragmentValues& OldSharedFragmentValues = CurrentArchetype->GetSharedFragmentValues(Entity.Index); check(OldSharedFragmentValues.ContainsType(&ConstSharedFragmentType)); FMassArchetypeSharedFragmentValues NewSharedFragmentValues(OldSharedFragmentValues); const FMassConstSharedFragmentBitSet ToRemove(ConstSharedFragmentType); NewSharedFragmentValues.Remove(ToRemove); NewSharedFragmentValues.Sort(); CurrentArchetype->MoveEntityToAnotherArchetype(Entity, *NewArchetype, &NewSharedFragmentValues); // Change the entity archetype GetEntityStorageInterface().SetArchetypeFromShared(Entity.Index, NewArchetypeHandle.DataPtr); return true; } bool FMassEntityManager::AddSharedFragmentToEntity(const FMassEntityHandle Entity, const FSharedStruct& InSharedFragment) { CHECK_SYNC_API_RETURN(false); if (!ensureMsgf(InSharedFragment.IsValid(), TEXT("%hs parameter Fragment is expected to be valid"), __FUNCTION__)) { return false; } CheckIfEntityIsActive(Entity); FMassArchetypeData* CurrentArchetype = GetEntityStorageInterface().GetArchetypeAsShared(Entity.Index).Get(); check(CurrentArchetype); const UScriptStruct* StructType = InSharedFragment.GetScriptStruct(); CA_ASSUME(StructType); if (CurrentArchetype->GetCompositionDescriptor().SharedFragments.Contains(*StructType)) { const FMassArchetypeSharedFragmentValues& SharedFragmentValues = CurrentArchetype->GetSharedFragmentValues(Entity); FConstSharedStruct ExistingSharedStruct = SharedFragmentValues.GetSharedFragmentStruct(StructType); if (ExistingSharedStruct == InSharedFragment || ExistingSharedStruct.CompareStructValues(InSharedFragment)) { // nothing to do return true; } UE_LOG(LogMass, Warning, TEXT("Changing shared fragment value of entities is not supported")); return false; } FMassArchetypeCompositionDescriptor NewComposition(CurrentArchetype->GetCompositionDescriptor()); NewComposition.SharedFragments.Add(*StructType); const FMassArchetypeHandle NewArchetypeHandle = CreateArchetype(NewComposition, FMassArchetypeCreationParams(*CurrentArchetype)); check(NewArchetypeHandle.IsValid()); FMassArchetypeData* NewArchetype = NewArchetypeHandle.DataPtr.Get(); check(NewArchetype); const FMassArchetypeSharedFragmentValues& OldSharedFragmentValues = CurrentArchetype->GetSharedFragmentValues(Entity.Index); check(!OldSharedFragmentValues.ContainsType(StructType)); FMassArchetypeSharedFragmentValues NewSharedFragmentValues(OldSharedFragmentValues); NewSharedFragmentValues.Add(InSharedFragment); NewSharedFragmentValues.Sort(); CurrentArchetype->MoveEntityToAnotherArchetype(Entity, *NewArchetype, &NewSharedFragmentValues); // Change the entity archetype GetEntityStorageInterface().SetArchetypeFromShared(Entity.Index, NewArchetypeHandle.DataPtr); return true; } bool FMassEntityManager::RemoveSharedFragmentFromEntity(const FMassEntityHandle Entity, const UScriptStruct& SharedFragmentType) { CHECK_SYNC_API_RETURN(false); if (!ensureMsgf(UE::Mass::IsA(&SharedFragmentType), TEXT("%hs parameter SharedFragmentType is expected to be a FMassSharedFragment"), __FUNCTION__)) { return false; } CheckIfEntityIsActive(Entity); FMassArchetypeData* CurrentArchetype = GetEntityStorageInterface().GetArchetypeAsShared(Entity.Index).Get(); check(CurrentArchetype); if (!CurrentArchetype->GetCompositionDescriptor().SharedFragments.Contains(SharedFragmentType)) { // Nothing to do. Returning false to indicate nothing has been removed, as per function's documentation return false; } FMassArchetypeCompositionDescriptor NewComposition(CurrentArchetype->GetCompositionDescriptor()); NewComposition.SharedFragments.Remove(SharedFragmentType); const FMassArchetypeHandle NewArchetypeHandle = CreateArchetype(NewComposition); check(NewArchetypeHandle.IsValid()); FMassArchetypeData* NewArchetype = NewArchetypeHandle.DataPtr.Get(); check(NewArchetype); const FMassArchetypeSharedFragmentValues& OldSharedFragmentValues = CurrentArchetype->GetSharedFragmentValues(Entity.Index); check(OldSharedFragmentValues.ContainsType(&SharedFragmentType)); FMassArchetypeSharedFragmentValues NewSharedFragmentValues(OldSharedFragmentValues); const FMassSharedFragmentBitSet ToRemove(SharedFragmentType); NewSharedFragmentValues.Remove(ToRemove); NewSharedFragmentValues.Sort(); CurrentArchetype->MoveEntityToAnotherArchetype(Entity, *NewArchetype, &NewSharedFragmentValues); // Change the entity archetype GetEntityStorageInterface().SetArchetypeFromShared(Entity.Index, NewArchetypeHandle.DataPtr); return true; } void FMassEntityManager::BatchChangeTagsForEntities(TConstArrayView EntityCollections, const FMassTagBitSet& TagsToAdd, const FMassTagBitSet& TagsToRemove) { CHECK_SYNC_API(); TRACE_CPUPROFILER_EVENT_SCOPE(Mass_BatchChangeTagsForEntities); for (const FMassArchetypeEntityCollection& Collection : EntityCollections) { FMassArchetypeData* CurrentArchetype = Collection.GetArchetype().DataPtr.Get(); const FMassTagBitSet NewTagComposition = CurrentArchetype ? (CurrentArchetype->GetTagBitSet() + TagsToAdd - TagsToRemove) : (TagsToAdd - TagsToRemove); if (ensure(CurrentArchetype) && CurrentArchetype->GetTagBitSet() != NewTagComposition) { FMassTagBitSet TagsAdded = TagsToAdd - CurrentArchetype->GetTagBitSet(); const bool bTagsAddedAreObserved = ObserverManager.HasObserversForBitSet(TagsAdded, EMassObservedOperation::Add); FMassTagBitSet TagsRemoved = TagsToRemove.GetOverlap(CurrentArchetype->GetTagBitSet()); if (TagsRemoved.IsEmpty() == false) { ObserverManager.OnCompositionChanged(Collection, MoveTemp(TagsRemoved), EMassObservedOperation::Remove); } FMassArchetypeHandle NewArchetypeHandle = InternalCreateSimilarArchetype(Collection.GetArchetype().DataPtr, NewTagComposition); checkSlow(NewArchetypeHandle.IsValid()); // Move the entity over FMassArchetypeEntityCollection::FEntityRangeArray NewArchetypeEntityRanges; TArray EntitiesBeingMoved; CurrentArchetype->BatchMoveEntitiesToAnotherArchetype(Collection, *NewArchetypeHandle.DataPtr.Get(), EntitiesBeingMoved , bTagsAddedAreObserved ? &NewArchetypeEntityRanges : nullptr); for (const FMassEntityHandle& Entity : EntitiesBeingMoved) { check(GetEntityStorageInterface().IsValidIndex(Entity.Index)); GetEntityStorageInterface().SetArchetypeFromShared(Entity.Index, NewArchetypeHandle.DataPtr); } if (TagsAdded.IsEmpty() == false) { ObserverManager.OnCompositionChanged( FMassArchetypeEntityCollection(NewArchetypeHandle, MoveTemp(NewArchetypeEntityRanges)) , MoveTemp(TagsAdded) , EMassObservedOperation::Add); } } } } void FMassEntityManager::BatchChangeFragmentCompositionForEntities(TConstArrayView EntityCollections, const FMassFragmentBitSet& FragmentsToAdd, const FMassFragmentBitSet& FragmentsToRemove) { CHECK_SYNC_API(); TRACE_CPUPROFILER_EVENT_SCOPE(Mass_BatchChangeFragmentCompositionForEntities); for (const FMassArchetypeEntityCollection& Collection : EntityCollections) { FMassArchetypeData* CurrentArchetype = Collection.GetArchetype().DataPtr.Get(); const FMassFragmentBitSet NewFragmentComposition = CurrentArchetype ? (CurrentArchetype->GetFragmentBitSet() + FragmentsToAdd - FragmentsToRemove) : (FragmentsToAdd - FragmentsToRemove); if (CurrentArchetype) { if (CurrentArchetype->GetFragmentBitSet() != NewFragmentComposition) { FMassFragmentBitSet FragmentsAdded = FragmentsToAdd - CurrentArchetype->GetFragmentBitSet(); const bool bFragmentsAddedAreObserved = ObserverManager.HasObserversForBitSet(FragmentsAdded, EMassObservedOperation::Add); FMassFragmentBitSet FragmentsRemoved = FragmentsToRemove.GetOverlap(CurrentArchetype->GetFragmentBitSet()); if (FragmentsRemoved.IsEmpty() == false) { ObserverManager.OnCompositionChanged(Collection, MoveTemp(FragmentsRemoved), EMassObservedOperation::Remove); } FMassArchetypeHandle NewArchetypeHandle = InternalCreateSimilarArchetype(Collection.GetArchetype().DataPtr, NewFragmentComposition); checkSlow(NewArchetypeHandle.IsValid()); // Move the entity over FMassArchetypeEntityCollection::FEntityRangeArray NewArchetypeEntityRanges; TArray EntitiesBeingMoved; CurrentArchetype->BatchMoveEntitiesToAnotherArchetype(Collection, *NewArchetypeHandle.DataPtr.Get(), EntitiesBeingMoved , bFragmentsAddedAreObserved ? &NewArchetypeEntityRanges : nullptr); for (const FMassEntityHandle& Entity : EntitiesBeingMoved) { check(GetEntityStorageInterface().IsValidIndex(Entity.Index)); GetEntityStorageInterface().SetArchetypeFromShared(Entity.Index, NewArchetypeHandle.DataPtr); } if (bFragmentsAddedAreObserved) { ObserverManager.OnCompositionChanged( FMassArchetypeEntityCollection(NewArchetypeHandle, MoveTemp(NewArchetypeEntityRanges)) , MoveTemp(FragmentsAdded) , EMassObservedOperation::Add); } } } else { BatchBuildEntities(FMassArchetypeEntityCollectionWithPayload(Collection), NewFragmentComposition, FMassArchetypeSharedFragmentValues()); } } } void FMassEntityManager::BatchAddFragmentInstancesForEntities(TConstArrayView EntityCollections, const FMassFragmentBitSet& FragmentsAffected) { CHECK_SYNC_API(); TRACE_CPUPROFILER_EVENT_SCOPE(Mass_BatchAddFragmentInstancesForEntities); // here's the scenario: // * we get entities from potentially different archetypes // * adding a fragment instance consists of two operations: A) add fragment type & B) set fragment value // * some archetypes might already have the "added" fragments so no need for step A // * there might be an "empty" archetype in the mix - then step A results in archetype creation and assigning // * if step A is required then the initial FMassArchetypeEntityCollection instance is no longer valid // * setting value can be done uniformly for all entities, remembering some might be in different chunks already // * @todo note that after adding fragment type some entities originally in different archetypes end up in the same // archetype. This could be utilized as a basis for optimization. To be investigated. // for (const FMassArchetypeEntityCollectionWithPayload& EntityRangesWithPayload : EntityCollections) { FMassArchetypeHandle TargetArchetypeHandle = EntityRangesWithPayload.GetEntityCollection().GetArchetype(); FMassArchetypeData* CurrentArchetype = TargetArchetypeHandle.DataPtr.Get(); if (CurrentArchetype) { FMassArchetypeEntityCollection::FEntityRangeArray TargetArchetypeEntityRanges; bool bFragmentsAddedAreObserved = false; FMassFragmentBitSet NewFragmentComposition = CurrentArchetype ? (CurrentArchetype->GetFragmentBitSet() + FragmentsAffected) : FragmentsAffected; FMassFragmentBitSet FragmentsAdded; if (CurrentArchetype->GetFragmentBitSet() != NewFragmentComposition) { FragmentsAdded = FragmentsAffected - CurrentArchetype->GetFragmentBitSet(); bFragmentsAddedAreObserved = ObserverManager.HasObserversForBitSet(FragmentsAdded, EMassObservedOperation::Add); FMassArchetypeHandle NewArchetypeHandle = InternalCreateSimilarArchetype(TargetArchetypeHandle.DataPtr, NewFragmentComposition); checkSlow(NewArchetypeHandle.IsValid()); // Move the entity over TArray EntitiesBeingMoved; CurrentArchetype->BatchMoveEntitiesToAnotherArchetype(EntityRangesWithPayload.GetEntityCollection(), *NewArchetypeHandle.DataPtr.Get() , EntitiesBeingMoved, &TargetArchetypeEntityRanges); for (const FMassEntityHandle& Entity : EntitiesBeingMoved) { check(GetEntityStorageInterface().IsValidIndex(Entity.Index)); GetEntityStorageInterface().SetArchetypeFromShared(Entity.Index, NewArchetypeHandle.DataPtr); } TargetArchetypeHandle = NewArchetypeHandle; } else { TargetArchetypeEntityRanges = EntityRangesWithPayload.GetEntityCollection().GetRanges(); } // at this point all the entities are in the target archetype, we can set the values // note that even though the "subchunk" information could have changed the order of entities is the same and // corresponds to the order in FMassArchetypeEntityCollectionWithPayload's payload TargetArchetypeHandle.DataPtr->BatchSetFragmentValues(TargetArchetypeEntityRanges, EntityRangesWithPayload.GetPayload()); if (bFragmentsAddedAreObserved) { ObserverManager.OnCompositionChanged( FMassArchetypeEntityCollection(TargetArchetypeHandle, MoveTemp(TargetArchetypeEntityRanges)) , MoveTemp(FragmentsAdded) , EMassObservedOperation::Add); } } else { BatchBuildEntities(EntityRangesWithPayload, FragmentsAffected, FMassArchetypeSharedFragmentValues()); } } } void FMassEntityManager::BatchAddSharedFragmentsForEntities(TConstArrayView EntityCollections , const FMassArchetypeSharedFragmentValues& AddedFragmentValues) { CHECK_SYNC_API(); TRACE_CPUPROFILER_EVENT_SCOPE(Mass_BatchAddConstSharedFragmentForEntities); for (const FMassArchetypeEntityCollection& Collection : EntityCollections) { FMassArchetypeData* CurrentArchetype = Collection.GetArchetype().DataPtr.Get(); testableCheckfReturn(CurrentArchetype, continue, TEXT("Adding shared fragments to archetype-less entities is not supported")); FMassArchetypeCompositionDescriptor NewComposition(CurrentArchetype->GetCompositionDescriptor()); NewComposition.SharedFragments += AddedFragmentValues.GetSharedFragmentBitSet(); NewComposition.ConstSharedFragments += AddedFragmentValues.GetConstSharedFragmentBitSet(); const FMassArchetypeHandle NewArchetypeHandle = CreateArchetype(NewComposition, FMassArchetypeCreationParams(*CurrentArchetype)); check(NewArchetypeHandle.IsValid()); FMassArchetypeData* NewArchetype = NewArchetypeHandle.DataPtr.Get(); check(NewArchetype); if (!testableEnsureMsgf(CurrentArchetype != NewArchetype, TEXT("Setting shared fragment values without archetype change is not supported"))) { UE_LOG(LogMass, Warning, TEXT("Trying to set shared fragment values, without adding new shared fragments, is not supported.")); continue; } TArray EntitiesBeingMoved; CurrentArchetype->BatchMoveEntitiesToAnotherArchetype(Collection, *NewArchetype, EntitiesBeingMoved, /*OutNewChunks=*/nullptr, &AddedFragmentValues); for (const FMassEntityHandle& Entity : EntitiesBeingMoved) { check(GetEntityStorageInterface().IsValidIndex(Entity.Index)); GetEntityStorageInterface().SetArchetypeFromShared(Entity.Index, NewArchetypeHandle.DataPtr); } } } void FMassEntityManager::MoveEntityToAnotherArchetype(FMassEntityHandle Entity, FMassArchetypeHandle NewArchetypeHandle, const FMassArchetypeSharedFragmentValues* SharedFragmentValuesOverride) { CHECK_SYNC_API(); CheckIfEntityIsActive(Entity); FMassArchetypeData& NewArchetype = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(NewArchetypeHandle); // Move the entity over FMassArchetypeData* CurrentArchetype = GetEntityStorageInterface().GetArchetype(Entity.Index); check(CurrentArchetype); FMassArchetypeCompositionDescriptor CompositionRemoved = CurrentArchetype->GetCompositionDescriptor().CalculateDifference(NewArchetype.GetCompositionDescriptor()); ObserverManager.OnCompositionChanged(Entity, MoveTemp(CompositionRemoved), EMassObservedOperation::Remove); CurrentArchetype->MoveEntityToAnotherArchetype(Entity, NewArchetype, SharedFragmentValuesOverride); GetEntityStorageInterface().SetArchetypeFromShared(Entity.Index, NewArchetypeHandle.DataPtr); FMassArchetypeCompositionDescriptor CompositionAdded = NewArchetype.GetCompositionDescriptor().CalculateDifference(CurrentArchetype->GetCompositionDescriptor()); ObserverManager.OnCompositionChanged(Entity, MoveTemp(CompositionAdded), EMassObservedOperation::Add); } void FMassEntityManager::SetEntityFragmentValues(FMassEntityHandle Entity, TArrayView FragmentInstanceList) { CheckIfEntityIsActive(Entity); FMassArchetypeData* CurrentArchetype = GetEntityStorageInterface().GetArchetype(Entity.Index); check(CurrentArchetype); CurrentArchetype->SetFragmentsData(Entity, FragmentInstanceList); } void FMassEntityManager::BatchSetEntityFragmentValues(const FMassArchetypeEntityCollection& SparseEntities, TArrayView FragmentInstanceList) { if (FragmentInstanceList.Num()) { BatchSetEntityFragmentValues(MakeArrayView(&SparseEntities, 1), FragmentInstanceList); } } void FMassEntityManager::BatchSetEntityFragmentValues(TConstArrayView EntityCollections, TArrayView FragmentInstanceList) { CHECK_SYNC_API(); if (FragmentInstanceList.IsEmpty()) { return; } for (const FMassArchetypeEntityCollection& SparseEntities : EntityCollections) { TRACE_CPUPROFILER_EVENT_SCOPE(Mass_BatchSetEntityFragmentValues); FMassArchetypeData* Archetype = SparseEntities.GetArchetype().DataPtr.Get(); check(Archetype); for (const FInstancedStruct& FragmentTemplate : FragmentInstanceList) { Archetype->SetFragmentData(SparseEntities.GetRanges(), FragmentTemplate); } } } void* FMassEntityManager::InternalGetFragmentDataChecked(FMassEntityHandle Entity, const UScriptStruct* FragmentType) const { // note that FragmentType is guaranteed to be of valid type - it's either statically checked by the template versions // or `checkf`ed by the non-template one CheckIfEntityIsActive(Entity); const FMassArchetypeData* CurrentArchetype = GetEntityStorageInterface().GetArchetype(Entity.Index); check(CurrentArchetype); return CurrentArchetype->GetFragmentDataForEntityChecked(FragmentType, Entity.Index); } void* FMassEntityManager::InternalGetFragmentDataPtr(FMassEntityHandle Entity, const UScriptStruct* FragmentType) const { // note that FragmentType is guaranteed to be of valid type - it's either statically checked by the template versions // or `checkf`ed by the non-template one CheckIfEntityIsActive(Entity); const FMassArchetypeData* CurrentArchetype = GetEntityStorageInterface().GetArchetype(Entity.Index); check(CurrentArchetype); return CurrentArchetype->GetFragmentDataForEntity(FragmentType, Entity.Index); } const FConstSharedStruct* FMassEntityManager::InternalGetConstSharedFragmentPtr(FMassEntityHandle Entity, const UScriptStruct* ConstSharedFragmentType) const { // note that ConstSharedFragmentType is guaranteed to be of valid type - it's either statically checked by the template versions // or `checkf`ed by the non-template one CheckIfEntityIsActive(Entity); const FMassArchetypeData* CurrentArchetype = GetEntityStorageInterface().GetArchetype(Entity.Index); check(CurrentArchetype); const FConstSharedStruct* SharedFragment = CurrentArchetype->GetSharedFragmentValues(Entity).GetConstSharedFragments().FindByPredicate(FStructTypeEqualOperator(ConstSharedFragmentType)); return SharedFragment; } const FSharedStruct* FMassEntityManager::InternalGetSharedFragmentPtr(FMassEntityHandle Entity, const UScriptStruct* SharedFragmentType) const { // note that SharedFragmentType is guaranteed to be of valid type - it's either statically checked by the template versions // or `checkf`ed by the non-template one CheckIfEntityIsActive(Entity); const FMassArchetypeData* CurrentArchetype = GetEntityStorageInterface().GetArchetype(Entity.Index); check(CurrentArchetype); const FSharedStruct* SharedFragment = CurrentArchetype->GetSharedFragmentValues(Entity).GetSharedFragments().FindByPredicate(FStructTypeEqualOperator(SharedFragmentType)); return SharedFragment; } bool FMassEntityManager::IsEntityValid(FMassEntityHandle Entity) const { return (Entity.Index != UE::Mass::Private::InvalidEntityIndex) && GetEntityStorageInterface().IsValidIndex(Entity.Index) && (GetEntityStorageInterface().GetSerialNumber(Entity.Index) == Entity.SerialNumber); } bool FMassEntityManager::IsEntityBuilt(FMassEntityHandle Entity) const { CheckIfEntityIsValid(Entity); const UE::Mass::IEntityStorageInterface::EEntityState CurrentState = GetEntityStorageInterface().GetEntityState(Entity.Index); return CurrentState == UE::Mass::IEntityStorageInterface::EEntityState::Created; } bool FMassEntityManager::IsEntityReserved(FMassEntityHandle EntityHandle) const { CheckIfEntityIsValid(EntityHandle); return GetEntityStorageInterface().GetEntityState(EntityHandle.Index) == UE::Mass::IEntityStorageInterface::EEntityState::Reserved; } void FMassEntityManager::CheckIfEntityIsValid(FMassEntityHandle Entity) const { checkf(IsEntityValid(Entity), TEXT("Invalid entity (ID: %d, SN:%d, %s)"), Entity.Index, Entity.SerialNumber, (Entity.Index == 0) ? TEXT("was never initialized") : TEXT("already destroyed")); } void FMassEntityManager::CheckIfEntityIsActive(FMassEntityHandle Entity) const { checkf(IsEntityBuilt(Entity), TEXT("Entity not yet created(ID: %d, SN:%d)"), Entity.Index, Entity.SerialNumber); } void FMassEntityManager::GetMatchingArchetypes(const FMassFragmentRequirements& Requirements, TArray& OutValidArchetypes, const uint32 FromArchetypeDataVersion) const { for (int32 ArchetypeIndex = FromArchetypeDataVersion; ArchetypeIndex < AllArchetypes.Num(); ++ArchetypeIndex) { checkf(AllArchetypes[ArchetypeIndex].IsValid(), TEXT("We never expect to get any invalid shared ptrs in AllArchetypes")); FMassArchetypeData& Archetype = *(AllArchetypes[ArchetypeIndex].Get()); // Only return archetypes with a newer created version than the specified version, this is for incremental query updates ensureMsgf(Archetype.GetCreatedArchetypeDataVersion() > FromArchetypeDataVersion , TEXT("There's a stron assumption that archetype's data version corresponds to its index in AllArchetypes")); if (Requirements.DoesArchetypeMatchRequirements(Archetype.GetCompositionDescriptor())) { OutValidArchetypes.Add(AllArchetypes[ArchetypeIndex]); } #if WITH_MASSENTITY_DEBUG else { UE_VLOG_UELOG(GetOwner(), LogMass, VeryVerbose, TEXT("%s") , *FMassDebugger::GetArchetypeRequirementCompatibilityDescription(Requirements, Archetype.GetCompositionDescriptor())); } #endif // WITH_MASSENTITY_DEBUG } } FMassExecutionContext FMassEntityManager::CreateExecutionContext(const float DeltaSeconds) { FMassExecutionContext ExecutionContext(*this, DeltaSeconds); ExecutionContext.SetDeferredCommandBuffer(DeferredCommandBuffers[OpenedCommandBufferIndex]); return MoveTemp(ExecutionContext); } void FMassEntityManager::FlushCommands(const TSharedPtr& InCommandBuffer) { if (!ensureMsgf(IsInGameThread(), TEXT("Calling %hs is supported only on the Game Tread"), __FUNCTION__)) { return; } if (!ensureMsgf(IsProcessing() == false, TEXT("Calling %hs is not supported while Mass Processing is active. Call FMassEntityManager::AppendCommands instead."), __FUNCTION__)) { return; } if (UNLIKELY(InitializationState != EInitializationState::Initialized)) { UE_CVLOG_UELOG(InitializationState == EInitializationState::Uninitialized, GetOwner(), LogMass, Warning , TEXT("FlushCommands called before Initialize call, which means this FMassEntityManager instance is not ready to process commands and will cancel them.")); UE_CVLOG_UELOG(InitializationState == EInitializationState::Deinitialized, GetOwner(), LogMass, Log , TEXT("FlushCommands called after Deinitialize call, which means this FMassEntityManager instance is going away, can't process commands and will cancel them.")); InCommandBuffer->CancelCommands(); return; } if (InCommandBuffer && InCommandBuffer->HasPendingCommands() && (Algo::Find(DeferredCommandBuffers, InCommandBuffer) == nullptr)) { AppendCommands(InCommandBuffer); } FlushCommands(); } void FMassEntityManager::FlushCommands() { constexpr int32 MaxIterations = 5; if (!ensureMsgf(IsInGameThread(), TEXT("Calling %hs is supported only on the Game Tread"), __FUNCTION__)) { return; } if (!ensureMsgf(IsProcessing() == false, TEXT("Calling %hs is not supported while Mass Processing is active. Call FMassEntityManager::AppendCommands instead."), __FUNCTION__)) { return; } if (bCommandBufferFlushingInProgress == false && IsProcessing() == false) { ON_SCOPE_EXIT { bCommandBufferFlushingInProgress = false; }; bCommandBufferFlushingInProgress = true; int32 IterationCount = 0; do { const int32 CommandBufferIndexToFlush = OpenedCommandBufferIndex; // buffer swap. Code instigated by observers can still use Defer() to push commands. OpenedCommandBufferIndex = (OpenedCommandBufferIndex + 1) % DeferredCommandBuffers.Num(); ensureMsgf(DeferredCommandBuffers[OpenedCommandBufferIndex]->HasPendingCommands() == false , TEXT("The freshly opened command buffer is expected to be empty upon switching")); DeferredCommandBuffers[CommandBufferIndexToFlush]->Flush(*this); // repeat if there were commands submitted while commands were being flushed (by observers for example) } while (DeferredCommandBuffers[OpenedCommandBufferIndex]->HasPendingCommands() && ++IterationCount < MaxIterations); UE_CVLOG_UELOG(IterationCount >= MaxIterations, GetOwner(), LogMass, Error, TEXT("Reached loop count limit while flushing commands. Limiting the number of commands pushed during commands flushing could help.")); } } void FMassEntityManager::AppendCommands(const TSharedPtr& InOutCommandBuffer) { if (!ensureMsgf(Algo::Find(DeferredCommandBuffers, InOutCommandBuffer) == nullptr , TEXT("We don't expect AppendCommands to be called with EntityManager's command buffer as the input parameter"))) { return; } LLM_SCOPE_BYNAME(TEXT("Mass/EntityManager")); Defer().MoveAppend(*InOutCommandBuffer.Get()); } TSharedRef FMassEntityManager::GetOrMakeCreationContext() { return ObserverManager.GetOrMakeCreationContext(); } UE::Mass::FEntityBuilder FMassEntityManager::MakeEntityBuilder() { return UE::Mass::FEntityBuilder(AsShared()); } bool FMassEntityManager::DebugDoCollectionsOverlapCreationContext(TConstArrayView EntityCollections) const { if (TSharedPtr AsSharedPtr = ObserverManager.GetCreationContext()) { TArray CreationCollections = AsSharedPtr->GetEntityCollections(*this); return CreationCollections.GetData() <= EntityCollections.GetData() && EntityCollections.GetData() <= CreationCollections.GetData() + CreationCollections.Num(); } return false; } void FMassEntityManager::SetDebugName(const FString& NewDebugGame) { #if WITH_MASSENTITY_DEBUG DebugName = NewDebugGame; #endif // WITH_MASSENTITY_DEBUG } #if WITH_MASSENTITY_DEBUG void FMassEntityManager::DebugPrintArchetypes(FOutputDevice& Ar, const bool bIncludeEmpty) const { Ar.Logf(ELogVerbosity::Log, TEXT("Listing archetypes contained in EntityManager owned by %s"), *GetPathNameSafe(GetOwner())); int32 NumBuckets = 0; int32 NumArchetypes = 0; int32 LongestArchetypeBucket = 0; for (const auto& KVP : FragmentHashToArchetypeMap) { for (const TSharedPtr& ArchetypePtr : KVP.Value) { if (ArchetypePtr.IsValid() && (bIncludeEmpty == true || ArchetypePtr->GetChunkCount() > 0)) { ArchetypePtr->DebugPrintArchetype(Ar); } } const int32 NumArchetypesInBucket = KVP.Value.Num(); LongestArchetypeBucket = FMath::Max(LongestArchetypeBucket, NumArchetypesInBucket); NumArchetypes += NumArchetypesInBucket; ++NumBuckets; } Ar.Logf(ELogVerbosity::Log, TEXT("FragmentHashToArchetypeMap: %d archetypes across %d buckets, longest bucket is %d"), NumArchetypes, NumBuckets, LongestArchetypeBucket); } void FMassEntityManager::DebugGetArchetypesStringDetails(FOutputDevice& Ar, const bool bIncludeEmpty) const { Ar.SetAutoEmitLineTerminator(true); for (auto Pair : FragmentHashToArchetypeMap) { Ar.Logf(ELogVerbosity::Log, TEXT("\n-----------------------------------\nHash: %u"), Pair.Key); for (TSharedPtr Archetype : Pair.Value) { if (Archetype.IsValid() && (bIncludeEmpty == true || Archetype->GetChunkCount() > 0)) { Archetype->DebugPrintArchetype(Ar); Ar.Logf(ELogVerbosity::Log, TEXT("+++++++++++++++++++++++++\n")); } } } } void FMassEntityManager::DebugGetArchetypeFragmentTypes(const FMassArchetypeHandle& Archetype, TArray& InOutFragmentList) const { if (Archetype.IsValid()) { const FMassArchetypeData& ArchetypeData = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(Archetype); ArchetypeData.GetCompositionDescriptor().Fragments.ExportTypes(InOutFragmentList); } } int32 FMassEntityManager::DebugGetArchetypeEntitiesCount(const FMassArchetypeHandle& Archetype) const { return Archetype.IsValid() ? FMassArchetypeHelper::ArchetypeDataFromHandleChecked(Archetype).GetNumEntities() : 0; } int32 FMassEntityManager::DebugGetArchetypeEntitiesCountPerChunk(const FMassArchetypeHandle& Archetype) const { return Archetype.IsValid() ? FMassArchetypeHelper::ArchetypeDataFromHandleChecked(Archetype).GetNumEntitiesPerChunk() : 0; } int32 FMassEntityManager::DebugGetEntityCount() const { return GetEntityStorageInterface().Num() - NumReservedEntities - GetEntityStorageInterface().ComputeFreeSize(); } int32 FMassEntityManager::DebugGetArchetypesCount() const { return AllArchetypes.Num(); } void FMassEntityManager::DebugRemoveAllEntities() { for (int EntityIndex = NumReservedEntities, EndIndex = GetEntityStorageInterface().Num(); EntityIndex < EndIndex; ++EntityIndex) { if (GetEntityStorageInterface().IsValid(EntityIndex) == false) { // already dead continue; } FMassArchetypeData* Archetype = GetEntityStorageInterface().GetArchetype(EntityIndex); check(Archetype); FMassEntityHandle Entity; Entity.Index = EntityIndex; Entity.SerialNumber = GetEntityStorageInterface().GetSerialNumber(EntityIndex); Archetype->RemoveEntity(Entity); GetEntityStorageInterface().ForceReleaseOne(Entity); } } void FMassEntityManager::DebugForceArchetypeDataVersionBump() { ++ArchetypeDataVersion; } void FMassEntityManager::DebugGetArchetypeStrings(const FMassArchetypeHandle& Archetype, TArray& OutFragmentNames, TArray& OutTagNames) { if (Archetype.IsValid() == false) { return; } const FMassArchetypeData& ArchetypeRef = FMassArchetypeHelper::ArchetypeDataFromHandleChecked(Archetype); OutFragmentNames.Reserve(ArchetypeRef.GetFragmentConfigs().Num()); for (const FMassArchetypeFragmentConfig& FragmentConfig : ArchetypeRef.GetFragmentConfigs()) { checkSlow(FragmentConfig.FragmentType); OutFragmentNames.Add(FragmentConfig.FragmentType->GetFName()); } ArchetypeRef.GetTagBitSet().DebugGetIndividualNames(OutTagNames); } FMassEntityHandle FMassEntityManager::DebugGetEntityIndexHandle(const int32 EntityIndex) const { return (GetEntityStorageInterface().IsValidIndex(EntityIndex) && GetEntityStorageInterface().GetEntityState(EntityIndex) == UE::Mass::IEntityStorageInterface::EEntityState::Created) ? FMassEntityHandle(EntityIndex, GetEntityStorageInterface().GetSerialNumber(EntityIndex)) : FMassEntityHandle(); } const FString& FMassEntityManager::DebugGetName() const { return DebugName; } void FMassEntityManager::DebugEnableDebugFeature(EDebugFeatures Features) { EnumAddFlags(EnabledDebugFeatures, Features); } void FMassEntityManager::DebugDisableDebugFeature(EDebugFeatures Features) { EnumRemoveFlags(EnabledDebugFeatures, Features); } bool FMassEntityManager::DebugHasAllDebugFeatures(EDebugFeatures Features) const { return EnumHasAllFlags(EnabledDebugFeatures, Features); } FMassRequirementAccessDetector& FMassEntityManager::GetRequirementAccessDetector() { return RequirementAccessDetector; } UE::Mass::FStorageType& FMassEntityManager::DebugGetEntityStorageInterface() { return GetEntityStorageInterface(); } const UE::Mass::FStorageType& FMassEntityManager::DebugGetEntityStorageInterface() const { return GetEntityStorageInterface(); } bool FMassEntityManager::DebugHasCommandsToFlush() const { checkfSlow(NumCommandBuffers == 2, TEXT("This check relies on there being two command buffers.")); return DeferredCommandBuffers[0]->HasPendingCommands() || DeferredCommandBuffers[1]->HasPendingCommands(); } #endif // WITH_MASSENTITY_DEBUG //----------------------------------------------------------------------------- // DEPRECATED //----------------------------------------------------------------------------- FMassArchetypeHandle FMassEntityManager::InternalCreateSimilarArchetype(const FMassArchetypeData& SourceArchetypeRef, FMassArchetypeCompositionDescriptor&& NewComposition) { return InternalCreateSimilarArchetype(SourceArchetypeRef, Forward(NewComposition), {}); } void FMassEntityManager::SetEntityFragmentsValues(FMassEntityHandle Entity, TArrayView FragmentInstanceList) { SetEntityFragmentValues(Entity, FragmentInstanceList); } void FMassEntityManager::BatchSetEntityFragmentsValues(const FMassArchetypeEntityCollection& SparseEntities, TArrayView FragmentInstanceList) { ensureMsgf(false, TEXT("The static BatchSetEntityFragmentsValues is not expected to be called anymore. There's no way to deduce the EntityManager instance related to the call")); } void FMassEntityManager::BatchSetEntityFragmentsValues(TConstArrayView EntityCollections, TArrayView FragmentInstanceList) { ensureMsgf(false, TEXT("The static BatchSetEntityFragmentsValues is not expected to be called anymore. There's no way to deduce the EntityManager instance related to the call")); } #undef CHECK_SYNC_API