summaryrefslogtreecommitdiffstats
path: root/modules/mono/csharp_script.cpp
diff options
context:
space:
mode:
authorIgnacio Etcheverry <ignalfonsore@gmail.com>2019-07-03 09:44:53 +0200
committerIgnacio Etcheverry <ignalfonsore@gmail.com>2019-07-05 09:38:23 +0200
commit270af6fa089ccfb93ace68ada8d476bd902b10fa (patch)
treefee771a4f58a8d799f70d37547391831f52b5cd2 /modules/mono/csharp_script.cpp
parent7b569e91c0c6b84965cad416b8e86dcfdacbcfc4 (diff)
downloadredot-engine-270af6fa089ccfb93ace68ada8d476bd902b10fa.tar.gz
Re-write mono module editor code in C#
Make the build system automatically build the C# Api assemblies to be shipped with the editor. Make the editor, editor player and debug export templates use Api assemblies built with debug symbols. Always run MSBuild to build the editor tools and Api assemblies when building Godot. Several bugs fixed related to assembly hot reloading and restoring state. Fix StringExtensions internal calls not being registered correctly, resulting in MissingMethodException.
Diffstat (limited to 'modules/mono/csharp_script.cpp')
-rw-r--r--modules/mono/csharp_script.cpp444
1 files changed, 344 insertions, 100 deletions
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index cdec10eaba..b5c91a8585 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -42,9 +42,9 @@
#include "editor/bindings_generator.h"
#include "editor/csharp_project.h"
#include "editor/editor_node.h"
-#include "editor/godotsharp_editor.h"
#endif
+#include "editor/editor_internal_calls.h"
#include "godotsharp_dirs.h"
#include "mono_gd/gd_mono_class.h"
#include "mono_gd/gd_mono_marshal.h"
@@ -65,8 +65,8 @@ static bool _create_project_solution_if_needed() {
if (!FileAccess::exists(sln_path) || !FileAccess::exists(csproj_path)) {
// A solution does not yet exist, create a new one
- CRASH_COND(GodotSharpEditor::get_singleton() == NULL);
- return GodotSharpEditor::get_singleton()->call("_create_project_solution");
+ CRASH_COND(CSharpLanguage::get_singleton()->get_godotsharp_editor() == NULL);
+ return CSharpLanguage::get_singleton()->get_godotsharp_editor()->call("CreateProjectSolution");
}
return true;
@@ -96,14 +96,6 @@ Error CSharpLanguage::execute_file(const String &p_path) {
return OK;
}
-#ifdef TOOLS_ENABLED
-void gdsharp_editor_init_callback() {
-
- EditorNode *editor = EditorNode::get_singleton();
- editor->add_child(memnew(GodotSharpEditor(editor)));
-}
-#endif
-
void CSharpLanguage::init() {
gdmono = memnew(GDMono);
@@ -114,14 +106,12 @@ void CSharpLanguage::init() {
#endif
#if defined(TOOLS_ENABLED) && defined(DEBUG_METHODS_ENABLED)
- if (gdmono->get_editor_tools_assembly() != NULL) {
- List<String> cmdline_args = OS::get_singleton()->get_cmdline_args();
- BindingsGenerator::handle_cmdline_args(cmdline_args);
- }
+ List<String> cmdline_args = OS::get_singleton()->get_cmdline_args();
+ BindingsGenerator::handle_cmdline_args(cmdline_args);
#endif
#ifdef TOOLS_ENABLED
- EditorNode::add_init_callback(&gdsharp_editor_init_callback);
+ EditorNode::add_init_callback(&_editor_init_callback);
GLOBAL_DEF("mono/export/include_scripts_content", false);
#endif
@@ -664,7 +654,7 @@ void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft
CRASH_COND(!Engine::get_singleton()->is_editor_hint());
#ifdef TOOLS_ENABLED
- MonoReloadNode::get_singleton()->restart_reload_timer();
+ get_godotsharp_editor()->get_node(NodePath("HotReloadAssemblyWatcher"))->call("RestartTimer");
#endif
#ifdef GD_MONO_HOT_RELOAD
@@ -731,58 +721,93 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
SCOPED_MUTEX_LOCK(script_instances_mutex);
for (SelfList<CSharpScript> *elem = script_list.first(); elem; elem = elem->next()) {
- if (elem->self()->get_path().is_resource_file()) {
- // Cast to CSharpScript to avoid being erased by accident
- scripts.push_back(Ref<CSharpScript>(elem->self()));
- }
+ // Cast to CSharpScript to avoid being erased by accident
+ scripts.push_back(Ref<CSharpScript>(elem->self()));
}
}
List<Ref<CSharpScript> > to_reload;
+ // We need to keep reference instances alive during reloading
+ List<Ref<Reference> > ref_instances;
+
+ for (Map<Object *, CSharpScriptBinding>::Element *E = script_bindings.front(); E; E = E->next()) {
+ CSharpScriptBinding &script_binding = E->value();
+ Reference *ref = Object::cast_to<Reference>(script_binding.owner);
+ if (ref) {
+ ref_instances.push_back(Ref<Reference>(ref));
+ }
+ }
+
// As scripts are going to be reloaded, must proceed without locking here
scripts.sort_custom<CSharpScriptDepSort>(); // Update in inheritance dependency order
for (List<Ref<CSharpScript> >::Element *E = scripts.front(); E; E = E->next()) {
-
Ref<CSharpScript> &script = E->get();
to_reload.push_back(script);
+ if (script->get_path().empty()) {
+ script->tied_class_name_for_reload = script->script_class->get_name();
+ script->tied_class_namespace_for_reload = script->script_class->get_namespace();
+ }
+
// Script::instances are deleted during managed object disposal, which happens on domain finalize.
// Only placeholders are kept. Therefore we need to keep a copy before that happens.
for (Set<Object *>::Element *F = script->instances.front(); F; F = F->next()) {
- script->pending_reload_instances.insert(F->get()->get_instance_id());
+ Object *obj = F->get();
+ script->pending_reload_instances.insert(obj->get_instance_id());
+
+ Reference *ref = Object::cast_to<Reference>(obj);
+ if (ref) {
+ ref_instances.push_back(Ref<Reference>(ref));
+ }
}
#ifdef TOOLS_ENABLED
for (Set<PlaceHolderScriptInstance *>::Element *F = script->placeholders.front(); F; F = F->next()) {
- script->pending_reload_instances.insert(F->get()->get_owner()->get_instance_id());
+ Object *obj = F->get()->get_owner();
+ script->pending_reload_instances.insert(obj->get_instance_id());
+
+ Reference *ref = Object::cast_to<Reference>(obj);
+ if (ref) {
+ ref_instances.push_back(Ref<Reference>(ref));
+ }
}
#endif
- // FIXME: What about references? Need to keep them alive if only managed code references them.
-
// Save state and remove script from instances
Map<ObjectID, CSharpScript::StateBackup> &owners_map = script->pending_reload_state;
- while (script->instances.front()) {
- Object *obj = script->instances.front()->get();
- // Save instance info
- CSharpScript::StateBackup state;
+ for (Set<Object *>::Element *F = script->instances.front(); F; F = F->next()) {
+ Object *obj = F->get();
ERR_CONTINUE(!obj->get_script_instance());
- // TODO: Proper state backup (Not only variants, serialize managed state of scripts)
- obj->get_script_instance()->get_property_state(state.properties);
+ CSharpInstance *csi = static_cast<CSharpInstance *>(obj->get_script_instance());
- Ref<MonoGCHandle> gchandle = CAST_CSHARP_INSTANCE(obj->get_script_instance())->gchandle;
- if (gchandle.is_valid())
- gchandle->release();
+ // Call OnBeforeSerialize
+ if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener)))
+ obj->get_script_instance()->call_multilevel(string_names.on_before_serialize);
+
+ // Save instance info
+ CSharpScript::StateBackup state;
+
+ // TODO: Proper state backup (Not only variants, serialize managed state of scripts)
+ csi->get_properties_state_for_reloading(state.properties);
owners_map[obj->get_instance_id()] = state;
+ }
+ }
+
+ // After the state of all instances is saved, clear scripts and script instances
+ for (List<Ref<CSharpScript> >::Element *E = scripts.front(); E; E = E->next()) {
+ Ref<CSharpScript> &script = E->get();
+
+ while (script->instances.front()) {
+ Object *obj = script->instances.front()->get();
obj->set_script(RefPtr()); // Remove script and existing script instances (placeholder are not removed before domain reload)
}
@@ -825,26 +850,76 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
scr->pending_reload_state.erase(obj_id);
}
}
+
return;
}
+ List<Ref<CSharpScript> > to_reload_state;
+
for (List<Ref<CSharpScript> >::Element *E = to_reload.front(); E; E = E->next()) {
+ Ref<CSharpScript> script = E->get();
- Ref<CSharpScript> scr = E->get();
+ if (!script->get_path().empty()) {
#ifdef TOOLS_ENABLED
- scr->exports_invalidated = true;
+ script->exports_invalidated = true;
#endif
- scr->signals_invalidated = true;
- scr->reload(p_soft_reload);
- scr->update_exports();
+ script->signals_invalidated = true;
+
+ script->reload(p_soft_reload);
+ script->update_exports();
+ } else {
+ const StringName &class_namespace = script->tied_class_namespace_for_reload;
+ const StringName &class_name = script->tied_class_name_for_reload;
+ GDMonoAssembly *project_assembly = gdmono->get_project_assembly();
+ GDMonoAssembly *tools_assembly = gdmono->get_tools_assembly();
+
+ // Search in project and tools assemblies first as those are the most likely to have the class
+ GDMonoClass *script_class = (project_assembly ? project_assembly->get_class(class_namespace, class_name) : NULL);
+ if (!script_class) {
+ script_class = (tools_assembly ? tools_assembly->get_class(class_namespace, class_name) : NULL);
+ }
+ if (!script_class) {
+ script_class = gdmono->get_class(class_namespace, class_name);
+ }
+
+ if (!script_class) {
+ // The class was removed, can't reload
+ script->pending_reload_instances.clear();
+ continue;
+ }
+
+ bool obj_type = CACHED_CLASS(GodotObject)->is_assignable_from(script_class);
+ if (!obj_type) {
+ // The class no longer inherits Godot.Object, can't reload
+ script->pending_reload_instances.clear();
+ continue;
+ }
+
+ GDMonoClass *native = GDMonoUtils::get_class_native_base(script_class);
+
+ Ref<CSharpScript> new_script = CSharpScript::create_for_managed_type(script_class, native);
+ CRASH_COND(new_script.is_null());
+
+ new_script->pending_reload_instances = script->pending_reload_instances;
+ new_script->pending_reload_state = script->pending_reload_state;
+ script = new_script;
+ }
+
+ String native_name = NATIVE_GDMONOCLASS_NAME(script->native);
{
- for (Set<ObjectID>::Element *F = scr->pending_reload_instances.front(); F; F = F->next()) {
+ for (Set<ObjectID>::Element *F = script->pending_reload_instances.front(); F; F = F->next()) {
ObjectID obj_id = F->get();
Object *obj = ObjectDB::get_instance(obj_id);
if (!obj) {
- scr->pending_reload_state.erase(obj_id);
+ script->pending_reload_state.erase(obj_id);
+ continue;
+ }
+
+ if (!ClassDB::is_parent_class(obj->get_class_name(), native_name)) {
+ // No longer inherits the same compatible type, can't reload
+ script->pending_reload_state.erase(obj_id);
continue;
}
@@ -856,28 +931,20 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
// Non-placeholder script instances are removed in godot_icall_Object_Disposed.
CRASH_COND(!si->is_placeholder());
- if (scr->is_tool() || ScriptServer::is_scripting_enabled()) {
+ if (script->is_tool() || ScriptServer::is_scripting_enabled()) {
// Replace placeholder with a script instance
- CSharpScript::StateBackup &state_backup = scr->pending_reload_state[obj_id];
+ CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id];
// Backup placeholder script instance state before replacing it with a script instance
si->get_property_state(state_backup.properties);
- ScriptInstance *script_instance = scr->instance_create(obj);
+ ScriptInstance *script_instance = script->instance_create(obj);
if (script_instance) {
- scr->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(si));
+ script->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(si));
obj->set_script_instance(script_instance);
}
-
- // TODO: Restore serialized state
-
- for (List<Pair<StringName, Variant> >::Element *G = state_backup.properties.front(); G; G = G->next()) {
- script_instance->set(G->get().first, G->get().second);
- }
-
- scr->pending_reload_state.erase(obj_id);
}
continue;
@@ -887,19 +954,42 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
#endif
// Re-create script instance
- obj->set_script(scr.get_ref_ptr()); // will create the script instance as well
+ obj->set_script(script.get_ref_ptr()); // will create the script instance as well
+ }
+ }
- // TODO: Restore serialized state
+ to_reload_state.push_back(script);
+ }
- for (List<Pair<StringName, Variant> >::Element *G = scr->pending_reload_state[obj_id].properties.front(); G; G = G->next()) {
- obj->get_script_instance()->set(G->get().first, G->get().second);
- }
+ for (List<Ref<CSharpScript> >::Element *E = to_reload_state.front(); E; E = E->next()) {
+ Ref<CSharpScript> script = E->get();
- scr->pending_reload_state.erase(obj_id);
+ for (Set<ObjectID>::Element *F = script->pending_reload_instances.front(); F; F = F->next()) {
+ ObjectID obj_id = F->get();
+ Object *obj = ObjectDB::get_instance(obj_id);
+
+ if (!obj) {
+ script->pending_reload_state.erase(obj_id);
+ continue;
}
- scr->pending_reload_instances.clear();
+ ERR_CONTINUE(!obj->get_script_instance());
+
+ // TODO: Restore serialized state
+
+ CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id];
+
+ for (List<Pair<StringName, Variant> >::Element *G = state_backup.properties.front(); G; G = G->next()) {
+ obj->get_script_instance()->set(G->get().first, G->get().second);
+ }
+
+ // Call OnAfterDeserialization
+ CSharpInstance *csi = CAST_CSHARP_INSTANCE(obj->get_script_instance());
+ if (csi && csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener)))
+ obj->get_script_instance()->call_multilevel(string_names.on_after_deserialize);
}
+
+ script->pending_reload_instances.clear();
}
#ifdef TOOLS_ENABLED
@@ -964,12 +1054,12 @@ void CSharpLanguage::get_recognized_extensions(List<String> *p_extensions) const
#ifdef TOOLS_ENABLED
Error CSharpLanguage::open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) {
- return GodotSharpEditor::get_singleton()->open_in_external_editor(p_script, p_line, p_col);
+ return (Error)(int)get_godotsharp_editor()->call("OpenInExternalEditor", p_script, p_line, p_col);
}
bool CSharpLanguage::overrides_external_editor() {
- return GodotSharpEditor::get_singleton()->overrides_external_editor();
+ return get_godotsharp_editor()->call("OverridesExternalEditor");
}
#endif
@@ -1027,6 +1117,34 @@ void CSharpLanguage::_on_scripts_domain_unloaded() {
scripts_metadata_invalidated = true;
}
+#ifdef TOOLS_ENABLED
+void CSharpLanguage::_editor_init_callback() {
+
+ register_editor_internal_calls();
+
+ // Initialize GodotSharpEditor
+
+ GDMonoClass *editor_klass = GDMono::get_singleton()->get_tools_assembly()->get_class("GodotTools", "GodotSharpEditor");
+ CRASH_COND(editor_klass == NULL);
+
+ MonoObject *mono_object = mono_object_new(mono_domain_get(), editor_klass->get_mono_ptr());
+ CRASH_COND(mono_object == NULL);
+
+ MonoException *exc = NULL;
+ GDMonoUtils::runtime_object_init(mono_object, editor_klass, &exc);
+ UNHANDLED_EXCEPTION(exc);
+
+ EditorPlugin *godotsharp_editor = Object::cast_to<EditorPlugin>(GDMonoMarshal::mono_object_to_variant(mono_object));
+ CRASH_COND(godotsharp_editor == NULL);
+
+ // Enable it as a plugin
+ EditorNode::add_editor_plugin(godotsharp_editor);
+ godotsharp_editor->enable_plugin();
+
+ get_singleton()->godotsharp_editor = godotsharp_editor;
+}
+#endif
+
void CSharpLanguage::set_language_index(int p_idx) {
ERR_FAIL_COND(lang_idx != -1);
@@ -1084,6 +1202,8 @@ CSharpLanguage::CSharpLanguage() {
lang_idx = -1;
scripts_metadata_invalidated = true;
+
+ godotsharp_editor = NULL;
}
CSharpLanguage::~CSharpLanguage() {
@@ -1139,6 +1259,7 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b
r_script_binding.type_name = type_name;
r_script_binding.wrapper_class = type_class; // cache
r_script_binding.gchandle = MonoGCHandle::create_strong(mono_object);
+ r_script_binding.owner = p_object;
// Tie managed to unmanaged
Reference *ref = Object::cast_to<Reference>(p_object);
@@ -1223,6 +1344,9 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) {
CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
+ if (!script_binding.inited)
+ return;
+
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.
// This means the owner is being referenced again by the unmanaged side,
@@ -1247,14 +1371,17 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) {
CRASH_COND(!ref_owner);
#endif
- int refcount = ref_owner->reference_get_count();
-
void *data = p_object->get_script_instance_binding(get_language_index());
CRASH_COND(!data);
CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
+ int refcount = ref_owner->reference_get_count();
+
+ if (!script_binding.inited)
+ return refcount == 0;
+
if (refcount == 1 && gchandle.is_valid() && !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,
// the managed instance takes responsibility of deleting the owner when GCed.
@@ -1417,6 +1544,31 @@ bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const {
return false;
}
+void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Variant> > &r_state) {
+
+ List<PropertyInfo> pinfo;
+ get_property_list(&pinfo);
+
+ for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) {
+ Pair<StringName, Variant> state_pair;
+ state_pair.first = E->get().name;
+
+ ManagedType managedType;
+
+ GDMonoField *field = script->script_class->get_field(state_pair.first);
+ if (!field)
+ continue; // Properties ignored. We get the property baking fields instead.
+
+ managedType = field->get_type();
+
+ if (GDMonoMarshal::managed_to_variant_type(managedType) != Variant::NIL) { // If we can marshal it
+ if (get(state_pair.first, state_pair.second)) {
+ r_state.push_back(state_pair);
+ }
+ }
+ }
+}
+
void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const {
for (Map<StringName, PropertyInfo>::Element *E = script->member_info.front(); E; E = E->next()) {
@@ -1614,17 +1766,18 @@ MonoObject *CSharpInstance::_internal_new_managed() {
ERR_FAIL_NULL_V(owner, NULL);
ERR_FAIL_COND_V(script.is_null(), NULL);
- MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, script->script_class->get_mono_ptr());
+ MonoObject *mono_object = mono_object_new(mono_domain_get(), script->script_class->get_mono_ptr());
if (!mono_object) {
// Important to clear this before destroying the script instance here
script = Ref<CSharpScript>();
- owner = NULL;
bool die = _unreference_owner_unsafe();
// Not ok for the owner to die here. If there is a situation where this can happen, it will be considered a bug.
CRASH_COND(die == true);
+ owner = NULL;
+
ERR_EXPLAIN("Failed to allocate memory for the object");
ERR_FAIL_V(NULL);
}
@@ -1940,7 +2093,16 @@ CSharpInstance::~CSharpInstance() {
CRASH_COND(data == NULL);
CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
- CRASH_COND(!script_binding.inited);
+
+ if (!script_binding.inited) {
+ SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->get_language_bind_mutex());
+
+ if (!script_binding.inited) { // Other thread may have set it up
+ // Already had a binding that needs to be setup
+ CSharpLanguage::get_singleton()->setup_csharp_script_binding(script_binding, owner);
+ CRASH_COND(!script_binding.inited);
+ }
+ }
bool die = _unreference_owner_unsafe();
CRASH_COND(die == true); // The "instance binding" should be holding a reference
@@ -1984,6 +2146,52 @@ void CSharpScript::_update_exports_values(Map<StringName, Variant> &values, List
}
#endif
+void CSharpScript::_update_member_info_no_exports() {
+
+ if (exports_invalidated) {
+ exports_invalidated = false;
+
+ member_info.clear();
+
+ 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 (_get_member_export(field, /* inspect export: */ false, prop_info, exported)) {
+ StringName member_name = field->get_name();
+
+ member_info[member_name] = prop_info;
+ exported_members_cache.push_front(prop_info);
+ exported_members_defval_cache[member_name] = Variant();
+ }
+ }
+
+ const Vector<GDMonoProperty *> &properties = top->get_all_properties();
+
+ for (int i = properties.size() - 1; i >= 0; i--) {
+ GDMonoProperty *property = properties[i];
+
+ if (_get_member_export(property, /* inspect export: */ false, prop_info, exported)) {
+ StringName member_name = property->get_name();
+
+ member_info[member_name] = prop_info;
+ exported_members_cache.push_front(prop_info);
+ exported_members_defval_cache[member_name] = Variant();
+ }
+ }
+
+ top = top->get_parent_class();
+ }
+ }
+}
+
bool CSharpScript::_update_exports() {
#ifdef TOOLS_ENABLED
@@ -2008,7 +2216,7 @@ bool CSharpScript::_update_exports() {
// 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());
+ MonoObject *tmp_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr());
if (!tmp_object) {
ERR_PRINT("Failed to allocate temporary MonoObject");
@@ -2049,18 +2257,18 @@ bool CSharpScript::_update_exports() {
for (int i = fields.size() - 1; i >= 0; i--) {
GDMonoField *field = fields[i];
- if (_get_member_export(field, prop_info, exported)) {
- StringName name = field->get_name();
+ if (_get_member_export(field, /* inspect export: */ true, prop_info, exported)) {
+ StringName member_name = field->get_name();
if (exported) {
- member_info[name] = prop_info;
+ member_info[member_name] = prop_info;
exported_members_cache.push_front(prop_info);
if (tmp_object) {
- exported_members_defval_cache[name] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object));
+ exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object));
}
} else {
- member_info[name] = prop_info;
+ member_info[member_name] = prop_info;
}
}
}
@@ -2070,25 +2278,25 @@ bool CSharpScript::_update_exports() {
for (int i = properties.size() - 1; i >= 0; i--) {
GDMonoProperty *property = properties[i];
- if (_get_member_export(property, prop_info, exported)) {
- StringName name = property->get_name();
+ if (_get_member_export(property, /* inspect export: */ true, prop_info, exported)) {
+ StringName member_name = property->get_name();
if (exported) {
- member_info[name] = prop_info;
+ member_info[member_name] = prop_info;
exported_members_cache.push_front(prop_info);
if (tmp_object) {
MonoException *exc = NULL;
MonoObject *ret = property->get_value(tmp_object, &exc);
if (exc) {
- exported_members_defval_cache[name] = Variant();
+ exported_members_defval_cache[member_name] = Variant();
GDMonoUtils::debug_print_unhandled_exception(exc);
} else {
- exported_members_defval_cache[name] = GDMonoMarshal::mono_object_to_variant(ret);
+ exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(ret);
}
}
} else {
- member_info[name] = prop_info;
+ member_info[member_name] = prop_info;
}
}
}
@@ -2197,7 +2405,7 @@ bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Ve
* 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(IMonoClassMember *p_member, PropertyInfo &r_prop_info, bool &r_exported) {
+bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported) {
// Goddammit, C++. All I wanted was some nested functions.
#define MEMBER_FULL_QUALIFIED_NAME(m_member) \
@@ -2222,26 +2430,30 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, PropertyInfo &
CRASH_NOW();
}
- Variant::Type variant_type = GDMonoMarshal::managed_to_variant_type(type);
-
- if (!p_member->has_attribute(CACHED_CLASS(ExportAttribute))) {
- r_prop_info = PropertyInfo(variant_type, (String)p_member->get_name(), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE);
- r_exported = false;
- return true;
- }
+ bool exported = p_member->has_attribute(CACHED_CLASS(ExportAttribute));
if (p_member->get_member_type() == IMonoClassMember::MEMBER_TYPE_PROPERTY) {
GDMonoProperty *property = static_cast<GDMonoProperty *>(p_member);
if (!property->has_getter()) {
- ERR_PRINTS("Read-only property cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member));
+ if (exported)
+ ERR_PRINTS("Read-only property cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member));
return false;
}
if (!property->has_setter()) {
- ERR_PRINTS("Set-only property (without getter) cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member));
+ if (exported)
+ ERR_PRINTS("Write-only property (without getter) cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member));
return false;
}
}
+ Variant::Type variant_type = GDMonoMarshal::managed_to_variant_type(type);
+
+ if (!p_inspect_export || !exported) {
+ r_prop_info = PropertyInfo(variant_type, (String)p_member->get_name(), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE);
+ r_exported = false;
+ return true;
+ }
+
MonoObject *attr = p_member->get_attribute(CACHED_CLASS(ExportAttribute));
PropertyHint hint = PROPERTY_HINT_NONE;
@@ -2463,9 +2675,9 @@ Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GD
// This method should not fail
- CRASH_COND(!p_class);
+ CRASH_COND(p_class == NULL);
- // TODO: Cache the 'CSharpScript' associated with this 'p_class' instead of allocating a new one every time
+ // TODO OPTIMIZE: Cache the 'CSharpScript' associated with this 'p_class' instead of allocating a new one every time
Ref<CSharpScript> script = memnew(CSharpScript);
script->name = p_class->get_name();
@@ -2479,6 +2691,20 @@ Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GD
if (base != script->native)
script->base = base;
+ script->valid = true;
+ script->tool = script->script_class->has_attribute(CACHED_CLASS(ToolAttribute));
+
+ if (!script->tool) {
+ GDMonoClass *nesting_class = script->script_class->get_nesting_class();
+ script->tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute));
+ }
+
+#if TOOLS_ENABLED
+ if (!script->tool) {
+ script->tool = script->script_class->get_assembly() == GDMono::get_singleton()->get_tools_assembly();
+ }
+#endif
+
#ifdef DEBUG_ENABLED
// For debug builds, we must fetch from all native base methods as well.
// Native base methods must be fetched before the current class.
@@ -2507,6 +2733,7 @@ Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GD
}
script->load_script_signals(script->script_class, script->native);
+ script->_update_member_info_no_exports();
return script;
}
@@ -2516,7 +2743,8 @@ bool CSharpScript::can_instance() const {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
- if (get_path().find("::") == -1) { // Ignore if built-in script. Can happen if the file is deleted...
+ // Hack to lower the risk of attached scripts not being added to the C# project
+ if (!get_path().empty() && get_path().find("::") == -1) { // Ignore if built-in script. Can happen if the file is deleted...
if (_create_project_solution_if_needed()) {
CSharpProject::add_item(GodotSharpDirs::get_project_csproj_path(),
"Compile",
@@ -2568,7 +2796,9 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), p_argcount);
if (ctor == NULL) {
if (p_argcount == 0) {
- ERR_PRINTS("Cannot create script instance because the class does not define a parameterless constructor: " + get_path());
+ String path = get_path();
+ ERR_PRINTS("Cannot create script instance. The class '" + script_class->get_full_name() +
+ "' does not define a parameterless constructor." + (path.empty() ? String() : ". Path: " + path));
}
ERR_EXPLAIN("Constructor not found");
@@ -2610,7 +2840,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_mono_ptr());
+ MonoObject *mono_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr());
if (!mono_object) {
// Important to clear this before destroying the script instance here
@@ -2691,7 +2921,7 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) {
#endif
if (native) {
- String native_name = native->get_name();
+ String native_name = NATIVE_GDMONOCLASS_NAME(native);
if (!ClassDB::is_parent_class(p_this->get_class_name(), native_name)) {
if (ScriptDebugger::get_singleton()) {
CSharpLanguage::get_singleton()->debug_break_parse(get_path(), 0, "Script inherits from native type '" + native_name + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'");
@@ -2817,11 +3047,22 @@ Error CSharpScript::reload(bool p_keep_state) {
if (script_class) {
#ifdef DEBUG_ENABLED
- print_verbose("Found class " + script_class->get_namespace() + "." + script_class->get_name() + " for script " + get_path());
+ print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path());
#endif
tool = script_class->has_attribute(CACHED_CLASS(ToolAttribute));
+ if (!tool) {
+ GDMonoClass *nesting_class = script_class->get_nesting_class();
+ tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute));
+ }
+
+#if TOOLS_ENABLED
+ if (!tool) {
+ tool = script_class->get_assembly() == GDMono::get_singleton()->get_tools_assembly();
+ }
+#endif
+
native = GDMonoUtils::get_class_native_base(script_class);
CRASH_COND(native == NULL);
@@ -3019,7 +3260,8 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p
#endif
#ifdef TOOLS_ENABLED
- if (Engine::get_singleton()->is_editor_hint() && mono_domain_get() == NULL) {
+ MonoDomain *domain = mono_domain_get();
+ if (Engine::get_singleton()->is_editor_hint() && domain == NULL) {
CRASH_COND(Thread::get_caller_id() == Thread::get_main_id());
@@ -3027,8 +3269,8 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p
// because this may be called by one of the editor's worker threads.
// Attach this thread temporarily to reload the script.
- if (SCRIPTS_DOMAIN) {
- MonoThread *mono_thread = mono_thread_attach(SCRIPTS_DOMAIN);
+ if (domain) {
+ MonoThread *mono_thread = mono_thread_attach(domain);
CRASH_COND(mono_thread == NULL);
script->reload();
mono_thread_detach(mono_thread);
@@ -3128,5 +3370,7 @@ CSharpLanguage::StringNameCache::StringNameCache() {
_get_property_list = StaticCString::create("_get_property_list");
_notification = StaticCString::create("_notification");
_script_source = StaticCString::create("script/source");
+ on_before_serialize = StaticCString::create("OnBeforeSerialize");
+ on_after_deserialize = StaticCString::create("OnAfterDeserialize");
dotctor = StaticCString::create(".ctor");
}