// Copyright Epic Games, Inc. All Rights Reserved. #include "EntitySystem/MovieSceneEntityManager.h" #include "EntitySystem/MovieSceneEntityMutations.h" #include "EntitySystem/MovieSceneComponentRegistry.h" #include "EntitySystem/BuiltInComponentTypes.h" #include "Algo/Find.h" #include "UObject/StrongObjectPtr.h" #include "Containers/SortedMap.h" #include "AutoRTFM.h" #include "HAL/PlatformProcess.h" #include "Misc/FeedbackContext.h" #include "EntitySystem/EntityAllocationIterator.h" #include "AutoRTFM.h" #if DO_GUARD_SLOW bool GCheckMovieSceneEntityManagerInvariants = false; FAutoConsoleVariableRef CVarCheckMovieSceneEntityManagerInvariants( TEXT("Sequencer.CheckEntityManagerInvariants"), GCheckMovieSceneEntityManagerInvariants, TEXT("Defines whether FEntityManager invariants should be checked on mutation or not. Note: severely impairs performance.\n"), ECVF_Default ); #endif namespace UE { namespace MovieScene { FComponentMask GEntityManagerEmptyMask; // @todo: this is a very rough initial guess at the break even point for when threaded evaluation becomes beneficial, and will vary highly between platforms and hardware. // We may wish to make this more flexible in future (such as only threading hot paths such as float channel evaluation) by enabling threading per-task, but more data is required to make such decisions int32 GThreadedEvaluationAllocationThreshold = 32; FAutoConsoleVariableRef CVarThreadedEvaluationAllocationThreshold( TEXT("Sequencer.ThreadedEvaluation.AllocationThreshold"), GThreadedEvaluationAllocationThreshold, TEXT("(Default: 32) Defines the entity allocation fragmentation threshold above which threaded evaluation will be used.\n"), ECVF_Default ); int32 GThreadedEvaluationEntityThreshold = 256; FAutoConsoleVariableRef CVarThreadedEvaluationEntityThreshold( TEXT("Sequencer.ThreadedEvaluation.EntityThreshold"), GThreadedEvaluationEntityThreshold, TEXT("(Default: 256) Defines the number of entities that need to exist to justify threaded evaluation.\n"), ECVF_Default ); #if UE_MOVIESCENE_ENTITY_DEBUG bool GRichComponentDebuggingInitialized = false; bool GRichComponentDebugging = false; FAutoConsoleVariableRef CVarRichComponentDebugging( TEXT("Sequencer.RichComponentDebugging"), GRichComponentDebugging, TEXT("(Default: false. Whether to enable rich component debugging within Sequencer.") ); #endif // UE_MOVIESCENE_ENTITY_DEBUG FEntityManager* GEntityManagerForDebuggingVisualizers = nullptr; static bool IsValidUint16(int32 Test) { return (Test & 0xFFFF0000) == 0; } struct FEntityInitializer { static void AddEntity(FEntityAllocation* Allocation, int32 ActualOffsetWithinAllocation, FMovieSceneEntityID EntityID) { check( IsValidUint16(ActualOffsetWithinAllocation) ); check(ActualOffsetWithinAllocation == Allocation->Size); // Assign the currently free entry to the specified offset Allocation->EntityIDs[ActualOffsetWithinAllocation] = EntityID; ++Allocation->Size; } static void MoveEntryIndex(FEntityAllocation* Allocation, int32 InCurrentOffset, int32 InNewOffset) { check( IsValidUint16(InCurrentOffset) && IsValidUint16(InNewOffset) && InNewOffset < Allocation->Size ); Allocation->EntityIDs[InNewOffset] = Allocation->EntityIDs[InCurrentOffset]; } static void MigrateAllocation(FEntityAllocation* DestAllocation, FEntityAllocation* SrcAllocation, const FComponentRegistry* InComponentRegistry) { check(DestAllocation->Capacity - DestAllocation->Size >= SrcAllocation->Size); const int32 NumEntities = SrcAllocation->Size; const int32 DstStartOffset = DestAllocation->Size; DestAllocation->Size += SrcAllocation->Size; // Initialize entity IDs FMemory::Memcpy(DestAllocation->EntityIDs + DstStartOffset, SrcAllocation->EntityIDs, sizeof(FMovieSceneEntityID)*NumEntities); TArrayView DstHeaders = DestAllocation->GetComponentHeaders(); TArrayView SrcHeaders = SrcAllocation->GetComponentHeaders(); check(DstHeaders.Num() == SrcHeaders.Num()); for (int32 HeaderIndex = 0; HeaderIndex < DstHeaders.Num(); ++HeaderIndex) { const FComponentHeader* SrcHeader = &SrcHeaders[HeaderIndex]; const FComponentHeader* DstHeader = &DstHeaders[HeaderIndex]; check(DstHeader->ComponentType == SrcHeader->ComponentType); if (!SrcHeader->IsTag()) { const FComponentTypeInfo& ComponentTypeInfo = InComponentRegistry->GetComponentTypeChecked(SrcHeader->ComponentType); void* DstValue = DstHeader->GetValuePtr(DstStartOffset); void* SrcValue = SrcHeader->GetValuePtr(0); ComponentTypeInfo.RelocateConstructItems(DstValue, SrcValue, NumEntities); // Mark these components as having be relocated, so they don't need to be destructed later. SrcHeader->Components = nullptr; } } SrcAllocation->Size = 0; } static void SetEntryIndex(FEntityAllocation* Allocation, int32 InEntityOffset, FMovieSceneEntityID EntityID) { check( IsValidUint16(InEntityOffset) && InEntityOffset < Allocation->Size && EntityID ); Allocation->EntityIDs[InEntityOffset] = EntityID; } static void FreeEntryIndex(FEntityAllocation* Allocation, int32 EntityIndex) { check( IsValidUint16(EntityIndex) ); // This entry is now free Allocation->EntityIDs[EntityIndex] = FMovieSceneEntityID::Invalid(); --Allocation->Size; } struct FEntityAllocationInitializationInfo { uint32 AllocationID = (uint32)-1; int32 NumComponents = 0; uint16 InitialCapacity = 0; uint16 MaxCapacity = 0; SIZE_T SizeofComponentHeaders = 0; SIZE_T SizeofEntityIDs = 0; // If size is valid and pointer is null, allocate a new data buffer. // If size is 0 and pointer is non-null, steal the given allocation's buffer. // Both can't be valid at the same time. SIZE_T SizeofComponentData = 0; FEntityAllocation* MigrateComponentDataFrom = nullptr; }; static FEntityAllocation* Initialize(const FEntityManager& EntityManager, const FComponentMask& EntityComponentMask, const FEntityAllocationInitializationInfo& InitInfo) { const FEntityAllocationWriteContext WriteContext(EntityManager); check(IsValidUint16(InitInfo.NumComponents)); // Compute the size that we need: struct size + component headers array + entity IDs array. const SIZE_T RawStructSize = sizeof(FEntityAllocation); const SIZE_T TotalAllocationSize = RawStructSize + alignof(FComponentHeader) + InitInfo.SizeofComponentHeaders + alignof(FMovieSceneEntityID) + InitInfo.SizeofEntityIDs; // Allocate the structure. uint8* const AllocationStart = (uint8*)FMemory::Malloc(TotalAllocationSize); FEntityAllocation* const Allocation = new (AllocationStart) FEntityAllocation(); // Initialize the structure. Allocation->UniqueID = InitInfo.AllocationID; Allocation->NumComponents = InitInfo.NumComponents; Allocation->Size = 0; Allocation->Capacity = InitInfo.InitialCapacity; Allocation->MaxCapacity = InitInfo.MaxCapacity; Allocation->SerialNumber = WriteContext.GetSystemSerial(); // Fixup pointer offsets: // // ComponentHeaders exist right after the main structure. uint8* RawComponentHeadersPtr = AllocationStart + sizeof(FEntityAllocation); Allocation->ComponentHeaders = reinterpret_cast(Align(RawComponentHeadersPtr, alignof(FComponentHeader))); // EntityIDs exist right after the component headers. uint8* RawEntityIDsPtr = reinterpret_cast(Allocation->ComponentHeaders) + InitInfo.SizeofComponentHeaders; Allocation->EntityIDs = reinterpret_cast(Align(RawEntityIDsPtr, alignof(FMovieSceneEntityID))); // Component data buffer: allocate, or re-use/share/migrate. check((InitInfo.SizeofComponentData > 0 && InitInfo.MigrateComponentDataFrom == nullptr) || (InitInfo.SizeofComponentData == 0 && InitInfo.MigrateComponentDataFrom != nullptr)); if (InitInfo.MigrateComponentDataFrom != nullptr) { Allocation->ComponentData = InitInfo.MigrateComponentDataFrom->ComponentData; // Mark the original allocation's buffer as "stolen" so we don't free it later. InitInfo.MigrateComponentDataFrom->ComponentData = nullptr; for (FComponentHeader& ComponentHeader : InitInfo.MigrateComponentDataFrom->GetComponentHeaders()) { ComponentHeader.Components = nullptr; } } else if (InitInfo.SizeofComponentData > 0) { uint8* const ComponentDataPtrStart = (uint8*)FMemory::Malloc(InitInfo.SizeofComponentData); Allocation->ComponentData = ComponentDataPtrStart; } // Initialize component headers. { uint8* ComponentDataPtr = Allocation->ComponentData; FComponentHeader* Header = Allocation->ComponentHeaders; for (FComponentMaskIterator It = EntityComponentMask.Iterate(); It; ++It, ++Header) { FComponentTypeID ComponentTypeID = FComponentTypeID::FromBitIndex(It.GetIndex()); const FComponentTypeInfo& TypeInfo = EntityManager.GetComponents()->GetComponentTypeChecked(ComponentTypeID); #if UE_MOVIESCENE_ENTITY_DEBUG if (GRichComponentDebugging) { TypeInfo.DebugInfo->InitializeComponentHeader(Header); Header->Size = &Allocation->Size; } else #endif { UE_AUTORTFM_OPEN { new (Header) FComponentHeader(); }; UE_AUTORTFM_ONABORT(Header) { Header->~FComponentHeader(); }; } Header->ComponentType = ComponentTypeID; Header->Sizeof = TypeInfo.Sizeof; Header->PostWriteComponents(WriteContext); if (TypeInfo.IsTag()) { Header->Components = nullptr; } else { // We align explicitly to cache lines to remove thread contention on read/write of neighboring component types uint8 Alignment = FMath::Max(PLATFORM_CACHE_LINE_SIZE, TypeInfo.Alignment); ComponentDataPtr = Align(ComponentDataPtr, Alignment); Header->ScheduledAccessCount.exchange(0, std::memory_order_relaxed); Header->Components = reinterpret_cast(ComponentDataPtr); check(IsAligned(Header->Components, TypeInfo.Alignment)); ComponentDataPtr += TypeInfo.Sizeof * InitInfo.InitialCapacity; #if UE_MOVIESCENE_ENTITY_DEBUG if (GRichComponentDebugging) { TypeInfo.DebugInfo->InitializeDebugComponentData(*Header, InitInfo.InitialCapacity); } #endif } } } return Allocation; } static void Duplicate(FEntityAllocation* Dest, const FEntityAllocation* Source) { Dest->Size = Source->Size; FMemory::Memcpy(Dest->EntityIDs, Source->EntityIDs, sizeof(FMovieSceneEntityID)*Source->Size); } static void TearDown(FEntityAllocation* Allocation) { check(Allocation != nullptr); if (Allocation->ComponentData != nullptr) { FMemory::Free(Allocation->ComponentData); } FMemory::Free(Allocation); } }; void FFreeEntityOperation::MarkAllocationForFree(int32 AllocationIndex) { AllocationsToDestroy.Add(AllocationIndex); } void FFreeEntityOperation::MarkEntityForFree(FMovieSceneEntityID EntityID) { LooseEntitiesToDestroy.Add(EntityID); } FFreeEntityOperation::FCommitData FFreeEntityOperation::Commit() const { FFreeEntityOperation::FCommitData CommitData; FBuiltInComponentTypes* BuiltInComponents = FBuiltInComponentTypes::Get(); TArray> EntitiesScratch; auto MarkChildrenForFree = [this, &EntitiesScratch, &CommitData, BuiltInComponents](FMovieSceneEntityID Entity) { for (auto ChildIt = this->EntityManager->ParentToChild.CreateKeyIterator(Entity); ChildIt; ++ChildIt) { EntitiesScratch.Add(ChildIt.Value()); } while (EntitiesScratch.Num() != 0) { const int32 StartNum = EntitiesScratch.Num(); for (int32 MarkIndex = 0; MarkIndex < StartNum; ++MarkIndex) { FMovieSceneEntityID MarkedEntity = EntitiesScratch[MarkIndex]; FEntityManager::FEntityLocation Location = EntityManager->EntityLocations[MarkedEntity.AsIndex()]; if (Location.IsValid()) { ensureAlwaysMsgf(EntityManager->EntityAllocationMasks[Location.GetAllocationIndex()].Contains(BuiltInComponents->Tags.NeedsUnlink), TEXT("Attempting to free an entity that has not been unlinked - this might result in stale references")); FAllocationMask& Mask = CommitData.AllocationsToEntities.FindOrAdd(Location.GetAllocationIndex()); if (!Mask.bDestroyAllocation) { const int32 BitIndex = Location.GetEntryIndexWithinAllocation(); Mask.Mask.PadToNum(BitIndex + 1, false); Mask.Mask[BitIndex] = true; } } else { CommitData.EmptyEntities.Add(MarkedEntity); } // Mark all children for (auto ChildIt = EntityManager->ParentToChild.CreateKeyIterator(MarkedEntity); ChildIt; ++ChildIt) { EntitiesScratch.Add(ChildIt.Value()); } } // Remove current iteration EntitiesScratch.RemoveAtSwap(0, StartNum, EAllowShrinking::No); } }; // Populate full allocations for (int32 AllocationIndex : AllocationsToDestroy) { FEntityAllocation* Allocation = EntityManager->EntityAllocations[AllocationIndex]; CommitData.AllocationsToEntities.Add(AllocationIndex).bDestroyAllocation = true; for (FMovieSceneEntityID Entity : Allocation->GetEntityIDs()) { MarkChildrenForFree(Entity); } } for (FMovieSceneEntityID Entity : LooseEntitiesToDestroy) { FEntityManager::FEntityLocation Location = EntityManager->EntityLocations[Entity.AsIndex()]; if (Location.IsValid()) { FAllocationMask& Mask = CommitData.AllocationsToEntities.FindOrAdd(Location.GetAllocationIndex()); if (!Mask.bDestroyAllocation) { const int32 BitIndex = Location.GetEntryIndexWithinAllocation(); Mask.Mask.PadToNum(BitIndex + 1, false); Mask.Mask[BitIndex] = true; } } else { CommitData.EmptyEntities.Add(Entity); } MarkChildrenForFree(Entity); } return CommitData; } FEntityManager::FEntityManager() { IterationCount = 0; NextAllocationID = 0; CurrentHandleGeneration = 0; bHandleGenerationStale = false; GatherThread = ENamedThreads::AnyThread; DispatchThread = ENamedThreads::AnyThread; ManagerDebugName = TEXT("UE::MovieScene::FEntityManager"); LockdownState = ELockdownState::Unlocked; SystemSerialNumber = 1; StructureMutationSystemSerialNumber = 0; ThreadingModel = EEntityThreadingModel::NoThreading; #if UE_MOVIESCENE_ENTITY_DEBUG if (!GRichComponentDebuggingInitialized) { GRichComponentDebugging = FPlatformMisc::IsDebuggerPresent(); GRichComponentDebuggingInitialized = true; } RichComponentDebuggingPtr = &GRichComponentDebugging; #endif } FEntityManager::~FEntityManager() { // Call destructors for any allocated component data for (FEntityAllocation* Allocation : EntityAllocations) { DestroyAllocation(Allocation); } } void FEntityManager::DestroyAllocation(FEntityAllocation* Allocation, bool bDestructComponentData) { CheckCanChangeStructure(); if (Allocation->Num() > 0 && bDestructComponentData) { for (const FComponentHeader& Header : Allocation->GetComponentHeaders()) { if (Header.HasData()) // If this is NOT a tag, nor is it a component header whose data buffer was relocated... { check(Header.Components != nullptr); const FComponentTypeInfo& ComponentTypeInfo = ComponentRegistry->GetComponentTypeChecked(Header.ComponentType); ComponentTypeInfo.DestructItems(Header.Components, Allocation->Num()); } } } Allocation->~FEntityAllocation(); FEntityInitializer::TearDown(Allocation); } void FEntityManager::Destroy() { CheckCanChangeStructure(); bHandleGenerationStale = true; EntityGenerationMap.Reset(); for (FEntityAllocation* Allocation : EntityAllocations) { DestroyAllocation(Allocation); } EntityLocations.Reset(); AllocationsWithCapacity.Reset(); EntityAllocationMasks.Reset(); EntityAllocations.Reset(); ParentToChild.Reset(); OnStructureChanged(); } FMovieSceneEntityID FEntityManager::AllocateEntity() { CheckCanChangeStructure(); const int32 NewEntityIndex = EntityLocations.Add(FEntityLocation()); return FMovieSceneEntityID::FromIndex(NewEntityIndex); } FEntityInfo FEntityManager::AllocateEntity(const FComponentMask& EntityComponentMask) { if (EntityComponentMask.Find(true) == INDEX_NONE) { FMovieSceneEntityID NewEntityID = AllocateEntity(); return FEntityInfo{ FEntityDataLocation{ nullptr, INDEX_NONE }, NewEntityID }; } checkf(LockdownState == ELockdownState::Unlocked, TEXT("Structural changes to the entity manager are not permitted while it is locked down")); CheckCanChangeStructure(); const int32 NewEntityIndex = EntityLocations.Add(FEntityLocation{}); FMovieSceneEntityID NewEntityID = FMovieSceneEntityID::FromIndex(NewEntityIndex); int32 AllocationIndex = GetOrCreateAllocationWithSlack(EntityComponentMask); int32 EntryIndexWithinAllocation = AddEntityToAllocation(AllocationIndex, NewEntityID); EntityLocations[NewEntityIndex].Set(AllocationIndex, EntryIndexWithinAllocation); FEntityInfo NewEntity = { FEntityDataLocation { EntityAllocations[AllocationIndex], EntryIndexWithinAllocation }, NewEntityID }; OnStructureChanged(); return NewEntity; } FEntityDataLocation FEntityManager::AllocateContiguousEntities(const FComponentMask& EntityComponentMask, int32* InOutNum) { check(InOutNum && *InOutNum >= 1); CheckCanChangeStructure(); const int32 AllocationIndex = GetOrCreateAllocationWithSlack(EntityComponentMask, InOutNum); const int32 NumAllocated = *InOutNum; const int32 FirstEntityIndex = EntityLocations.Add(FEntityLocation{}); FMovieSceneEntityID FirstEntityID = FMovieSceneEntityID::FromIndex(FirstEntityIndex); int32 FirstComponentOffset = AddEntityToAllocation(AllocationIndex, FirstEntityID); EntityLocations[FirstEntityIndex].Set(AllocationIndex, FirstComponentOffset); for (int32 Index = 1; Index < NumAllocated; ++Index) { const int32 NewEntityIndex = EntityLocations.Add(FEntityLocation{}); FMovieSceneEntityID NewEntityID = FMovieSceneEntityID::FromIndex(NewEntityIndex); int32 EntryIndexWithinAllocation = AddEntityToAllocation(AllocationIndex, NewEntityID); EntityLocations[NewEntityIndex].Set(AllocationIndex, EntryIndexWithinAllocation); } FEntityDataLocation DataLocation = { EntityAllocations[AllocationIndex], FirstComponentOffset }; CheckInvariants(); OnStructureChanged(); return DataLocation; } FEntityInfo FEntityManager::GetEntity(FMovieSceneEntityID EntityID) const { const int32 Index = EntityID.AsIndex(); check(EntityLocations.IsValidIndex(Index)); FEntityLocation Location = EntityLocations[Index]; if (Location.IsValid()) { return FEntityInfo { FEntityDataLocation{ EntityAllocations[Location.GetAllocationIndex()], Location.GetEntryIndexWithinAllocation() }, EntityID }; } return FEntityInfo { FEntityDataLocation{ nullptr, INDEX_NONE }, EntityID }; } FEntityHandle FEntityManager::GetEntityHandle(FMovieSceneEntityID EntityID) { if (!EntityID) { return FEntityHandle{}; } checkSlow(EntityLocations.IsValidIndex(EntityID.AsIndex())); // Does a handle already exist for this entityID? const uint32* ExistingGeneration = EntityGenerationMap.Find(EntityID); if (ExistingGeneration) { return FEntityHandle(EntityID, *ExistingGeneration); } const uint32 NewHandleGeneration = GetHandleGeneration(); EntityGenerationMap.Add(EntityID, NewHandleGeneration); return FEntityHandle(EntityID, NewHandleGeneration); } bool FEntityManager::IsHandleValid(FEntityHandle InEntityHandle) const { if (!InEntityHandle.ID) { return false; } const uint32* Generation = EntityGenerationMap.Find(InEntityHandle.ID); return Generation && *Generation == InEntityHandle.HandleGeneration; } EEntityThreadingModel FEntityManager::ComputeThreadingModel() const { const bool bCanThread = FPlatformProcess::SupportsMultithreading(); const bool bShouldThread = bCanThread && (EntityAllocations.Num() >= GThreadedEvaluationAllocationThreshold || EntityLocations.Num() >= GThreadedEvaluationEntityThreshold); return bShouldThread ? EEntityThreadingModel::TaskGraph : EEntityThreadingModel::NoThreading; } EEntityThreadingModel FEntityManager::GetThreadingModel() const { return ThreadingModel; } void FEntityManager::UpdateThreadingModel() { ThreadingModel = ComputeThreadingModel(); } const FComponentMask& FEntityManager::GetAccumulatedMask() const { FEntityManager* This = const_cast(this); if (This->bAccumulatedMaskStale == true) { This->AccumulatedMask.Reset(); for (const FComponentMask& Mask : EntityAllocationMasks) { This->AccumulatedMask.CombineWithBitwiseOR(Mask, EBitwiseOperatorFlags::MaxSize); } This->bAccumulatedMaskStale = false; } return AccumulatedMask; } void FEntityManager::FreeEntity(FMovieSceneEntityID EntityID) { check(EntityLocations.IsValidIndex(EntityID.AsIndex())); CheckCanChangeStructure(); FFreeEntityOperation Operation(this); Operation.MarkEntityForFree(EntityID); FreeEntities(Operation); } int32 FEntityManager::FreeEntities(const FEntityComponentFilter& Filter, TSet* OutFreedEntities) { CheckCanChangeStructure(); check(Filter.IsValid()); FFreeEntityOperation Operation(this); for (auto AllocationIt = EntityAllocationMasks.CreateIterator(); AllocationIt; ++AllocationIt) { const int32 AllocationIndex = AllocationIt.GetIndex(); if (Filter.Match(EntityAllocationMasks[AllocationIndex])) { Operation.MarkAllocationForFree(AllocationIndex); } } return FreeEntities(Operation, OutFreedEntities); } int32 FEntityManager::FreeEntities(const FFreeEntityOperation& Operation, TSet* OutFreedEntities) { CheckCanChangeStructure(); if (Operation.LooseEntitiesToDestroy.Num() == 0 && Operation.AllocationsToDestroy.Num() == 0) { return 0; } FEntityAllocationWriteContext WriteContext(*this); FBuiltInComponentTypes* BuiltInComponents = FBuiltInComponentTypes::Get(); FFreeEntityOperation::FCommitData Committed = Operation.Commit(); auto ReleaseEntity = [this, OutFreedEntities](FMovieSceneEntityID EntityID) { const int32 Index = EntityID.AsIndex(); FEntityLocation Location = EntityLocations[Index]; if (Location.GetParentID()) { ParentToChild.Remove(Location.GetParentID(), EntityID); } ParentToChild.Remove(EntityID); EntityGenerationMap.Remove(EntityID); EntityLocations.RemoveAt(Index); if (OutFreedEntities) { OutFreedEntities->Add(EntityID); } }; const int32 StartingHandleCount = EntityGenerationMap.Num(); int32 NumFreed = Committed.EmptyEntities.Num(); // First off free any entities that have no data for (FMovieSceneEntityID EntityID : Committed.EmptyEntities) { ReleaseEntity(EntityID); } // Next go through all the allocations that have entities to be removed TBitArray<> ReversedSetBits; for (const TTuple& Pair : Committed.AllocationsToEntities) { const int32 AllocationIndex = Pair.Key; FEntityAllocation* Allocation = EntityAllocations[AllocationIndex]; TArrayView EntityIDs = Allocation->GetEntityIDs(); const int32 Num = Allocation->Num(); const int32 NumToFree = Pair.Value.bDestroyAllocation ? Num : Pair.Value.Mask.CountSetBits(); ensureMsgf(Allocation->HasComponent(BuiltInComponents->Tags.NeedsUnlink), TEXT("Attempting to free an entity that has not been unlinked - this might result in stale references")); NumFreed += NumToFree; // If we're freeing everything, just destroy the whole allocation if (NumToFree == Num) { for (FMovieSceneEntityID Entity : EntityIDs) { ReleaseEntity(Entity); } DestroyAllocation(Allocation); EntityAllocations.RemoveAt(AllocationIndex); EntityAllocationMasks.RemoveAt(AllocationIndex); AllocationsWithCapacity[AllocationIndex] = false; continue; } const bool bHadCapacity = Allocation->Num() != Allocation->GetMaxCapacity(); int32 LastEntityIndex = Num - 1; // Modify allocation and headers Allocation->PostModifyStructure(WriteContext); // Reverse the set bits so we can iterate backwards const int32 MaskSize = Pair.Value.Mask.Num(); ReversedSetBits.Init(false, MaskSize+1); for (TConstSetBitIterator<> SetBit(Pair.Value.Mask); SetBit; ++SetBit) { const int32 ReversedIndex = MaskSize - SetBit.GetIndex(); ReversedSetBits[ReversedIndex] = true; } // Iterate the entities to free starting from the last for (TConstSetBitIterator<> SetBit(ReversedSetBits); SetBit; ++SetBit) { const int32 EntityOffset = MaskSize - SetBit.GetIndex(); FMovieSceneEntityID Entity = EntityIDs[EntityOffset]; // Destruct its components for (FComponentHeader& Header : Allocation->GetComponentHeaders()) { if (!Header.IsTag()) { const FComponentTypeInfo& ComponentTypeInfo = ComponentRegistry->GetComponentTypeChecked(Header.ComponentType); void* Value = Header.GetValuePtr(EntityOffset); ComponentTypeInfo.DestructItems(Value, 1); if (LastEntityIndex != EntityOffset) { void* SwapSource = Header.GetValuePtr(LastEntityIndex); ComponentTypeInfo.RelocateConstructItems(Value, SwapSource, 1); } } } // When removing we just swap the tail element with the element to remove, and fix up the indices if (LastEntityIndex != EntityOffset) { FEntityInitializer::MoveEntryIndex(Allocation, LastEntityIndex, EntityOffset); // Fixup entry offset for the changed entry FMovieSceneEntityID SwappedEntityID = Allocation->GetEntityIDs()[EntityOffset]; EntityLocations[SwappedEntityID.AsIndex()].Set(AllocationIndex, EntityOffset); } // Free this entity index without needing to fix up any other indices FEntityInitializer::FreeEntryIndex(Allocation, LastEntityIndex); ReleaseEntity(Entity); --LastEntityIndex; } if (!bHadCapacity) { AllocationsWithCapacity[AllocationIndex] = true; } } if (StartingHandleCount != EntityGenerationMap.Num()) { bHandleGenerationStale = true; } CheckInvariants(); OnStructureChanged(); return NumFreed; } void FEntityManager::Compact() { // Step 1: Combine equal allocations for (int32 AllocationIndex = 0; AllocationIndex < EntityAllocations.GetMaxIndex(); ++AllocationIndex) { if (!EntityAllocations.IsAllocated(AllocationIndex)) { continue; } int32 CombineWithIndex = INDEX_NONE; int32 RequiredSlack = EntityAllocations[AllocationIndex]->Num(); // Find an allocation we can combine wtih for (TConstSetBitIterator<> It(AllocationsWithCapacity); It && It.GetIndex() < AllocationIndex; ++It) { const int32 PotentialCombinationIndex = It.GetIndex(); if (EntityAllocationMasks[PotentialCombinationIndex].CompareSetBits(EntityAllocationMasks[AllocationIndex]) == true) { FEntityAllocation* CombineWithAllocation = EntityAllocations[PotentialCombinationIndex]; if (CombineWithAllocation->GetMaxCapacity() - CombineWithAllocation->Num() >= RequiredSlack) { CombineWithIndex = PotentialCombinationIndex; break; } } } if (CombineWithIndex != INDEX_NONE) { // Combine allocations will destroy the entry at AllocationReadIndex, so we do not increment AllocationWriteIndex // So this allocation entry can be reused CombineAllocations(CombineWithIndex, AllocationIndex); } } // Step 2: Remove empty allocations int32 AllocationReadIndex = 0; int32 AllocationWriteIndex = 0; TMap OldToNewIndex; for (; AllocationReadIndex < EntityAllocations.GetMaxIndex(); ++AllocationReadIndex) { if (!EntityAllocations.IsAllocated(AllocationReadIndex)) { continue; } FEntityAllocation* Allocation = EntityAllocations[AllocationReadIndex]; if (Allocation->Num() == 0) { DestroyAllocation(Allocation); AllocationsWithCapacity[AllocationReadIndex] = false; continue; } if (AllocationReadIndex != AllocationWriteIndex) { EntityAllocations.Insert(AllocationWriteIndex, Allocation); EntityAllocations.RemoveAt(AllocationReadIndex); EntityAllocationMasks.Insert(AllocationWriteIndex, EntityAllocationMasks[AllocationReadIndex]); EntityAllocationMasks.RemoveAt(AllocationReadIndex); AllocationsWithCapacity[AllocationWriteIndex] = AllocationsWithCapacity[AllocationReadIndex]; AllocationsWithCapacity[AllocationReadIndex] = false; OldToNewIndex.Add(AllocationReadIndex, AllocationWriteIndex); } ++AllocationWriteIndex; } if (OldToNewIndex.Num() > 0) { for (FEntityLocation& TypeInfo : EntityLocations) { if (TypeInfo.IsValid()) { if (const int32* NewIndex = OldToNewIndex.Find(TypeInfo.GetAllocationIndex())) { TypeInfo.Set(*NewIndex, TypeInfo.GetEntryIndexWithinAllocation()); } } } } if (AllocationWriteIndex != AllocationReadIndex) { EntityAllocations.Shrink(); EntityAllocationMasks.Shrink(); // TBitArray does not have a Shrink method, so we copy it to a new one with the smallest number of bytes then swap it back AllocationsWithCapacity.RemoveAt(AllocationWriteIndex, AllocationsWithCapacity.Num() - AllocationWriteIndex - 1); TBitArray<> Temp = AllocationsWithCapacity; Swap(AllocationsWithCapacity, Temp); } EntityLocations.Shrink(); CheckInvariants(); OnStructureChanged(); } void FEntityManager::ReplaceEntityID(FMovieSceneEntityID& InOutEntity, FMovieSceneEntityID NewEntityID) { if (!NewEntityID) { if (InOutEntity) { FreeEntity(InOutEntity); InOutEntity = FMovieSceneEntityID(); } } else { // At this point we know that we have a loaded entity - if we're loading over the top of another, handle that now if (InOutEntity) { check(EntityLocations.IsValidIndex(InOutEntity.AsIndex())); if (ComponentRegistry->GetPreservationMask().Find(true) != INDEX_NONE) { CombineComponents(NewEntityID, InOutEntity, &ComponentRegistry->GetPreservationMask()); } FEntityLocation NewLocation = EntityLocations[NewEntityID.AsIndex()]; FEntityLocation OldLocation = EntityLocations[InOutEntity.AsIndex()]; if (NewLocation.IsValid()) { FEntityInitializer::SetEntryIndex(EntityAllocations[NewLocation.GetAllocationIndex()], NewLocation.GetEntryIndexWithinAllocation(), InOutEntity); } EntityLocations[InOutEntity.AsIndex()] = NewLocation; if (OldLocation.IsValid()) { RemoveEntityFromAllocation(OldLocation.GetAllocationIndex(), OldLocation.GetEntryIndexWithinAllocation()); } EntityLocations.RemoveAt(NewEntityID.AsIndex()); } else { // Not loading over any entity, so just assign the new ID InOutEntity = NewEntityID; } } CheckInvariants(); OnStructureChanged(); } FMovieSceneEntityID FEntityManager::DuplicateEntity(FMovieSceneEntityID InOther) { if (!InOther) { return FMovieSceneEntityID::Invalid(); } check(EntityLocations.IsValidIndex(InOther.AsIndex())); const FEntityLocation SourceEntry = EntityLocations[InOther.AsIndex()]; if (!SourceEntry.IsValid()) { return AllocateEntity(); } const int32 NewEntityIndex = EntityLocations.Add(FEntityLocation{}); FMovieSceneEntityID NewEntityID = FMovieSceneEntityID::FromIndex(NewEntityIndex); const int32 DestAllocationIndex = GetOrCreateAllocationWithSlack(EntityAllocationMasks[SourceEntry.GetAllocationIndex()]); const int32 DestEntryIndexWithinAllocation = AddEntityToAllocation(DestAllocationIndex, NewEntityID); EntityLocations[NewEntityIndex].Set(DestAllocationIndex, DestEntryIndexWithinAllocation); CopyComponents(DestAllocationIndex, DestEntryIndexWithinAllocation, SourceEntry.GetAllocationIndex(), SourceEntry.GetEntryIndexWithinAllocation()); CheckInvariants(); OnStructureChanged(); return NewEntityID; } void FEntityManager::OverwriteEntityWithDuplicate(FMovieSceneEntityID& InOutEntity, FMovieSceneEntityID InEntityToDuplicate) { FMovieSceneEntityID NewEntityID; if (InEntityToDuplicate) { NewEntityID = DuplicateEntity(InEntityToDuplicate); } ReplaceEntityID(InOutEntity, NewEntityID); CheckInvariants(); OnStructureChanged(); } FEntityAllocationIteratorProxy FEntityManager::Iterate(const FEntityComponentFilter* InFilter) const { check(InFilter); return FEntityAllocationIteratorProxy(this, InFilter); } bool FEntityManager::Contains(const FEntityComponentFilter& InFilter) const { FEntityAllocationIteratorProxy It = Iterate(&InFilter); return It.begin() != It.end(); } void FEntityManager::AccumulateMask(const FEntityComponentFilter& InFilter, FComponentMask& OutMask) const { for (const FComponentMask& Mask : EntityAllocationMasks) { if (InFilter.Match(Mask)) { OutMask.CombineWithBitwiseOR(Mask, EBitwiseOperatorFlags::MaxSize); } } } void FEntityManager::EnterIteration() const { IterationCount.Increment(ThreadingModel); } void FEntityManager::ExitIteration() const { checkSlow(IterationCount.Load(ThreadingModel) > 0); IterationCount.Decrement(ThreadingModel); } FEntityAllocation* FEntityManager::CreateEntityAllocation(const FComponentMask& EntityComponentMask, uint16 InitialCapacity, uint16 MaxCapacity, FEntityAllocation* MigrateComponentDataFrom) { CheckCanChangeStructure(); check(!(EntityComponentMask.Contains(FBuiltInComponentTypes::Get()->Tags.NeedsLink) && EntityComponentMask.Contains(FBuiltInComponentTypes::Get()->Tags.NeedsUnlink))); check(InitialCapacity <= MaxCapacity && MaxCapacity < MAX_uint16); const int32 NumComponents = EntityComponentMask.NumComponents(); check(EntityComponentMask.Num() < TNumericLimits::Max()); check(NumComponents > 0 && NumComponents < TNumericLimits::Max()); const SIZE_T SizeofComponentHeaders = NumComponents*sizeof(FComponentHeader); SIZE_T ComponentDataSize = PLATFORM_CACHE_LINE_SIZE; for (FComponentMaskIterator It = EntityComponentMask.Iterate(); It; ++It) { const FComponentTypeInfo& TypeInfo = ComponentRegistry->GetComponentTypeChecked(FComponentTypeID::FromBitIndex(It.GetIndex())); if (!TypeInfo.IsTag()) // No need to reserve any memory if the component is a tag. { uint8 Alignment = FMath::Max(PLATFORM_CACHE_LINE_SIZE, TypeInfo.Alignment); ComponentDataSize += Alignment + TypeInfo.Sizeof*InitialCapacity; } } const SIZE_T SizeofEntityIDs = InitialCapacity*sizeof(FMovieSceneEntityID); // Create the memory layout for the new entity allocation. FEntityInitializer::FEntityAllocationInitializationInfo InitInfo; InitInfo.AllocationID = NextAllocationID; InitInfo.NumComponents = NumComponents; InitInfo.InitialCapacity = InitialCapacity; InitInfo.MaxCapacity = MaxCapacity; InitInfo.SizeofComponentHeaders = SizeofComponentHeaders; InitInfo.SizeofEntityIDs = SizeofEntityIDs; InitInfo.SizeofComponentData = (MigrateComponentDataFrom == nullptr) ? ComponentDataSize : 0; InitInfo.MigrateComponentDataFrom = MigrateComponentDataFrom; FEntityAllocation* Structure = FEntityInitializer::Initialize(*this, EntityComponentMask, InitInfo); ++NextAllocationID; return Structure; } int32 FEntityManager::CreateEntityAllocationEntry(const FComponentMask& EntityComponentMask, uint16 InitialCapacity, uint16 MaxCapacity) { CheckCanChangeStructure(); check(EntityAllocations.Num() < TNumericLimits::Max()); check(EntityAllocationMasks.Num() < TNumericLimits::Max()); FEntityAllocation* NewAllocation = CreateEntityAllocation(EntityComponentMask, InitialCapacity, MaxCapacity); const int32 MaskIndex = EntityAllocationMasks.Add(EntityComponentMask); const int32 AllocationIndex = EntityAllocations.Add(NewAllocation); check(MaskIndex == AllocationIndex); AllocationsWithCapacity.PadToNum(MaskIndex+1, false); AllocationsWithCapacity[MaskIndex] = true; return AllocationIndex; } int32 FEntityManager::GetOrCreateAllocationWithSlack(const FComponentMask& EntityComponentMask, int32* InOutDesiredSlack) { check(!InOutDesiredSlack || IsValidUint16(*InOutDesiredSlack)); for (TConstSetBitIterator<> It(AllocationsWithCapacity); It; ++It) { const int32 AllocationIndex = It.GetIndex(); if (EntityAllocationMasks[AllocationIndex].CompareSetBits(EntityComponentMask) == true) { if (InOutDesiredSlack) { *InOutDesiredSlack = ReserveAllocation(AllocationIndex, *InOutDesiredSlack); } return AllocationIndex; } } return CreateAllocationWithSlack(EntityComponentMask, InOutDesiredSlack); } int32 FEntityManager::CreateAllocationWithSlack(const FComponentMask& EntityComponentMask, int32* InOutDesiredSlack) { static const uint16 MaxCapacity = 64; uint16 DefaultCapacity = 4; if (InOutDesiredSlack) { *InOutDesiredSlack = FMath::Min((uint16)*InOutDesiredSlack, MaxCapacity); DefaultCapacity = *InOutDesiredSlack; } return CreateEntityAllocationEntry(EntityComponentMask, DefaultCapacity, MaxCapacity); } void FEntityManager::AddComponent(FMovieSceneEntityID EntityID, FComponentTypeID ComponentType) { check(EntityLocations.IsValidIndex(EntityID.AsIndex())); CheckCanChangeStructure(); FComponentMask NewEntityComponentMask; NewEntityComponentMask.Set(ComponentType); FEntityLocation& Location = EntityLocations[EntityID.AsIndex()]; if (Location.IsValid()) { const FComponentMask& ExistingMask = EntityAllocationMasks[Location.GetAllocationIndex()]; if (ExistingMask.Contains(ComponentType)) { // Entity already has this component type return; } NewEntityComponentMask.CombineWithBitwiseOR(EntityAllocationMasks[Location.GetAllocationIndex()], EBitwiseOperatorFlags::MaxSize); } int32 NewAllocationIndex = GetOrCreateAllocationWithSlack(NewEntityComponentMask); int32 NewEntityIndex; if (!Location.IsValid()) { NewEntityIndex = AddEntityToAllocation(NewAllocationIndex, EntityID); } else { NewEntityIndex = MigrateEntity(NewAllocationIndex, Location.GetAllocationIndex(), Location.GetEntryIndexWithinAllocation()); } Location.Set(NewAllocationIndex, NewEntityIndex); CheckInvariants(); OnStructureChanged(); } void FEntityManager::AddComponents(FMovieSceneEntityID EntityID, const FComponentMask& ComponentMask) { if (ComponentMask.Find(true) == INDEX_NONE) { return; } CheckCanChangeStructure(); FComponentMask NewEntityComponentMask = ComponentMask; FEntityLocation& Location = EntityLocations[EntityID.AsIndex()]; if (Location.IsValid()) { const FComponentMask& ExistingMask = EntityAllocationMasks[Location.GetAllocationIndex()]; NewEntityComponentMask.CombineWithBitwiseOR(ExistingMask, EBitwiseOperatorFlags::MaxSize); if (NewEntityComponentMask.CompareSetBits(ExistingMask)) { // Entity already has all the component types return; } } int32 NewAllocationIndex = GetOrCreateAllocationWithSlack(NewEntityComponentMask); int32 NewEntityIndex; if (!Location.IsValid()) { NewEntityIndex = AddEntityToAllocation(NewAllocationIndex, EntityID); } else { NewEntityIndex = MigrateEntity(NewAllocationIndex, Location.GetAllocationIndex(), Location.GetEntryIndexWithinAllocation()); } Location.Set(NewAllocationIndex, NewEntityIndex); CheckInvariants(); OnStructureChanged(); } void FEntityManager::AddComponent(FMovieSceneEntityID ThisEntityID, FComponentTypeID ComponentTypeID, EEntityRecursion Recursion) { if (EnumHasAnyFlags(Recursion, EEntityRecursion::This)) { AddComponent(ThisEntityID, ComponentTypeID); } if (EnumHasAnyFlags(Recursion, EEntityRecursion::Children)) { IterateChildren_ParentFirst(ThisEntityID, [this, ComponentTypeID](FMovieSceneEntityID EntityID) { this->AddComponent(EntityID, ComponentTypeID); }); } } void FEntityManager::AddComponents(FMovieSceneEntityID ThisEntityID, const FComponentMask& EntityComponentMask, EEntityRecursion Recursion) { if (EnumHasAnyFlags(Recursion, EEntityRecursion::This)) { AddComponents(ThisEntityID, EntityComponentMask); } if (EnumHasAnyFlags(Recursion, EEntityRecursion::Children)) { IterateChildren_ParentFirst(ThisEntityID, [this, &EntityComponentMask](FMovieSceneEntityID EntityID) { this->AddComponents(EntityID, EntityComponentMask); }); } } bool FEntityManager::HasComponent(FMovieSceneEntityID EntityID, FComponentTypeID ComponentType) const { FEntityLocation Location = EntityLocations[EntityID.AsIndex()]; return Location.IsValid() && EntityAllocationMasks[Location.GetAllocationIndex()].Contains(ComponentType); } const FComponentMask& FEntityManager::GetEntityType(FMovieSceneEntityID EntityID) const { FEntityLocation Location = EntityLocations[EntityID.AsIndex()]; return Location.IsValid() ? EntityAllocationMasks[Location.GetAllocationIndex()] : GEntityManagerEmptyMask; } void FEntityManager::ChangeEntityType(FMovieSceneEntityID EntityID, const FComponentMask& InNewMask) { CheckCanChangeStructure(); const bool bNewTypeIsEmpty = (InNewMask.Find(true) == INDEX_NONE); FEntityLocation& Location = EntityLocations[EntityID.AsIndex()]; if (Location.IsValid()) { const FComponentMask& ExistingMask = EntityAllocationMasks[Location.GetAllocationIndex()]; if (ExistingMask.CompareSetBits(InNewMask)) { // Already exactly this type return; } if (bNewTypeIsEmpty) { // Remove all components RemoveEntityFromAllocation(Location.GetAllocationIndex(), Location.GetEntryIndexWithinAllocation()); Location.Reset(); return; } } else if (bNewTypeIsEmpty) { return; } int32 NewAllocationIndex = GetOrCreateAllocationWithSlack(InNewMask); int32 NewEntityIndex; if (!Location.IsValid()) { NewEntityIndex = AddEntityToAllocation(NewAllocationIndex, EntityID); } else { NewEntityIndex = MigrateEntity(NewAllocationIndex, Location.GetAllocationIndex(), Location.GetEntryIndexWithinAllocation()); } Location.Set(NewAllocationIndex, NewEntityIndex); CheckInvariants(); OnStructureChanged(); } void FEntityManager::RemoveComponent(FMovieSceneEntityID EntityID, FComponentTypeID ComponentType) { CheckCanChangeStructure(); FEntityLocation& Location = EntityLocations[EntityID.AsIndex()]; if (!Location.IsValid()) { return; } else if (!EntityAllocationMasks[Location.GetAllocationIndex()].Contains(ComponentType)) { return; } bool bNewEntityHasComponents = false; FComponentMask NewEntityComponentMask; FEntityAllocation* Allocation = EntityAllocations[Location.GetAllocationIndex()]; for (const FComponentHeader& Header : Allocation->GetComponentHeaders()) { // @todo: Should this be an error? if (Header.ComponentType != ComponentType) { NewEntityComponentMask.Set(Header.ComponentType); bNewEntityHasComponents = true; } } FEntityLocation OldLocation = Location; if (!bNewEntityHasComponents) { Location.Reset(); RemoveEntityFromAllocation(OldLocation.GetAllocationIndex(), OldLocation.GetEntryIndexWithinAllocation()); } else { int32 AllocationIndex = GetOrCreateAllocationWithSlack(MoveTemp(NewEntityComponentMask)); int32 EntityIndex = MigrateEntity(AllocationIndex, OldLocation.GetAllocationIndex(), OldLocation.GetEntryIndexWithinAllocation()); Location.Set(AllocationIndex, EntityIndex); } CheckInvariants(); OnStructureChanged(); } void FEntityManager::RemoveComponents(FMovieSceneEntityID EntityID, const FComponentMask& EntitiesToRemove) { CheckCanChangeStructure(); if (EntitiesToRemove.Find(true) == INDEX_NONE) { return; } FEntityLocation& Location = EntityLocations[EntityID.AsIndex()]; if (!Location.IsValid()) { return; } const FComponentMask& CurrentMask = EntityAllocationMasks[Location.GetAllocationIndex()]; // Initialize a new mask with all bits set FComponentMask NewEntityComponentMask(true, CurrentMask.Num()); // Unset any bits that we need to remove NewEntityComponentMask.CombineWithBitwiseXOR(EntitiesToRemove, EBitwiseOperatorFlags::MaintainSize); // Copy over bits that still match NewEntityComponentMask.CombineWithBitwiseAND(CurrentMask, EBitwiseOperatorFlags::MaintainSize); if (EntityAllocationMasks[Location.GetAllocationIndex()].CompareSetBits(NewEntityComponentMask)) { return; } const bool bNewEntityHasComponents = NewEntityComponentMask.Find(true) != INDEX_NONE; FEntityLocation OldLocation = Location; if (!bNewEntityHasComponents) { Location.Reset(); RemoveEntityFromAllocation(OldLocation.GetAllocationIndex(), OldLocation.GetEntryIndexWithinAllocation()); } else { int32 AllocationIndex = GetOrCreateAllocationWithSlack(MoveTemp(NewEntityComponentMask)); int32 EntityIndex = MigrateEntity(AllocationIndex, OldLocation.GetAllocationIndex(), OldLocation.GetEntryIndexWithinAllocation()); Location.Set(AllocationIndex, EntityIndex); } CheckInvariants(); OnStructureChanged(); } void FEntityManager::RemoveComponent(FMovieSceneEntityID ThisEntityID, FComponentTypeID ComponentTypeID, EEntityRecursion Recursion) { if (EnumHasAnyFlags(Recursion, EEntityRecursion::This)) { RemoveComponent(ThisEntityID, ComponentTypeID); } if (EnumHasAnyFlags(Recursion, EEntityRecursion::Children)) { IterateChildren_ParentFirst(ThisEntityID, [this, ComponentTypeID](FMovieSceneEntityID EntityID) { this->RemoveComponent(EntityID, ComponentTypeID); }); } } void FEntityManager::RemoveComponents(FMovieSceneEntityID ThisEntityID, const FComponentMask& EntitiesToRemove, EEntityRecursion Recursion) { if (EnumHasAnyFlags(Recursion, EEntityRecursion::This)) { RemoveComponents(ThisEntityID, EntitiesToRemove); } if (EnumHasAnyFlags(Recursion, EEntityRecursion::Children)) { IterateChildren_ParentFirst(ThisEntityID, [this, &EntitiesToRemove](FMovieSceneEntityID EntityID) { this->RemoveComponents(EntityID, EntitiesToRemove); }); } } bool FEntityManager::CopyComponent(FMovieSceneEntityID SrcEntityID, FMovieSceneEntityID DstEntityID, FComponentTypeID ComponentTypeID) { CheckCanChangeStructure(); FEntityLocation SrcLocation = EntityLocations[SrcEntityID.AsIndex()]; if (!SrcLocation.IsValid()) { return false; } else if (!EntityAllocationMasks[SrcLocation.GetAllocationIndex()].Contains(ComponentTypeID)) { return false; } FEntityLocation DstLocation = EntityLocations[DstEntityID.AsIndex()]; bool bDidChangeStructure = false; if (!HasComponent(DstEntityID, ComponentTypeID)) { FComponentMask NewEntityComponentMask; if (DstLocation.IsValid()) { NewEntityComponentMask = EntityAllocationMasks[DstLocation.GetAllocationIndex()]; } NewEntityComponentMask.Set(ComponentTypeID); // Need to reallocate the destination entity because it doesn't contain this component const int32 NewAllocationIndex = GetOrCreateAllocationWithSlack(NewEntityComponentMask); int32 NewEntityIndex; if (!DstLocation.IsValid()) { NewEntityIndex = AddEntityToAllocation(NewAllocationIndex, DstEntityID); } else { NewEntityIndex = MigrateEntity(NewAllocationIndex, DstLocation.GetAllocationIndex(), DstLocation.GetEntryIndexWithinAllocation()); } DstLocation.Set(NewAllocationIndex, NewEntityIndex); EntityLocations[DstEntityID.AsIndex()] = DstLocation; bDidChangeStructure = true; } FComponentHeader& SrcHeader = EntityAllocations[SrcLocation.GetAllocationIndex()]->GetComponentHeaderChecked(ComponentTypeID); FComponentHeader& DstHeader = EntityAllocations[DstLocation.GetAllocationIndex()]->GetComponentHeaderChecked(ComponentTypeID); // Copy the component value to the destination allocation const FComponentTypeInfo& ComponentTypeInfo = ComponentRegistry->GetComponentTypeChecked(ComponentTypeID); void* DstValue = DstHeader.GetValuePtr(DstLocation.GetEntryIndexWithinAllocation()); const void* SrcValue = SrcHeader.GetValuePtr(SrcLocation.GetEntryIndexWithinAllocation()); FEntityAllocationWriteContext WriteContext(*this); ComponentTypeInfo.CopyItems(DstValue, SrcValue, 1); DstHeader.PostWriteComponents(WriteContext); if (bDidChangeStructure) { CheckInvariants(); OnStructureChanged(); } return true; } void FEntityManager::CopyComponents(FMovieSceneEntityID SrcEntityID, FMovieSceneEntityID DstEntityID, const FComponentMask& ComponentsToCopy) { CheckCanChangeStructure(); FEntityLocation SrcLocation = EntityLocations[SrcEntityID.AsIndex()]; FEntityLocation DstLocation = EntityLocations[DstEntityID.AsIndex()]; if (!SrcLocation.IsValid()) { return; } bool bDidChangeStructure = false; FComponentMask ComponentsOnSource = FComponentMask::BitwiseAND(ComponentsToCopy, EntityAllocationMasks[SrcLocation.GetAllocationIndex()], EBitwiseOperatorFlags::MinSize); if (ComponentsOnSource.Find(true) == INDEX_NONE) { return; } if (!DstLocation.IsValid()) { const int32 NewAllocationIndex = GetOrCreateAllocationWithSlack(ComponentsOnSource); const int32 NewEntityIndex = AddEntityToAllocation(NewAllocationIndex, DstEntityID); DstLocation.Set(NewAllocationIndex, NewEntityIndex); EntityLocations[DstEntityID.AsIndex()] = DstLocation; bDidChangeStructure = true; } else { FComponentMask ExistingType = EntityAllocationMasks[DstLocation.GetAllocationIndex()]; FComponentMask NewEntityType = FComponentMask::BitwiseOR(ExistingType, ComponentsOnSource, EBitwiseOperatorFlags::MaxSize); if (!NewEntityType.CompareSetBits(ExistingType)) { const int32 NewAllocationIndex = GetOrCreateAllocationWithSlack(NewEntityType); const int32 NewEntityIndex = MigrateEntity(NewAllocationIndex, DstLocation.GetAllocationIndex(), DstLocation.GetEntryIndexWithinAllocation()); DstLocation.Set(NewAllocationIndex, NewEntityIndex); EntityLocations[DstEntityID.AsIndex()] = DstLocation; bDidChangeStructure = true; } } CopyComponents( DstLocation.GetAllocationIndex(), DstLocation.GetEntryIndexWithinAllocation(), SrcLocation.GetAllocationIndex(), SrcLocation.GetEntryIndexWithinAllocation(), &ComponentsOnSource); if (bDidChangeStructure) { CheckInvariants(); OnStructureChanged(); } } void FEntityManager::FilterComponents(FMovieSceneEntityID EntityID, const FComponentMask& EntitiesToKeep) { CheckCanChangeStructure(); FEntityLocation& Location = EntityLocations[EntityID.AsIndex()]; if (!Location.IsValid()) { return; } FComponentMask NewEntityComponentMask = FComponentMask::BitwiseAND(EntityAllocationMasks[Location.GetAllocationIndex()], EntitiesToKeep, EBitwiseOperatorFlags::MaxSize); if (EntityAllocationMasks[Location.GetAllocationIndex()].CompareSetBits(NewEntityComponentMask)) { return; } const bool bNewEntityHasComponents = NewEntityComponentMask.Find(true) != INDEX_NONE; FEntityLocation OldLocation = Location; if (!bNewEntityHasComponents) { Location.Reset(); RemoveEntityFromAllocation(OldLocation.GetAllocationIndex(), OldLocation.GetEntryIndexWithinAllocation()); } else { int32 AllocationIndex = GetOrCreateAllocationWithSlack(MoveTemp(NewEntityComponentMask)); int32 EntityIndex = MigrateEntity(AllocationIndex, OldLocation.GetAllocationIndex(), OldLocation.GetEntryIndexWithinAllocation()); Location.Set(AllocationIndex, EntityIndex); } CheckInvariants(); OnStructureChanged(); } void FEntityManager::CombineComponents(FMovieSceneEntityID DestinationEntityID, FMovieSceneEntityID SourceEntityID, const FComponentMask* OptionalMask) { CheckCanChangeStructure(); FEntityLocation SourceLocation = EntityLocations[SourceEntityID.AsIndex()]; FEntityLocation DestinationLocation = EntityLocations[DestinationEntityID.AsIndex()]; if (!SourceLocation.IsValid()) { return; } const int32 OldAllocationIndex = DestinationLocation.IsValid() ? DestinationLocation.GetAllocationIndex() : INDEX_NONE; FComponentMask NewEntityComponentMask = EntityAllocationMasks[SourceLocation.GetAllocationIndex()]; if (OptionalMask) { NewEntityComponentMask.CombineWithBitwiseAND(*OptionalMask, EBitwiseOperatorFlags::MaxSize); } if (OldAllocationIndex != INDEX_NONE) { NewEntityComponentMask.CombineWithBitwiseOR(EntityAllocationMasks[OldAllocationIndex], EBitwiseOperatorFlags::MaxSize); int32 NewEntityIndex = DestinationLocation.GetEntryIndexWithinAllocation(); int32 NewAllocationIndex = OldAllocationIndex; if (!NewEntityComponentMask.CompareSetBits(EntityAllocationMasks[OldAllocationIndex])) { NewAllocationIndex = GetOrCreateAllocationWithSlack(NewEntityComponentMask); NewEntityIndex = MigrateEntity(NewAllocationIndex, OldAllocationIndex, DestinationLocation.GetEntryIndexWithinAllocation()); EntityLocations[DestinationEntityID.AsIndex()].Set(NewAllocationIndex, NewEntityIndex); } CopyComponents(NewAllocationIndex, NewEntityIndex, SourceLocation.GetAllocationIndex(), SourceLocation.GetEntryIndexWithinAllocation(), OptionalMask); } else { const int32 NewAllocationIndex = GetOrCreateAllocationWithSlack(NewEntityComponentMask); int32 NewEntityIndex = AddEntityToAllocation(NewAllocationIndex, DestinationEntityID); CopyComponents(NewAllocationIndex, NewEntityIndex, SourceLocation.GetAllocationIndex(), SourceLocation.GetEntryIndexWithinAllocation()); EntityLocations[DestinationEntityID.AsIndex()].Set(NewAllocationIndex, NewEntityIndex); } CheckInvariants(); OnStructureChanged(); } int32 FEntityManager::MutateAll(const FEntityComponentFilter& Filter, const IMovieSceneEntityMutation& Mutation, EMutuallyInclusiveComponentType MutualTypes) { CheckCanChangeStructure(); FMutualComponentInitializers MutualInitializers; FEntityAllocationWriteContext WriteContext(*this); int32 TotalNumMutations = 0; for (int32 AllocationIndex = 0; AllocationIndex < EntityAllocationMasks.GetMaxIndex(); ++AllocationIndex) { if (!EntityAllocationMasks.IsAllocated(AllocationIndex) || !Filter.Match(EntityAllocationMasks[AllocationIndex])) { continue; } // Process the mutation FComponentMask NewAllocationType = EntityAllocationMasks[AllocationIndex]; Mutation.CreateMutation(this, &NewAllocationType); // Add mutual components MutualInitializers.Reset(); ComponentRegistry->Factories.ComputeMutuallyInclusiveComponents(MutualTypes, NewAllocationType, MutualInitializers); FEntityAllocation* SourceAllocation = EntityAllocations[AllocationIndex]; // If the type hasn't changed at all, we can just run the unmodified initializer if (NewAllocationType.CompareSetBits(EntityAllocationMasks[AllocationIndex])) { Mutation.InitializeUnmodifiedAllocation(SourceAllocation, NewAllocationType); continue; } // The type has changed so we need to migrate the allocation - we just reallocate within // the same allocation index to avoid having to fix up specific entity entry indices FEntityAllocation* NewAllocation = MigrateAllocation(AllocationIndex, NewAllocationType); TotalNumMutations += NewAllocation->Num(); FComponentMask OldAllocationType = EntityAllocationMasks[AllocationIndex]; EntityAllocationMasks[AllocationIndex] = NewAllocationType; EntityAllocations[AllocationIndex] = NewAllocation; FEntityAllocationMutexGuard LockGuard(NewAllocation, EComponentHeaderLockMode::LockFree); // Default construct all the new components in the allocation, and then allow the mutation to further initialize this data if needed for (FComponentMaskIterator Component = NewAllocationType.Iterate(); Component; ++Component) { FComponentTypeID ComponentTypeID = FComponentTypeID::FromBitIndex(Component.GetIndex()); if (OldAllocationType.Contains(ComponentTypeID)) { continue; } const FComponentHeader& ComponentHeader = NewAllocation->GetComponentHeaderChecked(ComponentTypeID); if (!ComponentHeader.IsTag()) { const FComponentTypeInfo& ComponentTypeInfo = ComponentRegistry->GetComponentTypeChecked(ComponentTypeID); void* Components = ComponentHeader.GetValuePtr(0); ComponentTypeInfo.ConstructItems(Components, NewAllocation->Num()); } } // Run custom initializers MutualInitializers.Execute(FEntityRange{ NewAllocation, 0, NewAllocation->Num() }, WriteContext); // Run mutation initializer Mutation.InitializeAllocation(NewAllocation, NewAllocationType); // Destroy the old data DestroyAllocation(SourceAllocation); } if (TotalNumMutations != 0) { CheckInvariants(); OnStructureChanged(); } return TotalNumMutations; } int32 FEntityManager::MutateConditional(const FEntityComponentFilter& Filter, const IMovieSceneConditionalEntityMutation& Mutation, EMutuallyInclusiveComponentType MutualTypes) { CheckCanChangeStructure(); struct FConditionalMutationState { FComponentMask NewType; TBitArray<> MarkedEntities; FMutualComponentInitializers MutualInitializers; }; TMap AllocationMutations; FMutualComponentInitializers MutualInitializersScratch; for (int32 AllocationIndex = 0; AllocationIndex < EntityAllocationMasks.GetMaxIndex(); ++AllocationIndex) { if (EntityAllocationMasks.IsAllocated(AllocationIndex) && Filter.Match(EntityAllocationMasks[AllocationIndex])) { FEntityAllocation* SourceAllocation = EntityAllocations[AllocationIndex]; TBitArray<> MarkedEntities; Mutation.MarkAllocation(SourceAllocation, MarkedEntities); if (MarkedEntities.Num() == 0) { continue; } const int32 NumIrrelevantBits = MarkedEntities.Num() - SourceAllocation->Num(); if (NumIrrelevantBits > 1) { MarkedEntities.RemoveAt(SourceAllocation->Num(), NumIrrelevantBits); } FComponentMask NewMutation = EntityAllocationMasks[AllocationIndex]; Mutation.CreateMutation(this, &NewMutation); // Add mutual components MutualInitializersScratch.Reset(); ComponentRegistry->Factories.ComputeMutuallyInclusiveComponents(MutualTypes, NewMutation, MutualInitializersScratch); if (!NewMutation.CompareSetBits(EntityAllocationMasks[AllocationIndex])) { AllocationMutations.Add(AllocationIndex, FConditionalMutationState{ MoveTemp(NewMutation), MoveTemp(MarkedEntities), MoveTemp(MutualInitializersScratch) }); } } } if (AllocationMutations.Num() == 0) { return 0; } int32 TotalNumMutations = 0; FMutualComponentInitializers MutualInitializers; FEntityAllocationWriteContext WriteContext(*this); for (TTuple& Pair : AllocationMutations) { const int32 SourceAllocationIndex = Pair.Key; FEntityAllocation* SourceAllocation = EntityAllocations[SourceAllocationIndex]; const int32 NumEntities = Pair.Value.MarkedEntities.CountSetBits(); const bool bAllEntitiesMarked = NumEntities == SourceAllocation->Num(); TotalNumMutations += NumEntities; const FComponentMask& DesiredType = Pair.Value.NewType; if (bAllEntitiesMarked) { // When adding a component to an entire allocation we just reallocate within the same allocation entry to avoid having to fix up // Specific entity entry indices FEntityAllocation* NewAllocation = MigrateAllocation(SourceAllocationIndex, DesiredType); // Default construct all the new components in the allocation, and then allow the mutation to further initialize this data if needed for (FComponentMaskIterator Component = DesiredType.Iterate(); Component; ++Component) { FComponentTypeID ComponentTypeID = FComponentTypeID::FromBitIndex(Component.GetIndex()); if (EntityAllocationMasks[SourceAllocationIndex].Contains(ComponentTypeID)) { continue; } const FComponentHeader& ComponentHeader = NewAllocation->GetComponentHeaderChecked(ComponentTypeID); if (!ComponentHeader.IsTag()) { const FComponentTypeInfo& ComponentTypeInfo = ComponentRegistry->GetComponentTypeChecked(ComponentTypeID); void* Components = ComponentHeader.GetValuePtr(0); ComponentTypeInfo.ConstructItems(Components, NewAllocation->Num()); } } EntityAllocationMasks[SourceAllocationIndex] = DesiredType; EntityAllocations[SourceAllocationIndex] = NewAllocation; FEntityRange Range { NewAllocation, 0, NewAllocation->Num() }; // Run custom initializers Pair.Value.MutualInitializers.Execute(Range, WriteContext); Mutation.InitializeEntities(Range, DesiredType); DestroyAllocation(SourceAllocation); } else { int32 DesiredSlack = NumEntities; const int32 NewAllocationIndex = CreateAllocationWithSlack(DesiredType, &DesiredSlack); const FEntityAllocation* DestAllocation = EntityAllocations[NewAllocationIndex]; FEntityRange Range { DestAllocation, DestAllocation->Num(), NumEntities }; const FMovieSceneEntityID* EntityIDs = SourceAllocation->GetRawEntityIDs(); // Migrate entities to the new allocation - care is taken to iterate the allocation backwards // since entities can shift around in the allocation as they are migrated for (TBitArray<>::FConstReverseIterator It(Pair.Value.MarkedEntities); It; ++It) { if (It.GetValue()) { FEntityLocation& Location = EntityLocations[EntityIDs[It.GetIndex()].AsIndex()]; int32 NewEntityIndex = MigrateEntity(NewAllocationIndex, Location.GetAllocationIndex(), Location.GetEntryIndexWithinAllocation()); Location.Set(NewAllocationIndex, NewEntityIndex); } } check(Range.ComponentStartOffset + Range.Num == Range.Allocation->Num()); // Run custom initializers Pair.Value.MutualInitializers.Execute(Range, WriteContext); Mutation.InitializeEntities(Range, DesiredType); } } CheckInvariants(); OnStructureChanged(); return TotalNumMutations; } void FEntityManager::TouchEntity(FMovieSceneEntityID EntityID) { FEntityLocation Location = EntityLocations[EntityID.AsIndex()]; if (Location.IsValid()) { FEntityAllocation* Allocation = GetAllocation(Location.GetAllocationIndex()); check(Allocation); FEntityAllocationWriteContext WriteContext(*this); for (FComponentHeader& Header : Allocation->GetComponentHeaders()) { if (!Header.IsTag()) { Header.PostWriteComponents(WriteContext); } } } } void FEntityManager::AddChild(FMovieSceneEntityID ParentID, FMovieSceneEntityID ChildID) { FEntityLocation& Location = EntityLocations[ChildID.AsIndex()]; if (Location.GetParentID()) { ParentToChild.Remove(Location.GetParentID(), ChildID); } ParentToChild.Add(ParentID, ChildID); Location.SetParentID(ParentID); } void FEntityManager::GetImmediateChildren(FMovieSceneEntityID ParentID, TArray& OutChildren) const { for (auto ChildIt = ParentToChild.CreateConstKeyIterator(ParentID); ChildIt; ++ChildIt) { OutChildren.Add(ChildIt.Value()); } } void FEntityManager::GetChildren_ParentFirst(FMovieSceneEntityID ParentID, TArray& OutChildren) const { for (auto ChildIt = ParentToChild.CreateConstKeyIterator(ParentID); ChildIt; ++ChildIt) { OutChildren.Add(ChildIt.Value()); GetChildren_ParentFirst(ChildIt.Value(), OutChildren); } } void FEntityManager::InitializeChildAllocation(const FComponentMask& ParentType, const FComponentMask& ChildType, const FEntityAllocation* ParentAllocation, TArrayView ParentAllocationOffsets, const FEntityRange& InChildEntityRange) { ComponentRegistry->Factories.RunInitializers(ParentType, ChildType, ParentAllocation, ParentAllocationOffsets, InChildEntityRange); for (TInlineValue& ChildInit : InstancedChildInitializers) { if (ChildInit->IsRelevant(ParentType, ChildType)) { ChildInit->Run(InChildEntityRange, ParentAllocation, ParentAllocationOffsets); } } } void FEntityManager::AddMutualComponents() { AddMutualComponents(FEntityComponentFilter()); } void FEntityManager::AddMutualComponents(const FEntityComponentFilter& InFilter) { CheckCanChangeStructure(); struct FBenignMutation : IMovieSceneEntityMutation { void CreateMutation(FEntityManager* EntityManager, FComponentMask* InOutEntityComponentTypes) const override {} }; FBenignMutation BenignMutation; MutateAll(InFilter, BenignMutation, EMutuallyInclusiveComponentType::All); } FEntityAllocation* FEntityManager::MigrateAllocation(int32 AllocationIndex, const FComponentMask& NewComponentMask) { CheckCanChangeStructure(); check(EntityAllocations.IsValidIndex(AllocationIndex)); FEntityAllocation* Source = EntityAllocations[AllocationIndex]; // Check if the migration only adds/removes tags, in which case we can do that in-place without re-allocating // the components' data buffer. const FComponentMask OldComponentMask = EntityAllocationMasks[AllocationIndex]; const FComponentMask ChangedComponentsMask = FComponentMask::BitwiseXOR(OldComponentMask, NewComponentMask, EBitwiseOperatorFlags::MaxSize); const FComponentMask ChangedNonTagComponentsMask = FComponentMask::BitwiseAND(ComponentRegistry->GetDataComponentTypes(), ChangedComponentsMask, EBitwiseOperatorFlags::MaxSize); const bool bOnlyTagComponentsChanged = (ChangedNonTagComponentsMask.Find(true) == INDEX_NONE); // Create a new allocation of the necessary capacity with the correct new mask FEntityAllocation* Dest = nullptr; if (bOnlyTagComponentsChanged) { // ~~~~~~~~~~~ WARNING: after this, the source allocation has null component pointers! ~~~~~~~~~~~~ Dest = CreateEntityAllocation(NewComponentMask, Source->GetCapacity(), Source->GetMaxCapacity(), Source); } else { Dest = CreateEntityAllocation(NewComponentMask, Source->GetCapacity(), Source->GetMaxCapacity()); } check(Dest != nullptr); FEntityInitializer::Duplicate(Dest, Source); // Relocate component data component by component if the migration has changed the memory layout. // If it hasn't (i.e. if we only added/removed tags), then we don't have anything to do, because the memory // layout is the same and we "stole" the data buffer directly from the source allocation (see above). if (!bOnlyTagComponentsChanged) { // Iterate all destination component types FComponentHeader* SrcComponentHeader = Source->ComponentHeaders; FComponentHeader* SrcLastComponentHeader = Source->ComponentHeaders + Source->GetNumComponentTypes() - 1; for (int32 DstHeaderOffset = 0; DstHeaderOffset < Dest->GetNumComponentTypes(); ++DstHeaderOffset) { FComponentHeader* DstComponentHeader = &Dest->ComponentHeaders[DstHeaderOffset]; // Try to find a matching source component type while (SrcComponentHeader != SrcLastComponentHeader && SrcComponentHeader->ComponentType.BitIndex() < DstComponentHeader->ComponentType.BitIndex()) { ++SrcComponentHeader; } // Copy the component value to the new allocation if (DstComponentHeader->ComponentType == SrcComponentHeader->ComponentType && !SrcComponentHeader->IsTag()) { const FComponentTypeInfo& ComponentTypeInfo = ComponentRegistry->GetComponentTypeChecked(SrcComponentHeader->ComponentType); void* DstValue = DstComponentHeader->GetValuePtr(0); void* SrcValue = SrcComponentHeader->GetValuePtr(0); ComponentTypeInfo.RelocateConstructItems(DstValue, SrcValue, Source->Num()); // Mark these components as having be relocated, so they don't need to be destructed later. SrcComponentHeader->Components = nullptr; } } } CheckInvariants(); OnStructureChanged(); return Dest; } void FEntityManager::CombineAllocations(int32 DestinationIndex, int32 SourceIndex) { CheckCanChangeStructure(); FEntityAllocation* DstAllocation = EntityAllocations[DestinationIndex]; FEntityAllocation* SrcAllocation = EntityAllocations[SourceIndex]; check(DstAllocation->GetMaxCapacity() - DstAllocation->Num() >= SrcAllocation->Num()) if (DstAllocation->GetCapacity() - DstAllocation->Num() < SrcAllocation->Num()) { // Need to grow the allocation DstAllocation = GrowAllocation(DestinationIndex, SrcAllocation->Num()); } // Write new entity locations { const int32 NewEntityStart = DstAllocation->Num(); TArrayView EntityIDs = SrcAllocation->GetEntityIDs(); for (int32 EntityIndex = 0; EntityIndex < EntityIDs.Num(); ++EntityIndex) { FMovieSceneEntityID EntityID = EntityIDs[EntityIndex]; EntityLocations[EntityID.AsIndex()].Set(DestinationIndex, NewEntityStart + EntityIndex); } } // Copy the entitiy IDs and component data over to the new allocation FEntityInitializer::MigrateAllocation(DstAllocation, SrcAllocation, ComponentRegistry); // Destroy the old allocation const bool bDestructComponentData = false; // We have relocated all the component data, there's nothing left to destroy. DestroyAllocation(SrcAllocation, bDestructComponentData); // Reset allocation entries EntityAllocationMasks.RemoveAt(SourceIndex); EntityAllocations.RemoveAt(SourceIndex); AllocationsWithCapacity[SourceIndex] = false; AllocationsWithCapacity[DestinationIndex] = DstAllocation->GetMaxCapacity() > DstAllocation->Num(); CheckInvariants(); OnStructureChanged(); } void FEntityManager::AddReferencedObjects(FReferenceCollector& ReferenceCollector) { for (FEntityAllocation* Allocation : EntityAllocations) { const int32 AllocationSize = Allocation->Num(); for (const FComponentHeader& Header : Allocation->GetComponentHeaders()) { const FComponentTypeInfo& ComponentTypeInfo = ComponentRegistry->GetComponentTypeChecked(Header.ComponentType); if (!Header.IsTag() && ComponentTypeInfo.bHasReferencedObjects) { ComponentTypeInfo.AddReferencedObjects(ReferenceCollector, Header.Components, AllocationSize); } } } } FString FEntityManager::GetReferencerName() const { return ManagerDebugName; } void FEntityManager::NotifyUObjectDeleted(const UObjectBase* Object, int32 Index) { } void FEntityManager::OnUObjectArrayShutdown() { } int32 FEntityManager::MigrateEntity(int32 DestAllocationIndex, int32 SourceAllocationIndex, int32 SourceEntityIndex) { check(DestAllocationIndex != SourceAllocationIndex); CheckCanChangeStructure(); FEntityAllocation* Dst = EntityAllocations[DestAllocationIndex]; FEntityAllocation* Src = EntityAllocations[SourceAllocationIndex]; check(SourceEntityIndex < Src->Num()); if (Dst->GetCapacity() == Dst->Num()) { Dst = GrowAllocation(DestAllocationIndex); } FMovieSceneEntityID EntityID = Src->GetEntityIDs()[SourceEntityIndex]; // Make space for the new entity in the destination without initializing the memory // This is important because we either default construct or relocate construct into this new allocation ourselves const int32 DestEntityIndex = AddEntityToAllocation(DestAllocationIndex, EntityID, EMemoryType::DefaultConstructed); const int32 SrcOffset = SourceEntityIndex; const int32 DstOffset = DestEntityIndex; const int32 LastEntityIndex = Src->Num() - 1; check(SrcOffset < Src->Num() && DstOffset < Dst->Num()); FEntityAllocationWriteContext WriteContext(*this); Src->PostModifyStructureExcludingHeaders(WriteContext); Dst->PostModifyStructureExcludingHeaders(WriteContext); const int32 NumSrcHeaders = Src->GetNumComponentTypes(); int32 SrcHeaderIndex = 0; // This function takes a component header and address from the original allocation and removes it from the allocation // by destructing the component, and relocating the last element in the component array into its place, similar to TArray::RemoveAtSwap // This allows minimal shuffling of component data when moving entities between allocations. auto RemoveAtSwapComponent = [this, LastEntityIndex, WriteContext](const FComponentHeader& SrcComponentHeader, int32 RemoveAtIndex) { SrcComponentHeader.PostWriteComponents(WriteContext); if (!SrcComponentHeader.IsTag()) { const FComponentTypeInfo& ComponentTypeInfo = this->ComponentRegistry->GetComponentTypeChecked(SrcComponentHeader.ComponentType); void* RemovedValueAddress = SrcComponentHeader.GetValuePtr(RemoveAtIndex); // Destroy the component at the address ComponentTypeInfo.DestructItems(RemovedValueAddress, 1); // Swap the last entity's components in this allocation with the migrated one if (RemoveAtIndex != LastEntityIndex) { void* LastItem = SrcComponentHeader.GetValuePtr(LastEntityIndex); ComponentTypeInfo.RelocateConstructItems(RemovedValueAddress, LastItem, 1); } } }; // Iterate all destination component types and either default construct, or relocate the existing component for (int32 DstHeaderOffset = 0; DstHeaderOffset < Dst->GetNumComponentTypes(); ++DstHeaderOffset) { FComponentHeader* DstComponentHeader = &Dst->ComponentHeaders[DstHeaderOffset]; if (DstComponentHeader->IsTag()) { continue; } // Try to locate a matching source component header for this component type by walking through the source headers // Component headers are sorted by component type, so if we encounter any with a type ID less than the current destination type // it must not exist in the new allocation, and so should be destroyed from this one while (SrcHeaderIndex < NumSrcHeaders && Src->ComponentHeaders[SrcHeaderIndex].ComponentType.BitIndex() < DstComponentHeader->ComponentType.BitIndex()) { // Destination does not have this component type so it needs destroying RemoveAtSwapComponent(Src->ComponentHeaders[SrcHeaderIndex], SrcOffset); ++SrcHeaderIndex; } const FComponentTypeInfo& ComponentTypeInfo = ComponentRegistry->GetComponentTypeChecked(DstComponentHeader->ComponentType); void* DstValue = DstComponentHeader->GetValuePtr(DstOffset); // Relocate the component value to the new allocation if it is the same type as our current source header if (SrcHeaderIndex < NumSrcHeaders && Src->ComponentHeaders[SrcHeaderIndex].ComponentType.BitIndex() == DstComponentHeader->ComponentType.BitIndex()) { void* MigratedValueAddress = Src->ComponentHeaders[SrcHeaderIndex].GetValuePtr(SrcOffset); ComponentTypeInfo.RelocateConstructItems(DstValue, MigratedValueAddress, 1); // We do not use RemoveAtSwapComponent here because the SrcValue is now already considered destructed // Manually swap the last entity's components in this allocation with the migrated one if (LastEntityIndex != SrcOffset) { void* LastItem = Src->ComponentHeaders[SrcHeaderIndex].GetValuePtr(LastEntityIndex); ComponentTypeInfo.RelocateConstructItems(MigratedValueAddress, LastItem, 1); } // This source header is now dealt with so skip over it ++SrcHeaderIndex; } // or default construct it if it's a new component that didn't exist before else { ComponentTypeInfo.ConstructItems(DstValue, 1); } } // Process any remaining components in the source allocation that were not in the destination // by destructing the components and potentially relocating the RemoveAtSwap candidate for ( ; SrcHeaderIndex < NumSrcHeaders; ++SrcHeaderIndex) { RemoveAtSwapComponent(Src->ComponentHeaders[SrcHeaderIndex], SrcOffset); } // When removing we just swap the tail element with the element to remove, and fix up the indices if (LastEntityIndex != SrcOffset) { FEntityInitializer::MoveEntryIndex(Src, LastEntityIndex, SrcOffset); // Fixup entry offset for the changed entry FMovieSceneEntityID SwappedEntityID = Src->GetEntityIDs()[SrcOffset]; EntityLocations[SwappedEntityID.AsIndex()].Set(SourceAllocationIndex, SrcOffset); } // Free this entity index without needing to fix up any other indices FEntityInitializer::FreeEntryIndex(Src, LastEntityIndex); // Does the one we just added to no longer have any capacity? if (Dst->Num() == Dst->GetMaxCapacity()) { AllocationsWithCapacity[DestAllocationIndex] = false; } // Does the one we just removed from now have capacity when it didn't before? if (Src->Num() == 0) { DestroyAllocation(Src); EntityAllocationMasks.RemoveAt(SourceAllocationIndex); EntityAllocations.RemoveAt(SourceAllocationIndex); AllocationsWithCapacity[SourceAllocationIndex] = false; } else if (Src->Num() == Src->GetMaxCapacity()-1) { AllocationsWithCapacity[SourceAllocationIndex] = true; } OnStructureChanged(); return DestEntityIndex; } void FEntityManager::CopyComponents(int32 DestAllocationIndex, int32 DestEntityIndex, int32 SourceAllocationIndex, int32 SourceEntityIndex, const FComponentMask* OptionalMask) { CheckCanChangeStructure(); FEntityAllocation* Dst = EntityAllocations[DestAllocationIndex]; FEntityAllocation* Src = EntityAllocations[SourceAllocationIndex]; FComponentHeader* SrcComponentHeader = Src->ComponentHeaders; FComponentHeader* SrcLastComponentHeader = Src->ComponentHeaders + Src->GetNumComponentTypes() - 1; const int32 SrcOffset = SourceEntityIndex; const int32 DstOffset = DestEntityIndex; check(SrcOffset < Src->Num() && DstOffset < Dst->Num()); FEntityAllocationWriteContext WriteContext(*this); Dst->PostModifyStructureExcludingHeaders(WriteContext); // Iterate all source component types for (int32 DstHeaderOffset = 0; DstHeaderOffset < Dst->GetNumComponentTypes(); ++DstHeaderOffset) { FComponentHeader* DstComponentHeader = &Dst->ComponentHeaders[DstHeaderOffset]; if (OptionalMask && !OptionalMask->Contains(DstComponentHeader->ComponentType)) { continue; } DstComponentHeader->PostWriteComponents(WriteContext); // Try to find a matching source component type while (SrcComponentHeader != SrcLastComponentHeader && SrcComponentHeader->ComponentType.BitIndex() < DstComponentHeader->ComponentType.BitIndex()) { ++SrcComponentHeader; } // Copy the component value to the destination allocation if (DstComponentHeader->ComponentType == SrcComponentHeader->ComponentType && !SrcComponentHeader->IsTag()) { const FComponentTypeInfo& ComponentTypeInfo = ComponentRegistry->GetComponentTypeChecked(SrcComponentHeader->ComponentType); void* DstValue = DstComponentHeader->GetValuePtr(DstOffset); const void* SrcValue = SrcComponentHeader->GetValuePtr(SrcOffset); ComponentTypeInfo.CopyItems(DstValue, SrcValue, 1); } } OnStructureChanged(); } int32 FEntityManager::ReserveAllocation(int32 AllocationIndex, int32 NumToReserve) { CheckCanChangeStructure(); FEntityAllocation* Allocation = EntityAllocations[AllocationIndex]; // Max out this allocation if possible const int32 MaxAvailableSlack = Allocation->GetMaxCapacity() - Allocation->Num(); NumToReserve = FMath::Min(MaxAvailableSlack, NumToReserve); if (Allocation->Num() + NumToReserve > Allocation->GetCapacity()) { GrowAllocation(AllocationIndex, NumToReserve); } return NumToReserve; } FEntityAllocation* FEntityManager::GrowAllocation(int32 AllocationIndex, int32 MinNumToGrowBy) { CheckCanChangeStructure(); FEntityAllocation* Allocation = EntityAllocations[AllocationIndex]; check(MinNumToGrowBy > 0 && Allocation->Num() + MinNumToGrowBy <= Allocation->GetMaxCapacity()); const int32 EntityCount = Allocation->Num(); const bool bAllowQuantize = true; // Increase the size of this allocation to house the new entity int32 NewCapacity = DefaultCalculateSlackGrow(EntityCount + MinNumToGrowBy, Allocation->GetCapacity(), 1, bAllowQuantize); NewCapacity = FMath::Max(EntityCount + MinNumToGrowBy, NewCapacity); NewCapacity = FMath::Min(NewCapacity, Allocation->GetMaxCapacity()); // Create a new allocation with the same mask FEntityAllocation* NewAllocation = CreateEntityAllocation(EntityAllocationMasks[AllocationIndex], NewCapacity, Allocation->GetMaxCapacity()); FEntityInitializer::Duplicate(NewAllocation, Allocation); check(Allocation->GetNumComponentTypes() == NewAllocation->GetNumComponentTypes()); for (int32 Index = 0; Index < Allocation->GetNumComponentTypes(); ++Index) { const FComponentHeader* SrcHeader = Allocation->ComponentHeaders + Index; FComponentHeader* DstHeader = NewAllocation->ComponentHeaders + Index; check(SrcHeader->ComponentType == DstHeader->ComponentType); if (!SrcHeader->IsTag()) { const FComponentTypeInfo& ComponentTypeInfo = ComponentRegistry->GetComponentTypeChecked(SrcHeader->ComponentType); void* SrcValue = SrcHeader->GetValuePtr(0); void* DstValue = DstHeader->GetValuePtr(0); ComponentTypeInfo.RelocateConstructItems(DstValue, SrcValue, Allocation->Num()); } } const bool bDestructComponentData = false; // We have relocated all the component data, there's nothing left to destroy. DestroyAllocation(Allocation, bDestructComponentData); EntityAllocations[AllocationIndex] = NewAllocation; return NewAllocation; } int32 FEntityManager::AddEntityToAllocation(int32 AllocationIndex, FMovieSceneEntityID ID, EMemoryType MemoryType) { CheckCanChangeStructure(); FEntityAllocation* Allocation = EntityAllocations[AllocationIndex]; check(Allocation->Num() < Allocation->GetMaxCapacity()); if (Allocation->Num() == Allocation->GetCapacity()) { Allocation = GrowAllocation(AllocationIndex); } const int32 ActualEntityOffset = Allocation->Num(); FEntityInitializer::AddEntity(Allocation, ActualEntityOffset, ID); FEntityAllocationWriteContext WriteContext(*this); Allocation->PostModifyStructureExcludingHeaders(WriteContext); for (FComponentHeader& Header : Allocation->GetComponentHeaders()) { Header.PostWriteComponents(WriteContext); if (!Header.IsTag() && MemoryType == EMemoryType::DefaultConstructed) { const FComponentTypeInfo& ComponentTypeInfo = ComponentRegistry->GetComponentTypeChecked(Header.ComponentType); void* Value = Header.GetValuePtr(ActualEntityOffset); ComponentTypeInfo.ConstructItems(Value, 1); } } if (Allocation->Num() == Allocation->GetMaxCapacity()) { AllocationsWithCapacity[AllocationIndex] = false; } OnStructureChanged(); return ActualEntityOffset; } void FEntityManager::RemoveEntityFromAllocation(int32 AllocationIndex, int32 EntryIndexWithinAllocation) { CheckCanChangeStructure(); FEntityAllocation* Allocation = EntityAllocations[AllocationIndex]; const int32 ActualEntityOffset = EntryIndexWithinAllocation; const int32 LastEntityIndex = Allocation->Num() - 1; const bool bHadCapacity = Allocation->Num() != Allocation->GetMaxCapacity(); FEntityAllocationWriteContext WriteContext(*this); Allocation->PostModifyStructureExcludingHeaders(WriteContext); // Destruct its components for (FComponentHeader& Header : Allocation->GetComponentHeaders()) { Header.PostWriteComponents(WriteContext); if (Header.IsTag()) { continue; } const FComponentTypeInfo& ComponentTypeInfo = ComponentRegistry->GetComponentTypeChecked(Header.ComponentType); void* Value = Header.GetValuePtr(ActualEntityOffset); ComponentTypeInfo.DestructItems(Value, 1); if (LastEntityIndex != ActualEntityOffset) { void* SwapSource = Header.GetValuePtr(LastEntityIndex); ComponentTypeInfo.RelocateConstructItems(Value, SwapSource, 1); } } // When removing we just swap the tail element with the element to remove, and fix up the indices if (LastEntityIndex != ActualEntityOffset) { FEntityInitializer::MoveEntryIndex(Allocation, LastEntityIndex, ActualEntityOffset); // Fixup entry offset for the changed entry FMovieSceneEntityID SwappedEntityID = Allocation->GetEntityIDs()[ActualEntityOffset]; EntityLocations[SwappedEntityID.AsIndex()].Set(AllocationIndex, ActualEntityOffset); } // Free this entity index without needing to fix up any other indices FEntityInitializer::FreeEntryIndex(Allocation, LastEntityIndex); // Does the one we just removed from now have capacity when it didn't before? if (Allocation->Num() == 0) { DestroyAllocation(Allocation); EntityAllocationMasks.RemoveAt(AllocationIndex); EntityAllocations.RemoveAt(AllocationIndex); AllocationsWithCapacity[AllocationIndex] = false; } else if (!bHadCapacity) { AllocationsWithCapacity[AllocationIndex] = true; } OnStructureChanged(); } void FEntityManager::OnStructureChanged() { StructureMutationSystemSerialNumber = SystemSerialNumber; bAccumulatedMaskStale = true; } void FEntityManager::CheckInvariants() { #if DO_GUARD_SLOW if (!GCheckMovieSceneEntityManagerInvariants) { return; } for (int32 AllocationIndex = 0; AllocationIndex < EntityAllocations.GetMaxIndex(); ++AllocationIndex) { if (EntityAllocations.IsAllocated(AllocationIndex)) { TArrayView EntityIDs = EntityAllocations[AllocationIndex]->GetEntityIDs(); for (int32 EntityIndex = 0; EntityIndex < EntityIDs.Num(); ++EntityIndex) { FEntityLocation Location = EntityLocations[EntityIDs[EntityIndex].AsIndex()]; check(Location.GetAllocationIndex() == AllocationIndex && Location.GetEntryIndexWithinAllocation() == EntityIndex); } } } for (int32 EntityIndex = 0; EntityIndex < EntityLocations.GetMaxIndex(); ++EntityIndex) { if (EntityLocations.IsAllocated(EntityIndex)) { FEntityLocation Location = EntityLocations[EntityIndex]; if (Location.IsValid()) { const FEntityAllocation* Allocation = EntityAllocations[Location.GetAllocationIndex()]; check(Allocation->GetEntityIDs()[Location.GetEntryIndexWithinAllocation()].AsIndex() == EntityIndex); } } } #endif } } // namespace MovieScene } // namespace UE