Skip to content

Commit 5cd8fa1

Browse files
committed
Split PluginItem into separate structs per variant
Make constructors + builders for the structs Move more things out of proc-macros and into struct constructors/builders Rework the safety invariants of `godot_dyn` Document lots of these apis better
1 parent 7461251 commit 5cd8fa1

File tree

12 files changed

+622
-476
lines changed

12 files changed

+622
-476
lines changed

godot-core/src/builder/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ use std::marker::PhantomData;
1010

1111
mod method;
1212

13+
/// Class builder to store state for registering a class with Godot.
14+
///
15+
/// In the future this will be used, but for now it's a dummy struct.
1316
pub struct ClassBuilder<C> {
1417
_c: PhantomData<C>,
1518
}

godot-core/src/docs.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77

88
use crate::meta::ClassName;
9-
use crate::registry::plugin::{InherentImpl, PluginItem};
9+
use crate::registry::plugin::{ITraitImpl, InherentImpl, PluginItem, Struct};
1010
use std::collections::HashMap;
1111

1212
/// Created for documentation on
@@ -81,14 +81,14 @@ pub fn gather_xml_docs() -> impl Iterator<Item = String> {
8181
map.entry(class_name).or_default().inherent = docs
8282
}
8383

84-
PluginItem::ITraitImpl {
84+
PluginItem::ITraitImpl(ITraitImpl {
8585
virtual_method_docs,
8686
..
87-
} => map.entry(class_name).or_default().virtual_methods = virtual_method_docs,
87+
}) => map.entry(class_name).or_default().virtual_methods = virtual_method_docs,
8888

89-
PluginItem::Struct {
89+
PluginItem::Struct(Struct {
9090
docs: Some(docs), ..
91-
} => map.entry(class_name).or_default().definition = docs,
91+
}) => map.entry(class_name).or_default().definition = docs,
9292

9393
_ => (),
9494
}

godot-core/src/private.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ pub use crate::gen::classes::class_macros;
99
pub use crate::obj::rtti::ObjectRtti;
1010
pub use crate::registry::callbacks;
1111
pub use crate::registry::plugin::{
12-
ClassPlugin, ErasedDynGd, ErasedRegisterFn, ErasedRegisterRpcsFn, InherentImpl, PluginItem,
12+
ClassPlugin, DynTraitImpl, ErasedDynGd, ErasedRegisterFn, ITraitImpl, InherentImpl, PluginItem,
13+
Struct,
1314
};
1415
pub use crate::storage::{as_storage, Storage};
1516
pub use sys::out;

godot-core/src/registry/callbacks.rs

+22-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212

1313
use crate::builder::ClassBuilder;
1414
use crate::builtin::{StringName, Variant};
15-
use crate::obj::{cap, Base, GodotClass, UserClass};
15+
use crate::classes::Object;
16+
use crate::obj::{bounds, cap, AsDyn, Base, Bounds, Gd, GodotClass, Inherits, UserClass};
17+
use crate::registry::plugin::ErasedDynGd;
1618
use crate::storage::{as_storage, InstanceStorage, Storage, StorageRefCounted};
1719
use godot_ffi as sys;
1820
use std::any::Any;
@@ -396,3 +398,22 @@ pub fn register_user_methods_constants<T: cap::ImplementsGodotApi>(_class_builde
396398
pub fn register_user_rpcs<T: cap::ImplementsGodotApi>(object: &mut dyn Any) {
397399
T::__register_rpcs(object);
398400
}
401+
402+
/// # Safety
403+
///
404+
/// `obj` must be castable to `T`.
405+
#[deny(unsafe_op_in_unsafe_fn)]
406+
pub unsafe fn dynify_fn<T, D>(obj: Gd<Object>) -> ErasedDynGd
407+
where
408+
T: GodotClass + Inherits<Object> + AsDyn<D> + Bounds<Declarer = bounds::DeclUser>,
409+
D: ?Sized + 'static,
410+
{
411+
// SAFETY: `obj` is castable to `T`.
412+
let obj = unsafe { obj.try_cast::<T>().unwrap_unchecked() };
413+
let obj = obj.into_dyn::<D>();
414+
let obj = obj.upcast::<Object>();
415+
416+
ErasedDynGd {
417+
boxed: Box::new(obj),
418+
}
419+
}

godot-core/src/registry/class.rs

+30-54
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ use crate::meta::ClassName;
1616
use crate::obj::{cap, DynGd, Gd, GodotClass};
1717
use crate::private::{ClassPlugin, PluginItem};
1818
use crate::registry::callbacks;
19-
use crate::registry::plugin::{ErasedDynifyFn, ErasedRegisterFn, InherentImpl};
20-
use crate::{classes, godot_error, godot_warn, sys};
19+
use crate::registry::plugin::{DynTraitImpl, ErasedRegisterFn, ITraitImpl, InherentImpl, Struct};
20+
use crate::{godot_error, godot_warn, sys};
2121
use sys::{interface_fn, out, Global, GlobalGuard, GlobalLockError};
2222

2323
/// Returns a lock to a global map of loaded classes, by initialization level.
@@ -45,9 +45,8 @@ fn global_loaded_classes_by_name() -> GlobalGuard<'static, HashMap<ClassName, Cl
4545
lock_or_panic(&LOADED_CLASSES_BY_NAME, "loaded classes (by name)")
4646
}
4747

48-
fn global_dyn_traits_by_typeid(
49-
) -> GlobalGuard<'static, HashMap<any::TypeId, Vec<DynToClassRelation>>> {
50-
static DYN_TRAITS_BY_TYPEID: Global<HashMap<any::TypeId, Vec<DynToClassRelation>>> =
48+
fn global_dyn_traits_by_typeid() -> GlobalGuard<'static, HashMap<any::TypeId, Vec<DynTraitImpl>>> {
49+
static DYN_TRAITS_BY_TYPEID: Global<HashMap<any::TypeId, Vec<DynTraitImpl>>> =
5150
Global::default();
5251

5352
lock_or_panic(&DYN_TRAITS_BY_TYPEID, "dyn traits")
@@ -68,12 +67,6 @@ pub struct LoadedClass {
6867
// Currently empty, but should already work for per-class queries.
6968
pub struct ClassMetadata {}
7069

71-
/// Represents a `dyn Trait` implemented (and registered) for a class.
72-
pub struct DynToClassRelation {
73-
implementing_class_name: ClassName,
74-
erased_dynify_fn: ErasedDynifyFn,
75-
}
76-
7770
// ----------------------------------------------------------------------------------------------------------------------------------------------
7871

7972
// This works as long as fields are called the same. May still need individual #[cfg]s for newer fields.
@@ -110,7 +103,7 @@ struct ClassRegistrationInfo {
110103
is_editor_plugin: bool,
111104

112105
/// One entry for each `dyn Trait` implemented (and registered) for this class.
113-
dynify_fns_by_trait: HashMap<any::TypeId, ErasedDynifyFn>,
106+
dynify_fns_by_trait: HashMap<any::TypeId, DynTraitImpl>,
114107

115108
/// Used to ensure that each component is only filled once.
116109
component_already_filled: [bool; 4],
@@ -251,14 +244,11 @@ fn register_classes_and_dyn_traits(
251244
let metadata = ClassMetadata {};
252245

253246
// Transpose Class->Trait relations to Trait->Class relations.
254-
for (trait_type_id, dynify_fn) in info.dynify_fns_by_trait.drain() {
247+
for (trait_type_id, dyn_trait_impl) in info.dynify_fns_by_trait.drain() {
255248
dyn_traits_by_typeid
256249
.entry(trait_type_id)
257250
.or_default()
258-
.push(DynToClassRelation {
259-
implementing_class_name: class_name,
260-
erased_dynify_fn: dynify_fn,
261-
});
251+
.push(dyn_trait_impl);
262252
}
263253

264254
loaded_classes_by_level
@@ -301,15 +291,16 @@ pub fn auto_register_rpcs<T: GodotClass>(object: &mut T) {
301291
}
302292
}
303293

304-
/// Tries to upgrade a polymorphic `Gd<T>` to `DynGd<T, D>`, where the `T` -> `D` relation is only present via derived objects.
294+
/// Tries to convert a `Gd<T>` to a `DynGd<T, D>` for some class `T` and trait object `D`, where the trait may only be implemented for
295+
/// some subclass of `T`.
305296
///
306-
/// This works without direct `T: AsDyn<D>` because it considers `object`'s dynamic type `Td : Inherits<T>`.
297+
/// This works even when `T` doesn't implement `AsDyn<D>`, as long as the dynamic class of `object` implements `AsDyn<D>`.
307298
///
308-
/// Only direct relations are considered, i.e. the `Td: AsDyn<D>` must be fulfilled (and registered). If any intermediate base class of `Td`
309-
/// implements the trait `D`, this will not consider it. Base-derived conversions are theoretically possible, but need quite a bit of extra
310-
/// machinery.
299+
/// This only looks for an `AsDyn<D>` implementation in the dynamic class; the conversion will fail if the dynamic class doesn't
300+
/// implement `AsDyn<D>`, even if there exists some superclass that does implement `AsDyn<D>`. This restriction could in theory be
301+
/// lifted, but would need quite a bit of extra machinery to work.
311302
pub(crate) fn try_dynify_object<T: GodotClass, D: ?Sized + 'static>(
312-
object: Gd<T>,
303+
mut object: Gd<T>,
313304
) -> Result<DynGd<T, D>, ConvertError> {
314305
let typeid = any::TypeId::of::<D>();
315306
let trait_name = sys::short_type_name::<D>();
@@ -322,28 +313,16 @@ pub(crate) fn try_dynify_object<T: GodotClass, D: ?Sized + 'static>(
322313

323314
// TODO maybe use 2nd hashmap instead of linear search.
324315
// (probably not pair of typeid/classname, as that wouldn't allow the above check).
325-
let dynamic_class = object.dynamic_class_string();
326-
327316
for relation in relations {
328-
if dynamic_class == relation.implementing_class_name.to_string_name() {
329-
let erased = (relation.erased_dynify_fn)(object.upcast_object());
330-
331-
// Must succeed, or was registered wrong.
332-
let dyn_gd_object = erased.boxed.downcast::<DynGd<classes::Object, D>>();
333-
334-
// SAFETY: the relation ensures that the **unified** (for storage) pointer was of type `DynGd<classes::Object, D>`.
335-
let dyn_gd_object = unsafe { dyn_gd_object.unwrap_unchecked() };
336-
337-
// SAFETY: the relation ensures that the **original** pointer was of type `DynGd<T, D>`.
338-
let dyn_gd_t = unsafe { dyn_gd_object.cast_unchecked::<T>() };
339-
340-
return Ok(dyn_gd_t);
317+
match relation.get_dyn_gd(object) {
318+
Ok(dyn_gd) => return Ok(dyn_gd),
319+
Err(obj) => object = obj,
341320
}
342321
}
343322

344323
let error = FromGodotError::UnimplementedDynTrait {
345324
trait_name,
346-
class_name: dynamic_class.to_string(),
325+
class_name: object.dynamic_class_string().to_string(),
347326
};
348327

349328
Err(error.into_error(object))
@@ -376,8 +355,8 @@ where
376355
trait_name = sys::short_type_name::<D>()
377356
);
378357

379-
GString::from(join_with(relations.iter(), ", ", |relation| {
380-
relation.implementing_class_name.to_cow_str()
358+
GString::from(join_with(relations.iter(), ", ", |dyn_trait| {
359+
dyn_trait.class_name().to_cow_str()
381360
}))
382361
}
383362

@@ -388,7 +367,7 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
388367
// out!("| reg (before): {c:?}");
389368
// out!("| comp: {component:?}");
390369
match item {
391-
PluginItem::Struct {
370+
PluginItem::Struct(Struct {
392371
base_class_name,
393372
generated_create_fn,
394373
generated_recreate_fn,
@@ -401,7 +380,7 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
401380
is_instantiable,
402381
#[cfg(all(since_api = "4.3", feature = "register-docs"))]
403382
docs: _,
404-
} => {
383+
}) => {
405384
c.parent_class_name = Some(base_class_name);
406385
c.default_virtual_fn = default_get_virtual_fn;
407386
c.register_properties_fn = Some(register_properties_fn);
@@ -458,7 +437,7 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
458437
c.register_methods_constants_fn = Some(register_methods_constants_fn);
459438
}
460439

461-
PluginItem::ITraitImpl {
440+
PluginItem::ITraitImpl(ITraitImpl {
462441
user_register_fn,
463442
user_create_fn,
464443
user_recreate_fn,
@@ -473,7 +452,7 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
473452
user_property_get_revert_fn,
474453
#[cfg(all(since_api = "4.3", feature = "register-docs"))]
475454
virtual_method_docs: _,
476-
} => {
455+
}) => {
477456
c.user_register_fn = user_register_fn;
478457

479458
// The following unwraps of fill_into() shouldn't panic, since rustc will error if there are
@@ -497,20 +476,17 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
497476
c.godot_params.free_property_list_func = user_free_property_list_fn;
498477
c.godot_params.property_can_revert_func = user_property_can_revert_fn;
499478
c.godot_params.property_get_revert_func = user_property_get_revert_fn;
500-
c.user_virtual_fn = Some(get_virtual_fn);
479+
c.user_virtual_fn = get_virtual_fn;
501480
}
502-
PluginItem::DynTraitImpl {
503-
dyn_trait_typeid,
504-
erased_dynify_fn,
505-
} => {
506-
let prev = c
507-
.dynify_fns_by_trait
508-
.insert(dyn_trait_typeid, erased_dynify_fn);
481+
PluginItem::DynTraitImpl(dyn_trait_impl) => {
482+
let type_id = dyn_trait_impl.dyn_trait_typeid();
483+
484+
let prev = c.dynify_fns_by_trait.insert(type_id, dyn_trait_impl);
509485

510486
assert!(
511487
prev.is_none(),
512488
"Duplicate registration of {:?} for class {}",
513-
dyn_trait_typeid,
489+
type_id,
514490
c.class_name
515491
);
516492
}

0 commit comments

Comments
 (0)