This commit is contained in:
@ -14,6 +14,13 @@ set(ZLIB_COMPAT ON CACHE BOOL "" FORCE)
|
||||
set(ZLIB_ENABLE_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(WITH_GZFILEOP ON CACHE BOOL "" FORCE)
|
||||
|
||||
FetchContent_Declare(
|
||||
Oxide
|
||||
GIT_REPOSITORY https://github.com/I-A-S/Oxide
|
||||
GIT_TAG main
|
||||
OVERRIDE_FIND_PACKAGE
|
||||
)
|
||||
|
||||
FetchContent_Declare(
|
||||
zlib
|
||||
GIT_REPOSITORY https://github.com/zlib-ng/zlib-ng.git
|
||||
@ -77,14 +84,6 @@ FetchContent_Declare(
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/CMake/PatchMimalloc.cmake
|
||||
)
|
||||
|
||||
FetchContent_Declare(
|
||||
tl-expected
|
||||
GIT_REPOSITORY https://github.com/TartanLlama/expected.git
|
||||
GIT_TAG v1.3.1
|
||||
SYSTEM
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
|
||||
FetchContent_Declare(
|
||||
unordered_dense
|
||||
GIT_REPOSITORY https://github.com/martinus/unordered_dense.git
|
||||
@ -134,7 +133,7 @@ if(NOT TARGET zstd::libzstd)
|
||||
add_library(zstd::libzstd ALIAS libzstd_static)
|
||||
endif()
|
||||
|
||||
FetchContent_MakeAvailable(httplib pugixml nlohmann_json glaze simdjson tl-expected unordered_dense mimalloc highway)
|
||||
FetchContent_MakeAvailable(Oxide httplib pugixml nlohmann_json glaze simdjson unordered_dense mimalloc highway)
|
||||
|
||||
if(NOT TARGET simdjson::simdjson)
|
||||
add_library(simdjson::simdjson ALIAS simdjson)
|
||||
|
||||
313
Docs/CODING-STYLE.md
Normal file
313
Docs/CODING-STYLE.md
Normal file
@ -0,0 +1,313 @@
|
||||
# **IACore - C++ Style Guide**
|
||||
|
||||
This document defines the coding standards for IACore and all IA Projects.
|
||||
Philosophy: Enforce Rust-like memory safety and discipline within modern C++20.
|
||||
|
||||
---
|
||||
### **1. Oxide Usage**
|
||||
|
||||
Usage of [`Oxide`](https://github.com/I-A-S/Oxide) library and its philosophy is mandatory.
|
||||
|
||||
`Oxide` is a single header library and a truncated version of it is listed below.
|
||||
|
||||
```cpp
|
||||
#define OX_UNUSED(v) (void)(v)
|
||||
|
||||
namespace Oxide {
|
||||
|
||||
// =============================================================================
|
||||
// Primitive Types
|
||||
// =============================================================================
|
||||
|
||||
using u8 = std::uint8_t;
|
||||
using u16 = std::uint16_t;
|
||||
using u32 = std::uint32_t;
|
||||
using u64 = std::uint64_t;
|
||||
|
||||
using i8 = std::int8_t;
|
||||
using i16 = std::int16_t;
|
||||
using i32 = std::int32_t;
|
||||
using i64 = std::int64_t;
|
||||
|
||||
using f32 = float;
|
||||
using f64 = double;
|
||||
|
||||
using usize = std::size_t;
|
||||
using isize = std::ptrdiff_t;
|
||||
|
||||
// =============================================================================
|
||||
// Template Types
|
||||
// =============================================================================
|
||||
|
||||
template <typename T> using Const = const T;
|
||||
template <typename T> using Mut = T;
|
||||
|
||||
template <typename T> using Ref = const T &;
|
||||
template <typename T> using MutRef = T &;
|
||||
template <typename T> using ForwardRef = T &&;
|
||||
|
||||
// =============================================================================
|
||||
// Memory & Ownership
|
||||
// =============================================================================
|
||||
|
||||
template <typename T> using Box = std::unique_ptr<T>;
|
||||
template <typename T> using Arc = std::shared_ptr<T>;
|
||||
template <typename T> using Weak = std::weak_ptr<T>;
|
||||
|
||||
template <typename T, typename... Args>
|
||||
[[nodiscard]] inline auto make_box(ForwardRef<Args>... args) -> Box<T> {
|
||||
return std::make_unique<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
[[nodiscard]] inline auto make_arc(ForwardRef<Args>... args) -> Arc<T> {
|
||||
return std::make_shared<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Error Handling
|
||||
// =============================================================================
|
||||
|
||||
template <typename T, typename E = std::string>
|
||||
using Result = Oxide::Internal::Expected<T, E>;
|
||||
|
||||
template <typename E> [[nodiscard]] inline auto fail(ForwardRef<E> error) {
|
||||
return Oxide::Internal::make_unexpected(std::forward<E>(error));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
[[nodiscard]] inline auto fail(Ref<std::format_string<Args...>> fmt,
|
||||
ForwardRef<Args>... args) {
|
||||
return Oxide::Internal::make_unexpected(
|
||||
std::format(fmt, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Utilities
|
||||
// =============================================================================
|
||||
|
||||
namespace Env {
|
||||
#if defined(NDEBUG)
|
||||
constexpr Const<bool> IS_DEBUG = false;
|
||||
constexpr Const<bool> IS_RELEASE = true;
|
||||
#else
|
||||
constexpr Const<bool> IS_DEBUG = true;
|
||||
constexpr Const<bool> IS_RELEASE = false;
|
||||
#endif
|
||||
} // namespace Env
|
||||
|
||||
[[noreturn]] inline void
|
||||
panic(Ref<std::string> msg,
|
||||
Ref<std::source_location> loc = std::source_location::current()) {
|
||||
std::cerr << "\n[panic] " << msg << "\n At: " << loc.file_name()
|
||||
<< ":" << loc.line() << "\n";
|
||||
std::abort();
|
||||
}
|
||||
|
||||
inline void
|
||||
ensure(Const<bool> condition, Ref<std::string> msg,
|
||||
Ref<std::source_location> loc = std::source_location::current()) {
|
||||
if (Env::IS_DEBUG && !condition) {
|
||||
std::cerr << "\n[assert] " << msg << "\n At: " << loc.file_name()
|
||||
<< ":" << loc.line() << "\n";
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
using String = std::string;
|
||||
using StringView = std::string_view;
|
||||
|
||||
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 T, usize Count> using Array = std::array<T, Count>;
|
||||
|
||||
} // namespace Oxide
|
||||
|
||||
#define OX_TRY_PURE(expr) \
|
||||
{ \
|
||||
auto _ox_res = (expr); \
|
||||
if (!_ox_res) { \
|
||||
return Oxide::Internal::make_unexpected(std::move(_ox_res.error())); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define OX_TRY(expr) \
|
||||
__extension__({ \
|
||||
auto _ox_res = (expr); \
|
||||
if (!_ox_res) { \
|
||||
return Oxide::Internal::make_unexpected(std::move(_ox_res.error())); \
|
||||
} \
|
||||
std::move(*_ox_res); \
|
||||
})
|
||||
|
||||
#define OX_TRY_DISCARD(expr) \
|
||||
{ \
|
||||
auto _ox_res = (expr); \
|
||||
if (!_ox_res) { \
|
||||
return Oxide::Internal::make_unexpected(std::move(_ox_res.error())); \
|
||||
} \
|
||||
OX_UNUSED(*_ox_res); \
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Note: no need to explicity use `Oxide::` prefix as Oxide namespace is almost always globally used in IA projects.
|
||||
|
||||
All variables (local, member and parameter/argument) MUST always be wrapped in one of `Const<T>, Mut<T>, Ref<T>, MutRef<T>` or `ForwardRef<T>`.
|
||||
|
||||
> For outer most template of parameters/arguments, use the following Rule to decide between `Ref<T>` or `Const<T>`:
|
||||
> 1) Is the parameter size <= 64, then use `Const`
|
||||
> 2) Otherwise use `Ref`
|
||||
|
||||
| Use This | Instead of This |
|
||||
| :---- | :---- |
|
||||
| ```Box``` | ```std::unique_ptr``` |
|
||||
| ```Arc``` | ```std::shared_ptr``` |
|
||||
| ```Weak``` | ```std::weak_ptr``` |
|
||||
| ```make_box``` | ```std::make_unique``` |
|
||||
| ```make_arc``` | ```std::make_shared``` |
|
||||
|
||||
---
|
||||
|
||||
### **2. Naming Conventions**
|
||||
|
||||
We use **Rust Naming Conventions** to strictly distinguish Types from Values.
|
||||
|
||||
| Element | Style | Example |
|
||||
| :---- | :---- | :---- |
|
||||
| **Types / Classes** | PascalCase | ```StreamReader, TcpSocket``` |
|
||||
| **Functions** | snake_case | ```read_bytes(), create_instance()``` |
|
||||
| **Variables** | snake_case | ```buffer_size, socket_id``` |
|
||||
| **Private Members** | m_snake_case | ```m_parser, m_data_size``` |
|
||||
| **Constants** | SCREAMING_SNAKE | ```MAX_PATH_LENGTH``` |
|
||||
| **Namespaces** | PascalCase | ```IACore, Oxide, IACore::Env``` |
|
||||
| **Files** | PascalCase | ```StreamReader.hpp``` |
|
||||
|
||||
**Rule:** Never use Hungarian notation (e.g., iCount, strName) except for the m_ prefix on private members.
|
||||
|
||||
---
|
||||
|
||||
### **3. Primitives Types**
|
||||
|
||||
Do not use standard C++ primitives (int, long, unsigned int, double, char).
|
||||
Use the IACore Rust Aliases defined in IACore.hpp.
|
||||
|
||||
| Use This | Instead of This |
|
||||
| :---- | :---- |
|
||||
| ```i8, i16, i32, i64``` | ```int8_t, short, int, long long``` |
|
||||
| ```u8, u16, u32, u64``` | ```uint8_t, unsigned short, unsigned int``` |
|
||||
| ```f32, f64``` | ```float, double``` |
|
||||
| ```usize``` | ```size_t, std::size_t``` |
|
||||
| ```isize``` | ```std::ptrdiff_t``` |
|
||||
|
||||
**Exception:** Use char only for C-string literals or std::format compatibility.
|
||||
|
||||
---
|
||||
|
||||
### **4. Ownership & Pointers**
|
||||
|
||||
Adhere to strict ownership semantics. **Raw pointers (T\*) are non-owning.**
|
||||
|
||||
* **Box (ia::Box<T>):** Exclusive ownership. Use ia::make_box<T>().
|
||||
* **Arc (ia::Arc<T>):** Shared ownership (atomic ref-counting). Use sparingly.
|
||||
* **Raw Pointers (T\*):** Borrowed, non-owning, mutable view. **Must not be deleted.**
|
||||
* **Const Pointers (const T\*):** Borrowed, non-owning, read-only view.
|
||||
|
||||
**Forbidden:**
|
||||
|
||||
* new / delete / malloc / free.
|
||||
* Passing std::shared_ptr by value (unless sharing ownership is the intent).
|
||||
|
||||
---
|
||||
|
||||
### **5. Error Handling**
|
||||
|
||||
Exceptions are BANNED.
|
||||
All fallible functions must return ia::Result<T>.
|
||||
**Correct:**
|
||||
|
||||
```cpp
|
||||
// Returns value OR error string
|
||||
auto read_file(const Path& path) -> ia::Result<String>;
|
||||
```
|
||||
Checking Errors (The IA_TRY Macro):
|
||||
Use IA_TRY to propagate errors (equivalent to Rust's ? operator).
|
||||
|
||||
```cpp
|
||||
auto process_data() -> Result<String> {
|
||||
IA_TRY(auto data, read_file("test.txt")); // Returns error if read_file fails
|
||||
return process(data);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **6. Function Signatures**
|
||||
|
||||
Use **Trailing Return Syntax** for all functions (even void returns). This aligns with Rust (fn name() -> type) and handles complex template returns better.
|
||||
|
||||
**Correct:**
|
||||
|
||||
```cpp
|
||||
auto calculate_sum(u32 a, u32 b) -> u32;
|
||||
auto do_something() -> void;
|
||||
```
|
||||
**Incorrect:**
|
||||
|
||||
```cpp
|
||||
u32 calculate_sum(u32 a, u32 b);
|
||||
void do_something();
|
||||
```
|
||||
---
|
||||
|
||||
### **7. Const Correctness and Auto Usage**
|
||||
|
||||
Immutable by Default.
|
||||
|
||||
`auto` keyword is BANNED.
|
||||
|
||||
Variables should be const unless mutation is explicitly required.
|
||||
|
||||
**Correct:**
|
||||
|
||||
```cpp
|
||||
Const<String> path = "data.bin";
|
||||
Const<i32> result = process(path);
|
||||
```
|
||||
**Incorrect:**
|
||||
|
||||
|
||||
```cpp
|
||||
String path = "data.bin";
|
||||
const auto result = process(path);
|
||||
```
|
||||
---
|
||||
|
||||
### **8. Macros**
|
||||
|
||||
Avoid Macros.
|
||||
Use constexpr for constants and inline templates for logic.
|
||||
|
||||
* **Allowed:** IA_TRY, IA_NODISCARD, Platform detection (internal).
|
||||
* **Banned:** TRUE, FALSE, CONST, IN, OUT.
|
||||
|
||||
### **9. Class Design (The "Safe Wrapper" Pattern)**
|
||||
|
||||
If a class wraps a C-library or raw resource (like simdjson), it must:
|
||||
|
||||
1. Be **Move-Only** (Delete Copy Constructor).
|
||||
2. Use a **Factory Method** (create()) that returns Result<Box<T>> or Result<T>.
|
||||
3. Never expose raw handles that outlive the wrapper.
|
||||
|
||||
```cpp
|
||||
class SafeWrapper {
|
||||
public:
|
||||
static auto create() -> Result<SafeWrapper>;
|
||||
|
||||
SafeWrapper(SafeWrapper&&) = default;
|
||||
SafeWrapper(const SafeWrapper&) = delete;
|
||||
};
|
||||
```
|
||||
---
|
||||
@ -30,8 +30,8 @@ target_include_directories(IACore PRIVATE imp/hpp/)
|
||||
target_link_libraries(IACore PUBLIC
|
||||
hwy
|
||||
zlib
|
||||
Oxide
|
||||
zstd::libzstd
|
||||
tl::expected
|
||||
glaze::glaze
|
||||
httplib::httplib
|
||||
pugixml::pugixml
|
||||
|
||||
@ -16,20 +16,20 @@
|
||||
#include <IACore/AsyncOps.hpp>
|
||||
|
||||
namespace IACore {
|
||||
std::mutex AsyncOps::s_queue_mutex;
|
||||
std::condition_variable AsyncOps::s_wake_condition;
|
||||
Vec<std::jthread> AsyncOps::s_schedule_workers;
|
||||
std::deque<AsyncOps::ScheduledTask> AsyncOps::s_high_priority_queue;
|
||||
std::deque<AsyncOps::ScheduledTask> AsyncOps::s_normal_priority_queue;
|
||||
Mut<std::mutex> AsyncOps::s_queue_mutex;
|
||||
Mut<std::condition_variable> AsyncOps::s_wake_condition;
|
||||
Mut<Vec<std::jthread>> AsyncOps::s_schedule_workers;
|
||||
Mut<std::deque<AsyncOps::ScheduledTask>> AsyncOps::s_high_priority_queue;
|
||||
Mut<std::deque<AsyncOps::ScheduledTask>> AsyncOps::s_normal_priority_queue;
|
||||
|
||||
auto AsyncOps::run_task(std::function<void()> task) -> void {
|
||||
auto AsyncOps::run_task(Mut<std::function<void()>> task) -> void {
|
||||
std::jthread(std::move(task)).detach();
|
||||
}
|
||||
|
||||
auto AsyncOps::initialize_scheduler(u8 worker_count) -> Result<void> {
|
||||
auto AsyncOps::initialize_scheduler(Mut<u8> worker_count) -> Result<void> {
|
||||
if (worker_count == 0) {
|
||||
const auto hw_concurrency = std::thread::hardware_concurrency();
|
||||
u32 threads = 2;
|
||||
Const<u32> hw_concurrency = std::thread::hardware_concurrency();
|
||||
Mut<u32> threads = 2;
|
||||
if (hw_concurrency > 2) {
|
||||
threads = hw_concurrency - 2;
|
||||
}
|
||||
@ -40,7 +40,7 @@ auto AsyncOps::initialize_scheduler(u8 worker_count) -> Result<void> {
|
||||
worker_count = static_cast<u8>(threads);
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < worker_count; ++i) {
|
||||
for (Mut<u32> i = 0; i < worker_count; ++i) {
|
||||
s_schedule_workers.emplace_back(schedule_worker_loop,
|
||||
static_cast<WorkerId>(i + 1));
|
||||
}
|
||||
@ -49,13 +49,13 @@ auto AsyncOps::initialize_scheduler(u8 worker_count) -> Result<void> {
|
||||
}
|
||||
|
||||
auto AsyncOps::terminate_scheduler() -> void {
|
||||
for (auto &worker : s_schedule_workers) {
|
||||
for (MutRef<std::jthread> worker : s_schedule_workers) {
|
||||
worker.request_stop();
|
||||
}
|
||||
|
||||
s_wake_condition.notify_all();
|
||||
|
||||
for (auto &worker : s_schedule_workers) {
|
||||
for (MutRef<std::jthread> worker : s_schedule_workers) {
|
||||
if (worker.joinable()) {
|
||||
worker.join();
|
||||
}
|
||||
@ -64,15 +64,15 @@ auto AsyncOps::terminate_scheduler() -> void {
|
||||
s_schedule_workers.clear();
|
||||
}
|
||||
|
||||
auto AsyncOps::schedule_task(std::function<void(WorkerId worker_id)> task,
|
||||
TaskTag tag, Schedule *schedule, Priority priority)
|
||||
-> void {
|
||||
auto AsyncOps::schedule_task(Mut<std::function<void(WorkerId worker_id)>> task,
|
||||
Const<TaskTag> tag, Const<Schedule *> schedule,
|
||||
Const<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);
|
||||
Const<std::lock_guard<std::mutex>> lock(s_queue_mutex);
|
||||
if (priority == Priority::High) {
|
||||
s_high_priority_queue.emplace_back(
|
||||
ScheduledTask{tag, schedule, std::move(task)});
|
||||
@ -84,11 +84,14 @@ auto AsyncOps::schedule_task(std::function<void(WorkerId worker_id)> task,
|
||||
s_wake_condition.notify_one();
|
||||
}
|
||||
|
||||
auto AsyncOps::cancel_tasks_of_tag(TaskTag tag) -> void {
|
||||
std::lock_guard lock(s_queue_mutex);
|
||||
auto AsyncOps::cancel_tasks_of_tag(Const<TaskTag> tag) -> void {
|
||||
Const<std::lock_guard<std::mutex>> lock(s_queue_mutex);
|
||||
|
||||
auto cancel_from_queue = [&](std::deque<ScheduledTask> &queue) {
|
||||
for (auto it = queue.begin(); it != queue.end(); /* no increment */) {
|
||||
{
|
||||
MutRef<std::deque<ScheduledTask>> queue = s_high_priority_queue;
|
||||
for (Mut<std::deque<ScheduledTask>::iterator> it = queue.begin();
|
||||
it != queue.end();
|
||||
/* no incr */) {
|
||||
if (it->tag == tag) {
|
||||
if (it->schedule_handle->counter.fetch_sub(1) == 1) {
|
||||
it->schedule_handle->counter.notify_all();
|
||||
@ -98,21 +101,35 @@ auto AsyncOps::cancel_tasks_of_tag(TaskTag tag) -> void {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
cancel_from_queue(s_high_priority_queue);
|
||||
cancel_from_queue(s_normal_priority_queue);
|
||||
{
|
||||
MutRef<std::deque<ScheduledTask>> queue = s_normal_priority_queue;
|
||||
for (Mut<std::deque<ScheduledTask>::iterator> it = queue.begin();
|
||||
it != queue.end();
|
||||
/* no incr */) {
|
||||
if (it->tag == tag) {
|
||||
if (it->schedule_handle->counter.fetch_sub(1) == 1) {
|
||||
it->schedule_handle->counter.notify_all();
|
||||
}
|
||||
it = queue.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto AsyncOps::wait_for_schedule_completion(Schedule *schedule) -> void {
|
||||
auto AsyncOps::wait_for_schedule_completion(Const<Schedule *> schedule)
|
||||
-> void {
|
||||
ensure(!s_schedule_workers.empty(), "Scheduler must be initialized before "
|
||||
"calling wait_for_schedule_completion");
|
||||
|
||||
while (schedule->counter.load() > 0) {
|
||||
ScheduledTask task;
|
||||
bool found_task = false;
|
||||
Mut<ScheduledTask> task;
|
||||
Mut<bool> found_task = false;
|
||||
{
|
||||
std::unique_lock lock(s_queue_mutex);
|
||||
Mut<std::unique_lock<std::mutex>> lock(s_queue_mutex);
|
||||
if (!s_high_priority_queue.empty()) {
|
||||
task = std::move(s_high_priority_queue.front());
|
||||
s_high_priority_queue.pop_front();
|
||||
@ -130,7 +147,7 @@ auto AsyncOps::wait_for_schedule_completion(Schedule *schedule) -> void {
|
||||
task.schedule_handle->counter.notify_all();
|
||||
}
|
||||
} else {
|
||||
const auto current_val = schedule->counter.load();
|
||||
Const<u32> current_val = schedule->counter.load();
|
||||
if (current_val > 0) {
|
||||
schedule->counter.wait(current_val);
|
||||
}
|
||||
@ -142,13 +159,13 @@ auto AsyncOps::get_worker_count() -> WorkerId {
|
||||
return static_cast<WorkerId>(s_schedule_workers.size());
|
||||
}
|
||||
|
||||
auto AsyncOps::schedule_worker_loop(std::stop_token stop_token,
|
||||
WorkerId worker_id) -> void {
|
||||
auto AsyncOps::schedule_worker_loop(Const<std::stop_token> stop_token,
|
||||
Const<WorkerId> worker_id) -> void {
|
||||
while (!stop_token.stop_requested()) {
|
||||
ScheduledTask task;
|
||||
bool found_task = false;
|
||||
Mut<ScheduledTask> task;
|
||||
Mut<bool> found_task = false;
|
||||
{
|
||||
std::unique_lock lock(s_queue_mutex);
|
||||
Mut<std::unique_lock<std::mutex>> lock(s_queue_mutex);
|
||||
|
||||
s_wake_condition.wait(lock, [&stop_token] {
|
||||
return !s_high_priority_queue.empty() ||
|
||||
|
||||
@ -16,11 +16,12 @@
|
||||
#include <IACore/CLI.hpp>
|
||||
|
||||
namespace IACore {
|
||||
CLIParser::CLIParser(Span<const String> args) : m_arg_list(args) {
|
||||
CLIParser::CLIParser(Const<Span<Const<String>>> args) : m_arg_list(args) {
|
||||
m_current_arg = m_arg_list.begin();
|
||||
|
||||
// Skip executable path
|
||||
if (m_current_arg != m_arg_list.end())
|
||||
if (m_current_arg != m_arg_list.end()) {
|
||||
m_current_arg++;
|
||||
}
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -31,46 +31,46 @@
|
||||
|
||||
namespace IACore {
|
||||
template <typename T>
|
||||
[[nodiscard]] inline auto read_unaligned(const u8 *ptr) -> T {
|
||||
T v;
|
||||
[[nodiscard]] inline auto read_unaligned(Const<Const<u8> *> ptr) -> T {
|
||||
Mut<T> v;
|
||||
std::memcpy(&v, ptr, sizeof(T));
|
||||
return v;
|
||||
}
|
||||
|
||||
struct Crc32Tables {
|
||||
u32 table[8][256] = {};
|
||||
Mut<u32> table[8][256] = {};
|
||||
|
||||
consteval Crc32Tables() {
|
||||
constexpr u32 T = 0x82F63B78;
|
||||
constexpr Const<u32> T = 0x82F63B78;
|
||||
|
||||
for (u32 i = 0; i < 256; i++) {
|
||||
u32 crc = i;
|
||||
for (i32 j = 0; j < 8; j++) {
|
||||
for (Mut<u32> i = 0; i < 256; i++) {
|
||||
Mut<u32> crc = i;
|
||||
for (Mut<i32> j = 0; j < 8; j++) {
|
||||
crc = (crc >> 1) ^ ((crc & 1) ? T : 0);
|
||||
}
|
||||
table[0][i] = crc;
|
||||
}
|
||||
|
||||
for (i32 i = 0; i < 256; i++) {
|
||||
for (i32 slice = 1; slice < 8; slice++) {
|
||||
const u32 prev = table[slice - 1][i];
|
||||
for (Mut<i32> i = 0; i < 256; i++) {
|
||||
for (Mut<i32> slice = 1; slice < 8; slice++) {
|
||||
Const<u32> prev = table[slice - 1][i];
|
||||
table[slice][i] = (prev >> 8) ^ table[0][prev & 0xFF];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr Crc32Tables CRC32_TABLES{};
|
||||
static constexpr Const<Crc32Tables> CRC32_TABLES{};
|
||||
|
||||
#if IA_ARCH_X64
|
||||
inline auto crc32_x64_hw(Span<const u8> data) -> u32 {
|
||||
const u8 *p = data.data();
|
||||
inline auto crc32_x64_hw(Ref<Span<Const<u8>>> data) -> u32 {
|
||||
Mut<const u8 *> p = data.data();
|
||||
|
||||
u32 crc = 0xFFFFFFFF;
|
||||
usize len = data.size();
|
||||
Mut<u32> crc = 0xFFFFFFFF;
|
||||
Mut<usize> len = data.size();
|
||||
|
||||
while (len >= 8) {
|
||||
const u64 chunk = read_unaligned<u64>(p);
|
||||
Const<u64> chunk = read_unaligned<u64>(p);
|
||||
crc = static_cast<u32>(_mm_crc32_u64(static_cast<u64>(crc), chunk));
|
||||
p += 8;
|
||||
len -= 8;
|
||||
@ -85,15 +85,15 @@ inline auto crc32_x64_hw(Span<const u8> data) -> u32 {
|
||||
#endif
|
||||
|
||||
#if IA_ARCH_ARM64
|
||||
__attribute__((target("+crc"))) inline auto crc32_arm64_hw(Span<const u8> data)
|
||||
-> u32 {
|
||||
const u8 *p = data.data();
|
||||
__attribute__((target("+crc"))) inline auto
|
||||
crc32_arm64_hw(Ref<Span<Const<u8>>> data) -> u32 {
|
||||
Mut<const u8 *> p = data.data();
|
||||
|
||||
u32 crc = 0xFFFFFFFF;
|
||||
usize len = data.size();
|
||||
Mut<u32> crc = 0xFFFFFFFF;
|
||||
Mut<usize> len = data.size();
|
||||
|
||||
while (len >= 8) {
|
||||
const u64 chunk = read_unaligned<u64>(p);
|
||||
Const<u64> chunk = read_unaligned<u64>(p);
|
||||
crc = __crc32cd(crc, chunk);
|
||||
p += 8;
|
||||
len -= 8;
|
||||
@ -107,14 +107,14 @@ __attribute__((target("+crc"))) inline auto crc32_arm64_hw(Span<const u8> data)
|
||||
}
|
||||
#endif
|
||||
|
||||
inline auto crc32_software_slice8(Span<const u8> data) -> u32 {
|
||||
const u8 *p = data.data();
|
||||
u32 crc = 0xFFFFFFFF;
|
||||
usize len = data.size();
|
||||
inline auto crc32_software_slice8(Ref<Span<Const<u8>>> data) -> u32 {
|
||||
Mut<const u8 *> p = data.data();
|
||||
Mut<u32> crc = 0xFFFFFFFF;
|
||||
Mut<usize> len = data.size();
|
||||
|
||||
while (len >= 8) {
|
||||
const u32 term1 = crc ^ read_unaligned<u32>(p);
|
||||
const u32 term2 = read_unaligned<u32>(p + 4);
|
||||
Const<u32> term1 = crc ^ read_unaligned<u32>(p);
|
||||
Const<u32> term2 = read_unaligned<u32>(p + 4);
|
||||
|
||||
crc = CRC32_TABLES.table[7][term1 & 0xFF] ^
|
||||
CRC32_TABLES.table[6][(term1 >> 8) & 0xFF] ^
|
||||
@ -136,10 +136,9 @@ inline auto crc32_software_slice8(Span<const u8> data) -> u32 {
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
auto DataOps::crc32(Span<const u8> data) -> u32 {
|
||||
auto DataOps::crc32(Ref<Span<Const<u8>>> data) -> u32 {
|
||||
#if IA_ARCH_X64
|
||||
// IACore mandates AVX2 so no need to check
|
||||
// for Platform::GetCapabilities().HardwareCRC32
|
||||
return crc32_x64_hw(data);
|
||||
#elif IA_ARCH_ARM64
|
||||
if (Platform::GetCapabilities().HardwareCRC32) {
|
||||
@ -149,41 +148,38 @@ auto DataOps::crc32(Span<const u8> data) -> u32 {
|
||||
return crc32_software_slice8(data);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// xxHash
|
||||
// =============================================================================
|
||||
constexpr Const<u32> XXH_PRIME32_1 = 0x9E3779B1U;
|
||||
constexpr Const<u32> XXH_PRIME32_2 = 0x85EBCA77U;
|
||||
constexpr Const<u32> XXH_PRIME32_3 = 0xC2B2AE3DU;
|
||||
constexpr Const<u32> XXH_PRIME32_4 = 0x27D4EB2FU;
|
||||
constexpr Const<u32> XXH_PRIME32_5 = 0x165667B1U;
|
||||
|
||||
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 {
|
||||
inline auto xxh32_round(Mut<u32> seed, Const<u32> input) -> u32 {
|
||||
seed += input * XXH_PRIME32_2;
|
||||
seed = std::rotl(seed, 13);
|
||||
seed *= XXH_PRIME32_1;
|
||||
return seed;
|
||||
}
|
||||
|
||||
auto DataOps::hash_xxhash(const String &string, u32 seed) -> u32 {
|
||||
return hash_xxhash(Span<const u8>(reinterpret_cast<const u8 *>(string.data()),
|
||||
auto DataOps::hash_xxhash(Ref<String> string, Const<u32> seed) -> u32 {
|
||||
return hash_xxhash(
|
||||
Span<Const<u8>>(reinterpret_cast<const u8 *>(string.data()),
|
||||
string.length()),
|
||||
seed);
|
||||
}
|
||||
|
||||
auto DataOps::hash_xxhash(Span<const u8> data, u32 seed) -> u32 {
|
||||
const u8 *p = data.data();
|
||||
const u8 *const b_end = p + data.size();
|
||||
u32 h32{};
|
||||
auto DataOps::hash_xxhash(Ref<Span<Const<u8>>> data, Const<u32> seed) -> u32 {
|
||||
Mut<const u8 *> p = data.data();
|
||||
Const<const u8 *> b_end = p + data.size();
|
||||
Mut<u32> h32{};
|
||||
|
||||
if (data.size() >= 16) {
|
||||
const u8 *const limit = b_end - 16;
|
||||
Const<const u8 *> limit = b_end - 16;
|
||||
|
||||
u32 v1 = seed + XXH_PRIME32_1 + XXH_PRIME32_2;
|
||||
u32 v2 = seed + XXH_PRIME32_2;
|
||||
u32 v3 = seed + 0;
|
||||
u32 v4 = seed - XXH_PRIME32_1;
|
||||
Mut<u32> v1 = seed + XXH_PRIME32_1 + XXH_PRIME32_2;
|
||||
Mut<u32> v2 = seed + XXH_PRIME32_2;
|
||||
Mut<u32> v3 = seed + 0;
|
||||
Mut<u32> v4 = seed - XXH_PRIME32_1;
|
||||
|
||||
do {
|
||||
v1 = xxh32_round(v1, read_unaligned<u32>(p));
|
||||
@ -205,7 +201,7 @@ auto DataOps::hash_xxhash(Span<const u8> data, u32 seed) -> u32 {
|
||||
h32 += static_cast<u32>(data.size());
|
||||
|
||||
while (p + 4 <= b_end) {
|
||||
const auto t = read_unaligned<u32>(p) * XXH_PRIME32_3;
|
||||
Const<u32> t = read_unaligned<u32>(p) * XXH_PRIME32_3;
|
||||
h32 += t;
|
||||
h32 = std::rotl(h32, 17) * XXH_PRIME32_4;
|
||||
p += 4;
|
||||
@ -225,49 +221,39 @@ auto DataOps::hash_xxhash(Span<const u8> data, u32 seed) -> u32 {
|
||||
return h32;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// FNV-1a
|
||||
// =============================================================================
|
||||
constexpr Const<u32> FNV1A_32_PRIME = 0x01000193;
|
||||
constexpr Const<u32> FNV1A_32_OFFSET = 0x811c9dc5;
|
||||
|
||||
constexpr u32 FNV1A_32_PRIME = 0x01000193;
|
||||
constexpr u32 FNV1A_32_OFFSET = 0x811c9dc5;
|
||||
|
||||
auto DataOps::hash_fnv1a(const String &string) -> u32 {
|
||||
u32 hash = FNV1A_32_OFFSET;
|
||||
for (const char c : string) {
|
||||
auto DataOps::hash_fnv1a(Ref<String> string) -> u32 {
|
||||
Mut<u32> hash = FNV1A_32_OFFSET;
|
||||
for (Const<char> c : string) {
|
||||
hash ^= static_cast<u8>(c);
|
||||
hash *= FNV1A_32_PRIME;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
auto DataOps::hash_fnv1a(Span<const u8> data) -> u32 {
|
||||
u32 hash = FNV1A_32_OFFSET;
|
||||
const auto *ptr = data.data();
|
||||
auto DataOps::hash_fnv1a(Ref<Span<Const<u8>>> data) -> u32 {
|
||||
Mut<u32> hash = FNV1A_32_OFFSET;
|
||||
Const<const u8 *> ptr = data.data();
|
||||
|
||||
for (usize i = 0; i < data.size(); ++i) {
|
||||
for (Mut<usize> i = 0; i < data.size(); ++i) {
|
||||
hash ^= ptr[i];
|
||||
hash *= FNV1A_32_PRIME;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Compression
|
||||
// =============================================================================
|
||||
|
||||
auto DataOps::detect_compression(Span<const u8> data) -> CompressionType {
|
||||
auto DataOps::detect_compression(Const<Span<Const<u8>>> data)
|
||||
-> CompressionType {
|
||||
if (data.size() < 2) {
|
||||
return CompressionType::None;
|
||||
}
|
||||
|
||||
// Check for GZIP Magic Number (0x1F 0x8B)
|
||||
if (data[0] == 0x1F && data[1] == 0x8B) {
|
||||
return CompressionType::Gzip;
|
||||
}
|
||||
|
||||
// Check for ZLIB Magic Number (starts with 0x78)
|
||||
// 0x78 = Deflate compression with 32k window size
|
||||
if (data[0] == 0x78 &&
|
||||
(data[1] == 0x01 || data[1] == 0x9C || data[1] == 0xDA)) {
|
||||
return CompressionType::Zlib;
|
||||
@ -276,13 +262,12 @@ auto DataOps::detect_compression(Span<const u8> data) -> CompressionType {
|
||||
return CompressionType::None;
|
||||
}
|
||||
|
||||
auto DataOps::zlib_inflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||
z_stream zs{};
|
||||
auto DataOps::zlib_inflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>> {
|
||||
Mut<z_stream> zs{};
|
||||
zs.zalloc = Z_NULL;
|
||||
zs.zfree = Z_NULL;
|
||||
zs.opaque = Z_NULL;
|
||||
|
||||
// 15 + 32 = Auto-detect Gzip or Zlib
|
||||
if (inflateInit2(&zs, 15 + 32) != Z_OK) {
|
||||
return fail("Failed to initialize zlib inflate");
|
||||
}
|
||||
@ -290,20 +275,19 @@ auto DataOps::zlib_inflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||
zs.next_in = const_cast<Bytef *>(data.data());
|
||||
zs.avail_in = static_cast<uInt>(data.size());
|
||||
|
||||
Vec<u8> out_buffer;
|
||||
// Start with 2x input size.
|
||||
const usize guess_size =
|
||||
Mut<Vec<u8>> out_buffer;
|
||||
Const<usize> guess_size =
|
||||
data.size() < 1024 ? data.size() * 4 : data.size() * 2;
|
||||
out_buffer.resize(guess_size);
|
||||
|
||||
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data());
|
||||
zs.avail_out = static_cast<uInt>(out_buffer.size());
|
||||
|
||||
int ret;
|
||||
Mut<int> ret;
|
||||
do {
|
||||
if (zs.avail_out == 0) {
|
||||
const usize current_pos = zs.total_out;
|
||||
const usize new_size = out_buffer.size() * 2;
|
||||
Const<usize> current_pos = zs.total_out;
|
||||
Const<usize> new_size = out_buffer.size() * 2;
|
||||
out_buffer.resize(new_size);
|
||||
|
||||
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data() + current_pos);
|
||||
@ -325,8 +309,8 @@ auto DataOps::zlib_inflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
auto DataOps::zlib_deflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||
z_stream zs{};
|
||||
auto DataOps::zlib_deflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>> {
|
||||
Mut<z_stream> zs{};
|
||||
zs.zalloc = Z_NULL;
|
||||
zs.zfree = Z_NULL;
|
||||
zs.opaque = Z_NULL;
|
||||
@ -338,13 +322,13 @@ auto DataOps::zlib_deflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||
zs.next_in = const_cast<Bytef *>(data.data());
|
||||
zs.avail_in = static_cast<uInt>(data.size());
|
||||
|
||||
Vec<u8> out_buffer;
|
||||
Mut<Vec<u8>> out_buffer;
|
||||
out_buffer.resize(deflateBound(&zs, static_cast<uLong>(data.size())));
|
||||
|
||||
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data());
|
||||
zs.avail_out = static_cast<uInt>(out_buffer.size());
|
||||
|
||||
const int ret = deflate(&zs, Z_FINISH);
|
||||
Const<int> ret = deflate(&zs, Z_FINISH);
|
||||
|
||||
if (ret != Z_STREAM_END) {
|
||||
deflateEnd(&zs);
|
||||
@ -357,8 +341,8 @@ auto DataOps::zlib_deflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
auto DataOps::zstd_inflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||
const unsigned long long content_size =
|
||||
auto DataOps::zstd_inflate(Ref<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) {
|
||||
@ -366,11 +350,10 @@ auto DataOps::zstd_inflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||
}
|
||||
|
||||
if (content_size != ZSTD_CONTENTSIZE_UNKNOWN) {
|
||||
// FAST PATH: We know the size
|
||||
Vec<u8> out_buffer;
|
||||
Mut<Vec<u8>> out_buffer;
|
||||
out_buffer.resize(static_cast<usize>(content_size));
|
||||
|
||||
const usize d_size = ZSTD_decompress(out_buffer.data(), out_buffer.size(),
|
||||
Const<usize> d_size = ZSTD_decompress(out_buffer.data(), out_buffer.size(),
|
||||
data.data(), data.size());
|
||||
|
||||
if (ZSTD_isError(d_size)) {
|
||||
@ -380,14 +363,14 @@ auto DataOps::zstd_inflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
ZSTD_DCtx *dctx = ZSTD_createDCtx();
|
||||
Vec<u8> out_buffer;
|
||||
Mut<ZSTD_DCtx *> dctx = ZSTD_createDCtx();
|
||||
Mut<Vec<u8>> out_buffer;
|
||||
out_buffer.resize(data.size() * 2);
|
||||
|
||||
ZSTD_inBuffer input = {data.data(), data.size(), 0};
|
||||
ZSTD_outBuffer output = {out_buffer.data(), out_buffer.size(), 0};
|
||||
Mut<ZSTD_inBuffer> input = {data.data(), data.size(), 0};
|
||||
Mut<ZSTD_outBuffer> output = {out_buffer.data(), out_buffer.size(), 0};
|
||||
|
||||
usize ret;
|
||||
Mut<usize> ret;
|
||||
do {
|
||||
ret = ZSTD_decompressStream(dctx, &output, &input);
|
||||
|
||||
@ -397,7 +380,7 @@ auto DataOps::zstd_inflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||
}
|
||||
|
||||
if (output.pos == output.size) {
|
||||
const usize new_size = out_buffer.size() * 2;
|
||||
Const<usize> new_size = out_buffer.size() * 2;
|
||||
out_buffer.resize(new_size);
|
||||
output.dst = out_buffer.data();
|
||||
output.size = new_size;
|
||||
@ -411,13 +394,13 @@ auto DataOps::zstd_inflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
auto DataOps::zstd_deflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||
const usize max_dst_size = ZSTD_compressBound(data.size());
|
||||
auto DataOps::zstd_deflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>> {
|
||||
Const<usize> max_dst_size = ZSTD_compressBound(data.size());
|
||||
|
||||
Vec<u8> out_buffer;
|
||||
Mut<Vec<u8>> out_buffer;
|
||||
out_buffer.resize(max_dst_size);
|
||||
|
||||
const usize compressed_size = ZSTD_compress(out_buffer.data(), 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)) {
|
||||
@ -428,15 +411,12 @@ auto DataOps::zstd_deflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
auto DataOps::gzip_deflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||
z_stream zs{};
|
||||
auto DataOps::gzip_deflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>> {
|
||||
Mut<z_stream> zs{};
|
||||
zs.zalloc = Z_NULL;
|
||||
zs.zfree = Z_NULL;
|
||||
zs.opaque = Z_NULL;
|
||||
|
||||
// WindowBits = 15 + 16 (31) = Enforce GZIP encoding
|
||||
// MemLevel = 8 (default)
|
||||
// Strategy = Z_DEFAULT_STRATEGY
|
||||
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8,
|
||||
Z_DEFAULT_STRATEGY) != Z_OK) {
|
||||
return fail("Failed to initialize gzip deflate");
|
||||
@ -445,15 +425,14 @@ auto DataOps::gzip_deflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||
zs.next_in = const_cast<Bytef *>(data.data());
|
||||
zs.avail_in = static_cast<uInt>(data.size());
|
||||
|
||||
Vec<u8> out_buffer;
|
||||
Mut<Vec<u8>> out_buffer;
|
||||
|
||||
out_buffer.resize(deflateBound(&zs, static_cast<uLong>(data.size())) +
|
||||
1024); // Additional 1KB buffer for safety
|
||||
out_buffer.resize(deflateBound(&zs, static_cast<uLong>(data.size())) + 1024);
|
||||
|
||||
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data());
|
||||
zs.avail_out = static_cast<uInt>(out_buffer.size());
|
||||
|
||||
const int ret = deflate(&zs, Z_FINISH);
|
||||
Const<int> ret = deflate(&zs, Z_FINISH);
|
||||
|
||||
if (ret != Z_STREAM_END) {
|
||||
deflateEnd(&zs);
|
||||
@ -466,7 +445,7 @@ auto DataOps::gzip_deflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
auto DataOps::gzip_inflate(Span<const u8> data) -> Result<Vec<u8>> {
|
||||
auto DataOps::gzip_inflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>> {
|
||||
return zlib_inflate(data);
|
||||
}
|
||||
|
||||
|
||||
@ -26,43 +26,44 @@
|
||||
|
||||
namespace IACore {
|
||||
|
||||
HashMap<const u8 *, std::tuple<void *, void *, void *>> FileOps::s_mapped_files;
|
||||
Mut<HashMap<const u8 *, std::tuple<void *, void *, void *>>>
|
||||
FileOps::s_mapped_files;
|
||||
|
||||
auto FileOps::unmap_file(const u8 *mapped_ptr) -> void {
|
||||
auto FileOps::unmap_file(Const<Const<u8> *> mapped_ptr) -> void {
|
||||
if (!s_mapped_files.contains(mapped_ptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = s_mapped_files.find(mapped_ptr);
|
||||
const auto handles = it->second;
|
||||
Mut<decltype(s_mapped_files)::iterator> it = s_mapped_files.find(mapped_ptr);
|
||||
Const<std::tuple<void *, void *, void *>> handles = it->second;
|
||||
s_mapped_files.erase(it);
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
::UnmapViewOfFile(std::get<1>(handles));
|
||||
::CloseHandle(static_cast<HANDLE>(std::get<2>(handles)));
|
||||
|
||||
const auto handle = static_cast<HANDLE>(std::get<0>(handles));
|
||||
Const<HANDLE> handle = static_cast<HANDLE>(std::get<0>(handles));
|
||||
if (handle != INVALID_HANDLE_VALUE) {
|
||||
::CloseHandle(handle);
|
||||
}
|
||||
#elif IA_PLATFORM_UNIX
|
||||
::munmap(std::get<1>(handles), (usize)std::get<2>(handles));
|
||||
const auto fd = (i32)((u64)std::get<0>(handles));
|
||||
Const<i32> fd = (i32)((u64)std::get<0>(handles));
|
||||
if (fd != -1) {
|
||||
::close(fd);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
auto FileOps::map_shared_memory(const String &name, usize size, bool is_owner)
|
||||
-> Result<u8 *> {
|
||||
auto FileOps::map_shared_memory(Ref<String> name, Const<usize> size,
|
||||
Const<bool> is_owner) -> Result<u8 *> {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
const int wchars_num =
|
||||
Const<int> wchars_num =
|
||||
MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, NULL, 0);
|
||||
std::wstring w_name(wchars_num, 0);
|
||||
Mut<std::wstring> w_name(wchars_num, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, &w_name[0], wchars_num);
|
||||
|
||||
HANDLE h_map = NULL;
|
||||
Mut<HANDLE> h_map = NULL;
|
||||
if (is_owner) {
|
||||
h_map = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
|
||||
(DWORD)(size >> 32), (DWORD)(size & 0xFFFFFFFF),
|
||||
@ -76,7 +77,7 @@ auto FileOps::map_shared_memory(const String &name, usize size, bool is_owner)
|
||||
is_owner ? "owner" : "consumer", name);
|
||||
}
|
||||
|
||||
auto *result =
|
||||
Mut<u8 *> result =
|
||||
static_cast<u8 *>(MapViewOfFile(h_map, FILE_MAP_ALL_ACCESS, 0, 0, size));
|
||||
if (result == NULL) {
|
||||
CloseHandle(h_map);
|
||||
@ -88,7 +89,7 @@ auto FileOps::map_shared_memory(const String &name, usize size, bool is_owner)
|
||||
return result;
|
||||
|
||||
#elif IA_PLATFORM_UNIX
|
||||
int fd = -1;
|
||||
Mut<int> fd = -1;
|
||||
if (is_owner) {
|
||||
fd = shm_open(name.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0666);
|
||||
if (fd != -1) {
|
||||
@ -107,13 +108,14 @@ auto FileOps::map_shared_memory(const String &name, usize size, bool is_owner)
|
||||
is_owner ? "owner" : "consumer", name);
|
||||
}
|
||||
|
||||
void *addr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
Mut<void *> addr =
|
||||
mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (addr == MAP_FAILED) {
|
||||
close(fd);
|
||||
return fail("Failed to mmap shared memory '{}'", name);
|
||||
}
|
||||
|
||||
auto *result = static_cast<u8 *>(addr);
|
||||
Mut<u8 *> result = static_cast<u8 *>(addr);
|
||||
|
||||
s_mapped_files[result] =
|
||||
std::make_tuple((void *)((u64)fd), (void *)addr, (void *)size);
|
||||
@ -121,7 +123,7 @@ auto FileOps::map_shared_memory(const String &name, usize size, bool is_owner)
|
||||
#endif
|
||||
}
|
||||
|
||||
auto FileOps::unlink_shared_memory(const String &name) -> void {
|
||||
auto FileOps::unlink_shared_memory(Ref<String> name) -> void {
|
||||
if (name.empty()) {
|
||||
return;
|
||||
}
|
||||
@ -130,9 +132,10 @@ auto FileOps::unlink_shared_memory(const String &name) -> void {
|
||||
#endif
|
||||
}
|
||||
|
||||
auto FileOps::map_file(const Path &path, usize &size) -> Result<const u8 *> {
|
||||
auto FileOps::map_file(Ref<Path> path, MutRef<usize> size)
|
||||
-> Result<const u8 *> {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
const auto handle = CreateFileA(
|
||||
Const<HANDLE> handle = CreateFileA(
|
||||
path.string().c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
|
||||
|
||||
@ -140,7 +143,7 @@ auto FileOps::map_file(const Path &path, usize &size) -> Result<const u8 *> {
|
||||
return fail("Failed to open {} for memory mapping", path.string());
|
||||
}
|
||||
|
||||
LARGE_INTEGER file_size;
|
||||
Mut<LARGE_INTEGER> file_size;
|
||||
if (!GetFileSizeEx(handle, &file_size)) {
|
||||
CloseHandle(handle);
|
||||
return fail("Failed to get size of {} for memory mapping", path.string());
|
||||
@ -151,13 +154,14 @@ auto FileOps::map_file(const Path &path, usize &size) -> Result<const u8 *> {
|
||||
return fail("Failed to get size of {} for memory mapping", path.string());
|
||||
}
|
||||
|
||||
auto h_map = CreateFileMappingW(handle, NULL, PAGE_READONLY, 0, 0, NULL);
|
||||
Mut<HANDLE> h_map =
|
||||
CreateFileMappingW(handle, NULL, PAGE_READONLY, 0, 0, NULL);
|
||||
if (h_map == NULL) {
|
||||
CloseHandle(handle);
|
||||
return fail("Failed to memory map {}", path.string());
|
||||
}
|
||||
|
||||
const auto *result =
|
||||
Const<u8> *result =
|
||||
static_cast<const u8 *>(MapViewOfFile(h_map, FILE_MAP_READ, 0, 0, 0));
|
||||
if (result == NULL) {
|
||||
CloseHandle(handle);
|
||||
@ -169,11 +173,11 @@ auto FileOps::map_file(const Path &path, usize &size) -> Result<const u8 *> {
|
||||
return result;
|
||||
|
||||
#elif IA_PLATFORM_UNIX
|
||||
const auto handle = open(path.string().c_str(), O_RDONLY);
|
||||
Const<int> handle = open(path.string().c_str(), O_RDONLY);
|
||||
if (handle == -1) {
|
||||
return fail("Failed to open {} for memory mapping", path.string());
|
||||
}
|
||||
struct stat sb;
|
||||
Mut<struct stat> sb;
|
||||
if (fstat(handle, &sb) == -1) {
|
||||
close(handle);
|
||||
return fail("Failed to get stats of {} for memory mapping", path.string());
|
||||
@ -183,12 +187,12 @@ auto FileOps::map_file(const Path &path, usize &size) -> Result<const u8 *> {
|
||||
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);
|
||||
Mut<void *> addr = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, handle, 0);
|
||||
if (addr == MAP_FAILED) {
|
||||
close(handle);
|
||||
return fail("Failed to memory map {}", path.string());
|
||||
}
|
||||
const auto *result = static_cast<const u8 *>(addr);
|
||||
Const<Const<u8> *> result = static_cast<const u8 *>(addr);
|
||||
madvise(addr, size, MADV_SEQUENTIAL);
|
||||
s_mapped_files[result] =
|
||||
std::make_tuple((void *)((u64)handle), (void *)addr, (void *)size);
|
||||
@ -196,7 +200,7 @@ auto FileOps::map_file(const Path &path, usize &size) -> Result<const u8 *> {
|
||||
#endif
|
||||
}
|
||||
|
||||
auto FileOps::stream_to_file(const Path &path, bool overwrite)
|
||||
auto FileOps::stream_to_file(Ref<Path> path, Const<bool> overwrite)
|
||||
-> Result<StreamWriter> {
|
||||
if (!overwrite && std::filesystem::exists(path)) {
|
||||
return fail("File already exists: {}", path.string());
|
||||
@ -204,21 +208,21 @@ auto FileOps::stream_to_file(const Path &path, bool overwrite)
|
||||
return StreamWriter::create_from_file(path);
|
||||
}
|
||||
|
||||
auto FileOps::stream_from_file(const Path &path) -> Result<StreamReader> {
|
||||
auto FileOps::stream_from_file(Ref<Path> path) -> Result<StreamReader> {
|
||||
if (!std::filesystem::exists(path)) {
|
||||
return fail("File does not exist: {}", path.string());
|
||||
}
|
||||
return StreamReader::create_from_file(path);
|
||||
}
|
||||
|
||||
auto FileOps::read_text_file(const Path &path) -> Result<String> {
|
||||
auto *f = fopen(path.string().c_str(), "r");
|
||||
auto FileOps::read_text_file(Ref<Path> path) -> Result<String> {
|
||||
Mut<FILE *> f = fopen(path.string().c_str(), "r");
|
||||
if (!f) {
|
||||
return fail("Failed to open file: {}", path.string());
|
||||
}
|
||||
String result;
|
||||
Mut<String> result;
|
||||
fseek(f, 0, SEEK_END);
|
||||
const long len = ftell(f);
|
||||
Const<long> len = ftell(f);
|
||||
if (len > 0) {
|
||||
result.resize(static_cast<usize>(len));
|
||||
fseek(f, 0, SEEK_SET);
|
||||
@ -228,14 +232,14 @@ auto FileOps::read_text_file(const Path &path) -> Result<String> {
|
||||
return result;
|
||||
}
|
||||
|
||||
auto FileOps::read_binary_file(const Path &path) -> Result<Vec<u8>> {
|
||||
auto *f = fopen(path.string().c_str(), "rb");
|
||||
auto FileOps::read_binary_file(Ref<Path> path) -> Result<Vec<u8>> {
|
||||
Mut<FILE *> f = fopen(path.string().c_str(), "rb");
|
||||
if (!f) {
|
||||
return fail("Failed to open file: {}", path.string());
|
||||
}
|
||||
Vec<u8> result;
|
||||
Mut<Vec<u8>> result;
|
||||
fseek(f, 0, SEEK_END);
|
||||
const long len = ftell(f);
|
||||
Const<long> len = ftell(f);
|
||||
if (len > 0) {
|
||||
result.resize(static_cast<usize>(len));
|
||||
fseek(f, 0, SEEK_SET);
|
||||
@ -245,38 +249,38 @@ auto FileOps::read_binary_file(const Path &path) -> Result<Vec<u8>> {
|
||||
return result;
|
||||
}
|
||||
|
||||
auto FileOps::write_text_file(const Path &path, const String &contents,
|
||||
bool overwrite) -> Result<usize> {
|
||||
const char *mode = overwrite ? "w" : "wx";
|
||||
auto *f = fopen(path.string().c_str(), mode);
|
||||
auto FileOps::write_text_file(Ref<Path> path, Ref<String> contents,
|
||||
Const<bool> overwrite) -> Result<usize> {
|
||||
Const<Const<char> *> mode = overwrite ? "w" : "wx";
|
||||
Mut<FILE *> f = fopen(path.string().c_str(), mode);
|
||||
if (!f) {
|
||||
if (!overwrite && errno == EEXIST) {
|
||||
return fail("File already exists: {}", path.string());
|
||||
}
|
||||
return fail("Failed to write to file: {}", path.string());
|
||||
}
|
||||
const auto result = fwrite(contents.data(), 1, contents.size(), f);
|
||||
Const<usize> result = fwrite(contents.data(), 1, contents.size(), f);
|
||||
fclose(f);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto FileOps::write_binary_file(const Path &path, Span<const u8> contents,
|
||||
bool overwrite) -> Result<usize> {
|
||||
const char *mode = overwrite ? "w" : "wx";
|
||||
auto *f = fopen(path.string().c_str(), mode);
|
||||
auto FileOps::write_binary_file(Ref<Path> path, Const<Span<Const<u8>>> contents,
|
||||
Const<bool> overwrite) -> Result<usize> {
|
||||
Const<Const<char> *> mode = overwrite ? "w" : "wx";
|
||||
Mut<FILE *> f = fopen(path.string().c_str(), mode);
|
||||
if (!f) {
|
||||
if (!overwrite && errno == EEXIST) {
|
||||
return fail("File already exists: {}", path.string());
|
||||
}
|
||||
return fail("Failed to write to file: {}", path.string());
|
||||
}
|
||||
const auto result = fwrite(contents.data(), 1, contents.size(), f);
|
||||
Const<usize> result = fwrite(contents.data(), 1, contents.size(), f);
|
||||
fclose(f);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto FileOps::normalize_executable_path(const Path &path) -> Path {
|
||||
Path result = path;
|
||||
auto FileOps::normalize_executable_path(Ref<Path> path) -> Path {
|
||||
Mut<Path> result = path;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
if (!result.has_extension()) {
|
||||
@ -288,7 +292,7 @@ auto FileOps::normalize_executable_path(const Path &path) -> Path {
|
||||
}
|
||||
|
||||
if (result.is_relative()) {
|
||||
String path_str = result.string();
|
||||
Mut<String> path_str = result.string();
|
||||
if (!path_str.starts_with("./") && !path_str.starts_with("../")) {
|
||||
result = "./" + path_str;
|
||||
}
|
||||
@ -297,14 +301,14 @@ auto FileOps::normalize_executable_path(const Path &path) -> Path {
|
||||
return result;
|
||||
}
|
||||
|
||||
auto FileOps::native_open_file(const Path &path, FileAccess access,
|
||||
FileMode mode, u32 permissions)
|
||||
auto FileOps::native_open_file(Ref<Path> path, Const<FileAccess> access,
|
||||
Const<FileMode> mode, Const<u32> permissions)
|
||||
-> Result<NativeFileHandle> {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
DWORD dw_access = 0;
|
||||
DWORD dw_share = FILE_SHARE_READ;
|
||||
DWORD dw_disposition = 0;
|
||||
DWORD dw_flags_and_attributes = FILE_ATTRIBUTE_NORMAL;
|
||||
Mut<DWORD> dw_access = 0;
|
||||
Mut<DWORD> dw_share = FILE_SHARE_READ;
|
||||
Mut<DWORD> dw_disposition = 0;
|
||||
Mut<DWORD> dw_flags_and_attributes = FILE_ATTRIBUTE_NORMAL;
|
||||
|
||||
switch (access) {
|
||||
case FileAccess::Read:
|
||||
@ -336,7 +340,8 @@ auto FileOps::native_open_file(const Path &path, FileAccess access,
|
||||
break;
|
||||
}
|
||||
|
||||
HANDLE h_file = CreateFileA(path.string().c_str(), dw_access, dw_share, NULL,
|
||||
Mut<HANDLE> h_file =
|
||||
CreateFileA(path.string().c_str(), dw_access, dw_share, NULL,
|
||||
dw_disposition, dw_flags_and_attributes, NULL);
|
||||
|
||||
if (h_file == INVALID_HANDLE_VALUE) {
|
||||
@ -346,7 +351,7 @@ auto FileOps::native_open_file(const Path &path, FileAccess access,
|
||||
return h_file;
|
||||
|
||||
#elif IA_PLATFORM_UNIX
|
||||
int flags = 0;
|
||||
Mut<int> flags = 0;
|
||||
|
||||
switch (access) {
|
||||
case FileAccess::Read:
|
||||
@ -377,7 +382,7 @@ auto FileOps::native_open_file(const Path &path, FileAccess access,
|
||||
break;
|
||||
}
|
||||
|
||||
int fd = open(path.string().c_str(), flags, permissions);
|
||||
Mut<int> fd = open(path.string().c_str(), flags, permissions);
|
||||
|
||||
if (fd == -1) {
|
||||
return fail("Failed to open file '{}': {}", path.string(), errno);
|
||||
@ -387,7 +392,7 @@ auto FileOps::native_open_file(const Path &path, FileAccess access,
|
||||
#endif
|
||||
}
|
||||
|
||||
auto FileOps::native_close_file(NativeFileHandle handle) -> void {
|
||||
auto FileOps::native_close_file(Const<NativeFileHandle> handle) -> void {
|
||||
if (handle == INVALID_FILE_HANDLE) {
|
||||
return;
|
||||
}
|
||||
@ -399,19 +404,16 @@ auto FileOps::native_close_file(NativeFileHandle handle) -> void {
|
||||
#endif
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// MemoryMappedRegion
|
||||
// =============================================================================
|
||||
|
||||
FileOps::MemoryMappedRegion::~MemoryMappedRegion() { unmap(); }
|
||||
|
||||
FileOps::MemoryMappedRegion::MemoryMappedRegion(
|
||||
MemoryMappedRegion &&other) noexcept {
|
||||
ForwardRef<MemoryMappedRegion> other) noexcept {
|
||||
*this = std::move(other);
|
||||
}
|
||||
|
||||
auto FileOps::MemoryMappedRegion::operator=(MemoryMappedRegion &&other) noexcept
|
||||
-> MemoryMappedRegion & {
|
||||
auto FileOps::MemoryMappedRegion::operator=(
|
||||
ForwardRef<MemoryMappedRegion> other) noexcept
|
||||
-> MutRef<MemoryMappedRegion> {
|
||||
if (this != &other) {
|
||||
unmap();
|
||||
m_ptr = other.m_ptr;
|
||||
@ -426,8 +428,9 @@ auto FileOps::MemoryMappedRegion::operator=(MemoryMappedRegion &&other) noexcept
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto FileOps::MemoryMappedRegion::map(NativeFileHandle handle, u64 offset,
|
||||
usize size) -> Result<void> {
|
||||
auto FileOps::MemoryMappedRegion::map(Const<NativeFileHandle> handle,
|
||||
Const<u64> offset, Const<usize> size)
|
||||
-> Result<void> {
|
||||
unmap();
|
||||
|
||||
if (handle == INVALID_FILE_HANDLE) {
|
||||
@ -439,14 +442,14 @@ auto FileOps::MemoryMappedRegion::map(NativeFileHandle handle, u64 offset,
|
||||
}
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
LARGE_INTEGER file_size;
|
||||
Mut<LARGE_INTEGER> file_size;
|
||||
if (!GetFileSizeEx(handle, &file_size)) {
|
||||
return fail("Failed to get file size");
|
||||
}
|
||||
|
||||
u64 end_offset = offset + size;
|
||||
Const<u64> end_offset = offset + size;
|
||||
if (static_cast<u64>(file_size.QuadPart) < end_offset) {
|
||||
LARGE_INTEGER new_size;
|
||||
Mut<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");
|
||||
@ -462,8 +465,8 @@ auto FileOps::MemoryMappedRegion::map(NativeFileHandle handle, u64 offset,
|
||||
return fail("CreateFileMapping failed: {}", GetLastError());
|
||||
}
|
||||
|
||||
DWORD offset_high = static_cast<DWORD>(offset >> 32);
|
||||
DWORD offset_low = static_cast<DWORD>(offset & 0xFFFFFFFF);
|
||||
Const<DWORD> offset_high = static_cast<DWORD>(offset >> 32);
|
||||
Const<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));
|
||||
@ -476,20 +479,20 @@ auto FileOps::MemoryMappedRegion::map(NativeFileHandle handle, u64 offset,
|
||||
m_size = size;
|
||||
|
||||
#elif IA_PLATFORM_UNIX
|
||||
struct stat sb;
|
||||
Mut<struct stat> sb;
|
||||
if (fstat(handle, &sb) == -1) {
|
||||
return fail("Failed to fstat file");
|
||||
}
|
||||
|
||||
u64 end_offset = offset + size;
|
||||
Const<u64> end_offset = offset + size;
|
||||
if (static_cast<u64>(sb.st_size) < end_offset) {
|
||||
if (ftruncate(handle, static_cast<off_t>(end_offset)) == -1) {
|
||||
return fail("Failed to ftruncate (extend) file");
|
||||
}
|
||||
}
|
||||
|
||||
void *ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, handle,
|
||||
static_cast<off_t>(offset));
|
||||
Mut<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);
|
||||
}
|
||||
|
||||
@ -17,23 +17,17 @@
|
||||
#include <IACore/Http/Client.hpp>
|
||||
|
||||
namespace IACore {
|
||||
// Helper struct to access protected constructor via make_box (std::make_unique)
|
||||
struct PublicHttpClient : public HttpClient {
|
||||
explicit PublicHttpClient(httplib::Client &&client)
|
||||
: HttpClient(std::move(client)) {}
|
||||
};
|
||||
|
||||
auto HttpClient::create(const String &host) -> Result<Box<HttpClient>> {
|
||||
return make_box<PublicHttpClient>(httplib::Client(host));
|
||||
auto HttpClient::create(Ref<String> host) -> Result<Box<HttpClient>> {
|
||||
return make_box_protected<HttpClient>(httplib::Client(host));
|
||||
}
|
||||
|
||||
static auto build_headers(Span<const HttpClient::Header> headers,
|
||||
const char *default_content_type)
|
||||
static auto build_headers(Span<Const<HttpClient::Header>> headers,
|
||||
Const<Const<char> *> default_content_type)
|
||||
-> httplib::Headers {
|
||||
httplib::Headers out;
|
||||
bool has_content_type = false;
|
||||
Mut<httplib::Headers> out;
|
||||
Mut<bool> has_content_type = false;
|
||||
|
||||
for (const auto &h : headers) {
|
||||
for (Ref<HttpClient::Header> h : headers) {
|
||||
out.emplace(h.first, h.second);
|
||||
|
||||
if (h.first == HttpClient::header_type_to_string(
|
||||
@ -48,7 +42,7 @@ static auto build_headers(Span<const HttpClient::Header> headers,
|
||||
return out;
|
||||
}
|
||||
|
||||
HttpClient::HttpClient(httplib::Client &&client)
|
||||
HttpClient::HttpClient(ForwardRef<httplib::Client> client)
|
||||
: m_client(std::move(client)),
|
||||
m_last_response_code(EResponseCode::INTERNAL_SERVER_ERROR) {
|
||||
m_client.enable_server_certificate_verification(true);
|
||||
@ -56,34 +50,35 @@ HttpClient::HttpClient(httplib::Client &&client)
|
||||
|
||||
HttpClient::~HttpClient() = default;
|
||||
|
||||
void HttpClient::enable_certificate_verification() {
|
||||
auto HttpClient::enable_certificate_verification() -> void {
|
||||
m_client.enable_server_certificate_verification(true);
|
||||
}
|
||||
|
||||
void HttpClient::disable_certificate_verification() {
|
||||
auto HttpClient::disable_certificate_verification() -> void {
|
||||
m_client.enable_server_certificate_verification(false);
|
||||
}
|
||||
|
||||
auto HttpClient::preprocess_response(const String &response) -> String {
|
||||
const auto response_bytes = Span<const u8>{
|
||||
reinterpret_cast<const u8 *>(response.data()), response.size()};
|
||||
const auto compression = DataOps::detect_compression(response_bytes);
|
||||
auto HttpClient::preprocess_response(Ref<String> response) -> String {
|
||||
Const<Span<Const<u8>>> response_bytes = {
|
||||
reinterpret_cast<Const<u8> *>(response.data()), response.size()};
|
||||
Const<DataOps::CompressionType> compression =
|
||||
DataOps::detect_compression(response_bytes);
|
||||
|
||||
switch (compression) {
|
||||
case DataOps::CompressionType::Gzip: {
|
||||
const auto data = DataOps::gzip_inflate(response_bytes);
|
||||
Const<Result<Vec<u8>>> data = DataOps::gzip_inflate(response_bytes);
|
||||
if (!data) {
|
||||
return response;
|
||||
}
|
||||
return String(reinterpret_cast<const char *>(data->data()), data->size());
|
||||
return String(reinterpret_cast<Const<char> *>(data->data()), data->size());
|
||||
}
|
||||
|
||||
case DataOps::CompressionType::Zlib: {
|
||||
const auto data = DataOps::zlib_inflate(response_bytes);
|
||||
Const<Result<Vec<u8>>> data = DataOps::zlib_inflate(response_bytes);
|
||||
if (!data) {
|
||||
return response;
|
||||
}
|
||||
return String(reinterpret_cast<const char *>(data->data()), data->size());
|
||||
return String(reinterpret_cast<Const<char> *>(data->data()), data->size());
|
||||
}
|
||||
|
||||
case DataOps::CompressionType::None:
|
||||
@ -93,16 +88,19 @@ auto HttpClient::preprocess_response(const String &response) -> String {
|
||||
return response;
|
||||
}
|
||||
|
||||
auto HttpClient::raw_get(const String &path, Span<const Header> headers,
|
||||
const char *default_content_type) -> Result<String> {
|
||||
auto http_headers = build_headers(headers, default_content_type);
|
||||
auto HttpClient::raw_get(Ref<String> path, Span<Const<Header>> headers,
|
||||
Const<Const<char> *> default_content_type)
|
||||
-> Result<String> {
|
||||
Const<httplib::Headers> http_headers =
|
||||
build_headers(headers, default_content_type);
|
||||
|
||||
String adjusted_path = path;
|
||||
Mut<String> adjusted_path = path;
|
||||
if (!path.empty() && path[0] != '/') {
|
||||
adjusted_path = "/" + path;
|
||||
}
|
||||
|
||||
auto res = m_client.Get(adjusted_path.c_str(), http_headers);
|
||||
Const<httplib::Result> res =
|
||||
m_client.Get(adjusted_path.c_str(), http_headers);
|
||||
|
||||
if (res) {
|
||||
m_last_response_code = static_cast<EResponseCode>(res->status);
|
||||
@ -115,27 +113,29 @@ auto HttpClient::raw_get(const String &path, Span<const Header> headers,
|
||||
return fail("Network Error: {}", httplib::to_string(res.error()));
|
||||
}
|
||||
|
||||
auto HttpClient::raw_post(const String &path, Span<const Header> headers,
|
||||
const String &body, const char *default_content_type)
|
||||
auto HttpClient::raw_post(Ref<String> path, Span<Const<Header>> headers,
|
||||
Ref<String> body,
|
||||
Const<Const<char> *> default_content_type)
|
||||
-> Result<String> {
|
||||
auto http_headers = build_headers(headers, default_content_type);
|
||||
Mut<httplib::Headers> http_headers =
|
||||
build_headers(headers, default_content_type);
|
||||
|
||||
String content_type = default_content_type;
|
||||
Mut<String> content_type = default_content_type;
|
||||
if (http_headers.count("Content-Type")) {
|
||||
const auto t = http_headers.find("Content-Type");
|
||||
Const<httplib::Headers::iterator> t = http_headers.find("Content-Type");
|
||||
content_type = t->second;
|
||||
http_headers.erase(t);
|
||||
}
|
||||
|
||||
m_client.set_keep_alive(true);
|
||||
|
||||
String adjusted_path = path;
|
||||
Mut<String> adjusted_path = path;
|
||||
if (!path.empty() && path[0] != '/') {
|
||||
adjusted_path = "/" + path;
|
||||
}
|
||||
|
||||
auto res = m_client.Post(adjusted_path.c_str(), http_headers, body,
|
||||
content_type.c_str());
|
||||
Const<httplib::Result> res = m_client.Post(
|
||||
adjusted_path.c_str(), http_headers, body, content_type.c_str());
|
||||
|
||||
if (res) {
|
||||
m_last_response_code = static_cast<EResponseCode>(res->status);
|
||||
|
||||
@ -16,12 +16,12 @@
|
||||
#include <IACore/Http/Common.hpp>
|
||||
|
||||
namespace IACore {
|
||||
auto HttpCommon::url_encode(const String &value) -> String {
|
||||
std::stringstream escaped;
|
||||
auto HttpCommon::url_encode(Ref<String> value) -> String {
|
||||
Mut<std::stringstream> escaped;
|
||||
escaped.fill('0');
|
||||
escaped << std::hex << std::uppercase;
|
||||
|
||||
for (char c : value) {
|
||||
for (Const<char> c : value) {
|
||||
if (std::isalnum(static_cast<unsigned char>(c)) || c == '-' || c == '_' ||
|
||||
c == '.' || c == '~')
|
||||
escaped << c;
|
||||
@ -33,14 +33,14 @@ auto HttpCommon::url_encode(const String &value) -> String {
|
||||
return escaped.str();
|
||||
}
|
||||
|
||||
auto HttpCommon::url_decode(const String &value) -> String {
|
||||
String result;
|
||||
auto HttpCommon::url_decode(Ref<String> value) -> String {
|
||||
Mut<String> result;
|
||||
result.reserve(value.length());
|
||||
|
||||
for (size_t i = 0; i < value.length(); ++i) {
|
||||
for (Mut<size_t> i = 0; i < value.length(); ++i) {
|
||||
if (value[i] == '%' && i + 2 < value.length()) {
|
||||
std::string hex_str = value.substr(i + 1, 2);
|
||||
char decoded_char =
|
||||
Const<std::string> hex_str = value.substr(i + 1, 2);
|
||||
Const<char> decoded_char =
|
||||
static_cast<char>(std::strtol(hex_str.c_str(), nullptr, 16));
|
||||
result += decoded_char;
|
||||
i += 2;
|
||||
@ -53,7 +53,7 @@ auto HttpCommon::url_decode(const String &value) -> String {
|
||||
return result;
|
||||
}
|
||||
|
||||
auto HttpCommon::header_type_to_string(EHeaderType type) -> String {
|
||||
auto HttpCommon::header_type_to_string(Const<EHeaderType> type) -> String {
|
||||
switch (type) {
|
||||
case EHeaderType::ACCEPT:
|
||||
return "Accept";
|
||||
@ -111,7 +111,7 @@ auto HttpCommon::header_type_to_string(EHeaderType type) -> String {
|
||||
return "<Unknown>";
|
||||
}
|
||||
|
||||
auto HttpCommon::is_success_response_code(EResponseCode code) -> bool {
|
||||
auto HttpCommon::is_success_response_code(Const<EResponseCode> code) -> bool {
|
||||
return (i32)code >= 200 && (i32)code < 300;
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -16,65 +16,56 @@
|
||||
#include <IACore/Http/Server.hpp>
|
||||
|
||||
namespace IACore {
|
||||
// =============================================================================
|
||||
// Request Implementation
|
||||
// =============================================================================
|
||||
|
||||
auto HttpServer::Request::get_header(const String &key) const -> String {
|
||||
if (auto it = headers.find(key); it != headers.end()) {
|
||||
auto HttpServer::Request::get_header(Ref<String> key) const -> String {
|
||||
if (Const<HashMap<String, String>::const_iterator> it = headers.find(key);
|
||||
it != headers.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
auto HttpServer::Request::get_param(const String &key) const -> String {
|
||||
if (auto it = params.find(key); it != params.end()) {
|
||||
auto HttpServer::Request::get_param(Ref<String> key) const -> String {
|
||||
if (Const<HashMap<String, String>::const_iterator> it = params.find(key);
|
||||
it != params.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
auto HttpServer::Request::get_path_param(const String &key) const -> String {
|
||||
if (auto it = path_params.find(key); it != path_params.end()) {
|
||||
auto HttpServer::Request::get_path_param(Ref<String> key) const -> String {
|
||||
if (Const<HashMap<String, String>::const_iterator> it = path_params.find(key);
|
||||
it != path_params.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
auto HttpServer::Request::has_header(const String &key) const -> bool {
|
||||
auto HttpServer::Request::has_header(Ref<String> key) const -> bool {
|
||||
return headers.contains(key);
|
||||
}
|
||||
|
||||
auto HttpServer::Request::has_param(const String &key) const -> bool {
|
||||
auto HttpServer::Request::has_param(Ref<String> key) const -> bool {
|
||||
return params.contains(key);
|
||||
}
|
||||
|
||||
auto HttpServer::Request::has_path_param(const String &key) const -> bool {
|
||||
auto HttpServer::Request::has_path_param(Ref<String> key) const -> bool {
|
||||
return path_params.contains(key);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Response Implementation
|
||||
// =============================================================================
|
||||
|
||||
void HttpServer::Response::set_content(const String &content,
|
||||
const String &type) {
|
||||
void HttpServer::Response::set_content(Ref<String> content, Ref<String> type) {
|
||||
body = content;
|
||||
content_type = type;
|
||||
}
|
||||
|
||||
void HttpServer::Response::set_status(EResponseCode status_code) {
|
||||
void HttpServer::Response::set_status(Const<EResponseCode> status_code) {
|
||||
code = status_code;
|
||||
}
|
||||
|
||||
void HttpServer::Response::add_header(const String &key, const String &value) {
|
||||
void HttpServer::Response::add_header(Ref<String> key, Ref<String> value) {
|
||||
headers[key] = value;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// HttpServer Implementation
|
||||
// =============================================================================
|
||||
|
||||
struct PublicHttpServer : public HttpServer {
|
||||
PublicHttpServer() = default;
|
||||
};
|
||||
@ -87,7 +78,7 @@ auto HttpServer::create() -> Result<Box<HttpServer>> {
|
||||
return make_box<PublicHttpServer>();
|
||||
}
|
||||
|
||||
auto HttpServer::listen(const String &host, u32 port) -> Result<void> {
|
||||
auto HttpServer::listen(Ref<String> host, Const<u32> port) -> Result<void> {
|
||||
if (!m_server.listen(host.c_str(), static_cast<int>(port))) {
|
||||
return fail("Failed to start HTTP server on {}:{}", host, port);
|
||||
}
|
||||
@ -102,37 +93,34 @@ void HttpServer::stop() {
|
||||
|
||||
auto HttpServer::is_running() const -> bool { return m_server.is_running(); }
|
||||
|
||||
void HttpServer::register_handler(const String &method, const String &pattern,
|
||||
Handler handler) {
|
||||
auto wrapper = [handler = std::move(handler)](const httplib::Request &req,
|
||||
httplib::Response &res) {
|
||||
Request ia_req;
|
||||
void HttpServer::register_handler(Ref<String> method, Ref<String> pattern,
|
||||
Const<Handler> handler) {
|
||||
Const<httplib::Server::Handler> wrapper =
|
||||
[handler](Ref<httplib::Request> req, MutRef<httplib::Response> res) {
|
||||
Mut<Request> ia_req;
|
||||
ia_req.path = req.path;
|
||||
ia_req.method = req.method;
|
||||
ia_req.body = req.body;
|
||||
|
||||
// Convert headers
|
||||
for (const auto &item : req.headers) {
|
||||
for (Ref<Pair<const String, String>> item : req.headers) {
|
||||
ia_req.headers[item.first] = item.second;
|
||||
}
|
||||
|
||||
// Convert params
|
||||
for (const auto &item : req.params) {
|
||||
for (Ref<Pair<const String, String>> item : req.params) {
|
||||
ia_req.params[item.first] = item.second;
|
||||
}
|
||||
|
||||
// Convert path params
|
||||
for (const auto &item : req.path_params) {
|
||||
for (Ref<Pair<const String, String>> item : req.path_params) {
|
||||
ia_req.path_params[item.first] = item.second;
|
||||
}
|
||||
|
||||
Response ia_res;
|
||||
Mut<Response> ia_res;
|
||||
handler(ia_req, ia_res);
|
||||
|
||||
res.status = static_cast<int>(ia_res.code);
|
||||
res.set_content(ia_res.body, ia_res.content_type.c_str());
|
||||
|
||||
for (const auto &item : ia_res.headers) {
|
||||
for (Ref<Pair<String, String>> item : ia_res.headers) {
|
||||
res.set_header(item.first.c_str(), item.second.c_str());
|
||||
}
|
||||
};
|
||||
@ -150,24 +138,24 @@ void HttpServer::register_handler(const String &method, const String &pattern,
|
||||
}
|
||||
}
|
||||
|
||||
void HttpServer::get(const String &pattern, Handler handler) {
|
||||
register_handler("GET", pattern, std::move(handler));
|
||||
void HttpServer::get(Ref<String> pattern, Const<Handler> handler) {
|
||||
register_handler("GET", pattern, handler);
|
||||
}
|
||||
|
||||
void HttpServer::post(const String &pattern, Handler handler) {
|
||||
register_handler("POST", pattern, std::move(handler));
|
||||
void HttpServer::post(Ref<String> pattern, Const<Handler> handler) {
|
||||
register_handler("POST", pattern, handler);
|
||||
}
|
||||
|
||||
void HttpServer::put(const String &pattern, Handler handler) {
|
||||
register_handler("PUT", pattern, std::move(handler));
|
||||
void HttpServer::put(Ref<String> pattern, Const<Handler> handler) {
|
||||
register_handler("PUT", pattern, handler);
|
||||
}
|
||||
|
||||
void HttpServer::del(const String &pattern, Handler handler) {
|
||||
register_handler("DELETE", pattern, std::move(handler));
|
||||
void HttpServer::del(Ref<String> pattern, Const<Handler> handler) {
|
||||
register_handler("DELETE", pattern, handler);
|
||||
}
|
||||
|
||||
void HttpServer::options(const String &pattern, Handler handler) {
|
||||
register_handler("OPTIONS", pattern, std::move(handler));
|
||||
void HttpServer::options(Ref<String> pattern, Const<Handler> handler) {
|
||||
register_handler("OPTIONS", pattern, handler);
|
||||
}
|
||||
|
||||
} // namespace IACore
|
||||
@ -16,22 +16,18 @@
|
||||
#include <IACore/IACore.hpp>
|
||||
#include <IACore/Logger.hpp>
|
||||
|
||||
#include <mimalloc.h>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <mimalloc.h>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
auto g_start_time = std::chrono::high_resolution_clock::time_point{};
|
||||
namespace IACore {
|
||||
Mut<std::chrono::high_resolution_clock::time_point> g_start_time = {};
|
||||
|
||||
static auto g_main_thread_id = std::thread::id{};
|
||||
static auto g_core_init_count = i32{0};
|
||||
static Mut<std::thread::id> g_main_thread_id = {};
|
||||
static Mut<i32> g_core_init_count = 0;
|
||||
|
||||
auto initialize() -> void
|
||||
{
|
||||
auto initialize() -> void {
|
||||
g_core_init_count++;
|
||||
if (g_core_init_count > 1)
|
||||
{
|
||||
if (g_core_init_count > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -41,26 +37,20 @@ namespace IACore
|
||||
Logger::initialize();
|
||||
|
||||
mi_option_set(mi_option_verbose, 0);
|
||||
}
|
||||
}
|
||||
|
||||
auto terminate() -> void
|
||||
{
|
||||
auto terminate() -> void {
|
||||
g_core_init_count--;
|
||||
if (g_core_init_count > 0)
|
||||
{
|
||||
if (g_core_init_count > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::terminate();
|
||||
}
|
||||
}
|
||||
|
||||
auto is_initialized() -> bool
|
||||
{
|
||||
return g_core_init_count > 0;
|
||||
}
|
||||
auto is_initialized() -> bool { return g_core_init_count > 0; }
|
||||
|
||||
auto is_main_thread() -> bool
|
||||
{
|
||||
auto is_main_thread() -> bool {
|
||||
return std::this_thread::get_id() == g_main_thread_id;
|
||||
}
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -21,27 +21,25 @@
|
||||
#include <fcntl.h>
|
||||
|
||||
namespace IACore {
|
||||
// =============================================================================
|
||||
// Internal: Connection Descriptor
|
||||
// =============================================================================
|
||||
struct IpcConnectionDescriptor {
|
||||
String socket_path;
|
||||
String shared_mem_path;
|
||||
u32 shared_mem_size;
|
||||
Mut<String> socket_path;
|
||||
Mut<String> shared_mem_path;
|
||||
Mut<u32> shared_mem_size;
|
||||
|
||||
[[nodiscard]] auto serialize() const -> String {
|
||||
return std::format("{}|{}|{}|", socket_path, shared_mem_path,
|
||||
shared_mem_size);
|
||||
}
|
||||
|
||||
static auto deserialize(StringView data) -> Option<IpcConnectionDescriptor> {
|
||||
static auto deserialize(Const<StringView> data)
|
||||
-> Option<IpcConnectionDescriptor> {
|
||||
enum class ParseState { SocketPath, SharedMemPath, SharedMemSize };
|
||||
|
||||
IpcConnectionDescriptor result{};
|
||||
usize t = 0;
|
||||
auto state = ParseState::SocketPath;
|
||||
Mut<IpcConnectionDescriptor> result{};
|
||||
Mut<usize> t = 0;
|
||||
Mut<ParseState> state = ParseState::SocketPath;
|
||||
|
||||
for (usize i = 0; i < data.size(); ++i) {
|
||||
for (Mut<usize> i = 0; i < data.size(); ++i) {
|
||||
if (data[i] != '|') {
|
||||
continue;
|
||||
}
|
||||
@ -58,8 +56,8 @@ struct IpcConnectionDescriptor {
|
||||
break;
|
||||
|
||||
case ParseState::SharedMemSize: {
|
||||
const auto *start = data.data() + t;
|
||||
const auto *end = data.data() + i;
|
||||
Const<const char *> start = data.data() + t;
|
||||
Const<const char *> end = data.data() + i;
|
||||
if (std::from_chars(start, end, result.shared_mem_size).ec !=
|
||||
std::errc{}) {
|
||||
return std::nullopt;
|
||||
@ -73,37 +71,33 @@ struct IpcConnectionDescriptor {
|
||||
}
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// IpcNode Implementation
|
||||
// =============================================================================
|
||||
|
||||
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);
|
||||
auto IpcNode::connect(Const<const char *> connection_string) -> Result<void> {
|
||||
Const<Option<IpcConnectionDescriptor>> desc_opt =
|
||||
IpcConnectionDescriptor::deserialize(connection_string);
|
||||
if (!desc_opt) {
|
||||
return fail("Failed to parse connection string");
|
||||
}
|
||||
const auto &desc = *desc_opt;
|
||||
Ref<IpcConnectionDescriptor> desc = *desc_opt;
|
||||
m_shm_name = desc.shared_mem_path;
|
||||
|
||||
IA_TRY(m_socket, SocketOps::create_unix_socket());
|
||||
IA_TRY_PURE(
|
||||
m_socket = OX_TRY(SocketOps::create_unix_socket());
|
||||
OX_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));
|
||||
Mut<u8 *> mapped_ptr = OX_TRY(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);
|
||||
Mut<IpcSharedMemoryLayout *> layout =
|
||||
reinterpret_cast<IpcSharedMemoryLayout *>(m_shared_memory);
|
||||
|
||||
if (layout->meta.magic != 0x49414950) // "IAIP"
|
||||
{
|
||||
if (layout->meta.magic != 0x49414950) {
|
||||
return fail("Invalid shared memory header signature");
|
||||
}
|
||||
|
||||
@ -111,23 +105,19 @@ auto IpcNode::connect(const char *connection_string) -> Result<void> {
|
||||
return fail("IPC version mismatch");
|
||||
}
|
||||
|
||||
u8 *moni_ptr = m_shared_memory + layout->moni_data_offset;
|
||||
u8 *mino_ptr = m_shared_memory + layout->mino_data_offset;
|
||||
Mut<u8 *> moni_ptr = m_shared_memory + layout->moni_data_offset;
|
||||
Mut<u8 *> mino_ptr = m_shared_memory + layout->mino_data_offset;
|
||||
|
||||
IA_TRY(m_moni,
|
||||
RingBufferView::create(
|
||||
m_moni = OX_TRY(RingBufferView::create(
|
||||
&layout->moni_control,
|
||||
Span<u8>(moni_ptr, static_cast<usize>(layout->moni_data_size)),
|
||||
false));
|
||||
Span<u8>(moni_ptr, static_cast<usize>(layout->moni_data_size)), false));
|
||||
|
||||
IA_TRY(m_mino,
|
||||
RingBufferView::create(
|
||||
m_mino = OX_TRY(RingBufferView::create(
|
||||
&layout->mino_control,
|
||||
Span<u8>(mino_ptr, static_cast<usize>(layout->mino_data_size)),
|
||||
false));
|
||||
Span<u8>(mino_ptr, static_cast<usize>(layout->mino_data_size)), false));
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
u_long mode = 1;
|
||||
Mut<u_long> mode = 1;
|
||||
ioctlsocket(m_socket, FIONBIO, &mode);
|
||||
#else
|
||||
fcntl(m_socket, F_SETFL, O_NONBLOCK);
|
||||
@ -143,55 +133,49 @@ void IpcNode::update() {
|
||||
return;
|
||||
}
|
||||
|
||||
IpcPacketHeader header;
|
||||
Mut<IpcPacketHeader> header;
|
||||
|
||||
// Process all available messages from Manager
|
||||
while (m_moni.pop(
|
||||
header, Span<u8>(m_receive_buffer.data(), m_receive_buffer.size()))) {
|
||||
on_packet(header.id, {m_receive_buffer.data(), header.payload_size});
|
||||
}
|
||||
|
||||
u8 signal = 0;
|
||||
const auto res = recv(m_socket, reinterpret_cast<char *>(&signal), 1, 0);
|
||||
Mut<u8> signal = 0;
|
||||
Const<isize> res = recv(m_socket, reinterpret_cast<char *>(&signal), 1, 0);
|
||||
if (res == 1) {
|
||||
on_signal(signal);
|
||||
} else if (res == 0 || (res < 0 && !SocketOps::is_would_block())) {
|
||||
SocketOps::close(m_socket);
|
||||
FileOps::unlink_shared_memory(m_shm_name);
|
||||
|
||||
// Manager disconnected, exit immediately
|
||||
std::exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
void IpcNode::send_signal(u8 signal) {
|
||||
void IpcNode::send_signal(Const<u8> signal) {
|
||||
if (m_socket != INVALID_SOCKET) {
|
||||
send(m_socket, reinterpret_cast<const char *>(&signal), sizeof(signal), 0);
|
||||
}
|
||||
}
|
||||
|
||||
auto IpcNode::send_packet(u16 packet_id, Span<const u8> payload)
|
||||
auto IpcNode::send_packet(Const<u16> packet_id, Const<Span<Const<u8>>> payload)
|
||||
-> Result<void> {
|
||||
if (!m_mino.is_valid())
|
||||
return fail("invalid MINO");
|
||||
return m_mino.push(packet_id, payload);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// IpcManager Implementation
|
||||
// =============================================================================
|
||||
|
||||
void IpcManager::NodeSession::send_signal(u8 signal) {
|
||||
void IpcManager::NodeSession::send_signal(Const<u8> signal) {
|
||||
if (data_socket != INVALID_SOCKET) {
|
||||
send(data_socket, reinterpret_cast<const char *>(&signal), sizeof(signal),
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
auto IpcManager::NodeSession::send_packet(u16 packet_id, Span<const u8> payload)
|
||||
auto IpcManager::NodeSession::send_packet(Const<u16> packet_id,
|
||||
Const<Span<Const<u8>>> payload)
|
||||
-> Result<void> {
|
||||
// Protect the RingBuffer write cursor from concurrent threads
|
||||
std::scoped_lock lock(send_mutex);
|
||||
Const<std::scoped_lock<std::mutex>> lock(send_mutex);
|
||||
if (!moni.is_valid())
|
||||
return fail("invalid MONI");
|
||||
return moni.push(packet_id, payload);
|
||||
@ -205,7 +189,7 @@ IpcManager::IpcManager() {
|
||||
}
|
||||
|
||||
IpcManager::~IpcManager() {
|
||||
for (auto &session : m_active_sessions) {
|
||||
for (MutRef<Box<NodeSession>> session : m_active_sessions) {
|
||||
ProcessOps::terminate_process(session->node_process);
|
||||
FileOps::unmap_file(session->mapped_ptr);
|
||||
FileOps::unlink_shared_memory(session->shared_mem_name);
|
||||
@ -213,7 +197,7 @@ IpcManager::~IpcManager() {
|
||||
}
|
||||
m_active_sessions.clear();
|
||||
|
||||
for (auto &session : m_pending_sessions) {
|
||||
for (MutRef<Box<NodeSession>> session : m_pending_sessions) {
|
||||
ProcessOps::terminate_process(session->node_process);
|
||||
FileOps::unmap_file(session->mapped_ptr);
|
||||
FileOps::unlink_shared_memory(session->shared_mem_name);
|
||||
@ -223,11 +207,13 @@ IpcManager::~IpcManager() {
|
||||
}
|
||||
|
||||
void IpcManager::update() {
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
Const<std::chrono::system_clock::time_point> now =
|
||||
std::chrono::system_clock::now();
|
||||
|
||||
for (isize i = static_cast<isize>(m_pending_sessions.size()) - 1; i >= 0;
|
||||
for (Mut<isize> i = static_cast<isize>(m_pending_sessions.size()) - 1; i >= 0;
|
||||
--i) {
|
||||
auto &session = m_pending_sessions[static_cast<usize>(i)];
|
||||
MutRef<Box<NodeSession>> session =
|
||||
m_pending_sessions[static_cast<usize>(i)];
|
||||
|
||||
if (now - session->creation_time > std::chrono::seconds(5)) {
|
||||
ProcessOps::terminate_process(session->node_process);
|
||||
@ -240,15 +226,18 @@ void IpcManager::update() {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto new_sock = accept(session->listener_socket, nullptr, nullptr);
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
Mut<u64> new_sock = accept(session->listener_socket, nullptr, nullptr);
|
||||
#else
|
||||
Mut<i32> new_sock = accept(session->listener_socket, nullptr, nullptr);
|
||||
#endif
|
||||
|
||||
if (new_sock != INVALID_SOCKET) {
|
||||
session->data_socket = new_sock;
|
||||
session->is_ready = true;
|
||||
|
||||
// Set Data Socket to Non-Blocking
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
u_long mode = 1;
|
||||
Mut<u_long> mode = 1;
|
||||
ioctlsocket(session->data_socket, FIONBIO, &mode);
|
||||
#else
|
||||
fcntl(session->data_socket, F_SETFL, O_NONBLOCK);
|
||||
@ -257,21 +246,21 @@ void IpcManager::update() {
|
||||
SocketOps::close(session->listener_socket);
|
||||
session->listener_socket = INVALID_SOCKET;
|
||||
|
||||
const auto session_id = session->node_process->id.load();
|
||||
auto *session_ptr = session.get();
|
||||
Const<NativeProcessID> session_id = session->node_process->id.load();
|
||||
Mut<NodeSession *> session_ptr = session.get();
|
||||
m_active_sessions.push_back(std::move(session));
|
||||
m_pending_sessions.erase(m_pending_sessions.begin() + i);
|
||||
m_active_session_map[session_id] = session_ptr;
|
||||
}
|
||||
}
|
||||
|
||||
for (isize i = static_cast<isize>(m_active_sessions.size()) - 1; i >= 0;
|
||||
for (Mut<isize> i = static_cast<isize>(m_active_sessions.size()) - 1; i >= 0;
|
||||
--i) {
|
||||
auto &node = m_active_sessions[static_cast<usize>(i)];
|
||||
MutRef<Box<NodeSession>> node = m_active_sessions[static_cast<usize>(i)];
|
||||
|
||||
auto node_id = node->node_process->id.load();
|
||||
Mut<NativeProcessID> node_id = node->node_process->id.load();
|
||||
|
||||
IpcPacketHeader header;
|
||||
Mut<IpcPacketHeader> header;
|
||||
|
||||
while (node->mino.pop(
|
||||
header, Span<u8>(m_receive_buffer.data(), m_receive_buffer.size()))) {
|
||||
@ -279,8 +268,8 @@ void IpcManager::update() {
|
||||
{m_receive_buffer.data(), header.payload_size});
|
||||
}
|
||||
|
||||
u8 signal = 0;
|
||||
const auto res =
|
||||
Mut<u8> signal = 0;
|
||||
Const<isize> res =
|
||||
recv(node->data_socket, reinterpret_cast<char *>(&signal), 1, 0);
|
||||
|
||||
if (res == 1) {
|
||||
@ -298,48 +287,50 @@ void IpcManager::update() {
|
||||
}
|
||||
}
|
||||
|
||||
auto IpcManager::spawn_node(const Path &executable_path, u32 shared_memory_size)
|
||||
auto IpcManager::spawn_node(Ref<Path> executable_path,
|
||||
Const<u32> shared_memory_size)
|
||||
-> Result<NativeProcessID> {
|
||||
auto session = make_box<NodeSession>();
|
||||
Mut<Box<NodeSession>> session = make_box<NodeSession>();
|
||||
|
||||
static std::atomic<u32> s_id_gen{0};
|
||||
const u32 sid = ++s_id_gen;
|
||||
static Mut<std::atomic<u32>> s_id_gen{0};
|
||||
Const<u32> sid = ++s_id_gen;
|
||||
|
||||
String sock_path;
|
||||
Mut<String> sock_path;
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
char temp_path[MAX_PATH];
|
||||
Mut<char[MAX_PATH]> temp_path;
|
||||
GetTempPathA(MAX_PATH, temp_path);
|
||||
sock_path = std::format("{}\\ia_sess_{}.sock", temp_path, sid);
|
||||
#else
|
||||
sock_path = std::format("/tmp/ia_sess_{}.sock", sid);
|
||||
#endif
|
||||
|
||||
IA_TRY(session->listener_socket, SocketOps::create_unix_socket());
|
||||
IA_TRY_PURE(
|
||||
session->listener_socket = OX_TRY(SocketOps::create_unix_socket());
|
||||
OX_TRY_PURE(
|
||||
SocketOps::bind_unix_socket(session->listener_socket, sock_path.c_str()));
|
||||
IA_TRY_PURE(SocketOps::listen(session->listener_socket, 1));
|
||||
OX_TRY_PURE(SocketOps::listen(session->listener_socket, 1));
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
u_long mode = 1;
|
||||
Mut<u_long> mode = 1;
|
||||
ioctlsocket(session->listener_socket, FIONBIO, &mode);
|
||||
#else
|
||||
fcntl(session->listener_socket, F_SETFL, O_NONBLOCK);
|
||||
#endif
|
||||
|
||||
const String shm_name = std::format("ia_shm_{}", sid);
|
||||
IA_TRY(session->mapped_ptr,
|
||||
FileOps::map_shared_memory(shm_name, shared_memory_size, true));
|
||||
Const<String> shm_name = std::format("ia_shm_{}", sid);
|
||||
session->mapped_ptr =
|
||||
OX_TRY(FileOps::map_shared_memory(shm_name, shared_memory_size, true));
|
||||
|
||||
auto *layout = reinterpret_cast<IpcSharedMemoryLayout *>(session->mapped_ptr);
|
||||
Mut<IpcSharedMemoryLayout *> layout =
|
||||
reinterpret_cast<IpcSharedMemoryLayout *>(session->mapped_ptr);
|
||||
|
||||
layout->meta.magic = 0x49414950;
|
||||
layout->meta.version = 1;
|
||||
layout->meta.total_size = shared_memory_size;
|
||||
|
||||
const u64 header_size = IpcSharedMemoryLayout::get_header_size();
|
||||
const u64 usable_bytes = shared_memory_size - header_size;
|
||||
Const<u64> header_size = IpcSharedMemoryLayout::get_header_size();
|
||||
Const<u64> usable_bytes = shared_memory_size - header_size;
|
||||
|
||||
u64 half_size = (usable_bytes / 2);
|
||||
Mut<u64> half_size = (usable_bytes / 2);
|
||||
half_size -= (half_size % 64);
|
||||
|
||||
layout->moni_data_offset = header_size;
|
||||
@ -348,59 +339,54 @@ auto IpcManager::spawn_node(const Path &executable_path, u32 shared_memory_size)
|
||||
layout->mino_data_offset = header_size + half_size;
|
||||
layout->mino_data_size = half_size;
|
||||
|
||||
IA_TRY(session->moni,
|
||||
RingBufferView::create(
|
||||
session->moni = OX_TRY(RingBufferView::create(
|
||||
&layout->moni_control,
|
||||
Span<u8>(session->mapped_ptr + layout->moni_data_offset,
|
||||
static_cast<usize>(layout->moni_data_size)),
|
||||
true));
|
||||
|
||||
IA_TRY(session->mino,
|
||||
RingBufferView::create(
|
||||
session->mino = OX_TRY(RingBufferView::create(
|
||||
&layout->mino_control,
|
||||
Span<u8>(session->mapped_ptr + layout->mino_data_offset,
|
||||
static_cast<usize>(layout->mino_data_size)),
|
||||
true));
|
||||
|
||||
IpcConnectionDescriptor desc;
|
||||
Mut<IpcConnectionDescriptor> desc;
|
||||
desc.socket_path = sock_path;
|
||||
desc.shared_mem_path = shm_name;
|
||||
desc.shared_mem_size = shared_memory_size;
|
||||
|
||||
const String args = std::format("\"{}\"", desc.serialize());
|
||||
Const<String> args = std::format("\"{}\"", desc.serialize());
|
||||
|
||||
IA_TRY(session->node_process,
|
||||
ProcessOps::spawn_process_async(
|
||||
session->node_process = OX_TRY(ProcessOps::spawn_process_async(
|
||||
FileOps::normalize_executable_path(executable_path).string(), args,
|
||||
[sid](StringView line) {
|
||||
if (env::is_debug) {
|
||||
[sid](Const<StringView> line) {
|
||||
if (Env::IS_DEBUG) {
|
||||
std::cout << std::format("{}[Node:{}:STDOUT|STDERR]: {}{}\n",
|
||||
console::MAGENTA, sid, line,
|
||||
console::RESET);
|
||||
console::MAGENTA, sid, line, console::RESET);
|
||||
}
|
||||
},
|
||||
[sid](Result<i32> result) {
|
||||
if (env::is_debug) {
|
||||
[sid](Const<Result<i32>> result) {
|
||||
if (Env::IS_DEBUG) {
|
||||
if (!result) {
|
||||
std::cout << std::format(
|
||||
"{}[Node: {}]: Failed to spawn with error '{}'{}\n",
|
||||
console::RED, sid, result.error(), console::RESET);
|
||||
} else {
|
||||
std::cout << std::format(
|
||||
"{}[Node: {}]: Exited with code {}{}\n", console::RED,
|
||||
sid, *result, console::RESET);
|
||||
std::cout << std::format("{}[Node: {}]: Exited with code {}{}\n",
|
||||
console::RED, sid, *result,
|
||||
console::RESET);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Give some time for child node to stablize
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
if (!session->node_process->is_active()) {
|
||||
return fail("Failed to spawn the child process \"{}\"",
|
||||
executable_path.string());
|
||||
}
|
||||
|
||||
const auto process_id = session->node_process->id.load();
|
||||
Const<NativeProcessID> process_id = session->node_process->id.load();
|
||||
|
||||
session->shared_mem_name = shm_name;
|
||||
session->creation_time = std::chrono::system_clock::now();
|
||||
@ -409,11 +395,12 @@ auto IpcManager::spawn_node(const Path &executable_path, u32 shared_memory_size)
|
||||
return process_id;
|
||||
}
|
||||
|
||||
auto IpcManager::wait_till_node_is_online(NativeProcessID node_id) -> bool {
|
||||
bool is_pending = true;
|
||||
auto IpcManager::wait_till_node_is_online(Const<NativeProcessID> node_id)
|
||||
-> bool {
|
||||
Mut<bool> is_pending = true;
|
||||
while (is_pending) {
|
||||
is_pending = false;
|
||||
for (const auto &session : m_pending_sessions) {
|
||||
for (Const<Box<NodeSession>> &session : m_pending_sessions) {
|
||||
if (session->node_process->id.load() == node_id) {
|
||||
is_pending = true;
|
||||
break;
|
||||
@ -425,13 +412,14 @@ auto IpcManager::wait_till_node_is_online(NativeProcessID node_id) -> bool {
|
||||
return m_active_session_map.contains(node_id);
|
||||
}
|
||||
|
||||
void IpcManager::shutdown_node(NativeProcessID node_id) {
|
||||
const auto it_node = m_active_session_map.find(node_id);
|
||||
void IpcManager::shutdown_node(Const<NativeProcessID> node_id) {
|
||||
Const<HashMap<NativeProcessID, NodeSession *>::iterator> it_node =
|
||||
m_active_session_map.find(node_id);
|
||||
if (it_node == m_active_session_map.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto *node = it_node->second;
|
||||
Mut<NodeSession *> node = it_node->second;
|
||||
|
||||
ProcessOps::terminate_process(node->node_process);
|
||||
FileOps::unmap_file(node->mapped_ptr);
|
||||
@ -439,21 +427,23 @@ void IpcManager::shutdown_node(NativeProcessID node_id) {
|
||||
SocketOps::close(node->data_socket);
|
||||
|
||||
std::erase_if(m_active_sessions,
|
||||
[&](const auto &s) { return s.get() == node; });
|
||||
[&](Ref<Box<NodeSession>> 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);
|
||||
void IpcManager::send_signal(Const<NativeProcessID> node, Const<u8> signal) {
|
||||
Const<HashMap<NativeProcessID, NodeSession *>::iterator> it_node =
|
||||
m_active_session_map.find(node);
|
||||
if (it_node == m_active_session_map.end()) {
|
||||
return;
|
||||
}
|
||||
it_node->second->send_signal(signal);
|
||||
}
|
||||
|
||||
auto IpcManager::send_packet(NativeProcessID node, u16 packet_id,
|
||||
Span<const u8> payload) -> Result<void> {
|
||||
const auto it_node = m_active_session_map.find(node);
|
||||
auto IpcManager::send_packet(Const<NativeProcessID> node, Const<u16> packet_id,
|
||||
Const<Span<Const<u8>>> payload) -> Result<void> {
|
||||
Const<HashMap<NativeProcessID, NodeSession *>::iterator> it_node =
|
||||
m_active_session_map.find(node);
|
||||
if (it_node == m_active_session_map.end())
|
||||
return fail("no such node");
|
||||
return it_node->second->send_packet(packet_id, payload);
|
||||
|
||||
@ -15,7 +15,4 @@
|
||||
|
||||
#include <IACore/JSON.hpp>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
|
||||
} // namespace IACore
|
||||
namespace IACore {} // namespace IACore
|
||||
@ -1,16 +1,34 @@
|
||||
// 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 <chrono>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
namespace IACore {
|
||||
Logger::LogLevel Logger::m_log_level = Logger::LogLevel::Info;
|
||||
std::ofstream Logger::m_log_file;
|
||||
Mut<Logger::LogLevel> Logger::m_log_level = Logger::LogLevel::Info;
|
||||
Mut<std::ofstream> Logger::m_log_file;
|
||||
|
||||
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;
|
||||
static Const<std::chrono::time_point<std::chrono::steady_clock>> start_time =
|
||||
std::chrono::steady_clock::now();
|
||||
Const<std::chrono::time_point<std::chrono::steady_clock>> now =
|
||||
std::chrono::steady_clock::now();
|
||||
Const<std::chrono::duration<f64>> duration = now - start_time;
|
||||
return duration.count();
|
||||
}
|
||||
|
||||
@ -23,7 +41,8 @@ auto Logger::terminate() -> void {
|
||||
}
|
||||
}
|
||||
|
||||
auto Logger::enable_logging_to_disk(const char *file_path) -> Result<void> {
|
||||
auto Logger::enable_logging_to_disk(Const<Const<char> *> file_path)
|
||||
-> Result<void> {
|
||||
if (m_log_file.is_open()) {
|
||||
m_log_file.flush();
|
||||
m_log_file.close();
|
||||
@ -38,7 +57,7 @@ auto Logger::enable_logging_to_disk(const char *file_path) -> Result<void> {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Logger::set_log_level(LogLevel log_level) -> void {
|
||||
auto Logger::set_log_level(Const<LogLevel> log_level) -> void {
|
||||
m_log_level = log_level;
|
||||
}
|
||||
|
||||
@ -49,10 +68,11 @@ auto Logger::flush_logs() -> void {
|
||||
}
|
||||
}
|
||||
|
||||
auto Logger::log_internal(const char *prefix, const char *tag, String &&msg)
|
||||
-> void {
|
||||
const auto seconds = get_seconds_count();
|
||||
const auto out_line = std::format("[{:>8.3f}]: [{}]: {}", seconds, tag, msg);
|
||||
auto Logger::log_internal(Const<Const<char> *> prefix, Const<Const<char> *> tag,
|
||||
ForwardRef<String> msg) -> void {
|
||||
Const<f64> seconds = get_seconds_count();
|
||||
Const<String> out_line =
|
||||
std::format("[{:>8.3f}]: [{}]: {}", seconds, tag, msg);
|
||||
|
||||
std::cout << prefix << out_line << console::RESET << '\n';
|
||||
|
||||
|
||||
@ -16,100 +16,91 @@
|
||||
#include <IACore/Platform.hpp>
|
||||
|
||||
#if defined(IA_ARCH_X64)
|
||||
# ifndef _MSC_VER
|
||||
# include <cpuid.h>
|
||||
# endif
|
||||
#ifndef _MSC_VER
|
||||
#include <cpuid.h>
|
||||
#endif
|
||||
#elif defined(IA_ARCH_ARM64)
|
||||
# if defined(__linux__) || defined(__ANDROID__)
|
||||
# include <sys/auxv.h>
|
||||
# include <asm/hwcap.h>
|
||||
# endif
|
||||
#if defined(__linux__) || defined(__ANDROID__)
|
||||
#include <asm/hwcap.h>
|
||||
#include <sys/auxv.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
Platform::Capabilities Platform::s_capabilities{};
|
||||
namespace IACore {
|
||||
Mut<Platform::Capabilities> Platform::s_capabilities{};
|
||||
|
||||
#if defined(IA_ARCH_X64)
|
||||
auto Platform::cpuid(i32 function, i32 sub_function, i32 out[4]) -> void
|
||||
{
|
||||
# ifdef _MSC_VER
|
||||
__cpuidex(reinterpret_cast<int *>(out), static_cast<int>(function), static_cast<int>(sub_function));
|
||||
# else
|
||||
u32 a = 0;
|
||||
u32 b = 0;
|
||||
u32 c = 0;
|
||||
u32 d = 0;
|
||||
auto Platform::cpuid(Const<i32> function, Const<i32> sub_function,
|
||||
Mut<i32> out[4]) -> void {
|
||||
#ifdef _MSC_VER
|
||||
__cpuidex(reinterpret_cast<i32 *>(out), static_cast<i32>(function),
|
||||
static_cast<i32>(sub_function));
|
||||
#else
|
||||
Mut<u32> a = 0;
|
||||
Mut<u32> b = 0;
|
||||
Mut<u32> c = 0;
|
||||
Mut<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
|
||||
|
||||
auto Platform::check_cpu() -> bool
|
||||
{
|
||||
auto Platform::check_cpu() -> bool {
|
||||
#if defined(IA_ARCH_X64)
|
||||
i32 cpu_info[4];
|
||||
Mut<i32> cpu_info[4];
|
||||
|
||||
cpuid(0, 0, cpu_info);
|
||||
if (cpu_info[0] < 7)
|
||||
{
|
||||
if (cpu_info[0] < 7) {
|
||||
return false;
|
||||
}
|
||||
|
||||
cpuid(1, 0, cpu_info);
|
||||
// Bit 27: OSXSAVE, Bit 28: AVX, Bit 12: FMA
|
||||
const bool osxsave = (cpu_info[2] & (1 << 27)) != 0;
|
||||
const bool avx = (cpu_info[2] & (1 << 28)) != 0;
|
||||
const bool fma = (cpu_info[2] & (1 << 12)) != 0;
|
||||
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;
|
||||
|
||||
if (!osxsave || !avx || !fma)
|
||||
{
|
||||
if (!osxsave || !avx || !fma) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const u64 xcr_feature_mask = _xgetbv(0);
|
||||
if ((xcr_feature_mask & 0x6) != 0x6)
|
||||
{
|
||||
Const<u64> xcr_feature_mask = _xgetbv(0);
|
||||
if ((xcr_feature_mask & 0x6) != 0x6) {
|
||||
return false;
|
||||
}
|
||||
|
||||
cpuid(7, 0, cpu_info);
|
||||
// Bit 5: AVX2
|
||||
const bool avx2 = (cpu_info[1] & (1 << 5)) != 0;
|
||||
if (!avx2)
|
||||
{
|
||||
Const<bool> avx2 = (cpu_info[1] & (1 << 5)) != 0;
|
||||
if (!avx2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
s_capabilities.hardware_crc32 = true;
|
||||
|
||||
#elif defined(IA_ARCH_ARM64)
|
||||
# if defined(__linux__) || defined(__ANDROID__)
|
||||
const unsigned long hw_caps = getauxval(AT_HWCAP);
|
||||
#if defined(__linux__) || defined(__ANDROID__)
|
||||
Const<usize> hw_caps = getauxval(AT_HWCAP);
|
||||
|
||||
# ifndef HWCAP_CRC32
|
||||
# define HWCAP_CRC32 (1 << 7)
|
||||
# endif
|
||||
#ifndef HWCAP_CRC32
|
||||
#define HWCAP_CRC32 (1 << 7)
|
||||
#endif
|
||||
|
||||
s_capabilities.hardware_crc32 = (hw_caps & HWCAP_CRC32) != 0;
|
||||
# elif defined(IA_PLATFORM_APPLE)
|
||||
// Apple silicon always has hardware CRC32
|
||||
#elif defined(IA_PLATFORM_APPLE)
|
||||
s_capabilities.hardware_crc32 = true;
|
||||
# else
|
||||
#else
|
||||
s_capabilities.hardware_crc32 = false;
|
||||
# endif
|
||||
#endif
|
||||
#else
|
||||
s_capabilities.hardware_crc32 = false;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
auto Platform::get_architecture_name() -> const char *
|
||||
{
|
||||
auto Platform::get_architecture_name() -> const char * {
|
||||
#if defined(IA_ARCH_X64)
|
||||
return "x86_64";
|
||||
#elif defined(IA_ARCH_ARM64)
|
||||
@ -119,18 +110,17 @@ namespace IACore
|
||||
#else
|
||||
return "unknown";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
auto Platform::get_operating_system_name() -> const char *
|
||||
{
|
||||
auto Platform::get_operating_system_name() -> const char * {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
return "Windows";
|
||||
#elif defined(IA_PLATFORM_APPLE)
|
||||
# if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
|
||||
#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
|
||||
return "iOS";
|
||||
# else
|
||||
#else
|
||||
return "macOS";
|
||||
# endif
|
||||
#endif
|
||||
#elif defined(__ANDROID__)
|
||||
return "Android";
|
||||
#elif IA_PLATFORM_LINUX
|
||||
@ -140,5 +130,5 @@ namespace IACore
|
||||
#else
|
||||
return "Unknown";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -16,23 +16,18 @@
|
||||
#include <IACore/ProcessOps.hpp>
|
||||
|
||||
namespace IACore {
|
||||
// ---------------------------------------------------------------------
|
||||
// Output Buffering Helper
|
||||
// Splits raw chunks into lines, preserving partial lines across chunks
|
||||
// ---------------------------------------------------------------------
|
||||
struct LineBuffer {
|
||||
String m_accumulator;
|
||||
std::function<void(StringView)> &m_callback;
|
||||
Mut<String> m_accumulator;
|
||||
Const<std::function<void(Const<StringView>)>> m_callback;
|
||||
|
||||
void append(const char *data, usize size);
|
||||
void flush();
|
||||
auto append(Const<char *> data, Const<usize> size) -> void;
|
||||
auto flush() -> void;
|
||||
};
|
||||
|
||||
void LineBuffer::append(const char *data, usize size) {
|
||||
usize start = 0;
|
||||
for (usize i = 0; i < size; ++i) {
|
||||
auto LineBuffer::append(Const<char *> data, Const<usize> size) -> void {
|
||||
Mut<usize> start = 0;
|
||||
for (Mut<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()) {
|
||||
@ -45,20 +40,18 @@ void LineBuffer::append(const char *data, usize size) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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() {
|
||||
auto LineBuffer::flush() -> void {
|
||||
if (!m_accumulator.empty()) {
|
||||
m_callback(m_accumulator);
|
||||
m_accumulator.clear();
|
||||
@ -74,11 +67,11 @@ auto ProcessOps::get_current_process_id() -> NativeProcessID {
|
||||
}
|
||||
|
||||
auto ProcessOps::spawn_process_sync(
|
||||
const String &command, const String &args,
|
||||
std::function<void(StringView line)> on_output_line_callback)
|
||||
Ref<String> command, Ref<String> args,
|
||||
Const<std::function<void(Const<StringView>)>> on_output_line_callback)
|
||||
-> Result<i32> {
|
||||
std::atomic<NativeProcessID> id = 0;
|
||||
if constexpr (env::is_windows) {
|
||||
Mut<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);
|
||||
@ -86,23 +79,21 @@ auto ProcessOps::spawn_process_sync(
|
||||
}
|
||||
|
||||
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)
|
||||
Ref<String> command, Ref<String> args,
|
||||
Const<std::function<void(Const<StringView>)>> on_output_line_callback,
|
||||
Const<std::function<void(Const<Result<i32>>)>> on_finish_callback)
|
||||
-> Result<Box<ProcessHandle>> {
|
||||
auto handle = make_box<ProcessHandle>();
|
||||
Mut<Box<ProcessHandle>> 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();
|
||||
Mut<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");
|
||||
Mut<Result<i32>> result = fail("Platform not supported");
|
||||
|
||||
if constexpr (env::is_windows) {
|
||||
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);
|
||||
@ -122,18 +113,18 @@ auto ProcessOps::spawn_process_async(
|
||||
return handle;
|
||||
}
|
||||
|
||||
void ProcessOps::terminate_process(const Box<ProcessHandle> &handle) {
|
||||
auto ProcessOps::terminate_process(Ref<Box<ProcessHandle>> handle) -> void {
|
||||
if (!handle || !handle->is_active()) {
|
||||
return;
|
||||
}
|
||||
|
||||
NativeProcessID pid = handle->id.load();
|
||||
Const<NativeProcessID> pid = handle->id.load();
|
||||
if (pid == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
HANDLE h_process = OpenProcess(PROCESS_TERMINATE, false, pid);
|
||||
Mut<HANDLE> h_process = OpenProcess(PROCESS_TERMINATE, false, pid);
|
||||
if (h_process != NULL) {
|
||||
::TerminateProcess(h_process, 9);
|
||||
CloseHandle(h_process);
|
||||
@ -145,39 +136,35 @@ void ProcessOps::terminate_process(const Box<ProcessHandle> &handle) {
|
||||
}
|
||||
|
||||
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> {
|
||||
Ref<String> command, Ref<String> args,
|
||||
Const<std::function<void(Const<StringView>)>> on_output_line_callback,
|
||||
MutRef<std::atomic<NativeProcessID>> id) -> Result<i32> {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
SECURITY_ATTRIBUTES sa_attr = {sizeof(SECURITY_ATTRIBUTES), NULL,
|
||||
true}; // Allow inheritance
|
||||
HANDLE h_read = NULL;
|
||||
HANDLE h_write = NULL;
|
||||
Mut<SECURITY_ATTRIBUTES> sa_attr = {sizeof(SECURITY_ATTRIBUTES), NULL, true};
|
||||
Mut<HANDLE> h_read = NULL;
|
||||
Mut<HANDLE> h_write = NULL;
|
||||
|
||||
if (!CreatePipe(&h_read, &h_write, &sa_attr, 0)) {
|
||||
return fail("Failed to create pipe");
|
||||
}
|
||||
|
||||
// Ensure the read handle to the pipe for STDOUT is NOT inherited
|
||||
if (!SetHandleInformation(h_read, HANDLE_FLAG_INHERIT, 0)) {
|
||||
return fail("Failed to secure pipe handles");
|
||||
}
|
||||
|
||||
STARTUPINFOA si = {sizeof(STARTUPINFOA)};
|
||||
Mut<STARTUPINFOA> si = {sizeof(STARTUPINFOA)};
|
||||
si.dwFlags |= STARTF_USESTDHANDLES;
|
||||
si.hStdOutput = h_write;
|
||||
si.hStdError = h_write; // Merge stderr
|
||||
si.hStdInput = NULL; // No input
|
||||
si.hStdError = h_write;
|
||||
si.hStdInput = NULL;
|
||||
|
||||
PROCESS_INFORMATION pi = {0};
|
||||
Mut<PROCESS_INFORMATION> pi = {0};
|
||||
|
||||
// Windows command line needs to be mutable and concatenated
|
||||
String command_line = std::format("\"{}\" {}", command, args);
|
||||
Mut<String> command_line = std::format("\"{}\" {}", command, args);
|
||||
|
||||
BOOL success = CreateProcessA(NULL, command_line.data(), NULL, NULL, true, 0,
|
||||
NULL, NULL, &si, &pi);
|
||||
Const<BOOL> success = CreateProcessA(NULL, command_line.data(), NULL, NULL,
|
||||
true, 0, NULL, NULL, &si, &pi);
|
||||
|
||||
// Close write end in parent, otherwise ReadFile never returns EOF!
|
||||
CloseHandle(h_write);
|
||||
|
||||
if (!success) {
|
||||
@ -187,19 +174,18 @@ auto ProcessOps::spawn_process_windows(
|
||||
|
||||
id.store(pi.dwProcessId);
|
||||
|
||||
// Read Loop
|
||||
LineBuffer line_buf{"", on_output_line_callback};
|
||||
DWORD bytes_read = 0;
|
||||
char buffer[4096];
|
||||
Mut<LineBuffer> line_buf{"", on_output_line_callback};
|
||||
Mut<DWORD> bytes_read = 0;
|
||||
Mut<Array<char, 4096>> buffer;
|
||||
|
||||
while (ReadFile(h_read, buffer, sizeof(buffer), &bytes_read, NULL) &&
|
||||
while (ReadFile(h_read, buffer.data(), static_cast<DWORD>(buffer.size()),
|
||||
&bytes_read, NULL) &&
|
||||
bytes_read != 0) {
|
||||
line_buf.append(buffer, bytes_read);
|
||||
line_buf.append(buffer.data(), bytes_read);
|
||||
}
|
||||
line_buf.flush();
|
||||
|
||||
// NOW we wait for exit code
|
||||
DWORD exit_code = 0;
|
||||
Mut<DWORD> exit_code = 0;
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
GetExitCodeProcess(pi.hProcess, &exit_code);
|
||||
|
||||
@ -210,69 +196,63 @@ auto ProcessOps::spawn_process_windows(
|
||||
|
||||
return static_cast<i32>(exit_code);
|
||||
#else
|
||||
(void)command;
|
||||
(void)args;
|
||||
(void)on_output_line_callback;
|
||||
(void)id;
|
||||
OX_UNUSED(command);
|
||||
OX_UNUSED(args);
|
||||
OX_UNUSED(on_output_line_callback);
|
||||
OX_UNUSED(id);
|
||||
return fail("Windows implementation not available.");
|
||||
#endif
|
||||
}
|
||||
|
||||
auto ProcessOps::spawn_process_posix(
|
||||
const String &command, const String &args,
|
||||
std::function<void(StringView)> on_output_line_callback,
|
||||
std::atomic<NativeProcessID> &id) -> Result<i32> {
|
||||
Ref<String> command, Ref<String> args,
|
||||
Const<std::function<void(Const<StringView>)>> on_output_line_callback,
|
||||
MutRef<std::atomic<NativeProcessID>> id) -> Result<i32> {
|
||||
#if IA_PLATFORM_UNIX
|
||||
int pipefd[2];
|
||||
if (pipe(pipefd) == -1) {
|
||||
Mut<Array<i32, 2>> pipefd;
|
||||
if (pipe(pipefd.data()) == -1) {
|
||||
return fail("Failed to create pipe");
|
||||
}
|
||||
|
||||
pid_t pid = fork();
|
||||
Const<pid_t> pid = fork();
|
||||
|
||||
if (pid == -1) {
|
||||
return fail("Failed to fork process");
|
||||
} else if (pid == 0) {
|
||||
// --- Child Process ---
|
||||
close(pipefd[0]);
|
||||
|
||||
dup2(pipefd[1], STDOUT_FILENO);
|
||||
dup2(pipefd[1], STDERR_FILENO);
|
||||
close(pipefd[1]);
|
||||
|
||||
Vec<String> arg_storage; // To keep strings alive
|
||||
Vec<char *> argv;
|
||||
Mut<Vec<String>> arg_storage;
|
||||
Mut<Vec<char *>> argv;
|
||||
|
||||
String cmd_str = command;
|
||||
Mut<String> cmd_str = command;
|
||||
argv.push_back(cmd_str.data());
|
||||
|
||||
// Manual Quote-Aware Splitter
|
||||
String current_token;
|
||||
bool in_quotes = false;
|
||||
bool is_escaped = false;
|
||||
Mut<String> current_token;
|
||||
Mut<bool> in_quotes = false;
|
||||
Mut<bool> is_escaped = false;
|
||||
|
||||
for (char c : args) {
|
||||
for (Const<char> c : args) {
|
||||
if (is_escaped) {
|
||||
// Previous char was '\', so we treat this char literally.
|
||||
current_token += c;
|
||||
is_escaped = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '\\') {
|
||||
// Escape sequence start
|
||||
is_escaped = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '\"') {
|
||||
// Toggle quote state
|
||||
in_quotes = !in_quotes;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == ' ' && !in_quotes) {
|
||||
// Token boundary
|
||||
if (!current_token.empty()) {
|
||||
arg_storage.push_back(current_token);
|
||||
current_token.clear();
|
||||
@ -286,8 +266,7 @@ auto ProcessOps::spawn_process_posix(
|
||||
arg_storage.push_back(current_token);
|
||||
}
|
||||
|
||||
// Build char* array from the std::string storage
|
||||
for (auto &s : arg_storage) {
|
||||
for (MutRef<String> s : arg_storage) {
|
||||
argv.push_back(s.data());
|
||||
}
|
||||
argv.push_back(nullptr);
|
||||
@ -295,22 +274,21 @@ auto ProcessOps::spawn_process_posix(
|
||||
execvp(argv[0], argv.data());
|
||||
_exit(127);
|
||||
} else {
|
||||
// --- Parent Process ---
|
||||
id.store(pid);
|
||||
|
||||
close(pipefd[1]);
|
||||
|
||||
LineBuffer line_buf{"", on_output_line_callback};
|
||||
char buffer[4096];
|
||||
ssize_t count;
|
||||
Mut<LineBuffer> line_buf{"", on_output_line_callback};
|
||||
Mut<Array<char, 4096>> buffer;
|
||||
Mut<isize> count;
|
||||
|
||||
while ((count = read(pipefd[0], buffer, sizeof(buffer))) > 0) {
|
||||
line_buf.append(buffer, static_cast<usize>(count));
|
||||
while ((count = read(pipefd[0], buffer.data(), buffer.size())) > 0) {
|
||||
line_buf.append(buffer.data(), static_cast<usize>(count));
|
||||
}
|
||||
line_buf.flush();
|
||||
close(pipefd[0]);
|
||||
|
||||
int status;
|
||||
Mut<i32> status;
|
||||
waitpid(pid, &status, 0);
|
||||
|
||||
id.store(0);
|
||||
@ -320,10 +298,10 @@ auto ProcessOps::spawn_process_posix(
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
(void)command;
|
||||
(void)args;
|
||||
(void)on_output_line_callback;
|
||||
(void)id;
|
||||
OX_UNUSED(command);
|
||||
OX_UNUSED(args);
|
||||
OX_UNUSED(on_output_line_callback);
|
||||
OX_UNUSED(id);
|
||||
return fail("Posix implementation not available.");
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -15,7 +15,4 @@
|
||||
|
||||
#include <IACore/SIMD.hpp>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
|
||||
}
|
||||
namespace IACore {} // namespace IACore
|
||||
@ -17,9 +17,9 @@
|
||||
#include <cstring>
|
||||
|
||||
namespace IACore {
|
||||
i32 SocketOps::s_init_count = 0;
|
||||
Mut<i32> SocketOps::s_init_count = 0;
|
||||
|
||||
auto SocketOps::close(SocketHandle sock) -> void {
|
||||
auto SocketOps::close(Const<SocketHandle> sock) -> void {
|
||||
if (sock == INVALID_SOCKET) {
|
||||
return;
|
||||
}
|
||||
@ -30,7 +30,8 @@ auto SocketOps::close(SocketHandle sock) -> void {
|
||||
#endif
|
||||
}
|
||||
|
||||
auto SocketOps::listen(SocketHandle sock, i32 queue_size) -> Result<void> {
|
||||
auto SocketOps::listen(Const<SocketHandle> sock, Const<i32> queue_size)
|
||||
-> Result<void> {
|
||||
if (::listen(sock, queue_size) == 0) {
|
||||
return {};
|
||||
}
|
||||
@ -43,7 +44,7 @@ auto SocketOps::listen(SocketHandle sock, i32 queue_size) -> Result<void> {
|
||||
}
|
||||
|
||||
auto SocketOps::create_unix_socket() -> Result<SocketHandle> {
|
||||
const SocketHandle sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
Const<SocketHandle> sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (sock == INVALID_SOCKET) {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
return fail("socket(AF_UNIX) failed: {}", WSAGetLastError());
|
||||
@ -54,18 +55,18 @@ auto SocketOps::create_unix_socket() -> Result<SocketHandle> {
|
||||
return sock;
|
||||
}
|
||||
|
||||
auto SocketOps::bind_unix_socket(SocketHandle sock, const char *path)
|
||||
-> Result<void> {
|
||||
auto SocketOps::bind_unix_socket(Const<SocketHandle> sock,
|
||||
Const<const char *> path) -> Result<void> {
|
||||
if (sock == INVALID_SOCKET) {
|
||||
return fail("Invalid socket handle");
|
||||
}
|
||||
|
||||
unlink_file(path);
|
||||
|
||||
sockaddr_un addr{};
|
||||
Mut<sockaddr_un> addr{};
|
||||
addr.sun_family = AF_UNIX;
|
||||
|
||||
const usize max_len = 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
|
||||
@ -84,16 +85,16 @@ auto SocketOps::bind_unix_socket(SocketHandle sock, const char *path)
|
||||
return {};
|
||||
}
|
||||
|
||||
auto SocketOps::connect_unix_socket(SocketHandle sock, const char *path)
|
||||
-> Result<void> {
|
||||
auto SocketOps::connect_unix_socket(Const<SocketHandle> sock,
|
||||
Const<const char *> path) -> Result<void> {
|
||||
if (sock == INVALID_SOCKET) {
|
||||
return fail("Invalid socket handle");
|
||||
}
|
||||
|
||||
sockaddr_un addr{};
|
||||
Mut<sockaddr_un> addr{};
|
||||
addr.sun_family = AF_UNIX;
|
||||
|
||||
const usize max_len = 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
|
||||
@ -112,19 +113,18 @@ auto SocketOps::connect_unix_socket(SocketHandle sock, const char *path)
|
||||
return {};
|
||||
}
|
||||
|
||||
auto SocketOps::is_port_available(u16 port, i32 type) -> bool {
|
||||
// Use 0 for protocol to let OS select default (TCP for STREAM, UDP for DGRAM)
|
||||
const SocketHandle sock = socket(AF_INET, type, 0);
|
||||
auto SocketOps::is_port_available(Const<u16> port, Const<i32> type) -> bool {
|
||||
Const<SocketHandle> sock = socket(AF_INET, type, 0);
|
||||
if (sock == INVALID_SOCKET) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sockaddr_in addr{};
|
||||
Mut<sockaddr_in> addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
|
||||
bool is_free = false;
|
||||
Mut<bool> is_free = false;
|
||||
if (::bind(sock, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) ==
|
||||
0) {
|
||||
is_free = true;
|
||||
|
||||
@ -17,30 +17,29 @@
|
||||
#include <IACore/StreamReader.hpp>
|
||||
|
||||
namespace IACore {
|
||||
auto StreamReader::create_from_file(const Path &path) -> Result<StreamReader> {
|
||||
usize size = 0;
|
||||
auto StreamReader::create_from_file(Ref<Path> path) -> Result<StreamReader> {
|
||||
Mut<usize> size = 0;
|
||||
|
||||
const u8 *ptr;
|
||||
IA_TRY(ptr, FileOps::map_file(path, size));
|
||||
Const<const u8 *> ptr = OX_TRY(FileOps::map_file(path, size));
|
||||
|
||||
StreamReader reader(Span<const u8>(ptr, size));
|
||||
Mut<StreamReader> reader(Span<const u8>(ptr, size));
|
||||
reader.m_storage_type = StorageType::OwningMmap;
|
||||
|
||||
return reader;
|
||||
}
|
||||
|
||||
StreamReader::StreamReader(Vec<u8> &&data)
|
||||
StreamReader::StreamReader(ForwardRef<Vec<u8>> data)
|
||||
: m_owning_vector(std::move(data)),
|
||||
m_storage_type(StorageType::OwningVector) {
|
||||
m_data = m_owning_vector.data();
|
||||
m_data_size = m_owning_vector.size();
|
||||
}
|
||||
|
||||
StreamReader::StreamReader(Span<const u8> data)
|
||||
StreamReader::StreamReader(Const<Span<const u8>> data)
|
||||
: m_data(data.data()), m_data_size(data.size()),
|
||||
m_storage_type(StorageType::NonOwning) {}
|
||||
|
||||
StreamReader::StreamReader(StreamReader &&other)
|
||||
StreamReader::StreamReader(ForwardRef<StreamReader> other)
|
||||
: m_data(other.m_data), m_cursor(other.m_cursor),
|
||||
m_data_size(other.m_data_size),
|
||||
m_owning_vector(std::move(other.m_owning_vector)),
|
||||
@ -49,11 +48,13 @@ StreamReader::StreamReader(StreamReader &&other)
|
||||
other.m_data = {};
|
||||
other.m_data_size = 0;
|
||||
|
||||
if (m_storage_type == StorageType::OwningVector)
|
||||
if (m_storage_type == StorageType::OwningVector) {
|
||||
m_data = m_owning_vector.data();
|
||||
}
|
||||
}
|
||||
|
||||
auto StreamReader::operator=(StreamReader &&other) -> StreamReader & {
|
||||
auto StreamReader::operator=(ForwardRef<StreamReader> other)
|
||||
-> MutRef<StreamReader> {
|
||||
if (this != &other) {
|
||||
if (m_storage_type == StorageType::OwningMmap) {
|
||||
FileOps::unmap_file(m_data);
|
||||
@ -65,8 +66,9 @@ auto StreamReader::operator=(StreamReader &&other) -> StreamReader & {
|
||||
m_owning_vector = std::move(other.m_owning_vector);
|
||||
m_storage_type = other.m_storage_type;
|
||||
|
||||
if (m_storage_type == StorageType::OwningVector)
|
||||
if (m_storage_type == StorageType::OwningVector) {
|
||||
m_data = m_owning_vector.data();
|
||||
}
|
||||
|
||||
other.m_storage_type = StorageType::NonOwning;
|
||||
other.m_data = {};
|
||||
|
||||
@ -17,14 +17,14 @@
|
||||
|
||||
namespace IACore {
|
||||
|
||||
auto StreamWriter::create_from_file(const Path &path) -> Result<StreamWriter> {
|
||||
FILE *f = std::fopen(path.string().c_str(), "wb");
|
||||
auto StreamWriter::create_from_file(Ref<Path> path) -> Result<StreamWriter> {
|
||||
Mut<FILE *> f = std::fopen(path.string().c_str(), "wb");
|
||||
if (!f) {
|
||||
return fail("Failed to open file for writing: {}", path.string());
|
||||
}
|
||||
std::fclose(f);
|
||||
|
||||
StreamWriter writer;
|
||||
Mut<StreamWriter> writer;
|
||||
writer.m_file_path = path;
|
||||
writer.m_storage_type = StorageType::OwningFile;
|
||||
|
||||
@ -37,11 +37,11 @@ StreamWriter::StreamWriter() : m_storage_type(StorageType::OwningVector) {
|
||||
m_buffer = m_owning_vector.data();
|
||||
}
|
||||
|
||||
StreamWriter::StreamWriter(Span<u8> data)
|
||||
StreamWriter::StreamWriter(Const<Span<u8>> data)
|
||||
: m_buffer(data.data()), m_cursor(0), m_capacity(data.size()),
|
||||
m_storage_type(StorageType::NonOwning) {}
|
||||
|
||||
StreamWriter::StreamWriter(StreamWriter &&other)
|
||||
StreamWriter::StreamWriter(ForwardRef<StreamWriter> other)
|
||||
: m_buffer(other.m_buffer), m_cursor(other.m_cursor),
|
||||
m_capacity(other.m_capacity), m_file_path(other.m_file_path),
|
||||
m_owning_vector(std::move(other.m_owning_vector)),
|
||||
@ -54,11 +54,11 @@ StreamWriter::StreamWriter(StreamWriter &&other)
|
||||
m_buffer = m_owning_vector.data();
|
||||
}
|
||||
|
||||
auto StreamWriter::operator=(StreamWriter &&other) -> StreamWriter & {
|
||||
auto StreamWriter::operator=(ForwardRef<StreamWriter> other)
|
||||
-> MutRef<StreamWriter> {
|
||||
if (this != &other) {
|
||||
// Flush current if needed
|
||||
if (m_storage_type == StorageType::OwningFile) {
|
||||
if (auto res = flush_to_disk(); !res) {
|
||||
if (Const<Result<void>> res = flush_to_disk(); !res) {
|
||||
std::fprintf(stderr, "[IACore] Data loss in StreamWriter move: %s\n",
|
||||
res.error().c_str());
|
||||
}
|
||||
@ -67,7 +67,7 @@ auto StreamWriter::operator=(StreamWriter &&other) -> StreamWriter & {
|
||||
m_buffer = other.m_buffer;
|
||||
m_cursor = other.m_cursor;
|
||||
m_capacity = other.m_capacity;
|
||||
m_file_path = std::move(other.m_file_path); // Use move for string/path
|
||||
m_file_path = std::move(other.m_file_path);
|
||||
m_owning_vector = std::move(other.m_owning_vector);
|
||||
m_storage_type = other.m_storage_type;
|
||||
|
||||
@ -84,9 +84,7 @@ auto StreamWriter::operator=(StreamWriter &&other) -> StreamWriter & {
|
||||
|
||||
StreamWriter::~StreamWriter() {
|
||||
if (m_storage_type == StorageType::OwningFile) {
|
||||
// We can't return errors here, so we log them.
|
||||
// Ideally, the user calls save() before this runs.
|
||||
if (auto res = flush_to_disk(); !res) {
|
||||
if (Const<Result<void>> res = flush_to_disk(); !res) {
|
||||
std::fprintf(stderr, "[IACore] LOST DATA in ~StreamWriter: %s\n",
|
||||
res.error().c_str());
|
||||
}
|
||||
@ -94,11 +92,9 @@ StreamWriter::~StreamWriter() {
|
||||
}
|
||||
|
||||
auto StreamWriter::flush() -> Result<void> {
|
||||
auto res = flush_to_disk();
|
||||
// Prevent double-write in destructor if save was successful
|
||||
Mut<Result<void>> res = flush_to_disk();
|
||||
if (res.has_value()) {
|
||||
m_storage_type = StorageType::OwningVector; // downgrade to vector so dtor
|
||||
// doesn't write again
|
||||
m_storage_type = StorageType::OwningVector;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@ -108,12 +104,12 @@ auto StreamWriter::flush_to_disk() -> Result<void> {
|
||||
return {};
|
||||
}
|
||||
|
||||
FILE *f = std::fopen(m_file_path.string().c_str(), "wb");
|
||||
Mut<FILE *> f = std::fopen(m_file_path.string().c_str(), "wb");
|
||||
if (!f) {
|
||||
return fail("Failed to open file for writing: {}", m_file_path.string());
|
||||
}
|
||||
|
||||
usize written = std::fwrite(m_buffer, 1, m_cursor, f);
|
||||
Const<usize> written = std::fwrite(m_buffer, 1, m_cursor, f);
|
||||
std::fclose(f);
|
||||
|
||||
if (written != m_cursor) {
|
||||
@ -122,15 +118,15 @@ auto StreamWriter::flush_to_disk() -> Result<void> {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto StreamWriter::write(u8 byte, usize count) -> Result<void> {
|
||||
auto StreamWriter::write(Const<u8> byte, Const<usize> count) -> Result<void> {
|
||||
if (m_cursor + count > m_capacity) {
|
||||
if (m_storage_type == StorageType::NonOwning) {
|
||||
return fail("StreamWriter buffer overflow (NonOwning)");
|
||||
}
|
||||
|
||||
const usize required = m_cursor + count;
|
||||
const usize double_cap = m_capacity * 2;
|
||||
const usize new_capacity = (double_cap > required) ? double_cap : required;
|
||||
Const<usize> required = m_cursor + count;
|
||||
Const<usize> double_cap = m_capacity * 2;
|
||||
Const<usize> new_capacity = (double_cap > required) ? double_cap : required;
|
||||
|
||||
m_owning_vector.resize(new_capacity);
|
||||
m_capacity = m_owning_vector.size();
|
||||
@ -142,17 +138,16 @@ auto StreamWriter::write(u8 byte, usize count) -> Result<void> {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto StreamWriter::write(const void *buffer, usize size) -> Result<void> {
|
||||
auto StreamWriter::write(Const<const void *> buffer, Const<usize> size)
|
||||
-> Result<void> {
|
||||
if (m_cursor + size > m_capacity) {
|
||||
if (m_storage_type == StorageType::NonOwning) {
|
||||
return fail("StreamWriter buffer overflow (NonOwning)");
|
||||
}
|
||||
|
||||
// NEW STRATEGY: Max(Double Capacity, Required Size)
|
||||
// This prevents frequent reallocations for repeated small writes
|
||||
const usize required = m_cursor + size;
|
||||
const usize double_cap = m_capacity * 2;
|
||||
const usize new_capacity = (double_cap > required) ? double_cap : required;
|
||||
Const<usize> required = m_cursor + size;
|
||||
Const<usize> double_cap = m_capacity * 2;
|
||||
Const<usize> new_capacity = (double_cap > required) ? double_cap : required;
|
||||
|
||||
m_owning_vector.resize(new_capacity);
|
||||
m_capacity = m_owning_vector.size();
|
||||
|
||||
@ -17,15 +17,15 @@
|
||||
|
||||
namespace IACore {
|
||||
|
||||
static const String BASE64_CHAR_TABLE =
|
||||
static Const<String> BASE64_CHAR_TABLE =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
static auto is_base64(u8 c) -> bool {
|
||||
static auto is_base64(Const<u8> c) -> bool {
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
|
||||
(c >= '0' && c <= '9') || (c == '+') || (c == '/');
|
||||
}
|
||||
|
||||
static auto get_base64_index(u8 c) -> u8 {
|
||||
static auto get_base64_index(Const<u8> c) -> u8 {
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
return c - 'A';
|
||||
if (c >= 'a' && c <= 'z')
|
||||
@ -39,16 +39,16 @@ static auto get_base64_index(u8 c) -> u8 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto StringOps::encode_base64(Span<const u8> data) -> String {
|
||||
String result;
|
||||
auto StringOps::encode_base64(Const<Span<Const<u8>>> data) -> String {
|
||||
Mut<String> result;
|
||||
result.reserve(((data.size() + 2) / 3) * 4);
|
||||
|
||||
for (usize i = 0; i < data.size(); i += 3) {
|
||||
u32 b0 = data[i];
|
||||
u32 b1 = (i + 1 < data.size()) ? data[i + 1] : 0;
|
||||
u32 b2 = (i + 2 < data.size()) ? data[i + 2] : 0;
|
||||
for (Mut<usize> i = 0; i < data.size(); i += 3) {
|
||||
Const<u32> b0 = data[i];
|
||||
Const<u32> b1 = (i + 1 < data.size()) ? data[i + 1] : 0;
|
||||
Const<u32> b2 = (i + 2 < data.size()) ? data[i + 2] : 0;
|
||||
|
||||
u32 triple = (b0 << 16) | (b1 << 8) | b2;
|
||||
Const<u32> triple = (b0 << 16) | (b1 << 8) | b2;
|
||||
|
||||
result += BASE64_CHAR_TABLE[(triple >> 18) & 0x3F];
|
||||
result += BASE64_CHAR_TABLE[(triple >> 12) & 0x3F];
|
||||
@ -68,15 +68,15 @@ auto StringOps::encode_base64(Span<const u8> data) -> String {
|
||||
return result;
|
||||
}
|
||||
|
||||
auto StringOps::decode_base64(const String &data) -> Vec<u8> {
|
||||
Vec<u8> result;
|
||||
auto StringOps::decode_base64(Ref<String> data) -> Vec<u8> {
|
||||
Mut<Vec<u8>> result;
|
||||
result.reserve(data.size() * 3 / 4);
|
||||
|
||||
i32 i = 0;
|
||||
u8 tmp_buf[4];
|
||||
Mut<i32> i = 0;
|
||||
Mut<Array<u8, 4>> tmp_buf = {};
|
||||
|
||||
for (char c_char : data) {
|
||||
u8 c = static_cast<u8>(c_char);
|
||||
for (Const<char> c_char : data) {
|
||||
Const<u8> c = static_cast<u8>(c_char);
|
||||
if (c == '=') {
|
||||
break;
|
||||
}
|
||||
@ -86,10 +86,10 @@ auto StringOps::decode_base64(const String &data) -> Vec<u8> {
|
||||
|
||||
tmp_buf[i++] = c;
|
||||
if (i == 4) {
|
||||
u8 n0 = get_base64_index(tmp_buf[0]);
|
||||
u8 n1 = get_base64_index(tmp_buf[1]);
|
||||
u8 n2 = get_base64_index(tmp_buf[2]);
|
||||
u8 n3 = get_base64_index(tmp_buf[3]);
|
||||
Const<u8> n0 = get_base64_index(tmp_buf[0]);
|
||||
Const<u8> n1 = get_base64_index(tmp_buf[1]);
|
||||
Const<u8> n2 = get_base64_index(tmp_buf[2]);
|
||||
Const<u8> n3 = get_base64_index(tmp_buf[3]);
|
||||
|
||||
result.push_back((n0 << 2) | ((n1 & 0x30) >> 4));
|
||||
result.push_back(((n1 & 0x0F) << 4) | ((n2 & 0x3C) >> 2));
|
||||
@ -100,13 +100,13 @@ auto StringOps::decode_base64(const String &data) -> Vec<u8> {
|
||||
}
|
||||
|
||||
if (i > 0) {
|
||||
for (i32 j = i; j < 4; ++j) {
|
||||
tmp_buf[j] = 'A'; // Pad with 'A' (index 0)
|
||||
for (Mut<i32> j = i; j < 4; ++j) {
|
||||
tmp_buf[j] = 'A';
|
||||
}
|
||||
|
||||
u8 n0 = get_base64_index(tmp_buf[0]);
|
||||
u8 n1 = get_base64_index(tmp_buf[1]);
|
||||
u8 n2 = get_base64_index(tmp_buf[2]);
|
||||
Const<u8> n0 = get_base64_index(tmp_buf[0]);
|
||||
Const<u8> n1 = get_base64_index(tmp_buf[1]);
|
||||
Const<u8> n2 = get_base64_index(tmp_buf[2]);
|
||||
|
||||
if (i > 1) {
|
||||
result.push_back((n0 << 2) | ((n1 & 0x30) >> 4));
|
||||
|
||||
@ -17,98 +17,90 @@
|
||||
#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();
|
||||
namespace IACore {
|
||||
namespace {
|
||||
auto from_hex_char(Const<char> c) -> i32 {
|
||||
if (c >= '0' && c <= '9') {
|
||||
return c - '0';
|
||||
}
|
||||
|
||||
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();
|
||||
if (c >= 'A' && c <= 'F') {
|
||||
return c - 'A' + 10;
|
||||
}
|
||||
|
||||
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());
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
return c - 'a' + 10;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
auto Utils::get_random() -> f32
|
||||
{
|
||||
extern Mut<std::chrono::high_resolution_clock::time_point> g_start_time;
|
||||
|
||||
auto Utils::get_unix_time() -> u64 {
|
||||
Const<std::chrono::system_clock::time_point> 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<std::chrono::high_resolution_clock::duration> 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<std::chrono::high_resolution_clock::duration> 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
|
||||
{
|
||||
auto Utils::get_random(Const<u64> max) -> u64 {
|
||||
return static_cast<u64>(static_cast<f32>(max) * get_random());
|
||||
}
|
||||
}
|
||||
|
||||
auto Utils::get_random(i64 min, i64 max) -> i64
|
||||
{
|
||||
auto Utils::get_random(Const<i64> min, Const<i64> max) -> i64 {
|
||||
return min + static_cast<i64>(static_cast<f32>(max - min) * get_random());
|
||||
}
|
||||
}
|
||||
|
||||
auto Utils::sleep(u64 milliseconds) -> void
|
||||
{
|
||||
auto Utils::sleep(Const<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();
|
||||
auto Utils::binary_to_hex_string(Const<Span<Const<u8>>> data) -> String {
|
||||
static constexpr Const<char[17]> lut = "0123456789ABCDEF";
|
||||
Mut<String> res = String();
|
||||
res.reserve(data.size() * 2);
|
||||
|
||||
for (const auto b : data)
|
||||
{
|
||||
for (Const<u8> 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)
|
||||
{
|
||||
auto Utils::hex_string_to_binary(Const<StringView> hex) -> Result<Vec<u8>> {
|
||||
if (hex.size() % 2 != 0) {
|
||||
return fail("Hex string must have even length");
|
||||
}
|
||||
|
||||
auto out = Vec<u8>();
|
||||
Mut<Vec<u8>> 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];
|
||||
for (Mut<usize> i = 0; i < hex.size(); i += 2) {
|
||||
Const<char> high = hex[i];
|
||||
Const<char> 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<i32> h = from_hex_char(high);
|
||||
Const<i32> l = from_hex_char(low);
|
||||
|
||||
const auto h = from_hex_char(high);
|
||||
const auto l = from_hex_char(low);
|
||||
|
||||
if (h == -1 || l == -1)
|
||||
{
|
||||
if (h == -1 || l == -1) {
|
||||
return fail("Invalid hex character found");
|
||||
}
|
||||
|
||||
@ -116,5 +108,5 @@ namespace IACore
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -14,50 +14,47 @@
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/XML.hpp>
|
||||
#include <sstream>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
Result<XML::Document> XML::parse_from_string(const String &data)
|
||||
{
|
||||
Document doc;
|
||||
const auto parseResult = doc.load_string(data.data());
|
||||
if (!parseResult)
|
||||
return fail("Failed to parse XML {}", parseResult.description());
|
||||
return std::move(doc);
|
||||
namespace IACore {
|
||||
|
||||
auto XML::parse_from_string(Ref<String> data) -> Result<Document> {
|
||||
Mut<Document> doc;
|
||||
Const<pugi::xml_parse_result> parse_result = doc.load_string(data.c_str());
|
||||
if (!parse_result) {
|
||||
return fail("Failed to parse XML {}", parse_result.description());
|
||||
}
|
||||
|
||||
Result<XML::Document> XML::parse_from_file(const Path &path)
|
||||
{
|
||||
Document doc;
|
||||
const auto parseResult = doc.load_file(path.string().c_str());
|
||||
if (!parseResult)
|
||||
return fail("Failed to parse XML {}", parseResult.description());
|
||||
return std::move(doc);
|
||||
}
|
||||
}
|
||||
|
||||
String XML::serialize_to_string(const Node &node, bool escape)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
auto XML::parse_from_file(Ref<Path> path) -> Result<Document> {
|
||||
Mut<Document> doc;
|
||||
Const<pugi::xml_parse_result> parse_result =
|
||||
doc.load_file(path.string().c_str());
|
||||
if (!parse_result) {
|
||||
return fail("Failed to parse XML {}", parse_result.description());
|
||||
}
|
||||
return std::move(doc);
|
||||
}
|
||||
|
||||
auto XML::serialize_to_string(Ref<Node> node, Const<bool> escape) -> String {
|
||||
Mut<std::ostringstream> oss;
|
||||
node.print(oss);
|
||||
return escape ? escape_xml_string(oss.str()) : oss.str();
|
||||
}
|
||||
}
|
||||
|
||||
String XML::serialize_to_string(const Document &doc, bool escape)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
auto XML::serialize_to_string(Ref<Document> doc, Const<bool> escape) -> String {
|
||||
Mut<std::ostringstream> oss;
|
||||
doc.save(oss);
|
||||
return escape ? escape_xml_string(oss.str()) : oss.str();
|
||||
}
|
||||
}
|
||||
|
||||
String XML::escape_xml_string(const String &xml)
|
||||
{
|
||||
String buffer;
|
||||
auto XML::escape_xml_string(Ref<String> xml) -> String {
|
||||
Mut<String> buffer;
|
||||
buffer.reserve(xml.size() + (xml.size() / 10));
|
||||
|
||||
for (char c : xml)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
for (Const<char> c : xml) {
|
||||
switch (c) {
|
||||
case '&':
|
||||
buffer.append("&");
|
||||
break;
|
||||
@ -80,5 +77,6 @@ namespace IACore
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace IACore
|
||||
@ -16,96 +16,90 @@
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
#include <cstring>
|
||||
|
||||
namespace IACore {
|
||||
class RingBufferView {
|
||||
public:
|
||||
static constexpr u16 PACKET_ID_SKIP = 0;
|
||||
static constexpr Const<u16> PACKET_ID_SKIP = 0;
|
||||
|
||||
struct ControlBlock {
|
||||
struct alignas(64) {
|
||||
std::atomic<u32> write_offset{0};
|
||||
Mut<std::atomic<u32>> write_offset{0};
|
||||
} producer;
|
||||
|
||||
struct alignas(64) {
|
||||
std::atomic<u32> read_offset{0};
|
||||
// Capacity is effectively constant after init,
|
||||
// so it doesn't cause false sharing invalidations.
|
||||
u32 capacity{0};
|
||||
Mut<std::atomic<u32>> read_offset{0};
|
||||
Mut<u32> capacity{0};
|
||||
} consumer;
|
||||
};
|
||||
|
||||
static_assert(offsetof(ControlBlock, consumer) == 64,
|
||||
"False sharing detected in ControlBlock");
|
||||
|
||||
// All of the data in ring buffer will be stored as packets
|
||||
struct PacketHeader {
|
||||
PacketHeader() : id(0), payload_size(0) {}
|
||||
|
||||
PacketHeader(u16 id) : id(id), payload_size(0) {}
|
||||
PacketHeader(Const<u16> id) : id(id), payload_size(0) {}
|
||||
|
||||
PacketHeader(u16 id, u16 payload_size)
|
||||
PacketHeader(Const<u16> id, Const<u16> payload_size)
|
||||
: id(id), payload_size(payload_size) {}
|
||||
|
||||
u16 id{};
|
||||
u16 payload_size{};
|
||||
Mut<u16> id{};
|
||||
Mut<u16> payload_size{};
|
||||
};
|
||||
|
||||
public:
|
||||
static auto default_instance() -> RingBufferView;
|
||||
|
||||
static auto create(Span<u8> buffer, bool is_owner) -> Result<RingBufferView>;
|
||||
static auto create(ControlBlock *control_block, Span<u8> buffer,
|
||||
bool is_owner) -> Result<RingBufferView>;
|
||||
static auto create(Ref<Span<u8>> buffer, Const<bool> is_owner)
|
||||
-> Result<RingBufferView>;
|
||||
static auto create(Const<ControlBlock *> control_block, Ref<Span<u8>> buffer,
|
||||
Const<bool> is_owner) -> Result<RingBufferView>;
|
||||
|
||||
// Returns:
|
||||
// - Ok(nullopt) if empty
|
||||
// - Ok(bytes_read) if success
|
||||
// - nullopt if empty
|
||||
// - bytes_read if success
|
||||
// - Error if buffer too small
|
||||
auto pop(PacketHeader &out_header, Span<u8> out_buffer)
|
||||
auto pop(MutRef<PacketHeader> out_header, Ref<Span<u8>> out_buffer)
|
||||
-> Result<Option<usize>>;
|
||||
|
||||
// Returns:
|
||||
// - Ok() if success
|
||||
// - Error if full
|
||||
auto push(u16 packet_id, Span<const u8> data) -> Result<void>;
|
||||
auto push(Const<u16> packet_id, Ref<Span<const u8>> data) -> Result<void>;
|
||||
|
||||
auto get_control_block() -> ControlBlock *;
|
||||
|
||||
[[nodiscard]] auto is_valid() const -> bool;
|
||||
|
||||
protected:
|
||||
RingBufferView(Span<u8> buffer, bool is_owner);
|
||||
RingBufferView(ControlBlock *control_block, Span<u8> buffer, bool is_owner);
|
||||
RingBufferView(Ref<Span<u8>> buffer, Const<bool> is_owner);
|
||||
RingBufferView(Const<ControlBlock *> control_block, Ref<Span<u8>> buffer,
|
||||
Const<bool> is_owner);
|
||||
|
||||
private:
|
||||
u8 *m_data_ptr{};
|
||||
u32 m_capacity{};
|
||||
ControlBlock *m_control_block{};
|
||||
Mut<u8 *> m_data_ptr{};
|
||||
Mut<u32> m_capacity{};
|
||||
Mut<ControlBlock *> m_control_block{};
|
||||
|
||||
private:
|
||||
auto write_wrapped(u32 offset, const void *data, u32 size) -> void;
|
||||
auto read_wrapped(u32 offset, void *out_data, u32 size) -> void;
|
||||
auto write_wrapped(Const<u32> offset, Const<const void *> data,
|
||||
Const<u32> size) -> void;
|
||||
auto read_wrapped(Const<u32> offset, Const<void *> out_data, Const<u32> size)
|
||||
-> void;
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// Implementation
|
||||
// =============================================================================
|
||||
|
||||
inline auto RingBufferView::default_instance() -> RingBufferView {
|
||||
return RingBufferView(nullptr, {}, false);
|
||||
}
|
||||
|
||||
inline auto RingBufferView::create(Span<u8> buffer, bool is_owner)
|
||||
inline auto RingBufferView::create(Ref<Span<u8>> buffer, Const<bool> is_owner)
|
||||
-> Result<RingBufferView> {
|
||||
if (buffer.size() <= sizeof(ControlBlock)) {
|
||||
return fail("Buffer too small for ControlBlock");
|
||||
}
|
||||
|
||||
if (!is_owner) {
|
||||
auto *cb = reinterpret_cast<ControlBlock *>(buffer.data());
|
||||
u32 capacity = static_cast<u32>(buffer.size()) - sizeof(ControlBlock);
|
||||
Const<ControlBlock *> cb = reinterpret_cast<ControlBlock *>(buffer.data());
|
||||
Const<u32> capacity =
|
||||
static_cast<u32>(buffer.size()) - sizeof(ControlBlock);
|
||||
if (cb->consumer.capacity != capacity) {
|
||||
return fail("Capacity mismatch");
|
||||
}
|
||||
@ -114,8 +108,9 @@ inline auto RingBufferView::create(Span<u8> buffer, bool is_owner)
|
||||
return RingBufferView(buffer, is_owner);
|
||||
}
|
||||
|
||||
inline auto RingBufferView::create(ControlBlock *control_block, Span<u8> buffer,
|
||||
bool is_owner) -> Result<RingBufferView> {
|
||||
inline auto RingBufferView::create(Const<ControlBlock *> control_block,
|
||||
Ref<Span<u8>> buffer, Const<bool> is_owner)
|
||||
-> Result<RingBufferView> {
|
||||
if (control_block == nullptr) {
|
||||
return fail("ControlBlock is null");
|
||||
}
|
||||
@ -126,7 +121,8 @@ inline auto RingBufferView::create(ControlBlock *control_block, Span<u8> buffer,
|
||||
return RingBufferView(control_block, buffer, is_owner);
|
||||
}
|
||||
|
||||
inline RingBufferView::RingBufferView(Span<u8> buffer, bool is_owner) {
|
||||
inline RingBufferView::RingBufferView(Ref<Span<u8>> buffer,
|
||||
Const<bool> is_owner) {
|
||||
m_control_block = reinterpret_cast<ControlBlock *>(buffer.data());
|
||||
m_data_ptr = buffer.data() + sizeof(ControlBlock);
|
||||
|
||||
@ -139,8 +135,9 @@ inline RingBufferView::RingBufferView(Span<u8> buffer, bool is_owner) {
|
||||
}
|
||||
}
|
||||
|
||||
inline RingBufferView::RingBufferView(ControlBlock *control_block,
|
||||
Span<u8> buffer, bool is_owner) {
|
||||
inline RingBufferView::RingBufferView(Const<ControlBlock *> control_block,
|
||||
Ref<Span<u8>> buffer,
|
||||
Const<bool> is_owner) {
|
||||
m_control_block = control_block;
|
||||
m_data_ptr = buffer.data();
|
||||
m_capacity = static_cast<u32>(buffer.size());
|
||||
@ -152,13 +149,14 @@ inline RingBufferView::RingBufferView(ControlBlock *control_block,
|
||||
}
|
||||
}
|
||||
|
||||
inline auto RingBufferView::pop(PacketHeader &out_header, Span<u8> out_buffer)
|
||||
inline auto RingBufferView::pop(MutRef<PacketHeader> out_header,
|
||||
Ref<Span<u8>> out_buffer)
|
||||
-> Result<Option<usize>> {
|
||||
u32 write =
|
||||
Const<u32> write =
|
||||
m_control_block->producer.write_offset.load(std::memory_order_acquire);
|
||||
u32 read =
|
||||
Const<u32> read =
|
||||
m_control_block->consumer.read_offset.load(std::memory_order_relaxed);
|
||||
u32 cap = m_capacity;
|
||||
Const<u32> cap = m_capacity;
|
||||
|
||||
if (read == write) {
|
||||
return std::nullopt;
|
||||
@ -172,12 +170,11 @@ inline auto RingBufferView::pop(PacketHeader &out_header, Span<u8> out_buffer)
|
||||
}
|
||||
|
||||
if (out_header.payload_size > 0) {
|
||||
u32 data_read_offset = (read + sizeof(PacketHeader)) % cap;
|
||||
Const<u32> data_read_offset = (read + sizeof(PacketHeader)) % cap;
|
||||
read_wrapped(data_read_offset, out_buffer.data(), out_header.payload_size);
|
||||
}
|
||||
|
||||
// Move read pointer forward
|
||||
u32 new_read_offset =
|
||||
Const<u32> new_read_offset =
|
||||
(read + sizeof(PacketHeader) + out_header.payload_size) % cap;
|
||||
m_control_block->consumer.read_offset.store(new_read_offset,
|
||||
std::memory_order_release);
|
||||
@ -185,39 +182,39 @@ inline auto RingBufferView::pop(PacketHeader &out_header, Span<u8> out_buffer)
|
||||
return std::make_optional(static_cast<usize>(out_header.payload_size));
|
||||
}
|
||||
|
||||
inline auto RingBufferView::push(u16 packet_id, Span<const u8> data)
|
||||
inline auto RingBufferView::push(Const<u16> packet_id, Ref<Span<const u8>> data)
|
||||
-> Result<void> {
|
||||
if (data.size() > std::numeric_limits<u16>::max()) {
|
||||
return fail("Data size exceeds u16 limit");
|
||||
}
|
||||
|
||||
const u32 total_size = sizeof(PacketHeader) + static_cast<u32>(data.size());
|
||||
Const<u32> total_size = sizeof(PacketHeader) + static_cast<u32>(data.size());
|
||||
|
||||
u32 read =
|
||||
Const<u32> read =
|
||||
m_control_block->consumer.read_offset.load(std::memory_order_acquire);
|
||||
u32 write =
|
||||
Const<u32> write =
|
||||
m_control_block->producer.write_offset.load(std::memory_order_relaxed);
|
||||
u32 cap = m_capacity;
|
||||
Const<u32> cap = m_capacity;
|
||||
|
||||
u32 free_space =
|
||||
Const<u32> free_space =
|
||||
(read <= write) ? (m_capacity - write) + read : (read - write);
|
||||
|
||||
// Ensure to always leave 1 byte empty to prevent Read == Write ambiguity
|
||||
// Leave 1 byte empty (prevent ambiguities)
|
||||
if (free_space <= total_size) {
|
||||
return fail("RingBuffer full");
|
||||
}
|
||||
|
||||
PacketHeader header{packet_id, static_cast<u16>(data.size())};
|
||||
Const<PacketHeader> header{packet_id, static_cast<u16>(data.size())};
|
||||
write_wrapped(write, &header, sizeof(PacketHeader));
|
||||
|
||||
u32 data_write_offset = (write + sizeof(PacketHeader)) % cap;
|
||||
Const<u32> data_write_offset = (write + sizeof(PacketHeader)) % cap;
|
||||
|
||||
if (!data.empty()) {
|
||||
write_wrapped(data_write_offset, data.data(),
|
||||
static_cast<u32>(data.size()));
|
||||
}
|
||||
|
||||
u32 new_write_offset = (data_write_offset + data.size()) % cap;
|
||||
Const<u32> new_write_offset = (data_write_offset + data.size()) % cap;
|
||||
m_control_block->producer.write_offset.store(new_write_offset,
|
||||
std::memory_order_release);
|
||||
|
||||
@ -228,34 +225,32 @@ inline auto RingBufferView::get_control_block() -> ControlBlock * {
|
||||
return m_control_block;
|
||||
}
|
||||
|
||||
inline auto RingBufferView::write_wrapped(u32 offset, const void *data,
|
||||
u32 size) -> void {
|
||||
inline auto RingBufferView::write_wrapped(Const<u32> offset,
|
||||
Const<const void *> data,
|
||||
Const<u32> size) -> void {
|
||||
if (offset + size <= m_capacity) {
|
||||
// Contiguous write
|
||||
std::memcpy(m_data_ptr + offset, data, size);
|
||||
} else {
|
||||
// Split write
|
||||
u32 first_chunk = m_capacity - offset;
|
||||
u32 second_chunk = size - first_chunk;
|
||||
Const<u32> first_chunk = m_capacity - offset;
|
||||
Const<u32> second_chunk = size - first_chunk;
|
||||
|
||||
const u8 *src = static_cast<const u8 *>(data);
|
||||
Const<const u8 *> src = static_cast<const u8 *>(data);
|
||||
|
||||
std::memcpy(m_data_ptr + offset, src, first_chunk);
|
||||
std::memcpy(m_data_ptr, src + first_chunk, second_chunk);
|
||||
}
|
||||
}
|
||||
|
||||
inline auto RingBufferView::read_wrapped(u32 offset, void *out_data, u32 size)
|
||||
-> void {
|
||||
inline auto RingBufferView::read_wrapped(Const<u32> offset,
|
||||
Const<void *> out_data,
|
||||
Const<u32> size) -> void {
|
||||
if (offset + size <= m_capacity) {
|
||||
// Contiguous read
|
||||
std::memcpy(out_data, m_data_ptr + offset, size);
|
||||
} else {
|
||||
// Split read
|
||||
u32 first_chunk = m_capacity - offset;
|
||||
u32 second_chunk = size - first_chunk;
|
||||
Const<u32> first_chunk = m_capacity - offset;
|
||||
Const<u32> second_chunk = size - first_chunk;
|
||||
|
||||
u8 *dst = static_cast<u8 *>(out_data);
|
||||
Const<u8 *> dst = static_cast<u8 *>(out_data);
|
||||
|
||||
std::memcpy(dst, m_data_ptr + offset, first_chunk);
|
||||
std::memcpy(dst + first_chunk, m_data_ptr, second_chunk);
|
||||
|
||||
@ -26,45 +26,46 @@ public:
|
||||
using TaskTag = u64;
|
||||
using WorkerId = u16;
|
||||
|
||||
static constexpr WorkerId MAIN_THREAD_WORKER_ID = 0;
|
||||
static constexpr Const<WorkerId> MAIN_THREAD_WORKER_ID = 0;
|
||||
|
||||
enum class Priority : u8 { High, Normal };
|
||||
|
||||
struct Schedule {
|
||||
std::atomic<i32> counter{0};
|
||||
Mut<std::atomic<i32>> counter{0};
|
||||
};
|
||||
|
||||
public:
|
||||
static auto initialize_scheduler(u8 worker_count = 0) -> Result<void>;
|
||||
static auto initialize_scheduler(Const<u8> worker_count = 0) -> Result<void>;
|
||||
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 schedule_task(Mut<std::function<void(Const<WorkerId>)>> task,
|
||||
Const<TaskTag> tag, Mut<Schedule *> schedule,
|
||||
Const<Priority> priority = Priority::Normal)
|
||||
-> void;
|
||||
|
||||
static auto cancel_tasks_of_tag(TaskTag tag) -> void;
|
||||
static auto cancel_tasks_of_tag(Const<TaskTag> tag) -> void;
|
||||
|
||||
static auto wait_for_schedule_completion(Schedule *schedule) -> void;
|
||||
static auto wait_for_schedule_completion(Mut<Schedule *> schedule) -> void;
|
||||
|
||||
static auto run_task(std::function<void()> task) -> void;
|
||||
static auto run_task(Mut<std::function<void()>> task) -> void;
|
||||
|
||||
[[nodiscard]] static auto get_worker_count() -> WorkerId;
|
||||
IA_NODISCARD static auto get_worker_count() -> WorkerId;
|
||||
|
||||
private:
|
||||
struct ScheduledTask {
|
||||
TaskTag tag{};
|
||||
Schedule *schedule_handle{};
|
||||
std::function<void(WorkerId worker_id)> task{};
|
||||
Mut<TaskTag> tag{};
|
||||
Mut<Schedule *> schedule_handle{};
|
||||
Mut<std::function<void(Const<WorkerId>)>> task{};
|
||||
};
|
||||
|
||||
static auto schedule_worker_loop(std::stop_token stop_token,
|
||||
WorkerId worker_id) -> void;
|
||||
static auto schedule_worker_loop(Mut<std::stop_token> stop_token,
|
||||
Const<WorkerId> worker_id) -> void;
|
||||
|
||||
private:
|
||||
static std::mutex s_queue_mutex;
|
||||
static std::condition_variable s_wake_condition;
|
||||
static Vec<std::jthread> s_schedule_workers;
|
||||
static std::deque<ScheduledTask> s_high_priority_queue;
|
||||
static std::deque<ScheduledTask> s_normal_priority_queue;
|
||||
static Mut<std::mutex> s_queue_mutex;
|
||||
static Mut<std::condition_variable> s_wake_condition;
|
||||
static Mut<Vec<std::jthread>> s_schedule_workers;
|
||||
static Mut<std::deque<ScheduledTask>> s_high_priority_queue;
|
||||
static Mut<std::deque<ScheduledTask>> s_normal_priority_queue;
|
||||
};
|
||||
} // namespace IACore
|
||||
@ -28,15 +28,15 @@ class CLIParser {
|
||||
*/
|
||||
|
||||
public:
|
||||
CLIParser(Span<const String> args);
|
||||
CLIParser(Const<Span<Const<String>>> args);
|
||||
~CLIParser() = default;
|
||||
|
||||
public:
|
||||
[[nodiscard]] auto remaining() const -> bool {
|
||||
IA_NODISCARD auto remaining() const -> bool {
|
||||
return m_current_arg < m_arg_list.end();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto peek() const -> StringView {
|
||||
IA_NODISCARD auto peek() const -> StringView {
|
||||
if (!remaining())
|
||||
return "";
|
||||
return *m_current_arg;
|
||||
@ -48,7 +48,7 @@ public:
|
||||
return *m_current_arg++;
|
||||
}
|
||||
|
||||
auto consume(const StringView &expected) -> bool {
|
||||
auto consume(Ref<StringView> expected) -> bool {
|
||||
if (peek() == expected) {
|
||||
next();
|
||||
return true;
|
||||
@ -57,7 +57,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
const Span<const String> m_arg_list;
|
||||
Span<const String>::const_iterator m_current_arg;
|
||||
Const<Span<Const<String>>> m_arg_list;
|
||||
Mut<Span<Const<String>>::const_iterator> m_current_arg;
|
||||
};
|
||||
} // namespace IACore
|
||||
@ -23,23 +23,25 @@ public:
|
||||
enum class CompressionType { None, Gzip, Zlib };
|
||||
|
||||
public:
|
||||
static auto hash_fnv1a(const String &string) -> u32;
|
||||
static auto hash_fnv1a(Span<const u8> data) -> u32;
|
||||
static auto hash_fnv1a(Ref<String> string) -> u32;
|
||||
static auto hash_fnv1a(Ref<Span<Const<u8>>> data) -> u32;
|
||||
|
||||
static auto hash_xxhash(const String &string, u32 seed = 0) -> u32;
|
||||
static auto hash_xxhash(Span<const u8> data, u32 seed = 0) -> u32;
|
||||
static auto hash_xxhash(Ref<String> string, Const<u32> seed = 0) -> u32;
|
||||
static auto hash_xxhash(Ref<Span<Const<u8>>> data, Const<u32> seed = 0)
|
||||
-> u32;
|
||||
|
||||
static auto crc32(Span<const u8> data) -> u32;
|
||||
static auto crc32(Ref<Span<Const<u8>>> data) -> u32;
|
||||
|
||||
static auto detect_compression(Span<const u8> data) -> CompressionType;
|
||||
static auto detect_compression(Const<Span<Const<u8>>> data)
|
||||
-> CompressionType;
|
||||
|
||||
static auto gzip_inflate(Span<const u8> data) -> Result<Vec<u8>>;
|
||||
static auto gzip_deflate(Span<const u8> data) -> Result<Vec<u8>>;
|
||||
static auto gzip_inflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>>;
|
||||
static auto gzip_deflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>>;
|
||||
|
||||
static auto zlib_inflate(Span<const u8> data) -> Result<Vec<u8>>;
|
||||
static auto zlib_deflate(Span<const u8> data) -> Result<Vec<u8>>;
|
||||
static auto zlib_inflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>>;
|
||||
static auto zlib_deflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>>;
|
||||
|
||||
static auto zstd_inflate(Span<const u8> data) -> Result<Vec<u8>>;
|
||||
static auto zstd_deflate(Span<const u8> data) -> Result<Vec<u8>>;
|
||||
static auto zstd_inflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>>;
|
||||
static auto zstd_deflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>>;
|
||||
};
|
||||
} // namespace IACore
|
||||
@ -21,18 +21,14 @@
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
// Ensure 'ia' namespace alias exists for IA_TRY macro usage
|
||||
namespace ia = IACore;
|
||||
|
||||
namespace IACore {
|
||||
|
||||
class DynamicLib {
|
||||
public:
|
||||
// Factory method to load a dynamic library
|
||||
[[nodiscard]] static auto load(const String &search_path, const String &name)
|
||||
IA_NODISCARD static auto load(Ref<String> search_path, Ref<String> name)
|
||||
-> Result<DynamicLib> {
|
||||
namespace fs = std::filesystem;
|
||||
auto full_path = fs::path(search_path) / name;
|
||||
Mut<Path> full_path = fs::path(search_path) / name;
|
||||
|
||||
if (!full_path.has_extension()) {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
@ -44,19 +40,18 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
DynamicLib lib;
|
||||
Mut<DynamicLib> lib;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
const HMODULE h = LoadLibraryA(full_path.string().c_str());
|
||||
Const<HMODULE> h = LoadLibraryA(full_path.string().c_str());
|
||||
if (!h) {
|
||||
return fail(get_windows_error());
|
||||
}
|
||||
lib.m_handle = static_cast<void *>(h);
|
||||
#else
|
||||
// RTLD_LAZY: Resolve symbols only as code executes (Standard for plugins)
|
||||
void *h = dlopen(full_path.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
||||
Mut<void *> h = dlopen(full_path.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
||||
if (!h) {
|
||||
const char *err = dlerror();
|
||||
Const<char *> err = dlerror();
|
||||
return fail(err ? err : "Unknown dlopen error");
|
||||
}
|
||||
lib.m_handle = h;
|
||||
@ -67,11 +62,11 @@ public:
|
||||
|
||||
DynamicLib() = default;
|
||||
|
||||
DynamicLib(DynamicLib &&other) noexcept : m_handle(other.m_handle) {
|
||||
DynamicLib(ForwardRef<DynamicLib> other) noexcept : m_handle(other.m_handle) {
|
||||
other.m_handle = nullptr;
|
||||
}
|
||||
|
||||
auto operator=(DynamicLib &&other) noexcept -> DynamicLib & {
|
||||
auto operator=(ForwardRef<DynamicLib> other) noexcept -> MutRef<DynamicLib> {
|
||||
if (this != &other) {
|
||||
unload();
|
||||
m_handle = other.m_handle;
|
||||
@ -80,17 +75,17 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
DynamicLib(const DynamicLib &) = delete;
|
||||
auto operator=(const DynamicLib &) -> DynamicLib & = delete;
|
||||
DynamicLib(Ref<DynamicLib>) = delete;
|
||||
auto operator=(Ref<DynamicLib>) -> MutRef<DynamicLib> = delete;
|
||||
|
||||
~DynamicLib() { unload(); }
|
||||
|
||||
[[nodiscard]] auto get_symbol(const String &name) const -> Result<void *> {
|
||||
IA_NODISCARD auto get_symbol(Ref<String> name) const -> Result<void *> {
|
||||
if (!m_handle) {
|
||||
return fail("Library not loaded");
|
||||
}
|
||||
|
||||
void *sym = nullptr;
|
||||
Mut<void *> sym = nullptr;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
sym = static_cast<void *>(
|
||||
@ -99,10 +94,9 @@ public:
|
||||
return fail(get_windows_error());
|
||||
}
|
||||
#else
|
||||
// Clear any previous error
|
||||
dlerror();
|
||||
dlerror(); // Clear prev errors
|
||||
sym = dlsym(m_handle, name.c_str());
|
||||
if (const char *err = dlerror()) {
|
||||
if (Const<char *> err = dlerror()) {
|
||||
return fail(err);
|
||||
}
|
||||
#endif
|
||||
@ -111,9 +105,9 @@ public:
|
||||
}
|
||||
|
||||
template <typename FuncT>
|
||||
[[nodiscard]] auto get_function(const String &name) const -> Result<FuncT> {
|
||||
void *sym = nullptr;
|
||||
IA_TRY(sym, get_symbol(name));
|
||||
IA_NODISCARD auto get_function(Ref<String> name) const -> Result<FuncT> {
|
||||
Mut<void *> sym = nullptr;
|
||||
sym = OX_TRY(get_symbol(name));
|
||||
return reinterpret_cast<FuncT>(sym);
|
||||
}
|
||||
|
||||
@ -128,26 +122,26 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto is_loaded() const -> bool { return m_handle != nullptr; }
|
||||
IA_NODISCARD auto is_loaded() const -> bool { return m_handle != nullptr; }
|
||||
|
||||
private:
|
||||
void *m_handle = nullptr;
|
||||
Mut<void *> m_handle = nullptr;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
static auto get_windows_error() -> String {
|
||||
const DWORD error_id = ::GetLastError();
|
||||
Const<DWORD> error_id = ::GetLastError();
|
||||
if (error_id == 0) {
|
||||
return String();
|
||||
}
|
||||
|
||||
LPSTR message_buffer = nullptr;
|
||||
const usize size = FormatMessageA(
|
||||
Mut<LPSTR> message_buffer = nullptr;
|
||||
Const<usize> size = FormatMessageA(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, error_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
reinterpret_cast<LPSTR>(&message_buffer), 0, nullptr);
|
||||
|
||||
String message(message_buffer, size);
|
||||
Const<String> message(message_buffer, size);
|
||||
LocalFree(message_buffer);
|
||||
return "Win32 Error: " + message;
|
||||
}
|
||||
|
||||
@ -19,32 +19,33 @@
|
||||
#include <cstdlib>
|
||||
|
||||
namespace IACore {
|
||||
|
||||
class Environment {
|
||||
public:
|
||||
static auto find(const String &name) -> Option<String> {
|
||||
static auto find(Ref<String> name) -> Option<String> {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
const DWORD buffer_size = GetEnvironmentVariableA(name.c_str(), nullptr, 0);
|
||||
Const<u32> buffer_size =
|
||||
static_cast<u32>(GetEnvironmentVariableA(name.c_str(), nullptr, 0));
|
||||
|
||||
if (buffer_size == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
String result;
|
||||
Mut<String> result;
|
||||
result.resize(buffer_size);
|
||||
|
||||
const DWORD actual_size =
|
||||
GetEnvironmentVariableA(name.c_str(), result.data(), buffer_size);
|
||||
Const<u32> actual_size = static_cast<u32>(
|
||||
GetEnvironmentVariableA(name.c_str(), result.data(), buffer_size));
|
||||
|
||||
if (actual_size == 0 || actual_size > buffer_size) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Resize down to exclude the null terminator and any slack
|
||||
result.resize(actual_size);
|
||||
return result;
|
||||
|
||||
#else
|
||||
const char *val = std::getenv(name.c_str());
|
||||
Const<char *> val = std::getenv(name.c_str());
|
||||
if (val == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
@ -52,12 +53,11 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
static auto get(const String &name, const String &default_value = "")
|
||||
-> String {
|
||||
static auto get(Ref<String> name, Ref<String> default_value = "") -> String {
|
||||
return find(name).value_or(default_value);
|
||||
}
|
||||
|
||||
static auto set(const String &name, const String &value) -> Result<void> {
|
||||
static auto set(Ref<String> name, Ref<String> value) -> Result<void> {
|
||||
if (name.empty()) {
|
||||
return fail("Environment variable name cannot be empty");
|
||||
}
|
||||
@ -74,7 +74,7 @@ public:
|
||||
return {};
|
||||
}
|
||||
|
||||
static auto unset(const String &name) -> Result<void> {
|
||||
static auto unset(Ref<String> name) -> Result<void> {
|
||||
if (name.empty()) {
|
||||
return fail("Environment variable name cannot be empty");
|
||||
}
|
||||
@ -91,8 +91,9 @@ public:
|
||||
return {};
|
||||
}
|
||||
|
||||
static auto exists(const String &name) -> bool {
|
||||
static auto exists(Ref<String> name) -> bool {
|
||||
return find(name).has_value();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace IACore
|
||||
@ -22,10 +22,11 @@
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
using NativeFileHandle = HANDLE;
|
||||
static constexpr NativeFileHandle INVALID_FILE_HANDLE = INVALID_HANDLE_VALUE;
|
||||
static constexpr ox::Const<NativeFileHandle> INVALID_FILE_HANDLE =
|
||||
INVALID_HANDLE_VALUE;
|
||||
#else
|
||||
using NativeFileHandle = int;
|
||||
static constexpr NativeFileHandle INVALID_FILE_HANDLE = -1;
|
||||
static constexpr ox::Const<NativeFileHandle> INVALID_FILE_HANDLE = -1;
|
||||
#endif
|
||||
|
||||
namespace IACore {
|
||||
@ -48,43 +49,46 @@ public:
|
||||
TruncateExisting // Opens existing and clears it
|
||||
};
|
||||
|
||||
static auto native_open_file(const Path &path, FileAccess access,
|
||||
FileMode mode, u32 permissions = 0644)
|
||||
static auto native_open_file(Ref<Path> path, Const<FileAccess> access,
|
||||
Const<FileMode> mode,
|
||||
Const<u32> permissions = 0644)
|
||||
-> Result<NativeFileHandle>;
|
||||
|
||||
static auto native_close_file(NativeFileHandle handle) -> void;
|
||||
static auto native_close_file(Const<NativeFileHandle> handle) -> void;
|
||||
|
||||
public:
|
||||
static auto normalize_executable_path(const Path &path) -> Path;
|
||||
static auto normalize_executable_path(Ref<Path> path) -> Path;
|
||||
|
||||
public:
|
||||
static auto unmap_file(const u8 *mapped_ptr) -> void;
|
||||
static auto unmap_file(Const<const u8 *> mapped_ptr) -> void;
|
||||
|
||||
static auto map_file(const Path &path, usize &size) -> Result<const u8 *>;
|
||||
static auto map_file(Ref<Path> path, MutRef<usize> size)
|
||||
-> Result<const u8 *>;
|
||||
|
||||
// @param `is_owner` true to allocate/truncate. false to just open.
|
||||
static auto map_shared_memory(const String &name, usize size, bool is_owner)
|
||||
-> Result<u8 *>;
|
||||
static auto map_shared_memory(Ref<String> name, Const<usize> size,
|
||||
Const<bool> is_owner) -> Result<u8 *>;
|
||||
|
||||
static auto unlink_shared_memory(const String &name) -> void;
|
||||
static auto unlink_shared_memory(Ref<String> name) -> void;
|
||||
|
||||
static auto stream_from_file(const Path &path) -> Result<StreamReader>;
|
||||
static auto stream_from_file(Ref<Path> path) -> Result<StreamReader>;
|
||||
|
||||
static auto stream_to_file(const Path &path, bool overwrite = false)
|
||||
static auto stream_to_file(Ref<Path> path, Const<bool> overwrite = false)
|
||||
-> Result<StreamWriter>;
|
||||
|
||||
static auto read_text_file(const Path &path) -> Result<String>;
|
||||
static auto read_text_file(Ref<Path> path) -> Result<String>;
|
||||
|
||||
static auto read_binary_file(const Path &path) -> Result<Vec<u8>>;
|
||||
static auto read_binary_file(Ref<Path> path) -> Result<Vec<u8>>;
|
||||
|
||||
static auto write_text_file(const Path &path, const String &contents,
|
||||
bool overwrite = false) -> Result<usize>;
|
||||
static auto write_text_file(Ref<Path> path, Ref<String> contents,
|
||||
Const<bool> overwrite = false) -> Result<usize>;
|
||||
|
||||
static auto write_binary_file(const Path &path, Span<const u8> contents,
|
||||
bool overwrite = false) -> Result<usize>;
|
||||
static auto write_binary_file(Ref<Path> path, Const<Span<const u8>> contents,
|
||||
Const<bool> overwrite = false) -> Result<usize>;
|
||||
|
||||
private:
|
||||
static HashMap<const u8 *, std::tuple<void *, void *, void *>> s_mapped_files;
|
||||
static Mut<HashMap<const u8 *, std::tuple<void *, void *, void *>>>
|
||||
s_mapped_files;
|
||||
};
|
||||
|
||||
class FileOps::MemoryMappedRegion {
|
||||
@ -92,13 +96,15 @@ public:
|
||||
MemoryMappedRegion() = default;
|
||||
~MemoryMappedRegion();
|
||||
|
||||
MemoryMappedRegion(const MemoryMappedRegion &) = delete;
|
||||
auto operator=(const MemoryMappedRegion &) -> MemoryMappedRegion & = delete;
|
||||
MemoryMappedRegion(Ref<MemoryMappedRegion>) = delete;
|
||||
auto operator=(Ref<MemoryMappedRegion>) -> MemoryMappedRegion & = delete;
|
||||
|
||||
MemoryMappedRegion(MemoryMappedRegion &&other) noexcept;
|
||||
auto operator=(MemoryMappedRegion &&other) noexcept -> MemoryMappedRegion &;
|
||||
MemoryMappedRegion(ForwardRef<MemoryMappedRegion> other) noexcept;
|
||||
auto operator=(ForwardRef<MemoryMappedRegion> other) noexcept
|
||||
-> MemoryMappedRegion &;
|
||||
|
||||
auto map(NativeFileHandle handle, u64 offset, usize size) -> Result<void>;
|
||||
auto map(Const<NativeFileHandle> handle, Const<u64> offset, Const<usize> size)
|
||||
-> Result<void>;
|
||||
|
||||
auto unmap() -> void;
|
||||
auto flush() -> void;
|
||||
@ -110,11 +116,11 @@ public:
|
||||
[[nodiscard]] auto is_valid() const -> bool { return m_ptr != nullptr; }
|
||||
|
||||
private:
|
||||
u8 *m_ptr = nullptr;
|
||||
usize m_size = 0;
|
||||
Mut<u8 *> m_ptr = nullptr;
|
||||
Mut<usize> m_size = 0;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
HANDLE m_map_handle = NULL;
|
||||
Mut<HANDLE> m_map_handle = NULL;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@ -21,56 +21,55 @@
|
||||
namespace IACore {
|
||||
class HttpClient : public HttpCommon {
|
||||
public:
|
||||
static auto create(const String &host) -> Result<Box<HttpClient>>;
|
||||
static auto create(Ref<String> host) -> Result<Box<HttpClient>>;
|
||||
|
||||
~HttpClient();
|
||||
|
||||
HttpClient(HttpClient &&) = default;
|
||||
HttpClient(const HttpClient &) = delete;
|
||||
auto operator=(HttpClient &&) -> HttpClient & = default;
|
||||
auto operator=(const HttpClient &) -> HttpClient & = delete;
|
||||
HttpClient(ForwardRef<HttpClient>) = default;
|
||||
HttpClient(Ref<HttpClient>) = delete;
|
||||
auto operator=(ForwardRef<HttpClient>) -> MutRef<HttpClient> = default;
|
||||
auto operator=(Ref<HttpClient>) -> MutRef<HttpClient> = delete;
|
||||
|
||||
public:
|
||||
auto raw_get(const String &path, Span<const Header> headers,
|
||||
const char *default_content_type =
|
||||
auto raw_get(Ref<String> path, Span<Const<Header>> headers,
|
||||
Const<char> *default_content_type =
|
||||
"application/x-www-form-urlencoded") -> Result<String>;
|
||||
|
||||
auto raw_post(
|
||||
const String &path, Span<const Header> headers, const String &body,
|
||||
const char *default_content_type = "application/x-www-form-urlencoded")
|
||||
-> Result<String>;
|
||||
auto raw_post(Ref<String> path, Span<Const<Header>> headers, Ref<String> body,
|
||||
Const<char> *default_content_type =
|
||||
"application/x-www-form-urlencoded") -> Result<String>;
|
||||
|
||||
template <typename ResponseType>
|
||||
auto json_get(const String &path, Span<const Header> headers)
|
||||
auto json_get(Ref<String> path, Span<Const<Header>> headers)
|
||||
-> Result<ResponseType>;
|
||||
|
||||
template <typename PayloadType, typename ResponseType>
|
||||
auto json_post(const String &path, Span<const Header> headers,
|
||||
const PayloadType &body) -> Result<ResponseType>;
|
||||
auto json_post(Ref<String> path, Span<Const<Header>> headers,
|
||||
Ref<PayloadType> body) -> Result<ResponseType>;
|
||||
|
||||
// Certificate verification is enabled by default
|
||||
void enable_certificate_verification();
|
||||
void disable_certificate_verification();
|
||||
auto enable_certificate_verification() -> void;
|
||||
auto disable_certificate_verification() -> void;
|
||||
|
||||
public:
|
||||
auto last_response_code() -> EResponseCode { return m_last_response_code; }
|
||||
|
||||
private:
|
||||
httplib::Client m_client;
|
||||
EResponseCode m_last_response_code;
|
||||
Mut<httplib::Client> m_client;
|
||||
Mut<EResponseCode> m_last_response_code;
|
||||
|
||||
private:
|
||||
auto preprocess_response(const String &response) -> String;
|
||||
auto preprocess_response(Ref<String> response) -> String;
|
||||
|
||||
protected:
|
||||
explicit HttpClient(httplib::Client &&client);
|
||||
explicit HttpClient(ForwardRef<httplib::Client> client);
|
||||
};
|
||||
|
||||
template <typename ResponseType>
|
||||
auto HttpClient::json_get(const String &path, Span<const Header> headers)
|
||||
auto HttpClient::json_get(Ref<String> path, Span<Const<Header>> headers)
|
||||
-> Result<ResponseType> {
|
||||
String raw_response;
|
||||
IA_TRY(raw_response, raw_get(path, headers, "application/json"));
|
||||
Const<String> raw_response =
|
||||
OX_TRY(raw_get(path, headers, "application/json"));
|
||||
|
||||
if (last_response_code() != EResponseCode::OK) {
|
||||
return fail("Server responded with code {}",
|
||||
@ -80,14 +79,12 @@ auto HttpClient::json_get(const String &path, Span<const Header> headers)
|
||||
}
|
||||
|
||||
template <typename PayloadType, typename ResponseType>
|
||||
auto HttpClient::json_post(const String &path, Span<const Header> headers,
|
||||
const PayloadType &body) -> Result<ResponseType> {
|
||||
String encoded_body;
|
||||
IA_TRY(encoded_body, Json::encode_struct(body));
|
||||
auto HttpClient::json_post(Ref<String> path, Span<Const<Header>> headers,
|
||||
Ref<PayloadType> body) -> Result<ResponseType> {
|
||||
Const<String> encoded_body = OX_TRY(Json::encode_struct(body));
|
||||
|
||||
String raw_response;
|
||||
IA_TRY(raw_response,
|
||||
raw_post(path, headers, encoded_body, "application/json"));
|
||||
Const<String> raw_response =
|
||||
OX_TRY(raw_post(path, headers, encoded_body, "application/json"));
|
||||
|
||||
if (last_response_code() != EResponseCode::OK) {
|
||||
return fail("Server responded with code {}",
|
||||
|
||||
@ -127,29 +127,29 @@ public:
|
||||
|
||||
using Header = Pair<String, String>;
|
||||
|
||||
static auto url_encode(const String &value) -> String;
|
||||
static auto url_decode(const String &value) -> String;
|
||||
static auto url_encode(Ref<String> value) -> String;
|
||||
static auto url_decode(Ref<String> value) -> String;
|
||||
|
||||
static auto header_type_to_string(EHeaderType type) -> String;
|
||||
static auto header_type_to_string(Const<EHeaderType> type) -> String;
|
||||
|
||||
static inline auto create_header(EHeaderType key, const String &value)
|
||||
static inline auto create_header(Const<EHeaderType> key, Ref<String> value)
|
||||
-> Header;
|
||||
static inline auto create_header(const String &key, const String &value)
|
||||
static inline auto create_header(Ref<String> key, Ref<String> value)
|
||||
-> Header;
|
||||
|
||||
static auto is_success_response_code(EResponseCode code) -> bool;
|
||||
static auto is_success_response_code(Const<EResponseCode> code) -> bool;
|
||||
|
||||
protected:
|
||||
HttpCommon() = default;
|
||||
};
|
||||
|
||||
auto HttpCommon::create_header(EHeaderType key, const String &value)
|
||||
auto HttpCommon::create_header(Const<EHeaderType> key, Ref<String> value)
|
||||
-> HttpCommon::Header {
|
||||
return std::make_pair(header_type_to_string(key), value);
|
||||
return Header{header_type_to_string(key), value};
|
||||
}
|
||||
|
||||
auto HttpCommon::create_header(const String &key, const String &value)
|
||||
auto HttpCommon::create_header(Ref<String> key, Ref<String> value)
|
||||
-> HttpCommon::Header {
|
||||
return std::make_pair(key, value);
|
||||
return Header{key, value};
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -23,34 +23,34 @@ namespace IACore {
|
||||
class HttpServer : public HttpCommon {
|
||||
public:
|
||||
struct Request {
|
||||
String path;
|
||||
String method;
|
||||
String body;
|
||||
HashMap<String, String> headers;
|
||||
HashMap<String, String> params; // Query parameters
|
||||
HashMap<String, String> path_params; // Path parameters (e.g. /users/:id)
|
||||
Mut<String> path;
|
||||
Mut<String> method;
|
||||
Mut<String> body;
|
||||
Mut<HashMap<String, String>> headers;
|
||||
Mut<HashMap<String, String>> params; // Query params
|
||||
Mut<HashMap<String, String>> path_params; // Path params (like /object/:id)
|
||||
|
||||
[[nodiscard]] auto get_header(const String &key) const -> String;
|
||||
[[nodiscard]] auto get_param(const String &key) const -> String;
|
||||
[[nodiscard]] auto get_path_param(const String &key) const -> String;
|
||||
[[nodiscard]] auto get_header(Ref<String> key) const -> String;
|
||||
[[nodiscard]] auto get_param(Ref<String> key) const -> String;
|
||||
[[nodiscard]] auto get_path_param(Ref<String> key) const -> String;
|
||||
|
||||
[[nodiscard]] auto has_header(const String &key) const -> bool;
|
||||
[[nodiscard]] auto has_param(const String &key) const -> bool;
|
||||
[[nodiscard]] auto has_path_param(const String &key) const -> bool;
|
||||
[[nodiscard]] auto has_header(Ref<String> key) const -> bool;
|
||||
[[nodiscard]] auto has_param(Ref<String> key) const -> bool;
|
||||
[[nodiscard]] auto has_path_param(Ref<String> key) const -> bool;
|
||||
};
|
||||
|
||||
struct Response {
|
||||
EResponseCode code = EResponseCode::OK;
|
||||
String body;
|
||||
HashMap<String, String> headers;
|
||||
String content_type = "text/plain";
|
||||
Mut<EResponseCode> code = EResponseCode::OK;
|
||||
Mut<String> body;
|
||||
Mut<HashMap<String, String>> headers;
|
||||
Mut<String> content_type = "text/plain";
|
||||
|
||||
void set_content(const String &content, const String &type);
|
||||
void set_status(EResponseCode status_code);
|
||||
void add_header(const String &key, const String &value);
|
||||
void set_content(Ref<String> content, Ref<String> type);
|
||||
void set_status(Const<EResponseCode> status_code);
|
||||
void add_header(Ref<String> key, Ref<String> value);
|
||||
};
|
||||
|
||||
using Handler = std::function<void(const Request &, Response &)>;
|
||||
using Handler = std::function<void(Ref<Request>, MutRef<Response>)>;
|
||||
|
||||
public:
|
||||
static auto create() -> Result<Box<HttpServer>>;
|
||||
@ -62,52 +62,49 @@ public:
|
||||
auto operator=(HttpServer &&) -> HttpServer & = delete;
|
||||
auto operator=(const HttpServer &) -> HttpServer & = delete;
|
||||
|
||||
auto listen(const String &host, u32 port) -> Result<void>;
|
||||
auto listen(Ref<String> host, Const<u32> port) -> Result<void>;
|
||||
void stop();
|
||||
auto is_running() const -> bool;
|
||||
|
||||
void get(const String &pattern, Handler handler);
|
||||
void post(const String &pattern, Handler handler);
|
||||
void put(const String &pattern, Handler handler);
|
||||
void del(const String &pattern, Handler handler);
|
||||
void options(const String &pattern, Handler handler);
|
||||
void get(Ref<String> pattern, Const<Handler> handler);
|
||||
void post(Ref<String> pattern, Const<Handler> handler);
|
||||
void put(Ref<String> pattern, Const<Handler> handler);
|
||||
void del(Ref<String> pattern, Const<Handler> handler);
|
||||
void options(Ref<String> pattern, Const<Handler> handler);
|
||||
|
||||
template <typename ResponseType>
|
||||
void json_get(const String &pattern,
|
||||
std::function<Result<ResponseType>(const Request &)> handler);
|
||||
void
|
||||
json_get(Ref<String> pattern,
|
||||
Const<std::function<Result<ResponseType>(Ref<Request>)>> handler);
|
||||
|
||||
template <typename PayloadType, typename ResponseType>
|
||||
void
|
||||
json_post(const String &pattern,
|
||||
std::function<Result<ResponseType>(const PayloadType &)> handler);
|
||||
void json_post(
|
||||
Ref<String> pattern,
|
||||
Const<std::function<Result<ResponseType>(Ref<PayloadType>)>> handler);
|
||||
|
||||
protected:
|
||||
HttpServer();
|
||||
|
||||
private:
|
||||
httplib::Server m_server;
|
||||
Mut<httplib::Server> m_server;
|
||||
|
||||
void register_handler(const String &method, const String &pattern,
|
||||
Handler handler);
|
||||
void register_handler(Ref<String> method, Ref<String> pattern,
|
||||
Const<Handler> handler);
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// Template Implementations
|
||||
// =============================================================================
|
||||
|
||||
template <typename ResponseType>
|
||||
void HttpServer::json_get(
|
||||
const String &pattern,
|
||||
std::function<Result<ResponseType>(const Request &)> handler) {
|
||||
get(pattern, [handler](const Request &req, Response &res) {
|
||||
auto result = handler(req);
|
||||
Ref<String> pattern,
|
||||
Const<std::function<Result<ResponseType>(Ref<Request>)>> handler) {
|
||||
get(pattern, [handler](Ref<Request> req, MutRef<Response> res) {
|
||||
Const<Result<ResponseType>> result = handler(req);
|
||||
if (!result) {
|
||||
res.set_status(EResponseCode::INTERNAL_SERVER_ERROR);
|
||||
res.set_content(result.error(), "text/plain");
|
||||
return;
|
||||
}
|
||||
|
||||
auto json_res = Json::encode_struct(*result);
|
||||
Const<Result<String>> json_res = Json::encode_struct(*result);
|
||||
if (!json_res) {
|
||||
res.set_status(EResponseCode::INTERNAL_SERVER_ERROR);
|
||||
res.set_content("Failed to encode JSON response", "text/plain");
|
||||
@ -121,24 +118,25 @@ void HttpServer::json_get(
|
||||
|
||||
template <typename PayloadType, typename ResponseType>
|
||||
void HttpServer::json_post(
|
||||
const String &pattern,
|
||||
std::function<Result<ResponseType>(const PayloadType &)> handler) {
|
||||
post(pattern, [handler](const Request &req, Response &res) {
|
||||
auto payload = Json::parse_to_struct<PayloadType>(req.body);
|
||||
Ref<String> pattern,
|
||||
Const<std::function<Result<ResponseType>(Ref<PayloadType>)>> handler) {
|
||||
post(pattern, [handler](Ref<Request> req, MutRef<Response> res) {
|
||||
Const<Result<PayloadType>> payload =
|
||||
Json::parse_to_struct<PayloadType>(req.body);
|
||||
if (!payload) {
|
||||
res.set_status(EResponseCode::BAD_REQUEST);
|
||||
res.set_content("Invalid JSON Payload", "text/plain");
|
||||
return;
|
||||
}
|
||||
|
||||
auto result = handler(*payload);
|
||||
Const<Result<ResponseType>> result = handler(*payload);
|
||||
if (!result) {
|
||||
res.set_status(EResponseCode::INTERNAL_SERVER_ERROR);
|
||||
res.set_content(result.error(), "text/plain");
|
||||
return;
|
||||
}
|
||||
|
||||
auto json_res = Json::encode_struct(*result);
|
||||
Const<Result<String>> json_res = Json::encode_struct(*result);
|
||||
if (!json_res) {
|
||||
res.set_status(EResponseCode::INTERNAL_SERVER_ERROR);
|
||||
res.set_content("Failed to encode JSON response", "text/plain");
|
||||
|
||||
@ -15,55 +15,52 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
#include <IACore/Logger.hpp>
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
#define IACORE_MAIN() \
|
||||
auto _app_entry(const IACore::Vec<IACore::String> &args) -> IACore::Result<IACore::i32>; \
|
||||
auto main(int argc, char *argv[]) -> int \
|
||||
{ \
|
||||
IACore::i32 exit_code = 0; \
|
||||
auto _app_entry(IACore::Ref<IACore::Vec<IACore::String>> args) \
|
||||
-> IACore::Result<IACore::i32>; \
|
||||
auto main(Const<int> argc, Mut<char *> argv[]) -> int { \
|
||||
IACore::Mut<IACore::i32> exit_code = 0; \
|
||||
IACore::initialize(); \
|
||||
IACore::Vec<IACore::String> args; \
|
||||
IACore::Mut<IACore::Vec<IACore::String>> args; \
|
||||
args.reserve(static_cast<IACore::usize>(argc)); \
|
||||
for (int i = 0; i < argc; ++i) \
|
||||
{ \
|
||||
for (IACore::Mut<IACore::i32> i = 0; i < argc; ++i) { \
|
||||
args.push_back(argv[i]); \
|
||||
} \
|
||||
const auto result = _app_entry(args); \
|
||||
if (!result) \
|
||||
{ \
|
||||
IACore::Logger::error("Application exited with an error: '{}'.", result.error()); \
|
||||
IACore::Const<IACore::Result<IACore::i32>> result = _app_entry(args); \
|
||||
if (!result) { \
|
||||
IACore::Logger::error("Application exited with an error: '{}'.", \
|
||||
result.error()); \
|
||||
exit_code = -20; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
} else { \
|
||||
exit_code = *result; \
|
||||
if (exit_code == 0) \
|
||||
{ \
|
||||
if (exit_code == 0) { \
|
||||
IACore::Logger::info("Application exited successfully."); \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
IACore::Logger::error("Application exited with error code: {}.", exit_code); \
|
||||
} 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>
|
||||
auto _app_entry(IACore::Ref<IACore::Vec<IACore::String>> args) \
|
||||
-> IACore::Result<IACore::i32>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
// Must be called from main thread
|
||||
// Safe to call multiple times but, every initialize call is paired with a corresponding terminate call
|
||||
auto initialize() -> void;
|
||||
namespace IACore {
|
||||
// Must be called from main thread
|
||||
// Safe to call multiple times but, given every initialize call is paired with a
|
||||
// corresponding terminate call
|
||||
auto initialize() -> void;
|
||||
|
||||
// Must be called from same thread as initialize
|
||||
// Safe to call multiple times but, every initialize call is paired with a corresponding terminate call
|
||||
auto terminate() -> void;
|
||||
// Must be called from same thread as initialize
|
||||
// Safe to call multiple times but, given every initialize call is paired with a
|
||||
// corresponding terminate call
|
||||
auto terminate() -> void;
|
||||
|
||||
auto is_initialized() -> bool;
|
||||
auto is_initialized() -> bool;
|
||||
|
||||
auto is_main_thread() -> bool;
|
||||
auto is_main_thread() -> bool;
|
||||
} // namespace IACore
|
||||
@ -55,7 +55,7 @@
|
||||
|
||||
#define IAT_BEGIN_TEST_LIST() \
|
||||
public: \
|
||||
void declare_tests() override {
|
||||
auto declare_tests() -> void override {
|
||||
#define IAT_ADD_TEST(name) IAT_UNIT(name)
|
||||
#define IAT_END_TEST_LIST() \
|
||||
} \
|
||||
@ -66,7 +66,7 @@ namespace IACore::Test {
|
||||
// -------------------------------------------------------------------------
|
||||
// String Conversion Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
template <typename T> auto to_string(const T &value) -> String {
|
||||
template <typename T> auto to_string(Ref<T> value) -> String {
|
||||
if constexpr (std::is_arithmetic_v<T>) {
|
||||
return std::to_string(value);
|
||||
} else if constexpr (std::is_same_v<T, String> ||
|
||||
@ -91,21 +91,22 @@ template <typename T> auto to_string(T *value) -> String {
|
||||
using TestFunctor = std::function<bool()>;
|
||||
|
||||
struct TestUnit {
|
||||
String name;
|
||||
TestFunctor functor;
|
||||
Mut<String> name;
|
||||
Mut<TestFunctor> functor;
|
||||
};
|
||||
|
||||
class Block {
|
||||
public:
|
||||
virtual ~Block() = default;
|
||||
[[nodiscard]] virtual auto get_name() const -> const char * = 0;
|
||||
virtual void declare_tests() = 0;
|
||||
virtual auto declare_tests() -> void = 0;
|
||||
|
||||
auto units() -> Vec<TestUnit> & { return m_units; }
|
||||
auto units() -> MutRef<Vec<TestUnit>> { return m_units; }
|
||||
|
||||
protected:
|
||||
template <typename T1, typename T2>
|
||||
auto _test_eq(const T1 &lhs, const T2 &rhs, const char *description) -> bool {
|
||||
auto _test_eq(Ref<T1> lhs, Ref<T2> rhs, Const<const char *> description)
|
||||
-> bool {
|
||||
if (lhs != rhs) {
|
||||
print_fail(description, to_string(lhs), to_string(rhs));
|
||||
return false;
|
||||
@ -114,7 +115,7 @@ protected:
|
||||
}
|
||||
|
||||
template <typename T1, typename T2>
|
||||
auto _test_neq(const T1 &lhs, const T2 &rhs, const char *description)
|
||||
auto _test_neq(Ref<T1> lhs, Ref<T2> rhs, Const<const char *> description)
|
||||
-> bool {
|
||||
if (lhs == rhs) {
|
||||
print_fail(description, to_string(lhs), "NOT " + to_string(rhs));
|
||||
@ -124,10 +125,11 @@ protected:
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto _test_approx(T lhs, T rhs, const char *description) -> bool {
|
||||
auto _test_approx(Const<T> lhs, Const<T> rhs, Const<const char *> description)
|
||||
-> bool {
|
||||
static_assert(std::is_floating_point_v<T>,
|
||||
"Approx only works for floats/doubles");
|
||||
T diff = std::abs(lhs - rhs);
|
||||
Const<T> diff = std::abs(lhs - rhs);
|
||||
if (diff > static_cast<T>(0.0001)) {
|
||||
print_fail(description, to_string(lhs), to_string(rhs));
|
||||
return false;
|
||||
@ -135,7 +137,7 @@ protected:
|
||||
return true;
|
||||
}
|
||||
|
||||
auto _test(bool value, const char *description) -> bool {
|
||||
auto _test(Const<bool> value, Const<const char *> description) -> bool {
|
||||
if (!value) {
|
||||
std::cout << console::BLUE << " " << description << "... "
|
||||
<< console::RED << "FAILED" << console::RESET << "\n";
|
||||
@ -144,7 +146,7 @@ protected:
|
||||
return true;
|
||||
}
|
||||
|
||||
auto _test_not(bool value, const char *description) -> bool {
|
||||
auto _test_not(Const<bool> value, Const<const char *> description) -> bool {
|
||||
if (value) {
|
||||
std::cout << console::BLUE << " " << description << "... "
|
||||
<< console::RED << "FAILED" << console::RESET << "\n";
|
||||
@ -153,12 +155,13 @@ protected:
|
||||
return true;
|
||||
}
|
||||
|
||||
void _test_unit(TestFunctor functor, const char *name) {
|
||||
auto _test_unit(Mut<TestFunctor> functor, Const<const char *> name) -> void {
|
||||
m_units.push_back({name, std::move(functor)});
|
||||
}
|
||||
|
||||
private:
|
||||
void print_fail(const char *desc, const String &v1, const String &v2) {
|
||||
auto print_fail(Const<const char *> desc, Ref<String> v1, Ref<String> v2)
|
||||
-> void {
|
||||
std::cout << console::BLUE << " " << desc << "... " << console::RED
|
||||
<< "FAILED" << console::RESET << "\n";
|
||||
std::cout << console::RED << " Expected: " << v2 << console::RESET
|
||||
@ -167,7 +170,7 @@ private:
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
Vec<TestUnit> m_units;
|
||||
Mut<Vec<TestUnit>> m_units;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
@ -184,37 +187,35 @@ public:
|
||||
|
||||
template <typename BlockClass>
|
||||
requires ValidBlockClass<BlockClass>
|
||||
void test_block();
|
||||
auto test_block() -> void;
|
||||
|
||||
private:
|
||||
void summarize();
|
||||
auto summarize() -> void;
|
||||
|
||||
usize m_test_count{0};
|
||||
usize m_fail_count{0};
|
||||
usize m_block_count{0};
|
||||
Mut<usize> m_test_count{0};
|
||||
Mut<usize> m_fail_count{0};
|
||||
Mut<usize> m_block_count{0};
|
||||
};
|
||||
|
||||
template <bool StopOnFail, bool IsVerbose>
|
||||
template <typename BlockClass>
|
||||
requires ValidBlockClass<BlockClass>
|
||||
void Runner<StopOnFail, IsVerbose>::test_block() {
|
||||
auto Runner<StopOnFail, IsVerbose>::test_block() -> void {
|
||||
m_block_count++;
|
||||
BlockClass b;
|
||||
Mut<BlockClass> b;
|
||||
b.declare_tests();
|
||||
|
||||
std::cout << console::MAGENTA << "Testing [" << b.get_name() << "]..."
|
||||
<< console::RESET << "\n";
|
||||
|
||||
for (auto &v : b.units()) {
|
||||
for (MutRef<TestUnit> v : b.units()) {
|
||||
m_test_count++;
|
||||
if constexpr (IsVerbose) {
|
||||
std::cout << console::YELLOW << " Testing " << v.name << "...\n"
|
||||
<< console::RESET;
|
||||
}
|
||||
|
||||
// Exceptions are DISABLED. We assume tests do not crash.
|
||||
// If a test crashes (segfault), the OS handles it.
|
||||
bool result = v.functor();
|
||||
Const<bool> result = v.functor();
|
||||
|
||||
if (!result) {
|
||||
m_fail_count++;
|
||||
@ -228,7 +229,7 @@ void Runner<StopOnFail, IsVerbose>::test_block() {
|
||||
}
|
||||
|
||||
template <bool StopOnFail, bool IsVerbose>
|
||||
void Runner<StopOnFail, IsVerbose>::summarize() {
|
||||
auto Runner<StopOnFail, IsVerbose>::summarize() -> void {
|
||||
std::cout << console::GREEN
|
||||
<< "\n-----------------------------------\n\t "
|
||||
"SUMMARY\n-----------------------------------\n";
|
||||
@ -236,7 +237,8 @@ void Runner<StopOnFail, IsVerbose>::summarize() {
|
||||
if (m_fail_count == 0) {
|
||||
std::cout << "\n\tALL TESTS PASSED!\n\n";
|
||||
} else {
|
||||
f64 success_rate = (100.0 * static_cast<f64>(m_test_count - m_fail_count) /
|
||||
Const<f64> success_rate =
|
||||
(100.0 * static_cast<f64>(m_test_count - m_fail_count) /
|
||||
static_cast<f64>(m_test_count));
|
||||
std::cout << console::RED << m_fail_count << " OF " << m_test_count
|
||||
<< " TESTS FAILED\n"
|
||||
@ -257,21 +259,21 @@ using DefaultRunner = Runner<false, true>;
|
||||
// -------------------------------------------------------------------------
|
||||
class TestRegistry {
|
||||
public:
|
||||
using TestEntry = std::function<void(DefaultRunner &)>;
|
||||
using TestEntry = std::function<void(MutRef<DefaultRunner>)>;
|
||||
|
||||
static auto get_entries() -> Vec<TestEntry> & {
|
||||
static Vec<TestEntry> entries;
|
||||
static auto get_entries() -> MutRef<Vec<TestEntry>> {
|
||||
static Mut<Vec<TestEntry>> entries;
|
||||
return entries;
|
||||
}
|
||||
|
||||
static auto run_all() -> i32 {
|
||||
DefaultRunner r;
|
||||
auto &entries = get_entries();
|
||||
Mut<DefaultRunner> r;
|
||||
MutRef<Vec<TestEntry>> entries = get_entries();
|
||||
std::cout << console::CYAN << "[IATest] Discovered " << entries.size()
|
||||
<< " Test Blocks\n\n"
|
||||
<< console::RESET;
|
||||
|
||||
for (auto &entry : entries) {
|
||||
for (MutRef<TestEntry> entry : entries) {
|
||||
entry(r);
|
||||
}
|
||||
|
||||
@ -282,7 +284,7 @@ public:
|
||||
template <typename BlockType> struct AutoRegister {
|
||||
AutoRegister() {
|
||||
TestRegistry::get_entries().push_back(
|
||||
[](DefaultRunner &r) { r.test_block<BlockType>(); });
|
||||
[](MutRef<DefaultRunner> r) { r.test_block<BlockType>(); });
|
||||
}
|
||||
};
|
||||
} // namespace IACore::Test
|
||||
|
||||
@ -24,44 +24,46 @@ using IpcPacketHeader = RingBufferView::PacketHeader;
|
||||
|
||||
struct alignas(64) IpcSharedMemoryLayout {
|
||||
// =========================================================
|
||||
// SECTION 1: METADATA & HANDSHAKE
|
||||
// METADATA & HANDSHAKE
|
||||
// =========================================================
|
||||
struct Header {
|
||||
u32 magic; // 0x49414950 ("IAIP")
|
||||
u32 version; // 1
|
||||
u64 total_size; // Total size of SHM block
|
||||
} meta;
|
||||
Mut<u32> magic; // 0x49414950 ("IAIP")
|
||||
Mut<u32> version; // 1
|
||||
Mut<u64> total_size; // Total size of SHM block
|
||||
};
|
||||
|
||||
Mut<Header> meta;
|
||||
|
||||
// Pad to ensure MONI starts on a fresh cache line (64 bytes)
|
||||
u8 _pad0[64 - sizeof(Header)];
|
||||
Const<Array<u8, 64 - sizeof(Header)>> _pad0;
|
||||
|
||||
// =========================================================
|
||||
// SECTION 2: RING BUFFER CONTROL BLOCKS
|
||||
// RING BUFFER CONTROL BLOCKS
|
||||
// =========================================================
|
||||
|
||||
// RingBufferView ControlBlock is already 64-byte aligned internally.
|
||||
RingBufferView::ControlBlock moni_control;
|
||||
RingBufferView::ControlBlock mino_control;
|
||||
Mut<RingBufferView::ControlBlock> moni_control;
|
||||
Mut<RingBufferView::ControlBlock> mino_control;
|
||||
|
||||
// =========================================================
|
||||
// SECTION 3: DATA BUFFER OFFSETS
|
||||
// DATA BUFFER OFFSETS
|
||||
// =========================================================
|
||||
|
||||
u64 moni_data_offset;
|
||||
u64 moni_data_size;
|
||||
Mut<u64> moni_data_offset;
|
||||
Mut<u64> moni_data_size;
|
||||
|
||||
u64 mino_data_offset;
|
||||
u64 mino_data_size;
|
||||
Mut<u64> mino_data_offset;
|
||||
Mut<u64> mino_data_size;
|
||||
|
||||
// Pad to ensure the actual Data Buffer starts on a fresh cache line
|
||||
u8 _pad1[64 - (sizeof(u64) * 4)];
|
||||
Const<Array<u8, 64 - (sizeof(u64) * 4)>> _pad1;
|
||||
|
||||
static constexpr auto get_header_size() -> usize {
|
||||
return sizeof(IpcSharedMemoryLayout);
|
||||
}
|
||||
};
|
||||
|
||||
// Static assert to ensure manual padding logic is correct
|
||||
// Check padding logic is gucci
|
||||
static_assert(sizeof(IpcSharedMemoryLayout) % 64 == 0,
|
||||
"IPC Layout is not cache-line aligned!");
|
||||
|
||||
@ -71,81 +73,86 @@ public:
|
||||
|
||||
// When Manager spawns a node, `connection_string` is passed
|
||||
// as the first command line argument
|
||||
auto connect(const char *connection_string) -> Result<void>;
|
||||
auto connect(Const<const char *> connection_string) -> Result<void>;
|
||||
|
||||
void update();
|
||||
auto update() -> void;
|
||||
|
||||
void send_signal(u8 signal);
|
||||
auto send_packet(u16 packet_id, Span<const u8> payload) -> Result<void>;
|
||||
auto send_signal(Const<u8> signal) -> void;
|
||||
auto send_packet(Const<u16> packet_id, Const<Span<Const<u8>>> payload)
|
||||
-> Result<void>;
|
||||
|
||||
protected:
|
||||
virtual void on_signal(u8 signal) = 0;
|
||||
virtual void on_packet(u16 packet_id, Span<const u8> payload) = 0;
|
||||
virtual auto on_signal(Const<u8> signal) -> void = 0;
|
||||
virtual auto on_packet(Const<u16> packet_id, Const<Span<Const<u8>>> payload)
|
||||
-> void = 0;
|
||||
|
||||
private:
|
||||
String m_shm_name;
|
||||
u8 *m_shared_memory{};
|
||||
Vec<u8> m_receive_buffer;
|
||||
SocketHandle m_socket{INVALID_SOCKET};
|
||||
Mut<String> m_shm_name;
|
||||
Mut<u8 *> m_shared_memory{};
|
||||
Mut<Vec<u8>> m_receive_buffer;
|
||||
Mut<SocketHandle> m_socket{INVALID_SOCKET};
|
||||
|
||||
RingBufferView m_moni; // Manager Out, Node In
|
||||
RingBufferView m_mino; // Manager In, Node Out
|
||||
Mut<RingBufferView> m_moni; // Manager Out, Node In
|
||||
Mut<RingBufferView> m_mino; // Manager In, Node Out
|
||||
};
|
||||
|
||||
class IpcManager {
|
||||
struct NodeSession {
|
||||
std::chrono::system_clock::time_point creation_time{};
|
||||
Box<ProcessHandle> node_process;
|
||||
Mut<std::chrono::system_clock::time_point> creation_time{};
|
||||
Mut<Box<ProcessHandle>> node_process;
|
||||
|
||||
std::mutex send_mutex;
|
||||
Mut<std::mutex> send_mutex;
|
||||
|
||||
String shared_mem_name;
|
||||
u8 *mapped_ptr{};
|
||||
Mut<String> shared_mem_name;
|
||||
Mut<u8 *> mapped_ptr{};
|
||||
|
||||
SocketHandle listener_socket{INVALID_SOCKET};
|
||||
SocketHandle data_socket{INVALID_SOCKET};
|
||||
Mut<SocketHandle> listener_socket{INVALID_SOCKET};
|
||||
Mut<SocketHandle> data_socket{INVALID_SOCKET};
|
||||
|
||||
RingBufferView moni =
|
||||
Mut<RingBufferView> moni =
|
||||
RingBufferView::default_instance(); // Manager Out, Node In
|
||||
RingBufferView mino =
|
||||
Mut<RingBufferView> mino =
|
||||
RingBufferView::default_instance(); // Manager In, Node Out
|
||||
|
||||
bool is_ready{false};
|
||||
Mut<bool> is_ready{false};
|
||||
|
||||
void send_signal(u8 signal);
|
||||
auto send_packet(u16 packet_id, Span<const u8> payload) -> Result<void>;
|
||||
auto send_signal(Const<u8> signal) -> void;
|
||||
auto send_packet(Const<u16> packet_id, Const<Span<Const<u8>>> payload)
|
||||
-> Result<void>;
|
||||
};
|
||||
|
||||
public:
|
||||
static constexpr u32 DEFAULT_NODE_SHARED_MEMORY_SIZE = 4 * 1024 * 1024; // 4MB
|
||||
static constexpr Const<u32> DEFAULT_NODE_SHARED_MEMORY_SIZE = 4 * 1024 * 1024;
|
||||
|
||||
public:
|
||||
virtual ~IpcManager();
|
||||
|
||||
void update();
|
||||
auto update() -> void;
|
||||
|
||||
auto spawn_node(const Path &executable_path,
|
||||
u32 shared_memory_size = DEFAULT_NODE_SHARED_MEMORY_SIZE)
|
||||
auto
|
||||
spawn_node(Ref<Path> executable_path,
|
||||
Const<u32> shared_memory_size = DEFAULT_NODE_SHARED_MEMORY_SIZE)
|
||||
-> Result<NativeProcessID>;
|
||||
|
||||
auto wait_till_node_is_online(NativeProcessID node) -> bool;
|
||||
auto wait_till_node_is_online(Const<NativeProcessID> node) -> bool;
|
||||
|
||||
void shutdown_node(NativeProcessID node);
|
||||
auto shutdown_node(Const<NativeProcessID> node) -> void;
|
||||
|
||||
void send_signal(NativeProcessID node, u8 signal);
|
||||
auto send_packet(NativeProcessID node, u16 packet_id, Span<const u8> payload)
|
||||
-> Result<void>;
|
||||
auto send_signal(Const<NativeProcessID> node, Const<u8> signal) -> void;
|
||||
auto send_packet(Const<NativeProcessID> node, Const<u16> packet_id,
|
||||
Const<Span<Const<u8>>> payload) -> Result<void>;
|
||||
|
||||
protected:
|
||||
virtual void on_signal(NativeProcessID node, u8 signal) = 0;
|
||||
virtual void on_packet(NativeProcessID node, u16 packet_id,
|
||||
Span<const u8> payload) = 0;
|
||||
virtual auto on_signal(Const<NativeProcessID> node, Const<u8> signal)
|
||||
-> void = 0;
|
||||
virtual auto on_packet(Const<NativeProcessID> node, Const<u16> packet_id,
|
||||
Const<Span<Const<u8>>> payload) -> void = 0;
|
||||
|
||||
private:
|
||||
Vec<u8> m_receive_buffer;
|
||||
Vec<Box<NodeSession>> m_active_sessions;
|
||||
Vec<Box<NodeSession>> m_pending_sessions;
|
||||
HashMap<NativeProcessID, NodeSession *> m_active_session_map;
|
||||
Mut<Vec<u8>> m_receive_buffer;
|
||||
Mut<Vec<Box<NodeSession>>> m_active_sessions;
|
||||
Mut<Vec<Box<NodeSession>>> m_pending_sessions;
|
||||
Mut<HashMap<NativeProcessID, NodeSession *>> m_active_session_map;
|
||||
|
||||
protected:
|
||||
IpcManager();
|
||||
|
||||
@ -24,67 +24,49 @@
|
||||
namespace IACore {
|
||||
class JsonDocument {
|
||||
public:
|
||||
// Move-only (Safety: Cannot copy the parser state cheaply)
|
||||
JsonDocument(JsonDocument &&) noexcept = default;
|
||||
JsonDocument &operator=(JsonDocument &&) noexcept = default;
|
||||
JsonDocument(const JsonDocument &) = delete;
|
||||
JsonDocument &operator=(const JsonDocument &) = delete;
|
||||
JsonDocument(ForwardRef<JsonDocument>) noexcept = default;
|
||||
auto operator=(ForwardRef<JsonDocument>) noexcept
|
||||
-> MutRef<JsonDocument> = default;
|
||||
|
||||
JsonDocument(Ref<JsonDocument>) = delete;
|
||||
auto operator=(Ref<JsonDocument>) -> MutRef<JsonDocument> = delete;
|
||||
|
||||
// Accessor: Get the root element (Object, Array, etc.)
|
||||
// The returned 'element' is valid only as long as this JsonDocument is alive.
|
||||
[[nodiscard]]
|
||||
auto root() const noexcept -> simdjson::dom::element {
|
||||
return m_root;
|
||||
}
|
||||
|
||||
private:
|
||||
// Only created via JSON::parse_read_only factory
|
||||
friend class Json;
|
||||
|
||||
JsonDocument(Box<simdjson::dom::parser> p, simdjson::dom::element r)
|
||||
JsonDocument(Mut<Box<simdjson::dom::parser>> p, Mut<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;
|
||||
Mut<Box<simdjson::dom::parser>> m_parser;
|
||||
Mut<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};
|
||||
static constexpr Const<glz::opts> 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;
|
||||
static auto parse(Ref<String> json_str) -> Result<nlohmann::json>;
|
||||
static auto encode(Ref<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>;
|
||||
static auto parse_read_only(Ref<String> json_str) -> Result<JsonDocument>;
|
||||
|
||||
template <typename T>
|
||||
static auto encode_struct(const T &data) -> Result<String>;
|
||||
static auto parse_to_struct(Ref<String> json_str) -> Result<T>;
|
||||
|
||||
template <typename T>
|
||||
static auto encode_struct(Ref<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);
|
||||
inline auto Json::parse(Ref<String> json_str) -> Result<nlohmann::json> {
|
||||
Const<nlohmann::json> res =
|
||||
nlohmann::json::parse(json_str, nullptr, false, true);
|
||||
|
||||
if (res.is_discarded()) {
|
||||
return fail("Failed to parse JSON (Invalid Syntax)");
|
||||
@ -92,35 +74,30 @@ inline auto Json::parse(const String &json_str) -> Result<nlohmann::json> {
|
||||
return res;
|
||||
}
|
||||
|
||||
inline auto Json::parse_read_only(const String &json_str)
|
||||
inline auto Json::parse_read_only(Ref<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>();
|
||||
Mut<Box<simdjson::dom::parser>> parser = make_box<simdjson::dom::parser>();
|
||||
|
||||
// 2. Use 'element' to support Arrays/Strings/Null roots, not just Objects
|
||||
simdjson::dom::element root;
|
||||
Mut<simdjson::dom::element> root;
|
||||
|
||||
// 3. Parse
|
||||
simdjson::error_code error = parser->parse(json_str).get(root);
|
||||
Const<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 {
|
||||
inline auto Json::encode(Ref<nlohmann::json> data) -> String {
|
||||
return data.dump();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline auto Json::parse_to_struct(const String &json_str) -> Result<T> {
|
||||
T result{};
|
||||
inline auto Json::parse_to_struct(Ref<String> json_str) -> Result<T> {
|
||||
Mut<T> result{};
|
||||
|
||||
const auto err = glz::read<GLAZE_OPTS>(result, json_str);
|
||||
Const<glz::error_ctx> err = glz::read<GLAZE_OPTS>(result, json_str);
|
||||
|
||||
if (err) {
|
||||
return fail("JSON Struct Parse Error: {}",
|
||||
@ -130,9 +107,9 @@ inline auto Json::parse_to_struct(const String &json_str) -> Result<T> {
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline auto Json::encode_struct(const T &data) -> Result<String> {
|
||||
String result;
|
||||
const auto err = glz::write_json(data, result);
|
||||
inline auto Json::encode_struct(Ref<T> data) -> Result<String> {
|
||||
Mut<String> result;
|
||||
Const<glz::error_ctx> err = glz::write_json(data, result);
|
||||
|
||||
if (err) {
|
||||
return fail("JSON Struct Encode Error");
|
||||
|
||||
@ -1,3 +1,18 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
@ -18,31 +33,37 @@ public:
|
||||
enum class LogLevel { Trace, Debug, Info, Warn, Error };
|
||||
|
||||
public:
|
||||
static auto enable_logging_to_disk(const char *file_path) -> Result<void>;
|
||||
static auto set_log_level(LogLevel log_level) -> void;
|
||||
static auto enable_logging_to_disk(Const<const char *> file_path)
|
||||
-> Result<void>;
|
||||
static auto set_log_level(Const<LogLevel> log_level) -> void;
|
||||
|
||||
template <typename... Args>
|
||||
static auto trace(std::format_string<Args...> fmt, Args &&...args) -> void {
|
||||
static auto trace(Const<std::format_string<Args...>> fmt,
|
||||
ForwardRef<Args>... args) -> void {
|
||||
log_trace(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
static auto debug(std::format_string<Args...> fmt, Args &&...args) -> void {
|
||||
static auto debug(Const<std::format_string<Args...>> fmt,
|
||||
ForwardRef<Args>... args) -> void {
|
||||
log_debug(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
static auto info(std::format_string<Args...> fmt, Args &&...args) -> void {
|
||||
static auto info(Const<std::format_string<Args...>> fmt,
|
||||
ForwardRef<Args>... args) -> void {
|
||||
log_info(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
static auto warn(std::format_string<Args...> fmt, Args &&...args) -> void {
|
||||
static auto warn(Const<std::format_string<Args...>> fmt,
|
||||
ForwardRef<Args>... args) -> void {
|
||||
log_warn(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
static auto error(std::format_string<Args...> fmt, Args &&...args) -> void {
|
||||
static auto error(Const<std::format_string<Args...>> fmt,
|
||||
ForwardRef<Args>... args) -> void {
|
||||
log_error(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
|
||||
@ -50,48 +71,48 @@ public:
|
||||
|
||||
private:
|
||||
#if IA_DISABLE_LOGGING > 0
|
||||
static auto log_trace(String &&msg) -> void { UNUSED(msg); }
|
||||
static auto log_trace(ForwardRef<String> msg) -> void { IA_UNUSED(msg); }
|
||||
|
||||
static auto log_debug(String &&msg) -> void { UNUSED(msg); }
|
||||
static auto log_debug(ForwardRef<String> msg) -> void { IA_UNUSED(msg); }
|
||||
|
||||
static auto log_info(String &&msg) -> void { UNUSED(msg); }
|
||||
static auto log_info(ForwardRef<String> msg) -> void { IA_UNUSED(msg); }
|
||||
|
||||
static auto log_warn(String &&msg) -> void { UNUSED(msg); }
|
||||
static auto log_warn(ForwardRef<String> msg) -> void { IA_UNUSED(msg); }
|
||||
|
||||
static auto log_error(String &&msg) -> void { UNUSED(msg); }
|
||||
static auto log_error(ForwardRef<String> msg) -> void { IA_UNUSED(msg); }
|
||||
#else
|
||||
static auto log_trace(String &&msg) -> void {
|
||||
static auto log_trace(ForwardRef<String> msg) -> void {
|
||||
if (m_log_level <= LogLevel::Trace)
|
||||
log_internal(console::RESET, "TRACE", std::move(msg));
|
||||
}
|
||||
|
||||
static auto log_debug(String &&msg) -> void {
|
||||
static auto log_debug(ForwardRef<String> msg) -> void {
|
||||
if (m_log_level <= LogLevel::Debug)
|
||||
log_internal(console::CYAN, "DEBUG", std::move(msg));
|
||||
}
|
||||
|
||||
static auto log_info(String &&msg) -> void {
|
||||
static auto log_info(ForwardRef<String> msg) -> void {
|
||||
if (m_log_level <= LogLevel::Info)
|
||||
log_internal(console::GREEN, "INFO", std::move(msg));
|
||||
}
|
||||
|
||||
static auto log_warn(String &&msg) -> void {
|
||||
static auto log_warn(ForwardRef<String> msg) -> void {
|
||||
if (m_log_level <= LogLevel::Warn)
|
||||
log_internal(console::YELLOW, "WARN", std::move(msg));
|
||||
}
|
||||
|
||||
static auto log_error(String &&msg) -> void {
|
||||
static auto log_error(ForwardRef<String> msg) -> void {
|
||||
if (m_log_level <= LogLevel::Error)
|
||||
log_internal(console::RED, "ERROR", std::move(msg));
|
||||
}
|
||||
#endif
|
||||
|
||||
static auto log_internal(const char *prefix, const char *tag, String &&msg)
|
||||
-> void;
|
||||
static auto log_internal(Const<const char *> prefix, Const<const char *> tag,
|
||||
ForwardRef<String> msg) -> void;
|
||||
|
||||
private:
|
||||
static LogLevel m_log_level;
|
||||
static std::ofstream m_log_file;
|
||||
static Mut<LogLevel> m_log_level;
|
||||
static Mut<std::ofstream> m_log_file;
|
||||
|
||||
static auto initialize() -> void;
|
||||
static auto terminate() -> void;
|
||||
|
||||
@ -54,144 +54,53 @@
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <source_location>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <tl/expected.hpp>
|
||||
|
||||
#include <oxide/oxide.hpp>
|
||||
|
||||
namespace IACore {
|
||||
|
||||
// =============================================================================
|
||||
// Primitive Types (Rust Style)
|
||||
// =============================================================================
|
||||
using u8 = std::uint8_t;
|
||||
using u16 = std::uint16_t;
|
||||
using u32 = std::uint32_t;
|
||||
using u64 = std::uint64_t;
|
||||
|
||||
using i8 = std::int8_t;
|
||||
using i16 = std::int16_t;
|
||||
using i32 = std::int32_t;
|
||||
using i64 = std::int64_t;
|
||||
|
||||
using f32 = float;
|
||||
using f64 = double;
|
||||
|
||||
using usize = std::size_t;
|
||||
using isize = std::ptrdiff_t;
|
||||
using namespace Oxide;
|
||||
|
||||
// =============================================================================
|
||||
// Build Environment & Constants
|
||||
// =============================================================================
|
||||
namespace env {
|
||||
#if defined(NDEBUG)
|
||||
constexpr bool is_debug = false;
|
||||
constexpr bool is_release = true;
|
||||
#else
|
||||
constexpr bool is_debug = true;
|
||||
constexpr bool is_release = false;
|
||||
#endif
|
||||
namespace Env {
|
||||
|
||||
using namespace Oxide::Env;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
constexpr bool is_windows = true;
|
||||
constexpr bool is_unix = false;
|
||||
constexpr Const<bool> IS_WINDOWS = true;
|
||||
constexpr Const<bool> IS_UNIX = false;
|
||||
#else
|
||||
constexpr bool is_windows = false;
|
||||
constexpr bool is_unix = true;
|
||||
constexpr Const<bool> IS_WINDOWS = false;
|
||||
constexpr Const<bool> IS_UNIX = true;
|
||||
#endif
|
||||
|
||||
constexpr usize max_path_len = 4096;
|
||||
} // namespace env
|
||||
constexpr Const<usize> MAX_PATH_LEN = 4096;
|
||||
|
||||
} // namespace Env
|
||||
|
||||
// =============================================================================
|
||||
// Memory & Ownership (Rust Semantics)
|
||||
// Data Structures & Aliases
|
||||
// =============================================================================
|
||||
template <typename T> using Box = std::unique_ptr<T>;
|
||||
|
||||
template <typename T> using Arc = std::shared_ptr<T>;
|
||||
|
||||
template <typename T> using Weak = std::weak_ptr<T>;
|
||||
|
||||
template <typename T, typename... Args>
|
||||
[[nodiscard]] inline auto make_box(Args &&...args) -> Box<T> {
|
||||
return std::make_unique<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
[[nodiscard]] inline auto make_arc(Args &&...args) -> Arc<T> {
|
||||
return std::make_shared<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Error Handling (Result)
|
||||
// =============================================================================
|
||||
template <typename T, typename E = std::string>
|
||||
using Result = tl::expected<T, E>;
|
||||
|
||||
template <typename E> [[nodiscard]] inline auto fail(E &&error) {
|
||||
return tl::make_unexpected(std::forward<E>(error));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
[[nodiscard]] inline auto fail(std::format_string<Args...> fmt,
|
||||
Args &&...args) {
|
||||
return tl::make_unexpected(std::format(fmt, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Common Data Structures
|
||||
// =============================================================================
|
||||
template <typename T> using Option = std::optional<T>;
|
||||
template <typename T> using Vec = std::vector<T>;
|
||||
template <typename T> using Span = std::span<T>;
|
||||
template <typename T1, typename T2> using Pair = std::pair<T1, T2>;
|
||||
template <typename K, typename V>
|
||||
using HashMap = ankerl::unordered_dense::map<K, V>;
|
||||
template <typename T> using HashSet = ankerl::unordered_dense::set<T>;
|
||||
|
||||
using String = std::string;
|
||||
using Path = std::filesystem::path;
|
||||
using StringView = std::string_view;
|
||||
|
||||
// =============================================================================
|
||||
// Utilities
|
||||
// =============================================================================
|
||||
|
||||
[[noreturn]] inline void
|
||||
panic(const std::string &msg,
|
||||
const std::source_location loc = std::source_location::current()) {
|
||||
std::cerr << "\n[IA_PANIC] " << msg << "\n At: " << loc.file_name()
|
||||
<< ":" << loc.line() << "\n";
|
||||
std::abort();
|
||||
}
|
||||
|
||||
inline void
|
||||
ensure(bool condition, const std::string &msg,
|
||||
const std::source_location loc = std::source_location::current()) {
|
||||
if (env::is_debug && !condition) {
|
||||
std::cerr << "\n[assert] " << msg << "\n At: " << loc.file_name()
|
||||
<< ":" << loc.line() << "\n";
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Versioning
|
||||
@ -201,7 +110,7 @@ struct Version {
|
||||
u32 minor = 0;
|
||||
u32 patch = 0;
|
||||
|
||||
constexpr auto to_u64() const -> u64 {
|
||||
[[nodiscard]] constexpr auto to_u64() const -> u64 {
|
||||
return (static_cast<u64>(major) << 40) | (static_cast<u64>(minor) << 16) |
|
||||
(static_cast<u64>(patch));
|
||||
}
|
||||
@ -211,37 +120,16 @@ struct Version {
|
||||
// Console Colors
|
||||
// =============================================================================
|
||||
namespace console {
|
||||
constexpr const char *RESET = "\033[0m";
|
||||
constexpr const char *RED = "\033[31m";
|
||||
constexpr const char *GREEN = "\033[32m";
|
||||
constexpr const char *YELLOW = "\033[33m";
|
||||
constexpr const char *BLUE = "\033[34m";
|
||||
constexpr const char *MAGENTA = "\033[35m";
|
||||
constexpr const char *CYAN = "\033[36m";
|
||||
constexpr Const<const char *> RESET = "\033[0m";
|
||||
constexpr Const<const char *> RED = "\033[31m";
|
||||
constexpr Const<const char *> GREEN = "\033[32m";
|
||||
constexpr Const<const char *> YELLOW = "\033[33m";
|
||||
constexpr Const<const char *> BLUE = "\033[34m";
|
||||
constexpr Const<const char *> MAGENTA = "\033[35m";
|
||||
constexpr Const<const char *> CYAN = "\033[36m";
|
||||
} // namespace console
|
||||
|
||||
} // namespace IACore
|
||||
|
||||
// =============================================================================
|
||||
// Macros
|
||||
// =============================================================================
|
||||
|
||||
#define IA_TRY_PURE(expr) \
|
||||
{ \
|
||||
auto _res = expr; \
|
||||
if (!_res) { \
|
||||
return fail(std::move(_res.error())); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define IA_TRY(lhs, expr) \
|
||||
{ \
|
||||
auto _res = expr; \
|
||||
if (!_res) { \
|
||||
return fail(std::move(_res.error())); \
|
||||
} \
|
||||
lhs = std::move(*_res); \
|
||||
}
|
||||
|
||||
#define IA_NODISCARD [[nodiscard]]
|
||||
#define IA_UNUSED [[maybe_unused]]
|
||||
#define IA_UNUSED(v) (void)(v)
|
||||
|
||||
@ -18,40 +18,35 @@
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
#if IA_ARCH_X64
|
||||
# ifdef _MSC_VER
|
||||
# include <intrin.h>
|
||||
# else
|
||||
# include <immintrin.h>
|
||||
# endif
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#else
|
||||
#include <immintrin.h>
|
||||
#endif
|
||||
#elif IA_ARCH_ARM64
|
||||
# include <arm_acle.h>
|
||||
#include <arm_acle.h>
|
||||
#endif
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
class Platform
|
||||
{
|
||||
public:
|
||||
struct Capabilities
|
||||
{
|
||||
bool hardware_crc32 = false;
|
||||
namespace IACore {
|
||||
class Platform {
|
||||
public:
|
||||
struct Capabilities {
|
||||
Mut<bool> hardware_crc32 = false;
|
||||
};
|
||||
|
||||
static auto check_cpu() -> bool;
|
||||
|
||||
#if IA_ARCH_X64
|
||||
static auto cpuid(i32 function, i32 sub_function, i32 out[4]) -> void;
|
||||
static auto cpuid(Const<i32> function, Const<i32> sub_function,
|
||||
Mut<i32 *> out) -> void;
|
||||
#endif
|
||||
|
||||
static auto get_architecture_name() -> const char *;
|
||||
static auto get_operating_system_name() -> const char *;
|
||||
|
||||
static auto get_capabilities() -> const Capabilities &
|
||||
{
|
||||
return s_capabilities;
|
||||
}
|
||||
static auto get_capabilities() -> Ref<Capabilities> { return s_capabilities; }
|
||||
|
||||
private:
|
||||
static Capabilities s_capabilities;
|
||||
};
|
||||
private:
|
||||
static Mut<Capabilities> s_capabilities;
|
||||
};
|
||||
} // namespace IACore
|
||||
@ -27,13 +27,13 @@ using NativeProcessID = pid_t;
|
||||
|
||||
namespace IACore {
|
||||
struct ProcessHandle {
|
||||
std::atomic<NativeProcessID> id{0};
|
||||
std::atomic<bool> is_running{false};
|
||||
Mut<std::atomic<NativeProcessID>> id{0};
|
||||
Mut<std::atomic<bool>> is_running{false};
|
||||
|
||||
[[nodiscard]] auto is_active() const -> bool { return is_running && id != 0; }
|
||||
|
||||
private:
|
||||
std::jthread m_thread_handle;
|
||||
Mut<std::jthread> m_thread_handle;
|
||||
|
||||
friend class ProcessOps;
|
||||
};
|
||||
@ -43,27 +43,27 @@ public:
|
||||
static auto get_current_process_id() -> NativeProcessID;
|
||||
|
||||
static auto spawn_process_sync(
|
||||
const String &command, const String &args,
|
||||
std::function<void(StringView line)> on_output_line_callback)
|
||||
Ref<String> command, Ref<String> args,
|
||||
Const<std::function<void(Const<StringView>)>> on_output_line_callback)
|
||||
-> Result<i32>;
|
||||
|
||||
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)
|
||||
Ref<String> command, Ref<String> args,
|
||||
Const<std::function<void(Const<StringView>)>> on_output_line_callback,
|
||||
Const<std::function<void(Const<Result<i32>>)>> on_finish_callback)
|
||||
-> Result<Box<ProcessHandle>>;
|
||||
|
||||
static auto terminate_process(const Box<ProcessHandle> &handle) -> void;
|
||||
static auto terminate_process(Ref<Box<ProcessHandle>> handle) -> void;
|
||||
|
||||
private:
|
||||
static auto
|
||||
spawn_process_windows(const String &command, const String &args,
|
||||
std::function<void(StringView)> on_output_line_callback,
|
||||
std::atomic<NativeProcessID> &id) -> Result<i32>;
|
||||
static auto spawn_process_windows(
|
||||
Ref<String> command, Ref<String> args,
|
||||
Const<std::function<void(Const<StringView>)>> on_output_line_callback,
|
||||
MutRef<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>;
|
||||
static auto spawn_process_posix(
|
||||
Ref<String> command, Ref<String> args,
|
||||
Const<std::function<void(Const<StringView>)>> on_output_line_callback,
|
||||
MutRef<std::atomic<NativeProcessID>> id) -> Result<i32>;
|
||||
};
|
||||
} // namespace IACore
|
||||
@ -1,303 +1,280 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
#if defined(__clang__)
|
||||
# pragma GCC diagnostic push
|
||||
# pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
# pragma GCC diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
#pragma GCC diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
|
||||
#endif
|
||||
|
||||
#include <hwy/highway.h>
|
||||
|
||||
#if defined(__clang__)
|
||||
# pragma GCC diagnostic pop
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
namespace hn = hwy::HWY_NAMESPACE;
|
||||
namespace IACore {
|
||||
namespace hn = hwy::HWY_NAMESPACE;
|
||||
|
||||
#if HWY_TARGET == HWY_SCALAR
|
||||
# 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
|
||||
|
||||
class alignas(16) IntVec4
|
||||
{
|
||||
public:
|
||||
class alignas(16) IntVec4 {
|
||||
public:
|
||||
IntVec4() = default;
|
||||
|
||||
inline explicit IntVec4(u32 s);
|
||||
inline explicit IntVec4(const u32 *values);
|
||||
inline explicit IntVec4(u32 a, u32 b, u32 c, u32 d);
|
||||
inline explicit IntVec4(Const<u32> s);
|
||||
inline explicit IntVec4(Const<const u32 *> values);
|
||||
inline explicit IntVec4(Const<u32> a, Const<u32> b, Const<u32> c,
|
||||
Const<u32> d);
|
||||
|
||||
inline auto operator+(const IntVec4 &other) const -> IntVec4;
|
||||
inline auto operator-(const IntVec4 &other) const -> IntVec4;
|
||||
inline auto operator*(const IntVec4 &other) const -> IntVec4;
|
||||
inline auto operator+(Ref<IntVec4> other) const -> IntVec4;
|
||||
inline auto operator-(Ref<IntVec4> other) const -> IntVec4;
|
||||
inline auto operator*(Ref<IntVec4> other) const -> IntVec4;
|
||||
|
||||
inline auto operator&(const IntVec4 &other) const -> IntVec4;
|
||||
inline auto operator|(const IntVec4 &other) const -> IntVec4;
|
||||
inline auto operator^(const IntVec4 &other) const -> IntVec4;
|
||||
inline auto operator&(Ref<IntVec4> other) const -> IntVec4;
|
||||
inline auto operator|(Ref<IntVec4> other) const -> IntVec4;
|
||||
inline auto operator^(Ref<IntVec4> other) const -> IntVec4;
|
||||
inline auto operator~() const -> IntVec4;
|
||||
|
||||
inline auto operator<<(u32 amount) const -> IntVec4;
|
||||
inline auto operator>>(u32 amount) const -> IntVec4;
|
||||
inline auto operator<<(Const<u32> amount) const -> IntVec4;
|
||||
inline auto operator>>(Const<u32> amount) const -> IntVec4;
|
||||
|
||||
inline auto sat_add(const IntVec4 &other) const -> IntVec4;
|
||||
inline auto sat_sub(const IntVec4 &other) const -> IntVec4;
|
||||
[[nodiscard]] inline auto sat_add(Ref<IntVec4> other) const -> IntVec4;
|
||||
[[nodiscard]] inline auto sat_sub(Ref<IntVec4> other) const -> IntVec4;
|
||||
|
||||
inline auto clamp(u32 min, u32 max) const -> IntVec4;
|
||||
[[nodiscard]] inline auto clamp(Const<u32> min, Const<u32> max) const
|
||||
-> IntVec4;
|
||||
|
||||
inline auto mult_add(const IntVec4 &multiplier, const IntVec4 &addend) const -> IntVec4;
|
||||
[[nodiscard]] inline auto mult_add(Ref<IntVec4> multiplier,
|
||||
Ref<IntVec4> addend) const -> IntVec4;
|
||||
|
||||
inline auto store(u32 *values) -> void;
|
||||
static inline auto load(const u32 *values) -> IntVec4;
|
||||
inline auto store(Mut<u32 *> values) -> void;
|
||||
static inline auto load(Const<const u32 *> values) -> IntVec4;
|
||||
|
||||
private:
|
||||
private:
|
||||
using Tag = hn::FixedTag<u32, 4>;
|
||||
|
||||
hn::Vec<Tag> m_data;
|
||||
Mut<hn::Vec<Tag>> m_data;
|
||||
|
||||
inline explicit IntVec4(hn::Vec<Tag> v) : m_data(v)
|
||||
{
|
||||
}
|
||||
};
|
||||
inline explicit IntVec4(Const<hn::Vec<Tag>> v) : m_data(v) {}
|
||||
};
|
||||
|
||||
class alignas(16) FloatVec4
|
||||
{
|
||||
public:
|
||||
class alignas(16) FloatVec4 {
|
||||
public:
|
||||
FloatVec4() = default;
|
||||
|
||||
inline explicit FloatVec4(f32 s);
|
||||
inline explicit FloatVec4(const f32 *values);
|
||||
inline explicit FloatVec4(f32 a, f32 b, f32 c, f32 d);
|
||||
inline explicit FloatVec4(Const<f32> s);
|
||||
inline explicit FloatVec4(Const<const f32 *> values);
|
||||
inline explicit FloatVec4(Const<f32> a, Const<f32> b, Const<f32> c,
|
||||
Const<f32> d);
|
||||
|
||||
inline auto operator+(const FloatVec4 &other) const -> FloatVec4;
|
||||
inline auto operator-(const FloatVec4 &other) const -> FloatVec4;
|
||||
inline auto operator*(const FloatVec4 &other) const -> FloatVec4;
|
||||
inline auto operator/(const FloatVec4 &other) const -> FloatVec4;
|
||||
inline auto operator+(Ref<FloatVec4> other) const -> FloatVec4;
|
||||
inline auto operator-(Ref<FloatVec4> other) const -> FloatVec4;
|
||||
inline auto operator*(Ref<FloatVec4> other) const -> FloatVec4;
|
||||
inline auto operator/(Ref<FloatVec4> other) const -> FloatVec4;
|
||||
|
||||
inline auto clamp(f32 min, f32 max) const -> FloatVec4;
|
||||
[[nodiscard]] inline auto clamp(Const<f32> min, Const<f32> max) const
|
||||
-> FloatVec4;
|
||||
|
||||
inline auto abs() const -> FloatVec4;
|
||||
inline auto sqrt() const -> FloatVec4;
|
||||
inline auto rsqrt() const -> FloatVec4;
|
||||
inline auto normalize() const -> FloatVec4;
|
||||
[[nodiscard]] inline auto abs() const -> FloatVec4;
|
||||
[[nodiscard]] inline auto sqrt() const -> FloatVec4;
|
||||
[[nodiscard]] inline auto rsqrt() const -> FloatVec4;
|
||||
[[nodiscard]] inline auto normalize() const -> FloatVec4;
|
||||
|
||||
inline auto dot(const FloatVec4 &other) const -> f32;
|
||||
[[nodiscard]] inline auto dot(Ref<FloatVec4> other) const -> f32;
|
||||
|
||||
inline auto mult_add(const FloatVec4 &multiplier, const FloatVec4 &addend) const -> FloatVec4;
|
||||
[[nodiscard]] inline auto mult_add(Ref<FloatVec4> multiplier,
|
||||
Ref<FloatVec4> addend) const -> FloatVec4;
|
||||
|
||||
inline auto store(f32 *values) -> void;
|
||||
static inline auto load(const f32 *values) -> FloatVec4;
|
||||
inline auto store(Mut<f32 *> values) -> void;
|
||||
static inline auto load(Const<const f32 *> values) -> FloatVec4;
|
||||
|
||||
private:
|
||||
private:
|
||||
using Tag = hn::FixedTag<f32, 4>;
|
||||
|
||||
hn::Vec<Tag> m_data;
|
||||
Mut<hn::Vec<Tag>> m_data;
|
||||
|
||||
inline explicit FloatVec4(hn::Vec<Tag> v) : m_data(v)
|
||||
{
|
||||
}
|
||||
};
|
||||
inline explicit FloatVec4(Const<hn::Vec<Tag>> v) : m_data(v) {}
|
||||
};
|
||||
} // namespace IACore
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
IntVec4::IntVec4(u32 s)
|
||||
{
|
||||
const Tag d;
|
||||
namespace IACore {
|
||||
IntVec4::IntVec4(Const<u32> s) {
|
||||
Const<Tag> d;
|
||||
m_data = hn::Set(d, s);
|
||||
}
|
||||
}
|
||||
|
||||
IntVec4::IntVec4(const u32 *values)
|
||||
{
|
||||
const Tag data;
|
||||
IntVec4::IntVec4(Const<const u32 *> values) {
|
||||
Const<Tag> data;
|
||||
m_data = hn::Load(data, values);
|
||||
}
|
||||
}
|
||||
|
||||
IntVec4::IntVec4(u32 a, u32 b, u32 c, u32 d)
|
||||
{
|
||||
const Tag data;
|
||||
alignas(16) u32 values[4] = {a, b, c, d};
|
||||
m_data = hn::Load(data, values);
|
||||
}
|
||||
IntVec4::IntVec4(Const<u32> a, Const<u32> b, Const<u32> c, Const<u32> d) {
|
||||
Const<Tag> data;
|
||||
alignas(16) Mut<Array<u32, 4>> values = {a, b, c, d};
|
||||
m_data = hn::Load(data, values.data());
|
||||
}
|
||||
|
||||
auto IntVec4::operator+(const IntVec4 &other) const -> IntVec4
|
||||
{
|
||||
auto IntVec4::operator+(Ref<IntVec4> other) const -> IntVec4 {
|
||||
return IntVec4(hn::Add(m_data, other.m_data));
|
||||
}
|
||||
}
|
||||
|
||||
auto IntVec4::operator-(const IntVec4 &other) const -> IntVec4
|
||||
{
|
||||
auto IntVec4::operator-(Ref<IntVec4> other) const -> IntVec4 {
|
||||
return IntVec4(hn::Sub(m_data, other.m_data));
|
||||
}
|
||||
}
|
||||
|
||||
auto IntVec4::operator*(const IntVec4 &other) const -> IntVec4
|
||||
{
|
||||
auto IntVec4::operator*(Ref<IntVec4> other) const -> IntVec4 {
|
||||
return IntVec4(hn::Mul(m_data, other.m_data));
|
||||
}
|
||||
}
|
||||
|
||||
auto IntVec4::operator&(const IntVec4 &other) const -> IntVec4
|
||||
{
|
||||
auto IntVec4::operator&(Ref<IntVec4> other) const -> IntVec4 {
|
||||
return IntVec4(hn::And(m_data, other.m_data));
|
||||
}
|
||||
}
|
||||
|
||||
auto IntVec4::operator|(const IntVec4 &other) const -> IntVec4
|
||||
{
|
||||
auto IntVec4::operator|(Ref<IntVec4> other) const -> IntVec4 {
|
||||
return IntVec4(hn::Or(m_data, other.m_data));
|
||||
}
|
||||
}
|
||||
|
||||
auto IntVec4::operator^(const IntVec4 &other) const -> IntVec4
|
||||
{
|
||||
auto IntVec4::operator^(Ref<IntVec4> other) const -> IntVec4 {
|
||||
return IntVec4(hn::Xor(m_data, other.m_data));
|
||||
}
|
||||
}
|
||||
|
||||
auto IntVec4::operator~() const -> IntVec4
|
||||
{
|
||||
return IntVec4(hn::Not(m_data));
|
||||
}
|
||||
auto IntVec4::operator~() const -> IntVec4 { return IntVec4(hn::Not(m_data)); }
|
||||
|
||||
auto IntVec4::operator<<(u32 amount) const -> IntVec4
|
||||
{
|
||||
auto IntVec4::operator<<(Const<u32> amount) const -> IntVec4 {
|
||||
return IntVec4(hn::ShiftLeftSame(m_data, amount));
|
||||
}
|
||||
}
|
||||
|
||||
auto IntVec4::operator>>(u32 amount) const -> IntVec4
|
||||
{
|
||||
auto IntVec4::operator>>(Const<u32> amount) const -> IntVec4 {
|
||||
return IntVec4(hn::ShiftRightSame(m_data, amount));
|
||||
}
|
||||
}
|
||||
|
||||
auto IntVec4::mult_add(const IntVec4 &multiplier, const IntVec4 &addend) const -> IntVec4
|
||||
{
|
||||
auto IntVec4::mult_add(Ref<IntVec4> multiplier, Ref<IntVec4> addend) const
|
||||
-> IntVec4 {
|
||||
return IntVec4(hn::MulAdd(m_data, multiplier.m_data, addend.m_data));
|
||||
}
|
||||
}
|
||||
|
||||
auto IntVec4::sat_add(const IntVec4 &other) const -> IntVec4
|
||||
{
|
||||
auto IntVec4::sat_add(Ref<IntVec4> other) const -> IntVec4 {
|
||||
return IntVec4(hn::SaturatedAdd(m_data, other.m_data));
|
||||
}
|
||||
}
|
||||
|
||||
auto IntVec4::sat_sub(const IntVec4 &other) const -> IntVec4
|
||||
{
|
||||
auto IntVec4::sat_sub(Ref<IntVec4> other) const -> IntVec4 {
|
||||
return IntVec4(hn::SaturatedSub(m_data, other.m_data));
|
||||
}
|
||||
}
|
||||
|
||||
auto IntVec4::clamp(u32 min, u32 max) const -> IntVec4
|
||||
{
|
||||
const Tag d;
|
||||
auto vMin = hn::Set(d, min);
|
||||
auto vMax = hn::Set(d, max);
|
||||
return IntVec4(hn::Min(hn::Max(m_data, vMin), vMax));
|
||||
}
|
||||
auto IntVec4::clamp(Const<u32> min, Const<u32> max) const -> IntVec4 {
|
||||
Const<Tag> d;
|
||||
Const<hn::Vec<Tag>> v_min = hn::Set(d, min);
|
||||
Const<hn::Vec<Tag>> v_max = hn::Set(d, max);
|
||||
return IntVec4(hn::Min(hn::Max(m_data, v_min), v_max));
|
||||
}
|
||||
|
||||
auto IntVec4::store(u32 *values) -> void
|
||||
{
|
||||
const Tag d;
|
||||
auto IntVec4::store(Mut<u32 *> values) -> void {
|
||||
Const<Tag> d;
|
||||
hn::Store(m_data, d, values);
|
||||
}
|
||||
}
|
||||
|
||||
auto IntVec4::load(const u32 *values) -> IntVec4
|
||||
{
|
||||
const Tag d;
|
||||
auto IntVec4::load(Const<const u32 *> values) -> IntVec4 {
|
||||
Const<Tag> d;
|
||||
return IntVec4(hn::Load(d, values));
|
||||
}
|
||||
}
|
||||
} // namespace IACore
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
FloatVec4::FloatVec4(f32 s)
|
||||
{
|
||||
const Tag d;
|
||||
namespace IACore {
|
||||
FloatVec4::FloatVec4(Const<f32> s) {
|
||||
Const<Tag> d;
|
||||
m_data = hn::Set(d, s);
|
||||
}
|
||||
}
|
||||
|
||||
FloatVec4::FloatVec4(const f32 *values)
|
||||
{
|
||||
const Tag d;
|
||||
FloatVec4::FloatVec4(Const<const f32 *> values) {
|
||||
Const<Tag> d;
|
||||
m_data = hn::Load(d, values);
|
||||
}
|
||||
}
|
||||
|
||||
FloatVec4::FloatVec4(f32 a, f32 b, f32 c, f32 d)
|
||||
{
|
||||
const Tag data;
|
||||
alignas(16) f32 temp[4] = {a, b, c, d};
|
||||
m_data = hn::Load(data, temp);
|
||||
}
|
||||
FloatVec4::FloatVec4(Const<f32> a, Const<f32> b, Const<f32> c, Const<f32> d) {
|
||||
Const<Tag> data;
|
||||
alignas(16) Mut<Array<f32, 4>> temp = {a, b, c, d};
|
||||
m_data = hn::Load(data, temp.data());
|
||||
}
|
||||
|
||||
auto FloatVec4::operator+(const FloatVec4 &other) const -> FloatVec4
|
||||
{
|
||||
auto FloatVec4::operator+(Ref<FloatVec4> other) const -> FloatVec4 {
|
||||
return FloatVec4(hn::Add(m_data, other.m_data));
|
||||
}
|
||||
}
|
||||
|
||||
auto FloatVec4::operator-(const FloatVec4 &other) const -> FloatVec4
|
||||
{
|
||||
auto FloatVec4::operator-(Ref<FloatVec4> other) const -> FloatVec4 {
|
||||
return FloatVec4(hn::Sub(m_data, other.m_data));
|
||||
}
|
||||
}
|
||||
|
||||
auto FloatVec4::operator*(const FloatVec4 &other) const -> FloatVec4
|
||||
{
|
||||
auto FloatVec4::operator*(Ref<FloatVec4> other) const -> FloatVec4 {
|
||||
return FloatVec4(hn::Mul(m_data, other.m_data));
|
||||
}
|
||||
}
|
||||
|
||||
auto FloatVec4::operator/(const FloatVec4 &other) const -> FloatVec4
|
||||
{
|
||||
auto FloatVec4::operator/(Ref<FloatVec4> other) const -> FloatVec4 {
|
||||
return FloatVec4(hn::Div(m_data, other.m_data));
|
||||
}
|
||||
}
|
||||
|
||||
auto FloatVec4::mult_add(const FloatVec4 &multiplier, const FloatVec4 &addend) const -> FloatVec4
|
||||
{
|
||||
auto FloatVec4::mult_add(Ref<FloatVec4> multiplier, Ref<FloatVec4> addend) const
|
||||
-> FloatVec4 {
|
||||
return FloatVec4(hn::MulAdd(m_data, multiplier.m_data, addend.m_data));
|
||||
}
|
||||
}
|
||||
|
||||
auto FloatVec4::clamp(f32 min, f32 max) const -> FloatVec4
|
||||
{
|
||||
const Tag d;
|
||||
auto vMin = hn::Set(d, min);
|
||||
auto vMax = hn::Set(d, max);
|
||||
return FloatVec4(hn::Min(hn::Max(m_data, vMin), vMax));
|
||||
}
|
||||
auto FloatVec4::clamp(Const<f32> min, Const<f32> max) const -> FloatVec4 {
|
||||
Const<Tag> d;
|
||||
Const<hn::Vec<Tag>> v_min = hn::Set(d, min);
|
||||
Const<hn::Vec<Tag>> v_max = hn::Set(d, max);
|
||||
return FloatVec4(hn::Min(hn::Max(m_data, v_min), v_max));
|
||||
}
|
||||
|
||||
auto FloatVec4::sqrt() const -> FloatVec4
|
||||
{
|
||||
auto FloatVec4::sqrt() const -> FloatVec4 {
|
||||
return FloatVec4(hn::Sqrt(m_data));
|
||||
}
|
||||
}
|
||||
|
||||
auto FloatVec4::rsqrt() const -> FloatVec4
|
||||
{
|
||||
auto FloatVec4::rsqrt() const -> FloatVec4 {
|
||||
return FloatVec4(hn::ApproximateReciprocalSqrt(m_data));
|
||||
}
|
||||
}
|
||||
|
||||
auto FloatVec4::abs() const -> FloatVec4
|
||||
{
|
||||
return FloatVec4(hn::Abs(m_data));
|
||||
}
|
||||
auto FloatVec4::abs() const -> FloatVec4 { return FloatVec4(hn::Abs(m_data)); }
|
||||
|
||||
auto FloatVec4::dot(const FloatVec4 &other) const -> f32
|
||||
{
|
||||
const Tag d;
|
||||
auto vMul = hn::Mul(m_data, other.m_data);
|
||||
return hn::ReduceSum(d, vMul);
|
||||
}
|
||||
auto FloatVec4::dot(Ref<FloatVec4> other) const -> f32 {
|
||||
Const<Tag> d;
|
||||
Const<hn::Vec<Tag>> v_mul = hn::Mul(m_data, other.m_data);
|
||||
return hn::ReduceSum(d, v_mul);
|
||||
}
|
||||
|
||||
auto FloatVec4::normalize() const -> FloatVec4
|
||||
{
|
||||
const Tag d;
|
||||
auto vMul = hn::Mul(m_data, m_data);
|
||||
auto vLenSq = hn::SumOfLanes(d, vMul);
|
||||
auto vInvLen = hn::ApproximateReciprocalSqrt(vLenSq);
|
||||
return FloatVec4(hn::Mul(m_data, vInvLen));
|
||||
}
|
||||
auto FloatVec4::normalize() const -> FloatVec4 {
|
||||
Const<Tag> d;
|
||||
Const<hn::Vec<Tag>> v_mul = hn::Mul(m_data, m_data);
|
||||
Const<hn::Vec<Tag>> v_len_sq = hn::SumOfLanes(d, v_mul);
|
||||
Const<hn::Vec<Tag>> v_inv_len = hn::ApproximateReciprocalSqrt(v_len_sq);
|
||||
return FloatVec4(hn::Mul(m_data, v_inv_len));
|
||||
}
|
||||
|
||||
auto FloatVec4::store(f32 *values) -> void
|
||||
{
|
||||
const Tag d;
|
||||
auto FloatVec4::store(Mut<f32 *> values) -> void {
|
||||
Const<Tag> d;
|
||||
hn::Store(m_data, d, values);
|
||||
}
|
||||
}
|
||||
|
||||
auto FloatVec4::load(const f32 *values) -> FloatVec4
|
||||
{
|
||||
const Tag d;
|
||||
auto FloatVec4::load(Const<const f32 *> values) -> FloatVec4 {
|
||||
Const<Tag> d;
|
||||
return FloatVec4(hn::Load(d, values));
|
||||
}
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -27,8 +27,6 @@
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
// Define INVALID_SOCKET for Unix to match Windows API for cross-platform
|
||||
// compatibility
|
||||
#ifndef INVALID_SOCKET
|
||||
#define INVALID_SOCKET -1
|
||||
#endif
|
||||
@ -40,7 +38,7 @@ namespace IACore {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
using SocketHandle = SOCKET;
|
||||
#elif IA_PLATFORM_UNIX
|
||||
using SocketHandle = int;
|
||||
using SocketHandle = i32;
|
||||
#endif
|
||||
|
||||
class SocketOps {
|
||||
@ -54,8 +52,8 @@ public:
|
||||
return {};
|
||||
}
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
WSADATA wsa_data;
|
||||
const auto res = WSAStartup(MAKEWORD(2, 2), &wsa_data);
|
||||
Mut<WSADATA> wsa_data;
|
||||
Const<i32> res = WSAStartup(MAKEWORD(2, 2), &wsa_data);
|
||||
if (res != 0) {
|
||||
s_init_count--;
|
||||
return fail("WSAStartup failed with error: {}", res);
|
||||
@ -79,28 +77,29 @@ public:
|
||||
|
||||
static auto is_initialized() -> bool { return s_init_count > 0; }
|
||||
|
||||
static auto is_port_available_tcp(u16 port) -> bool {
|
||||
static auto is_port_available_tcp(Const<u16> port) -> bool {
|
||||
return is_port_available(port, SOCK_STREAM);
|
||||
}
|
||||
|
||||
static auto is_port_available_udp(u16 port) -> bool {
|
||||
static auto is_port_available_udp(Const<u16> port) -> bool {
|
||||
return is_port_available(port, SOCK_DGRAM);
|
||||
}
|
||||
|
||||
static auto is_would_block() -> bool;
|
||||
|
||||
static auto close(SocketHandle sock) -> void;
|
||||
static auto close(Const<SocketHandle> sock) -> void;
|
||||
|
||||
static auto listen(SocketHandle sock, i32 queue_size = 5) -> Result<void>;
|
||||
static auto listen(Const<SocketHandle> sock, Const<i32> queue_size = 5)
|
||||
-> Result<void>;
|
||||
|
||||
static auto create_unix_socket() -> Result<SocketHandle>;
|
||||
|
||||
static auto bind_unix_socket(SocketHandle sock, const char *path)
|
||||
-> Result<void>;
|
||||
static auto connect_unix_socket(SocketHandle sock, const char *path)
|
||||
-> Result<void>;
|
||||
static auto bind_unix_socket(Const<SocketHandle> sock,
|
||||
Const<const char *> path) -> Result<void>;
|
||||
static auto connect_unix_socket(Const<SocketHandle> sock,
|
||||
Const<const char *> path) -> Result<void>;
|
||||
|
||||
static auto unlink_file(const char *path) -> void {
|
||||
static auto unlink_file(Const<const char *> path) -> void {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
DeleteFileA(path);
|
||||
#elif IA_PLATFORM_UNIX
|
||||
@ -109,9 +108,9 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
static auto is_port_available(u16 port, i32 type) -> bool;
|
||||
static auto is_port_available(Const<u16> port, Const<i32> type) -> bool;
|
||||
|
||||
private:
|
||||
static i32 s_init_count;
|
||||
static Mut<i32> s_init_count;
|
||||
};
|
||||
} // namespace IACore
|
||||
@ -28,29 +28,29 @@ public:
|
||||
OwningVector,
|
||||
};
|
||||
|
||||
static auto create_from_file(const Path &path) -> Result<StreamReader>;
|
||||
static auto create_from_file(Ref<Path> path) -> Result<StreamReader>;
|
||||
|
||||
explicit StreamReader(Vec<u8> &&data);
|
||||
explicit StreamReader(Span<const u8> data);
|
||||
explicit StreamReader(ForwardRef<Vec<u8>> data);
|
||||
explicit StreamReader(Const<Span<const u8>> data);
|
||||
~StreamReader();
|
||||
|
||||
StreamReader(StreamReader &&other);
|
||||
auto operator=(StreamReader &&other) -> StreamReader &;
|
||||
StreamReader(ForwardRef<StreamReader> other);
|
||||
auto operator=(ForwardRef<StreamReader> other) -> MutRef<StreamReader>;
|
||||
|
||||
StreamReader(const StreamReader &) = delete;
|
||||
auto operator=(const StreamReader &) -> StreamReader & = delete;
|
||||
StreamReader(Ref<StreamReader>) = delete;
|
||||
auto operator=(Ref<StreamReader>) -> MutRef<StreamReader> = delete;
|
||||
|
||||
auto read(void *buffer, usize size) -> Result<void>;
|
||||
auto read(Mut<void *> buffer, Const<usize> size) -> Result<void>;
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard("Check for EOF")]]
|
||||
auto read() -> Result<T>;
|
||||
|
||||
auto skip(usize amount) -> void {
|
||||
auto skip(Const<usize> amount) -> void {
|
||||
m_cursor = std::min(m_cursor + amount, m_data_size);
|
||||
}
|
||||
|
||||
auto seek(usize pos) -> void {
|
||||
auto seek(Const<usize> pos) -> void {
|
||||
m_cursor = (pos > m_data_size) ? m_data_size : pos;
|
||||
}
|
||||
|
||||
@ -65,14 +65,15 @@ public:
|
||||
[[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;
|
||||
Mut<const u8 *> m_data = nullptr;
|
||||
Mut<usize> m_cursor = 0;
|
||||
Mut<usize> m_data_size = 0;
|
||||
Mut<Vec<u8>> m_owning_vector;
|
||||
Mut<StorageType> m_storage_type = StorageType::NonOwning;
|
||||
};
|
||||
|
||||
inline auto StreamReader::read(void *buffer, usize size) -> Result<void> {
|
||||
inline auto StreamReader::read(Mut<void *> buffer, Const<usize> size)
|
||||
-> Result<void> {
|
||||
if (m_cursor + size > m_data_size) [[unlikely]] {
|
||||
return fail("Unexpected EOF while reading");
|
||||
}
|
||||
@ -89,13 +90,13 @@ inline auto StreamReader::read() -> Result<T> {
|
||||
static_assert(std::is_trivially_copyable_v<T>,
|
||||
"T must be trivially copyable to read via memcpy");
|
||||
|
||||
constexpr usize SIZE = sizeof(T);
|
||||
constexpr Const<usize> SIZE = sizeof(T);
|
||||
|
||||
if (m_cursor + SIZE > m_data_size) [[unlikely]] {
|
||||
return fail("Unexpected EOF while reading");
|
||||
}
|
||||
|
||||
T value;
|
||||
Mut<T> value;
|
||||
std::memcpy(&value, &m_data[m_cursor], SIZE);
|
||||
m_cursor += SIZE;
|
||||
|
||||
|
||||
@ -27,23 +27,23 @@ public:
|
||||
OwningVector,
|
||||
};
|
||||
|
||||
static auto create_from_file(const Path &path) -> Result<StreamWriter>;
|
||||
static auto create_from_file(Ref<Path> path) -> Result<StreamWriter>;
|
||||
|
||||
StreamWriter();
|
||||
explicit StreamWriter(Span<u8> data);
|
||||
explicit StreamWriter(Const<Span<u8>> data);
|
||||
|
||||
StreamWriter(StreamWriter &&other);
|
||||
auto operator=(StreamWriter &&other) -> StreamWriter &;
|
||||
StreamWriter(ForwardRef<StreamWriter> other);
|
||||
auto operator=(ForwardRef<StreamWriter> other) -> MutRef<StreamWriter>;
|
||||
|
||||
StreamWriter(const StreamWriter &) = delete;
|
||||
auto operator=(const StreamWriter &) -> StreamWriter & = delete;
|
||||
StreamWriter(Ref<StreamWriter>) = delete;
|
||||
auto operator=(Ref<StreamWriter>) -> MutRef<StreamWriter> = delete;
|
||||
|
||||
~StreamWriter();
|
||||
|
||||
auto write(u8 byte, usize count) -> Result<void>;
|
||||
auto write(const void *buffer, usize size) -> Result<void>;
|
||||
auto write(Const<u8> byte, Const<usize> count) -> Result<void>;
|
||||
auto write(Const<const void *> buffer, Const<usize> size) -> Result<void>;
|
||||
|
||||
template <typename T> auto write(const T &value) -> Result<void>;
|
||||
template <typename T> auto write(Ref<T> value) -> Result<void>;
|
||||
|
||||
[[nodiscard]] auto data() const -> const u8 * { return m_buffer; }
|
||||
|
||||
@ -52,19 +52,19 @@ public:
|
||||
auto flush() -> Result<void>;
|
||||
|
||||
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;
|
||||
Mut<u8 *> m_buffer = nullptr;
|
||||
Mut<usize> m_cursor = 0;
|
||||
Mut<usize> m_capacity = 0;
|
||||
Mut<Path> m_file_path;
|
||||
Mut<Vec<u8>> m_owning_vector;
|
||||
Mut<StorageType> m_storage_type = StorageType::OwningVector;
|
||||
|
||||
private:
|
||||
auto flush_to_disk() -> Result<void>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
inline auto StreamWriter::write(const T &value) -> Result<void> {
|
||||
inline auto StreamWriter::write(Ref<T> value) -> Result<void> {
|
||||
return write(&value, sizeof(T));
|
||||
}
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
namespace IACore {
|
||||
class StringOps {
|
||||
public:
|
||||
static auto encode_base64(Span<const u8> data) -> String;
|
||||
static auto decode_base64(const String &data) -> Vec<u8>;
|
||||
static auto encode_base64(Const<Span<Const<u8>>> data) -> String;
|
||||
static auto decode_base64(Ref<String> data) -> Vec<u8>;
|
||||
};
|
||||
} // namespace IACore
|
||||
@ -19,11 +19,9 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
class Utils
|
||||
{
|
||||
public:
|
||||
namespace IACore {
|
||||
class Utils {
|
||||
public:
|
||||
static auto get_unix_time() -> u64;
|
||||
|
||||
static auto get_ticks_count() -> u64;
|
||||
@ -31,64 +29,63 @@ namespace IACore
|
||||
static auto get_seconds_count() -> f64;
|
||||
|
||||
static auto get_random() -> f32;
|
||||
static auto get_random(u64 max) -> u64;
|
||||
static auto get_random(i64 min, i64 max) -> i64;
|
||||
static auto get_random(Const<u64> max) -> u64;
|
||||
static auto get_random(Const<i64> min, Const<i64> max) -> i64;
|
||||
|
||||
static auto sleep(u64 milliseconds) -> void;
|
||||
static auto sleep(Const<u64> milliseconds) -> void;
|
||||
|
||||
static auto binary_to_hex_string(Span<const u8> data) -> String;
|
||||
static auto binary_to_hex_string(Const<Span<Const<u8>>> data) -> String;
|
||||
|
||||
static auto hex_string_to_binary(StringView hex) -> Result<Vec<u8>>;
|
||||
static auto hex_string_to_binary(Const<StringView> hex) -> Result<Vec<u8>>;
|
||||
|
||||
template<typename Range> inline static void sort(Range &&range)
|
||||
{
|
||||
std::ranges::sort(range);
|
||||
template <typename Range>
|
||||
inline static auto sort(ForwardRef<Range> range) -> void {
|
||||
std::ranges::sort(std::forward<Range>(range));
|
||||
}
|
||||
|
||||
template<typename Range, typename T> inline static auto binary_search_left(Range &&range, const T &value)
|
||||
{
|
||||
return std::ranges::lower_bound(range, value);
|
||||
template <typename Range, typename T>
|
||||
inline static auto binary_search_left(ForwardRef<Range> range, Ref<T> value)
|
||||
-> auto {
|
||||
return std::ranges::lower_bound(std::forward<Range>(range), value);
|
||||
}
|
||||
|
||||
template<typename Range, typename T> inline static auto binary_search_right(Range &&range, const T &value)
|
||||
{
|
||||
return std::ranges::upper_bound(range, value);
|
||||
template <typename Range, typename T>
|
||||
inline static auto binary_search_right(ForwardRef<Range> range, Ref<T> value)
|
||||
-> auto {
|
||||
return std::ranges::upper_bound(std::forward<Range>(range), value);
|
||||
}
|
||||
|
||||
template<typename T> inline static void hash_combine(u64 &seed, const T &v)
|
||||
{
|
||||
u64 h;
|
||||
template <typename T>
|
||||
inline static auto hash_combine(MutRef<u64> seed, Ref<T> v) -> void {
|
||||
Mut<u64> h = 0;
|
||||
|
||||
if constexpr (std::is_constructible_v<StringView, T>)
|
||||
{
|
||||
StringView sv(v);
|
||||
auto hasher = ankerl::unordered_dense::hash<StringView>();
|
||||
if constexpr (std::is_constructible_v<StringView, T>) {
|
||||
Const<StringView> sv(v);
|
||||
Const<ankerl::unordered_dense::hash<StringView>> hasher;
|
||||
h = hasher(sv);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto hasher = ankerl::unordered_dense::hash<T>();
|
||||
} else {
|
||||
Const<ankerl::unordered_dense::hash<T>> hasher;
|
||||
h = hasher(v);
|
||||
}
|
||||
|
||||
seed ^= h + 0x9e3779b97f4a7c15 + (seed << 6) + (seed >> 2);
|
||||
}
|
||||
|
||||
template<typename... Args> inline static auto compute_hash(const Args &...args) -> u64
|
||||
{
|
||||
u64 seed = 0;
|
||||
template <typename... Args>
|
||||
inline static auto compute_hash(Ref<Args>... args) -> u64 {
|
||||
Mut<u64> seed = 0;
|
||||
(hash_combine(seed, args), ...);
|
||||
return seed;
|
||||
}
|
||||
|
||||
template<typename T, typename... MemberPtrs>
|
||||
inline static auto compute_hash_flat(const T &obj, MemberPtrs... members) -> u64
|
||||
{
|
||||
u64 seed = 0;
|
||||
template <typename T, typename... MemberPtrs>
|
||||
inline static auto compute_hash_flat(Ref<T> obj, Const<MemberPtrs>... members)
|
||||
-> u64 {
|
||||
Mut<u64> seed = 0;
|
||||
(hash_combine(seed, obj.*members), ...);
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
};
|
||||
} // namespace IACore
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@ -101,13 +98,10 @@ namespace IACore
|
||||
// IA_MAKE_HASHABLE(Vector3, &Vector3::x, &Vector3::y, &Vector3::z)
|
||||
// -----------------------------------------------------------------------------
|
||||
#define IA_MAKE_HASHABLE(Type, ...) \
|
||||
template<> struct ankerl::unordered_dense::hash<Type> \
|
||||
{ \
|
||||
template <> struct ankerl::unordered_dense::hash<Type> { \
|
||||
using is_avalanching = void; \
|
||||
IA_NODISCARD \
|
||||
auto operator()(const Type &v) const noexcept -> IACore::u64 \
|
||||
{ \
|
||||
/* Pass the object and the list of member pointers */ \
|
||||
auto operator()(IACore::Ref<Type> v) const noexcept -> IACore::u64 { \
|
||||
return IACore::Utils::compute_hash_flat(v, __VA_ARGS__); \
|
||||
} \
|
||||
};
|
||||
@ -19,21 +19,21 @@
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
class XML
|
||||
{
|
||||
public:
|
||||
namespace IACore {
|
||||
class XML {
|
||||
public:
|
||||
using Node = pugi::xml_node;
|
||||
using Document = pugi::xml_document;
|
||||
|
||||
public:
|
||||
static auto parse_from_string(const String &data) -> Result<Document>;
|
||||
static auto parse_from_file(const Path &path) -> Result<Document>;
|
||||
public:
|
||||
static auto parse_from_string(Ref<String> data) -> Result<Document>;
|
||||
static auto parse_from_file(Ref<Path> path) -> Result<Document>;
|
||||
|
||||
static auto serialize_to_string(const Node &node, bool escape = false) -> String;
|
||||
static auto serialize_to_string(const Document &doc, bool escape = false) -> String;
|
||||
static auto serialize_to_string(Ref<Node> node, Const<bool> escape = false)
|
||||
-> String;
|
||||
static auto serialize_to_string(Ref<Document> doc, Const<bool> escape = false)
|
||||
-> String;
|
||||
|
||||
static auto escape_xml_string(const String &xml) -> String;
|
||||
};
|
||||
static auto escape_xml_string(Ref<String> xml) -> String;
|
||||
};
|
||||
} // namespace IACore
|
||||
File diff suppressed because it is too large
Load Diff
@ -23,7 +23,7 @@ using namespace IACore;
|
||||
IACORE_MAIN() {
|
||||
(void)args;
|
||||
|
||||
IA_TRY_PURE(SocketOps::initialize());
|
||||
OX_TRY_PURE(SocketOps::initialize());
|
||||
|
||||
std::cout
|
||||
<< console::GREEN
|
||||
|
||||
@ -192,7 +192,7 @@ auto test_multi_line() -> bool {
|
||||
bool found_a = false;
|
||||
bool found_b = false;
|
||||
|
||||
IA_UNUSED const auto res =
|
||||
const auto res =
|
||||
ProcessOps::spawn_process_sync(cmd, arg, [&](StringView line) {
|
||||
line_count++;
|
||||
if (line.find("LineA") != String::npos) {
|
||||
@ -202,6 +202,7 @@ auto test_multi_line() -> bool {
|
||||
found_b = true;
|
||||
}
|
||||
});
|
||||
IAT_CHECK(res.has_value());
|
||||
|
||||
IAT_CHECK(found_a);
|
||||
IAT_CHECK(found_b);
|
||||
|
||||
Reference in New Issue
Block a user