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> #include <IACore/PCH.hpp>
namespace IACore { namespace IACore
{
class Process { class Process
public: {
public:
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Static One-Shot Execution // Static One-Shot Execution
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Returns Exit Code or Error String // Returns Exit Code or Error String
// callback receives distinct lines of output (stdout + stderr merged) // callback receives distinct lines of output (stdout + stderr merged)
STATIC tl::expected<INT32, String> Run( STATIC tl::expected<INT32, String> Run(CONST String &cmd, CONST String &args,
CONST String& cmd, Function<VOID(StringView)> onOutputLine)
CONST String& args, {
Function<VOID(StringView)> onOutputLine #if IA_PLATFORM_WINDOWS
) { return RunWindows(cmd, args, onOutputLine);
#if IA_PLATFORM_WINDOWS #else
return RunWindows(cmd, args, onOutputLine); return RunPosix(cmd, args, onOutputLine);
#else #endif
return RunPosix(cmd, args, onOutputLine);
#endif
} }
private: private:
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Output Buffering Helper // Output Buffering Helper
// Splits raw chunks into lines, preserving partial lines across chunks // Splits raw chunks into lines, preserving partial lines across chunks
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
struct LineBuffer { struct LineBuffer
{
String accumulator; 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; SIZE_T start = 0;
for (SIZE_T i = 0; i < size; ++i) { for (SIZE_T i = 0; i < size; ++i)
if (data[i] == '\n' || data[i] == '\r') { {
if (data[i] == '\n' || data[i] == '\r')
{
// Flush accumulator + current chunk // Flush accumulator + current chunk
if (!accumulator.empty()) { if (!accumulator.empty())
{
accumulator.append(data + start, i - start); accumulator.append(data + start, i - start);
if (!accumulator.empty()) callback(accumulator); if (!accumulator.empty())
callback(accumulator);
accumulator.clear(); accumulator.clear();
} else { }
else
{
// Zero copy optimization for pure lines in one chunk // 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 // 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; start = i + 1;
} }
} }
// Save remaining partial line // Save remaining partial line
if (start < size) { if (start < size)
{
accumulator.append(data + start, size - start); accumulator.append(data + start, size - start);
} }
} }
VOID Flush() { VOID Flush()
if (!accumulator.empty()) { {
if (!accumulator.empty())
{
callback(accumulator); callback(accumulator);
accumulator.clear(); accumulator.clear();
} }
} }
}; };
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Windows Implementation // Windows Implementation
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
#if IA_PLATFORM_WINDOWS #if IA_PLATFORM_WINDOWS
STATIC tl::expected<INT32, String> RunWindows(CONST String& cmd, CONST String& args, Function<VOID(StringView)> cb) { STATIC tl::expected<INT32, String> RunWindows(CONST String &cmd, CONST String &args,
SECURITY_ATTRIBUTES saAttr = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE }; // Allow inheritance Function<VOID(StringView)> cb)
{
SECURITY_ATTRIBUTES saAttr = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE}; // Allow inheritance
HANDLE hRead = NULL, hWrite = NULL; HANDLE hRead = NULL, hWrite = NULL;
if (!CreatePipe(&hRead, &hWrite, &saAttr, 0)) if (!CreatePipe(&hRead, &hWrite, &saAttr, 0))
@ -97,35 +112,35 @@ namespace IACore {
if (!SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0)) if (!SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0))
return tl::make_unexpected("Failed to secure pipe handles"); return tl::make_unexpected("Failed to secure pipe handles");
STARTUPINFOA si = { sizeof(STARTUPINFOA) }; STARTUPINFOA si = {sizeof(STARTUPINFOA)};
si.dwFlags |= STARTF_USESTDHANDLES; si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdOutput = hWrite; si.hStdOutput = hWrite;
si.hStdError = hWrite; // Merge stderr si.hStdError = hWrite; // Merge stderr
si.hStdInput = NULL; // No input si.hStdInput = NULL; // No input
PROCESS_INFORMATION pi = { 0 }; PROCESS_INFORMATION pi = {0};
// Windows command line needs to be mutable and concatenated // Windows command line needs to be mutable and concatenated
String commandLine = BuildString("\"", cmd, "\" ", args); String commandLine = BuildString("\"", cmd, "\" ", args);
BOOL success = CreateProcessA( BOOL success = CreateProcessA(NULL, commandLine.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
NULL, commandLine.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi
);
// Important: Close write end in parent, otherwise ReadFile never returns EOF! // Important: Close write end in parent, otherwise ReadFile never returns EOF!
CloseHandle(hWrite); CloseHandle(hWrite);
if (!success) { if (!success)
{
CloseHandle(hRead); CloseHandle(hRead);
return tl::make_unexpected(String("CreateProcess failed: ") + std::to_string(GetLastError())); return tl::make_unexpected(String("CreateProcess failed: ") + std::to_string(GetLastError()));
} }
// Read Loop // Read Loop
LineBuffer lineBuf{ "", cb }; LineBuffer lineBuf{"", cb};
DWORD bytesRead; DWORD bytesRead;
CHAR buffer[4096]; 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.Append(buffer, bytesRead);
} }
lineBuf.Flush(); lineBuf.Flush();
@ -141,59 +156,95 @@ namespace IACore {
return static_cast<INT32>(exitCode); return static_cast<INT32>(exitCode);
} }
#endif #endif
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// POSIX (Linux/Mac) Implementation // POSIX (Linux/Mac) Implementation
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
#if IA_PLATFORM_UNIX #if IA_PLATFORM_UNIX
STATIC tl::expected<INT32, String> RunPosix(CONST String& cmd, CONST String& args, Function<VOID(StringView)> cb) { STATIC tl::expected<INT32, String> RunPosix(CONST String &cmd, CONST String &args,
Function<VOID(StringView)> cb)
{
int pipefd[2]; 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(); pid_t pid = fork();
if (pid == -1) { if (pid == -1)
{
return tl::make_unexpected("Failed to fork process"); return tl::make_unexpected("Failed to fork process");
} }
else if (pid == 0) { else if (pid == 0)
{
// --- Child Process --- // --- Child Process ---
close(pipefd[0]); // Close read end close(pipefd[0]);
// Redirect stdout/stderr to pipe
dup2(pipefd[1], STDOUT_FILENO); dup2(pipefd[1], STDOUT_FILENO);
dup2(pipefd[1], STDERR_FILENO); dup2(pipefd[1], STDERR_FILENO);
close(pipefd[1]); // Close original write end close(pipefd[1]);
// Prepare Args. execvp requires an array of char* // --- ARGUMENT PARSING START ---
// This is quick and dirty splitting. std::vector<std::string> argStorage; // To keep strings alive
// In a real engine you might pass args as a vector<string>. std::vector<char *> argv;
std::vector<char*> argv;
std::string cmdStr = cmd; // copy to modify if needed std::string cmdStr = cmd;
argv.push_back(cmdStr.data()); argv.push_back(cmdStr.data());
// Split args string by space (simplistic) // Manual Quote-Aware Splitter
// Better: Pass vector<string> to Run() std::string currentToken;
std::string argsCopy = args; bool inQuotes = false;
char* token = strtok(argsCopy.data(), " ");
while (token) { for (char c : args)
argv.push_back(token); {
token = strtok(NULL, " "); 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); argv.push_back(nullptr);
// --- ARGUMENT PARSING END ---
execvp(argv[0], argv.data()); execvp(argv[0], argv.data());
_exit(127); // If execvp fails _exit(127);
} }
else { else
{
// --- Parent Process --- // --- Parent Process ---
close(pipefd[1]); // Close write end close(pipefd[1]);
LineBuffer lineBuf{ "", cb }; LineBuffer lineBuf{"", cb};
char buffer[4096]; char buffer[4096];
ssize_t count; 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.Append(buffer, count);
} }
lineBuf.Flush(); lineBuf.Flush();
@ -202,10 +253,11 @@ namespace IACore {
int status; int status;
waitpid(pid, &status, 0); waitpid(pid, &status, 0);
if (WIFEXITED(status)) return WEXITSTATUS(status); if (WIFEXITED(status))
return -1; // Crashed or killed 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) template<typename T> INLINE STATIC void HashCombine(UINT64 &seed, CONST T &v)
{ {
// Use Ankerl's high-speed hasher for the individual type UINT64 h;
// This automatically handles ints, floats, strings, etc. efficiently.
auto hasher = ankerl::unordered_dense::hash<T>(); // 1. Compile-Time check: Can this be treated as a string view?
UINT64 h = hasher(v); // 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 // 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); seed ^= h + 0x9e3779b97f4a7c15 + (seed << 6) + (seed >> 2);
} }
@ -123,7 +134,7 @@ namespace IACore
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// F;pat Hasher // Flat Hasher
// Allows: IACore::ComputeHashFlat(x, y, z); // Allows: IACore::ComputeHashFlat(x, y, z);
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
template<typename T, typename... MemberPtrs> INLINE STATIC UINT64 ComputeHashFlat(CONST T &obj, MemberPtrs... members) 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) // B. Hex To Binary (Valid Upper)
auto resUpper = IACore::Utils::HexStringToBinary("DEADBEEF00FF"); auto resUpper = IACore::Utils::HexStringToBinary("DEADBEEF00FF");
IAT_CHECK(resUpper.has_value()); 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)[0], 0xDE);
IAT_CHECK_EQ((*resUpper)[5], 0xFF); 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_EQ(h1, h2); // Same content = same hash
IAT_CHECK_NEQ(h1, h3); // Different content = different 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) // Verify ComputeHash integration
// Since ComputeHash uses ankerl::unordered_dense::hash<T> internally, this should work: // -------------------------------------------------------------
// 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); 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; return TRUE;
} }