From e59e340777fd24d91ae02e0ecc35946180bbb01e Mon Sep 17 00:00:00 2001 From: paladin Date: Mon, 15 Jun 2026 12:44:21 +0100 Subject: [PATCH] Reorganising file and module structure. --- Cargo.lock | 22 ++ Cargo.toml | 1 + src/assets/script.lua | 0 src/{ => assets}/shader.wgsl | 0 src/{ => assets}/shader2.wgsl | 0 src/{ => assets}/test.png | Bin src/engine.rs | 479 +++++++++++++++++++++++++++++++++ src/examples/script.lua | 1 + src/examples/test.nel | 11 - src/lang.rs | 173 ++++++++++-- src/lib.rs | 484 +--------------------------------- src/main.rs | 2 +- 12 files changed, 662 insertions(+), 511 deletions(-) create mode 100644 src/assets/script.lua rename src/{ => assets}/shader.wgsl (100%) rename src/{ => assets}/shader2.wgsl (100%) rename src/{ => assets}/test.png (100%) create mode 100644 src/engine.rs create mode 100644 src/examples/script.lua delete mode 100644 src/examples/test.nel diff --git a/Cargo.lock b/Cargo.lock index 2a3d033..5e7fbbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -416,6 +416,27 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -577,6 +598,7 @@ dependencies = [ "anyhow", "console_error_panic_hook", "console_log", + "derive_more", "env_logger", "log", "pollster", diff --git a/Cargo.toml b/Cargo.toml index 81491de..2d22f8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ log = "0.4" wgpu = "29.0.3" pollster = "0.4.0" console_error_panic_hook = "0.1.7" +derive_more = "2.1.1" [target.'cfg(target_arch = "wasm32")'.dependencies] console_error_panic_hook = "0.1.6" diff --git a/src/assets/script.lua b/src/assets/script.lua new file mode 100644 index 0000000..e69de29 diff --git a/src/shader.wgsl b/src/assets/shader.wgsl similarity index 100% rename from src/shader.wgsl rename to src/assets/shader.wgsl diff --git a/src/shader2.wgsl b/src/assets/shader2.wgsl similarity index 100% rename from src/shader2.wgsl rename to src/assets/shader2.wgsl diff --git a/src/test.png b/src/assets/test.png similarity index 100% rename from src/test.png rename to src/assets/test.png diff --git a/src/engine.rs b/src/engine.rs new file mode 100644 index 0000000..31e602a --- /dev/null +++ b/src/engine.rs @@ -0,0 +1,479 @@ +#![recursion_limit = "256"] +// Credit of most code to https://sotrh.github.io/learn-wgpu/ since I'm not familiar with wgpu +use std::sync::Arc; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; +use winit::dpi::PhysicalPosition; +#[cfg(target_arch = "wasm32")] +use winit::platform::web::EventLoopExtWebSys; +use winit::{ + application::ApplicationHandler, + event::*, + event_loop::{ActiveEventLoop, EventLoop}, + keyboard::{KeyCode, PhysicalKey}, + window::Window, +}; + +// This will store the state of our game +pub struct State { + surface: wgpu::Surface<'static>, + device: wgpu::Device, + queue: wgpu::Queue, + config: wgpu::SurfaceConfiguration, + is_surface_configured: bool, + window: Arc, + render_pipeline1: wgpu::RenderPipeline, + render_pipeline2: wgpu::RenderPipeline, + other_pipeline: bool, + sky: wgpu::Color, +} + +impl State { + // We don't need this to be async right now, + // but we will in the next tutorial + pub async fn new(window: Arc) -> anyhow::Result { + let size = window.inner_size(); + + // The instance is a handle to our GPU + // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + #[cfg(not(target_arch = "wasm32"))] + backends: wgpu::Backends::PRIMARY, + #[cfg(target_arch = "wasm32")] + backends: wgpu::Backends::GL, + flags: Default::default(), + memory_budget_thresholds: Default::default(), + backend_options: Default::default(), + display: None, + }); + + let surface = instance.create_surface(window.clone()).unwrap(); + + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await?; + + let (device, queue) = adapter + .request_device(&wgpu::DeviceDescriptor { + label: None, + required_features: wgpu::Features::empty(), + experimental_features: wgpu::ExperimentalFeatures::disabled(), + // WebGL doesn't support all of wgpu's features, so if + // we're building for the web we'll have to disable some. + required_limits: if cfg!(target_arch = "wasm32") { + wgpu::Limits::downlevel_webgl2_defaults() + } else { + wgpu::Limits::default() + }, + memory_hints: Default::default(), + trace: wgpu::Trace::Off, + }) + .await?; + + let surface_caps = surface.get_capabilities(&adapter); + // Shader code in this tutorial assumes an sRGB surface texture. Using a different + // one will result in all the colors coming out darker. If you want to support non + // sRGB surfaces, you'll need to account for that when drawing to the frame. + let surface_format = surface_caps + .formats + .iter() + .find(|f| f.is_srgb()) + .copied() + .unwrap_or(surface_caps.formats[0]); + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface_format, + width: size.width, + height: size.height, + present_mode: surface_caps.present_modes[0], + alpha_mode: surface_caps.alpha_modes[0], + view_formats: vec![], + desired_maximum_frame_latency: 2, + }; + + let shader1 = device.create_shader_module(wgpu::include_wgsl!("assets/shader.wgsl")); + + let render_pipeline_layout1 = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[], + immediate_size: 0, + }); + + let render_pipeline1 = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render Pipeline"), + layout: Some(&render_pipeline_layout1), + vertex: wgpu::VertexState { + module: &shader1, + entry_point: Some("vs_main"), // 1. + buffers: &[], // 2. + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + // 3. + module: &shader1, + entry_point: Some("fs_main"), + targets: &[Some(wgpu::ColorTargetState { + // 4. + format: config.format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, // 1. + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, // 2. + cull_mode: Some(wgpu::Face::Back), + // Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE + polygon_mode: wgpu::PolygonMode::Fill, + // Requires Features::DEPTH_CLIP_CONTROL + unclipped_depth: false, + // Requires Features::CONSERVATIVE_RASTERIZATION + conservative: false, + }, + depth_stencil: None, // 1. + multisample: wgpu::MultisampleState { + count: 1, // 2. + mask: !0, // 3. + alpha_to_coverage_enabled: false, // 4. + }, + multiview_mask: None, // 5. + cache: None, // 6. + }); + + let shader2 = device.create_shader_module(wgpu::include_wgsl!("assets/shader2.wgsl")); + + let render_pipeline_layout2 = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[], + immediate_size: 0, + }); + + let render_pipeline2 = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render Pipeline"), + layout: Some(&render_pipeline_layout2), + vertex: wgpu::VertexState { + module: &shader2, + entry_point: Some("vs_main"), // 1. + buffers: &[], // 2. + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + // 3. + module: &shader2, + entry_point: Some("fs_main"), + targets: &[Some(wgpu::ColorTargetState { + // 4. + format: config.format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, // 1. + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, // 2. + cull_mode: Some(wgpu::Face::Back), + // Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE + polygon_mode: wgpu::PolygonMode::Fill, + // Requires Features::DEPTH_CLIP_CONTROL + unclipped_depth: false, + // Requires Features::CONSERVATIVE_RASTERIZATION + conservative: false, + }, + depth_stencil: None, // 1. + multisample: wgpu::MultisampleState { + count: 1, // 2. + mask: !0, // 3. + alpha_to_coverage_enabled: false, // 4. + }, + multiview_mask: None, // 5. + cache: None, // 6. + }); + + Ok(Self { + surface, + device, + queue, + config, + is_surface_configured: false, + window, + render_pipeline1, + render_pipeline2, + other_pipeline: false, + sky: wgpu::Color { + r: 0.1, + g: 0.2, + b: 0.3, + a: 1.0, + }, + }) + } + + pub fn resize(&mut self, width: u32, height: u32) { + if width > 0 && height > 0 { + let max = 2048; + self.config.width = width.min(max); + self.config.height = height.min(max); + self.surface.configure(&self.device, &self.config); + self.is_surface_configured = true; + } + } + + fn update(&mut self) { + // ... + } + + fn render(&mut self) -> anyhow::Result<()> { + self.window.request_redraw(); + + // We can't render unless the surface is configured + if !self.is_surface_configured { + return Ok(()); + } + + let output = match self.surface.get_current_texture() { + wgpu::CurrentSurfaceTexture::Success(surface_texture) => surface_texture, + wgpu::CurrentSurfaceTexture::Suboptimal(surface_texture) => { + self.surface.configure(&self.device, &self.config); + surface_texture + } + wgpu::CurrentSurfaceTexture::Timeout + | wgpu::CurrentSurfaceTexture::Occluded + | wgpu::CurrentSurfaceTexture::Validation => { + // Skip this frame + return Ok(()); + } + wgpu::CurrentSurfaceTexture::Outdated => { + self.surface.configure(&self.device, &self.config); + return Ok(()); + } + wgpu::CurrentSurfaceTexture::Lost => { + // You could recreate the devices and all resources + // created with it here, but we'll just bail + anyhow::bail!("Lost device"); + } + }; + let view = output + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Render Encoder"), + }); + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + depth_slice: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(self.sky), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + multiview_mask: None, + }); + + if self.other_pipeline { + render_pass.set_pipeline(&self.render_pipeline1); + } else { + render_pass.set_pipeline(&self.render_pipeline2); + } + render_pass.draw(0..3, 0..1); // 3. + } + + // submit will accept anything that implements IntoIter + self.queue.submit(std::iter::once(encoder.finish())); + output.present(); + + Ok(()) + } + + fn handle_key(&mut self, event_loop: &ActiveEventLoop, code: KeyCode, is_pressed: bool) { + match (code, is_pressed) { + (KeyCode::Escape, true) => event_loop.exit(), + (KeyCode::Space, true) => { + self.other_pipeline = !self.other_pipeline; + } + _ => {} + } + } + + fn handle_mouse_moved(&mut self, position: PhysicalPosition) { + self.sky = wgpu::Color { + r: 0.3, + g: position.x / 1000.0, + b: position.y / 1000.0, + a: 1.0, + } + } +} + +pub struct App { + #[cfg(target_arch = "wasm32")] + proxy: Option>, + state: Option, +} + +impl App { + pub fn new(#[cfg(target_arch = "wasm32")] event_loop: &EventLoop) -> Self { + #[cfg(target_arch = "wasm32")] + let proxy = Some(event_loop.create_proxy()); + Self { + state: None, + #[cfg(target_arch = "wasm32")] + proxy, + } + } +} + +impl ApplicationHandler for App { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + #[allow(unused_mut)] + let mut window_attributes = Window::default_attributes(); + + #[cfg(target_arch = "wasm32")] + { + use wasm_bindgen::JsCast; + use winit::platform::web::WindowAttributesExtWebSys; + + const CANVAS_ID: &str = "canvas"; + + let window = wgpu::web_sys::window().unwrap_throw(); + let document = window.document().unwrap_throw(); + let canvas = document.get_element_by_id(CANVAS_ID).unwrap_throw(); + let html_canvas_element = canvas.unchecked_into(); + window_attributes = window_attributes.with_canvas(Some(html_canvas_element)); + } + + let window = Arc::new(event_loop.create_window(window_attributes).unwrap()); + + #[cfg(not(target_arch = "wasm32"))] + { + // If we are not on web we can use pollster to + // await the window creation + self.state = Some(pollster::block_on(State::new(window)).unwrap()); + } + + #[cfg(target_arch = "wasm32")] + { + // Run the future asynchronously and use the + // proxy to send the results to the event loop + if let Some(proxy) = self.proxy.take() { + wasm_bindgen_futures::spawn_local(async move { + assert!( + proxy + .send_event( + State::new(window) + .await + .expect("Unable to create canvas!!!") + ) + .is_ok() + ) + }); + } + } + } + + #[allow(unused_mut)] + fn user_event(&mut self, _event_loop: &ActiveEventLoop, mut event: State) { + // This is where proxy.send_event() ends up + #[cfg(target_arch = "wasm32")] + { + event.window.request_redraw(); + event.resize( + event.window.inner_size().width, + event.window.inner_size().height, + ); + } + self.state = Some(event); + } + + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + _window_id: winit::window::WindowId, + event: WindowEvent, + ) { + let state = match &mut self.state { + Some(canvas) => canvas, + None => return, + }; + + match event { + WindowEvent::CloseRequested => event_loop.exit(), + WindowEvent::Resized(size) => state.resize(size.width, size.height), + WindowEvent::RedrawRequested => { + state.update(); + match state.render() { + Ok(_) => {} + Err(e) => { + // Log the error and exit gracefully + log::error!("{e}"); + event_loop.exit(); + } + } + } + WindowEvent::CursorMoved { position: pos, .. } => state.handle_mouse_moved(pos), + WindowEvent::KeyboardInput { + event: + KeyEvent { + physical_key: PhysicalKey::Code(code), + state: key_state, + .. + }, + .. + } => state.handle_key(event_loop, code, key_state.is_pressed()), + _ => {} + } + } +} + +pub fn run() -> anyhow::Result<()> { + #[cfg(not(target_arch = "wasm32"))] + { + env_logger::init(); + } + #[cfg(target_arch = "wasm32")] + { + console_log::init_with_level(log::Level::Info).unwrap_throw(); + } + + let event_loop = EventLoop::with_user_event().build()?; + #[cfg(not(target_arch = "wasm32"))] + { + let mut app = App::new(); + event_loop.run_app(&mut app)?; + } + #[cfg(target_arch = "wasm32")] + { + let app = App::new(&event_loop); + event_loop.spawn_app(app); + } + + Ok(()) +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen(start)] +pub fn run_web() -> Result<(), wasm_bindgen::JsValue> { + console_error_panic_hook::set_once(); + run().unwrap_throw(); + + Ok(()) +} diff --git a/src/examples/script.lua b/src/examples/script.lua new file mode 100644 index 0000000..e75154b --- /dev/null +++ b/src/examples/script.lua @@ -0,0 +1 @@ +print("hello world") \ No newline at end of file diff --git a/src/examples/test.nel b/src/examples/test.nel deleted file mode 100644 index 4413394..0000000 --- a/src/examples/test.nel +++ /dev/null @@ -1,11 +0,0 @@ -/* -@native @inline -@expr(say (&a: String) (b: i32) times) -fn say_something(&a: String, b: i32) -> Pack { - for index in 0..b { - a += a - } - return {a,b} -} -*/ -println("where the huzz") \ No newline at end of file diff --git a/src/lang.rs b/src/lang.rs index 783eacf..6a9aa33 100644 --- a/src/lang.rs +++ b/src/lang.rs @@ -1,5 +1,49 @@ +use derive_more; + +// State of 'cursor' that is reading through the text stream +// todo: implement locations for the 'cursor' through Piece +struct Reader<'a>(&'a str); +struct Piece { + line: usize, + index: usize, + column: usize, + piece: T, +} + +// Strips comments and whitespace +// todo: make this a Option<&str> and handle errors. +// todo: implement comments returning for debugging. +fn trim(_str: &str) -> &str { + let str = _str.trim(); + if str.starts_with("--") { + if str[2..].starts_with("[[") { + match str.find("]]") { + Some(i) => { + &str[i+2..].trim() + } + _ => { + "" + } + } + } else { + match str.find("\n") { + Some(i) => { + &str[i+1..].trim() + } + _ => { + "" + } + } + } + } else { + str.trim() + } +} + +// Macro for expanding syntax form into a struct macro_rules! definition { {@build ($n:ident structure) ($($t:tt)*) null () } => { + #[derive(Display)] struct $n ($($t)*); }; {@build ($n:ident enumeration new ($($f:tt)*) ($m:ident($($mt:tt)*)$($r:tt)*)) () null ()} => { @@ -18,6 +62,7 @@ macro_rules! definition { definition!{@build ($n enumeration filter ($($e)*$m,) ($($t)*))} }; {@build ($n:ident enumeration filter ($($e:tt)*) ())} => { + #[derive(Display)] enum $n { $($e)* } @@ -53,7 +98,7 @@ macro_rules! definition { definition!{@build $m ($($t)*) $i ($($a)*$n) ($($b)*) $($r)*} }; } - +// Macro for expanding syntax form into an implementation of trait Parse macro_rules! implement { {@begin $n:ident structure $($t:tt)*} => { implement!{@build (state result) (structure $n) {} {} $($t)*} @@ -197,7 +242,6 @@ macro_rules! implement { } }; } - macro_rules! class { ($i:ident($($t:tt)*)) => { definition!{@build ($i structure) () null () $($t)*} @@ -224,35 +268,130 @@ trait Parse { } impl Parse for Name { - fn consume(state: &str) -> Result<(Self, &str), Error> + fn consume(_state: &str) -> Result<(Self, &str), Error> where Self: Sized { - todo!() - } -} -impl Parse for String { - fn consume(state: &str) -> Result<(Self, &str), Error> - where - Self: Sized - { - todo!() + let state = _state.trim_start(); + let mut chars = state.chars(); + let mut end = 1; + match state.chars().next() { + None => { + return Err("Expected identifier".into()) + } + Some(c) => { + if !(c.is_alphabetic() || c == '_') { + return Err("Identifier must begin with alphabetic character".into()) + } + } + }; + end += state.chars().take_while(|c| { c.is_alphanumeric() || *c == '_' }).count(); + Ok((Name(state[..end].into()), &state[end..])) } } impl Parse for Number { - fn consume(state: &str) -> Result<(Self, &str), Error> + fn consume(_state: &str) -> Result<(Self, &str), Error> where Self: Sized { - todo!() + let state = _state.trim_start(); + let error = Err("Could not parse number".into()); + if state.starts_with("0x") { + let end = state[2..].chars().take_while(|c| c.is_digit(16)).count()+2; + if end == 0 { + error + } else { + Ok((Number(state[..end].into()),&state[end..])) + } + } else { + let end = state.chars().take_while(|c| c.is_digit(10) || *c == '.').count(); + if end == 0 { + error + } else { + Ok((Number(state[..end].into()),&state[end..])) + } + } } } -parser!( - Block2 ( {Statement} [Return] ); -); +impl Parse for String { + fn consume(_state: &str) -> Result<(Self, &str), Error> + where + Self: Sized, + { + let state = _state.trim_start(); + let chars: &mut std::str::Chars = &mut state.chars(); + let mut string = String::new(); + let term1: char; + let term2: Option; + match chars.next() { + Some('"') => { + term1 = '"'; + term2 = None; + }, + Some('\'') => { + term1 = '\''; + term2 = None; + } + Some('[') if chars.next().is_some_and(|it| it == '[') => { + term1 = ']'; + term2 = Some(']'); + } + _ => { + return Err("Could not parse string".into()) + } + } + loop { + match chars.next() { + Some(c) => { + match c { + '\\' => { + match chars.next() { + Some(c) => { + match c { + 'n' => string.push('\n'), + 'r' => string.push('\r'), + 't' => string.push('\t'), + '"' => string.push('"'), + '\'' => string.push('\''), + '\\' => string.push('\\'), + it => { + string.push(it); + } + } + } + None => {} + } + } + '\r' => {} // todo: ? + '\n' if term2.is_none() => { + return Err("Found newline while parsing string".into()) + } + term if term == term1 => { + // If the second terminator exists and is the next char, or there is no second terminator + if !term2.is_some() || chars.next().is_some_and(|it| it == term2.unwrap()) { + return Ok((string,chars.as_str())) + // There is a second terminator and it wasn't the next char + } else { + string.push(term) + } + } + _ => { + string.push(c); + } + } + } + None => { + return Err("Found EOF while parsing string".into()) + } + } + } + } +} +// todo: reimplement attrib here +// https://www.lua.org/manual/5.5/manual.html#9 parser! { // LiteralString -> "..." // LiteralNumber -> 0x... 123.456 diff --git a/src/lib.rs b/src/lib.rs index 8618666..159e22c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,482 +1,2 @@ -#![recursion_limit = "256"] -mod lang; -mod neural; - -// Credit of most code to https://sotrh.github.io/learn-wgpu/ since I come from OpenGl not wgpu -use std::sync::Arc; - -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; -use winit::dpi::PhysicalPosition; -#[cfg(target_arch = "wasm32")] -use winit::platform::web::EventLoopExtWebSys; -use winit::{ - application::ApplicationHandler, - event::*, - event_loop::{ActiveEventLoop, EventLoop}, - keyboard::{KeyCode, PhysicalKey}, - window::Window, -}; - -// This will store the state of our game -pub struct State { - surface: wgpu::Surface<'static>, - device: wgpu::Device, - queue: wgpu::Queue, - config: wgpu::SurfaceConfiguration, - is_surface_configured: bool, - window: Arc, - render_pipeline1: wgpu::RenderPipeline, - render_pipeline2: wgpu::RenderPipeline, - other_pipeline: bool, - sky: wgpu::Color, -} - -impl State { - // We don't need this to be async right now, - // but we will in the next tutorial - pub async fn new(window: Arc) -> anyhow::Result { - let size = window.inner_size(); - - // The instance is a handle to our GPU - // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - #[cfg(not(target_arch = "wasm32"))] - backends: wgpu::Backends::PRIMARY, - #[cfg(target_arch = "wasm32")] - backends: wgpu::Backends::GL, - flags: Default::default(), - memory_budget_thresholds: Default::default(), - backend_options: Default::default(), - display: None, - }); - - let surface = instance.create_surface(window.clone()).unwrap(); - - let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::default(), - compatible_surface: Some(&surface), - force_fallback_adapter: false, - }) - .await?; - - let (device, queue) = adapter - .request_device(&wgpu::DeviceDescriptor { - label: None, - required_features: wgpu::Features::empty(), - experimental_features: wgpu::ExperimentalFeatures::disabled(), - // WebGL doesn't support all of wgpu's features, so if - // we're building for the web we'll have to disable some. - required_limits: if cfg!(target_arch = "wasm32") { - wgpu::Limits::downlevel_webgl2_defaults() - } else { - wgpu::Limits::default() - }, - memory_hints: Default::default(), - trace: wgpu::Trace::Off, - }) - .await?; - - let surface_caps = surface.get_capabilities(&adapter); - // Shader code in this tutorial assumes an sRGB surface texture. Using a different - // one will result in all the colors coming out darker. If you want to support non - // sRGB surfaces, you'll need to account for that when drawing to the frame. - let surface_format = surface_caps - .formats - .iter() - .find(|f| f.is_srgb()) - .copied() - .unwrap_or(surface_caps.formats[0]); - let config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: surface_format, - width: size.width, - height: size.height, - present_mode: surface_caps.present_modes[0], - alpha_mode: surface_caps.alpha_modes[0], - view_formats: vec![], - desired_maximum_frame_latency: 2, - }; - - let shader1 = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); - - let render_pipeline_layout1 = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Render Pipeline Layout"), - bind_group_layouts: &[], - immediate_size: 0, - }); - - let render_pipeline1 = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Render Pipeline"), - layout: Some(&render_pipeline_layout1), - vertex: wgpu::VertexState { - module: &shader1, - entry_point: Some("vs_main"), // 1. - buffers: &[], // 2. - compilation_options: wgpu::PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - // 3. - module: &shader1, - entry_point: Some("fs_main"), - targets: &[Some(wgpu::ColorTargetState { - // 4. - format: config.format, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: wgpu::PipelineCompilationOptions::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, // 1. - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, // 2. - cull_mode: Some(wgpu::Face::Back), - // Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE - polygon_mode: wgpu::PolygonMode::Fill, - // Requires Features::DEPTH_CLIP_CONTROL - unclipped_depth: false, - // Requires Features::CONSERVATIVE_RASTERIZATION - conservative: false, - }, - depth_stencil: None, // 1. - multisample: wgpu::MultisampleState { - count: 1, // 2. - mask: !0, // 3. - alpha_to_coverage_enabled: false, // 4. - }, - multiview_mask: None, // 5. - cache: None, // 6. - }); - - let shader2 = device.create_shader_module(wgpu::include_wgsl!("shader2.wgsl")); - - let render_pipeline_layout2 = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Render Pipeline Layout"), - bind_group_layouts: &[], - immediate_size: 0, - }); - - let render_pipeline2 = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Render Pipeline"), - layout: Some(&render_pipeline_layout2), - vertex: wgpu::VertexState { - module: &shader2, - entry_point: Some("vs_main"), // 1. - buffers: &[], // 2. - compilation_options: wgpu::PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - // 3. - module: &shader2, - entry_point: Some("fs_main"), - targets: &[Some(wgpu::ColorTargetState { - // 4. - format: config.format, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: wgpu::PipelineCompilationOptions::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, // 1. - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, // 2. - cull_mode: Some(wgpu::Face::Back), - // Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE - polygon_mode: wgpu::PolygonMode::Fill, - // Requires Features::DEPTH_CLIP_CONTROL - unclipped_depth: false, - // Requires Features::CONSERVATIVE_RASTERIZATION - conservative: false, - }, - depth_stencil: None, // 1. - multisample: wgpu::MultisampleState { - count: 1, // 2. - mask: !0, // 3. - alpha_to_coverage_enabled: false, // 4. - }, - multiview_mask: None, // 5. - cache: None, // 6. - }); - - Ok(Self { - surface, - device, - queue, - config, - is_surface_configured: false, - window, - render_pipeline1, - render_pipeline2, - other_pipeline: false, - sky: wgpu::Color { - r: 0.1, - g: 0.2, - b: 0.3, - a: 1.0, - }, - }) - } - - pub fn resize(&mut self, width: u32, height: u32) { - if width > 0 && height > 0 { - let max = 2048; - self.config.width = width.min(max); - self.config.height = height.min(max); - self.surface.configure(&self.device, &self.config); - self.is_surface_configured = true; - } - } - - fn update(&mut self) { - // ... - } - - fn render(&mut self) -> anyhow::Result<()> { - self.window.request_redraw(); - - // We can't render unless the surface is configured - if !self.is_surface_configured { - return Ok(()); - } - - let output = match self.surface.get_current_texture() { - wgpu::CurrentSurfaceTexture::Success(surface_texture) => surface_texture, - wgpu::CurrentSurfaceTexture::Suboptimal(surface_texture) => { - self.surface.configure(&self.device, &self.config); - surface_texture - } - wgpu::CurrentSurfaceTexture::Timeout - | wgpu::CurrentSurfaceTexture::Occluded - | wgpu::CurrentSurfaceTexture::Validation => { - // Skip this frame - return Ok(()); - } - wgpu::CurrentSurfaceTexture::Outdated => { - self.surface.configure(&self.device, &self.config); - return Ok(()); - } - wgpu::CurrentSurfaceTexture::Lost => { - // You could recreate the devices and all resources - // created with it here, but we'll just bail - anyhow::bail!("Lost device"); - } - }; - let view = output - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - let mut encoder = self - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Render Encoder"), - }); - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - depth_slice: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(self.sky), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - occlusion_query_set: None, - timestamp_writes: None, - multiview_mask: None, - }); - - if self.other_pipeline { - render_pass.set_pipeline(&self.render_pipeline1); - } else { - render_pass.set_pipeline(&self.render_pipeline2); - } - render_pass.draw(0..3, 0..1); // 3. - } - - // submit will accept anything that implements IntoIter - self.queue.submit(std::iter::once(encoder.finish())); - output.present(); - - Ok(()) - } - - fn handle_key(&mut self, event_loop: &ActiveEventLoop, code: KeyCode, is_pressed: bool) { - match (code, is_pressed) { - (KeyCode::Escape, true) => event_loop.exit(), - (KeyCode::Space, true) => { - self.other_pipeline = !self.other_pipeline; - } - _ => {} - } - } - - fn handle_mouse_moved(&mut self, position: PhysicalPosition) { - self.sky = wgpu::Color { - r: 0.3, - g: position.x / 1000.0, - b: position.y / 1000.0, - a: 1.0, - } - } -} - -pub struct App { - #[cfg(target_arch = "wasm32")] - proxy: Option>, - state: Option, -} - -impl App { - pub fn new(#[cfg(target_arch = "wasm32")] event_loop: &EventLoop) -> Self { - #[cfg(target_arch = "wasm32")] - let proxy = Some(event_loop.create_proxy()); - Self { - state: None, - #[cfg(target_arch = "wasm32")] - proxy, - } - } -} - -impl ApplicationHandler for App { - fn resumed(&mut self, event_loop: &ActiveEventLoop) { - #[allow(unused_mut)] - let mut window_attributes = Window::default_attributes(); - - #[cfg(target_arch = "wasm32")] - { - use wasm_bindgen::JsCast; - use winit::platform::web::WindowAttributesExtWebSys; - - const CANVAS_ID: &str = "canvas"; - - let window = wgpu::web_sys::window().unwrap_throw(); - let document = window.document().unwrap_throw(); - let canvas = document.get_element_by_id(CANVAS_ID).unwrap_throw(); - let html_canvas_element = canvas.unchecked_into(); - window_attributes = window_attributes.with_canvas(Some(html_canvas_element)); - } - - let window = Arc::new(event_loop.create_window(window_attributes).unwrap()); - - #[cfg(not(target_arch = "wasm32"))] - { - // If we are not on web we can use pollster to - // await the window creation - self.state = Some(pollster::block_on(State::new(window)).unwrap()); - } - - #[cfg(target_arch = "wasm32")] - { - // Run the future asynchronously and use the - // proxy to send the results to the event loop - if let Some(proxy) = self.proxy.take() { - wasm_bindgen_futures::spawn_local(async move { - assert!( - proxy - .send_event( - State::new(window) - .await - .expect("Unable to create canvas!!!") - ) - .is_ok() - ) - }); - } - } - } - - #[allow(unused_mut)] - fn user_event(&mut self, _event_loop: &ActiveEventLoop, mut event: State) { - // This is where proxy.send_event() ends up - #[cfg(target_arch = "wasm32")] - { - event.window.request_redraw(); - event.resize( - event.window.inner_size().width, - event.window.inner_size().height, - ); - } - self.state = Some(event); - } - - fn window_event( - &mut self, - event_loop: &ActiveEventLoop, - _window_id: winit::window::WindowId, - event: WindowEvent, - ) { - let state = match &mut self.state { - Some(canvas) => canvas, - None => return, - }; - - match event { - WindowEvent::CloseRequested => event_loop.exit(), - WindowEvent::Resized(size) => state.resize(size.width, size.height), - WindowEvent::RedrawRequested => { - state.update(); - match state.render() { - Ok(_) => {} - Err(e) => { - // Log the error and exit gracefully - log::error!("{e}"); - event_loop.exit(); - } - } - } - WindowEvent::CursorMoved { position: pos, .. } => state.handle_mouse_moved(pos), - WindowEvent::KeyboardInput { - event: - KeyEvent { - physical_key: PhysicalKey::Code(code), - state: key_state, - .. - }, - .. - } => state.handle_key(event_loop, code, key_state.is_pressed()), - _ => {} - } - } -} - -pub fn run() -> anyhow::Result<()> { - #[cfg(not(target_arch = "wasm32"))] - { - env_logger::init(); - } - #[cfg(target_arch = "wasm32")] - { - console_log::init_with_level(log::Level::Info).unwrap_throw(); - } - - let event_loop = EventLoop::with_user_event().build()?; - #[cfg(not(target_arch = "wasm32"))] - { - let mut app = App::new(); - event_loop.run_app(&mut app)?; - } - #[cfg(target_arch = "wasm32")] - { - let app = App::new(&event_loop); - event_loop.spawn_app(app); - } - - Ok(()) -} - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen(start)] -pub fn run_web() -> Result<(), wasm_bindgen::JsValue> { - console_error_panic_hook::set_once(); - run().unwrap_throw(); - - Ok(()) -} +pub mod engine; +mod lang; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index fc66e18..2cce76f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ #![recursion_limit = "256"] -use game::run; +use game::engine::run; fn main() { run().unwrap();