Skip to content

Commit

Permalink
Simplify init module: remove ExtensionLayer + InitHandle, separate us…
Browse files Browse the repository at this point in the history
…er + gdext hooks
  • Loading branch information
Bromeon committed Aug 22, 2023
1 parent c76a0ba commit 015e992
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 123 deletions.
202 changes: 80 additions & 122 deletions godot-core/src/init/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@
*/

use godot_ffi as sys;
use sys::out;

use std::cell;
use std::collections::BTreeMap;

#[doc(hidden)]
// TODO consider body safe despite unsafe function, and explicitly mark unsafe {} locations
Expand All @@ -30,20 +28,18 @@ pub unsafe fn __gdext_load_library<E: ExtensionLibrary>(

sys::initialize(interface_or_get_proc_address, library, config);

let mut handle = InitHandle::new();

let success = E::load_library(&mut handle);
// No early exit, unclear if Godot still requires output parameters to be set
// Currently no way to express failure; could be exposed to E if necessary.
// No early exit, unclear if Godot still requires output parameters to be set.
let success = true;

let godot_init_params = sys::GDExtensionInitialization {
minimum_initialization_level: handle.lowest_init_level().to_sys(),
minimum_initialization_level: E::min_level().to_sys(),
userdata: std::ptr::null_mut(),
initialize: Some(ffi_initialize_layer),
deinitialize: Some(ffi_deinitialize_layer),
initialize: Some(ffi_initialize_layer::<E>),
deinitialize: Some(ffi_deinitialize_layer::<E>),
};

*init = godot_init_params;
INIT_HANDLE = Some(handle);

success as u8
};
Expand All @@ -54,45 +50,61 @@ pub unsafe fn __gdext_load_library<E: ExtensionLibrary>(
is_success.unwrap_or(0)
}

unsafe extern "C" fn ffi_initialize_layer(
unsafe extern "C" fn ffi_initialize_layer<E: ExtensionLibrary>(
_userdata: *mut std::ffi::c_void,
init_level: sys::GDExtensionInitializationLevel,
) {
let ctx = || {
format!(
"failed to initialize GDExtension layer `{:?}`",
InitLevel::from_sys(init_level)
)
};
let level = InitLevel::from_sys(init_level);
let ctx = || format!("failed to initialize GDExtension level `{:?}`", level);

crate::private::handle_panic(ctx, || {
let handle = INIT_HANDLE.as_mut().unwrap();
handle.run_init_function(InitLevel::from_sys(init_level));
gdext_on_level_init(level);
E::on_level_init(level);
});
}

unsafe extern "C" fn ffi_deinitialize_layer(
unsafe extern "C" fn ffi_deinitialize_layer<E: ExtensionLibrary>(
_userdata: *mut std::ffi::c_void,
init_level: sys::GDExtensionInitializationLevel,
) {
let ctx = || {
format!(
"failed to deinitialize GDExtension layer `{:?}`",
InitLevel::from_sys(init_level)
)
};
let level = InitLevel::from_sys(init_level);
let ctx = || format!("failed to deinitialize GDExtension level `{:?}`", level);

crate::private::handle_panic(ctx, || {
let handle = INIT_HANDLE.as_mut().unwrap();
handle.run_deinit_function(InitLevel::from_sys(init_level));
E::on_level_deinit(level);
gdext_on_level_deinit(level);
});
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
/// Tasks needed to be done by gdext internally upon loading an initialization level. Called before user code.
fn gdext_on_level_init(level: InitLevel) {
// SAFETY: we are in the main thread, during initialization, no other logic is happening.
// TODO: in theory, a user could start a thread in one of the early levels, and run concurrent code that messes with the global state
// (e.g. class registration). This would break the assumption that the load_class_method_table() calls are exclusive.
// We could maybe protect globals with a mutex until initialization is complete, and then move it to a directly-accessible, read-only static.
unsafe {
match level {
InitLevel::Core => {}
InitLevel::Servers => {
sys::load_class_method_table(sys::ClassApiLevel::Server);
}
InitLevel::Scene => {
sys::load_class_method_table(sys::ClassApiLevel::Scene);
crate::auto_register_classes();
}
InitLevel::Editor => {
sys::load_class_method_table(sys::ClassApiLevel::Editor);
}
}
}
}

// FIXME make safe
#[doc(hidden)]
pub static mut INIT_HANDLE: Option<InitHandle> = None;
/// Tasks needed to be done by gdext internally upon unloading an initialization level. Called after user code.
fn gdext_on_level_deinit(_level: InitLevel) {
// No logic at the moment.
}

// ----------------------------------------------------------------------------------------------------------------------------------------------

/// Defines the entry point for a GDExtension Rust library.
///
Expand Down Expand Up @@ -125,15 +137,31 @@ pub static mut INIT_HANDLE: Option<InitHandle> = None;
/// [safety]: https://godot-rust.github.io/book/gdext/advanced/safety.html
// FIXME intra-doc link
pub unsafe trait ExtensionLibrary {
fn load_library(handle: &mut InitHandle) -> bool {
handle.register_layer(InitLevel::Scene, DefaultLayer);
true
}

/// Determines if and how an extension's code is run in the editor.
fn editor_run_behavior() -> EditorRunBehavior {
EditorRunBehavior::ToolClassesOnly
}

/// Determines the initialization level at which the extension is loaded (`Scene` by default).
///
/// If the level is lower than [`InitLevel::Scene`], the engine needs to be restarted to take effect.
fn min_level() -> InitLevel {
InitLevel::Scene
}

/// Custom logic when a certain init-level of Godot is loaded.
///
/// This will only be invoked for levels >= [`Self::min_level()`], in ascending order. Use `if` or `match` to hook to specific levels.
fn on_level_init(_level: InitLevel) {
// Nothing by default.
}

/// Custom logic when a certain init-level of Godot is unloaded.
///
/// This will only be invoked for levels >= [`Self::min_level()`], in descending order. Use `if` or `match` to hook to specific levels.
fn on_level_deinit(_level: InitLevel) {
// Nothing by default.
}
}

/// Determines if and how an extension's code is run in the editor.
Expand Down Expand Up @@ -162,98 +190,28 @@ pub enum EditorRunBehavior {
AllClasses,
}

pub trait ExtensionLayer: 'static {
fn initialize(&mut self);
fn deinitialize(&mut self);
}

// ----------------------------------------------------------------------------------------------------------------------------------------------

struct DefaultLayer;

impl ExtensionLayer for DefaultLayer {
fn initialize(&mut self) {
// TODO move both calls out of user code, so it's not accidentally skipped when using a different ExtensionLayer.

// SAFETY: available in Scene level, and only called once -- from the main thread and after interface has been initialized.
unsafe {
sys::load_class_method_table();
}

crate::auto_register_classes();
}

fn deinitialize(&mut self) {
// Nothing -- note that any cleanup task should be performed outside of this method,
// as the user is free to use a different impl, so cleanup code may not be run.
}
}

// ----------------------------------------------------------------------------------------------------------------------------------------------

pub struct InitHandle {
layers: BTreeMap<InitLevel, Box<dyn ExtensionLayer>>,
// success: bool,
}

impl InitHandle {
pub fn new() -> Self {
Self {
layers: BTreeMap::new(),
// success: true,
}
}

pub fn register_layer(&mut self, level: InitLevel, layer: impl ExtensionLayer) {
self.layers.insert(level, Box::new(layer));
}

// pub fn mark_failed(&mut self) {
// self.success = false;
// }

pub fn lowest_init_level(&self) -> InitLevel {
self.layers
.iter()
.next()
.map(|(k, _v)| *k)
.unwrap_or(InitLevel::Scene)
}

pub fn run_init_function(&mut self, level: InitLevel) {
// if let Some(f) = self.init_levels.remove(&level) {
// f();
// }
if let Some(layer) = self.layers.get_mut(&level) {
out!("init: initialize level {level:?}...");
layer.initialize()
} else {
out!("init: skip init of level {level:?}.");
}
}

pub fn run_deinit_function(&mut self, level: InitLevel) {
if let Some(layer) = self.layers.get_mut(&level) {
out!("init: deinitialize level {level:?}...");
layer.deinitialize()
} else {
out!("init: skip deinit of level {level:?}.");
}
}
}

impl Default for InitHandle {
fn default() -> Self {
Self::new()
}
}
// ----------------------------------------------------------------------------------------------------------------------------------------------

/// Stage of the Godot initialization process.
///
/// Godot's initialization and deinitialization processes are split into multiple stages, like a stack. At each level,
/// a different amount of engine functionality is available. Deinitialization happens in reverse order.
///
/// See also:
/// - [`ExtensionLibrary::on_level_init()`]
/// - [`ExtensionLibrary::on_level_deinit()`]
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum InitLevel {
/// First level loaded by Godot. Builtin types are available, classes are not.
Core,

/// Second level loaded by Godot. Only server classes and builtins are available.
Servers,

/// Third level loaded by Godot. Most classes are available.
Scene,

/// Fourth level loaded by Godot, only in the editor. All classes are available.
Editor,
}

Expand Down
2 changes: 1 addition & 1 deletion godot/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ pub mod prelude {
PackedSceneVirtual, RefCounted, RefCountedVirtual, Resource, ResourceVirtual, SceneTree,
SceneTreeVirtual,
};
pub use super::init::{gdextension, ExtensionLayer, ExtensionLibrary, InitHandle, InitLevel};
pub use super::init::{gdextension, ExtensionLibrary, InitLevel};
pub use super::log::*;
pub use super::obj::{Base, Gd, GdMut, GdRef, GodotClass, Inherits, InstanceId, Share};

Expand Down

0 comments on commit 015e992

Please sign in to comment.