This commit is contained in:
2025-12-15 23:51:06 +05:30
parent 534767ced9
commit 957a3572c3
18 changed files with 175 additions and 231 deletions

View File

@ -23,8 +23,6 @@ message(STATUS "Configured IACore for Multi-Config (Debug/Release rules generate
message(STATUS "Detected Compiler ID: ${CMAKE_CXX_COMPILER_ID}") message(STATUS "Detected Compiler ID: ${CMAKE_CXX_COMPILER_ID}")
# Check if the compiler is MSVC (cl.exe), but allow Clang acting like MSVC (clang-cl) # Check if the compiler is MSVC (cl.exe), but allow Clang acting like MSVC (clang-cl)
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
# Note: clang-cl usually reports itself as "Clang" in newer CMake versions,
# but if it reports MSVC with a Clang simulation, we want to allow it.
if (NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") if (NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
message(FATAL_ERROR message(FATAL_ERROR
"\n\n" "\n\n"
@ -40,7 +38,6 @@ endif()
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
add_compile_options( add_compile_options(
-Wall -Wextra -Wpedantic -Wall -Wextra -Wpedantic
# Suppress warning for the statement expression macro if -pedantic is on
-Wno-language-extension-token -Wno-language-extension-token
) )
endif() endif()

View File

@ -130,6 +130,45 @@ if (res) {
} }
``` ```
## 🤝 Contributing
We welcome contributions from the community! However, to maintain the architectural integrity and licensing flexibility of the project, we have specific guidelines for Pull Requests.
### What we accept immediately:
* **📚 Documentation:** Improvements to comments, the README, or external docs.
* **🧪 Tests:** New unit tests (in `Tests/`) to improve coverage or reproduce bugs.
* **💡 Examples:** New usage examples or sample projects.
* **🐛 Bug Reports:** detailed issues describing reproduction steps are highly valued.
### Core Library Policy (`Src/` Directory)
Currently, **we are not accepting Pull Requests that modify the core source code (`Src/`)**.
**Why?**
IACore is a dual-licensed product. To offer commercial licenses to proprietary software vendors in the future, **IASoft (PVT) LTD.** must retain 100% copyright ownership of the core library.
We are currently establishing a **Contributor License Agreement (CLA)** process. Once that is in place, we will open the core library for contributions, provided the contributor signs the CLA to assign copyright or grant an unlimited license to the project maintainers.
If you find a critical bug in `Src/`, please open an **Issue** rather than a PR, and the core team will implement the fix to ensure legal compliance.
## 🤝 Credits & Acknowledgements
**IACore** is an architectural effort by **IASoft (PVT) LTD.**, designed and maintained by its lead developers.
While the core architecture, API design, and final logic verification were strictly human-led, this project leveraged **Google Gemini 3** as an advanced AI thought partner to accelerate development.
#### AI contributions include:
* **Boilerplate Generation:** Rapid prototyping of standard C++20 structures and repetitive implementations.
* **Research & Selection:** Analyzing and comparing open-source libraries (e.g., `glaze` vs. `nlohmann`, `zstd` vs. `lz4`) to select the best-in-class dependencies.
* **Design Analysis:** Researching and contrasting design patterns (e.g., SPSC Ring Buffers vs. Mutex Queues) for optimal performance.
* **Documentation:** Drafting comprehensive documentation and this README.
* **Code Review:** Automated logic checking and static analysis support.
**Methodology:** Every line of code, whether written by hand or generated by AI, has been manually reviewed, tested, and verified by human engineers to ensure zero hallucinations and maximum reliability. *Trust, but verify.*
## ⚖️ License ## ⚖️ License
This project is licensed under the GNU General Public License v3 (GPLv3). This project is licensed under the GNU General Public License v3 (GPLv3).

View File

@ -111,7 +111,6 @@ namespace IACore
// Check for ZLIB Magic Number (starts with 0x78) // Check for ZLIB Magic Number (starts with 0x78)
// 0x78 = Deflate compression with 32k window size // 0x78 = Deflate compression with 32k window size
// Valid second bytes: 0x01 (Fastest), 0x9C (Default), 0xDA (Best)
if (data[0] == 0x78 && (data[1] == 0x01 || data[1] == 0x9C || data[1] == 0xDA)) if (data[0] == 0x78 && (data[1] == 0x01 || data[1] == 0x9C || data[1] == 0xDA))
return CompressionType::Zlib; return CompressionType::Zlib;
@ -134,7 +133,6 @@ namespace IACore
Vector<UINT8> outBuffer; Vector<UINT8> outBuffer;
// Start with 2x input size. // Start with 2x input size.
// Small packets compress well, so maybe 4x for very small inputs.
size_t guessSize = data.size() < 1024 ? data.size() * 4 : data.size() * 2; size_t guessSize = data.size() < 1024 ? data.size() * 4 : data.size() * 2;
outBuffer.resize(guessSize); outBuffer.resize(guessSize);
@ -282,7 +280,7 @@ namespace IACore
zs.zfree = Z_NULL; zs.zfree = Z_NULL;
zs.opaque = Z_NULL; zs.opaque = Z_NULL;
// WindowBits = 15 + 16 (31) -> This forces GZIP encoding // WindowBits = 15 + 16 (31) = Enforce GZIP encoding
// MemLevel = 8 (default) // MemLevel = 8 (default)
// Strategy = Z_DEFAULT_STRATEGY // Strategy = Z_DEFAULT_STRATEGY
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK) if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK)

View File

@ -182,7 +182,7 @@ namespace IACore
IPC_Manager::IPC_Manager() IPC_Manager::IPC_Manager()
{ {
// SocketOps is smart enough to handle multiple inits // SocketOps is smart enough to track multiple inits
SocketOps::Initialize(); SocketOps::Initialize();
m_recieveBuffer.resize(UINT16_MAX + 1); m_recieveBuffer.resize(UINT16_MAX + 1);
@ -208,7 +208,7 @@ namespace IACore
} }
m_pendingSessions.clear(); m_pendingSessions.clear();
// SocketOps is smart enough to handle multiple terminates // SocketOps is smart enough to track multiple terminates
SocketOps::Terminate(); SocketOps::Terminate();
} }

View File

@ -136,7 +136,7 @@ namespace IACore
BOOL success = CreateProcessA(NULL, commandLine.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); BOOL success = CreateProcessA(NULL, commandLine.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
// Important: Close write end in parent, otherwise ReadFile never returns EOF! // Close write end in parent, otherwise ReadFile never returns EOF!
CloseHandle(hWrite); CloseHandle(hWrite);
if (!success) if (!success)
@ -196,7 +196,6 @@ namespace IACore
dup2(pipefd[1], STDERR_FILENO); dup2(pipefd[1], STDERR_FILENO);
close(pipefd[1]); close(pipefd[1]);
// --- ARGUMENT PARSING START ---
std::vector<std::string> argStorage; // To keep strings alive std::vector<std::string> argStorage; // To keep strings alive
std::vector<char *> argv; std::vector<char *> argv;
@ -258,7 +257,6 @@ namespace IACore
argv.push_back(s.data()); argv.push_back(s.data());
} }
argv.push_back(nullptr); argv.push_back(nullptr);
// --- ARGUMENT PARSING END ---
execvp(argv[0], argv.data()); execvp(argv[0], argv.data());
_exit(127); _exit(127);
@ -312,7 +310,6 @@ namespace IACore
} }
else else
{ {
// Zero copy optimization for pure lines in one chunk
if (i > start) if (i > start)
Callback(StringView(data + start, i - start)); Callback(StringView(data + start, i - start));
} }

View File

@ -1,16 +1,16 @@
// IACore-OSS; The Core Library for All IA Open Source Projects // IACore-OSS; The Core Library for All IA Open Source Projects
// Copyright (C) 2025 IAS (ias@iasoft.dev) // Copyright (C) 2025 IAS (ias@iasoft.dev)
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
@ -19,30 +19,31 @@
#include <IACore/PCH.hpp> #include <IACore/PCH.hpp>
#if IA_PLATFORM_WINDOWS #if IA_PLATFORM_WINDOWS
#include <libloaderapi.h> # include <libloaderapi.h>
#include <errhandlingapi.h> # include <errhandlingapi.h>
#else #else
#include <dlfcn.h> # include <dlfcn.h>
#endif #endif
namespace IACore { namespace IACore
{
class DynamicLib { class DynamicLib
public: {
// --------------------------------------------------------------------- public:
// Constructors / Destructors (Move Only) DynamicLib() : m_handle(nullptr)
// --------------------------------------------------------------------- {
}
DynamicLib() : m_handle(nullptr) {}
// Move Constructor: Steal ownership, nullify source DynamicLib(DynamicLib &&other) NOEXCEPT : m_handle(other.m_handle)
DynamicLib(DynamicLib&& other) NOEXCEPT : m_handle(other.m_handle) { {
other.m_handle = nullptr; other.m_handle = nullptr;
} }
// Move Assignment DynamicLib &operator=(DynamicLib &&other) NOEXCEPT
DynamicLib& operator=(DynamicLib&& other) NOEXCEPT { {
if (this != &other) { if (this != &other)
{
Unload(); // Free current if exists Unload(); // Free current if exists
m_handle = other.m_handle; m_handle = other.m_handle;
other.m_handle = nullptr; other.m_handle = nullptr;
@ -50,135 +51,129 @@ namespace IACore {
return *this; return *this;
} }
// No Copying allowed (Library handles are unique resources) DynamicLib(CONST DynamicLib &) = delete;
DynamicLib(CONST DynamicLib&) = delete; DynamicLib &operator=(CONST DynamicLib &) = delete;
DynamicLib& operator=(CONST DynamicLib&) = delete;
~DynamicLib() { ~DynamicLib()
{
Unload(); Unload();
} }
// ---------------------------------------------------------------------
// Static Loader
// ---------------------------------------------------------------------
// Automatically detects extension (.dll/.so) if not provided // Automatically detects extension (.dll/.so) if not provided
NO_DISCARD("Check for load errors") NO_DISCARD("Check for load errors")
STATIC tl::expected<DynamicLib, String> Load(CONST String& searchPath, CONST String& name) {
STATIC tl::expected<DynamicLib, String> Load(CONST String &searchPath, CONST String &name)
{
namespace fs = std::filesystem; namespace fs = std::filesystem;
// 1. Build Path safely
fs::path fullPath = fs::path(searchPath) / name; fs::path fullPath = fs::path(searchPath) / name;
// 2. Auto-append extension if missing if (!fullPath.has_extension())
if (!fullPath.has_extension()) { {
#if IA_PLATFORM_WINDOWS #if IA_PLATFORM_WINDOWS
fullPath += ".dll"; fullPath += ".dll";
#elif IA_PLATFORM_MAC #elif IA_PLATFORM_MAC
fullPath += ".dylib"; fullPath += ".dylib";
#else #else
fullPath += ".so"; fullPath += ".so";
#endif #endif
} }
DynamicLib lib; DynamicLib lib;
#if IA_PLATFORM_WINDOWS #if IA_PLATFORM_WINDOWS
// Use LoadLibraryA (ANSI/UTF-8) assuming manifest is set for UTF-8 HMODULE h = LoadLibraryA(fullPath.string().c_str());
HMODULE h = LoadLibraryA(fullPath.string().c_str()); if (!h)
if (!h) { {
return tl::make_unexpected(GetWindowsError()); return tl::make_unexpected(GetWindowsError());
} }
lib.m_handle = CAST(h, PVOID); lib.m_handle = CAST(h, PVOID);
#else #else
// RTLD_LAZY: Resolve symbols only as code executes (Standard for plugins) // RTLD_LAZY: Resolve symbols only as code executes (Standard for plugins)
// RTLD_NOW: Resolve all immediately (Safer for strict engines) void *h = dlopen(fullPath.c_str(), RTLD_LAZY | RTLD_LOCAL);
void* h = dlopen(fullPath.c_str(), RTLD_LAZY | RTLD_LOCAL); if (!h)
if (!h) { {
// dlerror returns a string describing the last error // dlerror returns a string describing the last error
const char* err = dlerror(); const char *err = dlerror();
return tl::make_unexpected(String(err ? err : "Unknown dlopen error")); return tl::make_unexpected(String(err ? err : "Unknown dlopen error"));
} }
lib.m_handle = h; lib.m_handle = h;
#endif #endif
return IA_MOVE(lib); return IA_MOVE(lib);
} }
// ---------------------------------------------------------------------
// Symbol Access
// ---------------------------------------------------------------------
NO_DISCARD("Check if symbol exists") NO_DISCARD("Check if symbol exists")
tl::expected<PVOID, String> GetSymbol(CONST String& name) CONST {
if (!m_handle) return tl::make_unexpected(String("Library not loaded")); tl::expected<PVOID, String> GetSymbol(CONST String &name) CONST
{
if (!m_handle)
return tl::make_unexpected(String("Library not loaded"));
PVOID sym = nullptr; PVOID sym = nullptr;
#if IA_PLATFORM_WINDOWS #if IA_PLATFORM_WINDOWS
sym = CAST(GetProcAddress(CAST(m_handle, HMODULE), name.c_str()), PVOID); sym = CAST(GetProcAddress(CAST(m_handle, HMODULE), name.c_str()), PVOID);
if (!sym) return tl::make_unexpected(GetWindowsError()); if (!sym)
#else return tl::make_unexpected(GetWindowsError());
// Clear any previous error #else
dlerror(); // Clear any previous error
sym = dlsym(m_handle, name.c_str()); dlerror();
const char* err = dlerror(); sym = dlsym(m_handle, name.c_str());
if (err) return tl::make_unexpected(String(err)); const char *err = dlerror();
#endif if (err)
return tl::make_unexpected(String(err));
#endif
return sym; return sym;
} }
// Template helper for casting // Template helper for casting
template<typename FuncT> template<typename FuncT> tl::expected<FuncT, String> GetFunction(CONST String &name) CONST
tl::expected<FuncT, String> GetFunction(CONST String& name) CONST { {
auto res = GetSymbol(name); auto res = GetSymbol(name);
if (!res) return tl::make_unexpected(res.error()); if (!res)
return tl::make_unexpected(res.error());
return REINTERPRET(*res, FuncT); return REINTERPRET(*res, FuncT);
} }
// --------------------------------------------------------------------- VOID Unload()
// State Management {
// --------------------------------------------------------------------- if (m_handle)
{
VOID Unload() { #if IA_PLATFORM_WINDOWS
if (m_handle) { FreeLibrary(CAST(m_handle, HMODULE));
#if IA_PLATFORM_WINDOWS #else
FreeLibrary(CAST(m_handle, HMODULE)); dlclose(m_handle);
#else #endif
dlclose(m_handle);
#endif
m_handle = nullptr; m_handle = nullptr;
} }
} }
BOOL IsLoaded() CONST { BOOL IsLoaded() CONST
{
return m_handle != nullptr; return m_handle != nullptr;
} }
private: private:
PVOID m_handle; PVOID m_handle;
// --------------------------------------------------------------------- #if IA_PLATFORM_WINDOWS
// Private Helpers STATIC String GetWindowsError()
// --------------------------------------------------------------------- {
#if IA_PLATFORM_WINDOWS
STATIC String GetWindowsError() {
DWORD errorID = ::GetLastError(); DWORD errorID = ::GetLastError();
if(errorID == 0) return String(); if (errorID == 0)
return String();
LPSTR messageBuffer = nullptr; LPSTR messageBuffer = nullptr;
size_t size = FormatMessageA( size_t size = FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
NULL, errorID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), errorID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR) &messageBuffer, 0, NULL);
(LPSTR)&messageBuffer, 0, NULL
);
String message(messageBuffer, size); String message(messageBuffer, size);
LocalFree(messageBuffer); LocalFree(messageBuffer);
return String("Win32 Error: ") + message; return String("Win32 Error: ") + message;
} }
#endif #endif
}; };
} } // namespace IACore

View File

@ -23,20 +23,14 @@ namespace IACore
class Environment class Environment
{ {
public: public:
// --------------------------------------------------------------------- STATIC Optional<String> Find(CONST String &name)
// Getters
// ---------------------------------------------------------------------
// Modern approach: Returns nullopt if variable doesn't exist
STATIC std::optional<String> Find(CONST String &name)
{ {
#if IA_PLATFORM_WINDOWS #if IA_PLATFORM_WINDOWS
// 1. Get required size (result includes null terminator if buffer is null/too small)
DWORD bufferSize = GetEnvironmentVariableA(name.c_str(), nullptr, 0); DWORD bufferSize = GetEnvironmentVariableA(name.c_str(), nullptr, 0);
if (bufferSize == 0) if (bufferSize == 0)
{ {
// DWORD 0 means failed (usually ERROR_ENVVAR_NOT_FOUND) // DWORD 0 means failed
return std::nullopt; return std::nullopt;
} }
@ -45,7 +39,7 @@ namespace IACore
result.resize(bufferSize); result.resize(bufferSize);
// 3. Fetch // 3. Fetch
// Returns num chars written EXCLUDING null terminator // Returns num chars written excluding null terminator
DWORD actualSize = GetEnvironmentVariableA(name.c_str(), result.data(), bufferSize); DWORD actualSize = GetEnvironmentVariableA(name.c_str(), result.data(), bufferSize);
if (actualSize == 0 || actualSize > bufferSize) if (actualSize == 0 || actualSize > bufferSize)
@ -58,8 +52,7 @@ namespace IACore
return result; return result;
#else #else
// POSIX (Linux/Mac) // getenv returns a pointer to the environment area
// getenv returns a pointer to the environment area. Do NOT free it.
const char *val = std::getenv(name.c_str()); const char *val = std::getenv(name.c_str());
if (val == nullptr) if (val == nullptr)
{ {
@ -69,17 +62,11 @@ namespace IACore
#endif #endif
} }
// Classic approach: Returns value or default (defaulting to "")
// Matches your old API behavior but is safer
STATIC String Get(CONST String &name, CONST String &defaultValue = "") STATIC String Get(CONST String &name, CONST String &defaultValue = "")
{ {
return Find(name).value_or(defaultValue); return Find(name).value_or(defaultValue);
} }
// ---------------------------------------------------------------------
// Setters
// ---------------------------------------------------------------------
STATIC BOOL Set(CONST String &name, CONST String &value) STATIC BOOL Set(CONST String &name, CONST String &value)
{ {
if (name.empty()) if (name.empty())
@ -88,7 +75,6 @@ namespace IACore
#if IA_PLATFORM_WINDOWS #if IA_PLATFORM_WINDOWS
return SetEnvironmentVariableA(name.c_str(), value.c_str()) != 0; return SetEnvironmentVariableA(name.c_str(), value.c_str()) != 0;
#else #else
// setenv(name, value, overwrite)
// Returns 0 on success, -1 on error // Returns 0 on success, -1 on error
return setenv(name.c_str(), value.c_str(), 1) == 0; return setenv(name.c_str(), value.c_str(), 1) == 0;
#endif #endif
@ -100,17 +86,12 @@ namespace IACore
return FALSE; return FALSE;
#if IA_PLATFORM_WINDOWS #if IA_PLATFORM_WINDOWS
// Windows unsets a variable by setting it to NULL
return SetEnvironmentVariableA(name.c_str(), nullptr) != 0; return SetEnvironmentVariableA(name.c_str(), nullptr) != 0;
#else #else
return unsetenv(name.c_str()) == 0; return unsetenv(name.c_str()) == 0;
#endif #endif
} }
// ---------------------------------------------------------------------
// Utilities
// ---------------------------------------------------------------------
STATIC BOOL Exists(CONST String &name) STATIC BOOL Exists(CONST String &name)
{ {
#if IA_PLATFORM_WINDOWS #if IA_PLATFORM_WINDOWS

View File

@ -28,7 +28,6 @@
# define valid_iatest_runner(type) iatest::_valid_iatest_runner<type>::value_type # define valid_iatest_runner(type) iatest::_valid_iatest_runner<type>::value_type
// Internal macro to handle the return logic
# define __iat_micro_test(call) \ # define __iat_micro_test(call) \
if (!(call)) \ if (!(call)) \
return FALSE return FALSE
@ -38,7 +37,6 @@
# define IAT_CHECK_EQ(lhs, rhs) __iat_micro_test(_test_eq((lhs), (rhs), #lhs " == " #rhs)) # define IAT_CHECK_EQ(lhs, rhs) __iat_micro_test(_test_eq((lhs), (rhs), #lhs " == " #rhs))
# define IAT_CHECK_NEQ(lhs, rhs) __iat_micro_test(_test_neq((lhs), (rhs), #lhs " != " #rhs)) # define IAT_CHECK_NEQ(lhs, rhs) __iat_micro_test(_test_neq((lhs), (rhs), #lhs " != " #rhs))
// Float specific checks (Game dev essential)
# define IAT_CHECK_APPROX(lhs, rhs) __iat_micro_test(_test_approx((lhs), (rhs), #lhs " ~= " #rhs)) # define IAT_CHECK_APPROX(lhs, rhs) __iat_micro_test(_test_approx((lhs), (rhs), #lhs " ~= " #rhs))
# define IAT_UNIT(func) _test_unit([this]() { return this->func(); }, #func) # define IAT_UNIT(func) _test_unit([this]() { return this->func(); }, #func)
@ -46,7 +44,6 @@
# define IAT_BLOCK(name) class name : public ia::iatest::block # define IAT_BLOCK(name) class name : public ia::iatest::block
// Concatenation fix for macros
# define IAT_BEGIN_BLOCK(_group, _name) \ # define IAT_BEGIN_BLOCK(_group, _name) \
class _group##_##_name : public ia::iatest::block \ class _group##_##_name : public ia::iatest::block \
{ \ { \
@ -74,9 +71,6 @@
namespace ia::iatest namespace ia::iatest
{ {
// -------------------------------------------------------------------------
// Type Printing Helper (To show WHAT failed)
// -------------------------------------------------------------------------
template<typename T> std::string ToString(CONST T &value) template<typename T> std::string ToString(CONST T &value)
{ {
if constexpr (std::is_arithmetic_v<T>) if constexpr (std::is_arithmetic_v<T>)
@ -93,7 +87,6 @@ namespace ia::iatest
} }
} }
// Specialization for pointers
template<typename T> std::string ToString(T *value) template<typename T> std::string ToString(T *value)
{ {
if (value == NULLPTR) if (value == NULLPTR)
@ -103,10 +96,6 @@ namespace ia::iatest
return ss.str(); return ss.str();
} }
// -------------------------------------------------------------------------
// Core Structures
// -------------------------------------------------------------------------
DEFINE_TYPE(functor_t, std::function<BOOL()>); DEFINE_TYPE(functor_t, std::function<BOOL()>);
struct unit_t struct unit_t
@ -128,7 +117,6 @@ namespace ia::iatest
} }
protected: protected:
// Generic Equality
template<typename T1, typename T2> BOOL _test_eq(IN CONST T1 &lhs, IN CONST T2 &rhs, IN PCCHAR description) template<typename T1, typename T2> BOOL _test_eq(IN CONST T1 &lhs, IN CONST T2 &rhs, IN PCCHAR description)
{ {
if (lhs != rhs) if (lhs != rhs)
@ -139,7 +127,6 @@ namespace ia::iatest
return TRUE; return TRUE;
} }
// Generic Inequality
template<typename T1, typename T2> BOOL _test_neq(IN CONST T1 &lhs, IN CONST T2 &rhs, IN PCCHAR description) template<typename T1, typename T2> BOOL _test_neq(IN CONST T1 &lhs, IN CONST T2 &rhs, IN PCCHAR description)
{ {
if (lhs == rhs) if (lhs == rhs)
@ -150,13 +137,12 @@ namespace ia::iatest
return TRUE; return TRUE;
} }
// Floating Point Approximation (Epsilon check)
template<typename T> BOOL _test_approx(IN T lhs, IN T rhs, IN PCCHAR description) template<typename T> BOOL _test_approx(IN T lhs, IN T rhs, IN PCCHAR description)
{ {
static_assert(std::is_floating_point_v<T>, "Approx only works for floats/doubles"); static_assert(std::is_floating_point_v<T>, "Approx only works for floats/doubles");
T diff = std::abs(lhs - rhs); T diff = std::abs(lhs - rhs);
if (diff > static_cast<T>(0.0001)) if (diff > static_cast<T>(0.0001))
{ // Default epsilon {
print_fail(description, ToString(lhs), ToString(rhs)); print_fail(description, ToString(lhs), ToString(rhs));
return FALSE; return FALSE;
} }
@ -202,10 +188,6 @@ namespace ia::iatest
template<typename block_class> template<typename block_class>
concept valid_block_class = std::derived_from<block_class, block>; concept valid_block_class = std::derived_from<block_class, block>;
// -------------------------------------------------------------------------
// Runner
// -------------------------------------------------------------------------
template<BOOL stopOnFail = false, BOOL isVerbose = false> class runner template<BOOL stopOnFail = false, BOOL isVerbose = false> class runner
{ {
public: public:
@ -253,7 +235,6 @@ namespace ia::iatest
BOOL result = FALSE; BOOL result = FALSE;
try try
{ {
// Execute the test function
result = v.Functor(); result = v.Functor();
} }
catch (const std::exception &e) catch (const std::exception &e)
@ -311,10 +292,6 @@ namespace ia::iatest
{ {
}; };
// -------------------------------------------------------------------------
// Global Test Registry
// -------------------------------------------------------------------------
// Standard runner configuration for the single executable
using DefaultRunner = runner<false, true>; using DefaultRunner = runner<false, true>;
class TestRegistry class TestRegistry
@ -352,7 +329,6 @@ namespace ia::iatest
}; };
} // namespace ia::iatest } // namespace ia::iatest
// Usage: IAT_REGISTER_ENTRY(Core, Utils)
# define IAT_REGISTER_ENTRY(Group, Name) static ia::iatest::AutoRegister<Group##_##Name> _iat_reg_##Group##_##Name; # define IAT_REGISTER_ENTRY(Group, Name) static ia::iatest::AutoRegister<Group##_##Name> _iat_reg_##Group##_##Name;
#endif // __cplusplus #endif // __cplusplus

View File

@ -43,7 +43,7 @@ namespace IACore
// SECTION 2: RING BUFFER CONTROL BLOCKS // SECTION 2: RING BUFFER CONTROL BLOCKS
// ========================================================= // =========================================================
// RingBufferView::ControlBlock is already 64-byte aligned internally. // RingBufferView ControlBlock is already 64-byte aligned internally.
RingBufferView::ControlBlock MONI_Control; RingBufferView::ControlBlock MONI_Control;
RingBufferView::ControlBlock MINO_Control; RingBufferView::ControlBlock MINO_Control;

View File

@ -54,8 +54,8 @@
# define NOMINMAX # define NOMINMAX
# endif # endif
# include <windows.h> # include <windows.h>
#undef VOID # undef VOID
#undef ERROR # undef ERROR
#elif IA_PLATFORM_UNIX #elif IA_PLATFORM_UNIX
# include <unistd.h> # include <unistd.h>
# include <sys/wait.h> # include <sys/wait.h>
@ -238,7 +238,6 @@
#define ALIGN(a) __attribute__((aligned(a))) #define ALIGN(a) __attribute__((aligned(a)))
// Mark every explicit assembly instruction as volatile
#define ASM(...) __asm__ volatile(__VA_ARGS__) #define ASM(...) __asm__ volatile(__VA_ARGS__)
#ifndef NULL #ifndef NULL
@ -539,7 +538,7 @@ STATIC CONST FLOAT64 FLOAT64_EPSILON = DBL_EPSILON;
#endif #endif
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// C++ Containers and Helpers // Containers and Helpers
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -43,7 +43,7 @@ using SocketHandle = int;
#else #else
# warning "IACore SocketOps is not supported on this platform." # error "IACore SocketOps is not supported on this platform."
#endif #endif

View File

@ -55,7 +55,6 @@ namespace IACore
char high = hex[i]; char high = hex[i];
char low = hex[i + 1]; char low = hex[i + 1];
// Quick helper to decode nibble
auto fromHexChar = [](char c) -> int { auto fromHexChar = [](char c) -> int {
if (c >= '0' && c <= '9') if (c >= '0' && c <= '9')
return c - '0'; return c - '0';
@ -95,16 +94,10 @@ namespace IACore
return std::ranges::upper_bound(range, value); return std::ranges::upper_bound(range, value);
} }
// -------------------------------------------------------------------------
// Hash Combination Logic
// Uses the "golden ratio" mix (similar to Boost) to preserve entropy.
// -------------------------------------------------------------------------
template<typename T> INLINE STATIC void HashCombine(UINT64 &seed, CONST T &v) template<typename T> INLINE STATIC void HashCombine(UINT64 &seed, CONST T &v)
{ {
UINT64 h; UINT64 h;
// 1. Compile-Time check: Can this be treated as a string view?
// This catches "Literal Strings" (char[N]), const char*, and std::string
if constexpr (std::is_constructible_v<std::string_view, T>) if constexpr (std::is_constructible_v<std::string_view, T>)
{ {
std::string_view sv(v); std::string_view sv(v);
@ -113,34 +106,24 @@ namespace IACore
} }
else else
{ {
// 2. Standard types (int, float, custom structs)
auto hasher = ankerl::unordered_dense::hash<T>(); auto hasher = ankerl::unordered_dense::hash<T>();
h = hasher(v); h = hasher(v);
} }
// 0x9e3779b97f4a7c15 is the 64-bit golden ratio (phi) approximation
seed ^= h + 0x9e3779b97f4a7c15 + (seed << 6) + (seed >> 2); seed ^= h + 0x9e3779b97f4a7c15 + (seed << 6) + (seed >> 2);
} }
// -------------------------------------------------------------------------
// Variadic Hasher
// Allows: IACore::ComputeHash(x, y, z);
// -------------------------------------------------------------------------
template<typename... Args> INLINE STATIC UINT64 ComputeHash(CONST Args &...args) template<typename... Args> INLINE STATIC UINT64 ComputeHash(CONST Args &...args)
{ {
UINT64 seed = 0; UINT64 seed = 0;
(HashCombine(seed, args), ...); // C++17/20 Fold Expression (HashCombine(seed, args), ...);
return seed; return seed;
} }
// ------------------------------------------------------------------------- template<typename T, typename... MemberPtrs>
// Flat Hasher INLINE STATIC UINT64 ComputeHashFlat(CONST T &obj, MemberPtrs... members)
// Allows: IACore::ComputeHashFlat(x, y, z);
// -------------------------------------------------------------------------
template<typename T, typename... MemberPtrs> INLINE STATIC UINT64 ComputeHashFlat(CONST T &obj, MemberPtrs... members)
{ {
UINT64 seed = 0; UINT64 seed = 0;
// C++17 Fold Expression: applies (seed, obj.*ptr) for every ptr in members
(HashCombine(seed, obj.*members), ...); (HashCombine(seed, obj.*members), ...);
return seed; return seed;
} }
@ -156,15 +139,14 @@ namespace IACore
// struct Vector3 { float x, y, z; }; // struct Vector3 { float x, y, z; };
// IA_MAKE_HASHABLE(Vector3, &Vector3::x, &Vector3::y, &Vector3::z) // IA_MAKE_HASHABLE(Vector3, &Vector3::x, &Vector3::y, &Vector3::z)
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#define IA_MAKE_HASHABLE(Type, ...) \ #define IA_MAKE_HASHABLE(Type, ...) \
template<> struct ankerl::unordered_dense::hash<Type> \ template<> struct ankerl::unordered_dense::hash<Type> \
{ \ { \
using is_avalanching = void; \ using is_avalanching = void; \
NO_DISCARD("Hash value should be used") \ NO_DISCARD("Hash value should be used") \
UINT64 operator()(CONST Type &v) const NOEXCEPT \ UINT64 operator()(CONST Type &v) const NOEXCEPT \
{ \ { \
/* Pass the object and the list of member pointers */ \ /* Pass the object and the list of member pointers */ \
return IACore::Utils::ComputeHashFlat(v, __VA_ARGS__); \ return IACore::Utils::ComputeHashFlat(v, __VA_ARGS__); \
} \ } \
}; };

View File

@ -23,13 +23,13 @@ set(TEST_SOURCES
add_executable(IACore_Test_Suite ${TEST_SOURCES}) add_executable(IACore_Test_Suite ${TEST_SOURCES})
# Enable exceptions for testing framework (even if Core is no-except) # Enable exceptions for testing framework
target_compile_options(IACore_Test_Suite PRIVATE -fexceptions) target_compile_options(IACore_Test_Suite PRIVATE -fexceptions)
set_target_properties(IACore_Test_Suite PROPERTIES USE_EXCEPTIONS ON) set_target_properties(IACore_Test_Suite PROPERTIES USE_EXCEPTIONS ON)
target_link_libraries(IACore_Test_Suite PRIVATE IACore) target_link_libraries(IACore_Test_Suite PRIVATE IACore)
# Copy necessary runtime assets if you have them (like the LongProcess test subject) # Copy necessary runtime assets
add_custom_command(TARGET IACore_Test_Suite POST_BUILD add_custom_command(TARGET IACore_Test_Suite POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:LongProcess> $<TARGET_FILE:LongProcess>

View File

@ -23,8 +23,6 @@ using namespace IACore;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Constants // Constants
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// We use a unique prefix to ensure we don't accidentally mess with real
// system variables like PATH or HOME.
static const char *TEST_KEY = "IA_TEST_ENV_VAR_12345"; static const char *TEST_KEY = "IA_TEST_ENV_VAR_12345";
static const char *TEST_VAL = "Hello World"; static const char *TEST_VAL = "Hello World";
@ -123,7 +121,6 @@ BOOL TestDefaults()
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 5. Empty Strings vs Null/Unset // 5. Empty Strings vs Null/Unset
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// This is a critical edge case.
// Does Set(Key, "") create an existing empty variable, or unset it? // Does Set(Key, "") create an existing empty variable, or unset it?
// Standard POSIX/Windows API behavior is that it EXISTS, but is empty. // Standard POSIX/Windows API behavior is that it EXISTS, but is empty.
BOOL TestEmptyValue() BOOL TestEmptyValue()

View File

@ -50,7 +50,6 @@ BOOL TestBasicRun()
IAT_CHECK(result.has_value()); IAT_CHECK(result.has_value());
IAT_CHECK_EQ(*result, 0); // Exit code 0 IAT_CHECK_EQ(*result, 0); // Exit code 0
// Note: Echo might add newline, but your LineBuffer trims/handles it.
// We check if "HelloIA" is contained or equal. // We check if "HelloIA" is contained or equal.
IAT_CHECK(captured.find("HelloIA") != String::npos); IAT_CHECK(captured.find("HelloIA") != String::npos);
@ -137,14 +136,9 @@ BOOL TestMissingExe()
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
BOOL TestLargeOutput() BOOL TestLargeOutput()
{ {
// We need to generate output larger than the internal 4096 buffer // Need to generate output larger than the internal 4096 buffer
// to ensure the "partial line" logic works when a line crosses a buffer boundary. // to ensure the "partial line" logic works when a line crosses a buffer boundary.
// We will construct a python script or shell command to print a massive line.
// Cross platform approach: Use Python if available, or just a long echo.
// Let's assume 'python3' or 'python' is in path, otherwise skip?
// Safer: Use pure shell loop if possible, or just a massive command line arg.
String massiveString; String massiveString;
massiveString.reserve(5000); massiveString.reserve(5000);
for (int i = 0; i < 500; ++i) for (int i = 0; i < 500; ++i)
@ -223,10 +217,6 @@ BOOL TestComplexArguments()
// 3. path/to/file // 3. path/to/file
String complexArgs = "-DDEFINED_MSG=\\\"Hello World\\\" -v path/to/file"; String complexArgs = "-DDEFINED_MSG=\\\"Hello World\\\" -v path/to/file";
// We can't easily inspect the child process argv in this unit test framework without
// writing a dedicated child program that prints its argv.
// However, for now, we ensure it doesn't crash or return error code 127.
// Use "echo" to verify what it received. // Use "echo" to verify what it received.
// Expected output: -DDEFINED_MSG="Hello World" -v path/to/file // Expected output: -DDEFINED_MSG="Hello World" -v path/to/file
String cmd = "/bin/echo"; String cmd = "/bin/echo";

View File

@ -21,7 +21,9 @@ using namespace IACore;
IAT_BEGIN_BLOCK(Core, RingBuffer) IAT_BEGIN_BLOCK(Core, RingBuffer)
// -------------------------------------------------------------------------
// 1. Basic Push Pop // 1. Basic Push Pop
// -------------------------------------------------------------------------
BOOL TestPushPop() BOOL TestPushPop()
{ {
// Allocate raw memory for the ring buffer // Allocate raw memory for the ring buffer
@ -54,7 +56,9 @@ BOOL TestPushPop()
return TRUE; return TRUE;
} }
// 2. Wrap Around (The hardest logic to get right) // -------------------------------------------------------------------------
// 2. Wrap Around
// -------------------------------------------------------------------------
BOOL TestWrapAround() BOOL TestWrapAround()
{ {
// Small buffer to force wrapping quickly // Small buffer to force wrapping quickly

View File

@ -20,10 +20,6 @@
using namespace IACore; using namespace IACore;
// -----------------------------------------------------------------------------
// Test Block Definition
// -----------------------------------------------------------------------------
IAT_BEGIN_BLOCK(Core, StreamReader) IAT_BEGIN_BLOCK(Core, StreamReader)
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@ -53,14 +49,13 @@ BOOL TestReadUint8()
BOOL TestReadMultiByte() BOOL TestReadMultiByte()
{ {
// 0x04030201 in Little Endian memory layout // 0x04030201 in Little Endian memory layout
// IACore always assumes a Little Endian machine
UINT8 data[] = {0x01, 0x02, 0x03, 0x04}; UINT8 data[] = {0x01, 0x02, 0x03, 0x04};
StreamReader reader(data); StreamReader reader(data);
auto val = reader.Read<UINT32>(); auto val = reader.Read<UINT32>();
IAT_CHECK(val.has_value()); IAT_CHECK(val.has_value());
// Assuming standard x86/ARM Little Endian for this test
// If your engine supports Big Endian, you'd check architecture here
IAT_CHECK_EQ(*val, (UINT32) 0x04030201); IAT_CHECK_EQ(*val, (UINT32) 0x04030201);
IAT_CHECK_EQ(reader.Cursor(), (SIZE_T) 4); IAT_CHECK_EQ(reader.Cursor(), (SIZE_T) 4);

View File

@ -88,7 +88,6 @@ BOOL TestHexErrors()
// Odd Length // Odd Length
auto odd = IACore::Utils::HexStringToBinary("ABC"); auto odd = IACore::Utils::HexStringToBinary("ABC");
IAT_CHECK_NOT(odd.has_value()); IAT_CHECK_NOT(odd.has_value());
// Optional: IAT_CHECK_EQ(odd.error(), "Hex string must have even length");
// Invalid Characters // Invalid Characters
auto invalid = IACore::Utils::HexStringToBinary("ZZTOP"); auto invalid = IACore::Utils::HexStringToBinary("ZZTOP");
@ -178,9 +177,8 @@ BOOL TestHashMacro()
{ {
TestVec3 v1{1.0f, 2.0f, 3.0f}; TestVec3 v1{1.0f, 2.0f, 3.0f};
TestVec3 v2{1.0f, 2.0f, 3.0f}; TestVec3 v2{1.0f, 2.0f, 3.0f};
TestVec3 v3{1.0f, 2.0f, 4.0f}; // Slight change TestVec3 v3{1.0f, 2.0f, 4.0f};
// Instantiate the hasher manually to verify the struct specialization exists
ankerl::unordered_dense::hash<TestVec3> hasher; ankerl::unordered_dense::hash<TestVec3> hasher;
UINT64 h1 = hasher(v1); UINT64 h1 = hasher(v1);
@ -194,10 +192,6 @@ BOOL TestHashMacro()
// Verify ComputeHash integration // Verify ComputeHash integration
// ------------------------------------------------------------- // -------------------------------------------------------------
// We cannot check EQ(h1, ComputeHash(v1)) because ComputeHash applies
// one extra layer of "Golden Ratio Mixing" on top of the object's hash.
// Instead, we verify that ComputeHash behaves exactly like a manual HashCombine.
UINT64 hManual = 0; UINT64 hManual = 0;
IACore::Utils::HashCombine(hManual, v1); IACore::Utils::HashCombine(hManual, v1);
@ -206,7 +200,7 @@ BOOL TestHashMacro()
// This proves ComputeHash found the specialization and mixed it correctly // This proves ComputeHash found the specialization and mixed it correctly
IAT_CHECK_EQ(hManual, hWrapper); IAT_CHECK_EQ(hManual, hWrapper);
// Optional: Verify the avalanche effect took place (hWrapper should NOT be h1) // Verify the avalanche effect took place (hWrapper should NOT be h1)
IAT_CHECK_NEQ(h1, hWrapper); IAT_CHECK_NEQ(h1, hWrapper);
return TRUE; return TRUE;