Skip to content

Commit 9bfaab8

Browse files
hesuteiachitoyuu
andauthored
Merge #847
847: Separate static name from `NativeClass` into new trait r=Bromeon a=chitoyuu This allows NativeScript types to be registered under names only determined at run time, enabling better ergonomics for libraries. Ref #601, #603 ## Unresolved questions This does not implement the `#[name = "MyCustomName"]` static renaming syntax as suggested in godot-rust/gdnative#601 (comment), because it's very uncommon (if at all possible) for a key-value pair to be the top-level meta in an attribute. It should also be noted that at this moment, the `NativeClass` macro already utilizes a large number of type-level attributes: - `inherit` - `user_data` - `register_with` - `no_constructor` ...so it might not be wise to add another into the mix, especially under such a non-specific identifier as `name`. We might want to consider bundling (some of) these into a single top-level attribute (like `variant` for the `From`/`ToVariant` macros) instead. See #848. Co-authored-by: Chitose Yuuzaki <[email protected]>
2 parents 23a38a0 + d75c8be commit 9bfaab8

File tree

17 files changed

+205
-98
lines changed

17 files changed

+205
-98
lines changed

gdnative-async/src/rt.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::fmt::Display;
12
use std::marker::PhantomData;
23

34
use func_state::FuncState;
@@ -89,9 +90,25 @@ impl Context {
8990

9091
/// Adds required supporting NativeScript classes to `handle`. This must be called once and
9192
/// only once per initialization.
93+
///
94+
/// This registers the internal types under an unspecified prefix, with the intention to avoid
95+
/// collision with user types. Users may provide a custom prefix using
96+
/// [`register_runtime_with_prefix`], should it be necessary to name these types.
9297
pub fn register_runtime(handle: &InitHandle) {
93-
handle.add_class::<bridge::SignalBridge>();
94-
handle.add_class::<func_state::FuncState>();
98+
register_runtime_with_prefix(handle, "__GDNATIVE_ASYNC_INTERNAL__")
99+
}
100+
101+
/// Adds required supporting NativeScript classes to `handle`. This must be called once and
102+
/// only once per initialization.
103+
///
104+
/// The user should ensure that no other NativeScript types is registered under the
105+
/// provided prefix.
106+
pub fn register_runtime_with_prefix<S>(handle: &InitHandle, prefix: S)
107+
where
108+
S: Display,
109+
{
110+
handle.add_class_as::<bridge::SignalBridge>(format!("{}SignalBridge", prefix));
111+
handle.add_class_as::<func_state::FuncState>(format!("{}FuncState", prefix));
95112
}
96113

97114
/// Releases all observers still in use. This should be called in the

gdnative-async/src/rt/bridge.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,6 @@ impl NativeClass for SignalBridge {
5252
type Base = Reference;
5353
type UserData = ArcData<SignalBridge>;
5454

55-
fn class_name() -> &'static str {
56-
// Sort of just praying that there will be no duplicates of this.
57-
"__GDNATIVE_ASYNC_INTERNAL__SignalBridge"
58-
}
59-
6055
fn register_properties(_builder: &ClassBuilder<Self>) {}
6156
}
6257

gdnative-async/src/rt/func_state.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,6 @@ impl NativeClass for FuncState {
2525
type Base = Reference;
2626
type UserData = LocalCellData<FuncState>;
2727

28-
fn class_name() -> &'static str {
29-
// Sort of just praying that there will be no duplicates of this.
30-
"__GDNATIVE_ASYNC_INTERNAL__FuncState"
31-
}
32-
3328
fn register_properties(builder: &ClassBuilder<Self>) {
3429
builder
3530
.signal("completed")

gdnative-core/src/core_types/variant.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::*;
2+
use std::borrow::Cow;
23
use std::default::Default;
34
use std::fmt;
45
use std::mem::{forget, transmute};
@@ -884,7 +885,7 @@ pub enum FromVariantError {
884885
},
885886

886887
/// Given object is not an instance of the expected NativeClass.
887-
InvalidInstance { expected: &'static str },
888+
InvalidInstance { expected: Cow<'static, str> },
888889
/// Collection contains an invalid field.
889890
InvalidField {
890891
field_name: &'static str,

gdnative-core/src/export/class.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::export::user_data::UserData;
2-
use crate::export::ClassBuilder;
2+
use crate::export::{class_registry, ClassBuilder};
33
use crate::object::ownership::{Ownership, Shared, Unique};
44
use crate::object::{GodotObject, Instance, Instanciable, TRef};
55

@@ -43,12 +43,6 @@ pub trait NativeClass: Sized + 'static {
4343
/// See module-level documentation on `user_data` for more info.
4444
type UserData: UserData<Target = Self>;
4545

46-
/// The name of the class.
47-
///
48-
/// In GDNative+NativeScript many classes can be defined in one dynamic library.
49-
/// To identify which class has to be used, a library-unique name has to be given.
50-
fn class_name() -> &'static str;
51-
5246
/// Function that creates a value of `Self`, used for the script-instance. The default
5347
/// implementation simply panics.
5448
///
@@ -62,7 +56,7 @@ pub trait NativeClass: Sized + 'static {
6256
fn init(_owner: TRef<'_, Self::Base, Shared>) -> Self {
6357
panic!(
6458
"{} does not have a zero-argument constructor",
65-
Self::class_name()
59+
class_registry::class_name_or_default::<Self>()
6660
)
6761
}
6862

@@ -103,6 +97,17 @@ pub trait NativeClass: Sized + 'static {
10397
}
10498
}
10599

100+
/// A NativeScript "class" that is statically named. [`NativeClass`] types that implement this
101+
/// trait can be registered using [`InitHandle::add_class`].
102+
pub trait StaticallyNamed: NativeClass {
103+
/// The name of the class.
104+
///
105+
/// This name must be unique for the dynamic library. For generic or library code where this
106+
/// is hard to satisfy, consider using [`InitHandle::add_class_as`] to provide a name
107+
/// at registration time instead.
108+
const CLASS_NAME: &'static str;
109+
}
110+
106111
/// Trait used to provide information of Godot-exposed methods of a script class.
107112
pub trait NativeClassMethods: NativeClass {
108113
/// Function that registers all exposed methods to Godot.

gdnative-core/src/export/class_registry.rs

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,51 @@
1-
use crate::export::NativeClass;
1+
use std::any::TypeId;
2+
use std::borrow::Cow;
3+
use std::collections::HashMap;
4+
25
use once_cell::sync::Lazy;
36
use parking_lot::RwLock;
4-
use std::any::TypeId;
5-
use std::collections::HashSet;
67

7-
static CLASS_REGISTRY: Lazy<RwLock<HashSet<TypeId>>> = Lazy::new(|| RwLock::new(HashSet::new()));
8+
use crate::export::NativeClass;
9+
10+
static CLASS_REGISTRY: Lazy<RwLock<HashMap<TypeId, ClassInfo>>> =
11+
Lazy::new(|| RwLock::new(HashMap::new()));
12+
13+
pub(crate) struct ClassInfo {
14+
pub name: Cow<'static, str>,
15+
}
816

9-
/// Can be used to validate whether or not `C` has been added using `InitHandle::add_class<C>()`
10-
/// Returns true if added otherwise false.
17+
/// Access the [`ClassInfo`] of the class `C`.
1118
#[inline]
12-
pub(crate) fn is_class_registered<C: NativeClass>() -> bool {
13-
let type_id = TypeId::of::<C>();
14-
CLASS_REGISTRY.read().contains(&type_id)
19+
pub(crate) fn with_class_info<C: NativeClass, F, R>(f: F) -> Option<R>
20+
where
21+
F: FnOnce(&ClassInfo) -> R,
22+
{
23+
CLASS_REGISTRY.read().get(&TypeId::of::<C>()).map(f)
24+
}
25+
26+
/// Returns the NativeScript name of the class `C` if it is registered.
27+
/// Can also be used to validate whether or not `C` has been added using `InitHandle::add_class<C>()`
28+
#[inline]
29+
pub(crate) fn class_name<C: NativeClass>() -> Option<Cow<'static, str>> {
30+
with_class_info::<C, _, _>(|i| i.name.clone())
31+
}
32+
33+
/// Returns the NativeScript name of the class `C` if it is registered, or a best-effort description
34+
/// of the type otherwise.
35+
///
36+
/// The returned string should only be used for diagnostic purposes, not for types that the user
37+
/// has to name explicitly. The format is not guaranteed.
38+
#[inline]
39+
pub(crate) fn class_name_or_default<C: NativeClass>() -> Cow<'static, str> {
40+
class_name::<C>().unwrap_or_else(|| Cow::Borrowed(std::any::type_name::<C>()))
1541
}
1642

17-
/// Registers the class `C` in the class registry.
18-
/// Returns `true` if `C` was not already present in the list.
19-
/// Returns `false` if `C` was already added.
43+
/// Registers the class `C` in the class registry, using a custom name.
44+
/// Returns the old `ClassInfo` if `C` was already added.
2045
#[inline]
21-
pub(crate) fn register_class<C: NativeClass>() -> bool {
46+
pub(crate) fn register_class_as<C: NativeClass>(name: Cow<'static, str>) -> Option<ClassInfo> {
2247
let type_id = TypeId::of::<C>();
23-
CLASS_REGISTRY.write().insert(type_id)
48+
CLASS_REGISTRY.write().insert(type_id, ClassInfo { name })
2449
}
2550

2651
/// Clears the registry

gdnative-core/src/export/emplace.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
use std::any::Any;
44
use std::cell::RefCell;
55

6+
use crate::export::class_registry;
7+
68
use super::NativeClass;
79

810
thread_local! {
@@ -39,7 +41,7 @@ pub fn take<T: NativeClass>() -> Option<T> {
3941
Ok(script) => *script,
4042
Err(any) => panic!(
4143
"expecting {} in the emplacement cell, got {:?} (this is a bug in the bindings)",
42-
T::class_name(),
44+
class_registry::class_name_or_default::<T>(),
4345
any.type_id(),
4446
),
4547
})

gdnative-core/src/export/method.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::marker::PhantomData;
66

77
use crate::core_types::{FromVariant, FromVariantError, Variant};
88
use crate::export::class::NativeClass;
9-
use crate::export::ClassBuilder;
9+
use crate::export::{class_registry, ClassBuilder};
1010
use crate::log::Site;
1111
use crate::object::ownership::Shared;
1212
use crate::object::{Ref, TInstance, TRef};
@@ -547,7 +547,7 @@ unsafe extern "C" fn method_wrapper<C: NativeClass, F: Method<C>>(
547547
F::site().unwrap_or_default(),
548548
format_args!(
549549
"gdnative-core: user data pointer for {} is null (did the constructor fail?)",
550-
C::class_name(),
550+
class_registry::class_name_or_default::<C>(),
551551
),
552552
);
553553
return Variant::nil().leak();
@@ -560,7 +560,7 @@ unsafe extern "C" fn method_wrapper<C: NativeClass, F: Method<C>>(
560560
F::site().unwrap_or_default(),
561561
format_args!(
562562
"gdnative-core: base object pointer for {} is null (probably a bug in Godot)",
563-
C::class_name(),
563+
class_registry::class_name_or_default::<C>(),
564564
),
565565
);
566566
return Variant::nil().leak();

gdnative-core/src/export/property/accessor.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::ptr::NonNull;
55

66
use crate::core_types::{FromVariant, ToVariant, Variant};
77
use crate::export::user_data::{Map, MapMut, UserData};
8-
use crate::export::NativeClass;
8+
use crate::export::{class_registry, NativeClass};
99
use crate::object::{GodotObject, RawObject, TRef};
1010

1111
/// Trait for raw property setters.
@@ -219,7 +219,7 @@ where
219219
if class.is_null() {
220220
godot_error!(
221221
"gdnative-core: user data pointer for {} is null (did the constructor fail?)",
222-
C::class_name(),
222+
class_registry::class_name_or_default::<C>(),
223223
);
224224
return;
225225
}
@@ -229,7 +229,7 @@ where
229229
None => {
230230
godot_error!(
231231
"gdnative-core: owner pointer for {} is null",
232-
C::class_name(),
232+
class_registry::class_name_or_default::<C>(),
233233
);
234234
return;
235235
}
@@ -294,7 +294,7 @@ where
294294
if class.is_null() {
295295
godot_error!(
296296
"gdnative-core: user data pointer for {} is null (did the constructor fail?)",
297-
C::class_name(),
297+
class_registry::class_name_or_default::<C>(),
298298
);
299299
return Variant::nil().leak();
300300
}
@@ -304,7 +304,7 @@ where
304304
None => {
305305
godot_error!(
306306
"gdnative-core: owner pointer for {} is null",
307-
C::class_name(),
307+
class_registry::class_name_or_default::<C>(),
308308
);
309309
return Variant::nil().leak();
310310
}

gdnative-core/src/export/property/invalid_accessor.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use std::mem;
44

55
use crate::core_types::{FromVariant, ToVariant, Variant};
6-
use crate::export::NativeClass;
6+
use crate::export::{class_registry, NativeClass};
77

88
use super::accessor::{RawGetter, RawSetter};
99

@@ -84,7 +84,7 @@ unsafe impl<'l, C: NativeClass, T: FromVariant> RawSetter<C, T> for InvalidSette
8484
let mut set = sys::godot_property_set_func::default();
8585

8686
let data = Box::new(InvalidAccessorData {
87-
class_name: C::class_name().to_string(),
87+
class_name: class_registry::class_name_or_default::<C>().into_owned(),
8888
property_name: self.property_name.to_string(),
8989
});
9090

@@ -101,7 +101,7 @@ unsafe impl<'l, C: NativeClass, T: ToVariant> RawGetter<C, T> for InvalidGetter<
101101
let mut get = sys::godot_property_get_func::default();
102102

103103
let data = Box::new(InvalidAccessorData {
104-
class_name: C::class_name().to_string(),
104+
class_name: class_registry::class_name_or_default::<C>().into_owned(),
105105
property_name: self.property_name.to_string(),
106106
});
107107

0 commit comments

Comments
 (0)