Skip to content

Commit 5ed9f10

Browse files
committed
Separate static name from NativeClass into new trait
This allows NativeScript types to be registered under names only determined at run time, enabling better ergonomics for libraries. Ref #601, #603
1 parent cf2e124 commit 5ed9f10

File tree

17 files changed

+192
-92
lines changed

17 files changed

+192
-92
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: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,50 @@
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

917
/// Can be used to validate whether or not `C` has been added using `InitHandle::add_class<C>()`
1018
/// Returns true if added otherwise false.
1119
#[inline]
1220
pub(crate) fn is_class_registered<C: NativeClass>() -> bool {
1321
let type_id = TypeId::of::<C>();
14-
CLASS_REGISTRY.read().contains(&type_id)
22+
CLASS_REGISTRY.read().contains_key(&type_id)
23+
}
24+
25+
/// Access the [`ClassInfo`] of the class `C`.
26+
#[inline]
27+
pub(crate) fn with_class_info<C: NativeClass, F, R>(f: F) -> Option<R>
28+
where
29+
F: FnOnce(&ClassInfo) -> R,
30+
{
31+
CLASS_REGISTRY.read().get(&TypeId::of::<C>()).map(f)
32+
}
33+
34+
/// Returns the NativeScript name of the class `C` if it is registered. Returns a best-effort
35+
/// description of the type for error reporting otherwise.
36+
#[inline]
37+
pub(crate) fn class_name_or_default<C: NativeClass>() -> Cow<'static, str> {
38+
with_class_info::<C, _, _>(|i| i.name.clone())
39+
.unwrap_or_else(|| Cow::Borrowed(std::any::type_name::<C>()))
1540
}
1641

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.
42+
/// Registers the class `C` in the class registry, using a custom name.
43+
/// Returns the old `ClassInfo` if `C` was already added.
2044
#[inline]
21-
pub(crate) fn register_class<C: NativeClass>() -> bool {
45+
pub(crate) fn register_class_as<C: NativeClass>(name: Cow<'static, str>) -> Option<ClassInfo> {
2246
let type_id = TypeId::of::<C>();
23-
CLASS_REGISTRY.write().insert(type_id)
47+
CLASS_REGISTRY.write().insert(type_id, ClassInfo { name })
2448
}
2549

2650
/// 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)