5
5
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
6
*/
7
7
8
+ use godot_ffi:: join_with;
8
9
use std:: collections:: HashMap ;
9
10
use std:: { any, ptr} ;
10
11
12
+ use crate :: builtin:: GString ;
11
13
use crate :: init:: InitLevel ;
12
14
use crate :: meta:: error:: { ConvertError , FromGodotError } ;
13
15
use crate :: meta:: ClassName ;
14
16
use crate :: obj:: { cap, DynGd , Gd , GodotClass } ;
15
17
use crate :: private:: { ClassPlugin , PluginItem } ;
16
18
use crate :: registry:: callbacks;
17
19
use crate :: registry:: plugin:: { ErasedDynifyFn , ErasedRegisterFn , InherentImpl } ;
18
- use crate :: { classes, godot_error, sys} ;
20
+ use crate :: { classes, godot_error, godot_warn , sys} ;
19
21
use sys:: { interface_fn, out, Global , GlobalGuard , GlobalLockError } ;
20
22
21
23
/// Returns a lock to a global map of loaded classes, by initialization level.
@@ -215,11 +217,30 @@ pub fn auto_register_classes(init_level: InitLevel) {
215
217
fill_class_info ( elem. item . clone ( ) , class_info) ;
216
218
} ) ;
217
219
220
+ // First register all the loaded classes and dyn traits.
221
+ // We need all the dyn classes in the registry to properly register DynGd properties;
222
+ // one can do it directly inside the loop – by locking and unlocking the mutex –
223
+ // but it is much slower and doesn't guarantee that all the dependent classes will be already loaded in most cases.
224
+ register_classes_and_dyn_traits ( & mut map, init_level) ;
225
+
226
+ // actually register all the classes
227
+ for info in map. into_values ( ) {
228
+ register_class_raw ( info) ;
229
+ out ! ( "Class {class_name} loaded." ) ;
230
+ }
231
+
232
+ out ! ( "All classes for level `{init_level:?}` auto-registered." ) ;
233
+ }
234
+
235
+ fn register_classes_and_dyn_traits (
236
+ map : & mut HashMap < ClassName , ClassRegistrationInfo > ,
237
+ init_level : InitLevel ,
238
+ ) {
218
239
let mut loaded_classes_by_level = global_loaded_classes_by_init_level ( ) ;
219
240
let mut loaded_classes_by_name = global_loaded_classes_by_name ( ) ;
220
241
let mut dyn_traits_by_typeid = global_dyn_traits_by_typeid ( ) ;
221
242
222
- for mut info in map. into_values ( ) {
243
+ for info in map. values_mut ( ) {
223
244
let class_name = info. class_name ;
224
245
out ! ( "Register class: {class_name} at level `{init_level:?}`" ) ;
225
246
@@ -246,13 +267,7 @@ pub fn auto_register_classes(init_level: InitLevel) {
246
267
. push ( loaded_class) ;
247
268
248
269
loaded_classes_by_name. insert ( class_name, metadata) ;
249
-
250
- register_class_raw ( info) ;
251
-
252
- out ! ( "Class {class_name} loaded." ) ;
253
270
}
254
-
255
- out ! ( "All classes for level `{init_level:?}` auto-registered." ) ;
256
271
}
257
272
258
273
pub fn unregister_classes ( init_level : InitLevel ) {
@@ -334,6 +349,38 @@ pub(crate) fn try_dynify_object<T: GodotClass, D: ?Sized + 'static>(
334
349
Err ( error. into_error ( object) )
335
350
}
336
351
352
+ /// Responsible for creating hint_string for [`DynGd<T, D>`][crate::obj::DynGd] properties which works with [`PropertyHint::NODE_TYPE`][crate::global::PropertyHint::NODE_TYPE] or [`PropertyHint::RESOURCE_TYPE`][crate::global::PropertyHint::RESOURCE_TYPE].
353
+ ///
354
+ /// Godot offers very limited capabilities when it comes to validating properties in the editor if given class isn't a tool.
355
+ /// Proper hint string combined with `PropertyHint::NODE_TYPE` or `PropertyHint::RESOURCE_TYPE` allows to limit selection only to valid classes - those registered as implementors of given `DynGd<T, D>`'s `D` trait.
356
+ ///
357
+ /// See also [Godot docs for PropertyHint](https://docs.godotengine.org/en/stable/classes/[email protected] #enum-globalscope-propertyhint).
358
+ pub ( crate ) fn get_dyn_property_hint_string < D > ( ) -> GString
359
+ where
360
+ D : ?Sized + ' static ,
361
+ {
362
+ let typeid = any:: TypeId :: of :: < D > ( ) ;
363
+ let dyn_traits_by_typeid = global_dyn_traits_by_typeid ( ) ;
364
+ let Some ( relations) = dyn_traits_by_typeid. get ( & typeid) else {
365
+ let trait_name = sys:: short_type_name :: < D > ( ) ;
366
+ godot_warn ! (
367
+ "godot-rust: No class has been linked to trait {trait_name} with #[godot_dyn]."
368
+ ) ;
369
+ return GString :: default ( ) ;
370
+ } ;
371
+ assert ! (
372
+ !relations. is_empty( ) ,
373
+ "Trait {trait_name} has been registered as DynGd Trait \
374
+ despite no class being related to it \n \
375
+ **this is a bug, please report it**",
376
+ trait_name = sys:: short_type_name:: <D >( )
377
+ ) ;
378
+
379
+ GString :: from ( join_with ( relations. iter ( ) , ", " , |relation| {
380
+ relation. implementing_class_name . to_cow_str ( )
381
+ } ) )
382
+ }
383
+
337
384
/// Populate `c` with all the relevant data from `component` (depending on component type).
338
385
fn fill_class_info ( item : PluginItem , c : & mut ClassRegistrationInfo ) {
339
386
c. validate_unique ( & item) ;
0 commit comments