Description

Context

The LevelStreamingPersistence plugin can be used to remember and restore properties on actors and components that are part of a streamed level. Saved property values are restored when a level gets added to the world. Objects with records are found via FindObject, and serialized property values are restored on them using FProperty functions.

Users may want to persist properties on actors that may be destroyed during gameplay. For example: barrels with health (persisted) that can be destroyed. Lootable chests with inventory, that can be destroyed.

Problem

A map actor may be destroyed during gameplay. When the level gets streamed out and in without garbage collection in-between, the actor will still exist in memory even though it’s not in play. ULevelStreamingPersistenceManager::RestorePublicProperties() assumes that the object found via FindObject is valid, causing a crash.

Suggested Fix

Call-sites should not pass in objects that aren’t valid. They shouldn’t just null-check, but also account for non-null actors that are marked as garbage. Support for persisting the actor’s destruction across level package reloads is logged as a separate task.

Steps to Reproduce

In the attached repro project:

  • Open MAP_DestroyedActorCrash
  • Start PIE
  • Press Z to destroy the actor.
  • Move away to cause level to stream out.
  • Move back to cause level to stream back in.
  • Observe: Crash (see callstack)
  • Expected: No crash

In a blank project:

  • Enable LevelStreamingPersistence plugin
  • Create a blueprint BP_MyActor
    • Add a float variable to it, TimeAlive
    • On BeginPlay, delay 5 seconds, then let it Destroy itself
  • Add to DefaultEngine.ini to persist the value:
[/Script/LevelStreamingPersistence.LevelStreamingPersistenceSettings]
+Properties=(Path="/Game/Blueprints/BP_MyActor.BP_MyActor_C:TimeAlive", bIsPublic=true)
  • Place BP_MyActor in the level. Leave it as spatially partitioned.
  • In World Settings, lower Cell Size and Loading Range to easily trigger level streaming in and out.
  • Start PIE
  • Wait for BP_MyActor to destroy itself
  • Move away to cause level to stream out.
  • Move back to cause level to stream back in.
  • Observe: Crash (see callstack)
  • Expected: No crash
Callstack

Fatal callstack (5.7.4):

 	UnrealEditor-LevelStreamingPersistence.dll!ULevelStreamingPersistenceManager::RestorePublicProperties(UObject * Object, const FLevelStreamingPersistentObjectPublicProperties & ObjectsPublicPropertyValues) Line 475	C++
>	UnrealEditor-LevelStreamingPersistence.dll!ULevelStreamingPersistenceManager::RestoreLevelPersistentPropertyValues(const ULevel * InLevel) Line 444	C++
 	UnrealEditor-LevelStreamingPersistence.dll!ULevelStreamingPersistenceManager::OnLevelBeginMakingVisible(UWorld * World, const ULevelStreaming * InStreamingLevel, ULevel * InLoadedLevel) Line 287	C++
 	[Inline Frame] UnrealEditor-LevelStreamingPersistence.dll!Invoke(void(ULevelStreamingPersistenceManager::*)(UWorld *, const ULevelStreaming *, ULevel *) PtrMemFun, ULevelStreamingPersistenceManager * &) Line 66	C++
 	[Inline Frame] UnrealEditor-LevelStreamingPersistence.dll!UE::Core::Private::Tuple::TTupleBase<TIntegerSequence<unsigned int>>::ApplyAfter(void(ULevelStreamingPersistenceManager::*)(UWorld *, const ULevelStreaming *, ULevel *) &) Line 326	C++
 	UnrealEditor-LevelStreamingPersistence.dll!TBaseUObjectMethodDelegateInstance<0,ULevelStreamingPersistenceManager,void __cdecl(UWorld *,ULevelStreaming const *,ULevel *),FDefaultDelegateUserPolicy>::ExecuteIfSafe(UWorld * <Params_0>, const ULevelStreaming * <Params_1>, ULevel * <Params_2>) Line 713	C++
 	[Inline Frame] UnrealEditor-Engine.dll!TMulticastDelegateBase<FDefaultDelegateUserPolicy>::Broadcast(UWorld *) Line 301	C++
 	UnrealEditor-Engine.dll!TMulticastDelegate<void __cdecl(UWorld *,ULevelStreaming const *,ULevel *),FDefaultDelegateUserPolicy>::Broadcast(UWorld * <Params_0>, const ULevelStreaming * <Params_1>, ULevel * <Params_2>) Line 1076	C++
 	UnrealEditor-Engine.dll!UWorld::AddToWorld(ULevel * Level, const UE::Math::TTransform<double> & LevelTransform, bool bConsiderTimeLimit, const TOptional<UE::FTimeout const> & ExternalTimeout, FNetLevelVisibilityTransactionId TransactionId, ULevelStreaming * InOwningLevelStreaming) Line 3652	C++
 	UnrealEditor-Engine.dll!ULevelStreaming::UpdateStreamingState(bool & bOutUpdateAgain, bool & bOutRedetermineTarget, const TOptional<UE::FTimeout const> & InExternalTimeout) Line 1059	C++
 	[Inline Frame] UnrealEditor-Engine.dll!FStreamingLevelPrivateAccessor::UpdateStreamingState(ULevelStreaming *) Line 808	C++
 	UnrealEditor-Engine.dll!UWorld::UpdateLevelStreaming(const TOptional<UE::FTimeout const> & ExternalTimeout) Line 5045	C++
 	UnrealEditor-Engine.dll!UGameViewportClient::Draw(FViewport * InViewport, FCanvas * SceneCanvas) Line 1848	C++
 	UnrealEditor-Engine.dll!FViewport::Draw(bool bShouldPresent) Line 1798	C++
 	UnrealEditor-UnrealEd.dll!UEditorEngine::Tick(float DeltaSeconds, bool bIdleMode) Line 2440	C++
 	UnrealEditor-UnrealEd.dll!UUnrealEdEngine::Tick(float DeltaSeconds, bool bIdleMode) Line 537	C++
 	UnrealEditor.exe!FEngineLoop::Tick() Line 5828	C++

Have Comments or More Details?

There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-368861 in the post.

0
Login to Vote

Fixed
Fix Commit52302680
CreatedMar 6, 2026
ResolvedMar 31, 2026
UpdatedJun 8, 2026
View Jira Issue