Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 26 additions & 7 deletions godot-core/src/meta/class_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ impl ClassId {
/// We discourage calling this function from different places for the same `T`. But if you do so, `init_fn` must return the same string.
///
/// # Panics
/// If the string is not ASCII and the Godot version is older than 4.4. From Godot 4.4 onwards, class names can be Unicode.
/// If the string contains non-ASCII characters and the Godot version is older than 4.4. From Godot 4.4 onwards, class names can be Unicode;
/// See <https://github.com/godotengine/godot/pull/96501>.
pub fn new_cached<T: GodotClass>(init_fn: impl FnOnce() -> String) -> Self {
Self::new_cached_inner::<T>(init_fn)
}
Expand All @@ -94,14 +95,29 @@ impl ClassId {
cache.insert_class_id(Cow::Owned(name), Some(type_id), false)
}

/// Create a `ClassId` from a runtime string (for dynamic class names).
/// Create a `ClassId` from a class name only known at runtime.
///
/// Will reuse existing `ClassId` entries if the string is recognized.
// Deliberately not public.
pub(crate) fn new_dynamic(class_name: String) -> Self {
/// Unlike [`ClassId::new_cached()`], this doesn't require a static type parameter. Useful for classes defined outside Rust code, e.g. in
/// scripts.
///
/// Multiple calls with the same name return equal `ClassId` instances (but may need a lookup).
///
/// # Example
/// ```no_run
/// use godot::meta::ClassId;
///
/// let a = ClassId::new_dynamic("MyGDScriptClass");
/// let b = ClassId::new_dynamic("MyGDScriptClass");
/// assert_eq!(a, b);
/// ```
///
/// # Panics
/// If the string contains non-ASCII characters and the Godot version is older than 4.4. From Godot 4.4 onwards, class names can be Unicode;
/// See <https://github.com/godotengine/godot/pull/96501>.
pub fn new_dynamic(class_name: impl Into<CowStr>) -> Self {
let mut cache = CLASS_ID_CACHE.lock();

cache.insert_class_id(Cow::Owned(class_name), None, false)
cache.insert_class_id(class_name.into(), None, false)
}

// Test-only APIs.
Expand All @@ -117,7 +133,10 @@ impl ClassId {
Self::new_dynamic(class_name.to_string())
}

#[doc(hidden)]
/// Returns a `ClassId` representing "no class" (empty class name) for non-object property types.
///
/// This is used for properties that don't have an associated class, e.g. built-in types like `i32`, `GString`, `Vector3` etc.
/// When constructing a [`PropertyInfo`](crate::meta::PropertyInfo) for non-class types, you can use `ClassId::none()` for the `class_id` field.
pub fn none() -> Self {
// First element is always the empty string name.
Self { global_index: 0 }
Expand Down
88 changes: 82 additions & 6 deletions godot-core/src/meta/method_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,95 @@ use crate::global::MethodFlags;
use crate::meta::{ClassId, PropertyInfo};
use crate::sys;

/// Describes a method in Godot.
/// Describes a method's signature and metadata required by the Godot engine.
///
/// Abstraction of the low-level `sys::GDExtensionMethodInfo`.
// Currently used for ScriptInstance.
// TODO check overlap with (private) ClassMethodInfo.
/// Primarily used when implementing custom script instances via the [`ScriptInstance`][crate::obj::script::ScriptInstance] trait.
/// It contains metadata Godot needs to describe and call a method.
///
/// `MethodInfo` is a high-level abstraction over the low-level FFI type `sys::GDExtensionMethodInfo`.
///
/// See also [`PropertyInfo`] for describing individual method parameters and return types.
///
/// # Example
/// ```no_run
/// use godot::meta::{MethodInfo, PropertyInfo, PropertyHintInfo, ClassId};
/// use godot::builtin::{StringName, Variant, VariantType};
/// use godot::global::{MethodFlags, PropertyUsageFlags};
/// use godot::classes::Node2D;
/// use godot::obj::GodotClass; // Trait method ::class_id().
///
/// // Describe a Godot method (`World` is a GDScript class):
/// // func spawn_at(world: World, position: Vector2) -> Node2D.
/// let method = MethodInfo {
/// id: 0,
/// method_name: StringName::from("spawn_at"),
/// class_name: ClassId::none(),
/// return_type: PropertyInfo {
/// variant_type: VariantType::OBJECT,
/// class_id: Node2D::class_id(),
/// property_name: StringName::default(), // Return types use empty string.
/// hint_info: PropertyHintInfo::none(),
/// usage: PropertyUsageFlags::DEFAULT,
/// },
/// arguments: vec![
/// PropertyInfo {
/// variant_type: VariantType::OBJECT,
/// class_id: ClassId::new_dynamic("World"),
/// property_name: StringName::from("world"),
/// hint_info: PropertyHintInfo::none(),
/// usage: PropertyUsageFlags::DEFAULT,
/// },
/// PropertyInfo {
/// variant_type: VariantType::VECTOR2,
/// class_id: ClassId::none(),
/// property_name: StringName::from("position"),
/// hint_info: PropertyHintInfo::none(),
/// usage: PropertyUsageFlags::DEFAULT,
/// },
/// ],
/// default_arguments: vec![],
/// flags: MethodFlags::DEFAULT,
/// };
/// ```
#[derive(Clone, Debug)]
pub struct MethodInfo {
/// Unique identifier for the method within its class.
///
/// This ID can be used to distinguish between methods and is typically set by the implementation. For script instances,
/// this is often just a sequential index.
pub id: i32,

/// The name of the method, as it appears in Godot.
pub method_name: StringName,

/// The class this method belongs to.
///
/// For script-defined methods, this is typically the script's class ID obtained via [`ClassId::new_dynamic()`].
/// Use [`ClassId::none()`] if the class is not applicable or unknown.
pub class_name: ClassId,

/// Description of the method's return type.
///
/// See [`PropertyInfo`] for how to construct type information. For methods that
/// don't return a value (void), use `VariantType::NIL`.
pub return_type: PropertyInfo,

/// Descriptions of each method parameter.
///
/// Each element describes one parameter's type, name, and metadata. The order
/// matches the parameter order in the method signature.
pub arguments: Vec<PropertyInfo>,
/// Whether default arguments are real "arguments" is controversial. From the function PoV they are, but for the caller,
/// they are just pre-set values to fill in for missing arguments.

/// Default values for parameters with defaults.
///
/// Contains the actual default [`Variant`] values for parameters that have them.
/// The length of this vector is typically less than or equal to `arguments.len()`,
/// containing defaults only for trailing parameters.
pub default_arguments: Vec<Variant>,

/// Method flags controlling behavior and access.
///
/// See [`MethodFlags`] for available options like `NORMAL`, `VIRTUAL`, `CONST`, etc.
pub flags: MethodFlags,
}

Expand All @@ -35,6 +109,7 @@ impl MethodInfo {
/// [`free_owned_method_sys`](Self::free_owned_method_sys).
///
/// This will leak memory unless used together with `free_owned_method_sys`.
#[doc(hidden)]
pub fn into_owned_method_sys(self) -> sys::GDExtensionMethodInfo {
use crate::obj::EngineBitfield as _;

Expand Down Expand Up @@ -88,6 +163,7 @@ impl MethodInfo {
///
/// * Must only be used on a struct returned from a call to `into_owned_method_sys`, without modification.
/// * Must not be called more than once on a `sys::GDExtensionMethodInfo` struct.
#[doc(hidden)]
#[deny(unsafe_op_in_unsafe_fn)]
pub unsafe fn free_owned_method_sys(info: sys::GDExtensionMethodInfo) {
// Destructure info to ensure all fields are used.
Expand Down
96 changes: 76 additions & 20 deletions godot-core/src/meta/property_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,40 @@ use crate::registry::class::get_dyn_property_hint_string;
use crate::registry::property::{Export, Var};
use crate::{classes, sys};

/// Describes a property in Godot.
/// Describes a property's type, name and metadata for Godot.
///
/// Abstraction of the low-level `sys::GDExtensionPropertyInfo`.
/// `PropertyInfo` is used throughout the Godot binding to describe properties, method parameters and return types.
///
/// Keeps the actual allocated values (the `sys` equivalent only keeps pointers, which fall out of scope).
/// This is a high-level abstraction over the low-level FFI type `sys::GDExtensionPropertyInfo`. Unlike the FFI version which only stores
/// pointers, `PropertyInfo` owns its data, ensuring it remains valid for the lifetime of the struct.
///
/// See also [`MethodInfo`](crate::meta::MethodInfo) for describing method signatures and [`ClassId`] for type-IDs of Godot classes.
///
/// # Construction
/// For most use cases, prefer the convenience constructors:
/// - [`new_var::<T>()`](Self::new_var) -- creates property info for a `#[var]` attribute.
/// - [`new_export::<T>()`](Self::new_export) -- for an `#[export]` attribute.
/// - [`new_group()`](Self::new_group) / [`new_subgroup()`](Self::new_subgroup) -- for editor groups.
///
/// # Example
/// ```no_run
/// use godot::meta::{PropertyInfo, PropertyHintInfo, ClassId};
/// use godot::builtin::{StringName, VariantType};
/// use godot::global::PropertyUsageFlags;
///
/// // Integer property without a specific class
/// let count_property = PropertyInfo {
/// variant_type: VariantType::INT,
/// class_id: ClassId::none(), // Only OBJECT types need a real class ID.
/// property_name: StringName::from("count"),
/// hint_info: PropertyHintInfo::none(),
/// usage: PropertyUsageFlags::DEFAULT,
/// };
/// ```
///
/// Here, `class_id` is set to [`ClassId::none()`] because integer properties do not require a specific class. For objects, you can use
/// [`ClassId::new_cached::<T>(...)`][ClassId::new_cached] if you have a Rust type, or [`ClassId::new_dynamic(...)`][ClassId::new_dynamic]
/// for dynamic contexts and script classes.
#[derive(Clone, Debug)]
// Note: is not #[non_exhaustive], so adding fields is a breaking change. Mostly used internally at the moment though.
// Note: There was an idea of a high-level representation of the following, but it's likely easier and more efficient to use introspection
Expand All @@ -30,46 +59,65 @@ use crate::{classes, sys};
// Object { class_id: ClassId },
// }
pub struct PropertyInfo {
/// Which type this property has.
/// Type of the property.
///
/// For objects this should be set to [`VariantType::OBJECT`], and the `class_name` field to the actual name of the class.
///
/// For [`Variant`][crate::builtin::Variant], this should be set to [`VariantType::NIL`].
/// For objects, this should be set to [`VariantType::OBJECT`] and use the `class_id` field to specify the actual class. \
/// For generic [`Variant`](crate::builtin::Variant) properties, use [`VariantType::NIL`].
pub variant_type: VariantType,

/// Which class this property is.
/// The specific class identifier for object-typed properties in Godot.
///
/// This is only relevant when `variant_type` is set to [`VariantType::OBJECT`].
///
/// # Example
/// ```no_run
/// use godot::meta::ClassId;
/// use godot::classes::Node3D;
/// use godot::obj::GodotClass; // Trait method ::class_id().
///
/// This should be set to [`ClassId::none()`] unless the variant type is `Object`. You can use
/// [`GodotClass::class_id()`] to get the right name to use here.
/// let none_id = ClassId::none(); // For built-ins (not classes).
/// let static_id = Node3D::class_id(); // For classes with a Rust type.
/// let dynamic_id = ClassId::new_dynamic("MyScript"); // For runtime class names.
/// ```
pub class_id: ClassId,

/// The name of this property in Godot.
/// The name of this property as it appears in Godot's object system.
pub property_name: StringName,

/// Additional type information for this property, e.g. about array types or enum values. Split into `hint` and `hint_string` members.
/// Additional type information and validation constraints for this property.
///
/// Use functions from [`export_info_functions`](crate::registry::property::export_info_functions) to create common hints,
/// or [`PropertyHintInfo::none()`] for no hints.
///
/// See also [`PropertyHint`] in the Godot docs.
/// See [`PropertyHintInfo`] struct in Rust, as well as [`PropertyHint`] in the official Godot documentation.
///
/// [`PropertyHint`]: https://docs.godotengine.org/en/latest/classes/class_%40globalscope.html#enum-globalscope-propertyhint
pub hint_info: PropertyHintInfo,

/// How this property should be used. See [`PropertyUsageFlags`] in Godot for the meaning.
/// Flags controlling how this property should be used and displayed by the Godot engine.
///
/// Common values:
/// - [`PropertyUsageFlags::DEFAULT`] -- standard property (readable, writable, saved, appears in editor).
/// - [`PropertyUsageFlags::STORAGE`] -- persisted, but not shown in editor.
/// - [`PropertyUsageFlags::EDITOR`] -- shown in editor, but not persisted.
///
/// See also [`PropertyUsageFlags`] in the official Godot documentation for a complete list of flags.
///
/// [`PropertyUsageFlags`]: https://docs.godotengine.org/en/latest/classes/class_%40globalscope.html#enum-globalscope-propertyusageflags
pub usage: PropertyUsageFlags,
}

impl PropertyInfo {
/// Create a new `PropertyInfo` representing a property named `property_name` with type `T`.
/// Create a new `PropertyInfo` representing a property named `property_name` with type `T` automatically.
///
/// This will generate property info equivalent to what a `#[var]` attribute would.
/// This will generate property info equivalent to what a `#[var]` attribute would produce.
pub fn new_var<T: Var>(property_name: &str) -> Self {
T::Via::property_info(property_name).with_hint_info(T::var_hint())
}

/// Create a new `PropertyInfo` representing an exported property named `property_name` with type `T`.
/// Create a new `PropertyInfo` for an exported property named `property_name` with type `T` automatically.
///
/// This will generate property info equivalent to what an `#[export]` attribute would.
/// This will generate property info equivalent to what an `#[export]` attribute would produce.
pub fn new_export<T: Export>(property_name: &str) -> Self {
T::Via::property_info(property_name).with_hint_info(T::export_hint())
}
Expand All @@ -79,8 +127,7 @@ impl PropertyInfo {
/// See [`export_info_functions`](crate::registry::property::export_info_functions) for functions that return appropriate `PropertyHintInfo`s for
/// various Godot annotations.
///
/// # Examples
///
/// # Example
/// Creating an `@export_range` property.
///
// TODO: Make this nicer to use.
Expand Down Expand Up @@ -156,6 +203,7 @@ impl PropertyInfo {
// FFI conversion functions

/// Converts to the FFI type. Keep this object allocated while using that!
#[doc(hidden)]
pub fn property_sys(&self) -> sys::GDExtensionPropertyInfo {
use crate::obj::{EngineBitfield as _, EngineEnum as _};

Expand All @@ -169,6 +217,7 @@ impl PropertyInfo {
}
}

#[doc(hidden)]
pub fn empty_sys() -> sys::GDExtensionPropertyInfo {
use crate::obj::{EngineBitfield as _, EngineEnum as _};

Expand Down Expand Up @@ -265,6 +314,13 @@ impl PropertyInfo {
// ----------------------------------------------------------------------------------------------------------------------------------------------

/// Info needed by Godot, for how to export a type to the editor.
///
/// Property hints provide extra metadata about the property, such as:
/// - Range constraints for numeric values.
/// - Enum value lists.
/// - File/directory paths.
/// - Resource types.
/// - Array element types.
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct PropertyHintInfo {
pub hint: PropertyHint,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,17 @@ fn class_name_alloc_panic() {
});
}
}

#[itest]
fn class_id_none() {
let none = ClassId::none();
assert_eq!(none.to_string(), "");
assert_eq!(none, ClassId::none());
}

#[itest]
fn class_id_new_dynamic() {
let class_id = ClassId::new_dynamic("Someクラス名");
assert_eq!(class_id.to_string(), "Someクラス名");
assert_eq!(class_id, ClassId::new_dynamic("Someクラス名"));
}
2 changes: 1 addition & 1 deletion itest/rust/src/object_tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

mod base_test;
mod call_deferred_test;
mod class_name_test;
mod class_id_test;
mod class_rename_test;
mod dyn_gd_test;
mod dynamic_call_test;
Expand Down
Loading