summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author0scar <qgt268@alumni.ku.dk>2020-09-29 06:51:02 +0000
committer0scar <qgt268@alumni.ku.dk>2023-07-28 09:48:17 +0000
commit6c16f339224a4736f4ed57d15bb3e5f968a635ab (patch)
treeab13afea4b6f9acfb4a139a3125f265c90bc9d80
Initial independent commit
-rw-r--r--.gitignore4
-rw-r--r--CMakeLists.txt88
-rw-r--r--LICENSE.txt165
-rw-r--r--README.md15
-rw-r--r--include/engine/btree.h57
-rw-r--r--include/engine/engine.h138
-rw-r--r--include/engine/fov.h26
-rw-r--r--include/engine/hashmap.h55
-rw-r--r--include/engine/list.h11
-rw-r--r--include/engine/logging.h44
-rw-r--r--include/engine/memory.h25
-rw-r--r--include/engine/rendering.h81
-rw-r--r--include/engine/stack.h49
-rw-r--r--include/engine/state.h22
-rw-r--r--include/engine/types.h32
-rw-r--r--include/engine/ui.h234
-rw-r--r--include/engine/utils.h34
-rw-r--r--include/engine/vector.h31
-rw-r--r--include_states.h.in0
-rw-r--r--src/btree.c850
-rw-r--r--src/engine.c686
-rw-r--r--src/fov.c97
-rw-r--r--src/hashmap.c5
-rw-r--r--src/logging.c80
-rw-r--r--src/memory.c62
-rw-r--r--src/rendering.c152
-rw-r--r--src/stack.c88
-rw-r--r--src/state.c91
-rw-r--r--src/ui_positioning.c877
-rw-r--r--src/ui_rendering.c295
-rw-r--r--src/utils.c85
-rw-r--r--src/vector.c28
-rw-r--r--state_type_list.h.in0
-rwxr-xr-xtools/scripts/lint28
-rw-r--r--tools/scripts/scaling.py27
35 files changed, 4562 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e27d710
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+rogue
+obj
+compile_commands.json
+build
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..6067c5c
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,88 @@
+cmake_minimum_required(VERSION 3.16)
+
+if(NOT DEFINED PROJECT_NAME)
+ set(NOT_SUBPROJECT ON)
+else()
+ set(NOT_SUBPROJECT OFF)
+endif()
+
+option(ENGINE_DEBUG "Compile engine with debugging features" OFF)
+option(ENGINE_HOT_RELOADING "Compile with hot-reloading enabled (only works with debug mode)" ON)
+
+set(ENGINE_SOURCES
+ src/btree.c
+ src/engine.c
+ src/fov.c
+ src/hashmap.c
+ src/logging.c
+ src/memory.c
+ src/rendering.c
+ src/stack.c
+ src/state.c
+ src/ui_positioning.c
+ src/ui_rendering.c
+ src/utils.c
+ src/vector.c
+)
+
+add_library(engine ${ENGINE_SOURCES})
+
+target_include_directories(engine PUBLIC
+ include
+ ${CMAKE_BINARY_DIR}/include
+ ${ENGINE_INCLUDE})
+target_link_libraries(engine SDL2 SDL2_image SDL2_ttf m)
+target_compile_features(engine PRIVATE c_std_99)
+
+configure_file(${CMAKE_CURRENT_LIST_DIR}/state_type_list.h.in
+ ${CMAKE_BINARY_DIR}/include/state_type_list.h)
+
+configure_file(${CMAKE_CURRENT_LIST_DIR}/include_states.h.in
+ ${CMAKE_BINARY_DIR}/include/include_states.h)
+
+
+# Add the directory to the list of states
+# The directorys contents will be compiled into a shared object file and linked
+# to the main executable
+macro(add_state STATEDIR)
+ if(NOT_SUBPROJECT)
+ else()
+ set_property(TARGET engine
+ APPEND PROPERTY INCLUDE_DIRECTORIES
+ ${CMAKE_SOURCE_DIR}/state_${STATEDIR}/include)
+
+ file(APPEND ${CMAKE_BINARY_DIR}/include/state_type_list.h
+ "State(${STATEDIR})\n")
+
+ file(APPEND ${CMAKE_BINARY_DIR}/include/include_states.h
+ "#include <states/${STATEDIR}.h>\n")
+
+ file(GLOB STATE_SOURCES
+ LIST_DIRECTORIES false
+ state_${STATEDIR}/src/*.c
+ )
+
+ # TODO: When state reloading is implemented properly, add MODULE library
+ # option In general, this should only be available when debugging.
+ if(BUILD_SHARED_LIBS)
+ add_library(${STATEDIR} SHARED ${STATE_SOURCES})
+ else()
+ add_library(${STATEDIR} OBJECT ${STATE_SOURCES})
+ endif()
+
+ target_include_directories(${STATEDIR} PUBLIC
+ state_${STATEDIR}/include
+ engine/include
+ ${CMAKE_BINARY_DIR}/include
+ include
+ )
+
+ set_property(TARGET engine
+ APPEND PROPERTY LINK_LIBRARIES
+ ${STATEDIR})
+
+ endif()
+
+ list(APPEND STATE_LIST ${STATEDIR})
+ #list(APPEND ENGINE_INCLUDE state_${STATEDIR}/include)
+endmacro()
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..0a04128
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5996b17
--- /dev/null
+++ b/README.md
@@ -0,0 +1,15 @@
+Daw
+===
+
+Daw is yet another amateur WIP game engine.
+
+It is written in C99 and can be compiled and linked to easily in any CMake
+project.
+
+It currently has a very limited amount of features, but comes with some
+stand-alone utility sources:
+* [BTree](src/btree.c), a copy of [my own btree implementation](https://github.com/0undefined/btree).
+* [stack](src/stack.c), yet another implementation of a stack.
+* [list](include/engine/list.h), just a linked list.
+* The `memory` module provides a simple arena allocator.
+* The `vector` module just provides some simple 2d vector functions and datatype.
diff --git a/include/engine/btree.h b/include/engine/btree.h
new file mode 100644
index 0000000..5405e7e
--- /dev/null
+++ b/include/engine/btree.h
@@ -0,0 +1,57 @@
+#ifndef BTREE_H
+#define BTREE_H
+
+#include <stddef.h>
+
+#define BTREE_DEGREE_DEFAULT 4
+
+#define BTREE_SIZE_MIN 8
+#define BTREE_SIZE_MAX 4096
+
+#define BTREE_CMP_LT ( -1 )
+#define BTREE_CMP_EQ ( 0 )
+#define BTREE_CMP_GT ( 1 )
+
+struct btree;
+struct btree_iter_t;
+
+/* elem_size: the size of the elements, typically `sizeof(struct <your struct>)`
+ * t: degree of the btree, if you're in doubt, use `BTREE_SIZE_DEFAULT`
+ * cmp: comparison function, in order to support any operations on the tree.
+ *
+ * This function just calls `btree_new_with_allocator` with `free` and `malloc`
+ * as initializers.
+ */
+struct btree* btree_new(size_t elem_size,
+ size_t t,
+ int (*cmp)(const void *a, const void *b));
+
+/* Same as `btree_new`, except that it actually initializes a btree, but with
+ * the given allocators.
+ */
+struct btree* btree_new_with_allocator(
+ size_t elem_size,
+ size_t t,
+ int (*cmp)(const void *a, const void *b),
+ void *(*alloc)(size_t),
+ void (*dealloc)(void*));
+
+void btree_free(struct btree **btree);
+
+void* btree_search(struct btree *btree, void *elem);
+void btree_insert(struct btree *btree, void *elem);
+int btree_delete(struct btree *btree, void *elem);
+
+void btree_print(struct btree *btree, void (*print_elem)(const void*));
+
+void* btree_first(struct btree *btree);
+void* btree_last(struct btree *btree);
+
+size_t btree_size(struct btree *btree);
+
+struct btree_iter_t* btree_iter_t_new(struct btree* tree);
+void btree_iter_t_reset(struct btree *tree, struct btree_iter_t** it);
+
+void* btree_iter(struct btree *tree, struct btree_iter_t *iter);
+
+#endif
diff --git a/include/engine/engine.h b/include/engine/engine.h
new file mode 100644
index 0000000..bd961a6
--- /dev/null
+++ b/include/engine/engine.h
@@ -0,0 +1,138 @@
+#ifndef ENGINE_H
+#define ENGINE_H
+
+#include <stdbool.h>
+
+#include <engine/types.h>
+#include <engine/stack.h>
+#include <engine/vector.h>
+#include <engine/memory.h>
+#include <engine/logging.h>
+#include <engine/state.h>
+
+typedef struct {
+ u32 texture_id;
+ i32 x, y,
+ w, h;
+} RenderUnit;
+
+typedef struct {
+ const char *font_path;
+ i32 ptsize;
+} FontSpec;
+
+typedef struct {
+ i32 width;
+ i32 height;
+ const char *path;
+} TextureSpec;
+
+typedef struct Window Window;
+
+typedef struct Keybinding {
+ i32 keycode;
+ i32 modifiers;
+ void (*action)(void*);
+} Keybinding;
+
+typedef struct {
+ void *data; /* Contains textures and such */
+ u64 data_len;
+
+ Window *window;
+ bool quit;
+
+ u64 frame;
+ f32 fps_target;
+
+ v2_i32 mouse_pos;
+
+ v2_i32 mousedown;
+ v2_i32 mouseup;
+
+ bool mouse_lclick;
+ bool mouse_rclick;
+
+ i32 camera_x;
+ i32 camera_y;
+
+ /* Text input/editing is currently not used/implemented */
+ char *edit_text;
+ usize edit_pos;
+
+ memory *mem;
+
+ void *bindings;
+} Platform;
+
+/* Essential functions */
+Platform *engine_init(
+ const char *windowtitle,
+ v2_i32 windowsize,
+ const f32 render_scale,
+ const u32 flags,
+ const usize initial_memory,
+ const FontSpec *fonts[],
+ const TextureSpec *textures[]);
+
+i32 engine_run(Platform *p, StateType initial_state);
+
+void engine_stop(Platform *p);
+
+/* Utility functions */
+void engine_fps_max(u64 cap);
+void engine_bindkey(i32 key, void (*action)(void*));
+
+void render_set_zoom(f32 new_zoom);
+void render_adjust_zoom(f32 diff);
+void render_add_unit(RenderUnit *u);
+
+u32 get_time(void);
+v2_i32 get_windowsize(void);
+v2_i32 *get_mousepos(void);
+
+#include "rendering.h"
+
+#ifdef ENGINE_INTERNALS
+
+#define MAX(a,b) (a > b ? a : b)
+
+/* Window */
+struct Window {
+ SDL_Window *window;
+ SDL_Renderer *renderer;
+ f32 render_scale;
+
+ v2_i32 windowsize;
+
+ i32* game_w;
+ i32* game_h;
+};
+
+typedef struct {
+ SDL_Texture *texture;
+ const i32 tilesize;
+ const i32 width;
+ const i32 height;
+} Texture;
+
+struct Resources {
+ usize textures_len;
+ usize textures_size;
+ usize fonts_len;
+
+ usize texturepaths_len;
+ usize fontpaths_len;
+
+ /* Paths for our sources, kept in case the user wants to reload them */
+ TextureSpec **texture_paths;
+ FontSpec **font_paths;
+
+ /* Our actual sources */
+ Texture **textures;
+ TTF_Font **fonts;
+};
+
+
+#endif
+#endif
diff --git a/include/engine/fov.h b/include/engine/fov.h
new file mode 100644
index 0000000..3af421e
--- /dev/null
+++ b/include/engine/fov.h
@@ -0,0 +1,26 @@
+#ifndef ENGINE_FOV_H
+#define ENGINE_FOV_H
+
+#include <stdbool.h>
+#include "types.h"
+#include "vector.h"
+
+/* `fov_shadowcast`: */
+/* map: your 2D enum tile grid
+ * mapsize: x: width, y: height of the map
+ * visblocking: pointer to a function that returns `true` when receiving a
+ * pointer to a LOS blocking tile
+ * lightmap: [out] 2D lightmap, this is simply overwritten with the distance to
+ * the source.
+ * range: visibility range/radius.
+ * src: 2D point to calculate FOV from
+ * */
+void fov_shadowcast(
+ const void *map,
+ const v2_i32 mapsize,
+ bool (*visblocking)(const void*),
+ i32 *lightmap,
+ const i32 range,
+ v2_i32 src);
+
+#endif
diff --git a/include/engine/hashmap.h b/include/engine/hashmap.h
new file mode 100644
index 0000000..4b97b8e
--- /dev/null
+++ b/include/engine/hashmap.h
@@ -0,0 +1,55 @@
+#ifndef ENGINE_HASHMAP_H
+#define ENGINE_HASHMAP_H
+
+#include "types.h"
+
+#include <stdlib.h>
+#include "list.h"
+#include "memory.h"
+
+
+i32 lolhash(const usize s, i32 v);
+
+/* Define a linked list before using this */
+/* Example: DEFINE_LLIST(i32) */
+#define DEFINE_HASHMAP(type, lsize, cmp, type_to_int) \
+typedef DEFINE_LLIST(type); \
+typedef struct hashmap_##type { \
+ usize size; \
+ List_##type elems[64]; \
+} hashmap_##type; \
+ \
+type* hashmap_##type##_lookup(hashmap_##type* hmap, const type* val) { \
+ const i32 idx = lolhash(64, type_to_int(val)); \
+ List_##type *head = &hmap->elems[idx]; \
+ while (head != NULL) { \
+ if (!cmp(&(head->value), val)) return &(head->value); \
+ head = head->next; \
+ } \
+ return NULL; \
+} \
+ \
+void hashmap_##type##_insert(memory *m, hashmap_##type *hmap, const type *val) { \
+ const i32 idx = lolhash(64, type_to_int(val)); \
+ List_##type *head = &(hmap->elems[idx]); \
+ \
+ /* This is highly dependant on whether the memory is zero-initialized */ \
+ if (!type_to_int(&(head->value))) \
+ { \
+ memcpy(&(head->value), val, sizeof(type)); \
+ return; \
+ } \
+ \
+ \
+ while (head->next != NULL && cmp(&head->value, val)) { \
+ head = head->next; \
+ } \
+ \
+ if (!cmp(&head->value, val)) memcpy(&(head->value), val, sizeof(type)); \
+ else { \
+ head->next = memory_allocate(m, sizeof(List_##type)); \
+ memcpy(&(head->next->value), val, sizeof(type)); \
+ } \
+}
+
+#endif
diff --git a/include/engine/list.h b/include/engine/list.h
new file mode 100644
index 0000000..5d3f80b
--- /dev/null
+++ b/include/engine/list.h
@@ -0,0 +1,11 @@
+#ifndef ENGINE_LIST_H
+#define ENGINE_LIST_H
+
+#define DEFINE_LLIST(type) \
+struct List_##type { \
+ type value; \
+ struct List_##type* next; \
+ /* Force the user to add `;` for style consistency */\
+} List_##type
+
+#endif
diff --git a/include/engine/logging.h b/include/engine/logging.h
new file mode 100644
index 0000000..1365a5b
--- /dev/null
+++ b/include/engine/logging.h
@@ -0,0 +1,44 @@
+#ifndef LOGGING_H
+#define LOGGING_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <math.h>
+
+#include "types.h"
+
+#if defined __linux__ || defined __APPLE__
+#define TERM_COLOR_RESET "\033[0m"
+#define TERM_COLOR_RED "\033[31m"
+#define TERM_COLOR_GREEN "\033[32m"
+#define TERM_COLOR_YELLOW "\033[33m"
+#define TERM_COLOR_BLUE "\033[34m"
+#define TERM_COLOR_PURPLE "\033[35m"
+#else
+#define TERM_COLOR_RESET
+#define TERM_COLOR_RED
+#define TERM_COLOR_GREEN
+#define TERM_COLOR_YELLOW
+#define TERM_COLOR_BLUE
+#define TERM_COLOR_PURPLE
+#endif
+
+#define STR(a) (#a)
+
+void _log(FILE *stream, const char *prefix, const char *fmt, va_list ap);
+
+void LOG(const char *fmt, ...);
+
+void INFO_(const char *fmt, ...);
+void INFO(const char *fmt, ...);
+
+#define _DEBUG(...) __DEBUG(__FILE__,__LINE__, __func__, __VA_ARGS__)
+void __DEBUG(const char* file, const i32 line, const char* func, const char *fmt, ...);
+
+void WARN(const char *fmt, ...);
+
+void ERROR(const char *fmt, ...);
+
+#endif
diff --git a/include/engine/memory.h b/include/engine/memory.h
new file mode 100644
index 0000000..8b37747
--- /dev/null
+++ b/include/engine/memory.h
@@ -0,0 +1,25 @@
+#ifndef MEMORY_H
+#define MEMORY_H
+
+#include "types.h"
+//#include <stdlib.h>
+
+typedef struct memory {
+ void *data;
+ usize size;
+ usize pos;
+ usize free;
+} memory;
+
+memory *memory_new(usize max_size);
+
+/* Returns a pointer to the allocated data */
+void *memory_allocate(memory* mem, usize size);
+
+memory memory_init(void *data, usize size);
+
+void memory_free(memory *mem, usize size);
+
+void memory_clear(memory *mem);
+
+#endif
diff --git a/include/engine/rendering.h b/include/engine/rendering.h
new file mode 100644
index 0000000..3ec8416
--- /dev/null
+++ b/include/engine/rendering.h
@@ -0,0 +1,81 @@
+#ifndef RENDERING_H
+#define RENDERING_H
+
+#include "types.h"
+#include "vector.h"
+
+/* Definitions */
+#define RGBA(_r,_g,_b,_a) ((Engine_color){.r=_r, .g=_g, .b=_b, .a=_a})
+#define RGB(_r,_g,_b) RGBA(_r,_g,_b,0xFF)
+
+/* Types */
+typedef struct {
+ u8 r;
+ u8 g;
+ u8 b;
+ u8 a;
+} Engine_color;
+
+typedef struct {
+ u32 texture_id;
+ v2_i32 coord;
+} Sprite;
+
+#include "ui.h"
+#include "engine.h"
+
+/* Rendering functions */
+void render_begin(Window *w);
+void render_present(Window *w);
+void drawcall_reset(void);
+void render(Window *w);
+
+/* Misc */
+void engine_window_resize_pointers(i32* w, i32* h);
+void engine_window_resize_pointers_reset(void);
+
+/* UI rendering */
+/* See rendering_ui.c for implementation */
+i64 engine_render_text(i32 font_id, Engine_color fg, char *text, v2_i32 *size_out, bool wrapped);
+void engine_draw_uitree(UITree *t);
+void engine_draw_sprite(Sprite *s, v2_i32 *pos, f32 scale);
+void engine_draw_sprite_ex(Sprite *s, v2_i32 *pos, f32 scale, Engine_color colormod);
+
+Sprite sprite_new(u64 tid, u8 coord);
+
+#ifdef ENGINE_INTERNALS
+#include "engine.h"
+
+#define TEXTURES_INCREMENT 512
+
+typedef enum {
+ RenderDrawCallType_UITree,
+ /*RenderDrawCallType_UIButton,*/
+ RenderDrawCallType_Text,
+ RenderDrawCallType_Sprite,
+} RenderDrawCallType;
+
+typedef struct {
+ RenderDrawCallType type;
+ union {
+ void *data;
+ struct {
+ Sprite *sprite;
+ i32 x; i32 y;
+ f32 scale;
+ SDL_Color mod;
+ } sprite;
+ } data;
+} RenderDrawCall;
+
+void render_uitree(Window *w, UITree *t);
+
+void render_container(Window *w, UITree_container *t);
+void render_button(Window *w, UITree_button *t);
+void render_title(Window *w, UITree_title *t);
+void render_text(Window *w, UITree_text *t);
+v2_i32 elem_size(UITree *root);
+
+#endif
+
+#endif
diff --git a/include/engine/stack.h b/include/engine/stack.h
new file mode 100644
index 0000000..636c306
--- /dev/null
+++ b/include/engine/stack.h
@@ -0,0 +1,49 @@
+/* Copyright © 2021 Upqwerk
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the “Software”), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ * */
+
+/* Stack implementation
+ * Author: Upqwerk
+ */
+#ifndef STACK_H
+#define STACK_H
+
+#include "types.h"
+
+typedef struct {
+ isize head; /* current number of elements */
+ const usize elem_size; /* size in bytes of each element */
+ usize size; /* current memory size used by the stack */
+ const usize chunk_size; /* size of which the stack increases when running out of mem */
+ void *data; /* memory buffer */
+} Stack;
+
+Stack stack_new_ex(const usize element_size, const usize size);
+
+Stack stack_new(const usize element_size);
+
+void stack_free(Stack *s);
+void *stack_pop(Stack *s);
+void stack_push(Stack *s, void *elem);
+void *stack_peek(Stack *s);
+isize stack_size(const Stack *s);
+void stack_swap(Stack *s, Stack *t);
+
+#endif
diff --git a/include/engine/state.h b/include/engine/state.h
new file mode 100644
index 0000000..83b57a2
--- /dev/null
+++ b/include/engine/state.h
@@ -0,0 +1,22 @@
+#ifndef STATE_H
+#define STATE_H
+
+#include <engine/memory.h>
+
+typedef enum StateType {
+ STATE_null,
+#define State(name) STATE_##name,
+#include <state_type_list.h>
+#undef State
+ STATE_quit,
+} StateType;
+
+extern const char *StateTypeStr[];
+
+StateType(*State_updateFunc(StateType type))(void*);
+
+void State_init(StateType type, memory *mem);
+void State_free(StateType type, memory *mem);
+StateType State_update(StateType type, memory *mem);
+
+#endif
diff --git a/include/engine/types.h b/include/engine/types.h
new file mode 100644
index 0000000..0fc5870
--- /dev/null
+++ b/include/engine/types.h
@@ -0,0 +1,32 @@
+#ifndef ENGINE_TYPES_H
+#define ENGINE_TYPES_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/* Signed */
+typedef int8_t i8;
+typedef int16_t i16;
+typedef int32_t i32;
+typedef int64_t i64;
+
+/* Unsigned */
+typedef uint8_t u8;
+typedef uint16_t u16;
+typedef uint32_t u32;
+typedef uint64_t u64;
+
+/* floating points */
+typedef float f32;
+typedef double f64;
+
+/* sizes */
+#if __x86_64__ || __ppc64__ || _WIN64
+typedef u64 usize;
+typedef i64 isize;
+#else
+typedef u32 usize;
+typedef i32 isize;
+#endif
+
+#endif
diff --git a/include/engine/ui.h b/include/engine/ui.h
new file mode 100644
index 0000000..bea8d3e
--- /dev/null
+++ b/include/engine/ui.h
@@ -0,0 +1,234 @@
+#ifndef ENGINE_UI_H
+#define ENGINE_UI_H
+
+#include "types.h"
+#include "vector.h"
+#include "list.h"
+
+#define DIRECTION_HORIZONTAL true
+#define DIRECTION_VERTICAL false
+
+/* Ui positioning constraints */
+typedef enum {
+ ui_size_pixel,
+ ui_size_percent,
+} ui_size_t;
+
+
+typedef union {
+ ui_size_t type;
+ struct {ui_size_t type; i32 value;} pixel;
+ struct {ui_size_t type; f32 value;} percent;
+} ui_size;
+
+
+typedef enum {
+ ui_constraint_width,
+ ui_constraint_height,
+
+ ui_constraint_horizontal_align,
+ ui_constraint_vertical_align,
+
+ ui_constraint_left,
+ ui_constraint_right,
+ ui_constraint_top,
+ ui_constraint_bottom,
+} ui_constraint_t;
+
+
+typedef enum {
+ ui_constraint_vertical_align_top,
+ ui_constraint_vertical_align_center,
+ ui_constraint_vertical_align_bottom,
+} ui_constraint_vertical_align_t;
+
+
+typedef enum {
+ ui_constraint_horizontal_align_left,
+ ui_constraint_horizontal_align_center,
+ ui_constraint_horizontal_align_right,
+} ui_constraint_horizontal_align_t;
+
+typedef union {
+ struct {ui_size size;} width;
+ struct {ui_size size;} height;
+
+ /* spacing is the padding between the aligned edge and the container itself */
+ struct {ui_constraint_horizontal_align_t align; ui_size spacing;} horizontal_align;
+ struct {ui_constraint_vertical_align_t align; ui_size spacing;} vertical_align;
+
+ struct {ui_size pos;} left;
+ struct {ui_size pos;} top;
+ struct {ui_size pos;} right;
+ struct {ui_size pos;} bottom;
+} constraintval_t;
+
+#define CONSTRAINT_POSITION(axis, alignment_sub, margin, cnext) { \
+ .value = { \
+ .type = ui_constraint_##axis##_align, \
+ .constraint.axis##_align = { \
+ .align = ui_constraint_##axis##_align_##alignment_sub, \
+ .spacing = margin, \
+ }, \
+ }, \
+ .next = cnext, \
+ }
+
+#define CONSTRAINT_SIZE(c_type, csize, cnext) { \
+ .value = { \
+ .type = ui_constraint_##c_type, \
+ .constraint.c_type = { \
+ .size = csize, \
+ }, \
+ }, \
+ .next = cnext, \
+ }
+
+typedef struct {
+ ui_constraint_t type;
+ constraintval_t constraint;
+} ui_constraint;
+
+typedef DEFINE_LLIST(ui_constraint);
+
+
+enum uitype {
+ uitype_container,
+ uitype_button,
+ uitype_title,
+ uitype_text,
+
+ uitype_MAX,
+};
+
+
+typedef struct UITree_container {
+ enum uitype type;
+ bool visible;
+ bool direction;
+ i32 x, y, w, h;
+ i32 padding;
+ i32 margin;
+
+ Engine_color fg;
+ Engine_color bg;
+ Engine_color bordercolor;
+
+ usize children_len; /* Number of children */
+ usize children_size; /* Memory size */
+ union UITree** children;
+ struct List_ui_constraint *constraints;
+} UITree_container;
+
+typedef struct UITree_button {
+ enum uitype type;
+
+ bool enabled;
+ u64 id;
+
+ i32 x, y, w, h;
+ i32 padding;
+ i32 margin;
+
+ Engine_color fg;
+ Engine_color bg;
+
+ i32 font_id;
+ const char *text_original;
+ u64 text_texture_index;
+ v2_i32 texture_size;
+ struct List_ui_constraint *constraints;
+} UITree_button;
+
+typedef struct UITree_title {
+ enum uitype type;
+ i32 x, y, w, h;
+
+ i32 padding;
+ i32 margin;
+
+ Engine_color fg;
+
+ i32 font_id;
+ const char *text_original;
+ u64 text_texture_index;
+ v2_i32 texture_size;
+ struct List_ui_constraint *constraints;
+} UITree_title;
+
+typedef struct UITree_text {
+ enum uitype type;
+ i32 x, y, w, h;
+
+ i32 padding;
+ i32 margin;
+
+ Engine_color fg;
+
+ i32 font_id;
+ const char *text_original;
+ u64 text_texture_index;
+ v2_i32 texture_size;
+ struct List_ui_constraint *constraints;
+} UITree_text;
+
+typedef union UITree {
+ enum uitype type;
+
+ UITree_container container;
+ UITree_button button;
+ UITree_title title;
+ UITree_text text;
+ struct List_ui_constraint constraints;
+} UITree;
+
+/* Sizes */
+f32 ui_size_pixel_to_percent_width(i32 pixels);
+f32 ui_size_pixel_to_percent_height(i32 pixels);
+f32 ui_size_pixel_to_percent(i32 pixels);
+i32 ui_size_percent_width_to_pixel(f32 percent);
+i32 ui_size_percent_height_to_pixel(f32 percent);
+i32 ui_size_percent_to_pixel(f32 percent);
+i32 ui_size_to_pixel(ui_size s);
+
+#define ui_size_percent_new(p) {.percent = {.type = ui_size_percent, .value = p}}
+#define ui_size_pixel_new(p) {.pixel = {.type = ui_size_pixel, .value = p}}
+
+/* Constructors */
+UITree *ui_container(bool direction, struct List_ui_constraint *constraints);
+UITree *ui_button(u64 id, i32 font_id, char *text, struct List_ui_constraint *constraints);
+UITree *ui_title(i32 font_id, const char *text, struct List_ui_constraint *constraints);
+UITree *ui_text(i32 font_id, const char *text, struct List_ui_constraint *constraints);
+
+/* Extended Constructors */
+UITree *ui_container_new_ex(
+ Engine_color fg,
+ Engine_color bg,
+ Engine_color border,
+ bool direction,
+ struct List_ui_constraint *constraints);
+
+/* Destructors */
+void clear_ui(void);
+void uitree_free(UITree *t);
+
+/* Manipulation */
+void ui_container_attach(UITree *container, UITree *child);
+
+void ui_resolve_constraints(void);
+
+void ui_button_enable(UITree_button *b);
+void ui_button_disable(UITree_button *b);
+
+/* Tweaking default colors */
+void ui_set_default_colors(Engine_color fg, Engine_color bg);
+void ui_set_default_padding(i32 padding);
+Engine_color ui_get_default_fg(void);
+Engine_color ui_get_default_bg(void);
+
+#define ELEM_NOT_FOUND (-1)
+#define NO_CLICK (-2)
+/* Returns -1 if not found */
+u64 ui_check_click(UITree *root);
+
+#endif
diff --git a/include/engine/utils.h b/include/engine/utils.h
new file mode 100644
index 0000000..65fa35c
--- /dev/null
+++ b/include/engine/utils.h
@@ -0,0 +1,34 @@
+#ifndef ENGINE_UTILS_H
+#define ENGINE_UTILS_H
+
+#include "types.h"
+#include "vector.h"
+
+#define MIN(a,b) ((a < b) ? (a) : (b))
+#define MAX(a,b) ((a > b) ? (a) : (b))
+
+#define MASK_TL (1 << 0)
+#define MASK_T (1 << 1)
+#define MASK_TR (1 << 2)
+#define MASK_L (1 << 3)
+#define MASK_C (1 << 4)
+#define MASK_R (1 << 5)
+#define MASK_BL (1 << 6)
+#define MASK_B (1 << 7)
+#define MASK_BR (1 << 8)
+
+/* Linear interpolate */
+f32 lerp(f32 dt, f32 a, f32 b);
+i32 int_lerp(f32 dt, i32 a, i32 b);
+
+/* Hashes */
+u32 hash(char *str);
+
+/* Masks surrounding tiles of a kernel size of 3 */
+/* In reality we only need 9 bits for this, but I think I had a reason for using i32 */
+i32* kernmap(const void *map, i32 *dstmap, const v2_i32 mapsize, bool (*predicate)(const void*));
+
+/* Returns an index from the given weights. */
+i32 pick_from_sample(const i32 *weights, i32 len);
+
+#endif
diff --git a/include/engine/vector.h b/include/engine/vector.h
new file mode 100644
index 0000000..396e12d
--- /dev/null
+++ b/include/engine/vector.h
@@ -0,0 +1,31 @@
+#ifndef VECTOR_H
+#define VECTOR_H
+
+#include "types.h"
+
+#include <stdio.h>
+#include <stdbool.h>
+
+typedef struct {
+ i32 x;
+ i32 y;
+} v2_i32;
+
+bool v2_i32_eq(const v2_i32 a, const v2_i32 b);
+
+v2_i32 v2_i32_add (v2_i32 a, v2_i32 b);
+v2_i32 v2_i32_add_i(v2_i32 a, i32 b);
+v2_i32 v2_i32_sub (v2_i32 a, v2_i32 b);
+v2_i32 v2_i32_sub_i(v2_i32 a, i32 b);
+v2_i32 v2_i32_div (v2_i32 a, v2_i32 b);
+v2_i32 v2_i32_div_i(v2_i32 a, i32 b);
+v2_i32 v2_i32_mul (v2_i32 a, v2_i32 b);
+v2_i32 v2_i32_mul_i(v2_i32 a, i32 b);
+v2_i32 v2_i32_mod (v2_i32 a, v2_i32 b);
+v2_i32 v2_i32_mod_i(v2_i32 a, i32 b);
+v2_i32 v2_i32_max (v2_i32 a, v2_i32 b);
+v2_i32 v2_i32_min (v2_i32 a, v2_i32 b);
+v2_i32 v2_i32_lerp (f32 dt, v2_i32 a, v2_i32 b);
+
+void v2_i32_fprintf(FILE *stream, v2_i32 a);
+#endif
diff --git a/include_states.h.in b/include_states.h.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/include_states.h.in
diff --git a/src/btree.c b/src/btree.c
new file mode 100644
index 0000000..63c2513
--- /dev/null
+++ b/src/btree.c
@@ -0,0 +1,850 @@
+#include <engine/btree.h>
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+
+/* Definitions */
+typedef unsigned char byte;
+
+struct node {
+ ssize_t n; /* number of items/keys/elements */
+ ssize_t c; /* number of children */
+ byte *items;
+ struct node **children;
+};
+
+struct btree {
+ /* Memory stuffs */
+ void *(*alloc)(size_t);
+ void (*dealloc)(void*);
+
+ /* Size stuffs */
+ size_t elem_size;
+ ssize_t degree;
+
+ struct node *root;
+
+ /* comparison */
+ int (*cmp)(const void *a, const void *b);
+};
+
+struct btree_iter_t {
+ size_t head;
+ struct stack {
+ int pos;
+ struct node* node;
+ } stack[512];
+ /* This heavily relies on the assumption that a tree never grows deeper than
+ * 512 nodes */
+};
+
+/**********************/
+/* Node functionality */
+/**********************/
+#define \
+node_leaf(node) (node->children == NULL)
+
+#define \
+node_maxdegree(t) (2 * t - 1)
+
+#define \
+node_mindegree(t) (t - 1)
+
+#define \
+node_full(degree, t) (t->n >= 2 * degree - 1)
+
+/* Node memory */
+
+/* `node_new` allocates a new leaf node, children should be added and allocated
+ * when the node is no longer a leaf node */
+struct node* node_new(const ssize_t degree, const size_t elem_size) {
+ const size_t max_items = 2 * degree;
+ struct node *retval = calloc(1, sizeof(struct node));
+
+ retval->n = 0;
+ retval->c = 0;
+ retval->items = calloc(max_items, elem_size);
+ retval->children = NULL;
+
+ return retval;
+}
+
+/* `node_transition` turns a leaf node into a non-leaf. Children are not
+ * allocated.
+ * returnvalue: `false` if we we're unable to allocate space for the new
+ * children. */
+bool node_transition(struct node *node, const ssize_t degree) {
+ const int max_children = 2 * degree + 1;
+ int c;
+
+ if (!node_leaf(node)) {
+ perror("node_transition was called on an already non-leaf node?");
+ return false;
+ }
+
+ /* Allocate pointers for children */
+ node->children = calloc(max_children, sizeof(struct node*));
+
+ if (node->children == NULL) {
+ perror("could not allocate space for children pointers");
+ return false;
+ }
+
+ /* Allocate children */
+ for (c = 0; c < max_children; c++) {
+ node->children[c] = NULL;
+ }
+
+ return true;
+}
+
+void node_free(struct node **node, size_t elem_size, void (*dealloc)(void*)) {
+ if (*node == NULL) return;
+
+ if (!node_leaf((*node))) {
+ ssize_t i;
+ for (i = 0; i < (*node)->c; i++) {
+ node_free(&((*node)->children[i]), elem_size, dealloc);
+ }
+ dealloc((*node)->children);
+ }
+
+ dealloc((*node)->items);
+ (*node)->items = NULL;
+
+ dealloc(*node);
+ *node = NULL;
+}
+
+
+/* `node_tree_split_child` splits a _full_ node (c = 2t-1 items) into two nodes
+ * with t-1 items each.
+ * The median key/item/element moves up to the original nodes parent, to signify
+ * the split. If the parent is full too, we need to split it before inserting
+ * the median key.
+ * This can potentially split full nodes all the way up throughout the tree. */
+/* Instead of waiting to find out wether we should split the nodes, we split the
+ * full nodes we encounter on the way down, including the leafs themselves.
+ * By doing this, we are assured that whenever we split a node, its parent has
+ * room for the median key. */
+void node_tree_split_child(
+ const ssize_t t,
+ const size_t elem_size,
+ struct node *nonfull,
+ ssize_t i) {
+ struct node *z = node_new(t, elem_size);
+ struct node *y = nonfull->children[i];
+ ssize_t j;
+
+ /* `z` should be a branching node if `y` is */
+ if (!node_leaf(y)) {
+ node_transition(z, t);
+ }
+
+ z->n = t - 1;
+
+ /* Move last `t-1` items to new node `z` */
+ /* TODO This can be done with one memcpy */
+ for (j = 0; j < t-1; j++) {
+ const size_t offset_dst = elem_size * j;
+ const size_t offset_src = elem_size * (t+j);
+ memcpy((z->items) + offset_dst, (y->items) + offset_src, elem_size);
+ }
+ /* Set unused item-memory to zero? */
+
+ /* Move children t..2t, if applicable*/
+ if (!node_leaf(y)) {
+ for (j = 0; j < t+1; j++) {
+ z->children[j] = y->children[j + t];
+ }
+ y->c = t;
+ z->c = t;
+ }
+
+ y->n = t - 1;
+
+ /* Move children +1 */
+ for (j = nonfull->n; j > i; j--) {
+ nonfull->children[j+1] = nonfull->children[j];
+ }
+
+ /* new child */
+ nonfull->children[i+1] = z;
+ nonfull->c++;
+
+ /* moving keys i..n + 1*/
+ /* TODO This can be done with one memcpy */
+ for (j = nonfull->n; j >= i; j--) {
+ const size_t offset = j * elem_size;
+ memcpy((nonfull->items) + offset + elem_size,
+ (nonfull->items) + offset,
+ elem_size);
+ }
+
+ /* Lastly, copy the median element to nonfull-parent*/
+ memcpy((nonfull->items) + i * elem_size,
+ (y->items) + (t-1) * elem_size,
+ elem_size);
+
+ nonfull->n++;
+}
+
+/* `node_child_merge`: Merges two children around the key at index `i` (k)
+ * by appending k to the left child (y) followed by
+ * appending the right child (z) to y
+ *
+ * `x`: The parent node of y and z
+ * `i`: Index of the item that acts as the new median of the merged node
+ *
+ * WARNING: THIS FUNCTION ASSUMES THAT `i` IS A VALID INDEX
+ */
+void node_child_merge(
+ struct node *x,
+ ssize_t i,
+ const size_t elem_size, void (*dealloc)(void*)) {
+ struct node* y = x->children[i ];
+ struct node* z = x->children[i+1];
+ int j = 0;
+
+ /* append k to y */
+ memcpy(y->items + (elem_size * y->n++),
+ x->items + (elem_size * i),
+ elem_size);
+
+ /* append keys in z to y */
+ memcpy(y->items + (elem_size * y->n),
+ z->items,
+ elem_size * z->n);
+ y->n += z->n;
+
+ /* Move children from z to y */
+ for (j = 0; j < z->c; j++) {
+ y->children[y->c + j] = z->children[j];
+ }
+ y->c += z->c;
+
+ /* Remove z from x */
+ for (j = i+1; j < x->c; j++) {
+ x->children[j] = x->children[j+1];
+ }
+ x->c--;
+
+ /* remove k from x */
+ /* TODO check if we need to use (x->n - 1 - i) instead */
+ memmove(x->items + (elem_size * i),
+ x->items + (elem_size * (i+1)),
+ elem_size * (x->n - i));
+ x->n--;
+
+ dealloc(z); /* DO NOT USE THE RECURSIVE ONE AS CHILDREN WILL BE LOST!!! */
+}
+
+/* ASSUME i < x->c */
+void node_shift_left(
+ struct node *x,
+ ssize_t i,
+ const size_t elem_size) {
+ struct node* y = x->children[i ];
+ struct node* z = x->children[i+1];
+ byte *x_k = x->items + (elem_size * i);
+
+ /* Append x.k[i] to y */
+ memcpy(y->items + (elem_size * y->n++),
+ x_k,
+ elem_size);
+
+ /* Move first element of z to x.k[i] */
+ memcpy(x_k,
+ z->items,
+ elem_size);
+
+ /* Shift z's items left */
+ memmove(z->items,
+ z->items + elem_size,
+ elem_size * (z->n - 1));
+
+ if (!node_leaf(z)) {
+ ssize_t j;
+ /* append first child of z to y */
+ y->children[y->c++] = z->children[0];
+
+ /* Shift z's children left */
+ for (j = 0; j < z->c; j++) {
+ z->children[j] = z->children[j+1];
+ }
+ z->c--;
+ }
+
+ z->n--;
+}
+
+void node_shift_right(
+ struct node *x,
+ ssize_t i,
+ const size_t elem_size) {
+ struct node* y = x->children[i ];
+ struct node* z = x->children[i+1];
+ byte *x_k = x->items + (elem_size * i);
+
+ /* Shift z's items right */
+ memmove(z->items + elem_size,
+ z->items,
+ elem_size * z->n);
+
+ /* Prepend x.k[i] to z */
+ memcpy(z->items,
+ x_k,
+ elem_size);
+
+ /* Move last element of y to x.k[i] */
+ memcpy(x_k,
+ y->items + (elem_size * --(y->n)),
+ elem_size);
+
+ if (!node_leaf(z)) {
+ size_t j;
+ /* Shift z's children right */
+ for (j = z->c; j > 0; j--) {
+ z->children[j] = z->children[j-1];
+ }
+ z->c++;
+
+ /* prepend last child of y to z */
+ z->children[0] = y->children[--(y->c)];
+ }
+
+ z->n++;
+}
+
+/* return: Returns the new root, if a split happens */
+void node_insert_nonfull(
+ struct node *root,
+ void *elem,
+ const ssize_t degree,
+ const size_t elem_size,
+ int (*cmp)(const void *a, const void *b)) {
+
+ /* TODO check correctness */
+ ssize_t i = root->n - 1;
+
+ if (node_leaf(root)) {
+ size_t offset = elem_size * i;
+ while (i >= 0 && cmp(elem, root->items + offset) < 0) {
+ /* TODO This can be done with one memcpy */
+ memcpy(root->items + offset + elem_size,
+ root->items + offset,
+ elem_size);
+
+ i--;
+ offset = elem_size * i;
+ }
+ offset = elem_size * (++i);
+ memcpy(root->items + offset, elem, elem_size);
+ root->n++;
+
+ } else {
+ size_t offset = elem_size * i;
+ struct node *nextchild = NULL;
+ while (i >= 0 && cmp(elem, root->items + offset) < 0) {
+ i--;
+ offset = elem_size * i;
+ }
+ i++;
+ nextchild = root->children[i];
+ if (node_full(degree, nextchild)) {
+ /* TODO Check if the root has changed */
+ node_tree_split_child(degree, elem_size, root, i);
+ if (cmp(elem, root->items + elem_size * i) > 0) {
+ nextchild = root->children[++i];
+ }
+ }
+ node_insert_nonfull(nextchild, elem, degree, elem_size, cmp);
+ }
+}
+
+/* Returns the new root, if a split occurs */
+struct node* node_insert(
+ struct node *root,
+ void *elem,
+ const ssize_t degree,
+ const size_t elem_size,
+ int (*cmp)(const void *a, const void *b)) {
+
+ struct node *s = root;
+
+ if (node_full(degree, root)) {
+ s = node_new(degree, elem_size);
+ if (s == NULL) {
+ fputs("BTree error: Failed to allocate new node for insertion!\n", stderr);
+ return NULL;
+ }
+ node_transition(s, degree);
+ s->children[s->c++] = root;
+ /* TODO Check if the root has changed */
+ node_tree_split_child(degree, elem_size, s, 0);
+ node_insert_nonfull(s, elem, degree, elem_size, cmp);
+ }
+ else {
+ node_insert_nonfull(s, elem, degree, elem_size, cmp);
+ }
+ return s;
+}
+
+void* node_search(struct node *x,
+ void *key,
+ int(*cmp)(const void *a, const void *b),
+ const size_t elem_size) {
+ /* We set to one, since we pre-emptively do a comparison with the assumption
+ * that there's already one in the items */
+ ssize_t i = 0;
+ int last_cmp_res = 0;
+
+ while (i < x->n
+ && (last_cmp_res = cmp(key, (const void*)(x->items + (i * elem_size))))
+ > 0) {
+ i++;
+ }
+
+ if ((ssize_t)i < x->n && last_cmp_res == 0) {
+ return (void*)(x->items + (i * elem_size));
+ } else if (node_leaf(x)) {
+ return NULL;
+ }
+
+ /* Assumption: ¬node_leaf(x) → x.children is allocated */
+ return node_search(x->children[i], key, cmp, elem_size);
+}
+
+int node_delete(struct node *x,
+ void *key,
+ int(*cmp)(const void *a, const void *b),
+ const ssize_t degree,
+ const size_t elem_size, void *(*alloc)(size_t), void (*dealloc)(void*)) {
+ /* Determine wether the key is in the node */
+ int i = 0; /* Index of `k`, if found */
+ int last_cmp_res = 0;
+
+ while (i < x->n
+ && (last_cmp_res = cmp(key, (const void*)(x->items + (i * elem_size))))
+ > 0) {
+ i++;
+ }
+
+ if (last_cmp_res == 0) {
+
+ if (node_leaf(x)) {
+ /* 1. k ϵ x && node_leaf(x) */
+ /* Delete k from x */
+ int j = i;
+ while (j + 1 < x->n) {
+ const size_t offset_dst = elem_size * j;
+ const size_t offset_src = elem_size * (j+1);
+ memcpy((x->items) + offset_dst,
+ (x->items) + offset_src,
+ elem_size);
+ j++;
+ }
+ x->n--;
+ return 1;
+ } else {
+ /* 2. k ϵ x && !node_leaf(x) */
+ /* let i be the index of k in x */
+ /* 2a: if size(child[i]) >= t; find the largest k' in child[i] */
+ /* replace k with k' */
+ if (x->children[i]->n >= degree) {
+ struct node* y = x->children[i];
+ byte *kk = alloc(elem_size);
+
+ /* Find the predecessor, k' of k in y */
+ {
+ struct node* tmp = y;
+ while (!node_leaf(tmp)) {
+ tmp = tmp->children[tmp->n-1];
+ }
+
+ /* copy kk */
+ memcpy(kk, tmp->items + elem_size * (tmp->n - 1), elem_size);
+ }
+
+ /* Recursively delete kk from y */
+ return node_delete(y, kk, cmp, degree, elem_size, alloc, dealloc);
+
+ /* replace k with kk */
+ memcpy(x->items + (elem_size * i),
+ kk,
+ elem_size);
+
+ dealloc(kk);
+
+ return 1;
+
+ } else if (x->children[i+1]->n >= degree) {
+ struct node* z = x->children[i+1];
+ byte *kk = alloc(elem_size);
+
+ /* Find the successor, k' of k in z */
+ {
+ struct node* tmp = z->children[0];
+ while (!node_leaf(tmp)) {
+ tmp = tmp->children[0];
+ }
+
+ /* copy kk */
+ memcpy(kk, tmp->items + elem_size * (tmp->n - 1), elem_size);
+ }
+
+ /* Recursively delete kk from y */
+ return node_delete(z, kk, cmp, degree, elem_size, alloc, dealloc);
+
+ /* replace k with kk */
+ memcpy(x->items + (elem_size * i),
+ kk,
+ elem_size);
+
+ dealloc(kk);
+
+ return 1;
+ } else {
+ /* Merge k and z into y */
+ node_child_merge(x, i, elem_size, dealloc);
+
+ /* recurse */
+ return node_delete(x->children[i], key, cmp, degree, elem_size, alloc, dealloc);
+ }
+ }
+ } else if (node_leaf(x)) {
+ return 0;
+ } else {
+ /* 3. !(k ϵ x) */
+
+ /* if x is a leaf, then it is not in the tree */
+
+ struct node* y;
+ int ii; /* index of x.c[i] */
+
+ /* Determine x.c[i] that must contain k */
+ /* if last cmp < 0, then the child must be in the left child of x.items[i]*/
+ if (last_cmp_res < 0) ii = i;
+ /* Otherwise, it must be the very last child */
+ else if (i < x->n) ii = i+1;
+ else ii = i;
+
+ y = x->children[ii];
+
+ if (y->n < degree) {
+ /* we are left biased */
+ if (ii > 0 && x->children[ii-1]->n >= degree) {
+ node_shift_right(x, ii-1, elem_size);
+
+ } else if (ii < x->c - 1 && x->children[ii+1]->n >= degree) {
+ node_shift_left (x, ii, elem_size);
+
+ } else {
+ /* We need to determine wether we merge left or right, if possible */
+ if (ii > 0) {
+ node_child_merge(x, ii - 1, elem_size, dealloc);
+ y = x->children[ii - 1];
+ }
+ else if (ii < x->c - 1) {
+ node_child_merge(x, ii, elem_size, dealloc);
+ }
+ else {
+ perror("Cannot merge!");
+ }
+ }
+
+ }
+
+ return node_delete(y, key, cmp, degree, elem_size, alloc, dealloc);
+ }
+ return 0;
+}
+
+
+/***********************/
+/* Btree functionality */
+/***********************/
+struct btree* btree_new(size_t elem_size,
+ size_t t,
+ int(*cmp)(const void *a, const void *b)) {
+ return btree_new_with_allocator(elem_size, t, cmp, malloc, free);
+}
+
+struct btree* btree_new_with_allocator(size_t elem_size,
+ size_t t,
+ int(*cmp)(const void *a, const void *b),
+ void *(*alloc)(size_t),
+ void (*dealloc)(void*)) {
+ struct btree *new_tree = alloc(sizeof(struct btree));
+
+ new_tree->alloc = alloc;
+ new_tree->dealloc = dealloc;
+
+ new_tree->elem_size = elem_size;
+ new_tree->degree = t;
+
+ new_tree->root = NULL;
+
+ new_tree->cmp = cmp;
+
+ return new_tree;
+}
+
+void btree_free(struct btree **btree) {
+ node_free(&((*btree)->root), (*btree)->elem_size, (*btree)->dealloc);
+ (*btree)->dealloc(*btree);
+ *btree = NULL;
+}
+
+void btree_insert(struct btree *btree, void *elem) {
+ if (btree == NULL) {
+ fputs("BTree error: Inserting into a NULL ptr!\n", stderr);
+ return;
+ }
+ if (elem == NULL) {
+ fputs("BTree error: Inserting NULL into a tree!\n", stderr);
+ return;
+ }
+ if (btree->root == NULL) {
+ btree->root = node_new(btree->degree, btree->elem_size);
+ if (btree->root == NULL) {
+ fputs("BTree error: Failed to create new root node!\n", stderr);
+ return;
+ }
+ node_insert(btree->root,
+ elem,
+ btree->degree,
+ btree->elem_size,
+ btree->cmp);
+ }
+ else {
+ btree->root = node_insert(btree->root,
+ elem,
+ btree->degree,
+ btree->elem_size,
+ btree->cmp);
+ }
+}
+
+void* btree_search(struct btree *btree, void *elem) {
+ return node_search(btree->root, elem, btree->cmp, btree->elem_size);
+}
+
+int btree_delete(struct btree *btree, void *elem) {
+ struct node *newroot = btree->root;
+ int res = node_delete(btree->root, elem, btree->cmp, btree->degree, btree->elem_size, btree->alloc, btree->dealloc);
+ if (newroot->n == 0) {
+ if (node_leaf(newroot)) return res;
+ /* shrink the tree */
+ struct node *newroot_p = newroot->children[0];
+ btree->dealloc(newroot);
+ btree->root = newroot_p;
+ }
+ return res;
+}
+
+void node_print(struct node *root, const size_t elem_size, const int indent, void (*print_elem)(const void*)) {
+ ssize_t i;
+ int t;
+
+ for (t = 0; t < indent - 1; t++) { fputs(" ┃ ", stdout); }
+ if (indent > 0) { fputs(" ┣┯", stdout); }
+ printf("printing node \x1b[33m%0lx\x1b[0m,"
+ " c:%ld n:%ld\t\t"
+ "\x1b[30m%p\x1b[0m\n",
+ (unsigned long)((size_t)root % 0x10000),
+ root->c,
+ root->n,
+ (void*)root);
+
+ if (node_leaf(root)) {
+ for (i = 0; i < root->n - 1; i++) {
+ const size_t ofst = i * elem_size;
+ for (t = 0; t < indent; t++) { fputs(" ┃├", stdout); }
+ print_elem(root->items + ofst);
+ }
+ for (t = 0; t < indent; t++) { fputs(" ┃└", stdout); }
+ print_elem(root->items + i * elem_size);
+ } else {
+ size_t ofst = 0;
+ for (i = 0; i < root->c - 1; i++) {
+ node_print(root->children[i], elem_size, indent + 1, print_elem);
+ for (t = 0; t < indent; t++) { fputs(" ┃ ", stdout); }
+ print_elem(root->items + ofst);
+ ofst += elem_size;
+ }
+ node_print(root->children[i], elem_size, indent + 1, print_elem);
+ }
+
+}
+
+void btree_print(struct btree *btree, void (*print_elem)(const void*)) {
+ printf("BTRee: degree:%ld\n", btree->degree);
+ if (btree->root == NULL) return;
+ node_print(btree->root, btree->elem_size, 0, print_elem);
+}
+
+void* btree_first(struct btree *btree) {
+ struct node *root;
+ if (btree == NULL) return NULL;
+ root = btree->root;
+
+ if (root == NULL) return NULL;
+
+ while (!node_leaf(root)) root = root->children[0];
+
+ if (root->n == 0) return NULL;
+ return root->items; /* Return first element */
+}
+
+void* btree_last(struct btree *btree) {
+ struct node *root;
+
+ if (btree == NULL) return NULL;
+ root = btree->root;
+
+ if (root == NULL) return NULL;
+
+ while (!node_leaf(root)) root = root->children[root->c];
+
+ if (root->n == 0) return NULL;
+ return root->items + btree->elem_size * (root->n - 1); /* Return first element */
+}
+
+size_t btree_height(struct btree *btree) {
+ struct node *root;
+ size_t height = 0;
+
+ if (btree == NULL) return 0;
+ root = btree->root;
+
+ if (root == NULL) return 0;
+
+ while (!node_leaf(root)) {
+ root = root->children[0];
+ height++;
+ }
+
+ return height;
+}
+
+size_t u32_pow(size_t base, size_t exponent) {
+ size_t res = 1;
+ while (exponent > 0) {
+ res *= base;
+ exponent--;
+ }
+ return res;
+}
+
+
+size_t btree_size(struct btree *btree) {
+ return u32_pow(2 * btree->degree, btree_height(btree)) - 1;
+}
+
+
+struct btree_iter_t* btree_iter_t_new(struct btree *tree) {
+ struct btree_iter_t *iter = NULL;
+
+ if (tree == NULL) return NULL;
+
+ iter = (struct btree_iter_t*)tree->alloc(sizeof(struct btree_iter_t));
+
+ if (tree != NULL) {
+ iter->head = 0;
+ memset(iter->stack, 0, 512 * sizeof(struct node*));
+
+ iter->stack[iter->head].pos = 0;
+ iter->stack[iter->head].node = tree->root;
+ } else {
+ perror("Cannot instantiate iterator from null-pointer tree");
+ }
+ return iter;
+}
+
+
+void btree_iter_t_reset(struct btree *tree, struct btree_iter_t** it) {
+ (*it)->head = 0;
+
+ (*it)->stack[0].pos = 0;
+ (*it)->stack[0].node = tree->root;
+}
+
+
+void* btree_iter(struct btree *tree, struct btree_iter_t *iter) {
+ register int pos = 0;
+ register ssize_t head = 0;
+ register ssize_t n = 0;
+
+ if (iter->stack[head].node == NULL) return NULL;
+
+ head = iter->head;
+ pos = iter->stack[head].pos;
+ n = iter->stack[head].node->n;
+
+#define BTREE_ITER_POP(it) { \
+ iter->stack[head].pos = 0; \
+ iter->stack[head].node = NULL; \
+ iter->head--; head--; \
+ iter->stack[head].pos++; \
+ \
+ pos = iter->stack[head].pos; \
+ n = iter->stack[head].node->n; \
+}
+
+ /* Check if we have reached the end of a node.
+ * Take note of the difference of inequality in the if statement and
+ * following while */
+
+ /* Leafs are a special case, as, if the only node is the root node, we might
+ * want to exit */
+ if (node_leaf(iter->stack[iter->head].node) && pos >= 2 * n) {
+ if (head == 0) return NULL;
+
+ /* Pop, if so */
+ else BTREE_ITER_POP(iter);
+ }
+
+ /* Otherwise, pop while we have reached the end of a node */
+ while (pos > 2 * n) {
+ if (head == 0) return NULL;
+
+ /* Pop, if so */
+ else BTREE_ITER_POP(iter);
+ }
+
+#undef BTREE_ITER_POP
+
+ /* On evens, we decent into children */
+ if (!node_leaf(iter->stack[head].node)) {
+ if (pos % 2 == 0) {
+ /* push child node onto iter->stack */
+ iter->stack[head + 1].pos = 0;
+ iter->stack[head + 1].node = iter->stack[head].node->children[pos / 2];
+ iter->head++; head++;
+
+ /* Decent all the way to the left, if pos == 0 */
+ while (!node_leaf(iter->stack[iter->head].node)) {
+ iter->stack[head + 1].pos = 0;
+ iter->stack[head + 1].node = iter->stack[head].node->children[0];
+ iter->head++; head++;
+ }
+ }
+ }
+
+ /* Finally, update index and return a value */
+ if (node_leaf(iter->stack[head].node)) {
+ iter->stack[head].pos += 2;
+ pos = iter->stack[head].pos;
+ }
+ else {
+ iter->stack[head].pos++;
+ pos = iter->stack[head].pos;
+ }
+
+ return iter->stack[head].node->items + tree->elem_size * ( (pos - 1) / 2 );
+}
diff --git a/src/engine.c b/src/engine.c
new file mode 100644
index 0000000..fa528c0
--- /dev/null
+++ b/src/engine.c
@@ -0,0 +1,686 @@
+#include <stdlib.h>
+
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_image.h>
+#include <SDL2/SDL_ttf.h>
+
+#if defined (_WIN32) || defined (__WIN32__) || defined (WIN32)
+ /* include winapi */
+#elif defined (__APPLE__)
+ /* mac includes */
+#elif defined (__linux) || defined (__linux__) || defined (linux)
+
+#include <unistd.h>
+
+#endif
+
+
+#define ENGINE_INTERNALS
+#include <engine/engine.h>
+#include <engine/btree.h>
+#include <engine/hashmap.h>
+#include <engine/list.h>
+
+#include <engine/state.h>
+//#include <states/titlescreen.h>
+//#include <states/gameover.h>
+
+#define DEFAULT_NUM_PROCS 8
+
+#ifdef BENCHMARK
+#define BENCHEXPR(timevar, expr) { \
+ u32 t = SDL_GetTicks(); \
+ expr \
+ timevar += SDL_GetTicks() - t; \
+}
+
+extern i32 drawcall_len;
+
+#else
+#define BENCHEXPR(timevar, expr) expr
+#endif
+
+i32 bindings_cmp(const Keybinding *a, const Keybinding *b) {
+ return a->modifiers ^ b->modifiers;
+}
+
+i32 binding_to_int(const Keybinding *b) {
+ return b->keycode;
+}
+
+/* Defines (struct) List_Keybinding */
+/* Defines (struct) hashmap_Keybinding,
+ * and hashmap_Keybinding_lookup, and hashmap_Keybinding_insert */
+DEFINE_HASHMAP(Keybinding, 64, bindings_cmp, binding_to_int)
+
+static u64 FPS_CAP = 50;
+Platform *GLOBAL_PLATFORM = NULL;
+
+i32 nproc(void) {
+ return SDL_GetCPUCount();
+}
+
+i32 cmp_int(const void *a, const void *b) {
+ const i32 *x = a;
+ const i32 *y = b;
+
+ return *x - *y;
+}
+
+v2_i32 get_canvas_size(SDL_Renderer *renderer) {
+ v2_i32 realsize;
+ SDL_GetRendererOutputSize(renderer, &(realsize.x), &(realsize.y));
+
+ /* Set logical render size */
+ return realsize;
+}
+
+Texture *load_texture(SDL_Renderer *render, const TextureSpec *ts) {
+ SDL_Texture* new_texture = NULL;
+ SDL_Surface* loaded_surface = NULL;
+ Texture *t = NULL;
+
+ if (ts == NULL) {
+ ERROR("Invalid TextureSpec\n");
+ return NULL;
+ }
+
+ loaded_surface = IMG_Load(ts->path);
+ if (loaded_surface == NULL) {
+ ERROR("Unable to load image \"%s\"!\n", ts->path);
+ ERROR("SDL_image Error: %s\n", IMG_GetError());
+ return NULL;
+ }
+
+ const i32 tw = loaded_surface->w / ts->width;
+
+ SDL_SetColorKey(loaded_surface, SDL_TRUE,
+ SDL_MapRGB(loaded_surface->format, 0xFF, 0x00, 0xFF));
+
+ /*Create texture from surface pixels */
+ new_texture = SDL_CreateTextureFromSurface(render, loaded_surface);
+ if (new_texture == NULL) {
+ ERROR("Unable to create texture from \"%s\"!\n", ts->path);
+ ERROR("SDL Error: %s\n", SDL_GetError());
+ }
+
+ /*Get rid of old loaded surface */
+ SDL_FreeSurface(loaded_surface);
+
+ t = (Texture*)malloc(sizeof(Texture));
+ t->texture = new_texture;
+ /* Assigning const value */
+ *(i32*)&t->tilesize = tw;
+ *(i32*)&t->width = ts->width;
+ *(i32*)&t->height = ts->height;
+
+ return t;
+}
+
+void engine_update_window(Window *w, SDL_WindowEvent *e) {
+ switch (e->event) {
+ case SDL_WINDOWEVENT_NONE:
+ case SDL_WINDOWEVENT_SHOWN:
+ case SDL_WINDOWEVENT_HIDDEN:
+ case SDL_WINDOWEVENT_EXPOSED:
+ case SDL_WINDOWEVENT_MOVED:
+ break;
+ case SDL_WINDOWEVENT_RESIZED:
+ w->windowsize = get_canvas_size(w->renderer);
+ LOG("Resized window to %dx%d", w->windowsize.x, w->windowsize.y);
+ ui_resolve_constraints();
+ if (w->game_w != NULL && w->game_h != NULL) {
+ *w->game_h = w->windowsize.x;
+ *w->game_w = w->windowsize.y;
+ }
+ break;
+ case SDL_WINDOWEVENT_SIZE_CHANGED:
+ case SDL_WINDOWEVENT_MINIMIZED:
+ case SDL_WINDOWEVENT_MAXIMIZED:
+ case SDL_WINDOWEVENT_RESTORED:
+ case SDL_WINDOWEVENT_ENTER:
+ case SDL_WINDOWEVENT_LEAVE:
+ case SDL_WINDOWEVENT_FOCUS_GAINED:
+ case SDL_WINDOWEVENT_FOCUS_LOST:
+ case SDL_WINDOWEVENT_CLOSE:
+ case SDL_WINDOWEVENT_TAKE_FOCUS:
+ case SDL_WINDOWEVENT_HIT_TEST:
+ break;
+ default:
+ WARN("Unhandled window event 0x%04x", (i32)e->event);
+ break;
+ }
+ return;
+}
+
+Platform *engine_init(
+ const char *windowtitle,
+ v2_i32 windowsize,
+ const f32 render_scale,
+ const u32 flags,
+ const usize initial_memory,
+ const FontSpec *fonts[],
+ const TextureSpec *textures[]) {
+
+#ifdef BENCHMARK
+ u32 init_start = SDL_GetTicks();
+#endif
+
+#if defined (__linux) || defined (__linux__) || defined (linux)
+ {
+ pid_t pid = getpid();
+ INFO("Starting with pid %lu", pid);
+ }
+#endif
+
+ Platform *p = (Platform*)malloc(sizeof(Platform));
+ Window *w = (Window*)malloc(sizeof(Window));
+ SDL_Window *window = NULL;
+ SDL_Renderer *renderer = NULL;
+
+ /* initialize resources */
+ struct Resources *resources = (struct Resources*)malloc(sizeof(struct Resources));
+ resources->textures_len = 0;
+ resources->fonts_len = 0;
+ resources->texturepaths_len = 0;
+ resources->fontpaths_len = 0;
+ resources->texture_paths = NULL;
+ resources->font_paths = NULL;
+ resources->textures = NULL;
+ resources->fonts = NULL;
+
+
+ { /* Init subsystems */
+ INFO_("initializing sdl...");
+ if (SDL_Init(SDL_INIT_VIDEO) < 0) {
+ ERROR("failed to initialize sdl: %s\n", SDL_GetError());
+ exit(EXIT_FAILURE);
+ } else printf("ok\n");
+
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
+ SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
+ SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
+
+ INFO_("creating window...");
+ window = SDL_CreateWindow(windowtitle,
+ SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
+ windowsize.x, windowsize.y,
+ SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_MOUSE_FOCUS | flags);
+
+ if (window == NULL) {
+ ERROR("failed to create window: %s\n", SDL_GetError());
+ exit(EXIT_FAILURE);
+ } else printf("ok\n");
+
+ INFO_("creating renderer...");
+ renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
+ if (renderer == NULL) {
+ ERROR("failed to create renderer: %s\n", SDL_GetError());
+ exit(EXIT_FAILURE);
+ } else printf("ok\n");
+
+ INFO_("initializing sdl_image...");
+ if (IMG_Init(IMG_INIT_PNG) != IMG_INIT_PNG) {
+ ERROR("failed to initialize sdl_image png support\n");
+ exit(EXIT_FAILURE);
+ } else printf("ok\n");
+
+ INFO_("initializing sdl_ttf...");
+ if (TTF_Init() == -1) {
+ ERROR("failed to initialize sdl_ttf: %s\n", TTF_GetError());
+ exit(EXIT_FAILURE);
+ } else printf("ok\n");
+ }
+
+
+ { /* Resource loading */
+
+ /* Count resources */
+ usize n_textures = 0;
+ usize n_fonts = 0;
+
+ if (textures != NULL) while (textures[n_textures] != NULL) n_textures++;
+ if (fonts != NULL) while (fonts[n_fonts] != NULL) n_fonts++;
+
+ INFO("Number of textures: "TERM_COLOR_YELLOW"%d"TERM_COLOR_RESET, n_textures);
+ INFO("Number of fonts: "TERM_COLOR_YELLOW"%d"TERM_COLOR_RESET, n_fonts);
+
+
+ /* Save the textures and fonts, if we should need to reload them later */
+ resources->texture_paths = (TextureSpec**)textures;
+ resources->font_paths = (FontSpec**)fonts;
+
+ /* Allocate memory for textures and fonts */
+ resources->textures = (Texture**)malloc(sizeof(Texture*) * n_textures);
+ resources->fonts = (TTF_Font**)malloc(sizeof(TTF_Font*) * n_fonts);
+ resources->textures_size = n_textures;
+
+ for (usize i = 0; i < n_textures; i++) resources->textures[i] = NULL;
+ for (usize i = 0; i < n_fonts; i++) resources->fonts[i] = NULL;
+
+ /* Load textures */
+ for (usize i = 0; i < n_textures; i++) {
+ Texture *t = NULL;
+ INFO_("loading texture \""
+ TERM_COLOR_YELLOW"%s"TERM_COLOR_RESET
+ "\"...", textures[i]->path);
+
+ t = load_texture(renderer, textures[i]);
+ if (t == NULL) {
+ puts("");
+ ERROR("failed to load texture\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (t->tilesize < 8) {
+ puts("");
+ ERROR("texture too small!\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (t->texture == NULL) {
+ puts("");
+ ERROR("failed to load texture\n");
+ } else {
+ printf("ok\n");
+ resources->textures[i] = t;
+ resources->textures_len++;
+ }
+ }
+
+ /* Load fonts */
+ for (usize i = 0; i < n_fonts; i++) {
+ INFO_("loading font \""
+ TERM_COLOR_YELLOW"%s"TERM_COLOR_RESET
+ "\"...", fonts[i]->font_path);
+
+ TTF_Font *font = TTF_OpenFont(fonts[i]->font_path
+ , fonts[i]->ptsize);
+ if (!font) {
+ ERROR("failed to load font: %s\n", TTF_GetError());
+ } else {
+ printf("ok\n");
+ resources->fonts[i] = font;
+ resources->fonts_len++;
+ }
+ }
+
+ if (resources->textures_len != n_textures) {
+ WARN("Done. %d/%d textures loaded.", resources->textures_len, n_textures);
+ } else {
+ INFO("Done. All %d textures loaded.", n_textures);
+ }
+
+ if (resources->fonts_len != n_fonts) {
+ WARN("Done. %d/%d fonts loaded.", resources->fonts_len, n_fonts);
+ } else {
+ INFO("Done. All %d fonts loaded.", n_fonts);
+ }
+
+ resources->texturepaths_len = resources->textures_len;
+ resources->fontpaths_len = resources->fonts_len;
+
+ }
+
+
+ { /* Adjust window and such */
+ /* Set actual windowsize, which might be forced by OS */
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
+ INFO("Adjusting window size...");
+ windowsize = get_canvas_size(renderer);
+
+ SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
+
+ INFO("Windowsize: <%d,%d>", windowsize.x, windowsize.y);
+ }
+
+ w->renderer = renderer;
+ w->window = window;
+ w->render_scale = render_scale;
+ w->windowsize = windowsize;
+ w->game_w = NULL;
+ w->game_h = NULL;
+
+ p->data = (void*)resources;
+ p->data_len = sizeof(struct Resources);
+ p->window = w;
+ p->quit = false;
+
+ p->frame = 0;
+ p->fps_target = 60;
+
+ p->mem = memory_new(initial_memory);
+
+ /* Getting the mouse coords now resolves the issue where a click "isn't
+ * registered" when the mouse isn't moved before the user clicks */
+ SDL_GetMouseState(&p->mouse_pos.x, &p->mouse_pos.y);
+
+ p->mousedown = (v2_i32){-1, -1};
+ p->mouseup = (v2_i32){-1, -1};
+
+ p->mouse_lclick = false;
+ p->mouse_rclick = false;
+
+ p->camera_x = 0;
+ p->camera_y = 0;
+
+ p->edit_text = NULL;
+ p->edit_pos = 0;
+
+ p->bindings = NULL;
+
+#ifdef BENCHMARK
+ u32 init_stop = SDL_GetTicks();
+ INFO("Initialization took %dms", init_stop - init_start);
+#endif
+
+ INFO("Available cores: %d", nproc());
+
+ GLOBAL_PLATFORM = p;
+
+ return p;
+}
+
+i32 engine_run(Platform *p, StateType initial_state) {
+ if (p == NULL) {
+ ERROR("Platform is uninitialized.\n");
+ INFO("initialize with `engine_init`");
+ return -1;
+ }
+
+ memory* mem = p->mem;
+
+ StateType state = initial_state;
+
+ {
+ u32 state_init_time = SDL_GetTicks();
+ State_init(state, mem);
+ INFO("Initializing state \"%s\" took %ldms", StateTypeStr[state], SDL_GetTicks() - state_init_time);
+ }
+
+ u32 time = SDL_GetTicks();
+
+ // Update ticks
+ u64 ticks = 0;
+
+ /* Profiling values */
+#ifdef BENCHMARK
+ u64 profile_tick_counter = 0;
+ //u64 profile_slack = 0;
+ u64 profile_rendering = 0;
+ u64 profile_gameloop = 0;
+ u64 profile_input = 0;
+ u64 profile_input_handling = 0;
+ u64 profile_num_drawcalls = 0;
+ u32 profile_interval_timer = time;
+ const u32 profile_interval_ms = 5000;
+ const f32 profile_interval_scale = (f32)(profile_interval_ms) / 100.0f;
+#endif
+
+ const f64 frame_interval = 1000.0 / FPS_CAP;
+
+ StateType (*update_func)(void*) = State_updateFunc(state);
+
+ /* Main loop */
+ do {
+ const u32 now = SDL_GetTicks();
+ const u64 dt = now - time;
+ time = now;
+ /* Wait frame_interval */
+ if (dt < frame_interval) {
+#ifndef BENCHMARK
+ SDL_Delay(frame_interval - dt);
+
+#else
+ /* We want to know how much time is spend sleeping */
+ //profile_slack += frame_interval - dt;
+#endif
+ }
+
+#ifdef BENCHMARK
+ if (time - profile_interval_timer > profile_interval_ms) {
+ /* Ticks/frames since last measurement */
+ u32 fps = (ticks - profile_tick_counter) / profile_interval_scale;
+ u64 drawcalls = profile_num_drawcalls / profile_interval_scale / fps;
+
+ u32 sum =
+ + profile_rendering
+ //+ profile_slack
+ + profile_input
+ + profile_input_handling
+ + profile_gameloop
+ ;
+
+
+ /* Log fps and slack percentage */
+ LOG("fps:%d\t"
+ "rendering:%.2f%%\t"
+ //"slack:%.2f%%\t"
+ "input:%.2f%% (%.2f%%)\t"
+ "gameloop:%.2f%%\t"
+ "unaccounted:%llu / %llu ms\t"
+ "avg drawcalls:%llu",
+ fps,
+ 100.0f * (f32)profile_rendering / (f32)sum,
+ //100.0f * (f32)profile_slack / (f32)sum,
+ 100.0f * (f32)profile_input / (f32)sum,
+ 100.0f * (f32)profile_input_handling / (f32)sum,
+ 100.0f * (f32)profile_gameloop / (f32)sum,
+ time - profile_interval_timer - sum, sum,
+ drawcalls);
+ /* Reset values */
+ profile_tick_counter = ticks;
+ profile_interval_timer = time;
+ //profile_slack = 0;
+ profile_rendering = 0;
+ profile_gameloop = 0;
+ profile_input = 0;
+ profile_input_handling = 0;
+ profile_num_drawcalls = 0;
+ }
+#endif
+
+ /* Events */
+ BENCHEXPR(profile_input, {
+
+ if (GLOBAL_PLATFORM->mouse_lclick) {
+ GLOBAL_PLATFORM->mouseup.x = -1;
+ GLOBAL_PLATFORM->mouseup.y = -1;
+ GLOBAL_PLATFORM->mousedown.x = -1;
+ GLOBAL_PLATFORM->mousedown.y = -1;
+ GLOBAL_PLATFORM->mouse_lclick = false;
+ }
+ if (GLOBAL_PLATFORM->mouse_rclick) {
+ GLOBAL_PLATFORM->mouse_rclick = false;
+ }
+
+ /* Window events */
+ SDL_Event e[8];
+ i32 num_events;
+ SDL_PumpEvents();
+ while ((num_events = SDL_PeepEvents(e, 8, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_SYSWMEVENT)) > 0) {
+ for (i32 i = 0; i < num_events; i++) {
+ switch (e[i].type) {
+ case SDL_QUIT:
+ state = STATE_quit;
+ break;
+ case SDL_WINDOWEVENT:
+ engine_update_window(p->window, &e[i].window);
+ break;
+ default:
+ WARN("Unhandled event 0x%04x", (i32)e[i].type);
+ }
+ }
+ }
+
+ /* Mouse events */
+ while ((num_events = SDL_PeepEvents(e, 8, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEWHEEL)) > 0) {
+ for (i32 i = 0; i < num_events; i++) {
+ switch (e[i].type) {
+ case SDL_MOUSEWHEEL: break;
+ case SDL_MOUSEMOTION:
+ {
+ SDL_MouseMotionEvent m = e[i].motion;
+ /* In case of a first-person game, use xrel and yrel */
+ GLOBAL_PLATFORM->mouse_pos.x = m.x;
+ GLOBAL_PLATFORM->mouse_pos.y = m.y;
+ }
+ break;
+ case SDL_MOUSEBUTTONUP:
+ {
+ switch (e[i].button.button) {
+ case SDL_BUTTON_LEFT:
+ GLOBAL_PLATFORM->mouseup = GLOBAL_PLATFORM->mouse_pos;
+
+ GLOBAL_PLATFORM->mouse_lclick = true;
+ case SDL_BUTTON_RIGHT:
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ case SDL_MOUSEBUTTONDOWN:
+ switch (e[i].button.button) {
+ case SDL_BUTTON_LEFT:
+ GLOBAL_PLATFORM->mousedown = GLOBAL_PLATFORM->mouse_pos;
+ break;
+ case SDL_BUTTON_RIGHT:
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ WARN("Unhandled mouse event 0x%04x", (i32)e[i].type);
+ break;
+ }
+ }
+ }
+
+ BENCHEXPR(profile_input_handling, {
+ while ((num_events = SDL_PeepEvents(e, 8, SDL_GETEVENT, SDL_KEYDOWN, SDL_KEYUP)) > 0) {
+ for (i32 i = 0; i < num_events; i++) {
+ switch (e[i].type) {
+ case SDL_KEYDOWN: {
+ Keybinding lookupkey = ((Keybinding){
+ .keycode = e[i].key.keysym.sym,
+ .modifiers = e[i].key.keysym.mod
+ });
+ Keybinding *kb = hashmap_Keybinding_lookup(GLOBAL_PLATFORM->bindings, &lookupkey);
+ if (kb != NULL && kb->action != NULL) kb->action(mem->data);
+ }
+ break;
+ case SDL_KEYUP:
+ break;
+ default:
+ WARN("Unhandled mouse event 0x%04x", (i32)e[i].type);
+ break;
+ }
+ }
+ }
+ });
+ });
+
+ /* update */
+ StateType next_state;
+ BENCHEXPR(profile_gameloop, {next_state = update_func((void*)(mem->data));} );//State_update(state, mem);});
+
+ if (next_state != STATE_null) {
+ if (next_state == STATE_quit) break;
+
+ drawcall_reset();
+
+ engine_window_resize_pointers_reset();
+ State_free(state, mem);
+ memory_clear(mem);
+
+ GLOBAL_PLATFORM->bindings = NULL;
+
+ state = next_state;
+ update_func = State_updateFunc(state);
+#ifdef BENCHMARK
+ {
+ u32 t = SDL_GetTicks();
+ State_init(state, mem);
+ LOG("Initializing %s took %dms", StateTypeStr[state], SDL_GetTicks() - t);
+ }
+#else
+ State_init(state, mem);
+#endif
+ } else {
+#ifdef BENCHMARK
+ profile_num_drawcalls += drawcall_len;
+#endif
+ render_begin(p->window);
+ BENCHEXPR(profile_rendering, {render_present(p->window);})
+ }
+
+
+ ticks++;
+ } while (state != STATE_quit);
+
+ return 0;
+}
+
+
+void stop(Platform *p) {
+ if (p == NULL) return;
+
+ { /* Deallocate resources */
+ struct Resources *r = (struct Resources*)p->data;
+ if (r != NULL) {
+ /* Destroy textures */
+ for (usize i = 0; i < r->textures_len; i++) {
+ if (r->textures[i] != NULL) {
+ SDL_DestroyTexture(r->textures[i]->texture);
+ r->textures[i] = NULL;
+ }
+ }
+ free(r->textures);
+
+ /* Destroy Fonts */
+ for (usize i = 0; i < r->fonts_len; i++) {
+ if (r->fonts[i] != NULL) {
+ TTF_CloseFont(r->fonts[i]);
+ r->fonts[i] = NULL;
+ }
+ }
+ free(r->fonts);
+ }
+ }
+
+ { /* Deallocate window */
+ Window *w = p->window;
+ if (w != NULL) {
+ if (w->window != NULL) { SDL_DestroyWindow(w->window); w->window = NULL; }
+ if (w->renderer != NULL) { SDL_DestroyRenderer(w->renderer); w->renderer = NULL; }
+ }
+ }
+
+ TTF_Quit();
+ IMG_Quit();
+ SDL_Quit();
+}
+
+void engine_fps_max(u64 cap) { FPS_CAP = cap; }
+
+void engine_bindkey(i32 key, void (*action)(void*)) {
+ /* TODO: Make a hashmap ++ linked list */
+ //if (key >= 512) {
+ // ERROR("Key value too high! (got %d)\n", key);
+ //}
+ if (GLOBAL_PLATFORM->bindings == NULL) {
+ //GLOBAL_PLATFORM->bindings = memory_allocate(GLOBAL_PLATFORM->mem, 512 * sizeof(Keybinding));
+ GLOBAL_PLATFORM->bindings = memory_allocate(GLOBAL_PLATFORM->mem, sizeof(hashmap_Keybinding));
+ }
+
+ Keybinding kb = (Keybinding){.keycode = key, .action = action};
+ hashmap_Keybinding_insert(GLOBAL_PLATFORM->mem, GLOBAL_PLATFORM->bindings, &kb);
+}
+
+
+u32 get_time(void) {return SDL_GetTicks();}
+v2_i32 get_windowsize(void) {return GLOBAL_PLATFORM->window->windowsize;}
+v2_i32 *get_mousepos(void) { return &GLOBAL_PLATFORM->mouse_pos; }
diff --git a/src/fov.c b/src/fov.c
new file mode 100644
index 0000000..cdc7dc8
--- /dev/null
+++ b/src/fov.c
@@ -0,0 +1,97 @@
+#include <stdint.h>
+#include <math.h>
+#include <engine/fov.h>
+#include <engine/utils.h>
+
+void fov_shadowcast_rec(const void *map, const v2_i32 mapsize,
+ bool (*visblocking)(const void*), i32 *lightmap,
+ const i32 range, v2_i32 src,
+ const i32 row,
+ f32 start, const f32 end,
+ const i8 xx, const i8 xy, const i8 yx, const i8 yy) {
+
+ if (start < end) return;
+
+ const i32 range_2 = range * range;
+ f32 new_start = start;
+
+ for (i32 i = row; i <= range; i++) {
+ i32 dx = (-1 * i) - 1;
+ i32 dy = -1 * i;
+
+ bool blocked = false;
+
+ while (dx <= 0) {
+ dx += 1;
+
+ const i32 mapx = src.x + dx * xx + dy * xy;
+ const i32 mapy = src.y + dx * yx + dy * yy;
+
+ const f32 slope_l = (((f32)dx) - 0.5f) / (((f32)dy) + 0.5f);
+ const f32 slope_r = (((f32)dx) + 0.5f) / (((f32)dy) - 0.5f);
+
+ if (start < slope_r) continue;
+ if (end > slope_l) break;
+
+ if (dx * dx + dy * dy < range_2) {
+ /* set as visible */
+ if (mapx >= 0 && mapx < (long)mapsize.x
+ && mapy >= 0 && mapy < (long)mapsize.y) {
+ // TODO: Calculate proper dist from source
+ f32 x_2 = (src.x - mapx) * (src.x - mapx);
+ f32 y_2 = (src.y - mapy) * (src.y - mapy);
+ lightmap[mapy*mapsize.x + mapx] = MAX(lightmap[mapy*mapsize.x + mapx], range - sqrt((f32)(x_2 + y_2)));
+ }
+ }
+
+ /* sizeof(i32) is the size of enums */
+ /* -- unless the compiler doesn't follow standard behaviour */
+ const bool is_blocked = visblocking(
+ (void*)((u64)map
+ + sizeof(i32) /* ~ enum size */
+ * (mapsize.x * mapy + mapx) /* index */
+ ));
+
+ if (blocked) {
+ if (!is_blocked) {
+ new_start = slope_r;
+ } else {
+ blocked = false;
+ start = new_start;
+ }
+ } else if (!is_blocked && i < range) {
+ blocked = true;
+ fov_shadowcast_rec(map, mapsize, visblocking, lightmap, range, src, i+1, start, slope_l, xx, xy, yx, yy);
+ new_start = slope_r;
+ }
+ }
+
+ if (blocked) break;
+ }
+}
+
+/* http://www.roguebasin.com/index.php?title=FOV_using_recursive_shadowcasting */
+void fov_shadowcast(const void *map, const v2_i32 mapsize,
+ bool (*visblocking)(const void*), i32 *lightmap,
+ const i32 range, const v2_i32 src) {
+
+ const i8 m[4][8] = {
+ {1, 0, 0, -1, -1, 0, 0, 1},
+ {0, 1, -1, 0, 0, -1, 1, 0},
+ {0, 1, 1, 0, 0, -1, -1, 0},
+ {1, 0, 0, 1, -1, 0, 0, -1},
+ };
+
+ for (i32 oct = 0; oct < 8; oct++) {
+ fov_shadowcast_rec(
+ map, mapsize, visblocking, lightmap, range, src,
+ 1, 1.0, 0.0,
+ m[0][oct],
+ m[1][oct],
+ m[2][oct],
+ m[3][oct]);
+ }
+
+ /* The center is the most lit square */
+ lightmap[src.y * mapsize.x + src.x] = range;
+}
diff --git a/src/hashmap.c b/src/hashmap.c
new file mode 100644
index 0000000..edcf9cb
--- /dev/null
+++ b/src/hashmap.c
@@ -0,0 +1,5 @@
+#include <engine/hashmap.h>
+
+i32 lolhash(const usize s, i32 v) {
+ return v % s;
+}
diff --git a/src/logging.c b/src/logging.c
new file mode 100644
index 0000000..2ca5368
--- /dev/null
+++ b/src/logging.c
@@ -0,0 +1,80 @@
+#include <stdlib.h>
+#include <engine/logging.h>
+
+char *itoa(i32 x) {
+ const i32 size = (((i32)ceil(log10((f64)x)))+1) * sizeof(char);
+ char *retval = (char*)malloc(size);
+ if (retval == NULL) {
+ perror("Failed to allocate memory for itoa");
+ exit(EXIT_FAILURE);
+ }
+ sprintf(retval, "%d", x);
+ return retval;
+}
+
+void _log(FILE *stream, const char *prefix, const char *fmt, va_list ap) {
+ if (stream == NULL) {
+ fprintf(stderr, "_log got NULL in stream argument\n");
+ exit(EXIT_FAILURE);
+ }
+ fputs(prefix, stream);
+ vfprintf(stream, fmt, ap);
+}
+
+void LOG(const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ _log(stdout, "[" TERM_COLOR_BLUE "LOG" TERM_COLOR_RESET "] ", fmt, ap);
+ va_end(ap);
+ puts("");
+}
+
+void INFO_(const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ _log(stdout, "[" TERM_COLOR_GREEN "INFO" TERM_COLOR_RESET "] ", fmt, ap);
+ va_end(ap);
+}
+
+void INFO(const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ _log(stdout, "[" TERM_COLOR_GREEN "INFO" TERM_COLOR_RESET "] ", fmt, ap);
+ va_end(ap);
+ puts("");
+}
+
+void __DEBUG(const char* file, const i32 line, const char* func, const char *fmt, ...) {
+ va_list ap;
+
+ const usize prefix_len = 1024;
+
+ char *prefix = malloc(sizeof(char) * 1024);
+
+ snprintf(prefix,
+ prefix_len,
+ "["TERM_COLOR_YELLOW"DEBUG"TERM_COLOR_RESET"] "
+ "%s:%d <%s> ", file, line, func);
+
+ va_start(ap, fmt);
+ _log(stdout, prefix, fmt, ap);
+ va_end(ap);
+
+ free(prefix);
+}
+
+void WARN(const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ _log(stderr, "[" TERM_COLOR_PURPLE "WARN" TERM_COLOR_RESET "] ", fmt, ap);
+ va_end(ap);
+ puts("");
+}
+
+void ERROR(const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ _log(stderr, "[" TERM_COLOR_RED "ERROR" TERM_COLOR_RESET "] ", fmt, ap);
+ va_end(ap);
+ puts("");
+}
diff --git a/src/memory.c b/src/memory.c
new file mode 100644
index 0000000..6ef63ce
--- /dev/null
+++ b/src/memory.c
@@ -0,0 +1,62 @@
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <engine/logging.h>
+
+#include <engine/memory.h>
+
+memory *memory_new(usize max_size) {
+ memory *m = malloc(sizeof(memory));
+ m->data = malloc(max_size);
+ m->size = max_size;
+ m->pos = 0;
+ m->free = max_size;
+
+ memset(m->data, 0, max_size);
+
+ return m;
+}
+
+/* Returns a pointer to the allocated data */
+void *memory_allocate(memory* mem, usize size) {
+ void* data = NULL;
+
+ if (mem->pos + size <= mem->size) {
+ data = (void*)((usize)mem->data + mem->pos);
+ mem->pos += size;
+ mem->free -= size;
+ } else {
+ ERROR("Trying to allocate %lu in a %lu sized memory block", size, mem->size);
+ ERROR("No more room!");
+ exit(EXIT_FAILURE);
+ }
+
+ return data;
+}
+
+memory memory_init(void *data, usize size) {
+ memory m = {0};
+ m.data = data;
+ m.size = size;
+ m.free = 0;
+ return m;
+}
+
+void memory_free(memory *mem, usize size) {
+ if (size > mem->pos) {
+ perror("Freeing too much memory!");
+ exit(EXIT_FAILURE);
+ } else {
+ mem->pos -= size;
+ mem->free += size;
+ }
+}
+
+void memory_clear(memory *mem) {
+ mem->pos = 0;
+ mem->free = mem->size;
+ /* Reset the memory? */
+ memset(mem->data, 0, mem->size);
+}
diff --git a/src/rendering.c b/src/rendering.c
new file mode 100644
index 0000000..755eda0
--- /dev/null
+++ b/src/rendering.c
@@ -0,0 +1,152 @@
+#include <stdio.h>
+#include <string.h>
+
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_image.h>
+#include <SDL2/SDL_ttf.h>
+
+#define ENGINE_INTERNALS
+#include <engine/engine.h>
+#include <engine/rendering.h>
+
+/* Extern globals */
+extern Platform *GLOBAL_PLATFORM;
+
+/* Globals */
+#define drawcall_limit (64 * 1024)
+RenderDrawCall drawcalls[drawcall_limit];
+i32 drawcall_len = 0;
+
+/* Implementations */
+
+/* Clear the screen,
+ * To be used inbetween draw calls */
+void render_begin(Window *w) {
+#ifdef DEBUG
+ SDL_SetRenderDrawColor(w->renderer, 0x10, 0x0a, 0x33, 0x00);
+#else
+ SDL_SetRenderDrawColor(w->renderer, 0x00, 0x00, 0x00, 0x00);
+#endif
+ SDL_RenderClear(w->renderer);
+}
+
+void render_present(Window *w) {
+ for (i32 i = 0; i < drawcall_len; i++) {
+ RenderDrawCall dc = drawcalls[i];
+ switch (dc.type) {
+ case RenderDrawCallType_UITree:
+ render_uitree(w, dc.data.data);
+ break;
+ case RenderDrawCallType_Text:
+ LOG("RenderDrawCallType_Text rendering not implemented!");
+ break;
+ case RenderDrawCallType_Sprite:
+ {
+#ifdef DEBUG
+ if (dc.data.sprite.sprite == NULL) {
+ __asm__("int3;");
+ WARN("Sprite %lx in drawcall %d/%d had NULL reference", dc.data.sprite.sprite, i, drawcall_len);
+
+ drawcall_len = 0;
+ SDL_RenderPresent(w->renderer);
+ exit(EXIT_FAILURE);
+ }
+#endif
+ Sprite s = *dc.data.sprite.sprite;
+ Texture *t = ((struct Resources*)GLOBAL_PLATFORM->data)->textures[s.texture_id];
+ i32 ts = t->tilesize;
+ SDL_Rect src = {
+ s.coord.x,
+ s.coord.y,
+ ts,ts
+ };
+ SDL_Rect dst = {
+ dc.data.sprite.x,
+ dc.data.sprite.y,
+ ts * dc.data.sprite.scale,
+ ts * dc.data.sprite.scale,
+ };
+ SDL_SetTextureColorMod(t->texture,
+ dc.data.sprite.mod.r,
+ dc.data.sprite.mod.g,
+ dc.data.sprite.mod.b);
+ SDL_RenderCopy(
+ w->renderer,
+ t->texture,
+ &src, &dst);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ drawcall_len = 0;
+
+ SDL_RenderPresent(w->renderer);
+}
+
+void drawcall_reset(void) {
+ drawcall_len = 0;
+}
+
+void engine_window_resize_pointers(i32* w, i32* h) {
+ GLOBAL_PLATFORM->window->game_w = w;
+ GLOBAL_PLATFORM->window->game_h = h;
+}
+
+void engine_window_resize_pointers_reset(void) {
+ GLOBAL_PLATFORM->window->game_w = NULL;
+ GLOBAL_PLATFORM->window->game_h = NULL;
+}
+
+void engine_draw_uitree(UITree *t) {
+ if (drawcall_len + 1 >= drawcall_limit) return;
+ drawcalls[drawcall_len++] = (RenderDrawCall){
+ .type = RenderDrawCallType_UITree,
+ .data.data = (void*)t
+ };
+}
+
+void engine_draw_sprite(Sprite *s, v2_i32 *pos, f32 scale) {
+ if (drawcall_len + 1 >= drawcall_limit) return;
+#ifdef DEBUG
+ if (s == NULL) __asm__("int3;");
+#endif
+ drawcalls[drawcall_len++] = (RenderDrawCall){
+ .type = RenderDrawCallType_Sprite,
+ .data.sprite = {
+ .sprite = s,
+ .x = pos->x,
+ .y = pos->y,
+ .scale = scale,
+ .mod = {0xFF, 0xFF, 0xFF, 0xFF},
+ }
+ };
+}
+
+void engine_draw_sprite_ex(Sprite *s, v2_i32 *pos, f32 scale, Engine_color colormod) {
+ if (drawcall_len + 1 >= drawcall_limit) return;
+#ifdef DEBUG
+ if (s == NULL) __asm__("int3;");
+#endif
+ drawcalls[drawcall_len++] = (RenderDrawCall){
+ .type = RenderDrawCallType_Sprite,
+ .data.sprite = {
+ .sprite = s,
+ .x = pos->x,
+ .y = pos->y,
+ .scale = scale,
+ .mod = {colormod.r, colormod.g, colormod.b, colormod.a},
+ }
+ };
+}
+
+
+Sprite sprite_new(u64 tid, u8 coord) {
+ const i32 ts = ((struct Resources*)GLOBAL_PLATFORM->data)->textures[tid]->tilesize;
+ return (Sprite){.texture_id = tid, (v2_i32){
+ .x = ts * (coord & 0x0F),
+ .y = ts * ((coord & 0xF0) >> 4),
+ }};
+}
diff --git a/src/stack.c b/src/stack.c
new file mode 100644
index 0000000..edc2d9f
--- /dev/null
+++ b/src/stack.c
@@ -0,0 +1,88 @@
+#include <stdlib.h>
+#include <engine/logging.h>
+#include <engine/stack.h>
+
+
+Stack stack_new_ex(const usize element_size, const usize size) {
+ Stack s = {
+ .head = 0,
+ .elem_size = element_size,
+ .size = element_size * size,
+ .chunk_size = element_size * size,
+ .data = NULL,
+ };
+
+ s.data = (void*)calloc(element_size, size);
+ return s;
+}
+
+
+Stack stack_new(const usize element_size) {
+ return stack_new_ex(element_size, 512);
+}
+
+
+void stack_free(Stack *s) {
+ if (s->data == NULL) return;
+ free(s->data);
+ s->data = NULL;
+}
+
+
+void *stack_pop(Stack *s) {
+ if (s->head == 0) return NULL; /* Empty stack */
+ return (u8*)s->data + (--(s->head) * s->elem_size);
+}
+
+
+void stack_push(Stack *s, void *elem) {
+ if (elem == NULL) {WARN("%s received a nullptr", __func__); return;}
+ if (s->head > 0 && s->head * s->elem_size >= s->size) {
+ WARN("Allocating more stack memory");
+ /* Reallocate more memory and update size */
+ void* ptr = realloc(s->data, s->size + s->chunk_size);
+ if (ptr == NULL) {
+ ERROR("Failed to resize memory for stack");
+ exit(EXIT_FAILURE);
+ }
+ s->data = ptr;
+ //memset((void*)((u64)s->data + (s->size - s->elem_size)), 0, s->chunk_size);
+ s->size += s->chunk_size;
+ }
+ memcpy((u8*)s->data + s->head * s->elem_size, elem, s->elem_size);
+ s->head++;
+}
+
+
+void *stack_peek(Stack *s) {
+ if (s->head <= 0) return NULL; /* Empty stack */
+ return (u8*)s->data + ((s->head - 1) * s->elem_size);
+}
+
+
+isize stack_size(const Stack *s) {
+ return s->head;
+}
+
+
+void stack_swap(Stack *s, Stack *t) {
+ if (s->size > t->size) {
+ t->data = realloc(t->data, s->size);
+ } else if (t->size > s->size) {
+ s->data = realloc(s->data, t->size);
+ }
+ void* tmp = malloc(s->size);
+ if (tmp == NULL) {
+ ERROR("Failed to allocate memory for stack swapping!");
+ exit(EXIT_FAILURE);
+ }
+ isize shead = s->head;
+
+ memcpy(tmp, s->data, s->size);
+ memcpy(s->data, t->data, t->size);
+ memcpy(t->data, tmp, s->size);
+
+ s->head = t->head;
+ t->head = shead;
+ free(tmp);
+}
diff --git a/src/state.c b/src/state.c
new file mode 100644
index 0000000..6b1f9c8
--- /dev/null
+++ b/src/state.c
@@ -0,0 +1,91 @@
+#include <engine/state.h>
+
+#include <include_states.h>
+
+typedef StateType state_update_t (void*);
+
+const char *StateTypeStr[] = {
+ "null",
+#define State(name) #name,
+#include <state_type_list.h>
+#undef State
+ "quit",
+};
+
+void State_init(StateType type, memory *mem) {
+ switch (type) {
+#define State(name) \
+ case (STATE_##name): { \
+ name##_init(memory_allocate(mem, sizeof(name##_state))); \
+ break; \
+ }
+#include <state_type_list.h>
+#undef State
+ case STATE_null:
+ case STATE_quit:
+ _DEBUG("Got %s state.\n", StateTypeStr[type]);
+ break;
+ default:
+ exit(EXIT_FAILURE);
+ }
+}
+
+
+void State_free(StateType type, memory *mem) {
+ switch (type) {
+#define State(name) \
+ case (STATE_##name): { \
+ name##_free(mem->data); \
+ break; \
+ }
+#include <state_type_list.h>
+#undef State
+ case STATE_null:
+ case STATE_quit:
+ _DEBUG("Got %s state.\n", StateTypeStr[type]);
+ break;
+ default:
+ exit(EXIT_FAILURE);
+ }
+ memory_clear(mem);
+}
+
+
+StateType (*State_updateFunc(StateType type))(void*) {
+ switch (type) {
+#define State(name) \
+ case (STATE_##name): { \
+ return (state_update_t*)&name##_update; \
+ break; \
+ }
+#include <state_type_list.h>
+#undef State
+ case STATE_null:
+ case STATE_quit:
+ return NULL; //_DEBUG("Got %s state.\n", StateTypeStr[type]);
+ break;
+ default:
+ exit(EXIT_FAILURE);
+ }
+ return NULL;
+}
+
+StateType State_update(StateType type, memory *mem) {
+ StateType next_state = STATE_null;
+ switch (type) {
+#define State(name) \
+ case (STATE_##name): { \
+ next_state = name##_update(mem->data); \
+ break; \
+ }
+#include <state_type_list.h>
+#undef State
+ case STATE_null:
+ case STATE_quit:
+ _DEBUG("Got %s state.\n", StateTypeStr[type]);
+ break;
+ default:
+ exit(EXIT_FAILURE);
+ }
+ return next_state;
+}
diff --git a/src/ui_positioning.c b/src/ui_positioning.c
new file mode 100644
index 0000000..fc8ab0a
--- /dev/null
+++ b/src/ui_positioning.c
@@ -0,0 +1,877 @@
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_image.h>
+#include <SDL2/SDL_ttf.h>
+
+#include <stdint.h>
+#define ENGINE_INTERNALS
+
+#include <engine/engine.h>
+#include <engine/btree.h>
+
+static Engine_color DEFAULT_FG = {0xFF, 0xFF, 0xFF, 0xFF};
+static Engine_color DEFAULT_BG = {0x00, 0x00, 0x00, 0xFF};
+static i32 DEFAULT_PADDING = 8;
+static i32 DEFAULT_MARGIN = 0;
+
+/* We want to keep track of the roots of the UI elements, s.t. we can resize and
+ * reposition the elements if needed be. */
+struct btree *GLOBAL_UIROOTS = NULL;
+
+extern Platform *GLOBAL_PLATFORM;
+
+const char *uitype_str[] = {
+ [uitype_container] = "uitype_container",
+ [uitype_button] = "uitype_button",
+ [uitype_title] = "uitype_title",
+ [uitype_text] = "uitype_text",
+ [uitype_MAX] = "uitype_MAX",
+ NULL,
+};
+
+const char *constraint_str[] = {
+ [ui_constraint_width] = "ui_constraint_width",
+ [ui_constraint_height] = "ui_constraint_height",
+
+ [ui_constraint_horizontal_align] = "ui_constraint_horizontal_align",
+ [ui_constraint_vertical_align] = "ui_constraint_vertical_align",
+
+ [ui_constraint_left] = "ui_constraint_left",
+ [ui_constraint_right] = "ui_constraint_right",
+ [ui_constraint_top] = "ui_constraint_top",
+ [ui_constraint_bottom] = "ui_constraint_bottom",
+};
+
+static i32 pointer_cmp(const void *a, const void *b) {
+ const u64 *x = a;
+ const u64 *y = b;
+ /* We just use the address as index */
+ return *x - *y;
+}
+
+static void pointer_print(const void *a) {
+ const u64 *t = a;
+ printf("%lx\n", *t);
+}
+
+void ui_rearrange(UITree *root, v2_i32 ppos, v2_i32 psize);
+i32 get_padding(UITree *t);
+i32 get_margin(UITree *t);
+v2_i32 get_pos(UITree *t);
+v2_i32 get_size(UITree *t);
+
+f32 ui_size_pixel_to_percent_width(i32 pixels) {
+ return (f32)pixels / (f32)GLOBAL_PLATFORM->window->windowsize.x;
+}
+
+f32 ui_size_pixel_to_percent_height(i32 pixels) {
+ return (f32)pixels / (f32)GLOBAL_PLATFORM->window->windowsize.y;
+}
+
+f32 ui_size_pixel_to_percent(i32 pixels) { return ui_size_percent_height_to_pixel(pixels); }
+
+i32 ui_size_percent_width_to_pixel(f32 percent) {
+ return (i32)(percent * GLOBAL_PLATFORM->window->windowsize.x);
+}
+
+i32 ui_size_percent_height_to_pixel(f32 percent) {
+ return (i32)(percent * GLOBAL_PLATFORM->window->windowsize.y);
+}
+
+i32 ui_size_percent_to_pixel(f32 percent) {return ui_size_percent_height_to_pixel(percent); }
+
+i32 ui_size_to_pixel_ex(ui_size s, bool vertical, v2_i32 psize) {
+ if (s.type == ui_size_pixel) {
+ return s.pixel.value;
+ } else {
+ psize.x = MAX(1, psize.x);
+ psize.y = MAX(1, psize.y);
+ if (vertical) {
+ return psize.y * s.percent.value;
+ }
+ return psize.x * s.percent.value;
+ }
+}
+
+i32 ui_size_to_pixel(ui_size s) {
+ if (s.type == ui_size_pixel) {
+ return s.pixel.value;
+ } else {
+ return ui_size_percent_height_to_pixel(s.percent.value);
+ }
+}
+
+void ui_add(UITree *t) {
+
+ if (GLOBAL_UIROOTS == NULL) {
+ GLOBAL_UIROOTS = btree_new(sizeof(void*), 16, &pointer_cmp);
+ }
+
+ u64 tmp = (u64)t;
+ btree_insert(GLOBAL_UIROOTS, &tmp);
+}
+
+void clear_ui(void) {
+ UITree *t = NULL;
+ while((t = btree_first(GLOBAL_UIROOTS)) != NULL) {
+ uitree_free(t);
+ if (!btree_delete(GLOBAL_UIROOTS, t)) {
+ WARN("Failed deletion");
+ UITree *elem = (UITree*)t;
+ WARN("Failed to remove %s from global ui-table", uitype_str[elem->type]);
+ }
+ }
+}
+
+
+/* Unwraps the size to a v2_i32 */
+v2_i32 uitree_get_size(UITree *t) {
+ if (t == NULL) return (v2_i32){.x=-1, .y=-1};
+ switch (t->type) {
+ case uitype_container: return (v2_i32){.x=t->container.w, .y=t->container.h};
+ case uitype_button: return (v2_i32){.x=t->button.w, .y=t->button.h};
+ case uitype_title: return (v2_i32){.x=t->title.w, .y=t->title.h};
+ case uitype_text: return (v2_i32){.x=t->text.w, .y=t->text.h};
+ default: return (v2_i32){.x=-1, .y=-1};
+ }
+}
+
+
+/* elem_size also takes into account the padding and other stuffs */
+v2_i32 elem_size(UITree *root) {
+ if (root == NULL) return (v2_i32){0,0};
+
+ const i32 total_padding = 2 * get_padding(root);
+ return v2_i32_add_i(uitree_get_size(root), total_padding);
+}
+
+v2_i32 elem_size_spacing(UITree *root) {
+ if (root == NULL) return (v2_i32){0,0};
+
+ const i32 total_margin = 2 * get_padding(root) + 2 * get_margin(root);
+ return v2_i32_add_i(uitree_get_size(root), total_margin);
+}
+
+UITree *ui_container_new_ex(
+ Engine_color fg,
+ Engine_color bg,
+ Engine_color border,
+ bool direction,
+ struct List_ui_constraint *constraints) {
+
+ UITree *t = malloc(sizeof(UITree));
+
+ t->container.type = uitype_container;
+ t->container.visible = true;
+ t->container.direction = direction;
+
+ /* -1 == unsolved constraint */
+ t->container.x = -1;
+ t->container.y = -1;
+ t->container.w = -1;
+ t->container.h = -1;
+ t->container.padding = 0;
+ t->container.margin = 0;
+
+ t->container.fg = fg;
+ t->container.bg = bg;
+ t->container.bordercolor = border;
+
+
+ t->container.children = NULL;
+ t->container.children_len = 0;
+ t->container.children_size = 0;
+
+ t->container.constraints = constraints;
+
+ ui_rearrange(t, (v2_i32){0,0}, (v2_i32){0,0});
+
+ ui_add(t);
+
+ return t;
+}
+
+UITree *ui_container(bool direction, struct List_ui_constraint *constraints) {
+ return ui_container_new_ex(
+ /* We set border color to fg by default */
+ DEFAULT_FG, DEFAULT_BG, DEFAULT_FG, direction,
+ constraints);
+}
+
+UITree *ui_title(i32 font_id, const char *text, struct List_ui_constraint *constraints) {
+ UITree_title *t = malloc(sizeof(UITree));
+ t->type = uitype_title;
+
+ t->x = -1;
+ t->y = -1;
+
+ t->padding = DEFAULT_PADDING;
+ t->margin = DEFAULT_MARGIN;
+
+ t->fg = DEFAULT_FG;
+
+ t->font_id = font_id;
+ t->text_original = text;
+ t->text_texture_index =
+ engine_render_text(font_id, t->fg, (char*)text, &t->texture_size, false);
+
+ t->w = t->texture_size.x;
+ t->h = t->texture_size.y;
+
+ t->constraints = constraints;
+
+ ui_rearrange((UITree*)t, (v2_i32){0,0}, (v2_i32){0,0});
+
+ ui_add((UITree*)t);
+
+ return (UITree*)t;
+}
+
+UITree *ui_text(i32 font_id, const char *text, struct List_ui_constraint *constraints) {
+ UITree *t = malloc(sizeof(UITree));
+ t->text.type = uitype_text;
+
+ t->text.x = -1;
+ t->text.y = -1;
+
+ t->text.padding = 0;
+ t->text.margin = DEFAULT_MARGIN / 2;
+
+ t->text.fg = DEFAULT_FG;
+
+ t->text.font_id = font_id;
+ t->text.text_original = text;
+
+ /* Postpone rendering until we have a clear set of size-constraints */
+
+ //t->text.text_texture_index =
+ // engine_render_text(font_id, DEFAULT_FG, (char*)text, &t->text.texture_size, true);
+
+ //t->text.w = t->text.texture_size.x;
+ //t->text.h = t->text.texture_size.y;
+
+ t->text.constraints = constraints;
+
+ ui_rearrange(t, (v2_i32){0,0}, (v2_i32){0,0});
+
+ ui_add(t);
+
+ return t;
+}
+
+UITree *ui_button(u64 id, i32 font_id, char *text, struct List_ui_constraint *constraints) {
+ UITree_button *t = malloc(sizeof(UITree));
+ t->type = uitype_button;
+
+ t->enabled = true;
+ t->id = id;
+ t->x = -1;
+ t->y = -1;
+ t->padding = DEFAULT_PADDING;
+ t->margin = DEFAULT_MARGIN;
+
+ t->fg = DEFAULT_FG;
+ t->bg = DEFAULT_BG;
+
+ t->font_id = font_id;
+ t->text_original = text;
+ t->text_texture_index =
+ engine_render_text(font_id, t->fg, (char*)text, &t->texture_size, false);
+
+ t->w = t->texture_size.x;
+ t->h = t->texture_size.y;
+
+ t->constraints = constraints;
+
+ ui_rearrange((UITree*)t, (v2_i32){0,0}, (v2_i32){0,0});
+
+ ui_add((UITree*)t);
+
+ return (UITree*)t;
+}
+
+void uitree_free(UITree *t) {
+ switch (t->type) {
+ case uitype_container:
+ if (t->container.children != NULL && t->container.children_len > 0) {
+ for (usize i = 0; i < t->container.children_len; i++) {
+ uitree_free(t->container.children[i]);
+ }
+ free(t->container.children);
+ }
+ break;
+ case uitype_button:
+ case uitype_title:
+ case uitype_text:
+ free(t);
+ break;
+ default: break;
+ }
+}
+
+
+struct List_ui_constraint* get_constraints(UITree *t) {
+ switch (t->type) {
+ case uitype_container: return t->container.constraints;
+ case uitype_button: return t->button.constraints;
+ case uitype_title: return t->title.constraints;
+ case uitype_text: return t->text.constraints;
+ default: return NULL;
+ }
+}
+
+
+i32 get_padding(UITree *t) {
+ switch (t->type) {
+ case uitype_container: return t->container.padding;
+ case uitype_button: return t->button.padding;
+ case uitype_title: return t->title.padding;
+ case uitype_text: return t->text.padding;
+ default: return 0;
+ }
+}
+
+
+i32 get_margin(UITree *t) {
+ switch (t->type) {
+ case uitype_container: return t->container.margin;
+ case uitype_button: return t->button.margin;
+ case uitype_title: return t->title.margin;
+ case uitype_text: return t->text.margin;
+ default: return 0;
+ }
+}
+
+v2_i32 get_pos(UITree *t) {
+ switch (t->type) {
+ case uitype_container: return (v2_i32){t->container.x, t->container.y};
+ case uitype_button: return (v2_i32){t->button.x, t->button.y};
+ case uitype_title: return (v2_i32){t->title.x, t->title.y};
+ case uitype_text: return (v2_i32){t->text.x, t->text.y};
+ default: return (v2_i32){0, 0};
+ }
+}
+
+v2_i32 get_size(UITree *t) {
+ switch (t->type) {
+ case uitype_container: return (v2_i32){t->container.w, t->container.h};
+ case uitype_button: return (v2_i32){t->button.w, t->button.h};
+ case uitype_title: return (v2_i32){t->title.w, t->title.h};
+ case uitype_text: return (v2_i32){t->text.w, t->text.h};
+ default: return (v2_i32){0, 0};
+ }
+}
+
+#define SETVAL(VAL, tt) { if ((VAL) > 0) t->tt = VAL; }
+void uitree_set_pos(UITree *t, v2_i32 newpos) {
+ switch (t->type) {
+ case uitype_container: SETVAL( newpos.x, container.x); SETVAL( newpos.y, container.y); break;
+ case uitype_button: SETVAL( newpos.x, button.x ); SETVAL( newpos.y, button.y ); break;
+ case uitype_title: SETVAL( newpos.x, title.x ); SETVAL( newpos.y, title.y ); break;
+ case uitype_text: SETVAL( newpos.x, text.x ); SETVAL( newpos.y, text.y ); break;
+ default: return;
+ }
+}
+
+
+void uitree_set_size(UITree *t, v2_i32 size) {
+ switch (t->type) {
+ case uitype_container: SETVAL( size.x, container.w); SETVAL( size.y, container.h); break;
+ case uitype_button: SETVAL( size.x, button.w ); SETVAL( size.y, button.h ); break;
+ case uitype_title: SETVAL( size.x, title.w ); SETVAL( size.y, title.h ); break;
+ case uitype_text: SETVAL( size.x, text.w ); SETVAL( size.y, text.h ); break;
+ default: return;
+ }
+}
+
+
+bool uitree_has_constraint(UITree *t, const ui_constraint_t constraint) {
+ struct List_ui_constraint* c = NULL;
+ struct List_ui_constraint* first_constraint;
+ if (t == NULL) {ERROR("%s got nullptr argument!", __func__); exit(EXIT_FAILURE);}
+
+ switch (t->type) {
+ case uitype_container: c = t->container.constraints; break;
+ case uitype_button: c = t->button.constraints; break;
+ case uitype_title: c = t->title.constraints; break;
+ case uitype_text: c = t->text.constraints; break;
+ default: break;
+ }
+
+ first_constraint = c;
+
+ while (c != NULL) {
+ if ((void*)c->next == (void*)first_constraint) {
+ ERROR("Infinite constraint loop detected!");
+ exit(EXIT_FAILURE);
+ }
+ if (constraint == c->value.type) return true;
+
+ c = c->next;
+ }
+ return false;
+}
+
+
+void ui_constraints_apply(UITree *t, v2_i32 ppos, v2_i32 psize) {
+ struct List_ui_constraint* constraints = get_constraints(t);
+ struct List_ui_constraint* first_constraint = constraints;
+ v2_i32 tsize = uitree_get_size(t);
+
+ if (constraints == NULL) return;
+
+ while (constraints != NULL) {
+ if ((void*)constraints->next == (void*)first_constraint) {
+ ERROR("Infinite constraint loop detected!");
+ ERROR(" Type: %s", constraint_str[constraints->value.type]);
+ exit(EXIT_FAILURE);
+ }
+
+ const constraintval_t con = constraints->value.constraint;
+
+ switch(constraints->value.type) {
+ case ui_constraint_width:
+ {
+ i32 ww = ui_size_to_pixel_ex(con.width.size, false, psize);
+ if (con.width.size.type == ui_size_percent) ww -= 2 * get_padding(t);
+ /* FIXME: Resizing the window makes some elements retain their
+ * size on window resize.
+ * This is fixed by replacing MAX(...) with just `ww`,
+ * This in turn makes elements that are supposed to have some
+ * relative width not take up the full width. */
+ uitree_set_size(t, (v2_i32){.x=MAX(tsize.x, ww), .y=0});
+ tsize = uitree_get_size(t);
+ }
+ break;
+
+ case ui_constraint_height:
+ {
+ i32 ww = ui_size_to_pixel_ex(con.height.size, true, psize);
+ if (con.height.size.type == ui_size_percent) ww -= 2 * get_padding(t);
+ /* FIXME: Same as above */
+ uitree_set_size(t, (v2_i32){.y=MAX(tsize.y, ww), .x=0});
+ tsize = uitree_get_size(t);
+ }
+ break;
+
+
+ case ui_constraint_horizontal_align:
+ {
+ const i32 size = ui_size_to_pixel_ex(con.horizontal_align.spacing, false, psize);
+ i32 new_x = ppos.x;
+ switch (con.horizontal_align.align) {
+ case ui_constraint_horizontal_align_left:
+ new_x += size + get_margin(t);
+ break;
+ case ui_constraint_horizontal_align_center:
+ new_x += (psize.x / 2) - (tsize.x / 2);
+ break;
+ case ui_constraint_horizontal_align_right:
+ new_x += psize.x - size - tsize.x - get_margin(t);
+ break;
+ }
+ uitree_set_pos(t, (v2_i32){.x = new_x, .y = 0});
+ }
+ break;
+
+ case ui_constraint_vertical_align:
+ {
+ i32 size = ui_size_to_pixel_ex(con.horizontal_align.spacing, true, psize);
+ i32 new_y = ppos.y;
+ switch (con.vertical_align.align) {
+ case ui_constraint_vertical_align_top:
+ new_y += size + get_margin(t);
+ break;
+ case ui_constraint_vertical_align_center:
+ new_y += ((psize.y) / 2) - (tsize.y / 2);
+ break;
+ case ui_constraint_vertical_align_bottom:
+ new_y += psize.y - size - tsize.y - get_margin(t);
+ break;
+ }
+ uitree_set_pos(t, (v2_i32){.x = 0, .y = new_y});
+ }
+ break;
+
+ case ui_constraint_left:
+ uitree_set_pos(t, (v2_i32){
+ .x = ppos.x + ui_size_to_pixel_ex(con.left.pos, false, psize),
+ .y=0
+ });
+ break;
+
+ case ui_constraint_top:
+ uitree_set_pos(t, (v2_i32){
+ .y = ppos.y + ui_size_to_pixel_ex(con.top.pos, true, psize),
+ .x=0
+ });
+ break;
+
+ case ui_constraint_right:
+ if (tsize.x > 0)
+ uitree_set_pos(t, (v2_i32){
+ .x = ppos.x + psize.x - ui_size_to_pixel_ex(con.right.pos, false, psize) - tsize.x,
+ .y=0
+ });
+ break;
+
+ case ui_constraint_bottom:
+ if (tsize.y > 0)
+ uitree_set_pos(t, (v2_i32){
+ .y = ppos.y + psize.y - ui_size_to_pixel_ex(con.bottom.pos, true, psize) - tsize.y,
+ .x = 0
+ });
+ break;
+
+ default:
+ ERROR("Unknown constraint: %d", constraints->value.type);
+ exit(EXIT_FAILURE);
+ break;
+ }
+
+ constraints = constraints->next;
+ }
+}
+
+void ui_rearrange_container(UITree_container *c, v2_i32 ppos, v2_i32 psize) {
+ struct List_ui_constraint* constraints = c->constraints;
+ struct List_ui_constraint* first_constraint = constraints;
+ bool fitwidth = !uitree_has_constraint((UITree*)c, ui_constraint_width);
+ bool fitheight = !uitree_has_constraint((UITree*)c, ui_constraint_height);
+
+ if (fitwidth) c->w = 0;
+ if (fitheight) c->h = 0;
+
+ ui_constraints_apply((UITree*)c, ppos, psize);
+ /* Apply constraints to children here? */
+ {
+ const v2_i32 pos = v2_i32_sub_i((v2_i32){.x=c->x, .y=c->y}, 0);//c->margin);
+ const v2_i32 size = v2_i32_sub_i((v2_i32){.x=c->w, .y=c->h}, (2+MAX(c->children_len-1, 0)) * c->padding);
+ for (usize cc = 0; cc < c->children_len; cc++) {
+ ui_rearrange(c->children[cc], pos, size);
+ }
+ }
+
+ /* TODO: We want to update children regardless of whether we are doing a fit
+ * on the size of children. Switch the conditions (fitwidth||fitheight) and
+ * (children_len > 0); the size-fit doesn't matter if there's no children,
+ * the childrens size only matters if we do a size-fit. */
+
+ /* If we do not have a size > -1 we need to adapt the size to all children
+ * and add a size constraint on the largest of one of them */
+ if (fitwidth || fitheight) {
+
+ i32 maxwidth = 2 * c->padding;
+ i32 maxheight = maxwidth;
+
+ if (c->children_len > 0) {
+ if (c->children == NULL) {
+ ERROR("Container has children_len(%d) > 0 but children pointer is NULL!", c->children_len);
+ exit(EXIT_FAILURE);
+ }
+
+ /* Calculate new posisitions and save the largest one */
+ v2_i32 startpos = (v2_i32){.x=c->x + c->margin, .y=c->y + c->margin};
+ v2_i32 startsize = (v2_i32){.x=c->w - (2 * c->padding), .y=c->h - (2 * c->padding)};
+
+ for (usize cc = 0; cc < c->children_len; cc++) {
+ const v2_i32 childsize = elem_size(c->children[cc]);
+ i32 margin = (cc + 1 == c->children_len) ? 0 : MAX(c->padding, get_margin(c->children[cc]));
+
+ /* We just sum up in the direction we are going */
+ if (c->direction == DIRECTION_HORIZONTAL) {
+ maxwidth += childsize.x + margin;
+ maxheight = MAX(childsize.y + 2 * c->padding, maxheight);
+
+ startpos.x += childsize.x + margin;
+ startsize.x -= childsize.x + margin;
+ } else {
+ maxwidth = MAX(childsize.x + 2 * c->padding, maxwidth);
+ maxheight += childsize.y + margin;
+
+ startpos.y += childsize.y + margin;
+ startsize.y -= childsize.y + margin;
+ }
+ }
+
+ struct List_ui_constraint* constraint_head = c->constraints;
+ struct List_ui_constraint constraint_width;
+ struct List_ui_constraint constraint_height;
+
+ if (fitwidth) {
+ /* Create new size constraint */
+ constraint_width = (struct List_ui_constraint){
+ .value = {
+ .type = ui_constraint_width,
+ .constraint.width = {
+ .size = ui_size_pixel_new(maxwidth),
+ },
+ },
+ .next = constraint_head,
+ };
+
+ /* Prepend it to the list of constraints */
+ c->constraints = &constraint_width;
+ constraint_head = c->constraints;
+ }
+ if (fitheight) {
+ constraint_height = (struct List_ui_constraint){
+ .value = {
+ .type = ui_constraint_height,
+ .constraint.height = {
+ .size = ui_size_pixel_new(maxheight),
+ },
+ },
+ .next = constraint_head,
+ };
+ /* Prepend it to the list of constraints */
+ c->constraints = &constraint_height;
+ //constraint_head = c->constraints;
+ /* TODO: Convert to scrollable container if the height is
+ * greater than some constant relative to the screen height */
+ }
+
+ /* Recurse with new constraints */
+ ui_rearrange_container(c, ppos, psize);
+
+ /* Replace the original set of constraints, as the new ones will be
+ * out of scope once we return from the current call frame */
+ c->constraints = first_constraint;
+
+ /* Update child elements */
+ /* This is 1:1 (almost) to the previous iteration of children */
+ startpos = (v2_i32){c->x + c->padding, c->y + c->padding};
+ startsize = (v2_i32){c->w - 2 * c->padding, c->h - 2 * c->padding};
+
+ for (usize cc = 0; cc < c->children_len; cc++) {
+ v2_i32 childsize = elem_size(c->children[cc]);
+ i32 margin = MAX(c->padding, get_margin(c->children[cc]));
+
+ v2_i32 startsize_2;
+ if (c->direction == DIRECTION_HORIZONTAL) {
+ startsize_2 = (v2_i32){childsize.x, startsize.y};
+ } else {
+ startsize_2 = (v2_i32){startsize.x, childsize.y};
+ }
+ ui_rearrange(c->children[cc], startpos, startsize_2);
+
+ if (c->direction == DIRECTION_HORIZONTAL) {
+ maxwidth += childsize.x + margin;
+ maxheight = MAX(childsize.y, maxheight);
+
+ startpos.x += childsize.x + margin;
+ startsize.x -= childsize.x + margin;
+
+ } else {
+ maxwidth = MAX(childsize.x + 2 * c->padding, maxwidth);
+ maxheight += childsize.y + margin;
+
+ startpos.y += childsize.y + margin;
+ startsize.y -= childsize.y + margin;
+ }
+ }
+ }
+ }
+}
+
+void ui_rearrange_button(UITree_button *t, v2_i32 ppos, v2_i32 psize) {
+ t->x = ppos.x;
+ t->y = ppos.y;
+
+ ui_constraints_apply((UITree*)t, ppos, psize);
+}
+
+void ui_rearrange_title(UITree_title *t, v2_i32 ppos, v2_i32 psize) {
+ /*Defaults to just sit in the top-left corner of the parent*/
+ t->x = ppos.x;
+ t->y = ppos.y;
+
+ ui_constraints_apply((UITree*)t, ppos, psize);
+}
+
+void ui_rearrange_text(UITree_text *t, v2_i32 ppos, v2_i32 psize) {
+ t->x = ppos.x;
+ t->y = ppos.y;
+ t->w = psize.x;
+ t->h = psize.y;
+
+ t->texture_size.x = psize.x;
+ t->texture_size.y = psize.y;
+
+ /* Free the old texture */
+ /* Re-render the text */
+ t->text_texture_index =
+ engine_render_text(t->font_id, t->fg, (char*)t->text_original, &t->texture_size, true);
+
+ t->w = t->texture_size.x;
+ t->h = t->texture_size.y;
+
+ ui_constraints_apply((UITree*)t, ppos, psize);
+}
+
+
+void ui_rearrange(UITree *root, v2_i32 ppos, v2_i32 psize) {
+ if (root == NULL) {
+ WARN("%s received a null pointer, ignoring", __func__);
+ return;
+ }
+
+ switch (root->type) {
+ case uitype_container:
+ ui_rearrange_container(&root->container, ppos, psize);
+ break;
+ case uitype_button:
+ ui_rearrange_button(&root->button, ppos, psize);
+ break;
+ case uitype_title:
+ ui_rearrange_title(&root->title, ppos, psize);
+ break;
+ case uitype_text:
+ ui_rearrange_text(&root->text, ppos, psize);
+ break;
+ default:
+ if (root->type >= uitype_MAX) {
+ ERROR("Unknown uitype: %d", root->type);
+ exit(EXIT_FAILURE);
+ } else {
+ WARN("Rendering not implemented for %s", uitype_str[root->type]);
+ }
+ break;
+ }
+}
+
+void ui_resolve_constraints(void) {
+ u64 *i = NULL;
+ struct btree_iter_t *it = NULL;
+
+ /*absolutely bonkers way to do this*/
+ if (GLOBAL_UIROOTS == NULL) {
+ WARN("UIROOTS not initialized");
+ }
+
+ it = btree_iter_t_new(GLOBAL_UIROOTS);
+
+ while ((i = btree_iter(GLOBAL_UIROOTS, it)) != NULL) {
+ ui_rearrange((UITree*)*i, (v2_i32){0,0}, GLOBAL_PLATFORM->window->windowsize);
+ }
+ free(it);
+}
+
+void ui_container_attach(UITree *root, UITree *child) {
+ if (root == NULL) {
+ WARN("%s received a null pointer", __func__);
+ return;
+ }
+ if (root->type != uitype_container) {
+ WARN("Trying to attach a child to a non-container UI element");
+ return;
+ }
+
+ UITree_container *c = &(root->container);
+
+ static const usize size_increment = 32;
+ static const usize uitree_sz = sizeof(UITree*);
+
+ if (c->children == NULL) {
+ /* Allocate space for children */
+ c->children = malloc(uitree_sz * size_increment);
+
+ } else if ((c->children_len + 1) * uitree_sz >= c->children_size) {
+ /* If there's not enough room for more children we will need to
+ * reallocate some more memory */
+ c->children = realloc(c->children, (c->children_len + size_increment) * uitree_sz);
+ c->children_size += size_increment * uitree_sz;
+ }
+
+ /* Finally: attach the new element */
+ c->children[c->children_len++] = child;
+
+ /* Since the child is no longer a part of the root-ui we can delete it from
+ * the btree-mapping */
+ {
+ u64 tmp = (u64)child;
+ if (!btree_delete(GLOBAL_UIROOTS, &tmp)) {
+ WARN("Could not find child %p in global lookup table", child);
+ btree_print(GLOBAL_UIROOTS, &pointer_print);
+ }
+ }
+}
+
+void ui_button_enable(UITree_button *b) {
+ b->enabled = true;
+ SDL_Texture *t = ((struct Resources*)GLOBAL_PLATFORM->data)->textures[b->text_texture_index]->texture;
+ SDL_SetTextureColorMod(t, 0xff, 0xff, 0xff);
+}
+
+void ui_button_disable(UITree_button *b) {
+ b->enabled = false;
+
+ SDL_Texture *t = ((struct Resources*)GLOBAL_PLATFORM->data)->textures[b->text_texture_index]->texture;
+ SDL_SetTextureColorMod(t, 0x7f, 0x7f, 0x7f);
+}
+
+void ui_set_default_colors(Engine_color fg, Engine_color bg) {
+ DEFAULT_FG = fg;
+ DEFAULT_BG = bg;
+}
+
+void ui_set_default_padding(i32 padding) { DEFAULT_PADDING = padding; }
+
+Engine_color ui_get_default_fg(void) { return DEFAULT_FG; }
+Engine_color ui_get_default_bg(void) { return DEFAULT_BG; }
+
+bool check_point_in_rect(const v2_i32 *p, const SDL_Rect *r) {
+ if (p->x > r->x && p->x < r->x + r->w
+ && p->y > r->y && p->y < r->y + r->h) return true;
+ return false;
+}
+
+u64 ui_check_click_internal(UITree *root, v2_i32 pos) {
+ v2_i32 ps = get_pos(root);
+ v2_i32 sz = get_size(root);
+ SDL_Rect elem_rect = {.x=ps.x, .y=ps.y
+ ,.w=sz.x, .h=sz.y};
+ /* */
+ switch (root->type) {
+ case uitype_container:
+ if (root->container.children != NULL && root->container.children_len > 0) {
+ const UITree_container *container = &root->container;
+
+ if (check_point_in_rect(&pos, &elem_rect)) {
+
+ for (usize i = 0; i < container->children_len; i++) {
+ u64 res = ui_check_click_internal(container->children[i], pos);
+ if (res != (u64)ELEM_NOT_FOUND) {
+ return res;
+ }
+ }
+ }
+ }
+ break;
+ case uitype_button:
+ if (root->button.enabled) {
+ WARN("Checking button");
+ elem_rect.h += root->button.padding * 2;
+ elem_rect.w += root->button.padding * 2;
+ WARN("Button specs: <%d,%d>+%d,%d", elem_rect.w, elem_rect.h, elem_rect.x, elem_rect.y);
+ if (check_point_in_rect(&pos, &elem_rect)) {
+ return root->button.id;
+ }
+ }
+ break;
+ case uitype_title:
+ case uitype_text:
+ default:
+ break;
+ }
+ return ELEM_NOT_FOUND;
+}
+
+u64 ui_check_click(UITree *root) {
+ if (!GLOBAL_PLATFORM->mouse_lclick) return NO_CLICK;
+
+ v2_i32 pos = GLOBAL_PLATFORM->mouse_pos;
+
+ INFO("Mousepos: <%d,%d>", pos.x, pos.y);
+
+ return ui_check_click_internal(root, pos);
+}
diff --git a/src/ui_rendering.c b/src/ui_rendering.c
new file mode 100644
index 0000000..7024e0b
--- /dev/null
+++ b/src/ui_rendering.c
@@ -0,0 +1,295 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_image.h>
+#include <SDL2/SDL_ttf.h>
+
+#define ENGINE_INTERNALS
+
+#include <engine/engine.h>
+#include <engine/rendering.h>
+
+
+extern Platform *GLOBAL_PLATFORM;
+
+extern const char *uitype_str[];
+
+void render_uitree(Window *w, UITree *t) {
+ switch (t->type) {
+ case uitype_container:
+ render_container(w, &t->container);
+ break;
+ case uitype_button:
+ render_button(w, &t->button);
+ break;
+ case uitype_title:
+ render_title(w, &t->title);
+ break;
+ case uitype_text:
+ render_text(w, &t->text);
+ break;
+ default:
+ if (t->type >= uitype_MAX) {
+ ERROR("Unknown uitype: %d", t->type);
+ } else {
+ WARN("Rendering not implemented for %s", uitype_str[t->type]);
+ }
+ break;
+ }
+}
+
+void render_container(Window *w, UITree_container *t) {
+ SDL_Rect r = {
+ .x = t->x,
+ .y = t->y,
+ .w = t->w,
+ .h = t->h
+ };
+
+ SDL_SetRenderDrawColor(w->renderer,
+ t->bg.r,
+ t->bg.g,
+ t->bg.b,
+ t->bg.a);
+
+ SDL_RenderFillRect(w->renderer, &r);
+
+ SDL_SetRenderDrawColor(w->renderer,
+ t->fg.r,
+ t->fg.g,
+ t->fg.b,
+ t->fg.a);
+
+ SDL_RenderDrawRect(w->renderer, &r);
+
+#ifdef DEBUG
+ r.x += t->padding;
+ r.y += t->padding;
+ r.w -= t->padding * 2;
+ r.h -= t->padding * 2;
+ SDL_SetRenderDrawColor(w->renderer,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0x7A);
+ SDL_RenderDrawRect(w->renderer, &r);
+#endif
+
+ if (t->children != NULL && t->children_len > 0) {
+ for (usize i = 0; i < t->children_len; i++) {
+ render_uitree(w, t->children[i]);
+ }
+ }
+}
+
+void render_button(Window *w, UITree_button *t) {
+
+ SDL_Rect inner_r = {
+ .x = t->x + t->padding + (( t->w - t->texture_size.x) / 2),
+ .y = t->y + t->padding + (( t->h - t->texture_size.y) / 2),
+ .w = t->texture_size.x,
+ .h = t->texture_size.y,
+ };
+
+ SDL_Rect outer_r = {
+ .x = t->x,
+ .y = t->y,
+ .w = t->w + t->padding * 2,
+ .h = t->h + t->padding * 2,
+ };
+
+ SDL_Color fg = {
+ t->fg.r,
+ t->fg.g,
+ t->fg.b,
+ t->fg.a,
+ };
+
+ if (t->enabled == false) {
+ fg.r /= 2;
+ fg.g /= 2;
+ fg.b /= 2;
+ }
+
+ SDL_SetRenderDrawColor(w->renderer,
+ t->bg.r,
+ t->bg.g,
+ t->bg.b,
+ t->bg.a);
+
+ SDL_RenderFillRect(w->renderer, &outer_r);
+
+ SDL_SetRenderDrawColor(w->renderer,
+ fg.r,
+ fg.g,
+ fg.b,
+ fg.a);
+
+ SDL_RenderDrawRect(w->renderer, &outer_r);
+ SDL_Texture *texture = ((struct Resources*)GLOBAL_PLATFORM->data)
+ ->textures[t->text_texture_index]->texture;
+ SDL_RenderCopy(
+ w->renderer,
+ texture,
+ NULL,
+ &inner_r);
+
+}
+
+
+void render_title(Window *w, UITree_title *t) {
+ SDL_Rect r = {
+ .x = t->x,
+ .y = t->y,
+ .w = t->texture_size.x,
+ .h = t->texture_size.y,
+ };
+
+ SDL_SetRenderDrawColor(w->renderer,
+ t->fg.r,
+ t->fg.g,
+ t->fg.b,
+ t->fg.a);
+
+ SDL_Texture *texture = ((struct Resources*)GLOBAL_PLATFORM->data)
+ ->textures[t->text_texture_index]->texture;
+
+ SDL_RenderCopy(
+ w->renderer,
+ texture,
+ NULL,
+ &r);
+
+#ifdef DEBUG
+ SDL_SetRenderDrawColor(w->renderer,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0x7A);
+
+ SDL_RenderDrawRect(w->renderer, &r);
+#endif
+}
+
+void render_text(Window *w, UITree_text *t) {
+ SDL_Rect r = {
+ .x = t->x,
+ .y = t->y,
+ .w = t->texture_size.x,
+ .h = t->texture_size.y,
+ };
+
+ SDL_SetRenderDrawColor(w->renderer,
+ t->fg.r,
+ t->fg.g,
+ t->fg.b,
+ t->fg.a);
+
+ SDL_Texture *texture = ((struct Resources*)GLOBAL_PLATFORM->data)
+ ->textures[t->text_texture_index]->texture;
+
+ SDL_RenderCopy(
+ w->renderer,
+ texture,
+ NULL,
+ &r);
+
+#ifdef DEBUG
+ SDL_SetRenderDrawColor(w->renderer,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0x7A);
+
+ SDL_RenderDrawRect(w->renderer, &r);
+#endif
+
+}
+
+i64 add_texture(struct Resources *resptr, Texture *t) {
+ if (NULL == resptr
+ || NULL == t) return -1;
+
+ if (resptr->textures == NULL) {
+ resptr->textures = malloc(sizeof(Texture*) * TEXTURES_INCREMENT);
+ } else if (resptr->textures_len + 1 >= resptr->textures_size) {
+ resptr->textures_size += TEXTURES_INCREMENT;
+ resptr->textures = realloc(resptr->textures, sizeof(Texture*) * resptr->textures_size);
+ memset(resptr->textures + resptr->textures_size - TEXTURES_INCREMENT, 0, TEXTURES_INCREMENT);
+ }
+
+ resptr->textures[resptr->textures_len] = t;
+ return resptr->textures_len++;
+}
+
+i64 engine_render_text(i32 font_id, Engine_color fg, char *text, v2_i32 *size_out, bool wrapped) {
+ const SDL_Color sdl_fg = {.r = fg.r, .g = fg.g, .b = fg.b, .a = fg.a};
+ Texture *t = malloc(sizeof(Texture));
+ struct Resources *r;
+
+ if (t == NULL) {
+ ERROR("Failed to allocate memory for texture!\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (GLOBAL_PLATFORM == NULL) {
+ ERROR("Platform is uninitialized.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ r = (struct Resources*)GLOBAL_PLATFORM->data;
+
+ if (r == NULL) {
+ ERROR("Resources not loaded!\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (r->fonts == NULL) {
+ ERROR("No fonts are initialized!\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if ((usize)font_id >= r->fonts_len) {
+ ERROR("font-id \"%d\" is out of bounds!\n", font_id);
+ ERROR("Couldn't render text \"%s\"\n", text);
+ }
+
+ SDL_Surface *s = NULL;
+
+ if (wrapped) {
+ s = TTF_RenderUTF8_Solid_Wrapped(r->fonts[font_id], text, sdl_fg, size_out->x);
+
+ if (s == NULL) {
+ ERROR("Failed call to TTF_RenderUTF8_Solid_Wrapped: %s\n", TTF_GetError());
+ exit(EXIT_FAILURE);
+ }
+ } else {
+ s = TTF_RenderUTF8_Solid(r->fonts[font_id], text, sdl_fg);
+
+ if (s == NULL) {
+ ERROR("Failed call to TTF_RenderUTF8_Solid: %s\n", TTF_GetError());
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ t->texture = SDL_CreateTextureFromSurface(GLOBAL_PLATFORM->window->renderer, s);
+
+ if (t->texture == NULL) {
+ ERROR("Failed call to SDL_CreateTextureFromSurface: %s\n", SDL_GetError());
+ INFO("FontID: %d", font_id);
+ INFO("Text: \"%s\" [%lu]", text, strlen(text));
+ exit(EXIT_FAILURE);
+ }
+
+ *(i32*)&t->width = s->w;
+ *(i32*)&t->height = s->h;
+
+ size_out->x = s->w;
+ size_out->y = s->h;
+
+ SDL_FreeSurface(s);
+
+ return add_texture(r, t);
+}
diff --git a/src/utils.c b/src/utils.c
new file mode 100644
index 0000000..748b82f
--- /dev/null
+++ b/src/utils.c
@@ -0,0 +1,85 @@
+#include <stdlib.h>
+#include <stdint.h>
+
+#include <string.h>
+
+#include <engine/utils.h>
+#include <engine/logging.h>
+
+f32 lerp(f32 dt, f32 a, f32 b) {
+ return (a * (1.0f - dt)) + (b * dt);
+}
+
+i32 int_lerp(f32 dt, i32 a, i32 b) {
+ return ((f32)a * (1.0f - dt)) + ((f32)b * dt);
+}
+
+u32 hash(char *str) {
+ u32 sum = 0;
+ while (*str != '\0') {
+ sum ^= (*str) * 0xdeece66d + 0xb;
+ str++;
+ }
+ return sum;
+}
+
+/* Populates dstmap
+ * on success: return pointer to dstmap
+ * on failure: return NULL */
+i32* kernmap(const void *map, i32 *dstmap, const v2_i32 mapsize, bool (*predicate)(const void*)) {
+ const i32 w = mapsize.x;
+ const i32 h = mapsize.y;
+ i32 mask[w*h];
+
+ if (w * h < 1) return NULL;
+
+ for (i32 i = 0; i < w * h; i++) {
+ mask[i] = predicate((void*)((u64)map + sizeof(i32) * i)) ? 1 : 0;
+ }
+
+ for (i32 y = 1; y < h - 1; y++) {
+ for (i32 x = 1; x < w - 1; x++) {
+ const i32 global_idx = (y * w) + x;
+ const i32 offs = global_idx - w - 1;
+ i32 _sum = 0;
+
+ i32 shift = 0;
+
+ /* We go in the following order */
+ /* ....|0|1|2|....*/
+ /* ....|3|4|5|....*/
+ /* ....|6|7|8|....*/
+ /* Where `4` is in the center, MASK_C */
+ for (i32 yy = offs; yy <= offs + w + w; yy += w) {
+ for (i32 xx = yy; xx < yy + 3; xx++) {
+ _sum = _sum | (mask[xx] << shift++);
+ }
+ }
+
+ dstmap[global_idx] = _sum;
+ }
+ }
+ return dstmap;
+}
+
+/* Returns an index from the given weights. */
+i32 pick_from_sample(const i32 *weights, i32 len) {
+ if (len <= 0) return 0;
+
+ /* Cumulative sum */
+ i32 cumweights[len];
+ i32 sum = 0;
+ for (i32 i = 0; i < len; i++) {
+ sum += weights[i];
+ cumweights[i] = sum;
+ }
+
+ if (sum == 0) return 0;
+
+ i32 pick = rand() % sum;
+
+ for (i32 i = 0; i < len; i++) {
+ if (pick < cumweights[i]) return i;
+ }
+ return -1;
+}
diff --git a/src/vector.c b/src/vector.c
new file mode 100644
index 0000000..1a4cc61
--- /dev/null
+++ b/src/vector.c
@@ -0,0 +1,28 @@
+#include <engine/vector.h>
+#include <engine/utils.h>
+
+bool v2_i32_eq (const v2_i32 a,
+ const v2_i32 b) { return (a.x == b.x) && (a.y == b.y); }
+v2_i32 v2_i32_add (v2_i32 a, v2_i32 b) { return (v2_i32){a.x + b.x, a.y + b.y}; }
+v2_i32 v2_i32_add_i(v2_i32 a, i32 b) { return (v2_i32){a.x + b , a.y + b }; }
+v2_i32 v2_i32_sub (v2_i32 a, v2_i32 b) { return (v2_i32){a.x - b.x, a.y - b.y}; }
+v2_i32 v2_i32_sub_i(v2_i32 a, i32 b) { return (v2_i32){a.x - b , a.y - b }; }
+v2_i32 v2_i32_div (v2_i32 a, v2_i32 b) { return (v2_i32){a.x / b.x, a.y / b.y}; }
+v2_i32 v2_i32_div_i(v2_i32 a, i32 b) { return (v2_i32){a.x / b , a.y / b }; }
+v2_i32 v2_i32_mul (v2_i32 a, v2_i32 b) { return (v2_i32){a.x * b.x, a.y * b.y}; }
+v2_i32 v2_i32_mul_i(v2_i32 a, i32 b) { return (v2_i32){a.x * b , a.y * b }; }
+v2_i32 v2_i32_mod (v2_i32 a, v2_i32 b) { return (v2_i32){a.x % b.x, a.y % b.y}; }
+v2_i32 v2_i32_mod_i(v2_i32 a, i32 b) { return (v2_i32){a.x % b , a.y % b }; }
+v2_i32 v2_i32_max (v2_i32 a, v2_i32 b) { return (v2_i32){MAX(a.x, b.x), MAX(a.y, b.y)}; }
+v2_i32 v2_i32_min (v2_i32 a, v2_i32 b) { return (v2_i32){MIN(a.x, b.x), MIN(a.y, b.y)}; }
+v2_i32 v2_i32_lerp (f32 dt, v2_i32 a, v2_i32 b) {
+ return (v2_i32){
+ .x = lerp(dt, (f32)a.x, (f32)b.x),
+ .y = lerp(dt, (f32)a.y, (f32)b.y),
+ };
+}
+
+
+void v2_i32_fprintf(FILE *stream, v2_i32 a) {
+ fprintf(stream, "<%d,%d>", a.x, a.y);
+}
diff --git a/state_type_list.h.in b/state_type_list.h.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/state_type_list.h.in
diff --git a/tools/scripts/lint b/tools/scripts/lint
new file mode 100755
index 0000000..0c0c352
--- /dev/null
+++ b/tools/scripts/lint
@@ -0,0 +1,28 @@
+#!/usr/bin/env sh
+
+SRCDIR="${SRCDIR:-src}"
+
+echo "Running bear..."
+if which bear >/dev/null ; then
+ make clean
+ bear -- make 2>&1>/dev/null
+else
+ echo -e "\e[31mnot found. Without compile_commands.json results might be bad or outright wrong.\e[0m"
+fi
+
+echo "Running cppcheck..."
+if which cppcheck >/dev/null ; then
+ echo ""
+ if [ -e 'compile_commands.json' ] ; then
+ cppcheck --language=c --std=c99 --enable=all \
+ --suppress={missingIncludeSystem,unusedFunction} \
+ --project=compile_commands.json \
+ -q "${SRCDIR}"
+ else
+ cppcheck --language=c --std=c99 --enable=all \
+ --suppress={missingIncludeSystem,unusedFunction} \
+ -q "${SRCDIR}"
+ fi
+else
+ echo -e "\e[31mnot found\e[0m"
+fi
diff --git a/tools/scripts/scaling.py b/tools/scripts/scaling.py
new file mode 100644
index 0000000..a60da46
--- /dev/null
+++ b/tools/scripts/scaling.py
@@ -0,0 +1,27 @@
+import numpy as np
+import matplotlib.pyplot as plt
+
+fig = plt.figure()
+
+def f(x):
+ # experience to level
+ #return (np.log(x/4+1)**2) / 4 + 1
+ # Difficulty scaling
+ return 1 + (2**(np.log(1 + x)) + np.floor(x / 5) / 2) * 100
+ # Light level
+ #return np.log(1+x) / np.log(2) * (4*log(2)/log(3))
+
+x = np.arange(25)
+y = [f(i)/15 for i in x]
+
+
+cutoff = len([v for v in y if v >= 0])
+#
+#
+plt.grid(visible=True, which='major', axis='both')
+plt.plot(x[:cutoff], y[:cutoff])
+
+plt.show()
+
+for v in zip(x,y):
+ print('{}:{:.2f}'.format(v[0], v[1]))