Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document all members of bevy_dynamic_plugin #12029

Merged
merged 6 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions crates/bevy_dynamic_plugin/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
// FIXME(3492): remove once docs are ready
#![allow(missing_docs)]
//! Bevy's dynamic plugin loading functionality.
//!
//! This crate allows loading dynamic libraries (`.dylib`, `.so`) that export a single
//! [`Plugin`](bevy_app::Plugin). For usage, see [`dynamically_load_plugin`].
//!
//! Note that dynamic linking and loading is inherently unsafe because it allows executing foreign
//! code. Additionally, Rust does not have a stable ABI and may produce
//! incompatible libraries across Rust versions, or even subsequent compilations. This will not work
//! well in scenarios such as modding, but can work if the dynamic plugins and the main app are
//! built at the same time, such as with Downloadable Content (DLC) packs.
//!
//! You may be interested in these safer alternatives:
//!
//! - [Bevy Assets - Scripting]: Scripting and modding libraries for Bevy
//! - [Bevy Assets - Development tools]: Hot reloading and other development functionality
//! - [`stabby`]: Stable Rust ABI
//!
//! [Bevy Assets - Scripting]: https://bevyengine.org/assets/#scripting
//! [Bevy Assets - Development tools]: https://bevyengine.org/assets/#development-tools
//! [`stabby`]: https://github.com/ZettaScaleLabs/stabby

mod loader;

Expand Down
23 changes: 15 additions & 8 deletions crates/bevy_dynamic_plugin/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ use bevy_app::{App, CreatePlugin, Plugin};
/// Errors that can occur when loading a dynamic plugin
#[derive(Debug, Error)]
pub enum DynamicPluginLoadError {
/// An error occurred when loading a dynamic library.
#[error("cannot load library for dynamic plugin: {0}")]
Library(#[source] libloading::Error),
/// An error occurred when loading a library without a valid Bevy plugin.
#[error("dynamic library does not contain a valid Bevy dynamic plugin")]
Plugin(#[source] libloading::Error),
}
Expand All @@ -18,22 +20,22 @@ pub enum DynamicPluginLoadError {
///
/// # Safety
///
/// The specified plugin must be linked against the exact same libbevy.so as this program.
/// The specified plugin must be linked against the exact same `libbevy.so` as this program.
/// In addition the `_bevy_create_plugin` symbol must not be manually created, but instead created
/// by deriving `DynamicPlugin` on a unit struct implementing [`Plugin`].
///
/// Dynamically loading plugins is orchestrated through dynamic linking. When linking against foreign
/// code, initialization routines may be run (as well as termination routines when the program exits).
/// The caller of this function is responsible for ensuring these routines are sound. For more
/// information, please see the safety section of [`libloading::Library::new`].
/// Dynamically loading plugins is orchestrated through dynamic linking. When linking against
/// foreign code, initialization routines may be run (as well as termination routines when the
/// program exits). The caller of this function is responsible for ensuring these routines are
/// sound. For more information, please see the safety section of [`libloading::Library::new`].
pub unsafe fn dynamically_load_plugin<P: AsRef<OsStr>>(
path: P,
) -> Result<(Library, Box<dyn Plugin>), DynamicPluginLoadError> {
// SAFETY: Caller must follow the safety requirements of Library::new.
let lib = unsafe { Library::new(path).map_err(DynamicPluginLoadError::Library)? };

// SAFETY: Loaded plugins are not allowed to specify `_bevy_create_plugin` symbol manually, but must
// instead automatically generate it through `DynamicPlugin`.
// SAFETY: Loaded plugins are not allowed to specify `_bevy_create_plugin` symbol manually, but
// must instead automatically generate it through `DynamicPlugin`.
let func: Symbol<CreatePlugin> = unsafe {
lib.get(b"_bevy_create_plugin")
.map_err(DynamicPluginLoadError::Plugin)?
Expand All @@ -46,10 +48,15 @@ pub unsafe fn dynamically_load_plugin<P: AsRef<OsStr>>(
Ok((lib, plugin))
}

/// An extension trait for [`App`] that allows loading dynamic plugins.
pub trait DynamicPluginExt {
/// Dynamically links a plugin at the given path, registering the plugin.
///
/// For more details, see [`dynamically_load_plugin`].
///
/// # Safety
///
/// Same as [`dynamically_load_plugin`].
/// See [`dynamically_load_plugin`]'s safety section.
unsafe fn load_plugin<P: AsRef<OsStr>>(&mut self, path: P) -> &mut Self;
}

Expand Down