// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. //! Engine is container for all subsystems (renderer, ui, sound, resource manager). It also //! creates a window and an OpenGL context. #![warn(missing_docs)] pub mod error; pub mod executor; pub mod input; pub mod task; mod hotreload; mod wasm_utils; use crate::plugin::error::{GameError, GameResult}; use crate::{ asset::{ event::ResourceEvent, manager::{ResourceManager, ResourceWaitContext}, state::ResourceState, untyped::ResourceKind, }, core::{ algebra::Vector2, err, futures::{executor::block_on, future::join_all}, instant, log::Log, pool::Handle, reflect::prelude::*, reflect::Reflect, task::TaskPool, warn, SafeLock, }, engine::{error::EngineError, input::InputState, task::TaskPoolHandler}, event::Event, graph::SceneGraph, graphics::error::FrameworkError, gui::{ constructor::WidgetConstructorContainer, font::{ loader::FontLoader, Font, BOLD_ITALIC, BUILT_IN_BOLD, BUILT_IN_FONT, BUILT_IN_ITALIC, }, loader::UserInterfaceLoader, style::{self, resource::StyleLoader, Style}, RenderMode, UiContainer, UiUpdateSwitches, UserInterface, }, material::{ self, loader::MaterialLoader, shader::{loader::ShaderLoader, Shader, ShaderResource, ShaderResourceExtension}, Material, }, plugin::{ dylib::DyLibDynamicPlugin, DynamicPlugin, Plugin, PluginContainer, PluginContext, PluginRegistrationContext, }, renderer::{ui_renderer::UiRenderInfo, Renderer}, resource::{ curve::{loader::CurveLoader, CurveResourceState}, gltf::material::GLTF_SHADER, model::{loader::ModelLoader, Model, ModelResource}, texture::{ self, loader::TextureLoader, CompressionOptions, Texture, TextureImportOptions, TextureKind, TextureMinificationFilter, TextureResource, TextureResourceExtension, }, }, scene::{ base::NodeScriptMessage, graph::GraphUpdateSwitches, mesh::surface::{self, SurfaceData, SurfaceDataLoader}, node::{ constructor::{new_node_constructor_container, NodeConstructorContainer}, Node, }, skybox::SkyBoxKind, sound::SoundEngine, tilemap::{ brush::{TileMapBrush, TileMapBrushLoader}, tileset::{TileSet, TileSetLoader}, CustomTileCollider, TileMapData, }, Scene, SceneContainer, }, script::{ constructor::ScriptConstructorContainer, DynamicTypeId, PluginsRefMut, RoutingStrategy, Script, ScriptContext, ScriptDeinitContext, ScriptMessage, ScriptMessageContext, ScriptMessageKind, ScriptMessageSender, UniversalScriptContext, }, window::Window, }; use fxhash::{FxHashMap, FxHashSet}; use fyrox_animation::AnimationTracksData; use fyrox_core::dyntype::DynTypeConstructorContainer; use fyrox_core::NameProvider; use fyrox_graphics::server::SharedGraphicsServer; use fyrox_graphics_gl::server::GlGraphicsServer; use fyrox_sound::{ buffer::{loader::SoundBufferLoader, SoundBuffer}, renderer::hrtf::{HrirSphereLoader, HrirSphereResourceData}, }; use std::fmt::Debug; use std::{ any::TypeId, cell::Cell, collections::{HashSet, VecDeque}, fmt::{Display, Formatter}, io::Cursor, ops::{Deref, DerefMut}, path::Path, rc::Rc, sync::{ mpsc::{channel, Receiver}, Arc, }, time::Duration, }; use uuid::Uuid; use winit::event::{DeviceEvent, ElementState, WindowEvent}; use winit::event_loop::{ActiveEventLoop, EventLoop}; use winit::{ dpi::{Position, Size}, window::Icon, window::WindowAttributes, }; /// Serialization context holds runtime type information that allows to create unknown types using /// their UUIDs and a respective constructors. #[derive(Reflect)] #[reflect( hide_all, non_cloneable, non_comparable, type_uuid = "1973a8c9-6ac6-48a2-bca1-9b4f3cce0774" )] pub struct SerializationContext { /// A node constructor container. pub node_constructors: NodeConstructorContainer, /// A script constructor container. pub script_constructors: ScriptConstructorContainer, } impl Debug for SerializationContext { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "SerializationContext") } } impl Default for SerializationContext { fn default() -> Self { Self::new() } } impl SerializationContext { /// Creates default serialization context. pub fn new() -> Self { Self { node_constructors: new_node_constructor_container(), script_constructors: ScriptConstructorContainer::new(), } } /// Removes all registered constructors. pub fn clear(&self) { self.node_constructors.clear(); self.script_constructors.clear(); } } /// Performance statistics. #[derive(Debug, Default)] pub struct PerformanceStatistics { /// Amount of time spent in the UI system. pub ui_time: Duration, /// Amount of time spent in updating/initializing/destructing scripts of all scenes. pub scripts_time: Duration, /// Amount of time spent in plugins updating. pub plugins_time: Duration, } impl Display for PerformanceStatistics { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { writeln!( f, "Performance Statistics:\n\tUI: {:?}\n\tScripts: {:?}\n\tPlugins: {:?}", self.ui_time, self.scripts_time, self.plugins_time ) } } /// An initialized graphics context. It contains the main application window and the renderer instance. pub struct InitializedGraphicsContext { /// Main application window. pub window: Window, /// Current renderer. pub renderer: Renderer, params: GraphicsContextParams, } impl InitializedGraphicsContext { /// Tries to set a new icon for the window from the given data source. The data source must contain /// some of the supported texture types data (png, bmp, jpg images). You can call this method /// with [`include_bytes`] macro to pass file's data directly. pub fn set_window_icon_from_memory(&mut self, data: &[u8]) { if let Ok(texture) = TextureResource::load_from_memory( Uuid::new_v4(), ResourceKind::Embedded, data, TextureImportOptions::default() .with_compression(CompressionOptions::NoCompression) .with_minification_filter(TextureMinificationFilter::Linear), ) { self.set_window_icon_from_texture(&texture); } } /// Tries to set a new icon for the window using the given texture. pub fn set_window_icon_from_texture(&mut self, texture: &TextureResource) { let data = texture.data_ref(); if let TextureKind::Rectangle { width, height } = data.kind() { if let Ok(img) = Icon::from_rgba(data.data().to_vec(), width, height) { self.window.set_window_icon(Some(img)); } } } } /// Graphics context of the engine, it could be in two main states: /// /// - [`GraphicsContext::Initialized`] - active graphics context, that is fully initialized and ready for use. /// - [`GraphicsContext::Uninitialized`] - suspended graphics context, that contains a set of params that could /// be used for further initialization. /// /// By default, when you creating an engine, there's no graphics context initialized. It must be initialized /// manually (if you need it) on [`Event::Resumed`]. On most operating systems, it is possible to initialize /// graphics context right after the engine was created. However Android won't allow you to do this, also on /// some versions of macOS immediate initialization could lead to panic. /// /// You can switch between these states whenever you need, for example if your application does not need a /// window and a renderer at all you can just not create graphics context. This could be useful for game /// servers or background applications. When you destroy a graphics context, the engine will remember the options /// with which it was created and some of the main window parameters (position, size, etc.) and will re-use these /// parameters on a next initialization attempt. #[allow(clippy::large_enum_variant)] pub enum GraphicsContext { /// Fully initialized graphics context. See [`InitializedGraphicsContext`] docs for more info. Initialized(InitializedGraphicsContext), /// Uninitialized (suspended) graphics context. See [`GraphicsContextParams`] docs for more info. Uninitialized(GraphicsContextParams), } impl GraphicsContext { /// Attempts to cast a graphics context to its initialized version. The method will panic if the context /// is not initialized. pub fn as_initialized_ref(&self) -> &InitializedGraphicsContext { if let GraphicsContext::Initialized(ctx) = self { ctx } else { panic!("Graphics context is uninitialized!") } } /// Attempts to cast a graphics context to its initialized version. The method will panic if the context /// is not initialized. pub fn as_initialized_mut(&mut self) -> &mut InitializedGraphicsContext { if let GraphicsContext::Initialized(ctx) = self { ctx } else { panic!("Graphics context is uninitialized!") } } } pub(crate) enum GameErrorSource { PluginMethod(&'static str), ScriptMethod { scene_handle: Handle, node_handle: Handle, method_name: &'static str, }, } pub(crate) struct GameErrorContainer { source: GameErrorSource, error: GameError, } type ErrorQueue = VecDeque; /// See module docs. pub struct Engine { /// Graphics context of the engine. See [`GraphicsContext`] docs for more info. pub graphics_context: GraphicsContext, /// Current resource manager. Resource manager can be cloned (it does clone only ref) to be able to /// use resource manager from any thread, this is useful to load resources from multiple /// threads to decrease loading times of your game by utilizing all available power of /// your CPU. pub resource_manager: ResourceManager, /// All available user interfaces in the engine. pub user_interfaces: UiContainer, /// All available scenes in the engine. pub scenes: SceneContainer, /// Task pool for asynchronous task management. pub task_pool: TaskPoolHandler, performance_statistics: PerformanceStatistics, model_events_receiver: Receiver, sound_engine: SoundEngine, // A set of plugins used by the engine. plugins: Vec, plugins_enabled: bool, // Amount of time (in seconds) that passed from creation of the engine. elapsed_time: f32, input_state: InputState, /// A special container that is able to create nodes by their type UUID. Use a copy of this /// value whenever you need it as a parameter in other parts of the engine. pub serialization_context: Arc, /// A container with widget constructors. pub widget_constructors: Arc, /// A container with constructors for dynamic types. See [`DynTypeConstructorContainer`] for more /// info. pub dyn_type_constructors: Arc, /// Script processor is used to run script methods in a strict order. pub script_processor: ScriptProcessor, error_queue: ErrorQueue, } #[derive(Debug, Hash, PartialEq, Eq)] enum MessageTypeId { Static(TypeId), Dynamic(DynamicTypeId), } /// Performs dispatch of script messages. pub struct ScriptMessageDispatcher { type_groups: FxHashMap>>, message_receiver: Receiver, } fn try_enqueue_plugin_error(method_name: &'static str, result: GameResult, queue: &mut ErrorQueue) { if let Err(error) = result { queue.push_back(GameErrorContainer { source: GameErrorSource::PluginMethod(method_name), error, }); } } impl ScriptMessageDispatcher { fn new(message_receiver: Receiver) -> Self { Self { type_groups: Default::default(), message_receiver, } } /// Subscribes a node to receive any message of the given type `T`. Subscription is automatically removed /// if the node dies. pub fn subscribe_to(&mut self, receiver: Handle) { self.subscribe_internal_to(receiver, MessageTypeId::Static(TypeId::of::())); } /// Subscribes a node to receive any message of the given type `type_id`. Subscription is automatically removed /// if the node dies. pub fn subscribe_dynamic_to(&mut self, receiver: Handle, type_id: DynamicTypeId) { self.subscribe_internal_to(receiver, MessageTypeId::Dynamic(type_id)); } #[inline] fn subscribe_internal_to(&mut self, receiver: Handle, type_id: MessageTypeId) { self.type_groups .entry(type_id) .and_modify(|v| { v.insert(receiver); }) .or_insert_with(|| FxHashSet::from_iter([receiver])); } /// Unsubscribes a node from receiving any messages of the given type `T`. pub fn unsubscribe_from(&mut self, receiver: Handle) { self.unsubscribe_internal_from(receiver, &MessageTypeId::Static(TypeId::of::())); } /// Unsubscribes a node from receiving any messages of the given type `type_id`. pub fn unsubscribe_dynamic_from(&mut self, receiver: Handle, type_id: DynamicTypeId) { self.unsubscribe_internal_from(receiver, &MessageTypeId::Dynamic(type_id)); } #[inline] fn unsubscribe_internal_from(&mut self, receiver: Handle, type_id: &MessageTypeId) { if let Some(group) = self.type_groups.get_mut(type_id) { group.remove(&receiver); } } /// Unsubscribes a node from receiving any messages. pub fn unsubscribe(&mut self, receiver: Handle) { for group in self.type_groups.values_mut() { group.remove(&receiver); } } fn dispatch_messages( &self, scene: &mut Scene, scene_handle: Handle, plugins: &mut [PluginContainer], resource_manager: &ResourceManager, dt: f32, elapsed_time: f32, message_sender: &ScriptMessageSender, user_interfaces: &mut UiContainer, graphics_context: &mut GraphicsContext, task_pool: &mut TaskPoolHandler, input_state: &InputState, error_queue: &mut ErrorQueue, ) { while let Ok(message) = self.message_receiver.try_recv() { let type_id = match message.payload.get_dynamic_type_id() { Some(it) => MessageTypeId::Dynamic(it), None => MessageTypeId::Static(message.payload.deref().type_id()), }; let receivers = self.type_groups.get(&type_id); if receivers.is_none_or(|r| r.is_empty()) { Log::warn(format!( "Script message {message:?} was sent, but there's no receivers. \ Did you forgot to subscribe your script to the message?" )); } if let Some(receivers) = receivers { let mut payload = message.payload; match message.kind { ScriptMessageKind::Targeted(target) => { if receivers.contains(&target) { let mut context = ScriptMessageContext { dt, elapsed_time, plugins: PluginsRefMut(plugins), handle: target, scene, scene_handle, resource_manager, message_sender, task_pool, graphics_context, user_interfaces, script_index: 0, input_state, }; process_node_scripts( "on_message", &mut context, scene_handle, error_queue, &mut |s, ctx| s.on_message(&mut *payload, ctx), ) } } ScriptMessageKind::Hierarchical { root, routing } => match routing { RoutingStrategy::Up => { let mut node = root; while let Ok(node_ref) = scene.graph.try_get_node(node) { let parent = node_ref.parent(); let mut context = ScriptMessageContext { dt, elapsed_time, plugins: PluginsRefMut(plugins), handle: node, scene, scene_handle, resource_manager, message_sender, task_pool, graphics_context, user_interfaces, script_index: 0, input_state, }; if receivers.contains(&node) { process_node_scripts( "on_message", &mut context, scene_handle, error_queue, &mut |s, ctx| s.on_message(&mut *payload, ctx), ); } node = parent; } } RoutingStrategy::Down => { for node in scene.graph.traverse_handle_iter(root).collect::>() { let mut context = ScriptMessageContext { dt, elapsed_time, plugins: PluginsRefMut(plugins), handle: node, scene, scene_handle, resource_manager, message_sender, task_pool, graphics_context, user_interfaces, script_index: 0, input_state, }; if receivers.contains(&node) { process_node_scripts( "on_message", &mut context, scene_handle, error_queue, &mut |s, ctx| s.on_message(&mut *payload, ctx), ); } } } }, ScriptMessageKind::Global => { for &node in receivers { let mut context = ScriptMessageContext { dt, elapsed_time, plugins: PluginsRefMut(plugins), handle: node, scene, scene_handle, resource_manager, message_sender, task_pool, graphics_context, user_interfaces, script_index: 0, input_state, }; process_node_scripts( "on_message", &mut context, scene_handle, error_queue, &mut |s, ctx| s.on_message(&mut *payload, ctx), ); } } } } } } } /// Scripted scene is a handle to scene with some additional data associated with it. pub struct ScriptedScene { /// Handle of a scene. pub handle: Handle, /// Script message sender. pub message_sender: ScriptMessageSender, message_dispatcher: ScriptMessageDispatcher, } /// Script processor is used to run script methods in a strict order. #[derive(Default)] pub struct ScriptProcessor { wait_list: Vec, /// A list of scenes. pub scripted_scenes: Vec, } impl ScriptProcessor { fn has_scripted_scene(&self, scene: Handle) -> bool { self.scripted_scenes.iter().any(|s| s.handle == scene) } fn register_scripted_scene( &mut self, scene: Handle, resource_manager: &ResourceManager, ) { // Ensure that the scene wasn't registered previously. assert!(!self.has_scripted_scene(scene)); let (tx, rx) = channel(); self.scripted_scenes.push(ScriptedScene { handle: scene, message_sender: ScriptMessageSender { sender: tx }, message_dispatcher: ScriptMessageDispatcher::new(rx), }); self.wait_list .push(resource_manager.state().get_wait_context()); } fn handle_scripts( &mut self, scenes: &mut SceneContainer, plugins: &mut [PluginContainer], resource_manager: &ResourceManager, task_pool: &mut TaskPoolHandler, graphics_context: &mut GraphicsContext, user_interfaces: &mut UiContainer, dt: f32, elapsed_time: f32, input_state: &InputState, error_queue: &mut ErrorQueue, ) { self.wait_list .retain_mut(|context| !context.is_all_loaded()); if !self.wait_list.is_empty() { return; } self.scripted_scenes .retain(|s| scenes.is_valid_handle(s.handle)); 'scene_loop: for scripted_scene in self.scripted_scenes.iter_mut() { let scene = &mut scenes[scripted_scene.handle]; // Disabled scenes should not update their scripts. if !*scene.enabled { continue 'scene_loop; } // Fill in initial handles to nodes to initialize, start, update. let mut update_queue = VecDeque::new(); let mut start_queue = VecDeque::new(); let script_message_sender = scene.graph.script_message_sender.clone(); for (handle, node) in scene.graph.pair_iter_mut() { // Remove unused script entries. node.scripts .retain(|e| e.script.is_some() && !e.should_be_deleted); if node.is_globally_enabled() { for (i, entry) in node.scripts.iter().enumerate() { if let Some(script) = entry.script.as_ref() { if script.initialized { if script.started { update_queue.push_back((handle, i)); } else { start_queue.push_back((handle, i)); } } else { script_message_sender .send(NodeScriptMessage::InitializeScript { handle, script_index: i, }) .unwrap(); } } } } } // We'll gather all scripts queued for destruction and destroy them all at once at the // end of the frame. let mut destruction_queue = VecDeque::new(); let max_iterations = 64; 'update_loop: for update_loop_iteration in 0..max_iterations { let mut context = ScriptContext { dt, elapsed_time, plugins: PluginsRefMut(plugins), handle: Default::default(), scene, scene_handle: scripted_scene.handle, resource_manager, message_sender: &scripted_scene.message_sender, message_dispatcher: &mut scripted_scene.message_dispatcher, task_pool, graphics_context, user_interfaces, script_index: 0, input_state, }; 'init_loop: for init_loop_iteration in 0..max_iterations { // Process events first. `on_init` of a script can also create some other instances // and these will be correctly initialized on current frame. while let Ok(event) = context.scene.graph.script_message_receiver.try_recv() { match event { NodeScriptMessage::InitializeScript { handle, script_index, } => { context.handle = handle; context.script_index = script_index; process_node_script( "on_init", script_index, scripted_scene.handle, &mut context, error_queue, &mut |script, context| { if !script.initialized { script.on_init(context)?; script.initialized = true; } // `on_start` must be called even if the script was initialized. start_queue.push_back((handle, script_index)); Ok(()) }, ); } NodeScriptMessage::DestroyScript { handle, script, script_index, } => { // Destruction is delayed to the end of the frame. destruction_queue.push_back((handle, script, script_index)); } } } if start_queue.is_empty() { // There is no more new nodes, we can safely leave the init loop. break 'init_loop; } else { // Call `on_start` for every recently initialized node and go to next // iteration of init loop. This is needed because `on_start` can spawn // some other nodes that must be initialized before update. while let Some((handle, script_index)) = start_queue.pop_front() { context.handle = handle; context.script_index = script_index; process_node_script( "on_start", script_index, scripted_scene.handle, &mut context, error_queue, &mut |script, context| { if script.initialized && !script.started { let result = script.on_start(context); script.started = true; update_queue.push_back((handle, script_index)); result } else { Ok(()) } }, ); } } if init_loop_iteration == max_iterations - 1 { Log::warn( "Infinite init loop detected! Most likely some of \ your scripts causing infinite prefab instantiation!", ) } } // Update all initialized and started scripts until there is something to initialize. if update_queue.is_empty() { break 'update_loop; } else { while let Some((handle, script_index)) = update_queue.pop_front() { context.handle = handle; context.script_index = script_index; process_node_script( "on_update", script_index, scripted_scene.handle, &mut context, error_queue, &mut |script, context| script.on_update(context), ); } } if update_loop_iteration == max_iterations - 1 { Log::warn( "Infinite update loop detected! Most likely some of \ your scripts causing infinite prefab instantiation!", ) } } // Dispatch script messages only when everything is initialized and updated. This has to // be done this way, because all those methods could spawn new messages. However, if a new // message is spawned directly in `on_message` the dispatcher will correctly handle it // on this frame, since it will be placed in the common queue anyway. scripted_scene.message_dispatcher.dispatch_messages( scene, scripted_scene.handle, plugins, resource_manager, dt, elapsed_time, &scripted_scene.message_sender, user_interfaces, graphics_context, task_pool, input_state, error_queue, ); // As the last step, destroy queued scripts. let mut context = ScriptDeinitContext { elapsed_time, plugins: PluginsRefMut(plugins), resource_manager, scene, scene_handle: scripted_scene.handle, node_handle: Default::default(), message_sender: &scripted_scene.message_sender, user_interfaces, graphics_context, task_pool, script_index: 0, input_state, }; while let Some((handle, mut script, index)) = destruction_queue.pop_front() { context.node_handle = handle; context.script_index = index; // Unregister self in message dispatcher. scripted_scene.message_dispatcher.unsubscribe(handle); // `on_deinit` could also spawn new nodes, but we won't take those into account on // this frame. They'll be correctly handled on next frame. if let Err(error) = script.on_deinit(&mut context) { error_queue.push_back(GameErrorContainer { source: GameErrorSource::ScriptMethod { scene_handle: scripted_scene.handle, node_handle: handle, method_name: "on_deinit", }, error, }); } } } // Process scripts from destroyed scenes. for (handle, mut detached_scene) in scenes.destruction_list.drain(..) { if let Some(scripted_scene) = self.scripted_scenes.iter().find(|s| s.handle == handle) { let mut context = ScriptDeinitContext { elapsed_time, plugins: PluginsRefMut(plugins), resource_manager, scene: &mut detached_scene, scene_handle: scripted_scene.handle, node_handle: Default::default(), message_sender: &scripted_scene.message_sender, task_pool, graphics_context, user_interfaces, script_index: 0, input_state, }; // Destroy every script instance from nodes that were still alive. for node_index in 0..context.scene.graph.capacity() { let handle_node = context.scene.graph.handle_from_index(node_index); context.node_handle = handle_node; process_node_scripts( "on_deinit", &mut context, scripted_scene.handle, error_queue, &mut |script, context| { if script.initialized { script.on_deinit(context) } else { Ok(()) } }, ); } } } } } struct ResourceGraphVertex { resource: ModelResource, children: Vec, } impl ResourceGraphVertex { pub fn new(model: ModelResource, resource_manager: ResourceManager) -> Self { let mut children = Vec::new(); // Look for dependent resources. let mut dependent_resources = HashSet::new(); for resource in resource_manager.state().iter() { if let Some(other_model) = resource.try_cast::() { let mut state = other_model.state(); if let Some(model_data) = state.data() { if model_data .get_scene() .graph .linear_iter() .any(|n| n.resource.as_ref() == Some(&model)) { dependent_resources.insert(other_model.clone()); } } } } children.extend( dependent_resources .into_iter() .map(|r| ResourceGraphVertex::new(r, resource_manager.clone())), ); Self { resource: model, children, } } pub fn resolve(&self) { Log::info(format!( "Resolving {} resource from dependency graph...", self.resource.resource_uuid() )); // Wait until resource is fully loaded, then resolve. if block_on(self.resource.clone()).is_ok() { self.resource.data_ref().get_scene_mut(); for child in self.children.iter() { child.resolve(); } } } } struct ResourceDependencyGraph { root: ResourceGraphVertex, } impl ResourceDependencyGraph { pub fn new(model: ModelResource, resource_manager: ResourceManager) -> Self { Self { root: ResourceGraphVertex::new(model, resource_manager), } } pub fn resolve(&self) { self.root.resolve() } } /// A result returned by a graphics server constructor. pub type GraphicsServerConstructorResult = Result<(Window, SharedGraphicsServer), FrameworkError>; /// Graphics server constructor callback responsible for actual server creation. Graphics server /// initialization usually tied together with window creation on some operating systems, that's why /// the constructor accepts window builder and must return the window built with the builder as well /// as the server. pub type GraphicsServerConstructorCallback = dyn Fn( &GraphicsContextParams, &ActiveEventLoop, WindowAttributes, bool, ) -> GraphicsServerConstructorResult; /// Graphics server constructor is used to initialize different graphics servers in unified manner. /// [`Default`] trait implementation currently creates OpenGL graphics server. #[derive(Clone)] pub struct GraphicsServerConstructor(Rc); impl Default for GraphicsServerConstructor { fn default() -> Self { Self(Rc::new( |params, window_target, window_builder, named_objects| { GlGraphicsServer::new( params.vsync, params.msaa_sample_count, window_target, window_builder, named_objects, ) }, )) } } /// A set of parameters that could be used to initialize graphics context. #[derive(Clone)] pub struct GraphicsContextParams { /// Attributes of the main application window. pub window_attributes: WindowAttributes, /// Whether to use vertical synchronization or not. V-sync will force your game to render frames with the synchronization /// rate of your monitor (which is ~60 FPS). Keep in mind that vertical synchronization might not be available on your OS. pub vsync: bool, /// Amount of samples for MSAA. Must be a power of two (1, 2, 4, 8). `None` means disabled. /// MSAA works only for forward rendering and does not work for deferred rendering. pub msaa_sample_count: Option, /// Graphic server constructor. See [`GraphicsServerConstructor`] docs for more info. pub graphics_server_constructor: GraphicsServerConstructor, /// A flag, that if raised tells the engine to assign meaningful names for GPU objects. This /// option is very useful for debugging. This option is off by default, because if may cause /// crashes on some video driver due to poor implementation in the driver. pub named_objects: bool, } impl Default for GraphicsContextParams { fn default() -> Self { Self { window_attributes: Default::default(), vsync: true, msaa_sample_count: None, graphics_server_constructor: Default::default(), named_objects: false, } } } /// Engine initialization parameters. pub struct EngineInitParams { /// A set of parameters for graphics context initialization. Keep in mind that the engine **will not** initialize /// graphics context for you. Instead, you need to call [`Engine::initialize_graphics_context`] on [`Event::Resumed`] /// event and [`Engine::destroy_graphics_context`] on [`Event::Suspended`] event. If you don't need a graphics context /// (for example for game servers), then you can pass [`Default::default`] here and do not call any methods. pub graphics_context_params: GraphicsContextParams, /// A special container that is able to create nodes by their type UUID. pub serialization_context: Arc, /// A container with widget constructors. pub widget_constructors: Arc, /// A container for dynamic types. See [`DynTypeConstructorContainer`] docs for more info. pub dyn_type_constructors: Arc, /// A resource manager. pub resource_manager: ResourceManager, /// Task pool for asynchronous task management. pub task_pool: Arc, } fn process_node_script( caller_name: &'static str, index: usize, scene_handle: Handle, context: &mut C, error_queue: &mut ErrorQueue, func: &mut T, ) -> bool where T: FnMut(&mut Script, &mut C) -> GameResult, C: UniversalScriptContext, { let Ok(node) = context.node() else { // A node was destroyed. return false; }; if !node.is_globally_enabled() { return false; } let Some(entry) = node.scripts.get_mut(index) else { // All scripts were visited. return false; }; let Some(mut script) = entry.take() else { return false; }; if let Err(error) = func(&mut script, context) { let node = context.node(); let handle = node.map_or(Handle::NONE, |n| n.handle()); error_queue.push_back(GameErrorContainer { source: GameErrorSource::ScriptMethod { scene_handle, node_handle: handle, method_name: caller_name, }, error, }); } match context.node() { Ok(node) => { let entry = node .scripts .get_mut(index) .expect("Scripts array cannot be modified!"); if entry.should_be_deleted { context.destroy_script_deferred(script, index); } else { // Put the script back at its place. entry.script = Some(script); } } Err(_) => { // If the node was deleted by the `func` call, we must send the script to destruction // queue, not silently drop it. context.destroy_script_deferred(script, index); } } true } fn process_node_scripts( caller_name: &'static str, context: &mut C, scene_handle: Handle, error_queue: &mut ErrorQueue, func: &mut T, ) where T: FnMut(&mut Script, &mut C) -> GameResult, C: UniversalScriptContext, { let mut index = 0; loop { context.set_script_index(index); if !process_node_script(caller_name, index, scene_handle, context, error_queue, func) { return; } // Advance to the next script. index += 1; } } pub(crate) fn process_scripts( caller_name: &'static str, scene: &mut Scene, scene_handle: Handle, plugins: &mut [PluginContainer], resource_manager: &ResourceManager, message_sender: &ScriptMessageSender, message_dispatcher: &mut ScriptMessageDispatcher, task_pool: &mut TaskPoolHandler, graphics_context: &mut GraphicsContext, user_interfaces: &mut UiContainer, dt: f32, elapsed_time: f32, input_state: &InputState, error_queue: &mut ErrorQueue, mut func: T, ) where T: FnMut(&mut Script, &mut ScriptContext) -> GameResult, { let mut context = ScriptContext { dt, elapsed_time, plugins: PluginsRefMut(plugins), handle: Default::default(), scene, scene_handle, resource_manager, message_sender, message_dispatcher, task_pool, graphics_context, user_interfaces, script_index: 0, input_state, }; for node_index in 0..context.scene.graph.capacity() { context.handle = context.scene.graph.handle_from_index(node_index); process_node_scripts( caller_name, &mut context, scene_handle, error_queue, &mut func, ); } } pub(crate) fn initialize_resource_manager_loaders( resource_manager: &ResourceManager, serialization_context: Arc, widget_constructors: Arc, dyn_type_constructors: Arc, ) { let model_loader = ModelLoader { resource_manager: resource_manager.clone(), serialization_context, dyn_type_constructors: dyn_type_constructors.clone(), default_import_options: Default::default(), }; let mut state = resource_manager.state(); for shader in ShaderResource::standard_shaders() { state.built_in_resources.add((*shader).clone()); } state.built_in_resources.add(GLTF_SHADER.clone()); for texture in SkyBoxKind::built_in_skybox_textures() { state.built_in_resources.add(texture.clone()); } state.built_in_resources.add(BUILT_IN_FONT.clone()); state.built_in_resources.add(BUILT_IN_BOLD.clone()); state.built_in_resources.add(BUILT_IN_ITALIC.clone()); state.built_in_resources.add(BOLD_ITALIC.clone()); state.built_in_resources.add(texture::PLACEHOLDER.clone()); state.built_in_resources.add(texture::PURE_COLOR.clone()); state.built_in_resources.add(style::DEFAULT_STYLE.clone()); state.built_in_resources.add(style::LIGHT_STYLE.clone()); for material in [ &*material::STANDARD, &*material::STANDARD_2D, &*material::STANDARD_SPRITE, &*material::STANDARD_TERRAIN, &*material::STANDARD_TWOSIDES, &*material::STANDARD_PARTICLE_SYSTEM, &*material::STANDARD_WIDGET, ] { state.built_in_resources.add(material.clone()); } for surface in [ &*surface::CUBE, &*surface::QUAD, &*surface::CYLINDER, &*surface::SPHERE, &*surface::CONE, &*surface::TORUS, ] { state.built_in_resources.add(surface.clone()); } state.constructors_container.add::(); state.constructors_container.add::(); state.constructors_container.add::(); state.constructors_container.add::(); state.constructors_container.add::(); state.constructors_container.add::(); state.constructors_container.add::(); state.constructors_container.add::(); state.constructors_container.add::(); state.constructors_container.add::(); state.constructors_container.add::(); state.constructors_container.add::(); state.constructors_container.add::(); state.constructors_container.add::(); state.constructors_container.add::(); state.constructors_container.add::