I went deeper to find the cause of this problem.
And I’ve made it this far.
In ULevel::Serialize using FPIEFixupSerializer, none of the conditional statements in FLazyObjectPtr::PossiblySerializeObjectGuid are caught.
Even though the address of the pointer to ULandscapeHeightfieldCollisionComponent::RenderComponent changes when “ULevel::Serialize”.
LazyObjectPtr.cpp
void FLazyObjectPtr::PossiblySerializeObjectGuid(UObject *Object, FStructuredArchive::FRecord Record)
{
FArchive& UnderlyingArchive = Record.GetUnderlyingArchive();
if (UnderlyingArchive.IsSaving() || UnderlyingArchive.IsCountingMemory())
{
...
}
else if (UnderlyingArchive.IsLoading())
{
TOptional<FStructuredArchiveSlot> GuidSlot = Record.TryEnterField(SA_FIELD_NAME(TEXT("Guid")), false);
if (GuidSlot.IsSet())
{
FUniqueObjectGuid Guid;
GuidSlot.GetValue() << Guid;
// Don't try and resolve GUIDs when loading a package for diffing
const UPackage* Package = Object->GetOutermost();
const bool bLoadedForDiff = Package->HasAnyPackageFlags(PKG_ForDiffing);
if (!bLoadedForDiff && (!(UnderlyingArchive.GetPortFlags() & PPF_Duplicate) || (UnderlyingArchive.GetPortFlags() & PPF_DuplicateForPIE)))
{
check(!Guid.IsDefault());
UObject* OtherObject = Guid.ResolveObject();
if (OtherObject != Object) // on undo/redo, the object (potentially) already exists
{
const bool bDuplicate = OtherObject != nullptr;
const bool bReassigning = FParse::Param(FCommandLine::Get(), TEXT("AssignNewMapGuids"));
if (bDuplicate || bReassigning)
{
if (!bReassigning && OtherObject && OtherObject->HasAnyFlags(RF_NewerVersionExists))
{
GuidAnnotation.RemoveAnnotation(OtherObject);
GuidAnnotation.AddAnnotation(Object, Guid);
}
#if WITH_EDITOR
else if (Object->GetOutermostObject()->GetPackage()->HasAnyPackageFlags(PKG_PlayInEditor))
{
Guid = RemapGuid(Guid, Object->GetOutermostObject()->GetPackage()->GetPIEInstanceID());
GuidAnnotation.AddAnnotation(Object, Guid);
}
#endif
else
{
if (!bReassigning)
{
// Always warn for non-map packages, skip map packages in PIE or game
const bool bInGame = FApp::IsGame() || Package->HasAnyPackageFlags(PKG_PlayInEditor);
UE_CLOG(!Package->ContainsMap() || !bInGame, LogUObjectGlobals, Warning,
TEXT("Guid referenced by %s is already used by %s, which should never happen in the editor but could happen at runtime with duplicate level loading or PIE"),
*Object->GetFullName(), *OtherObject->GetFullName());
}
else
{
UE_LOG(LogUObjectGlobals, Warning, TEXT("Assigning new Guid to %s"), *Object->GetFullName());
}
// This guid is in use, which should never happen in the editor but could happen at runtime with duplicate level loading or PIE. If so give it an invalid GUID and don't add to the annotation map.
Guid = FGuid();
}
}
else
{
GuidAnnotation.AddAnnotation(Object, Guid);
}
FUniqueObjectGuid::InvalidateTag();
}
}
}
}
}
So, even if you “Get” LazyObjectPtr, ULandscapeHeightfieldCollisionComponent::RenderComponent, it does not find the correct object.
PersistentObjectPtr.h
/**
* Dereference the pointer, which may cause it to become valid again. Will not try to load pending outside of game thread
*
* @return nullptr if this object is gone or the pointer was null, otherwise a valid UObject pointer
*/
FORCEINLINE UObject* Get() const
{
UObject* Object = WeakPtr.Get();
// Do a full resolve if the returned object is null and either we think we've loaded new objects, or the weak ptr may be stale
if (!Object && ObjectID.IsValid() && (TObjectID::GetCurrentTag() != TagAtLastTest || !WeakPtr.IsExplicitlyNull()))
{
Object = ObjectID.ResolveObject();
WeakPtr = Object;
// Not safe to update tag during save as ResolveObject may have failed accidentally
if (Object || !GIsSavingPackage)
{
TagAtLastTest = TObjectID::GetCurrentTag();
}
// If this object is pending kill or otherwise invalid, this will return nullptr as expected
Object = WeakPtr.Get();
}
return Object;
}
LazyObjectPtr.cpp
UObject* FUniqueObjectGuid::ResolveObject() const
{
UObject* Result = GuidAnnotation.Find(*this);
return Result;
}