Skip to content

Commit 131aa18

Browse files
committed
fix: fixed ctrlc handling
1 parent 6489c57 commit 131aa18

File tree

4 files changed

+56
-38
lines changed

4 files changed

+56
-38
lines changed

tetanes/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ arboard = { version = "3.4", default-features = false, features = [
8888
"windows-sys",
8989
] }
9090
clap.workspace = true
91-
ctrlc = "3.4"
91+
ctrlc = { version = "3.4", features = ["termination"] }
9292
egui = { version = "0.31", default-features = false, features = ["accesskit"] }
9393
pollster = "0.4"
9494
reqwest = { version = "0.12", default-features = false, features = [

tetanes/src/nes.rs

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ use cfg_if::cfg_if;
1414
use config::Config;
1515
use crossbeam::channel::Receiver;
1616
use egui::ahash::HashMap;
17-
use std::sync::Arc;
17+
use std::sync::{
18+
Arc,
19+
atomic::{AtomicBool, Ordering},
20+
};
1821
use tetanes_core::{time::Instant, video::Frame};
1922
use thingbuf::mpsc::blocking;
2023
use winit::{
@@ -46,25 +49,31 @@ pub struct Nes {
4649
pub(crate) state: State,
4750
}
4851

49-
#[derive(Debug, Default)]
52+
#[derive(Debug)]
5053
#[must_use]
5154
pub(crate) enum State {
52-
#[default]
53-
Suspended,
55+
Suspended {
56+
should_terminate: Arc<AtomicBool>,
57+
},
5458
Pending {
5559
ctx: egui::Context,
5660
window: Arc<Window>,
5761
painter_rx: Receiver<Painter>,
62+
should_terminate: Arc<AtomicBool>,
5863
},
5964
Running(Box<Running>),
6065
Exiting,
6166
}
6267

63-
impl State {
64-
pub const fn is_suspended(&self) -> bool {
65-
matches!(self, Self::Suspended)
68+
impl Default for State {
69+
fn default() -> Self {
70+
Self::Suspended {
71+
should_terminate: Default::default(),
72+
}
6673
}
74+
}
6775

76+
impl State {
6877
pub const fn is_exiting(&self) -> bool {
6978
matches!(self, Self::Exiting)
7079
}
@@ -97,8 +106,9 @@ impl RunState {
97106
pub(crate) struct Running {
98107
pub(crate) cfg: Config,
99108
// Only used by wasm currently
100-
#[allow(unused)]
109+
#[cfg_attr(target_arch = "wasm32", allow(unused))]
101110
pub(crate) tx: NesEventProxy,
111+
pub(crate) should_terminate: Arc<AtomicBool>,
102112
pub(crate) emulation: Emulation,
103113
pub(crate) renderer: Renderer,
104114
pub(crate) input_bindings: InputBindings,
@@ -133,11 +143,33 @@ impl Nes {
133143
Ok(())
134144
}
135145

146+
/// Return whether the application should terminate.
147+
pub fn should_terminate(&self) -> bool {
148+
match &self.state {
149+
State::Suspended { should_terminate }
150+
| State::Pending {
151+
should_terminate, ..
152+
} => should_terminate.load(Ordering::Relaxed),
153+
State::Running(running) => running.should_terminate.load(Ordering::Relaxed),
154+
State::Exiting => true,
155+
}
156+
}
157+
136158
/// Create the NES instance.
137159
pub fn new(cfg: Config, event_loop: &EventLoop<NesEvent>) -> Self {
160+
let should_terminate = Arc::new(AtomicBool::new(false));
161+
#[cfg(not(target_arch = "wasm32"))]
162+
// Minor issue if this fails, but not enough to terminate the program
163+
let _ = ctrlc::set_handler({
164+
let should_terminate = Arc::clone(&should_terminate);
165+
move || {
166+
should_terminate.store(true, Ordering::Relaxed);
167+
}
168+
});
169+
138170
Self {
139171
init_state: Some((cfg, NesEventProxy::new(event_loop))),
140-
state: State::Suspended,
172+
state: State::Suspended { should_terminate },
141173
}
142174
}
143175

@@ -150,6 +182,7 @@ impl Nes {
150182
pub(crate) fn request_renderer_resources(
151183
&mut self,
152184
event_loop: &ActiveEventLoop,
185+
should_terminate: Arc<AtomicBool>,
153186
) -> anyhow::Result<()> {
154187
let (cfg, tx) = self
155188
.init_state
@@ -162,6 +195,7 @@ impl Nes {
162195
ctx,
163196
window,
164197
painter_rx,
198+
should_terminate,
165199
};
166200

167201
Ok(())
@@ -180,6 +214,7 @@ impl Nes {
180214
ctx,
181215
window,
182216
painter_rx,
217+
should_terminate,
183218
} => {
184219
let resources = Resources {
185220
ctx,
@@ -199,27 +234,10 @@ impl Nes {
199234
let emulation = Emulation::new(tx.clone(), frame_tx.clone(), &cfg)?;
200235
let renderer = Renderer::new(event_loop, tx.clone(), resources, frame_rx, &cfg)?;
201236

202-
// Minor issue if this fails, but not enough to terminate the program
203-
#[cfg(not(target_arch = "wasm32"))]
204-
let _ = ctrlc::set_handler({
205-
let tx = tx.clone();
206-
move || {
207-
use std::{process, thread, time::Duration};
208-
209-
tracing::info!("received ctrl-c. terminating...");
210-
211-
// Give application time to clean up
212-
tx.event(event::UiEvent::Terminate);
213-
thread::sleep(Duration::from_millis(200));
214-
215-
tracing::debug!("forcing termination...");
216-
process::exit(0);
217-
}
218-
});
219-
220237
let mut running = Running {
221238
cfg,
222239
tx,
240+
should_terminate,
223241
emulation,
224242
renderer,
225243
input_bindings,
@@ -239,7 +257,7 @@ impl Nes {
239257
self.state = State::Running(running);
240258
Ok(())
241259
}
242-
State::Suspended | State::Exiting => anyhow::bail!("not in pending state"),
260+
State::Suspended { .. } | State::Exiting => anyhow::bail!("not in pending state"),
243261
}
244262
}
245263
}

tetanes/src/nes/emulation.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ impl FrameTimeDiag {
133133
fn shutdown(tx: &NesEventProxy, err: impl std::fmt::Display) {
134134
error!("{err}");
135135
tx.event(UiEvent::Terminate);
136-
std::process::exit(1);
137136
}
138137

139138
#[derive(Debug)]
@@ -229,7 +228,6 @@ impl Emulation {
229228
handle.thread().unpark();
230229
if let Err(err) = tx.try_send(event.clone()) {
231230
error!("failed to send emulation event: {event:?}. {err:?}");
232-
std::process::exit(1);
233231
}
234232
}
235233
}
@@ -250,7 +248,6 @@ impl Emulation {
250248
handle.thread().unpark();
251249
if let Err(err) = tx.try_send(NesEvent::Ui(UiEvent::Terminate)) {
252250
error!("failed to send termination event. {err:?}");
253-
std::process::exit(1);
254251
}
255252
}
256253
}

tetanes/src/nes/event.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::{
1616
};
1717
use anyhow::anyhow;
1818
use egui::ViewportId;
19-
use std::path::PathBuf;
19+
use std::{path::PathBuf, sync::Arc};
2020
use tetanes_core::{
2121
action::Action as DeckAction,
2222
apu::{Apu, Channel},
@@ -31,7 +31,7 @@ use tetanes_core::{
3131
time::{Duration, Instant},
3232
video::VideoFilter,
3333
};
34-
use tracing::{debug, error, info, trace};
34+
use tracing::{debug, error, trace, warn};
3535
use uuid::Uuid;
3636
use winit::{
3737
application::ApplicationHandler,
@@ -60,8 +60,7 @@ impl NesEventProxy {
6060
let event = event.into();
6161
trace!("sending event: {event:?}");
6262
if self.0.send_event(event).is_err() {
63-
info!("event loop closed, exiting");
64-
std::process::exit(1);
63+
warn!("event loop closed");
6564
}
6665
}
6766

@@ -342,8 +341,10 @@ impl ApplicationHandler<NesEvent> for Nes {
342341
if let Some(window_id) = state.renderer.root_window_id() {
343342
state.repaint_times.insert(window_id, Instant::now());
344343
}
345-
} else if self.state.is_suspended() {
346-
if let Err(err) = self.request_renderer_resources(event_loop) {
344+
} else if let State::Suspended { should_terminate } = &self.state {
345+
if let Err(err) =
346+
self.request_renderer_resources(event_loop, Arc::clone(should_terminate))
347+
{
347348
error!("failed to request renderer resources: {err:?}");
348349
event_loop.exit();
349350
}
@@ -393,6 +394,8 @@ impl ApplicationHandler<NesEvent> for Nes {
393394
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
394395
if self.state.is_exiting() {
395396
return;
397+
} else if self.should_terminate() {
398+
event_loop.exit();
396399
}
397400

398401
#[cfg(feature = "profiling")]

0 commit comments

Comments
 (0)