// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Async/TaskGraphInterfaces.h" #include "Containers/Array.h" #include "Containers/UnrealString.h" #include "EntitySystem/EntityAllocationIterator.h" #include "EntitySystem/MovieSceneComponentPtr.h" #include "EntitySystem/MovieSceneEntityManager.h" #include "EntitySystem/MovieSceneEntitySystemTypes.h" #include "EntitySystem/MovieSceneSystemTaskDependencies.h" #include "EntitySystem/RelativePtr.h" #include "HAL/Platform.h" #include "Misc/AssertionMacros.h" #include "MovieSceneEntityIDs.h" #include "Templates/Tuple.h" #include namespace UE { namespace MovieScene { class FEntityManager; using FPreLockedDataPtr = TRelativePtr; struct FComponentAccess { FComponentTypeID ComponentType; FORCEINLINE void PreLockComponentData(const FEntityAllocation* Allocation, FPreLockedDataPtr* PrelockedComponentData) const { // Always relative to the component data which is a separate allocation const FComponentHeader& Header = Allocation->GetComponentHeaderChecked(ComponentType); PrelockedComponentData->Reset(Allocation->GetComponentDataAddress(), Header.Components); } }; struct FReadAccess : FComponentAccess { FReadAccess(FComponentTypeID InComponentType) : FComponentAccess{ InComponentType } {} }; struct FWriteAccess : FComponentAccess { FWriteAccess(FComponentTypeID InComponentType) : FComponentAccess{ InComponentType } {} }; struct FOptionalComponentAccess { FComponentTypeID ComponentType; FComponentTypeIDFilter Condition; FORCEINLINE void PreLockComponentData(FEntityAllocationIteratorItem Item, FPreLockedDataPtr* PrelockedComponentData) const { if (!Condition.Passes(Item.GetAllocationType())) { return; } const FEntityAllocation* Allocation = Item.GetAllocation(); const FComponentHeader* Header = Allocation->FindComponentHeader(ComponentType); if (Header) { // Always relative to the component data which is a separate allocation PrelockedComponentData->Reset(Allocation->GetComponentDataAddress(), Header->Components); } } }; struct FOptionalReadAccess : FOptionalComponentAccess { FOptionalReadAccess(FComponentTypeID InComponentType, FComponentTypeIDFilter InConditionType = FComponentTypeIDFilter()) : FOptionalComponentAccess{ InComponentType, InConditionType } {} }; struct FOptionalWriteAccess : FOptionalComponentAccess { FOptionalWriteAccess(FComponentTypeID InComponentType, FComponentTypeIDFilter InConditionType = FComponentTypeIDFilter()) : FOptionalComponentAccess{ InComponentType, InConditionType } {} }; struct FEntityIDAccess { using AccessType = const FMovieSceneEntityID; static constexpr int32 PreLockedDataNum = 1; FORCEINLINE void PreLockComponentData(const FEntityAllocation* Allocation, FPreLockedDataPtr* PrelockedComponentData) const { // Must be relative to the base allocation ptr PrelockedComponentData->Reset(Allocation, Allocation->GetRawEntityIDs()); } FORCEINLINE TRead ResolvePreLockedComponentData(const FEntityAllocation* Allocation, const FPreLockedDataPtr* Ptr, FEntityAllocationWriteContext WriteContext) const { // Resolve using the same base ptr as PreLockComponentData return TRead(Ptr->Resolve(Allocation)); } FORCEINLINE TRead LockComponentData(const FEntityAllocation* Allocation, FEntityAllocationWriteContext WriteContext) const { return TRead(Allocation->GetRawEntityIDs()); } }; struct FFilterMatchPassthrough { using AccessType = bool; //static constexpr int32 PreLockedDataNum = 1; FORCEINLINE void PreLockComponentData(FEntityAllocationIteratorItem Item, FPreLockedDataPtr* PrelockedComponentData) const { // Must be relative to the base allocation ptr const bool bFilterMatch = Filter.Match(Item.GetAllocationType()); // We abuse the pointer here by using either the allocation ptr itself as 'true' and a nullptr as 'false' if (bFilterMatch) { PrelockedComponentData->Reset(nullptr); } else { const FEntityAllocation* Allocation = Item.GetAllocation(); PrelockedComponentData->Reset(Allocation, Allocation); } } FORCEINLINE bool ResolvePreLockedComponentData(const FEntityAllocation* Allocation, const FPreLockedDataPtr* Ptr, FEntityAllocationWriteContext WriteContext) const { return Ptr->Resolve(Allocation) != nullptr; } FORCEINLINE bool LockComponentData(FEntityAllocationIteratorItem Item, FEntityAllocationWriteContext WriteContext) const { return Filter.Match(Item.GetAllocationType()); } FEntityComponentFilter Filter; }; template struct TReadAccess : FReadAccess { using AccessType = const T; static constexpr int32 PreLockedDataNum = 1; TReadAccess(FComponentTypeID InComponentTypeID) : FReadAccess{ InComponentTypeID } {} FORCEINLINE TRead ResolvePreLockedComponentData(const FEntityAllocation* Allocation, const FPreLockedDataPtr* Ptr, FEntityAllocationWriteContext WriteContext) const { // Resolve using the same base ptr as PreLockComponentData return TRead(Ptr->Resolve(Allocation->GetComponentDataAddress())); } FORCEINLINE TComponentLock> LockComponentData(const FEntityAllocation* Allocation, FEntityAllocationWriteContext WriteContext) const { return Allocation->ReadComponents(ComponentType.ReinterpretCast()); } }; struct FErasedReadAccess : FReadAccess { static constexpr int32 PreLockedDataNum = 1; FErasedReadAccess(FComponentTypeID InComponentTypeID) : FReadAccess{ InComponentTypeID } {} FORCEINLINE void PreLockComponentData(const FEntityAllocation* Allocation, FPreLockedDataPtr* PrelockedComponentData) const { // Erased reads always pass the header since they need the size information const FComponentHeader& Header = Allocation->GetComponentHeaderChecked(ComponentType); PrelockedComponentData->Reset(Allocation, &Header); } FORCEINLINE FReadErased ResolvePreLockedComponentData(const FEntityAllocation* Allocation, const FPreLockedDataPtr* Ptr, FEntityAllocationWriteContext WriteContext) const { // Resolve using the same base ptr as PreLockComponentData return FReadErased(Ptr->Resolve(Allocation)); } FORCEINLINE FComponentReader LockComponentData(const FEntityAllocation* Allocation, FEntityAllocationWriteContext WriteContext) const { return Allocation->ReadComponentsErased(ComponentType); } }; struct FErasedOptionalReadAccess : FReadAccess { static constexpr int32 PreLockedDataNum = 1; FComponentTypeIDFilter Condition; FErasedOptionalReadAccess(FComponentTypeID InComponentTypeID, FComponentTypeIDFilter InCondition) : FReadAccess{ InComponentTypeID } , Condition(InCondition) {} FORCEINLINE void PreLockComponentData(FEntityAllocationIteratorItem Item, FPreLockedDataPtr* PrelockedComponentData) const { if (!Condition.Passes(Item.GetAllocationType())) { return; } const FEntityAllocation* Allocation = Item.GetAllocation(); const FComponentHeader* Header = Allocation->FindComponentHeader(ComponentType); if (Header) { // Erased reads always pass the header since they need the size information PrelockedComponentData->Reset(Allocation, Header); } } FORCEINLINE FReadErasedOptional ResolvePreLockedComponentData(const FEntityAllocation* Allocation, const FPreLockedDataPtr* Ptr, FEntityAllocationWriteContext WriteContext) const { // Resolve using the same base ptr as PreLockComponentData return FReadErasedOptional(Ptr->Resolve(Allocation)); } FORCEINLINE FOptionalComponentReader LockComponentData(FEntityAllocationIteratorItem Item, FEntityAllocationWriteContext WriteContext) const { if (!Condition.Passes(Item.GetAllocationType())) { return FOptionalComponentReader(); } return Item.GetAllocation()->TryReadComponentsErased(ComponentType); } }; struct FErasedWriteAccess : FWriteAccess { static constexpr int32 PreLockedDataNum = 1; FErasedWriteAccess(FComponentTypeID InComponentTypeID) : FWriteAccess{ InComponentTypeID } {} FORCEINLINE void PreLockComponentData(const FEntityAllocation* Allocation, FPreLockedDataPtr* PrelockedComponentData) const { const FComponentHeader& Header = Allocation->GetComponentHeaderChecked(ComponentType); // Erased writes always pass the header since they need the size information PrelockedComponentData->Reset(Allocation, &Header); } FORCEINLINE FWriteErased ResolvePreLockedComponentData(const FEntityAllocation* Allocation, const FPreLockedDataPtr* Ptr, FEntityAllocationWriteContext WriteContext) const { // Resolve using the same base ptr as PreLockComponentData return FWriteErased(Ptr->Resolve(Allocation)); } FORCEINLINE FComponentWriter LockComponentData(const FEntityAllocation* Allocation, FEntityAllocationWriteContext WriteContext) const { return Allocation->WriteComponentsErased(ComponentType, WriteContext); } }; template struct TWriteAccess : FWriteAccess { using AccessType = T; static constexpr int32 PreLockedDataNum = 1; TWriteAccess(FComponentTypeID InComponentTypeID) : FWriteAccess{ InComponentTypeID } {} FORCEINLINE TWrite ResolvePreLockedComponentData(const FEntityAllocation* Allocation, const FPreLockedDataPtr* Ptr, FEntityAllocationWriteContext WriteContext) const { // Resolve using the same base ptr as PreLockComponentData return TWrite(Ptr->Resolve(Allocation->GetComponentDataAddress())); } FORCEINLINE TComponentLock> LockComponentData(const FEntityAllocation* Allocation, FEntityAllocationWriteContext WriteContext) const { return Allocation->WriteComponents(ComponentType.ReinterpretCast(), WriteContext); } }; template struct TOptionalReadAccess : FOptionalReadAccess { using AccessType = const T; static constexpr int32 PreLockedDataNum = 1; TOptionalReadAccess(FComponentTypeID InComponentTypeID, FComponentTypeIDFilter InConditionType = FComponentTypeIDFilter()) : FOptionalReadAccess{InComponentTypeID, InConditionType} {} FORCEINLINE TReadOptional ResolvePreLockedComponentData(const FEntityAllocation* Allocation, const FPreLockedDataPtr* Ptr, FEntityAllocationWriteContext WriteContext) const { // Resolve using the same base ptr as PreLockComponentData return TReadOptional(Ptr->Resolve(Allocation->GetComponentDataAddress())); } FORCEINLINE TComponentLock> LockComponentData(const FEntityAllocation* Allocation, FEntityAllocationWriteContext WriteContext) const { return Allocation->TryReadComponents(ComponentType.ReinterpretCast()); } }; template struct TOptionalWriteAccess : FOptionalWriteAccess { using AccessType = T; static constexpr int32 PreLockedDataNum = 1; TOptionalWriteAccess(FComponentTypeID InComponentTypeID, FComponentTypeIDFilter InConditionType = FComponentTypeIDFilter()) : FOptionalWriteAccess{ InComponentTypeID, InConditionType } {} FORCEINLINE TWriteOptional ResolvePreLockedComponentData(const FEntityAllocation* Allocation, const FPreLockedDataPtr* Ptr, FEntityAllocationWriteContext WriteContext) const { // Resolve using the same base ptr as PreLockComponentData return TWriteOptional(Ptr->Resolve(Allocation->GetComponentDataAddress())); } FORCEINLINE TComponentLock> LockComponentData(const FEntityAllocation* Allocation, FEntityAllocationWriteContext WriteContext) const { return Allocation->TryWriteComponents(ComponentType.ReinterpretCast(), WriteContext); } }; template struct TUnpackMultiComponentData; template struct TUnpackMultiComponentData, T...> { static TMultiComponentData...> ResolvePreLockedComponentData(const FEntityAllocation* Allocation, const FOptionalReadAccess* ComponentTypes, const FPreLockedDataPtr* PrelockedComponentData, FEntityAllocationWriteContext WriteContext) { return TMultiComponentData...>( TOptionalReadAccess(ComponentTypes[Indices].ComponentType).ResolvePreLockedComponentData(Allocation, &PrelockedComponentData[Indices], WriteContext)... ); } static TMultiComponentLock...> LockComponentData(const FEntityAllocation* Allocation, const FOptionalReadAccess* ComponentTypes, FEntityAllocationWriteContext WriteContext) { return TMultiComponentLock...>( TOptionalReadAccess(ComponentTypes[Indices].ComponentType).LockComponentData(Allocation, WriteContext)... ); } }; template struct TReadOneOfAccessor { using AccessType = TMultiComponentLock...>; static constexpr int32 PreLockedDataNum = sizeof...(T); TReadOneOfAccessor(TComponentTypeID... InComponentTypeIDs) : ComponentTypes{ InComponentTypeIDs... } {} void PreLockComponentData(FEntityAllocationIteratorItem Item, FPreLockedDataPtr* PrelockedComponentData) const { for (int32 Index = 0; Index < sizeof...(T); ++Index) { ComponentTypes[Index].PreLockComponentData(Item, &PrelockedComponentData[Index]); } } TMultiComponentData...> ResolvePreLockedComponentData(const FEntityAllocation* Allocation, const FPreLockedDataPtr* PrelockedComponentData, FEntityAllocationWriteContext WriteContext) const { return TUnpackMultiComponentData, T...> ::ResolvePreLockedComponentData(Allocation, ComponentTypes, PrelockedComponentData, WriteContext); } TMultiComponentLock...> LockComponentData(const FEntityAllocation* Allocation, FEntityAllocationWriteContext WriteContext) const { return TUnpackMultiComponentData, T...> ::LockComponentData(Allocation, ComponentTypes, WriteContext); } FOptionalReadAccess ComponentTypes[sizeof...(T)]; }; template struct TReadOneOrMoreOfAccessor { using AccessType = TMultiComponentLock...>; static constexpr int32 PreLockedDataNum = sizeof...(T); TReadOneOrMoreOfAccessor(TComponentTypeID... InComponentTypeIDs) : ComponentTypes{ InComponentTypeIDs... } {} void PreLockComponentData(FEntityAllocationIteratorItem Item, FPreLockedDataPtr* PrelockedComponentData) const { for (int32 Index = 0; Index < sizeof...(T); ++Index) { ComponentTypes[Index].PreLockComponentData(Item, &PrelockedComponentData[Index]); } } TMultiComponentData...> ResolvePreLockedComponentData(const FEntityAllocation* Allocation, const FPreLockedDataPtr* PrelockedComponentData, FEntityAllocationWriteContext WriteContext) const { return TUnpackMultiComponentData, T...> ::ResolvePreLockedComponentData(Allocation, ComponentTypes, PrelockedComponentData, WriteContext); } TMultiComponentLock...> LockComponentData(const FEntityAllocation* Allocation, FEntityAllocationWriteContext WriteContext) const { return TUnpackMultiComponentData, T...> ::LockComponentData(Allocation, ComponentTypes, WriteContext); } FOptionalReadAccess ComponentTypes[sizeof...(T)]; }; inline void AddAccessorToFilter(const FEntityIDAccess*, FEntityComponentFilter* OutFilter) { } inline void AddAccessorToFilter(const FFilterMatchPassthrough*, FEntityComponentFilter* OutFilter) { } inline void AddAccessorToFilter(const FComponentAccess* In, FEntityComponentFilter* OutFilter) { check(In->ComponentType); OutFilter->All({ In->ComponentType }); } inline void AddAccessorToFilter(const FOptionalComponentAccess* In, FEntityComponentFilter* OutFilter) { } template void AddAccessorToFilter(const TReadOneOfAccessor* In, FEntityComponentFilter* OutFilter) { FComponentMask Mask; for (int32 Index = 0; Index < sizeof...(T); ++Index) { FComponentTypeID Component = In->ComponentTypes[Index].ComponentType; if (Component) { Mask.Set(Component); } } check(Mask.NumComponents() != 0); OutFilter->Complex(Mask, EComplexFilterMode::OneOf); } template void AddAccessorToFilter(const TReadOneOrMoreOfAccessor* In, FEntityComponentFilter* OutFilter) { FComponentMask Mask; for (int32 Index = 0; Index < sizeof...(T); ++Index) { FComponentTypeID Component = In->ComponentTypes[Index].ComponentType; if (Component) { Mask.Set(Component); } } check(Mask.NumComponents() != 0); OutFilter->Complex(Mask, EComplexFilterMode::OneOrMoreOf); } inline void PopulatePrerequisites(const FEntityIDAccess*, const FSystemTaskPrerequisites& InPrerequisites, FGraphEventArray* OutGatheredPrereqs) { } inline void PopulatePrerequisites(const FComponentAccess* In, const FSystemTaskPrerequisites& InPrerequisites, FGraphEventArray* OutGatheredPrereqs) { check(In->ComponentType); InPrerequisites.FilterByComponent(*OutGatheredPrereqs, In->ComponentType); } inline void PopulatePrerequisites(const FOptionalComponentAccess* In, const FSystemTaskPrerequisites& InPrerequisites, FGraphEventArray* OutGatheredPrereqs) { if (In->ComponentType) { check(In->ComponentType); InPrerequisites.FilterByComponent(*OutGatheredPrereqs, In->ComponentType); } } template void PopulatePrerequisites(const TReadOneOfAccessor* In, const FSystemTaskPrerequisites& InPrerequisites, FGraphEventArray* OutGatheredPrereqs) { for (int32 Index = 0; Index < sizeof...(T); ++Index) { PopulatePrerequisites(&In->ComponentTypes[Index], InPrerequisites, OutGatheredPrereqs); } } template void PopulatePrerequisites(const TReadOneOrMoreOfAccessor* In, const FSystemTaskPrerequisites& InPrerequisites, FGraphEventArray* OutGatheredPrereqs) { for (int32 Index = 0; Index < sizeof...(T); ++Index) { PopulatePrerequisites(&In->ComponentTypes[Index], InPrerequisites, OutGatheredPrereqs); } } inline void PopulateSubsequents(const FWriteAccess* In, const FGraphEventRef& InEvent, FSystemSubsequentTasks& OutSubsequents) { check(In->ComponentType); OutSubsequents.AddComponentTask(In->ComponentType, InEvent); } inline void PopulateSubsequents(const FOptionalWriteAccess* In, const FGraphEventRef& InEvent, FSystemSubsequentTasks& OutSubsequents) { if (In->ComponentType) { OutSubsequents.AddComponentTask(In->ComponentType, InEvent); } } inline void PopulateSubsequents(const void* In, const FGraphEventRef& InEvent, FSystemSubsequentTasks& OutSubsequents) { } inline void PopulateReadWriteDependencies(const FEntityIDAccess*, FComponentMask& OutReadDependencies, FComponentMask& OutWriteDependencies) { } inline void PopulateReadWriteDependencies(const FReadAccess* In, FComponentMask& OutReadDependencies, FComponentMask& OutWriteDependencies) { checkSlow(In->ComponentType); OutReadDependencies.Set(In->ComponentType); } inline void PopulateReadWriteDependencies(const FOptionalReadAccess* In, FComponentMask& OutReadDependencies, FComponentMask& OutWriteDependencies) { if (In->ComponentType) { OutReadDependencies.Set(In->ComponentType); } } inline void PopulateReadWriteDependencies(const FWriteAccess* In, FComponentMask& OutReadDependencies, FComponentMask& OutWriteDependencies) { checkSlow(In->ComponentType); OutWriteDependencies.Set(In->ComponentType); } inline void PopulateReadWriteDependencies(const FOptionalWriteAccess* In, FComponentMask& OutReadDependencies, FComponentMask& OutWriteDependencies) { if (In->ComponentType) { OutWriteDependencies.Set(In->ComponentType); } } template void PopulateReadWriteDependencies(const TReadOneOfAccessor* In, FComponentMask& OutReadDependencies, FComponentMask& OutWriteDependencies) { for (int32 Index = 0; Index < sizeof...(T); ++Index) { PopulateReadWriteDependencies(&In->ComponentTypes[Index], OutReadDependencies, OutWriteDependencies); } } template void PopulateReadWriteDependencies(const TReadOneOrMoreOfAccessor* In, FComponentMask& OutReadDependencies, FComponentMask& OutWriteDependencies) { for (int32 Index = 0; Index < sizeof...(T); ++Index) { PopulateReadWriteDependencies(&In->ComponentTypes[Index], OutReadDependencies, OutWriteDependencies); } } inline bool HasBeenWrittenToSince(const FEntityIDAccess* In, FEntityAllocation* Allocation, uint64 InSystemSerial) { return Allocation->HasStructureChangedSince(InSystemSerial); } inline bool HasBeenWrittenToSince(const FComponentAccess* In, FEntityAllocation* Allocation, uint64 InSystemSerial) { return Allocation->GetComponentHeaderChecked(In->ComponentType).HasBeenWrittenToSince(InSystemSerial); } inline bool HasBeenWrittenToSince(const FOptionalComponentAccess* In, FEntityAllocation* Allocation, uint64 InSystemSerial) { if (FComponentHeader* Header = Allocation->FindComponentHeader(In->ComponentType)) { return Header->HasBeenWrittenToSince(InSystemSerial); } return false; } template bool HasBeenWrittenToSince(const TReadOneOfAccessor* In, FEntityAllocation* Allocation, uint64 InSystemSerial) { bool bAnyWrittenTo = false; for (int32 Index = 0; Index < sizeof...(T); ++Index) { bAnyWrittenTo |= HasBeenWrittenToSince(&In->ComponentTypes[Index], Allocation, InSystemSerial); } return bAnyWrittenTo; } template bool HasBeenWrittenToSince(const TReadOneOrMoreOfAccessor* In, FEntityAllocation* Allocation, uint64 InSystemSerial) { bool bAnyWrittenTo = false; for (int32 Index = 0; Index < sizeof...(T); ++Index) { bAnyWrittenTo |= HasBeenWrittenToSince(&In->ComponentTypes[Index], Allocation, InSystemSerial); } return bAnyWrittenTo; } template auto GetComponentAtIndex(T* InAccessor, int32 Index) -> decltype(DeclVal().ComponentAtIndex(0)) { return InAccessor->ComponentAtIndex(Index); } inline bool GetComponentAtIndex(const bool* BoolPassthrough, int32 Index) { return *BoolPassthrough; } inline bool IsAccessorValid(const FEntityIDAccess*) { return true; } inline bool IsAccessorValid(const FFilterMatchPassthrough*) { return true; } inline bool IsAccessorValid(const FComponentAccess* In) { return In->ComponentType != FComponentTypeID::Invalid(); } inline bool IsAccessorValid(const FOptionalComponentAccess* In) { return true; } template inline bool IsAccessorValid(const TReadOneOfAccessor* In) { bool bValid = false; for (int32 Index = 0; Index < sizeof...(T); ++Index) { bValid |= IsAccessorValid(&In->ComponentTypes[Index]); } return bValid; } template inline bool IsAccessorValid(const TReadOneOrMoreOfAccessor* In) { bool bValid = false; for (int32 Index = 0; Index < sizeof...(T); ++Index) { bValid |= IsAccessorValid(&In->ComponentTypes[Index]); } return bValid; } inline bool HasAccessorWork(const FEntityManager*, const FEntityIDAccess*) { return true; } inline bool HasAccessorWork(const FEntityManager* EntityManager, const FComponentAccess* In) { return EntityManager->ContainsComponent(In->ComponentType); } inline bool HasAccessorWork(const FEntityManager* EntityManager, const FOptionalComponentAccess* In) { return true; } template inline bool HasAccessorWork(const FEntityManager* EntityManager, const TReadOneOfAccessor* In) { bool bAnyWork = false; for (int32 Index = 0; Index < sizeof...(T); ++Index) { bAnyWork |= HasAccessorWork(EntityManager, &In->ComponentTypes[Index]); } return bAnyWork; } template inline bool HasAccessorWork(const FEntityManager* EntityManager, const TReadOneOrMoreOfAccessor* In) { bool bAnyWork = false; for (int32 Index = 0; Index < sizeof...(T); ++Index) { bAnyWork |= HasAccessorWork(EntityManager, &In->ComponentTypes[Index]); } return bAnyWork; } #if UE_MOVIESCENE_ENTITY_DEBUG MOVIESCENE_API void AccessorToString(const FReadAccess* In, FEntityManager* EntityManager, FString& OutString); MOVIESCENE_API void AccessorToString(const FWriteAccess* In, FEntityManager* EntityManager, FString& OutString); MOVIESCENE_API void AccessorToString(const FOptionalReadAccess* In, FEntityManager* EntityManager, FString& OutString); MOVIESCENE_API void AccessorToString(const FOptionalWriteAccess* In, FEntityManager* EntityManager, FString& OutString); MOVIESCENE_API void AccessorToString(const FEntityIDAccess*, FEntityManager* EntityManager, FString& OutString); MOVIESCENE_API void OneOfAccessorToString(const FOptionalReadAccess* In, FEntityManager* EntityManager, FString& OutString); template void AccessorToString(const TReadOneOfAccessor* In, FEntityManager* EntityManager, FString& OutString) { TArray Strings; for (int32 Index = 0; Index < sizeof...(T); ++Index) { OneOfAccessorToString(&In->ComponentTypes[Index], EntityManager, Strings.Emplace_GetRef()); } OutString += FString::Printf(TEXT("\n\tRead One Of: [ %s ]"), *FString::Join(Strings, TEXT(","))); } template void AccessorToString(const TReadOneOrMoreOfAccessor* In, FEntityManager* EntityManager, FString& OutString) { TArray Strings; for (int32 Index = 0; Index < sizeof...(T); ++Index) { OneOfAccessorToString(&In->ComponentTypes[Index], EntityManager, Strings.Emplace_GetRef()); } OutString += FString::Printf(TEXT("\n\tRead One Or More Of: [ %s ]"), *FString::Join(Strings, TEXT(","))); } #endif // UE_MOVIESCENE_ENTITY_DEBUG } // namespace MovieScene } // namespace UE