// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreTypes.h" #include "Containers/Map.h" #include "Containers/SparseArray.h" #include "Algo/AnyOf.h" #include "EntitySystem/MovieSceneEntitySystem.h" #include "EntitySystem/MovieSceneComponentAccessors.h" #include "EntitySystem/MovieSceneEntitySystemTask.h" #include "EntitySystem/MovieSceneEntitySystemLinker.h" #include "EntitySystem/BuiltInComponentTypes.h" #include "EntitySystem/MovieSceneComponentTypeInfo.h" namespace UE { namespace MovieScene { /** * Aggregate of multiple input entities for an output defined in a TOverlappingEntityTracker */ struct FEntityOutputAggregate { bool bNeedsRestoration = false; }; struct FGarbageTraits { FORCEINLINE static constexpr bool IsGarbage(...) { return false; } template FORCEINLINE static std::enable_if_t::Value, bool> IsGarbage(T* InObject) { constexpr bool bTypeDependentFalse = !std::is_same_v; static_assert(bTypeDependentFalse, "Raw object pointers are no longer supported. Please use TObjectPtr instead."); } FORCEINLINE static bool IsGarbage(FObjectKey InObject) { return FBuiltInComponentTypes::IsBoundObjectGarbage(InObject.ResolveObjectPtr()); } template FORCEINLINE static bool IsGarbage(TObjectPtr& InObject) { return FBuiltInComponentTypes::IsBoundObjectGarbage(InObject); } FORCEINLINE static bool IsGarbage(FObjectComponent& InComponent) { return FBuiltInComponentTypes::IsBoundObjectGarbage(InComponent.GetObject()); } template FORCEINLINE static void AddReferencedObjects(FReferenceCollector& ReferenceCollector, T* In) { if constexpr (THasAddReferencedObjectForComponent::Value) { AddReferencedObjectForComponent(&ReferenceCollector, In); } } }; template struct TGarbageTraitsImpl; template struct TOverlappingEntityInput; template struct TGarbageTraitsImpl, T...> { static bool IsGarbage(TOverlappingEntityInput& InParam) { return (FGarbageTraits::IsGarbage(InParam.Key.template Get()) || ...); } static void AddReferencedObjects(FReferenceCollector& ReferenceCollector, TOverlappingEntityInput& InParam) { (FGarbageTraits::AddReferencedObjects(ReferenceCollector, &InParam.Key.template Get()), ...); } template static void Unpack(const TTuple& InTuple, CallbackType&& Callback) { Callback(InTuple.template Get()...); } }; /** Override for OutputType* in order to provide custom garbage collection logic */ inline void CollectGarbageForOutput(void*) { } template struct TOverlappingEntityInput { using GarbageTraits = TGarbageTraitsImpl, T...>; TTuple Key; template TOverlappingEntityInput(ArgTypes&&... InArgs) : Key(Forward(InArgs)...) {} friend uint32 GetTypeHash(const TOverlappingEntityInput& In) { return GetTypeHash(In.Key); } friend bool operator==(const TOverlappingEntityInput& A, const TOverlappingEntityInput& B) { return A.Key == B.Key; } template void Unpack(CallbackType&& Callback) { GarbageTraits::Unpack(Key, MoveTemp(Callback)); } }; /** * Templated utility class that assists in tracking the state of many -> one data relationships in an FEntityManager. * InputKeyTypes defines the component type(s) which defines the key that determines whether an entity animates the same output. * OutputType defines the user-specfied data to be associated with the multiple inputs (ie, its output) */ template struct TOverlappingEntityTrackerImpl { using KeyType = TOverlappingEntityInput; using ParamType = typename TCallTraits::ParamType; bool IsInitialized() const { return bIsInitialized; } /** * Update this tracker by iterating any entity that contains InKeyComponent, and matches the additional optional filter * Only entities tagged as NeedsLink or NeedsUnlink are iterated, invalidating their outputs */ void Update(UMovieSceneEntitySystemLinker* Linker, TComponentTypeID... InKeyComponents, const FEntityComponentFilter& InFilter) { UpdateFromComponents(Linker, InFilter, InKeyComponents...); } /** * Update this tracker by iterating any entity that contains InKeyComponent, and matches the additional optional filter * Only entities tagged as NeedsLink or NeedsUnlink are iterated, invalidating their outputs */ template void UpdateFromComponents(UMovieSceneEntitySystemLinker* Linker, const FEntityComponentFilter& InFilter, TComponentTypeID... InKeyComponents) { check(bIsInitialized); FBuiltInComponentTypes* BuiltInComponents = FBuiltInComponentTypes::Get(); // Visit unlinked entities TFilteredEntityTask<>(TEntityTaskComponents<>()) .CombineFilter(InFilter) .FilterAll({ BuiltInComponents->Tags.NeedsUnlink, FComponentTypeID(InKeyComponents)... }) .Iterate_PerAllocation(&Linker->EntityManager, [this](const FEntityAllocation* Allocation){ this->VisitUnlinkedAllocation(Allocation); }); // Visit newly or re-linked entities FEntityTaskBuilder() .ReadAllOf(InKeyComponents...) .CombineFilter(InFilter) .FilterAll({ BuiltInComponents->Tags.NeedsLink }) .Iterate_PerAllocation(&Linker->EntityManager, [this](const FEntityAllocation* Allocation, TRead... ReadKeys){ this->VisitLinkedAllocation(Allocation, ReadKeys...); }); } /** * Update this tracker by iterating any entity that contains InKeyComponent, and matches the additional optional filter * Only entities tagged as NeedsUnlink are iterated, invalidating their outputs */ template void UpdateUnlinkedOnly(UMovieSceneEntitySystemLinker* Linker, TComponentTypeID... InKeyComponent, const FEntityComponentFilter& InFilter) { check(bIsInitialized); FBuiltInComponentTypes* BuiltInComponents = FBuiltInComponentTypes::Get(); // Visit unlinked entities TFilteredEntityTask<>(TEntityTaskComponents<>()) .CombineFilter(InFilter) .FilterAll({ BuiltInComponents->Tags.NeedsUnlink, InKeyComponent... }) .Iterate_PerAllocation(&Linker->EntityManager, [this](const FEntityAllocation* Allocation){ this->VisitUnlinkedAllocation(Allocation); }); } /** * Update this tracker by (re)linking the specified allocation */ template void VisitActiveAllocation(const FEntityAllocation* Allocation, TComponentPtr... ReadKeys) { VisitActiveAllocationImpl(Allocation, ReadKeys...); } /** * Update this tracker by (re)linking the specified allocation */ template void VisitLinkedAllocation(const FEntityAllocation* Allocation, TComponentPtr... ReadKeys) { VisitActiveAllocationImpl(Allocation, ReadKeys...); } /** * Update this tracker by unlinking the specified allocation */ void VisitUnlinkedAllocation(const FEntityAllocation* Allocation) { VisitUnlinkedAllocationImpl(Allocation); } /** * Process any outputs that were invalidated as a result of Update being called using a custom handler. * * InHandler Any user-defined handler type that contains the following named functions: * // Called when an output is first created * void InitializeOutput(InputKeyTypes... Inputs, TArrayView Inputs, OutputType* Output, FEntityOutputAggregate Aggregate); * * // Called when an output has been updated with new inputs * void UpdateOutput(InputKeyTypes... Inputs, TArrayView Inputs, OutputType* Output, FEntityOutputAggregate Aggregate); * * // Called when all an output's inputs are no longer relevant, and as such the output should be destroyed (or restored) * void DestroyOutput(InputKeyTypes... Inputs, OutputType* Output, FEntityOutputAggregate Aggregate); */ template void ProcessInvalidatedOutputs(UMovieSceneEntitySystemLinker* Linker, HandlerType&& InHandler) { if (InvalidatedOutputs.Num() != 0) { TArray> InputArray; FComponentTypeID RestoreStateTag = FBuiltInComponentTypes::Get()->Tags.RestoreState; int32 NumRemoved = 0; auto RestoreStatePredicate = [Linker, RestoreStateTag](FMovieSceneEntityID InEntityID){ return Linker->EntityManager.HasComponent(InEntityID, RestoreStateTag); }; check(InvalidatedOutputs.Num() < NO_OUTPUT); for (TConstSetBitIterator<> InvalidOutput(InvalidatedOutputs); InvalidOutput; ++InvalidOutput) { const uint16 OutputIndex = static_cast(InvalidOutput.GetIndex()); InputArray.Reset(); for (auto Inputs = OutputToEntity.CreateKeyIterator(OutputIndex); Inputs; ++Inputs) { if (ensure(Linker->EntityManager.IsAllocated(Inputs.Value()))) { InputArray.Add(Inputs.Value()); } else { EntityToOutput.Remove(Inputs.Value()); Inputs.RemoveCurrent(); } } FOutput& Output = Outputs[OutputIndex]; if (InputArray.Num() > 0) { Output.Aggregate.bNeedsRestoration = Algo::AnyOf(InputArray, RestoreStatePredicate); if (NewOutputs.IsValidIndex(OutputIndex) && NewOutputs[OutputIndex] == true) { Output.Key.Unpack([&InHandler, &InputArray, &Output](typename TCallTraits::ParamType... InKeys){ InHandler.InitializeOutput(InKeys..., InputArray, &Output.OutputData, Output.Aggregate); }); } else { Output.Key.Unpack([&InHandler, &InputArray, &Output](typename TCallTraits::ParamType... InKeys){ InHandler.UpdateOutput(InKeys..., InputArray, &Output.OutputData, Output.Aggregate); }); } } else { Output.Key.Unpack([&InHandler, &Output](typename TCallTraits::ParamType... InKeys){ InHandler.DestroyOutput(InKeys..., &Output.OutputData, Output.Aggregate); }); } if (InputArray.Num() == 0) { KeyToOutput.Remove(Outputs[OutputIndex].Key); Outputs.RemoveAt(OutputIndex, 1); } } } InvalidatedOutputs.Empty(); NewOutputs.Empty(); } bool IsEmpty() const { return Outputs.Num() != 0; } /** * Destroy all the outputs currently being tracked */ template void Destroy(HandlerType&& InHandler) { for (FOutput& Output : Outputs) { Output.Key.Unpack([&InHandler, &Output](typename TCallTraits::ParamType... InKeys){ InHandler.DestroyOutput(InKeys..., &Output.OutputData, Output.Aggregate); }); } EntityToOutput.Empty(); OutputToEntity.Empty(); KeyToOutput.Empty(); Outputs.Empty(); InvalidatedOutputs.Empty(); NewOutputs.Empty(); } void FindEntityIDs(ParamType Key, TArray& OutEntityIDs) const { if (const uint16* OutputIndex = KeyToOutput.Find(Key)) { OutputToEntity.MultiFind(*OutputIndex, OutEntityIDs); } } const OutputType* FindOutput(FMovieSceneEntityID EntityID) const { if (const uint16* OutputIndex = EntityToOutput.Find(EntityID)) { if (ensure(Outputs.IsValidIndex(*OutputIndex))) { return &Outputs[*OutputIndex].OutputData; } } return nullptr; } const OutputType* FindOutput(ParamType Key) const { if (const uint16* OutputIndex = KeyToOutput.Find(Key)) { if (ensure(Outputs.IsValidIndex(*OutputIndex))) { return &Outputs[*OutputIndex].OutputData; } } return nullptr; } bool NeedsRestoration(ParamType Key, bool bEnsureOutput = false) const { const uint16 ExistingOutput = FindOutputByKey(Key); const bool bIsOutputValid = IsOutputValid(ExistingOutput); ensure(bIsOutputValid || !bEnsureOutput); if (bIsOutputValid) { return Outputs[ExistingOutput].Aggregate.bNeedsRestoration; } return false; } void SetNeedsRestoration(ParamType Key, bool bNeedsRestoration, bool bEnsureOutput = false) { const uint16 ExistingOutput = FindOutputByKey(Key); const bool bIsOutputValid = IsOutputValid(ExistingOutput); ensure(bIsOutputValid || !bEnsureOutput); if (bIsOutputValid) { Outputs[ExistingOutput].Aggregate.bNeedsRestoration = bNeedsRestoration; } } protected: template void VisitActiveAllocationImpl(const FEntityAllocation* Allocation, TComponentPtr... Keys) { check(bIsInitialized); const int32 Num = Allocation->Num(); const FMovieSceneEntityID* EntityIDs = Allocation->GetRawEntityIDs(); const bool bNeedsLink = Allocation->HasComponent(FBuiltInComponentTypes::Get()->Tags.NeedsLink); if (Allocation->HasComponent(FBuiltInComponentTypes::Get()->Tags.RestoreState)) { for (int32 Index = 0; Index < Num; ++Index) { const uint16 OutputIndex = MakeOutput(EntityIDs[Index], TOverlappingEntityInput(Keys[Index]...), bNeedsLink); Outputs[OutputIndex].Aggregate.bNeedsRestoration = true; } } else { for (int32 Index = 0; Index < Num; ++Index) { MakeOutput(EntityIDs[Index], TOverlappingEntityInput(Keys[Index]...), bNeedsLink); } } } void VisitUnlinkedAllocationImpl(const FEntityAllocation* Allocation) { check(bIsInitialized); const int32 Num = Allocation->Num(); const FMovieSceneEntityID* EntityIDs = Allocation->GetRawEntityIDs(); for (int32 Index = 0; Index < Num; ++Index) { ClearOutputByEntity(EntityIDs[Index]); } } uint16 MakeOutput(FMovieSceneEntityID EntityID, ParamType InKey, bool bAlwaysInvalidate) { const uint16 PreviousOutputIndex = FindOutputByEntity(EntityID); const uint16 DesiredOutputIndex = CreateOutputByKey(InKey); if (PreviousOutputIndex == DesiredOutputIndex) { if (bAlwaysInvalidate) { // Previous output is now invalidated since we're removing this entity InvalidatedOutputs.PadToNum(DesiredOutputIndex + 1, false); InvalidatedOutputs[DesiredOutputIndex] = true; } return DesiredOutputIndex; } if (PreviousOutputIndex != NO_OUTPUT) { // Previous output is now invalidated since we're removing this entity InvalidatedOutputs.PadToNum(PreviousOutputIndex + 1, false); InvalidatedOutputs[PreviousOutputIndex] = true; // Remove the entitiy's contribution from the previous output OutputToEntity.Remove(PreviousOutputIndex, EntityID); EntityToOutput.Remove(EntityID); } // Invalidate the new output InvalidatedOutputs.PadToNum(DesiredOutputIndex + 1, false); InvalidatedOutputs[DesiredOutputIndex] = true; EntityToOutput.Add(EntityID, DesiredOutputIndex); OutputToEntity.Add(DesiredOutputIndex, EntityID); return DesiredOutputIndex; } uint16 CreateOutputByKey(ParamType Key) { const uint16 ExistingOutput = FindOutputByKey(Key); if (ExistingOutput != NO_OUTPUT) { return ExistingOutput; } const int32 Index = Outputs.Add(FOutput{ Key, OutputType{} }); check(Index < NO_OUTPUT); const uint16 NewOutput = static_cast(Index); NewOutputs.PadToNum(NewOutput + 1, false); NewOutputs[NewOutput] = true; KeyToOutput.Add(Key, NewOutput); return NewOutput; } uint16 FindOutputByKey(ParamType Key) const { const uint16* OutputIndex = KeyToOutput.Find(Key); return OutputIndex ? *OutputIndex : NO_OUTPUT; } uint16 FindOutputByEntity(FMovieSceneEntityID EntityID) const { const uint16* OutputIndex = EntityToOutput.Find(EntityID); return OutputIndex ? *OutputIndex : NO_OUTPUT; } void ClearOutputByEntity(FMovieSceneEntityID EntityID) { const uint16 OutputIndex = FindOutputByEntity(EntityID); if (OutputIndex != NO_OUTPUT) { OutputToEntity.Remove(OutputIndex, EntityID); EntityToOutput.Remove(EntityID); InvalidatedOutputs.PadToNum(OutputIndex + 1, false); InvalidatedOutputs[OutputIndex] = true; } } bool IsOutputValid(uint16 OutputIndex) { return OutputIndex != NO_OUTPUT && (!InvalidatedOutputs.IsValidIndex(OutputIndex) || !InvalidatedOutputs[OutputIndex]); } struct FOutput { KeyType Key; OutputType OutputData; FEntityOutputAggregate Aggregate; }; TMap EntityToOutput; TMultiMap OutputToEntity; TMap KeyToOutput; TSparseArray< FOutput > Outputs; TBitArray<> InvalidatedOutputs, NewOutputs; bool bIsInitialized = false; static constexpr uint16 NO_OUTPUT = MAX_uint16; }; template struct TOverlappingEntityTracker_NoGarbage : TOverlappingEntityTrackerImpl { void Initialize(UMovieSceneEntitySystem* OwningSystem) { this->bIsInitialized = true; } }; template struct TOverlappingEntityTracker_WithGarbage : TOverlappingEntityTrackerImpl { using ThisType = TOverlappingEntityTracker_WithGarbage; using Super = TOverlappingEntityTrackerImpl; using typename Super::FOutput; using typename Super::KeyType; ~TOverlappingEntityTracker_WithGarbage() { UMovieSceneEntitySystem* OwningSystem = WeakOwningSystem.GetEvenIfUnreachable(); UMovieSceneEntitySystemLinker* Linker = OwningSystem ? OwningSystem->GetLinker() : nullptr; if (Linker) { Linker->Events.TagGarbage.RemoveAll(this); Linker->Events.CleanTaggedGarbage.RemoveAll(this); Linker->Events.AddReferencedObjects.RemoveAll(this); } } void Initialize(UMovieSceneEntitySystem* OwningSystem) { if (this->bIsInitialized) { return; } this->bIsInitialized = true; WeakOwningSystem = OwningSystem; OwningSystem->GetLinker()->Events.TagGarbage.AddRaw(this, &ThisType::TagGarbage); OwningSystem->GetLinker()->Events.CleanTaggedGarbage.AddRaw(this, &ThisType::CleanTaggedGarbage); OwningSystem->GetLinker()->Events.AddReferencedObjects.AddRaw(this, &ThisType::AddReferencedObjects); } void TagGarbage(UMovieSceneEntitySystemLinker* Linker) { for (int32 Index = this->Outputs.GetMaxIndex()-1; Index >= 0; --Index) { if (!this->Outputs.IsValidIndex(Index)) { continue; } const uint16 OutputIndex = static_cast(Index); FOutput& Output = this->Outputs[Index]; if (KeyType::GarbageTraits::IsGarbage(Output.Key)) { CollectGarbageForOutput(&this->Outputs[Index].OutputData); this->Outputs.RemoveAt(Index, 1); // Make sure this output is not flagged as invalidated because it is being destroyed. // This prevents us from blindly processing it in ProcessInvalidatedOutputs if (this->InvalidatedOutputs.IsValidIndex(OutputIndex)) { this->InvalidatedOutputs[OutputIndex] = false; } for (auto It = this->OutputToEntity.CreateKeyIterator(OutputIndex); It; ++It) { this->EntityToOutput.Remove(It.Value()); It.RemoveCurrent(); } } } for (auto It = this->KeyToOutput.CreateIterator(); It; ++It) { if (KeyType::GarbageTraits::IsGarbage(It.Key()) || !this->Outputs.IsValidIndex(It.Value())) { It.RemoveCurrent(); } } } void CleanTaggedGarbage(UMovieSceneEntitySystemLinker* Linker) { FComponentTypeID NeedsUnlink = FBuiltInComponentTypes::Get()->Tags.NeedsUnlink; // Check whether any of our inputs are about to be destroyed for (auto EntityToOutputIt = this->EntityToOutput.CreateIterator(); EntityToOutputIt; ++EntityToOutputIt) { const FMovieSceneEntityID EntityID = EntityToOutputIt.Key(); const uint16 OutputIndex = EntityToOutputIt.Value(); if (OutputIndex != Super::NO_OUTPUT && Linker->EntityManager.HasComponent(EntityID, NeedsUnlink)) { this->OutputToEntity.Remove(OutputIndex, EntityID); this->InvalidatedOutputs.PadToNum(OutputIndex + 1, false); this->InvalidatedOutputs[OutputIndex] = true; EntityToOutputIt.RemoveCurrent(); } } } void AddReferencedObjects(UMovieSceneEntitySystemLinker* Linker, FReferenceCollector& ReferenceCollector) { constexpr bool bKeyCanBeGarbage = (THasAddReferencedObjectForComponent::Value || ...); constexpr bool bOutputCanBeGarbage = THasAddReferencedObjectForComponent::Value; if constexpr (bKeyCanBeGarbage) { for (TPair& Pair : this->KeyToOutput) { KeyType::GarbageTraits::AddReferencedObjects(ReferenceCollector, Pair.Key); } } for (FOutput& Output : this->Outputs) { if constexpr (bKeyCanBeGarbage) { KeyType::GarbageTraits::AddReferencedObjects(ReferenceCollector, Output.Key); } if constexpr (bOutputCanBeGarbage) { FGarbageTraits::AddReferencedObjects(ReferenceCollector, Output.OutputData); } } } protected: TWeakObjectPtr WeakOwningSystem; }; template using TOverlappingEntityTracker = std::conditional_t< (THasAddReferencedObjectForComponent::Value || ...) || THasAddReferencedObjectForComponent::Value, TOverlappingEntityTracker_WithGarbage, TOverlappingEntityTracker_NoGarbage >; } // namespace MovieScene } // namespace UE