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.cpp682
1 files changed, 506 insertions, 176 deletions
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 3c9644127c..7492816f18 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
@@ -131,14 +121,6 @@ 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
-
// Make sure all script binding gchandles are released before finalizing GDMono
for (Map<Object *, CSharpScriptBinding>::Element *E = script_bindings.front(); E; E = E->next()) {
CSharpScriptBinding &script_binding = E->value();
@@ -672,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
@@ -690,19 +672,20 @@ bool CSharpLanguage::is_assembly_reloading_needed() {
GDMonoAssembly *proj_assembly = gdmono->get_project_assembly();
- String name = ProjectSettings::get_singleton()->get("application/config/name");
- if (name.empty()) {
- name = "UnnamedProject";
+ String appname = ProjectSettings::get_singleton()->get("application/config/name");
+ String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
+ if (appname_safe.empty()) {
+ appname_safe = "UnnamedProject";
}
- name += ".dll";
+ appname_safe += ".dll";
if (proj_assembly) {
String proj_asm_path = proj_assembly->get_path();
if (!FileAccess::exists(proj_assembly->get_path())) {
// Maybe it wasn't loaded from the default path, so check this as well
- proj_asm_path = GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(name);
+ proj_asm_path = GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe);
if (!FileAccess::exists(proj_asm_path))
return false; // No assembly to load
}
@@ -710,7 +693,7 @@ bool CSharpLanguage::is_assembly_reloading_needed() {
if (FileAccess::get_modified_time(proj_asm_path) <= proj_assembly->get_modified_time())
return false; // Already up to date
} else {
- if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(name)))
+ if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe)))
return false; // No assembly to load
}
@@ -738,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)
}
@@ -832,26 +850,80 @@ 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();
+
+ if (!script->valid) {
+ script->pending_reload_instances.clear();
+ continue;
+ }
+ } 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();
+
+ // 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);
+
+#ifdef TOOLS_ENABLED
+ if (!script_class) {
+ GDMonoAssembly *tools_assembly = gdmono->get_tools_assembly();
+ script_class = (tools_assembly ? tools_assembly->get_class(class_namespace, class_name) : NULL);
+ }
+#endif
+
+ 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);
+
+ CSharpScript::initialize_for_managed_type(script, script_class, native);
+ }
+
+ 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;
}
@@ -863,28 +935,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;
@@ -893,20 +957,42 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
CRASH_COND(si != NULL);
#endif
// Re-create script instance
+ obj->set_script(script.get_ref_ptr()); // will create the script instance as well
+ }
+ }
- obj->set_script(scr.get_ref_ptr()); // will create the script instance as well
+ to_reload_state.push_back(script);
+ }
- // TODO: Restore serialized state
+ for (List<Ref<CSharpScript> >::Element *E = to_reload_state.front(); E; E = E->next()) {
+ Ref<CSharpScript> script = E->get();
- 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 (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);
- scr->pending_reload_state.erase(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
@@ -919,7 +1005,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
}
#endif
-void CSharpLanguage::project_assembly_loaded() {
+void CSharpLanguage::_load_scripts_metadata() {
scripts_metadata.clear();
@@ -953,6 +1039,7 @@ void CSharpLanguage::project_assembly_loaded() {
}
scripts_metadata = old_dict_var.operator Dictionary();
+ scripts_metadata_invalidated = false;
print_verbose("Successfully loaded scripts metadata");
} else {
@@ -970,12 +1057,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
@@ -1024,12 +1111,42 @@ bool CSharpLanguage::debug_break(const String &p_error, bool p_allow_continue) {
}
}
-void CSharpLanguage::_uninitialize_script_bindings() {
+void CSharpLanguage::_on_scripts_domain_unloaded() {
for (Map<Object *, CSharpScriptBinding>::Element *E = script_bindings.front(); E; E = E->next()) {
CSharpScriptBinding &script_binding = E->value();
script_binding.inited = false;
}
+
+ 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) {
@@ -1086,6 +1203,12 @@ CSharpLanguage::CSharpLanguage() {
#endif
lang_idx = -1;
+
+ scripts_metadata_invalidated = true;
+
+#ifdef TOOLS_ENABLED
+ godotsharp_editor = NULL;
+#endif
}
CSharpLanguage::~CSharpLanguage() {
@@ -1141,6 +1264,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);
@@ -1225,6 +1349,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,
@@ -1249,14 +1376,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.
@@ -1419,6 +1549,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()) {
@@ -1616,17 +1771,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);
}
@@ -1858,6 +2014,34 @@ void CSharpInstance::_call_notification(int p_notification) {
}
}
+String CSharpInstance::to_string(bool *r_valid) {
+ MonoObject *mono_object = get_mono_object();
+
+ if (mono_object == NULL) {
+ if (r_valid)
+ *r_valid = false;
+ return String();
+ }
+
+ MonoException *exc = NULL;
+ MonoString *result = GDMonoUtils::object_to_string(mono_object, &exc);
+
+ if (exc) {
+ GDMonoUtils::set_pending_exception(exc);
+ if (r_valid)
+ *r_valid = false;
+ return String();
+ }
+
+ if (result == NULL) {
+ if (r_valid)
+ *r_valid = false;
+ return String();
+ }
+
+ return GDMonoMarshal::mono_string_to_godot(result);
+}
+
Ref<Script> CSharpInstance::get_script() const {
return script;
@@ -1914,7 +2098,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
@@ -1956,6 +2149,52 @@ void CSharpScript::_update_exports_values(Map<StringName, Variant> &values, List
propnames.push_back(E->get());
}
}
+
+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();
+ }
+ }
+}
#endif
bool CSharpScript::_update_exports() {
@@ -1982,7 +2221,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");
@@ -2023,18 +2262,18 @@ bool CSharpScript::_update_exports() {
for (int i = fields.size() - 1; i >= 0; i--) {
GDMonoField *field = fields[i];
- if (_get_member_export(top, 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;
}
}
}
@@ -2044,25 +2283,25 @@ bool CSharpScript::_update_exports() {
for (int i = properties.size() - 1; i >= 0; i--) {
GDMonoProperty *property = properties[i];
- if (_get_member_export(top, 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;
}
}
}
@@ -2171,17 +2410,19 @@ 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(GDMonoClass *p_class, 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) {
- StringName name = p_member->get_name();
+ // Goddammit, C++. All I wanted was some nested functions.
+#define MEMBER_FULL_QUALIFIED_NAME(m_member) \
+ (m_member->get_enclosing_class()->get_full_name() + "." + (String)m_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());
+ ERR_PRINTS("Cannot export member because it is static: " + MEMBER_FULL_QUALIFIED_NAME(p_member));
return false;
}
- if (member_info.has(name))
+ if (member_info.has(p_member->get_name()))
return false;
ManagedType type;
@@ -2194,39 +2435,68 @@ bool CSharpScript::_get_member_export(GDMonoClass *p_class, IMonoClassMember *p_
CRASH_NOW();
}
- GDMonoMarshal::ExportInfo export_info;
- Variant::Type variant_type = GDMonoMarshal::managed_to_variant_type(type, &export_info);
-
- 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;
- }
+ 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() || !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());
+ if (!property->has_getter()) {
+ if (exported)
+ ERR_PRINTS("Read-only property cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member));
+ return false;
+ }
+ if (!property->has_setter()) {
+ 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;
String hint_string;
if (variant_type == Variant::NIL) {
- ERR_PRINTS("Unknown type of exported member: " + p_class->get_full_name() + "." + name.operator String());
+ ERR_PRINTS("Unknown exported member type: " + MEMBER_FULL_QUALIFIED_NAME(p_member));
return false;
- } else if (variant_type == Variant::INT && type.type_encoding == MONO_TYPE_VALUETYPE && mono_class_is_enum(type.type_class->get_mono_ptr())) {
- // TODO: Move to ExportInfo?
- variant_type = Variant::INT;
- hint = PROPERTY_HINT_ENUM;
+ }
+
+ int hint_res = _try_get_member_export_hint(p_member, type, variant_type, /* allow_generics: */ true, hint, hint_string);
+
+ if (hint_res == -1) {
+ ERR_EXPLAIN("Error while trying to determine information about the exported member: " + MEMBER_FULL_QUALIFIED_NAME(p_member));
+ ERR_FAIL_V(false);
+ }
+
+ if (hint_res == 0) {
+ 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, (String)p_member->get_name(), hint, hint_string, PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE);
+ r_exported = true;
+
+ return true;
+
+#undef MEMBER_FULL_QUALIFIED_NAME
+}
- Vector<MonoClassField *> fields = type.type_class->get_enum_fields();
+int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string) {
- MonoType *enum_basetype = mono_class_enum_basetype(type.type_class->get_mono_ptr());
+ if (p_variant_type == Variant::INT && p_type.type_encoding == MONO_TYPE_VALUETYPE && mono_class_is_enum(p_type.type_class->get_mono_ptr())) {
+ r_hint = PROPERTY_HINT_ENUM;
+
+ Vector<MonoClassField *> fields = p_type.type_class->get_enum_fields();
+
+ MonoType *enum_basetype = mono_class_enum_basetype(p_type.type_class->get_mono_ptr());
String name_only_hint_string;
@@ -2239,12 +2509,12 @@ bool CSharpScript::_get_member_export(GDMonoClass *p_class, IMonoClassMember *p_
MonoClassField *field = fields[i];
if (i > 0) {
- hint_string += ",";
+ r_hint_string += ",";
name_only_hint_string += ",";
}
String enum_field_name = mono_field_get_name(field);
- hint_string += enum_field_name;
+ r_hint_string += enum_field_name;
name_only_hint_string += enum_field_name;
// TODO:
@@ -2254,53 +2524,73 @@ bool CSharpScript::_get_member_export(GDMonoClass *p_class, IMonoClassMember *p_
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;
+ ERR_EXPLAIN("Failed to get '" + enum_field_name + "' constant enum value");
+ ERR_FAIL_V(-1);
}
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;
+ ERR_EXPLAIN("Failed to unbox '" + enum_field_name + "' constant enum value");
+ ERR_FAIL_V(-1);
}
if (val != (unsigned int)i) {
uses_default_values = false;
}
- hint_string += ":";
- hint_string += String::num_uint64(val);
+ r_hint_string += ":";
+ r_hint_string += String::num_uint64(val);
}
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;
+ r_hint_string = name_only_hint_string;
}
- } else if (variant_type == Variant::OBJECT && CACHED_CLASS(GodotReference)->is_assignable_from(type.type_class)) {
- GDMonoClass *field_native_class = GDMonoUtils::get_class_native_base(type.type_class);
+ } else if (p_variant_type == Variant::OBJECT && CACHED_CLASS(GodotResource)->is_assignable_from(p_type.type_class)) {
+ GDMonoClass *field_native_class = GDMonoUtils::get_class_native_base(p_type.type_class);
CRASH_COND(field_native_class == NULL);
- hint = PROPERTY_HINT_RESOURCE_TYPE;
- hint_string = NATIVE_GDMONOCLASS_NAME(field_native_class);
- } else if (variant_type == Variant::ARRAY && export_info.array.element_type != Variant::NIL) {
- hint = PROPERTY_HINT_TYPE_STRING;
- hint_string = itos(export_info.array.element_type) + ":";
- } else if (variant_type == Variant::DICTIONARY && export_info.dictionary.key_type != Variant::NIL && export_info.dictionary.value_type != Variant::NIL) {
- // TODO: There is no hint for this yet
+ r_hint = PROPERTY_HINT_RESOURCE_TYPE;
+ r_hint_string = NATIVE_GDMONOCLASS_NAME(field_native_class);
+ } else if (p_allow_generics && p_variant_type == Variant::ARRAY) {
+ // Nested arrays are not supported in the inspector
+
+ ManagedType elem_type;
+
+ if (!GDMonoMarshal::try_get_array_element_type(p_type, elem_type))
+ return 0;
+
+ Variant::Type elem_variant_type = GDMonoMarshal::managed_to_variant_type(elem_type);
+
+ PropertyHint elem_hint = PROPERTY_HINT_NONE;
+ String elem_hint_string;
+
+ if (elem_variant_type == Variant::NIL) {
+ ERR_EXPLAIN("Unknown array element type");
+ ERR_FAIL_V(-1);
+ }
+
+ int hint_res = _try_get_member_export_hint(p_member, elem_type, elem_variant_type, /* allow_generics: */ false, elem_hint, elem_hint_string);
+
+ if (hint_res == -1) {
+ ERR_EXPLAIN("Error while trying to determine information about the array element type");
+ ERR_FAIL_V(-1);
+ }
+
+ // Format: type/hint:hint_string
+ r_hint_string = itos(elem_variant_type) + "/" + itos(elem_hint) + ":" + elem_hint_string;
+ r_hint = PROPERTY_HINT_TYPE_STRING;
+
+ } else if (p_allow_generics && p_variant_type == Variant::DICTIONARY) {
+ // TODO: Dictionaries are not supported in the inspector
} else {
- hint = PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr));
- hint_string = CACHED_FIELD(ExportAttribute, hintString)->get_string_value(attr);
+ return 0;
}
- r_prop_info = PropertyInfo(variant_type, name.operator String(), hint, hint_string, PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE);
- r_exported = true;
-
- return true;
+ return 1;
}
#endif
@@ -2388,33 +2678,58 @@ void CSharpScript::_bind_methods() {
Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GDMonoClass *p_native) {
- // This method should not fail
+ // This method should not fail, only assertions allowed
- 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();
- script->script_class = p_class;
- script->native = p_native;
+ initialize_for_managed_type(script, p_class, p_native);
+
+ return script;
+}
+
+void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native) {
- CRASH_COND(script->native == NULL);
+ // This method should not fail, only assertions allowed
- GDMonoClass *base = script->script_class->get_parent_class();
+ CRASH_COND(p_class == NULL);
- if (base != script->native)
- script->base = base;
+ p_script->name = p_class->get_name();
+ p_script->script_class = p_class;
+ p_script->native = p_native;
+
+ CRASH_COND(p_script->native == NULL);
+
+ GDMonoClass *base = p_script->script_class->get_parent_class();
+
+ if (base != p_script->native)
+ p_script->base = base;
+
+ p_script->valid = true;
+ p_script->tool = p_script->script_class->has_attribute(CACHED_CLASS(ToolAttribute));
+
+ if (!p_script->tool) {
+ GDMonoClass *nesting_class = p_script->script_class->get_nesting_class();
+ p_script->tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute));
+ }
+
+#if TOOLS_ENABLED
+ if (!p_script->tool) {
+ p_script->tool = p_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.
// Not needed if the script class itself is a native class.
- if (script->script_class != script->native) {
- GDMonoClass *native_top = script->native;
+ if (p_script->script_class != p_script->native) {
+ GDMonoClass *native_top = p_script->native;
while (native_top) {
- native_top->fetch_methods_with_godot_api_checks(script->native);
+ native_top->fetch_methods_with_godot_api_checks(p_script->native);
if (native_top == CACHED_CLASS(GodotObject))
break;
@@ -2424,18 +2739,19 @@ Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GD
}
#endif
- script->script_class->fetch_methods_with_godot_api_checks(script->native);
+ p_script->script_class->fetch_methods_with_godot_api_checks(p_script->native);
// Need to fetch method from base classes as well
- GDMonoClass *top = script->script_class;
- while (top && top != script->native) {
- top->fetch_methods_with_godot_api_checks(script->native);
+ GDMonoClass *top = p_script->script_class;
+ while (top && top != p_script->native) {
+ top->fetch_methods_with_godot_api_checks(p_script->native);
top = top->get_parent_class();
}
- script->load_script_signals(script->script_class, script->native);
-
- return script;
+ p_script->load_script_signals(p_script->script_class, p_script->native);
+#ifdef TOOLS_ENABLED
+ p_script->_update_member_info_no_exports();
+#endif
}
bool CSharpScript::can_instance() const {
@@ -2443,7 +2759,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",
@@ -2495,7 +2812,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");
@@ -2537,7 +2856,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
@@ -2618,7 +2937,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() + "'");
@@ -2629,7 +2948,7 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) {
}
Variant::CallError unchecked_error;
- return _create_instance(NULL, 0, p_this, Object::cast_to<Reference>(p_this), unchecked_error);
+ return _create_instance(NULL, 0, p_this, Object::cast_to<Reference>(p_this) != NULL, unchecked_error);
}
PlaceHolderScriptInstance *CSharpScript::placeholder_instance_create(Object *p_this) {
@@ -2744,11 +3063,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);
@@ -2826,10 +3156,7 @@ void CSharpScript::update_exports() {
}
bool CSharpScript::has_script_signal(const StringName &p_signal) const {
- if (_signals.has(p_signal))
- return true;
-
- return false;
+ return _signals.has(p_signal);
}
void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const {
@@ -2949,7 +3276,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());
@@ -2957,8 +3285,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);
@@ -3058,5 +3386,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");
}