--- /dev/null
+*.o
+*.wasm
+bin/heartbreak
+.vscode/
\ No newline at end of file
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
--- /dev/null
+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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+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
--- /dev/null
+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" ---
--- /dev/null
+package heartbreak.system
+
+init :: () -> bool #foreign "heartbreak" "system_init" ---
+destroy :: () -> void #foreign "heartbreak" "system_destroy" ---
+end_frame :: () -> bool #foreign "heartbreak" "system_end_frame" ---
+#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) {
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");
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;
}
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;
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;
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,
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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];
+}
+
#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