Compare commits

...

3 Commits

17 changed files with 128 additions and 42 deletions

View File

@@ -3,3 +3,10 @@ fail-fast = false
[profile.coverage]
status-level = "none"
[[profile.default.overrides]]
filter = 'test(pacman::game::)'
test-group = 'serial'
[test-groups]
serial = { max-threads = 1 }

View File

@@ -4,7 +4,7 @@ on: ["push", "pull_request"]
env:
CARGO_TERM_COLOR: always
RUST_TOOLCHAIN: 1.86.0
RUST_TOOLCHAIN: nightly
jobs:
coverage:
@@ -46,12 +46,11 @@ jobs:
- name: Generate coverage report
run: |
just coverage-codecov
just coverage-lcov
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v4
- name: Coveralls upload
uses: coverallsapp/github-action@v2
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./codecov.json
disable_search: true
verbose: true
github-token: ${{ secrets.COVERALLS_REPO_TOKEN }}
path-to-lcov: lcov.info
debug: true

4
.gitignore vendored
View File

@@ -20,3 +20,7 @@ coverage.html
# Profiling output
flamegraph.svg
/profile.*
# temporary
assets/game/sound/*.wav
/*.py

2
Cargo.lock generated
View File

@@ -663,7 +663,7 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "pacman"
version = "0.2.0"
version = "0.76.1"
dependencies = [
"anyhow",
"bevy_ecs",

View File

@@ -1,6 +1,6 @@
[package]
name = "pacman"
version = "0.2.0"
version = "0.76.1"
authors = ["Xevion"]
edition = "2021"
rust-version = "1.86.0"
@@ -98,3 +98,6 @@ x86_64-pc-windows-msvc = { triplet = "x64-windows-static-md" }
x86_64-unknown-linux-gnu = { triplet = "x64-linux" }
x86_64-apple-darwin = { triplet = "x64-osx" }
aarch64-apple-darwin = { triplet = "arm64-osx" }
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage,coverage_nightly)'] }

View File

@@ -1,9 +1,6 @@
set shell := ["bash", "-c"]
set windows-shell := ["powershell.exe", "-NoLogo", "-Command"]
# Regex to exclude files from coverage report, double escapes for Justfile + CLI
# You can use src/..., but the filename alone is acceptable too
coverage_exclude_pattern := "src/app\\.rs|audio\\.rs|src/error\\.rs|platform/emscripten\\.rs|bin/.+\\.rs|main\\.rs|platform/desktop\\.rs|platform/tracing_buffer\\.rs|platform/buffered_writer\\.rs|systems/debug\\.rs|systems/profiling\\.rs"
binary_extension := if os() == "windows" { ".exe" } else { "" }
@@ -11,39 +8,26 @@ binary_extension := if os() == "windows" { ".exe" } else { "" }
# !!! --remap-path-prefix prevents the absolute path from being used in the generated report
# Generate HTML report (for humans, source line inspection)
html: coverage-lcov
html: coverage
cargo llvm-cov report \
--remap-path-prefix \
--ignore-filename-regex "{{ coverage_exclude_pattern }}" \
--html \
--open
# Display report (for humans)
report-coverage: coverage-lcov
cargo llvm-cov report \
--remap-path-prefix \
--ignore-filename-regex "{{ coverage_exclude_pattern }}"
report-coverage: coverage
cargo llvm-cov report --remap-path-prefix
# Run & generate LCOV report (as base report)
coverage-lcov:
cargo llvm-cov \
coverage:
cargo +nightly llvm-cov \
--lcov \
--remap-path-prefix \
--ignore-filename-regex "{{ coverage_exclude_pattern }}" \
--workspace \
--output-path lcov.info \
--profile coverage \
--no-fail-fast nextest
# Run & generate Codecov report (for CI)
coverage-codecov:
cargo llvm-cov \
--codecov \
--remap-path-prefix \
--ignore-filename-regex "{{ coverage_exclude_pattern }}" \
--output-path codecov.json \
--profile coverage \
--no-fail-fast nextest
# Profile the project using 'samply'
samply:
cargo build --profile profile

View File

Binary file not shown.

View File

@@ -19,6 +19,8 @@ pub enum Asset {
AtlasImage,
/// Terminal Vector font for text rendering (TerminalVector.ttf)
Font,
/// Sound effect for Pac-Man's death
DeathSound,
}
impl Asset {
@@ -37,6 +39,7 @@ impl Asset {
Wav4 => "sound/waka/4.ogg",
AtlasImage => "atlas.png",
Font => "TerminalVector.ttf",
DeathSound => "sound/pacman_death.wav",
}
}
}

View File

@@ -16,6 +16,7 @@ const SOUND_ASSETS: [Asset; 4] = [Asset::Wav1, Asset::Wav2, Asset::Wav3, Asset::
pub struct Audio {
_mixer_context: Option<mixer::Sdl2MixerContext>,
sounds: Vec<Chunk>,
death_sound: Option<Chunk>,
next_sound_index: usize,
muted: bool,
disabled: bool,
@@ -44,6 +45,7 @@ impl Audio {
return Self {
_mixer_context: None,
sounds: Vec::new(),
death_sound: None,
next_sound_index: 0,
muted: false,
disabled: true,
@@ -65,6 +67,7 @@ impl Audio {
return Self {
_mixer_context: None,
sounds: Vec::new(),
death_sound: None,
next_sound_index: 0,
muted: false,
disabled: true,
@@ -93,12 +96,33 @@ impl Audio {
}
}
let death_sound = match get_asset_bytes(Asset::DeathSound) {
Ok(data) => match RWops::from_bytes(&data) {
Ok(rwops) => match rwops.load_wav() {
Ok(chunk) => Some(chunk),
Err(e) => {
tracing::warn!("Failed to load death sound from asset API: {}", e);
None
}
},
Err(e) => {
tracing::warn!("Failed to create RWops for death sound: {}", e);
None
}
},
Err(e) => {
tracing::warn!("Failed to load death sound asset: {}", e);
None
}
};
// If no sounds loaded successfully, disable audio
if sounds.is_empty() {
if sounds.is_empty() && death_sound.is_none() {
tracing::warn!("No sounds loaded successfully. Audio will be disabled.");
return Self {
_mixer_context: Some(mixer_context),
sounds: Vec::new(),
death_sound: None,
next_sound_index: 0,
muted: false,
disabled: true,
@@ -108,6 +132,7 @@ impl Audio {
Audio {
_mixer_context: Some(mixer_context),
sounds,
death_sound,
next_sound_index: 0,
muted: false,
disabled: false,
@@ -138,6 +163,24 @@ impl Audio {
self.next_sound_index = (self.next_sound_index + 1) % self.sounds.len();
}
/// Plays the death sound effect.
pub fn death(&mut self) {
if self.disabled || self.muted {
return;
}
if let Some(chunk) = &self.death_sound {
mixer::Channel::all().play(chunk, 0).ok();
}
}
/// Halts all currently playing audio channels.
pub fn stop_all(&mut self) {
if !self.disabled {
mixer::Channel::all().halt();
}
}
/// Instantly mutes or unmutes all audio channels by adjusting their volume.
///
/// Sets all 4 mixer channels to zero volume when muting, or restores them to

View File

@@ -1,3 +1,6 @@
#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
#![cfg_attr(coverage_nightly, coverage(off))]
use std::time::{Duration, Instant};
use sdl2::event::Event;

View File

@@ -1,3 +1,6 @@
#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
#![cfg_attr(coverage_nightly, coverage(off))]
use circular_buffer::CircularBuffer;
use pacman::constants::CANVAS_SIZE;
use sdl2::event::Event;

View File

@@ -1,14 +1,22 @@
//! Pac-Man game library crate.
#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
#[cfg_attr(coverage_nightly, coverage(off))]
pub mod app;
pub mod asset;
#[cfg_attr(coverage_nightly, coverage(off))]
pub mod audio;
pub mod constants;
#[cfg_attr(coverage_nightly, coverage(off))]
pub mod error;
#[cfg_attr(coverage_nightly, coverage(off))]
pub mod events;
#[cfg_attr(coverage_nightly, coverage(off))]
pub mod formatter;
#[cfg_attr(coverage_nightly, coverage(off))]
pub mod platform;
pub mod asset;
pub mod constants;
pub mod game;
pub mod map;
pub mod platform;
pub mod systems;
pub mod texture;

View File

@@ -1,20 +1,27 @@
// Note: This disables the console window on Windows. We manually re-attach to the parent terminal or process later on.
#![windows_subsystem = "windows"]
#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
use crate::{app::App, constants::LOOP_TIME};
use tracing::info;
#[cfg_attr(coverage_nightly, coverage(off))]
mod app;
mod asset;
#[cfg_attr(coverage_nightly, coverage(off))]
mod audio;
mod constants;
#[cfg_attr(coverage_nightly, coverage(off))]
mod error;
#[cfg_attr(coverage_nightly, coverage(off))]
mod events;
#[cfg_attr(coverage_nightly, coverage(off))]
mod formatter;
#[cfg_attr(coverage_nightly, coverage(off))]
mod platform;
mod asset;
mod constants;
mod game;
mod map;
mod platform;
mod systems;
mod texture;
@@ -22,6 +29,7 @@ mod texture;
///
/// This function initializes SDL, the window, the game state, and then enters
/// the main game loop.
#[cfg_attr(coverage_nightly, coverage(off))]
pub fn main() {
// On Windows, this connects output streams to the console dynamically
// On Emscripten, this connects the subscriber to the browser console

View File

@@ -65,6 +65,7 @@ pub fn get_asset_bytes(asset: Asset) -> Result<Cow<'static, [u8]>, AssetError> {
Asset::Wav4 => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/waka/4.ogg"))),
Asset::AtlasImage => Ok(Cow::Borrowed(include_bytes!("../../assets/game/atlas.png"))),
Asset::Font => Ok(Cow::Borrowed(include_bytes!("../../assets/game/TerminalVector.ttf"))),
Asset::DeathSound => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/pacman_death.wav"))),
}
}

View File

@@ -26,6 +26,10 @@ pub struct AudioState {
pub enum AudioEvent {
/// Play the "eat" sound when Pac-Man consumes a pellet
PlayEat,
/// Play the death sound
PlayDeath,
/// Stop all currently playing sounds
StopAll,
}
/// Non-send resource wrapper for SDL2 audio system
@@ -59,6 +63,16 @@ pub fn audio_system(
// 4 eat sounds available
}
}
AudioEvent::PlayDeath => {
if !audio.0.is_disabled() && !audio_state.muted {
audio.0.death();
}
}
AudioEvent::StopAll => {
if !audio.0.is_disabled() {
audio.0.stop_all();
}
}
}
}
}

View File

@@ -1,6 +1,5 @@
//! Debug rendering system
use std::cmp::Ordering;
#[cfg_attr(coverage_nightly, feature(coverage_attribute))]
use crate::constants::{self, BOARD_PIXEL_OFFSET};
use crate::map::builder::Map;
use crate::systems::{Collider, CursorPosition, NodeId, Position, SystemTimings};
@@ -13,6 +12,7 @@ use sdl2::rect::{Point, Rect};
use sdl2::render::{Canvas, Texture};
use sdl2::video::Window;
use smallvec::SmallVec;
use std::cmp::Ordering;
use std::collections::{HashMap, HashSet};
use tracing::warn;
@@ -149,6 +149,7 @@ fn transform_position_with_offset(pos: Vec2, scale: f32) -> IVec2 {
}
/// Renders timing information in the top-left corner of the screen using the debug text atlas
#[cfg_attr(coverage_nightly, coverage(off))]
fn render_timing_display(
canvas: &mut Canvas<Window>,
timings: &SystemTimings,
@@ -203,6 +204,7 @@ fn render_timing_display(
}
#[allow(clippy::too_many_arguments)]
#[cfg_attr(coverage_nightly, coverage(off))]
pub fn debug_render_system(
canvas: &mut Canvas<Window>,
ttf_atlas: &mut TtfAtlasResource,

View File

@@ -3,17 +3,21 @@
//! This module contains all the ECS-related logic, including components, systems,
//! and resources.
#[cfg_attr(coverage_nightly, coverage(off))]
pub mod audio;
pub mod blinking;
pub mod collision;
pub mod components;
#[cfg_attr(coverage_nightly, coverage(off))]
pub mod debug;
pub mod ghost;
pub mod input;
pub mod item;
pub mod movement;
pub mod player;
#[cfg_attr(coverage_nightly, coverage(off))]
pub mod profiling;
#[cfg_attr(coverage_nightly, coverage(off))]
pub mod render;
pub mod stage;