diff --git a/godot-codegen/src/codegen_special_cases.rs b/godot-codegen/src/codegen_special_cases.rs index 045f821e8..530e29e6c 100644 --- a/godot-codegen/src/codegen_special_cases.rs +++ b/godot-codegen/src/codegen_special_cases.rs @@ -164,6 +164,9 @@ const SELECTED_CLASSES: &[&str] = &[ "ResourceLoader", "RigidBody2D", "SceneTree", + "Script", + "ScriptExtension", + "ScriptLanguage", "Sprite2D", "SpriteFrames", "TextServer", diff --git a/godot-core/src/builtin/meta/mod.rs b/godot-core/src/builtin/meta/mod.rs index c858a8707..cf931a8b3 100644 --- a/godot-core/src/builtin/meta/mod.rs +++ b/godot-core/src/builtin/meta/mod.rs @@ -275,3 +275,63 @@ impl PropertyInfo { } } } + +#[derive(Debug)] +pub struct MethodInfo { + pub id: i32, + pub method_name: StringName, + pub class_name: ClassName, + pub return_type: PropertyInfo, + pub arguments: Vec, + pub default_arguments: Vec, + pub flags: global::MethodFlags, +} + +impl MethodInfo { + /// Converts to the FFI type. Keep this object allocated while using that! + /// + /// The struct returned by this function contains pointers into the fields of `self`. `self` should therefore not be dropped while the + /// [`sys::GDExtensionMethodInfo`] is still in use. + /// + /// This function also leaks memory that has to be cleaned up by the caller once it is no longer used. Specifically the `arguments` and + /// `default_arguments` vectors have to be reconstructed from the pointer and length and then dropped/freed. + /// + /// Each vector can be reconstructed with `Vec::from_raw_parts` since the pointers were created with `Vec::into_boxed_slice`, which + /// guarantees that the vector capacity and length are equal. + pub fn method_sys(&self) -> sys::GDExtensionMethodInfo { + use crate::obj::EngineBitfield as _; + + let argument_count = self.arguments.len() as u32; + let argument_vec = self + .arguments + .iter() + .map(|arg| arg.property_sys()) + .collect::>() + .into_boxed_slice(); + + // SAFETY: dereferencing the new box pointer is fine as it is guaranteed to not be null + let arguments = unsafe { (*Box::into_raw(argument_vec)).as_mut_ptr() }; + + let default_argument_count = self.default_arguments.len() as u32; + let default_argument_vec = self + .default_arguments + .iter() + .map(|arg| arg.var_sys()) + .collect::>() + .into_boxed_slice(); + + // SAFETY: dereferencing the new box pointer is fine as it is guaranteed to not be null + let default_arguments = unsafe { (*Box::into_raw(default_argument_vec)).as_mut_ptr() }; + + sys::GDExtensionMethodInfo { + id: self.id, + name: self.method_name.string_sys(), + return_value: self.return_type.property_sys(), + argument_count, + arguments, + default_argument_count, + default_arguments, + flags: u32::try_from(self.flags.ord()).expect("flags should be valid"), + } + } +} diff --git a/godot-core/src/builtin/variant/mod.rs b/godot-core/src/builtin/variant/mod.rs index a400b00fc..b3033acde 100644 --- a/godot-core/src/builtin/variant/mod.rs +++ b/godot-core/src/builtin/variant/mod.rs @@ -236,7 +236,6 @@ impl Variant { /// # Safety /// `variant_ptr_array` must be a valid pointer to an array of `length` variant pointers. /// The caller is responsible of keeping the backing storage alive while the unbounded references exist. - #[cfg(since_api = "4.2")] // unused before pub(crate) unsafe fn unbounded_refs_from_sys<'a>( variant_ptr_array: *const sys::GDExtensionConstVariantPtr, length: usize, @@ -253,6 +252,14 @@ impl Variant { pub(crate) fn ptr_from_sys_mut(variant_ptr: sys::GDExtensionVariantPtr) -> *mut Variant { variant_ptr as *mut Variant } + + /// Move `self` into a system pointer. This transfers ownership and thus does not call the destructor. + /// + /// # Safety + /// `dst` must be a pointer to a [`Variant`] which is suitable for ffi with Godot. + pub(crate) unsafe fn move_var_ptr(self, dst: sys::GDExtensionVariantPtr) { + self.move_return_ptr(dst as *mut _, sys::PtrcallType::Standard); + } } // SAFETY: diff --git a/godot-core/src/engine/mod.rs b/godot-core/src/engine/mod.rs index 4ad3a034c..bf8ea05d4 100644 --- a/godot-core/src/engine/mod.rs +++ b/godot-core/src/engine/mod.rs @@ -19,8 +19,10 @@ pub use crate::gen::utilities; use crate::sys; mod gfile; +mod script_instance; pub use gfile::{GFile, NotUniqueError}; +pub use script_instance::{create_script_instance, ScriptInstance}; /// Support for Godot _native structures_. /// diff --git a/godot-core/src/engine/script_instance.rs b/godot-core/src/engine/script_instance.rs new file mode 100644 index 000000000..203b04b04 --- /dev/null +++ b/godot-core/src/engine/script_instance.rs @@ -0,0 +1,878 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +use std::cell::RefCell; +use std::ffi::c_void; + +use crate::builtin::meta::{MethodInfo, PropertyInfo}; +use crate::builtin::{GString, StringName, Variant, VariantType}; +use crate::obj::Gd; +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). +pub trait ScriptInstance { + /// Name of the new class the script implements. + fn class_name(&self) -> GString; + + /// Property setter for Godots virtual dispatch system. The engine will call this function when it wants to change a property on the script. + fn set(&mut self, name: StringName, value: &Variant) -> bool; + + /// Property getter for Godots virtual dispatch system. The engine will call this function when it wants to read a property on the script. + fn get(&self, name: StringName) -> Option; + + /// A list of all the properties a script exposes to the engine. + fn get_property_list(&self) -> &[PropertyInfo]; + + /// A list of all the methods a script exposes to the engine. + fn get_method_list(&self) -> &[MethodInfo]; + + /// Method invoker for Godots virtual dispatch system. The engine will call this function when it wants to call a method on the script. + /// + /// All method calls are taking a mutable reference of the script instance, as the engine does not differentiate between immutable and + /// mutable method calls like rust. + /// + /// It's important that the script does not cause a second call to this function while executing a method call. This would result in a panic. + // TODO: map the sys::GDExtensionCallErrorType to some public API type. + fn call( + &mut self, + method: StringName, + args: &[&Variant], + ) -> Result; + + /// Identifies the script instance as a placeholder. If this function and + /// [IScriptExtension::is_placeholder_fallback_enabled](crate::engine::IScriptExtension::is_placeholder_fallback_enabled) return true, + /// Godot will call [`Self::property_set_fallback`] instead of [`Self::set`]. + fn is_placeholder(&self) -> bool; + + /// Validation function for the engine to verify if the script exposes a certain method. + fn has_method(&self, method: StringName) -> bool; + + /// Lets the engine get a reference to the script this instance was created for. + /// + /// This function has to return a reference, because Scripts are reference counted in Godot and it has to be guaranteed that the object is + /// not freed before the engine increased the reference count. (every time a `Gd` which contains a reference counted object is dropped the + /// reference count is decremented.) + fn get_script(&self) -> &Gd