-
[Unreal/C++] "Unable to destroy previously allocated sequence instance - this could indicate a memory leak." 에러 처리게임 엔진/Unreal 2023. 6. 17. 18:08
정석적인 방법은 Component 등 플레이 마다 Begin과 End 함수가 항상 동작하는 액터에서 시퀀스 플레이어를 관리하여 Begin에 생성, End에 제거하는 식으로 사용하는 것이다.
코드 참고 - ActorSequenceComponent코드 참고 - ActorSequenceComponent
void UActorSequenceComponent::BeginPlay() { Super::BeginPlay(); if (Sequence != nullptr) { SequencePlayer = NewObject<UActorSequencePlayer>(this, "SequencePlayer"); SequencePlayer->SetPlaybackClient(this); // Initialize this player for tick as soon as possible to ensure that a persistent // reference to the tick manager is maintained SequencePlayer->InitializeForTick(this); SequencePlayer->Initialize(Sequence, PlaybackSettings); if (PlaybackSettings.bAutoPlay) { SequencePlayer->Play(); } } } void UActorSequenceComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) { if (SequencePlayer) { // Stop the internal sequence player during EndPlay when it's safer // to modify other actor's state during state restoration. SequencePlayer->Stop(); SequencePlayer->TearDown(); } Super::EndPlay(EndPlayReason); }
약간의 꼼수를 부리자면 SequencePlayer의 엔진 코드에 Reset 함수를 추가하여 assert에 걸리는 부분을 해결해주는 것이다.
void UMovieSceneSequencePlayer::Reset() { RegisteredTickInterval.Reset(); RootTemplateInstance = FMovieSceneRootEvaluationTemplateInstance(); }
크래시가 발생하는 이유는 크게 두 가지다.
1. check(RunnerToUse);가 없다고 나옴->이유: TickManager는 UPROPERTY(transient)라서 게임 끄면 사라짐. 그런데 RegisteredTickInterval은 한 번 셋 되면 에디터 끄지 않으면 살아있음 -> 등록 안 하고 찾으려고 하게 됨.
코드 참고 - SceneSequencePlayer::Initialize코드 참고 - SceneSequencePlayer::Initialize
void UMovieSceneSequencePlayer::Initialize(UMovieSceneSequence* InSequence) { check(InSequence); check(!bIsEvaluating); // If we have a valid sequence that may have been played back, // Explicitly stop and tear down the template instance before // reinitializing it with the new sequence. Care should be taken // here that Stop is not called on the first Initialization as this // may be called during PostLoad. if (Sequence) { StopAtCurrentTime(); } Sequence = InSequence; FFrameTime StartTimeWithOffset = StartTime; EUpdateClockSource ClockToUse = EUpdateClockSource::Tick; UMovieScene* MovieScene = Sequence->GetMovieScene(); if (MovieScene) { EMovieSceneEvaluationType EvaluationType = MovieScene->GetEvaluationType(); FFrameRate TickResolution = MovieScene->GetTickResolution(); FFrameRate DisplayRate = MovieScene->GetDisplayRate(); UE_LOG(LogMovieScene, Verbose, TEXT("Initialize - MovieSceneSequence: %s, TickResolution: %f, DisplayRate: %d, CurrentTime: %d"), *InSequence->GetName(), TickResolution.Numerator, DisplayRate.Numerator); // We set the play position in terms of the display rate, // but want evaluation ranges in the moviescene's tick resolution PlayPosition.SetTimeBase(DisplayRate, TickResolution, EvaluationType); { // Set up the default frame range from the sequence's play range TRange<FFrameNumber> PlaybackRange = MovieScene->GetPlaybackRange(); const FFrameNumber SrcStartFrame = UE::MovieScene::DiscreteInclusiveLower(PlaybackRange); const FFrameNumber SrcEndFrame = UE::MovieScene::DiscreteExclusiveUpper(PlaybackRange); const FFrameTime EndingTime = ConvertFrameTime(SrcEndFrame, TickResolution, DisplayRate); const FFrameNumber StartingFrame = ConvertFrameTime(SrcStartFrame, TickResolution, DisplayRate).FloorToFrame(); const FFrameNumber EndingFrame = EndingTime.FloorToFrame(); SetFrameRange(StartingFrame.Value, (EndingFrame - StartingFrame).Value, EndingTime.GetSubFrame()); } // Reset the play position based on the user-specified start offset, or a random time FFrameTime SpecifiedStartOffset = PlaybackSettings.StartTime * DisplayRate; // Setup the starting time FFrameTime StartingTimeOffset = PlaybackSettings.bRandomStartTime ? FFrameTime(FMath::Rand() % GetFrameDuration()) : FMath::Clamp<FFrameTime>(SpecifiedStartOffset, 0, GetFrameDuration()-1); StartTimeWithOffset = StartTime + StartingTimeOffset; ClockToUse = MovieScene->GetClockSource(); if (ClockToUse == EUpdateClockSource::Custom) { TimeController = MovieScene->MakeCustomTimeController(GetPlaybackContext()); } } if (!TimeController.IsValid()) { switch (ClockToUse) { case EUpdateClockSource::Audio: TimeController = MakeShared<FMovieSceneTimeController_AudioClock>(); break; case EUpdateClockSource::Platform: TimeController = MakeShared<FMovieSceneTimeController_PlatformClock>(); break; case EUpdateClockSource::RelativeTimecode: TimeController = MakeShared<FMovieSceneTimeController_RelativeTimecodeClock>(); break; case EUpdateClockSource::Timecode: TimeController = MakeShared<FMovieSceneTimeController_TimecodeClock>(); break; case EUpdateClockSource::PlayEveryFrame: TimeController = MakeShared<FMovieSceneTimeController_PlayEveryFrame>(); break; default: TimeController = MakeShared<FMovieSceneTimeController_Tick>(); break; } if (!ensureMsgf(TimeController.IsValid(), TEXT("No time controller specified for sequence playback. Falling back to Engine Tick clock source."))) { TimeController = MakeShared<FMovieSceneTimeController_Tick>(); } } if (!TickManager) { InitializeForTick(GetPlaybackContext()); } FMovieSceneSequenceTickInterval TickInterval = PlaybackSettings.bInheritTickIntervalFromOwner ? FMovieSceneSequenceTickInterval::GetInheritedInterval(this) : PlaybackSettings.TickInterval; // If we haven't registered with the tick manager yet, register directly if (!RegisteredTickInterval.IsSet()) { TickManager->RegisterTickClient(TickInterval, this); } // If we were already registered with a different Tick Interval we need to re-register with the new one, which involves tearing everything down and setting up a new instance else if (RegisteredTickInterval.GetValue() != TickInterval) { RootTemplateInstance.BeginDestroy(); TickManager->UnregisterTickClient(this); TickManager->RegisterTickClient(RegisteredTickInterval.GetValue(), this); } RegisteredTickInterval = TickInterval; TSharedPtr<FMovieSceneEntitySystemRunner> RunnerToUse = TickManager->GetRunner(RegisteredTickInterval.GetValue()); if (EnumHasAnyFlags(Sequence->GetFlags(), EMovieSceneSequenceFlags::BlockingEvaluation)) { SynchronousRunner = MakeShared<FMovieSceneEntitySystemRunner>(); RunnerToUse = SynchronousRunner; } check(RunnerToUse); RootTemplateInstance.Initialize(*Sequence, *this, nullptr, RunnerToUse); LatentActionManager.ClearLatentActions(); // Set up playback position (with offset) after Stop(), which will reset the starting time to StartTime PlayPosition.Reset(StartTimeWithOffset); TimeController->Reset(GetCurrentTime()); UpdateNetworkSyncProperties(); }
2. TemplateInitialize시 ensureMsgf(false, TEXT("Unable t... 에 걸림 -> 이유: RootInstanceHandle가 Valid로 체크 되면서 이거 가지고 뭘 하려고 하는데 실제로는 아무것도 없어서.
코드 참고 - RootEvaluationTemplateInstance::Initialize코드 참고 - RootEvaluationTemplateInstance::Initialize
void FMovieSceneRootEvaluationTemplateInstance::Initialize(UMovieSceneSequence& InRootSequence, IMovieScenePlayer& Player, UMovieSceneCompiledDataManager* InCompiledDataManager, TWeakPtr<FMovieSceneEntitySystemRunner> InWeakRunner) { bool bReinitialize = ( // Initialize if we weren't initialized before and this is our first sequence. WeakRootSequence.Get() == nullptr || // Initialize if we lost our linker. EntitySystemLinker == nullptr); const UMovieSceneCompiledDataManager* PreviousCompiledDataManager = CompiledDataManager; if (InCompiledDataManager) { CompiledDataManager = InCompiledDataManager; } else { #if WITH_EDITOR EMovieSceneServerClientMask Mask = EmulatedNetworkMask; if (Mask == EMovieSceneServerClientMask::All) { UObject* PlaybackContext = Player.GetPlaybackContext(); UWorld* World = PlaybackContext ? PlaybackContext->GetWorld() : nullptr; if (World) { ENetMode NetMode = World->GetNetMode(); if (NetMode == ENetMode::NM_DedicatedServer) { Mask = EMovieSceneServerClientMask::Server; } else if (NetMode == ENetMode::NM_Client) { Mask = EMovieSceneServerClientMask::Client; } } } CompiledDataManager = UMovieSceneCompiledDataManager::GetPrecompiledData(Mask); #else CompiledDataManager = UMovieSceneCompiledDataManager::GetPrecompiledData(); #endif } bReinitialize |= (PreviousCompiledDataManager != CompiledDataManager); TSharedPtr<FMovieSceneEntitySystemRunner> PreviousRunner = WeakRunner.Pin(); if (UMovieSceneSequence* ExistingSequence = WeakRootSequence.Get()) { if (ExistingSequence != &InRootSequence) { bReinitialize = true; } } CompiledDataID = CompiledDataManager->GetDataID(&InRootSequence); WeakRootSequence = &InRootSequence; RootID = MovieSceneSequenceID::Root; WeakRunner = InWeakRunner; if (bReinitialize) { if (RootInstanceHandle.IsValid()) { if (PreviousRunner) { PreviousRunner->AbandonAndDestroyInstance(RootInstanceHandle); } else if (EntitySystemLinker) { EntitySystemLinker->GetInstanceRegistry()->DestroyInstance(RootInstanceHandle); } else { ensureMsgf(false, TEXT("Unable to destroy previously allocated sequence instance - this could indicate a memory leak.")); } } Player.State.PersistentEntityData.Reset(); Player.State.PersistentSharedData.Reset(); EntitySystemLinker = ConstructEntityLinker(Player); RootInstanceHandle = EntitySystemLinker->GetInstanceRegistry()->AllocateRootInstance(&Player); TSharedPtr<FMovieSceneEntitySystemRunner> Runner = WeakRunner.Pin(); if (ensure(Runner) && EntitySystemLinker) { if (Runner->IsAttachedToLinker() && Runner->GetLinker() != EntitySystemLinker) { Runner->DetachFromLinker(); } if (!Runner->IsAttachedToLinker()) { Runner->AttachToLinker(EntitySystemLinker); } } Player.PreAnimatedState.Initialize(EntitySystemLinker, RootInstanceHandle); } }