diff --git a/life/src/actions.rs b/life/src/actions.rs index 2bca68a..4b28acf 100644 --- a/life/src/actions.rs +++ b/life/src/actions.rs @@ -15,7 +15,6 @@ impl Plugin for GameActionsPlugin { .bind(ZoomAction::Zoom, [Binding::Scroll]) .bind(ZoomAction::ZoomIn, [Binding::Key(KeyCode::Equal)]) .bind(ZoomAction::ZoomOut, [Binding::Key(KeyCode::Minus)]), - ActionsPlugin::::new().bind(Action::Quit, [Binding::Key(KeyCode::Escape)]), )); } } @@ -101,14 +100,6 @@ pub(crate) enum ZoomAction { None, } -#[derive(Copy, Clone, Eq, PartialEq, Hash, GameAction, Default)] -pub(crate) enum Action { - #[action_handler(Action::quit)] - Quit, - #[default] - None, -} - impl ZoomAction { pub(crate) fn zoom_by(factor: f32, mut projection: Query<&mut Projection>) { projection.iter_mut().for_each(|mut p| { @@ -134,12 +125,6 @@ impl ZoomAction { } } -impl Action { - fn quit(mut writer: MessageWriter) { - writer.write(AppExit::Success); - } -} - #[derive(Copy, Clone, Eq, PartialEq, Hash, GameAction, Default)] pub(crate) enum AudioAction { #[action_handler(AudioAction::toggle)] diff --git a/life/src/main.rs b/life/src/main.rs index 469f18d..ae9e326 100644 --- a/life/src/main.rs +++ b/life/src/main.rs @@ -1,8 +1,4 @@ // TODO: -// * Performance: have a limit on per-note+octave sounds -// * Use a finite set of N audio entities and scale volume instead of -// spawning a bunch of audio entities which is causing issues at >100 -// cells // * Volume slider // * Invert mute/unmute icons // * Only move camera when cells move out of view @@ -28,10 +24,16 @@ const SIM_FRAME_DURATION: f32 = 0.5; fn main() { App::new() - .add_plugins(DefaultPlugins.set(AssetPlugin { - file_path: "assets".into(), - ..default() - })) + .add_plugins( + DefaultPlugins + .set(AssetPlugin { + file_path: "assets".into(), + ..default() + }) + .set(TaskPoolPlugin { + task_pool_options: TaskPoolOptions::with_num_threads(1), + }), + ) .add_plugins(FeathersPlugins) .add_plugins(GameActionsPlugin) .init_resource::() @@ -109,7 +111,7 @@ fn main() { .add_systems( Update, ( - control_audio_players.in_set(GameSet::React), + control_audio_players.run_if(any_component_changed::.or_else(state_changed::).or_else(state_changed::)).in_set(GameSet::React), update_pitch_map_resource_ui.run_if(resource_changed::), ), ) @@ -193,7 +195,6 @@ enum AudioPlayback { #[derive(Debug, Component, PartialEq, Default, Clone, FromTemplate)] #[require(Visibility, Transform)] enum UiButton { - Power, Pause, Step, Play, @@ -210,14 +211,13 @@ enum UiButton { impl UiButton { fn icon(&self) -> &'static str { match self { - Self::Power => "power.png", Self::Pause => "pause.png", Self::Step => "next.png", Self::Play => "forward.png", Self::ZoomOut => "zoomOut.png", Self::ZoomIn => "zoomIn.png", - Self::PlayAudio => "audioOn.png", - Self::MuteAudio => "audioOff.png", + Self::PlayAudio => "audioOff.png", + Self::MuteAudio => "audioOn.png", Self::Reset => "return.png", Self::Wipe => "trashcan.png", Self::None => todo!(), @@ -483,7 +483,6 @@ fn primary_ui() -> impl Scene { Text("Welcome to a little experiment I made: adding sound to Conways Game of Life") ThemedText, Text("---") ThemedText, Text("The buttons above control (in order)") ThemedText, - Text("Quit the game") ThemedText, Text("Start/Pause the simulation") ThemedText, Text("Step the simulation") ThemedText, Text("Reset the world state") ThemedText, @@ -530,8 +529,6 @@ fn primary_ui() -> impl Scene { justify_content: JustifyContent::Start, } Children [ - ui_button(UiButton::Power) - ActionSource(Action::Quit), ui_toggle_button((UiButton::Pause, bsn! { SimulationPlayback::Pause }), (UiButton::Play, bsn! { SimulationPlayback::Run })) ActionSource(SimAction::Toggle), ui_button(UiButton::Step) @@ -544,8 +541,10 @@ fn primary_ui() -> impl Scene { ActionSource(ZoomAction::ZoomOut), ui_button(UiButton::ZoomIn) ActionSource(ZoomAction::ZoomIn), - ui_toggle_button((UiButton::MuteAudio, bsn! { AudioPlayback::Mute }), (UiButton::PlayAudio, bsn! { AudioPlayback::Play })) - ActionSource(AudioAction::Toggle), + ui_toggle_button( + (UiButton::MuteAudio, bsn! { AudioPlayback::Mute }), + (UiButton::PlayAudio, bsn! { AudioPlayback::Play }) + ) ActionSource(AudioAction::Toggle), ] ] } @@ -893,26 +892,11 @@ enum Lifecycle { fn new_cell( Coordinates { x, y }: Coordinates, cell_assets: &Res, - sinks: &Query>, - sim_state: &Res>, - audio_state: &Res>, pitch_map: &Res, ) -> impl Scene { let c = Coordinates { x, y }; - - let volume = { - if *sim_state.get() == SimulationPlayback::Run && *audio_state.get() == AudioPlayback::Play - { - let ratio = 1.0 / (sinks.count() + 1) as f32; - Volume::Linear(ratio) - } else { - Volume::SILENT - } - }; - - let playback_settings = PlaybackSettings::ONCE.with_volume(volume); - let cell_pitch = c.to_cell_pitch(&(*pitch_map)); + let translation = c.as_translation() + Vec3::new(0.0, 0.0, 1.0); debug!("cell pitch: {:#?}", cell_pitch); debug!("materials: {:#?}", cell_assets.materials); @@ -921,8 +905,7 @@ fn new_cell( Cell Coordinates { x, y } template_value(cell_pitch.clone()) - template_value(playback_settings) - template_value(Transform::from_translation(c.as_translation() + Vec3::new(0.0, 0.0, 1.0))) + template_value(Transform::from_translation(translation)) template_value(Mesh2dTemplate(cell_assets.mesh.clone().into())) // MeshMaterial2d added by update_material template_value(MeshMaterial2dTemplate(cell_assets.materials.get(&cell_pitch).unwrap().clone().into())) @@ -933,23 +916,13 @@ fn cell_lifecycle( mut reader: MessageReader, mut commands: Commands, cell_assets: Res, - query: Query>, - sim_state: Res>, - audio_state: Res>, pitch_map: Res, ) { reader.read().for_each(|msg| match msg { Lifecycle::Alive(c) => { debug!("creating {:?}", c); // TODO: Safeguard: ensure no other cell there? - commands.spawn_scene(new_cell( - c.clone(), - &cell_assets, - &query, - &sim_state, - &audio_state, - &pitch_map, - )); + commands.spawn_scene(new_cell(c.clone(), &cell_assets, &pitch_map)); } Lifecycle::Dead(e) => { commands.entity(*e).despawn(); @@ -1414,7 +1387,7 @@ fn update_camera_position(mut query: Query<(&Coordinates, &mut Transform), With< fn spawn_audio_players(mut commands: Commands, cell_assets: Res) { CellPitch::iter_all().for_each(|cp| { let h = cell_assets.pitches.get(&cp).unwrap().clone(); - commands.spawn((AudioPlayer(h), cp, PlaybackSettings::default().with_volume(Volume::Linear(0.0)))); + commands.spawn((AudioPlayer(h), cp, PlaybackSettings::default().muted())); }); } @@ -1424,55 +1397,71 @@ fn control_audio_players( simulation_state: Res>, mut players: Query<(&mut AudioSink, &CellPitch)>, cells: Query<&CellPitch, With>, + mut active_cells: Local>, ) { + fn turn_off(sink: &mut AudioSink) { + if !sink.is_paused() { + sink.pause(); + } + if !sink.is_muted() { + sink.mute(); + } + } + fn turn_on(sink: &mut AudioSink, vol: f32) { + // Set the volume of the sink to the proportional volume + sink.set_volume(Volume::Linear(vol)); + + // Play a paused sink + if sink.is_paused() { + sink.play(); + } + + // Unmute a muted sink + if sink.is_muted() { + sink.unmute(); + } + } + match audio_state.get() { AudioPlayback::Play => { match simulation_state.get() { SimulationPlayback::Run => { - let mut s: HashMap = HashMap::new(); - + // Count number of each type of cell for audio cells.iter().for_each(|cp| { - let this = s.entry(cp.clone()).or_insert(0); + let this = active_cells.entry(cp.clone()).or_insert(0); *this += 1; }); - let total: usize = s.values().sum(); + // Get the total to compute per-cell ratio + let total: usize = active_cells.values().sum(); - players.iter_mut().for_each(|(mut sink, cp)| { - match s.get(cp) { + // Update each type of cell with the correct volume + players + .iter_mut() + .for_each(|(mut sink, cp)| match active_cells.get(cp) { Some(v) => { let new_volume: f32 = (*v as f64 / total as f64) as f32; - debug!("Setting volume for {:?} to {:?}", cp, new_volume); - if sink.is_paused() { - sink.play(); - } - sink.set_volume(Volume::Linear(new_volume)); - }, + + turn_on(sink.as_mut(), new_volume); + } None => { - sink.pause(); - sink.set_volume(Volume::Linear(0.0)); + turn_off(sink.as_mut()); } - } - }); - // create distribution of cell pitches - // then go back and adjust volume based on % with this CellPitch - // if volume is 0, pause - // if volume > 0, play - }, + }); + active_cells.clear(); + } // If the sim is paused or stepping, mute an dpause audios _ => { players.iter_mut().for_each(|(mut sink, _)| { - sink.pause(); - sink.set_volume(Volume::Linear(0.0)); + turn_off(sink.as_mut()); }); } } } AudioPlayback::Mute => { players.iter_mut().for_each(|(mut sink, _)| { - sink.pause(); - sink.set_volume(Volume::Linear(0.0)); + turn_off(sink.as_mut()); }); - }, + } } }