// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreTypes.h" #include "Evaluation/IMovieScenePlaybackCapability.h" #include "EntitySystem/RelativePtr.h" #include "MovieSceneFwd.h" #include "Templates/AlignmentTemplates.h" #include "Templates/PointerIsConvertibleFromTo.h" #include class IMovieScenePlayer; class IMovieScenePlaybackClient; namespace UE::MovieScene { enum class EPlaybackCapabilityStorageMode : uint8 { Inline = 0, RawPointer, SharedPointer }; /** * Return value for accessing raw capabalities. */ struct FPlaybackCapabilityPtr { void* Ptr = 0; EPlaybackCapabilityStorageMode StorageMode; template T* ResolveOptional() const { if (Ptr) { switch (StorageMode) { case EPlaybackCapabilityStorageMode::Inline: return static_cast(Ptr); case EPlaybackCapabilityStorageMode::RawPointer: return *static_cast(Ptr); case EPlaybackCapabilityStorageMode::SharedPointer: return static_cast*>(Ptr)->Get(); default: checkf(false, TEXT("Unexpected playback capability storage mode")); return nullptr; } } return nullptr; } template T& ResolveChecked() const { check(Ptr); switch (StorageMode) { case EPlaybackCapabilityStorageMode::Inline: return *static_cast(Ptr); case EPlaybackCapabilityStorageMode::RawPointer: return **static_cast(Ptr); case EPlaybackCapabilityStorageMode::SharedPointer: return *static_cast*>(Ptr)->Get(); default: checkf(false, TEXT("Unexpected playback capability storage mode")); // Nothing reasonable to do, let's just proceed as if it was inline return *static_cast(Ptr); } } }; // Utility callbacks for the concrete capability objects we have in a container. using FPlaybackCapabilityInterfaceCastHelper = IPlaybackCapability*(*)(void* Ptr); using FPlaybackCapabilityDestructionHelper = void(*)(void* Ptr); // Callback for casting a stored capabilty object to an IPlaybackCapability pointer if possible. template struct TPlaybackCapabilityInterfaceCast { static IPlaybackCapability* InterfaceCast(void* Ptr) { if constexpr (TPointerIsConvertibleFromTo::Value) { return static_cast((StorageType*)Ptr); } else { return nullptr; } } }; template struct TPlaybackCapabilityInterfaceCast { static IPlaybackCapability* InterfaceCast(void* Ptr) { if constexpr (TPointerIsConvertibleFromTo::Value) { PointedType* TypedPtr = *(PointedType**)Ptr; return static_cast(TypedPtr); } else { return nullptr; } }; }; template struct TPlaybackCapabilityInterfaceCast> { static IPlaybackCapability* InterfaceCast(void* Ptr) { if constexpr (TPointerIsConvertibleFromTo::Value) { TSharedPtr& TypedPtr = *(TSharedPtr*)Ptr; return static_cast(TypedPtr.Get()); } else { return nullptr; } }; }; // Callback for destroying the stored capability object, whether that's the capability itself // (when stored inline), or a shared pointer, or whatever else. template struct TPlaybackCapabilityDestructor { static void Destroy(void* Ptr) { StorageType* StoragePtr = (StorageType*)Ptr; StoragePtr->~StorageType(); } }; // Helper callbacks for managing the lifetime of a capability. struct FPlaybackCapabilityHelpers { FPlaybackCapabilityInterfaceCastHelper InterfaceCast = nullptr; FPlaybackCapabilityDestructionHelper Destructor = nullptr; }; template struct TPlaybackCapabilityHelpers { static FPlaybackCapabilityHelpers GetHelpers() { FPlaybackCapabilityHelpers Helpers; Helpers.InterfaceCast = &TPlaybackCapabilityInterfaceCast::InterfaceCast; Helpers.Destructor = &TPlaybackCapabilityDestructor::Destroy; return Helpers; } }; // Storage traits for playback capabilities, only for internal use. template struct TPlaybackCapabilityStorageTraits; template struct TPlaybackCapabilityStorageTraits::Value>::Type> { static EPlaybackCapabilityStorageMode GetStorageMode() { return EPlaybackCapabilityStorageMode::Inline; } static uint8 ComputePointerOffset(StorageType* StoragePtr) { const uint64 PointerOffset = reinterpret_cast(static_cast(StoragePtr)) - reinterpret_cast(StoragePtr); check(PointerOffset <= TNumericLimits::Max()); return static_cast(PointerOffset); } }; template struct TPlaybackCapabilityStorageTraits { static EPlaybackCapabilityStorageMode GetStorageMode() { return EPlaybackCapabilityStorageMode::RawPointer; } static uint8 ComputePointerOffset(CapabilityType** StoragePtr) { return 0; } }; template struct TPlaybackCapabilityStorageTraits, CapabilityType, void> { static EPlaybackCapabilityStorageMode GetStorageMode() { return EPlaybackCapabilityStorageMode::SharedPointer; } static uint8 ComputePointerOffset(TSharedPtr* StoragePtr) { return 0; } }; /** * Header that describes an entry in the capabilities buffer. */ struct FPlaybackCapabilityHeader { #if UE_MOVIESCENE_ENTITY_DEBUG // Extra debugging field. IPlaybackCapabilityDebuggingTypedPtr* DebugPtr = nullptr; #endif // 2 Bytes TRelativePtr Capability; // 2 Bytes uint16 Sizeof = 0; // 1 Byte uint8 Alignment = 0; // 1 Byte uint8 PointerOffset = 0; // 1 Byte EPlaybackCapabilityStorageMode StorageMode = EPlaybackCapabilityStorageMode::Inline; /** Resolve the given raw pointer into a capability pointer */ FPlaybackCapabilityPtr Resolve(void* InMemory) const { void* DerivedPointer = Capability.Resolve(InMemory); return FPlaybackCapabilityPtr{ (void*)((uint8*)DerivedPointer + PointerOffset), StorageMode }; } }; /** * Basic, mostly untyped, implementation of the capabilities buffer. */ struct FPlaybackCapabilitiesImpl { protected: FPlaybackCapabilitiesImpl() = default; FPlaybackCapabilitiesImpl(const FPlaybackCapabilitiesImpl&) = delete; FPlaybackCapabilitiesImpl& operator=(const FPlaybackCapabilitiesImpl&) = delete; FPlaybackCapabilitiesImpl(FPlaybackCapabilitiesImpl&&) = delete; FPlaybackCapabilitiesImpl& operator=(FPlaybackCapabilitiesImpl&&) = delete; bool HasCapability(uint32 CapabilityBit) const { return (AllCapabilities & CapabilityBit) != 0; } FPlaybackCapabilityPtr FindCapability(uint32 CapabilityBit) const { if (HasCapability(CapabilityBit)) { const int32 Index = GetCapabilityIndex(CapabilityBit); check(Index >= 0 && Index < 255); return GetHeader(static_cast(Index)).Resolve(Memory); } return FPlaybackCapabilityPtr(); } FPlaybackCapabilityPtr GetCapabilityChecked(uint32 CapabilityBit) const { const int32 Index = GetCapabilityIndex(CapabilityBit); check(Index >= 0 && Index < 255); return GetHeader(static_cast(Index)).Resolve(Memory); } /** * Creates and stores a new capability object at the given bit. * * - StorageType is what is actually constructed and stored in our memory buffer. It can be the same as * CapabilityType, a subclass of it, a pointer to it, or a shared pointer to it. * * - CapabilityType is the base capability type, used onlyfor inline storage when the StorageType is a subclass * and we need to compute the pointer offset. */ template FPlaybackCapabilityPtr AddCapability(uint32 CapabilityBit, ArgTypes&&... InArgs) { checkf((AllCapabilities & CapabilityBit) == 0, TEXT("Capability already exists!")); // Add the enum entry AllCapabilities |= CapabilityBit; // Find the index of the new capability by counting how many bits are set before it // For example, given CapabilityBit=0b00010000 and AllCapabilities=0b00011011: // (CapabilityBit-1) = 0b00001111 // AllCapabilities & (CapabilityBit-1) = 0b00001011 // CountBits(0b00001011) = 3 const int32 NewCapabilityIndex = static_cast(FMath::CountBits(AllCapabilities & (CapabilityBit-1u))); const FPlaybackCapabilityHeader* ExistingHeaders = reinterpret_cast(Memory); uint64 RequiredAlignment = FMath::Max(alignof(StorageType), alignof(FPlaybackCapabilityHeader)); const int32 ExistingNum = static_cast(Num); // Compute our required alignment for the allocation { for (int32 Index = 0; Index < ExistingNum; ++Index) { RequiredAlignment = FMath::Max(RequiredAlignment, (uint64)ExistingHeaders[Index].Alignment); } } // We'll keep track of where all our new capabilities will go, relative to the buffer start TArray> NewCapabilityOffsets; NewCapabilityOffsets.SetNum(ExistingNum + 1); // Compute the required size of our allocation uint64 RequiredSizeof = 0u; { // Allocate space for headers RequiredSizeof += (ExistingNum + 1) * sizeof(FPlaybackCapabilityHeader); int32 Index = 0; // Count up the sizes and alignments of pre-existing capabilities that exist before this new entry for (; Index < NewCapabilityIndex; ++Index) { RequiredSizeof = Align(RequiredSizeof, (uint64)ExistingHeaders[Index].Alignment); NewCapabilityOffsets[Index] = static_cast(RequiredSizeof); RequiredSizeof += ExistingHeaders[Index].Sizeof; } // Count up the size and alignment for the new capability RequiredSizeof = Align(RequiredSizeof, alignof(StorageType)); NewCapabilityOffsets[Index] = static_cast(RequiredSizeof); RequiredSizeof += sizeof(StorageType); ++Index; // Now count up the sizes and alignments of pre-existing capabilities that exist after this new entry for (; Index < ExistingNum+1; ++Index) { RequiredSizeof = Align(RequiredSizeof, (uint64)ExistingHeaders[Index-1].Alignment); NewCapabilityOffsets[Index] = static_cast(RequiredSizeof); RequiredSizeof += ExistingHeaders[Index-1].Sizeof; } } check( RequiredAlignment <= 0XFF ); /// ---------------------------------------- uint8* OldMemory = Memory; // Make a new allocation if necessary const bool bNeedsReallocation = RequiredAlignment > Alignment || RequiredSizeof > Capacity; if (bNeedsReallocation) { // Use the greater of the required size or double the current size to allow some additional capcity Capacity = static_cast(FMath::Max(RequiredSizeof, uint64(Capacity)*2)); Memory = reinterpret_cast(FMemory::Malloc(Capacity, RequiredAlignment)); } // We now have an extra entry ++Num; // We now need to re-arrange memory carefully, going back to front in order to avoid overlaps. // For instance we can only move headers at the end of the whole operation, otherwise they could overwrite // the memory of the first couple capabilities before we've had a chance to move them. So we do: // // 1) Relocate capabilities from the last one up to where the new one will go // 2) Allocate the new capability // 3) Relocate capabilities from the one just before the new one, down to the first one // 4) Relocate headers from the last one up to where the new one will go // 5) Allocate the new header // 6) Relocate headers from the one just before the new header, down to the first one // // In order to set the new capability pointers (obtained in steps 1-3) on the new headers (obtained in // steps 4-6), we save these pointers in a temporary array. const FPlaybackCapabilityHeader* OldHeaders = ExistingHeaders; // Better named variable for the rest of the steps... auto RelocateCapability = [this, OldMemory, OldHeaders, &NewCapabilityOffsets](int32 OldIndex, int32 NewIndex) { void* NewCapabilityPtr = this->Memory + NewCapabilityOffsets[NewIndex]; void* OldCapabilityPtr = OldHeaders[OldIndex].Capability.Resolve(OldMemory); FMemory::Memmove(NewCapabilityPtr, OldCapabilityPtr, OldHeaders[OldIndex].Sizeof); }; // Step 1 int32 Index = static_cast(Num) - 1; for (; Index > NewCapabilityIndex; --Index) { RelocateCapability(Index - 1, Index); } // Step 2 { void* NewCapabilityPtr = this->Memory + NewCapabilityOffsets[Index]; StorageType* NewCapabilityTypedPtr = reinterpret_cast(NewCapabilityPtr); new (NewCapabilityTypedPtr) StorageType (Forward(InArgs)...); } --Index; // Step 3 for (; Index >= 0; --Index) { RelocateCapability(Index, Index); } FPlaybackCapabilityHeader* NewHeaders = reinterpret_cast(Memory); auto RelocateHeader = [this, OldHeaders, NewHeaders, &NewCapabilityOffsets](int32 OldIndex, int32 NewIndex) { const FPlaybackCapabilityHeader& OldHeader(OldHeaders[OldIndex]); FPlaybackCapabilityHeader* NewHeaderPtr = &NewHeaders[NewIndex]; new (NewHeaderPtr) FPlaybackCapabilityHeader(OldHeader); // Re-create the relative capability pointer, since the offset has changed (it's one capability // further down), and the base pointer may have changed too (if we re-allocated the buffer) void* NewCapabilityPtr = this->Memory + NewCapabilityOffsets[NewIndex]; NewHeaderPtr->Capability = TRelativePtr(this->Memory, NewCapabilityPtr); // The new header will share its debugging pointer with the old header at this point. But the old // header will be gone when we free the entire old memory buffer after step 6. }; // Step 4 Index = static_cast(Num) - 1; for (; Index > NewCapabilityIndex; --Index) { RelocateHeader(Index - 1, Index); } // Step 5 FPlaybackCapabilityHeader* NewHeaderPtr = &NewHeaders[Index]; { using FStorageTraits = TPlaybackCapabilityStorageTraits; static_assert(alignof(StorageType) < 0x7F, "Required alignment of capability must fit in 7 bytes"); void* NewCapabilityPtr = Memory + NewCapabilityOffsets[Index]; StorageType* NewCapabilityTypedPtr = reinterpret_cast(NewCapabilityPtr); static_assert(sizeof(StorageType) <= MAX_uint16, "Can't store a playback capability with a size above 65535 bits. Store it as a raw or shared pointer."); static_assert(alignof(StorageType) <= MAX_uint8, "Can't store a playback capability with an alignment above 255 bits. Store it as a raw or shared pointer."); new (NewHeaderPtr) FPlaybackCapabilityHeader(); // Reset to default constructor NewHeaderPtr->Capability.Reset(Memory, NewCapabilityPtr); NewHeaderPtr->Sizeof = static_cast(sizeof(StorageType)); NewHeaderPtr->Alignment = static_cast(alignof(StorageType)); NewHeaderPtr->StorageMode = FStorageTraits::GetStorageMode(); // The pointer offset is whatever we need to add to the capability pointer in order to get // a pointer to capability type itself (in case the storage type is a sub-class of it) // We only need it if the capability object is stored inline inside our memory buffer. NewHeaderPtr->PointerOffset = FStorageTraits::ComputePointerOffset(NewCapabilityTypedPtr); #if UE_MOVIESCENE_ENTITY_DEBUG // Set the debug pointer. NewHeaderPtr->DebugPtr = new TPlaybackCapabilityDebuggingTypedPtr(NewCapabilityTypedPtr); #endif } --Index; // Step 6 for (; Index >= 0; --Index) { RelocateHeader(Index, Index); } /// ---------------------------------------- // Tidy up the old allocation. We do not call destructors here because we relocated everything. if (bNeedsReallocation && OldMemory) { FMemory::Free(OldMemory); } // Insert the helpers for the new capability. Helpers.Insert(TPlaybackCapabilityHelpers::GetHelpers(), NewCapabilityIndex); // Return the new capability pointer. We call the header's Resolve method here because returning // the pointer of the stored capability would return the derived type pointer. // We want the base (capability) pointer. return NewHeaderPtr->Resolve(Memory); } template bool OverwriteCapability(uint32 CapabilityBit, ArgTypes&&... InArgs) { if (!ensureMsgf(HasCapability(CapabilityBit), TEXT("The given capability does not exist in this container"))) { return false; } // Get the header for this capability const int32 Index = GetCapabilityIndex(CapabilityBit); FPlaybackCapabilityHeader& Header = GetHeader(static_cast(Index)); // Check that we are overwriting the same storage mode using FStorageTraits = TPlaybackCapabilityStorageTraits; EPlaybackCapabilityStorageMode GivenStorageMode = FStorageTraits::GetStorageMode(); if (!ensureMsgf(GivenStorageMode == Header.StorageMode, TEXT("The given capability storage mode does not match the existing one"))) { return false; } // Check that the types and alignments match size_t GivenSizeof = sizeof(StorageType); uint16 GivenAlignment = alignof(StorageType); if (!ensureMsgf( (GivenSizeof == Header.Sizeof && GivenAlignment == Header.Alignment), TEXT("The given capability size and alignment do not match the existing one"))) { return false; } // Don't use the header's Resolve method, we don't want to offset the pointer, we want // the actual pointer to the actual stored capability. void* CapabilityPtr = Header.Capability.Resolve(Memory); // Call the destructor on the previous capability, which is important if it was stored inline or as // a shared pointer. Helpers[Index].Destructor(CapabilityPtr); // Allocate the new capability. StorageType* TypedCapabilityPtr = reinterpret_cast(CapabilityPtr); new (TypedCapabilityPtr) StorageType(Forward(InArgs)...); // If we have inline storage, we could potentially have changed from a stored base-class to // a stored child-class, or vice-versa, or from one child-class to another child-class. In all these // cases, the helper functions (destructor, interface cast, etc) have changed, so let's store the // new helper functions on the header. Helpers[Index] = TPlaybackCapabilityHelpers::GetHelpers(); #if UE_MOVIESCENE_ENTITY_DEBUG // Update the debug pointer. delete Header.DebugPtr; Header.DebugPtr = new TPlaybackCapabilityDebuggingTypedPtr(TypedCapabilityPtr); #endif return true; } const FPlaybackCapabilityHeader& GetHeader(uint8 Index) const { check(Index < Num); return reinterpret_cast(Memory)[Index]; } FPlaybackCapabilityHeader& GetHeader(uint8 Index) { check(Index < Num); return reinterpret_cast(Memory)[Index]; } TArrayView GetHeaders() const { return MakeArrayView(reinterpret_cast(Memory), Num); } int32 GetCapabilityIndex(uint32 CapabilityBit) const { if ( (AllCapabilities & CapabilityBit) == 0) { return INDEX_NONE; } return FMath::CountBits(AllCapabilities & (CapabilityBit-1u)); } // Schema: // [header_1|...|header_n|entry_0|...|entry_n] uint8* Memory = nullptr; uint16 Alignment = 0u; uint16 Capacity = 0u; uint8 Num = 0u; uint32 AllCapabilities = 0u; // Function pointers for invalidating, destroying, etc. capabilities. TArray Helpers; }; namespace Internal { /** Gets the type of the playback capability ID in a backwards compatible way. */ template struct TPlaybackCapabilityIDType {}; /** Getter for the new GetPlaybackCapabilityID method. */ template struct TPlaybackCapabilityIDType< T, std::enable_if_t< std::is_assignable_v >> { using Type = std::decay_t; static Type Get() { return T::GetPlaybackCapabilityID(); } }; /** Getter for the old ID static field. */ template struct TPlaybackCapabilityIDType< T, std::enable_if_t< std::is_assignable_v >> { using Type = std::decay_t; static Type Get() { return T::ID; } }; } // namespace Internal /** * Actual playback capabilities container. */ struct FPlaybackCapabilities : FPlaybackCapabilitiesImpl { FPlaybackCapabilities() = default; FPlaybackCapabilities(const FPlaybackCapabilities&) = delete; FPlaybackCapabilities& operator=(const FPlaybackCapabilities&) = delete; MOVIESCENE_API FPlaybackCapabilities(FPlaybackCapabilities&&); MOVIESCENE_API FPlaybackCapabilities& operator=(FPlaybackCapabilities&&); MOVIESCENE_API ~FPlaybackCapabilities(); /** Checks whether this container has the given capability */ template bool HasCapability() const { uint32 CapabilityBit = 1 << Internal::TPlaybackCapabilityIDType::Get().Index; return FPlaybackCapabilitiesImpl::HasCapability(CapabilityBit); } /** Finds the specified capability within the container, if present */ template T* FindCapability() const { // T must be the base capability type, and not a sub-class, because we are returning a pointer to it, // and we can't check what sort of sub-class we have or not. using CapabilityIDType = typename Internal::TPlaybackCapabilityIDType::Type; static_assert(std::is_same::value, "You must pass the actual playback capability type as a template parameter, not a sub-class."); const TPlaybackCapabilityID CapabilityID(Internal::TPlaybackCapabilityIDType::Get()); uint32 CapabilityBit = 1 << CapabilityID.Index; FPlaybackCapabilityPtr Ptr = FPlaybackCapabilitiesImpl::FindCapability(CapabilityBit); return Ptr.ResolveOptional(); } /** Returns the specified capability within the container, asserts if not found */ template T& GetCapabilityChecked() const { // T must be the base capability type, and not a sub-class, because we are returning a reference to it, // and we can't check what sort of sub-class we have or not. using CapabilityIDType = typename Internal::TPlaybackCapabilityIDType::Type; static_assert(std::is_same::value, "You must pass the actual playback capability type as a template parameter, not a sub-class."); const TPlaybackCapabilityID CapabilityID(Internal::TPlaybackCapabilityIDType::Get()); uint32 CapabilityBit = 1 << CapabilityID.Index; FPlaybackCapabilityPtr Ptr = FPlaybackCapabilitiesImpl::GetCapabilityChecked(CapabilityBit); return Ptr.ResolveChecked(); } /** * Adds the specified capability to the container, using the supplied arguments to construct it. * The capability object will be stored inline and owned by this container. It will be destroyed when the * container itself is destroyed. * If the template parameter is a sub-class of the playback capability class, that sub-class will be * created and stored inline. */ template T& AddCapability(ArgTypes&&... InArgs) { using CapabilityIDType = typename Internal::TPlaybackCapabilityIDType::Type; using CapabilityType = typename CapabilityIDType::CapabilityType; CapabilityType& NewCapability = DoAddCapability(Internal::TPlaybackCapabilityIDType::Get(), Forward(InArgs)...); return static_cast(NewCapability); } /** * As per AsCapability, but with an explicit capability ID. This is useful when the first template * parameter doesn't have a single static member called ID providing the capability ID. */ template T& AddCapability(const TPlaybackCapabilityID CapabilityID, ArgTypes&&... InArgs) { U& NewCapability = DoAddCapability(CapabilityID, Forward(InArgs)...); return static_cast(NewCapability); } /** * Adds the specified capability to the container, as a simple raw pointer * Ownership of the capability object being pointed to is the caller's responsability. This container will only * store a raw pointer. */ template T& AddCapabilityRaw(T* InPointer) { using CapabilityIDType = typename Internal::TPlaybackCapabilityIDType::Type; using CapabilityType = typename CapabilityIDType::CapabilityType; CapabilityType& NewCapability = DoAddCapability(Internal::TPlaybackCapabilityIDType::Get(), static_cast(InPointer)); return static_cast(NewCapability); } /** * Adds the specified capability to the container, as a shared pointer * Ownership of the capability object being pointed to respects classic shared pointer semantics. This container * only maintains one such shared pointer until the container is destroyed. */ template T& AddCapabilityShared(TSharedRef InSharedRef) { using CapabilityIDType = typename Internal::TPlaybackCapabilityIDType::Type; using CapabilityType = typename CapabilityIDType::CapabilityType; CapabilityType& NewCapability = DoAddCapability>(Internal::TPlaybackCapabilityIDType::Get(), StaticCastSharedRef(InSharedRef)); return static_cast(NewCapability); } /** * Overwrites an existing capability, stored inline and owned by this container. * The previous storage mode of the capability must also be inline. * If the template parameter is a sub-class of the playback capability, the previously stored playback capability * must not only have been stored inline, but must have been of the same sub-class (or a sub-class with the exact * same size and alignment). */ template T& OverwriteCapability(ArgTypes&&... InArgs) { using CapabilityIDType = typename Internal::TPlaybackCapabilityIDType::Type; using CapabilityType = typename CapabilityIDType::CapabilityType; CapabilityType& NewCapability = DoOverwriteCapability(Internal::TPlaybackCapabilityIDType::Get(), Forward(InArgs)...); return static_cast(NewCapability); } /** * Overwrites an existing capability, stored as a raw pointer on the container. * The previous storage mode of the capability must also be a raw pointer. */ template T& OverwriteCapabilityRaw(T* InPointer) { using CapabilityIDType = typename Internal::TPlaybackCapabilityIDType::Type; using CapabilityType = typename CapabilityIDType::CapabilityType; CapabilityType& NewCapability = DoOverwriteCapability(Internal::TPlaybackCapabilityIDType::Get(), static_cast(InPointer)); return static_cast(NewCapability); } /** * Overwrites an existing capability, stored as a shared pointer on the container. * The previous storage mode of the capability must also be a shared pointer. */ template T& OverwriteCapabilityShared(TSharedRef InSharedRef) { using CapabilityIDType = typename Internal::TPlaybackCapabilityIDType::Type; using CapabilityType = typename CapabilityIDType::CapabilityType; CapabilityType& NewCapability = DoOverwriteCapability>(Internal::TPlaybackCapabilityIDType::Get(), StaticCastSharedRef(InSharedRef)); return static_cast(NewCapability); } public: /** * Calls OnSubInstanceCreated on any capability that implements the IPlaybackCapability interface. */ void OnSubInstanceCreated(TSharedRef Owner, const FInstanceHandle InstanceHandle); /** * Calls InvalidateCacheData on any capability that implements the IPlaybackCapability interface. */ void InvalidateCachedData(UMovieSceneEntitySystemLinker* Linker); private: template void ForEachCapabilityInterface(Callback&& InCallback, ArgTypes&&... InArgs) { TArrayView Headers = GetHeaders(); for (int32 Index = 0; Index < Headers.Num(); ++Index) { const FPlaybackCapabilityHeader& Header = Headers[Index]; const FPlaybackCapabilityHelpers& ThisHelpers = Helpers[Index]; check(ThisHelpers.InterfaceCast != nullptr); { void* Ptr = Header.Capability.Resolve(Memory); if (IPlaybackCapability* Interface = (*ThisHelpers.InterfaceCast)(Ptr)) { InCallback(*Interface, Forward(InArgs)...); } } } } template T& DoAddCapability(TPlaybackCapabilityID CapabilityID, ArgTypes&&... InArgs) { uint32 CapabilityBit = 1 << CapabilityID.Index; FPlaybackCapabilityPtr Ptr = FPlaybackCapabilitiesImpl::AddCapability(CapabilityBit, Forward(InArgs)...); return Ptr.ResolveChecked(); } template T& DoOverwriteCapability(TPlaybackCapabilityID CapabilityID, ArgTypes&&... InArgs) { uint32 CapabilityBit = 1 << CapabilityID.Index; FPlaybackCapabilitiesImpl::OverwriteCapability(CapabilityBit, Forward(InArgs)...); return GetCapabilityChecked(); } void Destroy(); }; } // namespace UE::MovieScene