Context
The LevelStreamingPersistence plugin can be used to remember and restore properties on actors, components and other objects that are part of a streamed level. Persisted properties are restored when a level is made visible, during AddToWorld.
A map placed actor’s blueprint may be modified since the instance was last saved. Components may have been added to the blueprint (i.e. SimpleConstructionScript components). In the editor, UE ensures that map placed actors have their latest component structure by running RerunConstructionScripts. This happens during AddToWorld, after restoring persistence.
Problem
Persistence of SCS component properties does not work if the component was added to actor blueprint after the map actor was last saved. During PIE, those values silently fail to persist when a streamed level was reloaded from disk. This can lead to confusion. Users likely won’t realize it fails, or realize why it’s failing.
When the map actor is resaved, the component will be part of the package and persistence works for them. This isn’t a problem for packaged builds, since actor instances are reconstructed and resaved during cook.
Suggested Fixes
The main problem is the silent failure: devs may not realize a property isn’t persisting properly in PIE. We can detect after RerunConstructionScript, if any components have been spawned with persisted properties, that no record was created for yet. That means the component was created late. We can then:
In the attached repro project:
Blank project setup steps:
[/Script/LevelStreamingPersistence.LevelStreamingPersistenceSettings] +Properties=(Path="/Game/Blueprints/BP_MyComponent.BP_MyComponent_C:TimeAlive", bIsPublic=true)
Repro steps:
Non-fatal callstack. This is the callstack where a SCS component that wasn’t saved on the actor is first created. This during AddToWorld, but after persisted properties have been restored:
> UnrealEditor-Engine.dll!UActorComponent::UActorComponent(const FObjectInitializer & ObjectInitializer) Line 560 C++ UnrealEditor-CoreUObject.dll!StaticConstructObject_Internal(const FStaticConstructObjectParameters & Params) Line 4969 C++ UnrealEditor-CoreUObject.dll!StaticDuplicateObjectEx(FObjectDuplicationParameters & Parameters) Line 3186 C++ UnrealEditor-Engine.dll!AActor::CreateComponentFromTemplate(UActorComponent * Template, const FName InName) Line 1127 C++ UnrealEditor-Engine.dll!USCS_Node::ExecuteNodeOnActor(AActor * Actor, USceneComponent * ParentComponent, const UE::Math::TTransform<double> * RootTransform, const FRotationConversionCache * RootRelativeRotationCache, bool bIsDefaultTransform, ESpawnActorScaleMethod TransformScaleMethod) Line 104 C++ UnrealEditor-Engine.dll!USimpleConstructionScript::ExecuteScriptOnActor(AActor * Actor, const TInlineComponentArray<USceneComponent *,24> & NativeSceneComponents, const UE::Math::TTransform<double> & RootTransform, const FRotationConversionCache * RootRelativeRotationCache, bool bIsDefaultTransform, ESpawnActorScaleMethod TransformScaleMethod) Line 640 C++ UnrealEditor-Engine.dll!AActor::ExecuteConstruction(const UE::Math::TTransform<double> & Transform, const FRotationConversionCache * TransformRotationCache, const FComponentInstanceDataCache * InstanceDataCache, bool bIsDefaultTransform, ESpawnActorScaleMethod TransformScaleMethod) Line 902 C++ UnrealEditor-Engine.dll!AActor::RerunConstructionScripts() Line 612 C++ UnrealEditor-Engine.dll!ULevel::IncrementalRunConstructionScripts(bool bProcessAllActors) Line 2078 C++ UnrealEditor-Engine.dll!ULevel::IncrementalUpdateComponents(int NumComponentsToUpdate, bool bRerunConstructionScripts, FRegisterComponentContext * InContext) Line 1891 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 3792 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++
There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-368857 in the post.