Skip to content

Commit

Permalink
refactor: removed a number of panic cases and cleaned up platform checks
Browse files Browse the repository at this point in the history
  • Loading branch information
lukexor committed Jun 6, 2024
1 parent 4f7e1c8 commit bdb71a9
Show file tree
Hide file tree
Showing 24 changed files with 487 additions and 814 deletions.
719 changes: 184 additions & 535 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 1 addition & 5 deletions tetanes-core/src/apu/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,10 +295,6 @@ impl Consume for FilterChain {

impl Sample for FilterChain {
fn output(&self) -> f32 {
self.filters
.last()
.expect("no filters defined")
.filter
.output()
self.filters.last().map_or(0.0, |f| f.filter.output())
}
}
9 changes: 5 additions & 4 deletions tetanes-core/src/control_deck.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Control Deck implementation. The primary entry-point for emulating the NES.
use crate::{
apu::{Apu, Channel},
apu::{self, Apu, Channel},
bus::Bus,
cart::{self, Cart},
common::{Clock, NesRegion, Regional, Reset, ResetKind, Sram},
Expand Down Expand Up @@ -253,9 +253,10 @@ impl ControlDeck {
cpu.bus.input.set_four_player(cfg.four_player);
cpu.bus.input.connect_zapper(cfg.zapper);
for (i, enabled) in cfg.channels_enabled.iter().enumerate() {
cpu.bus
.apu
.set_channel_enabled(Channel::try_from(i).expect("valid APU channel"), *enabled);
match Channel::try_from(i) {
Ok(channel) => cpu.bus.apu.set_channel_enabled(channel, *enabled),
Err(apu::ParseChannelError) => tracing::error!("invalid APU channel: {i}"),
}
}
for genie_code in cfg.genie_codes.iter().cloned() {
cpu.bus.add_genie_code(genie_code);
Expand Down
77 changes: 40 additions & 37 deletions tetanes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,42 +28,6 @@ bench = false
[lints]
workspace = true

[package.metadata.docs.rs]
targets = ["wasm32-unknown-unknown"]

[package.metadata.deb]
extended-description = """
`TetaNES` is a cross-platform emulator for the Nintendo Entertainment System
(NES) released in Japan in 1983 and North America in 1986, written in
Rust using wgpu. It runs on Linux, macOS, Windows, and in a web browser
with Web Assembly.
It started as a personal curiosity that turned into a passion project. It is
still being actively developed with new features and improvements constantly
being added. It is a fairly accurate emulator that can play most NES titles.
`TetaNES` is also meant to showcase using Rust's performance, memory safety, and
fearless concurrency features in a large project. Features used in this project
include complex enums, traits, generics, matching, iterators, channels, and
threads.
`TetaNES` also compiles for the web! Try it out in your browser
(http://lukeworks.tech/tetanes-web)!
"""
section = "game"
assets = [
[
'target/dist/tetanes',
'/usr/bin/',
'755',
],
[
"README.md",
"usr/share/doc/tetanes/README",
"644",
],
]

[features]
default = ["tetanes-core/cycle-accurate"]
profiling = ["tetanes-core/profiling", "dep:puffin", "dep:puffin_egui"]
Expand Down Expand Up @@ -93,7 +57,7 @@ hound = "3.5"
image.workspace = true
parking_lot = "0.12"
puffin_egui = { version = "0.27", optional = true }
rfd = "0.14"
rfd = { version = "0.14", default-features = false, features = ["gtk3"] }
ringbuf = "0.4"
serde.workspace = true
serde_json.workspace = true
Expand All @@ -113,6 +77,8 @@ chrono = { version = "0.4", default-features = false, features = ["clock"] }
egui-winit = "0.27"
pollster = "0.3"
puffin = { workspace = true, optional = true }
# TODO: replace blocking API with async and message passing as it's only used
# for the version updating checking
reqwest = { version = "0.12", features = ["blocking"] }
semver = "1"
wgpu = "0.19"
Expand Down Expand Up @@ -149,3 +115,40 @@ web-sys = { workspace = true, features = [
] }
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"

[package.metadata.docs.rs]
rustc-args = ["--cfg=web_sys_unstable_apis"]
targets = ["wasm32-unknown-unknown"]

[package.metadata.deb]
extended-description = """
`TetaNES` is a cross-platform emulator for the Nintendo Entertainment System
(NES) released in Japan in 1983 and North America in 1986, written in
Rust using wgpu. It runs on Linux, macOS, Windows, and in a web browser
with Web Assembly.
It started as a personal curiosity that turned into a passion project. It is
still being actively developed with new features and improvements constantly
being added. It is a fairly accurate emulator that can play most NES titles.
`TetaNES` is also meant to showcase using Rust's performance, memory safety, and
fearless concurrency features in a large project. Features used in this project
include complex enums, traits, generics, matching, iterators, channels, and
threads.
`TetaNES` also compiles for the web! Try it out in your browser
(http://lukeworks.tech/tetanes-web)!
"""
section = "game"
assets = [
[
'target/dist/tetanes',
'/usr/bin/',
'755',
],
[
"README.md",
"usr/share/doc/tetanes/README",
"644",
],
]
15 changes: 7 additions & 8 deletions tetanes/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,24 @@ body {
margin-bottom: 100px;
color: var(--color);
font-family: "Pixeloid Sans", "Courier New", Courier, monospace;
display: flex;
flex-direction: column;
align-items: center;
}

h1 {
color: var(--heading);
font-family: "Pixeloid Sans Bold", "Courier New", Courier, monospace;
text-align: center;
margin-top: 80px;
margin-bottom: 0px;
margin-bottom: 40px;
line-height: 0.5;
text-align: center;
}

h2 {
h1 span {
color: var(--color);
font-family: "Pixeloid Sans", "Courier New", Courier, monospace;
font-size: 0.8rem;
margin-bottom: 40px;
}

h3 {
h2 {
color: var(--heading);
font-family: "Pixeloid Sans Bold", "Courier New", Courier, monospace;
text-align: center;
Expand Down
9 changes: 4 additions & 5 deletions tetanes/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,18 @@
Javascript in your browser.
</noscript>

<h1>TetaNES</h1>
<h2 id="version"></h2>
<h1>TetaNES<br /><span id="version"></span></h1>

<div id="wrapper">
<canvas id="frame" width="512" height="480"></canvas>
<input type="file" id="load-rom" accept=".nes" class="hidden" />
<input type="file" id="load-replay" accept=".replay" class="hidden" />
</div>

<h3 id="loading-status">
<h2 id="loading-status">
<div class="loader"></div>
Loading...
</h3>
</h2>
<p id="error" class="hidden">
An internal error occurred. Try refreshing the page or filing a
<a href="https://github.com/lukexor/tetanes/issues/new">bug report</a>.
Expand Down Expand Up @@ -110,7 +109,7 @@ <h3 id="loading-status">
</div>
</div>

<h3>Controls</h3>
<h2>Controls</h2>
<table>
<tr>
<th>Action</th>
Expand Down
4 changes: 2 additions & 2 deletions tetanes/src/bin/build_artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,9 @@ impl Build {
.arg(format!("Get-FileHash -Algorithm SHA256 {} | select-object -ExpandProperty Hash", file.display())))?
} else {
cmd_output(Command::new("shasum")
.current_dir(file.parent().expect("parent directory"))
.current_dir(file.parent().with_context(|| format!("no parent directory for {file:?}"))?)
.args(["-a", "256"])
.arg(file.file_name().expect("filename")))?
.arg(file.file_name().with_context(|| format!("no file_name for {file:?}"))?))?
}
}
};
Expand Down
16 changes: 8 additions & 8 deletions tetanes/src/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@ use tracing_subscriber::{
};

fn create_registry() -> Layered<Targets, Registry> {
let default_filter = if cfg!(debug_assertions) {
let default_log = if cfg!(debug_assertions) {
"warn,tetanes=debug,tetanes-core=debug"
} else {
"warn,tetanes=info,tetanes-core=info"
}
.parse::<Targets>()
.expect("valid filter");
};
let default_filter = default_log.parse::<Targets>().unwrap_or_default();

tracing_subscriber::registry().with(
env::var("RUST_LOG")
Expand All @@ -25,10 +24,11 @@ fn create_registry() -> Layered<Targets, Registry> {
}

/// Initialize logging.
pub fn init() -> logging::Log {
let (registry, log) = logging::init_impl(create_registry());
pub fn init() -> anyhow::Result<logging::Log> {
let (registry, log) = logging::init_impl(create_registry())?;
if let Err(err) = registry.try_init() {
eprintln!("setting tracing default failed: {err:?}");
anyhow::bail!("setting tracing default failed: {err:?}");
}
log

Ok(log)
}
33 changes: 19 additions & 14 deletions tetanes/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,32 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use cfg_if::cfg_if;
use tetanes::{logging, nes::Nes};

#[cfg(not(target_arch = "wasm32"))]
pub mod opts;
mod opts;

fn main() -> anyhow::Result<()> {
let _log = logging::init();
let log = logging::init();
if let Err(err) = log {
eprintln!("failed to initialize logging: {err:?}");
}

#[cfg(feature = "profiling")]
puffin::set_scopes_on(true);

#[cfg(target_arch = "wasm32")]
let config = tetanes::nes::config::Config::load(None);
#[cfg(not(target_arch = "wasm32"))]
let config = {
use clap::Parser;
let opts = opts::Opts::parse();
tracing::debug!("CLI Options: {opts:?}");
opts.load()?
};

Nes::run(config)?;
Nes::run({
cfg_if! {
if #[cfg(target_arch = "wasm32")] {
tetanes::nes::config::Config::load(None)
} else {
use clap::Parser;

Ok(())
let opts = opts::Opts::parse();
tracing::debug!("CLI Options: {opts:?}");
opts.load()?
}
}
})
}
5 changes: 3 additions & 2 deletions tetanes/src/nes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::{
platform::{EventLoopExt, Initialize},
thread,
};
use anyhow::Context;
use config::Config;
use crossbeam::channel::{self, Receiver};
use egui::{ahash::HashMap, ViewportBuilder};
Expand Down Expand Up @@ -118,7 +119,7 @@ impl Nes {
let (cfg, tx) = self
.init_state
.as_ref()
.expect("config unexpectedly already taken");
.context("config unexpectedly already taken")?;
let ctx = egui::Context::default();
let (window, viewport_builder) = Renderer::create_window(event_loop, &ctx, cfg)?;
let window = Arc::new(window);
Expand Down Expand Up @@ -180,7 +181,7 @@ impl Nes {
let (mut cfg, tx) = self
.init_state
.take()
.expect("config unexpectedly already taken");
.context("config unexpectedly already taken")?;
let emulation = Emulation::new(tx.clone(), frame_tx.clone(), cfg.clone())?;
let renderer =
Renderer::new(tx.clone(), event_loop, resources, frame_rx, cfg.clone())?;
Expand Down
4 changes: 1 addition & 3 deletions tetanes/src/nes/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,7 @@ impl Default for EmulationConfig {
auto_load: true,
auto_save: true,
auto_save_interval: Duration::from_secs(5),
// WASM framerates suffer with garbage collection pauses when rewind is enabled.
// FIXME: Perhaps re-using Vec allocations could help resolve it.
rewind: cfg!(not(target_arch = "wasm32")),
rewind: true,
rewind_seconds: 30,
rewind_interval: 2,
// WASM struggles to run fast enough with run-ahead and low latency is not needed in
Expand Down
7 changes: 3 additions & 4 deletions tetanes/src/nes/emulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::{
},
renderer::{gui::MessageType, FrameRecycle},
},
platform::{self, Feature},
thread,
};
use anyhow::anyhow;
Expand Down Expand Up @@ -613,8 +614,7 @@ impl State {
viewport_id: ViewportId::ROOT,
when: Instant::now(),
});
// IMPORTANT: Wasm can't block
if self.audio.enabled() || cfg!(target_arch = "wasm32") {
if self.audio.enabled() || !platform::supports(Feature::Blocking) {
match self.frame_tx.try_send_ref() {
Ok(mut frame) => self.control_deck.frame_buffer_into(&mut frame),
Err(TrySendError::Full(_)) => debug!("dropped frame"),
Expand Down Expand Up @@ -904,8 +904,7 @@ impl State {
when: Instant::now(),
});

// IMPORTANT: Wasm can't block
if self.audio.enabled() || cfg!(target_arch = "wasm32") {
if self.audio.enabled() || !platform::supports(Feature::Blocking) {
// If audio is enabled or wasm, frame rate is controlled by park_timeout
// above
match self.frame_tx.try_send_ref() {
Expand Down
Loading

0 comments on commit bdb71a9

Please sign in to comment.