diff --git a/Cargo.toml b/Cargo.toml index 9030800ffd39e..5d8be4fe3c315 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -263,6 +263,13 @@ futures-lite = "1.11.3" crossbeam-channel = "0.5.0" argh = "0.1.12" +# Needed for the dynamic linking examples +libloading = { version = "0.8" } +parking_lot = "0.12.1" +bevy_dynamic_plugin = { path = "crates/bevy_dynamic_plugin" } +# bevy must be dynamically linked into the examples, or else dynamic plugin libraries get their own private copy of bevy and chaos ensues +bevy = { path = ".", features = ["dynamic_linking"] } + [[example]] name = "hello_world" path = "examples/hello_world.rs" @@ -2290,6 +2297,25 @@ description = "Demonstrates resizing and responding to resizing a window" category = "Window" wasm = true +[[example]] +name = "dynamic_plugin" +path = "examples/ecs/dynamic_plugin.rs" +# remember to use `-Zshare-generics=n` on Windows, see the template .cargo/config_fast_builds +crate-type = ["dylib"] +doc-scrape-examples = false + +[package.metadata.example.dynamic_plugin] +name = "Dynamic Plugin" +description = "A dynamic plugin in a shared library loaded by the load_dynamic_plugin example at run time" + +[[example]] +name = "load_dynamic_plugin" +path = "examples/ecs/load_dynamic_plugin.rs" + +[package.metadata.example.load_dynamic_plugin] +name = "Dynamic Plugin Loading" +description = "Demonstrates loading a dynamic plugin from a shared library" + [profile.wasm-release] inherits = "release" opt-level = "z" diff --git a/examples/ecs/dynamic_plugin.rs b/examples/ecs/dynamic_plugin.rs new file mode 100644 index 0000000000000..5cd95ee56f6a0 --- /dev/null +++ b/examples/ecs/dynamic_plugin.rs @@ -0,0 +1,18 @@ +//! A dynamic library that contains a dynamically loaded plugin. See the `load_dynamic_plugin` on how to load such a plugin. + +use bevy::prelude::*; + +/// Derive DynamicPlugin on one main plugin, which will then be loaded by [`bevy_dynamic_plugin::load_dynamic_plugin`]. +#[derive(DynamicPlugin)] +struct MyDynamicPlugin; + +impl Plugin for MyDynamicPlugin { + fn build(&self, app: &mut App) { + info!("Plugin is being loaded..."); + app.add_systems(Update, say_hello); + } +} + +fn say_hello() { + info!("Hello from the dynamic plugin!"); +} diff --git a/examples/ecs/load_dynamic_plugin.rs b/examples/ecs/load_dynamic_plugin.rs new file mode 100644 index 0000000000000..ee35db8567aaa --- /dev/null +++ b/examples/ecs/load_dynamic_plugin.rs @@ -0,0 +1,33 @@ +//! Loads a dynamic plugin from the shared library `dynamic_plugin` + +use std::path::PathBuf; + +use bevy::prelude::*; +use bevy_dynamic_plugin::dynamically_load_plugin; +use libloading::Library; +use parking_lot::Mutex; + +/// If the library wasn't stored here, libloading would unload it as soon as we were done with it below. +/// This would cause the program to crash/segfault later when bevy tries to invoke the plugin's systems. +/// Therefore, libraries should be stored such that they outlive the [`App`]. +/// A simple method is to use a global mutex. +static LIBRARY: Mutex> = Mutex::new(None); + +fn main() { + let mut app = App::new(); + app.add_plugins(DefaultPlugins); + + #[cfg(any(target_family = "windows", target_family = "unix"))] + { + // The ending can be the default library ending of the operating system (e.g. .dll, .so). + let plugin_name = PathBuf::from("dynamic_plugin"); + + let (library, plugin) = unsafe { dynamically_load_plugin(plugin_name) }.unwrap(); + app.add_plugins(plugin); + info!("Loaded plugin!"); + // Make sure the plugin stays alive by storing it in a global variable: + *LIBRARY.lock() = Some(library); + } + + app.run(); +}