basic initial working version
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Mon, 1 Nov 2021 21:48:28 +0000 (16:48 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Mon, 1 Nov 2021 21:48:28 +0000 (16:48 -0500)
13 files changed:
.gitignore [new file with mode: 0644]
Makefile
docs/design.md [new file with mode: 0644]
include/heartbreak.h [new file with mode: 0644]
include/utils.h [new file with mode: 0644]
misc/onyx/heartbreak.onyx [new file with mode: 0644]
misc/onyx/heartbreak_graphics.onyx [new file with mode: 0644]
misc/onyx/heartbreak_system.onyx [new file with mode: 0644]
src/heartbreak.c
src/heartbreak_graphics.c [new file with mode: 0644]
src/heartbreak_system.c [new file with mode: 0644]
src/utils.c [new file with mode: 0644]
tests/simp.onyx

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..b45a852
--- /dev/null
@@ -0,0 +1,4 @@
+*.o
+*.wasm
+bin/heartbreak
+.vscode/
\ No newline at end of file
index 0bcbf713c15ce2ffb6df123f465aaec5b93d89da..bda92c9eed05322219d00f2f0d6cab8dd3adf40e 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,15 +1,20 @@
 CC=gcc
 WARNINGS=-Wimplicit -Wmisleading-indentation -Wparentheses -Wsequence-point -Wreturn-type -Wshift-negative-value -Wunused-but-set-parameter -Wunused-but-set-variable -Wunused-function -Wunused-label -Wmaybe-uninitialized -Wsign-compare -Wstrict-overflow -Wduplicated-branches -Wduplicated-cond -Wtrigraphs -Waddress -Wlogical-op
-FLAGS=$(WARNINGS) -O3
+FLAGS=$(WARNINGS) -g3
 
 INCLUDES=-I./include -I./lib/linux_x86_64/include
-LIBS=-L./lib/linux_x86_64/lib -lwasmer -Wl,-rpath=./lib/linux_x86_64/lib
+LIBS=-L./lib/linux_x86_64/lib -lwasmer -Wl,-rpath=./lib/linux_x86_64/lib -lglfw -lGL
 TARGET=./bin/heartbreak
 
-OBJECT_FILES=./build/heartbreak.o
+OBJECT_FILES=./build/heartbreak.o ./build/utils.o ./build/heartbreak_system.o ./build/heartbreak_graphics.o
 
 build/%.o: src/%.c
        $(CC) $(FLAGS) $(INCLUDES) -o $@ $(LIBS) -c $<
 
+clean:
+       rm -f $(OBJECT_FILES) 2>/dev/null
+
 $(TARGET): $(OBJECT_FILES)
-       $(CC) $(FLAGS) -o $@ $< $(LIBS)
\ No newline at end of file
+       $(CC) $(FLAGS) -o $@ $^ $(LIBS)
+
+all: $(TARGET)
\ No newline at end of file
diff --git a/docs/design.md b/docs/design.md
new file mode 100644 (file)
index 0000000..24250b3
--- /dev/null
@@ -0,0 +1,11 @@
+Heartbreak
+-----------------------
+
+Heartbreak is a GLFW and OpenGL wrapper for WebAssembly modules. this will not be very useful for most people,
+as in order for them to create a WASM module, they would have had to write in a language that would also
+compile to native code. But for my language, Onyx, outputting to native code is not currently supported, so
+I thought I would make this tool instead.
+
+The biggest thing to decide is how "high-level" this tool is. Is it similar to LOVE where you write simple hooks
+and that's all? Everything else is set up for you? OR should you have to control the set up as well? I'm leaning
+toward the former, as I want to reduce the friction of starting a new project.
\ No newline at end of file
diff --git a/include/heartbreak.h b/include/heartbreak.h
new file mode 100644 (file)
index 0000000..0255e7b
--- /dev/null
@@ -0,0 +1,52 @@
+#ifndef HEARTBREAK_H
+#define HEARTBREAK_H
+
+#include "bh.h"
+#include "wasm.h"
+#include "wasmer.h"
+#include <GLFW/glfw3.h>
+
+//
+// Global OpenGL / GLFW things
+//
+extern GLFWwindow* glfw_window;
+
+
+//
+// Helper macro to easily define WASM-interface functions
+//
+
+#define _NUM_VALS(...) (sizeof((wasm_valkind_t[]) {__VA_ARGS__})/sizeof(wasm_valkind_t))
+#define _VALS(...) { _NUM_VALS(__VA_ARGS__), __VA_ARGS__ }
+
+typedef struct {
+    u32 count;
+    wasm_valkind_t types[20];
+} WasmValkindBuffer;
+
+typedef struct {
+    char* name;
+    wasm_func_callback_t func;
+
+    WasmValkindBuffer params;
+    WasmValkindBuffer results;
+} WasmFuncDefinition;
+
+#define STRINGIFY1(a) #a
+#define CONCAT2(a, b) a ## _ ## b
+#define CONCAT3(a, b, c) a ## _ ## b ## _ ## c
+#define HEARTBREAK_MODULE_NAME_GEN(m) CONCAT2(__heartbreak_module, m)
+#define HEARTBREAK_FUNC_NAME(m, n) CONCAT3(__heartbreak_internal, m, n)
+#define HEARTBREAK_IMPORT_NAME(m, n) STRINGIFY1(m) "_" #n
+
+#define HEARTBREAK_DEF(name) wasm_trap_t* HEARTBREAK_FUNC_NAME(HEARTBREAK_MODULE_NAME, name)(const wasm_val_vec_t* params, wasm_val_vec_t* results)
+#define HEARTBREAK_FUNC(name, params_types, result_types) (WasmFuncDefinition) { HEARTBREAK_IMPORT_NAME(HEARTBREAK_MODULE_NAME, name), HEARTBREAK_FUNC_NAME(HEARTBREAK_MODULE_NAME, name), _VALS params_types, _VALS result_types },
+
+#define HEARTBREAK_MODULE WasmFuncDefinition HEARTBREAK_MODULE_NAME_GEN(HEARTBREAK_MODULE_NAME) [] =
+
+
+// The Heartbreak modules
+extern WasmFuncDefinition __heartbreak_module_system[];
+extern WasmFuncDefinition __heartbreak_module_graphics[];
+
+#endif
\ No newline at end of file
diff --git a/include/utils.h b/include/utils.h
new file mode 100644 (file)
index 0000000..9277421
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef HEARTBREAK_UTILS_H
+#define HEARTBREAK_UTILS_H
+
+#include "wasm.h"
+#include "bh.h"
+
+//
+// Wasm-C-API Utilities
+// Things I think should come with the Wasm-C-API but don't
+//
+b32 wasm_name_equals(const wasm_name_t* name1, const wasm_name_t* name2);
+b32 wasm_name_equals_string(const wasm_name_t* name1, const char* name2);
+wasm_extern_t* wasm_extern_lookup_by_name(wasm_module_t* module, wasm_instance_t* instance, const char* name);
+
+
+#endif
\ No newline at end of file
diff --git a/misc/onyx/heartbreak.onyx b/misc/onyx/heartbreak.onyx
new file mode 100644 (file)
index 0000000..ce375c0
--- /dev/null
@@ -0,0 +1,23 @@
+package heartbreak
+
+#load "./heartbreak_graphics"
+#load "./heartbreak_system"
+
+use package core {println}
+
+HeartbreakFuncs :: struct {
+    update : (dt: f32) -> void;
+    draw   : () -> void;
+}
+
+run :: (use funcs: HeartbreakFuncs) {
+    if !system.init() {
+        println("Failed to initialize Heartbreak.");
+        return;
+    }
+
+    while system.end_frame() {
+        update(0);
+        draw();
+    }
+}
\ No newline at end of file
diff --git a/misc/onyx/heartbreak_graphics.onyx b/misc/onyx/heartbreak_graphics.onyx
new file mode 100644 (file)
index 0000000..d18f80b
--- /dev/null
@@ -0,0 +1,12 @@
+package heartbreak.graphics
+
+setClearColor :: (r, g, b, a: f32) -> void #foreign "heartbreak" "graphics_set_clear_color" ---
+clear         :: () -> void #foreign "heartbreak" "graphics_clear" ---
+
+FillMode :: enum {
+    Fill :: 0x01;
+    Line :: 0x02;
+}
+
+setColor  :: (r, g, b: f32, a: f32 = 1) -> void        #foreign "heartbreak" "graphics_set_color" ---
+rectangle :: (mode: FillMode, x, y, w, h: f32) -> void #foreign "heartbreak" "graphics_rectangle" ---
diff --git a/misc/onyx/heartbreak_system.onyx b/misc/onyx/heartbreak_system.onyx
new file mode 100644 (file)
index 0000000..17af279
--- /dev/null
@@ -0,0 +1,5 @@
+package heartbreak.system
+
+init      :: () -> bool #foreign "heartbreak" "system_init" ---
+destroy   :: () -> void #foreign "heartbreak" "system_destroy" ---
+end_frame :: () -> bool #foreign "heartbreak" "system_end_frame" ---
index 12b79de13d7925c9612850ebaa0d34d800d84bd3..307759ac5992c9c894f894cabf16d8922bdae8c6 100644 (file)
@@ -1,51 +1,48 @@
+#define VERSION "v0.0.1a"
+
 #define BH_DEFINE
 #include "bh.h"
+#include "utils.h"
+#include "heartbreak.h"
 
 #include "wasm.h"
 #include "wasmer.h"
+#include <GLES3/gl3.h>
+#include <GLFW/glfw3.h>
 
-b32 wasm_name_equals(const wasm_name_t* name1, const wasm_name_t* name2) {
-    if (name1->size != name2->size) return 0;
-    return !strncmp(name1->data, name2->data, name1->size);
-}
-
-b32 wasm_name_equals_string(const wasm_name_t* name1, const char* name2) {
-    u32 name2_size = strlen(name2);
-    if (name1->size != name2_size) return 0;
-    return !strncmp(name1->data, name2, name1->size);
-}
-
-#define _NUM_VALS(...) (sizeof((wasm_valkind_t[]) {__VA_ARGS__})/sizeof(wasm_valkind_t))
-#define VALS(...) { _NUM_VALS(__VA_ARGS__), __VA_ARGS__ }
-
-typedef struct {
-    u32 count;
-    wasm_valkind_t types[20];
-} WasmValkindBuffer;
-
-typedef struct {
-    char* name;
-    wasm_func_callback_t func;
+GLFWwindow* glfw_window = NULL;
 
-    WasmValkindBuffer params;
-    WasmValkindBuffer results;
-} WasmFuncDefinition;
-
-#define WASM_FUNCS \
-    WASM_FUNC(init, VALS(), VALS(WASM_I32)) \
-    WASM_FUNC(add,  VALS(WASM_I32, WASM_I32), VALS(WASM_I32))
+void build_heartbreak_imports(WasmFuncDefinition** out_wfds, i32* out_count) {
+    static WasmFuncDefinition* modules[] = {
+        __heartbreak_module_system,
+        __heartbreak_module_graphics,
+    };
 
-#define HEARTBREAK_FUNC(name) wasm_trap_t* __heartbreak_interface_ ## name (const wasm_val_vec_t* params, wasm_val_vec_t* results)
+    i32 module_count = sizeof(modules) / sizeof(WasmFuncDefinition*);
 
-HEARTBREAK_FUNC(init) {
-    results->data[0] = WASM_I32_VAL(1234);
+    i32 count = 0;
+    fori (i, 0, module_count) {
+        WasmFuncDefinition* wfd = modules[i];
+        while (wfd->name != NULL) {
+            count += 1;
+            wfd += 1;
+        }
+    }
 
-    return NULL;
-}
+    bh_allocator heap_allocator = bh_heap_allocator();
+    WasmFuncDefinition* imports = bh_alloc(heap_allocator, sizeof(WasmFuncDefinition) * count);
+
+    i32 k = 0;
+    fori (i, 0, module_count) {
+        WasmFuncDefinition* wfd = modules[i];
+        while (wfd->name != NULL) {
+            imports[k++] = *wfd;
+            wfd += 1;
+        }
+    }
 
-HEARTBREAK_FUNC(add) {
-    results->data[0] = WASM_I32_VAL(params->data[0].of.i32 + params->data[1].of.i32);
-    return NULL;
+    *out_wfds = imports;
+    *out_count = count;
 }
 
 void run_wasm_file(bh_buffer wasm_bytes) {
@@ -88,14 +85,10 @@ void run_wasm_file(bh_buffer wasm_bytes) {
     wasmer_named_extern_vec_t wasi_imports;
     wasi_get_unordered_imports(store, module, wasi_env, &wasi_imports);
 
-    #define WASM_FUNC(name, params_types, result_types) (WasmFuncDefinition) { #name, __heartbreak_interface_ ## name, params_types, result_types },
-    static WasmFuncDefinition defs[] = {
-        WASM_FUNCS
-        { NULL }
-    };
-    #undef WASM_FUNC
+    WasmFuncDefinition* defs;
+    i32 defs_count;
+    build_heartbreak_imports(&defs, &defs_count);
 
-    u32 defs_count = sizeof(defs) / sizeof(WasmFuncDefinition);
     wasm_name_t heartbreak_name;
     wasm_name_new_from_string(&heartbreak_name, "heartbreak");
 
@@ -136,7 +129,7 @@ void run_wasm_file(bh_buffer wasm_bytes) {
 
                 wasm_functype_t* wasm_functype = wasm_functype_new(&wasm_params, &wasm_results);
 
-                wasm_func_t* wasm_func = wasm_func_new(store, wasm_functype, defs[i].func);
+                wasm_func_t* wasm_func = wasm_func_new(store, wasm_functype, defs[j].func);
                 import = wasm_func_as_extern(wasm_func);
                 goto import_found;
             }
@@ -145,7 +138,7 @@ void run_wasm_file(bh_buffer wasm_bytes) {
         goto bad_import;
 
     import_found:
-        // bh_printf("Found import %b.%b.\n", module_name->data, module_name->size, import_name->data, import_name->size);
+        bh_printf("Found import %b.%b.\n", module_name->data, module_name->size, import_name->data, import_name->size);
         imports.data[i] = import;
         continue;
 
@@ -159,24 +152,7 @@ void run_wasm_file(bh_buffer wasm_bytes) {
     instance = wasm_instance_new(store, module, &imports, &traps);
     if (!instance) goto error_handling;
 
-    // Find the start function
-    i32 start_function_idx = -1;
-    wasm_exporttype_vec_t export_types;
-    wasm_module_exports(module, &export_types);
-    fori (i, 0, (i64) export_types.size) {
-        wasm_exporttype_t* export_type = export_types.data[i];
-        const wasm_name_t* export_name = wasm_exporttype_name(export_type);
-        
-        if (!strncmp(export_name->data, "_start", 6)) {
-            start_function_idx = i;
-            break;
-        }
-    }
-
-    wasm_extern_vec_t exports;
-    wasm_instance_exports(instance, &exports);
-
-    wasm_extern_t* start_extern = exports.data[start_function_idx];
+    wasm_extern_t* start_extern = wasm_extern_lookup_by_name(module, instance, "_start");
     wasm_func_t*   start_func   = wasm_extern_as_func(start_extern);
 
     wasm_val_vec_t args;
@@ -204,8 +180,14 @@ int main(int argc, char* argv[]) {
         return 1;
     }
 
+    if (!bh_file_exists(argv[1])) {
+        bh_printf("Failed to open '%s'.\n", argv[1]);
+        return 1;
+    }
+
     bh_allocator heap_allocator = bh_heap_allocator();
     bh_file_contents wasm_file = bh_file_read_contents_direct(heap_allocator, argv[1]);
+
     run_wasm_file((bh_buffer) {
         .data   = wasm_file.data,
         .length = wasm_file.length,
diff --git a/src/heartbreak_graphics.c b/src/heartbreak_graphics.c
new file mode 100644 (file)
index 0000000..c5898ab
--- /dev/null
@@ -0,0 +1,37 @@
+#define HEARTBREAK_MODULE_NAME graphics
+#include "heartbreak.h"
+
+static f32 clear_r, clear_g, clear_b, clear_a;
+
+HEARTBREAK_DEF(set_clear_color) {
+    clear_r = params->data[0].of.f32;
+    clear_g = params->data[1].of.f32;
+    clear_b = params->data[2].of.f32;
+    clear_a = params->data[3].of.f32;
+    return NULL;
+}
+
+HEARTBREAK_DEF(clear) {
+    glClearColor(clear_r, clear_g, clear_b, clear_a);
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+    return NULL;
+}
+
+HEARTBREAK_DEF(set_color) {
+
+    return NULL;
+}
+
+HEARTBREAK_DEF(rectangle) {
+
+    return NULL;
+}
+
+HEARTBREAK_MODULE {
+    HEARTBREAK_FUNC(set_clear_color, (WASM_F32,WASM_F32,WASM_F32,WASM_F32), ())
+    HEARTBREAK_FUNC(clear, (), ())
+    HEARTBREAK_FUNC(set_color, (WASM_F32,WASM_F32,WASM_F32,WASM_F32), ())
+    HEARTBREAK_FUNC(rectangle, (WASM_I32,WASM_F32,WASM_F32,WASM_F32,WASM_F32), ())
+
+    { NULL }
+};
\ No newline at end of file
diff --git a/src/heartbreak_system.c b/src/heartbreak_system.c
new file mode 100644 (file)
index 0000000..135ba53
--- /dev/null
@@ -0,0 +1,46 @@
+#define HEARTBREAK_MODULE_NAME system
+#include "heartbreak.h"
+
+HEARTBREAK_DEF(init) {
+    if (!glfwInit()) {
+        bh_printf("Failed to initialize GLFW.\n");
+        results->data[0] = WASM_I32_VAL(0);
+        return NULL;
+    }
+
+    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
+    glfw_window = glfwCreateWindow(800, 600, "Heartbreak", NULL, NULL);
+    if (!glfw_window) {
+        bh_printf("Failed to create GLFW window.\n");
+        results->data[0] = WASM_I32_VAL(0);
+        return NULL;
+    }
+
+    glfwMakeContextCurrent(glfw_window);
+
+    results->data[0] = WASM_I32_VAL(1);
+    return NULL;
+}
+
+HEARTBREAK_DEF(destroy) {
+    glfwDestroyWindow(glfw_window);
+    glfwTerminate();
+    return NULL;
+}
+
+HEARTBREAK_DEF(end_frame) {
+    glfwSwapBuffers(glfw_window);
+    glfwPollEvents();
+
+    results->data[0] = WASM_I32_VAL(!glfwWindowShouldClose(glfw_window));
+    return NULL;
+}
+
+HEARTBREAK_MODULE {
+    HEARTBREAK_FUNC(init, (), (WASM_I32))
+    HEARTBREAK_FUNC(destroy, (), ())
+    HEARTBREAK_FUNC(end_frame, (), (WASM_I32))
+
+    { NULL }
+};
\ No newline at end of file
diff --git a/src/utils.c b/src/utils.c
new file mode 100644 (file)
index 0000000..bc8b03f
--- /dev/null
@@ -0,0 +1,39 @@
+#include "utils.h"
+
+//
+// Wasm-C-API Utilities
+// Things I think should come with the Wasm-C-API but don't
+//
+b32 wasm_name_equals(const wasm_name_t* name1, const wasm_name_t* name2) {
+    if (name1->size != name2->size) return 0;
+    return !strncmp(name1->data, name2->data, name1->size);
+}
+
+b32 wasm_name_equals_string(const wasm_name_t* name1, const char* name2) {
+    u32 name2_size = strlen(name2);
+    if (name1->size != name2_size) return 0;
+    return !strncmp(name1->data, name2, name1->size);
+}
+
+wasm_extern_t* wasm_extern_lookup_by_name(wasm_module_t* module, wasm_instance_t* instance, const char* name) {
+    i32 name_len = strlen(name);
+
+    i32 idx = -1;
+    wasm_exporttype_vec_t export_types;
+    wasm_module_exports(module, &export_types);
+    fori (i, 0, (i64) export_types.size) {
+        wasm_exporttype_t* export_type = export_types.data[i];
+        const wasm_name_t* export_name = wasm_exporttype_name(export_type);
+        
+        if (!strncmp(export_name->data, name, name_len)) {
+            idx = i;
+            break;
+        }
+    }
+
+    wasm_extern_vec_t exports;
+    wasm_instance_exports(instance, &exports);
+
+    return exports.data[idx];
+}
+
index 17f74a68c8c3a830505b46b76efb3f6908ab85a8..d38276a34acbcc6ae2bd3e09c4ec668ba373b0ed 100644 (file)
@@ -1,13 +1,24 @@
 #load "core/std"
+#load "./../misc/onyx/heartbreak"
 
 use package core
+hb :: package heartbreak
 
-heartbreak_init :: () -> i32 #foreign "heartbreak" "init" ---
-heartbreak_add  :: (x, y: i32) -> i32 #foreign "heartbreak" "add" ---
+t: f32 = 0;
+update :: (dt: f32) {
+    t += 0.016;
+}
+
+draw :: () {
+    hb.graphics.setClearColor(math.sin(t), 0, math.cos(t), 1);
+    hb.graphics.clear();
+
+    hb.graphics.setColor(0, 1, 0);
+    hb.graphics.rectangle(.Fill, 0, 0, 100, 100);
+}
 
 main :: (_) => {
     printf("Simp test is working!\n");
 
-    heartbreak_init() |> println();
-    heartbreak_add(3, 5) |> println();
+    hb.run(.{ update, draw });
 }
\ No newline at end of file