This commit is contained in:
@ -139,8 +139,7 @@ auto AsyncOps::wait_for_schedule_completion(Schedule *schedule) -> void {
|
||||
}
|
||||
|
||||
auto AsyncOps::get_worker_count() -> WorkerId {
|
||||
// +1 for MainThread (Work Stealing)
|
||||
return static_cast<WorkerId>(s_schedule_workers.size() + 1);
|
||||
return static_cast<WorkerId>(s_schedule_workers.size());
|
||||
}
|
||||
|
||||
auto AsyncOps::schedule_worker_loop(std::stop_token stop_token,
|
||||
|
||||
@ -15,16 +15,12 @@
|
||||
|
||||
#include <IACore/CLI.hpp>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
CLIParser::CLIParser(Span<const String> args) : m_argList(args)
|
||||
{
|
||||
assert(args.size());
|
||||
namespace IACore {
|
||||
CLIParser::CLIParser(Span<const String> args) : m_arg_list(args) {
|
||||
m_current_arg = m_arg_list.begin();
|
||||
|
||||
m_currentArg = m_argList.begin();
|
||||
|
||||
// Skip executable path
|
||||
if (m_currentArg != m_argList.end())
|
||||
m_currentArg++;
|
||||
}
|
||||
// Skip executable path
|
||||
if (m_current_arg != m_arg_list.end())
|
||||
m_current_arg++;
|
||||
}
|
||||
} // namespace IACore
|
||||
@ -68,7 +68,7 @@ auto FileOps::map_shared_memory(const String &name, usize size, bool is_owner)
|
||||
(DWORD)(size >> 32), (DWORD)(size & 0xFFFFFFFF),
|
||||
w_name.c_str());
|
||||
} else {
|
||||
h_map = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, w_name.c_str());
|
||||
h_map = OpenFileMappingW(FILE_MAP_ALL_ACCESS, false, w_name.c_str());
|
||||
}
|
||||
|
||||
if (h_map == NULL) {
|
||||
@ -201,7 +201,7 @@ auto FileOps::stream_to_file(const Path &path, bool overwrite)
|
||||
if (!overwrite && std::filesystem::exists(path)) {
|
||||
return fail("File already exists: {}", path.string());
|
||||
}
|
||||
return StreamWriter::create(path);
|
||||
return StreamWriter::create_from_file(path);
|
||||
}
|
||||
|
||||
auto FileOps::stream_from_file(const Path &path) -> Result<StreamReader> {
|
||||
|
||||
@ -133,7 +133,7 @@ void ProcessOps::terminate_process(const Box<ProcessHandle> &handle) {
|
||||
}
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
HANDLE h_process = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
|
||||
HANDLE h_process = OpenProcess(PROCESS_TERMINATE, false, pid);
|
||||
if (h_process != NULL) {
|
||||
::TerminateProcess(h_process, 9);
|
||||
CloseHandle(h_process);
|
||||
@ -150,7 +150,7 @@ auto ProcessOps::spawn_process_windows(
|
||||
std::atomic<NativeProcessID> &id) -> Result<i32> {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
SECURITY_ATTRIBUTES sa_attr = {sizeof(SECURITY_ATTRIBUTES), NULL,
|
||||
TRUE}; // Allow inheritance
|
||||
true}; // Allow inheritance
|
||||
HANDLE h_read = NULL;
|
||||
HANDLE h_write = NULL;
|
||||
|
||||
@ -174,7 +174,7 @@ auto ProcessOps::spawn_process_windows(
|
||||
// Windows command line needs to be mutable and concatenated
|
||||
String command_line = std::format("\"{}\" {}", command, args);
|
||||
|
||||
BOOL success = CreateProcessA(NULL, command_line.data(), NULL, NULL, TRUE, 0,
|
||||
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!
|
||||
|
||||
@ -40,6 +40,41 @@ StreamReader::StreamReader(Span<const u8> data)
|
||||
: m_data(data.data()), m_data_size(data.size()),
|
||||
m_storage_type(StorageType::NonOwning) {}
|
||||
|
||||
StreamReader::StreamReader(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)),
|
||||
m_storage_type(other.m_storage_type) {
|
||||
other.m_storage_type = StorageType::NonOwning;
|
||||
other.m_data = {};
|
||||
other.m_data_size = 0;
|
||||
|
||||
if (m_storage_type == StorageType::OwningVector)
|
||||
m_data = m_owning_vector.data();
|
||||
}
|
||||
|
||||
auto StreamReader::operator=(StreamReader &&other) -> StreamReader & {
|
||||
if (this != &other) {
|
||||
if (m_storage_type == StorageType::OwningMmap) {
|
||||
FileOps::unmap_file(m_data);
|
||||
}
|
||||
|
||||
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);
|
||||
m_storage_type = other.m_storage_type;
|
||||
|
||||
if (m_storage_type == StorageType::OwningVector)
|
||||
m_data = m_owning_vector.data();
|
||||
|
||||
other.m_storage_type = StorageType::NonOwning;
|
||||
other.m_data = {};
|
||||
other.m_data_size = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
StreamReader::~StreamReader() {
|
||||
if (m_storage_type == StorageType::OwningMmap) {
|
||||
FileOps::unmap_file(m_data);
|
||||
|
||||
@ -17,9 +17,7 @@
|
||||
|
||||
namespace IACore {
|
||||
|
||||
auto StreamWriter::create(const Path &path) -> Result<StreamWriter> {
|
||||
// Try to open the file to ensure we have write permissions and to truncate
|
||||
// it.
|
||||
auto StreamWriter::create_from_file(const Path &path) -> Result<StreamWriter> {
|
||||
FILE *f = std::fopen(path.string().c_str(), "wb");
|
||||
if (!f) {
|
||||
return fail("Failed to open file for writing: {}", path.string());
|
||||
@ -43,34 +41,97 @@ StreamWriter::StreamWriter(Span<u8> data)
|
||||
: m_buffer(data.data()), m_cursor(0), m_capacity(data.size()),
|
||||
m_storage_type(StorageType::NonOwning) {}
|
||||
|
||||
StreamWriter::~StreamWriter() {
|
||||
if (m_storage_type == StorageType::OwningFile) {
|
||||
if (m_file_path.empty()) {
|
||||
return;
|
||||
StreamWriter::StreamWriter(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)),
|
||||
m_storage_type(other.m_storage_type) {
|
||||
other.m_capacity = {};
|
||||
other.m_buffer = {};
|
||||
other.m_storage_type = StorageType::NonOwning;
|
||||
|
||||
if (m_storage_type == StorageType::OwningVector)
|
||||
m_buffer = m_owning_vector.data();
|
||||
}
|
||||
|
||||
auto StreamWriter::operator=(StreamWriter &&other) -> StreamWriter & {
|
||||
if (this != &other) {
|
||||
// Flush current if needed
|
||||
if (m_storage_type == StorageType::OwningFile) {
|
||||
if (auto res = flush_to_disk(); !res) {
|
||||
std::fprintf(stderr, "[IACore] Data loss in StreamWriter move: %s\n",
|
||||
res.error().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
FILE *f = std::fopen(m_file_path.string().c_str(), "wb");
|
||||
if (f) {
|
||||
// Write the actual data accumulated in the buffer.
|
||||
std::fwrite(m_buffer, 1, m_cursor, f);
|
||||
std::fclose(f);
|
||||
} else {
|
||||
// Logger is disabled, print to stderr as a fallback for data loss
|
||||
// warning.
|
||||
std::fprintf(stderr, "[IACore] StreamWriter failed to save file: %s\n",
|
||||
m_file_path.string().c_str());
|
||||
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_owning_vector = std::move(other.m_owning_vector);
|
||||
m_storage_type = other.m_storage_type;
|
||||
|
||||
if (m_storage_type == StorageType::OwningVector)
|
||||
m_buffer = m_owning_vector.data();
|
||||
|
||||
other.m_capacity = 0;
|
||||
other.m_cursor = 0;
|
||||
other.m_buffer = nullptr;
|
||||
other.m_storage_type = StorageType::NonOwning;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
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) {
|
||||
std::fprintf(stderr, "[IACore] LOST DATA in ~StreamWriter: %s\n",
|
||||
res.error().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto StreamWriter::flush() -> Result<void> {
|
||||
auto res = flush_to_disk();
|
||||
// Prevent double-write in destructor if save was successful
|
||||
if (res.has_value()) {
|
||||
m_storage_type = StorageType::OwningVector; // downgrade to vector so dtor
|
||||
// doesn't write again
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
auto StreamWriter::flush_to_disk() -> Result<void> {
|
||||
if (m_storage_type != StorageType::OwningFile || m_file_path.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
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);
|
||||
std::fclose(f);
|
||||
|
||||
if (written != m_cursor) {
|
||||
return fail("Incomplete write: {} of {} bytes written", written, m_cursor);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
auto StreamWriter::write(u8 byte, usize count) -> Result<void> {
|
||||
if (m_cursor + count > m_capacity) {
|
||||
if (m_storage_type == StorageType::NonOwning) {
|
||||
return fail("StreamWriter buffer overflow (NonOwning)");
|
||||
}
|
||||
|
||||
// Growth strategy: Current capacity + (count * 2)
|
||||
const usize new_capacity = m_capacity + (count << 1);
|
||||
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();
|
||||
m_buffer = m_owning_vector.data();
|
||||
@ -87,7 +148,12 @@ auto StreamWriter::write(const void *buffer, usize size) -> Result<void> {
|
||||
return fail("StreamWriter buffer overflow (NonOwning)");
|
||||
}
|
||||
|
||||
const usize new_capacity = m_capacity + (size << 1);
|
||||
// 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;
|
||||
|
||||
m_owning_vector.resize(new_capacity);
|
||||
m_capacity = m_owning_vector.size();
|
||||
m_buffer = m_owning_vector.data();
|
||||
|
||||
@ -16,28 +16,53 @@
|
||||
#include <IACore/StringOps.hpp>
|
||||
|
||||
namespace IACore {
|
||||
const String BASE64_CHAR_TABLE =
|
||||
|
||||
static const String BASE64_CHAR_TABLE =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
static auto is_base64(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 {
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
return c - 'A';
|
||||
if (c >= 'a' && c <= 'z')
|
||||
return c - 'a' + 26;
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0' + 52;
|
||||
if (c == '+')
|
||||
return 62;
|
||||
if (c == '/')
|
||||
return 63;
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto StringOps::encode_base64(Span<const u8> data) -> String {
|
||||
String result;
|
||||
result.reserve(((data.size() + 2) / 3) * 4);
|
||||
for (size_t i = 0; i < data.size(); i += 3) {
|
||||
uint32_t value = 0;
|
||||
i32 num_bytes = 0;
|
||||
for (i32 j = 0; j < 3 && (i + j) < data.size(); ++j) {
|
||||
value = (value << 8) | data[i + j];
|
||||
num_bytes++;
|
||||
|
||||
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;
|
||||
|
||||
u32 triple = (b0 << 16) | (b1 << 8) | b2;
|
||||
|
||||
result += BASE64_CHAR_TABLE[(triple >> 18) & 0x3F];
|
||||
result += BASE64_CHAR_TABLE[(triple >> 12) & 0x3F];
|
||||
|
||||
if (i + 1 < data.size()) {
|
||||
result += BASE64_CHAR_TABLE[(triple >> 6) & 0x3F];
|
||||
} else {
|
||||
result += '=';
|
||||
}
|
||||
for (i32 j = 0; j < num_bytes + 1; ++j) {
|
||||
if (j < 4) {
|
||||
result += BASE64_CHAR_TABLE[(value >> (6 * (3 - j))) & 0x3F];
|
||||
}
|
||||
}
|
||||
if (num_bytes < 3) {
|
||||
for (i32 j = 0; j < (3 - num_bytes); ++j) {
|
||||
result += '=';
|
||||
}
|
||||
|
||||
if (i + 2 < data.size()) {
|
||||
result += BASE64_CHAR_TABLE[triple & 0x3F];
|
||||
} else {
|
||||
result += '=';
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@ -45,47 +70,53 @@ auto StringOps::encode_base64(Span<const u8> data) -> String {
|
||||
|
||||
auto StringOps::decode_base64(const String &data) -> Vec<u8> {
|
||||
Vec<u8> result;
|
||||
result.reserve(data.size() * 3 / 4);
|
||||
|
||||
const auto is_base64 = [](u8 c) {
|
||||
return (isalnum(c) || (c == '+') || (c == '/'));
|
||||
};
|
||||
i32 i = 0;
|
||||
u8 tmp_buf[4];
|
||||
|
||||
i32 in_len = data.size();
|
||||
i32 i = 0, j = 0, in = 0;
|
||||
u8 tmp_buf0[4], tmp_buf1[3];
|
||||
for (char c_char : data) {
|
||||
u8 c = static_cast<u8>(c_char);
|
||||
if (c == '=') {
|
||||
break;
|
||||
}
|
||||
if (!is_base64(c)) {
|
||||
break;
|
||||
}
|
||||
|
||||
while (in_len-- && (data[in] != '=') && is_base64(data[in])) {
|
||||
tmp_buf0[i++] = data[in];
|
||||
in++;
|
||||
tmp_buf[i++] = c;
|
||||
if (i == 4) {
|
||||
for (i = 0; i < 4; i++)
|
||||
tmp_buf0[i] = BASE64_CHAR_TABLE.find(tmp_buf0[i]);
|
||||
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]);
|
||||
|
||||
tmp_buf1[0] = (tmp_buf0[0] << 2) + ((tmp_buf0[1] & 0x30) >> 4);
|
||||
tmp_buf1[1] = ((tmp_buf0[1] & 0xf) << 4) + ((tmp_buf0[2] & 0x3c) >> 2);
|
||||
tmp_buf1[2] = ((tmp_buf0[2] & 0x3) << 6) + tmp_buf0[3];
|
||||
result.push_back((n0 << 2) | ((n1 & 0x30) >> 4));
|
||||
result.push_back(((n1 & 0x0F) << 4) | ((n2 & 0x3C) >> 2));
|
||||
result.push_back(((n2 & 0x03) << 6) | n3);
|
||||
|
||||
for (i = 0; (i < 3); i++)
|
||||
result.push_back(tmp_buf1[i]);
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (i) {
|
||||
for (j = i; j < 4; j++)
|
||||
tmp_buf0[j] = 0;
|
||||
if (i > 0) {
|
||||
for (i32 j = i; j < 4; ++j) {
|
||||
tmp_buf[j] = 'A'; // Pad with 'A' (index 0)
|
||||
}
|
||||
|
||||
for (j = 0; j < 4; j++)
|
||||
tmp_buf0[j] = BASE64_CHAR_TABLE.find(tmp_buf0[j]);
|
||||
u8 n0 = get_base64_index(tmp_buf[0]);
|
||||
u8 n1 = get_base64_index(tmp_buf[1]);
|
||||
u8 n2 = get_base64_index(tmp_buf[2]);
|
||||
|
||||
tmp_buf1[0] = (tmp_buf0[0] << 2) + ((tmp_buf0[1] & 0x30) >> 4);
|
||||
tmp_buf1[1] = ((tmp_buf0[1] & 0xf) << 4) + ((tmp_buf0[2] & 0x3c) >> 2);
|
||||
tmp_buf1[2] = ((tmp_buf0[2] & 0x3) << 6) + tmp_buf0[3];
|
||||
|
||||
for (j = 0; (j < i - 1); j++)
|
||||
result.push_back(tmp_buf1[j]);
|
||||
if (i > 1) {
|
||||
result.push_back((n0 << 2) | ((n1 & 0x30) >> 4));
|
||||
}
|
||||
if (i > 2) {
|
||||
result.push_back(((n1 & 0x0F) << 4) | ((n2 & 0x3C) >> 2));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace IACore
|
||||
@ -93,7 +93,7 @@ private:
|
||||
// Implementation
|
||||
// =============================================================================
|
||||
|
||||
auto RingBufferView::default_instance() -> RingBufferView {
|
||||
inline auto RingBufferView::default_instance() -> RingBufferView {
|
||||
return RingBufferView(nullptr, {}, false);
|
||||
}
|
||||
|
||||
@ -262,7 +262,7 @@ inline auto RingBufferView::read_wrapped(u32 offset, void *out_data, u32 size)
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto RingBufferView::is_valid() const -> bool {
|
||||
[[nodiscard]] inline auto RingBufferView::is_valid() const -> bool {
|
||||
return m_control_block && m_data_ptr && m_capacity;
|
||||
}
|
||||
|
||||
|
||||
@ -17,54 +17,47 @@
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
class CLIParser
|
||||
{
|
||||
/*
|
||||
* PLEASE READ
|
||||
*
|
||||
* CLIParser is still very much in it's baby stages.
|
||||
* Subject to heavy and frequent changes, use with
|
||||
* caution!
|
||||
*/
|
||||
namespace IACore {
|
||||
class CLIParser {
|
||||
/*
|
||||
* PLEASE READ
|
||||
*
|
||||
* CLIParser is still very much in it's baby stages.
|
||||
* Subject to heavy and frequent changes, use with
|
||||
* caution!
|
||||
*/
|
||||
|
||||
public:
|
||||
CLIParser(Span<const String> args);
|
||||
~CLIParser() = default;
|
||||
public:
|
||||
CLIParser(Span<const String> args);
|
||||
~CLIParser() = default;
|
||||
|
||||
public:
|
||||
auto remaining() const -> bool
|
||||
{
|
||||
return m_currentArg < m_argList.end();
|
||||
}
|
||||
public:
|
||||
[[nodiscard]] auto remaining() const -> bool {
|
||||
return m_current_arg < m_arg_list.end();
|
||||
}
|
||||
|
||||
auto peek() const -> StringView
|
||||
{
|
||||
if (!remaining())
|
||||
return "";
|
||||
return *m_currentArg;
|
||||
}
|
||||
[[nodiscard]] auto peek() const -> StringView {
|
||||
if (!remaining())
|
||||
return "";
|
||||
return *m_current_arg;
|
||||
}
|
||||
|
||||
auto next() -> StringView
|
||||
{
|
||||
if (!remaining())
|
||||
return "";
|
||||
return *m_currentArg++;
|
||||
}
|
||||
auto next() -> StringView {
|
||||
if (!remaining())
|
||||
return "";
|
||||
return *m_current_arg++;
|
||||
}
|
||||
|
||||
auto consume(const StringView &expected) -> bool
|
||||
{
|
||||
if (peek() == expected)
|
||||
{
|
||||
next();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
auto consume(const StringView &expected) -> bool {
|
||||
if (peek() == expected) {
|
||||
next();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
const Span<const String> m_argList;
|
||||
Span<const String>::const_iterator m_currentArg;
|
||||
};
|
||||
private:
|
||||
const Span<const String> m_arg_list;
|
||||
Span<const String>::const_iterator m_current_arg;
|
||||
};
|
||||
} // namespace IACore
|
||||
@ -21,295 +21,271 @@
|
||||
// Macros
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#define __iat_micro_test(call) \
|
||||
if (!(call)) \
|
||||
return false
|
||||
#define __iat_micro_test(call) \
|
||||
if (!(call)) \
|
||||
return false
|
||||
|
||||
#define IAT_CHECK(v) __iat_micro_test(_test((v), #v))
|
||||
#define IAT_CHECK_NOT(v) __iat_micro_test(_test_not((v), "NOT " #v))
|
||||
#define IAT_CHECK_EQ(lhs, rhs) __iat_micro_test(_test_eq((lhs), (rhs), #lhs " == " #rhs))
|
||||
#define IAT_CHECK_NEQ(lhs, rhs) __iat_micro_test(_test_neq((lhs), (rhs), #lhs " != " #rhs))
|
||||
#define IAT_CHECK_EQ(lhs, rhs) \
|
||||
__iat_micro_test(_test_eq((lhs), (rhs), #lhs " == " #rhs))
|
||||
#define IAT_CHECK_NEQ(lhs, rhs) \
|
||||
__iat_micro_test(_test_neq((lhs), (rhs), #lhs " != " #rhs))
|
||||
|
||||
#define IAT_CHECK_APPROX(lhs, rhs) __iat_micro_test(_test_approx((lhs), (rhs), #lhs " ~= " #rhs))
|
||||
#define IAT_CHECK_APPROX(lhs, rhs) \
|
||||
__iat_micro_test(_test_approx((lhs), (rhs), #lhs " ~= " #rhs))
|
||||
|
||||
#define IAT_UNIT(func) _test_unit([this]() { return this->func(); }, #func)
|
||||
#define IAT_NAMED_UNIT(n, func) _test_unit([this]() { return this->func(); }, n)
|
||||
|
||||
#define IAT_BLOCK(name) class name : public ia::iatest::Block
|
||||
#define IAT_BLOCK(name) class name : public IACore::Test::Block
|
||||
|
||||
#define IAT_BEGIN_BLOCK(_group, _name) \
|
||||
class _group##_##_name : public ia::iatest::Block \
|
||||
{ \
|
||||
public: \
|
||||
[[nodiscard]] auto get_name() const -> const char * override \
|
||||
{ \
|
||||
return #_group "::" #_name; \
|
||||
} \
|
||||
\
|
||||
private:
|
||||
|
||||
#define IAT_END_BLOCK() \
|
||||
} \
|
||||
;
|
||||
|
||||
#define IAT_BEGIN_TEST_LIST() \
|
||||
public: \
|
||||
void declare_tests() override \
|
||||
{
|
||||
#define IAT_ADD_TEST(name) IAT_UNIT(name)
|
||||
#define IAT_END_TEST_LIST() \
|
||||
} \
|
||||
\
|
||||
#define IAT_BEGIN_BLOCK(_group, _name) \
|
||||
class _group##_##_name : public IACore::Test::Block { \
|
||||
public: \
|
||||
[[nodiscard]] auto get_name() const -> const char * override { \
|
||||
return #_group "::" #_name; \
|
||||
} \
|
||||
\
|
||||
private:
|
||||
|
||||
namespace IACore
|
||||
{
|
||||
// -------------------------------------------------------------------------
|
||||
// String Conversion Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
template<typename T> auto to_string(const T &value) -> String
|
||||
{
|
||||
if constexpr (std::is_arithmetic_v<T>)
|
||||
{
|
||||
return std::to_string(value);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, String> || std::is_same_v<T, const char *> || std::is_same_v<T, char *>)
|
||||
{
|
||||
return String("\"") + String(value) + "\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "{Object}";
|
||||
}
|
||||
#define IAT_END_BLOCK() \
|
||||
} \
|
||||
;
|
||||
|
||||
#define IAT_BEGIN_TEST_LIST() \
|
||||
public: \
|
||||
void declare_tests() override {
|
||||
#define IAT_ADD_TEST(name) IAT_UNIT(name)
|
||||
#define IAT_END_TEST_LIST() \
|
||||
} \
|
||||
\
|
||||
private:
|
||||
|
||||
namespace IACore::Test {
|
||||
// -------------------------------------------------------------------------
|
||||
// String Conversion Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
template <typename T> auto to_string(const T &value) -> String {
|
||||
if constexpr (std::is_arithmetic_v<T>) {
|
||||
return std::to_string(value);
|
||||
} else if constexpr (std::is_same_v<T, String> ||
|
||||
std::is_same_v<T, const char *> ||
|
||||
std::is_same_v<T, char *>) {
|
||||
return String("\"") + String(value) + "\"";
|
||||
} else {
|
||||
return "{Object}";
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> auto to_string(T *value) -> String {
|
||||
if (value == nullptr) {
|
||||
return "nullptr";
|
||||
}
|
||||
return std::format("ptr({})", static_cast<const void *>(value));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Types
|
||||
// -------------------------------------------------------------------------
|
||||
using TestFunctor = std::function<bool()>;
|
||||
|
||||
struct TestUnit {
|
||||
String name;
|
||||
TestFunctor functor;
|
||||
};
|
||||
|
||||
class Block {
|
||||
public:
|
||||
virtual ~Block() = default;
|
||||
[[nodiscard]] virtual auto get_name() const -> const char * = 0;
|
||||
virtual void declare_tests() = 0;
|
||||
|
||||
auto units() -> Vec<TestUnit> & { return m_units; }
|
||||
|
||||
protected:
|
||||
template <typename T1, typename T2>
|
||||
auto _test_eq(const T1 &lhs, const T2 &rhs, const char *description) -> bool {
|
||||
if (lhs != rhs) {
|
||||
print_fail(description, to_string(lhs), to_string(rhs));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T1, typename T2>
|
||||
auto _test_neq(const T1 &lhs, const T2 &rhs, const char *description)
|
||||
-> bool {
|
||||
if (lhs == rhs) {
|
||||
print_fail(description, to_string(lhs), "NOT " + to_string(rhs));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto _test_approx(T lhs, T rhs, const char *description) -> bool {
|
||||
static_assert(std::is_floating_point_v<T>,
|
||||
"Approx only works for floats/doubles");
|
||||
T diff = std::abs(lhs - rhs);
|
||||
if (diff > static_cast<T>(0.0001)) {
|
||||
print_fail(description, to_string(lhs), to_string(rhs));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto _test(bool value, const char *description) -> bool {
|
||||
if (!value) {
|
||||
std::cout << console::BLUE << " " << description << "... "
|
||||
<< console::RED << "FAILED" << console::RESET << "\n";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto _test_not(bool value, const char *description) -> bool {
|
||||
if (value) {
|
||||
std::cout << console::BLUE << " " << description << "... "
|
||||
<< console::RED << "FAILED" << console::RESET << "\n";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void _test_unit(TestFunctor functor, const char *name) {
|
||||
m_units.push_back({name, std::move(functor)});
|
||||
}
|
||||
|
||||
private:
|
||||
void print_fail(const char *desc, const String &v1, const String &v2) {
|
||||
std::cout << console::BLUE << " " << desc << "... " << console::RED
|
||||
<< "FAILED" << console::RESET << "\n";
|
||||
std::cout << console::RED << " Expected: " << v2 << console::RESET
|
||||
<< "\n";
|
||||
std::cout << console::RED << " Actual: " << v1 << console::RESET
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
Vec<TestUnit> m_units;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept ValidBlockClass = std::derived_from<T, Block>;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Runner
|
||||
// -------------------------------------------------------------------------
|
||||
template <bool StopOnFail = false, bool IsVerbose = false> class Runner {
|
||||
public:
|
||||
Runner() = default;
|
||||
|
||||
~Runner() { summarize(); }
|
||||
|
||||
template <typename BlockClass>
|
||||
requires ValidBlockClass<BlockClass>
|
||||
void test_block();
|
||||
|
||||
private:
|
||||
void summarize();
|
||||
|
||||
usize m_test_count{0};
|
||||
usize m_fail_count{0};
|
||||
usize m_block_count{0};
|
||||
};
|
||||
|
||||
template <bool StopOnFail, bool IsVerbose>
|
||||
template <typename BlockClass>
|
||||
requires ValidBlockClass<BlockClass>
|
||||
void Runner<StopOnFail, IsVerbose>::test_block() {
|
||||
m_block_count++;
|
||||
BlockClass b;
|
||||
b.declare_tests();
|
||||
|
||||
std::cout << console::MAGENTA << "Testing [" << b.get_name() << "]..."
|
||||
<< console::RESET << "\n";
|
||||
|
||||
for (auto &v : b.units()) {
|
||||
m_test_count++;
|
||||
if constexpr (IsVerbose) {
|
||||
std::cout << console::YELLOW << " Testing " << v.name << "...\n"
|
||||
<< console::RESET;
|
||||
}
|
||||
|
||||
template<typename T> auto to_string(T *value) -> String
|
||||
{
|
||||
if (value == nullptr)
|
||||
{
|
||||
return "nullptr";
|
||||
}
|
||||
return std::format("ptr({})", static_cast<const void *>(value));
|
||||
// Exceptions are DISABLED. We assume tests do not crash.
|
||||
// If a test crashes (segfault), the OS handles it.
|
||||
bool result = v.functor();
|
||||
|
||||
if (!result) {
|
||||
m_fail_count++;
|
||||
if constexpr (StopOnFail) {
|
||||
summarize();
|
||||
std::exit(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
template <bool StopOnFail, bool IsVerbose>
|
||||
void Runner<StopOnFail, IsVerbose>::summarize() {
|
||||
std::cout << console::GREEN
|
||||
<< "\n-----------------------------------\n\t "
|
||||
"SUMMARY\n-----------------------------------\n";
|
||||
|
||||
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) /
|
||||
static_cast<f64>(m_test_count));
|
||||
std::cout << console::RED << m_fail_count << " OF " << m_test_count
|
||||
<< " TESTS FAILED\n"
|
||||
<< console::YELLOW
|
||||
<< std::format("Success Rate: {:.2f}%\n", success_rate);
|
||||
}
|
||||
|
||||
std::cout << console::MAGENTA << "Ran " << m_test_count << " test(s) across "
|
||||
<< m_block_count << " block(s)\n"
|
||||
<< console::GREEN << "-----------------------------------"
|
||||
<< console::RESET << "\n";
|
||||
}
|
||||
|
||||
using DefaultRunner = Runner<false, true>;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Registry
|
||||
// -------------------------------------------------------------------------
|
||||
class TestRegistry {
|
||||
public:
|
||||
using TestEntry = std::function<void(DefaultRunner &)>;
|
||||
|
||||
static auto get_entries() -> Vec<TestEntry> & {
|
||||
static Vec<TestEntry> entries;
|
||||
return entries;
|
||||
}
|
||||
|
||||
static auto run_all() -> i32 {
|
||||
DefaultRunner r;
|
||||
auto &entries = get_entries();
|
||||
std::cout << console::CYAN << "[IATest] Discovered " << entries.size()
|
||||
<< " Test Blocks\n\n"
|
||||
<< console::RESET;
|
||||
|
||||
for (auto &entry : entries) {
|
||||
entry(r);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Types
|
||||
// -------------------------------------------------------------------------
|
||||
using TestFunctor = std::function<bool()>;
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct TestUnit
|
||||
{
|
||||
String name;
|
||||
TestFunctor functor;
|
||||
};
|
||||
template <typename BlockType> struct AutoRegister {
|
||||
AutoRegister() {
|
||||
TestRegistry::get_entries().push_back(
|
||||
[](DefaultRunner &r) { r.test_block<BlockType>(); });
|
||||
}
|
||||
};
|
||||
} // namespace IACore::Test
|
||||
|
||||
class Block
|
||||
{
|
||||
public:
|
||||
virtual ~Block() = default;
|
||||
[[nodiscard]] virtual auto get_name() const -> const char * = 0;
|
||||
virtual void declare_tests() = 0;
|
||||
|
||||
auto units() -> Vec<TestUnit> &
|
||||
{
|
||||
return m_units;
|
||||
}
|
||||
|
||||
protected:
|
||||
template<typename T1, typename T2> auto _test_eq(const T1 &lhs, const T2 &rhs, const char *description) -> bool
|
||||
{
|
||||
if (lhs != rhs)
|
||||
{
|
||||
print_fail(description, to_string(lhs), to_string(rhs));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T1, typename T2> auto _test_neq(const T1 &lhs, const T2 &rhs, const char *description) -> bool
|
||||
{
|
||||
if (lhs == rhs)
|
||||
{
|
||||
print_fail(description, to_string(lhs), "NOT " + to_string(rhs));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T> auto _test_approx(T lhs, T rhs, const char *description) -> bool
|
||||
{
|
||||
static_assert(std::is_floating_point_v<T>, "Approx only works for floats/doubles");
|
||||
T diff = std::abs(lhs - rhs);
|
||||
if (diff > static_cast<T>(0.0001))
|
||||
{
|
||||
print_fail(description, to_string(lhs), to_string(rhs));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto _test(bool value, const char *description) -> bool
|
||||
{
|
||||
if (!value)
|
||||
{
|
||||
std::cout << console::blue << " " << description << "... " << console::red << "FAILED"
|
||||
<< console::reset << "\n";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto _test_not(bool value, const char *description) -> bool
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
std::cout << console::blue << " " << description << "... " << console::red << "FAILED"
|
||||
<< console::reset << "\n";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void _test_unit(TestFunctor functor, const char *name)
|
||||
{
|
||||
m_units.push_back({name, std::move(functor)});
|
||||
}
|
||||
|
||||
private:
|
||||
void print_fail(const char *desc, const String &v1, const String &v2)
|
||||
{
|
||||
std::cout << console::blue << " " << desc << "... " << console::red << "FAILED" << console::reset
|
||||
<< "\n";
|
||||
std::cout << console::red << " Expected: " << v2 << console::reset << "\n";
|
||||
std::cout << console::red << " Actual: " << v1 << console::reset << "\n";
|
||||
}
|
||||
|
||||
Vec<TestUnit> m_units;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
concept ValidBlockClass = std::derived_from<T, Block>;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Runner
|
||||
// -------------------------------------------------------------------------
|
||||
template<bool StopOnFail = false, bool IsVerbose = false> class Runner
|
||||
{
|
||||
public:
|
||||
Runner() = default;
|
||||
|
||||
~Runner()
|
||||
{
|
||||
summarize();
|
||||
}
|
||||
|
||||
template<typename BlockClass>
|
||||
requires ValidBlockClass<BlockClass>
|
||||
void test_block();
|
||||
|
||||
private:
|
||||
void summarize();
|
||||
|
||||
usize m_test_count{0};
|
||||
usize m_fail_count{0};
|
||||
usize m_block_count{0};
|
||||
};
|
||||
|
||||
template<bool StopOnFail, bool IsVerbose>
|
||||
template<typename BlockClass>
|
||||
requires ValidBlockClass<BlockClass>
|
||||
void Runner<StopOnFail, IsVerbose>::test_block()
|
||||
{
|
||||
m_block_count++;
|
||||
BlockClass b;
|
||||
b.declare_tests();
|
||||
|
||||
std::cout << console::magenta << "Testing [" << b.get_name() << "]..." << console::reset << "\n";
|
||||
|
||||
for (auto &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();
|
||||
|
||||
if (!result)
|
||||
{
|
||||
m_fail_count++;
|
||||
if constexpr (StopOnFail)
|
||||
{
|
||||
summarize();
|
||||
std::exit(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
template<bool StopOnFail, bool IsVerbose> void Runner<StopOnFail, IsVerbose>::summarize()
|
||||
{
|
||||
std::cout << console::green
|
||||
<< "\n-----------------------------------\n\t SUMMARY\n-----------------------------------\n";
|
||||
|
||||
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) / static_cast<f64>(m_test_count));
|
||||
std::cout << console::red << m_fail_count << " OF " << m_test_count << " TESTS FAILED\n"
|
||||
<< console::yellow << std::format("Success Rate: {:.2f}%\n", success_rate);
|
||||
}
|
||||
|
||||
std::cout << console::magenta << "Ran " << m_test_count << " test(s) across " << m_block_count << " block(s)\n"
|
||||
<< console::green << "-----------------------------------" << console::reset << "\n";
|
||||
}
|
||||
|
||||
using DefaultRunner = Runner<false, true>;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Registry
|
||||
// -------------------------------------------------------------------------
|
||||
class TestRegistry
|
||||
{
|
||||
public:
|
||||
using TestEntry = std::function<void(DefaultRunner &)>;
|
||||
|
||||
static auto get_entries() -> Vec<TestEntry> &
|
||||
{
|
||||
static Vec<TestEntry> entries;
|
||||
return entries;
|
||||
}
|
||||
|
||||
static auto run_all() -> i32
|
||||
{
|
||||
DefaultRunner r;
|
||||
auto &entries = get_entries();
|
||||
std::cout << console::cyan << "[IATest] Discovered " << entries.size() << " Test Blocks\n\n"
|
||||
<< console::reset;
|
||||
|
||||
for (auto &entry : entries)
|
||||
{
|
||||
entry(r);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename BlockType> struct AutoRegister
|
||||
{
|
||||
AutoRegister()
|
||||
{
|
||||
TestRegistry::get_entries().push_back([](DefaultRunner &r) { r.test_block<BlockType>(); });
|
||||
}
|
||||
};
|
||||
} // namespace IACore
|
||||
|
||||
#define IAT_REGISTER_ENTRY(Group, Name) static ia::iatest::AutoRegister<Group##_##Name> _iat_reg_##Group##_##Name;
|
||||
#define IAT_REGISTER_ENTRY(Group, Name) \
|
||||
static IACore::Test::AutoRegister<Group##_##Name> _iat_reg_##Group##_##Name;
|
||||
@ -119,8 +119,8 @@ inline auto Json::encode(const nlohmann::json &data) -> String {
|
||||
template <typename T>
|
||||
inline auto Json::parse_to_struct(const String &json_str) -> Result<T> {
|
||||
T result{};
|
||||
// glz::read_json returns an error code (bool-like optional)
|
||||
const auto err = glz::read_json<GLAZE_OPTS>(result, json_str);
|
||||
|
||||
const auto err = glz::read<GLAZE_OPTS>(result, json_str);
|
||||
|
||||
if (err) {
|
||||
return fail("JSON Struct Parse Error: {}",
|
||||
|
||||
@ -34,8 +34,8 @@ public:
|
||||
explicit StreamReader(Span<const u8> data);
|
||||
~StreamReader();
|
||||
|
||||
StreamReader(StreamReader &&) = default;
|
||||
auto operator=(StreamReader &&) -> StreamReader & = default;
|
||||
StreamReader(StreamReader &&other);
|
||||
auto operator=(StreamReader &&other) -> StreamReader &;
|
||||
|
||||
StreamReader(const StreamReader &) = delete;
|
||||
auto operator=(const StreamReader &) -> StreamReader & = delete;
|
||||
@ -86,6 +86,9 @@ inline auto StreamReader::read(void *buffer, usize size) -> Result<void> {
|
||||
template <typename T>
|
||||
[[nodiscard("Check for EOF")]]
|
||||
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);
|
||||
|
||||
if (m_cursor + SIZE > m_data_size) [[unlikely]] {
|
||||
|
||||
@ -27,18 +27,19 @@ public:
|
||||
OwningVector,
|
||||
};
|
||||
|
||||
static auto create(const Path &path) -> Result<StreamWriter>;
|
||||
static auto create_from_file(const Path &path) -> Result<StreamWriter>;
|
||||
|
||||
StreamWriter();
|
||||
explicit StreamWriter(Span<u8> data);
|
||||
~StreamWriter();
|
||||
|
||||
StreamWriter(StreamWriter &&) = default;
|
||||
auto operator=(StreamWriter &&) -> StreamWriter & = default;
|
||||
StreamWriter(StreamWriter &&other);
|
||||
auto operator=(StreamWriter &&other) -> StreamWriter &;
|
||||
|
||||
StreamWriter(const StreamWriter &) = delete;
|
||||
auto operator=(const StreamWriter &) -> StreamWriter & = delete;
|
||||
|
||||
~StreamWriter();
|
||||
|
||||
auto write(u8 byte, usize count) -> Result<void>;
|
||||
auto write(const void *buffer, usize size) -> Result<void>;
|
||||
|
||||
@ -48,6 +49,8 @@ public:
|
||||
|
||||
[[nodiscard]] auto cursor() const -> usize { return m_cursor; }
|
||||
|
||||
auto flush() -> Result<void>;
|
||||
|
||||
private:
|
||||
u8 *m_buffer = nullptr;
|
||||
usize m_cursor = 0;
|
||||
@ -55,6 +58,9 @@ private:
|
||||
Path m_file_path;
|
||||
Vec<u8> m_owning_vector;
|
||||
StorageType m_storage_type = StorageType::OwningVector;
|
||||
|
||||
private:
|
||||
auto flush_to_disk() -> Result<void>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
|
||||
2770
Src/IACore/inc/full_repo_context.txt
Normal file
2770
Src/IACore/inc/full_repo_context.txt
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user