Skip to content

Commit a5f6d06

Browse files
committed
Use Global<T> instead of Mutex<Option<T>> in one place
Leave in 2 places: one actually needs Option for late-init, the other needs try_lock(). Also use ClassName by value whenever possible, since it's Copy.
1 parent c485d5f commit a5f6d06

File tree

4 files changed

+23
-33
lines changed

4 files changed

+23
-33
lines changed

godot-core/src/builtin/meta/class_name.rs

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,15 @@
88
use godot_ffi as sys;
99
use std::collections::HashMap;
1010
use std::ffi::CStr;
11-
use std::{fmt, sync};
12-
11+
use std::fmt;
1312
use std::hash::{Hash, Hasher};
1413

1514
use crate::builtin::*;
15+
use sys::Global;
1616

17-
// Why is this so ugly?
18-
// - Mutex: needed for global access (Sync).
19-
// - Option: needed to initialize lazily, because HashMap::new() is not const.
20-
// - Box: needed for pointer stability (HashMap insertion may invalidate pointers -- with_capacity() would be an alternative,
21-
// but we don't know how many classes).
22-
// In theory a static mut would do the job, however if we allow for manual class registration (at any time), we need to count with
23-
// later adjustments.
24-
// We may also consider OnceLock with a static per class, but that needs to be code-generated (for #[derive] and engine classes), and
25-
// any manually registered classes would need to replicate it later.
26-
static CACHED_STRING_NAMES: sync::Mutex<Option<HashMap<ClassName, Box<StringName>>>> =
27-
sync::Mutex::new(None);
17+
// Box is needed for pointer stability (HashMap insertion may invalidate pointers -- with_capacity() would be an alternative,
18+
// but we don't know how many classes).
19+
static CACHED_STRING_NAMES: Global<HashMap<ClassName, Box<StringName>>> = Global::default();
2820

2921
/// Name of a class registered with Godot.
3022
///
@@ -80,8 +72,7 @@ impl ClassName {
8072

8173
// Takes a closure because the mutex guard protects the reference; so the &StringName cannot leave the scope.
8274
fn with_string_name<R>(&self, func: impl FnOnce(&StringName) -> R) -> R {
83-
let mut guard = CACHED_STRING_NAMES.lock().unwrap();
84-
let map = guard.get_or_insert_with(HashMap::new);
75+
let mut map = CACHED_STRING_NAMES.lock();
8576

8677
let value = map
8778
.entry(*self)

godot-core/src/builtin/meta/registration/constant.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ impl IntegerConstant {
2727
}
2828
}
2929

30-
fn register(&self, class_name: &ClassName, enum_name: &StringName, is_bitfield: bool) {
30+
fn register(&self, class_name: ClassName, enum_name: &StringName, is_bitfield: bool) {
3131
unsafe {
3232
interface_fn!(classdb_register_extension_class_integer_constant)(
3333
sys::get_library(),
@@ -56,7 +56,7 @@ pub enum ConstantKind {
5656
}
5757

5858
impl ConstantKind {
59-
fn register(&self, class_name: &ClassName) {
59+
fn register(&self, class_name: ClassName) {
6060
match self {
6161
ConstantKind::Integer(integer) => {
6262
integer.register(class_name, &StringName::default(), false)
@@ -87,6 +87,6 @@ impl ExportConstant {
8787
}
8888

8989
pub fn register(&self) {
90-
self.kind.register(&self.class_name)
90+
self.kind.register(self.class_name)
9191
}
9292
}

godot-core/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ pub mod private {
5151
pub use crate::gen::classes::class_macros;
5252
pub use crate::registry::{callbacks, ClassPlugin, ErasedRegisterFn, PluginComponent};
5353
pub use crate::storage::as_storage;
54-
pub use godot_ffi::out;
54+
pub use sys::out;
5555

5656
use crate::{log, sys};
5757

godot-core/src/registry.rs

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,28 @@
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
77

8-
#![allow(dead_code)] // FIXME
9-
8+
use crate::builtin::meta::ClassName;
9+
use crate::builtin::StringName;
1010
use crate::init::InitLevel;
1111
use crate::log;
1212
use crate::obj::*;
13+
use crate::out;
1314
use crate::private::as_storage;
1415
use crate::storage::InstanceStorage;
1516
use godot_ffi as sys;
1617

1718
use sys::interface_fn;
1819

19-
use crate::builtin::meta::ClassName;
20-
use crate::builtin::StringName;
21-
use crate::out;
2220
use std::any::Any;
2321
use std::collections::HashMap;
2422
use std::sync::{Mutex, MutexGuard, TryLockError};
2523
use std::{fmt, ptr};
2624

27-
// For now, that variable is needed for class unregistering. It's populated during class
28-
// registering. There is no actual concurrency here, because Godot call register/unregister in main
29-
// thread - Mutex is just casual way to ensure safety in this performance non-critical path.
30-
// Note that we panic on concurrent access instead of blocking - that's fail fast approach. If that
31-
// happen, most likely something changed on Godot side and analysis required to adopt these changes.
25+
// Needed for class unregistering. The variable is populated during class registering. There is no actual concurrency here, because Godot
26+
// calls register/unregister in the main thread. Mutex is just casual way to ensure safety in this non-performance-critical path.
27+
// Note that we panic on concurrent access instead of blocking (fail-fast approach). If that happens, most likely something changed on Godot
28+
// side and analysis required to adopt these changes.
29+
// For now, don't use Global<T> because we need try_lock(), which isn't yet provided.
3230
static LOADED_CLASSES: Mutex<Option<HashMap<InitLevel, Vec<ClassName>>>> = Mutex::new(None);
3331

3432
// TODO(bromeon): some information coming from the proc-macro API is deferred through PluginComponent, while others is directly
@@ -182,6 +180,7 @@ struct ClassRegistrationInfo {
182180
godot_params: sys::GDExtensionClassCreationInfo,
183181
#[cfg(since_api = "4.2")]
184182
godot_params: sys::GDExtensionClassCreationInfo2,
183+
#[allow(dead_code)] // Currently unused; may be useful for diagnostics in the future.
185184
init_level: InitLevel,
186185
is_editor_plugin: bool,
187186
}
@@ -305,7 +304,7 @@ pub fn unregister_classes(init_level: InitLevel) {
305304
.unwrap_or_default();
306305
out!("Unregistering classes of level {init_level:?}...");
307306
for class_name in loaded_classes_current_level.iter().rev() {
308-
unregister_class_raw(class_name);
307+
unregister_class_raw(*class_name);
309308
}
310309
}
311310

@@ -315,9 +314,9 @@ fn get_loaded_classes_with_mutex() -> MutexGuard<'static, Option<HashMap<InitLev
315314
Ok(it) => it,
316315
Err(err) => match err {
317316
TryLockError::Poisoned(_err) => panic!(
318-
"LOADED_CLASSES poisoned. seems like class registration or deregistration panicked."
317+
"global lock for loaded classes poisoned; class registration or deregistration may have panicked"
319318
),
320-
TryLockError::WouldBlock => panic!("unexpected concurrent access detected to CLASSES"),
319+
TryLockError::WouldBlock => panic!("unexpected concurrent access to global lock for loaded classes"),
321320
},
322321
}
323322
}
@@ -494,7 +493,7 @@ fn register_class_raw(mut info: ClassRegistrationInfo) {
494493
assert!(!info.is_editor_plugin);
495494
}
496495

497-
fn unregister_class_raw(class_name: &ClassName) {
496+
fn unregister_class_raw(class_name: ClassName) {
498497
out!("Unregister class: {class_name}");
499498
unsafe {
500499
#[allow(clippy::let_unit_value)]

0 commit comments

Comments
 (0)