// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "Engine/EngineTypes.h" #include "UObject/UnrealType.h" #include "UObject/ObjectKey.h" #include "Curves/KeyHandle.h" #include "Misc/FrameNumber.h" #include "UObject/WeakFieldPtr.h" class AActor; class UCameraComponent; class UMovieScene; class UMovieSceneSection; class UMovieSceneSubSection; class UMovieSceneSequence; class USceneComponent; class USoundBase; template class TSubclassOf; class UMovieSceneCustomBinding; struct FRichCurve; enum class EMovieSceneKeyInterpolation : uint8; struct FMovieSceneSequenceID; class UMovieSceneCondition; class UMovieSceneTrack; namespace UE::MovieScene { struct FSharedPlaybackState; } class MovieSceneHelpers { public: /* * Helper struct to cache the package dirty state and then to restore it * after this leaves scope. This is for a few minor areas where calling * functions on actors dirties them, but Sequencer doesn't actually want * the package to be dirty as it causes Sequencer to unnecessairly dirty * actors. */ struct FMovieSceneScopedPackageDirtyGuard { MOVIESCENE_API FMovieSceneScopedPackageDirtyGuard(class USceneComponent* InComponent); MOVIESCENE_API virtual ~FMovieSceneScopedPackageDirtyGuard(); private: class USceneComponent* Component; bool bPackageWasDirty; }; /** * @return Whether the section is keyable (active, on a track that is not muted, etc */ static MOVIESCENE_API bool IsSectionKeyable(const UMovieSceneSection*); /** * Finds a section that exists at a given time * * @param Time The time to find a section at * @param RowIndex Limit the search to a given row index * @return The found section or null */ static MOVIESCENE_API UMovieSceneSection* FindSectionAtTime( TArrayView Sections, FFrameNumber Time, int32 RowIndex = INDEX_NONE ); /** * Finds the nearest section to the given time * * @param Time The time to find a section at * @param RowIndex Limit the search to a given row index * @return The found section or null */ static MOVIESCENE_API UMovieSceneSection* FindNearestSectionAtTime( TArrayView Sections, FFrameNumber Time, int32 RowIndex = INDEX_NONE ); /** Find the next section that doesn't overlap - the section that has the next closest start time to the requested start time */ static MOVIESCENE_API UMovieSceneSection* FindNextSection(TArrayView Sections, FFrameNumber Time); /** Find the previous section that doesn't overlap - the section that has the previous closest start time to the requested start time */ static MOVIESCENE_API UMovieSceneSection* FindPreviousSection(TArrayView Sections, FFrameNumber Time); /* * Fix up consecutive sections so that there are no gaps * * @param Sections All the sections * @param Section The section that was modified * @param bDelete Was this a deletion? * @param bCleanUp Should we cleanup any invalid sections? * @return Whether the list of sections was modified as part of the clean-up */ static MOVIESCENE_API bool FixupConsecutiveSections(TArray& Sections, UMovieSceneSection& Section, bool bDelete, bool bCleanUp = false); /** * Fix up consecutive sections so that there are no gaps, but there can be overlaps, in which case the sections * blend together. * * @param Sections All the sections * @param Section The section that was modified * @param bDelete Was this a deletion? * @param bCleanUp Should we cleanup any invalid sections? * @return Whether the list of sections was modified as part of the clean-up */ static MOVIESCENE_API bool FixupConsecutiveBlendingSections(TArray& Sections, UMovieSceneSection& Section, bool bDelete, bool bCleanUp = false); /* * Sort consecutive sections so that they are in order based on start time */ static MOVIESCENE_API void SortConsecutiveSections(TArray& Sections); /* * Gather up descendant movie scenes from the incoming sequence */ static MOVIESCENE_API void GetDescendantMovieScenes(UMovieSceneSequence* InSequence, TArray & InMovieScenes); /* * Gather up descendant movie scene sub-sections from the incoming movie scene */ static MOVIESCENE_API void GetDescendantSubSections(const UMovieScene* InMovieScene, TArray& InSubSections); /** * Get the scene component from the runtime object * * @param Object The object to get the scene component for * @return The found scene component */ static MOVIESCENE_API USceneComponent* SceneComponentFromRuntimeObject(UObject* Object); static MOVIESCENE_API UObject* ResolveSceneComponentBoundObject(UObject* Object); /** * Get the active camera component from the actor * * @param InActor The actor to look for the camera component on * @return The active camera component */ static MOVIESCENE_API UCameraComponent* CameraComponentFromActor(const AActor* InActor); /** * Find and return camera component from the runtime object * * @param Object The object to get the camera component for * @return The found camera component */ static MOVIESCENE_API UCameraComponent* CameraComponentFromRuntimeObject(UObject* RuntimeObject); /** * Set the runtime object movable * * @param Object The object to set the mobility for * @param Mobility The mobility of the runtime object */ static MOVIESCENE_API void SetRuntimeObjectMobility(UObject* Object, EComponentMobility::Type ComponentMobility = EComponentMobility::Movable); /* * Get the duration for the given sound * @param Sound The sound to get the duration for * @return The duration in seconds */ static MOVIESCENE_API float GetSoundDuration(USoundBase* Sound); /** * Sort predicate that sorts lower bounds of a range */ static bool SortLowerBounds(TRangeBound A, TRangeBound B) { return TRangeBound::MinLower(A, B) == A && A != B; } /** * Sort predicate that sorts upper bounds of a range */ static bool SortUpperBounds(TRangeBound A, TRangeBound B) { return TRangeBound::MinUpper(A, B) == A && A != B; } /** * Sort predicate that sorts overlapping sections by row primarily, then by overlap priority */ static MOVIESCENE_API bool SortOverlappingSections(const UMovieSceneSection* A, const UMovieSceneSection* B); /* * Get weight needed to modify the global difference in order to correctly key this section due to it possibly being blended by other sections. * @param Section The Section who's weight we are calculating. * @param Time we are at. * @return Returns the weight that needs to be applied to the global difference to correctly key this section. */ static MOVIESCENE_API float CalculateWeightForBlending(UMovieSceneSection* SectionToKey, FFrameNumber Time); /* * Return a name unique to the binding names in the given movie scene * @param InMovieScene The movie scene to look for existing possessables. * @param InName The requested name to make unique. * @return The unique name */ static MOVIESCENE_API FString MakeUniqueBindingName(UMovieScene* InMovieScene, const FString& InName); /* * Return a name unique to the spawnable names in the given movie scene * @param InMovieScene The movie scene to look for existing spawnables. * @param InName The requested name to make unique. * @return The unique name */ static MOVIESCENE_API FString MakeUniqueSpawnableName(UMovieScene* InMovieScene, const FString& InName); /** * Return a copy of the source object, suitable for use as a spawnable template. * @param InSourceObject The source object to convert into a spawnable template * @param InMovieScene The movie scene the spawnable template will be associated with * @param InName The name to use for the spawnable template * @return The spawnable template */ static MOVIESCENE_API UObject* MakeSpawnableTemplateFromInstance(UObject& InSourceObject, UMovieScene* InMovieScene, FName InName); /* * Returns whether the given ObjectId is valid and is currently bound to at least 1 spawnable give the current context. * More specifically, if a FMovieSceneSpawnable exists with this ObjectId, true will be returned. * If a Level Sequence binding reference exists with a Custom Binding implementing MovieSceneSpawnableBindingBase, true will be returned. * If a Level Sequence binding reference exists with a Custom Binding implementing MovieSceneReplaceableBindingBase and the Context is an editor world, then true will be returned. * Otherwise, false will be returned. */ static MOVIESCENE_API bool IsBoundToAnySpawnable(UMovieSceneSequence* Sequence, const FGuid& ObjectId, TSharedRef SharedPlaybackState); /* * Returns whether the given ObjectId is valid and is the given bindingindex is currently bound to a spawnable give the current context. * More specifically, if a FMovieSceneSpawnable exists with this ObjectId, true will be returned. * If a Level Sequence binding reference for this guid with the given BindingIndex exists with a Custom Binding implementing MovieSceneSpawnableBindingBase, true will be returned. * If a Level Sequence binding reference for this guid with the given BindingIndex exists with a Custom Binding implementing MovieSceneReplaceableBindingBase and the Context is an editor world, then true will be returned. * Otherwise, false will be returned. */ static MOVIESCENE_API bool IsBoundToSpawnable(UMovieSceneSequence* Sequence, const FGuid& ObjectId, TSharedRef SharedPlaybackState, int32 BindingIndex = 0); /* * Attempts to create a new custom spawnable binding for the passed in UObject*. * Where possible, it is preferred to call FSequencerUtilities::CreateOrReplaceBinding as it handles more cases. This should only be called in cases where there is no editor or sequencer context. * FactoryCreatedActor may be passed in as an alternative option for creating the binding in the case an actor factory was able to create an actor from this object. */ static MOVIESCENE_API FGuid TryCreateCustomSpawnableBinding(UMovieSceneSequence* Sequence, UObject* CustomBindingObject); /* * Returns the single bound object currently bound to the given objectid and binding index (optional). */ static MOVIESCENE_API UObject* GetSingleBoundObject(UMovieSceneSequence* Sequence, const FGuid& ObjectId, TSharedRef SharedPlaybackState, int32 BindingIndex = 0); /* * If the binding for the given ObjectId supports object templates, returns the template, otherwise returns nullptr */ static MOVIESCENE_API UObject* GetObjectTemplate(UMovieSceneSequence* Sequence, const FGuid& ObjectId, TSharedRef SharedPlaybackState, int32 BindingIndex = 0); /* * If the binding for the given ObjectId supports object templates, sets the template and returns true, otherwise returns false */ static MOVIESCENE_API bool SetObjectTemplate(UMovieSceneSequence* Sequence, const FGuid& ObjectId, UObject* InSourceObject, TSharedRef SharedPlaybackState, int32 BindingIndex = 0); /* * Returns whether the binding for the given ObjectId supports object templates */ static MOVIESCENE_API bool SupportsObjectTemplate(UMovieSceneSequence* Sequence, const FGuid& ObjectId, TSharedRef SharedPlaybackState, int32 BindingIndex = 0); /* * If the binding for the given ObjectId supports object templates, copies the object template into the binding and returns true, otherwise returns false */ static MOVIESCENE_API bool CopyObjectTemplate(UMovieSceneSequence* Sequence, const FGuid& ObjectId, UObject* InSourceObject, TSharedRef SharedPlaybackState, int32 BindingIndex = 0); #if WITH_EDITORONLY_DATA /* * Returns the bound object class for the binding for the given ObjectId. */ static MOVIESCENE_API const UClass* GetBoundObjectClass(UMovieSceneSequence* Sequence, const FGuid& ObjectId, int32 BindingIndex = 0); #endif /* Returns a sorted list of all custom binding type classes currently known. Slow, may desire to cache result*/ static void MOVIESCENE_API GetPrioritySortedCustomBindingTypes(TArray>& OutCustomBindingTypes); /* For cases where the user does not have a IMovieScenePlayer with a shared playback state, creates a transient one. Use sparingly. */ static TSharedRef MOVIESCENE_API CreateTransientSharedPlaybackState(UObject* WorldContext, UMovieSceneSequence* Sequence); /* Finds the resolution context to use to resolve the given guid. */ static MOVIESCENE_API UObject* GetResolutionContext(UMovieSceneSequence* Sequence, const FGuid& ObjectId, const FMovieSceneSequenceID& SequenceID, TSharedRef SharedPlaybackState); /* Given a movie scene track and an optional section inside it, returns an optional single condition that needs to be evaluated. * If multiple conditions exist in the given scope (for example a track condition, a track row condition for the row the section is on, and a section), * a UMovieSceneGroupCondition will be generated, and the caller is responsible for holding a reference to this new UObject. * If bFromCompilation is true, then any generated conditions will be stored on the movie scene. */ static MOVIESCENE_API const UMovieSceneCondition* GetSequenceCondition(const UMovieSceneTrack* Track, const UMovieSceneSection* Section, bool bFromCompilation=false); /* Helper function for evaluating a condition in a movie scene, taking advantage of any cacheing that may apply. */ static MOVIESCENE_API bool EvaluateSequenceCondition(const FGuid& BindingID, const FMovieSceneSequenceID& SequenceID, const UMovieSceneCondition* Condition, UObject* ConditionOwnerObject, TSharedRef SharedPlaybackState); }; /** * Manages bindings to keyed properties for a track instance. * Calls UFunctions to set the value on runtime objects */ class FTrackInstancePropertyBindings { public: MOVIESCENE_API FTrackInstancePropertyBindings(FName InPropertyName, const FString& InPropertyPath); /** * Calls the setter function for a specific runtime object or if the setter function does not exist, the property is set directly * * @param InRuntimeObject The runtime object whose function to call * @param PropertyValue The new value to assign to the property */ template void CallFunction(UObject& InRuntimeObject, typename TCallTraits::ParamType PropertyValue) { FResolvedPropertyAndFunction PropAndFunction = FindOrAdd(InRuntimeObject); FProperty* Property = PropAndFunction.ResolvedProperty.GetValidProperty(); if (Property && Property->HasSetter()) { Property->CallSetter(&InRuntimeObject, &PropertyValue); } else if (UFunction* SetterFunction = PropAndFunction.SetterFunction.Get()) { InvokeSetterFunction(&InRuntimeObject, SetterFunction, PropertyValue); } else if (ValueType* Val = PropAndFunction.GetPropertyAddress()) { *Val = MoveTempIfPossible(PropertyValue); } if (UFunction* NotifyFunction = PropAndFunction.NotifyFunction.Get()) { InRuntimeObject.ProcessEvent(NotifyFunction, nullptr); } } /** * Calls the setter function for a specific runtime object or if the setter function does not exist, the property is set directly * * @param InRuntimeObject The runtime object whose function to call * @param PropertyValue The new value to assign to the property */ MOVIESCENE_API void CallFunctionForEnum( UObject& InRuntimeObject, int64 PropertyValue ); /** * Rebuilds the property and function mappings for a single runtime object, and adds them to the cache * * @param InRuntimeObject The object to cache mappings for */ MOVIESCENE_API void CacheBinding(const UObject& InRuntimeObject); /** * Gets the FProperty that is bound to the track instance. * * @param Object The Object that owns the property * @return The property on the object if it exists */ MOVIESCENE_API FProperty* GetProperty(const UObject& Object); /** * Returns whether this binding is valid for the given object. * * @param Object The Object that owns the property * @return Whether the property binding is valid */ MOVIESCENE_API bool HasValidBinding(const UObject& Object); /** * Gets the structure type of the bound property if it is a property of that type. * * @param Object The Object that owns the property * @return The structure type */ MOVIESCENE_API const UStruct* GetPropertyStruct(const UObject& Object); /** * Gets the current value of a property on an object * * @param Object The object to get the property from * @return ValueType The current value */ template ValueType GetCurrentValue(const UObject& Object) { ValueType Value{}; FResolvedPropertyAndFunction PropAndFunction = FindOrAdd(Object); TryGetPropertyValue(PropAndFunction.ResolvedProperty, Value); return Value; } /** * Optionally gets the current value of a property on an object * * @param Object The object to get the property from * @return (Optional) The current value of the property on the object */ template TOptional GetOptionalValue(const UObject& Object) { ValueType Value{}; FResolvedPropertyAndFunction PropAndFunction = FindOrAdd(Object); if (TryGetPropertyValue(PropAndFunction.ResolvedProperty, Value)) { return Value; } return TOptional(); } /** * Static function for accessing a property value on an object without caching its address * * @param Object The object to get the property from * @param InPropertyPath The path to the property to retrieve * @return (Optional) The current value of the property on the object */ template static TOptional StaticValue(const UObject* Object, const FString& InPropertyPath) { checkf(Object, TEXT("No object specified")); FTrackInstancePropertyBindings Temp(NAME_None, InPropertyPath); FResolvedProperty ResolvedProperty = ResolveProperty(Temp, *Object); ValueType Value; if (TryGetPropertyValue(ResolvedProperty, Value)) { return Value; } return TOptional(); } /** * Gets the current value of a property on an object * * @param Object The object to get the property from * @return ValueType The current value */ MOVIESCENE_API int64 GetCurrentValueForEnum(const UObject& Object); /** * Sets the current value of a property on an object * * @param Object The object to set the property on * @param InValue The value to set */ template void SetCurrentValue(UObject& Object, typename TCallTraits::ParamType InValue) { FResolvedPropertyAndFunction PropAndFunction = FindOrAdd(Object); if (ValueType* Val = PropAndFunction.GetPropertyAddress()) { *Val = InValue; if (UFunction* NotifyFunction = PropAndFunction.NotifyFunction.Get()) { Object.ProcessEvent(NotifyFunction, nullptr); } } } /** @return the property path that this binding was initialized from */ const FString& GetPropertyPath() const { return PropertyPath; } /** @return the property name that this binding was initialized from */ const FName& GetPropertyName() const { return PropertyName; } static MOVIESCENE_API FProperty* FindProperty(const UObject* Object, const FString& InPropertyPath); private: /** * Wrapper for UObject::ProcessEvent that attempts to pass the new property value directly to the function as a parameter, * but handles cases where multiple parameters or a return value exists. The setter parameter must be the first in the list, * any other parameters will be default constructed. */ template static void InvokeSetterFunction(UObject* InRuntimeObject, UFunction* Setter, T&& InPropertyValue); struct FResolvedProperty { TWeakFieldPtr Property; void* ContainerAddress; int32 ArrayIndex = INDEX_NONE; FProperty* GetValidProperty() const { FProperty* PropertyPtr = Property.Get(); if (PropertyPtr && ContainerAddress && !PropertyPtr->HasAnyFlags(RF_BeginDestroyed | RF_FinishDestroyed)) { return PropertyPtr; } return nullptr; } template ValueType* GetPropertyAddress() const { if (FProperty* PropertyPtr = GetValidProperty()) { if (ArrayIndex == INDEX_NONE) { return PropertyPtr->ContainerPtrToValuePtr(ContainerAddress); } else if (FArrayProperty* ArrayProp = CastFieldChecked(PropertyPtr)) { FScriptArrayHelper ArrayHelper(ArrayProp, ArrayProp->ContainerPtrToValuePtr(ContainerAddress)); check(ArrayHelper.IsValidIndex(ArrayIndex)); return (ValueType*)ArrayHelper.GetRawPtr(ArrayIndex); } } return nullptr; } FResolvedProperty() : Property(nullptr) , ContainerAddress(nullptr) {} }; struct FResolvedPropertyAndFunction { FResolvedProperty ResolvedProperty; TWeakObjectPtr SetterFunction; TWeakObjectPtr NotifyFunction; template ValueType* GetPropertyAddress() const { return ResolvedProperty.GetPropertyAddress(); } FResolvedPropertyAndFunction() : ResolvedProperty() , SetterFunction( nullptr ) , NotifyFunction( nullptr ) {} }; template static bool TryGetPropertyValue(const FResolvedProperty& ResolvedProperty, ValueType& OutValue) { if (const ValueType* Value = ResolvedProperty.GetPropertyAddress()) { OutValue = *Value; return true; } return false; } static MOVIESCENE_API FResolvedProperty ResolvePropertyRecursive(FTrackInstancePropertyBindings& Bindings, void* BasePointer, UStruct* InStruct, TArray& InPropertyNames, uint32 Index); static MOVIESCENE_API FResolvedProperty ResolveProperty(FTrackInstancePropertyBindings& Bindings, const UObject& Object); static MOVIESCENE_API void FindProperty(FTrackInstancePropertyBindings& Bindings, void* BasePointer, UStruct* InStruct, const FString& InPropertyName, FResolvedProperty& OutResolvedProperty); static MOVIESCENE_API FResolvedProperty FindPropertyAndArrayIndex(FTrackInstancePropertyBindings& Bindings, void* BasePointer, UStruct* InStruct, const FString& PropertyName); /** Find or add the FResolvedPropertyAndFunction for the specified object */ MOVIESCENE_API FResolvedPropertyAndFunction FindOrAdd(const UObject& InObject); private: /** Mapping of objects to bound functions that will be called to update data on the track */ TMap< FObjectKey, FResolvedPropertyAndFunction > RuntimeObjectToFunctionMap; /** Path to the property we are bound to */ FString PropertyPath; /** Name of the function to call to set values */ FName FunctionName; /** Name of a function to call when a value has been set */ FName NotifyFunctionName; /** Actual name of the property we are bound to */ FName PropertyName; }; /** Explicit specializations for bools */ template<> MOVIESCENE_API void FTrackInstancePropertyBindings::CallFunction(UObject& InRuntimeObject, TCallTraits::ParamType PropertyValue); template<> MOVIESCENE_API bool FTrackInstancePropertyBindings::TryGetPropertyValue(const FResolvedProperty& ResolvedProperty, bool& OutValue); template<> MOVIESCENE_API void FTrackInstancePropertyBindings::SetCurrentValue(UObject& Object, TCallTraits::ParamType InValue); /** Explicit specializations for object pointers */ template<> MOVIESCENE_API void FTrackInstancePropertyBindings::CallFunction(UObject& InRuntimeObject, UObject* PropertyValue); template<> MOVIESCENE_API bool FTrackInstancePropertyBindings::TryGetPropertyValue(const FResolvedProperty& ResolvedProperty, UObject*& OutValue); template<> MOVIESCENE_API void FTrackInstancePropertyBindings::SetCurrentValue(UObject& Object, UObject* InValue); template void FTrackInstancePropertyBindings::InvokeSetterFunction(UObject* InRuntimeObject, UFunction* Setter, T&& InPropertyValue) { // CacheBinding already guarantees that the function has >= 1 parameters const int32 ParmsSize = Setter->ParmsSize; // This should all be const really, but ProcessEvent only takes a non-const void* void* InputParameter = const_cast::Type*>(&InPropertyValue); // By default we try and use the existing stack value uint8* Params = reinterpret_cast(InputParameter); check(InRuntimeObject && Setter); if (Setter->ReturnValueOffset != MAX_uint16 || Setter->NumParms > 0) { // Function has a return value or multiple parameters, we need to initialize memory for the entire parameter pack // We use alloca here (as in UObject::ProcessEvent) to avoid a heap allocation. Alloca memory survives the current function's stack frame. Params = reinterpret_cast(FMemory_Alloca(ParmsSize)); bool bFirstProperty = true; for (FProperty* Property = Setter->PropertyLink; Property; Property = Property->PropertyLinkNext) { // Initialize the parameter pack with any param properties that reside in the container if (Property->IsInContainer(ParmsSize)) { Property->InitializeValue_InContainer(Params); // The first encountered property is assumed to be the input value so initialize this with the user-specified value from InPropertyValue if (Property->HasAnyPropertyFlags(CPF_Parm) && !Property->HasAnyPropertyFlags(CPF_ReturnParm) && bFirstProperty) { const bool bIsValid = ensureMsgf(sizeof(T) == Property->GetElementSize(), TEXT("Property type does not match for Sequencer setter function %s::%s (%" SIZE_T_FMT "bytes != %ibytes"), *InRuntimeObject->GetName(), *Setter->GetName(), sizeof(T), Property->GetElementSize()); if (bIsValid) { Property->CopyCompleteValue(Property->ContainerPtrToValuePtr(Params), &InPropertyValue); } else { return; } } bFirstProperty = false; } } } // Now we have the parameters set up correctly, call the function InRuntimeObject->ProcessEvent(Setter, Params); }