veloren/voxygen/src/run.rs
Marcel Märtens e4e5c6e55b massivly switch clock algorithm.
- before we had a Clock that tried to average multiple ticks and predict the next sleep.
   This system is massivly bugged.
   a) We know exactly how long the busy time took, so we dont need to predict anything in the first place
   b) Preduction was totally unrealistic after a single lag spike
   c) When a very slow tick happens, we dont benefit from 10 fast ticks.
 - Instead we just try to keep the tick time exact what we expect.
   If we can't manage a constant tick time because we are to slow, the systems have to "catch" this via the `dt` anyway.
2020-11-10 18:31:42 +01:00

181 lines
6.7 KiB
Rust

use crate::{
menu::main::MainMenuState,
ui,
window::{Event, EventLoop},
Direction, GlobalState, PlayState, PlayStateResult,
};
use common::{no_guard_span, span, util::GuardlessSpan};
use std::{mem, time::Duration};
use tracing::debug;
pub fn run(mut global_state: GlobalState, event_loop: EventLoop) {
// Set up the initial play state.
let mut states: Vec<Box<dyn PlayState>> = vec![Box::new(MainMenuState::new(&mut global_state))];
states.last_mut().map(|current_state| {
current_state.enter(&mut global_state, Direction::Forwards);
let current_state = current_state.name();
debug!(?current_state, "Started game with state");
});
// Used to ignore every other `MainEventsCleared`
// This is a workaround for a bug on macos in which mouse motion events are only
// reported every other cycle of the event loop
// See: https://github.com/rust-windowing/winit/issues/1418
let mut polled_twice = false;
let mut poll_span = None;
let mut event_span = None;
event_loop.run(move |event, _, control_flow| {
// Continuously run loop since we handle sleeping
*control_flow = winit::event_loop::ControlFlow::Poll;
// Get events for the ui.
if let Some(event) = ui::Event::try_from(&event, global_state.window.window()) {
global_state.window.send_event(Event::Ui(event));
}
match event {
winit::event::Event::NewEvents(_) => {
event_span = Some(no_guard_span!("Process Events"));
},
winit::event::Event::MainEventsCleared => {
event_span.take();
poll_span.take();
if polled_twice {
handle_main_events_cleared(&mut states, control_flow, &mut global_state);
}
poll_span = Some(no_guard_span!("Poll Winit"));
polled_twice = !polled_twice;
},
winit::event::Event::WindowEvent { event, .. } => {
span!(_guard, "Handle WindowEvent");
global_state
.window
.handle_window_event(event, &mut global_state.settings)
},
winit::event::Event::DeviceEvent { event, .. } => {
span!(_guard, "Handle DeviceEvent");
global_state.window.handle_device_event(event)
},
winit::event::Event::LoopDestroyed => {
// Save any unsaved changes to settings and profile
global_state.settings.save_to_file_warn();
global_state.profile.save_to_file_warn();
},
_ => {},
}
});
}
fn handle_main_events_cleared(
states: &mut Vec<Box<dyn PlayState>>,
control_flow: &mut winit::event_loop::ControlFlow,
global_state: &mut GlobalState,
) {
span!(guard, "Handle MainEventsCleared");
// Screenshot / Fullscreen toggle
global_state
.window
.resolve_deduplicated_events(&mut global_state.settings);
// Run tick here
// What's going on here?
// ---------------------
// The state system used by Voxygen allows for the easy development of
// stack-based menus. For example, you may want a "title" state
// that can push a "main menu" state on top of it, which can in
// turn push a "settings" state or a "game session" state on top of it.
// The code below manages the state transfer logic automatically so that we
// don't have to re-engineer it for each menu we decide to add
// to the game.
let mut exit = true;
while let Some(state_result) = states.last_mut().map(|last| {
let events = global_state.window.fetch_events();
last.tick(global_state, events)
}) {
// Implement state transfer logic.
match state_result {
PlayStateResult::Continue => {
exit = false;
break;
},
PlayStateResult::Shutdown => {
debug!("Shutting down all states...");
while states.last().is_some() {
states.pop().map(|old_state| {
debug!("Popped state '{}'.", old_state.name());
global_state.on_play_state_changed();
});
}
},
PlayStateResult::Pop => {
states.pop().map(|old_state| {
debug!("Popped state '{}'.", old_state.name());
global_state.on_play_state_changed();
});
states.last_mut().map(|new_state| {
new_state.enter(global_state, Direction::Backwards);
});
},
PlayStateResult::Push(mut new_state) => {
new_state.enter(global_state, Direction::Forwards);
debug!("Pushed state '{}'.", new_state.name());
states.push(new_state);
global_state.on_play_state_changed();
},
PlayStateResult::Switch(mut new_state) => {
new_state.enter(global_state, Direction::Forwards);
states.last_mut().map(|old_state| {
debug!(
"Switching to state '{}' from state '{}'.",
new_state.name(),
old_state.name()
);
mem::swap(old_state, &mut new_state);
global_state.on_play_state_changed();
});
},
}
}
if exit {
*control_flow = winit::event_loop::ControlFlow::Exit;
}
drop(guard);
if let Some(last) = states.last_mut() {
span!(guard, "Render");
let renderer = global_state.window.renderer_mut();
// Clear the shadow maps.
renderer.clear_shadows();
// Clear the screen
renderer.clear();
// Render the screen using the global renderer
last.render(renderer, &global_state.settings);
// Finish the frame.
global_state.window.renderer_mut().flush();
// Display the frame on the window.
global_state
.window
.swap_buffers()
.expect("Failed to swap window buffers!");
drop(guard);
#[cfg(feature = "tracy")]
common::util::tracy_client::finish_continuous_frame!();
}
if !exit {
// Wait for the next tick.
span!(_guard, "Main thread sleep");
global_state.clock.set_target_dt(Duration::from_millis(
1000 / global_state.settings.graphics.max_fps as u64,
));
global_state.clock.tick();
span!(_guard, "Maintain global state");
// Maintain global state.
global_state.maintain(global_state.clock.dt());
}
}