split engine into lib and runtime

This commit is contained in:
Rokas Puzonas 2026-02-08 00:33:17 +02:00
parent e5fccc21f2
commit 6407263540
22 changed files with 956 additions and 660 deletions

View File

@ -23,13 +23,6 @@ const InitOptions = struct {
pub fn init(outer_builder: *std.Build, opts: InitOptions) void { pub fn init(outer_builder: *std.Build, opts: InitOptions) void {
const b = opts.dep_engine.builder; const b = opts.dep_engine.builder;
const engine_module = createEngineModule(b, .{
.target = opts.target,
.optimize = opts.optimize,
.has_tracy = opts.has_tracy,
.has_imgui = opts.has_imgui
});
opts.root_module.resolved_target = opts.target; opts.root_module.resolved_target = opts.target;
opts.root_module.optimize = opts.optimize; opts.root_module.optimize = opts.optimize;
const game_lib = b.addLibrary(.{ const game_lib = b.addLibrary(.{
@ -37,14 +30,25 @@ pub fn init(outer_builder: *std.Build, opts: InitOptions) void {
.root_module = opts.root_module, .root_module = opts.root_module,
.linkage = .static, .linkage = .static,
}); });
opts.root_module.addImport("engine", engine_module.root_module); const engine_lib = b.createModule(.{
.root_source_file = b.path("src/lib/root.zig")
});
opts.root_module.addImport("engine", engine_lib);
const engine_module = createEngineModule(b, .{
.target = opts.target,
.optimize = opts.optimize,
.has_tracy = opts.has_tracy,
.has_imgui = opts.has_imgui
});
const mod_main = b.createModule(.{ const mod_main = b.createModule(.{
.root_source_file = b.path("src/main.zig"), .root_source_file = b.path("src/runtime/main.zig"),
.target = opts.target, .target = opts.target,
.optimize = opts.optimize, .optimize = opts.optimize,
.link_libc = true .link_libc = true
}); });
mod_main.addImport("lib", engine_lib);
mod_main.addImport("engine", engine_module.root_module); mod_main.addImport("engine", engine_module.root_module);
mod_main.linkLibrary(game_lib); mod_main.linkLibrary(game_lib);
@ -110,11 +114,15 @@ fn createEngineModule(b: *std.Build, opts: EngineModule.Options) EngineModule {
} }
const mod = b.createModule(.{ const mod = b.createModule(.{
.root_source_file = b.path("src/root.zig"), .root_source_file = b.path("src/runtime/root.zig"),
.target = opts.target, .target = opts.target,
.optimize = opts.optimize, .optimize = opts.optimize,
.link_libc = true .link_libc = true
}); });
const lib = b.createModule(.{
.root_source_file = b.path("src/lib/root.zig")
});
mod.addImport("lib", lib);
const dep_sokol = b.dependency("sokol", .{ const dep_sokol = b.dependency("sokol", .{
.target = opts.target, .target = opts.target,
@ -169,7 +177,7 @@ fn createEngineModule(b: *std.Build, opts: EngineModule.Options) EngineModule {
mod.addIncludePath(dep_sokol_c.path("util")); mod.addIncludePath(dep_sokol_c.path("util"));
mod.addCSourceFile(.{ mod.addCSourceFile(.{
.file = b.path("src/fontstash/sokol_fontstash_impl.c"), .file = b.path("src/runtime/fontstash/sokol_fontstash_impl.c"),
.flags = cflags.items .flags = cflags.items
}); });
} }

View File

@ -1,291 +0,0 @@
const std = @import("std");
const build_options = @import("build_options");
const log = std.log.scoped(.engine);
const InputSystem = @import("./input.zig");
const KeyCode = InputSystem.KeyCode;
const MouseButton = InputSystem.MouseButton;
const AudioSystem = @import("./audio/root.zig");
const AudioData = AudioSystem.Data;
const AudioCommand = AudioSystem.Command;
const GraphicsSystem = @import("./graphics.zig");
const TextureId = GraphicsSystem.TextureId;
const GraphicsCommand = GraphicsSystem.Command;
const Font = GraphicsSystem.Font;
const Sprite = GraphicsSystem.Sprite;
const Math = @import("./math.zig");
const Rect = Math.Rect;
const Vec4 = Math.Vec4;
const Vec2 = Math.Vec2;
const rgb = Math.rgb;
pub const Nanoseconds = u64;
const Frame = @This();
pub const Input = struct {
keyboard: InputSystem.ButtonStateSet(KeyCode),
mouse_button: InputSystem.ButtonStateSet(MouseButton),
mouse_position: ?Vec2,
pub const empty = Input{
.keyboard = .empty,
.mouse_button = .empty,
.mouse_position = null,
};
};
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 Audio = struct {
commands: std.ArrayList(AudioCommand),
pub const empty = Audio{
.commands = .empty
};
};
pub const Graphics = struct {
clear_color: Vec4,
screen_size: Vec2,
canvas_size: ?Vec2,
scissor_stack: std.ArrayList(Rect),
commands: std.ArrayList(GraphicsCommand),
pub const empty = Graphics{
.clear_color = rgb(0, 0, 0),
.screen_size = .init(0, 0),
.canvas_size = null,
.scissor_stack = .empty,
.commands = .empty
};
};
arena: std.heap.ArenaAllocator,
time_ns: Nanoseconds,
dt_ns: Nanoseconds,
input: Input,
audio: Audio,
graphics: Graphics,
show_debug: bool,
hide_cursor: bool,
pub fn init(self: *Frame, gpa: std.mem.Allocator) void {
self.* = Frame{
.arena = std.heap.ArenaAllocator.init(gpa),
.time_ns = 0,
.dt_ns = 0,
.input = .empty,
.audio = .empty,
.graphics = .empty,
.show_debug = false,
.hide_cursor = false
};
}
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;
}
pub fn isKeyDown(self: Frame, key_code: KeyCode) bool {
const keyboard = &self.input.keyboard;
return keyboard.down.contains(key_code);
}
pub fn getKeyDownDuration(self: Frame, key_code: KeyCode) ?f32 {
if (!self.isKeyDown(key_code)) {
return null;
}
const keyboard = &self.input.keyboard;
const pressed_at_ns = 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 isKeyPressed(self: Frame, key_code: KeyCode) bool {
const keyboard = &self.input.keyboard;
return keyboard.pressed.contains(key_code);
}
pub fn isKeyReleased(self: Frame, key_code: KeyCode) bool {
const keyboard = &self.input.keyboard;
return keyboard.released.contains(key_code);
}
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 fn isMousePressed(self: Frame, button: MouseButton) bool {
return self.input.mouse_button.pressed.contains(button);
}
pub fn isMouseDown(self: Frame, button: MouseButton) bool {
return self.input.mouse_button.down.contains(button);
}
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 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 prependGraphicsCommand(self: *Frame, command: GraphicsCommand) void {
const arena = self.arena.allocator();
self.graphics.commands.insert(arena, 0, command) catch |e|{
log.warn("Failed to push graphics command: {}", .{e});
};
}
pub fn playAudio(self: *Frame, options: AudioCommand.Play) void {
self.pushAudioCommand(.{
.play = options,
});
}
pub fn pushScissor(self: *Frame, rect: Rect) void {
const arena = self.arena.allocator();
self.graphics.scissor_stack.append(arena, rect) catch |e| {
log.warn("Failed to push scissor region: {}", .{e});
return;
};
self.pushGraphicsCommand(.{
.set_scissor = rect
});
}
pub fn popScissor(self: *Frame) void {
_ = self.graphics.scissor_stack.pop().?;
const rect = self.graphics.scissor_stack.getLast();
self.pushGraphicsCommand(.{
.set_scissor = rect
});
}
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 const DrawTextOptions = struct {
font: Font.Id,
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,
}
});
}
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 = {}
});
}

View File

@ -1,124 +0,0 @@
const std = @import("std");
const sokol = @import("sokol");
const Frame = @import("./frame.zig");
const Nanoseconds = Frame.Nanoseconds;
const Math = @import("./math.zig");
const Vec2 = Math.Vec2;
const Input = @This();
const SokolKeyCodeInfo = @typeInfo(sokol.app.Keycode);
pub const KeyCode = @Type(.{
.@"enum" = .{
.tag_type = std.math.IntFittingRange(0, sokol.app.max_keycodes-1),
.decls = SokolKeyCodeInfo.@"enum".decls,
.fields = SokolKeyCodeInfo.@"enum".fields,
.is_exhaustive = false
}
});
pub const MouseButton = enum(i3) {
left = @intFromEnum(sokol.app.Mousebutton.LEFT),
right = @intFromEnum(sokol.app.Mousebutton.RIGHT),
middle = @intFromEnum(sokol.app.Mousebutton.MIDDLE),
_
};
pub 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(.{}),
};
fn press(self: *Self, button: E, now: Nanoseconds) void {
self.pressed_at.put(button, now);
self.pressed.insert(button);
self.down.insert(button);
}
fn release(self: *Self, button: E) void {
self.down.remove(button);
self.released.insert(button);
self.pressed_at.remove(button);
}
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 Event = union(enum) {
mouse_pressed: struct {
button: MouseButton,
position: Vec2,
},
mouse_released: struct {
button: MouseButton,
position: Vec2,
},
mouse_move: Vec2,
mouse_enter: Vec2,
mouse_leave,
mouse_scroll: Vec2,
key_pressed: struct {
code: KeyCode,
repeat: bool
},
key_released: Input.KeyCode,
window_resize,
char: u21,
};
pub fn processEvent(frame: *Frame, event: Event) void {
const input = &frame.input;
switch (event) {
.key_pressed => |opts| {
if (!opts.repeat) {
input.keyboard.press(opts.code, frame.time_ns);
}
},
.key_released => |key_code| {
input.keyboard.release(key_code);
},
.mouse_leave => {
input.keyboard.releaseAll();
input.mouse_position = null;
input.mouse_button = .empty;
},
.mouse_enter => |pos| {
input.mouse_position = pos;
},
.mouse_move => |pos| {
input.mouse_position = pos;
},
.mouse_pressed => |opts| {
input.mouse_position = opts.position;
input.mouse_button.press(opts.button, frame.time_ns);
},
.mouse_released => |opts| {
input.mouse_position = opts.position;
input.mouse_button.release(opts.button);
},
else => {}
}
}

37
engine/src/lib/imgui.zig Normal file
View File

@ -0,0 +1,37 @@
const std = @import("std");
const log = std.log.scoped(.imgui);
const ImGui = @This();
const Command = union(enum) {
text: [:0]const u8
};
arena_state: std.heap.ArenaAllocator,
commands: std.ArrayList(Command),
pub fn init(gpa: std.mem.Allocator) ImGui {
return ImGui{
.arena_state = .init(gpa),
.commands = .empty
};
}
pub fn deinit(self: *ImGui) void {
self.arena_state.deinit();
self.* = undefined;
}
pub fn text(self: *ImGui, comptime fmt: []const u8, args: anytype) void {
const arena = self.arena_state.allocator();
const formatted = std.fmt.allocPrintSentinel(arena, fmt, args, 0) catch |e| {
log.warn("formatting text failed: {}", .{e});
return;
};
self.commands.append(arena, .{ .text = formatted }) catch |e| {
log.warn("appending command failed: {}", .{e});
return;
};
}

473
engine/src/lib/root.zig Normal file
View File

@ -0,0 +1,473 @@
const std = @import("std");
const log = std.log.scoped(.engine);
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,
show_debug: bool,
pub fn init(self: *Frame, gpa: std.mem.Allocator) void {
self.* = 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),
.show_debug = false,
.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
};

View File

@ -2,6 +2,7 @@ const std = @import("std");
const assert = std.debug.assert; const assert = std.debug.assert;
const STBVorbis = @import("stb_vorbis"); const STBVorbis = @import("stb_vorbis");
const Lib = @import("lib");
pub const Data = union(enum) { pub const Data = union(enum) {
raw: struct { raw: struct {
@ -70,5 +71,5 @@ pub const Data = union(enum) {
}; };
} }
pub const Id = enum (u16) { _ }; pub const Id = Lib.AudioId;
}; };

View File

@ -6,26 +6,21 @@ const Allocator = std.mem.Allocator;
const sokol = @import("sokol"); const sokol = @import("sokol");
const saudio = sokol.audio; const saudio = sokol.audio;
const Lib = @import("lib");
const AudioData = @import("./data.zig").Data; const AudioData = @import("./data.zig").Data;
pub const Store = @import("./store.zig"); pub const Store = @import("./store.zig");
const Mixer = @This(); const Mixer = @This();
pub const Instance = struct { pub const Instance = struct {
data_id: AudioData.Id, data_id: Lib.AudioId,
volume: f32 = 0, volume: f32 = 0,
cursor: u32 = 0, cursor: u32 = 0,
}; };
pub const Command = union(enum) { pub const Command = Lib.AudioCommand;
pub const Play = struct {
id: AudioData.Id,
volume: f32 = 1
};
play: Play, pub const RingBuffer = struct {
pub const RingBuffer = struct {
// TODO: This ring buffer will work in a single producer single consumer configuration // TODO: This ring buffer will work in a single producer single consumer configuration
// For my game this will be good enough // For my game this will be good enough
@ -59,13 +54,12 @@ pub const Command = union(enum) {
self.tail.store(@mod(tail + 1, self.items.len), .monotonic); self.tail.store(@mod(tail + 1, self.items.len), .monotonic);
return result; return result;
} }
};
}; };
// TODO: Tracks // TODO: Tracks
instances: std.ArrayList(Instance), instances: std.ArrayList(Instance),
commands: Command.RingBuffer, commands: RingBuffer,
working_buffer: []f32, working_buffer: []f32,
pub fn init( pub fn init(

View File

@ -4,7 +4,7 @@ const assert = std.debug.assert;
const tracy = @import("tracy"); const tracy = @import("tracy");
const Math = @import("../math.zig"); const Math = @import("lib").Math;
const STBVorbis = @import("stb_vorbis"); const STBVorbis = @import("stb_vorbis");
pub const Data = @import("./data.zig").Data; pub const Data = @import("./data.zig").Data;

View File

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const assert = std.debug.assert; const assert = std.debug.assert;
const Math = @import("../math.zig"); const Math = @import("lib").Math;
const STBVorbis = @import("stb_vorbis"); const STBVorbis = @import("stb_vorbis");
const AudioData = @import("./data.zig").Data; const AudioData = @import("./data.zig").Data;

View File

@ -6,7 +6,7 @@ const sapp = sokol.app;
const simgui = sokol.imgui; const simgui = sokol.imgui;
const sgl = sokol.gl; const sgl = sokol.gl;
const Math = @import("./math.zig"); const Math = @import("lib").Math;
const Vec2 = Math.Vec2; const Vec2 = Math.Vec2;
const Vec4 = Math.Vec4; const Vec4 = Math.Vec4;
const rgb = Math.rgb; const rgb = Math.rgb;
@ -21,7 +21,8 @@ const tracy = @import("tracy");
const fontstash = @import("./fontstash/root.zig"); const fontstash = @import("./fontstash/root.zig");
pub const Font = fontstash.Font; pub const Font = fontstash.Font;
const GraphicsFrame = @import("./frame.zig").Graphics; const Lib = @import("lib");
const Command = Lib.GraphicsCommand;
const Graphics = @This(); const Graphics = @This();
@ -43,37 +44,6 @@ const Options = struct {
imgui_font: ?ImguiFont = null imgui_font: ?ImguiFont = null
}; };
pub const Command = union(enum) {
pub const DrawRectangle = struct {
rect: Rect,
color: Vec4,
sprite: ?Sprite = null,
rotation: f32 = 0,
origin: Vec2 = .init(0, 0),
};
set_scissor: Rect,
draw_rectangle: DrawRectangle,
draw_line: struct {
pos1: Vec2,
pos2: Vec2,
color: Vec4,
width: f32
},
draw_text: struct {
pos: Vec2,
text: []const u8,
font: Font.Id,
size: f32,
color: Vec4,
},
push_transformation: struct {
translation: Vec2,
scale: Vec2
},
pop_transformation: void
};
const Texture = struct { const Texture = struct {
image: sg.Image, image: sg.Image,
view: sg.View, view: sg.View,
@ -84,20 +54,16 @@ const Texture = struct {
height: u32, height: u32,
}; };
const Id = enum(u32) { _ };
const Data = struct { const Data = struct {
width: u32, width: u32,
height: u32, height: u32,
rgba: [*]u8 rgba: [*]u8
}; };
}; };
pub const TextureId = Texture.Id; pub const TextureId = Lib.TextureId;
pub const TextureInfo = Texture.Info; pub const TextureInfo = Texture.Info;
pub const Sprite = struct { pub const Sprite = Lib.Sprite;
texture: TextureId,
uv: Rect,
};
main_pipeline: sgl.Pipeline, main_pipeline: sgl.Pipeline,
linear_sampler: sg.Sampler, linear_sampler: sg.Sampler,
@ -108,6 +74,11 @@ textures: std.ArrayList(Texture) = .empty,
scale_stack_buffer: [32]Vec2 = undefined, scale_stack_buffer: [32]Vec2 = undefined,
scale_stack: std.ArrayList(Vec2) = .empty, scale_stack: std.ArrayList(Vec2) = .empty,
scissor_stack_buffer: [32]Rect = undefined,
scissor_stack: std.ArrayList(Rect) = .empty,
font_id_lookup: std.AutoArrayHashMapUnmanaged(Lib.FontId, fontstash.Font.Id) = .empty,
pub fn init(self: *Graphics, options: Options) !void { pub fn init(self: *Graphics, options: Options) !void {
sg.setup(.{ sg.setup(.{
.logger = options.logger, .logger = options.logger,
@ -187,6 +158,7 @@ pub fn init(self: *Graphics, options: Options) !void {
} }
pub fn deinit(self: *Graphics, gpa: std.mem.Allocator) void { pub fn deinit(self: *Graphics, gpa: std.mem.Allocator) void {
self.font_id_lookup.deinit(gpa);
self.textures.deinit(gpa); self.textures.deinit(gpa);
imgui.shutdown(); imgui.shutdown();
self.font_context.deinit(); self.font_context.deinit();
@ -206,12 +178,29 @@ pub fn drawCommand(self: *Graphics, command: Command) void {
.draw_rectangle => |opts| { .draw_rectangle => |opts| {
self.drawRectangle(opts); self.drawRectangle(opts);
}, },
.set_scissor => |opts| { .push_scissor => |rect| {
self.scissor_stack.appendBounded(rect) catch |e| {
log.warn("Failed to push scissor region: {}", .{e});
return;
};
sgl.scissorRectf( sgl.scissorRectf(
opts.pos.x, rect.pos.x,
opts.pos.y, rect.pos.y,
opts.size.x, rect.size.x,
opts.size.y, rect.size.y,
true
);
},
.pop_scissor => {
_ = self.scissor_stack.pop().?;
const rect = self.scissor_stack.getLast();
sgl.scissorRectf(
rect.pos.x,
rect.pos.y,
rect.size.x,
rect.size.y,
true true
); );
}, },
@ -231,7 +220,11 @@ pub fn drawCommand(self: *Graphics, command: Command) void {
sgl.scale(1/font_resolution_scale.x, 1/font_resolution_scale.y, 1); sgl.scale(1/font_resolution_scale.x, 1/font_resolution_scale.y, 1);
self.font_context.setFont(opts.font); const font_id = self.font_id_lookup.get(opts.font) orelse {
log.warn("Attempt to use font that doesn't exist", .{});
return;
};
self.font_context.setFont(font_id);
self.font_context.setSize(opts.size * font_resolution_scale.y); self.font_context.setSize(opts.size * font_resolution_scale.y);
self.font_context.setAlign(.{ .x = .left, .y = .top }); self.font_context.setAlign(.{ .x = .left, .y = .top });
self.font_context.setSpacing(0); self.font_context.setSpacing(0);
@ -271,6 +264,9 @@ pub fn beginFrame(self: *Graphics) void {
self.scale_stack = .initBuffer(&self.scale_stack_buffer); self.scale_stack = .initBuffer(&self.scale_stack_buffer);
self.scale_stack.appendAssumeCapacity(.init(1, 1)); self.scale_stack.appendAssumeCapacity(.init(1, 1));
self.scissor_stack = .initBuffer(&self.scissor_stack_buffer);
self.scissor_stack.appendAssumeCapacity(.init(0, 0, sapp.widthf(), sapp.heightf()));
self.font_context.clearState(); self.font_context.clearState();
sgl.defaults(); sgl.defaults();
sgl.matrixModeProjection(); sgl.matrixModeProjection();
@ -310,7 +306,7 @@ pub fn endFrame(self: *Graphics, clear_color: Vec4) void {
sg.commit(); sg.commit();
} }
fn pushTransform(self: *Graphics, translation: Vec2, scale: Vec2) void { pub fn pushTransform(self: *Graphics, translation: Vec2, scale: Vec2) void {
sgl.pushMatrix(); sgl.pushMatrix();
sgl.translate(translation.x, translation.y, 0); sgl.translate(translation.x, translation.y, 0);
sgl.scale(scale.x, scale.y, 1); sgl.scale(scale.x, scale.y, 1);
@ -318,7 +314,7 @@ fn pushTransform(self: *Graphics, translation: Vec2, scale: Vec2) void {
self.scale_stack.appendAssumeCapacity(self.scale_stack.getLast().multiply(scale)); self.scale_stack.appendAssumeCapacity(self.scale_stack.getLast().multiply(scale));
} }
fn popTransform(self: *Graphics) void { pub fn popTransform(self: *Graphics) void {
sgl.popMatrix(); sgl.popMatrix();
_ = self.scale_stack.pop().?; _ = self.scale_stack.pop().?;
} }
@ -357,7 +353,7 @@ fn drawQuadNoUVs(quad: [4]Vec2, color: Vec4) void {
} }
} }
fn drawRectangle(self: *Graphics, opts: Command.DrawRectangle) void { pub fn drawRectangle(self: *Graphics, opts: Command.DrawRectangle) void {
const pos = opts.rect.pos; const pos = opts.rect.pos;
const size = opts.rect.size; const size = opts.rect.size;

View File

@ -1,5 +1,6 @@
const std = @import("std"); const std = @import("std");
const Math = @import("./math.zig"); const Lib = @import("lib");
const Math = Lib.Math;
const build_options = @import("build_options"); const build_options = @import("build_options");
pub const ig = @import("cimgui"); pub const ig = @import("cimgui");
const Vec2 = Math.Vec2; const Vec2 = Math.Vec2;

View File

@ -0,0 +1,210 @@
const std = @import("std");
const assert = std.debug.assert;
const sokol = @import("sokol");
const lib = @import("lib");
const Frame = lib.Frame;
const Nanoseconds = lib.Nanoseconds;
const Vec2 = lib.Math.Vec2;
const Input = @This();
const SokolKeyCodeInfo = @typeInfo(sokol.app.Keycode);
pub const Event = union(enum) {
mouse_pressed: struct {
button: lib.MouseButton,
position: Vec2,
},
mouse_released: struct {
button: lib.MouseButton,
position: Vec2,
},
mouse_move: Vec2,
mouse_enter: Vec2,
mouse_leave,
mouse_scroll: Vec2,
key_pressed: struct {
code: lib.KeyCode,
repeat: bool
},
key_released: lib.KeyCode,
window_resize,
char: u21,
};
mouse_position: ?Vec2 = null,
pub fn processEvent(self: *Input, frame: *Frame, event: Event) void {
switch (event) {
.key_pressed => |opts| {
if (!opts.repeat) {
frame.keyboard.press(opts.code, frame.time_ns);
}
},
.key_released => |key_code| {
frame.keyboard.release(key_code);
},
.mouse_leave => {
frame.keyboard.releaseAll();
assert(self.mouse_position != null);
self.mouse_position = null;
frame.mouse_button = .empty;
},
.mouse_enter => |pos| {
assert(self.mouse_position == null);
self.mouse_position = pos;
},
.mouse_move => |pos| {
assert(self.mouse_position != null);
self.mouse_position = pos;
},
.mouse_pressed => |opts| {
assert(self.mouse_position != null);
self.mouse_position = opts.position;
frame.mouse_button.press(opts.button, frame.time_ns);
},
.mouse_released => |opts| {
assert(self.mouse_position != null);
self.mouse_position = opts.position;
frame.mouse_button.release(opts.button);
},
else => {}
}
}
pub fn getKeyCodeFromSokol(key_code: sokol.app.Keycode) ?lib.KeyCode {
return switch (key_code) {
.SPACE => .SPACE,
.APOSTROPHE => .APOSTROPHE,
.COMMA => .COMMA,
.MINUS => .MINUS,
.PERIOD => .PERIOD,
.SLASH => .SLASH,
._0 => ._0,
._1 => ._1,
._2 => ._2,
._3 => ._3,
._4 => ._4,
._5 => ._5,
._6 => ._6,
._7 => ._7,
._8 => ._8,
._9 => ._9,
.SEMICOLON => .SEMICOLON,
.EQUAL => .EQUAL,
.A => .A,
.B => .B,
.C => .C,
.D => .D,
.E => .E,
.F => .F,
.G => .G,
.H => .H,
.I => .I,
.J => .J,
.K => .K,
.L => .L,
.M => .M,
.N => .N,
.O => .O,
.P => .P,
.Q => .Q,
.R => .R,
.S => .S,
.T => .T,
.U => .U,
.V => .V,
.W => .W,
.X => .X,
.Y => .Y,
.Z => .Z,
.LEFT_BRACKET => .LEFT_BRACKET,
.BACKSLASH => .BACKSLASH,
.RIGHT_BRACKET => .RIGHT_BRACKET,
.GRAVE_ACCENT => .GRAVE_ACCENT,
.WORLD_1 => .WORLD_1,
.WORLD_2 => .WORLD_2,
.ESCAPE => .ESCAPE,
.ENTER => .ENTER,
.TAB => .TAB,
.BACKSPACE => .BACKSPACE,
.INSERT => .INSERT,
.DELETE => .DELETE,
.RIGHT => .RIGHT,
.LEFT => .LEFT,
.DOWN => .DOWN,
.UP => .UP,
.PAGE_UP => .PAGE_UP,
.PAGE_DOWN => .PAGE_DOWN,
.HOME => .HOME,
.END => .END,
.CAPS_LOCK => .CAPS_LOCK,
.SCROLL_LOCK => .SCROLL_LOCK,
.NUM_LOCK => .NUM_LOCK,
.PRINT_SCREEN => .PRINT_SCREEN,
.PAUSE => .PAUSE,
.F1 => .F1,
.F2 => .F2,
.F3 => .F3,
.F4 => .F4,
.F5 => .F5,
.F6 => .F6,
.F7 => .F7,
.F8 => .F8,
.F9 => .F9,
.F10 => .F10,
.F11 => .F11,
.F12 => .F12,
.F13 => .F13,
.F14 => .F14,
.F15 => .F15,
.F16 => .F16,
.F17 => .F17,
.F18 => .F18,
.F19 => .F19,
.F20 => .F20,
.F21 => .F21,
.F22 => .F22,
.F23 => .F23,
.F24 => .F24,
.F25 => .F25,
.KP_0 => .KP_0,
.KP_1 => .KP_1,
.KP_2 => .KP_2,
.KP_3 => .KP_3,
.KP_4 => .KP_4,
.KP_5 => .KP_5,
.KP_6 => .KP_6,
.KP_7 => .KP_7,
.KP_8 => .KP_8,
.KP_9 => .KP_9,
.KP_DECIMAL => .KP_DECIMAL,
.KP_DIVIDE => .KP_DIVIDE,
.KP_MULTIPLY => .KP_MULTIPLY,
.KP_SUBTRACT => .KP_SUBTRACT,
.KP_ADD => .KP_ADD,
.KP_ENTER => .KP_ENTER,
.KP_EQUAL => .KP_EQUAL,
.LEFT_SHIFT => .LEFT_SHIFT,
.LEFT_CONTROL => .LEFT_CONTROL,
.LEFT_ALT => .LEFT_ALT,
.LEFT_SUPER => .LEFT_SUPER,
.RIGHT_SHIFT => .RIGHT_SHIFT,
.RIGHT_CONTROL => .RIGHT_CONTROL,
.RIGHT_ALT => .RIGHT_ALT,
.RIGHT_SUPER => .RIGHT_SUPER,
.MENU => .MENU,
else => null
};
}
pub fn getMouseButtonFromSokol(mouse_button: sokol.app.Mousebutton) ?lib.MouseButton {
return switch (mouse_button) {
.RIGHT => .right,
.MIDDLE => .middle,
.LEFT => .left,
else => null
};
}

View File

@ -5,14 +5,8 @@ const assert = std.debug.assert;
const sokol = @import("sokol"); const sokol = @import("sokol");
const sapp = sokol.app; const sapp = sokol.app;
pub const Math = @import("./math.zig");
pub const Vec2 = Math.Vec2;
const rgb = Math.rgb;
pub const Input = @import("./input.zig"); pub const Input = @import("./input.zig");
pub const Frame = @import("./frame.zig");
const ScreenScalar = @import("./screen_scaler.zig"); const ScreenScalar = @import("./screen_scaler.zig");
pub const imgui = @import("./imgui.zig"); pub const imgui = @import("./imgui.zig");
pub const Graphics = @import("./graphics.zig"); pub const Graphics = @import("./graphics.zig");
@ -23,37 +17,44 @@ const STBImage = @import("stb_image");
const Gfx = Graphics; const Gfx = Graphics;
const Lib = @import("lib");
pub const Math = Lib.Math;
pub const Vec2 = Math.Vec2;
const rgb = Math.rgb;
const GameCallbacks = struct { const GameCallbacks = struct {
init: *const fn(opts: *const Engine.InitOptions) callconv(.c) void, init: *const fn(opts: *const Lib.Init) callconv(.c) void,
tick: *const fn(frame: *Frame) callconv(.c) void, tick: *const fn(frame: *Lib.Frame) callconv(.c) void,
deinit: *const fn() callconv(.c) void, deinit: *const fn() callconv(.c) void,
debug: *const fn() callconv(.c) void, debug: *const fn(imgui: *Lib.ImGui) callconv(.c) void,
}; };
extern fn init(opts: *const Engine.InitOptions) void; extern fn init(opts: *const Lib.Init) void;
extern fn tick(frame: *Frame) void; extern fn tick(frame: *Lib.Frame) void;
extern fn deinit() void; extern fn deinit() void;
extern fn debug() void; extern fn debug(imgui: *Lib.ImGui) void;
const static_game_callbacks = GameCallbacks{
.init = init,
.deinit = deinit,
.tick = tick,
.debug = debug,
};
const Engine = @This(); const Engine = @This();
pub const Nanoseconds = u64; pub const Nanoseconds = u64;
pub const InitOptions = struct {
gpa: std.mem.Allocator,
seed: u64
};
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
started_at: std.time.Instant, started_at: std.time.Instant,
mouse_inside: bool,
last_frame_at: Nanoseconds, last_frame_at: Nanoseconds,
graphics: Graphics, graphics: Graphics,
input: Input,
game_callbacks: GameCallbacks, game_callbacks: GameCallbacks,
frame: Frame, frame: Lib.Frame,
canvas_size: ?Vec2 = null, previous_tick_canvas_size: ?Vec2 = null,
const RunOptions = struct { const RunOptions = struct {
window_title: [*:0]const u8 = "Game", window_title: [*:0]const u8 = "Game",
@ -65,16 +66,11 @@ pub fn run(self: *Engine, opts: RunOptions) !void {
self.* = Engine{ self.* = Engine{
.allocator = undefined, .allocator = undefined,
.started_at = std.time.Instant.now() catch @panic("Instant.now() unsupported"), .started_at = std.time.Instant.now() catch @panic("Instant.now() unsupported"),
.mouse_inside = false,
.last_frame_at = 0, .last_frame_at = 0,
.frame = undefined, .frame = undefined,
.graphics = undefined, .graphics = undefined,
.game_callbacks = .{ .game_callbacks = static_game_callbacks,
.init = init, .input = .{}
.deinit = deinit,
.tick = tick,
.debug = debug,
}
}; };
var debug_allocator: std.heap.DebugAllocator(.{}) = .init; var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
@ -157,7 +153,7 @@ fn sokolInit(self: *Engine) !void {
const seed: u64 = @bitCast(std.time.milliTimestamp()); const seed: u64 = @bitCast(std.time.milliTimestamp());
const opts = InitOptions{ const opts = Lib.Init{
.gpa = self.allocator, .gpa = self.allocator,
.seed = seed, .seed = seed,
}; };
@ -188,83 +184,71 @@ fn sokolFrame(self: *Engine) !void {
const screen_size = Vec2.init(sapp.widthf(), sapp.heightf()); const screen_size = Vec2.init(sapp.widthf(), sapp.heightf());
var revert_mouse_position: ?Vec2 = null; var maybe_screen_scaler: ?ScreenScalar = null;
if (self.canvas_size) |canvas_size| { if (self.previous_tick_canvas_size) |canvas_size| {
if (self.frame.input.mouse_position) |mouse| { maybe_screen_scaler = ScreenScalar.init(screen_size, canvas_size);
const transform = ScreenScalar.init(screen_size, canvas_size);
revert_mouse_position = mouse;
self.frame.input.mouse_position = mouse.sub(transform.translation).divideScalar(transform.scale);
}
} }
{ {
_ = frame.arena.reset(.retain_capacity); _ = frame.arena.reset(.retain_capacity);
const arena = frame.arena.allocator(); const arena = frame.arena.allocator();
const audio_commands_capacity = frame.audio.commands.capacity; const audio_commands_capacity = frame.audio_commands.capacity;
frame.audio = .empty; frame.audio_commands = .empty;
try frame.audio.commands.ensureTotalCapacity(arena, audio_commands_capacity); try frame.audio_commands.ensureTotalCapacity(arena, audio_commands_capacity);
const graphics_commands_capacity = frame.graphics.commands.capacity; const graphics_commands_capacity = frame.graphics_commands.capacity;
const scissor_stack_capacity = frame.graphics.scissor_stack.capacity; frame.graphics_commands = .empty;
frame.graphics = .empty; try frame.graphics_commands.ensureTotalCapacity(arena, graphics_commands_capacity);
frame.graphics.screen_size = screen_size;
try frame.graphics.commands.ensureTotalCapacity(arena, graphics_commands_capacity);
try frame.graphics.scissor_stack.ensureTotalCapacity(arena, scissor_stack_capacity);
frame.pushScissor(.init(0, 0, sapp.widthf(), sapp.heightf()));
frame.screen_size = screen_size;
frame.time_ns = time_passed; frame.time_ns = time_passed;
frame.dt_ns = time_passed - self.last_frame_at; frame.dt_ns = time_passed - self.last_frame_at;
frame.hide_cursor = false;
self.game_callbacks.tick(&self.frame); frame.mouse_position = self.input.mouse_position;
if (maybe_screen_scaler) |screen_scaler| {
screen_scaler.push(frame);
frame.input.keyboard.pressed = .initEmpty(); if (frame.mouse_position) |mouse_position| {
frame.input.keyboard.released = .initEmpty(); frame.mouse_position = mouse_position.sub(screen_scaler.translation).divideScalar(screen_scaler.scale);
frame.input.mouse_button.pressed = .initEmpty(); }
frame.input.mouse_button.released = .initEmpty();
} }
if (self.canvas_size) |canvas_size| { self.game_callbacks.tick(frame);
const transform = ScreenScalar.init(
screen_size, if (maybe_screen_scaler) |screen_scaler| {
canvas_size screen_scaler.pop(frame, frame.clear_color);
);
transform.apply(
screen_size,
&self.frame,
rgb(0, 0, 0)
);
} }
sapp.showMouse(!self.frame.hide_cursor); frame.keyboard.pressed = .initEmpty();
frame.keyboard.released = .initEmpty();
frame.mouse_button.pressed = .initEmpty();
frame.mouse_button.released = .initEmpty();
}
// Canvas size modification must always be applied a frame later. // Canvas size modification must always be applied a frame later.
// So that mouse coordinate transformations are consistent. // So that mouse coordinate transformations are consistent.
self.canvas_size = self.frame.graphics.canvas_size; self.previous_tick_canvas_size = self.frame.canvas_size;
sapp.showMouse(!self.frame.hide_cursor);
{ {
self.graphics.beginFrame(); self.graphics.beginFrame();
defer self.graphics.endFrame(frame.graphics.clear_color); defer self.graphics.endFrame(frame.clear_color);
self.graphics.drawCommands(frame.graphics.commands.items); self.graphics.drawCommands(frame.graphics_commands.items);
if (frame.show_debug) { if (frame.show_debug) {
try self.showDebugWindow(&self.frame); try self.showDebugWindow(frame);
} }
} }
for (frame.audio.commands.items) |command| { for (frame.audio_commands.items) |command| {
try Audio.mixer.commands.push(command); try Audio.mixer.commands.push(command);
} }
if (revert_mouse_position) |pos| {
self.frame.input.mouse_position = pos;
}
} }
fn showDebugWindow(self: *Engine, frame: *Frame) !void { fn showDebugWindow(self: *Engine, frame: *Lib.Frame) !void {
if (!imgui.beginWindow(.{ if (!imgui.beginWindow(.{
.name = "Debug", .name = "Debug",
.pos = Vec2.init(20, 20), .pos = Vec2.init(20, 20),
@ -274,24 +258,31 @@ fn showDebugWindow(self: *Engine, frame: *Frame) !void {
} }
defer imgui.endWindow(); defer imgui.endWindow();
if (imgui.beginTabBar("debug")) { _ = imgui.beginTabBar("debug");
defer imgui.endTabBar();
if (imgui.beginTabItem("Game")) { if (imgui.beginTabItem("Game")) {
defer imgui.endTabItem(); defer imgui.endTabItem();
self.game_callbacks.debug();
var imgui_ctx = Lib.ImGui.init(self.allocator);
defer imgui_ctx.deinit();
self.game_callbacks.debug(&imgui_ctx);
for (imgui_ctx.commands.items) |cmd| {
switch (cmd) {
.text => |str| imgui.text(str)
} }
}
}
if (imgui.beginTabItem("Engine")) { if (imgui.beginTabItem("Engine")) {
defer imgui.endTabItem(); defer imgui.endTabItem();
imgui.textFmt("Draw commands: {}\n", .{ imgui.textFmt("Draw commands: {}\n", .{
frame.graphics.commands.items.len, frame.graphics_commands.items.len,
}); });
imgui.textFmt("Audio instances: {}/{}\n", .{ imgui.textFmt("Audio instances: {}/{}\n", .{
Audio.mixer.instances.items.len, Audio.mixer.instances.items.len,
Audio.mixer.instances.capacity Audio.mixer.instances.capacity
}); });
} }
}
} }
fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool { fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
@ -301,20 +292,22 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
const e = e_ptr.*; const e = e_ptr.*;
if (imgui.handleEvent(e)) { if (imgui.handleEvent(e)) {
if (self.mouse_inside) { if (self.input.mouse_position != null) {
Input.processEvent(&self.frame, .{ self.input.processEvent(&self.frame, .{
.mouse_leave = {} .mouse_leave = {}
}); });
} }
self.mouse_inside = false; self.input.mouse_position = null;
return true; return true;
} }
switch (e.type) { blk: switch (e.type) {
.MOUSE_DOWN => { .MOUSE_DOWN => {
Input.processEvent(&self.frame, .{ const mouse_button = Input.getMouseButtonFromSokol(e.mouse_button) orelse break :blk;
self.input.processEvent(&self.frame, .{
.mouse_pressed = .{ .mouse_pressed = .{
.button = @enumFromInt(@intFromEnum(e.mouse_button)), .button = mouse_button,
.position = Vec2.init(e.mouse_x, e.mouse_y) .position = Vec2.init(e.mouse_x, e.mouse_y)
} }
}); });
@ -322,9 +315,11 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
return true; return true;
}, },
.MOUSE_UP => { .MOUSE_UP => {
Input.processEvent(&self.frame, .{ const mouse_button = Input.getMouseButtonFromSokol(e.mouse_button) orelse break :blk;
self.input.processEvent(&self.frame, .{
.mouse_released = .{ .mouse_released = .{
.button = @enumFromInt(@intFromEnum(e.mouse_button)), .button = mouse_button,
.position = Vec2.init(e.mouse_x, e.mouse_y) .position = Vec2.init(e.mouse_x, e.mouse_y)
} }
}); });
@ -332,64 +327,59 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
return true; return true;
}, },
.MOUSE_MOVE => { .MOUSE_MOVE => {
if (!self.mouse_inside) { if (self.input.mouse_position == null) {
Input.processEvent(&self.frame, .{ self.input.processEvent(&self.frame, .{
.mouse_enter = Vec2.init(e.mouse_x, e.mouse_y) .mouse_enter = Vec2.init(e.mouse_x, e.mouse_y)
}); });
} else { } else {
Input.processEvent(&self.frame, .{ self.input.processEvent(&self.frame, .{
.mouse_move = Vec2.init(e.mouse_x, e.mouse_y) .mouse_move = Vec2.init(e.mouse_x, e.mouse_y)
}); });
} }
self.mouse_inside = true;
return true; return true;
}, },
.MOUSE_ENTER => { .MOUSE_ENTER => {
if (!self.mouse_inside) { if (self.input.mouse_position == null) {
Input.processEvent(&self.frame, .{ self.input.processEvent(&self.frame, .{
.mouse_enter = Vec2.init(e.mouse_x, e.mouse_y) .mouse_enter = Vec2.init(e.mouse_x, e.mouse_y)
}); });
} }
self.mouse_inside = true;
return true; return true;
}, },
.RESIZED => { .RESIZED => {
if (self.mouse_inside) { if (self.input.mouse_position != null) {
Input.processEvent(&self.frame, .{ self.input.processEvent(&self.frame, .{
.mouse_leave = {} .mouse_leave = {}
}); });
} }
Input.processEvent(&self.frame, .{ self.input.processEvent(&self.frame, .{
.window_resize = {} .window_resize = {}
}); });
self.mouse_inside = false;
return true; return true;
}, },
.MOUSE_LEAVE => { .MOUSE_LEAVE => {
if (self.mouse_inside) { if (self.input.mouse_position != null) {
Input.processEvent(&self.frame, .{ self.input.processEvent(&self.frame, .{
.mouse_leave = {} .mouse_leave = {}
}); });
} }
self.mouse_inside = false;
return true; return true;
}, },
.MOUSE_SCROLL => { .MOUSE_SCROLL => {
Input.processEvent(&self.frame, .{ self.input.processEvent(&self.frame, .{
.mouse_scroll = Vec2.init(e.scroll_x, e.scroll_y) .mouse_scroll = Vec2.init(e.scroll_x, e.scroll_y)
}); });
return true; return true;
}, },
.KEY_DOWN => { .KEY_DOWN => {
Input.processEvent(&self.frame, .{ const key_code = Input.getKeyCodeFromSokol(e.key_code) orelse break :blk;
self.input.processEvent(&self.frame, .{
.key_pressed = .{ .key_pressed = .{
.code = @enumFromInt(@intFromEnum(e.key_code)), .code = key_code,
.repeat = e.key_repeat .repeat = e.key_repeat
} }
}); });
@ -397,14 +387,16 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
return true; return true;
}, },
.KEY_UP => { .KEY_UP => {
Input.processEvent(&self.frame, .{ const key_code = Input.getKeyCodeFromSokol(e.key_code) orelse break :blk;
.key_released = @enumFromInt(@intFromEnum(e.key_code))
self.input.processEvent(&self.frame, .{
.key_released = key_code
}); });
return true; return true;
}, },
.CHAR => { .CHAR => {
Input.processEvent(&self.frame, .{ self.input.processEvent(&self.frame, .{
.char = @intCast(e.char_code) .char = @intCast(e.char_code)
}); });

View File

@ -1,11 +1,12 @@
const Gfx = @import("./graphics.zig"); const Gfx = @import("./graphics.zig");
const Math = @import("./math.zig"); const Math = @import("lib").Math;
const Vec2 = Math.Vec2; const Vec2 = Math.Vec2;
const Vec4 = Math.Vec4; const Vec4 = Math.Vec4;
const rgb = Math.rgb; const rgb = Math.rgb;
const Frame = @import("./frame.zig"); const Lib = @import("lib");
const Frame = Lib.Frame;
const ScreenScalar = @This(); const ScreenScalar = @This();
@ -34,30 +35,30 @@ pub fn init(window_size: Vec2, canvas_size: Vec2) ScreenScalar {
}; };
} }
pub fn apply(self: ScreenScalar, window_size: Vec2, frame: *Frame, color: Vec4) void { pub fn push(self: ScreenScalar, frame: *Frame) void {
const scale = self.scale; const scale = self.scale;
const translation = self.translation; const translation = self.translation;
frame.prependGraphicsCommand(.{ frame.pushTransform(translation, .init(scale, scale));
.push_transformation = .{ }
.translation = translation,
.scale = .init(scale, scale) pub fn pop(self: ScreenScalar, frame: *Frame, color: Vec4) void {
} const translation = self.translation;
});
frame.popTransform(); frame.popTransform();
frame.drawRectangle(.{ frame.drawRectangle(.{
.rect = .{ .rect = .{
.pos = .init(0, 0), .pos = .init(0, 0),
.size = .init(window_size.x, translation.y), .size = .init(frame.screen_size.x, translation.y),
}, },
.color = color .color = color
}); });
frame.drawRectangle(.{ frame.drawRectangle(.{
.rect = .{ .rect = .{
.pos = .init(0, window_size.y - translation.y), .pos = .init(0, frame.screen_size.y - translation.y),
.size = .init(window_size.x, translation.y), .size = .init(frame.screen_size.x, translation.y),
}, },
.color = color .color = color
}); });
@ -65,15 +66,15 @@ pub fn apply(self: ScreenScalar, window_size: Vec2, frame: *Frame, color: Vec4)
frame.drawRectangle(.{ frame.drawRectangle(.{
.rect = .{ .rect = .{
.pos = .init(0, 0), .pos = .init(0, 0),
.size = .init(translation.x, window_size.y), .size = .init(translation.x, frame.screen_size.y),
}, },
.color = color .color = color
}); });
frame.drawRectangle(.{ frame.drawRectangle(.{
.rect = .{ .rect = .{
.pos = .init(window_size.x - translation.x, 0), .pos = .init(frame.screen_size.x - translation.x, 0),
.size = .init(translation.x, window_size.y), .size = .init(translation.x, frame.screen_size.y),
}, },
.color = color .color = color
}); });

View File

@ -3,11 +3,9 @@ const Allocator = std.mem.Allocator;
const assert = std.debug.assert; const assert = std.debug.assert;
const Engine = @import("engine"); const Engine = @import("engine");
const imgui = Engine.imgui; const FontId = Engine.FontId;
const Vec2 = Engine.Vec2; const Vec2 = Engine.Math.Vec2;
const rgb = Engine.Math.rgb; const rgb = Engine.Math.rgb;
const Gfx = Engine.Graphics;
const Audio = Engine.Audio;
const Game = @This(); const Game = @This();
@ -16,7 +14,7 @@ const FontName = enum {
bold, bold,
italic, italic,
const EnumArray = std.EnumArray(FontName, Gfx.Font.Id); const EnumArray = std.EnumArray(FontName, FontId);
}; };
const State = struct { const State = struct {
@ -29,7 +27,7 @@ const State = struct {
var g_state: *State = undefined; var g_state: *State = undefined;
pub export fn init(opts: *const Engine.InitOptions) void { pub export fn init(opts: *const Engine.Init) void {
const gpa = opts.gpa; const gpa = opts.gpa;
// const font_id_array: FontName.EnumArray = .init(.{ // const font_id_array: FontName.EnumArray = .init(.{
@ -63,7 +61,7 @@ pub export fn tick(frame: *Engine.Frame) void {
const dt = frame.deltaTime(); const dt = frame.deltaTime();
const canvas_size = Vec2.init(100, 100); const canvas_size = Vec2.init(100, 100);
frame.graphics.canvas_size = canvas_size; frame.canvas_size = canvas_size;
if (frame.isKeyPressed(.F3)) { if (frame.isKeyPressed(.F3)) {
frame.show_debug = !frame.show_debug; frame.show_debug = !frame.show_debug;
@ -116,6 +114,6 @@ pub export fn tick(frame: *Engine.Frame) void {
// }); // });
} }
pub export fn debug() void { pub export fn debug(imgui: *Engine.ImGui) void {
imgui.text("Hello World!\n"); imgui.text("Hello World!\n", .{});
} }