oml_game/window/
window.rs

1//use chrono::prelude::*;
2use glutin::dpi::PhysicalPosition;
3use glutin::event::VirtualKeyCode;
4use glutin::event::{ElementState, Event, KeyboardInput, WindowEvent};
5use glutin::event_loop::{ControlFlow, EventLoop};
6use glutin::window::WindowBuilder;
7use glutin::ContextBuilder;
8use glutin::{ContextWrapper, PossiblyCurrent};
9use tracing::*;
10
11use crate::math::Vector2;
12pub use crate::window::window_update_context::WindowUpdateContext;
13use crate::DefaultTelemetry;
14
15const TARGET_FPS: f64 = 60.0;
16const TARGET_FRAME_TIME: f64 = 1000.0 / TARGET_FPS;
17
18#[derive(Default)]
19#[allow(dead_code)]
20pub struct WindowCallbacks {
21	update: Option<Box<dyn FnMut(&mut Box<dyn WindowUserData>, &mut WindowUpdateContext) -> bool>>,
22	fixed_update: Option<Box<dyn FnMut(&mut Box<dyn WindowUserData>, f64)>>,
23	render:       Option<Box<dyn FnMut(&mut Box<dyn WindowUserData>)>>,
24}
25
26impl<'a> WindowCallbacks {
27	//	pub fn with_update( mut self, f: &'a mut (dyn for<'r> FnMut(&'r mut WindowUpdateContext) -> bool + 'a) ) -> Self {
28	pub fn with_update(
29		mut self,
30		f: Box<dyn FnMut(&mut Box<dyn WindowUserData>, &mut WindowUpdateContext) -> bool>,
31	) -> Self {
32		self.update = Some(f);
33		self
34	}
35	pub fn with_fixed_update(
36		mut self,
37		f: Box<dyn FnMut(&mut Box<dyn WindowUserData>, f64)>,
38	) -> Self {
39		self.fixed_update = Some(f);
40		self
41	}
42	pub fn with_render(mut self, f: Box<dyn FnMut(&mut Box<dyn WindowUserData>)>) -> Self {
43		self.render = Some(f);
44		self
45	}
46}
47
48pub trait WindowUserData {
49	fn as_any(&self) -> &dyn std::any::Any;
50	fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
51}
52
53pub struct Window {
54	el:               Option<EventLoop<()>>,
55	windowed_context: Option<ContextWrapper<PossiblyCurrent, glutin::window::Window>>,
56	title:            String,
57	pos:              Vector2,
58	size:             Vector2,
59}
60
61impl Window {
62	pub fn new() -> Self {
63		Self {
64			el:               None,
65			windowed_context: None,
66			title:            String::new(),
67			pos:              Vector2::new(100.0, 100.0),
68			size:             Vector2::new(1400.0, 700.0),
69		}
70	}
71
72	// some form of configuration
73	pub fn set_title(&mut self, title: &str) {
74		self.title = title.to_string();
75		// if the window is already open fix the title
76		if let Some(ctx) = &mut self.windowed_context {
77			ctx.window().set_title(&self.title);
78		}
79	}
80
81	pub fn set_position(&mut self, pos: &Vector2) {
82		self.pos = *pos;
83	}
84
85	pub fn set_size(&mut self, size: &Vector2) {
86		self.size = *size;
87	}
88
89	pub fn scale_factor(&self) -> f64 {
90		if let Some(ctx) = &self.windowed_context {
91			ctx.window().scale_factor()
92		} else {
93			1.0
94		}
95	}
96
97	pub fn setup(&mut self) -> anyhow::Result<()> {
98		let el = EventLoop::new();
99		let wb = WindowBuilder::new()
100			//	    			.with_inner_size( glutin::dpi::PhysicalSize{ width: 1920/2, height: 1080/2 } )
101			//	    			.with_inner_size( glutin::dpi::PhysicalSize{ width: 1920/2, height: 512 } )
102			.with_inner_size(glutin::dpi::PhysicalSize {
103				width:  self.size.x as i32,
104				height: self.size.y as i32,
105			})
106			//	    			.with_inner_size( glutin::dpi::PhysicalSize{ width: 1880, height: 700 } )
107			//	    			.with_inner_size( glutin::dpi::PhysicalSize{ width: 512, height: 512 } )
108			.with_position(glutin::dpi::PhysicalPosition {
109				x: self.pos.x as i32,
110				y: self.pos.y as i32,
111			})
112			.with_title(&self.title);
113
114		let windowed_context = ContextBuilder::new()
115			.with_vsync(true) // yes?
116			.build_windowed(wb, &el)
117			.unwrap();
118
119		let windowed_context = unsafe { windowed_context.make_current().unwrap() };
120
121		println!(
122			"Pixel format of the window's GL context: {:?}",
123			windowed_context.get_pixel_format()
124		);
125
126		//	    let window = windowed_context.window();
127		//	    window.set_outer_position( glutin::dpi::PhysicalPosition{ x: 2300, y: 100 } );
128		self.el = Some(el);
129		self.windowed_context = Some(windowed_context);
130
131		Ok(())
132	}
133
134	pub fn teardown(&mut self) {}
135
136	pub fn get_proc_address(&self, addr: &str) -> *const core::ffi::c_void {
137		match &self.windowed_context {
138			Some(windowed_context) => windowed_context.get_proc_address(addr),
139			None => std::ptr::null(),
140		}
141	}
142
143	fn run_event_loop(
144		mut parent_thread: Option<std::thread::Thread>,
145		el: EventLoop<()>,
146		windowed_context: ContextWrapper<PossiblyCurrent, glutin::window::Window>,
147		mut userdata: Box<dyn WindowUserData>,
148		mut callbacks: WindowCallbacks,
149	) {
150		// let el = window.el.take().unwrap();
151		// let windowed_context = window.windowed_context.take().unwrap();
152		let mut is_done = false;
153		let mut window_update_context = WindowUpdateContext::new();
154
155		//let mut previous_now: DateTime<Utc> = Utc::now();
156		let mut previous_now = std::time::Instant::now();
157
158		let mut event_count = 0;
159		let mut next_time = std::time::Instant::now();
160
161		let mut slowest_frame_ms = 0.0;
162		let mut slow_frame_count = 0;
163
164		el.run(move |event, _, control_flow| {
165			event_count += 1;
166			let start_time = std::time::Instant::now();
167
168			window_update_context.window_size.x =
169				windowed_context.window().inner_size().width as f32;
170			window_update_context.window_size.y =
171				windowed_context.window().inner_size().height as f32;
172
173			match windowed_context.window().inner_position() {
174				Ok(PhysicalPosition { x, y }) => {
175					let x = x as f32;
176					let y = y as f32;
177					if window_update_context.window_pos.x != x {
178						window_update_context.window_pos.x = x;
179						window_update_context.window_changed = true;
180					}
181					if window_update_context.window_pos.y != y {
182						window_update_context.window_pos.y = y;
183						window_update_context.window_changed = true;
184					}
185				},
186				_ => {},
187			}
188			/*
189			match windowed_context.window().outer_position() {
190				Ok(PhysicalPosition{ x, y }) => {
191					let x = x as f32;
192					let y = y as f32;
193					if window_update_context.window_pos.x != x {
194						window_update_context.window_pos.x = x;
195						window_update_context.window_changed = true;
196					}
197					if window_update_context.window_pos.y != y {
198						window_update_context.window_pos.y = y;
199						window_update_context.window_changed = true;
200					}
201				},
202				_ => {},
203			}
204			*/
205			match event {
206				Event::LoopDestroyed => return,
207				Event::WindowEvent { event, .. } => match event {
208					WindowEvent::Resized(physical_size) => {
209						dbg!(&physical_size);
210						windowed_context.resize(physical_size)
211					},
212					WindowEvent::Moved(physical_size) => {
213						dbg!(&physical_size);
214						//	                	windowed_context.resize(physical_size)
215					},
216					WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
217					WindowEvent::MouseWheel { delta, .. } => {
218						// debug!("MouseWheel delta {:?}", delta );
219						match delta {
220							glutin::event::MouseScrollDelta::LineDelta(x, y) => {
221								window_update_context.mouse_wheel_line_delta.x = x;
222								window_update_context.mouse_wheel_line_delta.y = y;
223							},
224							_ => {},
225						}
226					},
227					WindowEvent::CursorMoved { position, .. } => {
228						let inner_size = windowed_context.window().inner_size();
229
230						let w = inner_size.width as f64;
231						let h = inner_size.height as f64;
232						let mouse_x = position.x / w;
233						let mouse_y = (h - position.y) / h;
234						let scale = 0.5;
235						window_update_context.mouse_pos.x = mouse_x as f32;
236						window_update_context.mouse_pos.y = mouse_y as f32;
237						/*
238						tracing::debug!(
239							"{:?} {:?} {} {}",
240							&position,
241							&inner_size,
242							&mouse_x,
243							&mouse_y
244						);
245						*/
246					},
247					WindowEvent::MouseInput { state, button, .. } => {
248						let button_index = match button {
249							glutin::event::MouseButton::Left => 0,
250							glutin::event::MouseButton::Middle => 1,
251							glutin::event::MouseButton::Right => 2,
252							_ => 0,
253						};
254						window_update_context.set_mouse_button(
255							button_index,
256							state == glutin::event::ElementState::Pressed,
257						)
258
259						//	                	dbg!(&state, &button, &window_update_context.mouse_buttons);
260					},
261					WindowEvent::KeyboardInput {
262						input:
263							KeyboardInput {
264								virtual_keycode: Some(virtual_code),
265								state,
266								..
267							},
268						..
269					} => match (virtual_code, state) {
270						(VirtualKeyCode::Escape, state) => {
271							window_update_context.is_escape_pressed =
272								state == ElementState::Pressed;
273							//                			println!("Escape {:?}", &state );
274						},
275						(VirtualKeyCode::Space, state) => {
276							window_update_context.is_space_pressed = state == ElementState::Pressed;
277							//                			println!("Space {:?}", &state );
278						},
279						(vkc, state) if vkc >= VirtualKeyCode::A && vkc <= VirtualKeyCode::Z => {
280							let o = ((vkc as u16) - (VirtualKeyCode::A as u16)) as u8;
281							let o = (o + 'a' as u8) as usize;
282							println!("KeyboardInput A-Z {:?} -> {}", &vkc, &o);
283							window_update_context.is_key_pressed[o] =
284								state == ElementState::Pressed;
285						},
286						_ => {
287							// println!("KeyboardInput {:?}", &virtual_code);
288							if let Some(ascii) = match virtual_code {
289								VirtualKeyCode::Equals => Some(61),
290								VirtualKeyCode::LBracket => Some(91),
291								VirtualKeyCode::Backslash => Some(92),
292								VirtualKeyCode::RBracket => Some(93),
293								VirtualKeyCode::Caret => Some(94),
294								VirtualKeyCode::Slash => Some(47),
295								VirtualKeyCode::Grave => Some(96),
296								_ => None,
297							} {
298								window_update_context.is_key_pressed[ascii] =
299									state == ElementState::Pressed;
300							} else if let Some(fkey) = match virtual_code {
301								VirtualKeyCode::F1 => Some(1),
302								VirtualKeyCode::F2 => Some(2),
303								VirtualKeyCode::F3 => Some(3),
304								VirtualKeyCode::F4 => Some(4),
305								VirtualKeyCode::F5 => Some(5),
306								VirtualKeyCode::F6 => Some(6),
307								VirtualKeyCode::F7 => Some(7),
308								VirtualKeyCode::F8 => Some(8),
309								VirtualKeyCode::F9 => Some(9),
310								VirtualKeyCode::F10 => Some(10),
311								VirtualKeyCode::F11 => Some(11),
312								VirtualKeyCode::F12 => Some(12),
313								_ => None,
314							} {
315								window_update_context.is_function_key_pressed[fkey] =
316									state == ElementState::Pressed;
317							} else {
318								match virtual_code {
319									VirtualKeyCode::LShift | VirtualKeyCode::RShift => {
320										window_update_context.set_modifier_pressed(
321											crate::window::ModifierKey::Shift,
322											state == ElementState::Pressed,
323										);
324									},
325									VirtualKeyCode::LControl | VirtualKeyCode::RControl => {
326										window_update_context.set_modifier_pressed(
327											crate::window::ModifierKey::Ctrl,
328											state == ElementState::Pressed,
329										);
330									},
331									VirtualKeyCode::LAlt | VirtualKeyCode::RAlt => {
332										window_update_context.set_modifier_pressed(
333											crate::window::ModifierKey::Alt,
334											state == ElementState::Pressed,
335										);
336									},
337									vc => {
338										println!("Unmapped KeyboardInput {:?} !", &vc)
339									},
340								};
341							}
342						},
343					},
344					_ => (),
345				},
346				Event::RedrawRequested(_) => {
347					//	                gl.draw_frame([1.0, 0.5, 0.7, 1.0]);
348					windowed_context.swap_buffers().unwrap();
349				},
350				Event::RedrawEventsCleared => {
351					// :TODO: :HACK: swapped RedrawEventsCleared and MainEventsCleared for testing
352					// debug!("RedrawEventsCleared {}", event_count);
353
354					// all evens handled, lets render
355					//let now: DateTime<Utc> = Utc::now();
356					let now = std::time::Instant::now();
357					//let frame_duration = now.signed_duration_since(previous_now);
358					let frame_duration = now - previous_now;
359					//let time_step = frame_duration.num_milliseconds() as f64 / 1000.0;
360					let time_step = frame_duration.as_secs_f64();
361					previous_now = now;
362					window_update_context.time_step = time_step;
363
364					let done = if let Some(ref mut ucb) = callbacks.update {
365						ucb(&mut userdata, &mut window_update_context)
366					} else {
367						true
368					};
369
370					if !is_done && done {
371						println!("update returned false");
372						*control_flow = ControlFlow::Exit;
373						is_done = true;
374						if let Some(parent_thread) = parent_thread.take() {
375							parent_thread.unpark();
376						}
377					}
378
379					if !is_done {
380						if let Some(ref mut fucb) = callbacks.fixed_update {
381							// :TODO: make time step fixed
382							let half_time_step = 0.5 * time_step;
383							fucb(&mut userdata, half_time_step);
384							fucb(&mut userdata, half_time_step);
385						}
386					}
387
388					if let Some(ref mut rcb) = callbacks.render {
389						rcb(&mut userdata);
390					}
391
392					window_update_context.update();
393					windowed_context.swap_buffers().unwrap();
394					match *control_flow {
395						glutin::event_loop::ControlFlow::Exit => {},
396						_ => {
397							//	        println!("{:?}", event);
398							//	        *control_flow = ControlFlow::Poll;
399							let elapsed_time = std::time::Instant::now()
400								.duration_since(start_time)
401								.as_millis() as f64;
402							let wait_millis = match TARGET_FRAME_TIME >= elapsed_time {
403								true => {
404									/*
405									tracing::debug!(
406										"Fast frame {} > {} (ms)",
407										elapsed_time,
408										TARGET_FRAME_TIME
409									);
410									*/
411									DefaultTelemetry::trace::<f64>(
412										"fast frame",
413										elapsed_time / 1000.0,
414									);
415									TARGET_FRAME_TIME - elapsed_time
416								},
417								false => {
418									DefaultTelemetry::trace::<f64>(
419										"slow frame",
420										elapsed_time / 1000.0,
421									);
422									/*
423									warn!(
424										"Slow frame {} > {} (ms)",
425										elapsed_time, TARGET_FRAME_TIME
426									);
427									if slowest_frame_ms < elapsed_time {
428										slowest_frame_ms = elapsed_time;
429									}
430									*/
431									slow_frame_count += 1;
432
433									0.0
434								},
435							};
436							//debug!("Waiting {}", wait_millis);
437							//let next_frame_time = std::time::Instant::now() + std::time::Duration::from_nanos(16_666_667);
438							let next_frame_time = std::time::Instant::now()
439								+ std::time::Duration::from_millis(wait_millis as u64);
440							*control_flow =
441								glutin::event_loop::ControlFlow::WaitUntil(next_frame_time);
442							next_time = next_frame_time;
443							// *control_flow = glutin::event_loop::ControlFlow::Wait;
444							// *control_flow = glutin::event_loop::ControlFlow::Poll;
445						},
446					}
447				},
448				Event::MainEventsCleared => {
449					// debug!("MainEventsCleared");
450				},
451				Event::NewEvents(_) => {
452					event_count = 0;
453					/*
454					debug!("--------");
455					let late = if start_time > next_time {
456						(start_time - next_time).as_secs_f64()
457					} else {
458						-(next_time - start_time).as_secs_f64()
459					};
460					debug!("{:?} - {:?}", start_time, next_time);
461					debug!("Late: {}", late);
462					*/
463				},
464				Event::DeviceEvent { .. } => { // :TODO: handle Button
465				},
466				e => {
467					println!("Unhandled event: {:?}", e);
468				},
469			}
470			/*
471				*/
472		});
473	}
474	pub fn run(
475		&mut self,
476		parent_thread: Option<std::thread::Thread>,
477		userdata: Box<dyn WindowUserData>,
478		callbacks: WindowCallbacks,
479	) {
480		let el = self.el.take().unwrap();
481		let windowed_context = self.windowed_context.take().unwrap();
482
483		// glutin's EventLoop run hijacks the current thread and never returns it, so we have to work around that
484		Window::run_event_loop(parent_thread, el, windowed_context, userdata, callbacks);
485	}
486}