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 122 123 124 125 126 127
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
#![warn(missing_docs)]
use cfg_if::cfg_if;
use handler::AppHandler;
use std::sync::Arc;
use web_time::Duration;
use winit::{
event::Event,
event_loop::{ControlFlow, EventLoop},
window::Window,
};
mod handler;
mod input;
pub use input::{Input, InputState};
pub use anyhow;
pub use winit;
/// Update context.
#[derive(Debug)]
pub struct Context {
target_frame_time: Duration,
max_frame_time: Duration,
exit: bool,
delta_time: Duration,
/// Input handler.
pub input: Input,
}
impl Context {
/// Create a new context.
#[inline]
pub fn new(window: Arc<Window>, fps: u32, max_frame_time: Duration) -> Self {
Self {
target_frame_time: Duration::from_secs_f64(1. / fps as f64),
max_frame_time,
delta_time: Duration::ZERO,
exit: false,
input: Input::new(window),
}
}
/// `winit` window.
#[inline]
pub fn window(&self) -> &Window {
&self.input.window
}
/// Time between previous and current update.
#[inline]
pub fn frame_time(&self) -> Duration {
self.delta_time
}
/// Set the desired (minimum) time between application updates.
/// Implemented based on <https://gafferongames.com/post/fix_your_timestep>.
#[inline]
pub fn set_target_frame_time(&mut self, time: Duration) {
self.target_frame_time = time;
}
/// Set the desired FPS. Overrides `target_frame_time` since they are inversions of each other.
#[inline]
pub fn set_target_fps(&mut self, fps: u32) {
self.target_frame_time = Duration::from_secs_f64(1. / fps as f64);
}
/// Set the maximum time between application updates.
/// The real frame time can be longer, but [`Context::frame_time()`] will not exceed this value.
/// Implemented based on <https://gafferongames.com/post/fix_your_timestep>.
#[inline]
pub fn set_max_frame_time(&mut self, time: Duration) {
self.max_frame_time = time;
}
/// Exit the application.
#[inline]
pub fn exit(&mut self) {
self.exit = true;
}
}
/// Application trait.
pub trait App {
/// Application update.
/// Rate of updates can be set using [`Context`].
fn update(&mut self, ctx: &mut Context) -> anyhow::Result<()>;
/// Application render.
/// Will be called once every frame.
fn render(&mut self, blending_factor: f64) -> anyhow::Result<()>;
/// Custom event handler if needed.
#[inline]
fn handle(&mut self, _event: Event<()>) -> anyhow::Result<()> {
Ok(())
}
}
/// Start the application.
///
/// Depending on the platform, this function may not return (see <https://docs.rs/winit/latest/winit/event_loop/struct.EventLoop.html#method.run_app>).
/// On web uses <https://docs.rs/winit/latest/wasm32-unknown-unknown/winit/platform/web/trait.EventLoopExtWebSys.html#tymethod.spawn_app> instead of `run_app()`.
pub fn start(
event_loop: EventLoop<()>,
context: Context,
app: impl App + 'static,
) -> anyhow::Result<()> {
event_loop.set_control_flow(ControlFlow::Poll);
#[cfg_attr(target_arch = "wasm32", allow(unused_mut))]
let mut handler = AppHandler::new(context, app);
cfg_if! {
if #[cfg(target_arch = "wasm32")] {
use winit::platform::web::EventLoopExtWebSys;
event_loop.spawn_app(handler);
} else {
event_loop.run_app(&mut handler)?;
}
}
Ok(())
}