게임 엔진/Unreal
[Unreal/C++] "Unable to destroy previously allocated sequence instance - this could indicate a memory leak." 에러 처리
niamdank
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);
}
}