// Copyright Epic Games, Inc. All Rights Reserved. #include "MovieSceneSignedObject.h" #include "Templates/Casts.h" #include "MovieSceneSequence.h" #include "Compilation/MovieSceneCompiledDataManager.h" #include "UObject/Package.h" #include "CoreGlobals.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(MovieSceneSignedObject) bool GMovieSceneSuppressSignatureChangeInCook = true; static FAutoConsoleVariableRef CVarMovieSceneSuppressSignatureChangeInCook( TEXT("Sequencer.SuppressSignatureChangeInCook"), GMovieSceneSuppressSignatureChangeInCook, TEXT("(Default: true) Globally suppress signature GUID changes inside cook commandlets."), ECVF_Default ); namespace UE { namespace MovieScene { static TSet> GDeferredSignedObjectFlushSignals; static TWeakPtr GDeferredSignedObjectChangeHandler; static uint32 GScopedSignedObjectDeferCount = 0; static uint32 GScopedSignedObjectSuppressCount = 0; static uint32 GScopedSignedObjectWarnModifyCompiledData = 0; void SignalScopedSignedObjectModifyFlush() { // Copy to a temporary set to guard against re-entrancy TSet> Temp; Swap(Temp, GDeferredSignedObjectFlushSignals); for (TWeakPtr WeakSignal : Temp) { if (TSharedPtr Signal = WeakSignal.Pin()) { Signal->OnDeferredModifyFlush(); } } } FScopedSignedObjectModifyDefer::FScopedSignedObjectModifyDefer(bool bInForceFlush) { ++GScopedSignedObjectDeferCount; bForceFlush = bInForceFlush; } FScopedSignedObjectModifyDefer::~FScopedSignedObjectModifyDefer() { --GScopedSignedObjectDeferCount; if (GScopedSignedObjectDeferCount == 0 || bForceFlush) { if (GDeferredSignedObjectChangeHandler.IsValid()) { GDeferredSignedObjectChangeHandler.Pin()->Flush(); } SignalScopedSignedObjectModifyFlush(); } } FScopedSignedObjectModifySuppress::FScopedSignedObjectModifySuppress(bool bInWarnModifyCompiledData) : bWarnModifyCompiledData(bInWarnModifyCompiledData) { ++GScopedSignedObjectSuppressCount; if (bWarnModifyCompiledData) { ++GScopedSignedObjectWarnModifyCompiledData; } } FScopedSignedObjectModifySuppress::~FScopedSignedObjectModifySuppress() { --GScopedSignedObjectSuppressCount; check(GScopedSignedObjectSuppressCount >= 0); if (bWarnModifyCompiledData) { --GScopedSignedObjectWarnModifyCompiledData; check(GScopedSignedObjectWarnModifyCompiledData >= 0); } } } // namespace MovieScene } // namespace UE UMovieSceneSignedObject::UMovieSceneSignedObject(const FObjectInitializer& Init) : Super(Init) { using namespace UE::MovieScene; if (HasAnyFlags(RF_Transactional) && GDeferredSignedObjectChangeHandler.IsValid() #if WITH_EDITOR && GIsTransacting #endif ) { GDeferredSignedObjectChangeHandler.Pin()->DeferMarkAsChanged(this); } } TWeakPtr UMovieSceneSignedObject::GetDeferredHandler() { return UE::MovieScene::GDeferredSignedObjectChangeHandler; } void UMovieSceneSignedObject::SetDeferredHandler(TWeakPtr&& InHandler) { UE::MovieScene::GDeferredSignedObjectChangeHandler = MoveTemp(InHandler); } void UMovieSceneSignedObject::AddFlushSignal(TWeakPtr Signal) { UE::MovieScene::GDeferredSignedObjectFlushSignals.Add(Signal); } void UMovieSceneSignedObject::ResetImplicitScopedModifyDefer() { using namespace UE::MovieScene; if (GDeferredSignedObjectChangeHandler.IsValid()) { GDeferredSignedObjectChangeHandler.Pin()->ResetImplicitScopedModifyDefer(); } } void UMovieSceneSignedObject::PostInitProperties() { Super::PostInitProperties(); // Always seed newly created objects with a new signature // (CDO and archetypes always have a zero GUID) if (!HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject | RF_NeedLoad | RF_LoadCompleted) && Signature == GetDefault()->Signature) { Signature = FGuid::NewGuid(); } } void UMovieSceneSignedObject::PostLoad() { Super::PostLoad(); } void UMovieSceneSignedObject::MarkAsChanged() { using namespace UE::MovieScene; const bool bGlobalCookSuppression = (GMovieSceneSuppressSignatureChangeInCook && IsRunningCookCommandlet()); if (GScopedSignedObjectWarnModifyCompiledData > 0 || bGlobalCookSuppression) { // During cooking, we don't want to regenerate new GUIDs, since they would be different // every time and trigger non-detrministic cooking errors. We therefore bail out early // and leave the original GUID. // // However, we also check that nobody is trying to modify data after we have compiled the // sequence, as this would lead to a mismatch between the source data and the compiled // data we use to run it. We therefore check with the compiled data manager whether our // parent sequence has already been compiled or not. UMovieSceneSequence* OuterSequence = GetTypedOuter(); if (OuterSequence) { UMovieSceneCompiledDataManager* CompiledDataManager = UMovieSceneCompiledDataManager::GetPrecompiledData(); check(CompiledDataManager); const bool bCanMarkAsChanged = CompiledDataManager->CanMarkSignedObjectAsChangedDuringCook(OuterSequence); ensureAlwaysMsgf(bCanMarkAsChanged, TEXT("This object's signature was locked after its sequence was compiled, " "but someone attempted to mark it, or one of its inner objects, as changed! " "Current object: %s ; Current sequence: %s"), *GetPathName(), *OuterSequence->GetPathName()); } } // Completely skip signature changes and notifications if we are suppressing things. if (GScopedSignedObjectSuppressCount > 0 || bGlobalCookSuppression) { return; } // We always change the signature immediately to ensure that any external code that wants // to directly check our signature (eg, to clear caches) can still do so even while there // is an outstanding signal pending for this object Signature = FGuid::NewGuid(); // Regenerate the signature for all parents of this class UObject* Outer = GetOuter(); while (Outer) { UMovieSceneSignedObject* TypedOuter = Cast(Outer); if (TypedOuter) { TypedOuter->Signature = FGuid::NewGuid(); } Outer = Outer->GetOuter(); } // Give the change handler an opportunity to create an implicit scope if there's // no explicit one active. if (GScopedSignedObjectDeferCount == 0 && GDeferredSignedObjectChangeHandler.IsValid()) { GDeferredSignedObjectChangeHandler.Pin()->CreateImplicitScopedModifyDefer(); } if (GScopedSignedObjectDeferCount == 0 || !GDeferredSignedObjectChangeHandler.IsValid()) { BroadcastChanged(); SignalScopedSignedObjectModifyFlush(); } else { GDeferredSignedObjectChangeHandler.Pin()->DeferMarkAsChanged(this); } } void UMovieSceneSignedObject::BroadcastChanged() { using namespace UE::MovieScene; EventHandlers.Trigger(&ISignedObjectEventHandler::OnModifiedDirectly, this); OnSignatureChangedEvent.Broadcast(); UObject* Outer = GetOuter(); while (Outer) { UMovieSceneSignedObject* TypedOuter = Cast(Outer); if (TypedOuter) { TypedOuter->EventHandlers.Trigger(&ISignedObjectEventHandler::OnModifiedIndirectly, this); TypedOuter->OnSignatureChangedEvent.Broadcast(); } Outer = Outer->GetOuter(); } } #if WITH_EDITOR bool UMovieSceneSignedObject::Modify(bool bAlwaysMarkDirty) { using namespace UE::MovieScene; bool bModified = Super::Modify(bAlwaysMarkDirty); if ( bAlwaysMarkDirty ) { MarkAsChanged(); } return bModified; } void UMovieSceneSignedObject::PostEditChangeProperty( struct FPropertyChangedEvent& PropertyChangedEvent) { Super::PostEditChangeProperty(PropertyChangedEvent); MarkAsChanged(); } void UMovieSceneSignedObject::PostEditUndo() { using namespace UE::MovieScene; Super::PostEditUndo(); MarkAsChanged(); EventHandlers.Trigger(&ISignedObjectEventHandler::OnPostUndo); } void UMovieSceneSignedObject::PostEditUndo(TSharedPtr TransactionAnnotation) { using namespace UE::MovieScene; Super::PostEditUndo(TransactionAnnotation); MarkAsChanged(); EventHandlers.Trigger(&ISignedObjectEventHandler::OnPostUndo); } #endif