Hi, I’m seeing non-deterministic cook output for ALandscapeStreamingProxy actors when cooking large world that uses world partitioning.
The immediate problem is the following - at some point, the primary ALandscape actor had some property changes (some new entries added to TargetLayers collection). The ALandscapeStreamingProxy actors linked to this landscape in other world partition cells were not resaved, so normally during cook the PostLoad calls FixupSharedData -> SynchronizeSharedProperties, which syncs these new layers. However, in some cases FixupSharedData is not called, because GetLandscapeActor() returns null.
GetLandscapeActor() does TSoftObjectPtr<ALandscape>::Get(), and when I put a breakpoint to catch this situation, I see that this soft pointer has null weak pointer, so Get fails. According to the logs, this happens right after GC, so presumably the root cause is that there’s nothing keeping the primary landscape actor alive while all the packages containing proxies are cooked.
So far I haven’t found a way to reproduce it reliably, e.g. when trying to manually force a GC at various point of cook I don’t see a destructor of ALandscape being called, so I presume I’m missing something - maybe there is _some_ mechanism that is simply not reliable?
One other potentially suspicious thing that I’ve noticed is this - normally the primary ALandscape is loaded by UWorldPartition::PrepareGeneratorPackageForCook:
- it loops over collection returned by RuntimeHash->GetAlwaysLoadedCells(), and calls UWorldPartitionRuntimeLevelStreamingCell::OnPrepareGeneratorPackageForCook()
- this function calls FWorldPartitionLevelHelper::LoadActors(), one of the actors it loads is the ALandscape. Judging by the name of the function, I’d expect the actors loaded here to somehow be kept alive until the end of the cook? This would then solve the issue…
- one thing that is done later is FWorldPartitionCookPackageSplitter::PopulateGeneratorPackage calling PopulateContext.ReportKeepReferencedPackages, and the list passed as argument includes a package that the landscape was loaded from. Again, judging by the name, this looks like this mechanism designed to keep it alive?.. however - by now this package is called ‘TrashedPackage’ (it was renamed by WorldPartitionLevelHelper::MoveExternalActorsToLevel; the ALandscape has IsPackageExternal() returning true), and the comments there imply that the package is now empty and so probably that doesn’t reference the landscape actor anymore?
So my question is - how is it even supposed to work? What is the mechanism that is supposed to keep the landscape alive? Is there even such a mechanism? If not, how is it expected to keep cook results deterministic? Is the ReportKeepReferencedPackages function really expecting to be called with ‘trashed packages’ as arguments?