Window controls:
-[ ] window_set_property(window, prop, value)
+[ ] window_set_property(prop, value)
prop is:
.Fullscreen
.Resizable
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.
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
//
// 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
use package core {println}
HeartbreakFuncs :: struct {
- init : () -> void;
+ load : () -> void;
update : (dt: f32) -> void;
draw : () -> void;
}
return;
}
- init();
+ load();
while system.end_frame() {
update(0);
}
system.destroy();
-}
\ No newline at end of file
+}
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" ---
#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 });
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;
+}
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[] = {
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;
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;
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
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.
// 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;
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;
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)
HEARTBREAK_FUNC(image_param)
HEARTBREAK_FUNC(image_property)
+ HEARTBREAK_FUNC(get_text_width)
+
{ NULL }
};
#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");
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);
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;
}
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
+};
r, g, b, a: f32;
}
-init :: () {
+load :: () {
w := hb.window.getWidth();
h := hb.window.getHeight();
printf("The window is {}x{}\n", w, h);
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;