summaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs121
1 files changed, 121 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..6303919
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,121 @@
+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},
+};
+
+struct App {
+ window: Option<Arc<Window>>,
+}
+
+impl App {
+ fn new() -> Self {
+ Self { window: None }
+ }
+}
+
+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_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}"),
+ };
+
+ self.window = Some(window);
+ }
+
+ 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::<f32>(window.scale_factor())
+ );
+ }
+ WindowEvent::CursorLeft { .. } => {
+ println!("left");
+ }
+ 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(_) = &self.window else {
+ // NOTE: `window`が`Some`になる`resumed`メソッドで`ControlFlow::Poll`を指定しているため、
+ // ここで`ControlFlow::WaitUntil`を指定しても問題ない。
+ event_loop.set_control_flow(ControlFlow::WaitUntil(start + Duration::from_millis(16)));
+ return;
+ };
+
+ 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::new()) {
+ Ok(()) => {}
+ Err(e) => panic!("error: {e}"),
+ }
+}