Kajetan Johannes Hammerle преди 2 седмици
родител
ревизия
e076804076
променени са 100 файла, в които са добавени 3278 реда и са изтрити 3184 реда
  1. 4 1
      .clangd
  2. 1 0
      .gitignore
  3. 106 193
      CMakeLists.txt
  4. 60 0
      cmake/clang_warnings.cmake
  5. 93 0
      cmake/gcc_warnings.cmake
  6. 1 8
      include/core/AlignedData.hpp
  7. 3 14
      include/core/Array.hpp
  8. 30 7
      include/core/ArrayList.hpp
  9. 7 17
      include/core/BitArray.hpp
  10. 2 7
      include/core/Box.hpp
  11. 34 0
      include/core/Buffer.hpp
  12. 19 0
      include/core/Check.hpp
  13. 27 0
      include/core/Clock.hpp
  14. 9 7
      include/core/Color.hpp
  15. 39 37
      include/core/Components.hpp
  16. 10 0
      include/core/File.hpp
  17. 28 0
      include/core/Frustum.hpp
  18. 395 0
      include/core/HashMap.hpp
  19. 5 6
      include/core/HashedString.hpp
  20. 25 16
      include/core/List.hpp
  21. 90 0
      include/core/Logger.hpp
  22. 8 6
      include/core/Math.hpp
  23. 6 7
      include/core/Matrix.hpp
  24. 6 0
      include/core/Meta.hpp
  25. 3 4
      include/core/Plane.hpp
  26. 3 5
      include/core/Quaternion.hpp
  27. 23 20
      include/core/Queue.hpp
  28. 27 0
      include/core/Random.hpp
  29. 12 0
      include/core/ReadLine.hpp
  30. 120 0
      include/core/Terminal.hpp
  31. 61 0
      include/core/Test.hpp
  32. 45 0
      include/core/Thread.hpp
  33. 88 0
      include/core/ToString.hpp
  34. 1 2
      include/core/Types.hpp
  35. 18 0
      include/core/Unicode.hpp
  36. 1 1
      include/core/UniquePointer.hpp
  37. 78 0
      include/core/Utility.hpp
  38. 63 35
      include/core/Vector.hpp
  39. 12 9
      include/core/View.hpp
  40. 0 281
      include/core/data/HashMap.hpp
  41. 0 179
      include/core/data/LinkedList.hpp
  42. 0 316
      include/core/data/ProbingHashMap.hpp
  43. 0 57
      include/core/data/Stack.hpp
  44. 0 12
      include/core/io/File.hpp
  45. 0 29
      include/core/io/FileReader.hpp
  46. 0 97
      include/core/math/BufferedValue.hpp
  47. 0 36
      include/core/math/Frustum.hpp
  48. 0 47
      include/core/math/MatrixStack.hpp
  49. 0 26
      include/core/thread/Mutex.hpp
  50. 0 24
      include/core/thread/SpinLock.hpp
  51. 0 27
      include/core/thread/Thread.hpp
  52. 0 38
      include/core/utils/Buffer.hpp
  53. 0 24
      include/core/utils/Check.hpp
  54. 0 30
      include/core/utils/Clock.hpp
  55. 0 79
      include/core/utils/Error.hpp
  56. 0 95
      include/core/utils/HashCode.hpp
  57. 0 80
      include/core/utils/Logger.hpp
  58. 0 19
      include/core/utils/New.hpp
  59. 0 33
      include/core/utils/Random.hpp
  60. 0 48
      include/core/utils/Utility.hpp
  61. 0 0
      old/ArrayString.cpp
  62. 0 0
      old/ArrayString.hpp
  63. 0 0
      old/ArrayStringTests.cpp
  64. 35 79
      performance/Main.cpp
  65. 71 52
      src/BitArray.cpp
  66. 30 16
      src/Box.cpp
  67. 19 23
      src/Buffer.cpp
  68. 34 24
      src/Clock.cpp
  69. 0 20
      src/Error.cpp
  70. 9 3
      src/ErrorSimulator.cpp
  71. 34 10
      src/ErrorSimulator.hpp
  72. 49 0
      src/File.cpp
  73. 0 73
      src/FileReader.cpp
  74. 42 33
      src/Frustum.cpp
  75. 22 9
      src/Logger.cpp
  76. 17 14
      src/Matrix.cpp
  77. 0 23
      src/Mutex.cpp
  78. 0 33
      src/New.cpp
  79. 15 12
      src/Plane.cpp
  80. 26 28
      src/Quaternion.cpp
  81. 25 42
      src/Random.cpp
  82. 233 0
      src/ReadLine.cpp
  83. 0 22
      src/SpinLock.cpp
  84. 232 0
      src/Terminal.cpp
  85. 109 0
      src/Test.cpp
  86. 62 35
      src/Thread.cpp
  87. 64 0
      src/ToString.cpp
  88. 54 0
      src/Unicode.cpp
  89. 187 71
      src/Utility.cpp
  90. 11 11
      src/Vector.cpp
  91. 28 21
      src/View.cpp
  92. 0 189
      tasks
  93. 82 49
      test/Main.cpp
  94. 0 27
      test/Test.cpp
  95. 0 94
      test/Test.hpp
  96. 44 35
      test/Tests.hpp
  97. 56 37
      test/modules/ArrayListTests.cpp
  98. 10 7
      test/modules/ArrayTests.cpp
  99. 72 76
      test/modules/BitArrayTests.cpp
  100. 43 37
      test/modules/BoxTests.cpp

+ 4 - 1
.clangd

@@ -1,3 +1,6 @@
 CompileFlags:
-  Add: [-ferror-limit=0, -std=c++20, -DERROR_SIMULATOR=true]
+  Add: [-ferror-limit=0, -DERROR_SIMULATOR=true]
   CompilationDatabase: ./build_debug/
+Diagnostics:
+  Includes:
+    IgnoreHeader: "new"

+ 1 - 0
.gitignore

@@ -1,4 +1,5 @@
 build_debug
+build_profile
 build_release
 install
 profile

+ 106 - 193
CMakeLists.txt

@@ -1,248 +1,161 @@
 cmake_minimum_required(VERSION 3.25)
-project(core)
+project(core CXX)
 
-set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD 23)
 
 set(SRC
-    "src/Logger.cpp"
-    "src/Utility.cpp"
-    "src/Error.cpp"
-    "src/New.cpp"
+    "src/BitArray.cpp"
+    "src/Box.cpp"
     "src/Buffer.cpp"
     "src/Clock.cpp"
-    "src/Random.cpp"
-    "src/BitArray.cpp"
-    "src/Vector.cpp"
-    "src/Quaternion.cpp"
+    "src/File.cpp"
+    "src/Frustum.cpp"
+    "src/Logger.cpp"
     "src/Matrix.cpp"
-    "src/Box.cpp"
     "src/Plane.cpp"
-    "src/Frustum.cpp"
-    "src/View.cpp"
+    "src/Quaternion.cpp"
+    "src/Random.cpp"
+    "src/ReadLine.cpp"
+    "src/Terminal.cpp"
+    "src/Test.cpp"
     "src/Thread.cpp"
-    "src/Mutex.cpp"
-    "src/SpinLock.cpp"
-    "src/FileReader.cpp"
-    "src/ErrorSimulator.cpp"
-    "src/ArrayString.cpp"
+    "src/ToString.cpp"
+    "src/Unicode.cpp"
+    "src/Utility.cpp"
+    "src/Vector.cpp"
+    "src/View.cpp"
 )
 
 set(SRC_TESTS
     "test/Main.cpp"
-    "test/Test.cpp"
-    "test/modules/ArrayTests.cpp"
-    "test/modules/ArrayStringTests.cpp"
-    "test/modules/UtilityTests.cpp"
     "test/modules/ArrayListTests.cpp"
+    "test/modules/ArrayTests.cpp"
     "test/modules/BitArrayTests.cpp"
-    "test/modules/MathTests.cpp"
-    "test/modules/ListTests.cpp"
-    "test/modules/LinkedListTests.cpp"
-    "test/modules/UniquePointerTests.cpp"
-    "test/modules/HashMapTests.cpp"
-    "test/modules/StackTests.cpp"
-    "test/modules/RingBufferTests.cpp"
-    "test/modules/ComponentsTests.cpp"
-    "test/modules/VectorTests.cpp"
-    "test/modules/QuaternionTests.cpp"
-    "test/modules/MatrixTests.cpp"
     "test/modules/BoxTests.cpp"
-    "test/modules/BufferedValueTests.cpp"
-    "test/modules/PlaneTests.cpp"
-    "test/modules/FrustumTests.cpp"
-    "test/modules/ViewTests.cpp"
-    "test/modules/MatrixStackTests.cpp"
-    "test/modules/ColorTests.cpp"
     "test/modules/BufferTests.cpp"
     "test/modules/ClockTests.cpp"
+    "test/modules/ColorTests.cpp"
+    "test/modules/ComponentsTests.cpp"
+    "test/modules/FileTests.cpp"
+    "test/modules/FrustumTests.cpp"
+    "test/modules/HashMapTests.cpp"
+    "test/modules/HashedStringTests.cpp"
+    "test/modules/ListTests.cpp"
+    "test/modules/MathTests.cpp"
+    "test/modules/MatrixTests.cpp"
+    "test/modules/PlaneTests.cpp"
+    "test/modules/QuaternionTests.cpp"
+    "test/modules/QueueTests.cpp"
     "test/modules/RandomTests.cpp"
+    "test/modules/ReadLineTests.cpp"
+    "test/modules/TerminalTests.cpp"
+    "test/modules/TestTests.cpp"
     "test/modules/ThreadTests.cpp"
-    "test/modules/FileReaderTests.cpp"
-    "test/modules/ErrorTests.cpp"
-    "test/modules/NewTests.cpp"
-    "test/modules/HashedStringTests.cpp"
+    "test/modules/UnicodeTests.cpp"
+    "test/modules/UniquePointerTests.cpp"
+    "test/modules/UtilityTests.cpp"
+    "test/modules/VectorTests.cpp"
+    "test/modules/ViewTests.cpp"
 )
 
 set(SRC_PERFORMANCE
     "performance/Main.cpp"
-    "test/Test.cpp"
 )
 
 if("${CMAKE_BUILD_TYPE}" STREQUAL "Release")
-    set(COMPILE_OPTIONS -flto)
-    set(LINK_OPTIONS -flto)
+    set(COMPILE_OPTIONS "")
+    set(LINK_OPTIONS "")
     set(LOG_LEVEL 2)
-    set(ERROR_SIMULATOR "")
+    set(DEFINITIONS "")
+elseif("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo")
+    set(COMPILE_OPTIONS "")
+    set(LINK_OPTIONS "")
+    set(LOG_LEVEL 3)
+    set(DEFINITIONS CHECK_MEMORY)
 else()
+    set(DEFINITIONS ERROR_SIMULATOR CHECK_MEMORY)
     if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
         set(COMPILE_OPTIONS --coverage)
+        set(LINK_OPTIONS gcov)
+    elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
+        set(COMPILE_OPTIONS -fprofile-instr-generate -fcoverage-mapping)
+        set(LINK_OPTIONS ${COMPILE_OPTIONS})
     endif()
-    set(LINK_OPTIONS gcov)
     set(LOG_LEVEL 4)
-    set(ERROR_SIMULATOR "ERROR_SIMULATOR")
+    list(APPEND SRC "src/ErrorSimulator.cpp")
 endif()
 
 if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
-    set(MORE_WARNINGS
-        -Waligned-new=all
-        -Walloc-zero
-        -Wanalyzer-too-complex
-        -Warith-conversion
-        -Warray-bounds=2
-        -Wattribute-alias=2
-        -Wbidi-chars=any
-        -Wcast-align=strict
-        -Wcatch-value=3
-        -Wcomma-subscript
-        -Wconditionally-supported
-        -Wduplicated-branches
-        -Wduplicated-cond
-        -Wformat-overflow=2
-        -Wformat-signedness
-        -Wformat-truncation=2
-        -Wimplicit-fallthrough=5
-        -Winvalid-imported-macros
-        -Wlogical-op
-        -Wmultiple-inheritance
-        -Wnoexcept
-        -Wnormalized=nfkc
-        -Wplacement-new=2
-        -Wredundant-tags
-        -Wshift-overflow=2
-        -Wstack-usage=8388608
-        -Wstrict-null-sentinel
-        -Wstringop-overflow=4
-        -Wsuggest-final-methods
-        -Wsuggest-final-types
-        -Wtrampolines
-        -Wtrivial-auto-var-init
-        -Wunused-const-variable=2
-        -Wuse-after-free=3
-        -Wvirtual-inheritance
-        -Wvolatile
-    )
+    include("cmake/gcc_warnings.cmake")
+elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
+    include("cmake/clang_warnings.cmake")
 endif()
 
 add_library(core STATIC ${SRC})
 target_compile_options(core PUBLIC
     ${COMPILE_OPTIONS}
+    ${WARNINGS}
     -fdiagnostics-color=always
-    -fno-exceptions
-    -fno-rtti
-    -fno-threadsafe-statics
-    # deactivated due to the need for <atomic>
-    #-nostdinc++
-    -pedantic
-    -pedantic-errors
-    -Wall
-    -Walloca
-    -Warray-parameter
-    -Wcast-qual
-    -Wconversion
-    -Wctad-maybe-unsupported
-    -Wctor-dtor-privacy
-    -Wdate-time
-    -Wdeprecated-copy-dtor
-    -Wdeprecated-enum-enum-conversion
-    -Wdeprecated-enum-float-conversion
-    -Wdisabled-optimization
-    -Wdouble-promotion
-    -Weffc++
-    -Wenum-compare
-    -Wenum-conversion
-    -Werror
-    -Wextra
-    -Wextra-semi
-    -Wfloat-equal
-    -Wformat=2
-    -Wframe-larger-than=8388608
-    -Winfinite-recursion
-    -Winit-self
-    -Winvalid-pch
-    -Wlarger-than=1073741824
-    -Wmismatched-tags
-    -Wmissing-braces
-    -Wmissing-declarations
-    -Wmissing-include-dirs
-    -Wmultichar
-    -Wnon-virtual-dtor
-    -Wnull-dereference
-    -Wold-style-cast
-    -Woverlength-strings
-    -Woverloaded-virtual
-    -Wredundant-decls
-    -Wregister
-    -Wshadow
-    -Wsign-conversion
-    -Wsign-promo
-    -Wstack-protector
-    -Wstrict-overflow=2
-    -Wsuggest-override
-    -Wswitch-enum
-    -Wsynth
-    -Wundef
-    -Wunreachable-code
-    -Wvla
-    -Wwrite-strings
-    -Wzero-as-null-pointer-constant
-    ${MORE_WARNINGS}
 )
-target_compile_definitions(core 
-    PUBLIC CORE_LOG_LEVEL=${LOG_LEVEL}
-    PRIVATE ${ERROR_SIMULATOR}
+target_compile_definitions(core
+    PRIVATE LOG_LEVEL=${LOG_LEVEL}
+    PRIVATE ${DEFINITIONS}
 )
-target_link_libraries(core 
-    PUBLIC -nodefaultlibs c m
-    PRIVATE ${LINK_OPTIONS}
+target_link_libraries(core
+    PRIVATE m ${LINK_OPTIONS}
 )
-target_sources(core PUBLIC 
+target_sources(core PUBLIC
     FILE_SET HEADERS
     BASE_DIRS include
-    FILES 
-        ./include/core/data/Stack.hpp
-        ./include/core/data/HashMap.hpp
-        ./include/core/data/Components.hpp
-        ./include/core/data/ArrayList.hpp
-        ./include/core/data/ProbingHashMap.hpp
-        ./include/core/data/List.hpp
-        ./include/core/data/LinkedList.hpp
-        ./include/core/data/BitArray.hpp
-        ./include/core/data/Array.hpp
-        ./include/core/data/RingBuffer.hpp
-        ./include/core/thread/Thread.hpp
-        ./include/core/utils/HashCode.hpp
-        ./include/core/utils/New.hpp
-        ./include/core/utils/Check.hpp
-        ./include/core/utils/Buffer.hpp
-        ./include/core/utils/Random.hpp
-        ./include/core/utils/UniquePointer.hpp
-        ./include/core/utils/Types.hpp
-        ./include/core/utils/Color.hpp
-        ./include/core/utils/Logger.hpp
-        ./include/core/utils/ArrayString.hpp
-        ./include/core/utils/Utility.hpp
-        ./include/core/utils/Meta.hpp
-        ./include/core/utils/AlignedData.hpp
-        ./include/core/utils/Clock.hpp
-        ./include/core/utils/Error.hpp
-        ./include/core/math/Quaternion.hpp
-        ./include/core/math/Box.hpp
-        ./include/core/math/Frustum.hpp
-        ./include/core/math/Vector.hpp
-        ./include/core/math/Matrix.hpp
-        ./include/core/math/View.hpp
-        ./include/core/math/BufferedValue.hpp
-        ./include/core/math/Plane.hpp
-        ./include/core/math/MatrixStack.hpp
-        ./include/core/math/Math.hpp
-        ./include/core/io/File.hpp
-        ./include/core/io/FileReader.hpp
+    FILES
+        ./include/core/AlignedData.hpp
+        ./include/core/Array.hpp
+        ./include/core/ArrayList.hpp
+        ./include/core/BitArray.hpp
+        ./include/core/Box.hpp
+        ./include/core/Buffer.hpp
+        ./include/core/Check.hpp
+        ./include/core/Clock.hpp
+        ./include/core/Color.hpp
+        ./include/core/Components.hpp
+        ./include/core/File.hpp
+        ./include/core/Frustum.hpp
+        ./include/core/HashMap.hpp
+        ./include/core/HashedString.hpp
+        ./include/core/List.hpp
+        ./include/core/Logger.hpp
+        ./include/core/Math.hpp
+        ./include/core/Matrix.hpp
+        ./include/core/Meta.hpp
+        ./include/core/Plane.hpp
+        ./include/core/Quaternion.hpp
+        ./include/core/Queue.hpp
+        ./include/core/Random.hpp
+        ./include/core/ReadLine.hpp
+        ./include/core/Terminal.hpp
+        ./include/core/Test.hpp
+        ./include/core/Thread.hpp
+        ./include/core/ToString.hpp
+        ./include/core/Types.hpp
+        ./include/core/Unicode.hpp
+        ./include/core/UniquePointer.hpp
+        ./include/core/Utility.hpp
+        ./include/core/Vector.hpp
+        ./include/core/View.hpp
 )
 install(TARGETS core FILE_SET HEADERS)
 
 add_executable(test ${SRC_TESTS})
 target_link_libraries(test PRIVATE core)
-target_compile_definitions(test PRIVATE ${ERROR_SIMULATOR})
+target_compile_definitions(test PRIVATE ${DEFINITIONS})
+target_compile_definitions(test
+    PRIVATE LOG_LEVEL=4
+)
 
 add_executable(performance ${SRC_PERFORMANCE})
 target_link_libraries(performance PRIVATE core)
+target_include_directories(performance PRIVATE include)
+target_compile_definitions(performance PRIVATE ${DEFINITIONS})
+target_compile_definitions(performance
+    PRIVATE LOG_LEVEL=4
+)

+ 60 - 0
cmake/clang_warnings.cmake

@@ -0,0 +1,60 @@
+set(WARNINGS
+    -Wall
+    -Walloca
+    -Warray-parameter
+    -Wbad-function-cast
+    -Wcast-qual
+    -Wconditional-uninitialized
+    -Wconversion
+    -Wdate-time
+    -Wdisabled-optimization
+    -Wdouble-promotion
+    -Wenum-compare
+    -Wenum-conversion
+    -Werror
+    -Wextra
+    -Wextra-semi-stmt
+    -Wfloat-equal
+    -Wformat=2
+    -Wframe-larger-than=8388608
+    -Winfinite-recursion
+    -Winit-self
+    -Winvalid-pch
+    -Wlarger-than=1073741824
+    -Wmissing-braces
+    -Wmissing-declarations
+    -Wmissing-include-dirs
+    -Wmissing-noreturn 
+    -Wmissing-prototypes
+    -Wmissing-variable-declarations
+    -Wmultichar
+    -Wnarrowing
+    -Wnested-externs
+    -Wnull-dereference
+    -Wold-style-definition
+    -Woverlength-strings
+    -Wredundant-decls
+    -Wshadow
+    -Wsign-conversion
+    -Wstack-protector
+    -Wstrict-overflow=2
+    -Wstrict-prototypes
+    -Wswitch-enum
+    -Wundef
+    -Wunreachable-code
+    -Wvla
+    -Wwrite-strings
+    -pedantic
+    -pedantic-errors
+)
+
+if(0)
+    set(WARNINGS ${WARNINGS} 
+        -Weverything
+        -Wno-unsafe-buffer-usage
+        -Wno-c++98-compat
+        -Wno-declaration-after-statement
+        -Wno-pre-c2x-compat
+        -Wno-padded
+    )
+endif()

+ 93 - 0
cmake/gcc_warnings.cmake

@@ -0,0 +1,93 @@
+set(WARNINGS
+    -Waligned-new=all
+    -Wall
+    -Walloc-zero
+    -Walloca
+    -Wanalyzer-too-complex
+    -Warith-conversion
+    -Warray-bounds=2
+    -Warray-parameter
+    -Wattribute-alias=2
+    -Wbidi-chars=any
+    -Wcast-align=strict
+    -Wcast-qual
+    -Wcatch-value=3
+    -Wcomma-subscript
+    -Wconditionally-supported
+    -Wconversion
+    -Wctad-maybe-unsupported
+    -Wctor-dtor-privacy
+    -Wdate-time
+    -Wdeprecated-copy-dtor
+    -Wdeprecated-enum-enum-conversion
+    -Wdeprecated-enum-float-conversion
+    -Wdisabled-optimization
+    -Wdouble-promotion
+    -Wduplicated-branches
+    -Wduplicated-cond
+    -Weffc++
+    -Wenum-compare
+    -Wenum-conversion
+    -Werror
+    -Wextra
+    -Wextra-semi
+    -Wfloat-equal
+    -Wformat-overflow=2
+    -Wformat-signedness
+    -Wformat-truncation=2
+    -Wformat=2
+    -Wframe-larger-than=8388608
+    -Wimplicit-fallthrough=5
+    -Winfinite-recursion
+    -Winit-self
+    -Winvalid-constexpr
+    -Winvalid-imported-macros
+    -Winvalid-pch
+    -Winvalid-utf8
+    -Wlarger-than=1073741824
+    -Wlogical-op
+    -Wmismatched-tags
+    -Wmissing-braces
+    -Wmissing-declarations
+    -Wmissing-include-dirs
+    -Wmultichar
+    -Wmultiple-inheritance
+    -Wnoexcept
+    -Wnon-virtual-dtor
+    -Wnormalized=nfkc
+    -Wnull-dereference
+    -Wold-style-cast
+    -Woverlength-strings
+    -Woverloaded-virtual
+    -Wplacement-new=2
+    -Wredundant-decls
+    -Wredundant-tags
+    -Wregister
+    -Wshadow
+    -Wshift-overflow=2
+    -Wsign-conversion
+    -Wsign-promo
+    -Wstack-protector
+    -Wstack-usage=8388608
+    -Wstrict-null-sentinel
+    -Wstrict-overflow=2
+    -Wstringop-overflow=4
+    -Wsuggest-final-methods
+    -Wsuggest-final-types
+    -Wsuggest-override
+    -Wswitch-enum
+    -Wsynth
+    -Wtrampolines
+    -Wtrivial-auto-var-init
+    -Wundef
+    -Wunreachable-code
+    -Wunused-const-variable=2
+    -Wuse-after-free=3
+    -Wvirtual-inheritance
+    -Wvla
+    -Wvolatile
+    -Wwrite-strings
+    -Wzero-as-null-pointer-constant
+    -pedantic
+    -pedantic-errors
+)

+ 1 - 8
include/core/utils/AlignedData.hpp → include/core/AlignedData.hpp

@@ -1,14 +1,7 @@
 #ifndef CORE_ALIGNED_DATA_HPP
 #define CORE_ALIGNED_DATA_HPP
 
-#include "core/utils/Types.hpp"
-
-#define CORE_ASSERT_ALIGNED_DATA(var, type)                                    \
-    static_assert(sizeof(var) == sizeof(type), "aligned data size missmatch"); \
-    static_assert(decltype(var)::getSize() >= decltype(var)::getAlignment(),   \
-                  "size >= alignment");                                        \
-    static_assert(alignof(decltype(var)) == alignof(type),                     \
-                  "aligned data alignment missmatch");
+#include <cstddef>
 
 namespace Core {
     template<size_t SIZE, size_t ALIGNMENT>

+ 3 - 14
include/core/data/Array.hpp → include/core/Array.hpp

@@ -1,19 +1,12 @@
 #ifndef CORE_ARRAY_HPP
 #define CORE_ARRAY_HPP
 
-#include "core/utils/ArrayString.hpp"
+#include <cstddef>
 
 namespace Core {
     template<typename T, size_t N>
-    class Array final {
-        T data[N];
-
-    public:
-        constexpr Array() = default;
-
-        constexpr Array(const T& t) {
-            fill(t);
-        }
+    struct Array final {
+        T data[N] = {};
 
         constexpr void fill(const T& t) {
             for(size_t i = 0; i < N; i++) {
@@ -48,10 +41,6 @@ namespace Core {
         static constexpr size_t getLength() {
             return N;
         }
-
-        void toString(BufferString& s) const {
-            Core::toString(s, *this);
-        }
     };
 }
 

+ 30 - 7
include/core/data/ArrayList.hpp → include/core/ArrayList.hpp

@@ -1,10 +1,10 @@
 #ifndef CORE_ARRAYLIST_HPP
 #define CORE_ARRAYLIST_HPP
 
-#include <assert.h>
+#include <cassert>
 
-#include "core/utils/AlignedData.hpp"
-#include "core/utils/ArrayString.hpp"
+#include "core/AlignedData.hpp"
+#include "core/Meta.hpp"
 
 namespace Core {
     template<typename T, size_t N>
@@ -84,10 +84,24 @@ namespace Core {
             return begin()[index];
         }
 
+        T& getLast() {
+            assert(length > 0);
+            return begin()[length - 1];
+        }
+
+        const T& getLast() const {
+            assert(length > 0);
+            return begin()[length - 1];
+        }
+
         size_t getLength() const {
             return length;
         }
 
+        consteval size_t getCapacity() const {
+            return N;
+        }
+
         void clear() {
             for(size_t i = 0; i < length; i++) {
                 begin()[i].~T();
@@ -104,12 +118,21 @@ namespace Core {
             begin()[length].~T();
         }
 
-        void removeLast() {
-            removeBySwap(length - 1);
+        void remove(size_t index) {
+            assert(index < length);
+            length--;
+            T* currentT = begin() + index;
+            T* endT = end();
+            while(currentT != endT) {
+                T* nextT = currentT + 1;
+                *currentT = Core::move(*nextT);
+                currentT = nextT;
+            }
+            endT->~T();
         }
 
-        void toString(BufferString& s) const {
-            Core::toString(s, *this);
+        void removeLast() {
+            removeBySwap(length - 1);
         }
 
     private:

+ 7 - 17
include/core/data/BitArray.hpp → include/core/BitArray.hpp

@@ -1,11 +1,12 @@
 #ifndef CORE_BIT_ARRAY_HPP
 #define CORE_BIT_ARRAY_HPP
 
-#include "core/utils/ArrayString.hpp"
+#include "core/Types.hpp"
 
 namespace Core {
     class BitArray final {
-        u64 lengthBits;
+        u64 length : 56;
+        u64 bits : 8;
         u64* data;
 
     public:
@@ -21,24 +22,13 @@ namespace Core {
         size_t getBits() const;
         size_t getInternalByteSize() const;
         i64 select(size_t index) const;
-        CError resize(size_t newLength, size_t newBits);
+        void resize(size_t newLength, size_t newBits);
         void fill(u64 value);
-
-        void toString(BufferString& s) const {
-            s.append("[");
-            size_t length = getLength();
-            if(length > 0) {
-                length--;
-                for(size_t i = 0; i < length; i++) {
-                    s.append(get(i)).append(", ");
-                }
-                s.append(get(length));
-            }
-            s.append("]");
-        }
-
+        size_t toString(char* s, size_t n) const;
         void swap(BitArray& other);
     };
+
+    static_assert(sizeof(BitArray) == 16, "invalid bit array size");
 }
 
 #endif

+ 2 - 7
include/core/math/Box.hpp → include/core/Box.hpp

@@ -1,8 +1,7 @@
 #ifndef CORE_BOX_HPP
 #define CORE_BOX_HPP
 
-#include "core/math/Vector.hpp"
-#include "core/utils/ArrayString.hpp"
+#include "core/Vector.hpp"
 
 namespace Core {
     class Box final {
@@ -22,11 +21,7 @@ namespace Core {
         const Vector3& getMin() const;
         const Vector3& getMax() const;
 
-        void toString(BufferString& s) const {
-            s.append("Box(");
-            s.append(min).append(", ");
-            s.append(max).append(")");
-        }
+        size_t toString(char* s, size_t n) const;
     };
 }
 

+ 34 - 0
include/core/Buffer.hpp

@@ -0,0 +1,34 @@
+#ifndef CORE_BUFFER_HPP
+#define CORE_BUFFER_HPP
+
+#include <cstddef>
+
+namespace Core {
+    class Buffer final {
+        size_t length;
+        size_t capacity;
+        char* buffer;
+
+    public:
+        Buffer();
+        Buffer(const Buffer& other);
+        Buffer(Buffer&& other) noexcept;
+        ~Buffer();
+        Buffer& operator=(const Buffer& other);
+        Buffer& operator=(Buffer&& other) noexcept;
+
+        Buffer& add(const void* data, size_t size);
+
+        template<typename T>
+        Buffer& add(const T& t) {
+            return add(&t, sizeof(T));
+        }
+
+        size_t getLength() const;
+        const char* getData() const;
+        void clear();
+        void swap(Buffer& other);
+    };
+}
+
+#endif

+ 19 - 0
include/core/Check.hpp

@@ -0,0 +1,19 @@
+#ifndef CORE_CHECK_HPP
+#define CORE_CHECK_HPP
+
+#if defined(__GNUC__)
+#define check_format(format_index, arg_start_index)                \
+    __attribute__((format(printf, format_index, arg_start_index)))
+#else
+#error "please add a 'check_format' option"
+#endif
+
+#if defined(__GNUC__)
+#define likely(x) __builtin_expect(!!(x), 1)
+#define unlikely(x) __builtin_expect(!!(x), 0)
+#else
+#define likely(x) x
+#define unlikely(x) x
+#endif
+
+#endif

+ 27 - 0
include/core/Clock.hpp

@@ -0,0 +1,27 @@
+#ifndef CORE_CLOCK_HPP
+#define CORE_CLOCK_HPP
+
+#include "core/Array.hpp"
+#include "core/Types.hpp"
+
+namespace Core {
+    struct Clock final {
+        size_t index;
+        i64 last;
+        i64 sum;
+        Array<i64, 1 << 7> time;
+
+    public:
+        Clock();
+
+        // the first invocation will always return 0 nanos
+        i64 update();
+        float getUpdatesPerSecond() const;
+
+        static bool sleepNanos(i64 nanos);
+        static bool sleepMillis(i64 millis);
+        static i64 getNanos();
+    };
+}
+
+#endif

+ 9 - 7
include/core/utils/Color.hpp → include/core/Color.hpp

@@ -1,10 +1,10 @@
 #ifndef CORE_COLOR_HPP
 #define CORE_COLOR_HPP
 
-#include "core/utils/Types.hpp"
+#include "core/Types.hpp"
 
 namespace Core {
-    using ColorChannel = unsigned char;
+    using ColorChannel = u8;
 
     template<size_t N>
     class Color final {
@@ -15,11 +15,13 @@ namespace Core {
         Color() = default;
 
         template<typename OT, typename... Args>
-        Color(OT a, Args&&... args)
-            : data(static_cast<ColorChannel>(a),
-                   static_cast<ColorChannel>(args)...) {
-            static_assert(sizeof...(args) + 1 == N,
-                          "color channel parameters do not match its size");
+        Color(OT a, Args&&... args) :
+            data(
+                static_cast<ColorChannel>(a),
+                static_cast<ColorChannel>(args)...) {
+            static_assert(
+                sizeof...(args) + 1 == N,
+                "color channel parameters do not match its size");
         }
 
         float asFloat(size_t index) const {

+ 39 - 37
include/core/data/Components.hpp → include/core/Components.hpp

@@ -1,10 +1,11 @@
 #ifndef CORE_COMPONENTS_HPP
 #define CORE_COMPONENTS_HPP
 
-#include "core/data/HashMap.hpp"
+#include "core/HashMap.hpp"
+#include "core/List.hpp"
 
 namespace Core {
-    using Entity = int;
+    using Entity = size_t;
 
     template<typename T>
     class Components final {
@@ -15,57 +16,61 @@ namespace Core {
     public:
         template<typename R>
         struct Node final {
-            const Entity& entity;
+            Entity entity;
             R& component;
         };
 
-        template<typename C, typename R>
-        class EntityIterator final {
-            C& components;
-            size_t index;
+        template<typename C>
+        class Iterator final {
+            using EntityIterator = decltype(C().indexToEntity.begin());
+            using ComponentIterator = decltype(C().components.begin());
+            using Component = decltype(*ComponentIterator());
+
+            EntityIterator entityIterator{};
+            ComponentIterator componentIterator{};
 
         public:
-            EntityIterator(C& components_, size_t index_)
-                : components(components_), index(index_) {
+            Iterator(EntityIterator e, ComponentIterator c) :
+                entityIterator(e), componentIterator(c) {
             }
 
-            EntityIterator& operator++() {
-                index++;
+            Iterator& operator++() {
+                ++entityIterator;
+                ++componentIterator;
                 return *this;
             }
 
-            bool operator!=(const EntityIterator& other) const {
-                return index != other.index;
+            bool operator!=(const Iterator& other) const {
+                return entityIterator != other.entityIterator;
             }
 
-            Node<R> operator*() const {
-                return {components.indexToEntity[index],
-                        components.components[index]};
+            Node<Component> operator*() const {
+                return {*entityIterator, *componentIterator};
             }
         };
 
-        template<typename C, typename R>
-        struct EntityIteratorAdapter final {
-            C& components;
+        template<typename C>
+        struct IteratorAdapter final {
+            C& c;
 
-            EntityIterator<C, R> begin() {
-                return EntityIterator<C, R>(components, 0);
+            Iterator<C> begin() {
+                return Iterator<C>(
+                    c.indexToEntity.begin(), c.components.begin());
             }
 
-            EntityIterator<C, R> end() {
-                return EntityIterator<C, R>(components,
-                                            components.components.getLength());
+            Iterator<C> end() {
+                return Iterator<C>(c.indexToEntity.end(), c.components.end());
             }
         };
 
         template<typename... Args>
-        bool put(T*& t, Entity ent, Args&&... args) {
+        bool put(T*& t, Entity e, Args&&... args) {
             size_t index = components.getLength();
             size_t* indexP = nullptr;
-            if(!entityToIndex.tryEmplace(indexP, ent, index)) {
+            if(!entityToIndex.tryEmplace(indexP, e, index)) {
                 return false;
             }
-            indexToEntity.add(ent);
+            indexToEntity.add(e);
             t = &components.put(Core::forward<Args>(args)...);
             return true;
         }
@@ -76,14 +81,14 @@ namespace Core {
             return put(t, e, Core::forward<Args>(args)...);
         }
 
-        bool remove(Entity ent) {
-            size_t* indexP = entityToIndex.search(ent);
+        bool remove(Entity e) {
+            size_t* indexP = entityToIndex.search(e);
             if(indexP == nullptr) {
                 return false;
             }
             size_t lastIndex = components.getLength() - 1;
             size_t index = *indexP;
-            entityToIndex.remove(ent);
+            entityToIndex.remove(e);
             components.removeBySwap(index);
             if(index == lastIndex) {
                 indexToEntity.removeBySwap(index);
@@ -96,11 +101,8 @@ namespace Core {
         }
 
         T* search(Entity e) {
-            size_t* index = entityToIndex.search(e);
-            if(index == nullptr) {
-                return nullptr;
-            }
-            return &(components[*index]);
+            return const_cast<T*>(
+                static_cast<const Components*>(this)->search(e));
         }
 
         const T* search(Entity e) const {
@@ -127,11 +129,11 @@ namespace Core {
             return components.end();
         }
 
-        EntityIteratorAdapter<Components, T> entities() {
+        IteratorAdapter<Components> entities() {
             return {*this};
         }
 
-        EntityIteratorAdapter<const Components, const T> entities() const {
+        IteratorAdapter<const Components> entities() const {
             return {*this};
         }
     };

+ 10 - 0
include/core/File.hpp

@@ -0,0 +1,10 @@
+#ifndef CORE_FILE_HPP
+#define CORE_FILE_HPP
+
+#include "core/List.hpp"
+
+namespace Core {
+    bool readFile(List<char>& content, const char* path);
+}
+
+#endif

+ 28 - 0
include/core/Frustum.hpp

@@ -0,0 +1,28 @@
+#ifndef CORE_FRUSTUM_HPP
+#define CORE_FRUSTUM_HPP
+
+#include "core/Array.hpp"
+#include "core/Matrix.hpp"
+#include "core/Plane.hpp"
+
+namespace Core {
+    class Frustum final {
+        Matrix projection;
+        Array<Plane, 6> planes;
+        float tan;
+        float near;
+        float far;
+
+    public:
+        Frustum(float fieldOfView, float nearClip, float farClip);
+        const Matrix& updateProjection(const IntVector2& size);
+        void updatePlanes(
+            const Vector3& pos, const Vector3& right, const Vector3& up,
+            const Vector3& front, const IntVector2& size);
+
+        bool isInside(const Vector3& pos) const;
+        bool isInside(const Vector3& pos, float radius) const;
+    };
+}
+
+#endif

+ 395 - 0
include/core/HashMap.hpp

@@ -0,0 +1,395 @@
+#ifndef CORE_HASHMAP_HPP
+#define CORE_HASHMAP_HPP
+
+#include "core/List.hpp"
+#include "core/ToString.hpp"
+#include "core/Types.hpp"
+#include "core/Utility.hpp"
+
+template<typename T>
+concept Hashable = requires(const T& t) { t.hashCode(); };
+template<typename T>
+concept HashCast = requires(const T& t) { static_cast<size_t>(t); };
+
+namespace Core {
+    template<Hashable H>
+    inline size_t hashCode(const H& key) {
+        return key.hashCode();
+    }
+
+    template<HashCast H>
+    inline size_t hashCode(const H& key) {
+        return static_cast<size_t>(key);
+    }
+
+    template<typename K, typename V>
+    struct HashMap final {
+        template<typename Value>
+        class Node final {
+            friend HashMap;
+            friend List<Node>;
+            K key;
+
+        public:
+            Value& value;
+
+            const K& getKey() const {
+                return key;
+            }
+
+            size_t toString(char* s, size_t n) const {
+                size_t total = 0;
+                addString(key, s, n, total);
+                addString(" = ", s, n, total);
+                addString(value, s, n, total);
+                return total;
+            }
+
+        private:
+            Node(const K& key_, Value& value_) : key(key_), value(value_) {
+            }
+        };
+
+    private:
+        static inline K INVALID = {};
+
+        template<typename Value, typename R, R (*A)(const K&, Value&)>
+        class Iterator final {
+            const K* currentKey;
+            const K* endKey;
+            Value* currentValue;
+
+        public:
+            Iterator(
+                const K* key, const K* endKey_, Value* value, bool invalidSet) :
+                currentKey(key), endKey(endKey_), currentValue(value) {
+                if(!invalidSet) {
+                    skip();
+                }
+            }
+
+            Iterator& operator++() {
+                ++currentKey;
+                ++currentValue;
+                skip();
+                return *this;
+            }
+
+            bool operator!=(const Iterator& other) const {
+                return currentKey != other.currentKey;
+            }
+
+            R operator*() const {
+                return A(*currentKey, *currentValue);
+            }
+
+        private:
+            void skip() {
+                while(currentKey != endKey && *currentKey == INVALID) {
+                    ++currentKey;
+                    ++currentValue;
+                }
+            }
+        };
+
+        template<typename Value>
+        static Node<Value> access(const K& key, Value& value) {
+            return Node<Value>(key, value);
+        }
+
+        template<typename Value>
+        static Value& accessValue(const K&, Value& value) {
+            return value;
+        }
+
+        static const K& accessKey(const K& key, const V&) {
+            return key;
+        }
+
+        template<typename Value>
+        using BaseEntryIterator = Iterator<Value, Node<Value>, access<Value>>;
+        using EntryIterator = BaseEntryIterator<V>;
+        using ConstEntryIterator = BaseEntryIterator<const V>;
+
+        template<typename Value>
+        using BaseValueIterator = Iterator<Value, Value&, accessValue<Value>>;
+        using ValueIterator = BaseValueIterator<V>;
+        using ConstValueIterator = BaseValueIterator<const V>;
+
+        using ConstKeyIterator = Iterator<const V, const K&, accessKey>;
+
+        template<typename M, typename I>
+        struct IteratorAdapter final {
+            M& map;
+
+            I begin() const {
+                return {
+                    map.keys.begin(), map.keys.end(), map.values,
+                    map.invalidSet};
+            }
+
+            I end() const {
+                return {
+                    map.keys.end(), map.keys.end(), nullptr, map.invalidSet};
+            }
+        };
+
+        using ValueIteratorAdapter = IteratorAdapter<HashMap, ValueIterator>;
+        using ConstValueIteratorAdapter =
+            IteratorAdapter<const HashMap, ConstValueIterator>;
+
+        using ConstKeyIteratorAdapter =
+            IteratorAdapter<const HashMap, ConstKeyIterator>;
+
+    private:
+        List<K> keys{};
+        V* values = nullptr;
+        List<i8> jumps{};
+        size_t entries = 0;
+        bool invalidSet = false;
+
+    public:
+        HashMap() = default;
+
+        HashMap(const HashMap& other) {
+            for(const auto& e : other) {
+                add(e.getKey(), e.value);
+            }
+        }
+
+        HashMap(HashMap&& other) {
+            swap(other);
+        }
+
+        ~HashMap() {
+            size_t length = keys.getLength();
+            if(length > 0) {
+                for(size_t i = 1; i < length; i++) {
+                    if(keys[i] != INVALID) {
+                        values[i].~V();
+                    }
+                }
+                if(invalidSet) {
+                    values[length].~V();
+                }
+            }
+            coreDeleteN(reinterpret_cast<AlignedType<V>*>(values));
+        }
+
+        HashMap& operator=(HashMap other) {
+            swap(other);
+            return *this;
+        }
+
+        void rehash(size_t minCapacity) {
+            if(minCapacity <= keys.getLength()) {
+                return;
+            }
+            HashMap<K, V> map;
+            size_t l = (1lu << roundUpLog2(max(minCapacity, 8lu))) + 1;
+            map.keys.resize(l, INVALID);
+            map.values = reinterpret_cast<V*>(coreNewN(AlignedType<V>, l));
+            map.jumps.resize(l, 0);
+            size_t length = keys.getLength();
+            if(length > 0) {
+                for(size_t i = 1; i < length; i++) {
+                    if(keys[i] != INVALID) {
+                        map.add(keys[i], values[i]);
+                    }
+                }
+                if(invalidSet) {
+                    map.add(INVALID, values[length]);
+                }
+            }
+            swap(map);
+        }
+
+        template<typename... Args>
+        bool tryEmplace(V*& v, const K& key, Args&&... args) {
+            size_t index = 0;
+            if(key == INVALID) {
+                if(invalidSet) {
+                    return false;
+                }
+                rehash(1);
+                invalidSet = true;
+            } else {
+                index = searchSlot(key);
+                if(keys[index] == key) {
+                    return false;
+                }
+            }
+            keys[index] = key;
+            v = new(values + index) V(Core::forward<Args>(args)...);
+            entries++;
+            markSlot(key);
+            return true;
+        }
+
+        template<typename VA>
+        V& put(const K& key, VA&& value) {
+            size_t index = 0;
+            if(key == INVALID) {
+                if(invalidSet) {
+                    return (values[0] = Core::forward<VA>(value));
+                }
+                rehash(1);
+                invalidSet = true;
+            } else {
+                index = searchSlot(key);
+                if(keys[index] == key) {
+                    return (values[index] = Core::forward<VA>(value));
+                }
+            }
+            new(values + index) V(Core::forward<VA>(value));
+            entries++;
+            keys[index] = key;
+            markSlot(key);
+            return values[index];
+        }
+
+        template<typename VA>
+        HashMap& add(const K& key, VA&& value) {
+            put(key, Core::forward<VA>(value));
+            return *this;
+        }
+
+        bool remove(const K& key) {
+            size_t index = 0;
+            if(key == INVALID) {
+                if(!invalidSet) {
+                    return false;
+                }
+                invalidSet = false;
+            } else {
+                index = searchSlot(key);
+                if(keys[index] != key) {
+                    return false;
+                }
+            }
+            values[index].~V();
+            entries--;
+            demarkSlot(key);
+            keys[index] = INVALID;
+            return true;
+        }
+
+        const V* search(const K& key) const {
+            return searchValue<const V>(key);
+        }
+
+        V* search(const K& key) {
+            return searchValue<V>(key);
+        }
+
+        bool contains(const K& key) const {
+            return search(key) != nullptr;
+        }
+
+        HashMap& clear() {
+            HashMap<K, V> map;
+            swap(map);
+            return *this;
+        }
+
+        ConstKeyIteratorAdapter getKeys() const {
+            return {*this};
+        }
+
+        ValueIteratorAdapter getValues() {
+            return {*this};
+        }
+
+        ConstValueIteratorAdapter getValues() const {
+            return {*this};
+        }
+
+        EntryIterator begin() {
+            return {keys.begin(), keys.end(), values, invalidSet};
+        }
+
+        EntryIterator end() {
+            return {keys.end(), keys.end(), nullptr, invalidSet};
+        }
+
+        ConstEntryIterator begin() const {
+            return {keys.begin(), keys.end(), values, invalidSet};
+        }
+
+        ConstEntryIterator end() const {
+            return {keys.end(), keys.end(), nullptr, invalidSet};
+        }
+
+        void swap(HashMap& o) {
+            Core::swap(o.keys, keys);
+            Core::swap(o.values, values);
+            Core::swap(o.jumps, jumps);
+            Core::swap(o.entries, entries);
+            Core::swap(o.invalidSet, invalidSet);
+        }
+
+    private:
+        // clang-format off
+        #define FOR_EACH_HASH_START()                           \
+            do {                                                \
+                size_t baseHash = hashCode(key) * 514'685'581u; \
+                size_t end = keys.getLength() - 2;              \
+                for(size_t i = 0; i <= 5; i++) {                \
+                    size_t hash = 1 + ((baseHash + i) & end)
+        #define FOR_EACH_HASH_STOP() \
+                }                    \
+            } while(false)
+        // clang-format on
+
+        size_t searchSlot(const K& key) {
+            rehash(1);
+            while(true) {
+                // rehash on bad clustering
+                FOR_EACH_HASH_START();
+                if((keys[hash] == INVALID && jumps[hash] == 0) ||
+                   keys[hash] == key) {
+                    return hash;
+                }
+                FOR_EACH_HASH_STOP();
+                rehash(keys.getLength() + 1);
+            }
+        }
+
+        void markSlot(const K& key) {
+            FOR_EACH_HASH_START();
+            if(keys[hash] == key) {
+                return;
+            }
+            jumps[hash]++;
+            FOR_EACH_HASH_STOP();
+        }
+
+        void demarkSlot(const K& key) {
+            FOR_EACH_HASH_START();
+            if(keys[hash] == key) {
+                return;
+            }
+            jumps[hash]--;
+            FOR_EACH_HASH_STOP();
+        }
+
+        template<typename Value>
+        Value* searchValue(const K& key) const {
+            if(keys.getLength() != 0) {
+                if(key == INVALID) {
+                    return invalidSet ? values : nullptr;
+                }
+                FOR_EACH_HASH_START();
+                if(keys[hash] == key) {
+                    return values + hash;
+                } else if(jumps[hash] == 0) {
+                    return nullptr;
+                }
+                FOR_EACH_HASH_STOP();
+            }
+            return nullptr;
+        }
+    };
+}
+
+#endif

+ 5 - 6
include/core/utils/HashedString.hpp → include/core/HashedString.hpp

@@ -1,9 +1,7 @@
 #ifndef CORE_HASHED_STRING_HPP
 #define CORE_HASHED_STRING_HPP
 
-#include <string.h>
-
-#include "core/utils/Types.hpp"
+#include <cstring>
 
 namespace Core {
     struct StringHash {
@@ -15,7 +13,7 @@ namespace Core {
         StringHash h;
         while(*s != '\0') {
             h.length++;
-            h.hash = 2120251889lu * h.hash + static_cast<size_t>(*(s++));
+            h.hash = 2'120'251'889lu * h.hash + static_cast<size_t>(*(s++));
         }
         return h;
     }
@@ -26,11 +24,12 @@ namespace Core {
         char data[N];
 
     public:
-        HashedString() : hash() {
+        HashedString() : hash(), data({}) {
         }
 
         HashedString(const char* s) : hash(hashString(s)) {
-            strncpy(data, s, N);
+            strncpy(data, s, N - 1);
+            data[N - 1] = 0;
         }
 
         bool operator==(const HashedString& other) const {

+ 25 - 16
include/core/data/List.hpp → include/core/List.hpp

@@ -1,12 +1,13 @@
 #ifndef CORE_LIST_HPP
 #define CORE_LIST_HPP
 
-#include <assert.h>
+#include <cassert>
+#include <new>
 
-#include "core/math/Math.hpp"
-#include "core/utils/AlignedData.hpp"
-#include "core/utils/ArrayString.hpp"
-#include "core/utils/New.hpp"
+#include "core/AlignedData.hpp"
+#include "core/Math.hpp"
+#include "core/Meta.hpp"
+#include "core/Utility.hpp"
 
 namespace Core {
     template<typename T>
@@ -19,9 +20,9 @@ namespace Core {
         List() : length(0), capacity(0), data(nullptr) {
         }
 
-        List(const List& other)
-            : length(0), capacity(other.capacity),
-              data(allocate(other.capacity)) {
+        List(const List& other) :
+            length(0), capacity(other.capacity),
+            data(allocate(other.capacity)) {
             for(const T& t : other) {
                 unsafeAdd(t);
             }
@@ -110,13 +111,25 @@ namespace Core {
         }
 
         T& operator[](size_t index) {
+            assert(index < length);
             return data[index];
         }
 
         const T& operator[](size_t index) const {
+            assert(index < length);
             return data[index];
         }
 
+        T& getLast() {
+            assert(length > 0);
+            return data[length - 1];
+        }
+
+        const T& getLast() const {
+            assert(length > 0);
+            return data[length - 1];
+        }
+
         size_t getLength() const {
             return length;
         }
@@ -126,8 +139,8 @@ namespace Core {
         }
 
         void clear() {
-            for(size_t i = 0; i < length; i++) {
-                data[i].~T();
+            for(T& t : *this) {
+                t.~T();
             }
             length = 0;
         }
@@ -158,10 +171,6 @@ namespace Core {
             removeBySwap(length - 1);
         }
 
-        void toString(BufferString& s) const {
-            Core::toString(s, *this);
-        }
-
         void swap(List& other) {
             Core::swap(length, other.length);
             Core::swap(capacity, other.capacity);
@@ -173,12 +182,12 @@ namespace Core {
             if(n <= 0) {
                 return nullptr;
             }
-            return reinterpret_cast<T*>(new(noThrow) AlignedType<T>[n]);
+            return reinterpret_cast<T*>(coreNewN(AlignedType<T>, n));
         }
 
         void ensureCapacity() {
             if(length >= capacity) {
-                reserve(capacity + Math::max(4lu, capacity / 4));
+                reserve(capacity + Core::max(4lu, capacity / 4));
             }
         }
 

+ 90 - 0
include/core/Logger.hpp

@@ -0,0 +1,90 @@
+#ifndef CORE_LOGGER_HPP
+#define CORE_LOGGER_HPP
+
+#include <cstdio>
+
+#include "core/Terminal.hpp"
+#include "core/ToString.hpp"
+
+namespace Core {
+    enum class LogLevel { NONE, ERROR, WARNING, INFO, DEBUG };
+
+    extern LogLevel logLevel;
+
+    using ReportHandler = void (*)(
+        LogLevel l, const char* file, int line, void* data,
+        const char* message);
+    void callReportHandler(
+        LogLevel l, const char* file, int line, const char* report);
+    void setReportHandler(ReportHandler h, void* data);
+
+    template<typename... Args>
+    void callReportHandler(
+        LogLevel l, const char* file, int line, const char* format,
+        Args&&... args) {
+        char buffer[512];
+        formatBuffer(
+            buffer, sizeof(buffer), format, Core::forward<Args>(args)...);
+        callReportHandler(l, file, line, buffer);
+    }
+
+#define REPORT(l, ...)                                          \
+    Core::callReportHandler(l, __FILE__, __LINE__, __VA_ARGS__)
+
+    const char* getShortFileName(const char* s);
+
+    template<typename... Args>
+    void printLog(
+        LogLevel l, const char* file, int line, const char* prefix,
+        const char* format, Args&&... args) {
+        if(logLevel < l) {
+            return;
+        }
+        file = getShortFileName(file);
+        fputs(prefix, stdout);
+        printf("%s:%d | ", file, line);
+        char buffer[512];
+        formatBuffer(
+            buffer, sizeof(buffer), format, Core::forward<Args>(args)...);
+        fputs(buffer, stdout);
+        puts(TERMINAL_RESET);
+    }
+}
+
+#if defined(LOG_LEVEL) && LOG_LEVEL >= 1
+#define LOG_ERROR(...)                               \
+    Core::printLog(                                  \
+        Core::LogLevel::ERROR, __FILE__, __LINE__,   \
+        TERMINAL_BRIGHT_RED "[ERROR] ", __VA_ARGS__)
+#else
+#define LOG_ERROR(...)
+#endif
+
+#if defined(LOG_LEVEL) && LOG_LEVEL >= 2
+#define LOG_WARNING(...)                                  \
+    Core::printLog(                                       \
+        Core::LogLevel::WARNING, __FILE__, __LINE__,      \
+        TERMINAL_BRIGHT_YELLOW "[WARNING] ", __VA_ARGS__)
+#else
+#define LOG_WARNING(...)
+#endif
+
+#if defined(LOG_LEVEL) && LOG_LEVEL >= 3
+#define LOG_INFO(...)                                                      \
+    Core::printLog(                                                        \
+        Core::LogLevel::INFO, __FILE__, __LINE__, TERMINAL_BOLD "[INFO] ", \
+        __VA_ARGS__)
+#else
+#define LOG_INFO(...)
+#endif
+
+#if defined(LOG_LEVEL) && LOG_LEVEL >= 4
+#define LOG_DEBUG(...)                                               \
+    Core::printLog(                                                  \
+        Core::LogLevel::DEBUG, __FILE__, __LINE__,                   \
+        TERMINAL_BOLD TERMINAL_BRIGHT_BLACK "[DEBUG] ", __VA_ARGS__)
+#else
+#define LOG_DEBUG(...)
+#endif
+
+#endif

+ 8 - 6
include/core/math/Math.hpp → include/core/Math.hpp

@@ -1,9 +1,9 @@
 #ifndef CORE_MATH_HPP
 #define CORE_MATH_HPP
 
-#include "core/utils/Meta.hpp"
+#include "core/Meta.hpp"
 
-namespace Core::Math {
+namespace Core {
     template<typename T>
     T interpolate(const T& a, const T& b, float f) {
         return a * (1.0f - f) + b * f;
@@ -56,12 +56,14 @@ namespace Core::Math {
 
     constexpr float PI = 3.14159265358979323846f;
 
-    constexpr float radianToDegree(float radians) {
-        return radians * (180.0f / PI);
+    template<typename T>
+    constexpr T radianToDegree(const T& radians) {
+        return radians * static_cast<T>(180.0f / PI);
     }
 
-    constexpr float degreeToRadian(float degrees) {
-        return degrees * (PI / 180.0f);
+    template<typename T>
+    constexpr T degreeToRadian(const T& radians) {
+        return radians * static_cast<T>(PI / 180.0f);
     }
 }
 

+ 6 - 7
include/core/math/Matrix.hpp → include/core/Matrix.hpp

@@ -1,8 +1,7 @@
 #ifndef CORE_MATRIX_HPP
 #define CORE_MATRIX_HPP
 
-#include "core/math/Quaternion.hpp"
-#include "core/utils/ArrayString.hpp"
+#include "core/Quaternion.hpp"
 
 namespace Core {
     class Matrix final {
@@ -12,7 +11,7 @@ namespace Core {
         Matrix();
         Matrix& unit();
         Matrix& set(size_t index, const Vector4& v);
-        Matrix transpose();
+        Matrix transpose() const;
         const float* getValues() const;
         Matrix& operator*=(const Matrix& other);
         Matrix operator*(const Matrix& other) const;
@@ -24,11 +23,11 @@ namespace Core {
         Matrix& translateY(float ty);
         Matrix& translateZ(float tz);
         Matrix& translateTo(const Vector3& v);
-        Matrix& rotateX(float degrees);
-        Matrix& rotateY(float degrees);
-        Matrix& rotateZ(float degrees);
+        Matrix& rotateX(float radians);
+        Matrix& rotateY(float radians);
+        Matrix& rotateZ(float radians);
         Matrix& rotate(const Quaternion& q);
-        void toString(BufferString& s) const;
+        size_t toString(char* s, size_t n) const;
 
     private:
         Matrix& rotate(float degrees, int a, int b);

+ 6 - 0
include/core/utils/Meta.hpp → include/core/Meta.hpp

@@ -49,6 +49,12 @@ namespace Core {
         };
     }
 
+    template<typename T>
+    concept Iterable = requires(T& t) {
+        t.begin();
+        t.end();
+    };
+
     template<typename T>
     using RemovePointer = Internal::BaseRemovePointer<T>::Type;
 

+ 3 - 4
include/core/math/Plane.hpp → include/core/Plane.hpp

@@ -1,8 +1,7 @@
 #ifndef CORE_PLANE_HPP
 #define CORE_PLANE_HPP
 
-#include "core/math/Vector.hpp"
-#include "core/utils/ArrayString.hpp"
+#include "core/Vector.hpp"
 
 namespace Core {
     class Plane final {
@@ -12,8 +11,8 @@ namespace Core {
     public:
         Plane();
         Plane(const Vector3& a, const Vector3& b, const Vector3& c);
-        float getSignedDistance(const Vector3& v) const;
-        void toString(BufferString& s) const;
+        float signedDistance(const Vector3& v) const;
+        size_t toString(char* s, size_t n) const;
     };
 }
 

+ 3 - 5
include/core/math/Quaternion.hpp → include/core/Quaternion.hpp

@@ -1,13 +1,11 @@
 #ifndef CORE_QUATERNION_HPP
 #define CORE_QUATERNION_HPP
 
-#include "core/math/Vector.hpp"
-#include "core/utils/ArrayString.hpp"
+#include "core/Vector.hpp"
 
 namespace Core {
     class Quaternion final {
-        Vector3 xyz;
-        float w;
+        Vector4 v;
 
     public:
         Quaternion();
@@ -16,7 +14,7 @@ namespace Core {
         Quaternion& operator*=(const Quaternion& other);
         Quaternion operator*(const Quaternion& other) const;
         Vector3 operator*(const Vector3& v) const;
-        void toString(BufferString& s) const;
+        size_t toString(char* s, size_t n) const;
     };
 }
 

+ 23 - 20
include/core/data/RingBuffer.hpp → include/core/Queue.hpp

@@ -1,36 +1,36 @@
-#ifndef CORE_RINGBUFFER_HPP
-#define CORE_RINGBUFFER_HPP
+#ifndef CORE_QUEUE_HPP
+#define CORE_QUEUE_HPP
 
-#include <assert.h>
+#include <cassert>
 
-#include "core/utils/AlignedData.hpp"
-#include "core/utils/ArrayString.hpp"
+#include "core/AlignedData.hpp"
+#include "core/ToString.hpp"
 
 namespace Core {
     template<typename T, size_t N>
-    class RingBuffer final {
+    class Queue final {
         AlignedType<T> data[N];
         size_t writeIndex = 0;
         size_t readIndex = 0;
         size_t values = 0;
 
     public:
-        RingBuffer() = default;
+        Queue() = default;
 
-        RingBuffer(const RingBuffer& other) {
+        Queue(const Queue& other) {
             copy(other);
         }
 
-        RingBuffer(RingBuffer&& other) {
+        Queue(Queue&& other) noexcept {
             move(Core::move(other));
             other.clear();
         }
 
-        ~RingBuffer() {
+        ~Queue() {
             clear();
         }
 
-        RingBuffer& operator=(const RingBuffer& other) {
+        Queue& operator=(const Queue& other) {
             if(&other != this) {
                 clear();
                 copy(other);
@@ -38,7 +38,7 @@ namespace Core {
             return *this;
         }
 
-        RingBuffer& operator=(RingBuffer&& other) {
+        Queue& operator=(Queue&& other) noexcept {
             if(&other != this) {
                 clear();
                 move(Core::move(other));
@@ -59,7 +59,7 @@ namespace Core {
         }
 
         template<typename... Args>
-        RingBuffer& add(Args&&... args) {
+        Queue& add(Args&&... args) {
             put(Core::forward<Args>(args)...);
             return *this;
         }
@@ -96,27 +96,30 @@ namespace Core {
             readIndex = (readIndex + 1) % N;
         }
 
-        void toString(BufferString& s) const {
-            s.append("[");
+        size_t toString(char* s, size_t n) const {
+            size_t total = 0;
+            addString("[", s, n, total);
             size_t end = getLength();
             if(end > 0) {
                 end--;
                 for(size_t i = 0; i < end; i++) {
-                    s.append((*this)[i]).append(", ");
+                    addString((*this)[i], s, n, total);
+                    addString(", ", s, n, total);
                 }
-                s.append((*this)[end]);
+                addString((*this)[end], s, n, total);
             }
-            s.append("]");
+            addString("]", s, n, total);
+            return total;
         }
 
     private:
-        void copy(const RingBuffer& other) {
+        void copy(const Queue& other) {
             for(size_t i = 0; i < other.getLength(); i++) {
                 add(other[i]);
             }
         }
 
-        void move(RingBuffer&& other) {
+        void move(Queue&& other) {
             for(size_t i = 0; i < other.getLength(); i++) {
                 add(Core::move(other[i]));
             }

+ 27 - 0
include/core/Random.hpp

@@ -0,0 +1,27 @@
+#ifndef CORE_RANDOM_HPP
+#define CORE_RANDOM_HPP
+
+#include "core/Array.hpp"
+#include "core/Types.hpp"
+
+namespace Core {
+    class Random final {
+        Array<u32, 25> data;
+        size_t index;
+
+    public:
+        Random(u32 seed);
+
+        u32 nextU32();
+        u32 nextU32(u32 min, u32 exclusiveMax);
+        i32 nextI32(i32 min, i32 exclusiveMax);
+        size_t nextSize(size_t min, size_t exclusiveMax);
+        bool nextBool();
+        float nextFloat();
+
+    private:
+        void update();
+    };
+}
+
+#endif

+ 12 - 0
include/core/ReadLine.hpp

@@ -0,0 +1,12 @@
+#ifndef CORE_READ_LINE_HPP
+#define CORE_READ_LINE_HPP
+
+#include <cstddef>
+
+namespace Core {
+    bool startReadLine();
+    bool readLine(char* buffer, size_t n);
+    void stopReadLine();
+}
+
+#endif

+ 120 - 0
include/core/Terminal.hpp

@@ -0,0 +1,120 @@
+#ifndef CORE_TERMINAL_HPP
+#define CORE_TERMINAL_HPP
+
+#include "core/Types.hpp"
+#include "core/Vector.hpp"
+
+#define ESC "\33["
+#define TERMINAL_RESET ESC "0m"
+#define TERMINAL_BOLD ESC "1m"
+
+#define TERMINAL_BLACK ESC "30m"
+#define TERMINAL_RED ESC "31m"
+#define TERMINAL_GREEN ESC "32m"
+#define TERMINAL_YELLOW ESC "33m"
+#define TERMINAL_BLUE ESC "34m"
+#define TERMINAL_MAGENTA ESC "35m"
+#define TERMINAL_CYAN ESC "36m"
+#define TERMINAL_WHITE ESC "37m"
+#define TERMINAL_BRIGHT_BLACK ESC "90m"
+#define TERMINAL_BRIGHT_RED ESC "91m"
+#define TERMINAL_BRIGHT_GREEN ESC "92m"
+#define TERMINAL_BRIGHT_YELLOW ESC "93m"
+#define TERMINAL_BRIGHT_BLUE ESC "94m"
+#define TERMINAL_BRIGHT_MAGENTA ESC "95m"
+#define TERMINAL_BRIGHT_CYAN ESC "96m"
+#define TERMINAL_BRIGHT_WHITE ESC "97m"
+
+#define TERMINAL_FG_BLACK TERMINAL_BLACK
+#define TERMINAL_FG_RED TERMINAL_RED
+#define TERMINAL_FG_GREEN TERMINAL_GREEN
+#define TERMINAL_FG_YELLOW TERMINAL_YELLOW
+#define TERMINAL_FG_BLUE TERMINAL_BLUE
+#define TERMINAL_FG_MAGENTA TERMINAL_MAGENTA
+#define TERMINAL_FG_CYAN TERMINAL_CYAN
+#define TERMINAL_FG_WHITE TERMINAL_WHITE
+#define TERMINAL_FG_BRIGHT_BLACK TERMINAL_BRIGHT_BLACK
+#define TERMINAL_FG_BRIGHT_RED TERMINAL_BRIGHT_RED
+#define TERMINAL_FG_BRIGHT_GREEN TERMINAL_BRIGHT_GREEN
+#define TERMINAL_FG_BRIGHT_YELLOW TERMINAL_BRIGHT_YELLOW
+#define TERMINAL_FG_BRIGHT_BLUE TERMINAL_BRIGHT_BLUE
+#define TERMINAL_FG_BRIGHT_MAGENTA TERMINAL_BRIGHT_MAGENTA
+#define TERMINAL_FG_BRIGHT_CYAN TERMINAL_BRIGHT_CYAN
+#define TERMINAL_FG_BRIGHT_WHITE TERMINAL_BRIGHT_WHITE
+
+#define TERMINAL_BG_BLACK ESC "40m"
+#define TERMINAL_BG_RED ESC "41m"
+#define TERMINAL_BG_GREEN ESC "42m"
+#define TERMINAL_BG_YELLOW ESC "43m"
+#define TERMINAL_BG_BLUE ESC "44m"
+#define TERMINAL_BG_MAGENTA ESC "45m"
+#define TERMINAL_BG_CYAN ESC "46m"
+#define TERMINAL_BG_WHITE ESC "47m"
+#define TERMINAL_BG_BRIGHT_BLACK ESC "100m"
+#define TERMINAL_BG_BRIGHT_RED ESC "101m"
+#define TERMINAL_BG_BRIGHT_GREEN ESC "102m"
+#define TERMINAL_BG_BRIGHT_YELLOW ESC "103m"
+#define TERMINAL_BG_BRIGHT_BLUE ESC "104m"
+#define TERMINAL_BG_BRIGHT_MAGENTA ESC "105m"
+#define TERMINAL_BG_BRIGHT_CYAN ESC "106m"
+#define TERMINAL_BG_BRIGHT_WHITE ESC "107m"
+// keycodes
+#define TERMINAL_KEY_UNKNOWN 0x1'0000'0000lu
+// default keycodes
+#define TERMINAL_KEY_ARROW_LEFT 0x1'0000'0001lu
+#define TERMINAL_KEY_ARROW_RIGHT 0x1'0000'0002lu
+#define TERMINAL_KEY_ARROW_UP 0x1'0000'0003lu
+#define TERMINAL_KEY_ARROW_DOWN 0x1'0000'0004lu
+#define TERMINAL_KEY_DELETE 0x1'0000'0005lu
+#define TERMINAL_KEY_F1 0x1'0000'0006lu
+#define TERMINAL_KEY_F2 0x1'0000'0007lu
+#define TERMINAL_KEY_F3 0x1'0000'0008lu
+#define TERMINAL_KEY_F4 0x1'0000'0009lu
+#define TERMINAL_KEY_F5 0x1'0000'000Alu
+#define TERMINAL_KEY_F6 0x1'0000'000Blu
+#define TERMINAL_KEY_F7 0x1'0000'000Clu
+#define TERMINAL_KEY_F8 0x1'0000'000Dlu
+#define TERMINAL_KEY_F9 0x1'0000'000Elu
+#define TERMINAL_KEY_F10 0x1'0000'000Flu
+#define TERMINAL_KEY_F11 0x1'0000'0010lu
+#define TERMINAL_KEY_F12 0x1'0000'0011lu
+#define TERMINAL_KEY_PAGE_UP 0x1'0000'0012lu
+#define TERMINAL_KEY_PAGE_DOWN 0x1'0000'0013lu
+#define TERMINAL_KEY_HOME 0x1'0000'0014lu
+#define TERMINAL_KEY_END 0x1'0000'0015lu
+// key modifiers
+#define TERMINAL_KEY_CTRL 0x2'0000'0000lu
+#define TERMINAL_KEY_SHIFT 0x4'0000'0000lu
+#define TERMINAL_KEY_ALT 0x8'0000'0000lu
+
+namespace Core {
+    void enterAlternativeTerminal();
+    void leaveAlternativeTerminal();
+    IntVector2 getTerminalSize();
+    void clearTerminal();
+    void clearTerminalLine();
+
+    void hideCursor();
+    void showCursor();
+    void resetCursor();
+    void moveCursorLeft(int i);
+    void moveCursorRight(int i);
+    void moveCursorUp(int i);
+    void moveCursorDown(int i);
+
+    bool enterRawTerminal();
+    bool leaveRawTerminal();
+    u64 getRawChar();
+    bool isSpecialChar(u64 u);
+
+    struct SpecialChar {
+        u64 key = 0;
+        bool control = 0;
+        bool shift = 0;
+        bool alt = 0;
+    };
+
+    SpecialChar convertToSpecialChar(u64 u);
+}
+
+#endif

+ 61 - 0
include/core/Test.hpp

@@ -0,0 +1,61 @@
+#ifndef CORE_TEST_HPP
+#define CORE_TEST_HPP
+
+#include "core/Logger.hpp"
+
+namespace Core {
+    void finalizeTests(void);
+    bool addTestResult(const char* file, bool comparison);
+
+    template<typename T>
+    bool testEqual(
+        const char* file, int line, const T& wanted, const T& actual) {
+        file = getShortFileName(file);
+        if(addTestResult(file, wanted == actual)) {
+            return true;
+        }
+        char buffer[512];
+        formatBuffer(
+            buffer, sizeof(buffer),
+            TERMINAL_RED "#:# - expected '#' got '#'" TERMINAL_RESET, file,
+            line, wanted, actual);
+        puts(buffer);
+        return false;
+    }
+
+    bool testString(
+        const char* file, int line, const char* wanted, const char* actual);
+
+    template<typename A, typename B>
+    bool testString(
+        const char* file, int line, const A& wanted, const B& actual) {
+        char wantedString[512];
+        size_t lw = toString(wanted, wantedString, sizeof(wantedString));
+        char actualString[512];
+        size_t la = toString(actual, actualString, sizeof(actualString));
+        testEqual(file, line, lw, la);
+        return testString(
+            file, line, static_cast<const char*>(wantedString),
+            static_cast<const char*>(actualString));
+    }
+
+    bool testFloat(
+        const char* file, int line, float wanted, float actual, float error);
+    bool testNull(const char* file, int line, const void* p);
+    bool testNotNull(const char* file, int line, const void* p);
+
+#define TEST_FLOAT(wanted, actual, error)                      \
+    Core::testFloat(__FILE__, __LINE__, wanted, actual, error)
+
+#define TEST(wanted, actual)                                  \
+    Core::testEqual<Core::RemoveReference<decltype(actual)>>( \
+        __FILE__, __LINE__, wanted, actual)
+#define TEST_STRING(wanted, actual)                      \
+    Core::testString(__FILE__, __LINE__, wanted, actual)
+#define TEST_FALSE(actual) TEST(false, actual)
+#define TEST_TRUE(actual) TEST(true, actual)
+#define TEST_NULL(actual) Core::testNull(__FILE__, __LINE__, actual)
+#define TEST_NOT_NULL(actual) Core::testNotNull(__FILE__, __LINE__, actual)
+}
+
+#endif

+ 45 - 0
include/core/Thread.hpp

@@ -0,0 +1,45 @@
+#ifndef CORE_THREAD_HPP
+#define CORE_THREAD_HPP
+
+#include <mutex>
+#include <thread>
+
+namespace Core {
+    class Mutex final {
+        std::mutex mutex{};
+
+    public:
+        void lock() noexcept;
+        void unlock() noexcept;
+    };
+
+    class MutexGuard {
+        Mutex& mutex;
+
+    public:
+        MutexGuard(Mutex& m);
+        MutexGuard(const MutexGuard&) = delete;
+        MutexGuard(MutexGuard&&) = delete;
+        ~MutexGuard();
+        MutexGuard& operator=(const MutexGuard&) = delete;
+        MutexGuard& operator=(MutexGuard&&) = delete;
+    };
+
+    class Thread final {
+        std::thread thread;
+
+    public:
+        using Function = void (*)(void*);
+
+        Thread();
+        Thread(const Thread& other) = delete;
+        Thread(Thread&& other);
+        ~Thread();
+        Thread& operator=(const Thread& other) = delete;
+        Thread& operator=(Thread&& other);
+        void swap(Thread& other);
+        bool start(Function f, void* p);
+        bool join();
+    };
+}
+#endif

+ 88 - 0
include/core/ToString.hpp

@@ -0,0 +1,88 @@
+#ifndef CORE_TO_STRING_HPP
+#define CORE_TO_STRING_HPP
+
+#include <cstdio>
+#include <cstring>
+
+#include "core/Math.hpp"
+#include "core/Meta.hpp"
+
+namespace Core {
+    size_t toString(char v, char* s, size_t n);
+    size_t toString(short v, char* s, size_t n);
+    size_t toString(int v, char* s, size_t n);
+    size_t toString(long v, char* s, size_t n);
+    size_t toString(long long v, char* s, size_t n);
+    size_t toString(unsigned char v, char* s, size_t n);
+    size_t toString(unsigned short v, char* s, size_t n);
+    size_t toString(unsigned int v, char* s, size_t n);
+    size_t toString(unsigned long v, char* s, size_t n);
+    size_t toString(unsigned long long v, char* s, size_t n);
+    size_t toString(float v, char* s, size_t n);
+    size_t toString(double v, char* s, size_t n);
+    size_t toString(const char* v, char* s, size_t n);
+    size_t toString(char* v, char* s, size_t n);
+    size_t toString(const unsigned char* v, char* s, size_t n);
+    size_t toString(unsigned char* v, char* s, size_t n);
+    size_t toString(bool v, char* s, size_t n);
+
+    template<typename T>
+    size_t toString(const T& t, char* s, size_t n);
+
+    template<typename T>
+    void addString(const T& t, char*& s, size_t& n, size_t& total) {
+        size_t w = toString(t, s, n);
+        total += w;
+        w = Core::min(n, w);
+        s += w;
+        n -= w;
+    }
+
+    template<typename T>
+    size_t toString(const T& t, char* s, size_t n) {
+        if constexpr(Core::Iterable<T>) {
+            size_t total = 0;
+            addString("[", s, n, total);
+            auto current = t.begin();
+            auto end = t.end();
+            if(current != end) {
+                addString(*current, s, n, total);
+                ++current;
+            }
+            while(current != end) {
+                addString(", ", s, n, total);
+                addString(*current, s, n, total);
+                ++current;
+            }
+            addString("]", s, n, total);
+            return total;
+        } else {
+            return t.toString(s, n);
+        }
+    }
+
+    size_t copyFormatUntil(const char*& format, char*& s, size_t& n);
+
+    template<typename T, typename... Args>
+    void formatR(
+        const char*& format, char*& s, size_t& n, size_t& total, const T& t,
+        Args&&... args) {
+        total += copyFormatUntil(format, s, n);
+        addString(t, s, n, total);
+        if constexpr(sizeof...(args) > 0) {
+            formatR(format, s, n, total, Core::forward<Args>(args)...);
+        }
+    }
+
+    template<typename... Args>
+    size_t formatBuffer(char* s, size_t n, const char* format, Args&&... args) {
+        if constexpr(sizeof...(args) > 0) {
+            size_t total = 0;
+            formatR(format, s, n, total, Core::forward<Args>(args)...);
+            return total + toString(format, s, n);
+        }
+        return copyFormatUntil(format, s, n);
+    }
+}
+
+#endif

+ 1 - 2
include/core/utils/Types.hpp → include/core/Types.hpp

@@ -1,7 +1,7 @@
 #ifndef CORE_TYPES_HPP
 #define CORE_TYPES_HPP
 
-#include <stdint.h>
+#include <cstdint>
 
 using i64 = int64_t;
 using i32 = int32_t;
@@ -11,7 +11,6 @@ using u64 = uint64_t;
 using u32 = uint32_t;
 using u16 = uint16_t;
 using u8 = uint8_t;
-using c32 = char32_t;
 using size_t = decltype(sizeof(int));
 
 #endif

+ 18 - 0
include/core/Unicode.hpp

@@ -0,0 +1,18 @@
+#ifndef CORE_UNICODE_HPP
+#define CORE_UNICODE_HPP
+
+#include "core/Types.hpp"
+
+namespace Core {
+    struct UTF8 {
+        u8 data[4] = {0};
+        u32 length = 0;
+    };
+
+    UTF8 convertUnicodeToUTF8(u32 c);
+    u32 convertUTF8toUnicode(UTF8 c);
+    bool isUTF8Remainder(u8 c);
+    u32 getUTF8Length(u8 c);
+}
+
+#endif

+ 1 - 1
include/core/utils/UniquePointer.hpp → include/core/UniquePointer.hpp

@@ -51,4 +51,4 @@ namespace Core {
     };
 }
 
-#endif
+#endif

+ 78 - 0
include/core/Utility.hpp

@@ -0,0 +1,78 @@
+#ifndef CORE_UTILITY_HPP
+#define CORE_UTILITY_HPP
+
+#include <cstddef>
+
+#include "core/Meta.hpp"
+
+#ifdef CHECK_MEMORY
+void* operator new(size_t count, const char* file, int line);
+void* operator new[](size_t count, const char* file, int line);
+#endif
+
+namespace Core {
+    inline void nothing() {
+    }
+
+    template<typename T, typename C = int>
+    C popCount(const T& t) {
+        static constexpr C map[16] = {0, 1, 1, 2, 1, 2, 2, 3,
+                                      1, 2, 2, 3, 2, 3, 3, 4};
+        C sum = 0;
+        for(size_t i = 0; i < sizeof(T) * 8; i += 4) {
+            sum += map[(t >> i) & 0xF];
+        }
+        return sum;
+    }
+
+    using ExitHandler = void (*)(int, void*);
+    [[noreturn]] void exitWithHandler(const char* file, int line, int value);
+    void setExitHandler(ExitHandler h, void* data);
+#define EXIT(exitValue) Core::exitWithHandler(__FILE__, __LINE__, exitValue)
+
+    using OutOfMemoryHandler = void (*)(void*);
+    void setOutOfMemoryHandler(OutOfMemoryHandler h, void* data);
+
+#ifdef CHECK_MEMORY
+    void* debugAllocateRaw(const char* file, int line, size_t n);
+    void* debugZeroAllocateRaw(const char* file, int line, size_t n);
+    void* debugReallocateRaw(const char* file, int line, void* p, size_t n);
+    void debugDeallocateRaw(void* p);
+    void printMemoryReport(void);
+#define allocateRaw(n) debugAllocateRaw(__FILE__, __LINE__, n)
+#define zeroAllocateRaw(n) debugZeroAllocateRaw(__FILE__, __LINE__, n)
+#define reallocateRaw(p, n) debugReallocateRaw(__FILE__, __LINE__, p, n)
+#define deallocateRaw(p) debugDeallocateRaw(p)
+#define coreNew(type, ...) new(__FILE__, __LINE__) type(__VA_ARGS__)
+#define coreNewN(type, n) new(__FILE__, __LINE__) type[n]
+#define coreDelete(p) delete(p)
+#define coreDeleteN(p) delete[](p)
+#else
+    void* allocateRaw(size_t n);
+    void* zeroAllocateRaw(size_t n);
+    void* reallocateRaw(void* p, size_t n);
+    void deallocateRaw(void* p);
+#define printMemoryReport() nothing()
+#define coreNew(type, ...) new type(__VA_ARGS__)
+#define coreNewN(type, n) new type[n]
+#define coreDelete(p) delete(p)
+#define coreDeleteN(p) delete[](p)
+#endif
+
+    template<typename T>
+    void bubbleSort(T* data, size_t n) {
+        bool swapped = true;
+        while(swapped && n > 0) {
+            swapped = false;
+            n--;
+            for(size_t i = 0; i < n; i++) {
+                if(data[i] > data[i + 1]) {
+                    swap(data[i], data[i + 1]);
+                    swapped = true;
+                }
+            }
+        }
+    }
+}
+
+#endif

+ 63 - 35
include/core/math/Vector.hpp → include/core/Vector.hpp

@@ -1,14 +1,14 @@
 #ifndef CORE_VECTOR_HPP
 #define CORE_VECTOR_HPP
 
-#include <math.h>
+#include <cmath>
 
-#include "core/math/Math.hpp"
-#include "core/utils/ArrayString.hpp"
+#include "core/Math.hpp"
+#include "core/ToString.hpp"
 
 namespace Core {
     template<size_t N, typename T>
-    class alignas(sizeof(T) * (Math::isPowerOf2(N) ? N : 1)) Vector final {
+    class alignas(sizeof(T) * (Core::isPowerOf2(N) ? N : 1)) Vector final {
         T values[N];
 
     public:
@@ -19,16 +19,13 @@ namespace Core {
         }
 
         template<typename OT, typename... Args>
-        Vector(OT a, Args&&... args)
-            : values(static_cast<T>(a), static_cast<T>(args)...) {
-            static_assert(sizeof...(args) + 1 == N,
-                          "vector parameters do not match its size");
+        Vector(OT a, Args&&... args) :
+            values(static_cast<T>(a), static_cast<T>(args)...) {
+            static_assert(
+                sizeof...(args) + 1 == N,
+                "vector parameters do not match its size");
         }
 
-    public:
-        Vector& setAngles(float, float) = delete;
-        Vector cross(const Vector&) const = delete;
-
         Vector& operator+=(const Vector& other) {
             for(size_t i = 0; i < N; i++) {
                 values[i] += other.values[i];
@@ -165,15 +162,49 @@ namespace Core {
             return to<float>();
         }
 
-        void toString(BufferString& s) const {
-            s.append("[");
+        size_t toString(char* s, size_t n) const {
+            size_t total = 0;
+            addString("[", s, n, total);
             for(size_t i = 0; i < N - 1; i++) {
-                s.append(values[i]).append(", ");
+                addString(values[i], s, n, total);
+                addString(", ", s, n, total);
             }
-            if(N > 0) {
-                s.append(values[N - 1]);
+            if constexpr(N > 0) {
+                addString(values[N - 1], s, n, total);
+            }
+            addString("]", s, n, total);
+            return total;
+        }
+
+        bool operator==(const Vector& other) const {
+            for(size_t i = 0; i < N; i++) {
+                if(notEqual(values[i], other.values[i])) {
+                    return false;
+                }
             }
-            s.append("]");
+            return true;
+        }
+
+        template<Core::If<N >= 3, int, void*> = 0>
+        auto& xyz() {
+            return *reinterpret_cast<Vector<3, T>*>(this);
+        }
+
+        template<Core::If<N >= 3, int, void*> = 0>
+        const auto& xyz() const {
+            return *reinterpret_cast<const Vector<3, T>*>(this);
+        }
+
+    private:
+        template<typename O>
+        static bool notEqual(const O& a, const O& b) {
+            return a != b;
+        }
+
+        static bool notEqual(float a, float b) {
+            constexpr float e = 0.00001f;
+            float diff = a - b;
+            return diff < -e || diff > e;
         }
     };
 
@@ -181,29 +212,26 @@ namespace Core {
     using Vector3 = Vector<3, float>;
     using Vector2 = Vector<2, float>;
 
-    static_assert(alignof(Vector4) == sizeof(float) * 4,
-                  "invalid Vector4 alignment");
-    static_assert(alignof(Vector3) == sizeof(float),
-                  "invalid Vector3 alignment");
-    static_assert(alignof(Vector2) == sizeof(float) * 2,
-                  "invalid Vector2 alignment");
+    static_assert(
+        alignof(Vector4) == sizeof(float) * 4, "invalid Vector4 alignment");
+    static_assert(
+        alignof(Vector3) == sizeof(float), "invalid Vector3 alignment");
+    static_assert(
+        alignof(Vector2) == sizeof(float) * 2, "invalid Vector2 alignment");
 
     using IntVector4 = Vector<4, int>;
     using IntVector3 = Vector<3, int>;
     using IntVector2 = Vector<2, int>;
 
-    static_assert(alignof(IntVector4) == sizeof(int) * 4,
-                  "invalid IntVector4 alignment");
-    static_assert(alignof(IntVector3) == sizeof(int),
-                  "invalid IntVector3 alignment");
-    static_assert(alignof(IntVector2) == sizeof(int) * 2,
-                  "invalid IntVector2 alignment");
-
-    template<>
-    Vector3& Vector3::setAngles(float lengthAngle, float widthAngle);
+    static_assert(
+        alignof(IntVector4) == sizeof(int) * 4, "invalid IntVector4 alignment");
+    static_assert(
+        alignof(IntVector3) == sizeof(int), "invalid IntVector3 alignment");
+    static_assert(
+        alignof(IntVector2) == sizeof(int) * 2, "invalid IntVector2 alignment");
 
-    template<>
-    Vector3 Vector3::cross(const Vector3& other) const;
+    void setAngles(Vector3& v, float lengthAngle, float widthAngle);
+    Vector3 cross(const Vector3& a, const Vector3& b);
 }
 
 template<size_t N, typename T>

+ 12 - 9
include/core/math/View.hpp → include/core/View.hpp

@@ -1,25 +1,28 @@
 #ifndef CORE_VIEW_HPP
 #define CORE_VIEW_HPP
 
-#include "core/math/Matrix.hpp"
+#include "core/Matrix.hpp"
 
 namespace Core {
-    class View final {
+    class View {
         Matrix view{};
+        Vector3 back{};
+        Vector3 down{};
+        Vector3 front{};
+        Vector3 left{};
         Vector3 right{};
         Vector3 up{};
-        Vector3 back{};
 
     public:
         void updateDirections(float lengthAngle, float widthAngle);
         void updateDirections(const Quaternion& q);
         const Matrix& updateMatrix(const Vector3& pos);
-        Vector3 getUp() const;
-        Vector3 getDown() const;
-        Vector3 getLeft() const;
-        Vector3 getRight() const;
-        Vector3 getFront() const;
-        Vector3 getBack() const;
+        const Vector3& getBack() const;
+        const Vector3& getDown() const;
+        const Vector3& getFront() const;
+        const Vector3& getLeft() const;
+        const Vector3& getRight() const;
+        const Vector3& getUp() const;
     };
 }
 

+ 0 - 281
include/core/data/HashMap.hpp

@@ -1,281 +0,0 @@
-#ifndef CORE_HASHMAP_HPP
-#define CORE_HASHMAP_HPP
-
-#include "core/data/LinkedList.hpp"
-#include "core/data/List.hpp"
-#include "core/utils/ArrayString.hpp"
-#include "core/utils/HashCode.hpp"
-#include "core/utils/Meta.hpp"
-
-namespace Core {
-    template<typename K, typename V>
-    struct HashMap final {
-        class Node final {
-            friend HashMap;
-            friend List<Node>;
-            friend LinkedList<Node>;
-            K key;
-
-        public:
-            V value;
-
-            const K& getKey() const {
-                return key;
-            }
-
-            void toString(BufferString& s) const {
-                s.append(key).append(" = ").append(value);
-            }
-
-        private:
-            template<typename... Args>
-            Node(const K& key_, Args&&... args)
-                : key(key_), value(Core::forward<Args>(args)...) {
-            }
-        };
-
-    private:
-        using NodePointer = LinkedList<Node>::Node*;
-        using NodePointerList = List<NodePointer>;
-        using NodeIterator = LinkedList<Node>::Iterator;
-        using ConstNodeIterator = LinkedList<Node>::ConstIterator;
-
-        template<typename N, typename I, typename R, R& (*A)(I&)>
-        class Iterator final {
-            N iterator;
-
-        public:
-            Iterator(N iterator_) : iterator(iterator_) {
-            }
-
-            Iterator& operator++() {
-                ++iterator;
-                return *this;
-            }
-
-            bool operator!=(const Iterator& other) const {
-                return iterator != other.iterator;
-            }
-
-            R& operator*() const {
-                return A(*iterator);
-            }
-        };
-
-        template<typename R>
-        static R& access(R& node) {
-            return node;
-        }
-
-        template<typename I, typename R>
-        static R& accessValue(I& node) {
-            return node.value;
-        }
-
-        static const K& accessKey(const Node& node) {
-            return node.getKey();
-        }
-
-        template<typename N, typename R>
-        using BaseEntryIterator = Iterator<N, R, R, access<R>>;
-        using EntryIterator = BaseEntryIterator<NodeIterator, Node>;
-        using ConstEntryIterator =
-            BaseEntryIterator<ConstNodeIterator, const Node>;
-
-        template<typename N, typename I, typename R>
-        using BaseValueIterator = Iterator<N, I, R, accessValue<I, R>>;
-        using ValueIterator = BaseValueIterator<NodeIterator, Node, V>;
-        using ConstValueIterator =
-            BaseValueIterator<ConstNodeIterator, const Node, const V>;
-
-        using ConstKeyIterator =
-            Iterator<ConstNodeIterator, const Node, const K, accessKey>;
-
-        template<typename M, typename I>
-        struct IteratorAdapter final {
-            M& map;
-
-            I begin() const {
-                return I(map.nodes.begin());
-            }
-
-            I end() const {
-                return I(map.nodes.end());
-            }
-        };
-
-        using ValueIteratorAdapter = IteratorAdapter<HashMap, ValueIterator>;
-        using ConstValueIteratorAdapter =
-            IteratorAdapter<const HashMap, ConstValueIterator>;
-
-        using ConstKeyIteratorAdapter =
-            IteratorAdapter<const HashMap, ConstKeyIterator>;
-
-    private:
-        LinkedList<Node> nodes{};
-        List<NodePointerList> nodePointers{};
-
-    public:
-        HashMap() = default;
-
-        HashMap(const HashMap& other) {
-            for(const auto& en : other) {
-                add(en.getKey(), en.value);
-            }
-        }
-
-        HashMap(HashMap&& other) {
-            swap(other);
-        }
-
-        HashMap& operator=(HashMap other) {
-            swap(other);
-            return *this;
-        }
-
-        void rehash(size_t minCapacity) {
-            if(minCapacity <= nodePointers.getLength()) {
-                return;
-            }
-            HashMap<K, V> map;
-            size_t l =
-                1lu << Math::roundUpLog2(Core::Math::max(minCapacity, 8lu));
-            map.nodePointers.resize(l);
-            for(NodePointerList& list : nodePointers) {
-                for(NodePointer& n : list) {
-                    size_t h = map.hashIndex(n->data.key);
-                    map.nodePointers[h].add(n);
-                }
-            }
-            Core::swap(map.nodePointers, nodePointers);
-        }
-
-        template<typename... Args>
-        bool tryEmplace(V*& v, const K& key, Args&&... args) {
-            rehash(nodes.getLength() + 1);
-            size_t h = hashIndex(key);
-            v = searchList(key, h);
-            if(v != nullptr) {
-                return false;
-            }
-            NodePointer np = nodes.put(key, Core::forward<Args>(args)...);
-            nodePointers[h].add(np);
-            v = &(np->data.value);
-            return true;
-        }
-
-        template<typename VA>
-        V& put(const K& key, VA&& value) {
-            rehash(nodes.getLength() + 1);
-            size_t h = hashIndex(key);
-            V* v = searchList(key, h);
-            if(v != nullptr) {
-                *v = Core::forward<VA>(value);
-                return *v;
-            }
-            NodePointer np = nodes.put(key, Core::forward<VA>(value));
-            nodePointers[h].add(np);
-            return np->data.value;
-        }
-
-        template<typename VA>
-        HashMap& add(const K& key, VA&& value) {
-            put(key, Core::forward<VA>(value));
-            return *this;
-        }
-
-        bool remove(const K& key) {
-            NodePointerList& list = nodePointers[hashIndex(key)];
-            for(size_t i = 0; i < list.getLength(); i++) {
-                if(list[i]->data.key == key) {
-                    nodes.remove(list[i]);
-                    list.removeBySwap(i);
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        const V* search(const K& key) const {
-            return searchList(key, hashIndex(key));
-        }
-
-        V* search(const K& key) {
-            return searchList(key, hashIndex(key));
-        }
-
-        bool contains(const K& key) const {
-            return search(key) != nullptr;
-        }
-
-        HashMap& clear() {
-            nodes.clear();
-            for(NodePointerList& n : nodePointers) {
-                n.clear();
-            }
-            return *this;
-        }
-
-        ConstKeyIteratorAdapter getKeys() const {
-            return {*this};
-        }
-
-        ValueIteratorAdapter getValues() {
-            return {*this};
-        }
-
-        ConstValueIteratorAdapter getValues() const {
-            return {*this};
-        }
-
-        EntryIterator begin() {
-            return EntryIterator(nodes.begin());
-        }
-
-        EntryIterator end() {
-            return EntryIterator(nodes.end());
-        }
-
-        ConstEntryIterator begin() const {
-            return ConstEntryIterator(nodes.begin());
-        }
-
-        ConstEntryIterator end() const {
-            return ConstEntryIterator(nodes.end());
-        }
-
-        void swap(HashMap& other) {
-            Core::swap(nodes, other.nodes);
-            Core::swap(nodePointers, other.nodePointers);
-        }
-
-        void toString(BufferString& s) const {
-            Core::toString(s, *this);
-        }
-
-    private:
-        template<typename H>
-        size_t hashIndex(const H& key) const {
-            return hashCode(key) & (nodePointers.getLength() - 1);
-        }
-
-        const V* searchList(const K& key, size_t h) const {
-            if(nodePointers.getLength() == 0) {
-                return nullptr;
-            }
-            for(const NodePointer& n : nodePointers[h]) {
-                if(n->data.key == key) {
-                    return &n->data.value;
-                }
-            }
-            return nullptr;
-        }
-
-        V* searchList(const K& key, size_t h) {
-            return const_cast<V*>(
-                static_cast<const HashMap*>(this)->searchList(key, h));
-        }
-    };
-}
-
-#endif

+ 0 - 179
include/core/data/LinkedList.hpp

@@ -1,179 +0,0 @@
-#ifndef CORE_LINKED_LIST_HPP
-#define CORE_LINKED_LIST_HPP
-
-#include "core/utils/ArrayString.hpp"
-#include "core/utils/New.hpp"
-
-namespace Core {
-    template<typename T>
-    struct LinkedList final {
-        class Node final {
-            friend LinkedList;
-
-            Node* next;
-            Node* previous;
-
-        public:
-            T data;
-
-            template<typename... Args>
-            Node(Args&&... args)
-                : next(nullptr), previous(nullptr),
-                  data(Core::forward<Args>(args)...) {
-            }
-        };
-
-        template<typename NT, typename R>
-        struct IteratorBase final {
-            NT* current;
-
-        public:
-            IteratorBase& operator++() {
-                current = current->next;
-                return *this;
-            }
-
-            bool operator!=(const IteratorBase& other) const {
-                return current != other.current;
-            }
-
-            R& operator*() const {
-                return current->data;
-            }
-        };
-
-        using Iterator = IteratorBase<Node, T>;
-        using ConstIterator = IteratorBase<const Node, const T>;
-
-    private:
-        Node* first;
-        Node* last;
-        size_t length;
-
-    public:
-        LinkedList() : first(nullptr), last(nullptr), length(0) {
-        }
-
-        LinkedList(const LinkedList& other) : LinkedList() {
-            for(const T& t : other) {
-                add(t);
-            }
-        }
-
-        LinkedList(LinkedList&& other) : LinkedList() {
-            swap(other);
-        }
-
-        ~LinkedList() {
-            clear();
-        }
-
-        LinkedList& operator=(LinkedList other) {
-            swap(other);
-            return *this;
-        }
-
-        template<typename... Args>
-        Node* put(Args&&... args) {
-            Node* n = new(noThrow) Node(Core::forward<Args>(args)...);
-            length++;
-            if(first == nullptr) {
-                first = n;
-                last = n;
-                return n;
-            }
-            last->next = n;
-            n->previous = last;
-            last = n;
-            return n;
-        }
-
-        template<typename... Args>
-        LinkedList& add(Args&&... args) {
-            put(Core::forward<Args>(args)...);
-            return *this;
-        }
-
-        Iterator begin() {
-            return {first};
-        }
-
-        Iterator end() {
-            return {nullptr};
-        }
-
-        ConstIterator begin() const {
-            return {first};
-        }
-
-        ConstIterator end() const {
-            return {nullptr};
-        }
-
-        size_t getLength() const {
-            return length;
-        }
-
-        void clear() {
-            Node* n = first;
-            while(n != nullptr) {
-                Node* next = n->next;
-                delete n;
-                n = next;
-            }
-            length = 0;
-            first = nullptr;
-            last = nullptr;
-        }
-
-        void toString(BufferString& s) const {
-            Core::toString(s, *this);
-        }
-
-        void remove(Node*& n) {
-            if(n == nullptr) {
-                return;
-            }
-            if(n == first) {
-                if(first->next != nullptr) {
-                    first->next->previous = nullptr;
-                }
-                first = first->next;
-            }
-            if(n == last) {
-                if(last->previous != nullptr) {
-                    last->previous->next = nullptr;
-                }
-                last = last->previous;
-            }
-            if(n->previous != nullptr) {
-                n->previous->next = n->next;
-            }
-            if(n->next != nullptr) {
-                n->next->previous = n->previous;
-            }
-            length--;
-            delete n;
-            n = nullptr;
-        }
-
-        void removeFirst() {
-            Node* n = first; // prevent first from becoming null
-            remove(n);
-        }
-
-        void removeLast() {
-            Node* n = last; // prevent last from becoming null
-            remove(n);
-        }
-
-        void swap(LinkedList& other) {
-            Core::swap(first, other.first);
-            Core::swap(last, other.last);
-            Core::swap(length, other.length);
-        }
-    };
-
-}
-
-#endif

+ 0 - 316
include/core/data/ProbingHashMap.hpp

@@ -1,316 +0,0 @@
-#ifndef CORE_PROBING_HASHMAP_HPP
-#define CORE_PROBING_HASHMAP_HPP
-
-#include "core/data/List.hpp"
-#include "core/utils/ArrayString.hpp"
-#include "core/utils/HashCode.hpp"
-#include "core/utils/New.hpp"
-#include "core/utils/Types.hpp"
-
-namespace Core {
-    template<typename K, typename V>
-    struct ProbingHashMap final {
-        template<typename Value>
-        class Node final {
-            friend ProbingHashMap;
-            friend List<Node>;
-            K key;
-
-        public:
-            Value& value;
-
-            const K& getKey() const {
-                return key;
-            }
-
-            void toString(BufferString& s) const {
-                s.append(key).append(" = ").append(value);
-            }
-
-        private:
-            Node(const K& key_, Value& value_) : key(key_), value(value_) {
-            }
-        };
-
-    private:
-        static constexpr K INVALID = emptyValue<K>();
-
-        template<typename Value, typename R, R (*A)(const K&, Value&)>
-        class Iterator final {
-            const K* currentKey;
-            const K* endKey;
-            Value* currentValue;
-
-        public:
-            Iterator(const K* key, const K* endKey_, Value* value)
-                : currentKey(key), endKey(endKey_), currentValue(value) {
-                skip();
-            }
-
-            Iterator& operator++() {
-                ++currentKey;
-                ++currentValue;
-                skip();
-                return *this;
-            }
-
-            bool operator!=(const Iterator& other) const {
-                return currentKey != other.currentKey;
-            }
-
-            R operator*() const {
-                return A(*currentKey, *currentValue);
-            }
-
-        private:
-            void skip() {
-                while(currentKey != endKey && !((*currentKey != INVALID) !=
-                                                (currentKey + 1 == endKey))) {
-                    ++currentKey;
-                    ++currentValue;
-                }
-            }
-        };
-
-        template<typename Value>
-        static Node<Value> access(const K& key, Value& value) {
-            return Node<Value>(key, value);
-        }
-
-        template<typename Value>
-        static Value& accessValue(const K&, Value& value) {
-            return value;
-        }
-
-        static const K& accessKey(const K& key, const V&) {
-            return key;
-        }
-
-        template<typename Value>
-        using BaseEntryIterator = Iterator<Value, Node<Value>, access<Value>>;
-        using EntryIterator = BaseEntryIterator<V>;
-        using ConstEntryIterator = BaseEntryIterator<const V>;
-
-        template<typename Value>
-        using BaseValueIterator = Iterator<Value, Value&, accessValue<Value>>;
-        using ValueIterator = BaseValueIterator<V>;
-        using ConstValueIterator = BaseValueIterator<const V>;
-
-        using ConstKeyIterator = Iterator<const V, const K&, accessKey>;
-
-        template<typename M, typename I>
-        struct IteratorAdapter final {
-            M& map;
-
-            I begin() const {
-                return {map.keys.begin(), map.keys.end(), map.values};
-            }
-
-            I end() const {
-                return {map.keys.end(), map.keys.end(), nullptr};
-            }
-        };
-
-        using ValueIteratorAdapter =
-            IteratorAdapter<ProbingHashMap, ValueIterator>;
-        using ConstValueIteratorAdapter =
-            IteratorAdapter<const ProbingHashMap, ConstValueIterator>;
-
-        using ConstKeyIteratorAdapter =
-            IteratorAdapter<const ProbingHashMap, ConstKeyIterator>;
-
-    private:
-        List<K> keys{};
-        V* values = nullptr;
-        size_t entries = 0;
-
-    public:
-        ProbingHashMap() = default;
-
-        ProbingHashMap(const ProbingHashMap& other) {
-            for(const auto& e : other) {
-                add(e.getKey(), e.value);
-            }
-        }
-
-        ProbingHashMap(ProbingHashMap&& other) {
-            swap(other);
-        }
-
-        ~ProbingHashMap() {
-            size_t length = keys.getLength();
-            if(length > 0) {
-                length--;
-                for(size_t i = 0; i < length; i++) {
-                    if(keys[i] != INVALID) {
-                        values[i].~V();
-                    }
-                }
-                if(keys[length] == INVALID) {
-                    values[length].~V();
-                }
-            }
-            delete[] reinterpret_cast<AlignedType<V>*>(values);
-        }
-
-        ProbingHashMap& operator=(ProbingHashMap other) {
-            swap(other);
-            return *this;
-        }
-
-        void rehash(size_t minCapacity) {
-            if(minCapacity <= keys.getLength()) {
-                return;
-            }
-            ProbingHashMap<K, V> map;
-            size_t l =
-                (1lu << Math::roundUpLog2(Math::max(minCapacity, 8lu))) + 1;
-            map.keys.resize(l, INVALID);
-            map.keys[map.keys.getLength() - 1] = K();
-            map.values = reinterpret_cast<V*>(new(noThrow) AlignedType<V>[l]);
-            size_t length = keys.getLength();
-            if(length > 0) {
-                length--;
-                for(size_t i = 0; i < length; i++) {
-                    if(keys[i] != INVALID) {
-                        map.add(keys[i], values[i]);
-                    }
-                }
-                if(keys[length] == INVALID) {
-                    map.add(keys[length], values[length]);
-                }
-            }
-            swap(map);
-        }
-
-        template<typename... Args>
-        bool tryEmplace(V*& v, const K& key, Args&&... args) {
-            size_t index = searchSlot(key);
-            if(keys[index] == key) {
-                return false;
-            }
-            keys[index] = key;
-            v = new(values + index) V(Core::forward<Args>(args)...);
-            entries++;
-            return true;
-        }
-
-        template<typename VA>
-        V& put(const K& key, VA&& value) {
-            size_t index = searchSlot(key);
-            if(keys[index] == key) {
-                return (values[index] = Core::forward<VA>(value));
-            }
-            new(values + index) V(Core::forward<VA>(value));
-            entries++;
-            keys[index] = key;
-            return values[index];
-        }
-
-        template<typename VA>
-        ProbingHashMap& add(const K& key, VA&& value) {
-            put(key, Core::forward<VA>(value));
-            return *this;
-        }
-
-        const V* search(const K& key) const {
-            return searchValue<const V>(key);
-        }
-
-        V* search(const K& key) {
-            return searchValue<V>(key);
-        }
-
-        bool contains(const K& key) const {
-            return search(key) != nullptr;
-        }
-
-        ProbingHashMap& clear() {
-            ProbingHashMap<K, V> map;
-            swap(map);
-            return *this;
-        }
-
-        ConstKeyIteratorAdapter getKeys() const {
-            return {*this};
-        }
-
-        ValueIteratorAdapter getValues() {
-            return {*this};
-        }
-
-        ConstValueIteratorAdapter getValues() const {
-            return {*this};
-        }
-
-        EntryIterator begin() {
-            return {keys.begin(), keys.end(), values};
-        }
-
-        EntryIterator end() {
-            return {keys.end(), keys.end(), nullptr};
-        }
-
-        ConstEntryIterator begin() const {
-            return {keys.begin(), keys.end(), values};
-        }
-
-        ConstEntryIterator end() const {
-            return {keys.end(), keys.end(), nullptr};
-        }
-
-        void toString(BufferString& s) const {
-            Core::toString(s, *this);
-        }
-
-        void swap(ProbingHashMap& o) {
-            Core::swap(o.keys, keys);
-            Core::swap(o.values, values);
-            Core::swap(o.entries, entries);
-        }
-
-    private:
-        size_t searchSlot(const K& key) {
-            size_t rehashFactor = 2;
-            while(true) {
-                rehash(entries * rehashFactor + 1);
-                if(key == INVALID) {
-                    return keys.getLength() - 1;
-                }
-                size_t baseHash = hashCode(key) * 514685581u;
-                size_t end = keys.getLength() - 2;
-                // rehash on bad clustering
-                for(size_t i = 0; i <= 5; i++) {
-                    size_t hash = (baseHash + i) & end;
-                    if(keys[hash] == INVALID || keys[hash] == key) {
-                        return hash;
-                    }
-                }
-                rehashFactor *= 2;
-            }
-        }
-
-        template<typename Value>
-        Value* searchValue(const K& key) const {
-            if(keys.getLength() != 0) {
-                if(key == INVALID) {
-                    size_t i = keys.getLength() - 1;
-                    return keys[i] == INVALID ? values + i : nullptr;
-                }
-                size_t baseHash = hashCode(key) * 514685581u;
-                size_t end = keys.getLength() - 2;
-                for(size_t i = 0; i <= end; i++) [[unlikely]] {
-                    size_t hash = (baseHash + i) & end;
-                    if(keys[hash] == key) [[likely]] {
-                        return values + hash;
-                    } else if(keys[hash] == INVALID) {
-                        return nullptr;
-                    }
-                }
-            }
-            return nullptr;
-        }
-    };
-}
-
-#endif

+ 0 - 57
include/core/data/Stack.hpp

@@ -1,57 +0,0 @@
-#ifndef CORE_STACK_HPP
-#define CORE_STACK_HPP
-
-#include "core/data/ArrayList.hpp"
-#include "core/data/List.hpp"
-#include "core/utils/Error.hpp"
-
-namespace Core {
-    namespace Internal {
-        template<typename T, typename S>
-        class BaseStack final {
-            S data{};
-
-        public:
-            template<typename... Args>
-            BaseStack& push(Args&&... args) {
-                data.add(Core::forward<Args>(args)...);
-                return *this;
-            }
-
-            void clear() {
-                data.clear();
-            }
-
-            void pop() {
-                assert(data.getLength() > 0);
-                data.removeBySwap(data.getLength() - 1);
-            }
-
-            bool isEmpty() const {
-                return data.getLength() == 0;
-            }
-
-            T& peek() {
-                assert(data.getLength() > 0);
-                return data[data.getLength() - 1];
-            }
-
-            const T& peek() const {
-                assert(data.getLength() > 0);
-                return data[data.getLength() - 1];
-            }
-
-            void toString(BufferString& s) const {
-                s.append(data);
-            }
-        };
-    }
-
-    template<typename T>
-    using ListStack = Internal::BaseStack<T, List<T>>;
-
-    template<typename T, size_t N>
-    using ArrayStack = Internal::BaseStack<T, ArrayList<T, N>>;
-}
-
-#endif

+ 0 - 12
include/core/io/File.hpp

@@ -1,12 +0,0 @@
-#ifndef CORE_FILE_HPP
-#define CORE_FILE_HPP
-
-#include <limits.h>
-
-#include "core/utils/ArrayString.hpp"
-
-namespace Core {
-    using Path = ArrayString<PATH_MAX>;
-}
-
-#endif

+ 0 - 29
include/core/io/FileReader.hpp

@@ -1,29 +0,0 @@
-#ifndef CORE_FILE_READER_HPP
-#define CORE_FILE_READER_HPP
-
-#include <stdio.h>
-
-#include "core/io/File.hpp"
-
-namespace Core {
-    class FileReader final {
-        FILE* file;
-        Path path;
-
-    public:
-        FileReader();
-        FileReader(const FileReader& other) = delete;
-        FileReader(FileReader&& other);
-        ~FileReader();
-        FileReader& operator=(const FileReader& other) = delete;
-        FileReader& operator=(FileReader&& other);
-        const Path& getPath() const;
-        Error open(const Path& path);
-        Error open(const char* path);
-        Error readChar(int& c);
-        Error readChars(char* buffer, size_t bufferSize);
-        void swap(FileReader& other);
-    };
-}
-
-#endif

+ 0 - 97
include/core/math/BufferedValue.hpp

@@ -1,97 +0,0 @@
-#ifndef CORE_BUFFERED_VALUE_HPP
-#define CORE_BUFFERED_VALUE_HPP
-
-#include "core/math/Math.hpp"
-#include "core/utils/Types.hpp"
-
-namespace Core {
-    template<typename T>
-    class BufferedValue final {
-        T last;
-        T current;
-
-    public:
-        BufferedValue(const T& t) : last(t), current(t) {
-        }
-
-        void update() {
-            last = current;
-        }
-
-        T get(float lag) const {
-            return Math::interpolate(last, current, lag);
-        }
-
-        template<typename O>
-        BufferedValue& operator=(const O& o) {
-            current = o;
-            return *this;
-        }
-
-        template<typename O>
-        BufferedValue& operator+=(const O& o) {
-            current += o;
-            return *this;
-        }
-
-        template<typename O>
-        BufferedValue& operator-=(const O& o) {
-            current -= o;
-            return *this;
-        }
-
-        template<typename O>
-        BufferedValue& operator*=(const O& o) {
-            current *= o;
-            return *this;
-        }
-
-        template<typename O>
-        BufferedValue& operator/=(const O& o) {
-            current /= o;
-            return *this;
-        }
-
-        template<typename O>
-        auto operator+(const O& o) const {
-            return current + o;
-        }
-
-        template<typename O>
-        auto operator-(const O& o) const {
-            return current - o;
-        }
-
-        template<typename O>
-        auto operator*(const O& o) const {
-            return current * o;
-        }
-
-        template<typename O>
-        auto operator/(const O& o) const {
-            return current / o;
-        }
-
-        auto operator-() const {
-            return -current;
-        }
-
-        auto& operator[](size_t index) {
-            return current[index];
-        }
-
-        const auto& operator[](size_t index) const {
-            return current[index];
-        }
-
-        operator T&() {
-            return current;
-        }
-
-        operator const T&() const {
-            return current;
-        }
-    };
-}
-
-#endif

+ 0 - 36
include/core/math/Frustum.hpp

@@ -1,36 +0,0 @@
-#ifndef CORE_FRUSTUM_HPP
-#define CORE_FRUSTUM_HPP
-
-#include "core/data/Array.hpp"
-#include "core/math/Matrix.hpp"
-#include "core/math/Plane.hpp"
-
-namespace Core {
-    class Frustum final {
-        Matrix projection;
-        Array<Plane, 6> planes;
-
-    public:
-        float tan;
-        float nearClip;
-        float farClip;
-
-        Frustum(float fieldOfView, float nearClip, float farClip);
-        const Matrix& updateProjection(const IntVector2& size);
-        void updatePlanes(const Vector3& pos, const Vector3& right,
-                          const Vector3& up, const Vector3& front,
-                          const IntVector2& size);
-
-        bool isInside(const Vector3& pos) const;
-        bool isInside(const Vector3& pos, float radius) const;
-
-        void toString(BufferString& s) const {
-            s.append("(tan = ").append(tan);
-            s.append(", nearClip = ").append(nearClip);
-            s.append(", farClip = ").append(farClip);
-            s.append(')');
-        }
-    };
-}
-
-#endif

+ 0 - 47
include/core/math/MatrixStack.hpp

@@ -1,47 +0,0 @@
-#ifndef CORE_MATRIX_STACK_HPP
-#define CORE_MATRIX_STACK_HPP
-
-#include "core/data/ArrayList.hpp"
-#include "core/math/Matrix.hpp"
-
-namespace Core {
-    template<size_t N>
-    class MatrixStack final {
-        ArrayList<Matrix, N> stack;
-
-    public:
-        MatrixStack() : stack() {
-            stack.add(Matrix());
-        }
-
-        void pop() {
-            assert(stack.getLength() > 0);
-            stack.removeLast();
-        }
-
-        void push() {
-            stack.add(peek());
-        }
-
-        Matrix& peek() {
-            assert(stack.getLength() > 0);
-            return stack[stack.getLength() - 1];
-        }
-
-        const Matrix& peek() const {
-            assert(stack.getLength() > 0);
-            return stack[stack.getLength() - 1];
-        }
-
-        void clear() {
-            stack.clear();
-            stack.add(Matrix());
-        }
-
-        void toString(BufferString& s) const {
-            s.append(stack);
-        }
-    };
-}
-
-#endif

+ 0 - 26
include/core/thread/Mutex.hpp

@@ -1,26 +0,0 @@
-#ifndef CORE_MUTEX_HPP
-#define CORE_MUTEX_HPP
-
-#include <threads.h>
-
-#include "core/utils/Check.hpp"
-#include "core/utils/Error.hpp"
-
-namespace Core {
-    class Mutex final {
-        mtx_t mutex;
-
-    public:
-        Mutex();
-        Mutex(const Mutex& other) = delete;
-        Mutex(Mutex&& other) = delete;
-        ~Mutex();
-        Mutex& operator=(const Mutex& other) = delete;
-        Mutex& operator=(Mutex&& other) = delete;
-        cbool init();
-        cbool lock();
-        cbool unlock();
-    };
-}
-
-#endif

+ 0 - 24
include/core/thread/SpinLock.hpp

@@ -1,24 +0,0 @@
-#ifndef CORE_SPIN_LOCK_HPP
-#define CORE_SPIN_LOCK_HPP
-
-// stdatomic.h is not compatible with C++
-// all calls are noexcept
-#include <atomic>
-using atomic_bool = std::atomic_bool;
-
-namespace Core {
-    class SpinLock final {
-        atomic_bool locked;
-
-    public:
-        SpinLock();
-        SpinLock(const SpinLock& other) = delete;
-        SpinLock(SpinLock&& other) = delete;
-        SpinLock& operator=(const SpinLock& other) = delete;
-        SpinLock& operator=(SpinLock&& other) = delete;
-        void lock();
-        void unlock();
-    };
-}
-
-#endif

+ 0 - 27
include/core/thread/Thread.hpp

@@ -1,27 +0,0 @@
-#ifndef CORE_THREAD_HPP
-#define CORE_THREAD_HPP
-
-#include <threads.h>
-
-#include "core/utils/Check.hpp"
-
-namespace Core {
-    class Thread final {
-        thrd_t thread;
-
-    public:
-        using Function = int (*)(void*);
-
-        Thread();
-        Thread(const Thread& other) = delete;
-        Thread(Thread&& other);
-        ~Thread();
-        Thread& operator=(const Thread& other) = delete;
-        Thread& operator=(Thread&& other);
-        void swap(Thread& other);
-        cbool start(Function f, void* p);
-        cbool join(int* returnValue = nullptr);
-    };
-}
-
-#endif

+ 0 - 38
include/core/utils/Buffer.hpp

@@ -1,38 +0,0 @@
-#ifndef CORE_BUFFER_HPP
-#define CORE_BUFFER_HPP
-
-#include "core/utils/Check.hpp"
-#include "core/utils/Error.hpp"
-#include "core/utils/Types.hpp"
-
-namespace Core {
-    class Buffer final {
-        size_t length;
-        size_t capacity;
-        char* buffer;
-
-    public:
-        Buffer(size_t initialSize = 32);
-        Buffer(const Buffer& other) = delete;
-        Buffer(Buffer&& other);
-        ~Buffer();
-        Buffer& operator=(const Buffer& other) = delete;
-        Buffer& operator=(Buffer&& other);
-
-        CError copyFrom(const Buffer& other);
-        CError add(const void* data, size_t size);
-
-        template<typename T>
-        check_return Error add(const T& t) {
-            return add(&t, sizeof(T));
-        }
-
-        size_t getLength() const;
-        operator const char*() const;
-        const char* getData() const;
-        void clear();
-        void swap(Buffer& other);
-    };
-}
-
-#endif

+ 0 - 24
include/core/utils/Check.hpp

@@ -1,24 +0,0 @@
-#ifndef CORE_CHECK_HPP
-#define CORE_CHECK_HPP
-
-#if defined(__cplusplus) && __cplusplus > 201700L
-#define check_return [[nodiscard]]
-#elif defined(__STDC_VERSION__) && __STDC_VERSION__ > 202300L
-#define check_return [[nodiscard]]
-#elif defined(__GNUC__)
-#define check_return __attribute__((warn_unused_result))
-#else
-#error "please add a 'check_return' option"
-#endif
-
-#if defined(__GNUC__)
-#define check_format(format_index, arg_start_index)                            \
-    __attribute__((format(printf, format_index, arg_start_index)))
-#else
-#error "please add a 'check_format' option"
-#endif
-
-#define CError check_return Core::Error
-#define cbool check_return bool
-
-#endif

+ 0 - 30
include/core/utils/Clock.hpp

@@ -1,30 +0,0 @@
-#ifndef CORE_CLOCK_HPP
-#define CORE_CLOCK_HPP
-
-#include "core/data/Array.hpp"
-#include "core/utils/Check.hpp"
-#include "core/utils/Types.hpp"
-
-namespace Core {
-    struct Clock final {
-        using Nanos = i64;
-
-    private:
-        size_t index;
-        Nanos last;
-        Nanos sum;
-        Array<Nanos, 1 << 7> time;
-
-    public:
-        Clock();
-
-        // the first invocation will always return 0 nanos
-        check_return Error update(Nanos& n);
-        float getUpdatesPerSecond() const;
-
-        check_return static Error wait(Nanos nanos);
-        check_return static Error getNanos(Nanos& n);
-    };
-}
-
-#endif

+ 0 - 79
include/core/utils/Error.hpp

@@ -1,79 +0,0 @@
-#ifndef CORE_ERROR_HPP
-#define CORE_ERROR_HPP
-
-#include "core/utils/Check.hpp"
-#include "core/utils/Types.hpp"
-
-namespace Core {
-    struct Error {
-        using Code = unsigned int;
-        static_assert(sizeof(Code) >= 4, "error code too small");
-        Code code;
-
-        constexpr Error(Code c) : code(c) {
-        }
-
-        bool check() const {
-            return code != 0;
-        }
-
-        bool contains(Error e) const {
-            return code & e.code;
-        }
-
-        bool operator==(Error e) const {
-            return code == e.code;
-        }
-
-        bool operator!=(Error e) const {
-            return !(*this == e);
-        }
-
-        Error& operator|=(Error e) {
-            code |= e.code;
-            return *this;
-        }
-
-        Error operator|(Error e) const {
-            e |= *this;
-            return e;
-        }
-    };
-
-    namespace ErrorCode {
-        static constexpr Error NONE = 0;
-        static constexpr Error ERROR = 1 << 0;
-        static constexpr Error NEGATIVE_ARGUMENT = 1 << 1;
-        static constexpr Error CAPACITY_REACHED = 1 << 2;
-        static constexpr Error BLOCKED_STDOUT = 1 << 3;
-        static constexpr Error OUT_OF_MEMORY = 1 << 4;
-        static constexpr Error INVALID_CHAR = 1 << 5;
-        static constexpr Error NOT_FOUND = 1 << 6;
-        static constexpr Error INVALID_STATE = 1 << 7;
-        static constexpr Error INVALID_INDEX = 1 << 8;
-        static constexpr Error INVALID_ARGUMENT = 1 << 9;
-        static constexpr Error TIME_NOT_AVAILABLE = 1 << 10;
-        static constexpr Error SLEEP_INTERRUPTED = 1 << 11;
-        static constexpr Error THREAD_ERROR = 1 << 12;
-        static constexpr Error MUTEX_ERROR = 1 << 13;
-        static constexpr Error EXISTING_KEY = 1 << 14;
-        static constexpr Error CANNOT_OPEN_FILE = 1 << 15;
-        static constexpr Error END_OF_FILE = 1 << 16;
-    }
-
-    size_t toString(Error e, char* buffer, size_t size);
-
-    inline bool checkError(Error& storage, Error e) {
-        return (storage = e).check();
-    }
-
-#define CORE_RETURN_ERROR(checked)                                             \
-    {                                                                          \
-        Core::Error error = Core::ErrorCode::NONE;                             \
-        if(checkError(error, checked)) [[unlikely]] {                          \
-            return error;                                                      \
-        }                                                                      \
-    }
-}
-
-#endif

+ 0 - 95
include/core/utils/HashCode.hpp

@@ -1,95 +0,0 @@
-#ifndef CORE_HPPASH_CODE_HPP
-#define CORE_HPPASH_CODE_HPP
-
-#include <limits.h>
-
-#include "core/utils/Meta.hpp"
-#include "core/utils/Types.hpp"
-
-namespace Core {
-    template<typename H>
-    inline size_t hashCode(const H& key) {
-        return key.hashCode();
-    }
-
-    template<typename T>
-    inline size_t hashNumber(T t) {
-        static_assert(sizeof(t) <= sizeof(size_t));
-        return static_cast<size_t>(t);
-    }
-
-    inline size_t hashCode(char key) {
-        return hashNumber(key);
-    }
-
-    inline size_t hashCode(signed char key) {
-        return hashNumber(key);
-    }
-
-    inline size_t hashCode(signed short key) {
-        return hashNumber(key);
-    }
-
-    inline size_t hashCode(signed int key) {
-        return hashNumber(key);
-    }
-
-    inline size_t hashCode(signed long key) {
-        return hashNumber(key);
-    }
-
-    inline size_t hashCode(signed long long key) {
-        return hashNumber(key);
-    }
-
-    inline size_t hashCode(unsigned char key) {
-        return hashNumber(key);
-    }
-
-    inline size_t hashCode(unsigned short key) {
-        return hashNumber(key);
-    }
-
-    inline size_t hashCode(unsigned int key) {
-        return hashNumber(key);
-    }
-
-    inline size_t hashCode(unsigned long key) {
-        return hashNumber(key);
-    }
-
-    inline size_t hashCode(unsigned long long key) {
-        return hashNumber(key);
-    }
-
-    template<typename T>
-    inline consteval T emptyValue() {
-        if constexpr(Core::IsSame<char, T>) {
-            return CHAR_MAX;
-        } else if constexpr(Core::IsSame<signed char, T>) {
-            return SCHAR_MAX;
-        } else if constexpr(Core::IsSame<signed short, T>) {
-            return SHRT_MAX;
-        } else if constexpr(Core::IsSame<signed int, T>) {
-            return INT_MAX;
-        } else if constexpr(Core::IsSame<signed long, T>) {
-            return LONG_MAX;
-        } else if constexpr(Core::IsSame<signed long long, T>) {
-            return LLONG_MAX;
-        } else if constexpr(Core::IsSame<unsigned char, T>) {
-            return UCHAR_MAX;
-        } else if constexpr(Core::IsSame<unsigned short, T>) {
-            return USHRT_MAX;
-        } else if constexpr(Core::IsSame<unsigned int, T>) {
-            return UINT_MAX;
-        } else if constexpr(Core::IsSame<unsigned long, T>) {
-            return ULONG_MAX;
-        } else if constexpr(Core::IsSame<unsigned long long, T>) {
-            return ULLONG_MAX;
-        } else {
-            return T::emptyValue();
-        }
-    }
-}
-
-#endif

+ 0 - 80
include/core/utils/Logger.hpp

@@ -1,80 +0,0 @@
-#ifndef CORE_LOGGER_HPP
-#define CORE_LOGGER_HPP
-
-#include "core/utils/ArrayString.hpp"
-
-namespace Core::Logger {
-    enum class Level { ERROR, WARNING, INFO, DEBUG };
-    extern Level level;
-
-    [[maybe_unused]] static constexpr const char* COLOR_RED = "\33[1;31m";
-    [[maybe_unused]] static constexpr const char* COLOR_YELLOW = "\33[1;33m";
-    [[maybe_unused]] static constexpr const char* COLOR_GRAY = "\33[1;37m";
-    [[maybe_unused]] static constexpr const char* COLOR_GREEN = "\33[1;32m";
-    [[maybe_unused]] static constexpr const char* COLOR_RESET = "\33[0m";
-
-    const char* getFileName(const char* path);
-
-    // aborts on critical logging failure
-    template<typename... Args>
-    void log(Level l, const char* file, int line, const char* prefix,
-             const char* tag, const char* format, Args&&... args) {
-        if(Core::Logger::level < l) {
-            return;
-        }
-        file = getFileName(file);
-        Core::ArrayString<2048> s;
-        s.append(prefix).append(tag).append("#:# | ");
-        s.format(file, line);
-        s.append(format);
-        s.format(Core::forward<Args>(args)...);
-        s.append(COLOR_RESET);
-        s.printLine();
-    }
-
-    template<typename... Args>
-    void log(const char* prefix, const char* format, Args&&... args) {
-        Core::ArrayString<2048> s;
-        s.append(prefix).append(format);
-        s.format(Core::forward<Args>(args)...);
-        s.append(COLOR_RESET).printLine();
-    }
-}
-
-#if defined(CORE_LOG_LEVEL) && CORE_LOG_LEVEL >= 1
-#define CORE_LOG_ERROR(format, ...)                                            \
-    log(Core::Logger::Level::ERROR, __FILE__, __LINE__,                        \
-        Core::Logger::COLOR_RED, "[ERROR] ",                                   \
-        format __VA_OPT__(, ) __VA_ARGS__);
-#else
-#define CORE_LOG_ERROR(format, ...)
-#endif
-
-#if defined(CORE_LOG_LEVEL) && CORE_LOG_LEVEL >= 2
-#define CORE_LOG_WARNING(format, ...)                                          \
-    log(Core::Logger::Level::WARNING, __FILE__, __LINE__,                      \
-        Core::Logger::COLOR_YELLOW, "[WARNING] ",                              \
-        format __VA_OPT__(, ) __VA_ARGS__);
-#else
-#define CORE_LOG_WARNING(format, ...)
-#endif
-
-#if defined(CORE_LOG_LEVEL) && CORE_LOG_LEVEL >= 3
-#define CORE_LOG_INFO(format, ...)                                             \
-    log(Core::Logger::Level::INFO, __FILE__, __LINE__,                         \
-        Core::Logger::COLOR_GRAY, "[INFO] ",                                   \
-        format __VA_OPT__(, ) __VA_ARGS__);
-#else
-#define CORE_LOG_INFO(format, ...)
-#endif
-
-#if defined(CORE_LOG_LEVEL) && CORE_LOG_LEVEL >= 4
-#define CORE_LOG_DEBUG(format, ...)                                            \
-    log(Core::Logger::Level::DEBUG, __FILE__, __LINE__,                        \
-        Core::Logger::COLOR_GREEN, "[DEBUG] ",                                 \
-        format __VA_OPT__(, ) __VA_ARGS__);
-#else
-#define CORE_LOG_DEBUG(format, ...)
-#endif
-
-#endif

+ 0 - 19
include/core/utils/New.hpp

@@ -1,19 +0,0 @@
-#ifndef CORE_NEW_HPP
-#define CORE_NEW_HPP
-
-using size_t = decltype(sizeof(0));
-
-struct NoThrow {
-    explicit NoThrow() = default;
-};
-[[maybe_unused]] static constexpr NoThrow noThrow;
-
-void* operator new(size_t bytes, const NoThrow&) noexcept;
-void* operator new[](size_t bytes, const NoThrow&) noexcept;
-void operator delete(void* p) noexcept;
-void operator delete[](void* p) noexcept;
-void operator delete(void* p, size_t bytes) noexcept;
-void operator delete[](void* p, size_t bytes) noexcept;
-void* operator new(size_t bytes, void* p) noexcept;
-
-#endif

+ 0 - 33
include/core/utils/Random.hpp

@@ -1,33 +0,0 @@
-#ifndef CORE_RANDOM_HPP
-#define CORE_RANDOM_HPP
-
-#include "core/data/Array.hpp"
-#include "core/utils/Types.hpp"
-
-namespace Core {
-    struct Random final {
-        using Seed = u32;
-
-    private:
-        Array<Seed, 25> data;
-        size_t index;
-
-    public:
-        Random(Seed seed);
-
-        Seed next();
-        Seed next(Seed min, Seed inclusiveMax);
-        i32 nextI32();
-        i32 nextI32(i32 min, i32 inclusiveMax);
-        size_t nextSize();
-        size_t nextSize(size_t min, size_t inclusiveMax);
-        bool nextBool();
-        float nextFloat();
-        float nextFloat(float min, float exclusiveMax);
-
-    private:
-        void update();
-    };
-}
-
-#endif

+ 0 - 48
include/core/utils/Utility.hpp

@@ -1,48 +0,0 @@
-#ifndef CORE_UTILITY_HPP
-#define CORE_UTILITY_HPP
-
-#include "core/utils/Check.hpp"
-#include "core/utils/Error.hpp"
-#include "core/utils/Types.hpp"
-
-namespace Core {
-    template<typename T, typename C = int>
-    C popCount(const T& t) {
-        static constexpr C map[16] = {0, 1, 1, 2, 1, 2, 2, 3,
-                                      1, 2, 2, 3, 2, 3, 3, 4};
-        C sum = 0;
-        for(size_t i = 0; i < sizeof(T) * 8; i += 4) {
-            sum += map[(t >> i) & 0xF];
-        }
-        return sum;
-    }
-
-    using ExitHandler = void (*)(int, void*);
-    void exitWithHandler(const char* file, int line, int value);
-    void setExitHandler(ExitHandler eh, void* data);
-#define CORE_EXIT(exitValue)                                                   \
-    Core::exitWithHandler(__FILE__, __LINE__, exitValue)
-
-    size_t toString(signed short s, char* buffer, size_t size);
-    size_t toString(unsigned short s, char* buffer, size_t size);
-    size_t toString(signed int i, char* buffer, size_t size);
-    size_t toString(unsigned int i, char* buffer, size_t size);
-    size_t toString(signed long l, char* buffer, size_t size);
-    size_t toString(unsigned long l, char* buffer, size_t size);
-    size_t toString(signed long long ll, char* buffer, size_t size);
-    size_t toString(unsigned long long ll, char* buffer, size_t size);
-    size_t toString(float f, char* buffer, size_t size);
-    size_t toString(double d, char* buffer, size_t size);
-    size_t toString(long double ld, char* buffer, size_t size);
-
-    void print(int c);
-    void print(const char* s);
-    void printLine(const char* s);
-
-    using OutOfMemoryHandler = void (*)(void*);
-    void setOutOfMemoryHandler(OutOfMemoryHandler h, void* data);
-    void* allocate(size_t n);
-    void* reallocate(void* p, size_t n);
-}
-
-#endif

+ 0 - 0
src/ArrayString.cpp → old/ArrayString.cpp


+ 0 - 0
include/core/utils/ArrayString.hpp → old/ArrayString.hpp


+ 0 - 0
test/modules/ArrayStringTests.cpp → old/ArrayStringTests.cpp


+ 35 - 79
performance/Main.cpp

@@ -1,31 +1,15 @@
-#include "../test/Test.hpp"
-#include "core/data/HashMap.hpp"
-#include "core/data/ProbingHashMap.hpp"
-#include "core/utils/Clock.hpp"
-#include "core/utils/Random.hpp"
+#include "core/Clock.hpp"
+#include "core/HashMap.hpp"
+#include "core/Logger.hpp"
+#include "core/Random.hpp"
 
-using Nanos = Core::Clock::Nanos;
-namespace Logger = Core::Logger;
+using HashMapInt = Core::HashMap<int, int>;
+using Core::Clock;
 
-struct Timer {
-    Nanos nanos;
-
-    Timer() : nanos(0) {
-        CORE_TEST_ERROR(Core::Clock::getNanos(nanos));
-    }
-
-    Nanos get() const {
-        Nanos nanos2 = 0;
-        CORE_TEST_ERROR(Core::Clock::getNanos(nanos2));
-        return nanos2 - nanos;
-    }
-};
-
-template<typename Map>
-static Nanos testSearch(const Map& m) {
-    Timer t;
+static i64 testSearch(const HashMapInt& m) {
+    i64 nanos = Clock::getNanos();
     volatile int sum = 0;
-    for(int i = 0; i < 10000; i++) {
+    for(int i = 0; i < 10'000; i++) {
         for(int k = -5000; k < 5000; k++) {
             const int* s = m.search(i + k);
             if(s != nullptr) {
@@ -33,12 +17,11 @@ static Nanos testSearch(const Map& m) {
             }
         }
     }
-    return t.get();
+    return Clock::getNanos() - nanos;
 }
 
-template<typename Map>
-static Nanos testEmptySearch(const Map& m) {
-    Timer t;
+static i64 testEmptySearch(const HashMapInt& m) {
+    i64 nanos = Clock::getNanos();
     volatile int sum = 0;
     for(int i = 0; i < 100'000'000; i++) {
         const int* s = m.search(-i);
@@ -46,80 +29,53 @@ static Nanos testEmptySearch(const Map& m) {
             sum = sum + *s;
         }
     }
-    return t.get();
+    return Clock::getNanos() - nanos;
 }
 
-template<typename Map>
-static void fillOrder(Map& m) {
-    for(int i = 0; i < 10000; i++) {
-        m.add(i, i * i);
+static void fillOrder(HashMapInt& m) {
+    i64 nanos = Clock::getNanos();
+    for(int i = 0; i < 10'000; i++) {
+        m.put(i, i * i);
     }
+    LOG_INFO("Fill Order: #ns", Clock::getNanos() - nanos);
 }
 
-template<typename Map>
-static void fillChaos(Map& m) {
+static void fillChaos(HashMapInt& m) {
+    i64 nanos = Clock::getNanos();
     Core::Random random(0);
-    for(int i = 0; i < 10000; i++) {
-        int r = random.nextI32(0, 9999);
-        m.add(r, r * r);
+    for(int i = 0; i < 10'000; i++) {
+        int r = random.nextI32(0, 10'000);
+        m.put(r, r * r);
     }
+    LOG_INFO("Fill Chaos: #ns", Clock::getNanos() - nanos);
 }
 
-template<typename Map>
-static int average(Map& m, Nanos (*f)(const Map&), int n) {
-    Nanos sum = 0;
+static i64 average(HashMapInt& m, i64 (*f)(const HashMapInt& m), int n) {
+    i64 sum = 0;
     for(int i = 0; i < n; i++) {
         sum += f(m);
     }
-    return static_cast<int>(sum / (n * 1'000'000));
+    return static_cast<i64>(sum / (n * 1'000'000));
 }
 
 static void order(int n) {
-    Core::HashMap<int, int> m;
-    Core::ProbingHashMap<int, int> m2;
+    HashMapInt m;
     fillOrder(m);
-    fillOrder(m2);
-    Logger::log(Logger::COLOR_GRAY, "Order Chaining | Order Probing");
-    Logger::log(Logger::COLOR_GRAY, "Search | # ms | # ms",
-                average(m, testSearch, n), average(m2, testSearch, n));
-    Logger::log(Logger::COLOR_GRAY, "EmptySearch | # ms | # ms",
-                average(m, testEmptySearch, n),
-                average(m2, testEmptySearch, n));
+    LOG_INFO("Order Probing");
+    LOG_INFO("Search | #ms", average(m, testSearch, n));
+    LOG_INFO("EmptySearch | #ms", average(m, testEmptySearch, n));
 }
 
 static void chaos(int n) {
-    Core::HashMap<int, int> m;
-    Core::ProbingHashMap<int, int> m2;
+    HashMapInt m;
     fillChaos(m);
-    fillChaos(m2);
-    Logger::log(Logger::COLOR_GRAY, "Chaos Chaining | Chaos Probing");
-    Logger::log(Logger::COLOR_GRAY, "Search | # ms | # ms",
-                average(m, testSearch, n), average(m2, testSearch, n));
-    Logger::log(Logger::COLOR_GRAY, "EmptySearch | # ms | # ms",
-                average(m, testEmptySearch, n),
-                average(m2, testEmptySearch, n));
-}
-
-static void testProbing(int n) {
-    Core::ProbingHashMap<int, int> m;
-    Core::ProbingHashMap<int, int> m2;
-    fillOrder(m);
-    fillChaos(m2);
-    Logger::log(Logger::COLOR_GRAY, "Order | Chaos");
-    Logger::log(Logger::COLOR_GRAY, "Search | # ms | # ms",
-                average(m, testSearch, n), average(m2, testSearch, n));
-    Logger::log(Logger::COLOR_GRAY, "EmptySearch | # ms | # ms",
-                average(m, testEmptySearch, n),
-                average(m2, testEmptySearch, n));
+    LOG_INFO("Chaos Probing");
+    LOG_INFO("Search | #ms", average(m, testSearch, n));
+    LOG_INFO("EmptySearch | #ms", average(m, testEmptySearch, n));
 }
 
 int main() {
-    (void)order;
-    (void)chaos;
-    (void)testProbing;
     order(3);
     chaos(3);
-    // testProbing(3);
-    Core::Test::finalize();
     return 0;
 }

+ 71 - 52
src/BitArray.cpp

@@ -1,22 +1,16 @@
-#include "core/data/BitArray.hpp"
+#include "core/BitArray.hpp"
 
-#include <string.h>
+#include <cassert>
+#include <cstring>
 
-#include "core/math/Math.hpp"
-#include "core/utils/New.hpp"
+#include "core/BitArray.hpp"
+#include "core/ToString.hpp"
+#include "core/Utility.hpp"
 
-static u64 roundUpDivide(u64 a, u64 b) {
-    if(a % b == 0) {
-        return a / b;
-    }
-    return a / b + 1;
-}
+using Core::BitArray;
 
-static constexpr u64 U64_BITS = 64;
-static constexpr u64 DIVIDE_BITS = Core::Math::roundUpLog2(U64_BITS);
-static constexpr u64 LENGTH_MASK = 0x01FF'FFFF'FFFF'FFFF;
-static constexpr u64 LENGTH_BITS = Core::Math::roundUpLog2(LENGTH_MASK);
-static_assert(LENGTH_BITS == 57, "bit array calculation error");
+static constexpr size_t U64_BITS = 64;
+static constexpr size_t DIVIDE_BITS = 6;
 
 static u64 readBits(const u64* data, size_t index, u64 bits) {
     u64 dataIndexA = (index * bits) >> DIVIDE_BITS;
@@ -46,55 +40,57 @@ static void setBits(u64* data, size_t index, size_t bits, u64 value) {
     }
 }
 
+static u64 roundUpDivide(u64 a, u64 b) {
+    return a / b + ((a % b) != 0);
+}
+
 static size_t getArrayLength(size_t length, size_t bits) {
     return roundUpDivide(length * bits, U64_BITS);
 }
 
-Core::BitArray::BitArray() : lengthBits(0), data(nullptr) {
+BitArray::BitArray() : length(0), bits(0), data(nullptr) {
 }
 
-Core::BitArray::BitArray(const BitArray& other) : BitArray() {
-    (void)resize(other.getLength(), other.getBits());
-    size_t length = getLength();
-    for(size_t i = 0; i < length; i++) {
+BitArray::BitArray(const BitArray& other) : BitArray() {
+    resize(other.getLength(), other.getBits());
+    size_t l = getLength();
+    for(size_t i = 0; i < l; i++) {
         set(i, other.get(i));
     }
 }
 
-Core::BitArray::BitArray(BitArray&& other) : BitArray() {
+BitArray::BitArray(BitArray&& other) : BitArray() {
     swap(other);
 }
 
-Core::BitArray::~BitArray() {
-    delete[] data;
+BitArray::~BitArray() {
+    coreDeleteN(data);
 }
 
-Core::BitArray& Core::BitArray::operator=(BitArray other) {
+BitArray& BitArray::operator=(BitArray other) {
     swap(other);
     return *this;
 }
 
-Core::BitArray& Core::BitArray::set(size_t index, u64 value) {
-    if(data == nullptr || index >= getLength()) {
-        return *this;
-    }
-    setBits(data, index, getBits(), value);
+BitArray& BitArray::set(size_t index, u64 value) {
+    assert(data != nullptr);
+    assert(index < length);
+    setBits(data, index, bits, value);
     return *this;
 }
 
-u64 Core::BitArray::get(size_t index) const {
-    if(data == nullptr || index >= getLength()) {
-        return 0;
-    }
-    return readBits(data, index, getBits());
+u64 BitArray::get(size_t index) const {
+    assert(data != nullptr);
+    assert(index < length);
+    return readBits(data, index, bits);
 }
 
-size_t Core::BitArray::getLength() const {
-    return lengthBits & LENGTH_MASK;
+size_t BitArray::getLength() const {
+    return length;
 }
 
-size_t Core::BitArray::getBits() const {
-    return (lengthBits & ~LENGTH_MASK) >> LENGTH_BITS;
+size_t BitArray::getBits() const {
+    return bits;
 }
 
 size_t Core::BitArray::getInternalByteSize() const {
@@ -104,12 +100,12 @@ size_t Core::BitArray::getInternalByteSize() const {
     return getArrayLength(getLength(), getBits()) * sizeof(u64);
 }
 
-i64 Core::BitArray::select(u64 index) const {
+i64 BitArray::select(u64 index) const {
     if(index <= 0) {
         return -1;
     }
     u64 found = 0;
-    size_t end = getArrayLength(getLength(), getBits());
+    size_t end = getArrayLength(length, bits);
     for(size_t i = 0; i < end; i++) {
         u64 ones = Core::popCount<u64, u64>(data[i]);
         found += ones;
@@ -128,22 +124,24 @@ i64 Core::BitArray::select(u64 index) const {
     return -1;
 }
 
-void Core::BitArray::fill(u64 value) {
-    size_t length = getLength();
-    for(size_t i = 0; i < length; i++) {
+void BitArray::fill(u64 value) {
+    size_t l = getLength();
+    for(size_t i = 0; i < l; i++) {
         set(i, value);
     }
 }
 
-CError Core::BitArray::resize(size_t newLength, size_t newBits) {
-    if(newLength == 0 || newBits == 0 || newBits > 64) {
-        return ErrorCode::INVALID_ARGUMENT;
+void BitArray::resize(size_t newLength, size_t newBits) {
+    if(newLength == 0 || newBits == 0) {
+        return;
+    } else if(newBits > 64) {
+        newBits = 64;
     }
     size_t arrayLength = getArrayLength(newLength, newBits);
-    u64* newData = new(noThrow) u64[arrayLength];
+    u64* newData = coreNewN(u64, arrayLength);
     memset(newData, 0, arrayLength * sizeof(u64));
 
-    size_t end = Math::min(getLength(), newLength);
+    size_t end = Core::min(length, newLength);
     for(size_t i = 0; i < end; i++) {
         setBits(newData, i, newBits, get(i));
     }
@@ -152,11 +150,32 @@ CError Core::BitArray::resize(size_t newLength, size_t newBits) {
     }
     delete[] data;
     data = newData;
-    lengthBits = newLength | (newBits << LENGTH_BITS);
-    return ErrorCode::NONE;
+    length = newLength & 0x00FF'FFFF;
+    bits = newBits & 0xFF;
+}
+
+size_t BitArray::toString(char* s, size_t n) const {
+    size_t total = 0;
+    addString("[", s, n, total);
+    size_t l = length;
+    if(l > 0) {
+        l--;
+        for(size_t i = 0; i < l; i++) {
+            addString(get(i), s, n, total);
+            addString(", ", s, n, total);
+        }
+        addString(get(l), s, n, total);
+    }
+    addString("]", s, n, total);
+    return total;
 }
 
-void Core::BitArray::swap(BitArray& other) {
-    Core::swap(lengthBits, other.lengthBits);
+void BitArray::swap(BitArray& other) {
+    u64 l = length;
+    u64 b = bits;
+    length = other.length;
+    bits = other.bits;
+    other.length = l & 0x00FF'FFFF;
+    other.bits = b & 0xFF;
     Core::swap(data, other.data);
 }

+ 30 - 16
src/Box.cpp

@@ -1,10 +1,13 @@
-#include "core/math/Box.hpp"
+#include "core/Box.hpp"
 
-Core::Box::Box(const Vector3& min_, const Vector3& max_)
-    : min(min_), max(max_) {
+#include <cstdio>
+
+using Core::Box;
+
+Box::Box(const Vector3& min_, const Vector3& max_) : min(min_), max(max_) {
 }
 
-Core::Box::Box(const Vector3& size) : min(), max() {
+Box::Box(const Vector3& size) : min(), max() {
     for(size_t i = 0; i < 3; i++) {
         if(size[i] < 0.0f) {
             min[i] = size[i];
@@ -14,27 +17,29 @@ Core::Box::Box(const Vector3& size) : min(), max() {
     }
 }
 
-Core::Box Core::Box::offset(const Vector3& offset) const {
+Box Box::offset(const Vector3& offset) const {
     return Box(min + offset, max + offset);
 }
 
-bool Core::Box::collidesWith(const Box& other) const {
+bool Box::collidesWith(const Box& other) const {
     return max[0] > other.min[0] && min[0] < other.max[0] &&
            max[1] > other.min[1] && min[1] < other.max[1] &&
            max[2] > other.min[2] && min[2] < other.max[2];
 }
 
-Core::Box Core::Box::expand(const Vector3& offset) const {
-    Vector3 add(offset[0] > 0.0f ? offset[0] : 0.0f,
-                offset[1] > 0.0f ? offset[1] : 0.0f,
-                offset[2] > 0.0f ? offset[2] : 0.0f);
-    Vector3 sub(offset[0] < 0.0f ? offset[0] : 0.0f,
-                offset[1] < 0.0f ? offset[1] : 0.0f,
-                offset[2] < 0.0f ? offset[2] : 0.0f);
+Box Box::expand(const Vector3& offset) const {
+    Vector3 add(
+        offset[0] > 0.0f ? offset[0] : 0.0f,
+        offset[1] > 0.0f ? offset[1] : 0.0f,
+        offset[2] > 0.0f ? offset[2] : 0.0f);
+    Vector3 sub(
+        offset[0] < 0.0f ? offset[0] : 0.0f,
+        offset[1] < 0.0f ? offset[1] : 0.0f,
+        offset[2] < 0.0f ? offset[2] : 0.0f);
     return Box(min + sub, max + add);
 }
 
-Core::Box Core::Box::grow(const Vector3& growth) const {
+Box Box::grow(const Vector3& growth) const {
     Vector3 half = growth * 0.5f;
     Vector3 nMin = min - half;
     Vector3 nMax = max + half;
@@ -47,10 +52,19 @@ Core::Box Core::Box::grow(const Vector3& growth) const {
     return Box(nMin, nMax);
 }
 
-const Core::Vector3& Core::Box::getMin() const {
+const Core::Vector3& Box::getMin() const {
     return min;
 }
 
-const Core::Vector3& Core::Box::getMax() const {
+const Core::Vector3& Box::getMax() const {
     return max;
 }
+
+size_t Box::toString(char* s, size_t n) const {
+    int w = snprintf(
+        s, n, "Box([%.2f, %.2f, %.2f], [%.2f, %.2f, %.2f])",
+        static_cast<double>(min[0]), static_cast<double>(min[1]),
+        static_cast<double>(min[2]), static_cast<double>(max[0]),
+        static_cast<double>(max[1]), static_cast<double>(max[2]));
+    return w >= 0 ? static_cast<size_t>(w) : 0;
+}

+ 19 - 23
src/Buffer.cpp

@@ -1,53 +1,49 @@
-#include "core/utils/Buffer.hpp"
+#include "core/Buffer.hpp"
 
-#include <stdlib.h>
-#include <string.h>
+#include <cstring>
 
-#include "core/math/Math.hpp"
-#include "core/utils/Utility.hpp"
+#include "core/Buffer.hpp"
+#include "core/Math.hpp"
+#include "core/Utility.hpp"
 
-Core::Buffer::Buffer(size_t initialSize)
-    : length(0), capacity(initialSize <= 0 ? 1 : initialSize), buffer(nullptr) {
+Core::Buffer::Buffer() : length(0), capacity(0), buffer(nullptr) {
 }
 
-Core::Buffer::Buffer(Buffer&& other) : Buffer(1) {
+Core::Buffer::Buffer(const Buffer& other) : Buffer() {
+    add(other.getData(), other.getLength());
+}
+
+Core::Buffer::Buffer(Buffer&& other) noexcept : Buffer() {
     swap(other);
 }
 
 Core::Buffer::~Buffer() {
-    free(buffer);
+    deallocateRaw(buffer);
 }
 
-Core::Buffer& Core::Buffer::operator=(Buffer&& other) {
+Core::Buffer& Core::Buffer::operator=(Buffer&& other) noexcept {
     swap(other);
     return *this;
 }
 
-Core::Error Core::Buffer::copyFrom(const Buffer& other) {
-    clear();
+Core::Buffer& Core::Buffer::operator=(const Buffer& other) {
     return add(other.getData(), other.length);
 }
 
-Core::Error Core::Buffer::add(const void* data, size_t size) {
-    if(length + size > capacity || buffer == nullptr) {
-        while(length + size > capacity) {
-            capacity += Math::max<size_t>(4, capacity / 4);
-        }
-        buffer = static_cast<char*>(reallocate(buffer, capacity));
+Core::Buffer& Core::Buffer::add(const void* data, size_t size) {
+    while(length + size > capacity) {
+        capacity += Core::max<size_t>(8, capacity / 4);
+        buffer = static_cast<char*>(reallocateRaw(buffer, capacity));
     }
     memcpy(buffer + length, data, size);
     length += size;
-    return ErrorCode::NONE;
+    return *this;
 }
 
 size_t Core::Buffer::getLength() const {
     return length;
 }
 
-Core::Buffer::operator const char*() const {
-    return buffer;
-}
-
 const char* Core::Buffer::getData() const {
     return buffer;
 }

+ 34 - 24
src/Clock.cpp

@@ -1,48 +1,58 @@
-#include "core/utils/Clock.hpp"
+#include "core/Clock.hpp"
 
-#include <threads.h>
-#include <time.h>
+#include <chrono>
+#include <thread>
 
 #include "ErrorSimulator.hpp"
 
-Core::Clock::Clock() : index(0), last(0), sum(0), time(0) {
+using Core::Clock;
+
+Clock::Clock() : index(0), last(0), sum(0), time({}) {
 }
 
-Core::Error Core::Clock::update(Clock::Nanos& n) {
-    Nanos current = 0;
-    CORE_RETURN_ERROR(getNanos(current));
+i64 Clock::update() {
+    i64 current = getNanos();
+    if(current < 0) {
+        return current;
+    }
     if(last == 0) {
         last = current;
-        n = 0;
-        return ErrorCode::NONE;
+        return 0;
     }
     index = (index + 1) & (time.getLength() - 1);
     sum -= time[index];
     time[index] = current - last;
     sum += time[index];
     last = current;
-    n = time[index];
-    return ErrorCode::NONE;
+    return time[index];
 }
 
 float Core::Clock::getUpdatesPerSecond() const {
     return (time.getLength() * 1000000000.0f) / static_cast<float>(sum);
 }
 
-Core::Error Core::Clock::getNanos(Clock::Nanos& n) {
-    timespec ts;
-    if(timespec_get(&ts, TIME_UTC) == 0 || CORE_TIME_GET_FAIL) {
-        return ErrorCode::TIME_NOT_AVAILABLE;
+bool Clock::sleepNanos(i64 nanos) {
+    try {
+        FAIL_STEP_THROW();
+        std::this_thread::sleep_for(std::chrono::nanoseconds(nanos));
+    } catch(...) {
+        return true;
     }
-    n = static_cast<Clock::Nanos>(ts.tv_sec) * 1'000'000'000L +
-        static_cast<Clock::Nanos>(ts.tv_nsec);
-    return ErrorCode::NONE;
+    return false;
 }
 
-Core::Error Core::Clock::wait(Nanos nanos) {
-    timespec t;
-    t.tv_nsec = nanos % 1'000'000'000;
-    t.tv_sec = nanos / 1'000'000'000;
-    return thrd_sleep(&t, nullptr) != 0 ? ErrorCode::SLEEP_INTERRUPTED
-                                        : ErrorCode::NONE;
+bool Clock::sleepMillis(i64 millis) {
+    return sleepNanos(millis * 1'000'000l);
+}
+
+i64 Clock::getNanos(void) {
+    try {
+        FAIL_STEP_THROW();
+        using namespace std::chrono;
+        return duration_cast<nanoseconds>(
+                   high_resolution_clock::now().time_since_epoch())
+            .count();
+    } catch(std::exception& e) {
+        return -1;
+    }
 }

+ 0 - 20
src/Error.cpp

@@ -1,20 +0,0 @@
-#include "core/utils/Error.hpp"
-
-size_t Core::toString(Error e, char* buffer, size_t size) {
-    size_t written = 0;
-    Error::Code c = e.code;
-    for(size_t i = size; i > 1; i--) {
-        *(buffer++) = (c & 1) ? '1' : '0';
-        written++;
-        c >>= 1;
-        if(c == 0) {
-            break;
-        }
-    }
-    *buffer = '\0';
-    while(c != 0) {
-        written++;
-        c >>= 1;
-    }
-    return written;
-}

+ 9 - 3
src/ErrorSimulator.cpp

@@ -1,8 +1,14 @@
 #ifdef ERROR_SIMULATOR
+
 #include "ErrorSimulator.hpp"
 
-bool Core::Fail::alloc = false;
-bool Core::Fail::fileClose = false;
-bool Core::Fail::timeGet = false;
+bool failTimeGet = false;
+bool failThreadInit = false;
+bool failThreadJoin = false;
+bool failMutexInit = false;
+bool failMutexLock = false;
+bool failMutexUnlock = false;
+int failStep = -1;
+int failStepThrow = -1;
 
 #endif

+ 34 - 10
src/ErrorSimulator.hpp

@@ -1,19 +1,43 @@
 #ifndef CORE_ERROR_SIMULATOR_HPP
 #define CORE_ERROR_SIMULATOR_HPP
 
+#include <stdexcept>
+
 #ifdef ERROR_SIMULATOR
-namespace Core::Fail {
-    extern bool alloc;
-    extern bool fileClose;
-    extern bool timeGet;
+extern bool failTimeGet;
+extern bool failThreadInit;
+extern bool failThreadJoin;
+extern bool failMutexInit;
+extern bool failMutexLock;
+extern bool failMutexUnlock;
+extern int failStep;
+extern int failStepThrow;
+#define TIME_GET_FAIL failTimeGet
+#define THREAD_INIT_FAIL failThreadInit
+#define THREAD_JOIN_FAIL failThreadJoin
+#define MUTEX_INIT_FAIL failMutexInit
+#define MUTEX_LOCK_FAIL failMutexLock
+#define MUTEX_UNLOCK_FAIL failMutexUnlock
+#define FAIL_STEP (--failStep == 0)
+
+inline void debugThrow() {
+    if(--failStepThrow == 0) {
+        throw std::runtime_error("not a real error");
+    }
 }
-#define CORE_ALLOC_FAIL Core::Fail::alloc
-#define CORE_FILE_CLOSE_FAIL Core::Fail::fileClose
-#define CORE_TIME_GET_FAIL Core::Fail::timeGet
+
+#define FAIL_STEP_THROW() debugThrow()
 #else
-#define CORE_ALLOC_FAIL(...) false
-#define CORE_FILE_CLOSE_FAIL false
-#define CORE_TIME_GET_FAIL false
+#define TIME_GET_FAIL false
+#define THREAD_INIT_FAIL false
+#define THREAD_JOIN_FAIL false
+#define MUTEX_INIT_FAIL false
+#define MUTEX_LOCK_FAIL false
+#define MUTEX_UNLOCK_FAIL false
+#define FAIL_STEP false
+#define FAIL_STEP_THROW() \
+    do {                  \
+    } while(false)
 #endif
 
 #endif

+ 49 - 0
src/File.cpp

@@ -0,0 +1,49 @@
+#include "core/File.hpp"
+
+#include <stdio.h>
+
+#include "ErrorSimulator.hpp"
+#include "core/Logger.hpp"
+
+using Core::LogLevel;
+
+static bool readOpenFile(FILE* file, Core::List<char>& f, const char* path) {
+    if(FAIL_STEP || fseek(file, 0, SEEK_END)) {
+        REPORT(LogLevel::ERROR, "cannot seek file end of '#'", path);
+        return true;
+    }
+    long l = ftell(file);
+    if(FAIL_STEP || l < 0) {
+        REPORT(LogLevel::ERROR, "cannot tell file position of '#'", path);
+        return true;
+    }
+    size_t size = static_cast<size_t>(l);
+    f.resize(size + 1);
+    if(FAIL_STEP || fseek(file, 0, SEEK_SET)) {
+        REPORT(LogLevel::ERROR, "cannot seek file start of '#'", path);
+        return true;
+    }
+    size_t read = fread(&f[0], 1, size, file);
+    f.getLast() = 0;
+    if(FAIL_STEP || read != size) {
+        REPORT(
+            LogLevel::ERROR, "expected to read # bytes from '#' but read #",
+            size, path, read);
+        return true;
+    }
+    return false;
+}
+
+bool Core::readFile(List<char>& f, const char* path) {
+    FILE* file = fopen(path, "rb");
+    if(file == nullptr) {
+        REPORT(LogLevel::ERROR, "cannot read file '#'", path);
+        return true;
+    }
+    bool r = readOpenFile(file, f, path);
+    if(FAIL_STEP || fclose(file)) {
+        REPORT(LogLevel::ERROR, "cannot close file '#'", path);
+        r = true;
+    }
+    return r;
+}

+ 0 - 73
src/FileReader.cpp

@@ -1,73 +0,0 @@
-#include "core/io/FileReader.hpp"
-
-#include "ErrorSimulator.hpp"
-#include "core/utils/Logger.hpp"
-
-Core::FileReader::FileReader() : file(nullptr), path() {
-}
-
-Core::FileReader::FileReader(FileReader&& other) : FileReader() {
-    swap(other);
-}
-
-Core::FileReader::~FileReader() {
-    if(file != nullptr) {
-        int r = fclose(file);
-        if(r != 0 || CORE_FILE_CLOSE_FAIL) {
-            CORE_LOG_WARNING("Cannot close file #: #", path, r);
-        }
-        file = nullptr;
-    }
-}
-
-Core::FileReader& Core::FileReader::operator=(FileReader&& other) {
-    swap(other);
-    return *this;
-}
-
-const Core::Path& Core::FileReader::getPath() const {
-    return path;
-}
-
-Core::Error Core::FileReader::open(const Path& p) {
-    if(file != nullptr) {
-        return ErrorCode::INVALID_STATE;
-    }
-    path = p;
-    file = fopen(path, "rb");
-    return file != nullptr ? ErrorCode::NONE : ErrorCode::CANNOT_OPEN_FILE;
-}
-
-Core::Error Core::FileReader::open(const char* p) {
-    if(file != nullptr) {
-        return ErrorCode::INVALID_STATE;
-    }
-    path.append(p);
-    file = fopen(path, "rb");
-    return file != nullptr ? ErrorCode::NONE : ErrorCode::CANNOT_OPEN_FILE;
-}
-
-Core::Error Core::FileReader::readChar(int& c) {
-    if(file == nullptr) {
-        return ErrorCode::INVALID_STATE;
-    }
-    c = fgetc(file);
-    return c == EOF ? ErrorCode::END_OF_FILE : ErrorCode::NONE;
-}
-
-Core::Error Core::FileReader::readChars(char* buffer, size_t bufferSize) {
-    if(file == nullptr) {
-        return ErrorCode::INVALID_STATE;
-    } else if(bufferSize <= 0) {
-        return ErrorCode::INVALID_ARGUMENT;
-    }
-    size_t size = bufferSize - 1;
-    size_t readBytes = fread(buffer, 1, size, file);
-    buffer[readBytes] = '\0';
-    return readBytes != size ? ErrorCode::END_OF_FILE : ErrorCode::NONE;
-}
-
-void Core::FileReader::swap(FileReader& other) {
-    Core::swap(file, other.file);
-    Core::swap(path, other.path);
-}

+ 42 - 33
src/Frustum.cpp

@@ -1,64 +1,73 @@
-#include "core/math/Frustum.hpp"
+#include "core/Frustum.hpp"
 
-Core::Frustum::Frustum(float fieldOfView, float nearClip_, float farClip_)
-    : projection(), planes(),
-      tan(tanf(Core::Math::degreeToRadian(fieldOfView) * 0.5f)),
-      nearClip(nearClip_), farClip(farClip_) {
-    float diff = 1.0f / (nearClip - farClip);
-    projection.set(1, Vector4(0.0f, 1.0f / tan, 0.0f, 0.0f));
-    projection.set(2, Vector4(0.0f, 0.0f, (nearClip + farClip) * diff,
-                              (2.0f * nearClip * farClip) * diff));
-    projection.set(3, Vector4(0.0f, 0.0f, -1.0f, 0.0f));
+#include <cmath>
+
+using Core::Frustum;
+using V3 = Core::Vector3;
+using V4 = Core::Vector4;
+
+Frustum::Frustum(float fieldOfView, float nearClip, float farClip) :
+    projection(), planes(), tan(tanf(fieldOfView * 0.5f)), near(nearClip),
+    far(farClip) {
+    float diff = 1.0f / (near - far);
+    projection.set(1, V4(0.0f, 1.0f / tan, 0.0f, 0.0f));
+    projection.set(
+        2, V4(0.0f, 0.0f, (near + far) * diff, (2.0f * near * far) * diff));
+    projection.set(3, V4(0.0f, 0.0f, -1.0f, 0.0f));
 }
 
-const Core::Matrix& Core::Frustum::updateProjection(const IntVector2& size) {
-    projection.set(0, Vector4(static_cast<float>(size[1]) /
-                                  (tan * static_cast<float>(size[0])),
-                              0.0f, 0.0f, 0.0f));
+const Core::Matrix& Frustum::updateProjection(const IntVector2& size) {
+    projection.set(
+        0, V4(static_cast<float>(size[1]) / (tan * static_cast<float>(size[0])),
+              0.0f, 0.0f, 0.0f));
     return projection;
 }
 
-void Core::Frustum::updatePlanes(const Vector3& pos, const Vector3& right,
-                                 const Vector3& up, const Vector3& front,
-                                 const IntVector2& size) {
+void Frustum::updatePlanes(
+    const V3& pos, const V3& right, const V3& up, const V3& front,
+    const IntVector2& size) {
     float aspect = static_cast<float>(size[0]) / static_cast<float>(size[1]);
 
-    float hNearHeight = tan * nearClip;
+    float hNearHeight = tan * near;
     float hNearWidth = hNearHeight * aspect;
 
-    float hFarHeight = tan * farClip;
+    float hFarHeight = tan * far;
     float hFarWidth = hFarHeight * aspect;
 
-    Vector3 fCenter = pos + front * farClip;
-    Vector3 fTopLeft = fCenter + (up * hFarHeight) - (right * hFarWidth);
-    Vector3 fTopRight = fCenter + (up * hFarHeight) + (right * hFarWidth);
-    Vector3 fBottomRight = fCenter - (up * hFarHeight) + (right * hFarWidth);
+    V3 fCenter = pos + front * far;
+    V3 upFar = up * hFarHeight;
+    V3 rightFar = right * hFarWidth;
+    V3 fTopLeft = fCenter + upFar - rightFar;
+    V3 fTopRight = fCenter + upFar + rightFar;
+    V3 fBottomRight = fCenter - upFar + rightFar;
 
-    Vector3 nCenter = pos + front * nearClip;
-    Vector3 nTopLeft = nCenter + (up * hNearHeight) - (right * hNearWidth);
-    Vector3 nBottomLeft = nCenter - (up * hNearHeight) - (right * hNearWidth);
-    Vector3 nBottomRight = nCenter - (up * hNearHeight) + (right * hNearWidth);
+    V3 nCenter = pos + front * near;
+    V3 upNear = up * hNearHeight;
+    V3 rightNear = right * hNearWidth;
+    V3 nTopLeft = nCenter + upNear - rightNear;
+    V3 nBottomLeft = nCenter - upNear - rightNear;
+    V3 nBottomRight = nCenter - upNear + rightNear;
 
-    planes[0] = Plane(nBottomRight, nTopLeft, nBottomLeft);     // n plane
-    planes[1] = Plane(fTopRight, fBottomRight, fTopLeft);       // f plane
+    planes[0] = Plane(nBottomRight, nTopLeft, nBottomLeft);     // near plane
+    planes[1] = Plane(fTopRight, fBottomRight, fTopLeft);       // far plane
     planes[2] = Plane(nBottomRight, nBottomLeft, fBottomRight); // bottom plane
     planes[3] = Plane(fTopLeft, nTopLeft, fTopRight);           // top plane
     planes[4] = Plane(nBottomLeft, nTopLeft, fTopLeft);         // left plane
     planes[5] = Plane(fBottomRight, fTopRight, nBottomRight);   // right plane
 }
 
-bool Core::Frustum::isInside(const Vector3& pos) const {
+bool Frustum::isInside(const V3& pos) const {
     for(const Plane& p : planes) {
-        if(p.getSignedDistance(pos) < 0.0f) {
+        if(p.signedDistance(pos) < 0.0f) {
             return false;
         }
     }
     return true;
 }
 
-bool Core::Frustum::isInside(const Vector3& pos, float radius) const {
+bool Frustum::isInside(const V3& pos, float radius) const {
     for(const Plane& p : planes) {
-        if(p.getSignedDistance(pos) < -radius) {
+        if(p.signedDistance(pos) < -radius) {
             return false;
         }
     }

+ 22 - 9
src/Logger.cpp

@@ -1,14 +1,27 @@
-#include "core/utils/Logger.hpp"
+#include "core/Logger.hpp"
 
-Core::Logger::Level Core::Logger::level = Core::Logger::Level::DEBUG;
+Core::LogLevel Core::logLevel = Core::LogLevel::DEBUG;
+static Core::ReportHandler reportHandler = nullptr;
+static void* reportData = nullptr;
 
-const char* Core::Logger::getFileName(const char* path) {
-    int end = 0;
-    while(path[end] != '\0') {
-        end++;
+void Core::setReportHandler(ReportHandler h, void* data) {
+    reportHandler = h;
+    reportData = data;
+}
+
+void Core::callReportHandler(
+    LogLevel l, const char* file, int line, const char* report) {
+    if(reportHandler != nullptr) {
+        reportHandler(l, file, line, reportData, report);
     }
-    while(end > 0 && path[end - 1] != '/') {
-        end--;
+}
+
+const char* Core::getShortFileName(const char* s) {
+    const char* r = s;
+    while(*s != '\0') {
+        if(*(s++) == '/') {
+            r = s;
+        }
     }
-    return path + end;
+    return r;
 }

+ 17 - 14
src/Matrix.cpp

@@ -1,6 +1,4 @@
-#include "core/math/Matrix.hpp"
-
-#include "core/math/Math.hpp"
+#include "core/Matrix.hpp"
 
 Core::Matrix::Matrix() {
     unit();
@@ -15,7 +13,7 @@ Core::Matrix& Core::Matrix::set(size_t index, const Vector4& v) {
     return *this;
 }
 
-Core::Matrix Core::Matrix::transpose() {
+Core::Matrix Core::Matrix::transpose() const {
     Matrix m;
     for(size_t x = 0; x < 4; x++) {
         for(size_t y = 0; y < 4; y++) {
@@ -91,12 +89,12 @@ Core::Matrix& Core::Matrix::translateTo(const Vector3& v) {
     return *this;
 }
 
-Core::Matrix& Core::Matrix::rotate(float degrees, int a, int b) {
+Core::Matrix& Core::Matrix::rotate(float radians, int a, int b) {
     float sin = 0.0f;
     float cos = 0.0f;
-    sincosf(Core::Math::degreeToRadian(degrees), &sin, &cos);
+    sincosf(radians, &sin, &cos);
     Vector4 v = data[a];
-    data[a] = cos * data[a] - sin * data[b];
+    data[a] = cos * v - sin * data[b];
     data[b] = sin * v + cos * data[b];
     return *this;
 }
@@ -124,11 +122,16 @@ Core::Matrix& Core::Matrix::rotate(const Quaternion& q) {
     return *this;
 }
 
-void Core::Matrix::toString(BufferString& s) const {
-    s.append('[');
-    s.append(data[0]).append(", ");
-    s.append(data[1]).append(", ");
-    s.append(data[2]).append(", ");
-    s.append(data[3]);
-    s.append("]");
+size_t Core::Matrix::toString(char* s, size_t n) const {
+    size_t total = 0;
+    addString("[", s, n, total);
+    addString(data[0], s, n, total);
+    addString(", ", s, n, total);
+    addString(data[1], s, n, total);
+    addString(", ", s, n, total);
+    addString(data[2], s, n, total);
+    addString(", ", s, n, total);
+    addString(data[3], s, n, total);
+    addString("]", s, n, total);
+    return total;
 }

+ 0 - 23
src/Mutex.cpp

@@ -1,23 +0,0 @@
-#include "core/thread/Mutex.hpp"
-
-#include <string.h>
-
-Core::Mutex::Mutex() : mutex() {
-    memset(&mutex, 0, sizeof(mutex));
-}
-
-Core::Mutex::~Mutex() {
-    mtx_destroy(&mutex);
-}
-
-cbool Core::Mutex::init() {
-    return mtx_init(&mutex, mtx_plain) != thrd_success;
-}
-
-cbool Core::Mutex::lock() {
-    return mtx_lock(&mutex) != thrd_success;
-}
-
-cbool Core::Mutex::unlock() {
-    return mtx_unlock(&mutex) != thrd_success;
-}

+ 0 - 33
src/New.cpp

@@ -1,33 +0,0 @@
-#include "core/utils/New.hpp"
-
-#include <stdlib.h>
-
-#include "core/utils/Utility.hpp"
-
-void* operator new(size_t bytes, const NoThrow&) noexcept {
-    return Core::allocate(bytes);
-}
-
-void* operator new[](size_t bytes, const NoThrow&) noexcept {
-    return Core::allocate(bytes);
-}
-
-void operator delete(void* p) noexcept {
-    free(p);
-}
-
-void operator delete[](void* p) noexcept {
-    free(p);
-}
-
-void operator delete(void* p, size_t) noexcept {
-    operator delete(p);
-}
-
-void operator delete[](void* p, size_t) noexcept {
-    free(p);
-}
-
-void* operator new(size_t, void* p) noexcept {
-    return p;
-}

+ 15 - 12
src/Plane.cpp

@@ -1,21 +1,24 @@
-#include "core/math/Plane.hpp"
+#include "core/Plane.hpp"
 
-Core::Plane::Plane() : abc(), d(0) {
+#include <cstdio>
+
+using Core::Plane;
+
+Plane::Plane() : abc(), d(0) {
 }
 
-Core::Plane::Plane(const Vector3& a, const Vector3& b, const Vector3& c)
-    : abc((b - a).cross(c - a).normalize()), d(-abc.dot(b)) {
+Plane::Plane(const Vector3& a, const Vector3& b, const Vector3& c) :
+    abc(cross(b - a, c - a).normalize()), d(-abc.dot(b)) {
 }
 
-float Core::Plane::getSignedDistance(const Vector3& v) const {
+float Plane::signedDistance(const Vector3& v) const {
     return abc.dot(v) + d;
 }
 
-void Core::Plane::toString(BufferString& s) const {
-    s.append("(");
-    s.append(abc[0]).append(" x + ");
-    s.append(abc[1]).append(" y + ");
-    s.append(abc[2]).append(" z + ");
-    s.append(d);
-    s.append(')');
+size_t Plane::toString(char* s, size_t n) const {
+    int w = snprintf(
+        s, n, "(%.3f x + %.3f y + %.3f z + %.3f)", static_cast<double>(abc[0]),
+        static_cast<double>(abc[1]), static_cast<double>(abc[2]),
+        static_cast<double>(d));
+    return w >= 0 ? static_cast<size_t>(w) : 0;
 }

+ 26 - 28
src/Quaternion.cpp

@@ -1,31 +1,31 @@
-#include "core/math/Quaternion.hpp"
+#include "core/Quaternion.hpp"
 
-Core::Quaternion::Quaternion() : xyz(), w(1.0f) {
+#include <cmath>
+
+Core::Quaternion::Quaternion() : v(0.0f, 0.0f, 0.0f, 1.0f) {
 }
 
-Core::Quaternion::Quaternion(const Vector3& axis, float angle)
-    : xyz(axis), w(1.0f) {
-    xyz.normalize();
+Core::Quaternion::Quaternion(const Vector3& axis, float angle) :
+    v(axis[0], axis[1], axis[2], 1.0f) {
+    v.xyz().normalize();
     float factor = 0.0f;
-    sincosf(Core::Math::degreeToRadian(angle) * 0.5f, &factor, &w);
-    xyz *= factor;
+    sincosf(angle * 0.5f, &factor, &v[3]);
+    v.xyz() *= factor;
 }
 
-Core::Quaternion Core::Quaternion::lerp(float f,
-                                        const Quaternion& other) const {
+Core::Quaternion Core::Quaternion::lerp(
+    float f, const Quaternion& other) const {
     Quaternion q;
-    q.xyz = xyz * (1.0f - f) + other.xyz * f;
-    q.w = w * (1.0f - f) + other.w * f;
-    float iLength = 1.0f / sqrtf(q.xyz.squareLength() + q.w * q.w);
-    q.xyz *= iLength;
-    q.w *= iLength;
+    q.v = interpolate(v, other.v, f);
+    q.v.normalize();
     return q;
 }
 
 Core::Quaternion& Core::Quaternion::operator*=(const Quaternion& other) {
-    float dot = xyz.dot(other.xyz);
-    xyz = other.xyz * w + xyz * other.w + xyz.cross(other.xyz);
-    w = w * other.w - dot;
+    float dot = v.xyz().dot(other.v.xyz());
+    v.xyz() = other.v.xyz() * v[3] + v.xyz() * other.v[3] +
+              cross(v.xyz(), other.v.xyz());
+    v[3] = v[3] * other.v[3] - dot;
     return *this;
 }
 
@@ -35,17 +35,15 @@ Core::Quaternion Core::Quaternion::operator*(const Quaternion& other) const {
     return q;
 }
 
-Core::Vector3 Core::Quaternion::operator*(const Vector3& v) const {
-    Vector3 qv = v * w + xyz.cross(v);
-    Vector3 qvq = xyz * xyz.dot(v) + qv * w - qv.cross(xyz);
-    return qvq;
+Core::Vector3 Core::Quaternion::operator*(const Vector3& v3) const {
+    Vector3 qv = v3 * v[3] + cross(v.xyz(), v3);
+    return v.xyz() * v.xyz().dot(v3) + qv * v[3] - cross(qv, v.xyz());
 }
 
-void Core::Quaternion::toString(BufferString& s) const {
-    s.append("(");
-    s.append(xyz[0]).append(" i + ");
-    s.append(xyz[1]).append(" j + ");
-    s.append(xyz[2]).append(" k + ");
-    s.append(w);
-    s.append(')');
+size_t Core::Quaternion::toString(char* s, size_t n) const {
+    int w = snprintf(
+        s, n, "(%.3f i + %.3f j + %.3f k + %.3f)", static_cast<double>(v[0]),
+        static_cast<double>(v[1]), static_cast<double>(v[2]),
+        static_cast<double>(v[3]));
+    return w >= 0 ? static_cast<size_t>(w) : 0;
 }

+ 25 - 42
src/Random.cpp

@@ -1,18 +1,19 @@
-#include "core/utils/Random.hpp"
+#include "core/Random.hpp"
 
-#include <stdio.h>
+#include <cstring>
 
-constexpr static int M = 7;
+using Core::Random;
 
-Core::Random::Random(Seed seed) : data(), index(0) {
-    for(size_t i = 0; i < data.getLength(); i++) {
-        data[i] = seed;
+Random::Random(u32 seed) : data(), index(0) {
+    for(u32& u : data) {
+        u = seed;
         seed = seed * 7 + 31;
     }
 }
 
-void Core::Random::update() {
-    static const Seed map[2] = {0, 0x8EBFD028};
+void Random::update() {
+    constexpr int M = 7;
+    static const u32 map[2] = {0, 0x8EBF'D028};
     for(size_t i = 0; i < data.getLength() - M; i++) {
         data[i] = data[i + M] ^ (data[i] >> 1) ^ map[data[i] & 1];
     }
@@ -23,57 +24,39 @@ void Core::Random::update() {
     index = 0;
 }
 
-Core::Random::Seed Core::Random::next() {
+u32 Random::nextU32() {
     if(index >= data.getLength()) {
         update();
     }
-    Seed r = data[index++];
-    r ^= (r << 7) & 0x2B5B2500;
-    r ^= (r << 15) & 0xDB8B0000;
-    r ^= (r >> 16);
-    return r;
+    u32 u = data[index++];
+    u ^= (u << 7) & 0x2B5B'2500;
+    u ^= (u << 15) & 0xDB8B'0000;
+    u ^= (u >> 16);
+    return u;
 }
 
 template<typename T>
-T limit(T value, T min, T inclusiveMax) {
-    return min + value % (inclusiveMax - min + 1);
+T limit(T value, T min, T exclusiveMax) {
+    return min + value % (exclusiveMax - min);
 }
 
-Core::Random::Seed Core::Random::next(Seed min, Seed inclusiveMax) {
-    return limit(next(), min, inclusiveMax);
+u32 Random::nextU32(u32 min, u32 exclusiveMax) {
+    return limit(nextU32(), min, exclusiveMax);
 }
 
-i32 Core::Random::nextI32() {
-    return static_cast<i32>(next() >> 1);
+i32 Random::nextI32(i32 min, i32 exclusiveMax) {
+    return limit(static_cast<i32>(nextU32() >> 1), min, exclusiveMax);
 }
 
-i32 Core::Random::nextI32(i32 min, i32 inclusiveMax) {
-    return limit(nextI32(), min, inclusiveMax);
-}
-
-size_t Core::Random::nextSize() {
-    if constexpr(sizeof(size_t) <= sizeof(Seed)) {
-        return static_cast<size_t>(next());
-    } else {
-        return static_cast<size_t>(next()) |
-               (static_cast<size_t>(next()) >> 32);
-    }
-}
-
-size_t Core::Random::nextSize(size_t min, size_t inclusiveMax) {
-    return limit(nextSize(), min, inclusiveMax);
+size_t Core::Random::nextSize(size_t min, size_t exclusiveMax) {
+    return limit<size_t>(nextU32(), min, exclusiveMax);
 }
 
 bool Core::Random::nextBool() {
-    return next() & 1;
+    return nextU32() & 1;
 }
 
 float Core::Random::nextFloat() {
-    static constexpr i32 m = 0x7FFFFFFF;
-    float f = static_cast<float>(next() & m) * (1.0f / static_cast<float>(m));
+    float f = static_cast<float>(nextU32()) / static_cast<float>(0xFFFF'FFFFu);
     return f >= 1.0f ? nextFloat() : f;
 }
-
-float Core::Random::nextFloat(float min, float exclusiveMax) {
-    return min + nextFloat() * (exclusiveMax - min);
-}

+ 233 - 0
src/ReadLine.cpp

@@ -0,0 +1,233 @@
+#include "core/ReadLine.hpp"
+
+#include <atomic>
+
+#include "core/Array.hpp"
+#include "core/Clock.hpp"
+#include "core/Logger.hpp"
+#include "core/Queue.hpp"
+#include "core/Thread.hpp"
+#include "core/Unicode.hpp"
+
+static constexpr size_t HISTORY_LENGTH = 10;
+static constexpr size_t CONSOLE_BUFFER_SIZE = 256;
+
+struct ConsoleLine {
+    Core::Array<char, 256> data{};
+    size_t length = 0;
+};
+
+static std::atomic_bool running = true;
+static Core::Thread readThread;
+
+static Core::Queue<ConsoleLine, 10> buffer;
+static ConsoleLine currentBuffer;
+static size_t move = 0;
+static size_t cursorMove = 0;
+static Core::Mutex bufferMutex;
+
+static ConsoleLine history[HISTORY_LENGTH];
+static size_t historyOffset = 0;
+static size_t historyIndex = 0;
+static size_t historyLength = 0;
+
+static void addChar(u64 c) {
+    Core::UTF8 u = Core::convertUnicodeToUTF8(static_cast<u32>(c));
+    for(u32 k = 0; k < u.length; k++) {
+        if(currentBuffer.length >= CONSOLE_BUFFER_SIZE - 1) {
+            return;
+        }
+        for(size_t i = 0; i < move; i++) {
+            currentBuffer.data[currentBuffer.length - i] =
+                currentBuffer.data[currentBuffer.length - i - 1];
+        }
+        currentBuffer.length++;
+        currentBuffer.data[currentBuffer.length - move - 1] =
+            static_cast<char>(u.data[k]);
+        currentBuffer.data[currentBuffer.length] = '\0';
+    }
+}
+
+static void print(const char* s) {
+    fputs(s, stdout);
+}
+
+static void refreshLine(const char* prefix) {
+    print(prefix);
+    print(currentBuffer.data.begin());
+    if(cursorMove > 0) {
+        Core::moveCursorLeft(static_cast<int>(cursorMove));
+    }
+    fflush(stdout);
+    Core::clearTerminalLine();
+}
+
+static bool clear() {
+    move = 0;
+    cursorMove = 0;
+    currentBuffer.length = 0;
+    currentBuffer.data[0] = '\0';
+    historyOffset = 0;
+    return false;
+}
+
+static void addToHistory() {
+    if(historyLength < HISTORY_LENGTH) {
+        historyLength++;
+    }
+    history[historyIndex] = currentBuffer;
+    historyIndex = (historyIndex + 1) % HISTORY_LENGTH;
+}
+
+static void addLine() {
+    addToHistory();
+    Core::MutexGuard mg(bufferMutex);
+    buffer.add(currentBuffer);
+    clear();
+}
+
+static bool removeChar() {
+    size_t pos = currentBuffer.length - move;
+    if(pos > 0) {
+        size_t l = 1;
+        while(pos >= l) {
+            if(!Core::isUTF8Remainder(
+                   static_cast<u8>(currentBuffer.data[pos - l]))) {
+                break;
+            }
+            l++;
+        }
+        currentBuffer.length -= l;
+        for(size_t i = pos - l; i <= currentBuffer.length; i++) {
+            currentBuffer.data[i] = currentBuffer.data[i + l];
+        }
+    }
+    return false;
+}
+
+static bool handleControlKey(u64 c) {
+    switch(c) {
+        case 3: return clear();
+        case 10:
+        case 13: return true;
+        case 127: return removeChar();
+    }
+    return false;
+}
+
+static void copyHistory() {
+    currentBuffer = history
+        [(historyIndex - historyOffset + HISTORY_LENGTH) % HISTORY_LENGTH];
+    move = 0;
+    cursorMove = 0;
+}
+
+static void handleUpArrow() {
+    if(historyOffset >= historyLength) {
+        return;
+    }
+    historyOffset++;
+    copyHistory();
+}
+
+static void handleDownArrow() {
+    if(historyOffset <= 1) {
+        return;
+    }
+    historyOffset--;
+    copyHistory();
+}
+
+static char getMoved() {
+    return currentBuffer.data[currentBuffer.length - move];
+}
+
+static void handleLeftArrow() {
+    if(move < currentBuffer.length) {
+        move++;
+        while(move < currentBuffer.length &&
+              Core::isUTF8Remainder(static_cast<u8>(getMoved()))) {
+            move++;
+        }
+        cursorMove++;
+    }
+}
+
+static void handleRightArrow() {
+    if(move > 0) {
+        move--;
+        while(move > 0 && Core::isUTF8Remainder(static_cast<u8>(getMoved()))) {
+            move--;
+        }
+        cursorMove--;
+    }
+}
+
+static void handleChars() {
+    while(true) {
+        u64 c = Core::getRawChar();
+        if(c == 0) {
+            break;
+        } else if(c == TERMINAL_KEY_ARROW_UP) {
+            handleUpArrow();
+        } else if(c == TERMINAL_KEY_ARROW_DOWN) {
+            handleDownArrow();
+        } else if(c == TERMINAL_KEY_ARROW_RIGHT) {
+            handleRightArrow();
+        } else if(c == TERMINAL_KEY_ARROW_LEFT) {
+            handleLeftArrow();
+        } else if(c == TERMINAL_KEY_DELETE) {
+            if(move > 0) {
+                handleRightArrow();
+                removeChar();
+            }
+        } else if(iscntrl(c & 0x7FFF'FFFF)) {
+            if(handleControlKey(c)) {
+                addLine();
+                return;
+            }
+        } else if(!Core::isSpecialChar(c)) {
+            addChar(c);
+        }
+    }
+}
+
+static void loop(void*) {
+    while(running) {
+        handleChars();
+        refreshLine("> ");
+        Core::Clock::sleepMillis(1);
+    }
+}
+
+bool Core::startReadLine(void) {
+    if(enterRawTerminal()) {
+        REPORT(LogLevel::WARNING, "cannot set terminal attributes");
+    }
+    running = true;
+    if(readThread.start(loop, nullptr)) {
+        REPORT(LogLevel::ERROR, "cannot start read thread");
+        stopReadLine();
+        return true;
+    }
+    return false;
+}
+
+bool Core::readLine(char* buffer_, size_t n) {
+    if(buffer.getLength() == 0) {
+        return false;
+    }
+    Core::MutexGuard mg(bufferMutex);
+    snprintf(buffer_, n, "%s", buffer[0].data.begin());
+    buffer.remove();
+    return true;
+}
+
+void Core::stopReadLine() {
+    running = false;
+    readThread.join();
+    if(Core::leaveRawTerminal()) {
+        REPORT(LogLevel::WARNING, "cannot restore terminal attributes");
+    }
+    buffer.clear();
+}

+ 0 - 22
src/SpinLock.cpp

@@ -1,22 +0,0 @@
-#include "core/thread/SpinLock.hpp"
-
-#include <threads.h>
-
-Core::SpinLock::SpinLock() : locked() {
-    atomic_init(&locked, false);
-}
-
-void Core::SpinLock::lock() {
-    while(true) {
-        bool expected = false;
-        if(atomic_compare_exchange_weak(&locked, &expected, true)) {
-            break;
-        }
-        timespec s{0, 0};
-        thrd_sleep(&s, nullptr);
-    }
-}
-
-void Core::SpinLock::unlock() {
-    atomic_store(&locked, false);
-}

+ 232 - 0
src/Terminal.cpp

@@ -0,0 +1,232 @@
+#include "core/Terminal.hpp"
+
+#include <cstdio>
+
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "core/Array.hpp"
+#include "core/Logger.hpp"
+#include "core/Unicode.hpp"
+
+#define ESC "\33["
+#define esc(s) fputs(ESC s, stdout)
+
+using Core::LogLevel;
+static termios originalTerminal;
+
+struct EscapeSequence {
+    const u8 path[16] = {};
+    u64 mapping = 0;
+};
+
+#define K_CTRL TERMINAL_KEY_CTRL
+#define K_SHIFT TERMINAL_KEY_SHIFT
+#define K_ALT TERMINAL_KEY_ALT
+
+#define KEY_WITH_MODIFIER(c, m, n) {c, m | TERMINAL_KEY_##n}
+#define KEYS_WITH_MODIFIER(c, m)                       \
+    {u8"[1;" c "A", m | TERMINAL_KEY_ARROW_UP},        \
+        {u8"[1;" c "B", m | TERMINAL_KEY_ARROW_DOWN},  \
+        {u8"[1;" c "C", m | TERMINAL_KEY_ARROW_RIGHT}, \
+        {u8"[1;" c "D", m | TERMINAL_KEY_ARROW_LEFT},  \
+        {u8"[3;" c "~", m | TERMINAL_KEY_DELETE},      \
+        {u8"[1;" c "P", m | TERMINAL_KEY_F1},          \
+        {u8"[1;" c "Q", m | TERMINAL_KEY_F2},          \
+        {u8"[1;" c "R", m | TERMINAL_KEY_F3},          \
+        {u8"[1;" c "S", m | TERMINAL_KEY_F4},          \
+        {u8"[15;" c "~", m | TERMINAL_KEY_F5},         \
+        {u8"[17;" c "~", m | TERMINAL_KEY_F6},         \
+        {u8"[18;" c "~", m | TERMINAL_KEY_F7},         \
+        {u8"[19;" c "~", m | TERMINAL_KEY_F8},         \
+        {u8"[20;" c "~", m | TERMINAL_KEY_F9},         \
+        {u8"[21;" c "~", m | TERMINAL_KEY_F10},        \
+        {u8"[23;" c "~", m | TERMINAL_KEY_F11},        \
+        {u8"[24;" c "~", m | TERMINAL_KEY_F12},        \
+        {u8"[5;" c "~", m | TERMINAL_KEY_PAGE_UP},     \
+        {u8"[6;" c "~", m | TERMINAL_KEY_PAGE_DOWN},   \
+        {u8"[1;" c "H", m | TERMINAL_KEY_HOME}, {      \
+        u8"[1;" c "F", m | TERMINAL_KEY_END            \
+    }
+
+static const Core::Array<EscapeSequence, 175> ESCAPE_SEQUENCES = {{
+    {u8"[A", TERMINAL_KEY_ARROW_UP},
+    {u8"[B", TERMINAL_KEY_ARROW_DOWN},
+    {u8"[C", TERMINAL_KEY_ARROW_RIGHT},
+    {u8"[D", TERMINAL_KEY_ARROW_LEFT},
+    {u8"[3~", TERMINAL_KEY_DELETE},
+    {u8"OP", TERMINAL_KEY_F1},
+    {u8"[[A", TERMINAL_KEY_F1},
+    {u8"OQ", TERMINAL_KEY_F2},
+    {u8"[[B", TERMINAL_KEY_F2},
+    {u8"OR", TERMINAL_KEY_F3},
+    {u8"[[C", TERMINAL_KEY_F3},
+    {u8"OS", TERMINAL_KEY_F4},
+    {u8"[[D", TERMINAL_KEY_F4},
+    {u8"[15~", TERMINAL_KEY_F5},
+    {u8"[[E", TERMINAL_KEY_F5},
+    {u8"[17~", TERMINAL_KEY_F6},
+    {u8"[18~", TERMINAL_KEY_F7},
+    {u8"[19~", TERMINAL_KEY_F8},
+    {u8"[20~", TERMINAL_KEY_F9},
+    {u8"[21~", TERMINAL_KEY_F10},
+    {u8"[23~", TERMINAL_KEY_F11},
+    {u8"[24~", TERMINAL_KEY_F12},
+    {u8"[5~", TERMINAL_KEY_PAGE_UP},
+    {u8"[6~", TERMINAL_KEY_PAGE_DOWN},
+    {u8"[H", TERMINAL_KEY_HOME},
+    {u8"[1~", TERMINAL_KEY_HOME},
+    {u8"[F", TERMINAL_KEY_END},
+    {u8"[4~", TERMINAL_KEY_END},
+    KEYS_WITH_MODIFIER("2", K_SHIFT),
+    KEYS_WITH_MODIFIER("3", K_ALT),
+    KEYS_WITH_MODIFIER("4", K_ALT | K_SHIFT),
+    KEYS_WITH_MODIFIER("5", K_CTRL),
+    KEYS_WITH_MODIFIER("6", K_CTRL | K_SHIFT),
+    KEYS_WITH_MODIFIER("7", K_CTRL | K_ALT),
+    KEYS_WITH_MODIFIER("8", K_CTRL | K_ALT | K_SHIFT),
+}};
+
+void Core::enterAlternativeTerminal() {
+    esc("?1049h");
+}
+
+void Core::leaveAlternativeTerminal() {
+    esc("?1049l");
+}
+
+void Core::clearTerminal() {
+    esc("2J");
+}
+
+Core::IntVector2 Core::getTerminalSize() {
+    winsize w;
+    if(ioctl(0, TIOCGWINSZ, &w)) {
+        return IntVector2(0, 0);
+    }
+    return IntVector2(w.ws_col, w.ws_row);
+}
+
+void Core::clearTerminalLine() {
+    esc("2K\r");
+}
+
+void Core::hideCursor() {
+    esc("?25l");
+}
+
+void Core::showCursor() {
+    esc("?25h");
+}
+
+void Core::resetCursor() {
+    esc("H");
+}
+
+void Core::moveCursorLeft(int i) {
+    printf(ESC "%dD", i);
+}
+
+void Core::moveCursorRight(int i) {
+    printf(ESC "%dC", i);
+}
+
+void Core::moveCursorUp(int i) {
+    printf(ESC "%dA", i);
+}
+
+void Core::moveCursorDown(int i) {
+    printf(ESC "%dB", i);
+}
+
+bool Core::enterRawTerminal() {
+    if(tcgetattr(STDIN_FILENO, &originalTerminal)) {
+        return true;
+    }
+    termios raw = originalTerminal;
+    raw.c_iflag &= ~static_cast<tcflag_t>(ICRNL | IXON);
+    raw.c_lflag &= ~static_cast<tcflag_t>(ECHO | ICANON | IEXTEN | ISIG);
+    raw.c_cc[VMIN] = 0;
+    raw.c_cc[VTIME] = 0;
+    return tcsetattr(STDIN_FILENO, TCSANOW, &raw);
+}
+
+bool Core::leaveRawTerminal() {
+    return tcsetattr(STDIN_FILENO, TCSAFLUSH, &originalTerminal);
+}
+
+static u8 readChar() {
+    u8 c = 0;
+    ssize_t bytes = read(STDIN_FILENO, &c, 1);
+    return bytes <= 0 ? '\0' : c;
+}
+
+static bool couldMatch(const u8* s, const u8* e, size_t l) {
+    while(l > 0 && *s != 0 && *e != 0 && *s == *e) {
+        s++;
+        e++;
+        l--;
+    }
+    return l == 0;
+}
+
+static u64 readEscapeSequence() {
+    constexpr size_t CHARS = 10;
+    u8 s[CHARS] = {0};
+    s[0] = readChar();
+    s[1] = readChar();
+    if(s[1] == 0) {
+        return s[0] | K_ALT;
+    }
+    size_t l = 2;
+    while(true) {
+        bool matchFound = false;
+        for(size_t i = 0; i < ESCAPE_SEQUENCES.getLength(); i++) {
+            const EscapeSequence& e = ESCAPE_SEQUENCES[i];
+            if(couldMatch(s, e.path, l)) {
+                matchFound = true;
+                if(e.path[l] == 0) {
+                    return e.mapping;
+                }
+            }
+        }
+        if(!matchFound || l >= CHARS) {
+            break;
+        }
+        s[l++] = readChar();
+    }
+    REPORT(LogLevel::WARNING, "Unknown escape sequence starting with '#'", s);
+    return TERMINAL_KEY_UNKNOWN;
+}
+
+static u64 readUTF8() {
+    Core::UTF8 u = {};
+    u.data[0] = readChar();
+    u.length = Core::getUTF8Length(u.data[0]);
+    for(u32 i = 1; i < u.length; i++) {
+        u.data[i] = readChar();
+    }
+    return convertUTF8toUnicode(u);
+}
+
+u64 Core::getRawChar() {
+    u64 c = readUTF8();
+    if(c == 27) {
+        return readEscapeSequence();
+    }
+    return c;
+}
+
+bool Core::isSpecialChar(u64 u) {
+    return u >= TERMINAL_KEY_UNKNOWN;
+}
+
+Core::SpecialChar Core::convertToSpecialChar(u64 u) {
+    SpecialChar c;
+    c.shift = TERMINAL_KEY_SHIFT & u;
+    c.control = TERMINAL_KEY_CTRL & u;
+    c.alt = TERMINAL_KEY_ALT & u;
+    c.key = u & 0x1'FFFF'FFFF;
+    return c;
+}

+ 109 - 0
src/Test.cpp

@@ -0,0 +1,109 @@
+#include "core/Test.hpp"
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "core/Logger.hpp"
+#include "core/Utility.hpp"
+
+typedef struct {
+    char* file;
+    int tests;
+    int successTests;
+} Result;
+
+static Result* results = nullptr;
+static size_t resultsIndex = 0;
+static size_t resultsCapacity = 0;
+
+static Result* getResult(const char* file) {
+    for(size_t i = 0; i < resultsIndex; i++) {
+        Result* r = results + i;
+        if(strcmp(r->file, file) == 0) {
+            return r;
+        }
+    }
+    while(resultsIndex >= resultsCapacity) {
+        size_t newCapacity = resultsCapacity == 0 ? 8 : resultsCapacity * 2;
+        results = static_cast<Result*>(
+            Core::reallocateRaw(results, newCapacity * sizeof(Result)));
+        resultsCapacity = newCapacity;
+    }
+    Result* r = results + (resultsIndex++);
+    r->file = strdup(file);
+    r->successTests = 0;
+    r->tests = 0;
+    return r;
+}
+
+void Core::finalizeTests(void) {
+    for(size_t i = 0; i < resultsIndex; i++) {
+        Result* r = results + i;
+        bool c = r->successTests == r->tests;
+        fputs(c ? TERMINAL_GREEN : TERMINAL_RED, stdout);
+        printf(
+            "%s - %d / %d tests succeeded", r->file, r->successTests, r->tests);
+        puts(TERMINAL_RESET);
+        free(r->file);
+    }
+    Core::deallocateRaw(results);
+    results = nullptr;
+    resultsIndex = 0;
+    resultsCapacity = 0;
+}
+
+bool Core::addTestResult(const char* file, bool comparison) {
+    Result* r = getResult(file);
+    r->tests++;
+    if(comparison) {
+        r->successTests++;
+        return true;
+    }
+    return false;
+}
+
+#define TEST_SUCCESS(result)                \
+    file = getShortFileName(file);          \
+    if(Core::addTestResult(file, result)) { \
+        return true;                        \
+    }
+
+bool Core::testString(
+    const char* file, int line, const char* wanted, const char* actual) {
+    TEST_SUCCESS(strcmp(wanted, actual) == 0)
+    fputs(TERMINAL_RED, stdout);
+    printf("%s:%d - expected '%s' got '%s'", file, line, wanted, actual);
+    puts(TERMINAL_RESET);
+    return false;
+}
+
+bool Core::testFloat(
+    const char* file, int line, float wanted, float actual, float error) {
+    float diff = wanted - actual;
+    diff = diff < 0.0f ? -diff : diff;
+    TEST_SUCCESS(diff <= error)
+    fputs(TERMINAL_RED, stdout);
+    printf(
+        "%s:%d - expected '%.3f' got '%.3f'", file, line,
+        static_cast<double>(wanted), static_cast<double>(actual));
+    puts(TERMINAL_RESET);
+    return false;
+}
+
+bool Core::testNull(const char* file, int line, const void* actual) {
+    TEST_SUCCESS(actual == nullptr)
+    fputs(TERMINAL_RED, stdout);
+    printf("%s:%d - expected null", file, line);
+    puts(TERMINAL_RESET);
+    return false;
+}
+
+bool Core::testNotNull(const char* file, int line, const void* actual) {
+    TEST_SUCCESS(actual != nullptr)
+    fputs(TERMINAL_RED, stdout);
+    printf("%s:%d - expected valid pointer", file, line);
+    puts(TERMINAL_RESET);
+    return false;
+}

+ 62 - 35
src/Thread.cpp

@@ -1,57 +1,84 @@
-#include "core/thread/Thread.hpp"
+#include "core/Thread.hpp"
 
-#include <string.h>
+#include "ErrorSimulator.hpp"
+#include "core/Logger.hpp"
 
-static void reset(thrd_t& t) {
-    memset(&t, 0, sizeof(thrd_t));
+using Core::Mutex;
+using Core::MutexGuard;
+using Core::Thread;
+
+void Mutex::lock() noexcept {
+    try {
+        FAIL_STEP_THROW();
+        mutex.lock();
+    } catch(std::exception& e) {
+        REPORT(LogLevel::ERROR, "Could not lock mutex: #", e.what());
+    }
 }
 
-Core::Thread::Thread() : thread() {
-    reset(thread);
+void Mutex::unlock() noexcept {
+    try {
+        FAIL_STEP_THROW();
+        mutex.unlock();
+    } catch(std::exception& e) {
+        REPORT(LogLevel::ERROR, "Could not unlock mutex: #", e.what());
+    }
 }
 
-Core::Thread::Thread(Thread&& other) : thread() {
-    swap(other);
+MutexGuard::MutexGuard(Mutex& m) : mutex(m) {
+    mutex.lock();
 }
 
-static bool doesExist(thrd_t& t) {
-    thrd_t zero{};
-    return memcmp(&zero, &t, sizeof(thrd_t)) != 0;
+MutexGuard::~MutexGuard() {
+    mutex.unlock();
 }
 
-Core::Thread::~Thread() {
-    if(doesExist(thread)) {
-        (void)join(nullptr);
-    }
+Thread::Thread() : thread() {
 }
 
-Core::Thread& Core::Thread::operator=(Thread&& other) {
+Thread::Thread(Thread&& other) : thread() {
+    swap(other);
+}
+
+Thread::~Thread() {
+    join();
+}
+
+Thread& Thread::operator=(Thread&& other) {
     if(this != &other) {
-        if(doesExist(thread)) {
-            (void)join(nullptr);
-        }
-        reset(thread);
-        swap(other);
+        join();
+        thread = Core::move(other.thread);
     }
     return *this;
 }
 
-cbool Core::Thread::start(Function f, void* p) {
-    if(doesExist(thread)) {
-        return true;
-    }
-    return thrd_create(&thread, f, p) != thrd_success;
+void Core::Thread::swap(Thread& other) {
+    Core::swap(thread, other.thread);
 }
 
-cbool Core::Thread::join(int* returnValue) {
-    int e = thrd_join(thread, returnValue);
-    reset(thread);
-    return e != thrd_success;
+bool Thread::start(Function f, void* p) {
+    if(thread.joinable()) {
+        return true;
+    }
+    try {
+        FAIL_STEP_THROW();
+        thread = std::thread([f, p]() { f(p); });
+    } catch(std::exception& e) {
+        REPORT(LogLevel::ERROR, "Could not start thread: #", e.what());
+        return true;
+    }
+    return false;
 }
 
-void Core::Thread::swap(Thread& other) {
-    thrd_t tmp;
-    memcpy(&tmp, &thread, sizeof(thrd_t));
-    memcpy(&thread, &other.thread, sizeof(thrd_t));
-    memcpy(&other.thread, &tmp, sizeof(thrd_t));
+bool Thread::join() {
+    if(thread.joinable()) {
+        try {
+            FAIL_STEP_THROW();
+            thread.join();
+        } catch(std::exception& e) {
+            REPORT(LogLevel::ERROR, "Could not join thread: #", e.what());
+            return true;
+        }
+    }
+    return false;
 }

+ 64 - 0
src/ToString.cpp

@@ -0,0 +1,64 @@
+#include "core/ToString.hpp"
+
+#include <cstdarg>
+#include <cstdio>
+
+#define TO_STRING(type, format)                        \
+    size_t Core::toString(type v, char* s, size_t n) { \
+        int e = snprintf(s, n, format, v);             \
+        return e < 0 ? 0 : static_cast<size_t>(e);     \
+    }
+
+TO_STRING(char, "%c")
+TO_STRING(short, "%hd")
+TO_STRING(int, "%d")
+TO_STRING(long, "%ld")
+TO_STRING(long long, "%lld")
+TO_STRING(unsigned char, "%c")
+TO_STRING(unsigned short, "%hu")
+TO_STRING(unsigned int, "%u")
+TO_STRING(unsigned long, "%lu")
+TO_STRING(unsigned long long, "%llu")
+TO_STRING(double, "%.2f")
+TO_STRING(const char*, "%s")
+TO_STRING(const unsigned char*, "%s")
+
+size_t Core::toString(float v, char* s, size_t n) {
+    int e = snprintf(s, n, "%.2f", static_cast<double>(v));
+    return e < 0 ? 0 : static_cast<size_t>(e);
+}
+
+size_t Core::toString(char* v, char* s, size_t n) {
+    return toString(static_cast<const char*>(v), s, n);
+}
+
+size_t Core::toString(unsigned char* v, char* s, size_t n) {
+    return toString(static_cast<const unsigned char*>(v), s, n);
+}
+
+size_t Core::toString(bool v, char* s, size_t n) {
+    int e = snprintf(s, n, "%s", v ? "true" : "false");
+    return e < 0 ? 0 : static_cast<size_t>(e);
+}
+
+size_t Core::copyFormatUntil(const char*& format, char*& s, size_t& n) {
+    size_t w = 0;
+    while(*format != '\0') {
+        char u = *(format++);
+        if(u == '#') {
+            if(*format != '#') {
+                break;
+            }
+            format++;
+        }
+        if(n > 1) {
+            *(s++) = u;
+            n--;
+        }
+        w++;
+    }
+    if(n > 0) {
+        *s = '\0';
+    }
+    return w;
+}

+ 54 - 0
src/Unicode.cpp

@@ -0,0 +1,54 @@
+#include "core/Unicode.hpp"
+
+Core::UTF8 Core::convertUnicodeToUTF8(u32 c) {
+    UTF8 u = {};
+    if(c >= 0x10000) {
+        u.length = 4;
+        u.data[0] = 0b1111'0000 | ((c >> 18) & 0b0000'0111);
+        u.data[1] = 0b1000'0000 | ((c >> 12) & 0b0011'1111);
+        u.data[2] = 0b1000'0000 | ((c >> 6) & 0b0011'1111);
+        u.data[3] = 0b1000'0000 | (c & 0b0011'1111);
+    } else if(c >= 0x800) {
+        u.length = 3;
+        u.data[0] = 0b1110'0000 | ((c >> 12) & 0b0000'1111);
+        u.data[1] = 0b1000'0000 | ((c >> 6) & 0b0011'1111);
+        u.data[2] = 0b1000'0000 | (c & 0b0011'1111);
+    } else if(c >= 0x80) {
+        u.length = 2;
+        u.data[0] = 0b1100'0000 | ((c >> 6) & 0b0001'1111);
+        u.data[1] = 0b1000'0000 | (c & 0b0011'1111);
+    } else {
+        u.length = 1;
+        u.data[0] = c & 0b0111'1111;
+    }
+    return u;
+}
+
+u32 Core::convertUTF8toUnicode(UTF8 c) {
+    if(c.length == 4) {
+        return ((c.data[0] & 0b0000'0111u) << 18) |
+               ((c.data[1] & 0b0011'1111u) << 12) |
+               ((c.data[2] & 0b0011'1111u) << 6) | (c.data[3] & 0b0011'1111u);
+    } else if(c.length == 3) {
+        return ((c.data[0] & 0b0000'1111u) << 12) |
+               ((c.data[1] & 0b0011'1111u) << 6) | (c.data[2] & 0b0011'1111u);
+    } else if(c.length == 2) {
+        return ((c.data[0] & 0b0001'1111u) << 6) | (c.data[1] & 0b0011'1111u);
+    }
+    return c.data[0];
+}
+
+bool Core::isUTF8Remainder(u8 c) {
+    return (c & 0b1100'0000) == 0b1000'0000;
+}
+
+u32 Core::getUTF8Length(u8 c) {
+    if((c & 0b1111'1000) == 0b1111'0000) {
+        return 4;
+    } else if((c & 0b1111'0000) == 0b1110'0000) {
+        return 3;
+    } else if((c & 0b1110'0000) == 0b1100'0000) {
+        return 2;
+    }
+    return 1;
+}

+ 187 - 71
src/Utility.cpp

@@ -1,23 +1,21 @@
-#include "core/utils/Utility.hpp"
+#include "core/Utility.hpp"
 
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <ctime>
 
-#include "ErrorSimulator.hpp"
-#include "core/utils/Error.hpp"
-#include "core/utils/Logger.hpp"
+#include "core/Logger.hpp"
 
 static Core::ExitHandler exitHandler = nullptr;
 static void* exitData = nullptr;
 static Core::OutOfMemoryHandler outOfMemoryHandler = nullptr;
 static void* outOfMemoryData = nullptr;
 
-void Core::exitWithHandler(const char* file, int line, int value) {
+[[noreturn]] void Core::exitWithHandler(const char* file, int line, int value) {
     if(value != 0) {
-        printf("%sExit from %s:%d with value %d%s\n", Core::Logger::COLOR_RED,
-               Core::Logger::getFileName(file), line, value,
-               Core::Logger::COLOR_RESET);
+        file = getShortFileName(file);
+        LOG_ERROR("Exit from #:# with value #", file, line, value);
     }
     if(exitHandler != nullptr) {
         exitHandler(value, exitData);
@@ -30,91 +28,209 @@ void Core::setExitHandler(ExitHandler eh, void* data) {
     exitData = data;
 }
 
-#define CORE_TO_STRING(type, cast, format)                                     \
-    size_t Core::toString(type t, char* buffer, size_t size) {                 \
-        int w = snprintf(buffer, size, format, static_cast<cast>(t));          \
-        return w < 0 ? 0 : static_cast<size_t>(w);                             \
-    }
-
-CORE_TO_STRING(signed short, signed short, "%hd")
-CORE_TO_STRING(unsigned short, unsigned short, "%hu")
-CORE_TO_STRING(signed int, signed int, "%d")
-CORE_TO_STRING(unsigned int, unsigned int, "%u")
-CORE_TO_STRING(signed long, signed long, "%ld")
-CORE_TO_STRING(unsigned long, unsigned long, "%lu")
-CORE_TO_STRING(signed long long, signed long long, "%lld")
-CORE_TO_STRING(unsigned long long, unsigned long long, "%llu")
-CORE_TO_STRING(float, double, "%.2f")
-CORE_TO_STRING(double, double, "%.2lf")
-CORE_TO_STRING(long double, long double, "%.2Lf")
-
-void Core::print(int c) {
-    if(putchar(c) < 0) {
-        CORE_EXIT(ErrorCode::BLOCKED_STDOUT.code); // CoverageIgnore
-    }
-}
-
-void Core::print(const char* s) {
-    if(fputs(s, stdout) < 0) {
-        CORE_EXIT(ErrorCode::BLOCKED_STDOUT.code); // CoverageIgnore
-    }
-}
-
-void Core::printLine(const char* s) {
-    if(puts(s) < 0) {
-        CORE_EXIT(ErrorCode::BLOCKED_STDOUT.code); // CoverageIgnore
-    }
-}
-
 void Core::setOutOfMemoryHandler(OutOfMemoryHandler h, void* data) {
     outOfMemoryHandler = h;
     outOfMemoryData = data;
 }
 
-void* Core::allocate(size_t n) {
-    // deny too large allocations instantly
-    // this makes LTO happy
-    if(n >= 1024lu * 1024lu * 1024lu * 64lu) {
-        CORE_EXIT(ErrorCode::OUT_OF_MEMORY.code); // CoverageIgnore
+static void* exitOnNull(void* p, size_t n) {
+    if(p == nullptr) {
+        LOG_ERROR("Out of memory, requested '#' bytes", n);
+        EXIT(1);
     }
+    return p;
+}
+
+static void* realAllocate(size_t n) {
     void* p = malloc(n);
-#ifdef ERROR_SIMULATOR
-    if(CORE_ALLOC_FAIL && p != nullptr) {
-        free(p);
-        p = nullptr;
-    }
-#endif
     while(p == nullptr && outOfMemoryHandler != nullptr) {
         outOfMemoryHandler(outOfMemoryData);
         p = malloc(n);
     }
-    if(p == nullptr) {
-        CORE_EXIT(ErrorCode::OUT_OF_MEMORY.code); // CoverageIgnore
-    }
-    return p;
+    return exitOnNull(p, n);
 }
 
-void* Core::reallocate(void* oldP, size_t n) {
+static void* realReallocate(void* oldP, size_t n) {
     if(n <= 0) {
         free(oldP);
         return nullptr;
     }
     void* p = realloc(oldP, n);
-#ifdef ERROR_SIMULATOR
-    if(CORE_ALLOC_FAIL && p != nullptr) {
-        oldP = p;
-        p = nullptr;
-    }
-#endif
-    // this double check is to prevent the compiler from complaining
     if(p == nullptr) {
         while(p == nullptr && outOfMemoryHandler != nullptr) {
             outOfMemoryHandler(outOfMemoryData);
             p = realloc(oldP, n);
         }
     }
+    return exitOnNull(p, n);
+}
+
+static void realFree(void* p) {
+    free(p);
+}
+
+#ifdef CHECK_MEMORY
+static const u8 CANARY[16] = {0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF,
+                              0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF};
+
+struct MemoryInfo {
+    MemoryInfo* next;
+    MemoryInfo* previous;
+    size_t size;
+    int line;
+    char buffer[64 - 2 * sizeof(void*) - sizeof(size_t) - sizeof(int)];
+    char canary[sizeof(CANARY)];
+};
+
+static_assert(sizeof(MemoryInfo) == 80, "memory info has invalid size");
+static MemoryInfo* headMemoryInfo = nullptr;
+
+static void addMemoryInfo(MemoryInfo* i, const char* file, int line, size_t n) {
+    i->next = nullptr;
+    i->previous = nullptr;
+    i->size = n;
+    i->line = line;
+    snprintf(i->buffer, sizeof(i->buffer), "%s", Core::getShortFileName(file));
+    memcpy(i->canary, CANARY, sizeof(CANARY));
+    memcpy(
+        reinterpret_cast<char*>(i) + n - sizeof(CANARY), CANARY,
+        sizeof(CANARY));
+    if(headMemoryInfo == nullptr) {
+        headMemoryInfo = i;
+    } else {
+        headMemoryInfo->previous = i;
+        i->next = headMemoryInfo;
+        headMemoryInfo = i;
+    }
+}
+
+static void removeMemoryInfo(MemoryInfo* info) {
+    if(info->previous == nullptr) {
+        if(info->next == nullptr) {
+            headMemoryInfo = nullptr;
+        } else {
+            headMemoryInfo = info->next;
+            info->next->previous = nullptr;
+        }
+    } else {
+        if(info->next == nullptr) {
+            info->previous->next = nullptr;
+        } else {
+            info->previous->next = info->next;
+            info->next->previous = info->previous;
+        }
+    }
+}
+
+void* Core::debugAllocateRaw(const char* file, int line, size_t n) {
+    n += sizeof(MemoryInfo) + sizeof(CANARY);
+    void* p = realAllocate(n + sizeof(CANARY));
+    addMemoryInfo(static_cast<MemoryInfo*>(p), file, line, n);
+    return static_cast<char*>(p) + sizeof(MemoryInfo);
+}
+
+void* Core::debugZeroAllocateRaw(const char* file, int line, size_t n) {
+    void* p = debugAllocateRaw(file, line, n);
+    memset(p, 0, n);
+    return p;
+}
+
+void* Core::debugReallocateRaw(const char* file, int line, void* p, size_t n) {
+    if(n > 0) {
+        n += sizeof(MemoryInfo) + sizeof(CANARY);
+    }
+    void* rp = p;
+    if(rp != nullptr) {
+        rp = static_cast<char*>(rp) - sizeof(MemoryInfo);
+        removeMemoryInfo(static_cast<MemoryInfo*>(rp));
+    }
+    void* np = realReallocate(rp, n);
+    if(np == nullptr) {
+        return nullptr;
+    }
+    addMemoryInfo(static_cast<MemoryInfo*>(np), file, line, n);
+    return static_cast<char*>(np) + sizeof(MemoryInfo);
+}
+
+static bool checkCanary(void* p) {
+    return memcmp(p, CANARY, sizeof(CANARY)) != 0;
+}
+
+void Core::debugDeallocateRaw(void* p) {
     if(p == nullptr) {
-        CORE_EXIT(ErrorCode::OUT_OF_MEMORY.code); // CoverageIgnore
+        return;
+    }
+    void* w = static_cast<char*>(p) - sizeof(MemoryInfo);
+    MemoryInfo* rp = static_cast<MemoryInfo*>(w);
+    rp->buffer[sizeof(rp->buffer) - 1] = '\0'; // end might be broken
+    if(checkCanary(rp->canary)) {
+        LOG_ERROR("Free at #:# violated pre canary", rp->buffer, rp->line);
+        EXIT(1);
+    } else if(checkCanary(
+                  reinterpret_cast<char*>(rp) + rp->size - sizeof(CANARY))) {
+        LOG_ERROR("Free at #:# violated post canary", rp->buffer, rp->line);
+        EXIT(1);
     }
+    removeMemoryInfo(rp);
+    realFree(rp);
+}
+
+void Core::printMemoryReport() {
+    for(MemoryInfo* i = headMemoryInfo; i != nullptr; i = i->next) {
+        LOG_ERROR("#:# was not freed", i->buffer, i->line);
+    }
+}
+
+void* operator new(size_t count) {
+    return Core::debugAllocateRaw("unknown", -1, count);
+}
+
+void* operator new(size_t count, const char* file, int line) {
+    return Core::debugAllocateRaw(file, line, count);
+}
+
+void* operator new[](size_t count) {
+    return Core::debugAllocateRaw("unknown", -1, count);
+}
+
+void* operator new[](size_t count, const char* file, int line) {
+    return Core::debugAllocateRaw(file, line, count);
+}
+
+void operator delete(void* p) noexcept {
+    Core::debugDeallocateRaw(p);
+}
+
+void operator delete(void* p, size_t) noexcept {
+    Core::debugDeallocateRaw(p);
+}
+
+void operator delete[](void* p) noexcept {
+    Core::debugDeallocateRaw(p);
+}
+
+void operator delete[](void* p, size_t) noexcept {
+    Core::debugDeallocateRaw(p);
+}
+
+#else
+
+void* Core::allocateRaw(size_t n) {
+    return realAllocate(n);
+}
+
+void* Core::zeroAllocateRaw(size_t n) {
+    void* p = allocateRaw(n);
+    memset(p, 0, n);
     return p;
 }
+
+void* Core::reallocateRaw(void* p, size_t n) {
+    return realReallocate(p, n);
+}
+
+void Core::deallocateRaw(void* p) {
+    realFree(p);
+}
+
+#endif

+ 11 - 11
src/Vector.cpp

@@ -1,21 +1,21 @@
-#include "core/math/Vector.hpp"
+#include "core/Vector.hpp"
 
-template<>
-Core::Vector3& Core::Vector3::setAngles(float lengthAngle, float widthAngle) {
+void Core::setAngles(Vector3& v, float lengthAngle, float widthAngle) {
     float sWidth = 0.0f;
     float cWidth = 0.0f;
-    sincosf(Math::degreeToRadian(widthAngle), &sWidth, &cWidth);
+    sincosf(Core::degreeToRadian(widthAngle), &sWidth, &cWidth);
 
     float sLength = 0.0f;
     float cLength = 0.0f;
-    sincosf(Math::degreeToRadian(lengthAngle), &sLength, &cLength);
+    sincosf(Core::degreeToRadian(lengthAngle), &sLength, &cLength);
 
-    return *this = Vector3(cWidth * cLength, sWidth, -sLength * cWidth);
+    v[0] = cWidth * cLength;
+    v[1] = sWidth;
+    v[2] = -sLength * cWidth;
 }
 
-template<>
-Core::Vector3 Core::Vector3::cross(const Vector3& other) const {
-    return Vector3(values[1] * other.values[2] - values[2] * other.values[1],
-                   values[2] * other.values[0] - values[0] * other.values[2],
-                   values[0] * other.values[1] - values[1] * other.values[0]);
+Core::Vector3 Core::cross(const Vector3& a, const Vector3& b) {
+    return Vector3(
+        a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2],
+        a[0] * b[1] - a[1] * b[0]);
 }

+ 28 - 21
src/View.cpp

@@ -1,49 +1,56 @@
-#include "core/math/View.hpp"
+#include "core/View.hpp"
 
-void Core::View::updateDirections(float lengthAngle, float widthAngle) {
-    back.setAngles(lengthAngle, widthAngle);
-    right = back.cross(Vector3(0.0f, 1.0f, 0.0f));
+using Core::View;
+
+void View::updateDirections(float lengthAngle, float widthAngle) {
+    setAngles(front, lengthAngle, widthAngle);
+    right = cross(front, Vector3(0.0f, 1.0f, 0.0f));
     right.normalize();
-    up = right.cross(back);
+    up = cross(right, front);
     up.normalize();
-    back = -back;
+    left = -right;
+    back = -front;
+    down = -up;
 }
 
-void Core::View::updateDirections(const Quaternion& q) {
+void View::updateDirections(const Quaternion& q) {
     up = q * Vector3(0.0f, 1.0f, 0.0f);
     back = q * Vector3(-1.0f, 0.0f, 0.0f);
-    right = up.cross(back);
+    right = cross(up, back);
     right.normalize();
+    left = -right;
+    front = -back;
+    down = -up;
 }
 
-const Core::Matrix& Core::View::updateMatrix(const Vector3& pos) {
-    view.set(0, Vector4(right[0], right[1], right[2], right.dot(-pos)));
-    view.set(1, Vector4(up[0], up[1], up[2], up.dot(-pos)));
-    view.set(2, Vector4(back[0], back[1], back[2], back.dot(-pos)));
+const Core::Matrix& View::updateMatrix(const Vector3& pos) {
+    view.set(0, Vector4(right[0], right[1], right[2], -right.dot(pos)));
+    view.set(1, Vector4(up[0], up[1], up[2], -up.dot(pos)));
+    view.set(2, Vector4(back[0], back[1], back[2], -back.dot(pos)));
     view.set(3, Vector4(0.0f, 0.0f, 0.0f, 1.0f));
     return view;
 }
 
-Core::Vector3 Core::View::getUp() const {
+const Core::Vector3& Core::View::getUp() const {
     return up;
 }
 
-Core::Vector3 Core::View::getDown() const {
-    return -up;
+const Core::Vector3& Core::View::getDown() const {
+    return down;
 }
 
-Core::Vector3 Core::View::getLeft() const {
-    return -right;
+const Core::Vector3& Core::View::getLeft() const {
+    return left;
 }
 
-Core::Vector3 Core::View::getRight() const {
+const Core::Vector3& Core::View::getRight() const {
     return right;
 }
 
-Core::Vector3 Core::View::getFront() const {
-    return -back;
+const Core::Vector3& Core::View::getFront() const {
+    return front;
 }
 
-Core::Vector3 Core::View::getBack() const {
+const Core::Vector3& Core::View::getBack() const {
     return back;
 }

+ 0 - 189
tasks

@@ -1,189 +0,0 @@
-#!/bin/bash
-set -e
-clear
-cd $(dirname $0)
-
-compiler="g++"
-if [ -e compiler ]; then
-    compiler=$(cat compiler)
-    echo "compiling with $compiler"
-fi
-
-printHelpExit() {
-    echo "$0 clean           | remove build results"
-    echo "$0 build <type>    | build everything"
-    echo "$0 install         | move build results into the install folder"
-    echo "$0 test <type>     | run the tests"
-    echo "$0 valgrind <type> | run the tests with valgrind"
-    echo "$0 coverage        | generate code coverage"
-    echo "$0 performance     | run the performance tests"
-    echo "$0 final           | find classes / structs which are not final"
-    echo "$0 macro           | find macros without CORE" 
-    echo "$0 include         | find system includes" 
-    echo "$0 time            | check build time"
-    exit 0
-}
-
-task=$1
-if [ -z "$task" ]; then
-    printHelpExit
-fi
-
-# task vars
-build_debug=false
-build_release=false
-
-test_debug=false
-test_release=false
-
-valgrind=""
-performance=false
-time=false
-install=false
-coverage=false
-
-export CMAKE_EXPORT_COMPILE_COMMANDS=true
-
-# parsing
-if [ "$task" = "clean" ]; then
-    rm -rf build_debug build_release install
-elif [ "$task" = "build" ]; then
-    type=$2
-    if [ "$type" = "debug" ]; then
-        build_debug=true
-    elif [ "$type" = "release" ]; then
-        build_release=true
-    elif [ "$type" = "all" ]; then
-        build_debug=true
-        build_release=true
-    else
-        echo "Valid build types are: debug, release, all"
-        printHelpExit
-    fi
-elif [ "$task" = "install" ]; then
-    build_release=true
-    install=true
-elif [ "$task" = "coverage" ]; then
-    build_debug=true
-    test_debug=true
-    coverage=true
-elif [ "$task" = "test" ]; then
-    type=$2
-    if [ "$type" = "debug" ]; then
-        build_debug=true
-        test_debug=true
-    elif [ "$type" = "release" ]; then
-        build_release=true
-        test_release=true
-    elif [ "$type" = "all" ]; then
-        build_debug=true
-        test_debug=true
-        build_release=true
-        test_release=true
-    else
-        echo "Valid test types are: debug, release, all"
-        printHelpExit
-    fi
-elif [ "$task" = "valgrind" ]; then
-    type=$2
-    if [ "$type" = "debug" ]; then
-        build_debug=true
-        test_debug=true
-    elif [ "$type" = "release" ]; then
-        build_release=true
-        test_release=true
-    elif [ "$type" = "all" ]; then
-        build_debug=true
-        test_debug=true
-        build_release=true
-        test_release=true
-    else
-        echo "Valid valgrind types are: debug, release, all"
-        printHelpExit
-    fi
-    valgrind="valgrind"
-elif [ "$task" = "performance" ]; then
-    build_release=true
-    performance=true
-elif [ "$task" = "time" ]; then
-    build_release=true
-    time=true
-elif [ "$task" = "final" ]; then
-    grep -r " class" src include | grep -v -E 'final|enum|.git' || true
-    grep -r " struct" src include | grep -v -E 'final|enum|.git' || true
-    exit 0
-elif [ "$task" = "macro" ]; then
-    grep -r "#define" src include | grep -v " CORE" || true
-    exit 0
-elif [ "$task" = "include" ]; then
-    echo "System includes in header files:"
-    grep -r "#include <" src include | grep "\.hpp" || true
-    echo "-------------------------------------------------" 
-    echo "System includes in source files:"
-    grep -r "#include <" src include | grep "\.cpp" || true
-    exit 0
-else
-    echo "unknown task"
-    printHelpExit
-fi
-
-# task execution
-buildProfile() {
-    folder=$1
-    shift 1
-    if [ ! -e "$folder" ]; then 
-        cmake -B "$folder" -S . -G Ninja -DCMAKE_CXX_COMPILER=${compiler} -DCMAKE_INSTALL_PREFIX=./install $@
-    fi
-    ninja -C "$folder"
-}
-
-if $build_debug; then
-    buildProfile build_debug -DCMAKE_BUILD_TYPE=Debug
-fi
-if $build_release; then
-    buildProfile build_release -DCMAKE_BUILD_TYPE=Release
-fi
-if $install; then
-    ninja -C build_release install
-fi
-if $test_debug; then
-    cd build_debug
-    $valgrind ./test light $valgrind || true
-    cd ..
-fi
-if $test_release; then
-    cd build_release
-    $valgrind ./test light $valgrind || true
-    cd ..
-fi
-if $performance; then
-    cd build_release
-    ./performance
-    cd ..
-fi
-if $time; then
-    lines=$(cat build_release/.ninja_log | grep "^[0-9]")
-
-    startMillis=0
-    endMillis=0
-    name=""
-    i=0
-    output=""
-    for arg in $lines; do
-        if [ $i == 0 ]; then
-            startMillis=$arg
-        elif [ $i == 1 ]; then
-            endMillis=$arg
-        elif [ $i == 3 ]; then
-            name=$arg
-            diff=$(expr $endMillis - $startMillis)
-            output="${output}\n$diff $name"
-        fi
-        i=$(expr $(expr $i + 1) % 5) && true
-    done
-    printf "$output" | sort -n
-fi
-if $coverage; then
-    gcovr -r . build_debug -e test -e performance \
-        --exclude-lines-by-pattern ".*CoverageIgnore.*"
-fi

+ 82 - 49
test/Main.cpp

@@ -1,69 +1,102 @@
-#include <locale.h>
-#include <string.h>
+#include <clocale>
+#include <cstdio>
+#include <cstring>
 
-#include "../src/ErrorSimulator.hpp"
-#include "Test.hpp"
 #include "Tests.hpp"
-#include "core/utils/ArrayString.hpp"
-#include "core/utils/Utility.hpp"
+#include "core/Logger.hpp"
+#include "core/Test.hpp"
+#include "core/Utility.hpp"
+
+static void finalize() {
+    Core::finalizeTests();
+    Core::printMemoryReport();
+}
 
 static void onExit(int code, void* data) {
     unsigned int i = *static_cast<unsigned int*>(data);
-    Core::ArrayString<1024> s;
-    s.append("Hello from exit #: #");
-    s.format(code, i);
-    s.printLine();
-    Core::print('A');
-    Core::Test::finalize();
+    LOG_WARNING("Hello from exit #: #", code, i);
+    finalize();
 }
 
 int main(int argAmount, const char** args) {
+    if(argAmount >= 2 && strcmp(args[1], "help") == 0) {
+        puts("alloc");
+        puts("realloc");
+        puts("pre_canary");
+        puts("pre_canary_new");
+        puts("pre_canary_new_array");
+        puts("post_canary");
+        puts("test;ignore");
+        puts("terminal");
+        puts("light;testData/readLineTest");
+        return 0;
+    }
     setlocale(LC_ALL, "en_US.utf8");
     bool light = false;
     for(int i = 0; i < argAmount; i++) {
         if(strcmp(args[i], "light") == 0) {
             light = true;
+        } else if(strcmp(args[i], "alloc") == 0) {
+            testInvalidAllocate();
+        } else if(strcmp(args[i], "realloc") == 0) {
+            testInvalidReallocate();
+        } else if(strcmp(args[i], "pre_canary") == 0) {
+            testPreCanary();
+        } else if(strcmp(args[i], "pre_canary_new") == 0) {
+            testPreCanaryNew();
+        } else if(strcmp(args[i], "pre_canary_new_array") == 0) {
+            testPreCanaryNewArray();
+        } else if(strcmp(args[i], "post_canary") == 0) {
+            testPostCanary();
+        } else if(strcmp(args[i], "test") == 0) {
+            testTest();
+            return 0;
+        } else if(strcmp(args[i], "terminal") == 0) {
+            testTerminal(true);
+            finalize();
+            return 0;
+        } else if(strcmp(args[i], "iterminal") == 0) {
+            testInteractiveTerminal();
+            return 0;
         }
     }
-    Core::testArrayList(light);
-    Core::testArrayString();
-    Core::testArray();
-    Core::testBitArray();
-    Core::testBox();
-    Core::testBuffer(light);
-    Core::testBufferedValue();
-    Core::testClock(light);
-    Core::testColor();
-    Core::testComponents();
-    Core::testError();
-    Core::testFileReader();
-    Core::testFrustum();
-    Core::testHashedString();
-    Core::testHashMap(light);
-    Core::testLinkedList(light);
-    Core::testList(light);
-    Core::testMath();
-    Core::testMatrixStack(light);
-    Core::testMatrix();
-    Core::testNew();
-    Core::testPlane();
-    Core::testQuaternion();
-    Core::testRandom(light);
-    Core::testRingBuffer();
-    Core::testStack(light);
-    Core::testThread();
-    Core::testUniquePointer();
-    Core::testUtility();
-    Core::testVector();
-    Core::testView();
 
-    Core::Logger::level = Core::Logger::Level::WARNING;
-    CORE_LOG_DEBUG("You won't see this!");
-    Core::Logger::level = Core::Logger::Level::DEBUG;
+    testList(light);
+    testUniquePointer();
+    testVector();
+    testMath();
+    testClock(light);
+    testArray();
+    testBitArray();
+    testBox();
+    testBuffer(light);
+    testComponents();
+    testFile();
+    testFrustum();
+    testArrayList(light);
+    testHashMap(light);
+    testMatrix();
+    testColor();
+    testHashedString();
+    testPlane();
+    testQuaternion();
+    testThread();
+    testQueue();
+    testRandom(light);
+    if(light) {
+        testReadLine();
+    }
+    testTerminal(!light);
+    testUnicode();
+    testUtility();
+    testView();
+
+    Core::logLevel = Core::LogLevel::WARNING;
+    LOG_DEBUG("You won't see this!");
+    Core::logLevel = Core::LogLevel::DEBUG;
 
-    unsigned int data = 123456789;
+    unsigned int data = 123'456'789;
     Core::setExitHandler(onExit, &data);
 
-    CORE_EXIT(1);
-    return 0;
+    EXIT(1);
 }

+ 0 - 27
test/Test.cpp

@@ -1,27 +0,0 @@
-#include "Test.hpp"
-
-namespace Internal = Core::Test::Internal;
-
-Core::HashMap<Internal::FileName, Internal::Result> Internal::results;
-
-void Internal::warn(const char* file, int line, Error e) {
-    Logger::log("#:# | #", Logger::getFileName(file), line, e);
-}
-
-bool Internal::checkFloat(const char* file, int line, float wanted,
-                          float actual, float error) {
-    float diff = wanted - actual;
-    diff = diff < 0.0f ? -diff : diff;
-    return check(file, line, wanted, actual, diff <= error);
-}
-
-void Core::Test::finalize() {
-    for(const auto& e : Internal::results) {
-        const char* color = e.value.successTests == e.value.tests
-                                ? Logger::COLOR_GREEN
-                                : Logger::COLOR_RED;
-        Logger::log(color, "# - # / # tests succeeded", e.getKey(),
-                    e.value.successTests, e.value.tests);
-    }
-    Internal::results.clear();
-}

+ 0 - 94
test/Test.hpp

@@ -1,94 +0,0 @@
-#ifndef CORE_TEST_HPP
-#define CORE_TEST_HPP
-
-#include "core/data/HashMap.hpp"
-#include "core/math/Vector.hpp"
-#include "core/utils/HashedString.hpp"
-#include "core/utils/Logger.hpp"
-
-namespace Core::Test {
-    namespace Internal {
-        struct Result final {
-            int tests = 0;
-            int successTests = 0;
-        };
-        using FileName = HashedString<256>;
-        extern HashMap<FileName, Result> results;
-
-        void warn(const char* file, int line, Error e);
-
-        template<typename T>
-        bool check(const char* file, int line, const T& wanted, const T& actual,
-                   bool c) {
-            file = Logger::getFileName(file);
-            Error e = ErrorCode::NONE;
-            FileName fileName(file);
-            Result* result = results.search(fileName);
-            if(result == nullptr && !results.tryEmplace(result, fileName)) {
-                warn(file, line, e);
-                return false;
-            }
-            result->tests++;
-            if(c) {
-                result->successTests++;
-                return true;
-            }
-            Core::Logger::log(Core::Logger::COLOR_RED,
-                              "#:# - expected '#' got '#'", fileName, line,
-                              wanted, actual);
-            return false;
-        }
-
-        template<typename T>
-        bool checkEqual(const char* file, int line, const T& wanted,
-                        const T& actual) {
-            return check(file, line, wanted, actual, wanted == actual);
-        }
-
-        template<typename A, typename B>
-        bool checkString(const char* file, int line, const A& wanted,
-                         const B& actual) {
-            ArrayString<2048> a;
-            a.append(wanted);
-            ArrayString<2048> b;
-            b.append(actual);
-            return checkEqual(file, line, a, b);
-        }
-
-        bool checkFloat(const char* file, int line, float wanted, float actual,
-                        float error);
-
-        template<size_t N, typename T>
-        bool checkVector(const char* file, int line,
-                         const Core::Vector<N, T>& wanted,
-                         const Core::Vector<N, T>& actual, float error) {
-            bool result = true;
-            for(size_t i = 0; i < N; i++) {
-                result &= checkFloat(file, line, static_cast<float>(wanted[i]),
-                                     static_cast<float>(actual[i]), error);
-            }
-            return result;
-        }
-    }
-    void finalize();
-}
-
-#define CORE_TEST_EQUAL(wanted, actual)                                        \
-    Core::Test::Internal::checkEqual<Core::RemoveReference<decltype(actual)>>( \
-        __FILE__, __LINE__, wanted, actual)
-#define CORE_TEST_STRING(wanted, actual)                                       \
-    Core::Test::Internal::checkString(__FILE__, __LINE__, wanted, actual)
-#define CORE_TEST_FALSE(actual) CORE_TEST_EQUAL(false, actual)
-#define CORE_TEST_TRUE(actual) CORE_TEST_EQUAL(true, actual)
-#define CORE_TEST_ERROR(actual) CORE_TEST_EQUAL(Core::ErrorCode::NONE, actual)
-#define CORE_TEST_NULL(actual) CORE_TEST_EQUAL(true, actual == nullptr)
-// double check to make the null check clear for the compiler
-#define CORE_TEST_NOT_NULL(actual)                                             \
-    (CORE_TEST_EQUAL(true, (actual) != nullptr) && (actual) != nullptr)
-#define CORE_TEST_FLOAT(wanted, actual, error)                                 \
-    Core::Test::Internal::checkFloat(__FILE__, __LINE__, wanted, actual, error)
-#define CORE_TEST_VECTOR(wanted, actual)                                       \
-    Core::Test::Internal::checkVector(__FILE__, __LINE__, wanted, actual,      \
-                                      0.0001f)
-
-#endif

+ 44 - 35
test/Tests.hpp

@@ -1,40 +1,49 @@
 #ifndef CORE_TESTS_HPP
 #define CORE_TESTS_HPP
 
-#include "Test.hpp"
-
-namespace Core {
-    void testArrayList(bool light);
-    void testArrayString();
-    void testArray();
-    void testBitArray();
-    void testBox();
-    void testBuffer(bool light);
-    void testBufferedValue();
-    void testClock(bool light);
-    void testColor();
-    void testComponents();
-    void testError();
-    void testFileReader();
-    void testFrustum();
-    void testHashedString();
-    void testHashMap(bool light);
-    void testLinkedList(bool light);
-    void testList(bool light);
-    void testMath();
-    void testMatrixStack(bool light);
-    void testMatrix();
-    void testNew();
-    void testPlane();
-    void testQuaternion();
-    void testRandom(bool light);
-    void testRingBuffer();
-    void testStack(bool light);
-    void testThread();
-    void testUniquePointer();
-    void testUtility();
-    void testVector();
-    void testView();
-}
+[[noreturn]] void testInvalidAllocate();
+[[noreturn]] void testInvalidReallocate();
+[[noreturn]] void testPostCanary();
+[[noreturn]] void testPreCanary();
+[[noreturn]] void testPreCanaryNew();
+[[noreturn]] void testPreCanaryNewArray();
+void testArray();
+void testArrayList(bool light);
+void testArrayString();
+void testBitArray();
+void testBox();
+void testBuffer(bool light);
+void testBufferedValue();
+void testClock(bool light);
+void testColor();
+void testComponents();
+void testError();
+void testFile();
+void testFileReader();
+void testFrustum();
+void testHashMap(bool light);
+void testHashedString();
+void testInteractiveTerminal();
+void testLinkedList(bool light);
+void testList(bool light);
+void testMath();
+void testMatrix();
+void testMatrixStack(bool light);
+void testNew();
+void testPlane();
+void testQuaternion();
+void testQueue();
+void testRandom(bool light);
+void testReadLine();
+void testRingBuffer();
+void testStack(bool light);
+void testTerminal(bool tty);
+void testTest();
+void testThread();
+void testUnicode();
+void testUniquePointer();
+void testUtility();
+void testVector();
+void testView();
 
 #endif

+ 56 - 37
test/modules/ArrayListTests.cpp

@@ -1,5 +1,6 @@
 #include "../Tests.hpp"
-#include "core/data/ArrayList.hpp"
+#include "core/ArrayList.hpp"
+#include "core/Test.hpp"
 
 template class Core::ArrayList<size_t, 20>;
 using IntList = Core::ArrayList<size_t, 20>;
@@ -7,31 +8,34 @@ using IntList = Core::ArrayList<size_t, 20>;
 static void testAdd() {
     IntList list;
     list.add(5u);
-    CORE_TEST_EQUAL(5, list[0]);
-    CORE_TEST_EQUAL(1, list.getLength());
+    TEST(5, list[0]);
+    TEST(5, list.getLast());
+    const IntList& cList = list;
+    TEST(5, cList.getLast());
+    TEST(1, list.getLength());
 }
 
 static void testMultipleAdd() {
     IntList list;
     list.add(4u).add(3u).add(2u);
-    CORE_TEST_EQUAL(4, list[0]);
-    CORE_TEST_EQUAL(3, list[1]);
-    CORE_TEST_EQUAL(2, list[2]);
-    CORE_TEST_EQUAL(3, list.getLength());
+    TEST(4, list[0]);
+    TEST(3, list[1]);
+    TEST(2, list[2]);
+    TEST(3, list.getLength());
 }
 
 static void testAddReplace() {
     IntList list;
     list.add(5u);
     list[0] = 3;
-    CORE_TEST_EQUAL(3, list[0]);
+    TEST(3, list[0]);
 }
 
 static void testClear() {
     IntList list;
     list.add(5u).add(4u);
     list.clear();
-    CORE_TEST_EQUAL(0, list.getLength());
+    TEST(0, list.getLength());
 }
 
 static void testOverflow(bool light) {
@@ -39,12 +43,12 @@ static void testOverflow(bool light) {
     for(size_t i = 0; i < 20; i++) {
         list.add(i);
     }
-    size_t limit = light ? 1000 : 100000;
+    size_t limit = light ? 1000 : 100'000;
     for(size_t i = 0; i < limit; i++) {
         list.add(i);
     }
     for(size_t i = 0; i < list.getLength(); i++) {
-        CORE_TEST_EQUAL(i, list[i]);
+        TEST(i, list[i]);
     }
 }
 
@@ -52,9 +56,9 @@ static void testCopy() {
     IntList list;
     list.add(1u).add(2u).add(3u);
     IntList copy(list);
-    CORE_TEST_EQUAL(list.getLength(), copy.getLength());
+    TEST(list.getLength(), copy.getLength());
     for(size_t i = 0; i < copy.getLength() && i < list.getLength(); i++) {
-        CORE_TEST_EQUAL(list[i], copy[i]);
+        TEST(list[i], copy[i]);
     }
 }
 
@@ -63,9 +67,9 @@ static void testCopyAssignment() {
     list.add(1u).add(2u).add(3u);
     IntList copy;
     copy = list;
-    CORE_TEST_EQUAL(list.getLength(), copy.getLength());
+    TEST(list.getLength(), copy.getLength());
     for(size_t i = 0; i < copy.getLength() && i < list.getLength(); i++) {
-        CORE_TEST_EQUAL(list[i], copy[i]);
+        TEST(list[i], copy[i]);
     }
 }
 
@@ -73,11 +77,11 @@ static void testMove() {
     IntList list;
     list.add(1u).add(2u).add(3u);
     IntList move(Core::move(list));
-    CORE_TEST_EQUAL(0, list.getLength());
-    CORE_TEST_EQUAL(3, move.getLength());
-    CORE_TEST_EQUAL(1, move[0]);
-    CORE_TEST_EQUAL(2, move[1]);
-    CORE_TEST_EQUAL(3, move[2]);
+    TEST(0, list.getLength());
+    TEST(3, move.getLength());
+    TEST(1, move[0]);
+    TEST(2, move[1]);
+    TEST(3, move[2]);
 }
 
 static void testMoveAssignment() {
@@ -85,33 +89,47 @@ static void testMoveAssignment() {
     list.add(1u).add(2u).add(3u);
     IntList move;
     move = Core::move(list);
-    CORE_TEST_EQUAL(0, list.getLength());
-    CORE_TEST_EQUAL(3, move.getLength());
-    CORE_TEST_EQUAL(1, move[0]);
-    CORE_TEST_EQUAL(2, move[1]);
-    CORE_TEST_EQUAL(3, move[2]);
+    TEST(0, list.getLength());
+    TEST(3, move.getLength());
+    TEST(1, move[0]);
+    TEST(2, move[1]);
+    TEST(3, move[2]);
 }
 
 static void testToString() {
     IntList list;
     list.add(1u).add(243u).add(423u);
-    CORE_TEST_STRING("[1, 243, 423]", list);
-    CORE_TEST_STRING("[1]", IntList().add(1u));
-    CORE_TEST_STRING("[]", IntList());
+    TEST_STRING("[1, 243, 423]", list);
+    TEST_STRING("[1]", IntList().add(1u));
+    TEST_STRING("[]", IntList());
 }
 
-static void testRemove() {
+static void testRemoveBySwap() {
     IntList list;
     list.add(4u).add(3u).add(2u);
     list.removeBySwap(0);
-    CORE_TEST_EQUAL(2, list[0]);
-    CORE_TEST_EQUAL(3, list[1]);
-    CORE_TEST_EQUAL(2, list.getLength());
+    TEST(2, list[0]);
+    TEST(3, list[1]);
+    TEST(2, list.getLength());
     list.removeBySwap(1);
-    CORE_TEST_EQUAL(2, list[0]);
-    CORE_TEST_EQUAL(1, list.getLength());
+    TEST(2, list[0]);
+    TEST(1, list.getLength());
     list.removeBySwap(0);
-    CORE_TEST_EQUAL(0, list.getLength());
+    TEST(0, list.getLength());
+}
+
+static void testRemove() {
+    IntList list;
+    list.add(4u).add(3u).add(2u);
+    list.remove(0);
+    TEST(3, list[0]);
+    TEST(2, list[1]);
+    TEST(2, list.getLength());
+    list.remove(1);
+    TEST(3, list[0]);
+    TEST(1, list.getLength());
+    list.removeLast();
+    TEST(0, list.getLength());
 }
 
 static void testForRange() {
@@ -121,11 +139,11 @@ static void testForRange() {
         i++;
     }
     for(size_t i = 0; i < list.getLength(); i++) {
-        CORE_TEST_EQUAL(i + 2, list[i]);
+        TEST(i + 2, list[i]);
     }
 }
 
-void Core::testArrayList(bool light) {
+void testArrayList(bool light) {
     testAdd();
     testMultipleAdd();
     testAddReplace();
@@ -136,6 +154,7 @@ void Core::testArrayList(bool light) {
     testMove();
     testMoveAssignment();
     testToString();
+    testRemoveBySwap();
     testRemove();
     testForRange();
 }

+ 10 - 7
test/modules/ArrayTests.cpp

@@ -1,7 +1,8 @@
 #include "../Tests.hpp"
-#include "core/data/Array.hpp"
+#include "core/Array.hpp"
+#include "core/Test.hpp"
 
-template class Core::Array<size_t, 3>;
+template struct Core::Array<size_t, 3>;
 using TestArray = Core::Array<size_t, 3>;
 
 static void testToString1() {
@@ -9,13 +10,15 @@ static void testToString1() {
     a[0] = 1;
     a[1] = 243;
     a[2] = 423;
-    CORE_TEST_STRING("[1, 243, 423]", a);
+    TEST_STRING("[1, 243, 423]", a);
+    Core::Array<int, 3> b = {{1, 2, 3}};
+    TEST_STRING("[1, 2, 3]", b);
 }
 
 static void testToString2() {
     Core::Array<int, 1> a;
     a[0] = 1;
-    CORE_TEST_STRING("[1]", a);
+    TEST_STRING("[1]", a);
 }
 
 static void testReadConst() {
@@ -25,7 +28,7 @@ static void testReadConst() {
     }
     const TestArray& c = a;
     for(size_t i = 0; i < c.getLength(); i++) {
-        CORE_TEST_EQUAL(i, c[i]);
+        TEST(i, c[i]);
     }
 }
 
@@ -38,11 +41,11 @@ static void testRangeFor() {
         i++;
     }
     for(size_t i = 0; i < a.getLength(); i++) {
-        CORE_TEST_EQUAL(i + 1, a[i]);
+        TEST(i + 1, a[i]);
     }
 }
 
-void Core::testArray() {
+void testArray() {
     testToString1();
     testToString2();
     testReadConst();

+ 72 - 76
test/modules/BitArrayTests.cpp

@@ -1,157 +1,153 @@
-#include "../../src/ErrorSimulator.hpp"
 #include "../Tests.hpp"
-#include "core/data/BitArray.hpp"
+#include "core/BitArray.hpp"
+#include "core/Test.hpp"
 
 static void testSetRead() {
     Core::BitArray bits;
-    CORE_TEST_ERROR(bits.resize(4, 3));
+    bits.resize(4, 3);
     bits.set(0, 1).set(1, 2).set(2, 3).set(3, 4);
-    CORE_TEST_EQUAL(1, bits.get(0));
-    CORE_TEST_EQUAL(2, bits.get(1));
-    CORE_TEST_EQUAL(3, bits.get(2));
-    CORE_TEST_EQUAL(4, bits.get(3));
-}
-
-static void testOutOfBoundsSetRead() {
-    Core::BitArray bits;
-    bits.set(0, 1).set(1, 2).set(2, 3).set(3, 4);
-    CORE_TEST_EQUAL(0, bits.get(0));
-    CORE_TEST_EQUAL(0, bits.get(1));
-    CORE_TEST_EQUAL(0, bits.get(2));
-    CORE_TEST_EQUAL(0, bits.get(3));
+    TEST(1, bits.get(0));
+    TEST(2, bits.get(1));
+    TEST(3, bits.get(2));
+    TEST(4, bits.get(3));
+    Core::BitArray copy;
+    copy = bits;
+    TEST(1, copy.get(0));
+    TEST(2, copy.get(1));
+    TEST(3, copy.get(2));
+    TEST(4, copy.get(3));
+    Core::BitArray move = Core::move(copy);
+    TEST(1, move.get(0));
+    TEST(2, move.get(1));
+    TEST(3, move.get(2));
+    TEST(4, move.get(3));
 }
 
 static void testBigSetRead() {
     Core::BitArray bits;
-    CORE_TEST_ERROR(bits.resize(100, 13));
+    bits.resize(100, 13);
     for(size_t i = 0; i < bits.getLength(); i++) {
         bits.set(i, i);
     }
     for(size_t i = 0; i < bits.getLength(); i++) {
-        CORE_TEST_EQUAL(i, bits.get(i));
+        TEST(i, bits.get(i));
     }
 }
 
 static void testRandomSetReadResize() {
-    const int length = 100;
+    constexpr int length = 100;
     u64 data[length];
     Core::BitArray bits;
-    CORE_TEST_ERROR(bits.resize(100, 13));
+    bits.resize(100, 13);
     u64 seed = 534;
     for(int k = 0; k < 20; k++) {
         for(u64 i = 0; i < bits.getLength(); i++) {
-            seed = seed * 636455 + 53453;
-            bits.set(i, seed & (0x1FFF));
+            seed = seed * 636'455 + 53'453;
+            bits.set(i, seed);
             data[i] = seed & (0x1FFF);
         }
     }
     for(size_t i = 0; i < bits.getLength(); i++) {
-        CORE_TEST_EQUAL(data[i], bits.get(i));
+        TEST(data[i], bits.get(i));
     }
-    CORE_TEST_ERROR(bits.resize(bits.getLength(), bits.getBits() + 1));
-    CORE_TEST_EQUAL(14, bits.getBits());
-    CORE_TEST_EQUAL(100, bits.getLength());
+    bits.resize(bits.getLength(), bits.getBits() + 1);
+    TEST(14, bits.getBits());
+    TEST(100, bits.getLength());
     for(size_t i = 0; i < bits.getLength(); i++) {
-        CORE_TEST_EQUAL(data[i], bits.get(i));
+        TEST(data[i], bits.get(i));
     }
 }
 
 static void testReadOnly() {
     Core::BitArray bits;
-    CORE_TEST_ERROR(bits.resize(4, 3));
+    bits.resize(4, 3);
     bits.set(0, 1).set(1, 2).set(2, 3).set(3, 4);
     const Core::BitArray copy = bits;
-    CORE_TEST_EQUAL(1, copy.get(0));
-    CORE_TEST_EQUAL(2, copy.get(1));
-    CORE_TEST_EQUAL(3, copy.get(2));
-    CORE_TEST_EQUAL(4, copy.get(3));
+    TEST(1, copy.get(0));
+    TEST(2, copy.get(1));
+    TEST(3, copy.get(2));
+    TEST(4, copy.get(3));
 }
 
 static void testSelect() {
     Core::BitArray bits;
-    CORE_TEST_ERROR(bits.resize(90, 1));
+    bits.resize(90, 1);
     bits.fill(0);
     bits.set(0, 1).set(5, 1).set(20, 1).set(31, 1);
     bits.set(32, 1).set(33, 1).set(60, 1);
-    CORE_TEST_EQUAL(-1, bits.select(0));
-    CORE_TEST_EQUAL(0, bits.select(1));
-    CORE_TEST_EQUAL(5, bits.select(2));
-    CORE_TEST_EQUAL(20, bits.select(3));
-    CORE_TEST_EQUAL(31, bits.select(4));
-    CORE_TEST_EQUAL(32, bits.select(5));
-    CORE_TEST_EQUAL(33, bits.select(6));
-    CORE_TEST_EQUAL(60, bits.select(7));
-    CORE_TEST_EQUAL(-1, bits.select(8));
+    TEST(-1, bits.select(0));
+    TEST(0, bits.select(1));
+    TEST(5, bits.select(2));
+    TEST(20, bits.select(3));
+    TEST(31, bits.select(4));
+    TEST(32, bits.select(5));
+    TEST(33, bits.select(6));
+    TEST(60, bits.select(7));
+    TEST(-1, bits.select(8));
 }
 
 static void testToString1() {
     Core::BitArray bits;
-    CORE_TEST_ERROR(bits.resize(4, 3));
+    bits.resize(4, 3);
     bits.set(0, 1).set(1, 2).set(2, 3).set(3, 4);
-    CORE_TEST_STRING("[1, 2, 3, 4]", bits);
+    TEST_STRING("[1, 2, 3, 4]", bits);
 }
 
 static void testToString2() {
     Core::BitArray bits;
-    CORE_TEST_ERROR(bits.resize(1, 3));
+    bits.resize(1, 3);
     bits.set(0, 1);
-    CORE_TEST_STRING("[1]", bits);
+    TEST_STRING("[1]", bits);
 }
 
 static void testToString3() {
     Core::BitArray bits;
-    CORE_TEST_STRING("[]", bits);
+    TEST_STRING("[]", bits);
 }
 
 static void testResizeExact() {
     Core::BitArray bits;
-    CORE_TEST_EQUAL(0, bits.getInternalByteSize());
+    TEST(0, bits.getInternalByteSize());
     // the size in bytes matches the internal storage type
     size_t elements = sizeof(u64);
-    CORE_TEST_ERROR(bits.resize(elements, 8));
+    bits.resize(elements, 8);
     for(size_t i = 0; i < elements; i++) {
         bits.set(i, i);
     }
     for(size_t i = 0; i < elements; i++) {
-        CORE_TEST_EQUAL(i, bits.get(i));
-    }
-    CORE_TEST_EQUAL(sizeof(u64), bits.getInternalByteSize());
-}
-
-static void testMoveAssignment() {
-    Core::BitArray bits;
-    CORE_TEST_ERROR(bits.resize(8, 8));
-    for(size_t i = 0; i < bits.getLength(); i++) {
-        bits.set(i, i);
-    }
-    Core::BitArray m;
-    m = Core::move(bits);
-    CORE_TEST_EQUAL(8, m.getLength());
-    for(size_t i = 0; i < m.getLength(); i++) {
-        CORE_TEST_EQUAL(i, m.get(i));
+        TEST(i, bits.get(i));
     }
+    TEST(sizeof(u64), bits.getInternalByteSize());
 }
 
 static void testInvalidArgument() {
     Core::BitArray bits;
-    CORE_TEST_EQUAL(Core::ErrorCode::INVALID_ARGUMENT, bits.resize(0, 5));
-    CORE_TEST_EQUAL(Core::ErrorCode::INVALID_ARGUMENT, bits.resize(5, 0));
-    CORE_TEST_EQUAL(Core::ErrorCode::INVALID_ARGUMENT, bits.resize(0, 0));
-    CORE_TEST_EQUAL(Core::ErrorCode::INVALID_ARGUMENT, bits.resize(1, 65));
-    CORE_TEST_EQUAL(Core::ErrorCode::INVALID_ARGUMENT, bits.resize(5, 68));
+    bits.resize(0, 5);
+    TEST(0, bits.getLength());
+    TEST(0, bits.getBits());
+    bits.resize(5, 0);
+    TEST(0, bits.getLength());
+    TEST(0, bits.getBits());
+    bits.resize(0, 0);
+    TEST(0, bits.getLength());
+    TEST(0, bits.getBits());
+    bits.resize(1, 65);
+    TEST(1, bits.getLength());
+    TEST(64, bits.getBits());
+    bits.resize(5, 68);
+    TEST(5, bits.getLength());
+    TEST(64, bits.getBits());
 }
 
-void Core::testBitArray() {
-    testSetRead();
-    testOutOfBoundsSetRead();
+void testBitArray() {
     testBigSetRead();
+    testInvalidArgument();
     testRandomSetReadResize();
     testReadOnly();
+    testResizeExact();
     testSelect();
+    testSetRead();
     testToString1();
     testToString2();
     testToString3();
-    testResizeExact();
-    testMoveAssignment();
-    testInvalidArgument();
 }

+ 43 - 37
test/modules/BoxTests.cpp

@@ -1,57 +1,63 @@
 #include "../Tests.hpp"
-#include "core/math/Box.hpp"
+#include "core/Box.hpp"
+#include "core/Test.hpp"
 
-static void testInit() {
-    Core::Box box(Core::Vector3(1.0f, 2.0f, 3.0f));
-    CORE_TEST_STRING("Box([0.00, 0.00, 0.00], [1.00, 2.00, 3.00])", box);
-    CORE_TEST_STRING("[0.00, 0.00, 0.00]", box.getMin());
-    CORE_TEST_STRING("[1.00, 2.00, 3.00]", box.getMax());
+using Core::Box;
+using V3 = Core::Vector3;
 
-    box = Core::Box(Core::Vector3(-1.0f, -2.0f, -3.0f));
-    CORE_TEST_STRING("Box([-1.00, -2.00, -3.00], [0.00, 0.00, 0.00])", box);
-    CORE_TEST_STRING("[-1.00, -2.00, -3.00]", box.getMin());
-    CORE_TEST_STRING("[0.00, 0.00, 0.00]", box.getMax());
+static void testInit() {
+    Box box(V3(1.0f, 2.0f, 3.0f));
+    TEST_STRING("Box([0.00, 0.00, 0.00], [1.00, 2.00, 3.00])", box);
+    TEST_STRING("[0.00, 0.00, 0.00]", box.getMin());
+    TEST_STRING("[1.00, 2.00, 3.00]", box.getMax());
+    box = Box(V3(-1.0f, -2.0f, -3.0f));
+    TEST_STRING("Box([-1.00, -2.00, -3.00], [0.00, 0.00, 0.00])", box);
+    TEST_STRING("[-1.00, -2.00, -3.00]", box.getMin());
+    TEST_STRING("[0.00, 0.00, 0.00]", box.getMax());
 }
 
 static void testOffset() {
-    Core::Box box(Core::Vector3(1.0f, 2.0f, 3.0f));
-    CORE_TEST_STRING("Box([7.00, -4.00, 6.00], [8.00, -2.00, 9.00])",
-                     box.offset(Core::Vector3(7.0f, -4.0f, 6.0f)));
+    Box box(V3(1.0f, 2.0f, 3.0f));
+    box = box.offset(V3(7.0f, -4.0f, 6.0f));
+    TEST_STRING("Box([7.00, -4.00, 6.00], [8.00, -2.00, 9.00])", box);
 }
 
 static void testCollidesWith() {
-    Core::Box boxA(Core::Vector3(1.0f, 2.0f, 3.0f));
-    Core::Box boxB(Core::Vector3(-1.0f, -2.0f, -3.0f));
-    Core::Box boxC(Core::Vector3(2.0f, 2.0f, 2.0f));
-    boxC = boxC.offset(Core::Vector3(-1.0f, -1.0f, -1.0f));
-
-    CORE_TEST_TRUE(boxC.collidesWith(boxA));
-    CORE_TEST_TRUE(boxC.collidesWith(boxB));
-    CORE_TEST_TRUE(boxA.collidesWith(boxC));
-    CORE_TEST_TRUE(boxB.collidesWith(boxC));
-    CORE_TEST_FALSE(boxA.collidesWith(boxB));
-    CORE_TEST_FALSE(boxB.collidesWith(boxA));
+    Box boxA(V3(1.0f, 2.0f, 3.0f));
+    Box boxB(V3(-1.0f, -2.0f, -3.0f));
+    Box boxC(V3(2.0f, 2.0f, 2.0f));
+    boxC = boxC.offset(V3(-1.0f, -1.0f, -1.0f));
+    TEST_TRUE(boxC.collidesWith(boxA));
+    TEST_TRUE(boxC.collidesWith(boxB));
+    TEST_TRUE(boxA.collidesWith(boxC));
+    TEST_TRUE(boxB.collidesWith(boxC));
+    TEST_FALSE(boxA.collidesWith(boxB));
+    TEST_FALSE(boxB.collidesWith(boxA));
 }
 
 static void testExpand() {
-    Core::Box box(Core::Vector3(1.0f, 2.0f, 3.0f));
-    CORE_TEST_STRING("Box([0.00, -4.00, 0.00], [8.00, 2.00, 9.00])",
-                     box.expand(Core::Vector3(7.0f, -4.0f, 6.0f)));
-    CORE_TEST_STRING("Box([-7.00, 0.00, -6.00], [1.00, 6.00, 3.00])",
-                     box.expand(Core::Vector3(-7.0f, 4.0f, -6.0f)));
+    Box box(V3(1.0f, 2.0f, 3.0f));
+    box = box.expand(V3(7.0f, -4.0f, 6.0f));
+    TEST_STRING("Box([0.00, -4.00, 0.00], [8.00, 2.00, 9.00])", box);
+    box = Box(V3(1.0f, 2.0f, 3.0f));
+    box = box.expand(V3(-7.0f, 4.0f, -6.0f));
+    TEST_STRING("Box([-7.00, 0.00, -6.00], [1.00, 6.00, 3.00])", box);
 }
 
 static void testGrow() {
-    Core::Box box(Core::Vector3(1.0f, 2.0f, 3.0f));
-    CORE_TEST_STRING("Box([-2.00, -1.00, -3.00], [3.00, 3.00, 6.00])",
-                     box.grow(Core::Vector3(4.0f, 2.0f, 6.0f)));
-    CORE_TEST_STRING("Box([0.50, 1.00, 1.50], [0.50, 1.00, 1.50])",
-                     box.grow(Core::Vector3(-4.0f, -2.0f, -6.0f)));
-    CORE_TEST_STRING("Box([0.05, 1.00, 0.50], [0.95, 1.00, 2.50])",
-                     box.grow(Core::Vector3(-0.1f, -4.0f, -1.0f)));
+    Box box(V3(1.0f, 2.0f, 3.0f));
+    TEST_STRING(
+        "Box([-2.00, -1.00, -3.00], [3.00, 3.00, 6.00])",
+        box.grow(V3(4.0f, 2.0f, 6.0f)));
+    TEST_STRING(
+        "Box([0.50, 1.00, 1.50], [0.50, 1.00, 1.50])",
+        box.grow(V3(-4.0f, -2.0f, -6.0f)));
+    TEST_STRING(
+        "Box([0.05, 1.00, 0.50], [0.95, 1.00, 2.50])",
+        box.grow(V3(-0.1f, -4.0f, -1.0f)));
 }
 
-void Core::testBox() {
+void testBox() {
     testInit();
     testOffset();
     testCollidesWith();

Някои файлове не бяха показани, защото твърде много файлове са промени