use crate::engine::ApplicationLoopController;
use crate::scene::Scene;
use crate::{
asset::manager::ResourceManager,
core::{
instant::Instant,
log::{Log, MessageKind},
task::TaskPool,
},
engine::{
Engine, EngineInitParams, GraphicsContext, GraphicsContextParams, SerializationContext,
},
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
plugin::Plugin,
utils::translate_event,
window::WindowAttributes,
};
use clap::Parser;
use fyrox_core::pool::Handle;
use fyrox_resource::io::FsResourceIo;
use fyrox_ui::constructor::new_widget_constructor_container;
use std::cell::Cell;
use std::collections::VecDeque;
use std::time::Duration;
use std::{
ops::{Deref, DerefMut},
sync::Arc,
};
use winit::event_loop::ActiveEventLoop;
#[derive(Parser, Debug, Default)]
#[clap(author, version, about, long_about = None)]
struct Args {
#[clap(short, long, default_value = None)]
override_scene: Option<String>,
}
pub struct Executor {
event_loop: Option<EventLoop<()>>,
engine: Engine,
desired_update_rate: f32,
throttle_threshold: f32,
throttle_frame_interval: usize,
resource_hot_reloading: bool,
}
impl Deref for Executor {
type Target = Engine;
fn deref(&self) -> &Self::Target {
&self.engine
}
}
impl DerefMut for Executor {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.engine
}
}
impl Executor {
pub const DEFAULT_UPDATE_RATE: f32 = 60.0;
pub const DEFAULT_TIME_STEP: f32 = 1.0 / Self::DEFAULT_UPDATE_RATE;
pub fn from_params(
event_loop: Option<EventLoop<()>>,
graphics_context_params: GraphicsContextParams,
) -> Self {
let serialization_context = Arc::new(SerializationContext::new());
let task_pool = Arc::new(TaskPool::new());
let io = Arc::new(FsResourceIo);
let engine = Engine::new(EngineInitParams {
graphics_context_params,
resource_manager: ResourceManager::new(io, task_pool.clone()),
serialization_context,
task_pool,
widget_constructors: Arc::new(new_widget_constructor_container()),
dyn_type_constructors: Default::default(),
})
.unwrap();
Self {
event_loop,
engine,
desired_update_rate: Self::DEFAULT_UPDATE_RATE,
throttle_threshold: 2.0 * Self::DEFAULT_TIME_STEP,
throttle_frame_interval: 5,
resource_hot_reloading: true,
}
}
pub fn new(event_loop: Option<EventLoop<()>>) -> Self {
let mut window_attributes = WindowAttributes::default();
window_attributes.resizable = true;
window_attributes.title = "Fyrox Game".to_string();
Self::from_params(
event_loop,
GraphicsContextParams {
window_attributes,
vsync: true,
msaa_sample_count: None,
graphics_server_constructor: Default::default(),
named_objects: false,
},
)
}
pub fn set_resource_hot_reloading_enabled(&mut self, enabled: bool) {
self.resource_hot_reloading = enabled;
}
pub fn is_resource_hot_reloading_enabled(&self) -> bool {
self.resource_hot_reloading
}
pub fn set_throttle_threshold(&mut self, threshold: f32) {
self.throttle_threshold = threshold.max(0.001);
}
pub fn throttle_threshold(&self) -> f32 {
self.throttle_threshold
}
pub fn set_throttle_frame_interval(&mut self, interval: usize) {
self.throttle_frame_interval = interval;
}
pub fn throttle_frame_interval(&self) -> usize {
self.throttle_frame_interval
}
pub fn set_desired_update_rate(&mut self, update_rate: f32) {
self.desired_update_rate = update_rate.abs();
}
pub fn desired_update_rate(&self) -> f32 {
self.desired_update_rate
}
pub fn add_plugin<P>(&mut self, plugin: P)
where
P: Plugin + 'static,
{
self.engine.add_plugin(plugin)
}
pub fn run(self) {
Log::info("Initializing resource registry.");
self.engine.resource_manager.update_or_load_registry();
let engine = self.engine;
let event_loop = self.event_loop;
let throttle_threshold = self.throttle_threshold;
let throttle_frame_interval = self.throttle_frame_interval;
if self.resource_hot_reloading {
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
{
use crate::core::watcher::FileSystemWatcher;
use std::time::Duration;
match FileSystemWatcher::new(".", Duration::from_secs(1)) {
Ok(watcher) => {
engine.resource_manager.state().set_watcher(Some(watcher));
}
Err(e) => {
Log::err(format!("Unable to create resource watcher. Reason {e:?}"));
}
}
}
}
let args = Args::try_parse().unwrap_or_default();
match event_loop {
Some(event_loop) => run_normal(
engine,
args.override_scene.as_deref(),
event_loop,
throttle_threshold,
throttle_frame_interval,
self.desired_update_rate,
),
None => run_headless(
engine,
args.override_scene.as_deref(),
throttle_threshold,
throttle_frame_interval,
self.desired_update_rate,
),
}
}
}
fn run_headless(
mut engine: Engine,
override_scene: Option<&str>,
throttle_threshold: f32,
throttle_frame_interval: usize,
desired_update_rate: f32,
) {
let mut previous = Instant::now();
let fixed_time_step = 1.0 / desired_update_rate;
let mut lag = fixed_time_step;
let mut frame_counter = 0usize;
let mut last_throttle_frame_number = 0usize;
let is_running = Cell::new(true);
while is_running.get() {
if !engine.plugins_enabled && engine.resource_manager.registry_is_loaded() {
engine.enable_plugins(
override_scene,
true,
ApplicationLoopController::Headless {
running: &is_running,
},
);
}
register_scripted_scenes(&mut engine);
game_loop_iteration(
&mut engine,
ApplicationLoopController::Headless {
running: &is_running,
},
&mut previous,
&mut lag,
fixed_time_step,
throttle_threshold,
throttle_frame_interval,
frame_counter,
&mut last_throttle_frame_number,
);
frame_counter += 1;
let sleep_time = (fixed_time_step - previous.elapsed().as_secs_f32()).max(0.0) * 0.66666;
if sleep_time > 0.0 {
std::thread::sleep(Duration::from_secs_f32(sleep_time));
}
}
}
fn run_normal(
mut engine: Engine,
override_scene: Option<&str>,
event_loop: EventLoop<()>,
throttle_threshold: f32,
throttle_frame_interval: usize,
desired_update_rate: f32,
) {
let mut previous = Instant::now();
let fixed_time_step = 1.0 / desired_update_rate;
let mut lag = 0.0;
let mut frame_counter = 0usize;
let mut last_throttle_frame_number = 0usize;
let override_scene = override_scene.map(|s| s.to_string());
enum GraphicsEvent {
GraphicsContextInitialized,
GraphicsContextDestroyed,
}
let mut graphics_event_queue = VecDeque::new();
run_executor(event_loop, move |event, active_event_loop| {
active_event_loop.set_control_flow(ControlFlow::Wait);
engine.handle_os_events(
&event,
fixed_time_step,
ApplicationLoopController::ActiveEventLoop(active_event_loop),
&mut lag,
);
if !engine.plugins_enabled && engine.resource_manager.registry_is_loaded() {
engine.enable_plugins(
override_scene.as_deref(),
true,
ApplicationLoopController::ActiveEventLoop(active_event_loop),
);
while let Some(graphics_event) = graphics_event_queue.pop_front() {
match graphics_event {
GraphicsEvent::GraphicsContextInitialized => {
engine.handle_graphics_context_created_by_plugins(
fixed_time_step,
ApplicationLoopController::ActiveEventLoop(active_event_loop),
&mut lag,
);
}
GraphicsEvent::GraphicsContextDestroyed => {
engine.handle_graphics_context_destroyed_by_plugins(
fixed_time_step,
ApplicationLoopController::ActiveEventLoop(active_event_loop),
&mut lag,
);
}
}
}
}
let scripted_scenes = register_scripted_scenes(&mut engine);
for scripted_scene in scripted_scenes {
engine.handle_os_event_by_scripts(&event, scripted_scene, fixed_time_step);
}
match event {
Event::Resumed => {
engine
.initialize_graphics_context(active_event_loop)
.expect("Unable to initialize graphics context!");
if engine.plugins_enabled {
engine.handle_graphics_context_created_by_plugins(
fixed_time_step,
ApplicationLoopController::ActiveEventLoop(active_event_loop),
&mut lag,
);
} else {
graphics_event_queue.push_back(GraphicsEvent::GraphicsContextInitialized);
}
}
Event::Suspended => {
engine
.destroy_graphics_context()
.expect("Unable to destroy graphics context!");
if engine.plugins_enabled {
engine.handle_graphics_context_destroyed_by_plugins(
fixed_time_step,
ApplicationLoopController::ActiveEventLoop(active_event_loop),
&mut lag,
);
} else {
graphics_event_queue.push_back(GraphicsEvent::GraphicsContextDestroyed);
}
}
Event::AboutToWait => {
game_loop_iteration(
&mut engine,
ApplicationLoopController::ActiveEventLoop(active_event_loop),
&mut previous,
&mut lag,
fixed_time_step,
throttle_threshold,
throttle_frame_interval,
frame_counter,
&mut last_throttle_frame_number,
);
}
Event::WindowEvent { event, .. } => {
match event {
WindowEvent::CloseRequested => active_event_loop.exit(),
WindowEvent::Resized(size) => {
if let Err(e) = engine.set_frame_size(size.into()) {
Log::writeln(
MessageKind::Error,
format!("Unable to set frame size: {e:?}"),
);
}
}
WindowEvent::RedrawRequested => {
engine.handle_before_rendering_by_plugins(
fixed_time_step,
ApplicationLoopController::ActiveEventLoop(active_event_loop),
&mut lag,
);
engine.render().unwrap();
frame_counter += 1;
}
_ => (),
}
if let Some(os_event) = translate_event(&event) {
for ui in engine.user_interfaces.iter_mut() {
ui.process_os_event(&os_event);
}
}
}
_ => (),
}
})
}
fn register_scripted_scenes(engine: &mut Engine) -> Vec<Handle<Scene>> {
let scenes = engine
.scenes
.pair_iter()
.map(|(s, _)| s)
.collect::<Vec<_>>();
for &scene_handle in scenes.iter() {
if !engine.has_scripted_scene(scene_handle) {
engine.register_scripted_scene(scene_handle);
}
}
scenes
}
fn game_loop_iteration(
engine: &mut Engine,
controller: ApplicationLoopController,
previous: &mut Instant,
lag: &mut f32,
fixed_time_step: f32,
throttle_threshold: f32,
throttle_frame_interval: usize,
frame_counter: usize,
last_throttle_frame_number: &mut usize,
) {
let elapsed = previous.elapsed();
*previous = Instant::now();
*lag += elapsed.as_secs_f32();
while *lag >= fixed_time_step {
let time_step;
if *lag >= throttle_threshold
&& (frame_counter - *last_throttle_frame_number >= throttle_frame_interval)
{
time_step = *lag;
*lag = 0.0;
*last_throttle_frame_number = frame_counter;
} else {
time_step = fixed_time_step;
}
engine.update(time_step, controller, lag, Default::default());
if *lag >= fixed_time_step {
*lag -= fixed_time_step;
} else if *lag < 0.0 {
*lag = 0.0;
}
}
if let GraphicsContext::Initialized(ref ctx) = engine.graphics_context {
ctx.window.request_redraw();
}
}
#[allow(deprecated)] fn run_executor<F>(event_loop: EventLoop<()>, callback: F)
where
F: FnMut(Event<()>, &ActiveEventLoop) + 'static,
{
#[cfg(target_arch = "wasm32")]
{
use winit::platform::web::EventLoopExtWebSys;
event_loop.spawn(callback);
}
#[cfg(not(target_arch = "wasm32"))]
{
event_loop.run(callback).unwrap();
}
}