LIBS=-L./lib/linux_x86_64/lib -lwasmer -Wl,-rpath=./lib/linux_x86_64/lib -lglfw -lGL -lm -lpthread
TARGET=./bin/heartbreak
-OBJECT_FILES=./build/heartbreak.o ./build/utils.o ./build/gfx.o ./build/heartbreak_system.o ./build/heartbreak_graphics.o ./build/heartbreak_input.o ./build/heartbreak_window.o ./build/heartbreak_thread.o
+OBJECT_FILES=./build/heartbreak.o ./build/utils.o ./build/gfx.o ./build/heartbreak_system.o ./build/heartbreak_graphics.o ./build/heartbreak_input.o ./build/heartbreak_window.o ./build/heartbreak_thread.o ./build/heartbreak_timer.o
build/%.o: src/%.c
$(CC) $(FLAGS) $(INCLUDES) -o $@ $(LIBS) -c $<
$(TARGET): $(OBJECT_FILES)
$(CC) $(FLAGS) -o $@ $^ $(LIBS)
-all: $(TARGET) tests/minesweeper.wasm tests/simp.wasm
+all: $(TARGET) tests/minesweeper.wasm tests/simp.wasm tests/snake.wasm
run: all
- ./bin/heartbreak tests/simp.wasm
+ ./bin/heartbreak tests/snake.wasm
@echo off
set FLAGS=/O2 /MT /Z7 /TC /std:c17
-set SOURCE=src/gfx.c src/heartbreak.c src/heartbreak_graphics.c src/heartbreak_input.c src/heartbreak_system.c src/heartbreak_thread.c src/heartbreak_window.c src/utils.c
+set SOURCE=src/gfx.c src/heartbreak.c src/heartbreak_graphics.c src/heartbreak_input.c src/heartbreak_system.c src/heartbreak_thread.c src/heartbreak_timer.c src/heartbreak_window.c src/utils.c
set LIBS=lib\windows_x86_64\lib\wasmer.lib C:\tools\glfw-3.3.5.bin.WIN64\lib-vc2019\glfw3.lib C:\tools\glfw-3.3.5.bin.WIN64\lib-vc2019\glfw3dll.lib opengl32.lib ws2_32.lib Advapi32.lib userenv.lib bcrypt.lib
set LINKFLAGS=/incremental:no /opt:ref /subsystem:console
set OUT=bin\heartbreak.exe
extern WasmFuncDefinition* __heartbreak_module_graphics[];
extern WasmFuncDefinition* __heartbreak_module_input[];
extern WasmFuncDefinition* __heartbreak_module_thread[];
+extern WasmFuncDefinition* __heartbreak_module_timer[];
extern WasmFuncDefinition* __heartbreak_module_window[];
#endif
#load "./heartbreak_system"
#load "./heartbreak_input"
#load "./heartbreak_thread_runtime"
-#load "./heartbreak_thread"
+#load "./heartbreak_timer"
#load "./heartbreak_window"
use package core {println}
load();
while system.end_frame() {
- update(0);
+ dt := timer.step();
+ update(dt);
draw();
}
+++ /dev/null
-package heartbreak.thread
-
-// This function exists because WASI's pole_oneoff has quirky bugs when combined with pthreads
-sleep :: (ms: i64) -> void #foreign "heartbreak" "thread_sleep" ---
--- /dev/null
+package heartbreak.timer
+
+step :: () -> f32 #foreign "heartbreak" "timer_step" ---
+getDelta :: () -> f32 #foreign "heartbreak" "timer_get_delta" ---
+getFPS :: () -> f32 #foreign "heartbreak" "timer_get_fps" ---
+getTime :: () -> f32 #foreign "heartbreak" "timer_get_time" ---
+
+sleep :: #match {
+ (milliseconds: i64) -> void #foreign "heartbreak" "timer_sleep" --- ,
+
+ macro (seconds: i64) -> void {
+ sleep :: sleep
+ sleep(milliseconds = seconds * 1000);
+ }
+}
\ No newline at end of file
}
void gfx_immediate_renderer_render_text(ImmediateRenderer *ir, f32 x, f32 y, char* msgptr, i32 msglen, f32 max_width) {
- if (ir->tris_vertex_count > 0) gfx_immediate_renderer_flush(ir);
+ if (ir->tris_vertex_count > 0 || ir->world_transform_dirty) gfx_immediate_renderer_flush(ir);
stbtt_aligned_quad* quads = (stbtt_aligned_quad *) alloca(msglen * sizeof(stbtt_aligned_quad));
i32 quad_num = 0;
__heartbreak_module_graphics,
__heartbreak_module_input,
__heartbreak_module_thread,
+ __heartbreak_module_timer,
__heartbreak_module_window,
};
#ifdef _BH_LINUX
#include <pthread.h>
#include <signal.h>
- #include <unistd.h>
#endif
#define HEARTBREAK_MODULE_NAME thread
return NULL;
}
-HEARTBREAK_DEF(sleep, (WASM_I64), ()) {
- i32 t = params->data[0].of.i64;
-
- #ifdef _BH_LINUX
- usleep(t * 1000);
- #endif
-
- #ifdef _BH_WINDOWS
- Sleep(t);
- #endif
- return NULL;
-}
-
HEARTBREAK_MODULE {
HEARTBREAK_FUNC(spawn)
HEARTBREAK_FUNC(kill)
- HEARTBREAK_FUNC(sleep)
NULL
};
--- /dev/null
+#include "heartbreak.h"
+
+#ifdef _BH_LINUX
+ #include <unistd.h>
+#endif
+
+#define HEARTBREAK_MODULE_NAME timer
+
+static f64 last_queried_time = 0;
+static f64 fps = 0;
+
+HEARTBREAK_DEF(step, (), (WASM_F32)) {
+ static i32 called_this_second = 0;
+ static f64 cumulative_delta = 0;
+
+ f64 new_time = glfwGetTime();
+ f64 delta = new_time - last_queried_time;
+
+ called_this_second += 1;
+ cumulative_delta += delta;
+
+ // If the integer parts are different, then we crossed the "second" boundary.
+ i32 i1 = (i32) new_time;
+ i32 i2 = (i32) last_queried_time;
+ if (i1 != i2) {
+ fps = called_this_second / cumulative_delta;
+ cumulative_delta = 0;
+ called_this_second = 0;
+ }
+
+ last_queried_time = new_time;
+
+ results->data[0] = WASM_F32_VAL(delta);
+ return NULL;
+}
+
+HEARTBREAK_DEF(get_delta, (), (WASM_F32)) {
+ f64 new_time = glfwGetTime();
+ results->data[0] = WASM_F32_VAL(new_time - last_queried_time);
+ return NULL;
+}
+
+HEARTBREAK_DEF(get_fps, (), (WASM_F32)) {
+ results->data[0] = WASM_F32_VAL(fps);
+ return NULL;
+}
+
+HEARTBREAK_DEF(get_time, (), (WASM_F32)) {
+ results->data[0] = WASM_F32_VAL(glfwGetTime());
+ return NULL;
+}
+
+HEARTBREAK_DEF(sleep, (WASM_I64), ()) {
+ i32 t = params->data[0].of.i64;
+
+ #ifdef _BH_LINUX
+ usleep(t * 1000);
+ #endif
+
+ #ifdef _BH_WINDOWS
+ Sleep(t);
+ #endif
+ return NULL;
+}
+
+HEARTBREAK_MODULE {
+ HEARTBREAK_FUNC(step)
+ HEARTBREAK_FUNC(get_delta)
+ HEARTBREAK_FUNC(get_fps)
+ HEARTBREAK_FUNC(get_time)
+ HEARTBREAK_FUNC(sleep)
+
+ NULL
+};
\ No newline at end of file
dummy: hb.graphics.Image;
squares: [..] Square;
+square_resource: Resource(typeof squares)
Square :: struct {
x, y: f32;
r, g, b, a: f32;
}
+Resource :: struct (T: type_expr) {
+ resource: ^T;
+ lock: sync.Mutex;
+}
+
+create_resource :: (v: ^$T) -> Resource(T) {
+ res: Resource(T);
+ res.resource = v;
+ sync.mutex_init(^res.lock);
+ return res;
+}
+
+with_resource :: macro (r: ^Resource($T), block: Code) -> u32 {
+ sync.scoped_mutex(^r.lock);
+
+ it := r.resource;
+ #insert block;
+
+ return 0;
+}
+
load :: () {
w := hb.window.getWidth();
h := hb.window.getHeight();
printf("{} by {}\n", w, h);
array.init(^squares);
+ square_resource = create_resource(^squares);
printf("Width is: {}\n", hb.graphics.getTextWidth("Hello, World!"));
- better_font := hb.graphics.newFont("C:\\Windows\\Fonts\\calibri.ttf", 32);
+ better_font := hb.graphics.newFont("/usr/share/fonts/TTF/InputSerif-Regular.ttf", 32);
assert(better_font != 0, "Failed to load font!");
hb.graphics.setFont(better_font);
// hb.graphics.setFont();
thread.spawn(^dummy_thread, cast(^void) null, (_) => {
while true {
println("Loop");
- hb.thread.sleep(1000);
+ with_resource(^square_resource, #code {
+ mx, my := hb.input.mouseGetPos();
+
+ *it << .{
+ x = ~~mx, y = ~~my,
+ w = 20, h = 20,
+ r = random.float(0, 1),
+ g = random.float(0, 1),
+ b = random.float(0, 1),
+ a = 1,
+ };
+ });
+
+ hb.timer.sleep(500);
}
});
}
hb.window.setShouldClose(true);
}
- if hb.input.mouseIsDown(.Left) {
+ if hb.input.mouseIsDown(.Left) do with_resource(^square_resource, #code {
mx, my := hb.input.mouseGetPos();
- squares << .{
+ *it << .{
x = ~~mx, y = ~~my,
w = 10, h = 10,
r = random.float(0, 1),
b = random.float(0, 1),
a = random.float(0, 1),
};
- }
+ });
}
draw :: () {
hb.graphics.setColor(1, 1, 1, 1);
hb.graphics.drawImage(dummy, 100, 100, 100, 100);
- hb.graphics.newQuad(4, 4, 8, 8, dummy) |> hb.graphics.drawQuad(200, 100);
+ hb.graphics.newQuad(4, 4, 8, 8, dummy) |> hb.graphics.drawQuad(200, 100, 100, 100);
for it: squares {
hb.graphics.setColor(it.r, it.g, it.b, it.a);
--- /dev/null
+#load "./../misc/onyx/qhb"
+
+use package core
+
+Vec2 :: struct (T: type_expr) {
+ x, y: T;
+
+ convert :: (use this: ^Vec2($T), $R: type_expr) -> Vec2(R) {
+ return .{ cast(R) x, cast(R) y };
+ }
+}
+#operator + (v1, v2: Vec2($T)) => Vec2(T).{ v1.x + v2.x, v1.y + v2.y };
+#operator - (v1, v2: Vec2($T)) => Vec2(T).{ v1.x - v2.x, v1.y - v2.y };
+#operator * (v1: Vec2($T), s: T) => Vec2(T).{ v1.x * s, v1.y * s };
+#operator == (v1, v2: Vec2($T)) => v1.x == v2.x && v1.y == v2.y;
+
+Vec2i :: #type Vec2(i32);
+
+Snake :: struct {
+ head: Vec2i;
+ body: [..] Vec2i;
+
+ direction: Vec2i;
+}
+
+snake_make :: (head: Vec2i) -> Snake {
+ s: Snake;
+ s.head = head;
+ s.direction = .{ 1, 0 };
+ array.init(^s.body);
+ for i: 4 do s.body << head;
+ return s;
+}
+
+snake_move :: (use this: ^Snake) {
+ for i: iter.as_iterator(range.{ body.count - 1, 1, -1 }) {
+ body[i] = body[i - 1];
+ }
+ body[0] = head;
+
+ head += direction;
+}
+
+snake_draw :: (use this: ^Snake, cell_size: f32) {
+ hb.graphics.setColor(0, 0.6, 0);
+ for ^it: body {
+ hb.graphics.rectangle(.Fill, ~~it.x * cell_size, ~~it.y * cell_size, cell_size, cell_size);
+ }
+
+ hb.graphics.setColor(0, 1, 0);
+ hb.graphics.rectangle(.Fill, ~~head.x * cell_size, ~~head.y * cell_size, cell_size, cell_size);
+
+ hb.graphics.setColor(0, 0, 0);
+ eye_pos := head->convert(f32) * cell_size + Vec2(f32).{ cell_size, cell_size } * 0.5;
+ eye_pos += direction->convert(f32) * 0.25 * cell_size;
+ hb.graphics.rectangle(.Fill, eye_pos.x - cell_size / 8, eye_pos.y - cell_size / 8, cell_size / 4, cell_size / 4);
+}
+
+
+the_snake: Snake;
+
+load :: () {
+ the_snake = snake_make(.{ 0, 0 });
+}
+
+update :: (dt: f32) {
+ if hb.input.keyIsDown(.Escape) {
+ hb.window.setShouldClose(true);
+ }
+
+ if hb.input.keyIsDown(.Left) do the_snake.direction = .{ -1, 0 };
+ if hb.input.keyIsDown(.Right) do the_snake.direction = .{ 1, 0 };
+ if hb.input.keyIsDown(.Up) do the_snake.direction = .{ 0, -1 };
+ if hb.input.keyIsDown(.Down) do the_snake.direction = .{ 0, 1 };
+
+ #persist snake_timer := 1.0f;
+ snake_timer -= dt;
+ if snake_timer <= 0 {
+ snake_timer = 0.2;
+ snake_move(^the_snake);
+ }
+}
+
+draw :: () {
+ hb.graphics.setClearColor(0.1, 0.1, 0.1);
+ hb.graphics.clear();
+
+ snake_draw(^the_snake, 32);
+}
\ No newline at end of file