use std::{ hint, sync::Arc, thread, time::{Duration, Instant}, }; use winit::{ application::ApplicationHandler, dpi::LogicalSize, event::{ElementState, WindowEvent}, event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, window::{Window, WindowId}, }; mod graphics; use graphics::{AcquireSurfaceTextureResult, Renderer}; #[derive(Default)] struct App { window: Option>, renderer: Option, } impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { // ウィンドウメッセージをpollで捌く // キューが空になる度に`about_to_wait`メソッドが呼ばれる event_loop.set_control_flow(ControlFlow::Poll); let attributes = Window::default_attributes() .with_title("君は呪われている!") .with_resizable(false) .with_inner_size(LogicalSize::new(1280, 720)) .with_fullscreen(None); let window = match event_loop.create_window(attributes) { Ok(v) => Arc::new(v), Err(e) => panic!("error: {e}"), }; let renderer = pollster::block_on(Renderer::new( Box::new(event_loop.owned_display_handle()), window.clone(), )); let renderer = match renderer { Ok(v) => v, Err(e) => panic!("error: {e}"), }; self.window = Some(window); self.renderer = Some(renderer); } fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { match event { WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::MouseInput { state, button, .. } => match state { ElementState::Pressed => { println!("pressed {:?}", button); } ElementState::Released => { println!("released {:?}", button); } }, WindowEvent::CursorMoved { position, .. } => { let Some(window) = self.window.as_ref() else { return; }; println!( "moved {:?}", position.to_logical::(window.scale_factor()) ); } WindowEvent::CursorLeft { .. } => { println!("left"); } WindowEvent::Resized(size) => { if let Some(renderer) = self.renderer.as_ref() { renderer.configure_surface(size); } } WindowEvent::ScaleFactorChanged { .. } => { let (Some(window), Some(renderer)) = (&self.window, &self.renderer) else { return; }; renderer.configure_surface(window.inner_size()); } WindowEvent::Focused(false) | WindowEvent::Occluded(true) => { println!("focused or occluded"); } _ => {} } } fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { let start = Instant::now(); let (Some(window), Some(renderer)) = (&self.window, &mut self.renderer) else { // NOTE: `window`が`Some`になる`resumed`メソッドで`ControlFlow::Poll`を指定しているため、 // ここで`ControlFlow::WaitUntil`を指定しても問題ない。 event_loop.set_control_flow(ControlFlow::WaitUntil(start + Duration::from_millis(16))); return; }; // NOTE: Presentation modeがFIFOの場合はここでスレッドが停止する。 let current_texture = match renderer.get_current_texture(window) { AcquireSurfaceTextureResult::Ok(v) => v, AcquireSurfaceTextureResult::ShouldSkip => { event_loop .set_control_flow(ControlFlow::WaitUntil(start + Duration::from_millis(16))); return; } // TODO: 一応対応する。 AcquireSurfaceTextureResult::Lost => panic!("fatal: device or surface lost."), }; // DEBUG: renderer.render( window, current_texture, &[ graphics::command::RenderCommand::Image(graphics::command::Image { id: graphics::image::ImageId::Image, origin: glam::Vec2::new(900.0, 400.0), rotation: 0.2, scale: glam::Vec2::new(0.25, 0.25), uv_offset: glam::Vec2::new(0.0, 0.0), uv_size: glam::Vec2::new(1.0, 1.0), }), graphics::command::RenderCommand::Text(graphics::command::Text { text: "君は呪われている!".to_string(), origin: glam::Vec2::new(240.0, 120.0), scale: 48, v_align: graphics::command::VerticalAlign::Top, h_align: graphics::command::HorizontalAlign::Left, }), ], ); if !renderer.is_presentation_mode_fifo() { sync_60fps(start); } } } /// 60fpsになるようにスレッドを停止する関数 /// /// * start - フレーム開始時刻 /// /// 15.66...msをsleepし、不足時間をスピンウェイトする。 /// 正直アニメーションの強いゲームではないので厳密さは要らないのだけれど、 /// 一応アニメーションのあるゲームだからね多少はね。 fn sync_60fps(start: Instant) { const TARGET_FRAME_TIME: Duration = Duration::from_nanos(1_000_000_000 / 60); // 60FPS const SPIN_THRESHOLD: Duration = Duration::from_millis(1); let elapsed = start.elapsed(); if elapsed < TARGET_FRAME_TIME { let remaining = TARGET_FRAME_TIME - elapsed; if remaining > SPIN_THRESHOLD { thread::sleep(remaining - SPIN_THRESHOLD); } while start.elapsed() < TARGET_FRAME_TIME { hint::spin_loop(); } } } fn main() { let event_loop = match EventLoop::new() { Ok(v) => v, Err(e) => panic!("error: {e}"), }; match event_loop.run_app(&mut App::default()) { Ok(()) => {} Err(e) => panic!("error: {e}"), } }