From 209e5f60c024e7debb00c2188fb8a7c1a9e0b938 Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Tue, 21 Jul 2026 14:22:35 -0700 Subject: [PATCH] web zip for pages.elijah.run, enforced system ordering for cell de-dupe --- .cargo/config.toml | 6 +- engine/Cargo.lock | 135 ------------- engine/Cargo.toml | 7 +- engine/src/lib.rs | 13 ++ justfile | 4 + life/Cargo.lock | 135 ------------- life/Cargo.toml | 7 +- life/src/actions.rs | 2 +- life/src/main.rs | 469 +++++++++++++++++++++++++++----------------- scripts/webbuild | 11 +- scripts/webzip | 23 +++ 11 files changed, 360 insertions(+), 452 deletions(-) create mode 100755 scripts/webzip diff --git a/.cargo/config.toml b/.cargo/config.toml index 9b49921..12eead2 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -20,8 +20,10 @@ codegen-backend = "llvm" [profile.wasm-release] inherits = "release" -opt-level = "s" -strip = "debuginfo" +opt-level = "z" +strip = true +panic = "abort" +lto = "thin" [target.x86_64-unknown-linux-gnu] linker = "clang" diff --git a/engine/Cargo.lock b/engine/Cargo.lock index ece6462..4292b69 100644 --- a/engine/Cargo.lock +++ b/engine/Cargo.lock @@ -349,12 +349,6 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - [[package]] name = "bevy" version = "0.19.0" @@ -400,7 +394,6 @@ dependencies = [ "bevy_derive", "bevy_ecs", "bevy_math", - "bevy_mesh", "bevy_platform", "bevy_reflect", "bevy_time", @@ -884,7 +877,6 @@ dependencies = [ "bevy_material", "bevy_math", "bevy_mesh", - "bevy_pbr", "bevy_reflect", "bevy_render", "bevy_shader", @@ -895,42 +887,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "bevy_gltf" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84e21ad83b7dc8d456491ea3d50057685d8c4cca8bc52a1b4a7161c86c369842" -dependencies = [ - "async-lock", - "base64", - "bevy_animation", - "bevy_app", - "bevy_asset", - "bevy_camera", - "bevy_color", - "bevy_ecs", - "bevy_image", - "bevy_light", - "bevy_material", - "bevy_math", - "bevy_mesh", - "bevy_platform", - "bevy_reflect", - "bevy_tasks", - "bevy_transform", - "bevy_world_serialization", - "fixedbitset", - "gltf", - "itertools", - "percent-encoding", - "serde", - "serde_json", - "smallvec", - "thiserror 2.0.18", - "tracing", - "wgpu-types", -] - [[package]] name = "bevy_image" version = "0.19.0" @@ -951,9 +907,7 @@ dependencies = [ "guillotiere", "half", "image", - "ktx2", "rectangle-pack", - "ruzstd", "serde", "thiserror 2.0.18", "tracing", @@ -1020,7 +974,6 @@ dependencies = [ "bevy_gilrs", "bevy_gizmos", "bevy_gizmos_render", - "bevy_gltf", "bevy_image", "bevy_input", "bevy_input_focus", @@ -1213,7 +1166,6 @@ dependencies = [ "bevy_derive", "bevy_diagnostic", "bevy_ecs", - "bevy_gltf", "bevy_image", "bevy_light", "bevy_log", @@ -2775,42 +2727,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "gltf" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ce1918195723ce6ac74e80542c5a96a40c2b26162c1957a5cd70799b8cacf7" -dependencies = [ - "byteorder", - "gltf-json", - "lazy_static", - "serde_json", -] - -[[package]] -name = "gltf-derive" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14070e711538afba5d6c807edb74bcb84e5dbb9211a3bf5dea0dfab5b24f4c51" -dependencies = [ - "inflections", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "gltf-json" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6176f9d60a7eab0a877e8e96548605dedbde9190a7ae1e80bbcc1c9af03ab14" -dependencies = [ - "gltf-derive", - "serde", - "serde_derive", - "serde_json", -] - [[package]] name = "glutin_wgl_sys" version = "0.6.1" @@ -3134,12 +3050,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "inflections" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" - [[package]] name = "inotify" version = "0.11.2" @@ -3317,15 +3227,6 @@ dependencies = [ "libc", ] -[[package]] -name = "ktx2" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d50be8a24c37debdf3170be6ca396d5b5b165f37f94837891adac735ca416b8" -dependencies = [ - "bitflags 2.12.1", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -3338,17 +3239,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" -[[package]] -name = "lewton" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030" -dependencies = [ - "byteorder", - "ogg", - "tinyvec", -] - [[package]] name = "libc" version = "0.2.186" @@ -4058,15 +3948,6 @@ dependencies = [ "nonmax", ] -[[package]] -name = "ogg" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e" -dependencies = [ - "byteorder", -] - [[package]] name = "once_cell" version = "1.21.4" @@ -4521,7 +4402,6 @@ checksum = "d0a536bb79db59098ef71a4dd4246c02eb87b316deceb1b68e0cde7167ec01eb" dependencies = [ "cpal", "dasp_sample", - "lewton", "num-rational", "thiserror 2.0.18", "tracing", @@ -4588,15 +4468,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "ruzstd" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c1c839d570d835527c9a5e4db7cb2198683a988cb9d7293fc8674e6bd58fc8" -dependencies = [ - "twox-hash", -] - [[package]] name = "same-file" version = "1.0.6" @@ -5146,12 +5017,6 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" -[[package]] -name = "twox-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" - [[package]] name = "typeid" version = "1.0.3" diff --git a/engine/Cargo.toml b/engine/Cargo.toml index edc980b..b2c2ebd 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -4,7 +4,12 @@ version = "0.1.0" edition = "2024" [dependencies] -bevy = "0.19.0" +bevy = { version = "0.19.0", default-features = false, features = [ + "2d", + "ui", + "bevy_audio", + "png", +] } engine_macros = { path = "macros" } [features] diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 79c9579..5e4347b 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -2,6 +2,7 @@ pub mod actions; pub use actions::*; pub mod color; +use bevy::ecs::query::QueryFilter; pub use color::*; pub use std::time::Duration; @@ -36,3 +37,15 @@ pub use bevy::{ checkbox_self_update, slider_self_update, }, }; + +pub fn any_component_added(q: Query>) -> bool { + !q.is_empty() +} + +pub fn any_component_changed(q: Query>) -> bool { + !q.is_empty() +} + +pub fn query_condition_empty(q: Query) -> bool { + q.is_empty() +} diff --git a/justfile b/justfile index da0433b..1098c40 100644 --- a/justfile +++ b/justfile @@ -37,6 +37,10 @@ fmt: webbuild target: scripts/webbuild {{target}} +# Zip the built web output from dist// into dist/.zip +webzip target: + scripts/webzip {{target}} + # Serve a built web target from dist// via simple-http-server serve target: scripts/serve {{target}} diff --git a/life/Cargo.lock b/life/Cargo.lock index 0cd2a6a..52f3950 100644 --- a/life/Cargo.lock +++ b/life/Cargo.lock @@ -349,12 +349,6 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - [[package]] name = "bevy" version = "0.19.0" @@ -399,7 +393,6 @@ dependencies = [ "bevy_derive", "bevy_ecs", "bevy_math", - "bevy_mesh", "bevy_platform", "bevy_reflect", "bevy_time", @@ -855,7 +848,6 @@ dependencies = [ "bevy_material", "bevy_math", "bevy_mesh", - "bevy_pbr", "bevy_reflect", "bevy_render", "bevy_shader", @@ -866,42 +858,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "bevy_gltf" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84e21ad83b7dc8d456491ea3d50057685d8c4cca8bc52a1b4a7161c86c369842" -dependencies = [ - "async-lock", - "base64", - "bevy_animation", - "bevy_app", - "bevy_asset", - "bevy_camera", - "bevy_color", - "bevy_ecs", - "bevy_image", - "bevy_light", - "bevy_material", - "bevy_math", - "bevy_mesh", - "bevy_platform", - "bevy_reflect", - "bevy_tasks", - "bevy_transform", - "bevy_world_serialization", - "fixedbitset", - "gltf", - "itertools", - "percent-encoding", - "serde", - "serde_json", - "smallvec", - "thiserror 2.0.18", - "tracing", - "wgpu-types", -] - [[package]] name = "bevy_image" version = "0.19.0" @@ -922,9 +878,7 @@ dependencies = [ "guillotiere", "half", "image", - "ktx2", "rectangle-pack", - "ruzstd", "serde", "thiserror 2.0.18", "tracing", @@ -990,7 +944,6 @@ dependencies = [ "bevy_gilrs", "bevy_gizmos", "bevy_gizmos_render", - "bevy_gltf", "bevy_image", "bevy_input", "bevy_input_focus", @@ -1183,7 +1136,6 @@ dependencies = [ "bevy_derive", "bevy_diagnostic", "bevy_ecs", - "bevy_gltf", "bevy_image", "bevy_light", "bevy_log", @@ -2727,42 +2679,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "gltf" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ce1918195723ce6ac74e80542c5a96a40c2b26162c1957a5cd70799b8cacf7" -dependencies = [ - "byteorder", - "gltf-json", - "lazy_static", - "serde_json", -] - -[[package]] -name = "gltf-derive" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14070e711538afba5d6c807edb74bcb84e5dbb9211a3bf5dea0dfab5b24f4c51" -dependencies = [ - "inflections", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "gltf-json" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6176f9d60a7eab0a877e8e96548605dedbde9190a7ae1e80bbcc1c9af03ab14" -dependencies = [ - "gltf-derive", - "serde", - "serde_derive", - "serde_json", -] - [[package]] name = "glutin_wgl_sys" version = "0.6.1" @@ -3086,12 +3002,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "inflections" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" - [[package]] name = "inotify" version = "0.11.2" @@ -3248,15 +3158,6 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" -[[package]] -name = "ktx2" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d50be8a24c37debdf3170be6ca396d5b5b165f37f94837891adac735ca416b8" -dependencies = [ - "bitflags 2.13.0", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -3269,17 +3170,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" -[[package]] -name = "lewton" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030" -dependencies = [ - "byteorder", - "ogg", - "tinyvec", -] - [[package]] name = "libc" version = "0.2.186" @@ -3945,15 +3835,6 @@ dependencies = [ "nonmax", ] -[[package]] -name = "ogg" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e" -dependencies = [ - "byteorder", -] - [[package]] name = "once_cell" version = "1.21.4" @@ -4398,7 +4279,6 @@ checksum = "d0a536bb79db59098ef71a4dd4246c02eb87b316deceb1b68e0cde7167ec01eb" dependencies = [ "cpal", "dasp_sample", - "lewton", "num-rational", "thiserror 2.0.18", "tracing", @@ -4465,15 +4345,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "ruzstd" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c1c839d570d835527c9a5e4db7cb2198683a988cb9d7293fc8674e6bd58fc8" -dependencies = [ - "twox-hash", -] - [[package]] name = "same-file" version = "1.0.6" @@ -5013,12 +4884,6 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" -[[package]] -name = "twox-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" - [[package]] name = "typeid" version = "1.0.3" diff --git a/life/Cargo.toml b/life/Cargo.toml index 623d054..7813dee 100644 --- a/life/Cargo.toml +++ b/life/Cargo.toml @@ -4,7 +4,12 @@ version = "0.1.0" edition = "2024" [dependencies] -bevy = "0.19.0" +bevy = { version = "0.19.0", default-features = false, features = [ + "2d", + "ui", + "bevy_audio", + "png", +] } [dependencies.engine] path = "../engine" diff --git a/life/src/actions.rs b/life/src/actions.rs index 6a57e83..2bca68a 100644 --- a/life/src/actions.rs +++ b/life/src/actions.rs @@ -110,7 +110,7 @@ pub(crate) enum Action { } impl ZoomAction { - fn zoom_by(factor: f32, mut projection: Query<&mut Projection>) { + pub(crate) fn zoom_by(factor: f32, mut projection: Query<&mut Projection>) { projection.iter_mut().for_each(|mut p| { if let Projection::Orthographic(o) = &mut *p { o.scale = (o.scale * factor).clamp(0.05, 20.0); diff --git a/life/src/main.rs b/life/src/main.rs index 9455b01..9025361 100644 --- a/life/src/main.rs +++ b/life/src/main.rs @@ -1,15 +1,17 @@ // TODO: -// * Fix randomly audio not working -// * Lock down system scheduling/run_if conditions -// * UI widget explaining rules/hotkeys +// * Lock down system scheduling/run_if conditions +// * 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 +// * Stop creating cells when I didn't mean to (clicking UI, click + dragging) +// * Keep some controls open when main ui panel is closed +// * Exclude power button from web build // * Show shadow of piece (same tile, just with alpha transparency) -// * Start with an interesting pattern -// * UI minimize widgets (mostly done) -// * Indicate to click on the UI -// * Add "follow action" that adjusts camera to zoom in/out/move to see everything +// * Dark mode // * Make click and drag feel better // * Add arrow keys to move around -// * web/wasm build +// * "Follow" enhancement: Zoom in/out #![allow(clippy::complexity)] // Only because of FromTemplat macros unfortunately... #![allow(dead_code)] @@ -52,23 +54,52 @@ fn main() { load_meshes, ), ) + .configure_sets( + Update, + (GameSet::Plan, GameSet::Apply, GameSet::React).chain(), + ) // Cell management/Simulation .add_systems( Update, ( - default_board.run_if(run_once), - cell_lifecycle.run_if(on_message::), - update_grid_position.run_if(on_message::), - de_spawn_cells - .run_if(not(is_ui_hovered)) - .run_if(not(is_panning)) - .run_if(input_just_released(MouseButton::Left)), - simulation_step - .run_if(in_state(SimulationPlayback::Run)) - .run_if(cooldown_secs(SIM_FRAME_DURATION)), - simulation_step.run_if(state_changed::), - pause_simulation.run_if(in_state(SimulationPlayback::Step)), - update_material, + // Produce lifecycle messages + ( + default_board.run_if(run_once), + update_grid_position.run_if(on_message::), + de_spawn_cells + .run_if(not(is_ui_hovered)) + .run_if(not(is_panning)) + .run_if(input_just_released(MouseButton::Left)), + simulation_step.run_if( + in_state(SimulationPlayback::Run) + .and_then(cooldown_secs(SIM_FRAME_DURATION)) + .or_else(state_changed::), + ), + pause_simulation.run_if(in_state(SimulationPlayback::Step)), + ) + .in_set(GameSet::Plan), + // Apply lifecycle message + cell_lifecycle + .run_if(on_message::) + .in_set(GameSet::Apply), + // React to board changes + ( + reassign_cell_pitch.run_if(resource_changed::), + update_material.run_if( + any_component_added:: + .or_else(any_component_changed::), + ), + camera_follow + .run_if(in_state(SimulationPlayback::Run)) + .run_if(any_component_changed::), + update_camera_position + .run_if(in_state(SimulationPlayback::Run)) + .run_if(not(query_condition_empty::<( + With, + Changed, + )>)), + ) + .in_set(GameSet::React), ), ) // Audio Systems @@ -77,19 +108,29 @@ fn main() { ( control_volume, update_pitch_map_resource_ui.run_if(resource_changed::), - reassign_cell_pitch.run_if(resource_changed::), - update_audio, + update_audio + .run_if( + any_component_added:: + .or_else(any_component_changed::), + ) + .in_set(GameSet::React), ), ) // Debugging .add_systems( Update, ( - assert_cell_uniqueness.run_if(any_with_component::), grid_gizmo, - update_debug_info_cell_count, update_debug_info_coordinates, - update_debug_info_active, + ( + assert_cell_uniqueness + .run_if(any_with_component::) + .after(update_material) + .after(update_audio), + update_debug_info_cell_count, + update_debug_info_active, + ) + .in_set(GameSet::React), ), ) // Ui Controllers @@ -116,6 +157,13 @@ fn main() { const SCALE: f32 = 100.0; +#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] +enum GameSet { + Plan, + Apply, + React, +} + #[derive(Component, PartialEq, Clone, Default)] enum DebugInfo { CellCount, @@ -157,11 +205,6 @@ enum UiButton { ZoomIn, PlayAudio, MuteAudio, - AddHighOctave, - SubHighOctave, - AddLowOctave, - SubLowOctave, - Note, Reset, Wipe, #[default] @@ -179,11 +222,6 @@ impl UiButton { Self::ZoomIn => "zoomIn.png", Self::PlayAudio => "audioOn.png", Self::MuteAudio => "audioOff.png", - Self::AddHighOctave => "todo.png", - Self::SubHighOctave => "todo.png", - Self::AddLowOctave => "todo.png", - Self::SubLowOctave => "todo.png", - Self::Note => "todo.png", Self::Reset => "return.png", Self::Wipe => "trashcan.png", Self::None => todo!(), @@ -264,6 +302,7 @@ fn ui_toggle_button( fn the_camera() -> impl Scene { bsn! { Camera2d + Coordinates } } @@ -434,6 +473,171 @@ fn primary_ui() -> impl Scene { }); } + fn description_text() -> impl Scene { + bsn! { + subpane() + Node { + display: Display::Flex, + flex_direction: FlexDirection::Column, + align_items: AlignItems::Stretch, + justify_content: JustifyContent::Start, + } + text_setup() + Children [ + 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, + Text("Wipe the world state") ThemedText, + Text("Zoom in/out") ThemedText, + Text("Toggle sound on/off") ThemedText, + Text("---") ThemedText, + Text("Below that are the notes you can toggle on/off") ThemedText, + Text("And below that are the number of octaves for each note") ThemedText, + Text("Notes are color coded horizontally") ThemedText, + Text("Octaves are color coded vertically") ThemedText, + Text("---") ThemedText, + Text("The camera automatically follows the action") ThemedText, + Text("---") ThemedText, + Text("Hotkyes") ThemedText, + Text("* Space: Play/Pause Simulation") ThemedText, + Text("* m: Mute/Play Audio") ThemedText, + Text("* r: Reset Simulation") ThemedText, + Text("* n: Next Simulation Step") ThemedText, + Text("* -/=: Zoom In/Out") ThemedText, + ] + } + } + + fn controls() -> impl Scene { + bsn! { + subpane() + Node { + display: Display::Flex, + flex_direction: FlexDirection::Row, + align_items: AlignItems::Stretch, + justify_content: JustifyContent::Start, + + } + Children [ + subpane_header() Children [ + Text("Nav") ThemedText + ] + subpane_body() + Node { + display: Display::Flex, + flex_direction: FlexDirection::Row, + align_items: AlignItems::Stretch, + 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) + ActionSource(SimAction::Step), + ui_button(UiButton::Reset) + ActionSource(SimAction::Reset), + ui_button(UiButton::Wipe) + ActionSource(SimAction::Wipe), + ui_button(UiButton::ZoomOut) + 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), + ] + ] + } + } + + fn notes() -> impl Scene { + bsn! { + subpane() + Children [ + subpane_header() Children [ + Text("Notes") ThemedText + ] + subpane_body() + Node { + display: Display::Flex, + flex_direction: FlexDirection::Row, + align_items: AlignItems::Stretch, + justify_content: JustifyContent::Start, + } + Children [ + ui_checkbox("A") + template_value(Note::A) + on(toggle_note), + ui_checkbox("A#") + template_value(Note::ASharp) + on(toggle_note), + ui_checkbox("B") + template_value(Note::B) + on(toggle_note), + ui_checkbox("C") + template_value(Note::C) + on(toggle_note), + ui_checkbox("C#") + template_value(Note::CSharp) + on(toggle_note), + ui_checkbox("D") + template_value(Note::D) + on(toggle_note), + ui_checkbox("D#") + template_value(Note::DSharp) + on(toggle_note), + ui_checkbox("E") + template_value(Note::E) + on(toggle_note), + ui_checkbox("F") + template_value(Note::F) + on(toggle_note), + ui_checkbox("F#") + template_value(Note::FSharp) + on(toggle_note), + ui_checkbox("G") + template_value(Note::G) + on(toggle_note), + ui_checkbox("G#") + template_value(Note::GSharp) + on(toggle_note), + ] + ] + } + } + + fn octaves() -> impl Scene { + bsn! { + subpane() + subpane_header() Children [ + Text("Octaves") ThemedText + ] + subpane_body() + Node { + display: Display::Flex, + flex_direction: FlexDirection::Row, + align_items: AlignItems::Stretch, + justify_content: JustifyContent::Start, + } + Children [ + @FeathersSlider { @max: 11., @value: 5., @min: 1. } + SliderStep(2.) + SliderPrecision(0) + on(|value_change: On>, mut pitch_map: ResMut| { + if value_change.is_final { + pitch_map.octaves = value_change.value as usize; + } + }) + on(slider_self_update), + ] + } + } + bsn! { Node { display: Display::Flex, @@ -447,134 +651,16 @@ fn primary_ui() -> impl Scene { on(toggle_main_ui) // todo: toggle main ui Children [ - Text("Conways Game of Life With Sound") ThemedText, + Text("Conways Game of Life With Sound (click here to open control panel)") ThemedText, ], pane_body() MainUi::Visible ToggleStateVisible Children [ - subpane() - Node { - display: Display::Flex, - flex_direction: FlexDirection::Row, - align_items: AlignItems::Stretch, - justify_content: JustifyContent::Start, - - } - Children [ - subpane_header() Children [ - Text("Nav") ThemedText - ] - subpane_body() - Node { - display: Display::Flex, - flex_direction: FlexDirection::Row, - align_items: AlignItems::Stretch, - 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) - ActionSource(SimAction::Step), - ui_button(UiButton::Reset) - ActionSource(SimAction::Reset), - ui_button(UiButton::Wipe) - ActionSource(SimAction::Wipe), - ui_button(UiButton::ZoomOut) - 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), - ] - ], - subpane() - Children [ - subpane_header() Children [ - Text("Notes") ThemedText - ] - subpane_body() - Node { - display: Display::Flex, - flex_direction: FlexDirection::Row, - align_items: AlignItems::Stretch, - justify_content: JustifyContent::Start, - } - Children [ - ui_checkbox("A") - template_value(Note::A) - on(toggle_note), - ui_checkbox("A#") - template_value(Note::ASharp) - on(toggle_note), - ui_checkbox("B") - template_value(Note::B) - on(toggle_note), - ui_checkbox("C") - template_value(Note::C) - on(toggle_note), - ui_checkbox("C#") - template_value(Note::CSharp) - on(toggle_note), - ui_checkbox("D") - template_value(Note::D) - on(toggle_note), - ui_checkbox("D#") - template_value(Note::DSharp) - on(toggle_note), - ui_checkbox("E") - template_value(Note::E) - on(toggle_note), - ui_checkbox("F") - template_value(Note::F) - on(toggle_note), - ui_checkbox("F#") - template_value(Note::FSharp) - on(toggle_note), - ui_checkbox("G") - template_value(Note::G) - on(toggle_note), - ui_checkbox("G#") - template_value(Note::GSharp) - on(toggle_note), - ] - ], - subpane() - subpane_header() Children [ - Text("Octaves") ThemedText - ] - subpane_body() - Node { - display: Display::Flex, - flex_direction: FlexDirection::Row, - align_items: AlignItems::Stretch, - justify_content: JustifyContent::Start, - } - Children [ - @FeathersSlider { @max: 5., @value: 3., @min: 1. } - SliderStep(1.) - SliderPrecision(0) - on(|value_change: On>, mut pitch_map: ResMut| { - if value_change.is_final { - pitch_map.octaves_up = value_change.value as usize; - debug!("(octave up) Updated pitch map: {:#?}", pitch_map); - } - }) - on(slider_self_update), - @FeathersSlider { @max: 5., @value: 3., @min: 1. } - SliderStep(1.) - SliderPrecision(0) - on(|value_change: On>, mut pitch_map: ResMut| { - if value_change.is_final { - pitch_map.octaves_down = value_change.value as usize; - debug!("(octave down) Updated pitch map: {:#?}", pitch_map); - } - }) - on(slider_self_update), - ], + controls(), + notes(), + octaves(), + description_text(), ] ] } @@ -641,10 +727,10 @@ impl Coordinates { /// For the purposes of creating assests, what does the given coordinate translate to CellPitch wise fn to_cell_pitch(&self, pm: &PitchMap) -> CellPitch { let rows = pm.notes.len(); - let cols = pm.octaves_up + pm.octaves_down + 1; + let cols = pm.octaves; let this_x = self.x.rem_euclid(rows as isize); - let this_y = self.y.rem_euclid(cols as isize) - (pm.octaves_down as isize); + let this_y = self.y.rem_euclid(cols as isize) - (pm.octaves as isize / 2); debug_assert!(this_x >= 0); @@ -1091,15 +1177,11 @@ fn load_meshes(mut cell_assets: ResMut, mut meshes: ResMut) { // https://playgameoflife.com/lexicon/R-pentomino - [ - (0,0), - (0,-1), - (-1,0), - (0,1), - (1,1), - ].into_iter().for_each(|(x, y)| { - writer.write(Lifecycle::Alive(Coordinates { x, y })); - }); + [(0, 0), (0, -1), (-1, 0), (0, 1), (1, 1)] + .into_iter() + .for_each(|(x, y)| { + writer.write(Lifecycle::Alive(Coordinates { x, y })); + }); } // TODO: Do not place piece if panning @@ -1185,16 +1267,14 @@ fn manage_interactive( #[derive(Resource, Debug)] struct PitchMap { notes: Vec, - octaves_up: usize, - octaves_down: usize, + octaves: usize, } impl Default for PitchMap { fn default() -> Self { Self { notes: vec![Note::A, Note::B, Note::CSharp, Note::E, Note::FSharp], - octaves_up: 3, - octaves_down: 3, + octaves: 5, } } } @@ -1215,8 +1295,7 @@ impl PitchMap { fn test_cell_pitch() { let pm = PitchMap { notes: vec![Note::A, Note::B, Note::C], - octaves_up: 1, - octaves_down: 1, + octaves: 3, }; let xs = -6..=5; let ys = -6..=4; @@ -1335,3 +1414,41 @@ fn toggle_state_visible( }; }) } + +fn camera_follow( + mut camera: Query<&mut Coordinates, With>, + cells: Query<&Coordinates, (With, Without)>, +) { + camera.iter_mut().for_each(|mut c| { + let left = cells + .iter() + .map(|Coordinates { x, .. }| *x) + .min() + .unwrap_or(0); + let right = cells + .iter() + .map(|Coordinates { x, .. }| *x) + .max() + .unwrap_or(0); + + let top = cells + .iter() + .map(|Coordinates { y, .. }| *y) + .max() + .unwrap_or(0); + let bottom = cells + .iter() + .map(|Coordinates { y, .. }| *y) + .min() + .unwrap_or(0); + + c.x = (right + left) / 2; + c.y = (top + bottom) / 2; + }); +} + +fn update_camera_position(mut query: Query<(&Coordinates, &mut Transform), With>) { + query.iter_mut().for_each(|(c, mut t)| { + t.translation = c.as_translation(); + }); +} diff --git a/scripts/webbuild b/scripts/webbuild index 53d47e4..d4c8c0e 100755 --- a/scripts/webbuild +++ b/scripts/webbuild @@ -35,8 +35,17 @@ wasm-bindgen \ --out-name "bin" \ "${WASM}" -# wasm-opt size pass +# wasm-opt size pass. +# Enable the wasm features rustc/wasm-bindgen emit (bulk-memory etc.) so +# validation passes, and strip debug/producers sections for size. wasm-opt -Oz \ + --strip-debug --strip-producers \ + --enable-bulk-memory \ + --enable-nontrapping-float-to-int \ + --enable-sign-ext \ + --enable-reference-types \ + --enable-mutable-globals \ + --enable-multivalue \ -o "${DIST}/bin_bg-tmp.wasm" \ "${DIST}/bin_bg.wasm" mv "${DIST}/bin_bg-tmp.wasm" "${DIST}/bin_bg.wasm" diff --git a/scripts/webzip b/scripts/webzip new file mode 100755 index 0000000..43383fe --- /dev/null +++ b/scripts/webzip @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +TARGET=$1 +NAME="$(basename "${TARGET}")" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +DIST="${REPO_ROOT}/dist/${TARGET}" +ARCHIVE="${REPO_ROOT}/dist/${NAME}.tar.zst" + +if [[ ! -d "${DIST}" ]]; then + echo "error: nothing built at ${DIST}; run 'just webbuild ${TARGET}' first" >&2 + exit 1 +fi + +# Archive the built web output (files at archive root). The host serves the +# unpacked files, so the archive only affects upload size -- use zstd at its +# max ratio (--ultra -22), which beats zip's DEFLATE substantially on the +# wasm-dominated payload while decompressing far faster than xz. +rm -f "${ARCHIVE}" +tar -C "${DIST}" -cf - . | zstd -q --ultra -22 -c > "${ARCHIVE}" + +echo "wrote ${ARCHIVE}"