fix(ui): reset, bios load and rom load are properly thread safe

This commit is contained in:
Rekai Nyangadzayi Musuka 2023-12-20 11:38:58 -06:00
parent 9183e6850d
commit 493d7aeede
6 changed files with 99 additions and 26 deletions

View File

@ -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",

View File

@ -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;

View File

@ -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)) {

View File

@ -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;

View File

@ -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) {

View File

@ -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();
}
};
}