Skip to main content

fyrox_impl/plugin/
mod.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Everything related to plugins. See [`Plugin`] docs for more info.
22
23#![warn(missing_docs)]
24
25pub mod dylib;
26pub mod error;
27
28use crate::plugin::error::GameError;
29use crate::{
30    asset::manager::ResourceManager,
31    core::{
32        define_as_any_trait, pool::Handle, reflect::Reflect, visitor::error::VisitError,
33        visitor::Visit,
34    },
35    engine::{
36        input::InputState, task::TaskPoolHandler, ApplicationLoopController, AsyncSceneLoader,
37        GraphicsContext, PerformanceStatistics, ScriptProcessor, SerializationContext,
38    },
39    event::Event,
40    gui::{
41        constructor::WidgetConstructorContainer,
42        inspector::editors::PropertyEditorDefinitionContainer, message::UiMessage, UiContainer,
43        UserInterface,
44    },
45    plugin::error::GameResult,
46    scene::{Scene, SceneContainer},
47};
48use fyrox_core::dyntype::DynTypeConstructorContainer;
49use std::{
50    ops::{Deref, DerefMut},
51    path::Path,
52    sync::Arc,
53};
54
55/// A wrapper for various plugin types.
56pub enum PluginContainer {
57    /// Statically linked plugin. Such plugins are meant to be used in final builds, to maximize
58    /// performance of the game.
59    Static(Box<dyn Plugin>),
60    /// Dynamically linked plugin. Such plugins are meant to be used in development mode for rapid
61    /// prototyping.
62    Dynamic(Box<dyn DynamicPlugin>),
63}
64
65/// Abstraction over different kind of plugins that can be reloaded on the fly (whatever it mean).
66/// The instance is polled by engine with `is_reload_needed_now()` time to time. if it returns true,
67/// then engine serializes current plugin state, then calls `unload()` and then calls `load()`
68pub trait DynamicPlugin {
69    /// returns human-redable short description of the plugin
70    fn display_name(&self) -> String;
71
72    /// engine polls is time to time to determine if it's time to reload plugin
73    fn is_reload_needed_now(&self) -> bool;
74
75    /// panics if not loaded
76    fn as_loaded_ref(&self) -> &dyn Plugin;
77
78    /// panics if not loaded
79    fn as_loaded_mut(&mut self) -> &mut dyn Plugin;
80
81    /// returns false if something bad happends during `reload`.
82    /// has no much use except prevention of error spamming
83    fn is_loaded(&self) -> bool;
84
85    /// called before saving state and detaching related objects
86    fn prepare_to_reload(&mut self) {}
87
88    /// called after plugin-related objects are detached
89    /// `fill_and_register` callback exposes plugin instance to engine to register constructors and restore the state
90    /// callback approach allows plugins to do some necessary actions right after plugin is registed
91    fn reload(
92        &mut self,
93        fill_and_register: &mut dyn FnMut(&mut dyn Plugin) -> Result<(), String>,
94    ) -> Result<(), String>;
95}
96
97impl Deref for PluginContainer {
98    type Target = dyn Plugin;
99
100    fn deref(&self) -> &Self::Target {
101        match self {
102            PluginContainer::Static(plugin) => &**plugin,
103            PluginContainer::Dynamic(plugin) => plugin.as_loaded_ref(),
104        }
105    }
106}
107
108impl DerefMut for PluginContainer {
109    fn deref_mut(&mut self) -> &mut Self::Target {
110        match self {
111            PluginContainer::Static(plugin) => &mut **plugin,
112            PluginContainer::Dynamic(plugin) => plugin.as_loaded_mut(),
113        }
114    }
115}
116
117/// Contains plugin environment for the registration stage.
118pub struct PluginRegistrationContext<'a> {
119    /// A reference to serialization context of the engine. See [`SerializationContext`] for more
120    /// info.
121    pub serialization_context: &'a Arc<SerializationContext>,
122    /// A reference to serialization context of the engine. See [`WidgetConstructorContainer`] for more
123    /// info.
124    pub widget_constructors: &'a Arc<WidgetConstructorContainer>,
125    /// A container with constructors for dynamic types. See [`DynTypeConstructorContainer`] for more
126    /// info.
127    pub dyn_type_constructors: &'a Arc<DynTypeConstructorContainer>,
128    /// A reference to the resource manager instance of the engine. Could be used to register resource loaders.
129    pub resource_manager: &'a ResourceManager,
130}
131
132/// Contains plugin environment.
133pub struct PluginContext<'a, 'b> {
134    /// A reference to scene container of the engine. You can add new scenes from [`Plugin`] methods
135    /// by using [`SceneContainer::add`].
136    pub scenes: &'a mut SceneContainer,
137
138    /// A reference to the resource manager, it can be used to load various resources and manage
139    /// them. See [`ResourceManager`] docs for more info.
140    pub resource_manager: &'a ResourceManager,
141
142    /// A reference to user interface container of the engine. The engine guarantees that there's
143    /// at least one user interface exists. Use `context.user_interfaces.first()/first_mut()` to
144    /// get a reference to it.
145    pub user_interfaces: &'a mut UiContainer,
146
147    /// A reference to the graphics_context, it contains a reference to the window and the current renderer.
148    /// It could be [`GraphicsContext::Uninitialized`] if your application is suspended (possible only on
149    /// Android; it is safe to call [`GraphicsContext::as_initialized_ref`] or [`GraphicsContext::as_initialized_mut`]
150    /// on every other platform).
151    pub graphics_context: &'a mut GraphicsContext,
152
153    /// The time (in seconds) that passed since last call of a method in which the context was
154    /// passed. It has fixed value that is defined by a caller (in most cases it is `Executor`).
155    pub dt: f32,
156
157    /// A reference to time accumulator, that holds remaining amount of time that should be used
158    /// to update a plugin. A caller splits `lag` into multiple sub-steps using `dt` and thus
159    /// stabilizes update rate. The main use of this variable, is to be able to reset `lag` when
160    /// you doing some heavy calculations in a your game loop (i.e. loading a new level) so the
161    /// engine won't try to "catch up" with all the time that was spent in heavy calculation.
162    pub lag: &'b mut f32,
163
164    /// A reference to serialization context of the engine. See [`SerializationContext`] for more
165    /// info.
166    pub serialization_context: &'a Arc<SerializationContext>,
167
168    /// A reference to serialization context of the engine. See [`WidgetConstructorContainer`] for more
169    /// info.
170    pub widget_constructors: &'a Arc<WidgetConstructorContainer>,
171
172    /// A container with constructors for dynamic types. See [`DynTypeConstructorContainer`] for more
173    /// info.
174    pub dyn_type_constructors: &'a Arc<DynTypeConstructorContainer>,
175
176    /// Performance statistics from the last frame.
177    pub performance_statistics: &'a PerformanceStatistics,
178
179    /// Amount of time (in seconds) that passed from creation of the engine. Keep in mind, that
180    /// this value is **not** guaranteed to match real time. A user can change delta time with
181    /// which the engine "ticks" and this delta time affects elapsed time.
182    pub elapsed_time: f32,
183
184    /// Script processor is used to run script methods in a strict order.
185    pub script_processor: &'a ScriptProcessor,
186
187    /// Asynchronous scene loader. It is used to request scene loading. See [`AsyncSceneLoader`] docs
188    /// for usage example.
189    pub async_scene_loader: &'a mut AsyncSceneLoader,
190
191    /// Special field that associates the main application event loop (not game loop) with OS-specific
192    /// windows. It also can be used to alternate control flow of the application. `None` if the
193    /// engine is running in headless mode.
194    pub loop_controller: ApplicationLoopController<'b>,
195
196    /// Task pool for asynchronous task management.
197    pub task_pool: &'a mut TaskPoolHandler,
198
199    /// A stored state of most common input events. It is used a "shortcut" in cases where event-based
200    /// approach is too verbose. It may be useful in simple scenarios where you just need to know
201    /// if a button (on keyboard, mouse) was pressed and do something.
202    ///
203    /// **Important:** this structure does not track from which device the corresponding event has
204    /// come from, if you have more than one keyboard and/or mouse, use event-based approach instead!
205    pub input_state: &'a InputState,
206}
207
208impl<'a, 'b> PluginContext<'a, 'b> {
209    /// Spawns an asynchronous task that tries to load a user interface from the given path.
210    /// When the task is completed, the specified callback is called that can be used to
211    /// modify the UI. The loaded UI must be registered in the engine, otherwise it will be
212    /// discarded.
213    ///
214    /// ## Example
215    ///
216    /// ```rust
217    /// # use fyrox_impl::{
218    /// #     core::{pool::Handle, reflect::prelude::*, visitor::prelude::*},
219    /// #     event::Event,
220    /// #     plugin::{error::GameResult, Plugin, PluginContext, PluginRegistrationContext},
221    /// #     scene::Scene,
222    /// # };
223    /// # use std::str::FromStr;
224    ///
225    /// #[derive(Default, Visit, Reflect, Debug)]
226    /// #[reflect(non_cloneable)]
227    /// struct MyGame {}
228    ///
229    /// impl Plugin for MyGame {
230    ///     fn init(&mut self, _scene_path: Option<&str>, mut ctx: PluginContext) -> GameResult {
231    ///         ctx.load_ui("data/my.ui", |result, game: &mut MyGame, mut ctx| {
232    ///             // The loaded UI must be registered in the engine.
233    ///             *ctx.user_interfaces.first_mut() = result?;
234    ///             Ok(())
235    ///         });
236    ///         Ok(())
237    ///     }
238    /// }
239    /// ```
240    pub fn load_ui<U, P, C>(&mut self, path: U, callback: C)
241    where
242        U: AsRef<Path> + Send + 'static,
243        P: Plugin,
244        for<'c, 'd> C: Fn(Result<UserInterface, VisitError>, &mut P, &mut PluginContext<'c, 'd>) -> GameResult
245            + 'static,
246    {
247        self.task_pool.spawn_plugin_task(
248            UserInterface::load_from_file(
249                path,
250                self.widget_constructors.clone(),
251                self.dyn_type_constructors.clone(),
252                self.resource_manager.clone(),
253            ),
254            callback,
255        );
256    }
257}
258
259define_as_any_trait!(PluginAsAny => Plugin);
260
261impl dyn Plugin {
262    /// Performs downcasting to a particular type.
263    pub fn cast<T: Plugin>(&self) -> Option<&T> {
264        PluginAsAny::as_any(self).downcast_ref::<T>()
265    }
266
267    /// Performs downcasting to a particular type.
268    pub fn cast_mut<T: Plugin>(&mut self) -> Option<&mut T> {
269        PluginAsAny::as_any_mut(self).downcast_mut::<T>()
270    }
271}
272
273/// Plugin is a convenient interface that allow you to extend engine's functionality.
274///
275/// # Example
276///
277/// ```rust
278/// # use fyrox_impl::{
279/// #     core::{pool::Handle}, core::visitor::prelude::*, core::reflect::prelude::*,
280/// #     plugin::{Plugin, PluginContext, PluginRegistrationContext, error::GameResult},
281/// #     scene::Scene,
282/// #     event::Event
283/// # };
284/// # use std::str::FromStr;
285///
286/// #[derive(Default, Visit, Reflect, Debug)]
287/// #[reflect(non_cloneable)]
288/// struct MyPlugin {}
289///
290/// impl Plugin for MyPlugin {
291///     fn on_deinit(&mut self, context: PluginContext) -> GameResult {
292///         // The method is called when the plugin is disabling.
293///         // The implementation is optional.
294///         Ok(())
295///     }
296///
297///     fn update(&mut self, context: &mut PluginContext) -> GameResult {
298///         // The method is called on every frame, it is guaranteed to have fixed update rate.
299///         // The implementation is optional.
300///         Ok(())
301///     }
302///
303///     fn on_os_event(&mut self, event: &Event<()>, context: PluginContext) -> GameResult {
304///         // The method is called when the main window receives an event from the OS.
305///         Ok(())
306///     }
307/// }
308/// ```
309///
310/// # Error Handling
311///
312/// Every plugin method returns [`GameResult`] (which is a simple wrapper over `Result<(), GameError>`),
313/// this helps to reduce the amount of boilerplate code related to error handling. There are a number
314/// of errors that can be automatically handled via `?` operator. All supported error types listed
315/// in [`error::GameError`] enum.
316///
317/// The following code snippet shows the most common use cases for error handling:
318///
319/// ```rust
320/// # use fyrox_impl::{
321/// #     core::{err, pool::Handle, reflect::prelude::*, visitor::prelude::*},
322/// #     event::Event,
323/// #     graph::SceneGraph,
324/// #     plugin::{error::GameResult, Plugin, PluginContext, PluginRegistrationContext},
325/// #     scene::{node::Node, Scene},
326/// # };
327/// # use std::str::FromStr;
328/// #[derive(Default, Visit, Reflect, Debug)]
329/// #[reflect(non_cloneable)]
330/// struct MyPlugin {
331///     scene: Handle<Scene>,
332///     player: Handle<Node>,
333/// }
334///
335/// impl Plugin for MyPlugin {
336///     fn update(&mut self, context: &mut PluginContext) -> GameResult {
337///         // 1. This is the old approach.
338///         match context.scenes.try_get(self.scene) {
339///             Ok(scene) => match scene.graph.try_get(self.player) {
340///                 Ok(player) => {
341///                     println!("Player name is: {}", player.name());
342///                 }
343///                 Err(error) => {
344///                     err!("Unable to borrow the player. Reason: {error}")
345///                 }
346///             },
347///             Err(error) => {
348///                 err!("Unable to borrow the scene. Reason: {error}")
349///             }
350///         }
351///
352///         // 2. This is the same code as above, but with shortcuts for easier error handling.
353///         // Message report is will be something like this:
354///         // `An error occurred during update plugin method call. Reason: <error message>`.
355///         let scene = context.scenes.try_get(self.scene)?;
356///         let player = scene.graph.try_get(self.player)?;
357///         println!("Player name is: {}", player.name());
358///
359///         Ok(())
360///     }
361/// }
362/// ```
363pub trait Plugin: PluginAsAny + Visit + Reflect {
364    /// The method is called when the plugin constructor was just registered in the engine. The main
365    /// use of this method is to register scripts and custom scene graph nodes in [`SerializationContext`].
366    fn register(
367        &self,
368        #[allow(unused_variables)] context: PluginRegistrationContext,
369    ) -> GameResult {
370        Ok(())
371    }
372
373    /// This method is used to register property editors for your game types; to make them editable
374    /// in the editor.
375    fn register_property_editors(
376        &self,
377        #[allow(unused_variables)] editors: Arc<PropertyEditorDefinitionContainer>,
378    ) {
379    }
380
381    /// This method is used to initialize your plugin.
382    fn init(
383        &mut self,
384        #[allow(unused_variables)] scene_path: Option<&str>,
385        #[allow(unused_variables)] context: PluginContext,
386    ) -> GameResult {
387        Ok(())
388    }
389
390    /// This method is called when your plugin was re-loaded from a dynamic library. It could be used
391    /// to restore some runtime state, that cannot be serialized. This method is called **only for
392    /// dynamic plugins!** It is guaranteed to be called after all plugins were constructed, so the
393    /// cross-plugins interactions are possible.
394    fn on_loaded(&mut self, #[allow(unused_variables)] context: PluginContext) -> GameResult {
395        Ok(())
396    }
397
398    /// The method is called before plugin will be disabled. It should be used for clean up, or some
399    /// additional actions.
400    fn on_deinit(&mut self, #[allow(unused_variables)] context: PluginContext) -> GameResult {
401        Ok(())
402    }
403
404    /// Updates the plugin internals at fixed rate (see [`PluginContext::dt`] parameter for more
405    /// info).
406    fn update(&mut self, #[allow(unused_variables)] context: &mut PluginContext) -> GameResult {
407        Ok(())
408    }
409
410    /// called after all Plugin and Script updates
411    fn post_update(
412        &mut self,
413        #[allow(unused_variables)] context: &mut PluginContext,
414    ) -> GameResult {
415        Ok(())
416    }
417
418    /// The method is called when the main window receives an event from the OS. The main use of
419    /// the method is to respond to some external events, for example an event from keyboard or
420    /// gamepad. See [`Event`] docs for more info.
421    fn on_os_event(
422        &mut self,
423        #[allow(unused_variables)] event: &Event<()>,
424        #[allow(unused_variables)] context: PluginContext,
425    ) -> GameResult {
426        Ok(())
427    }
428
429    /// The method is called when a graphics context was successfully created. It could be useful
430    /// to catch the moment when it was just created and do something in response.
431    fn on_graphics_context_initialized(
432        &mut self,
433        #[allow(unused_variables)] context: PluginContext,
434    ) -> GameResult {
435        Ok(())
436    }
437
438    /// The method is called before the actual frame rendering. It could be useful to render off-screen
439    /// data (render something to texture, that can be used later in the main frame).
440    fn before_rendering(
441        &mut self,
442        #[allow(unused_variables)] context: PluginContext,
443    ) -> GameResult {
444        Ok(())
445    }
446
447    /// The method is called when the current graphics context was destroyed.
448    fn on_graphics_context_destroyed(
449        &mut self,
450        #[allow(unused_variables)] context: PluginContext,
451    ) -> GameResult {
452        Ok(())
453    }
454
455    /// The method will be called when there is any message from a user interface (UI) instance
456    /// of the engine. Use `ui_handle` parameter to find out from which UI the message has come
457    /// from.
458    fn on_ui_message(
459        &mut self,
460        #[allow(unused_variables)] context: &mut PluginContext,
461        #[allow(unused_variables)] message: &UiMessage,
462        #[allow(unused_variables)] ui_handle: Handle<UserInterface>,
463    ) -> GameResult {
464        Ok(())
465    }
466
467    /// This method is called when the engine starts loading a scene from the given `path`. It could
468    /// be used to "catch" the moment when the scene is about to be loaded; to show a progress bar
469    /// for example. See [`AsyncSceneLoader`] docs for usage example.
470    fn on_scene_begin_loading(
471        &mut self,
472        #[allow(unused_variables)] path: &Path,
473        #[allow(unused_variables)] context: &mut PluginContext,
474    ) -> GameResult {
475        Ok(())
476    }
477
478    /// This method is called when the engine finishes loading a scene from the given `path`. Use
479    /// this method if you need do something with a newly loaded scene. See [`AsyncSceneLoader`] docs
480    /// for usage example.
481    fn on_scene_loaded(
482        &mut self,
483        #[allow(unused_variables)] path: &Path,
484        #[allow(unused_variables)] scene: Handle<Scene>,
485        #[allow(unused_variables)] data: &[u8],
486        #[allow(unused_variables)] context: &mut PluginContext,
487    ) -> GameResult {
488        Ok(())
489    }
490
491    /// This method is called when the engine finishes loading a scene from the given `path` with
492    /// some error. This method could be used to report any issues to a user.
493    fn on_scene_loading_failed(
494        &mut self,
495        #[allow(unused_variables)] path: &Path,
496        #[allow(unused_variables)] error: &VisitError,
497        #[allow(unused_variables)] context: &mut PluginContext,
498    ) -> GameResult {
499        Ok(())
500    }
501
502    /// This method is called when a game error has occurred, allowing you to perform some
503    /// specific action to react to it (for example - to show an error message UI in your game).
504    ///
505    /// ## Important notes
506    ///
507    /// This method is called at the end of the current frame, and before that, the engine collects
508    /// all the errors into a queue and then processes them one by one. This means that this method
509    /// won't be called immediately when an error was returned by any of your plugin or script methods,
510    /// but instead the processing will be delayed to the end of the frame.
511    ///
512    /// The error passed by a reference here instead of by-value, because there could be multiple
513    /// plugins that can handle the error. This might seem counterintuitive, but remember that
514    /// [`GameError`] can occur during script execution, which is not a part of a plugin and its
515    /// methods executed separately, outside the plugin routines.
516    ///
517    /// ## Error handling
518    ///
519    /// This method should return `true` if the error was handled and no logging is needed, otherwise
520    /// it should return `false` and in this case, the error will be logged by the engine. When
521    /// `true` is returned by the plugin, the error won't be passed to any other plugins. By default,
522    /// this method returns `false`, which means that it does not handle any errors and the engine
523    /// will log the errors as usual.
524    fn on_game_error(
525        &mut self,
526        #[allow(unused_variables)] context: &mut PluginContext,
527        #[allow(unused_variables)] error: &GameError,
528    ) -> bool {
529        false
530    }
531}