This commit is contained in:
@ -6,6 +6,7 @@ set(SRC_FILES
|
||||
"imp/cpp/JSON.cpp"
|
||||
"imp/cpp/IACore.cpp"
|
||||
"imp/cpp/Logger.cpp"
|
||||
"imp/cpp/Utils.cpp"
|
||||
"imp/cpp/FileOps.cpp"
|
||||
"imp/cpp/DataOps.cpp"
|
||||
"imp/cpp/AsyncOps.cpp"
|
||||
@ -16,9 +17,9 @@ set(SRC_FILES
|
||||
"imp/cpp/StreamReader.cpp"
|
||||
"imp/cpp/StreamWriter.cpp"
|
||||
|
||||
"imp/cpp/Http/Common.cpp"
|
||||
"imp/cpp/Http/Client.cpp"
|
||||
"imp/cpp/Http/Server.cpp"
|
||||
#"imp/cpp/Http/Common.cpp"
|
||||
#"imp/cpp/Http/Client.cpp"
|
||||
#"imp/cpp/Http/Server.cpp"
|
||||
)
|
||||
|
||||
add_library(IACore STATIC ${SRC_FILES})
|
||||
|
||||
@ -15,164 +15,170 @@
|
||||
|
||||
#include <IACore/AsyncOps.hpp>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
Mutex AsyncOps::s_queueMutex;
|
||||
ConditionVariable AsyncOps::s_wakeCondition;
|
||||
Vector<JoiningThread> AsyncOps::s_scheduleWorkers;
|
||||
Deque<AsyncOps::ScheduledTask> AsyncOps::s_highPriorityQueue;
|
||||
Deque<AsyncOps::ScheduledTask> AsyncOps::s_normalPriorityQueue;
|
||||
namespace IACore {
|
||||
std::mutex AsyncOps::s_queue_mutex;
|
||||
std::condition_variable AsyncOps::s_wake_condition;
|
||||
Vec<std::jthread> AsyncOps::s_schedule_workers;
|
||||
std::deque<AsyncOps::ScheduledTask> AsyncOps::s_high_priority_queue;
|
||||
std::deque<AsyncOps::ScheduledTask> AsyncOps::s_normal_priority_queue;
|
||||
|
||||
VOID AsyncOps::RunTask(IN Function<VOID()> task)
|
||||
{
|
||||
JoiningThread(task).detach();
|
||||
auto AsyncOps::run_task(std::function<void()> task) -> void {
|
||||
std::jthread(std::move(task)).detach();
|
||||
}
|
||||
|
||||
auto AsyncOps::initialize_scheduler(u8 worker_count) -> Result<void> {
|
||||
if (worker_count == 0) {
|
||||
const auto hw_concurrency = std::thread::hardware_concurrency();
|
||||
u32 threads = 2;
|
||||
if (hw_concurrency > 2) {
|
||||
threads = hw_concurrency - 2;
|
||||
}
|
||||
|
||||
VOID AsyncOps::InitializeScheduler(IN UINT8 workerCount)
|
||||
{
|
||||
if (!workerCount)
|
||||
workerCount = std::max((UINT32) 2, std::thread::hardware_concurrency() - 2);
|
||||
for (UINT32 i = 0; i < workerCount; i++)
|
||||
s_scheduleWorkers.emplace_back(AsyncOps::ScheduleWorkerLoop, i + 1);
|
||||
if (threads > 255) {
|
||||
threads = 255;
|
||||
}
|
||||
worker_count = static_cast<u8>(threads);
|
||||
}
|
||||
|
||||
VOID AsyncOps::TerminateScheduler()
|
||||
{
|
||||
for (auto &w : s_scheduleWorkers)
|
||||
{
|
||||
w.request_stop();
|
||||
for (u32 i = 0; i < worker_count; ++i) {
|
||||
s_schedule_workers.emplace_back(schedule_worker_loop,
|
||||
static_cast<WorkerId>(i + 1));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto AsyncOps::terminate_scheduler() -> void {
|
||||
for (auto &worker : s_schedule_workers) {
|
||||
worker.request_stop();
|
||||
}
|
||||
|
||||
s_wake_condition.notify_all();
|
||||
|
||||
for (auto &worker : s_schedule_workers) {
|
||||
if (worker.joinable()) {
|
||||
worker.join();
|
||||
}
|
||||
}
|
||||
|
||||
s_schedule_workers.clear();
|
||||
}
|
||||
|
||||
auto AsyncOps::schedule_task(std::function<void(WorkerId worker_id)> task,
|
||||
TaskTag tag, Schedule *schedule, Priority priority)
|
||||
-> void {
|
||||
ensure(!s_schedule_workers.empty(),
|
||||
"Scheduler must be initialized before calling schedule_task");
|
||||
|
||||
schedule->counter.fetch_add(1);
|
||||
{
|
||||
std::lock_guard lock(s_queue_mutex);
|
||||
if (priority == Priority::High) {
|
||||
s_high_priority_queue.emplace_back(
|
||||
ScheduledTask{tag, schedule, std::move(task)});
|
||||
} else {
|
||||
s_normal_priority_queue.emplace_back(
|
||||
ScheduledTask{tag, schedule, std::move(task)});
|
||||
}
|
||||
}
|
||||
s_wake_condition.notify_one();
|
||||
}
|
||||
|
||||
auto AsyncOps::cancel_tasks_of_tag(TaskTag tag) -> void {
|
||||
std::lock_guard lock(s_queue_mutex);
|
||||
|
||||
auto cancel_from_queue = [&](std::deque<ScheduledTask> &queue) {
|
||||
for (auto it = queue.begin(); it != queue.end(); /* no increment */) {
|
||||
if (it->tag == tag) {
|
||||
if (it->schedule_handle->counter.fetch_sub(1) == 1) {
|
||||
it->schedule_handle->counter.notify_all();
|
||||
}
|
||||
|
||||
s_wakeCondition.notify_all();
|
||||
|
||||
for (auto &w : s_scheduleWorkers)
|
||||
{
|
||||
if (w.joinable())
|
||||
{
|
||||
w.join();
|
||||
}
|
||||
}
|
||||
|
||||
s_scheduleWorkers.clear();
|
||||
it = queue.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
VOID AsyncOps::ScheduleTask(IN Function<VOID(IN WorkerID workerID)> task, IN TaskTag tag, IN Schedule *schedule,
|
||||
IN Priority priority)
|
||||
cancel_from_queue(s_high_priority_queue);
|
||||
cancel_from_queue(s_normal_priority_queue);
|
||||
}
|
||||
|
||||
auto AsyncOps::wait_for_schedule_completion(Schedule *schedule) -> void {
|
||||
ensure(!s_schedule_workers.empty(), "Scheduler must be initialized before "
|
||||
"calling wait_for_schedule_completion");
|
||||
|
||||
while (schedule->counter.load() > 0) {
|
||||
ScheduledTask task;
|
||||
bool found_task = false;
|
||||
{
|
||||
IA_ASSERT(s_scheduleWorkers.size() && "Scheduler must be initialized before calling this function");
|
||||
|
||||
schedule->Counter.fetch_add(1);
|
||||
{
|
||||
ScopedLock lock(s_queueMutex);
|
||||
if (priority == Priority::High)
|
||||
s_highPriorityQueue.emplace_back(ScheduledTask{tag, schedule, IA_MOVE(task)});
|
||||
else
|
||||
s_normalPriorityQueue.emplace_back(ScheduledTask{tag, schedule, IA_MOVE(task)});
|
||||
}
|
||||
s_wakeCondition.notify_one();
|
||||
std::unique_lock lock(s_queue_mutex);
|
||||
if (!s_high_priority_queue.empty()) {
|
||||
task = std::move(s_high_priority_queue.front());
|
||||
s_high_priority_queue.pop_front();
|
||||
found_task = true;
|
||||
} else if (!s_normal_priority_queue.empty()) {
|
||||
task = std::move(s_normal_priority_queue.front());
|
||||
s_normal_priority_queue.pop_front();
|
||||
found_task = true;
|
||||
}
|
||||
}
|
||||
|
||||
VOID AsyncOps::CancelTasksOfTag(IN TaskTag tag)
|
||||
if (found_task) {
|
||||
task.task(MAIN_THREAD_WORKER_ID);
|
||||
if (task.schedule_handle->counter.fetch_sub(1) == 1) {
|
||||
task.schedule_handle->counter.notify_all();
|
||||
}
|
||||
} else {
|
||||
const auto current_val = schedule->counter.load();
|
||||
if (current_val > 0) {
|
||||
schedule->counter.wait(current_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto AsyncOps::get_worker_count() -> WorkerId {
|
||||
// +1 for MainThread (Work Stealing)
|
||||
return static_cast<WorkerId>(s_schedule_workers.size() + 1);
|
||||
}
|
||||
|
||||
auto AsyncOps::schedule_worker_loop(std::stop_token stop_token,
|
||||
WorkerId worker_id) -> void {
|
||||
while (!stop_token.stop_requested()) {
|
||||
ScheduledTask task;
|
||||
bool found_task = false;
|
||||
{
|
||||
ScopedLock lock(s_queueMutex);
|
||||
std::unique_lock lock(s_queue_mutex);
|
||||
|
||||
auto cancelFromQueue = [&](Deque<ScheduledTask> &queue) {
|
||||
for (auto it = queue.begin(); it != queue.end(); /* no increment here */)
|
||||
{
|
||||
if (it->Tag == tag)
|
||||
{
|
||||
if (it->ScheduleHandle->Counter.fetch_sub(1) == 1)
|
||||
it->ScheduleHandle->Counter.notify_all();
|
||||
s_wake_condition.wait(lock, [&stop_token] {
|
||||
return !s_high_priority_queue.empty() ||
|
||||
!s_normal_priority_queue.empty() || stop_token.stop_requested();
|
||||
});
|
||||
|
||||
it = queue.erase(it);
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
};
|
||||
if (stop_token.stop_requested() && s_high_priority_queue.empty() &&
|
||||
s_normal_priority_queue.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
cancelFromQueue(s_highPriorityQueue);
|
||||
cancelFromQueue(s_normalPriorityQueue);
|
||||
if (!s_high_priority_queue.empty()) {
|
||||
task = std::move(s_high_priority_queue.front());
|
||||
s_high_priority_queue.pop_front();
|
||||
found_task = true;
|
||||
} else if (!s_normal_priority_queue.empty()) {
|
||||
task = std::move(s_normal_priority_queue.front());
|
||||
s_normal_priority_queue.pop_front();
|
||||
found_task = true;
|
||||
}
|
||||
}
|
||||
|
||||
VOID AsyncOps::WaitForScheduleCompletion(IN Schedule *schedule)
|
||||
{
|
||||
IA_ASSERT(s_scheduleWorkers.size() && "Scheduler must be initialized before calling this function");
|
||||
|
||||
while (schedule->Counter.load() > 0)
|
||||
{
|
||||
ScheduledTask task;
|
||||
BOOL foundTask{FALSE};
|
||||
{
|
||||
UniqueLock lock(s_queueMutex);
|
||||
if (!s_highPriorityQueue.empty())
|
||||
{
|
||||
task = IA_MOVE(s_highPriorityQueue.front());
|
||||
s_highPriorityQueue.pop_front();
|
||||
foundTask = TRUE;
|
||||
}
|
||||
else if (!s_normalPriorityQueue.empty())
|
||||
{
|
||||
task = IA_MOVE(s_normalPriorityQueue.front());
|
||||
s_normalPriorityQueue.pop_front();
|
||||
foundTask = TRUE;
|
||||
}
|
||||
}
|
||||
if (foundTask)
|
||||
{
|
||||
task.Task(MainThreadWorkerID);
|
||||
if (task.ScheduleHandle->Counter.fetch_sub(1) == 1)
|
||||
task.ScheduleHandle->Counter.notify_all();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto currentVal = schedule->Counter.load();
|
||||
if (currentVal > 0)
|
||||
schedule->Counter.wait(currentVal);
|
||||
}
|
||||
}
|
||||
if (found_task) {
|
||||
task.task(worker_id);
|
||||
if (task.schedule_handle->counter.fetch_sub(1) == 1) {
|
||||
task.schedule_handle->counter.notify_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AsyncOps::WorkerID AsyncOps::GetWorkerCount()
|
||||
{
|
||||
return static_cast<WorkerID>(s_scheduleWorkers.size() + 1); // +1 for MainThread (Work Stealing)
|
||||
}
|
||||
|
||||
VOID AsyncOps::ScheduleWorkerLoop(IN StopToken stopToken, IN WorkerID workerID)
|
||||
{
|
||||
while (!stopToken.stop_requested())
|
||||
{
|
||||
ScheduledTask task;
|
||||
BOOL foundTask{FALSE};
|
||||
{
|
||||
UniqueLock lock(s_queueMutex);
|
||||
|
||||
s_wakeCondition.wait(lock, [&stopToken] {
|
||||
return !s_highPriorityQueue.empty() || !s_normalPriorityQueue.empty() || stopToken.stop_requested();
|
||||
});
|
||||
|
||||
if (stopToken.stop_requested() && s_highPriorityQueue.empty() && s_normalPriorityQueue.empty())
|
||||
return;
|
||||
|
||||
if (!s_highPriorityQueue.empty())
|
||||
{
|
||||
task = IA_MOVE(s_highPriorityQueue.front());
|
||||
s_highPriorityQueue.pop_front();
|
||||
foundTask = TRUE;
|
||||
}
|
||||
else if (!s_normalPriorityQueue.empty())
|
||||
{
|
||||
task = IA_MOVE(s_normalPriorityQueue.front());
|
||||
s_normalPriorityQueue.pop_front();
|
||||
foundTask = TRUE;
|
||||
}
|
||||
}
|
||||
if (foundTask)
|
||||
{
|
||||
task.Task(workerID);
|
||||
if (task.ScheduleHandle->Counter.fetch_sub(1) == 1)
|
||||
task.ScheduleHandle->Counter.notify_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -17,9 +17,9 @@
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
CLIParser::CLIParser(IN Span<CONST String> args) : m_argList(args)
|
||||
CLIParser::CLIParser(Span<const String> args) : m_argList(args)
|
||||
{
|
||||
IA_RELEASE_ASSERT(args.size());
|
||||
assert(args.size());
|
||||
|
||||
m_currentArg = m_argList.begin();
|
||||
|
||||
|
||||
@ -16,457 +16,458 @@
|
||||
#include <IACore/DataOps.hpp>
|
||||
#include <IACore/Platform.hpp>
|
||||
|
||||
#include <bit>
|
||||
#include <cstring>
|
||||
#include <zlib.h>
|
||||
#include <zstd.h>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
template<typename T> INLINE T ReadUnaligned(IN PCUINT8 ptr)
|
||||
{
|
||||
T v;
|
||||
std::memcpy(&v, ptr, sizeof(T));
|
||||
return v;
|
||||
}
|
||||
|
||||
struct CRC32Tables
|
||||
{
|
||||
UINT32 table[8][256] = {};
|
||||
|
||||
CONSTEVAL CRC32Tables()
|
||||
{
|
||||
CONSTEXPR UINT32 t = 0x82F63B78;
|
||||
|
||||
for (UINT32 i = 0; i < 256; i++)
|
||||
{
|
||||
UINT32 crc = i;
|
||||
for (int j = 0; j < 8; j++)
|
||||
crc = (crc >> 1) ^ ((crc & 1) ? t : 0);
|
||||
table[0][i] = crc;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
for (int slice = 1; slice < 8; slice++)
|
||||
{
|
||||
UINT32 prev = table[slice - 1][i];
|
||||
table[slice][i] = (prev >> 8) ^ table[0][prev & 0xFF];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
STATIC CONSTEXPR CRC32Tables CRC32_TABLES{};
|
||||
|
||||
#if IA_ARCH_X64
|
||||
INLINE UINT32 CRC32_x64_HW(IN Span<CONST UINT8> data)
|
||||
{
|
||||
CONST UINT8 *p = data.data();
|
||||
|
||||
UINT32 crc = 0xFFFFFFFF;
|
||||
SIZE_T len = data.size();
|
||||
|
||||
while (len >= 8)
|
||||
{
|
||||
UINT64 chunk = ReadUnaligned<UINT64>(p);
|
||||
crc = (UINT32) _mm_crc32_u64((UINT64) crc, chunk);
|
||||
p += 8;
|
||||
len -= 8;
|
||||
}
|
||||
|
||||
while (len--)
|
||||
crc = _mm_crc32_u8(crc, *p++);
|
||||
|
||||
return ~crc;
|
||||
}
|
||||
#include <immintrin.h>
|
||||
#endif
|
||||
|
||||
#if IA_ARCH_ARM64
|
||||
__attribute__((target("+crc"))) INLINE UINT32 CRC32_ARM64_HW(IN Span<CONST UINT8> data)
|
||||
{
|
||||
CONST UINT8 *p = data.data();
|
||||
|
||||
UINT32 crc = 0xFFFFFFFF;
|
||||
SIZE_T len = data.size();
|
||||
|
||||
while (len >= 8)
|
||||
{
|
||||
UINT64 chunk = ReadUnaligned<UINT64>(p);
|
||||
crc = __crc32cd(crc, chunk);
|
||||
p += 8;
|
||||
len -= 8;
|
||||
}
|
||||
|
||||
while (len--)
|
||||
crc = __crc32cb(crc, *p++);
|
||||
|
||||
return ~crc;
|
||||
}
|
||||
#include <arm_acle.h>
|
||||
#endif
|
||||
|
||||
INLINE UINT32 CRC32_Software_Slice8(IN Span<CONST UINT8> data)
|
||||
{
|
||||
CONST UINT8 *p = data.data();
|
||||
UINT32 crc = 0xFFFFFFFF;
|
||||
SIZE_T len = data.size();
|
||||
namespace IACore {
|
||||
template <typename T>
|
||||
[[nodiscard]] inline auto read_unaligned(const u8 *ptr) -> T {
|
||||
T v;
|
||||
std::memcpy(&v, ptr, sizeof(T));
|
||||
return v;
|
||||
}
|
||||
|
||||
while (len >= 8)
|
||||
{
|
||||
UINT32 term1 = crc ^ ReadUnaligned<UINT32>(p);
|
||||
UINT32 term2 = ReadUnaligned<UINT32>(p + 4);
|
||||
struct Crc32Tables {
|
||||
u32 table[8][256] = {};
|
||||
|
||||
crc = CRC32_TABLES.table[7][term1 & 0xFF] ^ CRC32_TABLES.table[6][(term1 >> 8) & 0xFF] ^
|
||||
CRC32_TABLES.table[5][(term1 >> 16) & 0xFF] ^ CRC32_TABLES.table[4][(term1 >> 24)] ^
|
||||
CRC32_TABLES.table[3][term2 & 0xFF] ^ CRC32_TABLES.table[2][(term2 >> 8) & 0xFF] ^
|
||||
CRC32_TABLES.table[1][(term2 >> 16) & 0xFF] ^ CRC32_TABLES.table[0][(term2 >> 24)];
|
||||
consteval Crc32Tables() {
|
||||
constexpr u32 T = 0x82F63B78;
|
||||
|
||||
p += 8;
|
||||
len -= 8;
|
||||
}
|
||||
|
||||
while (len--)
|
||||
crc = (crc >> 8) ^ CRC32_TABLES.table[0][(crc ^ *p++) & 0xFF];
|
||||
|
||||
return ~crc;
|
||||
for (u32 i = 0; i < 256; i++) {
|
||||
u32 crc = i;
|
||||
for (i32 j = 0; j < 8; j++) {
|
||||
crc = (crc >> 1) ^ ((crc & 1) ? T : 0);
|
||||
}
|
||||
table[0][i] = crc;
|
||||
}
|
||||
|
||||
UINT32 DataOps::CRC32(IN Span<CONST UINT8> data)
|
||||
{
|
||||
for (i32 i = 0; i < 256; i++) {
|
||||
for (i32 slice = 1; slice < 8; slice++) {
|
||||
const u32 prev = table[slice - 1][i];
|
||||
table[slice][i] = (prev >> 8) ^ table[0][prev & 0xFF];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr Crc32Tables CRC32_TABLES{};
|
||||
|
||||
#if IA_ARCH_X64
|
||||
// IACore mandates AVX2 so no need to check
|
||||
// for Platform::GetCapabilities().HardwareCRC32
|
||||
return CRC32_x64_HW(data);
|
||||
#elif IA_ARCH_ARM64
|
||||
if (Platform::GetCapabilities().HardwareCRC32)
|
||||
return CRC32_ARM64_HW(data);
|
||||
inline auto crc32_x64_hw(Span<const u8> data) -> u32 {
|
||||
const u8 *p = data.data();
|
||||
|
||||
u32 crc = 0xFFFFFFFF;
|
||||
usize len = data.size();
|
||||
|
||||
while (len >= 8) {
|
||||
const u64 chunk = read_unaligned<u64>(p);
|
||||
crc = static_cast<u32>(_mm_crc32_u64(static_cast<u64>(crc), chunk));
|
||||
p += 8;
|
||||
len -= 8;
|
||||
}
|
||||
|
||||
while (len--) {
|
||||
crc = _mm_crc32_u8(crc, *p++);
|
||||
}
|
||||
|
||||
return ~crc;
|
||||
}
|
||||
#endif
|
||||
return CRC32_Software_Slice8(data);
|
||||
}
|
||||
} // namespace IACore
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
CONSTEXPR UINT32 XXH_PRIME32_1 = 0x9E3779B1U;
|
||||
CONSTEXPR UINT32 XXH_PRIME32_2 = 0x85EBCA77U;
|
||||
CONSTEXPR UINT32 XXH_PRIME32_3 = 0xC2B2AE3DU;
|
||||
CONSTEXPR UINT32 XXH_PRIME32_4 = 0x27D4EB2FU;
|
||||
CONSTEXPR UINT32 XXH_PRIME32_5 = 0x165667B1U;
|
||||
#if IA_ARCH_ARM64
|
||||
__attribute__((target("+crc"))) inline auto crc32_arm64_hw(Span<const u8> data)
|
||||
-> u32 {
|
||||
const u8 *p = data.data();
|
||||
|
||||
INLINE UINT32 XXH32_Round(IN UINT32 seed, IN UINT32 input)
|
||||
{
|
||||
seed += input * XXH_PRIME32_2;
|
||||
seed = std::rotl(seed, 13);
|
||||
seed *= XXH_PRIME32_1;
|
||||
return seed;
|
||||
u32 crc = 0xFFFFFFFF;
|
||||
usize len = data.size();
|
||||
|
||||
while (len >= 8) {
|
||||
const u64 chunk = read_unaligned<u64>(p);
|
||||
crc = __crc32cd(crc, chunk);
|
||||
p += 8;
|
||||
len -= 8;
|
||||
}
|
||||
|
||||
while (len--) {
|
||||
crc = __crc32cb(crc, *p++);
|
||||
}
|
||||
|
||||
return ~crc;
|
||||
}
|
||||
#endif
|
||||
|
||||
inline auto crc32_software_slice8(Span<const u8> data) -> u32 {
|
||||
const u8 *p = data.data();
|
||||
u32 crc = 0xFFFFFFFF;
|
||||
usize len = data.size();
|
||||
|
||||
while (len >= 8) {
|
||||
const u32 term1 = crc ^ read_unaligned<u32>(p);
|
||||
const u32 term2 = read_unaligned<u32>(p + 4);
|
||||
|
||||
crc = CRC32_TABLES.table[7][term1 & 0xFF] ^
|
||||
CRC32_TABLES.table[6][(term1 >> 8) & 0xFF] ^
|
||||
CRC32_TABLES.table[5][(term1 >> 16) & 0xFF] ^
|
||||
CRC32_TABLES.table[4][(term1 >> 24)] ^
|
||||
CRC32_TABLES.table[3][term2 & 0xFF] ^
|
||||
CRC32_TABLES.table[2][(term2 >> 8) & 0xFF] ^
|
||||
CRC32_TABLES.table[1][(term2 >> 16) & 0xFF] ^
|
||||
CRC32_TABLES.table[0][(term2 >> 24)];
|
||||
|
||||
p += 8;
|
||||
len -= 8;
|
||||
}
|
||||
|
||||
while (len--) {
|
||||
crc = (crc >> 8) ^ CRC32_TABLES.table[0][(crc ^ *p++) & 0xFF];
|
||||
}
|
||||
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
auto DataOps::crc32(Span<const u8> data) -> u32 {
|
||||
#if IA_ARCH_X64
|
||||
// IACore mandates AVX2 so no need to check
|
||||
// for Platform::GetCapabilities().HardwareCRC32
|
||||
return crc32_x64_hw(data);
|
||||
#elif IA_ARCH_ARM64
|
||||
if (Platform::GetCapabilities().HardwareCRC32) {
|
||||
return crc32_arm64_hw(data);
|
||||
}
|
||||
#endif
|
||||
return crc32_software_slice8(data);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// xxHash
|
||||
// =============================================================================
|
||||
|
||||
constexpr u32 XXH_PRIME32_1 = 0x9E3779B1U;
|
||||
constexpr u32 XXH_PRIME32_2 = 0x85EBCA77U;
|
||||
constexpr u32 XXH_PRIME32_3 = 0xC2B2AE3DU;
|
||||
constexpr u32 XXH_PRIME32_4 = 0x27D4EB2FU;
|
||||
constexpr u32 XXH_PRIME32_5 = 0x165667B1U;
|
||||
|
||||
inline auto xxh32_round(u32 seed, u32 input) -> u32 {
|
||||
seed += input * XXH_PRIME32_2;
|
||||
seed = std::rotl(seed, 13);
|
||||
seed *= XXH_PRIME32_1;
|
||||
return seed;
|
||||
}
|
||||
|
||||
auto DataOps::hash_xxhash(const String &string, u32 seed) -> u32 {
|
||||
return hash_xxhash(Span<const u8>(reinterpret_cast<const u8 *>(string.data()),
|
||||
string.length()),
|
||||
seed);
|
||||
}
|
||||
|
||||
auto DataOps::hash_xxhash(Span<const u8> data, u32 seed) -> u32 {
|
||||
const u8 *p = data.data();
|
||||
const u8 *const b_end = p + data.size();
|
||||
u32 h32{};
|
||||
|
||||
if (data.size() >= 16) {
|
||||
const u8 *const limit = b_end - 16;
|
||||
|
||||
u32 v1 = seed + XXH_PRIME32_1 + XXH_PRIME32_2;
|
||||
u32 v2 = seed + XXH_PRIME32_2;
|
||||
u32 v3 = seed + 0;
|
||||
u32 v4 = seed - XXH_PRIME32_1;
|
||||
|
||||
do {
|
||||
v1 = xxh32_round(v1, read_unaligned<u32>(p));
|
||||
p += 4;
|
||||
v2 = xxh32_round(v2, read_unaligned<u32>(p));
|
||||
p += 4;
|
||||
v3 = xxh32_round(v3, read_unaligned<u32>(p));
|
||||
p += 4;
|
||||
v4 = xxh32_round(v4, read_unaligned<u32>(p));
|
||||
p += 4;
|
||||
} while (p <= limit);
|
||||
|
||||
h32 = std::rotl(v1, 1) + std::rotl(v2, 7) + std::rotl(v3, 12) +
|
||||
std::rotl(v4, 18);
|
||||
} else {
|
||||
h32 = seed + XXH_PRIME32_5;
|
||||
}
|
||||
|
||||
h32 += static_cast<u32>(data.size());
|
||||
|
||||
while (p + 4 <= b_end) {
|
||||
const auto t = read_unaligned<u32>(p) * XXH_PRIME32_3;
|
||||
h32 += t;
|
||||
h32 = std::rotl(h32, 17) * XXH_PRIME32_4;
|
||||
p += 4;
|
||||
}
|
||||
|
||||
while (p < b_end) {
|
||||
h32 += (*p++) * XXH_PRIME32_5;
|
||||
h32 = std::rotl(h32, 11) * XXH_PRIME32_1;
|
||||
}
|
||||
|
||||
h32 ^= h32 >> 15;
|
||||
h32 *= XXH_PRIME32_2;
|
||||
h32 ^= h32 >> 13;
|
||||
h32 *= XXH_PRIME32_3;
|
||||
h32 ^= h32 >> 16;
|
||||
|
||||
return h32;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// FNV-1a
|
||||
// =============================================================================
|
||||
|
||||
constexpr u32 FNV1A_32_PRIME = 0x01000193;
|
||||
constexpr u32 FNV1A_32_OFFSET = 0x811c9dc5;
|
||||
|
||||
auto DataOps::hash_fnv1a(const String &string) -> u32 {
|
||||
u32 hash = FNV1A_32_OFFSET;
|
||||
for (const char c : string) {
|
||||
hash ^= static_cast<u8>(c);
|
||||
hash *= FNV1A_32_PRIME;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
auto DataOps::hash_fnv1a(Span<const u8> data) -> u32 {
|
||||
u32 hash = FNV1A_32_OFFSET;
|
||||
const auto *ptr = data.data();
|
||||
|
||||
for (usize i = 0; i < data.size(); ++i) {
|
||||
hash ^= ptr[i];
|
||||
hash *= FNV1A_32_PRIME;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Compression
|
||||
// =============================================================================
|
||||
|
||||
auto DataOps::detect_compression(Span<const u8> data) -> CompressionType {
|
||||
if (data.size() < 2) {
|
||||
return CompressionType::None;
|
||||
}
|
||||
|
||||
// Check for GZIP Magic Number (0x1F 0x8B)
|
||||
if (data[0] == 0x1F && data[1] == 0x8B) {
|
||||
return CompressionType::Gzip;
|
||||
}
|
||||
|
||||
// Check for ZLIB Magic Number (starts with 0x78)
|
||||
// 0x78 = Deflate compression with 32k window size
|
||||
if (data[0] == 0x78 &&
|
||||
(data[1] == 0x01 || data[1] == 0x9C || data[1] == 0xDA)) {
|
||||
return CompressionType::Zlib;
|
||||
}
|
||||
|
||||
return CompressionType::None;
|
||||
}
|
||||
|
||||
auto DataOps::zlib_inflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||
z_stream zs{};
|
||||
zs.zalloc = Z_NULL;
|
||||
zs.zfree = Z_NULL;
|
||||
zs.opaque = Z_NULL;
|
||||
|
||||
// 15 + 32 = Auto-detect Gzip or Zlib
|
||||
if (inflateInit2(&zs, 15 + 32) != Z_OK) {
|
||||
return fail("Failed to initialize zlib inflate");
|
||||
}
|
||||
|
||||
zs.next_in = const_cast<Bytef *>(data.data());
|
||||
zs.avail_in = static_cast<uInt>(data.size());
|
||||
|
||||
Vec<u8> out_buffer;
|
||||
// Start with 2x input size.
|
||||
const usize guess_size =
|
||||
data.size() < 1024 ? data.size() * 4 : data.size() * 2;
|
||||
out_buffer.resize(guess_size);
|
||||
|
||||
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data());
|
||||
zs.avail_out = static_cast<uInt>(out_buffer.size());
|
||||
|
||||
int ret;
|
||||
do {
|
||||
if (zs.avail_out == 0) {
|
||||
const usize current_pos = zs.total_out;
|
||||
const usize new_size = out_buffer.size() * 2;
|
||||
out_buffer.resize(new_size);
|
||||
|
||||
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data() + current_pos);
|
||||
zs.avail_out = static_cast<uInt>(new_size - current_pos);
|
||||
}
|
||||
|
||||
UINT32 DataOps::Hash_xxHash(IN CONST String &string, IN UINT32 seed)
|
||||
{
|
||||
return Hash_xxHash(Span<CONST UINT8>(reinterpret_cast<PCUINT8>(string.data()), string.length()), seed);
|
||||
ret = inflate(&zs, Z_NO_FLUSH);
|
||||
|
||||
} while (ret == Z_OK);
|
||||
|
||||
inflateEnd(&zs);
|
||||
|
||||
if (ret != Z_STREAM_END) {
|
||||
return fail("Failed to inflate: corrupt data or stream error");
|
||||
}
|
||||
|
||||
out_buffer.resize(zs.total_out);
|
||||
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
auto DataOps::zlib_deflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||
z_stream zs{};
|
||||
zs.zalloc = Z_NULL;
|
||||
zs.zfree = Z_NULL;
|
||||
zs.opaque = Z_NULL;
|
||||
|
||||
if (deflateInit(&zs, Z_DEFAULT_COMPRESSION) != Z_OK) {
|
||||
return fail("Failed to initialize zlib deflate");
|
||||
}
|
||||
|
||||
zs.next_in = const_cast<Bytef *>(data.data());
|
||||
zs.avail_in = static_cast<uInt>(data.size());
|
||||
|
||||
Vec<u8> out_buffer;
|
||||
out_buffer.resize(deflateBound(&zs, static_cast<uLong>(data.size())));
|
||||
|
||||
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data());
|
||||
zs.avail_out = static_cast<uInt>(out_buffer.size());
|
||||
|
||||
const int ret = deflate(&zs, Z_FINISH);
|
||||
|
||||
if (ret != Z_STREAM_END) {
|
||||
deflateEnd(&zs);
|
||||
return fail("Failed to deflate, ran out of buffer memory");
|
||||
}
|
||||
|
||||
out_buffer.resize(zs.total_out);
|
||||
|
||||
deflateEnd(&zs);
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
auto DataOps::zstd_inflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||
const unsigned long long content_size =
|
||||
ZSTD_getFrameContentSize(data.data(), data.size());
|
||||
|
||||
if (content_size == ZSTD_CONTENTSIZE_ERROR) {
|
||||
return fail("Failed to inflate: Not valid ZSTD compressed data");
|
||||
}
|
||||
|
||||
if (content_size != ZSTD_CONTENTSIZE_UNKNOWN) {
|
||||
// FAST PATH: We know the size
|
||||
Vec<u8> out_buffer;
|
||||
out_buffer.resize(static_cast<usize>(content_size));
|
||||
|
||||
const usize d_size = ZSTD_decompress(out_buffer.data(), out_buffer.size(),
|
||||
data.data(), data.size());
|
||||
|
||||
if (ZSTD_isError(d_size)) {
|
||||
return fail("Failed to inflate: {}", ZSTD_getErrorName(d_size));
|
||||
}
|
||||
|
||||
UINT32 DataOps::Hash_xxHash(IN Span<CONST UINT8> data, IN UINT32 seed)
|
||||
{
|
||||
CONST UINT8 *p = data.data();
|
||||
CONST UINT8 *CONST bEnd = p + data.size();
|
||||
UINT32 h32{};
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
if (data.size() >= 16)
|
||||
{
|
||||
const UINT8 *const limit = bEnd - 16;
|
||||
ZSTD_DCtx *dctx = ZSTD_createDCtx();
|
||||
Vec<u8> out_buffer;
|
||||
out_buffer.resize(data.size() * 2);
|
||||
|
||||
UINT32 v1 = seed + XXH_PRIME32_1 + XXH_PRIME32_2;
|
||||
UINT32 v2 = seed + XXH_PRIME32_2;
|
||||
UINT32 v3 = seed + 0;
|
||||
UINT32 v4 = seed - XXH_PRIME32_1;
|
||||
ZSTD_inBuffer input = {data.data(), data.size(), 0};
|
||||
ZSTD_outBuffer output = {out_buffer.data(), out_buffer.size(), 0};
|
||||
|
||||
do
|
||||
{
|
||||
v1 = XXH32_Round(v1, ReadUnaligned<UINT32>(p));
|
||||
p += 4;
|
||||
v2 = XXH32_Round(v2, ReadUnaligned<UINT32>(p));
|
||||
p += 4;
|
||||
v3 = XXH32_Round(v3, ReadUnaligned<UINT32>(p));
|
||||
p += 4;
|
||||
v4 = XXH32_Round(v4, ReadUnaligned<UINT32>(p));
|
||||
p += 4;
|
||||
} while (p <= limit);
|
||||
usize ret;
|
||||
do {
|
||||
ret = ZSTD_decompressStream(dctx, &output, &input);
|
||||
|
||||
h32 = std::rotl(v1, 1) + std::rotl(v2, 7) + std::rotl(v3, 12) + std::rotl(v4, 18);
|
||||
}
|
||||
else
|
||||
h32 = seed + XXH_PRIME32_5;
|
||||
|
||||
h32 += (UINT32) data.size();
|
||||
|
||||
while (p + 4 <= bEnd)
|
||||
{
|
||||
const auto t = ReadUnaligned<UINT32>(p) * XXH_PRIME32_3;
|
||||
h32 += t;
|
||||
h32 = std::rotl(h32, 17) * XXH_PRIME32_4;
|
||||
p += 4;
|
||||
}
|
||||
|
||||
while (p < bEnd)
|
||||
{
|
||||
h32 += (*p++) * XXH_PRIME32_5;
|
||||
h32 = std::rotl(h32, 11) * XXH_PRIME32_1;
|
||||
}
|
||||
|
||||
h32 ^= h32 >> 15;
|
||||
h32 *= XXH_PRIME32_2;
|
||||
h32 ^= h32 >> 13;
|
||||
h32 *= XXH_PRIME32_3;
|
||||
h32 ^= h32 >> 16;
|
||||
|
||||
return h32;
|
||||
}
|
||||
} // namespace IACore
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
// FNV-1a 32-bit Constants
|
||||
CONSTEXPR UINT32 FNV1A_32_PRIME = 0x01000193;
|
||||
CONSTEXPR UINT32 FNV1A_32_OFFSET = 0x811c9dc5;
|
||||
|
||||
UINT32 DataOps::Hash_FNV1A(IN CONST String &string)
|
||||
{
|
||||
UINT32 hash = FNV1A_32_OFFSET;
|
||||
for (char c : string)
|
||||
{
|
||||
hash ^= static_cast<uint8_t>(c);
|
||||
hash *= FNV1A_32_PRIME;
|
||||
}
|
||||
return hash;
|
||||
if (ZSTD_isError(ret)) {
|
||||
ZSTD_freeDCtx(dctx);
|
||||
return fail("Failed to inflate: {}", ZSTD_getErrorName(ret));
|
||||
}
|
||||
|
||||
UINT32 DataOps::Hash_FNV1A(IN Span<CONST UINT8> data)
|
||||
{
|
||||
UINT32 hash = FNV1A_32_OFFSET;
|
||||
const uint8_t *ptr = static_cast<const uint8_t *>(data.data());
|
||||
|
||||
for (size_t i = 0; i < data.size(); ++i)
|
||||
{
|
||||
hash ^= ptr[i];
|
||||
hash *= FNV1A_32_PRIME;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
} // namespace IACore
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
DataOps::CompressionType DataOps::DetectCompression(IN Span<CONST UINT8> data)
|
||||
{
|
||||
if (data.size() < 2)
|
||||
return CompressionType::None;
|
||||
|
||||
// Check for GZIP Magic Number (0x1F 0x8B)
|
||||
if (data[0] == 0x1F && data[1] == 0x8B)
|
||||
return CompressionType::Gzip;
|
||||
|
||||
// Check for ZLIB Magic Number (starts with 0x78)
|
||||
// 0x78 = Deflate compression with 32k window size
|
||||
if (data[0] == 0x78 && (data[1] == 0x01 || data[1] == 0x9C || data[1] == 0xDA))
|
||||
return CompressionType::Zlib;
|
||||
|
||||
return CompressionType::None;
|
||||
if (output.pos == output.size) {
|
||||
const usize new_size = out_buffer.size() * 2;
|
||||
out_buffer.resize(new_size);
|
||||
output.dst = out_buffer.data();
|
||||
output.size = new_size;
|
||||
}
|
||||
|
||||
EXPECT(Vector<UINT8>) DataOps::ZlibInflate(IN Span<CONST UINT8> data)
|
||||
{
|
||||
z_stream zs{};
|
||||
zs.zalloc = Z_NULL;
|
||||
zs.zfree = Z_NULL;
|
||||
zs.opaque = Z_NULL;
|
||||
} while (ret != 0);
|
||||
|
||||
// 15 + 32 = Auto-detect Gzip or Zlib
|
||||
if (inflateInit2(&zs, 15 + 32) != Z_OK)
|
||||
return MakeUnexpected("Failed to initialize zlib inflate");
|
||||
out_buffer.resize(output.pos);
|
||||
ZSTD_freeDCtx(dctx);
|
||||
|
||||
zs.next_in = const_cast<Bytef *>(data.data());
|
||||
zs.avail_in = static_cast<uInt>(data.size());
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
Vector<UINT8> outBuffer;
|
||||
// Start with 2x input size.
|
||||
size_t guessSize = data.size() < 1024 ? data.size() * 4 : data.size() * 2;
|
||||
outBuffer.resize(guessSize);
|
||||
auto DataOps::zstd_deflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||
const usize max_dst_size = ZSTD_compressBound(data.size());
|
||||
|
||||
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data());
|
||||
zs.avail_out = static_cast<uInt>(outBuffer.size());
|
||||
Vec<u8> out_buffer;
|
||||
out_buffer.resize(max_dst_size);
|
||||
|
||||
int ret;
|
||||
do
|
||||
{
|
||||
if (zs.avail_out == 0)
|
||||
{
|
||||
size_t currentPos = zs.total_out;
|
||||
const usize compressed_size = ZSTD_compress(out_buffer.data(), max_dst_size,
|
||||
data.data(), data.size(), 3);
|
||||
|
||||
size_t newSize = outBuffer.size() * 2;
|
||||
outBuffer.resize(newSize);
|
||||
if (ZSTD_isError(compressed_size)) {
|
||||
return fail("Failed to deflate: {}", ZSTD_getErrorName(compressed_size));
|
||||
}
|
||||
|
||||
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data() + currentPos);
|
||||
out_buffer.resize(compressed_size);
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
zs.avail_out = static_cast<uInt>(newSize - currentPos);
|
||||
}
|
||||
auto DataOps::gzip_deflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||
z_stream zs{};
|
||||
zs.zalloc = Z_NULL;
|
||||
zs.zfree = Z_NULL;
|
||||
zs.opaque = Z_NULL;
|
||||
|
||||
ret = inflate(&zs, Z_NO_FLUSH);
|
||||
// WindowBits = 15 + 16 (31) = Enforce GZIP encoding
|
||||
// MemLevel = 8 (default)
|
||||
// Strategy = Z_DEFAULT_STRATEGY
|
||||
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8,
|
||||
Z_DEFAULT_STRATEGY) != Z_OK) {
|
||||
return fail("Failed to initialize gzip deflate");
|
||||
}
|
||||
|
||||
} while (ret == Z_OK);
|
||||
zs.next_in = const_cast<Bytef *>(data.data());
|
||||
zs.avail_in = static_cast<uInt>(data.size());
|
||||
|
||||
inflateEnd(&zs);
|
||||
Vec<u8> out_buffer;
|
||||
|
||||
if (ret != Z_STREAM_END)
|
||||
return MakeUnexpected("Failed to inflate: corrupt data or stream error");
|
||||
out_buffer.resize(deflateBound(&zs, static_cast<uLong>(data.size())) +
|
||||
1024); // Additional 1KB buffer for safety
|
||||
|
||||
outBuffer.resize(zs.total_out);
|
||||
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data());
|
||||
zs.avail_out = static_cast<uInt>(out_buffer.size());
|
||||
|
||||
return outBuffer;
|
||||
}
|
||||
const int ret = deflate(&zs, Z_FINISH);
|
||||
|
||||
EXPECT(Vector<UINT8>) DataOps::ZlibDeflate(IN Span<CONST UINT8> data)
|
||||
{
|
||||
z_stream zs{};
|
||||
zs.zalloc = Z_NULL;
|
||||
zs.zfree = Z_NULL;
|
||||
zs.opaque = Z_NULL;
|
||||
if (ret != Z_STREAM_END) {
|
||||
deflateEnd(&zs);
|
||||
return fail("Failed to deflate");
|
||||
}
|
||||
|
||||
if (deflateInit(&zs, Z_DEFAULT_COMPRESSION) != Z_OK)
|
||||
return MakeUnexpected("Failed to initialize zlib deflate");
|
||||
out_buffer.resize(zs.total_out);
|
||||
|
||||
zs.next_in = const_cast<Bytef *>(data.data());
|
||||
zs.avail_in = static_cast<uInt>(data.size());
|
||||
deflateEnd(&zs);
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
Vector<UINT8> outBuffer;
|
||||
auto DataOps::gzip_inflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||
return zlib_inflate(data);
|
||||
}
|
||||
|
||||
outBuffer.resize(deflateBound(&zs, data.size()));
|
||||
|
||||
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data());
|
||||
zs.avail_out = static_cast<uInt>(outBuffer.size());
|
||||
|
||||
int ret = deflate(&zs, Z_FINISH);
|
||||
|
||||
if (ret != Z_STREAM_END)
|
||||
{
|
||||
deflateEnd(&zs);
|
||||
return MakeUnexpected("Failed to deflate, ran out of buffer memory");
|
||||
}
|
||||
|
||||
outBuffer.resize(zs.total_out);
|
||||
|
||||
deflateEnd(&zs);
|
||||
return outBuffer;
|
||||
}
|
||||
|
||||
EXPECT(Vector<UINT8>) DataOps::ZstdInflate(IN Span<CONST UINT8> data)
|
||||
{
|
||||
unsigned long long const contentSize = ZSTD_getFrameContentSize(data.data(), data.size());
|
||||
|
||||
if (contentSize == ZSTD_CONTENTSIZE_ERROR)
|
||||
return MakeUnexpected("Failed to inflate: Not valid ZSTD compressed data");
|
||||
|
||||
if (contentSize != ZSTD_CONTENTSIZE_UNKNOWN)
|
||||
{
|
||||
// FAST PATH: We know the size
|
||||
Vector<UINT8> outBuffer;
|
||||
outBuffer.resize(static_cast<size_t>(contentSize));
|
||||
|
||||
size_t const dSize = ZSTD_decompress(outBuffer.data(), outBuffer.size(), data.data(), data.size());
|
||||
|
||||
if (ZSTD_isError(dSize))
|
||||
return MakeUnexpected(std::format("Failed to inflate: {}", ZSTD_getErrorName(dSize)));
|
||||
|
||||
return outBuffer;
|
||||
}
|
||||
|
||||
ZSTD_DCtx *dctx = ZSTD_createDCtx();
|
||||
Vector<UINT8> outBuffer;
|
||||
outBuffer.resize(data.size() * 2);
|
||||
|
||||
ZSTD_inBuffer input = {data.data(), data.size(), 0};
|
||||
ZSTD_outBuffer output = {outBuffer.data(), outBuffer.size(), 0};
|
||||
|
||||
size_t ret;
|
||||
do
|
||||
{
|
||||
ret = ZSTD_decompressStream(dctx, &output, &input);
|
||||
|
||||
if (ZSTD_isError(ret))
|
||||
{
|
||||
ZSTD_freeDCtx(dctx);
|
||||
return MakeUnexpected(std::format("Failed to inflate: {}", ZSTD_getErrorName(ret)));
|
||||
}
|
||||
|
||||
if (output.pos == output.size)
|
||||
{
|
||||
size_t newSize = outBuffer.size() * 2;
|
||||
outBuffer.resize(newSize);
|
||||
output.dst = outBuffer.data();
|
||||
output.size = newSize;
|
||||
}
|
||||
|
||||
} while (ret != 0);
|
||||
|
||||
outBuffer.resize(output.pos);
|
||||
ZSTD_freeDCtx(dctx);
|
||||
|
||||
return outBuffer;
|
||||
}
|
||||
|
||||
EXPECT(Vector<UINT8>) DataOps::ZstdDeflate(IN Span<CONST UINT8> data)
|
||||
{
|
||||
size_t const maxDstSize = ZSTD_compressBound(data.size());
|
||||
|
||||
Vector<UINT8> outBuffer;
|
||||
outBuffer.resize(maxDstSize);
|
||||
|
||||
size_t const compressedSize = ZSTD_compress(outBuffer.data(), maxDstSize, data.data(), data.size(), 3);
|
||||
|
||||
if (ZSTD_isError(compressedSize))
|
||||
return MakeUnexpected(std::format("Failed to deflate: {}", ZSTD_getErrorName(compressedSize)));
|
||||
|
||||
outBuffer.resize(compressedSize);
|
||||
return outBuffer;
|
||||
}
|
||||
|
||||
EXPECT(Vector<UINT8>) DataOps::GZipDeflate(IN Span<CONST UINT8> data)
|
||||
{
|
||||
z_stream zs{};
|
||||
zs.zalloc = Z_NULL;
|
||||
zs.zfree = Z_NULL;
|
||||
zs.opaque = Z_NULL;
|
||||
|
||||
// WindowBits = 15 + 16 (31) = Enforce GZIP encoding
|
||||
// MemLevel = 8 (default)
|
||||
// Strategy = Z_DEFAULT_STRATEGY
|
||||
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK)
|
||||
return MakeUnexpected("Failed to initialize gzip deflate");
|
||||
|
||||
zs.next_in = const_cast<Bytef *>(data.data());
|
||||
zs.avail_in = static_cast<uInt>(data.size());
|
||||
|
||||
Vector<UINT8> outBuffer;
|
||||
|
||||
outBuffer.resize(deflateBound(&zs, data.size()) + 1024); // Additional 1KB buffer for safety
|
||||
|
||||
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data());
|
||||
zs.avail_out = static_cast<uInt>(outBuffer.size());
|
||||
|
||||
int ret = deflate(&zs, Z_FINISH);
|
||||
|
||||
if (ret != Z_STREAM_END)
|
||||
{
|
||||
deflateEnd(&zs);
|
||||
return MakeUnexpected("Failed to deflate");
|
||||
}
|
||||
|
||||
outBuffer.resize(zs.total_out);
|
||||
|
||||
deflateEnd(&zs);
|
||||
return outBuffer;
|
||||
}
|
||||
|
||||
EXPECT(Vector<UINT8>) DataOps::GZipInflate(IN Span<CONST UINT8> data)
|
||||
{
|
||||
return ZlibInflate(data);
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -14,501 +14,523 @@
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/FileOps.hpp>
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
UnorderedMap<PCUINT8, Tuple<PVOID, PVOID, PVOID>> FileOps::s_mappedFiles;
|
||||
|
||||
VOID FileOps::UnmapFile(IN PCUINT8 mappedPtr)
|
||||
{
|
||||
if (!s_mappedFiles.contains(mappedPtr))
|
||||
return;
|
||||
const auto handles = s_mappedFiles.extract(mappedPtr)->second;
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
::UnmapViewOfFile(std::get<1>(handles));
|
||||
::CloseHandle(std::get<2>(handles));
|
||||
|
||||
if (std::get<0>(handles) != INVALID_HANDLE_VALUE)
|
||||
::CloseHandle(std::get<0>(handles));
|
||||
#elif IA_PLATFORM_UNIX
|
||||
::munmap(std::get<1>(handles), (SIZE_T) std::get<2>(handles));
|
||||
const auto fd = (INT32) ((UINT64) std::get<0>(handles));
|
||||
if (fd != -1)
|
||||
::close(fd);
|
||||
#endif
|
||||
}
|
||||
|
||||
EXPECT(PUINT8) FileOps::MapSharedMemory(IN CONST String &name, IN SIZE_T size, IN BOOL isOwner)
|
||||
{
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
int wchars_num = MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, NULL, 0);
|
||||
std::wstring wName(wchars_num, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, &wName[0], wchars_num);
|
||||
|
||||
HANDLE hMap = NULL;
|
||||
if (isOwner)
|
||||
hMap = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, (DWORD) (size >> 32),
|
||||
(DWORD) (size & 0xFFFFFFFF), wName.c_str());
|
||||
else
|
||||
hMap = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, wName.c_str());
|
||||
|
||||
if (hMap == NULL)
|
||||
return MakeUnexpected(
|
||||
std::format("Failed to {} shared memory '{}'", isOwner ? "owner" : "consumer", name.c_str()));
|
||||
|
||||
const auto result = static_cast<PUINT8>(MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, size));
|
||||
if (result == NULL)
|
||||
{
|
||||
CloseHandle(hMap);
|
||||
return MakeUnexpected(std::format("Failed to map view of shared memory '{}'", name.c_str()));
|
||||
}
|
||||
|
||||
s_mappedFiles[result] = std::make_tuple((PVOID) INVALID_HANDLE_VALUE, (PVOID) result, (PVOID) hMap);
|
||||
return result;
|
||||
|
||||
#elif IA_PLATFORM_UNIX
|
||||
int fd = -1;
|
||||
if (isOwner)
|
||||
{
|
||||
fd = shm_open(name.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0666);
|
||||
if (fd != -1)
|
||||
{
|
||||
if (ftruncate(fd, size) == -1)
|
||||
{
|
||||
close(fd);
|
||||
shm_unlink(name.c_str());
|
||||
return MakeUnexpected(std::format("Failed to truncate shared memory '{}'", name.c_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
fd = shm_open(name.c_str(), O_RDWR, 0666);
|
||||
|
||||
if (fd == -1)
|
||||
return MakeUnexpected(
|
||||
std::format("Failed to {} shared memory '{}'", isOwner ? "owner" : "consumer", name.c_str()));
|
||||
|
||||
void *addr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (addr == MAP_FAILED)
|
||||
{
|
||||
close(fd);
|
||||
return MakeUnexpected(std::format("Failed to mmap shared memory '{}'", name.c_str()));
|
||||
}
|
||||
|
||||
const auto result = static_cast<PUINT8>(addr);
|
||||
|
||||
s_mappedFiles[result] = std::make_tuple((PVOID) ((UINT64) fd), (PVOID) addr, (PVOID) size);
|
||||
return result;
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
VOID FileOps::UnlinkSharedMemory(IN CONST String &name)
|
||||
{
|
||||
if (name.empty())
|
||||
return;
|
||||
#if IA_PLATFORM_UNIX
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace IACore {
|
||||
|
||||
HashMap<const u8 *, std::tuple<void *, void *, void *>> FileOps::s_mapped_files;
|
||||
|
||||
auto FileOps::unmap_file(const u8 *mapped_ptr) -> void {
|
||||
if (!s_mapped_files.contains(mapped_ptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = s_mapped_files.find(mapped_ptr);
|
||||
const auto handles = it->second;
|
||||
s_mapped_files.erase(it);
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
::UnmapViewOfFile(std::get<1>(handles));
|
||||
::CloseHandle(static_cast<HANDLE>(std::get<2>(handles)));
|
||||
|
||||
const auto handle = static_cast<HANDLE>(std::get<0>(handles));
|
||||
if (handle != INVALID_HANDLE_VALUE) {
|
||||
::CloseHandle(handle);
|
||||
}
|
||||
#elif IA_PLATFORM_UNIX
|
||||
::munmap(std::get<1>(handles), (usize)std::get<2>(handles));
|
||||
const auto fd = (i32)((u64)std::get<0>(handles));
|
||||
if (fd != -1) {
|
||||
::close(fd);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
auto FileOps::map_shared_memory(const String &name, usize size, bool is_owner)
|
||||
-> Result<u8 *> {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
const int wchars_num =
|
||||
MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, NULL, 0);
|
||||
std::wstring w_name(wchars_num, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, &w_name[0], wchars_num);
|
||||
|
||||
HANDLE h_map = NULL;
|
||||
if (is_owner) {
|
||||
h_map = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
|
||||
(DWORD)(size >> 32), (DWORD)(size & 0xFFFFFFFF),
|
||||
w_name.c_str());
|
||||
} else {
|
||||
h_map = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, w_name.c_str());
|
||||
}
|
||||
|
||||
if (h_map == NULL) {
|
||||
return fail("Failed to {} shared memory '{}'",
|
||||
is_owner ? "owner" : "consumer", name);
|
||||
}
|
||||
|
||||
auto *result =
|
||||
static_cast<u8 *>(MapViewOfFile(h_map, FILE_MAP_ALL_ACCESS, 0, 0, size));
|
||||
if (result == NULL) {
|
||||
CloseHandle(h_map);
|
||||
return fail("Failed to map view of shared memory '{}'", name);
|
||||
}
|
||||
|
||||
s_mapped_files[result] = std::make_tuple((void *)INVALID_HANDLE_VALUE,
|
||||
(void *)result, (void *)h_map);
|
||||
return result;
|
||||
|
||||
#elif IA_PLATFORM_UNIX
|
||||
int fd = -1;
|
||||
if (is_owner) {
|
||||
fd = shm_open(name.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0666);
|
||||
if (fd != -1) {
|
||||
if (ftruncate(fd, size) == -1) {
|
||||
close(fd);
|
||||
shm_unlink(name.c_str());
|
||||
#endif
|
||||
return fail("Failed to truncate shared memory '{}'", name);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fd = shm_open(name.c_str(), O_RDWR, 0666);
|
||||
}
|
||||
|
||||
EXPECT(PCUINT8) FileOps::MapFile(IN CONST FilePath &path, OUT SIZE_T &size)
|
||||
{
|
||||
if (fd == -1) {
|
||||
return fail("Failed to {} shared memory '{}'",
|
||||
is_owner ? "owner" : "consumer", name);
|
||||
}
|
||||
|
||||
void *addr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (addr == MAP_FAILED) {
|
||||
close(fd);
|
||||
return fail("Failed to mmap shared memory '{}'", name);
|
||||
}
|
||||
|
||||
auto *result = static_cast<u8 *>(addr);
|
||||
|
||||
s_mapped_files[result] =
|
||||
std::make_tuple((void *)((u64)fd), (void *)addr, (void *)size);
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
auto FileOps::unlink_shared_memory(const String &name) -> void {
|
||||
if (name.empty()) {
|
||||
return;
|
||||
}
|
||||
#if IA_PLATFORM_UNIX
|
||||
shm_unlink(name.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
auto FileOps::map_file(const Path &path, usize &size) -> Result<const u8 *> {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
const auto handle = CreateFileA(
|
||||
path.string().c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
|
||||
|
||||
const auto handle = CreateFileA(path.string().c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
return fail("Failed to open {} for memory mapping", path.string());
|
||||
}
|
||||
|
||||
if (handle == INVALID_HANDLE_VALUE)
|
||||
return MakeUnexpected(std::format("Failed to open {} for memory mapping", path.string().c_str()));
|
||||
LARGE_INTEGER file_size;
|
||||
if (!GetFileSizeEx(handle, &file_size)) {
|
||||
CloseHandle(handle);
|
||||
return fail("Failed to get size of {} for memory mapping", path.string());
|
||||
}
|
||||
size = static_cast<usize>(file_size.QuadPart);
|
||||
if (size == 0) {
|
||||
CloseHandle(handle);
|
||||
return fail("Failed to get size of {} for memory mapping", path.string());
|
||||
}
|
||||
|
||||
LARGE_INTEGER fileSize;
|
||||
if (!GetFileSizeEx(handle, &fileSize))
|
||||
{
|
||||
CloseHandle(handle);
|
||||
return MakeUnexpected(std::format("Failed to get size of {} for memory mapping", path.string().c_str()));
|
||||
}
|
||||
size = static_cast<size_t>(fileSize.QuadPart);
|
||||
if (size == 0)
|
||||
{
|
||||
CloseHandle(handle);
|
||||
return MakeUnexpected(std::format("Failed to get size of {} for memory mapping", path.string().c_str()));
|
||||
}
|
||||
auto h_map = CreateFileMappingW(handle, NULL, PAGE_READONLY, 0, 0, NULL);
|
||||
if (h_map == NULL) {
|
||||
CloseHandle(handle);
|
||||
return fail("Failed to memory map {}", path.string());
|
||||
}
|
||||
|
||||
auto hmap = CreateFileMappingW(handle, NULL, PAGE_READONLY, 0, 0, NULL);
|
||||
if (hmap == NULL)
|
||||
{
|
||||
CloseHandle(handle);
|
||||
return MakeUnexpected(std::format("Failed to memory map {}", path.string().c_str()));
|
||||
}
|
||||
|
||||
const auto result = static_cast<PCUINT8>(MapViewOfFile(hmap, FILE_MAP_READ, 0, 0, 0));
|
||||
if (result == NULL)
|
||||
{
|
||||
CloseHandle(handle);
|
||||
CloseHandle(hmap);
|
||||
return MakeUnexpected(std::format("Failed to memory map {}", path.string().c_str()));
|
||||
}
|
||||
s_mappedFiles[result] = std::make_tuple((PVOID) handle, (PVOID) result, (PVOID) hmap);
|
||||
return result;
|
||||
const auto *result =
|
||||
static_cast<const u8 *>(MapViewOfFile(h_map, FILE_MAP_READ, 0, 0, 0));
|
||||
if (result == NULL) {
|
||||
CloseHandle(handle);
|
||||
CloseHandle(h_map);
|
||||
return fail("Failed to memory map {}", path.string());
|
||||
}
|
||||
s_mapped_files[result] = std::make_tuple(
|
||||
(void *)handle, (void *)const_cast<u8 *>(result), (void *)h_map);
|
||||
return result;
|
||||
|
||||
#elif IA_PLATFORM_UNIX
|
||||
|
||||
const auto handle = open(path.string().c_str(), O_RDONLY);
|
||||
if (handle == -1)
|
||||
return MakeUnexpected(std::format("Failed to open {} for memory mapping", path.string().c_str()));
|
||||
struct stat sb;
|
||||
if (fstat(handle, &sb) == -1)
|
||||
{
|
||||
close(handle);
|
||||
return MakeUnexpected(std::format("Failed to get stats of {} for memory mapping", path.string().c_str()));
|
||||
}
|
||||
size = static_cast<size_t>(sb.st_size);
|
||||
if (size == 0)
|
||||
{
|
||||
close(handle);
|
||||
return MakeUnexpected(std::format("Failed to get size of {} for memory mapping", path.string().c_str()));
|
||||
}
|
||||
void *addr = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, handle, 0);
|
||||
if (addr == MAP_FAILED)
|
||||
{
|
||||
close(handle);
|
||||
return MakeUnexpected(std::format("Failed to memory map {}", path.string().c_str()));
|
||||
}
|
||||
const auto result = static_cast<PCUINT8>(addr);
|
||||
madvise(addr, size, MADV_SEQUENTIAL);
|
||||
s_mappedFiles[result] = std::make_tuple((PVOID) ((UINT64) handle), (PVOID) addr, (PVOID) size);
|
||||
return result;
|
||||
const auto handle = open(path.string().c_str(), O_RDONLY);
|
||||
if (handle == -1) {
|
||||
return fail("Failed to open {} for memory mapping", path.string());
|
||||
}
|
||||
struct stat sb;
|
||||
if (fstat(handle, &sb) == -1) {
|
||||
close(handle);
|
||||
return fail("Failed to get stats of {} for memory mapping", path.string());
|
||||
}
|
||||
size = static_cast<usize>(sb.st_size);
|
||||
if (size == 0) {
|
||||
close(handle);
|
||||
return fail("Failed to get size of {} for memory mapping", path.string());
|
||||
}
|
||||
void *addr = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, handle, 0);
|
||||
if (addr == MAP_FAILED) {
|
||||
close(handle);
|
||||
return fail("Failed to memory map {}", path.string());
|
||||
}
|
||||
const auto *result = static_cast<const u8 *>(addr);
|
||||
madvise(addr, size, MADV_SEQUENTIAL);
|
||||
s_mapped_files[result] =
|
||||
std::make_tuple((void *)((u64)handle), (void *)addr, (void *)size);
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT(StreamWriter) FileOps::StreamToFile(IN CONST FilePath &path, IN BOOL overwrite)
|
||||
{
|
||||
if (!overwrite && FileSystem::exists(path))
|
||||
return MakeUnexpected(std::format("File aready exists: {}", path.string().c_str()));
|
||||
return StreamWriter(path);
|
||||
}
|
||||
auto FileOps::stream_to_file(const Path &path, bool overwrite)
|
||||
-> Result<StreamWriter> {
|
||||
if (!overwrite && std::filesystem::exists(path)) {
|
||||
return fail("File already exists: {}", path.string());
|
||||
}
|
||||
return StreamWriter::create(path);
|
||||
}
|
||||
|
||||
EXPECT(StreamReader) FileOps::StreamFromFile(IN CONST FilePath &path)
|
||||
{
|
||||
if (!FileSystem::exists(path))
|
||||
return MakeUnexpected(std::format("File does not exist: {}", path.string().c_str()));
|
||||
return StreamReader(path);
|
||||
}
|
||||
auto FileOps::stream_from_file(const Path &path) -> Result<StreamReader> {
|
||||
if (!std::filesystem::exists(path)) {
|
||||
return fail("File does not exist: {}", path.string());
|
||||
}
|
||||
return StreamReader::create_from_file(path);
|
||||
}
|
||||
|
||||
EXPECT(String) FileOps::ReadTextFile(IN CONST FilePath &path)
|
||||
{
|
||||
const auto f = fopen(path.string().c_str(), "r");
|
||||
if (!f)
|
||||
return MakeUnexpected(std::format("Failed to open file: {}", path.string().c_str()));
|
||||
String result;
|
||||
fseek(f, 0, SEEK_END);
|
||||
result.resize(ftell(f));
|
||||
fseek(f, 0, SEEK_SET);
|
||||
fread(result.data(), 1, result.size(), f);
|
||||
fclose(f);
|
||||
return result;
|
||||
}
|
||||
auto FileOps::read_text_file(const Path &path) -> Result<String> {
|
||||
auto *f = fopen(path.string().c_str(), "r");
|
||||
if (!f) {
|
||||
return fail("Failed to open file: {}", path.string());
|
||||
}
|
||||
String result;
|
||||
fseek(f, 0, SEEK_END);
|
||||
const long len = ftell(f);
|
||||
if (len > 0) {
|
||||
result.resize(static_cast<usize>(len));
|
||||
fseek(f, 0, SEEK_SET);
|
||||
fread(result.data(), 1, result.size(), f);
|
||||
}
|
||||
fclose(f);
|
||||
return result;
|
||||
}
|
||||
|
||||
EXPECT(Vector<UINT8>) FileOps::ReadBinaryFile(IN CONST FilePath &path)
|
||||
{
|
||||
const auto f = fopen(path.string().c_str(), "rb");
|
||||
if (!f)
|
||||
return MakeUnexpected(std::format("Failed to open file: {}", path.string().c_str()));
|
||||
Vector<UINT8> result;
|
||||
fseek(f, 0, SEEK_END);
|
||||
result.resize(ftell(f));
|
||||
fseek(f, 0, SEEK_SET);
|
||||
fread(result.data(), 1, result.size(), f);
|
||||
fclose(f);
|
||||
return result;
|
||||
}
|
||||
auto FileOps::read_binary_file(const Path &path) -> Result<Vec<u8>> {
|
||||
auto *f = fopen(path.string().c_str(), "rb");
|
||||
if (!f) {
|
||||
return fail("Failed to open file: {}", path.string());
|
||||
}
|
||||
Vec<u8> result;
|
||||
fseek(f, 0, SEEK_END);
|
||||
const long len = ftell(f);
|
||||
if (len > 0) {
|
||||
result.resize(static_cast<usize>(len));
|
||||
fseek(f, 0, SEEK_SET);
|
||||
fread(result.data(), 1, result.size(), f);
|
||||
}
|
||||
fclose(f);
|
||||
return result;
|
||||
}
|
||||
|
||||
EXPECT(SIZE_T) FileOps::WriteTextFile(IN CONST FilePath &path, IN CONST String &contents, IN BOOL overwrite)
|
||||
{
|
||||
const char *mode = overwrite ? "w" : "wx";
|
||||
const auto f = fopen(path.string().c_str(), mode);
|
||||
if (!f)
|
||||
{
|
||||
if (!overwrite && errno == EEXIST)
|
||||
return MakeUnexpected(std::format("File already exists: {}", path.string().c_str()));
|
||||
return MakeUnexpected(std::format("Failed to write to file: {}", path.string().c_str()));
|
||||
}
|
||||
const auto result = fwrite(contents.data(), 1, contents.size(), f);
|
||||
fclose(f);
|
||||
return result;
|
||||
auto FileOps::write_text_file(const Path &path, const String &contents,
|
||||
bool overwrite) -> Result<usize> {
|
||||
const char *mode = overwrite ? "w" : "wx";
|
||||
auto *f = fopen(path.string().c_str(), mode);
|
||||
if (!f) {
|
||||
if (!overwrite && errno == EEXIST) {
|
||||
return fail("File already exists: {}", path.string());
|
||||
}
|
||||
return fail("Failed to write to file: {}", path.string());
|
||||
}
|
||||
const auto result = fwrite(contents.data(), 1, contents.size(), f);
|
||||
fclose(f);
|
||||
return result;
|
||||
}
|
||||
|
||||
EXPECT(SIZE_T) FileOps::WriteBinaryFile(IN CONST FilePath &path, IN Span<UINT8> contents, IN BOOL overwrite)
|
||||
{
|
||||
const char *mode = overwrite ? "w" : "wx";
|
||||
const auto f = fopen(path.string().c_str(), mode);
|
||||
if (!f)
|
||||
{
|
||||
if (!overwrite && errno == EEXIST)
|
||||
return MakeUnexpected(std::format("File already exists: {}", path.string().c_str()));
|
||||
return MakeUnexpected(std::format("Failed to write to file: {}", path.string().c_str()));
|
||||
}
|
||||
const auto result = fwrite(contents.data(), 1, contents.size(), f);
|
||||
fclose(f);
|
||||
return result;
|
||||
auto FileOps::write_binary_file(const Path &path, Span<const u8> contents,
|
||||
bool overwrite) -> Result<usize> {
|
||||
const char *mode = overwrite ? "w" : "wx";
|
||||
auto *f = fopen(path.string().c_str(), mode);
|
||||
if (!f) {
|
||||
if (!overwrite && errno == EEXIST) {
|
||||
return fail("File already exists: {}", path.string());
|
||||
}
|
||||
return fail("Failed to write to file: {}", path.string());
|
||||
}
|
||||
const auto result = fwrite(contents.data(), 1, contents.size(), f);
|
||||
fclose(f);
|
||||
return result;
|
||||
}
|
||||
|
||||
FilePath FileOps::NormalizeExecutablePath(IN CONST FilePath &path)
|
||||
{
|
||||
FilePath result = path;
|
||||
auto FileOps::normalize_executable_path(const Path &path) -> Path {
|
||||
Path result = path;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
if (!result.has_extension())
|
||||
result.replace_extension(".exe");
|
||||
if (!result.has_extension()) {
|
||||
result.replace_extension(".exe");
|
||||
}
|
||||
#elif IA_PLATFORM_UNIX
|
||||
if (result.extension() == ".exe") {
|
||||
result.replace_extension("");
|
||||
}
|
||||
|
||||
if (result.is_relative()) {
|
||||
String path_str = result.string();
|
||||
if (!path_str.starts_with("./") && !path_str.starts_with("../")) {
|
||||
result = "./" + path_str;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
auto FileOps::native_open_file(const Path &path, FileAccess access,
|
||||
FileMode mode, u32 permissions)
|
||||
-> Result<NativeFileHandle> {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
DWORD dw_access = 0;
|
||||
DWORD dw_share = FILE_SHARE_READ;
|
||||
DWORD dw_disposition = 0;
|
||||
DWORD dw_flags_and_attributes = FILE_ATTRIBUTE_NORMAL;
|
||||
|
||||
switch (access) {
|
||||
case FileAccess::Read:
|
||||
dw_access = GENERIC_READ;
|
||||
break;
|
||||
case FileAccess::Write:
|
||||
dw_access = GENERIC_WRITE;
|
||||
break;
|
||||
case FileAccess::ReadWrite:
|
||||
dw_access = GENERIC_READ | GENERIC_WRITE;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case FileMode::OpenExisting:
|
||||
dw_disposition = OPEN_EXISTING;
|
||||
break;
|
||||
case FileMode::OpenAlways:
|
||||
dw_disposition = OPEN_ALWAYS;
|
||||
break;
|
||||
case FileMode::CreateNew:
|
||||
dw_disposition = CREATE_NEW;
|
||||
break;
|
||||
case FileMode::CreateAlways:
|
||||
dw_disposition = CREATE_ALWAYS;
|
||||
break;
|
||||
case FileMode::TruncateExisting:
|
||||
dw_disposition = TRUNCATE_EXISTING;
|
||||
break;
|
||||
}
|
||||
|
||||
HANDLE h_file = CreateFileA(path.string().c_str(), dw_access, dw_share, NULL,
|
||||
dw_disposition, dw_flags_and_attributes, NULL);
|
||||
|
||||
if (h_file == INVALID_HANDLE_VALUE) {
|
||||
return fail("Failed to open file '{}': {}", path.string(), GetLastError());
|
||||
}
|
||||
|
||||
return h_file;
|
||||
|
||||
#elif IA_PLATFORM_UNIX
|
||||
if (result.extension() == ".exe")
|
||||
result.replace_extension("");
|
||||
int flags = 0;
|
||||
|
||||
if (result.is_relative())
|
||||
{
|
||||
String pathStr = result.string();
|
||||
if (!pathStr.starts_with("./") && !pathStr.starts_with("../"))
|
||||
result = "./" + pathStr;
|
||||
}
|
||||
switch (access) {
|
||||
case FileAccess::Read:
|
||||
flags = O_RDONLY;
|
||||
break;
|
||||
case FileAccess::Write:
|
||||
flags = O_WRONLY;
|
||||
break;
|
||||
case FileAccess::ReadWrite:
|
||||
flags = O_RDWR;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case FileMode::OpenExisting:
|
||||
break;
|
||||
case FileMode::OpenAlways:
|
||||
flags |= O_CREAT;
|
||||
break;
|
||||
case FileMode::CreateNew:
|
||||
flags |= O_CREAT | O_EXCL;
|
||||
break;
|
||||
case FileMode::CreateAlways:
|
||||
flags |= O_CREAT | O_TRUNC;
|
||||
break;
|
||||
case FileMode::TruncateExisting:
|
||||
flags |= O_TRUNC;
|
||||
break;
|
||||
}
|
||||
|
||||
int fd = open(path.string().c_str(), flags, permissions);
|
||||
|
||||
if (fd == -1) {
|
||||
return fail("Failed to open file '{}': {}", path.string(), errno);
|
||||
}
|
||||
|
||||
return fd;
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
} // namespace IACore
|
||||
}
|
||||
|
||||
auto FileOps::native_close_file(NativeFileHandle handle) -> void {
|
||||
if (handle == INVALID_FILE_HANDLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
EXPECT(NativeFileHandle)
|
||||
FileOps::NativeOpenFile(IN CONST FilePath &path, IN EFileAccess access, IN EFileMode mode, IN UINT32 permissions)
|
||||
{
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
DWORD dwAccess = 0;
|
||||
DWORD dwShare = FILE_SHARE_READ;
|
||||
DWORD dwDisposition = 0;
|
||||
DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
|
||||
CloseHandle(handle);
|
||||
#elif IA_PLATFORM_UNIX
|
||||
close(handle);
|
||||
#endif
|
||||
}
|
||||
|
||||
switch (access)
|
||||
{
|
||||
case EFileAccess::READ:
|
||||
dwAccess = GENERIC_READ;
|
||||
break;
|
||||
case EFileAccess::WRITE:
|
||||
dwAccess = GENERIC_WRITE;
|
||||
break;
|
||||
case EFileAccess::READ_WRITE:
|
||||
dwAccess = GENERIC_READ | GENERIC_WRITE;
|
||||
break;
|
||||
}
|
||||
// =============================================================================
|
||||
// MemoryMappedRegion
|
||||
// =============================================================================
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case EFileMode::OPEN_EXISTING:
|
||||
dwDisposition = OPEN_EXISTING;
|
||||
break;
|
||||
case EFileMode::OPEN_ALWAYS:
|
||||
dwDisposition = OPEN_ALWAYS;
|
||||
break;
|
||||
case EFileMode::CREATE_NEW:
|
||||
dwDisposition = CREATE_NEW;
|
||||
break;
|
||||
case EFileMode::CREATE_ALWAYS:
|
||||
dwDisposition = CREATE_ALWAYS;
|
||||
break;
|
||||
case EFileMode::TRUNCATE_EXISTING:
|
||||
dwDisposition = TRUNCATE_EXISTING;
|
||||
break;
|
||||
}
|
||||
FileOps::MemoryMappedRegion::~MemoryMappedRegion() { unmap(); }
|
||||
|
||||
HANDLE hFile =
|
||||
CreateFileA(path.string().c_str(), dwAccess, dwShare, NULL, dwDisposition, dwFlagsAndAttributes, NULL);
|
||||
FileOps::MemoryMappedRegion::MemoryMappedRegion(
|
||||
MemoryMappedRegion &&other) noexcept {
|
||||
*this = std::move(other);
|
||||
}
|
||||
|
||||
if (hFile == INVALID_HANDLE_VALUE)
|
||||
return MakeUnexpected(std::format("Failed to open file '{}': {}", path.string(), GetLastError()));
|
||||
auto FileOps::MemoryMappedRegion::operator=(MemoryMappedRegion &&other) noexcept
|
||||
-> MemoryMappedRegion & {
|
||||
if (this != &other) {
|
||||
unmap();
|
||||
m_ptr = other.m_ptr;
|
||||
m_size = other.m_size;
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
m_map_handle = other.m_map_handle;
|
||||
other.m_map_handle = NULL;
|
||||
#endif
|
||||
other.m_ptr = nullptr;
|
||||
other.m_size = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
return hFile;
|
||||
auto FileOps::MemoryMappedRegion::map(NativeFileHandle handle, u64 offset,
|
||||
usize size) -> Result<void> {
|
||||
unmap();
|
||||
|
||||
if (handle == INVALID_FILE_HANDLE) {
|
||||
return fail("Invalid file handle provided to Map");
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
return fail("Cannot map region of size 0");
|
||||
}
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
LARGE_INTEGER file_size;
|
||||
if (!GetFileSizeEx(handle, &file_size)) {
|
||||
return fail("Failed to get file size");
|
||||
}
|
||||
|
||||
u64 end_offset = offset + size;
|
||||
if (static_cast<u64>(file_size.QuadPart) < end_offset) {
|
||||
LARGE_INTEGER new_size;
|
||||
new_size.QuadPart = static_cast<LONGLONG>(end_offset);
|
||||
if (!SetFilePointerEx(handle, new_size, NULL, FILE_BEGIN)) {
|
||||
return fail("Failed to seek to new end of file");
|
||||
}
|
||||
|
||||
if (!SetEndOfFile(handle)) {
|
||||
return fail("Failed to extend file for mapping");
|
||||
}
|
||||
}
|
||||
|
||||
m_map_handle = CreateFileMappingW(handle, NULL, PAGE_READWRITE, 0, 0, NULL);
|
||||
if (m_map_handle == NULL) {
|
||||
return fail("CreateFileMapping failed: {}", GetLastError());
|
||||
}
|
||||
|
||||
DWORD offset_high = static_cast<DWORD>(offset >> 32);
|
||||
DWORD offset_low = static_cast<DWORD>(offset & 0xFFFFFFFF);
|
||||
|
||||
m_ptr = static_cast<u8 *>(MapViewOfFile(m_map_handle, FILE_MAP_WRITE,
|
||||
offset_high, offset_low, size));
|
||||
if (m_ptr == NULL) {
|
||||
CloseHandle(m_map_handle);
|
||||
m_map_handle = NULL;
|
||||
return fail("MapViewOfFile failed (Offset: {}, Size: {}): {}", offset, size,
|
||||
GetLastError());
|
||||
}
|
||||
m_size = size;
|
||||
|
||||
#elif IA_PLATFORM_UNIX
|
||||
int flags = 0;
|
||||
struct stat sb;
|
||||
if (fstat(handle, &sb) == -1) {
|
||||
return fail("Failed to fstat file");
|
||||
}
|
||||
|
||||
switch (access)
|
||||
{
|
||||
case EFileAccess::READ:
|
||||
flags = O_RDONLY;
|
||||
break;
|
||||
case EFileAccess::WRITE:
|
||||
flags = O_WRONLY;
|
||||
break;
|
||||
case EFileAccess::READ_WRITE:
|
||||
flags = O_RDWR;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case EFileMode::OPEN_EXISTING:
|
||||
break;
|
||||
case EFileMode::OPEN_ALWAYS:
|
||||
flags |= O_CREAT;
|
||||
break;
|
||||
case EFileMode::CREATE_NEW:
|
||||
flags |= O_CREAT | O_EXCL;
|
||||
break;
|
||||
case EFileMode::CREATE_ALWAYS:
|
||||
flags |= O_CREAT | O_TRUNC;
|
||||
break;
|
||||
case EFileMode::TRUNCATE_EXISTING:
|
||||
flags |= O_TRUNC;
|
||||
break;
|
||||
}
|
||||
|
||||
int fd = open(path.string().c_str(), flags, permissions);
|
||||
|
||||
if (fd == -1)
|
||||
{
|
||||
return MakeUnexpected(std::format("Failed to open file '{}': {}", path.string(), errno));
|
||||
}
|
||||
|
||||
return fd;
|
||||
#endif
|
||||
u64 end_offset = offset + size;
|
||||
if (static_cast<u64>(sb.st_size) < end_offset) {
|
||||
if (ftruncate(handle, static_cast<off_t>(end_offset)) == -1) {
|
||||
return fail("Failed to ftruncate (extend) file");
|
||||
}
|
||||
}
|
||||
|
||||
VOID FileOps::NativeCloseFile(IN NativeFileHandle handle)
|
||||
{
|
||||
if (handle == INVALID_FILE_HANDLE)
|
||||
return;
|
||||
void *ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, handle,
|
||||
static_cast<off_t>(offset));
|
||||
if (ptr == MAP_FAILED) {
|
||||
return fail("mmap failed: {}", errno);
|
||||
}
|
||||
|
||||
m_ptr = static_cast<u8 *>(ptr);
|
||||
m_size = size;
|
||||
|
||||
madvise(m_ptr, m_size, MADV_SEQUENTIAL);
|
||||
#endif
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto FileOps::MemoryMappedRegion::unmap() -> void {
|
||||
if (!m_ptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
CloseHandle(handle);
|
||||
UnmapViewOfFile(m_ptr);
|
||||
if (m_map_handle) {
|
||||
CloseHandle(m_map_handle);
|
||||
m_map_handle = NULL;
|
||||
}
|
||||
#elif IA_PLATFORM_UNIX
|
||||
close(handle);
|
||||
munmap(m_ptr, m_size);
|
||||
#endif
|
||||
}
|
||||
m_ptr = nullptr;
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
FileOps::MemoryMappedRegion::~MemoryMappedRegion()
|
||||
{
|
||||
Unmap();
|
||||
}
|
||||
|
||||
FileOps::MemoryMappedRegion::MemoryMappedRegion(MemoryMappedRegion &&other) NOEXCEPT
|
||||
{
|
||||
*this = std::move(other);
|
||||
}
|
||||
|
||||
FileOps::MemoryMappedRegion &FileOps::MemoryMappedRegion::operator=(MemoryMappedRegion &&other) NOEXCEPT
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
Unmap();
|
||||
m_ptr = other.m_ptr;
|
||||
m_size = other.m_size;
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
m_hMap = other.m_hMap;
|
||||
other.m_hMap = NULL;
|
||||
#endif
|
||||
other.m_ptr = nullptr;
|
||||
other.m_size = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
EXPECT(VOID) FileOps::MemoryMappedRegion::Map(NativeFileHandle handle, UINT64 offset, SIZE_T size)
|
||||
{
|
||||
Unmap();
|
||||
|
||||
if (handle == INVALID_FILE_HANDLE)
|
||||
return MakeUnexpected("Invalid file handle provided to Map");
|
||||
|
||||
if (size == 0)
|
||||
return MakeUnexpected("Cannot map region of size 0");
|
||||
auto FileOps::MemoryMappedRegion::flush() -> void {
|
||||
if (!m_ptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
LARGE_INTEGER fileSize;
|
||||
if (!GetFileSizeEx(handle, &fileSize))
|
||||
return MakeUnexpected("Failed to get file size");
|
||||
|
||||
UINT64 endOffset = offset + size;
|
||||
if (static_cast<UINT64>(fileSize.QuadPart) < endOffset)
|
||||
{
|
||||
LARGE_INTEGER newSize;
|
||||
newSize.QuadPart = endOffset;
|
||||
if (!SetFilePointerEx(handle, newSize, NULL, FILE_BEGIN))
|
||||
return MakeUnexpected("Failed to seek to new end of file");
|
||||
|
||||
if (!SetEndOfFile(handle))
|
||||
return MakeUnexpected("Failed to extend file for mapping");
|
||||
}
|
||||
|
||||
m_hMap = CreateFileMappingW(handle, NULL, PAGE_READWRITE, 0, 0, NULL);
|
||||
if (m_hMap == NULL)
|
||||
return MakeUnexpected(std::format("CreateFileMapping failed: {}", GetLastError()));
|
||||
|
||||
DWORD offsetHigh = static_cast<DWORD>(offset >> 32);
|
||||
DWORD offsetLow = static_cast<DWORD>(offset & 0xFFFFFFFF);
|
||||
|
||||
m_ptr = static_cast<PUINT8>(MapViewOfFile(m_hMap, FILE_MAP_WRITE, offsetHigh, offsetLow, size));
|
||||
if (m_ptr == NULL)
|
||||
{
|
||||
CloseHandle(m_hMap);
|
||||
m_hMap = NULL;
|
||||
return MakeUnexpected(
|
||||
std::format("MapViewOfFile failed (Offset: {}, Size: {}): {}", offset, size, GetLastError()));
|
||||
}
|
||||
m_size = size;
|
||||
|
||||
FlushViewOfFile(m_ptr, m_size);
|
||||
#elif IA_PLATFORM_UNIX
|
||||
struct stat sb;
|
||||
if (fstat(handle, &sb) == -1)
|
||||
return MakeUnexpected("Failed to fstat file");
|
||||
|
||||
UINT64 endOffset = offset + size;
|
||||
if (static_cast<UINT64>(sb.st_size) < endOffset)
|
||||
{
|
||||
if (ftruncate(handle, endOffset) == -1)
|
||||
return MakeUnexpected("Failed to ftruncate (extend) file");
|
||||
}
|
||||
|
||||
void *ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, handle, static_cast<off_t>(offset));
|
||||
if (ptr == MAP_FAILED)
|
||||
return MakeUnexpected(std::format("mmap failed: {}", errno));
|
||||
|
||||
m_ptr = static_cast<PUINT8>(ptr);
|
||||
m_size = size;
|
||||
|
||||
madvise(m_ptr, m_size, MADV_SEQUENTIAL);
|
||||
msync(m_ptr, m_size, MS_SYNC);
|
||||
#endif
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
VOID FileOps::MemoryMappedRegion::Unmap()
|
||||
{
|
||||
if (!m_ptr)
|
||||
return;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
UnmapViewOfFile(m_ptr);
|
||||
if (m_hMap)
|
||||
{
|
||||
CloseHandle(m_hMap);
|
||||
m_hMap = NULL;
|
||||
}
|
||||
#elif IA_PLATFORM_UNIX
|
||||
munmap(m_ptr, m_size);
|
||||
#endif
|
||||
m_ptr = nullptr;
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
VOID FileOps::MemoryMappedRegion::Flush()
|
||||
{
|
||||
if (!m_ptr)
|
||||
return;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
FlushViewOfFile(m_ptr, m_size);
|
||||
#elif IA_PLATFORM_UNIX
|
||||
msync(m_ptr, m_size, MS_SYNC);
|
||||
#endif
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -13,127 +13,121 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/Http/Client.hpp>
|
||||
#include <IACore/DataOps.hpp>
|
||||
#include <IACore/Http/Client.hpp>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
EXPECT(UniquePtr<HttpClient>) HttpClient::Create(IN CONST String &host)
|
||||
{
|
||||
return MakeUniqueProtected<HttpClient>(httplib::Client(host));
|
||||
}
|
||||
namespace IACore {
|
||||
Result<UniquePtr<HttpClient>> HttpClient::Create(const String &host) {
|
||||
return MakeUniqueProtected<HttpClient>(httplib::Client(host));
|
||||
}
|
||||
|
||||
httplib::Headers BuildHeaders(IN Span<CONST HttpClient::Header> headers, IN PCCHAR defaultContentType)
|
||||
{
|
||||
httplib::Headers out;
|
||||
bool hasContentType = false;
|
||||
httplib::Headers BuildHeaders(Span<const HttpClient::Header> headers,
|
||||
const char *defaultContentType) {
|
||||
httplib::Headers out;
|
||||
bool hasContentType = false;
|
||||
|
||||
for (const auto &h : headers)
|
||||
{
|
||||
out.emplace(h.first, h.second);
|
||||
for (const auto &h : headers) {
|
||||
out.emplace(h.first, h.second);
|
||||
|
||||
if (h.first == HttpClient::HeaderTypeToString(HttpClient::EHeaderType::CONTENT_TYPE))
|
||||
hasContentType = true;
|
||||
}
|
||||
if (h.first ==
|
||||
HttpClient::HeaderTypeToString(HttpClient::EHeaderType::CONTENT_TYPE))
|
||||
hasContentType = true;
|
||||
}
|
||||
|
||||
if (!hasContentType && defaultContentType)
|
||||
out.emplace("Content-Type", defaultContentType);
|
||||
return out;
|
||||
}
|
||||
if (!hasContentType && defaultContentType)
|
||||
out.emplace("Content-Type", defaultContentType);
|
||||
return out;
|
||||
}
|
||||
|
||||
HttpClient::HttpClient(IN httplib::Client &&client)
|
||||
: m_client(IA_MOVE(client)), m_lastResponseCode(EResponseCode::INTERNAL_SERVER_ERROR)
|
||||
{
|
||||
m_client.enable_server_certificate_verification(true);
|
||||
}
|
||||
HttpClient::HttpClient(httplib::Client &&client)
|
||||
: m_client(std::move(client)),
|
||||
m_lastResponseCode(EResponseCode::INTERNAL_SERVER_ERROR) {
|
||||
m_client.enable_server_certificate_verification(true);
|
||||
}
|
||||
|
||||
HttpClient::~HttpClient()
|
||||
{
|
||||
}
|
||||
HttpClient::~HttpClient() {}
|
||||
|
||||
VOID HttpClient::EnableCertificateVerfication()
|
||||
{
|
||||
m_client.enable_server_certificate_verification(true);
|
||||
}
|
||||
void HttpClient::EnableCertificateVerfication() {
|
||||
m_client.enable_server_certificate_verification(true);
|
||||
}
|
||||
|
||||
VOID HttpClient::DisableCertificateVerfication()
|
||||
{
|
||||
m_client.enable_server_certificate_verification(false);
|
||||
}
|
||||
void HttpClient::DisableCertificateVerfication() {
|
||||
m_client.enable_server_certificate_verification(false);
|
||||
}
|
||||
|
||||
String HttpClient::PreprocessResponse(IN CONST String &response)
|
||||
{
|
||||
const auto responseBytes = Span<CONST UINT8>{(PCUINT8) response.data(), response.size()};
|
||||
const auto compression = DataOps::DetectCompression(responseBytes);
|
||||
switch (compression)
|
||||
{
|
||||
case DataOps::CompressionType::Gzip: {
|
||||
const auto data = DataOps::GZipInflate(responseBytes);
|
||||
if (!data)
|
||||
return response;
|
||||
return String((PCCHAR) data->data(), data->size());
|
||||
}
|
||||
String HttpClient::PreprocessResponse(const String &response) {
|
||||
const auto responseBytes =
|
||||
Span<const u8>{(const u8 *)response.data(), response.size()};
|
||||
const auto compression = DataOps::DetectCompression(responseBytes);
|
||||
switch (compression) {
|
||||
case DataOps::CompressionType::Gzip: {
|
||||
const auto data = DataOps::GZipInflate(responseBytes);
|
||||
if (!data)
|
||||
return response;
|
||||
return String((const char *)data->data(), data->size());
|
||||
}
|
||||
|
||||
case DataOps::CompressionType::Zlib: {
|
||||
const auto data = DataOps::ZlibInflate(responseBytes);
|
||||
if (!data)
|
||||
return response;
|
||||
return String((PCCHAR) data->data(), data->size());
|
||||
}
|
||||
case DataOps::CompressionType::Zlib: {
|
||||
const auto data = DataOps::ZlibInflate(responseBytes);
|
||||
if (!data)
|
||||
return response;
|
||||
return String((const char *)data->data(), data->size());
|
||||
}
|
||||
|
||||
case DataOps::CompressionType::None:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
case DataOps::CompressionType::None:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
EXPECT(String)
|
||||
HttpClient::RawGet(IN CONST String &path, IN Span<CONST Header> headers, IN PCCHAR defaultContentType)
|
||||
{
|
||||
auto httpHeaders = BuildHeaders(headers, defaultContentType);
|
||||
Result<String>
|
||||
|
||||
auto res = m_client.Get((!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(), httpHeaders);
|
||||
HttpClient::RawGet(const String &path, Span<const Header> headers,
|
||||
const char *defaultContentType) {
|
||||
auto httpHeaders = BuildHeaders(headers, defaultContentType);
|
||||
|
||||
if (res)
|
||||
{
|
||||
m_lastResponseCode = static_cast<EResponseCode>(res->status);
|
||||
if (res->status >= 200 && res->status < 300)
|
||||
return PreprocessResponse(res->body);
|
||||
else
|
||||
return MakeUnexpected(std::format("HTTP Error {} : {}", res->status, res->body));
|
||||
}
|
||||
auto res = m_client.Get(
|
||||
(!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(),
|
||||
httpHeaders);
|
||||
|
||||
return MakeUnexpected(std::format("Network Error: {}", httplib::to_string(res.error())));
|
||||
}
|
||||
if (res) {
|
||||
m_lastResponseCode = static_cast<EResponseCode>(res->status);
|
||||
if (res->status >= 200 && res->status < 300)
|
||||
return PreprocessResponse(res->body);
|
||||
else
|
||||
return (std::format("HTTP Error {} : {}", res->status, res->body));
|
||||
}
|
||||
|
||||
EXPECT(String)
|
||||
HttpClient::RawPost(IN CONST String &path, IN Span<CONST Header> headers, IN CONST String &body,
|
||||
IN PCCHAR defaultContentType)
|
||||
{
|
||||
auto httpHeaders = BuildHeaders(headers, defaultContentType);
|
||||
return (std::format("Network Error: {}", httplib::to_string(res.error())));
|
||||
}
|
||||
|
||||
String contentType = defaultContentType;
|
||||
if (httpHeaders.count("Content-Type"))
|
||||
{
|
||||
const auto t = httpHeaders.find("Content-Type");
|
||||
contentType = t->second;
|
||||
httpHeaders.erase(t);
|
||||
}
|
||||
Result<String>
|
||||
|
||||
m_client.set_keep_alive(true);
|
||||
auto res = m_client.Post((!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(), httpHeaders,
|
||||
body, contentType.c_str());
|
||||
HttpClient::RawPost(const String &path, Span<const Header> headers,
|
||||
const String &body, const char *defaultContentType) {
|
||||
auto httpHeaders = BuildHeaders(headers, defaultContentType);
|
||||
|
||||
if (res)
|
||||
{
|
||||
m_lastResponseCode = static_cast<EResponseCode>(res->status);
|
||||
if (res->status >= 200 && res->status < 300)
|
||||
return PreprocessResponse(res->body);
|
||||
else
|
||||
return MakeUnexpected(std::format("HTTP Error {} : {}", res->status, res->body));
|
||||
}
|
||||
String contentType = defaultContentType;
|
||||
if (httpHeaders.count("Content-Type")) {
|
||||
const auto t = httpHeaders.find("Content-Type");
|
||||
contentType = t->second;
|
||||
httpHeaders.erase(t);
|
||||
}
|
||||
|
||||
return MakeUnexpected(std::format("Network Error: {}", httplib::to_string(res.error())));
|
||||
}
|
||||
m_client.set_keep_alive(true);
|
||||
auto res = m_client.Post(
|
||||
(!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(),
|
||||
httpHeaders, body, contentType.c_str());
|
||||
|
||||
if (res) {
|
||||
m_lastResponseCode = static_cast<EResponseCode>(res->status);
|
||||
if (res->status >= 200 && res->status < 300)
|
||||
return PreprocessResponse(res->body);
|
||||
else
|
||||
return (std::format("HTTP Error {} : {}", res->status, res->body));
|
||||
}
|
||||
|
||||
return (std::format("Network Error: {}", httplib::to_string(res.error())));
|
||||
}
|
||||
} // namespace IACore
|
||||
|
||||
@ -15,110 +15,103 @@
|
||||
|
||||
#include <IACore/Http/Common.hpp>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
String HttpCommon::UrlEncode(IN CONST String &value)
|
||||
{
|
||||
std::stringstream escaped;
|
||||
escaped.fill('0');
|
||||
escaped << std::hex << std::uppercase;
|
||||
namespace IACore {
|
||||
String HttpCommon::UrlEncode(const String &value) {
|
||||
std::stringstream escaped;
|
||||
escaped.fill('0');
|
||||
escaped << std::hex << std::uppercase;
|
||||
|
||||
for (char c : value)
|
||||
{
|
||||
if (std::isalnum(static_cast<unsigned char>(c)) || c == '-' || c == '_' || c == '.' || c == '~')
|
||||
escaped << c;
|
||||
else
|
||||
escaped << '%' << std::setw(2) << static_cast<int>(static_cast<unsigned char>(c));
|
||||
}
|
||||
for (char c : value) {
|
||||
if (std::isalnum(static_cast<unsigned char>(c)) || c == '-' || c == '_' ||
|
||||
c == '.' || c == '~')
|
||||
escaped << c;
|
||||
else
|
||||
escaped << '%' << std::setw(2)
|
||||
<< static_cast<int>(static_cast<unsigned char>(c));
|
||||
}
|
||||
|
||||
return escaped.str();
|
||||
}
|
||||
return escaped.str();
|
||||
}
|
||||
|
||||
String HttpCommon::UrlDecode(IN CONST String &value)
|
||||
{
|
||||
String result;
|
||||
result.reserve(value.length());
|
||||
String HttpCommon::UrlDecode(const String &value) {
|
||||
String result;
|
||||
result.reserve(value.length());
|
||||
|
||||
for (size_t i = 0; i < value.length(); ++i)
|
||||
{
|
||||
if (value[i] == '%' && i + 2 < value.length())
|
||||
{
|
||||
std::string hexStr = value.substr(i + 1, 2);
|
||||
char decodedChar = static_cast<char>(std::strtol(hexStr.c_str(), nullptr, 16));
|
||||
result += decodedChar;
|
||||
i += 2;
|
||||
}
|
||||
else if (value[i] == '+')
|
||||
result += ' ';
|
||||
else
|
||||
result += value[i];
|
||||
}
|
||||
for (size_t i = 0; i < value.length(); ++i) {
|
||||
if (value[i] == '%' && i + 2 < value.length()) {
|
||||
std::string hexStr = value.substr(i + 1, 2);
|
||||
char decodedChar =
|
||||
static_cast<char>(std::strtol(hexStr.c_str(), nullptr, 16));
|
||||
result += decodedChar;
|
||||
i += 2;
|
||||
} else if (value[i] == '+')
|
||||
result += ' ';
|
||||
else
|
||||
result += value[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
String HttpCommon::HeaderTypeToString(IN EHeaderType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case EHeaderType::ACCEPT:
|
||||
return "Accept";
|
||||
case EHeaderType::ACCEPT_CHARSET:
|
||||
return "Accept-Charset";
|
||||
case EHeaderType::ACCEPT_ENCODING:
|
||||
return "Accept-Encoding";
|
||||
case EHeaderType::ACCEPT_LANGUAGE:
|
||||
return "Accept-Language";
|
||||
case EHeaderType::AUTHORIZATION:
|
||||
return "Authorization";
|
||||
case EHeaderType::CACHE_CONTROL:
|
||||
return "Cache-Control";
|
||||
case EHeaderType::CONNECTION:
|
||||
return "Connection";
|
||||
case EHeaderType::CONTENT_LENGTH:
|
||||
return "Content-Length";
|
||||
case EHeaderType::CONTENT_TYPE:
|
||||
return "Content-Type";
|
||||
case EHeaderType::COOKIE:
|
||||
return "Cookie";
|
||||
case EHeaderType::DATE:
|
||||
return "Date";
|
||||
case EHeaderType::EXPECT:
|
||||
return "Expect";
|
||||
case EHeaderType::HOST:
|
||||
return "Host";
|
||||
case EHeaderType::IF_MATCH:
|
||||
return "If-Match";
|
||||
case EHeaderType::IF_MODIFIED_SINCE:
|
||||
return "If-Modified-Since";
|
||||
case EHeaderType::IF_NONE_MATCH:
|
||||
return "If-None-Match";
|
||||
case EHeaderType::ORIGIN:
|
||||
return "Origin";
|
||||
case EHeaderType::PRAGMA:
|
||||
return "Pragma";
|
||||
case EHeaderType::PROXY_AUTHORIZATION:
|
||||
return "Proxy-Authorization";
|
||||
case EHeaderType::RANGE:
|
||||
return "Range";
|
||||
case EHeaderType::REFERER:
|
||||
return "Referer";
|
||||
case EHeaderType::TE:
|
||||
return "TE";
|
||||
case EHeaderType::UPGRADE:
|
||||
return "Upgrade";
|
||||
case EHeaderType::USER_AGENT:
|
||||
return "User-Agent";
|
||||
case EHeaderType::VIA:
|
||||
return "Via";
|
||||
case EHeaderType::WARNING:
|
||||
return "Warning";
|
||||
}
|
||||
return "<Unknown>";
|
||||
}
|
||||
String HttpCommon::HeaderTypeToString(EHeaderType type) {
|
||||
switch (type) {
|
||||
case EHeaderType::ACCEPT:
|
||||
return "Accept";
|
||||
case EHeaderType::ACCEPT_CHARSET:
|
||||
return "Accept-Charset";
|
||||
case EHeaderType::ACCEPT_ENCODING:
|
||||
return "Accept-Encoding";
|
||||
case EHeaderType::ACCEPT_LANGUAGE:
|
||||
return "Accept-Language";
|
||||
case EHeaderType::AUTHORIZATION:
|
||||
return "Authorization";
|
||||
case EHeaderType::CACHE_CONTROL:
|
||||
return "Cache-Control";
|
||||
case EHeaderType::CONNECTION:
|
||||
return "Connection";
|
||||
case EHeaderType::CONTENT_LENGTH:
|
||||
return "Content-Length";
|
||||
case EHeaderType::CONTENT_TYPE:
|
||||
return "Content-Type";
|
||||
case EHeaderType::COOKIE:
|
||||
return "Cookie";
|
||||
case EHeaderType::DATE:
|
||||
return "Date";
|
||||
case EHeaderType::EXPECT:
|
||||
return "Expect";
|
||||
case EHeaderType::HOST:
|
||||
return "Host";
|
||||
case EHeaderType::IF_MATCH:
|
||||
return "If-Match";
|
||||
case EHeaderType::IF_MODIFIED_SINCE:
|
||||
return "If-Modified-Since";
|
||||
case EHeaderType::IF_NONE_MATCH:
|
||||
return "If-None-Match";
|
||||
case EHeaderType::ORIGIN:
|
||||
return "Origin";
|
||||
case EHeaderType::PRAGMA:
|
||||
return "Pragma";
|
||||
case EHeaderType::PROXY_AUTHORIZATION:
|
||||
return "Proxy-Authorization";
|
||||
case EHeaderType::RANGE:
|
||||
return "Range";
|
||||
case EHeaderType::REFERER:
|
||||
return "Referer";
|
||||
case EHeaderType::TE:
|
||||
return "TE";
|
||||
case EHeaderType::UPGRADE:
|
||||
return "Upgrade";
|
||||
case EHeaderType::USER_AGENT:
|
||||
return "User-Agent";
|
||||
case EHeaderType::VIA:
|
||||
return "Via";
|
||||
case EHeaderType::WARNING:
|
||||
return "Warning";
|
||||
}
|
||||
return "<Unknown>";
|
||||
}
|
||||
|
||||
BOOL HttpCommon::IsSuccessResponseCode(IN EResponseCode code)
|
||||
{
|
||||
return (INT32) code >= 200 && (INT32) code < 300;
|
||||
}
|
||||
bool HttpCommon::IsSuccessResponseCode(EResponseCode code) {
|
||||
return (i32)code >= 200 && (i32)code < 300;
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -17,76 +17,50 @@
|
||||
#include <IACore/Logger.hpp>
|
||||
|
||||
#include <mimalloc.h>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
HighResTimePoint g_startTime{};
|
||||
std::thread::id g_mainThreadID{};
|
||||
INT32 g_coreInitCount{};
|
||||
auto g_start_time = std::chrono::high_resolution_clock::time_point{};
|
||||
|
||||
VOID Initialize()
|
||||
static auto g_main_thread_id = std::thread::id{};
|
||||
static auto g_core_init_count = i32{0};
|
||||
|
||||
auto initialize() -> void
|
||||
{
|
||||
g_coreInitCount++;
|
||||
if (g_coreInitCount > 1)
|
||||
g_core_init_count++;
|
||||
if (g_core_init_count > 1)
|
||||
{
|
||||
return;
|
||||
g_mainThreadID = std::this_thread::get_id();
|
||||
g_startTime = HighResClock::now();
|
||||
Logger::Initialize();
|
||||
}
|
||||
|
||||
g_main_thread_id = std::this_thread::get_id();
|
||||
g_start_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
Logger::initialize();
|
||||
|
||||
mi_option_set(mi_option_verbose, 0);
|
||||
}
|
||||
|
||||
VOID Terminate()
|
||||
auto terminate() -> void
|
||||
{
|
||||
g_coreInitCount--;
|
||||
if (g_coreInitCount > 0)
|
||||
g_core_init_count--;
|
||||
if (g_core_init_count > 0)
|
||||
{
|
||||
return;
|
||||
Logger::Terminate();
|
||||
}
|
||||
|
||||
Logger::terminate();
|
||||
}
|
||||
|
||||
BOOL IsInitialized()
|
||||
auto is_initialized() -> bool
|
||||
{
|
||||
return g_coreInitCount > 0;
|
||||
return g_core_init_count > 0;
|
||||
}
|
||||
|
||||
UINT64 GetUnixTime()
|
||||
auto is_main_thread() -> bool
|
||||
{
|
||||
auto now = std::chrono::system_clock::now();
|
||||
return std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
|
||||
}
|
||||
|
||||
UINT64 GetTicksCount()
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(HighResClock::now() - g_startTime).count();
|
||||
}
|
||||
|
||||
FLOAT64 GetSecondsCount()
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::seconds>(HighResClock::now() - g_startTime).count();
|
||||
}
|
||||
|
||||
FLOAT32 GetRandom()
|
||||
{
|
||||
return static_cast<FLOAT32>(rand()) / static_cast<FLOAT32>(RAND_MAX);
|
||||
}
|
||||
|
||||
UINT64 GetRandom(IN UINT64 max)
|
||||
{
|
||||
return max * GetRandom();
|
||||
}
|
||||
|
||||
INT64 GetRandom(IN INT64 min, IN INT64 max)
|
||||
{
|
||||
return min + (max - min) * GetRandom();
|
||||
}
|
||||
|
||||
VOID Sleep(IN UINT64 milliseconds)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
|
||||
}
|
||||
|
||||
BOOL IsMainThread()
|
||||
{
|
||||
return std::this_thread::get_id() == g_mainThreadID;
|
||||
return std::this_thread::get_id() == g_main_thread_id;
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -17,435 +17,441 @@
|
||||
|
||||
#include <IACore/FileOps.hpp>
|
||||
#include <IACore/StringOps.hpp>
|
||||
#include <charconv>
|
||||
#include <fcntl.h>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
struct IPC_ConnectionDescriptor
|
||||
{
|
||||
String SocketPath;
|
||||
String SharedMemPath;
|
||||
UINT32 SharedMemSize;
|
||||
namespace IACore {
|
||||
// =============================================================================
|
||||
// Internal: Connection Descriptor
|
||||
// =============================================================================
|
||||
struct IpcConnectionDescriptor {
|
||||
String socket_path;
|
||||
String shared_mem_path;
|
||||
u32 shared_mem_size;
|
||||
|
||||
String Serialize() CONST
|
||||
{
|
||||
return std::format("{}|{}|{}|", SocketPath, SharedMemPath, SharedMemSize);
|
||||
[[nodiscard]] auto serialize() const -> String {
|
||||
return std::format("{}|{}|{}|", socket_path, shared_mem_path,
|
||||
shared_mem_size);
|
||||
}
|
||||
|
||||
static auto deserialize(StringView data) -> Option<IpcConnectionDescriptor> {
|
||||
enum class ParseState { SocketPath, SharedMemPath, SharedMemSize };
|
||||
|
||||
IpcConnectionDescriptor result{};
|
||||
usize t = 0;
|
||||
auto state = ParseState::SocketPath;
|
||||
|
||||
for (usize i = 0; i < data.size(); ++i) {
|
||||
if (data[i] != '|') {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case ParseState::SocketPath:
|
||||
result.socket_path = String(data.substr(t, i - t));
|
||||
state = ParseState::SharedMemPath;
|
||||
break;
|
||||
|
||||
case ParseState::SharedMemPath:
|
||||
result.shared_mem_path = String(data.substr(t, i - t));
|
||||
state = ParseState::SharedMemSize;
|
||||
break;
|
||||
|
||||
case ParseState::SharedMemSize: {
|
||||
const auto *start = data.data() + t;
|
||||
const auto *end = data.data() + i;
|
||||
if (std::from_chars(start, end, result.shared_mem_size).ec !=
|
||||
std::errc{}) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
STATIC IPC_ConnectionDescriptor Deserialize(IN CONST String &data)
|
||||
{
|
||||
enum class EParseState
|
||||
{
|
||||
SocketPath,
|
||||
SharedMemPath,
|
||||
SharedMemSize
|
||||
};
|
||||
|
||||
IPC_ConnectionDescriptor result{};
|
||||
|
||||
SIZE_T t{};
|
||||
EParseState state{EParseState::SocketPath};
|
||||
for (SIZE_T i = 0; i < data.size(); i++)
|
||||
{
|
||||
if (data[i] != '|')
|
||||
continue;
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case EParseState::SocketPath:
|
||||
result.SocketPath = data.substr(t, i - t);
|
||||
state = EParseState::SharedMemPath;
|
||||
break;
|
||||
|
||||
case EParseState::SharedMemPath:
|
||||
result.SharedMemPath = data.substr(t, i - t);
|
||||
state = EParseState::SharedMemSize;
|
||||
break;
|
||||
|
||||
case EParseState::SharedMemSize: {
|
||||
if (std::from_chars(&data[t], &data[i], result.SharedMemSize).ec != std::errc{})
|
||||
return {};
|
||||
goto done_parsing;
|
||||
}
|
||||
}
|
||||
t = i + 1;
|
||||
}
|
||||
|
||||
done_parsing:
|
||||
return result;
|
||||
}
|
||||
};
|
||||
} // namespace IACore
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
IPC_Node::~IPC_Node()
|
||||
{
|
||||
SocketOps::Close(m_socket); // SocketOps gracefully handles INVALID_SOCKET
|
||||
return result;
|
||||
}
|
||||
}
|
||||
t = i + 1;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
EXPECT(VOID) IPC_Node::Connect(IN PCCHAR connectionString)
|
||||
{
|
||||
auto desc = IPC_ConnectionDescriptor::Deserialize(connectionString);
|
||||
m_shmName = desc.SharedMemPath;
|
||||
// =============================================================================
|
||||
// IpcNode Implementation
|
||||
// =============================================================================
|
||||
|
||||
m_socket = SocketOps::CreateUnixSocket();
|
||||
if (!SocketOps::ConnectUnixSocket(m_socket, desc.SocketPath.c_str()))
|
||||
return MakeUnexpected("Failed to create an unix socket");
|
||||
IpcNode::~IpcNode() {
|
||||
if (m_socket != INVALID_SOCKET) {
|
||||
SocketOps::close(m_socket);
|
||||
}
|
||||
}
|
||||
|
||||
auto mapRes = FileOps::MapSharedMemory(desc.SharedMemPath, desc.SharedMemSize, FALSE);
|
||||
if (!mapRes.has_value())
|
||||
return MakeUnexpected("Failed to map the shared memory");
|
||||
auto IpcNode::connect(const char *connection_string) -> Result<void> {
|
||||
const auto desc_opt = IpcConnectionDescriptor::deserialize(connection_string);
|
||||
if (!desc_opt) {
|
||||
return fail("Failed to parse connection string");
|
||||
}
|
||||
const auto &desc = *desc_opt;
|
||||
m_shm_name = desc.shared_mem_path;
|
||||
|
||||
m_sharedMemory = mapRes.value();
|
||||
IA_TRY(m_socket, SocketOps::create_unix_socket());
|
||||
IA_TRY_PURE(
|
||||
SocketOps::connect_unix_socket(m_socket, desc.socket_path.c_str()));
|
||||
|
||||
auto *layout = reinterpret_cast<IPC_SharedMemoryLayout *>(m_sharedMemory);
|
||||
u8 *mapped_ptr{};
|
||||
IA_TRY(mapped_ptr, FileOps::map_shared_memory(desc.shared_mem_path,
|
||||
desc.shared_mem_size, false));
|
||||
m_shared_memory = mapped_ptr;
|
||||
|
||||
if (layout->Meta.Magic != 0x49414950) // "IAIP"
|
||||
return MakeUnexpected("Invalid shared memory header signature");
|
||||
auto *layout = reinterpret_cast<IpcSharedMemoryLayout *>(m_shared_memory);
|
||||
|
||||
if (layout->Meta.Version != 1)
|
||||
return MakeUnexpected("IPC version mismatch");
|
||||
if (layout->meta.magic != 0x49414950) // "IAIP"
|
||||
{
|
||||
return fail("Invalid shared memory header signature");
|
||||
}
|
||||
|
||||
PUINT8 moniDataPtr = m_sharedMemory + layout->MONI_DataOffset;
|
||||
PUINT8 minoDataPtr = m_sharedMemory + layout->MINO_DataOffset;
|
||||
if (layout->meta.version != 1) {
|
||||
return fail("IPC version mismatch");
|
||||
}
|
||||
|
||||
MONI = std::make_unique<RingBufferView>(
|
||||
&layout->MONI_Control, Span<UINT8>(moniDataPtr, static_cast<size_t>(layout->MONI_DataSize)), FALSE);
|
||||
u8 *moni_ptr = m_shared_memory + layout->moni_data_offset;
|
||||
u8 *mino_ptr = m_shared_memory + layout->mino_data_offset;
|
||||
|
||||
MINO = std::make_unique<RingBufferView>(
|
||||
&layout->MINO_Control, Span<UINT8>(minoDataPtr, static_cast<size_t>(layout->MINO_DataSize)), FALSE);
|
||||
m_moni = make_box<RingBufferView>(
|
||||
&layout->moni_control,
|
||||
Span<u8>(moni_ptr, static_cast<usize>(layout->moni_data_size)), false);
|
||||
|
||||
m_mino = make_box<RingBufferView>(
|
||||
&layout->mino_control,
|
||||
Span<u8>(mino_ptr, static_cast<usize>(layout->mino_data_size)), false);
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
u_long mode = 1;
|
||||
ioctlsocket(m_socket, FIONBIO, &mode);
|
||||
u_long mode = 1;
|
||||
ioctlsocket(m_socket, FIONBIO, &mode);
|
||||
#else
|
||||
fcntl(m_socket, F_SETFL, O_NONBLOCK);
|
||||
fcntl(m_socket, F_SETFL, O_NONBLOCK);
|
||||
#endif
|
||||
|
||||
m_receiveBuffer.resize(UINT16_MAX + 1);
|
||||
m_receive_buffer.resize(UINT16_MAX + 1);
|
||||
|
||||
return {};
|
||||
return {};
|
||||
}
|
||||
|
||||
void IpcNode::update() {
|
||||
if (!m_moni) {
|
||||
return;
|
||||
}
|
||||
|
||||
IpcPacketHeader header;
|
||||
|
||||
// Process all available messages from Manager
|
||||
while (m_moni->pop(
|
||||
header, Span<u8>(m_receive_buffer.data(), m_receive_buffer.size()))) {
|
||||
on_packet(header.id, {m_receive_buffer.data(), header.payload_size});
|
||||
}
|
||||
|
||||
u8 signal = 0;
|
||||
const auto res = recv(m_socket, reinterpret_cast<char *>(&signal), 1, 0);
|
||||
if (res == 1) {
|
||||
on_signal(signal);
|
||||
} else if (res == 0 || (res < 0 && !SocketOps::is_would_block())) {
|
||||
SocketOps::close(m_socket);
|
||||
FileOps::unlink_shared_memory(m_shm_name);
|
||||
|
||||
// Manager disconnected, exit immediately
|
||||
std::exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
void IpcNode::send_signal(u8 signal) {
|
||||
if (m_socket != INVALID_SOCKET) {
|
||||
send(m_socket, reinterpret_cast<const char *>(&signal), sizeof(signal), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void IpcNode::send_packet(u16 packet_id, Span<const u8> payload) {
|
||||
if (m_mino) {
|
||||
m_mino->push(packet_id, payload);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// IpcManager Implementation
|
||||
// =============================================================================
|
||||
|
||||
void IpcManager::NodeSession::send_signal(u8 signal) {
|
||||
if (data_socket != INVALID_SOCKET) {
|
||||
send(data_socket, reinterpret_cast<const char *>(&signal), sizeof(signal),
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
void IpcManager::NodeSession::send_packet(u16 packet_id,
|
||||
Span<const u8> payload) {
|
||||
// Protect the RingBuffer write cursor from concurrent threads
|
||||
std::scoped_lock lock(send_mutex);
|
||||
if (moni) {
|
||||
moni->push(packet_id, payload);
|
||||
}
|
||||
}
|
||||
|
||||
IpcManager::IpcManager() {
|
||||
// SocketOps is smart enough to track multiple inits
|
||||
SocketOps::initialize();
|
||||
m_receive_buffer.resize(UINT16_MAX + 1);
|
||||
}
|
||||
|
||||
IpcManager::~IpcManager() {
|
||||
for (auto &session : m_active_sessions) {
|
||||
ProcessOps::terminate_process(session->node_process);
|
||||
FileOps::unmap_file(session->mapped_ptr);
|
||||
FileOps::unlink_shared_memory(session->shared_mem_name);
|
||||
SocketOps::close(session->data_socket);
|
||||
}
|
||||
m_active_sessions.clear();
|
||||
|
||||
for (auto &session : m_pending_sessions) {
|
||||
ProcessOps::terminate_process(session->node_process);
|
||||
FileOps::unmap_file(session->mapped_ptr);
|
||||
FileOps::unlink_shared_memory(session->shared_mem_name);
|
||||
SocketOps::close(session->listener_socket);
|
||||
}
|
||||
m_pending_sessions.clear();
|
||||
|
||||
// SocketOps is smart enough to track multiple terminates
|
||||
SocketOps::terminate();
|
||||
}
|
||||
|
||||
void IpcManager::update() {
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
|
||||
for (isize i = static_cast<isize>(m_pending_sessions.size()) - 1; i >= 0;
|
||||
--i) {
|
||||
auto &session = m_pending_sessions[static_cast<usize>(i)];
|
||||
|
||||
if (now - session->creation_time > std::chrono::seconds(5)) {
|
||||
ProcessOps::terminate_process(session->node_process);
|
||||
|
||||
FileOps::unmap_file(session->mapped_ptr);
|
||||
FileOps::unlink_shared_memory(session->shared_mem_name);
|
||||
SocketOps::close(session->listener_socket);
|
||||
|
||||
m_pending_sessions.erase(m_pending_sessions.begin() + i);
|
||||
continue;
|
||||
}
|
||||
|
||||
VOID IPC_Node::Update()
|
||||
{
|
||||
if (!MONI)
|
||||
return;
|
||||
auto new_sock = accept(session->listener_socket, nullptr, nullptr);
|
||||
|
||||
RingBufferView::PacketHeader header;
|
||||
if (new_sock != INVALID_SOCKET) {
|
||||
session->data_socket = new_sock;
|
||||
session->is_ready = true;
|
||||
|
||||
// Process all available messages from Manager
|
||||
while (MONI->Pop(header, Span<UINT8>(m_receiveBuffer.data(), m_receiveBuffer.size())))
|
||||
OnPacket(header.ID, {m_receiveBuffer.data(), header.PayloadSize});
|
||||
|
||||
UINT8 signal;
|
||||
const auto res = recv(m_socket, (CHAR *) &signal, 1, 0);
|
||||
if (res == 1)
|
||||
OnSignal(signal);
|
||||
else if (res == 0 || (res < 0 && !SocketOps::IsWouldBlock()))
|
||||
{
|
||||
SocketOps::Close(m_socket);
|
||||
FileOps::UnlinkSharedMemory(m_shmName);
|
||||
|
||||
// Manager disconnected, exit immediately
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
VOID IPC_Node::SendSignal(IN UINT8 signal)
|
||||
{
|
||||
if (IS_VALID_SOCKET(m_socket))
|
||||
send(m_socket, (const char *) &signal, sizeof(signal), 0);
|
||||
}
|
||||
|
||||
VOID IPC_Node::SendPacket(IN UINT16 packetID, IN Span<CONST UINT8> payload)
|
||||
{
|
||||
MINO->Push(packetID, payload);
|
||||
}
|
||||
} // namespace IACore
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
VOID IPC_Manager::NodeSession::SendSignal(IN UINT8 signal)
|
||||
{
|
||||
if (IS_VALID_SOCKET(DataSocket))
|
||||
send(DataSocket, (const char *) &signal, sizeof(signal), 0);
|
||||
}
|
||||
|
||||
VOID IPC_Manager::NodeSession::SendPacket(IN UINT16 packetID, IN Span<CONST UINT8> payload)
|
||||
{
|
||||
// Protect the RingBuffer write cursor from concurrent threads
|
||||
ScopedLock lock(SendMutex);
|
||||
MONI->Push(packetID, payload);
|
||||
}
|
||||
|
||||
IPC_Manager::IPC_Manager()
|
||||
{
|
||||
// SocketOps is smart enough to track multiple inits
|
||||
SocketOps::Initialize();
|
||||
|
||||
m_receiveBuffer.resize(UINT16_MAX + 1);
|
||||
}
|
||||
|
||||
IPC_Manager::~IPC_Manager()
|
||||
{
|
||||
for (auto &session : m_activeSessions)
|
||||
{
|
||||
ProcessOps::TerminateProcess(session->NodeProcess);
|
||||
FileOps::UnmapFile(session->MappedPtr);
|
||||
FileOps::UnlinkSharedMemory(session->SharedMemName);
|
||||
SocketOps::Close(session->DataSocket);
|
||||
}
|
||||
m_activeSessions.clear();
|
||||
|
||||
for (auto &session : m_pendingSessions)
|
||||
{
|
||||
ProcessOps::TerminateProcess(session->NodeProcess);
|
||||
FileOps::UnmapFile(session->MappedPtr);
|
||||
FileOps::UnlinkSharedMemory(session->SharedMemName);
|
||||
SocketOps::Close(session->ListenerSocket);
|
||||
}
|
||||
m_pendingSessions.clear();
|
||||
|
||||
// SocketOps is smart enough to track multiple terminates
|
||||
SocketOps::Terminate();
|
||||
}
|
||||
|
||||
VOID IPC_Manager::Update()
|
||||
{
|
||||
const auto now = SteadyClock::now();
|
||||
|
||||
for (INT32 i = m_pendingSessions.size() - 1; i >= 0; i--)
|
||||
{
|
||||
auto &session = m_pendingSessions[i];
|
||||
|
||||
if (now - session->CreationTime > std::chrono::seconds(5))
|
||||
{
|
||||
ProcessOps::TerminateProcess(session->NodeProcess);
|
||||
|
||||
FileOps::UnmapFile(session->MappedPtr);
|
||||
FileOps::UnlinkSharedMemory(session->SharedMemName);
|
||||
SocketOps::Close(session->DataSocket);
|
||||
|
||||
m_pendingSessions.erase(m_pendingSessions.begin() + i);
|
||||
continue;
|
||||
}
|
||||
|
||||
SocketHandle newSock = accept(session->ListenerSocket, NULL, NULL);
|
||||
|
||||
if (IS_VALID_SOCKET(newSock))
|
||||
{
|
||||
session->DataSocket = newSock;
|
||||
session->IsReady = TRUE;
|
||||
|
||||
// Set Data Socket to Non-Blocking
|
||||
// Set Data Socket to Non-Blocking
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
u_long mode = 1;
|
||||
ioctlsocket(session->DataSocket, FIONBIO, &mode);
|
||||
u_long mode = 1;
|
||||
ioctlsocket(session->data_socket, FIONBIO, &mode);
|
||||
#else
|
||||
fcntl(session->DataSocket, F_SETFL, O_NONBLOCK);
|
||||
fcntl(session->data_socket, F_SETFL, O_NONBLOCK);
|
||||
#endif
|
||||
|
||||
SocketOps::Close(session->ListenerSocket);
|
||||
session->ListenerSocket = INVALID_SOCKET;
|
||||
SocketOps::close(session->listener_socket);
|
||||
session->listener_socket = INVALID_SOCKET;
|
||||
|
||||
const auto sessionID = session->NodeProcess->ID.load();
|
||||
const auto sessionPtr = session.get();
|
||||
m_activeSessions.push_back(std::move(session));
|
||||
m_pendingSessions.erase(m_pendingSessions.begin() + i);
|
||||
m_activeSessionMap[sessionID] = sessionPtr;
|
||||
}
|
||||
}
|
||||
const auto session_id = session->node_process->id.load();
|
||||
auto *session_ptr = session.get();
|
||||
m_active_sessions.push_back(std::move(session));
|
||||
m_pending_sessions.erase(m_pending_sessions.begin() + i);
|
||||
m_active_session_map[session_id] = session_ptr;
|
||||
}
|
||||
}
|
||||
|
||||
for (INT32 i = m_activeSessions.size() - 1; i >= 0; i--)
|
||||
{
|
||||
auto &node = m_activeSessions[i];
|
||||
for (isize i = static_cast<isize>(m_active_sessions.size()) - 1; i >= 0;
|
||||
--i) {
|
||||
auto &node = m_active_sessions[static_cast<usize>(i)];
|
||||
|
||||
auto nodeID = node->NodeProcess->ID.load();
|
||||
auto node_id = node->node_process->id.load();
|
||||
|
||||
RingBufferView::PacketHeader header;
|
||||
IpcPacketHeader header;
|
||||
|
||||
while (node->MINO->Pop(header, Span<UINT8>(m_receiveBuffer.data(), m_receiveBuffer.size())))
|
||||
OnPacket(nodeID, header.ID, {m_receiveBuffer.data(), header.PayloadSize});
|
||||
|
||||
UINT8 signal;
|
||||
const auto res = recv(node->DataSocket, (CHAR *) &signal, 1, 0);
|
||||
|
||||
if (res == 1)
|
||||
OnSignal(nodeID, signal);
|
||||
else if (res == 0 || (res < 0 && !SocketOps::IsWouldBlock()))
|
||||
{
|
||||
ProcessOps::TerminateProcess(node->NodeProcess);
|
||||
|
||||
FileOps::UnmapFile(node->MappedPtr);
|
||||
FileOps::UnlinkSharedMemory(node->SharedMemName);
|
||||
SocketOps::Close(node->DataSocket);
|
||||
|
||||
m_activeSessions.erase(m_activeSessions.begin() + i);
|
||||
m_activeSessionMap.erase(nodeID);
|
||||
}
|
||||
}
|
||||
while (node->mino->pop(
|
||||
header, Span<u8>(m_receive_buffer.data(), m_receive_buffer.size()))) {
|
||||
on_packet(node_id, header.id,
|
||||
{m_receive_buffer.data(), header.payload_size});
|
||||
}
|
||||
|
||||
EXPECT(NativeProcessID) IPC_Manager::SpawnNode(IN CONST FilePath &executablePath, IN UINT32 sharedMemorySize)
|
||||
{
|
||||
auto session = std::make_unique<NodeSession>();
|
||||
u8 signal = 0;
|
||||
const auto res =
|
||||
recv(node->data_socket, reinterpret_cast<char *>(&signal), 1, 0);
|
||||
|
||||
static Atomic<UINT32> s_idGen{0};
|
||||
UINT32 sid = ++s_idGen;
|
||||
if (res == 1) {
|
||||
on_signal(node_id, signal);
|
||||
} else if (res == 0 || (res < 0 && !SocketOps::is_would_block())) {
|
||||
ProcessOps::terminate_process(node->node_process);
|
||||
|
||||
FileOps::unmap_file(node->mapped_ptr);
|
||||
FileOps::unlink_shared_memory(node->shared_mem_name);
|
||||
SocketOps::close(node->data_socket);
|
||||
|
||||
m_active_sessions.erase(m_active_sessions.begin() + i);
|
||||
m_active_session_map.erase(node_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto IpcManager::spawn_node(const Path &executable_path, u32 shared_memory_size)
|
||||
-> Result<NativeProcessID> {
|
||||
auto session = make_box<NodeSession>();
|
||||
|
||||
static std::atomic<u32> s_id_gen{0};
|
||||
const u32 sid = ++s_id_gen;
|
||||
|
||||
String sock_path;
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
char temp_path[MAX_PATH];
|
||||
GetTempPathA(MAX_PATH, temp_path);
|
||||
sock_path = std::format("{}\\ia_sess_{}.sock", temp_path, sid);
|
||||
#else
|
||||
sock_path = std::format("/tmp/ia_sess_{}.sock", sid);
|
||||
#endif
|
||||
|
||||
IA_TRY(session->listener_socket, SocketOps::create_unix_socket());
|
||||
IA_TRY_PURE(
|
||||
SocketOps::bind_unix_socket(session->listener_socket, sock_path.c_str()));
|
||||
IA_TRY_PURE(SocketOps::listen(session->listener_socket, 1));
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
char tempPath[MAX_PATH];
|
||||
GetTempPathA(MAX_PATH, tempPath);
|
||||
String sockPath = std::format("{}\\ia_sess_{}.sock", tempPath, sid);
|
||||
u_long mode = 1;
|
||||
ioctlsocket(session->listener_socket, FIONBIO, &mode);
|
||||
#else
|
||||
String sockPath = std::format("/tmp/ia_sess_{}.sock", sid);
|
||||
fcntl(session->listener_socket, F_SETFL, O_NONBLOCK);
|
||||
#endif
|
||||
|
||||
session->ListenerSocket = SocketOps::CreateUnixSocket();
|
||||
if (!SocketOps::BindUnixSocket(session->ListenerSocket, sockPath.c_str()))
|
||||
return MakeUnexpected("Failed to bind unique socket");
|
||||
const String shm_name = std::format("ia_shm_{}", sid);
|
||||
IA_TRY(session->mapped_ptr,
|
||||
FileOps::map_shared_memory(shm_name, shared_memory_size, true));
|
||||
|
||||
if (listen(session->ListenerSocket, 1) != 0)
|
||||
return MakeUnexpected("Failed to listen on unique socket");
|
||||
auto *layout = reinterpret_cast<IpcSharedMemoryLayout *>(session->mapped_ptr);
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
u_long mode = 1;
|
||||
ioctlsocket(session->ListenerSocket, FIONBIO, &mode);
|
||||
#else
|
||||
fcntl(session->ListenerSocket, F_SETFL, O_NONBLOCK);
|
||||
#endif
|
||||
layout->meta.magic = 0x49414950;
|
||||
layout->meta.version = 1;
|
||||
layout->meta.total_size = shared_memory_size;
|
||||
|
||||
String shmName = std::format("ia_shm_{}", sid);
|
||||
auto mapRes = FileOps::MapSharedMemory(shmName, sharedMemorySize, TRUE);
|
||||
if (!mapRes.has_value())
|
||||
return MakeUnexpected("Failed to map shared memory");
|
||||
const u64 header_size = IpcSharedMemoryLayout::get_header_size();
|
||||
const u64 usable_bytes = shared_memory_size - header_size;
|
||||
|
||||
PUINT8 mappedPtr = mapRes.value();
|
||||
u64 half_size = (usable_bytes / 2);
|
||||
half_size -= (half_size % 64);
|
||||
|
||||
auto *layout = reinterpret_cast<IPC_SharedMemoryLayout *>(mappedPtr);
|
||||
layout->moni_data_offset = header_size;
|
||||
layout->moni_data_size = half_size;
|
||||
|
||||
layout->Meta.Magic = 0x49414950;
|
||||
layout->Meta.Version = 1;
|
||||
layout->Meta.TotalSize = sharedMemorySize;
|
||||
layout->mino_data_offset = header_size + half_size;
|
||||
layout->mino_data_size = half_size;
|
||||
|
||||
UINT64 headerSize = IPC_SharedMemoryLayout::GetHeaderSize();
|
||||
UINT64 usableBytes = sharedMemorySize - headerSize;
|
||||
session->moni = make_box<RingBufferView>(
|
||||
&layout->moni_control,
|
||||
Span<u8>(session->mapped_ptr + layout->moni_data_offset,
|
||||
static_cast<usize>(layout->moni_data_size)),
|
||||
true);
|
||||
|
||||
UINT64 halfSize = (usableBytes / 2);
|
||||
halfSize -= (halfSize % 64);
|
||||
session->mino = make_box<RingBufferView>(
|
||||
&layout->mino_control,
|
||||
Span<u8>(session->mapped_ptr + layout->mino_data_offset,
|
||||
static_cast<usize>(layout->mino_data_size)),
|
||||
true);
|
||||
|
||||
layout->MONI_DataOffset = headerSize;
|
||||
layout->MONI_DataSize = halfSize;
|
||||
IpcConnectionDescriptor desc;
|
||||
desc.socket_path = sock_path;
|
||||
desc.shared_mem_path = shm_name;
|
||||
desc.shared_mem_size = shared_memory_size;
|
||||
|
||||
layout->MINO_DataOffset = headerSize + halfSize;
|
||||
layout->MINO_DataSize = halfSize;
|
||||
const String args = std::format("\"{}\"", desc.serialize());
|
||||
|
||||
session->MONI = std::make_unique<RingBufferView>(
|
||||
&layout->MONI_Control, Span<UINT8>(mappedPtr + layout->MONI_DataOffset, layout->MONI_DataSize), TRUE);
|
||||
IA_TRY(session->node_process,
|
||||
ProcessOps::spawn_process_async(
|
||||
FileOps::normalize_executable_path(executable_path).string(), args,
|
||||
[sid](StringView line) {
|
||||
if (env::is_debug) {
|
||||
std::cout << std::format("{}[Node:{}:STDOUT|STDERR]: {}{}\n",
|
||||
console::MAGENTA, sid, line,
|
||||
console::RESET);
|
||||
}
|
||||
},
|
||||
[sid](Result<i32> result) {
|
||||
if (env::is_debug) {
|
||||
if (!result) {
|
||||
std::cout << std::format(
|
||||
"{}[Node: {}]: Failed to spawn with error '{}'{}\n",
|
||||
console::RED, sid, result.error(), console::RESET);
|
||||
} else {
|
||||
std::cout << std::format(
|
||||
"{}[Node: {}]: Exited with code {}{}\n", console::RED,
|
||||
sid, *result, console::RESET);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
session->MINO = std::make_unique<RingBufferView>(
|
||||
&layout->MINO_Control, Span<UINT8>(mappedPtr + layout->MINO_DataOffset, layout->MINO_DataSize), TRUE);
|
||||
// Give some time for child node to stablize
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
if (!session->node_process->is_active()) {
|
||||
return fail("Failed to spawn the child process \"{}\"",
|
||||
executable_path.string());
|
||||
}
|
||||
|
||||
IPC_ConnectionDescriptor desc;
|
||||
desc.SocketPath = sockPath;
|
||||
desc.SharedMemPath = shmName;
|
||||
desc.SharedMemSize = sharedMemorySize;
|
||||
const auto process_id = session->node_process->id.load();
|
||||
|
||||
String args = std::format("\"{}\"", desc.Serialize());
|
||||
session->shared_mem_name = shm_name;
|
||||
session->creation_time = std::chrono::system_clock::now();
|
||||
m_pending_sessions.push_back(std::move(session));
|
||||
|
||||
session->NodeProcess = ProcessOps::SpawnProcessAsync(
|
||||
FileOps::NormalizeExecutablePath(executablePath).string(), args,
|
||||
[sid](IN StringView line) {
|
||||
UNUSED(sid);
|
||||
UNUSED(line);
|
||||
#if __IA_DEBUG
|
||||
puts(std::format(__CC_MAGENTA "[Node:{}:STDOUT|STDERR]: {}" __CC_DEFAULT, sid, line).c_str());
|
||||
#endif
|
||||
},
|
||||
[sid](IN EXPECT(INT32) result) {
|
||||
UNUSED(sid);
|
||||
UNUSED(result);
|
||||
#if __IA_DEBUG
|
||||
if (!result)
|
||||
puts(std::format(__CC_RED "Failed to spawn Node: {} with error '{}'" __CC_DEFAULT, sid,
|
||||
result.error())
|
||||
.c_str());
|
||||
else
|
||||
puts(std::format(__CC_RED "[Node: {}]: Exited with code {}" __CC_DEFAULT, sid, *result).c_str());
|
||||
#endif
|
||||
});
|
||||
return process_id;
|
||||
}
|
||||
|
||||
// Give some time for child node to stablize
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
if (!session->NodeProcess->IsActive())
|
||||
return MakeUnexpected(std::format("Failed to spawn the child process \"{}\"", executablePath.string()));
|
||||
|
||||
auto processID = session->NodeProcess->ID.load();
|
||||
|
||||
session->CreationTime = SteadyClock::now();
|
||||
m_pendingSessions.push_back(std::move(session));
|
||||
|
||||
return processID;
|
||||
auto IpcManager::wait_till_node_is_online(NativeProcessID node_id) -> bool {
|
||||
bool is_pending = true;
|
||||
while (is_pending) {
|
||||
is_pending = false;
|
||||
for (const auto &session : m_pending_sessions) {
|
||||
if (session->node_process->id.load() == node_id) {
|
||||
is_pending = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
update();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
return m_active_session_map.contains(node_id);
|
||||
}
|
||||
|
||||
BOOL IPC_Manager::WaitTillNodeIsOnline(IN NativeProcessID nodeID)
|
||||
{
|
||||
BOOL isPending = true;
|
||||
while (isPending)
|
||||
{
|
||||
isPending = false;
|
||||
for (auto it = m_pendingSessions.begin(); it != m_pendingSessions.end(); it++)
|
||||
{
|
||||
if (it->get()->NodeProcess->ID.load() == nodeID)
|
||||
{
|
||||
isPending = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Update();
|
||||
}
|
||||
return m_activeSessionMap.contains(nodeID);
|
||||
}
|
||||
void IpcManager::shutdown_node(NativeProcessID node_id) {
|
||||
const auto it_node = m_active_session_map.find(node_id);
|
||||
if (it_node == m_active_session_map.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
VOID IPC_Manager::ShutdownNode(IN NativeProcessID nodeID)
|
||||
{
|
||||
const auto itNode = m_activeSessionMap.find(nodeID);
|
||||
if (itNode == m_activeSessionMap.end())
|
||||
return;
|
||||
auto *node = it_node->second;
|
||||
|
||||
auto &node = itNode->second;
|
||||
ProcessOps::terminate_process(node->node_process);
|
||||
FileOps::unmap_file(node->mapped_ptr);
|
||||
FileOps::unlink_shared_memory(node->shared_mem_name);
|
||||
SocketOps::close(node->data_socket);
|
||||
|
||||
ProcessOps::TerminateProcess(node->NodeProcess);
|
||||
FileOps::UnmapFile(node->MappedPtr);
|
||||
FileOps::UnlinkSharedMemory(node->SharedMemName);
|
||||
SocketOps::Close(node->DataSocket);
|
||||
std::erase_if(m_active_sessions,
|
||||
[&](const auto &s) { return s.get() == node; });
|
||||
m_active_session_map.erase(it_node);
|
||||
}
|
||||
|
||||
for (auto it = m_activeSessions.begin(); it != m_activeSessions.end(); it++)
|
||||
{
|
||||
if (it->get() == node)
|
||||
{
|
||||
m_activeSessions.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
void IpcManager::send_signal(NativeProcessID node, u8 signal) {
|
||||
const auto it_node = m_active_session_map.find(node);
|
||||
if (it_node == m_active_session_map.end()) {
|
||||
return;
|
||||
}
|
||||
it_node->second->send_signal(signal);
|
||||
}
|
||||
|
||||
m_activeSessionMap.erase(itNode);
|
||||
}
|
||||
|
||||
VOID IPC_Manager::SendSignal(IN NativeProcessID node, IN UINT8 signal)
|
||||
{
|
||||
const auto itNode = m_activeSessionMap.find(node);
|
||||
if (itNode == m_activeSessionMap.end())
|
||||
return;
|
||||
itNode->second->SendSignal(signal);
|
||||
}
|
||||
|
||||
VOID IPC_Manager::SendPacket(IN NativeProcessID node, IN UINT16 packetID, IN Span<CONST UINT8> payload)
|
||||
{
|
||||
const auto itNode = m_activeSessionMap.find(node);
|
||||
if (itNode == m_activeSessionMap.end())
|
||||
return;
|
||||
itNode->second->SendPacket(packetID, payload);
|
||||
}
|
||||
void IpcManager::send_packet(NativeProcessID node, u16 packet_id,
|
||||
Span<const u8> payload) {
|
||||
const auto it_node = m_active_session_map.find(node);
|
||||
if (it_node == m_active_session_map.end()) {
|
||||
return;
|
||||
}
|
||||
it_node->second->send_packet(packet_id, payload);
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -17,27 +17,5 @@
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
EXPECT(nlohmann::json) JSON::Parse(IN CONST String &json)
|
||||
{
|
||||
const auto parseResult = nlohmann::json::parse(json, nullptr, false, true);
|
||||
if (parseResult.is_discarded())
|
||||
return MakeUnexpected("Failed to parse JSON");
|
||||
return parseResult;
|
||||
}
|
||||
|
||||
EXPECT(Pair<SharedPtr<simdjson::dom::parser>, simdjson::dom::object>) JSON::ParseReadOnly(IN CONST String &json)
|
||||
{
|
||||
auto parser = std::make_shared<simdjson::dom::parser>();
|
||||
|
||||
simdjson::error_code error{};
|
||||
simdjson::dom::object object;
|
||||
if ((error = parser->parse(json).get(object)))
|
||||
return MakeUnexpected(std::format("Failed to parse JSON : {}", simdjson::error_message(error)));
|
||||
return std::make_pair(IA_MOVE(parser), IA_MOVE(object));
|
||||
}
|
||||
|
||||
String JSON::Encode(IN nlohmann::json data)
|
||||
{
|
||||
return data.dump();
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -1,72 +1,66 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/Logger.hpp>
|
||||
#include <IACore/IACore.hpp>
|
||||
#include <IACore/FileOps.hpp>
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
Logger::ELogLevel Logger::s_logLevel{Logger::ELogLevel::INFO};
|
||||
std::ofstream Logger::s_logFile{};
|
||||
namespace IACore {
|
||||
Logger::LogLevel Logger::m_log_level = Logger::LogLevel::Info;
|
||||
std::ofstream Logger::m_log_file;
|
||||
|
||||
VOID Logger::Initialize()
|
||||
{
|
||||
}
|
||||
static auto get_seconds_count() -> f64 {
|
||||
static const auto start_time = std::chrono::steady_clock::now();
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const std::chrono::duration<f64> duration = now - start_time;
|
||||
return duration.count();
|
||||
}
|
||||
|
||||
VOID Logger::Terminate()
|
||||
{
|
||||
if (s_logFile.is_open())
|
||||
{
|
||||
s_logFile.flush();
|
||||
s_logFile.close();
|
||||
}
|
||||
}
|
||||
auto Logger::initialize() -> void {}
|
||||
|
||||
BOOL Logger::EnableLoggingToDisk(IN PCCHAR filePath)
|
||||
{
|
||||
if (s_logFile.is_open())
|
||||
{
|
||||
s_logFile.flush();
|
||||
s_logFile.close();
|
||||
}
|
||||
s_logFile.open(filePath);
|
||||
return s_logFile.is_open();
|
||||
}
|
||||
auto Logger::terminate() -> void {
|
||||
if (m_log_file.is_open()) {
|
||||
m_log_file.flush();
|
||||
m_log_file.close();
|
||||
}
|
||||
}
|
||||
|
||||
VOID Logger::SetLogLevel(IN ELogLevel logLevel)
|
||||
{
|
||||
s_logLevel = logLevel;
|
||||
}
|
||||
auto Logger::enable_logging_to_disk(const char *file_path) -> Result<void> {
|
||||
if (m_log_file.is_open()) {
|
||||
m_log_file.flush();
|
||||
m_log_file.close();
|
||||
}
|
||||
|
||||
VOID Logger::FlushLogs()
|
||||
{
|
||||
std::cout.flush();
|
||||
if (s_logFile)
|
||||
s_logFile.flush();
|
||||
}
|
||||
m_log_file.open(file_path);
|
||||
|
||||
VOID Logger::LogInternal(IN PCCHAR prefix, IN PCCHAR tag, IN String &&msg)
|
||||
{
|
||||
const auto outLine = std::format("[{:>8.3f}]: [{}]: {}", GetSecondsCount(), tag, msg);
|
||||
std::cout << prefix << outLine << "\033[39m\n";
|
||||
if (s_logFile)
|
||||
{
|
||||
s_logFile.write(outLine.data(), outLine.size());
|
||||
s_logFile.put('\n');
|
||||
s_logFile.flush();
|
||||
}
|
||||
}
|
||||
if (!m_log_file.is_open()) {
|
||||
return fail("Failed to open log file: {}", file_path);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Logger::set_log_level(LogLevel log_level) -> void {
|
||||
m_log_level = log_level;
|
||||
}
|
||||
|
||||
auto Logger::flush_logs() -> void {
|
||||
std::cout.flush();
|
||||
if (m_log_file.is_open()) {
|
||||
m_log_file.flush();
|
||||
}
|
||||
}
|
||||
|
||||
auto Logger::log_internal(const char *prefix, const char *tag, String &&msg)
|
||||
-> void {
|
||||
const auto seconds = get_seconds_count();
|
||||
const auto out_line = std::format("[{:>8.3f}]: [{}]: {}", seconds, tag, msg);
|
||||
|
||||
std::cout << prefix << out_line << console::RESET << '\n';
|
||||
|
||||
if (m_log_file.is_open()) {
|
||||
m_log_file.write(out_line.data(),
|
||||
static_cast<std::streamsize>(out_line.size()));
|
||||
m_log_file.put('\n');
|
||||
m_log_file.flush();
|
||||
}
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -15,106 +15,130 @@
|
||||
|
||||
#include <IACore/Platform.hpp>
|
||||
|
||||
#if IA_ARCH_X64
|
||||
|
||||
#if defined(IA_ARCH_X64)
|
||||
# ifndef _MSC_VER
|
||||
# include <cpuid.h>
|
||||
# endif
|
||||
|
||||
#elif IA_ARCH_ARM64
|
||||
|
||||
#elif defined(IA_ARCH_ARM64)
|
||||
# if defined(__linux__) || defined(__ANDROID__)
|
||||
# include <sys/auxv.h>
|
||||
# include <asm/hwcap.h>
|
||||
# endif
|
||||
|
||||
#endif
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
Platform::Capabilities Platform::s_capabilities{};
|
||||
|
||||
#if IA_ARCH_X64
|
||||
VOID Platform::CPUID(IN INT32 function, IN INT32 subFunction, OUT INT32 out[4])
|
||||
#if defined(IA_ARCH_X64)
|
||||
auto Platform::cpuid(i32 function, i32 sub_function, i32 out[4]) -> void
|
||||
{
|
||||
# ifdef _MSC_VER
|
||||
__cpuidex(out, function, subFunction);
|
||||
__cpuidex(reinterpret_cast<int *>(out), static_cast<int>(function), static_cast<int>(sub_function));
|
||||
# else
|
||||
__cpuid_count(function, subFunction, out[0], out[1], out[2], out[3]);
|
||||
u32 a = 0;
|
||||
u32 b = 0;
|
||||
u32 c = 0;
|
||||
u32 d = 0;
|
||||
__cpuid_count(function, sub_function, a, b, c, d);
|
||||
out[0] = static_cast<i32>(a);
|
||||
out[1] = static_cast<i32>(b);
|
||||
out[2] = static_cast<i32>(c);
|
||||
out[3] = static_cast<i32>(d);
|
||||
# endif
|
||||
}
|
||||
#endif
|
||||
|
||||
BOOL Platform::CheckCPU()
|
||||
auto Platform::check_cpu() -> bool
|
||||
{
|
||||
#if IA_ARCH_X64
|
||||
INT32 cpuInfo[4];
|
||||
#if defined(IA_ARCH_X64)
|
||||
i32 cpu_info[4];
|
||||
|
||||
CPUID(0, 0, cpuInfo);
|
||||
if (cpuInfo[0] < 7)
|
||||
cpuid(0, 0, cpu_info);
|
||||
if (cpu_info[0] < 7)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
cpuid(1, 0, cpu_info);
|
||||
// Bit 27: OSXSAVE, Bit 28: AVX, Bit 12: FMA
|
||||
const bool osxsave = (cpu_info[2] & (1 << 27)) != 0;
|
||||
const bool avx = (cpu_info[2] & (1 << 28)) != 0;
|
||||
const bool fma = (cpu_info[2] & (1 << 12)) != 0;
|
||||
|
||||
CPUID(1, 0, cpuInfo);
|
||||
BOOL osxsave = (cpuInfo[2] & (1 << 27)) != 0;
|
||||
BOOL avx = (cpuInfo[2] & (1 << 28)) != 0;
|
||||
BOOL fma = (cpuInfo[2] & (1 << 12)) != 0;
|
||||
if (!osxsave || !avx || !fma)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UINT64 xcrFeatureMask = _xgetbv(0);
|
||||
if ((xcrFeatureMask & 0x6) != 0x6)
|
||||
const u64 xcr_feature_mask = _xgetbv(0);
|
||||
if ((xcr_feature_mask & 0x6) != 0x6)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CPUID(7, 0, cpuInfo);
|
||||
BOOL avx2 = (cpuInfo[1] & (1 << 5)) != 0;
|
||||
cpuid(7, 0, cpu_info);
|
||||
// Bit 5: AVX2
|
||||
const bool avx2 = (cpu_info[1] & (1 << 5)) != 0;
|
||||
if (!avx2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
s_capabilities.HardwareCRC32 = TRUE;
|
||||
#elif IA_ARCH_ARM64
|
||||
s_capabilities.hardware_crc32 = true;
|
||||
|
||||
#elif defined(IA_ARCH_ARM64)
|
||||
# if defined(__linux__) || defined(__ANDROID__)
|
||||
unsigned long hw_caps = getauxval(AT_HWCAP);
|
||||
const unsigned long hw_caps = getauxval(AT_HWCAP);
|
||||
|
||||
# ifndef HWCAP_CRC32
|
||||
# define HWCAP_CRC32 (1 << 7)
|
||||
# endif
|
||||
|
||||
s_capabilities.HardwareCRC32 = hw_caps & HWCAP_CRC32;
|
||||
# elif defined(__APPLE__)
|
||||
s_capabilities.hardware_crc32 = (hw_caps & HWCAP_CRC32) != 0;
|
||||
# elif defined(IA_PLATFORM_APPLE)
|
||||
// Apple silicon always has hardware CRC32
|
||||
s_capabilities.HardwareCRC32 = TRUE;
|
||||
s_capabilities.hardware_crc32 = true;
|
||||
# else
|
||||
s_capabilities.HardwareCRC32 = FALSE;
|
||||
s_capabilities.hardware_crc32 = false;
|
||||
# endif
|
||||
#else
|
||||
s_capabilities.HardwareCRC32 = FALSE;
|
||||
s_capabilities.hardware_crc32 = false;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
PCCHAR Platform::GetArchitectureName()
|
||||
auto Platform::get_architecture_name() -> const char *
|
||||
{
|
||||
#if IA_ARCH_X64
|
||||
#if defined(IA_ARCH_X64)
|
||||
return "x86_64";
|
||||
#elif IA_ARCH_ARM64
|
||||
#elif defined(IA_ARCH_ARM64)
|
||||
return "aarch64";
|
||||
#elif IA_ARCH_WASM
|
||||
#elif defined(IA_ARCH_WASM)
|
||||
return "wasm";
|
||||
#else
|
||||
return "unknown";
|
||||
#endif
|
||||
}
|
||||
|
||||
PCCHAR Platform::GetOperatingSystemName()
|
||||
auto Platform::get_operating_system_name() -> const char *
|
||||
{
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
return "Windows";
|
||||
#elif IA_PLATFORM_IOS
|
||||
#elif defined(IA_PLATFORM_APPLE)
|
||||
# if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
|
||||
return "iOS";
|
||||
#elif IA_PLATFORM_MAC
|
||||
return "Mac";
|
||||
#elif IA_PLATFORM_ANDROID
|
||||
# else
|
||||
return "macOS";
|
||||
# endif
|
||||
#elif defined(__ANDROID__)
|
||||
return "Android";
|
||||
#elif IA_PLATFORM_LINUX
|
||||
return "Linux";
|
||||
#elif IA_PLATFORM_WASM
|
||||
return "WebAssembly";
|
||||
#else
|
||||
return "Unknown";
|
||||
#endif
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -15,327 +15,317 @@
|
||||
|
||||
#include <IACore/ProcessOps.hpp>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
// ---------------------------------------------------------------------
|
||||
// Output Buffering Helper
|
||||
// Splits raw chunks into lines, preserving partial lines across chunks
|
||||
// ---------------------------------------------------------------------
|
||||
struct LineBuffer
|
||||
{
|
||||
String Accumulator;
|
||||
Function<VOID(StringView)> &Callback;
|
||||
namespace IACore {
|
||||
// ---------------------------------------------------------------------
|
||||
// Output Buffering Helper
|
||||
// Splits raw chunks into lines, preserving partial lines across chunks
|
||||
// ---------------------------------------------------------------------
|
||||
struct LineBuffer {
|
||||
String m_accumulator;
|
||||
std::function<void(StringView)> &m_callback;
|
||||
|
||||
VOID Append(IN PCCHAR data, IN SIZE_T size);
|
||||
VOID Flush();
|
||||
};
|
||||
} // namespace IACore
|
||||
void append(const char *data, usize size);
|
||||
void flush();
|
||||
};
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
NativeProcessID ProcessOps::GetCurrentProcessID()
|
||||
{
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
return ::GetCurrentProcessId();
|
||||
#else
|
||||
return getpid();
|
||||
#endif
|
||||
}
|
||||
|
||||
EXPECT(INT32)
|
||||
|
||||
ProcessOps::SpawnProcessSync(IN CONST String &command, IN CONST String &args,
|
||||
IN Function<VOID(IN StringView line)> onOutputLineCallback)
|
||||
{
|
||||
Atomic<NativeProcessID> id;
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
return SpawnProcessWindows(command, args, onOutputLineCallback, id);
|
||||
#else
|
||||
return SpawnProcessPosix(command, args, onOutputLineCallback, id);
|
||||
#endif
|
||||
}
|
||||
|
||||
SharedPtr<ProcessHandle> ProcessOps::SpawnProcessAsync(IN CONST String &command, IN CONST String &args,
|
||||
IN Function<VOID(IN StringView line)> onOutputLineCallback,
|
||||
IN Function<VOID(EXPECT(INT32))> onFinishCallback)
|
||||
{
|
||||
SharedPtr<ProcessHandle> handle = std::make_shared<ProcessHandle>();
|
||||
handle->IsRunning = true;
|
||||
|
||||
handle->ThreadHandle =
|
||||
JoiningThread([=, h = handle.get(), cmd = IA_MOVE(command), args = std::move(args)]() mutable {
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
auto result = SpawnProcessWindows(cmd, args, onOutputLineCallback, h->ID);
|
||||
#else
|
||||
auto result = SpawnProcessPosix(cmd, args, onOutputLineCallback, h->ID);
|
||||
#endif
|
||||
|
||||
h->IsRunning = false;
|
||||
|
||||
if (!onFinishCallback)
|
||||
return;
|
||||
|
||||
if (!result)
|
||||
onFinishCallback(MakeUnexpected(result.error()));
|
||||
else
|
||||
onFinishCallback(*result);
|
||||
});
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
VOID ProcessOps::TerminateProcess(IN CONST SharedPtr<ProcessHandle> &handle)
|
||||
{
|
||||
if (!handle || !handle->IsActive())
|
||||
return;
|
||||
|
||||
NativeProcessID pid = handle->ID.load();
|
||||
if (pid == 0)
|
||||
return;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
|
||||
if (hProcess != NULL)
|
||||
{
|
||||
::TerminateProcess(hProcess, 9);
|
||||
CloseHandle(hProcess);
|
||||
void LineBuffer::append(const char *data, usize size) {
|
||||
usize start = 0;
|
||||
for (usize i = 0; i < size; ++i) {
|
||||
if (data[i] == '\n' || data[i] == '\r') {
|
||||
// Flush Accumulator + current chunk
|
||||
if (!m_accumulator.empty()) {
|
||||
m_accumulator.append(data + start, i - start);
|
||||
if (!m_accumulator.empty()) {
|
||||
m_callback(m_accumulator);
|
||||
}
|
||||
#else
|
||||
kill(pid, SIGKILL);
|
||||
#endif
|
||||
}
|
||||
} // namespace IACore
|
||||
m_accumulator.clear();
|
||||
} else {
|
||||
if (i > start) {
|
||||
m_callback(StringView(data + start, i - start));
|
||||
}
|
||||
}
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
// Skip \r\n sequence if needed, or just start next
|
||||
if (data[i] == '\r' && i + 1 < size && data[i + 1] == '\n') {
|
||||
i++;
|
||||
}
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
// Save remaining partial line
|
||||
if (start < size) {
|
||||
m_accumulator.append(data + start, size - start);
|
||||
}
|
||||
}
|
||||
|
||||
void LineBuffer::flush() {
|
||||
if (!m_accumulator.empty()) {
|
||||
m_callback(m_accumulator);
|
||||
m_accumulator.clear();
|
||||
}
|
||||
}
|
||||
|
||||
auto ProcessOps::get_current_process_id() -> NativeProcessID {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
EXPECT(INT32)
|
||||
|
||||
ProcessOps::SpawnProcessWindows(IN CONST String &command, IN CONST String &args,
|
||||
IN Function<VOID(StringView)> onOutputLineCallback, OUT Atomic<NativeProcessID> &id)
|
||||
{
|
||||
SECURITY_ATTRIBUTES saAttr = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE}; // Allow inheritance
|
||||
HANDLE hRead = NULL, hWrite = NULL;
|
||||
|
||||
if (!CreatePipe(&hRead, &hWrite, &saAttr, 0))
|
||||
return MakeUnexpected("Failed to create pipe");
|
||||
|
||||
// Ensure the read handle to the pipe for STDOUT is NOT inherited
|
||||
if (!SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0))
|
||||
return MakeUnexpected("Failed to secure pipe handles");
|
||||
|
||||
STARTUPINFOA si = {sizeof(STARTUPINFOA)};
|
||||
si.dwFlags |= STARTF_USESTDHANDLES;
|
||||
si.hStdOutput = hWrite;
|
||||
si.hStdError = hWrite; // Merge stderr
|
||||
si.hStdInput = NULL; // No input
|
||||
|
||||
PROCESS_INFORMATION pi = {0};
|
||||
|
||||
// Windows command line needs to be mutable and concatenated
|
||||
String commandLine = std::format("\"{}\" {}", command, args);
|
||||
|
||||
BOOL success = CreateProcessA(NULL, commandLine.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
|
||||
|
||||
// Close write end in parent, otherwise ReadFile never returns EOF!
|
||||
CloseHandle(hWrite);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
CloseHandle(hRead);
|
||||
return MakeUnexpected(String("CreateProcess failed: ") + std::to_string(GetLastError()));
|
||||
}
|
||||
|
||||
id.store(pi.dwProcessId);
|
||||
|
||||
// Read Loop
|
||||
LineBuffer lineBuf{"", onOutputLineCallback};
|
||||
DWORD bytesRead;
|
||||
CHAR buffer[4096];
|
||||
|
||||
while (ReadFile(hRead, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead != 0)
|
||||
{
|
||||
lineBuf.Append(buffer, bytesRead);
|
||||
}
|
||||
lineBuf.Flush();
|
||||
|
||||
// NOW we wait for exit code
|
||||
DWORD exitCode = 0;
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
GetExitCodeProcess(pi.hProcess, &exitCode);
|
||||
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
CloseHandle(hRead);
|
||||
id.store(0);
|
||||
|
||||
return static_cast<INT32>(exitCode);
|
||||
}
|
||||
return ::GetCurrentProcessId();
|
||||
#else
|
||||
return getpid();
|
||||
#endif
|
||||
}
|
||||
|
||||
auto ProcessOps::spawn_process_sync(
|
||||
const String &command, const String &args,
|
||||
std::function<void(StringView line)> on_output_line_callback)
|
||||
-> Result<i32> {
|
||||
std::atomic<NativeProcessID> id = 0;
|
||||
if constexpr (env::is_windows) {
|
||||
return spawn_process_windows(command, args, on_output_line_callback, id);
|
||||
} else {
|
||||
return spawn_process_posix(command, args, on_output_line_callback, id);
|
||||
}
|
||||
}
|
||||
|
||||
auto ProcessOps::spawn_process_async(
|
||||
const String &command, const String &args,
|
||||
std::function<void(StringView line)> on_output_line_callback,
|
||||
std::function<void(Result<i32>)> on_finish_callback)
|
||||
-> Result<Box<ProcessHandle>> {
|
||||
auto handle = make_box<ProcessHandle>();
|
||||
handle->is_running = true;
|
||||
|
||||
// Capture raw pointer to handle internals safely because jthread joins on
|
||||
// destruction
|
||||
ProcessHandle *h_ptr = handle.get();
|
||||
|
||||
handle->m_thread_handle = std::jthread([h_ptr, cmd = command, arg = args,
|
||||
cb = on_output_line_callback,
|
||||
fin = on_finish_callback]() mutable {
|
||||
Result<i32> result = fail("Platform not supported");
|
||||
|
||||
if constexpr (env::is_windows) {
|
||||
result = spawn_process_windows(cmd, arg, cb, h_ptr->id);
|
||||
} else {
|
||||
result = spawn_process_posix(cmd, arg, cb, h_ptr->id);
|
||||
}
|
||||
|
||||
h_ptr->is_running = false;
|
||||
|
||||
if (fin) {
|
||||
if (!result) {
|
||||
fin(fail(std::move(result.error())));
|
||||
} else {
|
||||
fin(*result);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
void ProcessOps::terminate_process(const Box<ProcessHandle> &handle) {
|
||||
if (!handle || !handle->is_active()) {
|
||||
return;
|
||||
}
|
||||
|
||||
NativeProcessID pid = handle->id.load();
|
||||
if (pid == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
HANDLE h_process = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
|
||||
if (h_process != NULL) {
|
||||
::TerminateProcess(h_process, 9);
|
||||
CloseHandle(h_process);
|
||||
}
|
||||
#endif
|
||||
#if IA_PLATFORM_UNIX
|
||||
EXPECT(INT32)
|
||||
|
||||
ProcessOps::SpawnProcessPosix(IN CONST String &command, IN CONST String &args,
|
||||
IN Function<VOID(StringView)> onOutputLineCallback, OUT Atomic<NativeProcessID> &id)
|
||||
{
|
||||
int pipefd[2];
|
||||
if (pipe(pipefd) == -1)
|
||||
return MakeUnexpected("Failed to create pipe");
|
||||
|
||||
pid_t pid = fork();
|
||||
|
||||
if (pid == -1)
|
||||
{
|
||||
return MakeUnexpected("Failed to fork process");
|
||||
}
|
||||
else if (pid == 0)
|
||||
{
|
||||
// --- Child Process ---
|
||||
close(pipefd[0]);
|
||||
|
||||
dup2(pipefd[1], STDOUT_FILENO);
|
||||
dup2(pipefd[1], STDERR_FILENO);
|
||||
close(pipefd[1]);
|
||||
|
||||
std::vector<std::string> argStorage; // To keep strings alive
|
||||
std::vector<char *> argv;
|
||||
|
||||
std::string cmdStr = command;
|
||||
argv.push_back(cmdStr.data());
|
||||
|
||||
// Manual Quote-Aware Splitter
|
||||
std::string currentToken;
|
||||
bool inQuotes = false;
|
||||
bool isEscaped = false;
|
||||
|
||||
for (char c : args)
|
||||
{
|
||||
if (isEscaped)
|
||||
{
|
||||
// Previous char was '\', so we treat this char literally.
|
||||
currentToken += c;
|
||||
isEscaped = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '\\')
|
||||
{
|
||||
// Escape sequence start
|
||||
isEscaped = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '\"')
|
||||
{
|
||||
// Toggle quote state
|
||||
inQuotes = !inQuotes;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == ' ' && !inQuotes)
|
||||
{
|
||||
// Token boundary
|
||||
if (!currentToken.empty())
|
||||
{
|
||||
argStorage.push_back(currentToken);
|
||||
currentToken.clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
currentToken += c;
|
||||
}
|
||||
}
|
||||
|
||||
if (!currentToken.empty())
|
||||
{
|
||||
argStorage.push_back(currentToken);
|
||||
}
|
||||
|
||||
// Build char* array from the std::string storage
|
||||
for (auto &s : argStorage)
|
||||
{
|
||||
argv.push_back(s.data());
|
||||
}
|
||||
argv.push_back(nullptr);
|
||||
|
||||
execvp(argv[0], argv.data());
|
||||
_exit(127);
|
||||
}
|
||||
else
|
||||
{
|
||||
// --- Parent Process ---
|
||||
id.store(pid);
|
||||
|
||||
close(pipefd[1]);
|
||||
|
||||
LineBuffer lineBuf{"", onOutputLineCallback};
|
||||
char buffer[4096];
|
||||
ssize_t count;
|
||||
|
||||
while ((count = read(pipefd[0], buffer, sizeof(buffer))) > 0)
|
||||
{
|
||||
lineBuf.Append(buffer, count);
|
||||
}
|
||||
lineBuf.Flush();
|
||||
close(pipefd[0]);
|
||||
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
|
||||
id.store(0);
|
||||
if (WIFEXITED(status))
|
||||
return WEXITSTATUS(status);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
kill(pid, SIGKILL);
|
||||
#endif
|
||||
} // namespace IACore
|
||||
}
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
VOID LineBuffer::Append(IN PCCHAR data, IN SIZE_T size)
|
||||
{
|
||||
SIZE_T start = 0;
|
||||
for (SIZE_T i = 0; i < size; ++i)
|
||||
{
|
||||
if (data[i] == '\n' || data[i] == '\r')
|
||||
{
|
||||
// Flush Accumulator + current chunk
|
||||
if (!Accumulator.empty())
|
||||
{
|
||||
Accumulator.append(data + start, i - start);
|
||||
if (!Accumulator.empty())
|
||||
Callback(Accumulator);
|
||||
Accumulator.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i > start)
|
||||
Callback(StringView(data + start, i - start));
|
||||
}
|
||||
auto ProcessOps::spawn_process_windows(
|
||||
const String &command, const String &args,
|
||||
std::function<void(StringView)> on_output_line_callback,
|
||||
std::atomic<NativeProcessID> &id) -> Result<i32> {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
SECURITY_ATTRIBUTES sa_attr = {sizeof(SECURITY_ATTRIBUTES), NULL,
|
||||
TRUE}; // Allow inheritance
|
||||
HANDLE h_read = NULL;
|
||||
HANDLE h_write = NULL;
|
||||
|
||||
// Skip \r\n sequence if needed, or just start next
|
||||
if (data[i] == '\r' && i + 1 < size && data[i + 1] == '\n')
|
||||
i++;
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
// Save remaining partial line
|
||||
if (start < size)
|
||||
{
|
||||
Accumulator.append(data + start, size - start);
|
||||
if (!CreatePipe(&h_read, &h_write, &sa_attr, 0)) {
|
||||
return fail("Failed to create pipe");
|
||||
}
|
||||
|
||||
// Ensure the read handle to the pipe for STDOUT is NOT inherited
|
||||
if (!SetHandleInformation(h_read, HANDLE_FLAG_INHERIT, 0)) {
|
||||
return fail("Failed to secure pipe handles");
|
||||
}
|
||||
|
||||
STARTUPINFOA si = {sizeof(STARTUPINFOA)};
|
||||
si.dwFlags |= STARTF_USESTDHANDLES;
|
||||
si.hStdOutput = h_write;
|
||||
si.hStdError = h_write; // Merge stderr
|
||||
si.hStdInput = NULL; // No input
|
||||
|
||||
PROCESS_INFORMATION pi = {0};
|
||||
|
||||
// Windows command line needs to be mutable and concatenated
|
||||
String command_line = std::format("\"{}\" {}", command, args);
|
||||
|
||||
BOOL success = CreateProcessA(NULL, command_line.data(), NULL, NULL, TRUE, 0,
|
||||
NULL, NULL, &si, &pi);
|
||||
|
||||
// Close write end in parent, otherwise ReadFile never returns EOF!
|
||||
CloseHandle(h_write);
|
||||
|
||||
if (!success) {
|
||||
CloseHandle(h_read);
|
||||
return fail("CreateProcess failed: {}", GetLastError());
|
||||
}
|
||||
|
||||
id.store(pi.dwProcessId);
|
||||
|
||||
// Read Loop
|
||||
LineBuffer line_buf{"", on_output_line_callback};
|
||||
DWORD bytes_read = 0;
|
||||
char buffer[4096];
|
||||
|
||||
while (ReadFile(h_read, buffer, sizeof(buffer), &bytes_read, NULL) &&
|
||||
bytes_read != 0) {
|
||||
line_buf.append(buffer, bytes_read);
|
||||
}
|
||||
line_buf.flush();
|
||||
|
||||
// NOW we wait for exit code
|
||||
DWORD exit_code = 0;
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
GetExitCodeProcess(pi.hProcess, &exit_code);
|
||||
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
CloseHandle(h_read);
|
||||
id.store(0);
|
||||
|
||||
return static_cast<i32>(exit_code);
|
||||
#else
|
||||
(void)command;
|
||||
(void)args;
|
||||
(void)on_output_line_callback;
|
||||
(void)id;
|
||||
return fail("Windows implementation not available.");
|
||||
#endif
|
||||
}
|
||||
|
||||
auto ProcessOps::spawn_process_posix(
|
||||
const String &command, const String &args,
|
||||
std::function<void(StringView)> on_output_line_callback,
|
||||
std::atomic<NativeProcessID> &id) -> Result<i32> {
|
||||
#if IA_PLATFORM_UNIX
|
||||
int pipefd[2];
|
||||
if (pipe(pipefd) == -1) {
|
||||
return fail("Failed to create pipe");
|
||||
}
|
||||
|
||||
pid_t pid = fork();
|
||||
|
||||
if (pid == -1) {
|
||||
return fail("Failed to fork process");
|
||||
} else if (pid == 0) {
|
||||
// --- Child Process ---
|
||||
close(pipefd[0]);
|
||||
|
||||
dup2(pipefd[1], STDOUT_FILENO);
|
||||
dup2(pipefd[1], STDERR_FILENO);
|
||||
close(pipefd[1]);
|
||||
|
||||
Vec<String> arg_storage; // To keep strings alive
|
||||
Vec<char *> argv;
|
||||
|
||||
String cmd_str = command;
|
||||
argv.push_back(cmd_str.data());
|
||||
|
||||
// Manual Quote-Aware Splitter
|
||||
String current_token;
|
||||
bool in_quotes = false;
|
||||
bool is_escaped = false;
|
||||
|
||||
for (char c : args) {
|
||||
if (is_escaped) {
|
||||
// Previous char was '\', so we treat this char literally.
|
||||
current_token += c;
|
||||
is_escaped = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '\\') {
|
||||
// Escape sequence start
|
||||
is_escaped = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '\"') {
|
||||
// Toggle quote state
|
||||
in_quotes = !in_quotes;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == ' ' && !in_quotes) {
|
||||
// Token boundary
|
||||
if (!current_token.empty()) {
|
||||
arg_storage.push_back(current_token);
|
||||
current_token.clear();
|
||||
}
|
||||
} else {
|
||||
current_token += c;
|
||||
}
|
||||
}
|
||||
|
||||
VOID LineBuffer::Flush()
|
||||
{
|
||||
if (!Accumulator.empty())
|
||||
{
|
||||
Callback(Accumulator);
|
||||
Accumulator.clear();
|
||||
}
|
||||
if (!current_token.empty()) {
|
||||
arg_storage.push_back(current_token);
|
||||
}
|
||||
|
||||
// Build char* array from the std::string storage
|
||||
for (auto &s : arg_storage) {
|
||||
argv.push_back(s.data());
|
||||
}
|
||||
argv.push_back(nullptr);
|
||||
|
||||
execvp(argv[0], argv.data());
|
||||
_exit(127);
|
||||
} else {
|
||||
// --- Parent Process ---
|
||||
id.store(pid);
|
||||
|
||||
close(pipefd[1]);
|
||||
|
||||
LineBuffer line_buf{"", on_output_line_callback};
|
||||
char buffer[4096];
|
||||
ssize_t count;
|
||||
|
||||
while ((count = read(pipefd[0], buffer, sizeof(buffer))) > 0) {
|
||||
line_buf.append(buffer, static_cast<usize>(count));
|
||||
}
|
||||
line_buf.flush();
|
||||
close(pipefd[0]);
|
||||
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
|
||||
id.store(0);
|
||||
if (WIFEXITED(status)) {
|
||||
return WEXITSTATUS(status);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
(void)command;
|
||||
(void)args;
|
||||
(void)on_output_line_callback;
|
||||
(void)id;
|
||||
return fail("Posix implementation not available.");
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace IACore
|
||||
@ -14,92 +14,133 @@
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/SocketOps.hpp>
|
||||
#include <cstring>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
INT32 SocketOps::s_initCount{0};
|
||||
namespace IACore {
|
||||
i32 SocketOps::s_init_count = 0;
|
||||
|
||||
VOID SocketOps::Close(IN SocketHandle sock)
|
||||
{
|
||||
if (sock == INVALID_SOCKET)
|
||||
return;
|
||||
CLOSE_SOCKET(sock);
|
||||
}
|
||||
|
||||
BOOL SocketOps::Listen(IN SocketHandle sock, IN INT32 queueSize)
|
||||
{
|
||||
return listen(sock, queueSize) == 0;
|
||||
}
|
||||
|
||||
SocketHandle SocketOps::CreateUnixSocket()
|
||||
{
|
||||
return socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
}
|
||||
|
||||
BOOL SocketOps::BindUnixSocket(IN SocketHandle sock, IN PCCHAR path)
|
||||
{
|
||||
if (!IS_VALID_SOCKET(sock))
|
||||
return FALSE;
|
||||
|
||||
UNLINK_FILE(path);
|
||||
|
||||
sockaddr_un addr{};
|
||||
addr.sun_family = AF_UNIX;
|
||||
|
||||
size_t maxLen = sizeof(addr.sun_path) - 1;
|
||||
|
||||
strncpy(addr.sun_path, path, maxLen);
|
||||
|
||||
if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) == -1)
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL SocketOps::ConnectUnixSocket(IN SocketHandle sock, IN PCCHAR path)
|
||||
{
|
||||
if (!IS_VALID_SOCKET(sock))
|
||||
return FALSE;
|
||||
|
||||
sockaddr_un addr{};
|
||||
addr.sun_family = AF_UNIX;
|
||||
|
||||
size_t maxLen = sizeof(addr.sun_path) - 1;
|
||||
|
||||
strncpy(addr.sun_path, path, maxLen);
|
||||
|
||||
if (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) == -1)
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL SocketOps::IsPortAvailable(IN UINT16 port, IN INT32 type)
|
||||
{
|
||||
SocketHandle sock = socket(AF_INET, type, IPPROTO_UDP);
|
||||
if (!IS_VALID_SOCKET(sock))
|
||||
return false;
|
||||
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
|
||||
bool isFree = false;
|
||||
if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) == 0)
|
||||
isFree = true;
|
||||
|
||||
CLOSE_SOCKET(sock);
|
||||
|
||||
return isFree;
|
||||
}
|
||||
|
||||
BOOL SocketOps::IsWouldBlock()
|
||||
{
|
||||
auto SocketOps::close(SocketHandle sock) -> void {
|
||||
if (sock == INVALID_SOCKET) {
|
||||
return;
|
||||
}
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
return WSAGetLastError() == WSAEWOULDBLOCK;
|
||||
closesocket(sock);
|
||||
#else
|
||||
return errno == EWOULDBLOCK || errno == EAGAIN;
|
||||
::close(sock);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
auto SocketOps::listen(SocketHandle sock, i32 queue_size) -> Result<void> {
|
||||
if (::listen(sock, queue_size) == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
return fail("listen failed: {}", WSAGetLastError());
|
||||
#else
|
||||
return fail("listen failed: {}", errno);
|
||||
#endif
|
||||
}
|
||||
|
||||
auto SocketOps::create_unix_socket() -> Result<SocketHandle> {
|
||||
const SocketHandle sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (sock == INVALID_SOCKET) {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
return fail("socket(AF_UNIX) failed: {}", WSAGetLastError());
|
||||
#else
|
||||
return fail("socket(AF_UNIX) failed: {}", errno);
|
||||
#endif
|
||||
}
|
||||
return sock;
|
||||
}
|
||||
|
||||
auto SocketOps::bind_unix_socket(SocketHandle sock, const char *path)
|
||||
-> Result<void> {
|
||||
if (sock == INVALID_SOCKET) {
|
||||
return fail("Invalid socket handle");
|
||||
}
|
||||
|
||||
unlink_file(path);
|
||||
|
||||
sockaddr_un addr{};
|
||||
addr.sun_family = AF_UNIX;
|
||||
|
||||
const usize max_len = sizeof(addr.sun_path) - 1;
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
strncpy_s(addr.sun_path, sizeof(addr.sun_path), path, max_len);
|
||||
#else
|
||||
std::strncpy(addr.sun_path, path, max_len);
|
||||
#endif
|
||||
|
||||
if (::bind(sock, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) ==
|
||||
-1) {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
return fail("bind failed: {}", WSAGetLastError());
|
||||
#else
|
||||
return fail("bind failed: {}", errno);
|
||||
#endif
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto SocketOps::connect_unix_socket(SocketHandle sock, const char *path)
|
||||
-> Result<void> {
|
||||
if (sock == INVALID_SOCKET) {
|
||||
return fail("Invalid socket handle");
|
||||
}
|
||||
|
||||
sockaddr_un addr{};
|
||||
addr.sun_family = AF_UNIX;
|
||||
|
||||
const usize max_len = sizeof(addr.sun_path) - 1;
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
strncpy_s(addr.sun_path, sizeof(addr.sun_path), path, max_len);
|
||||
#else
|
||||
std::strncpy(addr.sun_path, path, max_len);
|
||||
#endif
|
||||
|
||||
if (::connect(sock, reinterpret_cast<struct sockaddr *>(&addr),
|
||||
sizeof(addr)) == -1) {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
return fail("connect failed: {}", WSAGetLastError());
|
||||
#else
|
||||
return fail("connect failed: {}", errno);
|
||||
#endif
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto SocketOps::is_port_available(u16 port, i32 type) -> bool {
|
||||
// Use 0 for protocol to let OS select default (TCP for STREAM, UDP for DGRAM)
|
||||
const SocketHandle sock = socket(AF_INET, type, 0);
|
||||
if (sock == INVALID_SOCKET) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
|
||||
bool is_free = false;
|
||||
if (::bind(sock, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) ==
|
||||
0) {
|
||||
is_free = true;
|
||||
}
|
||||
|
||||
close(sock);
|
||||
|
||||
return is_free;
|
||||
}
|
||||
|
||||
auto SocketOps::is_would_block() -> bool {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
return WSAGetLastError() == WSAEWOULDBLOCK;
|
||||
#else
|
||||
return errno == EWOULDBLOCK || errno == EAGAIN;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace IACore
|
||||
@ -13,46 +13,36 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/StreamReader.hpp>
|
||||
#include <IACore/FileOps.hpp>
|
||||
#include <IACore/Logger.hpp>
|
||||
#include <IACore/StreamReader.hpp>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
StreamReader::StreamReader(IN CONST FilePath &path) : m_storageType(EStorageType::OWNING_MMAP)
|
||||
{
|
||||
const auto t = FileOps::MapFile(path, m_dataSize);
|
||||
if (!t)
|
||||
{
|
||||
Logger::Error("Failed to memory map file {}", path.string());
|
||||
return;
|
||||
}
|
||||
m_data = *t;
|
||||
}
|
||||
namespace IACore {
|
||||
auto StreamReader::create_from_file(const Path &path) -> Result<StreamReader> {
|
||||
usize size = 0;
|
||||
|
||||
StreamReader::StreamReader(IN Vector<UINT8> &&data)
|
||||
: m_owningVector(IA_MOVE(data)), m_storageType(EStorageType::OWNING_VECTOR)
|
||||
{
|
||||
m_data = m_owningVector.data();
|
||||
m_dataSize = m_owningVector.size();
|
||||
}
|
||||
const u8 *ptr;
|
||||
IA_TRY(ptr, FileOps::map_file(path, size));
|
||||
|
||||
StreamReader::StreamReader(IN Span<CONST UINT8> data)
|
||||
: m_data(data.data()), m_dataSize(data.size()), m_storageType(EStorageType::NON_OWNING)
|
||||
{
|
||||
}
|
||||
StreamReader reader(Span<const u8>(ptr, size));
|
||||
reader.m_storage_type = StorageType::OwningMmap;
|
||||
|
||||
StreamReader::~StreamReader()
|
||||
{
|
||||
switch (m_storageType)
|
||||
{
|
||||
case EStorageType::OWNING_MMAP:
|
||||
FileOps::UnmapFile(m_data);
|
||||
break;
|
||||
return reader;
|
||||
}
|
||||
|
||||
case EStorageType::NON_OWNING:
|
||||
case EStorageType::OWNING_VECTOR:
|
||||
break;
|
||||
}
|
||||
}
|
||||
StreamReader::StreamReader(Vec<u8> &&data)
|
||||
: m_owning_vector(std::move(data)),
|
||||
m_storage_type(StorageType::OwningVector) {
|
||||
m_data = m_owning_vector.data();
|
||||
m_data_size = m_owning_vector.size();
|
||||
}
|
||||
|
||||
StreamReader::StreamReader(Span<const u8> data)
|
||||
: m_data(data.data()), m_data_size(data.size()),
|
||||
m_storage_type(StorageType::NonOwning) {}
|
||||
|
||||
StreamReader::~StreamReader() {
|
||||
if (m_storage_type == StorageType::OwningMmap) {
|
||||
FileOps::unmap_file(m_data);
|
||||
}
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -14,83 +14,88 @@
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/StreamWriter.hpp>
|
||||
#include <IACore/Logger.hpp>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
StreamWriter::StreamWriter() : m_storageType(EStorageType::OWNING_VECTOR)
|
||||
{
|
||||
m_owningVector.resize(m_capacity = 256);
|
||||
m_buffer = m_owningVector.data();
|
||||
namespace IACore {
|
||||
|
||||
auto StreamWriter::create(const Path &path) -> Result<StreamWriter> {
|
||||
// Try to open the file to ensure we have write permissions and to truncate
|
||||
// it.
|
||||
FILE *f = std::fopen(path.string().c_str(), "wb");
|
||||
if (!f) {
|
||||
return fail("Failed to open file for writing: {}", path.string());
|
||||
}
|
||||
std::fclose(f);
|
||||
|
||||
StreamWriter writer;
|
||||
writer.m_file_path = path;
|
||||
writer.m_storage_type = StorageType::OwningFile;
|
||||
|
||||
return writer;
|
||||
}
|
||||
|
||||
StreamWriter::StreamWriter() : m_storage_type(StorageType::OwningVector) {
|
||||
m_capacity = 256;
|
||||
m_owning_vector.resize(m_capacity);
|
||||
m_buffer = m_owning_vector.data();
|
||||
}
|
||||
|
||||
StreamWriter::StreamWriter(Span<u8> data)
|
||||
: m_buffer(data.data()), m_cursor(0), m_capacity(data.size()),
|
||||
m_storage_type(StorageType::NonOwning) {}
|
||||
|
||||
StreamWriter::~StreamWriter() {
|
||||
if (m_storage_type == StorageType::OwningFile) {
|
||||
if (m_file_path.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
StreamWriter::StreamWriter(IN Span<UINT8> data)
|
||||
: m_buffer(data.data()), m_capacity(data.size()), m_storageType(EStorageType::NON_OWNING)
|
||||
{
|
||||
FILE *f = std::fopen(m_file_path.string().c_str(), "wb");
|
||||
if (f) {
|
||||
// Write the actual data accumulated in the buffer.
|
||||
std::fwrite(m_buffer, 1, m_cursor, f);
|
||||
std::fclose(f);
|
||||
} else {
|
||||
// Logger is disabled, print to stderr as a fallback for data loss
|
||||
// warning.
|
||||
std::fprintf(stderr, "[IACore] StreamWriter failed to save file: %s\n",
|
||||
m_file_path.string().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto StreamWriter::write(u8 byte, usize count) -> Result<void> {
|
||||
if (m_cursor + count > m_capacity) {
|
||||
if (m_storage_type == StorageType::NonOwning) {
|
||||
return fail("StreamWriter buffer overflow (NonOwning)");
|
||||
}
|
||||
|
||||
StreamWriter::StreamWriter(IN CONST FilePath &path) : m_filePath(path), m_storageType(EStorageType::OWNING_FILE)
|
||||
{
|
||||
IA_RELEASE_ASSERT(!path.empty());
|
||||
const auto f = fopen(m_filePath.string().c_str(), "wb");
|
||||
if (!f)
|
||||
{
|
||||
Logger::Error("Failed to open file for writing {}", m_filePath.string().c_str());
|
||||
return;
|
||||
}
|
||||
fputc(0, f);
|
||||
fclose(f);
|
||||
// Growth strategy: Current capacity + (count * 2)
|
||||
const usize new_capacity = m_capacity + (count << 1);
|
||||
m_owning_vector.resize(new_capacity);
|
||||
m_capacity = m_owning_vector.size();
|
||||
m_buffer = m_owning_vector.data();
|
||||
}
|
||||
|
||||
m_owningVector.resize(m_capacity = 256);
|
||||
m_buffer = m_owningVector.data();
|
||||
std::memset(m_buffer + m_cursor, byte, count);
|
||||
m_cursor += count;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto StreamWriter::write(const void *buffer, usize size) -> Result<void> {
|
||||
if (m_cursor + size > m_capacity) {
|
||||
if (m_storage_type == StorageType::NonOwning) {
|
||||
return fail("StreamWriter buffer overflow (NonOwning)");
|
||||
}
|
||||
|
||||
StreamWriter::~StreamWriter()
|
||||
{
|
||||
switch (m_storageType)
|
||||
{
|
||||
case EStorageType::OWNING_FILE: {
|
||||
IA_RELEASE_ASSERT(!m_filePath.empty());
|
||||
const auto f = fopen(m_filePath.string().c_str(), "wb");
|
||||
if (!f)
|
||||
{
|
||||
Logger::Error("Failed to open file for writing {}", m_filePath.string().c_str());
|
||||
return;
|
||||
}
|
||||
fwrite(m_owningVector.data(), 1, m_owningVector.size(), f);
|
||||
fclose(f);
|
||||
}
|
||||
break;
|
||||
const usize new_capacity = m_capacity + (size << 1);
|
||||
m_owning_vector.resize(new_capacity);
|
||||
m_capacity = m_owning_vector.size();
|
||||
m_buffer = m_owning_vector.data();
|
||||
}
|
||||
|
||||
case EStorageType::OWNING_VECTOR:
|
||||
case EStorageType::NON_OWNING:
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::memcpy(m_buffer + m_cursor, buffer, size);
|
||||
m_cursor += size;
|
||||
return {};
|
||||
}
|
||||
|
||||
#define HANDLE_OUT_OF_CAPACITY(_size) \
|
||||
if B_UNLIKELY ((m_cursor + _size) > m_capacity) \
|
||||
{ \
|
||||
if (m_storageType == EStorageType::NON_OWNING) \
|
||||
return false; \
|
||||
m_owningVector.resize(m_capacity + (_size << 1)); \
|
||||
m_capacity = m_owningVector.size(); \
|
||||
m_buffer = m_owningVector.data(); \
|
||||
}
|
||||
|
||||
BOOL StreamWriter::Write(IN UINT8 byte, IN SIZE_T count)
|
||||
{
|
||||
HANDLE_OUT_OF_CAPACITY(count);
|
||||
memset(&m_buffer[m_cursor], byte, count);
|
||||
m_cursor += count;
|
||||
return true;
|
||||
}
|
||||
|
||||
BOOL StreamWriter::Write(IN PCVOID buffer, IN SIZE_T size)
|
||||
{
|
||||
HANDLE_OUT_OF_CAPACITY(size);
|
||||
memcpy(&m_buffer[m_cursor], buffer, size);
|
||||
m_cursor += size;
|
||||
return true;
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -15,86 +15,77 @@
|
||||
|
||||
#include <IACore/StringOps.hpp>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
CONST String BASE64_CHAR_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
namespace IACore {
|
||||
const String BASE64_CHAR_TABLE =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
String StringOps::EncodeBase64(IN Span<CONST UINT8> data)
|
||||
{
|
||||
String result;
|
||||
result.reserve(((data.size() + 2) / 3) * 4);
|
||||
for (size_t i = 0; i < data.size(); i += 3)
|
||||
{
|
||||
uint32_t value = 0;
|
||||
INT32 num_bytes = 0;
|
||||
for (INT32 j = 0; j < 3 && (i + j) < data.size(); ++j)
|
||||
{
|
||||
value = (value << 8) | data[i + j];
|
||||
num_bytes++;
|
||||
}
|
||||
for (INT32 j = 0; j < num_bytes + 1; ++j)
|
||||
{
|
||||
if (j < 4)
|
||||
{
|
||||
result += BASE64_CHAR_TABLE[(value >> (6 * (3 - j))) & 0x3F];
|
||||
}
|
||||
}
|
||||
if (num_bytes < 3)
|
||||
{
|
||||
for (INT32 j = 0; j < (3 - num_bytes); ++j)
|
||||
{
|
||||
result += '=';
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
auto StringOps::encode_base64(Span<const u8> data) -> String {
|
||||
String result;
|
||||
result.reserve(((data.size() + 2) / 3) * 4);
|
||||
for (size_t i = 0; i < data.size(); i += 3) {
|
||||
uint32_t value = 0;
|
||||
i32 num_bytes = 0;
|
||||
for (i32 j = 0; j < 3 && (i + j) < data.size(); ++j) {
|
||||
value = (value << 8) | data[i + j];
|
||||
num_bytes++;
|
||||
}
|
||||
|
||||
Vector<UINT8> StringOps::DecodeBase64(IN CONST String &data)
|
||||
{
|
||||
Vector<UINT8> result;
|
||||
|
||||
CONST AUTO isBase64 = [](UINT8 c) { return (isalnum(c) || (c == '+') || (c == '/')); };
|
||||
|
||||
INT32 in_len = data.size();
|
||||
INT32 i = 0, j = 0, in_ = 0;
|
||||
UINT8 tmpBuf0[4], tmpBuf1[3];
|
||||
|
||||
while (in_len-- && (data[in_] != '=') && isBase64(data[in_]))
|
||||
{
|
||||
tmpBuf0[i++] = data[in_];
|
||||
in_++;
|
||||
if (i == 4)
|
||||
{
|
||||
for (i = 0; i < 4; i++)
|
||||
tmpBuf0[i] = BASE64_CHAR_TABLE.find(tmpBuf0[i]);
|
||||
|
||||
tmpBuf1[0] = (tmpBuf0[0] << 2) + ((tmpBuf0[1] & 0x30) >> 4);
|
||||
tmpBuf1[1] = ((tmpBuf0[1] & 0xf) << 4) + ((tmpBuf0[2] & 0x3c) >> 2);
|
||||
tmpBuf1[2] = ((tmpBuf0[2] & 0x3) << 6) + tmpBuf0[3];
|
||||
|
||||
for (i = 0; (i < 3); i++)
|
||||
result.push_back(tmpBuf1[i]);
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (i)
|
||||
{
|
||||
for (j = i; j < 4; j++)
|
||||
tmpBuf0[j] = 0;
|
||||
|
||||
for (j = 0; j < 4; j++)
|
||||
tmpBuf0[j] = BASE64_CHAR_TABLE.find(tmpBuf0[j]);
|
||||
|
||||
tmpBuf1[0] = (tmpBuf0[0] << 2) + ((tmpBuf0[1] & 0x30) >> 4);
|
||||
tmpBuf1[1] = ((tmpBuf0[1] & 0xf) << 4) + ((tmpBuf0[2] & 0x3c) >> 2);
|
||||
tmpBuf1[2] = ((tmpBuf0[2] & 0x3) << 6) + tmpBuf0[3];
|
||||
|
||||
for (j = 0; (j < i - 1); j++)
|
||||
result.push_back(tmpBuf1[j]);
|
||||
}
|
||||
|
||||
return result;
|
||||
for (i32 j = 0; j < num_bytes + 1; ++j) {
|
||||
if (j < 4) {
|
||||
result += BASE64_CHAR_TABLE[(value >> (6 * (3 - j))) & 0x3F];
|
||||
}
|
||||
}
|
||||
if (num_bytes < 3) {
|
||||
for (i32 j = 0; j < (3 - num_bytes); ++j) {
|
||||
result += '=';
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
auto StringOps::decode_base64(const String &data) -> Vec<u8> {
|
||||
Vec<u8> result;
|
||||
|
||||
const auto is_base64 = [](u8 c) {
|
||||
return (isalnum(c) || (c == '+') || (c == '/'));
|
||||
};
|
||||
|
||||
i32 in_len = data.size();
|
||||
i32 i = 0, j = 0, in = 0;
|
||||
u8 tmp_buf0[4], tmp_buf1[3];
|
||||
|
||||
while (in_len-- && (data[in] != '=') && is_base64(data[in])) {
|
||||
tmp_buf0[i++] = data[in];
|
||||
in++;
|
||||
if (i == 4) {
|
||||
for (i = 0; i < 4; i++)
|
||||
tmp_buf0[i] = BASE64_CHAR_TABLE.find(tmp_buf0[i]);
|
||||
|
||||
tmp_buf1[0] = (tmp_buf0[0] << 2) + ((tmp_buf0[1] & 0x30) >> 4);
|
||||
tmp_buf1[1] = ((tmp_buf0[1] & 0xf) << 4) + ((tmp_buf0[2] & 0x3c) >> 2);
|
||||
tmp_buf1[2] = ((tmp_buf0[2] & 0x3) << 6) + tmp_buf0[3];
|
||||
|
||||
for (i = 0; (i < 3); i++)
|
||||
result.push_back(tmp_buf1[i]);
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (i) {
|
||||
for (j = i; j < 4; j++)
|
||||
tmp_buf0[j] = 0;
|
||||
|
||||
for (j = 0; j < 4; j++)
|
||||
tmp_buf0[j] = BASE64_CHAR_TABLE.find(tmp_buf0[j]);
|
||||
|
||||
tmp_buf1[0] = (tmp_buf0[0] << 2) + ((tmp_buf0[1] & 0x30) >> 4);
|
||||
tmp_buf1[1] = ((tmp_buf0[1] & 0xf) << 4) + ((tmp_buf0[2] & 0x3c) >> 2);
|
||||
tmp_buf1[2] = ((tmp_buf0[2] & 0x3) << 6) + tmp_buf0[3];
|
||||
|
||||
for (j = 0; (j < i - 1); j++)
|
||||
result.push_back(tmp_buf1[j]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
} // namespace IACore
|
||||
120
Src/IACore/imp/cpp/Utils.cpp
Normal file
120
Src/IACore/imp/cpp/Utils.cpp
Normal 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
|
||||
@ -17,39 +17,39 @@
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
EXPECT(XML::Document) XML::ParseFromString(IN CONST String &data)
|
||||
Result<XML::Document> XML::parse_from_string(const String &data)
|
||||
{
|
||||
Document doc;
|
||||
const auto parseResult = doc.load_string(data.data());
|
||||
if (!parseResult)
|
||||
return MakeUnexpected(std::format("Failed to parse XML: {}", parseResult.description()));
|
||||
return IA_MOVE(doc);
|
||||
return fail("Failed to parse XML {}", parseResult.description());
|
||||
return std::move(doc);
|
||||
}
|
||||
|
||||
EXPECT(XML::Document) XML::ParseFromFile(IN CONST FilePath &path)
|
||||
Result<XML::Document> XML::parse_from_file(const Path &path)
|
||||
{
|
||||
Document doc;
|
||||
const auto parseResult = doc.load_file(path.string().c_str());
|
||||
if (!parseResult)
|
||||
return MakeUnexpected(std::format("Failed to parse XML: {}", parseResult.description()));
|
||||
return IA_MOVE(doc);
|
||||
return fail("Failed to parse XML {}", parseResult.description());
|
||||
return std::move(doc);
|
||||
}
|
||||
|
||||
String XML::SerializeToString(IN CONST Node &node, IN BOOL escape)
|
||||
String XML::serialize_to_string(const Node &node, bool escape)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
node.print(oss);
|
||||
return escape ? EscapeXMLString(oss.str()) : oss.str();
|
||||
return escape ? escape_xml_string(oss.str()) : oss.str();
|
||||
}
|
||||
|
||||
String XML::SerializeToString(IN CONST Document &doc, IN BOOL escape)
|
||||
String XML::serialize_to_string(const Document &doc, bool escape)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
doc.save(oss);
|
||||
return escape ? EscapeXMLString(oss.str()) : oss.str();
|
||||
return escape ? escape_xml_string(oss.str()) : oss.str();
|
||||
}
|
||||
|
||||
String XML::EscapeXMLString(IN CONST String &xml)
|
||||
String XML::escape_xml_string(const String &xml)
|
||||
{
|
||||
String buffer;
|
||||
buffer.reserve(xml.size() + (xml.size() / 10));
|
||||
|
||||
@ -16,210 +16,216 @@
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
#include <cstring>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
class RingBufferView
|
||||
{
|
||||
public:
|
||||
STATIC CONSTEXPR UINT16 PACKET_ID_SKIP = 0;
|
||||
namespace IACore {
|
||||
class RingBufferView {
|
||||
public:
|
||||
static constexpr u16 PACKET_ID_SKIP = 0;
|
||||
|
||||
struct ControlBlock
|
||||
{
|
||||
struct alignas(64)
|
||||
{
|
||||
Atomic<UINT32> WriteOffset{0};
|
||||
} Producer;
|
||||
struct ControlBlock {
|
||||
struct alignas(64) {
|
||||
std::atomic<u32> write_offset{0};
|
||||
} producer;
|
||||
|
||||
struct alignas(64)
|
||||
{
|
||||
Atomic<UINT32> ReadOffset{0};
|
||||
// Capacity is effectively constant after init,
|
||||
// so it doesn't cause false sharing invalidations.
|
||||
UINT32 Capacity{0};
|
||||
} Consumer;
|
||||
};
|
||||
struct alignas(64) {
|
||||
std::atomic<u32> read_offset{0};
|
||||
// Capacity is effectively constant after init,
|
||||
// so it doesn't cause false sharing invalidations.
|
||||
u32 capacity{0};
|
||||
} consumer;
|
||||
};
|
||||
|
||||
static_assert(offsetof(ControlBlock, Consumer) == 64, "False sharing detected in ControlBlock");
|
||||
static_assert(offsetof(ControlBlock, consumer) == 64,
|
||||
"False sharing detected in ControlBlock");
|
||||
|
||||
// All of the data in ring buffer will be stored as packets
|
||||
struct PacketHeader
|
||||
{
|
||||
PacketHeader() : ID(0), PayloadSize(0)
|
||||
{
|
||||
}
|
||||
// All of the data in ring buffer will be stored as packets
|
||||
struct PacketHeader {
|
||||
PacketHeader() : id(0), payload_size(0) {}
|
||||
|
||||
PacketHeader(IN UINT16 id) : ID(id), PayloadSize(0)
|
||||
{
|
||||
}
|
||||
PacketHeader(u16 id) : id(id), payload_size(0) {}
|
||||
|
||||
PacketHeader(IN UINT16 id, IN UINT16 payloadSize) : ID(id), PayloadSize(payloadSize)
|
||||
{
|
||||
}
|
||||
PacketHeader(u16 id, u16 payload_size)
|
||||
: id(id), payload_size(payload_size) {}
|
||||
|
||||
UINT16 ID{};
|
||||
UINT16 PayloadSize{};
|
||||
};
|
||||
u16 id{};
|
||||
u16 payload_size{};
|
||||
};
|
||||
|
||||
public:
|
||||
INLINE RingBufferView(IN Span<UINT8> buffer, IN BOOL isOwner);
|
||||
INLINE RingBufferView(IN ControlBlock *controlBlock, IN Span<UINT8> buffer, IN BOOL isOwner);
|
||||
public:
|
||||
RingBufferView(Span<u8> buffer, bool is_owner);
|
||||
RingBufferView(ControlBlock *control_block, Span<u8> buffer, bool is_owner);
|
||||
|
||||
INLINE INT32 Pop(OUT PacketHeader &outHeader, OUT Span<UINT8> outBuffer);
|
||||
INLINE BOOL Push(IN UINT16 packetID, IN Span<CONST UINT8> data);
|
||||
// Returns:
|
||||
// - Ok(nullopt) if empty
|
||||
// - Ok(bytes_read) if success
|
||||
// - Error if buffer too small
|
||||
auto pop(PacketHeader &out_header, Span<u8> out_buffer)
|
||||
-> Result<Option<usize>>;
|
||||
|
||||
INLINE ControlBlock *GetControlBlock();
|
||||
// Returns:
|
||||
// - Ok() if success
|
||||
// - Error if full
|
||||
auto push(u16 packet_id, Span<const u8> data) -> Result<void>;
|
||||
|
||||
private:
|
||||
PUINT8 m_dataPtr{};
|
||||
UINT32 m_capacity{};
|
||||
ControlBlock *m_controlBlock{};
|
||||
auto get_control_block() -> ControlBlock *;
|
||||
|
||||
private:
|
||||
INLINE VOID WriteWrapped(IN UINT32 offset, IN PCVOID data, IN UINT32 size);
|
||||
INLINE VOID ReadWrapped(IN UINT32 offset, OUT PVOID outData, IN UINT32 size);
|
||||
};
|
||||
} // namespace IACore
|
||||
private:
|
||||
u8 *m_data_ptr{};
|
||||
u32 m_capacity{};
|
||||
ControlBlock *m_control_block{};
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
RingBufferView::RingBufferView(IN Span<UINT8> buffer, IN BOOL isOwner)
|
||||
{
|
||||
IA_ASSERT(buffer.size() > sizeof(ControlBlock));
|
||||
private:
|
||||
auto write_wrapped(u32 offset, const void *data, u32 size) -> void;
|
||||
auto read_wrapped(u32 offset, void *out_data, u32 size) -> void;
|
||||
};
|
||||
|
||||
m_controlBlock = reinterpret_cast<ControlBlock *>(buffer.data());
|
||||
m_dataPtr = buffer.data() + sizeof(ControlBlock);
|
||||
// =============================================================================
|
||||
// Implementation
|
||||
// =============================================================================
|
||||
|
||||
m_capacity = static_cast<UINT32>(buffer.size()) - sizeof(ControlBlock);
|
||||
inline RingBufferView::RingBufferView(Span<u8> buffer, bool is_owner) {
|
||||
ensure(buffer.size() > sizeof(ControlBlock),
|
||||
"Buffer too small for ControlBlock");
|
||||
|
||||
if (isOwner)
|
||||
{
|
||||
m_controlBlock->Consumer.Capacity = m_capacity;
|
||||
m_controlBlock->Producer.WriteOffset.store(0, std::memory_order_release);
|
||||
m_controlBlock->Consumer.ReadOffset.store(0, std::memory_order_release);
|
||||
}
|
||||
else
|
||||
IA_ASSERT(m_controlBlock->Consumer.Capacity == m_capacity);
|
||||
}
|
||||
m_control_block = reinterpret_cast<ControlBlock *>(buffer.data());
|
||||
m_data_ptr = buffer.data() + sizeof(ControlBlock);
|
||||
|
||||
RingBufferView::RingBufferView(IN ControlBlock *controlBlock, IN Span<UINT8> buffer, IN BOOL isOwner)
|
||||
{
|
||||
IA_ASSERT(controlBlock != nullptr);
|
||||
IA_ASSERT(buffer.size() > 0);
|
||||
m_capacity = static_cast<u32>(buffer.size()) - sizeof(ControlBlock);
|
||||
|
||||
m_controlBlock = controlBlock;
|
||||
m_dataPtr = buffer.data();
|
||||
m_capacity = static_cast<UINT32>(buffer.size());
|
||||
if (is_owner) {
|
||||
m_control_block->consumer.capacity = m_capacity;
|
||||
m_control_block->producer.write_offset.store(0, std::memory_order_release);
|
||||
m_control_block->consumer.read_offset.store(0, std::memory_order_release);
|
||||
} else {
|
||||
ensure(m_control_block->consumer.capacity == m_capacity,
|
||||
"Capacity mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
if (isOwner)
|
||||
{
|
||||
m_controlBlock->Consumer.Capacity = m_capacity;
|
||||
m_controlBlock->Producer.WriteOffset.store(0, std::memory_order_release);
|
||||
m_controlBlock->Consumer.ReadOffset.store(0, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
inline RingBufferView::RingBufferView(ControlBlock *control_block,
|
||||
Span<u8> buffer, bool is_owner) {
|
||||
ensure(control_block != nullptr, "ControlBlock is null");
|
||||
ensure(!buffer.empty(), "Buffer is empty");
|
||||
|
||||
INT32 RingBufferView::Pop(OUT PacketHeader &outHeader, OUT Span<UINT8> outBuffer)
|
||||
{
|
||||
UINT32 write = m_controlBlock->Producer.WriteOffset.load(std::memory_order_acquire);
|
||||
UINT32 read = m_controlBlock->Consumer.ReadOffset.load(std::memory_order_relaxed);
|
||||
UINT32 cap = m_capacity;
|
||||
m_control_block = control_block;
|
||||
m_data_ptr = buffer.data();
|
||||
m_capacity = static_cast<u32>(buffer.size());
|
||||
|
||||
if (read == write)
|
||||
return 0; // Empty
|
||||
if (is_owner) {
|
||||
m_control_block->consumer.capacity = m_capacity;
|
||||
m_control_block->producer.write_offset.store(0, std::memory_order_release);
|
||||
m_control_block->consumer.read_offset.store(0, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
ReadWrapped(read, &outHeader, sizeof(PacketHeader));
|
||||
inline auto RingBufferView::pop(PacketHeader &out_header, Span<u8> out_buffer)
|
||||
-> Result<Option<usize>> {
|
||||
u32 write =
|
||||
m_control_block->producer.write_offset.load(std::memory_order_acquire);
|
||||
u32 read =
|
||||
m_control_block->consumer.read_offset.load(std::memory_order_relaxed);
|
||||
u32 cap = m_capacity;
|
||||
|
||||
if (outHeader.PayloadSize > outBuffer.size())
|
||||
return -static_cast<INT32>(outHeader.PayloadSize);
|
||||
if (read == write) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (outHeader.PayloadSize > 0)
|
||||
{
|
||||
UINT32 dataReadOffset = (read + sizeof(PacketHeader)) % cap;
|
||||
ReadWrapped(dataReadOffset, outBuffer.data(), outHeader.PayloadSize);
|
||||
}
|
||||
read_wrapped(read, &out_header, sizeof(PacketHeader));
|
||||
|
||||
// Move read pointer forward
|
||||
UINT32 newReadOffset = (read + sizeof(PacketHeader) + outHeader.PayloadSize) % cap;
|
||||
m_controlBlock->Consumer.ReadOffset.store(newReadOffset, std::memory_order_release);
|
||||
if (out_header.payload_size > out_buffer.size()) {
|
||||
return fail("Buffer too small: needed {}, provided {}",
|
||||
out_header.payload_size, out_buffer.size());
|
||||
}
|
||||
|
||||
return outHeader.PayloadSize;
|
||||
}
|
||||
if (out_header.payload_size > 0) {
|
||||
u32 data_read_offset = (read + sizeof(PacketHeader)) % cap;
|
||||
read_wrapped(data_read_offset, out_buffer.data(), out_header.payload_size);
|
||||
}
|
||||
|
||||
BOOL RingBufferView::Push(IN UINT16 packetID, IN Span<CONST UINT8> data)
|
||||
{
|
||||
IA_ASSERT(data.size() <= UINT16_MAX);
|
||||
// Move read pointer forward
|
||||
u32 new_read_offset =
|
||||
(read + sizeof(PacketHeader) + out_header.payload_size) % cap;
|
||||
m_control_block->consumer.read_offset.store(new_read_offset,
|
||||
std::memory_order_release);
|
||||
|
||||
const UINT32 totalSize = sizeof(PacketHeader) + static_cast<UINT32>(data.size());
|
||||
return std::make_optional(static_cast<usize>(out_header.payload_size));
|
||||
}
|
||||
|
||||
UINT32 read = m_controlBlock->Consumer.ReadOffset.load(std::memory_order_acquire);
|
||||
UINT32 write = m_controlBlock->Producer.WriteOffset.load(std::memory_order_relaxed);
|
||||
UINT32 cap = m_capacity;
|
||||
inline auto RingBufferView::push(u16 packet_id, Span<const u8> data)
|
||||
-> Result<void> {
|
||||
ensure(data.size() <= std::numeric_limits<u16>::max(),
|
||||
"Data size exceeds u16 limit");
|
||||
|
||||
UINT32 freeSpace = (read <= write) ? (m_capacity - write) + read : (read - write);
|
||||
const u32 total_size = sizeof(PacketHeader) + static_cast<u32>(data.size());
|
||||
|
||||
// Ensure to always leave 1 byte empty to prevent Read == Write ambiguity (Wait-Free Ring Buffer standard)
|
||||
if (freeSpace <= totalSize)
|
||||
return FALSE;
|
||||
u32 read =
|
||||
m_control_block->consumer.read_offset.load(std::memory_order_acquire);
|
||||
u32 write =
|
||||
m_control_block->producer.write_offset.load(std::memory_order_relaxed);
|
||||
u32 cap = m_capacity;
|
||||
|
||||
PacketHeader header{packetID, static_cast<UINT16>(data.size())};
|
||||
WriteWrapped(write, &header, sizeof(PacketHeader));
|
||||
u32 free_space =
|
||||
(read <= write) ? (m_capacity - write) + read : (read - write);
|
||||
|
||||
UINT32 dataWriteOffset = (write + sizeof(PacketHeader)) % cap;
|
||||
// Ensure to always leave 1 byte empty to prevent Read == Write ambiguity
|
||||
if (free_space <= total_size) {
|
||||
return fail("RingBuffer full");
|
||||
}
|
||||
|
||||
if (data.size() > 0)
|
||||
{
|
||||
WriteWrapped(dataWriteOffset, data.data(), static_cast<UINT32>(data.size()));
|
||||
}
|
||||
PacketHeader header{packet_id, static_cast<u16>(data.size())};
|
||||
write_wrapped(write, &header, sizeof(PacketHeader));
|
||||
|
||||
UINT32 newWriteOffset = (dataWriteOffset + data.size()) % cap;
|
||||
m_controlBlock->Producer.WriteOffset.store(newWriteOffset, std::memory_order_release);
|
||||
u32 data_write_offset = (write + sizeof(PacketHeader)) % cap;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
if (!data.empty()) {
|
||||
write_wrapped(data_write_offset, data.data(),
|
||||
static_cast<u32>(data.size()));
|
||||
}
|
||||
|
||||
RingBufferView::ControlBlock *RingBufferView::GetControlBlock()
|
||||
{
|
||||
return m_controlBlock;
|
||||
}
|
||||
u32 new_write_offset = (data_write_offset + data.size()) % cap;
|
||||
m_control_block->producer.write_offset.store(new_write_offset,
|
||||
std::memory_order_release);
|
||||
|
||||
VOID RingBufferView::WriteWrapped(IN UINT32 offset, IN PCVOID data, IN UINT32 size)
|
||||
{
|
||||
if (offset + size <= m_capacity)
|
||||
{
|
||||
// Contiguous write
|
||||
memcpy(m_dataPtr + offset, data, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Split write
|
||||
UINT32 firstChunk = m_capacity - offset;
|
||||
UINT32 secondChunk = size - firstChunk;
|
||||
return {};
|
||||
}
|
||||
|
||||
const UINT8 *src = static_cast<const UINT8 *>(data);
|
||||
inline auto RingBufferView::get_control_block() -> ControlBlock * {
|
||||
return m_control_block;
|
||||
}
|
||||
|
||||
memcpy(m_dataPtr + offset, src, firstChunk);
|
||||
memcpy(m_dataPtr, src + firstChunk, secondChunk);
|
||||
}
|
||||
}
|
||||
inline auto RingBufferView::write_wrapped(u32 offset, const void *data,
|
||||
u32 size) -> void {
|
||||
if (offset + size <= m_capacity) {
|
||||
// Contiguous write
|
||||
std::memcpy(m_data_ptr + offset, data, size);
|
||||
} else {
|
||||
// Split write
|
||||
u32 first_chunk = m_capacity - offset;
|
||||
u32 second_chunk = size - first_chunk;
|
||||
|
||||
VOID RingBufferView::ReadWrapped(IN UINT32 offset, OUT PVOID outData, IN UINT32 size)
|
||||
{
|
||||
if (offset + size <= m_capacity)
|
||||
{
|
||||
// Contiguous read
|
||||
memcpy(outData, m_dataPtr + offset, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Split read
|
||||
UINT32 firstChunk = m_capacity - offset;
|
||||
UINT32 secondChunk = size - firstChunk;
|
||||
const u8 *src = static_cast<const u8 *>(data);
|
||||
|
||||
UINT8 *dst = static_cast<UINT8 *>(outData);
|
||||
std::memcpy(m_data_ptr + offset, src, first_chunk);
|
||||
std::memcpy(m_data_ptr, src + first_chunk, second_chunk);
|
||||
}
|
||||
}
|
||||
|
||||
inline auto RingBufferView::read_wrapped(u32 offset, void *out_data, u32 size)
|
||||
-> void {
|
||||
if (offset + size <= m_capacity) {
|
||||
// Contiguous read
|
||||
std::memcpy(out_data, m_data_ptr + offset, size);
|
||||
} else {
|
||||
// Split read
|
||||
u32 first_chunk = m_capacity - offset;
|
||||
u32 second_chunk = size - first_chunk;
|
||||
|
||||
u8 *dst = static_cast<u8 *>(out_data);
|
||||
|
||||
std::memcpy(dst, m_data_ptr + offset, first_chunk);
|
||||
std::memcpy(dst + first_chunk, m_data_ptr, second_chunk);
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(dst, m_dataPtr + offset, firstChunk);
|
||||
memcpy(dst + firstChunk, m_dataPtr, secondChunk);
|
||||
}
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -16,58 +16,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <stop_token>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
class AsyncOps
|
||||
{
|
||||
public:
|
||||
using TaskTag = UINT64;
|
||||
using WorkerID = UINT16;
|
||||
namespace IACore {
|
||||
class AsyncOps {
|
||||
public:
|
||||
using TaskTag = u64;
|
||||
using WorkerId = u16;
|
||||
|
||||
STATIC CONSTEXPR WorkerID MainThreadWorkerID = 0;
|
||||
static constexpr WorkerId MAIN_THREAD_WORKER_ID = 0;
|
||||
|
||||
enum class Priority : UINT8
|
||||
{
|
||||
High,
|
||||
Normal
|
||||
};
|
||||
enum class Priority : u8 { High, Normal };
|
||||
|
||||
struct Schedule
|
||||
{
|
||||
Atomic<INT32> Counter{0};
|
||||
};
|
||||
struct Schedule {
|
||||
std::atomic<i32> counter{0};
|
||||
};
|
||||
|
||||
public:
|
||||
STATIC VOID InitializeScheduler(IN UINT8 workerCount = 0);
|
||||
STATIC VOID TerminateScheduler();
|
||||
public:
|
||||
static auto initialize_scheduler(u8 worker_count = 0) -> Result<void>;
|
||||
static auto terminate_scheduler() -> void;
|
||||
|
||||
STATIC VOID ScheduleTask(IN Function<VOID(IN WorkerID workerID)> task, IN TaskTag tag, IN Schedule *schedule,
|
||||
IN Priority priority = Priority::Normal);
|
||||
static auto schedule_task(std::function<void(WorkerId worker_id)> task,
|
||||
TaskTag tag, Schedule *schedule,
|
||||
Priority priority = Priority::Normal) -> void;
|
||||
|
||||
STATIC VOID CancelTasksOfTag(IN TaskTag tag);
|
||||
static auto cancel_tasks_of_tag(TaskTag tag) -> void;
|
||||
|
||||
STATIC VOID WaitForScheduleCompletion(IN Schedule *schedule);
|
||||
static auto wait_for_schedule_completion(Schedule *schedule) -> void;
|
||||
|
||||
STATIC VOID RunTask(IN Function<VOID()> task);
|
||||
static auto run_task(std::function<void()> task) -> void;
|
||||
|
||||
STATIC WorkerID GetWorkerCount();
|
||||
[[nodiscard]] static auto get_worker_count() -> WorkerId;
|
||||
|
||||
private:
|
||||
struct ScheduledTask
|
||||
{
|
||||
TaskTag Tag{};
|
||||
Schedule *ScheduleHandle{};
|
||||
Function<VOID(IN WorkerID workerID)> Task{};
|
||||
};
|
||||
private:
|
||||
struct ScheduledTask {
|
||||
TaskTag tag{};
|
||||
Schedule *schedule_handle{};
|
||||
std::function<void(WorkerId worker_id)> task{};
|
||||
};
|
||||
|
||||
STATIC VOID ScheduleWorkerLoop(IN StopToken stopToken, IN WorkerID workerID);
|
||||
static auto schedule_worker_loop(std::stop_token stop_token,
|
||||
WorkerId worker_id) -> void;
|
||||
|
||||
private:
|
||||
STATIC Mutex s_queueMutex;
|
||||
STATIC ConditionVariable s_wakeCondition;
|
||||
STATIC Vector<JoiningThread> s_scheduleWorkers;
|
||||
STATIC Deque<ScheduledTask> s_highPriorityQueue;
|
||||
STATIC Deque<ScheduledTask> s_normalPriorityQueue;
|
||||
};
|
||||
private:
|
||||
static std::mutex s_queue_mutex;
|
||||
static std::condition_variable s_wake_condition;
|
||||
static Vec<std::jthread> s_schedule_workers;
|
||||
static std::deque<ScheduledTask> s_high_priority_queue;
|
||||
static std::deque<ScheduledTask> s_normal_priority_queue;
|
||||
};
|
||||
} // namespace IACore
|
||||
@ -30,41 +30,41 @@ namespace IACore
|
||||
*/
|
||||
|
||||
public:
|
||||
CLIParser(IN Span<CONST String> args);
|
||||
CLIParser(Span<const String> args);
|
||||
~CLIParser() = default;
|
||||
|
||||
public:
|
||||
BOOL Remaining() CONST
|
||||
auto remaining() const -> bool
|
||||
{
|
||||
return m_currentArg < m_argList.end();
|
||||
}
|
||||
|
||||
StringView Peek() CONST
|
||||
auto peek() const -> StringView
|
||||
{
|
||||
if (!Remaining())
|
||||
if (!remaining())
|
||||
return "";
|
||||
return *m_currentArg;
|
||||
}
|
||||
|
||||
StringView Next()
|
||||
auto next() -> StringView
|
||||
{
|
||||
if (!Remaining())
|
||||
if (!remaining())
|
||||
return "";
|
||||
return *m_currentArg++;
|
||||
}
|
||||
|
||||
BOOL Consume(IN CONST StringView &expected)
|
||||
auto consume(const StringView &expected) -> bool
|
||||
{
|
||||
if (Peek() == expected)
|
||||
if (peek() == expected)
|
||||
{
|
||||
Next();
|
||||
next();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
CONST Span<CONST String> m_argList;
|
||||
Span<CONST String>::const_iterator m_currentArg;
|
||||
const Span<const String> m_argList;
|
||||
Span<const String>::const_iterator m_currentArg;
|
||||
};
|
||||
} // namespace IACore
|
||||
@ -17,36 +17,29 @@
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
class DataOps
|
||||
{
|
||||
public:
|
||||
enum class CompressionType
|
||||
{
|
||||
None,
|
||||
Gzip,
|
||||
Zlib
|
||||
};
|
||||
namespace IACore {
|
||||
class DataOps {
|
||||
public:
|
||||
enum class CompressionType { None, Gzip, Zlib };
|
||||
|
||||
public:
|
||||
STATIC UINT32 Hash_FNV1A(IN CONST String &string);
|
||||
STATIC UINT32 Hash_FNV1A(IN Span<CONST UINT8> data);
|
||||
public:
|
||||
static auto hash_fnv1a(const String &string) -> u32;
|
||||
static auto hash_fnv1a(Span<const u8> data) -> u32;
|
||||
|
||||
STATIC UINT32 Hash_xxHash(IN CONST String &string, IN UINT32 seed = 0);
|
||||
STATIC UINT32 Hash_xxHash(IN Span<CONST UINT8> data, IN UINT32 seed = 0);
|
||||
static auto hash_xxhash(const String &string, u32 seed = 0) -> u32;
|
||||
static auto hash_xxhash(Span<const u8> data, u32 seed = 0) -> u32;
|
||||
|
||||
STATIC UINT32 CRC32(IN Span<CONST UINT8> data);
|
||||
static auto crc32(Span<const u8> data) -> u32;
|
||||
|
||||
STATIC CompressionType DetectCompression(IN Span<CONST UINT8> data);
|
||||
static auto detect_compression(Span<const u8> data) -> CompressionType;
|
||||
|
||||
STATIC EXPECT(Vector<UINT8>) GZipInflate(IN Span<CONST UINT8> data);
|
||||
STATIC EXPECT(Vector<UINT8>) GZipDeflate(IN Span<CONST UINT8> data);
|
||||
static auto gzip_inflate(Span<const u8> data) -> Result<Vec<u8>>;
|
||||
static auto gzip_deflate(Span<const u8> data) -> Result<Vec<u8>>;
|
||||
|
||||
STATIC EXPECT(Vector<UINT8>) ZlibInflate(IN Span<CONST UINT8> data);
|
||||
STATIC EXPECT(Vector<UINT8>) ZlibDeflate(IN Span<CONST UINT8> data);
|
||||
static auto zlib_inflate(Span<const u8> data) -> Result<Vec<u8>>;
|
||||
static auto zlib_deflate(Span<const u8> data) -> Result<Vec<u8>>;
|
||||
|
||||
STATIC EXPECT(Vector<UINT8>) ZstdInflate(IN Span<CONST UINT8> data);
|
||||
STATIC EXPECT(Vector<UINT8>) ZstdDeflate(IN Span<CONST UINT8> data);
|
||||
};
|
||||
static auto zstd_inflate(Span<const u8> data) -> Result<Vec<u8>>;
|
||||
static auto zstd_deflate(Span<const u8> data) -> Result<Vec<u8>>;
|
||||
};
|
||||
} // namespace IACore
|
||||
@ -18,161 +18,143 @@
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
# include <libloaderapi.h>
|
||||
# include <errhandlingapi.h>
|
||||
#include <errhandlingapi.h>
|
||||
#include <libloaderapi.h>
|
||||
#else
|
||||
# include <dlfcn.h>
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
namespace IACore {
|
||||
|
||||
class DynamicLib
|
||||
{
|
||||
public:
|
||||
DynamicLib() : m_handle(nullptr)
|
||||
{
|
||||
}
|
||||
class DynamicLib {
|
||||
public:
|
||||
DynamicLib() : m_handle(nullptr) {}
|
||||
|
||||
DynamicLib(DynamicLib &&other) NOEXCEPT : m_handle(other.m_handle)
|
||||
{
|
||||
other.m_handle = nullptr;
|
||||
}
|
||||
DynamicLib(DynamicLib &&other) NOEXCEPT : m_handle(other.m_handle) {
|
||||
other.m_handle = nullptr;
|
||||
}
|
||||
|
||||
DynamicLib &operator=(DynamicLib &&other) NOEXCEPT
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
Unload(); // Free current if exists
|
||||
m_handle = other.m_handle;
|
||||
other.m_handle = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
DynamicLib &operator=(DynamicLib &&other) NOEXCEPT {
|
||||
if (this != &other) {
|
||||
Unload(); // Free current if exists
|
||||
m_handle = other.m_handle;
|
||||
other.m_handle = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
DynamicLib(CONST DynamicLib &) = delete;
|
||||
DynamicLib &operator=(CONST DynamicLib &) = delete;
|
||||
DynamicLib(const DynamicLib &) = delete;
|
||||
DynamicLib &operator=(const DynamicLib &) = delete;
|
||||
|
||||
~DynamicLib()
|
||||
{
|
||||
Unload();
|
||||
}
|
||||
~DynamicLib() { Unload(); }
|
||||
|
||||
// Automatically detects extension (.dll/.so) if not provided
|
||||
NO_DISCARD("Check for load errors")
|
||||
// Automatically detects extension (.dll/.so) if not provided
|
||||
NO_DISCARD("Check for load errors")
|
||||
|
||||
STATIC tl::EXPECT(DynamicLib) Load(CONST String &searchPath, CONST String &name)
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
static tl::Result<DynamicLib> Load(const String &searchPath,
|
||||
const String &name) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
fs::path fullPath = fs::path(searchPath) / name;
|
||||
fs::path fullPath = fs::path(searchPath) / name;
|
||||
|
||||
if (!fullPath.has_extension())
|
||||
{
|
||||
if (!fullPath.has_extension()) {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
fullPath += ".dll";
|
||||
fullPath += ".dll";
|
||||
#elif IA_PLATFORM_MAC
|
||||
fullPath += ".dylib";
|
||||
fullPath += ".dylib";
|
||||
#else
|
||||
fullPath += ".so";
|
||||
fullPath += ".so";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
DynamicLib lib;
|
||||
DynamicLib lib;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
HMODULE h = LoadLibraryA(fullPath.string().c_str());
|
||||
if (!h)
|
||||
{
|
||||
return MakeUnexpected(GetWindowsError());
|
||||
}
|
||||
lib.m_handle = CAST(h, PVOID);
|
||||
HMODULE h = LoadLibraryA(fullPath.string().c_str());
|
||||
if (!h) {
|
||||
return (GetWindowsError());
|
||||
}
|
||||
lib.m_handle = CAST(h, void *);
|
||||
#else
|
||||
// RTLD_LAZY: Resolve symbols only as code executes (Standard for plugins)
|
||||
void *h = dlopen(fullPath.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
||||
if (!h)
|
||||
{
|
||||
// dlerror returns a string describing the last error
|
||||
const char *err = dlerror();
|
||||
return MakeUnexpected(String(err ? err : "Unknown dlopen error"));
|
||||
}
|
||||
lib.m_handle = h;
|
||||
// RTLD_LAZY: Resolve symbols only as code executes (Standard for plugins)
|
||||
void *h = dlopen(fullPath.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
||||
if (!h) {
|
||||
// dlerror returns a string describing the last error
|
||||
const char *err = dlerror();
|
||||
return (String(err ? err : "Unknown dlopen error"));
|
||||
}
|
||||
lib.m_handle = h;
|
||||
#endif
|
||||
|
||||
return IA_MOVE(lib);
|
||||
}
|
||||
return std::move(lib);
|
||||
}
|
||||
|
||||
NO_DISCARD("Check if symbol exists")
|
||||
NO_DISCARD("Check if symbol exists")
|
||||
|
||||
tl::EXPECT(PVOID) GetSymbol(CONST String &name) CONST
|
||||
{
|
||||
if (!m_handle)
|
||||
return MakeUnexpected(String("Library not loaded"));
|
||||
tl::Result<void *> GetSymbol(const String &name) const {
|
||||
if (!m_handle)
|
||||
return (String("Library not loaded"));
|
||||
|
||||
PVOID sym = nullptr;
|
||||
void *sym = nullptr;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
sym = CAST(GetProcAddress(CAST(m_handle, HMODULE), name.c_str()), PVOID);
|
||||
if (!sym)
|
||||
return MakeUnexpected(GetWindowsError());
|
||||
sym = CAST(GetProcAddress(CAST(m_handle, HMODULE), name.c_str()), void *);
|
||||
if (!sym)
|
||||
return (GetWindowsError());
|
||||
#else
|
||||
// Clear any previous error
|
||||
dlerror();
|
||||
sym = dlsym(m_handle, name.c_str());
|
||||
const char *err = dlerror();
|
||||
if (err)
|
||||
return MakeUnexpected(String(err));
|
||||
// Clear any previous error
|
||||
dlerror();
|
||||
sym = dlsym(m_handle, name.c_str());
|
||||
const char *err = dlerror();
|
||||
if (err)
|
||||
return (String(err));
|
||||
#endif
|
||||
|
||||
return sym;
|
||||
}
|
||||
return sym;
|
||||
}
|
||||
|
||||
// Template helper for casting
|
||||
template<typename FuncT> tl::EXPECT(FuncT) GetFunction(CONST String &name) CONST
|
||||
{
|
||||
auto res = GetSymbol(name);
|
||||
if (!res)
|
||||
return MakeUnexpected(res.error());
|
||||
return REINTERPRET(*res, FuncT);
|
||||
}
|
||||
// Template helper for casting
|
||||
template <typename FuncT>
|
||||
tl::Result<FuncT> GetFunction(const String &name) const {
|
||||
auto res = GetSymbol(name);
|
||||
if (!res)
|
||||
return (res.error());
|
||||
return REINTERPRET(*res, FuncT);
|
||||
}
|
||||
|
||||
VOID Unload()
|
||||
{
|
||||
if (m_handle)
|
||||
{
|
||||
void Unload() {
|
||||
if (m_handle) {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
FreeLibrary(CAST(m_handle, HMODULE));
|
||||
FreeLibrary(CAST(m_handle, HMODULE));
|
||||
#else
|
||||
dlclose(m_handle);
|
||||
dlclose(m_handle);
|
||||
#endif
|
||||
m_handle = nullptr;
|
||||
}
|
||||
}
|
||||
m_handle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL IsLoaded() CONST
|
||||
{
|
||||
return m_handle != nullptr;
|
||||
}
|
||||
bool IsLoaded() const { return m_handle != nullptr; }
|
||||
|
||||
private:
|
||||
PVOID m_handle;
|
||||
private:
|
||||
void *m_handle;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
STATIC String GetWindowsError()
|
||||
{
|
||||
DWORD errorID = ::GetLastError();
|
||||
if (errorID == 0)
|
||||
return String();
|
||||
static String GetWindowsError() {
|
||||
DWORD errorID = ::GetLastError();
|
||||
if (errorID == 0)
|
||||
return String();
|
||||
|
||||
LPSTR messageBuffer = nullptr;
|
||||
size_t size = FormatMessageA(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
|
||||
errorID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR) &messageBuffer, 0, NULL);
|
||||
LPSTR messageBuffer = nullptr;
|
||||
size_t size = FormatMessageA(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, errorID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(LPSTR)&messageBuffer, 0, NULL);
|
||||
|
||||
String message(messageBuffer, size);
|
||||
LocalFree(messageBuffer);
|
||||
return String("Win32 Error: ") + message;
|
||||
}
|
||||
String message(messageBuffer, size);
|
||||
LocalFree(messageBuffer);
|
||||
return String("Win32 Error: ") + message;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
};
|
||||
} // namespace IACore
|
||||
@ -22,7 +22,7 @@ namespace IACore
|
||||
class Environment
|
||||
{
|
||||
public:
|
||||
STATIC Optional<String> Find(CONST String &name)
|
||||
static Optional<String> Find(const String &name)
|
||||
{
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
DWORD bufferSize = GetEnvironmentVariableA(name.c_str(), nullptr, 0);
|
||||
@ -61,12 +61,12 @@ namespace IACore
|
||||
#endif
|
||||
}
|
||||
|
||||
STATIC String Get(CONST String &name, CONST String &defaultValue = "")
|
||||
static String Get(const String &name, const String &defaultValue = "")
|
||||
{
|
||||
return Find(name).value_or(defaultValue);
|
||||
}
|
||||
|
||||
STATIC BOOL Set(CONST String &name, CONST String &value)
|
||||
static bool Set(const String &name, const String &value)
|
||||
{
|
||||
if (name.empty())
|
||||
return FALSE;
|
||||
@ -79,7 +79,7 @@ namespace IACore
|
||||
#endif
|
||||
}
|
||||
|
||||
STATIC BOOL Unset(CONST String &name)
|
||||
static bool Unset(const String &name)
|
||||
{
|
||||
if (name.empty())
|
||||
return FALSE;
|
||||
@ -91,7 +91,7 @@ namespace IACore
|
||||
#endif
|
||||
}
|
||||
|
||||
STATIC BOOL Exists(CONST String &name)
|
||||
static bool Exists(const String &name)
|
||||
{
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
return GetEnvironmentVariableA(name.c_str(), nullptr, 0) > 0;
|
||||
|
||||
@ -15,107 +15,107 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
#include <IACore/StreamReader.hpp>
|
||||
#include <IACore/StreamWriter.hpp>
|
||||
#include <tuple>
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
using NativeFileHandle = HANDLE;
|
||||
STATIC CONSTEXPR NativeFileHandle INVALID_FILE_HANDLE = INVALID_HANDLE_VALUE;
|
||||
static constexpr NativeFileHandle INVALID_FILE_HANDLE = INVALID_HANDLE_VALUE;
|
||||
#else
|
||||
using NativeFileHandle = int;
|
||||
STATIC CONSTEXPR NativeFileHandle INVALID_FILE_HANDLE = -1;
|
||||
static constexpr NativeFileHandle INVALID_FILE_HANDLE = -1;
|
||||
#endif
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
class FileOps
|
||||
{
|
||||
public:
|
||||
class MemoryMappedRegion;
|
||||
namespace IACore {
|
||||
|
||||
enum class EFileAccess : UINT8
|
||||
{
|
||||
READ, // Read-only
|
||||
WRITE, // Write-only
|
||||
READ_WRITE // Read and Write
|
||||
};
|
||||
class FileOps {
|
||||
public:
|
||||
class MemoryMappedRegion;
|
||||
|
||||
enum class EFileMode : UINT8
|
||||
{
|
||||
OPEN_EXISTING, // Fails if file doesn't exist
|
||||
OPEN_ALWAYS, // Opens if exists, creates if not
|
||||
CREATE_NEW, // Fails if file exists
|
||||
CREATE_ALWAYS, // Overwrites existing
|
||||
TRUNCATE_EXISTING // Opens existing and clears it
|
||||
};
|
||||
enum class FileAccess : u8 {
|
||||
Read, // Read-only
|
||||
Write, // Write-only
|
||||
ReadWrite // Read and Write
|
||||
};
|
||||
|
||||
STATIC EXPECT(NativeFileHandle) NativeOpenFile(IN CONST FilePath &path, IN EFileAccess access,
|
||||
IN EFileMode mode, IN UINT32 permissions = 0644);
|
||||
STATIC VOID NativeCloseFile(IN NativeFileHandle handle);
|
||||
enum class FileMode : u8 {
|
||||
OpenExisting, // Fails if file doesn't exist
|
||||
OpenAlways, // Opens if exists, creates if not
|
||||
CreateNew, // Fails if file exists
|
||||
CreateAlways, // Overwrites existing
|
||||
TruncateExisting // Opens existing and clears it
|
||||
};
|
||||
|
||||
public:
|
||||
STATIC FilePath NormalizeExecutablePath(IN CONST FilePath &path);
|
||||
static auto native_open_file(const Path &path, FileAccess access,
|
||||
FileMode mode, u32 permissions = 0644)
|
||||
-> Result<NativeFileHandle>;
|
||||
|
||||
public:
|
||||
STATIC VOID UnmapFile(IN PCUINT8 mappedPtr);
|
||||
STATIC EXPECT(PCUINT8) MapFile(IN CONST FilePath &path, OUT SIZE_T &size);
|
||||
static auto native_close_file(NativeFileHandle handle) -> void;
|
||||
|
||||
// @param `isOwner` TRUE to allocate/truncate. FALSE to just open.
|
||||
STATIC EXPECT(PUINT8) MapSharedMemory(IN CONST String &name, IN SIZE_T size, IN BOOL isOwner);
|
||||
STATIC VOID UnlinkSharedMemory(IN CONST String &name);
|
||||
public:
|
||||
static auto normalize_executable_path(const Path &path) -> Path;
|
||||
|
||||
STATIC EXPECT(StreamReader) StreamFromFile(IN CONST FilePath &path);
|
||||
STATIC EXPECT(StreamWriter) StreamToFile(IN CONST FilePath &path, IN BOOL overwrite = false);
|
||||
public:
|
||||
static auto unmap_file(const u8 *mapped_ptr) -> void;
|
||||
|
||||
STATIC EXPECT(String) ReadTextFile(IN CONST FilePath &path);
|
||||
STATIC EXPECT(Vector<UINT8>) ReadBinaryFile(IN CONST FilePath &path);
|
||||
STATIC EXPECT(SIZE_T)
|
||||
WriteTextFile(IN CONST FilePath &path, IN CONST String &contents, IN BOOL overwrite = false);
|
||||
STATIC EXPECT(SIZE_T)
|
||||
WriteBinaryFile(IN CONST FilePath &path, IN Span<UINT8> contents, IN BOOL overwrite = false);
|
||||
static auto map_file(const Path &path, usize &size) -> Result<const u8 *>;
|
||||
|
||||
private:
|
||||
STATIC UnorderedMap<PCUINT8, Tuple<PVOID, PVOID, PVOID>> s_mappedFiles;
|
||||
};
|
||||
// @param `is_owner` true to allocate/truncate. false to just open.
|
||||
static auto map_shared_memory(const String &name, usize size, bool is_owner)
|
||||
-> Result<u8 *>;
|
||||
|
||||
class FileOps::MemoryMappedRegion
|
||||
{
|
||||
public:
|
||||
MemoryMappedRegion() = default;
|
||||
~MemoryMappedRegion();
|
||||
static auto unlink_shared_memory(const String &name) -> void;
|
||||
|
||||
MemoryMappedRegion(CONST MemoryMappedRegion &) = delete;
|
||||
MemoryMappedRegion &operator=(CONST MemoryMappedRegion &) = delete;
|
||||
static auto stream_from_file(const Path &path) -> Result<StreamReader>;
|
||||
|
||||
MemoryMappedRegion(MemoryMappedRegion &&other) NOEXCEPT;
|
||||
MemoryMappedRegion &operator=(MemoryMappedRegion &&other) NOEXCEPT;
|
||||
static auto stream_to_file(const Path &path, bool overwrite = false)
|
||||
-> Result<StreamWriter>;
|
||||
|
||||
EXPECT(VOID) Map(NativeFileHandle handle, UINT64 offset, SIZE_T size);
|
||||
static auto read_text_file(const Path &path) -> Result<String>;
|
||||
|
||||
VOID Unmap();
|
||||
VOID Flush();
|
||||
static auto read_binary_file(const Path &path) -> Result<Vec<u8>>;
|
||||
|
||||
PUINT8 GetPtr() CONST
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
static auto write_text_file(const Path &path, const String &contents,
|
||||
bool overwrite = false) -> Result<usize>;
|
||||
|
||||
SIZE_T GetSize() CONST
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
static auto write_binary_file(const Path &path, Span<const u8> contents,
|
||||
bool overwrite = false) -> Result<usize>;
|
||||
|
||||
BOOL IsValid() CONST
|
||||
{
|
||||
return m_ptr != nullptr;
|
||||
}
|
||||
private:
|
||||
static HashMap<const u8 *, std::tuple<void *, void *, void *>> s_mapped_files;
|
||||
};
|
||||
|
||||
private:
|
||||
PUINT8 m_ptr{nullptr};
|
||||
SIZE_T m_size{0};
|
||||
class FileOps::MemoryMappedRegion {
|
||||
public:
|
||||
MemoryMappedRegion() = default;
|
||||
~MemoryMappedRegion();
|
||||
|
||||
MemoryMappedRegion(const MemoryMappedRegion &) = delete;
|
||||
auto operator=(const MemoryMappedRegion &) -> MemoryMappedRegion & = delete;
|
||||
|
||||
MemoryMappedRegion(MemoryMappedRegion &&other) noexcept;
|
||||
auto operator=(MemoryMappedRegion &&other) noexcept -> MemoryMappedRegion &;
|
||||
|
||||
auto map(NativeFileHandle handle, u64 offset, usize size) -> Result<void>;
|
||||
|
||||
auto unmap() -> void;
|
||||
auto flush() -> void;
|
||||
|
||||
[[nodiscard]] auto get_ptr() const -> u8 * { return m_ptr; }
|
||||
|
||||
[[nodiscard]] auto get_size() const -> usize { return m_size; }
|
||||
|
||||
[[nodiscard]] auto is_valid() const -> bool { return m_ptr != nullptr; }
|
||||
|
||||
private:
|
||||
u8 *m_ptr = nullptr;
|
||||
usize m_size = 0;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
HANDLE m_hMap{NULL};
|
||||
HANDLE m_map_handle = NULL;
|
||||
#endif
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace IACore
|
||||
@ -17,74 +17,72 @@
|
||||
|
||||
#include <IACore/Http/Common.hpp>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
class HttpClient : public HttpCommon
|
||||
{
|
||||
public:
|
||||
STATIC EXPECT(UniquePtr<HttpClient>) Create(IN CONST String &host);
|
||||
namespace IACore {
|
||||
class HttpClient : public HttpCommon {
|
||||
public:
|
||||
static Result<UniquePtr<HttpClient>> Create(const String &host);
|
||||
|
||||
~HttpClient();
|
||||
~HttpClient();
|
||||
|
||||
public:
|
||||
EXPECT(String)
|
||||
RawGet(IN CONST String &path, IN Span<CONST Header> headers,
|
||||
IN PCCHAR defaultContentType = "application/x-www-form-urlencoded");
|
||||
EXPECT(String)
|
||||
RawPost(IN CONST String &path, IN Span<CONST Header> headers, IN CONST String &body,
|
||||
IN PCCHAR defaultContentType = "application/x-www-form-urlencoded");
|
||||
public:
|
||||
Result<String>
|
||||
RawGet(const String &path, Span<const Header> headers,
|
||||
const char *defaultContentType = "application/x-www-form-urlencoded");
|
||||
Result<String>
|
||||
RawPost(const String &path, Span<const Header> headers, const String &body,
|
||||
const char *defaultContentType = "application/x-www-form-urlencoded");
|
||||
|
||||
template<typename _response_type>
|
||||
EXPECT(_response_type)
|
||||
JsonGet(IN CONST String &path, IN Span<CONST Header> headers);
|
||||
template <typename _response_type>
|
||||
Result<_response_type> JsonGet(const String &path,
|
||||
Span<const Header> headers);
|
||||
|
||||
template<typename _payload_type, typename _response_type>
|
||||
EXPECT(_response_type)
|
||||
JsonPost(IN CONST String &path, IN Span<CONST Header> headers, IN CONST _payload_type &body);
|
||||
template <typename _payload_type, typename _response_type>
|
||||
Result<_response_type> JsonPost(const String &path,
|
||||
Span<const Header> headers,
|
||||
const _payload_type &body);
|
||||
|
||||
// Certificate verfication is enabled by default
|
||||
VOID EnableCertificateVerfication();
|
||||
VOID DisableCertificateVerfication();
|
||||
// Certificate verfication is enabled by default
|
||||
void EnableCertificateVerfication();
|
||||
void DisableCertificateVerfication();
|
||||
|
||||
public:
|
||||
EResponseCode LastResponseCode()
|
||||
{
|
||||
return m_lastResponseCode;
|
||||
}
|
||||
public:
|
||||
EResponseCode LastResponseCode() { return m_lastResponseCode; }
|
||||
|
||||
private:
|
||||
httplib::Client m_client;
|
||||
EResponseCode m_lastResponseCode;
|
||||
private:
|
||||
httplib::Client m_client;
|
||||
EResponseCode m_lastResponseCode;
|
||||
|
||||
private:
|
||||
String PreprocessResponse(IN CONST String &response);
|
||||
private:
|
||||
String PreprocessResponse(const String &response);
|
||||
|
||||
protected:
|
||||
HttpClient(IN httplib::Client &&client);
|
||||
};
|
||||
protected:
|
||||
HttpClient(httplib::Client &&client);
|
||||
};
|
||||
|
||||
template<typename _response_type>
|
||||
EXPECT(_response_type)
|
||||
HttpClient::JsonGet(IN CONST String &path, IN Span<CONST Header> headers)
|
||||
{
|
||||
const auto rawResponse = RawGet(path, headers, "application/json");
|
||||
if (!rawResponse)
|
||||
return MakeUnexpected(rawResponse.error());
|
||||
if (LastResponseCode() != EResponseCode::OK)
|
||||
return MakeUnexpected(std::format("Server responded with code {}", (INT32) LastResponseCode()));
|
||||
return JSON::ParseToStruct<_response_type>(*rawResponse);
|
||||
}
|
||||
template <typename _response_type>
|
||||
Result<_response_type> HttpClient::JsonGet(const String &path,
|
||||
Span<const Header> headers) {
|
||||
const auto rawResponse = RawGet(path, headers, "application/json");
|
||||
if (!rawResponse)
|
||||
return (rawResponse.error());
|
||||
if (LastResponseCode() != EResponseCode::OK)
|
||||
return (
|
||||
std::format("Server responded with code {}", (i32)LastResponseCode()));
|
||||
return JSON::ParseToStruct<_response_type>(*rawResponse);
|
||||
}
|
||||
|
||||
template<typename _payload_type, typename _response_type>
|
||||
EXPECT(_response_type)
|
||||
HttpClient::JsonPost(IN CONST String &path, IN Span<CONST Header> headers, IN CONST _payload_type &body)
|
||||
{
|
||||
const auto encodedBody = IA_TRY(JSON::EncodeStruct(body));
|
||||
const auto rawResponse = RawPost(path, headers, encodedBody, "application/json");
|
||||
if (!rawResponse)
|
||||
return MakeUnexpected(rawResponse.error());
|
||||
if (LastResponseCode() != EResponseCode::OK)
|
||||
return MakeUnexpected(std::format("Server responded with code {}", (INT32) LastResponseCode()));
|
||||
return JSON::ParseToStruct<_response_type>(*rawResponse);
|
||||
}
|
||||
template <typename _payload_type, typename _response_type>
|
||||
Result<_response_type> HttpClient::JsonPost(const String &path,
|
||||
Span<const Header> headers,
|
||||
const _payload_type &body) {
|
||||
const auto encodedBody = IA_TRY(JSON::EncodeStruct(body));
|
||||
const auto rawResponse =
|
||||
RawPost(path, headers, encodedBody, "application/json");
|
||||
if (!rawResponse)
|
||||
return (rawResponse.error());
|
||||
if (LastResponseCode() != EResponseCode::OK)
|
||||
return (
|
||||
std::format("Server responded with code {}", (i32)LastResponseCode()));
|
||||
return JSON::ParseToStruct<_response_type>(*rawResponse);
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -19,139 +19,135 @@
|
||||
|
||||
#include <httplib.h>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
class HttpCommon
|
||||
{
|
||||
public:
|
||||
enum class EHeaderType
|
||||
{
|
||||
ACCEPT,
|
||||
ACCEPT_CHARSET,
|
||||
ACCEPT_ENCODING,
|
||||
ACCEPT_LANGUAGE,
|
||||
AUTHORIZATION,
|
||||
CACHE_CONTROL,
|
||||
CONNECTION,
|
||||
CONTENT_LENGTH,
|
||||
CONTENT_TYPE,
|
||||
COOKIE,
|
||||
DATE,
|
||||
EXPECT,
|
||||
HOST,
|
||||
IF_MATCH,
|
||||
IF_MODIFIED_SINCE,
|
||||
IF_NONE_MATCH,
|
||||
ORIGIN,
|
||||
PRAGMA,
|
||||
PROXY_AUTHORIZATION,
|
||||
RANGE,
|
||||
REFERER,
|
||||
TE,
|
||||
UPGRADE,
|
||||
USER_AGENT,
|
||||
VIA,
|
||||
WARNING
|
||||
};
|
||||
namespace IACore {
|
||||
class HttpCommon {
|
||||
public:
|
||||
enum class EHeaderType {
|
||||
ACCEPT,
|
||||
ACCEPT_CHARSET,
|
||||
ACCEPT_ENCODING,
|
||||
ACCEPT_LANGUAGE,
|
||||
AUTHORIZATION,
|
||||
CACHE_CONTROL,
|
||||
CONNECTION,
|
||||
CONTENT_LENGTH,
|
||||
CONTENT_TYPE,
|
||||
COOKIE,
|
||||
DATE,
|
||||
EXPECT,
|
||||
HOST,
|
||||
IF_MATCH,
|
||||
IF_MODIFIED_SINCE,
|
||||
IF_NONE_MATCH,
|
||||
ORIGIN,
|
||||
PRAGMA,
|
||||
PROXY_AUTHORIZATION,
|
||||
RANGE,
|
||||
REFERER,
|
||||
TE,
|
||||
UPGRADE,
|
||||
USER_AGENT,
|
||||
VIA,
|
||||
WARNING
|
||||
};
|
||||
|
||||
enum class EResponseCode : INT32
|
||||
{
|
||||
// 1xx Informational
|
||||
CONTINUE = 100,
|
||||
SWITCHING_PROTOCOLS = 101,
|
||||
PROCESSING = 102,
|
||||
EARLY_HINTS = 103,
|
||||
enum class EResponseCode : i32 {
|
||||
// 1xx Informational
|
||||
CONTINUE = 100,
|
||||
SWITCHING_PROTOCOLS = 101,
|
||||
PROCESSING = 102,
|
||||
EARLY_HINTS = 103,
|
||||
|
||||
// 2xx Success
|
||||
OK = 200,
|
||||
CREATED = 201,
|
||||
ACCEPTED = 202,
|
||||
NON_AUTHORITATIVE_INFORMATION = 203,
|
||||
NO_CONTENT = 204,
|
||||
RESET_CONTENT = 205,
|
||||
PARTIAL_CONTENT = 206,
|
||||
MULTI_STATUS = 207,
|
||||
ALREADY_REPORTED = 208,
|
||||
IM_USED = 226,
|
||||
// 2xx Success
|
||||
OK = 200,
|
||||
CREATED = 201,
|
||||
ACCEPTED = 202,
|
||||
NON_AUTHORITATIVE_INFORMATION = 203,
|
||||
NO_CONTENT = 204,
|
||||
RESET_CONTENT = 205,
|
||||
PARTIAL_CONTENT = 206,
|
||||
MULTI_STATUS = 207,
|
||||
ALREADY_REPORTED = 208,
|
||||
IM_USED = 226,
|
||||
|
||||
// 3xx Redirection
|
||||
MULTIPLE_CHOICES = 300,
|
||||
MOVED_PERMANENTLY = 301,
|
||||
FOUND = 302,
|
||||
SEE_OTHER = 303,
|
||||
NOT_MODIFIED = 304,
|
||||
USE_PROXY = 305,
|
||||
TEMPORARY_REDIRECT = 307,
|
||||
PERMANENT_REDIRECT = 308,
|
||||
// 3xx Redirection
|
||||
MULTIPLE_CHOICES = 300,
|
||||
MOVED_PERMANENTLY = 301,
|
||||
FOUND = 302,
|
||||
SEE_OTHER = 303,
|
||||
NOT_MODIFIED = 304,
|
||||
USE_PROXY = 305,
|
||||
TEMPORARY_REDIRECT = 307,
|
||||
PERMANENT_REDIRECT = 308,
|
||||
|
||||
// 4xx Client Error
|
||||
BAD_REQUEST = 400,
|
||||
UNAUTHORIZED = 401,
|
||||
PAYMENT_REQUIRED = 402,
|
||||
FORBIDDEN = 403,
|
||||
NOT_FOUND = 404,
|
||||
METHOD_NOT_ALLOWED = 405,
|
||||
NOT_ACCEPTABLE = 406,
|
||||
PROXY_AUTHENTICATION_REQUIRED = 407,
|
||||
REQUEST_TIMEOUT = 408,
|
||||
CONFLICT = 409,
|
||||
GONE = 410,
|
||||
LENGTH_REQUIRED = 411,
|
||||
PRECONDITION_FAILED = 412,
|
||||
PAYLOAD_TOO_LARGE = 413,
|
||||
URI_TOO_LONG = 414,
|
||||
UNSUPPORTED_MEDIA_TYPE = 415,
|
||||
RANGE_NOT_SATISFIABLE = 416,
|
||||
EXPECTATION_FAILED = 417,
|
||||
IM_A_TEAPOT = 418,
|
||||
MISDIRECTED_REQUEST = 421,
|
||||
UNPROCESSABLE_ENTITY = 422,
|
||||
LOCKED = 423,
|
||||
FAILED_DEPENDENCY = 424,
|
||||
TOO_EARLY = 425,
|
||||
UPGRADE_REQUIRED = 426,
|
||||
PRECONDITION_REQUIRED = 428,
|
||||
TOO_MANY_REQUESTS = 429,
|
||||
REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
|
||||
UNAVAILABLE_FOR_LEGAL_REASONS = 451,
|
||||
// 4xx Client Error
|
||||
BAD_REQUEST = 400,
|
||||
UNAUTHORIZED = 401,
|
||||
PAYMENT_REQUIRED = 402,
|
||||
FORBIDDEN = 403,
|
||||
NOT_FOUND = 404,
|
||||
METHOD_NOT_ALLOWED = 405,
|
||||
NOT_ACCEPTABLE = 406,
|
||||
PROXY_AUTHENTICATION_REQUIRED = 407,
|
||||
REQUEST_TIMEOUT = 408,
|
||||
CONFLICT = 409,
|
||||
GONE = 410,
|
||||
LENGTH_REQUIRED = 411,
|
||||
PRECONDITION_FAILED = 412,
|
||||
PAYLOAD_TOO_LARGE = 413,
|
||||
URI_TOO_LONG = 414,
|
||||
UNSUPPORTED_MEDIA_TYPE = 415,
|
||||
RANGE_NOT_SATISFIABLE = 416,
|
||||
EXPECTATION_FAILED = 417,
|
||||
IM_A_TEAPOT = 418,
|
||||
MISDIRECTED_REQUEST = 421,
|
||||
UNPROCESSABLE_ENTITY = 422,
|
||||
LOCKED = 423,
|
||||
FAILED_DEPENDENCY = 424,
|
||||
TOO_EARLY = 425,
|
||||
UPGRADE_REQUIRED = 426,
|
||||
PRECONDITION_REQUIRED = 428,
|
||||
TOO_MANY_REQUESTS = 429,
|
||||
REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
|
||||
UNAVAILABLE_FOR_LEGAL_REASONS = 451,
|
||||
|
||||
// 5xx Server Error
|
||||
INTERNAL_SERVER_ERROR = 500,
|
||||
NOT_IMPLEMENTED = 501,
|
||||
BAD_GATEWAY = 502,
|
||||
SERVICE_UNAVAILABLE = 503,
|
||||
GATEWAY_TIMEOUT = 504,
|
||||
HTTP_VERSION_NOT_SUPPORTED = 505,
|
||||
VARIANT_ALSO_NEGOTIATES = 506,
|
||||
INSUFFICIENT_STORAGE = 507,
|
||||
LOOP_DETECTED = 508,
|
||||
NOT_EXTENDED = 510,
|
||||
NETWORK_AUTHENTICATION_REQUIRED = 511
|
||||
};
|
||||
// 5xx Server Error
|
||||
INTERNAL_SERVER_ERROR = 500,
|
||||
NOT_IMPLEMENTED = 501,
|
||||
BAD_GATEWAY = 502,
|
||||
SERVICE_UNAVAILABLE = 503,
|
||||
GATEWAY_TIMEOUT = 504,
|
||||
HTTP_VERSION_NOT_SUPPORTED = 505,
|
||||
VARIANT_ALSO_NEGOTIATES = 506,
|
||||
INSUFFICIENT_STORAGE = 507,
|
||||
LOOP_DETECTED = 508,
|
||||
NOT_EXTENDED = 510,
|
||||
NETWORK_AUTHENTICATION_REQUIRED = 511
|
||||
};
|
||||
|
||||
using Header = KeyValuePair<String, String>;
|
||||
using Header = KeyValuePair<String, String>;
|
||||
|
||||
STATIC String UrlEncode(IN CONST String &value);
|
||||
STATIC String UrlDecode(IN CONST String &value);
|
||||
static String UrlEncode(const String &value);
|
||||
static String UrlDecode(const String &value);
|
||||
|
||||
STATIC String HeaderTypeToString(IN EHeaderType type);
|
||||
static String HeaderTypeToString(EHeaderType type);
|
||||
|
||||
STATIC INLINE Header CreateHeader(IN EHeaderType key, IN CONST String &value);
|
||||
STATIC INLINE Header CreateHeader(IN CONST String &key, IN CONST String &value);
|
||||
static inline Header CreateHeader(EHeaderType key, const String &value);
|
||||
static inline Header CreateHeader(const String &key, const String &value);
|
||||
|
||||
STATIC BOOL IsSuccessResponseCode(IN EResponseCode code);
|
||||
static bool IsSuccessResponseCode(EResponseCode code);
|
||||
|
||||
protected:
|
||||
HttpCommon() = default;
|
||||
};
|
||||
protected:
|
||||
HttpCommon() = default;
|
||||
};
|
||||
|
||||
HttpCommon::Header HttpCommon::CreateHeader(IN EHeaderType key, IN CONST String &value)
|
||||
{
|
||||
return std::make_pair(HeaderTypeToString(key), value);
|
||||
}
|
||||
HttpCommon::Header HttpCommon::CreateHeader(EHeaderType key,
|
||||
const String &value) {
|
||||
return std::make_pair(HeaderTypeToString(key), value);
|
||||
}
|
||||
|
||||
HttpCommon::Header HttpCommon::CreateHeader(IN CONST String &key, IN CONST String &value)
|
||||
{
|
||||
return std::make_pair(key, value);
|
||||
}
|
||||
HttpCommon::Header HttpCommon::CreateHeader(const String &key,
|
||||
const String &value) {
|
||||
return std::make_pair(key, value);
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -22,6 +22,6 @@ namespace IACore
|
||||
class HttpServer : public HttpCommon
|
||||
{
|
||||
public:
|
||||
HttpServer(IN CONST String &host, IN UINT32 port);
|
||||
HttpServer(const String &host, u32 port);
|
||||
};
|
||||
} // namespace IACore
|
||||
@ -16,58 +16,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
#include <IACore/Logger.hpp>
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
# include <IACore/Logger.hpp>
|
||||
|
||||
# define IACORE_MAIN() \
|
||||
EXPECT(INT32) _app_entry(IN CONST Vector<String> &args); \
|
||||
int main(int argc, char *argv[]) \
|
||||
#define IACORE_MAIN() \
|
||||
auto _app_entry(const IACore::Vec<IACore::String> &args) -> IACore::Result<IACore::i32>; \
|
||||
auto main(int argc, char *argv[]) -> int \
|
||||
{ \
|
||||
IACore::i32 exit_code = 0; \
|
||||
IACore::initialize(); \
|
||||
IACore::Vec<IACore::String> args; \
|
||||
args.reserve(static_cast<IACore::usize>(argc)); \
|
||||
for (int i = 0; i < argc; ++i) \
|
||||
{ \
|
||||
int exitCode = 0; \
|
||||
IACore::Initialize(); \
|
||||
Vector<String> args; \
|
||||
for (int i = 0; i < argc; i++) \
|
||||
args.push_back(argv[i]); \
|
||||
const auto result = _app_entry(args); \
|
||||
if (!result) \
|
||||
{ \
|
||||
IACore::Logger::Error("Application exited with an error: '{}'.", result.error()); \
|
||||
exitCode = -20; \
|
||||
} \
|
||||
exitCode = *result; \
|
||||
if (!exitCode) \
|
||||
IACore::Logger::Info("Application exited successfully."); \
|
||||
else \
|
||||
IACore::Logger::Error("Application exited with error code: {}.", exitCode); \
|
||||
IACore::Terminate(); \
|
||||
return exitCode; \
|
||||
args.push_back(argv[i]); \
|
||||
} \
|
||||
EXPECT(INT32) _app_entry(IN CONST Vector<String> &args)
|
||||
const auto result = _app_entry(args); \
|
||||
if (!result) \
|
||||
{ \
|
||||
IACore::Logger::error("Application exited with an error: '{}'.", result.error()); \
|
||||
exit_code = -20; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
exit_code = *result; \
|
||||
if (exit_code == 0) \
|
||||
{ \
|
||||
IACore::Logger::info("Application exited successfully."); \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
IACore::Logger::error("Application exited with error code: {}.", exit_code); \
|
||||
} \
|
||||
} \
|
||||
IACore::terminate(); \
|
||||
return exit_code; \
|
||||
} \
|
||||
auto _app_entry(const IACore::Vec<IACore::String> &args) -> IACore::Result<IACore::i32>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
// Must be called from main thread
|
||||
// Safe to call multiple times but, every Initialize call is paired with a corresponding Terminate call
|
||||
VOID Initialize();
|
||||
// Safe to call multiple times but, every initialize call is paired with a corresponding terminate call
|
||||
auto initialize() -> void;
|
||||
|
||||
// Must be called from same thread as Initialize
|
||||
// Safe to call multiple times but, every Initialize call is paired with a corresponding Terminate call
|
||||
VOID Terminate();
|
||||
// Must be called from same thread as initialize
|
||||
// Safe to call multiple times but, every initialize call is paired with a corresponding terminate call
|
||||
auto terminate() -> void;
|
||||
|
||||
BOOL IsInitialized();
|
||||
auto is_initialized() -> bool;
|
||||
|
||||
UINT64 GetUnixTime();
|
||||
UINT64 GetTicksCount();
|
||||
FLOAT64 GetSecondsCount();
|
||||
|
||||
FLOAT32 GetRandom();
|
||||
INT64 GetRandom(IN INT64 min, IN INT64 max);
|
||||
UINT64 GetRandom(IN UINT64 max);
|
||||
|
||||
BOOL IsMainThread();
|
||||
VOID Sleep(IN UINT64 milliseconds);
|
||||
} // namespace IACore
|
||||
|
||||
#endif
|
||||
auto is_main_thread() -> bool;
|
||||
} // namespace IACore
|
||||
@ -17,295 +17,287 @@
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
# include <exception>
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Macros
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
# define valid_iatest_runner(type) iatest::_valid_iatest_runner<type>::value_type
|
||||
#define __iat_micro_test(call) \
|
||||
if (!(call)) \
|
||||
return false
|
||||
|
||||
# define __iat_micro_test(call) \
|
||||
if (!(call)) \
|
||||
return FALSE
|
||||
#define IAT_CHECK(v) __iat_micro_test(_test((v), #v))
|
||||
#define IAT_CHECK_NOT(v) __iat_micro_test(_test_not((v), "NOT " #v))
|
||||
#define IAT_CHECK_EQ(lhs, rhs) __iat_micro_test(_test_eq((lhs), (rhs), #lhs " == " #rhs))
|
||||
#define IAT_CHECK_NEQ(lhs, rhs) __iat_micro_test(_test_neq((lhs), (rhs), #lhs " != " #rhs))
|
||||
|
||||
# define IAT_CHECK(v) __iat_micro_test(_test((v), #v))
|
||||
# define IAT_CHECK_NOT(v) __iat_micro_test(_test_not((v), "NOT " #v))
|
||||
# define IAT_CHECK_EQ(lhs, rhs) __iat_micro_test(_test_eq((lhs), (rhs), #lhs " == " #rhs))
|
||||
# define IAT_CHECK_NEQ(lhs, rhs) __iat_micro_test(_test_neq((lhs), (rhs), #lhs " != " #rhs))
|
||||
#define IAT_CHECK_APPROX(lhs, rhs) __iat_micro_test(_test_approx((lhs), (rhs), #lhs " ~= " #rhs))
|
||||
|
||||
# define IAT_CHECK_APPROX(lhs, rhs) __iat_micro_test(_test_approx((lhs), (rhs), #lhs " ~= " #rhs))
|
||||
#define IAT_UNIT(func) _test_unit([this]() { return this->func(); }, #func)
|
||||
#define IAT_NAMED_UNIT(n, func) _test_unit([this]() { return this->func(); }, n)
|
||||
|
||||
# define IAT_UNIT(func) _test_unit([this]() { return this->func(); }, #func)
|
||||
# define IAT_NAMED_UNIT(n, func) _test_unit([this]() { return this->func(); }, n)
|
||||
#define IAT_BLOCK(name) class name : public ia::iatest::Block
|
||||
|
||||
# define IAT_BLOCK(name) class name : public ia::iatest::block
|
||||
|
||||
# define IAT_BEGIN_BLOCK(_group, _name) \
|
||||
class _group##_##_name : public ia::iatest::block \
|
||||
{ \
|
||||
public: \
|
||||
PCCHAR name() CONST OVERRIDE \
|
||||
{ \
|
||||
return #_group "::" #_name; \
|
||||
} \
|
||||
\
|
||||
private:
|
||||
|
||||
# define IAT_END_BLOCK() \
|
||||
} \
|
||||
;
|
||||
|
||||
# define IAT_BEGIN_TEST_LIST() \
|
||||
#define IAT_BEGIN_BLOCK(_group, _name) \
|
||||
class _group##_##_name : public ia::iatest::Block \
|
||||
{ \
|
||||
public: \
|
||||
VOID declareTests() OVERRIDE \
|
||||
{
|
||||
# define IAT_ADD_TEST(name) IAT_UNIT(name)
|
||||
# define IAT_END_TEST_LIST() \
|
||||
[[nodiscard]] auto get_name() const -> const char * override \
|
||||
{ \
|
||||
return #_group "::" #_name; \
|
||||
} \
|
||||
\
|
||||
private:
|
||||
|
||||
namespace ia::iatest
|
||||
#define IAT_END_BLOCK() \
|
||||
} \
|
||||
;
|
||||
|
||||
#define IAT_BEGIN_TEST_LIST() \
|
||||
public: \
|
||||
void declare_tests() override \
|
||||
{
|
||||
#define IAT_ADD_TEST(name) IAT_UNIT(name)
|
||||
#define IAT_END_TEST_LIST() \
|
||||
} \
|
||||
\
|
||||
private:
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
template<typename T> std::string ToString(CONST T &value)
|
||||
// -------------------------------------------------------------------------
|
||||
// String Conversion Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
template<typename T> auto to_string(const T &value) -> String
|
||||
{
|
||||
if constexpr (std::is_arithmetic_v<T>)
|
||||
{
|
||||
return std::to_string(value);
|
||||
else if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, const char *>)
|
||||
return std::string("\"") + value + "\"";
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, String> || std::is_same_v<T, const char *> || std::is_same_v<T, char *>)
|
||||
{
|
||||
return String("\"") + String(value) + "\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "{Object}";
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T> std::string ToString(T *value)
|
||||
template<typename T> auto to_string(T *value) -> String
|
||||
{
|
||||
if (value == NULLPTR)
|
||||
if (value == nullptr)
|
||||
{
|
||||
return "nullptr";
|
||||
std::stringstream ss;
|
||||
ss << "ptr(" << (void *) value << ")";
|
||||
return ss.str();
|
||||
}
|
||||
return std::format("ptr({})", static_cast<const void *>(value));
|
||||
}
|
||||
|
||||
DEFINE_TYPE(functor_t, std::function<BOOL()>);
|
||||
// -------------------------------------------------------------------------
|
||||
// Types
|
||||
// -------------------------------------------------------------------------
|
||||
using TestFunctor = std::function<bool()>;
|
||||
|
||||
struct unit_t
|
||||
struct TestUnit
|
||||
{
|
||||
std::string Name;
|
||||
functor_t Functor;
|
||||
String name;
|
||||
TestFunctor functor;
|
||||
};
|
||||
|
||||
class block
|
||||
class Block
|
||||
{
|
||||
public:
|
||||
virtual ~block() = default;
|
||||
PURE_VIRTUAL(PCCHAR name() CONST);
|
||||
PURE_VIRTUAL(VOID declareTests());
|
||||
virtual ~Block() = default;
|
||||
[[nodiscard]] virtual auto get_name() const -> const char * = 0;
|
||||
virtual void declare_tests() = 0;
|
||||
|
||||
std::vector<unit_t> &units()
|
||||
auto units() -> Vec<TestUnit> &
|
||||
{
|
||||
return m_units;
|
||||
}
|
||||
|
||||
protected:
|
||||
template<typename T1, typename T2> BOOL _test_eq(IN CONST T1 &lhs, IN CONST T2 &rhs, IN PCCHAR description)
|
||||
template<typename T1, typename T2> auto _test_eq(const T1 &lhs, const T2 &rhs, const char *description) -> bool
|
||||
{
|
||||
if (lhs != rhs)
|
||||
{
|
||||
print_fail(description, ToString(lhs), ToString(rhs));
|
||||
return FALSE;
|
||||
print_fail(description, to_string(lhs), to_string(rhs));
|
||||
return false;
|
||||
}
|
||||
return TRUE;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T1, typename T2> BOOL _test_neq(IN CONST T1 &lhs, IN CONST T2 &rhs, IN PCCHAR description)
|
||||
template<typename T1, typename T2> auto _test_neq(const T1 &lhs, const T2 &rhs, const char *description) -> bool
|
||||
{
|
||||
if (lhs == rhs)
|
||||
{
|
||||
print_fail(description, ToString(lhs), "NOT " + ToString(rhs));
|
||||
return FALSE;
|
||||
print_fail(description, to_string(lhs), "NOT " + to_string(rhs));
|
||||
return false;
|
||||
}
|
||||
return TRUE;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T> BOOL _test_approx(IN T lhs, IN T rhs, IN PCCHAR description)
|
||||
template<typename T> auto _test_approx(T lhs, T rhs, const char *description) -> bool
|
||||
{
|
||||
static_assert(std::is_floating_point_v<T>, "Approx only works for floats/doubles");
|
||||
T diff = std::abs(lhs - rhs);
|
||||
if (diff > static_cast<T>(0.0001))
|
||||
{
|
||||
print_fail(description, ToString(lhs), ToString(rhs));
|
||||
return FALSE;
|
||||
print_fail(description, to_string(lhs), to_string(rhs));
|
||||
return false;
|
||||
}
|
||||
return TRUE;
|
||||
return true;
|
||||
}
|
||||
|
||||
BOOL _test(IN BOOL value, IN PCCHAR description)
|
||||
auto _test(bool value, const char *description) -> bool
|
||||
{
|
||||
if (!value)
|
||||
{
|
||||
printf(__CC_BLUE " %s... " __CC_RED "FAILED" __CC_DEFAULT "\n", description);
|
||||
return FALSE;
|
||||
std::cout << console::blue << " " << description << "... " << console::red << "FAILED"
|
||||
<< console::reset << "\n";
|
||||
return false;
|
||||
}
|
||||
return TRUE;
|
||||
return true;
|
||||
}
|
||||
|
||||
BOOL _test_not(IN BOOL value, IN PCCHAR description)
|
||||
auto _test_not(bool value, const char *description) -> bool
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
printf(__CC_BLUE " %s... " __CC_RED "FAILED" __CC_DEFAULT "\n", description);
|
||||
return FALSE;
|
||||
std::cout << console::blue << " " << description << "... " << console::red << "FAILED"
|
||||
<< console::reset << "\n";
|
||||
return false;
|
||||
}
|
||||
return TRUE;
|
||||
return true;
|
||||
}
|
||||
|
||||
VOID _test_unit(IN functor_t functor, IN PCCHAR name)
|
||||
void _test_unit(TestFunctor functor, const char *name)
|
||||
{
|
||||
m_units.push_back({name, functor});
|
||||
m_units.push_back({name, std::move(functor)});
|
||||
}
|
||||
|
||||
private:
|
||||
VOID print_fail(PCCHAR desc, std::string v1, std::string v2)
|
||||
void print_fail(const char *desc, const String &v1, const String &v2)
|
||||
{
|
||||
printf(__CC_BLUE " %s... " __CC_RED "FAILED" __CC_DEFAULT "\n", desc);
|
||||
printf(__CC_RED " Expected: %s" __CC_DEFAULT "\n", v2.c_str());
|
||||
printf(__CC_RED " Actual: %s" __CC_DEFAULT "\n", v1.c_str());
|
||||
std::cout << console::blue << " " << desc << "... " << console::red << "FAILED" << console::reset
|
||||
<< "\n";
|
||||
std::cout << console::red << " Expected: " << v2 << console::reset << "\n";
|
||||
std::cout << console::red << " Actual: " << v1 << console::reset << "\n";
|
||||
}
|
||||
|
||||
std::vector<unit_t> m_units;
|
||||
Vec<TestUnit> m_units;
|
||||
};
|
||||
|
||||
template<typename block_class>
|
||||
concept valid_block_class = std::derived_from<block_class, block>;
|
||||
template<typename T>
|
||||
concept ValidBlockClass = std::derived_from<T, Block>;
|
||||
|
||||
template<BOOL stopOnFail = false, BOOL isVerbose = false> class runner
|
||||
// -------------------------------------------------------------------------
|
||||
// Runner
|
||||
// -------------------------------------------------------------------------
|
||||
template<bool StopOnFail = false, bool IsVerbose = false> class Runner
|
||||
{
|
||||
public:
|
||||
runner()
|
||||
{
|
||||
}
|
||||
Runner() = default;
|
||||
|
||||
~runner()
|
||||
~Runner()
|
||||
{
|
||||
summarize();
|
||||
}
|
||||
|
||||
template<typename block_class>
|
||||
requires valid_block_class<block_class>
|
||||
VOID testBlock();
|
||||
template<typename BlockClass>
|
||||
requires ValidBlockClass<BlockClass>
|
||||
void test_block();
|
||||
|
||||
private:
|
||||
VOID summarize();
|
||||
void summarize();
|
||||
|
||||
private:
|
||||
SIZE_T m_testCount{0};
|
||||
SIZE_T m_failCount{0};
|
||||
SIZE_T m_blockCount{0};
|
||||
usize m_test_count{0};
|
||||
usize m_fail_count{0};
|
||||
usize m_block_count{0};
|
||||
};
|
||||
|
||||
template<BOOL stopOnFail, BOOL isVerbose>
|
||||
template<typename block_class>
|
||||
requires valid_block_class<block_class>
|
||||
VOID runner<stopOnFail, isVerbose>::testBlock()
|
||||
template<bool StopOnFail, bool IsVerbose>
|
||||
template<typename BlockClass>
|
||||
requires ValidBlockClass<BlockClass>
|
||||
void Runner<StopOnFail, IsVerbose>::test_block()
|
||||
{
|
||||
m_blockCount++;
|
||||
block_class b;
|
||||
b.declareTests();
|
||||
m_block_count++;
|
||||
BlockClass b;
|
||||
b.declare_tests();
|
||||
|
||||
printf(__CC_MAGENTA "Testing [%s]..." __CC_DEFAULT "\n", b.name());
|
||||
std::cout << console::magenta << "Testing [" << b.get_name() << "]..." << console::reset << "\n";
|
||||
|
||||
for (auto &v : b.units())
|
||||
{
|
||||
m_testCount++;
|
||||
if constexpr (isVerbose)
|
||||
m_test_count++;
|
||||
if constexpr (IsVerbose)
|
||||
{
|
||||
printf(__CC_YELLOW " Testing %s...\n" __CC_DEFAULT, v.Name.c_str());
|
||||
std::cout << console::yellow << " Testing " << v.name << "...\n" << console::reset;
|
||||
}
|
||||
|
||||
BOOL result = FALSE;
|
||||
try
|
||||
{
|
||||
result = v.Functor();
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
printf(__CC_RED " CRITICAL EXCEPTION in %s: %s\n" __CC_DEFAULT, v.Name.c_str(), e.what());
|
||||
result = FALSE;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
printf(__CC_RED " UNKNOWN CRITICAL EXCEPTION in %s\n" __CC_DEFAULT, v.Name.c_str());
|
||||
result = FALSE;
|
||||
}
|
||||
// Exceptions are DISABLED. We assume tests do not crash.
|
||||
// If a test crashes (segfault), the OS handles it.
|
||||
bool result = v.functor();
|
||||
|
||||
if (!result)
|
||||
{
|
||||
m_failCount++;
|
||||
if constexpr (stopOnFail)
|
||||
m_fail_count++;
|
||||
if constexpr (StopOnFail)
|
||||
{
|
||||
summarize();
|
||||
exit(-1);
|
||||
std::exit(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
fputs("\n", stdout);
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
template<BOOL stopOnFail, BOOL isVerbose> VOID runner<stopOnFail, isVerbose>::summarize()
|
||||
template<bool StopOnFail, bool IsVerbose> void Runner<StopOnFail, IsVerbose>::summarize()
|
||||
{
|
||||
printf(__CC_GREEN
|
||||
"\n-----------------------------------\n\t SUMMARY\n-----------------------------------\n");
|
||||
std::cout << console::green
|
||||
<< "\n-----------------------------------\n\t SUMMARY\n-----------------------------------\n";
|
||||
|
||||
if (!m_failCount)
|
||||
if (m_fail_count == 0)
|
||||
{
|
||||
printf("\n\tALL TESTS PASSED!\n\n");
|
||||
std::cout << "\n\tALL TESTS PASSED!\n\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
FLOAT64 successRate =
|
||||
(100.0 * static_cast<FLOAT64>(m_testCount - m_failCount) / static_cast<FLOAT64>(m_testCount));
|
||||
printf(__CC_RED "%zu OUT OF %zu TESTS FAILED\n" __CC_YELLOW "Success Rate: %.2f%%\n", m_failCount,
|
||||
m_testCount, successRate);
|
||||
f64 success_rate = (100.0 * static_cast<f64>(m_test_count - m_fail_count) / static_cast<f64>(m_test_count));
|
||||
std::cout << console::red << m_fail_count << " OF " << m_test_count << " TESTS FAILED\n"
|
||||
<< console::yellow << std::format("Success Rate: {:.2f}%\n", success_rate);
|
||||
}
|
||||
|
||||
printf(__CC_MAGENTA "Ran %zu test(s) across %zu block(s)\n" __CC_GREEN
|
||||
"-----------------------------------" __CC_DEFAULT "\n",
|
||||
m_testCount, m_blockCount);
|
||||
std::cout << console::magenta << "Ran " << m_test_count << " test(s) across " << m_block_count << " block(s)\n"
|
||||
<< console::green << "-----------------------------------" << console::reset << "\n";
|
||||
}
|
||||
|
||||
template<typename> struct _valid_iatest_runner : std::false_type
|
||||
{
|
||||
};
|
||||
|
||||
template<BOOL stopOnFail, BOOL isVerbose>
|
||||
struct _valid_iatest_runner<runner<stopOnFail, isVerbose>> : std::true_type
|
||||
{
|
||||
};
|
||||
|
||||
using DefaultRunner = runner<false, true>;
|
||||
using DefaultRunner = Runner<false, true>;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Registry
|
||||
// -------------------------------------------------------------------------
|
||||
class TestRegistry
|
||||
{
|
||||
public:
|
||||
using TestEntry = std::function<void(DefaultRunner &)>;
|
||||
|
||||
static std::vector<TestEntry> &GetEntries()
|
||||
static auto get_entries() -> Vec<TestEntry> &
|
||||
{
|
||||
static std::vector<TestEntry> entries;
|
||||
static Vec<TestEntry> entries;
|
||||
return entries;
|
||||
}
|
||||
|
||||
static int RunAll()
|
||||
static auto run_all() -> i32
|
||||
{
|
||||
DefaultRunner r;
|
||||
auto &entries = GetEntries();
|
||||
printf(__CC_CYAN "[IATest] Discovered %zu Test Blocks\n\n" __CC_DEFAULT, entries.size());
|
||||
auto &entries = get_entries();
|
||||
std::cout << console::cyan << "[IATest] Discovered " << entries.size() << " Test Blocks\n\n"
|
||||
<< console::reset;
|
||||
|
||||
for (auto &entry : entries)
|
||||
{
|
||||
entry(r);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -315,11 +307,9 @@ namespace ia::iatest
|
||||
{
|
||||
AutoRegister()
|
||||
{
|
||||
TestRegistry::GetEntries().push_back([](DefaultRunner &r) { r.testBlock<BlockType>(); });
|
||||
TestRegistry::get_entries().push_back([](DefaultRunner &r) { r.test_block<BlockType>(); });
|
||||
}
|
||||
};
|
||||
} // namespace ia::iatest
|
||||
} // namespace IACore
|
||||
|
||||
# define IAT_REGISTER_ENTRY(Group, Name) static ia::iatest::AutoRegister<Group##_##Name> _iat_reg_##Group##_##Name;
|
||||
|
||||
#endif // __cplusplus
|
||||
#define IAT_REGISTER_ENTRY(Group, Name) static ia::iatest::AutoRegister<Group##_##Name> _iat_reg_##Group##_##Name;
|
||||
@ -19,133 +19,132 @@
|
||||
#include <IACore/ProcessOps.hpp>
|
||||
#include <IACore/SocketOps.hpp>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
using IPC_PacketHeader = RingBufferView::PacketHeader;
|
||||
namespace IACore {
|
||||
using IpcPacketHeader = RingBufferView::PacketHeader;
|
||||
|
||||
struct alignas(64) IPC_SharedMemoryLayout
|
||||
{
|
||||
// =========================================================
|
||||
// SECTION 1: METADATA & HANDSHAKE
|
||||
// =========================================================
|
||||
struct Header
|
||||
{
|
||||
UINT32 Magic; // 0x49414950 ("IAIP")
|
||||
UINT32 Version; // 1
|
||||
UINT64 TotalSize; // Total size of SHM block
|
||||
} Meta;
|
||||
struct alignas(64) IpcSharedMemoryLayout {
|
||||
// =========================================================
|
||||
// SECTION 1: METADATA & HANDSHAKE
|
||||
// =========================================================
|
||||
struct Header {
|
||||
u32 magic; // 0x49414950 ("IAIP")
|
||||
u32 version; // 1
|
||||
u64 total_size; // Total size of SHM block
|
||||
} meta;
|
||||
|
||||
// Pad to ensure MONI starts on a fresh cache line (64 bytes)
|
||||
UINT8 _pad0[64 - sizeof(Header)];
|
||||
// Pad to ensure MONI starts on a fresh cache line (64 bytes)
|
||||
u8 _pad0[64 - sizeof(Header)];
|
||||
|
||||
// =========================================================
|
||||
// SECTION 2: RING BUFFER CONTROL BLOCKS
|
||||
// =========================================================
|
||||
// =========================================================
|
||||
// SECTION 2: RING BUFFER CONTROL BLOCKS
|
||||
// =========================================================
|
||||
|
||||
// RingBufferView ControlBlock is already 64-byte aligned internally.
|
||||
RingBufferView::ControlBlock MONI_Control;
|
||||
RingBufferView::ControlBlock MINO_Control;
|
||||
// RingBufferView ControlBlock is already 64-byte aligned internally.
|
||||
RingBufferView::ControlBlock moni_control;
|
||||
RingBufferView::ControlBlock mino_control;
|
||||
|
||||
// =========================================================
|
||||
// SECTION 3: DATA BUFFER OFFSETS
|
||||
// =========================================================
|
||||
// =========================================================
|
||||
// SECTION 3: DATA BUFFER OFFSETS
|
||||
// =========================================================
|
||||
|
||||
UINT64 MONI_DataOffset;
|
||||
UINT64 MONI_DataSize;
|
||||
u64 moni_data_offset;
|
||||
u64 moni_data_size;
|
||||
|
||||
UINT64 MINO_DataOffset;
|
||||
UINT64 MINO_DataSize;
|
||||
u64 mino_data_offset;
|
||||
u64 mino_data_size;
|
||||
|
||||
// Pad to ensure the actual Data Buffer starts on a fresh cache line
|
||||
UINT8 _pad1[64 - (sizeof(UINT64) * 4)];
|
||||
// Pad to ensure the actual Data Buffer starts on a fresh cache line
|
||||
u8 _pad1[64 - (sizeof(u64) * 4)];
|
||||
|
||||
static constexpr size_t GetHeaderSize()
|
||||
{
|
||||
return sizeof(IPC_SharedMemoryLayout);
|
||||
}
|
||||
};
|
||||
static constexpr auto get_header_size() -> usize {
|
||||
return sizeof(IpcSharedMemoryLayout);
|
||||
}
|
||||
};
|
||||
|
||||
// Static assert to ensure manual padding logic is correct
|
||||
static_assert(sizeof(IPC_SharedMemoryLayout) % 64 == 0, "IPC Layout is not cache-line aligned!");
|
||||
// Static assert to ensure manual padding logic is correct
|
||||
static_assert(sizeof(IpcSharedMemoryLayout) % 64 == 0,
|
||||
"IPC Layout is not cache-line aligned!");
|
||||
|
||||
class IPC_Node
|
||||
{
|
||||
public:
|
||||
virtual ~IPC_Node();
|
||||
class IpcNode {
|
||||
public:
|
||||
virtual ~IpcNode();
|
||||
|
||||
// When Manager spawns a node, `connectionString` is passed
|
||||
// as the first command line argument
|
||||
EXPECT(VOID) Connect(IN PCCHAR connectionString);
|
||||
// When Manager spawns a node, `connection_string` is passed
|
||||
// as the first command line argument
|
||||
auto connect(const char *connection_string) -> Result<void>;
|
||||
|
||||
VOID Update();
|
||||
void update();
|
||||
|
||||
VOID SendSignal(IN UINT8 signal);
|
||||
VOID SendPacket(IN UINT16 packetID, IN Span<CONST UINT8> payload);
|
||||
void send_signal(u8 signal);
|
||||
void send_packet(u16 packet_id, Span<const u8> payload);
|
||||
|
||||
protected:
|
||||
PURE_VIRTUAL(VOID OnSignal(IN UINT8 signal));
|
||||
PURE_VIRTUAL(VOID OnPacket(IN UINT16 packetID, IN Span<CONST UINT8> payload));
|
||||
protected:
|
||||
virtual void on_signal(u8 signal) = 0;
|
||||
virtual void on_packet(u16 packet_id, Span<const u8> payload) = 0;
|
||||
|
||||
private:
|
||||
String m_shmName;
|
||||
PUINT8 m_sharedMemory{};
|
||||
Vector<UINT8> m_receiveBuffer;
|
||||
SocketHandle m_socket{INVALID_SOCKET};
|
||||
private:
|
||||
String m_shm_name;
|
||||
u8 *m_shared_memory{};
|
||||
Vec<u8> m_receive_buffer;
|
||||
SocketHandle m_socket{INVALID_SOCKET};
|
||||
|
||||
UniquePtr<RingBufferView> MONI; // Manager Out, Node In
|
||||
UniquePtr<RingBufferView> MINO; // Manager In, Node Out
|
||||
};
|
||||
Box<RingBufferView> m_moni; // Manager Out, Node In
|
||||
Box<RingBufferView> m_mino; // Manager In, Node Out
|
||||
};
|
||||
|
||||
class IPC_Manager
|
||||
{
|
||||
struct NodeSession
|
||||
{
|
||||
SteadyTimePoint CreationTime{};
|
||||
SharedPtr<ProcessHandle> NodeProcess;
|
||||
class IpcManager {
|
||||
struct NodeSession {
|
||||
std::chrono::system_clock::time_point creation_time{};
|
||||
Box<ProcessHandle> node_process;
|
||||
|
||||
Mutex SendMutex;
|
||||
std::mutex send_mutex;
|
||||
|
||||
String SharedMemName;
|
||||
PUINT8 MappedPtr{};
|
||||
String shared_mem_name;
|
||||
u8 *mapped_ptr{};
|
||||
|
||||
SocketHandle ListenerSocket{INVALID_SOCKET};
|
||||
SocketHandle DataSocket{INVALID_SOCKET};
|
||||
SocketHandle listener_socket{INVALID_SOCKET};
|
||||
SocketHandle data_socket{INVALID_SOCKET};
|
||||
|
||||
UniquePtr<RingBufferView> MONI; // Manager Out, Node In
|
||||
UniquePtr<RingBufferView> MINO; // Manager In, Node Out
|
||||
Box<RingBufferView> moni; // Manager Out, Node In
|
||||
Box<RingBufferView> mino; // Manager In, Node Out
|
||||
|
||||
BOOL IsReady{FALSE};
|
||||
bool is_ready{false};
|
||||
|
||||
VOID SendSignal(IN UINT8 signal);
|
||||
VOID SendPacket(IN UINT16 packetID, IN Span<CONST UINT8> payload);
|
||||
};
|
||||
void send_signal(u8 signal);
|
||||
void send_packet(u16 packet_id, Span<const u8> payload);
|
||||
};
|
||||
|
||||
public:
|
||||
STATIC CONSTEXPR UINT32 DEFAULT_NODE_SHARED_MEMORY_SIZE = SIZE_MB(4);
|
||||
public:
|
||||
static constexpr u32 DEFAULT_NODE_SHARED_MEMORY_SIZE = 4 * 1024 * 1024; // 4MB
|
||||
|
||||
public:
|
||||
IPC_Manager();
|
||||
virtual ~IPC_Manager();
|
||||
public:
|
||||
virtual ~IpcManager();
|
||||
|
||||
VOID Update();
|
||||
void update();
|
||||
|
||||
EXPECT(NativeProcessID)
|
||||
SpawnNode(IN CONST FilePath &executablePath, IN UINT32 sharedMemorySize = DEFAULT_NODE_SHARED_MEMORY_SIZE);
|
||||
BOOL WaitTillNodeIsOnline(IN NativeProcessID node);
|
||||
auto spawn_node(const Path &executable_path,
|
||||
u32 shared_memory_size = DEFAULT_NODE_SHARED_MEMORY_SIZE)
|
||||
-> Result<NativeProcessID>;
|
||||
|
||||
VOID ShutdownNode(IN NativeProcessID node);
|
||||
auto wait_till_node_is_online(NativeProcessID node) -> bool;
|
||||
|
||||
VOID SendSignal(IN NativeProcessID node, IN UINT8 signal);
|
||||
VOID SendPacket(IN NativeProcessID node, IN UINT16 packetID, IN Span<CONST UINT8> payload);
|
||||
void shutdown_node(NativeProcessID node);
|
||||
|
||||
protected:
|
||||
PURE_VIRTUAL(VOID OnSignal(IN NativeProcessID node, IN UINT8 signal));
|
||||
PURE_VIRTUAL(VOID OnPacket(IN NativeProcessID node, IN UINT16 packetID, IN Span<CONST UINT8> payload));
|
||||
void send_signal(NativeProcessID node, u8 signal);
|
||||
void send_packet(NativeProcessID node, u16 packet_id, Span<const u8> payload);
|
||||
|
||||
private:
|
||||
Vector<UINT8> m_receiveBuffer;
|
||||
Vector<UniquePtr<NodeSession>> m_activeSessions;
|
||||
Vector<UniquePtr<NodeSession>> m_pendingSessions;
|
||||
UnorderedMap<NativeProcessID, NodeSession *> m_activeSessionMap;
|
||||
};
|
||||
protected:
|
||||
virtual void on_signal(NativeProcessID node, u8 signal) = 0;
|
||||
virtual void on_packet(NativeProcessID node, u16 packet_id,
|
||||
Span<const u8> payload) = 0;
|
||||
|
||||
private:
|
||||
Vec<u8> m_receive_buffer;
|
||||
Vec<Box<NodeSession>> m_active_sessions;
|
||||
Vec<Box<NodeSession>> m_pending_sessions;
|
||||
HashMap<NativeProcessID, NodeSession *> m_active_session_map;
|
||||
|
||||
protected:
|
||||
IpcManager();
|
||||
};
|
||||
} // namespace IACore
|
||||
@ -17,42 +17,126 @@
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
#include <simdjson.h>
|
||||
#include <glaze/glaze.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <simdjson.h>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
class JSON
|
||||
{
|
||||
private:
|
||||
STATIC CONSTEXPR AUTO GLAZE_JSON_OPTS = glz::opts{.error_on_unknown_keys = false};
|
||||
namespace IACore {
|
||||
class JsonDocument {
|
||||
public:
|
||||
// Move-only (Safety: Cannot copy the parser state cheaply)
|
||||
JsonDocument(JsonDocument &&) noexcept = default;
|
||||
JsonDocument &operator=(JsonDocument &&) noexcept = default;
|
||||
JsonDocument(const JsonDocument &) = delete;
|
||||
JsonDocument &operator=(const JsonDocument &) = delete;
|
||||
|
||||
public:
|
||||
STATIC EXPECT(nlohmann::json) Parse(IN CONST String &json);
|
||||
STATIC EXPECT(Pair<SharedPtr<simdjson::dom::parser>, simdjson::dom::object>)
|
||||
ParseReadOnly(IN CONST String &json);
|
||||
STATIC String Encode(IN nlohmann::json data);
|
||||
// Accessor: Get the root element (Object, Array, etc.)
|
||||
// The returned 'element' is valid only as long as this JsonDocument is alive.
|
||||
[[nodiscard]]
|
||||
auto root() const noexcept -> simdjson::dom::element {
|
||||
return m_root;
|
||||
}
|
||||
|
||||
template<typename _object_type> STATIC EXPECT(_object_type) ParseToStruct(IN CONST String &json);
|
||||
template<typename _object_type> STATIC EXPECT(String) EncodeStruct(IN CONST _object_type &data);
|
||||
};
|
||||
private:
|
||||
// Only created via JSON::parse_read_only factory
|
||||
friend class Json;
|
||||
|
||||
template<typename _object_type> EXPECT(_object_type) JSON::ParseToStruct(IN CONST String &json)
|
||||
{
|
||||
_object_type result{};
|
||||
const auto parseError = glz::read_json<GLAZE_JSON_OPTS>(result, json);
|
||||
if (parseError)
|
||||
return MakeUnexpected(std::format("JSON Error: {}", glz::format_error(parseError, json)));
|
||||
return result;
|
||||
}
|
||||
JsonDocument(Box<simdjson::dom::parser> p, simdjson::dom::element r)
|
||||
: m_parser(std::move(p)), m_root(r) {}
|
||||
|
||||
template<typename _object_type> EXPECT(String) JSON::EncodeStruct(IN CONST _object_type &data)
|
||||
{
|
||||
String result;
|
||||
const auto encodeError = glz::write_json(data, result);
|
||||
if (encodeError)
|
||||
return MakeUnexpected(std::format("JSON Error: {}", glz::format_error(encodeError)));
|
||||
return result;
|
||||
}
|
||||
// ORDER MATTERS: Parser (Owner) must be destroyed AFTER the Root (View).
|
||||
// In C++, members are destroyed in reverse declaration order.
|
||||
Box<simdjson::dom::parser> m_parser;
|
||||
simdjson::dom::element m_root;
|
||||
};
|
||||
|
||||
class Json {
|
||||
private:
|
||||
// Glaze options (Compile-time configuration)
|
||||
static constexpr auto GLAZE_OPTS = glz::opts{.error_on_unknown_keys = false};
|
||||
|
||||
public:
|
||||
// -------------------------------------------------------------------------
|
||||
// 1. Standard Parsing (Nlohmann - mutable, owning, slower)
|
||||
// -------------------------------------------------------------------------
|
||||
static auto parse(const String &json_str) -> Result<nlohmann::json>;
|
||||
static auto encode(const nlohmann::json &data) -> String;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 2. Read-Only Parsing (Simdjson - immutable, zero-copyish, extremely fast)
|
||||
// -------------------------------------------------------------------------
|
||||
// Returns a safe JsonDocument wrapper instead of a raw pair
|
||||
static auto parse_read_only(const String &json_str) -> Result<JsonDocument>;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 3. Struct Serialization (Glaze - reflection-based, very fast)
|
||||
// -------------------------------------------------------------------------
|
||||
template <typename T>
|
||||
static auto parse_to_struct(const String &json_str) -> Result<T>;
|
||||
|
||||
template <typename T>
|
||||
static auto encode_struct(const T &data) -> Result<String>;
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// Implementation
|
||||
// =============================================================================
|
||||
|
||||
inline auto Json::parse(const String &json_str) -> Result<nlohmann::json> {
|
||||
// 3rd arg=false (no exception), 4th arg=true (ignore comments)
|
||||
const auto res = nlohmann::json::parse(json_str, nullptr, false, true);
|
||||
|
||||
if (res.is_discarded()) {
|
||||
return fail("Failed to parse JSON (Invalid Syntax)");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
inline auto Json::parse_read_only(const String &json_str)
|
||||
-> Result<JsonDocument> {
|
||||
// 1. Allocate parser on heap (reusing this via a pool would be even faster in
|
||||
// future)
|
||||
auto parser = make_box<simdjson::dom::parser>();
|
||||
|
||||
// 2. Use 'element' to support Arrays/Strings/Null roots, not just Objects
|
||||
simdjson::dom::element root;
|
||||
|
||||
// 3. Parse
|
||||
simdjson::error_code error = parser->parse(json_str).get(root);
|
||||
|
||||
if (error) {
|
||||
return fail("JSON Error: {}", simdjson::error_message(error));
|
||||
}
|
||||
|
||||
// 4. Return Safe Wrapper (Owner + View)
|
||||
return JsonDocument(std::move(parser), root);
|
||||
}
|
||||
|
||||
inline auto Json::encode(const nlohmann::json &data) -> String {
|
||||
return data.dump();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline auto Json::parse_to_struct(const String &json_str) -> Result<T> {
|
||||
T result{};
|
||||
// glz::read_json returns an error code (bool-like optional)
|
||||
const auto err = glz::read_json<GLAZE_OPTS>(result, json_str);
|
||||
|
||||
if (err) {
|
||||
return fail("JSON Struct Parse Error: {}",
|
||||
glz::format_error(err, json_str));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline auto Json::encode_struct(const T &data) -> Result<String> {
|
||||
String result;
|
||||
const auto err = glz::write_json(data, result);
|
||||
|
||||
if (err) {
|
||||
return fail("JSON Struct Encode Error");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -1,144 +1,102 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
#define IA_LOG_SET_FILE(path) IACore::Logger::EnableLoggingToDisk(path)
|
||||
#define IA_LOG_SET_LEVEL(level) IACore::Logger::SetLogLevel(IACore::Logger::ELogLevel::level)
|
||||
#define IA_LOG_SET_FILE(path) IACore::Logger::enable_logging_to_disk(path)
|
||||
#define IA_LOG_SET_LEVEL(level) \
|
||||
IACore::Logger::set_log_level(IACore::Logger::LogLevel::level)
|
||||
|
||||
#define IA_LOG_TRACE(...) IACore::Logger::Trace(__VA_ARGS__)
|
||||
#define IA_LOG_DEBUG(...) IACore::Logger::Debug(__VA_ARGS__)
|
||||
#define IA_LOG_INFO(...) IACore::Logger::Info(__VA_ARGS__)
|
||||
#define IA_LOG_WARN(...) IACore::Logger::Warn(__VA_ARGS__)
|
||||
#define IA_LOG_ERROR(...) IACore::Logger::Error(__VA_ARGS__)
|
||||
#define IA_LOG_TRACE(...) IACore::Logger::trace(__VA_ARGS__)
|
||||
#define IA_LOG_DEBUG(...) IACore::Logger::debug(__VA_ARGS__)
|
||||
#define IA_LOG_INFO(...) IACore::Logger::info(__VA_ARGS__)
|
||||
#define IA_LOG_WARN(...) IACore::Logger::warn(__VA_ARGS__)
|
||||
#define IA_LOG_ERROR(...) IACore::Logger::error(__VA_ARGS__)
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
class Logger
|
||||
{
|
||||
public:
|
||||
enum class ELogLevel
|
||||
{
|
||||
TRACE,
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARN,
|
||||
ERROR
|
||||
};
|
||||
namespace IACore {
|
||||
class Logger {
|
||||
public:
|
||||
enum class LogLevel { Trace, Debug, Info, Warn, Error };
|
||||
|
||||
public:
|
||||
STATIC BOOL EnableLoggingToDisk(IN PCCHAR filePath);
|
||||
STATIC VOID SetLogLevel(IN ELogLevel logLevel);
|
||||
public:
|
||||
static auto enable_logging_to_disk(const char *file_path) -> Result<void>;
|
||||
static auto set_log_level(LogLevel log_level) -> void;
|
||||
|
||||
template<typename... Args> STATIC VOID Trace(FormatterString<Args...> fmt, Args &&...args)
|
||||
{
|
||||
LogTrace(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
template <typename... Args>
|
||||
static auto trace(std::format_string<Args...> fmt, Args &&...args) -> void {
|
||||
log_trace(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
|
||||
template<typename... Args> STATIC VOID Debug(FormatterString<Args...> fmt, Args &&...args)
|
||||
{
|
||||
LogDebug(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
template <typename... Args>
|
||||
static auto debug(std::format_string<Args...> fmt, Args &&...args) -> void {
|
||||
log_debug(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
|
||||
template<typename... Args> STATIC VOID Info(FormatterString<Args...> fmt, Args &&...args)
|
||||
{
|
||||
LogInfo(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
template <typename... Args>
|
||||
static auto info(std::format_string<Args...> fmt, Args &&...args) -> void {
|
||||
log_info(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
|
||||
template<typename... Args> STATIC VOID Warn(FormatterString<Args...> fmt, Args &&...args)
|
||||
{
|
||||
LogWarn(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
template <typename... Args>
|
||||
static auto warn(std::format_string<Args...> fmt, Args &&...args) -> void {
|
||||
log_warn(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
|
||||
template<typename... Args> STATIC VOID Error(FormatterString<Args...> fmt, Args &&...args)
|
||||
{
|
||||
LogError(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
template <typename... Args>
|
||||
static auto error(std::format_string<Args...> fmt, Args &&...args) -> void {
|
||||
log_error(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
|
||||
STATIC VOID FlushLogs();
|
||||
static auto flush_logs() -> void;
|
||||
|
||||
private:
|
||||
private:
|
||||
#if IA_DISABLE_LOGGING > 0
|
||||
STATIC VOID LogTrace(IN String &&msg)
|
||||
{
|
||||
UNUSED(msg);
|
||||
}
|
||||
static auto log_trace(String &&msg) -> void { UNUSED(msg); }
|
||||
|
||||
STATIC VOID LogDebug(IN String &&msg)
|
||||
{
|
||||
UNUSED(msg);
|
||||
}
|
||||
static auto log_debug(String &&msg) -> void { UNUSED(msg); }
|
||||
|
||||
STATIC VOID LogInfo(IN String &&msg)
|
||||
{
|
||||
UNUSED(msg);
|
||||
}
|
||||
static auto log_info(String &&msg) -> void { UNUSED(msg); }
|
||||
|
||||
STATIC VOID LogWarn(IN String &&msg)
|
||||
{
|
||||
UNUSED(msg);
|
||||
}
|
||||
static auto log_warn(String &&msg) -> void { UNUSED(msg); }
|
||||
|
||||
STATIC VOID LogError(IN String &&msg)
|
||||
{
|
||||
UNUSED(msg);
|
||||
}
|
||||
static auto log_error(String &&msg) -> void { UNUSED(msg); }
|
||||
#else
|
||||
STATIC VOID LogTrace(IN String &&msg)
|
||||
{
|
||||
if (s_logLevel <= ELogLevel::TRACE)
|
||||
LogInternal(__CC_WHITE, "TRACE", IA_MOVE(msg));
|
||||
}
|
||||
static auto log_trace(String &&msg) -> void {
|
||||
if (m_log_level <= LogLevel::Trace)
|
||||
log_internal(console::RESET, "TRACE", std::move(msg));
|
||||
}
|
||||
|
||||
STATIC VOID LogDebug(IN String &&msg)
|
||||
{
|
||||
if (s_logLevel <= ELogLevel::DEBUG)
|
||||
LogInternal(__CC_CYAN, "DEBUG", IA_MOVE(msg));
|
||||
}
|
||||
static auto log_debug(String &&msg) -> void {
|
||||
if (m_log_level <= LogLevel::Debug)
|
||||
log_internal(console::CYAN, "DEBUG", std::move(msg));
|
||||
}
|
||||
|
||||
STATIC VOID LogInfo(IN String &&msg)
|
||||
{
|
||||
if (s_logLevel <= ELogLevel::INFO)
|
||||
LogInternal(__CC_GREEN, "INFO", IA_MOVE(msg));
|
||||
}
|
||||
static auto log_info(String &&msg) -> void {
|
||||
if (m_log_level <= LogLevel::Info)
|
||||
log_internal(console::GREEN, "INFO", std::move(msg));
|
||||
}
|
||||
|
||||
STATIC VOID LogWarn(IN String &&msg)
|
||||
{
|
||||
if (s_logLevel <= ELogLevel::WARN)
|
||||
LogInternal(__CC_YELLOW, "WARN", IA_MOVE(msg));
|
||||
}
|
||||
static auto log_warn(String &&msg) -> void {
|
||||
if (m_log_level <= LogLevel::Warn)
|
||||
log_internal(console::YELLOW, "WARN", std::move(msg));
|
||||
}
|
||||
|
||||
STATIC VOID LogError(IN String &&msg)
|
||||
{
|
||||
if (s_logLevel <= ELogLevel::ERROR)
|
||||
LogInternal(__CC_RED, "ERROR", IA_MOVE(msg));
|
||||
}
|
||||
static auto log_error(String &&msg) -> void {
|
||||
if (m_log_level <= LogLevel::Error)
|
||||
log_internal(console::RED, "ERROR", std::move(msg));
|
||||
}
|
||||
#endif
|
||||
|
||||
STATIC VOID LogInternal(IN PCCHAR prefix, IN PCCHAR tag, IN String &&msg);
|
||||
static auto log_internal(const char *prefix, const char *tag, String &&msg)
|
||||
-> void;
|
||||
|
||||
private:
|
||||
STATIC ELogLevel s_logLevel;
|
||||
STATIC std::ofstream s_logFile;
|
||||
private:
|
||||
static LogLevel m_log_level;
|
||||
static std::ofstream m_log_file;
|
||||
|
||||
STATIC VOID Initialize();
|
||||
STATIC VOID Terminate();
|
||||
static auto initialize() -> void;
|
||||
static auto terminate() -> void;
|
||||
|
||||
friend VOID Initialize();
|
||||
friend VOID Terminate();
|
||||
};
|
||||
friend void initialize();
|
||||
friend void terminate();
|
||||
};
|
||||
} // namespace IACore
|
||||
@ -15,624 +15,233 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Platform Detection
|
||||
// -------------------------------------------------------------------------
|
||||
#if defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64)
|
||||
# define IA_ARCH_X64 1
|
||||
#define IA_ARCH_X64 1
|
||||
#elif defined(__aarch64__) || defined(_M_ARM64)
|
||||
# define IA_ARCH_ARM64 1
|
||||
#define IA_ARCH_ARM64 1
|
||||
#elif defined(__wasm__) || defined(__wasm32__) || defined(__wasm64__)
|
||||
# define IA_ARCH_WASM 1
|
||||
#define IA_ARCH_WASM 1
|
||||
#else
|
||||
# error "IACore: Unsupported Architecture. Only x64, ARM64, and WASM are supported."
|
||||
#error "IACore: Unsupported Architecture."
|
||||
#endif
|
||||
|
||||
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
|
||||
# ifdef _WIN64
|
||||
# define IA_PLATFORM_WIN64 1
|
||||
# define IA_PLATFORM_WINDOWS 1
|
||||
# else
|
||||
# error "IACore: 32-bit Windows is not supported"
|
||||
# endif
|
||||
#define IA_PLATFORM_WINDOWS 1
|
||||
#elif __APPLE__
|
||||
# include <TargetConditionals.h>
|
||||
# if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR || TARGET_OS_MACCATALYST
|
||||
# define IA_PLATFORM_IOS 1
|
||||
# elif TARGET_OS_MAC
|
||||
# define IA_PLATFORM_MAC 1
|
||||
# endif
|
||||
# define IA_PLATFORM_UNIX 1
|
||||
#elif __ANDROID__
|
||||
# define IA_PLATFORM_ANDROID 1
|
||||
# define IA_PLATFORM_LINUX 1
|
||||
# define IA_PLATFORM_UNIX 1
|
||||
#include <TargetConditionals.h>
|
||||
#define IA_PLATFORM_APPLE 1
|
||||
#define IA_PLATFORM_UNIX 1
|
||||
#elif __linux__
|
||||
# define IA_PLATFORM_LINUX 1
|
||||
# define IA_PLATFORM_UNIX 1
|
||||
#define IA_PLATFORM_LINUX 1
|
||||
#define IA_PLATFORM_UNIX 1
|
||||
#elif __wasm__
|
||||
# define IA_PLATFORM_WASM 1
|
||||
#define IA_PLATFORM_WASM 1
|
||||
#else
|
||||
# error "IACore: Unsupported Platform. Only Windows, Linux, MacOS, Android and iOS are supported."
|
||||
#error "IACore: Unsupported Platform."
|
||||
#endif
|
||||
|
||||
#if IA_PLATFORM_WIN64
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# ifndef NOMINMAX
|
||||
# define NOMINMAX
|
||||
# endif
|
||||
# include <windows.h>
|
||||
# undef VOID
|
||||
# undef ERROR
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#elif IA_PLATFORM_UNIX
|
||||
# include <unistd.h>
|
||||
# include <sys/wait.h>
|
||||
# include <sys/mman.h>
|
||||
# include <sys/stat.h>
|
||||
# include <fcntl.h>
|
||||
# include <spawn.h>
|
||||
# include <signal.h>
|
||||
#include <signal.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Configuration Macros
|
||||
// -----------------------------------------------------------------------------
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <source_location>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#define IA_CHECK(o) (o > 0)
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <tl/expected.hpp>
|
||||
|
||||
#if defined(__IA_DEBUG) && __IA_DEBUG
|
||||
# define __DEBUG_MODE__
|
||||
# define __BUILD_MODE_NAME "debug"
|
||||
# define DEBUG_ONLY(v) v
|
||||
# ifndef _DEBUG
|
||||
# define _DEBUG
|
||||
# endif
|
||||
namespace IACore {
|
||||
|
||||
// =============================================================================
|
||||
// Primitive Types (Rust Style)
|
||||
// =============================================================================
|
||||
using u8 = std::uint8_t;
|
||||
using u16 = std::uint16_t;
|
||||
using u32 = std::uint32_t;
|
||||
using u64 = std::uint64_t;
|
||||
|
||||
using i8 = std::int8_t;
|
||||
using i16 = std::int16_t;
|
||||
using i32 = std::int32_t;
|
||||
using i64 = std::int64_t;
|
||||
|
||||
using f32 = float;
|
||||
using f64 = double;
|
||||
|
||||
using usize = std::size_t;
|
||||
using isize = std::ptrdiff_t;
|
||||
|
||||
// =============================================================================
|
||||
// Build Environment & Constants
|
||||
// =============================================================================
|
||||
namespace env {
|
||||
#if defined(NDEBUG)
|
||||
constexpr bool is_debug = false;
|
||||
constexpr bool is_release = true;
|
||||
#else
|
||||
# define __RELEASE_MODE__
|
||||
# define __BUILD_MODE_NAME "release"
|
||||
# ifndef NDEBUG
|
||||
# define NDEBUG
|
||||
# endif
|
||||
# ifndef __OPTIMIZE__
|
||||
# define __OPTIMIZE__
|
||||
# endif
|
||||
# define DEBUG_ONLY(f)
|
||||
constexpr bool is_debug = true;
|
||||
constexpr bool is_release = false;
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <memory.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
# include <bit>
|
||||
# include <new>
|
||||
# include <span>
|
||||
# include <atomic>
|
||||
# include <mutex>
|
||||
# include <thread>
|
||||
# include <limits>
|
||||
# include <cstring>
|
||||
# include <cstddef>
|
||||
# include <chrono>
|
||||
# include <iomanip>
|
||||
# include <charconv>
|
||||
# include <fstream>
|
||||
# include <iostream>
|
||||
# include <concepts>
|
||||
# include <filesystem>
|
||||
# include <functional>
|
||||
# include <type_traits>
|
||||
# include <initializer_list>
|
||||
# include <condition_variable>
|
||||
|
||||
# include <tuple>
|
||||
# include <array>
|
||||
# include <deque>
|
||||
# include <string>
|
||||
# include <vector>
|
||||
# include <format>
|
||||
# include <sstream>
|
||||
# include <optional>
|
||||
# include <string_view>
|
||||
|
||||
# include <tl/expected.hpp>
|
||||
# include <ankerl/unordered_dense.h>
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
constexpr bool is_windows = true;
|
||||
constexpr bool is_unix = false;
|
||||
#else
|
||||
# include <float.h>
|
||||
# include <stdbool.h>
|
||||
# include <stddef.h>
|
||||
# include <string.h>
|
||||
constexpr bool is_windows = false;
|
||||
constexpr bool is_unix = true;
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Security Macros
|
||||
// -----------------------------------------------------------------------------
|
||||
constexpr usize max_path_len = 4096;
|
||||
} // namespace env
|
||||
|
||||
#define IA_PANIC(msg) \
|
||||
{ \
|
||||
fprintf(stderr, "PANIC: %s\n", msg); \
|
||||
__builtin_trap(); \
|
||||
}
|
||||
// =============================================================================
|
||||
// Memory & Ownership (Rust Semantics)
|
||||
// =============================================================================
|
||||
template <typename T> using Box = std::unique_ptr<T>;
|
||||
|
||||
// Advanced Security features are not included in OSS builds
|
||||
// (OSS version does not implement 'IAC_CHECK_*'s)
|
||||
#define IACORE_SECURITY_LEVEL 0
|
||||
template <typename T> using Arc = std::shared_ptr<T>;
|
||||
|
||||
#define IAC_SEC_LEVEL(v) (IACORE_SECURITY_LEVEL >= v)
|
||||
template <typename T> using Weak = std::weak_ptr<T>;
|
||||
|
||||
#if __IA_DEBUG || IAC_SEC_LEVEL(1)
|
||||
# define __IAC_OVERFLOW_CHECKS 1
|
||||
#else
|
||||
# define __IAC_OVERFLOW_CHECKS 0
|
||||
#endif
|
||||
#if __IA_DEBUG || IAC_SEC_LEVEL(2)
|
||||
# define __IAC_SANITY_CHECKS 1
|
||||
#else
|
||||
# define __IAC_SANITY_CHECKS 0
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Language Abstraction Macros
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#define AUTO auto
|
||||
#define CONST const
|
||||
#define STATIC static
|
||||
#define EXTERN extern
|
||||
|
||||
#ifdef __cplusplus
|
||||
# define VIRTUAL virtual
|
||||
# define OVERRIDE override
|
||||
# define CONSTEXPR constexpr
|
||||
# define CONSTEVAL consteval
|
||||
# define EXPLICIT explicit
|
||||
# define NOEXCEPT noexcept
|
||||
# define NULLPTR nullptr
|
||||
# define IA_MOVE(...) std::move(__VA_ARGS__)
|
||||
# define DECONST(t, v) const_cast<t>(v)
|
||||
# define NORETURN [[noreturn]]
|
||||
#else
|
||||
# define VIRTUAL
|
||||
# define OVERRIDE
|
||||
# define CONSTEXPR const
|
||||
# define CONSTEVAL
|
||||
# define EXPLICIT
|
||||
# define NOEXCEPT
|
||||
# define NULLPTR NULL
|
||||
# define IA_MOVE(...) (__VA_ARGS__)
|
||||
# define DECONST(t, v) ((t) (v))
|
||||
# define NORETURN
|
||||
#endif
|
||||
|
||||
#define DEFINE_TYPE(t, v) typedef v t
|
||||
#define FORWARD_DECLARE(t, i) t i
|
||||
|
||||
#ifdef __cplusplus
|
||||
# define PURE_VIRTUAL(...) VIRTUAL __VA_ARGS__ = 0
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Attributes & Compiler Intrinsics
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#define INLINE inline
|
||||
#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
|
||||
# define ALWAYS_INLINE __attribute__((always_inline)) inline
|
||||
#elif defined(_MSC_VER)
|
||||
# define ALWAYS_INLINE __forceinline
|
||||
#else
|
||||
# define ALWAYS_INLINE inline
|
||||
#endif
|
||||
|
||||
#define UNUSED(v) ((void) v);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
# define NO_DISCARD(s) [[nodiscard(s)]]
|
||||
# define B_LIKELY(cond) (cond) [[likely]]
|
||||
# define B_UNLIKELY(cond) (cond) [[unlikely]]
|
||||
#else
|
||||
# define NO_DISCARD(s)
|
||||
# define B_LIKELY(cond) (cond)
|
||||
# define B_UNLIKELY(cond) (cond)
|
||||
#endif
|
||||
|
||||
#define __INTERNAL_IA_STRINGIFY(value) #value
|
||||
#define IA_STRINGIFY(value) __INTERNAL_IA_STRINGIFY(value)
|
||||
|
||||
#define ALIGN(a) alignas(a)
|
||||
|
||||
#define ASM(...) __asm__ volatile(__VA_ARGS__)
|
||||
|
||||
#ifndef NULL
|
||||
# define NULL 0
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
# undef TRUE
|
||||
# undef FALSE
|
||||
# ifdef __cplusplus
|
||||
# define FALSE false
|
||||
# define TRUE true
|
||||
# else
|
||||
# define FALSE 0
|
||||
# define TRUE 1
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Parameter Annotations
|
||||
#define IN
|
||||
#define OUT
|
||||
#define INOUT
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Extern C Handling
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#ifdef __cplusplus
|
||||
# define IA_EXTERN_C_BEGIN \
|
||||
extern "C" \
|
||||
{
|
||||
# define IA_EXTERN_C_END }
|
||||
# define C_DECL(f) extern "C" f
|
||||
#else
|
||||
# define IA_EXTERN_C_BEGIN
|
||||
# define IA_EXTERN_C_END
|
||||
# define C_DECL(f) f
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Utilities
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#ifdef __cplusplus
|
||||
# define CAST(v, t) (static_cast<t>(v))
|
||||
# define REINTERPRET(v, t) (reinterpret_cast<t>(v))
|
||||
#else
|
||||
# define CAST(v, t) ((t) (v))
|
||||
#endif
|
||||
|
||||
// Templates and Aliases
|
||||
#ifdef __cplusplus
|
||||
# define ALIAS_FUNCTION(alias, function) \
|
||||
template<typename... Args> auto alias(Args &&...args) -> decltype(function(std::forward<Args>(args)...)) \
|
||||
{ \
|
||||
return function(std::forward<Args>(args)...); \
|
||||
}
|
||||
|
||||
# define ALIAS_TEMPLATE_FUNCTION(t, alias, function) \
|
||||
template<typename t, typename... Args> \
|
||||
auto alias(Args &&...args) -> decltype(function<t>(std::forward<Args>(args)...)) \
|
||||
{ \
|
||||
return function<t>(std::forward<Args>(args)...); \
|
||||
}
|
||||
#endif
|
||||
|
||||
// Assertions
|
||||
#define IA_RELEASE_ASSERT(v) assert((v))
|
||||
#define IA_RELEASE_ASSERT_MSG(v, m) assert((v) && m)
|
||||
|
||||
#if defined(__DEBUG_MODE__)
|
||||
# define IA_ASSERT(v) IA_RELEASE_ASSERT(v)
|
||||
# define IA_ASSERT_MSG(v, m) IA_RELEASE_ASSERT_MSG(v, m)
|
||||
#else
|
||||
# define IA_ASSERT(v)
|
||||
# define IA_ASSERT_MSG(v, m)
|
||||
#endif
|
||||
|
||||
#define IA_ASSERT_EQ(a, b) IA_ASSERT((a) == (b))
|
||||
#define IA_ASSERT_GE(a, b) IA_ASSERT((a) >= (b))
|
||||
#define IA_ASSERT_LE(a, b) IA_ASSERT(a <= b)
|
||||
#define IA_ASSERT_LT(a, b) IA_ASSERT(a < b)
|
||||
#define IA_ASSERT_GT(a, b) IA_ASSERT(a > b)
|
||||
#define IA_ASSERT_IMPLIES(a, b) IA_ASSERT(!(a) || (b))
|
||||
#define IA_ASSERT_NOT_NULL(v) IA_ASSERT(((v) != NULLPTR))
|
||||
|
||||
#define IA_UNREACHABLE(msg) IA_RELEASE_ASSERT_MSG(FALSE, "Unreachable code: " msg)
|
||||
|
||||
#define IA_TRY_PURE(expr) \
|
||||
{ \
|
||||
auto _ia_res = (expr); \
|
||||
if (!_ia_res) \
|
||||
{ \
|
||||
return MakeUnexpected(std::move(_ia_res.error())); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define IA_TRY(expr) \
|
||||
__extension__({ \
|
||||
auto _ia_res = (expr); \
|
||||
if (!_ia_res) \
|
||||
{ \
|
||||
return MakeUnexpected(std::move(_ia_res.error())); \
|
||||
} \
|
||||
std::move(*_ia_res); \
|
||||
})
|
||||
|
||||
#define IA_TRY_DISCARD(expr) \
|
||||
{ \
|
||||
auto _ia_res = (expr); \
|
||||
if (!_ia_res) \
|
||||
{ \
|
||||
return MakeUnexpected(std::move(_ia_res.error())); \
|
||||
} \
|
||||
UNUSED(*_ia_res); \
|
||||
}
|
||||
|
||||
#define IA_CONCAT_IMPL(x, y) x##y
|
||||
#define IA_CONCAT(x, y) IA_CONCAT_IMPL(x, y)
|
||||
#define IA_UNIQUE_NAME(prefix) IA_CONCAT(prefix, __LINE__)
|
||||
|
||||
#define SIZE_KB(v) (v * 1024)
|
||||
#define SIZE_MB(v) (v * 1024 * 1024)
|
||||
#define SIZE_GB(v) (v * 1024 * 1024 * 1024)
|
||||
|
||||
#define ENSURE_BINARY_COMPATIBILITY(A, B) \
|
||||
static_assert(sizeof(A) == sizeof(B), \
|
||||
#A ", " #B " size mismatch! Do not add virtual functions or new member variables.");
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Limits & Versioning
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#ifdef __cplusplus
|
||||
# define IA_MAX_POSSIBLE_SIZE (static_cast<SIZE_T>(0x7FFFFFFFFFFFF))
|
||||
#else
|
||||
# define IA_MAX_POSSIBLE_SIZE ((SIZE_T) (0x7FFFFFFFFFFFF))
|
||||
#endif
|
||||
|
||||
#define IA_MAX_PATH_LENGTH 4096
|
||||
#define IA_MAX_STRING_LENGTH (IA_MAX_POSSIBLE_SIZE >> 8)
|
||||
|
||||
#define IA_VERSION_TYPE uint64_t
|
||||
#define IA_VERSION_MAJOR(v) ((v >> 40) & 0xFFFFFF)
|
||||
#define IA_VERSION_MINOR(v) ((v >> 16) & 0xFFFFFF)
|
||||
#define IA_VERSION_PATCH(v) (v & 0xFFFF)
|
||||
#define IA_MAKE_VERSION(major, minor, patch) \
|
||||
((((uint64_t) (major) & 0xFFFFFF) << 40) | (((uint64_t) (minor) & 0xFFFFFF) << 16) | ((uint64_t) (patch) & 0xFFFF))
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// DLL Export/Import
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# define IA_DLL_EXPORT __declspec(dllexport)
|
||||
# define IA_DLL_IMPORT __declspec(dllimport)
|
||||
#elif defined(__GNUC__)
|
||||
# define IA_DLL_EXPORT __attribute__((visibility("default")))
|
||||
# define IA_DLL_IMPORT
|
||||
#else
|
||||
# define IA_DLL_EXPORT
|
||||
# define IA_DLL_IMPORT
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Console Colors (ANSI Escape Codes)
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#define __CC_BLACK "\033[30m"
|
||||
#define __CC_RED "\033[31m"
|
||||
#define __CC_GREEN "\033[32m"
|
||||
#define __CC_YELLOW "\033[33m"
|
||||
#define __CC_BLUE "\033[34m"
|
||||
#define __CC_MAGENTA "\033[35m"
|
||||
#define __CC_CYAN "\033[36m"
|
||||
#define __CC_WHITE "\033[37m"
|
||||
#define __CC_DEFAULT "\033[39m"
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Base Types
|
||||
// -------------------------------------------------------------------------
|
||||
typedef void VOID;
|
||||
|
||||
#ifndef _WIN32
|
||||
# ifdef __cplusplus
|
||||
typedef bool BOOL;
|
||||
# else
|
||||
typedef _Bool BOOL;
|
||||
# endif
|
||||
#endif
|
||||
|
||||
typedef char CHAR;
|
||||
typedef uint16_t CHAR16;
|
||||
|
||||
typedef int8_t INT8;
|
||||
typedef int16_t INT16;
|
||||
typedef int32_t INT32;
|
||||
typedef int64_t INT64;
|
||||
|
||||
typedef uint8_t UINT8;
|
||||
typedef uint16_t UINT16;
|
||||
typedef uint32_t UINT32;
|
||||
typedef uint64_t UINT64;
|
||||
|
||||
typedef float FLOAT32;
|
||||
typedef double FLOAT64;
|
||||
|
||||
typedef INT32 INT;
|
||||
typedef UINT32 UINT;
|
||||
typedef size_t SIZE_T;
|
||||
|
||||
#ifdef __cplusplus
|
||||
typedef std::make_signed_t<size_t> SSIZE_T;
|
||||
typedef std::align_val_t ALIGN_T;
|
||||
#else
|
||||
typedef ptrdiff_t SSIZE_T;
|
||||
typedef size_t ALIGN_T;
|
||||
#endif
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Pointer Types
|
||||
// -------------------------------------------------------------------------
|
||||
typedef VOID *PVOID;
|
||||
typedef BOOL *PBOOL;
|
||||
typedef CHAR *PCHAR;
|
||||
typedef CHAR16 *PCHAR16;
|
||||
|
||||
typedef INT8 *PINT8;
|
||||
typedef INT16 *PINT16;
|
||||
typedef INT32 *PINT32;
|
||||
typedef INT64 *PINT64;
|
||||
|
||||
typedef UINT8 *PUINT8;
|
||||
typedef UINT16 *PUINT16;
|
||||
typedef UINT32 *PUINT32;
|
||||
typedef UINT64 *PUINT64;
|
||||
|
||||
typedef INT *PINT;
|
||||
typedef UINT *PUINT;
|
||||
|
||||
typedef FLOAT32 *PFLOAT32;
|
||||
typedef FLOAT64 *PFLOAT64;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Const Pointer Types
|
||||
// -------------------------------------------------------------------------
|
||||
typedef CONST VOID *PCVOID;
|
||||
typedef CONST BOOL *PCBOOL;
|
||||
typedef CONST CHAR *PCCHAR;
|
||||
typedef CONST CHAR16 *PCCHAR16;
|
||||
|
||||
typedef CONST INT8 *PCINT8;
|
||||
typedef CONST INT16 *PCINT16;
|
||||
typedef CONST INT32 *PCINT32;
|
||||
typedef CONST INT64 *PCINT64;
|
||||
|
||||
typedef CONST UINT8 *PCUINT8;
|
||||
typedef CONST UINT16 *PCUINT16;
|
||||
typedef CONST UINT32 *PCUINT32;
|
||||
typedef CONST UINT64 *PCUINT64;
|
||||
|
||||
typedef CONST INT *PCINT;
|
||||
typedef CONST UINT *PCUINT;
|
||||
typedef CONST SIZE_T *PCSIZE;
|
||||
typedef CONST SSIZE_T *PCSSIZE;
|
||||
|
||||
typedef CONST FLOAT32 *PCFLOAT32;
|
||||
typedef CONST FLOAT64 *PCFLOAT64;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// GUID Structure
|
||||
// -------------------------------------------------------------------------
|
||||
#ifndef _WIN32
|
||||
typedef struct _IA_GUID
|
||||
{
|
||||
UINT32 Data1;
|
||||
UINT16 Data2;
|
||||
UINT16 Data3;
|
||||
UINT8 Data4[8];
|
||||
|
||||
# ifdef __cplusplus
|
||||
bool operator==(const _IA_GUID &other) const
|
||||
{
|
||||
return __builtin_memcmp(this, &other, sizeof(_IA_GUID)) == 0;
|
||||
}
|
||||
|
||||
bool operator!=(const _IA_GUID &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
# endif
|
||||
} GUID;
|
||||
#endif
|
||||
|
||||
STATIC INLINE BOOL IA_GUID_Equals(CONST GUID *a, CONST GUID *b)
|
||||
{
|
||||
if (a == NULLPTR || b == NULLPTR)
|
||||
return FALSE;
|
||||
return memcmp(a, b, sizeof(GUID)) == 0;
|
||||
template <typename T, typename... Args>
|
||||
[[nodiscard]] inline auto make_box(Args &&...args) -> Box<T> {
|
||||
return std::make_unique<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Numeric Constants
|
||||
// -------------------------------------------------------------------------
|
||||
#ifdef __cplusplus
|
||||
STATIC CONSTEXPR FLOAT32 FLOAT32_EPSILON = std::numeric_limits<FLOAT32>::epsilon();
|
||||
STATIC CONSTEXPR FLOAT64 FLOAT64_EPSILON = std::numeric_limits<FLOAT64>::epsilon();
|
||||
#else
|
||||
STATIC CONST FLOAT32 FLOAT32_EPSILON = FLT_EPSILON;
|
||||
STATIC CONST FLOAT64 FLOAT64_EPSILON = DBL_EPSILON;
|
||||
#endif
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Containers and Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
#ifdef __cplusplus
|
||||
|
||||
template<typename _function_type> using Function = std::function<_function_type>;
|
||||
template<typename _value_type> using InitializerList = std::initializer_list<_value_type>;
|
||||
template<typename _value_type, SIZE_T count> using Array = std::array<_value_type, count>;
|
||||
template<typename _value_type> using Vector = std::vector<_value_type>;
|
||||
template<typename _value_type> using Optional = std::optional<_value_type>;
|
||||
template<typename _key_type> using UnorderedSet = ankerl::unordered_dense::set<_key_type>;
|
||||
template<typename _value_type> using Span = std::span<_value_type>;
|
||||
template<typename _key_type, typename _value_type>
|
||||
using UnorderedMap = ankerl::unordered_dense::map<_key_type, _value_type>;
|
||||
template<typename _value_type> using Atomic = std::atomic<_value_type>;
|
||||
template<typename _value_type> using SharedPtr = std::shared_ptr<_value_type>;
|
||||
template<typename _value_type> using UniquePtr = std::unique_ptr<_value_type>;
|
||||
template<typename _value_type> using Deque = std::deque<_value_type>;
|
||||
template<typename _type_a, typename _type_b> using Pair = std::pair<_type_a, _type_b>;
|
||||
template<typename... types> using Tuple = std::tuple<types...>;
|
||||
template<typename _key_type, typename _value_type> using KeyValuePair = std::pair<_key_type, _value_type>;
|
||||
|
||||
ALIAS_TEMPLATE_FUNCTION(_value_type, MakeShared, std::make_shared);
|
||||
ALIAS_TEMPLATE_FUNCTION(_value_type, MakeUnique, std::make_unique);
|
||||
|
||||
template<typename T, typename... Args> inline SharedPtr<T> MakeSharedProtected(Args &&...args)
|
||||
{
|
||||
struct make_shared_enabler : public T
|
||||
{
|
||||
make_shared_enabler(Args &&...args) : T(std::forward<Args>(args)...)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
return MakeShared<make_shared_enabler>(std::forward<Args>(args)...);
|
||||
template <typename T, typename... Args>
|
||||
[[nodiscard]] inline auto make_arc(Args &&...args) -> Arc<T> {
|
||||
return std::make_shared<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename T, typename... Args> inline UniquePtr<T> MakeUniqueProtected(Args &&...args)
|
||||
{
|
||||
struct make_unique_enabler : public T
|
||||
{
|
||||
make_unique_enabler(Args &&...args) : T(std::forward<Args>(args)...)
|
||||
{
|
||||
}
|
||||
};
|
||||
// =============================================================================
|
||||
// Error Handling (Result)
|
||||
// =============================================================================
|
||||
template <typename T, typename E = std::string>
|
||||
using Result = tl::expected<T, E>;
|
||||
|
||||
return MakeUnique<make_unique_enabler>(std::forward<Args>(args)...);
|
||||
template <typename E> [[nodiscard]] inline auto fail(E &&error) {
|
||||
return tl::make_unexpected(std::forward<E>(error));
|
||||
}
|
||||
|
||||
template<typename _expected_type, typename _unexpected_type>
|
||||
using Expected = tl::expected<_expected_type, _unexpected_type>;
|
||||
ALIAS_FUNCTION(MakeUnexpected, tl::make_unexpected);
|
||||
template <typename... Args>
|
||||
[[nodiscard]] inline auto fail(std::format_string<Args...> fmt,
|
||||
Args &&...args) {
|
||||
return tl::make_unexpected(std::format(fmt, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
# define EXPECT(...) Expected<__VA_ARGS__, String>
|
||||
# define UNEXPECTED(...) MakeUnexpected(std::format(__VA_ARGS__))
|
||||
// =============================================================================
|
||||
// Common Data Structures
|
||||
// =============================================================================
|
||||
template <typename T> using Option = std::optional<T>;
|
||||
template <typename T> using Vec = std::vector<T>;
|
||||
template <typename T> using Span = std::span<T>;
|
||||
template <typename T1, typename T2> using Pair = std::pair<T1, T2>;
|
||||
template <typename K, typename V>
|
||||
using HashMap = ankerl::unordered_dense::map<K, V>;
|
||||
template <typename T> using HashSet = ankerl::unordered_dense::set<T>;
|
||||
|
||||
using String = std::string;
|
||||
using Path = std::filesystem::path;
|
||||
using StringView = std::string_view;
|
||||
using StringStream = std::stringstream;
|
||||
|
||||
using SteadyClock = std::chrono::steady_clock;
|
||||
using SteadyTimePoint = std::chrono::time_point<SteadyClock>;
|
||||
using HighResClock = std::chrono::high_resolution_clock;
|
||||
using HighResTimePoint = std::chrono::time_point<HighResClock>;
|
||||
// =============================================================================
|
||||
// Utilities
|
||||
// =============================================================================
|
||||
|
||||
using Mutex = std::mutex;
|
||||
using StopToken = std::stop_token;
|
||||
using ScopedLock = std::scoped_lock<Mutex>;
|
||||
using UniqueLock = std::unique_lock<Mutex>;
|
||||
using JoiningThread = std::jthread;
|
||||
using ConditionVariable = std::condition_variable;
|
||||
[[noreturn]] inline void
|
||||
panic(const std::string &msg,
|
||||
const std::source_location loc = std::source_location::current()) {
|
||||
std::cerr << "\n[IA_PANIC] " << msg << "\n At: " << loc.file_name()
|
||||
<< ":" << loc.line() << "\n";
|
||||
std::abort();
|
||||
}
|
||||
|
||||
namespace FileSystem = std::filesystem;
|
||||
using FilePath = FileSystem::path;
|
||||
inline void
|
||||
ensure(bool condition, const std::string &msg,
|
||||
const std::source_location loc = std::source_location::current()) {
|
||||
if (env::is_debug && !condition) {
|
||||
std::cerr << "\n[assert] " << msg << "\n At: " << loc.file_name()
|
||||
<< ":" << loc.line() << "\n";
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Args> using FormatterString = std::format_string<Args...>;
|
||||
// =============================================================================
|
||||
// Versioning
|
||||
// =============================================================================
|
||||
struct Version {
|
||||
u32 major = 0;
|
||||
u32 minor = 0;
|
||||
u32 patch = 0;
|
||||
|
||||
#endif
|
||||
constexpr auto to_u64() const -> u64 {
|
||||
return (static_cast<u64>(major) << 40) | (static_cast<u64>(minor) << 16) |
|
||||
(static_cast<u64>(patch));
|
||||
}
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// Console Colors
|
||||
// =============================================================================
|
||||
namespace console {
|
||||
constexpr const char *RESET = "\033[0m";
|
||||
constexpr const char *RED = "\033[31m";
|
||||
constexpr const char *GREEN = "\033[32m";
|
||||
constexpr const char *YELLOW = "\033[33m";
|
||||
constexpr const char *BLUE = "\033[34m";
|
||||
constexpr const char *MAGENTA = "\033[35m";
|
||||
constexpr const char *CYAN = "\033[36m";
|
||||
} // namespace console
|
||||
|
||||
} // namespace IACore
|
||||
|
||||
// =============================================================================
|
||||
// Macros
|
||||
// =============================================================================
|
||||
|
||||
#define IA_TRY_PURE(expr) \
|
||||
{ \
|
||||
auto _res = expr; \
|
||||
if (!_res) { \
|
||||
return fail(std::move(_res.error())); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define IA_TRY(lhs, expr) \
|
||||
{ \
|
||||
auto _res = expr; \
|
||||
if (!_res) { \
|
||||
return fail(std::move(_res.error())); \
|
||||
} \
|
||||
lhs = std::move(*_res); \
|
||||
}
|
||||
|
||||
#define IA_NODISCARD [[nodiscard]]
|
||||
#define IA_UNUSED [[maybe_unused]]
|
||||
@ -18,17 +18,13 @@
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
#if IA_ARCH_X64
|
||||
|
||||
# ifdef _MSC_VER
|
||||
# include <intrin.h>
|
||||
# else
|
||||
# include <immintrin.h>
|
||||
# endif
|
||||
|
||||
#elif IA_ARCH_ARM64
|
||||
|
||||
# include <arm_acle.h>
|
||||
|
||||
#endif
|
||||
|
||||
namespace IACore
|
||||
@ -38,26 +34,24 @@ namespace IACore
|
||||
public:
|
||||
struct Capabilities
|
||||
{
|
||||
BOOL HardwareCRC32{FALSE};
|
||||
bool hardware_crc32 = false;
|
||||
};
|
||||
|
||||
public:
|
||||
STATIC BOOL CheckCPU();
|
||||
static auto check_cpu() -> bool;
|
||||
|
||||
#if IA_ARCH_X64
|
||||
STATIC VOID CPUID(IN INT32 function, IN INT32 subFunction, OUT INT32 out[4]);
|
||||
static auto cpuid(i32 function, i32 sub_function, i32 out[4]) -> void;
|
||||
#endif
|
||||
|
||||
STATIC PCCHAR GetArchitectureName();
|
||||
STATIC PCCHAR GetOperatingSystemName();
|
||||
static auto get_architecture_name() -> const char *;
|
||||
static auto get_operating_system_name() -> const char *;
|
||||
|
||||
public:
|
||||
STATIC CONST Capabilities &GetCapabilities()
|
||||
static auto get_capabilities() -> const Capabilities &
|
||||
{
|
||||
return s_capabilities;
|
||||
}
|
||||
|
||||
private:
|
||||
STATIC Capabilities s_capabilities;
|
||||
static Capabilities s_capabilities;
|
||||
};
|
||||
} // namespace IACore
|
||||
@ -22,46 +22,48 @@ using NativeProcessID = DWORD;
|
||||
#elif IA_PLATFORM_UNIX
|
||||
using NativeProcessID = pid_t;
|
||||
#else
|
||||
# error "This platform does not support IACore ProcessOps"
|
||||
#error "This platform does not support IACore ProcessOps"
|
||||
#endif
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
struct ProcessHandle
|
||||
{
|
||||
Atomic<NativeProcessID> ID{0};
|
||||
Atomic<BOOL> IsRunning{false};
|
||||
namespace IACore {
|
||||
struct ProcessHandle {
|
||||
std::atomic<NativeProcessID> id{0};
|
||||
std::atomic<bool> is_running{false};
|
||||
|
||||
BOOL IsActive() CONST
|
||||
{
|
||||
return IsRunning && ID != 0;
|
||||
}
|
||||
[[nodiscard]] auto is_active() const -> bool { return is_running && id != 0; }
|
||||
|
||||
private:
|
||||
JoiningThread ThreadHandle;
|
||||
private:
|
||||
std::jthread m_thread_handle;
|
||||
|
||||
friend class ProcessOps;
|
||||
};
|
||||
friend class ProcessOps;
|
||||
};
|
||||
|
||||
class ProcessOps
|
||||
{
|
||||
public:
|
||||
STATIC NativeProcessID GetCurrentProcessID();
|
||||
class ProcessOps {
|
||||
public:
|
||||
static auto get_current_process_id() -> NativeProcessID;
|
||||
|
||||
STATIC EXPECT(INT32) SpawnProcessSync(IN CONST String &command, IN CONST String &args,
|
||||
IN Function<VOID(IN StringView line)> onOutputLineCallback);
|
||||
STATIC SharedPtr<ProcessHandle> SpawnProcessAsync(IN CONST String &command, IN CONST String &args,
|
||||
IN Function<VOID(IN StringView line)> onOutputLineCallback,
|
||||
IN Function<VOID(EXPECT(INT32))> onFinishCallback);
|
||||
static auto spawn_process_sync(
|
||||
const String &command, const String &args,
|
||||
std::function<void(StringView line)> on_output_line_callback)
|
||||
-> Result<i32>;
|
||||
|
||||
STATIC VOID TerminateProcess(IN CONST SharedPtr<ProcessHandle> &handle);
|
||||
static auto spawn_process_async(
|
||||
const String &command, const String &args,
|
||||
std::function<void(StringView line)> on_output_line_callback,
|
||||
std::function<void(Result<i32>)> on_finish_callback)
|
||||
-> Result<Box<ProcessHandle>>;
|
||||
|
||||
private:
|
||||
STATIC EXPECT(INT32)
|
||||
SpawnProcessWindows(IN CONST String &command, IN CONST String &args,
|
||||
IN Function<VOID(StringView)> onOutputLineCallback, OUT Atomic<NativeProcessID> &id);
|
||||
STATIC EXPECT(INT32)
|
||||
SpawnProcessPosix(IN CONST String &command, IN CONST String &args,
|
||||
IN Function<VOID(StringView)> onOutputLineCallback, OUT Atomic<NativeProcessID> &id);
|
||||
};
|
||||
static auto terminate_process(const Box<ProcessHandle> &handle) -> void;
|
||||
|
||||
private:
|
||||
static auto
|
||||
spawn_process_windows(const String &command, const String &args,
|
||||
std::function<void(StringView)> on_output_line_callback,
|
||||
std::atomic<NativeProcessID> &id) -> Result<i32>;
|
||||
|
||||
static auto
|
||||
spawn_process_posix(const String &command, const String &args,
|
||||
std::function<void(StringView)> on_output_line_callback,
|
||||
std::atomic<NativeProcessID> &id) -> Result<i32>;
|
||||
};
|
||||
} // namespace IACore
|
||||
@ -1,18 +1,3 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
@ -37,81 +22,81 @@ namespace IACore
|
||||
# pragma message("Warning: Configuration mismatch. IACore is being compiled for SCALAR SIMD (Slow)")
|
||||
#endif
|
||||
|
||||
class ALIGN(16) IntVec4
|
||||
class alignas(16) IntVec4
|
||||
{
|
||||
public:
|
||||
IntVec4() = default;
|
||||
|
||||
INLINE EXPLICIT IntVec4(IN UINT32 s);
|
||||
INLINE EXPLICIT IntVec4(IN PCUINT32 values);
|
||||
INLINE EXPLICIT IntVec4(IN UINT32 a, IN UINT32 b, IN UINT32 c, IN UINT32 d);
|
||||
inline explicit IntVec4(u32 s);
|
||||
inline explicit IntVec4(const u32 *values);
|
||||
inline explicit IntVec4(u32 a, u32 b, u32 c, u32 d);
|
||||
|
||||
INLINE IntVec4 operator+(IN CONST IntVec4 &other) CONST;
|
||||
INLINE IntVec4 operator-(IN CONST IntVec4 &other) CONST;
|
||||
INLINE IntVec4 operator*(IN CONST IntVec4 &other) CONST;
|
||||
inline auto operator+(const IntVec4 &other) const -> IntVec4;
|
||||
inline auto operator-(const IntVec4 &other) const -> IntVec4;
|
||||
inline auto operator*(const IntVec4 &other) const -> IntVec4;
|
||||
|
||||
INLINE IntVec4 operator&(IN CONST IntVec4 &other) CONST;
|
||||
INLINE IntVec4 operator|(IN CONST IntVec4 &other) CONST;
|
||||
INLINE IntVec4 operator^(IN CONST IntVec4 &other) CONST;
|
||||
INLINE IntVec4 operator~() CONST;
|
||||
inline auto operator&(const IntVec4 &other) const -> IntVec4;
|
||||
inline auto operator|(const IntVec4 &other) const -> IntVec4;
|
||||
inline auto operator^(const IntVec4 &other) const -> IntVec4;
|
||||
inline auto operator~() const -> IntVec4;
|
||||
|
||||
INLINE IntVec4 operator<<(IN UINT32 amount) CONST;
|
||||
INLINE IntVec4 operator>>(IN UINT32 amount) CONST;
|
||||
inline auto operator<<(u32 amount) const -> IntVec4;
|
||||
inline auto operator>>(u32 amount) const -> IntVec4;
|
||||
|
||||
INLINE IntVec4 SatAdd(IN CONST IntVec4 &other) CONST;
|
||||
INLINE IntVec4 SatSub(IN CONST IntVec4 &other) CONST;
|
||||
inline auto sat_add(const IntVec4 &other) const -> IntVec4;
|
||||
inline auto sat_sub(const IntVec4 &other) const -> IntVec4;
|
||||
|
||||
INLINE IntVec4 Clamp(IN UINT32 min, IN UINT32 max) CONST;
|
||||
inline auto clamp(u32 min, u32 max) const -> IntVec4;
|
||||
|
||||
INLINE IntVec4 MultAdd(IN CONST IntVec4 &multiplier, IN CONST IntVec4 &addend) CONST;
|
||||
inline auto mult_add(const IntVec4 &multiplier, const IntVec4 &addend) const -> IntVec4;
|
||||
|
||||
INLINE VOID Store(OUT PUINT32 values);
|
||||
STATIC INLINE IntVec4 Load(IN PCUINT32 values);
|
||||
inline auto store(u32 *values) -> void;
|
||||
static inline auto load(const u32 *values) -> IntVec4;
|
||||
|
||||
private:
|
||||
using Tag = hn::FixedTag<UINT32, 4>;
|
||||
using Tag = hn::FixedTag<u32, 4>;
|
||||
|
||||
hn::Vec<Tag> m_data;
|
||||
|
||||
INLINE EXPLICIT IntVec4(hn::Vec<Tag> v) : m_data(v)
|
||||
inline explicit IntVec4(hn::Vec<Tag> v) : m_data(v)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class ALIGN(16) FloatVec4
|
||||
class alignas(16) FloatVec4
|
||||
{
|
||||
public:
|
||||
FloatVec4() = default;
|
||||
|
||||
INLINE EXPLICIT FloatVec4(IN FLOAT32 s);
|
||||
INLINE EXPLICIT FloatVec4(IN PCFLOAT32 values);
|
||||
INLINE EXPLICIT FloatVec4(IN FLOAT32 a, IN FLOAT32 b, IN FLOAT32 c, IN FLOAT32 d);
|
||||
inline explicit FloatVec4(f32 s);
|
||||
inline explicit FloatVec4(const f32 *values);
|
||||
inline explicit FloatVec4(f32 a, f32 b, f32 c, f32 d);
|
||||
|
||||
INLINE FloatVec4 operator+(IN CONST FloatVec4 &other) CONST;
|
||||
INLINE FloatVec4 operator-(IN CONST FloatVec4 &other) CONST;
|
||||
INLINE FloatVec4 operator*(IN CONST FloatVec4 &other) CONST;
|
||||
INLINE FloatVec4 operator/(IN CONST FloatVec4 &other) CONST;
|
||||
inline auto operator+(const FloatVec4 &other) const -> FloatVec4;
|
||||
inline auto operator-(const FloatVec4 &other) const -> FloatVec4;
|
||||
inline auto operator*(const FloatVec4 &other) const -> FloatVec4;
|
||||
inline auto operator/(const FloatVec4 &other) const -> FloatVec4;
|
||||
|
||||
INLINE FloatVec4 Clamp(IN FLOAT32 min, IN FLOAT32 max) CONST;
|
||||
inline auto clamp(f32 min, f32 max) const -> FloatVec4;
|
||||
|
||||
INLINE FloatVec4 Abs() CONST;
|
||||
INLINE FloatVec4 Sqrt() CONST;
|
||||
INLINE FloatVec4 Rsqrt() CONST;
|
||||
INLINE FloatVec4 Normalize() CONST;
|
||||
inline auto abs() const -> FloatVec4;
|
||||
inline auto sqrt() const -> FloatVec4;
|
||||
inline auto rsqrt() const -> FloatVec4;
|
||||
inline auto normalize() const -> FloatVec4;
|
||||
|
||||
INLINE FLOAT32 Dot(IN CONST FloatVec4 &other) CONST;
|
||||
inline auto dot(const FloatVec4 &other) const -> f32;
|
||||
|
||||
INLINE FloatVec4 MultAdd(IN CONST FloatVec4 &multiplier, IN CONST FloatVec4 &addend) CONST;
|
||||
inline auto mult_add(const FloatVec4 &multiplier, const FloatVec4 &addend) const -> FloatVec4;
|
||||
|
||||
INLINE VOID Store(OUT PFLOAT32 values);
|
||||
STATIC INLINE FloatVec4 Load(IN PCFLOAT32 values);
|
||||
inline auto store(f32 *values) -> void;
|
||||
static inline auto load(const f32 *values) -> FloatVec4;
|
||||
|
||||
private:
|
||||
using Tag = hn::FixedTag<FLOAT32, 4>;
|
||||
using Tag = hn::FixedTag<f32, 4>;
|
||||
|
||||
hn::Vec<Tag> m_data;
|
||||
|
||||
INLINE EXPLICIT FloatVec4(hn::Vec<Tag> v) : m_data(v)
|
||||
inline explicit FloatVec4(hn::Vec<Tag> v) : m_data(v)
|
||||
{
|
||||
}
|
||||
};
|
||||
@ -119,153 +104,153 @@ namespace IACore
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
IntVec4::IntVec4(IN UINT32 s)
|
||||
IntVec4::IntVec4(u32 s)
|
||||
{
|
||||
CONST Tag d;
|
||||
const Tag d;
|
||||
m_data = hn::Set(d, s);
|
||||
}
|
||||
|
||||
IntVec4::IntVec4(IN PCUINT32 values)
|
||||
IntVec4::IntVec4(const u32 *values)
|
||||
{
|
||||
CONST Tag data;
|
||||
const Tag data;
|
||||
m_data = hn::Load(data, values);
|
||||
}
|
||||
|
||||
IntVec4::IntVec4(IN UINT32 a, IN UINT32 b, IN UINT32 c, IN UINT32 d)
|
||||
IntVec4::IntVec4(u32 a, u32 b, u32 c, u32 d)
|
||||
{
|
||||
CONST Tag data;
|
||||
ALIGN(16) UINT32 values[4] = {a, b, c, d};
|
||||
const Tag data;
|
||||
alignas(16) u32 values[4] = {a, b, c, d};
|
||||
m_data = hn::Load(data, values);
|
||||
}
|
||||
|
||||
IntVec4 IntVec4::operator+(IN CONST IntVec4 &other) CONST
|
||||
auto IntVec4::operator+(const IntVec4 &other) const -> IntVec4
|
||||
{
|
||||
return IntVec4(hn::Add(m_data, other.m_data));
|
||||
}
|
||||
|
||||
IntVec4 IntVec4::operator-(IN CONST IntVec4 &other) CONST
|
||||
auto IntVec4::operator-(const IntVec4 &other) const -> IntVec4
|
||||
{
|
||||
return IntVec4(hn::Sub(m_data, other.m_data));
|
||||
}
|
||||
|
||||
IntVec4 IntVec4::operator*(IN CONST IntVec4 &other) CONST
|
||||
auto IntVec4::operator*(const IntVec4 &other) const -> IntVec4
|
||||
{
|
||||
return IntVec4(hn::Mul(m_data, other.m_data));
|
||||
}
|
||||
|
||||
IntVec4 IntVec4::operator&(IN CONST IntVec4 &other) CONST
|
||||
auto IntVec4::operator&(const IntVec4 &other) const -> IntVec4
|
||||
{
|
||||
return IntVec4(hn::And(m_data, other.m_data));
|
||||
}
|
||||
|
||||
IntVec4 IntVec4::operator|(IN CONST IntVec4 &other) CONST
|
||||
auto IntVec4::operator|(const IntVec4 &other) const -> IntVec4
|
||||
{
|
||||
return IntVec4(hn::Or(m_data, other.m_data));
|
||||
}
|
||||
|
||||
IntVec4 IntVec4::operator^(IN CONST IntVec4 &other) CONST
|
||||
auto IntVec4::operator^(const IntVec4 &other) const -> IntVec4
|
||||
{
|
||||
return IntVec4(hn::Xor(m_data, other.m_data));
|
||||
}
|
||||
|
||||
IntVec4 IntVec4::operator~() CONST
|
||||
auto IntVec4::operator~() const -> IntVec4
|
||||
{
|
||||
return IntVec4(hn::Not(m_data));
|
||||
}
|
||||
|
||||
IntVec4 IntVec4::operator<<(IN UINT32 amount) CONST
|
||||
auto IntVec4::operator<<(u32 amount) const -> IntVec4
|
||||
{
|
||||
return IntVec4(hn::ShiftLeftSame(m_data, amount));
|
||||
}
|
||||
|
||||
IntVec4 IntVec4::operator>>(IN UINT32 amount) CONST
|
||||
auto IntVec4::operator>>(u32 amount) const -> IntVec4
|
||||
{
|
||||
return IntVec4(hn::ShiftRightSame(m_data, amount));
|
||||
}
|
||||
|
||||
IntVec4 IntVec4::MultAdd(IN CONST IntVec4 &multiplier, IN CONST IntVec4 &addend) CONST
|
||||
auto IntVec4::mult_add(const IntVec4 &multiplier, const IntVec4 &addend) const -> IntVec4
|
||||
{
|
||||
return IntVec4(hn::MulAdd(m_data, multiplier.m_data, addend.m_data));
|
||||
}
|
||||
|
||||
IntVec4 IntVec4::SatAdd(IN CONST IntVec4 &other) CONST
|
||||
auto IntVec4::sat_add(const IntVec4 &other) const -> IntVec4
|
||||
{
|
||||
return IntVec4(hn::SaturatedAdd(m_data, other.m_data));
|
||||
}
|
||||
|
||||
IntVec4 IntVec4::SatSub(IN CONST IntVec4 &other) CONST
|
||||
auto IntVec4::sat_sub(const IntVec4 &other) const -> IntVec4
|
||||
{
|
||||
return IntVec4(hn::SaturatedSub(m_data, other.m_data));
|
||||
}
|
||||
|
||||
IntVec4 IntVec4::Clamp(IN UINT32 min, IN UINT32 max) CONST
|
||||
auto IntVec4::clamp(u32 min, u32 max) const -> IntVec4
|
||||
{
|
||||
CONST Tag d;
|
||||
const Tag d;
|
||||
auto vMin = hn::Set(d, min);
|
||||
auto vMax = hn::Set(d, max);
|
||||
return IntVec4(hn::Min(hn::Max(m_data, vMin), vMax));
|
||||
}
|
||||
|
||||
VOID IntVec4::Store(OUT PUINT32 values)
|
||||
auto IntVec4::store(u32 *values) -> void
|
||||
{
|
||||
CONST Tag d;
|
||||
const Tag d;
|
||||
hn::Store(m_data, d, values);
|
||||
}
|
||||
|
||||
IntVec4 IntVec4::Load(IN PCUINT32 values)
|
||||
auto IntVec4::load(const u32 *values) -> IntVec4
|
||||
{
|
||||
CONST Tag d;
|
||||
const Tag d;
|
||||
return IntVec4(hn::Load(d, values));
|
||||
}
|
||||
} // namespace IACore
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
FloatVec4::FloatVec4(IN FLOAT32 s)
|
||||
FloatVec4::FloatVec4(f32 s)
|
||||
{
|
||||
const Tag d;
|
||||
m_data = hn::Set(d, s);
|
||||
}
|
||||
|
||||
FloatVec4::FloatVec4(IN PCFLOAT32 values)
|
||||
FloatVec4::FloatVec4(const f32 *values)
|
||||
{
|
||||
const Tag d;
|
||||
m_data = hn::Load(d, values);
|
||||
}
|
||||
|
||||
FloatVec4::FloatVec4(IN FLOAT32 a, IN FLOAT32 b, IN FLOAT32 c, IN FLOAT32 d)
|
||||
FloatVec4::FloatVec4(f32 a, f32 b, f32 c, f32 d)
|
||||
{
|
||||
const Tag data;
|
||||
ALIGN(16) FLOAT32 temp[4] = {a, b, c, d};
|
||||
alignas(16) f32 temp[4] = {a, b, c, d};
|
||||
m_data = hn::Load(data, temp);
|
||||
}
|
||||
|
||||
FloatVec4 FloatVec4::operator+(IN CONST FloatVec4 &other) CONST
|
||||
auto FloatVec4::operator+(const FloatVec4 &other) const -> FloatVec4
|
||||
{
|
||||
return FloatVec4(hn::Add(m_data, other.m_data));
|
||||
}
|
||||
|
||||
FloatVec4 FloatVec4::operator-(IN CONST FloatVec4 &other) CONST
|
||||
auto FloatVec4::operator-(const FloatVec4 &other) const -> FloatVec4
|
||||
{
|
||||
return FloatVec4(hn::Sub(m_data, other.m_data));
|
||||
}
|
||||
|
||||
FloatVec4 FloatVec4::operator*(IN CONST FloatVec4 &other) CONST
|
||||
auto FloatVec4::operator*(const FloatVec4 &other) const -> FloatVec4
|
||||
{
|
||||
return FloatVec4(hn::Mul(m_data, other.m_data));
|
||||
}
|
||||
|
||||
FloatVec4 FloatVec4::operator/(IN CONST FloatVec4 &other) CONST
|
||||
auto FloatVec4::operator/(const FloatVec4 &other) const -> FloatVec4
|
||||
{
|
||||
return FloatVec4(hn::Div(m_data, other.m_data));
|
||||
}
|
||||
|
||||
FloatVec4 FloatVec4::MultAdd(IN CONST FloatVec4 &multiplier, IN CONST FloatVec4 &addend) CONST
|
||||
auto FloatVec4::mult_add(const FloatVec4 &multiplier, const FloatVec4 &addend) const -> FloatVec4
|
||||
{
|
||||
return FloatVec4(hn::MulAdd(m_data, multiplier.m_data, addend.m_data));
|
||||
}
|
||||
|
||||
FloatVec4 FloatVec4::Clamp(IN FLOAT32 min, IN FLOAT32 max) CONST
|
||||
auto FloatVec4::clamp(f32 min, f32 max) const -> FloatVec4
|
||||
{
|
||||
const Tag d;
|
||||
auto vMin = hn::Set(d, min);
|
||||
@ -273,29 +258,29 @@ namespace IACore
|
||||
return FloatVec4(hn::Min(hn::Max(m_data, vMin), vMax));
|
||||
}
|
||||
|
||||
FloatVec4 FloatVec4::Sqrt() CONST
|
||||
auto FloatVec4::sqrt() const -> FloatVec4
|
||||
{
|
||||
return FloatVec4(hn::Sqrt(m_data));
|
||||
}
|
||||
|
||||
FloatVec4 FloatVec4::Rsqrt() CONST
|
||||
auto FloatVec4::rsqrt() const -> FloatVec4
|
||||
{
|
||||
return FloatVec4(hn::ApproximateReciprocalSqrt(m_data));
|
||||
}
|
||||
|
||||
FloatVec4 FloatVec4::Abs() CONST
|
||||
auto FloatVec4::abs() const -> FloatVec4
|
||||
{
|
||||
return FloatVec4(hn::Abs(m_data));
|
||||
}
|
||||
|
||||
FLOAT32 FloatVec4::Dot(IN CONST FloatVec4 &other) CONST
|
||||
auto FloatVec4::dot(const FloatVec4 &other) const -> f32
|
||||
{
|
||||
const Tag d;
|
||||
auto vMul = hn::Mul(m_data, other.m_data);
|
||||
return hn::ReduceSum(d, vMul);
|
||||
}
|
||||
|
||||
FloatVec4 FloatVec4::Normalize() CONST
|
||||
auto FloatVec4::normalize() const -> FloatVec4
|
||||
{
|
||||
const Tag d;
|
||||
auto vMul = hn::Mul(m_data, m_data);
|
||||
@ -304,13 +289,13 @@ namespace IACore
|
||||
return FloatVec4(hn::Mul(m_data, vInvLen));
|
||||
}
|
||||
|
||||
VOID FloatVec4::Store(OUT PFLOAT32 values)
|
||||
auto FloatVec4::store(f32 *values) -> void
|
||||
{
|
||||
const Tag d;
|
||||
hn::Store(m_data, d, values);
|
||||
}
|
||||
|
||||
FloatVec4 FloatVec4::Load(IN PCFLOAT32 values)
|
||||
auto FloatVec4::load(const f32 *values) -> FloatVec4
|
||||
{
|
||||
const Tag d;
|
||||
return FloatVec4(hn::Load(d, values));
|
||||
|
||||
@ -18,89 +18,98 @@
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
|
||||
# include <winsock2.h>
|
||||
# include <ws2tcpip.h>
|
||||
# include <afunix.h>
|
||||
# pragma comment(lib, "ws2_32.lib")
|
||||
# define CLOSE_SOCKET(s) closesocket(s)
|
||||
# define IS_VALID_SOCKET(s) (s != INVALID_SOCKET)
|
||||
# define UNLINK_FILE(p) DeleteFileA(p)
|
||||
using SocketHandle = SOCKET;
|
||||
|
||||
#include <afunix.h>
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
#elif IA_PLATFORM_UNIX
|
||||
|
||||
# include <sys/un.h>
|
||||
# include <sys/types.h>
|
||||
# include <sys/socket.h>
|
||||
# include <netinet/in.h>
|
||||
# define CLOSE_SOCKET(s) close(s)
|
||||
# define IS_VALID_SOCKET(s) (s >= 0)
|
||||
# define INVALID_SOCKET -1
|
||||
# define UNLINK_FILE(p) unlink(p)
|
||||
using SocketHandle = int;
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
// Define INVALID_SOCKET for Unix to match Windows API for cross-platform
|
||||
// compatibility
|
||||
#ifndef INVALID_SOCKET
|
||||
#define INVALID_SOCKET -1
|
||||
#endif
|
||||
#else
|
||||
|
||||
# error "IACore SocketOps is not supported on this platform."
|
||||
|
||||
#error "IACore SocketOps is not supported on this platform."
|
||||
#endif
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
class SocketOps
|
||||
{
|
||||
public:
|
||||
// SocketOps correctly handles multiple calls to Initialize and Terminate. Make sure
|
||||
// every Initialize call is paired with a corresponding Terminate call
|
||||
STATIC VOID Initialize()
|
||||
{
|
||||
s_initCount++;
|
||||
if (s_initCount > 1)
|
||||
return;
|
||||
namespace IACore {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
WSADATA wsaData;
|
||||
WSAStartup(MAKEWORD(2, 2), &wsaData);
|
||||
using SocketHandle = SOCKET;
|
||||
#elif IA_PLATFORM_UNIX
|
||||
using SocketHandle = int;
|
||||
#endif
|
||||
}
|
||||
|
||||
// SocketOps correctly handles multiple calls to Initialize and Terminate. Make sure
|
||||
// every Initialize call is paired with a corresponding Terminate call
|
||||
STATIC VOID Terminate()
|
||||
{
|
||||
s_initCount--;
|
||||
if (s_initCount > 0)
|
||||
return;
|
||||
class SocketOps {
|
||||
public:
|
||||
// SocketOps correctly handles multiple calls to initialize and terminate.
|
||||
// Make sure every initialize call is paired with a corresponding terminate
|
||||
// call.
|
||||
static auto initialize() -> Result<void> {
|
||||
s_init_count++;
|
||||
if (s_init_count > 1) {
|
||||
return {};
|
||||
}
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
WSACleanup();
|
||||
WSADATA wsa_data;
|
||||
const auto res = WSAStartup(MAKEWORD(2, 2), &wsa_data);
|
||||
if (res != 0) {
|
||||
s_init_count--;
|
||||
return fail("WSAStartup failed with error: {}", res);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
STATIC BOOL IsPortAvailableTCP(IN UINT16 port)
|
||||
{
|
||||
return IsPortAvailable(port, SOCK_STREAM);
|
||||
}
|
||||
// SocketOps correctly handles multiple calls to initialize and terminate.
|
||||
// Make sure every initialize call is paired with a corresponding terminate
|
||||
// call.
|
||||
static auto terminate() -> void {
|
||||
s_init_count--;
|
||||
if (s_init_count > 0) {
|
||||
return;
|
||||
}
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
WSACleanup();
|
||||
#endif
|
||||
}
|
||||
|
||||
STATIC BOOL IsPortAvailableUDP(IN UINT16 port)
|
||||
{
|
||||
return IsPortAvailable(port, SOCK_DGRAM);
|
||||
}
|
||||
static auto is_port_available_tcp(u16 port) -> bool {
|
||||
return is_port_available(port, SOCK_STREAM);
|
||||
}
|
||||
|
||||
STATIC BOOL IsWouldBlock();
|
||||
static auto is_port_available_udp(u16 port) -> bool {
|
||||
return is_port_available(port, SOCK_DGRAM);
|
||||
}
|
||||
|
||||
STATIC VOID Close(IN SocketHandle sock);
|
||||
static auto is_would_block() -> bool;
|
||||
|
||||
STATIC BOOL Listen(IN SocketHandle sock, IN INT32 queueSize = 5);
|
||||
static auto close(SocketHandle sock) -> void;
|
||||
|
||||
STATIC SocketHandle CreateUnixSocket();
|
||||
static auto listen(SocketHandle sock, i32 queue_size = 5) -> Result<void>;
|
||||
|
||||
STATIC BOOL BindUnixSocket(IN SocketHandle sock, IN PCCHAR path);
|
||||
STATIC BOOL ConnectUnixSocket(IN SocketHandle sock, IN PCCHAR path);
|
||||
static auto create_unix_socket() -> Result<SocketHandle>;
|
||||
|
||||
private:
|
||||
STATIC BOOL IsPortAvailable(IN UINT16 port, IN INT32 type);
|
||||
static auto bind_unix_socket(SocketHandle sock, const char *path)
|
||||
-> Result<void>;
|
||||
static auto connect_unix_socket(SocketHandle sock, const char *path)
|
||||
-> Result<void>;
|
||||
|
||||
private:
|
||||
STATIC INT32 s_initCount;
|
||||
};
|
||||
static auto unlink_file(const char *path) -> void {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
DeleteFileA(path);
|
||||
#elif IA_PLATFORM_UNIX
|
||||
unlink(path);
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
static auto is_port_available(u16 port, i32 type) -> bool;
|
||||
|
||||
private:
|
||||
static i32 s_init_count;
|
||||
};
|
||||
} // namespace IACore
|
||||
@ -16,88 +16,87 @@
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
class StreamReader
|
||||
{
|
||||
enum class EStorageType
|
||||
{
|
||||
NON_OWNING,
|
||||
OWNING_MMAP,
|
||||
OWNING_VECTOR,
|
||||
};
|
||||
namespace IACore {
|
||||
class StreamReader {
|
||||
public:
|
||||
enum class StorageType {
|
||||
NonOwning,
|
||||
OwningMmap,
|
||||
OwningVector,
|
||||
};
|
||||
|
||||
public:
|
||||
INLINE EXPECT(VOID) Read(IN PVOID buffer, IN SIZE_T size);
|
||||
template<typename T> NO_DISCARD("Check for EOF") EXPECT(T) Read();
|
||||
static auto create_from_file(const Path &path) -> Result<StreamReader>;
|
||||
|
||||
VOID Skip(SIZE_T amount)
|
||||
{
|
||||
m_cursor = std::min(m_cursor + amount, m_dataSize);
|
||||
}
|
||||
explicit StreamReader(Vec<u8> &&data);
|
||||
explicit StreamReader(Span<const u8> data);
|
||||
~StreamReader();
|
||||
|
||||
VOID Seek(SIZE_T pos)
|
||||
{
|
||||
m_cursor = (pos > m_dataSize) ? m_dataSize : pos;
|
||||
}
|
||||
StreamReader(StreamReader &&) = default;
|
||||
auto operator=(StreamReader &&) -> StreamReader & = default;
|
||||
|
||||
SIZE_T Cursor() CONST
|
||||
{
|
||||
return m_cursor;
|
||||
}
|
||||
StreamReader(const StreamReader &) = delete;
|
||||
auto operator=(const StreamReader &) -> StreamReader & = delete;
|
||||
|
||||
SIZE_T Size() CONST
|
||||
{
|
||||
return m_dataSize;
|
||||
}
|
||||
auto read(void *buffer, usize size) -> Result<void>;
|
||||
|
||||
SIZE_T Remaining() CONST
|
||||
{
|
||||
return m_dataSize - m_cursor;
|
||||
}
|
||||
template <typename T>
|
||||
[[nodiscard("Check for EOF")]]
|
||||
auto read() -> Result<T>;
|
||||
|
||||
BOOL IsEOF() CONST
|
||||
{
|
||||
return m_cursor >= m_dataSize;
|
||||
}
|
||||
auto skip(usize amount) -> void {
|
||||
m_cursor = std::min(m_cursor + amount, m_data_size);
|
||||
}
|
||||
|
||||
public:
|
||||
StreamReader(IN CONST FilePath &path);
|
||||
explicit StreamReader(IN Vector<UINT8> &&data);
|
||||
explicit StreamReader(IN Span<CONST UINT8> data);
|
||||
~StreamReader();
|
||||
auto seek(usize pos) -> void {
|
||||
m_cursor = (pos > m_data_size) ? m_data_size : pos;
|
||||
}
|
||||
|
||||
private:
|
||||
PCUINT8 m_data{};
|
||||
SIZE_T m_cursor{};
|
||||
SIZE_T m_dataSize{};
|
||||
Vector<UINT8> m_owningVector;
|
||||
CONST EStorageType m_storageType;
|
||||
};
|
||||
[[nodiscard]] auto cursor() const -> usize { return m_cursor; }
|
||||
|
||||
EXPECT(VOID) StreamReader::Read(IN PVOID buffer, IN SIZE_T size)
|
||||
{
|
||||
if B_UNLIKELY ((m_cursor + size > m_dataSize))
|
||||
return MakeUnexpected(String("Unexpected EOF while reading"));
|
||||
[[nodiscard]] auto size() const -> usize { return m_data_size; }
|
||||
|
||||
std::memcpy(buffer, &m_data[m_cursor], size);
|
||||
m_cursor += size;
|
||||
[[nodiscard]] auto remaining() const -> usize {
|
||||
return m_data_size - m_cursor;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
[[nodiscard]] auto is_eof() const -> bool { return m_cursor >= m_data_size; }
|
||||
|
||||
template<typename T> NO_DISCARD("Check for EOF") EXPECT(T) StreamReader::Read()
|
||||
{
|
||||
constexpr SIZE_T size = sizeof(T);
|
||||
private:
|
||||
const u8 *m_data = nullptr;
|
||||
usize m_cursor = 0;
|
||||
usize m_data_size = 0;
|
||||
Vec<u8> m_owning_vector;
|
||||
StorageType m_storage_type = StorageType::NonOwning;
|
||||
};
|
||||
|
||||
if B_UNLIKELY ((m_cursor + size > m_dataSize))
|
||||
return MakeUnexpected(String("Unexpected EOF while reading"));
|
||||
inline auto StreamReader::read(void *buffer, usize size) -> Result<void> {
|
||||
if (m_cursor + size > m_data_size) [[unlikely]] {
|
||||
return fail("Unexpected EOF while reading");
|
||||
}
|
||||
|
||||
T value;
|
||||
std::memcpy(&value, &m_data[m_cursor], size);
|
||||
m_cursor += size;
|
||||
std::memcpy(buffer, &m_data[m_cursor], size);
|
||||
m_cursor += size;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard("Check for EOF")]]
|
||||
inline auto StreamReader::read() -> Result<T> {
|
||||
constexpr usize SIZE = sizeof(T);
|
||||
|
||||
if (m_cursor + SIZE > m_data_size) [[unlikely]] {
|
||||
return fail("Unexpected EOF while reading");
|
||||
}
|
||||
|
||||
T value;
|
||||
std::memcpy(&value, &m_data[m_cursor], SIZE);
|
||||
m_cursor += SIZE;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -17,49 +17,49 @@
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
class StreamWriter
|
||||
{
|
||||
enum class EStorageType
|
||||
{
|
||||
NON_OWNING,
|
||||
OWNING_FILE,
|
||||
OWNING_VECTOR,
|
||||
};
|
||||
namespace IACore {
|
||||
|
||||
public:
|
||||
BOOL Write(IN UINT8 byte, IN SIZE_T count);
|
||||
BOOL Write(IN PCVOID buffer, IN SIZE_T size);
|
||||
template<typename T> BOOL Write(IN CONST T &value);
|
||||
class StreamWriter {
|
||||
public:
|
||||
enum class StorageType {
|
||||
NonOwning,
|
||||
OwningFile,
|
||||
OwningVector,
|
||||
};
|
||||
|
||||
PCUINT8 Data() CONST
|
||||
{
|
||||
return m_buffer;
|
||||
}
|
||||
static auto create(const Path &path) -> Result<StreamWriter>;
|
||||
|
||||
SIZE_T Cursor() CONST
|
||||
{
|
||||
return m_cursor;
|
||||
}
|
||||
StreamWriter();
|
||||
explicit StreamWriter(Span<u8> data);
|
||||
~StreamWriter();
|
||||
|
||||
public:
|
||||
StreamWriter();
|
||||
explicit StreamWriter(IN Span<UINT8> data);
|
||||
explicit StreamWriter(IN CONST FilePath &path);
|
||||
~StreamWriter();
|
||||
StreamWriter(StreamWriter &&) = default;
|
||||
auto operator=(StreamWriter &&) -> StreamWriter & = default;
|
||||
|
||||
private:
|
||||
PUINT8 m_buffer{};
|
||||
SIZE_T m_cursor{};
|
||||
SIZE_T m_capacity{};
|
||||
FilePath m_filePath{};
|
||||
Vector<UINT8> m_owningVector;
|
||||
CONST EStorageType m_storageType;
|
||||
};
|
||||
StreamWriter(const StreamWriter &) = delete;
|
||||
auto operator=(const StreamWriter &) -> StreamWriter & = delete;
|
||||
|
||||
auto write(u8 byte, usize count) -> Result<void>;
|
||||
auto write(const void *buffer, usize size) -> Result<void>;
|
||||
|
||||
template <typename T> auto write(const T &value) -> Result<void>;
|
||||
|
||||
[[nodiscard]] auto data() const -> const u8 * { return m_buffer; }
|
||||
|
||||
[[nodiscard]] auto cursor() const -> usize { return m_cursor; }
|
||||
|
||||
private:
|
||||
u8 *m_buffer = nullptr;
|
||||
usize m_cursor = 0;
|
||||
usize m_capacity = 0;
|
||||
Path m_file_path;
|
||||
Vec<u8> m_owning_vector;
|
||||
StorageType m_storage_type = StorageType::OwningVector;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
inline auto StreamWriter::write(const T &value) -> Result<void> {
|
||||
return write(&value, sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T> BOOL StreamWriter::Write(IN CONST T &value)
|
||||
{
|
||||
return Write(&value, sizeof(T));
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -17,12 +17,10 @@
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
class StringOps
|
||||
{
|
||||
public:
|
||||
STATIC String EncodeBase64(IN Span<CONST UINT8> data);
|
||||
STATIC Vector<UINT8> DecodeBase64(IN CONST String &data);
|
||||
};
|
||||
namespace IACore {
|
||||
class StringOps {
|
||||
public:
|
||||
static auto encode_base64(Span<const u8> data) -> String;
|
||||
static auto decode_base64(const String &data) -> Vec<u8>;
|
||||
};
|
||||
} // namespace IACore
|
||||
@ -21,86 +21,48 @@
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
|
||||
class Utils
|
||||
{
|
||||
public:
|
||||
INLINE STATIC String BinaryToHexString(std::span<const UINT8> data)
|
||||
{
|
||||
STATIC CONSTEXPR char LUT[] = "0123456789ABCDEF";
|
||||
String res;
|
||||
res.reserve(data.size() * 2);
|
||||
static auto get_unix_time() -> u64;
|
||||
|
||||
for (UINT8 b : data)
|
||||
{
|
||||
res.push_back(LUT[(b >> 4) & 0x0F]);
|
||||
res.push_back(LUT[b & 0x0F]);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
static auto get_ticks_count() -> u64;
|
||||
|
||||
INLINE STATIC EXPECT(Vector<UINT8>) HexStringToBinary(CONST StringView &hex)
|
||||
{
|
||||
if (hex.size() % 2 != 0)
|
||||
{
|
||||
return MakeUnexpected(String("Hex string must have even length"));
|
||||
}
|
||||
static auto get_seconds_count() -> f64;
|
||||
|
||||
Vector<UINT8> out;
|
||||
out.reserve(hex.size() / 2);
|
||||
static auto get_random() -> f32;
|
||||
static auto get_random(u64 max) -> u64;
|
||||
static auto get_random(i64 min, i64 max) -> i64;
|
||||
|
||||
for (SIZE_T i = 0; i < hex.size(); i += 2)
|
||||
{
|
||||
char high = hex[i];
|
||||
char low = hex[i + 1];
|
||||
static auto sleep(u64 milliseconds) -> void;
|
||||
|
||||
auto fromHexChar = [](char c) -> int {
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
if (c >= 'A' && c <= 'F')
|
||||
return c - 'A' + 10;
|
||||
if (c >= 'a' && c <= 'f')
|
||||
return c - 'a' + 10;
|
||||
return -1;
|
||||
};
|
||||
static auto binary_to_hex_string(Span<const u8> data) -> String;
|
||||
|
||||
int h = fromHexChar(high);
|
||||
int l = fromHexChar(low);
|
||||
static auto hex_string_to_binary(StringView hex) -> Result<Vec<u8>>;
|
||||
|
||||
if (h == -1 || l == -1)
|
||||
{
|
||||
return MakeUnexpected(String("Invalid hex character found"));
|
||||
}
|
||||
|
||||
out.push_back(CAST((h << 4) | l, UINT8));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
template<typename Range> INLINE STATIC VOID Sort(Range &&range)
|
||||
template<typename Range> inline static void sort(Range &&range)
|
||||
{
|
||||
std::ranges::sort(range);
|
||||
}
|
||||
|
||||
template<typename Range, typename T> INLINE STATIC auto BinarySearchLeft(Range &&range, CONST T &value)
|
||||
template<typename Range, typename T> inline static auto binary_search_left(Range &&range, const T &value)
|
||||
{
|
||||
return std::ranges::lower_bound(range, value);
|
||||
}
|
||||
|
||||
template<typename Range, typename T> INLINE STATIC auto BinarySearchRight(Range &&range, CONST T &value)
|
||||
template<typename Range, typename T> inline static auto binary_search_right(Range &&range, const T &value)
|
||||
{
|
||||
return std::ranges::upper_bound(range, value);
|
||||
}
|
||||
|
||||
template<typename T> INLINE STATIC void HashCombine(UINT64 &seed, CONST T &v)
|
||||
template<typename T> inline static void hash_combine(u64 &seed, const T &v)
|
||||
{
|
||||
UINT64 h;
|
||||
u64 h;
|
||||
|
||||
if constexpr (std::is_constructible_v<std::string_view, T>)
|
||||
if constexpr (std::is_constructible_v<StringView, T>)
|
||||
{
|
||||
std::string_view sv(v);
|
||||
auto hasher = ankerl::unordered_dense::hash<std::string_view>();
|
||||
StringView sv(v);
|
||||
auto hasher = ankerl::unordered_dense::hash<StringView>();
|
||||
h = hasher(sv);
|
||||
}
|
||||
else
|
||||
@ -112,18 +74,18 @@ namespace IACore
|
||||
seed ^= h + 0x9e3779b97f4a7c15 + (seed << 6) + (seed >> 2);
|
||||
}
|
||||
|
||||
template<typename... Args> INLINE STATIC UINT64 ComputeHash(CONST Args &...args)
|
||||
template<typename... Args> inline static auto compute_hash(const Args &...args) -> u64
|
||||
{
|
||||
UINT64 seed = 0;
|
||||
(HashCombine(seed, args), ...);
|
||||
u64 seed = 0;
|
||||
(hash_combine(seed, args), ...);
|
||||
return seed;
|
||||
}
|
||||
|
||||
template<typename T, typename... MemberPtrs>
|
||||
INLINE STATIC UINT64 ComputeHashFlat(CONST T &obj, MemberPtrs... members)
|
||||
inline static auto compute_hash_flat(const T &obj, MemberPtrs... members) -> u64
|
||||
{
|
||||
UINT64 seed = 0;
|
||||
(HashCombine(seed, obj.*members), ...);
|
||||
u64 seed = 0;
|
||||
(hash_combine(seed, obj.*members), ...);
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
@ -142,10 +104,10 @@ namespace IACore
|
||||
template<> struct ankerl::unordered_dense::hash<Type> \
|
||||
{ \
|
||||
using is_avalanching = void; \
|
||||
NO_DISCARD("Hash value should be used") \
|
||||
UINT64 operator()(CONST Type &v) const NOEXCEPT \
|
||||
IA_NODISCARD \
|
||||
auto operator()(const Type &v) const noexcept -> IACore::u64 \
|
||||
{ \
|
||||
/* Pass the object and the list of member pointers */ \
|
||||
return IACore::Utils::ComputeHashFlat(v, __VA_ARGS__); \
|
||||
return IACore::Utils::compute_hash_flat(v, __VA_ARGS__); \
|
||||
} \
|
||||
};
|
||||
};
|
||||
@ -28,12 +28,12 @@ namespace IACore
|
||||
using Document = pugi::xml_document;
|
||||
|
||||
public:
|
||||
STATIC EXPECT(Document) ParseFromString(IN CONST String &data);
|
||||
STATIC EXPECT(Document) ParseFromFile(IN CONST FilePath &path);
|
||||
static auto parse_from_string(const String &data) -> Result<Document>;
|
||||
static auto parse_from_file(const Path &path) -> Result<Document>;
|
||||
|
||||
STATIC String SerializeToString(IN CONST Node &node, IN BOOL escape = false);
|
||||
STATIC String SerializeToString(IN CONST Document &doc, IN BOOL escape = false);
|
||||
static auto serialize_to_string(const Node &node, bool escape = false) -> String;
|
||||
static auto serialize_to_string(const Document &doc, bool escape = false) -> String;
|
||||
|
||||
STATIC String EscapeXMLString(IN CONST String &xml);
|
||||
static auto escape_xml_string(const String &xml) -> String;
|
||||
};
|
||||
} // namespace IACore
|
||||
Reference in New Issue
Block a user