`FWidgetAnimationState::Pause()` sets `PlaybackStatus` to `EMovieScenePlayerStatus::Stopped` (acknowledged as legacy behavior in engine source comments). This causes `UUserWidget::ClearStoppedAnimationStates()` to tear down paused animation states, destroying their MovieScene entities. When the animation is played again, a new entity with a new EntityID is allocated. Since MovieScene EntityIDs use a 16-bit sequential counter that is never recycled, this destroy-recreate cycle eventually exhausts the 65,535 limit, causing the assertion crash above.
// Engine/Source/Runtime/UMG/Private/UserWidget.cpp
void UUserWidget::ClearStoppedAnimationStates()
{
for (auto It = ActiveAnimations.CreateIterator(); It; ++It)
{
FWidgetAnimationState& State(*It);
if (!State.IsValid())
{
It.RemoveCurrent();
}
else if ((State.GetPlaybackStatus() == EMovieScenePlayerStatus::Stopped)
&& !State.IsStopping())
{
State.TearDown(); // ← Also tears down PAUSED states!
It.RemoveCurrent();
}
}
}
// Since `Pause()` sets status to `Stopped`, this function cannot distinguish between truly stopped and paused animations. All paused states get destroyed.
// Added a `bIsPaused` flag to `FWidgetAnimationState` to distinguish Paused from Stopped.
// WidgetAnimationState.h
bool IsPaused() const { return bIsPaused; }
// ...
bool bIsPaused : 1;
// WidgetAnimationState.cpp
// Constructor
bIsPaused = false;
// Play()
bIsPaused = false;
// Stop()
bIsPaused = false;
// Pause()
PlaybackManager.SetPlaybackStatus(EMovieScenePlayerStatus::Stopped);
bIsPaused = true;
// UserWidget.cpp — ClearStoppedAnimationStates
else if ((State.GetPlaybackStatus() == EMovieScenePlayerStatus::Stopped)
&& !State.IsStopping())
{
// Pause() sets PlaybackStatus to Stopped, but the animation is not truly stopped.
// Tearing it down would discard the EntityID, leading to EntityID overflow on long sessions.
if (State.IsPaused())
{
continue;
}
State.TearDown();
It.RemoveCurrent();
}
1. **Is `bIsPaused` the correct approach?** Or would Epic prefer changing `Pause()` to set `EMovieScenePlayerStatus::Paused` instead of `Stopped`?
2. **Why was the `Pause() → Stopped` legacy behavior preserved?** Are there other systems that depend on paused animations having `Stopped` status?
3. **Are MovieScene EntityIDs intentionally non-recyclable?** If recycling were implemented, the overflow would not occur regardless of the Pause/Stop issue.
4. **Is there a plan to address this in 5.7+?** We see CL-2 does not fix this. Is a separate fix planned?