diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..3e649942
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+.vscode/
+.idea/
+
+bits/
+cmake-build-*/
+bin/
+build/
+
+CMakeUserPresets.json
diff --git a/CMakeGraphVizOptions.cmake b/CMakeGraphVizOptions.cmake
new file mode 100644
index 00000000..c05a2f01
--- /dev/null
+++ b/CMakeGraphVizOptions.cmake
@@ -0,0 +1,5 @@
+set(GRAPHVIZ_GRAPH_NAME "Draconic Engine dependency graph")
+set(GRAPHVIZ_GRAPH_HEADER "node [ fontsize = \"10\" ];")
+set(GRAPHVIZ_EXECUTABLES FALSE)
+set(GRAPHVIZ_EXTERNAL_LIBS FALSE)
+set(GRAPHVIZ_IGNORE_TARGETS "CMAKE_.*;test_.*|.*_test$")
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 00000000..6697d689
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,10 @@
+cmake_minimum_required(VERSION 4.2)
+#4.0: set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "a9e1cf81-9932-4810-974b-6eccaf14e457")
+#4.2: set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444")
+project(DraconicEngine LANGUAGES C CXX)
+
+list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
+
+include(CTest)
+
+add_subdirectory(engine/native)
\ No newline at end of file
diff --git a/CMakePresets.json b/CMakePresets.json
new file mode 100644
index 00000000..664e41eb
--- /dev/null
+++ b/CMakePresets.json
@@ -0,0 +1,50 @@
+{
+ "version": 10,
+ "cmakeMinimumRequired": {
+ "major": 4,
+ "minor": 0,
+ "patch": 3
+ },
+ "configurePresets": [
+ {
+ "name": "default",
+ "hidden": true,
+ "displayName": "Default Config",
+ "description": "Base configuration",
+ "generator": "Ninja",
+ "graphviz": "graph/deps.dot",
+ "warnings": {
+ "unusedCli": false,
+ "dev": false
+ },
+ "cacheVariables": {
+ "CMAKE_EXPERIMENTAL_CXX_IMPORT_STD": "d0edc3af-4c50-42ea-a356-e2862fe7a444",
+ "CMAKE_CXX_STANDARD": "23",
+ "CMAKE_CXX_STANDARD_REQUIRED": "ON",
+ "CMAKE_CXX_EXTENSIONS": "OFF",
+ "CMAKE_CXX_MODULE_STD": "1",
+ "CMAKE_CXX_FLAGS_INIT": "-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0"
+ }
+ },
+ {
+ "name": "release",
+ "inherits": "default",
+ "displayName": "Release",
+ "description": "Release configuration, including C# support",
+ "binaryDir": "${sourceDir}/build/release",
+ "cacheVariables": {
+ "CMAKE_BUILD_TYPE": "Release"
+ }
+ },
+ {
+ "name": "debug",
+ "inherits": "default",
+ "displayName": "Debug",
+ "description": "Debug configuration, including C# support",
+ "binaryDir": "${sourceDir}/build/debug",
+ "cacheVariables": {
+ "CMAKE_BUILD_TYPE": "Debug"
+ }
+ }
+ ]
+}
diff --git a/README.md b/README.md
index e48bea7d..815a7f52 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
# Draconic Engine
-
-
+
+
diff --git a/icon.png b/assets/draconic_logo_no_text.png
similarity index 100%
rename from icon.png
rename to assets/draconic_logo_no_text.png
diff --git a/icon.svg b/assets/draconic_logo_no_text.svg
similarity index 100%
rename from icon.svg
rename to assets/draconic_logo_no_text.svg
diff --git a/logo.png b/assets/draconic_logo_text.png
similarity index 100%
rename from logo.png
rename to assets/draconic_logo_text.png
diff --git a/cmake/Compiler.cmake b/cmake/Compiler.cmake
new file mode 100644
index 00000000..45258856
--- /dev/null
+++ b/cmake/Compiler.cmake
@@ -0,0 +1,23 @@
+include_guard(GLOBAL)
+
+include(CheckIPOSupported)
+check_ipo_supported(RESULT IPO_SUPPORTED OUTPUT ERROR)
+
+if (CMAKE_BUILD_TYPE STREQUAL "Release")
+ if(IPO_SUPPORTED)
+ message(STATUS "IPO / LTO enabled")
+ add_link_options(-flto)
+ else()
+ message(STATUS "IPO / LTO not supported: <${ERROR}>")
+ endif()
+else()
+ message(STATUS "IPO / LTO disabled")
+ add_compile_definitions(DEBUG)
+endif()
+
+if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
+ if (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
+ # TODO: Make SIMD level configurable or detect at runtime
+ add_compile_options(-mavx2 -mfma)
+ endif()
+endif()
\ No newline at end of file
diff --git a/cmake/Modules.cmake b/cmake/Modules.cmake
new file mode 100644
index 00000000..277065da
--- /dev/null
+++ b/cmake/Modules.cmake
@@ -0,0 +1,110 @@
+include_guard(GLOBAL)
+
+set(NATIVE_SOURCE_DIR "${PROJECT_SOURCE_DIR}/engine/native")
+
+set(NATIVE_THIRD_PARTY_DIR "${NATIVE_SOURCE_DIR}/thirdparty")
+
+if (BUILD_TESTING)
+ message(STATUS "Bootstrapping unit tests module boost.ut")
+ add_library(boost_ut_main "${NATIVE_THIRD_PARTY_DIR}/boost/ut_main.cpp")
+ target_sources(boost_ut_main
+ PUBLIC
+ FILE_SET CXX_MODULES
+ BASE_DIRS "${NATIVE_THIRD_PARTY_DIR}/boost"
+ FILES "${NATIVE_THIRD_PARTY_DIR}/boost/ut.cppm"
+ )
+ target_compile_features(boost_ut_main PUBLIC cxx_std_23)
+endif()
+
+function(add_modules_library)
+ cmake_parse_arguments(
+ MOD_LIB # prefix for all variables
+ "STATIC;SHARED" # tags for flags (only defined ones will be true)
+ "" # tags for single values
+ "" # tags for lists
+ "${ARGN}"
+ )
+
+ set(LIB_PATH ${ARGV0})
+
+ if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${LIB_PATH}")
+ message(FATAL_ERROR "Library directory ${LIB_PATH} not found")
+ endif()
+
+ set(LIB_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${LIB_PATH}")
+
+ if (EXISTS "${LIB_DIR}/CMakeLists.txt") # allow recursion
+ add_subdirectory(${LIB_DIR})
+ endif()
+
+ string(MAKE_C_IDENTIFIER ${LIB_PATH} LIB_TARGET)
+ file(GLOB CPP_MODULE_FILES CONFIGURE_DEPENDS "${LIB_PATH}/*.cppm")
+ file(GLOB CPP_UNIT_TESTS CONFIGURE_DEPENDS "${LIB_PATH}/*.test.cpp")
+ file(GLOB CPP_SRC_FILES CONFIGURE_DEPENDS "${LIB_PATH}/*.cpp")
+ if (CPP_UNIT_TESTS)
+ list(REMOVE_ITEM CPP_SRC_FILES ${CPP_UNIT_TESTS})
+ endif()
+
+ if (MOD_LIB_SHARED)
+ message(STATUS "Adding shared modules library ${LIB_TARGET}")
+ add_library(${LIB_TARGET} SHARED)
+ else()
+ message(STATUS "Adding static modules library ${LIB_TARGET}")
+ add_library(${LIB_TARGET} STATIC)
+ endif()
+ target_compile_features(${LIB_TARGET} PUBLIC cxx_std_23)
+ target_include_directories(${LIB_TARGET} PUBLIC ${NATIVE_SOURCE_DIR})
+
+ target_sources(${LIB_TARGET}
+ PUBLIC
+ FILE_SET CXX_MODULES
+ BASE_DIRS ${LIB_DIR}
+ FILES ${CPP_MODULE_FILES}
+ )
+
+ target_sources(${LIB_TARGET} PRIVATE ${CPP_SRC_FILES})
+
+ if(CMAKE_TESTING_ENABLED)
+ foreach(UNIT_TEST_FILE ${CPP_UNIT_TESTS})
+ string(REPLACE "${LIB_DIR}/" "" UNIT_TEST_TARGET "${UNIT_TEST_FILE}")
+ string(REPLACE ".test.cpp" "_test" UNIT_TEST_TARGET ${UNIT_TEST_TARGET})
+ string(MAKE_C_IDENTIFIER ${UNIT_TEST_TARGET} UNIT_TEST_TARGET)
+ if (NOT UNIT_TEST_TARGET MATCHES ".*${LIB_TARGET}.*")
+ string(PREPEND UNIT_TEST_TARGET "${LIB_TARGET}_")
+ endif()
+ add_executable(${UNIT_TEST_TARGET} ${UNIT_TEST_FILE})
+ target_compile_features(${UNIT_TEST_TARGET} PUBLIC cxx_std_23)
+ target_link_libraries(${UNIT_TEST_TARGET} PRIVATE boost_ut_main ${LIB_TARGET})
+ message(STATUS "Unit test ${UNIT_TEST_TARGET}")
+ add_test(NAME ${UNIT_TEST_TARGET} COMMAND ${UNIT_TEST_TARGET} --reporter junit --out "Testing/${UNIT_TEST_TARGET}.xml")
+ endforeach()
+ endif()
+
+endfunction()
+
+function(target_link_modules)
+ cmake_parse_arguments(
+ MOD_LINK # prefix for all variables
+ "" # tags for flags (only defined ones will be true)
+ "" # tags for single values
+ "PRIVATE;PUBLIC" # tags for lists
+ "${ARGN}"
+ )
+
+ if (MOD_LINK_PUBLIC)
+ foreach(NAME ${MOD_LINK_PUBLIC})
+ set(DIR "${CMAKE_CURRENT_SOURCE_DIR}/${NAME}")
+ string(MAKE_C_IDENTIFIER ${NAME} TARGET)
+ target_link_libraries(${ARGV0} PUBLIC ${TARGET})
+ endforeach()
+ endif()
+
+ if (MOD_LINK_PRIVATE)
+ foreach(NAME ${MOD_LINK_PRIVATE})
+ set(DIR "${CMAKE_CURRENT_SOURCE_DIR}/${NAME}")
+ string(MAKE_C_IDENTIFIER ${NAME} TARGET)
+ target_link_libraries(${ARGV0} PRIVATE ${TARGET})
+ endforeach()
+ endif()
+
+endfunction()
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 00000000..e69de29b
diff --git a/engine/native/CMakeLists.txt b/engine/native/CMakeLists.txt
new file mode 100644
index 00000000..65a4a5a7
--- /dev/null
+++ b/engine/native/CMakeLists.txt
@@ -0,0 +1,5 @@
+include(Compiler)
+include(Modules)
+
+add_modules_library(core SHARED)
+target_link_libraries(core PUBLIC definitions math)
diff --git a/engine/native/core/CMakeLists.txt b/engine/native/core/CMakeLists.txt
new file mode 100644
index 00000000..4474e103
--- /dev/null
+++ b/engine/native/core/CMakeLists.txt
@@ -0,0 +1,3 @@
+add_modules_library(definitions)
+add_modules_library(math)
+target_link_libraries(math PUBLIC definitions)
\ No newline at end of file
diff --git a/engine/native/core/core.cppm b/engine/native/core/core.cppm
new file mode 100644
index 00000000..3b208b31
--- /dev/null
+++ b/engine/native/core/core.cppm
@@ -0,0 +1,3 @@
+export module core;
+export import core.defs;
+export import core.math;
diff --git a/engine/native/core/definitions/definitions.cppm b/engine/native/core/definitions/definitions.cppm
new file mode 100644
index 00000000..c1860c26
--- /dev/null
+++ b/engine/native/core/definitions/definitions.cppm
@@ -0,0 +1,32 @@
+export module core.defs;
+export import core.version;
+import std;
+
+static_assert(__cplusplus >= 202302L, "Minimum of C++23 required.");
+
+export namespace draco {
+
+ // Traits and Concepts
+
+ template
+ concept arithmetic = std::is_arithmetic_v;
+
+ template
+ concept trivial = std::is_trivial_v;
+
+ // Whether the default value of a type is just all-0 bytes.
+ // This can most commonly be exploited by using memset for these types instead of loop-construct.
+ // Must be explicitly specialized to mark a type as such.
+ template
+ struct is_zero_constructible : std::false_type {};
+
+ template
+ constexpr bool is_zero_constructible_v = is_zero_constructible::value;
+
+ template
+ concept zero_constructible = is_zero_constructible_v;
+
+ // Limit the depth of recursive algorithms when dealing with Array/Dictionary
+ constexpr int MAX_RECURSION = 100;
+
+}
\ No newline at end of file
diff --git a/engine/native/core/definitions/version.cppm b/engine/native/core/definitions/version.cppm
new file mode 100644
index 00000000..ab9742e3
--- /dev/null
+++ b/engine/native/core/definitions/version.cppm
@@ -0,0 +1,26 @@
+export module core.version;
+export import std;
+import std.compat;
+
+export namespace draco {
+
+ struct Version {
+ uint16_t major;
+ uint16_t minor;
+ uint16_t patch;
+ };
+
+ constexpr Version VERSION { .major = 2026, .minor = 0, .patch = 0 };
+}
+
+export namespace std {
+ template<> struct formatter {
+ constexpr auto parse(std::format_parse_context& ctx) {
+ return ctx.begin(); // Accept any format spec (or parse custom ones)
+ }
+
+ auto format(const draco::Version& v, std::format_context& ctx) const {
+ return std::format_to(ctx.out(), "{}.{}.{}", v.major, v.minor, v.patch);
+ }
+ };
+}
\ No newline at end of file
diff --git a/engine/native/core/math/constants.cppm b/engine/native/core/math/constants.cppm
new file mode 100644
index 00000000..875501cd
--- /dev/null
+++ b/engine/native/core/math/constants.cppm
@@ -0,0 +1,26 @@
+export module core.math.constants;
+import std;
+
+export namespace draco::math {
+ constexpr double SQRT2 = std::numbers::sqrt2_v;
+ constexpr double SQRT3 = std::numbers::sqrt3_v;
+ constexpr double SQRT12 = 1. / SQRT2;
+ constexpr double SQRT13 = 1. / SQRT3;
+ constexpr double LN2 = std::numbers::ln2_v;
+ constexpr double LN10 = std::numbers::ln10_v;
+ constexpr double PI = std::numbers::pi_v;
+ constexpr double TAU = 2. * PI;
+ constexpr double E = std::numbers::e_v;
+ constexpr double INF = std::numeric_limits::infinity();
+ constexpr double NaN = std::numeric_limits::quiet_NaN();
+ constexpr double DB_CONVERSION_GAIN = 8.6858896380650365530225783783321;
+ constexpr double GAIN_CONVERSION_DB = 0.11512925464970228420089957273422;
+ constexpr double UINT32_MAX_D = 1. / static_cast(std::numeric_limits::max());
+ constexpr float UINT32_MAX_F = 1.f / static_cast(std::numeric_limits::max());
+
+ template constexpr T CMP_EPSILON = T{0.000001};
+ template constexpr T CMP_EPSILON2 = CMP_EPSILON * CMP_EPSILON;
+
+ template constexpr T CMP_NORMALIZE_TOLERANCE = T{0.000001};
+ template constexpr T CMP_POINT_IN_PLANE_EPSILON = T{0.00001};
+}
diff --git a/engine/native/core/math/math.cppm b/engine/native/core/math/math.cppm
new file mode 100644
index 00000000..50282e59
--- /dev/null
+++ b/engine/native/core/math/math.cppm
@@ -0,0 +1,147 @@
+export module core.math;
+export import core.math.constants;
+export import core.math.vector4;
+export import core.defs;
+import std;
+
+export namespace draco::math {
+ template
+ constexpr T sqr(T x) noexcept { return x*x; }
+
+ template
+ [[nodiscard]] constexpr bool is_nan(T val) noexcept {
+ // Only NaN does not equal itself.
+ return val != val;
+ }
+
+ template
+ [[nodiscard]] constexpr bool is_inf(T val) noexcept {
+ return std::isinf(val);
+ }
+
+ template
+ [[nodiscard]] constexpr bool is_finite(T val) noexcept {
+ return std::isfinite(val);
+ }
+
+ template
+ constexpr T abs(T value) noexcept {
+ // Manually compute abs for signed types.
+ // Also avoids potential int8_t -> int issues.
+ if constexpr (std::floating_point) {
+ return value < T{0} ? -value : value;
+ } else if constexpr (std::signed_integral) {
+ if (value == std::numeric_limits::min()) {
+ return std::numeric_limits::max(); // define saturating behavior explicitly
+ }
+ return value < T{0} ? -value : value;
+ } else {
+ // unsigned is always positive! :^)
+ return value;
+ }
+ }
+
+ template
+ constexpr T deg_to_rad(T y) noexcept {
+ return y * (std::numbers::pi_v / T{180.});
+ }
+
+ template
+ constexpr T rad_to_deg(T y) noexcept {
+ return y * (T{180.} / std::numbers::pi_v);
+ }
+
+ template
+ T pow(T x, T y) {
+ return static_cast(std::pow(x, y));
+ }
+
+ template
+ constexpr T lerp(T from, T to, T weight) noexcept {
+ return std::lerp(from, to, weight);
+ }
+
+ template
+ constexpr T cubic_interpolate(T from, T to, T before, T after, T weight) noexcept {
+ // weight squared.
+ T w2 = weight * weight;
+ // weight cubed.
+ T w3 = weight * w2;
+
+ // calculate coefficients.
+ T a = -before + to;
+ T b = T{2} * before - T{5} * from + T{4} * to - after;
+ T c = -before + T{3} * from - T{3} * to + after;
+
+ // Catmull-Rom Interpolation:
+ // 0.5 * ((2 * p_from) + (a * w) + (b * w^2) + (c * w^3))
+
+ if consteval {
+ // compile time
+ return T{0.5} * (T{2.}*from + a*weight + b*w2 + c*w3);
+ } else {
+ // runtime
+ return T{0.5} * std::fma(c, w3, std::fma(b, w2, std::fma(a, weight, T{2} * from)));
+ }
+ }
+
+ template
+ constexpr T cubic_interpolate_in_time(
+ T from, T to,
+ T before, T after, T weight,
+ T to_t, T before_t, T after_t) noexcept {
+ /* Barry-Goldman method */
+ T t = lerp(T{0.}, to_t, weight);
+
+ // At least try to make this easier to parse for others.
+ T pre_scale = before_t == T{0.} ? T{0.} : (t - before_t) / -before_t;
+ T to_scale = (to_t == T{0.}) ? T{.5} : t / to_t;
+ T post_range = after_t - to_t;
+ T post_scale = (post_range == T{0.}) ? T{1.} : (t - to_t) / post_range;
+
+ // First layer.
+ T a1 = lerp(before, from, pre_scale);
+ T a2 = lerp(from, to, to_scale);
+ T a3 = lerp(to, after, post_scale);
+
+ // More parsing.
+ T mid_range = to_t - before_t;
+ T from_to_scale = (mid_range == T{0.}) ? T{0.} : (t - before_t) / mid_range;
+ T to_post_scale = (after_t == T{0.}) ? T{1.} : t / after_t;
+
+ // Second layer.
+ T b1 = lerp(a1, a2, from_to_scale);
+ T b2 = lerp(a2, a3, to_post_scale);
+
+ // One more for the road.
+ T final_scale = (to_t == T{0.}) ? T{.5} : t / to_t;
+
+ return lerp(b1, b2, final_scale);
+ }
+
+ template
+ constexpr T bezier_interpolate(T start, T control_1, T control_2, T end, T t) noexcept {
+ /* Formula from Wikipedia article on Bezier curves. */
+ // one minus t.
+ T omt = T{1.} - t;
+ T omt2 = omt * omt;
+ T omt3 = omt2 * omt;
+ T t2 = t * t;
+ T t3 = t2 * t;
+
+ // B(t) = (1-t)^3 * P_0 + 3(1 - t)^2 * t * P_1 + 3(1 - t) * t^2 * P_2 + t^3 * P_3
+ T d = start * omt3 + control_1 * omt2 * t * T{3.} + control_2 * omt * t2 * T{3.} + end * t3;
+ return d;
+ }
+
+ template
+ constexpr T bezier_derivative(T start, T control_1, T control_2, T end, T t) noexcept {
+ /* Formula from Wikipedia article on Bezier curves. */
+ T omt = T{1.} - t;
+ T omt2 = omt * omt;
+ T t2 = t * t;
+
+ T d = (control_1 - start) * T{3.} * omt2 + (control_2 - control_1) * T{6.} * omt * t + (end - control_2) * T{3.} * t2;
+ return d;
+ }
+}
\ No newline at end of file
diff --git a/engine/native/core/math/math.test.cpp b/engine/native/core/math/math.test.cpp
new file mode 100644
index 00000000..b425529f
--- /dev/null
+++ b/engine/native/core/math/math.test.cpp
@@ -0,0 +1,87 @@
+import boost.ut;
+import core.math;
+
+using namespace boost::ut;
+
+suite<"core.math"> core_math_test = [] {
+ "pow"_test = [] {
+ double result = draco::math::pow(2., .5);
+ constexpr double expected = std::numbers::sqrt2_v;
+ expect(result == expected);
+ };
+
+ "abs"_test = [] {
+ using draco::math::abs;
+
+ expect(abs(-1.f) == 1.f);
+ expect(abs(4.56f) == 4.56f);
+ expect(abs(-1.) == 1.);
+ expect(abs(4.56) == 4.56);
+ expect(abs(-5) == 5);
+ expect(abs(3L) == 3L);
+ expect(abs(-32L) == 32L);
+ expect(abs(5000ULL) == 5000ULL);
+ };
+};
+
+suite<"core.math.vector4"> vector4_tests = [] {
+ "construct_and_access"_test = [] {
+ using draco::math::Vector4;
+ static constexpr Vector4 v{1.0f, 2.0f, 3.0f, 4.0f};
+ static_assert(v[0] == 1.0f);
+ static_assert(v[1] == 2.0f);
+ static_assert(v[2] == 3.0f);
+ static_assert(v[3] == 4.0f);
+ expect(v[0] == 1.0f);
+ expect(v[1] == 2.0f);
+ expect(v[2] == 3.0f);
+ expect(v[3] == 4.0f);
+ };
+
+ "swap"_test = [] {
+ using draco::math::Vector4;
+
+ Vector4 a{1.f, 2.f, 3.f, 4.f};
+ Vector4 b{4.f, 3.f, 2.f, 1.f};
+
+ std::swap(a, b);
+
+ expect(a == Vector4{4.f, 3.f, 2.f, 1.f});
+ expect(b == Vector4{1.f, 2.f, 3.f, 4.f});
+ };
+
+ "dot_basic"_test = [] {
+ using draco::math::Vector4;
+
+ static constexpr Vector4 a{1.0f, 2.0f, 3.0f, 4.0f};
+ static constexpr Vector4 b{5.0f, 6.0f, 7.0f, 8.0f};
+
+ const float result = draco::math::dot(a, b);
+ // 1 * 5 + 2 * 6 + 3 * 7 + 4 * 8
+ const float expected = 70.0f;
+
+ expect(result == expected);
+ };
+
+ "dot_zero"_test = [] {
+ using draco::math::Vector4;
+ using draco::math::dot;
+
+ Vector4 a{0.0f, 0.0f, 0.0f, 0.0f};
+ Vector4 b{1.0f, 2.0f, 3.0f, 4.0f};
+
+ expect(dot(a, b) == 0.0f);
+ };
+
+ "dot_self"_test = [] {
+ using draco::math::Vector4;
+ using draco::math::dot;
+
+ Vector4 v{1.0f, 2.0f, 3.0f, 4.0f};
+
+ const float result = dot(v, v);
+ constexpr float expected = 30.0f;
+
+ expect(result == expected);
+ };
+};
\ No newline at end of file
diff --git a/engine/native/core/math/vector4.cppm b/engine/native/core/math/vector4.cppm
new file mode 100644
index 00000000..d268d01a
--- /dev/null
+++ b/engine/native/core/math/vector4.cppm
@@ -0,0 +1,150 @@
+module;
+
+#include "platform/simd.h"
+
+#if ARCH_X64
+ #include
+#elif ARCH_ARM64
+ #include
+#endif
+
+export module core.math.vector4;
+import core.defs;
+import std;
+
+export namespace draco::math {
+
+ struct alignas(16) Vector4 {
+ float x, y, z, w;
+
+ // constructors.
+ constexpr Vector4() noexcept = default;
+ constexpr Vector4(const float x, const float y, const float z, const float w) noexcept
+ : x{x}, y{y}, z{z}, w{w} { }
+
+ // element access.
+ constexpr float& operator[](const int i) noexcept {
+ if consteval {
+ switch (i) {
+ case 0: return x;
+ case 1: return y;
+ case 2: return z;
+ default:
+ case 3: return w;
+ }
+ } else { return (&x)[i]; }
+ }
+
+ constexpr const float& operator[](const int i) const noexcept {
+ if consteval {
+ switch (i) {
+ case 0: return x;
+ case 1: return y;
+ case 2: return z;
+ default:
+ case 3: return w;
+ }
+ } else { return (&x)[i]; }
+ }
+
+ [[nodiscard]] constexpr bool operator==(const Vector4& other) const noexcept = default;
+
+ constexpr Vector4& operator+=(const Vector4& other) noexcept {
+ x += other.x;
+ y += other.y;
+ z += other.z;
+ w += other.w;
+ return *this;
+ }
+
+ constexpr Vector4& operator-=(const Vector4& other) noexcept {
+ x -= other.x;
+ y -= other.y;
+ z -= other.z;
+ w -= other.w;
+ return *this;
+ }
+
+ constexpr Vector4& operator*=(const Vector4& other) noexcept {
+ x *= other.x;
+ y *= other.y;
+ z *= other.z;
+ w *= other.w;
+ return *this;
+ }
+
+ constexpr Vector4& operator*=(const float s) noexcept {
+ x *= s;
+ y *= s;
+ z *= s;
+ w *= s;
+ return *this;
+ }
+
+ constexpr Vector4& operator/=(const float s) noexcept {
+ const float inv = 1.0f / s;
+ x *= inv;
+ y *= inv;
+ z *= inv;
+ w *= inv;
+ return *this;
+ }
+ };
+
+ // safety features go brr
+ static_assert(sizeof(Vector4) == 16, "Vector4 must be 16 bytes");
+ static_assert(alignof(Vector4) == 16, "Vector4 must be 16-byte aligned");
+ static_assert(trivial, "Vector4 must be trivial");
+ static_assert(std::is_standard_layout_v, "Vector4 must be standard layout");
+
+ [[nodiscard]] constexpr Vector4 operator+(const Vector4& a, const Vector4& b) noexcept {
+ return { a.x+b.x, a.y+b.y, a.z+b.z, a.w+b.w };
+ }
+
+ [[nodiscard]] constexpr Vector4 operator-(const Vector4& a, const Vector4& b) noexcept {
+ return { a.x-b.x, a.y-b.y, a.z-b.z, a.w-b.w };
+ }
+
+ [[nodiscard]] constexpr Vector4 operator*(const Vector4& a, const Vector4& b) noexcept {
+ return { a.x*b.x, a.y*b.y, a.z*b.z, a.w*b.w };
+ }
+
+ [[nodiscard]] constexpr Vector4 operator*(const Vector4& a, const float b) noexcept {
+ return { a.x*b, a.y*b, a.z*b, a.w*b };
+ }
+
+ [[nodiscard]] constexpr Vector4 operator*(const float s, const Vector4& v) noexcept {
+ return v*s;
+ }
+
+ [[nodiscard]] constexpr Vector4 operator/(const Vector4& v, const float s) noexcept {
+ return v * (1.f/s);
+ }
+
+ [[nodiscard]] FORCEINLINE float dot(const Vector4 &a, const Vector4 &b) noexcept {
+ #if ARCH_X64
+ // There's only 4 floats, so SSE is what we will use.
+ // If there's a situation with multiple dot calls, we can setup a
+ // way to call 8 / 16 / 32 floats, but over-head could upset gains.
+ // Be sure it occurs commonly enough to matter here.
+ // Shuffle-first reduction worked best here.
+ __m128 va = _mm_load_ps(&a.x);
+ __m128 vb = _mm_load_ps(&b.x);
+
+ __m128 m = _mm_mul_ps(va, vb);
+
+ __m128 shuf = _mm_movehdup_ps(m);
+ __m128 sum = _mm_add_ps(m, shuf);
+
+ shuf = _mm_movehl_ps(shuf, sum);
+ sum = _mm_add_ss(sum, shuf);
+
+ return _mm_cvtss_f32(sum);
+ #elif ARCH_ARM64
+ #error "ARM64 NEON support not yet implemented."
+ #else
+ // scalar.
+ return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w;
+ #endif
+ }
+} // namespace draco::math
diff --git a/engine/native/platform/cpu/cpu_info.h b/engine/native/platform/cpu/cpu_info.h
new file mode 100644
index 00000000..4ce187b0
--- /dev/null
+++ b/engine/native/platform/cpu/cpu_info.h
@@ -0,0 +1,13 @@
+// platform/cpu/cpu_info.h
+
+#pragma once
+namespace draconic::platform::cpu {
+ enum class CpuFeature : unsigned char {
+ NONE,
+ AVX2,
+ AVX512F,
+ NEON,
+ };
+
+ void validate_cpu() noexcept;
+}
diff --git a/engine/native/platform/cpu/cpu_info_neon.cpp b/engine/native/platform/cpu/cpu_info_neon.cpp
new file mode 100644
index 00000000..e24dc8be
--- /dev/null
+++ b/engine/native/platform/cpu/cpu_info_neon.cpp
@@ -0,0 +1,14 @@
+// platform/cpu/cpu_info_neon.cpp
+
+#include "platform/cpu/cpu_info.h"
+
+#if !defined(__aarch64__)
+ #error cpu_info_neon.cpp compiled on non-ARM64 platform
+#endif
+
+namespace draconic::platform::cpu {
+ void validate_cpu() noexcept {
+ // NEON is mandatory on AArch64.
+ // So, if this compiles - It's valid.
+ }
+}
diff --git a/engine/native/platform/cpu/cpu_info_x64.cpp b/engine/native/platform/cpu/cpu_info_x64.cpp
new file mode 100644
index 00000000..443093c5
--- /dev/null
+++ b/engine/native/platform/cpu/cpu_info_x64.cpp
@@ -0,0 +1,101 @@
+// platform/cpu/cpu_info_x64.cpp
+
+#include "platform/cpu/cpu_info.h"
+
+#include // std::abort
+
+#if defined(_MSC_VER)
+ #include
+#else
+ #include // for mingw.
+ #include
+#endif
+
+#if !defined(__x86_64__) && !defined(_M_X64)
+ #error cpu_info_x64.cpp compiled on non-x86-64 platform
+#endif
+
+namespace draconic::platform::cpu {
+
+ namespace {
+ // Checks if OS has enabled save/restore YMM states. Required for AVX.
+ unsigned long long get_xcr0() noexcept {
+ #if defined(_MSC_VER) || defined(__GNUC__) || defined(__clang__)
+ return _xgetbv(0);
+ #else
+ return 0;
+ #endif
+ }
+
+ // XCR0[1] is XMM, XCR0[2] is YMM.
+ bool os_has_ymm() noexcept {
+ return (get_xcr0() & 0x6ULL) == 0x6ULL;
+ }
+
+ // full ZMM state is required for AVX512.
+ bool os_has_zmm() noexcept {
+ return (get_xcr0() & 0xE6ULL) == 0xE6ULL;
+ }
+
+ void cpuid(unsigned int leaf, unsigned int subleaf, unsigned int& eax, unsigned int& ebx, unsigned int& ecx, unsigned int& edx) noexcept {
+ #if defined(_MSC_VER)
+ int regs[4];
+ __cpuidex(regs, leaf, subleaf);
+ eax = regs[0]; ebx = regs[1]; ecx = regs[2]; edx = regs[3];
+ #else
+ __cpuid_count(leaf, subleaf, eax, ebx, ecx, edx);
+ #endif
+ }
+
+ CpuFeature detect_cpu_feature() noexcept {
+ unsigned int eax = 0;
+ unsigned int ebx = 0;
+ unsigned int ecx = 0;
+ unsigned int edx = 0;
+
+ // leaf 1.
+ cpuid(1, 0, eax, ebx, ecx, edx);
+
+ constexpr unsigned int OSXSAVE = 1u << 27;
+ constexpr unsigned int AVX = 1u << 28;
+ constexpr unsigned int FMA = 1u << 12;
+
+ if ((ecx & (OSXSAVE | AVX | FMA)) != (OSXSAVE | AVX | FMA)) {
+ return CpuFeature::NONE;
+ }
+
+ if (!os_has_ymm()) {
+ return CpuFeature::NONE;
+ }
+
+ // leaf 7
+ cpuid(7, 0, eax, ebx, ecx, edx);
+
+ constexpr unsigned int AVX2 = 1u << 5;
+ constexpr unsigned int AVX512F = 1u << 16;
+
+ if (!(ebx & AVX2)) {
+ return CpuFeature::NONE;
+ }
+
+ if ((ebx & AVX512F) && os_has_zmm()) {
+ return CpuFeature::AVX512F;
+ }
+
+ return CpuFeature::AVX2;
+ }
+
+ } // anonymous namespace.
+
+ void validate_cpu() noexcept {
+ #if defined(__x86_64__) || defined(_M_X64)
+ const CpuFeature level = detect_cpu_feature();
+
+ if(level == CpuFeature::NONE) {
+ std::abort();
+ }
+ #else
+ #error Unsupported architecture.
+ #endif
+ }
+}
diff --git a/engine/native/platform/simd.h b/engine/native/platform/simd.h
new file mode 100644
index 00000000..830b96d6
--- /dev/null
+++ b/engine/native/platform/simd.h
@@ -0,0 +1,72 @@
+// platform/simd.h
+
+#pragma once
+
+// Compiler detection.
+#if defined(__clang__)
+ #define USING_COMPILER_CLANG 1
+#elif defined(_MSC_VER)
+ #define USING_COMPILER_MSVC 1
+#elif defined(__GNUC__)
+ #define USING_COMPILER_GCC 1
+#else
+ #error Unsupported compiler
+#endif
+
+// Architecture detection
+#if defined(__x86_64__) || defined(_M_X64)
+ #define ARCH_X64 1
+#elif defined(__aarch64__)
+ #define ARCH_ARM64 1
+#else
+ #error Unsupported architecture
+#endif
+
+// SIMD level
+#if ARCH_X64
+ #define SIMD_AVX2 1
+
+ // We MAY remove this and have it auto-detect later.
+ #if defined(ENABLE_AVX512)
+ #define SIMD_AVX512 1
+ #endif
+
+#elif ARCH_ARM64
+ #define SIMD_NEON 1
+#endif
+
+// Force inline.
+#if USING_COMPILER_MSVC
+ #define FORCEINLINE __forceinline
+#else
+ #define FORCEINLINE inline __attribute__((always_inline))
+#endif
+
+// Restrict
+#if USING_COMPILER_MSVC
+ #define RESTRICT __restrict
+#else
+ #define RESTRICT __restrict__
+#endif
+
+// Alignment helpers.
+#if USING_COMPILER_MSVC
+ #define ALIGN(N) __declspec(align(N))
+#else
+ #define ALIGN(N) __attribute__((aligned(N)))
+#endif
+
+// assume and unreachable.
+#ifdef DEBUG
+ #if USING_COMPILER_MSVC
+ #define ASSUME(x) do { if (!(x)) __debugbreak(); } while (0)
+ #define UNREACHABLE() __debugbreak()
+ #else
+ #define ASSUME(x) do { if (!(x)) __builtin_trap(); } while (0)
+ #define UNREACHABLE() __builtin_trap()
+ #endif
+#else
+ // TODO: just use [[assume]] in the code
+ #define ASSUME(x) [[assume(x)]] // C++23 — GCC≥13, Clang≥19, MSVC≥17.3
+ #define UNREACHABLE() ASSUME(false)
+#endif
diff --git a/engine/native/thirdparty/boost/ut.cppm b/engine/native/thirdparty/boost/ut.cppm
new file mode 100644
index 00000000..100ac890
--- /dev/null
+++ b/engine/native/thirdparty/boost/ut.cppm
@@ -0,0 +1,22 @@
+module;
+
+#if __has_include() and __has_include()
+#include
+#include
+#endif
+
+export module boost.ut;
+export import std;
+
+#define BOOST_UT_CXX_MODULES 1
+#include "ut.hpp"
+
+template class boost::ut::reporter_junit;
+template void boost::ut::reporter_junit::on(boost::ut::events::log);
+template void boost::ut::reporter_junit::on(boost::ut::events::assertion_pass);
+template void boost::ut::reporter_junit::on(boost::ut::events::assertion_fail);
+template auto boost::ut::detail::test::operator=<>(test_location _test);
+template auto boost::ut::expect(const bool&expr,const reflection::source_location&);
+template void boost::ut::reporter_junit<>::on>(events::assertion_fail>);
+template void boost::ut::reporter_junit<>::on>(events::assertion_pass>);
+template void boost::ut::reporter_junit<>::on>(events::log>);
diff --git a/engine/native/thirdparty/boost/ut.hpp b/engine/native/thirdparty/boost/ut.hpp
new file mode 100644
index 00000000..0d059224
--- /dev/null
+++ b/engine/native/thirdparty/boost/ut.hpp
@@ -0,0 +1,3351 @@
+//
+// Copyright (c) 2019-2021 Kris Jusiak (kris at jusiak dot net)
+//
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+//
+#if defined(BOOST_UT_CXX_MODULES)
+#define BOOST_UT_EXPORT export
+#else
+#pragma once
+#define BOOST_UT_EXPORT
+#endif
+
+#if !defined(BOOST_UT_CXX_MODULES)
+#include
+#endif
+
+#if defined(_MSC_VER)
+#pragma push_macro("min")
+#pragma push_macro("max")
+#undef min
+#undef max
+#endif
+// Before libc++ 17 had experimental support for format and it required a
+// special build flag. Currently libc++ has not implemented all C++20 chrono
+// improvements. Therefore doesn't define __cpp_lib_format, instead query the
+// library version to detect the support status.
+//
+// MSVC STL and libstdc++ provide __cpp_lib_format.
+#if defined(__cpp_lib_format) or \
+ (defined(_LIBCPP_VERSION) and _LIBCPP_VERSION >= 170000)
+#define BOOST_UT_HAS_FORMAT
+#endif
+
+#if not defined(__cpp_rvalue_references)
+#error "[Boost::ext].UT requires support for rvalue references";
+#elif not defined(__cpp_decltype)
+#error "[Boost::ext].UT requires support for decltype";
+#elif not defined(__cpp_return_type_deduction)
+#error "[Boost::ext].UT requires support for return type deduction";
+#elif not defined(__cpp_deduction_guides)
+#error "[Boost::ext].UT requires support for return deduction guides";
+#elif not defined(__cpp_generic_lambdas)
+#error "[Boost::ext].UT requires support for generic lambdas";
+#elif not defined(__cpp_constexpr)
+#error "[Boost::ext].UT requires support for constexpr";
+#elif not defined(__cpp_alias_templates)
+#error "[Boost::ext].UT requires support for alias templates";
+#elif not defined(__cpp_variadic_templates)
+#error "[Boost::ext].UT requires support for variadic templates";
+#elif not defined(__cpp_fold_expressions)
+#error "[Boost::ext].UT requires support for return fold expressions";
+#elif not defined(__cpp_static_assert)
+#error "[Boost::ext].UT requires support for static assert";
+#else
+#define BOOST_UT_VERSION 2'3'1
+
+#if defined(__has_builtin) and defined(__GNUC__) and (__GNUC__ < 10) and \
+ not defined(__clang__)
+#undef __has_builtin
+#endif
+
+#if not defined(__has_builtin)
+#if defined(__GNUC__) and (__GNUC__ >= 9)
+#define __has___builtin_FILE 1
+#define __has___builtin_LINE 1
+#endif
+#define __has_builtin(...) __has_##__VA_ARGS__
+#endif
+
+#if !defined(BOOST_UT_CXX_MODULES)
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#if __has_include() and __has_include()
+#include
+#include
+#endif
+#if defined(__cpp_exceptions)
+#include
+#endif
+
+#if __has_include()
+#include
+#endif
+#if __has_include()
+#include
+#endif
+#endif // cxx modules
+
+struct unique_name_for_auto_detect_prefix_and_suffix_length_0123456789_struct_ {
+};
+
+BOOST_UT_EXPORT
+namespace boost::inline ext::ut::inline v2_3_1 {
+namespace utility {
+template
+class function;
+template
+class function {
+ public:
+ constexpr function() = default;
+ template
+ constexpr /*explicit(false)*/ function(T data)
+ : invoke_{invoke_impl},
+ destroy_{destroy_impl},
+ data_{new T{static_cast(data)}} {}
+ constexpr function(function&& other) noexcept
+ : invoke_{static_cast(other.invoke_)},
+ destroy_{static_cast(other.destroy_)},
+ data_{static_cast(other.data_)} {
+ other.data_ = {};
+ }
+ constexpr function(const function&) = delete;
+ ~function() { destroy_(data_); }
+
+ constexpr function& operator=(const function&) = delete;
+ constexpr function& operator=(function&&) = delete;
+ [[nodiscard]] constexpr auto operator()(TArgs... args) -> R {
+ return invoke_(data_, args...);
+ }
+ [[nodiscard]] constexpr auto operator()(TArgs... args) const -> R {
+ return invoke_(data_, args...);
+ }
+
+ private:
+ template
+ [[nodiscard]] static auto invoke_impl(void* data, TArgs... args) -> R {
+ return (*static_cast(data))(args...);
+ }
+
+ template
+ static auto destroy_impl(void* data) -> void {
+ delete static_cast(data);
+ }
+
+ R (*invoke_)(void*, TArgs...){};
+ void (*destroy_)(void*){};
+ void* data_{};
+};
+
+[[nodiscard]] inline auto is_match(std::string_view input,
+ std::string_view pattern) -> bool {
+ if (std::empty(pattern)) {
+ return std::empty(input);
+ }
+
+ if (std::empty(input)) {
+ return pattern[0] == '*' ? is_match(input, pattern.substr(1)) : false;
+ }
+
+ if (pattern[0] != '?' and pattern[0] != '*' and pattern[0] != input[0]) {
+ return false;
+ }
+
+ if (pattern[0] == '*') {
+ for (decltype(std::size(input)) i = 0u; i <= std::size(input); ++i) {
+ if (is_match(input.substr(i), pattern.substr(1))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ return is_match(input.substr(1), pattern.substr(1));
+}
+
+template
+[[nodiscard]] constexpr auto match(const TPattern& pattern, const TStr& str)
+ -> std::vector {
+ std::vector groups{};
+ auto pi = 0u;
+ auto si = 0u;
+
+ const auto matcher = [&](char b, char e, char c = 0) {
+ const auto match = si;
+ while (str[si] and str[si] != b and str[si] != c) {
+ ++si;
+ }
+ groups.emplace_back(str.substr(match, si - match));
+ while (pattern[pi] and pattern[pi] != e) {
+ ++pi;
+ }
+ pi++;
+ };
+
+ while (pi < std::size(pattern) && si < std::size(str)) {
+ if (pattern[pi] == '\'' and str[si] == '\'' and pattern[pi + 1] == '{') {
+ ++si;
+ matcher('\'', '}');
+ } else if (pattern[pi] == '{') {
+ matcher(' ', '}', ',');
+ } else if (pattern[pi] != str[si]) {
+ return {};
+ }
+ ++pi;
+ ++si;
+ }
+
+ if (si < str.size() or pi < std::size(pattern)) {
+ return {};
+ }
+
+ return groups;
+}
+
+template
+[[nodiscard]] inline auto split(T input, TDelim delim) -> std::vector {
+ std::vector output{};
+ std::size_t first{};
+ while (first < std::size(input)) {
+ const auto second = input.find_first_of(delim, first);
+ if (first != second) {
+ output.emplace_back(input.substr(first, second - first));
+ }
+ if (second == T::npos) {
+ break;
+ }
+ first = second + 1;
+ }
+ return output;
+}
+constexpr auto regex_match(const char* str, const char* pattern) -> bool {
+ if (*pattern == '\0' && *str == '\0') {
+ return true;
+ }
+ if (*pattern == '\0' && *str != '\0') {
+ return false;
+ }
+ if (*str == '\0' && *pattern != '\0') {
+ return false;
+ }
+ if (*pattern == '.') {
+ return regex_match(str + 1, pattern + 1);
+ }
+ if (*pattern == *str) {
+ return regex_match(str + 1, pattern + 1);
+ }
+ return false;
+}
+} // namespace utility
+
+namespace reflection {
+#if defined(__cpp_lib_source_location) && !defined(_LIBCPP_APPLE_CLANG_VER)
+using source_location = std::source_location;
+#else
+class source_location {
+ public:
+ [[nodiscard]] static constexpr auto current(
+#if (__has_builtin(__builtin_FILE) and __has_builtin(__builtin_LINE))
+ const char* file = __builtin_FILE(), int line = __builtin_LINE()
+#else
+ const char* file = "unknown", int line = {}
+#endif
+ ) noexcept {
+ source_location sl{};
+ sl.file_ = file;
+ sl.line_ = line;
+ return sl;
+ }
+ [[nodiscard]] constexpr auto file_name() const noexcept { return file_; }
+ [[nodiscard]] constexpr auto line() const noexcept { return line_; }
+
+ private:
+ const char* file_{"unknown"};
+ int line_{};
+};
+#endif
+namespace detail {
+template
+[[nodiscard]] constexpr auto get_template_function_name_use_type()
+ -> std::string_view {
+// for over compiler need over macros
+#if defined(_MSC_VER) && !defined(__clang__)
+ return {&__FUNCSIG__[0], sizeof(__FUNCSIG__)};
+#else
+ return {&__PRETTY_FUNCTION__[0], sizeof(__PRETTY_FUNCTION__)};
+#endif
+}
+
+// decay allows you to highlight a cleaner name
+template
+[[nodiscard]] constexpr auto get_template_function_name_use_decay_type()
+ -> std::string_view {
+ return get_template_function_name_use_type>();
+}
+
+inline constexpr const std::string_view raw_type_name =
+ get_template_function_name_use_decay_type<
+ unique_name_for_auto_detect_prefix_and_suffix_length_0123456789_struct_>();
+
+inline constexpr const std::size_t raw_length = raw_type_name.length();
+inline constexpr const std::string_view need_name =
+#if defined(_MSC_VER) and not defined(__clang__)
+ "struct "
+ "unique_name_for_auto_detect_prefix_and_suffix_length_0123456789_struct_";
+#else
+ "unique_name_for_auto_detect_prefix_and_suffix_length_0123456789_struct_";
+#endif
+inline constexpr const std::size_t need_length = need_name.length();
+static_assert(need_length <= raw_length,
+ "Auto find prefix and suffix length broken error 1");
+inline constexpr const std::size_t prefix_length =
+ raw_type_name.find(need_name);
+static_assert(prefix_length != std::string_view::npos,
+ "Auto find prefix and suffix length broken error 2");
+static_assert(prefix_length <= raw_length,
+ "Auto find prefix and suffix length broken error 3");
+inline constexpr const std::size_t tail_length = raw_length - prefix_length;
+static_assert(need_length <= tail_length,
+ "Auto find prefix and suffix length broken error 4");
+inline constexpr const std::size_t suffix_length = tail_length - need_length;
+
+} // namespace detail
+
+template
+[[nodiscard]] constexpr auto type_name() -> std::string_view {
+ const std::string_view raw_type_name =
+ detail::get_template_function_name_use_type();
+ const std::size_t end = raw_type_name.length() - detail::suffix_length;
+ const std::size_t len = end - detail::prefix_length;
+ std::string_view result = raw_type_name.substr(detail::prefix_length, len);
+ return result;
+}
+
+// decay allows you to highlight a cleaner name
+template
+[[nodiscard]] constexpr auto decay_type_name() -> std::string_view {
+ const std::string_view raw_type_name =
+ detail::get_template_function_name_use_decay_type();
+ const std::size_t end = raw_type_name.length() - detail::suffix_length;
+ const std::size_t len = end - detail::prefix_length;
+ std::string_view result = raw_type_name.substr(detail::prefix_length, len);
+ return result;
+}
+} // namespace reflection
+
+namespace math {
+template
+[[nodiscard]] constexpr auto abs(const T t) -> T {
+ return t < T{} ? -t : t;
+}
+
+template
+[[nodiscard]] constexpr auto abs_diff(const T t, const U u)
+ -> decltype(t < u ? u - t : t - u) {
+ return t < u ? u - t : t - u;
+}
+
+template
+[[nodiscard]] constexpr auto min_value(const T& lhs, const T& rhs) -> const T& {
+ return (rhs < lhs) ? rhs : lhs;
+}
+
+template
+[[nodiscard]] constexpr auto pow(const T base, const TExp exp) -> T {
+ return exp ? T(base * pow(base, exp - TExp(1))) : T(1);
+}
+
+template
+[[nodiscard]] constexpr auto num() -> T {
+ static_assert(
+ ((Cs == '.' or Cs == '\'' or (Cs >= '0' and Cs <= '9')) and ...));
+ T result{};
+ for (const char c : std::array{Cs...}) {
+ if (c == '.') {
+ break;
+ }
+ if (c >= '0' and c <= '9') {
+ result = result * T(10) + T(c - '0');
+ }
+ }
+ return result;
+}
+
+template
+[[nodiscard]] constexpr auto den() -> T {
+ constexpr const std::array cs{Cs...};
+ T result{};
+ auto i = 0u;
+ while (cs[i++] != '.') {
+ }
+
+ for (auto j = i; j < sizeof...(Cs); ++j) {
+ result += pow(T(10), sizeof...(Cs) - j) * T(cs[j] - '0');
+ }
+ return result;
+}
+
+template
+[[nodiscard]] constexpr auto den_size() -> T {
+ constexpr const std::array cs{Cs...};
+ T i{};
+ while (cs[i++] != '.') {
+ }
+
+ return T(sizeof...(Cs)) - i + T(1);
+}
+
+template
+[[nodiscard]] constexpr auto den_size(TValue value) -> T {
+ constexpr auto precision = TValue(1e-7);
+ T result{};
+ TValue tmp{};
+ do {
+ value *= 10;
+ tmp = value - T(value);
+ ++result;
+ } while (tmp > precision);
+
+ return result;
+}
+
+} // namespace math
+
+namespace type_traits {
+template
+struct list {};
+
+template
+struct identity {
+ using type = T;
+};
+
+template
+struct function_traits : function_traits {};
+
+template
+struct function_traits {
+ using result_type = R;
+ using args = list;
+};
+
+template
+struct function_traits {
+ using result_type = R;
+ using args = list;
+};
+
+template
+struct function_traits {
+ using result_type = R;
+ using args = list;
+};
+
+template
+struct function_traits {
+ using result_type = R;
+ using args = list;
+};
+
+template
+struct has_static_member_object_value : std::false_type {};
+
+template
+struct has_static_member_object_value<
+ T, std::void_t().value)>>
+ : std::bool_constant &&
+ !std::is_function_v> {};
+
+template
+inline constexpr bool has_static_member_object_value_v =
+ has_static_member_object_value::value;
+
+template
+struct has_static_member_object_epsilon : std::false_type {};
+
+template
+struct has_static_member_object_epsilon<
+ T, std::void_t().epsilon)>>
+ : std::bool_constant &&
+ !std::is_function_v> {};
+
+template
+inline constexpr bool has_static_member_object_epsilon_v =
+ has_static_member_object_epsilon::value;
+
+} // namespace type_traits
+
+namespace concepts {
+
+// std::convertible_to also requires implicit conversion to work
+// See https://stackoverflow.com/a/76547623
+template
+concept explicitly_convertible_to =
+ requires { static_cast(std::declval()); };
+
+template
+concept ostreamable = requires(std::ostringstream& os, T t) { os << t; };
+
+} // namespace concepts
+
+template
+struct fixed_string {
+ constexpr static std::size_t N = SIZE;
+ CharT _data[N + 1] = {};
+
+ constexpr explicit(false) fixed_string(const CharT (&str)[N + 1]) noexcept {
+ if constexpr (N != 0) {
+ for (std::size_t i = 0; i < N; ++i) {
+ _data[i] = str[i];
+ }
+ }
+ }
+
+ [[nodiscard]] constexpr std::size_t size() const noexcept { return N; }
+ [[nodiscard]] constexpr bool empty() const noexcept { return N == 0; }
+ [[nodiscard]] constexpr explicit operator std::string_view() const noexcept {
+ return {_data, N};
+ }
+ [[nodiscard]] explicit operator std::string() const noexcept {
+ return {_data, N};
+ }
+ [[nodiscard]] operator const char*() const noexcept { return _data; }
+ [[nodiscard]] constexpr bool operator==(
+ const fixed_string& other) const noexcept {
+ return std::string_view{_data, N} == std::string_view(other);
+ }
+
+ template
+ [[nodiscard]] friend constexpr bool operator==(
+ const fixed_string&, const fixed_string&) {
+ return false;
+ }
+};
+
+template
+fixed_string(const CharT (&str)[N]) -> fixed_string;
+
+struct none {};
+
+namespace events {
+struct run_begin {
+ int argc{};
+ const char** argv{};
+};
+struct test_begin {
+ std::string_view type{};
+ std::string_view name{};
+ reflection::source_location location{};
+};
+struct suite_begin {
+ std::string_view type{};
+ std::string_view name{};
+ reflection::source_location location{};
+};
+struct suite_end {
+ std::string_view type{};
+ std::string_view name{};
+ reflection::source_location location{};
+};
+template
+struct test {
+ std::string_view type{};
+ std::string name{}; /// might be dynamic
+ std::vector tag{};
+ reflection::source_location location{};
+ TArg arg{};
+ Test run{};
+
+ constexpr auto operator()() { run_impl(static_cast(run), arg); }
+ constexpr auto operator()() const { run_impl(static_cast(run), arg); }
+
+ private:
+ static constexpr auto run_impl(Test test, const none&) { test(); }
+
+ template
+ static constexpr auto run_impl(T test, const TArg& arg)
+ -> decltype(test(arg), void()) {
+ test(arg);
+ }
+
+ template
+ static constexpr auto run_impl(T test, const TArg&)
+ -> decltype(test.template operator()(), void()) {
+ test.template operator()();
+ }
+};
+template
+test(std::string_view, std::string_view, std::string_view,
+ reflection::source_location, TArg, Test) -> test;
+template
+struct suite {
+ TSuite run{};
+ std::string_view name{};
+ constexpr auto operator()() { run(); }
+ constexpr auto operator()() const { run(); }
+};
+template
+suite(TSuite) -> suite;
+struct test_run {
+ std::string_view type{};
+ std::string_view name{};
+};
+struct test_finish {
+ std::string_view type{};
+ std::string_view name{};
+};
+template
+struct skip {
+ std::string_view type{};
+ std::string_view name{};
+ TArg arg{};
+};
+template
+skip(std::string_view, std::string_view, TArg) -> skip;
+struct test_skip {
+ std::string_view type{};
+ std::string_view name{};
+};
+template
+struct assertion {
+ TExpr expr{};
+ reflection::source_location location{};
+};
+template
+assertion(TExpr, reflection::source_location) -> assertion;
+template
+struct assertion_pass {
+ TExpr expr{};
+ reflection::source_location location{};
+};
+template
+assertion_pass(TExpr) -> assertion_pass;
+template
+struct assertion_fail {
+ TExpr expr{};
+ reflection::source_location location{};
+};
+template
+assertion_fail(TExpr) -> assertion_fail;
+struct test_end {
+ std::string_view type{};
+ std::string_view name{};
+};
+template
+struct log {
+ TMsg msg{};
+};
+template
+log(TMsg) -> log;
+struct fatal_assertion : std::exception {};
+struct exception {
+ const char* msg{};
+ [[nodiscard]] auto what() const -> const char* { return msg; }
+};
+struct summary {};
+} // namespace events
+
+namespace detail {
+struct op {};
+
+template
+struct fatal_;
+
+struct fatal {
+ template
+ [[nodiscard]] auto operator()(const T& t, const reflection::source_location& sl = reflection::source_location::current()) const {
+ return detail::fatal_{t, sl};
+ }
+};
+struct cfg {
+ using value_ref = std::variant,
+ std::reference_wrapper,
+ std::reference_wrapper>;
+ using option = std::tuple;
+ static inline reflection::source_location location{};
+ static inline bool wip{};
+
+#if defined(_MSC_VER)
+ static inline int largc = __argc;
+ static inline const char** largv = const_cast(__argv);
+#else
+ static inline int largc = 0;
+ static inline const char** largv = nullptr;
+#endif
+
+ static inline std::string executable_name = "unknown executable";
+ static inline std::string query_pattern; // <- done
+ static inline bool invert_query_pattern = false; // <- done
+ static inline std::string query_regex_pattern; // <- done
+ static inline bool show_help = false; // <- done
+ static inline bool show_tests = false; // <- done
+ static inline bool list_tags = false; // <- done
+ static inline bool show_successful_tests = false; // <- done
+ static inline std::string output_filename;
+ static inline std::string use_reporter = "console"; // <- done
+ static inline std::string suite_name;
+ static inline bool abort_early = false; // <- done
+ static inline std::size_t abort_after_n_failures =
+ std::numeric_limits::max(); // <- done
+ static inline bool show_duration = false; // <- done
+ static inline std::size_t show_min_duration = 0;
+ static inline std::string input_filename;
+ static inline bool show_test_names = false; // <- done
+ static inline bool show_reporters = false; // <- done
+ static inline std::string sort_order = "decl";
+ static inline std::size_t rnd_seed = 0; // 0: use time
+ static inline std::string use_colour = "yes"; // <- done
+ static inline bool show_lib_identity = false; // <- done
+ static inline std::string wait_for_keypress = "never";
+
+ static inline const std::vector