diff --git a/engine/build.zig b/engine/build.zig index b70a9fa..96b59ad 100644 --- a/engine/build.zig +++ b/engine/build.zig @@ -23,13 +23,6 @@ const InitOptions = struct { pub fn init(outer_builder: *std.Build, opts: InitOptions) void { 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.optimize = opts.optimize; const game_lib = b.addLibrary(.{ @@ -37,14 +30,25 @@ pub fn init(outer_builder: *std.Build, opts: InitOptions) void { .root_module = opts.root_module, .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(.{ - .root_source_file = b.path("src/main.zig"), + .root_source_file = b.path("src/runtime/main.zig"), .target = opts.target, .optimize = opts.optimize, .link_libc = true }); + mod_main.addImport("lib", engine_lib); mod_main.addImport("engine", engine_module.root_module); mod_main.linkLibrary(game_lib); @@ -110,11 +114,15 @@ fn createEngineModule(b: *std.Build, opts: EngineModule.Options) EngineModule { } const mod = b.createModule(.{ - .root_source_file = b.path("src/root.zig"), + .root_source_file = b.path("src/runtime/root.zig"), .target = opts.target, .optimize = opts.optimize, .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", .{ .target = opts.target, @@ -169,7 +177,7 @@ fn createEngineModule(b: *std.Build, opts: EngineModule.Options) EngineModule { mod.addIncludePath(dep_sokol_c.path("util")); mod.addCSourceFile(.{ - .file = b.path("src/fontstash/sokol_fontstash_impl.c"), + .file = b.path("src/runtime/fontstash/sokol_fontstash_impl.c"), .flags = cflags.items }); } diff --git a/engine/src/frame.zig b/engine/src/frame.zig deleted file mode 100644 index 4b4f2bf..0000000 --- a/engine/src/frame.zig +++ /dev/null @@ -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 = {} - }); -} diff --git a/engine/src/input.zig b/engine/src/input.zig deleted file mode 100644 index ee7ed33..0000000 --- a/engine/src/input.zig +++ /dev/null @@ -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 => {} - } -} diff --git a/engine/src/lib/imgui.zig b/engine/src/lib/imgui.zig new file mode 100644 index 0000000..792fa2b --- /dev/null +++ b/engine/src/lib/imgui.zig @@ -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; + }; +} diff --git a/engine/src/math.zig b/engine/src/lib/math.zig similarity index 100% rename from engine/src/math.zig rename to engine/src/lib/math.zig diff --git a/engine/src/lib/root.zig b/engine/src/lib/root.zig new file mode 100644 index 0000000..004b107 --- /dev/null +++ b/engine/src/lib/root.zig @@ -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 +}; diff --git a/engine/src/audio/data.zig b/engine/src/runtime/audio/data.zig similarity index 97% rename from engine/src/audio/data.zig rename to engine/src/runtime/audio/data.zig index 0fd8bfe..8962212 100644 --- a/engine/src/audio/data.zig +++ b/engine/src/runtime/audio/data.zig @@ -2,6 +2,7 @@ const std = @import("std"); const assert = std.debug.assert; const STBVorbis = @import("stb_vorbis"); +const Lib = @import("lib"); pub const Data = union(enum) { raw: struct { @@ -70,5 +71,5 @@ pub const Data = union(enum) { }; } - pub const Id = enum (u16) { _ }; + pub const Id = Lib.AudioId; }; diff --git a/engine/src/audio/mixer.zig b/engine/src/runtime/audio/mixer.zig similarity index 68% rename from engine/src/audio/mixer.zig rename to engine/src/runtime/audio/mixer.zig index 2cc6daf..ed70048 100644 --- a/engine/src/audio/mixer.zig +++ b/engine/src/runtime/audio/mixer.zig @@ -6,66 +6,60 @@ const Allocator = std.mem.Allocator; const sokol = @import("sokol"); const saudio = sokol.audio; +const Lib = @import("lib"); const AudioData = @import("./data.zig").Data; pub const Store = @import("./store.zig"); const Mixer = @This(); pub const Instance = struct { - data_id: AudioData.Id, + data_id: Lib.AudioId, volume: f32 = 0, cursor: u32 = 0, }; -pub const Command = union(enum) { - pub const Play = struct { - id: AudioData.Id, - volume: f32 = 1 - }; +pub const Command = Lib.AudioCommand; - play: Play, +pub const RingBuffer = struct { + // TODO: This ring buffer will work in a single producer single consumer configuration + // For my game this will be good enough - pub const RingBuffer = struct { - // TODO: This ring buffer will work in a single producer single consumer configuration - // For my game this will be good enough + items: []Command, + head: std.atomic.Value(usize) = .init(0), + tail: std.atomic.Value(usize) = .init(0), - items: []Command, - head: std.atomic.Value(usize) = .init(0), - tail: std.atomic.Value(usize) = .init(0), + pub fn push(self: *RingBuffer, command: Command) error{OutOfMemory}!void { + const head = self.head.load(.monotonic); + const tail = self.tail.load(.monotonic); + const next_head = @mod(head + 1, self.items.len); - pub fn push(self: *RingBuffer, command: Command) error{OutOfMemory}!void { - const head = self.head.load(.monotonic); - const tail = self.tail.load(.monotonic); - const next_head = @mod(head + 1, self.items.len); - - // A single slot in the .items array will always not be used. - if (next_head == tail) { - return error.OutOfMemory; - } - - self.items[head] = command; - self.head.store(next_head, .monotonic); + // A single slot in the .items array will always not be used. + if (next_head == tail) { + return error.OutOfMemory; } - pub fn pop(self: *RingBuffer) ?Command { - const head = self.head.load(.monotonic); - const tail = self.tail.load(.monotonic); + self.items[head] = command; + self.head.store(next_head, .monotonic); + } - if (head == tail) { - return null; - } + pub fn pop(self: *RingBuffer) ?Command { + const head = self.head.load(.monotonic); + const tail = self.tail.load(.monotonic); - const result = self.items[tail]; - self.tail.store(@mod(tail + 1, self.items.len), .monotonic); - return result; + if (head == tail) { + return null; } - }; + + const result = self.items[tail]; + self.tail.store(@mod(tail + 1, self.items.len), .monotonic); + return result; + } }; // TODO: Tracks instances: std.ArrayList(Instance), -commands: Command.RingBuffer, +commands: RingBuffer, working_buffer: []f32, pub fn init( diff --git a/engine/src/audio/root.zig b/engine/src/runtime/audio/root.zig similarity index 98% rename from engine/src/audio/root.zig rename to engine/src/runtime/audio/root.zig index a1d9365..881dc60 100644 --- a/engine/src/audio/root.zig +++ b/engine/src/runtime/audio/root.zig @@ -4,7 +4,7 @@ const assert = std.debug.assert; const tracy = @import("tracy"); -const Math = @import("../math.zig"); +const Math = @import("lib").Math; const STBVorbis = @import("stb_vorbis"); pub const Data = @import("./data.zig").Data; diff --git a/engine/src/audio/store.zig b/engine/src/runtime/audio/store.zig similarity index 98% rename from engine/src/audio/store.zig rename to engine/src/runtime/audio/store.zig index 10e3b13..8613e7f 100644 --- a/engine/src/audio/store.zig +++ b/engine/src/runtime/audio/store.zig @@ -1,7 +1,7 @@ const std = @import("std"); const assert = std.debug.assert; -const Math = @import("../math.zig"); +const Math = @import("lib").Math; const STBVorbis = @import("stb_vorbis"); const AudioData = @import("./data.zig").Data; diff --git a/engine/src/fontstash/context.zig b/engine/src/runtime/fontstash/context.zig similarity index 100% rename from engine/src/fontstash/context.zig rename to engine/src/runtime/fontstash/context.zig diff --git a/engine/src/fontstash/font.zig b/engine/src/runtime/fontstash/font.zig similarity index 100% rename from engine/src/fontstash/font.zig rename to engine/src/runtime/fontstash/font.zig diff --git a/engine/src/fontstash/root.zig b/engine/src/runtime/fontstash/root.zig similarity index 100% rename from engine/src/fontstash/root.zig rename to engine/src/runtime/fontstash/root.zig diff --git a/engine/src/fontstash/sokol_fontstash_impl.c b/engine/src/runtime/fontstash/sokol_fontstash_impl.c similarity index 100% rename from engine/src/fontstash/sokol_fontstash_impl.c rename to engine/src/runtime/fontstash/sokol_fontstash_impl.c diff --git a/engine/src/graphics.zig b/engine/src/runtime/graphics.zig similarity index 88% rename from engine/src/graphics.zig rename to engine/src/runtime/graphics.zig index 95a5439..d3918a7 100644 --- a/engine/src/graphics.zig +++ b/engine/src/runtime/graphics.zig @@ -6,7 +6,7 @@ const sapp = sokol.app; const simgui = sokol.imgui; const sgl = sokol.gl; -const Math = @import("./math.zig"); +const Math = @import("lib").Math; const Vec2 = Math.Vec2; const Vec4 = Math.Vec4; const rgb = Math.rgb; @@ -21,7 +21,8 @@ const tracy = @import("tracy"); const fontstash = @import("./fontstash/root.zig"); pub const Font = fontstash.Font; -const GraphicsFrame = @import("./frame.zig").Graphics; +const Lib = @import("lib"); +const Command = Lib.GraphicsCommand; const Graphics = @This(); @@ -43,37 +44,6 @@ const Options = struct { 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 { image: sg.Image, view: sg.View, @@ -84,20 +54,16 @@ const Texture = struct { height: u32, }; - const Id = enum(u32) { _ }; const Data = struct { width: u32, height: u32, rgba: [*]u8 }; }; -pub const TextureId = Texture.Id; +pub const TextureId = Lib.TextureId; pub const TextureInfo = Texture.Info; -pub const Sprite = struct { - texture: TextureId, - uv: Rect, -}; +pub const Sprite = Lib.Sprite; main_pipeline: sgl.Pipeline, linear_sampler: sg.Sampler, @@ -108,6 +74,11 @@ textures: std.ArrayList(Texture) = .empty, scale_stack_buffer: [32]Vec2 = undefined, 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 { sg.setup(.{ .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 { + self.font_id_lookup.deinit(gpa); self.textures.deinit(gpa); imgui.shutdown(); self.font_context.deinit(); @@ -206,12 +178,29 @@ pub fn drawCommand(self: *Graphics, command: Command) void { .draw_rectangle => |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( - opts.pos.x, - opts.pos.y, - opts.size.x, - opts.size.y, + rect.pos.x, + rect.pos.y, + rect.size.x, + 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 ); }, @@ -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); - 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.setAlign(.{ .x = .left, .y = .top }); 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.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(); sgl.defaults(); sgl.matrixModeProjection(); @@ -310,7 +306,7 @@ pub fn endFrame(self: *Graphics, clear_color: Vec4) void { sg.commit(); } -fn pushTransform(self: *Graphics, translation: Vec2, scale: Vec2) void { +pub fn pushTransform(self: *Graphics, translation: Vec2, scale: Vec2) void { sgl.pushMatrix(); sgl.translate(translation.x, translation.y, 0); 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)); } -fn popTransform(self: *Graphics) void { +pub fn popTransform(self: *Graphics) void { sgl.popMatrix(); _ = 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 size = opts.rect.size; diff --git a/engine/src/imgui.zig b/engine/src/runtime/imgui.zig similarity index 99% rename from engine/src/imgui.zig rename to engine/src/runtime/imgui.zig index e92f419..aa63c40 100644 --- a/engine/src/imgui.zig +++ b/engine/src/runtime/imgui.zig @@ -1,5 +1,6 @@ const std = @import("std"); -const Math = @import("./math.zig"); +const Lib = @import("lib"); +const Math = Lib.Math; const build_options = @import("build_options"); pub const ig = @import("cimgui"); const Vec2 = Math.Vec2; diff --git a/engine/src/runtime/input.zig b/engine/src/runtime/input.zig new file mode 100644 index 0000000..1907c41 --- /dev/null +++ b/engine/src/runtime/input.zig @@ -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 + }; +} diff --git a/engine/src/main.zig b/engine/src/runtime/main.zig similarity index 100% rename from engine/src/main.zig rename to engine/src/runtime/main.zig diff --git a/engine/src/root.zig b/engine/src/runtime/root.zig similarity index 68% rename from engine/src/root.zig rename to engine/src/runtime/root.zig index 314ecbe..7661c42 100644 --- a/engine/src/root.zig +++ b/engine/src/runtime/root.zig @@ -5,14 +5,8 @@ const assert = std.debug.assert; const sokol = @import("sokol"); 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 Frame = @import("./frame.zig"); - const ScreenScalar = @import("./screen_scaler.zig"); pub const imgui = @import("./imgui.zig"); pub const Graphics = @import("./graphics.zig"); @@ -23,37 +17,44 @@ const STBImage = @import("stb_image"); const Gfx = Graphics; +const Lib = @import("lib"); +pub const Math = Lib.Math; +pub const Vec2 = Math.Vec2; +const rgb = Math.rgb; + const GameCallbacks = struct { - init: *const fn(opts: *const Engine.InitOptions) callconv(.c) void, - tick: *const fn(frame: *Frame) callconv(.c) void, + init: *const fn(opts: *const Lib.Init) callconv(.c) void, + tick: *const fn(frame: *Lib.Frame) 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 tick(frame: *Frame) void; +extern fn init(opts: *const Lib.Init) void; +extern fn tick(frame: *Lib.Frame) 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(); pub const Nanoseconds = u64; -pub const InitOptions = struct { - gpa: std.mem.Allocator, - seed: u64 -}; - allocator: std.mem.Allocator, started_at: std.time.Instant, -mouse_inside: bool, last_frame_at: Nanoseconds, graphics: Graphics, +input: Input, game_callbacks: GameCallbacks, -frame: Frame, +frame: Lib.Frame, -canvas_size: ?Vec2 = null, +previous_tick_canvas_size: ?Vec2 = null, const RunOptions = struct { window_title: [*:0]const u8 = "Game", @@ -65,16 +66,11 @@ pub fn run(self: *Engine, opts: RunOptions) !void { self.* = Engine{ .allocator = undefined, .started_at = std.time.Instant.now() catch @panic("Instant.now() unsupported"), - .mouse_inside = false, .last_frame_at = 0, .frame = undefined, .graphics = undefined, - .game_callbacks = .{ - .init = init, - .deinit = deinit, - .tick = tick, - .debug = debug, - } + .game_callbacks = static_game_callbacks, + .input = .{} }; var debug_allocator: std.heap.DebugAllocator(.{}) = .init; @@ -157,7 +153,7 @@ fn sokolInit(self: *Engine) !void { const seed: u64 = @bitCast(std.time.milliTimestamp()); - const opts = InitOptions{ + const opts = Lib.Init{ .gpa = self.allocator, .seed = seed, }; @@ -188,83 +184,71 @@ fn sokolFrame(self: *Engine) !void { const screen_size = Vec2.init(sapp.widthf(), sapp.heightf()); - var revert_mouse_position: ?Vec2 = null; - if (self.canvas_size) |canvas_size| { - if (self.frame.input.mouse_position) |mouse| { - const transform = ScreenScalar.init(screen_size, canvas_size); - - revert_mouse_position = mouse; - self.frame.input.mouse_position = mouse.sub(transform.translation).divideScalar(transform.scale); - } + var maybe_screen_scaler: ?ScreenScalar = null; + if (self.previous_tick_canvas_size) |canvas_size| { + maybe_screen_scaler = ScreenScalar.init(screen_size, canvas_size); } { _ = frame.arena.reset(.retain_capacity); const arena = frame.arena.allocator(); - const audio_commands_capacity = frame.audio.commands.capacity; - frame.audio = .empty; - try frame.audio.commands.ensureTotalCapacity(arena, audio_commands_capacity); + const audio_commands_capacity = frame.audio_commands.capacity; + frame.audio_commands = .empty; + try frame.audio_commands.ensureTotalCapacity(arena, audio_commands_capacity); - const graphics_commands_capacity = frame.graphics.commands.capacity; - const scissor_stack_capacity = frame.graphics.scissor_stack.capacity; - frame.graphics = .empty; - 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())); + const graphics_commands_capacity = frame.graphics_commands.capacity; + frame.graphics_commands = .empty; + try frame.graphics_commands.ensureTotalCapacity(arena, graphics_commands_capacity); + frame.screen_size = screen_size; frame.time_ns = time_passed; 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(); - frame.input.keyboard.released = .initEmpty(); - frame.input.mouse_button.pressed = .initEmpty(); - frame.input.mouse_button.released = .initEmpty(); + if (frame.mouse_position) |mouse_position| { + frame.mouse_position = mouse_position.sub(screen_scaler.translation).divideScalar(screen_scaler.scale); + } + } + + self.game_callbacks.tick(frame); + + if (maybe_screen_scaler) |screen_scaler| { + screen_scaler.pop(frame, frame.clear_color); + } + + frame.keyboard.pressed = .initEmpty(); + frame.keyboard.released = .initEmpty(); + frame.mouse_button.pressed = .initEmpty(); + frame.mouse_button.released = .initEmpty(); } - if (self.canvas_size) |canvas_size| { - const transform = ScreenScalar.init( - screen_size, - canvas_size - ); - transform.apply( - screen_size, - &self.frame, - rgb(0, 0, 0) - ); - } - - sapp.showMouse(!self.frame.hide_cursor); - // Canvas size modification must always be applied a frame later. // 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(); - 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) { - 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); } - - 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(.{ .name = "Debug", .pos = Vec2.init(20, 20), @@ -274,24 +258,31 @@ fn showDebugWindow(self: *Engine, frame: *Frame) !void { } defer imgui.endWindow(); - if (imgui.beginTabBar("debug")) { - defer imgui.endTabBar(); + _ = imgui.beginTabBar("debug"); - if (imgui.beginTabItem("Game")) { - defer imgui.endTabItem(); - self.game_callbacks.debug(); - } - if (imgui.beginTabItem("Engine")) { - defer imgui.endTabItem(); - imgui.textFmt("Draw commands: {}\n", .{ - frame.graphics.commands.items.len, - }); - imgui.textFmt("Audio instances: {}/{}\n", .{ - Audio.mixer.instances.items.len, - Audio.mixer.instances.capacity - }); + if (imgui.beginTabItem("Game")) { + defer imgui.endTabItem(); + + 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")) { + defer imgui.endTabItem(); + imgui.textFmt("Draw commands: {}\n", .{ + frame.graphics_commands.items.len, + }); + imgui.textFmt("Audio instances: {}/{}\n", .{ + Audio.mixer.instances.items.len, + Audio.mixer.instances.capacity + }); + } } 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.*; if (imgui.handleEvent(e)) { - if (self.mouse_inside) { - Input.processEvent(&self.frame, .{ + if (self.input.mouse_position != null) { + self.input.processEvent(&self.frame, .{ .mouse_leave = {} }); } - self.mouse_inside = false; + self.input.mouse_position = null; return true; } - switch (e.type) { + blk: switch (e.type) { .MOUSE_DOWN => { - Input.processEvent(&self.frame, .{ + const mouse_button = Input.getMouseButtonFromSokol(e.mouse_button) orelse break :blk; + + self.input.processEvent(&self.frame, .{ .mouse_pressed = .{ - .button = @enumFromInt(@intFromEnum(e.mouse_button)), + .button = mouse_button, .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; }, .MOUSE_UP => { - Input.processEvent(&self.frame, .{ + const mouse_button = Input.getMouseButtonFromSokol(e.mouse_button) orelse break :blk; + + self.input.processEvent(&self.frame, .{ .mouse_released = .{ - .button = @enumFromInt(@intFromEnum(e.mouse_button)), + .button = mouse_button, .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; }, .MOUSE_MOVE => { - if (!self.mouse_inside) { - Input.processEvent(&self.frame, .{ + if (self.input.mouse_position == null) { + self.input.processEvent(&self.frame, .{ .mouse_enter = Vec2.init(e.mouse_x, e.mouse_y) }); } else { - Input.processEvent(&self.frame, .{ + self.input.processEvent(&self.frame, .{ .mouse_move = Vec2.init(e.mouse_x, e.mouse_y) }); } - self.mouse_inside = true; return true; }, .MOUSE_ENTER => { - if (!self.mouse_inside) { - Input.processEvent(&self.frame, .{ + if (self.input.mouse_position == null) { + self.input.processEvent(&self.frame, .{ .mouse_enter = Vec2.init(e.mouse_x, e.mouse_y) }); } - - self.mouse_inside = true; return true; }, .RESIZED => { - if (self.mouse_inside) { - Input.processEvent(&self.frame, .{ + if (self.input.mouse_position != null) { + self.input.processEvent(&self.frame, .{ .mouse_leave = {} }); } - Input.processEvent(&self.frame, .{ + self.input.processEvent(&self.frame, .{ .window_resize = {} }); - - self.mouse_inside = false; return true; }, .MOUSE_LEAVE => { - if (self.mouse_inside) { - Input.processEvent(&self.frame, .{ + if (self.input.mouse_position != null) { + self.input.processEvent(&self.frame, .{ .mouse_leave = {} }); } - - self.mouse_inside = false; return true; }, .MOUSE_SCROLL => { - Input.processEvent(&self.frame, .{ + self.input.processEvent(&self.frame, .{ .mouse_scroll = Vec2.init(e.scroll_x, e.scroll_y) }); return true; }, .KEY_DOWN => { - Input.processEvent(&self.frame, .{ + const key_code = Input.getKeyCodeFromSokol(e.key_code) orelse break :blk; + + self.input.processEvent(&self.frame, .{ .key_pressed = .{ - .code = @enumFromInt(@intFromEnum(e.key_code)), + .code = key_code, .repeat = e.key_repeat } }); @@ -397,14 +387,16 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool { return true; }, .KEY_UP => { - Input.processEvent(&self.frame, .{ - .key_released = @enumFromInt(@intFromEnum(e.key_code)) + const key_code = Input.getKeyCodeFromSokol(e.key_code) orelse break :blk; + + self.input.processEvent(&self.frame, .{ + .key_released = key_code }); return true; }, .CHAR => { - Input.processEvent(&self.frame, .{ + self.input.processEvent(&self.frame, .{ .char = @intCast(e.char_code) }); diff --git a/engine/src/screen_scaler.zig b/engine/src/runtime/screen_scaler.zig similarity index 68% rename from engine/src/screen_scaler.zig rename to engine/src/runtime/screen_scaler.zig index 08a57ea..3f0528b 100644 --- a/engine/src/screen_scaler.zig +++ b/engine/src/runtime/screen_scaler.zig @@ -1,11 +1,12 @@ const Gfx = @import("./graphics.zig"); -const Math = @import("./math.zig"); +const Math = @import("lib").Math; const Vec2 = Math.Vec2; const Vec4 = Math.Vec4; const rgb = Math.rgb; -const Frame = @import("./frame.zig"); +const Lib = @import("lib"); +const Frame = Lib.Frame; 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 translation = self.translation; - frame.prependGraphicsCommand(.{ - .push_transformation = .{ - .translation = translation, - .scale = .init(scale, scale) - } - }); + frame.pushTransform(translation, .init(scale, scale)); +} + +pub fn pop(self: ScreenScalar, frame: *Frame, color: Vec4) void { + const translation = self.translation; + frame.popTransform(); frame.drawRectangle(.{ .rect = .{ .pos = .init(0, 0), - .size = .init(window_size.x, translation.y), + .size = .init(frame.screen_size.x, translation.y), }, .color = color }); frame.drawRectangle(.{ .rect = .{ - .pos = .init(0, window_size.y - translation.y), - .size = .init(window_size.x, translation.y), + .pos = .init(0, frame.screen_size.y - translation.y), + .size = .init(frame.screen_size.x, translation.y), }, .color = color }); @@ -65,15 +66,15 @@ pub fn apply(self: ScreenScalar, window_size: Vec2, frame: *Frame, color: Vec4) frame.drawRectangle(.{ .rect = .{ .pos = .init(0, 0), - .size = .init(translation.x, window_size.y), + .size = .init(translation.x, frame.screen_size.y), }, .color = color }); frame.drawRectangle(.{ .rect = .{ - .pos = .init(window_size.x - translation.x, 0), - .size = .init(translation.x, window_size.y), + .pos = .init(frame.screen_size.x - translation.x, 0), + .size = .init(translation.x, frame.screen_size.y), }, .color = color }); diff --git a/engine/src/shell.html b/engine/src/runtime/shell.html similarity index 100% rename from engine/src/shell.html rename to engine/src/runtime/shell.html diff --git a/src/game.zig b/src/game.zig index 9a6f136..851bf62 100644 --- a/src/game.zig +++ b/src/game.zig @@ -3,11 +3,9 @@ const Allocator = std.mem.Allocator; const assert = std.debug.assert; const Engine = @import("engine"); -const imgui = Engine.imgui; -const Vec2 = Engine.Vec2; +const FontId = Engine.FontId; +const Vec2 = Engine.Math.Vec2; const rgb = Engine.Math.rgb; -const Gfx = Engine.Graphics; -const Audio = Engine.Audio; const Game = @This(); @@ -16,7 +14,7 @@ const FontName = enum { bold, italic, - const EnumArray = std.EnumArray(FontName, Gfx.Font.Id); + const EnumArray = std.EnumArray(FontName, FontId); }; const State = struct { @@ -29,7 +27,7 @@ const State = struct { 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 font_id_array: FontName.EnumArray = .init(.{ @@ -63,7 +61,7 @@ pub export fn tick(frame: *Engine.Frame) void { const dt = frame.deltaTime(); const canvas_size = Vec2.init(100, 100); - frame.graphics.canvas_size = canvas_size; + frame.canvas_size = canvas_size; if (frame.isKeyPressed(.F3)) { frame.show_debug = !frame.show_debug; @@ -116,6 +114,6 @@ pub export fn tick(frame: *Engine.Frame) void { // }); } -pub export fn debug() void { - imgui.text("Hello World!\n"); +pub export fn debug(imgui: *Engine.ImGui) void { + imgui.text("Hello World!\n", .{}); }