mirror of
https://github.com/italicsjenga/mini_gl_fb.git
synced 2024-11-22 15:31:31 +11:00
Add wakeup support (#7)
* Add wakeup support This also fixes a bug (the `glutin_handle_basic_input` callback was never actually given mutable access to the `BasicInput`, so there's no way it could have set `BasicInput::wait` anyway). Glad I got that before the 0.8 release, phew. * Fix bug in reschedule_wakeup * Fix adjust_wakeup
This commit is contained in:
parent
59618adfed
commit
db2f86ce15
|
@ -6,11 +6,15 @@ use mini_gl_fb::glutin::event::{VirtualKeyCode, MouseButton};
|
||||||
use mini_gl_fb::glutin::event_loop::EventLoop;
|
use mini_gl_fb::glutin::event_loop::EventLoop;
|
||||||
use mini_gl_fb::glutin::dpi::LogicalSize;
|
use mini_gl_fb::glutin::dpi::LogicalSize;
|
||||||
|
|
||||||
use std::time::SystemTime;
|
use std::time::{Instant, Duration};
|
||||||
|
use mini_gl_fb::breakout::Wakeup;
|
||||||
|
|
||||||
const WIDTH: usize = 200;
|
const WIDTH: usize = 200;
|
||||||
const HEIGHT: usize = 200;
|
const HEIGHT: usize = 200;
|
||||||
|
|
||||||
|
const NORMAL_SPEED: u64 = 500;
|
||||||
|
const TURBO_SPEED: u64 = 20;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut event_loop = EventLoop::new();
|
let mut event_loop = EventLoop::new();
|
||||||
let mut fb = mini_gl_fb::get_fancy(config! {
|
let mut fb = mini_gl_fb::get_fancy(config! {
|
||||||
|
@ -35,38 +39,62 @@ fn main() {
|
||||||
cells[52 * WIDTH + 50] = true;
|
cells[52 * WIDTH + 50] = true;
|
||||||
cells[52 * WIDTH + 51] = true;
|
cells[52 * WIDTH + 51] = true;
|
||||||
|
|
||||||
let mut previous = SystemTime::now();
|
// ID of the Wakeup which means we should update the board
|
||||||
let mut extra_delay: f64 = 0.0;
|
let mut update_id: Option<u32> = None;
|
||||||
|
|
||||||
fb.glutin_handle_basic_input(&mut event_loop, |fb, input| {
|
fb.glutin_handle_basic_input(&mut event_loop, |fb, input| {
|
||||||
let elapsed = previous.elapsed().unwrap();
|
// We're going to use wakeups to update the grid
|
||||||
let seconds = elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 * 1e-9;
|
input.wait = true;
|
||||||
|
|
||||||
|
if update_id.is_none() {
|
||||||
|
update_id = Some(input.schedule_wakeup(Instant::now() + Duration::from_millis(500)))
|
||||||
|
} else if let Some(mut wakeup) = input.wakeup {
|
||||||
|
if Some(wakeup.id) == update_id {
|
||||||
|
// Time to update our grid
|
||||||
|
calculate_neighbors(&mut cells, &mut neighbors);
|
||||||
|
make_some_babies(&mut cells, &mut neighbors);
|
||||||
|
fb.update_buffer(&cells);
|
||||||
|
|
||||||
|
// Reschedule another update
|
||||||
|
wakeup.when = Instant::now() + Duration::from_millis(
|
||||||
|
if input.key_is_down(VirtualKeyCode::LShift) {
|
||||||
|
TURBO_SPEED
|
||||||
|
} else {
|
||||||
|
NORMAL_SPEED
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
input.reschedule_wakeup(wakeup);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We will get called again after all wakeups are handled
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if input.key_is_down(VirtualKeyCode::Escape) {
|
if input.key_is_down(VirtualKeyCode::Escape) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.mouse_is_down(MouseButton::Left) {
|
if input.mouse_is_down(MouseButton::Left) || input.mouse_is_down(MouseButton::Right) {
|
||||||
// Mouse was pressed
|
// Mouse was pressed
|
||||||
let (x, y) = input.mouse_pos;
|
let (x, y) = input.mouse_pos;
|
||||||
let x = x.min(WIDTH as f64 - 0.0001).max(0.0).floor() as usize;
|
let x = x.min(WIDTH as f64 - 0.0001).max(0.0).floor() as usize;
|
||||||
let y = y.min(HEIGHT as f64 - 0.0001).max(0.0).floor() as usize;
|
let y = y.min(HEIGHT as f64 - 0.0001).max(0.0).floor() as usize;
|
||||||
cells[y * WIDTH + x] = true;
|
cells[y * WIDTH + x] = input.mouse_is_down(MouseButton::Left);
|
||||||
fb.update_buffer(&cells);
|
fb.update_buffer(&cells);
|
||||||
// Give the user extra time to make something pretty each time they click
|
// Give the user extra time to make something pretty each time they click
|
||||||
previous = SystemTime::now();
|
if !input.key_is_down(VirtualKeyCode::LShift) {
|
||||||
extra_delay = (extra_delay + 0.5).min(2.0);
|
input.adjust_wakeup(update_id.unwrap(), Wakeup::after_millis(2000));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Each generation should stay on screen for half a second
|
// TODO support right shift. Probably by querying modifiers somehow. (modifiers support)
|
||||||
if seconds > 0.5 + extra_delay {
|
if input.key_pressed(VirtualKeyCode::LShift) {
|
||||||
previous = SystemTime::now();
|
// immediately update
|
||||||
calculate_neighbors(&mut cells, &mut neighbors);
|
input.adjust_wakeup(update_id.unwrap(), Wakeup::after_millis(0));
|
||||||
make_some_babies(&mut cells, &mut neighbors);
|
} else if input.key_released(VirtualKeyCode::LShift) {
|
||||||
fb.update_buffer(&cells);
|
// immediately stop updating
|
||||||
extra_delay = 0.0;
|
input.adjust_wakeup(update_id.unwrap(), Wakeup::after_millis(NORMAL_SPEED));
|
||||||
} else if input.resized {
|
|
||||||
fb.redraw();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
|
|
|
@ -6,6 +6,7 @@ use core::Framebuffer;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use glutin::event::{MouseButton, VirtualKeyCode, ModifiersState};
|
use glutin::event::{MouseButton, VirtualKeyCode, ModifiersState};
|
||||||
|
use std::time::{Instant, Duration};
|
||||||
|
|
||||||
/// `GlutinBreakout` is useful when you are growing out of the basic input methods and synchronous
|
/// `GlutinBreakout` is useful when you are growing out of the basic input methods and synchronous
|
||||||
/// nature of [`MiniGlFb`][crate::MiniGlFb], since it's more powerful than the the higher-level
|
/// nature of [`MiniGlFb`][crate::MiniGlFb], since it's more powerful than the the higher-level
|
||||||
|
@ -215,6 +216,37 @@ impl GlutinBreakout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
|
pub struct Wakeup {
|
||||||
|
/// The [`Instant`] at which this wakeup is scheduled to happen. If the [`Instant`] is in the
|
||||||
|
/// past, the wakeup will happen instantly.
|
||||||
|
pub when: Instant,
|
||||||
|
|
||||||
|
/// A numeric identifier that can be used to determine which wakeup your callback is being run
|
||||||
|
/// for.
|
||||||
|
pub id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Wakeup {
|
||||||
|
/// Returns [`Instant::now`]`() + duration`.
|
||||||
|
pub fn after(duration: Duration) -> Instant {
|
||||||
|
Instant::now() + duration
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The same as [`Wakeup::after`], but constructs a [`Duration`] from a number of milliseconds,
|
||||||
|
/// since [`Duration`] methods are so long...
|
||||||
|
pub fn after_millis(millis: u64) -> Instant {
|
||||||
|
Self::after(Duration::from_millis(millis))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Modifies this wakeup to trigger after `duration` has passed from [`Instant::now`],
|
||||||
|
/// calculated via [`Wakeup::after`].
|
||||||
|
pub fn trigger_after(&mut self, duration: Duration) {
|
||||||
|
self.when = Self::after(duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Used for [`MiniGlFb::glutin_handle_basic_input`][crate::MiniGlFb::glutin_handle_basic_input].
|
/// Used for [`MiniGlFb::glutin_handle_basic_input`][crate::MiniGlFb::glutin_handle_basic_input].
|
||||||
/// Contains the current state of the window in a polling-like fashion.
|
/// Contains the current state of the window in a polling-like fashion.
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
@ -245,6 +277,24 @@ pub struct BasicInput {
|
||||||
/// If this is set to `true` by your callback, it will not be called as fast as possible, but
|
/// If this is set to `true` by your callback, it will not be called as fast as possible, but
|
||||||
/// rather only when the input changes.
|
/// rather only when the input changes.
|
||||||
pub wait: bool,
|
pub wait: bool,
|
||||||
|
/// A record of all the [`Wakeup`]s that are scheduled to happen. If your callback is being
|
||||||
|
/// called because of a wakeup, [`BasicInput::wakeup`] will be set to `Some(id)` where `id` is
|
||||||
|
/// the unique identifier of the [`Wakeup`].
|
||||||
|
///
|
||||||
|
/// Wakeups can be scheduled using [`BasicInput::schedule_wakeup`]. Wakeups can be cancelled
|
||||||
|
/// using [`BasicInput::cancel_wakeup`], or by removing the item from the [`Vec`].
|
||||||
|
// NOTE: THIS VEC IS SUPPOSED TO ALWAYS BE SORTED BY SOONEST WAKEUP FIRST!
|
||||||
|
// This contract MUST be upheld at all times, or else weird behavior will result. Only the
|
||||||
|
// wakeup at index 0 is ever checked at a time, no other wakeups will be queued if it is not due
|
||||||
|
// yet. DO NOT IGNORE THIS WARNING!
|
||||||
|
pub wakeups: Vec<Wakeup>,
|
||||||
|
/// Indicates to your callback which [`Wakeup`] it should be handling. Normally, it's okay to
|
||||||
|
/// ignore this, as it will always be [`None`] unless you manually schedule wakeups using
|
||||||
|
/// [`BasicInput::schedule_wakeup`].
|
||||||
|
pub wakeup: Option<Wakeup>,
|
||||||
|
// Internal variable used to keep track of what the next wakeup ID should be. Doesn't need to be
|
||||||
|
// `pub`; `BasicInput` is already `#[non_exhaustive]`.
|
||||||
|
_next_wakeup_id: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BasicInput {
|
impl BasicInput {
|
||||||
|
@ -287,4 +337,41 @@ impl BasicInput {
|
||||||
pub fn key_released(&self, button: VirtualKeyCode) -> bool {
|
pub fn key_released(&self, button: VirtualKeyCode) -> bool {
|
||||||
&(true, false) == self.keys.get(&button).unwrap_or(&(false, false))
|
&(true, false) == self.keys.get(&button).unwrap_or(&(false, false))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Given an [`Instant`] in the future (or in the past, in which case it will be triggered
|
||||||
|
/// immediately), schedules a wakeup to be triggered then. Returns the ID of the wakeup, which
|
||||||
|
/// will be the ID of [`BasicInput::wakeup`] if your callback is getting called by the wakeup.
|
||||||
|
pub fn schedule_wakeup(&mut self, when: Instant) -> u32 {
|
||||||
|
let wakeup = Wakeup { when, id: self._next_wakeup_id };
|
||||||
|
self._next_wakeup_id += 1;
|
||||||
|
self.reschedule_wakeup(wakeup);
|
||||||
|
wakeup.id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reschedules a wakeup. It is perfectly valid to re-use IDs of wakeups that have already been
|
||||||
|
/// triggered; that is why [`BasicInput::wakeup`] is a [`Wakeup`] and not just a [`u32`].
|
||||||
|
pub fn reschedule_wakeup(&mut self, wakeup: Wakeup) {
|
||||||
|
let at = self.wakeups.iter().position(|o| o.when > wakeup.when).unwrap_or(self.wakeups.len());
|
||||||
|
self.wakeups.insert(at, wakeup);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cancels a previously scheduled [`Wakeup`] by its ID. Returns the [`Wakeup`] if it is found,
|
||||||
|
/// otherwise returns [`None`].
|
||||||
|
pub fn cancel_wakeup(&mut self, id: u32) -> Option<Wakeup> {
|
||||||
|
Some(self.wakeups.remove(self.wakeups.iter().position(|w| w.id == id)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changing the time of an upcoming wakeup is common enough that there's a utility method to do
|
||||||
|
/// it for you. Given an ID and an [`Instant`], finds the [`Wakeup`] with the given ID and sets
|
||||||
|
/// its time to `when`. Returns `true` if a wakeup was found, `false` otherwise.
|
||||||
|
pub fn adjust_wakeup(&mut self, id: u32, when: Instant) -> bool {
|
||||||
|
if let Some(mut wakeup) = self.cancel_wakeup(id) {
|
||||||
|
// Put it back in the queue; this is important because it might end up somewhere else
|
||||||
|
wakeup.when = when;
|
||||||
|
self.reschedule_wakeup(wakeup);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
58
src/core.rs
58
src/core.rs
|
@ -12,7 +12,8 @@ use std::mem::size_of_val;
|
||||||
use glutin::window::WindowBuilder;
|
use glutin::window::WindowBuilder;
|
||||||
use glutin::event_loop::{EventLoop, ControlFlow};
|
use glutin::event_loop::{EventLoop, ControlFlow};
|
||||||
use glutin::platform::run_return::EventLoopExtRunReturn;
|
use glutin::platform::run_return::EventLoopExtRunReturn;
|
||||||
use glutin::event::{Event, WindowEvent, VirtualKeyCode, ElementState, KeyboardInput};
|
use glutin::event::{Event, WindowEvent, VirtualKeyCode, ElementState, KeyboardInput, StartCause};
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
/// Create a context using glutin given a configuration.
|
/// Create a context using glutin given a configuration.
|
||||||
pub fn init_glutin_context<S: ToString, ET: 'static>(
|
pub fn init_glutin_context<S: ToString, ET: 'static>(
|
||||||
|
@ -215,7 +216,7 @@ impl Internal {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn glutin_handle_basic_input<ET: 'static, F: FnMut(&mut Framebuffer, &BasicInput) -> bool>(
|
pub fn glutin_handle_basic_input<ET: 'static, F: FnMut(&mut Framebuffer, &mut BasicInput) -> bool>(
|
||||||
&mut self, event_loop: &mut EventLoop<ET>, mut handler: F
|
&mut self, event_loop: &mut EventLoop<ET>, mut handler: F
|
||||||
) {
|
) {
|
||||||
let mut previous_input: Option<BasicInput> = None;
|
let mut previous_input: Option<BasicInput> = None;
|
||||||
|
@ -234,9 +235,12 @@ impl Internal {
|
||||||
val.0 = val.1;
|
val.0 = val.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
match event {
|
match &event {
|
||||||
Event::WindowEvent { event, .. } => match event {
|
Event::WindowEvent { event, .. } => match event {
|
||||||
WindowEvent::CloseRequested => *flow = ControlFlow::Exit,
|
WindowEvent::CloseRequested => {
|
||||||
|
*flow = ControlFlow::Exit;
|
||||||
|
return;
|
||||||
|
},
|
||||||
WindowEvent::KeyboardInput {
|
WindowEvent::KeyboardInput {
|
||||||
input: KeyboardInput {
|
input: KeyboardInput {
|
||||||
virtual_keycode: Some(vk),
|
virtual_keycode: Some(vk),
|
||||||
|
@ -245,23 +249,23 @@ impl Internal {
|
||||||
},
|
},
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let key = input.keys.entry(vk)
|
let key = input.keys.entry(*vk)
|
||||||
.or_insert((false, false));
|
.or_insert((false, false));
|
||||||
key.1 = state == ElementState::Pressed;
|
key.1 = *state == ElementState::Pressed;
|
||||||
}
|
}
|
||||||
WindowEvent::CursorMoved { position, .. } => {
|
WindowEvent::CursorMoved { position, .. } => {
|
||||||
new_mouse_pos = Some(position);
|
new_mouse_pos = Some(*position);
|
||||||
}
|
}
|
||||||
WindowEvent::MouseInput { state, button, .. } => {
|
WindowEvent::MouseInput { state, button, .. } => {
|
||||||
let button = input.mouse.entry(button)
|
let button = input.mouse.entry(*button)
|
||||||
.or_insert((false, false));
|
.or_insert((false, false));
|
||||||
button.1 = state == ElementState::Pressed;
|
button.1 = *state == ElementState::Pressed;
|
||||||
}
|
}
|
||||||
WindowEvent::ModifiersChanged(modifiers) => {
|
WindowEvent::ModifiersChanged(modifiers) => {
|
||||||
input.modifiers = modifiers;
|
input.modifiers = *modifiers;
|
||||||
}
|
}
|
||||||
WindowEvent::Resized(logical_size) => {
|
WindowEvent::Resized(logical_size) => {
|
||||||
new_size = Some(logical_size);
|
new_size = Some(*logical_size);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
|
@ -270,7 +274,7 @@ impl Internal {
|
||||||
|
|
||||||
if let Some(size) = new_size {
|
if let Some(size) = new_size {
|
||||||
self.resize_viewport(size.width, size.height);
|
self.resize_viewport(size.width, size.height);
|
||||||
input.resized = false;
|
input.resized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(pos) = new_mouse_pos {
|
if let Some(pos) = new_mouse_pos {
|
||||||
|
@ -289,18 +293,40 @@ impl Internal {
|
||||||
input.mouse_pos = mouse_pos;
|
input.mouse_pos = mouse_pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while let Some(wakeup) = input.wakeups.get(0) {
|
||||||
|
if wakeup.when > Instant::now() { break; }
|
||||||
|
|
||||||
|
input.wakeup = Some(*wakeup);
|
||||||
|
input.wakeups.remove(0);
|
||||||
|
|
||||||
|
if !handler(&mut self.fb, &mut input) {
|
||||||
|
*flow = ControlFlow::Exit;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.wakeup = None;
|
||||||
|
|
||||||
if input.wait {
|
if input.wait {
|
||||||
*flow = ControlFlow::Wait;
|
if let Some(wakeup) = input.wakeups.get(0) {
|
||||||
|
*flow = ControlFlow::WaitUntil(wakeup.when)
|
||||||
|
} else {
|
||||||
|
*flow = ControlFlow::Wait;
|
||||||
|
}
|
||||||
|
|
||||||
// handler only wants to be notified when the input changes
|
// handler only wants to be notified when the input changes
|
||||||
if previous_input.as_ref().map_or(true, |p| *p != input) {
|
if previous_input.as_ref().map_or(true, |p| *p != input) {
|
||||||
if !handler(&mut self.fb, &input) {
|
// wakeups have already been handled
|
||||||
*flow = ControlFlow::Exit;
|
if let Event::NewEvents(StartCause::ResumeTimeReached { .. }) = &event {
|
||||||
|
} else {
|
||||||
|
if !handler(&mut self.fb, &mut input) {
|
||||||
|
*flow = ControlFlow::Exit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// handler wants to be notified regardless
|
// handler wants to be notified regardless
|
||||||
if !handler(&mut self.fb, &input) {
|
if !handler(&mut self.fb, &mut input) {
|
||||||
*flow = ControlFlow::Exit;
|
*flow = ControlFlow::Exit;
|
||||||
} else {
|
} else {
|
||||||
*flow = ControlFlow::Poll;
|
*flow = ControlFlow::Poll;
|
||||||
|
|
|
@ -352,7 +352,7 @@ impl MiniGlFb {
|
||||||
/// You can cause the handler to exit by returning false from it. This does not kill the
|
/// You can cause the handler to exit by returning false from it. This does not kill the
|
||||||
/// window, so as long as you still have it in scope, you can actually keep using it and,
|
/// window, so as long as you still have it in scope, you can actually keep using it and,
|
||||||
/// for example, resume handling input but with a different handler callback.
|
/// for example, resume handling input but with a different handler callback.
|
||||||
pub fn glutin_handle_basic_input<ET: 'static, F: FnMut(&mut Framebuffer, &BasicInput) -> bool>(
|
pub fn glutin_handle_basic_input<ET: 'static, F: FnMut(&mut Framebuffer, &mut BasicInput) -> bool>(
|
||||||
&mut self, event_loop: &mut EventLoop<ET>, handler: F
|
&mut self, event_loop: &mut EventLoop<ET>, handler: F
|
||||||
) {
|
) {
|
||||||
self.internal.glutin_handle_basic_input(event_loop, handler);
|
self.internal.glutin_handle_basic_input(event_loop, handler);
|
||||||
|
|
Loading…
Reference in a new issue