fix(ui): reset, bios load and rom load are properly thread safe
This commit is contained in:
parent
9183e6850d
commit
493d7aeede
|
@ -19,8 +19,8 @@
|
||||||
.hash = "1220f48518ce22882e102255ed3bcdb7aeeb4891f50b2cdd3bd74b5b2e24d3149ba2",
|
.hash = "1220f48518ce22882e102255ed3bcdb7aeeb4891f50b2cdd3bd74b5b2e24d3149ba2",
|
||||||
},
|
},
|
||||||
.@"zba-util" = .{
|
.@"zba-util" = .{
|
||||||
.url = "https://git.musuka.dev/paoda/zba-util/archive/322c798e384a0d24cc84ffcfa2e4a3ca807798a0.tar.gz",
|
.url = "https://git.musuka.dev/paoda/zba-util/archive/14ea006f4ffae77a333de4993c89690ce94d4abc.tar.gz",
|
||||||
.hash = "12209ce0e729460b997706e47a53a32f1842672cd120189e612f4871731780a30ed0",
|
.hash = "1220f0d7c5802ae0a297844f96b2226ccc3d4d895277278e4345c3660161debbe85d",
|
||||||
},
|
},
|
||||||
.tomlz = .{
|
.tomlz = .{
|
||||||
.url = "https://github.com/mattyhall/tomlz/archive/47067cd7c902485f7d6e928331fd171ed47f72da.tar.gz",
|
.url = "https://github.com/mattyhall/tomlz/archive/47067cd7c902485f7d6e928331fd171ed47f72da.tar.gz",
|
||||||
|
|
|
@ -6,8 +6,7 @@ const Scheduler = @import("scheduler.zig").Scheduler;
|
||||||
const Arm7tdmi = @import("arm32").Arm7tdmi;
|
const Arm7tdmi = @import("arm32").Arm7tdmi;
|
||||||
const Bus = @import("Bus.zig");
|
const Bus = @import("Bus.zig");
|
||||||
const Tracker = @import("../util.zig").FpsTracker;
|
const Tracker = @import("../util.zig").FpsTracker;
|
||||||
|
const Channel = @import("../util.zig").Queue;
|
||||||
pub const Message = enum { Pause, Resume, Quit };
|
|
||||||
|
|
||||||
const stepDmaTransfer = @import("cpu_util.zig").stepDmaTransfer;
|
const stepDmaTransfer = @import("cpu_util.zig").stepDmaTransfer;
|
||||||
const isHalted = @import("cpu_util.zig").isHalted;
|
const isHalted = @import("cpu_util.zig").isHalted;
|
||||||
|
@ -17,10 +16,27 @@ const Timer = std.time.Timer;
|
||||||
pub const Synchro = struct {
|
pub const Synchro = struct {
|
||||||
const AtomicBool = std.atomic.Atomic(bool);
|
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?
|
paused: AtomicBool = AtomicBool.init(true), // FIXME: can ui_busy and paused be the same?
|
||||||
should_quit: AtomicBool = AtomicBool.init(false),
|
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
|
/// 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", .{});
|
log.info("Emulation w/out video sync", .{});
|
||||||
|
|
||||||
while (!sync.should_quit.load(.Monotonic)) {
|
while (!sync.should_quit.load(.Monotonic)) {
|
||||||
|
handleChannel(cpu, &sync.ch);
|
||||||
if (sync.paused.load(.Monotonic)) continue;
|
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);
|
audioSync(audio_sync, bus_ptr.apu.stream, &bus_ptr.apu.is_buffer_full);
|
||||||
|
|
||||||
if (kind == .UnlimitedFPS) tracker.?.tick();
|
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;
|
var wake_time: u64 = frame_period;
|
||||||
|
|
||||||
while (!sync.should_quit.load(.Monotonic)) {
|
while (!sync.should_quit.load(.Monotonic)) {
|
||||||
|
handleChannel(cpu, &sync.ch);
|
||||||
if (sync.paused.load(.Monotonic)) continue;
|
if (sync.paused.load(.Monotonic)) continue;
|
||||||
|
|
||||||
runFrame(sync, scheduler, cpu);
|
runFrame(scheduler, cpu);
|
||||||
const new_wake_time = videoSync(&timer, wake_time);
|
const new_wake_time = videoSync(&timer, wake_time);
|
||||||
|
|
||||||
// Spin to make up the difference of OS scheduler innacuracies
|
// 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 {
|
inline fn handleChannel(cpu: *Arm7tdmi, channel: *Channel(Synchro.Message)) void {
|
||||||
sync.emu_access.lock();
|
const message = channel.pop() orelse return;
|
||||||
defer sync.emu_access.unlock();
|
|
||||||
|
|
||||||
|
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;
|
const frame_end = sched.tick + cycles_per_frame;
|
||||||
|
|
||||||
while (sched.tick < frame_end) {
|
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();
|
// @breakpoint();
|
||||||
cpu.sched.reset(); // Yes this is order sensitive, see the PPU reset for why
|
cpu.sched.reset(); // Yes this is order sensitive, see the PPU reset for why
|
||||||
cpu.bus.reset();
|
cpu.bus.reset();
|
||||||
cpu.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));
|
const bus_ptr: *Bus = @ptrCast(@alignCast(cpu.bus.ptr));
|
||||||
|
|
||||||
try bus_ptr.replaceGamepak(file_path);
|
try bus_ptr.replaceGamepak(file_path);
|
||||||
reset(cpu);
|
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 bus_ptr: *Bus = @ptrCast(@alignCast(cpu.bus.ptr));
|
||||||
|
|
||||||
const allocator = bus_ptr.bios.allocator;
|
const allocator = bus_ptr.bios.allocator;
|
||||||
|
|
|
@ -13,6 +13,7 @@ const Gui = @import("platform.zig").Gui;
|
||||||
const Arm7tdmi = @import("arm32").Arm7tdmi;
|
const Arm7tdmi = @import("arm32").Arm7tdmi;
|
||||||
const Scheduler = @import("core/scheduler.zig").Scheduler;
|
const Scheduler = @import("core/scheduler.zig").Scheduler;
|
||||||
const Bus = @import("core/Bus.zig");
|
const Bus = @import("core/Bus.zig");
|
||||||
|
const Synchro = @import("core/emu.zig").Synchro;
|
||||||
|
|
||||||
const RingBuffer = @import("zba-util").RingBuffer;
|
const RingBuffer = @import("zba-util").RingBuffer;
|
||||||
const Dimensions = @import("platform.zig").Dimensions;
|
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 scn_scale = config.config().host.win_scale;
|
||||||
const bus_ptr: *Bus = @ptrCast(@alignCast(cpu.bus.ptr));
|
const bus_ptr: *Bus = @ptrCast(@alignCast(cpu.bus.ptr));
|
||||||
|
|
||||||
zgui.backend.newFrame(@floatFromInt(win_dim.width), @floatFromInt(win_dim.height));
|
zgui.backend.newFrame(@floatFromInt(win_dim.width), @floatFromInt(win_dim.height));
|
||||||
|
|
||||||
|
state.title = handleTitle(&bus_ptr.pak.title);
|
||||||
|
|
||||||
{
|
{
|
||||||
_ = zgui.beginMainMenuBar();
|
_ = zgui.beginMainMenuBar();
|
||||||
defer zgui.endMainMenuBar();
|
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);
|
defer nfd.freePath(file_path);
|
||||||
|
|
||||||
log.info("user chose: \"{s}\"", .{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;
|
break :blk;
|
||||||
};
|
};
|
||||||
|
|
||||||
state.title = handleTitle(&bus_ptr.pak.title);
|
|
||||||
state.emulation = .{ .Transition = .Active };
|
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);
|
defer nfd.freePath(file_path);
|
||||||
|
|
||||||
log.info("user chose: \"{s}\"", .{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;
|
break :blk;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -149,7 +165,7 @@ pub fn draw(state: *State, win_dim: Dimensions, tex_id: GLuint, cpu: *Arm7tdmi)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zgui.menuItem("Restart", .{}))
|
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)) {
|
if (zgui.beginMenu("Stats", true)) {
|
||||||
|
|
|
@ -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});
|
var gui = Gui.init(allocator, &bus.apu, title_ptr) catch |e| exitln("failed to init gui: {}", .{e});
|
||||||
defer gui.deinit();
|
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) {
|
if (result.args.gdb != 0) {
|
||||||
const Server = @import("gdbstub").Server;
|
const Server = @import("gdbstub").Server;
|
||||||
|
|
|
@ -207,9 +207,6 @@ pub const Gui = struct {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
.Active => {
|
.Active => {
|
||||||
sync.emu_access.lock();
|
|
||||||
defer sync.emu_access.unlock();
|
|
||||||
|
|
||||||
// Add FPS count to the histogram
|
// Add FPS count to the histogram
|
||||||
if (tracker) |t| self.state.fps_hist.push(t.value()) catch {};
|
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);
|
SDL.SDL_LockAudioDevice(self.audio.device);
|
||||||
defer SDL.SDL_UnlockAudioDevice(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) {
|
if (zgui_redraw) {
|
||||||
|
|
28
src/util.zig
28
src/util.zig
|
@ -295,3 +295,31 @@ pub const FrameBuffer = struct {
|
||||||
return self.layers[if (dev == .Emulator) self.current else ~self.current];
|
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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue