diff --git a/build.zig.zon b/build.zig.zon index 645c757..d6a9151 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -19,8 +19,8 @@ .hash = "1220f48518ce22882e102255ed3bcdb7aeeb4891f50b2cdd3bd74b5b2e24d3149ba2", }, .@"zba-util" = .{ - .url = "https://git.musuka.dev/paoda/zba-util/archive/322c798e384a0d24cc84ffcfa2e4a3ca807798a0.tar.gz", - .hash = "12209ce0e729460b997706e47a53a32f1842672cd120189e612f4871731780a30ed0", + .url = "https://git.musuka.dev/paoda/zba-util/archive/14ea006f4ffae77a333de4993c89690ce94d4abc.tar.gz", + .hash = "1220f0d7c5802ae0a297844f96b2226ccc3d4d895277278e4345c3660161debbe85d", }, .tomlz = .{ .url = "https://github.com/mattyhall/tomlz/archive/47067cd7c902485f7d6e928331fd171ed47f72da.tar.gz", diff --git a/src/core/emu.zig b/src/core/emu.zig index 2d3ca85..6c282a5 100644 --- a/src/core/emu.zig +++ b/src/core/emu.zig @@ -6,8 +6,7 @@ const Scheduler = @import("scheduler.zig").Scheduler; const Arm7tdmi = @import("arm32").Arm7tdmi; const Bus = @import("Bus.zig"); const Tracker = @import("../util.zig").FpsTracker; - -pub const Message = enum { Pause, Resume, Quit }; +const Channel = @import("../util.zig").Queue; const stepDmaTransfer = @import("cpu_util.zig").stepDmaTransfer; const isHalted = @import("cpu_util.zig").isHalted; @@ -17,10 +16,27 @@ const Timer = std.time.Timer; pub const Synchro = struct { const AtomicBool = std.atomic.Atomic(bool); + // FIXME: This Enum ends up being really LARGE!!! + pub const Message = union(enum) { + rom_path: [std.fs.MAX_PATH_BYTES]u8, + bios_path: [std.fs.MAX_PATH_BYTES]u8, + restart: void, + }; + paused: AtomicBool = AtomicBool.init(true), // FIXME: can ui_busy and paused be the same? should_quit: AtomicBool = AtomicBool.init(false), - emu_access: std.Thread.Mutex = .{}, + ch: Channel(Message), + + pub fn init(allocator: std.mem.Allocator) !@This() { + const msg_buf = try allocator.alloc(Message, 1); + return .{ .ch = Channel(Message).init(msg_buf) }; + } + + pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void { + allocator.free(self.ch.inner.buf); + self.* = undefined; + } }; /// 4 Cycles in 1 dot @@ -75,9 +91,10 @@ fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *S log.info("Emulation w/out video sync", .{}); while (!sync.should_quit.load(.Monotonic)) { + handleChannel(cpu, &sync.ch); if (sync.paused.load(.Monotonic)) continue; - runFrame(sync, scheduler, cpu); + runFrame(scheduler, cpu); audioSync(audio_sync, bus_ptr.apu.stream, &bus_ptr.apu.is_buffer_full); if (kind == .UnlimitedFPS) tracker.?.tick(); @@ -89,9 +106,10 @@ fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *S var wake_time: u64 = frame_period; while (!sync.should_quit.load(.Monotonic)) { + handleChannel(cpu, &sync.ch); if (sync.paused.load(.Monotonic)) continue; - runFrame(sync, scheduler, cpu); + runFrame(scheduler, cpu); const new_wake_time = videoSync(&timer, wake_time); // Spin to make up the difference of OS scheduler innacuracies @@ -109,10 +127,23 @@ fn inner(comptime kind: RunKind, audio_sync: bool, cpu: *Arm7tdmi, scheduler: *S } } -pub fn runFrame(sync: *Synchro, sched: *Scheduler, cpu: *Arm7tdmi) void { - sync.emu_access.lock(); - defer sync.emu_access.unlock(); +inline fn handleChannel(cpu: *Arm7tdmi, channel: *Channel(Synchro.Message)) void { + const message = channel.pop() orelse return; + switch (message) { + .rom_path => |path_buf| { + const path = std.mem.sliceTo(&path_buf, 0); + replaceGamepak(cpu, path) catch |e| log.err("failed to replace GamePak: {}", .{e}); + }, + .bios_path => |path_buf| { + const path = std.mem.sliceTo(&path_buf, 0); + replaceBios(cpu, path) catch |e| log.err("failed to replace BIOS: {}", .{e}); + }, + .restart => reset(cpu), + } +} + +pub fn runFrame(sched: *Scheduler, cpu: *Arm7tdmi) void { const frame_end = sched.tick + cycles_per_frame; while (sched.tick < frame_end) { @@ -249,21 +280,21 @@ pub const EmuThing = struct { } }; -pub fn reset(cpu: *Arm7tdmi) void { +fn reset(cpu: *Arm7tdmi) void { // @breakpoint(); cpu.sched.reset(); // Yes this is order sensitive, see the PPU reset for why cpu.bus.reset(); cpu.reset(); } -pub fn replaceGamepak(cpu: *Arm7tdmi, file_path: []const u8) !void { +fn replaceGamepak(cpu: *Arm7tdmi, file_path: []const u8) !void { const bus_ptr: *Bus = @ptrCast(@alignCast(cpu.bus.ptr)); try bus_ptr.replaceGamepak(file_path); reset(cpu); } -pub fn replaceBios(cpu: *Arm7tdmi, file_path: []const u8) !void { +fn replaceBios(cpu: *Arm7tdmi, file_path: []const u8) !void { const bus_ptr: *Bus = @ptrCast(@alignCast(cpu.bus.ptr)); const allocator = bus_ptr.bios.allocator; diff --git a/src/imgui.zig b/src/imgui.zig index b3e1290..961e6ae 100644 --- a/src/imgui.zig +++ b/src/imgui.zig @@ -13,6 +13,7 @@ const Gui = @import("platform.zig").Gui; const Arm7tdmi = @import("arm32").Arm7tdmi; const Scheduler = @import("core/scheduler.zig").Scheduler; const Bus = @import("core/Bus.zig"); +const Synchro = @import("core/emu.zig").Synchro; const RingBuffer = @import("zba-util").RingBuffer; const Dimensions = @import("platform.zig").Dimensions; @@ -70,12 +71,14 @@ pub const State = struct { } }; -pub fn draw(state: *State, win_dim: Dimensions, tex_id: GLuint, cpu: *Arm7tdmi) bool { +pub fn draw(sync: *Synchro, state: *State, win_dim: Dimensions, tex_id: GLuint, cpu: *const Arm7tdmi) bool { const scn_scale = config.config().host.win_scale; const bus_ptr: *Bus = @ptrCast(@alignCast(cpu.bus.ptr)); zgui.backend.newFrame(@floatFromInt(win_dim.width), @floatFromInt(win_dim.height)); + state.title = handleTitle(&bus_ptr.pak.title); + { _ = zgui.beginMainMenuBar(); defer zgui.endMainMenuBar(); @@ -99,12 +102,18 @@ pub fn draw(state: *State, win_dim: Dimensions, tex_id: GLuint, cpu: *Arm7tdmi) defer nfd.freePath(file_path); log.info("user chose: \"{s}\"", .{file_path}); - emu.replaceGamepak(cpu, file_path) catch |e| { - log.err("failed to replace GamePak: {}", .{e}); + + const message = tmp: { + var msg: Synchro.Message = .{ .rom_path = undefined }; + @memcpy(msg.rom_path[0..file_path.len], file_path); + break :tmp msg; + }; + + sync.ch.push(message) catch |e| { + log.err("failed to send file path to emu thread: {}", .{e}); break :blk; }; - state.title = handleTitle(&bus_ptr.pak.title); state.emulation = .{ .Transition = .Active }; } @@ -121,8 +130,15 @@ pub fn draw(state: *State, win_dim: Dimensions, tex_id: GLuint, cpu: *Arm7tdmi) defer nfd.freePath(file_path); log.info("user chose: \"{s}\"", .{file_path}); - emu.replaceBios(cpu, file_path) catch |e| { - log.err("failed to replace BIOS: {}", .{e}); + + const message = tmp: { + var msg: Synchro.Message = .{ .bios_path = undefined }; + @memcpy(msg.bios_path[0..file_path.len], file_path); + break :tmp msg; + }; + + sync.ch.push(message) catch |e| { + log.err("failed to send file path to emu thread: {}", .{e}); break :blk; }; } @@ -149,7 +165,7 @@ pub fn draw(state: *State, win_dim: Dimensions, tex_id: GLuint, cpu: *Arm7tdmi) } if (zgui.menuItem("Restart", .{})) - emu.reset(cpu); + sync.ch.push(.restart) catch |e| log.err("failed to send restart req to emu thread: {}", .{e}); } if (zgui.beginMenu("Stats", true)) { diff --git a/src/main.zig b/src/main.zig index 2299f1d..e9d5a2a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -119,7 +119,8 @@ pub fn main() void { var gui = Gui.init(allocator, &bus.apu, title_ptr) catch |e| exitln("failed to init gui: {}", .{e}); defer gui.deinit(); - var sync: Synchro = .{}; + var sync = Synchro.init(allocator) catch |e| exitln("failed to allocate sync types: {}", .{e}); + defer sync.deinit(allocator); if (result.args.gdb != 0) { const Server = @import("gdbstub").Server; diff --git a/src/platform.zig b/src/platform.zig index ce5798e..732b5ca 100644 --- a/src/platform.zig +++ b/src/platform.zig @@ -207,9 +207,6 @@ pub const Gui = struct { }, }, .Active => { - sync.emu_access.lock(); - defer sync.emu_access.unlock(); - // Add FPS count to the histogram if (tracker) |t| self.state.fps_hist.push(t.value()) catch {}; @@ -228,9 +225,9 @@ pub const Gui = struct { SDL.SDL_LockAudioDevice(self.audio.device); defer SDL.SDL_UnlockAudioDevice(self.audio.device); - zgui_redraw = imgui.draw(&self.state, win_dim, out_tex, cpu); + zgui_redraw = imgui.draw(sync, &self.state, win_dim, out_tex, cpu); }, - .Inactive => zgui_redraw = imgui.draw(&self.state, win_dim, out_tex, cpu), + .Inactive => zgui_redraw = imgui.draw(sync, &self.state, win_dim, out_tex, cpu), } if (zgui_redraw) { diff --git a/src/util.zig b/src/util.zig index 42e61ca..c241a29 100644 --- a/src/util.zig +++ b/src/util.zig @@ -295,3 +295,31 @@ pub const FrameBuffer = struct { return self.layers[if (dev == .Emulator) self.current else ~self.current]; } }; + +const RingBuffer = @import("zba-util").RingBuffer; + +// TODO: Lock Free Queue? +pub fn Queue(comptime T: type) type { + return struct { + inner: RingBuffer(T), + mtx: std.Thread.Mutex = .{}, + + pub fn init(buf: []T) @This() { + return .{ .inner = RingBuffer(T).init(buf) }; + } + + pub fn push(self: *@This(), value: T) !void { + self.mtx.lock(); + defer self.mtx.unlock(); + + try self.inner.push(value); + } + + pub fn pop(self: *@This()) ?T { + self.mtx.lock(); + defer self.mtx.unlock(); + + return self.inner.pop(); + } + }; +}