summaryrefslogtreecommitdiff
path: root/src/main.rs
blob: 63039196e0c58f6eda2bd704eb4af305258eb652 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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}"),
    }
}