Test 2/2
This commit is contained in:
@ -18,9 +18,11 @@
|
|||||||
|
|
||||||
#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
|
||||||
@ -28,16 +30,14 @@ namespace IACore {
|
|||||||
|
|
||||||
// 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
|
||||||
) {
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
|
||||||
return RunWindows(cmd, args, onOutputLine);
|
return RunWindows(cmd, args, onOutputLine);
|
||||||
#else
|
#else
|
||||||
return RunPosix(cmd, args, onOutputLine);
|
return RunPosix(cmd, args, onOutputLine);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -45,49 +45,64 @@ namespace IACore {
|
|||||||
// 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
|
||||||
@ -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.
|
|
||||||
|
// 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>();
|
auto hasher = ankerl::unordered_dense::hash<T>();
|
||||||
UINT64 h = hasher(v);
|
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)
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user