1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
//! Everything related to plugins. See [`Plugin`] docs for more info.
#![warn(missing_docs)]
pub mod dynamic;
use crate::{
asset::manager::ResourceManager,
core::{
notify::RecommendedWatcher, pool::Handle, reflect::Reflect, visitor::Visit,
visitor::VisitError,
},
engine::{
task::TaskPoolHandler, AsyncSceneLoader, GraphicsContext, PerformanceStatistics,
ScriptProcessor, SerializationContext,
},
event::Event,
gui::{
constructor::WidgetConstructorContainer,
inspector::editors::PropertyEditorDefinitionContainer, message::UiMessage, UiContainer,
},
plugin::dynamic::DynamicPlugin,
scene::{Scene, SceneContainer},
};
use std::{
any::Any,
ops::{Deref, DerefMut},
path::{Path, PathBuf},
sync::{atomic::AtomicBool, Arc},
};
use winit::event_loop::EventLoopWindowTarget;
/// Actual state of a dynamic plugin.
pub enum DynamicPluginState {
/// Unloaded plugin.
Unloaded {
/// Serialized content of the plugin.
binary_blob: Vec<u8>,
},
/// Loaded plugin.
Loaded(DynamicPlugin),
}
impl DynamicPluginState {
/// Tries to interpret the state as [`Self::Loaded`], panics if the plugin is unloaded.
pub fn as_loaded_ref(&self) -> &DynamicPlugin {
match self {
DynamicPluginState::Unloaded { .. } => {
panic!("Cannot obtain a reference to the plugin, because it is unloaded!")
}
DynamicPluginState::Loaded(dynamic) => dynamic,
}
}
/// Tries to interpret the state as [`Self::Loaded`], panics if the plugin is unloaded.
pub fn as_loaded_mut(&mut self) -> &mut DynamicPlugin {
match self {
DynamicPluginState::Unloaded { .. } => {
panic!("Cannot obtain a reference to the plugin, because it is unloaded!")
}
DynamicPluginState::Loaded(dynamic) => dynamic,
}
}
}
/// A wrapper for various plugin types.
pub enum PluginContainer {
/// Statically linked plugin. Such plugins are meant to be used in final builds, to maximize
/// performance of the game.
Static(Box<dyn Plugin>),
/// Dynamically linked plugin. Such plugins are meant to be used in development mode for rapid
/// prototyping.
Dynamic {
/// Dynamic plugin state.
state: DynamicPluginState,
/// Target path of the library of the plugin.
lib_path: PathBuf,
/// Path to the source file, that is emitted by the compiler. If hot reloading is enabled,
/// this library will be cloned to `lib_path` and loaded. This is needed, because usually
/// OS locks the library and it is not possible to overwrite it while it is loaded in a process.
source_lib_path: PathBuf,
/// Optional file system watcher, that is configured to watch the source library and re-load
/// the plugin if the source library has changed. If the watcher is `None`, then hot reloading
/// is disabled.
watcher: Option<RecommendedWatcher>,
/// A flag, that tells the engine that the plugin needs to be reloaded. Usually the engine
/// will do that at the end of the update tick.
need_reload: Arc<AtomicBool>,
},
}
impl Deref for PluginContainer {
type Target = dyn Plugin;
fn deref(&self) -> &Self::Target {
match self {
PluginContainer::Static(plugin) => &**plugin,
PluginContainer::Dynamic { state: plugin, .. } => &*plugin.as_loaded_ref().plugin,
}
}
}
impl DerefMut for PluginContainer {
fn deref_mut(&mut self) -> &mut Self::Target {
match self {
PluginContainer::Static(plugin) => &mut **plugin,
PluginContainer::Dynamic { state: plugin, .. } => &mut *plugin.as_loaded_mut().plugin,
}
}
}
/// Contains plugin environment for the registration stage.
pub struct PluginRegistrationContext<'a> {
/// A reference to serialization context of the engine. See [`SerializationContext`] for more
/// info.
pub serialization_context: &'a Arc<SerializationContext>,
/// A reference to serialization context of the engine. See [`WidgetConstructorContainer`] for more
/// info.
pub widget_constructors: &'a Arc<WidgetConstructorContainer>,
/// A reference to the resource manager instance of the engine. Could be used to register resource loaders.
pub resource_manager: &'a ResourceManager,
}
/// Contains plugin environment.
pub struct PluginContext<'a, 'b> {
/// A reference to scene container of the engine. You can add new scenes from [`Plugin`] methods
/// by using [`SceneContainer::add`].
pub scenes: &'a mut SceneContainer,
/// A reference to the resource manager, it can be used to load various resources and manage
/// them. See [`ResourceManager`] docs for more info.
pub resource_manager: &'a ResourceManager,
/// A reference to user interface container of the engine. The engine guarantees that there's
/// at least one user interface exists. Use `context.user_interfaces.first()/first_mut()` to
/// get a reference to it.
pub user_interfaces: &'a mut UiContainer,
/// A reference to the graphics_context, it contains a reference to the window and the current renderer.
/// It could be [`GraphicsContext::Uninitialized`] if your application is suspended (possible only on
/// Android; it is safe to call [`GraphicsContext::as_initialized_ref`] or [`GraphicsContext::as_initialized_mut`]
/// on every other platform).
pub graphics_context: &'a mut GraphicsContext,
/// The time (in seconds) that passed since last call of a method in which the context was
/// passed. It has fixed value that is defined by a caller (in most cases it is `Executor`).
pub dt: f32,
/// A reference to time accumulator, that holds remaining amount of time that should be used
/// to update a plugin. A caller splits `lag` into multiple sub-steps using `dt` and thus
/// stabilizes update rate. The main use of this variable, is to be able to reset `lag` when
/// you doing some heavy calculations in a your game loop (i.e. loading a new level) so the
/// engine won't try to "catch up" with all the time that was spent in heavy calculation.
pub lag: &'b mut f32,
/// A reference to serialization context of the engine. See [`SerializationContext`] for more
/// info.
pub serialization_context: &'a Arc<SerializationContext>,
/// A reference to serialization context of the engine. See [`WidgetConstructorContainer`] for more
/// info.
pub widget_constructors: &'a Arc<WidgetConstructorContainer>,
/// Performance statistics from the last frame.
pub performance_statistics: &'a PerformanceStatistics,
/// Amount of time (in seconds) that passed from creation of the engine. Keep in mind, that
/// this value is **not** guaranteed to match real time. A user can change delta time with
/// which the engine "ticks" and this delta time affects elapsed time.
pub elapsed_time: f32,
/// Script processor is used to run script methods in a strict order.
pub script_processor: &'a ScriptProcessor,
/// Asynchronous scene loader. It is used to request scene loading. See [`AsyncSceneLoader`] docs
/// for usage example.
pub async_scene_loader: &'a mut AsyncSceneLoader,
/// Special field that associates main application event loop (not game loop) with OS-specific
/// windows. It also can be used to alternate control flow of the application.
pub window_target: Option<&'b EventLoopWindowTarget<()>>,
/// Task pool for asynchronous task management.
pub task_pool: &'a mut TaskPoolHandler,
}
/// Base plugin automatically implements type casting for plugins.
pub trait BasePlugin: Any + 'static {
/// Returns a reference to Any trait. It is used for type casting.
fn as_any(&self) -> &dyn Any;
/// Returns a reference to Any trait. It is used for type casting.
fn as_any_mut(&mut self) -> &mut dyn Any;
}
impl<T> BasePlugin for T
where
T: Any + Plugin + 'static,
{
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl dyn Plugin {
/// Performs downcasting to a particular type.
pub fn cast<T: Plugin>(&self) -> Option<&T> {
BasePlugin::as_any(self).downcast_ref::<T>()
}
/// Performs downcasting to a particular type.
pub fn cast_mut<T: Plugin>(&mut self) -> Option<&mut T> {
BasePlugin::as_any_mut(self).downcast_mut::<T>()
}
}
/// Plugin is a convenient interface that allow you to extend engine's functionality.
///
/// # Static vs dynamic plugins
///
/// Every plugin must be linked statically to ensure that everything is memory safe. There was some
/// long research about hot reloading and dynamic plugins (in DLLs) and it turned out that they're
/// not guaranteed to be memory safe because Rust does not have stable ABI. When a plugin compiled
/// into DLL, Rust compiler is free to reorder struct members in any way it needs to. It is not
/// guaranteed that two projects that uses the same library will have compatible ABI. This fact
/// indicates that you either have to use static linking of your plugins or provide C interface
/// to every part of the engine and "communicate" with plugin using C interface with C ABI (which
/// is standardized and guaranteed to be compatible). The main problem with C interface is
/// boilerplate code and the need to mark every structure "visible" through C interface with
/// `#[repr(C)]` attribute which is not always easy and even possible (because some structures could
/// be re-exported from dependencies). These are the main reasons why the engine uses static plugins.
///
/// # Example
///
/// ```rust
/// # use fyrox_impl::{
/// # core::{pool::Handle}, core::visitor::prelude::*, core::reflect::prelude::*,
/// # plugin::{Plugin, PluginContext, PluginRegistrationContext},
/// # scene::Scene,
/// # event::Event
/// # };
/// # use std::str::FromStr;
///
/// #[derive(Default, Visit, Reflect, Debug)]
/// struct MyPlugin {}
///
/// impl Plugin for MyPlugin {
/// fn on_deinit(&mut self, context: PluginContext) {
/// // The method is called when the plugin is disabling.
/// // The implementation is optional.
/// }
///
/// fn update(&mut self, context: &mut PluginContext) {
/// // The method is called on every frame, it is guaranteed to have fixed update rate.
/// // The implementation is optional.
/// }
///
/// fn on_os_event(&mut self, event: &Event<()>, context: PluginContext) {
/// // The method is called when the main window receives an event from the OS.
/// }
/// }
/// ```
pub trait Plugin: BasePlugin + Visit + Reflect {
/// The method is called when the plugin constructor was just registered in the engine. The main
/// use of this method is to register scripts and custom scene graph nodes in [`SerializationContext`].
fn register(&self, #[allow(unused_variables)] context: PluginRegistrationContext) {}
/// This method is used to register property editors for your game types; to make them editable
/// in the editor.
fn register_property_editors(&self) -> PropertyEditorDefinitionContainer {
PropertyEditorDefinitionContainer::empty()
}
/// This method is used to initialize your plugin.
fn init(
&mut self,
#[allow(unused_variables)] scene_path: Option<&str>,
#[allow(unused_variables)] context: PluginContext,
) {
}
/// This method is called when your plugin was re-loaded from a dynamic library. It could be used
/// to restore some runtime state, that cannot be serialized. This method is called **only for
/// dynamic plugins!** It is guaranteed to be called after all plugins were constructed, so the
/// cross-plugins interactions are possible.
fn on_loaded(&mut self, #[allow(unused_variables)] context: PluginContext) {}
/// The method is called before plugin will be disabled. It should be used for clean up, or some
/// additional actions.
fn on_deinit(&mut self, #[allow(unused_variables)] context: PluginContext) {}
/// Updates the plugin internals at fixed rate (see [`PluginContext::dt`] parameter for more
/// info).
fn update(&mut self, #[allow(unused_variables)] context: &mut PluginContext) {}
/// The method is called when the main window receives an event from the OS. The main use of
/// the method is to respond to some external events, for example an event from keyboard or
/// gamepad. See [`Event`] docs for more info.
fn on_os_event(
&mut self,
#[allow(unused_variables)] event: &Event<()>,
#[allow(unused_variables)] context: PluginContext,
) {
}
/// The method is called when a graphics context was successfully created. It could be useful
/// to catch the moment when it was just created and do something in response.
fn on_graphics_context_initialized(
&mut self,
#[allow(unused_variables)] context: PluginContext,
) {
}
/// The method is called before the actual frame rendering. It could be useful to render off-screen
/// data (render something to texture, that can be used later in the main frame).
fn before_rendering(&mut self, #[allow(unused_variables)] context: PluginContext) {}
/// The method is called when the current graphics context was destroyed.
fn on_graphics_context_destroyed(&mut self, #[allow(unused_variables)] context: PluginContext) {
}
/// The method will be called when there is any message from main user interface instance
/// of the engine.
fn on_ui_message(
&mut self,
#[allow(unused_variables)] context: &mut PluginContext,
#[allow(unused_variables)] message: &UiMessage,
) {
}
/// This method is called when the engine starts loading a scene from the given `path`. It could
/// be used to "catch" the moment when the scene is about to be loaded; to show a progress bar
/// for example. See [`AsyncSceneLoader`] docs for usage example.
fn on_scene_begin_loading(
&mut self,
#[allow(unused_variables)] path: &Path,
#[allow(unused_variables)] context: &mut PluginContext,
) {
}
/// This method is called when the engine finishes loading a scene from the given `path`. Use
/// this method if you need do something with a newly loaded scene. See [`AsyncSceneLoader`] docs
/// for usage example.
fn on_scene_loaded(
&mut self,
#[allow(unused_variables)] path: &Path,
#[allow(unused_variables)] scene: Handle<Scene>,
#[allow(unused_variables)] data: &[u8],
#[allow(unused_variables)] context: &mut PluginContext,
) {
}
/// This method is called when the engine finishes loading a scene from the given `path` with
/// some error. This method could be used to report any issues to a user.
fn on_scene_loading_failed(
&mut self,
#[allow(unused_variables)] path: &Path,
#[allow(unused_variables)] error: &VisitError,
#[allow(unused_variables)] context: &mut PluginContext,
) {
}
}