Skip to content

Commit fd16af5

Browse files
authored
Merge pull request #1325 from godot-rust/feature/singleton-trait
Migrate inherent `singleton()` fn to new `Singleton` trait
2 parents c4051c0 + 0891530 commit fd16af5

File tree

37 files changed

+160
-205
lines changed

37 files changed

+160
-205
lines changed

godot-codegen/src/generator/classes.rs

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,10 @@ struct GeneratedClassModule {
8080
}
8181

8282
struct Construction {
83-
constructor: TokenStream,
8483
construct_doc: &'static str,
8584
final_doc: Option<&'static str>,
8685
godot_default_impl: TokenStream,
86+
singleton_impl: TokenStream,
8787
}
8888

8989
fn make_class(class: &Class, ctx: &mut Context, view: &ApiView) -> GeneratedClass {
@@ -102,10 +102,10 @@ fn make_class(class: &Class, ctx: &mut Context, view: &ApiView) -> GeneratedClas
102102
};
103103

104104
let Construction {
105-
constructor,
106105
construct_doc,
107106
final_doc,
108107
godot_default_impl,
108+
singleton_impl,
109109
} = make_constructor_and_default(class, ctx);
110110

111111
let mut extended_class_doc = construct_doc.replace("Self", &class_name.rust_ty.to_string());
@@ -234,7 +234,7 @@ fn make_class(class: &Class, ctx: &mut Context, view: &ApiView) -> GeneratedClas
234234
#interface_trait
235235
#notification_enum
236236
impl #class_name {
237-
#constructor
237+
// Constructors all through traits: NewGd, NewAlloc, Singleton.
238238
#methods
239239
#notify_methods
240240
#internal_methods
@@ -267,6 +267,7 @@ fn make_class(class: &Class, ctx: &mut Context, view: &ApiView) -> GeneratedClas
267267
)*
268268

269269
#godot_default_impl
270+
#singleton_impl
270271
#deref_impl
271272
#inherits_macro_code
272273
}
@@ -424,34 +425,18 @@ fn make_constructor_and_default(class: &Class, ctx: &Context) -> Construction {
424425
// Note: this could use class_id() but is not yet done due to potential future lazy-load refactoring.
425426
//let class_name_obj = quote! { <Self as crate::obj::GodotClass>::class_id() };
426427

427-
let (constructor, construct_doc, has_godot_default_impl);
428+
let (construct_doc, has_godot_default_impl);
428429
if ctx.is_singleton(class_name) {
429-
// Note: we cannot return &'static mut Self, as this would be very easy to mutably alias.
430-
// &'static Self would be possible, but we would lose the whole mutability information (even if that is best-effort and
431-
// not strict Rust mutability, it makes the API much more usable).
432-
// As long as the user has multiple Gd smart pointers to the same singletons, only the internal raw pointers are aliased.
433-
// See also Deref/DerefMut impl for Gd.
434-
constructor = quote! {
435-
pub fn singleton() -> Gd<Self> {
436-
unsafe {
437-
let __class_name = #godot_class_stringname;
438-
let __object_ptr = sys::interface_fn!(global_get_singleton)(__class_name.string_sys());
439-
Gd::from_obj_sys(__object_ptr)
440-
}
441-
}
442-
};
443430
construct_doc = "# Singleton\n\n\
444-
This class is a singleton. You can get the one instance using [`Self::singleton()`][Self::singleton].";
431+
This class is a singleton. You can get the one instance using [`Singleton::singleton()`][crate::obj::Singleton::singleton].";
445432
has_godot_default_impl = false;
446433
} else if !class.is_instantiable {
447434
// Abstract base classes or non-singleton classes without constructor.
448-
constructor = TokenStream::new();
449435
construct_doc = "# Not instantiable\n\nThis class cannot be constructed. Obtain `Gd<Self>` instances via Godot APIs.";
450436
has_godot_default_impl = false;
451437
} else {
452438
// Manually managed classes (Object, Node, ...) as well as ref-counted ones (RefCounted, Resource, ...).
453439
// The constructors are provided as associated methods in NewGd::new_gd() and NewAlloc::new_alloc().
454-
constructor = TokenStream::new();
455440

456441
if class.is_refcounted {
457442
construct_doc = "# Construction\n\n\
@@ -488,11 +473,25 @@ fn make_constructor_and_default(class: &Class, ctx: &Context) -> Construction {
488473
TokenStream::new()
489474
};
490475

476+
let singleton_impl = if ctx.is_singleton(class_name) {
477+
let class_name = &class.name().rust_ty;
478+
quote! {
479+
impl crate::obj::Singleton for #class_name {
480+
fn singleton() -> crate::obj::Gd<Self> {
481+
// SAFETY: Class name matches type T, per code generator.
482+
unsafe { crate::classes::singleton_unchecked(&#godot_class_stringname) }
483+
}
484+
}
485+
}
486+
} else {
487+
TokenStream::new()
488+
};
489+
491490
Construction {
492-
constructor,
493491
construct_doc,
494492
final_doc,
495493
godot_default_impl,
494+
singleton_impl,
496495
}
497496
}
498497

@@ -573,8 +572,6 @@ fn make_class_method_definition(
573572
return FnDefinition::none();
574573
};
575574

576-
// Note: parameter type replacements (int -> enum) are already handled during domain mapping.
577-
578575
let rust_class_name = class.name().rust_ty.to_string();
579576
let rust_method_name = method.name();
580577
let godot_method_name = method.godot_name();

godot-core/src/builtin/callable.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use sys::{ffi_methods, ExtVariantType, GodotFfi};
1313
use crate::builtin::{inner, GString, StringName, Variant, VariantArray};
1414
use crate::meta::{GodotType, ToGodot};
1515
use crate::obj::bounds::DynMemory;
16-
use crate::obj::{Bounds, Gd, GodotClass, InstanceId};
16+
use crate::obj::{Bounds, Gd, GodotClass, InstanceId, Singleton};
1717
use crate::{classes, meta};
1818

1919
#[cfg(before_api = "4.3")]

godot-core/src/builtin/string/node_path.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use std::fmt;
99

1010
use godot_ffi as sys;
11-
use godot_ffi::{ffi_methods, ExtVariantType, GodotFfi};
11+
use godot_ffi::{ffi_methods, ExtVariantType, GdextBuild, GodotFfi};
1212

1313
use super::{GString, StringName};
1414
use crate::builtin::inner;
@@ -160,7 +160,7 @@ impl NodePath {
160160
pub fn subpath(&self, range: impl SignedRange) -> NodePath {
161161
let (from, exclusive_end) = range.signed();
162162
// Polyfill for bug https://github.com/godotengine/godot/pull/100954, fixed in 4.4.
163-
let begin = if sys::GdextBuild::since_api("4.4") {
163+
let begin = if GdextBuild::since_api("4.4") {
164164
from
165165
} else {
166166
let name_count = self.get_name_count() as i64;

godot-core/src/classes/class_runtime.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::classes::{ClassDb, Object};
1313
use crate::meta::CallContext;
1414
#[cfg(debug_assertions)]
1515
use crate::meta::ClassId;
16-
use crate::obj::{bounds, Bounds, Gd, GodotClass, InstanceId, RawGd};
16+
use crate::obj::{bounds, Bounds, Gd, GodotClass, InstanceId, RawGd, Singleton};
1717
use crate::sys;
1818

1919
pub(crate) fn debug_string<T: GodotClass>(
@@ -181,6 +181,16 @@ where
181181
obj
182182
}
183183

184+
/// # Safety
185+
/// The caller must ensure that `class_name` corresponds to the actual class name of type `T`.
186+
pub(crate) unsafe fn singleton_unchecked<T>(class_name: &StringName) -> Gd<T>
187+
where
188+
T: GodotClass,
189+
{
190+
let object_ptr = unsafe { sys::interface_fn!(global_get_singleton)(class_name.string_sys()) };
191+
Gd::<T>::from_obj_sys(object_ptr)
192+
}
193+
184194
pub(crate) fn ensure_object_alive(
185195
instance_id: InstanceId,
186196
old_object_ptr: sys::GDExtensionObjectPtr,

godot-core/src/meta/property_info.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::meta::{element_godot_type_name, ArrayElement, ClassId, GodotType, Pac
1313
use crate::obj::{bounds, Bounds, EngineBitfield, EngineEnum, GodotClass};
1414
use crate::registry::class::get_dyn_property_hint_string;
1515
use crate::registry::property::{Export, Var};
16-
use crate::{classes, godot_str, sys};
16+
use crate::{classes, sys};
1717

1818
/// Describes a property in Godot.
1919
///
@@ -298,13 +298,6 @@ impl PropertyHintInfo {
298298
}
299299
}
300300

301-
pub fn type_name_range<T: GodotType + std::fmt::Display>(lower: T, upper: T) -> Self {
302-
Self {
303-
hint: PropertyHint::RANGE,
304-
hint_string: godot_str!("{lower},{upper}"), // also valid for <4.3.
305-
}
306-
}
307-
308301
/// Use for `#[var]` properties -- [`PROPERTY_HINT_ARRAY_TYPE`](PropertyHint::ARRAY_TYPE) with the type name as hint string.
309302
pub fn var_array_element<T: ArrayElement>() -> Self {
310303
Self {

godot-core/src/obj/traits.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,23 @@ pub trait NewAlloc: GodotClass {
684684
fn new_alloc() -> Gd<Self>;
685685
}
686686

687+
/// Trait for singleton classes in Godot.
688+
///
689+
/// There is only one instance of each singleton class in the engine, accessible through [`singleton()`][Self::singleton].
690+
pub trait Singleton: GodotClass {
691+
// Note: we cannot return &'static mut Self, as this would be very easy to mutably alias. Returning &'static Self is possible, but we'd
692+
// lose the whole mutability information (even if that is best-effort and not strict Rust mutability, it makes the API much more usable).
693+
// As long as the user has multiple Gd smart pointers to the same singletons, only the internal raw pointers are aliased.
694+
// See also Deref/DerefMut impl for Gd.
695+
696+
/// Returns the singleton instance.
697+
///
698+
/// # Panics
699+
/// If called during global init/deinit of godot-rust. Most singletons are only available after the first frame has run.
700+
/// See also [`ExtensionLibrary`](../init/trait.ExtensionLibrary.html#availability-of-godot-apis-during-init-and-deinit).
701+
fn singleton() -> Gd<Self>;
702+
}
703+
687704
impl<T> NewAlloc for T
688705
where
689706
T: cap::GodotDefault + Bounds<Memory = bounds::MemManual>,

godot-core/src/private.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ pub const fn is_editor_plugin<T: crate::obj::Inherits<crate::classes::EditorPlug
199199
// Starting from 4.3, Godot has "runtime classes"; this emulation is no longer needed.
200200
#[cfg(before_api = "4.3")]
201201
pub fn is_class_inactive(is_tool: bool) -> bool {
202+
use crate::obj::Singleton;
203+
202204
if is_tool {
203205
return false;
204206
}

godot-core/src/registry/class.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::classes::ClassDb;
1515
use crate::init::InitLevel;
1616
use crate::meta::error::FromGodotError;
1717
use crate::meta::ClassId;
18-
use crate::obj::{cap, DynGd, Gd, GodotClass};
18+
use crate::obj::{cap, DynGd, Gd, GodotClass, Singleton};
1919
use crate::private::{ClassPlugin, PluginItem};
2020
use crate::registry::callbacks;
2121
use crate::registry::plugin::{DynTraitImpl, ErasedRegisterFn, ITraitImpl, InherentImpl, Struct};

0 commit comments

Comments
 (0)