Compare commits

..

2 Commits

Author SHA1 Message Date
131ad3120c backup 2025-11-27 03:36:22 +05:30
d24c7f3246 ProcessOps 2025-11-26 22:12:49 +05:30
29 changed files with 1103 additions and 929 deletions

2
.gitignore vendored
View File

@ -36,7 +36,6 @@
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
@ -47,4 +46,5 @@
*.vsix
.cache/
.local/
Build/

View File

@ -50,3 +50,10 @@ add_subdirectory(Src/)
if(IACore_BUILD_TESTS)
add_subdirectory(Tests)
endif()
# -------------------------------------------------
# Local Development Sandboxes (not included in source control)
# -------------------------------------------------
if (EXISTS ".local")
add_subdirectory(.local)
endif()

View File

@ -1,6 +1,12 @@
set(SRC_FILES
"imp/cpp/IACore.cpp"
"imp/cpp/Logger.cpp"
"imp/cpp/FileOps.cpp"
"imp/cpp/AsyncOps.cpp"
"imp/cpp/SocketOps.cpp"
"imp/cpp/ProcessOps.cpp"
"imp/cpp/BinaryStreamReader.cpp"
"imp/cpp/BinaryStreamWriter.cpp"
)
add_library(IACore STATIC ${SRC_FILES})

View File

@ -0,0 +1,22 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IACore/AsyncOps.hpp>
namespace IACore
{
}

View File

@ -0,0 +1,186 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IACore/FileOps.hpp>
namespace IACore
{
UnorderedMap<PCUINT8, PVOID> FileOps::s_mappedFiles;
VOID FileOps::UnmapFile(IN PCUINT8 mappedPtr)
{
if (!s_mappedFiles.contains(mappedPtr))
return;
const auto handle = s_mappedFiles.extract(mappedPtr)->second;
#if IA_PLATFORM_WINDOWS
#elif IA_PLATFORM_UNIX
#else
# error "IACore FileOps does not support this platform"
#endif
}
Expected<PUINT8, String> FileOps::MapFile(IN CONST FilePath &path)
{
#if IA_PLATFORM_WINDOWS
const auto handle = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (handle == INVALID_HANDLE_VALUE)
return MakeUnexpected(std::format("Failed to open {} for memory mapping", path.c_str()));
auto hmap = CreateFileMappingW(handle, NULL, PAGE_READWRITE, 0, 0, NULL);
if (hmap == NULL)
{
CloseHandle(handle);
return MakeUnexpected(std::format("Failed to memory map {}", path.c_str()));
}
const auto result = static_cast<PUINT8>(MapViewOfFile(hmap, FILE_MAP_ALL_ACCESS, 0, 0, 0));
if (result == NULL)
{
CloseHandle(handle);
CloseHandle(hmap);
return MakeUnexpected(std::format("Failed to memory map {}", path.c_str()));
}
s_mappedFiles[result] = (PVOID) handle;
return result;
#elif IA_PLATFORM_UNIX
const auto handle = open(path.c_str(), O_RDONLY);
if (handle == -1)
return MakeUnexpected(std::format("Failed to open {} for memory mapping", path.c_str()));
void *addr = mmap(nullptr, 0, PROT_WRITE | PROT_READ, MAP_PRIVATE, handle, 0);
if (addr == MAP_FAILED)
{
close(handle);
return MakeUnexpected(std::format("Failed to memory map {}", path.c_str()));
}
const auto result = static_cast<PUINT8>(addr);
s_mappedFiles[result] = (PVOID) ((UINT64) handle);
return result;
#else
# error "IACore FileOps does not support this platform"
#endif
}
Expected<PCUINT8, String> FileOps::MapReadonlyFile(IN CONST FilePath &path)
{
SIZE_T size;
return MapReadonlyFile(path, size);
}
Expected<PCUINT8, String> FileOps::MapReadonlyFile(IN CONST FilePath &path, OUT SIZE_T &size)
{
#if IA_PLATFORM_WINDOWS
const auto handle = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (handle == INVALID_HANDLE_VALUE)
return MakeUnexpected(std::format("Failed to open {} for memory mapping", path.c_str()));
LARGE_INTEGER fileSize;
if (!GetFileSizeEx(handle, &fileSize))
{
CloseHandle(handle);
return MakeUnexpected(std::format("Failed to get size of {} for memory mapping", path.c_str()));
}
size = static_cast<size_t>(fileSize.QuadPart);
if (size == 0)
{
CloseHandle(handle);
return MakeUnexpected(std::format("Failed to get size of {} for memory mapping", path.c_str()));
}
auto hmap = CreateFileMappingW(handle, NULL, PAGE_READONLY, 0, 0, NULL);
if (hmap == NULL)
{
CloseHandle(handle);
return MakeUnexpected(std::format("Failed to memory map {}", path.c_str()));
}
const auto result = static_cast<PCUINT8>(MapViewOfFile(hmap, FILE_MAP_READ, 0, 0, 0));
if (result == NULL)
{
CloseHandle(handle);
CloseHandle(hmap);
return MakeUnexpected(std::format("Failed to memory map {}", path.c_str()));
}
s_mappedFiles[result] = (PVOID) handle;
return result;
#elif IA_PLATFORM_UNIX
const auto handle = open(path.c_str(), O_RDONLY);
if (handle == -1)
return MakeUnexpected(std::format("Failed to open {} for memory mapping", path.c_str()));
struct stat sb;
if (fstat(handle, &sb) == -1)
{
close(handle);
return MakeUnexpected(std::format("Failed to get stats of {} for memory mapping", path.c_str()));
}
size = static_cast<size_t>(sb.st_size);
if (size == 0)
{
close(handle);
return MakeUnexpected(std::format("Failed to get size of {} for memory mapping", path.c_str()));
}
void *addr = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, handle, 0);
if (addr == MAP_FAILED)
{
close(handle);
return MakeUnexpected(std::format("Failed to memory map {}", path.c_str()));
}
const auto result = static_cast<PCUINT8>(addr);
madvise(addr, size, MADV_SEQUENTIAL);
s_mappedFiles[result] = (PVOID) ((UINT64) handle);
return result;
#else
# error "IACore FileOps does not support this platform"
#endif
}
Expected<StreamWriter, String> FileOps::StreamToFile(IN CONST FilePath &path)
{
}
Expected<StreamReader, String> FileOps::StreamFromFile(IN CONST FilePath &path)
{
}
Expected<String, String> FileOps::ReadTextFile(IN CONST FilePath &path)
{
}
Expected<Vector<UINT8>, String> FileOps::ReadBinaryFile(IN CONST FilePath &path)
{
}
Expected<SIZE_T, String> FileOps::WriteTextFile(IN CONST FilePath &path, IN CONST String &contents)
{
}
Expected<SIZE_T, String> FileOps::WriteBinaryFile(IN CONST FilePath &path, IN Span<UINT8> contents)
{
}
} // namespace IACore

View File

@ -15,7 +15,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IACore/Logger.hpp>
#include <IACore/File.hpp>
#include <IACore/FileOps.hpp>
namespace IACore
{

View File

@ -0,0 +1,308 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IACore/ProcessOps.hpp>
namespace IACore
{
// ---------------------------------------------------------------------
// Output Buffering Helper
// Splits raw chunks into lines, preserving partial lines across chunks
// ---------------------------------------------------------------------
struct LineBuffer
{
String Accumulator;
Function<VOID(StringView)> &Callback;
VOID Append(IN PCCHAR data, IN SIZE_T size);
VOID Flush();
};
} // namespace IACore
namespace IACore
{
Expected<INT32, String> ProcessOps::SpawnProcessSync(IN CONST String &command, IN CONST String &args,
IN Function<VOID(IN StringView line)> onOutputLineCallback)
{
Atomic<NativeProcessID> id;
#if IA_PLATFORM_WINDOWS
return SpawnProcessWindows(command, args, onOutputLineCallback, id);
#else
return SpawnProcessPosix(command, args, onOutputLineCallback, id);
#endif
}
SharedPtr<ProcessHandle> ProcessOps::SpawnProcessAsync(IN CONST String &command, IN CONST String &args,
IN Function<VOID(IN StringView line)> onOutputLineCallback,
IN Function<VOID(Expected<INT32, String>)> onFinishCallback)
{
SharedPtr<ProcessHandle> handle = std::make_shared<ProcessHandle>();
handle->IsRunning = true;
handle->ThreadHandle = JoiningThread([&, cmd = IA_MOVE(command), args = std::move(args)]() mutable {
#if IA_PLATFORM_WINDOWS
auto result = SpawnProcessWindows(cmd, args, onOutputLineCallback, handle->ID);
#else
auto result = SpawnProcessPosix(cmd, args, onOutputLineCallback, handle->ID);
#endif
handle->IsRunning = false;
if (onFinishCallback)
onFinishCallback(IA_MOVE(result));
});
return handle;
}
VOID ProcessOps::TerminateProcess(IN CONST SharedPtr<ProcessHandle> &handle)
{
if (!handle || !handle->IsActive())
return;
NativeProcessID pid = handle->ID.load();
if (pid == 0)
return;
#if IA_PLATFORM_WINDOWS
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
if (hProcess != NULL)
{
TerminateProcess(hProcess, 9);
CloseHandle(hProcess);
}
#else
kill(pid, SIGKILL);
#endif
}
} // namespace IACore
namespace IACore
{
#if IA_PLATFORM_WINDOWS
Expected<INT32, String> ProcessOps::SpawnProcessWindows(IN CONST String &command, IN CONST String &args,
IN Function<VOID(StringView)> onOutputLineCallback,
OUT Atomic<NativeProcessID> &id)
{
SECURITY_ATTRIBUTES saAttr = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE}; // Allow inheritance
HANDLE hRead = NULL, hWrite = NULL;
if (!CreatePipe(&hRead, &hWrite, &saAttr, 0))
return tl::make_unexpected("Failed to create pipe");
// Ensure the read handle to the pipe for STDOUT is NOT inherited
if (!SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0))
return tl::make_unexpected("Failed to secure pipe handles");
STARTUPINFOA si = {sizeof(STARTUPINFOA)};
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdOutput = hWrite;
si.hStdError = hWrite; // Merge stderr
si.hStdInput = NULL; // No input
PROCESS_INFORMATION pi = {0};
// Windows command line needs to be mutable and concatenated
String commandLine = BuildString("\"", command, "\" ", args);
BOOL success = CreateProcessA(NULL, commandLine.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
// Important: Close write end in parent, otherwise ReadFile never returns EOF!
CloseHandle(hWrite);
if (!success)
{
CloseHandle(hRead);
return tl::make_unexpected(String("CreateProcess failed: ") + std::to_string(GetLastError()));
}
id.store(pi.dwProcessId);
// Read Loop
LineBuffer lineBuf{"", onOutputLineCallback};
DWORD bytesRead;
CHAR buffer[4096];
while (ReadFile(hRead, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead != 0)
{
lineBuf.Append(buffer, bytesRead);
}
lineBuf.Flush();
// NOW we wait for exit code
DWORD exitCode = 0;
WaitForSingleObject(pi.hProcess, INFINITE);
GetExitCodeProcess(pi.hProcess, &exitCode);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hRead);
id.store(0);
return static_cast<INT32>(exitCode);
}
#endif
#if IA_PLATFORM_UNIX
Expected<INT32, String> ProcessOps::SpawnProcessPosix(IN CONST String &command, IN CONST String &args,
IN Function<VOID(StringView)> onOutputLineCallback,
OUT Atomic<NativeProcessID> &id)
{
int pipefd[2];
if (pipe(pipefd) == -1)
return tl::make_unexpected("Failed to create pipe");
pid_t pid = fork();
if (pid == -1)
{
return tl::make_unexpected("Failed to fork process");
}
else if (pid == 0)
{
// --- Child Process ---
close(pipefd[0]);
dup2(pipefd[1], STDOUT_FILENO);
dup2(pipefd[1], STDERR_FILENO);
close(pipefd[1]);
// --- ARGUMENT PARSING START ---
std::vector<std::string> argStorage; // To keep strings alive
std::vector<char *> argv;
std::string cmdStr = command;
argv.push_back(cmdStr.data());
// Manual Quote-Aware Splitter
std::string currentToken;
bool inQuotes = false;
for (char c : args)
{
if (c == '\"')
{
inQuotes = !inQuotes;
// Determine if you want to keep the quotes or strip them.
// Usually for execvp, you strip them so the shell receives the raw content.
continue;
}
if (c == ' ' && !inQuotes)
{
if (!currentToken.empty())
{
argStorage.push_back(currentToken);
currentToken.clear();
}
}
else
{
currentToken += c;
}
}
if (!currentToken.empty())
{
argStorage.push_back(currentToken);
}
// Build char* array from the std::string storage
for (auto &s : argStorage)
{
argv.push_back(s.data());
}
argv.push_back(nullptr);
// --- ARGUMENT PARSING END ---
execvp(argv[0], argv.data());
_exit(127);
}
else
{
// --- Parent Process ---
id.store(pid);
close(pipefd[1]);
LineBuffer lineBuf{"", onOutputLineCallback};
char buffer[4096];
ssize_t count;
while ((count = read(pipefd[0], buffer, sizeof(buffer))) > 0)
{
lineBuf.Append(buffer, count);
}
lineBuf.Flush();
close(pipefd[0]);
int status;
waitpid(pid, &status, 0);
id.store(0);
if (WIFEXITED(status))
return WEXITSTATUS(status);
return -1;
}
}
#endif
} // namespace IACore
namespace IACore
{
VOID LineBuffer::Append(IN PCCHAR data, IN SIZE_T size)
{
SIZE_T start = 0;
for (SIZE_T i = 0; i < size; ++i)
{
if (data[i] == '\n' || data[i] == '\r')
{
// Flush Accumulator + current chunk
if (!Accumulator.empty())
{
Accumulator.append(data + start, i - start);
if (!Accumulator.empty())
Callback(Accumulator);
Accumulator.clear();
}
else
{
// Zero copy optimization for pure lines in one chunk
if (i > start)
Callback(StringView(data + start, i - start));
}
// Skip \r\n sequence if needed, or just start next
if (data[i] == '\r' && i + 1 < size && data[i + 1] == '\n')
i++;
start = i + 1;
}
}
// Save remaining partial line
if (start < size)
{
Accumulator.append(data + start, size - start);
}
}
VOID LineBuffer::Flush()
{
if (!Accumulator.empty())
{
Callback(Accumulator);
Accumulator.clear();
}
}
} // namespace IACore

View File

@ -0,0 +1,40 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IACore/SocketOps.hpp>
namespace IACore
{
BOOL SocketOps::IsPortAvailable(IN UINT16 port, IN INT32 type)
{
SocketHandle sock = socket(AF_INET, type, IPPROTO_UDP);
if (!IS_VALID_SOCKET(sock))
return false;
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
bool isFree = false;
if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) == 0)
isFree = true;
CLOSE_SOCKET(sock);
return isFree;
}
} // namespace IACore

View File

@ -0,0 +1,59 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IACore/StreamReader.hpp>
#include <IACore/FileOps.hpp>
#include <IACore/Logger.hpp>
namespace IACore
{
StreamReader::StreamReader(IN CONST FilePath &path) : m_storageType(EStorageType::OWNING_MMAP)
{
const auto t = FileOps::MapReadonlyFile(path, m_dataSize);
if (!t)
{
Logger::Error("Failed to memory map file {}", path.string());
return;
}
m_data = *t;
}
StreamReader::StreamReader(IN Vector<UINT8> &&data)
: m_owningVector(IA_MOVE(data)), m_storageType(EStorageType::OWNING_VECTOR)
{
m_data = m_owningVector.data();
m_dataSize = m_owningVector.size();
}
StreamReader::StreamReader(IN Span<CONST UINT8> data)
: m_data(data.data()), m_dataSize(data.size()), m_storageType(EStorageType::NON_OWNING)
{
}
StreamReader::~StreamReader()
{
switch (m_storageType)
{
case EStorageType::OWNING_MMAP:
FileOps::UnmapFile(m_data);
break;
case EStorageType::NON_OWNING:
case EStorageType::OWNING_VECTOR:
break;
}
}
} // namespace IACore

View File

@ -0,0 +1,88 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IACore/StreamWriter.hpp>
#include <IACore/FileOps.hpp>
#include <IACore/Logger.hpp>
namespace IACore
{
StreamWriter::StreamWriter() : m_storageType(EStorageType::OWNING_VECTOR)
{
m_owningVector.resize(m_capacity = 256);
m_buffer = m_owningVector.data();
}
StreamWriter::StreamWriter(IN Span<UINT8> data)
: m_buffer(data.data()), m_capacity(data.size()), m_storageType(EStorageType::NON_OWNING)
{
}
StreamWriter::StreamWriter(IN CONST FilePath &path) : m_storageType(EStorageType::OWNING_MMAP)
{
const auto t = FileOps::MapFile(path);
if (!t)
{
Logger::Error("Failed to memory map file {}", path.string());
return;
}
m_buffer = *t;
m_capacity = SIZE_MAX;
}
StreamWriter::~StreamWriter()
{
switch (m_storageType)
{
case EStorageType::OWNING_MMAP:
FileOps::UnmapFile(m_buffer);
break;
case EStorageType::OWNING_VECTOR:
case EStorageType::NON_OWNING:
break;
}
}
#define HANDLE_OUT_OF_CAPACITY(_size) \
if B_UNLIKELY ((m_cursor + _size) > m_capacity) \
{ \
if (m_storageType == EStorageType::OWNING_VECTOR) \
{ \
m_owningVector.resize(m_capacity + (_size << 1)); \
m_capacity = m_owningVector.size(); \
m_buffer = m_owningVector.data(); \
} \
else \
return false; \
}
BOOL StreamWriter::Write(IN UINT8 byte, IN SIZE_T count)
{
HANDLE_OUT_OF_CAPACITY(count);
memset(&m_buffer[m_cursor], byte, count);
m_cursor += count;
return true;
}
BOOL StreamWriter::Write(IN PCVOID buffer, IN SIZE_T size)
{
HANDLE_OUT_OF_CAPACITY(size);
memcpy(&m_buffer[m_cursor], buffer, size);
m_cursor += size;
return true;
}
} // namespace IACore

View File

@ -0,0 +1,24 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include <IACore/PCH.hpp>
namespace IACore
{
}

View File

@ -1,101 +0,0 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include <IACore/PCH.hpp>
namespace IACore {
class BinaryReader {
public:
// ---------------------------------------------------------------------
// Construction (Zero Copy)
// ---------------------------------------------------------------------
// Accepts Vector<UINT8>, std::array, or C-arrays automatically
BinaryReader(std::span<const UINT8> data)
: m_span(data), m_cursor(0) {}
// ---------------------------------------------------------------------
// Core API
// ---------------------------------------------------------------------
// Generic Primitive Reader (Read<UINT32>(), Read<FLOAT32>(), etc.)
template <typename T>
NO_DISCARD("Check for EOF")
Expected<T, String> Read() {
constexpr SIZE_T size = sizeof(T);
if B_UNLIKELY((m_cursor + size > m_span.size())) {
return MakeUnexpected(String("Unexpected EOF reading type"));
}
T value;
// SAFE: memcpy handles alignment issues on ARM/Android automatically.
// Modern compilers optimize this into a single register load instruction.
std::memcpy(&value, &m_span[m_cursor], size);
m_cursor += size;
return value;
}
// Batch Read (Copy to external buffer)
tl::expected<void, String> Read(PVOID buffer, SIZE_T size) {
if B_UNLIKELY((m_cursor + size > m_span.size())) {
return tl::make_unexpected(String("Unexpected EOF reading buffer"));
}
std::memcpy(buffer, &m_span[m_cursor], size);
m_cursor += size;
return {};
}
// String Reader (Null Terminated or Length Prefixed)
tl::expected<String, String> ReadString(SIZE_T length) {
if B_UNLIKELY((m_cursor + length > m_span.size())) {
return tl::make_unexpected(String("Unexpected EOF reading string"));
}
// Create string from current pointer
String str(REINTERPRET(&m_span[m_cursor], const char*), length);
m_cursor += length;
return str;
}
// ---------------------------------------------------------------------
// Navigation
// ---------------------------------------------------------------------
VOID Skip(SIZE_T amount) {
// Clamp to end
m_cursor = std::min(m_cursor + amount, m_span.size());
}
VOID Seek(SIZE_T pos) {
if (pos > m_span.size()) m_cursor = m_span.size();
else m_cursor = pos;
}
SIZE_T Cursor() CONST { return m_cursor; }
SIZE_T Remaining() CONST { return m_span.size() - m_cursor; }
BOOL IsEOF() CONST { return m_cursor >= m_span.size(); }
private:
std::span<const UINT8> m_span;
SIZE_T m_cursor;
};
}

View File

@ -1,100 +0,0 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include <IACore/PCH.hpp>
namespace IACore {
class BinaryWriter {
public:
// Mode 1: Append to a Vector (Growing)
BinaryWriter(Vector<UINT8>& target) : m_buffer(&target), m_span({}), m_cursor(target.size()), m_isVector(true) {}
// Mode 2: Write into existing fixed memory (No allocs)
BinaryWriter(std::span<UINT8> target) : m_buffer(nullptr), m_span(target), m_cursor(0), m_isVector(false) {}
// ---------------------------------------------------------------------
// Core API
// ---------------------------------------------------------------------
// Append T (Handling Endianness automatically if needed)
template <typename T>
VOID Write(T value) {
// If we are Little Endian (x86/ARM), this compiles to a raw MOV/memcpy.
// If we need specific Endianness (Network Byte Order), use std::byteswap (C++23)
// or a simple swap helper.
// For a game engine, we usually stick to Native Endian (LE) for speed.
CONST SIZE_T size = sizeof(T);
if (m_isVector) {
// Vector guarantees contiguous memory, but push_back is slow for primitives.
// We resize and memcpy.
SIZE_T currentPos = m_buffer->size();
m_buffer->resize(currentPos + size);
std::memcpy(m_buffer->data() + currentPos, &value, size);
} else {
// Fixed Buffer Safety Check
if B_UNLIKELY((m_cursor + size > m_span.size())) {
IA_PANIC("BinaryWriter Overflow");
}
std::memcpy(m_span.data() + m_cursor, &value, size);
m_cursor += size;
}
}
// Random Access Write (Replaces put32, put16, etc.)
template <typename T>
VOID WriteAt(SIZE_T position, T value) {
PUINT8 ptr = GetPtrAt(position);
if (ptr) {
std::memcpy(ptr, &value, sizeof(T));
} else {
IA_PANIC("BinaryWriter Out of Bounds Write");
}
}
VOID WriteBytes(CONST PVOID data, SIZE_T size) {
if (m_isVector) {
SIZE_T currentPos = m_buffer->size();
m_buffer->resize(currentPos + size);
std::memcpy(m_buffer->data() + currentPos, data, size);
} else {
if B_UNLIKELY((m_cursor + size > m_span.size())) IA_PANIC("Overflow");
std::memcpy(m_span.data() + m_cursor, data, size);
m_cursor += size;
}
}
PUINT8 GetPtrAt(SIZE_T pos) {
if (m_isVector) {
if (pos >= m_buffer->size()) return nullptr;
return m_buffer->data() + pos;
} else {
if (pos >= m_span.size()) return nullptr;
return m_span.data() + pos;
}
}
private:
Vector<UINT8>* m_buffer;
std::span<UINT8> m_span;
SIZE_T m_cursor;
BOOL m_isVector;
};
}

View File

@ -1,357 +0,0 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include <IACore/PCH.hpp>
#include <fstream>
#include <filesystem>
namespace IACore
{
namespace fs = std::filesystem;
class File
{
public:
// Modern mapping of flags to standard IO streams
enum class EOpenFlags : UINT32
{
Read = 1 << 0, // std::ios::in
Write = 1 << 1, // std::ios::out
Binary = 1 << 2, // std::ios::binary
Append = 1 << 3, // std::ios::app
Trunc = 1 << 4 // std::ios::trunc
};
// ---------------------------------------------------------------------
// Static Helper API (The "One-Liners")
// ---------------------------------------------------------------------
// Reads entire file into a binary vector
NO_DISCARD("Handle the error")
STATIC tl::expected<Vector<UINT8>, String> ReadToVector(CONST String &path)
{
// 1. Check File Existence & Size
std::error_code ec;
auto fileSize = fs::file_size(path, ec);
if (ec)
{
return tl::make_unexpected(String("File not found or inaccessible: ") + path);
}
// 2. Open Stream
std::ifstream file(path, std::ios::binary);
if (!file.is_open())
{
return tl::make_unexpected(String("Failed to open file handle: ") + path);
}
// 3. Read
Vector<UINT8> buffer;
buffer.resize(CAST(fileSize, SIZE_T));
file.read(REINTERPRET(buffer.data(), char *), fileSize);
if (file.fail())
return tl::make_unexpected(String("Read error: ") + path);
return buffer;
}
// Reads entire file into a String
NO_DISCARD("Handle the error")
STATIC tl::expected<String, String> ReadToString(CONST String &path)
{
// Reuse the binary logic to avoid code duplication, then cast
auto result = ReadToVector(path);
if (!result)
{
return tl::make_unexpected(result.error());
}
// Efficient move into string (reinterpret_cast approach or move)
// Since Vector<UINT8> and String memory layout isn't guaranteed identical, we copy.
// (Though in many STL implementations you could theoretically steal the buffer, copying is safer)
String str(result->begin(), result->end());
return str;
}
// ---------------------------------------------------------------------
// Path Utilities (Replaces manual parsing)
// ---------------------------------------------------------------------
// Old: ExtractFilenameFromPath<true/false>
// New: true -> filename(), false -> stem()
template<BOOL includeExtension = true> STATIC String ExtractFilename(CONST String &pathStr)
{
fs::path p(pathStr);
if CONSTEXPR (includeExtension)
{
return p.filename().string(); // "sprite.png"
}
else
{
return p.stem().string(); // "sprite"
}
}
// Old: ExtractDirectoryFromPath
STATIC String ExtractDirectory(CONST String &pathStr)
{
fs::path p(pathStr);
return p.parent_path().string(); // "assets/textures"
}
STATIC BOOL Exists(CONST String &pathStr)
{
std::error_code ec;
return fs::exists(pathStr, ec);
}
public:
// ---------------------------------------------------------------------
// Instance API (For streaming/partial reads)
// ---------------------------------------------------------------------
File() = default;
#ifdef __cplusplus
File(CONST String &path, EOpenFlags flags)
#else
File(CONST String &path, UINT32 flags)
#endif
{
UNUSED(Open(path, flags));
}
~File()
{
Close();
}
#ifdef __cplusplus
tl::expected<void, String> Open(CONST String &path, EOpenFlags flags)
#else
tl::expected<void, String> Open(CONST String &path, UINT32 flags)
#endif
{
Close(); // Ensure previous handle is closed
std::ios_base::openmode mode = static_cast<std::ios_base::openmode>(0);
if ((UINT32) flags & (UINT32) EOpenFlags::Read)
mode |= std::ios::in;
if ((UINT32) flags & (UINT32) EOpenFlags::Write)
mode |= std::ios::out;
if ((UINT32) flags & (UINT32) EOpenFlags::Binary)
mode |= std::ios::binary;
if ((UINT32) flags & (UINT32) EOpenFlags::Append)
mode |= std::ios::app;
if ((UINT32) flags & (UINT32) EOpenFlags::Trunc)
mode |= std::ios::trunc;
m_fs.open(path, mode);
if (!m_fs.is_open())
{
return tl::make_unexpected(String("Failed to open file: ") + path);
}
return {};
}
VOID Close()
{
if (m_fs.is_open())
{
m_fs.close();
}
}
BOOL IsOpen() CONST
{
return m_fs.is_open();
}
// Returns number of bytes read
SIZE_T Read(PVOID buffer, SIZE_T size)
{
if B_UNLIKELY (!m_fs.is_open())
return 0;
m_fs.read(REINTERPRET(buffer, char *), size);
return static_cast<SIZE_T>(m_fs.gcount());
}
VOID Write(CONST PVOID buffer, SIZE_T size)
{
if B_UNLIKELY (!m_fs.is_open())
return;
m_fs.write(REINTERPRET(buffer, const char *), size);
}
std::fstream *GetStreamHandle()
{
return &m_fs;
}
private:
std::fstream m_fs;
};
class MMFile
{
public:
MMFile() = default;
// RAII - Automatically unmap on destruction
~MMFile()
{
Close();
}
// Disable copy (managing ownership of raw handles is messy)
MMFile(const MMFile &) = delete;
MMFile &operator=(const MMFile &) = delete;
// Open and Map
bool Map(const std::string &filepath)
{
Close(); // Cleanup any existing map
#if IA_PLATFORM_WINDOWS > 0
// 1. Open File
hFile_ = ::CreateFileA(filepath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFile_ == INVALID_HANDLE_VALUE)
return false;
// 2. Get File Size
LARGE_INTEGER size;
if (!::GetFileSizeEx(hFile_, &size))
{
Close();
return false;
}
size_ = static_cast<size_t>(size.QuadPart);
// 3. Create Mapping Object
hMapping_ = ::CreateFileMappingA(hFile_, nullptr, PAGE_READWRITE, 0, 0, nullptr);
if (!hMapping_)
{
Close();
return false;
}
// 4. Map View
data_ = ::MapViewOfFile(hMapping_, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if (!data_)
{
Close();
return false;
}
#else // LINUX / POSIX
// 1. Open File
fd_ = ::open(filepath.c_str(), O_RDWR);
if (fd_ == -1)
return false;
// 2. Get File Size
struct stat sb;
if (fstat(fd_, &sb) == -1)
{
Close();
return false;
}
size_ = static_cast<size_t>(sb.st_size);
// 3. mmap
// PROT_READ: Read only
// MAP_PRIVATE: Copy-on-write (safe if you accidentally modify, though we return const)
data_ = ::mmap(nullptr, size_, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd_, 0);
if (data_ == MAP_FAILED)
{
data_ = nullptr;
Close();
return false;
}
#endif
return true;
}
void Close()
{
#ifdef _WIN32
if (data_)
{
::UnmapViewOfFile(data_);
data_ = nullptr;
}
if (hMapping_)
{
::CloseHandle(hMapping_);
hMapping_ = nullptr;
}
if (hFile_ != INVALID_HANDLE_VALUE)
{
::CloseHandle(hFile_);
hFile_ = INVALID_HANDLE_VALUE;
}
#else
if (data_)
{
::munmap(data_, size_);
data_ = nullptr;
}
if (fd_ != -1)
{
::close(fd_);
fd_ = -1;
}
#endif
size_ = 0;
}
// Accessors
const void *GetData() const
{
return data_;
}
size_t GetSize() const
{
return size_;
}
bool IsValid() const
{
return data_ != nullptr;
}
private:
void *data_ = nullptr;
size_t size_ = 0;
#ifdef _WIN32
HANDLE hFile_ = INVALID_HANDLE_VALUE;
HANDLE hMapping_ = nullptr;
#else
int fd_ = -1;
#endif
};
} // namespace IACore

View File

@ -0,0 +1,43 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include <IACore/StreamReader.hpp>
#include <IACore/StreamWriter.hpp>
namespace IACore
{
class FileOps
{
public:
STATIC VOID UnmapFile(IN PCUINT8 mappedPtr);
STATIC Expected<PUINT8, String> MapFile(IN CONST FilePath &path);
STATIC Expected<PCUINT8, String> MapReadonlyFile(IN CONST FilePath &path);
STATIC Expected<PCUINT8, String> MapReadonlyFile(IN CONST FilePath &path, OUT SIZE_T &size);
STATIC Expected<StreamWriter, String> StreamToFile(IN CONST FilePath &path);
STATIC Expected<StreamReader, String> StreamFromFile(IN CONST FilePath &path);
STATIC Expected<String, String> ReadTextFile(IN CONST FilePath &path);
STATIC Expected<Vector<UINT8>, String> ReadBinaryFile(IN CONST FilePath &path);
STATIC Expected<SIZE_T, String> WriteTextFile(IN CONST FilePath &path, IN CONST String &contents);
STATIC Expected<SIZE_T, String> WriteBinaryFile(IN CONST FilePath &path, IN Span<UINT8> contents);
private:
STATIC UnorderedMap<PCUINT8, PVOID> s_mappedFiles;
};
} // namespace IACore

View File

@ -27,13 +27,16 @@
# include <new>
# include <span>
# include <atomic>
# include <mutex>
# include <thread>
# include <limits>
# include <cstring>
# include <cstddef>
# include <chrono>
# include <fstream>
# include <iostream>
# include <concepts>
# include <filesystem>
# include <functional>
# include <type_traits>
# include <initializer_list>
@ -501,6 +504,7 @@ STATIC CONST FLOAT64 FLOAT64_EPSILON = DBL_EPSILON;
# include <sys/stat.h>
# include <fcntl.h>
# include <spawn.h>
# include <signal.h>
#endif
#if IA_CHECK(IA_PLATFORM_WIN64) || IA_CHECK(IA_PLATFORM_UNIX)
@ -524,6 +528,9 @@ template<typename _key_type> using UnorderedSet = ankerl::unordered_dense::set<_
template<typename _value_type> using Span = std::span<_value_type>;
template<typename _key_type, typename _value_type>
using UnorderedMap = ankerl::unordered_dense::map<_key_type, _value_type>;
template<typename _value_type> using Atomic = std::atomic<_value_type>;
template<typename _value_type> using SharedPtr = std::shared_ptr<_value_type>;
template<typename _value_type> using UniquePtr = std::unique_ptr<_value_type>;
template<typename _expected_type, typename _unexpected_type>
using Expected = tl::expected<_expected_type, _unexpected_type>;
@ -537,7 +544,12 @@ using HRClock = std::chrono::high_resolution_clock;
using HRTimePoint = std::chrono::time_point<HRClock>;
using Mutex = std::mutex;
using LockGuard = std::lock_guard<Mutex>;
using ScopedLock = std::scoped_lock<Mutex>;
using UniqueLock = std::unique_lock<Mutex>;
using JoiningThread = std::jthread;
namespace FileSystem = std::filesystem;
using FilePath = FileSystem::path;
template<typename... Args> using FormatterString = std::format_string<Args...>;

View File

@ -1,292 +0,0 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include <IACore/PCH.hpp>
namespace IACore
{
class Process
{
public:
// ---------------------------------------------------------------------
// Static One-Shot Execution
// ---------------------------------------------------------------------
// Returns Exit Code or Error String
// callback receives distinct lines of output (stdout + stderr merged)
STATIC tl::expected<INT32, String> Run(CONST String &cmd, CONST String &args,
Function<VOID(StringView)> onOutputLine)
{
#if IA_PLATFORM_WINDOWS
return RunWindows(cmd, args, onOutputLine);
#else
return RunPosix(cmd, args, onOutputLine);
#endif
}
// ---------------------------------------------------------------------
// Async Execution
// ---------------------------------------------------------------------
// Returns a jthread.
// - Store it if you want to wait for it later (join).
// - If you destroy the returned jthread immediately, it will BLOCK (join) by design.
// - If you want "fire and forget", call .detach() on the returned thread.
STATIC std::jthread RunAsync(String cmd, String args, Function<VOID(StringView)> onOutputLine,
Function<VOID(tl::expected<INT32, String>)> onComplete)
{
// We capture arguments by VALUE (=) to ensure the thread owns its own copies of the strings.
return std::jthread([=, cmd = std::move(cmd), args = std::move(args)]() mutable {
tl::expected<INT32, String> result = tl::make_unexpected("Not started");
#if IA_PLATFORM_WINDOWS
result = RunWindows(cmd, args, onOutputLine);
#else
result = RunPosix(cmd, args, onOutputLine);
#endif
// Report final result to the callback
if (onComplete)
{
onComplete(std::move(result));
}
});
}
private:
// ---------------------------------------------------------------------
// Output Buffering Helper
// Splits raw chunks into lines, preserving partial lines across chunks
// ---------------------------------------------------------------------
struct LineBuffer
{
String accumulator;
Function<VOID(StringView)> &callback;
VOID Append(const char *data, SIZE_T size)
{
SIZE_T start = 0;
for (SIZE_T i = 0; i < size; ++i)
{
if (data[i] == '\n' || data[i] == '\r')
{
// Flush accumulator + current chunk
if (!accumulator.empty())
{
accumulator.append(data + start, i - start);
if (!accumulator.empty())
callback(accumulator);
accumulator.clear();
}
else
{
// Zero copy optimization for pure lines in one chunk
if (i > start)
callback(StringView(data + start, i - start));
}
// Skip \r\n sequence if needed, or just start next
if (data[i] == '\r' && i + 1 < size && data[i + 1] == '\n')
i++;
start = i + 1;
}
}
// Save remaining partial line
if (start < size)
{
accumulator.append(data + start, size - start);
}
}
VOID Flush()
{
if (!accumulator.empty())
{
callback(accumulator);
accumulator.clear();
}
}
};
// ---------------------------------------------------------------------
// Windows Implementation
// ---------------------------------------------------------------------
#if IA_PLATFORM_WINDOWS
STATIC tl::expected<INT32, String> RunWindows(CONST String &cmd, CONST String &args,
Function<VOID(StringView)> cb)
{
SECURITY_ATTRIBUTES saAttr = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE}; // Allow inheritance
HANDLE hRead = NULL, hWrite = NULL;
if (!CreatePipe(&hRead, &hWrite, &saAttr, 0))
return tl::make_unexpected("Failed to create pipe");
// Ensure the read handle to the pipe for STDOUT is NOT inherited
if (!SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0))
return tl::make_unexpected("Failed to secure pipe handles");
STARTUPINFOA si = {sizeof(STARTUPINFOA)};
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdOutput = hWrite;
si.hStdError = hWrite; // Merge stderr
si.hStdInput = NULL; // No input
PROCESS_INFORMATION pi = {0};
// Windows command line needs to be mutable and concatenated
String commandLine = BuildString("\"", cmd, "\" ", args);
BOOL success = CreateProcessA(NULL, commandLine.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
// Important: Close write end in parent, otherwise ReadFile never returns EOF!
CloseHandle(hWrite);
if (!success)
{
CloseHandle(hRead);
return tl::make_unexpected(String("CreateProcess failed: ") + std::to_string(GetLastError()));
}
// Read Loop
LineBuffer lineBuf{"", cb};
DWORD bytesRead;
CHAR buffer[4096];
while (ReadFile(hRead, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead != 0)
{
lineBuf.Append(buffer, bytesRead);
}
lineBuf.Flush();
// NOW we wait for exit code
DWORD exitCode = 0;
WaitForSingleObject(pi.hProcess, INFINITE);
GetExitCodeProcess(pi.hProcess, &exitCode);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hRead);
return static_cast<INT32>(exitCode);
}
#endif
// ---------------------------------------------------------------------
// POSIX (Linux/Mac) Implementation
// ---------------------------------------------------------------------
#if IA_PLATFORM_UNIX
STATIC tl::expected<INT32, String> RunPosix(CONST String &cmd, CONST String &args,
Function<VOID(StringView)> cb)
{
int pipefd[2];
if (pipe(pipefd) == -1)
return tl::make_unexpected("Failed to create pipe");
pid_t pid = fork();
if (pid == -1)
{
return tl::make_unexpected("Failed to fork process");
}
else if (pid == 0)
{
// --- Child Process ---
close(pipefd[0]);
dup2(pipefd[1], STDOUT_FILENO);
dup2(pipefd[1], STDERR_FILENO);
close(pipefd[1]);
// --- ARGUMENT PARSING START ---
std::vector<std::string> argStorage; // To keep strings alive
std::vector<char *> argv;
std::string cmdStr = cmd;
argv.push_back(cmdStr.data());
// Manual Quote-Aware Splitter
std::string currentToken;
bool inQuotes = false;
for (char c : args)
{
if (c == '\"')
{
inQuotes = !inQuotes;
// Determine if you want to keep the quotes or strip them.
// Usually for execvp, you strip them so the shell receives the raw content.
continue;
}
if (c == ' ' && !inQuotes)
{
if (!currentToken.empty())
{
argStorage.push_back(currentToken);
currentToken.clear();
}
}
else
{
currentToken += c;
}
}
if (!currentToken.empty())
{
argStorage.push_back(currentToken);
}
// Build char* array from the std::string storage
for (auto &s : argStorage)
{
argv.push_back(s.data());
}
argv.push_back(nullptr);
// --- ARGUMENT PARSING END ---
execvp(argv[0], argv.data());
_exit(127);
}
else
{
// --- Parent Process ---
close(pipefd[1]);
LineBuffer lineBuf{"", cb};
char buffer[4096];
ssize_t count;
while ((count = read(pipefd[0], buffer, sizeof(buffer))) > 0)
{
lineBuf.Append(buffer, count);
}
lineBuf.Flush();
close(pipefd[0]);
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status))
return WEXITSTATUS(status);
return -1;
}
}
#endif
};
} // namespace IACore

View File

@ -0,0 +1,64 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include <IACore/PCH.hpp>
#if IA_PLATFORM_WINDOWS
using NativeProcessID = DWORD;
#elif IA_PLATFORM_UNIX
using NativeProcessID = pid_t;
#else
# error "This platform does not support IACore ProcessOps"
#endif
namespace IACore
{
struct ProcessHandle
{
Atomic<NativeProcessID> ID{0};
Atomic<BOOL> IsRunning{false};
BOOL IsActive() CONST
{
return IsRunning && ID != 0;
}
private:
JoiningThread ThreadHandle;
friend class ProcessOps;
};
class ProcessOps
{
public:
STATIC Expected<INT32, String> SpawnProcessSync(IN CONST String &command, IN CONST String &args,
IN Function<VOID(IN StringView line)> onOutputLineCallback);
STATIC SharedPtr<ProcessHandle> SpawnProcessAsync(IN CONST String &command, IN CONST String &args,
IN Function<VOID(IN StringView line)> onOutputLineCallback,
IN Function<VOID(Expected<INT32, String>)> onFinishCallback);
STATIC VOID TerminateProcess(IN CONST SharedPtr<ProcessHandle> &handle);
private:
STATIC Expected<INT32, String> SpawnProcessWindows(IN CONST String &command, IN CONST String &args,
IN Function<VOID(StringView)> onOutputLineCallback, OUT Atomic<NativeProcessID>& id);
STATIC Expected<INT32, String> SpawnProcessPosix(IN CONST String &command, IN CONST String &args,
IN Function<VOID(StringView)> onOutputLineCallback, OUT Atomic<NativeProcessID>& id);
};
} // namespace IACore

View File

@ -73,24 +73,6 @@ namespace IACore
}
private:
STATIC BOOL IsPortAvailable(IN UINT16 port, IN INT32 type)
{
SocketHandle sock = socket(AF_INET, type, IPPROTO_UDP);
if (!IS_VALID_SOCKET(sock))
return false;
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
bool isFree = false;
if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) == 0)
isFree = true;
CLOSE_SOCKET(sock);
return isFree;
}
STATIC BOOL IsPortAvailable(IN UINT16 port, IN INT32 type);
};
} // namespace IACore

View File

@ -0,0 +1,104 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include <IACore/PCH.hpp>
namespace IACore
{
class StreamReader
{
enum class EStorageType
{
NON_OWNING,
OWNING_MMAP,
OWNING_VECTOR,
};
public:
INLINE Expected<VOID, String> Read(IN PVOID buffer, IN SIZE_T size);
template<typename T> NO_DISCARD("Check for EOF") Expected<T, String> Read();
VOID Skip(SIZE_T amount)
{
m_cursor = std::min(m_cursor + amount, m_dataSize);
}
VOID Seek(SIZE_T pos)
{
m_cursor = (pos > m_dataSize) ? m_dataSize : pos;
}
SIZE_T Cursor() CONST
{
return m_cursor;
}
SIZE_T Size() CONST
{
return m_dataSize;
}
SIZE_T Remaining() CONST
{
return m_dataSize - m_cursor;
}
BOOL IsEOF() CONST
{
return m_cursor >= m_dataSize;
}
public:
StreamReader(IN CONST FilePath &path);
explicit StreamReader(IN Vector<UINT8> &&data);
explicit StreamReader(IN Span<CONST UINT8> data);
~StreamReader();
private:
PCUINT8 m_data{};
SIZE_T m_cursor{};
SIZE_T m_dataSize{};
Vector<UINT8> m_owningVector;
CONST EStorageType m_storageType;
};
Expected<VOID, String> StreamReader::Read(IN PVOID buffer, IN SIZE_T size)
{
if B_UNLIKELY ((m_cursor + size > m_dataSize))
return MakeUnexpected(String("Unexpected EOF while reading"));
std::memcpy(buffer, &m_data[m_cursor], size);
m_cursor += size;
return {};
}
template<typename T> NO_DISCARD("Check for EOF") Expected<T, String> StreamReader::Read()
{
constexpr SIZE_T size = sizeof(T);
if B_UNLIKELY ((m_cursor + size > m_dataSize))
return MakeUnexpected(String("Unexpected EOF while reading"));
T value;
std::memcpy(&value, &m_data[m_cursor], size);
m_cursor += size;
return value;
}
} // namespace IACore

View File

@ -0,0 +1,65 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include <IACore/PCH.hpp>
namespace IACore
{
class StreamWriter
{
enum class EStorageType
{
NON_OWNING,
OWNING_MMAP,
OWNING_VECTOR,
};
public:
BOOL Write(IN UINT8 byte, IN SIZE_T count);
BOOL Write(IN PCVOID buffer, IN SIZE_T size);
template<typename T> BOOL Write(IN CONST T &value);
PCUINT8 Data() CONST
{
return m_buffer;
}
SIZE_T Cursor() CONST
{
return m_cursor;
}
public:
StreamWriter();
explicit StreamWriter(IN Span<UINT8> data);
explicit StreamWriter(IN CONST FilePath &path);
~StreamWriter();
private:
PUINT8 m_buffer{};
SIZE_T m_cursor{};
SIZE_T m_capacity{};
Vector<UINT8> m_owningVector;
CONST EStorageType m_storageType;
};
template<typename T> BOOL StreamWriter::Write(IN CONST T &value)
{
return Write(&value, sizeof(T));
}
} // namespace IACore

View File

@ -1,3 +1,4 @@
add_subdirectory(Subjects/)
add_subdirectory(Unit/)
add_subdirectory(Regression/)

View File

@ -0,0 +1 @@
add_executable(LongProcess LongProcess/Main.cpp)

View File

@ -0,0 +1,12 @@
#include <thread>
#include <iostream>
int main(int, char **)
{
std::cout << "Started!\n";
std::cout.flush();
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "Ended!\n";
std::cout.flush();
return 100;
}

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IACore/BinaryReader.hpp>
#include <IACore/BinaryStreamReader.hpp>
#include <IACore/IATest.hpp>
@ -24,7 +24,7 @@ using namespace IACore;
// Test Block Definition
// -----------------------------------------------------------------------------
IAT_BEGIN_BLOCK(Core, BinaryReader)
IAT_BEGIN_BLOCK(Core, BinaryStreamReader)
// -------------------------------------------------------------------------
// 1. Basic Primitive Reading (UINT8)
@ -32,7 +32,7 @@ IAT_BEGIN_BLOCK(Core, BinaryReader)
BOOL TestReadUint8()
{
UINT8 data[] = { 0xAA, 0xBB, 0xCC };
BinaryReader reader(data);
BinaryStreamReader reader(data);
// Read First Byte
auto val1 = reader.Read<UINT8>();
@ -54,7 +54,7 @@ IAT_BEGIN_BLOCK(Core, BinaryReader)
{
// 0x04030201 in Little Endian memory layout
UINT8 data[] = { 0x01, 0x02, 0x03, 0x04 };
BinaryReader reader(data);
BinaryStreamReader reader(data);
auto val = reader.Read<UINT32>();
IAT_CHECK(val.has_value());
@ -79,7 +79,7 @@ IAT_BEGIN_BLOCK(Core, BinaryReader)
UINT8 data[4];
std::memcpy(data, &pi, 4);
BinaryReader reader(data);
BinaryStreamReader reader(data);
auto val = reader.Read<FLOAT32>();
IAT_CHECK(val.has_value());
@ -94,7 +94,7 @@ IAT_BEGIN_BLOCK(Core, BinaryReader)
BOOL TestReadString()
{
const char* raw = "HelloIA";
BinaryReader reader(std::span<const UINT8>((const UINT8*)raw, 7));
BinaryStreamReader reader(std::span<const UINT8>((const UINT8*)raw, 7));
// Read "Hello" (5 bytes)
auto str = reader.ReadString(5);
@ -116,7 +116,7 @@ IAT_BEGIN_BLOCK(Core, BinaryReader)
{
UINT8 src[] = { 1, 2, 3, 4, 5 };
UINT8 dst[3] = { 0 };
BinaryReader reader(src);
BinaryStreamReader reader(src);
// Read 3 bytes into dst
auto res = reader.Read(dst, 3);
@ -139,7 +139,7 @@ IAT_BEGIN_BLOCK(Core, BinaryReader)
BOOL TestNavigation()
{
UINT8 data[10] = { 0 }; // Zero init
BinaryReader reader(data);
BinaryStreamReader reader(data);
IAT_CHECK_EQ(reader.Remaining(), (SIZE_T)10);
@ -168,7 +168,7 @@ IAT_BEGIN_BLOCK(Core, BinaryReader)
BOOL TestBoundaryChecks()
{
UINT8 data[] = { 0x00, 0x00 };
BinaryReader reader(data);
BinaryStreamReader reader(data);
// Valid read
UNUSED(reader.Read<UINT16>());
@ -213,7 +213,7 @@ int main(int argc, char* argv[])
// Define runner (StopOnFail=false, Verbose=true)
ia::iatest::runner<false, true> testRunner;
// Run the BinaryReader block
// Run the BinaryStreamReader block
testRunner.testBlock<Core_BinaryReader>();
return 0;

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IACore/BinaryWriter.hpp>
#include <IACore/BinaryStreamWriter.hpp>
#include <IACore/IATest.hpp>
@ -24,7 +24,7 @@ using namespace IACore;
// Test Block Definition
// -----------------------------------------------------------------------------
IAT_BEGIN_BLOCK(Core, BinaryWriter)
IAT_BEGIN_BLOCK(Core, BinaryStreamWriter)
// -------------------------------------------------------------------------
// 1. Vector Mode (Dynamic Growth)
@ -33,7 +33,7 @@ IAT_BEGIN_BLOCK(Core, BinaryWriter)
{
std::vector<UINT8> buffer;
// Start empty
BinaryWriter writer(buffer);
BinaryStreamWriter writer(buffer);
// Write 1 Byte
writer.Write<UINT8>(0xAA);
@ -62,7 +62,7 @@ IAT_BEGIN_BLOCK(Core, BinaryWriter)
{
// Vector starts with existing data
std::vector<UINT8> buffer = { 0x01, 0x02 };
BinaryWriter writer(buffer);
BinaryStreamWriter writer(buffer);
// Should append to end, not overwrite 0x01
writer.Write<UINT8>(0x03);
@ -83,7 +83,7 @@ IAT_BEGIN_BLOCK(Core, BinaryWriter)
// Initialize with zeros
std::memset(rawData, 0, 10);
BinaryWriter writer(rawData); // Implicit conversion to span
BinaryStreamWriter writer(rawData); // Implicit conversion to span
// Write UINT8
writer.Write<UINT8>(0xFF);
@ -106,7 +106,7 @@ IAT_BEGIN_BLOCK(Core, BinaryWriter)
BOOL TestWriteBytes()
{
std::vector<UINT8> buffer;
BinaryWriter writer(buffer);
BinaryStreamWriter writer(buffer);
const char* msg = "IA";
writer.WriteBytes((PVOID)msg, 2);
@ -126,7 +126,7 @@ IAT_BEGIN_BLOCK(Core, BinaryWriter)
BOOL TestRandomAccess()
{
std::vector<UINT8> buffer;
BinaryWriter writer(buffer);
BinaryStreamWriter writer(buffer);
// Fill with placeholders
writer.Write<UINT32>(0xFFFFFFFF);
@ -152,7 +152,7 @@ IAT_BEGIN_BLOCK(Core, BinaryWriter)
BOOL TestWriteFloat()
{
std::vector<UINT8> buffer;
BinaryWriter writer(buffer);
BinaryStreamWriter writer(buffer);
FLOAT32 val = 1.234f;
writer.Write<FLOAT32>(val);
@ -174,7 +174,7 @@ IAT_BEGIN_BLOCK(Core, BinaryWriter)
BOOL TestGetPtr()
{
std::vector<UINT8> buffer = { 0x10, 0x20, 0x30 };
BinaryWriter writer(buffer);
BinaryStreamWriter writer(buffer);
PUINT8 p1 = writer.GetPtrAt(1);
IAT_CHECK(p1 != nullptr);
@ -216,7 +216,7 @@ int main(int argc, char* argv[])
ia::iatest::runner<false, true> testRunner;
// Run the BinaryReader block
testRunner.testBlock<Core_BinaryWriter>();
testRunner.testBlock<Core_BinaryStreamWriter>();
return 0;
}

View File

@ -13,21 +13,21 @@ set_target_properties(${TEST_NAME_PREFIX}CCompile PROPERTIES
target_link_libraries(${TEST_NAME_PREFIX}CCompile PRIVATE IACore)
# ------------------------------------------------
# Unit: BinaryReader
# ------------------------------------------------
add_executable(${TEST_NAME_PREFIX}BinaryReader "BinaryReader.cpp")
target_link_libraries(${TEST_NAME_PREFIX}BinaryReader PRIVATE IACore)
target_compile_options(${TEST_NAME_PREFIX}BinaryReader PRIVATE -fexceptions)
set_target_properties(${TEST_NAME_PREFIX}BinaryReader PROPERTIES USE_EXCEPTIONS ON)
# ------------------------------------------------
# Unit: BinaryWriter
# ------------------------------------------------
add_executable(${TEST_NAME_PREFIX}BinaryWriter "BinaryWriter.cpp")
target_link_libraries(${TEST_NAME_PREFIX}BinaryWriter PRIVATE IACore)
target_compile_options(${TEST_NAME_PREFIX}BinaryWriter PRIVATE -fexceptions)
set_target_properties(${TEST_NAME_PREFIX}BinaryWriter PROPERTIES USE_EXCEPTIONS ON)
## ------------------------------------------------
## Unit: BinaryReader
## ------------------------------------------------
#add_executable(${TEST_NAME_PREFIX}BinaryReader "BinaryReader.cpp")
#target_link_libraries(${TEST_NAME_PREFIX}BinaryReader PRIVATE IACore)
#target_compile_options(${TEST_NAME_PREFIX}BinaryReader PRIVATE -fexceptions)
#set_target_properties(${TEST_NAME_PREFIX}BinaryReader PROPERTIES USE_EXCEPTIONS ON)
#
## ------------------------------------------------
## Unit: BinaryWriter
## ------------------------------------------------
#add_executable(${TEST_NAME_PREFIX}BinaryWriter "BinaryWriter.cpp")
#target_link_libraries(${TEST_NAME_PREFIX}BinaryWriter PRIVATE IACore)
#target_compile_options(${TEST_NAME_PREFIX}BinaryWriter PRIVATE -fexceptions)
#set_target_properties(${TEST_NAME_PREFIX}BinaryWriter PROPERTIES USE_EXCEPTIONS ON)
# ------------------------------------------------
# Unit: Environment
@ -38,20 +38,20 @@ target_compile_options(${TEST_NAME_PREFIX}Environment PRIVATE -fexceptions)
set_target_properties(${TEST_NAME_PREFIX}Environment PROPERTIES USE_EXCEPTIONS ON)
# ------------------------------------------------
# Unit: File
# Unit: FileOps
# ------------------------------------------------
add_executable(${TEST_NAME_PREFIX}File "File.cpp")
target_link_libraries(${TEST_NAME_PREFIX}File PRIVATE IACore)
target_compile_options(${TEST_NAME_PREFIX}File PRIVATE -fexceptions)
set_target_properties(${TEST_NAME_PREFIX}File PROPERTIES USE_EXCEPTIONS ON)
add_executable(${TEST_NAME_PREFIX}FileOps "FileOps.cpp")
target_link_libraries(${TEST_NAME_PREFIX}FileOps PRIVATE IACore)
target_compile_options(${TEST_NAME_PREFIX}FileOps PRIVATE -fexceptions)
set_target_properties(${TEST_NAME_PREFIX}FileOps PROPERTIES USE_EXCEPTIONS ON)
# ------------------------------------------------
# Unit: Process
# Unit: ProcessOps
# ------------------------------------------------
add_executable(${TEST_NAME_PREFIX}Process "Process.cpp")
target_link_libraries(${TEST_NAME_PREFIX}Process PRIVATE IACore)
target_compile_options(${TEST_NAME_PREFIX}Process PRIVATE -fexceptions)
set_target_properties(${TEST_NAME_PREFIX}Process PROPERTIES USE_EXCEPTIONS ON)
add_executable(${TEST_NAME_PREFIX}ProcessOps "ProcessOps.cpp")
target_link_libraries(${TEST_NAME_PREFIX}ProcessOps PRIVATE IACore)
target_compile_options(${TEST_NAME_PREFIX}ProcessOps PRIVATE -fexceptions)
set_target_properties(${TEST_NAME_PREFIX}ProcessOps PROPERTIES USE_EXCEPTIONS ON)
# ------------------------------------------------
# Unit: Utils

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IACore/File.hpp>
#include <IACore/FileOps.hpp>
#include <IACore/IATest.hpp>
@ -129,7 +129,7 @@ IAT_BEGIN_BLOCK(Core, File)
// 1. Write
{
File f;
auto res = f.Open(path, flagsWrite);
auto res = f.Open(path, (File::EOpenFlags)flagsWrite);
IAT_CHECK(res.has_value());
IAT_CHECK(f.IsOpen());
@ -145,7 +145,7 @@ IAT_BEGIN_BLOCK(Core, File)
UINT32 flagsRead = (UINT32)File::EOpenFlags::Read |
(UINT32)File::EOpenFlags::Binary;
File f(path, flagsRead); // Test RAII constructor
File f(path, (File::EOpenFlags)flagsRead); // Test RAII constructor
IAT_CHECK(f.IsOpen());
UINT32 magicRead = 0;
@ -172,7 +172,7 @@ IAT_BEGIN_BLOCK(Core, File)
// Open in append mode
File f;
const auto openResult = f.Open(path, flagsAppend);
const auto openResult = f.Open(path, (File::EOpenFlags)flagsAppend);
if(!openResult)
{
IA_PANIC(openResult.error().c_str())
@ -209,7 +209,7 @@ IAT_BEGIN_BLOCK(Core, File)
// Test Instance
File f;
auto resInstance = f.Open(ghostPath, (UINT32)File::EOpenFlags::Read);
auto resInstance = f.Open(ghostPath, File::EOpenFlags::Read);
IAT_CHECK_NOT(resInstance.has_value());
IAT_CHECK_NOT(f.IsOpen());

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IACore/Process.hpp>
#include <IACore/ProcessOps.hpp>
#include <IACore/IATest.hpp>
@ -44,7 +44,7 @@ IAT_BEGIN_BLOCK(Core, Process)
// Simple "echo hello"
String captured;
auto result = Process::Run(CMD_ECHO_EXE, CMD_ARG_PREFIX " HelloIA",
auto result = ProcessOps::SpawnProcessSync(CMD_ECHO_EXE, CMD_ARG_PREFIX " HelloIA",
[&](StringView line) {
captured = line;
}
@ -73,7 +73,7 @@ IAT_BEGIN_BLOCK(Core, Process)
String args = String(CMD_ARG_PREFIX) + " one two";
if(args[0] == ' ') args.erase(0, 1); // cleanup space if prefix empty
auto result = Process::Run(CMD_ECHO_EXE, args,
auto result = ProcessOps::SpawnProcessSync(CMD_ECHO_EXE, args,
[&](StringView line) {
lines.push_back(String(line));
}
@ -107,7 +107,7 @@ IAT_BEGIN_BLOCK(Core, Process)
arg = "-c \"exit 42\""; // quotes needed for sh -c
#endif
auto result = Process::Run(cmd, arg, [](StringView){});
auto result = ProcessOps::SpawnProcessSync(cmd, arg, [](StringView){});
IAT_CHECK(result.has_value());
IAT_CHECK_EQ(*result, 42);
@ -121,7 +121,7 @@ IAT_BEGIN_BLOCK(Core, Process)
BOOL TestMissingExe()
{
// Try to run a random string
auto result = Process::Run("sdflkjghsdflkjg", "", [](StringView){});
auto result = ProcessOps::SpawnProcessSync("sdflkjghsdflkjg", "", [](StringView){});
// Windows: CreateProcess usually fails -> returns unexpected
// Linux: execvp fails inside child, returns 127 via waitpid
@ -166,7 +166,7 @@ IAT_BEGIN_BLOCK(Core, Process)
#endif
String captured;
auto result = Process::Run(cmd, arg,
auto result = ProcessOps::SpawnProcessSync(cmd, arg,
[&](StringView line) {
captured += line;
}
@ -203,7 +203,7 @@ IAT_BEGIN_BLOCK(Core, Process)
bool foundA = false;
bool foundB = false;
UNUSED(Process::Run(cmd, arg, [&](StringView line) {
UNUSED(ProcessOps::SpawnProcessSync(cmd, arg, [&](StringView line) {
lineCount++;
if (line.find("LineA") != String::npos) foundA = true;
if (line.find("LineB") != String::npos) foundB = true;