Release v1.0.0
Some checks failed
CI / Linux (Clang / Release) (push) Has been cancelled
CI / Windows (Clang-CL / Debug) (push) Has been cancelled

This commit is contained in:
2025-12-16 01:52:28 +05:30
commit 96c5eeac29
60 changed files with 7114 additions and 0 deletions

2
Src/CMakeLists.txt Normal file
View File

@ -0,0 +1,2 @@
add_subdirectory(IACore/)

70
Src/IACore/CMakeLists.txt Normal file
View File

@ -0,0 +1,70 @@
set(SRC_FILES
"imp/cpp/IPC.cpp"
"imp/cpp/JSON.cpp"
"imp/cpp/IACore.cpp"
"imp/cpp/Logger.cpp"
"imp/cpp/FileOps.cpp"
"imp/cpp/AsyncOps.cpp"
"imp/cpp/DataOps.cpp"
"imp/cpp/SocketOps.cpp"
"imp/cpp/StringOps.cpp"
"imp/cpp/ProcessOps.cpp"
"imp/cpp/HttpClient.cpp"
"imp/cpp/StreamReader.cpp"
"imp/cpp/StreamWriter.cpp"
)
add_library(IACore STATIC ${SRC_FILES})
target_include_directories(IACore PUBLIC inc/)
target_include_directories(IACore PRIVATE imp/hpp/)
target_link_libraries(IACore PUBLIC
zlibstatic
libzstd_static
tl::expected
glaze::glaze
simdjson::simdjson
nlohmann_json::nlohmann_json
unordered_dense::unordered_dense
)
target_link_libraries(IACore PRIVATE
httplib::httplib
OpenSSL::SSL
OpenSSL::Crypto
)
target_link_libraries(IACore PUBLIC mimalloc-static)
if(WIN32)
if(MSVC)
target_link_options(IACore PUBLIC "/INCLUDE:mi_version")
else()
target_link_options(IACore PUBLIC "")
endif()
endif()
target_precompile_headers(IACore PUBLIC inc/IACore/PCH.hpp)
set(NO_EXCEPT_FLAG "$<IF:$<CXX_COMPILER_ID:MSVC>,/EHs-c-,-fno-exceptions>")
target_compile_options(IACore PRIVATE ${NO_EXCEPT_FLAG})
target_compile_options(IACore INTERFACE
$<$<NOT:$<BOOL:$<TARGET_PROPERTY:USE_EXCEPTIONS>>>:${NO_EXCEPT_FLAG}>
)
define_property(TARGET PROPERTY USE_EXCEPTIONS
BRIEF_DOCS "If ON, this target is allowed to use C++ exceptions."
FULL_DOCS "Prevents IACore from propagating -fno-exceptions to this target."
)
target_compile_definitions(IACore PRIVATE
CPPHTTPLIB_OPENSSL_SUPPORT
CPPHTTPLIB_ZLIB_SUPPORT
NOMINMAX
)
target_compile_definitions(IACore PUBLIC
$<$<CONFIG:Debug>:__IA_DEBUG=1>
$<$<CONFIG:Release>:__IA_DEBUG=0>
)

View File

@ -0,0 +1,179 @@
// 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
{
Mutex AsyncOps::s_queueMutex;
ConditionVariable AsyncOps::s_wakeCondition;
Vector<JoiningThread> AsyncOps::s_scheduleWorkers;
Deque<AsyncOps::ScheduledTask> AsyncOps::s_highPriorityQueue;
Deque<AsyncOps::ScheduledTask> AsyncOps::s_normalPriorityQueue;
VOID AsyncOps::RunTask(IN Function<VOID()> task)
{
JoiningThread(task).detach();
}
VOID AsyncOps::InitializeScheduler(IN UINT8 workerCount)
{
if (!workerCount)
workerCount = std::max((UINT32) 2, std::thread::hardware_concurrency() - 2);
for (UINT32 i = 0; i < workerCount; i++)
s_scheduleWorkers.emplace_back(AsyncOps::ScheduleWorkerLoop, i + 1);
}
VOID AsyncOps::TerminateScheduler()
{
for (auto &w : s_scheduleWorkers)
{
w.request_stop();
}
s_wakeCondition.notify_all();
for (auto &w : s_scheduleWorkers)
{
if (w.joinable())
{
w.join();
}
}
s_scheduleWorkers.clear();
}
VOID AsyncOps::ScheduleTask(IN Function<VOID(IN WorkerID workerID)> task, IN TaskTag tag, IN Schedule *schedule,
IN Priority priority)
{
IA_ASSERT(s_scheduleWorkers.size() && "Scheduler must be initialized before calling this function");
schedule->Counter.fetch_add(1);
{
ScopedLock lock(s_queueMutex);
if (priority == Priority::High)
s_highPriorityQueue.emplace_back(ScheduledTask{tag, schedule, IA_MOVE(task)});
else
s_normalPriorityQueue.emplace_back(ScheduledTask{tag, schedule, IA_MOVE(task)});
}
s_wakeCondition.notify_one();
}
VOID AsyncOps::CancelTasksOfTag(IN TaskTag tag)
{
ScopedLock lock(s_queueMutex);
auto cancelFromQueue = [&](Deque<ScheduledTask> &queue) {
for (auto it = queue.begin(); it != queue.end(); /* no increment here */)
{
if (it->Tag == tag)
{
if (it->ScheduleHandle->Counter.fetch_sub(1) == 1)
it->ScheduleHandle->Counter.notify_all();
it = queue.erase(it);
}
else
++it;
}
};
cancelFromQueue(s_highPriorityQueue);
cancelFromQueue(s_normalPriorityQueue);
}
VOID AsyncOps::WaitForScheduleCompletion(IN Schedule *schedule)
{
IA_ASSERT(s_scheduleWorkers.size() && "Scheduler must be initialized before calling this function");
while (schedule->Counter.load() > 0)
{
ScheduledTask task;
BOOL foundTask{FALSE};
{
UniqueLock lock(s_queueMutex);
if (!s_highPriorityQueue.empty())
{
task = IA_MOVE(s_highPriorityQueue.front());
s_highPriorityQueue.pop_front();
foundTask = TRUE;
}
else if (!s_normalPriorityQueue.empty())
{
task = IA_MOVE(s_normalPriorityQueue.front());
s_normalPriorityQueue.pop_front();
foundTask = TRUE;
}
}
if (foundTask)
{
task.Task(MainThreadWorkerID);
if (task.ScheduleHandle->Counter.fetch_sub(1) == 1)
task.ScheduleHandle->Counter.notify_all();
}
else
{
auto currentVal = schedule->Counter.load();
if (currentVal > 0)
schedule->Counter.wait(currentVal);
}
}
}
AsyncOps::WorkerID AsyncOps::GetWorkerCount()
{
return static_cast<WorkerID>(s_scheduleWorkers.size() + 1); // +1 for MainThread (Work Stealing)
}
VOID AsyncOps::ScheduleWorkerLoop(IN StopToken stopToken, IN WorkerID workerID)
{
while (!stopToken.stop_requested())
{
ScheduledTask task;
BOOL foundTask{FALSE};
{
UniqueLock lock(s_queueMutex);
s_wakeCondition.wait(lock, [&stopToken] {
return !s_highPriorityQueue.empty() || !s_normalPriorityQueue.empty() || stopToken.stop_requested();
});
if (stopToken.stop_requested() && s_highPriorityQueue.empty() && s_normalPriorityQueue.empty())
return;
if (!s_highPriorityQueue.empty())
{
task = IA_MOVE(s_highPriorityQueue.front());
s_highPriorityQueue.pop_front();
foundTask = TRUE;
}
else if (!s_normalPriorityQueue.empty())
{
task = IA_MOVE(s_normalPriorityQueue.front());
s_normalPriorityQueue.pop_front();
foundTask = TRUE;
}
}
if (foundTask)
{
task.Task(workerID);
if (task.ScheduleHandle->Counter.fetch_sub(1) == 1)
task.ScheduleHandle->Counter.notify_all();
}
}
}
} // namespace IACore

View File

@ -0,0 +1,317 @@
// 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/DataOps.hpp>
#include <zlib.h>
#include <zstd.h>
namespace IACore
{
STATIC CONSTEXPR UINT32 CRC32_TABLE[] = {
/* CRC polynomial 0xedb88320 */
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832,
0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a,
0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3,
0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab,
0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4,
0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074,
0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525,
0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615,
0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76,
0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6,
0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7,
0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7,
0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278,
0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330,
0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
};
// FNV-1a 32-bit Constants
constexpr uint32_t FNV1A_32_PRIME = 0x01000193; // 16777619
constexpr uint32_t FNV1A_32_OFFSET = 0x811c9dc5; // 2166136261
UINT32 DataOps::Hash(IN CONST String &string)
{
uint32_t hash = FNV1A_32_OFFSET;
for (char c : string)
{
hash ^= static_cast<uint8_t>(c);
hash *= FNV1A_32_PRIME;
}
return hash;
}
UINT32 DataOps::Hash(IN Span<CONST UINT8> data)
{
uint32_t hash = FNV1A_32_OFFSET;
const uint8_t *ptr = static_cast<const uint8_t *>(data.data());
for (size_t i = 0; i < data.size(); ++i)
{
hash ^= ptr[i];
hash *= FNV1A_32_PRIME;
}
return hash;
}
UINT32 DataOps::CRC32(IN Span<CONST UINT8> _data)
{
UINT32 crc32 = 0xFFFFFFFF;
#define UPDC32(octet, crc) (CRC32_TABLE[((crc) ^ ((UINT8) octet)) & 0xff] ^ ((crc) >> 8))
auto data = _data.data();
auto size = _data.size();
for (; size; --size, ++data)
{
crc32 = UPDC32(*data, crc32);
}
#undef UPDC32
return ~crc32;
}
DataOps::CompressionType DataOps::DetectCompression(IN Span<CONST UINT8> data)
{
if (data.size() < 2)
return CompressionType::None;
// Check for GZIP Magic Number (0x1F 0x8B)
if (data[0] == 0x1F && data[1] == 0x8B)
return CompressionType::Gzip;
// Check for ZLIB Magic Number (starts with 0x78)
// 0x78 = Deflate compression with 32k window size
if (data[0] == 0x78 && (data[1] == 0x01 || data[1] == 0x9C || data[1] == 0xDA))
return CompressionType::Zlib;
return CompressionType::None;
}
Expected<Vector<UINT8>, String> DataOps::ZlibInflate(IN Span<CONST UINT8> data)
{
z_stream zs{};
zs.zalloc = Z_NULL;
zs.zfree = Z_NULL;
zs.opaque = Z_NULL;
// 15 + 32 = Auto-detect Gzip or Zlib
if (inflateInit2(&zs, 15 + 32) != Z_OK)
return MakeUnexpected("Failed to initialize zlib inflate");
zs.next_in = const_cast<Bytef *>(data.data());
zs.avail_in = static_cast<uInt>(data.size());
Vector<UINT8> outBuffer;
// Start with 2x input size.
size_t guessSize = data.size() < 1024 ? data.size() * 4 : data.size() * 2;
outBuffer.resize(guessSize);
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data());
zs.avail_out = static_cast<uInt>(outBuffer.size());
int ret;
do
{
if (zs.avail_out == 0)
{
size_t currentPos = zs.total_out;
size_t newSize = outBuffer.size() * 2;
outBuffer.resize(newSize);
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data() + currentPos);
zs.avail_out = static_cast<uInt>(newSize - currentPos);
}
ret = inflate(&zs, Z_NO_FLUSH);
} while (ret == Z_OK);
inflateEnd(&zs);
if (ret != Z_STREAM_END)
return MakeUnexpected("Failed to inflate: corrupt data or stream error");
outBuffer.resize(zs.total_out);
return outBuffer;
}
Expected<Vector<UINT8>, String> DataOps::ZlibDeflate(IN Span<CONST UINT8> data)
{
z_stream zs{};
zs.zalloc = Z_NULL;
zs.zfree = Z_NULL;
zs.opaque = Z_NULL;
if (deflateInit(&zs, Z_DEFAULT_COMPRESSION) != Z_OK)
return MakeUnexpected("Failed to initialize zlib deflate");
zs.next_in = const_cast<Bytef *>(data.data());
zs.avail_in = static_cast<uInt>(data.size());
Vector<UINT8> outBuffer;
outBuffer.resize(deflateBound(&zs, data.size()));
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data());
zs.avail_out = static_cast<uInt>(outBuffer.size());
int ret = deflate(&zs, Z_FINISH);
if (ret != Z_STREAM_END)
{
deflateEnd(&zs);
return MakeUnexpected("Failed to deflate, ran out of buffer memory");
}
outBuffer.resize(zs.total_out);
deflateEnd(&zs);
return outBuffer;
}
Expected<Vector<UINT8>, String> DataOps::ZstdInflate(IN Span<CONST UINT8> data)
{
unsigned long long const contentSize = ZSTD_getFrameContentSize(data.data(), data.size());
if (contentSize == ZSTD_CONTENTSIZE_ERROR)
return MakeUnexpected("Failed to inflate: Not valid ZSTD compressed data");
if (contentSize != ZSTD_CONTENTSIZE_UNKNOWN)
{
// FAST PATH: We know the size
Vector<UINT8> outBuffer;
outBuffer.resize(static_cast<size_t>(contentSize));
size_t const dSize = ZSTD_decompress(outBuffer.data(), outBuffer.size(), data.data(), data.size());
if (ZSTD_isError(dSize))
return MakeUnexpected(std::format("Failed to inflate: {}", ZSTD_getErrorName(dSize)));
return outBuffer;
}
ZSTD_DCtx *dctx = ZSTD_createDCtx();
Vector<UINT8> outBuffer;
outBuffer.resize(data.size() * 2);
ZSTD_inBuffer input = {data.data(), data.size(), 0};
ZSTD_outBuffer output = {outBuffer.data(), outBuffer.size(), 0};
size_t ret;
do
{
ret = ZSTD_decompressStream(dctx, &output, &input);
if (ZSTD_isError(ret))
{
ZSTD_freeDCtx(dctx);
return MakeUnexpected(std::format("Failed to inflate: {}", ZSTD_getErrorName(ret)));
}
if (output.pos == output.size)
{
size_t newSize = outBuffer.size() * 2;
outBuffer.resize(newSize);
output.dst = outBuffer.data();
output.size = newSize;
}
} while (ret != 0);
outBuffer.resize(output.pos);
ZSTD_freeDCtx(dctx);
return outBuffer;
}
Expected<Vector<UINT8>, String> DataOps::ZstdDeflate(IN Span<CONST UINT8> data)
{
size_t const maxDstSize = ZSTD_compressBound(data.size());
Vector<UINT8> outBuffer;
outBuffer.resize(maxDstSize);
size_t const compressedSize = ZSTD_compress(outBuffer.data(), maxDstSize, data.data(), data.size(), 3);
if (ZSTD_isError(compressedSize))
return MakeUnexpected(std::format("Failed to deflate: {}", ZSTD_getErrorName(compressedSize)));
outBuffer.resize(compressedSize);
return outBuffer;
}
Expected<Vector<UINT8>, String> DataOps::GZipDeflate(IN Span<CONST UINT8> data)
{
z_stream zs{};
zs.zalloc = Z_NULL;
zs.zfree = Z_NULL;
zs.opaque = Z_NULL;
// WindowBits = 15 + 16 (31) = Enforce GZIP encoding
// MemLevel = 8 (default)
// Strategy = Z_DEFAULT_STRATEGY
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK)
return MakeUnexpected("Failed to initialize gzip deflate");
zs.next_in = const_cast<Bytef *>(data.data());
zs.avail_in = static_cast<uInt>(data.size());
Vector<UINT8> outBuffer;
outBuffer.resize(deflateBound(&zs, data.size()) + 1024); // Additional 1KB buffer for safety
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data());
zs.avail_out = static_cast<uInt>(outBuffer.size());
int ret = deflate(&zs, Z_FINISH);
if (ret != Z_STREAM_END)
{
deflateEnd(&zs);
return MakeUnexpected("Failed to deflate");
}
outBuffer.resize(zs.total_out);
deflateEnd(&zs);
return outBuffer;
}
Expected<Vector<UINT8>, String> DataOps::GZipInflate(IN Span<CONST UINT8> data)
{
return ZlibInflate(data);
}
} // namespace IACore

View File

@ -0,0 +1,282 @@
// 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, Tuple<PVOID, PVOID, PVOID>> FileOps::s_mappedFiles;
VOID FileOps::UnmapFile(IN PCUINT8 mappedPtr)
{
if (!s_mappedFiles.contains(mappedPtr))
return;
const auto handles = s_mappedFiles.extract(mappedPtr)->second;
#if IA_PLATFORM_WINDOWS
::UnmapViewOfFile(std::get<1>(handles));
::CloseHandle(std::get<2>(handles));
if (std::get<0>(handles) != INVALID_HANDLE_VALUE)
::CloseHandle(std::get<0>(handles));
#elif IA_PLATFORM_UNIX
::munmap(std::get<1>(handles), (SIZE_T) std::get<2>(handles));
const auto fd = (INT32) ((UINT64) std::get<0>(handles));
if (fd != -1)
::close(fd);
#endif
}
Expected<PUINT8, String> FileOps::MapSharedMemory(IN CONST String &name, IN SIZE_T size, IN BOOL isOwner)
{
#if IA_PLATFORM_WINDOWS
int wchars_num = MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, NULL, 0);
std::wstring wName(wchars_num, 0);
MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, &wName[0], wchars_num);
HANDLE hMap = NULL;
if (isOwner)
hMap = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, (DWORD) (size >> 32),
(DWORD) (size & 0xFFFFFFFF), wName.c_str());
else
hMap = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, wName.c_str());
if (hMap == NULL)
return MakeUnexpected(
std::format("Failed to {} shared memory '{}'", isOwner ? "owner" : "consumer", name.c_str()));
const auto result = static_cast<PUINT8>(MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, size));
if (result == NULL)
{
CloseHandle(hMap);
return MakeUnexpected(std::format("Failed to map view of shared memory '{}'", name.c_str()));
}
s_mappedFiles[result] = std::make_tuple((PVOID) INVALID_HANDLE_VALUE, (PVOID) result, (PVOID) hMap);
return result;
#elif IA_PLATFORM_UNIX
int fd = -1;
if (isOwner)
{
fd = shm_open(name.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0666);
if (fd != -1)
{
if (ftruncate(fd, size) == -1)
{
close(fd);
shm_unlink(name.c_str());
return MakeUnexpected(std::format("Failed to truncate shared memory '{}'", name.c_str()));
}
}
}
else
fd = shm_open(name.c_str(), O_RDWR, 0666);
if (fd == -1)
return MakeUnexpected(
std::format("Failed to {} shared memory '{}'", isOwner ? "owner" : "consumer", name.c_str()));
void *addr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED)
{
close(fd);
return MakeUnexpected(std::format("Failed to mmap shared memory '{}'", name.c_str()));
}
const auto result = static_cast<PUINT8>(addr);
s_mappedFiles[result] = std::make_tuple((PVOID) ((UINT64) fd), (PVOID) addr, (PVOID) size);
return result;
#endif
}
VOID FileOps::UnlinkSharedMemory(IN CONST String &name)
{
if (name.empty())
return;
#if IA_PLATFORM_UNIX
shm_unlink(name.c_str());
#endif
}
Expected<PCUINT8, String> FileOps::MapFile(IN CONST FilePath &path, OUT SIZE_T &size)
{
#if IA_PLATFORM_WINDOWS
const auto handle = CreateFileA(path.string().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.string().c_str()));
LARGE_INTEGER fileSize;
if (!GetFileSizeEx(handle, &fileSize))
{
CloseHandle(handle);
return MakeUnexpected(std::format("Failed to get size of {} for memory mapping", path.string().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.string().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.string().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.string().c_str()));
}
s_mappedFiles[result] = std::make_tuple((PVOID) handle, (PVOID) result, (PVOID) hmap);
return result;
#elif IA_PLATFORM_UNIX
const auto handle = open(path.string().c_str(), O_RDONLY);
if (handle == -1)
return MakeUnexpected(std::format("Failed to open {} for memory mapping", path.string().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.string().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.string().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.string().c_str()));
}
const auto result = static_cast<PCUINT8>(addr);
madvise(addr, size, MADV_SEQUENTIAL);
s_mappedFiles[result] = std::make_tuple((PVOID) ((UINT64) handle), (PVOID) addr, (PVOID) size);
return result;
#endif
}
Expected<StreamWriter, String> FileOps::StreamToFile(IN CONST FilePath &path, IN BOOL overwrite)
{
if (!overwrite && FileSystem::exists(path))
return MakeUnexpected(std::format("File aready exists: {}", path.string().c_str()));
return StreamWriter(path);
}
Expected<StreamReader, String> FileOps::StreamFromFile(IN CONST FilePath &path)
{
if (!FileSystem::exists(path))
return MakeUnexpected(std::format("File does not exist: {}", path.string().c_str()));
return StreamReader(path);
}
Expected<String, String> FileOps::ReadTextFile(IN CONST FilePath &path)
{
const auto f = fopen(path.string().c_str(), "r");
if (!f)
return MakeUnexpected(std::format("Failed to open file: {}", path.string().c_str()));
String result;
fseek(f, 0, SEEK_END);
result.resize(ftell(f));
fseek(f, 0, SEEK_SET);
fread(result.data(), 1, result.size(), f);
fclose(f);
return result;
}
Expected<Vector<UINT8>, String> FileOps::ReadBinaryFile(IN CONST FilePath &path)
{
const auto f = fopen(path.string().c_str(), "rb");
if (!f)
return MakeUnexpected(std::format("Failed to open file: {}", path.string().c_str()));
Vector<UINT8> result;
fseek(f, 0, SEEK_END);
result.resize(ftell(f));
fseek(f, 0, SEEK_SET);
fread(result.data(), 1, result.size(), f);
fclose(f);
return result;
}
Expected<SIZE_T, String> FileOps::WriteTextFile(IN CONST FilePath &path, IN CONST String &contents,
IN BOOL overwrite)
{
const char *mode = overwrite ? "w" : "wx";
const auto f = fopen(path.string().c_str(), mode);
if (!f)
{
if (!overwrite && errno == EEXIST)
return MakeUnexpected(std::format("File already exists: {}", path.string().c_str()));
return MakeUnexpected(std::format("Failed to write to file: {}", path.string().c_str()));
}
const auto result = fwrite(contents.data(), 1, contents.size(), f);
fputc(0, f);
fclose(f);
return result;
}
Expected<SIZE_T, String> FileOps::WriteBinaryFile(IN CONST FilePath &path, IN Span<UINT8> contents,
IN BOOL overwrite)
{
const char *mode = overwrite ? "w" : "wx";
const auto f = fopen(path.string().c_str(), mode);
if (!f)
{
if (!overwrite && errno == EEXIST)
return MakeUnexpected(std::format("File already exists: {}", path.string().c_str()));
return MakeUnexpected(std::format("Failed to write to file: {}", path.string().c_str()));
}
const auto result = fwrite(contents.data(), 1, contents.size(), f);
fclose(f);
return result;
}
FilePath FileOps::NormalizeExecutablePath(IN CONST FilePath &path)
{
FilePath result = path;
#if IA_PLATFORM_WINDOWS
if (!result.has_extension())
result.replace_extension(".exe");
#elif IA_PLATFORM_UNIX
if (result.extension() == ".exe")
result.replace_extension("");
if (result.is_relative())
{
String pathStr = result.string();
if (!pathStr.starts_with("./") && !pathStr.starts_with("../"))
result = "./" + pathStr;
}
#endif
return result;
}
} // namespace IACore

View File

@ -0,0 +1,242 @@
// 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/HttpClient.hpp>
#include <IACore/DataOps.hpp>
#include <httplib.h>
namespace IACore
{
httplib::Headers BuildHeaders(IN Span<CONST HttpClient::Header> headers, IN PCCHAR defaultContentType)
{
httplib::Headers out;
bool hasContentType = false;
for (const auto &h : headers)
{
std::string key = HttpClient::HeaderTypeToString(h.first);
out.emplace(key, h.second);
if (h.first == HttpClient::EHeaderType::CONTENT_TYPE)
hasContentType = true;
}
if (!hasContentType && defaultContentType)
out.emplace("Content-Type", defaultContentType);
return out;
}
HttpClient::HttpClient(IN CONST String &host)
: m_client(new httplib::Client(host)), m_lastResponseCode(EResponseCode::INTERNAL_SERVER_ERROR)
{
}
HttpClient::~HttpClient()
{
}
String HttpClient::PreprocessResponse(IN CONST String &response)
{
const auto responseBytes = Span<CONST UINT8>{(PCUINT8) response.data(), response.size()};
const auto compression = DataOps::DetectCompression(responseBytes);
switch (compression)
{
case DataOps::CompressionType::Gzip: {
const auto data = DataOps::GZipInflate(responseBytes);
if (!data)
return response;
return String((PCCHAR) data->data(), data->size());
}
case DataOps::CompressionType::Zlib: {
const auto data = DataOps::ZlibInflate(responseBytes);
if (!data)
return response;
return String((PCCHAR) data->data(), data->size());
}
case DataOps::CompressionType::None:
default:
break;
}
return response;
}
Expected<String, String> HttpClient::RawGet(IN CONST String &path, IN Span<CONST Header> headers,
IN PCCHAR defaultContentType)
{
auto httpHeaders = BuildHeaders(headers, defaultContentType);
static_cast<httplib::Client *>(m_client)->enable_server_certificate_verification(false);
auto res = static_cast<httplib::Client *>(m_client)->Get(
(!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(), httpHeaders);
if (res)
{
m_lastResponseCode = static_cast<EResponseCode>(res->status);
if (res->status >= 200 && res->status < 300)
return PreprocessResponse(res->body);
else
return MakeUnexpected(std::format("HTTP Error {} : {}", res->status, res->body));
}
return MakeUnexpected(std::format("Network Error: {}", httplib::to_string(res.error())));
}
Expected<String, String> HttpClient::RawPost(IN CONST String &path, IN Span<CONST Header> headers,
IN CONST String &body, IN PCCHAR defaultContentType)
{
auto httpHeaders = BuildHeaders(headers, defaultContentType);
String contentType = defaultContentType;
if (httpHeaders.count("Content-Type"))
{
const auto t = httpHeaders.find("Content-Type");
contentType = t->second;
httpHeaders.erase(t);
}
static_cast<httplib::Client *>(m_client)->set_keep_alive(true);
static_cast<httplib::Client *>(m_client)->enable_server_certificate_verification(false);
auto res = static_cast<httplib::Client *>(m_client)->Post(
(!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(), httpHeaders, body,
contentType.c_str());
if (res)
{
m_lastResponseCode = static_cast<EResponseCode>(res->status);
if (res->status >= 200 && res->status < 300)
return PreprocessResponse(res->body);
else
return MakeUnexpected(std::format("HTTP Error {} : {}", res->status, res->body));
}
return MakeUnexpected(std::format("Network Error: {}", httplib::to_string(res.error())));
}
} // namespace IACore
namespace IACore
{
HttpClient::Header HttpClient::CreateHeader(IN EHeaderType key, IN CONST String &value)
{
return std::make_pair(key, value);
}
String HttpClient::UrlEncode(IN CONST String &value)
{
std::stringstream escaped;
escaped.fill('0');
escaped << std::hex << std::uppercase;
for (char c : value)
{
if (std::isalnum(static_cast<unsigned char>(c)) || c == '-' || c == '_' || c == '.' || c == '~')
escaped << c;
else
escaped << '%' << std::setw(2) << static_cast<int>(static_cast<unsigned char>(c));
}
return escaped.str();
}
String HttpClient::UrlDecode(IN CONST String &value)
{
String result;
result.reserve(value.length());
for (size_t i = 0; i < value.length(); ++i)
{
if (value[i] == '%' && i + 2 < value.length())
{
std::string hexStr = value.substr(i + 1, 2);
char decodedChar = static_cast<char>(std::strtol(hexStr.c_str(), nullptr, 16));
result += decodedChar;
i += 2;
}
else if (value[i] == '+')
result += ' ';
else
result += value[i];
}
return result;
}
String HttpClient::HeaderTypeToString(IN EHeaderType type)
{
switch (type)
{
case EHeaderType::ACCEPT:
return "Accept";
case EHeaderType::ACCEPT_CHARSET:
return "Accept-Charset";
case EHeaderType::ACCEPT_ENCODING:
return "Accept-Encoding";
case EHeaderType::ACCEPT_LANGUAGE:
return "Accept-Language";
case EHeaderType::AUTHORIZATION:
return "Authorization";
case EHeaderType::CACHE_CONTROL:
return "Cache-Control";
case EHeaderType::CONNECTION:
return "Connection";
case EHeaderType::CONTENT_LENGTH:
return "Content-Length";
case EHeaderType::CONTENT_TYPE:
return "Content-Type";
case EHeaderType::COOKIE:
return "Cookie";
case EHeaderType::DATE:
return "Date";
case EHeaderType::EXPECT:
return "Expect";
case EHeaderType::HOST:
return "Host";
case EHeaderType::IF_MATCH:
return "If-Match";
case EHeaderType::IF_MODIFIED_SINCE:
return "If-Modified-Since";
case EHeaderType::IF_NONE_MATCH:
return "If-None-Match";
case EHeaderType::ORIGIN:
return "Origin";
case EHeaderType::PRAGMA:
return "Pragma";
case EHeaderType::PROXY_AUTHORIZATION:
return "Proxy-Authorization";
case EHeaderType::RANGE:
return "Range";
case EHeaderType::REFERER:
return "Referer";
case EHeaderType::TE:
return "TE";
case EHeaderType::UPGRADE:
return "Upgrade";
case EHeaderType::USER_AGENT:
return "User-Agent";
case EHeaderType::VIA:
return "Via";
case EHeaderType::WARNING:
return "Warning";
}
}
BOOL HttpClient::IsSuccessResponseCode(IN EResponseCode code)
{
return (INT32) code >= 200 && (INT32) code < 300;
}
} // namespace IACore

View File

@ -0,0 +1,79 @@
// 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/IACore.hpp>
#include <IACore/Logger.hpp>
namespace IACore
{
HighResTimePoint g_startTime{};
std::thread::id g_mainThreadID{};
VOID Initialize()
{
g_mainThreadID = std::this_thread::get_id();
g_startTime = HighResClock::now();
Logger::Initialize();
}
VOID Terminate()
{
Logger::Terminate();
}
UINT64 GetUnixTime()
{
auto now = std::chrono::system_clock::now();
return std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
}
UINT64 GetTicksCount()
{
return std::chrono::duration_cast<std::chrono::milliseconds>(HighResClock::now() - g_startTime).count();
}
FLOAT64 GetSecondsCount()
{
return std::chrono::duration_cast<std::chrono::seconds>(HighResClock::now() - g_startTime).count();
}
FLOAT32 GetRandom()
{
return static_cast<FLOAT32>(rand()) / static_cast<FLOAT32>(RAND_MAX);
}
UINT32 GetRandom(IN UINT32 seed)
{
srand(seed);
return (UINT32) GetRandom(0, UINT32_MAX);
}
INT64 GetRandom(IN INT64 min, IN INT64 max)
{
const auto t = static_cast<FLOAT32>(rand()) / static_cast<FLOAT32>(RAND_MAX);
return min + (max - min) * t;
}
VOID Sleep(IN UINT64 milliseconds)
{
std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
}
BOOL IsMainThread()
{
return std::this_thread::get_id() == g_mainThreadID;
}
} // namespace IACore

453
Src/IACore/imp/cpp/IPC.cpp Normal file
View File

@ -0,0 +1,453 @@
// 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/IPC.hpp>
#include <IACore/FileOps.hpp>
#include <IACore/StringOps.hpp>
namespace IACore
{
struct IPC_ConnectionDescriptor
{
String SocketPath;
String SharedMemPath;
UINT32 SharedMemSize;
String Serialize() CONST
{
return std::format("{}|{}|{}|", SocketPath, SharedMemPath, SharedMemSize);
}
STATIC IPC_ConnectionDescriptor Deserialize(IN CONST String &data)
{
enum class EParseState
{
SocketPath,
SharedMemPath,
SharedMemSize
};
IPC_ConnectionDescriptor result{};
SIZE_T t{};
EParseState state{EParseState::SocketPath};
for (SIZE_T i = 0; i < data.size(); i++)
{
if (data[i] != '|')
continue;
switch (state)
{
case EParseState::SocketPath:
result.SocketPath = data.substr(t, i - t);
state = EParseState::SharedMemPath;
break;
case EParseState::SharedMemPath:
result.SharedMemPath = data.substr(t, i - t);
state = EParseState::SharedMemSize;
break;
case EParseState::SharedMemSize: {
if (std::from_chars(&data[t], &data[i], result.SharedMemSize).ec != std::errc{})
return {};
goto done_parsing;
}
}
t = i + 1;
}
done_parsing:
return result;
}
};
} // namespace IACore
namespace IACore
{
IPC_Node::~IPC_Node()
{
SocketOps::Close(m_socket); // SocketOps gracefully handles INVALID_SOCKET
}
Expected<VOID, String> IPC_Node::Connect(IN PCCHAR connectionString)
{
auto desc = IPC_ConnectionDescriptor::Deserialize(connectionString);
m_shmName = desc.SharedMemPath;
m_socket = SocketOps::CreateUnixSocket();
if (!SocketOps::ConnectUnixSocket(m_socket, desc.SocketPath.c_str()))
return MakeUnexpected("Failed to create an unix socket");
auto mapRes = FileOps::MapSharedMemory(desc.SharedMemPath, desc.SharedMemSize, FALSE);
if (!mapRes.has_value())
return MakeUnexpected("Failed to map the shared memory");
m_sharedMemory = mapRes.value();
auto *layout = reinterpret_cast<IPC_SharedMemoryLayout *>(m_sharedMemory);
if (layout->Meta.Magic != 0x49414950) // "IAIP"
return MakeUnexpected("Invalid shared memory header signature");
if (layout->Meta.Version != 1)
return MakeUnexpected("IPC version mismatch");
PUINT8 moniDataPtr = m_sharedMemory + layout->MONI_DataOffset;
PUINT8 minoDataPtr = m_sharedMemory + layout->MINO_DataOffset;
MONI = std::make_unique<RingBufferView>(
&layout->MONI_Control, Span<UINT8>(moniDataPtr, static_cast<size_t>(layout->MONI_DataSize)), FALSE);
MINO = std::make_unique<RingBufferView>(
&layout->MINO_Control, Span<UINT8>(minoDataPtr, static_cast<size_t>(layout->MINO_DataSize)), FALSE);
#if IA_PLATFORM_WINDOWS
u_long mode = 1;
ioctlsocket(m_socket, FIONBIO, &mode);
#else
fcntl(m_socket, F_SETFL, O_NONBLOCK);
#endif
m_receiveBuffer.resize(UINT16_MAX + 1);
return {};
}
VOID IPC_Node::Update()
{
if (!MONI)
return;
RingBufferView::PacketHeader header;
// Process all available messages from Manager
while (MONI->Pop(header, Span<UINT8>(m_receiveBuffer.data(), m_receiveBuffer.size())))
OnPacket(header.ID, {m_receiveBuffer.data(), header.PayloadSize});
UINT8 signal;
const auto res = recv(m_socket, (CHAR *) &signal, 1, 0);
if (res == 1)
OnSignal(signal);
else if (res == 0 || (res < 0 && !SocketOps::IsWouldBlock()))
{
SocketOps::Close(m_socket);
FileOps::UnlinkSharedMemory(m_shmName);
// Manager disconnected, exit immediately
exit(-1);
}
}
VOID IPC_Node::SendSignal(IN UINT8 signal)
{
if (IS_VALID_SOCKET(m_socket))
send(m_socket, (const char *) &signal, sizeof(signal), 0);
}
VOID IPC_Node::SendPacket(IN UINT16 packetID, IN Span<CONST UINT8> payload)
{
MINO->Push(packetID, payload);
}
} // namespace IACore
namespace IACore
{
VOID IPC_Manager::NodeSession::SendSignal(IN UINT8 signal)
{
if (IS_VALID_SOCKET(DataSocket))
send(DataSocket, (const char *) &signal, sizeof(signal), 0);
}
VOID IPC_Manager::NodeSession::SendPacket(IN UINT16 packetID, IN Span<CONST UINT8> payload)
{
// Protect the RingBuffer write cursor from concurrent threads
ScopedLock lock(SendMutex);
MONI->Push(packetID, payload);
}
IPC_Manager::IPC_Manager()
{
// SocketOps is smart enough to track multiple inits
SocketOps::Initialize();
m_receiveBuffer.resize(UINT16_MAX + 1);
}
IPC_Manager::~IPC_Manager()
{
for (auto &session : m_activeSessions)
{
ProcessOps::TerminateProcess(session->ProcessHandle);
FileOps::UnmapFile(session->MappedPtr);
FileOps::UnlinkSharedMemory(session->SharedMemName);
SocketOps::Close(session->DataSocket);
}
m_activeSessions.clear();
for (auto &session : m_pendingSessions)
{
ProcessOps::TerminateProcess(session->ProcessHandle);
FileOps::UnmapFile(session->MappedPtr);
FileOps::UnlinkSharedMemory(session->SharedMemName);
SocketOps::Close(session->ListenerSocket);
}
m_pendingSessions.clear();
// SocketOps is smart enough to track multiple terminates
SocketOps::Terminate();
}
VOID IPC_Manager::Update()
{
const auto now = SteadyClock::now();
for (INT32 i = m_pendingSessions.size() - 1; i >= 0; i--)
{
auto &session = m_pendingSessions[i];
if (now - session->CreationTime > std::chrono::seconds(5))
{
ProcessOps::TerminateProcess(session->ProcessHandle);
FileOps::UnmapFile(session->MappedPtr);
FileOps::UnlinkSharedMemory(session->SharedMemName);
SocketOps::Close(session->DataSocket);
m_pendingSessions.erase(m_pendingSessions.begin() + i);
continue;
}
SocketHandle newSock = accept(session->ListenerSocket, NULL, NULL);
if (IS_VALID_SOCKET(newSock))
{
session->DataSocket = newSock;
session->IsReady = TRUE;
// Set Data Socket to Non-Blocking
#if IA_PLATFORM_WINDOWS
u_long mode = 1;
ioctlsocket(session->DataSocket, FIONBIO, &mode);
#else
fcntl(session->DataSocket, F_SETFL, O_NONBLOCK);
#endif
SocketOps::Close(session->ListenerSocket);
session->ListenerSocket = INVALID_SOCKET;
const auto sessionID = session->ProcessHandle->ID.load();
const auto sessionPtr = session.get();
m_activeSessions.push_back(std::move(session));
m_pendingSessions.erase(m_pendingSessions.begin() + i);
m_activeSessionMap[sessionID] = sessionPtr;
}
}
for (INT32 i = m_activeSessions.size() - 1; i >= 0; i--)
{
auto &node = m_activeSessions[i];
auto nodeID = node->ProcessHandle->ID.load();
RingBufferView::PacketHeader header;
while (node->MINO->Pop(header, Span<UINT8>(m_receiveBuffer.data(), m_receiveBuffer.size())))
OnPacket(nodeID, header.ID, {m_receiveBuffer.data(), header.PayloadSize});
UINT8 signal;
const auto res = recv(node->DataSocket, (CHAR *) &signal, 1, 0);
if (res == 1)
OnSignal(nodeID, signal);
else if (res == 0 || (res < 0 && !SocketOps::IsWouldBlock()))
{
ProcessOps::TerminateProcess(node->ProcessHandle);
FileOps::UnmapFile(node->MappedPtr);
FileOps::UnlinkSharedMemory(node->SharedMemName);
SocketOps::Close(node->DataSocket);
m_activeSessions.erase(m_activeSessions.begin() + i);
m_activeSessionMap.erase(nodeID);
}
}
}
Expected<NativeProcessID, String> IPC_Manager::SpawnNode(IN CONST FilePath &executablePath,
IN UINT32 sharedMemorySize)
{
auto session = std::make_unique<NodeSession>();
static Atomic<UINT32> s_idGen{0};
UINT32 sid = ++s_idGen;
#if IA_PLATFORM_WINDOWS
char tempPath[MAX_PATH];
GetTempPathA(MAX_PATH, tempPath);
String sockPath = std::format("{}\\ia_sess_{}.sock", tempPath, sid);
#else
String sockPath = std::format("/tmp/ia_sess_{}.sock", sid);
#endif
session->ListenerSocket = SocketOps::CreateUnixSocket();
if (!SocketOps::BindUnixSocket(session->ListenerSocket, sockPath.c_str()))
return MakeUnexpected("Failed to bind unique socket");
if (listen(session->ListenerSocket, 1) != 0)
return MakeUnexpected("Failed to listen on unique socket");
#if IA_PLATFORM_WINDOWS
u_long mode = 1;
ioctlsocket(session->ListenerSocket, FIONBIO, &mode);
#else
fcntl(session->ListenerSocket, F_SETFL, O_NONBLOCK);
#endif
String shmName = std::format("ia_shm_{}", sid);
auto mapRes = FileOps::MapSharedMemory(shmName, sharedMemorySize, TRUE);
if (!mapRes.has_value())
return MakeUnexpected("Failed to map shared memory");
PUINT8 mappedPtr = mapRes.value();
auto *layout = reinterpret_cast<IPC_SharedMemoryLayout *>(mappedPtr);
layout->Meta.Magic = 0x49414950;
layout->Meta.Version = 1;
layout->Meta.TotalSize = sharedMemorySize;
UINT64 headerSize = IPC_SharedMemoryLayout::GetHeaderSize();
UINT64 usableBytes = sharedMemorySize - headerSize;
UINT64 halfSize = (usableBytes / 2);
halfSize -= (halfSize % 64);
layout->MONI_DataOffset = headerSize;
layout->MONI_DataSize = halfSize;
layout->MINO_DataOffset = headerSize + halfSize;
layout->MINO_DataSize = halfSize;
session->MONI = std::make_unique<RingBufferView>(
&layout->MONI_Control, Span<UINT8>(mappedPtr + layout->MONI_DataOffset, layout->MONI_DataSize), TRUE);
session->MINO = std::make_unique<RingBufferView>(
&layout->MINO_Control, Span<UINT8>(mappedPtr + layout->MINO_DataOffset, layout->MINO_DataSize), TRUE);
IPC_ConnectionDescriptor desc;
desc.SocketPath = sockPath;
desc.SharedMemPath = shmName;
desc.SharedMemSize = sharedMemorySize;
String args = std::format("\"{}\"", desc.Serialize());
session->ProcessHandle = ProcessOps::SpawnProcessAsync(
FileOps::NormalizeExecutablePath(executablePath).string(), args,
[sid](IN StringView line) {
UNUSED(sid);
UNUSED(line);
#if __IA_DEBUG
puts(std::format(__CC_MAGENTA "[Node:{}:STDOUT|STDERR]: {}" __CC_DEFAULT, sid, line).c_str());
#endif
},
[sid](IN Expected<INT32, String> result) {
UNUSED(sid);
UNUSED(result);
#if __IA_DEBUG
if (!result)
puts(std::format(__CC_RED "Failed to spawn Node: {} with error '{}'" __CC_DEFAULT, sid,
result.error())
.c_str());
else
puts(std::format(__CC_RED "[Node: {}]: Exited with code {}" __CC_DEFAULT, sid, *result).c_str());
#endif
});
// Give some time for child node to stablize
std::this_thread::sleep_for(std::chrono::seconds(1));
if (!session->ProcessHandle->IsActive())
return MakeUnexpected(std::format("Failed to spawn the child process \"{}\"", executablePath.string()));
auto processID = session->ProcessHandle->ID.load();
session->CreationTime = SteadyClock::now();
m_pendingSessions.push_back(std::move(session));
return processID;
}
BOOL IPC_Manager::WaitTillNodeIsOnline(IN NativeProcessID nodeID)
{
BOOL isPending = true;
while (isPending)
{
isPending = false;
for (auto it = m_pendingSessions.begin(); it != m_pendingSessions.end(); it++)
{
if (it->get()->ProcessHandle->ID.load() == nodeID)
{
isPending = true;
break;
}
}
Update();
}
return m_activeSessionMap.contains(nodeID);
}
VOID IPC_Manager::ShutdownNode(IN NativeProcessID nodeID)
{
const auto itNode = m_activeSessionMap.find(nodeID);
if (itNode == m_activeSessionMap.end())
return;
auto &node = itNode->second;
ProcessOps::TerminateProcess(node->ProcessHandle);
FileOps::UnmapFile(node->MappedPtr);
FileOps::UnlinkSharedMemory(node->SharedMemName);
SocketOps::Close(node->DataSocket);
for (auto it = m_activeSessions.begin(); it != m_activeSessions.end(); it++)
{
if (it->get() == node)
{
m_activeSessions.erase(it);
break;
}
}
m_activeSessionMap.erase(itNode);
}
VOID IPC_Manager::SendSignal(IN NativeProcessID node, IN UINT8 signal)
{
const auto itNode = m_activeSessionMap.find(node);
if (itNode == m_activeSessionMap.end())
return;
itNode->second->SendSignal(signal);
}
VOID IPC_Manager::SendPacket(IN NativeProcessID node, IN UINT16 packetID, IN Span<CONST UINT8> payload)
{
const auto itNode = m_activeSessionMap.find(node);
if (itNode == m_activeSessionMap.end())
return;
itNode->second->SendPacket(packetID, payload);
}
} // 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/>.
#include <IACore/JSON.hpp>
namespace IACore
{
Expected<nlohmann::json, String> JSON::Parse(IN CONST String &json)
{
const auto parseResult = nlohmann::json::parse(json, nullptr, false, true);
if (parseResult.is_discarded())
return MakeUnexpected("Failed to parse JSON");
return parseResult;
}
Expected<Pair<simdjson::dom::parser, simdjson::dom::object>, String> JSON::ParseReadOnly(IN CONST String &json)
{
simdjson::error_code error{};
simdjson::dom::parser parser;
simdjson::dom::object object;
if ((error = parser.parse(json).get(object)))
return MakeUnexpected(std::format("Failed to parse JSON : {}", simdjson::error_message(error)));
return std::make_pair(IA_MOVE(parser), IA_MOVE(object));
}
String JSON::Encode(IN nlohmann::json data)
{
return data.dump();
}
} // namespace IACore

View File

@ -0,0 +1,73 @@
// 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/Logger.hpp>
#include <IACore/IACore.hpp>
#include <IACore/FileOps.hpp>
namespace IACore
{
Logger::ELogLevel Logger::s_logLevel{Logger::ELogLevel::WARN};
std::ofstream Logger::s_logFile{};
VOID Logger::Initialize()
{
}
VOID Logger::Terminate()
{
if (s_logFile.is_open())
{
s_logFile.flush();
s_logFile.close();
}
}
BOOL Logger::EnableLoggingToDisk(IN PCCHAR filePath)
{
if (s_logFile.is_open())
{
s_logFile.flush();
s_logFile.close();
}
s_logFile.open(filePath);
return s_logFile.is_open();
}
VOID Logger::SetLogLevel(IN ELogLevel logLevel)
{
s_logLevel = logLevel;
}
VOID Logger::FlushLogs()
{
std::cout.flush();
if (s_logFile)
s_logFile.flush();
}
VOID Logger::LogInternal(IN PCCHAR prefix, IN PCCHAR tag, IN String &&msg)
{
const auto outLine = std::format("[{:>8.3f}]: [{}]: {}", GetSecondsCount(), tag, msg);
std::cout << prefix << outLine << "\033[39m\n";
if (s_logFile)
{
s_logFile.write(outLine.data(), outLine.size());
s_logFile.put('\n');
s_logFile.flush();
}
}
} // namespace IACore

View File

@ -0,0 +1,338 @@
// 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
{
NativeProcessID ProcessOps::GetCurrentProcessID()
{
#if IA_PLATFORM_WINDOWS
return ::GetCurrentProcessId();
#else
return getpid();
#endif
}
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([=, h = handle.get(), cmd = IA_MOVE(command), args = std::move(args)]() mutable {
#if IA_PLATFORM_WINDOWS
auto result = SpawnProcessWindows(cmd, args, onOutputLineCallback, h->ID);
#else
auto result = SpawnProcessPosix(cmd, args, onOutputLineCallback, h->ID);
#endif
h->IsRunning = false;
if (!onFinishCallback)
return;
if (!result)
onFinishCallback(MakeUnexpected(result.error()));
else
onFinishCallback(*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 = std::format("\"{}\" {}", command, args);
BOOL success = CreateProcessA(NULL, commandLine.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
// 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]);
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;
bool isEscaped = false;
for (char c : args)
{
if (isEscaped)
{
// Previous char was '\', so we treat this char literally.
currentToken += c;
isEscaped = false;
continue;
}
if (c == '\\')
{
// Escape sequence start
isEscaped = true;
continue;
}
if (c == '\"')
{
// Toggle quote state
inQuotes = !inQuotes;
continue;
}
if (c == ' ' && !inQuotes)
{
// Token boundary
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);
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
{
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,106 @@
// 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
{
INT32 SocketOps::s_initCount{0};
VOID SocketOps::Close(IN SocketHandle sock)
{
if (sock == INVALID_SOCKET)
return;
CLOSE_SOCKET(sock);
}
BOOL SocketOps::Listen(IN SocketHandle sock, IN INT32 queueSize)
{
return listen(sock, queueSize) == 0;
}
SocketHandle SocketOps::CreateUnixSocket()
{
return socket(AF_UNIX, SOCK_STREAM, 0);
}
BOOL SocketOps::BindUnixSocket(IN SocketHandle sock, IN PCCHAR path)
{
if (!IS_VALID_SOCKET(sock))
return FALSE;
UNLINK_FILE(path);
sockaddr_un addr{};
addr.sun_family = AF_UNIX;
size_t maxLen = sizeof(addr.sun_path) - 1;
strncpy(addr.sun_path, path, maxLen);
if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) == -1)
return FALSE;
return TRUE;
}
BOOL SocketOps::ConnectUnixSocket(IN SocketHandle sock, IN PCCHAR path)
{
if (!IS_VALID_SOCKET(sock))
return FALSE;
sockaddr_un addr{};
addr.sun_family = AF_UNIX;
size_t maxLen = sizeof(addr.sun_path) - 1;
strncpy(addr.sun_path, path, maxLen);
if (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) == -1)
return FALSE;
return TRUE;
}
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;
}
BOOL SocketOps::IsWouldBlock()
{
#if IA_PLATFORM_WINDOWS
return WSAGetLastError() == WSAEWOULDBLOCK;
#else
return errno == EWOULDBLOCK || errno == EAGAIN;
#endif
}
} // 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::MapFile(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,97 @@
// 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/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_filePath(path), m_storageType(EStorageType::OWNING_FILE)
{
IA_RELEASE_ASSERT(!path.empty());
const auto f = fopen(m_filePath.string().c_str(), "wb");
if (!f)
{
Logger::Error("Failed to open file for writing {}", m_filePath.string().c_str());
return;
}
fputc(0, f);
fclose(f);
m_owningVector.resize(m_capacity = 256);
m_buffer = m_owningVector.data();
}
StreamWriter::~StreamWriter()
{
switch (m_storageType)
{
case EStorageType::OWNING_FILE: {
IA_RELEASE_ASSERT(!m_filePath.empty());
const auto f = fopen(m_filePath.string().c_str(), "wb");
if (!f)
{
Logger::Error("Failed to open file for writing {}", m_filePath.string().c_str());
return;
}
fwrite(m_owningVector.data(), 1, m_owningVector.size(), f);
fclose(f);
}
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::NON_OWNING) \
return false; \
m_owningVector.resize(m_capacity + (_size << 1)); \
m_capacity = m_owningVector.size(); \
m_buffer = m_owningVector.data(); \
}
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,101 @@
// 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/StringOps.hpp>
namespace IACore
{
CONST String BASE64_CHAR_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
String StringOps::EncodeBase64(IN Span<CONST UINT8> data)
{
String result;
result.reserve(((data.size() + 2) / 3) * 4);
for (size_t i = 0; i < data.size(); i += 3)
{
uint32_t value = 0;
INT32 num_bytes = 0;
for (INT32 j = 0; j < 3 && (i + j) < data.size(); ++j)
{
value = (value << 8) | data[i + j];
num_bytes++;
}
for (INT32 j = 0; j < num_bytes + 1; ++j)
{
if (j < 4)
{
result += BASE64_CHAR_TABLE[(value >> (6 * (3 - j))) & 0x3F];
}
}
if (num_bytes < 3)
{
for (INT32 j = 0; j < (3 - num_bytes); ++j)
{
result += '=';
}
}
}
return result;
}
Vector<UINT8> StringOps::DecodeBase64(IN CONST String &data)
{
Vector<UINT8> result;
CONST AUTO isBase64 = [](UINT8 c) { return (isalnum(c) || (c == '+') || (c == '/')); };
INT32 in_len = data.size();
INT32 i = 0, j = 0, in_ = 0;
UINT8 tmpBuf0[4], tmpBuf1[3];
while (in_len-- && (data[in_] != '=') && isBase64(data[in_]))
{
tmpBuf0[i++] = data[in_];
in_++;
if (i == 4)
{
for (i = 0; i < 4; i++)
tmpBuf0[i] = BASE64_CHAR_TABLE.find(tmpBuf0[i]);
tmpBuf1[0] = (tmpBuf0[0] << 2) + ((tmpBuf0[1] & 0x30) >> 4);
tmpBuf1[1] = ((tmpBuf0[1] & 0xf) << 4) + ((tmpBuf0[2] & 0x3c) >> 2);
tmpBuf1[2] = ((tmpBuf0[2] & 0x3) << 6) + tmpBuf0[3];
for (i = 0; (i < 3); i++)
result.push_back(tmpBuf1[i]);
i = 0;
}
}
if (i)
{
for (j = i; j < 4; j++)
tmpBuf0[j] = 0;
for (j = 0; j < 4; j++)
tmpBuf0[j] = BASE64_CHAR_TABLE.find(tmpBuf0[j]);
tmpBuf1[0] = (tmpBuf0[0] << 2) + ((tmpBuf0[1] & 0x30) >> 4);
tmpBuf1[1] = ((tmpBuf0[1] & 0xf) << 4) + ((tmpBuf0[2] & 0x3c) >> 2);
tmpBuf1[2] = ((tmpBuf0[2] & 0x3) << 6) + tmpBuf0[3];
for (j = 0; (j < i - 1); j++)
result.push_back(tmpBuf1[j]);
}
return result;
}
} // namespace IACore

View File

@ -0,0 +1,226 @@
// 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 RingBufferView
{
public:
STATIC CONSTEXPR UINT16 PACKET_ID_SKIP = 0;
struct ControlBlock
{
struct alignas(64)
{
Atomic<UINT32> WriteOffset{0};
} Producer;
struct alignas(64)
{
Atomic<UINT32> ReadOffset{0};
// Capacity is effectively constant after init,
// so it doesn't cause false sharing invalidations.
UINT32 Capacity{0};
} Consumer;
};
static_assert(offsetof(ControlBlock, Consumer) == 64, "False sharing detected in ControlBlock");
// All of the data in ring buffer will be stored as packets
struct PacketHeader
{
PacketHeader() : ID(0), PayloadSize(0)
{
}
PacketHeader(IN UINT16 id) : ID(id), PayloadSize(0)
{
}
PacketHeader(IN UINT16 id, IN UINT16 payloadSize) : ID(id), PayloadSize(payloadSize)
{
}
UINT16 ID{};
UINT16 PayloadSize{};
};
public:
INLINE RingBufferView(IN Span<UINT8> buffer, IN BOOL isOwner);
INLINE RingBufferView(IN ControlBlock *controlBlock, IN Span<UINT8> buffer, IN BOOL isOwner);
INLINE INT32 Pop(OUT PacketHeader &outHeader, OUT Span<UINT8> outBuffer);
INLINE BOOL Push(IN UINT16 packetID, IN Span<CONST UINT8> data);
INLINE ControlBlock *GetControlBlock();
private:
PUINT8 m_dataPtr{};
UINT32 m_capacity{};
ControlBlock *m_controlBlock{};
private:
INLINE VOID WriteWrapped(IN UINT32 offset, IN PCVOID data, IN UINT32 size);
INLINE VOID ReadWrapped(IN UINT32 offset, OUT PVOID outData, IN UINT32 size);
};
} // namespace IACore
namespace IACore
{
RingBufferView::RingBufferView(IN Span<UINT8> buffer, IN BOOL isOwner)
{
IA_ASSERT(buffer.size() > sizeof(ControlBlock));
m_controlBlock = reinterpret_cast<ControlBlock *>(buffer.data());
m_dataPtr = buffer.data() + sizeof(ControlBlock);
m_capacity = static_cast<UINT32>(buffer.size()) - sizeof(ControlBlock);
if (isOwner)
{
m_controlBlock->Consumer.Capacity = m_capacity;
m_controlBlock->Producer.WriteOffset.store(0, std::memory_order_release);
m_controlBlock->Consumer.ReadOffset.store(0, std::memory_order_release);
}
else
IA_ASSERT(m_controlBlock->Consumer.Capacity == m_capacity);
}
RingBufferView::RingBufferView(IN ControlBlock *controlBlock, IN Span<UINT8> buffer, IN BOOL isOwner)
{
IA_ASSERT(controlBlock != nullptr);
IA_ASSERT(buffer.size() > 0);
m_controlBlock = controlBlock;
m_dataPtr = buffer.data();
m_capacity = static_cast<UINT32>(buffer.size());
if (isOwner)
{
m_controlBlock->Consumer.Capacity = m_capacity;
m_controlBlock->Producer.WriteOffset.store(0, std::memory_order_release);
m_controlBlock->Consumer.ReadOffset.store(0, std::memory_order_release);
}
}
INT32 RingBufferView::Pop(OUT PacketHeader &outHeader, OUT Span<UINT8> outBuffer)
{
UINT32 write = m_controlBlock->Producer.WriteOffset.load(std::memory_order_acquire);
UINT32 read = m_controlBlock->Consumer.ReadOffset.load(std::memory_order_relaxed);
UINT32 cap = m_capacity;
if (read == write)
return 0; // Empty
ReadWrapped(read, &outHeader, sizeof(PacketHeader));
if (outHeader.PayloadSize > outBuffer.size())
return -static_cast<INT32>(outHeader.PayloadSize);
if (outHeader.PayloadSize > 0)
{
UINT32 dataReadOffset = (read + sizeof(PacketHeader)) % cap;
ReadWrapped(dataReadOffset, outBuffer.data(), outHeader.PayloadSize);
}
// Move read pointer forward
UINT32 newReadOffset = (read + sizeof(PacketHeader) + outHeader.PayloadSize) % cap;
m_controlBlock->Consumer.ReadOffset.store(newReadOffset, std::memory_order_release);
return outHeader.PayloadSize;
}
BOOL RingBufferView::Push(IN UINT16 packetID, IN Span<CONST UINT8> data)
{
IA_ASSERT(data.size() <= UINT16_MAX);
const UINT32 totalSize = sizeof(PacketHeader) + static_cast<UINT32>(data.size());
UINT32 read = m_controlBlock->Consumer.ReadOffset.load(std::memory_order_acquire);
UINT32 write = m_controlBlock->Producer.WriteOffset.load(std::memory_order_relaxed);
UINT32 cap = m_capacity;
UINT32 freeSpace = (read <= write) ? (m_capacity - write) + read : (read - write);
// Ensure to always leave 1 byte empty to prevent Read == Write ambiguity (Wait-Free Ring Buffer standard)
if (freeSpace <= totalSize)
return FALSE;
PacketHeader header{packetID, static_cast<UINT16>(data.size())};
WriteWrapped(write, &header, sizeof(PacketHeader));
UINT32 dataWriteOffset = (write + sizeof(PacketHeader)) % cap;
if (data.size() > 0)
{
WriteWrapped(dataWriteOffset, data.data(), static_cast<UINT32>(data.size()));
}
UINT32 newWriteOffset = (dataWriteOffset + data.size()) % cap;
m_controlBlock->Producer.WriteOffset.store(newWriteOffset, std::memory_order_release);
return TRUE;
}
RingBufferView::ControlBlock *RingBufferView::GetControlBlock()
{
return m_controlBlock;
}
VOID RingBufferView::WriteWrapped(IN UINT32 offset, IN PCVOID data, IN UINT32 size)
{
if (offset + size <= m_capacity)
{
// Contiguous write
memcpy(m_dataPtr + offset, data, size);
}
else
{
// Split write
UINT32 firstChunk = m_capacity - offset;
UINT32 secondChunk = size - firstChunk;
const UINT8 *src = static_cast<const UINT8 *>(data);
memcpy(m_dataPtr + offset, src, firstChunk);
memcpy(m_dataPtr, src + firstChunk, secondChunk);
}
}
VOID RingBufferView::ReadWrapped(IN UINT32 offset, OUT PVOID outData, IN UINT32 size)
{
if (offset + size <= m_capacity)
{
// Contiguous read
memcpy(outData, m_dataPtr + offset, size);
}
else
{
// Split read
UINT32 firstChunk = m_capacity - offset;
UINT32 secondChunk = size - firstChunk;
UINT8 *dst = static_cast<UINT8 *>(outData);
memcpy(dst, m_dataPtr + offset, firstChunk);
memcpy(dst + firstChunk, m_dataPtr, secondChunk);
}
}
} // namespace IACore

View File

@ -0,0 +1,74 @@
// 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 AsyncOps
{
public:
using TaskTag = UINT64;
using WorkerID = UINT16;
STATIC CONSTEXPR WorkerID MainThreadWorkerID = 0;
enum class Priority : UINT8
{
High,
Normal
};
struct Schedule
{
Atomic<INT32> Counter{0};
};
public:
STATIC VOID InitializeScheduler(IN UINT8 workerCount = 0);
STATIC VOID TerminateScheduler();
STATIC VOID ScheduleTask(IN Function<VOID(IN WorkerID workerID)> task, IN TaskTag tag, IN Schedule *schedule,
IN Priority priority = Priority::Normal);
STATIC VOID CancelTasksOfTag(IN TaskTag tag);
STATIC VOID WaitForScheduleCompletion(IN Schedule *schedule);
STATIC VOID RunTask(IN Function<VOID()> task);
STATIC WorkerID GetWorkerCount();
private:
struct ScheduledTask
{
TaskTag Tag{};
Schedule *ScheduleHandle{};
Function<VOID(IN WorkerID workerID)> Task{};
};
STATIC VOID ScheduleWorkerLoop(IN StopToken stopToken, IN WorkerID workerID);
private:
STATIC Mutex s_queueMutex;
STATIC ConditionVariable s_wakeCondition;
STATIC Vector<JoiningThread> s_scheduleWorkers;
STATIC Deque<ScheduledTask> s_highPriorityQueue;
STATIC Deque<ScheduledTask> s_normalPriorityQueue;
};
} // namespace IACore

View File

@ -0,0 +1,50 @@
// 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 DataOps
{
public:
enum class CompressionType
{
None,
Gzip,
Zlib
};
public:
STATIC UINT32 Hash(IN CONST String &string);
STATIC UINT32 Hash(IN Span<CONST UINT8> data);
STATIC UINT32 CRC32(IN Span<CONST UINT8> data);
STATIC CompressionType DetectCompression(IN Span<CONST UINT8> data);
STATIC Expected<Vector<UINT8>, String> GZipInflate(IN Span<CONST UINT8> data);
STATIC Expected<Vector<UINT8>, String> GZipDeflate(IN Span<CONST UINT8> data);
STATIC Expected<Vector<UINT8>, String> ZlibInflate(IN Span<CONST UINT8> data);
STATIC Expected<Vector<UINT8>, String> ZlibDeflate(IN Span<CONST UINT8> data);
STATIC Expected<Vector<UINT8>, String> ZstdInflate(IN Span<CONST UINT8> data);
STATIC Expected<Vector<UINT8>, String> ZstdDeflate(IN Span<CONST UINT8> data);
};
} // namespace IACore

View File

@ -0,0 +1,179 @@
// 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
# include <libloaderapi.h>
# include <errhandlingapi.h>
#else
# include <dlfcn.h>
#endif
namespace IACore
{
class DynamicLib
{
public:
DynamicLib() : m_handle(nullptr)
{
}
DynamicLib(DynamicLib &&other) NOEXCEPT : m_handle(other.m_handle)
{
other.m_handle = nullptr;
}
DynamicLib &operator=(DynamicLib &&other) NOEXCEPT
{
if (this != &other)
{
Unload(); // Free current if exists
m_handle = other.m_handle;
other.m_handle = nullptr;
}
return *this;
}
DynamicLib(CONST DynamicLib &) = delete;
DynamicLib &operator=(CONST DynamicLib &) = delete;
~DynamicLib()
{
Unload();
}
// Automatically detects extension (.dll/.so) if not provided
NO_DISCARD("Check for load errors")
STATIC tl::expected<DynamicLib, String> Load(CONST String &searchPath, CONST String &name)
{
namespace fs = std::filesystem;
fs::path fullPath = fs::path(searchPath) / name;
if (!fullPath.has_extension())
{
#if IA_PLATFORM_WINDOWS
fullPath += ".dll";
#elif IA_PLATFORM_MAC
fullPath += ".dylib";
#else
fullPath += ".so";
#endif
}
DynamicLib lib;
#if IA_PLATFORM_WINDOWS
HMODULE h = LoadLibraryA(fullPath.string().c_str());
if (!h)
{
return tl::make_unexpected(GetWindowsError());
}
lib.m_handle = CAST(h, PVOID);
#else
// RTLD_LAZY: Resolve symbols only as code executes (Standard for plugins)
void *h = dlopen(fullPath.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!h)
{
// dlerror returns a string describing the last error
const char *err = dlerror();
return tl::make_unexpected(String(err ? err : "Unknown dlopen error"));
}
lib.m_handle = h;
#endif
return IA_MOVE(lib);
}
NO_DISCARD("Check if symbol exists")
tl::expected<PVOID, String> GetSymbol(CONST String &name) CONST
{
if (!m_handle)
return tl::make_unexpected(String("Library not loaded"));
PVOID sym = nullptr;
#if IA_PLATFORM_WINDOWS
sym = CAST(GetProcAddress(CAST(m_handle, HMODULE), name.c_str()), PVOID);
if (!sym)
return tl::make_unexpected(GetWindowsError());
#else
// Clear any previous error
dlerror();
sym = dlsym(m_handle, name.c_str());
const char *err = dlerror();
if (err)
return tl::make_unexpected(String(err));
#endif
return sym;
}
// Template helper for casting
template<typename FuncT> tl::expected<FuncT, String> GetFunction(CONST String &name) CONST
{
auto res = GetSymbol(name);
if (!res)
return tl::make_unexpected(res.error());
return REINTERPRET(*res, FuncT);
}
VOID Unload()
{
if (m_handle)
{
#if IA_PLATFORM_WINDOWS
FreeLibrary(CAST(m_handle, HMODULE));
#else
dlclose(m_handle);
#endif
m_handle = nullptr;
}
}
BOOL IsLoaded() CONST
{
return m_handle != nullptr;
}
private:
PVOID m_handle;
#if IA_PLATFORM_WINDOWS
STATIC String GetWindowsError()
{
DWORD errorID = ::GetLastError();
if (errorID == 0)
return String();
LPSTR messageBuffer = nullptr;
size_t size = FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
errorID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR) &messageBuffer, 0, NULL);
String message(messageBuffer, size);
LocalFree(messageBuffer);
return String("Win32 Error: ") + message;
}
#endif
};
} // 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 Environment
{
public:
STATIC Optional<String> Find(CONST String &name)
{
#if IA_PLATFORM_WINDOWS
DWORD bufferSize = GetEnvironmentVariableA(name.c_str(), nullptr, 0);
if (bufferSize == 0)
{
// DWORD 0 means failed
return std::nullopt;
}
// 2. Allocate (bufferSize includes the null terminator request)
String result;
result.resize(bufferSize);
// 3. Fetch
// Returns num chars written excluding null terminator
DWORD actualSize = GetEnvironmentVariableA(name.c_str(), result.data(), bufferSize);
if (actualSize == 0 || actualSize > bufferSize)
{
return std::nullopt;
}
// Resize down to exclude the null terminator and any slack
result.resize(actualSize);
return result;
#else
// getenv returns a pointer to the environment area
const char *val = std::getenv(name.c_str());
if (val == nullptr)
{
return std::nullopt;
}
return String(val);
#endif
}
STATIC String Get(CONST String &name, CONST String &defaultValue = "")
{
return Find(name).value_or(defaultValue);
}
STATIC BOOL Set(CONST String &name, CONST String &value)
{
if (name.empty())
return FALSE;
#if IA_PLATFORM_WINDOWS
return SetEnvironmentVariableA(name.c_str(), value.c_str()) != 0;
#else
// Returns 0 on success, -1 on error
return setenv(name.c_str(), value.c_str(), 1) == 0;
#endif
}
STATIC BOOL Unset(CONST String &name)
{
if (name.empty())
return FALSE;
#if IA_PLATFORM_WINDOWS
return SetEnvironmentVariableA(name.c_str(), nullptr) != 0;
#else
return unsetenv(name.c_str()) == 0;
#endif
}
STATIC BOOL Exists(CONST String &name)
{
#if IA_PLATFORM_WINDOWS
return GetEnvironmentVariableA(name.c_str(), nullptr, 0) > 0;
#else
return std::getenv(name.c_str()) != nullptr;
#endif
}
};
} // namespace IACore

View File

@ -0,0 +1,50 @@
// 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 FilePath NormalizeExecutablePath(IN CONST FilePath &path);
public:
STATIC VOID UnmapFile(IN PCUINT8 mappedPtr);
STATIC Expected<PCUINT8, String> MapFile(IN CONST FilePath &path, OUT SIZE_T &size);
// @param `isOwner` TRUE to allocate/truncate. FALSE to just open.
STATIC Expected<PUINT8, String> MapSharedMemory(IN CONST String &name, IN SIZE_T size, IN BOOL isOwner);
STATIC VOID UnlinkSharedMemory(IN CONST String &name);
STATIC Expected<StreamReader, String> StreamFromFile(IN CONST FilePath &path);
STATIC Expected<StreamWriter, String> StreamToFile(IN CONST FilePath &path, IN BOOL overwrite = false);
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,
IN BOOL overwrite = false);
STATIC Expected<SIZE_T, String> WriteBinaryFile(IN CONST FilePath &path, IN Span<UINT8> contents,
IN BOOL overwrite = false);
private:
STATIC UnorderedMap<PCUINT8, Tuple<PVOID, PVOID, PVOID>> s_mappedFiles;
};
} // namespace IACore

View File

@ -0,0 +1,196 @@
// 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/JSON.hpp>
namespace IACore
{
class HttpClient
{
public:
enum class EHeaderType
{
ACCEPT,
ACCEPT_CHARSET,
ACCEPT_ENCODING,
ACCEPT_LANGUAGE,
AUTHORIZATION,
CACHE_CONTROL,
CONNECTION,
CONTENT_LENGTH,
CONTENT_TYPE,
COOKIE,
DATE,
EXPECT,
HOST,
IF_MATCH,
IF_MODIFIED_SINCE,
IF_NONE_MATCH,
ORIGIN,
PRAGMA,
PROXY_AUTHORIZATION,
RANGE,
REFERER,
TE,
UPGRADE,
USER_AGENT,
VIA,
WARNING
};
enum class EResponseCode : INT32
{
// 1xx Informational
CONTINUE = 100,
SWITCHING_PROTOCOLS = 101,
PROCESSING = 102,
EARLY_HINTS = 103,
// 2xx Success
OK = 200,
CREATED = 201,
ACCEPTED = 202,
NON_AUTHORITATIVE_INFORMATION = 203,
NO_CONTENT = 204,
RESET_CONTENT = 205,
PARTIAL_CONTENT = 206,
MULTI_STATUS = 207,
ALREADY_REPORTED = 208,
IM_USED = 226,
// 3xx Redirection
MULTIPLE_CHOICES = 300,
MOVED_PERMANENTLY = 301,
FOUND = 302,
SEE_OTHER = 303,
NOT_MODIFIED = 304,
USE_PROXY = 305,
TEMPORARY_REDIRECT = 307,
PERMANENT_REDIRECT = 308,
// 4xx Client Error
BAD_REQUEST = 400,
UNAUTHORIZED = 401,
PAYMENT_REQUIRED = 402,
FORBIDDEN = 403,
NOT_FOUND = 404,
METHOD_NOT_ALLOWED = 405,
NOT_ACCEPTABLE = 406,
PROXY_AUTHENTICATION_REQUIRED = 407,
REQUEST_TIMEOUT = 408,
CONFLICT = 409,
GONE = 410,
LENGTH_REQUIRED = 411,
PRECONDITION_FAILED = 412,
PAYLOAD_TOO_LARGE = 413,
URI_TOO_LONG = 414,
UNSUPPORTED_MEDIA_TYPE = 415,
RANGE_NOT_SATISFIABLE = 416,
EXPECTATION_FAILED = 417,
IM_A_TEAPOT = 418,
MISDIRECTED_REQUEST = 421,
UNPROCESSABLE_ENTITY = 422,
LOCKED = 423,
FAILED_DEPENDENCY = 424,
TOO_EARLY = 425,
UPGRADE_REQUIRED = 426,
PRECONDITION_REQUIRED = 428,
TOO_MANY_REQUESTS = 429,
REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
UNAVAILABLE_FOR_LEGAL_REASONS = 451,
// 5xx Server Error
INTERNAL_SERVER_ERROR = 500,
NOT_IMPLEMENTED = 501,
BAD_GATEWAY = 502,
SERVICE_UNAVAILABLE = 503,
GATEWAY_TIMEOUT = 504,
HTTP_VERSION_NOT_SUPPORTED = 505,
VARIANT_ALSO_NEGOTIATES = 506,
INSUFFICIENT_STORAGE = 507,
LOOP_DETECTED = 508,
NOT_EXTENDED = 510,
NETWORK_AUTHENTICATION_REQUIRED = 511
};
using Header = KeyValuePair<EHeaderType, String>;
public:
HttpClient(IN CONST String &host);
~HttpClient();
public:
Expected<String, String> RawGet(IN CONST String &path, IN Span<CONST Header> headers,
IN PCCHAR defaultContentType = "application/x-www-form-urlencoded");
Expected<String, String> RawPost(IN CONST String &path, IN Span<CONST Header> headers, IN CONST String &body,
IN PCCHAR defaultContentType = "application/x-www-form-urlencoded");
template<typename _response_type>
Expected<_response_type, String> JsonGet(IN CONST String &path, IN Span<CONST Header> headers);
template<typename _payload_type, typename _response_type>
Expected<_response_type, String> JsonPost(IN CONST String &path, IN Span<CONST Header> headers,
IN CONST _payload_type &body);
public:
STATIC String UrlEncode(IN CONST String &value);
STATIC String UrlDecode(IN CONST String &value);
STATIC String HeaderTypeToString(IN EHeaderType type);
STATIC Header CreateHeader(IN EHeaderType key, IN CONST String &value);
STATIC BOOL IsSuccessResponseCode(IN EResponseCode code);
public:
EResponseCode LastResponseCode()
{
return m_lastResponseCode;
}
private:
PVOID m_client{};
EResponseCode m_lastResponseCode;
private:
String PreprocessResponse(IN CONST String& response);
};
template<typename _response_type>
Expected<_response_type, String> HttpClient::JsonGet(IN CONST String &path, IN Span<CONST Header> headers)
{
const auto rawResponse = RawGet(path, headers, "application/json");
if (!rawResponse)
return MakeUnexpected(rawResponse.error());
if (LastResponseCode() != EResponseCode::OK)
return MakeUnexpected(std::format("Server responded with code {}", (INT32) LastResponseCode()));
return JSON::ParseToStruct<_response_type>(*rawResponse);
}
template<typename _payload_type, typename _response_type>
Expected<_response_type, String> HttpClient::JsonPost(IN CONST String &path, IN Span<CONST Header> headers,
IN CONST _payload_type &body)
{
const auto encodedBody = IA_TRY(JSON::EncodeStruct(body));
const auto rawResponse = RawPost(path, headers, encodedBody, "application/json");
if (!rawResponse)
return MakeUnexpected(rawResponse.error());
if (LastResponseCode() != EResponseCode::OK)
return MakeUnexpected(std::format("Server responded with code {}", (INT32) LastResponseCode()));
return JSON::ParseToStruct<_response_type>(*rawResponse);
}
} // namespace IACore

View File

@ -0,0 +1,42 @@
// 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>
#ifdef __cplusplus
namespace IACore
{
// Must be called from main thread
VOID Initialize();
// Must be called from same thread as Initialize
VOID Terminate();
UINT64 GetUnixTime();
UINT64 GetTicksCount();
FLOAT64 GetSecondsCount();
FLOAT32 GetRandom();
UINT32 GetRandom(IN UINT32 seed);
INT64 GetRandom(IN INT64 min, IN INT64 max);
BOOL IsMainThread();
VOID Sleep(IN UINT64 milliseconds);
} // namespace IACore
#endif

View File

@ -0,0 +1,334 @@
// 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>
#ifdef __cplusplus
# include <exception>
// -----------------------------------------------------------------------------
// Macros
// -----------------------------------------------------------------------------
# define valid_iatest_runner(type) iatest::_valid_iatest_runner<type>::value_type
# define __iat_micro_test(call) \
if (!(call)) \
return FALSE
# define IAT_CHECK(v) __iat_micro_test(_test((v), #v))
# define IAT_CHECK_NOT(v) __iat_micro_test(_test_not((v), "NOT " #v))
# define IAT_CHECK_EQ(lhs, rhs) __iat_micro_test(_test_eq((lhs), (rhs), #lhs " == " #rhs))
# define IAT_CHECK_NEQ(lhs, rhs) __iat_micro_test(_test_neq((lhs), (rhs), #lhs " != " #rhs))
# define IAT_CHECK_APPROX(lhs, rhs) __iat_micro_test(_test_approx((lhs), (rhs), #lhs " ~= " #rhs))
# define IAT_UNIT(func) _test_unit([this]() { return this->func(); }, #func)
# define IAT_NAMED_UNIT(n, func) _test_unit([this]() { return this->func(); }, n)
# define IAT_BLOCK(name) class name : public ia::iatest::block
# define IAT_BEGIN_BLOCK(_group, _name) \
class _group##_##_name : public ia::iatest::block \
{ \
public: \
PCCHAR name() CONST OVERRIDE \
{ \
return #_group "::" #_name; \
} \
\
private:
# define IAT_END_BLOCK() \
} \
;
# define IAT_BEGIN_TEST_LIST() \
public: \
VOID declareTests() OVERRIDE \
{
# define IAT_ADD_TEST(name) IAT_UNIT(name)
# define IAT_END_TEST_LIST() \
} \
\
private:
namespace ia::iatest
{
template<typename T> std::string ToString(CONST T &value)
{
if constexpr (std::is_arithmetic_v<T>)
{
return std::to_string(value);
}
else if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, const char *>)
{
return std::string("\"") + value + "\"";
}
else
{
return "{Object}"; // Fallback for complex types
}
}
template<typename T> std::string ToString(T *value)
{
if (value == NULLPTR)
return "nullptr";
std::stringstream ss;
ss << "ptr(" << (void *) value << ")";
return ss.str();
}
DEFINE_TYPE(functor_t, std::function<BOOL()>);
struct unit_t
{
std::string Name;
functor_t Functor;
};
class block
{
public:
virtual ~block() = default;
PURE_VIRTUAL(PCCHAR name() CONST);
PURE_VIRTUAL(VOID declareTests());
std::vector<unit_t> &units()
{
return m_units;
}
protected:
template<typename T1, typename T2> BOOL _test_eq(IN CONST T1 &lhs, IN CONST T2 &rhs, IN PCCHAR description)
{
if (lhs != rhs)
{
print_fail(description, ToString(lhs), ToString(rhs));
return FALSE;
}
return TRUE;
}
template<typename T1, typename T2> BOOL _test_neq(IN CONST T1 &lhs, IN CONST T2 &rhs, IN PCCHAR description)
{
if (lhs == rhs)
{
print_fail(description, ToString(lhs), "NOT " + ToString(rhs));
return FALSE;
}
return TRUE;
}
template<typename T> BOOL _test_approx(IN T lhs, IN T rhs, IN PCCHAR description)
{
static_assert(std::is_floating_point_v<T>, "Approx only works for floats/doubles");
T diff = std::abs(lhs - rhs);
if (diff > static_cast<T>(0.0001))
{
print_fail(description, ToString(lhs), ToString(rhs));
return FALSE;
}
return TRUE;
}
BOOL _test(IN BOOL value, IN PCCHAR description)
{
if (!value)
{
printf(__CC_BLUE " %s... " __CC_RED "FAILED" __CC_DEFAULT "\n", description);
return FALSE;
}
return TRUE;
}
BOOL _test_not(IN BOOL value, IN PCCHAR description)
{
if (value)
{
printf(__CC_BLUE " %s... " __CC_RED "FAILED" __CC_DEFAULT "\n", description);
return FALSE;
}
return TRUE;
}
VOID _test_unit(IN functor_t functor, IN PCCHAR name)
{
m_units.push_back({name, functor});
}
private:
VOID print_fail(PCCHAR desc, std::string v1, std::string v2)
{
printf(__CC_BLUE " %s... " __CC_RED "FAILED" __CC_DEFAULT "\n", desc);
printf(__CC_RED " Expected: %s" __CC_DEFAULT "\n", v2.c_str());
printf(__CC_RED " Actual: %s" __CC_DEFAULT "\n", v1.c_str());
}
std::vector<unit_t> m_units;
};
template<typename block_class>
concept valid_block_class = std::derived_from<block_class, block>;
template<BOOL stopOnFail = false, BOOL isVerbose = false> class runner
{
public:
runner()
{
}
~runner()
{
summarize();
}
template<typename block_class>
requires valid_block_class<block_class>
VOID testBlock();
private:
VOID summarize();
private:
SIZE_T m_testCount{0};
SIZE_T m_failCount{0};
SIZE_T m_blockCount{0};
};
template<BOOL stopOnFail, BOOL isVerbose>
template<typename block_class>
requires valid_block_class<block_class>
VOID runner<stopOnFail, isVerbose>::testBlock()
{
m_blockCount++;
block_class b;
b.declareTests();
printf(__CC_MAGENTA "Testing [%s]..." __CC_DEFAULT "\n", b.name());
for (auto &v : b.units())
{
m_testCount++;
if constexpr (isVerbose)
{
printf(__CC_YELLOW " Testing %s...\n" __CC_DEFAULT, v.Name.c_str());
}
BOOL result = FALSE;
try
{
result = v.Functor();
}
catch (const std::exception &e)
{
printf(__CC_RED " CRITICAL EXCEPTION in %s: %s\n" __CC_DEFAULT, v.Name.c_str(), e.what());
result = FALSE;
}
catch (...)
{
printf(__CC_RED " UNKNOWN CRITICAL EXCEPTION in %s\n" __CC_DEFAULT, v.Name.c_str());
result = FALSE;
}
if (!result)
{
m_failCount++;
if constexpr (stopOnFail)
{
summarize();
exit(-1);
}
}
}
fputs("\n", stdout);
}
template<BOOL stopOnFail, BOOL isVerbose> VOID runner<stopOnFail, isVerbose>::summarize()
{
printf(__CC_GREEN
"\n-----------------------------------\n\t SUMMARY\n-----------------------------------\n");
if (!m_failCount)
{
printf("\n\tALL TESTS PASSED!\n\n");
}
else
{
FLOAT64 successRate =
(100.0 * static_cast<FLOAT64>(m_testCount - m_failCount) / static_cast<FLOAT64>(m_testCount));
printf(__CC_RED "%zu OUT OF %zu TESTS FAILED\n" __CC_YELLOW "Success Rate: %.2f%%\n", m_failCount,
m_testCount, successRate);
}
printf(__CC_MAGENTA "Ran %zu test(s) across %zu block(s)\n" __CC_GREEN
"-----------------------------------" __CC_DEFAULT "\n",
m_testCount, m_blockCount);
}
template<typename> struct _valid_iatest_runner : std::false_type
{
};
template<BOOL stopOnFail, BOOL isVerbose>
struct _valid_iatest_runner<runner<stopOnFail, isVerbose>> : std::true_type
{
};
using DefaultRunner = runner<false, true>;
class TestRegistry
{
public:
using TestEntry = std::function<void(DefaultRunner &)>;
static std::vector<TestEntry> &GetEntries()
{
static std::vector<TestEntry> entries;
return entries;
}
static int RunAll()
{
DefaultRunner r;
auto &entries = GetEntries();
printf(__CC_CYAN "[IATest] Discovered %zu Test Blocks\n\n" __CC_DEFAULT, entries.size());
for (auto &entry : entries)
{
entry(r);
}
// The destructor of 'r' will automatically print the summary
return 0;
}
};
template<typename BlockType> struct AutoRegister
{
AutoRegister()
{
TestRegistry::GetEntries().push_back([](DefaultRunner &r) { r.testBlock<BlockType>(); });
}
};
} // namespace ia::iatest
# define IAT_REGISTER_ENTRY(Group, Name) static ia::iatest::AutoRegister<Group##_##Name> _iat_reg_##Group##_##Name;
#endif // __cplusplus

View File

@ -0,0 +1,152 @@
// 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/ADT/RingBuffer.hpp>
#include <IACore/ProcessOps.hpp>
#include <IACore/SocketOps.hpp>
namespace IACore
{
using IPC_PacketHeader = RingBufferView::PacketHeader;
struct alignas(64) IPC_SharedMemoryLayout
{
// =========================================================
// SECTION 1: METADATA & HANDSHAKE
// =========================================================
struct Header
{
UINT32 Magic; // 0x49414950 ("IAIP")
UINT32 Version; // 1
UINT64 TotalSize; // Total size of SHM block
} Meta;
// Pad to ensure MONI starts on a fresh cache line (64 bytes)
UINT8 _pad0[64 - sizeof(Header)];
// =========================================================
// SECTION 2: RING BUFFER CONTROL BLOCKS
// =========================================================
// RingBufferView ControlBlock is already 64-byte aligned internally.
RingBufferView::ControlBlock MONI_Control;
RingBufferView::ControlBlock MINO_Control;
// =========================================================
// SECTION 3: DATA BUFFER OFFSETS
// =========================================================
UINT64 MONI_DataOffset;
UINT64 MONI_DataSize;
UINT64 MINO_DataOffset;
UINT64 MINO_DataSize;
// Pad to ensure the actual Data Buffer starts on a fresh cache line
UINT8 _pad1[64 - (sizeof(UINT64) * 4)];
static constexpr size_t GetHeaderSize()
{
return sizeof(IPC_SharedMemoryLayout);
}
};
// Static assert to ensure manual padding logic is correct
static_assert(sizeof(IPC_SharedMemoryLayout) % 64 == 0, "IPC Layout is not cache-line aligned!");
class IPC_Node
{
public:
virtual ~IPC_Node();
// When Manager spawns a node, `connectionString` is passed
// as the first command line argument
Expected<VOID, String> Connect(IN PCCHAR connectionString);
VOID Update();
VOID SendSignal(IN UINT8 signal);
VOID SendPacket(IN UINT16 packetID, IN Span<CONST UINT8> payload);
protected:
PURE_VIRTUAL(VOID OnSignal(IN UINT8 signal));
PURE_VIRTUAL(VOID OnPacket(IN UINT16 packetID, IN Span<CONST UINT8> payload));
private:
String m_shmName;
PUINT8 m_sharedMemory{};
Vector<UINT8> m_receiveBuffer;
SocketHandle m_socket{INVALID_SOCKET};
UniquePtr<RingBufferView> MONI; // Manager Out, Node In
UniquePtr<RingBufferView> MINO; // Manager In, Node Out
};
class IPC_Manager
{
struct NodeSession
{
SteadyTimePoint CreationTime{};
SharedPtr<ProcessHandle> ProcessHandle;
Mutex SendMutex;
String SharedMemName;
PUINT8 MappedPtr{};
SocketHandle ListenerSocket{INVALID_SOCKET};
SocketHandle DataSocket{INVALID_SOCKET};
UniquePtr<RingBufferView> MONI; // Manager Out, Node In
UniquePtr<RingBufferView> MINO; // Manager In, Node Out
BOOL IsReady{FALSE};
VOID SendSignal(IN UINT8 signal);
VOID SendPacket(IN UINT16 packetID, IN Span<CONST UINT8> payload);
};
public:
STATIC CONSTEXPR UINT32 DEFAULT_NODE_SHARED_MEMORY_SIZE = SIZE_MB(4);
public:
IPC_Manager();
virtual ~IPC_Manager();
VOID Update();
Expected<NativeProcessID, String> SpawnNode(IN CONST FilePath &executablePath,
IN UINT32 sharedMemorySize = DEFAULT_NODE_SHARED_MEMORY_SIZE);
BOOL WaitTillNodeIsOnline(IN NativeProcessID node);
VOID ShutdownNode(IN NativeProcessID node);
VOID SendSignal(IN NativeProcessID node, IN UINT8 signal);
VOID SendPacket(IN NativeProcessID node, IN UINT16 packetID, IN Span<CONST UINT8> payload);
protected:
PURE_VIRTUAL(VOID OnSignal(IN NativeProcessID node, IN UINT8 signal));
PURE_VIRTUAL(VOID OnPacket(IN NativeProcessID node, IN UINT16 packetID, IN Span<CONST UINT8> payload));
private:
Vector<UINT8> m_receiveBuffer;
Vector<UniquePtr<NodeSession>> m_activeSessions;
Vector<UniquePtr<NodeSession>> m_pendingSessions;
UnorderedMap<NativeProcessID, NodeSession *> m_activeSessionMap;
};
} // 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/>.
#pragma once
#include <IACore/PCH.hpp>
#include <simdjson.h>
#include <glaze/glaze.hpp>
#include <nlohmann/json.hpp>
namespace IACore
{
class JSON
{
private:
STATIC CONSTEXPR AUTO GLAZE_JSON_OPTS = glz::opts{.error_on_unknown_keys = false};
public:
STATIC Expected<nlohmann::json, String> Parse(IN CONST String &json);
STATIC Expected<Pair<simdjson::dom::parser, simdjson::dom::object>, String> ParseReadOnly(
IN CONST String &json);
template<typename _object_type> STATIC Expected<_object_type, String> ParseToStruct(IN CONST String &json);
STATIC String Encode(IN nlohmann::json data);
template<typename _object_type> STATIC Expected<String, String> EncodeStruct(IN CONST _object_type &data);
};
template<typename _object_type> Expected<_object_type, String> JSON::ParseToStruct(IN CONST String &json)
{
_object_type result{};
const auto parseError = glz::read_json<GLAZE_JSON_OPTS>(result, json);
if (parseError)
return MakeUnexpected(std::format("JSON Error: {}", glz::format_error(parseError, json)));
return result;
}
template<typename _object_type> Expected<String, String> JSON::EncodeStruct(IN CONST _object_type &data)
{
String result;
const auto encodeError = glz::write_json(data, result);
if (encodeError)
return MakeUnexpected(std::format("JSON Error: {}", glz::format_error(encodeError)));
return result;
}
} // namespace IACore

View File

@ -0,0 +1,119 @@
// 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 Logger
{
public:
enum class ELogLevel
{
VERBOSE,
INFO,
WARN,
ERROR
};
public:
STATIC BOOL EnableLoggingToDisk(IN PCCHAR filePath);
STATIC VOID SetLogLevel(IN ELogLevel logLevel);
template<typename... Args> STATIC VOID Status(FormatterString<Args...> fmt, Args &&...args)
{
LogStatus(std::vformat(fmt.get(), std::make_format_args(args...)));
}
template<typename... Args> STATIC VOID Info(FormatterString<Args...> fmt, Args &&...args)
{
LogInfo(std::vformat(fmt.get(), std::make_format_args(args...)));
}
template<typename... Args> STATIC VOID Warn(FormatterString<Args...> fmt, Args &&...args)
{
LogWarn(std::vformat(fmt.get(), std::make_format_args(args...)));
}
template<typename... Args> STATIC VOID Error(FormatterString<Args...> fmt, Args &&...args)
{
LogError(std::vformat(fmt.get(), std::make_format_args(args...)));
}
STATIC VOID FlushLogs();
private:
#if IA_DISABLE_LOGGING > 0
STATIC VOID LogStatus(IN String &&msg)
{
UNUSED(msg);
}
STATIC VOID LogInfo(IN String &&msg)
{
UNUSED(msg);
}
STATIC VOID LogWarn(IN String &&msg)
{
UNUSED(msg);
}
STATIC VOID LogError(IN String &&msg)
{
UNUSED(msg);
}
#else
STATIC VOID LogStatus(IN String &&msg)
{
if (s_logLevel <= ELogLevel::VERBOSE)
LogInternal(__CC_WHITE, "STATUS", IA_MOVE(msg));
}
STATIC VOID LogInfo(IN String &&msg)
{
if (s_logLevel <= ELogLevel::INFO)
LogInternal(__CC_GREEN, "INFO", IA_MOVE(msg));
}
STATIC VOID LogWarn(IN String &&msg)
{
if (s_logLevel <= ELogLevel::WARN)
LogInternal(__CC_YELLOW, "WARN", IA_MOVE(msg));
}
STATIC VOID LogError(IN String &&msg)
{
if (s_logLevel <= ELogLevel::ERROR)
LogInternal(__CC_RED, "ERROR", IA_MOVE(msg));
}
#endif
STATIC VOID LogInternal(IN PCCHAR prefix, IN PCCHAR tag, IN String &&msg);
private:
STATIC ELogLevel s_logLevel;
STATIC std::ofstream s_logFile;
STATIC VOID Initialize();
STATIC VOID Terminate();
friend VOID Initialize();
friend VOID Terminate();
};
} // namespace IACore

View File

@ -0,0 +1,587 @@
// 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
// -------------------------------------------------------------------------
// Platform Detection
// -------------------------------------------------------------------------
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
# ifdef _WIN64
# define IA_PLATFORM_WIN64 1
# define IA_PLATFORM_WINDOWS 1
# else
# define IA_PLATFORM_WIN32 1
# define IA_PLATFORM_WINDOWS 1
# endif
#elif __APPLE__
# include <TargetConditionals.h>
# if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR || TARGET_OS_MACCATALYST
# define IA_PLATFORM_IOS 1
# elif TARGET_OS_MAC
# define IA_PLATFORM_MAC 1
# endif
# define IA_PLATFORM_UNIX 1
#elif __ANDROID__
# define IA_PLATFORM_ANDROID 1
# define IA_PLATFORM_LINUX 1
# define IA_PLATFORM_UNIX 1
#elif __linux__
# define IA_PLATFORM_LINUX 1
# define IA_PLATFORM_UNIX 1
#elif __unix__
# define IA_PLATFORM_UNIX 1
#endif
#if IA_PLATFORM_WIN32 || IA_PLATFORM_WIN64
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# ifndef NOMINMAX
# define NOMINMAX
# endif
# include <windows.h>
# undef VOID
# undef ERROR
#elif IA_PLATFORM_UNIX
# include <unistd.h>
# include <sys/wait.h>
# include <sys/mman.h>
# include <sys/stat.h>
# include <fcntl.h>
# include <spawn.h>
# include <signal.h>
#endif
// -----------------------------------------------------------------------------
// Configuration Macros
// -----------------------------------------------------------------------------
#define IA_CHECK(o) (o > 0)
#if defined(__IA_DEBUG) && __IA_DEBUG
# define __DEBUG_MODE__
# define __BUILD_MODE_NAME "debug"
# define DEBUG_ONLY(v) v
# ifndef _DEBUG
# define _DEBUG
# endif
#else
# define __RELEASE_MODE__
# define __BUILD_MODE_NAME "release"
# ifndef NDEBUG
# define NDEBUG
# endif
# ifndef __OPTIMIZE__
# define __OPTIMIZE__
# endif
# define DEBUG_ONLY(f)
#endif
#if IA_CHECK(IA_PLATFORM_WIN64) || IA_CHECK(IA_PLATFORM_UNIX)
# define IA_CORE_PLATFORM_FEATURES 1
#else
# define IA_CORE_PLATFORM_FEATURES 0
# warning "IACore Unsupported Platform: Platform specific features will be disabled"
#endif
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#ifdef __cplusplus
# include <bit>
# include <new>
# include <span>
# include <atomic>
# include <mutex>
# include <thread>
# include <limits>
# include <cstring>
# include <cstddef>
# include <chrono>
# include <iomanip>
# include <fstream>
# include <iostream>
# include <concepts>
# include <filesystem>
# include <functional>
# include <type_traits>
# include <initializer_list>
# include <condition_variable>
# include <tuple>
# include <array>
# include <deque>
# include <string>
# include <vector>
# include <format>
# include <sstream>
# include <optional>
# include <string_view>
# include <tl/expected.hpp>
# include <ankerl/unordered_dense.h>
#else
# include <float.h>
# include <stdbool.h>
# include <stddef.h>
# include <string.h>
#endif
// -----------------------------------------------------------------------------
// Security Macros
// -----------------------------------------------------------------------------
#define IA_PANIC(msg) \
{ \
fprintf(stderr, "PANIC: %s\n", msg); \
__builtin_trap(); \
}
// Advanced Security features are not included in OSS builds
// (OSS version does not implement 'IAC_CHECK_*'s)
#define IACORE_SECURITY_LEVEL 0
#define IAC_SEC_LEVEL(v) (IACORE_SECURITY_LEVEL >= v)
#if __IA_DEBUG || IAC_SEC_LEVEL(1)
# define __IAC_OVERFLOW_CHECKS 1
#else
# define __IAC_OVERFLOW_CHECKS 0
#endif
#if __IA_DEBUG || IAC_SEC_LEVEL(2)
# define __IAC_SANITY_CHECKS 1
#else
# define __IAC_SANITY_CHECKS 0
#endif
// -----------------------------------------------------------------------------
// Language Abstraction Macros
// -----------------------------------------------------------------------------
#define AUTO auto
#define CONST const
#define STATIC static
#define EXTERN extern
#ifdef __cplusplus
# define VIRTUAL virtual
# define OVERRIDE override
# define CONSTEXPR constexpr
# define NOEXCEPT noexcept
# define NULLPTR nullptr
# define IA_MOVE(...) std::move(__VA_ARGS__)
# define DECONST(t, v) const_cast<t>(v)
# define NORETURN [[noreturn]]
#else
# define VIRTUAL
# define OVERRIDE
# define CONSTEXPR const
# define NOEXCEPT
# define NULLPTR NULL
# define IA_MOVE(...) (__VA_ARGS__)
# define DECONST(t, v) ((t) (v))
# define NORETURN
#endif
#define DEFINE_TYPE(t, v) typedef v t
#define FORWARD_DECLARE(t, i) t i
#ifdef __cplusplus
# define PURE_VIRTUAL(f) VIRTUAL f = 0
#endif
// -----------------------------------------------------------------------------
// Attributes & Compiler Intrinsics
// -----------------------------------------------------------------------------
#define INLINE inline
#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
# define ALWAYS_INLINE __attribute__((always_inline)) inline
#elif defined(_MSC_VER)
# define ALWAYS_INLINE __forceinline
#else
# define ALWAYS_INLINE inline
#endif
#define UNUSED(v) ((void) v);
#if defined(__cplusplus)
# define NO_DISCARD(s) [[nodiscard(s)]]
# define B_LIKELY(cond) (cond) [[likely]]
# define B_UNLIKELY(cond) (cond) [[unlikely]]
#else
# define NO_DISCARD(s)
# define B_LIKELY(cond) (cond)
# define B_UNLIKELY(cond) (cond)
#endif
#define __INTERNAL_IA_STRINGIFY(value) #value
#define IA_STRINGIFY(value) __INTERNAL_IA_STRINGIFY(value)
#define ALIGN(a) __attribute__((aligned(a)))
#define ASM(...) __asm__ volatile(__VA_ARGS__)
#ifndef NULL
# define NULL 0
#endif
#ifndef _WIN32
# undef TRUE
# undef FALSE
# ifdef __cplusplus
# define FALSE false
# define TRUE true
# else
# define FALSE 0
# define TRUE 1
# endif
#endif
// Parameter Annotations
#define IN
#define OUT
#define INOUT
// -----------------------------------------------------------------------------
// Extern C Handling
// -----------------------------------------------------------------------------
#ifdef __cplusplus
# define IA_EXTERN_C_BEGIN \
extern "C" \
{
# define IA_EXTERN_C_END }
# define C_DECL(f) extern "C" f
#else
# define IA_EXTERN_C_BEGIN
# define IA_EXTERN_C_END
# define C_DECL(f) f
#endif
// -----------------------------------------------------------------------------
// Utilities
// -----------------------------------------------------------------------------
#ifdef __cplusplus
# define CAST(v, t) (static_cast<t>(v))
# define REINTERPRET(v, t) (reinterpret_cast<t>(v))
#else
# define CAST(v, t) ((t) (v))
#endif
// Templates and Aliases
#ifdef __cplusplus
# define ALIAS_FUNCTION(alias, function) \
template<typename... Args> auto alias(Args &&...args) -> decltype(function(std::forward<Args>(args)...)) \
{ \
return function(std::forward<Args>(args)...); \
}
# define ALIAS_TEMPLATE_FUNCTION(t, alias, function) \
template<typename t, typename... Args> \
auto alias(Args &&...args) -> decltype(function<t>(std::forward<Args>(args)...)) \
{ \
return function<t>(std::forward<Args>(args)...); \
}
#endif
// Assertions
#define IA_RELEASE_ASSERT(v) assert((v))
#define IA_RELEASE_ASSERT_MSG(v, m) assert((v) && m)
#if defined(__DEBUG_MODE__)
# define IA_ASSERT(v) IA_RELEASE_ASSERT(v)
# define IA_ASSERT_MSG(v, m) IA_RELEASE_ASSERT_MSG(v, m)
#else
# define IA_ASSERT(v)
# define IA_ASSERT_MSG(v, m)
#endif
#define IA_ASSERT_EQ(a, b) IA_ASSERT((a) == (b))
#define IA_ASSERT_GE(a, b) IA_ASSERT((a) >= (b))
#define IA_ASSERT_LE(a, b) IA_ASSERT(a <= b)
#define IA_ASSERT_LT(a, b) IA_ASSERT(a < b)
#define IA_ASSERT_GT(a, b) IA_ASSERT(a > b)
#define IA_ASSERT_IMPLIES(a, b) IA_ASSERT(!(a) || (b))
#define IA_ASSERT_NOT_NULL(v) IA_ASSERT(((v) != NULLPTR))
#define IA_UNREACHABLE(msg) IA_RELEASE_ASSERT_MSG(FALSE, "Unreachable code: " msg)
#define IA_TRY_PURE(expr) \
{ \
auto _ia_res = (expr); \
if (!_ia_res) \
{ \
return tl::make_unexpected(std::move(_ia_res.error())); \
} \
}
#define IA_TRY(expr) \
__extension__({ \
auto _ia_res = (expr); \
if (!_ia_res) \
{ \
return tl::make_unexpected(std::move(_ia_res.error())); \
} \
std::move(*_ia_res); \
})
#define IA_CONCAT_IMPL(x, y) x##y
#define IA_CONCAT(x, y) IA_CONCAT_IMPL(x, y)
#define IA_UNIQUE_NAME(prefix) IA_CONCAT(prefix, __LINE__)
#define SIZE_KB(v) (v * 1024)
#define SIZE_MB(v) (v * 1024 * 1024)
#define SIZE_GB(v) (v * 1024 * 1024 * 1024)
#define ENSURE_BINARY_COMPATIBILITY(A, B) \
static_assert(sizeof(A) == sizeof(B), \
#A ", " #B " size mismatch! Do not add virtual functions or new member variables.");
// -----------------------------------------------------------------------------
// Limits & Versioning
// -----------------------------------------------------------------------------
#ifdef __cplusplus
# define IA_MAX_POSSIBLE_SIZE (static_cast<SIZE_T>(0x7FFFFFFFFFFFF))
#else
# define IA_MAX_POSSIBLE_SIZE ((SIZE_T) (0x7FFFFFFFFFFFF))
#endif
#define IA_MAX_PATH_LENGTH 4096
#define IA_MAX_STRING_LENGTH (IA_MAX_POSSIBLE_SIZE >> 8)
#define IA_VERSION_TYPE uint64_t
#define IA_MAKE_VERSION(major, minor, patch) \
((((uint64_t) (major) & 0xFFFFFF) << 40) | (((uint64_t) (minor) & 0xFFFFFF) << 16) | ((uint64_t) (patch) & 0xFFFF))
// -----------------------------------------------------------------------------
// DLL Export/Import
// -----------------------------------------------------------------------------
#if defined(_MSC_VER)
# define IA_DLL_EXPORT __declspec(dllexport)
# define IA_DLL_IMPORT __declspec(dllimport)
#elif defined(__GNUC__)
# define IA_DLL_EXPORT __attribute__((visibility("default")))
# define IA_DLL_IMPORT
#else
# define IA_DLL_EXPORT
# define IA_DLL_IMPORT
#endif
// -----------------------------------------------------------------------------
// Console Colors (ANSI Escape Codes)
// -----------------------------------------------------------------------------
#define __CC_BLACK "\033[30m"
#define __CC_RED "\033[31m"
#define __CC_GREEN "\033[32m"
#define __CC_YELLOW "\033[33m"
#define __CC_BLUE "\033[34m"
#define __CC_MAGENTA "\033[35m"
#define __CC_CYAN "\033[36m"
#define __CC_WHITE "\033[37m"
#define __CC_DEFAULT "\033[39m"
// -------------------------------------------------------------------------
// Base Types
// -------------------------------------------------------------------------
typedef void VOID;
#ifndef _WIN32
# ifdef __cplusplus
typedef bool BOOL;
# else
typedef _Bool BOOL;
# endif
#endif
typedef char CHAR;
typedef uint16_t CHAR16;
typedef int8_t INT8;
typedef int16_t INT16;
typedef int32_t INT32;
typedef int64_t INT64;
typedef uint8_t UINT8;
typedef uint16_t UINT16;
typedef uint32_t UINT32;
typedef uint64_t UINT64;
typedef float FLOAT32;
typedef double FLOAT64;
typedef INT32 INT;
typedef UINT32 UINT;
typedef size_t SIZE_T;
#ifdef __cplusplus
typedef std::make_signed_t<size_t> SSIZE_T;
typedef std::align_val_t ALIGN_T;
#else
typedef ptrdiff_t SSIZE_T;
typedef size_t ALIGN_T;
#endif
// -------------------------------------------------------------------------
// Pointer Types
// -------------------------------------------------------------------------
typedef VOID *PVOID;
typedef BOOL *PBOOL;
typedef CHAR *PCHAR;
typedef CHAR16 *PCHAR16;
typedef INT8 *PINT8;
typedef INT16 *PINT16;
typedef INT32 *PINT32;
typedef INT64 *PINT64;
typedef UINT8 *PUINT8;
typedef UINT16 *PUINT16;
typedef UINT32 *PUINT32;
typedef UINT64 *PUINT64;
typedef INT *PINT;
typedef UINT *PUINT;
typedef FLOAT32 *PFLOAT32;
typedef FLOAT64 *PFLOAT64;
// -------------------------------------------------------------------------
// Const Pointer Types
// -------------------------------------------------------------------------
typedef CONST VOID *PCVOID;
typedef CONST BOOL *PCBOOL;
typedef CONST CHAR *PCCHAR;
typedef CONST CHAR16 *PCCHAR16;
typedef CONST INT8 *PCINT8;
typedef CONST INT16 *PCINT16;
typedef CONST INT32 *PCINT32;
typedef CONST INT64 *PCINT64;
typedef CONST UINT8 *PCUINT8;
typedef CONST UINT16 *PCUINT16;
typedef CONST UINT32 *PCUINT32;
typedef CONST UINT64 *PCUINT64;
typedef CONST INT *PCINT;
typedef CONST UINT *PCUINT;
typedef CONST SIZE_T *PCSIZE;
typedef CONST SSIZE_T *PCSSIZE;
typedef CONST FLOAT32 *PCFLOAT32;
typedef CONST FLOAT64 *PCFLOAT64;
// -------------------------------------------------------------------------
// GUID Structure
// -------------------------------------------------------------------------
#ifndef _WIN32
typedef struct _IA_GUID
{
UINT32 Data1;
UINT16 Data2;
UINT16 Data3;
UINT8 Data4[8];
# ifdef __cplusplus
bool operator==(const _IA_GUID &other) const
{
return __builtin_memcmp(this, &other, sizeof(_IA_GUID)) == 0;
}
bool operator!=(const _IA_GUID &other) const
{
return !(*this == other);
}
# endif
} GUID;
#endif
STATIC INLINE BOOL IA_GUID_Equals(CONST GUID *a, CONST GUID *b)
{
if (a == NULLPTR || b == NULLPTR)
return FALSE;
return memcmp(a, b, sizeof(GUID)) == 0;
}
// -------------------------------------------------------------------------
// Numeric Constants
// -------------------------------------------------------------------------
#ifdef __cplusplus
STATIC CONSTEXPR FLOAT32 FLOAT32_EPSILON = std::numeric_limits<FLOAT32>::epsilon();
STATIC CONSTEXPR FLOAT64 FLOAT64_EPSILON = std::numeric_limits<FLOAT64>::epsilon();
#else
STATIC CONST FLOAT32 FLOAT32_EPSILON = FLT_EPSILON;
STATIC CONST FLOAT64 FLOAT64_EPSILON = DBL_EPSILON;
#endif
// -------------------------------------------------------------------------
// Containers and Helpers
// -------------------------------------------------------------------------
#ifdef __cplusplus
template<typename _function_type> using Function = std::function<_function_type>;
template<typename _value_type> using InitializerList = std::initializer_list<_value_type>;
template<typename _value_type, SIZE_T count> using Array = std::array<_value_type, count>;
template<typename _value_type> using Vector = std::vector<_value_type>;
template<typename _value_type> using Optional = std::optional<_value_type>;
template<typename _key_type> using UnorderedSet = ankerl::unordered_dense::set<_key_type>;
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 _value_type> using Deque = std::deque<_value_type>;
template<typename _type_a, typename _type_b> using Pair = std::pair<_type_a, _type_b>;
template<typename... types> using Tuple = std::tuple<types...>;
template<typename _key_type, typename _value_type> using KeyValuePair = std::pair<_key_type, _value_type>;
template<typename _expected_type, typename _unexpected_type>
using Expected = tl::expected<_expected_type, _unexpected_type>;
ALIAS_FUNCTION(MakeUnexpected, tl::make_unexpected);
using String = std::string;
using StringView = std::string_view;
using StringStream = std::stringstream;
using SteadyClock = std::chrono::steady_clock;
using SteadyTimePoint = std::chrono::time_point<SteadyClock>;
using HighResClock = std::chrono::high_resolution_clock;
using HighResTimePoint = std::chrono::time_point<HighResClock>;
using Mutex = std::mutex;
using StopToken = std::stop_token;
using ScopedLock = std::scoped_lock<Mutex>;
using UniqueLock = std::unique_lock<Mutex>;
using JoiningThread = std::jthread;
using ConditionVariable = std::condition_variable;
namespace FileSystem = std::filesystem;
using FilePath = FileSystem::path;
template<typename... Args> using FormatterString = std::format_string<Args...>;
#endif

View File

@ -0,0 +1,68 @@
// 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 NativeProcessID GetCurrentProcessID();
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

@ -0,0 +1,107 @@
// 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
# include <winsock2.h>
# include <ws2tcpip.h>
# include <afunix.h>
# pragma comment(lib, "ws2_32.lib")
# define CLOSE_SOCKET(s) closesocket(s)
# define IS_VALID_SOCKET(s) (s != INVALID_SOCKET)
# define UNLINK_FILE(p) DeleteFileA(p)
using SocketHandle = SOCKET;
#elif IA_PLATFORM_UNIX
# include <sys/un.h>
# include <sys/types.h>
# include <sys/socket.h>
# include <netinet/in.h>
# define CLOSE_SOCKET(s) close(s)
# define IS_VALID_SOCKET(s) (s >= 0)
# define INVALID_SOCKET -1
# define UNLINK_FILE(p) unlink(p)
using SocketHandle = int;
#else
# error "IACore SocketOps is not supported on this platform."
#endif
namespace IACore
{
class SocketOps
{
public:
// SocketOps correctly handles multiple calls to Initialize and Terminate. Make sure
// every Initialize call is paired with a corresponding Terminate call
STATIC VOID Initialize()
{
s_initCount++;
if (s_initCount > 1)
return;
#if IA_PLATFORM_WINDOWS
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
#endif
}
// SocketOps correctly handles multiple calls to Initialize and Terminate. Make sure
// every Initialize call is paired with a corresponding Terminate call
STATIC VOID Terminate()
{
s_initCount--;
if (s_initCount > 0)
return;
#if IA_PLATFORM_WINDOWS
WSACleanup();
#endif
}
STATIC BOOL IsPortAvailableTCP(IN UINT16 port)
{
return IsPortAvailable(port, SOCK_STREAM);
}
STATIC BOOL IsPortAvailableUDP(IN UINT16 port)
{
return IsPortAvailable(port, SOCK_DGRAM);
}
STATIC BOOL IsWouldBlock();
STATIC VOID Close(IN SocketHandle sock);
STATIC BOOL Listen(IN SocketHandle sock, IN INT32 queueSize = 5);
STATIC SocketHandle CreateUnixSocket();
STATIC BOOL BindUnixSocket(IN SocketHandle sock, IN PCCHAR path);
STATIC BOOL ConnectUnixSocket(IN SocketHandle sock, IN PCCHAR path);
private:
STATIC BOOL IsPortAvailable(IN UINT16 port, IN INT32 type);
private:
STATIC INT32 s_initCount;
};
} // 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,66 @@
// 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_FILE,
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{};
FilePath m_filePath{};
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

@ -0,0 +1,29 @@
// 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 StringOps
{
public:
STATIC String EncodeBase64(IN Span<CONST UINT8> data);
STATIC Vector<UINT8> DecodeBase64(IN CONST String& data);
};
}

View File

@ -0,0 +1,152 @@
// 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 <algorithm>
namespace IACore
{
class Utils
{
public:
INLINE STATIC String BinaryToHexString(std::span<const UINT8> data)
{
STATIC CONSTEXPR char LUT[] = "0123456789ABCDEF";
String res;
res.reserve(data.size() * 2);
for (UINT8 b : data)
{
res.push_back(LUT[(b >> 4) & 0x0F]);
res.push_back(LUT[b & 0x0F]);
}
return res;
}
INLINE STATIC tl::expected<Vector<UINT8>, String> HexStringToBinary(CONST StringView &hex)
{
if (hex.size() % 2 != 0)
{
return tl::make_unexpected(String("Hex string must have even length"));
}
Vector<UINT8> out;
out.reserve(hex.size() / 2);
for (SIZE_T i = 0; i < hex.size(); i += 2)
{
char high = hex[i];
char low = hex[i + 1];
auto fromHexChar = [](char c) -> int {
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
return -1;
};
int h = fromHexChar(high);
int l = fromHexChar(low);
if (h == -1 || l == -1)
{
return tl::make_unexpected(String("Invalid hex character found"));
}
out.push_back(CAST((h << 4) | l, UINT8));
}
return out;
}
template<typename Range> INLINE STATIC VOID Sort(Range &&range)
{
std::ranges::sort(range);
}
template<typename Range, typename T> INLINE STATIC auto BinarySearchLeft(Range &&range, CONST T &value)
{
return std::ranges::lower_bound(range, value);
}
template<typename Range, typename T> INLINE STATIC auto BinarySearchRight(Range &&range, CONST T &value)
{
return std::ranges::upper_bound(range, value);
}
template<typename T> INLINE STATIC void HashCombine(UINT64 &seed, CONST T &v)
{
UINT64 h;
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
{
auto hasher = ankerl::unordered_dense::hash<T>();
h = hasher(v);
}
seed ^= h + 0x9e3779b97f4a7c15 + (seed << 6) + (seed >> 2);
}
template<typename... Args> INLINE STATIC UINT64 ComputeHash(CONST Args &...args)
{
UINT64 seed = 0;
(HashCombine(seed, args), ...);
return seed;
}
template<typename T, typename... MemberPtrs>
INLINE STATIC UINT64 ComputeHashFlat(CONST T &obj, MemberPtrs... members)
{
UINT64 seed = 0;
(HashCombine(seed, obj.*members), ...);
return seed;
}
};
} // namespace IACore
// -----------------------------------------------------------------------------
// MACRO: IA_MAKE_HASHABLE
//
// Injects the specialization for ankerl::unordered_dense::hash.
//
// Usage:
// struct Vector3 { float x, y, z; };
// IA_MAKE_HASHABLE(Vector3, &Vector3::x, &Vector3::y, &Vector3::z)
// -----------------------------------------------------------------------------
#define IA_MAKE_HASHABLE(Type, ...) \
template<> struct ankerl::unordered_dense::hash<Type> \
{ \
using is_avalanching = void; \
NO_DISCARD("Hash value should be used") \
UINT64 operator()(CONST Type &v) const NOEXCEPT \
{ \
/* Pass the object and the list of member pointers */ \
return IACore::Utils::ComputeHashFlat(v, __VA_ARGS__); \
} \
};