web zip for pages.elijah.run, enforced system ordering for cell de-dupe

main
Elijah Voigt 23 hours ago
parent a5825af471
commit 209e5f60c0

@ -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"

135
engine/Cargo.lock generated

@ -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"

@ -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]

@ -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<C: Component>(q: Query<Entity, Added<C>>) -> bool {
!q.is_empty()
}
pub fn any_component_changed<C: Component>(q: Query<Entity, Changed<C>>) -> bool {
!q.is_empty()
}
pub fn query_condition_empty<F: QueryFilter>(q: Query<Entity, F>) -> bool {
q.is_empty()
}

@ -37,6 +37,10 @@ fmt:
webbuild target:
scripts/webbuild {{target}}
# Zip the built web output from dist/<target>/ into dist/<name>.zip
webzip target:
scripts/webzip {{target}}
# Serve a built web target from dist/<target>/ via simple-http-server
serve target:
scripts/serve {{target}}

135
life/Cargo.lock generated

@ -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"

@ -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"

@ -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);

@ -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::<Lifecycle>),
update_grid_position.run_if(on_message::<CursorMoved>),
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::<SimulationPlayback>),
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::<CursorMoved>),
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::<SimulationPlayback>),
),
pause_simulation.run_if(in_state(SimulationPlayback::Step)),
)
.in_set(GameSet::Plan),
// Apply lifecycle message
cell_lifecycle
.run_if(on_message::<Lifecycle>)
.in_set(GameSet::Apply),
// React to board changes
(
reassign_cell_pitch.run_if(resource_changed::<PitchMap>),
update_material.run_if(
any_component_added::<CellPitch>
.or_else(any_component_changed::<CellPitch>),
),
camera_follow
.run_if(in_state(SimulationPlayback::Run))
.run_if(any_component_changed::<Coordinates>),
update_camera_position
.run_if(in_state(SimulationPlayback::Run))
.run_if(not(query_condition_empty::<(
With<Camera>,
Changed<Coordinates>,
)>)),
)
.in_set(GameSet::React),
),
)
// Audio Systems
@ -77,19 +108,29 @@ fn main() {
(
control_volume,
update_pitch_map_resource_ui.run_if(resource_changed::<PitchMap>),
reassign_cell_pitch.run_if(resource_changed::<PitchMap>),
update_audio,
update_audio
.run_if(
any_component_added::<CellPitch>
.or_else(any_component_changed::<CellPitch>),
)
.in_set(GameSet::React),
),
)
// Debugging
.add_systems(
Update,
(
assert_cell_uniqueness.run_if(any_with_component::<Cell>),
grid_gizmo,
update_debug_info_cell_count,
update_debug_info_coordinates,
update_debug_info_active,
(
assert_cell_uniqueness
.run_if(any_with_component::<Cell>)
.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<A: Scene, B: Scene>(
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>(Action::Quit),
ui_toggle_button((UiButton::Pause, bsn! { SimulationPlayback::Pause }), (UiButton::Play, bsn! { SimulationPlayback::Run }))
ActionSource<SimAction>(SimAction::Toggle),
ui_button(UiButton::Step)
ActionSource<SimAction>(SimAction::Step),
ui_button(UiButton::Reset)
ActionSource<SimAction>(SimAction::Reset),
ui_button(UiButton::Wipe)
ActionSource<SimAction>(SimAction::Wipe),
ui_button(UiButton::ZoomOut)
ActionSource<ZoomAction>(ZoomAction::ZoomOut),
ui_button(UiButton::ZoomIn)
ActionSource<ZoomAction>(ZoomAction::ZoomIn),
ui_toggle_button((UiButton::MuteAudio, bsn! { AudioPlayback::Mute }), (UiButton::PlayAudio, bsn! { AudioPlayback::Play }))
ActionSource<AudioAction>(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<ValueChange<f32>>, mut pitch_map: ResMut<PitchMap>| {
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>(Action::Quit),
ui_toggle_button((UiButton::Pause, bsn! { SimulationPlayback::Pause }), (UiButton::Play, bsn! { SimulationPlayback::Run }))
ActionSource<SimAction>(SimAction::Toggle),
ui_button(UiButton::Step)
ActionSource<SimAction>(SimAction::Step),
ui_button(UiButton::Reset)
ActionSource<SimAction>(SimAction::Reset),
ui_button(UiButton::Wipe)
ActionSource<SimAction>(SimAction::Wipe),
ui_button(UiButton::ZoomOut)
ActionSource<ZoomAction>(ZoomAction::ZoomOut),
ui_button(UiButton::ZoomIn)
ActionSource<ZoomAction>(ZoomAction::ZoomIn),
ui_toggle_button((UiButton::MuteAudio, bsn! { AudioPlayback::Mute }), (UiButton::PlayAudio, bsn! { AudioPlayback::Play }))
ActionSource<AudioAction>(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<ValueChange<f32>>, mut pitch_map: ResMut<PitchMap>| {
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<ValueChange<f32>>, mut pitch_map: ResMut<PitchMap>| {
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<CellAssets>, mut meshes: ResMut<Assets<Me
fn default_board(mut writer: MessageWriter<Lifecycle>) {
// 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<T: Component + States + PartialEq>(
#[derive(Resource, Debug)]
struct PitchMap {
notes: Vec<Note>,
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<S: States + Component>(
};
})
}
fn camera_follow(
mut camera: Query<&mut Coordinates, With<Camera>>,
cells: Query<&Coordinates, (With<Cell>, Without<Camera>)>,
) {
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<Camera>>) {
query.iter_mut().for_each(|(c, mut t)| {
t.translation = c.as_translation();
});
}

@ -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"

@ -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}"
Loading…
Cancel
Save