summaryrefslogtreecommitdiff
path: root/modules/mono/csharp_script.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/mono/csharp_script.cpp')
-rw-r--r--modules/mono/csharp_script.cpp483
1 files changed, 361 insertions, 122 deletions
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index d2a861dbe8..91fd482235 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -32,10 +32,10 @@
#include <mono/metadata/threads.h>
-#include "os/file_access.h"
-#include "os/os.h"
-#include "os/thread.h"
-#include "project_settings.h"
+#include "core/os/file_access.h"
+#include "core/os/os.h"
+#include "core/os/thread.h"
+#include "core/project_settings.h"
#ifdef TOOLS_ENABLED
#include "editor/bindings_generator.h"
@@ -107,7 +107,7 @@ void CSharpLanguage::init() {
gdmono = memnew(GDMono);
gdmono->initialize();
-#ifdef MONO_GLUE_DISABLED
+#ifndef MONO_GLUE_ENABLED
WARN_PRINT("This binary is built with `mono_glue=no` and cannot be used for scripting");
#endif
@@ -138,7 +138,7 @@ void CSharpLanguage::finish() {
#endif
// Release gchandle bindings before finalizing mono runtime
- gchandle_bindings.clear();
+ script_bindings.clear();
if (gdmono) {
memdelete(gdmono);
@@ -551,22 +551,22 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec
void CSharpLanguage::frame() {
- const Ref<MonoGCHandle> &task_scheduler_handle = GDMonoUtils::mono_cache.task_scheduler_handle;
+ if (gdmono && gdmono->is_runtime_initialized() && gdmono->get_core_api_assembly() != NULL) {
+ const Ref<MonoGCHandle> &task_scheduler_handle = GDMonoUtils::mono_cache.task_scheduler_handle;
- if (task_scheduler_handle.is_valid()) {
- MonoObject *task_scheduler = task_scheduler_handle->get_target();
+ if (task_scheduler_handle.is_valid()) {
+ MonoObject *task_scheduler = task_scheduler_handle->get_target();
- if (task_scheduler) {
- GDMonoUtils::GodotTaskScheduler_Activate thunk = CACHED_METHOD_THUNK(GodotTaskScheduler, Activate);
+ if (task_scheduler) {
+ GDMonoUtils::GodotTaskScheduler_Activate thunk = CACHED_METHOD_THUNK(GodotTaskScheduler, Activate);
- ERR_FAIL_NULL(thunk);
+ MonoException *exc = NULL;
+ thunk(task_scheduler, (MonoObject **)&exc);
- MonoException *exc = NULL;
- thunk(task_scheduler, (MonoObject **)&exc);
-
- if (exc) {
- GDMonoUtils::debug_unhandled_exception(exc);
- _UNREACHABLE_();
+ if (exc) {
+ GDMonoUtils::debug_unhandled_exception(exc);
+ _UNREACHABLE_();
+ }
}
}
}
@@ -892,6 +892,48 @@ void CSharpLanguage::set_language_index(int p_idx) {
lang_idx = p_idx;
}
+void CSharpLanguage::release_script_gchandle(Ref<MonoGCHandle> &p_gchandle) {
+
+ if (!p_gchandle->is_released()) { // Do not locking unnecessarily
+#ifndef NO_THREADS
+ get_singleton()->script_gchandle_release_lock->lock();
+#endif
+
+ p_gchandle->release();
+
+#ifndef NO_THREADS
+ get_singleton()->script_gchandle_release_lock->unlock();
+#endif
+ }
+}
+
+void CSharpLanguage::release_script_gchandle(MonoObject *p_pinned_expected_obj, Ref<MonoGCHandle> &p_gchandle) {
+
+ uint32_t pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(p_pinned_expected_obj); // we might lock after this, so pin it
+
+ if (!p_gchandle->is_released()) { // Do not locking unnecessarily
+#ifndef NO_THREADS
+ get_singleton()->script_gchandle_release_lock->lock();
+#endif
+
+ MonoObject *target = p_gchandle->get_target();
+
+ // We release the gchandle if it points to the MonoObject* we expect (otherwise it was
+ // already released and could have been replaced) or if we can't get its target MonoObject*
+ // (which doesn't necessarily mean it was released, and we want it released in order to
+ // avoid locking other threads unnecessarily).
+ if (target == p_pinned_expected_obj || target == NULL) {
+ p_gchandle->release();
+ }
+
+#ifndef NO_THREADS
+ get_singleton()->script_gchandle_release_lock->unlock();
+#endif
+ }
+
+ MonoGCHandle::free_handle(pinned_gchandle);
+}
+
CSharpLanguage::CSharpLanguage() {
ERR_FAIL_COND(singleton);
@@ -904,9 +946,11 @@ CSharpLanguage::CSharpLanguage() {
#ifdef NO_THREADS
lock = NULL;
gchandle_bind_lock = NULL;
+ script_gchandle_release_lock = NULL;
#else
lock = Mutex::create();
script_bind_lock = Mutex::create();
+ script_gchandle_release_lock = Mutex::create();
#endif
lang_idx = -1;
@@ -926,6 +970,11 @@ CSharpLanguage::~CSharpLanguage() {
script_bind_lock = NULL;
}
+ if (script_gchandle_release_lock) {
+ memdelete(script_gchandle_release_lock);
+ script_gchandle_release_lock = NULL;
+ }
+
singleton = NULL;
}
@@ -954,30 +1003,34 @@ void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) {
ERR_FAIL_NULL_V(mono_object, NULL);
- // Tie managed to unmanaged
- Reference *ref = Object::cast_to<Reference>(p_object);
-
- if (ref) {
- // Unsafe refcount increment. The managed instance also counts as a reference.
- // This way if the unmanaged world has no references to our owner
- // but the managed instance is alive, the refcount will be 1 instead of 0.
- // See: _GodotSharp::_dispose_object(Object *p_object)
+ CSharpScriptBinding script_binding;
- ref->reference();
- }
-
- Ref<MonoGCHandle> gchandle = MonoGCHandle::create_strong(mono_object);
+ script_binding.type_name = type_name;
+ script_binding.wrapper_class = type_class; // cache
+ script_binding.gchandle = MonoGCHandle::create_strong(mono_object);
#ifndef NO_THREADS
script_bind_lock->lock();
#endif
- void *data = (void *)gchandle_bindings.insert(p_object, gchandle);
+ void *data = (void *)script_bindings.insert(p_object, script_binding);
#ifndef NO_THREADS
script_bind_lock->unlock();
#endif
+ // Tie managed to unmanaged
+ Reference *ref = Object::cast_to<Reference>(p_object);
+
+ if (ref) {
+ // Unsafe refcount increment. The managed instance also counts as a reference.
+ // This way if the unmanaged world has no references to our owner
+ // but the managed instance is alive, the refcount will be 1 instead of 0.
+ // See: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr)
+
+ ref->reference();
+ }
+
return data;
}
@@ -985,7 +1038,7 @@ void CSharpLanguage::free_instance_binding_data(void *p_data) {
if (GDMono::get_singleton() == NULL) {
#ifdef DEBUG_ENABLED
- CRASH_COND(!gchandle_bindings.empty());
+ CRASH_COND(!script_bindings.empty());
#endif
// Mono runtime finalized, all the gchandle bindings were already released
return;
@@ -998,15 +1051,15 @@ void CSharpLanguage::free_instance_binding_data(void *p_data) {
script_bind_lock->lock();
#endif
- Map<Object *, Ref<MonoGCHandle> >::Element *data = (Map<Object *, Ref<MonoGCHandle> >::Element *)p_data;
+ Map<Object *, CSharpScriptBinding>::Element *data = (Map<Object *, CSharpScriptBinding>::Element *)p_data;
// Set the native instance field to IntPtr.Zero, if not yet garbage collected
- MonoObject *mono_object = data->value()->get_target();
+ MonoObject *mono_object = data->value().gchandle->get_target();
if (mono_object) {
CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, NULL);
}
- gchandle_bindings.erase(data);
+ script_bindings.erase(data);
#ifndef NO_THREADS
script_bind_lock->unlock();
@@ -1024,7 +1077,7 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) {
void *data = p_object->get_script_instance_binding(get_language_index());
if (!data)
return;
- Ref<MonoGCHandle> &gchandle = ((Map<Object *, Ref<MonoGCHandle> >::Element *)data)->get();
+ Ref<MonoGCHandle> &gchandle = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get().gchandle;
if (ref_owner->reference_get_count() > 1 && gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0
// The reference count was increased after the managed side was the only one referencing our owner.
@@ -1036,7 +1089,7 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) {
return; // Called after the managed side was collected, so nothing to do here
// Release the current weak handle and replace it with a strong handle.
- uint32_t strong_gchandle = MonoGCHandle::make_strong_handle(target);
+ uint32_t strong_gchandle = MonoGCHandle::new_strong_handle(target);
gchandle->release();
gchandle->set_handle(strong_gchandle, MonoGCHandle::STRONG_HANDLE);
}
@@ -1055,7 +1108,7 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) {
void *data = p_object->get_script_instance_binding(get_language_index());
if (!data)
return refcount == 0;
- Ref<MonoGCHandle> &gchandle = ((Map<Object *, Ref<MonoGCHandle> >::Element *)data)->get();
+ Ref<MonoGCHandle> &gchandle = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get().gchandle;
if (refcount == 1 && !gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0
// If owner owner is no longer referenced by the unmanaged side,
@@ -1066,7 +1119,7 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) {
return refcount == 0; // Called after the managed side was collected, so nothing to do here
// Release the current strong handle and replace it with a weak handle.
- uint32_t weak_gchandle = MonoGCHandle::make_weak_handle(target);
+ uint32_t weak_gchandle = MonoGCHandle::new_weak_handle(target);
gchandle->release();
gchandle->set_handle(weak_gchandle, MonoGCHandle::WEAK_HANDLE);
@@ -1096,9 +1149,8 @@ CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpS
}
MonoObject *CSharpInstance::get_mono_object() const {
-#ifdef DEBUG_ENABLED
- CRASH_COND(gchandle.is_null());
-#endif
+
+ ERR_FAIL_COND_V(gchandle.is_null(), NULL);
return gchandle->get_target();
}
@@ -1122,7 +1174,7 @@ bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) {
GDMonoProperty *property = script->script_class->get_property(p_name);
if (property) {
- property->set_value(mono_object, GDMonoMarshal::variant_to_mono_object(p_value));
+ property->set_value(mono_object, GDMonoMarshal::variant_to_mono_object(p_value, property->get_type()));
return true;
}
@@ -1326,10 +1378,12 @@ void CSharpInstance::call_multilevel_reversed(const StringName &p_method, const
call_multilevel(p_method, p_args, p_argcount);
}
-void CSharpInstance::_reference_owner_unsafe() {
+bool CSharpInstance::_reference_owner_unsafe() {
#ifdef DEBUG_ENABLED
CRASH_COND(!base_ref);
+ CRASH_COND(owner == NULL);
+ CRASH_COND(unsafe_referenced); // already referenced
#endif
// Unsafe refcount increment. The managed instance also counts as a reference.
@@ -1338,36 +1392,107 @@ void CSharpInstance::_reference_owner_unsafe() {
// See: _unreference_owner_unsafe()
// May not me referenced yet, so we must use init_ref() instead of reference()
- Object::cast_to<Reference>(owner)->init_ref();
+ bool success = Object::cast_to<Reference>(owner)->init_ref();
+ unsafe_referenced = success;
+ return success;
}
-void CSharpInstance::_unreference_owner_unsafe() {
+bool CSharpInstance::_unreference_owner_unsafe() {
#ifdef DEBUG_ENABLED
CRASH_COND(!base_ref);
+ CRASH_COND(owner == NULL);
#endif
+ if (!unsafe_referenced)
+ return false; // Already unreferenced
+
// Called from CSharpInstance::mono_object_disposed() or ~CSharpInstance()
// Unsafe refcount decrement. The managed instance also counts as a reference.
// See: _reference_owner_unsafe()
- if (Object::cast_to<Reference>(owner)->unreference()) {
+ bool die = static_cast<Reference *>(owner)->unreference();
+
+ if (die) {
memdelete(owner);
owner = NULL;
}
+
+ return die;
}
-void CSharpInstance::mono_object_disposed() {
+MonoObject *CSharpInstance::_internal_new_managed() {
+#ifdef DEBUG_ENABLED
+ CRASH_COND(!gchandle.is_valid());
+#endif
+
+ CSharpLanguage::get_singleton()->release_script_gchandle(gchandle);
+
+ ERR_FAIL_NULL_V(owner, NULL);
+ ERR_FAIL_COND_V(script.is_null(), NULL);
if (base_ref)
- _unreference_owner_unsafe();
+ _reference_owner_unsafe();
+
+ MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, script->script_class->get_mono_ptr());
+
+ if (!mono_object) {
+ script = Ref<CSharpScript>();
+ owner->set_script_instance(NULL);
+ ERR_EXPLAIN("Failed to allocate memory for the object");
+ ERR_FAIL_V(NULL);
+ }
+
+ CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, owner);
+
+ // Construct
+ GDMonoMethod *ctor = script->script_class->get_method(CACHED_STRING_NAME(dotctor), 0);
+ ctor->invoke_raw(mono_object, NULL);
+
+ // Tie managed to unmanaged
+ gchandle = MonoGCHandle::create_strong(mono_object);
+
+ return mono_object;
+}
+
+void CSharpInstance::mono_object_disposed(MonoObject *p_obj) {
+
+#ifdef DEBUG_ENABLED
+ CRASH_COND(base_ref == true);
+ CRASH_COND(gchandle.is_null());
+#endif
+ CSharpLanguage::get_singleton()->release_script_gchandle(p_obj, gchandle);
+}
+
+void CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_finalizer, bool &r_owner_deleted) {
+
+#ifdef DEBUG_ENABLED
+ CRASH_COND(base_ref == false);
+ CRASH_COND(gchandle.is_null());
+#endif
+ if (_unreference_owner_unsafe()) {
+ r_owner_deleted = true;
+ } else {
+ r_owner_deleted = false;
+ CSharpLanguage::get_singleton()->release_script_gchandle(p_obj, gchandle);
+ if (p_is_finalizer) {
+ // If the native instance is still alive, then it was
+ // referenced from another thread before the finalizer could
+ // unreference it and delete it, so we want to keep it.
+ // GC.ReRegisterForFinalize(this) is not safe because the objects
+ // referenced by this could have already been collected.
+ // Instead we will create a new managed instance here.
+ _internal_new_managed();
+ }
+ }
}
void CSharpInstance::refcount_incremented() {
#ifdef DEBUG_ENABLED
CRASH_COND(!base_ref);
+ CRASH_COND(owner == NULL);
#endif
Reference *ref_owner = Object::cast_to<Reference>(owner);
@@ -1378,7 +1503,7 @@ void CSharpInstance::refcount_incremented() {
// so the owner must hold the managed side alive again to avoid it from being GCed.
// Release the current weak handle and replace it with a strong handle.
- uint32_t strong_gchandle = MonoGCHandle::make_strong_handle(gchandle->get_target());
+ uint32_t strong_gchandle = MonoGCHandle::new_strong_handle(gchandle->get_target());
gchandle->release();
gchandle->set_handle(strong_gchandle, MonoGCHandle::STRONG_HANDLE);
}
@@ -1388,6 +1513,7 @@ bool CSharpInstance::refcount_decremented() {
#ifdef DEBUG_ENABLED
CRASH_COND(!base_ref);
+ CRASH_COND(owner == NULL);
#endif
Reference *ref_owner = Object::cast_to<Reference>(owner);
@@ -1399,7 +1525,7 @@ bool CSharpInstance::refcount_decremented() {
// the managed instance takes responsibility of deleting the owner when GCed.
// Release the current strong handle and replace it with a weak handle.
- uint32_t weak_gchandle = MonoGCHandle::make_weak_handle(gchandle->get_target());
+ uint32_t weak_gchandle = MonoGCHandle::new_weak_handle(gchandle->get_target());
gchandle->release();
gchandle->set_handle(weak_gchandle, MonoGCHandle::WEAK_HANDLE);
@@ -1415,18 +1541,20 @@ MultiplayerAPI::RPCMode CSharpInstance::_member_get_rpc_mode(GDMonoClassMember *
if (p_member->has_attribute(CACHED_CLASS(RemoteAttribute)))
return MultiplayerAPI::RPC_MODE_REMOTE;
- if (p_member->has_attribute(CACHED_CLASS(SyncAttribute)))
- return MultiplayerAPI::RPC_MODE_SYNC;
if (p_member->has_attribute(CACHED_CLASS(MasterAttribute)))
return MultiplayerAPI::RPC_MODE_MASTER;
+ if (p_member->has_attribute(CACHED_CLASS(PuppetAttribute)))
+ return MultiplayerAPI::RPC_MODE_PUPPET;
if (p_member->has_attribute(CACHED_CLASS(SlaveAttribute)))
- return MultiplayerAPI::RPC_MODE_SLAVE;
+ return MultiplayerAPI::RPC_MODE_PUPPET;
if (p_member->has_attribute(CACHED_CLASS(RemoteSyncAttribute)))
return MultiplayerAPI::RPC_MODE_REMOTESYNC;
+ if (p_member->has_attribute(CACHED_CLASS(SyncAttribute)))
+ return MultiplayerAPI::RPC_MODE_REMOTESYNC;
if (p_member->has_attribute(CACHED_CLASS(MasterSyncAttribute)))
return MultiplayerAPI::RPC_MODE_MASTERSYNC;
- if (p_member->has_attribute(CACHED_CLASS(SlaveSyncAttribute)))
- return MultiplayerAPI::RPC_MODE_SLAVESYNC;
+ if (p_member->has_attribute(CACHED_CLASS(PuppetSyncAttribute)))
+ return MultiplayerAPI::RPC_MODE_PUPPETSYNC;
return MultiplayerAPI::RPC_MODE_DISABLED;
}
@@ -1470,25 +1598,64 @@ MultiplayerAPI::RPCMode CSharpInstance::get_rset_mode(const StringName &p_variab
void CSharpInstance::notification(int p_notification) {
- MonoObject *mono_object = get_mono_object();
-
if (p_notification == Object::NOTIFICATION_PREDELETE) {
- if (mono_object != NULL) { // otherwise it was collected, and the finalizer already called NOTIFICATION_PREDELETE
- call_notification_no_check(mono_object, p_notification);
- // Set the native instance field to IntPtr.Zero
- CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, NULL);
+ // When NOTIFICATION_PREDELETE is sent, we also take the chance to call Dispose().
+ // It's safe to call Dispose() multiple times and NOTIFICATION_PREDELETE is guaranteed
+ // to be sent at least once, which happens right before the call to the destructor.
+
+ if (base_ref) {
+ // It's not safe to proceed if the owner derives Reference and the refcount reached 0.
+ // At this point, Dispose() was already called (manually or from the finalizer) so
+ // that's not a problem. The refcount wouldn't have reached 0 otherwise, since the
+ // managed side references it and Dispose() needs to be called to release it.
+ // However, this means C# Reference scripts can't receive NOTIFICATION_PREDELETE, but
+ // this is likely the case with GDScript as well: https://github.com/godotengine/godot/issues/6784
+ return;
+ }
+
+ _call_notification(p_notification);
+
+ MonoObject *mono_object = get_mono_object();
+ ERR_FAIL_NULL(mono_object);
+
+ GDMonoUtils::GodotObject_Dispose thunk = CACHED_METHOD_THUNK(GodotObject, Dispose);
+
+ MonoException *exc = NULL;
+ thunk(mono_object, (MonoObject **)&exc);
+
+ if (exc) {
+ GDMonoUtils::set_pending_exception(exc);
}
+
return;
}
- call_notification_no_check(mono_object, p_notification);
+ _call_notification(p_notification);
}
-void CSharpInstance::call_notification_no_check(MonoObject *p_mono_object, int p_notification) {
- Variant value = p_notification;
- const Variant *args[1] = { &value };
+void CSharpInstance::_call_notification(int p_notification) {
+
+ MonoObject *mono_object = get_mono_object();
+ ERR_FAIL_NULL(mono_object);
+
+ // Custom version of _call_multilevel, optimized for _notification
+
+ uint32_t arg = p_notification;
+ void *args[1] = { &arg };
+ StringName method_name = CACHED_STRING_NAME(_notification);
- _call_multilevel(p_mono_object, CACHED_STRING_NAME(_notification), args, 1);
+ GDMonoClass *top = script->script_class;
+
+ while (top && top != script->native) {
+ GDMonoMethod *method = top->get_method(method_name, 1);
+
+ if (method) {
+ method->invoke_raw(mono_object, args);
+ return;
+ }
+
+ top = top->get_parent_class();
+ }
}
Ref<Script> CSharpInstance::get_script() const {
@@ -1501,11 +1668,11 @@ ScriptLanguage *CSharpInstance::get_language() {
return CSharpLanguage::get_singleton();
}
-CSharpInstance::CSharpInstance() {
-
- owner = NULL;
- base_ref = false;
- ref_dying = false;
+CSharpInstance::CSharpInstance() :
+ owner(NULL),
+ base_ref(false),
+ ref_dying(false),
+ unsafe_referenced(false) {
}
CSharpInstance::~CSharpInstance() {
@@ -1514,10 +1681,7 @@ CSharpInstance::~CSharpInstance() {
gchandle->release(); // Make sure it's released
}
- if (base_ref && !ref_dying) { // it may be called from the owner's destructor
-#ifdef DEBUG_ENABLED
- CRASH_COND(!owner); // dunno, just in case
-#endif
+ if (base_ref && !ref_dying && owner) { // it may be called from the owner's destructor
_unreference_owner_unsafe();
}
@@ -1586,26 +1750,27 @@ bool CSharpScript::_update_exports() {
exported_members_cache.clear();
exported_members_defval_cache.clear();
- // We are creating a temporary new instance of the class here to get the default value
- // TODO Workaround. Should be replaced with IL opcodes analysis
+ // Here we create a temporary managed instance of the class to get the initial values
MonoObject *tmp_object = mono_object_new(SCRIPTS_DOMAIN, script_class->get_mono_ptr());
- if (tmp_object) {
- CACHED_FIELD(GodotObject, ptr)->set_value_raw(tmp_object, tmp_object); // FIXME WTF is this workaround
+ if (!tmp_object) {
+ ERR_PRINT("Failed to create temporary MonoObject");
+ return false;
+ }
- GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0);
- MonoException *exc = NULL;
- ctor->invoke(tmp_object, NULL, &exc);
+ uint32_t tmp_pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(tmp_object); // pin it (not sure if needed)
- if (exc) {
- ERR_PRINT("Exception thrown from constructor of temporary MonoObject:");
- GDMonoUtils::debug_print_unhandled_exception(exc);
- tmp_object = NULL;
- ERR_FAIL_V(false);
- }
- } else {
- ERR_PRINT("Failed to create temporary MonoObject");
+ GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0);
+ MonoException *ctor_exc = NULL;
+ ctor->invoke(tmp_object, NULL, &ctor_exc);
+
+ if (ctor_exc) {
+ MonoGCHandle::free_handle(tmp_pinned_gchandle);
+ tmp_object = NULL;
+
+ ERR_PRINT("Exception thrown from constructor of temporary MonoObject:");
+ GDMonoUtils::debug_print_unhandled_exception(ctor_exc);
return false;
}
@@ -1666,6 +1831,21 @@ bool CSharpScript::_update_exports() {
top = top->get_parent_class();
}
+
+ // Dispose the temporary managed instance
+
+ GDMonoUtils::GodotObject_Dispose thunk = CACHED_METHOD_THUNK(GodotObject, Dispose);
+
+ MonoException *exc = NULL;
+ thunk(tmp_object, (MonoObject **)&exc);
+
+ if (exc) {
+ ERR_PRINT("Exception thrown from method Dispose() of temporary MonoObject:");
+ GDMonoUtils::debug_print_unhandled_exception(exc);
+ }
+
+ MonoGCHandle::free_handle(tmp_pinned_gchandle);
+ tmp_object = NULL;
}
if (placeholders.size()) {
@@ -1750,6 +1930,10 @@ bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Ve
}
#ifdef TOOLS_ENABLED
+/**
+ * Returns false if there was an error, otherwise true.
+ * If there was an error, r_prop_info and r_exported are not assigned any value.
+ */
bool CSharpScript::_get_member_export(GDMonoClass *p_class, GDMonoClassMember *p_member, PropertyInfo &r_prop_info, bool &r_exported) {
StringName name = p_member->get_name();
@@ -1775,49 +1959,100 @@ bool CSharpScript::_get_member_export(GDMonoClass *p_class, GDMonoClassMember *p
Variant::Type variant_type = GDMonoMarshal::managed_to_variant_type(type);
- if (p_member->has_attribute(CACHED_CLASS(ExportAttribute))) {
- if (p_member->get_member_type() == GDMonoClassMember::MEMBER_TYPE_PROPERTY) {
- GDMonoProperty *property = static_cast<GDMonoProperty *>(p_member);
- if (!property->has_getter() || !property->has_setter()) {
- ERR_PRINTS("Cannot export property because it does not provide a getter or a setter: " + p_class->get_full_name() + "." + name.operator String());
- return false;
- }
+ if (!p_member->has_attribute(CACHED_CLASS(ExportAttribute))) {
+ r_prop_info = PropertyInfo(variant_type, name.operator String(), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE);
+ r_exported = false;
+ return true;
+ }
+
+ if (p_member->get_member_type() == GDMonoClassMember::MEMBER_TYPE_PROPERTY) {
+ GDMonoProperty *property = static_cast<GDMonoProperty *>(p_member);
+ if (!property->has_getter() || !property->has_setter()) {
+ ERR_PRINTS("Cannot export property because it does not provide a getter or a setter: " + p_class->get_full_name() + "." + name.operator String());
+ return false;
}
+ }
- MonoObject *attr = p_member->get_attribute(CACHED_CLASS(ExportAttribute));
+ MonoObject *attr = p_member->get_attribute(CACHED_CLASS(ExportAttribute));
- PropertyHint hint = PROPERTY_HINT_NONE;
- String hint_string;
+ PropertyHint hint = PROPERTY_HINT_NONE;
+ String hint_string;
- if (variant_type == Variant::NIL) {
- ERR_PRINTS("Unknown type of exported member: " + p_class->get_full_name() + "." + name.operator String());
- return false;
- } else if (variant_type == Variant::INT && type.type_encoding == MONO_TYPE_VALUETYPE && mono_class_is_enum(type.type_class->get_mono_ptr())) {
- variant_type = Variant::INT;
- hint = PROPERTY_HINT_ENUM;
+ if (variant_type == Variant::NIL) {
+ ERR_PRINTS("Unknown type of exported member: " + p_class->get_full_name() + "." + name.operator String());
+ return false;
+ } else if (variant_type == Variant::INT && type.type_encoding == MONO_TYPE_VALUETYPE && mono_class_is_enum(type.type_class->get_mono_ptr())) {
+ variant_type = Variant::INT;
+ hint = PROPERTY_HINT_ENUM;
+
+ Vector<MonoClassField *> fields = type.type_class->get_enum_fields();
+
+ MonoType *enum_basetype = mono_class_enum_basetype(type.type_class->get_mono_ptr());
- Vector<MonoClassField *> fields = type.type_class->get_enum_fields();
+ String name_only_hint_string;
- for (int i = 0; i < fields.size(); i++) {
- if (i > 0)
- hint_string += ",";
- hint_string += mono_field_get_name(fields[i]);
+ // True: enum Foo { Bar, Baz, Quux }
+ // True: enum Foo { Bar = 0, Baz = 1, Quux = 2 }
+ // False: enum Foo { Bar = 0, Baz = 7, Quux = 5 }
+ bool uses_default_values = true;
+
+ for (int i = 0; i < fields.size(); i++) {
+ MonoClassField *field = fields[i];
+
+ if (i > 0) {
+ hint_string += ",";
+ name_only_hint_string += ",";
}
- } else if (variant_type == Variant::OBJECT && CACHED_CLASS(GodotReference)->is_assignable_from(type.type_class)) {
- hint = PROPERTY_HINT_RESOURCE_TYPE;
- hint_string = NATIVE_GDMONOCLASS_NAME(type.type_class);
- } else {
- hint = PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr));
- hint_string = CACHED_FIELD(ExportAttribute, hintString)->get_string_value(attr);
+
+ String enum_field_name = mono_field_get_name(field);
+ hint_string += enum_field_name;
+ name_only_hint_string += enum_field_name;
+
+ // TODO:
+ // Instead of using mono_field_get_value_object, we can do this without boxing. Check the
+ // internal mono functions: ves_icall_System_Enum_GetEnumValuesAndNames and the get_enum_field.
+
+ MonoObject *val_obj = mono_field_get_value_object(mono_domain_get(), field, NULL);
+
+ if (val_obj == NULL) {
+ ERR_PRINTS("Failed to get '" + enum_field_name + "' constant enum value of exported member: " +
+ p_class->get_full_name() + "." + name.operator String());
+ return false;
+ }
+
+ bool r_error;
+ uint64_t val = GDMonoUtils::unbox_enum_value(val_obj, enum_basetype, r_error);
+ if (r_error) {
+ ERR_PRINTS("Failed to unbox '" + enum_field_name + "' constant enum value of exported member: " +
+ p_class->get_full_name() + "." + name.operator String());
+ return false;
+ }
+
+ if (val != i) {
+ uses_default_values = false;
+ }
+
+ hint_string += ":";
+ hint_string += String::num_uint64(val);
}
- r_prop_info = PropertyInfo(variant_type, name.operator String(), hint, hint_string, PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE);
- r_exported = true;
+ if (uses_default_values) {
+ // If we use the format NAME:VAL, that's what the editor displays.
+ // That's annoying if the user is not using custom values for the enum constants.
+ // This may not be needed in the future if the editor is changed to not display values.
+ hint_string = name_only_hint_string;
+ }
+ } else if (variant_type == Variant::OBJECT && CACHED_CLASS(GodotReference)->is_assignable_from(type.type_class)) {
+ hint = PROPERTY_HINT_RESOURCE_TYPE;
+ hint_string = NATIVE_GDMONOCLASS_NAME(type.type_class);
} else {
- r_prop_info = PropertyInfo(variant_type, name.operator String(), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE);
- r_exported = false;
+ hint = PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr));
+ hint_string = CACHED_FIELD(ExportAttribute, hintString)->get_string_value(attr);
}
+ r_prop_info = PropertyInfo(variant_type, name.operator String(), hint, hint_string, PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE);
+ r_exported = true;
+
return true;
}
#endif
@@ -2012,6 +2247,8 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
ERR_FAIL_V(NULL);
}
+ uint32_t pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(mono_object); // we might lock after this, so pin it
+
#ifndef NO_THREADS
CSharpLanguage::singleton->lock->lock();
#endif
@@ -2033,6 +2270,8 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
/* STEP 3, PARTY */
+ MonoGCHandle::free_handle(pinned_gchandle);
+
//@TODO make thread safe
return instance;
}