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

@ -1,13 +1,32 @@
--- ---
BasedOnStyle: Microsoft
IndentWidth: 4
SortIncludes: false
---
Language: Cpp Language: Cpp
Standard: c++20
BasedOnStyle: Microsoft
IndentWidth: 4
TabWidth: 4
UseTab: Never
AccessModifierOffset: -4
IndentPPDirectives: AfterHash IndentPPDirectives: AfterHash
IndentRequiresClause: true
BreakBeforeConceptDeclarations: true
SpaceAfterTemplateKeyword: false
FixNamespaceComments: true FixNamespaceComments: true
NamespaceIndentation: All NamespaceIndentation: All
SeparateDefinitionBlocks: Always CompactNamespaces: true
AlignAfterOpenBracket: Align
AlignOperands: Align
AlignTrailingComments: true
SpaceAfterCStyleCast: true SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false SpaceBeforeAssignmentOperators: true
SpaceInEmptyParentheses: false
SpacesInAngles: false
SeparateDefinitionBlocks: Always
SortIncludes: false
---

View File

@ -1,18 +1,76 @@
Checks: 'readability-identifier-naming' Checks: >
-*,
readability-identifier-naming,
readability-const-return-type,
modernize-use-nodiscard,
modernize-use-override,
modernize-use-nullptr,
bugprone-use-after-move
CheckOptions: CheckOptions:
- key: readability-identifier-naming.MethodCase # ----------------------------------------------------------------------------
value: PascalCase # Types (PascalCase) - Matches Rust structs/enums
# ----------------------------------------------------------------------------
- key: readability-identifier-naming.StructMemberCase
value: lower_case
- key: readability-identifier-naming.PrivateMemberCase
value: camelCase
- key: readability-identifier-naming.PrivateMemberPrefix
value: m_
- key: readability-identifier-naming.ClassCase - key: readability-identifier-naming.ClassCase
value: PascalCase value: PascalCase
- key: readability-identifier-naming.StructCase - key: readability-identifier-naming.StructCase
value: PascalCase value: PascalCase
- key: readability-identifier-naming.TypedefCase
value: PascalCase
- key: readability-identifier-naming.EnumCase
value: PascalCase
- key: readability-identifier-naming.TemplateParameterCase
value: PascalCase
# ----------------------------------------------------------------------------
# Functions & Methods (snake_case) - Matches Rust fn
# ----------------------------------------------------------------------------
- key: readability-identifier-naming.FunctionCase
value: lower_case
- key: readability-identifier-naming.MethodCase
value: lower_case
- key: readability-identifier-naming.ParameterCase
value: lower_case
# ----------------------------------------------------------------------------
# Variables (snake_case) - Matches Rust let
# ----------------------------------------------------------------------------
- key: readability-identifier-naming.VariableCase
value: lower_case
- key: readability-identifier-naming.LocalVariableCase
value: lower_case
# ----------------------------------------------------------------------------
# Members (m_snake_case for private, snake_case for public)
# ----------------------------------------------------------------------------
# Public struct members (like a Rust struct) -> x, y, width
- key: readability-identifier-naming.PublicMemberCase
value: lower_case
# Private/Protected class members -> m_parser, m_count
- key: readability-identifier-naming.PrivateMemberCase
value: lower_case
- key: readability-identifier-naming.PrivateMemberPrefix
value: m_
- key: readability-identifier-naming.ProtectedMemberCase
value: lower_case
- key: readability-identifier-naming.ProtectedMemberPrefix
value: m_
# ----------------------------------------------------------------------------
# Constants (SCREAMING_SNAKE_CASE)
# ----------------------------------------------------------------------------
- key: readability-identifier-naming.GlobalConstantCase
value: UPPER_CASE
- key: readability-identifier-naming.ConstexprVariableCase
value: UPPER_CASE
- key: readability-identifier-naming.EnumConstantCase
value: PascalCase
# Note: Rust uses PascalCase for Enum Variants (Option::None).
# Change to UPPER_CASE if you prefer C-style enums.
# ----------------------------------------------------------------------------
# Macros (SCREAMING_SNAKE_CASE)
# ----------------------------------------------------------------------------
- key: readability-identifier-naming.MacroDefinitionCase
value: UPPER_CASE

View File

@ -37,5 +37,5 @@ if(IACore_BUILD_TESTS)
endif() endif()
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/Sandbox") if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/Sandbox")
add_subdirectory(Sandbox) # add_subdirectory(Sandbox)
endif() endif()

View File

@ -1,5 +1,9 @@
# IACore (Independent Architecture Core) # IACore (Independent Architecture Core)
> [!WARNING]
> A major rewrite is coming to IACore API and coding style on Jan 30th. While the functionality and classes you're familiar will remain the same, this rewrite will introduce breaking changes to the overall API and how IACore is used. This rewrite will align IACore API and coding style with a Rust-inspired modern, clean and a vastly improved maintainable alternative.
<div align="center"> <div align="center">
<img src="logo.svg" alt="IACore Logo" width="400"/> <img src="logo.svg" alt="IACore Logo" width="400"/>
<br/> <br/>

View File

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

View File

@ -15,164 +15,170 @@
#include <IACore/AsyncOps.hpp> #include <IACore/AsyncOps.hpp>
namespace IACore namespace IACore {
{ std::mutex AsyncOps::s_queue_mutex;
Mutex AsyncOps::s_queueMutex; std::condition_variable AsyncOps::s_wake_condition;
ConditionVariable AsyncOps::s_wakeCondition; Vec<std::jthread> AsyncOps::s_schedule_workers;
Vector<JoiningThread> AsyncOps::s_scheduleWorkers; std::deque<AsyncOps::ScheduledTask> AsyncOps::s_high_priority_queue;
Deque<AsyncOps::ScheduledTask> AsyncOps::s_highPriorityQueue; std::deque<AsyncOps::ScheduledTask> AsyncOps::s_normal_priority_queue;
Deque<AsyncOps::ScheduledTask> AsyncOps::s_normalPriorityQueue;
VOID AsyncOps::RunTask(IN Function<VOID()> task) auto AsyncOps::run_task(std::function<void()> task) -> void {
{ std::jthread(std::move(task)).detach();
JoiningThread(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 (threads > 255) {
{ threads = 255;
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);
} }
worker_count = static_cast<u8>(threads);
}
VOID AsyncOps::TerminateScheduler() for (u32 i = 0; i < worker_count; ++i) {
{ s_schedule_workers.emplace_back(schedule_worker_loop,
for (auto &w : s_scheduleWorkers) static_cast<WorkerId>(i + 1));
{ }
w.request_stop();
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();
} }
it = queue.erase(it);
s_wakeCondition.notify_all(); } else {
++it;
for (auto &w : s_scheduleWorkers) }
{
if (w.joinable())
{
w.join();
}
}
s_scheduleWorkers.clear();
} }
};
VOID AsyncOps::ScheduleTask(IN Function<VOID(IN WorkerID workerID)> task, IN TaskTag tag, IN Schedule *schedule, cancel_from_queue(s_high_priority_queue);
IN Priority priority) 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"); std::unique_lock lock(s_queue_mutex);
if (!s_high_priority_queue.empty()) {
schedule->Counter.fetch_add(1); task = std::move(s_high_priority_queue.front());
{ s_high_priority_queue.pop_front();
ScopedLock lock(s_queueMutex); found_task = true;
if (priority == Priority::High) } else if (!s_normal_priority_queue.empty()) {
s_highPriorityQueue.emplace_back(ScheduledTask{tag, schedule, IA_MOVE(task)}); task = std::move(s_normal_priority_queue.front());
else s_normal_priority_queue.pop_front();
s_normalPriorityQueue.emplace_back(ScheduledTask{tag, schedule, IA_MOVE(task)}); found_task = true;
} }
s_wakeCondition.notify_one();
} }
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) { s_wake_condition.wait(lock, [&stop_token] {
for (auto it = queue.begin(); it != queue.end(); /* no increment here */) return !s_high_priority_queue.empty() ||
{ !s_normal_priority_queue.empty() || stop_token.stop_requested();
if (it->Tag == tag) });
{
if (it->ScheduleHandle->Counter.fetch_sub(1) == 1)
it->ScheduleHandle->Counter.notify_all();
it = queue.erase(it); if (stop_token.stop_requested() && s_high_priority_queue.empty() &&
} s_normal_priority_queue.empty()) {
else return;
++it; }
}
};
cancelFromQueue(s_highPriorityQueue); if (!s_high_priority_queue.empty()) {
cancelFromQueue(s_normalPriorityQueue); 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) if (found_task) {
{ task.task(worker_id);
IA_ASSERT(s_scheduleWorkers.size() && "Scheduler must be initialized before calling this function"); if (task.schedule_handle->counter.fetch_sub(1) == 1) {
task.schedule_handle->counter.notify_all();
while (schedule->Counter.load() > 0) }
{
ScheduledTask task;
BOOL foundTask{FALSE};
{
UniqueLock lock(s_queueMutex);
if (!s_highPriorityQueue.empty())
{
task = IA_MOVE(s_highPriorityQueue.front());
s_highPriorityQueue.pop_front();
foundTask = TRUE;
}
else if (!s_normalPriorityQueue.empty())
{
task = IA_MOVE(s_normalPriorityQueue.front());
s_normalPriorityQueue.pop_front();
foundTask = TRUE;
}
}
if (foundTask)
{
task.Task(MainThreadWorkerID);
if (task.ScheduleHandle->Counter.fetch_sub(1) == 1)
task.ScheduleHandle->Counter.notify_all();
}
else
{
auto currentVal = schedule->Counter.load();
if (currentVal > 0)
schedule->Counter.wait(currentVal);
}
}
} }
}
}
AsyncOps::WorkerID AsyncOps::GetWorkerCount()
{
return static_cast<WorkerID>(s_scheduleWorkers.size() + 1); // +1 for MainThread (Work Stealing)
}
VOID AsyncOps::ScheduleWorkerLoop(IN StopToken stopToken, IN WorkerID workerID)
{
while (!stopToken.stop_requested())
{
ScheduledTask task;
BOOL foundTask{FALSE};
{
UniqueLock lock(s_queueMutex);
s_wakeCondition.wait(lock, [&stopToken] {
return !s_highPriorityQueue.empty() || !s_normalPriorityQueue.empty() || stopToken.stop_requested();
});
if (stopToken.stop_requested() && s_highPriorityQueue.empty() && s_normalPriorityQueue.empty())
return;
if (!s_highPriorityQueue.empty())
{
task = IA_MOVE(s_highPriorityQueue.front());
s_highPriorityQueue.pop_front();
foundTask = TRUE;
}
else if (!s_normalPriorityQueue.empty())
{
task = IA_MOVE(s_normalPriorityQueue.front());
s_normalPriorityQueue.pop_front();
foundTask = TRUE;
}
}
if (foundTask)
{
task.Task(workerID);
if (task.ScheduleHandle->Counter.fetch_sub(1) == 1)
task.ScheduleHandle->Counter.notify_all();
}
}
}
} // namespace IACore } // namespace IACore

View File

@ -17,9 +17,9 @@
namespace IACore 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(); m_currentArg = m_argList.begin();

View File

@ -16,457 +16,458 @@
#include <IACore/DataOps.hpp> #include <IACore/DataOps.hpp>
#include <IACore/Platform.hpp> #include <IACore/Platform.hpp>
#include <bit>
#include <cstring>
#include <zlib.h> #include <zlib.h>
#include <zstd.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 #if IA_ARCH_X64
INLINE UINT32 CRC32_x64_HW(IN Span<CONST UINT8> data) #include <immintrin.h>
{
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;
}
#endif #endif
#if IA_ARCH_ARM64 #if IA_ARCH_ARM64
__attribute__((target("+crc"))) INLINE UINT32 CRC32_ARM64_HW(IN Span<CONST UINT8> data) #include <arm_acle.h>
{
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;
}
#endif #endif
INLINE UINT32 CRC32_Software_Slice8(IN Span<CONST UINT8> data) namespace IACore {
{ template <typename T>
CONST UINT8 *p = data.data(); [[nodiscard]] inline auto read_unaligned(const u8 *ptr) -> T {
UINT32 crc = 0xFFFFFFFF; T v;
SIZE_T len = data.size(); std::memcpy(&v, ptr, sizeof(T));
return v;
}
while (len >= 8) struct Crc32Tables {
{ u32 table[8][256] = {};
UINT32 term1 = crc ^ ReadUnaligned<UINT32>(p);
UINT32 term2 = ReadUnaligned<UINT32>(p + 4);
crc = CRC32_TABLES.table[7][term1 & 0xFF] ^ CRC32_TABLES.table[6][(term1 >> 8) & 0xFF] ^ consteval Crc32Tables() {
CRC32_TABLES.table[5][(term1 >> 16) & 0xFF] ^ CRC32_TABLES.table[4][(term1 >> 24)] ^ constexpr u32 T = 0x82F63B78;
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; for (u32 i = 0; i < 256; i++) {
len -= 8; u32 crc = i;
} for (i32 j = 0; j < 8; j++) {
crc = (crc >> 1) ^ ((crc & 1) ? T : 0);
while (len--) }
crc = (crc >> 8) ^ CRC32_TABLES.table[0][(crc ^ *p++) & 0xFF]; table[0][i] = crc;
return ~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 #if IA_ARCH_X64
// IACore mandates AVX2 so no need to check inline auto crc32_x64_hw(Span<const u8> data) -> u32 {
// for Platform::GetCapabilities().HardwareCRC32 const u8 *p = data.data();
return CRC32_x64_HW(data);
#elif IA_ARCH_ARM64 u32 crc = 0xFFFFFFFF;
if (Platform::GetCapabilities().HardwareCRC32) usize len = data.size();
return CRC32_ARM64_HW(data);
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 #endif
return CRC32_Software_Slice8(data);
}
} // namespace IACore
namespace IACore #if IA_ARCH_ARM64
{ __attribute__((target("+crc"))) inline auto crc32_arm64_hw(Span<const u8> data)
CONSTEXPR UINT32 XXH_PRIME32_1 = 0x9E3779B1U; -> u32 {
CONSTEXPR UINT32 XXH_PRIME32_2 = 0x85EBCA77U; const u8 *p = data.data();
CONSTEXPR UINT32 XXH_PRIME32_3 = 0xC2B2AE3DU;
CONSTEXPR UINT32 XXH_PRIME32_4 = 0x27D4EB2FU;
CONSTEXPR UINT32 XXH_PRIME32_5 = 0x165667B1U;
INLINE UINT32 XXH32_Round(IN UINT32 seed, IN UINT32 input) u32 crc = 0xFFFFFFFF;
{ usize len = data.size();
seed += input * XXH_PRIME32_2;
seed = std::rotl(seed, 13); while (len >= 8) {
seed *= XXH_PRIME32_1; const u64 chunk = read_unaligned<u64>(p);
return seed; 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) ret = inflate(&zs, Z_NO_FLUSH);
{
return Hash_xxHash(Span<CONST UINT8>(reinterpret_cast<PCUINT8>(string.data()), string.length()), seed); } 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) return out_buffer;
{ }
CONST UINT8 *p = data.data();
CONST UINT8 *CONST bEnd = p + data.size();
UINT32 h32{};
if (data.size() >= 16) ZSTD_DCtx *dctx = ZSTD_createDCtx();
{ Vec<u8> out_buffer;
const UINT8 *const limit = bEnd - 16; out_buffer.resize(data.size() * 2);
UINT32 v1 = seed + XXH_PRIME32_1 + XXH_PRIME32_2; ZSTD_inBuffer input = {data.data(), data.size(), 0};
UINT32 v2 = seed + XXH_PRIME32_2; ZSTD_outBuffer output = {out_buffer.data(), out_buffer.size(), 0};
UINT32 v3 = seed + 0;
UINT32 v4 = seed - XXH_PRIME32_1;
do usize ret;
{ do {
v1 = XXH32_Round(v1, ReadUnaligned<UINT32>(p)); ret = ZSTD_decompressStream(dctx, &output, &input);
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);
h32 = std::rotl(v1, 1) + std::rotl(v2, 7) + std::rotl(v3, 12) + std::rotl(v4, 18); if (ZSTD_isError(ret)) {
} ZSTD_freeDCtx(dctx);
else return fail("Failed to inflate: {}", ZSTD_getErrorName(ret));
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;
} }
UINT32 DataOps::Hash_FNV1A(IN Span<CONST UINT8> data) if (output.pos == output.size) {
{ const usize new_size = out_buffer.size() * 2;
UINT32 hash = FNV1A_32_OFFSET; out_buffer.resize(new_size);
const uint8_t *ptr = static_cast<const uint8_t *>(data.data()); output.dst = out_buffer.data();
output.size = new_size;
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;
} }
EXPECT(Vector<UINT8>) DataOps::ZlibInflate(IN Span<CONST UINT8> data) } while (ret != 0);
{
z_stream zs{};
zs.zalloc = Z_NULL;
zs.zfree = Z_NULL;
zs.opaque = Z_NULL;
// 15 + 32 = Auto-detect Gzip or Zlib out_buffer.resize(output.pos);
if (inflateInit2(&zs, 15 + 32) != Z_OK) ZSTD_freeDCtx(dctx);
return MakeUnexpected("Failed to initialize zlib inflate");
zs.next_in = const_cast<Bytef *>(data.data()); return out_buffer;
zs.avail_in = static_cast<uInt>(data.size()); }
Vector<UINT8> outBuffer; auto DataOps::zstd_deflate(Span<const u8> data) -> Result<Vec<u8>> {
// Start with 2x input size. const usize max_dst_size = ZSTD_compressBound(data.size());
size_t guessSize = data.size() < 1024 ? data.size() * 4 : data.size() * 2;
outBuffer.resize(guessSize);
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data()); Vec<u8> out_buffer;
zs.avail_out = static_cast<uInt>(outBuffer.size()); out_buffer.resize(max_dst_size);
int ret; const usize compressed_size = ZSTD_compress(out_buffer.data(), max_dst_size,
do data.data(), data.size(), 3);
{
if (zs.avail_out == 0)
{
size_t currentPos = zs.total_out;
size_t newSize = outBuffer.size() * 2; if (ZSTD_isError(compressed_size)) {
outBuffer.resize(newSize); 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) out_buffer.resize(deflateBound(&zs, static_cast<uLong>(data.size())) +
return MakeUnexpected("Failed to inflate: corrupt data or stream error"); 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) if (ret != Z_STREAM_END) {
{ deflateEnd(&zs);
z_stream zs{}; return fail("Failed to deflate");
zs.zalloc = Z_NULL; }
zs.zfree = Z_NULL;
zs.opaque = Z_NULL;
if (deflateInit(&zs, Z_DEFAULT_COMPRESSION) != Z_OK) out_buffer.resize(zs.total_out);
return MakeUnexpected("Failed to initialize zlib deflate");
zs.next_in = const_cast<Bytef *>(data.data()); deflateEnd(&zs);
zs.avail_in = static_cast<uInt>(data.size()); 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 } // namespace IACore

View File

@ -14,501 +14,523 @@
// limitations under the License. // limitations under the License.
#include <IACore/FileOps.hpp> #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 #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()); 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 #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, if (handle == INVALID_HANDLE_VALUE) {
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); return fail("Failed to open {} for memory mapping", path.string());
}
if (handle == INVALID_HANDLE_VALUE) LARGE_INTEGER file_size;
return MakeUnexpected(std::format("Failed to open {} for memory mapping", path.string().c_str())); 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; auto h_map = CreateFileMappingW(handle, NULL, PAGE_READONLY, 0, 0, NULL);
if (!GetFileSizeEx(handle, &fileSize)) if (h_map == NULL) {
{ CloseHandle(handle);
CloseHandle(handle); return fail("Failed to memory map {}", path.string());
return MakeUnexpected(std::format("Failed to get size of {} for memory mapping", path.string().c_str())); }
}
size = static_cast<size_t>(fileSize.QuadPart);
if (size == 0)
{
CloseHandle(handle);
return MakeUnexpected(std::format("Failed to get size of {} for memory mapping", path.string().c_str()));
}
auto hmap = CreateFileMappingW(handle, NULL, PAGE_READONLY, 0, 0, NULL); const auto *result =
if (hmap == NULL) static_cast<const u8 *>(MapViewOfFile(h_map, FILE_MAP_READ, 0, 0, 0));
{ if (result == NULL) {
CloseHandle(handle); CloseHandle(handle);
return MakeUnexpected(std::format("Failed to memory map {}", path.string().c_str())); CloseHandle(h_map);
} return fail("Failed to memory map {}", path.string());
}
const auto result = static_cast<PCUINT8>(MapViewOfFile(hmap, FILE_MAP_READ, 0, 0, 0)); s_mapped_files[result] = std::make_tuple(
if (result == NULL) (void *)handle, (void *)const_cast<u8 *>(result), (void *)h_map);
{ return result;
CloseHandle(handle);
CloseHandle(hmap);
return MakeUnexpected(std::format("Failed to memory map {}", path.string().c_str()));
}
s_mappedFiles[result] = std::make_tuple((PVOID) handle, (PVOID) result, (PVOID) hmap);
return result;
#elif IA_PLATFORM_UNIX #elif IA_PLATFORM_UNIX
const auto handle = open(path.string().c_str(), O_RDONLY);
const auto handle = open(path.string().c_str(), O_RDONLY); if (handle == -1) {
if (handle == -1) return fail("Failed to open {} for memory mapping", path.string());
return MakeUnexpected(std::format("Failed to open {} for memory mapping", path.string().c_str())); }
struct stat sb; struct stat sb;
if (fstat(handle, &sb) == -1) if (fstat(handle, &sb) == -1) {
{ close(handle);
close(handle); return fail("Failed to get stats of {} for memory mapping", path.string());
return MakeUnexpected(std::format("Failed to get stats of {} for memory mapping", path.string().c_str())); }
} size = static_cast<usize>(sb.st_size);
size = static_cast<size_t>(sb.st_size); if (size == 0) {
if (size == 0) close(handle);
{ return fail("Failed to get size of {} for memory mapping", path.string());
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) {
void *addr = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, handle, 0); close(handle);
if (addr == MAP_FAILED) return fail("Failed to memory map {}", path.string());
{ }
close(handle); const auto *result = static_cast<const u8 *>(addr);
return MakeUnexpected(std::format("Failed to memory map {}", path.string().c_str())); madvise(addr, size, MADV_SEQUENTIAL);
} s_mapped_files[result] =
const auto result = static_cast<PCUINT8>(addr); std::make_tuple((void *)((u64)handle), (void *)addr, (void *)size);
madvise(addr, size, MADV_SEQUENTIAL); return result;
s_mappedFiles[result] = std::make_tuple((PVOID) ((UINT64) handle), (PVOID) addr, (PVOID) size);
return result;
#endif #endif
} }
EXPECT(StreamWriter) FileOps::StreamToFile(IN CONST FilePath &path, IN BOOL overwrite) auto FileOps::stream_to_file(const Path &path, bool overwrite)
{ -> Result<StreamWriter> {
if (!overwrite && FileSystem::exists(path)) if (!overwrite && std::filesystem::exists(path)) {
return MakeUnexpected(std::format("File aready exists: {}", path.string().c_str())); return fail("File already exists: {}", path.string());
return StreamWriter(path); }
} return StreamWriter::create(path);
}
EXPECT(StreamReader) FileOps::StreamFromFile(IN CONST FilePath &path) auto FileOps::stream_from_file(const Path &path) -> Result<StreamReader> {
{ if (!std::filesystem::exists(path)) {
if (!FileSystem::exists(path)) return fail("File does not exist: {}", path.string());
return MakeUnexpected(std::format("File does not exist: {}", path.string().c_str())); }
return StreamReader(path); return StreamReader::create_from_file(path);
} }
EXPECT(String) FileOps::ReadTextFile(IN CONST FilePath &path) auto FileOps::read_text_file(const Path &path) -> Result<String> {
{ auto *f = fopen(path.string().c_str(), "r");
const auto f = fopen(path.string().c_str(), "r"); if (!f) {
if (!f) return fail("Failed to open file: {}", path.string());
return MakeUnexpected(std::format("Failed to open file: {}", path.string().c_str())); }
String result; String result;
fseek(f, 0, SEEK_END); fseek(f, 0, SEEK_END);
result.resize(ftell(f)); const long len = ftell(f);
fseek(f, 0, SEEK_SET); if (len > 0) {
fread(result.data(), 1, result.size(), f); result.resize(static_cast<usize>(len));
fclose(f); fseek(f, 0, SEEK_SET);
return result; fread(result.data(), 1, result.size(), f);
} }
fclose(f);
return result;
}
EXPECT(Vector<UINT8>) FileOps::ReadBinaryFile(IN CONST FilePath &path) auto FileOps::read_binary_file(const Path &path) -> Result<Vec<u8>> {
{ auto *f = fopen(path.string().c_str(), "rb");
const auto f = fopen(path.string().c_str(), "rb"); if (!f) {
if (!f) return fail("Failed to open file: {}", path.string());
return MakeUnexpected(std::format("Failed to open file: {}", path.string().c_str())); }
Vector<UINT8> result; Vec<u8> result;
fseek(f, 0, SEEK_END); fseek(f, 0, SEEK_END);
result.resize(ftell(f)); const long len = ftell(f);
fseek(f, 0, SEEK_SET); if (len > 0) {
fread(result.data(), 1, result.size(), f); result.resize(static_cast<usize>(len));
fclose(f); fseek(f, 0, SEEK_SET);
return result; 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) auto FileOps::write_text_file(const Path &path, const String &contents,
{ bool overwrite) -> Result<usize> {
const char *mode = overwrite ? "w" : "wx"; const char *mode = overwrite ? "w" : "wx";
const auto f = fopen(path.string().c_str(), mode); auto *f = fopen(path.string().c_str(), mode);
if (!f) if (!f) {
{ if (!overwrite && errno == EEXIST) {
if (!overwrite && errno == EEXIST) return fail("File already exists: {}", path.string());
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;
} }
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) auto FileOps::write_binary_file(const Path &path, Span<const u8> contents,
{ bool overwrite) -> Result<usize> {
const char *mode = overwrite ? "w" : "wx"; const char *mode = overwrite ? "w" : "wx";
const auto f = fopen(path.string().c_str(), mode); auto *f = fopen(path.string().c_str(), mode);
if (!f) if (!f) {
{ if (!overwrite && errno == EEXIST) {
if (!overwrite && errno == EEXIST) return fail("File already exists: {}", path.string());
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;
} }
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) auto FileOps::normalize_executable_path(const Path &path) -> Path {
{ Path result = path;
FilePath result = path;
#if IA_PLATFORM_WINDOWS #if IA_PLATFORM_WINDOWS
if (!result.has_extension()) if (!result.has_extension()) {
result.replace_extension(".exe"); 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 #elif IA_PLATFORM_UNIX
if (result.extension() == ".exe") int flags = 0;
result.replace_extension("");
if (result.is_relative()) switch (access) {
{ case FileAccess::Read:
String pathStr = result.string(); flags = O_RDONLY;
if (!pathStr.starts_with("./") && !pathStr.starts_with("../")) break;
result = "./" + pathStr; 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 #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 #if IA_PLATFORM_WINDOWS
DWORD dwAccess = 0; CloseHandle(handle);
DWORD dwShare = FILE_SHARE_READ; #elif IA_PLATFORM_UNIX
DWORD dwDisposition = 0; close(handle);
DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL; #endif
}
switch (access) // =============================================================================
{ // MemoryMappedRegion
case EFileAccess::READ: // =============================================================================
dwAccess = GENERIC_READ;
break;
case EFileAccess::WRITE:
dwAccess = GENERIC_WRITE;
break;
case EFileAccess::READ_WRITE:
dwAccess = GENERIC_READ | GENERIC_WRITE;
break;
}
switch (mode) FileOps::MemoryMappedRegion::~MemoryMappedRegion() { unmap(); }
{
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;
}
HANDLE hFile = FileOps::MemoryMappedRegion::MemoryMappedRegion(
CreateFileA(path.string().c_str(), dwAccess, dwShare, NULL, dwDisposition, dwFlagsAndAttributes, NULL); MemoryMappedRegion &&other) noexcept {
*this = std::move(other);
}
if (hFile == INVALID_HANDLE_VALUE) auto FileOps::MemoryMappedRegion::operator=(MemoryMappedRegion &&other) noexcept
return MakeUnexpected(std::format("Failed to open file '{}': {}", path.string(), GetLastError())); -> 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 #elif IA_PLATFORM_UNIX
int flags = 0; struct stat sb;
if (fstat(handle, &sb) == -1) {
return fail("Failed to fstat file");
}
switch (access) u64 end_offset = offset + size;
{ if (static_cast<u64>(sb.st_size) < end_offset) {
case EFileAccess::READ: if (ftruncate(handle, static_cast<off_t>(end_offset)) == -1) {
flags = O_RDONLY; return fail("Failed to ftruncate (extend) file");
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
} }
}
VOID FileOps::NativeCloseFile(IN NativeFileHandle handle) void *ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, handle,
{ static_cast<off_t>(offset));
if (handle == INVALID_FILE_HANDLE) if (ptr == MAP_FAILED) {
return; 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 #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 #elif IA_PLATFORM_UNIX
close(handle); munmap(m_ptr, m_size);
#endif #endif
} m_ptr = nullptr;
m_size = 0;
}
FileOps::MemoryMappedRegion::~MemoryMappedRegion() auto FileOps::MemoryMappedRegion::flush() -> void {
{ if (!m_ptr) {
Unmap(); return;
} }
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");
#if IA_PLATFORM_WINDOWS #if IA_PLATFORM_WINDOWS
LARGE_INTEGER fileSize; FlushViewOfFile(m_ptr, m_size);
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;
#elif IA_PLATFORM_UNIX #elif IA_PLATFORM_UNIX
struct stat sb; msync(m_ptr, m_size, MS_SYNC);
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);
#endif #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 } // namespace IACore

View File

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

View File

@ -15,110 +15,103 @@
#include <IACore/Http/Common.hpp> #include <IACore/Http/Common.hpp>
namespace IACore namespace IACore {
{ String HttpCommon::UrlEncode(const String &value) {
String HttpCommon::UrlEncode(IN CONST String &value) std::stringstream escaped;
{ escaped.fill('0');
std::stringstream escaped; escaped << std::hex << std::uppercase;
escaped.fill('0');
escaped << std::hex << std::uppercase;
for (char c : value) for (char c : value) {
{ if (std::isalnum(static_cast<unsigned char>(c)) || c == '-' || c == '_' ||
if (std::isalnum(static_cast<unsigned char>(c)) || c == '-' || c == '_' || c == '.' || c == '~') c == '.' || c == '~')
escaped << c; escaped << c;
else else
escaped << '%' << std::setw(2) << static_cast<int>(static_cast<unsigned char>(c)); 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 HttpCommon::UrlDecode(const String &value) {
{ String result;
String result; result.reserve(value.length());
result.reserve(value.length());
for (size_t i = 0; i < value.length(); ++i) for (size_t i = 0; i < value.length(); ++i) {
{ if (value[i] == '%' && i + 2 < value.length()) {
if (value[i] == '%' && i + 2 < value.length()) std::string hexStr = value.substr(i + 1, 2);
{ char decodedChar =
std::string hexStr = value.substr(i + 1, 2); static_cast<char>(std::strtol(hexStr.c_str(), nullptr, 16));
char decodedChar = static_cast<char>(std::strtol(hexStr.c_str(), nullptr, 16)); result += decodedChar;
result += decodedChar; i += 2;
i += 2; } else if (value[i] == '+')
} result += ' ';
else if (value[i] == '+') else
result += ' '; result += value[i];
else }
result += value[i];
}
return result; return result;
} }
String HttpCommon::HeaderTypeToString(IN EHeaderType type) String HttpCommon::HeaderTypeToString(EHeaderType type) {
{ switch (type) {
switch (type) case EHeaderType::ACCEPT:
{ return "Accept";
case EHeaderType::ACCEPT: case EHeaderType::ACCEPT_CHARSET:
return "Accept"; return "Accept-Charset";
case EHeaderType::ACCEPT_CHARSET: case EHeaderType::ACCEPT_ENCODING:
return "Accept-Charset"; return "Accept-Encoding";
case EHeaderType::ACCEPT_ENCODING: case EHeaderType::ACCEPT_LANGUAGE:
return "Accept-Encoding"; return "Accept-Language";
case EHeaderType::ACCEPT_LANGUAGE: case EHeaderType::AUTHORIZATION:
return "Accept-Language"; return "Authorization";
case EHeaderType::AUTHORIZATION: case EHeaderType::CACHE_CONTROL:
return "Authorization"; return "Cache-Control";
case EHeaderType::CACHE_CONTROL: case EHeaderType::CONNECTION:
return "Cache-Control"; return "Connection";
case EHeaderType::CONNECTION: case EHeaderType::CONTENT_LENGTH:
return "Connection"; return "Content-Length";
case EHeaderType::CONTENT_LENGTH: case EHeaderType::CONTENT_TYPE:
return "Content-Length"; return "Content-Type";
case EHeaderType::CONTENT_TYPE: case EHeaderType::COOKIE:
return "Content-Type"; return "Cookie";
case EHeaderType::COOKIE: case EHeaderType::DATE:
return "Cookie"; return "Date";
case EHeaderType::DATE: case EHeaderType::EXPECT:
return "Date"; return "Expect";
case EHeaderType::EXPECT: case EHeaderType::HOST:
return "Expect"; return "Host";
case EHeaderType::HOST: case EHeaderType::IF_MATCH:
return "Host"; return "If-Match";
case EHeaderType::IF_MATCH: case EHeaderType::IF_MODIFIED_SINCE:
return "If-Match"; return "If-Modified-Since";
case EHeaderType::IF_MODIFIED_SINCE: case EHeaderType::IF_NONE_MATCH:
return "If-Modified-Since"; return "If-None-Match";
case EHeaderType::IF_NONE_MATCH: case EHeaderType::ORIGIN:
return "If-None-Match"; return "Origin";
case EHeaderType::ORIGIN: case EHeaderType::PRAGMA:
return "Origin"; return "Pragma";
case EHeaderType::PRAGMA: case EHeaderType::PROXY_AUTHORIZATION:
return "Pragma"; return "Proxy-Authorization";
case EHeaderType::PROXY_AUTHORIZATION: case EHeaderType::RANGE:
return "Proxy-Authorization"; return "Range";
case EHeaderType::RANGE: case EHeaderType::REFERER:
return "Range"; return "Referer";
case EHeaderType::REFERER: case EHeaderType::TE:
return "Referer"; return "TE";
case EHeaderType::TE: case EHeaderType::UPGRADE:
return "TE"; return "Upgrade";
case EHeaderType::UPGRADE: case EHeaderType::USER_AGENT:
return "Upgrade"; return "User-Agent";
case EHeaderType::USER_AGENT: case EHeaderType::VIA:
return "User-Agent"; return "Via";
case EHeaderType::VIA: case EHeaderType::WARNING:
return "Via"; return "Warning";
case EHeaderType::WARNING: }
return "Warning"; return "<Unknown>";
} }
return "<Unknown>";
}
BOOL HttpCommon::IsSuccessResponseCode(IN EResponseCode code) bool HttpCommon::IsSuccessResponseCode(EResponseCode code) {
{ return (i32)code >= 200 && (i32)code < 300;
return (INT32) code >= 200 && (INT32) code < 300; }
}
} // namespace IACore } // namespace IACore

View File

@ -17,76 +17,50 @@
#include <IACore/Logger.hpp> #include <IACore/Logger.hpp>
#include <mimalloc.h> #include <mimalloc.h>
#include <chrono>
#include <cstdlib>
namespace IACore namespace IACore
{ {
HighResTimePoint g_startTime{}; auto g_start_time = std::chrono::high_resolution_clock::time_point{};
std::thread::id g_mainThreadID{};
INT32 g_coreInitCount{};
VOID Initialize() static auto g_main_thread_id = std::thread::id{};
static auto g_core_init_count = i32{0};
auto initialize() -> void
{ {
g_coreInitCount++; g_core_init_count++;
if (g_coreInitCount > 1) if (g_core_init_count > 1)
{
return; 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); mi_option_set(mi_option_verbose, 0);
} }
VOID Terminate() auto terminate() -> void
{ {
g_coreInitCount--; g_core_init_count--;
if (g_coreInitCount > 0) if (g_core_init_count > 0)
{
return; 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::this_thread::get_id() == g_main_thread_id;
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;
} }
} // namespace IACore } // namespace IACore

View File

@ -17,435 +17,441 @@
#include <IACore/FileOps.hpp> #include <IACore/FileOps.hpp>
#include <IACore/StringOps.hpp> #include <IACore/StringOps.hpp>
#include <charconv>
#include <fcntl.h>
namespace IACore namespace IACore {
{ // =============================================================================
struct IPC_ConnectionDescriptor // Internal: Connection Descriptor
{ // =============================================================================
String SocketPath; struct IpcConnectionDescriptor {
String SharedMemPath; String socket_path;
UINT32 SharedMemSize; String shared_mem_path;
u32 shared_mem_size;
String Serialize() CONST [[nodiscard]] auto serialize() const -> String {
{ return std::format("{}|{}|{}|", socket_path, shared_mem_path,
return std::format("{}|{}|{}|", SocketPath, SharedMemPath, SharedMemSize); 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;
} }
return result;
STATIC IPC_ConnectionDescriptor Deserialize(IN CONST String &data) }
{ }
enum class EParseState t = i + 1;
{
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 std::nullopt;
}
};
EXPECT(VOID) IPC_Node::Connect(IN PCCHAR connectionString) // =============================================================================
{ // IpcNode Implementation
auto desc = IPC_ConnectionDescriptor::Deserialize(connectionString); // =============================================================================
m_shmName = desc.SharedMemPath;
m_socket = SocketOps::CreateUnixSocket(); IpcNode::~IpcNode() {
if (!SocketOps::ConnectUnixSocket(m_socket, desc.SocketPath.c_str())) if (m_socket != INVALID_SOCKET) {
return MakeUnexpected("Failed to create an unix socket"); SocketOps::close(m_socket);
}
}
auto mapRes = FileOps::MapSharedMemory(desc.SharedMemPath, desc.SharedMemSize, FALSE); auto IpcNode::connect(const char *connection_string) -> Result<void> {
if (!mapRes.has_value()) const auto desc_opt = IpcConnectionDescriptor::deserialize(connection_string);
return MakeUnexpected("Failed to map the shared memory"); 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" auto *layout = reinterpret_cast<IpcSharedMemoryLayout *>(m_shared_memory);
return MakeUnexpected("Invalid shared memory header signature");
if (layout->Meta.Version != 1) if (layout->meta.magic != 0x49414950) // "IAIP"
return MakeUnexpected("IPC version mismatch"); {
return fail("Invalid shared memory header signature");
}
PUINT8 moniDataPtr = m_sharedMemory + layout->MONI_DataOffset; if (layout->meta.version != 1) {
PUINT8 minoDataPtr = m_sharedMemory + layout->MINO_DataOffset; return fail("IPC version mismatch");
}
MONI = std::make_unique<RingBufferView>( u8 *moni_ptr = m_shared_memory + layout->moni_data_offset;
&layout->MONI_Control, Span<UINT8>(moniDataPtr, static_cast<size_t>(layout->MONI_DataSize)), FALSE); u8 *mino_ptr = m_shared_memory + layout->mino_data_offset;
MINO = std::make_unique<RingBufferView>( m_moni = make_box<RingBufferView>(
&layout->MINO_Control, Span<UINT8>(minoDataPtr, static_cast<size_t>(layout->MINO_DataSize)), FALSE); &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 #if IA_PLATFORM_WINDOWS
u_long mode = 1; u_long mode = 1;
ioctlsocket(m_socket, FIONBIO, &mode); ioctlsocket(m_socket, FIONBIO, &mode);
#else #else
fcntl(m_socket, F_SETFL, O_NONBLOCK); fcntl(m_socket, F_SETFL, O_NONBLOCK);
#endif #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() auto new_sock = accept(session->listener_socket, nullptr, nullptr);
{
if (!MONI)
return;
RingBufferView::PacketHeader header; if (new_sock != INVALID_SOCKET) {
session->data_socket = new_sock;
session->is_ready = true;
// Process all available messages from Manager // Set Data Socket to Non-Blocking
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
#if IA_PLATFORM_WINDOWS #if IA_PLATFORM_WINDOWS
u_long mode = 1; u_long mode = 1;
ioctlsocket(session->DataSocket, FIONBIO, &mode); ioctlsocket(session->data_socket, FIONBIO, &mode);
#else #else
fcntl(session->DataSocket, F_SETFL, O_NONBLOCK); fcntl(session->data_socket, F_SETFL, O_NONBLOCK);
#endif #endif
SocketOps::Close(session->ListenerSocket); SocketOps::close(session->listener_socket);
session->ListenerSocket = INVALID_SOCKET; session->listener_socket = INVALID_SOCKET;
const auto sessionID = session->NodeProcess->ID.load(); const auto session_id = session->node_process->id.load();
const auto sessionPtr = session.get(); auto *session_ptr = session.get();
m_activeSessions.push_back(std::move(session)); m_active_sessions.push_back(std::move(session));
m_pendingSessions.erase(m_pendingSessions.begin() + i); m_pending_sessions.erase(m_pending_sessions.begin() + i);
m_activeSessionMap[sessionID] = sessionPtr; m_active_session_map[session_id] = session_ptr;
} }
} }
for (INT32 i = m_activeSessions.size() - 1; i >= 0; i--) for (isize i = static_cast<isize>(m_active_sessions.size()) - 1; i >= 0;
{ --i) {
auto &node = m_activeSessions[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()))) while (node->mino->pop(
OnPacket(nodeID, header.ID, {m_receiveBuffer.data(), header.PayloadSize}); header, Span<u8>(m_receive_buffer.data(), m_receive_buffer.size()))) {
on_packet(node_id, header.id,
UINT8 signal; {m_receive_buffer.data(), header.payload_size});
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);
}
}
} }
EXPECT(NativeProcessID) IPC_Manager::SpawnNode(IN CONST FilePath &executablePath, IN UINT32 sharedMemorySize) u8 signal = 0;
{ const auto res =
auto session = std::make_unique<NodeSession>(); recv(node->data_socket, reinterpret_cast<char *>(&signal), 1, 0);
static Atomic<UINT32> s_idGen{0}; if (res == 1) {
UINT32 sid = ++s_idGen; 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 #if IA_PLATFORM_WINDOWS
char tempPath[MAX_PATH]; u_long mode = 1;
GetTempPathA(MAX_PATH, tempPath); ioctlsocket(session->listener_socket, FIONBIO, &mode);
String sockPath = std::format("{}\\ia_sess_{}.sock", tempPath, sid);
#else #else
String sockPath = std::format("/tmp/ia_sess_{}.sock", sid); fcntl(session->listener_socket, F_SETFL, O_NONBLOCK);
#endif #endif
session->ListenerSocket = SocketOps::CreateUnixSocket(); const String shm_name = std::format("ia_shm_{}", sid);
if (!SocketOps::BindUnixSocket(session->ListenerSocket, sockPath.c_str())) IA_TRY(session->mapped_ptr,
return MakeUnexpected("Failed to bind unique socket"); FileOps::map_shared_memory(shm_name, shared_memory_size, true));
if (listen(session->ListenerSocket, 1) != 0) auto *layout = reinterpret_cast<IpcSharedMemoryLayout *>(session->mapped_ptr);
return MakeUnexpected("Failed to listen on unique socket");
#if IA_PLATFORM_WINDOWS layout->meta.magic = 0x49414950;
u_long mode = 1; layout->meta.version = 1;
ioctlsocket(session->ListenerSocket, FIONBIO, &mode); layout->meta.total_size = shared_memory_size;
#else
fcntl(session->ListenerSocket, F_SETFL, O_NONBLOCK);
#endif
String shmName = std::format("ia_shm_{}", sid); const u64 header_size = IpcSharedMemoryLayout::get_header_size();
auto mapRes = FileOps::MapSharedMemory(shmName, sharedMemorySize, TRUE); const u64 usable_bytes = shared_memory_size - header_size;
if (!mapRes.has_value())
return MakeUnexpected("Failed to map shared memory");
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->mino_data_offset = header_size + half_size;
layout->Meta.Version = 1; layout->mino_data_size = half_size;
layout->Meta.TotalSize = sharedMemorySize;
UINT64 headerSize = IPC_SharedMemoryLayout::GetHeaderSize(); session->moni = make_box<RingBufferView>(
UINT64 usableBytes = sharedMemorySize - headerSize; &layout->moni_control,
Span<u8>(session->mapped_ptr + layout->moni_data_offset,
static_cast<usize>(layout->moni_data_size)),
true);
UINT64 halfSize = (usableBytes / 2); session->mino = make_box<RingBufferView>(
halfSize -= (halfSize % 64); &layout->mino_control,
Span<u8>(session->mapped_ptr + layout->mino_data_offset,
static_cast<usize>(layout->mino_data_size)),
true);
layout->MONI_DataOffset = headerSize; IpcConnectionDescriptor desc;
layout->MONI_DataSize = halfSize; desc.socket_path = sock_path;
desc.shared_mem_path = shm_name;
desc.shared_mem_size = shared_memory_size;
layout->MINO_DataOffset = headerSize + halfSize; const String args = std::format("\"{}\"", desc.serialize());
layout->MINO_DataSize = halfSize;
session->MONI = std::make_unique<RingBufferView>( IA_TRY(session->node_process,
&layout->MONI_Control, Span<UINT8>(mappedPtr + layout->MONI_DataOffset, layout->MONI_DataSize), TRUE); 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>( // Give some time for child node to stablize
&layout->MINO_Control, Span<UINT8>(mappedPtr + layout->MINO_DataOffset, layout->MINO_DataSize), TRUE); 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; const auto process_id = session->node_process->id.load();
desc.SocketPath = sockPath;
desc.SharedMemPath = shmName;
desc.SharedMemSize = sharedMemorySize;
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( return process_id;
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
});
// Give some time for child node to stablize auto IpcManager::wait_till_node_is_online(NativeProcessID node_id) -> bool {
std::this_thread::sleep_for(std::chrono::seconds(1)); bool is_pending = true;
if (!session->NodeProcess->IsActive()) while (is_pending) {
return MakeUnexpected(std::format("Failed to spawn the child process \"{}\"", executablePath.string())); is_pending = false;
for (const auto &session : m_pending_sessions) {
auto processID = session->NodeProcess->ID.load(); if (session->node_process->id.load() == node_id) {
is_pending = true;
session->CreationTime = SteadyClock::now(); break;
m_pendingSessions.push_back(std::move(session)); }
return processID;
} }
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) void IpcManager::shutdown_node(NativeProcessID node_id) {
{ const auto it_node = m_active_session_map.find(node_id);
BOOL isPending = true; if (it_node == m_active_session_map.end()) {
while (isPending) return;
{ }
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 IPC_Manager::ShutdownNode(IN NativeProcessID nodeID) auto *node = it_node->second;
{
const auto itNode = m_activeSessionMap.find(nodeID);
if (itNode == m_activeSessionMap.end())
return;
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); std::erase_if(m_active_sessions,
FileOps::UnmapFile(node->MappedPtr); [&](const auto &s) { return s.get() == node; });
FileOps::UnlinkSharedMemory(node->SharedMemName); m_active_session_map.erase(it_node);
SocketOps::Close(node->DataSocket); }
for (auto it = m_activeSessions.begin(); it != m_activeSessions.end(); it++) void IpcManager::send_signal(NativeProcessID node, u8 signal) {
{ const auto it_node = m_active_session_map.find(node);
if (it->get() == node) if (it_node == m_active_session_map.end()) {
{ return;
m_activeSessions.erase(it); }
break; it_node->second->send_signal(signal);
} }
}
m_activeSessionMap.erase(itNode); void IpcManager::send_packet(NativeProcessID node, u16 packet_id,
} Span<const u8> payload) {
const auto it_node = m_active_session_map.find(node);
VOID IPC_Manager::SendSignal(IN NativeProcessID node, IN UINT8 signal) if (it_node == m_active_session_map.end()) {
{ return;
const auto itNode = m_activeSessionMap.find(node); }
if (itNode == m_activeSessionMap.end()) it_node->second->send_packet(packet_id, payload);
return; }
itNode->second->SendSignal(signal);
}
VOID IPC_Manager::SendPacket(IN NativeProcessID node, IN UINT16 packetID, IN Span<CONST UINT8> payload)
{
const auto itNode = m_activeSessionMap.find(node);
if (itNode == m_activeSessionMap.end())
return;
itNode->second->SendPacket(packetID, payload);
}
} // namespace IACore } // namespace IACore

View File

@ -17,27 +17,5 @@
namespace IACore 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 } // 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/Logger.hpp>
#include <IACore/IACore.hpp> #include <chrono>
#include <IACore/FileOps.hpp> #include <fstream>
#include <iostream>
namespace IACore namespace IACore {
{ Logger::LogLevel Logger::m_log_level = Logger::LogLevel::Info;
Logger::ELogLevel Logger::s_logLevel{Logger::ELogLevel::INFO}; std::ofstream Logger::m_log_file;
std::ofstream Logger::s_logFile{};
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() auto Logger::initialize() -> void {}
{
if (s_logFile.is_open())
{
s_logFile.flush();
s_logFile.close();
}
}
BOOL Logger::EnableLoggingToDisk(IN PCCHAR filePath) auto Logger::terminate() -> void {
{ if (m_log_file.is_open()) {
if (s_logFile.is_open()) m_log_file.flush();
{ m_log_file.close();
s_logFile.flush(); }
s_logFile.close(); }
}
s_logFile.open(filePath);
return s_logFile.is_open();
}
VOID Logger::SetLogLevel(IN ELogLevel logLevel) auto Logger::enable_logging_to_disk(const char *file_path) -> Result<void> {
{ if (m_log_file.is_open()) {
s_logLevel = logLevel; m_log_file.flush();
} m_log_file.close();
}
VOID Logger::FlushLogs() m_log_file.open(file_path);
{
std::cout.flush();
if (s_logFile)
s_logFile.flush();
}
VOID Logger::LogInternal(IN PCCHAR prefix, IN PCCHAR tag, IN String &&msg) if (!m_log_file.is_open()) {
{ return fail("Failed to open log file: {}", file_path);
const auto outLine = std::format("[{:>8.3f}]: [{}]: {}", GetSecondsCount(), tag, msg); }
std::cout << prefix << outLine << "\033[39m\n";
if (s_logFile) return {};
{ }
s_logFile.write(outLine.data(), outLine.size());
s_logFile.put('\n'); auto Logger::set_log_level(LogLevel log_level) -> void {
s_logFile.flush(); 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 } // namespace IACore

View File

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

View File

@ -15,327 +15,317 @@
#include <IACore/ProcessOps.hpp> #include <IACore/ProcessOps.hpp>
namespace IACore namespace IACore {
{ // ---------------------------------------------------------------------
// --------------------------------------------------------------------- // Output Buffering Helper
// Output Buffering Helper // Splits raw chunks into lines, preserving partial lines across chunks
// Splits raw chunks into lines, preserving partial lines across chunks // ---------------------------------------------------------------------
// --------------------------------------------------------------------- struct LineBuffer {
struct LineBuffer String m_accumulator;
{ std::function<void(StringView)> &m_callback;
String Accumulator;
Function<VOID(StringView)> &Callback;
VOID Append(IN PCCHAR data, IN SIZE_T size); void append(const char *data, usize size);
VOID Flush(); void flush();
}; };
} // namespace IACore
namespace IACore void LineBuffer::append(const char *data, usize size) {
{ usize start = 0;
NativeProcessID ProcessOps::GetCurrentProcessID() for (usize i = 0; i < size; ++i) {
{ if (data[i] == '\n' || data[i] == '\r') {
#if IA_PLATFORM_WINDOWS // Flush Accumulator + current chunk
return ::GetCurrentProcessId(); if (!m_accumulator.empty()) {
#else m_accumulator.append(data + start, i - start);
return getpid(); if (!m_accumulator.empty()) {
#endif m_callback(m_accumulator);
}
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);
} }
#else m_accumulator.clear();
kill(pid, SIGKILL); } else {
#endif if (i > start) {
} m_callback(StringView(data + start, i - start));
} // namespace IACore }
}
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 #if IA_PLATFORM_WINDOWS
EXPECT(INT32) return ::GetCurrentProcessId();
#else
ProcessOps::SpawnProcessWindows(IN CONST String &command, IN CONST String &args, return getpid();
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);
}
#endif #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 #if IA_PLATFORM_UNIX
EXPECT(INT32) kill(pid, SIGKILL);
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;
}
}
#endif #endif
} // namespace IACore }
namespace IACore auto ProcessOps::spawn_process_windows(
{ const String &command, const String &args,
VOID LineBuffer::Append(IN PCCHAR data, IN SIZE_T size) std::function<void(StringView)> on_output_line_callback,
{ std::atomic<NativeProcessID> &id) -> Result<i32> {
SIZE_T start = 0; #if IA_PLATFORM_WINDOWS
for (SIZE_T i = 0; i < size; ++i) SECURITY_ATTRIBUTES sa_attr = {sizeof(SECURITY_ATTRIBUTES), NULL,
{ TRUE}; // Allow inheritance
if (data[i] == '\n' || data[i] == '\r') HANDLE h_read = NULL;
{ HANDLE h_write = NULL;
// Flush Accumulator + current chunk
if (!Accumulator.empty())
{
Accumulator.append(data + start, i - start);
if (!Accumulator.empty())
Callback(Accumulator);
Accumulator.clear();
}
else
{
if (i > start)
Callback(StringView(data + start, i - start));
}
// Skip \r\n sequence if needed, or just start next if (!CreatePipe(&h_read, &h_write, &sa_attr, 0)) {
if (data[i] == '\r' && i + 1 < size && data[i + 1] == '\n') return fail("Failed to create pipe");
i++; }
start = i + 1;
} // Ensure the read handle to the pipe for STDOUT is NOT inherited
} if (!SetHandleInformation(h_read, HANDLE_FLAG_INHERIT, 0)) {
// Save remaining partial line return fail("Failed to secure pipe handles");
if (start < size) }
{
Accumulator.append(data + start, size - start); 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 (!current_token.empty()) {
{ arg_storage.push_back(current_token);
if (!Accumulator.empty())
{
Callback(Accumulator);
Accumulator.clear();
}
} }
// 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 } // namespace IACore

View File

@ -14,92 +14,133 @@
// limitations under the License. // limitations under the License.
#include <IACore/SocketOps.hpp> #include <IACore/SocketOps.hpp>
#include <cstring>
namespace IACore namespace IACore {
{ i32 SocketOps::s_init_count = 0;
INT32 SocketOps::s_initCount{0};
VOID SocketOps::Close(IN SocketHandle sock) auto SocketOps::close(SocketHandle sock) -> void {
{ if (sock == INVALID_SOCKET) {
if (sock == INVALID_SOCKET) return;
return; }
CLOSE_SOCKET(sock);
}
BOOL SocketOps::Listen(IN SocketHandle sock, IN INT32 queueSize)
{
return listen(sock, queueSize) == 0;
}
SocketHandle SocketOps::CreateUnixSocket()
{
return socket(AF_UNIX, SOCK_STREAM, 0);
}
BOOL SocketOps::BindUnixSocket(IN SocketHandle sock, IN PCCHAR path)
{
if (!IS_VALID_SOCKET(sock))
return FALSE;
UNLINK_FILE(path);
sockaddr_un addr{};
addr.sun_family = AF_UNIX;
size_t maxLen = sizeof(addr.sun_path) - 1;
strncpy(addr.sun_path, path, maxLen);
if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) == -1)
return FALSE;
return TRUE;
}
BOOL SocketOps::ConnectUnixSocket(IN SocketHandle sock, IN PCCHAR path)
{
if (!IS_VALID_SOCKET(sock))
return FALSE;
sockaddr_un addr{};
addr.sun_family = AF_UNIX;
size_t maxLen = sizeof(addr.sun_path) - 1;
strncpy(addr.sun_path, path, maxLen);
if (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) == -1)
return FALSE;
return TRUE;
}
BOOL SocketOps::IsPortAvailable(IN UINT16 port, IN INT32 type)
{
SocketHandle sock = socket(AF_INET, type, IPPROTO_UDP);
if (!IS_VALID_SOCKET(sock))
return false;
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
bool isFree = false;
if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) == 0)
isFree = true;
CLOSE_SOCKET(sock);
return isFree;
}
BOOL SocketOps::IsWouldBlock()
{
#if IA_PLATFORM_WINDOWS #if IA_PLATFORM_WINDOWS
return WSAGetLastError() == WSAEWOULDBLOCK; closesocket(sock);
#else #else
return errno == EWOULDBLOCK || errno == EAGAIN; ::close(sock);
#endif #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 } // namespace IACore

View File

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

View File

@ -14,83 +14,88 @@
// limitations under the License. // limitations under the License.
#include <IACore/StreamWriter.hpp> #include <IACore/StreamWriter.hpp>
#include <IACore/Logger.hpp>
namespace IACore namespace IACore {
{
StreamWriter::StreamWriter() : m_storageType(EStorageType::OWNING_VECTOR) auto StreamWriter::create(const Path &path) -> Result<StreamWriter> {
{ // Try to open the file to ensure we have write permissions and to truncate
m_owningVector.resize(m_capacity = 256); // it.
m_buffer = m_owningVector.data(); 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) FILE *f = std::fopen(m_file_path.string().c_str(), "wb");
: m_buffer(data.data()), m_capacity(data.size()), m_storageType(EStorageType::NON_OWNING) 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) // Growth strategy: Current capacity + (count * 2)
{ const usize new_capacity = m_capacity + (count << 1);
IA_RELEASE_ASSERT(!path.empty()); m_owning_vector.resize(new_capacity);
const auto f = fopen(m_filePath.string().c_str(), "wb"); m_capacity = m_owning_vector.size();
if (!f) m_buffer = m_owning_vector.data();
{ }
Logger::Error("Failed to open file for writing {}", m_filePath.string().c_str());
return;
}
fputc(0, f);
fclose(f);
m_owningVector.resize(m_capacity = 256); std::memset(m_buffer + m_cursor, byte, count);
m_buffer = m_owningVector.data(); 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() const usize new_capacity = m_capacity + (size << 1);
{ m_owning_vector.resize(new_capacity);
switch (m_storageType) m_capacity = m_owning_vector.size();
{ m_buffer = m_owning_vector.data();
case EStorageType::OWNING_FILE: { }
IA_RELEASE_ASSERT(!m_filePath.empty());
const auto f = fopen(m_filePath.string().c_str(), "wb");
if (!f)
{
Logger::Error("Failed to open file for writing {}", m_filePath.string().c_str());
return;
}
fwrite(m_owningVector.data(), 1, m_owningVector.size(), f);
fclose(f);
}
break;
case EStorageType::OWNING_VECTOR: std::memcpy(m_buffer + m_cursor, buffer, size);
case EStorageType::NON_OWNING: m_cursor += size;
break; 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 } // namespace IACore

View File

@ -15,86 +15,77 @@
#include <IACore/StringOps.hpp> #include <IACore/StringOps.hpp>
namespace IACore namespace IACore {
{ const String BASE64_CHAR_TABLE =
CONST String BASE64_CHAR_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
String StringOps::EncodeBase64(IN Span<CONST UINT8> data) auto StringOps::encode_base64(Span<const u8> data) -> String {
{ String result;
String result; result.reserve(((data.size() + 2) / 3) * 4);
result.reserve(((data.size() + 2) / 3) * 4); for (size_t i = 0; i < data.size(); i += 3) {
for (size_t i = 0; i < data.size(); i += 3) uint32_t value = 0;
{ i32 num_bytes = 0;
uint32_t value = 0; for (i32 j = 0; j < 3 && (i + j) < data.size(); ++j) {
INT32 num_bytes = 0; value = (value << 8) | data[i + j];
for (INT32 j = 0; j < 3 && (i + j) < data.size(); ++j) num_bytes++;
{
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;
} }
for (i32 j = 0; j < num_bytes + 1; ++j) {
Vector<UINT8> StringOps::DecodeBase64(IN CONST String &data) if (j < 4) {
{ result += BASE64_CHAR_TABLE[(value >> (6 * (3 - j))) & 0x3F];
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;
} }
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 } // 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 namespace IACore
{ {
EXPECT(XML::Document) XML::ParseFromString(IN CONST String &data) Result<XML::Document> XML::parse_from_string(const String &data)
{ {
Document doc; Document doc;
const auto parseResult = doc.load_string(data.data()); const auto parseResult = doc.load_string(data.data());
if (!parseResult) if (!parseResult)
return MakeUnexpected(std::format("Failed to parse XML: {}", parseResult.description())); return fail("Failed to parse XML {}", parseResult.description());
return IA_MOVE(doc); 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; Document doc;
const auto parseResult = doc.load_file(path.string().c_str()); const auto parseResult = doc.load_file(path.string().c_str());
if (!parseResult) if (!parseResult)
return MakeUnexpected(std::format("Failed to parse XML: {}", parseResult.description())); return fail("Failed to parse XML {}", parseResult.description());
return IA_MOVE(doc); 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; std::ostringstream oss;
node.print(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; std::ostringstream oss;
doc.save(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; String buffer;
buffer.reserve(xml.size() + (xml.size() / 10)); buffer.reserve(xml.size() + (xml.size() / 10));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -19,139 +19,135 @@
#include <httplib.h> #include <httplib.h>
namespace IACore namespace IACore {
{ class HttpCommon {
class HttpCommon public:
{ enum class EHeaderType {
public: ACCEPT,
enum class EHeaderType ACCEPT_CHARSET,
{ ACCEPT_ENCODING,
ACCEPT, ACCEPT_LANGUAGE,
ACCEPT_CHARSET, AUTHORIZATION,
ACCEPT_ENCODING, CACHE_CONTROL,
ACCEPT_LANGUAGE, CONNECTION,
AUTHORIZATION, CONTENT_LENGTH,
CACHE_CONTROL, CONTENT_TYPE,
CONNECTION, COOKIE,
CONTENT_LENGTH, DATE,
CONTENT_TYPE, EXPECT,
COOKIE, HOST,
DATE, IF_MATCH,
EXPECT, IF_MODIFIED_SINCE,
HOST, IF_NONE_MATCH,
IF_MATCH, ORIGIN,
IF_MODIFIED_SINCE, PRAGMA,
IF_NONE_MATCH, PROXY_AUTHORIZATION,
ORIGIN, RANGE,
PRAGMA, REFERER,
PROXY_AUTHORIZATION, TE,
RANGE, UPGRADE,
REFERER, USER_AGENT,
TE, VIA,
UPGRADE, WARNING
USER_AGENT, };
VIA,
WARNING
};
enum class EResponseCode : INT32 enum class EResponseCode : i32 {
{ // 1xx Informational
// 1xx Informational CONTINUE = 100,
CONTINUE = 100, SWITCHING_PROTOCOLS = 101,
SWITCHING_PROTOCOLS = 101, PROCESSING = 102,
PROCESSING = 102, EARLY_HINTS = 103,
EARLY_HINTS = 103,
// 2xx Success // 2xx Success
OK = 200, OK = 200,
CREATED = 201, CREATED = 201,
ACCEPTED = 202, ACCEPTED = 202,
NON_AUTHORITATIVE_INFORMATION = 203, NON_AUTHORITATIVE_INFORMATION = 203,
NO_CONTENT = 204, NO_CONTENT = 204,
RESET_CONTENT = 205, RESET_CONTENT = 205,
PARTIAL_CONTENT = 206, PARTIAL_CONTENT = 206,
MULTI_STATUS = 207, MULTI_STATUS = 207,
ALREADY_REPORTED = 208, ALREADY_REPORTED = 208,
IM_USED = 226, IM_USED = 226,
// 3xx Redirection // 3xx Redirection
MULTIPLE_CHOICES = 300, MULTIPLE_CHOICES = 300,
MOVED_PERMANENTLY = 301, MOVED_PERMANENTLY = 301,
FOUND = 302, FOUND = 302,
SEE_OTHER = 303, SEE_OTHER = 303,
NOT_MODIFIED = 304, NOT_MODIFIED = 304,
USE_PROXY = 305, USE_PROXY = 305,
TEMPORARY_REDIRECT = 307, TEMPORARY_REDIRECT = 307,
PERMANENT_REDIRECT = 308, PERMANENT_REDIRECT = 308,
// 4xx Client Error // 4xx Client Error
BAD_REQUEST = 400, BAD_REQUEST = 400,
UNAUTHORIZED = 401, UNAUTHORIZED = 401,
PAYMENT_REQUIRED = 402, PAYMENT_REQUIRED = 402,
FORBIDDEN = 403, FORBIDDEN = 403,
NOT_FOUND = 404, NOT_FOUND = 404,
METHOD_NOT_ALLOWED = 405, METHOD_NOT_ALLOWED = 405,
NOT_ACCEPTABLE = 406, NOT_ACCEPTABLE = 406,
PROXY_AUTHENTICATION_REQUIRED = 407, PROXY_AUTHENTICATION_REQUIRED = 407,
REQUEST_TIMEOUT = 408, REQUEST_TIMEOUT = 408,
CONFLICT = 409, CONFLICT = 409,
GONE = 410, GONE = 410,
LENGTH_REQUIRED = 411, LENGTH_REQUIRED = 411,
PRECONDITION_FAILED = 412, PRECONDITION_FAILED = 412,
PAYLOAD_TOO_LARGE = 413, PAYLOAD_TOO_LARGE = 413,
URI_TOO_LONG = 414, URI_TOO_LONG = 414,
UNSUPPORTED_MEDIA_TYPE = 415, UNSUPPORTED_MEDIA_TYPE = 415,
RANGE_NOT_SATISFIABLE = 416, RANGE_NOT_SATISFIABLE = 416,
EXPECTATION_FAILED = 417, EXPECTATION_FAILED = 417,
IM_A_TEAPOT = 418, IM_A_TEAPOT = 418,
MISDIRECTED_REQUEST = 421, MISDIRECTED_REQUEST = 421,
UNPROCESSABLE_ENTITY = 422, UNPROCESSABLE_ENTITY = 422,
LOCKED = 423, LOCKED = 423,
FAILED_DEPENDENCY = 424, FAILED_DEPENDENCY = 424,
TOO_EARLY = 425, TOO_EARLY = 425,
UPGRADE_REQUIRED = 426, UPGRADE_REQUIRED = 426,
PRECONDITION_REQUIRED = 428, PRECONDITION_REQUIRED = 428,
TOO_MANY_REQUESTS = 429, TOO_MANY_REQUESTS = 429,
REQUEST_HEADER_FIELDS_TOO_LARGE = 431, REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
UNAVAILABLE_FOR_LEGAL_REASONS = 451, UNAVAILABLE_FOR_LEGAL_REASONS = 451,
// 5xx Server Error // 5xx Server Error
INTERNAL_SERVER_ERROR = 500, INTERNAL_SERVER_ERROR = 500,
NOT_IMPLEMENTED = 501, NOT_IMPLEMENTED = 501,
BAD_GATEWAY = 502, BAD_GATEWAY = 502,
SERVICE_UNAVAILABLE = 503, SERVICE_UNAVAILABLE = 503,
GATEWAY_TIMEOUT = 504, GATEWAY_TIMEOUT = 504,
HTTP_VERSION_NOT_SUPPORTED = 505, HTTP_VERSION_NOT_SUPPORTED = 505,
VARIANT_ALSO_NEGOTIATES = 506, VARIANT_ALSO_NEGOTIATES = 506,
INSUFFICIENT_STORAGE = 507, INSUFFICIENT_STORAGE = 507,
LOOP_DETECTED = 508, LOOP_DETECTED = 508,
NOT_EXTENDED = 510, NOT_EXTENDED = 510,
NETWORK_AUTHENTICATION_REQUIRED = 511 NETWORK_AUTHENTICATION_REQUIRED = 511
}; };
using Header = KeyValuePair<String, String>; using Header = KeyValuePair<String, String>;
STATIC String UrlEncode(IN CONST String &value); static String UrlEncode(const String &value);
STATIC String UrlDecode(IN 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(EHeaderType key, const String &value);
STATIC INLINE Header CreateHeader(IN CONST String &key, IN 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: protected:
HttpCommon() = default; HttpCommon() = default;
}; };
HttpCommon::Header HttpCommon::CreateHeader(IN EHeaderType key, IN CONST String &value) HttpCommon::Header HttpCommon::CreateHeader(EHeaderType key,
{ const String &value) {
return std::make_pair(HeaderTypeToString(key), value); return std::make_pair(HeaderTypeToString(key), value);
} }
HttpCommon::Header HttpCommon::CreateHeader(IN CONST String &key, IN CONST String &value) HttpCommon::Header HttpCommon::CreateHeader(const String &key,
{ const String &value) {
return std::make_pair(key, value); return std::make_pair(key, value);
} }
} // namespace IACore } // namespace IACore

View File

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

View File

@ -16,58 +16,54 @@
#pragma once #pragma once
#include <IACore/PCH.hpp> #include <IACore/PCH.hpp>
#include <IACore/Logger.hpp>
#ifdef __cplusplus #define IACORE_MAIN() \
auto _app_entry(const IACore::Vec<IACore::String> &args) -> IACore::Result<IACore::i32>; \
# include <IACore/Logger.hpp> auto main(int argc, char *argv[]) -> int \
{ \
# define IACORE_MAIN() \ IACore::i32 exit_code = 0; \
EXPECT(INT32) _app_entry(IN CONST Vector<String> &args); \ IACore::initialize(); \
int main(int argc, char *argv[]) \ IACore::Vec<IACore::String> args; \
args.reserve(static_cast<IACore::usize>(argc)); \
for (int i = 0; i < argc; ++i) \
{ \ { \
int exitCode = 0; \ args.push_back(argv[i]); \
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; \
} \ } \
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 namespace IACore
{ {
// Must be called from main thread // Must be called from main thread
// Safe to call multiple times but, every Initialize call is paired with a corresponding Terminate call // Safe to call multiple times but, every initialize call is paired with a corresponding terminate call
VOID Initialize(); auto initialize() -> void;
// Must be called from same thread as Initialize // Must be called from same thread as initialize
// Safe to call multiple times but, every Initialize call is paired with a corresponding Terminate call // Safe to call multiple times but, every initialize call is paired with a corresponding terminate call
VOID Terminate(); auto terminate() -> void;
BOOL IsInitialized(); auto is_initialized() -> bool;
UINT64 GetUnixTime(); auto is_main_thread() -> bool;
UINT64 GetTicksCount(); } // namespace IACore
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

View File

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

View File

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

View File

@ -17,42 +17,126 @@
#include <IACore/PCH.hpp> #include <IACore/PCH.hpp>
#include <simdjson.h>
#include <glaze/glaze.hpp> #include <glaze/glaze.hpp>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <simdjson.h>
namespace IACore namespace IACore {
{ class JsonDocument {
class JSON public:
{ // Move-only (Safety: Cannot copy the parser state cheaply)
private: JsonDocument(JsonDocument &&) noexcept = default;
STATIC CONSTEXPR AUTO GLAZE_JSON_OPTS = glz::opts{.error_on_unknown_keys = false}; JsonDocument &operator=(JsonDocument &&) noexcept = default;
JsonDocument(const JsonDocument &) = delete;
JsonDocument &operator=(const JsonDocument &) = delete;
public: // Accessor: Get the root element (Object, Array, etc.)
STATIC EXPECT(nlohmann::json) Parse(IN CONST String &json); // The returned 'element' is valid only as long as this JsonDocument is alive.
STATIC EXPECT(Pair<SharedPtr<simdjson::dom::parser>, simdjson::dom::object>) [[nodiscard]]
ParseReadOnly(IN CONST String &json); auto root() const noexcept -> simdjson::dom::element {
STATIC String Encode(IN nlohmann::json data); return m_root;
}
template<typename _object_type> STATIC EXPECT(_object_type) ParseToStruct(IN CONST String &json); private:
template<typename _object_type> STATIC EXPECT(String) EncodeStruct(IN CONST _object_type &data); // Only created via JSON::parse_read_only factory
}; friend class Json;
template<typename _object_type> EXPECT(_object_type) JSON::ParseToStruct(IN CONST String &json) JsonDocument(Box<simdjson::dom::parser> p, simdjson::dom::element r)
{ : m_parser(std::move(p)), m_root(r) {}
_object_type result{};
const auto parseError = glz::read_json<GLAZE_JSON_OPTS>(result, json);
if (parseError)
return MakeUnexpected(std::format("JSON Error: {}", glz::format_error(parseError, json)));
return result;
}
template<typename _object_type> EXPECT(String) JSON::EncodeStruct(IN CONST _object_type &data) // ORDER MATTERS: Parser (Owner) must be destroyed AFTER the Root (View).
{ // In C++, members are destroyed in reverse declaration order.
String result; Box<simdjson::dom::parser> m_parser;
const auto encodeError = glz::write_json(data, result); simdjson::dom::element m_root;
if (encodeError) };
return MakeUnexpected(std::format("JSON Error: {}", glz::format_error(encodeError)));
return result; 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 } // 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 #pragma once
#include <IACore/PCH.hpp> #include <IACore/PCH.hpp>
#define IA_LOG_SET_FILE(path) IACore::Logger::EnableLoggingToDisk(path) #define IA_LOG_SET_FILE(path) IACore::Logger::enable_logging_to_disk(path)
#define IA_LOG_SET_LEVEL(level) IACore::Logger::SetLogLevel(IACore::Logger::ELogLevel::level) #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_TRACE(...) IACore::Logger::trace(__VA_ARGS__)
#define IA_LOG_DEBUG(...) IACore::Logger::Debug(__VA_ARGS__) #define IA_LOG_DEBUG(...) IACore::Logger::debug(__VA_ARGS__)
#define IA_LOG_INFO(...) IACore::Logger::Info(__VA_ARGS__) #define IA_LOG_INFO(...) IACore::Logger::info(__VA_ARGS__)
#define IA_LOG_WARN(...) IACore::Logger::Warn(__VA_ARGS__) #define IA_LOG_WARN(...) IACore::Logger::warn(__VA_ARGS__)
#define IA_LOG_ERROR(...) IACore::Logger::Error(__VA_ARGS__) #define IA_LOG_ERROR(...) IACore::Logger::error(__VA_ARGS__)
namespace IACore namespace IACore {
{ class Logger {
class Logger public:
{ enum class LogLevel { Trace, Debug, Info, Warn, Error };
public:
enum class ELogLevel
{
TRACE,
DEBUG,
INFO,
WARN,
ERROR
};
public: public:
STATIC BOOL EnableLoggingToDisk(IN PCCHAR filePath); static auto enable_logging_to_disk(const char *file_path) -> Result<void>;
STATIC VOID SetLogLevel(IN ELogLevel logLevel); static auto set_log_level(LogLevel log_level) -> void;
template<typename... Args> STATIC VOID Trace(FormatterString<Args...> fmt, Args &&...args) template <typename... Args>
{ static auto trace(std::format_string<Args...> fmt, Args &&...args) -> void {
LogTrace(std::vformat(fmt.get(), std::make_format_args(args...))); log_trace(std::vformat(fmt.get(), std::make_format_args(args...)));
} }
template<typename... Args> STATIC VOID Debug(FormatterString<Args...> fmt, Args &&...args) template <typename... Args>
{ static auto debug(std::format_string<Args...> fmt, Args &&...args) -> void {
LogDebug(std::vformat(fmt.get(), std::make_format_args(args...))); log_debug(std::vformat(fmt.get(), std::make_format_args(args...)));
} }
template<typename... Args> STATIC VOID Info(FormatterString<Args...> fmt, Args &&...args) template <typename... Args>
{ static auto info(std::format_string<Args...> fmt, Args &&...args) -> void {
LogInfo(std::vformat(fmt.get(), std::make_format_args(args...))); log_info(std::vformat(fmt.get(), std::make_format_args(args...)));
} }
template<typename... Args> STATIC VOID Warn(FormatterString<Args...> fmt, Args &&...args) template <typename... Args>
{ static auto warn(std::format_string<Args...> fmt, Args &&...args) -> void {
LogWarn(std::vformat(fmt.get(), std::make_format_args(args...))); log_warn(std::vformat(fmt.get(), std::make_format_args(args...)));
} }
template<typename... Args> STATIC VOID Error(FormatterString<Args...> fmt, Args &&...args) template <typename... Args>
{ static auto error(std::format_string<Args...> fmt, Args &&...args) -> void {
LogError(std::vformat(fmt.get(), std::make_format_args(args...))); 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 #if IA_DISABLE_LOGGING > 0
STATIC VOID LogTrace(IN String &&msg) static auto log_trace(String &&msg) -> void { UNUSED(msg); }
{
UNUSED(msg);
}
STATIC VOID LogDebug(IN String &&msg) static auto log_debug(String &&msg) -> void { UNUSED(msg); }
{
UNUSED(msg);
}
STATIC VOID LogInfo(IN String &&msg) static auto log_info(String &&msg) -> void { UNUSED(msg); }
{
UNUSED(msg);
}
STATIC VOID LogWarn(IN String &&msg) static auto log_warn(String &&msg) -> void { UNUSED(msg); }
{
UNUSED(msg);
}
STATIC VOID LogError(IN String &&msg) static auto log_error(String &&msg) -> void { UNUSED(msg); }
{
UNUSED(msg);
}
#else #else
STATIC VOID LogTrace(IN String &&msg) static auto log_trace(String &&msg) -> void {
{ if (m_log_level <= LogLevel::Trace)
if (s_logLevel <= ELogLevel::TRACE) log_internal(console::RESET, "TRACE", std::move(msg));
LogInternal(__CC_WHITE, "TRACE", IA_MOVE(msg)); }
}
STATIC VOID LogDebug(IN String &&msg) static auto log_debug(String &&msg) -> void {
{ if (m_log_level <= LogLevel::Debug)
if (s_logLevel <= ELogLevel::DEBUG) log_internal(console::CYAN, "DEBUG", std::move(msg));
LogInternal(__CC_CYAN, "DEBUG", IA_MOVE(msg)); }
}
STATIC VOID LogInfo(IN String &&msg) static auto log_info(String &&msg) -> void {
{ if (m_log_level <= LogLevel::Info)
if (s_logLevel <= ELogLevel::INFO) log_internal(console::GREEN, "INFO", std::move(msg));
LogInternal(__CC_GREEN, "INFO", IA_MOVE(msg)); }
}
STATIC VOID LogWarn(IN String &&msg) static auto log_warn(String &&msg) -> void {
{ if (m_log_level <= LogLevel::Warn)
if (s_logLevel <= ELogLevel::WARN) log_internal(console::YELLOW, "WARN", std::move(msg));
LogInternal(__CC_YELLOW, "WARN", IA_MOVE(msg)); }
}
STATIC VOID LogError(IN String &&msg) static auto log_error(String &&msg) -> void {
{ if (m_log_level <= LogLevel::Error)
if (s_logLevel <= ELogLevel::ERROR) log_internal(console::RED, "ERROR", std::move(msg));
LogInternal(__CC_RED, "ERROR", IA_MOVE(msg)); }
}
#endif #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: private:
STATIC ELogLevel s_logLevel; static LogLevel m_log_level;
STATIC std::ofstream s_logFile; static std::ofstream m_log_file;
STATIC VOID Initialize(); static auto initialize() -> void;
STATIC VOID Terminate(); static auto terminate() -> void;
friend VOID Initialize(); friend void initialize();
friend VOID Terminate(); friend void terminate();
}; };
} // namespace IACore } // namespace IACore

View File

@ -15,624 +15,233 @@
#pragma once #pragma once
// -------------------------------------------------------------------------
// Platform Detection
// -------------------------------------------------------------------------
#if defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64) #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) #elif defined(__aarch64__) || defined(_M_ARM64)
# define IA_ARCH_ARM64 1 #define IA_ARCH_ARM64 1
#elif defined(__wasm__) || defined(__wasm32__) || defined(__wasm64__) #elif defined(__wasm__) || defined(__wasm32__) || defined(__wasm64__)
# define IA_ARCH_WASM 1 #define IA_ARCH_WASM 1
#else #else
# error "IACore: Unsupported Architecture. Only x64, ARM64, and WASM are supported." #error "IACore: Unsupported Architecture."
#endif #endif
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
# ifdef _WIN64 #define IA_PLATFORM_WINDOWS 1
# define IA_PLATFORM_WIN64 1
# define IA_PLATFORM_WINDOWS 1
# else
# error "IACore: 32-bit Windows is not supported"
# endif
#elif __APPLE__ #elif __APPLE__
# include <TargetConditionals.h> #include <TargetConditionals.h>
# if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR || TARGET_OS_MACCATALYST #define IA_PLATFORM_APPLE 1
# define IA_PLATFORM_IOS 1 #define IA_PLATFORM_UNIX 1
# elif TARGET_OS_MAC
# define IA_PLATFORM_MAC 1
# endif
# define IA_PLATFORM_UNIX 1
#elif __ANDROID__
# define IA_PLATFORM_ANDROID 1
# define IA_PLATFORM_LINUX 1
# define IA_PLATFORM_UNIX 1
#elif __linux__ #elif __linux__
# define IA_PLATFORM_LINUX 1 #define IA_PLATFORM_LINUX 1
# define IA_PLATFORM_UNIX 1 #define IA_PLATFORM_UNIX 1
#elif __wasm__ #elif __wasm__
# define IA_PLATFORM_WASM 1 #define IA_PLATFORM_WASM 1
#else #else
# error "IACore: Unsupported Platform. Only Windows, Linux, MacOS, Android and iOS are supported." #error "IACore: Unsupported Platform."
#endif #endif
#if IA_PLATFORM_WIN64 #if IA_PLATFORM_WINDOWS
# ifndef WIN32_LEAN_AND_MEAN #ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
# endif #endif
# ifndef NOMINMAX #ifndef NOMINMAX
# define NOMINMAX #define NOMINMAX
# endif #endif
# include <windows.h> #include <windows.h>
# undef VOID
# undef ERROR
#elif IA_PLATFORM_UNIX #elif IA_PLATFORM_UNIX
# include <unistd.h> #include <signal.h>
# include <sys/wait.h> #include <sys/wait.h>
# include <sys/mman.h> #include <unistd.h>
# include <sys/stat.h>
# include <fcntl.h>
# include <spawn.h>
# include <signal.h>
#endif #endif
// ----------------------------------------------------------------------------- #include <array>
// Configuration Macros #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 namespace IACore {
# define __DEBUG_MODE__
# define __BUILD_MODE_NAME "debug" // =============================================================================
# define DEBUG_ONLY(v) v // Primitive Types (Rust Style)
# ifndef _DEBUG // =============================================================================
# define _DEBUG using u8 = std::uint8_t;
# endif 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 #else
# define __RELEASE_MODE__ constexpr bool is_debug = true;
# define __BUILD_MODE_NAME "release" constexpr bool is_release = false;
# ifndef NDEBUG
# define NDEBUG
# endif
# ifndef __OPTIMIZE__
# define __OPTIMIZE__
# endif
# define DEBUG_ONLY(f)
#endif #endif
#include <assert.h> #if IA_PLATFORM_WINDOWS
#include <stdint.h> constexpr bool is_windows = true;
#include <stdio.h> constexpr bool is_unix = false;
#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>
#else #else
# include <float.h> constexpr bool is_windows = false;
# include <stdbool.h> constexpr bool is_unix = true;
# include <stddef.h>
# include <string.h>
#endif #endif
// ----------------------------------------------------------------------------- constexpr usize max_path_len = 4096;
// Security Macros } // namespace env
// -----------------------------------------------------------------------------
#define IA_PANIC(msg) \ // =============================================================================
{ \ // Memory & Ownership (Rust Semantics)
fprintf(stderr, "PANIC: %s\n", msg); \ // =============================================================================
__builtin_trap(); \ template <typename T> using Box = std::unique_ptr<T>;
}
// Advanced Security features are not included in OSS builds template <typename T> using Arc = std::shared_ptr<T>;
// (OSS version does not implement 'IAC_CHECK_*'s)
#define IACORE_SECURITY_LEVEL 0
#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) template <typename T, typename... Args>
# define __IAC_OVERFLOW_CHECKS 1 [[nodiscard]] inline auto make_box(Args &&...args) -> Box<T> {
#else return std::make_unique<T>(std::forward<Args>(args)...);
# 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>
// Numeric Constants [[nodiscard]] inline auto make_arc(Args &&...args) -> Arc<T> {
// ------------------------------------------------------------------------- return std::make_shared<T>(std::forward<Args>(args)...);
#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> inline UniquePtr<T> MakeUniqueProtected(Args &&...args) // =============================================================================
{ // Error Handling (Result)
struct make_unique_enabler : public T // =============================================================================
{ template <typename T, typename E = std::string>
make_unique_enabler(Args &&...args) : T(std::forward<Args>(args)...) 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> template <typename... Args>
using Expected = tl::expected<_expected_type, _unexpected_type>; [[nodiscard]] inline auto fail(std::format_string<Args...> fmt,
ALIAS_FUNCTION(MakeUnexpected, tl::make_unexpected); 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 String = std::string;
using Path = std::filesystem::path;
using StringView = std::string_view; using StringView = std::string_view;
using StringStream = std::stringstream;
using SteadyClock = std::chrono::steady_clock; // =============================================================================
using SteadyTimePoint = std::chrono::time_point<SteadyClock>; // Utilities
using HighResClock = std::chrono::high_resolution_clock; // =============================================================================
using HighResTimePoint = std::chrono::time_point<HighResClock>;
using Mutex = std::mutex; [[noreturn]] inline void
using StopToken = std::stop_token; panic(const std::string &msg,
using ScopedLock = std::scoped_lock<Mutex>; const std::source_location loc = std::source_location::current()) {
using UniqueLock = std::unique_lock<Mutex>; std::cerr << "\n[IA_PANIC] " << msg << "\n At: " << loc.file_name()
using JoiningThread = std::jthread; << ":" << loc.line() << "\n";
using ConditionVariable = std::condition_variable; std::abort();
}
namespace FileSystem = std::filesystem; inline void
using FilePath = FileSystem::path; 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> #include <IACore/PCH.hpp>
#if IA_ARCH_X64 #if IA_ARCH_X64
# ifdef _MSC_VER # ifdef _MSC_VER
# include <intrin.h> # include <intrin.h>
# else # else
# include <immintrin.h> # include <immintrin.h>
# endif # endif
#elif IA_ARCH_ARM64 #elif IA_ARCH_ARM64
# include <arm_acle.h> # include <arm_acle.h>
#endif #endif
namespace IACore namespace IACore
@ -38,26 +34,24 @@ namespace IACore
public: public:
struct Capabilities struct Capabilities
{ {
BOOL HardwareCRC32{FALSE}; bool hardware_crc32 = false;
}; };
public: static auto check_cpu() -> bool;
STATIC BOOL CheckCPU();
#if IA_ARCH_X64 #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 #endif
STATIC PCCHAR GetArchitectureName(); static auto get_architecture_name() -> const char *;
STATIC PCCHAR GetOperatingSystemName(); static auto get_operating_system_name() -> const char *;
public: static auto get_capabilities() -> const Capabilities &
STATIC CONST Capabilities &GetCapabilities()
{ {
return s_capabilities; return s_capabilities;
} }
private: private:
STATIC Capabilities s_capabilities; static Capabilities s_capabilities;
}; };
} // namespace IACore } // namespace IACore

View File

@ -22,46 +22,48 @@ using NativeProcessID = DWORD;
#elif IA_PLATFORM_UNIX #elif IA_PLATFORM_UNIX
using NativeProcessID = pid_t; using NativeProcessID = pid_t;
#else #else
# error "This platform does not support IACore ProcessOps" #error "This platform does not support IACore ProcessOps"
#endif #endif
namespace IACore namespace IACore {
{ struct ProcessHandle {
struct ProcessHandle std::atomic<NativeProcessID> id{0};
{ std::atomic<bool> is_running{false};
Atomic<NativeProcessID> ID{0};
Atomic<BOOL> IsRunning{false};
BOOL IsActive() CONST [[nodiscard]] auto is_active() const -> bool { return is_running && id != 0; }
{
return IsRunning && ID != 0;
}
private: private:
JoiningThread ThreadHandle; std::jthread m_thread_handle;
friend class ProcessOps; friend class ProcessOps;
}; };
class ProcessOps class ProcessOps {
{ public:
public: static auto get_current_process_id() -> NativeProcessID;
STATIC NativeProcessID GetCurrentProcessID();
STATIC EXPECT(INT32) SpawnProcessSync(IN CONST String &command, IN CONST String &args, static auto spawn_process_sync(
IN Function<VOID(IN StringView line)> onOutputLineCallback); const String &command, const String &args,
STATIC SharedPtr<ProcessHandle> SpawnProcessAsync(IN CONST String &command, IN CONST String &args, std::function<void(StringView line)> on_output_line_callback)
IN Function<VOID(IN StringView line)> onOutputLineCallback, -> Result<i32>;
IN Function<VOID(EXPECT(INT32))> onFinishCallback);
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 auto terminate_process(const Box<ProcessHandle> &handle) -> void;
STATIC EXPECT(INT32)
SpawnProcessWindows(IN CONST String &command, IN CONST String &args, private:
IN Function<VOID(StringView)> onOutputLineCallback, OUT Atomic<NativeProcessID> &id); static auto
STATIC EXPECT(INT32) spawn_process_windows(const String &command, const String &args,
SpawnProcessPosix(IN CONST String &command, IN CONST String &args, std::function<void(StringView)> on_output_line_callback,
IN Function<VOID(StringView)> onOutputLineCallback, OUT Atomic<NativeProcessID> &id); 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 } // 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 #pragma once
#include <IACore/PCH.hpp> #include <IACore/PCH.hpp>
@ -37,81 +22,81 @@ namespace IACore
# pragma message("Warning: Configuration mismatch. IACore is being compiled for SCALAR SIMD (Slow)") # pragma message("Warning: Configuration mismatch. IACore is being compiled for SCALAR SIMD (Slow)")
#endif #endif
class ALIGN(16) IntVec4 class alignas(16) IntVec4
{ {
public: public:
IntVec4() = default; IntVec4() = default;
INLINE EXPLICIT IntVec4(IN UINT32 s); inline explicit IntVec4(u32 s);
INLINE EXPLICIT IntVec4(IN PCUINT32 values); inline explicit IntVec4(const u32 *values);
INLINE EXPLICIT IntVec4(IN UINT32 a, IN UINT32 b, IN UINT32 c, IN UINT32 d); inline explicit IntVec4(u32 a, u32 b, u32 c, u32 d);
INLINE IntVec4 operator+(IN CONST IntVec4 &other) CONST; inline auto operator+(const IntVec4 &other) const -> IntVec4;
INLINE IntVec4 operator-(IN CONST IntVec4 &other) CONST; inline auto operator-(const IntVec4 &other) const -> IntVec4;
INLINE IntVec4 operator*(IN CONST IntVec4 &other) CONST; inline auto operator*(const IntVec4 &other) const -> IntVec4;
INLINE IntVec4 operator&(IN CONST IntVec4 &other) CONST; inline auto operator&(const IntVec4 &other) const -> IntVec4;
INLINE IntVec4 operator|(IN CONST IntVec4 &other) CONST; inline auto operator|(const IntVec4 &other) const -> IntVec4;
INLINE IntVec4 operator^(IN CONST IntVec4 &other) CONST; inline auto operator^(const IntVec4 &other) const -> IntVec4;
INLINE IntVec4 operator~() CONST; inline auto operator~() const -> IntVec4;
INLINE IntVec4 operator<<(IN UINT32 amount) CONST; inline auto operator<<(u32 amount) const -> IntVec4;
INLINE IntVec4 operator>>(IN UINT32 amount) CONST; inline auto operator>>(u32 amount) const -> IntVec4;
INLINE IntVec4 SatAdd(IN CONST IntVec4 &other) CONST; inline auto sat_add(const IntVec4 &other) const -> IntVec4;
INLINE IntVec4 SatSub(IN CONST IntVec4 &other) CONST; 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); inline auto store(u32 *values) -> void;
STATIC INLINE IntVec4 Load(IN PCUINT32 values); static inline auto load(const u32 *values) -> IntVec4;
private: private:
using Tag = hn::FixedTag<UINT32, 4>; using Tag = hn::FixedTag<u32, 4>;
hn::Vec<Tag> m_data; 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: public:
FloatVec4() = default; FloatVec4() = default;
INLINE EXPLICIT FloatVec4(IN FLOAT32 s); inline explicit FloatVec4(f32 s);
INLINE EXPLICIT FloatVec4(IN PCFLOAT32 values); inline explicit FloatVec4(const f32 *values);
INLINE EXPLICIT FloatVec4(IN FLOAT32 a, IN FLOAT32 b, IN FLOAT32 c, IN FLOAT32 d); inline explicit FloatVec4(f32 a, f32 b, f32 c, f32 d);
INLINE FloatVec4 operator+(IN CONST FloatVec4 &other) CONST; inline auto operator+(const FloatVec4 &other) const -> FloatVec4;
INLINE FloatVec4 operator-(IN CONST FloatVec4 &other) CONST; inline auto operator-(const FloatVec4 &other) const -> FloatVec4;
INLINE FloatVec4 operator*(IN CONST FloatVec4 &other) CONST; inline auto operator*(const FloatVec4 &other) const -> FloatVec4;
INLINE FloatVec4 operator/(IN CONST FloatVec4 &other) CONST; 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 auto abs() const -> FloatVec4;
INLINE FloatVec4 Sqrt() CONST; inline auto sqrt() const -> FloatVec4;
INLINE FloatVec4 Rsqrt() CONST; inline auto rsqrt() const -> FloatVec4;
INLINE FloatVec4 Normalize() CONST; 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); inline auto store(f32 *values) -> void;
STATIC INLINE FloatVec4 Load(IN PCFLOAT32 values); static inline auto load(const f32 *values) -> FloatVec4;
private: private:
using Tag = hn::FixedTag<FLOAT32, 4>; using Tag = hn::FixedTag<f32, 4>;
hn::Vec<Tag> m_data; 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 namespace IACore
{ {
IntVec4::IntVec4(IN UINT32 s) IntVec4::IntVec4(u32 s)
{ {
CONST Tag d; const Tag d;
m_data = hn::Set(d, s); 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); 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; const Tag data;
ALIGN(16) UINT32 values[4] = {a, b, c, d}; alignas(16) u32 values[4] = {a, b, c, d};
m_data = hn::Load(data, values); 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)); 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)); 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)); 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)); 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)); 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)); return IntVec4(hn::Xor(m_data, other.m_data));
} }
IntVec4 IntVec4::operator~() CONST auto IntVec4::operator~() const -> IntVec4
{ {
return IntVec4(hn::Not(m_data)); 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)); 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)); 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)); 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)); 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)); 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 vMin = hn::Set(d, min);
auto vMax = hn::Set(d, max); auto vMax = hn::Set(d, max);
return IntVec4(hn::Min(hn::Max(m_data, vMin), vMax)); 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); 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)); return IntVec4(hn::Load(d, values));
} }
} // namespace IACore } // namespace IACore
namespace IACore namespace IACore
{ {
FloatVec4::FloatVec4(IN FLOAT32 s) FloatVec4::FloatVec4(f32 s)
{ {
const Tag d; const Tag d;
m_data = hn::Set(d, s); m_data = hn::Set(d, s);
} }
FloatVec4::FloatVec4(IN PCFLOAT32 values) FloatVec4::FloatVec4(const f32 *values)
{ {
const Tag d; const Tag d;
m_data = hn::Load(d, values); 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; 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); 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)); 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)); 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)); 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)); 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)); 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; const Tag d;
auto vMin = hn::Set(d, min); auto vMin = hn::Set(d, min);
@ -273,29 +258,29 @@ namespace IACore
return FloatVec4(hn::Min(hn::Max(m_data, vMin), vMax)); 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)); return FloatVec4(hn::Sqrt(m_data));
} }
FloatVec4 FloatVec4::Rsqrt() CONST auto FloatVec4::rsqrt() const -> FloatVec4
{ {
return FloatVec4(hn::ApproximateReciprocalSqrt(m_data)); return FloatVec4(hn::ApproximateReciprocalSqrt(m_data));
} }
FloatVec4 FloatVec4::Abs() CONST auto FloatVec4::abs() const -> FloatVec4
{ {
return FloatVec4(hn::Abs(m_data)); 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; const Tag d;
auto vMul = hn::Mul(m_data, other.m_data); auto vMul = hn::Mul(m_data, other.m_data);
return hn::ReduceSum(d, vMul); return hn::ReduceSum(d, vMul);
} }
FloatVec4 FloatVec4::Normalize() CONST auto FloatVec4::normalize() const -> FloatVec4
{ {
const Tag d; const Tag d;
auto vMul = hn::Mul(m_data, m_data); auto vMul = hn::Mul(m_data, m_data);
@ -304,13 +289,13 @@ namespace IACore
return FloatVec4(hn::Mul(m_data, vInvLen)); return FloatVec4(hn::Mul(m_data, vInvLen));
} }
VOID FloatVec4::Store(OUT PFLOAT32 values) auto FloatVec4::store(f32 *values) -> void
{ {
const Tag d; const Tag d;
hn::Store(m_data, d, values); hn::Store(m_data, d, values);
} }
FloatVec4 FloatVec4::Load(IN PCFLOAT32 values) auto FloatVec4::load(const f32 *values) -> FloatVec4
{ {
const Tag d; const Tag d;
return FloatVec4(hn::Load(d, values)); return FloatVec4(hn::Load(d, values));

View File

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

View File

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

View File

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

View File

@ -17,12 +17,10 @@
#include <IACore/PCH.hpp> #include <IACore/PCH.hpp>
namespace IACore namespace IACore {
{ class StringOps {
class StringOps public:
{ static auto encode_base64(Span<const u8> data) -> String;
public: static auto decode_base64(const String &data) -> Vec<u8>;
STATIC String EncodeBase64(IN Span<CONST UINT8> data); };
STATIC Vector<UINT8> DecodeBase64(IN CONST String &data);
};
} // namespace IACore } // namespace IACore

View File

@ -21,86 +21,48 @@
namespace IACore namespace IACore
{ {
class Utils class Utils
{ {
public: public:
INLINE STATIC String BinaryToHexString(std::span<const UINT8> data) static auto get_unix_time() -> u64;
{
STATIC CONSTEXPR char LUT[] = "0123456789ABCDEF";
String res;
res.reserve(data.size() * 2);
for (UINT8 b : data) static auto get_ticks_count() -> u64;
{
res.push_back(LUT[(b >> 4) & 0x0F]);
res.push_back(LUT[b & 0x0F]);
}
return res;
}
INLINE STATIC EXPECT(Vector<UINT8>) HexStringToBinary(CONST StringView &hex) static auto get_seconds_count() -> f64;
{
if (hex.size() % 2 != 0)
{
return MakeUnexpected(String("Hex string must have even length"));
}
Vector<UINT8> out; static auto get_random() -> f32;
out.reserve(hex.size() / 2); 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) static auto sleep(u64 milliseconds) -> void;
{
char high = hex[i];
char low = hex[i + 1];
auto fromHexChar = [](char c) -> int { static auto binary_to_hex_string(Span<const u8> data) -> String;
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
return -1;
};
int h = fromHexChar(high); static auto hex_string_to_binary(StringView hex) -> Result<Vec<u8>>;
int l = fromHexChar(low);
if (h == -1 || l == -1) template<typename Range> inline static void sort(Range &&range)
{
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)
{ {
std::ranges::sort(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); 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); 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); StringView sv(v);
auto hasher = ankerl::unordered_dense::hash<std::string_view>(); auto hasher = ankerl::unordered_dense::hash<StringView>();
h = hasher(sv); h = hasher(sv);
} }
else else
@ -112,18 +74,18 @@ namespace IACore
seed ^= h + 0x9e3779b97f4a7c15 + (seed << 6) + (seed >> 2); 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; u64 seed = 0;
(HashCombine(seed, args), ...); (hash_combine(seed, args), ...);
return seed; return seed;
} }
template<typename T, typename... MemberPtrs> 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; u64 seed = 0;
(HashCombine(seed, obj.*members), ...); (hash_combine(seed, obj.*members), ...);
return seed; return seed;
} }
}; };
@ -142,10 +104,10 @@ namespace IACore
template<> struct ankerl::unordered_dense::hash<Type> \ template<> struct ankerl::unordered_dense::hash<Type> \
{ \ { \
using is_avalanching = void; \ using is_avalanching = void; \
NO_DISCARD("Hash value should be used") \ IA_NODISCARD \
UINT64 operator()(CONST Type &v) const NOEXCEPT \ auto operator()(const Type &v) const noexcept -> IACore::u64 \
{ \ { \
/* Pass the object and the list of member pointers */ \ /* 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; using Document = pugi::xml_document;
public: public:
STATIC EXPECT(Document) ParseFromString(IN CONST String &data); static auto parse_from_string(const String &data) -> Result<Document>;
STATIC EXPECT(Document) ParseFromFile(IN CONST FilePath &path); static auto parse_from_file(const Path &path) -> Result<Document>;
STATIC String SerializeToString(IN CONST Node &node, IN BOOL escape = false); static auto serialize_to_string(const Node &node, bool escape = false) -> String;
STATIC String SerializeToString(IN CONST Document &doc, IN BOOL escape = false); 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 } // namespace IACore

View File

@ -1,4 +1,4 @@
add_subdirectory(Subjects/) #add_subdirectory(Subjects/)
add_subdirectory(Unit/) #add_subdirectory(Unit/)
add_subdirectory(Regression/) #add_subdirectory(Regression/)

View File

@ -25,78 +25,78 @@ using namespace IACore;
IAT_BEGIN_BLOCK(Core, DataOps) IAT_BEGIN_BLOCK(Core, DataOps)
BOOL TestCRC32() bool TestCRC32() {
{ {
{ String s = "123456789";
String s = "123456789"; Span<const u8> span(reinterpret_cast<const u8 *>(s.data()), s.size());
Span<CONST UINT8> span(reinterpret_cast<PCUINT8>(s.data()), s.size()); u32 result = DataOps::CRC32(span);
UINT32 result = DataOps::CRC32(span);
IAT_CHECK_EQ(result, 0xE3069283); IAT_CHECK_EQ(result, 0xE3069283);
} }
{ {
UINT32 result = DataOps::CRC32({}); u32 result = DataOps::CRC32({});
IAT_CHECK_EQ(result, 0U); IAT_CHECK_EQ(result, 0U);
} }
{ {
std::vector<UINT8> buffer(33); std::vector<u8> buffer(33);
for (size_t i = 1; i < 33; ++i) for (size_t i = 1; i < 33; ++i)
buffer[i] = (UINT8) i; buffer[i] = (u8)i;
std::vector<UINT8> refData(32); std::vector<u8> refData(32);
for (size_t i = 0; i < 32; ++i) for (size_t i = 0; i < 32; ++i)
refData[i] = (UINT8) (i + 1); refData[i] = (u8)(i + 1);
UINT32 hashRef = DataOps::CRC32(Span<CONST UINT8>(refData.data(), refData.size())); u32 hashRef =
DataOps::CRC32(Span<const u8>(refData.data(), refData.size()));
UINT32 hashUnaligned = DataOps::CRC32(Span<CONST UINT8>(buffer.data() + 1, 32)); u32 hashUnaligned = DataOps::CRC32(Span<const u8>(buffer.data() + 1, 32));
IAT_CHECK_EQ(hashRef, hashUnaligned); IAT_CHECK_EQ(hashRef, hashUnaligned);
} }
return TRUE; return TRUE;
} }
BOOL TestHash_xxHash() bool TestHash_xxHash() {
{ {
{ String s = "123456789";
String s = "123456789"; u32 result = DataOps::Hash_xxHash(s);
UINT32 result = DataOps::Hash_xxHash(s); IAT_CHECK_EQ(result, 0x937bad67);
IAT_CHECK_EQ(result, 0x937bad67); }
}
{ {
String s = "The quick brown fox jumps over the lazy dog"; String s = "The quick brown fox jumps over the lazy dog";
UINT32 result = DataOps::Hash_xxHash(s); u32 result = DataOps::Hash_xxHash(s);
IAT_CHECK_EQ(result, 0xE85EA4DE); IAT_CHECK_EQ(result, 0xE85EA4DE);
} }
{ {
String s = "Test"; String s = "Test";
UINT32 r1 = DataOps::Hash_xxHash(s); u32 r1 = DataOps::Hash_xxHash(s);
UINT32 r2 = DataOps::Hash_xxHash(Span<CONST UINT8>((PCUINT8) s.data(), s.size())); u32 r2 =
IAT_CHECK_EQ(r1, r2); DataOps::Hash_xxHash(Span<const u8>((const u8 *)s.data(), s.size()));
} IAT_CHECK_EQ(r1, r2);
}
return TRUE; return TRUE;
} }
BOOL TestHash_FNV1A() bool TestHash_FNV1A() {
{ {
{ String s = "123456789";
String s = "123456789"; u32 result =
UINT32 result = DataOps::Hash_FNV1A(Span<CONST UINT8>((PCUINT8) s.data(), s.size())); DataOps::Hash_FNV1A(Span<const u8>((const u8 *)s.data(), s.size()));
IAT_CHECK_EQ(result, 0xbb86b11c); IAT_CHECK_EQ(result, 0xbb86b11c);
} }
{ {
UINT32 result = DataOps::Hash_FNV1A(Span<CONST UINT8>{}); u32 result = DataOps::Hash_FNV1A(Span<const u8>{});
IAT_CHECK_EQ(result, 0x811C9DC5); IAT_CHECK_EQ(result, 0x811C9DC5);
} }
return TRUE; return TRUE;
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------

View File

@ -34,14 +34,14 @@ IAT_BEGIN_BLOCK(Core, Environment)
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 1. Basic Set and Get (The Happy Path) // 1. Basic Set and Get (The Happy Path)
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestBasicCycle() bool TestBasicCycle()
{ {
// 1. Ensure clean slate // 1. Ensure clean slate
Environment::Unset(TEST_KEY); Environment::Unset(TEST_KEY);
IAT_CHECK_NOT(Environment::Exists(TEST_KEY)); IAT_CHECK_NOT(Environment::Exists(TEST_KEY));
// 2. Set // 2. Set
BOOL setRes = Environment::Set(TEST_KEY, TEST_VAL); bool setRes = Environment::Set(TEST_KEY, TEST_VAL);
IAT_CHECK(setRes); IAT_CHECK(setRes);
IAT_CHECK(Environment::Exists(TEST_KEY)); IAT_CHECK(Environment::Exists(TEST_KEY));
@ -62,7 +62,7 @@ BOOL TestBasicCycle()
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 2. Overwriting Values // 2. Overwriting Values
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestOverwrite() bool TestOverwrite()
{ {
Environment::Set(TEST_KEY, "ValueA"); Environment::Set(TEST_KEY, "ValueA");
IAT_CHECK_EQ(Environment::Get(TEST_KEY), String("ValueA")); IAT_CHECK_EQ(Environment::Get(TEST_KEY), String("ValueA"));
@ -78,12 +78,12 @@ BOOL TestOverwrite()
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 3. Unset Logic // 3. Unset Logic
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestUnset() bool TestUnset()
{ {
Environment::Set(TEST_KEY, "To Be Deleted"); Environment::Set(TEST_KEY, "To Be Deleted");
IAT_CHECK(Environment::Exists(TEST_KEY)); IAT_CHECK(Environment::Exists(TEST_KEY));
BOOL unsetRes = Environment::Unset(TEST_KEY); bool unsetRes = Environment::Unset(TEST_KEY);
IAT_CHECK(unsetRes); IAT_CHECK(unsetRes);
// Verify it is actually gone // Verify it is actually gone
@ -99,7 +99,7 @@ BOOL TestUnset()
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 4. Default Value Fallbacks // 4. Default Value Fallbacks
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestDefaults() bool TestDefaults()
{ {
const char *ghostKey = "IA_THIS_KEY_DOES_NOT_EXIST"; const char *ghostKey = "IA_THIS_KEY_DOES_NOT_EXIST";
@ -122,7 +122,7 @@ BOOL TestDefaults()
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Does Set(Key, "") create an existing empty variable, or unset it? // Does Set(Key, "") create an existing empty variable, or unset it?
// Standard POSIX/Windows API behavior is that it EXISTS, but is empty. // Standard POSIX/Windows API behavior is that it EXISTS, but is empty.
BOOL TestEmptyValue() bool TestEmptyValue()
{ {
Environment::Set(TEST_KEY, ""); Environment::Set(TEST_KEY, "");
@ -149,14 +149,14 @@ BOOL TestEmptyValue()
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 6. Validation / Bad Input // 6. Validation / Bad Input
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestBadInput() bool TestBadInput()
{ {
// Setting an empty key should fail gracefully // Setting an empty key should fail gracefully
BOOL res = Environment::Set("", "Value"); bool res = Environment::Set("", "Value");
IAT_CHECK_NOT(res); IAT_CHECK_NOT(res);
// Unsetting an empty key should fail // Unsetting an empty key should fail
BOOL resUnset = Environment::Unset(""); bool resUnset = Environment::Unset("");
IAT_CHECK_NOT(resUnset); IAT_CHECK_NOT(resUnset);
return TRUE; return TRUE;

View File

@ -37,7 +37,7 @@ IAT_BEGIN_BLOCK(Core, ProcessOps)
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 1. Basic Execution (Exit Code 0) // 1. Basic Execution (Exit Code 0)
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestBasicRun() bool TestBasicRun()
{ {
// Simple "echo hello" // Simple "echo hello"
String captured; String captured;
@ -57,9 +57,9 @@ BOOL TestBasicRun()
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 2. Argument Parsing // 2. Argument Parsing
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestArguments() bool TestArguments()
{ {
Vector<String> lines; Vec<String> lines;
// Echo two distinct words. // Echo two distinct words.
// Windows: cmd.exe /c echo one two // Windows: cmd.exe /c echo one two
@ -83,7 +83,7 @@ BOOL TestArguments()
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 3. Error / Non-Zero Exit Codes // 3. Error / Non-Zero Exit Codes
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestExitCodes() bool TestExitCodes()
{ {
// We need a command that returns non-zero. // We need a command that returns non-zero.
// Windows: cmd /c exit 1 // Windows: cmd /c exit 1
@ -110,7 +110,7 @@ BOOL TestExitCodes()
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 4. Missing Executable Handling // 4. Missing Executable Handling
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestMissingExe() bool TestMissingExe()
{ {
// Try to run a random string // Try to run a random string
auto result = ProcessOps::SpawnProcessSync("sdflkjghsdflkjg", "", [](StringView) {}); auto result = ProcessOps::SpawnProcessSync("sdflkjghsdflkjg", "", [](StringView) {});
@ -132,7 +132,7 @@ BOOL TestMissingExe()
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 5. Line Buffer Logic (The 4096 split test) // 5. Line Buffer Logic (The 4096 split test)
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestLargeOutput() bool TestLargeOutput()
{ {
// Need to generate output larger than the internal 4096 buffer // Need to generate output larger than the internal 4096 buffer
// to ensure the "partial line" logic works when a line crosses a buffer boundary. // to ensure the "partial line" logic works when a line crosses a buffer boundary.
@ -169,7 +169,7 @@ BOOL TestLargeOutput()
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 6. Multi-Line Handling // 6. Multi-Line Handling
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestMultiLine() bool TestMultiLine()
{ {
// Windows: cmd /c "echo A && echo B" // Windows: cmd /c "echo A && echo B"
// Linux: /bin/sh -c "echo A; echo B" // Linux: /bin/sh -c "echo A; echo B"
@ -207,7 +207,7 @@ BOOL TestMultiLine()
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 6. Complex Command Line Arguments Handling // 6. Complex Command Line Arguments Handling
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestComplexArguments() bool TestComplexArguments()
{ {
// Should parse as 3 arguments: // Should parse as 3 arguments:
// 1. -DDEFINED_MSG="Hello World" // 1. -DDEFINED_MSG="Hello World"

View File

@ -23,78 +23,75 @@ IAT_BEGIN_BLOCK(Core, RingBuffer)
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 1. Basic Push Pop // 1. Basic Push Pop
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestPushPop() bool TestPushPop() {
{ // Allocate raw memory for the ring buffer
// Allocate raw memory for the ring buffer // ControlBlock (128 bytes) + Data
// ControlBlock (128 bytes) + Data std::vector<u8> memory(sizeof(RingBufferView::ControlBlock) + 1024);
std::vector<UINT8> memory(sizeof(RingBufferView::ControlBlock) + 1024);
// Initialize as OWNER (Producer) // Initialize as OWNER (Producer)
RingBufferView producer(std::span<UINT8>(memory), TRUE); RingBufferView producer(std::span<u8>(memory), TRUE);
// Initialize as CONSUMER (Pointer to same memory) // Initialize as CONSUMER (Pointer to same memory)
RingBufferView consumer(std::span<UINT8>(memory), FALSE); RingBufferView consumer(std::span<u8>(memory), FALSE);
// Data to send // Data to send
String msg = "Hello RingBuffer"; String msg = "Hello RingBuffer";
BOOL pushed = producer.Push(1, {(const UINT8 *) msg.data(), msg.size()}); bool pushed = producer.Push(1, {(const u8 *)msg.data(), msg.size()});
IAT_CHECK(pushed); IAT_CHECK(pushed);
// Read back // Read back
RingBufferView::PacketHeader header; RingBufferView::PacketHeader header;
UINT8 readBuf[128]; u8 readBuf[128];
INT32 bytesRead = consumer.Pop(header, std::span<UINT8>(readBuf, 128)); i32 bytesRead = consumer.Pop(header, std::span<u8>(readBuf, 128));
IAT_CHECK_EQ(header.ID, (UINT16) 1); IAT_CHECK_EQ(header.ID, (u16)1);
IAT_CHECK_EQ(bytesRead, (INT32) msg.size()); IAT_CHECK_EQ(bytesRead, (i32)msg.size());
String readMsg((char *) readBuf, bytesRead); String readMsg((char *)readBuf, bytesRead);
IAT_CHECK_EQ(readMsg, msg); IAT_CHECK_EQ(readMsg, msg);
return TRUE; return TRUE;
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 2. Wrap Around // 2. Wrap Around
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestWrapAround() bool TestWrapAround() {
{ // Small buffer to force wrapping quickly
// Small buffer to force wrapping quickly // Capacity will be 100 bytes
// Capacity will be 100 bytes std::vector<u8> memory(sizeof(RingBufferView::ControlBlock) + 100);
std::vector<UINT8> memory(sizeof(RingBufferView::ControlBlock) + 100); RingBufferView rb(std::span<u8>(memory), TRUE);
RingBufferView rb(std::span<UINT8>(memory), TRUE);
// Fill buffer to near end // Fill buffer to near end
// Push 80 bytes // Push 80 bytes
std::vector<UINT8> junk(80, 0xFF); std::vector<u8> junk(80, 0xFF);
rb.Push(1, junk); rb.Push(1, junk);
// Pop them to advance READ cursor // Pop them to advance READ cursor
RingBufferView::PacketHeader header; RingBufferView::PacketHeader header;
UINT8 outBuf[100]; u8 outBuf[100];
rb.Pop(header, outBuf); rb.Pop(header, outBuf);
// Now READ and WRITE are near index 80. // Now READ and WRITE are near index 80.
// Pushing 40 bytes should trigger a wrap-around (split write) // Pushing 40 bytes should trigger a wrap-around (split write)
std::vector<UINT8> wrapData(40, 0xAA); std::vector<u8> wrapData(40, 0xAA);
BOOL pushed = rb.Push(2, wrapData); bool pushed = rb.Push(2, wrapData);
IAT_CHECK(pushed); IAT_CHECK(pushed);
// Pop and verify integrity // Pop and verify integrity
INT32 popSize = rb.Pop(header, outBuf); i32 popSize = rb.Pop(header, outBuf);
IAT_CHECK_EQ(popSize, 40); IAT_CHECK_EQ(popSize, 40);
// Check if data is intact // Check if data is intact
BOOL match = TRUE; bool match = TRUE;
for (int i = 0; i < 40; i++) for (int i = 0; i < 40; i++) {
{ if (outBuf[i] != 0xAA)
if (outBuf[i] != 0xAA) match = FALSE;
match = FALSE; }
} IAT_CHECK(match);
IAT_CHECK(match);
return TRUE; return TRUE;
} }
IAT_BEGIN_TEST_LIST() IAT_BEGIN_TEST_LIST()

View File

@ -20,12 +20,12 @@ using namespace IACore;
IAT_BEGIN_BLOCK(Core, FloatVec4) IAT_BEGIN_BLOCK(Core, FloatVec4)
BOOL TestFloatArithmetic() bool TestFloatArithmetic()
{ {
FloatVec4 v1(10.0f, 20.0f, 30.0f, 40.0f); FloatVec4 v1(10.0f, 20.0f, 30.0f, 40.0f);
FloatVec4 v2(2.0f, 4.0f, 5.0f, 8.0f); FloatVec4 v2(2.0f, 4.0f, 5.0f, 8.0f);
ALIGN(16) FLOAT32 res[4]; alignas(16) f32 res[4];
(v1 / v2).Store(res); (v1 / v2).Store(res);
IAT_CHECK_APPROX(res[0], 5.0f); IAT_CHECK_APPROX(res[0], 5.0f);
@ -40,9 +40,9 @@ BOOL TestFloatArithmetic()
return TRUE; return TRUE;
} }
BOOL TestMathHelpers() bool TestMathHelpers()
{ {
ALIGN(16) FLOAT32 res[4]; alignas(16) f32 res[4];
FloatVec4 vSq(4.0f, 9.0f, 16.0f, 25.0f); FloatVec4 vSq(4.0f, 9.0f, 16.0f, 25.0f);
vSq.Sqrt().Store(res); vSq.Sqrt().Store(res);
@ -63,9 +63,9 @@ BOOL TestMathHelpers()
return TRUE; return TRUE;
} }
BOOL TestApproxMath() bool TestApproxMath()
{ {
ALIGN(16) FLOAT32 res[4]; alignas(16) f32 res[4];
FloatVec4 v(16.0f, 25.0f, 100.0f, 1.0f); FloatVec4 v(16.0f, 25.0f, 100.0f, 1.0f);
v.Rsqrt().Store(res); v.Rsqrt().Store(res);
@ -76,16 +76,16 @@ BOOL TestApproxMath()
return TRUE; return TRUE;
} }
BOOL TestLinearAlgebra() bool TestLinearAlgebra()
{ {
FloatVec4 v1(1.0f, 2.0f, 3.0f, 4.0f); FloatVec4 v1(1.0f, 2.0f, 3.0f, 4.0f);
FloatVec4 v2(1.0f, 0.0f, 1.0f, 0.0f); FloatVec4 v2(1.0f, 0.0f, 1.0f, 0.0f);
FLOAT32 dot = v1.Dot(v2); f32 dot = v1.Dot(v2);
IAT_CHECK_APPROX(dot, 4.0f); IAT_CHECK_APPROX(dot, 4.0f);
FloatVec4 vNorm(10.0f, 0.0f, 0.0f, 0.0f); FloatVec4 vNorm(10.0f, 0.0f, 0.0f, 0.0f);
ALIGN(16) FLOAT32 res[4]; alignas(16) f32 res[4];
vNorm.Normalize().Store(res); vNorm.Normalize().Store(res);
IAT_CHECK_APPROX(res[0], 1.0f); IAT_CHECK_APPROX(res[0], 1.0f);

View File

@ -23,10 +23,10 @@ using namespace IACore;
IAT_BEGIN_BLOCK(Core, IntVec4) IAT_BEGIN_BLOCK(Core, IntVec4)
BOOL TestConstructors() bool TestConstructors()
{ {
IntVec4 vBroadcast(10); IntVec4 vBroadcast(10);
ALIGN(16) UINT32 storeBuf[4]; alignas(16) u32 storeBuf[4];
vBroadcast.Store(storeBuf); vBroadcast.Store(storeBuf);
IAT_CHECK_EQ(storeBuf[0], 10U); IAT_CHECK_EQ(storeBuf[0], 10U);
@ -37,7 +37,7 @@ BOOL TestConstructors()
IAT_CHECK_EQ(storeBuf[0], 1U); IAT_CHECK_EQ(storeBuf[0], 1U);
IAT_CHECK_EQ(storeBuf[3], 4U); IAT_CHECK_EQ(storeBuf[3], 4U);
ALIGN(16) UINT32 srcBuf[4] = {100, 200, 300, 400}; alignas(16) u32 srcBuf[4] = {100, 200, 300, 400};
IntVec4 vLoad = IntVec4::Load(srcBuf); IntVec4 vLoad = IntVec4::Load(srcBuf);
vLoad.Store(storeBuf); vLoad.Store(storeBuf);
IAT_CHECK_EQ(storeBuf[1], 200U); IAT_CHECK_EQ(storeBuf[1], 200U);
@ -45,13 +45,13 @@ BOOL TestConstructors()
return TRUE; return TRUE;
} }
BOOL TestArithmetic() bool TestArithmetic()
{ {
IntVec4 v1(10, 20, 30, 40); IntVec4 v1(10, 20, 30, 40);
IntVec4 v2(1, 2, 3, 4); IntVec4 v2(1, 2, 3, 4);
IntVec4 vAdd = v1 + v2; IntVec4 vAdd = v1 + v2;
ALIGN(16) UINT32 res[4]; alignas(16) u32 res[4];
vAdd.Store(res); vAdd.Store(res);
IAT_CHECK_EQ(res[0], 11U); IAT_CHECK_EQ(res[0], 11U);
IAT_CHECK_EQ(res[3], 44U); IAT_CHECK_EQ(res[3], 44U);
@ -69,13 +69,13 @@ BOOL TestArithmetic()
return TRUE; return TRUE;
} }
BOOL TestBitwise() bool TestBitwise()
{ {
IntVec4 vAllOnes(0xFFFFFFFF); IntVec4 vAllOnes(0xFFFFFFFF);
IntVec4 vZero((UINT32) 0); IntVec4 vZero((u32) 0);
IntVec4 vPattern(0xAAAAAAAA); IntVec4 vPattern(0xAAAAAAAA);
ALIGN(16) UINT32 res[4]; alignas(16) u32 res[4];
(vAllOnes & vPattern).Store(res); (vAllOnes & vPattern).Store(res);
IAT_CHECK_EQ(res[0], 0xAAAAAAAAU); IAT_CHECK_EQ(res[0], 0xAAAAAAAAU);
@ -100,13 +100,13 @@ BOOL TestBitwise()
return TRUE; return TRUE;
} }
BOOL TestSaturation() bool TestSaturation()
{ {
UINT32 max = 0xFFFFFFFF; u32 max = 0xFFFFFFFF;
IntVec4 vHigh(max - 10); IntVec4 vHigh(max - 10);
IntVec4 vAdd(20); IntVec4 vAdd(20);
ALIGN(16) UINT32 res[4]; alignas(16) u32 res[4];
vHigh.SatAdd(vAdd).Store(res); vHigh.SatAdd(vAdd).Store(res);
IAT_CHECK_EQ(res[0], max); IAT_CHECK_EQ(res[0], max);
@ -119,10 +119,10 @@ BOOL TestSaturation()
return TRUE; return TRUE;
} }
BOOL TestAdvancedOps() bool TestAdvancedOps()
{ {
IntVec4 v(0, 50, 100, 150); IntVec4 v(0, 50, 100, 150);
ALIGN(16) UINT32 res[4]; alignas(16) u32 res[4];
v.Clamp(40, 110).Store(res); v.Clamp(40, 110).Store(res);
IAT_CHECK_EQ(res[0], 40U); IAT_CHECK_EQ(res[0], 40U);

View File

@ -22,141 +22,135 @@ using namespace IACore;
IAT_BEGIN_BLOCK(Core, StreamReader) IAT_BEGIN_BLOCK(Core, StreamReader)
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 1. Basic Primitive Reading (UINT8) // 1. Basic Primitive Reading (u8)
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestReadUint8() bool TestReadUint8() {
{ u8 data[] = {0xAA, 0xBB, 0xCC};
UINT8 data[] = {0xAA, 0xBB, 0xCC}; StreamReader reader(data);
StreamReader reader(data);
// Read First Byte // Read First Byte
auto val1 = reader.Read<UINT8>(); auto val1 = reader.Read<u8>();
IAT_CHECK(val1.has_value()); IAT_CHECK(val1.has_value());
IAT_CHECK_EQ(*val1, 0xAA); IAT_CHECK_EQ(*val1, 0xAA);
IAT_CHECK_EQ(reader.Cursor(), (SIZE_T) 1); IAT_CHECK_EQ(reader.Cursor(), (usize)1);
// Read Second Byte // Read Second Byte
auto val2 = reader.Read<UINT8>(); auto val2 = reader.Read<u8>();
IAT_CHECK_EQ(*val2, 0xBB); IAT_CHECK_EQ(*val2, 0xBB);
return TRUE; return TRUE;
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 2. Multi-byte Reading (Endianness check) // 2. Multi-byte Reading (Endianness check)
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestReadMultiByte() bool TestReadMultiByte() {
{ // 0x04030201 in Little Endian memory layout
// 0x04030201 in Little Endian memory layout // IACore always assumes a Little Endian machine
// IACore always assumes a Little Endian machine u8 data[] = {0x01, 0x02, 0x03, 0x04};
UINT8 data[] = {0x01, 0x02, 0x03, 0x04}; StreamReader reader(data);
StreamReader reader(data);
auto val = reader.Read<UINT32>(); auto val = reader.Read<u32>();
IAT_CHECK(val.has_value()); IAT_CHECK(val.has_value());
IAT_CHECK_EQ(*val, (UINT32) 0x04030201); IAT_CHECK_EQ(*val, (u32)0x04030201);
IAT_CHECK_EQ(reader.Cursor(), (SIZE_T) 4); IAT_CHECK_EQ(reader.Cursor(), (usize)4);
IAT_CHECK(reader.IsEOF()); IAT_CHECK(reader.IsEOF());
return TRUE; return TRUE;
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 3. Floating Point (Approx check) // 3. Floating Point (Approx check)
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestReadFloat() bool TestReadFloat() {
{ f32 pi = 3.14159f;
FLOAT32 pi = 3.14159f; // Bit-cast float to bytes for setup
// Bit-cast float to bytes for setup u8 data[4];
UINT8 data[4]; std::memcpy(data, &pi, 4);
std::memcpy(data, &pi, 4);
StreamReader reader(data); StreamReader reader(data);
auto val = reader.Read<FLOAT32>(); auto val = reader.Read<f32>();
IAT_CHECK(val.has_value()); IAT_CHECK(val.has_value());
IAT_CHECK_APPROX(*val, pi); IAT_CHECK_APPROX(*val, pi);
return TRUE; return TRUE;
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 4. Batch Buffer Reading // 4. Batch Buffer Reading
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestReadBuffer() bool TestReadBuffer() {
{ u8 src[] = {1, 2, 3, 4, 5};
UINT8 src[] = {1, 2, 3, 4, 5}; u8 dst[3] = {0};
UINT8 dst[3] = {0}; StreamReader reader(src);
StreamReader reader(src);
// Read 3 bytes into dst // Read 3 bytes into dst
auto res = reader.Read(dst, 3); auto res = reader.Read(dst, 3);
IAT_CHECK(res.has_value()); IAT_CHECK(res.has_value());
// Verify dst content // Verify dst content
IAT_CHECK_EQ(dst[0], 1); IAT_CHECK_EQ(dst[0], 1);
IAT_CHECK_EQ(dst[1], 2); IAT_CHECK_EQ(dst[1], 2);
IAT_CHECK_EQ(dst[2], 3); IAT_CHECK_EQ(dst[2], 3);
// Verify cursor // Verify cursor
IAT_CHECK_EQ(reader.Cursor(), (SIZE_T) 3); IAT_CHECK_EQ(reader.Cursor(), (usize)3);
return TRUE; return TRUE;
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 5. Navigation (Seek, Skip, Remaining) // 5. Navigation (Seek, Skip, Remaining)
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestNavigation() bool TestNavigation() {
{ u8 data[10] = {0}; // Zero init
UINT8 data[10] = {0}; // Zero init StreamReader reader(data);
StreamReader reader(data);
IAT_CHECK_EQ(reader.Remaining(), (SIZE_T) 10); IAT_CHECK_EQ(reader.Remaining(), (usize)10);
// Skip // Skip
reader.Skip(5); reader.Skip(5);
IAT_CHECK_EQ(reader.Cursor(), (SIZE_T) 5); IAT_CHECK_EQ(reader.Cursor(), (usize)5);
IAT_CHECK_EQ(reader.Remaining(), (SIZE_T) 5); IAT_CHECK_EQ(reader.Remaining(), (usize)5);
// Skip clamping // Skip clamping
reader.Skip(100); // Should clamp to 10 reader.Skip(100); // Should clamp to 10
IAT_CHECK_EQ(reader.Cursor(), (SIZE_T) 10); IAT_CHECK_EQ(reader.Cursor(), (usize)10);
IAT_CHECK(reader.IsEOF()); IAT_CHECK(reader.IsEOF());
// Seek // Seek
reader.Seek(2); reader.Seek(2);
IAT_CHECK_EQ(reader.Cursor(), (SIZE_T) 2); IAT_CHECK_EQ(reader.Cursor(), (usize)2);
IAT_CHECK_EQ(reader.Remaining(), (SIZE_T) 8); IAT_CHECK_EQ(reader.Remaining(), (usize)8);
IAT_CHECK_NOT(reader.IsEOF()); IAT_CHECK_NOT(reader.IsEOF());
return TRUE; return TRUE;
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 6. Error Handling (EOF Protection) // 6. Error Handling (EOF Protection)
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestBoundaryChecks() bool TestBoundaryChecks() {
{ u8 data[] = {0x00, 0x00};
UINT8 data[] = {0x00, 0x00}; StreamReader reader(data);
StreamReader reader(data);
// Valid read // Valid read
UNUSED(reader.Read<UINT16>()); UNUSED(reader.Read<u16>());
IAT_CHECK(reader.IsEOF()); IAT_CHECK(reader.IsEOF());
// Invalid Read Primitive // Invalid Read Primitive
auto val = reader.Read<UINT8>(); auto val = reader.Read<u8>();
IAT_CHECK_NOT(val.has_value()); // Should be unexpected IAT_CHECK_NOT(val.has_value()); // Should be unexpected
// Invalid Batch Read // Invalid Batch Read
UINT8 buf[1]; u8 buf[1];
auto batch = reader.Read(buf, 1); auto batch = reader.Read(buf, 1);
IAT_CHECK_NOT(batch.has_value()); IAT_CHECK_NOT(batch.has_value());
return TRUE; return TRUE;
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------

View File

@ -25,7 +25,7 @@ using namespace IACore;
struct TestVec3 struct TestVec3
{ {
FLOAT32 x, y, z; f32 x, y, z;
// Equality operator required for hash maps, though strictly // Equality operator required for hash maps, though strictly
// the hash function itself doesn't need it, it's good practice to test both. // the hash function itself doesn't need it, it's good practice to test both.
@ -48,10 +48,10 @@ IAT_BEGIN_BLOCK(Core, Utils)
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 1. Binary <-> Hex String Conversion // 1. Binary <-> Hex String Conversion
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestHexConversion() bool TestHexConversion()
{ {
// A. Binary To Hex // A. Binary To Hex
UINT8 bin[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0xFF}; u8 bin[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0xFF};
String hex = IACore::Utils::BinaryToHexString(bin); String hex = IACore::Utils::BinaryToHexString(bin);
IAT_CHECK_EQ(hex, String("DEADBEEF00FF")); IAT_CHECK_EQ(hex, String("DEADBEEF00FF"));
@ -59,7 +59,7 @@ BOOL TestHexConversion()
// B. Hex To Binary (Valid Upper) // B. Hex To Binary (Valid Upper)
auto resUpper = IACore::Utils::HexStringToBinary("DEADBEEF00FF"); auto resUpper = IACore::Utils::HexStringToBinary("DEADBEEF00FF");
IAT_CHECK(resUpper.has_value()); IAT_CHECK(resUpper.has_value());
IAT_CHECK_EQ(resUpper->size(), (SIZE_T) 6); IAT_CHECK_EQ(resUpper->size(), (usize) 6);
IAT_CHECK_EQ((*resUpper)[0], 0xDE); IAT_CHECK_EQ((*resUpper)[0], 0xDE);
IAT_CHECK_EQ((*resUpper)[5], 0xFF); IAT_CHECK_EQ((*resUpper)[5], 0xFF);
@ -69,7 +69,7 @@ BOOL TestHexConversion()
IAT_CHECK_EQ((*resLower)[0], 0xDE); IAT_CHECK_EQ((*resLower)[0], 0xDE);
// D. Round Trip Integrity // D. Round Trip Integrity
Vector<UINT8> original = {1, 2, 3, 4, 5}; Vec<u8> original = {1, 2, 3, 4, 5};
String s = IACore::Utils::BinaryToHexString(original); String s = IACore::Utils::BinaryToHexString(original);
auto back = IACore::Utils::HexStringToBinary(s); auto back = IACore::Utils::HexStringToBinary(s);
IAT_CHECK(back.has_value()); IAT_CHECK(back.has_value());
@ -82,7 +82,7 @@ BOOL TestHexConversion()
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 2. Hex Error Handling // 2. Hex Error Handling
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestHexErrors() bool TestHexErrors()
{ {
// Odd Length // Odd Length
auto odd = IACore::Utils::HexStringToBinary("ABC"); auto odd = IACore::Utils::HexStringToBinary("ABC");
@ -95,7 +95,7 @@ BOOL TestHexErrors()
// Empty string is valid (empty vector) // Empty string is valid (empty vector)
auto empty = IACore::Utils::HexStringToBinary(""); auto empty = IACore::Utils::HexStringToBinary("");
IAT_CHECK(empty.has_value()); IAT_CHECK(empty.has_value());
IAT_CHECK_EQ(empty->size(), (SIZE_T) 0); IAT_CHECK_EQ(empty->size(), (usize) 0);
return TRUE; return TRUE;
} }
@ -103,9 +103,9 @@ BOOL TestHexErrors()
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 3. Algorithms: Sorting // 3. Algorithms: Sorting
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestSort() bool TestSort()
{ {
Vector<int> nums = {5, 1, 4, 2, 3}; Vec<int> nums = {5, 1, 4, 2, 3};
IACore::Utils::Sort(nums); IACore::Utils::Sort(nums);
@ -121,10 +121,10 @@ BOOL TestSort()
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 4. Algorithms: Binary Search (Left/Right) // 4. Algorithms: Binary Search (Left/Right)
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestBinarySearch() bool TestBinarySearch()
{ {
// Must be sorted for Binary Search // Must be sorted for Binary Search
Vector<int> nums = {10, 20, 20, 20, 30}; Vec<int> nums = {10, 20, 20, 20, 30};
// Search Left (Lower Bound) -> First element >= value // Search Left (Lower Bound) -> First element >= value
auto itLeft = IACore::Utils::BinarySearchLeft(nums, 20); auto itLeft = IACore::Utils::BinarySearchLeft(nums, 20);
@ -148,11 +148,11 @@ BOOL TestBinarySearch()
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 5. Hashing Basics // 5. Hashing Basics
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestHashBasics() bool TestHashBasics()
{ {
UINT64 h1 = IACore::Utils::ComputeHash(10, 20.5f, "Hello"); u64 h1 = IACore::Utils::ComputeHash(10, 20.5f, "Hello");
UINT64 h2 = IACore::Utils::ComputeHash(10, 20.5f, "Hello"); u64 h2 = IACore::Utils::ComputeHash(10, 20.5f, "Hello");
UINT64 h3 = IACore::Utils::ComputeHash(10, 20.5f, "World"); u64 h3 = IACore::Utils::ComputeHash(10, 20.5f, "World");
// Determinism // Determinism
IAT_CHECK_EQ(h1, h2); IAT_CHECK_EQ(h1, h2);
@ -162,8 +162,8 @@ BOOL TestHashBasics()
// Order sensitivity (Golden ratio combine should care about order) // Order sensitivity (Golden ratio combine should care about order)
// Hash(A, B) != Hash(B, A) // Hash(A, B) != Hash(B, A)
UINT64 orderA = IACore::Utils::ComputeHash(1, 2); u64 orderA = IACore::Utils::ComputeHash(1, 2);
UINT64 orderB = IACore::Utils::ComputeHash(2, 1); u64 orderB = IACore::Utils::ComputeHash(2, 1);
IAT_CHECK_NEQ(orderA, orderB); IAT_CHECK_NEQ(orderA, orderB);
return TRUE; return TRUE;
@ -172,7 +172,7 @@ BOOL TestHashBasics()
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 6. Macro Verification (IA_MAKE_HASHABLE) // 6. Macro Verification (IA_MAKE_HASHABLE)
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestHashMacro() bool TestHashMacro()
{ {
TestVec3 v1{1.0f, 2.0f, 3.0f}; TestVec3 v1{1.0f, 2.0f, 3.0f};
TestVec3 v2{1.0f, 2.0f, 3.0f}; TestVec3 v2{1.0f, 2.0f, 3.0f};
@ -180,9 +180,9 @@ BOOL TestHashMacro()
ankerl::unordered_dense::hash<TestVec3> hasher; ankerl::unordered_dense::hash<TestVec3> hasher;
UINT64 h1 = hasher(v1); u64 h1 = hasher(v1);
UINT64 h2 = hasher(v2); u64 h2 = hasher(v2);
UINT64 h3 = hasher(v3); u64 h3 = hasher(v3);
IAT_CHECK_EQ(h1, h2); // Same content = same hash IAT_CHECK_EQ(h1, h2); // Same content = same hash
IAT_CHECK_NEQ(h1, h3); // Different content = different hash IAT_CHECK_NEQ(h1, h3); // Different content = different hash
@ -191,10 +191,10 @@ BOOL TestHashMacro()
// Verify ComputeHash integration // Verify ComputeHash integration
// ------------------------------------------------------------- // -------------------------------------------------------------
UINT64 hManual = 0; u64 hManual = 0;
IACore::Utils::HashCombine(hManual, v1); IACore::Utils::HashCombine(hManual, v1);
UINT64 hWrapper = IACore::Utils::ComputeHash(v1); u64 hWrapper = IACore::Utils::ComputeHash(v1);
// This proves ComputeHash found the specialization and mixed it correctly // This proves ComputeHash found the specialization and mixed it correctly
IAT_CHECK_EQ(hManual, hWrapper); IAT_CHECK_EQ(hManual, hWrapper);