diff options
Diffstat (limited to 'modules/mono/csharp_script.cpp')
-rw-r--r-- | modules/mono/csharp_script.cpp | 334 |
1 files changed, 222 insertions, 112 deletions
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 46c40b2690..d2a861dbe8 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -49,6 +49,8 @@ #include "mono_gd/gd_mono_class.h" #include "mono_gd/gd_mono_marshal.h" #include "signal_awaiter_utils.h" +#include "utils/macros.h" +#include "utils/thread_local.h" #define CACHED_STRING_NAME(m_var) (CSharpLanguage::get_singleton()->get_string_names().m_var) @@ -119,7 +121,7 @@ void CSharpLanguage::init() { #ifdef TOOLS_ENABLED EditorNode::add_init_callback(&gdsharp_editor_init_callback); - GLOBAL_DEF("mono/export/include_scripts_content", true); + GLOBAL_DEF("mono/export/include_scripts_content", false); #endif } @@ -296,30 +298,28 @@ Ref<Script> CSharpLanguage::get_template(const String &p_class_name, const Strin String script_template = "using " BINDINGS_NAMESPACE ";\n" "using System;\n" "\n" - "public class %CLASS_NAME% : %BASE_CLASS_NAME%\n" + "public class %CLASS% : %BASE%\n" "{\n" - " // Member variables here, example:\n" + " // Declare member variables here. Examples:\n" " // private int a = 2;\n" - " // private string b = \"textvar\";\n" + " // private string b = \"text\";\n" "\n" + " // Called when the node enters the scene tree for the first time.\n" " public override void _Ready()\n" " {\n" - " // Called every time the node is added to the scene.\n" - " // Initialization here.\n" " \n" " }\n" "\n" - "// public override void _Process(float delta)\n" - "// {\n" - "// // Called every frame. Delta is time since last frame.\n" - "// // Update game logic here.\n" - "// \n" - "// }\n" + "// // Called every frame. 'delta' is the elapsed time since the previous frame.\n" + "// public override void _Process(float delta)\n" + "// {\n" + "// \n" + "// }\n" "}\n"; String base_class_name = get_base_class_name(p_base_class_name, p_class_name); - script_template = script_template.replace("%BASE_CLASS_NAME%", base_class_name) - .replace("%CLASS_NAME%", p_class_name); + script_template = script_template.replace("%BASE%", base_class_name) + .replace("%CLASS%", p_class_name); Ref<CSharpScript> script; script.instance(); @@ -476,7 +476,7 @@ String CSharpLanguage::_get_indentation() const { Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() { #ifdef DEBUG_ENABLED - // Printing an error here will result in endless recursion, so we must be careful + _TLS_RECURSION_GUARD_V_(Vector<StackInfo>()); if (!gdmono->is_runtime_initialized() || !GDMono::get_singleton()->get_core_api_assembly() || !GDMonoUtils::mono_cache.corlib_cache_updated) return Vector<StackInfo>(); @@ -500,15 +500,15 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() #ifdef DEBUG_ENABLED Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObject *p_stack_trace) { - // Printing an error here could result in endless recursion, so we must be careful + _TLS_RECURSION_GUARD_V_(Vector<StackInfo>()); - MonoObject *exc = NULL; + MonoException *exc = NULL; GDMonoUtils::StackTrace_GetFrames st_get_frames = CACHED_METHOD_THUNK(System_Diagnostics_StackTrace, GetFrames); - MonoArray *frames = st_get_frames(p_stack_trace, &exc); + MonoArray *frames = st_get_frames(p_stack_trace, (MonoObject **)&exc); if (exc) { - GDMonoUtils::print_unhandled_exception(exc, true /* fail silently to avoid endless recursion */); + GDMonoUtils::debug_print_unhandled_exception(exc); return Vector<StackInfo>(); } @@ -523,16 +523,16 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec si.resize(frame_count); for (int i = 0; i < frame_count; i++) { - StackInfo &sif = si[i]; + StackInfo &sif = si.write[i]; MonoObject *frame = mono_array_get(frames, MonoObject *, i); MonoString *file_name; int file_line_num; MonoString *method_decl; - get_sf_info(frame, &file_name, &file_line_num, &method_decl, &exc); + get_sf_info(frame, &file_name, &file_line_num, &method_decl, (MonoObject **)&exc); if (exc) { - GDMonoUtils::print_unhandled_exception(exc, true /* fail silently to avoid endless recursion */); + GDMonoUtils::debug_print_unhandled_exception(exc); return Vector<StackInfo>(); } @@ -561,12 +561,12 @@ void CSharpLanguage::frame() { ERR_FAIL_NULL(thunk); - MonoObject *ex; - thunk(task_scheduler, &ex); + MonoException *exc = NULL; + thunk(task_scheduler, (MonoObject **)&exc); - if (ex) { - mono_print_unhandled_exception(ex); - ERR_FAIL(); + if (exc) { + GDMonoUtils::debug_unhandled_exception(exc); + _UNREACHABLE_(); } } } @@ -638,33 +638,41 @@ void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft #ifdef TOOLS_ENABLED void CSharpLanguage::reload_assemblies_if_needed(bool p_soft_reload) { - if (gdmono->is_runtime_initialized()) { + if (!gdmono->is_runtime_initialized()) + return; - GDMonoAssembly *proj_assembly = gdmono->get_project_assembly(); + GDMonoAssembly *proj_assembly = gdmono->get_project_assembly(); - String name = ProjectSettings::get_singleton()->get("application/config/name"); - if (name.empty()) { - name = "UnnamedProject"; - } + String name = ProjectSettings::get_singleton()->get("application/config/name"); + if (name.empty()) { + name = "UnnamedProject"; + } - if (proj_assembly) { - String proj_asm_path = proj_assembly->get_path(); + name += ".dll"; - 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); - if (!FileAccess::exists(proj_asm_path)) - return; // No assembly to load - } + if (proj_assembly) { + String proj_asm_path = proj_assembly->get_path(); - if (FileAccess::get_modified_time(proj_asm_path) <= proj_assembly->get_modified_time()) - return; // Already up to date - } else { - if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(name))) + 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); + if (!FileAccess::exists(proj_asm_path)) return; // No assembly to load } + + if (FileAccess::get_modified_time(proj_asm_path) <= proj_assembly->get_modified_time()) + return; // Already up to date + } else { + if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(name))) + return; // No assembly to load } + if (!gdmono->get_core_api_assembly() && gdmono->metadata_is_api_assembly_invalidated(APIAssembly::API_CORE)) + return; // The core API assembly to load is invalidated + + if (!gdmono->get_editor_api_assembly() && gdmono->metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR)) + return; // The editor API assembly to load is invalidated + #ifndef NO_THREADS lock->lock(); #endif @@ -728,6 +736,9 @@ void CSharpLanguage::reload_assemblies_if_needed(bool p_soft_reload) { obj->get_script_instance()->get_property_state(state); map[obj->get_instance_id()] = state; obj->set_script(RefPtr()); + } else { + // no instance found. Let's remove it so we don't loop forever + E->get()->placeholders.erase(E->get()->placeholders.front()->get()); } } @@ -739,16 +750,31 @@ void CSharpLanguage::reload_assemblies_if_needed(bool p_soft_reload) { } } - if (gdmono->reload_scripts_domain() != OK) + if (gdmono->reload_scripts_domain() != OK) { + // Failed to reload the scripts domain + // Make sure to add the scripts back to their owners before returning + for (Map<Ref<CSharpScript>, Map<ObjectID, List<Pair<StringName, Variant> > > >::Element *E = to_reload.front(); E; E = E->next()) { + Ref<CSharpScript> scr = E->key(); + for (Map<ObjectID, List<Pair<StringName, Variant> > >::Element *F = E->get().front(); F; F = F->next()) { + Object *obj = ObjectDB::get_instance(F->key()); + if (!obj) + continue; + obj->set_script(scr.get_ref_ptr()); + // Save reload state for next time if not saved + if (!scr->pending_reload_state.has(obj->get_instance_id())) { + scr->pending_reload_state[obj->get_instance_id()] = F->get(); + } + } + } return; + } for (Map<Ref<CSharpScript>, Map<ObjectID, List<Pair<StringName, Variant> > > >::Element *E = to_reload.front(); E; E = E->next()) { Ref<CSharpScript> scr = E->key(); - scr->signals_invalidated = true; scr->exports_invalidated = true; + scr->signals_invalidated = true; scr->reload(p_soft_reload); - scr->update_signals(); scr->update_exports(); //restore state if saved @@ -771,6 +797,14 @@ void CSharpLanguage::reload_assemblies_if_needed(bool p_soft_reload) { continue; } + if (scr->valid && scr->is_tool() && obj->get_script_instance()->is_placeholder()) { + // Script instance was a placeholder, but now the script was built successfully and is a tool script. + // We have to replace the placeholder with an actual C# script instance. + scr->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(obj->get_script_instance())); + ScriptInstance *script_instance = scr->instance_create(obj); + obj->set_script_instance(script_instance); // Not necessary as it's already done in instance_create, but just in case... + } + for (List<Pair<StringName, Variant> >::Element *G = F->get().front(); G; G = G->next()) { obj->get_script_instance()->set(G->get().first, G->get().second); } @@ -979,6 +1013,69 @@ void CSharpLanguage::free_instance_binding_data(void *p_data) { #endif } +void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) { + + Reference *ref_owner = Object::cast_to<Reference>(p_object); + +#ifdef DEBUG_ENABLED + CRASH_COND(!ref_owner); +#endif + + void *data = p_object->get_script_instance_binding(get_language_index()); + if (!data) + return; + Ref<MonoGCHandle> &gchandle = ((Map<Object *, Ref<MonoGCHandle> >::Element *)data)->get(); + + 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, + // so the owner must hold the managed side alive again to avoid it from being GCed. + + MonoObject *target = gchandle->get_target(); + if (!target) + return; // Called after the managed side was collected, so nothing to do here + + // Release the current weak handle and replace it with a strong handle. + uint32_t strong_gchandle = MonoGCHandle::make_strong_handle(target); + gchandle->release(); + gchandle->set_handle(strong_gchandle, MonoGCHandle::STRONG_HANDLE); + } +} + +bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) { + + Reference *ref_owner = Object::cast_to<Reference>(p_object); + +#ifdef DEBUG_ENABLED + CRASH_COND(!ref_owner); +#endif + + int refcount = ref_owner->reference_get_count(); + + void *data = p_object->get_script_instance_binding(get_language_index()); + if (!data) + return refcount == 0; + Ref<MonoGCHandle> &gchandle = ((Map<Object *, Ref<MonoGCHandle> >::Element *)data)->get(); + + if (refcount == 1 && !gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 + // If owner owner is no longer referenced by the unmanaged side, + // the managed instance takes responsibility of deleting the owner when GCed. + + MonoObject *target = gchandle->get_target(); + if (!target) + return refcount == 0; // Called after the managed side was collected, so nothing to do here + + // Release the current strong handle and replace it with a weak handle. + uint32_t weak_gchandle = MonoGCHandle::make_weak_handle(target); + gchandle->release(); + gchandle->set_handle(weak_gchandle, MonoGCHandle::WEAK_HANDLE); + + return false; + } + + return refcount == 0; +} + CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpScript *p_script, const Ref<MonoGCHandle> &p_gchandle) { CSharpInstance *instance = memnew(CSharpInstance); @@ -1078,11 +1175,11 @@ bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const { GDMonoProperty *property = top->get_property(p_name); if (property) { - MonoObject *exc = NULL; + MonoException *exc = NULL; MonoObject *value = property->get_value(mono_object, &exc); if (exc) { r_ret = Variant(); - GDMonoUtils::print_unhandled_exception(exc); + GDMonoUtils::set_pending_exception(exc); } else { r_ret = GDMonoMarshal::mono_object_to_variant(value); } @@ -1269,11 +1366,13 @@ void CSharpInstance::mono_object_disposed() { void CSharpInstance::refcount_incremented() { +#ifdef DEBUG_ENABLED CRASH_COND(!base_ref); +#endif Reference *ref_owner = Object::cast_to<Reference>(owner); - if (ref_owner->reference_get_count() > 1) { // The managed side also holds a reference, hence 1 instead of 0 + 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, // so the owner must hold the managed side alive again to avoid it from being GCed. @@ -1281,26 +1380,28 @@ void CSharpInstance::refcount_incremented() { // Release the current weak handle and replace it with a strong handle. uint32_t strong_gchandle = MonoGCHandle::make_strong_handle(gchandle->get_target()); gchandle->release(); - gchandle->set_handle(strong_gchandle); + gchandle->set_handle(strong_gchandle, MonoGCHandle::STRONG_HANDLE); } } bool CSharpInstance::refcount_decremented() { +#ifdef DEBUG_ENABLED CRASH_COND(!base_ref); +#endif Reference *ref_owner = Object::cast_to<Reference>(owner); int refcount = ref_owner->reference_get_count(); - if (refcount == 1) { // The managed side also holds a reference, hence 1 instead of 0 + if (refcount == 1 && !gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 // If owner owner is no longer referenced by the unmanaged side, // the managed instance takes responsibility of deleting the owner when GCed. // Release the current strong handle and replace it with a weak handle. uint32_t weak_gchandle = MonoGCHandle::make_weak_handle(gchandle->get_target()); gchandle->release(); - gchandle->set_handle(weak_gchandle); + gchandle->set_handle(weak_gchandle, MonoGCHandle::WEAK_HANDLE); return false; } @@ -1467,8 +1568,12 @@ void CSharpScript::_update_exports_values(Map<StringName, Variant> &values, List bool CSharpScript::_update_exports() { #ifdef TOOLS_ENABLED - if (!valid) + if (!valid) { + for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) { + E->get()->set_build_failed(true); + } return false; + } bool changed = false; @@ -1490,12 +1595,12 @@ bool CSharpScript::_update_exports() { CACHED_FIELD(GodotObject, ptr)->set_value_raw(tmp_object, tmp_object); // FIXME WTF is this workaround GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0); - MonoObject *ex = NULL; - ctor->invoke(tmp_object, NULL, &ex); + MonoException *exc = NULL; + ctor->invoke(tmp_object, NULL, &exc); - if (ex) { + if (exc) { ERR_PRINT("Exception thrown from constructor of temporary MonoObject:"); - mono_print_unhandled_exception(ex); + GDMonoUtils::debug_print_unhandled_exception(exc); tmp_object = NULL; ERR_FAIL_V(false); } @@ -1544,11 +1649,11 @@ bool CSharpScript::_update_exports() { exported_members_cache.push_front(prop_info); if (tmp_object) { - MonoObject *exc = NULL; + MonoException *exc = NULL; MonoObject *ret = property->get_value(tmp_object, &exc); if (exc) { exported_members_defval_cache[name] = Variant(); - GDMonoUtils::print_unhandled_exception(exc); + GDMonoUtils::debug_print_unhandled_exception(exc); } else { exported_members_defval_cache[name] = GDMonoMarshal::mono_object_to_variant(ret); } @@ -1570,6 +1675,7 @@ bool CSharpScript::_update_exports() { _update_exports_values(values, propnames); for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) { + E->get()->set_build_failed(false); E->get()->update(propnames, values); } } @@ -1579,42 +1685,38 @@ bool CSharpScript::_update_exports() { return false; } -bool CSharpScript::_update_signals() { - if (!valid) - return false; - - bool changed = false; - - if (signals_invalidated) { - signals_invalidated = false; +void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_native_class) { - GDMonoClass *top = script_class; + // no need to load the script's signals more than once + if (!signals_invalidated) { + return; + } - _signals.clear(); - changed = true; // TODO Do a real check for change + // make sure this classes signals are empty when loading for the first time + _signals.clear(); - while (top && top != native) { - const Vector<GDMonoClass *> &delegates = top->get_all_delegates(); - for (int i = delegates.size() - 1; i >= 0; --i) { - Vector<Argument> parameters; + GDMonoClass *top = p_class; + while (top && top != p_native_class) { + const Vector<GDMonoClass *> &delegates = top->get_all_delegates(); + for (int i = delegates.size() - 1; i >= 0; --i) { + Vector<Argument> parameters; - GDMonoClass *delegate = delegates[i]; + GDMonoClass *delegate = delegates[i]; - if (_get_signal(top, delegate, parameters)) { - _signals[delegate->get_name()] = parameters; - } + if (_get_signal(top, delegate, parameters)) { + _signals[delegate->get_name()] = parameters; } - - top = top->get_parent_class(); } + + top = top->get_parent_class(); } - return changed; + signals_invalidated = false; } bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Vector<Argument> ¶ms) { if (p_delegate->has_attribute(CACHED_CLASS(SignalAttribute))) { - MonoType *raw_type = GDMonoClass::get_raw_type(p_delegate); + MonoType *raw_type = p_delegate->get_mono_type(); if (mono_type_get_type(raw_type) == MONO_TYPE_CLASS) { // Arguments are accessibles as arguments of .Invoke method @@ -1684,7 +1786,7 @@ bool CSharpScript::_get_member_export(GDMonoClass *p_class, GDMonoClassMember *p MonoObject *attr = p_member->get_attribute(CACHED_CLASS(ExportAttribute)); - PropertyHint hint; + PropertyHint hint = PROPERTY_HINT_NONE; String hint_string; if (variant_type == Variant::NIL) { @@ -1848,6 +1950,8 @@ Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class) { top = top->get_parent_class(); } + script->load_script_signals(script->script_class, script->native); + return script; } @@ -1868,7 +1972,11 @@ bool CSharpScript::can_instance() const { } #endif - return valid || (!tool && !ScriptServer::is_scripting_enabled()); +#ifdef TOOLS_ENABLED + return valid && (tool || ScriptServer::is_scripting_enabled()); +#else + return valid; +#endif } StringName CSharpScript::get_instance_base_type() const { @@ -1918,7 +2026,7 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg // Construct GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), p_argcount); - ctor->invoke(mono_object, p_args, NULL); + ctor->invoke(mono_object, p_args); // Tie managed to unmanaged instance->gchandle = MonoGCHandle::create_strong(mono_object); @@ -1966,33 +2074,23 @@ Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Variant::Call ScriptInstance *CSharpScript::instance_create(Object *p_this) { - ERR_FAIL_COND_V(!valid, NULL); - - if (!tool && !ScriptServer::is_scripting_enabled()) { -#ifdef TOOLS_ENABLED - PlaceHolderScriptInstance *si = memnew(PlaceHolderScriptInstance(CSharpLanguage::get_singleton(), Ref<Script>(this), p_this)); - placeholders.insert(si); - _update_exports(); - _update_signals(); - return si; -#else - return NULL; +#ifdef DEBUG_ENABLED + CRASH_COND(!valid); #endif - } if (!script_class) { if (GDMono::get_singleton()->get_project_assembly() == NULL) { // The project assembly is not loaded ERR_EXPLAIN("Cannot instance script because the project assembly is not loaded. Script: " + get_path()); ERR_FAIL_V(NULL); + } else { + // The project assembly is loaded, but the class could not found + ERR_EXPLAIN("Cannot instance script because the class '" + name + "' could not be found. Script: " + get_path()); + ERR_FAIL_V(NULL); } - - // The project assembly is loaded, but the class could not found - ERR_EXPLAIN("Cannot instance script because the class '" + name + "' could not be found. Script: " + get_path()); - ERR_FAIL_V(NULL); } - update_signals(); + ERR_FAIL_COND_V(!valid, NULL); if (native) { String native_name = native->get_name(); @@ -2009,6 +2107,18 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) { return _create_instance(NULL, 0, p_this, Object::cast_to<Reference>(p_this), unchecked_error); } +PlaceHolderScriptInstance *CSharpScript::placeholder_instance_create(Object *p_this) { + +#ifdef TOOLS_ENABLED + PlaceHolderScriptInstance *si = memnew(PlaceHolderScriptInstance(CSharpLanguage::get_singleton(), Ref<Script>(this), p_this)); + placeholders.insert(si); + _update_exports(); + return si; +#else + return NULL; +#endif +} + bool CSharpScript::instance_has(const Object *p_this) const { #ifndef NO_THREADS @@ -2046,6 +2156,9 @@ void CSharpScript::set_source_code(const String &p_code) { bool CSharpScript::has_method(const StringName &p_method) const { + if (!script_class) + return false; + return script_class->has_fetched_method_unknown_params(p_method); } @@ -2072,9 +2185,7 @@ Error CSharpScript::reload(bool p_keep_state) { if (script_class) { #ifdef DEBUG_ENABLED - OS::get_singleton()->print(String("Found class " + script_class->get_namespace() + "." + - script_class->get_name() + " for script " + get_path() + "\n") - .utf8()); + print_verbose("Found class " + script_class->get_namespace() + "." + script_class->get_name() + " for script " + get_path()); #endif tool = script_class->has_attribute(CACHED_CLASS(ToolAttribute)); @@ -2114,6 +2225,8 @@ Error CSharpScript::reload(bool p_keep_state) { top->fetch_methods_with_godot_api_checks(native); top = top->get_parent_class(); } + + load_script_signals(script_class, native); } return OK; @@ -2173,10 +2286,6 @@ void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const { } } -void CSharpScript::update_signals() { - _update_signals(); -} - Ref<Script> CSharpScript::get_base_script() const { // TODO search in metadata file once we have it, not important any way? @@ -2241,9 +2350,10 @@ CSharpScript::CSharpScript() : #ifdef TOOLS_ENABLED source_changed_cache = false; exports_invalidated = true; - signals_invalidated = true; #endif + signals_invalidated = true; + _resource_path_changed(); #ifdef DEBUG_ENABLED |