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.cpp348
1 files changed, 239 insertions, 109 deletions
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index af5a0334c3..949c636050 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -27,6 +27,7 @@
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
+
#include "csharp_script.h"
#include <mono/metadata/threads.h>
@@ -122,6 +123,16 @@ void CSharpLanguage::init() {
void CSharpLanguage::finish() {
+ finalizing = true;
+
+#ifdef TOOLS_ENABLED
+ // Must be here, to avoid StringName leaks
+ if (BindingsGenerator::singleton) {
+ memdelete(BindingsGenerator::singleton);
+ BindingsGenerator::singleton = NULL;
+ }
+#endif
+
// Release gchandle bindings before finalizing mono runtime
gchandle_bindings.clear();
@@ -129,6 +140,8 @@ void CSharpLanguage::finish() {
memdelete(gdmono);
gdmono = NULL;
}
+
+ finalizing = false;
}
void CSharpLanguage::get_reserved_words(List<String> *p_words) const {
@@ -742,6 +755,8 @@ CSharpLanguage::CSharpLanguage() {
ERR_FAIL_COND(singleton);
singleton = this;
+ finalizing = false;
+
gdmono = NULL;
#ifdef NO_THREADS
@@ -798,12 +813,9 @@ void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) {
ERR_FAIL_NULL_V(mono_object, NULL);
// Tie managed to unmanaged
- bool strong_handle = true;
Reference *ref = Object::cast_to<Reference>(p_object);
if (ref) {
- strong_handle = false;
-
// 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.
@@ -812,8 +824,7 @@ void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) {
ref->reference();
}
- Ref<MonoGCHandle> gchandle = strong_handle ? MonoGCHandle::create_strong(mono_object) :
- MonoGCHandle::create_weak(mono_object);
+ Ref<MonoGCHandle> gchandle = MonoGCHandle::create_strong(mono_object);
#ifndef NO_THREADS
script_bind_lock->lock();
@@ -838,27 +849,38 @@ void CSharpLanguage::free_instance_binding_data(void *p_data) {
return;
}
+ if (finalizing)
+ return; // inside CSharpLanguage::finish(), all the gchandle bindings are released there
+
#ifndef NO_THREADS
script_bind_lock->lock();
#endif
- gchandle_bindings.erase((Map<Object *, Ref<MonoGCHandle> >::Element *)p_data);
+ Map<Object *, Ref<MonoGCHandle> >::Element *data = (Map<Object *, Ref<MonoGCHandle> >::Element *)p_data;
+
+ // Set the native instance field to IntPtr.Zero, if not yet garbage collected
+ MonoObject *mono_object = data->value()->get_target();
+ if (mono_object) {
+ CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, NULL);
+ }
+
+ gchandle_bindings.erase(data);
#ifndef NO_THREADS
script_bind_lock->unlock();
#endif
}
-void CSharpInstance::_ml_call_reversed(GDMonoClass *klass, const StringName &p_method, const Variant **p_args, int p_argcount) {
+void CSharpInstance::_ml_call_reversed(MonoObject *p_mono_object, GDMonoClass *p_klass, const StringName &p_method, const Variant **p_args, int p_argcount) {
- GDMonoClass *base = klass->get_parent_class();
+ GDMonoClass *base = p_klass->get_parent_class();
if (base && base != script->native)
- _ml_call_reversed(base, p_method, p_args, p_argcount);
+ _ml_call_reversed(p_mono_object, base, p_method, p_args, p_argcount);
- GDMonoMethod *method = klass->get_method(p_method, p_argcount);
+ GDMonoMethod *method = p_klass->get_method(p_method, p_argcount);
if (method) {
- method->invoke(get_mono_object(), p_args);
+ method->invoke(p_mono_object, p_args);
}
}
@@ -892,19 +914,23 @@ bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) {
ERR_FAIL_COND_V(!script.is_valid(), false);
+ MonoObject *mono_object = get_mono_object();
+ ERR_FAIL_NULL_V(mono_object, false);
+
GDMonoClass *top = script->script_class;
while (top && top != script->native) {
GDMonoField *field = script->script_class->get_field(p_name);
if (field) {
- MonoObject *mono_object = get_mono_object();
-
- ERR_EXPLAIN("Reference has been garbage collected?");
- ERR_FAIL_NULL_V(mono_object, false);
+ field->set_value_from_variant(mono_object, p_value);
+ return true;
+ }
- field->set_value(mono_object, 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));
return true;
}
@@ -913,16 +939,15 @@ bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) {
// Call _set
- Variant name = p_name;
- const Variant *args[2] = { &name, &p_value };
-
- MonoObject *mono_object = get_mono_object();
top = script->script_class;
while (top && top != script->native) {
GDMonoMethod *method = top->get_method(CACHED_STRING_NAME(_set), 2);
if (method) {
+ Variant name = p_name;
+ const Variant *args[2] = { &name, &p_value };
+
MonoObject *ret = method->invoke(mono_object, args);
if (ret && GDMonoMarshal::unbox<MonoBoolean>(ret) == true)
@@ -939,31 +964,49 @@ bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const {
ERR_FAIL_COND_V(!script.is_valid(), false);
+ MonoObject *mono_object = get_mono_object();
+ ERR_FAIL_NULL_V(mono_object, false);
+
GDMonoClass *top = script->script_class;
while (top && top != script->native) {
GDMonoField *field = top->get_field(p_name);
if (field) {
- MonoObject *mono_object = get_mono_object();
-
- ERR_EXPLAIN("Reference has been garbage collected?");
- ERR_FAIL_NULL_V(mono_object, false);
-
MonoObject *value = field->get_value(mono_object);
r_ret = GDMonoMarshal::mono_object_to_variant(value, field->get_type());
return true;
}
- // Call _get
+ GDMonoProperty *property = top->get_property(p_name);
+
+ if (property) {
+ MonoObject *exc = NULL;
+ MonoObject *value = property->get_value(mono_object, &exc);
+ if (exc) {
+ r_ret = Variant();
+ GDMonoUtils::print_unhandled_exception(exc);
+ } else {
+ r_ret = GDMonoMarshal::mono_object_to_variant(value, property->get_type());
+ }
+ return true;
+ }
+
+ top = top->get_parent_class();
+ }
+
+ // Call _get
+ top = script->script_class;
+
+ while (top && top != script->native) {
GDMonoMethod *method = top->get_method(CACHED_STRING_NAME(_get), 1);
if (method) {
Variant name = p_name;
const Variant *args[1] = { &name };
- MonoObject *ret = method->invoke(get_mono_object(), args);
+ MonoObject *ret = method->invoke(mono_object, args);
if (ret) {
r_ret = GDMonoMarshal::mono_object_to_variant(ret);
@@ -1020,7 +1063,6 @@ Variant CSharpInstance::call(const StringName &p_method, const Variant **p_args,
MonoObject *mono_object = get_mono_object();
- ERR_EXPLAIN("Reference has been garbage collected?");
ERR_FAIL_NULL_V(mono_object, Variant());
if (!script.is_valid())
@@ -1054,23 +1096,34 @@ void CSharpInstance::call_multilevel(const StringName &p_method, const Variant *
if (script.is_valid()) {
MonoObject *mono_object = get_mono_object();
- GDMonoClass *top = script->script_class;
+ ERR_FAIL_NULL(mono_object);
- while (top && top != script->native) {
- GDMonoMethod *method = top->get_method(p_method, p_argcount);
+ _call_multilevel(mono_object, p_method, p_args, p_argcount);
+ }
+}
- if (method)
- method->invoke(mono_object, p_args);
+void CSharpInstance::_call_multilevel(MonoObject *p_mono_object, const StringName &p_method, const Variant **p_args, int p_argcount) {
- top = top->get_parent_class();
- }
+ GDMonoClass *top = script->script_class;
+
+ while (top && top != script->native) {
+ GDMonoMethod *method = top->get_method(p_method, p_argcount);
+
+ if (method)
+ method->invoke(p_mono_object, p_args);
+
+ top = top->get_parent_class();
}
}
void CSharpInstance::call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount) {
if (script.is_valid()) {
- _ml_call_reversed(script->script_class, p_method, p_args, p_argcount);
+ MonoObject *mono_object = get_mono_object();
+
+ ERR_FAIL_NULL(mono_object);
+
+ _ml_call_reversed(mono_object, script->script_class, p_method, p_args, p_argcount);
}
}
@@ -1118,7 +1171,7 @@ void CSharpInstance::refcount_incremented() {
Reference *ref_owner = Object::cast_to<Reference>(owner);
- if (ref_owner->reference_get_count() > 1) { // Remember the managed side holds a reference, hence 1 instead of 0 here
+ if (ref_owner->reference_get_count() > 1) { // 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.
// This means the owner is being referenced again by the unmanaged side,
// so the owner must hold the managed side alive again to avoid it from being GCed.
@@ -1138,7 +1191,7 @@ bool CSharpInstance::refcount_decremented() {
int refcount = ref_owner->reference_get_count();
- if (refcount == 1) { // Remember the managed side holds a reference, hence 1 instead of 0 here
+ if (refcount == 1) { // The managed side also holds a reference, hence 1 instead of 0
// If owner owner is no longer referenced by the unmanaged side,
// the managed instance takes responsibility of deleting the owner when GCed.
@@ -1155,6 +1208,20 @@ bool CSharpInstance::refcount_decremented() {
return ref_dying;
}
+ScriptInstance::RPCMode CSharpInstance::_member_get_rpc_mode(GDMonoClassMember *p_member) const {
+
+ if (p_member->has_attribute(CACHED_CLASS(RemoteAttribute)))
+ return RPC_MODE_REMOTE;
+ if (p_member->has_attribute(CACHED_CLASS(SyncAttribute)))
+ return RPC_MODE_SYNC;
+ if (p_member->has_attribute(CACHED_CLASS(MasterAttribute)))
+ return RPC_MODE_MASTER;
+ if (p_member->has_attribute(CACHED_CLASS(SlaveAttribute)))
+ return RPC_MODE_SLAVE;
+
+ return RPC_MODE_DISABLED;
+}
+
ScriptInstance::RPCMode CSharpInstance::get_rpc_mode(const StringName &p_method) const {
GDMonoClass *top = script->script_class;
@@ -1162,17 +1229,8 @@ ScriptInstance::RPCMode CSharpInstance::get_rpc_mode(const StringName &p_method)
while (top && top != script->native) {
GDMonoMethod *method = top->get_method(p_method);
- if (method) { // TODO should we reject static methods?
- // TODO cache result
- if (method->has_attribute(CACHED_CLASS(RemoteAttribute)))
- return RPC_MODE_REMOTE;
- if (method->has_attribute(CACHED_CLASS(SyncAttribute)))
- return RPC_MODE_SYNC;
- if (method->has_attribute(CACHED_CLASS(MasterAttribute)))
- return RPC_MODE_MASTER;
- if (method->has_attribute(CACHED_CLASS(SlaveAttribute)))
- return RPC_MODE_SLAVE;
- }
+ if (method && !method->is_static())
+ return _member_get_rpc_mode(method);
top = top->get_parent_class();
}
@@ -1187,17 +1245,13 @@ ScriptInstance::RPCMode CSharpInstance::get_rset_mode(const StringName &p_variab
while (top && top != script->native) {
GDMonoField *field = top->get_field(p_variable);
- if (field) { // TODO should we reject static fields?
- // TODO cache result
- if (field->has_attribute(CACHED_CLASS(RemoteAttribute)))
- return RPC_MODE_REMOTE;
- if (field->has_attribute(CACHED_CLASS(SyncAttribute)))
- return RPC_MODE_SYNC;
- if (field->has_attribute(CACHED_CLASS(MasterAttribute)))
- return RPC_MODE_MASTER;
- if (field->has_attribute(CACHED_CLASS(SlaveAttribute)))
- return RPC_MODE_SLAVE;
- }
+ if (field && !field->is_static())
+ return _member_get_rpc_mode(field);
+
+ GDMonoProperty *property = top->get_property(p_variable);
+
+ if (property && !property->is_static())
+ return _member_get_rpc_mode(property);
top = top->get_parent_class();
}
@@ -1207,10 +1261,25 @@ ScriptInstance::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);
+ }
+ return;
+ }
+
+ call_notification_no_check(mono_object, p_notification);
+}
+
+void CSharpInstance::call_notification_no_check(MonoObject *p_mono_object, int p_notification) {
Variant value = p_notification;
const Variant *args[1] = { &value };
- call_multilevel(CACHED_STRING_NAME(_notification), args, 1);
+ _call_multilevel(p_mono_object, CACHED_STRING_NAME(_notification), args, 1);
}
Ref<Script> CSharpInstance::get_script() const {
@@ -1307,7 +1376,7 @@ bool CSharpScript::_update_exports() {
// 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
- MonoObject *tmp_object = mono_object_new(SCRIPTS_DOMAIN, script_class->get_raw());
+ 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
@@ -1330,65 +1399,55 @@ bool CSharpScript::_update_exports() {
GDMonoClass *top = script_class;
while (top && top != native) {
+ PropertyInfo prop_info;
+ bool exported;
+
const Vector<GDMonoField *> &fields = top->get_all_fields();
for (int i = fields.size() - 1; i >= 0; i--) {
GDMonoField *field = fields[i];
- if (field->is_static()) {
- if (field->has_attribute(CACHED_CLASS(ExportAttribute)))
- ERR_PRINTS("Cannot export field because it is static: " + top->get_full_name() + "." + field->get_name());
- continue;
- }
-
- String name = field->get_name();
- StringName cname = name;
+ if (_get_member_export(top, field, prop_info, exported)) {
+ StringName name = field->get_name();
- if (member_info.has(cname))
- continue;
+ if (exported) {
+ member_info[name] = prop_info;
+ exported_members_cache.push_front(prop_info);
- ManagedType field_type = field->get_type();
- Variant::Type type = GDMonoMarshal::managed_to_variant_type(field_type);
+ if (tmp_object) {
+ exported_members_defval_cache[name] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object));
+ }
+ } else {
+ member_info[name] = prop_info;
+ }
+ }
+ }
- if (field->has_attribute(CACHED_CLASS(ExportAttribute))) {
- // Field has Export attribute
- MonoObject *attr = field->get_attribute(CACHED_CLASS(ExportAttribute));
+ const Vector<GDMonoProperty *> &properties = top->get_all_properties();
- PropertyHint hint;
- String hint_string;
+ for (int i = properties.size() - 1; i >= 0; i--) {
+ GDMonoProperty *property = properties[i];
- if (type == Variant::NIL) {
- ERR_PRINTS("Unknown type of exported field: " + top->get_full_name() + "." + field->get_name());
- continue;
- } else if (type == Variant::INT && field_type.type_encoding == MONO_TYPE_VALUETYPE && mono_class_is_enum(field_type.type_class->get_raw())) {
- type = Variant::INT;
- hint = PROPERTY_HINT_ENUM;
+ if (_get_member_export(top, property, prop_info, exported)) {
+ StringName name = property->get_name();
- Vector<MonoClassField *> fields = field_type.type_class->get_enum_fields();
+ if (exported) {
+ member_info[name] = prop_info;
+ exported_members_cache.push_front(prop_info);
- for (int i = 0; i < fields.size(); i++) {
- if (i > 0)
- hint_string += ",";
- hint_string += mono_field_get_name(fields[i]);
+ if (tmp_object) {
+ MonoObject *exc = NULL;
+ MonoObject *ret = property->get_value(tmp_object, &exc);
+ if (exc) {
+ exported_members_defval_cache[name] = Variant();
+ GDMonoUtils::print_unhandled_exception(exc);
+ } else {
+ exported_members_defval_cache[name] = GDMonoMarshal::mono_object_to_variant(ret);
+ }
}
- } else if (type == Variant::OBJECT && CACHED_CLASS(GodotReference)->is_assignable_from(field_type.type_class)) {
- hint = PROPERTY_HINT_RESOURCE_TYPE;
- hint_string = NATIVE_GDMONOCLASS_NAME(field_type.type_class);
} else {
- hint = PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr));
- hint_string = CACHED_FIELD(ExportAttribute, hint_string)->get_string_value(attr);
+ member_info[name] = prop_info;
}
-
- PropertyInfo prop_info = PropertyInfo(type, name, hint, hint_string, PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE);
-
- member_info[cname] = prop_info;
- exported_members_cache.push_front(prop_info);
-
- if (tmp_object) {
- exported_members_defval_cache[cname] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object));
- }
- } else {
- member_info[cname] = PropertyInfo(type, name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE);
}
}
@@ -1412,6 +1471,77 @@ bool CSharpScript::_update_exports() {
return false;
}
+bool CSharpScript::_get_member_export(GDMonoClass *p_class, GDMonoClassMember *p_member, PropertyInfo &r_prop_info, bool &r_exported) {
+
+ StringName name = p_member->get_name();
+
+ if (p_member->is_static()) {
+ if (p_member->has_attribute(CACHED_CLASS(ExportAttribute)))
+ ERR_PRINTS("Cannot export member because it is static: " + p_class->get_full_name() + "." + name.operator String());
+ return false;
+ }
+
+ if (member_info.has(name))
+ return false;
+
+ ManagedType type;
+
+ if (p_member->get_member_type() == GDMonoClassMember::MEMBER_TYPE_FIELD) {
+ type = static_cast<GDMonoField *>(p_member)->get_type();
+ } else if (p_member->get_member_type() == GDMonoClassMember::MEMBER_TYPE_PROPERTY) {
+ type = static_cast<GDMonoProperty *>(p_member)->get_type();
+ } else {
+ CRASH_NOW();
+ }
+
+ 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;
+ }
+ }
+
+ MonoObject *attr = p_member->get_attribute(CACHED_CLASS(ExportAttribute));
+
+ PropertyHint hint;
+ 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;
+
+ Vector<MonoClassField *> fields = type.type_class->get_enum_fields();
+
+ for (int i = 0; i < fields.size(); i++) {
+ if (i > 0)
+ hint_string += ",";
+ hint_string += mono_field_get_name(fields[i]);
+ }
+ } 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);
+ }
+
+ r_prop_info = PropertyInfo(variant_type, name.operator String(), hint, hint_string, PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE);
+ r_exported = true;
+ } else {
+ r_prop_info = PropertyInfo(variant_type, name.operator String(), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE);
+ r_exported = false;
+ }
+
+ return true;
+}
+
void CSharpScript::_clear() {
tool = false;
@@ -1580,7 +1710,7 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
/* STEP 2, INITIALIZE AND CONSTRUCT */
- MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, script_class->get_raw());
+ MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, script_class->get_mono_ptr());
if (!mono_object) {
instance->script = Ref<CSharpScript>();