Commit 0b814ea7 by Ignacio Etcheverry

Mono/C#: Optimize the way we store GC handles for scripts

Don't store GC handles for C# script instances and instance bindings as 'Ref<MonoGCHandle>'; store the raw data instead. Initially this was not possible as we needed to store a Variant, but this had not been the case for a looong time yet the stored type was never updated.
parent 989a223c
......@@ -240,7 +240,7 @@ class CSharpInstance : public ScriptInstance {
bool destructing_script_instance = false;
Ref<CSharpScript> script;
Ref<MonoGCHandle> gchandle;
MonoGCHandleData gchandle;
Vector<Callable> event_signal_callables;
......@@ -258,7 +258,7 @@ class CSharpInstance : public ScriptInstance {
// Do not use unless you know what you are doing
friend void GDMonoInternals::tie_managed_to_unmanaged(MonoObject *, Object *);
static CSharpInstance *create_for_managed_type(Object *p_owner, CSharpScript *p_script, const Ref<MonoGCHandle> &p_gchandle);
static CSharpInstance *create_for_managed_type(Object *p_owner, CSharpScript *p_script, const MonoGCHandleData &p_gchandle);
void _call_multilevel(MonoObject *p_mono_object, const StringName &p_method, const Variant **p_args, int p_argcount);
......@@ -326,8 +326,14 @@ struct CSharpScriptBinding {
bool inited;
StringName type_name;
GDMonoClass *wrapper_class;
Ref<MonoGCHandle> gchandle;
MonoGCHandleData gchandle;
Object *owner;
CSharpScriptBinding() :
inited(false),
wrapper_class(NULL),
owner(NULL) {
}
};
class ManagedCallableMiddleman : public Object {
......@@ -414,8 +420,8 @@ public:
_FORCE_INLINE_ EditorPlugin *get_godotsharp_editor() const { return godotsharp_editor; }
#endif
static void release_script_gchandle(Ref<MonoGCHandle> &p_gchandle);
static void release_script_gchandle(MonoObject *p_expected_obj, Ref<MonoGCHandle> &p_gchandle);
static void release_script_gchandle(MonoGCHandleData &p_gchandle);
static void release_script_gchandle(MonoObject *p_expected_obj, MonoGCHandleData &p_gchandle);
bool debug_break(const String &p_error, bool p_allow_continue = true);
bool debug_break_parse(const String &p_file, int p_line, const String &p_error);
......
......@@ -70,8 +70,8 @@ void godot_icall_Object_Disposed(MonoObject *p_obj, Object *p_ptr) {
if (data) {
CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
if (script_binding.inited) {
Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
if (gchandle.is_valid()) {
MonoGCHandleData &gchandle = script_binding.gchandle;
if (!gchandle.is_released()) {
CSharpLanguage::release_script_gchandle(p_obj, gchandle);
}
}
......@@ -117,8 +117,8 @@ void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, MonoBoolea
if (data) {
CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
if (script_binding.inited) {
Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
if (gchandle.is_valid()) {
MonoGCHandleData &gchandle = script_binding.gchandle;
if (!gchandle.is_released()) {
CSharpLanguage::release_script_gchandle(p_obj, gchandle);
}
}
......
......@@ -44,8 +44,8 @@ bool ManagedCallable::compare_equal(const CallableCustom *p_a, const CallableCus
const ManagedCallable *a = static_cast<const ManagedCallable *>(p_a);
const ManagedCallable *b = static_cast<const ManagedCallable *>(p_b);
MonoDelegate *delegate_a = (MonoDelegate *)a->delegate_handle->get_target();
MonoDelegate *delegate_b = (MonoDelegate *)b->delegate_handle->get_target();
MonoDelegate *delegate_a = (MonoDelegate *)a->delegate_handle.get_target();
MonoDelegate *delegate_b = (MonoDelegate *)b->delegate_handle.get_target();
if (!delegate_a || !delegate_b) {
if (!delegate_a && !delegate_b)
......@@ -66,7 +66,7 @@ bool ManagedCallable::compare_less(const CallableCustom *p_a, const CallableCust
uint32_t ManagedCallable::hash() const {
// hmm
uint32_t hash = delegate_invoke->get_name().hash();
return hash_djb2_one_64(delegate_handle.ptr()->handle, hash);
return hash_djb2_one_64(delegate_handle.handle, hash);
}
String ManagedCallable::get_as_text() const {
......@@ -92,12 +92,12 @@ void ManagedCallable::call(const Variant **p_arguments, int p_argcount, Variant
#ifdef GD_MONO_HOT_RELOAD
// Lost during hot-reload
ERR_FAIL_NULL(delegate_invoke);
ERR_FAIL_COND(delegate_handle.is_null());
ERR_FAIL_COND(delegate_handle.is_released());
#endif
ERR_FAIL_COND(delegate_invoke->get_parameters_count() < p_argcount);
MonoObject *delegate = delegate_handle->get_target();
MonoObject *delegate = delegate_handle.get_target();
MonoException *exc = NULL;
MonoObject *ret = delegate_invoke->invoke(delegate, p_arguments, &exc);
......@@ -111,7 +111,7 @@ void ManagedCallable::call(const Variant **p_arguments, int p_argcount, Variant
}
void ManagedCallable::set_delegate(MonoDelegate *p_delegate) {
delegate_handle = MonoGCHandle::create_strong((MonoObject *)p_delegate);
delegate_handle = MonoGCHandleData::new_strong_handle((MonoObject *)p_delegate);
MonoMethod *delegate_invoke_raw = mono_get_delegate_invoke(mono_object_get_class((MonoObject *)p_delegate));
const StringName &delegate_invoke_name = CSharpLanguage::get_singleton()->get_string_names().delegate_invoke_method_name;
delegate_invoke = memnew(GDMonoMethod(delegate_invoke_name, delegate_invoke_raw)); // TODO: Use pooling for this GDMonoMethod instances
......@@ -132,12 +132,14 @@ ManagedCallable::ManagedCallable(MonoDelegate *p_delegate) {
#endif
}
#ifdef GD_MONO_HOT_RELOAD
ManagedCallable::~ManagedCallable() {
#ifdef GD_MONO_HOT_RELOAD
{
MutexLock lock(instances_mutex);
instances.remove(&self_instance);
instances_pending_reload.erase(this);
}
}
#endif
delegate_handle.release();
}
......@@ -42,7 +42,7 @@
class ManagedCallable : public CallableCustom {
friend class CSharpLanguage;
Ref<MonoGCHandle> delegate_handle;
MonoGCHandleData delegate_handle;
GDMonoMethod *delegate_invoke;
#ifdef GD_MONO_HOT_RELOAD
......@@ -60,7 +60,7 @@ public:
ObjectID get_object() const override;
void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
_FORCE_INLINE_ MonoDelegate *get_delegate() { return (MonoDelegate *)delegate_handle->get_target(); }
_FORCE_INLINE_ MonoDelegate *get_delegate() { return (MonoDelegate *)delegate_handle.get_target(); }
void set_delegate(MonoDelegate *p_delegate);
......@@ -71,9 +71,7 @@ public:
static constexpr CompareEqualFunc compare_less_func_ptr = &ManagedCallable::compare_less;
ManagedCallable(MonoDelegate *p_delegate);
#ifdef GD_MONO_HOT_RELOAD
~ManagedCallable();
#endif
};
#endif // MANAGED_CALLABLE_H
......@@ -32,56 +32,35 @@
#include "mono_gd/gd_mono.h"
uint32_t MonoGCHandle::new_strong_handle(MonoObject *p_object) {
return mono_gchandle_new(p_object, /* pinned: */ false);
}
uint32_t MonoGCHandle::new_strong_handle_pinned(MonoObject *p_object) {
return mono_gchandle_new(p_object, /* pinned: */ true);
}
uint32_t MonoGCHandle::new_weak_handle(MonoObject *p_object) {
return mono_gchandle_new_weakref(p_object, /* track_resurrection: */ false);
}
void MonoGCHandle::free_handle(uint32_t p_gchandle) {
void MonoGCHandleData::release() {
#ifdef DEBUG_ENABLED
CRASH_COND(handle && GDMono::get_singleton() == NULL);
#endif
mono_gchandle_free(p_gchandle);
if (handle && GDMono::get_singleton()->is_runtime_initialized()) {
GDMonoUtils::free_gchandle(handle);
handle = 0;
}
}
Ref<MonoGCHandle> MonoGCHandle::create_strong(MonoObject *p_object) {
return memnew(MonoGCHandle(new_strong_handle(p_object), STRONG_HANDLE));
MonoGCHandleData MonoGCHandleData::new_strong_handle(MonoObject *p_object) {
return MonoGCHandleData(GDMonoUtils::new_strong_gchandle(p_object), gdmono::GCHandleType::STRONG_HANDLE);
}
Ref<MonoGCHandle> MonoGCHandle::create_weak(MonoObject *p_object) {
return memnew(MonoGCHandle(new_weak_handle(p_object), WEAK_HANDLE));
MonoGCHandleData MonoGCHandleData::new_strong_handle_pinned(MonoObject *p_object) {
return MonoGCHandleData(GDMonoUtils::new_strong_gchandle_pinned(p_object), gdmono::GCHandleType::STRONG_HANDLE);
}
void MonoGCHandle::release() {
#ifdef DEBUG_ENABLED
CRASH_COND(!released && GDMono::get_singleton() == NULL);
#endif
if (!released && GDMono::get_singleton()->is_runtime_initialized()) {
free_handle(handle);
released = true;
}
MonoGCHandleData MonoGCHandleData::new_weak_handle(MonoObject *p_object) {
return MonoGCHandleData(GDMonoUtils::new_weak_gchandle(p_object), gdmono::GCHandleType::WEAK_HANDLE);
}
MonoGCHandle::MonoGCHandle(uint32_t p_handle, HandleType p_handle_type) {
Ref<MonoGCHandleRef> MonoGCHandleRef::create_strong(MonoObject *p_object) {
released = false;
weak = p_handle_type == WEAK_HANDLE;
handle = p_handle;
return memnew(MonoGCHandleRef(MonoGCHandleData::new_strong_handle(p_object)));
}
MonoGCHandle::~MonoGCHandle() {
Ref<MonoGCHandleRef> MonoGCHandleRef::create_weak(MonoObject *p_object) {
release();
return memnew(MonoGCHandleRef(MonoGCHandleData::new_weak_handle(p_object)));
}
......@@ -35,44 +35,79 @@
#include "core/reference.h"
class MonoGCHandle : public Reference {
namespace gdmono {
GDCLASS(MonoGCHandle, Reference);
enum class GCHandleType : char {
NIL,
STRONG_HANDLE,
WEAK_HANDLE
};
bool released;
bool weak;
}
friend class ManagedCallable;
// Manual release of the GC handle must be done when using this struct
struct MonoGCHandleData {
uint32_t handle;
gdmono::GCHandleType type;
public:
enum HandleType {
STRONG_HANDLE,
WEAK_HANDLE
};
_FORCE_INLINE_ bool is_released() const { return !handle; }
_FORCE_INLINE_ bool is_weak() const { return type == gdmono::GCHandleType::WEAK_HANDLE; }
static uint32_t new_strong_handle(MonoObject *p_object);
static uint32_t new_strong_handle_pinned(MonoObject *p_object);
static uint32_t new_weak_handle(MonoObject *p_object);
static void free_handle(uint32_t p_gchandle);
_FORCE_INLINE_ MonoObject *get_target() const { return handle ? mono_gchandle_get_target(handle) : NULL; }
static Ref<MonoGCHandle> create_strong(MonoObject *p_object);
static Ref<MonoGCHandle> create_weak(MonoObject *p_object);
void release();
_FORCE_INLINE_ bool is_released() { return released; }
_FORCE_INLINE_ bool is_weak() { return weak; }
MonoGCHandleData &operator=(const MonoGCHandleData &p_other) {
#ifdef DEBUG_ENABLED
CRASH_COND(!is_released());
#endif
handle = p_other.handle;
type = p_other.type;
return *this;
}
_FORCE_INLINE_ MonoObject *get_target() const { return released ? NULL : mono_gchandle_get_target(handle); }
MonoGCHandleData(const MonoGCHandleData &) = default;
_FORCE_INLINE_ void set_handle(uint32_t p_handle, HandleType p_handle_type) {
released = false;
weak = p_handle_type == WEAK_HANDLE;
handle = p_handle;
MonoGCHandleData() :
handle(0),
type(gdmono::GCHandleType::NIL) {
}
void release();
MonoGCHandle(uint32_t p_handle, HandleType p_handle_type);
~MonoGCHandle();
MonoGCHandleData(uint32_t p_handle, gdmono::GCHandleType p_type) :
handle(p_handle),
type(p_type) {
}
static MonoGCHandleData new_strong_handle(MonoObject *p_object);
static MonoGCHandleData new_strong_handle_pinned(MonoObject *p_object);
static MonoGCHandleData new_weak_handle(MonoObject *p_object);
};
class MonoGCHandleRef : public Reference {
GDCLASS(MonoGCHandleRef, Reference);
MonoGCHandleData data;
public:
static Ref<MonoGCHandleRef> create_strong(MonoObject *p_object);
static Ref<MonoGCHandleRef> create_weak(MonoObject *p_object);
_FORCE_INLINE_ bool is_released() const { return data.is_released(); }
_FORCE_INLINE_ bool is_weak() const { return data.is_weak(); }
_FORCE_INLINE_ MonoObject *get_target() const { return data.get_target(); }
void release() { data.release(); }
_FORCE_INLINE_ void set_handle(uint32_t p_handle, gdmono::GCHandleType p_handle_type) {
data = MonoGCHandleData(p_handle, p_handle_type);
}
MonoGCHandleRef(const MonoGCHandleData &p_gc_handle_data) :
data(p_gc_handle_data) {
}
~MonoGCHandleRef() { release(); }
};
#endif // CSHARP_GC_HANDLE_H
......@@ -186,7 +186,7 @@ void CachedData::clear_godot_api_cache() {
// End of MarshalUtils methods
task_scheduler_handle = Ref<MonoGCHandle>();
task_scheduler_handle = Ref<MonoGCHandleRef>();
}
#define GODOT_API_CLASS(m_class) (GDMono::get_singleton()->get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, #m_class))
......@@ -316,7 +316,7 @@ void update_godot_api_cache() {
// TODO Move to CSharpLanguage::init() and do handle disposal
MonoObject *task_scheduler = mono_object_new(mono_domain_get(), GODOT_API_CLASS(GodotTaskScheduler)->get_mono_ptr());
GDMonoUtils::runtime_object_init(task_scheduler, GODOT_API_CLASS(GodotTaskScheduler));
cached_data.task_scheduler_handle = MonoGCHandle::create_strong(task_scheduler);
cached_data.task_scheduler_handle = MonoGCHandleRef::create_strong(task_scheduler);
cached_data.godot_api_cache_updated = true;
}
......
......@@ -156,7 +156,7 @@ struct CachedData {
// End of MarshalUtils methods
Ref<MonoGCHandle> task_scheduler_handle;
Ref<MonoGCHandleRef> task_scheduler_handle;
bool corlib_cache_updated;
bool godot_api_cache_updated;
......
......@@ -75,7 +75,7 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) {
script_binding.inited = true;
script_binding.type_name = NATIVE_GDMONOCLASS_NAME(klass);
script_binding.wrapper_class = klass;
script_binding.gchandle = ref ? MonoGCHandle::create_weak(managed) : MonoGCHandle::create_strong(managed);
script_binding.gchandle = ref ? MonoGCHandleData::new_weak_handle(managed) : MonoGCHandleData::new_strong_handle(managed);
script_binding.owner = unmanaged;
if (ref) {
......@@ -101,7 +101,7 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) {
return;
}
Ref<MonoGCHandle> gchandle = ref ? MonoGCHandle::create_weak(managed) : MonoGCHandle::create_strong(managed);
MonoGCHandleData gchandle = ref ? MonoGCHandleData::new_weak_handle(managed) : MonoGCHandleData::new_strong_handle(managed);
Ref<CSharpScript> script = CSharpScript::create_for_managed_type(klass, native);
......
......@@ -86,10 +86,9 @@ MonoObject *unmanaged_get_managed(Object *unmanaged) {
}
}
Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
ERR_FAIL_COND_V(gchandle.is_null(), NULL);
MonoGCHandleData &gchandle = script_binding.gchandle;
MonoObject *target = gchandle->get_target();
MonoObject *target = gchandle.get_target();
if (target)
return target;
......@@ -106,7 +105,7 @@ MonoObject *unmanaged_get_managed(Object *unmanaged) {
MonoObject *mono_object = GDMonoUtils::create_managed_for_godot_object(script_binding.wrapper_class, script_binding.type_name, unmanaged);
ERR_FAIL_NULL_V(mono_object, NULL);
gchandle->set_handle(MonoGCHandle::new_strong_handle(mono_object), MonoGCHandle::STRONG_HANDLE);
gchandle = MonoGCHandleData::new_strong_handle(mono_object);
// Tie managed to unmanaged
Reference *ref = Object::cast_to<Reference>(unmanaged);
......@@ -156,6 +155,22 @@ bool is_thread_attached() {
return mono_domain_get() != NULL;
}
uint32_t new_strong_gchandle(MonoObject *p_object) {
return mono_gchandle_new(p_object, /* pinned: */ false);
}
uint32_t new_strong_gchandle_pinned(MonoObject *p_object) {
return mono_gchandle_new(p_object, /* pinned: */ true);
}
uint32_t new_weak_gchandle(MonoObject *p_object) {
return mono_gchandle_new_weakref(p_object, /* track_resurrection: */ false);
}
void free_gchandle(uint32_t p_gchandle) {
mono_gchandle_free(p_gchandle);
}
void runtime_object_init(MonoObject *p_this_obj, GDMonoClass *p_class, MonoException **r_exc) {
GDMonoMethod *ctor = p_class->get_method(".ctor", 0);
ERR_FAIL_NULL(ctor);
......
......@@ -92,6 +92,11 @@ _FORCE_INLINE_ bool is_main_thread() {
return mono_domain_get() != NULL && mono_thread_get_main() == mono_thread_current();
}
uint32_t new_strong_gchandle(MonoObject *p_object);
uint32_t new_strong_gchandle_pinned(MonoObject *p_object);
uint32_t new_weak_gchandle(MonoObject *p_object);
void free_gchandle(uint32_t p_gchandle);
void runtime_object_init(MonoObject *p_this_obj, GDMonoClass *p_class, MonoException **r_exc = NULL);
bool mono_delegate_equal(MonoDelegate *p_a, MonoDelegate *p_b);
......
......@@ -114,8 +114,15 @@ void SignalAwaiterCallable::call(const Variant **p_arguments, int p_argcount, Va
mono_array_setref(signal_args, i, boxed);
}
MonoObject *awaiter = awaiter_handle.get_target();
if (!awaiter) {
r_call_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL;
return;
}
MonoException *exc = NULL;
CACHED_METHOD_THUNK(SignalAwaiter, SignalCallback).invoke(awaiter_handle->get_target(), signal_args, &exc);
CACHED_METHOD_THUNK(SignalAwaiter, SignalCallback).invoke(awaiter, signal_args, &exc);
if (exc) {
GDMonoUtils::set_pending_exception(exc);
......@@ -127,10 +134,14 @@ void SignalAwaiterCallable::call(const Variant **p_arguments, int p_argcount, Va
SignalAwaiterCallable::SignalAwaiterCallable(Object *p_target, MonoObject *p_awaiter, const StringName &p_signal) :
target_id(p_target->get_instance_id()),
awaiter_handle(MonoGCHandle::create_strong(p_awaiter)),
awaiter_handle(MonoGCHandleData::new_strong_handle(p_awaiter)),
signal(p_signal) {
}
SignalAwaiterCallable::~SignalAwaiterCallable() {
awaiter_handle.release();
}
bool EventSignalCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) {
const EventSignalCallable *a = static_cast<const EventSignalCallable *>(p_a);
const EventSignalCallable *b = static_cast<const EventSignalCallable *>(p_b);
......@@ -159,7 +170,6 @@ String EventSignalCallable::get_as_text() const {
String class_name = owner->get_class();
Ref<Script> script = owner->get_script();
if (script.is_valid() && script->get_path().is_resource_file()) {
class_name += "(" + script->get_path().get_file() + ")";
}
StringName signal = event_signal->field->get_name();
......
......@@ -40,7 +40,7 @@ Error gd_mono_connect_signal_awaiter(Object *p_source, const StringName &p_signa
class SignalAwaiterCallable : public CallableCustom {
ObjectID target_id;
Ref<MonoGCHandle> awaiter_handle;
MonoGCHandleData awaiter_handle;
StringName signal;
public:
......@@ -64,6 +64,7 @@ public:
void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
SignalAwaiterCallable(Object *p_target, MonoObject *p_awaiter, const StringName &p_signal);
~SignalAwaiterCallable();
};
class EventSignalCallable : public CallableCustom {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment