3333 lines
100 KiB
C++
3333 lines
100 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "CookPackageData.h"
|
|
|
|
#include "Algo/AnyOf.h"
|
|
#include "Algo/Count.h"
|
|
#include "Algo/Find.h"
|
|
#include "Algo/Sort.h"
|
|
#include "Algo/Unique.h"
|
|
#include "AssetCompilingManager.h"
|
|
#include "AssetRegistry/IAssetRegistry.h"
|
|
#include "Async/ParallelFor.h"
|
|
#include "CompactBinaryTCP.h"
|
|
#include "Cooker/BuildResultDependenciesMap.h"
|
|
#include "Cooker/CookDirector.h"
|
|
#include "Cooker/CookGenerationHelper.h"
|
|
#include "Cooker/CookLogPrivate.h"
|
|
#include "Cooker/CookPackagePreloader.h"
|
|
#include "Cooker/CookPlatformManager.h"
|
|
#include "Cooker/CookRequestCluster.h"
|
|
#include "Cooker/CookWorkerClient.h"
|
|
#include "Cooker/IWorkerRequests.h"
|
|
#include "Cooker/PackageTracker.h"
|
|
#include "CookOnTheSide/CookOnTheFlyServer.h"
|
|
#include "Containers/StringView.h"
|
|
#include "Engine/Console.h"
|
|
#include "HAL/FileManager.h"
|
|
#include "HAL/PlatformTime.h"
|
|
#include "Interfaces/IPluginManager.h"
|
|
#include "Interfaces/ITargetPlatform.h"
|
|
#include "Misc/CommandLine.h"
|
|
#include "Misc/CoreMiscDefines.h"
|
|
#include "Misc/PackageAccessTrackingOps.h"
|
|
#include "Misc/PackageName.h"
|
|
#include "Misc/Parse.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Misc/ScopeExit.h"
|
|
#include "Misc/ScopeLock.h"
|
|
#include "Serialization/CompactBinaryWriter.h"
|
|
#include "ShaderCompiler.h"
|
|
#include "UObject/Object.h"
|
|
#include "UObject/ObjectRedirector.h"
|
|
#include "UObject/Package.h"
|
|
#include "UObject/ReferenceChainSearch.h"
|
|
#include "UObject/UObjectGlobals.h"
|
|
#include "UObject/UObjectHash.h"
|
|
|
|
namespace UE::Cook
|
|
{
|
|
|
|
float GPollAsyncPeriod = .100f;
|
|
static FAutoConsoleVariableRef CVarPollAsyncPeriod(
|
|
TEXT("cook.PollAsyncPeriod"),
|
|
GPollAsyncPeriod,
|
|
TEXT("Minimum time in seconds between PollPendingCookedPlatformDatas."),
|
|
ECVF_Default);
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FPackageData
|
|
FPackagePlatformData::FPackagePlatformData()
|
|
: Reachability((uint8)EReachability::None), ReachabilityVisitedByCluster((uint8)EReachability::None)
|
|
, bSaveTimedOut(0), bCookable(1), bExplorable(1), bExplorableOverride(0) , bIncrementallyUnmodified(0)
|
|
, bIncrementallySkipped(0), bRegisteredForCachedObjectsInOuter(0), bCommitted(0)
|
|
, CookResults((uint8)ECookResult::NotAttempted)
|
|
{
|
|
}
|
|
|
|
void FPackagePlatformData::ResetReachable(EReachability InReachability)
|
|
{
|
|
ClearReachability(InReachability);
|
|
ClearVisitedByCluster(InReachability);
|
|
if (EnumHasAnyFlags(InReachability, EReachability::Runtime))
|
|
{
|
|
SetCookable(true);
|
|
SetExplorable(true);
|
|
}
|
|
}
|
|
|
|
void FPackagePlatformData::MarkAsExplorable()
|
|
{
|
|
ResetReachable(EReachability::Runtime);
|
|
SetExplorableOverride(true);
|
|
}
|
|
|
|
void FPackagePlatformData::MarkCommittableForWorker(EReachability InReachability, FCookWorkerClient& CookWorkerClient)
|
|
{
|
|
AddReachability(InReachability);
|
|
AddVisitedByCluster(InReachability);
|
|
if (EnumHasAnyFlags(InReachability, EReachability::Runtime))
|
|
{
|
|
SetExplorable(true);
|
|
SetCookable(true);
|
|
}
|
|
SetCommitted(false);
|
|
SetCookResults(ECookResult::NotAttempted);
|
|
}
|
|
|
|
bool FPackagePlatformData::NeedsCommit(const ITargetPlatform* PlatformItBelongsTo, EReachability InReachability) const
|
|
{
|
|
return !IsCommitted()
|
|
&& PlatformItBelongsTo != CookerLoadingPlatformKey
|
|
&& IsReachable(InReachability)
|
|
&& !(InReachability == EReachability::Runtime && !IsCookable());
|
|
}
|
|
|
|
FPackageData::FPackageData(FPackageDatas& PackageDatas, const FName& InPackageName, const FName& InFileName)
|
|
: ParentGenerationHelper(nullptr), PackageName(InPackageName), FileName(InFileName), PackageDatas(PackageDatas)
|
|
, Instigator(EInstigator::NotYetRequested), BuildInstigator(EInstigator::NotYetRequested)
|
|
, Urgency(static_cast<uint32>(EUrgency::Normal)), bIsCookLast(0)
|
|
, bIsVisited(0)
|
|
, bHasSaveCache(0), bPrepareSaveFailed(0), bPrepareSaveRequiresGC(0)
|
|
, MonitorCookResult((uint8)ECookResult::NotAttempted)
|
|
, bGenerated(0), bKeepReferencedDuringGC(0)
|
|
, bWasCookedThisSession(0)
|
|
, DoesGeneratedRequireGeneratorValue(static_cast<uint32>(ICookPackageSplitter::EGeneratedRequiresGenerator::None))
|
|
, bHasReplayedLogMessages(0)
|
|
{
|
|
SetState(EPackageState::Idle);
|
|
SetSaveSubState(ESaveSubState::StartSave);
|
|
SetSuppressCookReason(ESuppressCookReason::NotSuppressed);
|
|
|
|
SendToState(EPackageState::Idle, ESendFlags::QueueAdd, EStateChangeReason::Discovered);
|
|
}
|
|
|
|
FPackageData::~FPackageData()
|
|
{
|
|
// ClearReferences should have been called earlier, but call it here in case it was missed
|
|
ClearReferences();
|
|
// We need to send OnLastCookedPlatformRemoved message to the monitor, so call SetPlatformsNotCooked
|
|
ClearCookResults();
|
|
// Update the monitor's counters and call exit functions
|
|
SendToState(EPackageState::Idle, ESendFlags::QueueNone, EStateChangeReason::CookerShutdown);
|
|
|
|
// FPackageDatas guarantees that all references to GenerationHelper are removed before any PackageDatas are deleted.
|
|
// We rely on that so that we can be sure that when this PackageData is being deleted, its GenerationHelper - which
|
|
// assumes the FPackageData lifetime exceeds its own - has already been deleted.
|
|
check(GenerationHelper == nullptr);
|
|
// FPackageDatas guarantees that all references to PackagePreloaders are removed before any PackageDatas are deleted.
|
|
// We rely on that so that we can be sure that when this PackageData is being deleted, its PackagePreloader - which
|
|
// assumes the FPackageData lifetime exceeds its own - has already been deleted.
|
|
check(PackagePreloader == nullptr);
|
|
}
|
|
|
|
void FPackageData::ClearReferences()
|
|
{
|
|
if (GenerationHelper)
|
|
{
|
|
GenerationHelper->ClearSelfReferences();
|
|
}
|
|
SetParentGenerationHelper(nullptr, EStateChangeReason::CookerShutdown);
|
|
if (PackagePreloader)
|
|
{
|
|
PackagePreloader->Shutdown(); // Clears references to any other preloaders
|
|
}
|
|
ClearDiscoveredDependencies();
|
|
}
|
|
|
|
int32 FPackageData::GetPlatformsNeedingCommitNum(EReachability Reachability) const
|
|
{
|
|
int32 Result = 0;
|
|
for (const TPair<const ITargetPlatform*, FPackagePlatformData>& Pair : PlatformDatas)
|
|
{
|
|
if (Pair.Value.NeedsCommit(Pair.Key, Reachability))
|
|
{
|
|
++Result;
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
bool FPackageData::IsPlatformVisitedByCluster(const ITargetPlatform* Platform, EReachability InReachability) const
|
|
{
|
|
const FPackagePlatformData* PlatformData = FindPlatformData(Platform);
|
|
return PlatformData && PlatformData->IsVisitedByCluster(InReachability);
|
|
}
|
|
|
|
bool FPackageData::HasReachablePlatforms(EReachability InReachability,
|
|
const TArrayView<const ITargetPlatform* const>& Platforms) const
|
|
{
|
|
if (Platforms.Num() == 0)
|
|
{
|
|
return true;
|
|
}
|
|
if (PlatformDatas.Num() == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (const ITargetPlatform* QueryPlatform : Platforms)
|
|
{
|
|
const FPackagePlatformData* PlatformData = PlatformDatas.Find(QueryPlatform);
|
|
if (!PlatformData || !PlatformData->IsReachable(InReachability))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FPackageData::AreAllReachablePlatformsVisitedByCluster(EReachability InReachability) const
|
|
{
|
|
for (const TPair<const ITargetPlatform*, FPackagePlatformData>& Pair : PlatformDatas)
|
|
{
|
|
if (EnumHasAllFlags(Pair.Value.GetReachability(), InReachability) && !Pair.Value.IsVisitedByCluster(InReachability))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const TArray<const ITargetPlatform*>& FPackageData::GetSessionPlatformsInternal(UCookOnTheFlyServer& COTFS)
|
|
{
|
|
return COTFS.PlatformManager->GetSessionPlatforms();
|
|
}
|
|
|
|
void FPackageData::AddReachablePlatforms(FRequestCluster& RequestCluster, EReachability InReachability,
|
|
TConstArrayView<const ITargetPlatform*> Platforms, FInstigator&& InInstigator)
|
|
{
|
|
AddReachablePlatformsInternal(*this, InReachability, Platforms, MoveTemp(InInstigator));
|
|
}
|
|
|
|
void FPackageData::AddReachablePlatformsInternal(FPackageData& PackageData, EReachability InReachability,
|
|
TConstArrayView<const ITargetPlatform*> Platforms, FInstigator&& InInstigator)
|
|
{
|
|
// This is a static helper function to make it impossible to make a typo and use this->Instigator instead of
|
|
// InInstigator
|
|
bool bSessionPlatformModified = false;
|
|
for (const ITargetPlatform* Platform : Platforms)
|
|
{
|
|
FPackagePlatformData& PlatformData = PackageData.FindOrAddPlatformData(Platform);
|
|
bSessionPlatformModified |= (Platform != CookerLoadingPlatformKey && !PlatformData.IsReachable(InReachability));
|
|
PlatformData.AddReachability(InReachability);
|
|
}
|
|
if (bSessionPlatformModified)
|
|
{
|
|
PackageData.SetInstigatorInternal(InReachability, MoveTemp(InInstigator));
|
|
}
|
|
}
|
|
|
|
void FPackageData::QueueAsDiscovered(FInstigator&& InInstigator, FDiscoveredPlatformSet&& ReachablePlatforms,
|
|
EUrgency InUrgency)
|
|
{
|
|
QueueAsDiscoveredInternal(*this, MoveTemp(InInstigator), MoveTemp(ReachablePlatforms), InUrgency);
|
|
}
|
|
|
|
void FPackageData::QueueAsDiscoveredInternal(FPackageData& PackageData, FInstigator&& InInstigator,
|
|
FDiscoveredPlatformSet&& ReachablePlatforms, EUrgency InUrgency)
|
|
{
|
|
// This is a static helper function to make it impossible to make a typo and use this->Instigator instead of
|
|
// InInstigator
|
|
FPackageDatas& LocalPackageDatas = PackageData.PackageDatas;
|
|
FRequestQueue& RequestQueue = LocalPackageDatas.GetRequestQueue();
|
|
UCookOnTheFlyServer& COTFS = LocalPackageDatas.GetCookOnTheFlyServer();
|
|
if (InInstigator.Category != EInstigator::BuildDependency)
|
|
{
|
|
TRingBuffer<FDiscoveryQueueElement>& Queue = RequestQueue.GetDiscoveryQueue();
|
|
if (COTFS.GetCookPhase() == ECookPhase::BuildDependencies)
|
|
{
|
|
UE_LOG(LogCook, Warning, TEXT("Package was added to the runtime discovery queue after starting BuildDependencies phase.")
|
|
TEXT("\n\tPackage: %s"), *PackageData.GetPackageName().ToString());
|
|
constexpr int32 MaxCount = 5;
|
|
static int32 Count = 0;
|
|
if (Count++ < MaxCount)
|
|
{
|
|
FDebug::DumpStackTraceToLog(ELogVerbosity::Warning);
|
|
}
|
|
}
|
|
Queue.Add(FDiscoveryQueueElement{ &PackageData, MoveTemp(InInstigator), MoveTemp(ReachablePlatforms), InUrgency });
|
|
}
|
|
else
|
|
{
|
|
// Build dependencies always immediately mark the package as being reachable, rather than needing to wait for
|
|
// the discoveryqueue. Waiting for the discovery queue is only necessary for runtime dependencies because
|
|
// we need to know whether the package was expected.
|
|
TArray<const ITargetPlatform*, TInlineAllocator<ExpectedMaxNumPlatforms>> BufferPlatforms;
|
|
TConstArrayView<const ITargetPlatform*> PlatformArray =
|
|
ReachablePlatforms.GetPlatforms(COTFS, &InInstigator, TConstArrayView<const ITargetPlatform*>(),
|
|
EReachability::Build, BufferPlatforms);
|
|
bool bHasNewPlatforms = !PackageData.HasAllCommittedPlatforms(PlatformArray);
|
|
if (bHasNewPlatforms)
|
|
{
|
|
AddReachablePlatformsInternal(PackageData, EReachability::Build, PlatformArray, MoveTemp(InInstigator));
|
|
|
|
// If we have already kicked build dependencies, send the package to the discovery queue.
|
|
// Otherwise it will be added to the discoveryqueue when we kick build dependencies, if it hasn't been committed by then.
|
|
if (COTFS.GetCookPhase() == ECookPhase::BuildDependencies)
|
|
{
|
|
TRingBuffer<FPackageData*>& Queue = RequestQueue.GetBuildDependencyDiscoveryQueue();
|
|
Queue.Add(&PackageData);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPackageData::SetUrgency(EUrgency NewUrgency, ESendFlags SendFlags, bool bAllowUrgencyInIdle)
|
|
{
|
|
if (GetUrgency() == NewUrgency)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// It is illegal to SetUrgency to above normal when in the Idle state, unless the caller explicitly takes
|
|
// responsibility for changing the state immediately afterwards.
|
|
check(bAllowUrgencyInIdle || IsInProgress() || NewUrgency == EUrgency::Normal);
|
|
// For SendFlags when setting urgency, only AddAndRemove or None are supported
|
|
check(SendFlags == ESendFlags::QueueAddAndRemove || SendFlags == ESendFlags::QueueNone);
|
|
|
|
EUrgency OldUrgency = GetUrgency();
|
|
Urgency = static_cast<uint32>(NewUrgency);
|
|
if (SendFlags == ESendFlags::QueueAddAndRemove)
|
|
{
|
|
UpdateContainerUrgency(OldUrgency, NewUrgency);
|
|
}
|
|
PackageDatas.GetMonitor().OnUrgencyChanged(*this, OldUrgency, NewUrgency);
|
|
}
|
|
|
|
void FPackageData::SetIsCookLast(bool bValue)
|
|
{
|
|
bool bWasCookLast = GetIsCookLast();
|
|
if (bWasCookLast != bValue)
|
|
{
|
|
bIsCookLast = static_cast<uint32>(bValue);
|
|
PackageDatas.GetMonitor().OnCookLastChanged(*this);
|
|
}
|
|
}
|
|
|
|
void FPackageData::SetInstigator(FRequestCluster& Cluster, EReachability InReachability, FInstigator&& InInstigator)
|
|
{
|
|
SetInstigatorInternal(InReachability, MoveTemp(InInstigator));
|
|
}
|
|
|
|
void FPackageData::SetInstigator(FCookWorkerClient& Client, EReachability InReachability, FInstigator&& InInstigator)
|
|
{
|
|
SetInstigatorInternal(InReachability, MoveTemp(InInstigator));
|
|
}
|
|
|
|
void FPackageData::SetInstigator(FGenerationHelper& InHelper, EReachability InReachability, FInstigator&& InInstigator)
|
|
{
|
|
SetInstigatorInternal(InReachability, MoveTemp(InInstigator));
|
|
}
|
|
|
|
void FPackageData::SetInstigatorInternal(EReachability InReachability, FInstigator&& InInstigator)
|
|
{
|
|
if ((InReachability == EReachability::Runtime && this->Instigator.Category == EInstigator::NotYetRequested)
|
|
|| (InReachability == EReachability::Build && this->BuildInstigator.Category == EInstigator::NotYetRequested))
|
|
{
|
|
OnPackageDataFirstMarkedReachable(InReachability, MoveTemp(InInstigator));
|
|
}
|
|
}
|
|
|
|
void FPackageData::ClearInProgressData(EStateChangeReason StateChangeReason)
|
|
{
|
|
SetUrgency(EUrgency::Normal, ESendFlags::QueueNone);
|
|
CompletionCallback = FCompletionCallback();
|
|
if (GenerationHelper)
|
|
{
|
|
// ClearKeepForGeneratorSaveAllPlatforms might drop the last reference to the GenerationHelper, and delete
|
|
// it out from under the ClearKeepForGeneratorSaveAllPlatforms, which is not supported by
|
|
// ClearKeepForGeneratorSaveAllPlatforms, so keep it referenced across that call.
|
|
TRefCountPtr<FGenerationHelper> KeepReferenced = GenerationHelper;
|
|
// ClearKeepForGeneratorSave is called when finishing the save state, but not when demoting out of the save
|
|
// state after a garbage collect. Call it here in case we cancel the save of the packagedata after demotion.
|
|
// The other self-references (incremental, queued packages) should persist even when the packagedata is not
|
|
// in progress.
|
|
GenerationHelper->ClearKeepForGeneratorSaveAllPlatforms();
|
|
}
|
|
SetParentGenerationHelper(nullptr, StateChangeReason);
|
|
|
|
// Clear data that is no longer needed when we have comitted all platforms
|
|
if (HasAllCommittedPlatforms(PackageDatas.GetCookOnTheFlyServer().PlatformManager->GetSessionPlatforms()))
|
|
{
|
|
ClearLogMessages();
|
|
}
|
|
}
|
|
|
|
void FPackageData::SetPlatformsCooked(
|
|
const TConstArrayView<const ITargetPlatform*> TargetPlatforms,
|
|
const TConstArrayView<ECookResult> Result,
|
|
const bool bInWasCookedThisSession)
|
|
{
|
|
check(TargetPlatforms.Num() == Result.Num());
|
|
for (int32 n = 0; n < TargetPlatforms.Num(); ++n)
|
|
{
|
|
SetPlatformCooked(TargetPlatforms[n], Result[n], bInWasCookedThisSession);
|
|
}
|
|
}
|
|
|
|
void FPackageData::SetPlatformsCooked(
|
|
const TConstArrayView<const ITargetPlatform*> TargetPlatforms,
|
|
ECookResult Result,
|
|
const bool bInWasCookedThisSession)
|
|
{
|
|
for (const ITargetPlatform* TargetPlatform : TargetPlatforms)
|
|
{
|
|
SetPlatformCooked(TargetPlatform, Result, bInWasCookedThisSession);
|
|
}
|
|
}
|
|
|
|
void FPackageData::SetPlatformCooked(
|
|
const ITargetPlatform* TargetPlatform,
|
|
ECookResult CookResult,
|
|
const bool bInWasCookedThisSession)
|
|
{
|
|
bWasCookedThisSession |= bInWasCookedThisSession && (CookResult == ECookResult::Succeeded);
|
|
|
|
bool bNewCookAttemptedValue = (CookResult != ECookResult::NotAttempted);
|
|
bool bModifiedCookAttempted = false;
|
|
bool bHasAnyOtherCookAttempted = false;
|
|
bool bExists = false;
|
|
for (TPair<const ITargetPlatform*, FPackagePlatformData>& Pair : PlatformDatas)
|
|
{
|
|
if (Pair.Key == TargetPlatform)
|
|
{
|
|
bExists = true;
|
|
bModifiedCookAttempted = bModifiedCookAttempted | (Pair.Value.IsCookAttempted() != bNewCookAttemptedValue);
|
|
Pair.Value.SetCookResults(CookResult);
|
|
// Clear the SaveTimedOut when get a cook result, in case we save again later and need to allow retry again
|
|
Pair.Value.SetSaveTimedOut(false);
|
|
}
|
|
else
|
|
{
|
|
bHasAnyOtherCookAttempted = bHasAnyOtherCookAttempted | Pair.Value.IsCookAttempted();
|
|
}
|
|
}
|
|
|
|
if (!bExists && bNewCookAttemptedValue)
|
|
{
|
|
FPackagePlatformData& Value = PlatformDatas.FindOrAdd(TargetPlatform);
|
|
Value.SetCookResults(CookResult);
|
|
Value.SetSaveTimedOut(false);
|
|
bModifiedCookAttempted = true;
|
|
}
|
|
|
|
if (bModifiedCookAttempted && !bHasAnyOtherCookAttempted)
|
|
{
|
|
if (bNewCookAttemptedValue)
|
|
{
|
|
PackageDatas.GetMonitor().OnFirstCookedPlatformAdded(*this, CookResult);
|
|
}
|
|
else
|
|
{
|
|
bWasCookedThisSession = false;
|
|
PackageDatas.GetMonitor().OnLastCookedPlatformRemoved(*this);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPackageData::SetPlatformCommitted(const ITargetPlatform* TargetPlatform)
|
|
{
|
|
FPackagePlatformData& Value = PlatformDatas.FindOrAdd(TargetPlatform);
|
|
Value.SetCommitted(true);
|
|
}
|
|
|
|
void FPackageData::ClearCookResults(const TConstArrayView<const ITargetPlatform*> TargetPlatforms)
|
|
{
|
|
for (const ITargetPlatform* TargetPlatform : TargetPlatforms)
|
|
{
|
|
ClearCookResults(TargetPlatform);
|
|
}
|
|
}
|
|
|
|
void FPackageData::ResetReachable(EReachability InReachability)
|
|
{
|
|
for (TPair<const ITargetPlatform*, FPackagePlatformData>& Pair : PlatformDatas)
|
|
{
|
|
Pair.Value.ResetReachable(InReachability);
|
|
}
|
|
}
|
|
|
|
void FPackageData::ClearCookResults()
|
|
{
|
|
bool bModifiedCookAttempted = false;
|
|
for (TPair<const ITargetPlatform*, FPackagePlatformData>& Pair : PlatformDatas)
|
|
{
|
|
bModifiedCookAttempted = bModifiedCookAttempted | Pair.Value.IsCookAttempted();
|
|
Pair.Value.SetCookResults(ECookResult::NotAttempted);
|
|
Pair.Value.SetCommitted(false);
|
|
Pair.Value.SetSaveTimedOut(false);
|
|
}
|
|
if (bModifiedCookAttempted)
|
|
{
|
|
bWasCookedThisSession = false;
|
|
PackageDatas.GetMonitor().OnLastCookedPlatformRemoved(*this);
|
|
}
|
|
SetSuppressCookReason(ESuppressCookReason::NotSuppressed);
|
|
bHasReplayedLogMessages = false;
|
|
}
|
|
|
|
void FPackageData::ClearCookResults(const ITargetPlatform* TargetPlatform)
|
|
{
|
|
bool bHasAnyOthers = false;
|
|
bool bModifiedCookAttempted = false;
|
|
for (TPair<const ITargetPlatform*, FPackagePlatformData>& Pair : PlatformDatas)
|
|
{
|
|
if (Pair.Key == TargetPlatform)
|
|
{
|
|
bModifiedCookAttempted = bModifiedCookAttempted | Pair.Value.IsCookAttempted();
|
|
Pair.Value.SetCookResults(ECookResult::NotAttempted);
|
|
Pair.Value.SetCommitted(false);
|
|
Pair.Value.SetSaveTimedOut(false);
|
|
}
|
|
else
|
|
{
|
|
bHasAnyOthers = bHasAnyOthers | Pair.Value.IsCookAttempted();
|
|
}
|
|
}
|
|
if (bModifiedCookAttempted && !bHasAnyOthers)
|
|
{
|
|
bWasCookedThisSession = false;
|
|
PackageDatas.GetMonitor().OnLastCookedPlatformRemoved(*this);
|
|
bHasReplayedLogMessages = false;
|
|
}
|
|
}
|
|
|
|
const TSortedMap<const ITargetPlatform*, FPackagePlatformData, TInlineAllocator<1>>&
|
|
FPackageData::GetPlatformDatas() const
|
|
{
|
|
return PlatformDatas;
|
|
}
|
|
|
|
TSortedMap<const ITargetPlatform*, FPackagePlatformData, TInlineAllocator<1>>&
|
|
FPackageData::GetPlatformDatasConstKeysMutableValues()
|
|
{
|
|
return PlatformDatas;
|
|
}
|
|
|
|
FPackagePlatformData& FPackageData::FindOrAddPlatformData(const ITargetPlatform* TargetPlatform)
|
|
{
|
|
return PlatformDatas.FindOrAdd(TargetPlatform);
|
|
}
|
|
|
|
FPackagePlatformData* FPackageData::FindPlatformData(const ITargetPlatform* TargetPlatform)
|
|
{
|
|
return PlatformDatas.Find(TargetPlatform);
|
|
}
|
|
|
|
const FPackagePlatformData* FPackageData::FindPlatformData(const ITargetPlatform* TargetPlatform) const
|
|
{
|
|
return PlatformDatas.Find(TargetPlatform);
|
|
}
|
|
|
|
bool FPackageData::HasAnyCookedPlatform() const
|
|
{
|
|
return Algo::AnyOf(PlatformDatas,
|
|
[](const TPair<const ITargetPlatform*, FPackagePlatformData>& Pair)
|
|
{
|
|
return Pair.Key != CookerLoadingPlatformKey && Pair.Value.IsCookAttempted();
|
|
});
|
|
}
|
|
|
|
bool FPackageData::HasAnyCommittedPlatforms() const
|
|
{
|
|
return Algo::AnyOf(PlatformDatas,
|
|
[](const TPair<const ITargetPlatform*, FPackagePlatformData>& Pair)
|
|
{
|
|
return Pair.Key != CookerLoadingPlatformKey && Pair.Value.IsCommitted();
|
|
});
|
|
}
|
|
|
|
bool FPackageData::HasAnyCookedPlatforms(const TArrayView<const ITargetPlatform* const>& Platforms,
|
|
bool bIncludeFailed) const
|
|
{
|
|
if (PlatformDatas.Num() == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (const ITargetPlatform* QueryPlatform : Platforms)
|
|
{
|
|
if (HasCookedPlatform(QueryPlatform, bIncludeFailed))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FPackageData::HasAllCookedPlatforms(const TArrayView<const ITargetPlatform* const>& Platforms,
|
|
bool bIncludeFailed) const
|
|
{
|
|
if (Platforms.Num() == 0)
|
|
{
|
|
return true;
|
|
}
|
|
if (PlatformDatas.Num() == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (const ITargetPlatform* QueryPlatform : Platforms)
|
|
{
|
|
if (!HasCookedPlatform(QueryPlatform, bIncludeFailed))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FPackageData::HasCookedPlatform(const ITargetPlatform* Platform, bool bIncludeFailed) const
|
|
{
|
|
ECookResult Result = GetCookResults(Platform);
|
|
return (Result == ECookResult::Succeeded) | ((Result != ECookResult::NotAttempted) & (bIncludeFailed != 0));
|
|
}
|
|
|
|
ECookResult FPackageData::GetCookResults(const ITargetPlatform* Platform) const
|
|
{
|
|
const FPackagePlatformData* PlatformData = PlatformDatas.Find(Platform);
|
|
if (PlatformData)
|
|
{
|
|
return PlatformData->GetCookResults();
|
|
}
|
|
return ECookResult::NotAttempted;
|
|
}
|
|
|
|
bool FPackageData::HasAllCommittedPlatforms(const TArrayView<const ITargetPlatform* const>& Platforms) const
|
|
{
|
|
if (Platforms.Num() == 0)
|
|
{
|
|
return true;
|
|
}
|
|
if (PlatformDatas.Num() == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (const ITargetPlatform* QueryPlatform : Platforms)
|
|
{
|
|
if (!HasCommittedPlatform(QueryPlatform))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FPackageData::HasCommittedPlatform(const ITargetPlatform* Platform) const
|
|
{
|
|
const FPackagePlatformData* PlatformData = PlatformDatas.Find(Platform);
|
|
if (PlatformData)
|
|
{
|
|
return PlatformData->IsCommitted();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
UPackage* FPackageData::GetPackage() const
|
|
{
|
|
return Package.Get();
|
|
}
|
|
|
|
void FPackageData::SetPackage(UPackage* InPackage)
|
|
{
|
|
Package = InPackage;
|
|
}
|
|
|
|
EPackageState FPackageData::GetState() const
|
|
{
|
|
return static_cast<EPackageState>(State);
|
|
}
|
|
|
|
/** Boilerplate-reduction struct that defines all multi-state properties and sets them based on the given state. */
|
|
struct FStateProperties
|
|
{
|
|
EPackageStateProperty Properties;
|
|
explicit FStateProperties(EPackageState InState)
|
|
{
|
|
switch (InState)
|
|
{
|
|
case EPackageState::Idle:
|
|
Properties = EPackageStateProperty::None;
|
|
break;
|
|
case EPackageState::Request:
|
|
Properties = EPackageStateProperty::InProgress;
|
|
break;
|
|
case EPackageState::AssignedToWorker:
|
|
Properties = EPackageStateProperty::InProgress | EPackageStateProperty::AssignedToWorkerProperty;
|
|
break;
|
|
case EPackageState::Load:
|
|
Properties = EPackageStateProperty::InProgress;
|
|
break;
|
|
case EPackageState::SaveActive:
|
|
Properties = EPackageStateProperty::InProgress | EPackageStateProperty::Saving;
|
|
break;
|
|
case EPackageState::SaveStalledRetracted:
|
|
Properties = EPackageStateProperty::InProgress | EPackageStateProperty::Saving;
|
|
break;
|
|
case EPackageState::SaveStalledAssignedToWorker:
|
|
Properties = EPackageStateProperty::InProgress | EPackageStateProperty::Saving
|
|
| EPackageStateProperty::AssignedToWorkerProperty;
|
|
break;
|
|
default:
|
|
check(false);
|
|
Properties = EPackageStateProperty::None;
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
void FPackageData::SendToState(EPackageState NextState, ESendFlags SendFlags, EStateChangeReason ReleaseSaveReason)
|
|
{
|
|
EPackageState OldState = GetState();
|
|
switch (OldState)
|
|
{
|
|
case EPackageState::Idle:
|
|
OnExitIdle();
|
|
break;
|
|
case EPackageState::Request:
|
|
if (!!(SendFlags & ESendFlags::QueueRemove))
|
|
{
|
|
ensure(PackageDatas.GetRequestQueue().Remove(this) == 1);
|
|
}
|
|
OnExitRequest();
|
|
break;
|
|
case EPackageState::AssignedToWorker:
|
|
if (!!(SendFlags & ESendFlags::QueueRemove))
|
|
{
|
|
ensure(PackageDatas.GetAssignedToWorkerSet().Remove(this) == 1);
|
|
}
|
|
OnExitAssignedToWorker();
|
|
break;
|
|
case EPackageState::Load:
|
|
if (!!(SendFlags & ESendFlags::QueueRemove))
|
|
{
|
|
ensure(PackageDatas.GetLoadQueue().Remove(this) == 1);
|
|
}
|
|
OnExitLoad();
|
|
break;
|
|
case EPackageState::SaveActive:
|
|
if (!!(SendFlags & ESendFlags::QueueRemove))
|
|
{
|
|
ensure(PackageDatas.GetSaveQueue().Remove(this) == 1);
|
|
}
|
|
OnExitSaveActive();
|
|
break;
|
|
case EPackageState::SaveStalledRetracted:
|
|
if (!!(SendFlags & ESendFlags::QueueRemove))
|
|
{
|
|
ensure(PackageDatas.GetSaveStalledSet().Remove(this) == 1);
|
|
}
|
|
OnExitSaveStalledRetracted();
|
|
break;
|
|
case EPackageState::SaveStalledAssignedToWorker:
|
|
if (!!(SendFlags & ESendFlags::QueueRemove))
|
|
{
|
|
ensure(PackageDatas.GetSaveStalledSet().Remove(this) == 1);
|
|
}
|
|
OnExitSaveStalledAssignedToWorker();
|
|
break;
|
|
default:
|
|
check(false);
|
|
break;
|
|
}
|
|
|
|
FStateProperties OldProperties(OldState);
|
|
FStateProperties NewProperties(NextState);
|
|
// Exit state properties from highest to lowest; enter state properties from lowest to highest.
|
|
// This ensures that properties that rely on earlier properties are constructed later and torn down earlier
|
|
// than the earlier properties.
|
|
for (EPackageStateProperty Iterator = EPackageStateProperty::Max;
|
|
Iterator >= EPackageStateProperty::Min;
|
|
Iterator = static_cast<EPackageStateProperty>(static_cast<uint32>(Iterator) >> 1))
|
|
{
|
|
if (((OldProperties.Properties & Iterator) != EPackageStateProperty::None) &
|
|
((NewProperties.Properties & Iterator) == EPackageStateProperty::None))
|
|
{
|
|
switch (Iterator)
|
|
{
|
|
case EPackageStateProperty::InProgress:
|
|
OnExitInProgress(ReleaseSaveReason);
|
|
break;
|
|
case EPackageStateProperty::Saving:
|
|
OnExitSaving(ReleaseSaveReason, NextState);
|
|
break;
|
|
case EPackageStateProperty::AssignedToWorkerProperty:
|
|
OnExitAssignedToWorkerProperty();
|
|
break;
|
|
default:
|
|
check(false);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for (EPackageStateProperty Iterator = EPackageStateProperty::Min;
|
|
Iterator <= EPackageStateProperty::Max;
|
|
Iterator = static_cast<EPackageStateProperty>(static_cast<uint32>(Iterator) << 1))
|
|
{
|
|
if (((OldProperties.Properties & Iterator) == EPackageStateProperty::None) &
|
|
((NewProperties.Properties & Iterator) != EPackageStateProperty::None))
|
|
{
|
|
switch (Iterator)
|
|
{
|
|
case EPackageStateProperty::InProgress:
|
|
OnEnterInProgress();
|
|
break;
|
|
case EPackageStateProperty::Saving:
|
|
OnEnterSaving();
|
|
break;
|
|
case EPackageStateProperty::AssignedToWorkerProperty:
|
|
OnEnterAssignedToWorkerProperty();
|
|
break;
|
|
default:
|
|
check(false);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
SetState(NextState);
|
|
switch (NextState)
|
|
{
|
|
case EPackageState::Idle:
|
|
OnEnterIdle();
|
|
break;
|
|
case EPackageState::Request:
|
|
OnEnterRequest();
|
|
if (((SendFlags & ESendFlags::QueueAdd) != ESendFlags::QueueNone))
|
|
{
|
|
PackageDatas.GetRequestQueue().AddRequest(this);
|
|
}
|
|
break;
|
|
case EPackageState::AssignedToWorker:
|
|
OnEnterAssignedToWorker();
|
|
if (((SendFlags & ESendFlags::QueueAdd) != ESendFlags::QueueNone))
|
|
{
|
|
PackageDatas.GetAssignedToWorkerSet().Add(this);
|
|
}
|
|
break;
|
|
case EPackageState::Load:
|
|
OnEnterLoad();
|
|
if ((SendFlags & ESendFlags::QueueAdd) != ESendFlags::QueueNone)
|
|
{
|
|
PackageDatas.GetLoadQueue().Add(this);
|
|
}
|
|
break;
|
|
case EPackageState::SaveActive:
|
|
OnEnterSaveActive();
|
|
if (((SendFlags & ESendFlags::QueueAdd) != ESendFlags::QueueNone))
|
|
{
|
|
if (GetUrgency() > EUrgency::Normal)
|
|
{
|
|
PackageDatas.GetSaveQueue().AddFront(this);
|
|
}
|
|
else
|
|
{
|
|
PackageDatas.GetSaveQueue().Add(this);
|
|
}
|
|
}
|
|
break;
|
|
case EPackageState::SaveStalledRetracted:
|
|
OnEnterSaveStalledRetracted();
|
|
if (((SendFlags & ESendFlags::QueueAdd) != ESendFlags::QueueNone))
|
|
{
|
|
PackageDatas.GetSaveStalledSet().Add(this);
|
|
}
|
|
break;
|
|
case EPackageState::SaveStalledAssignedToWorker:
|
|
OnEnterSaveStalledAssignedToWorker();
|
|
if (((SendFlags & ESendFlags::QueueAdd) != ESendFlags::QueueNone))
|
|
{
|
|
PackageDatas.GetSaveStalledSet().Add(this);
|
|
}
|
|
break;
|
|
default:
|
|
check(false);
|
|
break;
|
|
}
|
|
|
|
PackageDatas.GetMonitor().OnStateChanged(*this, OldState);
|
|
}
|
|
|
|
void FPackageData::UpdateContainerUrgency(EUrgency OldUrgency, EUrgency NewUrgency)
|
|
{
|
|
switch (GetState())
|
|
{
|
|
case EPackageState::Idle:
|
|
// Urgency does not affect behavior in the Idle state
|
|
break;
|
|
case EPackageState::Request:
|
|
PackageDatas.GetRequestQueue().UpdateUrgency(this, OldUrgency, NewUrgency);
|
|
break;
|
|
case EPackageState::AssignedToWorker:
|
|
// Urgency does not affect behavior in the AssignedToWorker state
|
|
break;
|
|
case EPackageState::Load:
|
|
PackageDatas.GetLoadQueue().UpdateUrgency(this, OldUrgency, NewUrgency);
|
|
break;
|
|
case EPackageState::SaveActive:
|
|
if (NewUrgency > EUrgency::Normal)
|
|
{
|
|
FPackageDataQueue& Queue = PackageDatas.GetSaveQueue();
|
|
if (Queue.Remove(this) > 0)
|
|
{
|
|
Queue.AddFront(this);
|
|
}
|
|
}
|
|
break;
|
|
case EPackageState::SaveStalledRetracted:
|
|
// Urgency does not affect behavior in stalled states
|
|
break;
|
|
case EPackageState::SaveStalledAssignedToWorker:
|
|
// Urgency does not affect behavior in stalled states
|
|
break;
|
|
default:
|
|
check(false);
|
|
break;
|
|
}
|
|
|
|
// The Package preloader can be active in any state, and is contained in the LoadQueue.
|
|
// If it exists and we did not already call UpdateUrgency on the LoadQueue, then call it.
|
|
if (GetState() != EPackageState::Load && GetPackagePreloader())
|
|
{
|
|
PackageDatas.GetLoadQueue().UpdateUrgency(this, OldUrgency, NewUrgency);
|
|
}
|
|
}
|
|
|
|
void FPackageData::Stall(EPackageState TargetState, ESendFlags SendFlags)
|
|
{
|
|
switch (TargetState)
|
|
{
|
|
case EPackageState::SaveStalledAssignedToWorker:
|
|
case EPackageState::SaveStalledRetracted:
|
|
if (GetState() != EPackageState::SaveActive)
|
|
{
|
|
return;
|
|
}
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
SendToState(TargetState, SendFlags, EStateChangeReason::Retraction);
|
|
}
|
|
|
|
void FPackageData::UnStall(ESendFlags SendFlags)
|
|
{
|
|
EPackageState TargetState = EPackageState::Idle;
|
|
|
|
switch (GetState())
|
|
{
|
|
case EPackageState::SaveStalledAssignedToWorker:
|
|
case EPackageState::SaveStalledRetracted:
|
|
TargetState = EPackageState::SaveActive;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
UE_LOG(LogCook, Display, TEXT("Unstalling package %s; it will resume saving from the point at which it was retracted."),
|
|
*WriteToString<256>(GetPackageName()));
|
|
SendToState(TargetState, SendFlags, EStateChangeReason::Retraction);
|
|
}
|
|
|
|
bool FPackageData::IsStalled() const
|
|
{
|
|
switch (GetState())
|
|
{
|
|
case EPackageState::SaveStalledAssignedToWorker:
|
|
case EPackageState::SaveStalledRetracted:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void FPackageData::CheckInContainer() const
|
|
{
|
|
switch (GetState())
|
|
{
|
|
case EPackageState::Idle:
|
|
break;
|
|
case EPackageState::Request:
|
|
check(PackageDatas.GetRequestQueue().Contains(this));
|
|
break;
|
|
case EPackageState::AssignedToWorker:
|
|
check(PackageDatas.GetAssignedToWorkerSet().Contains(this));
|
|
break;
|
|
case EPackageState::Load:
|
|
check(PackageDatas.GetLoadQueue().Contains(this));
|
|
break;
|
|
case EPackageState::SaveActive:
|
|
// The save queue is huge and often pushed at end. Check last element first and then scan.
|
|
check(PackageDatas.GetSaveQueue().Num() && (PackageDatas.GetSaveQueue().Last() == this
|
|
|| Algo::Find(PackageDatas.GetSaveQueue(), this)));
|
|
break;
|
|
case EPackageState::SaveStalledRetracted:
|
|
check(PackageDatas.GetSaveStalledSet().Contains(this));
|
|
break;
|
|
case EPackageState::SaveStalledAssignedToWorker:
|
|
check(PackageDatas.GetSaveStalledSet().Contains(this));
|
|
break;
|
|
default:
|
|
check(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool FPackageData::IsInProgress() const
|
|
{
|
|
return IsInStateProperty(EPackageStateProperty::InProgress);
|
|
}
|
|
|
|
bool FPackageData::IsInStateProperty(EPackageStateProperty Property) const
|
|
{
|
|
return (FStateProperties(GetState()).Properties & Property) != EPackageStateProperty::None;
|
|
}
|
|
|
|
void FPackageData::OnEnterIdle()
|
|
{
|
|
// Note that this might be on construction of the PackageData
|
|
}
|
|
|
|
void FPackageData::OnExitIdle()
|
|
{
|
|
}
|
|
|
|
void FPackageData::OnEnterRequest()
|
|
{
|
|
}
|
|
|
|
void FPackageData::OnExitRequest()
|
|
{
|
|
}
|
|
|
|
void FPackageData::OnEnterAssignedToWorker()
|
|
{
|
|
if (IsGenerated())
|
|
{
|
|
// Clear the referencecount that we added in OnEnterInProgress; we don't want to keep the GenerationHelper
|
|
// referenced for the entire duration of assigned packages running on other CookWorkers. If this package gets
|
|
// retracted and moved into LoadState locally, we will recreate the GenerationHelper if necessary.
|
|
// Since we have set the ParentGenerationHelper to null, we can no automatically longer report to the
|
|
// GenerationHelper that the package has saved when it transitions to Idle. Reporting to the GenerationHelper
|
|
// that this FPackageData has saved is now the responsibility of the CookWorkerServer's RecordResults function.
|
|
SetParentGenerationHelper(nullptr, EStateChangeReason::Retraction);
|
|
}
|
|
}
|
|
|
|
void FPackageData::SetWorkerAssignment(FWorkerId InWorkerAssignment, ESendFlags SendFlags)
|
|
{
|
|
if (WorkerAssignment.IsValid())
|
|
{
|
|
checkf(InWorkerAssignment.IsInvalid(),
|
|
TEXT("Package %s is being assigned to worker %d while it is already assigned to worker %d."),
|
|
*GetPackageName().ToString(), WorkerAssignment.GetRemoteIndex(), WorkerAssignment.GetRemoteIndex());
|
|
if (EnumHasAnyFlags(SendFlags, ESendFlags::QueueRemove))
|
|
{
|
|
PackageDatas.GetCookOnTheFlyServer().NotifyRemovedFromWorker(*this);
|
|
}
|
|
WorkerAssignment = FWorkerId::Invalid();
|
|
}
|
|
else
|
|
{
|
|
if (InWorkerAssignment.IsValid())
|
|
{
|
|
checkf(IsInStateProperty(EPackageStateProperty::AssignedToWorkerProperty),
|
|
TEXT("Package %s is being assigned to worker %d while in state %s, which is not an AssignedToWorker state. This is invalid."),
|
|
*GetPackageName().ToString(), GetWorkerAssignment().GetRemoteIndex(), LexToString(GetState()));
|
|
}
|
|
WorkerAssignment = InWorkerAssignment;
|
|
}
|
|
}
|
|
|
|
void FPackageData::OnExitAssignedToWorker()
|
|
{
|
|
}
|
|
|
|
void FPackageData::OnEnterLoad()
|
|
{
|
|
TRefCountPtr<FPackagePreloader> Local = CreatePackagePreloader();
|
|
Local->SetSelfReference();
|
|
check(PackagePreloader);
|
|
}
|
|
|
|
void FPackageData::OnExitLoad()
|
|
{
|
|
check(PackagePreloader); // Guaranteed by OnEnterLoad
|
|
PackagePreloader->OnPackageLeaveLoadState();
|
|
PackagePreloader->ClearSelfReference();
|
|
// PackagePreloader might now be nullptr
|
|
}
|
|
|
|
void FPackageData::OnEnterSaveActive()
|
|
{
|
|
}
|
|
|
|
void FPackageData::OnExitSaveActive()
|
|
{
|
|
}
|
|
|
|
void FPackageData::OnEnterSaveStalledRetracted()
|
|
{
|
|
}
|
|
|
|
void FPackageData::OnExitSaveStalledRetracted()
|
|
{
|
|
}
|
|
|
|
void FPackageData::OnEnterSaveStalledAssignedToWorker()
|
|
{
|
|
}
|
|
|
|
void FPackageData::OnExitSaveStalledAssignedToWorker()
|
|
{
|
|
}
|
|
|
|
void FPackageData::OnEnterInProgress()
|
|
{
|
|
PackageDatas.GetMonitor().OnInProgressChanged(*this, true);
|
|
if (IsGenerated())
|
|
{
|
|
// Keep a refcount to the ParentGenerationHelper until we are saved so that it does not destruct
|
|
// and waste time reconstructing when we reach the LoadQueue.
|
|
GetOrFindParentGenerationHelper();
|
|
}
|
|
}
|
|
|
|
void FPackageData::OnExitInProgress(EStateChangeReason StateChangeReason)
|
|
{
|
|
PackageDatas.GetMonitor().OnInProgressChanged(*this, false);
|
|
UE::Cook::FCompletionCallback LocalCompletionCallback(MoveTemp(GetCompletionCallback()));
|
|
if (LocalCompletionCallback)
|
|
{
|
|
LocalCompletionCallback(this);
|
|
}
|
|
ClearInProgressData(StateChangeReason);
|
|
}
|
|
|
|
void FPackageData::OnEnterSaving()
|
|
{
|
|
check(GetPackage() != nullptr && GetPackage()->IsFullyLoaded());
|
|
check(GetLoadDependencies() != nullptr);
|
|
|
|
check(!HasPrepareSaveFailed());
|
|
CheckObjectCacheEmpty();
|
|
CheckCookedPlatformDataEmpty();
|
|
}
|
|
|
|
void FPackageData::OnExitSaving(EStateChangeReason ReleaseSaveReason, EPackageState NewState)
|
|
{
|
|
PackageDatas.GetCookOnTheFlyServer().ReleaseCookedPlatformData(*this, ReleaseSaveReason, NewState);
|
|
ClearObjectCache();
|
|
SetHasPrepareSaveFailed(false);
|
|
SetIsPrepareSaveRequiresGC(false);
|
|
SetPackage(nullptr);
|
|
}
|
|
|
|
void FPackageData::OnPackageDataFirstMarkedReachable(EReachability InReachability, FInstigator&& InInstigator)
|
|
{
|
|
if (InReachability == EReachability::Runtime)
|
|
{
|
|
TracePackage(GetPackageName().ToUnstableInt(), GetPackageName().ToString());
|
|
Instigator = MoveTemp(InInstigator);
|
|
PackageDatas.DebugInstigator(*this);
|
|
PackageDatas.UpdateThreadsafePackageData(*this);
|
|
}
|
|
else
|
|
{
|
|
check(InReachability == EReachability::Build);
|
|
BuildInstigator = MoveTemp(InInstigator);
|
|
}
|
|
}
|
|
|
|
void FPackageData::OnEnterAssignedToWorkerProperty()
|
|
{
|
|
}
|
|
|
|
void FPackageData::OnExitAssignedToWorkerProperty()
|
|
{
|
|
SetWorkerAssignment(FWorkerId::Invalid());
|
|
}
|
|
|
|
void FPackageData::SetState(EPackageState NextState)
|
|
{
|
|
State = static_cast<uint32>(NextState);
|
|
}
|
|
|
|
FCompletionCallback& FPackageData::GetCompletionCallback()
|
|
{
|
|
return CompletionCallback;
|
|
}
|
|
|
|
void FPackageData::AddCompletionCallback(TConstArrayView<const ITargetPlatform*> TargetPlatforms,
|
|
FCompletionCallback&& InCompletionCallback)
|
|
{
|
|
if (!InCompletionCallback)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (const ITargetPlatform* TargetPlatform : TargetPlatforms)
|
|
{
|
|
FPackagePlatformData* PlatformData = FindPlatformData(TargetPlatform);
|
|
// Adding a completion callback is only allowed after marking the requested platforms as runtime reachable
|
|
check(PlatformData);
|
|
check(PlatformData->IsReachable(EReachability::Runtime));
|
|
// Adding a completion callback is only allowed after putting the PackageData in progress.
|
|
// If it's not in progress because it already finished the desired platforms, that is allowed.
|
|
check(IsInProgress() || PlatformData->IsCookAttempted() || !PlatformData->IsCookable());
|
|
}
|
|
|
|
if (IsInProgress())
|
|
{
|
|
// We don't yet have a mechanism for calling two completion callbacks.
|
|
// CompletionCallbacks only come from external requests, and it should not be possible to request twice,
|
|
// so a failed check here shouldn't happen.
|
|
check(!CompletionCallback);
|
|
CompletionCallback = MoveTemp(InCompletionCallback);
|
|
}
|
|
else
|
|
{
|
|
// Already done; call the completioncallback immediately
|
|
InCompletionCallback(this);
|
|
}
|
|
}
|
|
|
|
TRefCountPtr<FPackagePreloader> FPackageData::GetPackagePreloader() const
|
|
{
|
|
return TRefCountPtr<FPackagePreloader>(PackagePreloader);
|
|
}
|
|
|
|
TRefCountPtr<FPackagePreloader> FPackageData::CreatePackagePreloader()
|
|
{
|
|
if (PackagePreloader)
|
|
{
|
|
return TRefCountPtr<FPackagePreloader>(PackagePreloader);
|
|
}
|
|
TRefCountPtr<FPackagePreloader> Result(new FPackagePreloader(*this));
|
|
PackagePreloader = Result.GetReference();
|
|
return Result;
|
|
}
|
|
|
|
void FPackageData::OnPackagePreloaderDestroyed(FPackagePreloader& InPackagePreloader)
|
|
{
|
|
check(PackagePreloader == &InPackagePreloader);
|
|
PackagePreloader = nullptr;
|
|
}
|
|
|
|
const FBuildResultDependenciesMap* FPackageData::GetLoadDependencies() const
|
|
{
|
|
return LoadDependencies.Get();
|
|
}
|
|
|
|
void FPackageData::CreateLoadDependencies()
|
|
{
|
|
if (!LoadDependencies)
|
|
{
|
|
UPackage* LocalPackage = Package.Get();
|
|
checkf(LocalPackage != nullptr,
|
|
TEXT("CreateLoadDependencies failed for package %s because this->Package == nullptr. It can only be called after the Package has been set."),
|
|
*GetPackageName().ToString());
|
|
LoadDependencies.Reset(new FBuildResultDependenciesMap(FBuildDependencySet::CollectLoadedPackage(LocalPackage)));
|
|
}
|
|
}
|
|
|
|
void FPackageData::ClearLoadDependencies()
|
|
{
|
|
LoadDependencies.Reset();
|
|
}
|
|
|
|
TArray<FCachedObjectInOuter>& FPackageData::GetCachedObjectsInOuter()
|
|
{
|
|
return CachedObjectsInOuter;
|
|
}
|
|
|
|
const TArray<FCachedObjectInOuter>& FPackageData::GetCachedObjectsInOuter() const
|
|
{
|
|
return CachedObjectsInOuter;
|
|
}
|
|
|
|
void FPackageData::CheckObjectCacheEmpty() const
|
|
{
|
|
check(CachedObjectsInOuter.Num() == 0);
|
|
check(!GetHasSaveCache());
|
|
}
|
|
|
|
void FPackageData::CreateObjectCache()
|
|
{
|
|
if (GetHasSaveCache())
|
|
{
|
|
return;
|
|
}
|
|
|
|
UPackage* LocalPackage = GetPackage();
|
|
if (LocalPackage && LocalPackage->IsFullyLoaded())
|
|
{
|
|
PackageName = LocalPackage->GetFName();
|
|
TArray<UObject*> ObjectsInOuter;
|
|
// ignore RF_Garbage objects; they will not be serialized out so we don't need to call
|
|
// BeginCacheForCookedPlatformData on them
|
|
GetObjectsWithOuter(LocalPackage, ObjectsInOuter, true /* bIncludeNestedObjects */, RF_NoFlags,
|
|
EInternalObjectFlags::Garbage);
|
|
CachedObjectsInOuter.Reset(ObjectsInOuter.Num());
|
|
for (UObject* Object : ObjectsInOuter)
|
|
{
|
|
FWeakObjectPtr ObjectWeakPointer(Object);
|
|
// GetObjectsWithOuter with Garbage filtered out should only return valid-for-weakptr objects
|
|
check(ObjectWeakPointer.Get());
|
|
CachedObjectsInOuter.Emplace(ObjectWeakPointer);
|
|
}
|
|
|
|
for (TPair<const ITargetPlatform*, FPackagePlatformData>& Pair : PlatformDatas)
|
|
{
|
|
FPackagePlatformData& PlatformData = Pair.Value;
|
|
check(!PlatformData.IsRegisteredForCachedObjectsInOuter());
|
|
if (PlatformData.NeedsCooking(Pair.Key))
|
|
{
|
|
PlatformData.SetRegisteredForCachedObjectsInOuter(true);
|
|
}
|
|
}
|
|
|
|
SetHasSaveCache(true);
|
|
}
|
|
else
|
|
{
|
|
check(false);
|
|
}
|
|
}
|
|
|
|
static TArray<UObject*> SetDifference(TArray<UObject*>& A, TArray<UObject*>& B)
|
|
{
|
|
Algo::Sort(A); // Don't use TArray.Sort, it sorts pointers as references and we want to sort them as pointers
|
|
Algo::Sort(B);
|
|
int32 ANum = A.Num();
|
|
int32 BNum = B.Num();
|
|
UObject** AData = A.GetData();
|
|
UObject** BData = B.GetData();
|
|
|
|
// Always move to the smallest next element from the two remaining lists and if it's in one set and not the
|
|
// other add it to the output if in A or skip it if in B.
|
|
int32 AIndex = 0;
|
|
int32 BIndex = 0;
|
|
TArray<UObject*> AMinusB;
|
|
while (AIndex < ANum && BIndex < BNum)
|
|
{
|
|
if (AData[AIndex] == BData[BIndex])
|
|
{
|
|
++AIndex;
|
|
++BIndex;
|
|
continue;
|
|
}
|
|
if (AData[AIndex] < BData[BIndex])
|
|
{
|
|
AMinusB.Add(AData[AIndex++]);
|
|
}
|
|
else
|
|
{
|
|
++BIndex;
|
|
}
|
|
}
|
|
|
|
// When we reach the end of B, all remaining elements of A are not in B.
|
|
while (AIndex < ANum)
|
|
{
|
|
AMinusB.Add(AData[AIndex++]);
|
|
}
|
|
return AMinusB;
|
|
}
|
|
|
|
EPollStatus FPackageData::RefreshObjectCache(bool& bOutFoundNewObjects)
|
|
{
|
|
check(Package.Get() != nullptr);
|
|
|
|
TArray<UObject*> OldObjects;
|
|
OldObjects.Reserve(CachedObjectsInOuter.Num());
|
|
for (FCachedObjectInOuter& Object : CachedObjectsInOuter)
|
|
{
|
|
UObject* ObjectPtr = Object.Object.Get();
|
|
if (ObjectPtr)
|
|
{
|
|
OldObjects.Add(ObjectPtr);
|
|
}
|
|
}
|
|
TArray<UObject*> CurrentObjects;
|
|
GetObjectsWithOuter(Package.Get(), CurrentObjects, true /* bIncludeNestedObjects */, RF_NoFlags,
|
|
EInternalObjectFlags::Garbage);
|
|
|
|
TArray<UObject*> NewObjects = SetDifference(CurrentObjects, OldObjects);
|
|
bOutFoundNewObjects = NewObjects.Num() > 0;
|
|
if (bOutFoundNewObjects)
|
|
{
|
|
CachedObjectsInOuter.Reserve(CachedObjectsInOuter.Num() + NewObjects.Num());
|
|
for (UObject* Object : NewObjects)
|
|
{
|
|
FWeakObjectPtr ObjectWeakPointer(Object);
|
|
// GetObjectsWithOuter with Garbage filtered out should only return valid-for-weakptr objects
|
|
check(ObjectWeakPointer.Get());
|
|
CachedObjectsInOuter.Emplace(MoveTemp(ObjectWeakPointer));
|
|
}
|
|
// GetCookedPlatformDataNextIndex is already where it should be, pointing at the first of the objects we have
|
|
// added. Caller is respnsible for changing state back to calling BeginCacheForCookedPlatformData.
|
|
|
|
if (++GetNumRetriesBeginCacheOnObjects() > FPackageData::GetMaxNumRetriesBeginCacheOnObjects())
|
|
{
|
|
UE_LOG(LogCook, Error,
|
|
TEXT("Cooker has repeatedly tried to call BeginCacheForCookedPlatformData on all objects in the package, but keeps finding new objects.\n")
|
|
TEXT("Aborting the save of the package; programmer needs to debug why objects keep getting added to the package.\n")
|
|
TEXT("Package: %s. Most recent created object: %s."),
|
|
*GetPackageName().ToString(), *NewObjects[0]->GetFullName());
|
|
return EPollStatus::Error;
|
|
}
|
|
}
|
|
return EPollStatus::Success;
|
|
}
|
|
|
|
void FPackageData::ClearObjectCache()
|
|
{
|
|
// Note we do not need to remove objects in CachedObjectsInOuter from CachedCookedPlatformDataObjects
|
|
// That removal is handled by ReleaseCookedPlatformData, and the caller is responsible for calling
|
|
// ReleaseCookedPlatformData before calling ClearObjectCache
|
|
CachedObjectsInOuter.Empty();
|
|
for (TPair<const ITargetPlatform*, FPackagePlatformData>& Pair : PlatformDatas)
|
|
{
|
|
Pair.Value.SetRegisteredForCachedObjectsInOuter(false);
|
|
}
|
|
SetHasSaveCache(false);
|
|
}
|
|
|
|
const int32& FPackageData::GetNumPendingCookedPlatformData() const
|
|
{
|
|
return NumPendingCookedPlatformData;
|
|
}
|
|
|
|
int32& FPackageData::GetNumPendingCookedPlatformData()
|
|
{
|
|
return NumPendingCookedPlatformData;
|
|
}
|
|
|
|
const int32& FPackageData::GetCookedPlatformDataNextIndex() const
|
|
{
|
|
return CookedPlatformDataNextIndex;
|
|
}
|
|
|
|
int32& FPackageData::GetCookedPlatformDataNextIndex()
|
|
{
|
|
return CookedPlatformDataNextIndex;
|
|
}
|
|
|
|
int32& FPackageData::GetNumRetriesBeginCacheOnObjects()
|
|
{
|
|
return NumRetriesBeginCacheOnObject;
|
|
}
|
|
|
|
int32 FPackageData::GetMaxNumRetriesBeginCacheOnObjects()
|
|
{
|
|
return 10;
|
|
}
|
|
|
|
void FPackageData::SetSaveSubState(ESaveSubState Value)
|
|
{
|
|
if (Value != ESaveSubState::StartSave && !IsInStateProperty(EPackageStateProperty::Saving))
|
|
{
|
|
UE_LOG(LogCook, Error, TEXT("SetSaveSubState(%s) called from invalid PackageState %s. The call will be ignored"),
|
|
LexToString(Value), LexToString(GetState()));
|
|
FDebug::DumpStackTraceToLog(ELogVerbosity::Warning);
|
|
return;
|
|
}
|
|
SaveSubState = static_cast<uint32>(Value);
|
|
}
|
|
|
|
void FPackageData::SetSaveSubStateComplete(ESaveSubState Value)
|
|
{
|
|
if (Value < ESaveSubState::Last)
|
|
{
|
|
Value = static_cast<ESaveSubState>(static_cast<uint32>(Value) + 1);
|
|
}
|
|
SetSaveSubState(Value);
|
|
}
|
|
|
|
void FPackageData::CheckCookedPlatformDataEmpty() const
|
|
{
|
|
check(GetCookedPlatformDataNextIndex() <= 0);
|
|
check(GetSaveSubState() <= ESaveSubState::StartSave);
|
|
}
|
|
|
|
void FPackageData::ClearCookedPlatformData()
|
|
{
|
|
CookedPlatformDataNextIndex = -1;
|
|
NumRetriesBeginCacheOnObject = 0;
|
|
// Note that GetNumPendingCookedPlatformData is not cleared; it persists across Saves and CookSessions
|
|
// Caller is responsible for calling SetSaveSubState(ESaveSubState::StartSave);
|
|
}
|
|
|
|
void FPackageData::OnRemoveSessionPlatform(const ITargetPlatform* Platform)
|
|
{
|
|
PlatformDatas.Remove(Platform);
|
|
if (DiscoveredDependencies)
|
|
{
|
|
DiscoveredDependencies->Remove(Platform);
|
|
}
|
|
}
|
|
|
|
bool FPackageData::HasReferencedObjects() const
|
|
{
|
|
return Package != nullptr || CachedObjectsInOuter.Num() > 0;
|
|
}
|
|
|
|
void FPackageData::RemapTargetPlatforms(const TMap<ITargetPlatform*, ITargetPlatform*>& Remap)
|
|
{
|
|
typedef TSortedMap<const ITargetPlatform*, FPackagePlatformData, TInlineAllocator<1>> MapType;
|
|
MapType NewPlatformDatas;
|
|
NewPlatformDatas.Reserve(PlatformDatas.Num());
|
|
for (TPair<const ITargetPlatform*, FPackagePlatformData>& ExistingPair : PlatformDatas)
|
|
{
|
|
ITargetPlatform* NewKey = Remap[ExistingPair.Key];
|
|
NewPlatformDatas.FindOrAdd(NewKey) = MoveTemp(ExistingPair.Value);
|
|
|
|
if (DiscoveredDependencies)
|
|
{
|
|
TMap<FPackageData*, EInstigator>* OldValue = DiscoveredDependencies->Find(ExistingPair.Key);
|
|
if (OldValue)
|
|
{
|
|
TMap<FPackageData*, EInstigator> MovedValue = MoveTemp(*OldValue);
|
|
DiscoveredDependencies->Remove(ExistingPair.Key);
|
|
DiscoveredDependencies->Add(NewKey, MoveTemp(MovedValue));
|
|
}
|
|
}
|
|
}
|
|
|
|
// The save state (and maybe more in the future) by contract can depend on the order of the request platforms
|
|
// remaining unchanged. If we change that order due to the remap, we need to demote back to request.
|
|
if (IsInProgress() && GetState() != EPackageState::Request)
|
|
{
|
|
bool bDemote = true;
|
|
MapType::TConstIterator OldIter = PlatformDatas.CreateConstIterator();
|
|
MapType::TConstIterator NewIter = NewPlatformDatas.CreateConstIterator();
|
|
for (; OldIter; ++OldIter, ++NewIter)
|
|
{
|
|
if (OldIter.Key() != NewIter.Key())
|
|
{
|
|
bDemote = true;
|
|
}
|
|
}
|
|
if (bDemote)
|
|
{
|
|
SendToState(EPackageState::Request, ESendFlags::QueueAddAndRemove, EStateChangeReason::ForceRecook);
|
|
}
|
|
}
|
|
PlatformDatas = MoveTemp(NewPlatformDatas);
|
|
}
|
|
|
|
void FPackageData::UpdateSaveAfterGarbageCollect(bool& bOutDemote)
|
|
{
|
|
bOutDemote = false;
|
|
if (!IsInStateProperty(EPackageStateProperty::Saving))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Reexecute PrepareSave if we already completed it; we need to refresh our CachedObjectsInOuter list
|
|
// and call BeginCacheOnCookedPlatformData on any new objects.
|
|
if (GetSaveSubState() >= ESaveSubState::LastCookedPlatformData_WaitingForIsLoaded)
|
|
{
|
|
SetSaveSubState(ESaveSubState::LastCookedPlatformData_WaitingForIsLoaded);
|
|
}
|
|
|
|
if (GetPackage() == nullptr || !GetPackage()->IsFullyLoaded())
|
|
{
|
|
bOutDemote = true;
|
|
}
|
|
else
|
|
{
|
|
for (FCachedObjectInOuter& CachedObjectInOuter : CachedObjectsInOuter)
|
|
{
|
|
if (CachedObjectInOuter.Object.Get() == nullptr)
|
|
{
|
|
// Deleting a public object puts the package in an invalid state; demote back to request
|
|
// and load/save it again
|
|
bool bPublicDeleted = !!(CachedObjectInOuter.ObjectFlags & RF_Public);;
|
|
bOutDemote |= bPublicDeleted;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (GenerationHelper)
|
|
{
|
|
GenerationHelper->UpdateSaveAfterGarbageCollect(*this, bOutDemote);
|
|
}
|
|
else if (IsGenerated())
|
|
{
|
|
if (!ParentGenerationHelper)
|
|
{
|
|
bOutDemote = true;
|
|
}
|
|
else
|
|
{
|
|
ParentGenerationHelper->UpdateSaveAfterGarbageCollect(*this, bOutDemote);
|
|
}
|
|
}
|
|
}
|
|
|
|
TRefCountPtr<UE::Cook::FGenerationHelper> FPackageData::GetGenerationHelper() const
|
|
{
|
|
return GenerationHelper;
|
|
}
|
|
|
|
void FPackageData::SetGenerated(FName InParentGenerator)
|
|
{
|
|
bGenerated = true;
|
|
ParentGenerator = InParentGenerator;
|
|
}
|
|
|
|
TRefCountPtr<FGenerationHelper> FPackageData::GetParentGenerationHelper() const
|
|
{
|
|
return ParentGenerationHelper;
|
|
}
|
|
|
|
void FPackageData::SetParentGenerationHelper(FGenerationHelper* InGenerationHelper,
|
|
EStateChangeReason StateChangeReason, FCookGenerationInfo* InfoOfPackageInGenerator)
|
|
{
|
|
check(InGenerationHelper == nullptr || IsGenerated());
|
|
check(!(ParentGenerationHelper && InGenerationHelper) || ParentGenerationHelper == InGenerationHelper);
|
|
|
|
if (ParentGenerationHelper && !InGenerationHelper && IsTerminalStateChange(StateChangeReason))
|
|
{
|
|
// The package's progress is completed and we will not come back to it; report the package was saved.
|
|
ParentGenerationHelper->SetAllPlatformsSaved(*this, InfoOfPackageInGenerator);
|
|
}
|
|
ParentGenerationHelper = InGenerationHelper;
|
|
}
|
|
|
|
TRefCountPtr<FGenerationHelper> FPackageData::GetOrFindParentGenerationHelper()
|
|
{
|
|
if (ParentGenerationHelper)
|
|
{
|
|
return ParentGenerationHelper;
|
|
}
|
|
if (!IsGenerated())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
FPackageData* OwnerPackageData = PackageDatas.FindPackageDataByPackageName(GetParentGenerator());
|
|
if (!OwnerPackageData)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
SetParentGenerationHelper(OwnerPackageData->GetGenerationHelper(), EStateChangeReason::Requested);
|
|
return ParentGenerationHelper;
|
|
}
|
|
|
|
TRefCountPtr<FGenerationHelper> FPackageData::GetOrFindParentGenerationHelperNoCache()
|
|
{
|
|
if (ParentGenerationHelper)
|
|
{
|
|
return ParentGenerationHelper;
|
|
}
|
|
if (!IsGenerated())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
FPackageData* OwnerPackageData = PackageDatas.FindPackageDataByPackageName(GetParentGenerator());
|
|
if (!OwnerPackageData)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
return OwnerPackageData->GetGenerationHelper();
|
|
}
|
|
|
|
TRefCountPtr<FGenerationHelper> FPackageData::TryCreateValidParentGenerationHelper()
|
|
{
|
|
if (ParentGenerationHelper)
|
|
{
|
|
if (!ParentGenerationHelper->IsValid())
|
|
{
|
|
SetParentGenerationHelper(nullptr, EStateChangeReason::Requested);
|
|
}
|
|
return ParentGenerationHelper;
|
|
}
|
|
if (!IsGenerated())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
FPackageData* OwnerPackageData = PackageDatas.FindPackageDataByPackageName(GetParentGenerator());
|
|
if (!OwnerPackageData)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// MPCOOKTODO: We need to support calling BeginCacheForCookedPlatformData/IsCachedCookedPlatformData
|
|
// on all objects in the generator package if they have not already been called, if
|
|
// RequiresCachedCookedPlatformDataBeforeSplit. For now we workaround our inability to do this
|
|
// by forcing EGeneratedRequiresGenerator::Save.
|
|
constexpr bool bCookedPlatformDataIsLoaded = true;
|
|
bool bNeedWaitForIsLoaded;
|
|
ParentGenerationHelper = OwnerPackageData->TryCreateValidGenerationHelper(bCookedPlatformDataIsLoaded,
|
|
bNeedWaitForIsLoaded);
|
|
check(ParentGenerationHelper.IsValid() || !bNeedWaitForIsLoaded);
|
|
|
|
return ParentGenerationHelper;
|
|
}
|
|
|
|
TRefCountPtr<FGenerationHelper> FPackageData::CreateUninitializedGenerationHelper()
|
|
{
|
|
if (GenerationHelper)
|
|
{
|
|
return GenerationHelper;
|
|
}
|
|
TRefCountPtr<UE::Cook::FGenerationHelper> Result = new UE::Cook::FGenerationHelper(*this);
|
|
GenerationHelper = Result.GetReference();
|
|
return Result;
|
|
}
|
|
|
|
TRefCountPtr<UE::Cook::FGenerationHelper> FPackageData::TryCreateValidGenerationHelper(
|
|
bool bCookedPlatformDataIsLoaded, bool& bOutNeedWaitForIsLoaded)
|
|
{
|
|
bOutNeedWaitForIsLoaded = false;
|
|
|
|
if (GenerationHelper && GenerationHelper->IsInitialized())
|
|
{
|
|
if (!GenerationHelper->IsValid())
|
|
{
|
|
// The GenerationHelper is not valid; we can get here if it was created from incremental cook data but this
|
|
// package is no longer a generator after syncing. If it has any self-references, clear them so that it
|
|
// will delete and this non-generator package will set the usual GenerationHelper=nullptr value.
|
|
GenerationHelper->ClearSelfReferences(); // Might set GenerationHelper=nullptr
|
|
// this->GenerationHelper might still be non-null, if there are some generated packages
|
|
// that still have a pointer to it. This will only happen in error-handling edge cases, but we
|
|
// need to check for invalid GenerationHelper everwhere we use them to cover this case.
|
|
// Our contract for TryCreateValidGenerationHelper this case is we return nullptr.
|
|
return nullptr;
|
|
}
|
|
return GenerationHelper;
|
|
}
|
|
|
|
UCookOnTheFlyServer& COTFS = PackageDatas.GetCookOnTheFlyServer();
|
|
UE::Cook::Private::FRegisteredCookPackageSplitter* RegisteredSplitterType = nullptr;
|
|
TUniquePtr<ICookPackageSplitter> CookPackageSplitterInstance;
|
|
UObject* SplitDataObject = nullptr;
|
|
UPackage* LocalPackage = GetPackage();
|
|
if (!LocalPackage)
|
|
{
|
|
LocalPackage = FGenerationHelper::FindOrLoadPackage(COTFS, *this);
|
|
}
|
|
if (LocalPackage)
|
|
{
|
|
TOptional<TConstArrayView<FCachedObjectInOuter>> LocalCachedObjectsInOuter;
|
|
if (GetHasSaveCache())
|
|
{
|
|
LocalCachedObjectsInOuter.Emplace(GetCachedObjectsInOuter());
|
|
}
|
|
FGenerationHelper::SearchForRegisteredSplitDataObject(COTFS, GetPackageName(),
|
|
LocalPackage, LocalCachedObjectsInOuter, SplitDataObject, RegisteredSplitterType,
|
|
CookPackageSplitterInstance, bCookedPlatformDataIsLoaded, bOutNeedWaitForIsLoaded);
|
|
}
|
|
TRefCountPtr<UE::Cook::FGenerationHelper> Result = GenerationHelper;
|
|
if (!SplitDataObject || !CookPackageSplitterInstance)
|
|
{
|
|
if (Result)
|
|
{
|
|
// Mark that GenerationHelper is invalid, and clear its references and return nullptr; see comment above.
|
|
Result->InitializeAsInvalid(); // cannot set GenerationHelper=nullptr because we have a local refcount.
|
|
Result->ClearSelfReferences();
|
|
}
|
|
return nullptr;
|
|
}
|
|
else
|
|
{
|
|
if (!Result)
|
|
{
|
|
Result = new UE::Cook::FGenerationHelper(*this);
|
|
GenerationHelper = Result.GetReference();
|
|
}
|
|
|
|
Result->Initialize(SplitDataObject, RegisteredSplitterType, MoveTemp(CookPackageSplitterInstance));
|
|
return Result;
|
|
}
|
|
}
|
|
|
|
TRefCountPtr<UE::Cook::FGenerationHelper> FPackageData::GetGenerationHelperIfValid()
|
|
{
|
|
if (GenerationHelper && GenerationHelper->IsValid())
|
|
{
|
|
return GenerationHelper;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void FPackageData::OnGenerationHelperDestroyed(FGenerationHelper& InGenerationHelper)
|
|
{
|
|
check(GenerationHelper == &InGenerationHelper);
|
|
GenerationHelper = nullptr;
|
|
}
|
|
|
|
FConstructPackageData FPackageData::CreateConstructData()
|
|
{
|
|
FConstructPackageData ConstructData;
|
|
ConstructData.PackageName = PackageName;
|
|
ConstructData.NormalizedFileName = FileName;
|
|
return ConstructData;
|
|
}
|
|
|
|
void FPackageData::AddDiscoveredDependency(const FDiscoveredPlatformSet& Platforms, FPackageData* Dependency,
|
|
EInstigator Category)
|
|
{
|
|
TConstArrayView<const ITargetPlatform*> PlatformArray;
|
|
TArray<const ITargetPlatform*, TInlineAllocator<ExpectedMaxNumPlatforms>> BufferPlatforms;
|
|
|
|
if (Platforms.GetSource() == EDiscoveredPlatformSet::CopyFromInstigator)
|
|
{
|
|
BufferPlatforms.Add(nullptr); // PlatformAgnostic platform
|
|
PlatformArray = BufferPlatforms;
|
|
}
|
|
else
|
|
{
|
|
UCookOnTheFlyServer& COTFS = PackageDatas.GetCookOnTheFlyServer();
|
|
PlatformArray = Platforms.GetPlatforms(COTFS, nullptr,
|
|
TConstArrayView<const ITargetPlatform*>(), EReachability::Runtime, BufferPlatforms);
|
|
if (PlatformArray.Num() == COTFS.PlatformManager->GetSessionPlatforms().Num())
|
|
{
|
|
BufferPlatforms.SetNum(1, EAllowShrinking::No);
|
|
BufferPlatforms[0] = nullptr; // PlatformAgnostic platform
|
|
PlatformArray = BufferPlatforms;
|
|
}
|
|
}
|
|
|
|
if (!DiscoveredDependencies)
|
|
{
|
|
DiscoveredDependencies = MakeUnique<TMap<const ITargetPlatform*, TMap<FPackageData*, EInstigator>>>();
|
|
}
|
|
|
|
for (const ITargetPlatform* TargetPlatform : PlatformArray)
|
|
{
|
|
TMap<FPackageData*, EInstigator>& PlatformDependencies = DiscoveredDependencies->FindOrAdd(TargetPlatform);
|
|
EInstigator& ExistingEdgeType = PlatformDependencies.FindOrAdd(Dependency, Category);
|
|
|
|
// Overwrite the previous edge type with the new edge type if the new edge type is higher priority.
|
|
if (Category == EInstigator::ForceExplorableSaveTimeSoftDependency)
|
|
{
|
|
ExistingEdgeType = Category;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPackageData::ClearDiscoveredDependencies()
|
|
{
|
|
DiscoveredDependencies.Reset();
|
|
}
|
|
|
|
TMap<FPackageData*, EInstigator>& FPackageData::CreateOrGetDiscoveredDependencies(const ITargetPlatform* TargetPlatform)
|
|
{
|
|
if (!DiscoveredDependencies)
|
|
{
|
|
DiscoveredDependencies = MakeUnique<TMap<const ITargetPlatform*, TMap<FPackageData*, EInstigator>>>();
|
|
}
|
|
return DiscoveredDependencies->FindOrAdd(TargetPlatform);
|
|
}
|
|
|
|
TMap<FPackageData*, EInstigator>* FPackageData::GetDiscoveredDependencies(const ITargetPlatform* TargetPlatform)
|
|
{
|
|
if (!DiscoveredDependencies)
|
|
{
|
|
return nullptr;
|
|
}
|
|
return DiscoveredDependencies->Find(TargetPlatform);
|
|
}
|
|
|
|
void FPackageData::AddLogMessage(FReplicatedLogData&& LogData)
|
|
{
|
|
if (!LogMessages)
|
|
{
|
|
LogMessages.Reset(new TArray<FReplicatedLogData>());
|
|
}
|
|
LogMessages->Add(MoveTemp(LogData));
|
|
}
|
|
|
|
TConstArrayView<FReplicatedLogData> FPackageData::GetLogMessages() const
|
|
{
|
|
if (!LogMessages)
|
|
{
|
|
return TConstArrayView<FReplicatedLogData>();
|
|
}
|
|
return *LogMessages;
|
|
}
|
|
|
|
void FPackageData::ClearLogMessages()
|
|
{
|
|
LogMessages.Reset();
|
|
}
|
|
|
|
} // namespace UE::Cook
|
|
|
|
FCbWriter& operator<<(FCbWriter& Writer, const UE::Cook::FConstructPackageData& PackageData)
|
|
{
|
|
Writer.BeginObject();
|
|
Writer << "P" << PackageData.PackageName;
|
|
Writer << "F" << PackageData.NormalizedFileName;
|
|
Writer.EndObject();
|
|
return Writer;
|
|
}
|
|
|
|
bool LoadFromCompactBinary(FCbFieldView Field, UE::Cook::FConstructPackageData& PackageData)
|
|
{
|
|
LoadFromCompactBinary(Field["P"], PackageData.PackageName);
|
|
LoadFromCompactBinary(Field["F"], PackageData.NormalizedFileName);
|
|
return !PackageData.PackageName.IsNone() && !PackageData.NormalizedFileName.IsNone();
|
|
}
|
|
|
|
namespace UE::Cook
|
|
{
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FPendingCookedPlatformData
|
|
|
|
|
|
FPendingCookedPlatformData::FPendingCookedPlatformData(UObject* InObject, const ITargetPlatform* InTargetPlatform,
|
|
FPackageData& InPackageData, bool bInNeedsResourceRelease, UCookOnTheFlyServer& InCookOnTheFlyServer)
|
|
: Object(InObject), TargetPlatform(InTargetPlatform), PackageData(InPackageData)
|
|
, CookOnTheFlyServer(InCookOnTheFlyServer), CancelManager(nullptr), ClassName(InObject->GetClass()->GetFName())
|
|
, bHasReleased(false), bNeedsResourceRelease(bInNeedsResourceRelease)
|
|
{
|
|
check(InObject);
|
|
PackageData.GetNumPendingCookedPlatformData() += 1;
|
|
}
|
|
|
|
FPendingCookedPlatformData::FPendingCookedPlatformData(FPendingCookedPlatformData&& Other)
|
|
: Object(Other.Object), TargetPlatform(Other.TargetPlatform), PackageData(Other.PackageData)
|
|
, CookOnTheFlyServer(Other.CookOnTheFlyServer), CancelManager(Other.CancelManager), ClassName(Other.ClassName)
|
|
, UpdatePeriodMultiplier(Other.UpdatePeriodMultiplier), bHasReleased(Other.bHasReleased)
|
|
, bNeedsResourceRelease(Other.bNeedsResourceRelease)
|
|
{
|
|
Other.Object = nullptr;
|
|
Other.bHasReleased = true;
|
|
}
|
|
|
|
FPendingCookedPlatformData::~FPendingCookedPlatformData()
|
|
{
|
|
Release();
|
|
}
|
|
|
|
bool FPendingCookedPlatformData::PollIsComplete()
|
|
{
|
|
if (bHasReleased)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
UObject* LocalObject = Object.Get();
|
|
if (!LocalObject)
|
|
{
|
|
Release();
|
|
return true;
|
|
}
|
|
UCookOnTheFlyServer& COTFS = PackageData.GetPackageDatas().GetCookOnTheFlyServer();
|
|
if (COTFS.RouteIsCachedCookedPlatformDataLoaded(PackageData, LocalObject, TargetPlatform,
|
|
nullptr /* ExistingEvent */))
|
|
{
|
|
Release();
|
|
return true;
|
|
}
|
|
|
|
// If something (another object's BeginCacheForCookedPlatformData, maybe) has marked the object as
|
|
// garbage, or renamed it out of the package, then we no longer need to wait on it.
|
|
// We might have removed the packagedata from the save state and no longer have a cached UPackage* on it,
|
|
// so compare current package vs original package by name instead of pointer.
|
|
FName CurrentPackageName = LocalObject->GetPackage()->GetFName();
|
|
if (CurrentPackageName != PackageData.GetPackageName())
|
|
{
|
|
UE_LOG(LogCook, Warning,
|
|
TEXT("We were waiting for IsCachedCookedPlatformData to return true for %s in package %s, but that object has been moved out of the package. We will stop waiting on it."),
|
|
*LocalObject->GetFullName(), *PackageData.GetPackageName().ToString());
|
|
Release();
|
|
return true;
|
|
}
|
|
if (LocalObject->HasAnyFlags(EObjectFlags::RF_MirroredGarbage))
|
|
{
|
|
UE_LOG(LogCook, Warning,
|
|
TEXT("We were waiting for IsCachedCookedPlatformData to return true for %s, but that object is now marked for garbage. We will stop waiting on it."),
|
|
*LocalObject->GetFullName());
|
|
Release();
|
|
return true;
|
|
}
|
|
|
|
#if DEBUG_COOKONTHEFLY
|
|
UE_LOG(LogCook, Display, TEXT("%s isn't cached yet"), *LocalObject->GetFullName());
|
|
#endif
|
|
/*if ( LocalObject->IsA(UMaterial::StaticClass()) )
|
|
{
|
|
if (GShaderCompilingManager->HasShaderJobs() == false)
|
|
{
|
|
UE_LOG(LogCook, Warning,
|
|
TEXT("Shader compiler is in a bad state! Shader %s is finished compile but shader compiling manager did not notify shader. "),
|
|
*LocalObject->GetPathName());
|
|
}
|
|
}*/
|
|
return false;
|
|
}
|
|
|
|
void FPendingCookedPlatformData::Release()
|
|
{
|
|
if (bHasReleased)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (bNeedsResourceRelease)
|
|
{
|
|
int32* CurrentAsyncCache = CookOnTheFlyServer.CurrentAsyncCacheForType.Find(ClassName);
|
|
// bNeedsRelease should not have been set if the AsyncCache does not have an entry for the class
|
|
check(CurrentAsyncCache != nullptr);
|
|
*CurrentAsyncCache += 1;
|
|
}
|
|
|
|
PackageData.GetNumPendingCookedPlatformData() -= 1;
|
|
check(PackageData.GetNumPendingCookedPlatformData() >= 0);
|
|
if (CancelManager)
|
|
{
|
|
CancelManager->Release(*this);
|
|
CancelManager = nullptr;
|
|
}
|
|
|
|
Object = nullptr;
|
|
bHasReleased = true;
|
|
}
|
|
|
|
void FPendingCookedPlatformData::RemapTargetPlatforms(const TMap<ITargetPlatform*, ITargetPlatform*>& Remap)
|
|
{
|
|
TargetPlatform = Remap[TargetPlatform];
|
|
}
|
|
|
|
void FPendingCookedPlatformData::ClearCachedCookedPlatformData(UObject* Object, FPackageData& PackageData,
|
|
bool bCompletedSuccesfully)
|
|
{
|
|
FPackageDatas& PackageDatas = PackageData.GetPackageDatas();
|
|
UCookOnTheFlyServer& COTFS = PackageDatas.GetCookOnTheFlyServer();
|
|
FMapOfCachedCookedPlatformDataState& CCPDs = PackageDatas.GetCachedCookedPlatformDataObjects();
|
|
|
|
uint32 ObjectKeyHash = FMapOfCachedCookedPlatformDataState::KeyFuncsType::GetKeyHash(Object);
|
|
FCachedCookedPlatformDataState* CCPDState = CCPDs.FindByHash(ObjectKeyHash, Object);
|
|
if (!CCPDState)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CCPDState->ReleaseFrom(&PackageData);
|
|
if (!CCPDState->IsReferenced())
|
|
{
|
|
for (const TPair<const ITargetPlatform*, ECachedCookedPlatformDataEvent>&
|
|
PlatformPair : CCPDState->PlatformStates)
|
|
{
|
|
Object->ClearCachedCookedPlatformData(PlatformPair.Key);
|
|
}
|
|
|
|
// ClearAllCachedCookedPlatformData and WillNeverCacheCookedPlatformDataAgain are not used in editor
|
|
if (!COTFS.IsCookingInEditor())
|
|
{
|
|
Object->ClearAllCachedCookedPlatformData();
|
|
if (bCompletedSuccesfully && COTFS.IsDirectorCookByTheBook())
|
|
{
|
|
Object->WillNeverCacheCookedPlatformDataAgain();
|
|
}
|
|
}
|
|
|
|
CCPDs.RemoveByHash(ObjectKeyHash, Object);
|
|
}
|
|
};
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FPendingCookedPlatformDataCancelManager
|
|
|
|
|
|
void FPendingCookedPlatformDataCancelManager::Release(FPendingCookedPlatformData& Data)
|
|
{
|
|
--NumPendingPlatforms;
|
|
if (NumPendingPlatforms <= 0)
|
|
{
|
|
check(NumPendingPlatforms == 0);
|
|
UObject* LocalObject = Data.Object.Get();
|
|
if (LocalObject)
|
|
{
|
|
FPendingCookedPlatformData::ClearCachedCookedPlatformData(LocalObject, Data.PackageData,
|
|
false /* bCompletedSuccesfully */);
|
|
}
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FPackageDataMonitor
|
|
FPackageDataMonitor::FPackageDataMonitor()
|
|
{
|
|
FMemory::Memset(NumUrgentInState, 0);
|
|
FMemory::Memset(NumCookLastInState, 0);
|
|
}
|
|
|
|
int32 FPackageDataMonitor::GetNumUrgent(EUrgency UrgencyLevel) const
|
|
{
|
|
check(EUrgency::Min <= UrgencyLevel && UrgencyLevel <= EUrgency::Max);
|
|
int32 UrgencyIndex = static_cast<uint32>(UrgencyLevel) - static_cast<uint32>(EUrgency::Min);
|
|
int32 NumUrgent = 0;
|
|
for (EPackageState State = EPackageState::Min;
|
|
State <= EPackageState::Max;
|
|
State = static_cast<EPackageState>(static_cast<uint32>(State) + 1))
|
|
{
|
|
int32 StateIndex = static_cast<uint32>(State) - static_cast<uint32>(EPackageState::Min);
|
|
NumUrgent += NumUrgentInState[StateIndex][UrgencyIndex];
|
|
}
|
|
return NumUrgent;
|
|
}
|
|
|
|
int32 FPackageDataMonitor::GetNumCookLast() const
|
|
{
|
|
int32 Num = 0;
|
|
for (EPackageState State = EPackageState::Min;
|
|
State <= EPackageState::Max;
|
|
State = static_cast<EPackageState>(static_cast<uint32>(State) + 1))
|
|
{
|
|
Num += NumCookLastInState[static_cast<uint32>(State) - static_cast<uint32>(EPackageState::Min)];
|
|
}
|
|
return Num;
|
|
}
|
|
|
|
int32 FPackageDataMonitor::GetNumUrgent(EPackageState InState, EUrgency UrgencyLevel) const
|
|
{
|
|
check(EUrgency::Min <= UrgencyLevel && UrgencyLevel <= EUrgency::Max);
|
|
int32 UrgencyIndex = static_cast<uint32>(UrgencyLevel) - static_cast<uint32>(EUrgency::Min);
|
|
check(EPackageState::Min <= InState && InState <= EPackageState::Max);
|
|
int32 StateIndex = static_cast<uint32>(InState) - static_cast<uint32>(EPackageState::Min);
|
|
return NumUrgentInState[StateIndex][UrgencyIndex];
|
|
}
|
|
|
|
int32 FPackageDataMonitor::GetNumCookLast(EPackageState InState) const
|
|
{
|
|
check(EPackageState::Min <= InState && InState <= EPackageState::Max);
|
|
int32 StateIndex = static_cast<uint32>(InState) - static_cast<uint32>(EPackageState::Min);
|
|
return NumCookLastInState[StateIndex];
|
|
}
|
|
|
|
int32 FPackageDataMonitor::GetNumPreloadAllocated() const
|
|
{
|
|
return NumPreloadAllocated;
|
|
}
|
|
|
|
int32 FPackageDataMonitor::GetNumInProgress() const
|
|
{
|
|
return NumInProgress;
|
|
}
|
|
|
|
int32 FPackageDataMonitor::GetNumCooked(ECookResult Result) const
|
|
{
|
|
return NumCooked[(uint8)Result];
|
|
}
|
|
|
|
void FPackageDataMonitor::OnInProgressChanged(FPackageData& PackageData, bool bInProgress)
|
|
{
|
|
NumInProgress += bInProgress ? 1 : -1;
|
|
check(NumInProgress >= 0);
|
|
}
|
|
|
|
void FPackageDataMonitor::OnPreloadAllocatedChanged(FPackageData& PackageData, bool bPreloadAllocated)
|
|
{
|
|
NumPreloadAllocated += bPreloadAllocated ? 1 : -1;
|
|
check(NumPreloadAllocated >= 0);
|
|
}
|
|
|
|
void FPackageDataMonitor::OnFirstCookedPlatformAdded(FPackageData& PackageData, ECookResult CookResult)
|
|
{
|
|
check(CookResult != ECookResult::NotAttempted);
|
|
if (PackageData.GetMonitorCookResult() == ECookResult::NotAttempted)
|
|
{
|
|
PackageData.SetMonitorCookResult(CookResult);
|
|
++NumCooked[(uint8)CookResult];
|
|
}
|
|
}
|
|
|
|
void FPackageDataMonitor::OnLastCookedPlatformRemoved(FPackageData& PackageData)
|
|
{
|
|
ECookResult CookResult = PackageData.GetMonitorCookResult();
|
|
if (CookResult != ECookResult::NotAttempted)
|
|
{
|
|
--NumCooked[(uint8)CookResult];
|
|
PackageData.SetMonitorCookResult(ECookResult::NotAttempted);
|
|
}
|
|
}
|
|
|
|
void FPackageDataMonitor::OnUrgencyChanged(FPackageData& PackageData, EUrgency OldUrgency, EUrgency NewUrgency)
|
|
{
|
|
TrackUrgentRequests(PackageData.GetState(), OldUrgency, -1);
|
|
TrackUrgentRequests(PackageData.GetState(), NewUrgency, 1);
|
|
}
|
|
|
|
void FPackageDataMonitor::OnCookLastChanged(FPackageData& PackageData)
|
|
{
|
|
int32 Delta = PackageData.GetIsCookLast() ? 1 : -1;
|
|
TrackCookLastRequests(PackageData.GetState(), Delta);
|
|
}
|
|
|
|
void FPackageDataMonitor::OnStateChanged(FPackageData& PackageData, EPackageState OldState)
|
|
{
|
|
EPackageState NewState = PackageData.GetState();
|
|
EUrgency Urgency = PackageData.GetUrgency();
|
|
if (Urgency > EUrgency::Normal)
|
|
{
|
|
TrackUrgentRequests(OldState, Urgency, -1);
|
|
TrackUrgentRequests(NewState, Urgency, 1);
|
|
}
|
|
if (PackageData.GetIsCookLast())
|
|
{
|
|
TrackCookLastRequests(OldState, -1);
|
|
TrackCookLastRequests(NewState, 1);
|
|
}
|
|
bool bOldStateAssignedToLocal = OldState != EPackageState::Idle &&
|
|
!EnumHasAnyFlags(FStateProperties(OldState).Properties, EPackageStateProperty::AssignedToWorkerProperty);
|
|
bool bNewStateAssignedToLocal = NewState != EPackageState::Idle &&
|
|
!EnumHasAnyFlags(FStateProperties(NewState).Properties, EPackageStateProperty::AssignedToWorkerProperty);
|
|
if (bOldStateAssignedToLocal != bNewStateAssignedToLocal)
|
|
{
|
|
++(bNewStateAssignedToLocal ? MPCookAssignedFenceMarker : MPCookRetiredFenceMarker);
|
|
}
|
|
}
|
|
|
|
void FPackageDataMonitor::TrackUrgentRequests(EPackageState State, EUrgency Urgency, int32 Delta)
|
|
{
|
|
if (State == EPackageState::Idle || Urgency == EUrgency::Normal)
|
|
{
|
|
// We don't track urgency count in idle, and we don't track normal urgency count.
|
|
return;
|
|
}
|
|
check(EPackageState::Min <= State && State <= EPackageState::Max);
|
|
check(EUrgency::Min <= Urgency && Urgency <= EUrgency::Max);
|
|
|
|
int32 StateIndex = static_cast<uint32>(State) - static_cast<uint32>(EPackageState::Min);
|
|
int32 UrgencyIndex = static_cast<uint32>(Urgency) - static_cast<uint32>(EUrgency::Min);
|
|
NumUrgentInState[StateIndex][UrgencyIndex] += Delta;
|
|
check(NumUrgentInState[StateIndex][UrgencyIndex] >= 0);
|
|
}
|
|
|
|
void FPackageDataMonitor::TrackCookLastRequests(EPackageState State, int32 Delta)
|
|
{
|
|
check(EPackageState::Min <= State && State <= EPackageState::Max);
|
|
if (State != EPackageState::Idle)
|
|
{
|
|
NumCookLastInState[static_cast<uint32>(State) - static_cast<uint32>(EPackageState::Min)] += Delta;
|
|
check(NumCookLastInState[static_cast<uint32>(State) - static_cast<uint32>(EPackageState::Min)] >= 0);
|
|
}
|
|
}
|
|
|
|
int32 FPackageDataMonitor::GetMPCookAssignedFenceMarker() const
|
|
{
|
|
return MPCookAssignedFenceMarker;
|
|
}
|
|
|
|
int32 FPackageDataMonitor::GetMPCookRetiredFenceMarker() const
|
|
{
|
|
return MPCookRetiredFenceMarker;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FPackageDatas
|
|
|
|
IAssetRegistry* FPackageDatas::AssetRegistry = nullptr;
|
|
|
|
FPackageDatas::FPackageDatas(UCookOnTheFlyServer& InCookOnTheFlyServer)
|
|
: CookOnTheFlyServer(InCookOnTheFlyServer)
|
|
, LastPollAsyncTime(0)
|
|
{
|
|
Allocator.SetMinBlockSize(1024);
|
|
Allocator.SetMaxBlockSize(65536);
|
|
}
|
|
|
|
void FPackageDatas::SetBeginCookConfigSettings(FStringView CookShowInstigator)
|
|
{
|
|
ShowInstigatorPackageData = nullptr;
|
|
if (!CookShowInstigator.IsEmpty())
|
|
{
|
|
FString LocalPath;
|
|
FString PackageName;
|
|
if (!FPackageName::TryConvertToMountedPath(CookShowInstigator, &LocalPath, &PackageName,
|
|
nullptr, nullptr, nullptr))
|
|
{
|
|
UE_LOG(LogCook, Fatal, TEXT("-CookShowInstigator argument %.*s is not a mounted filename or packagename"),
|
|
CookShowInstigator.Len(), CookShowInstigator.GetData());
|
|
}
|
|
else
|
|
{
|
|
FName PackageFName(*PackageName);
|
|
ShowInstigatorPackageData = TryAddPackageDataByPackageName(PackageFName);
|
|
if (!ShowInstigatorPackageData)
|
|
{
|
|
UE_LOG(LogCook, Fatal, TEXT("-CookShowInstigator argument %.*s could not be found on disk"),
|
|
CookShowInstigator.Len(), CookShowInstigator.GetData());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FPackageDatas::~FPackageDatas()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
void FPackageDatas::OnAssetRegistryGenerated(IAssetRegistry& InAssetRegistry)
|
|
{
|
|
AssetRegistry = &InAssetRegistry;
|
|
}
|
|
|
|
FString FPackageDatas::GetReferencerName() const
|
|
{
|
|
return TEXT("CookOnTheFlyServer");
|
|
}
|
|
|
|
void FPackageDatas::AddReferencedObjects(FReferenceCollector& Collector)
|
|
{
|
|
return CookOnTheFlyServer.CookerAddReferencedObjects(Collector);
|
|
}
|
|
|
|
FPackageData& FPackageDatas::FindOrAddPackageData(const FName& PackageName, const FName& NormalizedFileName)
|
|
{
|
|
{
|
|
FReadScopeLock ExistenceReadLock(ExistenceLock);
|
|
FPackageData** PackageDataMapAddr = PackageNameToPackageData.Find(PackageName);
|
|
if (PackageDataMapAddr != nullptr)
|
|
{
|
|
FPackageData** FileNameMapAddr = FileNameToPackageData.Find(NormalizedFileName);
|
|
checkf(FileNameMapAddr,
|
|
TEXT("Package %s is being added with filename %s, but it already exists with filename %s, ")
|
|
TEXT("and it is not present in FileNameToPackageData map under the new name."),
|
|
*PackageName.ToString(), *NormalizedFileName.ToString(),
|
|
*(*PackageDataMapAddr)->GetFileName().ToString());
|
|
checkf(*FileNameMapAddr == *PackageDataMapAddr,
|
|
TEXT("Package %s is being added with filename %s, but that filename maps to a different package %s."),
|
|
*PackageName.ToString(), *NormalizedFileName.ToString(),
|
|
*(*FileNameMapAddr)->GetPackageName().ToString());
|
|
return **PackageDataMapAddr;
|
|
}
|
|
|
|
checkf(FileNameToPackageData.Find(NormalizedFileName) == nullptr,
|
|
TEXT("Package \"%s\" and package \"%s\" share the same filename \"%s\"."),
|
|
*PackageName.ToString(), *(*FileNameToPackageData.Find(NormalizedFileName))->GetPackageName().ToString(),
|
|
*NormalizedFileName.ToString());
|
|
}
|
|
return CreatePackageData(PackageName, NormalizedFileName);
|
|
}
|
|
|
|
FPackageData* FPackageDatas::FindPackageDataByPackageName(const FName& PackageName)
|
|
{
|
|
if (PackageName.IsNone())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
FReadScopeLock ExistenceReadLock(ExistenceLock);
|
|
FPackageData** PackageDataMapAddr = PackageNameToPackageData.Find(PackageName);
|
|
return PackageDataMapAddr ? *PackageDataMapAddr : nullptr;
|
|
}
|
|
|
|
FPackageData* FPackageDatas::TryAddPackageDataByPackageName(const FName& PackageName, bool bRequireExists,
|
|
bool bCreateAsMap)
|
|
{
|
|
if (PackageName.IsNone())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
{
|
|
FReadScopeLock ExistenceReadLock(ExistenceLock);
|
|
FPackageData** PackageDataMapAddr = PackageNameToPackageData.Find(PackageName);
|
|
if (PackageDataMapAddr != nullptr)
|
|
{
|
|
return *PackageDataMapAddr;
|
|
}
|
|
}
|
|
|
|
FName FileName = LookupFileNameOnDisk(PackageName, bRequireExists, bCreateAsMap);
|
|
if (FileName.IsNone())
|
|
{
|
|
// This will happen if PackageName does not exist on disk
|
|
return nullptr;
|
|
}
|
|
{
|
|
FReadScopeLock ExistenceReadLock(ExistenceLock);
|
|
checkf(FileNameToPackageData.Find(FileName) == nullptr,
|
|
TEXT("Package \"%s\" and package \"%s\" share the same filename \"%s\"."),
|
|
*PackageName.ToString(), *(*FileNameToPackageData.Find(FileName))->GetPackageName().ToString(),
|
|
*FileName.ToString());
|
|
}
|
|
return &CreatePackageData(PackageName, FileName);
|
|
}
|
|
|
|
FPackageData& FPackageDatas::AddPackageDataByPackageNameChecked(const FName& PackageName, bool bRequireExists,
|
|
bool bCreateAsMap)
|
|
{
|
|
FPackageData* PackageData = TryAddPackageDataByPackageName(PackageName, bRequireExists, bCreateAsMap);
|
|
check(PackageData);
|
|
return *PackageData;
|
|
}
|
|
|
|
FPackageData* FPackageDatas::FindPackageDataByFileName(const FName& InFileName)
|
|
{
|
|
FName FileName(GetStandardFileName(InFileName));
|
|
if (FileName.IsNone())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
FReadScopeLock ExistenceReadLock(ExistenceLock);
|
|
FPackageData** PackageDataMapAddr = FileNameToPackageData.Find(FileName);
|
|
return PackageDataMapAddr ? *PackageDataMapAddr : nullptr;
|
|
}
|
|
|
|
FPackageData* FPackageDatas::TryAddPackageDataByFileName(const FName& InFileName)
|
|
{
|
|
return TryAddPackageDataByStandardFileName(GetStandardFileName(InFileName));
|
|
}
|
|
|
|
FPackageData* FPackageDatas::TryAddPackageDataByStandardFileName(const FName& FileName, bool bExactMatchRequired,
|
|
FName* OutFoundFileName)
|
|
{
|
|
FName FoundFileName = FileName;
|
|
ON_SCOPE_EXIT{ if (OutFoundFileName) { *OutFoundFileName = FoundFileName; } };
|
|
if (FileName.IsNone())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
{
|
|
FReadScopeLock ExistenceReadLock(ExistenceLock);
|
|
FPackageData** PackageDataMapAddr = FileNameToPackageData.Find(FileName);
|
|
if (PackageDataMapAddr != nullptr)
|
|
{
|
|
return *PackageDataMapAddr;
|
|
}
|
|
}
|
|
|
|
FName ExistingFileName;
|
|
FName PackageName = LookupPackageNameOnDisk(FileName, bExactMatchRequired, ExistingFileName);
|
|
if (PackageName.IsNone())
|
|
{
|
|
return nullptr;
|
|
}
|
|
if (ExistingFileName.IsNone())
|
|
{
|
|
if (!bExactMatchRequired)
|
|
{
|
|
FReadScopeLock ExistenceReadLock(ExistenceLock);
|
|
FPackageData** PackageDataMapAddr = PackageNameToPackageData.Find(PackageName);
|
|
if (PackageDataMapAddr != nullptr)
|
|
{
|
|
FoundFileName = (*PackageDataMapAddr)->GetFileName();
|
|
return *PackageDataMapAddr;
|
|
}
|
|
}
|
|
UE_LOG(LogCook, Warning,
|
|
TEXT("Unexpected failure to cook filename '%s'. It is mapped to PackageName '%s', but does not exist on disk and we cannot verify the extension."),
|
|
*FileName.ToString(), *PackageName.ToString());
|
|
return nullptr;
|
|
}
|
|
FoundFileName = ExistingFileName;
|
|
return &CreatePackageData(PackageName, ExistingFileName);
|
|
}
|
|
|
|
FPackageData& FPackageDatas::CreatePackageData(FName PackageName, FName FileName)
|
|
{
|
|
check(!PackageName.IsNone());
|
|
check(!FileName.IsNone());
|
|
|
|
FWriteScopeLock ExistenceWriteLock(ExistenceLock);
|
|
FPackageData*& ExistingByPackageName = PackageNameToPackageData.FindOrAdd(PackageName);
|
|
FPackageData*& ExistingByFileName = FileNameToPackageData.FindOrAdd(FileName);
|
|
if (ExistingByPackageName)
|
|
{
|
|
// The other CreatePackageData call should have added the FileName as well
|
|
check(ExistingByFileName == ExistingByPackageName);
|
|
return *ExistingByPackageName;
|
|
}
|
|
// If no other CreatePackageData added the PackageName, then they should not have added
|
|
// the FileName either
|
|
check(!ExistingByFileName);
|
|
FPackageData* PackageData = Allocator.NewElement(*this, PackageName, FileName);
|
|
ExistingByPackageName = PackageData;
|
|
ExistingByFileName = PackageData;
|
|
return *PackageData;
|
|
}
|
|
|
|
FPackageData& FPackageDatas::AddPackageDataByFileNameChecked(const FName& FileName)
|
|
{
|
|
FPackageData* PackageData = TryAddPackageDataByFileName(FileName);
|
|
check(PackageData);
|
|
return *PackageData;
|
|
}
|
|
|
|
FName FPackageDatas::GetFileNameByPackageName(FName PackageName, bool bRequireExists, bool bCreateAsMap)
|
|
{
|
|
FPackageData* PackageData = TryAddPackageDataByPackageName(PackageName, bRequireExists, bCreateAsMap);
|
|
return PackageData ? PackageData->GetFileName() : NAME_None;
|
|
}
|
|
|
|
bool FPackageDatas::TryGetNamesByFlexName(FName PackageOrFileName, FName* OutPackageName, FName* OutFileName,
|
|
bool bRequireExists, bool bCreateAsMap)
|
|
{
|
|
FString Buffer = PackageOrFileName.ToString();
|
|
if (!FPackageName::TryConvertFilenameToLongPackageName(Buffer, Buffer))
|
|
{
|
|
return false;
|
|
}
|
|
FName PackageName = FName(Buffer);
|
|
FName FileName = GetFileNameByPackageName(PackageName, bRequireExists, bCreateAsMap);
|
|
if (FileName.IsNone())
|
|
{
|
|
return false;
|
|
}
|
|
if (OutPackageName)
|
|
{
|
|
*OutPackageName = PackageName;
|
|
}
|
|
if (OutFileName)
|
|
{
|
|
*OutFileName = FileName;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
FName FPackageDatas::LookupFileNameOnDisk(FName PackageName, bool bRequireExists, bool bCreateAsMap)
|
|
{
|
|
FString FilenameOnDisk;
|
|
if (TryLookupFileNameOnDisk(PackageName, FilenameOnDisk))
|
|
{
|
|
}
|
|
else if (!bRequireExists)
|
|
{
|
|
FString Extension = bCreateAsMap ? FPackageName::GetMapPackageExtension() :
|
|
FPackageName::GetAssetPackageExtension();
|
|
if (!FPackageName::TryConvertLongPackageNameToFilename(PackageName.ToString(), FilenameOnDisk, Extension))
|
|
{
|
|
return NAME_None;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return NAME_None;
|
|
}
|
|
FilenameOnDisk = FPaths::ConvertRelativePathToFull(FilenameOnDisk);
|
|
FPaths::MakeStandardFilename(FilenameOnDisk);
|
|
return FName(FilenameOnDisk);
|
|
}
|
|
|
|
bool FPackageDatas::TryLookupFileNameOnDisk(FName PackageName, FString& OutFileName)
|
|
{
|
|
FString PackageNameStr = PackageName.ToString();
|
|
|
|
// Verse packages are editor-generated in-memory packages which don't have a corresponding
|
|
// asset file (yet). However, we still want to cook these packages out, producing cooked
|
|
// asset files for packaged projects.
|
|
if (FPackageName::IsVersePackage(PackageNameStr))
|
|
{
|
|
if (FindPackage(/*Outer =*/nullptr, *PackageNameStr))
|
|
{
|
|
if (!FPackageName::TryConvertLongPackageNameToFilename(PackageNameStr, OutFileName,
|
|
FPackageName::GetAssetPackageExtension()))
|
|
{
|
|
UE_LOG(LogCook, Warning,
|
|
TEXT("Package %s exists in memory but its PackageRoot is not mounted. It will not be cooked."),
|
|
*PackageNameStr);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
// else, the cooker could be responding to a NotifyUObjectCreated() event, and the object hasn't
|
|
// been fully constructed yet (missing from the FindObject() list) -- in this case, we've found
|
|
// that the linker loader is creating a dummy object to fill a referencing import slot, not loading
|
|
// the proper object (which means we want to ignore it).
|
|
}
|
|
|
|
if (!AssetRegistry)
|
|
{
|
|
return FPackageName::DoesPackageExist(PackageNameStr, &OutFileName, false /* InAllowTextFormats */);
|
|
}
|
|
else
|
|
{
|
|
FString PackageExtension;
|
|
if (!AssetRegistry->DoesPackageExistOnDisk(PackageName, nullptr, &PackageExtension))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return FPackageName::TryConvertLongPackageNameToFilename(PackageNameStr, OutFileName, PackageExtension);
|
|
}
|
|
}
|
|
|
|
FName FPackageDatas::LookupPackageNameOnDisk(FName NormalizedFileName, bool bExactMatchRequired, FName& FoundFileName)
|
|
{
|
|
FoundFileName = NormalizedFileName;
|
|
if (NormalizedFileName.IsNone())
|
|
{
|
|
return NAME_None;
|
|
}
|
|
FString Buffer = NormalizedFileName.ToString();
|
|
if (!FPackageName::TryConvertFilenameToLongPackageName(Buffer, Buffer))
|
|
{
|
|
return NAME_None;
|
|
}
|
|
FName PackageName = FName(*Buffer);
|
|
|
|
FName DiscoveredFileName = LookupFileNameOnDisk(PackageName, true /* bRequireExists */, false /* bCreateAsMap */);
|
|
if (DiscoveredFileName == NormalizedFileName || !bExactMatchRequired)
|
|
{
|
|
FoundFileName = DiscoveredFileName;
|
|
return PackageName;
|
|
}
|
|
else
|
|
{
|
|
// Either the file does not exist on disk or NormalizedFileName did not match its format or extension
|
|
return NAME_None;
|
|
}
|
|
}
|
|
|
|
FName FPackageDatas::GetStandardFileName(FName FileName)
|
|
{
|
|
FString FileNameString(FileName.ToString());
|
|
FPaths::MakeStandardFilename(FileNameString);
|
|
return FName(FileNameString);
|
|
}
|
|
|
|
FName FPackageDatas::GetStandardFileName(FStringView InFileName)
|
|
{
|
|
FString FileName(InFileName);
|
|
FPaths::MakeStandardFilename(FileName);
|
|
return FName(FileName);
|
|
}
|
|
|
|
void FPackageDatas::AddExistingPackageDatasForPlatform(TConstArrayView<FConstructPackageData> ExistingPackages,
|
|
const ITargetPlatform* TargetPlatform, bool bExpectPackageDatasAreNew, int32& OutPackageDataFromBaseGameNum)
|
|
{
|
|
int32 NumPackages = ExistingPackages.Num();
|
|
if (NumPackages == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Make the list unique
|
|
TArray<FConstructPackageData> UniqueArray(ExistingPackages);
|
|
Algo::Sort(UniqueArray, [](const FConstructPackageData& A, const FConstructPackageData& B)
|
|
{
|
|
return A.PackageName.FastLess(B.PackageName);
|
|
});
|
|
UniqueArray.SetNum(Algo::Unique(UniqueArray, [](const FConstructPackageData& A, const FConstructPackageData& B)
|
|
{
|
|
return A.PackageName == B.PackageName;
|
|
}));
|
|
ExistingPackages = UniqueArray;
|
|
|
|
FWriteScopeLock ExistenceWriteLock(ExistenceLock);
|
|
if (bExpectPackageDatasAreNew)
|
|
{
|
|
Allocator.ReserveDelta(NumPackages);
|
|
FileNameToPackageData.Reserve(FileNameToPackageData.Num() + NumPackages);
|
|
PackageNameToPackageData.Reserve(PackageNameToPackageData.Num() + NumPackages);
|
|
}
|
|
|
|
// Create the PackageDatas and mark them as cooked
|
|
for (const FConstructPackageData& ConstructData : ExistingPackages)
|
|
{
|
|
FName PackageName = ConstructData.PackageName;
|
|
FName NormalizedFileName = ConstructData.NormalizedFileName;
|
|
check(!PackageName.IsNone());
|
|
check(!NormalizedFileName.IsNone());
|
|
|
|
FPackageData*& PackageData = FileNameToPackageData.FindOrAdd(NormalizedFileName, nullptr);
|
|
if (!PackageData)
|
|
{
|
|
// create the package data (copied from CreatePackageData)
|
|
FPackageData* NewPackageData = Allocator.NewElement(*this, PackageName, NormalizedFileName);
|
|
FPackageData* ExistingByPackageName = PackageNameToPackageData.FindOrAdd(PackageName, NewPackageData);
|
|
// If no other CreatePackageData added the FileName, then they should not have added
|
|
// the PackageName either
|
|
check(ExistingByPackageName == NewPackageData);
|
|
|
|
PackageData = NewPackageData;
|
|
}
|
|
PackageData->SetPlatformCooked(TargetPlatform, ECookResult::Succeeded, /*bWasCookedThisSession=*/false);
|
|
}
|
|
OutPackageDataFromBaseGameNum += ExistingPackages.Num();
|
|
}
|
|
|
|
FPackageData* FPackageDatas::UpdateFileName(FName PackageName)
|
|
{
|
|
FWriteScopeLock ExistenceWriteLock(ExistenceLock);
|
|
|
|
FPackageData** PackageDataAddr = PackageNameToPackageData.Find(PackageName);
|
|
if (!PackageDataAddr)
|
|
{
|
|
FName NewFileName = LookupFileNameOnDisk(PackageName);
|
|
check(NewFileName.IsNone() || !FileNameToPackageData.Find(NewFileName));
|
|
return nullptr;
|
|
}
|
|
FPackageData* PackageData = *PackageDataAddr;
|
|
FName OldFileName = PackageData->GetFileName();
|
|
bool bIsMap = FPackageName::IsMapPackageExtension(*FPaths::GetExtension(OldFileName.ToString()));
|
|
FName NewFileName = LookupFileNameOnDisk(PackageName, false /* bRequireExists */, bIsMap);
|
|
if (OldFileName == NewFileName)
|
|
{
|
|
return PackageData;
|
|
}
|
|
if (NewFileName.IsNone())
|
|
{
|
|
UE_LOG(LogCook, Error, TEXT("Cannot update FileName for package %s because the package is no longer mounted."),
|
|
*PackageName.ToString())
|
|
return PackageData;
|
|
}
|
|
|
|
check(!OldFileName.IsNone());
|
|
FPackageData* ExistingByFileName;
|
|
ensure(FileNameToPackageData.RemoveAndCopyValue(OldFileName, ExistingByFileName));
|
|
check(ExistingByFileName == PackageData);
|
|
|
|
PackageData->SetFileName(NewFileName);
|
|
FPackageData* AddedByFileName = FileNameToPackageData.FindOrAdd(NewFileName, PackageData);
|
|
check(AddedByFileName == PackageData);
|
|
|
|
return PackageData;
|
|
}
|
|
|
|
FThreadsafePackageData::FThreadsafePackageData()
|
|
: bInitialized(false)
|
|
, bHasLoggedDiscoveryWarning(false)
|
|
, bHasLoggedDependencyWarning(false)
|
|
{
|
|
}
|
|
|
|
void FPackageDatas::UpdateThreadsafePackageData(const FPackageData& PackageData)
|
|
{
|
|
UpdateThreadsafePackageData(PackageData.GetPackageName(),
|
|
[&PackageData](FThreadsafePackageData& ThreadsafeData, bool bNew)
|
|
{
|
|
ThreadsafeData.Instigator = PackageData.GetInstigator(EReachability::Runtime);
|
|
ThreadsafeData.Generator = PackageData.GetParentGenerator();
|
|
});
|
|
}
|
|
|
|
int32 FPackageDatas::GetNumCooked()
|
|
{
|
|
int32 Count = 0;
|
|
for (uint8 CookResult = 0; CookResult < (uint8)ECookResult::Count; ++CookResult)
|
|
{
|
|
Count += Monitor.GetNumCooked((ECookResult)CookResult);
|
|
}
|
|
return Count;
|
|
}
|
|
|
|
int32 FPackageDatas::GetNumCooked(ECookResult CookResult)
|
|
{
|
|
return Monitor.GetNumCooked(CookResult);
|
|
}
|
|
|
|
void FPackageDatas::GetCommittedPackagesForPlatform(const ITargetPlatform* Platform,
|
|
TArray<FPackageData*>& SucceededPackages,
|
|
TArray<FPackageData*>& FailedPackages)
|
|
{
|
|
LockAndEnumeratePackageDatas(
|
|
[Platform, &SucceededPackages, &FailedPackages](FPackageData* PackageData)
|
|
{
|
|
FPackagePlatformData* PlatformData = PackageData->FindPlatformData(Platform);
|
|
if (PlatformData && PlatformData->IsCommitted())
|
|
{
|
|
ECookResult CookResults = PackageData->GetCookResults(Platform);
|
|
(CookResults == ECookResult::Succeeded ? SucceededPackages : FailedPackages).Add(PackageData);
|
|
}
|
|
});
|
|
}
|
|
|
|
void FPackageDatas::Clear()
|
|
{
|
|
FWriteScopeLock ExistenceWriteLock(ExistenceLock);
|
|
PendingCookedPlatformDataLists.Empty(); // These destructors will read/write PackageDatas
|
|
RequestQueue.Empty();
|
|
SaveQueue.Empty();
|
|
AssignedToWorkerSet.Empty();
|
|
SaveStalledSet.Empty();
|
|
PackageNameToPackageData.Empty();
|
|
FileNameToPackageData.Empty();
|
|
CachedCookedPlatformDataObjects.Empty();
|
|
{
|
|
// All references must be cleared before any PackageDatas are destroyed
|
|
EnumeratePackageDatasWithinLock([](FPackageData* PackageData)
|
|
{
|
|
PackageData->ClearReferences();
|
|
});
|
|
EnumeratePackageDatasWithinLock([](FPackageData* PackageData)
|
|
{
|
|
PackageData->~FPackageData();
|
|
});
|
|
Allocator.Empty();
|
|
}
|
|
|
|
ShowInstigatorPackageData = nullptr;
|
|
}
|
|
|
|
void FPackageDatas::ClearCookedPlatforms()
|
|
{
|
|
LockAndEnumeratePackageDatas([](FPackageData* PackageData)
|
|
{
|
|
PackageData->ResetReachable(EReachability::All);
|
|
PackageData->ClearCookResults();
|
|
});
|
|
}
|
|
|
|
void FPackageDatas::ClearCookResultsForPackages(const TSet<FName>& InPackages)
|
|
{
|
|
int32 AffectedPackagesCount = 0;
|
|
LockAndEnumeratePackageDatas([InPackages, &AffectedPackagesCount](FPackageData* PackageData)
|
|
{
|
|
const FName& PackageName = PackageData->GetPackageName();
|
|
if (InPackages.Contains(PackageName))
|
|
{
|
|
PackageData->ClearCookResults();
|
|
AffectedPackagesCount++;
|
|
}
|
|
});
|
|
|
|
UE_LOG(LogCook, Display,
|
|
TEXT("Cleared the cook results of %d packages because ClearCookResultsForPackages requested them to be recooked."),
|
|
AffectedPackagesCount);
|
|
}
|
|
|
|
void FPackageDatas::OnRemoveSessionPlatform(const ITargetPlatform* TargetPlatform)
|
|
{
|
|
LockAndEnumeratePackageDatas([TargetPlatform](FPackageData* PackageData)
|
|
{
|
|
PackageData->OnRemoveSessionPlatform(TargetPlatform);
|
|
});
|
|
}
|
|
|
|
constexpr int32 PendingPlatformDataReservationSize = 128;
|
|
constexpr int32 PendingPlatformDataMaxUpdatePeriod = 16;
|
|
|
|
void FPackageDatas::AddPendingCookedPlatformData(FPendingCookedPlatformData&& Data)
|
|
{
|
|
if (PendingCookedPlatformDataLists.IsEmpty())
|
|
{
|
|
PendingCookedPlatformDataLists.Emplace();
|
|
PendingCookedPlatformDataLists.Last().Reserve(PendingPlatformDataReservationSize );
|
|
}
|
|
PendingCookedPlatformDataLists.First().Add(MoveTemp(Data));
|
|
++PendingCookedPlatformDataNum;
|
|
}
|
|
|
|
void FPackageDatas::PollPendingCookedPlatformDatas(bool bForce, double& LastCookableObjectTickTime)
|
|
{
|
|
if (PendingCookedPlatformDataNum == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
double CurrentTime = FPlatformTime::Seconds();
|
|
if (!bForce)
|
|
{
|
|
// ProcessAsyncResults and IsCachedCookedPlatformDataLoaded can be expensive to call
|
|
// Cap the frequency at which we call them. We only update the last poll time at completion
|
|
// so that we don't suddenly saturate the game thread by making derived data key strings
|
|
// when the time to do the polls increases to GPollAsyncPeriod.
|
|
if (CurrentTime < LastPollAsyncTime + GPollAsyncPeriod)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
LastPollAsyncTime = CurrentTime;
|
|
|
|
// PendingPlatformDataLists is a rotating list of lists of PendingPlatformDatas
|
|
// The first list contains all of the PendingPlatformDatas that we should poll on this tick
|
|
// The nth list is all of the PendingPlatformDatas that we should poll after N more ticks
|
|
// Each poll period we pull the front list off and all other lists move frontwards by 1.
|
|
// New PendingPlatformDatas are inserted into the first list, to be polled in the next poll period
|
|
// When a PendingPlatformData signals it is not ready after polling, we increase its poll period
|
|
// exponentially - we double it.
|
|
// A poll period of N times the default poll period means we insert it into the Nth list in
|
|
// PendingPlatformDataLists.
|
|
FPendingCookedPlatformDataContainer List = PendingCookedPlatformDataLists.PopFrontValue();
|
|
if (!bForce && List.IsEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (bForce)
|
|
{
|
|
// When we are forced, because the caller has an urgent package to save, call ProcessAsyncResults
|
|
// with a small timeslice in case we need to process shaders to unblock the package
|
|
constexpr float TimeSlice = 0.01f;
|
|
GShaderCompilingManager->ProcessAsyncResults(TimeSlice, false /* bBlockOnGlobalShaderCompletion */);
|
|
}
|
|
|
|
FDelegateHandle EventHandle = FAssetCompilingManager::Get().OnPackageScopeEvent().AddLambda(
|
|
[this](UPackage* Package, bool bEntering)
|
|
{
|
|
if (bEntering)
|
|
{
|
|
CookOnTheFlyServer.SetActivePackage(Package->GetFName(),
|
|
#if UE_WITH_OBJECT_HANDLE_TRACKING
|
|
PackageAccessTrackingOps::NAME_CookerBuildObject
|
|
#else
|
|
FName()
|
|
#endif
|
|
);
|
|
}
|
|
else
|
|
{
|
|
CookOnTheFlyServer.ClearActivePackage();
|
|
}
|
|
});
|
|
FAssetCompilingManager::Get().ProcessAsyncTasks(true);
|
|
FAssetCompilingManager::Get().OnPackageScopeEvent().Remove(EventHandle);
|
|
|
|
if (LastCookableObjectTickTime + TickCookableObjectsFrameTime <= CurrentTime)
|
|
{
|
|
UE_SCOPED_COOKTIMER(TickCookableObjects);
|
|
FTickableCookObject::TickObjects(static_cast<float>(CurrentTime - LastCookableObjectTickTime),
|
|
false /* bTickComplete */);
|
|
LastCookableObjectTickTime = CurrentTime;
|
|
}
|
|
|
|
if (!bForce)
|
|
{
|
|
for (FPendingCookedPlatformData& Data : List)
|
|
{
|
|
if (Data.PollIsComplete())
|
|
{
|
|
// We are destructing all elements of List after the for loop is done; we leave
|
|
// the completed Data on List to be destructed.
|
|
--PendingCookedPlatformDataNum;
|
|
}
|
|
else
|
|
{
|
|
Data.UpdatePeriodMultiplier = FMath::Clamp(Data.UpdatePeriodMultiplier*2, 1,
|
|
PendingPlatformDataMaxUpdatePeriod);
|
|
int32 ContainerIndex = Data.UpdatePeriodMultiplier - 1;
|
|
while (PendingCookedPlatformDataLists.Num() <= ContainerIndex)
|
|
{
|
|
PendingCookedPlatformDataLists.Emplace();
|
|
PendingCookedPlatformDataLists.Last().Reserve(PendingPlatformDataReservationSize);
|
|
}
|
|
PendingCookedPlatformDataLists[ContainerIndex].Add(MoveTemp(Data));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// When called with bForce, we poll all PackageDatas in all lists, and do not update
|
|
// any PollPeriods.
|
|
PendingCookedPlatformDataLists.AddFront(MoveTemp(List));
|
|
for (FPendingCookedPlatformDataContainer& ForceList : PendingCookedPlatformDataLists)
|
|
{
|
|
for (int32 Index = 0; Index < ForceList.Num(); )
|
|
{
|
|
FPendingCookedPlatformData& Data = ForceList[Index];
|
|
if (Data.PollIsComplete())
|
|
{
|
|
ForceList.RemoveAtSwap(Index, EAllowShrinking::No);
|
|
--PendingCookedPlatformDataNum;
|
|
}
|
|
else
|
|
{
|
|
++Index;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPackageDatas::ClearCancelManager(FPackageData& PackageData)
|
|
{
|
|
ForEachPendingCookedPlatformData(
|
|
[&PackageData](FPendingCookedPlatformData& PendingCookedPlatformData)
|
|
{
|
|
if (&PendingCookedPlatformData.PackageData == &PackageData)
|
|
{
|
|
if (!PendingCookedPlatformData.PollIsComplete())
|
|
{
|
|
// Abandon it
|
|
PendingCookedPlatformData.Release();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void FPackageDatas::RemapTargetPlatforms(const TMap<ITargetPlatform*, ITargetPlatform*>& Remap)
|
|
{
|
|
LockAndEnumeratePackageDatas([&Remap](FPackageData* PackageData)
|
|
{
|
|
PackageData->RemapTargetPlatforms(Remap);
|
|
});
|
|
ForEachPendingCookedPlatformData([&Remap](FPendingCookedPlatformData& CookedPlatformData)
|
|
{
|
|
CookedPlatformData.RemapTargetPlatforms(Remap);
|
|
});
|
|
}
|
|
|
|
void FPackageDatas::DebugInstigator(FPackageData& PackageData)
|
|
{
|
|
if (ShowInstigatorPackageData == &PackageData)
|
|
{
|
|
TArray<FInstigator> Chain = CookOnTheFlyServer.GetInstigatorChain(PackageData.GetPackageName());
|
|
TStringBuilder<256> ChainText;
|
|
if (Chain.Num() == 0)
|
|
{
|
|
ChainText << TEXT("<NoInstigator>");
|
|
}
|
|
bool bFirst = true;
|
|
for (FInstigator& Instigator : Chain)
|
|
{
|
|
if (!bFirst) ChainText << TEXT(" <- ");
|
|
ChainText << TEXT("{ ") << Instigator.ToString() << TEXT(" }");
|
|
bFirst = false;
|
|
}
|
|
UE_LOG(LogCook, Display, TEXT("Instigator chain of %s: %s"),
|
|
*PackageData.GetPackageName().ToString(), ChainText.ToString());
|
|
}
|
|
UpdateThreadsafePackageData(PackageData);
|
|
}
|
|
|
|
void FRequestQueue::Empty()
|
|
{
|
|
RestartedRequests.Empty();
|
|
DiscoveryQueue.Empty();
|
|
BuildDependencyDiscoveryQueue.Empty();
|
|
RequestClusters.Empty();
|
|
RequestFencePackageListeners.Empty();
|
|
NormalRequests.Empty();
|
|
UrgentRequests.Empty();
|
|
}
|
|
|
|
bool FRequestQueue::IsEmpty() const
|
|
{
|
|
return Num() == 0;
|
|
}
|
|
|
|
uint32 FRequestQueue::Num() const
|
|
{
|
|
uint32 Count = RestartedRequests.Num() + ReadyRequestsNum();
|
|
for (const TUniquePtr<FRequestCluster>& RequestCluster : RequestClusters)
|
|
{
|
|
Count += RequestCluster->NumPackageDatas();
|
|
}
|
|
return Count;
|
|
}
|
|
|
|
bool FRequestQueue::Contains(const FPackageData* InPackageData) const
|
|
{
|
|
FPackageData* PackageData = const_cast<FPackageData*>(InPackageData);
|
|
if (RestartedRequests.Contains(PackageData) || NormalRequests.Contains(PackageData) ||
|
|
UrgentRequests.Contains(PackageData))
|
|
{
|
|
return true;
|
|
}
|
|
for (const TUniquePtr<FRequestCluster>& RequestCluster : RequestClusters)
|
|
{
|
|
if (RequestCluster->Contains(PackageData))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
uint32 FRequestQueue::RemoveRequestExceptFromCluster(FPackageData* PackageData, FRequestCluster* ExceptFromCluster)
|
|
{
|
|
uint32 OriginalNum = Num();
|
|
RestartedRequests.Remove(PackageData);
|
|
NormalRequests.Remove(PackageData);
|
|
UrgentRequests.Remove(PackageData);
|
|
for (TUniquePtr<FRequestCluster>& RequestCluster : RequestClusters)
|
|
{
|
|
if (RequestCluster.Get() != ExceptFromCluster)
|
|
{
|
|
RequestCluster->RemovePackageData(PackageData);
|
|
}
|
|
}
|
|
uint32 Result = OriginalNum - Num();
|
|
check(Result == 0 || Result == 1);
|
|
return Result;
|
|
}
|
|
|
|
uint32 FRequestQueue::RemoveRequest(FPackageData* PackageData)
|
|
{
|
|
return RemoveRequestExceptFromCluster(PackageData, nullptr);
|
|
}
|
|
|
|
uint32 FRequestQueue::Remove(FPackageData* PackageData)
|
|
{
|
|
return RemoveRequest(PackageData);
|
|
}
|
|
|
|
bool FRequestQueue::IsReadyRequestsEmpty() const
|
|
{
|
|
return ReadyRequestsNum() == 0;
|
|
}
|
|
|
|
bool FRequestQueue::HasRequestsToExplore() const
|
|
{
|
|
return !RequestClusters.IsEmpty() | !RestartedRequests.IsEmpty() | !DiscoveryQueue.IsEmpty()
|
|
| !BuildDependencyDiscoveryQueue.IsEmpty() | !RequestFencePackageListeners.IsEmpty();
|
|
}
|
|
|
|
uint32 FRequestQueue::ReadyRequestsNum() const
|
|
{
|
|
return UrgentRequests.Num() + NormalRequests.Num();
|
|
}
|
|
|
|
FPackageData* FRequestQueue::PopReadyRequest()
|
|
{
|
|
if (auto Iterator = UrgentRequests.CreateIterator(); Iterator)
|
|
{
|
|
FPackageData* PackageData = *Iterator;
|
|
Iterator.RemoveCurrent();
|
|
return PackageData;
|
|
}
|
|
if (auto Iterator = NormalRequests.CreateIterator(); Iterator)
|
|
{
|
|
FPackageData* PackageData = *Iterator;
|
|
Iterator.RemoveCurrent();
|
|
return PackageData;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void FRequestQueue::AddRequest(FPackageData* PackageData, bool bForceUrgent)
|
|
{
|
|
RestartedRequests.Add(PackageData);
|
|
}
|
|
|
|
void FRequestQueue::AddReadyRequest(FPackageData* PackageData, bool bForceUrgent)
|
|
{
|
|
if (bForceUrgent || PackageData->GetUrgency() > EUrgency::Normal)
|
|
{
|
|
UrgentRequests.Add(PackageData);
|
|
}
|
|
else
|
|
{
|
|
NormalRequests.Add(PackageData);
|
|
}
|
|
}
|
|
|
|
void FRequestQueue::UpdateUrgency(FPackageData* PackageData, EUrgency OldUrgency, EUrgency NewUrgency)
|
|
{
|
|
if (OldUrgency == EUrgency::Normal)
|
|
{
|
|
if (NormalRequests.Remove(PackageData) > 0)
|
|
{
|
|
UrgentRequests.Add(PackageData);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (UrgentRequests.Remove(PackageData) > 0)
|
|
{
|
|
NormalRequests.Add(PackageData);
|
|
}
|
|
}
|
|
// The other subcontainers do not handle urgency types differently
|
|
}
|
|
|
|
void FRequestQueue::AddRequestFenceListener(FName PackageName)
|
|
{
|
|
RequestFencePackageListeners.Add(PackageName);
|
|
}
|
|
|
|
void FRequestQueue::NotifyRequestFencePassed(FPackageDatas& PackageDatas)
|
|
{
|
|
for (FName PackageName : RequestFencePackageListeners)
|
|
{
|
|
FPackageData* PackageData = PackageDatas.FindPackageDataByPackageName(PackageName);
|
|
if (PackageData)
|
|
{
|
|
TRefCountPtr<FGenerationHelper> GenerationHelper = PackageData->GetGenerationHelper();
|
|
if (GenerationHelper)
|
|
{
|
|
GenerationHelper->OnRequestFencePassed(PackageDatas.GetCookOnTheFlyServer());
|
|
}
|
|
}
|
|
}
|
|
RequestFencePackageListeners.Empty();
|
|
}
|
|
|
|
FPoppedPackageDataScope::FPoppedPackageDataScope(FPackageData& InPackageData)
|
|
#if COOK_CHECKSLOW_PACKAGEDATA
|
|
: PackageData(InPackageData)
|
|
#endif
|
|
{
|
|
}
|
|
|
|
#if COOK_CHECKSLOW_PACKAGEDATA
|
|
FPoppedPackageDataScope::~FPoppedPackageDataScope()
|
|
{
|
|
PackageData.CheckInContainer();
|
|
}
|
|
#endif
|
|
|
|
const TCHAR* LexToString(ECachedCookedPlatformDataEvent Value)
|
|
{
|
|
switch (Value)
|
|
{
|
|
case ECachedCookedPlatformDataEvent::None:
|
|
return TEXT("None");
|
|
case ECachedCookedPlatformDataEvent::BeginCacheForCookedPlatformDataCalled:
|
|
return TEXT("BeginCacheForCookedPlatformDataCalled");
|
|
case ECachedCookedPlatformDataEvent::IsCachedCookedPlatformDataLoadedCalled:
|
|
return TEXT("IsCachedCookedPlatformDataLoadedCalled");
|
|
case ECachedCookedPlatformDataEvent::IsCachedCookedPlatformDataLoadedReturnedTrue:
|
|
return TEXT("IsCachedCookedPlatformDataLoadedReturnedTrue");
|
|
case ECachedCookedPlatformDataEvent::ClearCachedCookedPlatformDataCalled:
|
|
return TEXT("ClearCachedCookedPlatformDataCalled");
|
|
case ECachedCookedPlatformDataEvent::ClearAllCachedCookedPlatformDataCalled:
|
|
return TEXT("ClearAllCachedCookedPlatformDataCalled");
|
|
default: return TEXT("Invalid");
|
|
}
|
|
}
|
|
|
|
void FPackageDatas::CachedCookedPlatformDataObjectsPostGarbageCollect(
|
|
const TSet<UObject*>& SaveQueueObjectsThatStillExist)
|
|
{
|
|
for (TMap<UObject*, FCachedCookedPlatformDataState>::TIterator Iter(this->CachedCookedPlatformDataObjects);
|
|
Iter; ++Iter)
|
|
{
|
|
if (!SaveQueueObjectsThatStillExist.Contains(Iter->Key))
|
|
{
|
|
Iter.RemoveCurrent();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPackageDatas::CachedCookedPlatformDataObjectsOnDestroyedOutsideOfGC(const UObject* DestroyedObject)
|
|
{
|
|
CachedCookedPlatformDataObjects.Remove(DestroyedObject);
|
|
}
|
|
|
|
void FCachedCookedPlatformDataState::AddRefFrom(FPackageData* PackageData)
|
|
{
|
|
// Most objects will only be referenced by a single package.
|
|
// The exceptions:
|
|
// 1) Generator Packages that move the object from the generator into a generated
|
|
// 2) Bugs
|
|
// Even in case (1), the number of referencers will be 2.
|
|
// We therefore for now just use a flat array and AddUnique, to minimize memory and performance in the
|
|
// usual case on only a single referencer.
|
|
PackageDatas.AddUnique(PackageData);
|
|
}
|
|
|
|
void FCachedCookedPlatformDataState::ReleaseFrom(FPackageData* PackageData)
|
|
{
|
|
PackageDatas.Remove(PackageData);
|
|
}
|
|
|
|
bool FCachedCookedPlatformDataState::IsReferenced() const
|
|
{
|
|
return !PackageDatas.IsEmpty();
|
|
}
|
|
|
|
void FCachedCookedPlatformDataState::Construct(UObject* Object)
|
|
{
|
|
WeakPtr = Object;
|
|
bInitialized = true;
|
|
}
|
|
|
|
FCachedCookedPlatformDataState& FMapOfCachedCookedPlatformDataState::Add(
|
|
UObject* Object, const FCachedCookedPlatformDataState& Value)
|
|
{
|
|
FCachedCookedPlatformDataState* Existing = &Super::FindOrAdd(Object);
|
|
*Existing = Value;
|
|
if (!Existing->bInitialized)
|
|
{
|
|
Existing->Construct(Object);
|
|
}
|
|
return *Existing;
|
|
}
|
|
|
|
FCachedCookedPlatformDataState& FMapOfCachedCookedPlatformDataState::FindOrAdd(UObject* Object)
|
|
{
|
|
FCachedCookedPlatformDataState* Existing = &Super::FindOrAdd(Object);
|
|
if (!Existing->bInitialized)
|
|
{
|
|
Existing->Construct(Object);
|
|
}
|
|
if (!Existing->WeakPtr.Get())
|
|
{
|
|
Remove(Object);
|
|
Existing = &Super::FindOrAdd(Object);
|
|
}
|
|
return *Existing;
|
|
}
|
|
|
|
FCachedCookedPlatformDataState* FMapOfCachedCookedPlatformDataState::Find(UObject* Object)
|
|
{
|
|
FCachedCookedPlatformDataState* Existing = Super::Find(Object);
|
|
if (!Existing)
|
|
{
|
|
return nullptr;
|
|
}
|
|
if (!Existing->WeakPtr.Get())
|
|
{
|
|
Remove(Object);
|
|
return nullptr;
|
|
}
|
|
return Existing;
|
|
}
|
|
|
|
FCachedCookedPlatformDataState& FMapOfCachedCookedPlatformDataState::FindOrAddByHash(uint32 KeyHash, UObject* Object)
|
|
{
|
|
FCachedCookedPlatformDataState* Existing = &Super::FindOrAddByHash(KeyHash, Object);
|
|
if (!Existing->bInitialized)
|
|
{
|
|
Existing->Construct(Object);
|
|
}
|
|
if (!Existing->WeakPtr.Get())
|
|
{
|
|
Remove(Object);
|
|
Existing = &Super::FindOrAddByHash(KeyHash, Object);
|
|
}
|
|
return *Existing;
|
|
}
|
|
|
|
FCachedCookedPlatformDataState* FMapOfCachedCookedPlatformDataState::FindByHash(uint32 KeyHash, UObject* Object)
|
|
{
|
|
FCachedCookedPlatformDataState* Existing = Super::FindByHash(KeyHash, Object);
|
|
if (!Existing)
|
|
{
|
|
return nullptr;
|
|
}
|
|
if (!Existing->WeakPtr.Get())
|
|
{
|
|
Remove(Object);
|
|
return nullptr;
|
|
}
|
|
return Existing;
|
|
}
|
|
|
|
} // namespace UE::Cook
|