diff --git a/examples/builder-export/src/lib.rs b/examples/builder-export/src/lib.rs index 8f9481299..7ae74d4f3 100644 --- a/examples/builder-export/src/lib.rs +++ b/examples/builder-export/src/lib.rs @@ -57,8 +57,11 @@ impl ExportsArrays { } } -fn init(handle: InitHandle) { - handle.add_class::(); -} +struct BuilderExportLibrary; -godot_init!(init); +#[gdnative::init::callbacks] +impl GDNativeCallbacks for BuilderExportLibrary { + fn nativescript_init(handle: InitHandle) { + handle.add_class::(); + } +} diff --git a/examples/dodge-the-creeps/src/lib.rs b/examples/dodge-the-creeps/src/lib.rs index ddff7df7c..375e57bf8 100644 --- a/examples/dodge-the-creeps/src/lib.rs +++ b/examples/dodge-the-creeps/src/lib.rs @@ -5,11 +5,14 @@ mod main_scene; mod mob; mod player; -fn init(handle: InitHandle) { - handle.add_class::(); - handle.add_class::(); - handle.add_class::(); - handle.add_class::(); -} +struct DtcLibrary; -godot_init!(init); +#[gdnative::init::callbacks] +impl GDNativeCallbacks for DtcLibrary { + fn nativescript_init(handle: InitHandle) { + handle.add_class::(); + handle.add_class::(); + handle.add_class::(); + handle.add_class::(); + } +} diff --git a/examples/godot_tps_controller_port/src/lib.rs b/examples/godot_tps_controller_port/src/lib.rs index 078c7bd08..89148fc6b 100644 --- a/examples/godot_tps_controller_port/src/lib.rs +++ b/examples/godot_tps_controller_port/src/lib.rs @@ -2,8 +2,11 @@ use gdnative::prelude::*; mod player; -fn init(handle: InitHandle) { - handle.add_class::(); -} +struct TpsLibrary; -godot_init!(init); +#[gdnative::init::callbacks] +impl GDNativeCallbacks for TpsLibrary { + fn nativescript_init(handle: InitHandle) { + handle.add_class::(); + } +} diff --git a/examples/hello-world/src/lib.rs b/examples/hello-world/src/lib.rs index 19bf6be05..e358bcd2a 100644 --- a/examples/hello-world/src/lib.rs +++ b/examples/hello-world/src/lib.rs @@ -16,8 +16,11 @@ impl HelloWorld { } } -fn init(handle: InitHandle) { - handle.add_class::(); -} +struct HelloWorldLibrary; -godot_init!(init); +#[gdnative::init::callbacks] +impl GDNativeCallbacks for HelloWorldLibrary { + fn nativescript_init(handle: InitHandle) { + handle.add_class::(); + } +} diff --git a/examples/native-plugin/src/lib.rs b/examples/native-plugin/src/lib.rs index 8e1dc4276..a32a32964 100644 --- a/examples/native-plugin/src/lib.rs +++ b/examples/native-plugin/src/lib.rs @@ -54,9 +54,12 @@ impl MyButton { } } -fn init(handle: InitHandle) { - handle.add_tool_class::(); - handle.add_tool_class::(); -} +struct PluginLibrary; -godot_init!(init); +#[gdnative::init::callbacks] +impl GDNativeCallbacks for PluginLibrary { + fn nativescript_init(handle: InitHandle) { + handle.add_tool_class::(); + handle.add_tool_class::(); + } +} diff --git a/examples/property-export/src/lib.rs b/examples/property-export/src/lib.rs index aa9da6d2c..395128705 100644 --- a/examples/property-export/src/lib.rs +++ b/examples/property-export/src/lib.rs @@ -41,8 +41,11 @@ impl PropertyExport { } } -fn init(handle: InitHandle) { - handle.add_class::(); -} +struct PropertyExportLibrary; -godot_init!(init); +#[gdnative::init::callbacks] +impl GDNativeCallbacks for PropertyExportLibrary { + fn nativescript_init(handle: InitHandle) { + handle.add_class::(); + } +} diff --git a/examples/resource/src/lib.rs b/examples/resource/src/lib.rs index b94cf909c..d0b70963c 100644 --- a/examples/resource/src/lib.rs +++ b/examples/resource/src/lib.rs @@ -46,9 +46,12 @@ impl Greeter { } } -fn init(handle: InitHandle) { - handle.add_class::(); - handle.add_class::(); -} +struct ResourceLibrary; -godot_init!(init); +#[gdnative::init::callbacks] +impl GDNativeCallbacks for ResourceLibrary { + fn nativescript_init(handle: InitHandle) { + handle.add_class::(); + handle.add_class::(); + } +} diff --git a/examples/rpc/src/lib.rs b/examples/rpc/src/lib.rs index 538d1f618..fbf7db4e7 100644 --- a/examples/rpc/src/lib.rs +++ b/examples/rpc/src/lib.rs @@ -3,9 +3,12 @@ use gdnative::prelude::*; mod client; mod server; -fn init(handle: InitHandle) { - handle.add_class::(); - handle.add_class::(); -} +struct RpcLibrary; -godot_init!(init); +#[gdnative::init::callbacks] +impl GDNativeCallbacks for RpcLibrary { + fn nativescript_init(handle: InitHandle) { + handle.add_class::(); + handle.add_class::(); + } +} diff --git a/examples/scene-create/src/lib.rs b/examples/scene-create/src/lib.rs index e542926bc..f273625ee 100644 --- a/examples/scene-create/src/lib.rs +++ b/examples/scene-create/src/lib.rs @@ -100,10 +100,6 @@ impl SceneCreate { } } -fn init(handle: InitHandle) { - handle.add_class::(); -} - pub fn load_scene(path: &str) -> Option> { let scene = load::(path)?; let scene = unsafe { scene.assume_thread_local() }; @@ -151,4 +147,11 @@ fn update_panel(owner: &Spatial, num_children: i64) { } } -godot_init!(init); +struct SceneCreateLibrary; + +#[gdnative::init::callbacks] +impl GDNativeCallbacks for SceneCreateLibrary { + fn nativescript_init(handle: InitHandle) { + handle.add_class::(); + } +} diff --git a/examples/signals/src/lib.rs b/examples/signals/src/lib.rs index 9dec7e219..b780aabf3 100644 --- a/examples/signals/src/lib.rs +++ b/examples/signals/src/lib.rs @@ -95,9 +95,12 @@ impl SignalSubscriber { } } -fn init(handle: InitHandle) { - handle.add_class::(); - handle.add_class::(); -} +struct SignalLibrary; -godot_init!(init); +#[gdnative::init::callbacks] +impl GDNativeCallbacks for SignalLibrary { + fn nativescript_init(handle: InitHandle) { + handle.add_class::(); + handle.add_class::(); + } +} diff --git a/examples/spinning-cube/src/lib.rs b/examples/spinning-cube/src/lib.rs index f09bdf4a0..546a5f4ad 100644 --- a/examples/spinning-cube/src/lib.rs +++ b/examples/spinning-cube/src/lib.rs @@ -69,8 +69,11 @@ impl RustTest { } } -fn init(handle: InitHandle) { - handle.add_class::(); -} +struct CubeLibrary; -godot_init!(init); +#[gdnative::init::callbacks] +impl GDNativeCallbacks for CubeLibrary { + fn nativescript_init(handle: InitHandle) { + handle.add_class::(); + } +} diff --git a/gdnative-core/src/init/macros.rs b/gdnative-core/src/init/macros.rs index a0c783e69..a7200b8d1 100644 --- a/gdnative-core/src/init/macros.rs +++ b/gdnative-core/src/init/macros.rs @@ -1,169 +1,97 @@ #![macro_use] -/// Declare the API endpoint to initialize export classes on startup. +/// Legacy macro that declares all the API endpoints necessary to initialize a NativeScript library. /// -/// By default this declares an extern function named `godot_nativescript_init`. -/// This can be overridden, for example: -/// -/// ```ignore -/// // Declares an extern function named custom_nativescript_init instead of -/// // godot_nativescript_init. -/// godot_gdnative_terminate!(my_registration_callback as custom_nativescript_init); -/// ``` +/// This is a shim for the new [`#[gdnative::init::callbacks]`][crate::init::callbacks] attribute +/// macro, which is now used to declare API callbacks along with the +/// [`GDNativeCallbacks`][crate::init::GDNativeCallbacks] trait. +#[macro_export] +#[deprecated = "use the #[gdnative::init::callbacks] attribute macro instead"] +macro_rules! godot_init { + ($callback:ident) => { + const _: () = { + struct GDNativeCallbacksImpl; + + #[$crate::init::callbacks] + unsafe impl $crate::init::GDNativeCallbacks for GDNativeCallbacksImpl { + fn nativescript_init(handle: $crate::init::InitHandle) { + $callback(handle); + } + } + }; + }; +} + +/// Legacy macro that declares all the API endpoints necessary to initialize a NativeScript library. /// -/// Overriding the default entry point names can be useful if several gdnative -/// libraries are linked statically to avoid name clashes. +/// This is a shim for the new [`#[gdnative::init::callbacks]`][crate::init::callbacks] attribute +/// macro, which is now used to declare API callbacks along with the +/// [`GDNativeCallbacks`][crate::init::GDNativeCallbacks] trait. #[macro_export] +#[deprecated = "use the #[gdnative::init::callbacks] attribute macro instead"] macro_rules! godot_nativescript_init { () => { - fn godot_nativescript_init_empty(_init: $crate::init::InitHandle) {} - $crate::godot_nativescript_init!(godot_nativescript_init_empty); + const _: () = { + struct GDNativeCallbacksImpl; + + #[$crate::init::callbacks] + unsafe impl $crate::init::GDNativeCallbacks for GDNativeCallbacksImpl {} + }; }; ($callback:ident) => { - $crate::godot_nativescript_init!($callback as godot_nativescript_init); + const _: () = { + struct GDNativeCallbacksImpl; + + #[$crate::init::callbacks] + unsafe impl $crate::init::GDNativeCallbacks for GDNativeCallbacksImpl { + fn nativescript_init(handle: $crate::init::InitHandle) { + $callback(handle); + } + } + }; }; (_ as $fn_name:ident) => { - fn godot_nativescript_init_empty(_init: $crate::init::InitHandle) {} - $crate::godot_nativescript_init!(godot_nativescript_init_empty as $fn_name); + ::std::compile_error!("this syntax is no longer supported. use the #[gdnative::init::callbacks] attribute macro with a prefix instead"); }; ($callback:ident as $fn_name:ident) => { - #[no_mangle] - #[doc(hidden)] - #[allow(unused_unsafe)] - pub unsafe extern "C" fn $fn_name(handle: *mut $crate::libc::c_void) { - if !$crate::private::is_api_bound() { - return; - } - - $crate::private::report_panics("nativescript_init", || { - $crate::init::auto_register($crate::init::InitHandle::new( - handle, - $crate::init::InitLevel::AUTO, - )); - $callback($crate::init::InitHandle::new( - handle, - $crate::init::InitLevel::USER, - )); - - $crate::init::diagnostics::missing_suggested_diagnostics(); - }); - } + ::std::compile_error!("this syntax is no longer supported. use the #[gdnative::init::callbacks] attribute macro with a prefix instead"); }; } -/// Declare the API endpoint to initialize the gdnative API on startup. +/// This macro now does nothing. It is provided only for limited backwards compatibility. /// -/// By default this declares an extern function named `godot_gdnative_init`. -/// This can be overridden, for example: -/// -/// ```ignore -/// // Declares an extern function named custom_gdnative_init instead of -/// // godot_gdnative_init. -/// godot_gdnative_init!(my_init_callback as custom_gdnative_init); -/// ``` -/// -/// Overriding the default entry point names can be useful if several gdnative -/// libraries are linked statically to avoid name clashes. +/// Use the new [`#[gdnative::init::callbacks]`][crate::init::callbacks] attribute macro and, +/// [`GDNativeCallbacks`][crate::init::GDNativeCallbacks] trait instead. #[macro_export] +#[deprecated = "use the #[gdnative::init::callbacks] attribute macro instead"] macro_rules! godot_gdnative_init { - () => { - fn godot_gdnative_init_empty(_options: &$crate::init::InitializeInfo) {} - $crate::init::godot_gdnative_init!(godot_gdnative_init_empty); + () => {}; + ($callback:ident) => { + ::std::compile_error!("this syntax is no longer supported. use the #[gdnative::init::callbacks] attribute macro instead"); }; (_ as $fn_name:ident) => { - fn godot_gdnative_init_empty(_options: &$crate::init::InitializeInfo) {} - $crate::init::godot_gdnative_init!(godot_gdnative_init_empty as $fn_name); - }; - ($callback:ident) => { - $crate::init::godot_gdnative_init!($callback as godot_gdnative_init); + ::std::compile_error!("this syntax is no longer supported. use the #[gdnative::init::callbacks] attribute macro with a prefix instead"); }; ($callback:ident as $fn_name:ident) => { - #[no_mangle] - #[doc(hidden)] - #[allow(unused_unsafe)] - pub unsafe extern "C" fn $fn_name(options: *mut $crate::sys::godot_gdnative_init_options) { - if !$crate::private::bind_api(options) { - // Can't use godot_error here because the API is not bound. - // Init errors should be reported by bind_api. - return; - } - - $crate::init::diagnostics::godot_version_mismatch(); - - $crate::private::report_panics("gdnative_init", || { - let init_info = $crate::init::InitializeInfo::new(options); - $callback(&init_info) - }); - } + ::std::compile_error!("this syntax is no longer supported. use the #[gdnative::init::callbacks] attribute macro with a prefix instead"); }; } -/// Declare the API endpoint invoked during shutdown. -/// -/// By default this declares an extern function named `godot_gdnative_terminate`. -/// This can be overridden, for example: +/// This macro now does nothing. It is provided only for limited backwards compatibility. /// -/// ```ignore -/// // Declares an extern function named custom_gdnative_terminate instead of -/// // godot_gdnative_terminate. -/// godot_gdnative_terminate!(my_shutdown_callback as custom_gdnative_terminate); -/// ``` -/// -/// Overriding the default entry point names can be useful if several gdnative -/// libraries are linked statically to avoid name clashes. +/// Use the new [`#[gdnative::init::callbacks]`][crate::init::callbacks] attribute macro and, +/// [`GDNativeCallbacks`][crate::init::GDNativeCallbacks] trait instead. #[macro_export] +#[deprecated = "use the #[gdnative::init::callbacks] attribute macro instead"] macro_rules! godot_gdnative_terminate { - () => { - fn godot_gdnative_terminate_empty(_term_info: &$crate::init::TerminateInfo) {} - $crate::init::godot_gdnative_terminate!(godot_gdnative_terminate_empty); - }; + () => {}; ($callback:ident) => { - $crate::init::godot_gdnative_terminate!($callback as godot_gdnative_terminate); + ::std::compile_error!("this syntax is no longer supported. use the #[gdnative::init::callbacks] attribute macro instead"); }; (_ as $fn_name:ident) => { - fn godot_gdnative_terminate_empty(_term_info: &$crate::init::TerminateInfo) {} - $crate::init::godot_gdnative_terminate!(godot_gdnative_terminate_empty as $fn_name); + ::std::compile_error!("this syntax is no longer supported. use the #[gdnative::init::callbacks] attribute macro with a prefix instead"); }; ($callback:ident as $fn_name:ident) => { - #[no_mangle] - #[doc(hidden)] - #[allow(unused_unsafe)] - pub unsafe extern "C" fn $fn_name( - options: *mut $crate::sys::godot_gdnative_terminate_options, - ) { - if !$crate::private::is_api_bound() { - return; - } - - $crate::private::report_panics("gdnative_terminate", || { - let term_info = $crate::init::TerminateInfo::new(options); - $callback(&term_info) - }); - - $crate::private::cleanup_internal_state(); - } - }; -} - -/// Declare all the API endpoints necessary to initialize a NativeScript library. -/// -/// `godot_init!(init)` is a shorthand for: -/// -/// ```ignore -/// godot_gdnative_init!(); -/// godot_nativescript_init!(init); -/// godot_gdnative_terminate!(); -/// ``` -/// -/// This declares three extern functions, named `godot_gdnative_init`, -/// `godot_nativescript_init`, and `godot_gdnative_terminate`. If you need different prefixes -/// to avoid name clashes when multiple GDNative libraries are linked statically, please use -/// the respective macros directly. -#[macro_export] -macro_rules! godot_init { - ($callback:ident) => { - $crate::init::godot_gdnative_init!(); - $crate::init::godot_nativescript_init!($callback); - $crate::init::godot_gdnative_terminate!(); + ::std::compile_error!("this syntax is no longer supported. use the #[gdnative::init::callbacks] attribute macro with a prefix instead"); }; } diff --git a/gdnative-core/src/init/mod.rs b/gdnative-core/src/init/mod.rs index 5d03d74e4..2b4d08f9a 100644 --- a/gdnative-core/src/init/mod.rs +++ b/gdnative-core/src/init/mod.rs @@ -1,49 +1,179 @@ //! Global initialization and termination of the library. //! //! This module provides all the plumbing required for global initialization and shutdown of godot-rust. -//! -//! ## Init and exit hooks -//! -//! Three endpoints are automatically invoked by the engine during startup and shutdown: -//! -//! * [`godot_gdnative_init`], -//! * [`godot_nativescript_init`], -//! * [`godot_gdnative_terminate`], -//! -//! All three must be present. To quickly define all three endpoints using the default names, -//! use [`godot_init`]. -//! -//! ## Registering script classes -//! -//! [`InitHandle`] is the registry of all your exported symbols. -//! To register script classes, call [`InitHandle::add_class()`] or [`InitHandle::add_tool_class()`] -//! in your `godot_nativescript_init` or `godot_init` callback: -//! -//! ```no_run -//! use gdnative::prelude::*; -//! -//! #[derive(NativeClass)] -//! # #[no_constructor] -//! struct HelloWorld { /* ... */ } -//! -//! #[methods] -//! impl HelloWorld { /* ... */ } -//! -//! fn init(handle: InitHandle) { -//! handle.add_class::(); -//! } -//! -//! godot_init!(init); -//! ``` mod info; mod init_handle; mod macros; +mod terminate_handle; pub mod diagnostics; +/// Internal low-level API for use by macros and generated bindings. Not a part of the public API. +#[doc(hidden)] +pub mod private; + pub use info::*; pub use init_handle::*; +pub use terminate_handle::*; + +/// Trait for declaring library-level GDNative callbacks. See module-level docs for examples. +/// +/// Each end-user library must contain one and exactly one implementation of this trait. +/// It must be annotated with the [`#[gdnative::init::callbacks]`][callbacks] +/// proc-macro attribute. +/// +/// The most commonly useful callback to implement is [`Self::nativescript_init`], which allows +/// the implementor to manually register [`NativeClass`][crate::export::NativeClass] types so +/// they can be used from Godot. +/// +/// An alternative to manual registration is the `inventory` feature, which automatically +/// registers derived [`NativeClass`][crate::export::NativeClass] on +/// [supported platforms][inventory-support], which at the time of writing includes every platform +/// that Godot officially supports except WASM. +/// +/// For all callbacks in this trait, it's guaranteed that the Godot API is available when called. +/// +/// ## Example +/// +/// With manual registration: +/// +/// ```no_run +/// use gdnative::prelude::*; +/// +/// #[derive(NativeClass)] +/// # #[no_constructor] +/// struct HelloWorld { /* ... */ } +/// +/// #[methods] +/// impl HelloWorld { /* ... */ } +/// +/// struct MyLibrary; +/// +/// #[gdnative::init::callbacks] +/// impl GDNativeCallbacks for MyLibrary { +/// fn nativescript_init(handle: InitHandle) { +/// handle.add_class::(); +/// } +/// } +/// ``` +/// +/// With automatic registration: +/// +/// ```no_run +/// use gdnative::prelude::*; +/// +/// #[derive(NativeClass)] +/// # #[no_constructor] +/// struct HelloWorld { /* ... */ } +/// +/// #[methods] +/// impl HelloWorld { /* ... */ } +/// +/// struct MyLibrary; +/// +/// #[gdnative::init::callbacks] +/// impl GDNativeCallbacks for MyLibrary {} +/// ``` +/// +/// [inventory-support]: https://github.com/dtolnay/inventory#how-it-works +/// ``` +pub trait GDNativeCallbacks: private::TheGDNativeCallbacksAttributeIsRequired { + /// Callback invoked on startup, before any other callbacks. + /// + /// At the time [`Self::gdnative_init`] is called, it is guaranteed that: + /// + /// - No API callbacks have been invoked before. + #[inline] + #[allow(unused)] + fn gdnative_init(info: InitializeInfo) {} + + /// Callback invoked on shutdown, after all other callbacks. + /// + /// At the time [`Self::gdnative_terminate`] is called, it is guaranteed that: + /// + /// - No API callbacks will be invoked after. + #[inline] + #[allow(unused)] + fn gdnative_terminate(info: TerminateInfo) {} + + /// Callback invoked after startup, immediately after `gdnative_init`, if the `singleton` + /// option is set for the `GDNativeLibrary` resource. + /// + /// **Attention:** This is **NOT** what you're looking for! `gdnative_singleton` has nothing + /// to do with exposing "singleton" objects or static-looking methods to GDScript. Instead, + /// create a [`NativeClass`][crate::export::NativeClass] inheriting `Node` and make that an + /// auto-load in your project settings. See [the FAQ][auto-load-faq] for an example. + /// + /// Despite the confusing name, what the `singleton` option does is to mark the GDNative + /// library for automatic initialization at the startup of the application before any game + /// content and scripts are loaded, which might be useful in some use cases. The extra + /// callback itself doesn't have any special qualities. + /// + /// At the time [`Self::gdnative_singleton`] is called, it is guaranteed that: + /// + /// - [`Self::gdnative_init`] has been invoked exactly once, immediately before. + /// - No other API callbacks have been invoked. + /// + /// It is NOT guaranteed that: + /// + /// - There are any practical uses to this callback. **You do not need this to create a + /// singleton object, nor does it help you.** Use an [auto-load singleton][auto-load-faq] + /// instead. + /// + /// [auto-load-faq]: https://godot-rust.github.io/book/gdnative/faq/code.html#can-i-implement-static-methods-in-gdnative + #[inline] + fn gdnative_singleton() {} + + /// Callback invoked after startup, before `NativeScript`s are registered. + /// + /// At the time [`Self::nativescript_init`] is called, it is guaranteed that: + /// + /// - [`Self::gdnative_init`] has been invoked exactly once. + /// + /// It is NOT guaranteed that: + /// + /// - This is immediately invoked after [`Self::gdnative_init`]. + /// - [`Self::nativescript_init`] has not been invoked before. + #[inline] + #[allow(unused)] + fn nativescript_init(handle: InitHandle) {} + + /// Callback invoked during engine cleanup if NativeScript has been used and the library + /// is still loaded. + /// + /// Despite the naming, this is not guaranteed to be paired with [`Self::nativescript_init`]. + /// Godot may terminate (and later re-initialize) the library without calling this function + /// due to hot-reloading. + /// + /// While a handle argument is provided by the engine, its purpose is currently unclear. + /// + /// At the time [`Self::nativescript_terminate`] is called, it is guaranteed that: + /// + /// - [`Self::gdnative_init`] has been invoked exactly once. + /// - [`Self::nativescript_init`] has been invoked at least once. + /// + /// It is NOT guaranteed that: + /// + /// - This will always be called before [`Self::gdnative_terminate`]. + #[inline] + #[allow(unused)] + fn nativescript_terminate(handle: TerminateHandle) {} + + /// Callback invoked every frame if any NativeScripts are being used. + #[inline] + fn nativescript_frame() {} + + /// Callback invoked before a thread managed by Godot other than the main thread is entered, + /// if any NativeScripts are being used. + #[inline] + fn nativescript_thread_enter() {} + + /// Callback invoked after a thread managed by Godot other than the main thread has been + /// exited from, if any NativeScripts are being used. + #[inline] + fn nativescript_thread_exit() {} +} bitflags::bitflags! { /// Initialization level used to distinguish the source of init actions, such as class registration. @@ -73,6 +203,10 @@ pub fn auto_register(_init_handle: InitHandle) { // Nothing to do here. } +#[allow(deprecated)] pub use crate::{ godot_gdnative_init, godot_gdnative_terminate, godot_init, godot_nativescript_init, }; + +#[doc(inline)] +pub use gdnative_derive::callbacks; diff --git a/gdnative-core/src/init/private.rs b/gdnative-core/src/init/private.rs new file mode 100644 index 000000000..0fcc2cd04 --- /dev/null +++ b/gdnative-core/src/init/private.rs @@ -0,0 +1,82 @@ +use super::{GDNativeCallbacks, TerminateHandle}; + +pub trait TheGDNativeCallbacksAttributeIsRequired {} + +#[inline] +pub unsafe fn gdnative_init( + options: *mut crate::sys::godot_gdnative_init_options, +) { + if !crate::private::bind_api(options) { + // Can't use godot_error here because the API is not bound. + // Init errors should be reported by bind_api. + return; + } + + crate::init::diagnostics::godot_version_mismatch(); + + crate::private::report_panics("gdnative_init", || { + let init_info = crate::init::InitializeInfo::new(options); + C::gdnative_init(init_info) + }); +} + +#[inline] +pub unsafe fn gdnative_terminate( + options: *mut crate::sys::godot_gdnative_terminate_options, +) { + if !crate::private::is_api_bound() { + return; + } + + crate::private::report_panics("gdnative_terminate", || { + let term_info = crate::init::TerminateInfo::new(options); + C::gdnative_terminate(term_info) + }); + + crate::private::cleanup_internal_state(); +} + +#[inline] +pub unsafe fn gdnative_singleton() { + C::gdnative_singleton(); +} + +#[inline] +pub unsafe fn nativescript_init(handle: *mut libc::c_void) { + if !crate::private::is_api_bound() { + return; + } + + crate::private::report_panics("nativescript_init", || { + crate::init::auto_register(crate::init::InitHandle::new( + handle, + crate::init::InitLevel::AUTO, + )); + C::nativescript_init(crate::init::InitHandle::new( + handle, + crate::init::InitLevel::USER, + )); + + crate::init::diagnostics::missing_suggested_diagnostics(); + }); +} + +#[inline] +pub unsafe fn nativescript_terminate(handle: *mut libc::c_void) { + C::nativescript_terminate(TerminateHandle::new(handle)); +} + +#[inline] +pub unsafe fn nativescript_frame() { + C::nativescript_frame(); +} + +#[inline] +pub unsafe fn nativescript_thread_enter() { + C::nativescript_thread_enter(); +} + +#[inline] +pub unsafe fn nativescript_thread_exit() { + C::nativescript_thread_exit(); +} diff --git a/gdnative-core/src/init/terminate_handle.rs b/gdnative-core/src/init/terminate_handle.rs new file mode 100644 index 000000000..8213b3d94 --- /dev/null +++ b/gdnative-core/src/init/terminate_handle.rs @@ -0,0 +1,13 @@ +/// A handle passed from the engine during NativeScript termination. It's purpose is currently +/// unclear. +pub struct TerminateHandle { + _handle: *mut libc::c_void, +} + +impl TerminateHandle { + #[doc(hidden)] + #[inline] + pub unsafe fn new(handle: *mut libc::c_void) -> Self { + TerminateHandle { _handle: handle } + } +} diff --git a/gdnative-derive/src/init.rs b/gdnative-derive/src/init.rs new file mode 100644 index 000000000..ccc2b9848 --- /dev/null +++ b/gdnative-derive/src/init.rs @@ -0,0 +1,130 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use syn::{spanned::Spanned, AttributeArgs, ItemImpl, Lit, Meta, NestedMeta}; + +pub(crate) fn derive_callbacks( + args: AttributeArgs, + item_impl: ItemImpl, +) -> Result { + let mut prefix = None; + for arg in args { + match arg { + NestedMeta::Meta(Meta::NameValue(meta)) if meta.path.is_ident("prefix") => { + let span = meta.span(); + if prefix.replace(meta.lit).is_some() { + return Err(syn::Error::new(span, "duplicate argument")); + } + } + NestedMeta::Meta(meta) if meta.path().is_ident("prefix") => { + return Err(syn::Error::new(meta.span(), "expecting a name-value pair")); + } + _ => { + return Err(syn::Error::new(arg.span(), "unknown argument")); + } + } + } + + let prefix = match prefix { + Some(Lit::Str(s)) => s.value(), + Some(lit) => return Err(syn::Error::new(lit.span(), "expecting string literal")), + None => "godot_".into(), + }; + + let derived = crate::automatically_derived(); + let gdnative_core = crate::crate_gdnative_core(); + let self_ty = &item_impl.self_ty; + + if !item_impl.generics.params.is_empty() { + return Err(syn::Error::new(self_ty.span(), "generics are unsupported")); + } + + let gdnative_init = Ident::new(&format!("{prefix}gdnative_init"), Span::call_site()); + let gdnative_terminate = Ident::new(&format!("{prefix}gdnative_terminate"), Span::call_site()); + let gdnative_singleton = Ident::new(&format!("{prefix}gdnative_singleton"), Span::call_site()); + let nativescript_init = Ident::new(&format!("{prefix}nativescript_init"), Span::call_site()); + let nativescript_terminate = Ident::new( + &format!("{prefix}nativescript_terminate"), + Span::call_site(), + ); + let nativescript_frame = Ident::new(&format!("{prefix}nativescript_frame"), Span::call_site()); + let nativescript_thread_enter = Ident::new( + &format!("{prefix}nativescript_thread_enter"), + Span::call_site(), + ); + let nativescript_thread_exit = Ident::new( + &format!("{prefix}nativescript_thread_exit"), + Span::call_site(), + ); + + Ok(quote! { + #item_impl + + #derived + const _: () = { + impl #gdnative_core::init::private::TheGDNativeCallbacksAttributeIsRequired for #self_ty {} + + #[no_mangle] + #[doc(hidden)] + #[allow(unused_unsafe)] + pub unsafe extern "C" fn #gdnative_init( + options: *mut #gdnative_core::sys::godot_gdnative_init_options, + ) { + #gdnative_core::init::private::gdnative_init::<#self_ty>(options); + } + + #[no_mangle] + #[doc(hidden)] + #[allow(unused_unsafe)] + pub unsafe extern "C" fn #gdnative_terminate( + options: *mut #gdnative_core::sys::godot_gdnative_terminate_options, + ) { + #gdnative_core::init::private::gdnative_terminate::<#self_ty>(options); + } + + #[no_mangle] + #[doc(hidden)] + #[allow(unused_unsafe)] + pub unsafe extern "C" fn #gdnative_singleton() { + #gdnative_core::init::private::gdnative_singleton::<#self_ty>(); + } + + #[no_mangle] + #[doc(hidden)] + #[allow(unused_unsafe)] + pub unsafe extern "C" fn #nativescript_init( + handle: *mut #gdnative_core::libc::c_void, + ) { + #gdnative_core::init::private::nativescript_init::<#self_ty>(handle); + } + + #[no_mangle] + #[doc(hidden)] + #[allow(unused_unsafe)] + pub unsafe extern "C" fn #nativescript_terminate( + handle: *mut #gdnative_core::libc::c_void, + ) { + #gdnative_core::init::private::nativescript_terminate::<#self_ty>(handle); + } + + #[no_mangle] + #[doc(hidden)] + #[allow(unused_unsafe)] + pub unsafe extern "C" fn #nativescript_frame() { + #gdnative_core::init::private::nativescript_frame::<#self_ty>(); + } + + #[no_mangle] + #[doc(hidden)] + #[allow(unused_unsafe)] + pub unsafe extern "C" fn #nativescript_thread_enter() { + #gdnative_core::init::private::nativescript_thread_enter::<#self_ty>(); + } + + #[no_mangle] + #[doc(hidden)] + #[allow(unused_unsafe)] + pub unsafe extern "C" fn #nativescript_thread_exit() { + #gdnative_core::init::private::nativescript_thread_exit::<#self_ty>(); + } + }; + }) +} diff --git a/gdnative-derive/src/lib.rs b/gdnative-derive/src/lib.rs index 1788fa044..3ea22ee2d 100644 --- a/gdnative-derive/src/lib.rs +++ b/gdnative-derive/src/lib.rs @@ -10,6 +10,7 @@ use proc_macro2::TokenStream as TokenStream2; use quote::ToTokens; use syn::{parse::Parser, AttributeArgs, DeriveInput, ItemFn, ItemImpl, ItemType}; +mod init; mod methods; mod native_script; mod profiled; @@ -612,6 +613,28 @@ pub fn derive_from_varargs(input: TokenStream) -> TokenStream { } } +/// Declares the library-level GDNative callbacks. See [`gdnative::init::GDNativeCallbacks`]. +/// +/// ## Arguments +/// +/// The attribute takes an optional `prefix` argument, which can be used to change the prefixes of +/// the symbols generated by this macro. This is used by Godot on platforms where dynamic linking +/// is unavailable. The value of this argument should match the +/// [`GDNativeLibrary::symbol_prefix`][symbol-prefix] property for the library on the Godot side, +/// defaulting to `godot_`. +/// +/// [symbol-prefix]: https://docs.godotengine.org/en/stable/classes/class_gdnativelibrary.html +#[proc_macro_attribute] +pub fn callbacks(meta: TokenStream, input: TokenStream) -> TokenStream { + let args = parse_macro_input!(meta as AttributeArgs); + let item_impl = parse_macro_input!(input as ItemImpl); + + match init::derive_callbacks(args, item_impl) { + Ok(tokens) => tokens.into(), + Err(err) => err.to_compile_error().into(), + } +} + /// Convenience macro to wrap an object's method into a `Method` implementor /// that can be passed to the engine when registering a class. #[proc_macro] diff --git a/gdnative/src/prelude.rs b/gdnative/src/prelude.rs index ddf81c241..d50db8196 100644 --- a/gdnative/src/prelude.rs +++ b/gdnative/src/prelude.rs @@ -16,14 +16,18 @@ pub use gdnative_core::export::{ ClassBuilder, ExportInfo, Method, MethodBuilder, NativeClass, NativeClassMethods, Property, PropertyUsage, SignalBuilder, SignalParam, }; -pub use gdnative_core::init::InitHandle; +pub use gdnative_core::init::{GDNativeCallbacks, InitHandle}; pub use gdnative_core::object::{ memory::{ManuallyManaged, RefCounted}, ownership::{Shared, ThreadLocal, Unique}, AsArg, GodotObject, Instance, Instanciable, NewRef, Null, QueueFree, Ref, SubClass, TInstance, TRef, }; -pub use gdnative_core::{godot_dbg, godot_error, godot_init, godot_print, godot_warn}; +pub use gdnative_core::{godot_dbg, godot_error, godot_print, godot_warn}; +#[allow(deprecated)] +pub use gdnative_core::{ + godot_gdnative_init, godot_gdnative_terminate, godot_init, godot_nativescript_init, +}; pub use gdnative_derive::*; /// User-data attributes from [`export::user_data`][crate::export::user_data] module. diff --git a/test/src/lib.rs b/test/src/lib.rs index a04b4b48f..1f923b4e7 100644 --- a/test/src/lib.rs +++ b/test/src/lib.rs @@ -240,16 +240,25 @@ godot_itest! { test_from_instance_id { assert!(unsafe { Reference::try_from_instance_id(instance_id).is_none() }); }} -#[cfg(not(feature = "no-manual-register"))] -fn init(handle: InitHandle) { - handle.add_class::(); - handle.add_class::(); - delegate_init(handle); -} +struct TestLibrary; + +#[gdnative::init::callbacks] +impl GDNativeCallbacks for TestLibrary { + #[cfg(not(feature = "no-manual-register"))] + fn nativescript_init(handle: InitHandle) { + handle.add_class::(); + handle.add_class::(); + delegate_init(handle); + } + + #[cfg(feature = "no-manual-register")] + fn nativescript_init(handle: InitHandle) { + delegate_init(handle); + } -#[cfg(feature = "no-manual-register")] -fn init(handle: InitHandle) { - delegate_init(handle); + fn gdnative_terminate(_info: gdnative::init::TerminateInfo) { + gdnative::tasks::terminate_runtime(); + } } fn delegate_init(handle: InitHandle) { @@ -267,11 +276,3 @@ fn delegate_init(handle: InitHandle) { test_variant_call_args::register(handle); test_variant_ops::register(handle); } - -fn terminate(_term_info: &gdnative::init::TerminateInfo) { - gdnative::tasks::terminate_runtime(); -} - -gdnative::init::godot_gdnative_init!(); -gdnative::init::godot_nativescript_init!(init); -gdnative::init::godot_gdnative_terminate!(terminate);