Bladeren bron

Port to C++

Kajetan Johannes Hammerle 2 maanden geleden
bovenliggende
commit
4c6be8bc9f

+ 0 - 2
.gitignore

@@ -7,6 +7,4 @@ compiler
 .cache
 *.swp
 *.swo
-*.cpp
-*.hpp
 *.spv

+ 9 - 0
.gitmodules

@@ -0,0 +1,9 @@
+[submodule "stb"]
+	path = stb
+	url = https://github.com/nothings/stb.git
+[submodule "enet"]
+	path = enet
+	url = https://github.com/zpl-c/enet.git
+[submodule "glfw"]
+	path = glfw
+	url = https://github.com/glfw/glfw.git

+ 30 - 30
CMakeLists.txt

@@ -1,23 +1,23 @@
 cmake_minimum_required(VERSION 3.25)
-project(gaming_core C)
+project(gamingcore CXX)
 
 add_subdirectory(glfw SYSTEM)
 
-set(CMAKE_C_STANDARD 23)
+set(CMAKE_CXX_STANDARD 23)
 
 set(SRC
-    "src/Image.c"
-    "src/Network.c"
-    "src/VulkanUtils.c"
-    "src/VulkanWrapper.c"
-    "src/WindowManager.c"
+    "src/Image.cpp"
+    "src/Network.cpp"
+    "src/VulkanUtils.cpp"
+    "src/VulkanWrapper.cpp"
+    "src/WindowManager.cpp"
 )
 
 set(SRC_TESTS
-    "test/Main.c"
-    "test/modules/ImageTests.c"
-    "test/modules/NetworkTests.c"
-    "test/modules/WindowManagerTests.c"
+    "test/Main.cpp"
+    "test/modules/ImageTests.cpp"
+    "test/modules/NetworkTests.cpp"
+    "test/modules/WindowManagerTests.cpp"
 )
 
 if("${CMAKE_BUILD_TYPE}" STREQUAL "Release")
@@ -27,55 +27,55 @@ if("${CMAKE_BUILD_TYPE}" STREQUAL "Release")
     set(DEFINITIONS CHECK_MEMORY)
 else()
     set(DEFINITIONS ERROR_SIMULATOR CHECK_MEMORY DEBUG_VULKAN)
-    if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
+    if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
         set(COMPILE_OPTIONS --coverage)
         set(LINK_OPTIONS gcov)
-    elseif(CMAKE_C_COMPILER_ID STREQUAL "Clang")
+    elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
         set(COMPILE_OPTIONS -fprofile-instr-generate -fcoverage-mapping)
         set(LINK_OPTIONS ${COMPILE_OPTIONS})
     endif()
     set(LOG_LEVEL 4)
-    list(APPEND SRC "src/ErrorSimulator.c")
+    list(APPEND SRC "src/ErrorSimulator.cpp")
 endif()
 
-if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
+if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
     include("cmake/gcc_warnings.cmake")
-elseif(CMAKE_C_COMPILER_ID STREQUAL "Clang")
+elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
     include("cmake/clang_warnings.cmake")
 endif()
 
-add_library(gaming_core STATIC ${SRC})
-target_compile_options(gaming_core PUBLIC
+add_library(gamingcore STATIC ${SRC})
+target_compile_options(gamingcore PUBLIC
     ${COMPILE_OPTIONS}
     ${WARNINGS}
     -fdiagnostics-color=always
 )
-target_compile_definitions(gaming_core
+target_compile_definitions(gamingcore
     PUBLIC LOG_LEVEL=${LOG_LEVEL}
     PUBLIC ${DEFINITIONS}
 )
-target_link_libraries(gaming_core
-    PRIVATE m core glfw vulkan ${LINK_OPTIONS}
+target_link_libraries(gamingcore
+    PRIVATE core glfw vulkan ${LINK_OPTIONS}
 )
-target_include_directories(gaming_core SYSTEM
+target_include_directories(gamingcore SYSTEM
     PUBLIC ${CMAKE_INSTALL_PREFIX}/include
     PUBLIC enet/include
     PUBLIC stb
     PUBLIC glfw/include
 )
-target_link_directories(gaming_core
+target_link_directories(gamingcore
     PUBLIC ${CMAKE_INSTALL_PREFIX}/lib
 )
-target_sources(gaming_core PUBLIC
+target_sources(gamingcore PUBLIC
     FILE_SET HEADERS
     BASE_DIRS include
     FILES
-        ./include/core/Image.h
-        ./include/core/Network.h
-        ./include/core/VulkanWrapper.h
-        ./include/core/WindowManager.h
+        ./include/core/Image.hpp
+        ./include/core/Network.hpp
+        ./include/core/VulkanWrapper.hpp
+        ./include/core/WindowManager.hpp
 )
-install(TARGETS gaming_core FILE_SET HEADERS)
+install(TARGETS gamingcore FILE_SET HEADERS)
 
 include("cmake/add_shader.cmake")
 
@@ -83,4 +83,4 @@ add_shader(NAME vertex SOURCES shaders/vertex.vert.glsl)
 add_shader(NAME fragment SOURCES shaders/fragment.frag.glsl)
 
 add_executable(test ${SRC_TESTS})
-target_link_libraries(test PRIVATE gaming_core)
+target_link_libraries(test PRIVATE gamingcore)

+ 4 - 2
cmake/clang_warnings.cmake

@@ -24,7 +24,7 @@ set(WARNINGS
     -Wmissing-braces
     -Wmissing-declarations
     -Wmissing-include-dirs
-    -Wmissing-noreturn 
+    -Wmissing-noreturn
     -Wmissing-prototypes
     -Wmissing-variable-declarations
     -Wmultichar
@@ -45,10 +45,12 @@ set(WARNINGS
     -Wwrite-strings
     -pedantic
     -pedantic-errors
+
+    -Wno-missing-designated-field-initializers
 )
 
 if(0)
-    set(WARNINGS ${WARNINGS} 
+    set(WARNINGS ${WARNINGS}
         -Weverything
         -Wno-unsafe-buffer-usage
         -Wno-c++98-compat

+ 34 - 9
cmake/gcc_warnings.cmake

@@ -1,6 +1,5 @@
 set(WARNINGS
-    -Wno-attributes
-
+    -Waligned-new=all
     -Wall
     -Walloc-zero
     -Walloca
@@ -9,20 +8,29 @@ set(WARNINGS
     -Warray-bounds=2
     -Warray-parameter
     -Wattribute-alias=2
-    -Wbad-function-cast
     -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
@@ -32,38 +40,55 @@ set(WARNINGS
     -Wimplicit-fallthrough=5
     -Winfinite-recursion
     -Winit-self
+    -Winvalid-constexpr
+    -Winvalid-imported-macros
     -Winvalid-pch
-    -Wjump-misses-init
+    -Winvalid-utf8
     -Wlarger-than=1073741824
     -Wlogical-op
+    -Wmismatched-tags
     -Wmissing-braces
     -Wmissing-declarations
     -Wmissing-include-dirs
-    -Wmissing-prototypes
     -Wmultichar
-    -Wnarrowing
-    -Wnested-externs
+    -Wmultiple-inheritance
+    -Wnoexcept
+    -Wnon-virtual-dtor
     -Wnormalized=nfkc
     -Wnull-dereference
-    -Wold-style-definition
+    -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
-    -Wstrict-prototypes
     -Wstringop-overflow=4
+    -Wsuggest-final-methods
+    -Wsuggest-final-types
+    -Wsuggest-override
+    -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
+
+    -Wno-missing-field-initializers
 )

+ 1 - 0
enet

@@ -0,0 +1 @@
+Subproject commit 921e92075f33a86645f93077b3c6649d4d59baaa

+ 1 - 0
glfw

@@ -0,0 +1 @@
+Subproject commit e7ea71be039836da3a98cea55ae5569cb5eb885c

+ 0 - 25
include/core/Image.h

@@ -1,25 +0,0 @@
-#ifndef CORE_IMAGE_H
-#define CORE_IMAGE_H
-
-#include <core/Types.h>
-
-typedef struct {
-    u8* data;
-    int width;
-    int height;
-    int channels;
-} Image8;
-
-typedef struct {
-    u16* data;
-    int width;
-    int height;
-    int channels;
-} Image16;
-
-bool initImage8(Image8* image, const char* path);
-void destroyImage8(Image8* image);
-bool initImage16(Image16* image, const char* path);
-void destroyImage16(Image16* image);
-
-#endif

+ 27 - 0
include/core/Image.hpp

@@ -0,0 +1,27 @@
+#ifndef GAMINGCORE_IMAGE_HPP
+#define GAMINGCORE_IMAGE_HPP
+
+#include <core/List.hpp>
+#include <core/Types.hpp>
+
+namespace Core {
+    struct Image8 {
+        List<u8> data{};
+        int width = 0;
+        int height = 0;
+        int channels = 0;
+
+        bool read(const char* path);
+    };
+
+    struct Image16 {
+        List<u16> data{};
+        int width = 0;
+        int height = 0;
+        int channels = 0;
+
+        bool read(const char* path);
+    };
+}
+
+#endif

+ 0 - 83
include/core/Network.h

@@ -1,83 +0,0 @@
-#ifndef CORE_NETWORK_H
-#define CORE_NETWORK_H
-
-#include <core/Buffer.h>
-#include <core/Types.h>
-
-typedef enum {
-    PACKET_RELIABLE,
-    PACKET_SEQUENCED,
-    PACKET_UNSEQUENCED
-} PacketSendMode;
-
-typedef struct {
-    const char* data;
-    size_t size;
-    size_t index;
-} InPacket;
-
-void initInPacket(InPacket* in, const void* data, size_t n);
-bool readInPacketU8(InPacket* in, u8* u);
-bool readInPacketU16(InPacket* in, u16* u);
-bool readInPacketU32(InPacket* in, u32* u);
-bool readInPacketI8(InPacket* in, i8* i);
-bool readInPacketI16(InPacket* in, i16* i);
-bool readInPacketI32(InPacket* in, i32* i);
-bool readInPacketFloat(InPacket* in, float* f);
-size_t readInPacketString(InPacket* in, char* buffer, size_t n);
-bool readInPacket(InPacket* in, void* buffer, size_t n);
-
-typedef struct {
-    Buffer data;
-} OutPacket;
-
-void initOutPacket(OutPacket* out);
-void destroyOutPacket(OutPacket* out);
-void writeOutPacketU8(OutPacket* out, u8 u);
-void writeOutPacketU16(OutPacket* out, u16 u);
-void writeOutPacketU32(OutPacket* out, u32 u);
-void writeOutPacketI8(OutPacket* out, i8 i);
-void writeOutPacketI16(OutPacket* out, i16 i);
-void writeOutPacketI32(OutPacket* out, i32 i);
-void writeOutPacketFloat(OutPacket* out, float f);
-void writeOutPacketString(OutPacket* out, const char* buffer);
-void writeOutPacket(OutPacket* out, const void* buffer, size_t n);
-
-typedef u16 Port;
-typedef void (*OnServerConnect)(void);
-typedef void (*OnServerDisconnect)(void);
-typedef void (*OnServerPacket)(InPacket*);
-
-bool startClient(void);
-void stopClient(void);
-bool connectClient(const char* server, Port port, int timeoutTicks);
-void setClientTimeout(u32 timeout, u32 timeoutMin, u32 timeoutMax);
-void disconnectClient(int timeoutTicks);
-void sendClientPacket(const OutPacket* p, PacketSendMode mode);
-void tickClient(void);
-void setClientConnectHandler(OnServerConnect oc);
-void setClientDisconnectHandler(OnServerDisconnect od);
-void setClientPacketHandler(OnServerPacket op);
-void resetClientHandler(void);
-bool isClientConnecting(void);
-bool isClientConnected(void);
-
-typedef int Client;
-typedef void (*OnClientConnect)(Client);
-typedef void (*OnClientDisconnect)(Client);
-typedef void (*OnClientPacket)(Client, InPacket*);
-
-bool startServer(Port port, size_t maxClients);
-void stopServer(void);
-void tickServer(void);
-void sendServerPacketBroadcast(const OutPacket* p, PacketSendMode mode);
-void sendServerPacket(Client client, const OutPacket* p, PacketSendMode mode);
-void setServerTimeout(
-    Client client, u32 timeout, u32 timeoutMin, u32 timeoutMax);
-void disconnectServerClient(Client client);
-void setServerConnectHandler(OnClientConnect oc);
-void setServerDisconnectHandler(OnClientDisconnect od);
-void setServerPacketHandler(OnClientPacket op);
-void resetServerHandler(void);
-
-#endif

+ 107 - 0
include/core/Network.hpp

@@ -0,0 +1,107 @@
+#ifndef GAMINGCORE_NETWORK_HPP
+#define GAMINGCORE_NETWORK_HPP
+
+#include <core/Buffer.hpp>
+#include <core/Types.hpp>
+#include <core/UniquePointer.hpp>
+
+namespace Core {
+    enum class PacketSendMode { RELIABLE, SEQUENCED, UNSEQUENCED };
+
+    class InPacket {
+        const char* data;
+        size_t size;
+        size_t index;
+
+    public:
+        InPacket(const void* data, size_t n);
+        InPacket(const InPacket&) = delete;
+        InPacket(InPacket&&) = delete;
+        InPacket& operator=(const InPacket&) = delete;
+        InPacket& operator=(InPacket&&) = delete;
+
+        bool readU8(u8& u);
+        bool readU16(u16& u);
+        bool readU32(u32& u);
+        bool readI8(i8& i);
+        bool readI16(i16& i);
+        bool readI32(i32& i);
+        bool readFloat(float& f);
+        size_t readString(char* buffer, size_t n);
+        bool read(void* buffer, size_t n);
+    };
+
+    struct OutPacket {
+        Buffer data{};
+        void writeU8(u8 u);
+        void writeU16(u16 u);
+        void writeU32(u32 u);
+        void writeI8(i8 i);
+        void writeI16(i16 i);
+        void writeI32(i32 i);
+        void writeFloat(float f);
+        void writeString(const char* buffer);
+        void write(const void* buffer, size_t n);
+    };
+
+    using Port = u16;
+    using OnServerConnect = void (*)();
+    using OnServerDisconnect = void (*)();
+    using OnServerPacket = void (*)(InPacket&);
+
+    struct Client final {
+        Client();
+        ~Client();
+        bool start();
+        void stop();
+        bool connect(const char* server, Port port, int timeoutTicks);
+        void setTimeout(u32 timeout, u32 timeoutMin, u32 timeoutMax);
+        void disconnect(int timeoutTicks);
+        void sendPacket(const OutPacket& p, PacketSendMode mode);
+        void tick();
+        void setConnectHandler(OnServerConnect oc);
+        void setDisconnectHandler(OnServerDisconnect od);
+        void setPacketHandler(OnServerPacket op);
+        void resetHandler();
+        bool isConnecting();
+        bool isConnected();
+
+        struct Data;
+
+    private:
+        UniquePointer<Data> data;
+    };
+
+    struct Server;
+
+    using ClientHandle = int;
+    using OnClientConnect = void (*)(Server&, ClientHandle);
+    using OnClientDisconnect = void (*)(Server&, ClientHandle);
+    using OnClientPacket = void (*)(Server&, ClientHandle, InPacket&);
+
+    struct Server final {
+        Server();
+        ~Server();
+        bool start(Port port, size_t maxClients);
+        void stop();
+        void tick();
+        void sendPacketBroadcast(const OutPacket& p, PacketSendMode mode);
+        void sendPacket(
+            ClientHandle client, const OutPacket& p, PacketSendMode mode);
+        void setTimeout(
+            ClientHandle client, u32 timeout, u32 timeoutMin, u32 timeoutMax);
+        void disconnectClient(ClientHandle client);
+        void setConnectHandler(OnClientConnect oc);
+        void setDisconnectHandler(OnClientDisconnect od);
+        void setPacketHandler(OnClientPacket op);
+        void resetHandler();
+
+        struct Data;
+
+    private:
+        UniquePointer<Data> data;
+    };
+
+}
+
+#endif

+ 0 - 8
include/core/VulkanWrapper.h

@@ -1,8 +0,0 @@
-#ifndef CORE_VULKAN_WRAPPER_H
-#define CORE_VULKAN_WRAPPER_H
-
-bool initVulkan(void);
-void renderVulkan(void);
-void destroyVulkan(void);
-
-#endif

+ 10 - 0
include/core/VulkanWrapper.hpp

@@ -0,0 +1,10 @@
+#ifndef GAMINGCORE_VULKAN_WRAPPER_HPP
+#define GAMINGCORE_VULKAN_WRAPPER_HPP
+
+namespace Core::Vulkan {
+    bool init();
+    void render();
+    void destroy();
+}
+
+#endif

+ 0 - 63
include/core/WindowManager.h

@@ -1,63 +0,0 @@
-#ifndef CORE_WINDOW_MANAGER_H
-#define CORE_WINDOW_MANAGER_H
-
-#include <core/Vector.h>
-
-typedef bool (*WindowRunHandler)(void* data);
-typedef void (*WindowTickHandler)(void* data);
-typedef void (*WindowRenderHandler)(void* data, float lag);
-
-typedef struct {
-    IntVector2 size;
-    bool fullscreen;
-    const char* name;
-} WindowOptions;
-
-bool openWindow(const WindowOptions* options);
-void closeWindow(void);
-void showWindow(void);
-void trapWindowCursor(void);
-void freeWindowCursor(void);
-bool isWindowCursorTrapped(void);
-const IntVector2* getWindowSize(void);
-bool hasWindowSizeChanged(void);
-bool shouldWindowClose(void);
-
-void setWindowRunHandler(WindowRunHandler wr, void* data);
-void setWindowTickHandler(WindowTickHandler t, void* data);
-void setWindowRenderHandler(WindowRenderHandler r, void* data);
-void setWindowNanosPerTick(i64 nanos);
-void runWindow(void);
-float getWindowTicksPerSecond(void);
-float getWindowFramesPerSecond(void);
-
-void setInputLimit(size_t limit);
-void resetInput(void);
-void enableInput(void);
-void disableInput(void);
-bool isInputEnabled(void);
-void fillInput(const char* s);
-size_t getInputCursor(void);
-void setInputCursor(size_t index);
-const char* getInputString(void);
-
-typedef size_t Button;
-
-Button addButton(const char* name);
-void bindKeyToButton(Button b, int key);
-void bindGamepadToButton(Button b, int gamepadButton);
-void bindMouseToButton(Button b, int mouseButton);
-
-Vector2 getLastMousePosition(void);
-Vector2 getMousePosition(void);
-Vector2 getLeftGamepadAxis(void);
-Vector2 getRightGamepadAxis(void);
-float getLeftGamepadTrigger(void);
-float getRightGamepadTrigger(void);
-
-bool isButtonDown(Button b);
-int getButtonDownTime(Button b);
-bool wasButtonReleased(Button b);
-const char* getButtonName(Button b);
-
-#endif

+ 66 - 0
include/core/WindowManager.hpp

@@ -0,0 +1,66 @@
+#ifndef GAMINGCORE_WINDOW_MANAGER_HPP
+#define GAMINGCORE_WINDOW_MANAGER_HPP
+
+#include <core/Types.hpp>
+#include <core/Vector.hpp>
+
+namespace Core::Window {
+    using RunHandler = bool (*)(void* data);
+    using TickHandler = void (*)(void* data);
+    using RenderHandler = void (*)(void* data, float lag);
+
+    struct Options {
+        IntVector2 size{};
+        bool fullscreen = false;
+        const char* name = "Unknown";
+    };
+
+    bool open(const Options* options);
+    void close();
+    void show();
+    void trapCursor();
+    void freeCursor();
+    bool isCursorTrapped();
+    const IntVector2* getSize();
+    bool hasSizeChanged();
+    bool shouldClose();
+
+    void setRunHandler(RunHandler wr, void* data);
+    void setTickHandler(TickHandler t, void* data);
+    void setRenderHandler(RenderHandler r, void* data);
+    void setNanosPerTick(i64 nanos);
+    void run();
+    float getTicksPerSecond();
+    float getFramesPerSecond();
+
+    void setInputLimit(size_t limit);
+    void resetInput();
+    void enableInput();
+    void disableInput();
+    bool isInputEnabled();
+    void fillInput(const char* s);
+    size_t getInputCursor();
+    void setInputCursor(size_t index);
+    const char* getInputString();
+
+    using Button = size_t;
+
+    Button addButton(const char* name);
+    void bindKeyToButton(Button b, Button key);
+    void bindGamepadToButton(Button b, Button gamepadButton);
+    void bindMouseToButton(Button b, Button mouseButton);
+
+    Vector2 getLastMousePosition();
+    Vector2 getMousePosition();
+    Vector2 getLeftGamepadAxis();
+    Vector2 getRightGamepadAxis();
+    float getLeftGamepadTrigger();
+    float getRightGamepadTrigger();
+
+    bool isButtonDown(Button b);
+    int getButtonDownTime(Button b);
+    bool wasButtonReleased(Button b);
+    const char* getButtonName(Button b);
+}
+
+#endif

+ 1 - 1
src/ErrorSimulator.c → src/ErrorSimulator.cpp

@@ -1,6 +1,6 @@
 #ifdef ERROR_SIMULATOR
 
-#include "ErrorSimulator.h"
+#include "ErrorSimulator.hpp"
 
 int failCounter = -1;
 

+ 2 - 2
src/ErrorSimulator.h → src/ErrorSimulator.hpp

@@ -1,5 +1,5 @@
-#ifndef CORE_ERROR_SIMULATOR_H
-#define CORE_ERROR_SIMULATOR_H
+#ifndef GAMINGCORE_ERROR_SIMULATOR_HPP
+#define GAMINGCORE_ERROR_SIMULATOR_HPP
 
 #ifdef ERROR_SIMULATOR
 extern int failCounter;

+ 0 - 9
src/GLFW.h

@@ -1,9 +0,0 @@
-#ifndef CORE_GLFW_H
-#define CORE_GLFW_H
-
-#define GLFW_INCLUDE_VULKAN
-#include <GLFW/glfw3.h>
-
-GLFWwindow* getWindow(void);
-
-#endif

+ 11 - 0
src/GLFW.hpp

@@ -0,0 +1,11 @@
+#ifndef GAMINGCORE_GLFW_HPP
+#define GAMINGCORE_GLFW_HPP
+
+#define GLFW_INCLUDE_VULKAN
+#include <GLFW/glfw3.h>
+
+namespace Core::Window {
+    GLFWwindow* get();
+}
+
+#endif

+ 0 - 46
src/Image.c

@@ -1,46 +0,0 @@
-#include "core/Image.h"
-
-#define STB_IMAGE_IMPLEMENTATION
-#define STBI_ONLY_PNG
-#define STBI_MALLOC(n) coreAllocate(n)
-#define STBI_REALLOC(p, n) coreReallocate(p, n)
-#define STBI_FREE(p) coreFree(p)
-#include <core/Logger.h>
-#include <core/Utility.h>
-#include <errno.h>
-#include <stb_image.h>
-#include <string.h>
-
-static void printError(const char* path) {
-    LOG_ERROR("Cannot read image '%s': %s", path, strerror(errno));
-}
-
-bool initImage8(Image8* i, const char* path) {
-    *i = (Image8){0};
-    i->data = stbi_load(path, &i->width, &i->height, &i->channels, 0);
-    if(i->data == nullptr) {
-        printError(path);
-        return true;
-    }
-    return false;
-}
-
-void destroyImage8(Image8* i) {
-    stbi_image_free(i->data);
-    *i = (Image8){0};
-}
-
-bool initImage16(Image16* i, const char* path) {
-    *i = (Image16){0};
-    i->data = stbi_load_16(path, &i->width, &i->height, &i->channels, 0);
-    if(i->data == nullptr) {
-        printError(path);
-        return true;
-    }
-    return false;
-}
-
-void destroyImage16(Image16* i) {
-    stbi_image_free(i->data);
-    *i = (Image16){0};
-}

+ 51 - 0
src/Image.cpp

@@ -0,0 +1,51 @@
+#include "core/Image.hpp"
+
+#define STB_IMAGE_IMPLEMENTATION
+#define STBI_ONLY_PNG
+// #define STBI_MALLOC(n) Core::allocateRaw(n)
+// #define STBI_REALLOC(p, n) Core::reallocateRaw(p, n)
+// #define STBI_FREE(p) Core::deallocateRaw(p)
+#include <cerrno>
+#include <core/Logger.hpp>
+#include <core/Utility.hpp>
+#include <cstring>
+
+#include <stb_image.h>
+
+static void reportError(const char* path) {
+    REPORT(
+        Core::LogLevel::ERROR, "Cannot read image '#': #", path,
+        strerror(errno));
+}
+
+bool Core::Image8::read(const char* path) {
+    auto d = stbi_load(path, &width, &height, &channels, 0);
+    if(d == nullptr || width < 0 || height < 0 || channels < 0) {
+        reportError(path);
+        return true;
+    }
+    size_t bytes = static_cast<size_t>(width * height * channels);
+    data.resize(bytes);
+    memcpy(data.begin(), d, bytes);
+#ifdef CHECK_MEMORY
+    memset(d, 0, bytes); // canary check with lib allocated memory
+#endif
+    stbi_image_free(d);
+    return false;
+}
+
+bool Core::Image16::read(const char* path) {
+    auto d = stbi_load_16(path, &width, &height, &channels, 0);
+    if(d == nullptr || width < 0 || height < 0 || channels < 0) {
+        reportError(path);
+        return true;
+    }
+    size_t length = static_cast<size_t>(width * height * channels);
+    data.resize(length);
+    memcpy(data.begin(), d, length * 2);
+#ifdef CHECK_MEMORY
+    memset(d, 0, length * 2); // canary check with lib allocated memory
+#endif
+    stbi_image_free(d);
+    return false;
+}

+ 0 - 559
src/Network.c

@@ -1,559 +0,0 @@
-#include "core/Network.h"
-
-#define ENET_IMPLEMENTATION
-#include <assert.h>
-#include <core/HashMap.h>
-#include <core/Logger.h>
-#include <core/Utility.h>
-#include <enet.h>
-#include <string.h>
-
-#include "ErrorSimulator.h"
-
-// HashMap clients; // Client -> ENetPeer*
-HASHMAP(Client, ENetPeer*, Client)
-#define equalClient equalInt
-#define isInvalidKeyClient isInvalidKeyInt
-#define hashClient hashInt
-HASHMAP_SOURCE(Client, ENetPeer*, Client)
-
-void initInPacket(InPacket* in, const void* data, size_t n) {
-    in->data = data;
-    in->size = n;
-    in->index = 0;
-}
-
-bool readInPacketU8(InPacket* in, u8* u) {
-    return readInPacket(in, u, sizeof(*u));
-}
-
-bool readInPacketU16(InPacket* in, u16* u) {
-    if(readInPacket(in, u, sizeof(*u))) {
-        return true;
-    }
-    *u = ntohs(*u);
-    return false;
-}
-
-bool readInPacketU32(InPacket* in, u32* u) {
-    if(readInPacket(in, u, sizeof(*u))) {
-        return true;
-    }
-    *u = ntohl(*u);
-    return false;
-}
-
-bool readInPacketI8(InPacket* in, i8* i) {
-    u8 u;
-    if(readInPacketU8(in, &u)) {
-        return true;
-    }
-    *i = (i8)((i32)u - (i32)128);
-    return false;
-}
-
-bool readInPacketI16(InPacket* in, i16* i) {
-    u16 u;
-    if(readInPacketU16(in, &u)) {
-        return true;
-    }
-    *i = (i16)((i32)u - (i32)32'768);
-    return false;
-}
-
-bool readInPacketI32(InPacket* in, i32* i) {
-    u32 u;
-    if(readInPacketU32(in, &u)) {
-        return true;
-    }
-    if(u < 2'147'483'648) {
-        *i = (i32)((i32)u - (i32)2'147'483'648);
-    } else {
-        *i = (i32)(u - (u32)2'147'483'648);
-    }
-    return false;
-}
-
-bool readInPacketFloat(InPacket* in, float* f) {
-    u32 u;
-    static_assert(sizeof(u) == sizeof(*f), "float and u32 size do not match");
-    if(readInPacketU32(in, &u)) {
-        return true;
-    }
-    memcpy(f, &u, sizeof(float));
-    return false;
-}
-
-size_t readInPacketString(InPacket* in, char* buffer, size_t n) {
-    if(n == 0) {
-        return 0;
-    }
-    u16 size;
-    if(readInPacketU16(in, &size)) {
-        return 0;
-    }
-    size_t end = size;
-    char* bufferStart = buffer;
-    n--;
-    while(n-- > 0 && end > 0) {
-        end--;
-        u8 u;
-        if(readInPacketU8(in, &u)) {
-            *bufferStart = '\0';
-            break;
-        }
-        *(buffer++) = (char)u;
-    }
-    while(end-- > 0 && !readInPacketU8(in, &(u8){0})) {}
-    *buffer = '\0';
-    return size;
-}
-
-bool readInPacket(InPacket* in, void* buffer, size_t n) {
-    if(in->index + n > in->size) {
-        return true;
-    }
-    memcpy(buffer, in->data + in->index, n);
-    in->index += n;
-    return false;
-}
-
-void initOutPacket(OutPacket* out) {
-    initBuffer(&out->data);
-}
-
-void destroyOutPacket(OutPacket* out) {
-    destroyBuffer(&out->data);
-}
-
-void writeOutPacketU8(OutPacket* out, u8 u) {
-    addSizedBufferData(&out->data, &u, sizeof(u));
-}
-
-void writeOutPacketU16(OutPacket* out, u16 u) {
-    u = htons(u);
-    addSizedBufferData(&out->data, &u, sizeof(u));
-}
-
-void writeOutPacketU32(OutPacket* out, u32 u) {
-    u = htonl(u);
-    addSizedBufferData(&out->data, &u, sizeof(u));
-}
-
-void writeOutPacketI8(OutPacket* out, i8 i) {
-    writeOutPacketU8(out, (u8)((i32)i + (i32)128));
-}
-
-void writeOutPacketI16(OutPacket* out, i16 i) {
-    writeOutPacketU16(out, (u16)((i32)i + (i32)32'768));
-}
-
-void writeOutPacketI32(OutPacket* out, i32 i) {
-    if(i < 0) {
-        writeOutPacketU32(out, (u32)((i + (i32)2'147'483'647) + (i32)1));
-    } else {
-        writeOutPacketU32(out, (u32)((u32)i + (u32)2'147'483'648));
-    }
-}
-
-void writeOutPacketFloat(OutPacket* out, float f) {
-    u32 u;
-    static_assert(sizeof(u) == sizeof(f), "float and u32 size do not match");
-    memcpy(&u, &f, sizeof(float));
-    writeOutPacketU32(out, u);
-}
-
-void writeOutPacketString(OutPacket* out, const char* s) {
-    size_t marker = out->data.size;
-    writeOutPacketU16(out, 0);
-    size_t end = 0;
-    while(end < 65'534 && *s != '\0') {
-        writeOutPacketU8(out, (u8)(*(s++)));
-        end++;
-    }
-    writeOutPacketU8(out, 0);
-    end++;
-    size_t endMarker = out->data.size;
-    out->data.size = marker;
-    writeOutPacketU16(out, (u16)end);
-    out->data.size = endMarker;
-}
-
-void writeOutPacket(OutPacket* out, const void* buffer, size_t n) {
-    addSizedBufferData(&out->data, buffer, n);
-}
-
-static int enetCounter = 0;
-
-static bool addENet(void) {
-    if(enetCounter == 0 && FAIL(enet_initialize() != 0, true)) {
-        return true;
-    }
-    enetCounter++;
-    return false;
-}
-
-static void removeENet(void) {
-    if(enetCounter > 0 && --enetCounter == 0) {
-        enet_deinitialize();
-    }
-}
-
-static_assert(sizeof(enet_uint16) == sizeof(Port), "port has wrong type");
-
-static void voidVoidDummy(void) {
-}
-
-static void voidInPacketDummy(InPacket*) {
-}
-
-typedef struct {
-    ENetHost* client;
-    ENetPeer* connection;
-    OnServerConnect onConnect;
-    OnServerDisconnect onDisconnect;
-    OnServerPacket onPacket;
-    int connectTicks;
-    int connectTimeoutTicks;
-    int disconnectTicks;
-    int disconnectTimeoutTicks;
-} ClientData;
-
-static ClientData client = {
-    nullptr, nullptr, voidVoidDummy, voidVoidDummy, voidInPacketDummy, 0, 0,
-    0,       0};
-
-bool startClient(void) {
-    if(client.client != nullptr) {
-        LOG_WARNING("Client already started");
-        return true;
-    } else if(addENet()) {
-        LOG_ERROR("Client cannot initialize enet");
-        return true;
-    }
-    client.client = FAIL(enet_host_create(nullptr, 1, 2, 0, 0), nullptr);
-    if(client.client == nullptr) {
-        stopClient();
-        LOG_ERROR("Cannot create enet client host");
-        return true;
-    }
-    return false;
-}
-
-void stopClient(void) {
-    if(client.connection != nullptr) {
-        client.onDisconnect();
-        FAIL(
-            enet_peer_disconnect_now(client.connection, 0),
-            enet_peer_reset(client.connection));
-        client.connection = nullptr;
-    }
-    if(client.client != nullptr) {
-        enet_host_destroy(client.client);
-        client.client = nullptr;
-    }
-    removeENet();
-    client.connectTicks = 0;
-    client.disconnectTicks = 0;
-}
-
-bool connectClient(const char* server, Port port, int timeoutTicks) {
-    if(client.client == nullptr) {
-        LOG_WARNING("Client not started");
-        return true;
-    } else if(client.connection != nullptr) {
-        LOG_WARNING("Connection already exists");
-        return true;
-    }
-
-    ENetAddress address = {0};
-    enet_address_set_host(&address, server);
-    address.port = port;
-
-    client.connection =
-        FAIL(enet_host_connect(client.client, &address, 3, 0), nullptr);
-    if(client.connection == nullptr) {
-        LOG_ERROR("Cannot create connection");
-        return true;
-    }
-    client.connectTicks = 1;
-    client.connectTimeoutTicks = timeoutTicks;
-    return false;
-}
-
-void setClientTimeout(u32 timeout, u32 timeoutMin, u32 timeoutMax) {
-    if(client.connection != nullptr) {
-        enet_peer_timeout(client.connection, timeout, timeoutMin, timeoutMax);
-    }
-}
-
-void disconnectClient(int timeoutTicks) {
-    if(client.connection == nullptr) {
-        return;
-    }
-    client.connectTicks = 0;
-    enet_peer_disconnect(client.connection, 0);
-    client.disconnectTicks = 1;
-    client.disconnectTimeoutTicks = timeoutTicks;
-}
-
-void sendClientPacket(const OutPacket* p, PacketSendMode mode) {
-    if(client.client == nullptr || client.connection == nullptr ||
-       client.connectTicks >= 0) {
-        return;
-    }
-    static const enet_uint32 flags[] = {
-        ENET_PACKET_FLAG_RELIABLE, 0, ENET_PACKET_FLAG_UNSEQUENCED};
-    enet_uint8 i = (enet_uint8)mode;
-    enet_peer_send(
-        client.connection, i,
-        enet_packet_create(p->data.buffer, p->data.size, flags[i]));
-}
-
-static void tickClientEvents(void) {
-    ENetEvent e;
-    while(enet_host_service(client.client, &e, 0) >= 0) {
-        switch(e.type) {
-            case ENET_EVENT_TYPE_CONNECT:
-                client.connectTicks = -1;
-                client.onConnect();
-                break;
-            case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
-            case ENET_EVENT_TYPE_DISCONNECT:
-                client.disconnectTicks = 0;
-                client.connectTicks = 0;
-                client.onDisconnect();
-                client.connection = nullptr;
-                break;
-            case ENET_EVENT_TYPE_RECEIVE: {
-                InPacket in;
-                initInPacket(&in, e.packet->data, e.packet->dataLength);
-                client.onPacket(&in);
-                enet_packet_destroy(e.packet);
-                break;
-            }
-            case ENET_EVENT_TYPE_NONE: return;
-        }
-    }
-}
-
-void tickClient(void) {
-    if(client.client == nullptr) {
-        return;
-    }
-    tickClientEvents();
-    if(client.connectTicks >= 1 &&
-       ++client.connectTicks > client.connectTimeoutTicks) {
-        client.connectTicks = 0;
-        disconnectClient(client.connectTimeoutTicks);
-    }
-    if(client.disconnectTicks >= 1 &&
-       ++client.disconnectTicks > client.disconnectTimeoutTicks) {
-        client.disconnectTicks = 0;
-        client.onDisconnect();
-        if(client.connection != nullptr) {
-            enet_peer_reset(client.connection);
-            client.connection = nullptr;
-        }
-    }
-}
-
-void setClientConnectHandler(OnServerConnect oc) {
-    client.onConnect = oc == nullptr ? voidVoidDummy : oc;
-}
-
-void setClientDisconnectHandler(OnServerDisconnect od) {
-    client.onDisconnect = od == nullptr ? voidVoidDummy : od;
-}
-
-void setClientPacketHandler(OnServerPacket op) {
-    client.onPacket = op == nullptr ? voidInPacketDummy : op;
-}
-
-void resetClientHandler(void) {
-    client.onConnect = voidVoidDummy;
-    client.onDisconnect = voidVoidDummy;
-    client.onPacket = voidInPacketDummy;
-}
-
-bool isClientConnecting(void) {
-    return client.connectTicks >= 1;
-}
-
-bool isClientConnected(void) {
-    return client.connectTicks < 0;
-}
-
-static void voidClientDummy(Client) {
-}
-
-static void voidClientInPacketDummy(Client, InPacket*) {
-}
-
-typedef struct {
-    ENetHost* server;
-    HashMapClient clients;
-    Client idCounter;
-    OnClientConnect onConnect;
-    OnClientDisconnect onDisconnect;
-    OnClientPacket onPacket;
-} ServerData;
-
-static ServerData server = {
-    nullptr, {0}, 1, voidClientDummy, voidClientDummy, voidClientInPacketDummy};
-
-bool startServer(Port port, size_t maxClients) {
-    if(maxClients <= 0) {
-        LOG_ERROR("Invalid max client amount");
-        return true;
-    } else if(server.server != nullptr) {
-        LOG_WARNING("Server already started");
-        return true;
-    } else if(addENet()) {
-        LOG_ERROR("Server cannot initialize enet");
-        return true;
-    }
-
-    ENetAddress address = {.host = ENET_HOST_ANY, .port = port};
-    server.server =
-        FAIL(enet_host_create(&address, maxClients, 3, 0, 0), nullptr);
-    if(server.server == nullptr) {
-        stopServer();
-        LOG_ERROR("Cannot create enet server host");
-        return true;
-    }
-    initHashMapClient(&server.clients);
-    return false;
-}
-
-void stopServer(void) {
-    if(server.server != nullptr) {
-        HashMapIteratorClient i;
-        initHashMapIteratorClient(&i, &server.clients);
-        while(hasNextHashMapNodeClient(&i)) {
-            HashMapNodeClient* n = nextHashMapNodeClient(&i);
-            enet_peer_reset(*n->value);
-        }
-        enet_host_destroy(server.server);
-        server.server = nullptr;
-        destroyHashMapClient(&server.clients);
-    }
-    removeENet();
-}
-
-static void writeId(ENetPeer* peer, Client id) {
-    static_assert(
-        sizeof(peer->data) >= sizeof(id), "private data not big enough for id");
-    memcpy(&(peer->data), &id, sizeof(id));
-}
-
-static Client getId(ENetPeer* peer) {
-    assert(peer->data != nullptr);
-    Client id = -1;
-    memcpy(&id, &(peer->data), sizeof(id));
-    return id;
-}
-
-static void handleConnect(ENetEvent* e) {
-    Client id = server.idCounter++;
-    assert(searchHashMapKeyClient(&server.clients, id) == nullptr);
-    *putHashMapKeyClient(&server.clients, id) = e->peer;
-    writeId(e->peer, id);
-    server.onConnect(id);
-}
-
-static void handlePacket(ENetEvent* e) {
-    Client id = getId(e->peer);
-    InPacket in;
-    initInPacket(&in, e->packet->data, e->packet->dataLength);
-    server.onPacket(id, &in);
-}
-
-static void handleDisconnect(ENetEvent* e) {
-    Client id = getId(e->peer);
-    server.onDisconnect(id);
-    removeHashMapKeyClient(&server.clients, id);
-}
-
-void tickServer(void) {
-    if(server.server == nullptr) {
-        return;
-    }
-    ENetEvent e;
-    while(enet_host_service(server.server, &e, 0) >= 0) {
-        switch(e.type) {
-            case ENET_EVENT_TYPE_CONNECT: handleConnect(&e); break;
-            case ENET_EVENT_TYPE_RECEIVE:
-                handlePacket(&e);
-                enet_packet_destroy(e.packet);
-                break;
-            case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
-            case ENET_EVENT_TYPE_DISCONNECT: handleDisconnect(&e); break;
-            case ENET_EVENT_TYPE_NONE: return;
-        }
-    }
-}
-
-static ENetPacket* fromBuffer(const Buffer* buffer, enet_uint8 index) {
-    static const enet_uint32 flags[] = {
-        ENET_PACKET_FLAG_RELIABLE, 0, ENET_PACKET_FLAG_UNSEQUENCED};
-    return enet_packet_create(buffer->buffer, buffer->size, flags[index]);
-}
-
-void sendServerPacketBroadcast(const OutPacket* p, PacketSendMode mode) {
-    if(server.server != nullptr) {
-        enet_uint8 index = (enet_uint8)mode;
-        enet_host_broadcast(server.server, index, fromBuffer(&p->data, index));
-    }
-}
-
-void sendServerPacket(
-    Client clientId, const OutPacket* p, PacketSendMode mode) {
-    if(server.server == nullptr) {
-        return;
-    }
-    ENetPeer** peer = searchHashMapKeyClient(&server.clients, clientId);
-    if(peer != nullptr) {
-        enet_uint8 index = (enet_uint8)mode;
-        enet_peer_send(*peer, index, fromBuffer(&p->data, index));
-    }
-}
-
-void setServerTimeout(
-    Client clientId, u32 timeout, u32 timeoutMin, u32 timeoutMax) {
-    if(server.server == nullptr) {
-        return;
-    }
-    ENetPeer** peer = searchHashMapKeyClient(&server.clients, clientId);
-    if(peer != nullptr) {
-        enet_peer_timeout(*peer, timeout, timeoutMin, timeoutMax);
-    }
-}
-
-void disconnectServerClient(Client clientId) {
-    if(server.server == nullptr) {
-        return;
-    }
-    ENetPeer** peer = searchHashMapKeyClient(&server.clients, clientId);
-    if(peer != nullptr) {
-        enet_peer_disconnect(*peer, 0);
-    }
-}
-
-void setServerConnectHandler(OnClientConnect oc) {
-    server.onConnect = oc == nullptr ? voidClientDummy : oc;
-}
-
-void setServerDisconnectHandler(OnClientDisconnect od) {
-    server.onDisconnect = od == nullptr ? voidClientDummy : od;
-}
-
-void setServerPacketHandler(OnClientPacket op) {
-    server.onPacket = op == nullptr ? voidClientInPacketDummy : op;
-}
-
-void resetServerHandler(void) {
-    server.onConnect = voidClientDummy;
-    server.onDisconnect = voidClientDummy;
-    server.onPacket = voidClientInPacketDummy;
-}

+ 535 - 0
src/Network.cpp

@@ -0,0 +1,535 @@
+#include "core/Network.hpp"
+
+#define ENET_IMPLEMENTATION
+#include <cassert>
+#include <core/HashMap.hpp>
+#include <core/Logger.hpp>
+#include <core/Utility.hpp>
+#include <cstring>
+
+#include <enet.h>
+
+#include "ErrorSimulator.hpp"
+
+using Core::Client;
+using Core::InPacket;
+using Core::OutPacket;
+using Core::Server;
+
+InPacket::InPacket(const void* d, size_t n) :
+    data(static_cast<const char*>(d)), size(n), index(0) {
+}
+
+bool InPacket::readU8(u8& u) {
+    return read(&u, sizeof(u));
+}
+
+bool InPacket::readU16(u16& u) {
+    if(read(&u, sizeof(u))) {
+        return true;
+    }
+    u = ntohs(u);
+    return false;
+}
+
+bool InPacket::readU32(u32& u) {
+    if(read(&u, sizeof(u))) {
+        return true;
+    }
+    u = ntohl(u);
+    return false;
+}
+
+bool InPacket::readI8(i8& i) {
+    u8 u;
+    if(readU8(u)) {
+        return true;
+    }
+    i = static_cast<i8>(static_cast<i32>(u) - static_cast<i32>(128));
+    return false;
+}
+
+bool InPacket::readI16(i16& i) {
+    u16 u;
+    if(readU16(u)) {
+        return true;
+    }
+    i = static_cast<i16>(static_cast<i32>(u) - static_cast<i32>(32'768));
+    return false;
+}
+
+bool InPacket::readI32(i32& i) {
+    u32 u;
+    if(readU32(u)) {
+        return true;
+    }
+    i = static_cast<i32>(
+        static_cast<i64>(u) - static_cast<i64>(2'147'483'648ll));
+    return false;
+}
+
+bool InPacket::readFloat(float& f) {
+    u32 u;
+    static_assert(sizeof(u) == sizeof(f), "float and u32 size do not match");
+    if(readU32(u)) {
+        return true;
+    }
+    memcpy(&f, &u, sizeof(float));
+    return false;
+}
+
+size_t InPacket::readString(char* buffer, size_t n) {
+    if(n == 0) {
+        return 0;
+    }
+    u16 l;
+    if(readU16(l)) {
+        return 0;
+    }
+    size_t end = l;
+    char* bufferStart = buffer;
+    n--;
+    while(n-- > 0 && end > 0) {
+        end--;
+        u8 u;
+        if(readU8(u)) {
+            *bufferStart = '\0';
+            break;
+        }
+        *(buffer++) = static_cast<char>(u);
+    }
+    u8 dummy;
+    while(end-- > 0 && !readU8(dummy)) {}
+    *buffer = '\0';
+    return l;
+}
+
+bool InPacket::read(void* buffer, size_t n) {
+    if(index + n > size) {
+        return true;
+    }
+    memcpy(buffer, data + index, n);
+    index += n;
+    return false;
+}
+
+void OutPacket::writeU8(u8 u) {
+    data.add(u);
+}
+
+void OutPacket::writeU16(u16 u) {
+    u = htons(u);
+    data.add(u);
+}
+
+void OutPacket::writeU32(u32 u) {
+    u = htonl(u);
+    data.add(u);
+}
+
+void OutPacket::writeI8(i8 i) {
+    writeU8(static_cast<u8>(static_cast<i32>(i) + static_cast<i32>(128)));
+}
+
+void OutPacket::writeI16(i16 i) {
+    writeU16(static_cast<u16>(static_cast<i32>(i) + static_cast<i32>(32'768)));
+}
+
+void OutPacket::writeI32(i32 i) {
+    writeU32(static_cast<u32>(
+        static_cast<i64>(i) + static_cast<i64>(2'147'483'648)));
+}
+
+void OutPacket::writeFloat(float f) {
+    u32 u;
+    static_assert(sizeof(u) == sizeof(f), "float and u32 size do not match");
+    memcpy(&u, &f, sizeof(float));
+    writeU32(u);
+}
+
+void OutPacket::writeString(const char* s) {
+    size_t l = Core::min(strlen(s), 65'534);
+    writeU16(static_cast<u16>(l + 1));
+    write(s, l);
+    writeU8(0);
+}
+
+void OutPacket::write(const void* buffer, size_t n) {
+    data.add(buffer, n);
+}
+
+static int enetCounter = 0;
+
+static bool addENet() {
+    if(enetCounter == 0 && FAIL(enet_initialize() != 0, true)) {
+        return true;
+    }
+    enetCounter++;
+    return false;
+}
+
+static void removeENet() {
+    if(enetCounter > 0 && --enetCounter == 0) {
+        enet_deinitialize();
+    }
+}
+
+static_assert(sizeof(enet_uint16) == sizeof(Core::Port), "port has wrong type");
+
+static void voidVoidDummy() {
+}
+
+static void voidInPacketDummy(InPacket&) {
+}
+
+struct Client::Data {
+    ENetHost* client = nullptr;
+    ENetPeer* connection = nullptr;
+    Core::OnServerConnect onConnect = voidVoidDummy;
+    Core::OnServerDisconnect onDisconnect = voidVoidDummy;
+    Core::OnServerPacket onPacket = voidInPacketDummy;
+    int connectTicks = 0;
+    int connectTimeoutTicks = 0;
+    int disconnectTicks = 0;
+    int disconnectTimeoutTicks = 0;
+};
+
+Client::Client() : data(new Data()) {
+}
+
+Client::~Client() {
+    stop();
+}
+
+bool Client::start() {
+    if(data->client != nullptr) {
+        REPORT(LogLevel::WARNING, "Client already started");
+        return true;
+    } else if(addENet()) {
+        REPORT(LogLevel::ERROR, "Client cannot initialize enet");
+        return true;
+    }
+    data->client = FAIL(enet_host_create(nullptr, 1, 2, 0, 0), nullptr);
+    if(data->client == nullptr) {
+        stop();
+        REPORT(LogLevel::ERROR, "Cannot create enet client host");
+        return true;
+    }
+    return false;
+}
+
+void Client::stop() {
+    if(data->connection != nullptr) {
+        data->onDisconnect();
+        FAIL(
+            enet_peer_disconnect_now(data->connection, 0),
+            enet_peer_reset(data->connection));
+        data->connection = nullptr;
+    }
+    if(data->client != nullptr) {
+        enet_host_destroy(data->client);
+        data->client = nullptr;
+    }
+    removeENet();
+    data->connectTicks = 0;
+    data->disconnectTicks = 0;
+}
+
+bool Client::connect(const char* server, Port port, int timeoutTicks) {
+    if(data->client == nullptr) {
+        REPORT(LogLevel::WARNING, "Client not started");
+        return true;
+    } else if(data->connection != nullptr) {
+        REPORT(LogLevel::WARNING, "Connection already exists");
+        return true;
+    }
+
+    ENetAddress address = {};
+    enet_address_set_host(&address, server);
+    address.port = port;
+
+    data->connection =
+        FAIL(enet_host_connect(data->client, &address, 3, 0), nullptr);
+    if(data->connection == nullptr) {
+        REPORT(LogLevel::ERROR, "Cannot create connection");
+        return true;
+    }
+    data->connectTicks = 1;
+    data->connectTimeoutTicks = timeoutTicks;
+    return false;
+}
+
+void Client::setTimeout(u32 timeout, u32 timeoutMin, u32 timeoutMax) {
+    if(data->connection != nullptr) {
+        enet_peer_timeout(data->connection, timeout, timeoutMin, timeoutMax);
+    }
+}
+
+void Client::disconnect(int timeoutTicks) {
+    if(data->connection == nullptr) {
+        return;
+    }
+    data->connectTicks = 0;
+    enet_peer_disconnect(data->connection, 0);
+    data->disconnectTicks = 1;
+    data->disconnectTimeoutTicks = timeoutTicks;
+}
+
+static ENetPacket* fromBuffer(const Core::Buffer& b, enet_uint8 index) {
+    static const enet_uint32 flags[] = {
+        ENET_PACKET_FLAG_RELIABLE, 0, ENET_PACKET_FLAG_UNSEQUENCED};
+    return enet_packet_create(b.getData(), b.getLength(), flags[index]);
+}
+
+void Client::sendPacket(const OutPacket& p, PacketSendMode mode) {
+    if(data->client == nullptr || data->connection == nullptr ||
+       data->connectTicks >= 0) {
+        return;
+    }
+    enet_uint8 i = static_cast<enet_uint8>(mode);
+    enet_peer_send(data->connection, i, fromBuffer(p.data, i));
+}
+
+static void tickClientEvents(Client::Data& d) {
+    ENetEvent e;
+    while(enet_host_service(d.client, &e, 0) >= 0) {
+        switch(e.type) {
+            case ENET_EVENT_TYPE_CONNECT:
+                d.connectTicks = -1;
+                d.onConnect();
+                break;
+            case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
+            case ENET_EVENT_TYPE_DISCONNECT:
+                d.disconnectTicks = 0;
+                d.connectTicks = 0;
+                d.onDisconnect();
+                d.connection = nullptr;
+                break;
+            case ENET_EVENT_TYPE_RECEIVE: {
+                InPacket in(e.packet->data, e.packet->dataLength);
+                d.onPacket(in);
+                enet_packet_destroy(e.packet);
+                break;
+            }
+            case ENET_EVENT_TYPE_NONE: return;
+        }
+    }
+}
+
+void Client::tick() {
+    if(data->client == nullptr) {
+        return;
+    }
+    tickClientEvents(*data);
+    if(data->connectTicks >= 1 &&
+       ++data->connectTicks > data->connectTimeoutTicks) {
+        data->connectTicks = 0;
+        disconnect(data->connectTimeoutTicks);
+    }
+    if(data->disconnectTicks >= 1 &&
+       ++data->disconnectTicks > data->disconnectTimeoutTicks) {
+        data->disconnectTicks = 0;
+        data->onDisconnect();
+        if(data->connection != nullptr) {
+            enet_peer_reset(data->connection);
+            data->connection = nullptr;
+        }
+    }
+}
+
+void Client::setConnectHandler(OnServerConnect oc) {
+    data->onConnect = oc == nullptr ? voidVoidDummy : oc;
+}
+
+void Client::setDisconnectHandler(OnServerDisconnect od) {
+    data->onDisconnect = od == nullptr ? voidVoidDummy : od;
+}
+
+void Client::setPacketHandler(OnServerPacket op) {
+    data->onPacket = op == nullptr ? voidInPacketDummy : op;
+}
+
+void Client::resetHandler() {
+    data->onConnect = voidVoidDummy;
+    data->onDisconnect = voidVoidDummy;
+    data->onPacket = voidInPacketDummy;
+}
+
+bool Client::isConnecting() {
+    return data->connectTicks >= 1;
+}
+
+bool Client::isConnected() {
+    return data->connectTicks < 0;
+}
+
+static void voidClientDummy(Server&, Core::ClientHandle) {
+}
+
+static void voidClientInPacketDummy(Server&, Core::ClientHandle, InPacket&) {
+}
+
+struct Server::Data {
+    ENetHost* server = nullptr;
+    Core::HashMap<Core::ClientHandle, ENetPeer*> clients{};
+    Core::ClientHandle idCounter = 1;
+    Core::OnClientConnect onConnect = voidClientDummy;
+    Core::OnClientDisconnect onDisconnect = voidClientDummy;
+    Core::OnClientPacket onPacket = voidClientInPacketDummy;
+};
+
+Server::Server() : data(new Data()) {
+}
+
+Server::~Server() {
+    stop();
+}
+
+bool Server::start(Port port, size_t maxClients) {
+    if(maxClients <= 0) {
+        REPORT(LogLevel::ERROR, "Invalid max client amount");
+        return true;
+    } else if(data->server != nullptr) {
+        REPORT(LogLevel::WARNING, "Server already started");
+        return true;
+    } else if(addENet()) {
+        REPORT(LogLevel::ERROR, "Server cannot initialize enet");
+        return true;
+    }
+
+    ENetAddress address = {.host = ENET_HOST_ANY, .port = port};
+    data->server =
+        FAIL(enet_host_create(&address, maxClients, 3, 0, 0), nullptr);
+    if(data->server == nullptr) {
+        stop();
+        REPORT(LogLevel::ERROR, "Cannot create enet server host");
+        return true;
+    }
+    return false;
+}
+
+void Server::stop() {
+    if(data->server != nullptr) {
+        for(auto e : data->clients) {
+            enet_peer_reset(e.value);
+        }
+        enet_host_destroy(data->server);
+        data->server = nullptr;
+        data->clients.clear();
+    }
+    removeENet();
+}
+
+static void writeId(ENetPeer* peer, Core::ClientHandle id) {
+    static_assert(
+        sizeof(peer->data) >= sizeof(id), "private data not big enough for id");
+    memcpy(&(peer->data), &id, sizeof(id));
+}
+
+static Core::ClientHandle getId(ENetPeer* peer) {
+    assert(peer->data != nullptr);
+    Core::ClientHandle id = -1;
+    memcpy(&id, &(peer->data), sizeof(id));
+    return id;
+}
+
+static void handleConnect(Server& s, Server::Data& d, ENetEvent* e) {
+    Core::ClientHandle id = d.idCounter++;
+    assert(!d.clients.contains(id));
+    d.clients.add(id, e->peer);
+    writeId(e->peer, id);
+    d.onConnect(s, id);
+}
+
+static void handlePacket(Server& s, Server::Data& d, ENetEvent* e) {
+    Core::ClientHandle id = getId(e->peer);
+    InPacket in(e->packet->data, e->packet->dataLength);
+    d.onPacket(s, id, in);
+}
+
+static void handleDisconnect(Server& s, Server::Data& d, ENetEvent* e) {
+    Core::ClientHandle id = getId(e->peer);
+    d.onDisconnect(s, id);
+    d.clients.remove(id);
+}
+
+void Server::tick() {
+    if(data->server == nullptr) {
+        return;
+    }
+    ENetEvent e;
+    while(enet_host_service(data->server, &e, 0) >= 0) {
+        switch(e.type) {
+            case ENET_EVENT_TYPE_CONNECT:
+                handleConnect(*this, *data, &e);
+                break;
+            case ENET_EVENT_TYPE_RECEIVE:
+                handlePacket(*this, *data, &e);
+                enet_packet_destroy(e.packet);
+                break;
+            case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
+            case ENET_EVENT_TYPE_DISCONNECT:
+                handleDisconnect(*this, *data, &e);
+                break;
+            case ENET_EVENT_TYPE_NONE: return;
+        }
+    }
+}
+
+void Server::sendPacketBroadcast(const OutPacket& p, PacketSendMode mode) {
+    if(data->server != nullptr) {
+        enet_uint8 i = static_cast<enet_uint8>(mode);
+        enet_host_broadcast(data->server, i, fromBuffer(p.data, i));
+    }
+}
+
+void Server::sendPacket(
+    ClientHandle clientId, const OutPacket& p, PacketSendMode mode) {
+    if(data->server == nullptr) {
+        return;
+    }
+    ENetPeer** peer = data->clients.search(clientId);
+    if(peer != nullptr) {
+        enet_uint8 i = static_cast<enet_uint8>(mode);
+        enet_peer_send(*peer, i, fromBuffer(p.data, i));
+    }
+}
+
+void Server::setTimeout(
+    ClientHandle clientId, u32 timeout, u32 timeoutMin, u32 timeoutMax) {
+    if(data->server == nullptr) {
+        return;
+    }
+    ENetPeer** peer = data->clients.search(clientId);
+    if(peer != nullptr) {
+        enet_peer_timeout(*peer, timeout, timeoutMin, timeoutMax);
+    }
+}
+
+void Server::disconnectClient(ClientHandle clientId) {
+    if(data->server == nullptr) {
+        return;
+    }
+    ENetPeer** peer = data->clients.search(clientId);
+    if(peer != nullptr) {
+        enet_peer_disconnect(*peer, 0);
+    }
+}
+
+void Server::setConnectHandler(OnClientConnect oc) {
+    data->onConnect = oc == nullptr ? voidClientDummy : oc;
+}
+
+void Server::setDisconnectHandler(OnClientDisconnect od) {
+    data->onDisconnect = od == nullptr ? voidClientDummy : od;
+}
+
+void Server::setPacketHandler(OnClientPacket op) {
+    data->onPacket = op == nullptr ? voidClientInPacketDummy : op;
+}
+
+void Server::resetHandler() {
+    data->onConnect = voidClientDummy;
+    data->onDisconnect = voidClientDummy;
+    data->onPacket = voidClientInPacketDummy;
+}

+ 53 - 45
src/VulkanUtils.c → src/VulkanUtils.cpp

@@ -1,7 +1,8 @@
-#include "VulkanUtils.h"
+#include "VulkanUtils.hpp"
 
-#include <core/File.h>
-#include <core/Logger.h>
+#include <core/Array.hpp>
+#include <core/File.hpp>
+#include <core/Logger.hpp>
 
 static VkInstance instance;
 #ifdef DEBUG_VULKAN
@@ -72,7 +73,8 @@ static PFN_vkVoidFunction getVulkanFunction(const char* name) {
     return vkGetInstanceProcAddr(instance, name);
 }
 
-#define GET_VULKAN_FUNCTION(name) ((PFN_##name)getVulkanFunction(#name))
+#define GET_VULKAN_FUNCTION(name)                            \
+    (reinterpret_cast<PFN_##name>(getVulkanFunction(#name)))
 
 bool initVulkanInstance() {
     u32 baseCount = 0;
@@ -83,8 +85,8 @@ bool initVulkanInstance() {
     }
 #ifdef DEBUG_VULKAN
     u32 count = baseCount + 2;
-    const char* extensions[32];
-    if(count > ARRAY_LENGTH(extensions)) {
+    Core::Array<const char*, 32> extensions;
+    if(count > extensions.getLength()) {
         LOG_ERROR("Extension buffer is too small");
         return true;
     }
@@ -93,6 +95,7 @@ bool initVulkanInstance() {
     }
     extensions[baseCount] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME;
     extensions[baseCount + 1] = VK_EXT_DEBUG_REPORT_EXTENSION_NAME;
+    const char* layers[] = {"VK_LAYER_KHRONOS_validation"};
 #else
     u32 count = baseCount;
     const char** extensions = baseExtensions;
@@ -109,10 +112,10 @@ bool initVulkanInstance() {
         .pApplicationInfo = &appInfo,
 #ifdef DEBUG_VULKAN
         .enabledLayerCount = 1,
-        .ppEnabledLayerNames = (const char*[]){"VK_LAYER_KHRONOS_validation"},
+        .ppEnabledLayerNames = layers,
 #endif
         .enabledExtensionCount = count,
-        .ppEnabledExtensionNames = extensions};
+        .ppEnabledExtensionNames = extensions.begin()};
     VK_ASSERT(vkCreateInstance(&info, nullptr, &instance));
     return false;
 }
@@ -210,9 +213,9 @@ void destroyVulkanDebugging() {
 #endif
 
 u32 findVulkanQueueFamily(VkPhysicalDevice pd, VkQueueFlags flags) {
-    VkQueueFamilyProperties properties[32];
-    u32 count = ARRAY_LENGTH(properties);
-    vkGetPhysicalDeviceQueueFamilyProperties(pd, &count, properties);
+    Core::Array<VkQueueFamilyProperties, 32> properties;
+    u32 count = properties.getLength();
+    vkGetPhysicalDeviceQueueFamilyProperties(pd, &count, properties.begin());
     for(u32 i = 0; i < count; i++) {
         if((properties[i].queueFlags & flags) == flags) {
             return i;
@@ -242,12 +245,12 @@ u32 findVulkanSurfaceQueueFamily(VkPhysicalDevice pd, VkSurfaceKHR s) {
 bool findVulkanSurfaceFormat(
     VkSurfaceFormatKHR* sf, VkPhysicalDevice pd, VkSurfaceKHR s,
     VulkanSurfaceFormatSelector sfs) {
-    VkSurfaceFormatKHR formats[64];
-    u32 c = ARRAY_LENGTH(formats);
-    VK_ASSERT(vkGetPhysicalDeviceSurfaceFormatsKHR(pd, s, &c, formats));
+    Core::Array<VkSurfaceFormatKHR, 64> formats;
+    u32 c = formats.getLength();
+    VK_ASSERT(vkGetPhysicalDeviceSurfaceFormatsKHR(pd, s, &c, formats.begin()));
     int bestPoints = 0;
     for(u32 i = 0; i < c; i++) {
-        int points = sfs(formats + i);
+        int points = sfs(&formats[i]);
         if(points > bestPoints) {
             bestPoints = points;
             *sf = formats[i];
@@ -259,9 +262,10 @@ bool findVulkanSurfaceFormat(
 bool findVulkanSurfacePresentMode(
     VkPresentModeKHR* m, VkPhysicalDevice pd, VkSurfaceKHR s,
     VulkanSurfacePresentModeSelector spms) {
-    VkPresentModeKHR modes[64];
-    u32 c = ARRAY_LENGTH(modes);
-    VK_ASSERT(vkGetPhysicalDeviceSurfacePresentModesKHR(pd, s, &c, modes));
+    Core::Array<VkPresentModeKHR, 64> modes;
+    u32 c = modes.getLength();
+    VK_ASSERT(
+        vkGetPhysicalDeviceSurfacePresentModesKHR(pd, s, &c, modes.begin()));
     int bestPoints = 0;
     for(u32 i = 0; i < c; i++) {
         int points = spms(modes[i]);
@@ -275,9 +279,9 @@ bool findVulkanSurfacePresentMode(
 
 bool findVulkanPhysicalDevice(
     VkPhysicalDevice* pd, VulkanPhysicalDeviceSelector s) {
-    VkPhysicalDevice devices[32];
-    u32 c = ARRAY_LENGTH(devices);
-    VK_ASSERT(vkEnumeratePhysicalDevices(instance, &c, devices));
+    Core::Array<VkPhysicalDevice, 32> devices;
+    u32 c = devices.getLength();
+    VK_ASSERT(vkEnumeratePhysicalDevices(instance, &c, devices.begin()));
     int bestPoints = 0;
     for(u32 i = 0; i < c; i++) {
         int points = s(devices[i]);
@@ -290,9 +294,10 @@ bool findVulkanPhysicalDevice(
 }
 
 bool hasVulkanExtension(VkPhysicalDevice pd, const char* extension) {
-    VkExtensionProperties e[1024];
-    u32 c = ARRAY_LENGTH(e);
-    VkResult r = vkEnumerateDeviceExtensionProperties(pd, nullptr, &c, e);
+    Core::Array<VkExtensionProperties, 1024> e;
+    u32 c = e.getLength();
+    VkResult r =
+        vkEnumerateDeviceExtensionProperties(pd, nullptr, &c, e.begin());
     if(r != VK_SUCCESS) {
         LOG_ERROR(
             "Cannot get physical device extension properties: %s",
@@ -315,7 +320,7 @@ bool initVulkanDevice(
         LOG_ERROR("Vulkan device queue overload");
         return true;
     }
-    VkDeviceQueueCreateInfo deviceQueueInfo[LENGTH] = {0};
+    VkDeviceQueueCreateInfo deviceQueueInfo[LENGTH] = {};
     for(size_t i = 0; i < n; i++) {
         VkDeviceQueueCreateInfo* qInfo = deviceQueueInfo + i;
         qInfo->sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
@@ -323,13 +328,13 @@ bool initVulkanDevice(
         qInfo->queueCount = 1;
         qInfo->pQueuePriorities = &data[i].priority;
     }
-    VkPhysicalDeviceFeatures deviceFeatures = {0};
+    VkPhysicalDeviceFeatures deviceFeatures = {};
     vkGetPhysicalDeviceFeatures(pd, &deviceFeatures);
     VkDeviceCreateInfo info = {
         .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
-        .queueCreateInfoCount = (u32)n,
+        .queueCreateInfoCount = static_cast<u32>(n),
         .pQueueCreateInfos = deviceQueueInfo,
-        .enabledExtensionCount = (u32)nExtensions,
+        .enabledExtensionCount = static_cast<u32>(nExtensions),
         .ppEnabledExtensionNames = extensions,
         .pEnabledFeatures = &deviceFeatures};
     VK_ASSERT(vkCreateDevice(pd, &info, nullptr, d));
@@ -358,7 +363,7 @@ static u32 getSwapImageCount(const VkSurfaceCapabilitiesKHR* caps) {
     // according to VkSurfaceCapabilitiesKHR doc:
     // maxImageCount is 0 when there is no strict limit
     if(caps->maxImageCount != 0) {
-        return minU32(c, caps->maxImageCount);
+        return Core::min(c, caps->maxImageCount);
     }
     return c;
 }
@@ -414,8 +419,10 @@ static bool createImageView(
 bool initVulkanSwapchainImages(
     VulkanSwapchainImages* si, VkDevice d, VkSwapchainKHR sc, VkFormat format) {
     VK_ASSERT(vkGetSwapchainImagesKHR(d, sc, &si->amount, nullptr));
-    si->images = coreZeroAllocate(sizeof(VkImage) * si->amount);
-    si->imageViews = coreZeroAllocate(sizeof(VkImageView) * si->amount);
+    si->images = static_cast<VkImage*>(
+        Core::zeroAllocateRaw(sizeof(VkImage) * si->amount));
+    si->imageViews = static_cast<VkImageView*>(
+        Core::zeroAllocateRaw(sizeof(VkImageView) * si->amount));
     VK_ASSERT(vkGetSwapchainImagesKHR(d, sc, &si->amount, si->images));
     for(u32 x = 0; x < si->amount; x++) {
         if(createImageView(si->imageViews + x, d, si->images[x], format)) {
@@ -432,32 +439,32 @@ void destroyVulkanSwapchainImages(VulkanSwapchainImages* si, VkDevice d) {
     for(size_t x = 0; x < si->amount; x++) {
         vkDestroyImageView(d, si->imageViews[x], nullptr);
     }
-    coreFree(si->images);
-    coreFree(si->imageViews);
-    *si = (VulkanSwapchainImages){0};
+    Core::deallocateRaw(si->images);
+    Core::deallocateRaw(si->imageViews);
+    *si = VulkanSwapchainImages();
 }
 
-static bool initShaderModule(FileContent* f, VkDevice d, VkShaderModule* sm) {
-    if((f->length % 4) != 0) {
+static bool initShaderModule(
+    Core::List<char>& f, VkDevice d, VkShaderModule* sm) {
+    size_t l = f.getLength() - 1;
+    if((l % 4) != 0) {
         LOG_ERROR("Shader size is not a multiple of 4");
         return true;
     }
     VkShaderModuleCreateInfo info = {
         .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
-        .codeSize = f->length,
-        .pCode = (u32*)f->data};
+        .codeSize = l,
+        .pCode = reinterpret_cast<u32*>(static_cast<void*>(&f[0]))};
     VK_ASSERT(vkCreateShaderModule(d, &info, nullptr, sm));
     return false;
 }
 
 bool initVulkanShaderModule(VkShaderModule* sm, VkDevice d, const char* path) {
-    FileContent f = {0};
-    if(readFile(&f, path)) {
+    Core::List<char> f;
+    if(Core::readFile(f, path)) {
         return true;
     }
-    bool r = initShaderModule(&f, d, sm);
-    destroyFileContent(&f);
-    return r;
+    return initShaderModule(f, d, sm);
 }
 
 void destroyVulkanShaderModule(VkShaderModule sm, VkDevice d) {
@@ -486,7 +493,8 @@ void destroyVulkanPipelineLayout(VkPipelineLayout pl, VkDevice d) {
 bool initVulkanFramebuffers(
     VkFramebuffer** f, VulkanSwapchainImages* si, VkDevice d, VkRenderPass rp,
     u32 width, u32 height) {
-    *f = coreZeroAllocate(sizeof(VkFramebuffer) * si->amount);
+    *f = static_cast<VkFramebuffer*>(
+        Core::zeroAllocateRaw(sizeof(VkFramebuffer) * si->amount));
     for(u32 i = 0; i < si->amount; i++) {
         VkFramebufferCreateInfo info = {
             .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
@@ -508,7 +516,7 @@ void destroyVulkanFramebuffers(VkFramebuffer** f, u32 amount, VkDevice d) {
                 vkDestroyFramebuffer(d, (*f)[i], nullptr);
             }
         }
-        coreFree(*f);
+        Core::deallocateRaw(*f);
         *f = nullptr;
     }
 }

+ 23 - 21
src/VulkanUtils.h → src/VulkanUtils.hpp

@@ -1,9 +1,11 @@
-#ifndef CORE_VULKAN_UTILS_H
-#define CORE_VULKAN_UTILS_H
+#ifndef GAMINGCORE_VULKAN_UTILS_HPP
+#define GAMINGCORE_VULKAN_UTILS_HPP
 
 #define GLFW_INCLUDE_VULKAN
+#include <core/Types.hpp>
+#include <core/Utility.hpp>
+
 #include <GLFW/glfw3.h>
-#include <core/Utility.h>
 
 const char* getVulkanResultString(VkResult r);
 
@@ -22,7 +24,7 @@ void destroyVulkanInstance();
 bool initVulkanDebugging();
 void destroyVulkanDebugging();
 
-#define INVALID_VULKAN_QUEUE_FAMILY ((u32) - 1)
+#define INVALID_VULKAN_QUEUE_FAMILY (static_cast<u32>(-1))
 u32 findVulkanQueueFamily(VkPhysicalDevice pd, VkQueueFlags flags);
 u32 findVulkanSurfaceQueueFamily(VkPhysicalDevice pd, VkSurfaceKHR s);
 typedef int (*VulkanSurfaceFormatSelector)(const VkSurfaceFormatKHR* sf);
@@ -39,10 +41,10 @@ bool findVulkanPhysicalDevice(
     VkPhysicalDevice* pd, VulkanPhysicalDeviceSelector s);
 bool hasVulkanExtension(VkPhysicalDevice pd, const char* extension);
 
-typedef struct {
-    u32 queueFamilyIndex;
-    float priority;
-} VulkanDeviceQueueData;
+struct VulkanDeviceQueueData {
+    u32 queueFamilyIndex = 0;
+    float priority = 0.0f;
+};
 
 bool initVulkanDevice(
     VkDevice* d, VkPhysicalDevice pd, const VulkanDeviceQueueData* data,
@@ -52,26 +54,26 @@ void destroyVulkanDevice(VkDevice d);
 bool initVulkanSurface(VkSurfaceKHR* s, GLFWwindow* w);
 void destroyVulkanSurface(VkSurfaceKHR s);
 
-typedef struct {
-    VkPhysicalDevice physicalDevice;
-    VkDevice device;
-    VkSurfaceKHR surface;
-    VkExtent2D size;
-    VkSurfaceFormatKHR surfaceFormat;
-    VkPresentModeKHR presentMode;
-    VkSharingMode sharingMode;
-    u32 queueFamilyIndexCount;
-    u32* queueFamilyIndices;
-} VulkanSwapchainData;
+struct VulkanSwapchainData {
+    VkPhysicalDevice physicalDevice{};
+    VkDevice device{};
+    VkSurfaceKHR surface{};
+    VkExtent2D size{};
+    VkSurfaceFormatKHR surfaceFormat{};
+    VkPresentModeKHR presentMode{};
+    VkSharingMode sharingMode{};
+    u32 queueFamilyIndexCount = 0;
+    u32* queueFamilyIndices = nullptr;
+};
 
 bool initVulkanSwapchain(VkSwapchainKHR* sc, VulkanSwapchainData* d);
 void destroyVulkanSwapchain(VkSwapchainKHR s, VkDevice d);
 
-typedef struct {
+struct VulkanSwapchainImages {
     u32 amount;
     VkImage* images;
     VkImageView* imageViews;
-} VulkanSwapchainImages;
+};
 
 bool initVulkanSwapchainImages(
     VulkanSwapchainImages* si, VkDevice d, VkSwapchainKHR sc, VkFormat format);

+ 43 - 39
src/VulkanWrapper.c → src/VulkanWrapper.cpp

@@ -1,10 +1,13 @@
-#include "core/VulkanWrapper.h"
+#include "core/VulkanWrapper.hpp"
 
-#include <core/Logger.h>
-#include <core/Utility.h>
+#include <core/Array.hpp>
+#include <core/Logger.hpp>
+#include <core/Utility.hpp>
 
-#include "GLFW.h"
-#include "VulkanUtils.h"
+#include "GLFW.hpp"
+#include "VulkanUtils.hpp"
+
+namespace Vulkan = Core::Vulkan;
 
 static VkPhysicalDevice physicalDevice;
 static u32 graphicsFamily = 0;
@@ -62,22 +65,22 @@ static bool getSwapchainSize(VkExtent2D* size) {
     if(c.currentExtent.width != 0xFFFF'FFFFu &&
        c.currentExtent.height != 0xFFFF'FFFFu) {
         *size = c.currentExtent;
-        LOG_INFO("Swapchain size: %ux%u", size->width, size->height);
+        LOG_INFO("Swapchain size: #x#", size->width, size->height);
         return false;
     }
     int w = 0;
     int h = 0;
-    glfwGetFramebufferSize(getWindow(), &w, &h);
+    glfwGetFramebufferSize(Core::Window::get(), &w, &h);
     if(w <= 0 || h <= 0) {
         LOG_ERROR("Could not get framebuffer size");
         return true;
     }
-    LOG_INFO("Framebuffer size: %dx%d", w, h);
-    size->width =
-        clampU32((u32)w, c.minImageExtent.width, c.maxImageExtent.width);
-    size->height =
-        clampU32((u32)h, c.minImageExtent.height, c.maxImageExtent.height);
-    LOG_INFO("Swapchain size: %ux%u", size->width, size->height);
+    LOG_INFO("Framebuffer size: #x#", w, h);
+    size->width = Core::clamp(
+        static_cast<u32>(w), c.minImageExtent.width, c.maxImageExtent.width);
+    size->height = Core::clamp(
+        static_cast<u32>(h), c.minImageExtent.height, c.maxImageExtent.height);
+    LOG_INFO("Swapchain size: #x#", size->width, size->height);
     return false;
 }
 
@@ -125,8 +128,7 @@ static bool initDevice() {
         {graphicsFamily, 1.0f}, {presentFamily, 1.0f}};
     const char* extensions[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
     if(initVulkanDevice(
-           &device, physicalDevice, data, same ? 1 : 2, extensions,
-           ARRAY_LENGTH(extensions))) {
+           &device, physicalDevice, data, same ? 1 : 2, extensions, 1)) {
         return true;
     }
     vkGetDeviceQueue(device, graphicsFamily, 0, &graphicsQueue);
@@ -150,7 +152,7 @@ static int getDevicePoints(VkPhysicalDevice pd) {
     int points = 0;
     VkPhysicalDeviceProperties p;
     vkGetPhysicalDeviceProperties(pd, &p);
-    LOG_INFO("Checking '%s'", p.deviceName);
+    LOG_INFO("Checking '#'", p.deviceName);
     switch(p.deviceType) {
         case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: points += 100; break;
         case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: points += 50; break;
@@ -171,14 +173,14 @@ static int getDevicePoints(VkPhysicalDevice pd) {
         LOG_INFO("> ... has no swapchain support");
         points = -1;
     }
-    VkSurfaceFormatKHR sf = {0};
+    VkSurfaceFormatKHR sf = {};
     if(findVulkanSurfaceFormat(&sf, pd, surface, getSurfaceFormatPoints)) {
         LOG_INFO("> ... has no matching surface format");
         points = -1;
     } else {
         points += getSurfaceFormatPoints(&sf);
     }
-    VkPresentModeKHR m = 0;
+    VkPresentModeKHR m = {};
     if(findVulkanSurfacePresentMode(
            &m, pd, surface, getSurfacePresentModePoints)) {
         LOG_INFO("> ... has no matching present mode");
@@ -186,7 +188,7 @@ static int getDevicePoints(VkPhysicalDevice pd) {
     } else {
         points += getSurfacePresentModePoints(m);
     }
-    LOG_INFO("> Final points: %d", points);
+    LOG_INFO("> Final points: #", points);
     return points;
 }
 
@@ -198,7 +200,7 @@ static bool initPhysicalDevice() {
     }
     VkPhysicalDeviceProperties p;
     vkGetPhysicalDeviceProperties(physicalDevice, &p);
-    LOG_INFO("Best Device: %s", p.deviceName);
+    LOG_INFO("Best Device: #", p.deviceName);
     return false;
 }
 
@@ -208,7 +210,7 @@ static bool initSwapchainImages() {
         LOG_ERROR("Could not get swapchain images");
         return true;
     }
-    LOG_INFO("Found %u images", images.amount);
+    LOG_INFO("Found # images", images.amount);
     return false;
 }
 
@@ -238,13 +240,14 @@ static bool initPipeline() {
         .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
         .primitiveRestartEnable = false};
 
-    viewport = (VkViewport){.x = 0.0f,
-                            .y = 0.0f,
-                            .width = (float)swapchainSize.width,
-                            .height = (float)swapchainSize.height,
-                            .minDepth = 0.0f,
-                            .maxDepth = 1.0f};
-    scissor = (VkRect2D){.offset = {0, 0}, .extent = swapchainSize};
+    viewport = {
+        .x = 0.0f,
+        .y = 0.0f,
+        .width = static_cast<float>(swapchainSize.width),
+        .height = static_cast<float>(swapchainSize.height),
+        .minDepth = 0.0f,
+        .maxDepth = 1.0f};
+    scissor = {.offset = {0, 0}, .extent = swapchainSize};
     VkPipelineViewportStateCreateInfo viewportState = {
         .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
         .viewportCount = 1,
@@ -293,13 +296,13 @@ static bool initPipeline() {
         .pAttachments = &colorBlendAttachmentState,
         .blendConstants = {0.0f, 0.0f, 0.0f, 0.0f}};
 
-    VkDynamicState dynamicStates[] = {
-        VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
+    Core::Array<VkDynamicState, 2> dynamicStates = {
+        {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}};
 
     VkPipelineDynamicStateCreateInfo dynamicState = {
         .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
-        .dynamicStateCount = ARRAY_LENGTH(dynamicStates),
-        .pDynamicStates = dynamicStates};
+        .dynamicStateCount = dynamicStates.getLength(),
+        .pDynamicStates = dynamicStates.begin()};
 
     VkGraphicsPipelineCreateInfo info = {
         .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
@@ -405,11 +408,12 @@ static bool initFrames() {
     return false;
 }
 
-bool initVulkan() {
+bool Vulkan::init() {
     return initVulkanInstance() || initVulkanDebugging() ||
-           initVulkanSurface(&surface, getWindow()) || initPhysicalDevice() ||
-           initDevice() || initSwapchain() || initSwapchainImages() ||
-           initShaders() || initVulkanPipelineLayout(&pipelineLayout, device) ||
+           initVulkanSurface(&surface, Core::Window::get()) ||
+           initPhysicalDevice() || initDevice() || initSwapchain() ||
+           initSwapchainImages() || initShaders() ||
+           initVulkanPipelineLayout(&pipelineLayout, device) ||
            initRenderPass() || initPipeline() ||
            initVulkanFramebuffers(
                &framebuffers, &images, device, renderPass, swapchainSize.width,
@@ -463,13 +467,13 @@ static bool render() {
     return false;
 }
 
-void renderVulkan() {
-    if(render()) {
+void Vulkan::render() {
+    if(::render()) {
         shouldWait = true;
     }
 }
 
-void destroyVulkan(void) {
+void Vulkan::destroy(void) {
     if(device != VK_NULL_HANDLE) {
         vkDeviceWaitIdle(device);
         for(size_t i = 0; i < MAX_FRAMES; i++) {

+ 0 - 505
src/WindowManager.c

@@ -1,505 +0,0 @@
-#include "core/WindowManager.h"
-
-#include <core/Logger.h>
-#include <core/Utility.h>
-#include <limits.h>
-#include <stdio.h>
-#include <uchar.h>
-
-#include "GLFW.h"
-#include "core/VulkanWrapper.h"
-
-static bool dummyWindowRunHandler(void*) {
-    return !shouldWindowClose();
-}
-
-static void dummyWindowTickHandler(void*) {
-}
-
-static void dummyWindowRenderHandler(void*, float) {
-}
-
-static GLFWwindow* window = nullptr;
-static IntVector2 size = {0};
-static bool sizeChanged = false;
-static WindowRunHandler runHandler = dummyWindowRunHandler;
-static WindowTickHandler tickHandler = dummyWindowTickHandler;
-static WindowRenderHandler renderHandler = dummyWindowRenderHandler;
-static void* runHandlerData = nullptr;
-static void* tickHandlerData = nullptr;
-static void* renderHandlerData = nullptr;
-static i64 nanosPerTick = 1;
-
-#define CLOCK_SIZE 16
-
-typedef struct {
-    i64 last;
-    i64 sum;
-    i64 values[CLOCK_SIZE];
-    size_t index;
-} Clock;
-
-static Clock fps = {0};
-static Clock tps = {0};
-
-#define INPUT_SIZE 2048
-static char input[INPUT_SIZE] = {0};
-static size_t inputLength = 0;
-static size_t inputCursor = 0;
-static size_t inputLimit = INPUT_SIZE - 1;
-static bool inputActive = false;
-
-typedef struct {
-    char name[31];
-    bool released;
-    int key;
-    int downTime;
-    int upEvents;
-    int downEvents;
-    int controllerUp;
-    int controllerDown;
-} ButtonData;
-
-#define BUTTONS 100
-static ButtonData buttons[BUTTONS] = {{.name = "unknown"}};
-static size_t buttonIndex = 1;
-#define KEYS (GLFW_KEY_LAST + 1)
-static Button keyToButton[KEYS] = {0};
-#define GAMEPAD_BUTTONS (GLFW_GAMEPAD_BUTTON_LAST + 1)
-static Button gamepadToButton[GAMEPAD_BUTTONS] = {0};
-#define MOUSE_BUTTONS (GLFW_MOUSE_BUTTON_LAST + 1)
-static Button mouseToButton[MOUSE_BUTTONS] = {0};
-static Vector2 lastMousePosition = {0};
-static Vector2 mousePosition = {0};
-static int activeController = -1;
-static GLFWgamepadstate lastControllerState = {0};
-
-static void onButton(Button* map, int n, int key, int action) {
-    if(key < 0 || key >= n) {
-        return;
-    }
-    Button b = map[key];
-    if(b == 0 || b >= buttonIndex) {
-        return;
-    }
-    if(action == GLFW_RELEASE) {
-        buttons[b].upEvents++;
-    } else if(action == GLFW_PRESS) {
-        buttons[b].downEvents++;
-    }
-}
-
-static bool isCharSequence(size_t i) {
-    return (input[i] & 0xC0) == 0x80;
-}
-
-static void handleInputKey(int key, int action) {
-    if(action == GLFW_RELEASE) {
-        return;
-    }
-    switch(key) {
-        case GLFW_KEY_BACKSPACE:
-            if(inputLength >= inputCursor && inputCursor > 0) {
-                size_t w = 1;
-                while(inputCursor - w > 0 && isCharSequence(inputCursor - w)) {
-                    w++;
-                }
-                for(size_t i = inputCursor - w; i < inputLength; i++) {
-                    input[i] = input[i + w];
-                }
-                inputLength -= w;
-                inputCursor -= w;
-            }
-            break;
-        case GLFW_KEY_LEFT: {
-            inputCursor -= inputCursor > 0;
-            while(inputCursor > 0 && isCharSequence(inputCursor)) {
-                inputCursor--;
-            }
-            break;
-        }
-        case GLFW_KEY_RIGHT: {
-            inputCursor += inputCursor < inputLength;
-            while(inputCursor < inputLength && isCharSequence(inputCursor)) {
-                inputCursor++;
-            }
-            break;
-        }
-    }
-}
-
-static void onKey(GLFWwindow*, int key, int scancode, int action, int mods) {
-    (void)scancode;
-    (void)mods;
-    if(inputActive) {
-        handleInputKey(key, action);
-    }
-    onButton(keyToButton, KEYS, key, action);
-}
-
-static void addUnicode(u32 c) {
-    char buffer[MB_LEN_MAX + 1] = {0};
-    mbstate_t state = {0};
-    size_t w = c32rtomb(buffer, c, &state);
-    if(w >= MB_LEN_MAX || inputLength + w >= inputLimit) {
-        return;
-    }
-    if(inputLength > 0) {
-        for(size_t i = inputLength - 1; i >= inputCursor; i--) {
-            swap(input + (i + w), input + i);
-        }
-    }
-    memcpy(input + inputCursor, buffer, w);
-    inputLength += w;
-    inputCursor += w;
-}
-
-static void onChar(GLFWwindow*, u32 codepoint) {
-    if(inputActive) {
-        addUnicode(codepoint);
-    }
-}
-
-static void onResize(GLFWwindow*, int width, int height) {
-    sizeChanged = true;
-    size.data[0] = width;
-    size.data[1] = height;
-}
-
-static void onMouse(GLFWwindow*, int button, int action, int mods) {
-    (void)mods;
-    onButton(mouseToButton, MOUSE_BUTTONS, button, action);
-}
-
-static void onMouseMove(GLFWwindow*, double x, double y) {
-    mousePosition.data[0] = (float)x;
-    mousePosition.data[1] = (float)y;
-}
-
-static bool openWindowI(const WindowOptions* o) {
-    if(!glfwInit()) {
-        LOG_ERROR("could not initialize GLFW");
-        return true;
-    }
-
-    glfwDefaultWindowHints();
-    glfwWindowHint(GLFW_VISIBLE, false);
-    glfwWindowHint(GLFW_RESIZABLE, true);
-    glfwWindowHint(GLFW_DECORATED, !o->fullscreen);
-    glfwWindowHint(GLFW_DOUBLEBUFFER, true);
-    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
-
-    GLFWmonitor* m = o->fullscreen ? glfwGetPrimaryMonitor() : nullptr;
-    window =
-        glfwCreateWindow(o->size.data[0], o->size.data[1], o->name, m, nullptr);
-    if(window == nullptr) {
-        LOG_ERROR("could not create window");
-        return true;
-    }
-    size = o->size;
-    glfwSetKeyCallback(window, onKey);
-    glfwSetCharCallback(window, onChar);
-    glfwSetFramebufferSizeCallback(window, onResize);
-    glfwSetMouseButtonCallback(window, onMouse);
-    glfwSetCursorPosCallback(window, onMouseMove);
-    return initVulkan();
-}
-
-bool openWindow(const WindowOptions* o) {
-    if(openWindowI(o)) {
-        closeWindow();
-        return true;
-    }
-    return false;
-}
-
-void closeWindow(void) {
-    if(window != nullptr) {
-        destroyVulkan();
-        glfwDestroyWindow(window);
-        window = nullptr;
-    }
-    glfwTerminate();
-}
-
-void showWindow(void) {
-    glfwShowWindow(window);
-}
-
-void trapWindowCursor(void) {
-    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
-}
-
-void freeWindowCursor(void) {
-    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
-}
-
-bool isWindowCursorTrapped(void) {
-    return glfwGetInputMode(window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED;
-}
-
-const IntVector2* getWindowSize(void) {
-    return &size;
-}
-
-bool hasWindowSizeChanged(void) {
-    return sizeChanged;
-}
-
-bool shouldWindowClose(void) {
-    return glfwWindowShouldClose(window);
-}
-
-void setWindowRunHandler(WindowRunHandler wr, void* data) {
-    runHandlerData = data;
-    runHandler = wr == nullptr ? dummyWindowRunHandler : wr;
-}
-
-void setWindowTickHandler(WindowTickHandler t, void* data) {
-    tickHandlerData = data;
-    tickHandler = t == nullptr ? dummyWindowTickHandler : t;
-}
-
-void setWindowRenderHandler(WindowRenderHandler r, void* data) {
-    renderHandlerData = data;
-    renderHandler = r == nullptr ? dummyWindowRenderHandler : r;
-}
-
-void setWindowNanosPerTick(i64 nanos) {
-    nanosPerTick = nanos <= 0 ? 1 : nanos;
-}
-
-static i64 updateClock(Clock* c) {
-    i64 nanos = getNanos();
-    if(nanos < 0) {
-        LOG_WARNING("Cannot get nanos, using default");
-        nanos = 10'000'000 + c->last;
-    }
-    i64 diff = nanos - c->last;
-    c->last = nanos;
-    c->sum -= c->values[c->index];
-    c->values[c->index] = diff;
-    c->sum += diff;
-    c->index = (c->index + 1) % CLOCK_SIZE;
-    return diff;
-}
-
-static bool searchForGamepad() {
-    if(activeController != -1) {
-        return true;
-    }
-    for(int i = GLFW_JOYSTICK_1; i <= GLFW_JOYSTICK_LAST; i++) {
-        if(glfwJoystickIsGamepad(i)) {
-            activeController = i;
-            return true;
-        }
-    }
-    return false;
-}
-
-static void checkGamepad() {
-    GLFWgamepadstate state;
-    if(!glfwGetGamepadState(activeController, &state)) {
-        activeController = -1;
-        return;
-    }
-    for(int i = 0; i <= GLFW_GAMEPAD_BUTTON_LAST; i++) {
-        Button b = gamepadToButton[i];
-        if(b == 0 || b >= buttonIndex) {
-            return;
-        }
-        if(!lastControllerState.buttons[i] && state.buttons[i]) {
-            buttons[b].controllerDown++;
-        } else if(lastControllerState.buttons[i] && !state.buttons[i]) {
-            buttons[b].controllerUp++;
-        }
-    }
-    lastControllerState = state;
-}
-
-static void endFrame(void) {
-    sizeChanged = false;
-    glfwPollEvents();
-    if(searchForGamepad()) {
-        checkGamepad();
-    }
-}
-
-static void tickButton(ButtonData* b) {
-    bool down = (b->downEvents > 0) || (b->controllerDown > 0);
-    bool up = (b->upEvents == b->downEvents) &&
-              (b->controllerUp == b->controllerDown);
-    if(b->released) {
-        b->downTime = 0;
-    }
-    b->downTime += down;
-    b->released = down && up;
-
-    b->downEvents -= b->upEvents;
-    b->upEvents = 0;
-
-    b->controllerDown -= b->controllerUp;
-    b->controllerUp = 0;
-}
-
-static void tick() {
-    updateClock(&tps);
-    ButtonData* b = buttons;
-    for(size_t i = 0; i < buttonIndex; i++) {
-        tickButton(b++);
-    }
-    tickHandler(tickHandlerData);
-    lastMousePosition = mousePosition;
-}
-
-void runWindow(void) {
-    searchForGamepad(); // this is slow the first time
-    tps.last = getNanos();
-    fps.last = getNanos();
-    i64 lag = 0;
-    while(runHandler(runHandlerData)) {
-        lag += updateClock(&fps);
-        while(lag >= nanosPerTick) {
-            lag -= nanosPerTick;
-            tick();
-        }
-        renderVulkan();
-        renderHandler(renderHandlerData, (float)lag / (float)nanosPerTick);
-        endFrame();
-    }
-}
-
-float getWindowTicksPerSecond(void) {
-    return (1'000'000'000.0f * CLOCK_SIZE) / (float)tps.sum;
-}
-
-float getWindowFramesPerSecond(void) {
-    return (1'000'000'000.0f * CLOCK_SIZE) / (float)fps.sum;
-}
-
-void setInputLimit(size_t limit) {
-    inputLimit = limit >= INPUT_SIZE ? INPUT_SIZE - 1 : limit;
-    if(inputLength > inputLimit) {
-        inputLength = inputLimit;
-    }
-    if(inputCursor > inputLimit) {
-        inputCursor = inputLimit;
-    }
-}
-
-void resetInput() {
-    inputLength = 0;
-    inputCursor = 0;
-}
-
-void enableInput() {
-    inputActive = true;
-}
-
-void disableInput() {
-    inputActive = false;
-}
-
-bool isInputEnabled() {
-    return inputActive;
-}
-
-void fillInput(const char* s) {
-    resetInput();
-    snprintf(input, inputLength, "%s", s);
-}
-
-size_t getInputCursor() {
-    return inputCursor;
-}
-
-void setInputCursor(size_t index) {
-    if(index > inputLength) {
-        inputCursor = inputLength;
-    } else {
-        inputCursor = index;
-    }
-}
-
-const char* getInputString(void) {
-    return input;
-}
-
-Button addButton(const char* name) {
-    if(buttonIndex >= BUTTONS) {
-        return 0;
-    }
-    Button b = buttonIndex++;
-    snprintf(buttons[b].name, sizeof(buttons[b].name), "%s", name);
-    return b;
-}
-
-void bindKeyToButton(Button b, int key) {
-    if(key >= 0 && key < KEYS) {
-        keyToButton[key] = b;
-    }
-}
-
-void bindGamepadToButton(Button b, int gamepadButton) {
-    if(gamepadButton >= 0 && gamepadButton < GAMEPAD_BUTTONS) {
-        gamepadToButton[gamepadButton] = b;
-    }
-}
-
-void bindMouseToButton(Button b, int mouseButton) {
-    if(mouseButton >= 0 && mouseButton < MOUSE_BUTTONS) {
-        mouseToButton[mouseButton] = b;
-    }
-}
-
-Vector2 getLastMousePosition(void) {
-    return lastMousePosition;
-}
-
-Vector2 getMousePosition(void) {
-    return mousePosition;
-}
-
-Vector2 getLeftGamepadAxis(void) {
-    return (Vector2){
-        {lastControllerState.axes[GLFW_GAMEPAD_AXIS_LEFT_X],
-         lastControllerState.axes[GLFW_GAMEPAD_AXIS_LEFT_Y]}};
-}
-
-Vector2 getRightGamepadAxis(void) {
-    return (Vector2){
-        {lastControllerState.axes[GLFW_GAMEPAD_AXIS_RIGHT_X],
-         lastControllerState.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y]}};
-}
-
-float getLeftGamepadTrigger(void) {
-    return lastControllerState.axes[GLFW_GAMEPAD_AXIS_LEFT_TRIGGER];
-}
-
-float getRightGamepadTrigger(void) {
-    return lastControllerState.axes[GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER];
-}
-
-static const ButtonData* getButton(Button id) {
-    return buttons + (id >= buttonIndex ? 0 : id);
-}
-
-bool isButtonDown(Button b) {
-    return getButton(b)->downTime > 0;
-}
-
-int getButtonDownTime(Button b) {
-    return getButton(b)->downTime;
-}
-
-bool wasButtonReleased(Button b) {
-    return getButton(b)->released;
-}
-
-const char* getButtonName(Button b) {
-    return getButton(b)->name;
-}
-
-GLFWwindow* getWindow(void) {
-    return window;
-}

+ 465 - 0
src/WindowManager.cpp

@@ -0,0 +1,465 @@
+#include "core/WindowManager.hpp"
+
+#include <climits>
+#include <core/ArrayList.hpp>
+#include <core/Clock.hpp>
+#include <core/Logger.hpp>
+#include <core/Meta.hpp>
+#include <core/Unicode.hpp>
+#include <core/Utility.hpp>
+#include <cstdio>
+
+#include "GLFW.hpp"
+#include "core/VulkanWrapper.hpp"
+
+namespace Window = Core::Window;
+using Core::Array;
+
+static bool dummyWindowRunHandler(void*) {
+    return !Window::shouldClose();
+}
+
+static void dummyWindowTickHandler(void*) {
+}
+
+static void dummyWindowRenderHandler(void*, float) {
+}
+
+static GLFWwindow* window = nullptr;
+static Core::IntVector2 size;
+static bool sizeChanged = false;
+static Window::RunHandler runHandler = dummyWindowRunHandler;
+static Window::TickHandler tickHandler = dummyWindowTickHandler;
+static Window::RenderHandler renderHandler = dummyWindowRenderHandler;
+static void* runHandlerData = nullptr;
+static void* tickHandlerData = nullptr;
+static void* renderHandlerData = nullptr;
+static i64 nanosPerTick = 1;
+static Core::Clock fps;
+static Core::Clock tps;
+
+static Core::ArrayList<u32, 2048> input = {};
+static Core::ArrayList<char, input.getCapacity() * 4 + 1> convertedInput;
+static size_t inputCursor = 0;
+static size_t inputLimit = input.getCapacity();
+static bool inputActive = false;
+
+struct ButtonData {
+    char name[31] = {};
+    bool released = false;
+    int key = 0;
+    int downTime = 0;
+    int upEvents = 0;
+    int downEvents = 0;
+    int controllerUp = 0;
+    int controllerDown = 0;
+};
+
+static Array<ButtonData, 100> buttons = {{{.name = "unknown"}}};
+static size_t buttonIndex = 1;
+static Array<Window::Button, GLFW_KEY_LAST + 1> keyToButton = {};
+static Array<Window::Button, GLFW_GAMEPAD_BUTTON_LAST + 1> gamepadToButton = {};
+static Array<Window::Button, GLFW_MOUSE_BUTTON_LAST + 1> mouseToButton = {};
+static Core::Vector2 lastMousePosition;
+static Core::Vector2 mousePosition;
+static int activeController = -1;
+static GLFWgamepadstate lastControllerState;
+
+template<size_t N>
+static void onButton(Array<Window::Button, N>& map, int key, int action) {
+    if(key < 0 || key >= static_cast<int>(N)) {
+        return;
+    }
+    Window::Button b = map[static_cast<size_t>(key)];
+    if(b == 0 || b >= buttonIndex) {
+        return;
+    }
+    if(action == GLFW_RELEASE) {
+        buttons[b].upEvents++;
+    } else if(action == GLFW_PRESS) {
+        buttons[b].downEvents++;
+    }
+}
+
+static void handleInputKey(int key, int action) {
+    if(action == GLFW_RELEASE) {
+        return;
+    }
+    switch(key) {
+        case GLFW_KEY_BACKSPACE:
+            if(input.getLength() >= inputCursor && inputCursor > 0) {
+                inputCursor--;
+                input.remove(inputCursor);
+            }
+            break;
+        case GLFW_KEY_LEFT: inputCursor -= inputCursor > 0; break;
+        case GLFW_KEY_RIGHT:
+            inputCursor += inputCursor < input.getLength();
+            break;
+    }
+}
+
+static void onKey(GLFWwindow*, int key, int, int action, int) {
+    if(inputActive) {
+        handleInputKey(key, action);
+    }
+    onButton(keyToButton, key, action);
+}
+
+static void onChar(GLFWwindow*, u32 codepoint) {
+    if(inputActive && input.getLength() < inputLimit) {
+        inputCursor += input.putAt(inputCursor, codepoint) != nullptr;
+    }
+}
+
+static void onResize(GLFWwindow*, int width, int height) {
+    sizeChanged = true;
+    size[0] = width;
+    size[1] = height;
+}
+
+static void onMouse(GLFWwindow*, int button, int action, int) {
+    onButton(mouseToButton, button, action);
+}
+
+static void onMouseMove(GLFWwindow*, double x, double y) {
+    mousePosition[0] = static_cast<float>(x);
+    mousePosition[1] = static_cast<float>(y);
+}
+
+static bool openWindowI(const Window::Options* o) {
+    if(!glfwInit()) {
+        REPORT(Core::LogLevel::ERROR, "could not initialize GLFW");
+        return true;
+    }
+
+    glfwDefaultWindowHints();
+    glfwWindowHint(GLFW_VISIBLE, false);
+    glfwWindowHint(GLFW_RESIZABLE, true);
+    glfwWindowHint(GLFW_DECORATED, !o->fullscreen);
+    glfwWindowHint(GLFW_DOUBLEBUFFER, true);
+    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+
+    GLFWmonitor* m = o->fullscreen ? glfwGetPrimaryMonitor() : nullptr;
+    window = glfwCreateWindow(o->size[0], o->size[1], o->name, m, nullptr);
+    if(window == nullptr) {
+        REPORT(Core::LogLevel::ERROR, "could not create window");
+        return true;
+    }
+    size = o->size;
+    glfwSetKeyCallback(window, onKey);
+    glfwSetCharCallback(window, onChar);
+    glfwSetFramebufferSizeCallback(window, onResize);
+    glfwSetMouseButtonCallback(window, onMouse);
+    glfwSetCursorPosCallback(window, onMouseMove);
+    return Core::Vulkan::init();
+}
+
+bool Window::open(const Options* o) {
+    if(openWindowI(o)) {
+        close();
+        return true;
+    }
+    return false;
+}
+
+void Window::close() {
+    if(window != nullptr) {
+        Vulkan::destroy();
+        glfwDestroyWindow(window);
+        window = nullptr;
+    }
+    glfwTerminate();
+}
+
+void Window::show() {
+    glfwShowWindow(window);
+}
+
+void Window::trapCursor() {
+    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
+}
+
+void Window::freeCursor() {
+    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
+}
+
+bool Window::isCursorTrapped() {
+    return glfwGetInputMode(window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED;
+}
+
+const Core::IntVector2* Window::getSize() {
+    return &size;
+}
+
+bool Window::hasSizeChanged() {
+    return sizeChanged;
+}
+
+bool Window::shouldClose() {
+    return glfwWindowShouldClose(window);
+}
+
+void Window::setRunHandler(RunHandler wr, void* data) {
+    runHandlerData = data;
+    runHandler = wr == nullptr ? dummyWindowRunHandler : wr;
+}
+
+void Window::setTickHandler(TickHandler t, void* data) {
+    tickHandlerData = data;
+    tickHandler = t == nullptr ? dummyWindowTickHandler : t;
+}
+
+void Window::setRenderHandler(RenderHandler r, void* data) {
+    renderHandlerData = data;
+    renderHandler = r == nullptr ? dummyWindowRenderHandler : r;
+}
+
+void Window::setNanosPerTick(i64 nanos) {
+    nanosPerTick = nanos <= 0 ? 1 : nanos;
+}
+
+static bool searchForGamepad() {
+    if(activeController != -1) {
+        return true;
+    }
+    for(int i = GLFW_JOYSTICK_1; i <= GLFW_JOYSTICK_LAST; i++) {
+        if(glfwJoystickIsGamepad(i)) {
+            activeController = i;
+            return true;
+        }
+    }
+    return false;
+}
+
+static void checkGamepad() {
+    GLFWgamepadstate state;
+    if(!glfwGetGamepadState(activeController, &state)) {
+        activeController = -1;
+        return;
+    }
+    for(size_t i = 0; i < gamepadToButton.getLength(); i++) {
+        Window::Button b = gamepadToButton[i];
+        if(b == 0 || b >= buttonIndex) {
+            return;
+        }
+        if(!lastControllerState.buttons[i] && state.buttons[i]) {
+            buttons[b].controllerDown++;
+        } else if(lastControllerState.buttons[i] && !state.buttons[i]) {
+            buttons[b].controllerUp++;
+        }
+    }
+    lastControllerState = state;
+}
+
+static void endFrame() {
+    sizeChanged = false;
+    glfwPollEvents();
+    if(searchForGamepad()) {
+        checkGamepad();
+    }
+}
+
+static void tickButton(ButtonData* b) {
+    bool down = (b->downEvents > 0) || (b->controllerDown > 0);
+    bool up = (b->upEvents == b->downEvents) &&
+              (b->controllerUp == b->controllerDown);
+    if(b->released) {
+        b->downTime = 0;
+    }
+    b->downTime += down;
+    b->released = down && up;
+
+    b->downEvents -= b->upEvents;
+    b->upEvents = 0;
+
+    b->controllerDown -= b->controllerUp;
+    b->controllerUp = 0;
+}
+
+static void tick() {
+    tps.update();
+    ButtonData* b = buttons.begin();
+    for(size_t i = 0; i < buttonIndex; i++) {
+        tickButton(b++);
+    }
+    tickHandler(tickHandlerData);
+    lastMousePosition = mousePosition;
+}
+
+void Window::run() {
+    searchForGamepad(); // this is slow the first time
+    tps.last = Core::Clock::getNanos();
+    fps.last = Core::Clock::getNanos();
+    i64 lag = 0;
+    while(runHandler(runHandlerData)) {
+        lag += fps.update();
+        while(lag >= nanosPerTick) {
+            lag -= nanosPerTick;
+            tick();
+        }
+        Vulkan::render();
+        renderHandler(
+            renderHandlerData,
+            static_cast<float>(lag) / static_cast<float>(nanosPerTick));
+        endFrame();
+    }
+}
+
+float Window::getTicksPerSecond() {
+    return tps.getUpdatesPerSecond();
+}
+
+float Window::getFramesPerSecond() {
+    return fps.getUpdatesPerSecond();
+}
+
+void Window::setInputLimit(size_t limit) {
+    inputLimit = Core::min(limit, input.getCapacity() - 1);
+    while(input.getLength() > inputLimit) {
+        input.removeLast();
+    }
+    if(inputCursor > inputLimit) {
+        inputCursor = inputLimit;
+    }
+}
+
+void Window::resetInput() {
+    input.clear();
+    inputCursor = 0;
+}
+
+void Window::enableInput() {
+    inputActive = true;
+}
+
+void Window::disableInput() {
+    inputActive = false;
+}
+
+bool Window::isInputEnabled() {
+    return inputActive;
+}
+
+void Window::fillInput(const char* s) {
+    resetInput();
+    input.clear();
+    while(*s != 0) {
+        UTF8 u;
+        u.length = getUTF8Length(static_cast<u8>(*s));
+        u.data[0] = static_cast<u8>(*(s++));
+        for(size_t i = 1; i < u.length && *s != 0; i++) {
+            u.data[i] = static_cast<u8>(*(s++));
+        }
+        input.add(convertUTF8toUnicode(u));
+    }
+    while(input.getLength() > inputLimit) {
+        input.removeLast();
+    }
+}
+
+size_t Window::getInputCursor() {
+    return inputCursor;
+}
+
+void Window::setInputCursor(size_t index) {
+    if(index > input.getLength()) {
+        inputCursor = input.getLength();
+    } else {
+        inputCursor = index;
+    }
+}
+
+const char* Window::getInputString() {
+    convertedInput.clear();
+    for(u32 c : input) {
+        UTF8 u = convertUnicodeToUTF8(c);
+        for(size_t i = 0; i < u.length; i++) {
+            convertedInput.put(u.data[i]);
+        }
+    }
+    if(convertedInput.put(0) == nullptr) {
+        convertedInput.removeLast();
+        convertedInput.put(0);
+    }
+    return convertedInput.begin();
+}
+
+Window::Button Window::addButton(const char* name) {
+    if(buttonIndex >= buttons.getLength()) {
+        return 0;
+    }
+    Button b = buttonIndex++;
+    snprintf(buttons[b].name, sizeof(buttons[b].name), "%s", name);
+    return b;
+}
+
+void Window::bindKeyToButton(Button b, Button key) {
+    if(key < keyToButton.getLength()) {
+        keyToButton[key] = b;
+    }
+}
+
+void Window::bindGamepadToButton(Button b, Button gamepadButton) {
+    if(gamepadButton < gamepadToButton.getLength()) {
+        gamepadToButton[gamepadButton] = b;
+    }
+}
+
+void Window::bindMouseToButton(Button b, Button mouseButton) {
+    if(mouseButton < mouseToButton.getLength()) {
+        mouseToButton[mouseButton] = b;
+    }
+}
+
+Core::Vector2 Window::getLastMousePosition() {
+    return lastMousePosition;
+}
+
+Core::Vector2 Window::getMousePosition() {
+    return mousePosition;
+}
+
+Core::Vector2 Window::getLeftGamepadAxis() {
+    return {
+        lastControllerState.axes[GLFW_GAMEPAD_AXIS_LEFT_X],
+        lastControllerState.axes[GLFW_GAMEPAD_AXIS_LEFT_Y]};
+}
+
+Core::Vector2 Window::getRightGamepadAxis() {
+    return {
+        lastControllerState.axes[GLFW_GAMEPAD_AXIS_RIGHT_X],
+        lastControllerState.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y]};
+}
+
+float Window::getLeftGamepadTrigger() {
+    return lastControllerState.axes[GLFW_GAMEPAD_AXIS_LEFT_TRIGGER];
+}
+
+float Window::getRightGamepadTrigger() {
+    return lastControllerState.axes[GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER];
+}
+
+static const ButtonData& getButton(Window::Button id) {
+    return buttons[id >= buttonIndex ? 0 : id];
+}
+
+bool Window::isButtonDown(Button b) {
+    return getButton(b).downTime > 0;
+}
+
+int Window::getButtonDownTime(Button b) {
+    return getButton(b).downTime;
+}
+
+bool Window::wasButtonReleased(Button b) {
+    return getButton(b).released;
+}
+
+const char* Window::getButtonName(Button b) {
+    return getButton(b).name;
+}
+
+GLFWwindow* Window::get() {
+    return window;
+}

+ 1 - 0
stb

@@ -0,0 +1 @@
+Subproject commit 5c205738c191bcb0abc65c4febfa9bd25ff35234

+ 17 - 10
test/Main.c → test/Main.cpp

@@ -1,18 +1,25 @@
-#include <core/Logger.h>
-#include <core/Utility.h>
-#include <locale.h>
-#include <stdio.h>
-#include <string.h>
+#include <clocale>
+#include <core/Logger.hpp>
+#include <core/Utility.hpp>
+#include <cstdio>
+#include <cstring>
 
-#include "Tests.h"
+#include "Tests.hpp"
+
+static void reportHandler(
+    Core::LogLevel, const char*, int, void*, const char* message) {
+    if(useReport) {
+        LOG_ERROR(message);
+    }
+}
 
 int main(int argAmount, char** args) {
     if(argAmount >= 2 && strcmp(args[1], "help") == 0) {
-        // puts("test");
-        puts("window");
+        puts("test");
         return 0;
     }
     setlocale(LC_ALL, "en_US.utf8");
+    Core::setReportHandler(reportHandler, nullptr);
     if(argAmount < 2) {
         LOG_ERROR("missing mode");
         return 0;
@@ -22,7 +29,7 @@ int main(int argAmount, char** args) {
     } else if(strcmp("window", args[1]) == 0) {
         testWindow();
     }
-    finalizeTests();
-    printMemoryReport();
+    Core::finalizeTests();
+    Core::printMemoryReport();
     return 0;
 }

+ 0 - 10
test/Tests.h

@@ -1,10 +0,0 @@
-#ifndef CORE_TESTS_H
-#define CORE_TESTS_H
-
-#include <core/Test.h>
-
-void testImageReader(const char* path);
-void testNetwork(void);
-void testWindow(void);
-
-#endif

+ 11 - 0
test/Tests.hpp

@@ -0,0 +1,11 @@
+#ifndef GAMINGCORE_TESTS_HPP
+#define GAMINGCORE_TESTS_HPP
+
+#include <core/Test.hpp>
+
+inline bool useReport = true;
+void testImageReader(const char* path);
+void testNetwork(void);
+void testWindow(void);
+
+#endif

+ 0 - 60
test/modules/ImageTests.c

@@ -1,60 +0,0 @@
-#include <core/Logger.h>
-#include <stdio.h>
-
-#include "../Tests.h"
-#include "core/Image.h"
-
-static void testReadPNG8(const char* path, const char* name, int width,
-                         int height, int channels) {
-    char fullPath[512];
-    snprintf(fullPath, sizeof(fullPath), "%s/%s.png", path, name);
-    Image8 image;
-    TEST_BOOL(width == 0, initImage8(&image, fullPath));
-    if(width != 0) {
-        TEST_NOT_NULL(image.data);
-    }
-    TEST_INT(width, image.width);
-    TEST_INT(height, image.height);
-    TEST_INT(channels, image.channels);
-    destroyImage8(&image);
-}
-
-static void testReadPNG16(const char* path, const char* name, int width,
-                          int height, int channels) {
-    char fullPath[512];
-    snprintf(fullPath, sizeof(fullPath), "%s/%s.png", path, name);
-    Image16 image;
-    TEST_BOOL(width == 0, initImage16(&image, fullPath));
-    if(width != 0) {
-        TEST_NOT_NULL(image.data);
-    }
-    TEST_INT(width, image.width);
-    TEST_INT(height, image.height);
-    TEST_INT(channels, image.channels);
-    destroyImage16(&image);
-}
-
-void testImageReader(const char* path) {
-    testReadPNG8(path, "rgb8", 32, 64, 3);
-    testReadPNG8(path, "rgb16", 32, 64, 3);
-    testReadPNG8(path, "rgba8", 32, 64, 4);
-    testReadPNG8(path, "rgba16", 32, 64, 4);
-    testReadPNG8(path, "gray8", 32, 64, 1);
-    testReadPNG8(path, "gray16", 32, 64, 1);
-    testReadPNG8(path, "graya8", 32, 64, 2);
-    testReadPNG8(path, "graya16", 32, 64, 2);
-    logLevel = LOG_NONE;
-    testReadPNG8(path, "nope", 0, 0, 0);
-    logLevel = LOG_DEBUG;
-    testReadPNG16(path, "rgb8", 32, 64, 3);
-    testReadPNG16(path, "rgb16", 32, 64, 3);
-    testReadPNG16(path, "rgba8", 32, 64, 4);
-    testReadPNG16(path, "rgba16", 32, 64, 4);
-    testReadPNG16(path, "gray8", 32, 64, 1);
-    testReadPNG16(path, "gray16", 32, 64, 1);
-    testReadPNG16(path, "graya8", 32, 64, 2);
-    testReadPNG16(path, "graya16", 32, 64, 2);
-    logLevel = LOG_NONE;
-    testReadPNG16(path, "nope", 0, 0, 0);
-    logLevel = LOG_DEBUG;
-}

+ 58 - 0
test/modules/ImageTests.cpp

@@ -0,0 +1,58 @@
+#include <core/Logger.hpp>
+#include <cstdio>
+
+#include "../Tests.hpp"
+#include "core/Image.hpp"
+
+static void testReadPNG8(
+    const char* path, const char* name, int width, int height, int channels) {
+    char fullPath[512];
+    Core::formatBuffer(fullPath, sizeof(fullPath), "#/#.png", path, name);
+    Core::Image8 i;
+    TEST(width == 0, i.read(fullPath));
+    if(width != 0) {
+        TEST_TRUE(i.data.getLength() > 0);
+    }
+    TEST(width, i.width);
+    TEST(height, i.height);
+    TEST(channels, i.channels);
+}
+
+static void testReadPNG16(
+    const char* path, const char* name, int width, int height, int channels) {
+    char fullPath[512];
+    Core::formatBuffer(fullPath, sizeof(fullPath), "#/#.png", path, name);
+    Core::Image16 i;
+    TEST(width == 0, i.read(fullPath));
+    if(width != 0) {
+        TEST_TRUE(i.data.getLength() > 0);
+    }
+    TEST(width, i.width);
+    TEST(height, i.height);
+    TEST(channels, i.channels);
+}
+
+void testImageReader(const char* path) {
+    testReadPNG8(path, "rgb8", 32, 64, 3);
+    testReadPNG8(path, "rgb16", 32, 64, 3);
+    testReadPNG8(path, "rgba8", 32, 64, 4);
+    testReadPNG8(path, "rgba16", 32, 64, 4);
+    testReadPNG8(path, "gray8", 32, 64, 1);
+    testReadPNG8(path, "gray16", 32, 64, 1);
+    testReadPNG8(path, "graya8", 32, 64, 2);
+    testReadPNG8(path, "graya16", 32, 64, 2);
+    useReport = false;
+    testReadPNG8(path, "nope", 0, 0, 0);
+    useReport = true;
+    testReadPNG16(path, "rgb8", 32, 64, 3);
+    testReadPNG16(path, "rgb16", 32, 64, 3);
+    testReadPNG16(path, "rgba8", 32, 64, 4);
+    testReadPNG16(path, "rgba16", 32, 64, 4);
+    testReadPNG16(path, "gray8", 32, 64, 1);
+    testReadPNG16(path, "gray16", 32, 64, 1);
+    testReadPNG16(path, "graya8", 32, 64, 2);
+    testReadPNG16(path, "graya16", 32, 64, 2);
+    useReport = false;
+    testReadPNG16(path, "nope", 0, 0, 0);
+    useReport = true;
+}

+ 0 - 503
test/modules/NetworkTests.c

@@ -1,503 +0,0 @@
-#include <core/Logger.h>
-#include <core/Utility.h>
-#include <string.h>
-
-#include "../Tests.h"
-#include "../src/ErrorSimulator.h"
-#include "core/Network.h"
-
-#define TEST_READ(Type, type, value)                                           \
-    {                                                                          \
-        type u;                                                                \
-        TEST_FALSE(readInPacket##Type(&in, &u));                               \
-        TEST_TRUE(u == value);                                                 \
-    }
-
-static void testWriteRead(void) {
-    OutPacket out;
-    initOutPacket(&out);
-    writeOutPacketU8(&out, 200);
-    writeOutPacketU16(&out, 6656);
-    writeOutPacketU32(&out, 348923689);
-    writeOutPacketI8(&out, 90);
-    writeOutPacketI8(&out, -35);
-    writeOutPacketI16(&out, 843);
-    writeOutPacketI16(&out, -8961);
-    writeOutPacketI32(&out, 100430199);
-    writeOutPacketI32(&out, -534534);
-    writeOutPacketFloat(&out, 64564.5346f);
-    const char s[] = "This is Great";
-    writeOutPacketString(&out, s);
-
-    InPacket in;
-    initInPacket(&in, out.data.buffer, out.data.size);
-    TEST_READ(U8, u8, 200)
-    TEST_READ(U16, u16, 6656)
-    TEST_READ(U32, u32, 348923689)
-    TEST_READ(I8, i8, 90)
-    TEST_READ(I8, i8, -35)
-    TEST_READ(I16, i16, 843)
-    TEST_READ(I16, i16, -8961)
-    TEST_READ(I32, i32, 100430199)
-    TEST_READ(I32, i32, -534534)
-    float f;
-    TEST_FALSE(readInPacketFloat(&in, &f));
-    TEST_FLOAT(64564.5346f, f, 0.00001f);
-    char buffer[256];
-    size_t n = readInPacketString(&in, buffer, sizeof(buffer));
-    TEST_SIZE(14, n);
-    TEST_STRING(s, buffer);
-
-    TEST_TRUE(readInPacketU8(&in, &(u8){0}));
-    TEST_TRUE(readInPacketU16(&in, &(u16){0}));
-    TEST_TRUE(readInPacketU32(&in, &(u32){0}));
-    TEST_TRUE(readInPacketI8(&in, &(i8){0}));
-    TEST_TRUE(readInPacketI16(&in, &(i16){0}));
-    TEST_TRUE(readInPacketI32(&in, &(i32){0}));
-    TEST_TRUE(readInPacketFloat(&in, &(float){0}));
-    TEST_SIZE(0, readInPacketString(&in, nullptr, 0));
-    TEST_SIZE(0, readInPacketString(&in, buffer, sizeof(buffer)));
-
-    destroyOutPacket(&out);
-}
-
-static void testTooShortBuffer(void) {
-    OutPacket out;
-    initOutPacket(&out);
-    writeOutPacketString(&out, "This is Great");
-    writeOutPacketString(&out, "Well hoho");
-
-    InPacket in;
-    initInPacket(&in, out.data.buffer, out.data.size);
-    char buffer[8];
-    size_t n = readInPacketString(&in, buffer, sizeof(buffer));
-    TEST_SIZE(14, n);
-    TEST_STRING("This is", buffer);
-    char buffer2[6];
-    size_t n2 = readInPacketString(&in, buffer2, sizeof(buffer2));
-    TEST_SIZE(10, n2);
-    TEST_STRING("Well ", buffer2);
-
-    destroyOutPacket(&out);
-}
-
-typedef struct {
-    u8 a;
-    u8 b;
-} PacketTest;
-
-static void testBinaryData(void) {
-    OutPacket out;
-    initOutPacket(&out);
-    PacketTest data = {56, 3};
-    writeOutPacket(&out, &data, sizeof(data));
-
-    InPacket in;
-    initInPacket(&in, out.data.buffer, out.data.size);
-
-    PacketTest inData;
-    TEST_FALSE(readInPacket(&in, &inData, sizeof(inData)));
-    TEST_TRUE(memcmp(&inData, &data, sizeof(inData)) == 0);
-
-    destroyOutPacket(&out);
-}
-
-static void testShortString(void) {
-    OutPacket out;
-    initOutPacket(&out);
-    writeOutPacketU16(&out, 200);
-    writeOutPacketU16(&out, 65535);
-
-    InPacket in;
-    initInPacket(&in, out.data.buffer, out.data.size);
-
-    char buffer[256];
-    size_t n = readInPacketString(&in, buffer, sizeof(buffer));
-    TEST_SIZE(200, n);
-    TEST_STRING("", buffer);
-
-    destroyOutPacket(&out);
-}
-
-static void tickClientN(int ticks) {
-    for(int i = 0; i < ticks; i++) {
-        tickClient();
-    }
-}
-
-static void tick(int ticks) {
-    for(int i = 0; i < ticks; i++) {
-        tickClient();
-        tickServer();
-    }
-}
-
-static bool clientConnected = false;
-static bool clientDisconnected = false;
-static bool clientPackage = false;
-static int packageCounter = 0;
-static bool serverConnected = false;
-static bool serverDisconnect = false;
-static u8 data1 = 0;
-static u16 data2 = 0;
-static u32 data3 = 0;
-static i8 data4 = 0;
-static i16 data5 = 0;
-static i32 data6 = 0;
-static i8 data7 = 0;
-static i16 data8 = 0;
-static i32 data9 = 0;
-static char data10[20];
-static float data11 = 0.0f;
-
-static void onServerConnect(Client) {
-    serverConnected = true;
-}
-
-static void onServerDisconnect(Client) {
-    serverDisconnect = true;
-}
-
-static void onServerPacket(Client client, InPacket* in) {
-    TEST_FALSE(readInPacketU8(in, &data1));
-    TEST_FALSE(readInPacketU16(in, &data2));
-    TEST_FALSE(readInPacketU32(in, &data3));
-    TEST_FALSE(readInPacketI8(in, &data4));
-    TEST_FALSE(readInPacketI16(in, &data5));
-    TEST_FALSE(readInPacketI32(in, &data6));
-    TEST_FALSE(readInPacketI8(in, &data7));
-    TEST_FALSE(readInPacketI16(in, &data8));
-    TEST_FALSE(readInPacketI32(in, &data9));
-    TEST_SIZE(9, readInPacketString(in, data10, sizeof(data10)));
-    TEST_FALSE(readInPacketFloat(in, &data11));
-
-    OutPacket out;
-    initOutPacket(&out);
-    if(packageCounter == 0) {
-        sendServerPacket(client, &out, PACKET_RELIABLE);
-    } else if(packageCounter == 1) {
-        sendServerPacket(client, &out, PACKET_SEQUENCED);
-    } else if(packageCounter == 2) {
-        sendServerPacket(client, &out, PACKET_UNSEQUENCED);
-    }
-    destroyOutPacket(&out);
-    packageCounter++;
-}
-
-static void onClientConnect() {
-    clientConnected = true;
-}
-
-static void onClientDisconnect() {
-    clientDisconnected = true;
-}
-
-static void onClientPacket(InPacket*) {
-    clientPackage = true;
-}
-
-static void testConnect(PacketSendMode mode) {
-    clientConnected = false;
-    clientDisconnected = false;
-    clientPackage = false;
-    serverConnected = false;
-    serverDisconnect = false;
-    data1 = 0;
-    data2 = 0;
-    data3 = 0;
-    data4 = 0;
-    data5 = 0;
-    data6 = 0;
-    data7 = 0;
-    data8 = 0;
-    data9 = 0;
-    *data10 = '\0';
-    data11 = 0.0f;
-
-    resetServerHandler();
-    setServerConnectHandler(onServerConnect);
-    setServerDisconnectHandler(onServerDisconnect);
-    setServerPacketHandler(onServerPacket);
-
-    resetClientHandler();
-    setClientConnectHandler(onClientConnect);
-    setClientDisconnectHandler(onClientDisconnect);
-    setClientPacketHandler(onClientPacket);
-
-    if(!TEST_FALSE(startServer(54321, 5))) {
-        return;
-    } else if(!TEST_FALSE(startClient())) {
-        return;
-    } else if(!TEST_FALSE(connectClient("127.0.0.1", 54321, 90))) {
-        return;
-    }
-    TEST_FALSE(isClientConnected());
-    TEST_TRUE(isClientConnecting());
-    tick(100);
-    TEST_TRUE(clientConnected);
-    TEST_TRUE(isClientConnected());
-    TEST_FALSE(isClientConnecting());
-
-    OutPacket out;
-    initOutPacket(&out);
-    writeOutPacketU8(&out, 0xF1);
-    writeOutPacketU16(&out, 0xF123);
-    writeOutPacketU32(&out, 0xF1234567);
-    writeOutPacketI8(&out, -0x71);
-    writeOutPacketI16(&out, -0x7123);
-    writeOutPacketI32(&out, -0x71234567);
-    writeOutPacketI8(&out, 0x71);
-    writeOutPacketI16(&out, 0x7123);
-    writeOutPacketI32(&out, 0x71234567);
-    const char s[] = "Hi there";
-    writeOutPacketString(&out, s);
-    writeOutPacketFloat(&out, 252345.983f);
-    sendClientPacket(&out, mode);
-    destroyOutPacket(&out);
-
-    tick(100);
-
-    TEST_TRUE(clientPackage);
-    TEST_TRUE(serverConnected);
-
-    TEST_U64(0xF1, data1);
-    TEST_U64(0xF123, data2);
-    TEST_U64(0xF1234567, data3);
-    TEST_I64(-0x71, data4);
-    TEST_I64(-0x7123, data5);
-    TEST_I64(-0x71234567, data6);
-    TEST_I64(0x71, data7);
-    TEST_I64(0x7123, data8);
-    TEST_I64(0x71234567, data9);
-    TEST_STRING(s, data10);
-    TEST_FLOAT(252345.983f, data11, 0.01f);
-
-    disconnectClient(90);
-    TEST_FALSE(isClientConnected());
-    TEST_FALSE(isClientConnecting());
-    tick(100);
-    TEST_TRUE(clientDisconnected);
-    TEST_TRUE(serverDisconnect);
-
-    stopClient();
-    stopServer();
-}
-
-static bool disconnected = false;
-
-static void testStopDisconnect(void) {
-    disconnected = true;
-}
-
-static void testDisconnect(void) {
-    disconnected = false;
-    resetClientHandler();
-    setClientDisconnectHandler(testStopDisconnect);
-    if(!TEST_FALSE(startClient())) {
-        return;
-    } else if(!TEST_FALSE(connectClient("127.0.0.1", 54321, 90))) {
-        return;
-    }
-    TEST_FALSE(isClientConnected());
-    TEST_TRUE(isClientConnecting());
-    disconnectClient(50);
-    tickClientN(100);
-    TEST_FALSE(isClientConnected());
-    TEST_FALSE(isClientConnecting());
-    TEST_TRUE(disconnected);
-    stopClient();
-}
-
-static void testStop(void) {
-    disconnected = false;
-    resetClientHandler();
-    setClientDisconnectHandler(testStopDisconnect);
-    if(!TEST_FALSE(startClient())) {
-        return;
-    } else if(!TEST_FALSE(connectClient("127.0.0.1", 54321, 90))) {
-        return;
-    }
-    TEST_FALSE(isClientConnected());
-    TEST_TRUE(isClientConnecting());
-    stopClient();
-    TEST_FALSE(isClientConnected());
-    TEST_FALSE(isClientConnecting());
-    TEST_TRUE(disconnected);
-}
-
-static void testClientStartFails(void) {
-    TEST_FALSE(startClient());
-    logLevel = LOG_NONE;
-    TEST_TRUE(startClient());
-    stopClient();
-#ifdef ERROR_SIMULATOR
-    failCounter = 0;
-    TEST_TRUE(startClient());
-    failCounter = 1;
-    TEST_TRUE(startClient());
-    failCounter = -1;
-#endif
-    logLevel = LOG_DEBUG;
-}
-
-static void testClientConnectionFails(void) {
-    resetClientHandler();
-    logLevel = LOG_NONE;
-    TEST_TRUE(connectClient("", 54321, 100));
-    TEST_FALSE(startClient());
-#ifdef ERROR_SIMULATOR
-    failCounter = 0;
-    TEST_TRUE(connectClient("", 54321, 100));
-    failCounter = -1;
-#endif
-    TEST_FALSE(connectClient("", 54321, 100));
-    TEST_TRUE(connectClient("", 54321, 100));
-    logLevel = LOG_DEBUG;
-    tickClientN(100);
-    stopClient();
-}
-
-static void testInvalidClientAccess(void) {
-    disconnectClient(0);
-    sendClientPacket(nullptr, 0);
-    tickClient();
-}
-
-static void testServerStartFails(void) {
-    logLevel = LOG_NONE;
-    TEST_TRUE(startServer(54321, 0));
-#ifdef ERROR_SIMULATOR
-    failCounter = 0;
-    TEST_TRUE(startServer(54321, 5));
-    failCounter = 1;
-    TEST_TRUE(startServer(54321, 5));
-    failCounter = -1;
-#endif
-    TEST_FALSE(startServer(54321, 5));
-    TEST_TRUE(startServer(54321, 5));
-    logLevel = LOG_DEBUG;
-    stopServer();
-}
-
-static void testServerClosesOnConnected(void) {
-    clientDisconnected = false;
-    TEST_FALSE(startServer(54321, 5));
-    TEST_FALSE(startClient());
-    setClientDisconnectHandler(onClientDisconnect);
-    TEST_FALSE(connectClient("127.0.0.1", 54321, 50));
-    tick(100);
-    TEST_TRUE(isClientConnected());
-    stopServer();
-
-    setClientTimeout(500, 500, 500);
-    for(int i = 0; i < 500 && isClientConnected(); i++) {
-        tickClient();
-        sleepNanos(10000000);
-    }
-    TEST_FALSE(isClientConnected());
-    TEST_TRUE(clientDisconnected);
-    stopClient();
-}
-
-static Client clientId = 0;
-
-static void onConnectSetClient(Client client) {
-    clientId = client;
-}
-
-static void testServerDisconnectsClient(void) {
-    clientDisconnected = false;
-    TEST_FALSE(startServer(54321, 5));
-    TEST_FALSE(startClient());
-    setClientDisconnectHandler(onClientDisconnect);
-    setServerConnectHandler(onConnectSetClient);
-    TEST_FALSE(connectClient("127.0.0.1", 54321, 50));
-    tick(100);
-    TEST_TRUE(isClientConnected());
-
-    disconnectServerClient(clientId);
-
-    for(int i = 0; i < 500 && isClientConnected(); i++) {
-        tickClient();
-        tickServer();
-        sleepNanos(10000000);
-    }
-    TEST_FALSE(isClientConnected());
-    TEST_TRUE(clientDisconnected);
-    stopClient();
-    stopServer();
-}
-
-static void onConnectSetTimeout(Client client) {
-    setServerTimeout(client, 500, 500, 500);
-}
-
-static void testClientClosesOnConnected(void) {
-    serverDisconnect = false;
-    TEST_FALSE(startServer(54321, 5));
-    TEST_FALSE(startClient());
-    setServerDisconnectHandler(onServerDisconnect);
-    setServerConnectHandler(onConnectSetTimeout);
-    TEST_FALSE(connectClient("127.0.0.1", 54321, 50));
-    tick(100);
-    TEST_TRUE(isClientConnected());
-#ifdef ERROR_SIMULATOR
-    failCounter = 0;
-#endif
-    stopClient();
-#ifdef ERROR_SIMULATOR
-    failCounter = -1;
-#endif
-    for(int i = 0; i < 500 && !serverDisconnect; i++) {
-        tickServer();
-        sleepNanos(10000000);
-    }
-    TEST_TRUE(serverDisconnect);
-    stopServer();
-}
-
-static void testInvalidServerAccess(void) {
-    tickServer();
-    sendServerPacket(0, nullptr, 0);
-    setServerTimeout(0, 500, 500, 500);
-    disconnectServerClient(0);
-}
-
-static void testDummyCallbacks(void) {
-    resetClientHandler();
-    resetServerHandler();
-    TEST_FALSE(startServer(54321, 5));
-    TEST_FALSE(startClient());
-    TEST_FALSE(connectClient("127.0.0.1", 54321, 50));
-    tick(100);
-    TEST_TRUE(isClientConnected());
-    OutPacket out;
-    initOutPacket(&out);
-    sendServerPacketBroadcast(&out, PACKET_RELIABLE);
-    sendClientPacket(&out, PACKET_RELIABLE);
-    tick(100);
-    destroyOutPacket(&out);
-    stopClient();
-    stopServer();
-}
-
-void testNetwork(void) {
-    testWriteRead();
-    testTooShortBuffer();
-    testBinaryData();
-    testShortString();
-    testConnect(PACKET_UNSEQUENCED);
-    testConnect(PACKET_SEQUENCED);
-    testConnect(PACKET_RELIABLE);
-    testDisconnect();
-    testStop();
-    testClientStartFails();
-    testClientConnectionFails();
-    testInvalidClientAccess();
-    testServerStartFails();
-    testServerClosesOnConnected();
-    testServerDisconnectsClient();
-    testClientClosesOnConnected();
-    testInvalidServerAccess();
-    testDummyCallbacks();
-}

+ 520 - 0
test/modules/NetworkTests.cpp

@@ -0,0 +1,520 @@
+#include <core/Clock.hpp>
+#include <core/Logger.hpp>
+#include <core/Utility.hpp>
+#include <cstring>
+
+#include "../Tests.hpp"
+#include "../src/ErrorSimulator.hpp"
+#include "core/Network.hpp"
+
+using Core::Client;
+using Core::InPacket;
+using Core::OutPacket;
+using Core::Server;
+
+#define TEST_READ(Type, type, value)  \
+    {                                 \
+        type u;                       \
+        TEST_FALSE(in.read##Type(u)); \
+        TEST_TRUE(u == value);        \
+    }
+#define TEST_READ_FAIL(Type, type)   \
+    {                                \
+        type u;                      \
+        TEST_TRUE(in.read##Type(u)); \
+    }
+
+static void testWriteRead() {
+    OutPacket out;
+    out.writeU8(200);
+    out.writeU16(6656);
+    out.writeU32(348'923'689);
+    out.writeI8(90);
+    out.writeI8(-35);
+    out.writeI16(843);
+    out.writeI16(-8961);
+    out.writeI32(100'430'199);
+    out.writeI32(-534'534);
+    out.writeFloat(64564.5346f);
+    const char s[] = "This is Great";
+    out.writeString(s);
+
+    InPacket in(out.data.getData(), out.data.getLength());
+    TEST_READ(U8, u8, 200)
+    TEST_READ(U16, u16, 6656)
+    TEST_READ(U32, u32, 348'923'689)
+    TEST_READ(I8, i8, 90)
+    TEST_READ(I8, i8, -35)
+    TEST_READ(I16, i16, 843)
+    TEST_READ(I16, i16, -8961)
+    TEST_READ(I32, i32, 100'430'199)
+    TEST_READ(I32, i32, -534'534)
+    float f;
+    TEST_FALSE(in.readFloat(f));
+    TEST_FLOAT(64564.5346f, f, 0.00001f);
+    char buffer[256];
+    size_t n = in.readString(buffer, sizeof(buffer));
+    TEST(14, n);
+    TEST_STRING(s, buffer);
+
+    TEST_READ_FAIL(U8, u8)
+    TEST_READ_FAIL(U16, u16)
+    TEST_READ_FAIL(U32, u32)
+    TEST_READ_FAIL(I8, i8)
+    TEST_READ_FAIL(I16, i16)
+    TEST_READ_FAIL(I32, i32)
+    TEST_READ_FAIL(Float, float)
+    TEST(0, in.readString(nullptr, 0));
+    TEST(0, in.readString(buffer, sizeof(buffer)));
+}
+
+static void testTooShortBuffer() {
+    OutPacket out;
+    out.writeString("This is Great");
+    out.writeString("Well hoho");
+
+    InPacket in(out.data.getData(), out.data.getLength());
+    char buffer[8];
+    size_t n = in.readString(buffer, sizeof(buffer));
+    TEST(14, n);
+    TEST_STRING("This is", buffer);
+    char buffer2[6];
+    size_t n2 = in.readString(buffer2, sizeof(buffer2));
+    TEST(10, n2);
+    TEST_STRING("Well ", buffer2);
+}
+
+struct PacketTest {
+    u8 a;
+    u8 b;
+};
+
+static void testBinaryData() {
+    OutPacket out;
+    PacketTest data = {56, 3};
+    out.write(&data, sizeof(data));
+
+    InPacket in(out.data.getData(), out.data.getLength());
+
+    PacketTest inData;
+    TEST_FALSE(in.read(&inData, sizeof(inData)));
+    TEST_TRUE(memcmp(&inData, &data, sizeof(inData)) == 0);
+}
+
+static void testShortString() {
+    OutPacket out;
+    out.writeU16(200);
+    out.writeU16(65'535);
+
+    InPacket in(out.data.getData(), out.data.getLength());
+
+    char buffer[256];
+    size_t n = in.readString(buffer, sizeof(buffer));
+    TEST(200, n);
+    TEST_STRING("", buffer);
+}
+
+using BoolFunction = bool (*)(void*);
+
+static void tick(Server& s, Client& c, BoolFunction f, void* fd) {
+    for(int i = 0; i < 100'000 && f(fd); i++) {
+        c.tick();
+        s.tick();
+    }
+}
+
+static void tickUntilConnected(Server& s, Client& c) {
+    tick(
+        s, c, [](void* v) { return !static_cast<Client*>(v)->isConnected(); },
+        &c);
+}
+
+static bool clientConnected = false;
+static bool clientDisconnected = false;
+static bool clientPackage = false;
+static int packageCounter = 0;
+static bool serverConnected = false;
+static bool serverDisconnect = false;
+static u8 data1 = 0;
+static u16 data2 = 0;
+static u32 data3 = 0;
+static i8 data4 = 0;
+static i16 data5 = 0;
+static i32 data6 = 0;
+static i8 data7 = 0;
+static i16 data8 = 0;
+static i32 data9 = 0;
+static char data10[20];
+static float data11 = 0.0f;
+
+static void onServerConnect(Server&, Core::ClientHandle) {
+    serverConnected = true;
+}
+
+static void onServerDisconnect(Server&, Core::ClientHandle) {
+    serverDisconnect = true;
+}
+
+static void onServerPacket(Server& s, Core::ClientHandle client, InPacket& in) {
+    TEST_FALSE(in.readU8(data1));
+    TEST_FALSE(in.readU16(data2));
+    TEST_FALSE(in.readU32(data3));
+    TEST_FALSE(in.readI8(data4));
+    TEST_FALSE(in.readI16(data5));
+    TEST_FALSE(in.readI32(data6));
+    TEST_FALSE(in.readI8(data7));
+    TEST_FALSE(in.readI16(data8));
+    TEST_FALSE(in.readI32(data9));
+    TEST(9, in.readString(data10, sizeof(data10)));
+    TEST_FALSE(in.readFloat(data11));
+
+    OutPacket out;
+    if(packageCounter == 0) {
+        s.sendPacket(client, out, Core::PacketSendMode::RELIABLE);
+    } else if(packageCounter == 1) {
+        s.sendPacket(client, out, Core::PacketSendMode::SEQUENCED);
+    } else if(packageCounter == 2) {
+        s.sendPacket(client, out, Core::PacketSendMode::UNSEQUENCED);
+    }
+    packageCounter++;
+}
+
+static void onClientConnect() {
+    clientConnected = true;
+}
+
+static void onClientDisconnect() {
+    clientDisconnected = true;
+}
+
+static void onClientPacket(InPacket&) {
+    clientPackage = true;
+}
+
+static void testConnect(Core::PacketSendMode mode) {
+    clientConnected = false;
+    clientDisconnected = false;
+    clientPackage = false;
+    serverConnected = false;
+    serverDisconnect = false;
+    data1 = 0;
+    data2 = 0;
+    data3 = 0;
+    data4 = 0;
+    data5 = 0;
+    data6 = 0;
+    data7 = 0;
+    data8 = 0;
+    data9 = 0;
+    *data10 = '\0';
+    data11 = 0.0f;
+
+    Server s;
+    s.resetHandler();
+    s.setConnectHandler(onServerConnect);
+    s.setDisconnectHandler(onServerDisconnect);
+    s.setPacketHandler(onServerPacket);
+
+    Client c;
+    c.resetHandler();
+    c.setConnectHandler(onClientConnect);
+    c.setDisconnectHandler(onClientDisconnect);
+    c.setPacketHandler(onClientPacket);
+
+    if(!TEST_FALSE(s.start(54'321, 5))) {
+        return;
+    } else if(!TEST_FALSE(c.start())) {
+        return;
+    } else if(!TEST_FALSE(c.connect("127.0.0.1", 54'321, 90))) {
+        return;
+    }
+    TEST_FALSE(c.isConnected());
+    TEST_TRUE(c.isConnecting());
+    tickUntilConnected(s, c);
+    TEST_TRUE(clientConnected);
+    TEST_TRUE(c.isConnected());
+    TEST_FALSE(c.isConnecting());
+
+    OutPacket out;
+    out.writeU8(0xF1);
+    out.writeU16(0xF123);
+    out.writeU32(0xF123'4567);
+    out.writeI8(-0x71);
+    out.writeI16(-0x7123);
+    out.writeI32(-0x7123'4567);
+    out.writeI8(0x71);
+    out.writeI16(0x7123);
+    out.writeI32(0x7123'4567);
+    const char st[] = "Hi there";
+    out.writeString(st);
+    out.writeFloat(252345.983f);
+    c.sendPacket(out, mode);
+
+    tick(s, c, [](void*) { return !clientPackage || !serverConnected; }, &c);
+    TEST_TRUE(clientPackage);
+    TEST_TRUE(serverConnected);
+
+    TEST(0xF1, data1);
+    TEST(0xF123, data2);
+    TEST(0xF123'4567, data3);
+    TEST(-0x71, data4);
+    TEST(-0x7123, data5);
+    TEST(-0x7123'4567, data6);
+    TEST(0x71, data7);
+    TEST(0x7123, data8);
+    TEST(0x7123'4567, data9);
+    TEST_STRING(st, data10);
+    TEST_FLOAT(252345.983f, data11, 0.01f);
+
+    c.disconnect(90);
+    TEST_FALSE(c.isConnected());
+    TEST_FALSE(c.isConnecting());
+    tick(
+        s, c, [](void*) { return !clientDisconnected || !serverDisconnect; },
+        &c);
+    TEST_TRUE(clientDisconnected);
+    TEST_TRUE(serverDisconnect);
+
+    c.stop();
+    s.stop();
+}
+
+static bool disconnected = false;
+
+static void testStopDisconnect() {
+    disconnected = true;
+}
+
+static void testDisconnect() {
+    disconnected = false;
+    Client c;
+    c.resetHandler();
+    c.setDisconnectHandler(testStopDisconnect);
+    if(!TEST_FALSE(c.start())) {
+        return;
+    } else if(!TEST_FALSE(c.connect("127.0.0.1", 54'321, 90))) {
+        return;
+    }
+    TEST_FALSE(c.isConnected());
+    TEST_TRUE(c.isConnecting());
+    c.disconnect(50);
+    Server s;
+    tick(s, c, [](void*) { return !disconnected; }, &c);
+    TEST_FALSE(c.isConnected());
+    TEST_FALSE(c.isConnecting());
+    TEST_TRUE(disconnected);
+    c.stop();
+}
+
+static void testStop() {
+    disconnected = false;
+    Client c;
+    c.resetHandler();
+    c.setDisconnectHandler(testStopDisconnect);
+    if(!TEST_FALSE(c.start())) {
+        return;
+    } else if(!TEST_FALSE(c.connect("127.0.0.1", 54'321, 90))) {
+        return;
+    }
+    TEST_FALSE(c.isConnected());
+    TEST_TRUE(c.isConnecting());
+    c.stop();
+    TEST_FALSE(c.isConnected());
+    TEST_FALSE(c.isConnecting());
+    TEST_TRUE(disconnected);
+}
+
+static void testClientStartFails() {
+    Client c;
+    TEST_FALSE(c.start());
+    useReport = false;
+    TEST_TRUE(c.start());
+    c.stop();
+#ifdef ERROR_SIMULATOR
+    failCounter = 0;
+    TEST_TRUE(c.start());
+    failCounter = 1;
+    TEST_TRUE(c.start());
+    failCounter = -1;
+#endif
+    useReport = true;
+}
+
+static void testClientConnectionFails() {
+    Client c;
+    c.resetHandler();
+    useReport = false;
+    TEST_TRUE(c.connect("", 54'321, 100));
+    TEST_FALSE(c.start());
+#ifdef ERROR_SIMULATOR
+    failCounter = 0;
+    TEST_TRUE(c.connect("", 54'321, 100));
+    failCounter = -1;
+#endif
+    TEST_FALSE(c.connect("", 54'321, 100));
+    TEST_TRUE(c.connect("", 54'321, 100));
+    useReport = true;
+    for(int i = 0; i < 100; i++) {
+        c.tick();
+    }
+    c.stop();
+}
+
+static void testInvalidClientAccess() {
+    Client c;
+    c.disconnect(0);
+    OutPacket out;
+    c.sendPacket(out, Core::PacketSendMode::RELIABLE);
+    c.tick();
+}
+
+static void testServerStartFails() {
+    useReport = false;
+    Server s;
+    TEST_TRUE(s.start(54'321, 0));
+#ifdef ERROR_SIMULATOR
+    failCounter = 0;
+    TEST_TRUE(s.start(54'321, 5));
+    failCounter = 1;
+    TEST_TRUE(s.start(54'321, 5));
+    failCounter = -1;
+#endif
+    TEST_FALSE(s.start(54'321, 5));
+    TEST_TRUE(s.start(54'321, 5));
+    useReport = true;
+    s.stop();
+}
+
+static void testServerClosesOnConnected() {
+    Server s;
+    Client c;
+    clientDisconnected = false;
+    TEST_FALSE(s.start(54'321, 5));
+    TEST_FALSE(c.start());
+    c.setDisconnectHandler(onClientDisconnect);
+    TEST_FALSE(c.connect("127.0.0.1", 54'321, 50));
+    tickUntilConnected(s, c);
+    TEST_TRUE(c.isConnected());
+    s.stop();
+
+    c.setTimeout(500, 500, 500);
+    for(int i = 0; i < 500 && c.isConnected(); i++) {
+        c.tick();
+        Core::Clock::sleepMillis(10);
+    }
+    TEST_FALSE(c.isConnected());
+    TEST_TRUE(clientDisconnected);
+    c.stop();
+}
+
+static Core::ClientHandle clientId = 0;
+
+static void onConnectSetClient(Server&, Core::ClientHandle client) {
+    clientId = client;
+}
+
+static void testServerDisconnectsClient() {
+    clientDisconnected = false;
+    Server s;
+    Client c;
+    TEST_FALSE(s.start(54'321, 5));
+    TEST_FALSE(c.start());
+    c.setDisconnectHandler(onClientDisconnect);
+    s.setConnectHandler(onConnectSetClient);
+    TEST_FALSE(c.connect("127.0.0.1", 54'321, 50));
+    tickUntilConnected(s, c);
+    TEST_TRUE(c.isConnected());
+
+    s.disconnectClient(clientId);
+
+    for(int i = 0; i < 500 && c.isConnected(); i++) {
+        c.tick();
+        s.tick();
+        Core::Clock::sleepMillis(10);
+    }
+    TEST_FALSE(c.isConnected());
+    TEST_TRUE(clientDisconnected);
+    c.stop();
+    s.stop();
+}
+
+static void onConnectSetTimeout(Server& s, Core::ClientHandle client) {
+    s.setTimeout(client, 500, 500, 500);
+}
+
+static void testClientClosesOnConnected() {
+    serverDisconnect = false;
+    Server s;
+    Client c;
+    TEST_FALSE(s.start(54'321, 5));
+    TEST_FALSE(c.start());
+    s.setDisconnectHandler(onServerDisconnect);
+    s.setConnectHandler(onConnectSetTimeout);
+    TEST_FALSE(c.connect("127.0.0.1", 54'321, 50));
+    tickUntilConnected(s, c);
+    TEST_TRUE(c.isConnected());
+#ifdef ERROR_SIMULATOR
+    failCounter = 0;
+#endif
+    c.stop();
+#ifdef ERROR_SIMULATOR
+    failCounter = -1;
+#endif
+    for(int i = 0; i < 500 && !serverDisconnect; i++) {
+        s.tick();
+        Core::Clock::sleepMillis(10);
+    }
+    TEST_TRUE(serverDisconnect);
+    s.stop();
+}
+
+static void testInvalidServerAccess() {
+    Server s;
+    s.tick();
+    OutPacket out;
+    s.sendPacket(0, out, Core::PacketSendMode::RELIABLE);
+    s.setTimeout(0, 500, 500, 500);
+    s.disconnectClient(0);
+}
+
+static void testDummyCallbacks() {
+    Server s;
+    Client c;
+    c.resetHandler();
+    s.resetHandler();
+    TEST_FALSE(s.start(54'321, 5));
+    TEST_FALSE(c.start());
+    TEST_FALSE(c.connect("127.0.0.1", 54'321, 50));
+    tickUntilConnected(s, c);
+    TEST_TRUE(c.isConnected());
+    OutPacket out;
+    s.sendPacketBroadcast(out, Core::PacketSendMode::RELIABLE);
+    c.sendPacket(out, Core::PacketSendMode::RELIABLE);
+    for(int i = 0; i < 100; i++) {
+        c.tick();
+        s.tick();
+    }
+    c.stop();
+    s.stop();
+}
+
+void testNetwork() {
+    testWriteRead();
+    testTooShortBuffer();
+    testBinaryData();
+    testShortString();
+    testConnect(Core::PacketSendMode::UNSEQUENCED);
+    testConnect(Core::PacketSendMode::SEQUENCED);
+    testConnect(Core::PacketSendMode::RELIABLE);
+    testDisconnect();
+    testStop();
+    testClientStartFails();
+    testClientConnectionFails();
+    testInvalidClientAccess();
+    testServerStartFails();
+    testServerClosesOnConnected();
+    testServerDisconnectsClient();
+    testClientClosesOnConnected();
+    testInvalidServerAccess();
+    testDummyCallbacks();
+}

+ 0 - 78
test/modules/WindowManagerTests.c

@@ -1,78 +0,0 @@
-#include <GLFW/glfw3.h>
-#include <core/Logger.h>
-#include <stdio.h>
-
-#include "../Tests.h"
-#include "core/WindowManager.h"
-
-static int ticks = 2;
-static Button closeButton = 0;
-static Button testButton = 0;
-static Button textButton = 0;
-
-static bool isRunning(void*) {
-    return !shouldWindowClose() && /*ticks > 0 &&*/ !isButtonDown(closeButton);
-}
-
-static void tick(void*) {
-    ticks -= ticks > 0;
-    if(!isInputEnabled()) {
-        printf(
-            "TPS: %.3f\nFPS: %.3f\n", (double)getWindowTicksPerSecond(),
-            (double)getWindowFramesPerSecond());
-        printf(
-            "%12s | Down: %d | DownTime: %3d | Released: %d\n",
-            getButtonName(closeButton), isButtonDown(closeButton),
-            getButtonDownTime(closeButton), wasButtonReleased(closeButton));
-        printf(
-            "%12s | Down: %d | DownTime: %3d | Released: %d\n",
-            getButtonName(testButton), isButtonDown(testButton),
-            getButtonDownTime(testButton), wasButtonReleased(testButton));
-        Vector2 mouse = getLastMousePosition();
-        printf(
-            "Mouse: %.2f %.2f\n", (double)mouse.data[0], (double)mouse.data[1]);
-    }
-    if(getButtonDownTime(textButton) == 1) {
-        if(isInputEnabled()) {
-            disableInput();
-        } else {
-            enableInput();
-        }
-    }
-    if(isInputEnabled()) {
-        printf("Input: '%s'\n", getInputString());
-        printf("Cursor: %zu\n", getInputCursor());
-    }
-}
-
-static void render(void*, float) {
-}
-
-static void printReport(
-    LogLevel l, const char* file, int line, void*, const char* message) {
-    printLog(l, file, line, "", TERMINAL_RED, "%s", message);
-}
-
-void testWindow(void) {
-    setReportHandler(printReport, nullptr);
-
-    WindowOptions options = {{{800, 480}}, false, "Test"};
-    if(openWindow(&options)) {
-        return;
-    }
-
-    closeButton = addButton("Close Button");
-    bindKeyToButton(closeButton, GLFW_KEY_Q);
-    testButton = addButton("Test Button");
-    bindKeyToButton(testButton, GLFW_KEY_T);
-    textButton = addButton("Text Button");
-    bindKeyToButton(textButton, GLFW_KEY_C);
-
-    showWindow();
-    setWindowRunHandler(isRunning, nullptr);
-    setWindowTickHandler(tick, nullptr);
-    setWindowRenderHandler(render, nullptr);
-    setWindowNanosPerTick(50'000'000);
-    runWindow();
-    closeWindow();
-}

+ 79 - 0
test/modules/WindowManagerTests.cpp

@@ -0,0 +1,79 @@
+#include <core/Logger.hpp>
+#include <cstdio>
+
+#include <GLFW/glfw3.h>
+
+#include "../Tests.hpp"
+#include "core/WindowManager.hpp"
+
+namespace W = Core::Window;
+
+static W::Button closeButton = 0;
+static W::Button testButton = 0;
+static W::Button textButton = 0;
+
+static bool isRunning(void*) {
+    return !W::shouldClose() && !W::isButtonDown(closeButton);
+}
+
+static void tick(void*) {
+    if(!W::isInputEnabled()) {
+        LOG_INFO(
+            "TPS: #\nFPS: #\n", W::getTicksPerSecond(),
+            W::getFramesPerSecond());
+        printf(
+            "%12s | Down: %d | DownTime: %3d | Released: %d\n",
+            W::getButtonName(closeButton), W::isButtonDown(closeButton),
+            W::getButtonDownTime(closeButton),
+            W::wasButtonReleased(closeButton));
+        printf(
+            "%12s | Down: %d | DownTime: %3d | Released: %d\n",
+            W::getButtonName(testButton), W::isButtonDown(testButton),
+            W::getButtonDownTime(testButton), W::wasButtonReleased(testButton));
+        Core::Vector2 mouse = W::getLastMousePosition();
+        LOG_INFO("Mouse: # #\n", mouse[0], mouse[1]);
+    }
+    if(W::getButtonDownTime(textButton) == 1) {
+        if(W::isInputEnabled()) {
+            W::disableInput();
+        } else {
+            W::enableInput();
+        }
+    }
+    if(W::isInputEnabled()) {
+        printf("Input: '%s'\n", W::getInputString());
+        printf("Cursor: %zu\n", W::getInputCursor());
+    }
+}
+
+static void render(void*, float) {
+}
+
+static void printReport(
+    Core::LogLevel l, const char* file, int line, void*, const char* message) {
+    printLog(l, file, line, "", TERMINAL_RED, "%s", message);
+}
+
+void testWindow() {
+    setReportHandler(printReport, nullptr);
+
+    W::Options options = {{800, 480}, false, "Test"};
+    if(W::open(&options)) {
+        return;
+    }
+
+    closeButton = W::addButton("Close Button");
+    W::bindKeyToButton(closeButton, GLFW_KEY_Q);
+    testButton = W::addButton("Test Button");
+    W::bindKeyToButton(testButton, GLFW_KEY_T);
+    textButton = W::addButton("Text Button");
+    W::bindKeyToButton(textButton, GLFW_KEY_C);
+
+    W::show();
+    W::setRunHandler(isRunning, nullptr);
+    W::setTickHandler(tick, nullptr);
+    W::setRenderHandler(render, nullptr);
+    W::setNanosPerTick(50'000'000);
+    W::run();
+    W::close();
+}