sokoban-v1/engine/src/lib/root.zig
2026-02-08 21:55:59 +02:00

504 lines
12 KiB
Zig

const std = @import("std");
const log = std.log.scoped(.engine);
pub const build_options = @import("build_options");
pub const ImGui = @import("./imgui.zig");
pub const Math = @import("./math.zig");
const rgb = Math.rgb;
const Rect = Math.Rect;
const Vec2 = Math.Vec2;
const Vec4 = Math.Vec4;
pub const Nanoseconds = u64;
pub const KeyCode = enum {
SPACE,
APOSTROPHE,
COMMA,
MINUS,
PERIOD,
SLASH,
_0,
_1,
_2,
_3,
_4,
_5,
_6,
_7,
_8,
_9,
SEMICOLON,
EQUAL,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
LEFT_BRACKET,
BACKSLASH,
RIGHT_BRACKET,
GRAVE_ACCENT,
WORLD_1,
WORLD_2,
ESCAPE,
ENTER,
TAB,
BACKSPACE,
INSERT,
DELETE,
RIGHT,
LEFT,
DOWN,
UP,
PAGE_UP,
PAGE_DOWN,
HOME,
END,
CAPS_LOCK,
SCROLL_LOCK,
NUM_LOCK,
PRINT_SCREEN,
PAUSE,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
F13,
F14,
F15,
F16,
F17,
F18,
F19,
F20,
F21,
F22,
F23,
F24,
F25,
KP_0,
KP_1,
KP_2,
KP_3,
KP_4,
KP_5,
KP_6,
KP_7,
KP_8,
KP_9,
KP_DECIMAL,
KP_DIVIDE,
KP_MULTIPLY,
KP_SUBTRACT,
KP_ADD,
KP_ENTER,
KP_EQUAL,
LEFT_SHIFT,
LEFT_CONTROL,
LEFT_ALT,
LEFT_SUPER,
RIGHT_SHIFT,
RIGHT_CONTROL,
RIGHT_ALT,
RIGHT_SUPER,
MENU,
};
pub const MouseButton = enum {
left,
right,
middle,
};
pub const TextureId = enum(u16) { _ };
pub const FontId = enum(u16) { _ };
pub const AudioId = enum(u16) { _ };
pub const Sprite = struct {
texture: TextureId,
uv: Rect
};
fn ButtonStateSet(E: type) type {
return struct {
const Self = @This();
down: std.EnumSet(E),
pressed: std.EnumSet(E),
released: std.EnumSet(E),
pressed_at: std.EnumMap(E, Nanoseconds),
pub const empty = Self{
.down = .initEmpty(),
.pressed = .initEmpty(),
.released = .initEmpty(),
.pressed_at = .init(.{}),
};
pub fn press(self: *Self, button: E, now: Nanoseconds) void {
self.pressed_at.put(button, now);
self.pressed.insert(button);
self.down.insert(button);
}
pub fn release(self: *Self, button: E) void {
self.down.remove(button);
self.released.insert(button);
self.pressed_at.remove(button);
}
pub fn releaseAll(self: *Self) void {
var iter = self.down.iterator();
while (iter.next()) |key_code| {
self.released.insert(key_code);
}
self.down = .initEmpty();
self.pressed_at = .init(.{});
}
};
}
pub const GraphicsCommand = union(enum) {
pub const DrawRectangle = struct {
rect: Rect,
color: Vec4,
sprite: ?Sprite = null,
rotation: f32 = 0,
origin: Vec2 = .init(0, 0),
};
pub const DrawLine = struct {
pos1: Vec2,
pos2: Vec2,
color: Vec4,
width: f32
};
pub const DrawText = struct {
pos: Vec2,
text: []const u8,
font: FontId,
size: f32,
color: Vec4,
};
push_scissor: Rect,
pop_scissor: void,
draw_rectangle: DrawRectangle,
draw_line: DrawLine,
draw_text: DrawText,
push_transformation: struct {
translation: Vec2,
scale: Vec2
},
pop_transformation: void
};
pub const AudioCommand = union(enum) {
pub const Play = struct {
id: AudioId,
volume: f32 = 1
};
play: Play,
};
pub const KeyState = struct {
down: bool,
pressed: bool,
released: bool,
down_duration: ?f64,
pub const RepeatOptions = struct {
first_at: f64 = 0,
period: f64
};
pub fn repeat(self: KeyState, last_repeat_at: *?f64, opts: RepeatOptions) bool {
if (!self.down) {
last_repeat_at.* = null;
return false;
}
const down_duration = self.down_duration.?;
if (last_repeat_at.* != null) {
if (down_duration >= last_repeat_at.*.? + opts.period) {
last_repeat_at.* = last_repeat_at.*.? + opts.period;
return true;
}
} else {
if (down_duration >= opts.first_at) {
last_repeat_at.* = opts.first_at;
return true;
}
}
return false;
}
};
pub const Frame = struct {
arena: std.heap.ArenaAllocator,
time_ns: Nanoseconds,
dt_ns: Nanoseconds,
char_mapping: std.EnumMap(KeyCode, u21),
keyboard: ButtonStateSet(KeyCode),
mouse_button: ButtonStateSet(MouseButton),
mouse_position: ?Vec2,
hide_cursor: bool,
audio_commands: std.ArrayList(AudioCommand),
graphics_commands: std.ArrayList(GraphicsCommand),
canvas_size: ?Vec2,
screen_size: Vec2,
clear_color: Vec4,
pub fn init(gpa: std.mem.Allocator) Frame {
return Frame{
.arena = std.heap.ArenaAllocator.init(gpa),
.time_ns = 0,
.dt_ns = 0,
.char_mapping = .{},
.keyboard = .empty,
.mouse_button = .empty,
.mouse_position = null,
.graphics_commands = .empty,
.canvas_size = null,
.clear_color = rgb(0, 0, 0),
.audio_commands = .empty,
.hide_cursor = false,
.screen_size = .init(0, 0),
};
}
pub fn deinit(self: *Frame) void {
self.arena.deinit();
}
pub fn deltaTime(self: Frame) f32 {
return @as(f32, @floatFromInt(self.dt_ns)) / std.time.ns_per_s;
}
pub fn time(self: Frame) f64 {
return @as(f64, @floatFromInt(self.time_ns)) / std.time.ns_per_s;
}
// --------------------- Audio ----------------------- //
fn pushAudioCommand(self: *Frame, command: AudioCommand) void {
const arena = self.arena.allocator();
self.audio_commands.append(arena, command) catch |e| {
log.warn("Failed to play audio: {}", .{e});
};
}
pub fn playAudio(self: *Frame, options: AudioCommand.Play) void {
self.pushAudioCommand(.{
.play = options,
});
}
// --------------------- Graphics ----------------------- //
fn pushGraphicsCommand(self: *Frame, command: GraphicsCommand) void {
const arena = self.arena.allocator();
self.graphics_commands.append(arena, command) catch |e|{
log.warn("Failed to push graphics command: {}", .{e});
};
}
pub fn drawRectangle(self: *Frame, opts: GraphicsCommand.DrawRectangle) void {
self.pushGraphicsCommand(.{ .draw_rectangle = opts });
}
pub fn drawLine(self: *Frame, pos1: Vec2, pos2: Vec2, color: Vec4, width: f32) void {
self.pushGraphicsCommand(.{
.draw_line = .{
.pos1 = pos1,
.pos2 = pos2,
.width = width,
.color = color
}
});
}
pub fn drawRectanglOutline(self: *Frame, pos: Vec2, size: Vec2, color: Vec4, width: f32) void {
// TODO: Don't use line segments
self.drawLine(pos, pos.add(.{ .x = size.x, .y = 0 }), color, width);
self.drawLine(pos, pos.add(.{ .x = 0, .y = size.y }), color, width);
self.drawLine(pos.add(.{ .x = 0, .y = size.y }), pos.add(size), color, width);
self.drawLine(pos.add(.{ .x = size.x, .y = 0 }), pos.add(size), color, width);
}
pub fn pushScissor(self: *Frame, rect: Rect) void {
self.pushGraphicsCommand(.{
.push_scissor = rect
});
}
pub fn popScissor(self: *Frame) void {
self.pushGraphicsCommand(.{
.pop_scissor = {}
});
}
pub fn pushTransform(self: *Frame, translation: Vec2, scale: Vec2) void {
self.pushGraphicsCommand(.{
.push_transformation = .{
.translation = translation,
.scale = scale
}
});
}
pub fn popTransform(self: *Frame) void {
self.pushGraphicsCommand(.{
.pop_transformation = {}
});
}
pub const DrawTextOptions = struct {
font: FontId,
size: f32 = 16,
color: Vec4 = rgb(255, 255, 255),
};
pub fn drawText(self: *Frame, position: Vec2, text: []const u8, opts: DrawTextOptions) void {
const arena = self.arena.allocator();
const text_dupe = arena.dupe(u8, text) catch |e| {
log.warn("Failed to draw text: {}", .{e});
return;
};
self.pushGraphicsCommand(.{
.draw_text = .{
.pos = position,
.text = text_dupe,
.size = opts.size,
.font = opts.font,
.color = opts.color,
}
});
}
// --------------------- Mouse ----------------------- //
pub fn isMousePressed(self: Frame, button: MouseButton) bool {
return self.mouse_button.pressed.contains(button);
}
pub fn isMouseDown(self: Frame, button: MouseButton) bool {
return self.mouse_button.down.contains(button);
}
pub fn isMouseReleased(self: Frame, button: MouseButton) bool {
return self.mouse_button.released.contains(button);
}
// --------------------- Keyboard ----------------------- //
pub fn isKeyDown(self: Frame, key_code: KeyCode) bool {
return self.keyboard.down.contains(key_code);
}
pub fn isKeyPressed(self: Frame, key_code: KeyCode) bool {
return self.keyboard.pressed.contains(key_code);
}
pub fn isKeyReleased(self: Frame, key_code: KeyCode) bool {
return self.keyboard.released.contains(key_code);
}
pub fn getKeyDownDuration(self: Frame, key_code: KeyCode) ?f32 {
if (!self.isKeyDown(key_code)) {
return null;
}
const pressed_at_ns = self.keyboard.pressed_at.get(key_code).?;
const duration_ns = self.time_ns - pressed_at_ns;
return @as(f32, @floatFromInt(duration_ns)) / std.time.ns_per_s;
}
pub fn getKeyState(self: Frame, key_code: KeyCode) KeyState {
return KeyState{
.down = self.isKeyDown(key_code),
.released = self.isKeyReleased(key_code),
.pressed = self.isKeyPressed(key_code),
.down_duration = self.getKeyDownDuration(key_code)
};
}
};
pub const Init = struct {
gpa: std.mem.Allocator,
seed: u64
};
pub const Callbacks = struct {
pub const State = *anyopaque;
pub const InitFn = *const fn(opts: *const Init) callconv(.c) State;
pub const TickFn = *const fn(state: State, frame: *Frame) callconv(.c) void;
pub const DeinitFn = *const fn(state: State) callconv(.c) void;
pub const DebugFn = *const fn(state: State, imgui: *ImGui) callconv(.c) void;
init: InitFn,
tick: TickFn,
deinit: DeinitFn,
debug: if (build_options.has_imgui) DebugFn else void
};
pub fn exportCallbacksIfNeeded(
comptime init: Callbacks.InitFn,
comptime tick: Callbacks.TickFn,
comptime deinit: Callbacks.DeinitFn,
comptime debug: Callbacks.DebugFn
) void {
if (build_options.statically_linked) {
return;
}
@export(init, .{ .name = "init", .linkage = .strong });
@export(tick, .{ .name = "tick", .linkage = .strong });
@export(deinit, .{ .name = "deinit", .linkage = .strong });
if (build_options.has_imgui) {
@export(debug, .{ .name = "debug", .linkage = .strong });
}
}