ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [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);
       }
    }

     

    댓글

Designed by Tistory.