Remember whether game is muted

This commit is contained in:
Simon Gellis 2025-11-23 13:37:35 -05:00
parent 8dd64598ed
commit e1d08672fb
6 changed files with 92 additions and 66 deletions

View File

@ -61,12 +61,12 @@ impl Application {
pub fn new(
client: EmulatorClient,
proxy: EventLoopProxy<UserEvent>,
persistence: Persistence,
debug_port: Option<u16>,
profiling: bool,
) -> Self {
let wgpu = WgpuState::new();
let icon = load_icon().ok().map(Arc::new);
let persistence = Persistence::new();
let mappings = MappingProvider::new(persistence.clone());
let shortcuts = ShortcutProvider::new(persistence.clone());
let controllers = ControllerManager::new(client.clone(), &mappings);

58
src/config.rs Normal file
View File

@ -0,0 +1,58 @@
use anyhow::Result;
use egui::{Color32, Vec2};
use serde::{Deserialize, Serialize};
use crate::{emulator::SimId, persistence::Persistence, window::DisplayMode};
pub const COLOR_PRESETS: [[Color32; 2]; 3] = [
[
Color32::from_rgb(0xff, 0x00, 0x00),
Color32::from_rgb(0x00, 0xc6, 0xf0),
],
[
Color32::from_rgb(0x00, 0xb4, 0x00),
Color32::from_rgb(0xc8, 0x00, 0xff),
],
[
Color32::from_rgb(0xb4, 0x9b, 0x00),
Color32::from_rgb(0x00, 0x00, 0xff),
],
];
const fn default_audio_enabled() -> bool {
true
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct SimConfig {
pub display_mode: DisplayMode,
pub colors: [Color32; 2],
pub dimensions: Vec2,
#[serde(default = "default_audio_enabled")]
pub audio_enabled: bool,
}
impl SimConfig {
pub fn load(persistence: &Persistence, sim_id: SimId) -> Self {
if let Ok(config) = persistence.load_config(config_filename(sim_id)) {
return config;
}
Self {
display_mode: DisplayMode::Anaglyph,
colors: COLOR_PRESETS[0],
dimensions: DisplayMode::Anaglyph.proportions() + Vec2::new(0.0, 22.0),
audio_enabled: true,
}
}
pub fn save(&self, persistence: &Persistence, sim_id: SimId) -> Result<()> {
persistence.save_config(config_filename(sim_id), self)
}
}
fn config_filename(sim_id: SimId) -> &'static str {
match sim_id {
SimId::Player1 => "config_p1",
SimId::Player2 => "config_p2",
}
}

View File

@ -96,7 +96,6 @@ impl EmulatorBuilder {
queue,
sim_state: builder.sim_state.clone(),
state: builder.state.clone(),
audio_on: builder.audio_on.clone(),
linked: builder.linked.clone(),
};
(builder, client)
@ -116,6 +115,12 @@ impl EmulatorBuilder {
}
}
pub fn with_audio_on(self, p1: bool, p2: bool) -> Self {
self.audio_on[0].store(p1, Ordering::Relaxed);
self.audio_on[1].store(p2, Ordering::Relaxed);
self
}
pub fn build(self) -> Result<Emulator> {
let mut emulator = Emulator::new(
self.commands,
@ -715,9 +720,8 @@ impl Emulator {
};
sim.watch_stdout(true);
}
EmulatorCommand::SetAudioEnabled(p1, p2) => {
self.audio_on[SimId::Player1.to_index()].store(p1, Ordering::Release);
self.audio_on[SimId::Player2.to_index()].store(p2, Ordering::Release);
EmulatorCommand::SetAudioEnabled(sim_id, enabled) => {
self.audio_on[sim_id.to_index()].store(enabled, Ordering::Release);
}
EmulatorCommand::Link => {
self.link_sims();
@ -792,7 +796,7 @@ pub enum EmulatorCommand {
AddWatchpoint(SimId, u32, usize, VBWatchpointType),
RemoveWatchpoint(SimId, u32, usize, VBWatchpointType),
WatchStdout(SimId, mpsc::Sender<String>),
SetAudioEnabled(bool, bool),
SetAudioEnabled(SimId, bool),
Link,
Unlink,
Reset(SimId),
@ -858,7 +862,6 @@ pub struct EmulatorClient {
queue: mpsc::Sender<EmulatorCommand>,
sim_state: Arc<[Atomic<SimState>; 2]>,
state: Arc<Atomic<EmulatorState>>,
audio_on: Arc<[AtomicBool; 2]>,
linked: Arc<AtomicBool>,
}
@ -869,9 +872,6 @@ impl EmulatorClient {
pub fn emulator_state(&self) -> EmulatorState {
self.state.load(Ordering::Acquire)
}
pub fn is_audio_enabled(&self, sim_id: SimId) -> bool {
self.audio_on[sim_id.to_index()].load(Ordering::Acquire)
}
pub fn are_sims_linked(&self) -> bool {
self.linked.load(Ordering::Acquire)
}

View File

@ -12,8 +12,11 @@ use tracing::error;
use tracing_subscriber::{EnvFilter, Layer, layer::SubscriberExt, util::SubscriberInitExt};
use winit::event_loop::{ControlFlow, EventLoop};
use crate::{config::SimConfig, emulator::SimId, persistence::Persistence};
mod app;
mod audio;
mod config;
mod controller;
mod emulator;
mod gdbserver;
@ -100,6 +103,8 @@ fn main() -> Result<()> {
let args = Args::parse();
let persistence = Persistence::new();
let (mut builder, client) = EmulatorBuilder::new();
if let Some(path) = &args.rom {
builder = builder.with_rom(path);
@ -113,6 +118,9 @@ fn main() -> Result<()> {
if args.profile {
builder = builder.start_paused(true)
}
let p1_audio_on = SimConfig::load(&persistence, SimId::Player1).audio_enabled;
let p2_audio_on = SimConfig::load(&persistence, SimId::Player2).audio_enabled;
builder = builder.with_audio_on(p1_audio_on, p2_audio_on);
ThreadBuilder::default()
.name("Emulator".to_owned())
@ -134,6 +142,7 @@ fn main() -> Result<()> {
event_loop.run_app(&mut Application::new(
client,
proxy,
persistence,
args.debug_port,
args.profile,
))?;

View File

@ -3,6 +3,7 @@ use std::sync::Arc;
pub use about::AboutWindow;
use egui::{Context, ViewportBuilder, ViewportId};
pub use game::GameWindow;
pub use game_screen::DisplayMode;
pub use gdb::GdbServerWindow;
pub use hotkeys::HotkeysWindow;
pub use input::InputWindow;

View File

@ -5,6 +5,7 @@ use std::{
use crate::{
app::UserEvent,
config::{COLOR_PRESETS, SimConfig},
emulator::{EmulatorClient, EmulatorCommand, EmulatorState, SimId, SimState},
input::{Command, ShortcutProvider},
persistence::Persistence,
@ -16,7 +17,6 @@ use egui::{
ViewportBuilder, ViewportCommand, ViewportId, Window,
};
use egui_notify::{Anchor, Toast, Toasts};
use serde::{Deserialize, Serialize};
use winit::event_loop::EventLoopProxy;
use super::{
@ -25,28 +25,13 @@ use super::{
utils::UiExt as _,
};
const COLOR_PRESETS: [[Color32; 2]; 3] = [
[
Color32::from_rgb(0xff, 0x00, 0x00),
Color32::from_rgb(0x00, 0xc6, 0xf0),
],
[
Color32::from_rgb(0x00, 0xb4, 0x00),
Color32::from_rgb(0xc8, 0x00, 0xff),
],
[
Color32::from_rgb(0xb4, 0x9b, 0x00),
Color32::from_rgb(0x00, 0x00, 0xff),
],
];
pub struct GameWindow {
client: EmulatorClient,
proxy: EventLoopProxy<UserEvent>,
persistence: Persistence,
shortcuts: ShortcutProvider,
sim_id: SimId,
config: GameConfig,
config: SimConfig,
toasts: Toasts,
screen: Option<GameScreen>,
messages: Option<mpsc::Receiver<Toast>>,
@ -62,7 +47,7 @@ impl GameWindow {
shortcuts: ShortcutProvider,
sim_id: SimId,
) -> Self {
let config = load_config(&persistence, sim_id);
let config = SimConfig::load(&persistence, sim_id);
let toasts = Toasts::new()
.with_anchor(Anchor::BottomLeft)
.with_margin((10.0, 10.0).into())
@ -410,15 +395,15 @@ impl GameWindow {
});
});
ui.menu_button("Audio", |ui| {
let p1_enabled = self.client.is_audio_enabled(SimId::Player1);
let p2_enabled = self.client.is_audio_enabled(SimId::Player2);
if ui.selectable_button(p1_enabled, "Player 1").clicked() {
self.client
.send_command(EmulatorCommand::SetAudioEnabled(!p1_enabled, p2_enabled));
}
if ui.selectable_button(p2_enabled, "Player 2").clicked() {
self.client
.send_command(EmulatorCommand::SetAudioEnabled(p1_enabled, !p2_enabled));
if ui
.selectable_button(self.config.audio_enabled, "Enabled")
.clicked()
{
self.update_config(|c| c.audio_enabled = !c.audio_enabled);
self.client.send_command(EmulatorCommand::SetAudioEnabled(
self.sim_id,
self.config.audio_enabled,
));
}
});
ui.menu_button("Input", |ui| {
@ -460,13 +445,11 @@ impl GameWindow {
}
}
fn update_config(&mut self, update: impl FnOnce(&mut GameConfig)) {
fn update_config(&mut self, update: impl FnOnce(&mut SimConfig)) {
let mut new_config = self.config.clone();
update(&mut new_config);
if self.config != new_config {
let _ = self
.persistence
.save_config(config_filename(self.sim_id), &new_config);
let _ = new_config.save(&self.persistence, self.sim_id);
}
self.config = new_config;
}
@ -480,24 +463,6 @@ impl GameWindow {
}
}
fn config_filename(sim_id: SimId) -> &'static str {
match sim_id {
SimId::Player1 => "config_p1",
SimId::Player2 => "config_p2",
}
}
fn load_config(persistence: &Persistence, sim_id: SimId) -> GameConfig {
if let Ok(config) = persistence.load_config(config_filename(sim_id)) {
return config;
}
GameConfig {
display_mode: DisplayMode::Anaglyph,
colors: COLOR_PRESETS[0],
dimensions: DisplayMode::Anaglyph.proportions() + Vec2::new(0.0, 22.0),
}
}
impl AppWindow for GameWindow {
fn viewport_id(&self) -> ViewportId {
match self.sim_id {
@ -580,10 +545,3 @@ struct ColorPickerState {
just_opened: bool,
unpause_on_close: bool,
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
struct GameConfig {
display_mode: DisplayMode,
colors: [Color32; 2],
dimensions: Vec2,
}