This commit is contained in:
2025-11-23 10:00:38 +05:30
parent d1b1ba12c0
commit fa7045e8c3
3 changed files with 177 additions and 101 deletions

View File

@ -18,76 +18,91 @@
#include <IACore/PCH.hpp>
namespace IACore {
namespace IACore
{
class Process {
public:
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
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
}
private:
private:
// ---------------------------------------------------------------------
// Output Buffering Helper
// Splits raw chunks into lines, preserving partial lines across chunks
// ---------------------------------------------------------------------
struct LineBuffer {
struct LineBuffer
{
String accumulator;
Function<VOID(StringView)>& callback;
Function<VOID(StringView)> &callback;
VOID Append(const char* data, SIZE_T size) {
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') {
for (SIZE_T i = 0; i < size; ++i)
{
if (data[i] == '\n' || data[i] == '\r')
{
// Flush accumulator + current chunk
if (!accumulator.empty()) {
if (!accumulator.empty())
{
accumulator.append(data + start, i - start);
if (!accumulator.empty()) callback(accumulator);
if (!accumulator.empty())
callback(accumulator);
accumulator.clear();
} else {
}
else
{
// Zero copy optimization for pure lines in one chunk
if (i > start) callback(StringView(data + start, i - start));
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++;
if (data[i] == '\r' && i + 1 < size && data[i + 1] == '\n')
i++;
start = i + 1;
}
}
// Save remaining partial line
if (start < size) {
if (start < size)
{
accumulator.append(data + start, size - start);
}
}
VOID Flush() {
if (!accumulator.empty()) {
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
// ---------------------------------------------------------------------
// 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))
@ -97,35 +112,35 @@ namespace IACore {
if (!SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0))
return tl::make_unexpected("Failed to secure pipe handles");
STARTUPINFOA si = { sizeof(STARTUPINFOA) };
STARTUPINFOA si = {sizeof(STARTUPINFOA)};
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdOutput = hWrite;
si.hStdError = hWrite; // Merge stderr
si.hStdInput = NULL; // No input
si.hStdError = hWrite; // Merge stderr
si.hStdInput = NULL; // No input
PROCESS_INFORMATION pi = { 0 };
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
);
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) {
if (!success)
{
CloseHandle(hRead);
return tl::make_unexpected(String("CreateProcess failed: ") + std::to_string(GetLastError()));
}
// Read Loop
LineBuffer lineBuf{ "", cb };
LineBuffer lineBuf{"", cb};
DWORD bytesRead;
CHAR buffer[4096];
while (ReadFile(hRead, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead != 0) {
while (ReadFile(hRead, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead != 0)
{
lineBuf.Append(buffer, bytesRead);
}
lineBuf.Flush();
@ -141,59 +156,95 @@ namespace IACore {
return static_cast<INT32>(exitCode);
}
#endif
#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) {
// ---------------------------------------------------------------------
// 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");
if (pipe(pipefd) == -1)
return tl::make_unexpected("Failed to create pipe");
pid_t pid = fork();
if (pid == -1) {
if (pid == -1)
{
return tl::make_unexpected("Failed to fork process");
}
else if (pid == 0) {
else if (pid == 0)
{
// --- Child Process ---
close(pipefd[0]); // Close read end
close(pipefd[0]);
// Redirect stdout/stderr to pipe
dup2(pipefd[1], STDOUT_FILENO);
dup2(pipefd[1], STDERR_FILENO);
close(pipefd[1]); // Close original write end
close(pipefd[1]);
// Prepare Args. execvp requires an array of char*
// This is quick and dirty splitting.
// In a real engine you might pass args as a vector<string>.
std::vector<char*> argv;
std::string cmdStr = cmd; // copy to modify if needed
// --- 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());
// Split args string by space (simplistic)
// Better: Pass vector<string> to Run()
std::string argsCopy = args;
char* token = strtok(argsCopy.data(), " ");
while (token) {
argv.push_back(token);
token = strtok(NULL, " ");
// 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); // If execvp fails
_exit(127);
}
else {
else
{
// --- Parent Process ---
close(pipefd[1]); // Close write end
close(pipefd[1]);
LineBuffer lineBuf{ "", cb };
LineBuffer lineBuf{"", cb};
char buffer[4096];
ssize_t count;
while ((count = read(pipefd[0], buffer, sizeof(buffer))) > 0) {
while ((count = read(pipefd[0], buffer, sizeof(buffer))) > 0)
{
lineBuf.Append(buffer, count);
}
lineBuf.Flush();
@ -202,10 +253,11 @@ namespace IACore {
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) return WEXITSTATUS(status);
return -1; // Crashed or killed
if (WIFEXITED(status))
return WEXITSTATUS(status);
return -1;
}
}
#endif
#endif
};
}
} // namespace IACore

View File

@ -101,13 +101,24 @@ namespace IACore
// -------------------------------------------------------------------------
template<typename T> INLINE STATIC void HashCombine(UINT64 &seed, CONST T &v)
{
// Use Ankerl's high-speed hasher for the individual type
// This automatically handles ints, floats, strings, etc. efficiently.
auto hasher = ankerl::unordered_dense::hash<T>();
UINT64 h = hasher(v);
UINT64 h;
// 1. Compile-Time check: Can this be treated as a string view?
// This catches "Literal Strings" (char[N]), const char*, and std::string
if constexpr (std::is_constructible_v<std::string_view, T>)
{
std::string_view sv(v);
auto hasher = ankerl::unordered_dense::hash<std::string_view>();
h = hasher(sv);
}
else
{
// 2. Standard types (int, float, custom structs)
auto hasher = ankerl::unordered_dense::hash<T>();
h = hasher(v);
}
// 0x9e3779b97f4a7c15 is the 64-bit golden ratio (phi) approximation
// This spreads bits to avoid collisions in the hash table.
seed ^= h + 0x9e3779b97f4a7c15 + (seed << 6) + (seed >> 2);
}
@ -123,7 +134,7 @@ namespace IACore
}
// -------------------------------------------------------------------------
// F;pat Hasher
// Flat Hasher
// Allows: IACore::ComputeHashFlat(x, y, z);
// -------------------------------------------------------------------------
template<typename T, typename... MemberPtrs> INLINE STATIC UINT64 ComputeHashFlat(CONST T &obj, MemberPtrs... members)

View File

@ -59,7 +59,7 @@ IAT_BEGIN_BLOCK(Core, Utils)
// B. Hex To Binary (Valid Upper)
auto resUpper = IACore::Utils::HexStringToBinary("DEADBEEF00FF");
IAT_CHECK(resUpper.has_value());
IAT_CHECK_EQ(resUpper->size(), 6);
IAT_CHECK_EQ(resUpper->size(), (SIZE_T)6);
IAT_CHECK_EQ((*resUpper)[0], 0xDE);
IAT_CHECK_EQ((*resUpper)[5], 0xFF);
@ -189,11 +189,24 @@ IAT_BEGIN_BLOCK(Core, Utils)
IAT_CHECK_EQ(h1, h2); // Same content = same hash
IAT_CHECK_NEQ(h1, h3); // Different content = different hash
// Verify it works with ComputeHash when passed as object
// (Assuming you extend ComputeHash to handle custom types via the specialized hasher)
// Since ComputeHash uses ankerl::unordered_dense::hash<T> internally, this should work:
// -------------------------------------------------------------
// Verify ComputeHash integration
// -------------------------------------------------------------
// We cannot check EQ(h1, ComputeHash(v1)) because ComputeHash applies
// one extra layer of "Golden Ratio Mixing" on top of the object's hash.
// Instead, we verify that ComputeHash behaves exactly like a manual HashCombine.
UINT64 hManual = 0;
IACore::Utils::HashCombine(hManual, v1);
UINT64 hWrapper = IACore::Utils::ComputeHash(v1);
IAT_CHECK_EQ(h1, hWrapper);
// This proves ComputeHash found the specialization and mixed it correctly
IAT_CHECK_EQ(hManual, hWrapper);
// Optional: Verify the avalanche effect took place (hWrapper should NOT be h1)
IAT_CHECK_NEQ(h1, hWrapper);
return TRUE;
}