veloren/voxygen/src/run.rs
2020-11-11 03:02:22 -05:00

191 lines
7.1 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));
}
// iced ui events
// TODO: no clone
if let winit::event::Event::WindowEvent { event, .. } = &event {
let window = &mut global_state.window;
if let Some(event) =
ui::ice::window_event(event, window.scale_factor(), window.modifiers())
{
window.send_event(Event::IcedUi(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());
}
}