From 300abf26b5556305b66e319df9669398e0a5a76a Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Thu, 22 Feb 2024 19:53:20 +0100 Subject: [PATCH 1/2] Support class/interface-specific docs in codegen --- godot-codegen/src/generator/docs.rs | 18 +++++++++++---- .../src/generator/functions_common.rs | 5 ++++ godot-codegen/src/generator/virtual_traits.rs | 6 ++--- .../src/special_cases/special_cases.rs | 23 +++++++++++++++++++ 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/godot-codegen/src/generator/docs.rs b/godot-codegen/src/generator/docs.rs index a017ecda1..d642628f2 100644 --- a/godot-codegen/src/generator/docs.rs +++ b/godot-codegen/src/generator/docs.rs @@ -9,8 +9,8 @@ //! //! Single module for documentation, rather than having it in each symbol-specific file, so it's easier to keep docs consistent. -use crate::models::domain::ModName; -use crate::models::domain::TyName; +use crate::models::domain::{ModName, TyName}; +use crate::special_cases; use proc_macro2::Ident; pub fn make_class_doc( @@ -49,6 +49,10 @@ pub fn make_class_doc( let trait_name = class_name.virtual_trait_name(); + let notes = special_cases::get_class_extra_docs(class_name) + .map(|notes| format!("# Specific notes for this class\n\n{}", notes)) + .unwrap_or(String::new()); + format!( "Godot class `{godot_ty}.`\n\n\ \ @@ -59,11 +63,11 @@ pub fn make_class_doc( * [`{trait_name}`][crate::engine::{trait_name}]: virtual methods\n\ {notify_line}\ \n\n\ - See also [Godot docs for `{godot_ty}`]({online_link}).\n\n", + See also [Godot docs for `{godot_ty}`]({online_link}).\n\n{notes}", ) } -pub fn make_virtual_trait_doc(class_name: &TyName) -> String { +pub fn make_virtual_trait_doc(trait_name_str: &str, class_name: &TyName) -> String { let TyName { rust_ty, godot_ty } = class_name; let online_link = format!( @@ -71,12 +75,16 @@ pub fn make_virtual_trait_doc(class_name: &TyName) -> String { godot_ty.to_ascii_lowercase() ); + let notes = special_cases::get_interface_extra_docs(trait_name_str) + .map(|notes| format!("# Specific notes for this interface\n\n{}", notes)) + .unwrap_or(String::new()); + format!( "Virtual methods for class [`{rust_ty}`][crate::engine::{rust_ty}].\ \n\n\ These methods represent constructors (`init`) or callbacks invoked by the engine.\ \n\n\ - See also [Godot docs for `{godot_ty}` methods]({online_link}).\n\n" + See also [Godot docs for `{godot_ty}` methods]({online_link}).\n\n{notes}" ) } diff --git a/godot-codegen/src/generator/functions_common.rs b/godot-codegen/src/generator/functions_common.rs index 055c5164a..dade4d776 100644 --- a/godot-codegen/src/generator/functions_common.rs +++ b/godot-codegen/src/generator/functions_common.rs @@ -96,6 +96,11 @@ pub fn make_function_definition( make_vis(sig.is_private()) }; + // Functions are marked unsafe as soon as raw pointers are involved, irrespectively of whether they appear in parameter or return type + // position. In cases of virtual functions called by Godot, a returned pointer must be valid and of the expected type. It might be possible + // to only use `unsafe` for pointers in parameters (for outbound calls), and in return values (for virtual calls). Or technically more + // correct, make the entire trait unsafe as soon as one function can return pointers, but that's very unergonomic and non-local. + // Thus, let's keep things simple and more conservative. let (maybe_unsafe, safety_doc) = if let Some(safety_doc) = safety_doc { (quote! { unsafe }, safety_doc) } else if function_uses_pointers(sig) { diff --git a/godot-codegen/src/generator/virtual_traits.rs b/godot-codegen/src/generator/virtual_traits.rs index 3db45cc35..b42e09fc2 100644 --- a/godot-codegen/src/generator/virtual_traits.rs +++ b/godot-codegen/src/generator/virtual_traits.rs @@ -17,16 +17,16 @@ use quote::quote; pub fn make_virtual_methods_trait( class: &Class, all_base_names: &[TyName], - trait_name: &str, + trait_name_str: &str, notification_enum_name: &Ident, view: &ApiView, ) -> TokenStream { - let trait_name = ident(trait_name); + let trait_name = ident(trait_name_str); let virtual_method_fns = make_all_virtual_methods(class, all_base_names, view); let special_virtual_methods = special_virtual_methods(notification_enum_name); - let trait_doc = docs::make_virtual_trait_doc(class.name()); + let trait_doc = docs::make_virtual_trait_doc(trait_name_str, class.name()); quote! { #[doc = #trait_doc] diff --git a/godot-codegen/src/special_cases/special_cases.rs b/godot-codegen/src/special_cases/special_cases.rs index 575ecb909..92370eb66 100644 --- a/godot-codegen/src/special_cases/special_cases.rs +++ b/godot-codegen/src/special_cases/special_cases.rs @@ -285,3 +285,26 @@ pub fn maybe_rename_virtual_method(rust_method_name: &str) -> &str { _ => rust_method_name, } } + +pub fn get_class_extra_docs(class_name: &TyName) -> Option<&'static str> { + match class_name.godot_ty.as_str() { + "FileAccess" => { + Some("The gdext library provides a higher-level abstraction, which should be preferred: [`GFile`][crate::engine::GFile].") + } + "ScriptExtension" => { + Some("Use this in combination with [`ScriptInstance`][crate::engine::ScriptInstance].") + } + + _ => None, + } +} + +pub fn get_interface_extra_docs(trait_name: &str) -> Option<&'static str> { + match trait_name { + "IScriptExtension" => { + Some("Use this in combination with [`ScriptInstance`][crate::engine::ScriptInstance].") + } + + _ => None, + } +} From 2fc4953201d97f9bdcb81b07a00e4af4746af064 Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Thu, 22 Feb 2024 20:29:23 +0100 Subject: [PATCH 2/2] Improve docs in ScriptInstance Also rename {get|set} to {get|set}_property, consistent with IObject virtual methods. --- godot-codegen/src/generator/docs.rs | 4 +- godot-core/src/engine/script_instance.rs | 222 ++++++++++++------ .../script/script_instance_tests.rs | 4 +- 3 files changed, 154 insertions(+), 76 deletions(-) diff --git a/godot-codegen/src/generator/docs.rs b/godot-codegen/src/generator/docs.rs index d642628f2..763ea3385 100644 --- a/godot-codegen/src/generator/docs.rs +++ b/godot-codegen/src/generator/docs.rs @@ -51,7 +51,7 @@ pub fn make_class_doc( let notes = special_cases::get_class_extra_docs(class_name) .map(|notes| format!("# Specific notes for this class\n\n{}", notes)) - .unwrap_or(String::new()); + .unwrap_or_default(); format!( "Godot class `{godot_ty}.`\n\n\ @@ -77,7 +77,7 @@ pub fn make_virtual_trait_doc(trait_name_str: &str, class_name: &TyName) -> Stri let notes = special_cases::get_interface_extra_docs(trait_name_str) .map(|notes| format!("# Specific notes for this interface\n\n{}", notes)) - .unwrap_or(String::new()); + .unwrap_or_default(); format!( "Virtual methods for class [`{rust_ty}`][crate::engine::{rust_ty}].\ diff --git a/godot-core/src/engine/script_instance.rs b/godot-core/src/engine/script_instance.rs index 203b04b04..8bbd38408 100644 --- a/godot-core/src/engine/script_instance.rs +++ b/godot-core/src/engine/script_instance.rs @@ -15,18 +15,67 @@ use crate::sys; use super::{Script, ScriptLanguage}; -/// Interface for Godot's `GDExtensionScriptInstance`, which cannot be directly constructed by an extension. Instead a type that implements this -/// trait has to be passed to the [`create_script_instance`] function, which returns a [`sys::GDExtensionScriptInstancePtr`] pointer. This pointer -/// can then be returned from [`IScriptExtension::instance_create`](crate::engine::IScriptExtension::instance_create). +/// Implement custom scripts that can be attached to objects in Godot. +/// +/// To use script instances, implement this trait for your own type. +/// +/// You can use the [`create_script_instance()`] function to create a low-level pointer to your script instance. +/// This pointer should then be returned from [`IScriptExtension::instance_create()`](crate::engine::IScriptExtension::instance_create). +/// +/// # Example +/// +/// ```no_run +/// # use godot::prelude::*; +/// # use godot::engine::{IScriptExtension, Script, ScriptExtension}; +/// # trait ScriptInstance {} // trick 17 to avoid listing all the methods. Needs also a method. +/// # fn create_script_instance(_: MyScriptInstance) -> *mut std::ffi::c_void { std::ptr::null_mut() } +/// // 1) Define the script. +/// #[derive(GodotClass)] +/// #[class(init, base=ScriptExtension)] +/// struct MyScript { +/// base: Base, +/// // ... other fields +/// } +/// +/// // 2) Define the script _instance_, and implement the trait for it. +/// struct MyScriptInstance; +/// impl MyScriptInstance { +/// fn from_gd(script: Gd