use std::time::Duration;
use bevy::{prelude::*, winit::{UpdateMode, WinitSettings}};
use bevy_framepace::{FramepacePlugin, FramepaceSettings, Limiter};
pub struct QuickResponsePlugin {
mode: QuickResponseMode,
}
#[derive(Debug, Clone, PartialEq, Copy)]
pub enum QuickResponseMode {
FastVsync (QuickResponseParameters),
Immediate (QuickResponseParameters),
AutoNoVsync (QuickResponseParameters),
PowerSaving (QuickResponseParametersWithNoBaseFps),
None(bool)
}
impl Default for QuickResponseMode {
fn default() -> Self {
QuickResponseMode::FastVsync(QuickResponseParameters::default())
}
}
#[derive(Debug, Clone, PartialEq, Copy)]
pub struct QuickResponseParameters {
pub base_fps: f64,
pub max_fps: f64,
pub auto_init_default_plugins: bool
}
#[derive(Debug, Clone, PartialEq, Copy)]
pub struct QuickResponseParametersWithNoBaseFps {
pub max_fps: f64,
pub auto_init_default_plugins: bool
}
impl Default for QuickResponseParameters {
fn default() -> Self {
QuickResponseParameters {
base_fps: 60.0,
max_fps: 120.0,
auto_init_default_plugins: true
}
}
}
impl QuickResponsePlugin {
pub fn new(mode: QuickResponseMode) -> Self {
QuickResponsePlugin {
mode,
}
}
pub fn power_saving(max_fps: f64) -> Self {
QuickResponsePlugin::new(QuickResponseMode::PowerSaving(QuickResponseParametersWithNoBaseFps {
max_fps,
auto_init_default_plugins: true
}))
}
pub fn none(is_default_plugins_enabled: bool) -> Self {
QuickResponsePlugin::new(QuickResponseMode::None(true))
}
pub fn window_plugin(&self) -> WindowPlugin {
match self.mode {
QuickResponseMode::FastVsync(_) => {
WindowPlugin {
primary_window: Some(Window {
#[cfg(target_os = "windows")]
present_mode: bevy::window::PresentMode::Mailbox,
#[cfg(target_os = "macos")]
present_mode: bevy::window::PresentMode::AutoNoVsync,
#[cfg(target_os = "linux")]
present_mode: bevy::window::PresentMode::Mailbox,
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
present_mode: bevy::window::PresentMode::AutoNoVsync,
..default()
}),
..default()
}
},
QuickResponseMode::Immediate(_) => {
WindowPlugin {
primary_window: Some(Window {
present_mode: bevy::window::PresentMode::Immediate,
..default()
}),
..default()
}
},
QuickResponseMode::AutoNoVsync(_) => {
WindowPlugin {
primary_window: Some(Window {
present_mode: bevy::window::PresentMode::AutoNoVsync,
..default()
}),
..default()
}
},
QuickResponseMode::PowerSaving(_) => {
WindowPlugin {
primary_window: Some(Window {
#[cfg(target_os = "windows")]
present_mode: bevy::window::PresentMode::Mailbox,
#[cfg(target_os = "macos")]
present_mode: bevy::window::PresentMode::AutoNoVsync,
#[cfg(target_os = "linux")]
present_mode: bevy::window::PresentMode::Mailbox,
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
present_mode: bevy::window::PresentMode::AutoNoVsync,
..default()
}),
..default()
}
},
QuickResponseMode::None(_) => {
WindowPlugin::default()
}
}
}
}
impl Default for QuickResponsePlugin {
fn default() -> Self {
QuickResponsePlugin::new(QuickResponseMode::default())
}
}
fn setup_fps(max_fps: f64) -> impl Fn(ResMut<FramepaceSettings>) {
move |mut framepace_settings: ResMut<FramepaceSettings>| {
framepace_settings.limiter = Limiter::from_framerate(max_fps);
}
}
fn is_base_fps_enabled(mode: QuickResponseMode) -> bool {
match mode {
QuickResponseMode::FastVsync(_) => true,
QuickResponseMode::Immediate(_) => true,
QuickResponseMode::AutoNoVsync(_) => true,
QuickResponseMode::PowerSaving(_) => false,
QuickResponseMode::None(_) => false,
}
}
fn is_power_saving_enabled(mode: QuickResponseMode) -> bool {
match mode {
QuickResponseMode::FastVsync(_) => false,
QuickResponseMode::Immediate(_) => false,
QuickResponseMode::AutoNoVsync(_) => false,
QuickResponseMode::PowerSaving(_) => true,
QuickResponseMode::None(_) => false,
}
}
impl Plugin for QuickResponsePlugin {
fn build(&self, app: &mut App) {
if self.mode == QuickResponseMode::None(false) {
return;
} else if self.mode == QuickResponseMode::None(true) {
app.add_plugins(DefaultPlugins);
return;
}
if is_base_fps_enabled(self.mode) {
let base_fps = match self.mode {
QuickResponseMode::FastVsync(params) => params.base_fps,
QuickResponseMode::AutoNoVsync(params) => params.base_fps,
QuickResponseMode::Immediate(params) => params.base_fps,
QuickResponseMode::PowerSaving(_) => unreachable!(),
QuickResponseMode::None(_) => unreachable!(),
};
app
.insert_resource(WinitSettings {
focused_mode: UpdateMode::ReactiveLowPower { wait: Duration::from_secs_f64(1.0 / base_fps) },
unfocused_mode: UpdateMode::ReactiveLowPower { wait: Duration::from_secs_f64(1.0 / base_fps) },
..default()
})
;
} else if is_power_saving_enabled(self.mode) {
app
.insert_resource(WinitSettings::desktop_app())
;
}
let max_fps = match self.mode {
QuickResponseMode::FastVsync(params) => params.max_fps,
QuickResponseMode::AutoNoVsync(params) => params.max_fps,
QuickResponseMode::Immediate(params) => params.max_fps,
QuickResponseMode::PowerSaving(params) => params.max_fps,
QuickResponseMode::None(_) => unreachable!(),
};
let auto_init_default_plugins = match self.mode {
QuickResponseMode::FastVsync(params) => params.auto_init_default_plugins,
QuickResponseMode::AutoNoVsync(params) => params.auto_init_default_plugins,
QuickResponseMode::Immediate(params) => params.auto_init_default_plugins,
QuickResponseMode::PowerSaving(params) => params.auto_init_default_plugins,
QuickResponseMode::None(_) => unreachable!(),
};
app
.add_plugins(())
;
if auto_init_default_plugins {
app.add_plugins(DefaultPlugins.set(
self.window_plugin()
));
}
app.add_plugins(FramepacePlugin);
app.add_systems(Startup, setup_fps(max_fps));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plugin() {
App::new()
.add_plugins(QuickResponsePlugin::default())
.update();
}
}