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 128
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
#![warn(missing_docs)]
use cfg_if::cfg_if;
use handler::AppHandler;
use web_time::Duration;
use winit::{
event::Event,
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
};
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(crate) fn new(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(),
}
}
/// 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(())
}
}
/// App initialization function.
pub type AppInitFunc<A> = dyn FnOnce(&ActiveEventLoop) -> anyhow::Result<A>;
/// Start the application.
/// `app_init` will be called once during the next [`Event::Resumed`](https://docs.rs/winit/latest/winit/event/enum.Event.html#variant.Resumed).
///
/// 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<A>(
fps: u32,
max_frame_time: Duration,
app_init: Box<AppInitFunc<A>>,
) -> anyhow::Result<()>
where
A: App,
{
let event_loop = EventLoop::new()?;
event_loop.set_control_flow(ControlFlow::Poll);
#[cfg_attr(target_arch = "wasm32", allow(unused_mut))]
let mut handler = AppHandler::new(app_init, fps, max_frame_time);
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(())
}