Merge branch 'web'

pull/279/head
Ben Hansen 3 years ago
commit ac73aee9f5

@ -8,7 +8,10 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Install and Build
- name: Build WASM
run: sh build-wasm.sh
- name: Install and Build Vuepress
run: |
npm install
npm run build

151
Cargo.lock generated

@ -38,9 +38,9 @@ checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
[[package]]
name = "ahash"
version = "0.7.5"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "991984e3fd003e7ba02eb724f87a0f997b78677c46c0e91f8424ad7394c9886a"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom",
"once_cell",
@ -227,9 +227,9 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
[[package]]
name = "bumpalo"
version = "3.7.1"
version = "3.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538"
checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
[[package]]
name = "bytemuck"
@ -348,12 +348,6 @@ dependencies = [
"libloading 0.7.1",
]
[[package]]
name = "claxon"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bfbf56724aa9eca8afa4fcfadeb479e722935bb2a0900c2d37e0cc477af0688"
[[package]]
name = "cmake"
version = "0.1.46"
@ -440,6 +434,26 @@ dependencies = [
"winit",
]
[[package]]
name = "console_error_panic_hook"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
dependencies = [
"cfg-if 1.0.0",
"wasm-bindgen",
]
[[package]]
name = "console_log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501a375961cef1a0d44767200e66e4a559283097e91d0730b1d75dfb2f8a1494"
dependencies = [
"log",
"web-sys",
]
[[package]]
name = "copyless"
version = "0.1.5"
@ -568,6 +582,7 @@ dependencies = [
"parking_lot",
"stdweb",
"thiserror",
"wasm-bindgen",
"web-sys",
"winapi",
]
@ -856,8 +871,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
"libc",
"wasi",
"wasm-bindgen",
]
[[package]]
@ -1083,9 +1100,9 @@ checksum = "90953f308a79fe6d62a4643e51f848fbfddcd05975a38e69fdf4ab86a7baf7ca"
[[package]]
name = "instant"
version = "0.1.11"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "716d3d89f35ac6a34fd0eed635395f4c3b76fa889338a4632e5231a8684216bd"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
@ -1168,22 +1185,11 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[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.103"
version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6"
checksum = "7b2f96d100e1cf1929e7719b7edb3b90ab5298072638fccd77be9ce942ecdfce"
[[package]]
name = "libloading"
@ -1306,26 +1312,6 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c64630dcdd71f1a64c435f54885086a0de5d6a12d104d69b165fb7d5286d677"
[[package]]
name = "minimp3"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985438f75febf74c392071a975a29641b420dd84431135a6e6db721de4b74372"
dependencies = [
"minimp3-sys",
"slice-deque",
"thiserror",
]
[[package]]
name = "minimp3-sys"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e21c73734c69dc95696c9ed8926a2b393171d98b3f5f5935686a26a487ab9b90"
dependencies = [
"cc",
]
[[package]]
name = "miniz_oxide"
version = "0.3.7"
@ -1347,9 +1333,9 @@ dependencies = [
[[package]]
name = "mio"
version = "0.7.13"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16"
checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
dependencies = [
"libc",
"log",
@ -1652,15 +1638,6 @@ dependencies = [
"cc",
]
[[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.8.0"
@ -1749,9 +1726,9 @@ checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
[[package]]
name = "pkg-config"
version = "0.3.20"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c9b1041b4387893b91ee6746cddfc28516aff326a3519fb2adf820932c5e6cb"
checksum = "10e2fcbb64ecbe64c8e040a386c3104d384583af58b956d870aaaf229df6e66d"
[[package]]
name = "png"
@ -1777,13 +1754,24 @@ version = "0.1.0"
dependencies = [
"anyhow",
"bytemuck",
"cfg-if 1.0.0",
"cgmath",
"console_error_panic_hook",
"console_log",
"env_logger",
"fs_extra",
"getrandom",
"glob",
"instant",
"log",
"naga",
"pollster",
"rand",
"rayon",
"rodio",
"shaderc",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"wgpu",
"wgpu_glyph",
"winit",
@ -1990,18 +1978,15 @@ version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d98f5e557b61525057e2bc142c8cd7f0e70d75dc32852309bec440e6e046bf9"
dependencies = [
"claxon",
"cpal",
"hound",
"lewton",
"minimp3",
]
[[package]]
name = "ron"
version = "0.6.5"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45005aa836116903a49cf3461474da697cfe66221762c6e95871092009ec86d6"
checksum = "86018df177b1beef6c7c8ef949969c4f7cb9a9344181b92486b23c79995bdaa4"
dependencies = [
"base64",
"bitflags",
@ -2123,17 +2108,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
[[package]]
name = "slice-deque"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31ef6ee280cdefba6d2d0b4b78a84a1c1a3f3a4cec98c2d4231c8bc225de0f25"
dependencies = [
"libc",
"mach",
"winapi",
]
[[package]]
name = "slotmap"
version = "1.0.6"
@ -2267,21 +2241,6 @@ dependencies = [
"weezl",
]
[[package]]
name = "tinyvec"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83b2a3d4d9091d0abd7eba4dc2710b1718583bd4d8992e2190720ea38f391f7"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tobj"
version = "2.0.4"
@ -2294,7 +2253,7 @@ version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "267a53ebc1139c69d3feedc33a381c382b2e3b0c47f44f48679e90c4a69b8dac"
dependencies = [
"ahash 0.7.5",
"ahash 0.7.6",
]
[[package]]
@ -2824,9 +2783,9 @@ dependencies = [
[[package]]
name = "wgpu-core"
version = "0.11.0"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45af76ba5545b61a6904f26cdcf4287329144ae9e12f0c23ec4c9be982d675a6"
checksum = "3bdcbfa4885b32c2b1feb2faeb8b6a76065b752b8f08751b82f994e937687f46"
dependencies = [
"arrayvec",
"bitflags",
@ -2848,9 +2807,9 @@ dependencies = [
[[package]]
name = "wgpu-hal"
version = "0.11.2"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11095a81f4406b1e594dab7dc35d6508409d364e62458f2e5b07b3edc7aca517"
checksum = "0e493835d9edb153d5c8a9d8d016e1811dbe32ddb707a110be1453c7b051d3ec"
dependencies = [
"arrayvec",
"ash",
@ -2991,7 +2950,9 @@ dependencies = [
"raw-window-handle",
"scopeguard",
"smithay-client-toolkit",
"wasm-bindgen",
"wayland-client",
"web-sys",
"winapi",
"x11-dl",
]

@ -0,0 +1 @@
wasm-pack build --out-dir ../../../docs/.vuepress/components/wasm/pong code/showcase/pong

@ -4,9 +4,13 @@ version = "0.1.0"
authors = ["Ben Hansen <bhbenjaminhansen@gmail.com>"]
edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
cfg-if = "1"
env_logger = "0.9"
winit = "0.25"
shaderc = "0.7"
anyhow = "1.0"
bytemuck = { version = "1.4", features = [ "derive" ] }
cgmath = "0.18"
@ -14,10 +18,28 @@ pollster = "0.2"
wgpu = { version = "0.11", features = ["spirv"]}
wgpu_glyph = "0.15"
rand = "0.8"
rodio = "0.14"
rodio = { version = "0.14", default-features = false, features = ["wav"] }
log = "0.4"
instant = "0.1"
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.6"
console_log = "0.2.0"
getrandom = { version = "0.2", features = ["js"] }
rodio = { version = "0.14", default-features = false, features = ["wasm-bindgen", "wav"] }
wasm-bindgen = "0.2.76"
wasm-bindgen-futures = "0.4.20"
web-sys = { version = "0.3.53", features = [
"Document",
"Window",
"Element",
]}
winit = { version = "0.25", features = ["web-sys"] }
wgpu = { version = "0.11", features = ["spirv", "webgl"]}
[build-dependencies]
anyhow = "1.0"
fs_extra = "1.2"
glob = "0.3"
shaderc = "0.7"
rayon = "1.4"
naga = { version = "0.7", features = ["glsl-in", "spv-out", "wgsl-out"]}

@ -1,81 +1,97 @@
use anyhow::*;
use fs_extra::copy_items;
use fs_extra::dir::CopyOptions;
use glob::glob;
use std::fs::{read_to_string, write};
use std::path::PathBuf;
use naga::back::wgsl;
use naga::front::glsl::Options;
use naga::front::glsl::Parser;
use rayon::prelude::*;
use std::env;
use std::{fs::read_to_string, path::PathBuf};
struct ShaderData {
src: String,
src_path: PathBuf,
spv_path: PathBuf,
kind: shaderc::ShaderKind,
}
pub fn load_shader(src_path: PathBuf) -> Result<()> {
let extension = src_path
.extension()
.context("File has no extension")?
.to_str()
.context("Extension cannot be converted to &str")?;
let kind = match extension {
"vert" => naga::ShaderStage::Vertex,
"frag" => naga::ShaderStage::Fragment,
"comp" => naga::ShaderStage::Compute,
_ => bail!("Unsupported shader: {}", src_path.display()),
};
let src = read_to_string(src_path.clone())?;
// let spv_path = src_path.with_extension(format!("{}.spv", extension));
let wgsl_path = src_path.with_extension(format!("{}.wgsl", extension));
let mut parser = Parser::default();
let options = Options::from(kind);
let module = match parser.parse(&options, &src) {
Ok(it) => it,
Err(errors) => {
bail!(
"Failed to compile shader: {}\nErrors:\n{:#?}",
src_path.display(),
errors
);
}
};
impl ShaderData {
pub fn load(src_path: PathBuf) -> Result<Self> {
let extension = src_path
.extension()
.context("File has no extension")?
.to_str()
.context("Extension cannot be converted to &str")?;
let kind = match extension {
"vert" => shaderc::ShaderKind::Vertex,
"frag" => shaderc::ShaderKind::Fragment,
"comp" => shaderc::ShaderKind::Compute,
_ => bail!("Unsupported shader: {}", src_path.display()),
};
let info = naga::valid::Validator::new(
naga::valid::ValidationFlags::all(),
naga::valid::Capabilities::empty(),
)
.validate(&module)?;
std::fs::write(wgsl_path, wgsl::write_string(&module, &info)?)?;
let src = read_to_string(src_path.clone())?;
let spv_path = src_path.with_extension(format!("{}.spv", extension));
// let flags = spv::WriterFlags::DEBUG | spv::WriterFlags::ADJUST_COORDINATE_SPACE;
// let options = spv::Options {
// flags,
// ..Default::default()
// };
// let spv = spv::write_vec(&module, &info, &options)?;
// let dis = rspirv::dr::load_words(spv)
// .expect("Unable to disassemble shader")
// .disassemble();
// std::fs::write(spv_path, &spv)?;
Ok(Self {
src,
src_path,
spv_path,
kind,
})
}
Ok(())
}
fn main() -> Result<()> {
// This tells cargo to rerun this script if something in /src/ changes.
println!("cargo:rerun-if-changed=src/*");
// Collect all shaders recursively within /src/
let mut shader_paths = [
glob("./res/**/*.vert")?,
glob("./res/**/*.frag")?,
glob("./res/**/*.comp")?,
];
// UDPATED!
let shader_paths = {
let mut data = Vec::new();
data.extend(glob("./res/**/*.vert")?);
data.extend(glob("./res/**/*.frag")?);
data.extend(glob("./res/**/*.comp")?);
data
};
// This could be parallelized
let shaders = shader_paths
.iter_mut()
.flatten()
.map(|glob_result| ShaderData::load(glob_result?))
// UPDATED!
// This is parallelized
shader_paths
.into_par_iter()
.map(|glob_result| load_shader(glob_result?))
.collect::<Vec<Result<_>>>()
.into_iter()
.collect::<Result<Vec<_>>>()?;
let mut compiler = shaderc::Compiler::new().context("Unable to create shader compiler")?;
// This can't be parallelized. The [shaderc::Compiler] is not
// thread safe. Also, it creates a lot of resources. You could
// spawn multiple processes to handle this, but it would probably
// be better just to only compile shaders that have been changed
// recently.
for shader in shaders {
// This tells cargo to rerun this script if something in /src/ changes.
println!(
"cargo:rerun-if-changed={}",
shader.src_path.as_os_str().to_str().unwrap()
);
// This tells cargo to rerun this script if something in /res/ changes.
println!("cargo:rerun-if-changed=res/*");
let compiled = compiler.compile_into_spirv(
&shader.src,
shader.kind,
shader.src_path.to_str().unwrap(),
"main",
None,
)?;
write(shader.spv_path, compiled.as_binary_u8())?;
}
let out_dir = env::var("OUT_DIR")?;
let mut copy_options = CopyOptions::new();
let mut paths_to_copy = Vec::new();
copy_options.overwrite = true;
paths_to_copy.push("res/");
copy_items(&paths_to_copy, out_dir, &copy_options)?;
Ok(())
}

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pong with WASM</title>
</head>
<body>
<script type="module">
import init from "./pkg/pong.js";
init().then(() => {
console.log("WASM Loaded");
});
</script>
<style>
canvas {
background-color: black;
}
</style>
</body>
</html>

@ -0,0 +1,17 @@
struct FragmentOutput {
[[location(0)]] fColor: vec4<f32>;
};
var<private> fColor: vec4<f32>;
fn main1() {
fColor = vec4<f32>(f32(1));
return;
}
[[stage(fragment)]]
fn main() -> FragmentOutput {
main1();
let e3: vec4<f32> = fColor;
return FragmentOutput(e3);
}

@ -0,0 +1,20 @@
struct VertexOutput {
[[builtin(position)]] member: vec4<f32>;
};
var<private> aPosition1: vec2<f32>;
var<private> gl_Position: vec4<f32>;
fn main1() {
let e2: vec2<f32> = aPosition1;
gl_Position = vec4<f32>(e2, f32(0), f32(1));
return;
}
[[stage(vertex)]]
fn main([[location(0)]] aPosition: vec2<f32>) -> VertexOutput {
aPosition1 = aPosition;
main1();
let e5: vec4<f32> = gl_Position;
return VertexOutput(e5);
}

@ -0,0 +1,256 @@
mod input;
mod render;
mod sound;
mod state;
mod system;
mod util;
use input::Input;
use system::System;
#[cfg(target_arch="wasm32")]
use wasm_bindgen::prelude::*;
use winit::dpi::PhysicalSize;
use winit::event::*;
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::{Fullscreen, WindowBuilder};
#[cfg_attr(target_arch="wasm32", wasm_bindgen(start))]
pub fn start() {
cfg_if::cfg_if! {
if #[cfg(target_arch = "wasm32")] {
console_log::init_with_level(log::Level::Info).expect("Could't initialize logger");
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
} else {
env_logger::init();
}
}
let event_loop = EventLoop::new();
let monitor = event_loop.primary_monitor().unwrap();
let video_mode = monitor.video_modes().next();
let size = video_mode.clone().map_or(PhysicalSize::new(800, 600), |vm| vm.size());
let window = WindowBuilder::new()
.with_visible(false)
.with_title("Pong")
.with_fullscreen(video_mode.map(|vm| Fullscreen::Exclusive(vm)))
.build(&event_loop)
.unwrap();
if window.fullscreen().is_none() {
window.set_inner_size(PhysicalSize::new(512, 512));
}
window.set_cursor_visible(false);
#[cfg(target_arch = "wasm32")]
{
use winit::platform::web::WindowExtWebSys;
web_sys::window()
.and_then(|win| win.document())
.and_then(|doc| doc.body())
.and_then(|body| {
body.append_child(&web_sys::Element::from(window.canvas())).ok()
})
.expect("Couldn't append cavas to document body.");
}
log::info!("Setup...");
let mut render = pollster::block_on(render::Render::new(&window, size));
let mut state = state::State {
ball: state::Ball {
position: (0.0, 0.0).into(),
velocity: (0.0, 0.0).into(),
radius: 0.05,
visible: true,
},
player1: state::Player {
position: (-0.8, 0.0).into(),
size: (0.05, 0.4).into(),
score: 0,
visible: true,
},
player2: state::Player {
position: (0.8, 0.0).into(),
size: (0.05, 0.4).into(),
score: 0,
visible: true,
},
title_text: state::Text {
position: (20.0, 20.0).into(),
color: (1.0, 1.0, 1.0, 1.0).into(),
text: String::from("PONG"),
size: 64.0,
..Default::default()
},
play_button: state::Text {
position: (40.0, 100.0).into(),
color: (1.0, 1.0, 1.0, 1.0).into(),
text: String::from("Play"),
size: 32.0,
centered: false,
..Default::default()
},
quit_button: state::Text {
position: (40.0, 160.0).into(),
color: (1.0, 1.0, 1.0, 1.0).into(),
text: String::from("Quit"),
size: 32.0,
..Default::default()
},
player1_score: state::Text {
position: (render.width() * 0.25, 20.0).into(),
color: (1.0, 1.0, 1.0, 1.0).into(),
text: String::from("0"),
size: 32.0,
..Default::default()
},
player2_score: state::Text {
position: (render.width() * 0.75, 20.0).into(),
color: (1.0, 1.0, 1.0, 1.0).into(),
text: String::from("0"),
size: 32.0,
..Default::default()
},
win_text: state::Text {
position: (render.width() * 0.5, render.height() * 0.5).into(),
bounds: (render.width(), state::UNBOUNDED_F32).into(),
size: 32.0,
centered: true,
..Default::default()
},
game_state: state::GameState::MainMenu,
};
log::info!("Sound...");
let sound_system = sound::SoundSystem::new();
let sound_pack = sound::SoundPack::new();
let mut events = Vec::new();
let mut input = Input::new();
log::info!("Initializing Systems...");
let mut menu_system = system::MenuSystem;
let mut serving_system = system::ServingSystem::new();
let mut play_system = system::PlaySystem;
let ball_system = system::BallSystem;
let mut game_over_system = system::GameOverSystem::new();
let mut visiblity_system = system::VisibilitySystem;
visiblity_system.start(&mut state);
menu_system.start(&mut state);
window.set_visible(true);
log::info!("Event Loop...");
event_loop.run(move |event, _, control_flow| {
*control_flow = if state.game_state == state::GameState::Quiting {
ControlFlow::Exit
} else {
ControlFlow::Poll
};
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
state.game_state = state::GameState::Quiting;
}
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: element_state,
virtual_keycode: Some(key),
..
},
..
},
..
} => {
let input_handled = match state.game_state {
state::GameState::Quiting => true,
_ => input.update(key, element_state),
};
if !input_handled {
process_input(element_state, key, control_flow);
}
}
Event::RedrawRequested(_) => {
for event in &events {
match event {
state::Event::FocusChanged | state::Event::ButtonPressed => {
sound_system.queue(sound_pack.bounce());
}
state::Event::BallBounce(_pos) => {
sound_system.queue(sound_pack.bounce());
}
state::Event::Score(_) => {
sound_system.queue(sound_pack.bounce());
}
}
}
events.clear();
visiblity_system.update_state(&input, &mut state, &mut events);
match state.game_state {
state::GameState::MainMenu => {
menu_system.update_state(&input, &mut state, &mut events);
if state.game_state == state::GameState::Serving {
serving_system.start(&mut state);
}
}
state::GameState::Serving => {
serving_system.update_state(&input, &mut state, &mut events);
play_system.update_state(&input, &mut state, &mut events);
if state.game_state == state::GameState::Playing {
play_system.start(&mut state);
}
}
state::GameState::Playing => {
ball_system.update_state(&input, &mut state, &mut events);
play_system.update_state(&input, &mut state, &mut events);
if state.game_state == state::GameState::Serving {
serving_system.start(&mut state);
} else if state.game_state == state::GameState::GameOver {
game_over_system.start(&mut state);
}
}
state::GameState::GameOver => {
game_over_system.update_state(&input, &mut state, &mut events);
if state.game_state == state::GameState::MainMenu {
menu_system.start(&mut state);
}
}
state::GameState::Quiting => {}
}
render.render_state(&state);
if state.game_state != state::GameState::Quiting {
window.request_redraw();
}
}
_ => {}
}
});
}
fn process_input(
element_state: ElementState,
keycode: VirtualKeyCode,
control_flow: &mut ControlFlow,
) {
match (keycode, element_state) {
(VirtualKeyCode::Escape, ElementState::Pressed) => {
*control_flow = ControlFlow::Exit;
}
_ => {}
}
}

@ -1,216 +1,3 @@
mod input;
mod render;
mod sound;
mod state;
mod system;
mod util;
use input::Input;
use system::System;
use winit::event::*;
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::{Fullscreen, WindowBuilder};
fn main() {
let event_loop = EventLoop::new();
let monitor = event_loop.primary_monitor().unwrap();
let video_mode = monitor.video_modes().next().unwrap();
let window = WindowBuilder::new()
.with_visible(false)
.with_title("Pong")
.with_fullscreen(Some(Fullscreen::Exclusive(video_mode.clone())))
.build(&event_loop)
.unwrap();
window.set_cursor_visible(false);
let mut render = pollster::block_on(render::Render::new(&window, &video_mode));
let mut state = state::State {
ball: state::Ball {
position: (0.0, 0.0).into(),
velocity: (0.0, 0.0).into(),
radius: 0.05,
visible: true,
},
player1: state::Player {
position: (-0.8, 0.0).into(),
size: (0.05, 0.4).into(),
score: 0,
visible: true,
},
player2: state::Player {
position: (0.8, 0.0).into(),
size: (0.05, 0.4).into(),
score: 0,
visible: true,
},
title_text: state::Text {
position: (20.0, 20.0).into(),
color: (1.0, 1.0, 1.0, 1.0).into(),
text: String::from("PONG"),
size: 64.0,
..Default::default()
},
play_button: state::Text {
position: (40.0, 100.0).into(),
color: (1.0, 1.0, 1.0, 1.0).into(),
text: String::from("Play"),
size: 32.0,
centered: false,
..Default::default()
},
quit_button: state::Text {
position: (40.0, 160.0).into(),
color: (1.0, 1.0, 1.0, 1.0).into(),
text: String::from("Quit"),
size: 32.0,
..Default::default()
},
player1_score: state::Text {
position: (render.width() * 0.25, 20.0).into(),
color: (1.0, 1.0, 1.0, 1.0).into(),
text: String::from("0"),
size: 32.0,
..Default::default()
},
player2_score: state::Text {
position: (render.width() * 0.75, 20.0).into(),
color: (1.0, 1.0, 1.0, 1.0).into(),
text: String::from("0"),
size: 32.0,
..Default::default()
},
win_text: state::Text {
position: (render.width() * 0.5, render.height() * 0.5).into(),
bounds: (render.width(), state::UNBOUNDED_F32).into(),
size: 32.0,
centered: true,
..Default::default()
},
game_state: state::GameState::MainMenu,
};
let sound_system = sound::SoundSystem::new();
let sound_pack = sound::SoundPack::new();
let mut events = Vec::new();
let mut input = Input::new();
let mut menu_system = system::MenuSystem;
let mut serving_system = system::ServingSystem::new();
let mut play_system = system::PlaySystem;
let ball_system = system::BallSystem;
let mut game_over_system = system::GameOverSystem::new();
let mut visiblity_system = system::VisibilitySystem;
visiblity_system.start(&mut state);
menu_system.start(&mut state);
window.set_visible(true);
event_loop.run(move |event, _, control_flow| {
*control_flow = if state.game_state == state::GameState::Quiting {
ControlFlow::Exit
} else {
ControlFlow::Poll
};
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
state.game_state = state::GameState::Quiting;
}
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: element_state,
virtual_keycode: Some(key),
..
},
..
},
..
} => {
let input_handled = match state.game_state {
state::GameState::Quiting => true,
_ => input.update(key, element_state),
};
if !input_handled {
process_input(element_state, key, control_flow);
}
}
Event::RedrawRequested(_) => {
for event in &events {
match event {
state::Event::FocusChanged | state::Event::ButtonPressed => {
sound_system.queue(sound_pack.bounce());
}
state::Event::BallBounce(_pos) => {
sound_system.queue(sound_pack.bounce());
}
state::Event::Score(_) => {
sound_system.queue(sound_pack.bounce());
} // _ => {}
}
}
events.clear();
visiblity_system.update_state(&input, &mut state, &mut events);
match state.game_state {
state::GameState::MainMenu => {
menu_system.update_state(&input, &mut state, &mut events);
if state.game_state == state::GameState::Serving {
serving_system.start(&mut state);
}
}
state::GameState::Serving => {
serving_system.update_state(&input, &mut state, &mut events);
play_system.update_state(&input, &mut state, &mut events);
if state.game_state == state::GameState::Playing {
play_system.start(&mut state);
}
}
state::GameState::Playing => {
ball_system.update_state(&input, &mut state, &mut events);
play_system.update_state(&input, &mut state, &mut events);
if state.game_state == state::GameState::Serving {
serving_system.start(&mut state);
} else if state.game_state == state::GameState::GameOver {
game_over_system.start(&mut state);
}
}
state::GameState::GameOver => {
game_over_system.update_state(&input, &mut state, &mut events);
if state.game_state == state::GameState::MainMenu {
menu_system.start(&mut state);
}
}
state::GameState::Quiting => {}
}
render.render_state(&state);
if state.game_state != state::GameState::Quiting {
window.request_redraw();
}
}
_ => {}
}
});
}
fn process_input(
element_state: ElementState,
keycode: VirtualKeyCode,
control_flow: &mut ControlFlow,
) {
match (keycode, element_state) {
(VirtualKeyCode::Escape, ElementState::Pressed) => {
*control_flow = ControlFlow::Exit;
}
_ => {}
}
}
pong::start();
}

@ -3,7 +3,7 @@ mod buffer;
use std::iter;
use wgpu_glyph::{ab_glyph, Section, Text};
use winit::monitor::VideoMode;
use winit::dpi::PhysicalSize;
use winit::window::Window;
use buffer::*;
@ -36,7 +36,7 @@ impl Render {
self.config.height as f32
}
pub async fn new(window: &Window, video_mode: &VideoMode) -> Self {
pub async fn new(window: &Window, size: PhysicalSize<u32>) -> Self {
// The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::Backends::all());
@ -54,14 +54,13 @@ impl Render {
&wgpu::DeviceDescriptor {
label: None,
features: wgpu::Features::empty(),
limits: wgpu::Limits::default(),
limits: wgpu::Limits::downlevel_webgl2_defaults(),
},
None, // Trace path
)
.await
.unwrap();
let size = video_mode.size();
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface.get_preferred_format(&adapter).unwrap(),
@ -81,8 +80,8 @@ impl Render {
&pipeline_layout,
config.format,
&[Vertex::DESC],
wgpu::include_spirv!("../../res/shaders/textured.vert.spv"),
wgpu::include_spirv!("../../res/shaders/textured.frag.spv"),
wgpu::include_wgsl!("../../res/shaders/textured.vert.wgsl"),
wgpu::include_wgsl!("../../res/shaders/textured.frag.wgsl"),
);
let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
@ -193,12 +192,14 @@ impl Render {
self.staging_belt.finish();
self.queue.submit(iter::once(encoder.finish()));
frame.present();
}
Err(wgpu::SurfaceError::Outdated) => {
log::info!("Outdated surface texture");
self.surface.configure(&self.device, &self.config);
}
Err(e) => {
eprintln!("Error: {}", e);
log::error!("Error: {}", e);
}
}
}

@ -67,15 +67,19 @@ impl System for MenuSystem {
events.push(state::Event::FocusChanged);
state.play_button.focused = false;
state.quit_button.focused = true;
log::info!("Quit selected");
} else if state.quit_button.focused && input.ui_up_pressed() {
events.push(state::Event::FocusChanged);
state.quit_button.focused = false;
state.play_button.focused = true;
log::info!("Play selected");
}
if state.play_button.focused && input.enter_pressed {
log::info!("Starting game");
events.push(state::Event::ButtonPressed);
state.game_state = state::GameState::Serving;
log::info!("Quitting");
} else if state.quit_button.focused && input.enter_pressed {
events.push(state::Event::ButtonPressed);
state.game_state = state::GameState::Quiting;
@ -118,6 +122,7 @@ impl System for PlaySystem {
}
if state.player1.score > 2 || state.player2.score > 2 {
log::info!("Gameover");
state.game_state = state::GameState::GameOver;
}
}
@ -156,10 +161,12 @@ impl System for BallSystem {
}
if state.ball.position.x > 1.0 {
log::info!("Player 1 scored");
state.player1.score += 1;
state.game_state = state::GameState::Serving;
events.push(state::Event::Score(0));
} else if state.ball.position.x < -1.0 {
log::info!("Player 1 scored");
state.player2.score += 1;
state.game_state = state::GameState::Serving;
events.push(state::Event::Score(1));
@ -168,20 +175,20 @@ impl System for BallSystem {
}
pub struct ServingSystem {
last_time: std::time::Instant,
last_time: instant::Instant,
}
impl ServingSystem {
pub fn new() -> Self {
Self {
last_time: std::time::Instant::now(),
last_time: instant::Instant::now(),
}
}
}
impl System for ServingSystem {
fn start(&mut self, state: &mut state::State) {
self.last_time = std::time::Instant::now();
self.last_time = instant::Instant::now();
let direction = state.ball.position.x.signum();
state.ball.position = (0.0, 0.0).into();
state.ball.velocity = cgmath::Vector2::unit_x() * direction * -util::BALL_SPEED;
@ -195,29 +202,30 @@ impl System for ServingSystem {
state: &mut state::State,
_events: &mut Vec<state::Event>,
) {
let current_time = std::time::Instant::now();
let current_time = instant::Instant::now();
let delta_time = current_time - self.last_time;
if delta_time.as_secs_f32() > 2.0 {
log::info!("Serving...");
state.game_state = state::GameState::Playing;
}
}
}
pub struct GameOverSystem {
last_time: std::time::Instant,
last_time: instant::Instant,
}
impl GameOverSystem {
pub fn new() -> Self {
Self {
last_time: std::time::Instant::now(),
last_time: instant::Instant::now(),
}
}
}
impl System for GameOverSystem {
fn start(&mut self, state: &mut state::State) {
self.last_time = std::time::Instant::now();
self.last_time = instant::Instant::now();
state.player1_score.text = format!("{}", state.player1.score);
state.player2_score.text = format!("{}", state.player2.score);
@ -227,6 +235,8 @@ impl System for GameOverSystem {
} else {
String::from("Player 2 wins!")
};
log::info!("{}", state.win_text.text);
}
fn update_state(
@ -235,7 +245,7 @@ impl System for GameOverSystem {
state: &mut state::State,
_events: &mut Vec<state::Event>,
) {
let current_time = std::time::Instant::now();
let current_time = instant::Instant::now();
let delta_time = current_time - self.last_time;
if delta_time.as_secs_f32() > 1.0 {
state.game_state = state::GameState::MainMenu;

@ -3,7 +3,7 @@
<template>
<div>
<ul>
<li v-for="post in recentFiles">
<li v-for="i, post in recentFiles" :key="i">
<a :href="post.path">{{post.title}}</a>
</li>
</ul>

@ -0,0 +1,34 @@
<template>
<div class="wasm-example">
<div class="error" v-if="error">
{{ error }}
</div>
</div>
</template>
<script>
export default {
props: { example: "" },
data() {
return {
error: {
value: "",
required: true,
},
};
},
async mounted() {
await this.$nextTick();
try {
const init = await import(`./wasm/${this.example}/${this.example}.js`);
init().then(() => {
console.log("WASM Loaded");
});
} catch (e) {
this.error = `An error occurred loading "${this.example}": ${e}`;
console.error(e);
}
},
};
</script>

@ -263,3 +263,5 @@ I was going to have `BallBounce` play a positioned sound using a `SpatialSink`,
## Summary
A fun project to work on. It was overly architected, and kinda hard to make changes, but a good experience none the less.
<WasmExample example="pong"></WasmExample>

@ -0,0 +1,23 @@
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<!--
This is adapted from https://github.com/gfx-rs/wgpu/blob/master/run-wasm-example.sh
Eventually I'll have all the examples create there own components for vuepress to
use, but this will do for now.
-->
<script type="module">
import init from "./pong.js";
window.addEventListener("load", () => {
init();
});
</script>
</body>
</html>

@ -8,8 +8,8 @@
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "vuepress dev docs",
"build": "vuepress build docs",
"dev": "build-wasm.sh && vuepress dev docs",
"build": "build-wasm.sh && vuepress build docs",
"deploy": "sh deploy.sh"
},
"author": "",

Loading…
Cancel
Save