diff options
| author | LLLL Colonq <llll@colonq> | 2024-12-07 17:25:37 -0500 |
|---|---|---|
| committer | LLLL Colonq <llll@colonq> | 2024-12-07 17:25:37 -0500 |
| commit | d5a5b454a4670a72826882c6ce4c87d1f597767c (patch) | |
| tree | 5963ed2b53063d3579019e6b22a629ac52d3b612 /src | |
| parent | aa465e6ebee75e9e9ea03196db4dd83fde766f0a (diff) | |
Native
Diffstat (limited to 'src')
| -rw-r--r-- | src/audio.rs | 66 | ||||
| -rw-r--r-- | src/context.rs | 36 | ||||
| -rw-r--r-- | src/lib.rs | 284 | ||||
| -rw-r--r-- | src/module.rs | 45 | ||||
| -rw-r--r-- | src/request.rs | 20 | ||||
| -rw-r--r-- | src/state.rs | 28 |
6 files changed, 299 insertions, 180 deletions
diff --git a/src/audio.rs b/src/audio.rs index 2b190de..5cbce90 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -1,9 +1,11 @@ use std::{cell::RefCell, collections::HashMap}; +#[cfg(target_arch = "wasm32")] pub struct Context { pub audio: web_sys::AudioContext, } +#[cfg(target_arch = "wasm32")] impl Context { pub fn new() -> Self { let audio = web_sys::AudioContext::new() @@ -14,11 +16,13 @@ impl Context { } } +#[cfg(target_arch = "wasm32")] pub struct Audio { pub buffer: &'static RefCell<Option<web_sys::AudioBuffer>>, //pub source: &'static web_sys::AudioBufferSourceNode, } +#[cfg(target_arch = "wasm32")] impl Audio { pub fn new(ctx: &Context, bytes: &[u8]) -> Self { let sbuffer: &_ = Box::leak(Box::new(RefCell::new(None))); @@ -52,6 +56,7 @@ impl Audio { } } +#[cfg(target_arch = "wasm32")] pub struct Assets { pub ctx: Context, @@ -60,6 +65,7 @@ pub struct Assets { pub music_node: Option<web_sys::AudioBufferSourceNode>, } +#[cfg(target_arch = "wasm32")] impl Assets { pub fn new<F>(f : F) -> Self where F: Fn(&Context) -> HashMap<String, Audio> { let ctx = Context::new(); @@ -94,3 +100,63 @@ impl Assets { } } } + +#[cfg(not(target_arch = "wasm32"))] +pub struct Context { +} + +#[cfg(not(target_arch = "wasm32"))] +impl Context { + pub fn new() -> Self { + Self { + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +pub struct Audio { +} + +#[cfg(not(target_arch = "wasm32"))] +impl Audio { + pub fn new(ctx: &Context, _bytes: &[u8]) -> Self { + Self { + } + } + + pub fn play(&self, ctx: &Context, looping: Option<(Option<f64>, Option<f64>)>) { + } +} + +#[cfg(not(target_arch = "wasm32"))] +pub struct Assets { + pub ctx: Context, + pub audio: HashMap<String, Audio>, +} + +#[cfg(not(target_arch = "wasm32"))] +impl Assets { + pub fn new<F>(f : F) -> Self where F: Fn(&Context) -> HashMap<String, Audio> { + let ctx = Context::new(); + + let audio = f(&ctx); + + Self { + ctx, + audio, + } + } + + pub fn play_sfx(&mut self, name: &str) { + if let Some(a) = self.audio.get(name) { + a.play(&self.ctx, None); + } + } + + pub fn is_music_playing(&self) -> bool { + false + } + + pub fn play_music(&mut self, name: &str, start: Option<f64>, end: Option<f64>) { + } +} diff --git a/src/context.rs b/src/context.rs index dc1d3a2..c6c68bc 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,17 +1,15 @@ -use wasm_bindgen::JsCast; -use winit::platform::web::WindowExtWebSys; use glow::HasContext; +#[cfg(target_arch = "wasm32")] +use winit::platform::web::WindowExtWebSys; + +#[cfg(target_arch = "wasm32")] #[link(wasm_import_module = "./helpers.js")] extern { fn js_track_resized_setup(); fn js_poll_resized() -> bool; } -// pub const RENDER_WIDTH: f32 = 640.0; -// pub const RENDER_HEIGHT: f32 = 360.0; -// pub const RENDER_WIDTH: f32 = 320.0; -// pub const RENDER_HEIGHT: f32 = 180.0; pub const RENDER_WIDTH: f32 = 240.0; pub const RENDER_HEIGHT: f32 = 160.0; @@ -32,23 +30,13 @@ pub struct Context { pub window: winit::window::Window, pub gl: glow::Context, pub emptyvao: glow::VertexArray, + + #[cfg(target_arch = "wasm32")] pub performance: web_sys::Performance, } impl Context { - pub fn new(window: winit::window::Window) -> Self { - let gl = web_sys::window() - .and_then(|win| win.document()) - .and_then(|doc| { - let dst = doc.get_element_by_id("teleia-parent")?; - let canvas = web_sys::Element::from(window.canvas().expect("failed to find canvas")); - dst.append_child(&canvas).ok()?; - let c = canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok()?; - let webgl2_context = c.get_context("webgl2").ok()?? - .dyn_into::<web_sys::WebGl2RenderingContext>().ok()?; - Some(glow::Context::from_webgl2_context(webgl2_context)) - }) - .expect("couldn't add canvas to document"); + pub fn new(window: winit::window::Window, gl: glow::Context) -> Self { unsafe { gl.clear_color(0.1, 0.1, 0.1, 1.0); gl.clear_depth_f32(1.0); @@ -68,17 +56,21 @@ impl Context { gl.create_vertex_array().expect("failed to initialize vao") }; + #[cfg(target_arch = "wasm32")] unsafe { js_track_resized_setup(); } Self { window, gl, emptyvao, + + #[cfg(target_arch = "wasm32")] performance: web_sys::window().expect("failed to find window") .performance().expect("failed to get performance"), } } + #[cfg(target_arch = "wasm32")] pub fn maximize_canvas(&self) { web_sys::window() .and_then(|win| win.document()) @@ -100,12 +92,18 @@ impl Context { .expect("failed to resize canvas"); } + #[cfg(target_arch = "wasm32")] pub fn resize_necessary(&self) -> bool { unsafe { js_poll_resized() } } + #[cfg(not(target_arch = "wasm32"))] + pub fn resize_necessary(&self) -> bool { + false + } + pub fn clear_color(&self, color: glam::Vec4) { unsafe { self.gl.clear_color(color.x, color.y, color.z, color.w); @@ -1,8 +1,5 @@ -use winit::platform::web::EventLoopExtWebSys; - pub mod utils; pub mod ui; -pub mod request; pub mod context; pub mod state; pub mod framebuffer; @@ -10,9 +7,17 @@ pub mod shader; pub mod mesh; pub mod texture; pub mod font; -pub mod audio; pub mod shadow; -pub mod module; +pub mod audio; + +#[cfg(target_arch = "wasm32")] +use winit::platform::web::EventLoopExtWebSys; + +#[cfg(target_arch = "wasm32")] +use winit::platform::web::WindowExtWebSys; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::JsCast; static mut CTX: Option<*const context::Context> = None; static mut ST: Option<*mut state::State> = None; @@ -30,28 +35,186 @@ where } } +pub fn event_loop_body<G>(event: winit::event::Event<()>, elwt: &winit::event_loop::EventLoopWindowTarget<()>) + where G: state::Game + 'static, +{ + contextualize(|ctx, st, game: &mut G| { + match &event { + winit::event::Event::WindowEvent { + event: wev, + window_id, + .. + } => match wev { + winit::event::WindowEvent::CloseRequested + if *window_id == ctx.window.id() => elwt.exit(), + winit::event::WindowEvent::Resized{..} => { + #[cfg(target_arch = "wasm32")] + ctx.maximize_canvas(); + st.handle_resize(&ctx); + }, + winit::event::WindowEvent::Focused(false) => { + st.keys = state::Keys::new(); + }, + winit::event::WindowEvent::CursorMoved { position, ..} => { + st.mouse_moved(&ctx, position.x as f32, position.y as f32, game); + }, + winit::event::WindowEvent::MouseInput { + button, + state, + .. + } => match state { + winit::event::ElementState::Pressed => { + st.mouse_pressed(&ctx, *button, game) + }, + winit::event::ElementState::Released => { + st.mouse_released(&ctx, *button) + }, + } + winit::event::WindowEvent::KeyboardInput { + event: winit::event::KeyEvent { + physical_key: winit::keyboard::PhysicalKey::Code(key), + state, + repeat: false, + .. + }, + .. + } => match state { + winit::event::ElementState::Pressed => { + st.key_pressed(&ctx, *key) + }, + winit::event::ElementState::Released => { + st.key_released(&ctx, *key) + }, + } + _ => {}, + }, + + winit::event::Event::AboutToWait => { + if ctx.resize_necessary() { + #[cfg(target_arch = "wasm32")] + ctx.maximize_canvas(); + st.handle_resize(&ctx); + } + if let Some(f) = &mut st.request { + match std::future::Future::poll(f.as_mut(), &mut st.waker_ctx) { + std::task::Poll::Pending => {}, + std::task::Poll::Ready(res) => { + st.request = None; + match res { + Ok(r) => st.request_returned(&ctx, game, r), + Err(e) => log::warn!("error during HTTP request: {}", e), + } + }, + } + // f.poll(); + } + st.run_update(&ctx, game); + st.run_render(&ctx, game); + ctx.window.request_redraw(); + }, + + _ => {}, + } + }); +} + pub async fn run<'a, F, G, Fut>(gnew: F) where Fut: std::future::Future<Output = G>, G: state::Game + 'static, F: (Fn(&'a context::Context) -> Fut), { - console_log::init_with_level(log::Level::Debug).unwrap(); - console_error_panic_hook::set_once(); - tracing_wasm::set_as_global_default(); + #[cfg(target_arch = "wasm32")] + { + console_log::init_with_level(log::Level::Debug).unwrap(); + console_error_panic_hook::set_once(); + tracing_wasm::set_as_global_default(); + } + #[cfg(not(target_arch = "wasm32"))] + { + env_logger::Builder::new() + .filter(None, log::LevelFilter::Info) + .init(); + } log::info!("hello computer, starting up..."); let event_loop = winit::event_loop::EventLoop::new() .expect("failed to initialize event loop"); - let window = winit::window::WindowBuilder::new() - .with_maximized(true) - .with_decorations(false) - .build(&event_loop) - .expect("failed to initialize window"); + #[cfg(target_arch = "wasm32")] + let (window, gl) = { + let window = winit::window::WindowBuilder::new() + .with_maximized(true) + .with_decorations(false) + .build(&event_loop) + .expect("failed to initialize window"); + let gl = web_sys::window() + .and_then(|win| win.document()) + .and_then(|doc| { + let dst = doc.get_element_by_id("teleia-parent")?; + let canvas = web_sys::Element::from(window.canvas().expect("failed to find canvas")); + dst.append_child(&canvas).ok()?; + let c = canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok()?; + let webgl2_context = c.get_context("webgl2").ok()?? + .dyn_into::<web_sys::WebGl2RenderingContext>().ok()?; + Some(glow::Context::from_webgl2_context(webgl2_context)) + }) + .expect("couldn't add canvas to document"); + (window, gl) + }; + + #[cfg(not(target_arch = "wasm32"))] + let (window, gl) = { + use glutin::config::GlConfig; + use glutin::context::NotCurrentGlContext; + use glutin::display::{GlDisplay, GetGlDisplay}; + use glutin::surface::GlSurface; + use raw_window_handle::HasRawWindowHandle; + use glutin_winit::GlWindow; + let window_builder = winit::window::WindowBuilder::new() + .with_title("teleia") + .with_maximized(true) + .with_decorations(false); + let template = glutin::config::ConfigTemplateBuilder::new(); + let display_builder = glutin_winit::DisplayBuilder::new().with_window_builder(Some(window_builder)); + let (window, gl_config) = display_builder + .build(&event_loop, template, |configs| { + configs.reduce(|a, c| { + if c.num_samples() > a.num_samples() { c } else { a } + }).expect("failed to obtain select configuration") + }).expect("failed to obtain opengl display"); + let window = window.expect("failed to create window"); + let raw_window_handle = window.raw_window_handle(); + let gl_display = gl_config.display(); + let context_attributes = glutin::context::ContextAttributesBuilder::new() + // .with_context_api(glutin::context::ContextApi::OpenGl(Some(glutin::context::Version { + // major: 3, + // minor: 3, + // }))) + .build(Some(raw_window_handle)); + unsafe { + let not_current_gl_context = gl_display.create_context(&gl_config, &context_attributes) + .expect("failed to obtain opengl context"); + let attrs = window.build_surface_attributes(Default::default()); + let gl_surface = gl_display.create_window_surface(&gl_config, &attrs) + .expect("failed to create opengl surface"); + let gl_context = not_current_gl_context.make_current(&gl_surface) + .expect("failed to set openglt context"); + let gl = glow::Context::from_loader_function_cstr(|s| gl_display.get_proc_address(s)); + gl_surface + .set_swap_interval(&gl_context, glutin::surface::SwapInterval::Wait(std::num::NonZeroU32::new(1).unwrap())) + .expect("failed to set swap interval"); + (window, gl) + } + }; + + let ctx = Box::leak(Box::new(context::Context::new(window, gl))); + + #[cfg(target_arch = "wasm32")] + { + ctx.maximize_canvas(); + } - let ctx = Box::leak(Box::new(context::Context::new(window))); - ctx.maximize_canvas(); let game = Box::leak(Box::new(gnew(ctx).await)); let st = Box::leak(Box::new(state::State::new(&ctx))); // request = Some(Box::new(async { @@ -64,83 +227,20 @@ where G = Some(game as *mut G as *mut std::ffi::c_void); } - event_loop.set_control_flow(winit::event_loop::ControlFlow::Wait); - event_loop.spawn(|event, elwt| { - contextualize(|ctx, st, game: &mut G| { - match &event { - winit::event::Event::WindowEvent { - event: wev, - window_id, - .. - } => match wev { - winit::event::WindowEvent::CloseRequested - if *window_id == ctx.window.id() => elwt.exit(), - winit::event::WindowEvent::Resized{..} => { - ctx.maximize_canvas(); - st.handle_resize(&ctx); - }, - winit::event::WindowEvent::Focused(false) => { - st.keys = state::Keys::new(); - }, - winit::event::WindowEvent::CursorMoved { position, ..} => { - st.mouse_moved(&ctx, position.x as f32, position.y as f32, game); - }, - winit::event::WindowEvent::MouseInput { - button, - state, - .. - } => match state { - winit::event::ElementState::Pressed => { - st.mouse_pressed(&ctx, *button, game) - }, - winit::event::ElementState::Released => { - st.mouse_released(&ctx, *button) - }, - } - winit::event::WindowEvent::KeyboardInput { - event: winit::event::KeyEvent { - physical_key: winit::keyboard::PhysicalKey::Code(key), - state, - repeat: false, - .. - }, - .. - } => match state { - winit::event::ElementState::Pressed => { - st.key_pressed(&ctx, *key) - }, - winit::event::ElementState::Released => { - st.key_released(&ctx, *key) - }, - } - _ => {}, - }, - - winit::event::Event::AboutToWait => { - if ctx.resize_necessary() { - ctx.maximize_canvas(); - st.handle_resize(&ctx); - } - if let Some(f) = &mut st.request { - match std::future::Future::poll(f.as_mut(), &mut st.waker_ctx) { - std::task::Poll::Pending => {}, - std::task::Poll::Ready(res) => { - st.request = None; - match res { - Ok(r) => st.request_returned(&ctx, game, r), - Err(e) => log::warn!("error during HTTP request: {}", e), - } - }, - } - // f.poll(); - } - st.run_update(&ctx, game); - st.run_render(&ctx, game); - ctx.window.request_redraw(); - }, - _ => {}, - } + #[cfg(target_arch = "wasm32")] + { + event_loop.set_control_flow(winit::event_loop::ControlFlow::Wait); + event_loop.spawn(|event, elwt| { + event_loop_body::<G>(event, elwt); }); - }); + } + + #[cfg(not(target_arch = "wasm32"))] + { + event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll); + event_loop.run(|event, elwt| { + event_loop_body::<G>(event, elwt); + }).expect("window closed"); + } } diff --git a/src/module.rs b/src/module.rs deleted file mode 100644 index 1fc4100..0000000 --- a/src/module.rs +++ /dev/null @@ -1,45 +0,0 @@ -use wasm_bindgen::prelude::*; - -#[wasm_bindgen] -pub fn log_info(msg: i8) { - log::info!("{:?}", msg); -} - -#[wasm_bindgen(module="/src/js/module.js")] -extern "C" { - fn js_build_interface() -> js_sys::Object; -} - -pub struct Module { - pub wasm: js_sys::WebAssembly::Instance, -} - -impl Module { - pub async fn new(bytes: &[u8]) -> Option<Self> { - let imp = js_build_interface(); - let o = wasm_bindgen_futures::JsFuture::from( - js_sys::WebAssembly::instantiate_buffer(bytes, &imp) - ).await.unwrap(); - let i = js_sys::Reflect::get(&o, &"instance".into()).unwrap(); - if let Ok(wasm) = i.dyn_into::<js_sys::WebAssembly::Instance>() { - Some(Self { - wasm, - }) - } else { - log::info!("failed 3"); - None - } - } - pub fn call(&self, nm: &str) { - let exp = self.wasm.exports(); - if let Ok(fo) = js_sys::Reflect::get(&exp, &nm.into()) { - if let Ok(func) = fo.dyn_into::<js_sys::Function>() { - let _ = func.call0(&JsValue::undefined()); - } else { - log::warn!("couldn't cast module function: {}", nm); - } - } else { - log::warn!("couldn't find module function: {}", nm); - } - } -} diff --git a/src/request.rs b/src/request.rs deleted file mode 100644 index 7f66f57..0000000 --- a/src/request.rs +++ /dev/null @@ -1,20 +0,0 @@ -use wasm_bindgen::JsCast; - -pub async fn get_store(key: &str) -> Option<String> { - let mut opts = web_sys::RequestInit::new(); - opts.method("GET"); - opts.mode(web_sys::RequestMode::Cors); - - let url = format!("https://colonq.computer/bullfrog/api/get/{}", key); - - let request = web_sys::Request::new_with_str_and_init(&url, &opts).ok()?; - - let window = web_sys::window().unwrap(); - let resp_value = wasm_bindgen_futures::JsFuture::from(window.fetch_with_request(&request)).await.ok()?; - - assert!(resp_value.is_instance_of::<web_sys::Response>()); - let resp: web_sys::Response = resp_value.dyn_into().unwrap(); - - let text = wasm_bindgen_futures::JsFuture::from(resp.text().ok()?).await.ok()?; - text.as_string() -} diff --git a/src/state.rs b/src/state.rs index 8c70c51..c5478a9 100644 --- a/src/state.rs +++ b/src/state.rs @@ -93,9 +93,15 @@ pub struct PointLight { pub attenuation: glam::Vec2, } +#[cfg(target_arch = "wasm32")] +type Timestamp = f64; + +#[cfg(not(target_arch = "wasm32"))] +type Timestamp = std::time::Instant; + pub struct State { pub acc: f64, - pub last: f64, + pub last: Timestamp, pub tick: u64, pub rebinding: Option<Key>, @@ -119,10 +125,16 @@ pub struct State { pub log: Vec<(u64, String)>, } -pub fn now(ctx: &context::Context) -> f64 { +#[cfg(target_arch = "wasm32")] +pub fn now(ctx: &context::Context) -> Timestamp { ctx.performance.now() / 1000.0 } +#[cfg(not(target_arch = "wasm32"))] +pub fn now(_ctx: &context::Context) -> Timestamp { + std::time::Instant::now() +} + pub fn default_keybindings() -> BiHashMap<winit::keyboard::KeyCode, Key> { BiHashMap::from_iter(vec![ (winit::keyboard::KeyCode::KeyW, Key::Up), @@ -156,9 +168,12 @@ impl State { let cwaker = Box::leak(Box::new(waker.into())); let waker_ctx = std::task::Context::from_waker(cwaker); + let acc = 0.0; + let last = now(ctx); + Self { - acc: 0.0, - last: now(ctx), + acc, + last, // we initialize the tick to 1000, which allows us to use "0" as the default time for // various animation starts on entities without having them all play at game start tick: 1000, @@ -421,7 +436,12 @@ impl State { pub fn run_update<G>(&mut self, ctx: &context::Context, game: &mut G) where G: Game { let now = now(ctx); + + #[cfg(target_arch = "wasm32")] let diff = now - self.last; + #[cfg(not(target_arch = "wasm32"))] + let diff = now.duration_since(self.last).as_secs_f64(); + self.acc += diff; self.last = now; |
