started event system; added font_get_text_width
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Sun, 7 Nov 2021 22:08:40 +0000 (16:08 -0600)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Sun, 7 Nov 2021 22:08:40 +0000 (16:08 -0600)
12 files changed:
docs/todo.md
include/gfx.h
include/heartbreak.h
misc/onyx/heartbreak.onyx
misc/onyx/heartbreak_graphics.onyx
misc/onyx/qhb.onyx
src/gfx.c
src/heartbreak.c
src/heartbreak_graphics.c
src/heartbreak_system.c
src/heartbreak_window.c
tests/simp.onyx

index 7a7d6fc1cdd1693268fa5b3c331ad6f2c59ef904..511ea5e12264df5b9c7ffbeae60b140d2e65d753 100644 (file)
@@ -5,7 +5,7 @@ This is the short high-level list of things that need to be done:
 
 
 Window controls:
-[ ] window_set_property(window, prop, value)
+[ ] window_set_property(prop, value)
     prop is:
         .Fullscreen
         .Resizable
@@ -13,9 +13,35 @@ Window controls:
 
     value can be whatever each property needs
 
-[ ] window_get_property(window, prop) -> value
+[ ] window_get_property(prop) -> value
+
+[ ] get desktop dimensions
+[ ] get display count
+[ ] get display names
+[ ] get display orientations
+[ ] get title
+[ ] has focus
+[ ] is maximized
+[ ] is minimized
+[ ] request attention
+[ ] restore from minimized
     
 
 Event system:
 There is a little bit to consider here. Are event handlers going to be registered like in LOVE?
-Do you define them in your main program?
\ No newline at end of file
+Do you define them in your main program?
+
+The problem currently with registing event handlers via something like,
+    register_event_handle :: (kind: EventKind, handler: (x: i32) -> void)
+is that in Wasmer it is impossible to call the handler because of two missing functions:
+    wasm_table_get(...)
+    wasm_ref_as_func(...)
+Once these functions are available in the shared library, then it would be possible to call the
+WASM function dynamically from the GLFW event handler.
+
+In the meantime, it would be possible (and rather easy) to look for special exports in the binary
+like __heartbreak_keydown which would then get registered as event handlers. In Onyx, this won't
+create too much of a mess because in qhb.onyx there can simply be a #if #defined(keydown) to test
+if a keydown handler was defined, then it would export that function as __heartbreak_keydown for
+example. This will won't in the meantime while Wasmer gets their shit together and actually provides
+the API that they claim to.
index 5fe5c190dd99e1a1b41df38125fb358ad86a7994..712880fd6f3905dca0364e6bdad625870e3870f0 100644 (file)
@@ -110,4 +110,5 @@ void   gfx_image_bind(ImmediateRenderer *ir, Image *img, u32 texture_unit);
 void   gfx_image_unbind(ImmediateRenderer *ir);
 
 Font* gfx_font_load(ImmediateRenderer *ir, char* font_path, i32 font_size);
+f32   gfx_font_get_text_width(ImmediateRenderer *ir, char* msgptr, i32 msglen);
 #endif
index cb76cf271e4aff8604588194dffeb698d503a82a..6ab0d32929109bd98278ca95a86184e452dadb41 100644 (file)
 
 //
 // WebAssembly State
-extern wasm_memory_t* wasm_memory;
-extern wasm_table_t*  wasm_func_table;
+extern wasm_memory_t*   wasm_memory;
+extern wasm_table_t*    wasm_func_table;
+extern wasm_module_t*   wasm_module;
+extern wasm_instance_t* wasm_instance;
 
 //
 // Global OpenGL / GLFW things
index 8d327052fe4b091d77be92897042b1acdb31a530..36b70f999e20849457ff63d80389535f578c4cdd 100644 (file)
@@ -8,7 +8,7 @@ package heartbreak
 use package core {println}
 
 HeartbreakFuncs :: struct {
-    init   : () -> void;
+    load   : () -> void;
     update : (dt: f32) -> void;
     draw   : () -> void;
 }
@@ -19,7 +19,7 @@ run :: (use funcs: HeartbreakFuncs) {
         return;
     }
 
-    init();
+    load();
 
     while system.end_frame() {
         update(0);
@@ -27,4 +27,4 @@ run :: (use funcs: HeartbreakFuncs) {
     }
 
     system.destroy();
-}
\ No newline at end of file
+}
index 85bf8e0092696a3b700a05bc98fb8de2aff29b87..eef9531bf3d385a95a5010875b7112e7121a21ee 100644 (file)
@@ -70,3 +70,6 @@ ImageProperty :: enum {
 
 getImageProperty :: (img: Image, prop: ImageProperty) -> i32 #foreign "heartbreak" "graphics_image_property" ---
 getImageDimensions :: (img: Image) => getImageProperty(img, .Width), getImageProperty(img, .Height);
+
+
+getTextWidth :: (text: str) -> f32 #foreign "heartbreak" "graphics_get_text_width" ---
index 10dd8aea5986427f3ff1385707f2c46f0cb88ea5..acd26a965ce798234f066b5cacf18347f8256a1a 100644 (file)
@@ -8,16 +8,23 @@ package main
 
 #private hb :: package heartbreak
 
-#if !#defined((package main).init) {
-    init :: () {}
+#private_file symbol_source :: package main
+
+#if !#defined(symbol_source.load) {
+    load :: () {}
 }
 
-#if !#defined((package main).update) {
+#if !#defined(symbol_source.update) {
     update :: (dt: f32) {}
 }
 
-#if !#defined((package main).draw) {
+#if !#defined(symbol_source.draw) {
     draw :: () {}
 }
 
-main :: (_) => hb.run(.{ init, update, draw });
\ No newline at end of file
+// This is one place where top-level macros would be nice.
+#if #defined(symbol_source.keydown) { #export "__heartbreak_keydown" symbol_source.keydown }
+#if #defined(symbol_source.keyup)   { #export "__heartbreak_keyup"   symbol_source.keyup   }
+#if #defined(symbol_source.resize)  { #export "__heartbreak_resize"  symbol_source.resize  }
+
+main :: (_) => hb.run(.{ symbol_source.load, symbol_source.update, symbol_source.draw });
index b27b7149ebff4dead27d81f1d4fa68f51491c3ce..3c0ee21fb454cfcbfc197498bcf95ef7cf3bf77d 100644 (file)
--- a/src/gfx.c
+++ b/src/gfx.c
@@ -563,3 +563,19 @@ Font* gfx_font_load(ImmediateRenderer *ir, char* font_path, i32 font_size) {
 
     return font;
 }
+
+f32 gfx_font_get_text_width(ImmediateRenderer *ir, char* msgptr, i32 msglen) {
+    stbtt_aligned_quad quad;
+
+    f32 x=0, y=0;
+    while (msglen--) {
+        stbtt_GetPackedQuad(ir->active_font->char_data,
+                            FONT_INTERNAL_IMAGE_SIZE, FONT_INTERNAL_IMAGE_SIZE,
+                            *msgptr - ir->active_font->first_character,
+                            &x, &y, &quad, 0);
+
+        msgptr++;
+    }
+
+    return x;
+}
index 50be7c0b88f0fba52224af1a501eaa1873a8c0b6..11ea7f68084a89f9ba6fb861b8b87c2db40eb53e 100644 (file)
 
 GLFWwindow* glfw_window = NULL;
 ImmediateRenderer renderer;
-wasm_memory_t* wasm_memory;
-wasm_table_t*  wasm_func_table;
+wasm_module_t*   wasm_module;
+wasm_instance_t* wasm_instance;
+wasm_memory_t*   wasm_memory;
+wasm_table_t*    wasm_func_table;
 
 void build_heartbreak_imports(WasmFuncDefinition** out_wfds, i32* out_count) {
     static WasmFuncDefinition* modules[] = {
@@ -68,8 +70,6 @@ void run_wasm_file(bh_buffer wasm_bytes) {
 
     wasm_engine_t*   engine = NULL;
     wasm_store_t*    store = NULL;
-    wasm_module_t*   module = NULL;
-    wasm_instance_t* instance = NULL;
 
     config = wasm_config_new();
     if (!config) goto error_handling;
@@ -95,11 +95,11 @@ void run_wasm_file(bh_buffer wasm_bytes) {
     wasm_data.size = wasm_bytes.length;
     wasm_data.data = wasm_bytes.data;
 
-    module = wasm_module_new(store, &wasm_data);
-    if (!module) goto error_handling;
+    wasm_module = wasm_module_new(store, &wasm_data);
+    if (!wasm_module) goto error_handling;
 
     wasmer_named_extern_vec_t wasi_imports;
-    wasi_get_unordered_imports(store, module, wasi_env, &wasi_imports);
+    wasi_get_unordered_imports(store, wasm_module, wasi_env, &wasi_imports);
 
     WasmFuncDefinition* defs;
     i32 defs_count;
@@ -109,7 +109,7 @@ void run_wasm_file(bh_buffer wasm_bytes) {
     wasm_name_new_from_string(&heartbreak_name, "heartbreak"); // @Free
 
     wasm_importtype_vec_t module_imports;    // @Free
-    wasm_module_imports(module, &module_imports);
+    wasm_module_imports(wasm_module, &module_imports);
 
     wasm_extern_vec_t imports;
     wasm_extern_vec_new_uninitialized(&imports, module_imports.size); // @Free
@@ -165,13 +165,13 @@ void run_wasm_file(bh_buffer wasm_bytes) {
 
     wasm_trap_t* traps = NULL;
 
-    instance = wasm_instance_new(store, module, &imports, &traps);
-    if (!instance) goto error_handling;
+    wasm_instance = wasm_instance_new(store, wasm_module, &imports, &traps);
+    if (!wasm_instance) goto error_handling;
 
-    wasm_extern_t* start_extern = wasm_extern_lookup_by_name(module, instance, HEARTBREAK_WASM_START_FUNCTION_NAME);
+    wasm_extern_t* start_extern = wasm_extern_lookup_by_name(wasm_module, wasm_instance, HEARTBREAK_WASM_START_FUNCTION_NAME);
     wasm_func_t*   start_func   = wasm_extern_as_func(start_extern);
 
-    wasm_extern_t* memory_extern = wasm_extern_lookup_by_name(module, instance, HEARTBREAK_WASM_MEMORY_NAME);
+    wasm_extern_t* memory_extern = wasm_extern_lookup_by_name(wasm_module, wasm_instance, HEARTBREAK_WASM_MEMORY_NAME);
     wasm_memory = wasm_extern_as_memory(memory_extern);
 
     // This is storing the function table exported by WASM module in order to call functions passed by reference to the host.
@@ -179,7 +179,7 @@ void run_wasm_file(bh_buffer wasm_bytes) {
     // There is no way to get a reference out of a table, nor turn a reference into a function object to call. This means
     // that even if I passed functions using reftypes, I still couldn't convert them to a wasm_func_t to call. Wasmer is
     // normally pretty good so I was disappointed to find this level of laziness in the implementation.
-    wasm_extern_t* func_table_extern = wasm_extern_lookup_by_name(module, instance, HEARTBREAK_WASM_FUNCTION_TABLE_NAME);
+    wasm_extern_t* func_table_extern = wasm_extern_lookup_by_name(wasm_module, wasm_instance, HEARTBREAK_WASM_FUNCTION_TABLE_NAME);
     wasm_func_table = wasm_extern_as_table(func_table_extern);
 
     wasm_val_vec_t args;
@@ -200,8 +200,8 @@ error_handling:
     bh_printf("An error occured trying to run the WASM module...\n");
 
 cleanup:
-    if (instance)    wasm_instance_delete(instance);
-    if (module)      wasm_module_delete(module);
+    if (wasm_instance) wasm_instance_delete(wasm_instance);
+    if (wasm_module) wasm_module_delete(wasm_module);
     if (store)       wasm_store_delete(store);
     if (engine)      wasm_engine_delete(engine);
     return;
index 00c4656d5c5ffa5799876092493092579955b01a..6d85b11e4881136212d00fc5d8a10c765811ec2c 100644 (file)
@@ -308,6 +308,15 @@ HEARTBREAK_DEF(image_property, (WASM_I64, WASM_I32), (WASM_I32)) {
     return NULL;
 }
 
+HEARTBREAK_DEF(get_text_width, (WASM_I32, WASM_I32), (WASM_F32)) {
+    char* str_data = wasm_memory_data(wasm_memory) + params->data[0].of.i32;
+    u32   str_size = params->data[1].of.i32;
+
+    f32 res = gfx_font_get_text_width(&renderer, str_data, str_size);
+    results->data[0] = WASM_F32_VAL(res);
+    return NULL;
+}
+
 HEARTBREAK_MODULE {
     HEARTBREAK_FUNC(set_clear_color)
     HEARTBREAK_FUNC(clear)
@@ -332,5 +341,7 @@ HEARTBREAK_MODULE {
     HEARTBREAK_FUNC(image_param)
     HEARTBREAK_FUNC(image_property)
 
+    HEARTBREAK_FUNC(get_text_width)
+
     { NULL }
 };
index 1906048f68dfc5b8380a074970980da25b989f7f..2c467f11d716996e716b268ba7ffc266c2f8249b 100644 (file)
@@ -3,10 +3,77 @@
 
 #define HEARTBREAK_MODULE_NAME system
 
+#define HEARTBREAK_EVENTS   \
+        HB_EVENT(keydown,   (WASM_I32, WASM_I32))  \
+        HB_EVENT(keyup,     (WASM_I32))            \
+        HB_EVENT(mousedown, ()) \
+        HB_EVENT(mouseup,   ())   \
+        HB_EVENT(mousemove, ()) \
+        HB_EVENT(resize,    (WASM_I32, WASM_I32))    \
+
+#define HB_EVENT(name, pt) static wasm_func_t *__heartbreak_event_##name;
+HEARTBREAK_EVENTS
+#undef  HB_EVENT
+
+typedef struct HeartbreakEventType {
+    const char*       name;
+    wasm_func_t**     func;
+    WasmValkindBuffer params;
+} HeartbreakEventType;
+
+#define HB_EVENT(name, pt) static HeartbreakEventType __heartbreak_event_type_##name = { #name, &__heartbreak_event_##name, _VALS pt };
+HEARTBREAK_EVENTS
+#undef  HB_EVENT
+
+#define HEARTBREAK_EVENT_CALL(name, ...) __heartbreak_event_call(&__heartbreak_event_type_##name, __VA_ARGS__);
+
+void __heartbreak_event_call(HeartbreakEventType *ev, ...) {
+    if (*ev->func == NULL) return;
+
+    va_list args;
+    va_start(args, ev);
+
+    wasm_val_vec_t wasm_args, wasm_results;
+    wasm_val_vec_new_uninitialized(&wasm_args, ev->params.count);
+
+    fori (i, 0, (i32) ev->params.count) {
+        switch (ev->params.types[i]) {
+            case WASM_I32: wasm_args.data[i] = WASM_I32_VAL(va_arg(args, i32)); break;
+            case WASM_I64: wasm_args.data[i] = WASM_I64_VAL(va_arg(args, i64)); break;
+            case WASM_F32: wasm_args.data[i] = WASM_F32_VAL(va_arg(args, f64)); break;
+            case WASM_F64: wasm_args.data[i] = WASM_F64_VAL(va_arg(args, f64)); break;
+            default: assert(0);
+        }
+    }
+
+    const wasm_trap_t* trap = wasm_func_call(*ev->func, &wasm_args, &wasm_results);
+    if (trap) {
+        wasm_message_t msg;
+        wasm_trap_message(trap, &msg);
+
+        bh_printf("Error calling heartbreak event handler '%s': %b\n", ev->name, msg.data, msg.size);
+    }
+
+    va_end(args);
+}
+
 static void __glfw_window_size_callback(GLFWwindow *w, i32 new_width, i32 new_height) {
     gfx_immediate_renderer_update_window_size(&renderer, (f32) new_width, (f32) new_height);
+    HEARTBREAK_EVENT_CALL(resize, new_width, new_height);
 }
 
+static void __glfw_window_key_callback(GLFWwindow *w, i32 key, int scancode, int action, int mods) {
+    switch (action) {
+        case GLFW_PRESS:   HEARTBREAK_EVENT_CALL(keydown, key, mods); break;
+        case GLFW_RELEASE: HEARTBREAK_EVENT_CALL(keyup,   key);       break;
+    }
+}
+
+typedef struct HeartbreakEventLink {
+    const char   *name;
+    wasm_func_t **func;
+} HeartbreakEventLink;
+
 HEARTBREAK_DEF(init, (), (WASM_I32)) {
     if (!glfwInit()) {
         bh_printf("Failed to initialize GLFW.\n");
@@ -25,6 +92,7 @@ HEARTBREAK_DEF(init, (), (WASM_I32)) {
     glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
     glfwMakeContextCurrent(glfw_window);
     glfwSetWindowSizeCallback(glfw_window, __glfw_window_size_callback);
+    glfwSetKeyCallback(glfw_window, __glfw_window_key_callback);
 
     glfwSwapInterval(1);
 
@@ -38,6 +106,21 @@ HEARTBREAK_DEF(init, (), (WASM_I32)) {
     glfwGetWindowSize(glfw_window, &width, &height);
     gfx_immediate_renderer_update_window_size(&renderer, (f32) width, (f32) height);
 
+    #define HB_EVENT(name, pt) { "__heartbreak_" #name, &__heartbreak_event_##name },
+    static HeartbreakEventLink events[] = {
+        HEARTBREAK_EVENTS
+        { NULL }
+    };
+    #undef  HB_EVENT
+
+    wasm_extern_t* tmp_extern;
+    HeartbreakEventLink* ev = events;
+    while (ev->name) {
+        tmp_extern = wasm_extern_lookup_by_name(wasm_module, wasm_instance, ev->name);
+        if (tmp_extern) *ev->func = wasm_extern_as_func(tmp_extern);
+        ev++;
+    }
+
     results->data[0] = WASM_I32_VAL(1);
     return NULL;
 }
index 30ba079fa9bf043149378b7a640a42b68cf087b1..385bf6d385217107587e5f1ee8a68ae7ec00f40d 100644 (file)
@@ -26,11 +26,22 @@ HEARTBREAK_DEF(set_should_close, (WASM_I32), ()) {
     return NULL;
 }
 
+HEARTBREAK_DEF(get_property, (WASM_I32), (WASM_I32)) {
+    return NULL;
+}
+
+HEARTBREAK_DEF(set_property, (WASM_I32, WASM_I32), ()) {
+    return NULL;
+}
+
 HEARTBREAK_MODULE {
     HEARTBREAK_FUNC(get_width)
     HEARTBREAK_FUNC(get_height)
     HEARTBREAK_FUNC(set_dimensions)
     HEARTBREAK_FUNC(set_should_close)
 
+    HEARTBREAK_FUNC(get_property)
+    HEARTBREAK_FUNC(set_property)
+
     { NULL }
-};
\ No newline at end of file
+};
index ddec24900a6eeabb203d55d0557cd711b1d7c0b1..6aca651039c75d71e46cb9cb8c9c46aa053627a7 100644 (file)
@@ -11,7 +11,7 @@ Square :: struct {
     r, g, b, a: f32;
 }
 
-init :: () {
+load :: () {
     w := hb.window.getWidth();
     h := hb.window.getHeight();
     printf("The window is {}x{}\n", w, h);
@@ -24,9 +24,23 @@ init :: () {
 
     array.init(^squares);
 
+    printf("Width is: {}\n", hb.graphics.getTextWidth("Hello, World!"));
+
     // hb.window.setDimensions(1200, 900);
 }
 
+keydown :: (key, mods: i32) {
+    printf("Key down! {} {}\n", key, mods);
+}
+
+keyup :: (key: i32) {
+    printf("Key up! {}\n", key);
+}
+
+resize :: (nw, nh: i32) {
+    printf("The window is now {} by {}.\n", nw, nh);
+}
+
 t: f32 = 0;
 update :: (dt: f32) {
     t += 0.016;