This commit is contained in:
@ -1,13 +1,32 @@
|
|||||||
---
|
---
|
||||||
BasedOnStyle: Microsoft
|
|
||||||
IndentWidth: 4
|
|
||||||
SortIncludes: false
|
|
||||||
---
|
|
||||||
Language: Cpp
|
Language: Cpp
|
||||||
|
Standard: c++20
|
||||||
|
BasedOnStyle: Microsoft
|
||||||
|
|
||||||
|
IndentWidth: 4
|
||||||
|
TabWidth: 4
|
||||||
|
UseTab: Never
|
||||||
|
AccessModifierOffset: -4
|
||||||
|
|
||||||
IndentPPDirectives: AfterHash
|
IndentPPDirectives: AfterHash
|
||||||
|
IndentRequiresClause: true
|
||||||
|
BreakBeforeConceptDeclarations: true
|
||||||
|
SpaceAfterTemplateKeyword: false
|
||||||
|
|
||||||
FixNamespaceComments: true
|
FixNamespaceComments: true
|
||||||
NamespaceIndentation: All
|
NamespaceIndentation: All
|
||||||
SeparateDefinitionBlocks: Always
|
CompactNamespaces: true
|
||||||
|
|
||||||
|
AlignAfterOpenBracket: Align
|
||||||
|
AlignOperands: Align
|
||||||
|
AlignTrailingComments: true
|
||||||
|
|
||||||
SpaceAfterCStyleCast: true
|
SpaceAfterCStyleCast: true
|
||||||
SpaceAfterLogicalNot: false
|
SpaceAfterLogicalNot: false
|
||||||
SpaceAfterTemplateKeyword: false
|
SpaceBeforeAssignmentOperators: true
|
||||||
|
SpaceInEmptyParentheses: false
|
||||||
|
SpacesInAngles: false
|
||||||
|
|
||||||
|
SeparateDefinitionBlocks: Always
|
||||||
|
SortIncludes: false
|
||||||
|
---
|
||||||
82
.clang-tidy
82
.clang-tidy
@ -1,18 +1,76 @@
|
|||||||
Checks: 'readability-identifier-naming'
|
Checks: >
|
||||||
|
-*,
|
||||||
|
readability-identifier-naming,
|
||||||
|
readability-const-return-type,
|
||||||
|
modernize-use-nodiscard,
|
||||||
|
modernize-use-override,
|
||||||
|
modernize-use-nullptr,
|
||||||
|
bugprone-use-after-move
|
||||||
|
|
||||||
CheckOptions:
|
CheckOptions:
|
||||||
- key: readability-identifier-naming.MethodCase
|
# ----------------------------------------------------------------------------
|
||||||
value: PascalCase
|
# Types (PascalCase) - Matches Rust structs/enums
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
- key: readability-identifier-naming.StructMemberCase
|
|
||||||
value: lower_case
|
|
||||||
|
|
||||||
- key: readability-identifier-naming.PrivateMemberCase
|
|
||||||
value: camelCase
|
|
||||||
- key: readability-identifier-naming.PrivateMemberPrefix
|
|
||||||
value: m_
|
|
||||||
|
|
||||||
- key: readability-identifier-naming.ClassCase
|
- key: readability-identifier-naming.ClassCase
|
||||||
value: PascalCase
|
value: PascalCase
|
||||||
- key: readability-identifier-naming.StructCase
|
- key: readability-identifier-naming.StructCase
|
||||||
value: PascalCase
|
value: PascalCase
|
||||||
|
- key: readability-identifier-naming.TypedefCase
|
||||||
|
value: PascalCase
|
||||||
|
- key: readability-identifier-naming.EnumCase
|
||||||
|
value: PascalCase
|
||||||
|
- key: readability-identifier-naming.TemplateParameterCase
|
||||||
|
value: PascalCase
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Functions & Methods (snake_case) - Matches Rust fn
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
- key: readability-identifier-naming.FunctionCase
|
||||||
|
value: lower_case
|
||||||
|
- key: readability-identifier-naming.MethodCase
|
||||||
|
value: lower_case
|
||||||
|
- key: readability-identifier-naming.ParameterCase
|
||||||
|
value: lower_case
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Variables (snake_case) - Matches Rust let
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
- key: readability-identifier-naming.VariableCase
|
||||||
|
value: lower_case
|
||||||
|
- key: readability-identifier-naming.LocalVariableCase
|
||||||
|
value: lower_case
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Members (m_snake_case for private, snake_case for public)
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Public struct members (like a Rust struct) -> x, y, width
|
||||||
|
- key: readability-identifier-naming.PublicMemberCase
|
||||||
|
value: lower_case
|
||||||
|
|
||||||
|
# Private/Protected class members -> m_parser, m_count
|
||||||
|
- key: readability-identifier-naming.PrivateMemberCase
|
||||||
|
value: lower_case
|
||||||
|
- key: readability-identifier-naming.PrivateMemberPrefix
|
||||||
|
value: m_
|
||||||
|
- key: readability-identifier-naming.ProtectedMemberCase
|
||||||
|
value: lower_case
|
||||||
|
- key: readability-identifier-naming.ProtectedMemberPrefix
|
||||||
|
value: m_
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Constants (SCREAMING_SNAKE_CASE)
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
- key: readability-identifier-naming.GlobalConstantCase
|
||||||
|
value: UPPER_CASE
|
||||||
|
- key: readability-identifier-naming.ConstexprVariableCase
|
||||||
|
value: UPPER_CASE
|
||||||
|
- key: readability-identifier-naming.EnumConstantCase
|
||||||
|
value: PascalCase
|
||||||
|
# Note: Rust uses PascalCase for Enum Variants (Option::None).
|
||||||
|
# Change to UPPER_CASE if you prefer C-style enums.
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Macros (SCREAMING_SNAKE_CASE)
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
- key: readability-identifier-naming.MacroDefinitionCase
|
||||||
|
value: UPPER_CASE
|
||||||
@ -37,5 +37,5 @@ if(IACore_BUILD_TESTS)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/Sandbox")
|
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/Sandbox")
|
||||||
add_subdirectory(Sandbox)
|
# add_subdirectory(Sandbox)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
# IACore (Independent Architecture Core)
|
# IACore (Independent Architecture Core)
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> A major rewrite is coming to IACore API and coding style on Jan 30th. While the functionality and classes you're familiar will remain the same, this rewrite will introduce breaking changes to the overall API and how IACore is used. This rewrite will align IACore API and coding style with a Rust-inspired modern, clean and a vastly improved maintainable alternative.
|
||||||
|
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="logo.svg" alt="IACore Logo" width="400"/>
|
<img src="logo.svg" alt="IACore Logo" width="400"/>
|
||||||
<br/>
|
<br/>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ set(SRC_FILES
|
|||||||
"imp/cpp/JSON.cpp"
|
"imp/cpp/JSON.cpp"
|
||||||
"imp/cpp/IACore.cpp"
|
"imp/cpp/IACore.cpp"
|
||||||
"imp/cpp/Logger.cpp"
|
"imp/cpp/Logger.cpp"
|
||||||
|
"imp/cpp/Utils.cpp"
|
||||||
"imp/cpp/FileOps.cpp"
|
"imp/cpp/FileOps.cpp"
|
||||||
"imp/cpp/DataOps.cpp"
|
"imp/cpp/DataOps.cpp"
|
||||||
"imp/cpp/AsyncOps.cpp"
|
"imp/cpp/AsyncOps.cpp"
|
||||||
@ -16,9 +17,9 @@ set(SRC_FILES
|
|||||||
"imp/cpp/StreamReader.cpp"
|
"imp/cpp/StreamReader.cpp"
|
||||||
"imp/cpp/StreamWriter.cpp"
|
"imp/cpp/StreamWriter.cpp"
|
||||||
|
|
||||||
"imp/cpp/Http/Common.cpp"
|
#"imp/cpp/Http/Common.cpp"
|
||||||
"imp/cpp/Http/Client.cpp"
|
#"imp/cpp/Http/Client.cpp"
|
||||||
"imp/cpp/Http/Server.cpp"
|
#"imp/cpp/Http/Server.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(IACore STATIC ${SRC_FILES})
|
add_library(IACore STATIC ${SRC_FILES})
|
||||||
|
|||||||
@ -15,164 +15,170 @@
|
|||||||
|
|
||||||
#include <IACore/AsyncOps.hpp>
|
#include <IACore/AsyncOps.hpp>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
std::mutex AsyncOps::s_queue_mutex;
|
||||||
Mutex AsyncOps::s_queueMutex;
|
std::condition_variable AsyncOps::s_wake_condition;
|
||||||
ConditionVariable AsyncOps::s_wakeCondition;
|
Vec<std::jthread> AsyncOps::s_schedule_workers;
|
||||||
Vector<JoiningThread> AsyncOps::s_scheduleWorkers;
|
std::deque<AsyncOps::ScheduledTask> AsyncOps::s_high_priority_queue;
|
||||||
Deque<AsyncOps::ScheduledTask> AsyncOps::s_highPriorityQueue;
|
std::deque<AsyncOps::ScheduledTask> AsyncOps::s_normal_priority_queue;
|
||||||
Deque<AsyncOps::ScheduledTask> AsyncOps::s_normalPriorityQueue;
|
|
||||||
|
|
||||||
VOID AsyncOps::RunTask(IN Function<VOID()> task)
|
auto AsyncOps::run_task(std::function<void()> task) -> void {
|
||||||
{
|
std::jthread(std::move(task)).detach();
|
||||||
JoiningThread(task).detach();
|
}
|
||||||
|
|
||||||
|
auto AsyncOps::initialize_scheduler(u8 worker_count) -> Result<void> {
|
||||||
|
if (worker_count == 0) {
|
||||||
|
const auto hw_concurrency = std::thread::hardware_concurrency();
|
||||||
|
u32 threads = 2;
|
||||||
|
if (hw_concurrency > 2) {
|
||||||
|
threads = hw_concurrency - 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID AsyncOps::InitializeScheduler(IN UINT8 workerCount)
|
if (threads > 255) {
|
||||||
{
|
threads = 255;
|
||||||
if (!workerCount)
|
}
|
||||||
workerCount = std::max((UINT32) 2, std::thread::hardware_concurrency() - 2);
|
worker_count = static_cast<u8>(threads);
|
||||||
for (UINT32 i = 0; i < workerCount; i++)
|
|
||||||
s_scheduleWorkers.emplace_back(AsyncOps::ScheduleWorkerLoop, i + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID AsyncOps::TerminateScheduler()
|
for (u32 i = 0; i < worker_count; ++i) {
|
||||||
{
|
s_schedule_workers.emplace_back(schedule_worker_loop,
|
||||||
for (auto &w : s_scheduleWorkers)
|
static_cast<WorkerId>(i + 1));
|
||||||
{
|
|
||||||
w.request_stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s_wakeCondition.notify_all();
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
for (auto &w : s_scheduleWorkers)
|
auto AsyncOps::terminate_scheduler() -> void {
|
||||||
{
|
for (auto &worker : s_schedule_workers) {
|
||||||
if (w.joinable())
|
worker.request_stop();
|
||||||
{
|
}
|
||||||
w.join();
|
|
||||||
|
s_wake_condition.notify_all();
|
||||||
|
|
||||||
|
for (auto &worker : s_schedule_workers) {
|
||||||
|
if (worker.joinable()) {
|
||||||
|
worker.join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s_scheduleWorkers.clear();
|
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)});
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID AsyncOps::ScheduleTask(IN Function<VOID(IN WorkerID workerID)> task, IN TaskTag tag, IN Schedule *schedule,
|
|
||||||
IN Priority priority)
|
|
||||||
{
|
|
||||||
IA_ASSERT(s_scheduleWorkers.size() && "Scheduler must be initialized before calling this function");
|
|
||||||
|
|
||||||
schedule->Counter.fetch_add(1);
|
|
||||||
{
|
|
||||||
ScopedLock lock(s_queueMutex);
|
|
||||||
if (priority == Priority::High)
|
|
||||||
s_highPriorityQueue.emplace_back(ScheduledTask{tag, schedule, IA_MOVE(task)});
|
|
||||||
else
|
|
||||||
s_normalPriorityQueue.emplace_back(ScheduledTask{tag, schedule, IA_MOVE(task)});
|
|
||||||
}
|
}
|
||||||
s_wakeCondition.notify_one();
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID AsyncOps::CancelTasksOfTag(IN TaskTag tag)
|
|
||||||
{
|
|
||||||
ScopedLock lock(s_queueMutex);
|
|
||||||
|
|
||||||
auto cancelFromQueue = [&](Deque<ScheduledTask> &queue) {
|
|
||||||
for (auto it = queue.begin(); it != queue.end(); /* no increment here */)
|
|
||||||
{
|
|
||||||
if (it->Tag == tag)
|
|
||||||
{
|
|
||||||
if (it->ScheduleHandle->Counter.fetch_sub(1) == 1)
|
|
||||||
it->ScheduleHandle->Counter.notify_all();
|
|
||||||
|
|
||||||
it = queue.erase(it);
|
it = queue.erase(it);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
cancelFromQueue(s_highPriorityQueue);
|
cancel_from_queue(s_high_priority_queue);
|
||||||
cancelFromQueue(s_normalPriorityQueue);
|
cancel_from_queue(s_normal_priority_queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID AsyncOps::WaitForScheduleCompletion(IN Schedule *schedule)
|
auto AsyncOps::wait_for_schedule_completion(Schedule *schedule) -> void {
|
||||||
{
|
ensure(!s_schedule_workers.empty(), "Scheduler must be initialized before "
|
||||||
IA_ASSERT(s_scheduleWorkers.size() && "Scheduler must be initialized before calling this function");
|
"calling wait_for_schedule_completion");
|
||||||
|
|
||||||
while (schedule->Counter.load() > 0)
|
while (schedule->counter.load() > 0) {
|
||||||
{
|
|
||||||
ScheduledTask task;
|
ScheduledTask task;
|
||||||
BOOL foundTask{FALSE};
|
bool found_task = false;
|
||||||
{
|
{
|
||||||
UniqueLock lock(s_queueMutex);
|
std::unique_lock lock(s_queue_mutex);
|
||||||
if (!s_highPriorityQueue.empty())
|
if (!s_high_priority_queue.empty()) {
|
||||||
{
|
task = std::move(s_high_priority_queue.front());
|
||||||
task = IA_MOVE(s_highPriorityQueue.front());
|
s_high_priority_queue.pop_front();
|
||||||
s_highPriorityQueue.pop_front();
|
found_task = true;
|
||||||
foundTask = TRUE;
|
} else if (!s_normal_priority_queue.empty()) {
|
||||||
}
|
task = std::move(s_normal_priority_queue.front());
|
||||||
else if (!s_normalPriorityQueue.empty())
|
s_normal_priority_queue.pop_front();
|
||||||
{
|
found_task = true;
|
||||||
task = IA_MOVE(s_normalPriorityQueue.front());
|
|
||||||
s_normalPriorityQueue.pop_front();
|
|
||||||
foundTask = TRUE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (foundTask)
|
|
||||||
{
|
|
||||||
task.Task(MainThreadWorkerID);
|
|
||||||
if (task.ScheduleHandle->Counter.fetch_sub(1) == 1)
|
|
||||||
task.ScheduleHandle->Counter.notify_all();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto currentVal = schedule->Counter.load();
|
|
||||||
if (currentVal > 0)
|
|
||||||
schedule->Counter.wait(currentVal);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncOps::WorkerID AsyncOps::GetWorkerCount()
|
if (found_task) {
|
||||||
{
|
task.task(MAIN_THREAD_WORKER_ID);
|
||||||
return static_cast<WorkerID>(s_scheduleWorkers.size() + 1); // +1 for MainThread (Work Stealing)
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VOID AsyncOps::ScheduleWorkerLoop(IN StopToken stopToken, IN WorkerID workerID)
|
auto AsyncOps::get_worker_count() -> WorkerId {
|
||||||
{
|
// +1 for MainThread (Work Stealing)
|
||||||
while (!stopToken.stop_requested())
|
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;
|
ScheduledTask task;
|
||||||
BOOL foundTask{FALSE};
|
bool found_task = false;
|
||||||
{
|
{
|
||||||
UniqueLock lock(s_queueMutex);
|
std::unique_lock lock(s_queue_mutex);
|
||||||
|
|
||||||
s_wakeCondition.wait(lock, [&stopToken] {
|
s_wake_condition.wait(lock, [&stop_token] {
|
||||||
return !s_highPriorityQueue.empty() || !s_normalPriorityQueue.empty() || stopToken.stop_requested();
|
return !s_high_priority_queue.empty() ||
|
||||||
|
!s_normal_priority_queue.empty() || stop_token.stop_requested();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (stopToken.stop_requested() && s_highPriorityQueue.empty() && s_normalPriorityQueue.empty())
|
if (stop_token.stop_requested() && s_high_priority_queue.empty() &&
|
||||||
|
s_normal_priority_queue.empty()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!s_highPriorityQueue.empty())
|
if (!s_high_priority_queue.empty()) {
|
||||||
{
|
task = std::move(s_high_priority_queue.front());
|
||||||
task = IA_MOVE(s_highPriorityQueue.front());
|
s_high_priority_queue.pop_front();
|
||||||
s_highPriorityQueue.pop_front();
|
found_task = true;
|
||||||
foundTask = TRUE;
|
} else if (!s_normal_priority_queue.empty()) {
|
||||||
}
|
task = std::move(s_normal_priority_queue.front());
|
||||||
else if (!s_normalPriorityQueue.empty())
|
s_normal_priority_queue.pop_front();
|
||||||
{
|
found_task = true;
|
||||||
task = IA_MOVE(s_normalPriorityQueue.front());
|
|
||||||
s_normalPriorityQueue.pop_front();
|
|
||||||
foundTask = TRUE;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (foundTask)
|
|
||||||
{
|
if (found_task) {
|
||||||
task.Task(workerID);
|
task.task(worker_id);
|
||||||
if (task.ScheduleHandle->Counter.fetch_sub(1) == 1)
|
if (task.schedule_handle->counter.fetch_sub(1) == 1) {
|
||||||
task.ScheduleHandle->Counter.notify_all();
|
task.schedule_handle->counter.notify_all();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -17,9 +17,9 @@
|
|||||||
|
|
||||||
namespace IACore
|
namespace IACore
|
||||||
{
|
{
|
||||||
CLIParser::CLIParser(IN Span<CONST String> args) : m_argList(args)
|
CLIParser::CLIParser(Span<const String> args) : m_argList(args)
|
||||||
{
|
{
|
||||||
IA_RELEASE_ASSERT(args.size());
|
assert(args.size());
|
||||||
|
|
||||||
m_currentArg = m_argList.begin();
|
m_currentArg = m_argList.begin();
|
||||||
|
|
||||||
|
|||||||
@ -16,198 +16,202 @@
|
|||||||
#include <IACore/DataOps.hpp>
|
#include <IACore/DataOps.hpp>
|
||||||
#include <IACore/Platform.hpp>
|
#include <IACore/Platform.hpp>
|
||||||
|
|
||||||
|
#include <bit>
|
||||||
|
#include <cstring>
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
#include <zstd.h>
|
#include <zstd.h>
|
||||||
|
|
||||||
namespace IACore
|
#if IA_ARCH_X64
|
||||||
{
|
#include <immintrin.h>
|
||||||
template<typename T> INLINE T ReadUnaligned(IN PCUINT8 ptr)
|
#endif
|
||||||
{
|
|
||||||
|
#if IA_ARCH_ARM64
|
||||||
|
#include <arm_acle.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace IACore {
|
||||||
|
template <typename T>
|
||||||
|
[[nodiscard]] inline auto read_unaligned(const u8 *ptr) -> T {
|
||||||
T v;
|
T v;
|
||||||
std::memcpy(&v, ptr, sizeof(T));
|
std::memcpy(&v, ptr, sizeof(T));
|
||||||
return v;
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Crc32Tables {
|
||||||
|
u32 table[8][256] = {};
|
||||||
|
|
||||||
|
consteval Crc32Tables() {
|
||||||
|
constexpr u32 T = 0x82F63B78;
|
||||||
|
|
||||||
|
for (u32 i = 0; i < 256; i++) {
|
||||||
|
u32 crc = i;
|
||||||
|
for (i32 j = 0; j < 8; j++) {
|
||||||
|
crc = (crc >> 1) ^ ((crc & 1) ? T : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
table[0][i] = crc;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 256; i++)
|
for (i32 i = 0; i < 256; i++) {
|
||||||
{
|
for (i32 slice = 1; slice < 8; slice++) {
|
||||||
for (int slice = 1; slice < 8; slice++)
|
const u32 prev = table[slice - 1][i];
|
||||||
{
|
|
||||||
UINT32 prev = table[slice - 1][i];
|
|
||||||
table[slice][i] = (prev >> 8) ^ table[0][prev & 0xFF];
|
table[slice][i] = (prev >> 8) ^ table[0][prev & 0xFF];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
STATIC CONSTEXPR CRC32Tables CRC32_TABLES{};
|
static constexpr Crc32Tables CRC32_TABLES{};
|
||||||
|
|
||||||
#if IA_ARCH_X64
|
#if IA_ARCH_X64
|
||||||
INLINE UINT32 CRC32_x64_HW(IN Span<CONST UINT8> data)
|
inline auto crc32_x64_hw(Span<const u8> data) -> u32 {
|
||||||
{
|
const u8 *p = data.data();
|
||||||
CONST UINT8 *p = data.data();
|
|
||||||
|
|
||||||
UINT32 crc = 0xFFFFFFFF;
|
u32 crc = 0xFFFFFFFF;
|
||||||
SIZE_T len = data.size();
|
usize len = data.size();
|
||||||
|
|
||||||
while (len >= 8)
|
while (len >= 8) {
|
||||||
{
|
const u64 chunk = read_unaligned<u64>(p);
|
||||||
UINT64 chunk = ReadUnaligned<UINT64>(p);
|
crc = static_cast<u32>(_mm_crc32_u64(static_cast<u64>(crc), chunk));
|
||||||
crc = (UINT32) _mm_crc32_u64((UINT64) crc, chunk);
|
|
||||||
p += 8;
|
p += 8;
|
||||||
len -= 8;
|
len -= 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (len--)
|
while (len--) {
|
||||||
crc = _mm_crc32_u8(crc, *p++);
|
crc = _mm_crc32_u8(crc, *p++);
|
||||||
|
}
|
||||||
|
|
||||||
return ~crc;
|
return ~crc;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if IA_ARCH_ARM64
|
#if IA_ARCH_ARM64
|
||||||
__attribute__((target("+crc"))) INLINE UINT32 CRC32_ARM64_HW(IN Span<CONST UINT8> data)
|
__attribute__((target("+crc"))) inline auto crc32_arm64_hw(Span<const u8> data)
|
||||||
{
|
-> u32 {
|
||||||
CONST UINT8 *p = data.data();
|
const u8 *p = data.data();
|
||||||
|
|
||||||
UINT32 crc = 0xFFFFFFFF;
|
u32 crc = 0xFFFFFFFF;
|
||||||
SIZE_T len = data.size();
|
usize len = data.size();
|
||||||
|
|
||||||
while (len >= 8)
|
while (len >= 8) {
|
||||||
{
|
const u64 chunk = read_unaligned<u64>(p);
|
||||||
UINT64 chunk = ReadUnaligned<UINT64>(p);
|
|
||||||
crc = __crc32cd(crc, chunk);
|
crc = __crc32cd(crc, chunk);
|
||||||
p += 8;
|
p += 8;
|
||||||
len -= 8;
|
len -= 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (len--)
|
while (len--) {
|
||||||
crc = __crc32cb(crc, *p++);
|
crc = __crc32cb(crc, *p++);
|
||||||
|
}
|
||||||
|
|
||||||
return ~crc;
|
return ~crc;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
INLINE UINT32 CRC32_Software_Slice8(IN Span<CONST UINT8> data)
|
inline auto crc32_software_slice8(Span<const u8> data) -> u32 {
|
||||||
{
|
const u8 *p = data.data();
|
||||||
CONST UINT8 *p = data.data();
|
u32 crc = 0xFFFFFFFF;
|
||||||
UINT32 crc = 0xFFFFFFFF;
|
usize len = data.size();
|
||||||
SIZE_T len = data.size();
|
|
||||||
|
|
||||||
while (len >= 8)
|
while (len >= 8) {
|
||||||
{
|
const u32 term1 = crc ^ read_unaligned<u32>(p);
|
||||||
UINT32 term1 = crc ^ ReadUnaligned<UINT32>(p);
|
const u32 term2 = read_unaligned<u32>(p + 4);
|
||||||
UINT32 term2 = ReadUnaligned<UINT32>(p + 4);
|
|
||||||
|
|
||||||
crc = CRC32_TABLES.table[7][term1 & 0xFF] ^ CRC32_TABLES.table[6][(term1 >> 8) & 0xFF] ^
|
crc = CRC32_TABLES.table[7][term1 & 0xFF] ^
|
||||||
CRC32_TABLES.table[5][(term1 >> 16) & 0xFF] ^ CRC32_TABLES.table[4][(term1 >> 24)] ^
|
CRC32_TABLES.table[6][(term1 >> 8) & 0xFF] ^
|
||||||
CRC32_TABLES.table[3][term2 & 0xFF] ^ CRC32_TABLES.table[2][(term2 >> 8) & 0xFF] ^
|
CRC32_TABLES.table[5][(term1 >> 16) & 0xFF] ^
|
||||||
CRC32_TABLES.table[1][(term2 >> 16) & 0xFF] ^ CRC32_TABLES.table[0][(term2 >> 24)];
|
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;
|
p += 8;
|
||||||
len -= 8;
|
len -= 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (len--)
|
while (len--) {
|
||||||
crc = (crc >> 8) ^ CRC32_TABLES.table[0][(crc ^ *p++) & 0xFF];
|
crc = (crc >> 8) ^ CRC32_TABLES.table[0][(crc ^ *p++) & 0xFF];
|
||||||
|
|
||||||
return ~crc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UINT32 DataOps::CRC32(IN Span<CONST UINT8> data)
|
return ~crc;
|
||||||
{
|
}
|
||||||
|
|
||||||
|
auto DataOps::crc32(Span<const u8> data) -> u32 {
|
||||||
#if IA_ARCH_X64
|
#if IA_ARCH_X64
|
||||||
// IACore mandates AVX2 so no need to check
|
// IACore mandates AVX2 so no need to check
|
||||||
// for Platform::GetCapabilities().HardwareCRC32
|
// for Platform::GetCapabilities().HardwareCRC32
|
||||||
return CRC32_x64_HW(data);
|
return crc32_x64_hw(data);
|
||||||
#elif IA_ARCH_ARM64
|
#elif IA_ARCH_ARM64
|
||||||
if (Platform::GetCapabilities().HardwareCRC32)
|
if (Platform::GetCapabilities().HardwareCRC32) {
|
||||||
return CRC32_ARM64_HW(data);
|
return crc32_arm64_hw(data);
|
||||||
#endif
|
|
||||||
return CRC32_Software_Slice8(data);
|
|
||||||
}
|
}
|
||||||
} // namespace IACore
|
#endif
|
||||||
|
return crc32_software_slice8(data);
|
||||||
|
}
|
||||||
|
|
||||||
namespace IACore
|
// =============================================================================
|
||||||
{
|
// xxHash
|
||||||
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;
|
|
||||||
|
|
||||||
INLINE UINT32 XXH32_Round(IN UINT32 seed, IN UINT32 input)
|
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 += input * XXH_PRIME32_2;
|
||||||
seed = std::rotl(seed, 13);
|
seed = std::rotl(seed, 13);
|
||||||
seed *= XXH_PRIME32_1;
|
seed *= XXH_PRIME32_1;
|
||||||
return seed;
|
return seed;
|
||||||
}
|
}
|
||||||
|
|
||||||
UINT32 DataOps::Hash_xxHash(IN CONST String &string, IN UINT32 seed)
|
auto DataOps::hash_xxhash(const String &string, u32 seed) -> u32 {
|
||||||
{
|
return hash_xxhash(Span<const u8>(reinterpret_cast<const u8 *>(string.data()),
|
||||||
return Hash_xxHash(Span<CONST UINT8>(reinterpret_cast<PCUINT8>(string.data()), string.length()), seed);
|
string.length()),
|
||||||
}
|
seed);
|
||||||
|
}
|
||||||
|
|
||||||
UINT32 DataOps::Hash_xxHash(IN Span<CONST UINT8> data, IN UINT32 seed)
|
auto DataOps::hash_xxhash(Span<const u8> data, u32 seed) -> u32 {
|
||||||
{
|
const u8 *p = data.data();
|
||||||
CONST UINT8 *p = data.data();
|
const u8 *const b_end = p + data.size();
|
||||||
CONST UINT8 *CONST bEnd = p + data.size();
|
u32 h32{};
|
||||||
UINT32 h32{};
|
|
||||||
|
|
||||||
if (data.size() >= 16)
|
if (data.size() >= 16) {
|
||||||
{
|
const u8 *const limit = b_end - 16;
|
||||||
const UINT8 *const limit = bEnd - 16;
|
|
||||||
|
|
||||||
UINT32 v1 = seed + XXH_PRIME32_1 + XXH_PRIME32_2;
|
u32 v1 = seed + XXH_PRIME32_1 + XXH_PRIME32_2;
|
||||||
UINT32 v2 = seed + XXH_PRIME32_2;
|
u32 v2 = seed + XXH_PRIME32_2;
|
||||||
UINT32 v3 = seed + 0;
|
u32 v3 = seed + 0;
|
||||||
UINT32 v4 = seed - XXH_PRIME32_1;
|
u32 v4 = seed - XXH_PRIME32_1;
|
||||||
|
|
||||||
do
|
do {
|
||||||
{
|
v1 = xxh32_round(v1, read_unaligned<u32>(p));
|
||||||
v1 = XXH32_Round(v1, ReadUnaligned<UINT32>(p));
|
|
||||||
p += 4;
|
p += 4;
|
||||||
v2 = XXH32_Round(v2, ReadUnaligned<UINT32>(p));
|
v2 = xxh32_round(v2, read_unaligned<u32>(p));
|
||||||
p += 4;
|
p += 4;
|
||||||
v3 = XXH32_Round(v3, ReadUnaligned<UINT32>(p));
|
v3 = xxh32_round(v3, read_unaligned<u32>(p));
|
||||||
p += 4;
|
p += 4;
|
||||||
v4 = XXH32_Round(v4, ReadUnaligned<UINT32>(p));
|
v4 = xxh32_round(v4, read_unaligned<u32>(p));
|
||||||
p += 4;
|
p += 4;
|
||||||
} while (p <= limit);
|
} while (p <= limit);
|
||||||
|
|
||||||
h32 = std::rotl(v1, 1) + std::rotl(v2, 7) + std::rotl(v3, 12) + std::rotl(v4, 18);
|
h32 = std::rotl(v1, 1) + std::rotl(v2, 7) + std::rotl(v3, 12) +
|
||||||
}
|
std::rotl(v4, 18);
|
||||||
else
|
} else {
|
||||||
h32 = seed + XXH_PRIME32_5;
|
h32 = seed + XXH_PRIME32_5;
|
||||||
|
}
|
||||||
|
|
||||||
h32 += (UINT32) data.size();
|
h32 += static_cast<u32>(data.size());
|
||||||
|
|
||||||
while (p + 4 <= bEnd)
|
while (p + 4 <= b_end) {
|
||||||
{
|
const auto t = read_unaligned<u32>(p) * XXH_PRIME32_3;
|
||||||
const auto t = ReadUnaligned<UINT32>(p) * XXH_PRIME32_3;
|
|
||||||
h32 += t;
|
h32 += t;
|
||||||
h32 = std::rotl(h32, 17) * XXH_PRIME32_4;
|
h32 = std::rotl(h32, 17) * XXH_PRIME32_4;
|
||||||
p += 4;
|
p += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (p < bEnd)
|
while (p < b_end) {
|
||||||
{
|
|
||||||
h32 += (*p++) * XXH_PRIME32_5;
|
h32 += (*p++) * XXH_PRIME32_5;
|
||||||
h32 = std::rotl(h32, 11) * XXH_PRIME32_1;
|
h32 = std::rotl(h32, 11) * XXH_PRIME32_1;
|
||||||
}
|
}
|
||||||
@ -219,94 +223,91 @@ namespace IACore
|
|||||||
h32 ^= h32 >> 16;
|
h32 ^= h32 >> 16;
|
||||||
|
|
||||||
return h32;
|
return h32;
|
||||||
}
|
}
|
||||||
} // namespace IACore
|
|
||||||
|
|
||||||
namespace IACore
|
// =============================================================================
|
||||||
{
|
// FNV-1a
|
||||||
// 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)
|
constexpr u32 FNV1A_32_PRIME = 0x01000193;
|
||||||
{
|
constexpr u32 FNV1A_32_OFFSET = 0x811c9dc5;
|
||||||
UINT32 hash = FNV1A_32_OFFSET;
|
|
||||||
for (char c : string)
|
auto DataOps::hash_fnv1a(const String &string) -> u32 {
|
||||||
{
|
u32 hash = FNV1A_32_OFFSET;
|
||||||
hash ^= static_cast<uint8_t>(c);
|
for (const char c : string) {
|
||||||
|
hash ^= static_cast<u8>(c);
|
||||||
hash *= FNV1A_32_PRIME;
|
hash *= FNV1A_32_PRIME;
|
||||||
}
|
}
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
UINT32 DataOps::Hash_FNV1A(IN Span<CONST UINT8> data)
|
auto DataOps::hash_fnv1a(Span<const u8> data) -> u32 {
|
||||||
{
|
u32 hash = FNV1A_32_OFFSET;
|
||||||
UINT32 hash = FNV1A_32_OFFSET;
|
const auto *ptr = data.data();
|
||||||
const uint8_t *ptr = static_cast<const uint8_t *>(data.data());
|
|
||||||
|
|
||||||
for (size_t i = 0; i < data.size(); ++i)
|
for (usize i = 0; i < data.size(); ++i) {
|
||||||
{
|
|
||||||
hash ^= ptr[i];
|
hash ^= ptr[i];
|
||||||
hash *= FNV1A_32_PRIME;
|
hash *= FNV1A_32_PRIME;
|
||||||
}
|
}
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
} // namespace IACore
|
|
||||||
|
|
||||||
namespace IACore
|
// =============================================================================
|
||||||
{
|
// Compression
|
||||||
DataOps::CompressionType DataOps::DetectCompression(IN Span<CONST UINT8> data)
|
// =============================================================================
|
||||||
{
|
|
||||||
if (data.size() < 2)
|
auto DataOps::detect_compression(Span<const u8> data) -> CompressionType {
|
||||||
|
if (data.size() < 2) {
|
||||||
return CompressionType::None;
|
return CompressionType::None;
|
||||||
|
}
|
||||||
|
|
||||||
// Check for GZIP Magic Number (0x1F 0x8B)
|
// Check for GZIP Magic Number (0x1F 0x8B)
|
||||||
if (data[0] == 0x1F && data[1] == 0x8B)
|
if (data[0] == 0x1F && data[1] == 0x8B) {
|
||||||
return CompressionType::Gzip;
|
return CompressionType::Gzip;
|
||||||
|
}
|
||||||
|
|
||||||
// Check for ZLIB Magic Number (starts with 0x78)
|
// Check for ZLIB Magic Number (starts with 0x78)
|
||||||
// 0x78 = Deflate compression with 32k window size
|
// 0x78 = Deflate compression with 32k window size
|
||||||
if (data[0] == 0x78 && (data[1] == 0x01 || data[1] == 0x9C || data[1] == 0xDA))
|
if (data[0] == 0x78 &&
|
||||||
|
(data[1] == 0x01 || data[1] == 0x9C || data[1] == 0xDA)) {
|
||||||
return CompressionType::Zlib;
|
return CompressionType::Zlib;
|
||||||
|
|
||||||
return CompressionType::None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT(Vector<UINT8>) DataOps::ZlibInflate(IN Span<CONST UINT8> data)
|
return CompressionType::None;
|
||||||
{
|
}
|
||||||
|
|
||||||
|
auto DataOps::zlib_inflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||||
z_stream zs{};
|
z_stream zs{};
|
||||||
zs.zalloc = Z_NULL;
|
zs.zalloc = Z_NULL;
|
||||||
zs.zfree = Z_NULL;
|
zs.zfree = Z_NULL;
|
||||||
zs.opaque = Z_NULL;
|
zs.opaque = Z_NULL;
|
||||||
|
|
||||||
// 15 + 32 = Auto-detect Gzip or Zlib
|
// 15 + 32 = Auto-detect Gzip or Zlib
|
||||||
if (inflateInit2(&zs, 15 + 32) != Z_OK)
|
if (inflateInit2(&zs, 15 + 32) != Z_OK) {
|
||||||
return MakeUnexpected("Failed to initialize zlib inflate");
|
return fail("Failed to initialize zlib inflate");
|
||||||
|
}
|
||||||
|
|
||||||
zs.next_in = const_cast<Bytef *>(data.data());
|
zs.next_in = const_cast<Bytef *>(data.data());
|
||||||
zs.avail_in = static_cast<uInt>(data.size());
|
zs.avail_in = static_cast<uInt>(data.size());
|
||||||
|
|
||||||
Vector<UINT8> outBuffer;
|
Vec<u8> out_buffer;
|
||||||
// Start with 2x input size.
|
// Start with 2x input size.
|
||||||
size_t guessSize = data.size() < 1024 ? data.size() * 4 : data.size() * 2;
|
const usize guess_size =
|
||||||
outBuffer.resize(guessSize);
|
data.size() < 1024 ? data.size() * 4 : data.size() * 2;
|
||||||
|
out_buffer.resize(guess_size);
|
||||||
|
|
||||||
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data());
|
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data());
|
||||||
zs.avail_out = static_cast<uInt>(outBuffer.size());
|
zs.avail_out = static_cast<uInt>(out_buffer.size());
|
||||||
|
|
||||||
int ret;
|
int ret;
|
||||||
do
|
do {
|
||||||
{
|
if (zs.avail_out == 0) {
|
||||||
if (zs.avail_out == 0)
|
const usize current_pos = zs.total_out;
|
||||||
{
|
const usize new_size = out_buffer.size() * 2;
|
||||||
size_t currentPos = zs.total_out;
|
out_buffer.resize(new_size);
|
||||||
|
|
||||||
size_t newSize = outBuffer.size() * 2;
|
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data() + current_pos);
|
||||||
outBuffer.resize(newSize);
|
zs.avail_out = static_cast<uInt>(new_size - current_pos);
|
||||||
|
|
||||||
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data() + currentPos);
|
|
||||||
|
|
||||||
zs.avail_out = static_cast<uInt>(newSize - currentPos);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = inflate(&zs, Z_NO_FLUSH);
|
ret = inflate(&zs, Z_NO_FLUSH);
|
||||||
@ -315,121 +316,119 @@ namespace IACore
|
|||||||
|
|
||||||
inflateEnd(&zs);
|
inflateEnd(&zs);
|
||||||
|
|
||||||
if (ret != Z_STREAM_END)
|
if (ret != Z_STREAM_END) {
|
||||||
return MakeUnexpected("Failed to inflate: corrupt data or stream error");
|
return fail("Failed to inflate: corrupt data or stream error");
|
||||||
|
|
||||||
outBuffer.resize(zs.total_out);
|
|
||||||
|
|
||||||
return outBuffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT(Vector<UINT8>) DataOps::ZlibDeflate(IN Span<CONST UINT8> data)
|
out_buffer.resize(zs.total_out);
|
||||||
{
|
|
||||||
|
return out_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto DataOps::zlib_deflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||||
z_stream zs{};
|
z_stream zs{};
|
||||||
zs.zalloc = Z_NULL;
|
zs.zalloc = Z_NULL;
|
||||||
zs.zfree = Z_NULL;
|
zs.zfree = Z_NULL;
|
||||||
zs.opaque = Z_NULL;
|
zs.opaque = Z_NULL;
|
||||||
|
|
||||||
if (deflateInit(&zs, Z_DEFAULT_COMPRESSION) != Z_OK)
|
if (deflateInit(&zs, Z_DEFAULT_COMPRESSION) != Z_OK) {
|
||||||
return MakeUnexpected("Failed to initialize zlib deflate");
|
return fail("Failed to initialize zlib deflate");
|
||||||
|
}
|
||||||
|
|
||||||
zs.next_in = const_cast<Bytef *>(data.data());
|
zs.next_in = const_cast<Bytef *>(data.data());
|
||||||
zs.avail_in = static_cast<uInt>(data.size());
|
zs.avail_in = static_cast<uInt>(data.size());
|
||||||
|
|
||||||
Vector<UINT8> outBuffer;
|
Vec<u8> out_buffer;
|
||||||
|
out_buffer.resize(deflateBound(&zs, static_cast<uLong>(data.size())));
|
||||||
|
|
||||||
outBuffer.resize(deflateBound(&zs, data.size()));
|
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data());
|
||||||
|
zs.avail_out = static_cast<uInt>(out_buffer.size());
|
||||||
|
|
||||||
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data());
|
const int ret = deflate(&zs, Z_FINISH);
|
||||||
zs.avail_out = static_cast<uInt>(outBuffer.size());
|
|
||||||
|
|
||||||
int ret = deflate(&zs, Z_FINISH);
|
if (ret != Z_STREAM_END) {
|
||||||
|
|
||||||
if (ret != Z_STREAM_END)
|
|
||||||
{
|
|
||||||
deflateEnd(&zs);
|
deflateEnd(&zs);
|
||||||
return MakeUnexpected("Failed to deflate, ran out of buffer memory");
|
return fail("Failed to deflate, ran out of buffer memory");
|
||||||
}
|
}
|
||||||
|
|
||||||
outBuffer.resize(zs.total_out);
|
out_buffer.resize(zs.total_out);
|
||||||
|
|
||||||
deflateEnd(&zs);
|
deflateEnd(&zs);
|
||||||
return outBuffer;
|
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT(Vector<UINT8>) DataOps::ZstdInflate(IN Span<CONST UINT8> data)
|
if (content_size != ZSTD_CONTENTSIZE_UNKNOWN) {
|
||||||
{
|
|
||||||
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
|
// FAST PATH: We know the size
|
||||||
Vector<UINT8> outBuffer;
|
Vec<u8> out_buffer;
|
||||||
outBuffer.resize(static_cast<size_t>(contentSize));
|
out_buffer.resize(static_cast<usize>(content_size));
|
||||||
|
|
||||||
size_t const dSize = ZSTD_decompress(outBuffer.data(), outBuffer.size(), data.data(), data.size());
|
const usize d_size = ZSTD_decompress(out_buffer.data(), out_buffer.size(),
|
||||||
|
data.data(), data.size());
|
||||||
|
|
||||||
if (ZSTD_isError(dSize))
|
if (ZSTD_isError(d_size)) {
|
||||||
return MakeUnexpected(std::format("Failed to inflate: {}", ZSTD_getErrorName(dSize)));
|
return fail("Failed to inflate: {}", ZSTD_getErrorName(d_size));
|
||||||
|
}
|
||||||
|
|
||||||
return outBuffer;
|
return out_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
ZSTD_DCtx *dctx = ZSTD_createDCtx();
|
ZSTD_DCtx *dctx = ZSTD_createDCtx();
|
||||||
Vector<UINT8> outBuffer;
|
Vec<u8> out_buffer;
|
||||||
outBuffer.resize(data.size() * 2);
|
out_buffer.resize(data.size() * 2);
|
||||||
|
|
||||||
ZSTD_inBuffer input = {data.data(), data.size(), 0};
|
ZSTD_inBuffer input = {data.data(), data.size(), 0};
|
||||||
ZSTD_outBuffer output = {outBuffer.data(), outBuffer.size(), 0};
|
ZSTD_outBuffer output = {out_buffer.data(), out_buffer.size(), 0};
|
||||||
|
|
||||||
size_t ret;
|
usize ret;
|
||||||
do
|
do {
|
||||||
{
|
|
||||||
ret = ZSTD_decompressStream(dctx, &output, &input);
|
ret = ZSTD_decompressStream(dctx, &output, &input);
|
||||||
|
|
||||||
if (ZSTD_isError(ret))
|
if (ZSTD_isError(ret)) {
|
||||||
{
|
|
||||||
ZSTD_freeDCtx(dctx);
|
ZSTD_freeDCtx(dctx);
|
||||||
return MakeUnexpected(std::format("Failed to inflate: {}", ZSTD_getErrorName(ret)));
|
return fail("Failed to inflate: {}", ZSTD_getErrorName(ret));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (output.pos == output.size)
|
if (output.pos == output.size) {
|
||||||
{
|
const usize new_size = out_buffer.size() * 2;
|
||||||
size_t newSize = outBuffer.size() * 2;
|
out_buffer.resize(new_size);
|
||||||
outBuffer.resize(newSize);
|
output.dst = out_buffer.data();
|
||||||
output.dst = outBuffer.data();
|
output.size = new_size;
|
||||||
output.size = newSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} while (ret != 0);
|
} while (ret != 0);
|
||||||
|
|
||||||
outBuffer.resize(output.pos);
|
out_buffer.resize(output.pos);
|
||||||
ZSTD_freeDCtx(dctx);
|
ZSTD_freeDCtx(dctx);
|
||||||
|
|
||||||
return outBuffer;
|
return out_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto DataOps::zstd_deflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||||
|
const usize max_dst_size = ZSTD_compressBound(data.size());
|
||||||
|
|
||||||
|
Vec<u8> out_buffer;
|
||||||
|
out_buffer.resize(max_dst_size);
|
||||||
|
|
||||||
|
const usize compressed_size = ZSTD_compress(out_buffer.data(), max_dst_size,
|
||||||
|
data.data(), data.size(), 3);
|
||||||
|
|
||||||
|
if (ZSTD_isError(compressed_size)) {
|
||||||
|
return fail("Failed to deflate: {}", ZSTD_getErrorName(compressed_size));
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT(Vector<UINT8>) DataOps::ZstdDeflate(IN Span<CONST UINT8> data)
|
out_buffer.resize(compressed_size);
|
||||||
{
|
return out_buffer;
|
||||||
size_t const maxDstSize = ZSTD_compressBound(data.size());
|
}
|
||||||
|
|
||||||
Vector<UINT8> outBuffer;
|
auto DataOps::gzip_deflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||||
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{};
|
z_stream zs{};
|
||||||
zs.zalloc = Z_NULL;
|
zs.zalloc = Z_NULL;
|
||||||
zs.zfree = Z_NULL;
|
zs.zfree = Z_NULL;
|
||||||
@ -438,35 +437,37 @@ namespace IACore
|
|||||||
// WindowBits = 15 + 16 (31) = Enforce GZIP encoding
|
// WindowBits = 15 + 16 (31) = Enforce GZIP encoding
|
||||||
// MemLevel = 8 (default)
|
// MemLevel = 8 (default)
|
||||||
// Strategy = Z_DEFAULT_STRATEGY
|
// Strategy = Z_DEFAULT_STRATEGY
|
||||||
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK)
|
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8,
|
||||||
return MakeUnexpected("Failed to initialize gzip deflate");
|
Z_DEFAULT_STRATEGY) != Z_OK) {
|
||||||
|
return fail("Failed to initialize gzip deflate");
|
||||||
|
}
|
||||||
|
|
||||||
zs.next_in = const_cast<Bytef *>(data.data());
|
zs.next_in = const_cast<Bytef *>(data.data());
|
||||||
zs.avail_in = static_cast<uInt>(data.size());
|
zs.avail_in = static_cast<uInt>(data.size());
|
||||||
|
|
||||||
Vector<UINT8> outBuffer;
|
Vec<u8> out_buffer;
|
||||||
|
|
||||||
outBuffer.resize(deflateBound(&zs, data.size()) + 1024); // Additional 1KB buffer for safety
|
out_buffer.resize(deflateBound(&zs, static_cast<uLong>(data.size())) +
|
||||||
|
1024); // Additional 1KB buffer for safety
|
||||||
|
|
||||||
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data());
|
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data());
|
||||||
zs.avail_out = static_cast<uInt>(outBuffer.size());
|
zs.avail_out = static_cast<uInt>(out_buffer.size());
|
||||||
|
|
||||||
int ret = deflate(&zs, Z_FINISH);
|
const int ret = deflate(&zs, Z_FINISH);
|
||||||
|
|
||||||
if (ret != Z_STREAM_END)
|
if (ret != Z_STREAM_END) {
|
||||||
{
|
|
||||||
deflateEnd(&zs);
|
deflateEnd(&zs);
|
||||||
return MakeUnexpected("Failed to deflate");
|
return fail("Failed to deflate");
|
||||||
}
|
}
|
||||||
|
|
||||||
outBuffer.resize(zs.total_out);
|
out_buffer.resize(zs.total_out);
|
||||||
|
|
||||||
deflateEnd(&zs);
|
deflateEnd(&zs);
|
||||||
return outBuffer;
|
return out_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto DataOps::gzip_inflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||||
|
return zlib_inflate(data);
|
||||||
|
}
|
||||||
|
|
||||||
EXPECT(Vector<UINT8>) DataOps::GZipInflate(IN Span<CONST UINT8> data)
|
|
||||||
{
|
|
||||||
return ZlibInflate(data);
|
|
||||||
}
|
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -14,501 +14,523 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
#include <IACore/FileOps.hpp>
|
#include <IACore/FileOps.hpp>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
namespace IACore
|
#if IA_PLATFORM_UNIX
|
||||||
{
|
#include <fcntl.h>
|
||||||
UnorderedMap<PCUINT8, Tuple<PVOID, PVOID, PVOID>> FileOps::s_mappedFiles;
|
#include <sys/mman.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
VOID FileOps::UnmapFile(IN PCUINT8 mappedPtr)
|
namespace IACore {
|
||||||
{
|
|
||||||
if (!s_mappedFiles.contains(mappedPtr))
|
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;
|
return;
|
||||||
const auto handles = s_mappedFiles.extract(mappedPtr)->second;
|
}
|
||||||
|
|
||||||
|
auto it = s_mapped_files.find(mapped_ptr);
|
||||||
|
const auto handles = it->second;
|
||||||
|
s_mapped_files.erase(it);
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
::UnmapViewOfFile(std::get<1>(handles));
|
::UnmapViewOfFile(std::get<1>(handles));
|
||||||
::CloseHandle(std::get<2>(handles));
|
::CloseHandle(static_cast<HANDLE>(std::get<2>(handles)));
|
||||||
|
|
||||||
if (std::get<0>(handles) != INVALID_HANDLE_VALUE)
|
const auto handle = static_cast<HANDLE>(std::get<0>(handles));
|
||||||
::CloseHandle(std::get<0>(handles));
|
if (handle != INVALID_HANDLE_VALUE) {
|
||||||
|
::CloseHandle(handle);
|
||||||
|
}
|
||||||
#elif IA_PLATFORM_UNIX
|
#elif IA_PLATFORM_UNIX
|
||||||
::munmap(std::get<1>(handles), (SIZE_T) std::get<2>(handles));
|
::munmap(std::get<1>(handles), (usize)std::get<2>(handles));
|
||||||
const auto fd = (INT32) ((UINT64) std::get<0>(handles));
|
const auto fd = (i32)((u64)std::get<0>(handles));
|
||||||
if (fd != -1)
|
if (fd != -1) {
|
||||||
::close(fd);
|
::close(fd);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT(PUINT8) FileOps::MapSharedMemory(IN CONST String &name, IN SIZE_T size, IN BOOL isOwner)
|
auto FileOps::map_shared_memory(const String &name, usize size, bool is_owner)
|
||||||
{
|
-> Result<u8 *> {
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
int wchars_num = MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, NULL, 0);
|
const int wchars_num =
|
||||||
std::wstring wName(wchars_num, 0);
|
MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, NULL, 0);
|
||||||
MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, &wName[0], wchars_num);
|
std::wstring w_name(wchars_num, 0);
|
||||||
|
MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, &w_name[0], wchars_num);
|
||||||
|
|
||||||
HANDLE hMap = NULL;
|
HANDLE h_map = NULL;
|
||||||
if (isOwner)
|
if (is_owner) {
|
||||||
hMap = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, (DWORD) (size >> 32),
|
h_map = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
|
||||||
(DWORD) (size & 0xFFFFFFFF), wName.c_str());
|
(DWORD)(size >> 32), (DWORD)(size & 0xFFFFFFFF),
|
||||||
else
|
w_name.c_str());
|
||||||
hMap = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, wName.c_str());
|
} else {
|
||||||
|
h_map = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, w_name.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);
|
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;
|
return result;
|
||||||
|
|
||||||
#elif IA_PLATFORM_UNIX
|
#elif IA_PLATFORM_UNIX
|
||||||
int fd = -1;
|
int fd = -1;
|
||||||
if (isOwner)
|
if (is_owner) {
|
||||||
{
|
|
||||||
fd = shm_open(name.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0666);
|
fd = shm_open(name.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0666);
|
||||||
if (fd != -1)
|
if (fd != -1) {
|
||||||
{
|
if (ftruncate(fd, size) == -1) {
|
||||||
if (ftruncate(fd, size) == -1)
|
|
||||||
{
|
|
||||||
close(fd);
|
close(fd);
|
||||||
shm_unlink(name.c_str());
|
shm_unlink(name.c_str());
|
||||||
return MakeUnexpected(std::format("Failed to truncate shared memory '{}'", name.c_str()));
|
return fail("Failed to truncate shared memory '{}'", name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
fd = shm_open(name.c_str(), O_RDWR, 0666);
|
fd = shm_open(name.c_str(), O_RDWR, 0666);
|
||||||
|
}
|
||||||
|
|
||||||
if (fd == -1)
|
if (fd == -1) {
|
||||||
return MakeUnexpected(
|
return fail("Failed to {} shared memory '{}'",
|
||||||
std::format("Failed to {} shared memory '{}'", isOwner ? "owner" : "consumer", name.c_str()));
|
is_owner ? "owner" : "consumer", name);
|
||||||
|
}
|
||||||
|
|
||||||
void *addr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
void *addr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||||
if (addr == MAP_FAILED)
|
if (addr == MAP_FAILED) {
|
||||||
{
|
|
||||||
close(fd);
|
close(fd);
|
||||||
return MakeUnexpected(std::format("Failed to mmap shared memory '{}'", name.c_str()));
|
return fail("Failed to mmap shared memory '{}'", name);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto result = static_cast<PUINT8>(addr);
|
auto *result = static_cast<u8 *>(addr);
|
||||||
|
|
||||||
s_mappedFiles[result] = std::make_tuple((PVOID) ((UINT64) fd), (PVOID) addr, (PVOID) size);
|
s_mapped_files[result] =
|
||||||
|
std::make_tuple((void *)((u64)fd), (void *)addr, (void *)size);
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID FileOps::UnlinkSharedMemory(IN CONST String &name)
|
auto FileOps::unlink_shared_memory(const String &name) -> void {
|
||||||
{
|
if (name.empty()) {
|
||||||
if (name.empty())
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
#if IA_PLATFORM_UNIX
|
#if IA_PLATFORM_UNIX
|
||||||
shm_unlink(name.c_str());
|
shm_unlink(name.c_str());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT(PCUINT8) FileOps::MapFile(IN CONST FilePath &path, OUT SIZE_T &size)
|
auto FileOps::map_file(const Path &path, usize &size) -> Result<const u8 *> {
|
||||||
{
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
const auto handle = CreateFileA(
|
||||||
const auto handle = CreateFileA(path.string().c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
|
path.string().c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
|
||||||
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
|
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
|
||||||
|
|
||||||
if (handle == INVALID_HANDLE_VALUE)
|
if (handle == INVALID_HANDLE_VALUE) {
|
||||||
return MakeUnexpected(std::format("Failed to open {} for memory mapping", path.string().c_str()));
|
return fail("Failed to open {} 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 hmap = CreateFileMappingW(handle, NULL, PAGE_READONLY, 0, 0, NULL);
|
LARGE_INTEGER file_size;
|
||||||
if (hmap == NULL)
|
if (!GetFileSizeEx(handle, &file_size)) {
|
||||||
{
|
|
||||||
CloseHandle(handle);
|
CloseHandle(handle);
|
||||||
return MakeUnexpected(std::format("Failed to memory map {}", path.string().c_str()));
|
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto result = static_cast<PCUINT8>(MapViewOfFile(hmap, FILE_MAP_READ, 0, 0, 0));
|
auto h_map = CreateFileMappingW(handle, NULL, PAGE_READONLY, 0, 0, NULL);
|
||||||
if (result == NULL)
|
if (h_map == NULL) {
|
||||||
{
|
|
||||||
CloseHandle(handle);
|
CloseHandle(handle);
|
||||||
CloseHandle(hmap);
|
return fail("Failed to memory map {}", path.string());
|
||||||
return MakeUnexpected(std::format("Failed to memory map {}", path.string().c_str()));
|
|
||||||
}
|
}
|
||||||
s_mappedFiles[result] = std::make_tuple((PVOID) handle, (PVOID) result, (PVOID) hmap);
|
|
||||||
|
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;
|
return result;
|
||||||
|
|
||||||
#elif IA_PLATFORM_UNIX
|
#elif IA_PLATFORM_UNIX
|
||||||
|
|
||||||
const auto handle = open(path.string().c_str(), O_RDONLY);
|
const auto handle = open(path.string().c_str(), O_RDONLY);
|
||||||
if (handle == -1)
|
if (handle == -1) {
|
||||||
return MakeUnexpected(std::format("Failed to open {} for memory mapping", path.string().c_str()));
|
return fail("Failed to open {} for memory mapping", path.string());
|
||||||
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);
|
struct stat sb;
|
||||||
if (size == 0)
|
if (fstat(handle, &sb) == -1) {
|
||||||
{
|
|
||||||
close(handle);
|
close(handle);
|
||||||
return MakeUnexpected(std::format("Failed to get size of {} for memory mapping", path.string().c_str()));
|
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);
|
void *addr = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, handle, 0);
|
||||||
if (addr == MAP_FAILED)
|
if (addr == MAP_FAILED) {
|
||||||
{
|
|
||||||
close(handle);
|
close(handle);
|
||||||
return MakeUnexpected(std::format("Failed to memory map {}", path.string().c_str()));
|
return fail("Failed to memory map {}", path.string());
|
||||||
}
|
}
|
||||||
const auto result = static_cast<PCUINT8>(addr);
|
const auto *result = static_cast<const u8 *>(addr);
|
||||||
madvise(addr, size, MADV_SEQUENTIAL);
|
madvise(addr, size, MADV_SEQUENTIAL);
|
||||||
s_mappedFiles[result] = std::make_tuple((PVOID) ((UINT64) handle), (PVOID) addr, (PVOID) size);
|
s_mapped_files[result] =
|
||||||
|
std::make_tuple((void *)((u64)handle), (void *)addr, (void *)size);
|
||||||
return result;
|
return result;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT(StreamWriter) FileOps::StreamToFile(IN CONST FilePath &path, IN BOOL overwrite)
|
auto FileOps::stream_to_file(const Path &path, bool overwrite)
|
||||||
{
|
-> Result<StreamWriter> {
|
||||||
if (!overwrite && FileSystem::exists(path))
|
if (!overwrite && std::filesystem::exists(path)) {
|
||||||
return MakeUnexpected(std::format("File aready exists: {}", path.string().c_str()));
|
return fail("File already exists: {}", path.string());
|
||||||
return StreamWriter(path);
|
|
||||||
}
|
}
|
||||||
|
return StreamWriter::create(path);
|
||||||
|
}
|
||||||
|
|
||||||
EXPECT(StreamReader) FileOps::StreamFromFile(IN CONST FilePath &path)
|
auto FileOps::stream_from_file(const Path &path) -> Result<StreamReader> {
|
||||||
{
|
if (!std::filesystem::exists(path)) {
|
||||||
if (!FileSystem::exists(path))
|
return fail("File does not exist: {}", path.string());
|
||||||
return MakeUnexpected(std::format("File does not exist: {}", path.string().c_str()));
|
|
||||||
return StreamReader(path);
|
|
||||||
}
|
}
|
||||||
|
return StreamReader::create_from_file(path);
|
||||||
|
}
|
||||||
|
|
||||||
EXPECT(String) FileOps::ReadTextFile(IN CONST FilePath &path)
|
auto FileOps::read_text_file(const Path &path) -> Result<String> {
|
||||||
{
|
auto *f = fopen(path.string().c_str(), "r");
|
||||||
const auto f = fopen(path.string().c_str(), "r");
|
if (!f) {
|
||||||
if (!f)
|
return fail("Failed to open file: {}", path.string());
|
||||||
return MakeUnexpected(std::format("Failed to open file: {}", path.string().c_str()));
|
}
|
||||||
String result;
|
String result;
|
||||||
fseek(f, 0, SEEK_END);
|
fseek(f, 0, SEEK_END);
|
||||||
result.resize(ftell(f));
|
const long len = ftell(f);
|
||||||
|
if (len > 0) {
|
||||||
|
result.resize(static_cast<usize>(len));
|
||||||
fseek(f, 0, SEEK_SET);
|
fseek(f, 0, SEEK_SET);
|
||||||
fread(result.data(), 1, result.size(), f);
|
fread(result.data(), 1, result.size(), f);
|
||||||
|
}
|
||||||
fclose(f);
|
fclose(f);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT(Vector<UINT8>) FileOps::ReadBinaryFile(IN CONST FilePath &path)
|
auto FileOps::read_binary_file(const Path &path) -> Result<Vec<u8>> {
|
||||||
{
|
auto *f = fopen(path.string().c_str(), "rb");
|
||||||
const auto f = fopen(path.string().c_str(), "rb");
|
if (!f) {
|
||||||
if (!f)
|
return fail("Failed to open file: {}", path.string());
|
||||||
return MakeUnexpected(std::format("Failed to open file: {}", path.string().c_str()));
|
}
|
||||||
Vector<UINT8> result;
|
Vec<u8> result;
|
||||||
fseek(f, 0, SEEK_END);
|
fseek(f, 0, SEEK_END);
|
||||||
result.resize(ftell(f));
|
const long len = ftell(f);
|
||||||
|
if (len > 0) {
|
||||||
|
result.resize(static_cast<usize>(len));
|
||||||
fseek(f, 0, SEEK_SET);
|
fseek(f, 0, SEEK_SET);
|
||||||
fread(result.data(), 1, result.size(), f);
|
fread(result.data(), 1, result.size(), f);
|
||||||
|
}
|
||||||
fclose(f);
|
fclose(f);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT(SIZE_T) FileOps::WriteTextFile(IN CONST FilePath &path, IN CONST String &contents, IN BOOL overwrite)
|
auto FileOps::write_text_file(const Path &path, const String &contents,
|
||||||
{
|
bool overwrite) -> Result<usize> {
|
||||||
const char *mode = overwrite ? "w" : "wx";
|
const char *mode = overwrite ? "w" : "wx";
|
||||||
const auto f = fopen(path.string().c_str(), mode);
|
auto *f = fopen(path.string().c_str(), mode);
|
||||||
if (!f)
|
if (!f) {
|
||||||
{
|
if (!overwrite && errno == EEXIST) {
|
||||||
if (!overwrite && errno == EEXIST)
|
return fail("File already exists: {}", path.string());
|
||||||
return MakeUnexpected(std::format("File already exists: {}", path.string().c_str()));
|
}
|
||||||
return MakeUnexpected(std::format("Failed to write to file: {}", path.string().c_str()));
|
return fail("Failed to write to file: {}", path.string());
|
||||||
}
|
}
|
||||||
const auto result = fwrite(contents.data(), 1, contents.size(), f);
|
const auto result = fwrite(contents.data(), 1, contents.size(), f);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT(SIZE_T) FileOps::WriteBinaryFile(IN CONST FilePath &path, IN Span<UINT8> contents, IN BOOL overwrite)
|
auto FileOps::write_binary_file(const Path &path, Span<const u8> contents,
|
||||||
{
|
bool overwrite) -> Result<usize> {
|
||||||
const char *mode = overwrite ? "w" : "wx";
|
const char *mode = overwrite ? "w" : "wx";
|
||||||
const auto f = fopen(path.string().c_str(), mode);
|
auto *f = fopen(path.string().c_str(), mode);
|
||||||
if (!f)
|
if (!f) {
|
||||||
{
|
if (!overwrite && errno == EEXIST) {
|
||||||
if (!overwrite && errno == EEXIST)
|
return fail("File already exists: {}", path.string());
|
||||||
return MakeUnexpected(std::format("File already exists: {}", path.string().c_str()));
|
}
|
||||||
return MakeUnexpected(std::format("Failed to write to file: {}", path.string().c_str()));
|
return fail("Failed to write to file: {}", path.string());
|
||||||
}
|
}
|
||||||
const auto result = fwrite(contents.data(), 1, contents.size(), f);
|
const auto result = fwrite(contents.data(), 1, contents.size(), f);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
FilePath FileOps::NormalizeExecutablePath(IN CONST FilePath &path)
|
auto FileOps::normalize_executable_path(const Path &path) -> Path {
|
||||||
{
|
Path result = path;
|
||||||
FilePath result = path;
|
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
if (!result.has_extension())
|
if (!result.has_extension()) {
|
||||||
result.replace_extension(".exe");
|
result.replace_extension(".exe");
|
||||||
|
}
|
||||||
#elif IA_PLATFORM_UNIX
|
#elif IA_PLATFORM_UNIX
|
||||||
if (result.extension() == ".exe")
|
if (result.extension() == ".exe") {
|
||||||
result.replace_extension("");
|
result.replace_extension("");
|
||||||
|
}
|
||||||
|
|
||||||
if (result.is_relative())
|
if (result.is_relative()) {
|
||||||
{
|
String path_str = result.string();
|
||||||
String pathStr = result.string();
|
if (!path_str.starts_with("./") && !path_str.starts_with("../")) {
|
||||||
if (!pathStr.starts_with("./") && !pathStr.starts_with("../"))
|
result = "./" + path_str;
|
||||||
result = "./" + pathStr;
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
} // namespace IACore
|
|
||||||
|
|
||||||
namespace IACore
|
auto FileOps::native_open_file(const Path &path, FileAccess access,
|
||||||
{
|
FileMode mode, u32 permissions)
|
||||||
EXPECT(NativeFileHandle)
|
-> Result<NativeFileHandle> {
|
||||||
FileOps::NativeOpenFile(IN CONST FilePath &path, IN EFileAccess access, IN EFileMode mode, IN UINT32 permissions)
|
|
||||||
{
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
DWORD dwAccess = 0;
|
DWORD dw_access = 0;
|
||||||
DWORD dwShare = FILE_SHARE_READ;
|
DWORD dw_share = FILE_SHARE_READ;
|
||||||
DWORD dwDisposition = 0;
|
DWORD dw_disposition = 0;
|
||||||
DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
|
DWORD dw_flags_and_attributes = FILE_ATTRIBUTE_NORMAL;
|
||||||
|
|
||||||
switch (access)
|
switch (access) {
|
||||||
{
|
case FileAccess::Read:
|
||||||
case EFileAccess::READ:
|
dw_access = GENERIC_READ;
|
||||||
dwAccess = GENERIC_READ;
|
|
||||||
break;
|
break;
|
||||||
case EFileAccess::WRITE:
|
case FileAccess::Write:
|
||||||
dwAccess = GENERIC_WRITE;
|
dw_access = GENERIC_WRITE;
|
||||||
break;
|
break;
|
||||||
case EFileAccess::READ_WRITE:
|
case FileAccess::ReadWrite:
|
||||||
dwAccess = GENERIC_READ | GENERIC_WRITE;
|
dw_access = GENERIC_READ | GENERIC_WRITE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (mode)
|
switch (mode) {
|
||||||
{
|
case FileMode::OpenExisting:
|
||||||
case EFileMode::OPEN_EXISTING:
|
dw_disposition = OPEN_EXISTING;
|
||||||
dwDisposition = OPEN_EXISTING;
|
|
||||||
break;
|
break;
|
||||||
case EFileMode::OPEN_ALWAYS:
|
case FileMode::OpenAlways:
|
||||||
dwDisposition = OPEN_ALWAYS;
|
dw_disposition = OPEN_ALWAYS;
|
||||||
break;
|
break;
|
||||||
case EFileMode::CREATE_NEW:
|
case FileMode::CreateNew:
|
||||||
dwDisposition = CREATE_NEW;
|
dw_disposition = CREATE_NEW;
|
||||||
break;
|
break;
|
||||||
case EFileMode::CREATE_ALWAYS:
|
case FileMode::CreateAlways:
|
||||||
dwDisposition = CREATE_ALWAYS;
|
dw_disposition = CREATE_ALWAYS;
|
||||||
break;
|
break;
|
||||||
case EFileMode::TRUNCATE_EXISTING:
|
case FileMode::TruncateExisting:
|
||||||
dwDisposition = TRUNCATE_EXISTING;
|
dw_disposition = TRUNCATE_EXISTING;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
HANDLE hFile =
|
HANDLE h_file = CreateFileA(path.string().c_str(), dw_access, dw_share, NULL,
|
||||||
CreateFileA(path.string().c_str(), dwAccess, dwShare, NULL, dwDisposition, dwFlagsAndAttributes, NULL);
|
dw_disposition, dw_flags_and_attributes, NULL);
|
||||||
|
|
||||||
if (hFile == INVALID_HANDLE_VALUE)
|
if (h_file == INVALID_HANDLE_VALUE) {
|
||||||
return MakeUnexpected(std::format("Failed to open file '{}': {}", path.string(), GetLastError()));
|
return fail("Failed to open file '{}': {}", path.string(), GetLastError());
|
||||||
|
}
|
||||||
|
|
||||||
return hFile;
|
return h_file;
|
||||||
|
|
||||||
#elif IA_PLATFORM_UNIX
|
#elif IA_PLATFORM_UNIX
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
|
|
||||||
switch (access)
|
switch (access) {
|
||||||
{
|
case FileAccess::Read:
|
||||||
case EFileAccess::READ:
|
|
||||||
flags = O_RDONLY;
|
flags = O_RDONLY;
|
||||||
break;
|
break;
|
||||||
case EFileAccess::WRITE:
|
case FileAccess::Write:
|
||||||
flags = O_WRONLY;
|
flags = O_WRONLY;
|
||||||
break;
|
break;
|
||||||
case EFileAccess::READ_WRITE:
|
case FileAccess::ReadWrite:
|
||||||
flags = O_RDWR;
|
flags = O_RDWR;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (mode)
|
switch (mode) {
|
||||||
{
|
case FileMode::OpenExisting:
|
||||||
case EFileMode::OPEN_EXISTING:
|
|
||||||
break;
|
break;
|
||||||
case EFileMode::OPEN_ALWAYS:
|
case FileMode::OpenAlways:
|
||||||
flags |= O_CREAT;
|
flags |= O_CREAT;
|
||||||
break;
|
break;
|
||||||
case EFileMode::CREATE_NEW:
|
case FileMode::CreateNew:
|
||||||
flags |= O_CREAT | O_EXCL;
|
flags |= O_CREAT | O_EXCL;
|
||||||
break;
|
break;
|
||||||
case EFileMode::CREATE_ALWAYS:
|
case FileMode::CreateAlways:
|
||||||
flags |= O_CREAT | O_TRUNC;
|
flags |= O_CREAT | O_TRUNC;
|
||||||
break;
|
break;
|
||||||
case EFileMode::TRUNCATE_EXISTING:
|
case FileMode::TruncateExisting:
|
||||||
flags |= O_TRUNC;
|
flags |= O_TRUNC;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
int fd = open(path.string().c_str(), flags, permissions);
|
int fd = open(path.string().c_str(), flags, permissions);
|
||||||
|
|
||||||
if (fd == -1)
|
if (fd == -1) {
|
||||||
{
|
return fail("Failed to open file '{}': {}", path.string(), errno);
|
||||||
return MakeUnexpected(std::format("Failed to open file '{}': {}", path.string(), errno));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fd;
|
return fd;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID FileOps::NativeCloseFile(IN NativeFileHandle handle)
|
auto FileOps::native_close_file(NativeFileHandle handle) -> void {
|
||||||
{
|
if (handle == INVALID_FILE_HANDLE) {
|
||||||
if (handle == INVALID_FILE_HANDLE)
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
CloseHandle(handle);
|
CloseHandle(handle);
|
||||||
#elif IA_PLATFORM_UNIX
|
#elif IA_PLATFORM_UNIX
|
||||||
close(handle);
|
close(handle);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
FileOps::MemoryMappedRegion::~MemoryMappedRegion()
|
// =============================================================================
|
||||||
{
|
// MemoryMappedRegion
|
||||||
Unmap();
|
// =============================================================================
|
||||||
}
|
|
||||||
|
|
||||||
FileOps::MemoryMappedRegion::MemoryMappedRegion(MemoryMappedRegion &&other) NOEXCEPT
|
FileOps::MemoryMappedRegion::~MemoryMappedRegion() { unmap(); }
|
||||||
{
|
|
||||||
|
FileOps::MemoryMappedRegion::MemoryMappedRegion(
|
||||||
|
MemoryMappedRegion &&other) noexcept {
|
||||||
*this = std::move(other);
|
*this = std::move(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
FileOps::MemoryMappedRegion &FileOps::MemoryMappedRegion::operator=(MemoryMappedRegion &&other) NOEXCEPT
|
auto FileOps::MemoryMappedRegion::operator=(MemoryMappedRegion &&other) noexcept
|
||||||
{
|
-> MemoryMappedRegion & {
|
||||||
if (this != &other)
|
if (this != &other) {
|
||||||
{
|
unmap();
|
||||||
Unmap();
|
|
||||||
m_ptr = other.m_ptr;
|
m_ptr = other.m_ptr;
|
||||||
m_size = other.m_size;
|
m_size = other.m_size;
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
m_hMap = other.m_hMap;
|
m_map_handle = other.m_map_handle;
|
||||||
other.m_hMap = NULL;
|
other.m_map_handle = NULL;
|
||||||
#endif
|
#endif
|
||||||
other.m_ptr = nullptr;
|
other.m_ptr = nullptr;
|
||||||
other.m_size = 0;
|
other.m_size = 0;
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT(VOID) FileOps::MemoryMappedRegion::Map(NativeFileHandle handle, UINT64 offset, SIZE_T size)
|
if (size == 0) {
|
||||||
{
|
return fail("Cannot map region of size 0");
|
||||||
Unmap();
|
}
|
||||||
|
|
||||||
if (handle == INVALID_FILE_HANDLE)
|
|
||||||
return MakeUnexpected("Invalid file handle provided to Map");
|
|
||||||
|
|
||||||
if (size == 0)
|
|
||||||
return MakeUnexpected("Cannot map region of size 0");
|
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
LARGE_INTEGER fileSize;
|
LARGE_INTEGER file_size;
|
||||||
if (!GetFileSizeEx(handle, &fileSize))
|
if (!GetFileSizeEx(handle, &file_size)) {
|
||||||
return MakeUnexpected("Failed to get file size");
|
return fail("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);
|
u64 end_offset = offset + size;
|
||||||
if (m_hMap == NULL)
|
if (static_cast<u64>(file_size.QuadPart) < end_offset) {
|
||||||
return MakeUnexpected(std::format("CreateFileMapping failed: {}", GetLastError()));
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
DWORD offsetHigh = static_cast<DWORD>(offset >> 32);
|
if (!SetEndOfFile(handle)) {
|
||||||
DWORD offsetLow = static_cast<DWORD>(offset & 0xFFFFFFFF);
|
return fail("Failed to extend file for mapping");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m_ptr = static_cast<PUINT8>(MapViewOfFile(m_hMap, FILE_MAP_WRITE, offsetHigh, offsetLow, size));
|
m_map_handle = CreateFileMappingW(handle, NULL, PAGE_READWRITE, 0, 0, NULL);
|
||||||
if (m_ptr == NULL)
|
if (m_map_handle == NULL) {
|
||||||
{
|
return fail("CreateFileMapping failed: {}", GetLastError());
|
||||||
CloseHandle(m_hMap);
|
}
|
||||||
m_hMap = NULL;
|
|
||||||
return MakeUnexpected(
|
DWORD offset_high = static_cast<DWORD>(offset >> 32);
|
||||||
std::format("MapViewOfFile failed (Offset: {}, Size: {}): {}", offset, size, GetLastError()));
|
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;
|
m_size = size;
|
||||||
|
|
||||||
#elif IA_PLATFORM_UNIX
|
#elif IA_PLATFORM_UNIX
|
||||||
struct stat sb;
|
struct stat sb;
|
||||||
if (fstat(handle, &sb) == -1)
|
if (fstat(handle, &sb) == -1) {
|
||||||
return MakeUnexpected("Failed to fstat file");
|
return fail("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));
|
u64 end_offset = offset + size;
|
||||||
if (ptr == MAP_FAILED)
|
if (static_cast<u64>(sb.st_size) < end_offset) {
|
||||||
return MakeUnexpected(std::format("mmap failed: {}", errno));
|
if (ftruncate(handle, static_cast<off_t>(end_offset)) == -1) {
|
||||||
|
return fail("Failed to ftruncate (extend) file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m_ptr = static_cast<PUINT8>(ptr);
|
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;
|
m_size = size;
|
||||||
|
|
||||||
madvise(m_ptr, m_size, MADV_SEQUENTIAL);
|
madvise(m_ptr, m_size, MADV_SEQUENTIAL);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID FileOps::MemoryMappedRegion::Unmap()
|
auto FileOps::MemoryMappedRegion::unmap() -> void {
|
||||||
{
|
if (!m_ptr) {
|
||||||
if (!m_ptr)
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
UnmapViewOfFile(m_ptr);
|
UnmapViewOfFile(m_ptr);
|
||||||
if (m_hMap)
|
if (m_map_handle) {
|
||||||
{
|
CloseHandle(m_map_handle);
|
||||||
CloseHandle(m_hMap);
|
m_map_handle = NULL;
|
||||||
m_hMap = NULL;
|
|
||||||
}
|
}
|
||||||
#elif IA_PLATFORM_UNIX
|
#elif IA_PLATFORM_UNIX
|
||||||
munmap(m_ptr, m_size);
|
munmap(m_ptr, m_size);
|
||||||
#endif
|
#endif
|
||||||
m_ptr = nullptr;
|
m_ptr = nullptr;
|
||||||
m_size = 0;
|
m_size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID FileOps::MemoryMappedRegion::Flush()
|
auto FileOps::MemoryMappedRegion::flush() -> void {
|
||||||
{
|
if (!m_ptr) {
|
||||||
if (!m_ptr)
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
FlushViewOfFile(m_ptr, m_size);
|
FlushViewOfFile(m_ptr, m_size);
|
||||||
#elif IA_PLATFORM_UNIX
|
#elif IA_PLATFORM_UNIX
|
||||||
msync(m_ptr, m_size, MS_SYNC);
|
msync(m_ptr, m_size, MS_SYNC);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -13,72 +13,65 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
#include <IACore/Http/Client.hpp>
|
|
||||||
#include <IACore/DataOps.hpp>
|
#include <IACore/DataOps.hpp>
|
||||||
|
#include <IACore/Http/Client.hpp>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
Result<UniquePtr<HttpClient>> HttpClient::Create(const String &host) {
|
||||||
EXPECT(UniquePtr<HttpClient>) HttpClient::Create(IN CONST String &host)
|
|
||||||
{
|
|
||||||
return MakeUniqueProtected<HttpClient>(httplib::Client(host));
|
return MakeUniqueProtected<HttpClient>(httplib::Client(host));
|
||||||
}
|
}
|
||||||
|
|
||||||
httplib::Headers BuildHeaders(IN Span<CONST HttpClient::Header> headers, IN PCCHAR defaultContentType)
|
httplib::Headers BuildHeaders(Span<const HttpClient::Header> headers,
|
||||||
{
|
const char *defaultContentType) {
|
||||||
httplib::Headers out;
|
httplib::Headers out;
|
||||||
bool hasContentType = false;
|
bool hasContentType = false;
|
||||||
|
|
||||||
for (const auto &h : headers)
|
for (const auto &h : headers) {
|
||||||
{
|
|
||||||
out.emplace(h.first, h.second);
|
out.emplace(h.first, h.second);
|
||||||
|
|
||||||
if (h.first == HttpClient::HeaderTypeToString(HttpClient::EHeaderType::CONTENT_TYPE))
|
if (h.first ==
|
||||||
|
HttpClient::HeaderTypeToString(HttpClient::EHeaderType::CONTENT_TYPE))
|
||||||
hasContentType = true;
|
hasContentType = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasContentType && defaultContentType)
|
if (!hasContentType && defaultContentType)
|
||||||
out.emplace("Content-Type", defaultContentType);
|
out.emplace("Content-Type", defaultContentType);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpClient::HttpClient(IN httplib::Client &&client)
|
HttpClient::HttpClient(httplib::Client &&client)
|
||||||
: m_client(IA_MOVE(client)), m_lastResponseCode(EResponseCode::INTERNAL_SERVER_ERROR)
|
: m_client(std::move(client)),
|
||||||
{
|
m_lastResponseCode(EResponseCode::INTERNAL_SERVER_ERROR) {
|
||||||
m_client.enable_server_certificate_verification(true);
|
m_client.enable_server_certificate_verification(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpClient::~HttpClient()
|
HttpClient::~HttpClient() {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
VOID HttpClient::EnableCertificateVerfication()
|
void HttpClient::EnableCertificateVerfication() {
|
||||||
{
|
|
||||||
m_client.enable_server_certificate_verification(true);
|
m_client.enable_server_certificate_verification(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID HttpClient::DisableCertificateVerfication()
|
void HttpClient::DisableCertificateVerfication() {
|
||||||
{
|
|
||||||
m_client.enable_server_certificate_verification(false);
|
m_client.enable_server_certificate_verification(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
String HttpClient::PreprocessResponse(IN CONST String &response)
|
String HttpClient::PreprocessResponse(const String &response) {
|
||||||
{
|
const auto responseBytes =
|
||||||
const auto responseBytes = Span<CONST UINT8>{(PCUINT8) response.data(), response.size()};
|
Span<const u8>{(const u8 *)response.data(), response.size()};
|
||||||
const auto compression = DataOps::DetectCompression(responseBytes);
|
const auto compression = DataOps::DetectCompression(responseBytes);
|
||||||
switch (compression)
|
switch (compression) {
|
||||||
{
|
|
||||||
case DataOps::CompressionType::Gzip: {
|
case DataOps::CompressionType::Gzip: {
|
||||||
const auto data = DataOps::GZipInflate(responseBytes);
|
const auto data = DataOps::GZipInflate(responseBytes);
|
||||||
if (!data)
|
if (!data)
|
||||||
return response;
|
return response;
|
||||||
return String((PCCHAR) data->data(), data->size());
|
return String((const char *)data->data(), data->size());
|
||||||
}
|
}
|
||||||
|
|
||||||
case DataOps::CompressionType::Zlib: {
|
case DataOps::CompressionType::Zlib: {
|
||||||
const auto data = DataOps::ZlibInflate(responseBytes);
|
const auto data = DataOps::ZlibInflate(responseBytes);
|
||||||
if (!data)
|
if (!data)
|
||||||
return response;
|
return response;
|
||||||
return String((PCCHAR) data->data(), data->size());
|
return String((const char *)data->data(), data->size());
|
||||||
}
|
}
|
||||||
|
|
||||||
case DataOps::CompressionType::None:
|
case DataOps::CompressionType::None:
|
||||||
@ -86,54 +79,55 @@ namespace IACore
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT(String)
|
Result<String>
|
||||||
HttpClient::RawGet(IN CONST String &path, IN Span<CONST Header> headers, IN PCCHAR defaultContentType)
|
|
||||||
{
|
HttpClient::RawGet(const String &path, Span<const Header> headers,
|
||||||
|
const char *defaultContentType) {
|
||||||
auto httpHeaders = BuildHeaders(headers, defaultContentType);
|
auto httpHeaders = BuildHeaders(headers, defaultContentType);
|
||||||
|
|
||||||
auto res = m_client.Get((!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(), httpHeaders);
|
auto res = m_client.Get(
|
||||||
|
(!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(),
|
||||||
|
httpHeaders);
|
||||||
|
|
||||||
if (res)
|
if (res) {
|
||||||
{
|
|
||||||
m_lastResponseCode = static_cast<EResponseCode>(res->status);
|
m_lastResponseCode = static_cast<EResponseCode>(res->status);
|
||||||
if (res->status >= 200 && res->status < 300)
|
if (res->status >= 200 && res->status < 300)
|
||||||
return PreprocessResponse(res->body);
|
return PreprocessResponse(res->body);
|
||||||
else
|
else
|
||||||
return MakeUnexpected(std::format("HTTP Error {} : {}", res->status, res->body));
|
return (std::format("HTTP Error {} : {}", res->status, res->body));
|
||||||
}
|
}
|
||||||
|
|
||||||
return MakeUnexpected(std::format("Network Error: {}", httplib::to_string(res.error())));
|
return (std::format("Network Error: {}", httplib::to_string(res.error())));
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT(String)
|
Result<String>
|
||||||
HttpClient::RawPost(IN CONST String &path, IN Span<CONST Header> headers, IN CONST String &body,
|
|
||||||
IN PCCHAR defaultContentType)
|
HttpClient::RawPost(const String &path, Span<const Header> headers,
|
||||||
{
|
const String &body, const char *defaultContentType) {
|
||||||
auto httpHeaders = BuildHeaders(headers, defaultContentType);
|
auto httpHeaders = BuildHeaders(headers, defaultContentType);
|
||||||
|
|
||||||
String contentType = defaultContentType;
|
String contentType = defaultContentType;
|
||||||
if (httpHeaders.count("Content-Type"))
|
if (httpHeaders.count("Content-Type")) {
|
||||||
{
|
|
||||||
const auto t = httpHeaders.find("Content-Type");
|
const auto t = httpHeaders.find("Content-Type");
|
||||||
contentType = t->second;
|
contentType = t->second;
|
||||||
httpHeaders.erase(t);
|
httpHeaders.erase(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_client.set_keep_alive(true);
|
m_client.set_keep_alive(true);
|
||||||
auto res = m_client.Post((!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(), httpHeaders,
|
auto res = m_client.Post(
|
||||||
body, contentType.c_str());
|
(!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(),
|
||||||
|
httpHeaders, body, contentType.c_str());
|
||||||
|
|
||||||
if (res)
|
if (res) {
|
||||||
{
|
|
||||||
m_lastResponseCode = static_cast<EResponseCode>(res->status);
|
m_lastResponseCode = static_cast<EResponseCode>(res->status);
|
||||||
if (res->status >= 200 && res->status < 300)
|
if (res->status >= 200 && res->status < 300)
|
||||||
return PreprocessResponse(res->body);
|
return PreprocessResponse(res->body);
|
||||||
else
|
else
|
||||||
return MakeUnexpected(std::format("HTTP Error {} : {}", res->status, res->body));
|
return (std::format("HTTP Error {} : {}", res->status, res->body));
|
||||||
}
|
}
|
||||||
|
|
||||||
return MakeUnexpected(std::format("Network Error: {}", httplib::to_string(res.error())));
|
return (std::format("Network Error: {}", httplib::to_string(res.error())));
|
||||||
}
|
}
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
|
|||||||
@ -15,52 +15,46 @@
|
|||||||
|
|
||||||
#include <IACore/Http/Common.hpp>
|
#include <IACore/Http/Common.hpp>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
String HttpCommon::UrlEncode(const String &value) {
|
||||||
String HttpCommon::UrlEncode(IN CONST String &value)
|
|
||||||
{
|
|
||||||
std::stringstream escaped;
|
std::stringstream escaped;
|
||||||
escaped.fill('0');
|
escaped.fill('0');
|
||||||
escaped << std::hex << std::uppercase;
|
escaped << std::hex << std::uppercase;
|
||||||
|
|
||||||
for (char c : value)
|
for (char c : value) {
|
||||||
{
|
if (std::isalnum(static_cast<unsigned char>(c)) || c == '-' || c == '_' ||
|
||||||
if (std::isalnum(static_cast<unsigned char>(c)) || c == '-' || c == '_' || c == '.' || c == '~')
|
c == '.' || c == '~')
|
||||||
escaped << c;
|
escaped << c;
|
||||||
else
|
else
|
||||||
escaped << '%' << std::setw(2) << static_cast<int>(static_cast<unsigned char>(c));
|
escaped << '%' << std::setw(2)
|
||||||
|
<< static_cast<int>(static_cast<unsigned char>(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
return escaped.str();
|
return escaped.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
String HttpCommon::UrlDecode(IN CONST String &value)
|
String HttpCommon::UrlDecode(const String &value) {
|
||||||
{
|
|
||||||
String result;
|
String result;
|
||||||
result.reserve(value.length());
|
result.reserve(value.length());
|
||||||
|
|
||||||
for (size_t i = 0; i < value.length(); ++i)
|
for (size_t i = 0; i < value.length(); ++i) {
|
||||||
{
|
if (value[i] == '%' && i + 2 < value.length()) {
|
||||||
if (value[i] == '%' && i + 2 < value.length())
|
|
||||||
{
|
|
||||||
std::string hexStr = value.substr(i + 1, 2);
|
std::string hexStr = value.substr(i + 1, 2);
|
||||||
char decodedChar = static_cast<char>(std::strtol(hexStr.c_str(), nullptr, 16));
|
char decodedChar =
|
||||||
|
static_cast<char>(std::strtol(hexStr.c_str(), nullptr, 16));
|
||||||
result += decodedChar;
|
result += decodedChar;
|
||||||
i += 2;
|
i += 2;
|
||||||
}
|
} else if (value[i] == '+')
|
||||||
else if (value[i] == '+')
|
|
||||||
result += ' ';
|
result += ' ';
|
||||||
else
|
else
|
||||||
result += value[i];
|
result += value[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
String HttpCommon::HeaderTypeToString(IN EHeaderType type)
|
String HttpCommon::HeaderTypeToString(EHeaderType type) {
|
||||||
{
|
switch (type) {
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case EHeaderType::ACCEPT:
|
case EHeaderType::ACCEPT:
|
||||||
return "Accept";
|
return "Accept";
|
||||||
case EHeaderType::ACCEPT_CHARSET:
|
case EHeaderType::ACCEPT_CHARSET:
|
||||||
@ -115,10 +109,9 @@ namespace IACore
|
|||||||
return "Warning";
|
return "Warning";
|
||||||
}
|
}
|
||||||
return "<Unknown>";
|
return "<Unknown>";
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL HttpCommon::IsSuccessResponseCode(IN EResponseCode code)
|
bool HttpCommon::IsSuccessResponseCode(EResponseCode code) {
|
||||||
{
|
return (i32)code >= 200 && (i32)code < 300;
|
||||||
return (INT32) code >= 200 && (INT32) code < 300;
|
}
|
||||||
}
|
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -17,76 +17,50 @@
|
|||||||
#include <IACore/Logger.hpp>
|
#include <IACore/Logger.hpp>
|
||||||
|
|
||||||
#include <mimalloc.h>
|
#include <mimalloc.h>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore
|
||||||
{
|
{
|
||||||
HighResTimePoint g_startTime{};
|
auto g_start_time = std::chrono::high_resolution_clock::time_point{};
|
||||||
std::thread::id g_mainThreadID{};
|
|
||||||
INT32 g_coreInitCount{};
|
|
||||||
|
|
||||||
VOID Initialize()
|
static auto g_main_thread_id = std::thread::id{};
|
||||||
|
static auto g_core_init_count = i32{0};
|
||||||
|
|
||||||
|
auto initialize() -> void
|
||||||
|
{
|
||||||
|
g_core_init_count++;
|
||||||
|
if (g_core_init_count > 1)
|
||||||
{
|
{
|
||||||
g_coreInitCount++;
|
|
||||||
if (g_coreInitCount > 1)
|
|
||||||
return;
|
return;
|
||||||
g_mainThreadID = std::this_thread::get_id();
|
}
|
||||||
g_startTime = HighResClock::now();
|
|
||||||
Logger::Initialize();
|
g_main_thread_id = std::this_thread::get_id();
|
||||||
|
g_start_time = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
Logger::initialize();
|
||||||
|
|
||||||
mi_option_set(mi_option_verbose, 0);
|
mi_option_set(mi_option_verbose, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID Terminate()
|
auto terminate() -> void
|
||||||
|
{
|
||||||
|
g_core_init_count--;
|
||||||
|
if (g_core_init_count > 0)
|
||||||
{
|
{
|
||||||
g_coreInitCount--;
|
|
||||||
if (g_coreInitCount > 0)
|
|
||||||
return;
|
return;
|
||||||
Logger::Terminate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL IsInitialized()
|
Logger::terminate();
|
||||||
{
|
|
||||||
return g_coreInitCount > 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UINT64 GetUnixTime()
|
auto is_initialized() -> bool
|
||||||
{
|
{
|
||||||
auto now = std::chrono::system_clock::now();
|
return g_core_init_count > 0;
|
||||||
return std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UINT64 GetTicksCount()
|
auto is_main_thread() -> bool
|
||||||
{
|
{
|
||||||
return std::chrono::duration_cast<std::chrono::milliseconds>(HighResClock::now() - g_startTime).count();
|
return std::this_thread::get_id() == g_main_thread_id;
|
||||||
}
|
|
||||||
|
|
||||||
FLOAT64 GetSecondsCount()
|
|
||||||
{
|
|
||||||
return std::chrono::duration_cast<std::chrono::seconds>(HighResClock::now() - g_startTime).count();
|
|
||||||
}
|
|
||||||
|
|
||||||
FLOAT32 GetRandom()
|
|
||||||
{
|
|
||||||
return static_cast<FLOAT32>(rand()) / static_cast<FLOAT32>(RAND_MAX);
|
|
||||||
}
|
|
||||||
|
|
||||||
UINT64 GetRandom(IN UINT64 max)
|
|
||||||
{
|
|
||||||
return max * GetRandom();
|
|
||||||
}
|
|
||||||
|
|
||||||
INT64 GetRandom(IN INT64 min, IN INT64 max)
|
|
||||||
{
|
|
||||||
return min + (max - min) * GetRandom();
|
|
||||||
}
|
|
||||||
|
|
||||||
VOID Sleep(IN UINT64 milliseconds)
|
|
||||||
{
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
|
|
||||||
}
|
|
||||||
|
|
||||||
BOOL IsMainThread()
|
|
||||||
{
|
|
||||||
return std::this_thread::get_id() == g_mainThreadID;
|
|
||||||
}
|
}
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -17,103 +17,110 @@
|
|||||||
|
|
||||||
#include <IACore/FileOps.hpp>
|
#include <IACore/FileOps.hpp>
|
||||||
#include <IACore/StringOps.hpp>
|
#include <IACore/StringOps.hpp>
|
||||||
|
#include <charconv>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
// =============================================================================
|
||||||
struct IPC_ConnectionDescriptor
|
// Internal: Connection Descriptor
|
||||||
{
|
// =============================================================================
|
||||||
String SocketPath;
|
struct IpcConnectionDescriptor {
|
||||||
String SharedMemPath;
|
String socket_path;
|
||||||
UINT32 SharedMemSize;
|
String shared_mem_path;
|
||||||
|
u32 shared_mem_size;
|
||||||
|
|
||||||
String Serialize() CONST
|
[[nodiscard]] auto serialize() const -> String {
|
||||||
{
|
return std::format("{}|{}|{}|", socket_path, shared_mem_path,
|
||||||
return std::format("{}|{}|{}|", SocketPath, SharedMemPath, SharedMemSize);
|
shared_mem_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC IPC_ConnectionDescriptor Deserialize(IN CONST String &data)
|
static auto deserialize(StringView data) -> Option<IpcConnectionDescriptor> {
|
||||||
{
|
enum class ParseState { SocketPath, SharedMemPath, SharedMemSize };
|
||||||
enum class EParseState
|
|
||||||
{
|
|
||||||
SocketPath,
|
|
||||||
SharedMemPath,
|
|
||||||
SharedMemSize
|
|
||||||
};
|
|
||||||
|
|
||||||
IPC_ConnectionDescriptor result{};
|
IpcConnectionDescriptor result{};
|
||||||
|
usize t = 0;
|
||||||
|
auto state = ParseState::SocketPath;
|
||||||
|
|
||||||
SIZE_T t{};
|
for (usize i = 0; i < data.size(); ++i) {
|
||||||
EParseState state{EParseState::SocketPath};
|
if (data[i] != '|') {
|
||||||
for (SIZE_T i = 0; i < data.size(); i++)
|
|
||||||
{
|
|
||||||
if (data[i] != '|')
|
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
switch (state)
|
switch (state) {
|
||||||
{
|
case ParseState::SocketPath:
|
||||||
case EParseState::SocketPath:
|
result.socket_path = String(data.substr(t, i - t));
|
||||||
result.SocketPath = data.substr(t, i - t);
|
state = ParseState::SharedMemPath;
|
||||||
state = EParseState::SharedMemPath;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EParseState::SharedMemPath:
|
case ParseState::SharedMemPath:
|
||||||
result.SharedMemPath = data.substr(t, i - t);
|
result.shared_mem_path = String(data.substr(t, i - t));
|
||||||
state = EParseState::SharedMemSize;
|
state = ParseState::SharedMemSize;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EParseState::SharedMemSize: {
|
case ParseState::SharedMemSize: {
|
||||||
if (std::from_chars(&data[t], &data[i], result.SharedMemSize).ec != std::errc{})
|
const auto *start = data.data() + t;
|
||||||
return {};
|
const auto *end = data.data() + i;
|
||||||
goto done_parsing;
|
if (std::from_chars(start, end, result.shared_mem_size).ec !=
|
||||||
|
std::errc{}) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t = i + 1;
|
t = i + 1;
|
||||||
}
|
}
|
||||||
|
return std::nullopt;
|
||||||
done_parsing:
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} // namespace IACore
|
|
||||||
|
|
||||||
namespace IACore
|
// =============================================================================
|
||||||
{
|
// IpcNode Implementation
|
||||||
IPC_Node::~IPC_Node()
|
// =============================================================================
|
||||||
|
|
||||||
|
IpcNode::~IpcNode() {
|
||||||
|
if (m_socket != INVALID_SOCKET) {
|
||||||
|
SocketOps::close(m_socket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
IA_TRY(m_socket, SocketOps::create_unix_socket());
|
||||||
|
IA_TRY_PURE(
|
||||||
|
SocketOps::connect_unix_socket(m_socket, desc.socket_path.c_str()));
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
auto *layout = reinterpret_cast<IpcSharedMemoryLayout *>(m_shared_memory);
|
||||||
|
|
||||||
|
if (layout->meta.magic != 0x49414950) // "IAIP"
|
||||||
{
|
{
|
||||||
SocketOps::Close(m_socket); // SocketOps gracefully handles INVALID_SOCKET
|
return fail("Invalid shared memory header signature");
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT(VOID) IPC_Node::Connect(IN PCCHAR connectionString)
|
if (layout->meta.version != 1) {
|
||||||
{
|
return fail("IPC version mismatch");
|
||||||
auto desc = IPC_ConnectionDescriptor::Deserialize(connectionString);
|
}
|
||||||
m_shmName = desc.SharedMemPath;
|
|
||||||
|
|
||||||
m_socket = SocketOps::CreateUnixSocket();
|
u8 *moni_ptr = m_shared_memory + layout->moni_data_offset;
|
||||||
if (!SocketOps::ConnectUnixSocket(m_socket, desc.SocketPath.c_str()))
|
u8 *mino_ptr = m_shared_memory + layout->mino_data_offset;
|
||||||
return MakeUnexpected("Failed to create an unix socket");
|
|
||||||
|
|
||||||
auto mapRes = FileOps::MapSharedMemory(desc.SharedMemPath, desc.SharedMemSize, FALSE);
|
m_moni = make_box<RingBufferView>(
|
||||||
if (!mapRes.has_value())
|
&layout->moni_control,
|
||||||
return MakeUnexpected("Failed to map the shared memory");
|
Span<u8>(moni_ptr, static_cast<usize>(layout->moni_data_size)), false);
|
||||||
|
|
||||||
m_sharedMemory = mapRes.value();
|
m_mino = make_box<RingBufferView>(
|
||||||
|
&layout->mino_control,
|
||||||
auto *layout = reinterpret_cast<IPC_SharedMemoryLayout *>(m_sharedMemory);
|
Span<u8>(mino_ptr, static_cast<usize>(layout->mino_data_size)), false);
|
||||||
|
|
||||||
if (layout->Meta.Magic != 0x49414950) // "IAIP"
|
|
||||||
return MakeUnexpected("Invalid shared memory header signature");
|
|
||||||
|
|
||||||
if (layout->Meta.Version != 1)
|
|
||||||
return MakeUnexpected("IPC version mismatch");
|
|
||||||
|
|
||||||
PUINT8 moniDataPtr = m_sharedMemory + layout->MONI_DataOffset;
|
|
||||||
PUINT8 minoDataPtr = m_sharedMemory + layout->MINO_DataOffset;
|
|
||||||
|
|
||||||
MONI = std::make_unique<RingBufferView>(
|
|
||||||
&layout->MONI_Control, Span<UINT8>(moniDataPtr, static_cast<size_t>(layout->MONI_DataSize)), FALSE);
|
|
||||||
|
|
||||||
MINO = std::make_unique<RingBufferView>(
|
|
||||||
&layout->MINO_Control, Span<UINT8>(minoDataPtr, static_cast<size_t>(layout->MINO_DataSize)), FALSE);
|
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
u_long mode = 1;
|
u_long mode = 1;
|
||||||
@ -122,330 +129,329 @@ namespace IACore
|
|||||||
fcntl(m_socket, F_SETFL, O_NONBLOCK);
|
fcntl(m_socket, F_SETFL, O_NONBLOCK);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
m_receiveBuffer.resize(UINT16_MAX + 1);
|
m_receive_buffer.resize(UINT16_MAX + 1);
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void IpcNode::update() {
|
||||||
|
if (!m_moni) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID IPC_Node::Update()
|
IpcPacketHeader header;
|
||||||
{
|
|
||||||
if (!MONI)
|
|
||||||
return;
|
|
||||||
|
|
||||||
RingBufferView::PacketHeader header;
|
|
||||||
|
|
||||||
// Process all available messages from Manager
|
// Process all available messages from Manager
|
||||||
while (MONI->Pop(header, Span<UINT8>(m_receiveBuffer.data(), m_receiveBuffer.size())))
|
while (m_moni->pop(
|
||||||
OnPacket(header.ID, {m_receiveBuffer.data(), header.PayloadSize});
|
header, Span<u8>(m_receive_buffer.data(), m_receive_buffer.size()))) {
|
||||||
|
on_packet(header.id, {m_receive_buffer.data(), header.payload_size});
|
||||||
|
}
|
||||||
|
|
||||||
UINT8 signal;
|
u8 signal = 0;
|
||||||
const auto res = recv(m_socket, (CHAR *) &signal, 1, 0);
|
const auto res = recv(m_socket, reinterpret_cast<char *>(&signal), 1, 0);
|
||||||
if (res == 1)
|
if (res == 1) {
|
||||||
OnSignal(signal);
|
on_signal(signal);
|
||||||
else if (res == 0 || (res < 0 && !SocketOps::IsWouldBlock()))
|
} else if (res == 0 || (res < 0 && !SocketOps::is_would_block())) {
|
||||||
{
|
SocketOps::close(m_socket);
|
||||||
SocketOps::Close(m_socket);
|
FileOps::unlink_shared_memory(m_shm_name);
|
||||||
FileOps::UnlinkSharedMemory(m_shmName);
|
|
||||||
|
|
||||||
// Manager disconnected, exit immediately
|
// Manager disconnected, exit immediately
|
||||||
exit(-1);
|
std::exit(-1);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VOID IPC_Node::SendSignal(IN UINT8 signal)
|
void IpcNode::send_signal(u8 signal) {
|
||||||
{
|
if (m_socket != INVALID_SOCKET) {
|
||||||
if (IS_VALID_SOCKET(m_socket))
|
send(m_socket, reinterpret_cast<const char *>(&signal), sizeof(signal), 0);
|
||||||
send(m_socket, (const char *) &signal, sizeof(signal), 0);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VOID IPC_Node::SendPacket(IN UINT16 packetID, IN Span<CONST UINT8> payload)
|
void IpcNode::send_packet(u16 packet_id, Span<const u8> payload) {
|
||||||
{
|
if (m_mino) {
|
||||||
MINO->Push(packetID, payload);
|
m_mino->push(packet_id, payload);
|
||||||
}
|
}
|
||||||
} // namespace IACore
|
}
|
||||||
|
|
||||||
namespace IACore
|
// =============================================================================
|
||||||
{
|
// IpcManager Implementation
|
||||||
VOID IPC_Manager::NodeSession::SendSignal(IN UINT8 signal)
|
// =============================================================================
|
||||||
{
|
|
||||||
if (IS_VALID_SOCKET(DataSocket))
|
void IpcManager::NodeSession::send_signal(u8 signal) {
|
||||||
send(DataSocket, (const char *) &signal, sizeof(signal), 0);
|
if (data_socket != INVALID_SOCKET) {
|
||||||
|
send(data_socket, reinterpret_cast<const char *>(&signal), sizeof(signal),
|
||||||
|
0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VOID IPC_Manager::NodeSession::SendPacket(IN UINT16 packetID, IN Span<CONST UINT8> payload)
|
void IpcManager::NodeSession::send_packet(u16 packet_id,
|
||||||
{
|
Span<const u8> payload) {
|
||||||
// Protect the RingBuffer write cursor from concurrent threads
|
// Protect the RingBuffer write cursor from concurrent threads
|
||||||
ScopedLock lock(SendMutex);
|
std::scoped_lock lock(send_mutex);
|
||||||
MONI->Push(packetID, payload);
|
if (moni) {
|
||||||
|
moni->push(packet_id, payload);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
IPC_Manager::IPC_Manager()
|
IpcManager::IpcManager() {
|
||||||
{
|
|
||||||
// SocketOps is smart enough to track multiple inits
|
// SocketOps is smart enough to track multiple inits
|
||||||
SocketOps::Initialize();
|
SocketOps::initialize();
|
||||||
|
m_receive_buffer.resize(UINT16_MAX + 1);
|
||||||
|
}
|
||||||
|
|
||||||
m_receiveBuffer.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();
|
||||||
|
|
||||||
IPC_Manager::~IPC_Manager()
|
for (auto &session : m_pending_sessions) {
|
||||||
{
|
ProcessOps::terminate_process(session->node_process);
|
||||||
for (auto &session : m_activeSessions)
|
FileOps::unmap_file(session->mapped_ptr);
|
||||||
{
|
FileOps::unlink_shared_memory(session->shared_mem_name);
|
||||||
ProcessOps::TerminateProcess(session->NodeProcess);
|
SocketOps::close(session->listener_socket);
|
||||||
FileOps::UnmapFile(session->MappedPtr);
|
|
||||||
FileOps::UnlinkSharedMemory(session->SharedMemName);
|
|
||||||
SocketOps::Close(session->DataSocket);
|
|
||||||
}
|
}
|
||||||
m_activeSessions.clear();
|
m_pending_sessions.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 is smart enough to track multiple terminates
|
||||||
SocketOps::Terminate();
|
SocketOps::terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID IPC_Manager::Update()
|
void IpcManager::update() {
|
||||||
{
|
const auto now = std::chrono::system_clock::now();
|
||||||
const auto now = SteadyClock::now();
|
|
||||||
|
|
||||||
for (INT32 i = m_pendingSessions.size() - 1; i >= 0; i--)
|
for (isize i = static_cast<isize>(m_pending_sessions.size()) - 1; i >= 0;
|
||||||
{
|
--i) {
|
||||||
auto &session = m_pendingSessions[i];
|
auto &session = m_pending_sessions[static_cast<usize>(i)];
|
||||||
|
|
||||||
if (now - session->CreationTime > std::chrono::seconds(5))
|
if (now - session->creation_time > std::chrono::seconds(5)) {
|
||||||
{
|
ProcessOps::terminate_process(session->node_process);
|
||||||
ProcessOps::TerminateProcess(session->NodeProcess);
|
|
||||||
|
|
||||||
FileOps::UnmapFile(session->MappedPtr);
|
FileOps::unmap_file(session->mapped_ptr);
|
||||||
FileOps::UnlinkSharedMemory(session->SharedMemName);
|
FileOps::unlink_shared_memory(session->shared_mem_name);
|
||||||
SocketOps::Close(session->DataSocket);
|
SocketOps::close(session->listener_socket);
|
||||||
|
|
||||||
m_pendingSessions.erase(m_pendingSessions.begin() + i);
|
m_pending_sessions.erase(m_pending_sessions.begin() + i);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
SocketHandle newSock = accept(session->ListenerSocket, NULL, NULL);
|
auto new_sock = accept(session->listener_socket, nullptr, nullptr);
|
||||||
|
|
||||||
if (IS_VALID_SOCKET(newSock))
|
if (new_sock != INVALID_SOCKET) {
|
||||||
{
|
session->data_socket = new_sock;
|
||||||
session->DataSocket = newSock;
|
session->is_ready = true;
|
||||||
session->IsReady = TRUE;
|
|
||||||
|
|
||||||
// Set Data Socket to Non-Blocking
|
// Set Data Socket to Non-Blocking
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
u_long mode = 1;
|
u_long mode = 1;
|
||||||
ioctlsocket(session->DataSocket, FIONBIO, &mode);
|
ioctlsocket(session->data_socket, FIONBIO, &mode);
|
||||||
#else
|
#else
|
||||||
fcntl(session->DataSocket, F_SETFL, O_NONBLOCK);
|
fcntl(session->data_socket, F_SETFL, O_NONBLOCK);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
SocketOps::Close(session->ListenerSocket);
|
SocketOps::close(session->listener_socket);
|
||||||
session->ListenerSocket = INVALID_SOCKET;
|
session->listener_socket = INVALID_SOCKET;
|
||||||
|
|
||||||
const auto sessionID = session->NodeProcess->ID.load();
|
const auto session_id = session->node_process->id.load();
|
||||||
const auto sessionPtr = session.get();
|
auto *session_ptr = session.get();
|
||||||
m_activeSessions.push_back(std::move(session));
|
m_active_sessions.push_back(std::move(session));
|
||||||
m_pendingSessions.erase(m_pendingSessions.begin() + i);
|
m_pending_sessions.erase(m_pending_sessions.begin() + i);
|
||||||
m_activeSessionMap[sessionID] = sessionPtr;
|
m_active_session_map[session_id] = session_ptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (INT32 i = m_activeSessions.size() - 1; i >= 0; i--)
|
for (isize i = static_cast<isize>(m_active_sessions.size()) - 1; i >= 0;
|
||||||
{
|
--i) {
|
||||||
auto &node = m_activeSessions[i];
|
auto &node = m_active_sessions[static_cast<usize>(i)];
|
||||||
|
|
||||||
auto nodeID = node->NodeProcess->ID.load();
|
auto node_id = node->node_process->id.load();
|
||||||
|
|
||||||
RingBufferView::PacketHeader header;
|
IpcPacketHeader header;
|
||||||
|
|
||||||
while (node->MINO->Pop(header, Span<UINT8>(m_receiveBuffer.data(), m_receiveBuffer.size())))
|
while (node->mino->pop(
|
||||||
OnPacket(nodeID, header.ID, {m_receiveBuffer.data(), header.PayloadSize});
|
header, Span<u8>(m_receive_buffer.data(), m_receive_buffer.size()))) {
|
||||||
|
on_packet(node_id, header.id,
|
||||||
UINT8 signal;
|
{m_receive_buffer.data(), header.payload_size});
|
||||||
const auto res = recv(node->DataSocket, (CHAR *) &signal, 1, 0);
|
|
||||||
|
|
||||||
if (res == 1)
|
|
||||||
OnSignal(nodeID, signal);
|
|
||||||
else if (res == 0 || (res < 0 && !SocketOps::IsWouldBlock()))
|
|
||||||
{
|
|
||||||
ProcessOps::TerminateProcess(node->NodeProcess);
|
|
||||||
|
|
||||||
FileOps::UnmapFile(node->MappedPtr);
|
|
||||||
FileOps::UnlinkSharedMemory(node->SharedMemName);
|
|
||||||
SocketOps::Close(node->DataSocket);
|
|
||||||
|
|
||||||
m_activeSessions.erase(m_activeSessions.begin() + i);
|
|
||||||
m_activeSessionMap.erase(nodeID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT(NativeProcessID) IPC_Manager::SpawnNode(IN CONST FilePath &executablePath, IN UINT32 sharedMemorySize)
|
u8 signal = 0;
|
||||||
{
|
const auto res =
|
||||||
auto session = std::make_unique<NodeSession>();
|
recv(node->data_socket, reinterpret_cast<char *>(&signal), 1, 0);
|
||||||
|
|
||||||
static Atomic<UINT32> s_idGen{0};
|
if (res == 1) {
|
||||||
UINT32 sid = ++s_idGen;
|
on_signal(node_id, signal);
|
||||||
|
} else if (res == 0 || (res < 0 && !SocketOps::is_would_block())) {
|
||||||
|
ProcessOps::terminate_process(node->node_process);
|
||||||
|
|
||||||
|
FileOps::unmap_file(node->mapped_ptr);
|
||||||
|
FileOps::unlink_shared_memory(node->shared_mem_name);
|
||||||
|
SocketOps::close(node->data_socket);
|
||||||
|
|
||||||
|
m_active_sessions.erase(m_active_sessions.begin() + i);
|
||||||
|
m_active_session_map.erase(node_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto IpcManager::spawn_node(const Path &executable_path, u32 shared_memory_size)
|
||||||
|
-> Result<NativeProcessID> {
|
||||||
|
auto session = make_box<NodeSession>();
|
||||||
|
|
||||||
|
static std::atomic<u32> s_id_gen{0};
|
||||||
|
const u32 sid = ++s_id_gen;
|
||||||
|
|
||||||
|
String sock_path;
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
char tempPath[MAX_PATH];
|
char temp_path[MAX_PATH];
|
||||||
GetTempPathA(MAX_PATH, tempPath);
|
GetTempPathA(MAX_PATH, temp_path);
|
||||||
String sockPath = std::format("{}\\ia_sess_{}.sock", tempPath, sid);
|
sock_path = std::format("{}\\ia_sess_{}.sock", temp_path, sid);
|
||||||
#else
|
#else
|
||||||
String sockPath = std::format("/tmp/ia_sess_{}.sock", sid);
|
sock_path = std::format("/tmp/ia_sess_{}.sock", sid);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
session->ListenerSocket = SocketOps::CreateUnixSocket();
|
IA_TRY(session->listener_socket, SocketOps::create_unix_socket());
|
||||||
if (!SocketOps::BindUnixSocket(session->ListenerSocket, sockPath.c_str()))
|
IA_TRY_PURE(
|
||||||
return MakeUnexpected("Failed to bind unique socket");
|
SocketOps::bind_unix_socket(session->listener_socket, sock_path.c_str()));
|
||||||
|
IA_TRY_PURE(SocketOps::listen(session->listener_socket, 1));
|
||||||
if (listen(session->ListenerSocket, 1) != 0)
|
|
||||||
return MakeUnexpected("Failed to listen on unique socket");
|
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
u_long mode = 1;
|
u_long mode = 1;
|
||||||
ioctlsocket(session->ListenerSocket, FIONBIO, &mode);
|
ioctlsocket(session->listener_socket, FIONBIO, &mode);
|
||||||
#else
|
#else
|
||||||
fcntl(session->ListenerSocket, F_SETFL, O_NONBLOCK);
|
fcntl(session->listener_socket, F_SETFL, O_NONBLOCK);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
String shmName = std::format("ia_shm_{}", sid);
|
const String shm_name = std::format("ia_shm_{}", sid);
|
||||||
auto mapRes = FileOps::MapSharedMemory(shmName, sharedMemorySize, TRUE);
|
IA_TRY(session->mapped_ptr,
|
||||||
if (!mapRes.has_value())
|
FileOps::map_shared_memory(shm_name, shared_memory_size, true));
|
||||||
return MakeUnexpected("Failed to map shared memory");
|
|
||||||
|
|
||||||
PUINT8 mappedPtr = mapRes.value();
|
auto *layout = reinterpret_cast<IpcSharedMemoryLayout *>(session->mapped_ptr);
|
||||||
|
|
||||||
auto *layout = reinterpret_cast<IPC_SharedMemoryLayout *>(mappedPtr);
|
layout->meta.magic = 0x49414950;
|
||||||
|
layout->meta.version = 1;
|
||||||
|
layout->meta.total_size = shared_memory_size;
|
||||||
|
|
||||||
layout->Meta.Magic = 0x49414950;
|
const u64 header_size = IpcSharedMemoryLayout::get_header_size();
|
||||||
layout->Meta.Version = 1;
|
const u64 usable_bytes = shared_memory_size - header_size;
|
||||||
layout->Meta.TotalSize = sharedMemorySize;
|
|
||||||
|
|
||||||
UINT64 headerSize = IPC_SharedMemoryLayout::GetHeaderSize();
|
u64 half_size = (usable_bytes / 2);
|
||||||
UINT64 usableBytes = sharedMemorySize - headerSize;
|
half_size -= (half_size % 64);
|
||||||
|
|
||||||
UINT64 halfSize = (usableBytes / 2);
|
layout->moni_data_offset = header_size;
|
||||||
halfSize -= (halfSize % 64);
|
layout->moni_data_size = half_size;
|
||||||
|
|
||||||
layout->MONI_DataOffset = headerSize;
|
layout->mino_data_offset = header_size + half_size;
|
||||||
layout->MONI_DataSize = halfSize;
|
layout->mino_data_size = half_size;
|
||||||
|
|
||||||
layout->MINO_DataOffset = headerSize + halfSize;
|
session->moni = make_box<RingBufferView>(
|
||||||
layout->MINO_DataSize = halfSize;
|
&layout->moni_control,
|
||||||
|
Span<u8>(session->mapped_ptr + layout->moni_data_offset,
|
||||||
|
static_cast<usize>(layout->moni_data_size)),
|
||||||
|
true);
|
||||||
|
|
||||||
session->MONI = std::make_unique<RingBufferView>(
|
session->mino = make_box<RingBufferView>(
|
||||||
&layout->MONI_Control, Span<UINT8>(mappedPtr + layout->MONI_DataOffset, layout->MONI_DataSize), TRUE);
|
&layout->mino_control,
|
||||||
|
Span<u8>(session->mapped_ptr + layout->mino_data_offset,
|
||||||
|
static_cast<usize>(layout->mino_data_size)),
|
||||||
|
true);
|
||||||
|
|
||||||
session->MINO = std::make_unique<RingBufferView>(
|
IpcConnectionDescriptor desc;
|
||||||
&layout->MINO_Control, Span<UINT8>(mappedPtr + layout->MINO_DataOffset, layout->MINO_DataSize), TRUE);
|
desc.socket_path = sock_path;
|
||||||
|
desc.shared_mem_path = shm_name;
|
||||||
|
desc.shared_mem_size = shared_memory_size;
|
||||||
|
|
||||||
IPC_ConnectionDescriptor desc;
|
const String args = std::format("\"{}\"", desc.serialize());
|
||||||
desc.SocketPath = sockPath;
|
|
||||||
desc.SharedMemPath = shmName;
|
|
||||||
desc.SharedMemSize = sharedMemorySize;
|
|
||||||
|
|
||||||
String args = std::format("\"{}\"", desc.Serialize());
|
IA_TRY(session->node_process,
|
||||||
|
ProcessOps::spawn_process_async(
|
||||||
session->NodeProcess = ProcessOps::SpawnProcessAsync(
|
FileOps::normalize_executable_path(executable_path).string(), args,
|
||||||
FileOps::NormalizeExecutablePath(executablePath).string(), args,
|
[sid](StringView line) {
|
||||||
[sid](IN StringView line) {
|
if (env::is_debug) {
|
||||||
UNUSED(sid);
|
std::cout << std::format("{}[Node:{}:STDOUT|STDERR]: {}{}\n",
|
||||||
UNUSED(line);
|
console::MAGENTA, sid, line,
|
||||||
#if __IA_DEBUG
|
console::RESET);
|
||||||
puts(std::format(__CC_MAGENTA "[Node:{}:STDOUT|STDERR]: {}" __CC_DEFAULT, sid, line).c_str());
|
}
|
||||||
#endif
|
|
||||||
},
|
},
|
||||||
[sid](IN EXPECT(INT32) result) {
|
[sid](Result<i32> result) {
|
||||||
UNUSED(sid);
|
if (env::is_debug) {
|
||||||
UNUSED(result);
|
if (!result) {
|
||||||
#if __IA_DEBUG
|
std::cout << std::format(
|
||||||
if (!result)
|
"{}[Node: {}]: Failed to spawn with error '{}'{}\n",
|
||||||
puts(std::format(__CC_RED "Failed to spawn Node: {} with error '{}'" __CC_DEFAULT, sid,
|
console::RED, sid, result.error(), console::RESET);
|
||||||
result.error())
|
} else {
|
||||||
.c_str());
|
std::cout << std::format(
|
||||||
else
|
"{}[Node: {}]: Exited with code {}{}\n", console::RED,
|
||||||
puts(std::format(__CC_RED "[Node: {}]: Exited with code {}" __CC_DEFAULT, sid, *result).c_str());
|
sid, *result, console::RESET);
|
||||||
#endif
|
}
|
||||||
});
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
// Give some time for child node to stablize
|
// Give some time for child node to stablize
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
if (!session->NodeProcess->IsActive())
|
if (!session->node_process->is_active()) {
|
||||||
return MakeUnexpected(std::format("Failed to spawn the child process \"{}\"", executablePath.string()));
|
return fail("Failed to spawn the child process \"{}\"",
|
||||||
|
executable_path.string());
|
||||||
auto processID = session->NodeProcess->ID.load();
|
|
||||||
|
|
||||||
session->CreationTime = SteadyClock::now();
|
|
||||||
m_pendingSessions.push_back(std::move(session));
|
|
||||||
|
|
||||||
return processID;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL IPC_Manager::WaitTillNodeIsOnline(IN NativeProcessID nodeID)
|
const auto process_id = session->node_process->id.load();
|
||||||
{
|
|
||||||
BOOL isPending = true;
|
session->shared_mem_name = shm_name;
|
||||||
while (isPending)
|
session->creation_time = std::chrono::system_clock::now();
|
||||||
{
|
m_pending_sessions.push_back(std::move(session));
|
||||||
isPending = false;
|
|
||||||
for (auto it = m_pendingSessions.begin(); it != m_pendingSessions.end(); it++)
|
return process_id;
|
||||||
{
|
}
|
||||||
if (it->get()->NodeProcess->ID.load() == nodeID)
|
|
||||||
{
|
auto IpcManager::wait_till_node_is_online(NativeProcessID node_id) -> bool {
|
||||||
isPending = true;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Update();
|
update();
|
||||||
}
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
return m_activeSessionMap.contains(nodeID);
|
|
||||||
}
|
}
|
||||||
|
return m_active_session_map.contains(node_id);
|
||||||
|
}
|
||||||
|
|
||||||
VOID IPC_Manager::ShutdownNode(IN NativeProcessID nodeID)
|
void IpcManager::shutdown_node(NativeProcessID node_id) {
|
||||||
{
|
const auto it_node = m_active_session_map.find(node_id);
|
||||||
const auto itNode = m_activeSessionMap.find(nodeID);
|
if (it_node == m_active_session_map.end()) {
|
||||||
if (itNode == m_activeSessionMap.end())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto &node = itNode->second;
|
|
||||||
|
|
||||||
ProcessOps::TerminateProcess(node->NodeProcess);
|
|
||||||
FileOps::UnmapFile(node->MappedPtr);
|
|
||||||
FileOps::UnlinkSharedMemory(node->SharedMemName);
|
|
||||||
SocketOps::Close(node->DataSocket);
|
|
||||||
|
|
||||||
for (auto it = m_activeSessions.begin(); it != m_activeSessions.end(); it++)
|
|
||||||
{
|
|
||||||
if (it->get() == node)
|
|
||||||
{
|
|
||||||
m_activeSessions.erase(it);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_activeSessionMap.erase(itNode);
|
auto *node = it_node->second;
|
||||||
}
|
|
||||||
|
|
||||||
VOID IPC_Manager::SendSignal(IN NativeProcessID node, IN UINT8 signal)
|
ProcessOps::terminate_process(node->node_process);
|
||||||
{
|
FileOps::unmap_file(node->mapped_ptr);
|
||||||
const auto itNode = m_activeSessionMap.find(node);
|
FileOps::unlink_shared_memory(node->shared_mem_name);
|
||||||
if (itNode == m_activeSessionMap.end())
|
SocketOps::close(node->data_socket);
|
||||||
|
|
||||||
|
std::erase_if(m_active_sessions,
|
||||||
|
[&](const auto &s) { return s.get() == node; });
|
||||||
|
m_active_session_map.erase(it_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
return;
|
||||||
itNode->second->SendSignal(signal);
|
|
||||||
}
|
}
|
||||||
|
it_node->second->send_signal(signal);
|
||||||
|
}
|
||||||
|
|
||||||
VOID IPC_Manager::SendPacket(IN NativeProcessID node, IN UINT16 packetID, IN Span<CONST UINT8> payload)
|
void IpcManager::send_packet(NativeProcessID node, u16 packet_id,
|
||||||
{
|
Span<const u8> payload) {
|
||||||
const auto itNode = m_activeSessionMap.find(node);
|
const auto it_node = m_active_session_map.find(node);
|
||||||
if (itNode == m_activeSessionMap.end())
|
if (it_node == m_active_session_map.end()) {
|
||||||
return;
|
return;
|
||||||
itNode->second->SendPacket(packetID, payload);
|
|
||||||
}
|
}
|
||||||
|
it_node->second->send_packet(packet_id, payload);
|
||||||
|
}
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -17,27 +17,5 @@
|
|||||||
|
|
||||||
namespace IACore
|
namespace IACore
|
||||||
{
|
{
|
||||||
EXPECT(nlohmann::json) JSON::Parse(IN CONST String &json)
|
|
||||||
{
|
|
||||||
const auto parseResult = nlohmann::json::parse(json, nullptr, false, true);
|
|
||||||
if (parseResult.is_discarded())
|
|
||||||
return MakeUnexpected("Failed to parse JSON");
|
|
||||||
return parseResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
EXPECT(Pair<SharedPtr<simdjson::dom::parser>, simdjson::dom::object>) JSON::ParseReadOnly(IN CONST String &json)
|
|
||||||
{
|
|
||||||
auto parser = std::make_shared<simdjson::dom::parser>();
|
|
||||||
|
|
||||||
simdjson::error_code error{};
|
|
||||||
simdjson::dom::object object;
|
|
||||||
if ((error = parser->parse(json).get(object)))
|
|
||||||
return MakeUnexpected(std::format("Failed to parse JSON : {}", simdjson::error_message(error)));
|
|
||||||
return std::make_pair(IA_MOVE(parser), IA_MOVE(object));
|
|
||||||
}
|
|
||||||
|
|
||||||
String JSON::Encode(IN nlohmann::json data)
|
|
||||||
{
|
|
||||||
return data.dump();
|
|
||||||
}
|
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -1,72 +1,66 @@
|
|||||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
|
||||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
#include <IACore/Logger.hpp>
|
#include <IACore/Logger.hpp>
|
||||||
#include <IACore/IACore.hpp>
|
#include <chrono>
|
||||||
#include <IACore/FileOps.hpp>
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
Logger::LogLevel Logger::m_log_level = Logger::LogLevel::Info;
|
||||||
Logger::ELogLevel Logger::s_logLevel{Logger::ELogLevel::INFO};
|
std::ofstream Logger::m_log_file;
|
||||||
std::ofstream Logger::s_logFile{};
|
|
||||||
|
|
||||||
VOID Logger::Initialize()
|
static auto get_seconds_count() -> f64 {
|
||||||
{
|
static const auto start_time = std::chrono::steady_clock::now();
|
||||||
|
const auto now = std::chrono::steady_clock::now();
|
||||||
|
const std::chrono::duration<f64> duration = now - start_time;
|
||||||
|
return duration.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Logger::initialize() -> void {}
|
||||||
|
|
||||||
|
auto Logger::terminate() -> void {
|
||||||
|
if (m_log_file.is_open()) {
|
||||||
|
m_log_file.flush();
|
||||||
|
m_log_file.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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::Terminate()
|
m_log_file.open(file_path);
|
||||||
{
|
|
||||||
if (s_logFile.is_open())
|
if (!m_log_file.is_open()) {
|
||||||
{
|
return fail("Failed to open log file: {}", file_path);
|
||||||
s_logFile.flush();
|
|
||||||
s_logFile.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL Logger::EnableLoggingToDisk(IN PCCHAR filePath)
|
return {};
|
||||||
{
|
}
|
||||||
if (s_logFile.is_open())
|
|
||||||
{
|
|
||||||
s_logFile.flush();
|
|
||||||
s_logFile.close();
|
|
||||||
}
|
|
||||||
s_logFile.open(filePath);
|
|
||||||
return s_logFile.is_open();
|
|
||||||
}
|
|
||||||
|
|
||||||
VOID Logger::SetLogLevel(IN ELogLevel logLevel)
|
auto Logger::set_log_level(LogLevel log_level) -> void {
|
||||||
{
|
m_log_level = log_level;
|
||||||
s_logLevel = logLevel;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
VOID Logger::FlushLogs()
|
auto Logger::flush_logs() -> void {
|
||||||
{
|
|
||||||
std::cout.flush();
|
std::cout.flush();
|
||||||
if (s_logFile)
|
if (m_log_file.is_open()) {
|
||||||
s_logFile.flush();
|
m_log_file.flush();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VOID Logger::LogInternal(IN PCCHAR prefix, IN PCCHAR tag, IN String &&msg)
|
auto Logger::log_internal(const char *prefix, const char *tag, String &&msg)
|
||||||
{
|
-> void {
|
||||||
const auto outLine = std::format("[{:>8.3f}]: [{}]: {}", GetSecondsCount(), tag, msg);
|
const auto seconds = get_seconds_count();
|
||||||
std::cout << prefix << outLine << "\033[39m\n";
|
const auto out_line = std::format("[{:>8.3f}]: [{}]: {}", seconds, tag, msg);
|
||||||
if (s_logFile)
|
|
||||||
{
|
std::cout << prefix << out_line << console::RESET << '\n';
|
||||||
s_logFile.write(outLine.data(), outLine.size());
|
|
||||||
s_logFile.put('\n');
|
if (m_log_file.is_open()) {
|
||||||
s_logFile.flush();
|
m_log_file.write(out_line.data(),
|
||||||
}
|
static_cast<std::streamsize>(out_line.size()));
|
||||||
|
m_log_file.put('\n');
|
||||||
|
m_log_file.flush();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -15,106 +15,130 @@
|
|||||||
|
|
||||||
#include <IACore/Platform.hpp>
|
#include <IACore/Platform.hpp>
|
||||||
|
|
||||||
#if IA_ARCH_X64
|
#if defined(IA_ARCH_X64)
|
||||||
|
|
||||||
# ifndef _MSC_VER
|
# ifndef _MSC_VER
|
||||||
# include <cpuid.h>
|
# include <cpuid.h>
|
||||||
# endif
|
# endif
|
||||||
|
#elif defined(IA_ARCH_ARM64)
|
||||||
#elif IA_ARCH_ARM64
|
|
||||||
|
|
||||||
# if defined(__linux__) || defined(__ANDROID__)
|
# if defined(__linux__) || defined(__ANDROID__)
|
||||||
# include <sys/auxv.h>
|
# include <sys/auxv.h>
|
||||||
# include <asm/hwcap.h>
|
# include <asm/hwcap.h>
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore
|
||||||
{
|
{
|
||||||
Platform::Capabilities Platform::s_capabilities{};
|
Platform::Capabilities Platform::s_capabilities{};
|
||||||
|
|
||||||
#if IA_ARCH_X64
|
#if defined(IA_ARCH_X64)
|
||||||
VOID Platform::CPUID(IN INT32 function, IN INT32 subFunction, OUT INT32 out[4])
|
auto Platform::cpuid(i32 function, i32 sub_function, i32 out[4]) -> void
|
||||||
{
|
{
|
||||||
# ifdef _MSC_VER
|
# ifdef _MSC_VER
|
||||||
__cpuidex(out, function, subFunction);
|
__cpuidex(reinterpret_cast<int *>(out), static_cast<int>(function), static_cast<int>(sub_function));
|
||||||
# else
|
# else
|
||||||
__cpuid_count(function, subFunction, out[0], out[1], out[2], out[3]);
|
u32 a = 0;
|
||||||
|
u32 b = 0;
|
||||||
|
u32 c = 0;
|
||||||
|
u32 d = 0;
|
||||||
|
__cpuid_count(function, sub_function, a, b, c, d);
|
||||||
|
out[0] = static_cast<i32>(a);
|
||||||
|
out[1] = static_cast<i32>(b);
|
||||||
|
out[2] = static_cast<i32>(c);
|
||||||
|
out[3] = static_cast<i32>(d);
|
||||||
# endif
|
# endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
BOOL Platform::CheckCPU()
|
auto Platform::check_cpu() -> bool
|
||||||
{
|
{
|
||||||
#if IA_ARCH_X64
|
#if defined(IA_ARCH_X64)
|
||||||
INT32 cpuInfo[4];
|
i32 cpu_info[4];
|
||||||
|
|
||||||
CPUID(0, 0, cpuInfo);
|
cpuid(0, 0, cpu_info);
|
||||||
if (cpuInfo[0] < 7)
|
if (cpu_info[0] < 7)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cpuid(1, 0, cpu_info);
|
||||||
|
// Bit 27: OSXSAVE, Bit 28: AVX, Bit 12: FMA
|
||||||
|
const bool osxsave = (cpu_info[2] & (1 << 27)) != 0;
|
||||||
|
const bool avx = (cpu_info[2] & (1 << 28)) != 0;
|
||||||
|
const bool fma = (cpu_info[2] & (1 << 12)) != 0;
|
||||||
|
|
||||||
CPUID(1, 0, cpuInfo);
|
|
||||||
BOOL osxsave = (cpuInfo[2] & (1 << 27)) != 0;
|
|
||||||
BOOL avx = (cpuInfo[2] & (1 << 28)) != 0;
|
|
||||||
BOOL fma = (cpuInfo[2] & (1 << 12)) != 0;
|
|
||||||
if (!osxsave || !avx || !fma)
|
if (!osxsave || !avx || !fma)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
UINT64 xcrFeatureMask = _xgetbv(0);
|
const u64 xcr_feature_mask = _xgetbv(0);
|
||||||
if ((xcrFeatureMask & 0x6) != 0x6)
|
if ((xcr_feature_mask & 0x6) != 0x6)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
CPUID(7, 0, cpuInfo);
|
cpuid(7, 0, cpu_info);
|
||||||
BOOL avx2 = (cpuInfo[1] & (1 << 5)) != 0;
|
// Bit 5: AVX2
|
||||||
|
const bool avx2 = (cpu_info[1] & (1 << 5)) != 0;
|
||||||
if (!avx2)
|
if (!avx2)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
s_capabilities.HardwareCRC32 = TRUE;
|
s_capabilities.hardware_crc32 = true;
|
||||||
#elif IA_ARCH_ARM64
|
|
||||||
|
#elif defined(IA_ARCH_ARM64)
|
||||||
# if defined(__linux__) || defined(__ANDROID__)
|
# if defined(__linux__) || defined(__ANDROID__)
|
||||||
unsigned long hw_caps = getauxval(AT_HWCAP);
|
const unsigned long hw_caps = getauxval(AT_HWCAP);
|
||||||
|
|
||||||
# ifndef HWCAP_CRC32
|
# ifndef HWCAP_CRC32
|
||||||
# define HWCAP_CRC32 (1 << 7)
|
# define HWCAP_CRC32 (1 << 7)
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
s_capabilities.HardwareCRC32 = hw_caps & HWCAP_CRC32;
|
s_capabilities.hardware_crc32 = (hw_caps & HWCAP_CRC32) != 0;
|
||||||
# elif defined(__APPLE__)
|
# elif defined(IA_PLATFORM_APPLE)
|
||||||
// Apple silicon always has hardware CRC32
|
// Apple silicon always has hardware CRC32
|
||||||
s_capabilities.HardwareCRC32 = TRUE;
|
s_capabilities.hardware_crc32 = true;
|
||||||
# else
|
# else
|
||||||
s_capabilities.HardwareCRC32 = FALSE;
|
s_capabilities.hardware_crc32 = false;
|
||||||
# endif
|
# endif
|
||||||
#else
|
#else
|
||||||
s_capabilities.HardwareCRC32 = FALSE;
|
s_capabilities.hardware_crc32 = false;
|
||||||
#endif
|
#endif
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
PCCHAR Platform::GetArchitectureName()
|
auto Platform::get_architecture_name() -> const char *
|
||||||
{
|
{
|
||||||
#if IA_ARCH_X64
|
#if defined(IA_ARCH_X64)
|
||||||
return "x86_64";
|
return "x86_64";
|
||||||
#elif IA_ARCH_ARM64
|
#elif defined(IA_ARCH_ARM64)
|
||||||
return "aarch64";
|
return "aarch64";
|
||||||
#elif IA_ARCH_WASM
|
#elif defined(IA_ARCH_WASM)
|
||||||
return "wasm";
|
return "wasm";
|
||||||
|
#else
|
||||||
|
return "unknown";
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
PCCHAR Platform::GetOperatingSystemName()
|
auto Platform::get_operating_system_name() -> const char *
|
||||||
{
|
{
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
return "Windows";
|
return "Windows";
|
||||||
#elif IA_PLATFORM_IOS
|
#elif defined(IA_PLATFORM_APPLE)
|
||||||
|
# if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
|
||||||
return "iOS";
|
return "iOS";
|
||||||
#elif IA_PLATFORM_MAC
|
# else
|
||||||
return "Mac";
|
return "macOS";
|
||||||
#elif IA_PLATFORM_ANDROID
|
# endif
|
||||||
|
#elif defined(__ANDROID__)
|
||||||
return "Android";
|
return "Android";
|
||||||
#elif IA_PLATFORM_LINUX
|
#elif IA_PLATFORM_LINUX
|
||||||
return "Linux";
|
return "Linux";
|
||||||
|
#elif IA_PLATFORM_WASM
|
||||||
|
return "WebAssembly";
|
||||||
|
#else
|
||||||
|
return "Unknown";
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -15,183 +15,224 @@
|
|||||||
|
|
||||||
#include <IACore/ProcessOps.hpp>
|
#include <IACore/ProcessOps.hpp>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
// ---------------------------------------------------------------------
|
||||||
// ---------------------------------------------------------------------
|
// Output Buffering Helper
|
||||||
// Output Buffering Helper
|
// Splits raw chunks into lines, preserving partial lines across chunks
|
||||||
// Splits raw chunks into lines, preserving partial lines across chunks
|
// ---------------------------------------------------------------------
|
||||||
// ---------------------------------------------------------------------
|
struct LineBuffer {
|
||||||
struct LineBuffer
|
String m_accumulator;
|
||||||
{
|
std::function<void(StringView)> &m_callback;
|
||||||
String Accumulator;
|
|
||||||
Function<VOID(StringView)> &Callback;
|
|
||||||
|
|
||||||
VOID Append(IN PCCHAR data, IN SIZE_T size);
|
void append(const char *data, usize size);
|
||||||
VOID Flush();
|
void flush();
|
||||||
};
|
};
|
||||||
} // namespace IACore
|
|
||||||
|
|
||||||
namespace IACore
|
void LineBuffer::append(const char *data, usize size) {
|
||||||
{
|
usize start = 0;
|
||||||
NativeProcessID ProcessOps::GetCurrentProcessID()
|
for (usize i = 0; i < size; ++i) {
|
||||||
{
|
if (data[i] == '\n' || data[i] == '\r') {
|
||||||
|
// Flush Accumulator + current chunk
|
||||||
|
if (!m_accumulator.empty()) {
|
||||||
|
m_accumulator.append(data + start, i - start);
|
||||||
|
if (!m_accumulator.empty()) {
|
||||||
|
m_callback(m_accumulator);
|
||||||
|
}
|
||||||
|
m_accumulator.clear();
|
||||||
|
} else {
|
||||||
|
if (i > start) {
|
||||||
|
m_callback(StringView(data + start, i - start));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip \r\n sequence if needed, or just start next
|
||||||
|
if (data[i] == '\r' && i + 1 < size && data[i + 1] == '\n') {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
start = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Save remaining partial line
|
||||||
|
if (start < size) {
|
||||||
|
m_accumulator.append(data + start, size - start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineBuffer::flush() {
|
||||||
|
if (!m_accumulator.empty()) {
|
||||||
|
m_callback(m_accumulator);
|
||||||
|
m_accumulator.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ProcessOps::get_current_process_id() -> NativeProcessID {
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
return ::GetCurrentProcessId();
|
return ::GetCurrentProcessId();
|
||||||
#else
|
#else
|
||||||
return getpid();
|
return getpid();
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ProcessOps::spawn_process_sync(
|
||||||
|
const String &command, const String &args,
|
||||||
|
std::function<void(StringView line)> on_output_line_callback)
|
||||||
|
-> Result<i32> {
|
||||||
|
std::atomic<NativeProcessID> id = 0;
|
||||||
|
if constexpr (env::is_windows) {
|
||||||
|
return spawn_process_windows(command, args, on_output_line_callback, id);
|
||||||
|
} else {
|
||||||
|
return spawn_process_posix(command, args, on_output_line_callback, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ProcessOps::spawn_process_async(
|
||||||
|
const String &command, const String &args,
|
||||||
|
std::function<void(StringView line)> on_output_line_callback,
|
||||||
|
std::function<void(Result<i32>)> on_finish_callback)
|
||||||
|
-> Result<Box<ProcessHandle>> {
|
||||||
|
auto handle = make_box<ProcessHandle>();
|
||||||
|
handle->is_running = true;
|
||||||
|
|
||||||
|
// Capture raw pointer to handle internals safely because jthread joins on
|
||||||
|
// destruction
|
||||||
|
ProcessHandle *h_ptr = handle.get();
|
||||||
|
|
||||||
|
handle->m_thread_handle = std::jthread([h_ptr, cmd = command, arg = args,
|
||||||
|
cb = on_output_line_callback,
|
||||||
|
fin = on_finish_callback]() mutable {
|
||||||
|
Result<i32> result = fail("Platform not supported");
|
||||||
|
|
||||||
|
if constexpr (env::is_windows) {
|
||||||
|
result = spawn_process_windows(cmd, arg, cb, h_ptr->id);
|
||||||
|
} else {
|
||||||
|
result = spawn_process_posix(cmd, arg, cb, h_ptr->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT(INT32)
|
h_ptr->is_running = false;
|
||||||
|
|
||||||
ProcessOps::SpawnProcessSync(IN CONST String &command, IN CONST String &args,
|
if (fin) {
|
||||||
IN Function<VOID(IN StringView line)> onOutputLineCallback)
|
if (!result) {
|
||||||
{
|
fin(fail(std::move(result.error())));
|
||||||
Atomic<NativeProcessID> id;
|
} else {
|
||||||
#if IA_PLATFORM_WINDOWS
|
fin(*result);
|
||||||
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;
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProcessOps::terminate_process(const Box<ProcessHandle> &handle) {
|
||||||
|
if (!handle || !handle->is_active()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID ProcessOps::TerminateProcess(IN CONST SharedPtr<ProcessHandle> &handle)
|
NativeProcessID pid = handle->id.load();
|
||||||
{
|
if (pid == 0) {
|
||||||
if (!handle || !handle->IsActive())
|
|
||||||
return;
|
|
||||||
|
|
||||||
NativeProcessID pid = handle->ID.load();
|
|
||||||
if (pid == 0)
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
|
HANDLE h_process = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
|
||||||
if (hProcess != NULL)
|
if (h_process != NULL) {
|
||||||
{
|
::TerminateProcess(h_process, 9);
|
||||||
::TerminateProcess(hProcess, 9);
|
CloseHandle(h_process);
|
||||||
CloseHandle(hProcess);
|
|
||||||
}
|
}
|
||||||
#else
|
#endif
|
||||||
|
#if IA_PLATFORM_UNIX
|
||||||
kill(pid, SIGKILL);
|
kill(pid, SIGKILL);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
} // namespace IACore
|
|
||||||
|
|
||||||
namespace IACore
|
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
|
#if IA_PLATFORM_WINDOWS
|
||||||
EXPECT(INT32)
|
SECURITY_ATTRIBUTES sa_attr = {sizeof(SECURITY_ATTRIBUTES), NULL,
|
||||||
|
TRUE}; // Allow inheritance
|
||||||
|
HANDLE h_read = NULL;
|
||||||
|
HANDLE h_write = NULL;
|
||||||
|
|
||||||
ProcessOps::SpawnProcessWindows(IN CONST String &command, IN CONST String &args,
|
if (!CreatePipe(&h_read, &h_write, &sa_attr, 0)) {
|
||||||
IN Function<VOID(StringView)> onOutputLineCallback, OUT Atomic<NativeProcessID> &id)
|
return fail("Failed to create pipe");
|
||||||
{
|
}
|
||||||
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
|
// Ensure the read handle to the pipe for STDOUT is NOT inherited
|
||||||
if (!SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0))
|
if (!SetHandleInformation(h_read, HANDLE_FLAG_INHERIT, 0)) {
|
||||||
return MakeUnexpected("Failed to secure pipe handles");
|
return fail("Failed to secure pipe handles");
|
||||||
|
}
|
||||||
|
|
||||||
STARTUPINFOA si = {sizeof(STARTUPINFOA)};
|
STARTUPINFOA si = {sizeof(STARTUPINFOA)};
|
||||||
si.dwFlags |= STARTF_USESTDHANDLES;
|
si.dwFlags |= STARTF_USESTDHANDLES;
|
||||||
si.hStdOutput = hWrite;
|
si.hStdOutput = h_write;
|
||||||
si.hStdError = hWrite; // Merge stderr
|
si.hStdError = h_write; // Merge stderr
|
||||||
si.hStdInput = NULL; // No input
|
si.hStdInput = NULL; // No input
|
||||||
|
|
||||||
PROCESS_INFORMATION pi = {0};
|
PROCESS_INFORMATION pi = {0};
|
||||||
|
|
||||||
// Windows command line needs to be mutable and concatenated
|
// Windows command line needs to be mutable and concatenated
|
||||||
String commandLine = std::format("\"{}\" {}", command, args);
|
String command_line = std::format("\"{}\" {}", command, args);
|
||||||
|
|
||||||
BOOL success = CreateProcessA(NULL, commandLine.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
|
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!
|
// Close write end in parent, otherwise ReadFile never returns EOF!
|
||||||
CloseHandle(hWrite);
|
CloseHandle(h_write);
|
||||||
|
|
||||||
if (!success)
|
if (!success) {
|
||||||
{
|
CloseHandle(h_read);
|
||||||
CloseHandle(hRead);
|
return fail("CreateProcess failed: {}", GetLastError());
|
||||||
return MakeUnexpected(String("CreateProcess failed: ") + std::to_string(GetLastError()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
id.store(pi.dwProcessId);
|
id.store(pi.dwProcessId);
|
||||||
|
|
||||||
// Read Loop
|
// Read Loop
|
||||||
LineBuffer lineBuf{"", onOutputLineCallback};
|
LineBuffer line_buf{"", on_output_line_callback};
|
||||||
DWORD bytesRead;
|
DWORD bytes_read = 0;
|
||||||
CHAR buffer[4096];
|
char buffer[4096];
|
||||||
|
|
||||||
while (ReadFile(hRead, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead != 0)
|
while (ReadFile(h_read, buffer, sizeof(buffer), &bytes_read, NULL) &&
|
||||||
{
|
bytes_read != 0) {
|
||||||
lineBuf.Append(buffer, bytesRead);
|
line_buf.append(buffer, bytes_read);
|
||||||
}
|
}
|
||||||
lineBuf.Flush();
|
line_buf.flush();
|
||||||
|
|
||||||
// NOW we wait for exit code
|
// NOW we wait for exit code
|
||||||
DWORD exitCode = 0;
|
DWORD exit_code = 0;
|
||||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||||
GetExitCodeProcess(pi.hProcess, &exitCode);
|
GetExitCodeProcess(pi.hProcess, &exit_code);
|
||||||
|
|
||||||
CloseHandle(pi.hProcess);
|
CloseHandle(pi.hProcess);
|
||||||
CloseHandle(pi.hThread);
|
CloseHandle(pi.hThread);
|
||||||
CloseHandle(hRead);
|
CloseHandle(h_read);
|
||||||
id.store(0);
|
id.store(0);
|
||||||
|
|
||||||
return static_cast<INT32>(exitCode);
|
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
|
#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
|
#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];
|
int pipefd[2];
|
||||||
if (pipe(pipefd) == -1)
|
if (pipe(pipefd) == -1) {
|
||||||
return MakeUnexpected("Failed to create pipe");
|
return fail("Failed to create pipe");
|
||||||
|
}
|
||||||
|
|
||||||
pid_t pid = fork();
|
pid_t pid = fork();
|
||||||
|
|
||||||
if (pid == -1)
|
if (pid == -1) {
|
||||||
{
|
return fail("Failed to fork process");
|
||||||
return MakeUnexpected("Failed to fork process");
|
} else if (pid == 0) {
|
||||||
}
|
|
||||||
else if (pid == 0)
|
|
||||||
{
|
|
||||||
// --- Child Process ---
|
// --- Child Process ---
|
||||||
close(pipefd[0]);
|
close(pipefd[0]);
|
||||||
|
|
||||||
@ -199,143 +240,92 @@ namespace IACore
|
|||||||
dup2(pipefd[1], STDERR_FILENO);
|
dup2(pipefd[1], STDERR_FILENO);
|
||||||
close(pipefd[1]);
|
close(pipefd[1]);
|
||||||
|
|
||||||
std::vector<std::string> argStorage; // To keep strings alive
|
Vec<String> arg_storage; // To keep strings alive
|
||||||
std::vector<char *> argv;
|
Vec<char *> argv;
|
||||||
|
|
||||||
std::string cmdStr = command;
|
String cmd_str = command;
|
||||||
argv.push_back(cmdStr.data());
|
argv.push_back(cmd_str.data());
|
||||||
|
|
||||||
// Manual Quote-Aware Splitter
|
// Manual Quote-Aware Splitter
|
||||||
std::string currentToken;
|
String current_token;
|
||||||
bool inQuotes = false;
|
bool in_quotes = false;
|
||||||
bool isEscaped = false;
|
bool is_escaped = false;
|
||||||
|
|
||||||
for (char c : args)
|
for (char c : args) {
|
||||||
{
|
if (is_escaped) {
|
||||||
if (isEscaped)
|
|
||||||
{
|
|
||||||
// Previous char was '\', so we treat this char literally.
|
// Previous char was '\', so we treat this char literally.
|
||||||
currentToken += c;
|
current_token += c;
|
||||||
isEscaped = false;
|
is_escaped = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c == '\\')
|
if (c == '\\') {
|
||||||
{
|
|
||||||
// Escape sequence start
|
// Escape sequence start
|
||||||
isEscaped = true;
|
is_escaped = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c == '\"')
|
if (c == '\"') {
|
||||||
{
|
|
||||||
// Toggle quote state
|
// Toggle quote state
|
||||||
inQuotes = !inQuotes;
|
in_quotes = !in_quotes;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c == ' ' && !inQuotes)
|
if (c == ' ' && !in_quotes) {
|
||||||
{
|
|
||||||
// Token boundary
|
// Token boundary
|
||||||
if (!currentToken.empty())
|
if (!current_token.empty()) {
|
||||||
{
|
arg_storage.push_back(current_token);
|
||||||
argStorage.push_back(currentToken);
|
current_token.clear();
|
||||||
currentToken.clear();
|
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
current_token += c;
|
||||||
{
|
|
||||||
currentToken += c;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentToken.empty())
|
if (!current_token.empty()) {
|
||||||
{
|
arg_storage.push_back(current_token);
|
||||||
argStorage.push_back(currentToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build char* array from the std::string storage
|
// Build char* array from the std::string storage
|
||||||
for (auto &s : argStorage)
|
for (auto &s : arg_storage) {
|
||||||
{
|
|
||||||
argv.push_back(s.data());
|
argv.push_back(s.data());
|
||||||
}
|
}
|
||||||
argv.push_back(nullptr);
|
argv.push_back(nullptr);
|
||||||
|
|
||||||
execvp(argv[0], argv.data());
|
execvp(argv[0], argv.data());
|
||||||
_exit(127);
|
_exit(127);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// --- Parent Process ---
|
// --- Parent Process ---
|
||||||
id.store(pid);
|
id.store(pid);
|
||||||
|
|
||||||
close(pipefd[1]);
|
close(pipefd[1]);
|
||||||
|
|
||||||
LineBuffer lineBuf{"", onOutputLineCallback};
|
LineBuffer line_buf{"", on_output_line_callback};
|
||||||
char buffer[4096];
|
char buffer[4096];
|
||||||
ssize_t count;
|
ssize_t count;
|
||||||
|
|
||||||
while ((count = read(pipefd[0], buffer, sizeof(buffer))) > 0)
|
while ((count = read(pipefd[0], buffer, sizeof(buffer))) > 0) {
|
||||||
{
|
line_buf.append(buffer, static_cast<usize>(count));
|
||||||
lineBuf.Append(buffer, count);
|
|
||||||
}
|
}
|
||||||
lineBuf.Flush();
|
line_buf.flush();
|
||||||
close(pipefd[0]);
|
close(pipefd[0]);
|
||||||
|
|
||||||
int status;
|
int status;
|
||||||
waitpid(pid, &status, 0);
|
waitpid(pid, &status, 0);
|
||||||
|
|
||||||
id.store(0);
|
id.store(0);
|
||||||
if (WIFEXITED(status))
|
if (WIFEXITED(status)) {
|
||||||
return WEXITSTATUS(status);
|
return WEXITSTATUS(status);
|
||||||
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
#else
|
||||||
|
(void)command;
|
||||||
|
(void)args;
|
||||||
|
(void)on_output_line_callback;
|
||||||
|
(void)id;
|
||||||
|
return fail("Posix implementation not available.");
|
||||||
#endif
|
#endif
|
||||||
} // namespace IACore
|
}
|
||||||
|
|
||||||
namespace IACore
|
|
||||||
{
|
|
||||||
VOID LineBuffer::Append(IN PCCHAR data, IN SIZE_T size)
|
|
||||||
{
|
|
||||||
SIZE_T start = 0;
|
|
||||||
for (SIZE_T i = 0; i < size; ++i)
|
|
||||||
{
|
|
||||||
if (data[i] == '\n' || data[i] == '\r')
|
|
||||||
{
|
|
||||||
// Flush Accumulator + current chunk
|
|
||||||
if (!Accumulator.empty())
|
|
||||||
{
|
|
||||||
Accumulator.append(data + start, i - start);
|
|
||||||
if (!Accumulator.empty())
|
|
||||||
Callback(Accumulator);
|
|
||||||
Accumulator.clear();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (i > start)
|
|
||||||
Callback(StringView(data + start, i - start));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip \r\n sequence if needed, or just start next
|
|
||||||
if (data[i] == '\r' && i + 1 < size && data[i + 1] == '\n')
|
|
||||||
i++;
|
|
||||||
start = i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Save remaining partial line
|
|
||||||
if (start < size)
|
|
||||||
{
|
|
||||||
Accumulator.append(data + start, size - start);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VOID LineBuffer::Flush()
|
|
||||||
{
|
|
||||||
if (!Accumulator.empty())
|
|
||||||
{
|
|
||||||
Callback(Accumulator);
|
|
||||||
Accumulator.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -14,92 +14,133 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
#include <IACore/SocketOps.hpp>
|
#include <IACore/SocketOps.hpp>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
i32 SocketOps::s_init_count = 0;
|
||||||
INT32 SocketOps::s_initCount{0};
|
|
||||||
|
|
||||||
VOID SocketOps::Close(IN SocketHandle sock)
|
auto SocketOps::close(SocketHandle sock) -> void {
|
||||||
{
|
if (sock == INVALID_SOCKET) {
|
||||||
if (sock == INVALID_SOCKET)
|
|
||||||
return;
|
return;
|
||||||
CLOSE_SOCKET(sock);
|
}
|
||||||
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
closesocket(sock);
|
||||||
|
#else
|
||||||
|
::close(sock);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
auto SocketOps::listen(SocketHandle sock, i32 queue_size) -> Result<void> {
|
||||||
|
if (::listen(sock, queue_size) == 0) {
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL SocketOps::Listen(IN SocketHandle sock, IN INT32 queueSize)
|
#if IA_PLATFORM_WINDOWS
|
||||||
{
|
return fail("listen failed: {}", WSAGetLastError());
|
||||||
return listen(sock, queueSize) == 0;
|
#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");
|
||||||
}
|
}
|
||||||
|
|
||||||
SocketHandle SocketOps::CreateUnixSocket()
|
unlink_file(path);
|
||||||
{
|
|
||||||
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{};
|
sockaddr_un addr{};
|
||||||
addr.sun_family = AF_UNIX;
|
addr.sun_family = AF_UNIX;
|
||||||
|
|
||||||
size_t maxLen = sizeof(addr.sun_path) - 1;
|
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
|
||||||
|
|
||||||
strncpy(addr.sun_path, path, maxLen);
|
if (::bind(sock, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) ==
|
||||||
|
-1) {
|
||||||
if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) == -1)
|
#if IA_PLATFORM_WINDOWS
|
||||||
return FALSE;
|
return fail("bind failed: {}", WSAGetLastError());
|
||||||
|
#else
|
||||||
return TRUE;
|
return fail("bind failed: {}", errno);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL SocketOps::ConnectUnixSocket(IN SocketHandle sock, IN PCCHAR path)
|
return {};
|
||||||
{
|
}
|
||||||
if (!IS_VALID_SOCKET(sock))
|
|
||||||
return FALSE;
|
auto SocketOps::connect_unix_socket(SocketHandle sock, const char *path)
|
||||||
|
-> Result<void> {
|
||||||
|
if (sock == INVALID_SOCKET) {
|
||||||
|
return fail("Invalid socket handle");
|
||||||
|
}
|
||||||
|
|
||||||
sockaddr_un addr{};
|
sockaddr_un addr{};
|
||||||
addr.sun_family = AF_UNIX;
|
addr.sun_family = AF_UNIX;
|
||||||
|
|
||||||
size_t maxLen = sizeof(addr.sun_path) - 1;
|
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
|
||||||
|
|
||||||
strncpy(addr.sun_path, path, maxLen);
|
if (::connect(sock, reinterpret_cast<struct sockaddr *>(&addr),
|
||||||
|
sizeof(addr)) == -1) {
|
||||||
if (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) == -1)
|
#if IA_PLATFORM_WINDOWS
|
||||||
return FALSE;
|
return fail("connect failed: {}", WSAGetLastError());
|
||||||
|
#else
|
||||||
return TRUE;
|
return fail("connect failed: {}", errno);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL SocketOps::IsPortAvailable(IN UINT16 port, IN INT32 type)
|
return {};
|
||||||
{
|
}
|
||||||
SocketHandle sock = socket(AF_INET, type, IPPROTO_UDP);
|
|
||||||
if (!IS_VALID_SOCKET(sock))
|
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;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
sockaddr_in addr{};
|
sockaddr_in addr{};
|
||||||
addr.sin_family = AF_INET;
|
addr.sin_family = AF_INET;
|
||||||
addr.sin_port = htons(port);
|
addr.sin_port = htons(port);
|
||||||
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
|
||||||
bool isFree = false;
|
bool is_free = false;
|
||||||
if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) == 0)
|
if (::bind(sock, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) ==
|
||||||
isFree = true;
|
0) {
|
||||||
|
is_free = true;
|
||||||
CLOSE_SOCKET(sock);
|
|
||||||
|
|
||||||
return isFree;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL SocketOps::IsWouldBlock()
|
close(sock);
|
||||||
{
|
|
||||||
|
return is_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto SocketOps::is_would_block() -> bool {
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
return WSAGetLastError() == WSAEWOULDBLOCK;
|
return WSAGetLastError() == WSAEWOULDBLOCK;
|
||||||
#else
|
#else
|
||||||
return errno == EWOULDBLOCK || errno == EAGAIN;
|
return errno == EWOULDBLOCK || errno == EAGAIN;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -13,46 +13,36 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
#include <IACore/StreamReader.hpp>
|
|
||||||
#include <IACore/FileOps.hpp>
|
#include <IACore/FileOps.hpp>
|
||||||
#include <IACore/Logger.hpp>
|
#include <IACore/StreamReader.hpp>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
auto StreamReader::create_from_file(const Path &path) -> Result<StreamReader> {
|
||||||
StreamReader::StreamReader(IN CONST FilePath &path) : m_storageType(EStorageType::OWNING_MMAP)
|
usize size = 0;
|
||||||
{
|
|
||||||
const auto t = FileOps::MapFile(path, m_dataSize);
|
|
||||||
if (!t)
|
|
||||||
{
|
|
||||||
Logger::Error("Failed to memory map file {}", path.string());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_data = *t;
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamReader::StreamReader(IN Vector<UINT8> &&data)
|
const u8 *ptr;
|
||||||
: m_owningVector(IA_MOVE(data)), m_storageType(EStorageType::OWNING_VECTOR)
|
IA_TRY(ptr, FileOps::map_file(path, size));
|
||||||
{
|
|
||||||
m_data = m_owningVector.data();
|
|
||||||
m_dataSize = m_owningVector.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamReader::StreamReader(IN Span<CONST UINT8> data)
|
StreamReader reader(Span<const u8>(ptr, size));
|
||||||
: m_data(data.data()), m_dataSize(data.size()), m_storageType(EStorageType::NON_OWNING)
|
reader.m_storage_type = StorageType::OwningMmap;
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamReader::~StreamReader()
|
return reader;
|
||||||
{
|
}
|
||||||
switch (m_storageType)
|
|
||||||
{
|
|
||||||
case EStorageType::OWNING_MMAP:
|
|
||||||
FileOps::UnmapFile(m_data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EStorageType::NON_OWNING:
|
StreamReader::StreamReader(Vec<u8> &&data)
|
||||||
case EStorageType::OWNING_VECTOR:
|
: m_owning_vector(std::move(data)),
|
||||||
break;
|
m_storage_type(StorageType::OwningVector) {
|
||||||
}
|
m_data = m_owning_vector.data();
|
||||||
|
m_data_size = m_owning_vector.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamReader::StreamReader(Span<const u8> data)
|
||||||
|
: m_data(data.data()), m_data_size(data.size()),
|
||||||
|
m_storage_type(StorageType::NonOwning) {}
|
||||||
|
|
||||||
|
StreamReader::~StreamReader() {
|
||||||
|
if (m_storage_type == StorageType::OwningMmap) {
|
||||||
|
FileOps::unmap_file(m_data);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -14,83 +14,88 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
#include <IACore/StreamWriter.hpp>
|
#include <IACore/StreamWriter.hpp>
|
||||||
#include <IACore/Logger.hpp>
|
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
|
||||||
StreamWriter::StreamWriter() : m_storageType(EStorageType::OWNING_VECTOR)
|
auto StreamWriter::create(const Path &path) -> Result<StreamWriter> {
|
||||||
{
|
// Try to open the file to ensure we have write permissions and to truncate
|
||||||
m_owningVector.resize(m_capacity = 256);
|
// it.
|
||||||
m_buffer = m_owningVector.data();
|
FILE *f = std::fopen(path.string().c_str(), "wb");
|
||||||
|
if (!f) {
|
||||||
|
return fail("Failed to open file for writing: {}", path.string());
|
||||||
}
|
}
|
||||||
|
std::fclose(f);
|
||||||
|
|
||||||
StreamWriter::StreamWriter(IN Span<UINT8> data)
|
StreamWriter writer;
|
||||||
: m_buffer(data.data()), m_capacity(data.size()), m_storageType(EStorageType::NON_OWNING)
|
writer.m_file_path = path;
|
||||||
{
|
writer.m_storage_type = StorageType::OwningFile;
|
||||||
}
|
|
||||||
|
|
||||||
StreamWriter::StreamWriter(IN CONST FilePath &path) : m_filePath(path), m_storageType(EStorageType::OWNING_FILE)
|
return writer;
|
||||||
{
|
}
|
||||||
IA_RELEASE_ASSERT(!path.empty());
|
|
||||||
const auto f = fopen(m_filePath.string().c_str(), "wb");
|
StreamWriter::StreamWriter() : m_storage_type(StorageType::OwningVector) {
|
||||||
if (!f)
|
m_capacity = 256;
|
||||||
{
|
m_owning_vector.resize(m_capacity);
|
||||||
Logger::Error("Failed to open file for writing {}", m_filePath.string().c_str());
|
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;
|
return;
|
||||||
}
|
}
|
||||||
fputc(0, f);
|
|
||||||
fclose(f);
|
|
||||||
|
|
||||||
m_owningVector.resize(m_capacity = 256);
|
FILE *f = std::fopen(m_file_path.string().c_str(), "wb");
|
||||||
m_buffer = m_owningVector.data();
|
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()
|
// Growth strategy: Current capacity + (count * 2)
|
||||||
{
|
const usize new_capacity = m_capacity + (count << 1);
|
||||||
switch (m_storageType)
|
m_owning_vector.resize(new_capacity);
|
||||||
{
|
m_capacity = m_owning_vector.size();
|
||||||
case EStorageType::OWNING_FILE: {
|
m_buffer = m_owning_vector.data();
|
||||||
IA_RELEASE_ASSERT(!m_filePath.empty());
|
|
||||||
const auto f = fopen(m_filePath.string().c_str(), "wb");
|
|
||||||
if (!f)
|
|
||||||
{
|
|
||||||
Logger::Error("Failed to open file for writing {}", m_filePath.string().c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fwrite(m_owningVector.data(), 1, m_owningVector.size(), f);
|
|
||||||
fclose(f);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EStorageType::OWNING_VECTOR:
|
|
||||||
case EStorageType::NON_OWNING:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define HANDLE_OUT_OF_CAPACITY(_size) \
|
std::memset(m_buffer + m_cursor, byte, count);
|
||||||
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;
|
m_cursor += count;
|
||||||
return true;
|
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)");
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL StreamWriter::Write(IN PCVOID buffer, IN SIZE_T size)
|
const usize new_capacity = m_capacity + (size << 1);
|
||||||
{
|
m_owning_vector.resize(new_capacity);
|
||||||
HANDLE_OUT_OF_CAPACITY(size);
|
m_capacity = m_owning_vector.size();
|
||||||
memcpy(&m_buffer[m_cursor], buffer, size);
|
m_buffer = m_owning_vector.data();
|
||||||
m_cursor += size;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::memcpy(m_buffer + m_cursor, buffer, size);
|
||||||
|
m_cursor += size;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -15,86 +15,77 @@
|
|||||||
|
|
||||||
#include <IACore/StringOps.hpp>
|
#include <IACore/StringOps.hpp>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
const String BASE64_CHAR_TABLE =
|
||||||
CONST String BASE64_CHAR_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
|
||||||
String StringOps::EncodeBase64(IN Span<CONST UINT8> data)
|
auto StringOps::encode_base64(Span<const u8> data) -> String {
|
||||||
{
|
|
||||||
String result;
|
String result;
|
||||||
result.reserve(((data.size() + 2) / 3) * 4);
|
result.reserve(((data.size() + 2) / 3) * 4);
|
||||||
for (size_t i = 0; i < data.size(); i += 3)
|
for (size_t i = 0; i < data.size(); i += 3) {
|
||||||
{
|
|
||||||
uint32_t value = 0;
|
uint32_t value = 0;
|
||||||
INT32 num_bytes = 0;
|
i32 num_bytes = 0;
|
||||||
for (INT32 j = 0; j < 3 && (i + j) < data.size(); ++j)
|
for (i32 j = 0; j < 3 && (i + j) < data.size(); ++j) {
|
||||||
{
|
|
||||||
value = (value << 8) | data[i + j];
|
value = (value << 8) | data[i + j];
|
||||||
num_bytes++;
|
num_bytes++;
|
||||||
}
|
}
|
||||||
for (INT32 j = 0; j < num_bytes + 1; ++j)
|
for (i32 j = 0; j < num_bytes + 1; ++j) {
|
||||||
{
|
if (j < 4) {
|
||||||
if (j < 4)
|
|
||||||
{
|
|
||||||
result += BASE64_CHAR_TABLE[(value >> (6 * (3 - j))) & 0x3F];
|
result += BASE64_CHAR_TABLE[(value >> (6 * (3 - j))) & 0x3F];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (num_bytes < 3)
|
if (num_bytes < 3) {
|
||||||
{
|
for (i32 j = 0; j < (3 - num_bytes); ++j) {
|
||||||
for (INT32 j = 0; j < (3 - num_bytes); ++j)
|
|
||||||
{
|
|
||||||
result += '=';
|
result += '=';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector<UINT8> StringOps::DecodeBase64(IN CONST String &data)
|
auto StringOps::decode_base64(const String &data) -> Vec<u8> {
|
||||||
{
|
Vec<u8> result;
|
||||||
Vector<UINT8> result;
|
|
||||||
|
|
||||||
CONST AUTO isBase64 = [](UINT8 c) { return (isalnum(c) || (c == '+') || (c == '/')); };
|
const auto is_base64 = [](u8 c) {
|
||||||
|
return (isalnum(c) || (c == '+') || (c == '/'));
|
||||||
|
};
|
||||||
|
|
||||||
INT32 in_len = data.size();
|
i32 in_len = data.size();
|
||||||
INT32 i = 0, j = 0, in_ = 0;
|
i32 i = 0, j = 0, in = 0;
|
||||||
UINT8 tmpBuf0[4], tmpBuf1[3];
|
u8 tmp_buf0[4], tmp_buf1[3];
|
||||||
|
|
||||||
while (in_len-- && (data[in_] != '=') && isBase64(data[in_]))
|
while (in_len-- && (data[in] != '=') && is_base64(data[in])) {
|
||||||
{
|
tmp_buf0[i++] = data[in];
|
||||||
tmpBuf0[i++] = data[in_];
|
in++;
|
||||||
in_++;
|
if (i == 4) {
|
||||||
if (i == 4)
|
|
||||||
{
|
|
||||||
for (i = 0; i < 4; i++)
|
for (i = 0; i < 4; i++)
|
||||||
tmpBuf0[i] = BASE64_CHAR_TABLE.find(tmpBuf0[i]);
|
tmp_buf0[i] = BASE64_CHAR_TABLE.find(tmp_buf0[i]);
|
||||||
|
|
||||||
tmpBuf1[0] = (tmpBuf0[0] << 2) + ((tmpBuf0[1] & 0x30) >> 4);
|
tmp_buf1[0] = (tmp_buf0[0] << 2) + ((tmp_buf0[1] & 0x30) >> 4);
|
||||||
tmpBuf1[1] = ((tmpBuf0[1] & 0xf) << 4) + ((tmpBuf0[2] & 0x3c) >> 2);
|
tmp_buf1[1] = ((tmp_buf0[1] & 0xf) << 4) + ((tmp_buf0[2] & 0x3c) >> 2);
|
||||||
tmpBuf1[2] = ((tmpBuf0[2] & 0x3) << 6) + tmpBuf0[3];
|
tmp_buf1[2] = ((tmp_buf0[2] & 0x3) << 6) + tmp_buf0[3];
|
||||||
|
|
||||||
for (i = 0; (i < 3); i++)
|
for (i = 0; (i < 3); i++)
|
||||||
result.push_back(tmpBuf1[i]);
|
result.push_back(tmp_buf1[i]);
|
||||||
i = 0;
|
i = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i)
|
if (i) {
|
||||||
{
|
|
||||||
for (j = i; j < 4; j++)
|
for (j = i; j < 4; j++)
|
||||||
tmpBuf0[j] = 0;
|
tmp_buf0[j] = 0;
|
||||||
|
|
||||||
for (j = 0; j < 4; j++)
|
for (j = 0; j < 4; j++)
|
||||||
tmpBuf0[j] = BASE64_CHAR_TABLE.find(tmpBuf0[j]);
|
tmp_buf0[j] = BASE64_CHAR_TABLE.find(tmp_buf0[j]);
|
||||||
|
|
||||||
tmpBuf1[0] = (tmpBuf0[0] << 2) + ((tmpBuf0[1] & 0x30) >> 4);
|
tmp_buf1[0] = (tmp_buf0[0] << 2) + ((tmp_buf0[1] & 0x30) >> 4);
|
||||||
tmpBuf1[1] = ((tmpBuf0[1] & 0xf) << 4) + ((tmpBuf0[2] & 0x3c) >> 2);
|
tmp_buf1[1] = ((tmp_buf0[1] & 0xf) << 4) + ((tmp_buf0[2] & 0x3c) >> 2);
|
||||||
tmpBuf1[2] = ((tmpBuf0[2] & 0x3) << 6) + tmpBuf0[3];
|
tmp_buf1[2] = ((tmp_buf0[2] & 0x3) << 6) + tmp_buf0[3];
|
||||||
|
|
||||||
for (j = 0; (j < i - 1); j++)
|
for (j = 0; (j < i - 1); j++)
|
||||||
result.push_back(tmpBuf1[j]);
|
result.push_back(tmp_buf1[j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
} // namespace IACore
|
} // 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
|
namespace IACore
|
||||||
{
|
{
|
||||||
EXPECT(XML::Document) XML::ParseFromString(IN CONST String &data)
|
Result<XML::Document> XML::parse_from_string(const String &data)
|
||||||
{
|
{
|
||||||
Document doc;
|
Document doc;
|
||||||
const auto parseResult = doc.load_string(data.data());
|
const auto parseResult = doc.load_string(data.data());
|
||||||
if (!parseResult)
|
if (!parseResult)
|
||||||
return MakeUnexpected(std::format("Failed to parse XML: {}", parseResult.description()));
|
return fail("Failed to parse XML {}", parseResult.description());
|
||||||
return IA_MOVE(doc);
|
return std::move(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT(XML::Document) XML::ParseFromFile(IN CONST FilePath &path)
|
Result<XML::Document> XML::parse_from_file(const Path &path)
|
||||||
{
|
{
|
||||||
Document doc;
|
Document doc;
|
||||||
const auto parseResult = doc.load_file(path.string().c_str());
|
const auto parseResult = doc.load_file(path.string().c_str());
|
||||||
if (!parseResult)
|
if (!parseResult)
|
||||||
return MakeUnexpected(std::format("Failed to parse XML: {}", parseResult.description()));
|
return fail("Failed to parse XML {}", parseResult.description());
|
||||||
return IA_MOVE(doc);
|
return std::move(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
String XML::SerializeToString(IN CONST Node &node, IN BOOL escape)
|
String XML::serialize_to_string(const Node &node, bool escape)
|
||||||
{
|
{
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
node.print(oss);
|
node.print(oss);
|
||||||
return escape ? EscapeXMLString(oss.str()) : oss.str();
|
return escape ? escape_xml_string(oss.str()) : oss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
String XML::SerializeToString(IN CONST Document &doc, IN BOOL escape)
|
String XML::serialize_to_string(const Document &doc, bool escape)
|
||||||
{
|
{
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
doc.save(oss);
|
doc.save(oss);
|
||||||
return escape ? EscapeXMLString(oss.str()) : oss.str();
|
return escape ? escape_xml_string(oss.str()) : oss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
String XML::EscapeXMLString(IN CONST String &xml)
|
String XML::escape_xml_string(const String &xml)
|
||||||
{
|
{
|
||||||
String buffer;
|
String buffer;
|
||||||
buffer.reserve(xml.size() + (xml.size() / 10));
|
buffer.reserve(xml.size() + (xml.size() / 10));
|
||||||
|
|||||||
@ -16,210 +16,216 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <IACore/PCH.hpp>
|
#include <IACore/PCH.hpp>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
class RingBufferView {
|
||||||
class RingBufferView
|
public:
|
||||||
{
|
static constexpr u16 PACKET_ID_SKIP = 0;
|
||||||
public:
|
|
||||||
STATIC CONSTEXPR UINT16 PACKET_ID_SKIP = 0;
|
|
||||||
|
|
||||||
struct ControlBlock
|
struct ControlBlock {
|
||||||
{
|
struct alignas(64) {
|
||||||
struct alignas(64)
|
std::atomic<u32> write_offset{0};
|
||||||
{
|
} producer;
|
||||||
Atomic<UINT32> WriteOffset{0};
|
|
||||||
} Producer;
|
|
||||||
|
|
||||||
struct alignas(64)
|
struct alignas(64) {
|
||||||
{
|
std::atomic<u32> read_offset{0};
|
||||||
Atomic<UINT32> ReadOffset{0};
|
|
||||||
// Capacity is effectively constant after init,
|
// Capacity is effectively constant after init,
|
||||||
// so it doesn't cause false sharing invalidations.
|
// so it doesn't cause false sharing invalidations.
|
||||||
UINT32 Capacity{0};
|
u32 capacity{0};
|
||||||
} Consumer;
|
} consumer;
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(offsetof(ControlBlock, Consumer) == 64, "False sharing detected in ControlBlock");
|
static_assert(offsetof(ControlBlock, consumer) == 64,
|
||||||
|
"False sharing detected in ControlBlock");
|
||||||
|
|
||||||
// All of the data in ring buffer will be stored as packets
|
// All of the data in ring buffer will be stored as packets
|
||||||
struct PacketHeader
|
struct PacketHeader {
|
||||||
{
|
PacketHeader() : id(0), payload_size(0) {}
|
||||||
PacketHeader() : ID(0), PayloadSize(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
PacketHeader(IN UINT16 id) : ID(id), PayloadSize(0)
|
PacketHeader(u16 id) : id(id), payload_size(0) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
PacketHeader(IN UINT16 id, IN UINT16 payloadSize) : ID(id), PayloadSize(payloadSize)
|
PacketHeader(u16 id, u16 payload_size)
|
||||||
{
|
: id(id), payload_size(payload_size) {}
|
||||||
}
|
|
||||||
|
|
||||||
UINT16 ID{};
|
u16 id{};
|
||||||
UINT16 PayloadSize{};
|
u16 payload_size{};
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
INLINE RingBufferView(IN Span<UINT8> buffer, IN BOOL isOwner);
|
RingBufferView(Span<u8> buffer, bool is_owner);
|
||||||
INLINE RingBufferView(IN ControlBlock *controlBlock, IN Span<UINT8> buffer, IN BOOL isOwner);
|
RingBufferView(ControlBlock *control_block, Span<u8> buffer, bool is_owner);
|
||||||
|
|
||||||
INLINE INT32 Pop(OUT PacketHeader &outHeader, OUT Span<UINT8> outBuffer);
|
// Returns:
|
||||||
INLINE BOOL Push(IN UINT16 packetID, IN Span<CONST UINT8> data);
|
// - Ok(nullopt) if empty
|
||||||
|
// - Ok(bytes_read) if success
|
||||||
|
// - Error if buffer too small
|
||||||
|
auto pop(PacketHeader &out_header, Span<u8> out_buffer)
|
||||||
|
-> Result<Option<usize>>;
|
||||||
|
|
||||||
INLINE ControlBlock *GetControlBlock();
|
// Returns:
|
||||||
|
// - Ok() if success
|
||||||
|
// - Error if full
|
||||||
|
auto push(u16 packet_id, Span<const u8> data) -> Result<void>;
|
||||||
|
|
||||||
private:
|
auto get_control_block() -> ControlBlock *;
|
||||||
PUINT8 m_dataPtr{};
|
|
||||||
UINT32 m_capacity{};
|
|
||||||
ControlBlock *m_controlBlock{};
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
INLINE VOID WriteWrapped(IN UINT32 offset, IN PCVOID data, IN UINT32 size);
|
u8 *m_data_ptr{};
|
||||||
INLINE VOID ReadWrapped(IN UINT32 offset, OUT PVOID outData, IN UINT32 size);
|
u32 m_capacity{};
|
||||||
};
|
ControlBlock *m_control_block{};
|
||||||
} // namespace IACore
|
|
||||||
|
|
||||||
namespace IACore
|
private:
|
||||||
{
|
auto write_wrapped(u32 offset, const void *data, u32 size) -> void;
|
||||||
RingBufferView::RingBufferView(IN Span<UINT8> buffer, IN BOOL isOwner)
|
auto read_wrapped(u32 offset, void *out_data, u32 size) -> void;
|
||||||
{
|
};
|
||||||
IA_ASSERT(buffer.size() > sizeof(ControlBlock));
|
|
||||||
|
|
||||||
m_controlBlock = reinterpret_cast<ControlBlock *>(buffer.data());
|
// =============================================================================
|
||||||
m_dataPtr = buffer.data() + sizeof(ControlBlock);
|
// Implementation
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
m_capacity = static_cast<UINT32>(buffer.size()) - sizeof(ControlBlock);
|
inline RingBufferView::RingBufferView(Span<u8> buffer, bool is_owner) {
|
||||||
|
ensure(buffer.size() > sizeof(ControlBlock),
|
||||||
|
"Buffer too small for ControlBlock");
|
||||||
|
|
||||||
if (isOwner)
|
m_control_block = reinterpret_cast<ControlBlock *>(buffer.data());
|
||||||
{
|
m_data_ptr = buffer.data() + sizeof(ControlBlock);
|
||||||
m_controlBlock->Consumer.Capacity = m_capacity;
|
|
||||||
m_controlBlock->Producer.WriteOffset.store(0, std::memory_order_release);
|
m_capacity = static_cast<u32>(buffer.size()) - sizeof(ControlBlock);
|
||||||
m_controlBlock->Consumer.ReadOffset.store(0, std::memory_order_release);
|
|
||||||
|
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");
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
IA_ASSERT(m_controlBlock->Consumer.Capacity == m_capacity);
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
m_control_block = control_block;
|
||||||
|
m_data_ptr = buffer.data();
|
||||||
|
m_capacity = static_cast<u32>(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (read == write) {
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
RingBufferView::RingBufferView(IN ControlBlock *controlBlock, IN Span<UINT8> buffer, IN BOOL isOwner)
|
read_wrapped(read, &out_header, sizeof(PacketHeader));
|
||||||
{
|
|
||||||
IA_ASSERT(controlBlock != nullptr);
|
|
||||||
IA_ASSERT(buffer.size() > 0);
|
|
||||||
|
|
||||||
m_controlBlock = controlBlock;
|
if (out_header.payload_size > out_buffer.size()) {
|
||||||
m_dataPtr = buffer.data();
|
return fail("Buffer too small: needed {}, provided {}",
|
||||||
m_capacity = static_cast<UINT32>(buffer.size());
|
out_header.payload_size, out_buffer.size());
|
||||||
|
|
||||||
if (isOwner)
|
|
||||||
{
|
|
||||||
m_controlBlock->Consumer.Capacity = m_capacity;
|
|
||||||
m_controlBlock->Producer.WriteOffset.store(0, std::memory_order_release);
|
|
||||||
m_controlBlock->Consumer.ReadOffset.store(0, std::memory_order_release);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
INT32 RingBufferView::Pop(OUT PacketHeader &outHeader, OUT Span<UINT8> outBuffer)
|
if (out_header.payload_size > 0) {
|
||||||
{
|
u32 data_read_offset = (read + sizeof(PacketHeader)) % cap;
|
||||||
UINT32 write = m_controlBlock->Producer.WriteOffset.load(std::memory_order_acquire);
|
read_wrapped(data_read_offset, out_buffer.data(), out_header.payload_size);
|
||||||
UINT32 read = m_controlBlock->Consumer.ReadOffset.load(std::memory_order_relaxed);
|
|
||||||
UINT32 cap = m_capacity;
|
|
||||||
|
|
||||||
if (read == write)
|
|
||||||
return 0; // Empty
|
|
||||||
|
|
||||||
ReadWrapped(read, &outHeader, sizeof(PacketHeader));
|
|
||||||
|
|
||||||
if (outHeader.PayloadSize > outBuffer.size())
|
|
||||||
return -static_cast<INT32>(outHeader.PayloadSize);
|
|
||||||
|
|
||||||
if (outHeader.PayloadSize > 0)
|
|
||||||
{
|
|
||||||
UINT32 dataReadOffset = (read + sizeof(PacketHeader)) % cap;
|
|
||||||
ReadWrapped(dataReadOffset, outBuffer.data(), outHeader.PayloadSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move read pointer forward
|
// Move read pointer forward
|
||||||
UINT32 newReadOffset = (read + sizeof(PacketHeader) + outHeader.PayloadSize) % cap;
|
u32 new_read_offset =
|
||||||
m_controlBlock->Consumer.ReadOffset.store(newReadOffset, std::memory_order_release);
|
(read + sizeof(PacketHeader) + out_header.payload_size) % cap;
|
||||||
|
m_control_block->consumer.read_offset.store(new_read_offset,
|
||||||
|
std::memory_order_release);
|
||||||
|
|
||||||
return outHeader.PayloadSize;
|
return std::make_optional(static_cast<usize>(out_header.payload_size));
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
const u32 total_size = sizeof(PacketHeader) + static_cast<u32>(data.size());
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
u32 free_space =
|
||||||
|
(read <= write) ? (m_capacity - write) + read : (read - write);
|
||||||
|
|
||||||
|
// Ensure to always leave 1 byte empty to prevent Read == Write ambiguity
|
||||||
|
if (free_space <= total_size) {
|
||||||
|
return fail("RingBuffer full");
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL RingBufferView::Push(IN UINT16 packetID, IN Span<CONST UINT8> data)
|
PacketHeader header{packet_id, static_cast<u16>(data.size())};
|
||||||
{
|
write_wrapped(write, &header, sizeof(PacketHeader));
|
||||||
IA_ASSERT(data.size() <= UINT16_MAX);
|
|
||||||
|
|
||||||
const UINT32 totalSize = sizeof(PacketHeader) + static_cast<UINT32>(data.size());
|
u32 data_write_offset = (write + sizeof(PacketHeader)) % cap;
|
||||||
|
|
||||||
UINT32 read = m_controlBlock->Consumer.ReadOffset.load(std::memory_order_acquire);
|
if (!data.empty()) {
|
||||||
UINT32 write = m_controlBlock->Producer.WriteOffset.load(std::memory_order_relaxed);
|
write_wrapped(data_write_offset, data.data(),
|
||||||
UINT32 cap = m_capacity;
|
static_cast<u32>(data.size()));
|
||||||
|
|
||||||
UINT32 freeSpace = (read <= write) ? (m_capacity - write) + read : (read - write);
|
|
||||||
|
|
||||||
// Ensure to always leave 1 byte empty to prevent Read == Write ambiguity (Wait-Free Ring Buffer standard)
|
|
||||||
if (freeSpace <= totalSize)
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
PacketHeader header{packetID, static_cast<UINT16>(data.size())};
|
|
||||||
WriteWrapped(write, &header, sizeof(PacketHeader));
|
|
||||||
|
|
||||||
UINT32 dataWriteOffset = (write + sizeof(PacketHeader)) % cap;
|
|
||||||
|
|
||||||
if (data.size() > 0)
|
|
||||||
{
|
|
||||||
WriteWrapped(dataWriteOffset, data.data(), static_cast<UINT32>(data.size()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UINT32 newWriteOffset = (dataWriteOffset + data.size()) % cap;
|
u32 new_write_offset = (data_write_offset + data.size()) % cap;
|
||||||
m_controlBlock->Producer.WriteOffset.store(newWriteOffset, std::memory_order_release);
|
m_control_block->producer.write_offset.store(new_write_offset,
|
||||||
|
std::memory_order_release);
|
||||||
|
|
||||||
return TRUE;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
RingBufferView::ControlBlock *RingBufferView::GetControlBlock()
|
inline auto RingBufferView::get_control_block() -> ControlBlock * {
|
||||||
{
|
return m_control_block;
|
||||||
return m_controlBlock;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
VOID RingBufferView::WriteWrapped(IN UINT32 offset, IN PCVOID data, IN UINT32 size)
|
inline auto RingBufferView::write_wrapped(u32 offset, const void *data,
|
||||||
{
|
u32 size) -> void {
|
||||||
if (offset + size <= m_capacity)
|
if (offset + size <= m_capacity) {
|
||||||
{
|
|
||||||
// Contiguous write
|
// Contiguous write
|
||||||
memcpy(m_dataPtr + offset, data, size);
|
std::memcpy(m_data_ptr + offset, data, size);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// Split write
|
// Split write
|
||||||
UINT32 firstChunk = m_capacity - offset;
|
u32 first_chunk = m_capacity - offset;
|
||||||
UINT32 secondChunk = size - firstChunk;
|
u32 second_chunk = size - first_chunk;
|
||||||
|
|
||||||
const UINT8 *src = static_cast<const UINT8 *>(data);
|
const u8 *src = static_cast<const u8 *>(data);
|
||||||
|
|
||||||
memcpy(m_dataPtr + offset, src, firstChunk);
|
std::memcpy(m_data_ptr + offset, src, first_chunk);
|
||||||
memcpy(m_dataPtr, src + firstChunk, secondChunk);
|
std::memcpy(m_data_ptr, src + first_chunk, second_chunk);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VOID RingBufferView::ReadWrapped(IN UINT32 offset, OUT PVOID outData, IN UINT32 size)
|
inline auto RingBufferView::read_wrapped(u32 offset, void *out_data, u32 size)
|
||||||
{
|
-> void {
|
||||||
if (offset + size <= m_capacity)
|
if (offset + size <= m_capacity) {
|
||||||
{
|
|
||||||
// Contiguous read
|
// Contiguous read
|
||||||
memcpy(outData, m_dataPtr + offset, size);
|
std::memcpy(out_data, m_data_ptr + offset, size);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// Split read
|
// Split read
|
||||||
UINT32 firstChunk = m_capacity - offset;
|
u32 first_chunk = m_capacity - offset;
|
||||||
UINT32 secondChunk = size - firstChunk;
|
u32 second_chunk = size - first_chunk;
|
||||||
|
|
||||||
UINT8 *dst = static_cast<UINT8 *>(outData);
|
u8 *dst = static_cast<u8 *>(out_data);
|
||||||
|
|
||||||
memcpy(dst, m_dataPtr + offset, firstChunk);
|
std::memcpy(dst, m_data_ptr + offset, first_chunk);
|
||||||
memcpy(dst + firstChunk, m_dataPtr, secondChunk);
|
std::memcpy(dst + first_chunk, m_data_ptr, second_chunk);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -16,58 +16,55 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <IACore/PCH.hpp>
|
#include <IACore/PCH.hpp>
|
||||||
|
#include <deque>
|
||||||
|
#include <functional>
|
||||||
|
#include <stop_token>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
class AsyncOps {
|
||||||
class AsyncOps
|
public:
|
||||||
{
|
using TaskTag = u64;
|
||||||
public:
|
using WorkerId = u16;
|
||||||
using TaskTag = UINT64;
|
|
||||||
using WorkerID = UINT16;
|
|
||||||
|
|
||||||
STATIC CONSTEXPR WorkerID MainThreadWorkerID = 0;
|
static constexpr WorkerId MAIN_THREAD_WORKER_ID = 0;
|
||||||
|
|
||||||
enum class Priority : UINT8
|
enum class Priority : u8 { High, Normal };
|
||||||
{
|
|
||||||
High,
|
struct Schedule {
|
||||||
Normal
|
std::atomic<i32> counter{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Schedule
|
public:
|
||||||
{
|
static auto initialize_scheduler(u8 worker_count = 0) -> Result<void>;
|
||||||
Atomic<INT32> Counter{0};
|
static auto terminate_scheduler() -> void;
|
||||||
|
|
||||||
|
static auto schedule_task(std::function<void(WorkerId worker_id)> task,
|
||||||
|
TaskTag tag, Schedule *schedule,
|
||||||
|
Priority priority = Priority::Normal) -> void;
|
||||||
|
|
||||||
|
static auto cancel_tasks_of_tag(TaskTag tag) -> void;
|
||||||
|
|
||||||
|
static auto wait_for_schedule_completion(Schedule *schedule) -> void;
|
||||||
|
|
||||||
|
static auto run_task(std::function<void()> task) -> void;
|
||||||
|
|
||||||
|
[[nodiscard]] static auto get_worker_count() -> WorkerId;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct ScheduledTask {
|
||||||
|
TaskTag tag{};
|
||||||
|
Schedule *schedule_handle{};
|
||||||
|
std::function<void(WorkerId worker_id)> task{};
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
static auto schedule_worker_loop(std::stop_token stop_token,
|
||||||
STATIC VOID InitializeScheduler(IN UINT8 workerCount = 0);
|
WorkerId worker_id) -> void;
|
||||||
STATIC VOID TerminateScheduler();
|
|
||||||
|
|
||||||
STATIC VOID ScheduleTask(IN Function<VOID(IN WorkerID workerID)> task, IN TaskTag tag, IN Schedule *schedule,
|
private:
|
||||||
IN Priority priority = Priority::Normal);
|
static std::mutex s_queue_mutex;
|
||||||
|
static std::condition_variable s_wake_condition;
|
||||||
STATIC VOID CancelTasksOfTag(IN TaskTag tag);
|
static Vec<std::jthread> s_schedule_workers;
|
||||||
|
static std::deque<ScheduledTask> s_high_priority_queue;
|
||||||
STATIC VOID WaitForScheduleCompletion(IN Schedule *schedule);
|
static std::deque<ScheduledTask> s_normal_priority_queue;
|
||||||
|
};
|
||||||
STATIC VOID RunTask(IN Function<VOID()> task);
|
|
||||||
|
|
||||||
STATIC WorkerID GetWorkerCount();
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct ScheduledTask
|
|
||||||
{
|
|
||||||
TaskTag Tag{};
|
|
||||||
Schedule *ScheduleHandle{};
|
|
||||||
Function<VOID(IN WorkerID workerID)> Task{};
|
|
||||||
};
|
|
||||||
|
|
||||||
STATIC VOID ScheduleWorkerLoop(IN StopToken stopToken, IN WorkerID workerID);
|
|
||||||
|
|
||||||
private:
|
|
||||||
STATIC Mutex s_queueMutex;
|
|
||||||
STATIC ConditionVariable s_wakeCondition;
|
|
||||||
STATIC Vector<JoiningThread> s_scheduleWorkers;
|
|
||||||
STATIC Deque<ScheduledTask> s_highPriorityQueue;
|
|
||||||
STATIC Deque<ScheduledTask> s_normalPriorityQueue;
|
|
||||||
};
|
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -30,41 +30,41 @@ namespace IACore
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CLIParser(IN Span<CONST String> args);
|
CLIParser(Span<const String> args);
|
||||||
~CLIParser() = default;
|
~CLIParser() = default;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BOOL Remaining() CONST
|
auto remaining() const -> bool
|
||||||
{
|
{
|
||||||
return m_currentArg < m_argList.end();
|
return m_currentArg < m_argList.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
StringView Peek() CONST
|
auto peek() const -> StringView
|
||||||
{
|
{
|
||||||
if (!Remaining())
|
if (!remaining())
|
||||||
return "";
|
return "";
|
||||||
return *m_currentArg;
|
return *m_currentArg;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringView Next()
|
auto next() -> StringView
|
||||||
{
|
{
|
||||||
if (!Remaining())
|
if (!remaining())
|
||||||
return "";
|
return "";
|
||||||
return *m_currentArg++;
|
return *m_currentArg++;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL Consume(IN CONST StringView &expected)
|
auto consume(const StringView &expected) -> bool
|
||||||
{
|
{
|
||||||
if (Peek() == expected)
|
if (peek() == expected)
|
||||||
{
|
{
|
||||||
Next();
|
next();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CONST Span<CONST String> m_argList;
|
const Span<const String> m_argList;
|
||||||
Span<CONST String>::const_iterator m_currentArg;
|
Span<const String>::const_iterator m_currentArg;
|
||||||
};
|
};
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -17,36 +17,29 @@
|
|||||||
|
|
||||||
#include <IACore/PCH.hpp>
|
#include <IACore/PCH.hpp>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
class DataOps {
|
||||||
class DataOps
|
public:
|
||||||
{
|
enum class CompressionType { None, Gzip, Zlib };
|
||||||
public:
|
|
||||||
enum class CompressionType
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
Gzip,
|
|
||||||
Zlib
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
STATIC UINT32 Hash_FNV1A(IN CONST String &string);
|
static auto hash_fnv1a(const String &string) -> u32;
|
||||||
STATIC UINT32 Hash_FNV1A(IN Span<CONST UINT8> data);
|
static auto hash_fnv1a(Span<const u8> data) -> u32;
|
||||||
|
|
||||||
STATIC UINT32 Hash_xxHash(IN CONST String &string, IN UINT32 seed = 0);
|
static auto hash_xxhash(const String &string, u32 seed = 0) -> u32;
|
||||||
STATIC UINT32 Hash_xxHash(IN Span<CONST UINT8> data, IN UINT32 seed = 0);
|
static auto hash_xxhash(Span<const u8> data, u32 seed = 0) -> u32;
|
||||||
|
|
||||||
STATIC UINT32 CRC32(IN Span<CONST UINT8> data);
|
static auto crc32(Span<const u8> data) -> u32;
|
||||||
|
|
||||||
STATIC CompressionType DetectCompression(IN Span<CONST UINT8> data);
|
static auto detect_compression(Span<const u8> data) -> CompressionType;
|
||||||
|
|
||||||
STATIC EXPECT(Vector<UINT8>) GZipInflate(IN Span<CONST UINT8> data);
|
static auto gzip_inflate(Span<const u8> data) -> Result<Vec<u8>>;
|
||||||
STATIC EXPECT(Vector<UINT8>) GZipDeflate(IN Span<CONST UINT8> data);
|
static auto gzip_deflate(Span<const u8> data) -> Result<Vec<u8>>;
|
||||||
|
|
||||||
STATIC EXPECT(Vector<UINT8>) ZlibInflate(IN Span<CONST UINT8> data);
|
static auto zlib_inflate(Span<const u8> data) -> Result<Vec<u8>>;
|
||||||
STATIC EXPECT(Vector<UINT8>) ZlibDeflate(IN Span<CONST UINT8> data);
|
static auto zlib_deflate(Span<const u8> data) -> Result<Vec<u8>>;
|
||||||
|
|
||||||
STATIC EXPECT(Vector<UINT8>) ZstdInflate(IN Span<CONST UINT8> data);
|
static auto zstd_inflate(Span<const u8> data) -> Result<Vec<u8>>;
|
||||||
STATIC EXPECT(Vector<UINT8>) ZstdDeflate(IN Span<CONST UINT8> data);
|
static auto zstd_deflate(Span<const u8> data) -> Result<Vec<u8>>;
|
||||||
};
|
};
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -18,31 +18,24 @@
|
|||||||
#include <IACore/PCH.hpp>
|
#include <IACore/PCH.hpp>
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
# include <libloaderapi.h>
|
#include <errhandlingapi.h>
|
||||||
# include <errhandlingapi.h>
|
#include <libloaderapi.h>
|
||||||
#else
|
#else
|
||||||
# include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
|
||||||
|
|
||||||
class DynamicLib
|
class DynamicLib {
|
||||||
{
|
public:
|
||||||
public:
|
DynamicLib() : m_handle(nullptr) {}
|
||||||
DynamicLib() : m_handle(nullptr)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
DynamicLib(DynamicLib &&other) NOEXCEPT : m_handle(other.m_handle)
|
DynamicLib(DynamicLib &&other) NOEXCEPT : m_handle(other.m_handle) {
|
||||||
{
|
|
||||||
other.m_handle = nullptr;
|
other.m_handle = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamicLib &operator=(DynamicLib &&other) NOEXCEPT
|
DynamicLib &operator=(DynamicLib &&other) NOEXCEPT {
|
||||||
{
|
if (this != &other) {
|
||||||
if (this != &other)
|
|
||||||
{
|
|
||||||
Unload(); // Free current if exists
|
Unload(); // Free current if exists
|
||||||
m_handle = other.m_handle;
|
m_handle = other.m_handle;
|
||||||
other.m_handle = nullptr;
|
other.m_handle = nullptr;
|
||||||
@ -50,25 +43,21 @@ namespace IACore
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamicLib(CONST DynamicLib &) = delete;
|
DynamicLib(const DynamicLib &) = delete;
|
||||||
DynamicLib &operator=(CONST DynamicLib &) = delete;
|
DynamicLib &operator=(const DynamicLib &) = delete;
|
||||||
|
|
||||||
~DynamicLib()
|
~DynamicLib() { Unload(); }
|
||||||
{
|
|
||||||
Unload();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Automatically detects extension (.dll/.so) if not provided
|
// Automatically detects extension (.dll/.so) if not provided
|
||||||
NO_DISCARD("Check for load errors")
|
NO_DISCARD("Check for load errors")
|
||||||
|
|
||||||
STATIC tl::EXPECT(DynamicLib) Load(CONST String &searchPath, CONST String &name)
|
static tl::Result<DynamicLib> Load(const String &searchPath,
|
||||||
{
|
const String &name) {
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
fs::path fullPath = fs::path(searchPath) / name;
|
fs::path fullPath = fs::path(searchPath) / name;
|
||||||
|
|
||||||
if (!fullPath.has_extension())
|
if (!fullPath.has_extension()) {
|
||||||
{
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
fullPath += ".dll";
|
fullPath += ".dll";
|
||||||
#elif IA_PLATFORM_MAC
|
#elif IA_PLATFORM_MAC
|
||||||
@ -82,64 +71,59 @@ namespace IACore
|
|||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
HMODULE h = LoadLibraryA(fullPath.string().c_str());
|
HMODULE h = LoadLibraryA(fullPath.string().c_str());
|
||||||
if (!h)
|
if (!h) {
|
||||||
{
|
return (GetWindowsError());
|
||||||
return MakeUnexpected(GetWindowsError());
|
|
||||||
}
|
}
|
||||||
lib.m_handle = CAST(h, PVOID);
|
lib.m_handle = CAST(h, void *);
|
||||||
#else
|
#else
|
||||||
// RTLD_LAZY: Resolve symbols only as code executes (Standard for plugins)
|
// RTLD_LAZY: Resolve symbols only as code executes (Standard for plugins)
|
||||||
void *h = dlopen(fullPath.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
void *h = dlopen(fullPath.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
||||||
if (!h)
|
if (!h) {
|
||||||
{
|
|
||||||
// dlerror returns a string describing the last error
|
// dlerror returns a string describing the last error
|
||||||
const char *err = dlerror();
|
const char *err = dlerror();
|
||||||
return MakeUnexpected(String(err ? err : "Unknown dlopen error"));
|
return (String(err ? err : "Unknown dlopen error"));
|
||||||
}
|
}
|
||||||
lib.m_handle = h;
|
lib.m_handle = h;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return IA_MOVE(lib);
|
return std::move(lib);
|
||||||
}
|
}
|
||||||
|
|
||||||
NO_DISCARD("Check if symbol exists")
|
NO_DISCARD("Check if symbol exists")
|
||||||
|
|
||||||
tl::EXPECT(PVOID) GetSymbol(CONST String &name) CONST
|
tl::Result<void *> GetSymbol(const String &name) const {
|
||||||
{
|
|
||||||
if (!m_handle)
|
if (!m_handle)
|
||||||
return MakeUnexpected(String("Library not loaded"));
|
return (String("Library not loaded"));
|
||||||
|
|
||||||
PVOID sym = nullptr;
|
void *sym = nullptr;
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
sym = CAST(GetProcAddress(CAST(m_handle, HMODULE), name.c_str()), PVOID);
|
sym = CAST(GetProcAddress(CAST(m_handle, HMODULE), name.c_str()), void *);
|
||||||
if (!sym)
|
if (!sym)
|
||||||
return MakeUnexpected(GetWindowsError());
|
return (GetWindowsError());
|
||||||
#else
|
#else
|
||||||
// Clear any previous error
|
// Clear any previous error
|
||||||
dlerror();
|
dlerror();
|
||||||
sym = dlsym(m_handle, name.c_str());
|
sym = dlsym(m_handle, name.c_str());
|
||||||
const char *err = dlerror();
|
const char *err = dlerror();
|
||||||
if (err)
|
if (err)
|
||||||
return MakeUnexpected(String(err));
|
return (String(err));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Template helper for casting
|
// Template helper for casting
|
||||||
template<typename FuncT> tl::EXPECT(FuncT) GetFunction(CONST String &name) CONST
|
template <typename FuncT>
|
||||||
{
|
tl::Result<FuncT> GetFunction(const String &name) const {
|
||||||
auto res = GetSymbol(name);
|
auto res = GetSymbol(name);
|
||||||
if (!res)
|
if (!res)
|
||||||
return MakeUnexpected(res.error());
|
return (res.error());
|
||||||
return REINTERPRET(*res, FuncT);
|
return REINTERPRET(*res, FuncT);
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID Unload()
|
void Unload() {
|
||||||
{
|
if (m_handle) {
|
||||||
if (m_handle)
|
|
||||||
{
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
FreeLibrary(CAST(m_handle, HMODULE));
|
FreeLibrary(CAST(m_handle, HMODULE));
|
||||||
#else
|
#else
|
||||||
@ -149,30 +133,28 @@ namespace IACore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL IsLoaded() CONST
|
bool IsLoaded() const { return m_handle != nullptr; }
|
||||||
{
|
|
||||||
return m_handle != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PVOID m_handle;
|
void *m_handle;
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
STATIC String GetWindowsError()
|
static String GetWindowsError() {
|
||||||
{
|
|
||||||
DWORD errorID = ::GetLastError();
|
DWORD errorID = ::GetLastError();
|
||||||
if (errorID == 0)
|
if (errorID == 0)
|
||||||
return String();
|
return String();
|
||||||
|
|
||||||
LPSTR messageBuffer = nullptr;
|
LPSTR messageBuffer = nullptr;
|
||||||
size_t size = FormatMessageA(
|
size_t size = FormatMessageA(
|
||||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
||||||
errorID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR) &messageBuffer, 0, NULL);
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||||
|
NULL, errorID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||||
|
(LPSTR)&messageBuffer, 0, NULL);
|
||||||
|
|
||||||
String message(messageBuffer, size);
|
String message(messageBuffer, size);
|
||||||
LocalFree(messageBuffer);
|
LocalFree(messageBuffer);
|
||||||
return String("Win32 Error: ") + message;
|
return String("Win32 Error: ") + message;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -22,7 +22,7 @@ namespace IACore
|
|||||||
class Environment
|
class Environment
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
STATIC Optional<String> Find(CONST String &name)
|
static Optional<String> Find(const String &name)
|
||||||
{
|
{
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
DWORD bufferSize = GetEnvironmentVariableA(name.c_str(), nullptr, 0);
|
DWORD bufferSize = GetEnvironmentVariableA(name.c_str(), nullptr, 0);
|
||||||
@ -61,12 +61,12 @@ namespace IACore
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC String Get(CONST String &name, CONST String &defaultValue = "")
|
static String Get(const String &name, const String &defaultValue = "")
|
||||||
{
|
{
|
||||||
return Find(name).value_or(defaultValue);
|
return Find(name).value_or(defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC BOOL Set(CONST String &name, CONST String &value)
|
static bool Set(const String &name, const String &value)
|
||||||
{
|
{
|
||||||
if (name.empty())
|
if (name.empty())
|
||||||
return FALSE;
|
return FALSE;
|
||||||
@ -79,7 +79,7 @@ namespace IACore
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC BOOL Unset(CONST String &name)
|
static bool Unset(const String &name)
|
||||||
{
|
{
|
||||||
if (name.empty())
|
if (name.empty())
|
||||||
return FALSE;
|
return FALSE;
|
||||||
@ -91,7 +91,7 @@ namespace IACore
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC BOOL Exists(CONST String &name)
|
static bool Exists(const String &name)
|
||||||
{
|
{
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
return GetEnvironmentVariableA(name.c_str(), nullptr, 0) > 0;
|
return GetEnvironmentVariableA(name.c_str(), nullptr, 0) > 0;
|
||||||
|
|||||||
@ -15,107 +15,107 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <IACore/PCH.hpp>
|
||||||
#include <IACore/StreamReader.hpp>
|
#include <IACore/StreamReader.hpp>
|
||||||
#include <IACore/StreamWriter.hpp>
|
#include <IACore/StreamWriter.hpp>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
using NativeFileHandle = HANDLE;
|
using NativeFileHandle = HANDLE;
|
||||||
STATIC CONSTEXPR NativeFileHandle INVALID_FILE_HANDLE = INVALID_HANDLE_VALUE;
|
static constexpr NativeFileHandle INVALID_FILE_HANDLE = INVALID_HANDLE_VALUE;
|
||||||
#else
|
#else
|
||||||
using NativeFileHandle = int;
|
using NativeFileHandle = int;
|
||||||
STATIC CONSTEXPR NativeFileHandle INVALID_FILE_HANDLE = -1;
|
static constexpr NativeFileHandle INVALID_FILE_HANDLE = -1;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
|
||||||
class FileOps
|
class FileOps {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
class MemoryMappedRegion;
|
class MemoryMappedRegion;
|
||||||
|
|
||||||
enum class EFileAccess : UINT8
|
enum class FileAccess : u8 {
|
||||||
{
|
Read, // Read-only
|
||||||
READ, // Read-only
|
Write, // Write-only
|
||||||
WRITE, // Write-only
|
ReadWrite // Read and Write
|
||||||
READ_WRITE // Read and Write
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class EFileMode : UINT8
|
enum class FileMode : u8 {
|
||||||
{
|
OpenExisting, // Fails if file doesn't exist
|
||||||
OPEN_EXISTING, // Fails if file doesn't exist
|
OpenAlways, // Opens if exists, creates if not
|
||||||
OPEN_ALWAYS, // Opens if exists, creates if not
|
CreateNew, // Fails if file exists
|
||||||
CREATE_NEW, // Fails if file exists
|
CreateAlways, // Overwrites existing
|
||||||
CREATE_ALWAYS, // Overwrites existing
|
TruncateExisting // Opens existing and clears it
|
||||||
TRUNCATE_EXISTING // Opens existing and clears it
|
|
||||||
};
|
};
|
||||||
|
|
||||||
STATIC EXPECT(NativeFileHandle) NativeOpenFile(IN CONST FilePath &path, IN EFileAccess access,
|
static auto native_open_file(const Path &path, FileAccess access,
|
||||||
IN EFileMode mode, IN UINT32 permissions = 0644);
|
FileMode mode, u32 permissions = 0644)
|
||||||
STATIC VOID NativeCloseFile(IN NativeFileHandle handle);
|
-> Result<NativeFileHandle>;
|
||||||
|
|
||||||
public:
|
static auto native_close_file(NativeFileHandle handle) -> void;
|
||||||
STATIC FilePath NormalizeExecutablePath(IN CONST FilePath &path);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
STATIC VOID UnmapFile(IN PCUINT8 mappedPtr);
|
static auto normalize_executable_path(const Path &path) -> Path;
|
||||||
STATIC EXPECT(PCUINT8) MapFile(IN CONST FilePath &path, OUT SIZE_T &size);
|
|
||||||
|
|
||||||
// @param `isOwner` TRUE to allocate/truncate. FALSE to just open.
|
public:
|
||||||
STATIC EXPECT(PUINT8) MapSharedMemory(IN CONST String &name, IN SIZE_T size, IN BOOL isOwner);
|
static auto unmap_file(const u8 *mapped_ptr) -> void;
|
||||||
STATIC VOID UnlinkSharedMemory(IN CONST String &name);
|
|
||||||
|
|
||||||
STATIC EXPECT(StreamReader) StreamFromFile(IN CONST FilePath &path);
|
static auto map_file(const Path &path, usize &size) -> Result<const u8 *>;
|
||||||
STATIC EXPECT(StreamWriter) StreamToFile(IN CONST FilePath &path, IN BOOL overwrite = false);
|
|
||||||
|
|
||||||
STATIC EXPECT(String) ReadTextFile(IN CONST FilePath &path);
|
// @param `is_owner` true to allocate/truncate. false to just open.
|
||||||
STATIC EXPECT(Vector<UINT8>) ReadBinaryFile(IN CONST FilePath &path);
|
static auto map_shared_memory(const String &name, usize size, bool is_owner)
|
||||||
STATIC EXPECT(SIZE_T)
|
-> Result<u8 *>;
|
||||||
WriteTextFile(IN CONST FilePath &path, IN CONST String &contents, IN BOOL overwrite = false);
|
|
||||||
STATIC EXPECT(SIZE_T)
|
|
||||||
WriteBinaryFile(IN CONST FilePath &path, IN Span<UINT8> contents, IN BOOL overwrite = false);
|
|
||||||
|
|
||||||
private:
|
static auto unlink_shared_memory(const String &name) -> void;
|
||||||
STATIC UnorderedMap<PCUINT8, Tuple<PVOID, PVOID, PVOID>> s_mappedFiles;
|
|
||||||
};
|
|
||||||
|
|
||||||
class FileOps::MemoryMappedRegion
|
static auto stream_from_file(const Path &path) -> Result<StreamReader>;
|
||||||
{
|
|
||||||
public:
|
static auto stream_to_file(const Path &path, bool overwrite = false)
|
||||||
|
-> Result<StreamWriter>;
|
||||||
|
|
||||||
|
static auto read_text_file(const Path &path) -> Result<String>;
|
||||||
|
|
||||||
|
static auto read_binary_file(const Path &path) -> Result<Vec<u8>>;
|
||||||
|
|
||||||
|
static auto write_text_file(const Path &path, const String &contents,
|
||||||
|
bool overwrite = false) -> Result<usize>;
|
||||||
|
|
||||||
|
static auto write_binary_file(const Path &path, Span<const u8> contents,
|
||||||
|
bool overwrite = false) -> Result<usize>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static HashMap<const u8 *, std::tuple<void *, void *, void *>> s_mapped_files;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FileOps::MemoryMappedRegion {
|
||||||
|
public:
|
||||||
MemoryMappedRegion() = default;
|
MemoryMappedRegion() = default;
|
||||||
~MemoryMappedRegion();
|
~MemoryMappedRegion();
|
||||||
|
|
||||||
MemoryMappedRegion(CONST MemoryMappedRegion &) = delete;
|
MemoryMappedRegion(const MemoryMappedRegion &) = delete;
|
||||||
MemoryMappedRegion &operator=(CONST MemoryMappedRegion &) = delete;
|
auto operator=(const MemoryMappedRegion &) -> MemoryMappedRegion & = delete;
|
||||||
|
|
||||||
MemoryMappedRegion(MemoryMappedRegion &&other) NOEXCEPT;
|
MemoryMappedRegion(MemoryMappedRegion &&other) noexcept;
|
||||||
MemoryMappedRegion &operator=(MemoryMappedRegion &&other) NOEXCEPT;
|
auto operator=(MemoryMappedRegion &&other) noexcept -> MemoryMappedRegion &;
|
||||||
|
|
||||||
EXPECT(VOID) Map(NativeFileHandle handle, UINT64 offset, SIZE_T size);
|
auto map(NativeFileHandle handle, u64 offset, usize size) -> Result<void>;
|
||||||
|
|
||||||
VOID Unmap();
|
auto unmap() -> void;
|
||||||
VOID Flush();
|
auto flush() -> void;
|
||||||
|
|
||||||
PUINT8 GetPtr() CONST
|
[[nodiscard]] auto get_ptr() const -> u8 * { return m_ptr; }
|
||||||
{
|
|
||||||
return m_ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
SIZE_T GetSize() CONST
|
[[nodiscard]] auto get_size() const -> usize { return m_size; }
|
||||||
{
|
|
||||||
return m_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
BOOL IsValid() CONST
|
[[nodiscard]] auto is_valid() const -> bool { return m_ptr != nullptr; }
|
||||||
{
|
|
||||||
return m_ptr != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PUINT8 m_ptr{nullptr};
|
u8 *m_ptr = nullptr;
|
||||||
SIZE_T m_size{0};
|
usize m_size = 0;
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
HANDLE m_hMap{NULL};
|
HANDLE m_map_handle = NULL;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -17,74 +17,72 @@
|
|||||||
|
|
||||||
#include <IACore/Http/Common.hpp>
|
#include <IACore/Http/Common.hpp>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
class HttpClient : public HttpCommon {
|
||||||
class HttpClient : public HttpCommon
|
public:
|
||||||
{
|
static Result<UniquePtr<HttpClient>> Create(const String &host);
|
||||||
public:
|
|
||||||
STATIC EXPECT(UniquePtr<HttpClient>) Create(IN CONST String &host);
|
|
||||||
|
|
||||||
~HttpClient();
|
~HttpClient();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
EXPECT(String)
|
Result<String>
|
||||||
RawGet(IN CONST String &path, IN Span<CONST Header> headers,
|
RawGet(const String &path, Span<const Header> headers,
|
||||||
IN PCCHAR defaultContentType = "application/x-www-form-urlencoded");
|
const char *defaultContentType = "application/x-www-form-urlencoded");
|
||||||
EXPECT(String)
|
Result<String>
|
||||||
RawPost(IN CONST String &path, IN Span<CONST Header> headers, IN CONST String &body,
|
RawPost(const String &path, Span<const Header> headers, const String &body,
|
||||||
IN PCCHAR defaultContentType = "application/x-www-form-urlencoded");
|
const char *defaultContentType = "application/x-www-form-urlencoded");
|
||||||
|
|
||||||
template<typename _response_type>
|
template <typename _response_type>
|
||||||
EXPECT(_response_type)
|
Result<_response_type> JsonGet(const String &path,
|
||||||
JsonGet(IN CONST String &path, IN Span<CONST Header> headers);
|
Span<const Header> headers);
|
||||||
|
|
||||||
template<typename _payload_type, typename _response_type>
|
template <typename _payload_type, typename _response_type>
|
||||||
EXPECT(_response_type)
|
Result<_response_type> JsonPost(const String &path,
|
||||||
JsonPost(IN CONST String &path, IN Span<CONST Header> headers, IN CONST _payload_type &body);
|
Span<const Header> headers,
|
||||||
|
const _payload_type &body);
|
||||||
|
|
||||||
// Certificate verfication is enabled by default
|
// Certificate verfication is enabled by default
|
||||||
VOID EnableCertificateVerfication();
|
void EnableCertificateVerfication();
|
||||||
VOID DisableCertificateVerfication();
|
void DisableCertificateVerfication();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
EResponseCode LastResponseCode()
|
EResponseCode LastResponseCode() { return m_lastResponseCode; }
|
||||||
{
|
|
||||||
return m_lastResponseCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
httplib::Client m_client;
|
httplib::Client m_client;
|
||||||
EResponseCode m_lastResponseCode;
|
EResponseCode m_lastResponseCode;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
String PreprocessResponse(IN CONST String &response);
|
String PreprocessResponse(const String &response);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
HttpClient(IN httplib::Client &&client);
|
HttpClient(httplib::Client &&client);
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename _response_type>
|
template <typename _response_type>
|
||||||
EXPECT(_response_type)
|
Result<_response_type> HttpClient::JsonGet(const String &path,
|
||||||
HttpClient::JsonGet(IN CONST String &path, IN Span<CONST Header> headers)
|
Span<const Header> headers) {
|
||||||
{
|
|
||||||
const auto rawResponse = RawGet(path, headers, "application/json");
|
const auto rawResponse = RawGet(path, headers, "application/json");
|
||||||
if (!rawResponse)
|
if (!rawResponse)
|
||||||
return MakeUnexpected(rawResponse.error());
|
return (rawResponse.error());
|
||||||
if (LastResponseCode() != EResponseCode::OK)
|
if (LastResponseCode() != EResponseCode::OK)
|
||||||
return MakeUnexpected(std::format("Server responded with code {}", (INT32) LastResponseCode()));
|
return (
|
||||||
|
std::format("Server responded with code {}", (i32)LastResponseCode()));
|
||||||
return JSON::ParseToStruct<_response_type>(*rawResponse);
|
return JSON::ParseToStruct<_response_type>(*rawResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename _payload_type, typename _response_type>
|
template <typename _payload_type, typename _response_type>
|
||||||
EXPECT(_response_type)
|
Result<_response_type> HttpClient::JsonPost(const String &path,
|
||||||
HttpClient::JsonPost(IN CONST String &path, IN Span<CONST Header> headers, IN CONST _payload_type &body)
|
Span<const Header> headers,
|
||||||
{
|
const _payload_type &body) {
|
||||||
const auto encodedBody = IA_TRY(JSON::EncodeStruct(body));
|
const auto encodedBody = IA_TRY(JSON::EncodeStruct(body));
|
||||||
const auto rawResponse = RawPost(path, headers, encodedBody, "application/json");
|
const auto rawResponse =
|
||||||
|
RawPost(path, headers, encodedBody, "application/json");
|
||||||
if (!rawResponse)
|
if (!rawResponse)
|
||||||
return MakeUnexpected(rawResponse.error());
|
return (rawResponse.error());
|
||||||
if (LastResponseCode() != EResponseCode::OK)
|
if (LastResponseCode() != EResponseCode::OK)
|
||||||
return MakeUnexpected(std::format("Server responded with code {}", (INT32) LastResponseCode()));
|
return (
|
||||||
|
std::format("Server responded with code {}", (i32)LastResponseCode()));
|
||||||
return JSON::ParseToStruct<_response_type>(*rawResponse);
|
return JSON::ParseToStruct<_response_type>(*rawResponse);
|
||||||
}
|
}
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -19,13 +19,10 @@
|
|||||||
|
|
||||||
#include <httplib.h>
|
#include <httplib.h>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
class HttpCommon {
|
||||||
class HttpCommon
|
public:
|
||||||
{
|
enum class EHeaderType {
|
||||||
public:
|
|
||||||
enum class EHeaderType
|
|
||||||
{
|
|
||||||
ACCEPT,
|
ACCEPT,
|
||||||
ACCEPT_CHARSET,
|
ACCEPT_CHARSET,
|
||||||
ACCEPT_ENCODING,
|
ACCEPT_ENCODING,
|
||||||
@ -54,8 +51,7 @@ namespace IACore
|
|||||||
WARNING
|
WARNING
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class EResponseCode : INT32
|
enum class EResponseCode : i32 {
|
||||||
{
|
|
||||||
// 1xx Informational
|
// 1xx Informational
|
||||||
CONTINUE = 100,
|
CONTINUE = 100,
|
||||||
SWITCHING_PROTOCOLS = 101,
|
SWITCHING_PROTOCOLS = 101,
|
||||||
@ -131,27 +127,27 @@ namespace IACore
|
|||||||
|
|
||||||
using Header = KeyValuePair<String, String>;
|
using Header = KeyValuePair<String, String>;
|
||||||
|
|
||||||
STATIC String UrlEncode(IN CONST String &value);
|
static String UrlEncode(const String &value);
|
||||||
STATIC String UrlDecode(IN CONST String &value);
|
static String UrlDecode(const String &value);
|
||||||
|
|
||||||
STATIC String HeaderTypeToString(IN EHeaderType type);
|
static String HeaderTypeToString(EHeaderType type);
|
||||||
|
|
||||||
STATIC INLINE Header CreateHeader(IN EHeaderType key, IN CONST String &value);
|
static inline Header CreateHeader(EHeaderType key, const String &value);
|
||||||
STATIC INLINE Header CreateHeader(IN CONST String &key, IN CONST String &value);
|
static inline Header CreateHeader(const String &key, const String &value);
|
||||||
|
|
||||||
STATIC BOOL IsSuccessResponseCode(IN EResponseCode code);
|
static bool IsSuccessResponseCode(EResponseCode code);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
HttpCommon() = default;
|
HttpCommon() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
HttpCommon::Header HttpCommon::CreateHeader(IN EHeaderType key, IN CONST String &value)
|
HttpCommon::Header HttpCommon::CreateHeader(EHeaderType key,
|
||||||
{
|
const String &value) {
|
||||||
return std::make_pair(HeaderTypeToString(key), value);
|
return std::make_pair(HeaderTypeToString(key), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpCommon::Header HttpCommon::CreateHeader(IN CONST String &key, IN CONST String &value)
|
HttpCommon::Header HttpCommon::CreateHeader(const String &key,
|
||||||
{
|
const String &value) {
|
||||||
return std::make_pair(key, value);
|
return std::make_pair(key, value);
|
||||||
}
|
}
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -22,6 +22,6 @@ namespace IACore
|
|||||||
class HttpServer : public HttpCommon
|
class HttpServer : public HttpCommon
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
HttpServer(IN CONST String &host, IN UINT32 port);
|
HttpServer(const String &host, u32 port);
|
||||||
};
|
};
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -16,58 +16,54 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <IACore/PCH.hpp>
|
#include <IACore/PCH.hpp>
|
||||||
|
#include <IACore/Logger.hpp>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#define IACORE_MAIN() \
|
||||||
|
auto _app_entry(const IACore::Vec<IACore::String> &args) -> IACore::Result<IACore::i32>; \
|
||||||
# include <IACore/Logger.hpp>
|
auto main(int argc, char *argv[]) -> int \
|
||||||
|
{ \
|
||||||
# define IACORE_MAIN() \
|
IACore::i32 exit_code = 0; \
|
||||||
EXPECT(INT32) _app_entry(IN CONST Vector<String> &args); \
|
IACore::initialize(); \
|
||||||
int main(int argc, char *argv[]) \
|
IACore::Vec<IACore::String> args; \
|
||||||
|
args.reserve(static_cast<IACore::usize>(argc)); \
|
||||||
|
for (int i = 0; i < argc; ++i) \
|
||||||
{ \
|
{ \
|
||||||
int exitCode = 0; \
|
|
||||||
IACore::Initialize(); \
|
|
||||||
Vector<String> args; \
|
|
||||||
for (int i = 0; i < argc; i++) \
|
|
||||||
args.push_back(argv[i]); \
|
args.push_back(argv[i]); \
|
||||||
|
} \
|
||||||
const auto result = _app_entry(args); \
|
const auto result = _app_entry(args); \
|
||||||
if (!result) \
|
if (!result) \
|
||||||
{ \
|
{ \
|
||||||
IACore::Logger::Error("Application exited with an error: '{}'.", result.error()); \
|
IACore::Logger::error("Application exited with an error: '{}'.", result.error()); \
|
||||||
exitCode = -20; \
|
exit_code = -20; \
|
||||||
} \
|
} \
|
||||||
exitCode = *result; \
|
|
||||||
if (!exitCode) \
|
|
||||||
IACore::Logger::Info("Application exited successfully."); \
|
|
||||||
else \
|
else \
|
||||||
IACore::Logger::Error("Application exited with error code: {}.", exitCode); \
|
{ \
|
||||||
IACore::Terminate(); \
|
exit_code = *result; \
|
||||||
return exitCode; \
|
if (exit_code == 0) \
|
||||||
|
{ \
|
||||||
|
IACore::Logger::info("Application exited successfully."); \
|
||||||
} \
|
} \
|
||||||
EXPECT(INT32) _app_entry(IN CONST Vector<String> &args)
|
else \
|
||||||
|
{ \
|
||||||
|
IACore::Logger::error("Application exited with error code: {}.", exit_code); \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
IACore::terminate(); \
|
||||||
|
return exit_code; \
|
||||||
|
} \
|
||||||
|
auto _app_entry(const IACore::Vec<IACore::String> &args) -> IACore::Result<IACore::i32>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore
|
||||||
{
|
{
|
||||||
// Must be called from main thread
|
// Must be called from main thread
|
||||||
// Safe to call multiple times but, every Initialize call is paired with a corresponding Terminate call
|
// Safe to call multiple times but, every initialize call is paired with a corresponding terminate call
|
||||||
VOID Initialize();
|
auto initialize() -> void;
|
||||||
|
|
||||||
// Must be called from same thread as Initialize
|
// Must be called from same thread as initialize
|
||||||
// Safe to call multiple times but, every Initialize call is paired with a corresponding Terminate call
|
// Safe to call multiple times but, every initialize call is paired with a corresponding terminate call
|
||||||
VOID Terminate();
|
auto terminate() -> void;
|
||||||
|
|
||||||
BOOL IsInitialized();
|
auto is_initialized() -> bool;
|
||||||
|
|
||||||
UINT64 GetUnixTime();
|
auto is_main_thread() -> bool;
|
||||||
UINT64 GetTicksCount();
|
|
||||||
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
|
} // namespace IACore
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@ -17,295 +17,287 @@
|
|||||||
|
|
||||||
#include <IACore/PCH.hpp>
|
#include <IACore/PCH.hpp>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
|
|
||||||
# include <exception>
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Macros
|
// Macros
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
# define valid_iatest_runner(type) iatest::_valid_iatest_runner<type>::value_type
|
#define __iat_micro_test(call) \
|
||||||
|
|
||||||
# define __iat_micro_test(call) \
|
|
||||||
if (!(call)) \
|
if (!(call)) \
|
||||||
return FALSE
|
return false
|
||||||
|
|
||||||
# define IAT_CHECK(v) __iat_micro_test(_test((v), #v))
|
#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_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_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_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_UNIT(func) _test_unit([this]() { return this->func(); }, #func)
|
||||||
# define IAT_NAMED_UNIT(n, func) _test_unit([this]() { return this->func(); }, n)
|
#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) \
|
#define IAT_BEGIN_BLOCK(_group, _name) \
|
||||||
class _group##_##_name : public ia::iatest::block \
|
class _group##_##_name : public ia::iatest::Block \
|
||||||
{ \
|
{ \
|
||||||
public: \
|
public: \
|
||||||
PCCHAR name() CONST OVERRIDE \
|
[[nodiscard]] auto get_name() const -> const char * override \
|
||||||
{ \
|
{ \
|
||||||
return #_group "::" #_name; \
|
return #_group "::" #_name; \
|
||||||
} \
|
} \
|
||||||
\
|
\
|
||||||
private:
|
private:
|
||||||
|
|
||||||
# define IAT_END_BLOCK() \
|
#define IAT_END_BLOCK() \
|
||||||
} \
|
} \
|
||||||
;
|
;
|
||||||
|
|
||||||
# define IAT_BEGIN_TEST_LIST() \
|
#define IAT_BEGIN_TEST_LIST() \
|
||||||
public: \
|
public: \
|
||||||
VOID declareTests() OVERRIDE \
|
void declare_tests() override \
|
||||||
{
|
{
|
||||||
# define IAT_ADD_TEST(name) IAT_UNIT(name)
|
#define IAT_ADD_TEST(name) IAT_UNIT(name)
|
||||||
# define IAT_END_TEST_LIST() \
|
#define IAT_END_TEST_LIST() \
|
||||||
} \
|
} \
|
||||||
\
|
\
|
||||||
private:
|
private:
|
||||||
|
|
||||||
namespace ia::iatest
|
namespace IACore
|
||||||
{
|
{
|
||||||
template<typename T> std::string ToString(CONST T &value)
|
// -------------------------------------------------------------------------
|
||||||
|
// String Conversion Helpers
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
template<typename T> auto to_string(const T &value) -> String
|
||||||
{
|
{
|
||||||
if constexpr (std::is_arithmetic_v<T>)
|
if constexpr (std::is_arithmetic_v<T>)
|
||||||
|
{
|
||||||
return std::to_string(value);
|
return std::to_string(value);
|
||||||
else if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, const char *>)
|
}
|
||||||
return std::string("\"") + value + "\"";
|
else if constexpr (std::is_same_v<T, String> || std::is_same_v<T, const char *> || std::is_same_v<T, char *>)
|
||||||
|
{
|
||||||
|
return String("\"") + String(value) + "\"";
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
return "{Object}";
|
return "{Object}";
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T> std::string ToString(T *value)
|
|
||||||
{
|
|
||||||
if (value == NULLPTR)
|
|
||||||
return "nullptr";
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "ptr(" << (void *) value << ")";
|
|
||||||
return ss.str();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_TYPE(functor_t, std::function<BOOL()>);
|
template<typename T> auto to_string(T *value) -> String
|
||||||
|
|
||||||
struct unit_t
|
|
||||||
{
|
{
|
||||||
std::string Name;
|
if (value == nullptr)
|
||||||
functor_t Functor;
|
{
|
||||||
|
return "nullptr";
|
||||||
|
}
|
||||||
|
return std::format("ptr({})", static_cast<const void *>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Types
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
using TestFunctor = std::function<bool()>;
|
||||||
|
|
||||||
|
struct TestUnit
|
||||||
|
{
|
||||||
|
String name;
|
||||||
|
TestFunctor functor;
|
||||||
};
|
};
|
||||||
|
|
||||||
class block
|
class Block
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual ~block() = default;
|
virtual ~Block() = default;
|
||||||
PURE_VIRTUAL(PCCHAR name() CONST);
|
[[nodiscard]] virtual auto get_name() const -> const char * = 0;
|
||||||
PURE_VIRTUAL(VOID declareTests());
|
virtual void declare_tests() = 0;
|
||||||
|
|
||||||
std::vector<unit_t> &units()
|
auto units() -> Vec<TestUnit> &
|
||||||
{
|
{
|
||||||
return m_units;
|
return m_units;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
template<typename T1, typename T2> BOOL _test_eq(IN CONST T1 &lhs, IN CONST T2 &rhs, IN PCCHAR description)
|
template<typename T1, typename T2> auto _test_eq(const T1 &lhs, const T2 &rhs, const char *description) -> bool
|
||||||
{
|
{
|
||||||
if (lhs != rhs)
|
if (lhs != rhs)
|
||||||
{
|
{
|
||||||
print_fail(description, ToString(lhs), ToString(rhs));
|
print_fail(description, to_string(lhs), to_string(rhs));
|
||||||
return FALSE;
|
return false;
|
||||||
}
|
}
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T1, typename T2> BOOL _test_neq(IN CONST T1 &lhs, IN CONST T2 &rhs, IN PCCHAR description)
|
template<typename T1, typename T2> auto _test_neq(const T1 &lhs, const T2 &rhs, const char *description) -> bool
|
||||||
{
|
{
|
||||||
if (lhs == rhs)
|
if (lhs == rhs)
|
||||||
{
|
{
|
||||||
print_fail(description, ToString(lhs), "NOT " + ToString(rhs));
|
print_fail(description, to_string(lhs), "NOT " + to_string(rhs));
|
||||||
return FALSE;
|
return false;
|
||||||
}
|
}
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T> BOOL _test_approx(IN T lhs, IN T rhs, IN PCCHAR description)
|
template<typename T> auto _test_approx(T lhs, T rhs, const char *description) -> bool
|
||||||
{
|
{
|
||||||
static_assert(std::is_floating_point_v<T>, "Approx only works for floats/doubles");
|
static_assert(std::is_floating_point_v<T>, "Approx only works for floats/doubles");
|
||||||
T diff = std::abs(lhs - rhs);
|
T diff = std::abs(lhs - rhs);
|
||||||
if (diff > static_cast<T>(0.0001))
|
if (diff > static_cast<T>(0.0001))
|
||||||
{
|
{
|
||||||
print_fail(description, ToString(lhs), ToString(rhs));
|
print_fail(description, to_string(lhs), to_string(rhs));
|
||||||
return FALSE;
|
return false;
|
||||||
}
|
}
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL _test(IN BOOL value, IN PCCHAR description)
|
auto _test(bool value, const char *description) -> bool
|
||||||
{
|
{
|
||||||
if (!value)
|
if (!value)
|
||||||
{
|
{
|
||||||
printf(__CC_BLUE " %s... " __CC_RED "FAILED" __CC_DEFAULT "\n", description);
|
std::cout << console::blue << " " << description << "... " << console::red << "FAILED"
|
||||||
return FALSE;
|
<< console::reset << "\n";
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL _test_not(IN BOOL value, IN PCCHAR description)
|
auto _test_not(bool value, const char *description) -> bool
|
||||||
{
|
{
|
||||||
if (value)
|
if (value)
|
||||||
{
|
{
|
||||||
printf(__CC_BLUE " %s... " __CC_RED "FAILED" __CC_DEFAULT "\n", description);
|
std::cout << console::blue << " " << description << "... " << console::red << "FAILED"
|
||||||
return FALSE;
|
<< console::reset << "\n";
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return TRUE;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID _test_unit(IN functor_t functor, IN PCCHAR name)
|
void _test_unit(TestFunctor functor, const char *name)
|
||||||
{
|
{
|
||||||
m_units.push_back({name, functor});
|
m_units.push_back({name, std::move(functor)});
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VOID print_fail(PCCHAR desc, std::string v1, std::string v2)
|
void print_fail(const char *desc, const String &v1, const String &v2)
|
||||||
{
|
{
|
||||||
printf(__CC_BLUE " %s... " __CC_RED "FAILED" __CC_DEFAULT "\n", desc);
|
std::cout << console::blue << " " << desc << "... " << console::red << "FAILED" << console::reset
|
||||||
printf(__CC_RED " Expected: %s" __CC_DEFAULT "\n", v2.c_str());
|
<< "\n";
|
||||||
printf(__CC_RED " Actual: %s" __CC_DEFAULT "\n", v1.c_str());
|
std::cout << console::red << " Expected: " << v2 << console::reset << "\n";
|
||||||
|
std::cout << console::red << " Actual: " << v1 << console::reset << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<unit_t> m_units;
|
Vec<TestUnit> m_units;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename block_class>
|
template<typename T>
|
||||||
concept valid_block_class = std::derived_from<block_class, block>;
|
concept ValidBlockClass = std::derived_from<T, Block>;
|
||||||
|
|
||||||
template<BOOL stopOnFail = false, BOOL isVerbose = false> class runner
|
// -------------------------------------------------------------------------
|
||||||
|
// Runner
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
template<bool StopOnFail = false, bool IsVerbose = false> class Runner
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
runner()
|
Runner() = default;
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
~runner()
|
~Runner()
|
||||||
{
|
{
|
||||||
summarize();
|
summarize();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename block_class>
|
template<typename BlockClass>
|
||||||
requires valid_block_class<block_class>
|
requires ValidBlockClass<BlockClass>
|
||||||
VOID testBlock();
|
void test_block();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VOID summarize();
|
void summarize();
|
||||||
|
|
||||||
private:
|
usize m_test_count{0};
|
||||||
SIZE_T m_testCount{0};
|
usize m_fail_count{0};
|
||||||
SIZE_T m_failCount{0};
|
usize m_block_count{0};
|
||||||
SIZE_T m_blockCount{0};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template<BOOL stopOnFail, BOOL isVerbose>
|
template<bool StopOnFail, bool IsVerbose>
|
||||||
template<typename block_class>
|
template<typename BlockClass>
|
||||||
requires valid_block_class<block_class>
|
requires ValidBlockClass<BlockClass>
|
||||||
VOID runner<stopOnFail, isVerbose>::testBlock()
|
void Runner<StopOnFail, IsVerbose>::test_block()
|
||||||
{
|
{
|
||||||
m_blockCount++;
|
m_block_count++;
|
||||||
block_class b;
|
BlockClass b;
|
||||||
b.declareTests();
|
b.declare_tests();
|
||||||
|
|
||||||
printf(__CC_MAGENTA "Testing [%s]..." __CC_DEFAULT "\n", b.name());
|
std::cout << console::magenta << "Testing [" << b.get_name() << "]..." << console::reset << "\n";
|
||||||
|
|
||||||
for (auto &v : b.units())
|
for (auto &v : b.units())
|
||||||
{
|
{
|
||||||
m_testCount++;
|
m_test_count++;
|
||||||
if constexpr (isVerbose)
|
if constexpr (IsVerbose)
|
||||||
{
|
{
|
||||||
printf(__CC_YELLOW " Testing %s...\n" __CC_DEFAULT, v.Name.c_str());
|
std::cout << console::yellow << " Testing " << v.name << "...\n" << console::reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL result = FALSE;
|
// Exceptions are DISABLED. We assume tests do not crash.
|
||||||
try
|
// If a test crashes (segfault), the OS handles it.
|
||||||
{
|
bool result = v.functor();
|
||||||
result = v.Functor();
|
|
||||||
}
|
|
||||||
catch (const std::exception &e)
|
|
||||||
{
|
|
||||||
printf(__CC_RED " CRITICAL EXCEPTION in %s: %s\n" __CC_DEFAULT, v.Name.c_str(), e.what());
|
|
||||||
result = FALSE;
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
printf(__CC_RED " UNKNOWN CRITICAL EXCEPTION in %s\n" __CC_DEFAULT, v.Name.c_str());
|
|
||||||
result = FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result)
|
if (!result)
|
||||||
{
|
{
|
||||||
m_failCount++;
|
m_fail_count++;
|
||||||
if constexpr (stopOnFail)
|
if constexpr (StopOnFail)
|
||||||
{
|
{
|
||||||
summarize();
|
summarize();
|
||||||
exit(-1);
|
std::exit(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fputs("\n", stdout);
|
std::cout << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
template<BOOL stopOnFail, BOOL isVerbose> VOID runner<stopOnFail, isVerbose>::summarize()
|
template<bool StopOnFail, bool IsVerbose> void Runner<StopOnFail, IsVerbose>::summarize()
|
||||||
{
|
{
|
||||||
printf(__CC_GREEN
|
std::cout << console::green
|
||||||
"\n-----------------------------------\n\t SUMMARY\n-----------------------------------\n");
|
<< "\n-----------------------------------\n\t SUMMARY\n-----------------------------------\n";
|
||||||
|
|
||||||
if (!m_failCount)
|
if (m_fail_count == 0)
|
||||||
{
|
{
|
||||||
printf("\n\tALL TESTS PASSED!\n\n");
|
std::cout << "\n\tALL TESTS PASSED!\n\n";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FLOAT64 successRate =
|
f64 success_rate = (100.0 * static_cast<f64>(m_test_count - m_fail_count) / static_cast<f64>(m_test_count));
|
||||||
(100.0 * static_cast<FLOAT64>(m_testCount - m_failCount) / static_cast<FLOAT64>(m_testCount));
|
std::cout << console::red << m_fail_count << " OF " << m_test_count << " TESTS FAILED\n"
|
||||||
printf(__CC_RED "%zu OUT OF %zu TESTS FAILED\n" __CC_YELLOW "Success Rate: %.2f%%\n", m_failCount,
|
<< console::yellow << std::format("Success Rate: {:.2f}%\n", success_rate);
|
||||||
m_testCount, successRate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
printf(__CC_MAGENTA "Ran %zu test(s) across %zu block(s)\n" __CC_GREEN
|
std::cout << console::magenta << "Ran " << m_test_count << " test(s) across " << m_block_count << " block(s)\n"
|
||||||
"-----------------------------------" __CC_DEFAULT "\n",
|
<< console::green << "-----------------------------------" << console::reset << "\n";
|
||||||
m_testCount, m_blockCount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename> struct _valid_iatest_runner : std::false_type
|
using DefaultRunner = Runner<false, true>;
|
||||||
{
|
|
||||||
};
|
|
||||||
|
|
||||||
template<BOOL stopOnFail, BOOL isVerbose>
|
|
||||||
struct _valid_iatest_runner<runner<stopOnFail, isVerbose>> : std::true_type
|
|
||||||
{
|
|
||||||
};
|
|
||||||
|
|
||||||
using DefaultRunner = runner<false, true>;
|
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Registry
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
class TestRegistry
|
class TestRegistry
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using TestEntry = std::function<void(DefaultRunner &)>;
|
using TestEntry = std::function<void(DefaultRunner &)>;
|
||||||
|
|
||||||
static std::vector<TestEntry> &GetEntries()
|
static auto get_entries() -> Vec<TestEntry> &
|
||||||
{
|
{
|
||||||
static std::vector<TestEntry> entries;
|
static Vec<TestEntry> entries;
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int RunAll()
|
static auto run_all() -> i32
|
||||||
{
|
{
|
||||||
DefaultRunner r;
|
DefaultRunner r;
|
||||||
auto &entries = GetEntries();
|
auto &entries = get_entries();
|
||||||
printf(__CC_CYAN "[IATest] Discovered %zu Test Blocks\n\n" __CC_DEFAULT, entries.size());
|
std::cout << console::cyan << "[IATest] Discovered " << entries.size() << " Test Blocks\n\n"
|
||||||
|
<< console::reset;
|
||||||
|
|
||||||
for (auto &entry : entries)
|
for (auto &entry : entries)
|
||||||
|
{
|
||||||
entry(r);
|
entry(r);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -315,11 +307,9 @@ namespace ia::iatest
|
|||||||
{
|
{
|
||||||
AutoRegister()
|
AutoRegister()
|
||||||
{
|
{
|
||||||
TestRegistry::GetEntries().push_back([](DefaultRunner &r) { r.testBlock<BlockType>(); });
|
TestRegistry::get_entries().push_back([](DefaultRunner &r) { r.test_block<BlockType>(); });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} // namespace ia::iatest
|
} // namespace IACore
|
||||||
|
|
||||||
# define IAT_REGISTER_ENTRY(Group, Name) static ia::iatest::AutoRegister<Group##_##Name> _iat_reg_##Group##_##Name;
|
#define IAT_REGISTER_ENTRY(Group, Name) static ia::iatest::AutoRegister<Group##_##Name> _iat_reg_##Group##_##Name;
|
||||||
|
|
||||||
#endif // __cplusplus
|
|
||||||
@ -19,133 +19,132 @@
|
|||||||
#include <IACore/ProcessOps.hpp>
|
#include <IACore/ProcessOps.hpp>
|
||||||
#include <IACore/SocketOps.hpp>
|
#include <IACore/SocketOps.hpp>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
using IpcPacketHeader = RingBufferView::PacketHeader;
|
||||||
using IPC_PacketHeader = RingBufferView::PacketHeader;
|
|
||||||
|
|
||||||
struct alignas(64) IPC_SharedMemoryLayout
|
struct alignas(64) IpcSharedMemoryLayout {
|
||||||
{
|
|
||||||
// =========================================================
|
// =========================================================
|
||||||
// SECTION 1: METADATA & HANDSHAKE
|
// SECTION 1: METADATA & HANDSHAKE
|
||||||
// =========================================================
|
// =========================================================
|
||||||
struct Header
|
struct Header {
|
||||||
{
|
u32 magic; // 0x49414950 ("IAIP")
|
||||||
UINT32 Magic; // 0x49414950 ("IAIP")
|
u32 version; // 1
|
||||||
UINT32 Version; // 1
|
u64 total_size; // Total size of SHM block
|
||||||
UINT64 TotalSize; // Total size of SHM block
|
} meta;
|
||||||
} Meta;
|
|
||||||
|
|
||||||
// Pad to ensure MONI starts on a fresh cache line (64 bytes)
|
// Pad to ensure MONI starts on a fresh cache line (64 bytes)
|
||||||
UINT8 _pad0[64 - sizeof(Header)];
|
u8 _pad0[64 - sizeof(Header)];
|
||||||
|
|
||||||
// =========================================================
|
// =========================================================
|
||||||
// SECTION 2: RING BUFFER CONTROL BLOCKS
|
// SECTION 2: RING BUFFER CONTROL BLOCKS
|
||||||
// =========================================================
|
// =========================================================
|
||||||
|
|
||||||
// RingBufferView ControlBlock is already 64-byte aligned internally.
|
// RingBufferView ControlBlock is already 64-byte aligned internally.
|
||||||
RingBufferView::ControlBlock MONI_Control;
|
RingBufferView::ControlBlock moni_control;
|
||||||
RingBufferView::ControlBlock MINO_Control;
|
RingBufferView::ControlBlock mino_control;
|
||||||
|
|
||||||
// =========================================================
|
// =========================================================
|
||||||
// SECTION 3: DATA BUFFER OFFSETS
|
// SECTION 3: DATA BUFFER OFFSETS
|
||||||
// =========================================================
|
// =========================================================
|
||||||
|
|
||||||
UINT64 MONI_DataOffset;
|
u64 moni_data_offset;
|
||||||
UINT64 MONI_DataSize;
|
u64 moni_data_size;
|
||||||
|
|
||||||
UINT64 MINO_DataOffset;
|
u64 mino_data_offset;
|
||||||
UINT64 MINO_DataSize;
|
u64 mino_data_size;
|
||||||
|
|
||||||
// Pad to ensure the actual Data Buffer starts on a fresh cache line
|
// Pad to ensure the actual Data Buffer starts on a fresh cache line
|
||||||
UINT8 _pad1[64 - (sizeof(UINT64) * 4)];
|
u8 _pad1[64 - (sizeof(u64) * 4)];
|
||||||
|
|
||||||
static constexpr size_t GetHeaderSize()
|
static constexpr auto get_header_size() -> usize {
|
||||||
{
|
return sizeof(IpcSharedMemoryLayout);
|
||||||
return sizeof(IPC_SharedMemoryLayout);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Static assert to ensure manual padding logic is correct
|
// Static assert to ensure manual padding logic is correct
|
||||||
static_assert(sizeof(IPC_SharedMemoryLayout) % 64 == 0, "IPC Layout is not cache-line aligned!");
|
static_assert(sizeof(IpcSharedMemoryLayout) % 64 == 0,
|
||||||
|
"IPC Layout is not cache-line aligned!");
|
||||||
|
|
||||||
class IPC_Node
|
class IpcNode {
|
||||||
{
|
public:
|
||||||
public:
|
virtual ~IpcNode();
|
||||||
virtual ~IPC_Node();
|
|
||||||
|
|
||||||
// When Manager spawns a node, `connectionString` is passed
|
// When Manager spawns a node, `connection_string` is passed
|
||||||
// as the first command line argument
|
// as the first command line argument
|
||||||
EXPECT(VOID) Connect(IN PCCHAR connectionString);
|
auto connect(const char *connection_string) -> Result<void>;
|
||||||
|
|
||||||
VOID Update();
|
void update();
|
||||||
|
|
||||||
VOID SendSignal(IN UINT8 signal);
|
void send_signal(u8 signal);
|
||||||
VOID SendPacket(IN UINT16 packetID, IN Span<CONST UINT8> payload);
|
void send_packet(u16 packet_id, Span<const u8> payload);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
PURE_VIRTUAL(VOID OnSignal(IN UINT8 signal));
|
virtual void on_signal(u8 signal) = 0;
|
||||||
PURE_VIRTUAL(VOID OnPacket(IN UINT16 packetID, IN Span<CONST UINT8> payload));
|
virtual void on_packet(u16 packet_id, Span<const u8> payload) = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
String m_shmName;
|
String m_shm_name;
|
||||||
PUINT8 m_sharedMemory{};
|
u8 *m_shared_memory{};
|
||||||
Vector<UINT8> m_receiveBuffer;
|
Vec<u8> m_receive_buffer;
|
||||||
SocketHandle m_socket{INVALID_SOCKET};
|
SocketHandle m_socket{INVALID_SOCKET};
|
||||||
|
|
||||||
UniquePtr<RingBufferView> MONI; // Manager Out, Node In
|
Box<RingBufferView> m_moni; // Manager Out, Node In
|
||||||
UniquePtr<RingBufferView> MINO; // Manager In, Node Out
|
Box<RingBufferView> m_mino; // Manager In, Node Out
|
||||||
|
};
|
||||||
|
|
||||||
|
class IpcManager {
|
||||||
|
struct NodeSession {
|
||||||
|
std::chrono::system_clock::time_point creation_time{};
|
||||||
|
Box<ProcessHandle> node_process;
|
||||||
|
|
||||||
|
std::mutex send_mutex;
|
||||||
|
|
||||||
|
String shared_mem_name;
|
||||||
|
u8 *mapped_ptr{};
|
||||||
|
|
||||||
|
SocketHandle listener_socket{INVALID_SOCKET};
|
||||||
|
SocketHandle data_socket{INVALID_SOCKET};
|
||||||
|
|
||||||
|
Box<RingBufferView> moni; // Manager Out, Node In
|
||||||
|
Box<RingBufferView> mino; // Manager In, Node Out
|
||||||
|
|
||||||
|
bool is_ready{false};
|
||||||
|
|
||||||
|
void send_signal(u8 signal);
|
||||||
|
void send_packet(u16 packet_id, Span<const u8> payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
class IPC_Manager
|
public:
|
||||||
{
|
static constexpr u32 DEFAULT_NODE_SHARED_MEMORY_SIZE = 4 * 1024 * 1024; // 4MB
|
||||||
struct NodeSession
|
|
||||||
{
|
|
||||||
SteadyTimePoint CreationTime{};
|
|
||||||
SharedPtr<ProcessHandle> NodeProcess;
|
|
||||||
|
|
||||||
Mutex SendMutex;
|
public:
|
||||||
|
virtual ~IpcManager();
|
||||||
|
|
||||||
String SharedMemName;
|
void update();
|
||||||
PUINT8 MappedPtr{};
|
|
||||||
|
|
||||||
SocketHandle ListenerSocket{INVALID_SOCKET};
|
auto spawn_node(const Path &executable_path,
|
||||||
SocketHandle DataSocket{INVALID_SOCKET};
|
u32 shared_memory_size = DEFAULT_NODE_SHARED_MEMORY_SIZE)
|
||||||
|
-> Result<NativeProcessID>;
|
||||||
|
|
||||||
UniquePtr<RingBufferView> MONI; // Manager Out, Node In
|
auto wait_till_node_is_online(NativeProcessID node) -> bool;
|
||||||
UniquePtr<RingBufferView> MINO; // Manager In, Node Out
|
|
||||||
|
|
||||||
BOOL IsReady{FALSE};
|
void shutdown_node(NativeProcessID node);
|
||||||
|
|
||||||
VOID SendSignal(IN UINT8 signal);
|
void send_signal(NativeProcessID node, u8 signal);
|
||||||
VOID SendPacket(IN UINT16 packetID, IN Span<CONST UINT8> payload);
|
void send_packet(NativeProcessID node, u16 packet_id, Span<const u8> payload);
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
protected:
|
||||||
STATIC CONSTEXPR UINT32 DEFAULT_NODE_SHARED_MEMORY_SIZE = SIZE_MB(4);
|
virtual void on_signal(NativeProcessID node, u8 signal) = 0;
|
||||||
|
virtual void on_packet(NativeProcessID node, u16 packet_id,
|
||||||
|
Span<const u8> payload) = 0;
|
||||||
|
|
||||||
public:
|
private:
|
||||||
IPC_Manager();
|
Vec<u8> m_receive_buffer;
|
||||||
virtual ~IPC_Manager();
|
Vec<Box<NodeSession>> m_active_sessions;
|
||||||
|
Vec<Box<NodeSession>> m_pending_sessions;
|
||||||
|
HashMap<NativeProcessID, NodeSession *> m_active_session_map;
|
||||||
|
|
||||||
VOID Update();
|
protected:
|
||||||
|
IpcManager();
|
||||||
EXPECT(NativeProcessID)
|
};
|
||||||
SpawnNode(IN CONST FilePath &executablePath, IN UINT32 sharedMemorySize = DEFAULT_NODE_SHARED_MEMORY_SIZE);
|
|
||||||
BOOL WaitTillNodeIsOnline(IN NativeProcessID node);
|
|
||||||
|
|
||||||
VOID ShutdownNode(IN NativeProcessID node);
|
|
||||||
|
|
||||||
VOID SendSignal(IN NativeProcessID node, IN UINT8 signal);
|
|
||||||
VOID SendPacket(IN NativeProcessID node, IN UINT16 packetID, IN Span<CONST UINT8> payload);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
PURE_VIRTUAL(VOID OnSignal(IN NativeProcessID node, IN UINT8 signal));
|
|
||||||
PURE_VIRTUAL(VOID OnPacket(IN NativeProcessID node, IN UINT16 packetID, IN Span<CONST UINT8> payload));
|
|
||||||
|
|
||||||
private:
|
|
||||||
Vector<UINT8> m_receiveBuffer;
|
|
||||||
Vector<UniquePtr<NodeSession>> m_activeSessions;
|
|
||||||
Vector<UniquePtr<NodeSession>> m_pendingSessions;
|
|
||||||
UnorderedMap<NativeProcessID, NodeSession *> m_activeSessionMap;
|
|
||||||
};
|
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -17,42 +17,126 @@
|
|||||||
|
|
||||||
#include <IACore/PCH.hpp>
|
#include <IACore/PCH.hpp>
|
||||||
|
|
||||||
#include <simdjson.h>
|
|
||||||
#include <glaze/glaze.hpp>
|
#include <glaze/glaze.hpp>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <simdjson.h>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
class JsonDocument {
|
||||||
class JSON
|
public:
|
||||||
{
|
// Move-only (Safety: Cannot copy the parser state cheaply)
|
||||||
private:
|
JsonDocument(JsonDocument &&) noexcept = default;
|
||||||
STATIC CONSTEXPR AUTO GLAZE_JSON_OPTS = glz::opts{.error_on_unknown_keys = false};
|
JsonDocument &operator=(JsonDocument &&) noexcept = default;
|
||||||
|
JsonDocument(const JsonDocument &) = delete;
|
||||||
|
JsonDocument &operator=(const JsonDocument &) = delete;
|
||||||
|
|
||||||
public:
|
// Accessor: Get the root element (Object, Array, etc.)
|
||||||
STATIC EXPECT(nlohmann::json) Parse(IN CONST String &json);
|
// The returned 'element' is valid only as long as this JsonDocument is alive.
|
||||||
STATIC EXPECT(Pair<SharedPtr<simdjson::dom::parser>, simdjson::dom::object>)
|
[[nodiscard]]
|
||||||
ParseReadOnly(IN CONST String &json);
|
auto root() const noexcept -> simdjson::dom::element {
|
||||||
STATIC String Encode(IN nlohmann::json data);
|
return m_root;
|
||||||
|
|
||||||
template<typename _object_type> STATIC EXPECT(_object_type) ParseToStruct(IN CONST String &json);
|
|
||||||
template<typename _object_type> STATIC EXPECT(String) EncodeStruct(IN CONST _object_type &data);
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename _object_type> EXPECT(String) JSON::EncodeStruct(IN CONST _object_type &data)
|
private:
|
||||||
{
|
// Only created via JSON::parse_read_only factory
|
||||||
|
friend class Json;
|
||||||
|
|
||||||
|
JsonDocument(Box<simdjson::dom::parser> p, simdjson::dom::element r)
|
||||||
|
: m_parser(std::move(p)), m_root(r) {}
|
||||||
|
|
||||||
|
// 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;
|
String result;
|
||||||
const auto encodeError = glz::write_json(data, result);
|
const auto err = glz::write_json(data, result);
|
||||||
if (encodeError)
|
|
||||||
return MakeUnexpected(std::format("JSON Error: {}", glz::format_error(encodeError)));
|
if (err) {
|
||||||
return result;
|
return fail("JSON Struct Encode Error");
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
} // namespace IACore
|
} // 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
|
#pragma once
|
||||||
|
|
||||||
#include <IACore/PCH.hpp>
|
#include <IACore/PCH.hpp>
|
||||||
|
|
||||||
#define IA_LOG_SET_FILE(path) IACore::Logger::EnableLoggingToDisk(path)
|
#define IA_LOG_SET_FILE(path) IACore::Logger::enable_logging_to_disk(path)
|
||||||
#define IA_LOG_SET_LEVEL(level) IACore::Logger::SetLogLevel(IACore::Logger::ELogLevel::level)
|
#define IA_LOG_SET_LEVEL(level) \
|
||||||
|
IACore::Logger::set_log_level(IACore::Logger::LogLevel::level)
|
||||||
|
|
||||||
#define IA_LOG_TRACE(...) IACore::Logger::Trace(__VA_ARGS__)
|
#define IA_LOG_TRACE(...) IACore::Logger::trace(__VA_ARGS__)
|
||||||
#define IA_LOG_DEBUG(...) IACore::Logger::Debug(__VA_ARGS__)
|
#define IA_LOG_DEBUG(...) IACore::Logger::debug(__VA_ARGS__)
|
||||||
#define IA_LOG_INFO(...) IACore::Logger::Info(__VA_ARGS__)
|
#define IA_LOG_INFO(...) IACore::Logger::info(__VA_ARGS__)
|
||||||
#define IA_LOG_WARN(...) IACore::Logger::Warn(__VA_ARGS__)
|
#define IA_LOG_WARN(...) IACore::Logger::warn(__VA_ARGS__)
|
||||||
#define IA_LOG_ERROR(...) IACore::Logger::Error(__VA_ARGS__)
|
#define IA_LOG_ERROR(...) IACore::Logger::error(__VA_ARGS__)
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
class Logger {
|
||||||
class Logger
|
public:
|
||||||
{
|
enum class LogLevel { Trace, Debug, Info, Warn, Error };
|
||||||
public:
|
|
||||||
enum class ELogLevel
|
|
||||||
{
|
|
||||||
TRACE,
|
|
||||||
DEBUG,
|
|
||||||
INFO,
|
|
||||||
WARN,
|
|
||||||
ERROR
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
STATIC BOOL EnableLoggingToDisk(IN PCCHAR filePath);
|
static auto enable_logging_to_disk(const char *file_path) -> Result<void>;
|
||||||
STATIC VOID SetLogLevel(IN ELogLevel logLevel);
|
static auto set_log_level(LogLevel log_level) -> void;
|
||||||
|
|
||||||
template<typename... Args> STATIC VOID Trace(FormatterString<Args...> fmt, Args &&...args)
|
template <typename... Args>
|
||||||
{
|
static auto trace(std::format_string<Args...> fmt, Args &&...args) -> void {
|
||||||
LogTrace(std::vformat(fmt.get(), std::make_format_args(args...)));
|
log_trace(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename... Args> STATIC VOID Debug(FormatterString<Args...> fmt, Args &&...args)
|
template <typename... Args>
|
||||||
{
|
static auto debug(std::format_string<Args...> fmt, Args &&...args) -> void {
|
||||||
LogDebug(std::vformat(fmt.get(), std::make_format_args(args...)));
|
log_debug(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename... Args> STATIC VOID Info(FormatterString<Args...> fmt, Args &&...args)
|
template <typename... Args>
|
||||||
{
|
static auto info(std::format_string<Args...> fmt, Args &&...args) -> void {
|
||||||
LogInfo(std::vformat(fmt.get(), std::make_format_args(args...)));
|
log_info(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename... Args> STATIC VOID Warn(FormatterString<Args...> fmt, Args &&...args)
|
template <typename... Args>
|
||||||
{
|
static auto warn(std::format_string<Args...> fmt, Args &&...args) -> void {
|
||||||
LogWarn(std::vformat(fmt.get(), std::make_format_args(args...)));
|
log_warn(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename... Args> STATIC VOID Error(FormatterString<Args...> fmt, Args &&...args)
|
template <typename... Args>
|
||||||
{
|
static auto error(std::format_string<Args...> fmt, Args &&...args) -> void {
|
||||||
LogError(std::vformat(fmt.get(), std::make_format_args(args...)));
|
log_error(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC VOID FlushLogs();
|
static auto flush_logs() -> void;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#if IA_DISABLE_LOGGING > 0
|
#if IA_DISABLE_LOGGING > 0
|
||||||
STATIC VOID LogTrace(IN String &&msg)
|
static auto log_trace(String &&msg) -> void { UNUSED(msg); }
|
||||||
{
|
|
||||||
UNUSED(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
STATIC VOID LogDebug(IN String &&msg)
|
static auto log_debug(String &&msg) -> void { UNUSED(msg); }
|
||||||
{
|
|
||||||
UNUSED(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
STATIC VOID LogInfo(IN String &&msg)
|
static auto log_info(String &&msg) -> void { UNUSED(msg); }
|
||||||
{
|
|
||||||
UNUSED(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
STATIC VOID LogWarn(IN String &&msg)
|
static auto log_warn(String &&msg) -> void { UNUSED(msg); }
|
||||||
{
|
|
||||||
UNUSED(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
STATIC VOID LogError(IN String &&msg)
|
static auto log_error(String &&msg) -> void { UNUSED(msg); }
|
||||||
{
|
|
||||||
UNUSED(msg);
|
|
||||||
}
|
|
||||||
#else
|
#else
|
||||||
STATIC VOID LogTrace(IN String &&msg)
|
static auto log_trace(String &&msg) -> void {
|
||||||
{
|
if (m_log_level <= LogLevel::Trace)
|
||||||
if (s_logLevel <= ELogLevel::TRACE)
|
log_internal(console::RESET, "TRACE", std::move(msg));
|
||||||
LogInternal(__CC_WHITE, "TRACE", IA_MOVE(msg));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC VOID LogDebug(IN String &&msg)
|
static auto log_debug(String &&msg) -> void {
|
||||||
{
|
if (m_log_level <= LogLevel::Debug)
|
||||||
if (s_logLevel <= ELogLevel::DEBUG)
|
log_internal(console::CYAN, "DEBUG", std::move(msg));
|
||||||
LogInternal(__CC_CYAN, "DEBUG", IA_MOVE(msg));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC VOID LogInfo(IN String &&msg)
|
static auto log_info(String &&msg) -> void {
|
||||||
{
|
if (m_log_level <= LogLevel::Info)
|
||||||
if (s_logLevel <= ELogLevel::INFO)
|
log_internal(console::GREEN, "INFO", std::move(msg));
|
||||||
LogInternal(__CC_GREEN, "INFO", IA_MOVE(msg));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC VOID LogWarn(IN String &&msg)
|
static auto log_warn(String &&msg) -> void {
|
||||||
{
|
if (m_log_level <= LogLevel::Warn)
|
||||||
if (s_logLevel <= ELogLevel::WARN)
|
log_internal(console::YELLOW, "WARN", std::move(msg));
|
||||||
LogInternal(__CC_YELLOW, "WARN", IA_MOVE(msg));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC VOID LogError(IN String &&msg)
|
static auto log_error(String &&msg) -> void {
|
||||||
{
|
if (m_log_level <= LogLevel::Error)
|
||||||
if (s_logLevel <= ELogLevel::ERROR)
|
log_internal(console::RED, "ERROR", std::move(msg));
|
||||||
LogInternal(__CC_RED, "ERROR", IA_MOVE(msg));
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
STATIC VOID LogInternal(IN PCCHAR prefix, IN PCCHAR tag, IN String &&msg);
|
static auto log_internal(const char *prefix, const char *tag, String &&msg)
|
||||||
|
-> void;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
STATIC ELogLevel s_logLevel;
|
static LogLevel m_log_level;
|
||||||
STATIC std::ofstream s_logFile;
|
static std::ofstream m_log_file;
|
||||||
|
|
||||||
STATIC VOID Initialize();
|
static auto initialize() -> void;
|
||||||
STATIC VOID Terminate();
|
static auto terminate() -> void;
|
||||||
|
|
||||||
friend VOID Initialize();
|
friend void initialize();
|
||||||
friend VOID Terminate();
|
friend void terminate();
|
||||||
};
|
};
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -15,624 +15,233 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Platform Detection
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
#if defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64)
|
#if defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64)
|
||||||
# define IA_ARCH_X64 1
|
#define IA_ARCH_X64 1
|
||||||
#elif defined(__aarch64__) || defined(_M_ARM64)
|
#elif defined(__aarch64__) || defined(_M_ARM64)
|
||||||
# define IA_ARCH_ARM64 1
|
#define IA_ARCH_ARM64 1
|
||||||
#elif defined(__wasm__) || defined(__wasm32__) || defined(__wasm64__)
|
#elif defined(__wasm__) || defined(__wasm32__) || defined(__wasm64__)
|
||||||
# define IA_ARCH_WASM 1
|
#define IA_ARCH_WASM 1
|
||||||
#else
|
#else
|
||||||
# error "IACore: Unsupported Architecture. Only x64, ARM64, and WASM are supported."
|
#error "IACore: Unsupported Architecture."
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
|
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
|
||||||
# ifdef _WIN64
|
#define IA_PLATFORM_WINDOWS 1
|
||||||
# define IA_PLATFORM_WIN64 1
|
|
||||||
# define IA_PLATFORM_WINDOWS 1
|
|
||||||
# else
|
|
||||||
# error "IACore: 32-bit Windows is not supported"
|
|
||||||
# endif
|
|
||||||
#elif __APPLE__
|
#elif __APPLE__
|
||||||
# include <TargetConditionals.h>
|
#include <TargetConditionals.h>
|
||||||
# if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR || TARGET_OS_MACCATALYST
|
#define IA_PLATFORM_APPLE 1
|
||||||
# define IA_PLATFORM_IOS 1
|
#define IA_PLATFORM_UNIX 1
|
||||||
# elif TARGET_OS_MAC
|
|
||||||
# define IA_PLATFORM_MAC 1
|
|
||||||
# endif
|
|
||||||
# define IA_PLATFORM_UNIX 1
|
|
||||||
#elif __ANDROID__
|
|
||||||
# define IA_PLATFORM_ANDROID 1
|
|
||||||
# define IA_PLATFORM_LINUX 1
|
|
||||||
# define IA_PLATFORM_UNIX 1
|
|
||||||
#elif __linux__
|
#elif __linux__
|
||||||
# define IA_PLATFORM_LINUX 1
|
#define IA_PLATFORM_LINUX 1
|
||||||
# define IA_PLATFORM_UNIX 1
|
#define IA_PLATFORM_UNIX 1
|
||||||
#elif __wasm__
|
#elif __wasm__
|
||||||
# define IA_PLATFORM_WASM 1
|
#define IA_PLATFORM_WASM 1
|
||||||
#else
|
#else
|
||||||
# error "IACore: Unsupported Platform. Only Windows, Linux, MacOS, Android and iOS are supported."
|
#error "IACore: Unsupported Platform."
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if IA_PLATFORM_WIN64
|
#if IA_PLATFORM_WINDOWS
|
||||||
# ifndef WIN32_LEAN_AND_MEAN
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
# define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
# endif
|
#endif
|
||||||
# ifndef NOMINMAX
|
#ifndef NOMINMAX
|
||||||
# define NOMINMAX
|
#define NOMINMAX
|
||||||
# endif
|
#endif
|
||||||
# include <windows.h>
|
#include <windows.h>
|
||||||
# undef VOID
|
|
||||||
# undef ERROR
|
|
||||||
#elif IA_PLATFORM_UNIX
|
#elif IA_PLATFORM_UNIX
|
||||||
# include <unistd.h>
|
#include <signal.h>
|
||||||
# include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
# include <sys/mman.h>
|
#include <unistd.h>
|
||||||
# include <sys/stat.h>
|
|
||||||
# include <fcntl.h>
|
|
||||||
# include <spawn.h>
|
|
||||||
# include <signal.h>
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
#include <array>
|
||||||
// Configuration Macros
|
#include <atomic>
|
||||||
// -----------------------------------------------------------------------------
|
#include <cmath>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <format>
|
||||||
|
#include <iostream>
|
||||||
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
|
#include <source_location>
|
||||||
|
#include <span>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#define IA_CHECK(o) (o > 0)
|
#include <ankerl/unordered_dense.h>
|
||||||
|
#include <tl/expected.hpp>
|
||||||
|
|
||||||
#if defined(__IA_DEBUG) && __IA_DEBUG
|
namespace IACore {
|
||||||
# define __DEBUG_MODE__
|
|
||||||
# define __BUILD_MODE_NAME "debug"
|
// =============================================================================
|
||||||
# define DEBUG_ONLY(v) v
|
// Primitive Types (Rust Style)
|
||||||
# ifndef _DEBUG
|
// =============================================================================
|
||||||
# define _DEBUG
|
using u8 = std::uint8_t;
|
||||||
# endif
|
using u16 = std::uint16_t;
|
||||||
|
using u32 = std::uint32_t;
|
||||||
|
using u64 = std::uint64_t;
|
||||||
|
|
||||||
|
using i8 = std::int8_t;
|
||||||
|
using i16 = std::int16_t;
|
||||||
|
using i32 = std::int32_t;
|
||||||
|
using i64 = std::int64_t;
|
||||||
|
|
||||||
|
using f32 = float;
|
||||||
|
using f64 = double;
|
||||||
|
|
||||||
|
using usize = std::size_t;
|
||||||
|
using isize = std::ptrdiff_t;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Build Environment & Constants
|
||||||
|
// =============================================================================
|
||||||
|
namespace env {
|
||||||
|
#if defined(NDEBUG)
|
||||||
|
constexpr bool is_debug = false;
|
||||||
|
constexpr bool is_release = true;
|
||||||
#else
|
#else
|
||||||
# define __RELEASE_MODE__
|
constexpr bool is_debug = true;
|
||||||
# define __BUILD_MODE_NAME "release"
|
constexpr bool is_release = false;
|
||||||
# ifndef NDEBUG
|
|
||||||
# define NDEBUG
|
|
||||||
# endif
|
|
||||||
# ifndef __OPTIMIZE__
|
|
||||||
# define __OPTIMIZE__
|
|
||||||
# endif
|
|
||||||
# define DEBUG_ONLY(f)
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <assert.h>
|
#if IA_PLATFORM_WINDOWS
|
||||||
#include <stdint.h>
|
constexpr bool is_windows = true;
|
||||||
#include <stdio.h>
|
constexpr bool is_unix = false;
|
||||||
#include <stdlib.h>
|
|
||||||
#include <memory.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
# include <bit>
|
|
||||||
# include <new>
|
|
||||||
# include <span>
|
|
||||||
# include <atomic>
|
|
||||||
# include <mutex>
|
|
||||||
# include <thread>
|
|
||||||
# include <limits>
|
|
||||||
# include <cstring>
|
|
||||||
# include <cstddef>
|
|
||||||
# include <chrono>
|
|
||||||
# include <iomanip>
|
|
||||||
# include <charconv>
|
|
||||||
# include <fstream>
|
|
||||||
# include <iostream>
|
|
||||||
# include <concepts>
|
|
||||||
# include <filesystem>
|
|
||||||
# include <functional>
|
|
||||||
# include <type_traits>
|
|
||||||
# include <initializer_list>
|
|
||||||
# include <condition_variable>
|
|
||||||
|
|
||||||
# include <tuple>
|
|
||||||
# include <array>
|
|
||||||
# include <deque>
|
|
||||||
# include <string>
|
|
||||||
# include <vector>
|
|
||||||
# include <format>
|
|
||||||
# include <sstream>
|
|
||||||
# include <optional>
|
|
||||||
# include <string_view>
|
|
||||||
|
|
||||||
# include <tl/expected.hpp>
|
|
||||||
# include <ankerl/unordered_dense.h>
|
|
||||||
|
|
||||||
#else
|
#else
|
||||||
# include <float.h>
|
constexpr bool is_windows = false;
|
||||||
# include <stdbool.h>
|
constexpr bool is_unix = true;
|
||||||
# include <stddef.h>
|
|
||||||
# include <string.h>
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
constexpr usize max_path_len = 4096;
|
||||||
// Security Macros
|
} // namespace env
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#define IA_PANIC(msg) \
|
// =============================================================================
|
||||||
{ \
|
// Memory & Ownership (Rust Semantics)
|
||||||
fprintf(stderr, "PANIC: %s\n", msg); \
|
// =============================================================================
|
||||||
__builtin_trap(); \
|
template <typename T> using Box = std::unique_ptr<T>;
|
||||||
}
|
|
||||||
|
|
||||||
// Advanced Security features are not included in OSS builds
|
template <typename T> using Arc = std::shared_ptr<T>;
|
||||||
// (OSS version does not implement 'IAC_CHECK_*'s)
|
|
||||||
#define IACORE_SECURITY_LEVEL 0
|
|
||||||
|
|
||||||
#define IAC_SEC_LEVEL(v) (IACORE_SECURITY_LEVEL >= v)
|
template <typename T> using Weak = std::weak_ptr<T>;
|
||||||
|
|
||||||
#if __IA_DEBUG || IAC_SEC_LEVEL(1)
|
template <typename T, typename... Args>
|
||||||
# define __IAC_OVERFLOW_CHECKS 1
|
[[nodiscard]] inline auto make_box(Args &&...args) -> Box<T> {
|
||||||
#else
|
return std::make_unique<T>(std::forward<Args>(args)...);
|
||||||
# define __IAC_OVERFLOW_CHECKS 0
|
}
|
||||||
#endif
|
|
||||||
#if __IA_DEBUG || IAC_SEC_LEVEL(2)
|
|
||||||
# define __IAC_SANITY_CHECKS 1
|
|
||||||
#else
|
|
||||||
# define __IAC_SANITY_CHECKS 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
template <typename T, typename... Args>
|
||||||
// Language Abstraction Macros
|
[[nodiscard]] inline auto make_arc(Args &&...args) -> Arc<T> {
|
||||||
// -----------------------------------------------------------------------------
|
return std::make_shared<T>(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
#define AUTO auto
|
// =============================================================================
|
||||||
#define CONST const
|
// Error Handling (Result)
|
||||||
#define STATIC static
|
// =============================================================================
|
||||||
#define EXTERN extern
|
template <typename T, typename E = std::string>
|
||||||
|
using Result = tl::expected<T, E>;
|
||||||
|
|
||||||
#ifdef __cplusplus
|
template <typename E> [[nodiscard]] inline auto fail(E &&error) {
|
||||||
# define VIRTUAL virtual
|
return tl::make_unexpected(std::forward<E>(error));
|
||||||
# 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
|
template <typename... Args>
|
||||||
#define FORWARD_DECLARE(t, i) t i
|
[[nodiscard]] inline auto fail(std::format_string<Args...> fmt,
|
||||||
|
Args &&...args) {
|
||||||
|
return tl::make_unexpected(std::format(fmt, std::forward<Args>(args)...));
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef __cplusplus
|
// =============================================================================
|
||||||
# define PURE_VIRTUAL(...) VIRTUAL __VA_ARGS__ = 0
|
// Common Data Structures
|
||||||
#endif
|
// =============================================================================
|
||||||
|
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;
|
||||||
// Attributes & Compiler Intrinsics
|
using Path = std::filesystem::path;
|
||||||
// -----------------------------------------------------------------------------
|
using StringView = std::string_view;
|
||||||
|
|
||||||
#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
|
// Utilities
|
||||||
// -----------------------------------------------------------------------------
|
// =============================================================================
|
||||||
|
|
||||||
#ifdef __cplusplus
|
[[noreturn]] inline void
|
||||||
# define CAST(v, t) (static_cast<t>(v))
|
panic(const std::string &msg,
|
||||||
# define REINTERPRET(v, t) (reinterpret_cast<t>(v))
|
const std::source_location loc = std::source_location::current()) {
|
||||||
#else
|
std::cerr << "\n[IA_PANIC] " << msg << "\n At: " << loc.file_name()
|
||||||
# define CAST(v, t) ((t) (v))
|
<< ":" << loc.line() << "\n";
|
||||||
#endif
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
// Templates and Aliases
|
inline void
|
||||||
#ifdef __cplusplus
|
ensure(bool condition, const std::string &msg,
|
||||||
# define ALIAS_FUNCTION(alias, function) \
|
const std::source_location loc = std::source_location::current()) {
|
||||||
template<typename... Args> auto alias(Args &&...args) -> decltype(function(std::forward<Args>(args)...)) \
|
if (env::is_debug && !condition) {
|
||||||
{ \
|
std::cerr << "\n[assert] " << msg << "\n At: " << loc.file_name()
|
||||||
return function(std::forward<Args>(args)...); \
|
<< ":" << loc.line() << "\n";
|
||||||
|
std::abort();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# define ALIAS_TEMPLATE_FUNCTION(t, alias, function) \
|
// =============================================================================
|
||||||
template<typename t, typename... Args> \
|
// Versioning
|
||||||
auto alias(Args &&...args) -> decltype(function<t>(std::forward<Args>(args)...)) \
|
// =============================================================================
|
||||||
{ \
|
struct Version {
|
||||||
return function<t>(std::forward<Args>(args)...); \
|
u32 major = 0;
|
||||||
|
u32 minor = 0;
|
||||||
|
u32 patch = 0;
|
||||||
|
|
||||||
|
constexpr auto to_u64() const -> u64 {
|
||||||
|
return (static_cast<u64>(major) << 40) | (static_cast<u64>(minor) << 16) |
|
||||||
|
(static_cast<u64>(patch));
|
||||||
}
|
}
|
||||||
#endif
|
};
|
||||||
|
|
||||||
// Assertions
|
// =============================================================================
|
||||||
#define IA_RELEASE_ASSERT(v) assert((v))
|
// Console Colors
|
||||||
#define IA_RELEASE_ASSERT_MSG(v, m) assert((v) && m)
|
// =============================================================================
|
||||||
|
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
|
||||||
|
|
||||||
#if defined(__DEBUG_MODE__)
|
} // namespace IACore
|
||||||
# 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))
|
// Macros
|
||||||
#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) \
|
#define IA_TRY_PURE(expr) \
|
||||||
{ \
|
{ \
|
||||||
auto _ia_res = (expr); \
|
auto _res = expr; \
|
||||||
if (!_ia_res) \
|
if (!_res) { \
|
||||||
{ \
|
return fail(std::move(_res.error())); \
|
||||||
return MakeUnexpected(std::move(_ia_res.error())); \
|
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define IA_TRY(expr) \
|
#define IA_TRY(lhs, expr) \
|
||||||
__extension__({ \
|
|
||||||
auto _ia_res = (expr); \
|
|
||||||
if (!_ia_res) \
|
|
||||||
{ \
|
{ \
|
||||||
return MakeUnexpected(std::move(_ia_res.error())); \
|
auto _res = expr; \
|
||||||
|
if (!_res) { \
|
||||||
|
return fail(std::move(_res.error())); \
|
||||||
} \
|
} \
|
||||||
std::move(*_ia_res); \
|
lhs = std::move(*_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_NODISCARD [[nodiscard]]
|
||||||
#define IA_CONCAT(x, y) IA_CONCAT_IMPL(x, y)
|
#define IA_UNUSED [[maybe_unused]]
|
||||||
#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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// 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> inline UniquePtr<T> MakeUniqueProtected(Args &&...args)
|
|
||||||
{
|
|
||||||
struct make_unique_enabler : public T
|
|
||||||
{
|
|
||||||
make_unique_enabler(Args &&...args) : T(std::forward<Args>(args)...)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return MakeUnique<make_unique_enabler>(std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename _expected_type, typename _unexpected_type>
|
|
||||||
using Expected = tl::expected<_expected_type, _unexpected_type>;
|
|
||||||
ALIAS_FUNCTION(MakeUnexpected, tl::make_unexpected);
|
|
||||||
|
|
||||||
# define EXPECT(...) Expected<__VA_ARGS__, String>
|
|
||||||
# define UNEXPECTED(...) MakeUnexpected(std::format(__VA_ARGS__))
|
|
||||||
|
|
||||||
using String = std::string;
|
|
||||||
using StringView = std::string_view;
|
|
||||||
using StringStream = std::stringstream;
|
|
||||||
|
|
||||||
using SteadyClock = std::chrono::steady_clock;
|
|
||||||
using SteadyTimePoint = std::chrono::time_point<SteadyClock>;
|
|
||||||
using HighResClock = std::chrono::high_resolution_clock;
|
|
||||||
using HighResTimePoint = std::chrono::time_point<HighResClock>;
|
|
||||||
|
|
||||||
using Mutex = std::mutex;
|
|
||||||
using StopToken = std::stop_token;
|
|
||||||
using ScopedLock = std::scoped_lock<Mutex>;
|
|
||||||
using UniqueLock = std::unique_lock<Mutex>;
|
|
||||||
using JoiningThread = std::jthread;
|
|
||||||
using ConditionVariable = std::condition_variable;
|
|
||||||
|
|
||||||
namespace FileSystem = std::filesystem;
|
|
||||||
using FilePath = FileSystem::path;
|
|
||||||
|
|
||||||
template<typename... Args> using FormatterString = std::format_string<Args...>;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -18,17 +18,13 @@
|
|||||||
#include <IACore/PCH.hpp>
|
#include <IACore/PCH.hpp>
|
||||||
|
|
||||||
#if IA_ARCH_X64
|
#if IA_ARCH_X64
|
||||||
|
|
||||||
# ifdef _MSC_VER
|
# ifdef _MSC_VER
|
||||||
# include <intrin.h>
|
# include <intrin.h>
|
||||||
# else
|
# else
|
||||||
# include <immintrin.h>
|
# include <immintrin.h>
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
#elif IA_ARCH_ARM64
|
#elif IA_ARCH_ARM64
|
||||||
|
|
||||||
# include <arm_acle.h>
|
# include <arm_acle.h>
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore
|
||||||
@ -38,26 +34,24 @@ namespace IACore
|
|||||||
public:
|
public:
|
||||||
struct Capabilities
|
struct Capabilities
|
||||||
{
|
{
|
||||||
BOOL HardwareCRC32{FALSE};
|
bool hardware_crc32 = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
static auto check_cpu() -> bool;
|
||||||
STATIC BOOL CheckCPU();
|
|
||||||
|
|
||||||
#if IA_ARCH_X64
|
#if IA_ARCH_X64
|
||||||
STATIC VOID CPUID(IN INT32 function, IN INT32 subFunction, OUT INT32 out[4]);
|
static auto cpuid(i32 function, i32 sub_function, i32 out[4]) -> void;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
STATIC PCCHAR GetArchitectureName();
|
static auto get_architecture_name() -> const char *;
|
||||||
STATIC PCCHAR GetOperatingSystemName();
|
static auto get_operating_system_name() -> const char *;
|
||||||
|
|
||||||
public:
|
static auto get_capabilities() -> const Capabilities &
|
||||||
STATIC CONST Capabilities &GetCapabilities()
|
|
||||||
{
|
{
|
||||||
return s_capabilities;
|
return s_capabilities;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
STATIC Capabilities s_capabilities;
|
static Capabilities s_capabilities;
|
||||||
};
|
};
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -22,46 +22,48 @@ using NativeProcessID = DWORD;
|
|||||||
#elif IA_PLATFORM_UNIX
|
#elif IA_PLATFORM_UNIX
|
||||||
using NativeProcessID = pid_t;
|
using NativeProcessID = pid_t;
|
||||||
#else
|
#else
|
||||||
# error "This platform does not support IACore ProcessOps"
|
#error "This platform does not support IACore ProcessOps"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
struct ProcessHandle {
|
||||||
struct ProcessHandle
|
std::atomic<NativeProcessID> id{0};
|
||||||
{
|
std::atomic<bool> is_running{false};
|
||||||
Atomic<NativeProcessID> ID{0};
|
|
||||||
Atomic<BOOL> IsRunning{false};
|
|
||||||
|
|
||||||
BOOL IsActive() CONST
|
[[nodiscard]] auto is_active() const -> bool { return is_running && id != 0; }
|
||||||
{
|
|
||||||
return IsRunning && ID != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
JoiningThread ThreadHandle;
|
std::jthread m_thread_handle;
|
||||||
|
|
||||||
friend class ProcessOps;
|
friend class ProcessOps;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ProcessOps
|
class ProcessOps {
|
||||||
{
|
public:
|
||||||
public:
|
static auto get_current_process_id() -> NativeProcessID;
|
||||||
STATIC NativeProcessID GetCurrentProcessID();
|
|
||||||
|
|
||||||
STATIC EXPECT(INT32) SpawnProcessSync(IN CONST String &command, IN CONST String &args,
|
static auto spawn_process_sync(
|
||||||
IN Function<VOID(IN StringView line)> onOutputLineCallback);
|
const String &command, const String &args,
|
||||||
STATIC SharedPtr<ProcessHandle> SpawnProcessAsync(IN CONST String &command, IN CONST String &args,
|
std::function<void(StringView line)> on_output_line_callback)
|
||||||
IN Function<VOID(IN StringView line)> onOutputLineCallback,
|
-> Result<i32>;
|
||||||
IN Function<VOID(EXPECT(INT32))> onFinishCallback);
|
|
||||||
|
|
||||||
STATIC VOID TerminateProcess(IN CONST SharedPtr<ProcessHandle> &handle);
|
static auto spawn_process_async(
|
||||||
|
const String &command, const String &args,
|
||||||
|
std::function<void(StringView line)> on_output_line_callback,
|
||||||
|
std::function<void(Result<i32>)> on_finish_callback)
|
||||||
|
-> Result<Box<ProcessHandle>>;
|
||||||
|
|
||||||
private:
|
static auto terminate_process(const Box<ProcessHandle> &handle) -> void;
|
||||||
STATIC EXPECT(INT32)
|
|
||||||
SpawnProcessWindows(IN CONST String &command, IN CONST String &args,
|
private:
|
||||||
IN Function<VOID(StringView)> onOutputLineCallback, OUT Atomic<NativeProcessID> &id);
|
static auto
|
||||||
STATIC EXPECT(INT32)
|
spawn_process_windows(const String &command, const String &args,
|
||||||
SpawnProcessPosix(IN CONST String &command, IN CONST String &args,
|
std::function<void(StringView)> on_output_line_callback,
|
||||||
IN Function<VOID(StringView)> onOutputLineCallback, OUT Atomic<NativeProcessID> &id);
|
std::atomic<NativeProcessID> &id) -> Result<i32>;
|
||||||
};
|
|
||||||
|
static auto
|
||||||
|
spawn_process_posix(const String &command, const String &args,
|
||||||
|
std::function<void(StringView)> on_output_line_callback,
|
||||||
|
std::atomic<NativeProcessID> &id) -> Result<i32>;
|
||||||
|
};
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -1,18 +1,3 @@
|
|||||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
|
||||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <IACore/PCH.hpp>
|
#include <IACore/PCH.hpp>
|
||||||
@ -37,81 +22,81 @@ namespace IACore
|
|||||||
# pragma message("Warning: Configuration mismatch. IACore is being compiled for SCALAR SIMD (Slow)")
|
# pragma message("Warning: Configuration mismatch. IACore is being compiled for SCALAR SIMD (Slow)")
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class ALIGN(16) IntVec4
|
class alignas(16) IntVec4
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
IntVec4() = default;
|
IntVec4() = default;
|
||||||
|
|
||||||
INLINE EXPLICIT IntVec4(IN UINT32 s);
|
inline explicit IntVec4(u32 s);
|
||||||
INLINE EXPLICIT IntVec4(IN PCUINT32 values);
|
inline explicit IntVec4(const u32 *values);
|
||||||
INLINE EXPLICIT IntVec4(IN UINT32 a, IN UINT32 b, IN UINT32 c, IN UINT32 d);
|
inline explicit IntVec4(u32 a, u32 b, u32 c, u32 d);
|
||||||
|
|
||||||
INLINE IntVec4 operator+(IN CONST IntVec4 &other) CONST;
|
inline auto operator+(const IntVec4 &other) const -> IntVec4;
|
||||||
INLINE IntVec4 operator-(IN CONST IntVec4 &other) CONST;
|
inline auto operator-(const IntVec4 &other) const -> IntVec4;
|
||||||
INLINE IntVec4 operator*(IN CONST IntVec4 &other) CONST;
|
inline auto operator*(const IntVec4 &other) const -> IntVec4;
|
||||||
|
|
||||||
INLINE IntVec4 operator&(IN CONST IntVec4 &other) CONST;
|
inline auto operator&(const IntVec4 &other) const -> IntVec4;
|
||||||
INLINE IntVec4 operator|(IN CONST IntVec4 &other) CONST;
|
inline auto operator|(const IntVec4 &other) const -> IntVec4;
|
||||||
INLINE IntVec4 operator^(IN CONST IntVec4 &other) CONST;
|
inline auto operator^(const IntVec4 &other) const -> IntVec4;
|
||||||
INLINE IntVec4 operator~() CONST;
|
inline auto operator~() const -> IntVec4;
|
||||||
|
|
||||||
INLINE IntVec4 operator<<(IN UINT32 amount) CONST;
|
inline auto operator<<(u32 amount) const -> IntVec4;
|
||||||
INLINE IntVec4 operator>>(IN UINT32 amount) CONST;
|
inline auto operator>>(u32 amount) const -> IntVec4;
|
||||||
|
|
||||||
INLINE IntVec4 SatAdd(IN CONST IntVec4 &other) CONST;
|
inline auto sat_add(const IntVec4 &other) const -> IntVec4;
|
||||||
INLINE IntVec4 SatSub(IN CONST IntVec4 &other) CONST;
|
inline auto sat_sub(const IntVec4 &other) const -> IntVec4;
|
||||||
|
|
||||||
INLINE IntVec4 Clamp(IN UINT32 min, IN UINT32 max) CONST;
|
inline auto clamp(u32 min, u32 max) const -> IntVec4;
|
||||||
|
|
||||||
INLINE IntVec4 MultAdd(IN CONST IntVec4 &multiplier, IN CONST IntVec4 &addend) CONST;
|
inline auto mult_add(const IntVec4 &multiplier, const IntVec4 &addend) const -> IntVec4;
|
||||||
|
|
||||||
INLINE VOID Store(OUT PUINT32 values);
|
inline auto store(u32 *values) -> void;
|
||||||
STATIC INLINE IntVec4 Load(IN PCUINT32 values);
|
static inline auto load(const u32 *values) -> IntVec4;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using Tag = hn::FixedTag<UINT32, 4>;
|
using Tag = hn::FixedTag<u32, 4>;
|
||||||
|
|
||||||
hn::Vec<Tag> m_data;
|
hn::Vec<Tag> m_data;
|
||||||
|
|
||||||
INLINE EXPLICIT IntVec4(hn::Vec<Tag> v) : m_data(v)
|
inline explicit IntVec4(hn::Vec<Tag> v) : m_data(v)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class ALIGN(16) FloatVec4
|
class alignas(16) FloatVec4
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
FloatVec4() = default;
|
FloatVec4() = default;
|
||||||
|
|
||||||
INLINE EXPLICIT FloatVec4(IN FLOAT32 s);
|
inline explicit FloatVec4(f32 s);
|
||||||
INLINE EXPLICIT FloatVec4(IN PCFLOAT32 values);
|
inline explicit FloatVec4(const f32 *values);
|
||||||
INLINE EXPLICIT FloatVec4(IN FLOAT32 a, IN FLOAT32 b, IN FLOAT32 c, IN FLOAT32 d);
|
inline explicit FloatVec4(f32 a, f32 b, f32 c, f32 d);
|
||||||
|
|
||||||
INLINE FloatVec4 operator+(IN CONST FloatVec4 &other) CONST;
|
inline auto operator+(const FloatVec4 &other) const -> FloatVec4;
|
||||||
INLINE FloatVec4 operator-(IN CONST FloatVec4 &other) CONST;
|
inline auto operator-(const FloatVec4 &other) const -> FloatVec4;
|
||||||
INLINE FloatVec4 operator*(IN CONST FloatVec4 &other) CONST;
|
inline auto operator*(const FloatVec4 &other) const -> FloatVec4;
|
||||||
INLINE FloatVec4 operator/(IN CONST FloatVec4 &other) CONST;
|
inline auto operator/(const FloatVec4 &other) const -> FloatVec4;
|
||||||
|
|
||||||
INLINE FloatVec4 Clamp(IN FLOAT32 min, IN FLOAT32 max) CONST;
|
inline auto clamp(f32 min, f32 max) const -> FloatVec4;
|
||||||
|
|
||||||
INLINE FloatVec4 Abs() CONST;
|
inline auto abs() const -> FloatVec4;
|
||||||
INLINE FloatVec4 Sqrt() CONST;
|
inline auto sqrt() const -> FloatVec4;
|
||||||
INLINE FloatVec4 Rsqrt() CONST;
|
inline auto rsqrt() const -> FloatVec4;
|
||||||
INLINE FloatVec4 Normalize() CONST;
|
inline auto normalize() const -> FloatVec4;
|
||||||
|
|
||||||
INLINE FLOAT32 Dot(IN CONST FloatVec4 &other) CONST;
|
inline auto dot(const FloatVec4 &other) const -> f32;
|
||||||
|
|
||||||
INLINE FloatVec4 MultAdd(IN CONST FloatVec4 &multiplier, IN CONST FloatVec4 &addend) CONST;
|
inline auto mult_add(const FloatVec4 &multiplier, const FloatVec4 &addend) const -> FloatVec4;
|
||||||
|
|
||||||
INLINE VOID Store(OUT PFLOAT32 values);
|
inline auto store(f32 *values) -> void;
|
||||||
STATIC INLINE FloatVec4 Load(IN PCFLOAT32 values);
|
static inline auto load(const f32 *values) -> FloatVec4;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using Tag = hn::FixedTag<FLOAT32, 4>;
|
using Tag = hn::FixedTag<f32, 4>;
|
||||||
|
|
||||||
hn::Vec<Tag> m_data;
|
hn::Vec<Tag> m_data;
|
||||||
|
|
||||||
INLINE EXPLICIT FloatVec4(hn::Vec<Tag> v) : m_data(v)
|
inline explicit FloatVec4(hn::Vec<Tag> v) : m_data(v)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -119,153 +104,153 @@ namespace IACore
|
|||||||
|
|
||||||
namespace IACore
|
namespace IACore
|
||||||
{
|
{
|
||||||
IntVec4::IntVec4(IN UINT32 s)
|
IntVec4::IntVec4(u32 s)
|
||||||
{
|
{
|
||||||
CONST Tag d;
|
const Tag d;
|
||||||
m_data = hn::Set(d, s);
|
m_data = hn::Set(d, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
IntVec4::IntVec4(IN PCUINT32 values)
|
IntVec4::IntVec4(const u32 *values)
|
||||||
{
|
{
|
||||||
CONST Tag data;
|
const Tag data;
|
||||||
m_data = hn::Load(data, values);
|
m_data = hn::Load(data, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
IntVec4::IntVec4(IN UINT32 a, IN UINT32 b, IN UINT32 c, IN UINT32 d)
|
IntVec4::IntVec4(u32 a, u32 b, u32 c, u32 d)
|
||||||
{
|
{
|
||||||
CONST Tag data;
|
const Tag data;
|
||||||
ALIGN(16) UINT32 values[4] = {a, b, c, d};
|
alignas(16) u32 values[4] = {a, b, c, d};
|
||||||
m_data = hn::Load(data, values);
|
m_data = hn::Load(data, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
IntVec4 IntVec4::operator+(IN CONST IntVec4 &other) CONST
|
auto IntVec4::operator+(const IntVec4 &other) const -> IntVec4
|
||||||
{
|
{
|
||||||
return IntVec4(hn::Add(m_data, other.m_data));
|
return IntVec4(hn::Add(m_data, other.m_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
IntVec4 IntVec4::operator-(IN CONST IntVec4 &other) CONST
|
auto IntVec4::operator-(const IntVec4 &other) const -> IntVec4
|
||||||
{
|
{
|
||||||
return IntVec4(hn::Sub(m_data, other.m_data));
|
return IntVec4(hn::Sub(m_data, other.m_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
IntVec4 IntVec4::operator*(IN CONST IntVec4 &other) CONST
|
auto IntVec4::operator*(const IntVec4 &other) const -> IntVec4
|
||||||
{
|
{
|
||||||
return IntVec4(hn::Mul(m_data, other.m_data));
|
return IntVec4(hn::Mul(m_data, other.m_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
IntVec4 IntVec4::operator&(IN CONST IntVec4 &other) CONST
|
auto IntVec4::operator&(const IntVec4 &other) const -> IntVec4
|
||||||
{
|
{
|
||||||
return IntVec4(hn::And(m_data, other.m_data));
|
return IntVec4(hn::And(m_data, other.m_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
IntVec4 IntVec4::operator|(IN CONST IntVec4 &other) CONST
|
auto IntVec4::operator|(const IntVec4 &other) const -> IntVec4
|
||||||
{
|
{
|
||||||
return IntVec4(hn::Or(m_data, other.m_data));
|
return IntVec4(hn::Or(m_data, other.m_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
IntVec4 IntVec4::operator^(IN CONST IntVec4 &other) CONST
|
auto IntVec4::operator^(const IntVec4 &other) const -> IntVec4
|
||||||
{
|
{
|
||||||
return IntVec4(hn::Xor(m_data, other.m_data));
|
return IntVec4(hn::Xor(m_data, other.m_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
IntVec4 IntVec4::operator~() CONST
|
auto IntVec4::operator~() const -> IntVec4
|
||||||
{
|
{
|
||||||
return IntVec4(hn::Not(m_data));
|
return IntVec4(hn::Not(m_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
IntVec4 IntVec4::operator<<(IN UINT32 amount) CONST
|
auto IntVec4::operator<<(u32 amount) const -> IntVec4
|
||||||
{
|
{
|
||||||
return IntVec4(hn::ShiftLeftSame(m_data, amount));
|
return IntVec4(hn::ShiftLeftSame(m_data, amount));
|
||||||
}
|
}
|
||||||
|
|
||||||
IntVec4 IntVec4::operator>>(IN UINT32 amount) CONST
|
auto IntVec4::operator>>(u32 amount) const -> IntVec4
|
||||||
{
|
{
|
||||||
return IntVec4(hn::ShiftRightSame(m_data, amount));
|
return IntVec4(hn::ShiftRightSame(m_data, amount));
|
||||||
}
|
}
|
||||||
|
|
||||||
IntVec4 IntVec4::MultAdd(IN CONST IntVec4 &multiplier, IN CONST IntVec4 &addend) CONST
|
auto IntVec4::mult_add(const IntVec4 &multiplier, const IntVec4 &addend) const -> IntVec4
|
||||||
{
|
{
|
||||||
return IntVec4(hn::MulAdd(m_data, multiplier.m_data, addend.m_data));
|
return IntVec4(hn::MulAdd(m_data, multiplier.m_data, addend.m_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
IntVec4 IntVec4::SatAdd(IN CONST IntVec4 &other) CONST
|
auto IntVec4::sat_add(const IntVec4 &other) const -> IntVec4
|
||||||
{
|
{
|
||||||
return IntVec4(hn::SaturatedAdd(m_data, other.m_data));
|
return IntVec4(hn::SaturatedAdd(m_data, other.m_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
IntVec4 IntVec4::SatSub(IN CONST IntVec4 &other) CONST
|
auto IntVec4::sat_sub(const IntVec4 &other) const -> IntVec4
|
||||||
{
|
{
|
||||||
return IntVec4(hn::SaturatedSub(m_data, other.m_data));
|
return IntVec4(hn::SaturatedSub(m_data, other.m_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
IntVec4 IntVec4::Clamp(IN UINT32 min, IN UINT32 max) CONST
|
auto IntVec4::clamp(u32 min, u32 max) const -> IntVec4
|
||||||
{
|
{
|
||||||
CONST Tag d;
|
const Tag d;
|
||||||
auto vMin = hn::Set(d, min);
|
auto vMin = hn::Set(d, min);
|
||||||
auto vMax = hn::Set(d, max);
|
auto vMax = hn::Set(d, max);
|
||||||
return IntVec4(hn::Min(hn::Max(m_data, vMin), vMax));
|
return IntVec4(hn::Min(hn::Max(m_data, vMin), vMax));
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID IntVec4::Store(OUT PUINT32 values)
|
auto IntVec4::store(u32 *values) -> void
|
||||||
{
|
{
|
||||||
CONST Tag d;
|
const Tag d;
|
||||||
hn::Store(m_data, d, values);
|
hn::Store(m_data, d, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
IntVec4 IntVec4::Load(IN PCUINT32 values)
|
auto IntVec4::load(const u32 *values) -> IntVec4
|
||||||
{
|
{
|
||||||
CONST Tag d;
|
const Tag d;
|
||||||
return IntVec4(hn::Load(d, values));
|
return IntVec4(hn::Load(d, values));
|
||||||
}
|
}
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore
|
||||||
{
|
{
|
||||||
FloatVec4::FloatVec4(IN FLOAT32 s)
|
FloatVec4::FloatVec4(f32 s)
|
||||||
{
|
{
|
||||||
const Tag d;
|
const Tag d;
|
||||||
m_data = hn::Set(d, s);
|
m_data = hn::Set(d, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
FloatVec4::FloatVec4(IN PCFLOAT32 values)
|
FloatVec4::FloatVec4(const f32 *values)
|
||||||
{
|
{
|
||||||
const Tag d;
|
const Tag d;
|
||||||
m_data = hn::Load(d, values);
|
m_data = hn::Load(d, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
FloatVec4::FloatVec4(IN FLOAT32 a, IN FLOAT32 b, IN FLOAT32 c, IN FLOAT32 d)
|
FloatVec4::FloatVec4(f32 a, f32 b, f32 c, f32 d)
|
||||||
{
|
{
|
||||||
const Tag data;
|
const Tag data;
|
||||||
ALIGN(16) FLOAT32 temp[4] = {a, b, c, d};
|
alignas(16) f32 temp[4] = {a, b, c, d};
|
||||||
m_data = hn::Load(data, temp);
|
m_data = hn::Load(data, temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
FloatVec4 FloatVec4::operator+(IN CONST FloatVec4 &other) CONST
|
auto FloatVec4::operator+(const FloatVec4 &other) const -> FloatVec4
|
||||||
{
|
{
|
||||||
return FloatVec4(hn::Add(m_data, other.m_data));
|
return FloatVec4(hn::Add(m_data, other.m_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
FloatVec4 FloatVec4::operator-(IN CONST FloatVec4 &other) CONST
|
auto FloatVec4::operator-(const FloatVec4 &other) const -> FloatVec4
|
||||||
{
|
{
|
||||||
return FloatVec4(hn::Sub(m_data, other.m_data));
|
return FloatVec4(hn::Sub(m_data, other.m_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
FloatVec4 FloatVec4::operator*(IN CONST FloatVec4 &other) CONST
|
auto FloatVec4::operator*(const FloatVec4 &other) const -> FloatVec4
|
||||||
{
|
{
|
||||||
return FloatVec4(hn::Mul(m_data, other.m_data));
|
return FloatVec4(hn::Mul(m_data, other.m_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
FloatVec4 FloatVec4::operator/(IN CONST FloatVec4 &other) CONST
|
auto FloatVec4::operator/(const FloatVec4 &other) const -> FloatVec4
|
||||||
{
|
{
|
||||||
return FloatVec4(hn::Div(m_data, other.m_data));
|
return FloatVec4(hn::Div(m_data, other.m_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
FloatVec4 FloatVec4::MultAdd(IN CONST FloatVec4 &multiplier, IN CONST FloatVec4 &addend) CONST
|
auto FloatVec4::mult_add(const FloatVec4 &multiplier, const FloatVec4 &addend) const -> FloatVec4
|
||||||
{
|
{
|
||||||
return FloatVec4(hn::MulAdd(m_data, multiplier.m_data, addend.m_data));
|
return FloatVec4(hn::MulAdd(m_data, multiplier.m_data, addend.m_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
FloatVec4 FloatVec4::Clamp(IN FLOAT32 min, IN FLOAT32 max) CONST
|
auto FloatVec4::clamp(f32 min, f32 max) const -> FloatVec4
|
||||||
{
|
{
|
||||||
const Tag d;
|
const Tag d;
|
||||||
auto vMin = hn::Set(d, min);
|
auto vMin = hn::Set(d, min);
|
||||||
@ -273,29 +258,29 @@ namespace IACore
|
|||||||
return FloatVec4(hn::Min(hn::Max(m_data, vMin), vMax));
|
return FloatVec4(hn::Min(hn::Max(m_data, vMin), vMax));
|
||||||
}
|
}
|
||||||
|
|
||||||
FloatVec4 FloatVec4::Sqrt() CONST
|
auto FloatVec4::sqrt() const -> FloatVec4
|
||||||
{
|
{
|
||||||
return FloatVec4(hn::Sqrt(m_data));
|
return FloatVec4(hn::Sqrt(m_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
FloatVec4 FloatVec4::Rsqrt() CONST
|
auto FloatVec4::rsqrt() const -> FloatVec4
|
||||||
{
|
{
|
||||||
return FloatVec4(hn::ApproximateReciprocalSqrt(m_data));
|
return FloatVec4(hn::ApproximateReciprocalSqrt(m_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
FloatVec4 FloatVec4::Abs() CONST
|
auto FloatVec4::abs() const -> FloatVec4
|
||||||
{
|
{
|
||||||
return FloatVec4(hn::Abs(m_data));
|
return FloatVec4(hn::Abs(m_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
FLOAT32 FloatVec4::Dot(IN CONST FloatVec4 &other) CONST
|
auto FloatVec4::dot(const FloatVec4 &other) const -> f32
|
||||||
{
|
{
|
||||||
const Tag d;
|
const Tag d;
|
||||||
auto vMul = hn::Mul(m_data, other.m_data);
|
auto vMul = hn::Mul(m_data, other.m_data);
|
||||||
return hn::ReduceSum(d, vMul);
|
return hn::ReduceSum(d, vMul);
|
||||||
}
|
}
|
||||||
|
|
||||||
FloatVec4 FloatVec4::Normalize() CONST
|
auto FloatVec4::normalize() const -> FloatVec4
|
||||||
{
|
{
|
||||||
const Tag d;
|
const Tag d;
|
||||||
auto vMul = hn::Mul(m_data, m_data);
|
auto vMul = hn::Mul(m_data, m_data);
|
||||||
@ -304,13 +289,13 @@ namespace IACore
|
|||||||
return FloatVec4(hn::Mul(m_data, vInvLen));
|
return FloatVec4(hn::Mul(m_data, vInvLen));
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID FloatVec4::Store(OUT PFLOAT32 values)
|
auto FloatVec4::store(f32 *values) -> void
|
||||||
{
|
{
|
||||||
const Tag d;
|
const Tag d;
|
||||||
hn::Store(m_data, d, values);
|
hn::Store(m_data, d, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
FloatVec4 FloatVec4::Load(IN PCFLOAT32 values)
|
auto FloatVec4::load(const f32 *values) -> FloatVec4
|
||||||
{
|
{
|
||||||
const Tag d;
|
const Tag d;
|
||||||
return FloatVec4(hn::Load(d, values));
|
return FloatVec4(hn::Load(d, values));
|
||||||
|
|||||||
@ -18,89 +18,98 @@
|
|||||||
#include <IACore/PCH.hpp>
|
#include <IACore/PCH.hpp>
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
#include <afunix.h>
|
||||||
# include <winsock2.h>
|
#include <winsock2.h>
|
||||||
# include <ws2tcpip.h>
|
#include <ws2tcpip.h>
|
||||||
# include <afunix.h>
|
#pragma comment(lib, "ws2_32.lib")
|
||||||
# pragma comment(lib, "ws2_32.lib")
|
|
||||||
# define CLOSE_SOCKET(s) closesocket(s)
|
|
||||||
# define IS_VALID_SOCKET(s) (s != INVALID_SOCKET)
|
|
||||||
# define UNLINK_FILE(p) DeleteFileA(p)
|
|
||||||
using SocketHandle = SOCKET;
|
|
||||||
|
|
||||||
#elif IA_PLATFORM_UNIX
|
#elif IA_PLATFORM_UNIX
|
||||||
|
#include <netinet/in.h>
|
||||||
# include <sys/un.h>
|
#include <sys/socket.h>
|
||||||
# include <sys/types.h>
|
#include <sys/types.h>
|
||||||
# include <sys/socket.h>
|
#include <sys/un.h>
|
||||||
# include <netinet/in.h>
|
// Define INVALID_SOCKET for Unix to match Windows API for cross-platform
|
||||||
# define CLOSE_SOCKET(s) close(s)
|
// compatibility
|
||||||
# define IS_VALID_SOCKET(s) (s >= 0)
|
#ifndef INVALID_SOCKET
|
||||||
# define INVALID_SOCKET -1
|
#define INVALID_SOCKET -1
|
||||||
# define UNLINK_FILE(p) unlink(p)
|
#endif
|
||||||
using SocketHandle = int;
|
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
#error "IACore SocketOps is not supported on this platform."
|
||||||
# error "IACore SocketOps is not supported on this platform."
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
|
||||||
class SocketOps
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
// SocketOps correctly handles multiple calls to Initialize and Terminate. Make sure
|
|
||||||
// every Initialize call is paired with a corresponding Terminate call
|
|
||||||
STATIC VOID Initialize()
|
|
||||||
{
|
|
||||||
s_initCount++;
|
|
||||||
if (s_initCount > 1)
|
|
||||||
return;
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
WSADATA wsaData;
|
using SocketHandle = SOCKET;
|
||||||
WSAStartup(MAKEWORD(2, 2), &wsaData);
|
#elif IA_PLATFORM_UNIX
|
||||||
|
using SocketHandle = int;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
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
|
||||||
|
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 {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// SocketOps correctly handles multiple calls to Initialize and Terminate. Make sure
|
// SocketOps correctly handles multiple calls to initialize and terminate.
|
||||||
// every Initialize call is paired with a corresponding Terminate call
|
// Make sure every initialize call is paired with a corresponding terminate
|
||||||
STATIC VOID Terminate()
|
// call.
|
||||||
{
|
static auto terminate() -> void {
|
||||||
s_initCount--;
|
s_init_count--;
|
||||||
if (s_initCount > 0)
|
if (s_init_count > 0) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
WSACleanup();
|
WSACleanup();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC BOOL IsPortAvailableTCP(IN UINT16 port)
|
static auto is_port_available_tcp(u16 port) -> bool {
|
||||||
{
|
return is_port_available(port, SOCK_STREAM);
|
||||||
return IsPortAvailable(port, SOCK_STREAM);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC BOOL IsPortAvailableUDP(IN UINT16 port)
|
static auto is_port_available_udp(u16 port) -> bool {
|
||||||
{
|
return is_port_available(port, SOCK_DGRAM);
|
||||||
return IsPortAvailable(port, SOCK_DGRAM);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC BOOL IsWouldBlock();
|
static auto is_would_block() -> bool;
|
||||||
|
|
||||||
STATIC VOID Close(IN SocketHandle sock);
|
static auto close(SocketHandle sock) -> void;
|
||||||
|
|
||||||
STATIC BOOL Listen(IN SocketHandle sock, IN INT32 queueSize = 5);
|
static auto listen(SocketHandle sock, i32 queue_size = 5) -> Result<void>;
|
||||||
|
|
||||||
STATIC SocketHandle CreateUnixSocket();
|
static auto create_unix_socket() -> Result<SocketHandle>;
|
||||||
|
|
||||||
STATIC BOOL BindUnixSocket(IN SocketHandle sock, IN PCCHAR path);
|
static auto bind_unix_socket(SocketHandle sock, const char *path)
|
||||||
STATIC BOOL ConnectUnixSocket(IN SocketHandle sock, IN PCCHAR path);
|
-> Result<void>;
|
||||||
|
static auto connect_unix_socket(SocketHandle sock, const char *path)
|
||||||
|
-> Result<void>;
|
||||||
|
|
||||||
private:
|
static auto unlink_file(const char *path) -> void {
|
||||||
STATIC BOOL IsPortAvailable(IN UINT16 port, IN INT32 type);
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
DeleteFileA(path);
|
||||||
|
#elif IA_PLATFORM_UNIX
|
||||||
|
unlink(path);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
STATIC INT32 s_initCount;
|
static auto is_port_available(u16 port, i32 type) -> bool;
|
||||||
};
|
|
||||||
|
private:
|
||||||
|
static i32 s_init_count;
|
||||||
|
};
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -16,88 +16,87 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <IACore/PCH.hpp>
|
#include <IACore/PCH.hpp>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
class StreamReader {
|
||||||
class StreamReader
|
public:
|
||||||
{
|
enum class StorageType {
|
||||||
enum class EStorageType
|
NonOwning,
|
||||||
{
|
OwningMmap,
|
||||||
NON_OWNING,
|
OwningVector,
|
||||||
OWNING_MMAP,
|
|
||||||
OWNING_VECTOR,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
static auto create_from_file(const Path &path) -> Result<StreamReader>;
|
||||||
INLINE EXPECT(VOID) Read(IN PVOID buffer, IN SIZE_T size);
|
|
||||||
template<typename T> NO_DISCARD("Check for EOF") EXPECT(T) Read();
|
|
||||||
|
|
||||||
VOID Skip(SIZE_T amount)
|
explicit StreamReader(Vec<u8> &&data);
|
||||||
{
|
explicit StreamReader(Span<const u8> data);
|
||||||
m_cursor = std::min(m_cursor + amount, m_dataSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
VOID Seek(SIZE_T pos)
|
|
||||||
{
|
|
||||||
m_cursor = (pos > m_dataSize) ? m_dataSize : pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
SIZE_T Cursor() CONST
|
|
||||||
{
|
|
||||||
return m_cursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
SIZE_T Size() CONST
|
|
||||||
{
|
|
||||||
return m_dataSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
SIZE_T Remaining() CONST
|
|
||||||
{
|
|
||||||
return m_dataSize - m_cursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
BOOL IsEOF() CONST
|
|
||||||
{
|
|
||||||
return m_cursor >= m_dataSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
StreamReader(IN CONST FilePath &path);
|
|
||||||
explicit StreamReader(IN Vector<UINT8> &&data);
|
|
||||||
explicit StreamReader(IN Span<CONST UINT8> data);
|
|
||||||
~StreamReader();
|
~StreamReader();
|
||||||
|
|
||||||
private:
|
StreamReader(StreamReader &&) = default;
|
||||||
PCUINT8 m_data{};
|
auto operator=(StreamReader &&) -> StreamReader & = default;
|
||||||
SIZE_T m_cursor{};
|
|
||||||
SIZE_T m_dataSize{};
|
|
||||||
Vector<UINT8> m_owningVector;
|
|
||||||
CONST EStorageType m_storageType;
|
|
||||||
};
|
|
||||||
|
|
||||||
EXPECT(VOID) StreamReader::Read(IN PVOID buffer, IN SIZE_T size)
|
StreamReader(const StreamReader &) = delete;
|
||||||
{
|
auto operator=(const StreamReader &) -> StreamReader & = delete;
|
||||||
if B_UNLIKELY ((m_cursor + size > m_dataSize))
|
|
||||||
return MakeUnexpected(String("Unexpected EOF while reading"));
|
auto read(void *buffer, usize size) -> Result<void>;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
[[nodiscard("Check for EOF")]]
|
||||||
|
auto read() -> Result<T>;
|
||||||
|
|
||||||
|
auto skip(usize amount) -> void {
|
||||||
|
m_cursor = std::min(m_cursor + amount, m_data_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto seek(usize pos) -> void {
|
||||||
|
m_cursor = (pos > m_data_size) ? m_data_size : pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto cursor() const -> usize { return m_cursor; }
|
||||||
|
|
||||||
|
[[nodiscard]] auto size() const -> usize { return m_data_size; }
|
||||||
|
|
||||||
|
[[nodiscard]] auto remaining() const -> usize {
|
||||||
|
return m_data_size - m_cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto is_eof() const -> bool { return m_cursor >= m_data_size; }
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline auto StreamReader::read(void *buffer, usize size) -> Result<void> {
|
||||||
|
if (m_cursor + size > m_data_size) [[unlikely]] {
|
||||||
|
return fail("Unexpected EOF while reading");
|
||||||
|
}
|
||||||
|
|
||||||
std::memcpy(buffer, &m_data[m_cursor], size);
|
std::memcpy(buffer, &m_data[m_cursor], size);
|
||||||
m_cursor += size;
|
m_cursor += size;
|
||||||
|
|
||||||
return {};
|
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T> NO_DISCARD("Check for EOF") EXPECT(T) StreamReader::Read()
|
|
||||||
{
|
|
||||||
constexpr SIZE_T size = sizeof(T);
|
|
||||||
|
|
||||||
if B_UNLIKELY ((m_cursor + size > m_dataSize))
|
|
||||||
return MakeUnexpected(String("Unexpected EOF while reading"));
|
|
||||||
|
|
||||||
T value;
|
T value;
|
||||||
std::memcpy(&value, &m_data[m_cursor], size);
|
std::memcpy(&value, &m_data[m_cursor], SIZE);
|
||||||
m_cursor += size;
|
m_cursor += SIZE;
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -17,49 +17,49 @@
|
|||||||
|
|
||||||
#include <IACore/PCH.hpp>
|
#include <IACore/PCH.hpp>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
|
||||||
class StreamWriter
|
class StreamWriter {
|
||||||
{
|
public:
|
||||||
enum class EStorageType
|
enum class StorageType {
|
||||||
{
|
NonOwning,
|
||||||
NON_OWNING,
|
OwningFile,
|
||||||
OWNING_FILE,
|
OwningVector,
|
||||||
OWNING_VECTOR,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
static auto create(const Path &path) -> Result<StreamWriter>;
|
||||||
BOOL Write(IN UINT8 byte, IN SIZE_T count);
|
|
||||||
BOOL Write(IN PCVOID buffer, IN SIZE_T size);
|
|
||||||
template<typename T> BOOL Write(IN CONST T &value);
|
|
||||||
|
|
||||||
PCUINT8 Data() CONST
|
|
||||||
{
|
|
||||||
return m_buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
SIZE_T Cursor() CONST
|
|
||||||
{
|
|
||||||
return m_cursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
StreamWriter();
|
StreamWriter();
|
||||||
explicit StreamWriter(IN Span<UINT8> data);
|
explicit StreamWriter(Span<u8> data);
|
||||||
explicit StreamWriter(IN CONST FilePath &path);
|
|
||||||
~StreamWriter();
|
~StreamWriter();
|
||||||
|
|
||||||
private:
|
StreamWriter(StreamWriter &&) = default;
|
||||||
PUINT8 m_buffer{};
|
auto operator=(StreamWriter &&) -> StreamWriter & = default;
|
||||||
SIZE_T m_cursor{};
|
|
||||||
SIZE_T m_capacity{};
|
StreamWriter(const StreamWriter &) = delete;
|
||||||
FilePath m_filePath{};
|
auto operator=(const StreamWriter &) -> StreamWriter & = delete;
|
||||||
Vector<UINT8> m_owningVector;
|
|
||||||
CONST EStorageType m_storageType;
|
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
|
} // namespace IACore
|
||||||
@ -17,12 +17,10 @@
|
|||||||
|
|
||||||
#include <IACore/PCH.hpp>
|
#include <IACore/PCH.hpp>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore {
|
||||||
{
|
class StringOps {
|
||||||
class StringOps
|
public:
|
||||||
{
|
static auto encode_base64(Span<const u8> data) -> String;
|
||||||
public:
|
static auto decode_base64(const String &data) -> Vec<u8>;
|
||||||
STATIC String EncodeBase64(IN Span<CONST UINT8> data);
|
};
|
||||||
STATIC Vector<UINT8> DecodeBase64(IN CONST String &data);
|
|
||||||
};
|
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -21,86 +21,48 @@
|
|||||||
|
|
||||||
namespace IACore
|
namespace IACore
|
||||||
{
|
{
|
||||||
|
|
||||||
class Utils
|
class Utils
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
INLINE STATIC String BinaryToHexString(std::span<const UINT8> data)
|
static auto get_unix_time() -> u64;
|
||||||
{
|
|
||||||
STATIC CONSTEXPR char LUT[] = "0123456789ABCDEF";
|
|
||||||
String res;
|
|
||||||
res.reserve(data.size() * 2);
|
|
||||||
|
|
||||||
for (UINT8 b : data)
|
static auto get_ticks_count() -> u64;
|
||||||
{
|
|
||||||
res.push_back(LUT[(b >> 4) & 0x0F]);
|
|
||||||
res.push_back(LUT[b & 0x0F]);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
INLINE STATIC EXPECT(Vector<UINT8>) HexStringToBinary(CONST StringView &hex)
|
static auto get_seconds_count() -> f64;
|
||||||
{
|
|
||||||
if (hex.size() % 2 != 0)
|
|
||||||
{
|
|
||||||
return MakeUnexpected(String("Hex string must have even length"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector<UINT8> out;
|
static auto get_random() -> f32;
|
||||||
out.reserve(hex.size() / 2);
|
static auto get_random(u64 max) -> u64;
|
||||||
|
static auto get_random(i64 min, i64 max) -> i64;
|
||||||
|
|
||||||
for (SIZE_T i = 0; i < hex.size(); i += 2)
|
static auto sleep(u64 milliseconds) -> void;
|
||||||
{
|
|
||||||
char high = hex[i];
|
|
||||||
char low = hex[i + 1];
|
|
||||||
|
|
||||||
auto fromHexChar = [](char c) -> int {
|
static auto binary_to_hex_string(Span<const u8> data) -> String;
|
||||||
if (c >= '0' && c <= '9')
|
|
||||||
return c - '0';
|
|
||||||
if (c >= 'A' && c <= 'F')
|
|
||||||
return c - 'A' + 10;
|
|
||||||
if (c >= 'a' && c <= 'f')
|
|
||||||
return c - 'a' + 10;
|
|
||||||
return -1;
|
|
||||||
};
|
|
||||||
|
|
||||||
int h = fromHexChar(high);
|
static auto hex_string_to_binary(StringView hex) -> Result<Vec<u8>>;
|
||||||
int l = fromHexChar(low);
|
|
||||||
|
|
||||||
if (h == -1 || l == -1)
|
template<typename Range> inline static void sort(Range &&range)
|
||||||
{
|
|
||||||
return MakeUnexpected(String("Invalid hex character found"));
|
|
||||||
}
|
|
||||||
|
|
||||||
out.push_back(CAST((h << 4) | l, UINT8));
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Range> INLINE STATIC VOID Sort(Range &&range)
|
|
||||||
{
|
{
|
||||||
std::ranges::sort(range);
|
std::ranges::sort(range);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Range, typename T> INLINE STATIC auto BinarySearchLeft(Range &&range, CONST T &value)
|
template<typename Range, typename T> inline static auto binary_search_left(Range &&range, const T &value)
|
||||||
{
|
{
|
||||||
return std::ranges::lower_bound(range, value);
|
return std::ranges::lower_bound(range, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Range, typename T> INLINE STATIC auto BinarySearchRight(Range &&range, CONST T &value)
|
template<typename Range, typename T> inline static auto binary_search_right(Range &&range, const T &value)
|
||||||
{
|
{
|
||||||
return std::ranges::upper_bound(range, value);
|
return std::ranges::upper_bound(range, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T> INLINE STATIC void HashCombine(UINT64 &seed, CONST T &v)
|
template<typename T> inline static void hash_combine(u64 &seed, const T &v)
|
||||||
{
|
{
|
||||||
UINT64 h;
|
u64 h;
|
||||||
|
|
||||||
if constexpr (std::is_constructible_v<std::string_view, T>)
|
if constexpr (std::is_constructible_v<StringView, T>)
|
||||||
{
|
{
|
||||||
std::string_view sv(v);
|
StringView sv(v);
|
||||||
auto hasher = ankerl::unordered_dense::hash<std::string_view>();
|
auto hasher = ankerl::unordered_dense::hash<StringView>();
|
||||||
h = hasher(sv);
|
h = hasher(sv);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -112,18 +74,18 @@ namespace IACore
|
|||||||
seed ^= h + 0x9e3779b97f4a7c15 + (seed << 6) + (seed >> 2);
|
seed ^= h + 0x9e3779b97f4a7c15 + (seed << 6) + (seed >> 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename... Args> INLINE STATIC UINT64 ComputeHash(CONST Args &...args)
|
template<typename... Args> inline static auto compute_hash(const Args &...args) -> u64
|
||||||
{
|
{
|
||||||
UINT64 seed = 0;
|
u64 seed = 0;
|
||||||
(HashCombine(seed, args), ...);
|
(hash_combine(seed, args), ...);
|
||||||
return seed;
|
return seed;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename... MemberPtrs>
|
template<typename T, typename... MemberPtrs>
|
||||||
INLINE STATIC UINT64 ComputeHashFlat(CONST T &obj, MemberPtrs... members)
|
inline static auto compute_hash_flat(const T &obj, MemberPtrs... members) -> u64
|
||||||
{
|
{
|
||||||
UINT64 seed = 0;
|
u64 seed = 0;
|
||||||
(HashCombine(seed, obj.*members), ...);
|
(hash_combine(seed, obj.*members), ...);
|
||||||
return seed;
|
return seed;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -142,10 +104,10 @@ namespace IACore
|
|||||||
template<> struct ankerl::unordered_dense::hash<Type> \
|
template<> struct ankerl::unordered_dense::hash<Type> \
|
||||||
{ \
|
{ \
|
||||||
using is_avalanching = void; \
|
using is_avalanching = void; \
|
||||||
NO_DISCARD("Hash value should be used") \
|
IA_NODISCARD \
|
||||||
UINT64 operator()(CONST Type &v) const NOEXCEPT \
|
auto operator()(const Type &v) const noexcept -> IACore::u64 \
|
||||||
{ \
|
{ \
|
||||||
/* Pass the object and the list of member pointers */ \
|
/* Pass the object and the list of member pointers */ \
|
||||||
return IACore::Utils::ComputeHashFlat(v, __VA_ARGS__); \
|
return IACore::Utils::compute_hash_flat(v, __VA_ARGS__); \
|
||||||
} \
|
} \
|
||||||
};
|
};
|
||||||
@ -28,12 +28,12 @@ namespace IACore
|
|||||||
using Document = pugi::xml_document;
|
using Document = pugi::xml_document;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
STATIC EXPECT(Document) ParseFromString(IN CONST String &data);
|
static auto parse_from_string(const String &data) -> Result<Document>;
|
||||||
STATIC EXPECT(Document) ParseFromFile(IN CONST FilePath &path);
|
static auto parse_from_file(const Path &path) -> Result<Document>;
|
||||||
|
|
||||||
STATIC String SerializeToString(IN CONST Node &node, IN BOOL escape = false);
|
static auto serialize_to_string(const Node &node, bool escape = false) -> String;
|
||||||
STATIC String SerializeToString(IN CONST Document &doc, IN BOOL escape = false);
|
static auto serialize_to_string(const Document &doc, bool escape = false) -> String;
|
||||||
|
|
||||||
STATIC String EscapeXMLString(IN CONST String &xml);
|
static auto escape_xml_string(const String &xml) -> String;
|
||||||
};
|
};
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
@ -1,4 +1,4 @@
|
|||||||
|
|
||||||
add_subdirectory(Subjects/)
|
#add_subdirectory(Subjects/)
|
||||||
add_subdirectory(Unit/)
|
#add_subdirectory(Unit/)
|
||||||
add_subdirectory(Regression/)
|
#add_subdirectory(Regression/)
|
||||||
|
|||||||
@ -25,33 +25,33 @@ using namespace IACore;
|
|||||||
|
|
||||||
IAT_BEGIN_BLOCK(Core, DataOps)
|
IAT_BEGIN_BLOCK(Core, DataOps)
|
||||||
|
|
||||||
BOOL TestCRC32()
|
bool TestCRC32() {
|
||||||
{
|
|
||||||
{
|
{
|
||||||
String s = "123456789";
|
String s = "123456789";
|
||||||
Span<CONST UINT8> span(reinterpret_cast<PCUINT8>(s.data()), s.size());
|
Span<const u8> span(reinterpret_cast<const u8 *>(s.data()), s.size());
|
||||||
UINT32 result = DataOps::CRC32(span);
|
u32 result = DataOps::CRC32(span);
|
||||||
|
|
||||||
IAT_CHECK_EQ(result, 0xE3069283);
|
IAT_CHECK_EQ(result, 0xE3069283);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
UINT32 result = DataOps::CRC32({});
|
u32 result = DataOps::CRC32({});
|
||||||
IAT_CHECK_EQ(result, 0U);
|
IAT_CHECK_EQ(result, 0U);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
std::vector<UINT8> buffer(33);
|
std::vector<u8> buffer(33);
|
||||||
for (size_t i = 1; i < 33; ++i)
|
for (size_t i = 1; i < 33; ++i)
|
||||||
buffer[i] = (UINT8) i;
|
buffer[i] = (u8)i;
|
||||||
|
|
||||||
std::vector<UINT8> refData(32);
|
std::vector<u8> refData(32);
|
||||||
for (size_t i = 0; i < 32; ++i)
|
for (size_t i = 0; i < 32; ++i)
|
||||||
refData[i] = (UINT8) (i + 1);
|
refData[i] = (u8)(i + 1);
|
||||||
|
|
||||||
UINT32 hashRef = DataOps::CRC32(Span<CONST UINT8>(refData.data(), refData.size()));
|
u32 hashRef =
|
||||||
|
DataOps::CRC32(Span<const u8>(refData.data(), refData.size()));
|
||||||
|
|
||||||
UINT32 hashUnaligned = DataOps::CRC32(Span<CONST UINT8>(buffer.data() + 1, 32));
|
u32 hashUnaligned = DataOps::CRC32(Span<const u8>(buffer.data() + 1, 32));
|
||||||
|
|
||||||
IAT_CHECK_EQ(hashRef, hashUnaligned);
|
IAT_CHECK_EQ(hashRef, hashUnaligned);
|
||||||
}
|
}
|
||||||
@ -59,40 +59,40 @@ BOOL TestCRC32()
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL TestHash_xxHash()
|
bool TestHash_xxHash() {
|
||||||
{
|
|
||||||
{
|
{
|
||||||
String s = "123456789";
|
String s = "123456789";
|
||||||
UINT32 result = DataOps::Hash_xxHash(s);
|
u32 result = DataOps::Hash_xxHash(s);
|
||||||
IAT_CHECK_EQ(result, 0x937bad67);
|
IAT_CHECK_EQ(result, 0x937bad67);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
String s = "The quick brown fox jumps over the lazy dog";
|
String s = "The quick brown fox jumps over the lazy dog";
|
||||||
UINT32 result = DataOps::Hash_xxHash(s);
|
u32 result = DataOps::Hash_xxHash(s);
|
||||||
IAT_CHECK_EQ(result, 0xE85EA4DE);
|
IAT_CHECK_EQ(result, 0xE85EA4DE);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
String s = "Test";
|
String s = "Test";
|
||||||
UINT32 r1 = DataOps::Hash_xxHash(s);
|
u32 r1 = DataOps::Hash_xxHash(s);
|
||||||
UINT32 r2 = DataOps::Hash_xxHash(Span<CONST UINT8>((PCUINT8) s.data(), s.size()));
|
u32 r2 =
|
||||||
|
DataOps::Hash_xxHash(Span<const u8>((const u8 *)s.data(), s.size()));
|
||||||
IAT_CHECK_EQ(r1, r2);
|
IAT_CHECK_EQ(r1, r2);
|
||||||
}
|
}
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL TestHash_FNV1A()
|
bool TestHash_FNV1A() {
|
||||||
{
|
|
||||||
{
|
{
|
||||||
String s = "123456789";
|
String s = "123456789";
|
||||||
UINT32 result = DataOps::Hash_FNV1A(Span<CONST UINT8>((PCUINT8) s.data(), s.size()));
|
u32 result =
|
||||||
|
DataOps::Hash_FNV1A(Span<const u8>((const u8 *)s.data(), s.size()));
|
||||||
IAT_CHECK_EQ(result, 0xbb86b11c);
|
IAT_CHECK_EQ(result, 0xbb86b11c);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
UINT32 result = DataOps::Hash_FNV1A(Span<CONST UINT8>{});
|
u32 result = DataOps::Hash_FNV1A(Span<const u8>{});
|
||||||
IAT_CHECK_EQ(result, 0x811C9DC5);
|
IAT_CHECK_EQ(result, 0x811C9DC5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,14 +34,14 @@ IAT_BEGIN_BLOCK(Core, Environment)
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 1. Basic Set and Get (The Happy Path)
|
// 1. Basic Set and Get (The Happy Path)
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestBasicCycle()
|
bool TestBasicCycle()
|
||||||
{
|
{
|
||||||
// 1. Ensure clean slate
|
// 1. Ensure clean slate
|
||||||
Environment::Unset(TEST_KEY);
|
Environment::Unset(TEST_KEY);
|
||||||
IAT_CHECK_NOT(Environment::Exists(TEST_KEY));
|
IAT_CHECK_NOT(Environment::Exists(TEST_KEY));
|
||||||
|
|
||||||
// 2. Set
|
// 2. Set
|
||||||
BOOL setRes = Environment::Set(TEST_KEY, TEST_VAL);
|
bool setRes = Environment::Set(TEST_KEY, TEST_VAL);
|
||||||
IAT_CHECK(setRes);
|
IAT_CHECK(setRes);
|
||||||
IAT_CHECK(Environment::Exists(TEST_KEY));
|
IAT_CHECK(Environment::Exists(TEST_KEY));
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ BOOL TestBasicCycle()
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 2. Overwriting Values
|
// 2. Overwriting Values
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestOverwrite()
|
bool TestOverwrite()
|
||||||
{
|
{
|
||||||
Environment::Set(TEST_KEY, "ValueA");
|
Environment::Set(TEST_KEY, "ValueA");
|
||||||
IAT_CHECK_EQ(Environment::Get(TEST_KEY), String("ValueA"));
|
IAT_CHECK_EQ(Environment::Get(TEST_KEY), String("ValueA"));
|
||||||
@ -78,12 +78,12 @@ BOOL TestOverwrite()
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 3. Unset Logic
|
// 3. Unset Logic
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestUnset()
|
bool TestUnset()
|
||||||
{
|
{
|
||||||
Environment::Set(TEST_KEY, "To Be Deleted");
|
Environment::Set(TEST_KEY, "To Be Deleted");
|
||||||
IAT_CHECK(Environment::Exists(TEST_KEY));
|
IAT_CHECK(Environment::Exists(TEST_KEY));
|
||||||
|
|
||||||
BOOL unsetRes = Environment::Unset(TEST_KEY);
|
bool unsetRes = Environment::Unset(TEST_KEY);
|
||||||
IAT_CHECK(unsetRes);
|
IAT_CHECK(unsetRes);
|
||||||
|
|
||||||
// Verify it is actually gone
|
// Verify it is actually gone
|
||||||
@ -99,7 +99,7 @@ BOOL TestUnset()
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 4. Default Value Fallbacks
|
// 4. Default Value Fallbacks
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestDefaults()
|
bool TestDefaults()
|
||||||
{
|
{
|
||||||
const char *ghostKey = "IA_THIS_KEY_DOES_NOT_EXIST";
|
const char *ghostKey = "IA_THIS_KEY_DOES_NOT_EXIST";
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ BOOL TestDefaults()
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Does Set(Key, "") create an existing empty variable, or unset it?
|
// Does Set(Key, "") create an existing empty variable, or unset it?
|
||||||
// Standard POSIX/Windows API behavior is that it EXISTS, but is empty.
|
// Standard POSIX/Windows API behavior is that it EXISTS, but is empty.
|
||||||
BOOL TestEmptyValue()
|
bool TestEmptyValue()
|
||||||
{
|
{
|
||||||
Environment::Set(TEST_KEY, "");
|
Environment::Set(TEST_KEY, "");
|
||||||
|
|
||||||
@ -149,14 +149,14 @@ BOOL TestEmptyValue()
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 6. Validation / Bad Input
|
// 6. Validation / Bad Input
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestBadInput()
|
bool TestBadInput()
|
||||||
{
|
{
|
||||||
// Setting an empty key should fail gracefully
|
// Setting an empty key should fail gracefully
|
||||||
BOOL res = Environment::Set("", "Value");
|
bool res = Environment::Set("", "Value");
|
||||||
IAT_CHECK_NOT(res);
|
IAT_CHECK_NOT(res);
|
||||||
|
|
||||||
// Unsetting an empty key should fail
|
// Unsetting an empty key should fail
|
||||||
BOOL resUnset = Environment::Unset("");
|
bool resUnset = Environment::Unset("");
|
||||||
IAT_CHECK_NOT(resUnset);
|
IAT_CHECK_NOT(resUnset);
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|||||||
@ -37,7 +37,7 @@ IAT_BEGIN_BLOCK(Core, ProcessOps)
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 1. Basic Execution (Exit Code 0)
|
// 1. Basic Execution (Exit Code 0)
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestBasicRun()
|
bool TestBasicRun()
|
||||||
{
|
{
|
||||||
// Simple "echo hello"
|
// Simple "echo hello"
|
||||||
String captured;
|
String captured;
|
||||||
@ -57,9 +57,9 @@ BOOL TestBasicRun()
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 2. Argument Parsing
|
// 2. Argument Parsing
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestArguments()
|
bool TestArguments()
|
||||||
{
|
{
|
||||||
Vector<String> lines;
|
Vec<String> lines;
|
||||||
|
|
||||||
// Echo two distinct words.
|
// Echo two distinct words.
|
||||||
// Windows: cmd.exe /c echo one two
|
// Windows: cmd.exe /c echo one two
|
||||||
@ -83,7 +83,7 @@ BOOL TestArguments()
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 3. Error / Non-Zero Exit Codes
|
// 3. Error / Non-Zero Exit Codes
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestExitCodes()
|
bool TestExitCodes()
|
||||||
{
|
{
|
||||||
// We need a command that returns non-zero.
|
// We need a command that returns non-zero.
|
||||||
// Windows: cmd /c exit 1
|
// Windows: cmd /c exit 1
|
||||||
@ -110,7 +110,7 @@ BOOL TestExitCodes()
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 4. Missing Executable Handling
|
// 4. Missing Executable Handling
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestMissingExe()
|
bool TestMissingExe()
|
||||||
{
|
{
|
||||||
// Try to run a random string
|
// Try to run a random string
|
||||||
auto result = ProcessOps::SpawnProcessSync("sdflkjghsdflkjg", "", [](StringView) {});
|
auto result = ProcessOps::SpawnProcessSync("sdflkjghsdflkjg", "", [](StringView) {});
|
||||||
@ -132,7 +132,7 @@ BOOL TestMissingExe()
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 5. Line Buffer Logic (The 4096 split test)
|
// 5. Line Buffer Logic (The 4096 split test)
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestLargeOutput()
|
bool TestLargeOutput()
|
||||||
{
|
{
|
||||||
// Need to generate output larger than the internal 4096 buffer
|
// Need to generate output larger than the internal 4096 buffer
|
||||||
// to ensure the "partial line" logic works when a line crosses a buffer boundary.
|
// to ensure the "partial line" logic works when a line crosses a buffer boundary.
|
||||||
@ -169,7 +169,7 @@ BOOL TestLargeOutput()
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 6. Multi-Line Handling
|
// 6. Multi-Line Handling
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestMultiLine()
|
bool TestMultiLine()
|
||||||
{
|
{
|
||||||
// Windows: cmd /c "echo A && echo B"
|
// Windows: cmd /c "echo A && echo B"
|
||||||
// Linux: /bin/sh -c "echo A; echo B"
|
// Linux: /bin/sh -c "echo A; echo B"
|
||||||
@ -207,7 +207,7 @@ BOOL TestMultiLine()
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 6. Complex Command Line Arguments Handling
|
// 6. Complex Command Line Arguments Handling
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestComplexArguments()
|
bool TestComplexArguments()
|
||||||
{
|
{
|
||||||
// Should parse as 3 arguments:
|
// Should parse as 3 arguments:
|
||||||
// 1. -DDEFINED_MSG="Hello World"
|
// 1. -DDEFINED_MSG="Hello World"
|
||||||
|
|||||||
@ -23,33 +23,32 @@ IAT_BEGIN_BLOCK(Core, RingBuffer)
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 1. Basic Push Pop
|
// 1. Basic Push Pop
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestPushPop()
|
bool TestPushPop() {
|
||||||
{
|
|
||||||
// Allocate raw memory for the ring buffer
|
// Allocate raw memory for the ring buffer
|
||||||
// ControlBlock (128 bytes) + Data
|
// ControlBlock (128 bytes) + Data
|
||||||
std::vector<UINT8> memory(sizeof(RingBufferView::ControlBlock) + 1024);
|
std::vector<u8> memory(sizeof(RingBufferView::ControlBlock) + 1024);
|
||||||
|
|
||||||
// Initialize as OWNER (Producer)
|
// Initialize as OWNER (Producer)
|
||||||
RingBufferView producer(std::span<UINT8>(memory), TRUE);
|
RingBufferView producer(std::span<u8>(memory), TRUE);
|
||||||
|
|
||||||
// Initialize as CONSUMER (Pointer to same memory)
|
// Initialize as CONSUMER (Pointer to same memory)
|
||||||
RingBufferView consumer(std::span<UINT8>(memory), FALSE);
|
RingBufferView consumer(std::span<u8>(memory), FALSE);
|
||||||
|
|
||||||
// Data to send
|
// Data to send
|
||||||
String msg = "Hello RingBuffer";
|
String msg = "Hello RingBuffer";
|
||||||
BOOL pushed = producer.Push(1, {(const UINT8 *) msg.data(), msg.size()});
|
bool pushed = producer.Push(1, {(const u8 *)msg.data(), msg.size()});
|
||||||
IAT_CHECK(pushed);
|
IAT_CHECK(pushed);
|
||||||
|
|
||||||
// Read back
|
// Read back
|
||||||
RingBufferView::PacketHeader header;
|
RingBufferView::PacketHeader header;
|
||||||
UINT8 readBuf[128];
|
u8 readBuf[128];
|
||||||
|
|
||||||
INT32 bytesRead = consumer.Pop(header, std::span<UINT8>(readBuf, 128));
|
i32 bytesRead = consumer.Pop(header, std::span<u8>(readBuf, 128));
|
||||||
|
|
||||||
IAT_CHECK_EQ(header.ID, (UINT16) 1);
|
IAT_CHECK_EQ(header.ID, (u16)1);
|
||||||
IAT_CHECK_EQ(bytesRead, (INT32) msg.size());
|
IAT_CHECK_EQ(bytesRead, (i32)msg.size());
|
||||||
|
|
||||||
String readMsg((char *) readBuf, bytesRead);
|
String readMsg((char *)readBuf, bytesRead);
|
||||||
IAT_CHECK_EQ(readMsg, msg);
|
IAT_CHECK_EQ(readMsg, msg);
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
@ -58,37 +57,35 @@ BOOL TestPushPop()
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 2. Wrap Around
|
// 2. Wrap Around
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestWrapAround()
|
bool TestWrapAround() {
|
||||||
{
|
|
||||||
// Small buffer to force wrapping quickly
|
// Small buffer to force wrapping quickly
|
||||||
// Capacity will be 100 bytes
|
// Capacity will be 100 bytes
|
||||||
std::vector<UINT8> memory(sizeof(RingBufferView::ControlBlock) + 100);
|
std::vector<u8> memory(sizeof(RingBufferView::ControlBlock) + 100);
|
||||||
RingBufferView rb(std::span<UINT8>(memory), TRUE);
|
RingBufferView rb(std::span<u8>(memory), TRUE);
|
||||||
|
|
||||||
// Fill buffer to near end
|
// Fill buffer to near end
|
||||||
// Push 80 bytes
|
// Push 80 bytes
|
||||||
std::vector<UINT8> junk(80, 0xFF);
|
std::vector<u8> junk(80, 0xFF);
|
||||||
rb.Push(1, junk);
|
rb.Push(1, junk);
|
||||||
|
|
||||||
// Pop them to advance READ cursor
|
// Pop them to advance READ cursor
|
||||||
RingBufferView::PacketHeader header;
|
RingBufferView::PacketHeader header;
|
||||||
UINT8 outBuf[100];
|
u8 outBuf[100];
|
||||||
rb.Pop(header, outBuf);
|
rb.Pop(header, outBuf);
|
||||||
|
|
||||||
// Now READ and WRITE are near index 80.
|
// Now READ and WRITE are near index 80.
|
||||||
// Pushing 40 bytes should trigger a wrap-around (split write)
|
// Pushing 40 bytes should trigger a wrap-around (split write)
|
||||||
std::vector<UINT8> wrapData(40, 0xAA);
|
std::vector<u8> wrapData(40, 0xAA);
|
||||||
BOOL pushed = rb.Push(2, wrapData);
|
bool pushed = rb.Push(2, wrapData);
|
||||||
IAT_CHECK(pushed);
|
IAT_CHECK(pushed);
|
||||||
|
|
||||||
// Pop and verify integrity
|
// Pop and verify integrity
|
||||||
INT32 popSize = rb.Pop(header, outBuf);
|
i32 popSize = rb.Pop(header, outBuf);
|
||||||
IAT_CHECK_EQ(popSize, 40);
|
IAT_CHECK_EQ(popSize, 40);
|
||||||
|
|
||||||
// Check if data is intact
|
// Check if data is intact
|
||||||
BOOL match = TRUE;
|
bool match = TRUE;
|
||||||
for (int i = 0; i < 40; i++)
|
for (int i = 0; i < 40; i++) {
|
||||||
{
|
|
||||||
if (outBuf[i] != 0xAA)
|
if (outBuf[i] != 0xAA)
|
||||||
match = FALSE;
|
match = FALSE;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,12 +20,12 @@ using namespace IACore;
|
|||||||
|
|
||||||
IAT_BEGIN_BLOCK(Core, FloatVec4)
|
IAT_BEGIN_BLOCK(Core, FloatVec4)
|
||||||
|
|
||||||
BOOL TestFloatArithmetic()
|
bool TestFloatArithmetic()
|
||||||
{
|
{
|
||||||
FloatVec4 v1(10.0f, 20.0f, 30.0f, 40.0f);
|
FloatVec4 v1(10.0f, 20.0f, 30.0f, 40.0f);
|
||||||
FloatVec4 v2(2.0f, 4.0f, 5.0f, 8.0f);
|
FloatVec4 v2(2.0f, 4.0f, 5.0f, 8.0f);
|
||||||
|
|
||||||
ALIGN(16) FLOAT32 res[4];
|
alignas(16) f32 res[4];
|
||||||
|
|
||||||
(v1 / v2).Store(res);
|
(v1 / v2).Store(res);
|
||||||
IAT_CHECK_APPROX(res[0], 5.0f);
|
IAT_CHECK_APPROX(res[0], 5.0f);
|
||||||
@ -40,9 +40,9 @@ BOOL TestFloatArithmetic()
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL TestMathHelpers()
|
bool TestMathHelpers()
|
||||||
{
|
{
|
||||||
ALIGN(16) FLOAT32 res[4];
|
alignas(16) f32 res[4];
|
||||||
|
|
||||||
FloatVec4 vSq(4.0f, 9.0f, 16.0f, 25.0f);
|
FloatVec4 vSq(4.0f, 9.0f, 16.0f, 25.0f);
|
||||||
vSq.Sqrt().Store(res);
|
vSq.Sqrt().Store(res);
|
||||||
@ -63,9 +63,9 @@ BOOL TestMathHelpers()
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL TestApproxMath()
|
bool TestApproxMath()
|
||||||
{
|
{
|
||||||
ALIGN(16) FLOAT32 res[4];
|
alignas(16) f32 res[4];
|
||||||
FloatVec4 v(16.0f, 25.0f, 100.0f, 1.0f);
|
FloatVec4 v(16.0f, 25.0f, 100.0f, 1.0f);
|
||||||
|
|
||||||
v.Rsqrt().Store(res);
|
v.Rsqrt().Store(res);
|
||||||
@ -76,16 +76,16 @@ BOOL TestApproxMath()
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL TestLinearAlgebra()
|
bool TestLinearAlgebra()
|
||||||
{
|
{
|
||||||
FloatVec4 v1(1.0f, 2.0f, 3.0f, 4.0f);
|
FloatVec4 v1(1.0f, 2.0f, 3.0f, 4.0f);
|
||||||
FloatVec4 v2(1.0f, 0.0f, 1.0f, 0.0f);
|
FloatVec4 v2(1.0f, 0.0f, 1.0f, 0.0f);
|
||||||
|
|
||||||
FLOAT32 dot = v1.Dot(v2);
|
f32 dot = v1.Dot(v2);
|
||||||
IAT_CHECK_APPROX(dot, 4.0f);
|
IAT_CHECK_APPROX(dot, 4.0f);
|
||||||
|
|
||||||
FloatVec4 vNorm(10.0f, 0.0f, 0.0f, 0.0f);
|
FloatVec4 vNorm(10.0f, 0.0f, 0.0f, 0.0f);
|
||||||
ALIGN(16) FLOAT32 res[4];
|
alignas(16) f32 res[4];
|
||||||
|
|
||||||
vNorm.Normalize().Store(res);
|
vNorm.Normalize().Store(res);
|
||||||
IAT_CHECK_APPROX(res[0], 1.0f);
|
IAT_CHECK_APPROX(res[0], 1.0f);
|
||||||
|
|||||||
@ -23,10 +23,10 @@ using namespace IACore;
|
|||||||
|
|
||||||
IAT_BEGIN_BLOCK(Core, IntVec4)
|
IAT_BEGIN_BLOCK(Core, IntVec4)
|
||||||
|
|
||||||
BOOL TestConstructors()
|
bool TestConstructors()
|
||||||
{
|
{
|
||||||
IntVec4 vBroadcast(10);
|
IntVec4 vBroadcast(10);
|
||||||
ALIGN(16) UINT32 storeBuf[4];
|
alignas(16) u32 storeBuf[4];
|
||||||
vBroadcast.Store(storeBuf);
|
vBroadcast.Store(storeBuf);
|
||||||
|
|
||||||
IAT_CHECK_EQ(storeBuf[0], 10U);
|
IAT_CHECK_EQ(storeBuf[0], 10U);
|
||||||
@ -37,7 +37,7 @@ BOOL TestConstructors()
|
|||||||
IAT_CHECK_EQ(storeBuf[0], 1U);
|
IAT_CHECK_EQ(storeBuf[0], 1U);
|
||||||
IAT_CHECK_EQ(storeBuf[3], 4U);
|
IAT_CHECK_EQ(storeBuf[3], 4U);
|
||||||
|
|
||||||
ALIGN(16) UINT32 srcBuf[4] = {100, 200, 300, 400};
|
alignas(16) u32 srcBuf[4] = {100, 200, 300, 400};
|
||||||
IntVec4 vLoad = IntVec4::Load(srcBuf);
|
IntVec4 vLoad = IntVec4::Load(srcBuf);
|
||||||
vLoad.Store(storeBuf);
|
vLoad.Store(storeBuf);
|
||||||
IAT_CHECK_EQ(storeBuf[1], 200U);
|
IAT_CHECK_EQ(storeBuf[1], 200U);
|
||||||
@ -45,13 +45,13 @@ BOOL TestConstructors()
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL TestArithmetic()
|
bool TestArithmetic()
|
||||||
{
|
{
|
||||||
IntVec4 v1(10, 20, 30, 40);
|
IntVec4 v1(10, 20, 30, 40);
|
||||||
IntVec4 v2(1, 2, 3, 4);
|
IntVec4 v2(1, 2, 3, 4);
|
||||||
|
|
||||||
IntVec4 vAdd = v1 + v2;
|
IntVec4 vAdd = v1 + v2;
|
||||||
ALIGN(16) UINT32 res[4];
|
alignas(16) u32 res[4];
|
||||||
vAdd.Store(res);
|
vAdd.Store(res);
|
||||||
IAT_CHECK_EQ(res[0], 11U);
|
IAT_CHECK_EQ(res[0], 11U);
|
||||||
IAT_CHECK_EQ(res[3], 44U);
|
IAT_CHECK_EQ(res[3], 44U);
|
||||||
@ -69,13 +69,13 @@ BOOL TestArithmetic()
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL TestBitwise()
|
bool TestBitwise()
|
||||||
{
|
{
|
||||||
IntVec4 vAllOnes(0xFFFFFFFF);
|
IntVec4 vAllOnes(0xFFFFFFFF);
|
||||||
IntVec4 vZero((UINT32) 0);
|
IntVec4 vZero((u32) 0);
|
||||||
IntVec4 vPattern(0xAAAAAAAA);
|
IntVec4 vPattern(0xAAAAAAAA);
|
||||||
|
|
||||||
ALIGN(16) UINT32 res[4];
|
alignas(16) u32 res[4];
|
||||||
|
|
||||||
(vAllOnes & vPattern).Store(res);
|
(vAllOnes & vPattern).Store(res);
|
||||||
IAT_CHECK_EQ(res[0], 0xAAAAAAAAU);
|
IAT_CHECK_EQ(res[0], 0xAAAAAAAAU);
|
||||||
@ -100,13 +100,13 @@ BOOL TestBitwise()
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL TestSaturation()
|
bool TestSaturation()
|
||||||
{
|
{
|
||||||
UINT32 max = 0xFFFFFFFF;
|
u32 max = 0xFFFFFFFF;
|
||||||
IntVec4 vHigh(max - 10);
|
IntVec4 vHigh(max - 10);
|
||||||
IntVec4 vAdd(20);
|
IntVec4 vAdd(20);
|
||||||
|
|
||||||
ALIGN(16) UINT32 res[4];
|
alignas(16) u32 res[4];
|
||||||
|
|
||||||
vHigh.SatAdd(vAdd).Store(res);
|
vHigh.SatAdd(vAdd).Store(res);
|
||||||
IAT_CHECK_EQ(res[0], max);
|
IAT_CHECK_EQ(res[0], max);
|
||||||
@ -119,10 +119,10 @@ BOOL TestSaturation()
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL TestAdvancedOps()
|
bool TestAdvancedOps()
|
||||||
{
|
{
|
||||||
IntVec4 v(0, 50, 100, 150);
|
IntVec4 v(0, 50, 100, 150);
|
||||||
ALIGN(16) UINT32 res[4];
|
alignas(16) u32 res[4];
|
||||||
|
|
||||||
v.Clamp(40, 110).Store(res);
|
v.Clamp(40, 110).Store(res);
|
||||||
IAT_CHECK_EQ(res[0], 40U);
|
IAT_CHECK_EQ(res[0], 40U);
|
||||||
|
|||||||
@ -22,21 +22,20 @@ using namespace IACore;
|
|||||||
IAT_BEGIN_BLOCK(Core, StreamReader)
|
IAT_BEGIN_BLOCK(Core, StreamReader)
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 1. Basic Primitive Reading (UINT8)
|
// 1. Basic Primitive Reading (u8)
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestReadUint8()
|
bool TestReadUint8() {
|
||||||
{
|
u8 data[] = {0xAA, 0xBB, 0xCC};
|
||||||
UINT8 data[] = {0xAA, 0xBB, 0xCC};
|
|
||||||
StreamReader reader(data);
|
StreamReader reader(data);
|
||||||
|
|
||||||
// Read First Byte
|
// Read First Byte
|
||||||
auto val1 = reader.Read<UINT8>();
|
auto val1 = reader.Read<u8>();
|
||||||
IAT_CHECK(val1.has_value());
|
IAT_CHECK(val1.has_value());
|
||||||
IAT_CHECK_EQ(*val1, 0xAA);
|
IAT_CHECK_EQ(*val1, 0xAA);
|
||||||
IAT_CHECK_EQ(reader.Cursor(), (SIZE_T) 1);
|
IAT_CHECK_EQ(reader.Cursor(), (usize)1);
|
||||||
|
|
||||||
// Read Second Byte
|
// Read Second Byte
|
||||||
auto val2 = reader.Read<UINT8>();
|
auto val2 = reader.Read<u8>();
|
||||||
IAT_CHECK_EQ(*val2, 0xBB);
|
IAT_CHECK_EQ(*val2, 0xBB);
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
@ -45,19 +44,18 @@ BOOL TestReadUint8()
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 2. Multi-byte Reading (Endianness check)
|
// 2. Multi-byte Reading (Endianness check)
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestReadMultiByte()
|
bool TestReadMultiByte() {
|
||||||
{
|
|
||||||
// 0x04030201 in Little Endian memory layout
|
// 0x04030201 in Little Endian memory layout
|
||||||
// IACore always assumes a Little Endian machine
|
// IACore always assumes a Little Endian machine
|
||||||
UINT8 data[] = {0x01, 0x02, 0x03, 0x04};
|
u8 data[] = {0x01, 0x02, 0x03, 0x04};
|
||||||
StreamReader reader(data);
|
StreamReader reader(data);
|
||||||
|
|
||||||
auto val = reader.Read<UINT32>();
|
auto val = reader.Read<u32>();
|
||||||
IAT_CHECK(val.has_value());
|
IAT_CHECK(val.has_value());
|
||||||
|
|
||||||
IAT_CHECK_EQ(*val, (UINT32) 0x04030201);
|
IAT_CHECK_EQ(*val, (u32)0x04030201);
|
||||||
|
|
||||||
IAT_CHECK_EQ(reader.Cursor(), (SIZE_T) 4);
|
IAT_CHECK_EQ(reader.Cursor(), (usize)4);
|
||||||
IAT_CHECK(reader.IsEOF());
|
IAT_CHECK(reader.IsEOF());
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
@ -66,15 +64,14 @@ BOOL TestReadMultiByte()
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 3. Floating Point (Approx check)
|
// 3. Floating Point (Approx check)
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestReadFloat()
|
bool TestReadFloat() {
|
||||||
{
|
f32 pi = 3.14159f;
|
||||||
FLOAT32 pi = 3.14159f;
|
|
||||||
// Bit-cast float to bytes for setup
|
// Bit-cast float to bytes for setup
|
||||||
UINT8 data[4];
|
u8 data[4];
|
||||||
std::memcpy(data, &pi, 4);
|
std::memcpy(data, &pi, 4);
|
||||||
|
|
||||||
StreamReader reader(data);
|
StreamReader reader(data);
|
||||||
auto val = reader.Read<FLOAT32>();
|
auto val = reader.Read<f32>();
|
||||||
|
|
||||||
IAT_CHECK(val.has_value());
|
IAT_CHECK(val.has_value());
|
||||||
IAT_CHECK_APPROX(*val, pi);
|
IAT_CHECK_APPROX(*val, pi);
|
||||||
@ -85,10 +82,9 @@ BOOL TestReadFloat()
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 4. Batch Buffer Reading
|
// 4. Batch Buffer Reading
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestReadBuffer()
|
bool TestReadBuffer() {
|
||||||
{
|
u8 src[] = {1, 2, 3, 4, 5};
|
||||||
UINT8 src[] = {1, 2, 3, 4, 5};
|
u8 dst[3] = {0};
|
||||||
UINT8 dst[3] = {0};
|
|
||||||
StreamReader reader(src);
|
StreamReader reader(src);
|
||||||
|
|
||||||
// Read 3 bytes into dst
|
// Read 3 bytes into dst
|
||||||
@ -101,7 +97,7 @@ BOOL TestReadBuffer()
|
|||||||
IAT_CHECK_EQ(dst[2], 3);
|
IAT_CHECK_EQ(dst[2], 3);
|
||||||
|
|
||||||
// Verify cursor
|
// Verify cursor
|
||||||
IAT_CHECK_EQ(reader.Cursor(), (SIZE_T) 3);
|
IAT_CHECK_EQ(reader.Cursor(), (usize)3);
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@ -109,27 +105,26 @@ BOOL TestReadBuffer()
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 5. Navigation (Seek, Skip, Remaining)
|
// 5. Navigation (Seek, Skip, Remaining)
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestNavigation()
|
bool TestNavigation() {
|
||||||
{
|
u8 data[10] = {0}; // Zero init
|
||||||
UINT8 data[10] = {0}; // Zero init
|
|
||||||
StreamReader reader(data);
|
StreamReader reader(data);
|
||||||
|
|
||||||
IAT_CHECK_EQ(reader.Remaining(), (SIZE_T) 10);
|
IAT_CHECK_EQ(reader.Remaining(), (usize)10);
|
||||||
|
|
||||||
// Skip
|
// Skip
|
||||||
reader.Skip(5);
|
reader.Skip(5);
|
||||||
IAT_CHECK_EQ(reader.Cursor(), (SIZE_T) 5);
|
IAT_CHECK_EQ(reader.Cursor(), (usize)5);
|
||||||
IAT_CHECK_EQ(reader.Remaining(), (SIZE_T) 5);
|
IAT_CHECK_EQ(reader.Remaining(), (usize)5);
|
||||||
|
|
||||||
// Skip clamping
|
// Skip clamping
|
||||||
reader.Skip(100); // Should clamp to 10
|
reader.Skip(100); // Should clamp to 10
|
||||||
IAT_CHECK_EQ(reader.Cursor(), (SIZE_T) 10);
|
IAT_CHECK_EQ(reader.Cursor(), (usize)10);
|
||||||
IAT_CHECK(reader.IsEOF());
|
IAT_CHECK(reader.IsEOF());
|
||||||
|
|
||||||
// Seek
|
// Seek
|
||||||
reader.Seek(2);
|
reader.Seek(2);
|
||||||
IAT_CHECK_EQ(reader.Cursor(), (SIZE_T) 2);
|
IAT_CHECK_EQ(reader.Cursor(), (usize)2);
|
||||||
IAT_CHECK_EQ(reader.Remaining(), (SIZE_T) 8);
|
IAT_CHECK_EQ(reader.Remaining(), (usize)8);
|
||||||
IAT_CHECK_NOT(reader.IsEOF());
|
IAT_CHECK_NOT(reader.IsEOF());
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
@ -138,21 +133,20 @@ BOOL TestNavigation()
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 6. Error Handling (EOF Protection)
|
// 6. Error Handling (EOF Protection)
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestBoundaryChecks()
|
bool TestBoundaryChecks() {
|
||||||
{
|
u8 data[] = {0x00, 0x00};
|
||||||
UINT8 data[] = {0x00, 0x00};
|
|
||||||
StreamReader reader(data);
|
StreamReader reader(data);
|
||||||
|
|
||||||
// Valid read
|
// Valid read
|
||||||
UNUSED(reader.Read<UINT16>());
|
UNUSED(reader.Read<u16>());
|
||||||
IAT_CHECK(reader.IsEOF());
|
IAT_CHECK(reader.IsEOF());
|
||||||
|
|
||||||
// Invalid Read Primitive
|
// Invalid Read Primitive
|
||||||
auto val = reader.Read<UINT8>();
|
auto val = reader.Read<u8>();
|
||||||
IAT_CHECK_NOT(val.has_value()); // Should be unexpected
|
IAT_CHECK_NOT(val.has_value()); // Should be unexpected
|
||||||
|
|
||||||
// Invalid Batch Read
|
// Invalid Batch Read
|
||||||
UINT8 buf[1];
|
u8 buf[1];
|
||||||
auto batch = reader.Read(buf, 1);
|
auto batch = reader.Read(buf, 1);
|
||||||
IAT_CHECK_NOT(batch.has_value());
|
IAT_CHECK_NOT(batch.has_value());
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@ using namespace IACore;
|
|||||||
|
|
||||||
struct TestVec3
|
struct TestVec3
|
||||||
{
|
{
|
||||||
FLOAT32 x, y, z;
|
f32 x, y, z;
|
||||||
|
|
||||||
// Equality operator required for hash maps, though strictly
|
// Equality operator required for hash maps, though strictly
|
||||||
// the hash function itself doesn't need it, it's good practice to test both.
|
// the hash function itself doesn't need it, it's good practice to test both.
|
||||||
@ -48,10 +48,10 @@ IAT_BEGIN_BLOCK(Core, Utils)
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 1. Binary <-> Hex String Conversion
|
// 1. Binary <-> Hex String Conversion
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestHexConversion()
|
bool TestHexConversion()
|
||||||
{
|
{
|
||||||
// A. Binary To Hex
|
// A. Binary To Hex
|
||||||
UINT8 bin[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0xFF};
|
u8 bin[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0xFF};
|
||||||
String hex = IACore::Utils::BinaryToHexString(bin);
|
String hex = IACore::Utils::BinaryToHexString(bin);
|
||||||
|
|
||||||
IAT_CHECK_EQ(hex, String("DEADBEEF00FF"));
|
IAT_CHECK_EQ(hex, String("DEADBEEF00FF"));
|
||||||
@ -59,7 +59,7 @@ BOOL TestHexConversion()
|
|||||||
// B. Hex To Binary (Valid Upper)
|
// B. Hex To Binary (Valid Upper)
|
||||||
auto resUpper = IACore::Utils::HexStringToBinary("DEADBEEF00FF");
|
auto resUpper = IACore::Utils::HexStringToBinary("DEADBEEF00FF");
|
||||||
IAT_CHECK(resUpper.has_value());
|
IAT_CHECK(resUpper.has_value());
|
||||||
IAT_CHECK_EQ(resUpper->size(), (SIZE_T) 6);
|
IAT_CHECK_EQ(resUpper->size(), (usize) 6);
|
||||||
IAT_CHECK_EQ((*resUpper)[0], 0xDE);
|
IAT_CHECK_EQ((*resUpper)[0], 0xDE);
|
||||||
IAT_CHECK_EQ((*resUpper)[5], 0xFF);
|
IAT_CHECK_EQ((*resUpper)[5], 0xFF);
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ BOOL TestHexConversion()
|
|||||||
IAT_CHECK_EQ((*resLower)[0], 0xDE);
|
IAT_CHECK_EQ((*resLower)[0], 0xDE);
|
||||||
|
|
||||||
// D. Round Trip Integrity
|
// D. Round Trip Integrity
|
||||||
Vector<UINT8> original = {1, 2, 3, 4, 5};
|
Vec<u8> original = {1, 2, 3, 4, 5};
|
||||||
String s = IACore::Utils::BinaryToHexString(original);
|
String s = IACore::Utils::BinaryToHexString(original);
|
||||||
auto back = IACore::Utils::HexStringToBinary(s);
|
auto back = IACore::Utils::HexStringToBinary(s);
|
||||||
IAT_CHECK(back.has_value());
|
IAT_CHECK(back.has_value());
|
||||||
@ -82,7 +82,7 @@ BOOL TestHexConversion()
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 2. Hex Error Handling
|
// 2. Hex Error Handling
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestHexErrors()
|
bool TestHexErrors()
|
||||||
{
|
{
|
||||||
// Odd Length
|
// Odd Length
|
||||||
auto odd = IACore::Utils::HexStringToBinary("ABC");
|
auto odd = IACore::Utils::HexStringToBinary("ABC");
|
||||||
@ -95,7 +95,7 @@ BOOL TestHexErrors()
|
|||||||
// Empty string is valid (empty vector)
|
// Empty string is valid (empty vector)
|
||||||
auto empty = IACore::Utils::HexStringToBinary("");
|
auto empty = IACore::Utils::HexStringToBinary("");
|
||||||
IAT_CHECK(empty.has_value());
|
IAT_CHECK(empty.has_value());
|
||||||
IAT_CHECK_EQ(empty->size(), (SIZE_T) 0);
|
IAT_CHECK_EQ(empty->size(), (usize) 0);
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@ -103,9 +103,9 @@ BOOL TestHexErrors()
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 3. Algorithms: Sorting
|
// 3. Algorithms: Sorting
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestSort()
|
bool TestSort()
|
||||||
{
|
{
|
||||||
Vector<int> nums = {5, 1, 4, 2, 3};
|
Vec<int> nums = {5, 1, 4, 2, 3};
|
||||||
|
|
||||||
IACore::Utils::Sort(nums);
|
IACore::Utils::Sort(nums);
|
||||||
|
|
||||||
@ -121,10 +121,10 @@ BOOL TestSort()
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 4. Algorithms: Binary Search (Left/Right)
|
// 4. Algorithms: Binary Search (Left/Right)
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestBinarySearch()
|
bool TestBinarySearch()
|
||||||
{
|
{
|
||||||
// Must be sorted for Binary Search
|
// Must be sorted for Binary Search
|
||||||
Vector<int> nums = {10, 20, 20, 20, 30};
|
Vec<int> nums = {10, 20, 20, 20, 30};
|
||||||
|
|
||||||
// Search Left (Lower Bound) -> First element >= value
|
// Search Left (Lower Bound) -> First element >= value
|
||||||
auto itLeft = IACore::Utils::BinarySearchLeft(nums, 20);
|
auto itLeft = IACore::Utils::BinarySearchLeft(nums, 20);
|
||||||
@ -148,11 +148,11 @@ BOOL TestBinarySearch()
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 5. Hashing Basics
|
// 5. Hashing Basics
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestHashBasics()
|
bool TestHashBasics()
|
||||||
{
|
{
|
||||||
UINT64 h1 = IACore::Utils::ComputeHash(10, 20.5f, "Hello");
|
u64 h1 = IACore::Utils::ComputeHash(10, 20.5f, "Hello");
|
||||||
UINT64 h2 = IACore::Utils::ComputeHash(10, 20.5f, "Hello");
|
u64 h2 = IACore::Utils::ComputeHash(10, 20.5f, "Hello");
|
||||||
UINT64 h3 = IACore::Utils::ComputeHash(10, 20.5f, "World");
|
u64 h3 = IACore::Utils::ComputeHash(10, 20.5f, "World");
|
||||||
|
|
||||||
// Determinism
|
// Determinism
|
||||||
IAT_CHECK_EQ(h1, h2);
|
IAT_CHECK_EQ(h1, h2);
|
||||||
@ -162,8 +162,8 @@ BOOL TestHashBasics()
|
|||||||
|
|
||||||
// Order sensitivity (Golden ratio combine should care about order)
|
// Order sensitivity (Golden ratio combine should care about order)
|
||||||
// Hash(A, B) != Hash(B, A)
|
// Hash(A, B) != Hash(B, A)
|
||||||
UINT64 orderA = IACore::Utils::ComputeHash(1, 2);
|
u64 orderA = IACore::Utils::ComputeHash(1, 2);
|
||||||
UINT64 orderB = IACore::Utils::ComputeHash(2, 1);
|
u64 orderB = IACore::Utils::ComputeHash(2, 1);
|
||||||
IAT_CHECK_NEQ(orderA, orderB);
|
IAT_CHECK_NEQ(orderA, orderB);
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
@ -172,7 +172,7 @@ BOOL TestHashBasics()
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 6. Macro Verification (IA_MAKE_HASHABLE)
|
// 6. Macro Verification (IA_MAKE_HASHABLE)
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
BOOL TestHashMacro()
|
bool TestHashMacro()
|
||||||
{
|
{
|
||||||
TestVec3 v1{1.0f, 2.0f, 3.0f};
|
TestVec3 v1{1.0f, 2.0f, 3.0f};
|
||||||
TestVec3 v2{1.0f, 2.0f, 3.0f};
|
TestVec3 v2{1.0f, 2.0f, 3.0f};
|
||||||
@ -180,9 +180,9 @@ BOOL TestHashMacro()
|
|||||||
|
|
||||||
ankerl::unordered_dense::hash<TestVec3> hasher;
|
ankerl::unordered_dense::hash<TestVec3> hasher;
|
||||||
|
|
||||||
UINT64 h1 = hasher(v1);
|
u64 h1 = hasher(v1);
|
||||||
UINT64 h2 = hasher(v2);
|
u64 h2 = hasher(v2);
|
||||||
UINT64 h3 = hasher(v3);
|
u64 h3 = hasher(v3);
|
||||||
|
|
||||||
IAT_CHECK_EQ(h1, h2); // Same content = same hash
|
IAT_CHECK_EQ(h1, h2); // Same content = same hash
|
||||||
IAT_CHECK_NEQ(h1, h3); // Different content = different hash
|
IAT_CHECK_NEQ(h1, h3); // Different content = different hash
|
||||||
@ -191,10 +191,10 @@ BOOL TestHashMacro()
|
|||||||
// Verify ComputeHash integration
|
// Verify ComputeHash integration
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
|
|
||||||
UINT64 hManual = 0;
|
u64 hManual = 0;
|
||||||
IACore::Utils::HashCombine(hManual, v1);
|
IACore::Utils::HashCombine(hManual, v1);
|
||||||
|
|
||||||
UINT64 hWrapper = IACore::Utils::ComputeHash(v1);
|
u64 hWrapper = IACore::Utils::ComputeHash(v1);
|
||||||
|
|
||||||
// This proves ComputeHash found the specialization and mixed it correctly
|
// This proves ComputeHash found the specialization and mixed it correctly
|
||||||
IAT_CHECK_EQ(hManual, hWrapper);
|
IAT_CHECK_EQ(hManual, hWrapper);
|
||||||
|
|||||||
Reference in New Issue
Block a user