EM_Task/UnrealEd/Private/Cooker/CookWorkerClient.cpp

503 lines
19 KiB
C++
Raw Normal View History

2026-02-13 16:18:33 +08:00
#include "CookWorkerClient.h"
#include "CookDirector.h"
#include "CookWorkerServer.h"
#include "CookPackageData.h"
#include "IWorkerRequests.h"
#include "CookOnTheSide/CookOnTheFlyServer.h"
#include "SocketSubsystem.h"
#include "Sockets.h"
#include "SocketTypes.h"
#include "Misc/DefaultValueHelper.h"
#include "Commandlets/AssetRegistryGenerator.h"
namespace UE
{
namespace Cook
{
bool IsCookIgnoreTimeouts()
{
static bool bIsIgnoreCookTimeouts = FParse::Param(FCommandLine::Get(), TEXT("CookIgnoreTimeouts"));
return bIsIgnoreCookTimeouts;
}
FCookWorkerClient::FCookWorkerClient(UCookOnTheFlyServer& InCOTFS)
: COTFS(InCOTFS)
{
FMPCookSideEffectCollector::SetCookWorkerClient(this);
}
FCookWorkerClient::~FCookWorkerClient()
{
FMPCookSideEffectCollector::SetCookWorkerClient(nullptr);
}
bool FCookWorkerClient::TryConnect(FDirectorConnectionInfo&& ConnectInfo)
{
EPollStatus Status;
for (;;)
{
Status = PollTryConnect(ConnectInfo);
if (Status != EPollStatus::Incomplete)
{
break;
}
constexpr float SleepTime = 0.01f; // 10 ms
FPlatformProcess::Sleep(SleepTime);
}
return Status == EPollStatus::Success;
}
EPollStatus FCookWorkerClient::PollTryConnect(const FDirectorConnectionInfo& ConnectInfo)
{
for (;;)
{
switch (ConnectStatus)
{
case EConnectStatus::Connected:
return EPollStatus::Success;
case EConnectStatus::Uninitialized:
CreateServerSocket(ConnectInfo);
break;
case EConnectStatus::WaitInitialConfig:
WaitInitialConfigMessage();
if (ConnectStatus == EConnectStatus::WaitInitialConfig)
{
return EPollStatus::Incomplete;
}
break;
case EConnectStatus::LostConnection:
return EPollStatus::Error;
default:
return EPollStatus::Error;
}
}
}
void FCookWorkerClient::CreateServerSocket(const FDirectorConnectionInfo& ConnectInfo)
{
DirectorURI = ConnectInfo.HostURI;
ISocketSubsystem* SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
if (!SocketSubsystem)
{
UE_LOG(LogCook, Error, TEXT("CookWorker initialization failure: platform does not support network sockets, cannot connect to CookDirector."));
SendToState(EConnectStatus::LostConnection);
return;
}
UE_LOG(LogCook, Display, TEXT("Connecting to CookDirector at %s..."), *DirectorURI);
FSocket* ServerSocket = SocketSubsystem->CreateSocket(NAME_Stream, TEXT("FCookWorkerClient-WorkerConnect"));
if (!ServerSocket)
{
UE_LOG(LogCook, Error, TEXT("CookWorker initialization failure: Could not create socket."));
SendToState(EConnectStatus::LostConnection);
return;
}
FString IPString, PortString;
int32 Port;
if (!DirectorURI.Split(TEXT(":"), &IPString, &PortString, ESearchCase::IgnoreCase, ESearchDir::FromEnd))
{
IPString = DirectorURI;
}
if (!FDefaultValueHelper::ParseInt(PortString, Port))
{
Port = ConnectInfo.DirectorPort;
}
bool bIsValidIP = false;
auto DirectorAddr = SocketSubsystem->CreateInternetAddr();
DirectorAddr->SetIp(*IPString, bIsValidIP);
DirectorAddr->SetPort(Port);
if (!bIsValidIP)
{
UE_LOG(LogCook, Error, TEXT("CookWorker initialization failure: could not convert -CookDirectorHost=%s into an address (Invalid Format)."), *DirectorURI);
SendToState(EConnectStatus::LostConnection);
return;
}
constexpr float WaitForConnectTimeout = 60.f * 10; // 10 minutes
float ConditionalTimeoutSeconds = IsCookIgnoreTimeouts() ? FLT_MAX : WaitForConnectTimeout;
bool bConnectStarted = ServerSocket->Connect(*DirectorAddr);
bool bServerSocketReady = ServerSocket->Wait(ESocketWaitConditions::WaitForWrite, FTimespan::FromSeconds(ConditionalTimeoutSeconds));
if (!bServerSocketReady || ServerSocket->GetConnectionState() != ESocketConnectionState::SCS_Connected)
{
SocketSubsystem->DestroySocket(ServerSocket);
ServerSocket = nullptr;
UE_LOG(LogCook, Error, TEXT("CookWorker initialization failure: Timed out after %.0f seconds or connection failed trying to connect to CookDirector (State: %d)."),
ConditionalTimeoutSeconds, (int32)ServerSocket->GetConnectionState());
SendToState(EConnectStatus::LostConnection);
return;
}
InitSocket(ServerSocket);
FWorkerConnectMessage ConnectMessage;
ConnectMessage.RemoteIndex = ConnectInfo.RemoteIndex;
WritePacket(ConnectMessage);
SendToState(EConnectStatus::WaitInitialConfig);
}
void FCookWorkerClient::SendToState(EConnectStatus TargetStatus)
{
if (TargetStatus == EConnectStatus::LostConnection)
{
CloseSocket();
UE_LOG(LogCook, Error, TEXT("CookWorker LostConnection %d"), ConnectStatus);
}
else if (TargetStatus == EConnectStatus::ShuttingDown)
{
ConnectStartTimeSeconds = FPlatformTime::Seconds();
}
ConnectStatus = TargetStatus;
}
void FCookWorkerClient::WaitInitialConfigMessage()
{
check(!InitialConfigMessage);
InitialConfigMessage = MakeUnique<FInitialConfigMessage>();
if (!ReadPacket(*InitialConfigMessage))
{
UE_LOG(LogCook, Warning, TEXT("CookWorker initialization failure: Director sent an invalid InitialConfigMessage."));
SendToState(EConnectStatus::LostConnection);
}
DirectorCookMode = InitialConfigMessage->GetDirectorCookMode();
InitialConfigMessage->WriteToTargetPlatform(OrderedSessionPlatforms);
SendToState(EConnectStatus::Connected);
}
void FCookWorkerClient::DoneWithInitialSettings()
{
InitialConfigMessage.Reset();
BlockSocket(false);
}
void FCookWorkerClient::TickFromSchedulerThread(FTickStackData& StackData)
{
if (ConnectStatus == EConnectStatus::Connected)
{
PumpReceiveMessages();
if (ConnectStatus == EConnectStatus::Connected)
{
PumpSendMessages(bFlushPendingResults);
}
}
if (ConnectStatus == EConnectStatus::ShuttingDown || ConnectStatus == EConnectStatus::LostConnection)
{
PumpDisconnect(StackData);
}
}
void FCookWorkerClient::PumpReceiveMessages()
{
EConnectionStatus SocketStatus = TryReadPacket(PendingMessages);
if (SocketStatus != EConnectionStatus::Okay && SocketStatus != EConnectionStatus::Incomplete)
{
UE_LOG(LogCook, Error, TEXT("CookWorker initialization failure: failed to read from socket."));
SendToState(EConnectStatus::LostConnection);
return;
}
if (PendingMessages.Num() > 0)
{
HandleReceiveMessages();
return;
}
}
void FCookWorkerClient::HandleReceiveMessages(FName OptionalPackageName)
{
FAssignPackagesMessage AssignPackagesMessage;
for (auto& Message: PendingMessages)
{
if (ConnectStatus == EConnectStatus::ShuttingDown)
{
UE_LOG(LogCook, Log, TEXT("CookWorkerClient received message %s while shutting down. It will be ignored."), *Message.MessageType.ToString());
continue;
}
if (Message.MessageType == FShutdownRequestMessage::MessageType)
{
PumpSendMessages(true);
UE_LOG(LogCook, Display, TEXT("CookWorkerClient received ShutdownRequest from Director. Shutting down."));
SendToState(EConnectStatus::ShuttingDown);
break;
}
else if (Message.MessageType == FAssignPackagesMessage::MessageType)
{
AssignPackagesMessage.Packages.Reset();
if (!ReadMarshalledPacket(Message, AssignPackagesMessage))
{
LogInvalidMessage(TEXT("FAssignPackagesMessage"), Message.MessageType);
}
else
{
UE_LOG(LogCook, Display, TEXT("HandleReceiveMessages<<<<<<<<<<<< FAssignPackagesMessage %d"), AssignPackagesMessage.Packages.Num());
AssignPackages(AssignPackagesMessage);
}
}
else if (Message.MessageType == FSyncFenceMessage::MessageType)
{
FSyncFenceMessage SyncFenceMessage;
if (!ReadMarshalledPacket(Message, SyncFenceMessage))
{
LogInvalidMessage(TEXT("SyncFenceMessage"), Message.MessageType);
}
else
{
UE_LOG(LogCook, Display, TEXT("TrySyncFenceMessage<<<<<<<<<<<<<<< %d => %d"), SyncFenceMessage.FenceVersion, SyncFenceVersion);
if (SyncFenceVersion <= SyncFenceMessage.FenceVersion)
{
SyncFenceVersion = SyncFenceMessage.FenceVersion;
SendSyncFenceVersion = 0;
}
}
}
}
PendingMessages.Empty();
}
void FCookWorkerClient::AssignPackages(FAssignPackagesMessage& Message)
{
for (FAssignPackageData& AssignData: Message.Packages)
{
FName PackageName = AssignData.PackageName;
FPackageData& PackageData = COTFS.PackageDatas->FindOrAddPackageData(PackageName, AssignData.NormalizedFileName);
if (PackageData.GetState() <= EPackageState::WaitAssign)
{
ESendFlags SendFlag = PackageData.GetState() == EPackageState::Idle ? ESendFlags::QueueAdd : ESendFlags::QueueAddAndRemove;
PackageData.UpdateRequestData(OrderedSessionPlatforms, false, FCompletionCallback(), ESendFlags::QueueNone);
UPackage* ExistingPackage = (UPackage*)StaticFindObject(UPackage::StaticClass(), nullptr, *PackageName.ToString());
if (ExistingPackage != nullptr && ExistingPackage->IsFullyLoaded())
{
PackageData.SendToState(EPackageState::LoadReady, SendFlag);
}
else
{
PackageData.SendToState(EPackageState::LoadPrepare, SendFlag);
}
}
else
{
bool IsCooked = PackageData.HasAllCookedPlatforms(OrderedSessionPlatforms, true);
UE_LOG(LogCook, Error, TEXT("AssignPackages With Error Package State. %s %d %d"), *PackageName.ToString(), PackageData.GetState(), IsCooked);
ReportPackageMessage(PackageData, IsCooked ? EMPPackageResult::Success : EMPPackageResult::Failed);
}
}
SyncFenceVersion = Message.FenceVersion;
bFlushPendingResults = false;
}
void FCookWorkerClient::ReportDiscoveredPackage(TArray<FPackageData*>& Requests, TSet<FPackageData*>& LoadedPackages)
{
for (auto PackageData: Requests)
{
bool IsLoaded = LoadedPackages.Contains(PackageData);
auto& Discovered = PendingDiscoveredPackagesMessage.Packages.Emplace_GetRef();
Discovered.PackageName = PackageData->GetPackageName();
Discovered.NormalizedFileName = PackageData->GetFileName();
Discovered.TargetState = IsLoaded ? EPackageState::LoadReady : EPackageState::LoadPrepare;
PackageData->SendToState(EPackageState::WaitAssign, ESendFlags::QueueAddAndRemove);
}
}
void FCookWorkerClient::ReportPackageMessage(const FPackageData& PackageData, EMPPackageResult Reason)
{
auto& PackageResult = PendingPackageResultsMessage.Results.Emplace_GetRef();
FName PackageName = PackageData.GetPackageName();
PackageResult.PackageName = PackageName;
PackageResult.PackageResult = Reason;
if (Reason == EMPPackageResult::Success)
{
static TArray<ECookResult> FailedPlatformResults;
const int32 NumPlatforms = OrderedSessionPlatforms.Num();
FailedPlatformResults.Reset(NumPlatforms);
bool bExistsFailed = false;
for (int32 PlatformIndex = 0; PlatformIndex < NumPlatforms; ++PlatformIndex)
{
const ITargetPlatform* TargetPlatform = OrderedSessionPlatforms[PlatformIndex];
const ECookResult CookResult = PackageData.GetCookResults(TargetPlatform);
bExistsFailed = bExistsFailed || CookResult != ECookResult::Succeeded;
FailedPlatformResults.Add(CookResult);
}
if (bExistsFailed)
{
PackageResult.CookResult = FailedPlatformResults;
}
}
if (auto It = CookCollectorData.AssetPackageDataMap.Find(PackageName))
{
PackageResult.AssetPackages = MoveTemp(*It);
CookCollectorData.AssetPackageDataMap.Remove(PackageName);
}
}
void FCookWorkerClient::UpdateAssetRegistryPackageData(FAssetRegistryGenerator* Generator, const UPackage& Package, UCookOnTheFlyServer::FCreateOrFindArray& OutputAssets)
{
const FName PackageName = Package.GetFName();
FAssetPackageData* AssetPackageData = Generator->GetAssetPackageData(PackageName);
auto TargetPlatform = Generator->GetTargetPlatform();
const int32 NumPlatforms = OrderedSessionPlatforms.Num();
const int32 TargetIndex = OrderedSessionPlatforms.IndexOfByKey(TargetPlatform);
{
FScopeLock Lock(&CookCollectorData.AssetPackageDataLock);
auto& AssetPackageDatas = CookCollectorData.AssetPackageDataMap.FindOrAdd(PackageName);
if (AssetPackageDatas.Num() == 0)
{
AssetPackageDatas.AddDefaulted(NumPlatforms);
}
AssetPackageDatas[TargetIndex] = *AssetPackageData;
}
{
FScopeLock Lock(&CookCollectorData.AssetRegistryDataLock);
for (auto AssetData: OutputAssets)
{
const FName ObjectPath = AssetData->ObjectPath;
auto& AssetRegistryDatas = CookCollectorData.AssetRegistryDataMap.FindOrAdd(ObjectPath);
if (AssetRegistryDatas.Num() == 0)
{
AssetRegistryDatas.Reserve(NumPlatforms);
for (int Index = 0; Index < NumPlatforms; Index++)
{
auto& AssetRegistryData = AssetRegistryDatas.Emplace_GetRef();
AssetRegistryData.AssetClass = AssetData->AssetClass;
AssetRegistryData.PackageFlags = AssetData->PackageFlags;
}
}
auto& Remote = AssetRegistryDatas[TargetIndex];
for (const auto& Pair: AssetData->TagsAndValues)
{
Remote.Tags.Add(Pair.Key, Pair.Value.AsString());
}
}
MarkAssetRegistryDirty();
}
}
void FCookWorkerClient::TrySyncFenceMessage()
{
constexpr float WaitForSyncFenceTimeout = 3.f;
static float LastWaitForSyncFenceTime = 0.f;
if (FPlatformTime::Seconds() - LastWaitForSyncFenceTime <= WaitForSyncFenceTimeout)
{
return;
}
LastWaitForSyncFenceTime = FPlatformTime::Seconds();
if (SendSyncFenceVersion == SyncFenceVersion || PendingMessages.Num() > 0 ||
PendingPackageResultsMessage.Results.Num() > 0 || PendingDiscoveredPackagesMessage.Packages.Num() > 0 || bAssetRegistryDirty)
{
if (PendingMessages.Num() == 0 && PendingDiscoveredPackagesMessage.Packages.Num() == 0)
{
bFlushPendingResults = true;
}
else
{
bFlushAssetRegistryMessage = true;
}
return;
}
UE_LOG(LogCook, Display, TEXT("TrySyncFenceMessage>>>>>>>>>>>>>>> %d => %d"), SendSyncFenceVersion, SyncFenceVersion);
SendSyncFenceVersion = SyncFenceVersion;
FSyncFenceMessage SyncFenceMessage;
SyncFenceMessage.FenceVersion = SyncFenceVersion;
WriteMarshalledPacket(SyncFenceMessage);
CollectGarbage(RF_NoFlags, true);
}
void FCookWorkerClient::PumpSendMessages(bool bFlush)
{
int32 ResultNum = PendingPackageResultsMessage.Results.Num();
if (ResultNum >= FCookSocket::MaxSendPackageNum || (bFlush && ResultNum > 0))
{
PendingPackageResultsMessage.MessageSliceTo(ResultNum, SendPackageResultsMessage);
UE_LOG(LogCook, Display, TEXT("PumpSendMessages>>>>>>>>>>>> FPackageResultsMessage %d %d::%d"), bFlushPendingResults, PendingPackageResultsMessage.Results.Num(), SendPackageResultsMessage.Results.Num());
WriteMarshalledPacket(SendPackageResultsMessage);
bFlushAssetRegistryMessage = true;
bFlushPendingResults = false;
}
if (bAssetRegistryDirty && bFlushAssetRegistryMessage)
{
bFlushAssetRegistryMessage = false;
{
FScopeLock Lock(&CookCollectorData.AssetRegistryDataLock);
SendAssetRegistryMessage.AssetRegistryDataMap = MoveTemp(CookCollectorData.AssetRegistryDataMap);
bAssetRegistryDirty = false;
}
{
FScopeLock Lock(&CookCollectorData.CookSideEffectsLock);
CookCollectorData.UniqueToCookSideEffects(SendAssetRegistryMessage.CookSideEffects);
}
{
FScopeLock Lock(&CookCollectorData.AssetPackageDataLock);
SendAssetRegistryMessage.AssetPackageDataMap = MoveTemp(CookCollectorData.AssetPackageDataMap);
}
WriteMarshalledPacket(SendAssetRegistryMessage);
SendAssetRegistryMessage.AssetRegistryDataMap.Reset();
SendAssetRegistryMessage.CookSideEffects.Reset();
}
if (PendingDiscoveredPackagesMessage.Packages.Num() > 0)
{
PendingDiscoveredPackagesMessage.MessageSliceTo(FCookSocket::MaxSendPackageNum, SendDiscoveredPackagesMessage);
UE_LOG(LogCook, Display, TEXT("PumpSendMessages>>>>>>>>>>>> FDiscoveredPackagesMessage %d::%d"), PendingDiscoveredPackagesMessage.Packages.Num(), SendDiscoveredPackagesMessage.Packages.Num());
WriteMarshalledPacket(SendDiscoveredPackagesMessage);
}
}
void FCookWorkerClient::PumpDisconnect(FTickStackData& StackData)
{
if (ConnectStatus == EConnectStatus::ShuttingDown)
{
if (!bSendShutdownAcknowledgeMessage)
{
bSendShutdownAcknowledgeMessage = true;
UE_LOG(LogCook, Display, TEXT("CookWorkerClient sending shutdown acknowledge and closing connection."));
if (!WriteMarshalledPacket(FShutdownAcknowledgeMessage()))
{
SendToState(EConnectStatus::LostConnection);
}
}
constexpr float WaitForShutdownTimeout = 60.f;
if (FPlatformTime::Seconds() - ConnectStartTimeSeconds > WaitForShutdownTimeout)
{
UE_LOG(LogCook, Error, TEXT("CookWorkerClient timed out waiting for shutdown acknowledge. Terminating connection."));
SendToState(EConnectStatus::LostConnection);
}
}
if (ConnectStatus == EConnectStatus::LostConnection)
{
COTFS.CancelCookByTheBook();
}
}
void FCookWorkerClient::LogInvalidMessage(const TCHAR* MessageTypeName, FGuid MessageType)
{
UE_LOG(LogCook, Error, TEXT("CookWorkerClient received invalidly formatted message for type %s::%s from CookDirector. Ignoring it."), MessageTypeName, *MessageType.ToString());
}
FCookByTheBookOptions&& FCookWorkerClient::ConsumeCookByTheBookOptions()
{
check(InitialConfigMessage); // Should only be called after TryConnect and before DoneWithInitialSettings
return InitialConfigMessage->ConsumeCookByTheBookOptions();
}
FCookByTheBookStartupOptions&& FCookWorkerClient::ConsumeCookByTheBookStartupOptions()
{
check(InitialConfigMessage);
return InitialConfigMessage->ConsumeCookByTheBookStartupOptions();
}
const TArray<ITargetPlatform*>& FCookWorkerClient::GetTargetPlatforms() const
{
return OrderedSessionPlatforms;
}
} // namespace Cook
} // namespace UE