feat(ui): implement pausing

This commit is contained in:
Rekai Nyangadzayi Musuka 2023-05-21 15:01:42 -05:00
parent 89671f767e
commit 0204509923
3 changed files with 71 additions and 33 deletions

View File

@ -30,6 +30,7 @@ pub const State = struct {
fps_hist: RingBuffer(u32), fps_hist: RingBuffer(u32),
should_quit: bool = false, should_quit: bool = false,
emulation: Emulation,
win_stat: WindowStatus = .{}, win_stat: WindowStatus = .{},
@ -41,6 +42,12 @@ pub const State = struct {
show_palette: bool = false, show_palette: bool = false,
}; };
const Emulation = union(enum) {
Active,
Inactive,
Transition: enum { Active, Inactive },
};
/// if zba is initialized with a ROM already provided, this initializer should be called /// if zba is initialized with a ROM already provided, this initializer should be called
/// with `title_opt` being non-null /// with `title_opt` being non-null
pub fn init(allocator: Allocator, title_opt: ?*const [12]u8) !@This() { pub fn init(allocator: Allocator, title_opt: ?*const [12]u8) !@This() {
@ -48,6 +55,7 @@ pub const State = struct {
return .{ return .{
.title = handleTitle(title_opt), .title = handleTitle(title_opt),
.emulation = if (title_opt == null) .Inactive else .Active,
.fps_hist = RingBuffer(u32).init(history), .fps_hist = RingBuffer(u32).init(history),
}; };
} }
@ -90,6 +98,7 @@ pub fn draw(state: *State, tex_id: GLuint, cpu: *Arm7tdmi) void {
}; };
state.title = handleTitle(&cpu.bus.pak.title); state.title = handleTitle(&cpu.bus.pak.title);
state.emulation = .{ .Transition = .Active };
} }
} }
@ -105,6 +114,14 @@ pub fn draw(state: *State, tex_id: GLuint, cpu: *Arm7tdmi) void {
if (zgui.menuItem("Schedule", .{ .selected = state.win_stat.show_schedule })) if (zgui.menuItem("Schedule", .{ .selected = state.win_stat.show_schedule }))
state.win_stat.show_schedule = true; state.win_stat.show_schedule = true;
if (zgui.menuItem("Paused", .{ .selected = state.emulation == .Inactive })) {
state.emulation = switch (state.emulation) {
.Active => .{ .Transition = .Inactive },
.Inactive => .{ .Transition = .Active },
else => state.emulation,
};
}
if (zgui.menuItem("Restart", .{})) if (zgui.menuItem("Restart", .{}))
emu.reset(cpu); emu.reset(cpu);
} }

View File

@ -127,6 +127,10 @@ pub fn main() void {
} else { } else {
var tracker = FpsTracker.init(); var tracker = FpsTracker.init();
// emu should start paused if there's no ROM to run
if (paths.rom == null)
channel.emu.push(.Pause);
const thread = std.Thread.spawn(.{}, emu.run, .{ &cpu, &scheduler, &tracker, &channel }) catch |e| exitln("emu thread panicked: {}", .{e}); const thread = std.Thread.spawn(.{}, emu.run, .{ &cpu, &scheduler, &tracker, &channel }) catch |e| exitln("emu thread panicked: {}", .{e});
defer thread.join(); defer thread.join();

View File

@ -121,13 +121,6 @@ pub const Gui = struct {
var win_dim: Dimensions = default_dim; var win_dim: Dimensions = default_dim;
emu_loop: while (true) { emu_loop: while (true) {
// `quit` from RunOptions may be modified by the GDBSTUB thread,
// so we want to recognize that it may change to `true` and exit the GUI thread
if (channel.gui.pop()) |event| switch (event) {
.Quit => break :emu_loop,
.Paused => @panic("TODO: We want to peek (and then pop if it's .Quit), not always pop"),
};
// Outside of `SDL.SDL_QUIT` below, the DearImgui UI might signal that the program // Outside of `SDL.SDL_QUIT` below, the DearImgui UI might signal that the program
// should exit, in which case we should also handle this // should exit, in which case we should also handle this
if (self.state.should_quit) break :emu_loop; if (self.state.should_quit) break :emu_loop;
@ -190,14 +183,36 @@ pub const Gui = struct {
} }
} }
{ switch (self.state.emulation) {
.Transition => |inner| switch (inner) {
.Active => blk: {
if (channel.gui.peek() == null) break :blk;
if (channel.gui.peek().? != .Paused) break :blk;
log.debug("-> Active | {?}", .{channel.gui.peek()});
_ = channel.gui.pop();
channel.emu.push(.Resume);
self.state.emulation = .Active;
},
.Inactive => blk: {
if (channel.gui.peek()) |value|
if (value == .Paused) break :blk;
log.debug("-> Inactive | {?}", .{channel.gui.peek()});
channel.emu.push(.Pause);
self.state.emulation = .Inactive;
},
},
.Active => {
channel.emu.push(.Pause); channel.emu.push(.Pause);
defer channel.emu.push(.Resume); defer channel.emu.push(.Resume);
// Spin Loop until we know that the emu is paused // Spin Loop until we know that the emu is paused
wait: while (true) switch (channel.gui.pop() orelse continue) { wait: while (true) switch (channel.gui.pop() orelse continue) {
.Paused => break :wait, .Paused => break :wait,
else => |any| std.debug.panic("[Gui/Channel]: Unhandled Event: {}", .{any}), .Quit => break :emu_loop, // TODO: Ensure this behaves as expected
}; };
// Add FPS count to the histogram // Add FPS count to the histogram
@ -212,6 +227,9 @@ pub const Gui = struct {
gl.viewport(0, 0, gba_width, gba_height); gl.viewport(0, 0, gba_width, gba_height);
opengl_impl.drawScreenTexture(emu_tex, prog_id, objects, buf); opengl_impl.drawScreenTexture(emu_tex, prog_id, objects, buf);
} }
},
.Inactive => {},
}
// Background Colour // Background Colour
const size = zgui.io.getDisplaySize(); const size = zgui.io.getDisplaySize();
@ -222,7 +240,6 @@ pub const Gui = struct {
zgui.backend.newFrame(@intToFloat(f32, win_dim.width), @intToFloat(f32, win_dim.height)); zgui.backend.newFrame(@intToFloat(f32, win_dim.width), @intToFloat(f32, win_dim.height));
imgui.draw(&self.state, out_tex, cpu); imgui.draw(&self.state, out_tex, cpu);
zgui.backend.draw(); zgui.backend.draw();
}
SDL.SDL_GL_SwapWindow(self.window); SDL.SDL_GL_SwapWindow(self.window);
} }