From: Brendan Hansen Date: Mon, 1 Nov 2021 21:48:28 +0000 (-0500) Subject: basic initial working version X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=2db56310dfa2105ce006414b5555e192f0317a51;p=heartbreak.git basic initial working version --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b45a852 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.o +*.wasm +bin/heartbreak +.vscode/ \ No newline at end of file diff --git a/Makefile b/Makefile index 0bcbf71..bda92c9 100644 --- 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 index 0000000..24250b3 --- /dev/null +++ b/docs/design.md @@ -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 index 0000000..0255e7b --- /dev/null +++ b/include/heartbreak.h @@ -0,0 +1,52 @@ +#ifndef HEARTBREAK_H +#define HEARTBREAK_H + +#include "bh.h" +#include "wasm.h" +#include "wasmer.h" +#include + +// +// 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 index 0000000..9277421 --- /dev/null +++ b/include/utils.h @@ -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 index 0000000..ce375c0 --- /dev/null +++ b/misc/onyx/heartbreak.onyx @@ -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 index 0000000..d18f80b --- /dev/null +++ b/misc/onyx/heartbreak_graphics.onyx @@ -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 index 0000000..17af279 --- /dev/null +++ b/misc/onyx/heartbreak_system.onyx @@ -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" --- diff --git a/src/heartbreak.c b/src/heartbreak.c index 12b79de..307759a 100644 --- a/src/heartbreak.c +++ b/src/heartbreak.c @@ -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 +#include -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 index 0000000..c5898ab --- /dev/null +++ b/src/heartbreak_graphics.c @@ -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 index 0000000..135ba53 --- /dev/null +++ b/src/heartbreak_system.c @@ -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 index 0000000..bc8b03f --- /dev/null +++ b/src/utils.c @@ -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]; +} + diff --git a/tests/simp.onyx b/tests/simp.onyx index 17f74a6..d38276a 100644 --- a/tests/simp.onyx +++ b/tests/simp.onyx @@ -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