// Copyright Epic Games, Inc. All Rights Reserved. #include "CookRequests.h" #include "Algo/Find.h" #include "CookOnTheSide/CookLog.h" #include "CookPackageData.h" #include "CookPlatformManager.h" #include "Cooker/CookTypes.h" #include "HAL/Platform.h" #include "Interfaces/ITargetPlatform.h" #include "Logging/LogCategory.h" #include "Logging/LogMacros.h" #include "Misc/AssertionMacros.h" #include "Misc/ScopeLock.h" #include "Templates/UnrealTemplate.h" #include "Trace/Detail/Channel.h" #include "UObject/UnrealNames.h" namespace UE::Cook { ////////////////////////////////////////////////////////////////////////// // FFilePlatformRequest FFilePlatformRequest::FFilePlatformRequest(const FName& InFilename, FInstigator&& InInstigator) :FFilePlatformRequest(InFilename, MoveTemp(InInstigator), TArray(), FCompletionCallback()) { } FFilePlatformRequest::FFilePlatformRequest(const FName& InFilename, FInstigator&& InInstigator, const ITargetPlatform* InPlatform, FCompletionCallback&& InCompletionCallback) :FFilePlatformRequest(InFilename, MoveTemp(InInstigator), TArrayView({ InPlatform }), MoveTemp(InCompletionCallback)) { } FFilePlatformRequest::FFilePlatformRequest(const FName& InFilename, FInstigator&& InInstigator, const TArrayView& InPlatforms, FCompletionCallback&& InCompletionCallback) :FFilePlatformRequest(InFilename, MoveTemp(InInstigator), TArray(InPlatforms.GetData(), InPlatforms.Num()), MoveTemp(InCompletionCallback)) { } FFilePlatformRequest::FFilePlatformRequest(const FName& InFilename, FInstigator&& InInstigator, TArray&& InPlatforms, FCompletionCallback&& InCompletionCallback) : Platforms(MoveTemp(InPlatforms)) , CompletionCallback(MoveTemp(InCompletionCallback)) , Instigator(MoveTemp(InInstigator)) { SetFilename(InFilename.ToString()); } FFilePlatformRequest::FFilePlatformRequest(const FFilePlatformRequest& InFilePlatformRequest) : Filename(InFilePlatformRequest.Filename) , Platforms(InFilePlatformRequest.Platforms) , Instigator(InFilePlatformRequest.Instigator) , bUrgent(InFilePlatformRequest.bUrgent) { check(!InFilePlatformRequest.CompletionCallback); // CompletionCallbacks can not be copied, so the caller's intent is not clear in this constructor if the input has one } FFilePlatformRequest::FFilePlatformRequest(FFilePlatformRequest&& InFilePlatformRequest) : Filename(MoveTemp(InFilePlatformRequest.Filename)) , Platforms(MoveTemp(InFilePlatformRequest.Platforms)) , CompletionCallback(MoveTemp(InFilePlatformRequest.CompletionCallback)) , Instigator(MoveTemp(InFilePlatformRequest.Instigator)) , bUrgent(InFilePlatformRequest.bUrgent) { } FFilePlatformRequest& FFilePlatformRequest::operator=(FFilePlatformRequest&& InFileRequest) { Filename = MoveTemp(InFileRequest.Filename); Platforms = MoveTemp(InFileRequest.Platforms); check(!CompletionCallback); // We don't support holding multiple completion callbacks CompletionCallback = MoveTemp(InFileRequest.CompletionCallback); Instigator = MoveTemp(InFileRequest.Instigator); bUrgent = InFileRequest.bUrgent; return *this; } void FFilePlatformRequest::SetFilename(FString InFilename) { Filename = FPackageDatas::GetStandardFileName(InFilename); } const FName& FFilePlatformRequest::GetFilename() const { return Filename; } FInstigator& FFilePlatformRequest::GetInstigator() { return Instigator; } const TArray& FFilePlatformRequest::GetPlatforms() const { return Platforms; } TArray& FFilePlatformRequest::GetPlatforms() { return Platforms; } void FFilePlatformRequest::RemovePlatform(const ITargetPlatform* Platform) { Platforms.Remove(Platform); } void FFilePlatformRequest::AddPlatform(const ITargetPlatform* Platform) { check(Platform != nullptr); Platforms.Add(Platform); } bool FFilePlatformRequest::HasPlatform(const ITargetPlatform* Platform) const { return Platforms.Find(Platform) != INDEX_NONE; } FCompletionCallback& FFilePlatformRequest::GetCompletionCallback() { return CompletionCallback; } bool FFilePlatformRequest::IsValid() const { return Filename != NAME_None; } void FFilePlatformRequest::Clear() { Filename = TEXT(""); Platforms.Empty(); } bool FFilePlatformRequest::operator==(const FFilePlatformRequest& InFileRequest) const { if (InFileRequest.Filename == Filename) { if (InFileRequest.Platforms == Platforms) { return true; } } return false; } FString FFilePlatformRequest::ToString() const { FString Result = FString::Printf(TEXT("%s;"), *Filename.ToString()); for (const ITargetPlatform* Platform : Platforms) { Result += FString::Printf(TEXT("%s,"), *Platform->PlatformName()); } return Result; } void FFilePlatformRequest::RemapTargetPlatforms(const TMap& Remap) { RemapArrayElements(Platforms, Remap); } ////////////////////////////////////////////////////////////////////////// // FExternalRequests int32 FExternalRequests::GetNumRequests() const { return RequestCount.load(std::memory_order_relaxed); } bool FExternalRequests::HasRequests() const { return GetNumRequests() > 0; } void FExternalRequests::AddCallback(FSchedulerCallback&& Callback) { FScopeLock ScopeLock(&RequestLock); Callbacks.Add(MoveTemp(Callback)); RequestCount.fetch_add(1, std::memory_order_relaxed); } void FExternalRequests::EnqueueUnique(FFilePlatformRequest&& FileRequest, bool bForceFrontOfQueue) { FScopeLock ScopeLock(&RequestLock); FName Filename = FileRequest.GetFilename(); FFilePlatformRequest* ExistingRequest = RequestMap.Find(Filename); if (!ExistingRequest) { RequestMap.Add(Filename, MoveTemp(FileRequest)); if (bForceFrontOfQueue) { Queue.AddFront(Filename); } else { Queue.Add(Filename); } RequestCount.fetch_add(1, std::memory_order_relaxed); } else { if (FileRequest.GetCompletionCallback()) { check(!ExistingRequest->GetCompletionCallback()); // We don't support multiple callbacks ExistingRequest->GetCompletionCallback() = MoveTemp(FileRequest.GetCompletionCallback()); } // add the requested platforms to the platform list for (const ITargetPlatform* Platform : FileRequest.GetPlatforms()) { ExistingRequest->GetPlatforms().AddUnique(Platform); } if (bForceFrontOfQueue) { FName* ExistingName = Algo::Find(Queue, Filename); int32 Index = Queue.ConvertPointerToIndex(ExistingName); check(Index != INDEX_NONE); if (Index != 0) { Queue[Index] = Queue[0]; Queue[0] = Filename; } } } } EExternalRequestType FExternalRequests::DequeueNextCluster(TArray& OutCallbacks, TArray& OutBuildRequests) { FScopeLock ScopeLock(&RequestLock); if (ThreadUnsafeDequeueCallbacks(OutCallbacks)) { return EExternalRequestType::Callback; } else if (Queue.Num()) { OutBuildRequests.Reserve(Queue.Num() + OutBuildRequests.Num()); int32 NumDequeued = Queue.Num(); while (Queue.Num()) { FName Filename = Queue.PopFrontValue(); OutBuildRequests.Add(RequestMap.FindAndRemoveChecked(Filename)); } RequestCount.fetch_add(-NumDequeued, std::memory_order_relaxed); return EExternalRequestType::Cook; } else { return EExternalRequestType::None; } } bool FExternalRequests::DequeueCallbacks(TArray& OutCallbacks) { FScopeLock ScopeLock(&RequestLock); return ThreadUnsafeDequeueCallbacks(OutCallbacks); } bool FExternalRequests::ThreadUnsafeDequeueCallbacks(TArray& OutCallbacks) { if (Callbacks.Num() > 0) { OutCallbacks = MoveTemp(Callbacks); Callbacks.Empty(); RequestCount.fetch_add(-OutCallbacks.Num(), std::memory_order_relaxed); return true; } else { return false; } } void FExternalRequests::EmptyRequests() { FScopeLock ScopeLock(&RequestLock); Queue.Empty(); RequestMap.Empty(); Callbacks.Empty(); RequestCount.store(0, std::memory_order_relaxed); } void FExternalRequests::DequeueAll(TArray& OutCallbacks, TArray& OutCookRequests) { FScopeLock ScopeLock(&RequestLock); OutCallbacks = MoveTemp(Callbacks); Callbacks.Empty(); for (TPair& Request : RequestMap) { OutCookRequests.Add(MoveTemp(Request.Value)); } RequestMap.Empty(); Queue.Empty(); RequestCount.store(0, std::memory_order_relaxed); } void FExternalRequests::OnRemoveSessionPlatform(const ITargetPlatform* TargetPlatform) { FScopeLock ScopeLock(&RequestLock); // The caller should not be removing platforms if we have an active request referencing that platform, but in case they did, remove the platform // from all pending requests for (TPair& kvpair : RequestMap) { FFilePlatformRequest& Request = kvpair.Value; Request.GetPlatforms().Remove(TargetPlatform); if (Request.GetPlatforms().Num() == 0) { UE_LOG(LogCook, Error, TEXT("RemovePlatform call has left an empty list of platforms requested in CookOnTheSide request.")); } } } void FExternalRequests::RemapTargetPlatforms(const TMap& Remap) { for (TPair& KVPair : RequestMap) { KVPair.Value.RemapTargetPlatforms(Remap); } } void FExternalRequests::LogAllRequestedFiles() { UE_LOG(LogCook, Display, TEXT("Listing initially requested set of files to be cooked (does not include list of transitive dependencies):")); for (const TPair& KVPair : RequestMap) { UE_LOG(LogCook, Display, TEXT("%s"), *KVPair.Value.ToString()); } UE_LOG(LogCook, Display, TEXT("----------------------------------------------------------\n\n")); } }