Implement Platform::CreateProcess with SDL backend
Supports handling process standard output and standard error streams separately in realtime.
This commit is contained in:
@@ -2993,6 +2993,7 @@ bool LinuxPlatform::SetEnvironmentVariable(const String& name, const String& val
|
||||
return setenv(StringAsANSI<>(*name).Get(), StringAsANSI<>(*value).Get(), true) != 0;
|
||||
}
|
||||
|
||||
#if !PLATFORM_SDL
|
||||
int32 LinuxPlatform::CreateProcess(CreateProcessSettings& settings)
|
||||
{
|
||||
LOG(Info, "Command: {0} {1}", settings.FileName, settings.Arguments);
|
||||
@@ -3106,6 +3107,7 @@ int32 LinuxPlatform::CreateProcess(CreateProcessSettings& settings)
|
||||
|
||||
return returnCode;
|
||||
}
|
||||
#endif
|
||||
|
||||
void* LinuxPlatform::LoadLibrary(const Char* filename)
|
||||
{
|
||||
|
||||
@@ -148,7 +148,9 @@ public:
|
||||
static void GetEnvironmentVariables(Dictionary<String, String, HeapAllocation>& result);
|
||||
static bool GetEnvironmentVariable(const String& name, String& value);
|
||||
static bool SetEnvironmentVariable(const String& name, const String& value);
|
||||
#if !PLATFORM_SDL
|
||||
static int32 CreateProcess(CreateProcessSettings& settings);
|
||||
#endif
|
||||
static void* LoadLibrary(const Char* filename);
|
||||
static void FreeLibrary(void* handle);
|
||||
static void* GetProcAddress(void* handle, const char* symbol);
|
||||
|
||||
@@ -437,6 +437,7 @@ Window* MacPlatform::CreateWindow(const CreateWindowSettings& settings)
|
||||
|
||||
#endif
|
||||
|
||||
#if !PLATFORM_SDL
|
||||
int32 MacPlatform::CreateProcess(CreateProcessSettings& settings)
|
||||
{
|
||||
LOG(Info, "Command: {0} {1}", settings.FileName, settings.Arguments);
|
||||
@@ -576,5 +577,6 @@ int32 MacPlatform::CreateProcess(CreateProcessSettings& settings)
|
||||
|
||||
return returnCode;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -27,7 +27,9 @@ public:
|
||||
static Rectangle GetVirtualDesktopBounds();
|
||||
static String GetMainDirectory();
|
||||
static Window* CreateWindow(const CreateWindowSettings& settings);
|
||||
#if !PLATFORM_SDL
|
||||
static int32 CreateProcess(CreateProcessSettings& settings);
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -8,18 +8,19 @@
|
||||
#include "Engine/Input/Input.h"
|
||||
#include "Engine/Input/Mouse.h"
|
||||
#include "Engine/Platform/BatteryInfo.h"
|
||||
#include "Engine/Platform/CreateProcessSettings.h"
|
||||
#include "Engine/Platform/WindowsManager.h"
|
||||
#include "Engine/Platform/SDL/SDLInput.h"
|
||||
#include "Engine/Engine/Engine.h"
|
||||
|
||||
#include <SDL3/SDL_hints.h>
|
||||
#include <SDL3/SDL_init.h>
|
||||
#include <SDL3/SDL_locale.h>
|
||||
#include <SDL3/SDL_misc.h>
|
||||
#include <SDL3/SDL_power.h>
|
||||
#include <SDL3/SDL_process.h>
|
||||
#include <SDL3/SDL_revision.h>
|
||||
#include <SDL3/SDL_system.h>
|
||||
#include <SDL3/SDL_version.h>
|
||||
#include <SDL3/SDL_locale.h>
|
||||
|
||||
#if PLATFORM_LINUX
|
||||
#include "Engine/Engine/CommandLine.h"
|
||||
@@ -316,4 +317,152 @@ Window* SDLPlatform::CreateWindow(const CreateWindowSettings& settings)
|
||||
return New<SDLWindow>(settings);
|
||||
}
|
||||
|
||||
bool ReadStream(SDL_IOStream*& stream, char* buffer, int32 bufferLength, int32& bufferPosition, LogType logType, CreateProcessSettings& settings)
|
||||
{
|
||||
bool flushBuffer = false;
|
||||
bool success = true;
|
||||
int32 read = SDL_ReadIO(stream, buffer + bufferPosition, bufferLength - bufferPosition - 1);
|
||||
if (read == 0)
|
||||
{
|
||||
SDL_IOStatus status = SDL_GetIOStatus(stream);
|
||||
if (status != SDL_IO_STATUS_NOT_READY && status != SDL_IO_STATUS_EOF)
|
||||
success = false;
|
||||
if (status != SDL_IO_STATUS_NOT_READY)
|
||||
{
|
||||
stream = nullptr;
|
||||
flushBuffer = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int32 startPosition = bufferPosition;
|
||||
bufferPosition += read;
|
||||
if (bufferPosition == bufferLength - 1)
|
||||
{
|
||||
flushBuffer = true;
|
||||
buffer[bufferPosition++] = '\n'; // Make sure to flush fully filled buffer
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = startPosition; i < bufferPosition; ++i)
|
||||
{
|
||||
if (buffer[i] == '\n')
|
||||
{
|
||||
flushBuffer = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (flushBuffer)
|
||||
{
|
||||
int32 start = 0;
|
||||
for (int i = 0; i < bufferPosition; ++i)
|
||||
{
|
||||
if (buffer[i] != '\n')
|
||||
continue;
|
||||
|
||||
String str(&buffer[start], i - start + 1);
|
||||
#if LOG_ENABLE
|
||||
if (settings.LogOutput)
|
||||
Log::Logger::Write(logType, StringView(str.Get(), str.Length() - 1));
|
||||
#endif
|
||||
if (settings.SaveOutput)
|
||||
settings.Output.Add(str.Get(), str.Length());
|
||||
start = i + 1;
|
||||
}
|
||||
int32 length = bufferPosition - start;
|
||||
if (length > 0)
|
||||
{
|
||||
// TODO: Use memmove here? Overlapped memory regions with memcpy is undefined behaviour
|
||||
char temp[2048];
|
||||
Platform::MemoryCopy(temp, buffer + start, length);
|
||||
Platform::MemoryCopy(buffer, temp, length);
|
||||
bufferPosition = length;
|
||||
}
|
||||
else
|
||||
bufferPosition = 0;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
int32 SDLPlatform::CreateProcess(CreateProcessSettings& settings)
|
||||
{
|
||||
LOG(Info, "Command: {0} {1}", settings.FileName, settings.Arguments);
|
||||
if (settings.WorkingDirectory.HasChars())
|
||||
LOG(Info, "Working directory: {0}", settings.WorkingDirectory);
|
||||
|
||||
int32 result = 0;
|
||||
const bool captureStdOut = settings.LogOutput || settings.SaveOutput;
|
||||
const StringAnsi cmdLine = StringAnsi::Format("\"{0}\" {1}", StringAnsi(settings.FileName), StringAnsi(settings.Arguments));
|
||||
const char* cmd[] = { "sh", "-c", cmdLine.Get(), (char *)0 };
|
||||
StringAnsi workingDirectory(settings.WorkingDirectory);
|
||||
#if PLATFORM_WINDOWS
|
||||
bool background = !settings.WaitForEnd || settings.HiddenWindow; // This also hides the window on Windows
|
||||
#else
|
||||
bool background = !settings.WaitForEnd;
|
||||
#endif
|
||||
|
||||
// Populate environment with current values from parent environment.
|
||||
// SDL does not populate the environment with the latest values but with a snapshot captured during initialization.
|
||||
Dictionary<String, String> envDictionary;
|
||||
GetEnvironmentVariables(envDictionary);
|
||||
SDL_Environment* env = SDL_CreateEnvironment(false);
|
||||
for (auto iter = envDictionary.Begin(); iter != envDictionary.End(); ++iter)
|
||||
SDL_SetEnvironmentVariable(env, StringAnsi(iter->Key).Get(), StringAnsi(iter->Value).Get(), true);
|
||||
for (auto iter = settings.Environment.Begin(); iter != settings.Environment.End(); ++iter)
|
||||
SDL_SetEnvironmentVariable(env, StringAnsi(iter->Key).Get(), StringAnsi(iter->Value).Get(), true);
|
||||
|
||||
SDL_PropertiesID props = SDL_CreateProperties();
|
||||
SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, cmd);
|
||||
SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, env);
|
||||
SDL_SetBooleanProperty(props, SDL_PROP_PROCESS_CREATE_BACKGROUND_BOOLEAN, background);
|
||||
if (workingDirectory.HasChars())
|
||||
SDL_SetStringProperty(props, SDL_PROP_PROCESS_CREATE_WORKING_DIRECTORY_STRING, workingDirectory.Get());
|
||||
if (captureStdOut)
|
||||
{
|
||||
SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP);
|
||||
SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER, SDL_PROCESS_STDIO_APP);
|
||||
}
|
||||
SDL_Process* process = SDL_CreateProcessWithProperties(props);
|
||||
SDL_DestroyProperties(props);
|
||||
SDL_DestroyEnvironment(env);
|
||||
if (process == nullptr)
|
||||
{
|
||||
LOG(Error, "Failed to run process {}: {}", String(settings.FileName).Get(), String(SDL_GetError()));
|
||||
return -1;
|
||||
}
|
||||
|
||||
props = SDL_GetProcessProperties(process);
|
||||
int32 pid = SDL_GetNumberProperty(props, SDL_PROP_PROCESS_PID_NUMBER, 0);
|
||||
SDL_IOStream* stdoutStream = nullptr;
|
||||
SDL_IOStream* stderrStream = nullptr;
|
||||
if (captureStdOut)
|
||||
{
|
||||
stdoutStream = static_cast<SDL_IOStream*>(SDL_GetPointerProperty(props, SDL_PROP_PROCESS_STDOUT_POINTER, nullptr));
|
||||
stderrStream = static_cast<SDL_IOStream*>(SDL_GetPointerProperty(props, SDL_PROP_PROCESS_STDERR_POINTER, nullptr));
|
||||
}
|
||||
|
||||
// Handle process output in realtime
|
||||
char stdoutBuffer[2049];
|
||||
int32 stdoutPosition = 0;
|
||||
char stderrBuffer[2049];
|
||||
int32 stderrPosition = 0;
|
||||
while (stdoutStream != nullptr && stderrStream != nullptr)
|
||||
{
|
||||
if (stdoutStream != nullptr && !ReadStream(stdoutStream, stdoutBuffer, sizeof(stdoutBuffer), stdoutPosition, LogType::Info, settings))
|
||||
LOG(Warning, "Failed to read process {} stdout: {}", pid, String(SDL_GetError()));
|
||||
if (stderrStream != nullptr && !ReadStream(stderrStream, stderrBuffer, sizeof(stderrBuffer), stderrPosition, LogType::Error, settings))
|
||||
LOG(Warning, "Failed to read process {} stderr: {}", pid, String(SDL_GetError()));
|
||||
Sleep(1);
|
||||
}
|
||||
|
||||
if (settings.WaitForEnd)
|
||||
SDL_WaitProcess(process, true, &result);
|
||||
|
||||
SDL_DestroyProcess(process);
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -87,6 +87,7 @@ public:
|
||||
static Rectangle GetMonitorBounds(const Float2& screenPos);
|
||||
static Rectangle GetVirtualDesktopBounds();
|
||||
static Window* CreateWindow(const CreateWindowSettings& settings);
|
||||
static int32 CreateProcess(CreateProcessSettings& settings);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1121,6 +1121,7 @@ bool IsProcRunning(HANDLE handle)
|
||||
return WaitForSingleObject(handle, 0) == WAIT_TIMEOUT;
|
||||
}
|
||||
|
||||
#if !PLATFORM_SDL
|
||||
void ReadPipe(HANDLE pipe, Array<char>& rawData, Array<Char>& logData, LogType logType, CreateProcessSettings& settings)
|
||||
{
|
||||
// Check if any data is ready to read
|
||||
@@ -1329,7 +1330,6 @@ int32 WindowsPlatform::CreateProcess(CreateProcessSettings& settings)
|
||||
return result;
|
||||
}
|
||||
|
||||
#if !PLATFORM_SDL
|
||||
Window* WindowsPlatform::CreateWindow(const CreateWindowSettings& settings)
|
||||
{
|
||||
return New<WindowsWindow>(settings);
|
||||
|
||||
@@ -81,8 +81,8 @@ public:
|
||||
static void GetEnvironmentVariables(Dictionary<String, String, HeapAllocation>& result);
|
||||
static bool GetEnvironmentVariable(const String& name, String& value);
|
||||
static bool SetEnvironmentVariable(const String& name, const String& value);
|
||||
static int32 CreateProcess(CreateProcessSettings& settings);
|
||||
#if !PLATFORM_SDL
|
||||
static int32 CreateProcess(CreateProcessSettings& settings);
|
||||
static Window* CreateWindow(const CreateWindowSettings& settings);
|
||||
#endif
|
||||
static void* LoadLibrary(const Char* filename);
|
||||
|
||||
Reference in New Issue
Block a user