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.
In the attached repro project:
In a blank project:
[/Script/LevelStreamingPersistence.LevelStreamingPersistenceSettings] +Properties=(Path="/Game/Blueprints/BP_MyActor.BP_MyActor_C:TimeAlive", bIsPublic=true)
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++
There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-368861 in the post.