Files
UnrealEngine/Engine/Source/Programs/InterchangeWorker/Private/InterchangeWorkerImpl.cpp
2025-05-18 13:04:45 +08:00

344 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "InterchangeWorkerImpl.h"
#include "Async/Async.h"
#include "HAL/FileManager.h"
#include "HAL/PlatformFileManager.h"
#include "InterchangeCommands.h"
#include "InterchangeDispatcherConfig.h"
#include "InterchangeDispatcherTask.h"
#include "InterchangeFbxParser.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "SocketSubsystem.h"
#include "Sockets.h"
using namespace UE::Interchange;
FInterchangeWorkerImpl::FInterchangeWorkerImpl(int32 InServerPID, int32 InServerPort, FString& InResultFolder)
: ServerPID(InServerPID)
, ServerPort(InServerPort)
, PingStartCycle(0)
, ResultFolder(InResultFolder)
{
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
FPaths::NormalizeDirectoryName(ResultFolder);
if (!PlatformFile.DirectoryExists(*ResultFolder))
{
PlatformFile.CreateDirectory(*ResultFolder);
}
}
bool FInterchangeWorkerImpl::Run(const FString& WorkerVersionError)
{
UE_LOG(LogInterchangeWorker, Verbose, TEXT("connect to %d..."), ServerPort);
bool bConnected = NetworkInterface.Connect(TEXT("Interchange Worker"), ServerPort, Config::ConnectTimeout_s);
UE_LOG(LogInterchangeWorker, Verbose, TEXT("connected to %d %s"), ServerPort, bConnected ? TEXT("OK") : TEXT("FAIL"));
if (bConnected)
{
CommandIO.SetNetworkInterface(&NetworkInterface);
}
else
{
UE_LOG(LogInterchangeWorker, Error, TEXT("Server connection failure. exit"));
return false;
}
const bool bVersionError = !WorkerVersionError.IsEmpty();
if (bVersionError)
{
FErrorCommand ErrorCmd;
UInterchangeResultError_Generic* Message = FbxParser.AddMessage<UInterchangeResultError_Generic>();
Message->Text = FText::Format(NSLOCTEXT("InterchangeWorker", "WorkerVersionError", "Version Error: {0}"), FText::FromString(WorkerVersionError));
ErrorCmd.ErrorMessage = Message->ToJson();
CommandIO.SendCommand(ErrorCmd, Config::SendCommandTimeout_s);
//We want to time out after maximum of 5 seconds
double TimeOut = 5.0;
while (TimeOut > 0)
{
if (TSharedPtr<ICommand> Command = CommandIO.GetNextCommand(0.02))
{
if(Command->GetType() == ECommandId::Terminate)
{
UE_LOG(LogInterchangeWorker, Verbose, TEXT("Terminate command received. Exiting."));
break;
}
}
TimeOut -= 0.02;
}
return false;
}
InitiatePing();
bool bIsRunning = true;
while (bIsRunning)
{
if (TSharedPtr<ICommand> Command = CommandIO.GetNextCommand(0.02))
{
switch(Command->GetType())
{
case ECommandId::Ping:
ProcessCommand(*StaticCast<FPingCommand*>(Command.Get()));
break;
case ECommandId::BackPing:
ProcessCommand(*StaticCast<FBackPingCommand*>(Command.Get()));
break;
case ECommandId::RunTask:
{
FScopeLock Lock(&TFinishThreadCriticalSection);
//Use a randomGuid to generate thread unique name
int32 UniqueID = (FPlatformTime::Cycles64() & 0x00000000EFFFFFFF);
FString ThreadName = FString(TEXT("InterchangeWorkerCommand_")) + FString::FromInt(UniqueID);
ActiveThreads.Add(ThreadName, Async(
EAsyncExecution::ThreadPool,
[this, ThreadName, Command]()->bool
{
ProcessCommand(Command, ThreadName);
return true;
}
));
}
break;
case ECommandId::QueryTaskProgress:
ProcessCommand(*StaticCast<FQueryTaskProgressCommand*>(Command.Get()));
break;
case ECommandId::Terminate:
UE_LOG(LogInterchangeWorker, Verbose, TEXT("Terminate command received. Exiting."));
bIsRunning = false;
break;
case ECommandId::NotifyEndTask:
default:
break;
}
}
else
{
if (bIsRunning)
{
bIsRunning = ServerPID == 0 ? true : FPlatformProcess::IsApplicationRunning(ServerPID);
UE_CLOG(!bIsRunning, LogInterchangeWorker, Error, TEXT("Worker failure: server lost"));
}
}
//Cleanup Finish threads
{
FScopeLock Lock(&TFinishThreadCriticalSection);
for (const FString& ThreadName : CurrentFinishThreads)
{
if (TFuture<bool>* IsThreadCompleted = ActiveThreads.Find(ThreadName))
{
//Wait until process command is terminate
IsThreadCompleted->Get();
}
ActiveThreads.Remove(ThreadName);
}
CurrentFinishThreads.Empty();
}
//Sleep 0 to avoid using too much cpu
FPlatformProcess::Sleep(0.0f);
}
//Join all thread that is not terminate
for(TPair<FString, TFuture<bool>>& ActiveThreadPair : ActiveThreads)
{
ActiveThreadPair.Value.Get();
}
UE_CLOG(!bIsRunning, LogInterchangeWorker, Verbose, TEXT("Worker loop exit..."));
CommandIO.Disconnect(0);
return true;
}
void FInterchangeWorkerImpl::InitiatePing()
{
PingStartCycle = FPlatformTime::Cycles64();
FPingCommand Ping;
CommandIO.SendCommand(Ping, Config::SendCommandTimeout_s);
}
void FInterchangeWorkerImpl::ProcessCommand(const FPingCommand& PingCommand)
{
FBackPingCommand BackPing;
CommandIO.SendCommand(BackPing, Config::SendCommandTimeout_s);
}
void FInterchangeWorkerImpl::ProcessCommand(const FBackPingCommand& BackPingCommand)
{
if (PingStartCycle)
{
double ElapsedTime_s = FGenericPlatformTime::ToSeconds(FPlatformTime::Cycles64() - PingStartCycle);
UE_LOG(LogInterchangeWorker, Verbose, TEXT("Ping %f s"), ElapsedTime_s);
}
PingStartCycle = 0;
}
void FInterchangeWorkerImpl::ProcessCommand(const UE::Interchange::FQueryTaskProgressCommand& QueryTaskProgressCommand)
{
FCompletedQueryTaskProgressCommand CompletedCommand;
CompletedCommand.TaskStates.AddDefaulted(QueryTaskProgressCommand.TaskIndexes.Num());
{
FScopeLock Lock(&TFinishThreadCriticalSection);
for (int32 ProgressTaskIndex = 0; ProgressTaskIndex < QueryTaskProgressCommand.TaskIndexes.Num(); ++ProgressTaskIndex)
{
CompletedCommand.TaskStates[ProgressTaskIndex].TaskIndex = QueryTaskProgressCommand.TaskIndexes[ProgressTaskIndex];
//If we do not have any job to process, set the state to unknown. The caller will know we are not currently processing this task, whihc mean we never receive this task or this task is completed.
CompletedCommand.TaskStates[ProgressTaskIndex].TaskState = ActiveThreads.Num() == 0 ? ETaskState::Unknown : ETaskState::Running;
//TODO: implement better progress report, we currently always return 0.0
CompletedCommand.TaskStates[ProgressTaskIndex].TaskProgress = 0.0f;
}
}
CommandIO.SendCommand(CompletedCommand, Config::SendCommandTimeout_s);
}
void FInterchangeWorkerImpl::ProcessCommand(const TSharedPtr<UE::Interchange::ICommand> Command, const FString& ThreadName)
{
if (!Command.IsValid())
{
UE_LOG(LogInterchangeWorker, Error, TEXT("Process command error: The run task command is invalid"));
return;
}
FRunTaskCommand* RunTaskCommandPtr = StaticCast<FRunTaskCommand*>(Command.Get());
if (!RunTaskCommandPtr)
{
UE_LOG(LogInterchangeWorker, Error, TEXT("Process command error: The run task command is invalid"));
return;
}
const FRunTaskCommand& RunTaskCommand = *RunTaskCommandPtr;
const FString& JsonToProcess = RunTaskCommand.JsonDescription;
UE_LOG(LogInterchangeWorker, Verbose, TEXT("Process %s"), *JsonToProcess);
ETaskState ProcessResult = ETaskState::Unknown;
//Process the json and run the task
FString JSonResult;
TArray<FString> JSonMessages;
FJsonLoadSourceCmd LoadSourceCommand;
FJsonFetchAnimationQueriesCmd FetchAnimationQueries;
FJsonFetchMeshPayloadCmd FetchMeshPayloadCommand;
FJsonFetchPayloadCmd FetchPayloadCommand;
//Any command FromJson function return true if the Json descibe the command
if (LoadSourceCommand.FromJson(JsonToProcess))
{
//Load file command
if (LoadSourceCommand.GetTranslatorID().Equals(TEXT("FBX"), ESearchCase::IgnoreCase))
{
//We want to load an FBX file
ProcessResult = LoadFbxFile(LoadSourceCommand, JSonResult, JSonMessages);
}
}
else if (FetchAnimationQueries.FromJson(JsonToProcess))
{
//Load file command
if (FetchAnimationQueries.GetTranslatorID().Equals(TEXT("FBX"), ESearchCase::IgnoreCase))
{
//We want to load an FBX file
ProcessResult = FetchFbxPayload(FetchAnimationQueries, JSonResult, JSonMessages);
}
}
else if (FetchMeshPayloadCommand.FromJson(JsonToProcess))
{
//Load file command
if (FetchMeshPayloadCommand.GetTranslatorID().Equals(TEXT("FBX"), ESearchCase::IgnoreCase))
{
//We want to load an FBX file
ProcessResult = FetchFbxPayload(FetchMeshPayloadCommand, JSonResult, JSonMessages);
}
}
else if (FetchPayloadCommand.FromJson(JsonToProcess))
{
//Load file command
if (FetchPayloadCommand.GetTranslatorID().Equals(TEXT("FBX"), ESearchCase::IgnoreCase))
{
//We want to load an FBX file
ProcessResult = FetchFbxPayload(FetchPayloadCommand, JSonResult, JSonMessages);
}
}
else
{
ProcessResult = ETaskState::Unknown;
}
FCompletedTaskCommand CompletedTask;
CompletedTask.ProcessResult = ProcessResult;
CompletedTask.JSonMessages = JSonMessages;
CompletedTask.TaskIndex = RunTaskCommand.TaskIndex;
if (CompletedTask.ProcessResult == ETaskState::ProcessOk)
{
CompletedTask.JSonResult = JSonResult;
}
CommandIO.SendCommand(CompletedTask, Config::SendCommandTimeout_s);
UE_LOG(LogInterchangeWorker, Verbose, TEXT("End of Process %s"), *JsonToProcess);
//Notify the main thread we are done with this thread
{
FScopeLock Lock(&TFinishThreadCriticalSection);
CurrentFinishThreads.Add(ThreadName);
}
}
ETaskState FInterchangeWorkerImpl::LoadFbxFile(const FJsonLoadSourceCmd& LoadSourceCommand, FString& OutJSonResult, TArray<FString>& OutJSonMessages)
{
ETaskState ResultState = ETaskState::Unknown;
FString SourceFilename = LoadSourceCommand.GetSourceFilename();
FbxParser.Reset();
FbxParser.SetConvertSettings(LoadSourceCommand.GetDoesConvertScene(), LoadSourceCommand.GetDoesForceFrontXAxis(), LoadSourceCommand.GetDoesConvertSceneUnit(), LoadSourceCommand.GetDoesKeepFbxNamespace());
FbxParser.LoadFbxFile(SourceFilename, ResultFolder);
FJsonLoadSourceCmd::JsonResultParser ResultParser;
ResultParser.SetResultFilename(FbxParser.GetResultFilepath());
OutJSonMessages = FbxParser.GetJsonLoadMessages();
OutJSonResult = ResultParser.ToJson();
ResultState = ETaskState::ProcessOk;
return ResultState;
}
ETaskState FInterchangeWorkerImpl::FetchFbxPayload(const FJsonFetchPayloadCmd& FetchPayloadCommand, FString& OutJSonResult, TArray<FString>& OutJSonMessages)
{
ETaskState ResultState = ETaskState::Unknown;
FString PayloadKey = FetchPayloadCommand.GetPayloadKey();
FbxParser.FetchPayload(PayloadKey, ResultFolder);
FJsonLoadSourceCmd::JsonResultParser ResultParser;
ResultParser.SetResultFilename(FbxParser.GetResultPayloadFilepath(PayloadKey));
OutJSonMessages = FbxParser.GetJsonLoadMessages();
OutJSonResult = ResultParser.ToJson();
ResultState = ETaskState::ProcessOk;
return ResultState;
}
ETaskState FInterchangeWorkerImpl::FetchFbxPayload(const FJsonFetchMeshPayloadCmd& FetchMeshPayloadCommand, FString& OutJSonResult, TArray<FString>& OutJSonMessages)
{
ETaskState ResultState = ETaskState::Unknown;
FString PayloadKey = FetchMeshPayloadCommand.GetPayloadKey();
FTransform MeshGlobalTransform = FetchMeshPayloadCommand.GetMeshGlobalTransform();
FString ResultPayloadsUniqueId = FbxParser.FetchMeshPayload(PayloadKey, MeshGlobalTransform, ResultFolder);
FJsonLoadSourceCmd::JsonResultParser ResultParser;
ResultParser.SetResultFilename(FbxParser.GetResultPayloadFilepath(ResultPayloadsUniqueId));
OutJSonMessages = FbxParser.GetJsonLoadMessages();
OutJSonResult = ResultParser.ToJson();
ResultState = ETaskState::ProcessOk;
return ResultState;
}
ETaskState FInterchangeWorkerImpl::FetchFbxPayload(const FJsonFetchAnimationQueriesCmd& FetchAnimationBakeTransformPayloadCommand, FString& OutJSonResult, TArray<FString>& OutJSonMessages)
{
ETaskState ResultState = ETaskState::Unknown;
TMap<FString, FString> ResultPayloadFilePaths = FbxParser.FetchAnimationBakeTransformPayloads(FetchAnimationBakeTransformPayloadCommand.GetQueriesJsonString(), ResultFolder);
FJsonFetchAnimationQueriesCmd::JsonAnimationQueriesResultParser ResultParser;
ResultParser.SetHashToFilenames(ResultPayloadFilePaths);
OutJSonMessages = FbxParser.GetJsonLoadMessages();
OutJSonResult = ResultParser.ToJson();
ResultState = ETaskState::ProcessOk;
return ResultState;
}