Skip to content

Commit ba0edfe

Browse files
authored
Merge pull request #1992 from dsnopek/better-macos-thread-local-hack-maybe
Use a different method to avoid `thread_local` on MacOS
2 parents a5eba2d + 1b4e3a4 commit ba0edfe

3 files changed

Lines changed: 47 additions & 44 deletions

File tree

include/godot_cpp/classes/wrapped.hpp

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,9 @@
4040
#include <godot_cpp/godot.hpp>
4141

4242
#if defined(MACOS_ENABLED) && defined(HOT_RELOAD_ENABLED)
43+
#include <map>
4344
#include <mutex>
44-
#define _GODOT_CPP_AVOID_THREAD_LOCAL
45-
#define _GODOT_CPP_THREAD_LOCAL
46-
#else
47-
#define _GODOT_CPP_THREAD_LOCAL thread_local
45+
#include <thread>
4846
#endif
4947

5048
namespace godot {
@@ -65,21 +63,45 @@ class Wrapped {
6563
template <typename T, std::enable_if_t<std::is_base_of<::godot::Wrapped, T>::value, bool>>
6664
friend _ALWAYS_INLINE_ void _pre_initialize();
6765

68-
#ifdef _GODOT_CPP_AVOID_THREAD_LOCAL
69-
static std::recursive_mutex _constructing_mutex;
66+
struct ConstructInfo {
67+
const StringName *extension_class_name = nullptr;
68+
const GDExtensionInstanceBindingCallbacks *class_binding_callbacks = nullptr;
69+
#ifdef HOT_RELOAD_ENABLED
70+
GDExtensionObjectPtr recreate_owner = nullptr;
7071
#endif
72+
};
7173

72-
_GODOT_CPP_THREAD_LOCAL static const StringName *_constructing_extension_class_name;
73-
_GODOT_CPP_THREAD_LOCAL static const GDExtensionInstanceBindingCallbacks *_constructing_class_binding_callbacks;
74-
75-
#ifdef HOT_RELOAD_ENABLED
76-
_GODOT_CPP_THREAD_LOCAL static GDExtensionObjectPtr _constructing_recreate_owner;
74+
#if defined(MACOS_ENABLED) && defined(HOT_RELOAD_ENABLED)
75+
// On macOS, `thread_local` storage keeps the library from being unloaded,
76+
// which breaks hot-reload. Instead we keep each thread's `ConstructInfo` in a
77+
// map keyed by thread id.
78+
class ConstructInfoStore {
79+
std::mutex mutex;
80+
std::map<std::thread::id, ConstructInfo> infos;
81+
82+
public:
83+
ConstructInfo &get() {
84+
std::lock_guard<std::mutex> lock(mutex);
85+
return infos[std::this_thread::get_id()];
86+
}
87+
};
88+
89+
static ConstructInfo &_get_construct_info() {
90+
static ConstructInfoStore store;
91+
return store.get();
92+
}
93+
#else
94+
static ConstructInfo &_get_construct_info() {
95+
static thread_local ConstructInfo info;
96+
return info;
97+
}
7798
#endif
7899

79100
template <typename T>
80101
_ALWAYS_INLINE_ static void _set_construct_info() {
81-
_constructing_extension_class_name = T::_get_extension_class_name();
82-
_constructing_class_binding_callbacks = &T::_gde_binding_callbacks;
102+
ConstructInfo &info = _get_construct_info();
103+
info.extension_class_name = T::_get_extension_class_name();
104+
info.class_binding_callbacks = &T::_gde_binding_callbacks;
83105
}
84106

85107
protected:
@@ -124,9 +146,6 @@ class Wrapped {
124146

125147
template <typename T, std::enable_if_t<std::is_base_of<::godot::Wrapped, T>::value, bool>>
126148
_ALWAYS_INLINE_ void _pre_initialize() {
127-
#ifdef _GODOT_CPP_AVOID_THREAD_LOCAL
128-
Wrapped::_constructing_mutex.lock();
129-
#endif
130149
Wrapped::_set_construct_info<T>();
131150
}
132151

include/godot_cpp/core/class_db.hpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,7 @@ class ClassDB {
136136
static GDExtensionClassInstancePtr _recreate_instance_func(void *data, GDExtensionObjectPtr obj) {
137137
if constexpr (!std::is_abstract_v<T>) {
138138
#ifdef HOT_RELOAD_ENABLED
139-
#ifdef _GODOT_CPP_AVOID_THREAD_LOCAL
140-
std::lock_guard<std::recursive_mutex> lk(Wrapped::_constructing_mutex);
141-
#endif
142-
Wrapped::_constructing_recreate_owner = obj;
139+
Wrapped::_get_construct_info().recreate_owner = obj;
143140
T *new_instance = (T *)memalloc(sizeof(T));
144141
memnew_placement(new_instance, T);
145142
return new_instance;

src/classes/wrapped.cpp

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -40,26 +40,11 @@
4040

4141
namespace godot {
4242

43-
#ifdef _GODOT_CPP_AVOID_THREAD_LOCAL
44-
std::recursive_mutex Wrapped::_constructing_mutex;
45-
#endif
46-
47-
_GODOT_CPP_THREAD_LOCAL const StringName *Wrapped::_constructing_extension_class_name = nullptr;
48-
_GODOT_CPP_THREAD_LOCAL const GDExtensionInstanceBindingCallbacks *Wrapped::_constructing_class_binding_callbacks = nullptr;
49-
50-
#ifdef HOT_RELOAD_ENABLED
51-
_GODOT_CPP_THREAD_LOCAL GDExtensionObjectPtr Wrapped::_constructing_recreate_owner = nullptr;
52-
#endif
53-
5443
const StringName *Wrapped::_get_extension_class_name() {
5544
return nullptr;
5645
}
5746

5847
void Wrapped::_postinitialize() {
59-
#ifdef _GODOT_CPP_AVOID_THREAD_LOCAL
60-
Wrapped::_constructing_mutex.unlock();
61-
#endif
62-
6348
#if GODOT_VERSION_MINOR >= 4
6449
Object *obj = dynamic_cast<Object *>(this);
6550
if (obj) {
@@ -74,10 +59,12 @@ void Wrapped::_postinitialize() {
7459
}
7560

7661
Wrapped::Wrapped(const StringName &p_godot_class) {
62+
ConstructInfo &info = _get_construct_info();
63+
7764
#ifdef HOT_RELOAD_ENABLED
78-
if (unlikely(Wrapped::_constructing_recreate_owner)) {
79-
_owner = Wrapped::_constructing_recreate_owner;
80-
Wrapped::_constructing_recreate_owner = nullptr;
65+
if (unlikely(info.recreate_owner)) {
66+
_owner = info.recreate_owner;
67+
info.recreate_owner = nullptr;
8168
} else
8269
#endif
8370
{
@@ -90,14 +77,14 @@ Wrapped::Wrapped(const StringName &p_godot_class) {
9077
#endif
9178
}
9279

93-
if (_constructing_extension_class_name) {
94-
::godot::gdextension_interface::object_set_instance(_owner, reinterpret_cast<GDExtensionConstStringNamePtr>(_constructing_extension_class_name), this);
95-
_constructing_extension_class_name = nullptr;
80+
if (info.extension_class_name) {
81+
::godot::gdextension_interface::object_set_instance(_owner, reinterpret_cast<GDExtensionConstStringNamePtr>(info.extension_class_name), this);
82+
info.extension_class_name = nullptr;
9683
}
9784

98-
if (likely(_constructing_class_binding_callbacks)) {
99-
::godot::gdextension_interface::object_set_instance_binding(_owner, ::godot::gdextension_interface::token, this, _constructing_class_binding_callbacks);
100-
_constructing_class_binding_callbacks = nullptr;
85+
if (likely(info.class_binding_callbacks)) {
86+
::godot::gdextension_interface::object_set_instance_binding(_owner, ::godot::gdextension_interface::token, this, info.class_binding_callbacks);
87+
info.class_binding_callbacks = nullptr;
10188
} else {
10289
CRASH_NOW_MSG("BUG: Godot Object created without binding callbacks. Did you forget to use memnew()?");
10390
}

0 commit comments

Comments
 (0)