// Copyright Epic Games, Inc. All Rights Reserved. #include "CookWorkerServer.h" #include "Algo/Find.h" #include "Commandlets/AssetRegistryGenerator.h" #include "Cooker/CompactBinaryTCP.h" #include "Cooker/CookDirector.h" #include "Cooker/CookGenerationHelper.h" #include "Cooker/CookLogPrivate.h" #include "Cooker/CookPackageData.h" #include "Cooker/CookPlatformManager.h" #include "HAL/Platform.h" #include "HAL/PlatformProcess.h" #include "Interfaces/ITargetPlatform.h" #include "Interfaces/ITargetPlatformManagerModule.h" #include "Math/NumericLimits.h" #include "Misc/AssertionMacros.h" #include "Misc/Char.h" #include "Misc/FileHelper.h" #include "Misc/Optional.h" #include "Misc/Parse.h" #include "Misc/ScopeLock.h" #include "PackageResultsMessage.h" #include "PackageTracker.h" #include "UnrealEdMisc.h" namespace UE::Cook { FCookWorkerServer::FCookWorkerServer(FCookDirector& InDirector, int32 InProfileId, FWorkerId InWorkerId) : Director(InDirector) , COTFS(InDirector.COTFS) , ProfileId(InProfileId) , WorkerId(InWorkerId) { } FCookWorkerServer::~FCookWorkerServer() { FCommunicationScopeLock ScopeLock(this, ECookDirectorThread::CommunicateThread, ETickAction::Queue); checkf(PendingPackages.IsEmpty() && PackagesToAssign.IsEmpty(), TEXT("CookWorkerServer still has assigned packages when it is being destroyed; we will leak them and block the cook.")); if (ConnectStatus == EConnectStatus::Connected || ConnectStatus == EConnectStatus::PumpingCookComplete || ConnectStatus == EConnectStatus::WaitForDisconnect) { UE_LOG(LogCook, Error, TEXT("CookWorkerServer %d was destroyed before it finished Disconnect. The remote process may linger and may interfere with writes of future packages."), ProfileId); } DetachFromRemoteProcess(EWorkerDetachType::StillRunning); } void FCookWorkerServer::DetachFromRemoteProcess(EWorkerDetachType DetachType) { if (Socket != nullptr) { FCoreDelegates::OnMultiprocessWorkerDetached.Broadcast({WorkerId.GetMultiprocessId(), DetachType != EWorkerDetachType::Dismissed}); } Sockets::CloseSocket(Socket); CookWorkerHandle = FProcHandle(); CookWorkerProcessId = 0; bTerminateImmediately = false; SendBuffer.Reset(); ReceiveBuffer.Reset(); if (bNeedCrashDiagnostics) { SendCrashDiagnostics(); } } bool TryParseLogCategoryVerbosityMessage(FStringView Line, FName& OutCategory, ELogVerbosity::Type& OutVerbosity, FStringView& OutMessage) { TPair VerbosityMarkers[]{ { TEXTVIEW(": Fatal:"), ELogVerbosity::Fatal }, { TEXTVIEW(": Error:"), ELogVerbosity::Error }, { TEXTVIEW(": Warning:"), ELogVerbosity::Warning}, { TEXTVIEW(": Display:"), ELogVerbosity::Display }, { TEXTVIEW(":"), ELogVerbosity::Log }, }; // Find the first colon not in brackets and look for ": :". This is complicated by Log verbosity not // printing out the Verbosity: // [2023.03.20-16.32.48:878][ 0]LogCook: MessageText // [2023.03.20-16.32.48:878][ 0]LogCook: Display: MessageText int32 FirstColon = INDEX_NONE; int32 SubExpressionLevel = 0; for (int32 Index = 0; Index < Line.Len(); ++Index) { switch (Line[Index]) { case '[': ++SubExpressionLevel; break; case ']': if (SubExpressionLevel > 0) { --SubExpressionLevel; } break; case ':': if (SubExpressionLevel == 0) { FirstColon = Index; } break; default: break; } if (FirstColon != INDEX_NONE) { break; } } if (FirstColon == INDEX_NONE) { return false; } FStringView RestOfLine = FStringView(Line).RightChop(FirstColon); for (TPair& VerbosityPair : VerbosityMarkers) { if (RestOfLine.StartsWith(VerbosityPair.Key, ESearchCase::IgnoreCase)) { int32 CategoryEndIndex = FirstColon; while (CategoryEndIndex > 0 && FChar::IsWhitespace(Line[CategoryEndIndex - 1])) --CategoryEndIndex; int32 CategoryStartIndex = CategoryEndIndex > 0 ? CategoryEndIndex - 1 : CategoryEndIndex; while (CategoryStartIndex > 0 && FChar::IsAlnum(Line[CategoryStartIndex - 1])) --CategoryStartIndex; int32 MessageStartIndex = CategoryEndIndex + VerbosityPair.Key.Len(); while (MessageStartIndex < Line.Len() && FChar::IsWhitespace(Line[MessageStartIndex])) ++MessageStartIndex; OutCategory = FName(FStringView(Line).SubStr(CategoryStartIndex, CategoryEndIndex - CategoryStartIndex)); OutVerbosity = VerbosityPair.Value; OutMessage = FStringView(Line).SubStr(MessageStartIndex, Line.Len() - MessageStartIndex); return true; } } return false; } void FCookWorkerServer::SendCrashDiagnostics() { FString LogFileName = Director.GetWorkerLogFileName(ProfileId); UE_LOG(LogCook, Display, TEXT("LostConnection to CookWorker %d. Log messages written after communication loss:"), ProfileId); FString LogText; // To be able to open a file for read that might be open for write from another process, // we have to specify FILEREAD_AllowWrite int32 ReadFlags = FILEREAD_AllowWrite; bool bLoggedErrorMessage = false; if (!FFileHelper::LoadFileToString(LogText, *LogFileName, FFileHelper::EHashOptions::None, ReadFlags)) { UE_LOG(LogCook, Warning, TEXT("No log file found for CookWorker %d."), ProfileId); } else { FString LastSentHeartbeat = FString::Printf(TEXT("%.*s %d"), HeartbeatCategoryText.Len(), HeartbeatCategoryText.GetData(), LastReceivedHeartbeatNumber); int32 StartIndex = INDEX_NONE; for (FStringView MarkerText : { FStringView(LastSentHeartbeat), HeartbeatCategoryText, TEXTVIEW("Connection to CookDirector successful") }) { StartIndex = UE::String::FindLast(LogText, MarkerText); if (StartIndex >= 0) { break; } } const TCHAR* StartText = *LogText; FString Line; if (StartIndex != INDEX_NONE) { // Skip the MarkerLine StartText = *LogText + StartIndex; FParse::Line(&StartText, Line); if (*StartText == '\0') { // If there was no line after the MarkerLine, write out the MarkerLine StartText = *LogText + StartIndex; } } while (FParse::Line(&StartText, Line)) { // Get the Category,Severity,Message out of each line and log it with that Category and Severity // TODO: Change the CookWorkers to write out structured logs rather than interpreting their text logs FName Category; ELogVerbosity::Type Verbosity; FStringView Message; if (!TryParseLogCategoryVerbosityMessage(Line, Category, Verbosity, Message)) { Category = LogCook.GetCategoryName(); Verbosity = ELogVerbosity::Display; Message = Line; } // Downgrade Fatals in our local verbosity from Fatal to Error to avoid crashing the CookDirector if (Verbosity == ELogVerbosity::Fatal) { Verbosity = ELogVerbosity::Error; } bLoggedErrorMessage |= Verbosity == ELogVerbosity::Error; FMsg::Logf(__FILE__, __LINE__, Category, Verbosity, TEXT("[CookWorker %d]: %.*s"), ProfileId, Message.Len(), Message.GetData()); } } if (!CrashDiagnosticsError.IsEmpty()) { if (!bLoggedErrorMessage) { UE_LOG(LogCook, Error, TEXT("%s"), *CrashDiagnosticsError); } else { // When we already logged an error from the crashed worker, log the what-went-wrong as a warning rather // than an error, to avoid making it seem like a separate issue. UE_LOG(LogCook, Warning, TEXT("%s"), *CrashDiagnosticsError); } } bNeedCrashDiagnostics = false; CrashDiagnosticsError.Empty(); } void FCookWorkerServer::ShutdownRemoteProcess() { EWorkerDetachType DetachType = EWorkerDetachType::Dismissed; if (CookWorkerHandle.IsValid()) { FPlatformProcess::TerminateProc(CookWorkerHandle, /* bKillTree */true); DetachType = EWorkerDetachType::ForceTerminated; } DetachFromRemoteProcess(DetachType); } void FCookWorkerServer::AppendAssignments(TArrayView Assignments, TMap&& ExtraDatas, TArrayView InfoPackages, ECookDirectorThread TickThread) { FCommunicationScopeLock ScopeLock(this, TickThread, ETickAction::Queue); ++PackagesAssignedFenceMarker; PackagesToAssign.Append(Assignments); PackagesToAssignExtraDatas.Append(MoveTemp(ExtraDatas)); PackagesToAssignInfoPackages.Append(InfoPackages); } void FCookWorkerServer::AbortAllAssignments(TSet& OutPendingPackages, ECookDirectorThread TickThread, int32 CurrentHeartbeat) { FCommunicationScopeLock ScopeLock(this, TickThread, ETickAction::Queue); AbortAllAssignmentsInLock(OutPendingPackages, CurrentHeartbeat); } void FCookWorkerServer::AbortAllAssignmentsInLock(TSet& OutPendingPackages, int32 CurrentHeartbeat) { if (PendingPackages.Num()) { if (ConnectStatus == EConnectStatus::Connected) { TArray PackageNames; PackageNames.Reserve(PendingPackages.Num()); for (FPackageData* PackageData : PendingPackages) { PackageNames.Add(PackageData->GetPackageName()); } SendMessageInLock(FAbortPackagesMessage(MoveTemp(PackageNames))); } OutPendingPackages.Append(MoveTemp(PendingPackages)); PendingPackages.Empty(); } OutPendingPackages.Append(PackagesToAssign); PackagesToAssign.Empty(); PackagesToAssignExtraDatas.Empty(); PackagesToAssignInfoPackages.Empty(); ++PackagesRetiredFenceMarker; } void FCookWorkerServer::AbortAssignment(FPackageData& PackageData, ECookDirectorThread TickThread, int32 CurrentHeartbeat, ENotifyRemote NotifyRemote) { FPackageData* PackageDataPtr = &PackageData; AbortAssignments(TConstArrayView(&PackageDataPtr, 1), TickThread, CurrentHeartbeat, NotifyRemote); } void FCookWorkerServer::AbortAssignments(TConstArrayView PackageDatas, ECookDirectorThread TickThread, int32 CurrentHeartbeat, ENotifyRemote NotifyRemote) { FCommunicationScopeLock ScopeLock(this, TickThread, ETickAction::Queue); TArray PackageNamesToMessage; bool bSignalRemote = ConnectStatus == EConnectStatus::Connected && NotifyRemote == ENotifyRemote::NotifyRemote; for (FPackageData* PackageData : PackageDatas) { if (PendingPackages.Remove(PackageData)) { if (bSignalRemote) { PackageNamesToMessage.Add(PackageData->GetPackageName()); } } PackagesToAssign.Remove(PackageData); PackagesToAssignExtraDatas.Remove(PackageData); // We don't remove InfoPackages from PackagesToAssignInfoPackages because it would be too hard to calculate, // and it's not a problem to send extra InfoPackages. } ++PackagesRetiredFenceMarker; if (!PackageNamesToMessage.IsEmpty()) { SendMessageInLock(FAbortPackagesMessage(MoveTemp(PackageNamesToMessage))); } LastAbortHeartbeatNumber = CurrentHeartbeat; } void FCookWorkerServer::AbortWorker(TSet& OutPendingPackages, ECookDirectorThread TickThread, int32 CurrentHeartbeat) { FCommunicationScopeLock ScopeLock(this, TickThread, ETickAction::Tick); AbortAllAssignmentsInLock(OutPendingPackages, CurrentHeartbeat); switch (ConnectStatus) { case EConnectStatus::Uninitialized: // Fall through case EConnectStatus::WaitForConnect: SendToState(EConnectStatus::LostConnection); break; case EConnectStatus::Connected: // Fall through case EConnectStatus::PumpingCookComplete: { SendMessageInLock(FAbortWorkerMessage(FAbortWorkerMessage::EType::Abort)); SendToState(EConnectStatus::WaitForDisconnect); break; } default: break; } } void FCookWorkerServer::SendToState(EConnectStatus TargetStatus) { switch (TargetStatus) { case EConnectStatus::WaitForConnect: ConnectStartTimeSeconds = FPlatformTime::Seconds(); ConnectTestStartTimeSeconds = ConnectStartTimeSeconds; break; case EConnectStatus::WaitForDisconnect: ConnectStartTimeSeconds = FPlatformTime::Seconds(); ConnectTestStartTimeSeconds = ConnectStartTimeSeconds; break; case EConnectStatus::PumpingCookComplete: ConnectStartTimeSeconds = FPlatformTime::Seconds(); ConnectTestStartTimeSeconds = ConnectStartTimeSeconds; break; case EConnectStatus::LostConnection: DetachFromRemoteProcess(bNeedCrashDiagnostics ? EWorkerDetachType::Crashed : EWorkerDetachType::Dismissed); break; default: break; } ConnectStatus = TargetStatus; } bool FCookWorkerServer::IsConnected() const { FScopeLock CommunicationScopeLock(&CommunicationLock); return ConnectStatus == EConnectStatus::Connected; } bool FCookWorkerServer::IsShuttingDown() const { FScopeLock CommunicationScopeLock(&CommunicationLock); return ConnectStatus == EConnectStatus::PumpingCookComplete || ConnectStatus == EConnectStatus::WaitForDisconnect || ConnectStatus == EConnectStatus::LostConnection; } bool FCookWorkerServer::IsFlushingBeforeShutdown() const { FScopeLock CommunicationScopeLock(&CommunicationLock); return ConnectStatus == EConnectStatus::PumpingCookComplete; } bool FCookWorkerServer::IsShutdownComplete() const { FScopeLock CommunicationScopeLock(&CommunicationLock); return ConnectStatus == EConnectStatus::LostConnection; } int32 FCookWorkerServer::NumAssignments() const { FScopeLock CommunicationScopeLock(&CommunicationLock); return PackagesToAssign.Num() + PendingPackages.Num(); } bool FCookWorkerServer::HasMessages() const { FScopeLock CommunicationScopeLock(&CommunicationLock); return !ReceiveMessages.IsEmpty(); } int32 FCookWorkerServer::GetLastReceivedHeartbeatNumber() const { FScopeLock CommunicationScopeLock(&CommunicationLock); return LastReceivedHeartbeatNumber; } void FCookWorkerServer::SetLastReceivedHeartbeatNumberInLock(int32 InHeartbeatNumber) { LastReceivedHeartbeatNumber = InHeartbeatNumber; } int32 FCookWorkerServer::GetPackagesAssignedFenceMarker() const { FScopeLock CommunicationScopeLock(&CommunicationLock); return PackagesAssignedFenceMarker; } int32 FCookWorkerServer::GetPackagesRetiredFenceMarker() const { FScopeLock CommunicationScopeLock(&CommunicationLock); return PackagesRetiredFenceMarker; } bool FCookWorkerServer::TryHandleConnectMessage(FWorkerConnectMessage& Message, FSocket* InSocket, TArray&& OtherPacketMessages, ECookDirectorThread TickThread) { FCommunicationScopeLock ScopeLock(this, TickThread, ETickAction::Tick); if (ConnectStatus != EConnectStatus::WaitForConnect) { return false; } check(!Socket); Socket = InSocket; SendToState(EConnectStatus::Connected); UE_LOG(LogCook, Display, TEXT("CookWorker %d connected after %.3fs."), ProfileId, static_cast(FPlatformTime::Seconds() - ConnectStartTimeSeconds)); for (UE::CompactBinaryTCP::FMarshalledMessage& OtherMessage : OtherPacketMessages) { ReceiveMessages.Add(MoveTemp(OtherMessage)); } HandleReceiveMessagesInternal(); const FInitialConfigMessage& InitialConfigMessage = Director.GetInitialConfigMessage(); OrderedSessionPlatforms = InitialConfigMessage.GetOrderedSessionPlatforms(); OrderedSessionAndSpecialPlatforms.Reset(OrderedSessionPlatforms.Num() + 1); OrderedSessionAndSpecialPlatforms.Append(OrderedSessionPlatforms); OrderedSessionAndSpecialPlatforms.Add(CookerLoadingPlatformKey); SendMessageInLock(InitialConfigMessage); return true; } void FCookWorkerServer::TickCommunication(ECookDirectorThread TickThread) { FCommunicationScopeLock ScopeLock(this, TickThread, ETickAction::Tick); for (;;) { switch (ConnectStatus) { case EConnectStatus::Uninitialized: LaunchProcess(); break; case EConnectStatus::WaitForConnect: TickWaitForConnect(); if (ConnectStatus == EConnectStatus::WaitForConnect) { return; // Try again later } break; case EConnectStatus::Connected: PumpReceiveMessages(); if (ConnectStatus == EConnectStatus::Connected) { SendPendingMessages(); PumpSendMessages(); return; // Tick duties complete; yield the tick } break; case EConnectStatus::PumpingCookComplete: { PumpReceiveMessages(); if (ConnectStatus == EConnectStatus::PumpingCookComplete) { PumpSendMessages(); constexpr float WaitForPumpCompleteTimeout = 10.f * 60; if (FPlatformTime::Seconds() - ConnectStartTimeSeconds <= WaitForPumpCompleteTimeout || IsCookIgnoreTimeouts()) { return; // Try again later } UE_LOG(LogCook, Error, TEXT("CookWorker process of CookWorkerServer %d failed to finalize its cook within %.0f seconds; we will tell it to shutdown."), ProfileId, WaitForPumpCompleteTimeout); SendMessageInLock(FAbortWorkerMessage(FAbortWorkerMessage::EType::Abort)); SendToState(EConnectStatus::WaitForDisconnect); } break; } case EConnectStatus::WaitForDisconnect: TickWaitForDisconnect(); if (ConnectStatus == EConnectStatus::WaitForDisconnect) { return; // Try again later } break; case EConnectStatus::LostConnection: return; // Nothing further to do default: checkNoEntry(); return; } } } void FCookWorkerServer::SignalHeartbeat(ECookDirectorThread TickThread, int32 HeartbeatNumber) { FCommunicationScopeLock ScopeLock(this, TickThread, ETickAction::Tick); switch (ConnectStatus) { case EConnectStatus::Connected: SendMessageInLock(FHeartbeatMessage(HeartbeatNumber)); break; default: break; } } void FCookWorkerServer::SignalCookComplete(ECookDirectorThread TickThread) { FCommunicationScopeLock ScopeLock(this, TickThread, ETickAction::Tick); switch (ConnectStatus) { case EConnectStatus::Uninitialized: // Fall through case EConnectStatus::WaitForConnect: SendToState(EConnectStatus::LostConnection); break; case EConnectStatus::Connected: SendMessageInLock(FAbortWorkerMessage(FAbortWorkerMessage::EType::CookComplete)); SendToState(EConnectStatus::PumpingCookComplete); break; default: break; // Already in a disconnecting state } } void FCookWorkerServer::LaunchProcess() { FCookDirector::FLaunchInfo LaunchInfo = Director.GetLaunchInfo(WorkerId, ProfileId); bool bShowCookWorkers = LaunchInfo.ShowWorkerOption == FCookDirector::EShowWorker::SeparateWindows; CookWorkerHandle = FPlatformProcess::CreateProc(*LaunchInfo.CommandletExecutable, *LaunchInfo.WorkerCommandLine, true /* bLaunchDetached */, !bShowCookWorkers /* bLaunchHidden */, !bShowCookWorkers /* bLaunchReallyHidden */, &CookWorkerProcessId, 0 /* PriorityModifier */, *FPaths::GetPath(LaunchInfo.CommandletExecutable), nullptr /* PipeWriteChild */); if (CookWorkerHandle.IsValid()) { UE_LOG(LogCook, Display, TEXT("CookWorkerServer %d launched CookWorker as WorkerId %d and PID %u with commandline \"%s\"."), ProfileId, WorkerId.GetRemoteIndex(), CookWorkerProcessId, *LaunchInfo.WorkerCommandLine); FCoreDelegates::OnMultiprocessWorkerCreated.Broadcast({WorkerId.GetMultiprocessId()}); SendToState(EConnectStatus::WaitForConnect); } else { // GetLastError information was logged by CreateProc CrashDiagnosticsError = FString::Printf( TEXT("CookWorkerCrash: Failed to create process for CookWorker %d. Assigned packages will be returned to the director."), ProfileId); bNeedCrashDiagnostics = true; SendToState(EConnectStatus::LostConnection); } } void FCookWorkerServer::TickWaitForConnect() { constexpr float TestProcessExistencePeriod = 1.f; constexpr float WaitForConnectTimeout = 60.f * 20; // When the Socket is assigned we leave the WaitForConnect state, and we set it to null before entering check(!Socket); double CurrentTime = FPlatformTime::Seconds(); if (CurrentTime - ConnectTestStartTimeSeconds > TestProcessExistencePeriod) { if (!FPlatformProcess::IsProcRunning(CookWorkerHandle)) { CrashDiagnosticsError = FString::Printf( TEXT("CookWorkerCrash: CookWorker %d process terminated before connecting. Assigned packages will be returned to the director."), ProfileId); bNeedCrashDiagnostics = true; SendToState(EConnectStatus::LostConnection); return; } ConnectTestStartTimeSeconds = FPlatformTime::Seconds(); } if (CurrentTime - ConnectStartTimeSeconds > WaitForConnectTimeout && !IsCookIgnoreTimeouts()) { CrashDiagnosticsError = FString::Printf( TEXT("CookWorkerCrash: CookWorker %d process failed to connect within %.0f seconds. Assigned packages will be returned to the director."), ProfileId, WaitForConnectTimeout); bNeedCrashDiagnostics = true; ShutdownRemoteProcess(); SendToState(EConnectStatus::LostConnection); return; } } void FCookWorkerServer::TickWaitForDisconnect() { constexpr float TestProcessExistencePeriod = 1.f; constexpr float WaitForDisconnectTimeout = 60.f * 10; double CurrentTime = FPlatformTime::Seconds(); if (CurrentTime - ConnectTestStartTimeSeconds > TestProcessExistencePeriod) { if (!FPlatformProcess::IsProcRunning(CookWorkerHandle)) { SendToState(EConnectStatus::LostConnection); return; } ConnectTestStartTimeSeconds = FPlatformTime::Seconds(); } // We might have been blocked from sending the disconnect, so keep trying to flush the buffer UE::CompactBinaryTCP::TryFlushBuffer(Socket, SendBuffer); TArray Messages; TryReadPacket(Socket, ReceiveBuffer, Messages); if (bTerminateImmediately || (CurrentTime - ConnectStartTimeSeconds > WaitForDisconnectTimeout && !IsCookIgnoreTimeouts())) { UE_CLOG(!bTerminateImmediately, LogCook, Warning, TEXT("CookWorker process of CookWorkerServer %d failed to disconnect within %.0f seconds; we will terminate it."), ProfileId, WaitForDisconnectTimeout); bNeedCrashDiagnostics = true; ShutdownRemoteProcess(); SendToState(EConnectStatus::LostConnection); } } void FCookWorkerServer::PumpSendMessages() { UE::CompactBinaryTCP::EConnectionStatus Status = UE::CompactBinaryTCP::TryFlushBuffer(Socket, SendBuffer); if (Status == UE::CompactBinaryTCP::EConnectionStatus::Failed) { UE_LOG(LogCook, Error, TEXT("CookWorkerCrash: CookWorker %d failed to write to socket, we will shutdown the remote process. Assigned packages will be returned to the director."), ProfileId); bNeedCrashDiagnostics = true; SendToState(EConnectStatus::WaitForDisconnect); bTerminateImmediately = true; } } void FCookWorkerServer::SendPendingMessages() { // If we aborted any packages, do not allow any new assignment messages to be sent until we receive an acknowledge of // the abort. This prevents us from incorrectly assuming a package results message that was sent before the abort is // the package results message after reassignment of that package, with new requested platforms, that we sent after // the abort. // Because we contractually are not allowed to send QueuedMessagesToSendAfterPackagesToAssign until after we have sent // the assignment message, do not allow those to be sent out either. if (LastReceivedHeartbeatNumber <= LastAbortHeartbeatNumber) { return; } SendPendingPackages(); for (UE::CompactBinaryTCP::FMarshalledMessage& MarshalledMessage : QueuedMessagesToSendAfterPackagesToAssign) { UE::CompactBinaryTCP::QueueMessage(SendBuffer, MoveTemp(MarshalledMessage)); } QueuedMessagesToSendAfterPackagesToAssign.Empty(); } void FCookWorkerServer::SendPendingPackages() { if (PackagesToAssign.IsEmpty()) { PackagesToAssignExtraDatas.Empty(); PackagesToAssignInfoPackages.Empty(); return; } LLM_SCOPE_BYTAG(Cooker_MPCook); TArray AssignDatas; AssignDatas.Reserve(PackagesToAssign.Num()); TBitArray<> SessionPlatformNeedsCommit; TArray ExistenceInfos; ExistenceInfos.Reserve(PackagesToAssignInfoPackages.Num()); ECookPhase CookPhase = Director.COTFS.GetCookPhase(); EReachability Reachability = CookPhase == ECookPhase::Cook ? EReachability::Runtime : EReachability::Build; for (FPackageData* PackageData : PackagesToAssign) { FAssignPackageData& AssignData = AssignDatas.Emplace_GetRef(); AssignData.ConstructData = PackageData->CreateConstructData(); AssignData.ParentGenerator = PackageData->GetParentGenerator(); AssignData.DoesGeneratedRequireGenerator = PackageData->DoesGeneratedRequireGenerator(); AssignData.Reachability = Reachability; AssignData.Instigator = PackageData->GetInstigator(Reachability); AssignData.Urgency = PackageData->GetUrgency(); SessionPlatformNeedsCommit.Init(false, OrderedSessionPlatforms.Num()); int32 PlatformIndex = 0; int32 NumNeedCommitPlatforms = 0; for (const ITargetPlatform* SessionPlatform : OrderedSessionPlatforms) { FPackagePlatformData* PlatformData = PackageData->FindPlatformData(SessionPlatform); if (PlatformData && PlatformData->NeedsCommit(SessionPlatform, CookPhase)) { SessionPlatformNeedsCommit[PlatformIndex] = true; ++NumNeedCommitPlatforms; } ++PlatformIndex; } // It should not have been added to PackagesToAssign if there are no platforms to commit if (NumNeedCommitPlatforms == 0) { TStringBuilder<256> PlatformDataText; for (const TPair& PlatformPair : PackageData->GetPlatformDatas()) { PlatformDataText.Appendf(TEXT("{ %s: Reachable=%s, Committed=%s }, "), PlatformPair.Key == CookerLoadingPlatformKey ? TEXT("CookerLoadingPlatform") : *PlatformPair.Key->PlatformName(), PlatformPair.Value.IsReachable(CookPhase == ECookPhase::Cook ? EReachability::Runtime : EReachability::Build) ? TEXT("true") : TEXT("false"), PlatformPair.Value.IsCommitted() ? TEXT("true") : TEXT("false")); } checkf(false, TEXT("Package %s was assigned to worker, but at sendmessage time it has no platforms needing commit. State = %s. CookPhase = %s. [ %s ]"), *PackageData->GetPackageName().ToString(), LexToString(PackageData->GetState()), LexToString(CookPhase), *PlatformDataText); } AssignData.NeedCommitPlatforms = FDiscoveredPlatformSet(SessionPlatformNeedsCommit); FAssignPackageExtraData* ExtraData = PackagesToAssignExtraDatas.Find(PackageData); if (ExtraData) { AssignData.GeneratorPerPlatformPreviousGeneratedPackages.Reserve( ExtraData->GeneratorPerPlatformPreviousGeneratedPackages.Num()); for (TPair>& PlatformPair : ExtraData->GeneratorPerPlatformPreviousGeneratedPackages) { int32 PlatformIdInt = OrderedSessionPlatforms.IndexOfByKey(PlatformPair.Key); check(0 <= PlatformIdInt && PlatformIdInt <= MAX_uint8); uint8 PlatformId = static_cast(PlatformIdInt); TMap& DestMap = AssignData.GeneratorPerPlatformPreviousGeneratedPackages.Add(PlatformId); DestMap = MoveTemp(PlatformPair.Value); } AssignData.PerPackageCollectorMessages = MoveTemp(ExtraData->PerPackageCollectorMessages); } } for (FPackageData* PackageData : PackagesToAssignInfoPackages) { FPackageDataExistenceInfo& ExistenceInfo = ExistenceInfos.Emplace_GetRef(); ExistenceInfo.ConstructData = PackageData->CreateConstructData(); ExistenceInfo.ParentGenerator = PackageData->GetParentGenerator(); } PendingPackages.Append(PackagesToAssign); PackagesToAssign.Empty(); PackagesToAssignExtraDatas.Empty(); PackagesToAssignInfoPackages.Empty(); FAssignPackagesMessage AssignPackagesMessage(MoveTemp(AssignDatas), MoveTemp(ExistenceInfos)); AssignPackagesMessage.OrderedSessionPlatforms = OrderedSessionPlatforms; SendMessageInLock(MoveTemp(AssignPackagesMessage)); } void FCookWorkerServer::PumpReceiveMessages() { using namespace UE::CompactBinaryTCP; LLM_SCOPE_BYTAG(Cooker_MPCook); TArray Messages; EConnectionStatus SocketStatus = TryReadPacket(Socket, ReceiveBuffer, Messages); if (SocketStatus != EConnectionStatus::Okay && SocketStatus != EConnectionStatus::Incomplete) { CrashDiagnosticsError = FString::Printf( TEXT("CookWorkerCrash: CookWorker %d failed to read from socket with description: %s. we will shutdown the remote process. Assigned packages will be returned to the director."), ProfileId, DescribeStatus(SocketStatus)); bNeedCrashDiagnostics = true; SendToState(EConnectStatus::WaitForDisconnect); bTerminateImmediately = true; return; } for (FMarshalledMessage& Message : Messages) { ReceiveMessages.Add(MoveTemp(Message)); } HandleReceiveMessagesInternal(); } void FCookWorkerServer::HandleReceiveMessages(ECookDirectorThread TickThread) { FCommunicationScopeLock ScopeLock(this, TickThread, ETickAction::Queue); HandleReceiveMessagesInternal(); } void FCookWorkerServer::HandleReceiveMessagesInternal() { while (!ReceiveMessages.IsEmpty()) { UE::CompactBinaryTCP::FMarshalledMessage& PeekMessage = ReceiveMessages[0]; if (PeekMessage.MessageType == FAbortWorkerMessage::MessageType) { UE::CompactBinaryTCP::FMarshalledMessage Message = ReceiveMessages.PopFrontValue(); if (ConnectStatus != EConnectStatus::PumpingCookComplete && ConnectStatus != EConnectStatus::WaitForDisconnect) { CrashDiagnosticsError = FString::Printf( TEXT("CookWorkerCrash: CookWorker %d remote process shut down unexpectedly. Assigned packages will be returned to the director."), ProfileId); bNeedCrashDiagnostics = true; } SendMessageInLock(FAbortWorkerMessage(FAbortWorkerMessage::AbortAcknowledge)); SendToState(EConnectStatus::WaitForDisconnect); ReceiveMessages.Reset(); break; } if (TickState.TickThread != ECookDirectorThread::SchedulerThread) { break; } UE::CompactBinaryTCP::FMarshalledMessage Message = ReceiveMessages.PopFrontValue(); if (Message.MessageType == FPackageResultsMessage::MessageType) { FPackageResultsMessage ResultsMessage; if (!ResultsMessage.TryRead(Message.Object)) { LogInvalidMessage(TEXT("FPackageResultsMessage")); } else { RecordResults(ResultsMessage); } } else if (Message.MessageType == FDiscoveredPackagesMessage::MessageType) { FDiscoveredPackagesMessage DiscoveredMessage; DiscoveredMessage.OrderedSessionAndSpecialPlatforms = OrderedSessionAndSpecialPlatforms; if (!DiscoveredMessage.TryRead(Message.Object)) { LogInvalidMessage(TEXT("FDiscoveredPackagesMessage")); } else { for (FDiscoveredPackageReplication& DiscoveredPackage : DiscoveredMessage.Packages) { QueueDiscoveredPackage(MoveTemp(DiscoveredPackage)); } } } else if (Message.MessageType == FGeneratorEventMessage::MessageType) { FGeneratorEventMessage GeneratorMessage; if (!GeneratorMessage.TryRead(Message.Object)) { LogInvalidMessage(TEXT("FGeneratorEventMessage")); } else { HandleGeneratorMessage(GeneratorMessage); } } else { TRefCountPtr* Collector = Director.Collectors.Find(Message.MessageType); if (Collector) { check(*Collector); FMPCollectorServerMessageContext Context; Context.Server = this; Context.Platforms = OrderedSessionPlatforms; Context.WorkerId = WorkerId; Context.ProfileId = ProfileId; (*Collector)->ServerReceiveMessage(Context, Message.Object); } else { UE_LOG(LogCook, Error, TEXT("CookWorkerServer received message of unknown type %s from CookWorker. Ignoring it."), *Message.MessageType.ToString()); } } } } void FCookWorkerServer::HandleReceivedPackagePlatformMessages(FPackageData& PackageData, const ITargetPlatform* TargetPlatform, TArray&& Messages) { check(TickState.TickThread == ECookDirectorThread::SchedulerThread); if (Messages.IsEmpty()) { return; } FMPCollectorServerMessageContext Context; Context.Platforms = OrderedSessionPlatforms; Context.PackageName = PackageData.GetPackageName(); Context.TargetPlatform = TargetPlatform; Context.Server = this; Context.ProfileId = ProfileId; Context.WorkerId = WorkerId; for (UE::CompactBinaryTCP::FMarshalledMessage& Message : Messages) { TRefCountPtr* Collector = Director.Collectors.Find(Message.MessageType); if (Collector) { check(*Collector); (*Collector)->ServerReceiveMessage(Context, Message.Object); } else { UE_LOG(LogCook, Error, TEXT("CookWorkerServer received PackageMessage of unknown type %s from CookWorker. Ignoring it."), *Message.MessageType.ToString()); } } } void FCookWorkerServer::SendMessage(const IMPCollectorMessage& Message, ECookDirectorThread TickThread) { SendMessage(MarshalToCompactBinaryTCP(Message), TickThread); } void FCookWorkerServer::SendMessage(UE::CompactBinaryTCP::FMarshalledMessage&& Message, ECookDirectorThread TickThread) { FCommunicationScopeLock ScopeLock(this, TickThread, ETickAction::Tick); SendMessageInLock(MoveTemp(Message)); } void FCookWorkerServer::AppendMessage(const IMPCollectorMessage& Message, ECookDirectorThread TickThread) { AppendMessage(MarshalToCompactBinaryTCP(Message), TickThread); } void FCookWorkerServer::AppendMessage(UE::CompactBinaryTCP::FMarshalledMessage&& Message, ECookDirectorThread TickThread) { FCommunicationScopeLock ScopeLock(this, TickThread, ETickAction::Queue); QueuedMessagesToSendAfterPackagesToAssign.Add(MoveTemp(Message)); } void FCookWorkerServer::SendMessageInLock(const IMPCollectorMessage& Message) { SendMessageInLock(MarshalToCompactBinaryTCP(Message)); } void FCookWorkerServer::SendMessageInLock(UE::CompactBinaryTCP::FMarshalledMessage&& Message) { if (TickState.TickAction == ETickAction::Tick) { UE::CompactBinaryTCP::TryWritePacket(Socket, SendBuffer, MoveTemp(Message)); } else { check(TickState.TickAction == ETickAction::Queue); UE::CompactBinaryTCP::QueueMessage(SendBuffer, MoveTemp(Message)); } } FString WriteCookStatus(FPackageData& PackageData, TConstArrayView SessionPlatforms) { FString Result; Result.Reserve(256); auto BoolToString = [](bool bValue) { return bValue ? TEXT("true") : TEXT("false"); }; for (const ITargetPlatform* TargetPlatform : SessionPlatforms) { FPackagePlatformData* PlatformData = PackageData.FindPlatformData(TargetPlatform); Result.Appendf(TEXT("[ %s: { Reachable: %s, Cookable: %s, CookResult: %s }, "), *TargetPlatform->PlatformName(), BoolToString(PlatformData && PlatformData->IsReachable(EReachability::Runtime)), BoolToString(PlatformData && PlatformData->IsCookable()), (PlatformData ? ::LexToString(PlatformData->GetCookResults()) : TEXT(""))); } if (Result.Len() >= 2) { Result.LeftChopInline(2); // Remove the trailing ", " Result.Append(TEXT(" ]")); } return Result; } TArray GetCommittedPlatformListFromPlatformResults( TConstArrayView OrderedPlatforms, TConstArrayView PlatformResults) { TArray PlatformList; int32 NumPlatforms = OrderedPlatforms.Num(); if (NumPlatforms != PlatformResults.Num()) { return PlatformList; } for (int32 PlatformIndex = 0; PlatformIndex < NumPlatforms; ++PlatformIndex) { const FPackageRemoteResult::FPlatformResult& PlatformResult = PlatformResults[PlatformIndex]; if (PlatformResult.WasCommitted()) { PlatformList.Add(OrderedPlatforms[PlatformIndex]); } } return PlatformList; } FString PlatformListToString(TConstArrayView Platforms) { TStringBuilder<256> Result; for (const ITargetPlatform* TargetPlatform : Platforms) { Result << TargetPlatform->PlatformName() << TEXT(", "); } if (Result.Len() > 0) { Result.RemoveSuffix(2); // ", " } return FString(Result); } void FCookWorkerServer::RecordResults(FPackageResultsMessage& Message) { check(TickState.TickThread == ECookDirectorThread::SchedulerThread); bool bRetiredAnyPackages = false; for (FPackageRemoteResult& Result : Message.Results) { FPackageData* PackageData = COTFS.PackageDatas->FindPackageDataByPackageName(Result.GetPackageName()); if (!PackageData) { UE_LOG(LogCook, Warning, TEXT("CookWorkerServer %d received FPackageResultsMessage for invalid package %s. Ignoring it."), ProfileId, *Result.GetPackageName().ToString()); continue; } if (PendingPackages.Remove(PackageData) != 1) { UE_LOG(LogCook, Display, TEXT("CookWorkerServer %d received FPackageResultsMessage for package %s which is not a pending package. Ignoring it.") TEXT("\n\tState = %s, WorkerId = %s, CookResults = { %s }, Result.GetSuppressCookReason = %s"), ProfileId, *Result.GetPackageName().ToString(), LexToString(PackageData->GetState()), *Director.GetDisplayName(PackageData->GetWorkerAssignment()), *WriteCookStatus(*PackageData, COTFS.GetSessionPlatforms()), LexToString(Result.GetSuppressCookReason())); continue; } int32 NumPlatforms = OrderedSessionPlatforms.Num(); if (Result.GetPlatforms().Num() != NumPlatforms) { UE_LOG(LogCook, Error, TEXT("CookWorkerServer %d received FPackageResultsMessage for package %s with an invalid number of platform results: expected %d, actual %d. Ignoring it."), ProfileId, *Result.GetPackageName().ToString(), NumPlatforms, Result.GetPlatforms().Num()); continue; } bRetiredAnyPackages = true; bool bResultIsSaveResult = Result.GetSuppressCookReason() == ESuppressCookReason::NotSuppressed; EStateChangeReason StateChangeReason; bool bTerminalStateChange; if (bResultIsSaveResult) { StateChangeReason = EStateChangeReason::Saved; bTerminalStateChange = true; } else { StateChangeReason = ConvertToStateChangeReason(Result.GetSuppressCookReason()); bTerminalStateChange = IsTerminalStateChange(StateChangeReason); } // MPCOOKTODO: Refactor FSaveCookedPackageContext::FinishPlatform and ::FinishPackage so we can call them from // here to reduce duplication if (bResultIsSaveResult) { HandleReceivedPackagePlatformMessages(*PackageData, nullptr, Result.ReleaseMessages()); for (int32 PlatformIndex = 0; PlatformIndex < NumPlatforms; ++PlatformIndex) { ITargetPlatform* TargetPlatform = OrderedSessionPlatforms[PlatformIndex]; FPackageRemoteResult::FPlatformResult& PlatformResult = Result.GetPlatforms()[PlatformIndex]; FPackagePlatformData& ExistingData = PackageData->FindOrAddPlatformData(TargetPlatform); if (ExistingData.IsCommitted()) { if (PlatformResult.WasCommitted()) { UE_LOG(LogCook, Display, TEXT("CookWorkerServer %d received FPackageResultsMessage for package %s, platform %s, but that platform has already been committed. Ignoring the results for that platform."), ProfileId, *Result.GetPackageName().ToString(), *TargetPlatform->PlatformName()); } } else { bool bWasCooked = PlatformResult.GetCookResults() != ECookResult::Invalid && PlatformResult.GetCookResults() != ECookResult::NotAttempted; if (!ExistingData.NeedsCooking(TargetPlatform) && bWasCooked) { UE_LOG(LogCook, Display, TEXT("CookWorkerServer %d received FPackageResultsMessage for package %s, platform %s, but that platform has already been cooked. Ignoring the results for that platform."), ProfileId, *Result.GetPackageName().ToString(), *TargetPlatform->PlatformName()); } else { if (bWasCooked) { PackageData->SetPlatformCooked(TargetPlatform, PlatformResult.GetCookResults()); } else if (PlatformResult.WasCommitted()) { PackageData->SetPlatformCommitted(TargetPlatform); } HandleReceivedPackagePlatformMessages(*PackageData, TargetPlatform, PlatformResult.ReleaseMessages()); } } } COTFS.RecordExternalActorDependencies(Result.GetExternalActorDependencies()); } // For generator and generated packages, after we handle all of their save recording above, execute their state // changes in the required order: // *) Defer the GenerationHelper's events so that we don't yet complete it if this was the last save. // *) Mark saved on the generator, so that the generator has full context for the save. // *) Transition the PackageData state to complete. The code to automatically mark generated as saved with the // generator will be skipped since we already did it in the step above. // *) Unfreeze the GenerationHelper's events and call OnAllSavesCompleted if necessary. TOptional DeferGenerationHelperEvents; // If generated or generator, and this is a save or other end-of-cook signal, defer events and mark saved if (PackageData->IsGenerated() && bTerminalStateChange) { TRefCountPtr ParentGenerationHelper = PackageData->GetOrFindParentGenerationHelper(); if (!ParentGenerationHelper) { UE_LOG(LogCook, Warning, TEXT("RecordResults received for generated package %s, but its ParentGenerationHelper has already been destructed so we can not update the save flag. Leaving the save flag unupdated; this might cause workers to run out of memory due to keeping the Generator referenced."), *PackageData->GetPackageName().ToString()); } else { DeferGenerationHelperEvents.Emplace(ParentGenerationHelper); for (int32 PlatformIndex = 0; PlatformIndex < NumPlatforms; ++PlatformIndex) { ITargetPlatform* TargetPlatform = OrderedSessionPlatforms[PlatformIndex]; FPackageRemoteResult::FPlatformResult& PlatformResult = Result.GetPlatforms()[PlatformIndex]; if (!bResultIsSaveResult || PlatformResult.WasCommitted()) { ParentGenerationHelper->MarkPackageSavedRemotely(COTFS, *PackageData, TargetPlatform, GetWorkerId()); } } PackageData->SetParentGenerationHelper(nullptr, StateChangeReason); } } TRefCountPtr GenerationHelper = PackageData->GetGenerationHelper(); if (GenerationHelper) { DeferGenerationHelperEvents.Emplace(GenerationHelper); if (bTerminalStateChange) { for (int32 PlatformIndex = 0; PlatformIndex < NumPlatforms; ++PlatformIndex) { ITargetPlatform* TargetPlatform = OrderedSessionPlatforms[PlatformIndex]; FPackageRemoteResult::FPlatformResult& PlatformResult = Result.GetPlatforms()[PlatformIndex]; if (!bResultIsSaveResult || PlatformResult.WasCommitted()) { GenerationHelper->MarkPackageSavedRemotely(COTFS, *PackageData, TargetPlatform, GetWorkerId()); } } } GenerationHelper.SafeRelease(); } // For all packages, transition them to their next state PackageData->SetWorkerAssignment(FWorkerId::Invalid(), ESendFlags::QueueNone); if (bResultIsSaveResult) { ECookPhase CookPhase = COTFS.GetCookPhase(); if (PackageData->GetPlatformsNeedingCommitNum(CookPhase) > 0) { TArray Remaining; PackageData->GetPlatformsNeedingCommit(Remaining, CookPhase); UE_LOG(LogCook, Display, TEXT("Package %s was completed by CookWorker %d for platforms { %s }, but it still needs to commit platforms { %s }.") TEXT(" Sending it back to the request state."), *PackageData->GetPackageName().ToString(), ProfileId, *PlatformListToString(GetCommittedPlatformListFromPlatformResults(OrderedSessionPlatforms, Result.GetPlatforms())), *PlatformListToString(Remaining)); PackageData->SendToState(EPackageState::Request, ESendFlags::QueueAddAndRemove, EStateChangeReason::Discovered); } else { COTFS.PromoteToSaveComplete(*PackageData, ESendFlags::QueueAddAndRemove); } } else if (Result.GetSuppressCookReason() == ESuppressCookReason::RetractedByCookDirector) { UE_LOG(LogCook, Error, TEXT("Package %s was retracted by CookWorker %d, but it still sent a RecordResults message for the package which is supposed to be omitted for RetractedByCookDirector suppressions."), *PackageData->GetPackageName().ToString(), ProfileId); if (PackageData->GetWorkerAssignment() == GetWorkerId()) { PackageData->SendToState(EPackageState::Request, ESendFlags::QueueAddAndRemove, EStateChangeReason::Retraction); } } else if (!bTerminalStateChange) { // Non-terminal SuppressCookReasons send it back to request via COTFS.DemoteToRequest. // DemoteToRequest will also handle any request data changes indicated by the SuppressCookReason COTFS.DemoteToRequest(*PackageData, ESendFlags::QueueAddAndRemove, Result.GetSuppressCookReason()); } else { // Terminal SuppressCookReasons send it to idle via DemoteToIdle. // DemoteToIdle will also handle any required logging. COTFS.DemoteToIdle(*PackageData, ESendFlags::QueueAddAndRemove, Result.GetSuppressCookReason()); } // For generated packages, undefer events and process AllSavesCompleted if necessary. DeferGenerationHelperEvents.Reset(); } Director.ResetFinalIdleHeartbeatFence(); if (bRetiredAnyPackages) { ++PackagesRetiredFenceMarker; } } void FCookWorkerServer::LogInvalidMessage(const TCHAR* MessageTypeName) { UE_LOG(LogCook, Error, TEXT("CookWorkerServer received invalidly formatted message for type %s from CookWorker. Ignoring it."), MessageTypeName); } void FCookWorkerServer::QueueDiscoveredPackage(FDiscoveredPackageReplication&& DiscoveredPackage) { check(TickState.TickThread == ECookDirectorThread::SchedulerThread); FPackageDatas& PackageDatas = *COTFS.PackageDatas; FInstigator& Instigator = DiscoveredPackage.Instigator; FDiscoveredPlatformSet& Platforms = DiscoveredPackage.Platforms; FPackageData& PackageData = PackageDatas.FindOrAddPackageData(DiscoveredPackage.PackageName, DiscoveredPackage.NormalizedFileName); TArray> BufferPlatforms; TConstArrayView DiscoveredPlatforms; EReachability DiscoveredReachability = Instigator.Category == EInstigator::BuildDependency ? EReachability::Build : EReachability::Runtime; if (!COTFS.bSkipOnlyEditorOnly) { DiscoveredPlatforms = OrderedSessionAndSpecialPlatforms; } else { DiscoveredPlatforms = Platforms.GetPlatforms(COTFS, &Instigator, OrderedSessionAndSpecialPlatforms, DiscoveredReachability, BufferPlatforms); } if (Instigator.Category != EInstigator::ForceExplorableSaveTimeSoftDependency && PackageData.HasReachablePlatforms(DiscoveredReachability, DiscoveredPlatforms)) { // The CookWorker thought there were some new reachable platforms, but the Director already knows about // all of them; ignore the report return; } if (COTFS.bSkipOnlyEditorOnly && Instigator.Category == EInstigator::Unsolicited && Platforms.GetSource() == EDiscoveredPlatformSet::CopyFromInstigator && PackageData.FindOrAddPlatformData(CookerLoadingPlatformKey).IsReachable(EReachability::Runtime)) { // The CookWorker thought this package was new (previously unreachable even by editoronly references), // and it is not marked as a known used-in-game or editor-only issue, so it fell back to reporting it // as used-in-game-because-its-not-a-known-issue (see UCookOnTheFlyServer::ProcessUnsolicitedPackages's // use of PackageData->FindOrAddPlatformData(CookerLoadingPlatformKey).IsReachable(EReachability::Runtime)). // But we only do that fall back for unexpected packages not found by the search of editor-only AssetRegistry // dependencies. And this package was found by that search; the director has already marked it as reachable by // editoronly references. Correct the heuristic: ignore the unmarked load because the load is expected as an // editor-only reference. return; } if (!DiscoveredPackage.ParentGenerator.IsNone()) { // Registration of the discovered Generated package with its generator needs to come after we early-exit // for already discovered packages, because when one generated package can refer to another from the same // generator, the message that a CookWorker has discovered the referred-to generated package can show up // on the director AFTER all save messages have already been processed and the GenerationHelper has shut // down and destroyed its information about the list of generated packages. PackageData.SetGenerated(DiscoveredPackage.ParentGenerator); PackageData.SetDoesGeneratedRequireGenerator(DiscoveredPackage.DoesGeneratedRequireGenerator); FPackageData* GeneratorPackageData = PackageDatas.FindPackageDataByPackageName( DiscoveredPackage.ParentGenerator); if (GeneratorPackageData) { TRefCountPtr GenerationHelper = GeneratorPackageData->CreateUninitializedGenerationHelper(); GenerationHelper->NotifyStartQueueGeneratedPackages(COTFS, WorkerId); GenerationHelper->TrackGeneratedPackageListedRemotely(COTFS, PackageData, DiscoveredPackage.GeneratedPackageHash); } } if (PackageData.IsGenerated() && (PackageData.DoesGeneratedRequireGenerator() >= ICookPackageSplitter::EGeneratedRequiresGenerator::Save || COTFS.MPCookGeneratorSplit == EMPCookGeneratorSplit::AllOnSameWorker)) { PackageData.SetWorkerAssignmentConstraint(GetWorkerId()); } Director.ResetFinalIdleHeartbeatFence(); Platforms.ConvertFromBitfield(OrderedSessionAndSpecialPlatforms); COTFS.QueueDiscoveredPackageOnDirector(PackageData, MoveTemp(Instigator), MoveTemp(Platforms), DiscoveredPackage.Urgency); } void FCookWorkerServer::HandleGeneratorMessage(FGeneratorEventMessage& GeneratorMessage) { FPackageData* PackageData = COTFS.PackageDatas->FindPackageDataByPackageName(GeneratorMessage.PackageName); if (!PackageData) { // This error should be impossible because GeneratorMessages are only sent in response to assignment from the server. UE_LOG(LogCook, Error, TEXT("CookWorkerServer received unexpected GeneratorMessage for package %s. The PackageData %s does not exist on the CookDirector. ") TEXT("\n\tCook of this generator package and its generated packages will be invalid."), *GeneratorMessage.PackageName.ToString(), (!PackageData ? TEXT("does not exist") : TEXT("is not a valid generator"))); return; } TRefCountPtr GenerationHelper; GenerationHelper = PackageData->CreateUninitializedGenerationHelper(); check(GenerationHelper); switch (GeneratorMessage.Event) { case EGeneratorEvent::QueuedGeneratedPackages: GenerationHelper->EndQueueGeneratedPackagesOnDirector(COTFS, GetWorkerId()); break; default: // We do not handle the remaining GeneratorEvents on the server break; } } FCookWorkerServer::FTickState::FTickState() { TickThread = ECookDirectorThread::Invalid; TickAction = ETickAction::Invalid; } FCookWorkerServer::FCommunicationScopeLock::FCommunicationScopeLock(FCookWorkerServer* InServer, ECookDirectorThread TickThread, ETickAction TickAction) : ScopeLock(&InServer->CommunicationLock) , Server(*InServer) { check(TickThread != ECookDirectorThread::Invalid); check(TickAction != ETickAction::Invalid); check(Server.TickState.TickThread == ECookDirectorThread::Invalid); Server.TickState.TickThread = TickThread; Server.TickState.TickAction = TickAction; } FCookWorkerServer::FCommunicationScopeLock::~FCommunicationScopeLock() { check(Server.TickState.TickThread != ECookDirectorThread::Invalid); Server.TickState.TickThread = ECookDirectorThread::Invalid; Server.TickState.TickAction = ETickAction::Invalid; } UE::CompactBinaryTCP::FMarshalledMessage MarshalToCompactBinaryTCP(const IMPCollectorMessage& Message) { UE::CompactBinaryTCP::FMarshalledMessage Marshalled; Marshalled.MessageType = Message.GetMessageType(); FCbWriter Writer; Writer.BeginObject(); Message.Write(Writer); Writer.EndObject(); Marshalled.Object = Writer.Save().AsObject(); return Marshalled; } FAssignPackagesMessage::FAssignPackagesMessage(TArray&& InPackageDatas, TArray&& InExistenceInfos) : PackageDatas(MoveTemp(InPackageDatas)) , ExistenceInfos(MoveTemp(InExistenceInfos)) { } void FAssignPackagesMessage::Write(FCbWriter& Writer) const { Writer.BeginArray("P"); for (const FAssignPackageData& PackageData : PackageDatas) { WriteToCompactBinary(Writer, PackageData, OrderedSessionPlatforms); } Writer.EndArray(); Writer.BeginArray("I"); for (const FPackageDataExistenceInfo& ExistenceInfo : ExistenceInfos) { Writer << ExistenceInfo; } Writer.EndArray(); } bool FAssignPackagesMessage::TryRead(FCbObjectView Object) { bool bOk = true; PackageDatas.Reset(); for (FCbFieldView PackageField : Object["P"]) { FAssignPackageData& PackageData = PackageDatas.Emplace_GetRef(); if (!LoadFromCompactBinary(PackageField, PackageData, OrderedSessionPlatforms)) { PackageDatas.Pop(); bOk = false; } } ExistenceInfos.Reset(); for (FCbFieldView PackageField : Object["I"]) { FPackageDataExistenceInfo& ExistenceInfo = ExistenceInfos.Emplace_GetRef(); if (!LoadFromCompactBinary(PackageField, ExistenceInfo)) { ExistenceInfos.Pop(); bOk = false; } } return bOk; } FGuid FAssignPackagesMessage::MessageType(TEXT("B7B1542B73254B679319D73F753DB6F8")); void FAssignPackageData::Write(FCbWriter& Writer, TConstArrayView OrderedSessionPlatforms) const { Writer.BeginArray(); Writer << ConstructData; Writer << ParentGenerator; Writer << Instigator; Writer << static_cast(Urgency); static_assert(sizeof(EUrgency) <= sizeof(uint8), "We are storing it in a uint8"); Writer << static_cast(Reachability); static_assert(sizeof(EReachability) <= sizeof(uint8), "We are storing it in a uint8"); WriteToCompactBinary(Writer, NeedCommitPlatforms, OrderedSessionPlatforms); { Writer.BeginArray(); for (const TPair>& PlatformPair : GeneratorPerPlatformPreviousGeneratedPackages) { Writer.BeginArray(); Writer << PlatformPair.Key; Writer.BeginArray(); for (const TPair& PackagePair : PlatformPair.Value) { Writer.BeginArray(); Writer << PackagePair.Key; PackagePair.Value.NetworkWrite(Writer); Writer.EndArray(); } Writer.EndArray(); Writer.EndArray(); } Writer.EndArray(); } static_assert(sizeof(ICookPackageSplitter::EGeneratedRequiresGenerator) <= sizeof(uint8), "We are storing it in a uint8"); Writer << static_cast(DoesGeneratedRequireGenerator); Writer << PerPackageCollectorMessages; Writer.EndArray(); } bool FAssignPackageData::TryRead(FCbFieldView Field, TConstArrayView OrderedSessionPlatforms) { FCbFieldViewIterator It = Field.CreateViewIterator(); bool bOk = true; bOk = LoadFromCompactBinary(*It++, ConstructData) & bOk; bOk = LoadFromCompactBinary(*It++, ParentGenerator) & bOk; bOk = LoadFromCompactBinary(*It++, Instigator) & bOk; uint8 UrgencyInt = It->AsUInt8(); if (!(It++)->HasError() && UrgencyInt < static_cast(EUrgency::Count)) { Urgency = static_cast(UrgencyInt); } else { bOk = false; } uint8 ReachabilityInt = It->AsUInt8(); if (!(It++)->HasError() && ReachabilityInt < (static_cast(EReachability::MaxBit) << 1)) { Reachability = static_cast(ReachabilityInt); } else { bOk = false; } bOk = LoadFromCompactBinary(*It++, NeedCommitPlatforms, OrderedSessionPlatforms) & bOk; { FCbFieldView PlatformArrayFieldView = *It++; bool bGeneratorPreviousGeneratedPackagesOk = false; const uint64 PlatformLength = PlatformArrayFieldView.AsArrayView().Num(); if (PlatformLength <= MAX_int32) { GeneratorPerPlatformPreviousGeneratedPackages.Empty((int32)PlatformLength); bGeneratorPreviousGeneratedPackagesOk = !PlatformArrayFieldView.HasError(); for (const FCbFieldView& PlatformIt : PlatformArrayFieldView) { bool bPlatformPairOk = false; FCbFieldViewIterator PlatformPairIt = PlatformIt.CreateViewIterator(); uint8 PlatformIndex; TMap PackagesMap; if (LoadFromCompactBinary(*PlatformPairIt++, PlatformIndex)) { FCbFieldView PackagesArrayFieldView = *PlatformPairIt++; const uint64 PackagesLength = PackagesArrayFieldView.AsArrayView().Num(); if (PackagesLength <= MAX_int32) { PackagesMap.Empty((int32)PackagesLength); bPlatformPairOk = !PackagesArrayFieldView.HasError(); for (const FCbFieldView& PackagesElementField : PackagesArrayFieldView) { FCbFieldViewIterator PairIt = PackagesElementField.CreateViewIterator(); bool bElementOk = false; FName Key; FAssetPackageData Value; if (LoadFromCompactBinary(*PairIt++, Key)) { if (Value.TryNetworkRead(*PairIt++)) { PackagesMap.Add(Key, MoveTemp(Value)); bElementOk = true; } } bPlatformPairOk &= bElementOk; } } } if (bPlatformPairOk) { GeneratorPerPlatformPreviousGeneratedPackages.Add(PlatformIndex, MoveTemp(PackagesMap)); } bGeneratorPreviousGeneratedPackagesOk &= bPlatformPairOk; } } else { GeneratorPerPlatformPreviousGeneratedPackages.Empty(); } bOk &= bGeneratorPreviousGeneratedPackagesOk; } uint8 DoesGeneratedRequireGeneratorInt = It->AsUInt8(); if (!(It++)->HasError() && DoesGeneratedRequireGeneratorInt < static_cast(ICookPackageSplitter::EGeneratedRequiresGenerator::Count)) { DoesGeneratedRequireGenerator = static_cast(DoesGeneratedRequireGeneratorInt); } else { bOk = false; } bOk = LoadFromCompactBinary(*It++, PerPackageCollectorMessages) & bOk; return bOk; } void FPackageDataExistenceInfo::Write(FCbWriter& Writer) const { Writer.BeginArray(); Writer << ConstructData; Writer << ParentGenerator; Writer.EndArray(); } bool FPackageDataExistenceInfo::TryRead(FCbFieldView Field) { FCbFieldViewIterator It = Field.CreateViewIterator(); bool bOk = true; bOk = LoadFromCompactBinary(*It++, ConstructData) & bOk; bOk = LoadFromCompactBinary(*It++, ParentGenerator) & bOk; return bOk; } FCbWriter& operator<<(FCbWriter& Writer, const FInstigator& Instigator) { Writer.BeginObject(); Writer << "C" << static_cast(Instigator.Category); Writer << "R" << Instigator.Referencer; Writer.EndObject(); return Writer; } bool LoadFromCompactBinary(FCbFieldView Field, FInstigator& Instigator) { uint8 CategoryInt; bool bOk = true; if (LoadFromCompactBinary(Field["C"], CategoryInt) && CategoryInt < static_cast(EInstigator::Count)) { Instigator.Category = static_cast(CategoryInt); } else { Instigator.Category = EInstigator::InvalidCategory; bOk = false; } bOk = LoadFromCompactBinary(Field["R"], Instigator.Referencer) & bOk; return bOk; } FAbortPackagesMessage::FAbortPackagesMessage(TArray&& InPackageNames) : PackageNames(MoveTemp(InPackageNames)) { } void FAbortPackagesMessage::Write(FCbWriter& Writer) const { Writer << "PackageNames" << PackageNames; } bool FAbortPackagesMessage::TryRead(FCbObjectView Object) { return LoadFromCompactBinary(Object["PackageNames"], PackageNames); } FGuid FAbortPackagesMessage::MessageType(TEXT("D769F1BFF2F34978868D70E3CAEE94E7")); FAbortWorkerMessage::FAbortWorkerMessage(EType InType) : Type(InType) { } void FAbortWorkerMessage::Write(FCbWriter& Writer) const { Writer << "Type" << (uint8)Type; } bool FAbortWorkerMessage::TryRead(FCbObjectView Object) { Type = static_cast(Object["Type"].AsUInt8((uint8)EType::Abort)); return true; } FGuid FAbortWorkerMessage::MessageType(TEXT("83FD99DFE8DB4A9A8E71684C121BE6F3")); void FInitialConfigMessage::ReadFromLocal(const UCookOnTheFlyServer& COTFS, const TConstArrayView& InOrderedSessionPlatforms, const FCookByTheBookOptions& InCookByTheBookOptions, const FCookOnTheFlyOptions& InCookOnTheFlyOptions, const FBeginCookContextForWorker& InBeginContext) { InitialSettings.CopyFromLocal(COTFS); BeginCookSettings.CopyFromLocal(COTFS); BeginCookContext = InBeginContext; OrderedSessionPlatforms.Reset(InOrderedSessionPlatforms.Num()); for (const ITargetPlatform* Platform : InOrderedSessionPlatforms) { OrderedSessionPlatforms.Add(const_cast(Platform)); } DirectorCookMode = COTFS.GetCookMode(); CookInitializationFlags = COTFS.GetCookFlags(); CookByTheBookOptions = InCookByTheBookOptions; CookOnTheFlyOptions = InCookOnTheFlyOptions; bZenStore = COTFS.IsUsingZenStore(); } void FInitialConfigMessage::Write(FCbWriter& Writer) const { int32 LocalCookMode = static_cast(DirectorCookMode); Writer << "DirectorCookMode" << LocalCookMode; int32 LocalCookFlags = static_cast(CookInitializationFlags); Writer << "CookInitializationFlags" << LocalCookFlags; Writer << "ZenStore" << bZenStore; Writer.BeginArray("TargetPlatforms"); for (const ITargetPlatform* TargetPlatform : OrderedSessionPlatforms) { Writer << TargetPlatform->PlatformName(); } Writer.EndArray(); Writer << "InitialSettings" << InitialSettings; Writer << "BeginCookSettings" << BeginCookSettings; Writer << "BeginCookContext" << BeginCookContext; Writer << "CookByTheBookOptions" << CookByTheBookOptions; Writer << "CookOnTheFlyOptions" << CookOnTheFlyOptions; Writer << "MPCollectorMessages" << MPCollectorMessages; } bool FInitialConfigMessage::TryRead(FCbObjectView Object) { bool bOk = true; int32 LocalCookMode; bOk = LoadFromCompactBinary(Object["DirectorCookMode"], LocalCookMode) & bOk; DirectorCookMode = static_cast(LocalCookMode); int32 LocalCookFlags; bOk = LoadFromCompactBinary(Object["CookInitializationFlags"], LocalCookFlags) & bOk; CookInitializationFlags = static_cast(LocalCookFlags); bOk = LoadFromCompactBinary(Object["ZenStore"], bZenStore) & bOk; ITargetPlatformManagerModule& TPM(GetTargetPlatformManagerRef()); FCbFieldView TargetPlatformsField = Object["TargetPlatforms"]; { bOk = TargetPlatformsField.IsArray() & bOk; OrderedSessionPlatforms.Reset(TargetPlatformsField.AsArrayView().Num()); for (FCbFieldView ElementField : TargetPlatformsField) { TStringBuilder<128> KeyName; if (LoadFromCompactBinary(ElementField, KeyName)) { ITargetPlatform* TargetPlatform = TPM.FindTargetPlatform(KeyName.ToView()); if (TargetPlatform) { OrderedSessionPlatforms.Add(TargetPlatform); } else { UE_LOG(LogCook, Error, TEXT("Could not find TargetPlatform \"%.*s\" received from CookDirector."), KeyName.Len(), KeyName.GetData()); bOk = false; } } else { bOk = false; } } } bOk = LoadFromCompactBinary(Object["InitialSettings"], InitialSettings) & bOk; bOk = LoadFromCompactBinary(Object["BeginCookSettings"], BeginCookSettings) & bOk; bOk = LoadFromCompactBinary(Object["BeginCookContext"], BeginCookContext) & bOk; bOk = LoadFromCompactBinary(Object["CookByTheBookOptions"], CookByTheBookOptions) & bOk; bOk = LoadFromCompactBinary(Object["CookOnTheFlyOptions"], CookOnTheFlyOptions) & bOk; bOk = LoadFromCompactBinary(Object["MPCollectorMessages"], MPCollectorMessages) & bOk; return bOk; } FGuid FInitialConfigMessage::MessageType(TEXT("340CDCB927304CEB9C0A66B5F707FC2B")); void FDiscoveredPackageReplication::Write(FCbWriter& Writer, TConstArrayView OrderedSessionAndSpecialPlatforms) const { Writer.BeginArray(); Writer << PackageName; Writer << NormalizedFileName; Writer << ParentGenerator; Writer << static_cast(Instigator.Category); Writer << Instigator.Referencer; Writer << static_cast(DoesGeneratedRequireGenerator); static_assert(sizeof(ICookPackageSplitter::EGeneratedRequiresGenerator) <= sizeof(uint8), "We are storing it in a uint8"); Writer << static_cast(Urgency); static_assert(sizeof(EUrgency) <= sizeof(uint8), "We are storing it in a uint8"); bool bGeneratedPackageHash = !GeneratedPackageHash.IsZero(); Writer << bGeneratedPackageHash; if (bGeneratedPackageHash) { Writer << GeneratedPackageHash; } WriteToCompactBinary(Writer, Platforms, OrderedSessionAndSpecialPlatforms); Writer.EndArray(); } bool FDiscoveredPackageReplication::TryRead(FCbFieldView Field, TConstArrayView OrderedSessionAndSpecialPlatforms) { FCbArrayView FieldList = Field.AsArrayView(); if (Field.HasError()) { *this = FDiscoveredPackageReplication(); return false; } FCbFieldViewIterator Iter = FieldList.CreateViewIterator(); bool bOk = LoadFromCompactBinary(Iter++, PackageName); bOk = LoadFromCompactBinary(Iter++, NormalizedFileName) & bOk; bOk = LoadFromCompactBinary(Iter++, ParentGenerator) & bOk; uint8 CategoryInt; if (LoadFromCompactBinary(Iter++, CategoryInt) && CategoryInt < static_cast(EInstigator::Count)) { Instigator.Category = static_cast(CategoryInt); } else { bOk = false; } bOk = LoadFromCompactBinary(Iter++, Instigator.Referencer) & bOk; uint8 DoesGeneratedRequireGeneratorInt = Iter->AsUInt8(); if (!(Iter++)->HasError() && DoesGeneratedRequireGeneratorInt < static_cast(ICookPackageSplitter::EGeneratedRequiresGenerator::Count)) { DoesGeneratedRequireGenerator = static_cast( DoesGeneratedRequireGeneratorInt); } else { bOk = false; } uint8 UrgencyInt = Iter->AsUInt8(); if (!(Iter++)->HasError() && UrgencyInt < static_cast(EUrgency::Count)) { Urgency = static_cast(UrgencyInt); } else { bOk = false; } bool bGeneratedPackageHash = false; bOk = LoadFromCompactBinary(Iter++, bGeneratedPackageHash) & bOk; if (bGeneratedPackageHash) { bOk = LoadFromCompactBinary(Iter++, GeneratedPackageHash) & bOk; } else { GeneratedPackageHash = FIoHash::Zero; } bOk = LoadFromCompactBinary(Iter++, Platforms, OrderedSessionAndSpecialPlatforms) & bOk; if (!bOk) { *this = FDiscoveredPackageReplication(); } return bOk; } void FDiscoveredPackagesMessage::Write(FCbWriter& Writer) const { Writer.BeginArray("Packages"); for (const FDiscoveredPackageReplication& Package : Packages) { WriteToCompactBinary(Writer, Package, OrderedSessionAndSpecialPlatforms); } Writer.EndArray(); } bool FDiscoveredPackagesMessage::TryRead(FCbObjectView Object) { bool bOk = true; Packages.Reset(); for (FCbFieldView PackageField : Object["Packages"]) { FDiscoveredPackageReplication& Package = Packages.Emplace_GetRef(); if (!LoadFromCompactBinary(PackageField, Package, OrderedSessionAndSpecialPlatforms)) { Packages.Pop(); bOk = false; } } return bOk; } FGuid FDiscoveredPackagesMessage::MessageType(TEXT("C9F5BC5C11484B06B346B411F1ED3090")); FGeneratorEventMessage::FGeneratorEventMessage(EGeneratorEvent InEvent, FName InPackageName) : PackageName(InPackageName) , Event(InEvent) { } void FGeneratorEventMessage::Write(FCbWriter& Writer) const { Writer << "E" << static_cast(Event); Writer << "P" << PackageName; } bool FGeneratorEventMessage::TryRead(FCbObjectView Object) { bool bOk = true; FCbFieldView EventField = Object["E"]; uint8 EventInt = EventField.AsUInt8(); if (!EventField.HasError() && EventInt < static_cast(EGeneratorEvent::Num)) { Event = static_cast(EventInt); } else { Event = EGeneratorEvent::Invalid; bOk = false; } bOk = LoadFromCompactBinary(Object["P"], PackageName) & bOk; return bOk; } FGuid FGeneratorEventMessage::MessageType(TEXT("B6EE94CA70EC4F40B0D2214EDC11ED03")); FGuid FLogMessagesMessageHandler::MessageType(TEXT("DB024D28203D4FBAAAF6AAD7080CF277")); FLogMessagesMessageHandler::FLogMessagesMessageHandler(ILogHandler& InCOTFSLogHandler) : COTFSLogHandler(InCOTFSLogHandler) { } void FLogMessagesMessageHandler::ClientReportLogMessage(const FReplicatedLogData& LogData) { FScopeLock QueueScopeLock(&QueueLock); QueuedLogs.Add(LogData); } void FLogMessagesMessageHandler::ClientTick(FMPCollectorClientTickContext& Context) { { FScopeLock QueueScopeLock(&QueueLock); Swap(QueuedLogs, QueuedLogsBackBuffer); } if (!QueuedLogsBackBuffer.IsEmpty()) { FCbWriter Writer; Writer.BeginObject(); Writer << "Messages" << QueuedLogsBackBuffer; Writer.EndObject(); Context.AddMessage(Writer.Save().AsObject()); QueuedLogsBackBuffer.Reset(); } } void FLogMessagesMessageHandler::ServerReceiveMessage(FMPCollectorServerMessageContext& Context, FCbObjectView InMessage) { TArray Messages; if (!LoadFromCompactBinary(InMessage["Messages"], Messages)) { UE_LOG(LogCook, Error, TEXT("FLogMessagesMessageHandler received corrupted message from CookWorker")); return; } int32 CookWorkerProfileId = Context.GetProfileId(); for (FReplicatedLogData& LogData : Messages) { COTFSLogHandler.ReplayLogFromCookWorker(MoveTemp(LogData), CookWorkerProfileId); } COTFSLogHandler.ConditionalPruneReplay(); } FGuid FHeartbeatMessage::MessageType(TEXT("C08FFAF07BF34DD3A2FFB8A287CDDE83")); FHeartbeatMessage::FHeartbeatMessage(int32 InHeartbeatNumber) : HeartbeatNumber(InHeartbeatNumber) { } void FHeartbeatMessage::Write(FCbWriter& Writer) const { Writer << "H" << HeartbeatNumber; } bool FHeartbeatMessage::TryRead(FCbObjectView Object) { return LoadFromCompactBinary(Object["H"], HeartbeatNumber); } FPackageWriterMPCollector::FPackageWriterMPCollector(UCookOnTheFlyServer& InCOTFS) : COTFS(InCOTFS) { } void FPackageWriterMPCollector::ClientTickPackage(FMPCollectorClientTickPackageContext& Context) { for (const FMPCollectorClientTickPackageContext::FPlatformData& PlatformData : Context.GetPlatformDatas()) { if (PlatformData.CookResults == ECookResult::Invalid) { continue; } ICookedPackageWriter& PackageWriter = COTFS.FindOrCreatePackageWriter(PlatformData.TargetPlatform); TFuture ObjectFuture = PackageWriter.WriteMPCookMessageForPackage(Context.GetPackageName()); Context.AddAsyncPlatformMessage(PlatformData.TargetPlatform, MoveTemp(ObjectFuture)); } } void FPackageWriterMPCollector::ServerReceiveMessage(FMPCollectorServerMessageContext& Context, FCbObjectView Message) { FName PackageName = Context.GetPackageName(); const ITargetPlatform* TargetPlatform = Context.GetTargetPlatform(); check(PackageName.IsValid() && TargetPlatform); ICookedPackageWriter& PackageWriter = COTFS.FindOrCreatePackageWriter(TargetPlatform); if (!PackageWriter.TryReadMPCookMessageForPackage(PackageName, Message)) { UE_LOG(LogCook, Error, TEXT("CookWorkerServer received invalidly formatted PackageWriter message from CookWorker %d. Ignoring it."), Context.GetProfileId()); } } FGuid FPackageWriterMPCollector::MessageType(TEXT("D2B1CE3FD26644AF9EC28FBADB1BD331")); }