Base IACoreV2
Some checks failed
CI / build-linux-and-wasm (x64-linux) (push) Has been cancelled

This commit is contained in:
2026-01-22 05:54:26 +05:30
parent dedf28c6cb
commit 3ad1e3a2fb
57 changed files with 4398 additions and 4639 deletions

View File

@ -6,6 +6,7 @@ set(SRC_FILES
"imp/cpp/JSON.cpp"
"imp/cpp/IACore.cpp"
"imp/cpp/Logger.cpp"
"imp/cpp/Utils.cpp"
"imp/cpp/FileOps.cpp"
"imp/cpp/DataOps.cpp"
"imp/cpp/AsyncOps.cpp"
@ -16,9 +17,9 @@ set(SRC_FILES
"imp/cpp/StreamReader.cpp"
"imp/cpp/StreamWriter.cpp"
"imp/cpp/Http/Common.cpp"
"imp/cpp/Http/Client.cpp"
"imp/cpp/Http/Server.cpp"
#"imp/cpp/Http/Common.cpp"
#"imp/cpp/Http/Client.cpp"
#"imp/cpp/Http/Server.cpp"
)
add_library(IACore STATIC ${SRC_FILES})

View File

@ -15,164 +15,170 @@
#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;
namespace IACore {
std::mutex AsyncOps::s_queue_mutex;
std::condition_variable AsyncOps::s_wake_condition;
Vec<std::jthread> AsyncOps::s_schedule_workers;
std::deque<AsyncOps::ScheduledTask> AsyncOps::s_high_priority_queue;
std::deque<AsyncOps::ScheduledTask> AsyncOps::s_normal_priority_queue;
VOID AsyncOps::RunTask(IN Function<VOID()> task)
{
JoiningThread(task).detach();
auto AsyncOps::run_task(std::function<void()> task) -> void {
std::jthread(std::move(task)).detach();
}
auto AsyncOps::initialize_scheduler(u8 worker_count) -> Result<void> {
if (worker_count == 0) {
const auto hw_concurrency = std::thread::hardware_concurrency();
u32 threads = 2;
if (hw_concurrency > 2) {
threads = hw_concurrency - 2;
}
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);
if (threads > 255) {
threads = 255;
}
worker_count = static_cast<u8>(threads);
}
VOID AsyncOps::TerminateScheduler()
{
for (auto &w : s_scheduleWorkers)
{
w.request_stop();
for (u32 i = 0; i < worker_count; ++i) {
s_schedule_workers.emplace_back(schedule_worker_loop,
static_cast<WorkerId>(i + 1));
}
return {};
}
auto AsyncOps::terminate_scheduler() -> void {
for (auto &worker : s_schedule_workers) {
worker.request_stop();
}
s_wake_condition.notify_all();
for (auto &worker : s_schedule_workers) {
if (worker.joinable()) {
worker.join();
}
}
s_schedule_workers.clear();
}
auto AsyncOps::schedule_task(std::function<void(WorkerId worker_id)> task,
TaskTag tag, Schedule *schedule, Priority priority)
-> void {
ensure(!s_schedule_workers.empty(),
"Scheduler must be initialized before calling schedule_task");
schedule->counter.fetch_add(1);
{
std::lock_guard lock(s_queue_mutex);
if (priority == Priority::High) {
s_high_priority_queue.emplace_back(
ScheduledTask{tag, schedule, std::move(task)});
} else {
s_normal_priority_queue.emplace_back(
ScheduledTask{tag, schedule, std::move(task)});
}
}
s_wake_condition.notify_one();
}
auto AsyncOps::cancel_tasks_of_tag(TaskTag tag) -> void {
std::lock_guard lock(s_queue_mutex);
auto cancel_from_queue = [&](std::deque<ScheduledTask> &queue) {
for (auto it = queue.begin(); it != queue.end(); /* no increment */) {
if (it->tag == tag) {
if (it->schedule_handle->counter.fetch_sub(1) == 1) {
it->schedule_handle->counter.notify_all();
}
s_wakeCondition.notify_all();
for (auto &w : s_scheduleWorkers)
{
if (w.joinable())
{
w.join();
}
}
s_scheduleWorkers.clear();
it = queue.erase(it);
} else {
++it;
}
}
};
VOID AsyncOps::ScheduleTask(IN Function<VOID(IN WorkerID workerID)> task, IN TaskTag tag, IN Schedule *schedule,
IN Priority priority)
cancel_from_queue(s_high_priority_queue);
cancel_from_queue(s_normal_priority_queue);
}
auto AsyncOps::wait_for_schedule_completion(Schedule *schedule) -> void {
ensure(!s_schedule_workers.empty(), "Scheduler must be initialized before "
"calling wait_for_schedule_completion");
while (schedule->counter.load() > 0) {
ScheduledTask task;
bool found_task = false;
{
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();
std::unique_lock lock(s_queue_mutex);
if (!s_high_priority_queue.empty()) {
task = std::move(s_high_priority_queue.front());
s_high_priority_queue.pop_front();
found_task = true;
} else if (!s_normal_priority_queue.empty()) {
task = std::move(s_normal_priority_queue.front());
s_normal_priority_queue.pop_front();
found_task = true;
}
}
VOID AsyncOps::CancelTasksOfTag(IN TaskTag tag)
if (found_task) {
task.task(MAIN_THREAD_WORKER_ID);
if (task.schedule_handle->counter.fetch_sub(1) == 1) {
task.schedule_handle->counter.notify_all();
}
} else {
const auto current_val = schedule->counter.load();
if (current_val > 0) {
schedule->counter.wait(current_val);
}
}
}
}
auto AsyncOps::get_worker_count() -> WorkerId {
// +1 for MainThread (Work Stealing)
return static_cast<WorkerId>(s_schedule_workers.size() + 1);
}
auto AsyncOps::schedule_worker_loop(std::stop_token stop_token,
WorkerId worker_id) -> void {
while (!stop_token.stop_requested()) {
ScheduledTask task;
bool found_task = false;
{
ScopedLock lock(s_queueMutex);
std::unique_lock lock(s_queue_mutex);
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();
s_wake_condition.wait(lock, [&stop_token] {
return !s_high_priority_queue.empty() ||
!s_normal_priority_queue.empty() || stop_token.stop_requested();
});
it = queue.erase(it);
}
else
++it;
}
};
if (stop_token.stop_requested() && s_high_priority_queue.empty() &&
s_normal_priority_queue.empty()) {
return;
}
cancelFromQueue(s_highPriorityQueue);
cancelFromQueue(s_normalPriorityQueue);
if (!s_high_priority_queue.empty()) {
task = std::move(s_high_priority_queue.front());
s_high_priority_queue.pop_front();
found_task = true;
} else if (!s_normal_priority_queue.empty()) {
task = std::move(s_normal_priority_queue.front());
s_normal_priority_queue.pop_front();
found_task = true;
}
}
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);
}
}
if (found_task) {
task.task(worker_id);
if (task.schedule_handle->counter.fetch_sub(1) == 1) {
task.schedule_handle->counter.notify_all();
}
}
}
}
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

@ -17,9 +17,9 @@
namespace IACore
{
CLIParser::CLIParser(IN Span<CONST String> args) : m_argList(args)
CLIParser::CLIParser(Span<const String> args) : m_argList(args)
{
IA_RELEASE_ASSERT(args.size());
assert(args.size());
m_currentArg = m_argList.begin();

View File

@ -16,457 +16,458 @@
#include <IACore/DataOps.hpp>
#include <IACore/Platform.hpp>
#include <bit>
#include <cstring>
#include <zlib.h>
#include <zstd.h>
namespace IACore
{
template<typename T> INLINE T ReadUnaligned(IN PCUINT8 ptr)
{
T v;
std::memcpy(&v, ptr, sizeof(T));
return v;
}
struct CRC32Tables
{
UINT32 table[8][256] = {};
CONSTEVAL CRC32Tables()
{
CONSTEXPR UINT32 t = 0x82F63B78;
for (UINT32 i = 0; i < 256; i++)
{
UINT32 crc = i;
for (int j = 0; j < 8; j++)
crc = (crc >> 1) ^ ((crc & 1) ? t : 0);
table[0][i] = crc;
}
for (int i = 0; i < 256; i++)
{
for (int slice = 1; slice < 8; slice++)
{
UINT32 prev = table[slice - 1][i];
table[slice][i] = (prev >> 8) ^ table[0][prev & 0xFF];
}
}
}
};
STATIC CONSTEXPR CRC32Tables CRC32_TABLES{};
#if IA_ARCH_X64
INLINE UINT32 CRC32_x64_HW(IN Span<CONST UINT8> data)
{
CONST UINT8 *p = data.data();
UINT32 crc = 0xFFFFFFFF;
SIZE_T len = data.size();
while (len >= 8)
{
UINT64 chunk = ReadUnaligned<UINT64>(p);
crc = (UINT32) _mm_crc32_u64((UINT64) crc, chunk);
p += 8;
len -= 8;
}
while (len--)
crc = _mm_crc32_u8(crc, *p++);
return ~crc;
}
#include <immintrin.h>
#endif
#if IA_ARCH_ARM64
__attribute__((target("+crc"))) INLINE UINT32 CRC32_ARM64_HW(IN Span<CONST UINT8> data)
{
CONST UINT8 *p = data.data();
UINT32 crc = 0xFFFFFFFF;
SIZE_T len = data.size();
while (len >= 8)
{
UINT64 chunk = ReadUnaligned<UINT64>(p);
crc = __crc32cd(crc, chunk);
p += 8;
len -= 8;
}
while (len--)
crc = __crc32cb(crc, *p++);
return ~crc;
}
#include <arm_acle.h>
#endif
INLINE UINT32 CRC32_Software_Slice8(IN Span<CONST UINT8> data)
{
CONST UINT8 *p = data.data();
UINT32 crc = 0xFFFFFFFF;
SIZE_T len = data.size();
namespace IACore {
template <typename T>
[[nodiscard]] inline auto read_unaligned(const u8 *ptr) -> T {
T v;
std::memcpy(&v, ptr, sizeof(T));
return v;
}
while (len >= 8)
{
UINT32 term1 = crc ^ ReadUnaligned<UINT32>(p);
UINT32 term2 = ReadUnaligned<UINT32>(p + 4);
struct Crc32Tables {
u32 table[8][256] = {};
crc = CRC32_TABLES.table[7][term1 & 0xFF] ^ CRC32_TABLES.table[6][(term1 >> 8) & 0xFF] ^
CRC32_TABLES.table[5][(term1 >> 16) & 0xFF] ^ CRC32_TABLES.table[4][(term1 >> 24)] ^
CRC32_TABLES.table[3][term2 & 0xFF] ^ CRC32_TABLES.table[2][(term2 >> 8) & 0xFF] ^
CRC32_TABLES.table[1][(term2 >> 16) & 0xFF] ^ CRC32_TABLES.table[0][(term2 >> 24)];
consteval Crc32Tables() {
constexpr u32 T = 0x82F63B78;
p += 8;
len -= 8;
}
while (len--)
crc = (crc >> 8) ^ CRC32_TABLES.table[0][(crc ^ *p++) & 0xFF];
return ~crc;
for (u32 i = 0; i < 256; i++) {
u32 crc = i;
for (i32 j = 0; j < 8; j++) {
crc = (crc >> 1) ^ ((crc & 1) ? T : 0);
}
table[0][i] = crc;
}
UINT32 DataOps::CRC32(IN Span<CONST UINT8> data)
{
for (i32 i = 0; i < 256; i++) {
for (i32 slice = 1; slice < 8; slice++) {
const u32 prev = table[slice - 1][i];
table[slice][i] = (prev >> 8) ^ table[0][prev & 0xFF];
}
}
}
};
static constexpr Crc32Tables CRC32_TABLES{};
#if IA_ARCH_X64
// IACore mandates AVX2 so no need to check
// for Platform::GetCapabilities().HardwareCRC32
return CRC32_x64_HW(data);
#elif IA_ARCH_ARM64
if (Platform::GetCapabilities().HardwareCRC32)
return CRC32_ARM64_HW(data);
inline auto crc32_x64_hw(Span<const u8> data) -> u32 {
const u8 *p = data.data();
u32 crc = 0xFFFFFFFF;
usize len = data.size();
while (len >= 8) {
const u64 chunk = read_unaligned<u64>(p);
crc = static_cast<u32>(_mm_crc32_u64(static_cast<u64>(crc), chunk));
p += 8;
len -= 8;
}
while (len--) {
crc = _mm_crc32_u8(crc, *p++);
}
return ~crc;
}
#endif
return CRC32_Software_Slice8(data);
}
} // namespace IACore
namespace IACore
{
CONSTEXPR UINT32 XXH_PRIME32_1 = 0x9E3779B1U;
CONSTEXPR UINT32 XXH_PRIME32_2 = 0x85EBCA77U;
CONSTEXPR UINT32 XXH_PRIME32_3 = 0xC2B2AE3DU;
CONSTEXPR UINT32 XXH_PRIME32_4 = 0x27D4EB2FU;
CONSTEXPR UINT32 XXH_PRIME32_5 = 0x165667B1U;
#if IA_ARCH_ARM64
__attribute__((target("+crc"))) inline auto crc32_arm64_hw(Span<const u8> data)
-> u32 {
const u8 *p = data.data();
INLINE UINT32 XXH32_Round(IN UINT32 seed, IN UINT32 input)
{
seed += input * XXH_PRIME32_2;
seed = std::rotl(seed, 13);
seed *= XXH_PRIME32_1;
return seed;
u32 crc = 0xFFFFFFFF;
usize len = data.size();
while (len >= 8) {
const u64 chunk = read_unaligned<u64>(p);
crc = __crc32cd(crc, chunk);
p += 8;
len -= 8;
}
while (len--) {
crc = __crc32cb(crc, *p++);
}
return ~crc;
}
#endif
inline auto crc32_software_slice8(Span<const u8> data) -> u32 {
const u8 *p = data.data();
u32 crc = 0xFFFFFFFF;
usize len = data.size();
while (len >= 8) {
const u32 term1 = crc ^ read_unaligned<u32>(p);
const u32 term2 = read_unaligned<u32>(p + 4);
crc = CRC32_TABLES.table[7][term1 & 0xFF] ^
CRC32_TABLES.table[6][(term1 >> 8) & 0xFF] ^
CRC32_TABLES.table[5][(term1 >> 16) & 0xFF] ^
CRC32_TABLES.table[4][(term1 >> 24)] ^
CRC32_TABLES.table[3][term2 & 0xFF] ^
CRC32_TABLES.table[2][(term2 >> 8) & 0xFF] ^
CRC32_TABLES.table[1][(term2 >> 16) & 0xFF] ^
CRC32_TABLES.table[0][(term2 >> 24)];
p += 8;
len -= 8;
}
while (len--) {
crc = (crc >> 8) ^ CRC32_TABLES.table[0][(crc ^ *p++) & 0xFF];
}
return ~crc;
}
auto DataOps::crc32(Span<const u8> data) -> u32 {
#if IA_ARCH_X64
// IACore mandates AVX2 so no need to check
// for Platform::GetCapabilities().HardwareCRC32
return crc32_x64_hw(data);
#elif IA_ARCH_ARM64
if (Platform::GetCapabilities().HardwareCRC32) {
return crc32_arm64_hw(data);
}
#endif
return crc32_software_slice8(data);
}
// =============================================================================
// xxHash
// =============================================================================
constexpr u32 XXH_PRIME32_1 = 0x9E3779B1U;
constexpr u32 XXH_PRIME32_2 = 0x85EBCA77U;
constexpr u32 XXH_PRIME32_3 = 0xC2B2AE3DU;
constexpr u32 XXH_PRIME32_4 = 0x27D4EB2FU;
constexpr u32 XXH_PRIME32_5 = 0x165667B1U;
inline auto xxh32_round(u32 seed, u32 input) -> u32 {
seed += input * XXH_PRIME32_2;
seed = std::rotl(seed, 13);
seed *= XXH_PRIME32_1;
return seed;
}
auto DataOps::hash_xxhash(const String &string, u32 seed) -> u32 {
return hash_xxhash(Span<const u8>(reinterpret_cast<const u8 *>(string.data()),
string.length()),
seed);
}
auto DataOps::hash_xxhash(Span<const u8> data, u32 seed) -> u32 {
const u8 *p = data.data();
const u8 *const b_end = p + data.size();
u32 h32{};
if (data.size() >= 16) {
const u8 *const limit = b_end - 16;
u32 v1 = seed + XXH_PRIME32_1 + XXH_PRIME32_2;
u32 v2 = seed + XXH_PRIME32_2;
u32 v3 = seed + 0;
u32 v4 = seed - XXH_PRIME32_1;
do {
v1 = xxh32_round(v1, read_unaligned<u32>(p));
p += 4;
v2 = xxh32_round(v2, read_unaligned<u32>(p));
p += 4;
v3 = xxh32_round(v3, read_unaligned<u32>(p));
p += 4;
v4 = xxh32_round(v4, read_unaligned<u32>(p));
p += 4;
} while (p <= limit);
h32 = std::rotl(v1, 1) + std::rotl(v2, 7) + std::rotl(v3, 12) +
std::rotl(v4, 18);
} else {
h32 = seed + XXH_PRIME32_5;
}
h32 += static_cast<u32>(data.size());
while (p + 4 <= b_end) {
const auto t = read_unaligned<u32>(p) * XXH_PRIME32_3;
h32 += t;
h32 = std::rotl(h32, 17) * XXH_PRIME32_4;
p += 4;
}
while (p < b_end) {
h32 += (*p++) * XXH_PRIME32_5;
h32 = std::rotl(h32, 11) * XXH_PRIME32_1;
}
h32 ^= h32 >> 15;
h32 *= XXH_PRIME32_2;
h32 ^= h32 >> 13;
h32 *= XXH_PRIME32_3;
h32 ^= h32 >> 16;
return h32;
}
// =============================================================================
// FNV-1a
// =============================================================================
constexpr u32 FNV1A_32_PRIME = 0x01000193;
constexpr u32 FNV1A_32_OFFSET = 0x811c9dc5;
auto DataOps::hash_fnv1a(const String &string) -> u32 {
u32 hash = FNV1A_32_OFFSET;
for (const char c : string) {
hash ^= static_cast<u8>(c);
hash *= FNV1A_32_PRIME;
}
return hash;
}
auto DataOps::hash_fnv1a(Span<const u8> data) -> u32 {
u32 hash = FNV1A_32_OFFSET;
const auto *ptr = data.data();
for (usize i = 0; i < data.size(); ++i) {
hash ^= ptr[i];
hash *= FNV1A_32_PRIME;
}
return hash;
}
// =============================================================================
// Compression
// =============================================================================
auto DataOps::detect_compression(Span<const u8> data) -> CompressionType {
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;
}
auto DataOps::zlib_inflate(Span<const u8> data) -> Result<Vec<u8>> {
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 fail("Failed to initialize zlib inflate");
}
zs.next_in = const_cast<Bytef *>(data.data());
zs.avail_in = static_cast<uInt>(data.size());
Vec<u8> out_buffer;
// Start with 2x input size.
const usize guess_size =
data.size() < 1024 ? data.size() * 4 : data.size() * 2;
out_buffer.resize(guess_size);
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data());
zs.avail_out = static_cast<uInt>(out_buffer.size());
int ret;
do {
if (zs.avail_out == 0) {
const usize current_pos = zs.total_out;
const usize new_size = out_buffer.size() * 2;
out_buffer.resize(new_size);
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data() + current_pos);
zs.avail_out = static_cast<uInt>(new_size - current_pos);
}
UINT32 DataOps::Hash_xxHash(IN CONST String &string, IN UINT32 seed)
{
return Hash_xxHash(Span<CONST UINT8>(reinterpret_cast<PCUINT8>(string.data()), string.length()), seed);
ret = inflate(&zs, Z_NO_FLUSH);
} while (ret == Z_OK);
inflateEnd(&zs);
if (ret != Z_STREAM_END) {
return fail("Failed to inflate: corrupt data or stream error");
}
out_buffer.resize(zs.total_out);
return out_buffer;
}
auto DataOps::zlib_deflate(Span<const u8> data) -> Result<Vec<u8>> {
z_stream zs{};
zs.zalloc = Z_NULL;
zs.zfree = Z_NULL;
zs.opaque = Z_NULL;
if (deflateInit(&zs, Z_DEFAULT_COMPRESSION) != Z_OK) {
return fail("Failed to initialize zlib deflate");
}
zs.next_in = const_cast<Bytef *>(data.data());
zs.avail_in = static_cast<uInt>(data.size());
Vec<u8> out_buffer;
out_buffer.resize(deflateBound(&zs, static_cast<uLong>(data.size())));
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data());
zs.avail_out = static_cast<uInt>(out_buffer.size());
const int ret = deflate(&zs, Z_FINISH);
if (ret != Z_STREAM_END) {
deflateEnd(&zs);
return fail("Failed to deflate, ran out of buffer memory");
}
out_buffer.resize(zs.total_out);
deflateEnd(&zs);
return out_buffer;
}
auto DataOps::zstd_inflate(Span<const u8> data) -> Result<Vec<u8>> {
const unsigned long long content_size =
ZSTD_getFrameContentSize(data.data(), data.size());
if (content_size == ZSTD_CONTENTSIZE_ERROR) {
return fail("Failed to inflate: Not valid ZSTD compressed data");
}
if (content_size != ZSTD_CONTENTSIZE_UNKNOWN) {
// FAST PATH: We know the size
Vec<u8> out_buffer;
out_buffer.resize(static_cast<usize>(content_size));
const usize d_size = ZSTD_decompress(out_buffer.data(), out_buffer.size(),
data.data(), data.size());
if (ZSTD_isError(d_size)) {
return fail("Failed to inflate: {}", ZSTD_getErrorName(d_size));
}
UINT32 DataOps::Hash_xxHash(IN Span<CONST UINT8> data, IN UINT32 seed)
{
CONST UINT8 *p = data.data();
CONST UINT8 *CONST bEnd = p + data.size();
UINT32 h32{};
return out_buffer;
}
if (data.size() >= 16)
{
const UINT8 *const limit = bEnd - 16;
ZSTD_DCtx *dctx = ZSTD_createDCtx();
Vec<u8> out_buffer;
out_buffer.resize(data.size() * 2);
UINT32 v1 = seed + XXH_PRIME32_1 + XXH_PRIME32_2;
UINT32 v2 = seed + XXH_PRIME32_2;
UINT32 v3 = seed + 0;
UINT32 v4 = seed - XXH_PRIME32_1;
ZSTD_inBuffer input = {data.data(), data.size(), 0};
ZSTD_outBuffer output = {out_buffer.data(), out_buffer.size(), 0};
do
{
v1 = XXH32_Round(v1, ReadUnaligned<UINT32>(p));
p += 4;
v2 = XXH32_Round(v2, ReadUnaligned<UINT32>(p));
p += 4;
v3 = XXH32_Round(v3, ReadUnaligned<UINT32>(p));
p += 4;
v4 = XXH32_Round(v4, ReadUnaligned<UINT32>(p));
p += 4;
} while (p <= limit);
usize ret;
do {
ret = ZSTD_decompressStream(dctx, &output, &input);
h32 = std::rotl(v1, 1) + std::rotl(v2, 7) + std::rotl(v3, 12) + std::rotl(v4, 18);
}
else
h32 = seed + XXH_PRIME32_5;
h32 += (UINT32) data.size();
while (p + 4 <= bEnd)
{
const auto t = ReadUnaligned<UINT32>(p) * XXH_PRIME32_3;
h32 += t;
h32 = std::rotl(h32, 17) * XXH_PRIME32_4;
p += 4;
}
while (p < bEnd)
{
h32 += (*p++) * XXH_PRIME32_5;
h32 = std::rotl(h32, 11) * XXH_PRIME32_1;
}
h32 ^= h32 >> 15;
h32 *= XXH_PRIME32_2;
h32 ^= h32 >> 13;
h32 *= XXH_PRIME32_3;
h32 ^= h32 >> 16;
return h32;
}
} // namespace IACore
namespace IACore
{
// FNV-1a 32-bit Constants
CONSTEXPR UINT32 FNV1A_32_PRIME = 0x01000193;
CONSTEXPR UINT32 FNV1A_32_OFFSET = 0x811c9dc5;
UINT32 DataOps::Hash_FNV1A(IN CONST String &string)
{
UINT32 hash = FNV1A_32_OFFSET;
for (char c : string)
{
hash ^= static_cast<uint8_t>(c);
hash *= FNV1A_32_PRIME;
}
return hash;
if (ZSTD_isError(ret)) {
ZSTD_freeDCtx(dctx);
return fail("Failed to inflate: {}", ZSTD_getErrorName(ret));
}
UINT32 DataOps::Hash_FNV1A(IN Span<CONST UINT8> data)
{
UINT32 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;
}
} // namespace IACore
namespace IACore
{
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;
if (output.pos == output.size) {
const usize new_size = out_buffer.size() * 2;
out_buffer.resize(new_size);
output.dst = out_buffer.data();
output.size = new_size;
}
EXPECT(Vector<UINT8>) DataOps::ZlibInflate(IN Span<CONST UINT8> data)
{
z_stream zs{};
zs.zalloc = Z_NULL;
zs.zfree = Z_NULL;
zs.opaque = Z_NULL;
} while (ret != 0);
// 15 + 32 = Auto-detect Gzip or Zlib
if (inflateInit2(&zs, 15 + 32) != Z_OK)
return MakeUnexpected("Failed to initialize zlib inflate");
out_buffer.resize(output.pos);
ZSTD_freeDCtx(dctx);
zs.next_in = const_cast<Bytef *>(data.data());
zs.avail_in = static_cast<uInt>(data.size());
return out_buffer;
}
Vector<UINT8> outBuffer;
// Start with 2x input size.
size_t guessSize = data.size() < 1024 ? data.size() * 4 : data.size() * 2;
outBuffer.resize(guessSize);
auto DataOps::zstd_deflate(Span<const u8> data) -> Result<Vec<u8>> {
const usize max_dst_size = ZSTD_compressBound(data.size());
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data());
zs.avail_out = static_cast<uInt>(outBuffer.size());
Vec<u8> out_buffer;
out_buffer.resize(max_dst_size);
int ret;
do
{
if (zs.avail_out == 0)
{
size_t currentPos = zs.total_out;
const usize compressed_size = ZSTD_compress(out_buffer.data(), max_dst_size,
data.data(), data.size(), 3);
size_t newSize = outBuffer.size() * 2;
outBuffer.resize(newSize);
if (ZSTD_isError(compressed_size)) {
return fail("Failed to deflate: {}", ZSTD_getErrorName(compressed_size));
}
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data() + currentPos);
out_buffer.resize(compressed_size);
return out_buffer;
}
zs.avail_out = static_cast<uInt>(newSize - currentPos);
}
auto DataOps::gzip_deflate(Span<const u8> data) -> Result<Vec<u8>> {
z_stream zs{};
zs.zalloc = Z_NULL;
zs.zfree = Z_NULL;
zs.opaque = Z_NULL;
ret = inflate(&zs, Z_NO_FLUSH);
// 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 fail("Failed to initialize gzip deflate");
}
} while (ret == Z_OK);
zs.next_in = const_cast<Bytef *>(data.data());
zs.avail_in = static_cast<uInt>(data.size());
inflateEnd(&zs);
Vec<u8> out_buffer;
if (ret != Z_STREAM_END)
return MakeUnexpected("Failed to inflate: corrupt data or stream error");
out_buffer.resize(deflateBound(&zs, static_cast<uLong>(data.size())) +
1024); // Additional 1KB buffer for safety
outBuffer.resize(zs.total_out);
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data());
zs.avail_out = static_cast<uInt>(out_buffer.size());
return outBuffer;
}
const int ret = deflate(&zs, Z_FINISH);
EXPECT(Vector<UINT8>) DataOps::ZlibDeflate(IN Span<CONST UINT8> data)
{
z_stream zs{};
zs.zalloc = Z_NULL;
zs.zfree = Z_NULL;
zs.opaque = Z_NULL;
if (ret != Z_STREAM_END) {
deflateEnd(&zs);
return fail("Failed to deflate");
}
if (deflateInit(&zs, Z_DEFAULT_COMPRESSION) != Z_OK)
return MakeUnexpected("Failed to initialize zlib deflate");
out_buffer.resize(zs.total_out);
zs.next_in = const_cast<Bytef *>(data.data());
zs.avail_in = static_cast<uInt>(data.size());
deflateEnd(&zs);
return out_buffer;
}
Vector<UINT8> outBuffer;
auto DataOps::gzip_inflate(Span<const u8> data) -> Result<Vec<u8>> {
return zlib_inflate(data);
}
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;
}
EXPECT(Vector<UINT8>) 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;
}
EXPECT(Vector<UINT8>) 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;
}
EXPECT(Vector<UINT8>) 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;
}
EXPECT(Vector<UINT8>) DataOps::GZipInflate(IN Span<CONST UINT8> data)
{
return ZlibInflate(data);
}
} // namespace IACore

View File

@ -14,501 +14,523 @@
// limitations under the License.
#include <IACore/FileOps.hpp>
#include <cerrno>
#include <cstdio>
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
}
EXPECT(PUINT8) 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
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#endif
namespace IACore {
HashMap<const u8 *, std::tuple<void *, void *, void *>> FileOps::s_mapped_files;
auto FileOps::unmap_file(const u8 *mapped_ptr) -> void {
if (!s_mapped_files.contains(mapped_ptr)) {
return;
}
auto it = s_mapped_files.find(mapped_ptr);
const auto handles = it->second;
s_mapped_files.erase(it);
#if IA_PLATFORM_WINDOWS
::UnmapViewOfFile(std::get<1>(handles));
::CloseHandle(static_cast<HANDLE>(std::get<2>(handles)));
const auto handle = static_cast<HANDLE>(std::get<0>(handles));
if (handle != INVALID_HANDLE_VALUE) {
::CloseHandle(handle);
}
#elif IA_PLATFORM_UNIX
::munmap(std::get<1>(handles), (usize)std::get<2>(handles));
const auto fd = (i32)((u64)std::get<0>(handles));
if (fd != -1) {
::close(fd);
}
#endif
}
auto FileOps::map_shared_memory(const String &name, usize size, bool is_owner)
-> Result<u8 *> {
#if IA_PLATFORM_WINDOWS
const int wchars_num =
MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, NULL, 0);
std::wstring w_name(wchars_num, 0);
MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, &w_name[0], wchars_num);
HANDLE h_map = NULL;
if (is_owner) {
h_map = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
(DWORD)(size >> 32), (DWORD)(size & 0xFFFFFFFF),
w_name.c_str());
} else {
h_map = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, w_name.c_str());
}
if (h_map == NULL) {
return fail("Failed to {} shared memory '{}'",
is_owner ? "owner" : "consumer", name);
}
auto *result =
static_cast<u8 *>(MapViewOfFile(h_map, FILE_MAP_ALL_ACCESS, 0, 0, size));
if (result == NULL) {
CloseHandle(h_map);
return fail("Failed to map view of shared memory '{}'", name);
}
s_mapped_files[result] = std::make_tuple((void *)INVALID_HANDLE_VALUE,
(void *)result, (void *)h_map);
return result;
#elif IA_PLATFORM_UNIX
int fd = -1;
if (is_owner) {
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());
#endif
return fail("Failed to truncate shared memory '{}'", name);
}
}
} else {
fd = shm_open(name.c_str(), O_RDWR, 0666);
}
EXPECT(PCUINT8) FileOps::MapFile(IN CONST FilePath &path, OUT SIZE_T &size)
{
if (fd == -1) {
return fail("Failed to {} shared memory '{}'",
is_owner ? "owner" : "consumer", name);
}
void *addr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
close(fd);
return fail("Failed to mmap shared memory '{}'", name);
}
auto *result = static_cast<u8 *>(addr);
s_mapped_files[result] =
std::make_tuple((void *)((u64)fd), (void *)addr, (void *)size);
return result;
#endif
}
auto FileOps::unlink_shared_memory(const String &name) -> void {
if (name.empty()) {
return;
}
#if IA_PLATFORM_UNIX
shm_unlink(name.c_str());
#endif
}
auto FileOps::map_file(const Path &path, usize &size) -> Result<const u8 *> {
#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);
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 fail("Failed to open {} for memory mapping", path.string());
}
if (handle == INVALID_HANDLE_VALUE)
return MakeUnexpected(std::format("Failed to open {} for memory mapping", path.string().c_str()));
LARGE_INTEGER file_size;
if (!GetFileSizeEx(handle, &file_size)) {
CloseHandle(handle);
return fail("Failed to get size of {} for memory mapping", path.string());
}
size = static_cast<usize>(file_size.QuadPart);
if (size == 0) {
CloseHandle(handle);
return fail("Failed to get size of {} for memory mapping", path.string());
}
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 h_map = CreateFileMappingW(handle, NULL, PAGE_READONLY, 0, 0, NULL);
if (h_map == NULL) {
CloseHandle(handle);
return fail("Failed to memory map {}", path.string());
}
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;
const auto *result =
static_cast<const u8 *>(MapViewOfFile(h_map, FILE_MAP_READ, 0, 0, 0));
if (result == NULL) {
CloseHandle(handle);
CloseHandle(h_map);
return fail("Failed to memory map {}", path.string());
}
s_mapped_files[result] = std::make_tuple(
(void *)handle, (void *)const_cast<u8 *>(result), (void *)h_map);
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;
const auto handle = open(path.string().c_str(), O_RDONLY);
if (handle == -1) {
return fail("Failed to open {} for memory mapping", path.string());
}
struct stat sb;
if (fstat(handle, &sb) == -1) {
close(handle);
return fail("Failed to get stats of {} for memory mapping", path.string());
}
size = static_cast<usize>(sb.st_size);
if (size == 0) {
close(handle);
return fail("Failed to get size of {} for memory mapping", path.string());
}
void *addr = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, handle, 0);
if (addr == MAP_FAILED) {
close(handle);
return fail("Failed to memory map {}", path.string());
}
const auto *result = static_cast<const u8 *>(addr);
madvise(addr, size, MADV_SEQUENTIAL);
s_mapped_files[result] =
std::make_tuple((void *)((u64)handle), (void *)addr, (void *)size);
return result;
#endif
}
}
EXPECT(StreamWriter) 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);
}
auto FileOps::stream_to_file(const Path &path, bool overwrite)
-> Result<StreamWriter> {
if (!overwrite && std::filesystem::exists(path)) {
return fail("File already exists: {}", path.string());
}
return StreamWriter::create(path);
}
EXPECT(StreamReader) 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);
}
auto FileOps::stream_from_file(const Path &path) -> Result<StreamReader> {
if (!std::filesystem::exists(path)) {
return fail("File does not exist: {}", path.string());
}
return StreamReader::create_from_file(path);
}
EXPECT(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;
}
auto FileOps::read_text_file(const Path &path) -> Result<String> {
auto *f = fopen(path.string().c_str(), "r");
if (!f) {
return fail("Failed to open file: {}", path.string());
}
String result;
fseek(f, 0, SEEK_END);
const long len = ftell(f);
if (len > 0) {
result.resize(static_cast<usize>(len));
fseek(f, 0, SEEK_SET);
fread(result.data(), 1, result.size(), f);
}
fclose(f);
return result;
}
EXPECT(Vector<UINT8>) 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;
}
auto FileOps::read_binary_file(const Path &path) -> Result<Vec<u8>> {
auto *f = fopen(path.string().c_str(), "rb");
if (!f) {
return fail("Failed to open file: {}", path.string());
}
Vec<u8> result;
fseek(f, 0, SEEK_END);
const long len = ftell(f);
if (len > 0) {
result.resize(static_cast<usize>(len));
fseek(f, 0, SEEK_SET);
fread(result.data(), 1, result.size(), f);
}
fclose(f);
return result;
}
EXPECT(SIZE_T) 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);
fclose(f);
return result;
auto FileOps::write_text_file(const Path &path, const String &contents,
bool overwrite) -> Result<usize> {
const char *mode = overwrite ? "w" : "wx";
auto *f = fopen(path.string().c_str(), mode);
if (!f) {
if (!overwrite && errno == EEXIST) {
return fail("File already exists: {}", path.string());
}
return fail("Failed to write to file: {}", path.string());
}
const auto result = fwrite(contents.data(), 1, contents.size(), f);
fclose(f);
return result;
}
EXPECT(SIZE_T) 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;
auto FileOps::write_binary_file(const Path &path, Span<const u8> contents,
bool overwrite) -> Result<usize> {
const char *mode = overwrite ? "w" : "wx";
auto *f = fopen(path.string().c_str(), mode);
if (!f) {
if (!overwrite && errno == EEXIST) {
return fail("File already exists: {}", path.string());
}
return fail("Failed to write to file: {}", path.string());
}
const auto result = fwrite(contents.data(), 1, contents.size(), f);
fclose(f);
return result;
}
FilePath FileOps::NormalizeExecutablePath(IN CONST FilePath &path)
{
FilePath result = path;
auto FileOps::normalize_executable_path(const Path &path) -> Path {
Path result = path;
#if IA_PLATFORM_WINDOWS
if (!result.has_extension())
result.replace_extension(".exe");
if (!result.has_extension()) {
result.replace_extension(".exe");
}
#elif IA_PLATFORM_UNIX
if (result.extension() == ".exe") {
result.replace_extension("");
}
if (result.is_relative()) {
String path_str = result.string();
if (!path_str.starts_with("./") && !path_str.starts_with("../")) {
result = "./" + path_str;
}
}
#endif
return result;
}
auto FileOps::native_open_file(const Path &path, FileAccess access,
FileMode mode, u32 permissions)
-> Result<NativeFileHandle> {
#if IA_PLATFORM_WINDOWS
DWORD dw_access = 0;
DWORD dw_share = FILE_SHARE_READ;
DWORD dw_disposition = 0;
DWORD dw_flags_and_attributes = FILE_ATTRIBUTE_NORMAL;
switch (access) {
case FileAccess::Read:
dw_access = GENERIC_READ;
break;
case FileAccess::Write:
dw_access = GENERIC_WRITE;
break;
case FileAccess::ReadWrite:
dw_access = GENERIC_READ | GENERIC_WRITE;
break;
}
switch (mode) {
case FileMode::OpenExisting:
dw_disposition = OPEN_EXISTING;
break;
case FileMode::OpenAlways:
dw_disposition = OPEN_ALWAYS;
break;
case FileMode::CreateNew:
dw_disposition = CREATE_NEW;
break;
case FileMode::CreateAlways:
dw_disposition = CREATE_ALWAYS;
break;
case FileMode::TruncateExisting:
dw_disposition = TRUNCATE_EXISTING;
break;
}
HANDLE h_file = CreateFileA(path.string().c_str(), dw_access, dw_share, NULL,
dw_disposition, dw_flags_and_attributes, NULL);
if (h_file == INVALID_HANDLE_VALUE) {
return fail("Failed to open file '{}': {}", path.string(), GetLastError());
}
return h_file;
#elif IA_PLATFORM_UNIX
if (result.extension() == ".exe")
result.replace_extension("");
int flags = 0;
if (result.is_relative())
{
String pathStr = result.string();
if (!pathStr.starts_with("./") && !pathStr.starts_with("../"))
result = "./" + pathStr;
}
switch (access) {
case FileAccess::Read:
flags = O_RDONLY;
break;
case FileAccess::Write:
flags = O_WRONLY;
break;
case FileAccess::ReadWrite:
flags = O_RDWR;
break;
}
switch (mode) {
case FileMode::OpenExisting:
break;
case FileMode::OpenAlways:
flags |= O_CREAT;
break;
case FileMode::CreateNew:
flags |= O_CREAT | O_EXCL;
break;
case FileMode::CreateAlways:
flags |= O_CREAT | O_TRUNC;
break;
case FileMode::TruncateExisting:
flags |= O_TRUNC;
break;
}
int fd = open(path.string().c_str(), flags, permissions);
if (fd == -1) {
return fail("Failed to open file '{}': {}", path.string(), errno);
}
return fd;
#endif
return result;
}
} // namespace IACore
}
auto FileOps::native_close_file(NativeFileHandle handle) -> void {
if (handle == INVALID_FILE_HANDLE) {
return;
}
namespace IACore
{
EXPECT(NativeFileHandle)
FileOps::NativeOpenFile(IN CONST FilePath &path, IN EFileAccess access, IN EFileMode mode, IN UINT32 permissions)
{
#if IA_PLATFORM_WINDOWS
DWORD dwAccess = 0;
DWORD dwShare = FILE_SHARE_READ;
DWORD dwDisposition = 0;
DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
CloseHandle(handle);
#elif IA_PLATFORM_UNIX
close(handle);
#endif
}
switch (access)
{
case EFileAccess::READ:
dwAccess = GENERIC_READ;
break;
case EFileAccess::WRITE:
dwAccess = GENERIC_WRITE;
break;
case EFileAccess::READ_WRITE:
dwAccess = GENERIC_READ | GENERIC_WRITE;
break;
}
// =============================================================================
// MemoryMappedRegion
// =============================================================================
switch (mode)
{
case EFileMode::OPEN_EXISTING:
dwDisposition = OPEN_EXISTING;
break;
case EFileMode::OPEN_ALWAYS:
dwDisposition = OPEN_ALWAYS;
break;
case EFileMode::CREATE_NEW:
dwDisposition = CREATE_NEW;
break;
case EFileMode::CREATE_ALWAYS:
dwDisposition = CREATE_ALWAYS;
break;
case EFileMode::TRUNCATE_EXISTING:
dwDisposition = TRUNCATE_EXISTING;
break;
}
FileOps::MemoryMappedRegion::~MemoryMappedRegion() { unmap(); }
HANDLE hFile =
CreateFileA(path.string().c_str(), dwAccess, dwShare, NULL, dwDisposition, dwFlagsAndAttributes, NULL);
FileOps::MemoryMappedRegion::MemoryMappedRegion(
MemoryMappedRegion &&other) noexcept {
*this = std::move(other);
}
if (hFile == INVALID_HANDLE_VALUE)
return MakeUnexpected(std::format("Failed to open file '{}': {}", path.string(), GetLastError()));
auto FileOps::MemoryMappedRegion::operator=(MemoryMappedRegion &&other) noexcept
-> MemoryMappedRegion & {
if (this != &other) {
unmap();
m_ptr = other.m_ptr;
m_size = other.m_size;
#if IA_PLATFORM_WINDOWS
m_map_handle = other.m_map_handle;
other.m_map_handle = NULL;
#endif
other.m_ptr = nullptr;
other.m_size = 0;
}
return *this;
}
return hFile;
auto FileOps::MemoryMappedRegion::map(NativeFileHandle handle, u64 offset,
usize size) -> Result<void> {
unmap();
if (handle == INVALID_FILE_HANDLE) {
return fail("Invalid file handle provided to Map");
}
if (size == 0) {
return fail("Cannot map region of size 0");
}
#if IA_PLATFORM_WINDOWS
LARGE_INTEGER file_size;
if (!GetFileSizeEx(handle, &file_size)) {
return fail("Failed to get file size");
}
u64 end_offset = offset + size;
if (static_cast<u64>(file_size.QuadPart) < end_offset) {
LARGE_INTEGER new_size;
new_size.QuadPart = static_cast<LONGLONG>(end_offset);
if (!SetFilePointerEx(handle, new_size, NULL, FILE_BEGIN)) {
return fail("Failed to seek to new end of file");
}
if (!SetEndOfFile(handle)) {
return fail("Failed to extend file for mapping");
}
}
m_map_handle = CreateFileMappingW(handle, NULL, PAGE_READWRITE, 0, 0, NULL);
if (m_map_handle == NULL) {
return fail("CreateFileMapping failed: {}", GetLastError());
}
DWORD offset_high = static_cast<DWORD>(offset >> 32);
DWORD offset_low = static_cast<DWORD>(offset & 0xFFFFFFFF);
m_ptr = static_cast<u8 *>(MapViewOfFile(m_map_handle, FILE_MAP_WRITE,
offset_high, offset_low, size));
if (m_ptr == NULL) {
CloseHandle(m_map_handle);
m_map_handle = NULL;
return fail("MapViewOfFile failed (Offset: {}, Size: {}): {}", offset, size,
GetLastError());
}
m_size = size;
#elif IA_PLATFORM_UNIX
int flags = 0;
struct stat sb;
if (fstat(handle, &sb) == -1) {
return fail("Failed to fstat file");
}
switch (access)
{
case EFileAccess::READ:
flags = O_RDONLY;
break;
case EFileAccess::WRITE:
flags = O_WRONLY;
break;
case EFileAccess::READ_WRITE:
flags = O_RDWR;
break;
}
switch (mode)
{
case EFileMode::OPEN_EXISTING:
break;
case EFileMode::OPEN_ALWAYS:
flags |= O_CREAT;
break;
case EFileMode::CREATE_NEW:
flags |= O_CREAT | O_EXCL;
break;
case EFileMode::CREATE_ALWAYS:
flags |= O_CREAT | O_TRUNC;
break;
case EFileMode::TRUNCATE_EXISTING:
flags |= O_TRUNC;
break;
}
int fd = open(path.string().c_str(), flags, permissions);
if (fd == -1)
{
return MakeUnexpected(std::format("Failed to open file '{}': {}", path.string(), errno));
}
return fd;
#endif
u64 end_offset = offset + size;
if (static_cast<u64>(sb.st_size) < end_offset) {
if (ftruncate(handle, static_cast<off_t>(end_offset)) == -1) {
return fail("Failed to ftruncate (extend) file");
}
}
VOID FileOps::NativeCloseFile(IN NativeFileHandle handle)
{
if (handle == INVALID_FILE_HANDLE)
return;
void *ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, handle,
static_cast<off_t>(offset));
if (ptr == MAP_FAILED) {
return fail("mmap failed: {}", errno);
}
m_ptr = static_cast<u8 *>(ptr);
m_size = size;
madvise(m_ptr, m_size, MADV_SEQUENTIAL);
#endif
return {};
}
auto FileOps::MemoryMappedRegion::unmap() -> void {
if (!m_ptr) {
return;
}
#if IA_PLATFORM_WINDOWS
CloseHandle(handle);
UnmapViewOfFile(m_ptr);
if (m_map_handle) {
CloseHandle(m_map_handle);
m_map_handle = NULL;
}
#elif IA_PLATFORM_UNIX
close(handle);
munmap(m_ptr, m_size);
#endif
}
m_ptr = nullptr;
m_size = 0;
}
FileOps::MemoryMappedRegion::~MemoryMappedRegion()
{
Unmap();
}
FileOps::MemoryMappedRegion::MemoryMappedRegion(MemoryMappedRegion &&other) NOEXCEPT
{
*this = std::move(other);
}
FileOps::MemoryMappedRegion &FileOps::MemoryMappedRegion::operator=(MemoryMappedRegion &&other) NOEXCEPT
{
if (this != &other)
{
Unmap();
m_ptr = other.m_ptr;
m_size = other.m_size;
#if IA_PLATFORM_WINDOWS
m_hMap = other.m_hMap;
other.m_hMap = NULL;
#endif
other.m_ptr = nullptr;
other.m_size = 0;
}
return *this;
}
EXPECT(VOID) FileOps::MemoryMappedRegion::Map(NativeFileHandle handle, UINT64 offset, SIZE_T size)
{
Unmap();
if (handle == INVALID_FILE_HANDLE)
return MakeUnexpected("Invalid file handle provided to Map");
if (size == 0)
return MakeUnexpected("Cannot map region of size 0");
auto FileOps::MemoryMappedRegion::flush() -> void {
if (!m_ptr) {
return;
}
#if IA_PLATFORM_WINDOWS
LARGE_INTEGER fileSize;
if (!GetFileSizeEx(handle, &fileSize))
return MakeUnexpected("Failed to get file size");
UINT64 endOffset = offset + size;
if (static_cast<UINT64>(fileSize.QuadPart) < endOffset)
{
LARGE_INTEGER newSize;
newSize.QuadPart = endOffset;
if (!SetFilePointerEx(handle, newSize, NULL, FILE_BEGIN))
return MakeUnexpected("Failed to seek to new end of file");
if (!SetEndOfFile(handle))
return MakeUnexpected("Failed to extend file for mapping");
}
m_hMap = CreateFileMappingW(handle, NULL, PAGE_READWRITE, 0, 0, NULL);
if (m_hMap == NULL)
return MakeUnexpected(std::format("CreateFileMapping failed: {}", GetLastError()));
DWORD offsetHigh = static_cast<DWORD>(offset >> 32);
DWORD offsetLow = static_cast<DWORD>(offset & 0xFFFFFFFF);
m_ptr = static_cast<PUINT8>(MapViewOfFile(m_hMap, FILE_MAP_WRITE, offsetHigh, offsetLow, size));
if (m_ptr == NULL)
{
CloseHandle(m_hMap);
m_hMap = NULL;
return MakeUnexpected(
std::format("MapViewOfFile failed (Offset: {}, Size: {}): {}", offset, size, GetLastError()));
}
m_size = size;
FlushViewOfFile(m_ptr, m_size);
#elif IA_PLATFORM_UNIX
struct stat sb;
if (fstat(handle, &sb) == -1)
return MakeUnexpected("Failed to fstat file");
UINT64 endOffset = offset + size;
if (static_cast<UINT64>(sb.st_size) < endOffset)
{
if (ftruncate(handle, endOffset) == -1)
return MakeUnexpected("Failed to ftruncate (extend) file");
}
void *ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, handle, static_cast<off_t>(offset));
if (ptr == MAP_FAILED)
return MakeUnexpected(std::format("mmap failed: {}", errno));
m_ptr = static_cast<PUINT8>(ptr);
m_size = size;
madvise(m_ptr, m_size, MADV_SEQUENTIAL);
msync(m_ptr, m_size, MS_SYNC);
#endif
}
return {};
}
VOID FileOps::MemoryMappedRegion::Unmap()
{
if (!m_ptr)
return;
#if IA_PLATFORM_WINDOWS
UnmapViewOfFile(m_ptr);
if (m_hMap)
{
CloseHandle(m_hMap);
m_hMap = NULL;
}
#elif IA_PLATFORM_UNIX
munmap(m_ptr, m_size);
#endif
m_ptr = nullptr;
m_size = 0;
}
VOID FileOps::MemoryMappedRegion::Flush()
{
if (!m_ptr)
return;
#if IA_PLATFORM_WINDOWS
FlushViewOfFile(m_ptr, m_size);
#elif IA_PLATFORM_UNIX
msync(m_ptr, m_size, MS_SYNC);
#endif
}
} // namespace IACore

View File

@ -13,127 +13,121 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include <IACore/Http/Client.hpp>
#include <IACore/DataOps.hpp>
#include <IACore/Http/Client.hpp>
namespace IACore
{
EXPECT(UniquePtr<HttpClient>) HttpClient::Create(IN CONST String &host)
{
return MakeUniqueProtected<HttpClient>(httplib::Client(host));
}
namespace IACore {
Result<UniquePtr<HttpClient>> HttpClient::Create(const String &host) {
return MakeUniqueProtected<HttpClient>(httplib::Client(host));
}
httplib::Headers BuildHeaders(IN Span<CONST HttpClient::Header> headers, IN PCCHAR defaultContentType)
{
httplib::Headers out;
bool hasContentType = false;
httplib::Headers BuildHeaders(Span<const HttpClient::Header> headers,
const char *defaultContentType) {
httplib::Headers out;
bool hasContentType = false;
for (const auto &h : headers)
{
out.emplace(h.first, h.second);
for (const auto &h : headers) {
out.emplace(h.first, h.second);
if (h.first == HttpClient::HeaderTypeToString(HttpClient::EHeaderType::CONTENT_TYPE))
hasContentType = true;
}
if (h.first ==
HttpClient::HeaderTypeToString(HttpClient::EHeaderType::CONTENT_TYPE))
hasContentType = true;
}
if (!hasContentType && defaultContentType)
out.emplace("Content-Type", defaultContentType);
return out;
}
if (!hasContentType && defaultContentType)
out.emplace("Content-Type", defaultContentType);
return out;
}
HttpClient::HttpClient(IN httplib::Client &&client)
: m_client(IA_MOVE(client)), m_lastResponseCode(EResponseCode::INTERNAL_SERVER_ERROR)
{
m_client.enable_server_certificate_verification(true);
}
HttpClient::HttpClient(httplib::Client &&client)
: m_client(std::move(client)),
m_lastResponseCode(EResponseCode::INTERNAL_SERVER_ERROR) {
m_client.enable_server_certificate_verification(true);
}
HttpClient::~HttpClient()
{
}
HttpClient::~HttpClient() {}
VOID HttpClient::EnableCertificateVerfication()
{
m_client.enable_server_certificate_verification(true);
}
void HttpClient::EnableCertificateVerfication() {
m_client.enable_server_certificate_verification(true);
}
VOID HttpClient::DisableCertificateVerfication()
{
m_client.enable_server_certificate_verification(false);
}
void HttpClient::DisableCertificateVerfication() {
m_client.enable_server_certificate_verification(false);
}
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());
}
String HttpClient::PreprocessResponse(const String &response) {
const auto responseBytes =
Span<const u8>{(const u8 *)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((const char *)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::Zlib: {
const auto data = DataOps::ZlibInflate(responseBytes);
if (!data)
return response;
return String((const char *)data->data(), data->size());
}
case DataOps::CompressionType::None:
default:
break;
}
return response;
}
case DataOps::CompressionType::None:
default:
break;
}
return response;
}
EXPECT(String)
HttpClient::RawGet(IN CONST String &path, IN Span<CONST Header> headers, IN PCCHAR defaultContentType)
{
auto httpHeaders = BuildHeaders(headers, defaultContentType);
Result<String>
auto res = m_client.Get((!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(), httpHeaders);
HttpClient::RawGet(const String &path, Span<const Header> headers,
const char *defaultContentType) {
auto httpHeaders = BuildHeaders(headers, defaultContentType);
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));
}
auto res = m_client.Get(
(!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(),
httpHeaders);
return MakeUnexpected(std::format("Network Error: {}", httplib::to_string(res.error())));
}
if (res) {
m_lastResponseCode = static_cast<EResponseCode>(res->status);
if (res->status >= 200 && res->status < 300)
return PreprocessResponse(res->body);
else
return (std::format("HTTP Error {} : {}", res->status, res->body));
}
EXPECT(String)
HttpClient::RawPost(IN CONST String &path, IN Span<CONST Header> headers, IN CONST String &body,
IN PCCHAR defaultContentType)
{
auto httpHeaders = BuildHeaders(headers, defaultContentType);
return (std::format("Network Error: {}", httplib::to_string(res.error())));
}
String contentType = defaultContentType;
if (httpHeaders.count("Content-Type"))
{
const auto t = httpHeaders.find("Content-Type");
contentType = t->second;
httpHeaders.erase(t);
}
Result<String>
m_client.set_keep_alive(true);
auto res = m_client.Post((!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(), httpHeaders,
body, contentType.c_str());
HttpClient::RawPost(const String &path, Span<const Header> headers,
const String &body, const char *defaultContentType) {
auto httpHeaders = BuildHeaders(headers, defaultContentType);
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));
}
String contentType = defaultContentType;
if (httpHeaders.count("Content-Type")) {
const auto t = httpHeaders.find("Content-Type");
contentType = t->second;
httpHeaders.erase(t);
}
return MakeUnexpected(std::format("Network Error: {}", httplib::to_string(res.error())));
}
m_client.set_keep_alive(true);
auto res = 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 (std::format("HTTP Error {} : {}", res->status, res->body));
}
return (std::format("Network Error: {}", httplib::to_string(res.error())));
}
} // namespace IACore

View File

@ -15,110 +15,103 @@
#include <IACore/Http/Common.hpp>
namespace IACore
{
String HttpCommon::UrlEncode(IN CONST String &value)
{
std::stringstream escaped;
escaped.fill('0');
escaped << std::hex << std::uppercase;
namespace IACore {
String HttpCommon::UrlEncode(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));
}
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();
}
return escaped.str();
}
String HttpCommon::UrlDecode(IN CONST String &value)
{
String result;
result.reserve(value.length());
String HttpCommon::UrlDecode(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];
}
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;
}
return result;
}
String HttpCommon::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";
}
return "<Unknown>";
}
String HttpCommon::HeaderTypeToString(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";
}
return "<Unknown>";
}
BOOL HttpCommon::IsSuccessResponseCode(IN EResponseCode code)
{
return (INT32) code >= 200 && (INT32) code < 300;
}
bool HttpCommon::IsSuccessResponseCode(EResponseCode code) {
return (i32)code >= 200 && (i32)code < 300;
}
} // namespace IACore

View File

@ -17,76 +17,50 @@
#include <IACore/Logger.hpp>
#include <mimalloc.h>
#include <chrono>
#include <cstdlib>
namespace IACore
{
HighResTimePoint g_startTime{};
std::thread::id g_mainThreadID{};
INT32 g_coreInitCount{};
auto g_start_time = std::chrono::high_resolution_clock::time_point{};
VOID Initialize()
static auto g_main_thread_id = std::thread::id{};
static auto g_core_init_count = i32{0};
auto initialize() -> void
{
g_coreInitCount++;
if (g_coreInitCount > 1)
g_core_init_count++;
if (g_core_init_count > 1)
{
return;
g_mainThreadID = std::this_thread::get_id();
g_startTime = HighResClock::now();
Logger::Initialize();
}
g_main_thread_id = std::this_thread::get_id();
g_start_time = std::chrono::high_resolution_clock::now();
Logger::initialize();
mi_option_set(mi_option_verbose, 0);
}
VOID Terminate()
auto terminate() -> void
{
g_coreInitCount--;
if (g_coreInitCount > 0)
g_core_init_count--;
if (g_core_init_count > 0)
{
return;
Logger::Terminate();
}
Logger::terminate();
}
BOOL IsInitialized()
auto is_initialized() -> bool
{
return g_coreInitCount > 0;
return g_core_init_count > 0;
}
UINT64 GetUnixTime()
auto is_main_thread() -> bool
{
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);
}
UINT64 GetRandom(IN UINT64 max)
{
return max * GetRandom();
}
INT64 GetRandom(IN INT64 min, IN INT64 max)
{
return min + (max - min) * GetRandom();
}
VOID Sleep(IN UINT64 milliseconds)
{
std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
}
BOOL IsMainThread()
{
return std::this_thread::get_id() == g_mainThreadID;
return std::this_thread::get_id() == g_main_thread_id;
}
} // namespace IACore

View File

@ -17,435 +17,441 @@
#include <IACore/FileOps.hpp>
#include <IACore/StringOps.hpp>
#include <charconv>
#include <fcntl.h>
namespace IACore
{
struct IPC_ConnectionDescriptor
{
String SocketPath;
String SharedMemPath;
UINT32 SharedMemSize;
namespace IACore {
// =============================================================================
// Internal: Connection Descriptor
// =============================================================================
struct IpcConnectionDescriptor {
String socket_path;
String shared_mem_path;
u32 shared_mem_size;
String Serialize() CONST
{
return std::format("{}|{}|{}|", SocketPath, SharedMemPath, SharedMemSize);
[[nodiscard]] auto serialize() const -> String {
return std::format("{}|{}|{}|", socket_path, shared_mem_path,
shared_mem_size);
}
static auto deserialize(StringView data) -> Option<IpcConnectionDescriptor> {
enum class ParseState { SocketPath, SharedMemPath, SharedMemSize };
IpcConnectionDescriptor result{};
usize t = 0;
auto state = ParseState::SocketPath;
for (usize i = 0; i < data.size(); ++i) {
if (data[i] != '|') {
continue;
}
switch (state) {
case ParseState::SocketPath:
result.socket_path = String(data.substr(t, i - t));
state = ParseState::SharedMemPath;
break;
case ParseState::SharedMemPath:
result.shared_mem_path = String(data.substr(t, i - t));
state = ParseState::SharedMemSize;
break;
case ParseState::SharedMemSize: {
const auto *start = data.data() + t;
const auto *end = data.data() + i;
if (std::from_chars(start, end, result.shared_mem_size).ec !=
std::errc{}) {
return std::nullopt;
}
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
return result;
}
}
t = i + 1;
}
return std::nullopt;
}
};
EXPECT(VOID) IPC_Node::Connect(IN PCCHAR connectionString)
{
auto desc = IPC_ConnectionDescriptor::Deserialize(connectionString);
m_shmName = desc.SharedMemPath;
// =============================================================================
// IpcNode Implementation
// =============================================================================
m_socket = SocketOps::CreateUnixSocket();
if (!SocketOps::ConnectUnixSocket(m_socket, desc.SocketPath.c_str()))
return MakeUnexpected("Failed to create an unix socket");
IpcNode::~IpcNode() {
if (m_socket != INVALID_SOCKET) {
SocketOps::close(m_socket);
}
}
auto mapRes = FileOps::MapSharedMemory(desc.SharedMemPath, desc.SharedMemSize, FALSE);
if (!mapRes.has_value())
return MakeUnexpected("Failed to map the shared memory");
auto IpcNode::connect(const char *connection_string) -> Result<void> {
const auto desc_opt = IpcConnectionDescriptor::deserialize(connection_string);
if (!desc_opt) {
return fail("Failed to parse connection string");
}
const auto &desc = *desc_opt;
m_shm_name = desc.shared_mem_path;
m_sharedMemory = mapRes.value();
IA_TRY(m_socket, SocketOps::create_unix_socket());
IA_TRY_PURE(
SocketOps::connect_unix_socket(m_socket, desc.socket_path.c_str()));
auto *layout = reinterpret_cast<IPC_SharedMemoryLayout *>(m_sharedMemory);
u8 *mapped_ptr{};
IA_TRY(mapped_ptr, FileOps::map_shared_memory(desc.shared_mem_path,
desc.shared_mem_size, false));
m_shared_memory = mapped_ptr;
if (layout->Meta.Magic != 0x49414950) // "IAIP"
return MakeUnexpected("Invalid shared memory header signature");
auto *layout = reinterpret_cast<IpcSharedMemoryLayout *>(m_shared_memory);
if (layout->Meta.Version != 1)
return MakeUnexpected("IPC version mismatch");
if (layout->meta.magic != 0x49414950) // "IAIP"
{
return fail("Invalid shared memory header signature");
}
PUINT8 moniDataPtr = m_sharedMemory + layout->MONI_DataOffset;
PUINT8 minoDataPtr = m_sharedMemory + layout->MINO_DataOffset;
if (layout->meta.version != 1) {
return fail("IPC version mismatch");
}
MONI = std::make_unique<RingBufferView>(
&layout->MONI_Control, Span<UINT8>(moniDataPtr, static_cast<size_t>(layout->MONI_DataSize)), FALSE);
u8 *moni_ptr = m_shared_memory + layout->moni_data_offset;
u8 *mino_ptr = m_shared_memory + layout->mino_data_offset;
MINO = std::make_unique<RingBufferView>(
&layout->MINO_Control, Span<UINT8>(minoDataPtr, static_cast<size_t>(layout->MINO_DataSize)), FALSE);
m_moni = make_box<RingBufferView>(
&layout->moni_control,
Span<u8>(moni_ptr, static_cast<usize>(layout->moni_data_size)), false);
m_mino = make_box<RingBufferView>(
&layout->mino_control,
Span<u8>(mino_ptr, static_cast<usize>(layout->mino_data_size)), false);
#if IA_PLATFORM_WINDOWS
u_long mode = 1;
ioctlsocket(m_socket, FIONBIO, &mode);
u_long mode = 1;
ioctlsocket(m_socket, FIONBIO, &mode);
#else
fcntl(m_socket, F_SETFL, O_NONBLOCK);
fcntl(m_socket, F_SETFL, O_NONBLOCK);
#endif
m_receiveBuffer.resize(UINT16_MAX + 1);
m_receive_buffer.resize(UINT16_MAX + 1);
return {};
return {};
}
void IpcNode::update() {
if (!m_moni) {
return;
}
IpcPacketHeader header;
// Process all available messages from Manager
while (m_moni->pop(
header, Span<u8>(m_receive_buffer.data(), m_receive_buffer.size()))) {
on_packet(header.id, {m_receive_buffer.data(), header.payload_size});
}
u8 signal = 0;
const auto res = recv(m_socket, reinterpret_cast<char *>(&signal), 1, 0);
if (res == 1) {
on_signal(signal);
} else if (res == 0 || (res < 0 && !SocketOps::is_would_block())) {
SocketOps::close(m_socket);
FileOps::unlink_shared_memory(m_shm_name);
// Manager disconnected, exit immediately
std::exit(-1);
}
}
void IpcNode::send_signal(u8 signal) {
if (m_socket != INVALID_SOCKET) {
send(m_socket, reinterpret_cast<const char *>(&signal), sizeof(signal), 0);
}
}
void IpcNode::send_packet(u16 packet_id, Span<const u8> payload) {
if (m_mino) {
m_mino->push(packet_id, payload);
}
}
// =============================================================================
// IpcManager Implementation
// =============================================================================
void IpcManager::NodeSession::send_signal(u8 signal) {
if (data_socket != INVALID_SOCKET) {
send(data_socket, reinterpret_cast<const char *>(&signal), sizeof(signal),
0);
}
}
void IpcManager::NodeSession::send_packet(u16 packet_id,
Span<const u8> payload) {
// Protect the RingBuffer write cursor from concurrent threads
std::scoped_lock lock(send_mutex);
if (moni) {
moni->push(packet_id, payload);
}
}
IpcManager::IpcManager() {
// SocketOps is smart enough to track multiple inits
SocketOps::initialize();
m_receive_buffer.resize(UINT16_MAX + 1);
}
IpcManager::~IpcManager() {
for (auto &session : m_active_sessions) {
ProcessOps::terminate_process(session->node_process);
FileOps::unmap_file(session->mapped_ptr);
FileOps::unlink_shared_memory(session->shared_mem_name);
SocketOps::close(session->data_socket);
}
m_active_sessions.clear();
for (auto &session : m_pending_sessions) {
ProcessOps::terminate_process(session->node_process);
FileOps::unmap_file(session->mapped_ptr);
FileOps::unlink_shared_memory(session->shared_mem_name);
SocketOps::close(session->listener_socket);
}
m_pending_sessions.clear();
// SocketOps is smart enough to track multiple terminates
SocketOps::terminate();
}
void IpcManager::update() {
const auto now = std::chrono::system_clock::now();
for (isize i = static_cast<isize>(m_pending_sessions.size()) - 1; i >= 0;
--i) {
auto &session = m_pending_sessions[static_cast<usize>(i)];
if (now - session->creation_time > std::chrono::seconds(5)) {
ProcessOps::terminate_process(session->node_process);
FileOps::unmap_file(session->mapped_ptr);
FileOps::unlink_shared_memory(session->shared_mem_name);
SocketOps::close(session->listener_socket);
m_pending_sessions.erase(m_pending_sessions.begin() + i);
continue;
}
VOID IPC_Node::Update()
{
if (!MONI)
return;
auto new_sock = accept(session->listener_socket, nullptr, nullptr);
RingBufferView::PacketHeader header;
if (new_sock != INVALID_SOCKET) {
session->data_socket = new_sock;
session->is_ready = true;
// 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->NodeProcess);
FileOps::UnmapFile(session->MappedPtr);
FileOps::UnlinkSharedMemory(session->SharedMemName);
SocketOps::Close(session->DataSocket);
}
m_activeSessions.clear();
for (auto &session : m_pendingSessions)
{
ProcessOps::TerminateProcess(session->NodeProcess);
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->NodeProcess);
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
// Set Data Socket to Non-Blocking
#if IA_PLATFORM_WINDOWS
u_long mode = 1;
ioctlsocket(session->DataSocket, FIONBIO, &mode);
u_long mode = 1;
ioctlsocket(session->data_socket, FIONBIO, &mode);
#else
fcntl(session->DataSocket, F_SETFL, O_NONBLOCK);
fcntl(session->data_socket, F_SETFL, O_NONBLOCK);
#endif
SocketOps::Close(session->ListenerSocket);
session->ListenerSocket = INVALID_SOCKET;
SocketOps::close(session->listener_socket);
session->listener_socket = INVALID_SOCKET;
const auto sessionID = session->NodeProcess->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;
}
}
const auto session_id = session->node_process->id.load();
auto *session_ptr = session.get();
m_active_sessions.push_back(std::move(session));
m_pending_sessions.erase(m_pending_sessions.begin() + i);
m_active_session_map[session_id] = session_ptr;
}
}
for (INT32 i = m_activeSessions.size() - 1; i >= 0; i--)
{
auto &node = m_activeSessions[i];
for (isize i = static_cast<isize>(m_active_sessions.size()) - 1; i >= 0;
--i) {
auto &node = m_active_sessions[static_cast<usize>(i)];
auto nodeID = node->NodeProcess->ID.load();
auto node_id = node->node_process->id.load();
RingBufferView::PacketHeader header;
IpcPacketHeader 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->NodeProcess);
FileOps::UnmapFile(node->MappedPtr);
FileOps::UnlinkSharedMemory(node->SharedMemName);
SocketOps::Close(node->DataSocket);
m_activeSessions.erase(m_activeSessions.begin() + i);
m_activeSessionMap.erase(nodeID);
}
}
while (node->mino->pop(
header, Span<u8>(m_receive_buffer.data(), m_receive_buffer.size()))) {
on_packet(node_id, header.id,
{m_receive_buffer.data(), header.payload_size});
}
EXPECT(NativeProcessID) IPC_Manager::SpawnNode(IN CONST FilePath &executablePath, IN UINT32 sharedMemorySize)
{
auto session = std::make_unique<NodeSession>();
u8 signal = 0;
const auto res =
recv(node->data_socket, reinterpret_cast<char *>(&signal), 1, 0);
static Atomic<UINT32> s_idGen{0};
UINT32 sid = ++s_idGen;
if (res == 1) {
on_signal(node_id, signal);
} else if (res == 0 || (res < 0 && !SocketOps::is_would_block())) {
ProcessOps::terminate_process(node->node_process);
FileOps::unmap_file(node->mapped_ptr);
FileOps::unlink_shared_memory(node->shared_mem_name);
SocketOps::close(node->data_socket);
m_active_sessions.erase(m_active_sessions.begin() + i);
m_active_session_map.erase(node_id);
}
}
}
auto IpcManager::spawn_node(const Path &executable_path, u32 shared_memory_size)
-> Result<NativeProcessID> {
auto session = make_box<NodeSession>();
static std::atomic<u32> s_id_gen{0};
const u32 sid = ++s_id_gen;
String sock_path;
#if IA_PLATFORM_WINDOWS
char temp_path[MAX_PATH];
GetTempPathA(MAX_PATH, temp_path);
sock_path = std::format("{}\\ia_sess_{}.sock", temp_path, sid);
#else
sock_path = std::format("/tmp/ia_sess_{}.sock", sid);
#endif
IA_TRY(session->listener_socket, SocketOps::create_unix_socket());
IA_TRY_PURE(
SocketOps::bind_unix_socket(session->listener_socket, sock_path.c_str()));
IA_TRY_PURE(SocketOps::listen(session->listener_socket, 1));
#if IA_PLATFORM_WINDOWS
char tempPath[MAX_PATH];
GetTempPathA(MAX_PATH, tempPath);
String sockPath = std::format("{}\\ia_sess_{}.sock", tempPath, sid);
u_long mode = 1;
ioctlsocket(session->listener_socket, FIONBIO, &mode);
#else
String sockPath = std::format("/tmp/ia_sess_{}.sock", sid);
fcntl(session->listener_socket, F_SETFL, O_NONBLOCK);
#endif
session->ListenerSocket = SocketOps::CreateUnixSocket();
if (!SocketOps::BindUnixSocket(session->ListenerSocket, sockPath.c_str()))
return MakeUnexpected("Failed to bind unique socket");
const String shm_name = std::format("ia_shm_{}", sid);
IA_TRY(session->mapped_ptr,
FileOps::map_shared_memory(shm_name, shared_memory_size, true));
if (listen(session->ListenerSocket, 1) != 0)
return MakeUnexpected("Failed to listen on unique socket");
auto *layout = reinterpret_cast<IpcSharedMemoryLayout *>(session->mapped_ptr);
#if IA_PLATFORM_WINDOWS
u_long mode = 1;
ioctlsocket(session->ListenerSocket, FIONBIO, &mode);
#else
fcntl(session->ListenerSocket, F_SETFL, O_NONBLOCK);
#endif
layout->meta.magic = 0x49414950;
layout->meta.version = 1;
layout->meta.total_size = shared_memory_size;
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");
const u64 header_size = IpcSharedMemoryLayout::get_header_size();
const u64 usable_bytes = shared_memory_size - header_size;
PUINT8 mappedPtr = mapRes.value();
u64 half_size = (usable_bytes / 2);
half_size -= (half_size % 64);
auto *layout = reinterpret_cast<IPC_SharedMemoryLayout *>(mappedPtr);
layout->moni_data_offset = header_size;
layout->moni_data_size = half_size;
layout->Meta.Magic = 0x49414950;
layout->Meta.Version = 1;
layout->Meta.TotalSize = sharedMemorySize;
layout->mino_data_offset = header_size + half_size;
layout->mino_data_size = half_size;
UINT64 headerSize = IPC_SharedMemoryLayout::GetHeaderSize();
UINT64 usableBytes = sharedMemorySize - headerSize;
session->moni = make_box<RingBufferView>(
&layout->moni_control,
Span<u8>(session->mapped_ptr + layout->moni_data_offset,
static_cast<usize>(layout->moni_data_size)),
true);
UINT64 halfSize = (usableBytes / 2);
halfSize -= (halfSize % 64);
session->mino = make_box<RingBufferView>(
&layout->mino_control,
Span<u8>(session->mapped_ptr + layout->mino_data_offset,
static_cast<usize>(layout->mino_data_size)),
true);
layout->MONI_DataOffset = headerSize;
layout->MONI_DataSize = halfSize;
IpcConnectionDescriptor desc;
desc.socket_path = sock_path;
desc.shared_mem_path = shm_name;
desc.shared_mem_size = shared_memory_size;
layout->MINO_DataOffset = headerSize + halfSize;
layout->MINO_DataSize = halfSize;
const String args = std::format("\"{}\"", desc.serialize());
session->MONI = std::make_unique<RingBufferView>(
&layout->MONI_Control, Span<UINT8>(mappedPtr + layout->MONI_DataOffset, layout->MONI_DataSize), TRUE);
IA_TRY(session->node_process,
ProcessOps::spawn_process_async(
FileOps::normalize_executable_path(executable_path).string(), args,
[sid](StringView line) {
if (env::is_debug) {
std::cout << std::format("{}[Node:{}:STDOUT|STDERR]: {}{}\n",
console::MAGENTA, sid, line,
console::RESET);
}
},
[sid](Result<i32> result) {
if (env::is_debug) {
if (!result) {
std::cout << std::format(
"{}[Node: {}]: Failed to spawn with error '{}'{}\n",
console::RED, sid, result.error(), console::RESET);
} else {
std::cout << std::format(
"{}[Node: {}]: Exited with code {}{}\n", console::RED,
sid, *result, console::RESET);
}
}
}));
session->MINO = std::make_unique<RingBufferView>(
&layout->MINO_Control, Span<UINT8>(mappedPtr + layout->MINO_DataOffset, layout->MINO_DataSize), TRUE);
// Give some time for child node to stablize
std::this_thread::sleep_for(std::chrono::seconds(1));
if (!session->node_process->is_active()) {
return fail("Failed to spawn the child process \"{}\"",
executable_path.string());
}
IPC_ConnectionDescriptor desc;
desc.SocketPath = sockPath;
desc.SharedMemPath = shmName;
desc.SharedMemSize = sharedMemorySize;
const auto process_id = session->node_process->id.load();
String args = std::format("\"{}\"", desc.Serialize());
session->shared_mem_name = shm_name;
session->creation_time = std::chrono::system_clock::now();
m_pending_sessions.push_back(std::move(session));
session->NodeProcess = 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 EXPECT(INT32) 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
});
return process_id;
}
// Give some time for child node to stablize
std::this_thread::sleep_for(std::chrono::seconds(1));
if (!session->NodeProcess->IsActive())
return MakeUnexpected(std::format("Failed to spawn the child process \"{}\"", executablePath.string()));
auto processID = session->NodeProcess->ID.load();
session->CreationTime = SteadyClock::now();
m_pendingSessions.push_back(std::move(session));
return processID;
auto IpcManager::wait_till_node_is_online(NativeProcessID node_id) -> bool {
bool is_pending = true;
while (is_pending) {
is_pending = false;
for (const auto &session : m_pending_sessions) {
if (session->node_process->id.load() == node_id) {
is_pending = true;
break;
}
}
update();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
return m_active_session_map.contains(node_id);
}
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()->NodeProcess->ID.load() == nodeID)
{
isPending = true;
break;
}
}
Update();
}
return m_activeSessionMap.contains(nodeID);
}
void IpcManager::shutdown_node(NativeProcessID node_id) {
const auto it_node = m_active_session_map.find(node_id);
if (it_node == m_active_session_map.end()) {
return;
}
VOID IPC_Manager::ShutdownNode(IN NativeProcessID nodeID)
{
const auto itNode = m_activeSessionMap.find(nodeID);
if (itNode == m_activeSessionMap.end())
return;
auto *node = it_node->second;
auto &node = itNode->second;
ProcessOps::terminate_process(node->node_process);
FileOps::unmap_file(node->mapped_ptr);
FileOps::unlink_shared_memory(node->shared_mem_name);
SocketOps::close(node->data_socket);
ProcessOps::TerminateProcess(node->NodeProcess);
FileOps::UnmapFile(node->MappedPtr);
FileOps::UnlinkSharedMemory(node->SharedMemName);
SocketOps::Close(node->DataSocket);
std::erase_if(m_active_sessions,
[&](const auto &s) { return s.get() == node; });
m_active_session_map.erase(it_node);
}
for (auto it = m_activeSessions.begin(); it != m_activeSessions.end(); it++)
{
if (it->get() == node)
{
m_activeSessions.erase(it);
break;
}
}
void IpcManager::send_signal(NativeProcessID node, u8 signal) {
const auto it_node = m_active_session_map.find(node);
if (it_node == m_active_session_map.end()) {
return;
}
it_node->second->send_signal(signal);
}
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);
}
void IpcManager::send_packet(NativeProcessID node, u16 packet_id,
Span<const u8> payload) {
const auto it_node = m_active_session_map.find(node);
if (it_node == m_active_session_map.end()) {
return;
}
it_node->second->send_packet(packet_id, payload);
}
} // namespace IACore

View File

@ -17,27 +17,5 @@
namespace IACore
{
EXPECT(nlohmann::json) 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;
}
EXPECT(Pair<SharedPtr<simdjson::dom::parser>, simdjson::dom::object>) JSON::ParseReadOnly(IN CONST String &json)
{
auto parser = std::make_shared<simdjson::dom::parser>();
simdjson::error_code error{};
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

@ -1,72 +1,66 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2026 IAS (ias@iasoft.dev)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <IACore/Logger.hpp>
#include <IACore/IACore.hpp>
#include <IACore/FileOps.hpp>
#include <chrono>
#include <fstream>
#include <iostream>
namespace IACore
{
Logger::ELogLevel Logger::s_logLevel{Logger::ELogLevel::INFO};
std::ofstream Logger::s_logFile{};
namespace IACore {
Logger::LogLevel Logger::m_log_level = Logger::LogLevel::Info;
std::ofstream Logger::m_log_file;
VOID Logger::Initialize()
{
}
static auto get_seconds_count() -> f64 {
static const auto start_time = std::chrono::steady_clock::now();
const auto now = std::chrono::steady_clock::now();
const std::chrono::duration<f64> duration = now - start_time;
return duration.count();
}
VOID Logger::Terminate()
{
if (s_logFile.is_open())
{
s_logFile.flush();
s_logFile.close();
}
}
auto Logger::initialize() -> void {}
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();
}
auto Logger::terminate() -> void {
if (m_log_file.is_open()) {
m_log_file.flush();
m_log_file.close();
}
}
VOID Logger::SetLogLevel(IN ELogLevel logLevel)
{
s_logLevel = logLevel;
}
auto Logger::enable_logging_to_disk(const char *file_path) -> Result<void> {
if (m_log_file.is_open()) {
m_log_file.flush();
m_log_file.close();
}
VOID Logger::FlushLogs()
{
std::cout.flush();
if (s_logFile)
s_logFile.flush();
}
m_log_file.open(file_path);
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();
}
}
if (!m_log_file.is_open()) {
return fail("Failed to open log file: {}", file_path);
}
return {};
}
auto Logger::set_log_level(LogLevel log_level) -> void {
m_log_level = log_level;
}
auto Logger::flush_logs() -> void {
std::cout.flush();
if (m_log_file.is_open()) {
m_log_file.flush();
}
}
auto Logger::log_internal(const char *prefix, const char *tag, String &&msg)
-> void {
const auto seconds = get_seconds_count();
const auto out_line = std::format("[{:>8.3f}]: [{}]: {}", seconds, tag, msg);
std::cout << prefix << out_line << console::RESET << '\n';
if (m_log_file.is_open()) {
m_log_file.write(out_line.data(),
static_cast<std::streamsize>(out_line.size()));
m_log_file.put('\n');
m_log_file.flush();
}
}
} // namespace IACore

View File

@ -15,106 +15,130 @@
#include <IACore/Platform.hpp>
#if IA_ARCH_X64
#if defined(IA_ARCH_X64)
# ifndef _MSC_VER
# include <cpuid.h>
# endif
#elif IA_ARCH_ARM64
#elif defined(IA_ARCH_ARM64)
# if defined(__linux__) || defined(__ANDROID__)
# include <sys/auxv.h>
# include <asm/hwcap.h>
# endif
#endif
namespace IACore
{
Platform::Capabilities Platform::s_capabilities{};
#if IA_ARCH_X64
VOID Platform::CPUID(IN INT32 function, IN INT32 subFunction, OUT INT32 out[4])
#if defined(IA_ARCH_X64)
auto Platform::cpuid(i32 function, i32 sub_function, i32 out[4]) -> void
{
# ifdef _MSC_VER
__cpuidex(out, function, subFunction);
__cpuidex(reinterpret_cast<int *>(out), static_cast<int>(function), static_cast<int>(sub_function));
# else
__cpuid_count(function, subFunction, out[0], out[1], out[2], out[3]);
u32 a = 0;
u32 b = 0;
u32 c = 0;
u32 d = 0;
__cpuid_count(function, sub_function, a, b, c, d);
out[0] = static_cast<i32>(a);
out[1] = static_cast<i32>(b);
out[2] = static_cast<i32>(c);
out[3] = static_cast<i32>(d);
# endif
}
#endif
BOOL Platform::CheckCPU()
auto Platform::check_cpu() -> bool
{
#if IA_ARCH_X64
INT32 cpuInfo[4];
#if defined(IA_ARCH_X64)
i32 cpu_info[4];
CPUID(0, 0, cpuInfo);
if (cpuInfo[0] < 7)
cpuid(0, 0, cpu_info);
if (cpu_info[0] < 7)
{
return false;
}
cpuid(1, 0, cpu_info);
// Bit 27: OSXSAVE, Bit 28: AVX, Bit 12: FMA
const bool osxsave = (cpu_info[2] & (1 << 27)) != 0;
const bool avx = (cpu_info[2] & (1 << 28)) != 0;
const bool fma = (cpu_info[2] & (1 << 12)) != 0;
CPUID(1, 0, cpuInfo);
BOOL osxsave = (cpuInfo[2] & (1 << 27)) != 0;
BOOL avx = (cpuInfo[2] & (1 << 28)) != 0;
BOOL fma = (cpuInfo[2] & (1 << 12)) != 0;
if (!osxsave || !avx || !fma)
{
return false;
}
UINT64 xcrFeatureMask = _xgetbv(0);
if ((xcrFeatureMask & 0x6) != 0x6)
const u64 xcr_feature_mask = _xgetbv(0);
if ((xcr_feature_mask & 0x6) != 0x6)
{
return false;
}
CPUID(7, 0, cpuInfo);
BOOL avx2 = (cpuInfo[1] & (1 << 5)) != 0;
cpuid(7, 0, cpu_info);
// Bit 5: AVX2
const bool avx2 = (cpu_info[1] & (1 << 5)) != 0;
if (!avx2)
{
return false;
}
s_capabilities.HardwareCRC32 = TRUE;
#elif IA_ARCH_ARM64
s_capabilities.hardware_crc32 = true;
#elif defined(IA_ARCH_ARM64)
# if defined(__linux__) || defined(__ANDROID__)
unsigned long hw_caps = getauxval(AT_HWCAP);
const unsigned long hw_caps = getauxval(AT_HWCAP);
# ifndef HWCAP_CRC32
# define HWCAP_CRC32 (1 << 7)
# endif
s_capabilities.HardwareCRC32 = hw_caps & HWCAP_CRC32;
# elif defined(__APPLE__)
s_capabilities.hardware_crc32 = (hw_caps & HWCAP_CRC32) != 0;
# elif defined(IA_PLATFORM_APPLE)
// Apple silicon always has hardware CRC32
s_capabilities.HardwareCRC32 = TRUE;
s_capabilities.hardware_crc32 = true;
# else
s_capabilities.HardwareCRC32 = FALSE;
s_capabilities.hardware_crc32 = false;
# endif
#else
s_capabilities.HardwareCRC32 = FALSE;
s_capabilities.hardware_crc32 = false;
#endif
return true;
}
PCCHAR Platform::GetArchitectureName()
auto Platform::get_architecture_name() -> const char *
{
#if IA_ARCH_X64
#if defined(IA_ARCH_X64)
return "x86_64";
#elif IA_ARCH_ARM64
#elif defined(IA_ARCH_ARM64)
return "aarch64";
#elif IA_ARCH_WASM
#elif defined(IA_ARCH_WASM)
return "wasm";
#else
return "unknown";
#endif
}
PCCHAR Platform::GetOperatingSystemName()
auto Platform::get_operating_system_name() -> const char *
{
#if IA_PLATFORM_WINDOWS
return "Windows";
#elif IA_PLATFORM_IOS
#elif defined(IA_PLATFORM_APPLE)
# if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
return "iOS";
#elif IA_PLATFORM_MAC
return "Mac";
#elif IA_PLATFORM_ANDROID
# else
return "macOS";
# endif
#elif defined(__ANDROID__)
return "Android";
#elif IA_PLATFORM_LINUX
return "Linux";
#elif IA_PLATFORM_WASM
return "WebAssembly";
#else
return "Unknown";
#endif
}
} // namespace IACore

View File

@ -15,327 +15,317 @@
#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;
namespace IACore {
// ---------------------------------------------------------------------
// Output Buffering Helper
// Splits raw chunks into lines, preserving partial lines across chunks
// ---------------------------------------------------------------------
struct LineBuffer {
String m_accumulator;
std::function<void(StringView)> &m_callback;
VOID Append(IN PCCHAR data, IN SIZE_T size);
VOID Flush();
};
} // namespace IACore
void append(const char *data, usize size);
void flush();
};
namespace IACore
{
NativeProcessID ProcessOps::GetCurrentProcessID()
{
#if IA_PLATFORM_WINDOWS
return ::GetCurrentProcessId();
#else
return getpid();
#endif
}
EXPECT(INT32)
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(EXPECT(INT32))> 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);
void LineBuffer::append(const char *data, usize size) {
usize start = 0;
for (usize i = 0; i < size; ++i) {
if (data[i] == '\n' || data[i] == '\r') {
// Flush Accumulator + current chunk
if (!m_accumulator.empty()) {
m_accumulator.append(data + start, i - start);
if (!m_accumulator.empty()) {
m_callback(m_accumulator);
}
#else
kill(pid, SIGKILL);
#endif
}
} // namespace IACore
m_accumulator.clear();
} else {
if (i > start) {
m_callback(StringView(data + start, i - start));
}
}
namespace IACore
{
// 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) {
m_accumulator.append(data + start, size - start);
}
}
void LineBuffer::flush() {
if (!m_accumulator.empty()) {
m_callback(m_accumulator);
m_accumulator.clear();
}
}
auto ProcessOps::get_current_process_id() -> NativeProcessID {
#if IA_PLATFORM_WINDOWS
EXPECT(INT32)
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 MakeUnexpected("Failed to create pipe");
// Ensure the read handle to the pipe for STDOUT is NOT inherited
if (!SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0))
return MakeUnexpected("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 MakeUnexpected(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);
}
return ::GetCurrentProcessId();
#else
return getpid();
#endif
}
auto ProcessOps::spawn_process_sync(
const String &command, const String &args,
std::function<void(StringView line)> on_output_line_callback)
-> Result<i32> {
std::atomic<NativeProcessID> id = 0;
if constexpr (env::is_windows) {
return spawn_process_windows(command, args, on_output_line_callback, id);
} else {
return spawn_process_posix(command, args, on_output_line_callback, id);
}
}
auto ProcessOps::spawn_process_async(
const String &command, const String &args,
std::function<void(StringView line)> on_output_line_callback,
std::function<void(Result<i32>)> on_finish_callback)
-> Result<Box<ProcessHandle>> {
auto handle = make_box<ProcessHandle>();
handle->is_running = true;
// Capture raw pointer to handle internals safely because jthread joins on
// destruction
ProcessHandle *h_ptr = handle.get();
handle->m_thread_handle = std::jthread([h_ptr, cmd = command, arg = args,
cb = on_output_line_callback,
fin = on_finish_callback]() mutable {
Result<i32> result = fail("Platform not supported");
if constexpr (env::is_windows) {
result = spawn_process_windows(cmd, arg, cb, h_ptr->id);
} else {
result = spawn_process_posix(cmd, arg, cb, h_ptr->id);
}
h_ptr->is_running = false;
if (fin) {
if (!result) {
fin(fail(std::move(result.error())));
} else {
fin(*result);
}
}
});
return handle;
}
void ProcessOps::terminate_process(const Box<ProcessHandle> &handle) {
if (!handle || !handle->is_active()) {
return;
}
NativeProcessID pid = handle->id.load();
if (pid == 0) {
return;
}
#if IA_PLATFORM_WINDOWS
HANDLE h_process = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
if (h_process != NULL) {
::TerminateProcess(h_process, 9);
CloseHandle(h_process);
}
#endif
#if IA_PLATFORM_UNIX
EXPECT(INT32)
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 MakeUnexpected("Failed to create pipe");
pid_t pid = fork();
if (pid == -1)
{
return MakeUnexpected("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;
}
}
kill(pid, SIGKILL);
#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));
}
auto ProcessOps::spawn_process_windows(
const String &command, const String &args,
std::function<void(StringView)> on_output_line_callback,
std::atomic<NativeProcessID> &id) -> Result<i32> {
#if IA_PLATFORM_WINDOWS
SECURITY_ATTRIBUTES sa_attr = {sizeof(SECURITY_ATTRIBUTES), NULL,
TRUE}; // Allow inheritance
HANDLE h_read = NULL;
HANDLE h_write = NULL;
// 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);
if (!CreatePipe(&h_read, &h_write, &sa_attr, 0)) {
return fail("Failed to create pipe");
}
// Ensure the read handle to the pipe for STDOUT is NOT inherited
if (!SetHandleInformation(h_read, HANDLE_FLAG_INHERIT, 0)) {
return fail("Failed to secure pipe handles");
}
STARTUPINFOA si = {sizeof(STARTUPINFOA)};
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdOutput = h_write;
si.hStdError = h_write; // Merge stderr
si.hStdInput = NULL; // No input
PROCESS_INFORMATION pi = {0};
// Windows command line needs to be mutable and concatenated
String command_line = std::format("\"{}\" {}", command, args);
BOOL success = CreateProcessA(NULL, command_line.data(), NULL, NULL, TRUE, 0,
NULL, NULL, &si, &pi);
// Close write end in parent, otherwise ReadFile never returns EOF!
CloseHandle(h_write);
if (!success) {
CloseHandle(h_read);
return fail("CreateProcess failed: {}", GetLastError());
}
id.store(pi.dwProcessId);
// Read Loop
LineBuffer line_buf{"", on_output_line_callback};
DWORD bytes_read = 0;
char buffer[4096];
while (ReadFile(h_read, buffer, sizeof(buffer), &bytes_read, NULL) &&
bytes_read != 0) {
line_buf.append(buffer, bytes_read);
}
line_buf.flush();
// NOW we wait for exit code
DWORD exit_code = 0;
WaitForSingleObject(pi.hProcess, INFINITE);
GetExitCodeProcess(pi.hProcess, &exit_code);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(h_read);
id.store(0);
return static_cast<i32>(exit_code);
#else
(void)command;
(void)args;
(void)on_output_line_callback;
(void)id;
return fail("Windows implementation not available.");
#endif
}
auto ProcessOps::spawn_process_posix(
const String &command, const String &args,
std::function<void(StringView)> on_output_line_callback,
std::atomic<NativeProcessID> &id) -> Result<i32> {
#if IA_PLATFORM_UNIX
int pipefd[2];
if (pipe(pipefd) == -1) {
return fail("Failed to create pipe");
}
pid_t pid = fork();
if (pid == -1) {
return fail("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]);
Vec<String> arg_storage; // To keep strings alive
Vec<char *> argv;
String cmd_str = command;
argv.push_back(cmd_str.data());
// Manual Quote-Aware Splitter
String current_token;
bool in_quotes = false;
bool is_escaped = false;
for (char c : args) {
if (is_escaped) {
// Previous char was '\', so we treat this char literally.
current_token += c;
is_escaped = false;
continue;
}
if (c == '\\') {
// Escape sequence start
is_escaped = true;
continue;
}
if (c == '\"') {
// Toggle quote state
in_quotes = !in_quotes;
continue;
}
if (c == ' ' && !in_quotes) {
// Token boundary
if (!current_token.empty()) {
arg_storage.push_back(current_token);
current_token.clear();
}
} else {
current_token += c;
}
}
VOID LineBuffer::Flush()
{
if (!Accumulator.empty())
{
Callback(Accumulator);
Accumulator.clear();
}
if (!current_token.empty()) {
arg_storage.push_back(current_token);
}
// Build char* array from the std::string storage
for (auto &s : arg_storage) {
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 line_buf{"", on_output_line_callback};
char buffer[4096];
ssize_t count;
while ((count = read(pipefd[0], buffer, sizeof(buffer))) > 0) {
line_buf.append(buffer, static_cast<usize>(count));
}
line_buf.flush();
close(pipefd[0]);
int status;
waitpid(pid, &status, 0);
id.store(0);
if (WIFEXITED(status)) {
return WEXITSTATUS(status);
}
return -1;
}
#else
(void)command;
(void)args;
(void)on_output_line_callback;
(void)id;
return fail("Posix implementation not available.");
#endif
}
} // namespace IACore

View File

@ -14,92 +14,133 @@
// limitations under the License.
#include <IACore/SocketOps.hpp>
#include <cstring>
namespace IACore
{
INT32 SocketOps::s_initCount{0};
namespace IACore {
i32 SocketOps::s_init_count = 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()
{
auto SocketOps::close(SocketHandle sock) -> void {
if (sock == INVALID_SOCKET) {
return;
}
#if IA_PLATFORM_WINDOWS
return WSAGetLastError() == WSAEWOULDBLOCK;
closesocket(sock);
#else
return errno == EWOULDBLOCK || errno == EAGAIN;
::close(sock);
#endif
}
}
auto SocketOps::listen(SocketHandle sock, i32 queue_size) -> Result<void> {
if (::listen(sock, queue_size) == 0) {
return {};
}
#if IA_PLATFORM_WINDOWS
return fail("listen failed: {}", WSAGetLastError());
#else
return fail("listen failed: {}", errno);
#endif
}
auto SocketOps::create_unix_socket() -> Result<SocketHandle> {
const SocketHandle sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) {
#if IA_PLATFORM_WINDOWS
return fail("socket(AF_UNIX) failed: {}", WSAGetLastError());
#else
return fail("socket(AF_UNIX) failed: {}", errno);
#endif
}
return sock;
}
auto SocketOps::bind_unix_socket(SocketHandle sock, const char *path)
-> Result<void> {
if (sock == INVALID_SOCKET) {
return fail("Invalid socket handle");
}
unlink_file(path);
sockaddr_un addr{};
addr.sun_family = AF_UNIX;
const usize max_len = sizeof(addr.sun_path) - 1;
#if IA_PLATFORM_WINDOWS
strncpy_s(addr.sun_path, sizeof(addr.sun_path), path, max_len);
#else
std::strncpy(addr.sun_path, path, max_len);
#endif
if (::bind(sock, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) ==
-1) {
#if IA_PLATFORM_WINDOWS
return fail("bind failed: {}", WSAGetLastError());
#else
return fail("bind failed: {}", errno);
#endif
}
return {};
}
auto SocketOps::connect_unix_socket(SocketHandle sock, const char *path)
-> Result<void> {
if (sock == INVALID_SOCKET) {
return fail("Invalid socket handle");
}
sockaddr_un addr{};
addr.sun_family = AF_UNIX;
const usize max_len = sizeof(addr.sun_path) - 1;
#if IA_PLATFORM_WINDOWS
strncpy_s(addr.sun_path, sizeof(addr.sun_path), path, max_len);
#else
std::strncpy(addr.sun_path, path, max_len);
#endif
if (::connect(sock, reinterpret_cast<struct sockaddr *>(&addr),
sizeof(addr)) == -1) {
#if IA_PLATFORM_WINDOWS
return fail("connect failed: {}", WSAGetLastError());
#else
return fail("connect failed: {}", errno);
#endif
}
return {};
}
auto SocketOps::is_port_available(u16 port, i32 type) -> bool {
// Use 0 for protocol to let OS select default (TCP for STREAM, UDP for DGRAM)
const SocketHandle sock = socket(AF_INET, type, 0);
if (sock == INVALID_SOCKET) {
return false;
}
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
bool is_free = false;
if (::bind(sock, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) ==
0) {
is_free = true;
}
close(sock);
return is_free;
}
auto SocketOps::is_would_block() -> bool {
#if IA_PLATFORM_WINDOWS
return WSAGetLastError() == WSAEWOULDBLOCK;
#else
return errno == EWOULDBLOCK || errno == EAGAIN;
#endif
}
} // namespace IACore

View File

@ -13,46 +13,36 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include <IACore/StreamReader.hpp>
#include <IACore/FileOps.hpp>
#include <IACore/Logger.hpp>
#include <IACore/StreamReader.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;
}
namespace IACore {
auto StreamReader::create_from_file(const Path &path) -> Result<StreamReader> {
usize size = 0;
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();
}
const u8 *ptr;
IA_TRY(ptr, FileOps::map_file(path, size));
StreamReader::StreamReader(IN Span<CONST UINT8> data)
: m_data(data.data()), m_dataSize(data.size()), m_storageType(EStorageType::NON_OWNING)
{
}
StreamReader reader(Span<const u8>(ptr, size));
reader.m_storage_type = StorageType::OwningMmap;
StreamReader::~StreamReader()
{
switch (m_storageType)
{
case EStorageType::OWNING_MMAP:
FileOps::UnmapFile(m_data);
break;
return reader;
}
case EStorageType::NON_OWNING:
case EStorageType::OWNING_VECTOR:
break;
}
}
StreamReader::StreamReader(Vec<u8> &&data)
: m_owning_vector(std::move(data)),
m_storage_type(StorageType::OwningVector) {
m_data = m_owning_vector.data();
m_data_size = m_owning_vector.size();
}
StreamReader::StreamReader(Span<const u8> data)
: m_data(data.data()), m_data_size(data.size()),
m_storage_type(StorageType::NonOwning) {}
StreamReader::~StreamReader() {
if (m_storage_type == StorageType::OwningMmap) {
FileOps::unmap_file(m_data);
}
}
} // namespace IACore

View File

@ -14,83 +14,88 @@
// limitations under the License.
#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();
namespace IACore {
auto StreamWriter::create(const Path &path) -> Result<StreamWriter> {
// Try to open the file to ensure we have write permissions and to truncate
// it.
FILE *f = std::fopen(path.string().c_str(), "wb");
if (!f) {
return fail("Failed to open file for writing: {}", path.string());
}
std::fclose(f);
StreamWriter writer;
writer.m_file_path = path;
writer.m_storage_type = StorageType::OwningFile;
return writer;
}
StreamWriter::StreamWriter() : m_storage_type(StorageType::OwningVector) {
m_capacity = 256;
m_owning_vector.resize(m_capacity);
m_buffer = m_owning_vector.data();
}
StreamWriter::StreamWriter(Span<u8> data)
: m_buffer(data.data()), m_cursor(0), m_capacity(data.size()),
m_storage_type(StorageType::NonOwning) {}
StreamWriter::~StreamWriter() {
if (m_storage_type == StorageType::OwningFile) {
if (m_file_path.empty()) {
return;
}
StreamWriter::StreamWriter(IN Span<UINT8> data)
: m_buffer(data.data()), m_capacity(data.size()), m_storageType(EStorageType::NON_OWNING)
{
FILE *f = std::fopen(m_file_path.string().c_str(), "wb");
if (f) {
// Write the actual data accumulated in the buffer.
std::fwrite(m_buffer, 1, m_cursor, f);
std::fclose(f);
} else {
// Logger is disabled, print to stderr as a fallback for data loss
// warning.
std::fprintf(stderr, "[IACore] StreamWriter failed to save file: %s\n",
m_file_path.string().c_str());
}
}
}
auto StreamWriter::write(u8 byte, usize count) -> Result<void> {
if (m_cursor + count > m_capacity) {
if (m_storage_type == StorageType::NonOwning) {
return fail("StreamWriter buffer overflow (NonOwning)");
}
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);
// Growth strategy: Current capacity + (count * 2)
const usize new_capacity = m_capacity + (count << 1);
m_owning_vector.resize(new_capacity);
m_capacity = m_owning_vector.size();
m_buffer = m_owning_vector.data();
}
m_owningVector.resize(m_capacity = 256);
m_buffer = m_owningVector.data();
std::memset(m_buffer + m_cursor, byte, count);
m_cursor += count;
return {};
}
auto StreamWriter::write(const void *buffer, usize size) -> Result<void> {
if (m_cursor + size > m_capacity) {
if (m_storage_type == StorageType::NonOwning) {
return fail("StreamWriter buffer overflow (NonOwning)");
}
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;
const usize new_capacity = m_capacity + (size << 1);
m_owning_vector.resize(new_capacity);
m_capacity = m_owning_vector.size();
m_buffer = m_owning_vector.data();
}
case EStorageType::OWNING_VECTOR:
case EStorageType::NON_OWNING:
break;
}
}
std::memcpy(m_buffer + m_cursor, buffer, size);
m_cursor += size;
return {};
}
#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

@ -15,86 +15,77 @@
#include <IACore/StringOps.hpp>
namespace IACore
{
CONST String BASE64_CHAR_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
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;
auto StringOps::encode_base64(Span<const u8> data) -> String {
String result;
result.reserve(((data.size() + 2) / 3) * 4);
for (size_t i = 0; i < data.size(); i += 3) {
uint32_t value = 0;
i32 num_bytes = 0;
for (i32 j = 0; j < 3 && (i + j) < data.size(); ++j) {
value = (value << 8) | data[i + j];
num_bytes++;
}
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;
for (i32 j = 0; j < num_bytes + 1; ++j) {
if (j < 4) {
result += BASE64_CHAR_TABLE[(value >> (6 * (3 - j))) & 0x3F];
}
}
if (num_bytes < 3) {
for (i32 j = 0; j < (3 - num_bytes); ++j) {
result += '=';
}
}
}
return result;
}
auto StringOps::decode_base64(const String &data) -> Vec<u8> {
Vec<u8> result;
const auto is_base64 = [](u8 c) {
return (isalnum(c) || (c == '+') || (c == '/'));
};
i32 in_len = data.size();
i32 i = 0, j = 0, in = 0;
u8 tmp_buf0[4], tmp_buf1[3];
while (in_len-- && (data[in] != '=') && is_base64(data[in])) {
tmp_buf0[i++] = data[in];
in++;
if (i == 4) {
for (i = 0; i < 4; i++)
tmp_buf0[i] = BASE64_CHAR_TABLE.find(tmp_buf0[i]);
tmp_buf1[0] = (tmp_buf0[0] << 2) + ((tmp_buf0[1] & 0x30) >> 4);
tmp_buf1[1] = ((tmp_buf0[1] & 0xf) << 4) + ((tmp_buf0[2] & 0x3c) >> 2);
tmp_buf1[2] = ((tmp_buf0[2] & 0x3) << 6) + tmp_buf0[3];
for (i = 0; (i < 3); i++)
result.push_back(tmp_buf1[i]);
i = 0;
}
}
if (i) {
for (j = i; j < 4; j++)
tmp_buf0[j] = 0;
for (j = 0; j < 4; j++)
tmp_buf0[j] = BASE64_CHAR_TABLE.find(tmp_buf0[j]);
tmp_buf1[0] = (tmp_buf0[0] << 2) + ((tmp_buf0[1] & 0x30) >> 4);
tmp_buf1[1] = ((tmp_buf0[1] & 0xf) << 4) + ((tmp_buf0[2] & 0x3c) >> 2);
tmp_buf1[2] = ((tmp_buf0[2] & 0x3) << 6) + tmp_buf0[3];
for (j = 0; (j < i - 1); j++)
result.push_back(tmp_buf1[j]);
}
return result;
}
} // namespace IACore

View File

@ -0,0 +1,120 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2026 IAS (ias@iasoft.dev)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <IACore/Utils.hpp>
#include <chrono>
#include <cstdlib>
namespace IACore
{
extern std::chrono::high_resolution_clock::time_point g_start_time;
auto Utils::get_unix_time() -> u64
{
const auto now = std::chrono::system_clock::now();
return std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
}
auto Utils::get_ticks_count() -> u64
{
const auto duration = std::chrono::high_resolution_clock::now() - g_start_time;
return std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
}
auto Utils::get_seconds_count() -> f64
{
const auto duration = std::chrono::high_resolution_clock::now() - g_start_time;
return static_cast<f64>(std::chrono::duration_cast<std::chrono::seconds>(duration).count());
}
auto Utils::get_random() -> f32
{
return static_cast<f32>(std::rand()) / static_cast<f32>(RAND_MAX);
}
auto Utils::get_random(u64 max) -> u64
{
return static_cast<u64>(static_cast<f32>(max) * get_random());
}
auto Utils::get_random(i64 min, i64 max) -> i64
{
return min + static_cast<i64>(static_cast<f32>(max - min) * get_random());
}
auto Utils::sleep(u64 milliseconds) -> void
{
std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
}
auto Utils::binary_to_hex_string(Span<const u8> data) -> String
{
static constexpr char lut[] = "0123456789ABCDEF";
auto res = String();
res.reserve(data.size() * 2);
for (const auto b : data)
{
res.push_back(lut[(b >> 4) & 0x0F]);
res.push_back(lut[b & 0x0F]);
}
return res;
}
auto Utils::hex_string_to_binary(StringView hex) -> Result<Vec<u8>>
{
if (hex.size() % 2 != 0)
{
return fail("Hex string must have even length");
}
auto out = Vec<u8>();
out.reserve(hex.size() / 2);
for (usize i = 0; i < hex.size(); i += 2)
{
const auto high = hex[i];
const auto low = hex[i + 1];
const auto from_hex_char = [](char c) -> i32 {
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;
};
const auto h = from_hex_char(high);
const auto l = from_hex_char(low);
if (h == -1 || l == -1)
{
return fail("Invalid hex character found");
}
out.push_back(static_cast<u8>((h << 4) | l));
}
return out;
}
} // namespace IACore

View File

@ -17,39 +17,39 @@
namespace IACore
{
EXPECT(XML::Document) XML::ParseFromString(IN CONST String &data)
Result<XML::Document> XML::parse_from_string(const String &data)
{
Document doc;
const auto parseResult = doc.load_string(data.data());
if (!parseResult)
return MakeUnexpected(std::format("Failed to parse XML: {}", parseResult.description()));
return IA_MOVE(doc);
return fail("Failed to parse XML {}", parseResult.description());
return std::move(doc);
}
EXPECT(XML::Document) XML::ParseFromFile(IN CONST FilePath &path)
Result<XML::Document> XML::parse_from_file(const Path &path)
{
Document doc;
const auto parseResult = doc.load_file(path.string().c_str());
if (!parseResult)
return MakeUnexpected(std::format("Failed to parse XML: {}", parseResult.description()));
return IA_MOVE(doc);
return fail("Failed to parse XML {}", parseResult.description());
return std::move(doc);
}
String XML::SerializeToString(IN CONST Node &node, IN BOOL escape)
String XML::serialize_to_string(const Node &node, bool escape)
{
std::ostringstream oss;
node.print(oss);
return escape ? EscapeXMLString(oss.str()) : oss.str();
return escape ? escape_xml_string(oss.str()) : oss.str();
}
String XML::SerializeToString(IN CONST Document &doc, IN BOOL escape)
String XML::serialize_to_string(const Document &doc, bool escape)
{
std::ostringstream oss;
doc.save(oss);
return escape ? EscapeXMLString(oss.str()) : oss.str();
return escape ? escape_xml_string(oss.str()) : oss.str();
}
String XML::EscapeXMLString(IN CONST String &xml)
String XML::escape_xml_string(const String &xml)
{
String buffer;
buffer.reserve(xml.size() + (xml.size() / 10));

View File

@ -16,210 +16,216 @@
#pragma once
#include <IACore/PCH.hpp>
#include <cstring>
namespace IACore
{
class RingBufferView
{
public:
STATIC CONSTEXPR UINT16 PACKET_ID_SKIP = 0;
namespace IACore {
class RingBufferView {
public:
static constexpr u16 PACKET_ID_SKIP = 0;
struct ControlBlock
{
struct alignas(64)
{
Atomic<UINT32> WriteOffset{0};
} Producer;
struct ControlBlock {
struct alignas(64) {
std::atomic<u32> write_offset{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;
};
struct alignas(64) {
std::atomic<u32> read_offset{0};
// Capacity is effectively constant after init,
// so it doesn't cause false sharing invalidations.
u32 capacity{0};
} consumer;
};
static_assert(offsetof(ControlBlock, Consumer) == 64, "False sharing detected in ControlBlock");
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)
{
}
// All of the data in ring buffer will be stored as packets
struct PacketHeader {
PacketHeader() : id(0), payload_size(0) {}
PacketHeader(IN UINT16 id) : ID(id), PayloadSize(0)
{
}
PacketHeader(u16 id) : id(id), payload_size(0) {}
PacketHeader(IN UINT16 id, IN UINT16 payloadSize) : ID(id), PayloadSize(payloadSize)
{
}
PacketHeader(u16 id, u16 payload_size)
: id(id), payload_size(payload_size) {}
UINT16 ID{};
UINT16 PayloadSize{};
};
u16 id{};
u16 payload_size{};
};
public:
INLINE RingBufferView(IN Span<UINT8> buffer, IN BOOL isOwner);
INLINE RingBufferView(IN ControlBlock *controlBlock, IN Span<UINT8> buffer, IN BOOL isOwner);
public:
RingBufferView(Span<u8> buffer, bool is_owner);
RingBufferView(ControlBlock *control_block, Span<u8> buffer, bool is_owner);
INLINE INT32 Pop(OUT PacketHeader &outHeader, OUT Span<UINT8> outBuffer);
INLINE BOOL Push(IN UINT16 packetID, IN Span<CONST UINT8> data);
// Returns:
// - Ok(nullopt) if empty
// - Ok(bytes_read) if success
// - Error if buffer too small
auto pop(PacketHeader &out_header, Span<u8> out_buffer)
-> Result<Option<usize>>;
INLINE ControlBlock *GetControlBlock();
// Returns:
// - Ok() if success
// - Error if full
auto push(u16 packet_id, Span<const u8> data) -> Result<void>;
private:
PUINT8 m_dataPtr{};
UINT32 m_capacity{};
ControlBlock *m_controlBlock{};
auto get_control_block() -> 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
private:
u8 *m_data_ptr{};
u32 m_capacity{};
ControlBlock *m_control_block{};
namespace IACore
{
RingBufferView::RingBufferView(IN Span<UINT8> buffer, IN BOOL isOwner)
{
IA_ASSERT(buffer.size() > sizeof(ControlBlock));
private:
auto write_wrapped(u32 offset, const void *data, u32 size) -> void;
auto read_wrapped(u32 offset, void *out_data, u32 size) -> void;
};
m_controlBlock = reinterpret_cast<ControlBlock *>(buffer.data());
m_dataPtr = buffer.data() + sizeof(ControlBlock);
// =============================================================================
// Implementation
// =============================================================================
m_capacity = static_cast<UINT32>(buffer.size()) - sizeof(ControlBlock);
inline RingBufferView::RingBufferView(Span<u8> buffer, bool is_owner) {
ensure(buffer.size() > sizeof(ControlBlock),
"Buffer too small for 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);
}
m_control_block = reinterpret_cast<ControlBlock *>(buffer.data());
m_data_ptr = buffer.data() + sizeof(ControlBlock);
RingBufferView::RingBufferView(IN ControlBlock *controlBlock, IN Span<UINT8> buffer, IN BOOL isOwner)
{
IA_ASSERT(controlBlock != nullptr);
IA_ASSERT(buffer.size() > 0);
m_capacity = static_cast<u32>(buffer.size()) - sizeof(ControlBlock);
m_controlBlock = controlBlock;
m_dataPtr = buffer.data();
m_capacity = static_cast<UINT32>(buffer.size());
if (is_owner) {
m_control_block->consumer.capacity = m_capacity;
m_control_block->producer.write_offset.store(0, std::memory_order_release);
m_control_block->consumer.read_offset.store(0, std::memory_order_release);
} else {
ensure(m_control_block->consumer.capacity == m_capacity,
"Capacity mismatch");
}
}
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);
}
}
inline RingBufferView::RingBufferView(ControlBlock *control_block,
Span<u8> buffer, bool is_owner) {
ensure(control_block != nullptr, "ControlBlock is null");
ensure(!buffer.empty(), "Buffer is empty");
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;
m_control_block = control_block;
m_data_ptr = buffer.data();
m_capacity = static_cast<u32>(buffer.size());
if (read == write)
return 0; // Empty
if (is_owner) {
m_control_block->consumer.capacity = m_capacity;
m_control_block->producer.write_offset.store(0, std::memory_order_release);
m_control_block->consumer.read_offset.store(0, std::memory_order_release);
}
}
ReadWrapped(read, &outHeader, sizeof(PacketHeader));
inline auto RingBufferView::pop(PacketHeader &out_header, Span<u8> out_buffer)
-> Result<Option<usize>> {
u32 write =
m_control_block->producer.write_offset.load(std::memory_order_acquire);
u32 read =
m_control_block->consumer.read_offset.load(std::memory_order_relaxed);
u32 cap = m_capacity;
if (outHeader.PayloadSize > outBuffer.size())
return -static_cast<INT32>(outHeader.PayloadSize);
if (read == write) {
return std::nullopt;
}
if (outHeader.PayloadSize > 0)
{
UINT32 dataReadOffset = (read + sizeof(PacketHeader)) % cap;
ReadWrapped(dataReadOffset, outBuffer.data(), outHeader.PayloadSize);
}
read_wrapped(read, &out_header, sizeof(PacketHeader));
// Move read pointer forward
UINT32 newReadOffset = (read + sizeof(PacketHeader) + outHeader.PayloadSize) % cap;
m_controlBlock->Consumer.ReadOffset.store(newReadOffset, std::memory_order_release);
if (out_header.payload_size > out_buffer.size()) {
return fail("Buffer too small: needed {}, provided {}",
out_header.payload_size, out_buffer.size());
}
return outHeader.PayloadSize;
}
if (out_header.payload_size > 0) {
u32 data_read_offset = (read + sizeof(PacketHeader)) % cap;
read_wrapped(data_read_offset, out_buffer.data(), out_header.payload_size);
}
BOOL RingBufferView::Push(IN UINT16 packetID, IN Span<CONST UINT8> data)
{
IA_ASSERT(data.size() <= UINT16_MAX);
// Move read pointer forward
u32 new_read_offset =
(read + sizeof(PacketHeader) + out_header.payload_size) % cap;
m_control_block->consumer.read_offset.store(new_read_offset,
std::memory_order_release);
const UINT32 totalSize = sizeof(PacketHeader) + static_cast<UINT32>(data.size());
return std::make_optional(static_cast<usize>(out_header.payload_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;
inline auto RingBufferView::push(u16 packet_id, Span<const u8> data)
-> Result<void> {
ensure(data.size() <= std::numeric_limits<u16>::max(),
"Data size exceeds u16 limit");
UINT32 freeSpace = (read <= write) ? (m_capacity - write) + read : (read - write);
const u32 total_size = sizeof(PacketHeader) + static_cast<u32>(data.size());
// Ensure to always leave 1 byte empty to prevent Read == Write ambiguity (Wait-Free Ring Buffer standard)
if (freeSpace <= totalSize)
return FALSE;
u32 read =
m_control_block->consumer.read_offset.load(std::memory_order_acquire);
u32 write =
m_control_block->producer.write_offset.load(std::memory_order_relaxed);
u32 cap = m_capacity;
PacketHeader header{packetID, static_cast<UINT16>(data.size())};
WriteWrapped(write, &header, sizeof(PacketHeader));
u32 free_space =
(read <= write) ? (m_capacity - write) + read : (read - write);
UINT32 dataWriteOffset = (write + sizeof(PacketHeader)) % cap;
// Ensure to always leave 1 byte empty to prevent Read == Write ambiguity
if (free_space <= total_size) {
return fail("RingBuffer full");
}
if (data.size() > 0)
{
WriteWrapped(dataWriteOffset, data.data(), static_cast<UINT32>(data.size()));
}
PacketHeader header{packet_id, static_cast<u16>(data.size())};
write_wrapped(write, &header, sizeof(PacketHeader));
UINT32 newWriteOffset = (dataWriteOffset + data.size()) % cap;
m_controlBlock->Producer.WriteOffset.store(newWriteOffset, std::memory_order_release);
u32 data_write_offset = (write + sizeof(PacketHeader)) % cap;
return TRUE;
}
if (!data.empty()) {
write_wrapped(data_write_offset, data.data(),
static_cast<u32>(data.size()));
}
RingBufferView::ControlBlock *RingBufferView::GetControlBlock()
{
return m_controlBlock;
}
u32 new_write_offset = (data_write_offset + data.size()) % cap;
m_control_block->producer.write_offset.store(new_write_offset,
std::memory_order_release);
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;
return {};
}
const UINT8 *src = static_cast<const UINT8 *>(data);
inline auto RingBufferView::get_control_block() -> ControlBlock * {
return m_control_block;
}
memcpy(m_dataPtr + offset, src, firstChunk);
memcpy(m_dataPtr, src + firstChunk, secondChunk);
}
}
inline auto RingBufferView::write_wrapped(u32 offset, const void *data,
u32 size) -> void {
if (offset + size <= m_capacity) {
// Contiguous write
std::memcpy(m_data_ptr + offset, data, size);
} else {
// Split write
u32 first_chunk = m_capacity - offset;
u32 second_chunk = size - first_chunk;
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;
const u8 *src = static_cast<const u8 *>(data);
UINT8 *dst = static_cast<UINT8 *>(outData);
std::memcpy(m_data_ptr + offset, src, first_chunk);
std::memcpy(m_data_ptr, src + first_chunk, second_chunk);
}
}
inline auto RingBufferView::read_wrapped(u32 offset, void *out_data, u32 size)
-> void {
if (offset + size <= m_capacity) {
// Contiguous read
std::memcpy(out_data, m_data_ptr + offset, size);
} else {
// Split read
u32 first_chunk = m_capacity - offset;
u32 second_chunk = size - first_chunk;
u8 *dst = static_cast<u8 *>(out_data);
std::memcpy(dst, m_data_ptr + offset, first_chunk);
std::memcpy(dst + first_chunk, m_data_ptr, second_chunk);
}
}
memcpy(dst, m_dataPtr + offset, firstChunk);
memcpy(dst + firstChunk, m_dataPtr, secondChunk);
}
}
} // namespace IACore

View File

@ -16,58 +16,55 @@
#pragma once
#include <IACore/PCH.hpp>
#include <deque>
#include <functional>
#include <stop_token>
namespace IACore
{
class AsyncOps
{
public:
using TaskTag = UINT64;
using WorkerID = UINT16;
namespace IACore {
class AsyncOps {
public:
using TaskTag = u64;
using WorkerId = u16;
STATIC CONSTEXPR WorkerID MainThreadWorkerID = 0;
static constexpr WorkerId MAIN_THREAD_WORKER_ID = 0;
enum class Priority : UINT8
{
High,
Normal
};
enum class Priority : u8 { High, Normal };
struct Schedule
{
Atomic<INT32> Counter{0};
};
struct Schedule {
std::atomic<i32> counter{0};
};
public:
STATIC VOID InitializeScheduler(IN UINT8 workerCount = 0);
STATIC VOID TerminateScheduler();
public:
static auto initialize_scheduler(u8 worker_count = 0) -> Result<void>;
static auto terminate_scheduler() -> void;
STATIC VOID ScheduleTask(IN Function<VOID(IN WorkerID workerID)> task, IN TaskTag tag, IN Schedule *schedule,
IN Priority priority = Priority::Normal);
static auto schedule_task(std::function<void(WorkerId worker_id)> task,
TaskTag tag, Schedule *schedule,
Priority priority = Priority::Normal) -> void;
STATIC VOID CancelTasksOfTag(IN TaskTag tag);
static auto cancel_tasks_of_tag(TaskTag tag) -> void;
STATIC VOID WaitForScheduleCompletion(IN Schedule *schedule);
static auto wait_for_schedule_completion(Schedule *schedule) -> void;
STATIC VOID RunTask(IN Function<VOID()> task);
static auto run_task(std::function<void()> task) -> void;
STATIC WorkerID GetWorkerCount();
[[nodiscard]] static auto get_worker_count() -> WorkerId;
private:
struct ScheduledTask
{
TaskTag Tag{};
Schedule *ScheduleHandle{};
Function<VOID(IN WorkerID workerID)> Task{};
};
private:
struct ScheduledTask {
TaskTag tag{};
Schedule *schedule_handle{};
std::function<void(WorkerId worker_id)> task{};
};
STATIC VOID ScheduleWorkerLoop(IN StopToken stopToken, IN WorkerID workerID);
static auto schedule_worker_loop(std::stop_token stop_token,
WorkerId worker_id) -> void;
private:
STATIC Mutex s_queueMutex;
STATIC ConditionVariable s_wakeCondition;
STATIC Vector<JoiningThread> s_scheduleWorkers;
STATIC Deque<ScheduledTask> s_highPriorityQueue;
STATIC Deque<ScheduledTask> s_normalPriorityQueue;
};
private:
static std::mutex s_queue_mutex;
static std::condition_variable s_wake_condition;
static Vec<std::jthread> s_schedule_workers;
static std::deque<ScheduledTask> s_high_priority_queue;
static std::deque<ScheduledTask> s_normal_priority_queue;
};
} // namespace IACore

View File

@ -30,41 +30,41 @@ namespace IACore
*/
public:
CLIParser(IN Span<CONST String> args);
CLIParser(Span<const String> args);
~CLIParser() = default;
public:
BOOL Remaining() CONST
auto remaining() const -> bool
{
return m_currentArg < m_argList.end();
}
StringView Peek() CONST
auto peek() const -> StringView
{
if (!Remaining())
if (!remaining())
return "";
return *m_currentArg;
}
StringView Next()
auto next() -> StringView
{
if (!Remaining())
if (!remaining())
return "";
return *m_currentArg++;
}
BOOL Consume(IN CONST StringView &expected)
auto consume(const StringView &expected) -> bool
{
if (Peek() == expected)
if (peek() == expected)
{
Next();
next();
return true;
}
return false;
}
private:
CONST Span<CONST String> m_argList;
Span<CONST String>::const_iterator m_currentArg;
const Span<const String> m_argList;
Span<const String>::const_iterator m_currentArg;
};
} // namespace IACore

View File

@ -17,36 +17,29 @@
#include <IACore/PCH.hpp>
namespace IACore
{
class DataOps
{
public:
enum class CompressionType
{
None,
Gzip,
Zlib
};
namespace IACore {
class DataOps {
public:
enum class CompressionType { None, Gzip, Zlib };
public:
STATIC UINT32 Hash_FNV1A(IN CONST String &string);
STATIC UINT32 Hash_FNV1A(IN Span<CONST UINT8> data);
public:
static auto hash_fnv1a(const String &string) -> u32;
static auto hash_fnv1a(Span<const u8> data) -> u32;
STATIC UINT32 Hash_xxHash(IN CONST String &string, IN UINT32 seed = 0);
STATIC UINT32 Hash_xxHash(IN Span<CONST UINT8> data, IN UINT32 seed = 0);
static auto hash_xxhash(const String &string, u32 seed = 0) -> u32;
static auto hash_xxhash(Span<const u8> data, u32 seed = 0) -> u32;
STATIC UINT32 CRC32(IN Span<CONST UINT8> data);
static auto crc32(Span<const u8> data) -> u32;
STATIC CompressionType DetectCompression(IN Span<CONST UINT8> data);
static auto detect_compression(Span<const u8> data) -> CompressionType;
STATIC EXPECT(Vector<UINT8>) GZipInflate(IN Span<CONST UINT8> data);
STATIC EXPECT(Vector<UINT8>) GZipDeflate(IN Span<CONST UINT8> data);
static auto gzip_inflate(Span<const u8> data) -> Result<Vec<u8>>;
static auto gzip_deflate(Span<const u8> data) -> Result<Vec<u8>>;
STATIC EXPECT(Vector<UINT8>) ZlibInflate(IN Span<CONST UINT8> data);
STATIC EXPECT(Vector<UINT8>) ZlibDeflate(IN Span<CONST UINT8> data);
static auto zlib_inflate(Span<const u8> data) -> Result<Vec<u8>>;
static auto zlib_deflate(Span<const u8> data) -> Result<Vec<u8>>;
STATIC EXPECT(Vector<UINT8>) ZstdInflate(IN Span<CONST UINT8> data);
STATIC EXPECT(Vector<UINT8>) ZstdDeflate(IN Span<CONST UINT8> data);
};
static auto zstd_inflate(Span<const u8> data) -> Result<Vec<u8>>;
static auto zstd_deflate(Span<const u8> data) -> Result<Vec<u8>>;
};
} // namespace IACore

View File

@ -18,161 +18,143 @@
#include <IACore/PCH.hpp>
#if IA_PLATFORM_WINDOWS
# include <libloaderapi.h>
# include <errhandlingapi.h>
#include <errhandlingapi.h>
#include <libloaderapi.h>
#else
# include <dlfcn.h>
#include <dlfcn.h>
#endif
namespace IACore
{
namespace IACore {
class DynamicLib
{
public:
DynamicLib() : m_handle(nullptr)
{
}
class DynamicLib {
public:
DynamicLib() : m_handle(nullptr) {}
DynamicLib(DynamicLib &&other) NOEXCEPT : m_handle(other.m_handle)
{
other.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 &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(const DynamicLib &) = delete;
DynamicLib &operator=(const DynamicLib &) = delete;
~DynamicLib()
{
Unload();
}
~DynamicLib() { Unload(); }
// Automatically detects extension (.dll/.so) if not provided
NO_DISCARD("Check for load errors")
// Automatically detects extension (.dll/.so) if not provided
NO_DISCARD("Check for load errors")
STATIC tl::EXPECT(DynamicLib) Load(CONST String &searchPath, CONST String &name)
{
namespace fs = std::filesystem;
static tl::Result<DynamicLib> Load(const String &searchPath,
const String &name) {
namespace fs = std::filesystem;
fs::path fullPath = fs::path(searchPath) / name;
fs::path fullPath = fs::path(searchPath) / name;
if (!fullPath.has_extension())
{
if (!fullPath.has_extension()) {
#if IA_PLATFORM_WINDOWS
fullPath += ".dll";
fullPath += ".dll";
#elif IA_PLATFORM_MAC
fullPath += ".dylib";
fullPath += ".dylib";
#else
fullPath += ".so";
fullPath += ".so";
#endif
}
}
DynamicLib lib;
DynamicLib lib;
#if IA_PLATFORM_WINDOWS
HMODULE h = LoadLibraryA(fullPath.string().c_str());
if (!h)
{
return MakeUnexpected(GetWindowsError());
}
lib.m_handle = CAST(h, PVOID);
HMODULE h = LoadLibraryA(fullPath.string().c_str());
if (!h) {
return (GetWindowsError());
}
lib.m_handle = CAST(h, void *);
#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 MakeUnexpected(String(err ? err : "Unknown dlopen error"));
}
lib.m_handle = h;
// 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 (String(err ? err : "Unknown dlopen error"));
}
lib.m_handle = h;
#endif
return IA_MOVE(lib);
}
return std::move(lib);
}
NO_DISCARD("Check if symbol exists")
NO_DISCARD("Check if symbol exists")
tl::EXPECT(PVOID) GetSymbol(CONST String &name) CONST
{
if (!m_handle)
return MakeUnexpected(String("Library not loaded"));
tl::Result<void *> GetSymbol(const String &name) const {
if (!m_handle)
return (String("Library not loaded"));
PVOID sym = nullptr;
void *sym = nullptr;
#if IA_PLATFORM_WINDOWS
sym = CAST(GetProcAddress(CAST(m_handle, HMODULE), name.c_str()), PVOID);
if (!sym)
return MakeUnexpected(GetWindowsError());
sym = CAST(GetProcAddress(CAST(m_handle, HMODULE), name.c_str()), void *);
if (!sym)
return (GetWindowsError());
#else
// Clear any previous error
dlerror();
sym = dlsym(m_handle, name.c_str());
const char *err = dlerror();
if (err)
return MakeUnexpected(String(err));
// Clear any previous error
dlerror();
sym = dlsym(m_handle, name.c_str());
const char *err = dlerror();
if (err)
return (String(err));
#endif
return sym;
}
return sym;
}
// Template helper for casting
template<typename FuncT> tl::EXPECT(FuncT) GetFunction(CONST String &name) CONST
{
auto res = GetSymbol(name);
if (!res)
return MakeUnexpected(res.error());
return REINTERPRET(*res, FuncT);
}
// Template helper for casting
template <typename FuncT>
tl::Result<FuncT> GetFunction(const String &name) const {
auto res = GetSymbol(name);
if (!res)
return (res.error());
return REINTERPRET(*res, FuncT);
}
VOID Unload()
{
if (m_handle)
{
void Unload() {
if (m_handle) {
#if IA_PLATFORM_WINDOWS
FreeLibrary(CAST(m_handle, HMODULE));
FreeLibrary(CAST(m_handle, HMODULE));
#else
dlclose(m_handle);
dlclose(m_handle);
#endif
m_handle = nullptr;
}
}
m_handle = nullptr;
}
}
BOOL IsLoaded() CONST
{
return m_handle != nullptr;
}
bool IsLoaded() const { return m_handle != nullptr; }
private:
PVOID m_handle;
private:
void *m_handle;
#if IA_PLATFORM_WINDOWS
STATIC String GetWindowsError()
{
DWORD errorID = ::GetLastError();
if (errorID == 0)
return String();
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);
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;
}
String message(messageBuffer, size);
LocalFree(messageBuffer);
return String("Win32 Error: ") + message;
}
#endif
};
};
} // namespace IACore

View File

@ -22,7 +22,7 @@ namespace IACore
class Environment
{
public:
STATIC Optional<String> Find(CONST String &name)
static Optional<String> Find(const String &name)
{
#if IA_PLATFORM_WINDOWS
DWORD bufferSize = GetEnvironmentVariableA(name.c_str(), nullptr, 0);
@ -61,12 +61,12 @@ namespace IACore
#endif
}
STATIC String Get(CONST String &name, CONST String &defaultValue = "")
static String Get(const String &name, const String &defaultValue = "")
{
return Find(name).value_or(defaultValue);
}
STATIC BOOL Set(CONST String &name, CONST String &value)
static bool Set(const String &name, const String &value)
{
if (name.empty())
return FALSE;
@ -79,7 +79,7 @@ namespace IACore
#endif
}
STATIC BOOL Unset(CONST String &name)
static bool Unset(const String &name)
{
if (name.empty())
return FALSE;
@ -91,7 +91,7 @@ namespace IACore
#endif
}
STATIC BOOL Exists(CONST String &name)
static bool Exists(const String &name)
{
#if IA_PLATFORM_WINDOWS
return GetEnvironmentVariableA(name.c_str(), nullptr, 0) > 0;

View File

@ -15,107 +15,107 @@
#pragma once
#include <IACore/PCH.hpp>
#include <IACore/StreamReader.hpp>
#include <IACore/StreamWriter.hpp>
#include <tuple>
#if IA_PLATFORM_WINDOWS
using NativeFileHandle = HANDLE;
STATIC CONSTEXPR NativeFileHandle INVALID_FILE_HANDLE = INVALID_HANDLE_VALUE;
static constexpr NativeFileHandle INVALID_FILE_HANDLE = INVALID_HANDLE_VALUE;
#else
using NativeFileHandle = int;
STATIC CONSTEXPR NativeFileHandle INVALID_FILE_HANDLE = -1;
static constexpr NativeFileHandle INVALID_FILE_HANDLE = -1;
#endif
namespace IACore
{
class FileOps
{
public:
class MemoryMappedRegion;
namespace IACore {
enum class EFileAccess : UINT8
{
READ, // Read-only
WRITE, // Write-only
READ_WRITE // Read and Write
};
class FileOps {
public:
class MemoryMappedRegion;
enum class EFileMode : UINT8
{
OPEN_EXISTING, // Fails if file doesn't exist
OPEN_ALWAYS, // Opens if exists, creates if not
CREATE_NEW, // Fails if file exists
CREATE_ALWAYS, // Overwrites existing
TRUNCATE_EXISTING // Opens existing and clears it
};
enum class FileAccess : u8 {
Read, // Read-only
Write, // Write-only
ReadWrite // Read and Write
};
STATIC EXPECT(NativeFileHandle) NativeOpenFile(IN CONST FilePath &path, IN EFileAccess access,
IN EFileMode mode, IN UINT32 permissions = 0644);
STATIC VOID NativeCloseFile(IN NativeFileHandle handle);
enum class FileMode : u8 {
OpenExisting, // Fails if file doesn't exist
OpenAlways, // Opens if exists, creates if not
CreateNew, // Fails if file exists
CreateAlways, // Overwrites existing
TruncateExisting // Opens existing and clears it
};
public:
STATIC FilePath NormalizeExecutablePath(IN CONST FilePath &path);
static auto native_open_file(const Path &path, FileAccess access,
FileMode mode, u32 permissions = 0644)
-> Result<NativeFileHandle>;
public:
STATIC VOID UnmapFile(IN PCUINT8 mappedPtr);
STATIC EXPECT(PCUINT8) MapFile(IN CONST FilePath &path, OUT SIZE_T &size);
static auto native_close_file(NativeFileHandle handle) -> void;
// @param `isOwner` TRUE to allocate/truncate. FALSE to just open.
STATIC EXPECT(PUINT8) MapSharedMemory(IN CONST String &name, IN SIZE_T size, IN BOOL isOwner);
STATIC VOID UnlinkSharedMemory(IN CONST String &name);
public:
static auto normalize_executable_path(const Path &path) -> Path;
STATIC EXPECT(StreamReader) StreamFromFile(IN CONST FilePath &path);
STATIC EXPECT(StreamWriter) StreamToFile(IN CONST FilePath &path, IN BOOL overwrite = false);
public:
static auto unmap_file(const u8 *mapped_ptr) -> void;
STATIC EXPECT(String) ReadTextFile(IN CONST FilePath &path);
STATIC EXPECT(Vector<UINT8>) ReadBinaryFile(IN CONST FilePath &path);
STATIC EXPECT(SIZE_T)
WriteTextFile(IN CONST FilePath &path, IN CONST String &contents, IN BOOL overwrite = false);
STATIC EXPECT(SIZE_T)
WriteBinaryFile(IN CONST FilePath &path, IN Span<UINT8> contents, IN BOOL overwrite = false);
static auto map_file(const Path &path, usize &size) -> Result<const u8 *>;
private:
STATIC UnorderedMap<PCUINT8, Tuple<PVOID, PVOID, PVOID>> s_mappedFiles;
};
// @param `is_owner` true to allocate/truncate. false to just open.
static auto map_shared_memory(const String &name, usize size, bool is_owner)
-> Result<u8 *>;
class FileOps::MemoryMappedRegion
{
public:
MemoryMappedRegion() = default;
~MemoryMappedRegion();
static auto unlink_shared_memory(const String &name) -> void;
MemoryMappedRegion(CONST MemoryMappedRegion &) = delete;
MemoryMappedRegion &operator=(CONST MemoryMappedRegion &) = delete;
static auto stream_from_file(const Path &path) -> Result<StreamReader>;
MemoryMappedRegion(MemoryMappedRegion &&other) NOEXCEPT;
MemoryMappedRegion &operator=(MemoryMappedRegion &&other) NOEXCEPT;
static auto stream_to_file(const Path &path, bool overwrite = false)
-> Result<StreamWriter>;
EXPECT(VOID) Map(NativeFileHandle handle, UINT64 offset, SIZE_T size);
static auto read_text_file(const Path &path) -> Result<String>;
VOID Unmap();
VOID Flush();
static auto read_binary_file(const Path &path) -> Result<Vec<u8>>;
PUINT8 GetPtr() CONST
{
return m_ptr;
}
static auto write_text_file(const Path &path, const String &contents,
bool overwrite = false) -> Result<usize>;
SIZE_T GetSize() CONST
{
return m_size;
}
static auto write_binary_file(const Path &path, Span<const u8> contents,
bool overwrite = false) -> Result<usize>;
BOOL IsValid() CONST
{
return m_ptr != nullptr;
}
private:
static HashMap<const u8 *, std::tuple<void *, void *, void *>> s_mapped_files;
};
private:
PUINT8 m_ptr{nullptr};
SIZE_T m_size{0};
class FileOps::MemoryMappedRegion {
public:
MemoryMappedRegion() = default;
~MemoryMappedRegion();
MemoryMappedRegion(const MemoryMappedRegion &) = delete;
auto operator=(const MemoryMappedRegion &) -> MemoryMappedRegion & = delete;
MemoryMappedRegion(MemoryMappedRegion &&other) noexcept;
auto operator=(MemoryMappedRegion &&other) noexcept -> MemoryMappedRegion &;
auto map(NativeFileHandle handle, u64 offset, usize size) -> Result<void>;
auto unmap() -> void;
auto flush() -> void;
[[nodiscard]] auto get_ptr() const -> u8 * { return m_ptr; }
[[nodiscard]] auto get_size() const -> usize { return m_size; }
[[nodiscard]] auto is_valid() const -> bool { return m_ptr != nullptr; }
private:
u8 *m_ptr = nullptr;
usize m_size = 0;
#if IA_PLATFORM_WINDOWS
HANDLE m_hMap{NULL};
HANDLE m_map_handle = NULL;
#endif
};
};
} // namespace IACore

View File

@ -17,74 +17,72 @@
#include <IACore/Http/Common.hpp>
namespace IACore
{
class HttpClient : public HttpCommon
{
public:
STATIC EXPECT(UniquePtr<HttpClient>) Create(IN CONST String &host);
namespace IACore {
class HttpClient : public HttpCommon {
public:
static Result<UniquePtr<HttpClient>> Create(const String &host);
~HttpClient();
~HttpClient();
public:
EXPECT(String)
RawGet(IN CONST String &path, IN Span<CONST Header> headers,
IN PCCHAR defaultContentType = "application/x-www-form-urlencoded");
EXPECT(String)
RawPost(IN CONST String &path, IN Span<CONST Header> headers, IN CONST String &body,
IN PCCHAR defaultContentType = "application/x-www-form-urlencoded");
public:
Result<String>
RawGet(const String &path, Span<const Header> headers,
const char *defaultContentType = "application/x-www-form-urlencoded");
Result<String>
RawPost(const String &path, Span<const Header> headers, const String &body,
const char *defaultContentType = "application/x-www-form-urlencoded");
template<typename _response_type>
EXPECT(_response_type)
JsonGet(IN CONST String &path, IN Span<CONST Header> headers);
template <typename _response_type>
Result<_response_type> JsonGet(const String &path,
Span<const Header> headers);
template<typename _payload_type, typename _response_type>
EXPECT(_response_type)
JsonPost(IN CONST String &path, IN Span<CONST Header> headers, IN CONST _payload_type &body);
template <typename _payload_type, typename _response_type>
Result<_response_type> JsonPost(const String &path,
Span<const Header> headers,
const _payload_type &body);
// Certificate verfication is enabled by default
VOID EnableCertificateVerfication();
VOID DisableCertificateVerfication();
// Certificate verfication is enabled by default
void EnableCertificateVerfication();
void DisableCertificateVerfication();
public:
EResponseCode LastResponseCode()
{
return m_lastResponseCode;
}
public:
EResponseCode LastResponseCode() { return m_lastResponseCode; }
private:
httplib::Client m_client;
EResponseCode m_lastResponseCode;
private:
httplib::Client m_client;
EResponseCode m_lastResponseCode;
private:
String PreprocessResponse(IN CONST String &response);
private:
String PreprocessResponse(const String &response);
protected:
HttpClient(IN httplib::Client &&client);
};
protected:
HttpClient(httplib::Client &&client);
};
template<typename _response_type>
EXPECT(_response_type)
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 _response_type>
Result<_response_type> HttpClient::JsonGet(const String &path,
Span<const Header> headers) {
const auto rawResponse = RawGet(path, headers, "application/json");
if (!rawResponse)
return (rawResponse.error());
if (LastResponseCode() != EResponseCode::OK)
return (
std::format("Server responded with code {}", (i32)LastResponseCode()));
return JSON::ParseToStruct<_response_type>(*rawResponse);
}
template<typename _payload_type, typename _response_type>
EXPECT(_response_type)
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);
}
template <typename _payload_type, typename _response_type>
Result<_response_type> HttpClient::JsonPost(const String &path,
Span<const Header> headers,
const _payload_type &body) {
const auto encodedBody = IA_TRY(JSON::EncodeStruct(body));
const auto rawResponse =
RawPost(path, headers, encodedBody, "application/json");
if (!rawResponse)
return (rawResponse.error());
if (LastResponseCode() != EResponseCode::OK)
return (
std::format("Server responded with code {}", (i32)LastResponseCode()));
return JSON::ParseToStruct<_response_type>(*rawResponse);
}
} // namespace IACore

View File

@ -19,139 +19,135 @@
#include <httplib.h>
namespace IACore
{
class HttpCommon
{
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
};
namespace IACore {
class HttpCommon {
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,
enum class EResponseCode : i32 {
// 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,
// 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,
// 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,
// 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
};
// 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<String, String>;
using Header = KeyValuePair<String, String>;
STATIC String UrlEncode(IN CONST String &value);
STATIC String UrlDecode(IN CONST String &value);
static String UrlEncode(const String &value);
static String UrlDecode(const String &value);
STATIC String HeaderTypeToString(IN EHeaderType type);
static String HeaderTypeToString(EHeaderType type);
STATIC INLINE Header CreateHeader(IN EHeaderType key, IN CONST String &value);
STATIC INLINE Header CreateHeader(IN CONST String &key, IN CONST String &value);
static inline Header CreateHeader(EHeaderType key, const String &value);
static inline Header CreateHeader(const String &key, const String &value);
STATIC BOOL IsSuccessResponseCode(IN EResponseCode code);
static bool IsSuccessResponseCode(EResponseCode code);
protected:
HttpCommon() = default;
};
protected:
HttpCommon() = default;
};
HttpCommon::Header HttpCommon::CreateHeader(IN EHeaderType key, IN CONST String &value)
{
return std::make_pair(HeaderTypeToString(key), value);
}
HttpCommon::Header HttpCommon::CreateHeader(EHeaderType key,
const String &value) {
return std::make_pair(HeaderTypeToString(key), value);
}
HttpCommon::Header HttpCommon::CreateHeader(IN CONST String &key, IN CONST String &value)
{
return std::make_pair(key, value);
}
HttpCommon::Header HttpCommon::CreateHeader(const String &key,
const String &value) {
return std::make_pair(key, value);
}
} // namespace IACore

View File

@ -22,6 +22,6 @@ namespace IACore
class HttpServer : public HttpCommon
{
public:
HttpServer(IN CONST String &host, IN UINT32 port);
HttpServer(const String &host, u32 port);
};
} // namespace IACore

View File

@ -16,58 +16,54 @@
#pragma once
#include <IACore/PCH.hpp>
#include <IACore/Logger.hpp>
#ifdef __cplusplus
# include <IACore/Logger.hpp>
# define IACORE_MAIN() \
EXPECT(INT32) _app_entry(IN CONST Vector<String> &args); \
int main(int argc, char *argv[]) \
#define IACORE_MAIN() \
auto _app_entry(const IACore::Vec<IACore::String> &args) -> IACore::Result<IACore::i32>; \
auto main(int argc, char *argv[]) -> int \
{ \
IACore::i32 exit_code = 0; \
IACore::initialize(); \
IACore::Vec<IACore::String> args; \
args.reserve(static_cast<IACore::usize>(argc)); \
for (int i = 0; i < argc; ++i) \
{ \
int exitCode = 0; \
IACore::Initialize(); \
Vector<String> args; \
for (int i = 0; i < argc; i++) \
args.push_back(argv[i]); \
const auto result = _app_entry(args); \
if (!result) \
{ \
IACore::Logger::Error("Application exited with an error: '{}'.", result.error()); \
exitCode = -20; \
} \
exitCode = *result; \
if (!exitCode) \
IACore::Logger::Info("Application exited successfully."); \
else \
IACore::Logger::Error("Application exited with error code: {}.", exitCode); \
IACore::Terminate(); \
return exitCode; \
args.push_back(argv[i]); \
} \
EXPECT(INT32) _app_entry(IN CONST Vector<String> &args)
const auto result = _app_entry(args); \
if (!result) \
{ \
IACore::Logger::error("Application exited with an error: '{}'.", result.error()); \
exit_code = -20; \
} \
else \
{ \
exit_code = *result; \
if (exit_code == 0) \
{ \
IACore::Logger::info("Application exited successfully."); \
} \
else \
{ \
IACore::Logger::error("Application exited with error code: {}.", exit_code); \
} \
} \
IACore::terminate(); \
return exit_code; \
} \
auto _app_entry(const IACore::Vec<IACore::String> &args) -> IACore::Result<IACore::i32>
namespace IACore
{
// Must be called from main thread
// Safe to call multiple times but, every Initialize call is paired with a corresponding Terminate call
VOID Initialize();
// Safe to call multiple times but, every initialize call is paired with a corresponding terminate call
auto initialize() -> void;
// Must be called from same thread as Initialize
// Safe to call multiple times but, every Initialize call is paired with a corresponding Terminate call
VOID Terminate();
// Must be called from same thread as initialize
// Safe to call multiple times but, every initialize call is paired with a corresponding terminate call
auto terminate() -> void;
BOOL IsInitialized();
auto is_initialized() -> bool;
UINT64 GetUnixTime();
UINT64 GetTicksCount();
FLOAT64 GetSecondsCount();
FLOAT32 GetRandom();
INT64 GetRandom(IN INT64 min, IN INT64 max);
UINT64 GetRandom(IN UINT64 max);
BOOL IsMainThread();
VOID Sleep(IN UINT64 milliseconds);
} // namespace IACore
#endif
auto is_main_thread() -> bool;
} // namespace IACore

View File

@ -17,295 +17,287 @@
#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_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(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_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_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_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() \
#define IAT_BEGIN_BLOCK(_group, _name) \
class _group##_##_name : public ia::iatest::Block \
{ \
public: \
VOID declareTests() OVERRIDE \
{
# define IAT_ADD_TEST(name) IAT_UNIT(name)
# define IAT_END_TEST_LIST() \
[[nodiscard]] auto get_name() const -> const char * override \
{ \
return #_group "::" #_name; \
} \
\
private:
namespace ia::iatest
#define IAT_END_BLOCK() \
} \
;
#define IAT_BEGIN_TEST_LIST() \
public: \
void declare_tests() override \
{
#define IAT_ADD_TEST(name) IAT_UNIT(name)
#define IAT_END_TEST_LIST() \
} \
\
private:
namespace IACore
{
template<typename T> std::string ToString(CONST T &value)
// -------------------------------------------------------------------------
// String Conversion Helpers
// -------------------------------------------------------------------------
template<typename T> auto to_string(const T &value) -> String
{
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 if constexpr (std::is_same_v<T, String> || std::is_same_v<T, const char *> || std::is_same_v<T, char *>)
{
return String("\"") + String(value) + "\"";
}
else
{
return "{Object}";
}
}
template<typename T> std::string ToString(T *value)
template<typename T> auto to_string(T *value) -> String
{
if (value == NULLPTR)
if (value == nullptr)
{
return "nullptr";
std::stringstream ss;
ss << "ptr(" << (void *) value << ")";
return ss.str();
}
return std::format("ptr({})", static_cast<const void *>(value));
}
DEFINE_TYPE(functor_t, std::function<BOOL()>);
// -------------------------------------------------------------------------
// Types
// -------------------------------------------------------------------------
using TestFunctor = std::function<bool()>;
struct unit_t
struct TestUnit
{
std::string Name;
functor_t Functor;
String name;
TestFunctor functor;
};
class block
class Block
{
public:
virtual ~block() = default;
PURE_VIRTUAL(PCCHAR name() CONST);
PURE_VIRTUAL(VOID declareTests());
virtual ~Block() = default;
[[nodiscard]] virtual auto get_name() const -> const char * = 0;
virtual void declare_tests() = 0;
std::vector<unit_t> &units()
auto units() -> Vec<TestUnit> &
{
return m_units;
}
protected:
template<typename T1, typename T2> BOOL _test_eq(IN CONST T1 &lhs, IN CONST T2 &rhs, IN PCCHAR description)
template<typename T1, typename T2> auto _test_eq(const T1 &lhs, const T2 &rhs, const char *description) -> bool
{
if (lhs != rhs)
{
print_fail(description, ToString(lhs), ToString(rhs));
return FALSE;
print_fail(description, to_string(lhs), to_string(rhs));
return false;
}
return TRUE;
return true;
}
template<typename T1, typename T2> BOOL _test_neq(IN CONST T1 &lhs, IN CONST T2 &rhs, IN PCCHAR description)
template<typename T1, typename T2> auto _test_neq(const T1 &lhs, const T2 &rhs, const char *description) -> bool
{
if (lhs == rhs)
{
print_fail(description, ToString(lhs), "NOT " + ToString(rhs));
return FALSE;
print_fail(description, to_string(lhs), "NOT " + to_string(rhs));
return false;
}
return TRUE;
return true;
}
template<typename T> BOOL _test_approx(IN T lhs, IN T rhs, IN PCCHAR description)
template<typename T> auto _test_approx(T lhs, T rhs, const char *description) -> bool
{
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;
print_fail(description, to_string(lhs), to_string(rhs));
return false;
}
return TRUE;
return true;
}
BOOL _test(IN BOOL value, IN PCCHAR description)
auto _test(bool value, const char *description) -> bool
{
if (!value)
{
printf(__CC_BLUE " %s... " __CC_RED "FAILED" __CC_DEFAULT "\n", description);
return FALSE;
std::cout << console::blue << " " << description << "... " << console::red << "FAILED"
<< console::reset << "\n";
return false;
}
return TRUE;
return true;
}
BOOL _test_not(IN BOOL value, IN PCCHAR description)
auto _test_not(bool value, const char *description) -> bool
{
if (value)
{
printf(__CC_BLUE " %s... " __CC_RED "FAILED" __CC_DEFAULT "\n", description);
return FALSE;
std::cout << console::blue << " " << description << "... " << console::red << "FAILED"
<< console::reset << "\n";
return false;
}
return TRUE;
return true;
}
VOID _test_unit(IN functor_t functor, IN PCCHAR name)
void _test_unit(TestFunctor functor, const char *name)
{
m_units.push_back({name, functor});
m_units.push_back({name, std::move(functor)});
}
private:
VOID print_fail(PCCHAR desc, std::string v1, std::string v2)
void print_fail(const char *desc, const String &v1, const 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::cout << console::blue << " " << desc << "... " << console::red << "FAILED" << console::reset
<< "\n";
std::cout << console::red << " Expected: " << v2 << console::reset << "\n";
std::cout << console::red << " Actual: " << v1 << console::reset << "\n";
}
std::vector<unit_t> m_units;
Vec<TestUnit> m_units;
};
template<typename block_class>
concept valid_block_class = std::derived_from<block_class, block>;
template<typename T>
concept ValidBlockClass = std::derived_from<T, Block>;
template<BOOL stopOnFail = false, BOOL isVerbose = false> class runner
// -------------------------------------------------------------------------
// Runner
// -------------------------------------------------------------------------
template<bool StopOnFail = false, bool IsVerbose = false> class Runner
{
public:
runner()
{
}
Runner() = default;
~runner()
~Runner()
{
summarize();
}
template<typename block_class>
requires valid_block_class<block_class>
VOID testBlock();
template<typename BlockClass>
requires ValidBlockClass<BlockClass>
void test_block();
private:
VOID summarize();
void summarize();
private:
SIZE_T m_testCount{0};
SIZE_T m_failCount{0};
SIZE_T m_blockCount{0};
usize m_test_count{0};
usize m_fail_count{0};
usize m_block_count{0};
};
template<BOOL stopOnFail, BOOL isVerbose>
template<typename block_class>
requires valid_block_class<block_class>
VOID runner<stopOnFail, isVerbose>::testBlock()
template<bool StopOnFail, bool IsVerbose>
template<typename BlockClass>
requires ValidBlockClass<BlockClass>
void Runner<StopOnFail, IsVerbose>::test_block()
{
m_blockCount++;
block_class b;
b.declareTests();
m_block_count++;
BlockClass b;
b.declare_tests();
printf(__CC_MAGENTA "Testing [%s]..." __CC_DEFAULT "\n", b.name());
std::cout << console::magenta << "Testing [" << b.get_name() << "]..." << console::reset << "\n";
for (auto &v : b.units())
{
m_testCount++;
if constexpr (isVerbose)
m_test_count++;
if constexpr (IsVerbose)
{
printf(__CC_YELLOW " Testing %s...\n" __CC_DEFAULT, v.Name.c_str());
std::cout << console::yellow << " Testing " << v.name << "...\n" << console::reset;
}
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;
}
// Exceptions are DISABLED. We assume tests do not crash.
// If a test crashes (segfault), the OS handles it.
bool result = v.functor();
if (!result)
{
m_failCount++;
if constexpr (stopOnFail)
m_fail_count++;
if constexpr (StopOnFail)
{
summarize();
exit(-1);
std::exit(-1);
}
}
}
fputs("\n", stdout);
std::cout << "\n";
}
template<BOOL stopOnFail, BOOL isVerbose> VOID runner<stopOnFail, isVerbose>::summarize()
template<bool StopOnFail, bool IsVerbose> void Runner<StopOnFail, IsVerbose>::summarize()
{
printf(__CC_GREEN
"\n-----------------------------------\n\t SUMMARY\n-----------------------------------\n");
std::cout << console::green
<< "\n-----------------------------------\n\t SUMMARY\n-----------------------------------\n";
if (!m_failCount)
if (m_fail_count == 0)
{
printf("\n\tALL TESTS PASSED!\n\n");
std::cout << "\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);
f64 success_rate = (100.0 * static_cast<f64>(m_test_count - m_fail_count) / static_cast<f64>(m_test_count));
std::cout << console::red << m_fail_count << " OF " << m_test_count << " TESTS FAILED\n"
<< console::yellow << std::format("Success Rate: {:.2f}%\n", success_rate);
}
printf(__CC_MAGENTA "Ran %zu test(s) across %zu block(s)\n" __CC_GREEN
"-----------------------------------" __CC_DEFAULT "\n",
m_testCount, m_blockCount);
std::cout << console::magenta << "Ran " << m_test_count << " test(s) across " << m_block_count << " block(s)\n"
<< console::green << "-----------------------------------" << console::reset << "\n";
}
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>;
using DefaultRunner = Runner<false, true>;
// -------------------------------------------------------------------------
// Registry
// -------------------------------------------------------------------------
class TestRegistry
{
public:
using TestEntry = std::function<void(DefaultRunner &)>;
static std::vector<TestEntry> &GetEntries()
static auto get_entries() -> Vec<TestEntry> &
{
static std::vector<TestEntry> entries;
static Vec<TestEntry> entries;
return entries;
}
static int RunAll()
static auto run_all() -> i32
{
DefaultRunner r;
auto &entries = GetEntries();
printf(__CC_CYAN "[IATest] Discovered %zu Test Blocks\n\n" __CC_DEFAULT, entries.size());
auto &entries = get_entries();
std::cout << console::cyan << "[IATest] Discovered " << entries.size() << " Test Blocks\n\n"
<< console::reset;
for (auto &entry : entries)
{
entry(r);
}
return 0;
}
@ -315,11 +307,9 @@ namespace ia::iatest
{
AutoRegister()
{
TestRegistry::GetEntries().push_back([](DefaultRunner &r) { r.testBlock<BlockType>(); });
TestRegistry::get_entries().push_back([](DefaultRunner &r) { r.test_block<BlockType>(); });
}
};
} // namespace ia::iatest
} // namespace IACore
# define IAT_REGISTER_ENTRY(Group, Name) static ia::iatest::AutoRegister<Group##_##Name> _iat_reg_##Group##_##Name;
#endif // __cplusplus
#define IAT_REGISTER_ENTRY(Group, Name) static ia::iatest::AutoRegister<Group##_##Name> _iat_reg_##Group##_##Name;

View File

@ -19,133 +19,132 @@
#include <IACore/ProcessOps.hpp>
#include <IACore/SocketOps.hpp>
namespace IACore
{
using IPC_PacketHeader = RingBufferView::PacketHeader;
namespace IACore {
using IpcPacketHeader = 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;
struct alignas(64) IpcSharedMemoryLayout {
// =========================================================
// SECTION 1: METADATA & HANDSHAKE
// =========================================================
struct Header {
u32 magic; // 0x49414950 ("IAIP")
u32 version; // 1
u64 total_size; // Total size of SHM block
} meta;
// Pad to ensure MONI starts on a fresh cache line (64 bytes)
UINT8 _pad0[64 - sizeof(Header)];
// Pad to ensure MONI starts on a fresh cache line (64 bytes)
u8 _pad0[64 - sizeof(Header)];
// =========================================================
// SECTION 2: RING BUFFER CONTROL BLOCKS
// =========================================================
// =========================================================
// SECTION 2: RING BUFFER CONTROL BLOCKS
// =========================================================
// RingBufferView ControlBlock is already 64-byte aligned internally.
RingBufferView::ControlBlock MONI_Control;
RingBufferView::ControlBlock MINO_Control;
// RingBufferView ControlBlock is already 64-byte aligned internally.
RingBufferView::ControlBlock moni_control;
RingBufferView::ControlBlock mino_control;
// =========================================================
// SECTION 3: DATA BUFFER OFFSETS
// =========================================================
// =========================================================
// SECTION 3: DATA BUFFER OFFSETS
// =========================================================
UINT64 MONI_DataOffset;
UINT64 MONI_DataSize;
u64 moni_data_offset;
u64 moni_data_size;
UINT64 MINO_DataOffset;
UINT64 MINO_DataSize;
u64 mino_data_offset;
u64 mino_data_size;
// Pad to ensure the actual Data Buffer starts on a fresh cache line
UINT8 _pad1[64 - (sizeof(UINT64) * 4)];
// Pad to ensure the actual Data Buffer starts on a fresh cache line
u8 _pad1[64 - (sizeof(u64) * 4)];
static constexpr size_t GetHeaderSize()
{
return sizeof(IPC_SharedMemoryLayout);
}
};
static constexpr auto get_header_size() -> usize {
return sizeof(IpcSharedMemoryLayout);
}
};
// Static assert to ensure manual padding logic is correct
static_assert(sizeof(IPC_SharedMemoryLayout) % 64 == 0, "IPC Layout is not cache-line aligned!");
// Static assert to ensure manual padding logic is correct
static_assert(sizeof(IpcSharedMemoryLayout) % 64 == 0,
"IPC Layout is not cache-line aligned!");
class IPC_Node
{
public:
virtual ~IPC_Node();
class IpcNode {
public:
virtual ~IpcNode();
// When Manager spawns a node, `connectionString` is passed
// as the first command line argument
EXPECT(VOID) Connect(IN PCCHAR connectionString);
// When Manager spawns a node, `connection_string` is passed
// as the first command line argument
auto connect(const char *connection_string) -> Result<void>;
VOID Update();
void update();
VOID SendSignal(IN UINT8 signal);
VOID SendPacket(IN UINT16 packetID, IN Span<CONST UINT8> payload);
void send_signal(u8 signal);
void send_packet(u16 packet_id, Span<const u8> payload);
protected:
PURE_VIRTUAL(VOID OnSignal(IN UINT8 signal));
PURE_VIRTUAL(VOID OnPacket(IN UINT16 packetID, IN Span<CONST UINT8> payload));
protected:
virtual void on_signal(u8 signal) = 0;
virtual void on_packet(u16 packet_id, Span<const u8> payload) = 0;
private:
String m_shmName;
PUINT8 m_sharedMemory{};
Vector<UINT8> m_receiveBuffer;
SocketHandle m_socket{INVALID_SOCKET};
private:
String m_shm_name;
u8 *m_shared_memory{};
Vec<u8> m_receive_buffer;
SocketHandle m_socket{INVALID_SOCKET};
UniquePtr<RingBufferView> MONI; // Manager Out, Node In
UniquePtr<RingBufferView> MINO; // Manager In, Node Out
};
Box<RingBufferView> m_moni; // Manager Out, Node In
Box<RingBufferView> m_mino; // Manager In, Node Out
};
class IPC_Manager
{
struct NodeSession
{
SteadyTimePoint CreationTime{};
SharedPtr<ProcessHandle> NodeProcess;
class IpcManager {
struct NodeSession {
std::chrono::system_clock::time_point creation_time{};
Box<ProcessHandle> node_process;
Mutex SendMutex;
std::mutex send_mutex;
String SharedMemName;
PUINT8 MappedPtr{};
String shared_mem_name;
u8 *mapped_ptr{};
SocketHandle ListenerSocket{INVALID_SOCKET};
SocketHandle DataSocket{INVALID_SOCKET};
SocketHandle listener_socket{INVALID_SOCKET};
SocketHandle data_socket{INVALID_SOCKET};
UniquePtr<RingBufferView> MONI; // Manager Out, Node In
UniquePtr<RingBufferView> MINO; // Manager In, Node Out
Box<RingBufferView> moni; // Manager Out, Node In
Box<RingBufferView> mino; // Manager In, Node Out
BOOL IsReady{FALSE};
bool is_ready{false};
VOID SendSignal(IN UINT8 signal);
VOID SendPacket(IN UINT16 packetID, IN Span<CONST UINT8> payload);
};
void send_signal(u8 signal);
void send_packet(u16 packet_id, Span<const u8> payload);
};
public:
STATIC CONSTEXPR UINT32 DEFAULT_NODE_SHARED_MEMORY_SIZE = SIZE_MB(4);
public:
static constexpr u32 DEFAULT_NODE_SHARED_MEMORY_SIZE = 4 * 1024 * 1024; // 4MB
public:
IPC_Manager();
virtual ~IPC_Manager();
public:
virtual ~IpcManager();
VOID Update();
void update();
EXPECT(NativeProcessID)
SpawnNode(IN CONST FilePath &executablePath, IN UINT32 sharedMemorySize = DEFAULT_NODE_SHARED_MEMORY_SIZE);
BOOL WaitTillNodeIsOnline(IN NativeProcessID node);
auto spawn_node(const Path &executable_path,
u32 shared_memory_size = DEFAULT_NODE_SHARED_MEMORY_SIZE)
-> Result<NativeProcessID>;
VOID ShutdownNode(IN NativeProcessID node);
auto wait_till_node_is_online(NativeProcessID node) -> bool;
VOID SendSignal(IN NativeProcessID node, IN UINT8 signal);
VOID SendPacket(IN NativeProcessID node, IN UINT16 packetID, IN Span<CONST UINT8> payload);
void shutdown_node(NativeProcessID node);
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));
void send_signal(NativeProcessID node, u8 signal);
void send_packet(NativeProcessID node, u16 packet_id, Span<const u8> payload);
private:
Vector<UINT8> m_receiveBuffer;
Vector<UniquePtr<NodeSession>> m_activeSessions;
Vector<UniquePtr<NodeSession>> m_pendingSessions;
UnorderedMap<NativeProcessID, NodeSession *> m_activeSessionMap;
};
protected:
virtual void on_signal(NativeProcessID node, u8 signal) = 0;
virtual void on_packet(NativeProcessID node, u16 packet_id,
Span<const u8> payload) = 0;
private:
Vec<u8> m_receive_buffer;
Vec<Box<NodeSession>> m_active_sessions;
Vec<Box<NodeSession>> m_pending_sessions;
HashMap<NativeProcessID, NodeSession *> m_active_session_map;
protected:
IpcManager();
};
} // namespace IACore

View File

@ -17,42 +17,126 @@
#include <IACore/PCH.hpp>
#include <simdjson.h>
#include <glaze/glaze.hpp>
#include <nlohmann/json.hpp>
#include <simdjson.h>
namespace IACore
{
class JSON
{
private:
STATIC CONSTEXPR AUTO GLAZE_JSON_OPTS = glz::opts{.error_on_unknown_keys = false};
namespace IACore {
class JsonDocument {
public:
// Move-only (Safety: Cannot copy the parser state cheaply)
JsonDocument(JsonDocument &&) noexcept = default;
JsonDocument &operator=(JsonDocument &&) noexcept = default;
JsonDocument(const JsonDocument &) = delete;
JsonDocument &operator=(const JsonDocument &) = delete;
public:
STATIC EXPECT(nlohmann::json) Parse(IN CONST String &json);
STATIC EXPECT(Pair<SharedPtr<simdjson::dom::parser>, simdjson::dom::object>)
ParseReadOnly(IN CONST String &json);
STATIC String Encode(IN nlohmann::json data);
// Accessor: Get the root element (Object, Array, etc.)
// The returned 'element' is valid only as long as this JsonDocument is alive.
[[nodiscard]]
auto root() const noexcept -> simdjson::dom::element {
return m_root;
}
template<typename _object_type> STATIC EXPECT(_object_type) ParseToStruct(IN CONST String &json);
template<typename _object_type> STATIC EXPECT(String) EncodeStruct(IN CONST _object_type &data);
};
private:
// Only created via JSON::parse_read_only factory
friend class Json;
template<typename _object_type> EXPECT(_object_type) 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;
}
JsonDocument(Box<simdjson::dom::parser> p, simdjson::dom::element r)
: m_parser(std::move(p)), m_root(r) {}
template<typename _object_type> EXPECT(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;
}
// ORDER MATTERS: Parser (Owner) must be destroyed AFTER the Root (View).
// In C++, members are destroyed in reverse declaration order.
Box<simdjson::dom::parser> m_parser;
simdjson::dom::element m_root;
};
class Json {
private:
// Glaze options (Compile-time configuration)
static constexpr auto GLAZE_OPTS = glz::opts{.error_on_unknown_keys = false};
public:
// -------------------------------------------------------------------------
// 1. Standard Parsing (Nlohmann - mutable, owning, slower)
// -------------------------------------------------------------------------
static auto parse(const String &json_str) -> Result<nlohmann::json>;
static auto encode(const nlohmann::json &data) -> String;
// -------------------------------------------------------------------------
// 2. Read-Only Parsing (Simdjson - immutable, zero-copyish, extremely fast)
// -------------------------------------------------------------------------
// Returns a safe JsonDocument wrapper instead of a raw pair
static auto parse_read_only(const String &json_str) -> Result<JsonDocument>;
// -------------------------------------------------------------------------
// 3. Struct Serialization (Glaze - reflection-based, very fast)
// -------------------------------------------------------------------------
template <typename T>
static auto parse_to_struct(const String &json_str) -> Result<T>;
template <typename T>
static auto encode_struct(const T &data) -> Result<String>;
};
// =============================================================================
// Implementation
// =============================================================================
inline auto Json::parse(const String &json_str) -> Result<nlohmann::json> {
// 3rd arg=false (no exception), 4th arg=true (ignore comments)
const auto res = nlohmann::json::parse(json_str, nullptr, false, true);
if (res.is_discarded()) {
return fail("Failed to parse JSON (Invalid Syntax)");
}
return res;
}
inline auto Json::parse_read_only(const String &json_str)
-> Result<JsonDocument> {
// 1. Allocate parser on heap (reusing this via a pool would be even faster in
// future)
auto parser = make_box<simdjson::dom::parser>();
// 2. Use 'element' to support Arrays/Strings/Null roots, not just Objects
simdjson::dom::element root;
// 3. Parse
simdjson::error_code error = parser->parse(json_str).get(root);
if (error) {
return fail("JSON Error: {}", simdjson::error_message(error));
}
// 4. Return Safe Wrapper (Owner + View)
return JsonDocument(std::move(parser), root);
}
inline auto Json::encode(const nlohmann::json &data) -> String {
return data.dump();
}
template <typename T>
inline auto Json::parse_to_struct(const String &json_str) -> Result<T> {
T result{};
// glz::read_json returns an error code (bool-like optional)
const auto err = glz::read_json<GLAZE_OPTS>(result, json_str);
if (err) {
return fail("JSON Struct Parse Error: {}",
glz::format_error(err, json_str));
}
return result;
}
template <typename T>
inline auto Json::encode_struct(const T &data) -> Result<String> {
String result;
const auto err = glz::write_json(data, result);
if (err) {
return fail("JSON Struct Encode Error");
}
return result;
}
} // namespace IACore

View File

@ -1,144 +1,102 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2026 IAS (ias@iasoft.dev)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <IACore/PCH.hpp>
#define IA_LOG_SET_FILE(path) IACore::Logger::EnableLoggingToDisk(path)
#define IA_LOG_SET_LEVEL(level) IACore::Logger::SetLogLevel(IACore::Logger::ELogLevel::level)
#define IA_LOG_SET_FILE(path) IACore::Logger::enable_logging_to_disk(path)
#define IA_LOG_SET_LEVEL(level) \
IACore::Logger::set_log_level(IACore::Logger::LogLevel::level)
#define IA_LOG_TRACE(...) IACore::Logger::Trace(__VA_ARGS__)
#define IA_LOG_DEBUG(...) IACore::Logger::Debug(__VA_ARGS__)
#define IA_LOG_INFO(...) IACore::Logger::Info(__VA_ARGS__)
#define IA_LOG_WARN(...) IACore::Logger::Warn(__VA_ARGS__)
#define IA_LOG_ERROR(...) IACore::Logger::Error(__VA_ARGS__)
#define IA_LOG_TRACE(...) IACore::Logger::trace(__VA_ARGS__)
#define IA_LOG_DEBUG(...) IACore::Logger::debug(__VA_ARGS__)
#define IA_LOG_INFO(...) IACore::Logger::info(__VA_ARGS__)
#define IA_LOG_WARN(...) IACore::Logger::warn(__VA_ARGS__)
#define IA_LOG_ERROR(...) IACore::Logger::error(__VA_ARGS__)
namespace IACore
{
class Logger
{
public:
enum class ELogLevel
{
TRACE,
DEBUG,
INFO,
WARN,
ERROR
};
namespace IACore {
class Logger {
public:
enum class LogLevel { Trace, Debug, Info, Warn, Error };
public:
STATIC BOOL EnableLoggingToDisk(IN PCCHAR filePath);
STATIC VOID SetLogLevel(IN ELogLevel logLevel);
public:
static auto enable_logging_to_disk(const char *file_path) -> Result<void>;
static auto set_log_level(LogLevel log_level) -> void;
template<typename... Args> STATIC VOID Trace(FormatterString<Args...> fmt, Args &&...args)
{
LogTrace(std::vformat(fmt.get(), std::make_format_args(args...)));
}
template <typename... Args>
static auto trace(std::format_string<Args...> fmt, Args &&...args) -> void {
log_trace(std::vformat(fmt.get(), std::make_format_args(args...)));
}
template<typename... Args> STATIC VOID Debug(FormatterString<Args...> fmt, Args &&...args)
{
LogDebug(std::vformat(fmt.get(), std::make_format_args(args...)));
}
template <typename... Args>
static auto debug(std::format_string<Args...> fmt, Args &&...args) -> void {
log_debug(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 auto info(std::format_string<Args...> fmt, Args &&...args) -> void {
log_info(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 auto warn(std::format_string<Args...> fmt, Args &&...args) -> void {
log_warn(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...)));
}
template <typename... Args>
static auto error(std::format_string<Args...> fmt, Args &&...args) -> void {
log_error(std::vformat(fmt.get(), std::make_format_args(args...)));
}
STATIC VOID FlushLogs();
static auto flush_logs() -> void;
private:
private:
#if IA_DISABLE_LOGGING > 0
STATIC VOID LogTrace(IN String &&msg)
{
UNUSED(msg);
}
static auto log_trace(String &&msg) -> void { UNUSED(msg); }
STATIC VOID LogDebug(IN String &&msg)
{
UNUSED(msg);
}
static auto log_debug(String &&msg) -> void { UNUSED(msg); }
STATIC VOID LogInfo(IN String &&msg)
{
UNUSED(msg);
}
static auto log_info(String &&msg) -> void { UNUSED(msg); }
STATIC VOID LogWarn(IN String &&msg)
{
UNUSED(msg);
}
static auto log_warn(String &&msg) -> void { UNUSED(msg); }
STATIC VOID LogError(IN String &&msg)
{
UNUSED(msg);
}
static auto log_error(String &&msg) -> void { UNUSED(msg); }
#else
STATIC VOID LogTrace(IN String &&msg)
{
if (s_logLevel <= ELogLevel::TRACE)
LogInternal(__CC_WHITE, "TRACE", IA_MOVE(msg));
}
static auto log_trace(String &&msg) -> void {
if (m_log_level <= LogLevel::Trace)
log_internal(console::RESET, "TRACE", std::move(msg));
}
STATIC VOID LogDebug(IN String &&msg)
{
if (s_logLevel <= ELogLevel::DEBUG)
LogInternal(__CC_CYAN, "DEBUG", IA_MOVE(msg));
}
static auto log_debug(String &&msg) -> void {
if (m_log_level <= LogLevel::Debug)
log_internal(console::CYAN, "DEBUG", std::move(msg));
}
STATIC VOID LogInfo(IN String &&msg)
{
if (s_logLevel <= ELogLevel::INFO)
LogInternal(__CC_GREEN, "INFO", IA_MOVE(msg));
}
static auto log_info(String &&msg) -> void {
if (m_log_level <= LogLevel::Info)
log_internal(console::GREEN, "INFO", std::move(msg));
}
STATIC VOID LogWarn(IN String &&msg)
{
if (s_logLevel <= ELogLevel::WARN)
LogInternal(__CC_YELLOW, "WARN", IA_MOVE(msg));
}
static auto log_warn(String &&msg) -> void {
if (m_log_level <= LogLevel::Warn)
log_internal(console::YELLOW, "WARN", std::move(msg));
}
STATIC VOID LogError(IN String &&msg)
{
if (s_logLevel <= ELogLevel::ERROR)
LogInternal(__CC_RED, "ERROR", IA_MOVE(msg));
}
static auto log_error(String &&msg) -> void {
if (m_log_level <= LogLevel::Error)
log_internal(console::RED, "ERROR", std::move(msg));
}
#endif
STATIC VOID LogInternal(IN PCCHAR prefix, IN PCCHAR tag, IN String &&msg);
static auto log_internal(const char *prefix, const char *tag, String &&msg)
-> void;
private:
STATIC ELogLevel s_logLevel;
STATIC std::ofstream s_logFile;
private:
static LogLevel m_log_level;
static std::ofstream m_log_file;
STATIC VOID Initialize();
STATIC VOID Terminate();
static auto initialize() -> void;
static auto terminate() -> void;
friend VOID Initialize();
friend VOID Terminate();
};
friend void initialize();
friend void terminate();
};
} // namespace IACore

View File

@ -15,624 +15,233 @@
#pragma once
// -------------------------------------------------------------------------
// Platform Detection
// -------------------------------------------------------------------------
#if defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64)
# define IA_ARCH_X64 1
#define IA_ARCH_X64 1
#elif defined(__aarch64__) || defined(_M_ARM64)
# define IA_ARCH_ARM64 1
#define IA_ARCH_ARM64 1
#elif defined(__wasm__) || defined(__wasm32__) || defined(__wasm64__)
# define IA_ARCH_WASM 1
#define IA_ARCH_WASM 1
#else
# error "IACore: Unsupported Architecture. Only x64, ARM64, and WASM are supported."
#error "IACore: Unsupported Architecture."
#endif
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
# ifdef _WIN64
# define IA_PLATFORM_WIN64 1
# define IA_PLATFORM_WINDOWS 1
# else
# error "IACore: 32-bit Windows is not supported"
# endif
#define IA_PLATFORM_WINDOWS 1
#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
#include <TargetConditionals.h>
#define IA_PLATFORM_APPLE 1
#define IA_PLATFORM_UNIX 1
#elif __linux__
# define IA_PLATFORM_LINUX 1
# define IA_PLATFORM_UNIX 1
#define IA_PLATFORM_LINUX 1
#define IA_PLATFORM_UNIX 1
#elif __wasm__
# define IA_PLATFORM_WASM 1
#define IA_PLATFORM_WASM 1
#else
# error "IACore: Unsupported Platform. Only Windows, Linux, MacOS, Android and iOS are supported."
#error "IACore: Unsupported Platform."
#endif
#if 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
#if IA_PLATFORM_WINDOWS
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#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>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#endif
// -----------------------------------------------------------------------------
// Configuration Macros
// -----------------------------------------------------------------------------
#include <array>
#include <atomic>
#include <cmath>
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <format>
#include <iostream>
#include <limits>
#include <memory>
#include <mutex>
#include <optional>
#include <source_location>
#include <span>
#include <string>
#include <string_view>
#include <thread>
#include <vector>
#define IA_CHECK(o) (o > 0)
#include <ankerl/unordered_dense.h>
#include <tl/expected.hpp>
#if defined(__IA_DEBUG) && __IA_DEBUG
# define __DEBUG_MODE__
# define __BUILD_MODE_NAME "debug"
# define DEBUG_ONLY(v) v
# ifndef _DEBUG
# define _DEBUG
# endif
namespace IACore {
// =============================================================================
// Primitive Types (Rust Style)
// =============================================================================
using u8 = std::uint8_t;
using u16 = std::uint16_t;
using u32 = std::uint32_t;
using u64 = std::uint64_t;
using i8 = std::int8_t;
using i16 = std::int16_t;
using i32 = std::int32_t;
using i64 = std::int64_t;
using f32 = float;
using f64 = double;
using usize = std::size_t;
using isize = std::ptrdiff_t;
// =============================================================================
// Build Environment & Constants
// =============================================================================
namespace env {
#if defined(NDEBUG)
constexpr bool is_debug = false;
constexpr bool is_release = true;
#else
# define __RELEASE_MODE__
# define __BUILD_MODE_NAME "release"
# ifndef NDEBUG
# define NDEBUG
# endif
# ifndef __OPTIMIZE__
# define __OPTIMIZE__
# endif
# define DEBUG_ONLY(f)
constexpr bool is_debug = true;
constexpr bool is_release = false;
#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 <charconv>
# 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>
#if IA_PLATFORM_WINDOWS
constexpr bool is_windows = true;
constexpr bool is_unix = false;
#else
# include <float.h>
# include <stdbool.h>
# include <stddef.h>
# include <string.h>
constexpr bool is_windows = false;
constexpr bool is_unix = true;
#endif
// -----------------------------------------------------------------------------
// Security Macros
// -----------------------------------------------------------------------------
constexpr usize max_path_len = 4096;
} // namespace env
#define IA_PANIC(msg) \
{ \
fprintf(stderr, "PANIC: %s\n", msg); \
__builtin_trap(); \
}
// =============================================================================
// Memory & Ownership (Rust Semantics)
// =============================================================================
template <typename T> using Box = std::unique_ptr<T>;
// Advanced Security features are not included in OSS builds
// (OSS version does not implement 'IAC_CHECK_*'s)
#define IACORE_SECURITY_LEVEL 0
template <typename T> using Arc = std::shared_ptr<T>;
#define IAC_SEC_LEVEL(v) (IACORE_SECURITY_LEVEL >= v)
template <typename T> using Weak = std::weak_ptr<T>;
#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 CONSTEVAL consteval
# define EXPLICIT explicit
# 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 CONSTEVAL
# define EXPLICIT
# 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(...) VIRTUAL __VA_ARGS__ = 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) alignas(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 MakeUnexpected(std::move(_ia_res.error())); \
} \
}
#define IA_TRY(expr) \
__extension__({ \
auto _ia_res = (expr); \
if (!_ia_res) \
{ \
return MakeUnexpected(std::move(_ia_res.error())); \
} \
std::move(*_ia_res); \
})
#define IA_TRY_DISCARD(expr) \
{ \
auto _ia_res = (expr); \
if (!_ia_res) \
{ \
return MakeUnexpected(std::move(_ia_res.error())); \
} \
UNUSED(*_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_VERSION_MAJOR(v) ((v >> 40) & 0xFFFFFF)
#define IA_VERSION_MINOR(v) ((v >> 16) & 0xFFFFFF)
#define IA_VERSION_PATCH(v) (v & 0xFFFF)
#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;
template <typename T, typename... Args>
[[nodiscard]] inline auto make_box(Args &&...args) -> Box<T> {
return std::make_unique<T>(std::forward<Args>(args)...);
}
// -------------------------------------------------------------------------
// 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>;
ALIAS_TEMPLATE_FUNCTION(_value_type, MakeShared, std::make_shared);
ALIAS_TEMPLATE_FUNCTION(_value_type, MakeUnique, std::make_unique);
template<typename T, typename... Args> inline SharedPtr<T> MakeSharedProtected(Args &&...args)
{
struct make_shared_enabler : public T
{
make_shared_enabler(Args &&...args) : T(std::forward<Args>(args)...)
{
}
};
return MakeShared<make_shared_enabler>(std::forward<Args>(args)...);
template <typename T, typename... Args>
[[nodiscard]] inline auto make_arc(Args &&...args) -> Arc<T> {
return std::make_shared<T>(std::forward<Args>(args)...);
}
template<typename T, typename... Args> inline UniquePtr<T> MakeUniqueProtected(Args &&...args)
{
struct make_unique_enabler : public T
{
make_unique_enabler(Args &&...args) : T(std::forward<Args>(args)...)
{
}
};
// =============================================================================
// Error Handling (Result)
// =============================================================================
template <typename T, typename E = std::string>
using Result = tl::expected<T, E>;
return MakeUnique<make_unique_enabler>(std::forward<Args>(args)...);
template <typename E> [[nodiscard]] inline auto fail(E &&error) {
return tl::make_unexpected(std::forward<E>(error));
}
template<typename _expected_type, typename _unexpected_type>
using Expected = tl::expected<_expected_type, _unexpected_type>;
ALIAS_FUNCTION(MakeUnexpected, tl::make_unexpected);
template <typename... Args>
[[nodiscard]] inline auto fail(std::format_string<Args...> fmt,
Args &&...args) {
return tl::make_unexpected(std::format(fmt, std::forward<Args>(args)...));
}
# define EXPECT(...) Expected<__VA_ARGS__, String>
# define UNEXPECTED(...) MakeUnexpected(std::format(__VA_ARGS__))
// =============================================================================
// Common Data Structures
// =============================================================================
template <typename T> using Option = std::optional<T>;
template <typename T> using Vec = std::vector<T>;
template <typename T> using Span = std::span<T>;
template <typename T1, typename T2> using Pair = std::pair<T1, T2>;
template <typename K, typename V>
using HashMap = ankerl::unordered_dense::map<K, V>;
template <typename T> using HashSet = ankerl::unordered_dense::set<T>;
using String = std::string;
using Path = std::filesystem::path;
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>;
// =============================================================================
// Utilities
// =============================================================================
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;
[[noreturn]] inline void
panic(const std::string &msg,
const std::source_location loc = std::source_location::current()) {
std::cerr << "\n[IA_PANIC] " << msg << "\n At: " << loc.file_name()
<< ":" << loc.line() << "\n";
std::abort();
}
namespace FileSystem = std::filesystem;
using FilePath = FileSystem::path;
inline void
ensure(bool condition, const std::string &msg,
const std::source_location loc = std::source_location::current()) {
if (env::is_debug && !condition) {
std::cerr << "\n[assert] " << msg << "\n At: " << loc.file_name()
<< ":" << loc.line() << "\n";
std::abort();
}
}
template<typename... Args> using FormatterString = std::format_string<Args...>;
// =============================================================================
// Versioning
// =============================================================================
struct Version {
u32 major = 0;
u32 minor = 0;
u32 patch = 0;
#endif
constexpr auto to_u64() const -> u64 {
return (static_cast<u64>(major) << 40) | (static_cast<u64>(minor) << 16) |
(static_cast<u64>(patch));
}
};
// =============================================================================
// Console Colors
// =============================================================================
namespace console {
constexpr const char *RESET = "\033[0m";
constexpr const char *RED = "\033[31m";
constexpr const char *GREEN = "\033[32m";
constexpr const char *YELLOW = "\033[33m";
constexpr const char *BLUE = "\033[34m";
constexpr const char *MAGENTA = "\033[35m";
constexpr const char *CYAN = "\033[36m";
} // namespace console
} // namespace IACore
// =============================================================================
// Macros
// =============================================================================
#define IA_TRY_PURE(expr) \
{ \
auto _res = expr; \
if (!_res) { \
return fail(std::move(_res.error())); \
} \
}
#define IA_TRY(lhs, expr) \
{ \
auto _res = expr; \
if (!_res) { \
return fail(std::move(_res.error())); \
} \
lhs = std::move(*_res); \
}
#define IA_NODISCARD [[nodiscard]]
#define IA_UNUSED [[maybe_unused]]

View File

@ -18,17 +18,13 @@
#include <IACore/PCH.hpp>
#if IA_ARCH_X64
# ifdef _MSC_VER
# include <intrin.h>
# else
# include <immintrin.h>
# endif
#elif IA_ARCH_ARM64
# include <arm_acle.h>
#endif
namespace IACore
@ -38,26 +34,24 @@ namespace IACore
public:
struct Capabilities
{
BOOL HardwareCRC32{FALSE};
bool hardware_crc32 = false;
};
public:
STATIC BOOL CheckCPU();
static auto check_cpu() -> bool;
#if IA_ARCH_X64
STATIC VOID CPUID(IN INT32 function, IN INT32 subFunction, OUT INT32 out[4]);
static auto cpuid(i32 function, i32 sub_function, i32 out[4]) -> void;
#endif
STATIC PCCHAR GetArchitectureName();
STATIC PCCHAR GetOperatingSystemName();
static auto get_architecture_name() -> const char *;
static auto get_operating_system_name() -> const char *;
public:
STATIC CONST Capabilities &GetCapabilities()
static auto get_capabilities() -> const Capabilities &
{
return s_capabilities;
}
private:
STATIC Capabilities s_capabilities;
static Capabilities s_capabilities;
};
} // namespace IACore

View File

@ -22,46 +22,48 @@ using NativeProcessID = DWORD;
#elif IA_PLATFORM_UNIX
using NativeProcessID = pid_t;
#else
# error "This platform does not support IACore ProcessOps"
#error "This platform does not support IACore ProcessOps"
#endif
namespace IACore
{
struct ProcessHandle
{
Atomic<NativeProcessID> ID{0};
Atomic<BOOL> IsRunning{false};
namespace IACore {
struct ProcessHandle {
std::atomic<NativeProcessID> id{0};
std::atomic<bool> is_running{false};
BOOL IsActive() CONST
{
return IsRunning && ID != 0;
}
[[nodiscard]] auto is_active() const -> bool { return is_running && id != 0; }
private:
JoiningThread ThreadHandle;
private:
std::jthread m_thread_handle;
friend class ProcessOps;
};
friend class ProcessOps;
};
class ProcessOps
{
public:
STATIC NativeProcessID GetCurrentProcessID();
class ProcessOps {
public:
static auto get_current_process_id() -> NativeProcessID;
STATIC EXPECT(INT32) 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(EXPECT(INT32))> onFinishCallback);
static auto spawn_process_sync(
const String &command, const String &args,
std::function<void(StringView line)> on_output_line_callback)
-> Result<i32>;
STATIC VOID TerminateProcess(IN CONST SharedPtr<ProcessHandle> &handle);
static auto spawn_process_async(
const String &command, const String &args,
std::function<void(StringView line)> on_output_line_callback,
std::function<void(Result<i32>)> on_finish_callback)
-> Result<Box<ProcessHandle>>;
private:
STATIC EXPECT(INT32)
SpawnProcessWindows(IN CONST String &command, IN CONST String &args,
IN Function<VOID(StringView)> onOutputLineCallback, OUT Atomic<NativeProcessID> &id);
STATIC EXPECT(INT32)
SpawnProcessPosix(IN CONST String &command, IN CONST String &args,
IN Function<VOID(StringView)> onOutputLineCallback, OUT Atomic<NativeProcessID> &id);
};
static auto terminate_process(const Box<ProcessHandle> &handle) -> void;
private:
static auto
spawn_process_windows(const String &command, const String &args,
std::function<void(StringView)> on_output_line_callback,
std::atomic<NativeProcessID> &id) -> Result<i32>;
static auto
spawn_process_posix(const String &command, const String &args,
std::function<void(StringView)> on_output_line_callback,
std::atomic<NativeProcessID> &id) -> Result<i32>;
};
} // namespace IACore

View File

@ -1,18 +1,3 @@
// IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2026 IAS (ias@iasoft.dev)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <IACore/PCH.hpp>
@ -37,81 +22,81 @@ namespace IACore
# pragma message("Warning: Configuration mismatch. IACore is being compiled for SCALAR SIMD (Slow)")
#endif
class ALIGN(16) IntVec4
class alignas(16) IntVec4
{
public:
IntVec4() = default;
INLINE EXPLICIT IntVec4(IN UINT32 s);
INLINE EXPLICIT IntVec4(IN PCUINT32 values);
INLINE EXPLICIT IntVec4(IN UINT32 a, IN UINT32 b, IN UINT32 c, IN UINT32 d);
inline explicit IntVec4(u32 s);
inline explicit IntVec4(const u32 *values);
inline explicit IntVec4(u32 a, u32 b, u32 c, u32 d);
INLINE IntVec4 operator+(IN CONST IntVec4 &other) CONST;
INLINE IntVec4 operator-(IN CONST IntVec4 &other) CONST;
INLINE IntVec4 operator*(IN CONST IntVec4 &other) CONST;
inline auto operator+(const IntVec4 &other) const -> IntVec4;
inline auto operator-(const IntVec4 &other) const -> IntVec4;
inline auto operator*(const IntVec4 &other) const -> IntVec4;
INLINE IntVec4 operator&(IN CONST IntVec4 &other) CONST;
INLINE IntVec4 operator|(IN CONST IntVec4 &other) CONST;
INLINE IntVec4 operator^(IN CONST IntVec4 &other) CONST;
INLINE IntVec4 operator~() CONST;
inline auto operator&(const IntVec4 &other) const -> IntVec4;
inline auto operator|(const IntVec4 &other) const -> IntVec4;
inline auto operator^(const IntVec4 &other) const -> IntVec4;
inline auto operator~() const -> IntVec4;
INLINE IntVec4 operator<<(IN UINT32 amount) CONST;
INLINE IntVec4 operator>>(IN UINT32 amount) CONST;
inline auto operator<<(u32 amount) const -> IntVec4;
inline auto operator>>(u32 amount) const -> IntVec4;
INLINE IntVec4 SatAdd(IN CONST IntVec4 &other) CONST;
INLINE IntVec4 SatSub(IN CONST IntVec4 &other) CONST;
inline auto sat_add(const IntVec4 &other) const -> IntVec4;
inline auto sat_sub(const IntVec4 &other) const -> IntVec4;
INLINE IntVec4 Clamp(IN UINT32 min, IN UINT32 max) CONST;
inline auto clamp(u32 min, u32 max) const -> IntVec4;
INLINE IntVec4 MultAdd(IN CONST IntVec4 &multiplier, IN CONST IntVec4 &addend) CONST;
inline auto mult_add(const IntVec4 &multiplier, const IntVec4 &addend) const -> IntVec4;
INLINE VOID Store(OUT PUINT32 values);
STATIC INLINE IntVec4 Load(IN PCUINT32 values);
inline auto store(u32 *values) -> void;
static inline auto load(const u32 *values) -> IntVec4;
private:
using Tag = hn::FixedTag<UINT32, 4>;
using Tag = hn::FixedTag<u32, 4>;
hn::Vec<Tag> m_data;
INLINE EXPLICIT IntVec4(hn::Vec<Tag> v) : m_data(v)
inline explicit IntVec4(hn::Vec<Tag> v) : m_data(v)
{
}
};
class ALIGN(16) FloatVec4
class alignas(16) FloatVec4
{
public:
FloatVec4() = default;
INLINE EXPLICIT FloatVec4(IN FLOAT32 s);
INLINE EXPLICIT FloatVec4(IN PCFLOAT32 values);
INLINE EXPLICIT FloatVec4(IN FLOAT32 a, IN FLOAT32 b, IN FLOAT32 c, IN FLOAT32 d);
inline explicit FloatVec4(f32 s);
inline explicit FloatVec4(const f32 *values);
inline explicit FloatVec4(f32 a, f32 b, f32 c, f32 d);
INLINE FloatVec4 operator+(IN CONST FloatVec4 &other) CONST;
INLINE FloatVec4 operator-(IN CONST FloatVec4 &other) CONST;
INLINE FloatVec4 operator*(IN CONST FloatVec4 &other) CONST;
INLINE FloatVec4 operator/(IN CONST FloatVec4 &other) CONST;
inline auto operator+(const FloatVec4 &other) const -> FloatVec4;
inline auto operator-(const FloatVec4 &other) const -> FloatVec4;
inline auto operator*(const FloatVec4 &other) const -> FloatVec4;
inline auto operator/(const FloatVec4 &other) const -> FloatVec4;
INLINE FloatVec4 Clamp(IN FLOAT32 min, IN FLOAT32 max) CONST;
inline auto clamp(f32 min, f32 max) const -> FloatVec4;
INLINE FloatVec4 Abs() CONST;
INLINE FloatVec4 Sqrt() CONST;
INLINE FloatVec4 Rsqrt() CONST;
INLINE FloatVec4 Normalize() CONST;
inline auto abs() const -> FloatVec4;
inline auto sqrt() const -> FloatVec4;
inline auto rsqrt() const -> FloatVec4;
inline auto normalize() const -> FloatVec4;
INLINE FLOAT32 Dot(IN CONST FloatVec4 &other) CONST;
inline auto dot(const FloatVec4 &other) const -> f32;
INLINE FloatVec4 MultAdd(IN CONST FloatVec4 &multiplier, IN CONST FloatVec4 &addend) CONST;
inline auto mult_add(const FloatVec4 &multiplier, const FloatVec4 &addend) const -> FloatVec4;
INLINE VOID Store(OUT PFLOAT32 values);
STATIC INLINE FloatVec4 Load(IN PCFLOAT32 values);
inline auto store(f32 *values) -> void;
static inline auto load(const f32 *values) -> FloatVec4;
private:
using Tag = hn::FixedTag<FLOAT32, 4>;
using Tag = hn::FixedTag<f32, 4>;
hn::Vec<Tag> m_data;
INLINE EXPLICIT FloatVec4(hn::Vec<Tag> v) : m_data(v)
inline explicit FloatVec4(hn::Vec<Tag> v) : m_data(v)
{
}
};
@ -119,153 +104,153 @@ namespace IACore
namespace IACore
{
IntVec4::IntVec4(IN UINT32 s)
IntVec4::IntVec4(u32 s)
{
CONST Tag d;
const Tag d;
m_data = hn::Set(d, s);
}
IntVec4::IntVec4(IN PCUINT32 values)
IntVec4::IntVec4(const u32 *values)
{
CONST Tag data;
const Tag data;
m_data = hn::Load(data, values);
}
IntVec4::IntVec4(IN UINT32 a, IN UINT32 b, IN UINT32 c, IN UINT32 d)
IntVec4::IntVec4(u32 a, u32 b, u32 c, u32 d)
{
CONST Tag data;
ALIGN(16) UINT32 values[4] = {a, b, c, d};
const Tag data;
alignas(16) u32 values[4] = {a, b, c, d};
m_data = hn::Load(data, values);
}
IntVec4 IntVec4::operator+(IN CONST IntVec4 &other) CONST
auto IntVec4::operator+(const IntVec4 &other) const -> IntVec4
{
return IntVec4(hn::Add(m_data, other.m_data));
}
IntVec4 IntVec4::operator-(IN CONST IntVec4 &other) CONST
auto IntVec4::operator-(const IntVec4 &other) const -> IntVec4
{
return IntVec4(hn::Sub(m_data, other.m_data));
}
IntVec4 IntVec4::operator*(IN CONST IntVec4 &other) CONST
auto IntVec4::operator*(const IntVec4 &other) const -> IntVec4
{
return IntVec4(hn::Mul(m_data, other.m_data));
}
IntVec4 IntVec4::operator&(IN CONST IntVec4 &other) CONST
auto IntVec4::operator&(const IntVec4 &other) const -> IntVec4
{
return IntVec4(hn::And(m_data, other.m_data));
}
IntVec4 IntVec4::operator|(IN CONST IntVec4 &other) CONST
auto IntVec4::operator|(const IntVec4 &other) const -> IntVec4
{
return IntVec4(hn::Or(m_data, other.m_data));
}
IntVec4 IntVec4::operator^(IN CONST IntVec4 &other) CONST
auto IntVec4::operator^(const IntVec4 &other) const -> IntVec4
{
return IntVec4(hn::Xor(m_data, other.m_data));
}
IntVec4 IntVec4::operator~() CONST
auto IntVec4::operator~() const -> IntVec4
{
return IntVec4(hn::Not(m_data));
}
IntVec4 IntVec4::operator<<(IN UINT32 amount) CONST
auto IntVec4::operator<<(u32 amount) const -> IntVec4
{
return IntVec4(hn::ShiftLeftSame(m_data, amount));
}
IntVec4 IntVec4::operator>>(IN UINT32 amount) CONST
auto IntVec4::operator>>(u32 amount) const -> IntVec4
{
return IntVec4(hn::ShiftRightSame(m_data, amount));
}
IntVec4 IntVec4::MultAdd(IN CONST IntVec4 &multiplier, IN CONST IntVec4 &addend) CONST
auto IntVec4::mult_add(const IntVec4 &multiplier, const IntVec4 &addend) const -> IntVec4
{
return IntVec4(hn::MulAdd(m_data, multiplier.m_data, addend.m_data));
}
IntVec4 IntVec4::SatAdd(IN CONST IntVec4 &other) CONST
auto IntVec4::sat_add(const IntVec4 &other) const -> IntVec4
{
return IntVec4(hn::SaturatedAdd(m_data, other.m_data));
}
IntVec4 IntVec4::SatSub(IN CONST IntVec4 &other) CONST
auto IntVec4::sat_sub(const IntVec4 &other) const -> IntVec4
{
return IntVec4(hn::SaturatedSub(m_data, other.m_data));
}
IntVec4 IntVec4::Clamp(IN UINT32 min, IN UINT32 max) CONST
auto IntVec4::clamp(u32 min, u32 max) const -> IntVec4
{
CONST Tag d;
const Tag d;
auto vMin = hn::Set(d, min);
auto vMax = hn::Set(d, max);
return IntVec4(hn::Min(hn::Max(m_data, vMin), vMax));
}
VOID IntVec4::Store(OUT PUINT32 values)
auto IntVec4::store(u32 *values) -> void
{
CONST Tag d;
const Tag d;
hn::Store(m_data, d, values);
}
IntVec4 IntVec4::Load(IN PCUINT32 values)
auto IntVec4::load(const u32 *values) -> IntVec4
{
CONST Tag d;
const Tag d;
return IntVec4(hn::Load(d, values));
}
} // namespace IACore
namespace IACore
{
FloatVec4::FloatVec4(IN FLOAT32 s)
FloatVec4::FloatVec4(f32 s)
{
const Tag d;
m_data = hn::Set(d, s);
}
FloatVec4::FloatVec4(IN PCFLOAT32 values)
FloatVec4::FloatVec4(const f32 *values)
{
const Tag d;
m_data = hn::Load(d, values);
}
FloatVec4::FloatVec4(IN FLOAT32 a, IN FLOAT32 b, IN FLOAT32 c, IN FLOAT32 d)
FloatVec4::FloatVec4(f32 a, f32 b, f32 c, f32 d)
{
const Tag data;
ALIGN(16) FLOAT32 temp[4] = {a, b, c, d};
alignas(16) f32 temp[4] = {a, b, c, d};
m_data = hn::Load(data, temp);
}
FloatVec4 FloatVec4::operator+(IN CONST FloatVec4 &other) CONST
auto FloatVec4::operator+(const FloatVec4 &other) const -> FloatVec4
{
return FloatVec4(hn::Add(m_data, other.m_data));
}
FloatVec4 FloatVec4::operator-(IN CONST FloatVec4 &other) CONST
auto FloatVec4::operator-(const FloatVec4 &other) const -> FloatVec4
{
return FloatVec4(hn::Sub(m_data, other.m_data));
}
FloatVec4 FloatVec4::operator*(IN CONST FloatVec4 &other) CONST
auto FloatVec4::operator*(const FloatVec4 &other) const -> FloatVec4
{
return FloatVec4(hn::Mul(m_data, other.m_data));
}
FloatVec4 FloatVec4::operator/(IN CONST FloatVec4 &other) CONST
auto FloatVec4::operator/(const FloatVec4 &other) const -> FloatVec4
{
return FloatVec4(hn::Div(m_data, other.m_data));
}
FloatVec4 FloatVec4::MultAdd(IN CONST FloatVec4 &multiplier, IN CONST FloatVec4 &addend) CONST
auto FloatVec4::mult_add(const FloatVec4 &multiplier, const FloatVec4 &addend) const -> FloatVec4
{
return FloatVec4(hn::MulAdd(m_data, multiplier.m_data, addend.m_data));
}
FloatVec4 FloatVec4::Clamp(IN FLOAT32 min, IN FLOAT32 max) CONST
auto FloatVec4::clamp(f32 min, f32 max) const -> FloatVec4
{
const Tag d;
auto vMin = hn::Set(d, min);
@ -273,29 +258,29 @@ namespace IACore
return FloatVec4(hn::Min(hn::Max(m_data, vMin), vMax));
}
FloatVec4 FloatVec4::Sqrt() CONST
auto FloatVec4::sqrt() const -> FloatVec4
{
return FloatVec4(hn::Sqrt(m_data));
}
FloatVec4 FloatVec4::Rsqrt() CONST
auto FloatVec4::rsqrt() const -> FloatVec4
{
return FloatVec4(hn::ApproximateReciprocalSqrt(m_data));
}
FloatVec4 FloatVec4::Abs() CONST
auto FloatVec4::abs() const -> FloatVec4
{
return FloatVec4(hn::Abs(m_data));
}
FLOAT32 FloatVec4::Dot(IN CONST FloatVec4 &other) CONST
auto FloatVec4::dot(const FloatVec4 &other) const -> f32
{
const Tag d;
auto vMul = hn::Mul(m_data, other.m_data);
return hn::ReduceSum(d, vMul);
}
FloatVec4 FloatVec4::Normalize() CONST
auto FloatVec4::normalize() const -> FloatVec4
{
const Tag d;
auto vMul = hn::Mul(m_data, m_data);
@ -304,13 +289,13 @@ namespace IACore
return FloatVec4(hn::Mul(m_data, vInvLen));
}
VOID FloatVec4::Store(OUT PFLOAT32 values)
auto FloatVec4::store(f32 *values) -> void
{
const Tag d;
hn::Store(m_data, d, values);
}
FloatVec4 FloatVec4::Load(IN PCFLOAT32 values)
auto FloatVec4::load(const f32 *values) -> FloatVec4
{
const Tag d;
return FloatVec4(hn::Load(d, values));

View File

@ -18,89 +18,98 @@
#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;
#include <afunix.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#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;
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
// Define INVALID_SOCKET for Unix to match Windows API for cross-platform
// compatibility
#ifndef INVALID_SOCKET
#define INVALID_SOCKET -1
#endif
#else
# error "IACore SocketOps is not supported on this platform."
#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;
namespace IACore {
#if IA_PLATFORM_WINDOWS
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
using SocketHandle = SOCKET;
#elif IA_PLATFORM_UNIX
using SocketHandle = int;
#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;
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 auto initialize() -> Result<void> {
s_init_count++;
if (s_init_count > 1) {
return {};
}
#if IA_PLATFORM_WINDOWS
WSACleanup();
WSADATA wsa_data;
const auto res = WSAStartup(MAKEWORD(2, 2), &wsa_data);
if (res != 0) {
s_init_count--;
return fail("WSAStartup failed with error: {}", res);
}
#endif
}
return {};
}
STATIC BOOL IsPortAvailableTCP(IN UINT16 port)
{
return IsPortAvailable(port, SOCK_STREAM);
}
// SocketOps correctly handles multiple calls to initialize and terminate.
// Make sure every initialize call is paired with a corresponding terminate
// call.
static auto terminate() -> void {
s_init_count--;
if (s_init_count > 0) {
return;
}
#if IA_PLATFORM_WINDOWS
WSACleanup();
#endif
}
STATIC BOOL IsPortAvailableUDP(IN UINT16 port)
{
return IsPortAvailable(port, SOCK_DGRAM);
}
static auto is_port_available_tcp(u16 port) -> bool {
return is_port_available(port, SOCK_STREAM);
}
STATIC BOOL IsWouldBlock();
static auto is_port_available_udp(u16 port) -> bool {
return is_port_available(port, SOCK_DGRAM);
}
STATIC VOID Close(IN SocketHandle sock);
static auto is_would_block() -> bool;
STATIC BOOL Listen(IN SocketHandle sock, IN INT32 queueSize = 5);
static auto close(SocketHandle sock) -> void;
STATIC SocketHandle CreateUnixSocket();
static auto listen(SocketHandle sock, i32 queue_size = 5) -> Result<void>;
STATIC BOOL BindUnixSocket(IN SocketHandle sock, IN PCCHAR path);
STATIC BOOL ConnectUnixSocket(IN SocketHandle sock, IN PCCHAR path);
static auto create_unix_socket() -> Result<SocketHandle>;
private:
STATIC BOOL IsPortAvailable(IN UINT16 port, IN INT32 type);
static auto bind_unix_socket(SocketHandle sock, const char *path)
-> Result<void>;
static auto connect_unix_socket(SocketHandle sock, const char *path)
-> Result<void>;
private:
STATIC INT32 s_initCount;
};
static auto unlink_file(const char *path) -> void {
#if IA_PLATFORM_WINDOWS
DeleteFileA(path);
#elif IA_PLATFORM_UNIX
unlink(path);
#endif
}
private:
static auto is_port_available(u16 port, i32 type) -> bool;
private:
static i32 s_init_count;
};
} // namespace IACore

View File

@ -16,88 +16,87 @@
#pragma once
#include <IACore/PCH.hpp>
#include <algorithm>
#include <cstring>
namespace IACore
{
class StreamReader
{
enum class EStorageType
{
NON_OWNING,
OWNING_MMAP,
OWNING_VECTOR,
};
namespace IACore {
class StreamReader {
public:
enum class StorageType {
NonOwning,
OwningMmap,
OwningVector,
};
public:
INLINE EXPECT(VOID) Read(IN PVOID buffer, IN SIZE_T size);
template<typename T> NO_DISCARD("Check for EOF") EXPECT(T) Read();
static auto create_from_file(const Path &path) -> Result<StreamReader>;
VOID Skip(SIZE_T amount)
{
m_cursor = std::min(m_cursor + amount, m_dataSize);
}
explicit StreamReader(Vec<u8> &&data);
explicit StreamReader(Span<const u8> data);
~StreamReader();
VOID Seek(SIZE_T pos)
{
m_cursor = (pos > m_dataSize) ? m_dataSize : pos;
}
StreamReader(StreamReader &&) = default;
auto operator=(StreamReader &&) -> StreamReader & = default;
SIZE_T Cursor() CONST
{
return m_cursor;
}
StreamReader(const StreamReader &) = delete;
auto operator=(const StreamReader &) -> StreamReader & = delete;
SIZE_T Size() CONST
{
return m_dataSize;
}
auto read(void *buffer, usize size) -> Result<void>;
SIZE_T Remaining() CONST
{
return m_dataSize - m_cursor;
}
template <typename T>
[[nodiscard("Check for EOF")]]
auto read() -> Result<T>;
BOOL IsEOF() CONST
{
return m_cursor >= m_dataSize;
}
auto skip(usize amount) -> void {
m_cursor = std::min(m_cursor + amount, m_data_size);
}
public:
StreamReader(IN CONST FilePath &path);
explicit StreamReader(IN Vector<UINT8> &&data);
explicit StreamReader(IN Span<CONST UINT8> data);
~StreamReader();
auto seek(usize pos) -> void {
m_cursor = (pos > m_data_size) ? m_data_size : pos;
}
private:
PCUINT8 m_data{};
SIZE_T m_cursor{};
SIZE_T m_dataSize{};
Vector<UINT8> m_owningVector;
CONST EStorageType m_storageType;
};
[[nodiscard]] auto cursor() const -> usize { return m_cursor; }
EXPECT(VOID) StreamReader::Read(IN PVOID buffer, IN SIZE_T size)
{
if B_UNLIKELY ((m_cursor + size > m_dataSize))
return MakeUnexpected(String("Unexpected EOF while reading"));
[[nodiscard]] auto size() const -> usize { return m_data_size; }
std::memcpy(buffer, &m_data[m_cursor], size);
m_cursor += size;
[[nodiscard]] auto remaining() const -> usize {
return m_data_size - m_cursor;
}
return {};
}
[[nodiscard]] auto is_eof() const -> bool { return m_cursor >= m_data_size; }
template<typename T> NO_DISCARD("Check for EOF") EXPECT(T) StreamReader::Read()
{
constexpr SIZE_T size = sizeof(T);
private:
const u8 *m_data = nullptr;
usize m_cursor = 0;
usize m_data_size = 0;
Vec<u8> m_owning_vector;
StorageType m_storage_type = StorageType::NonOwning;
};
if B_UNLIKELY ((m_cursor + size > m_dataSize))
return MakeUnexpected(String("Unexpected EOF while reading"));
inline auto StreamReader::read(void *buffer, usize size) -> Result<void> {
if (m_cursor + size > m_data_size) [[unlikely]] {
return fail("Unexpected EOF while reading");
}
T value;
std::memcpy(&value, &m_data[m_cursor], size);
m_cursor += size;
std::memcpy(buffer, &m_data[m_cursor], size);
m_cursor += size;
return {};
}
template <typename T>
[[nodiscard("Check for EOF")]]
inline auto StreamReader::read() -> Result<T> {
constexpr usize SIZE = sizeof(T);
if (m_cursor + SIZE > m_data_size) [[unlikely]] {
return fail("Unexpected EOF while reading");
}
T value;
std::memcpy(&value, &m_data[m_cursor], SIZE);
m_cursor += SIZE;
return value;
}
return value;
}
} // namespace IACore

View File

@ -17,49 +17,49 @@
#include <IACore/PCH.hpp>
namespace IACore
{
class StreamWriter
{
enum class EStorageType
{
NON_OWNING,
OWNING_FILE,
OWNING_VECTOR,
};
namespace IACore {
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);
class StreamWriter {
public:
enum class StorageType {
NonOwning,
OwningFile,
OwningVector,
};
PCUINT8 Data() CONST
{
return m_buffer;
}
static auto create(const Path &path) -> Result<StreamWriter>;
SIZE_T Cursor() CONST
{
return m_cursor;
}
StreamWriter();
explicit StreamWriter(Span<u8> data);
~StreamWriter();
public:
StreamWriter();
explicit StreamWriter(IN Span<UINT8> data);
explicit StreamWriter(IN CONST FilePath &path);
~StreamWriter();
StreamWriter(StreamWriter &&) = default;
auto operator=(StreamWriter &&) -> StreamWriter & = default;
private:
PUINT8 m_buffer{};
SIZE_T m_cursor{};
SIZE_T m_capacity{};
FilePath m_filePath{};
Vector<UINT8> m_owningVector;
CONST EStorageType m_storageType;
};
StreamWriter(const StreamWriter &) = delete;
auto operator=(const StreamWriter &) -> StreamWriter & = delete;
auto write(u8 byte, usize count) -> Result<void>;
auto write(const void *buffer, usize size) -> Result<void>;
template <typename T> auto write(const T &value) -> Result<void>;
[[nodiscard]] auto data() const -> const u8 * { return m_buffer; }
[[nodiscard]] auto cursor() const -> usize { return m_cursor; }
private:
u8 *m_buffer = nullptr;
usize m_cursor = 0;
usize m_capacity = 0;
Path m_file_path;
Vec<u8> m_owning_vector;
StorageType m_storage_type = StorageType::OwningVector;
};
template <typename T>
inline auto StreamWriter::write(const T &value) -> Result<void> {
return write(&value, sizeof(T));
}
template<typename T> BOOL StreamWriter::Write(IN CONST T &value)
{
return Write(&value, sizeof(T));
}
} // namespace IACore

View File

@ -17,12 +17,10 @@
#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);
};
namespace IACore {
class StringOps {
public:
static auto encode_base64(Span<const u8> data) -> String;
static auto decode_base64(const String &data) -> Vec<u8>;
};
} // namespace IACore

View File

@ -21,86 +21,48 @@
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);
static auto get_unix_time() -> u64;
for (UINT8 b : data)
{
res.push_back(LUT[(b >> 4) & 0x0F]);
res.push_back(LUT[b & 0x0F]);
}
return res;
}
static auto get_ticks_count() -> u64;
INLINE STATIC EXPECT(Vector<UINT8>) HexStringToBinary(CONST StringView &hex)
{
if (hex.size() % 2 != 0)
{
return MakeUnexpected(String("Hex string must have even length"));
}
static auto get_seconds_count() -> f64;
Vector<UINT8> out;
out.reserve(hex.size() / 2);
static auto get_random() -> f32;
static auto get_random(u64 max) -> u64;
static auto get_random(i64 min, i64 max) -> i64;
for (SIZE_T i = 0; i < hex.size(); i += 2)
{
char high = hex[i];
char low = hex[i + 1];
static auto sleep(u64 milliseconds) -> void;
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;
};
static auto binary_to_hex_string(Span<const u8> data) -> String;
int h = fromHexChar(high);
int l = fromHexChar(low);
static auto hex_string_to_binary(StringView hex) -> Result<Vec<u8>>;
if (h == -1 || l == -1)
{
return MakeUnexpected(String("Invalid hex character found"));
}
out.push_back(CAST((h << 4) | l, UINT8));
}
return out;
}
template<typename Range> INLINE STATIC VOID Sort(Range &&range)
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)
template<typename Range, typename T> inline static auto binary_search_left(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)
template<typename Range, typename T> inline static auto binary_search_right(Range &&range, const T &value)
{
return std::ranges::upper_bound(range, value);
}
template<typename T> INLINE STATIC void HashCombine(UINT64 &seed, CONST T &v)
template<typename T> inline static void hash_combine(u64 &seed, const T &v)
{
UINT64 h;
u64 h;
if constexpr (std::is_constructible_v<std::string_view, T>)
if constexpr (std::is_constructible_v<StringView, T>)
{
std::string_view sv(v);
auto hasher = ankerl::unordered_dense::hash<std::string_view>();
StringView sv(v);
auto hasher = ankerl::unordered_dense::hash<StringView>();
h = hasher(sv);
}
else
@ -112,18 +74,18 @@ namespace IACore
seed ^= h + 0x9e3779b97f4a7c15 + (seed << 6) + (seed >> 2);
}
template<typename... Args> INLINE STATIC UINT64 ComputeHash(CONST Args &...args)
template<typename... Args> inline static auto compute_hash(const Args &...args) -> u64
{
UINT64 seed = 0;
(HashCombine(seed, args), ...);
u64 seed = 0;
(hash_combine(seed, args), ...);
return seed;
}
template<typename T, typename... MemberPtrs>
INLINE STATIC UINT64 ComputeHashFlat(CONST T &obj, MemberPtrs... members)
inline static auto compute_hash_flat(const T &obj, MemberPtrs... members) -> u64
{
UINT64 seed = 0;
(HashCombine(seed, obj.*members), ...);
u64 seed = 0;
(hash_combine(seed, obj.*members), ...);
return seed;
}
};
@ -142,10 +104,10 @@ namespace IACore
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 \
IA_NODISCARD \
auto operator()(const Type &v) const noexcept -> IACore::u64 \
{ \
/* Pass the object and the list of member pointers */ \
return IACore::Utils::ComputeHashFlat(v, __VA_ARGS__); \
return IACore::Utils::compute_hash_flat(v, __VA_ARGS__); \
} \
};
};

View File

@ -28,12 +28,12 @@ namespace IACore
using Document = pugi::xml_document;
public:
STATIC EXPECT(Document) ParseFromString(IN CONST String &data);
STATIC EXPECT(Document) ParseFromFile(IN CONST FilePath &path);
static auto parse_from_string(const String &data) -> Result<Document>;
static auto parse_from_file(const Path &path) -> Result<Document>;
STATIC String SerializeToString(IN CONST Node &node, IN BOOL escape = false);
STATIC String SerializeToString(IN CONST Document &doc, IN BOOL escape = false);
static auto serialize_to_string(const Node &node, bool escape = false) -> String;
static auto serialize_to_string(const Document &doc, bool escape = false) -> String;
STATIC String EscapeXMLString(IN CONST String &xml);
static auto escape_xml_string(const String &xml) -> String;
};
} // namespace IACore