From 49fc08825949f02eb322e7981af89c0928ab7bdc Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Sun, 30 Jul 2023 13:01:36 +0200 Subject: [PATCH 1/2] No longer automatically run GDExtension callbacks in editor Add enum EditorRunBehavior to configure semantics. --- examples/dodge-the-creeps/rust/src/player.rs | 10 +-- godot-core/src/init/mod.rs | 64 ++++++++++++++++---- godot-ffi/src/lib.rs | 24 +++++++- godot-macros/src/godot_api.rs | 7 +++ godot-macros/src/lib.rs | 2 +- 5 files changed, 85 insertions(+), 22 deletions(-) diff --git a/examples/dodge-the-creeps/rust/src/player.rs b/examples/dodge-the-creeps/rust/src/player.rs index ca9b5a116..1d1111b1e 100644 --- a/examples/dodge-the-creeps/rust/src/player.rs +++ b/examples/dodge-the-creeps/rust/src/player.rs @@ -1,6 +1,4 @@ -use godot::engine::{ - AnimatedSprite2D, Area2D, Area2DVirtual, CollisionShape2D, Engine, PhysicsBody2D, -}; +use godot::engine::{AnimatedSprite2D, Area2D, Area2DVirtual, CollisionShape2D, PhysicsBody2D}; use godot::prelude::*; #[derive(GodotClass)] @@ -60,11 +58,7 @@ impl Area2DVirtual for Player { } fn process(&mut self, delta: f64) { - // Don't process if running in editor. This part should be removed when - // issue is resolved: https://github.com/godot-rust/gdext/issues/70 - if Engine::singleton().is_editor_hint() { - return; - } + println!("process"); let mut animated_sprite = self .base diff --git a/godot-core/src/init/mod.rs b/godot-core/src/init/mod.rs index 83e30eb31..b068e8aa6 100644 --- a/godot-core/src/init/mod.rs +++ b/godot-core/src/init/mod.rs @@ -5,7 +5,10 @@ */ use godot_ffi as sys; -use std::collections::btree_map::BTreeMap; +use sys::out; + +use std::cell; +use std::collections::BTreeMap; #[doc(hidden)] // TODO consider body safe despite unsafe function, and explicitly mark unsafe {} locations @@ -15,7 +18,17 @@ pub unsafe fn __gdext_load_library( init: *mut sys::GDExtensionInitialization, ) -> sys::GDExtensionBool { let init_code = || { - sys::initialize(interface_or_get_proc_address, library); + let virtuals_active_in_editor = match E::editor_run_behavior() { + EditorRunBehavior::Full => true, + EditorRunBehavior::NoVirtuals => false, + }; + + let config = sys::GdextConfig { + virtuals_active_in_editor, + is_editor: cell::OnceCell::new(), + }; + + sys::initialize(interface_or_get_proc_address, library, config); let mut handle = InitHandle::new(); @@ -41,11 +54,6 @@ pub unsafe fn __gdext_load_library( is_success.unwrap_or(0) } -#[doc(hidden)] -pub fn __gdext_default_init(handle: &mut InitHandle) { - handle.register_layer(InitLevel::Scene, DefaultLayer); -} - unsafe extern "C" fn ffi_initialize_layer( _userdata: *mut std::ffi::c_void, init_level: sys::GDExtensionInitializationLevel, @@ -97,8 +105,8 @@ pub static mut INIT_HANDLE: Option = None; /// /// ``` /// # use godot::init::*; -/// -/// // This is just a type tag without any functionality +/// // This is just a type tag without any functionality. +/// // Its name is irrelevant. /// struct MyExtension; /// /// #[gdextension] @@ -113,14 +121,42 @@ pub static mut INIT_HANDLE: Option = None; /// responsible to uphold them: namely in GDScript code or other GDExtension bindings loaded by the engine. /// Violating this may cause undefined behavior, even when invoking _safe_ functions. /// -/// [gdextension]: crate::init::gdextension -/// [safety]: https://godot-rust.github.io/book/gdextension/safety.html +/// [gdextension]: attr.gdextension.html +/// [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::NoVirtuals + } +} + +/// Determines if and how an extension's code is run in the editor. +/// +/// By default, Godot 4 runs all virtual lifecycle callbacks (`_ready`, `_process`, `_physics_process`, ...) +/// for extensions. This behavior is different from Godot 3, where extension classes needed to be explicitly +/// marked as "tool". +/// +/// In many cases, users write extension code with the intention to run in games, not inside the editor. +/// This is why the default behavior in gdext deviates from Godot: lifecycle callbacks are disabled inside the +/// editor. It is however possible to restore the original behavior with this enum. +/// +/// See also [`ExtensionLibrary::editor_run_behavior()`]. +#[derive(Copy, Clone, Debug)] +#[non_exhaustive] +pub enum EditorRunBehavior { + /// Runs the extension with full functionality in editor. + Full, + + /// Does not invoke any Godot virtual functions. + /// + /// Classes are still registered, and calls from GDScript to Rust are still possible. + NoVirtuals, } pub trait ExtensionLayer: 'static { @@ -179,13 +215,19 @@ impl InitHandle { // 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:?}."); } } } diff --git a/godot-ffi/src/lib.rs b/godot-ffi/src/lib.rs index 174f876e9..f5fb9f3e5 100644 --- a/godot-ffi/src/lib.rs +++ b/godot-ffi/src/lib.rs @@ -27,6 +27,7 @@ mod plugins; mod toolbox; use compat::BindingCompat; +use std::cell; use std::ffi::CStr; // See https://github.com/dtolnay/paste/issues/69#issuecomment-962418430 @@ -52,12 +53,18 @@ struct GodotBinding { library: GDExtensionClassLibraryPtr, method_table: GlobalMethodTable, runtime_metadata: GdextRuntimeMetadata, + config: GdextConfig, } struct GdextRuntimeMetadata { godot_version: GDExtensionGodotVersion, } +pub struct GdextConfig { + pub virtuals_active_in_editor: bool, + pub is_editor: cell::OnceCell, +} + /// Late-init globals // Note: static mut is _very_ dangerous. Here a bit less so, since modification happens only once (during init) and no // &mut references are handed out (except for registry, see below). Overall, UnsafeCell/RefCell + Sync might be a safer abstraction. @@ -71,7 +78,11 @@ static mut BINDING: Option = None; /// - The `library` pointer must be the pointer given by Godot at initialisation. /// - This function must not be called from multiple threads. /// - This function must be called before any use of [`get_library`]. -pub unsafe fn initialize(compat: InitCompat, library: GDExtensionClassLibraryPtr) { +pub unsafe fn initialize( + compat: InitCompat, + library: GDExtensionClassLibraryPtr, + config: GdextConfig, +) { out!("Initialize gdext..."); out!( @@ -100,6 +111,7 @@ pub unsafe fn initialize(compat: InitCompat, library: GDExtensionClassLibraryPtr method_table, library, runtime_metadata, + config, }); out!("Assigned binding."); @@ -144,12 +156,20 @@ pub unsafe fn method_table() -> &'static GlobalMethodTable { /// # Safety /// -/// Must be accessed from the main thread. +/// Must be accessed from the main thread, and the interface must have been initialized. #[inline(always)] pub(crate) unsafe fn runtime_metadata() -> &'static GdextRuntimeMetadata { &BINDING.as_ref().unwrap().runtime_metadata } +/// # Safety +/// +/// Must be accessed from the main thread, and the interface must have been initialized. +#[inline] +pub unsafe fn config() -> &'static GdextConfig { + &BINDING.as_ref().unwrap().config +} + // ---------------------------------------------------------------------------------------------------------------------------------------------- // Macros to access low-level function bindings diff --git a/godot-macros/src/godot_api.rs b/godot-macros/src/godot_api.rs index f4bfb0264..799af605a 100644 --- a/godot-macros/src/godot_api.rs +++ b/godot-macros/src/godot_api.rs @@ -468,6 +468,13 @@ fn transform_trait_impl(original_impl: Impl) -> Result { fn __virtual_call(name: &str) -> ::godot::sys::GDExtensionClassCallVirtual { //println!("virtual_call: {}.{}", std::any::type_name::(), name); + let __config = unsafe { ::godot::sys::config() }; + + if !__config.virtuals_active_in_editor + && *__config.is_editor.get_or_init(|| ::godot::engine::Engine::singleton().is_editor_hint()) { + return None; + } + match name { #( #virtual_method_names => #virtual_method_callbacks, diff --git a/godot-macros/src/lib.rs b/godot-macros/src/lib.rs index 68f683bf5..56d72ec21 100644 --- a/godot-macros/src/lib.rs +++ b/godot-macros/src/lib.rs @@ -469,7 +469,7 @@ pub fn itest(meta: TokenStream, input: TokenStream) -> TokenStream { /// Proc-macro attribute to be used in combination with the [`ExtensionLibrary`] trait. /// -/// [`ExtensionLibrary`]: crate::init::ExtensionLibrary +/// [`ExtensionLibrary`]: trait.ExtensionLibrary.html // FIXME intra-doc link #[proc_macro_attribute] pub fn gdextension(meta: TokenStream, input: TokenStream) -> TokenStream { From 235e0fadd61fb398347a01eb706cc7972b8250e8 Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Sun, 30 Jul 2023 14:50:13 +0200 Subject: [PATCH 2/2] Update MSRV 1.66.0 -> 1.70.0 Required for OnceCell/OnceLock, which are needed for EditorRunBehavior and will likely play an important role in safe access to global handles. --- .github/workflows/full-ci.yml | 2 +- examples/dodge-the-creeps/rust/Cargo.toml | 2 +- godot-bindings/Cargo.toml | 2 +- godot-codegen/Cargo.toml | 2 +- godot-core/Cargo.toml | 2 +- godot-ffi/Cargo.toml | 2 +- godot-macros/Cargo.toml | 2 +- godot/Cargo.toml | 2 +- itest/rust/Cargo.toml | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/full-ci.yml b/.github/workflows/full-ci.yml index 0a638f70f..119a6f008 100644 --- a/.github/workflows/full-ci.yml +++ b/.github/workflows/full-ci.yml @@ -93,7 +93,7 @@ jobs: - name: linux os: ubuntu-20.04 - rust-toolchain: '1.66.0' + rust-toolchain: '1.70.0' rust-special: -msrv steps: diff --git a/examples/dodge-the-creeps/rust/Cargo.toml b/examples/dodge-the-creeps/rust/Cargo.toml index c6769d582..8f650fb82 100644 --- a/examples/dodge-the-creeps/rust/Cargo.toml +++ b/examples/dodge-the-creeps/rust/Cargo.toml @@ -2,7 +2,7 @@ name = "dodge-the-creeps" version = "0.1.0" edition = "2021" -rust-version = "1.66" +rust-version = "1.70" publish = false [lib] diff --git a/godot-bindings/Cargo.toml b/godot-bindings/Cargo.toml index e872b68dc..31a167d7c 100644 --- a/godot-bindings/Cargo.toml +++ b/godot-bindings/Cargo.toml @@ -2,7 +2,7 @@ name = "godot-bindings" version = "0.1.0" edition = "2021" -rust-version = "1.66" +rust-version = "1.70" license = "MPL-2.0" keywords = ["gamedev", "godot", "engine", "ffi", "sys"] categories = ["game-engines", "graphics"] diff --git a/godot-codegen/Cargo.toml b/godot-codegen/Cargo.toml index a84b24797..1784b414c 100644 --- a/godot-codegen/Cargo.toml +++ b/godot-codegen/Cargo.toml @@ -2,7 +2,7 @@ name = "godot-codegen" version = "0.1.0" edition = "2021" -rust-version = "1.66" +rust-version = "1.70" license = "MPL-2.0" keywords = ["gamedev", "godot", "engine", "codegen"] categories = ["game-engines", "graphics"] diff --git a/godot-core/Cargo.toml b/godot-core/Cargo.toml index 8c96dd95b..d3dbe35b6 100644 --- a/godot-core/Cargo.toml +++ b/godot-core/Cargo.toml @@ -2,7 +2,7 @@ name = "godot-core" version = "0.1.0" edition = "2021" -rust-version = "1.66" +rust-version = "1.70" license = "MPL-2.0" keywords = ["gamedev", "godot", "engine", "2d", "3d"] # possibly: "ffi" categories = ["game-engines", "graphics"] diff --git a/godot-ffi/Cargo.toml b/godot-ffi/Cargo.toml index ccf2b6f49..a76d0b6db 100644 --- a/godot-ffi/Cargo.toml +++ b/godot-ffi/Cargo.toml @@ -2,7 +2,7 @@ name = "godot-ffi" version = "0.1.0" edition = "2021" -rust-version = "1.66" +rust-version = "1.70" license = "MPL-2.0" keywords = ["gamedev", "godot", "engine", "ffi"] categories = ["game-engines", "graphics"] diff --git a/godot-macros/Cargo.toml b/godot-macros/Cargo.toml index 005b2a187..d9172e7cb 100644 --- a/godot-macros/Cargo.toml +++ b/godot-macros/Cargo.toml @@ -2,7 +2,7 @@ name = "godot-macros" version = "0.1.0" edition = "2021" -rust-version = "1.66" +rust-version = "1.70" license = "MPL-2.0" keywords = ["gamedev", "godot", "engine", "derive", "macro"] categories = ["game-engines", "graphics"] diff --git a/godot/Cargo.toml b/godot/Cargo.toml index 92bc9ed19..f2390b59f 100644 --- a/godot/Cargo.toml +++ b/godot/Cargo.toml @@ -2,7 +2,7 @@ name = "godot" version = "0.1.0" edition = "2021" -rust-version = "1.66" +rust-version = "1.70" license = "MPL-2.0" keywords = ["gamedev", "godot", "engine", "2d", "3d"] # possibly: "ffi" categories = ["game-engines", "graphics"] diff --git a/itest/rust/Cargo.toml b/itest/rust/Cargo.toml index 709eb9a39..db7cce8a6 100644 --- a/itest/rust/Cargo.toml +++ b/itest/rust/Cargo.toml @@ -2,7 +2,7 @@ name = "itest" version = "0.1.0" edition = "2021" -rust-version = "1.66" +rust-version = "1.70" publish = false [lib]