summaryrefslogtreecommitdiffstats
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.cpp215
1 files changed, 130 insertions, 85 deletions
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index e06440230b..3cc32bff10 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -30,7 +30,23 @@
#include "csharp_script.h"
-#include <stdint.h>
+#include "godotsharp_dirs.h"
+#include "managed_callable.h"
+#include "mono_gd/gd_mono_cache.h"
+#include "signal_awaiter_utils.h"
+#include "utils/macros.h"
+#include "utils/naming_utils.h"
+#include "utils/path_utils.h"
+#include "utils/string_utils.h"
+
+#ifdef DEBUG_METHODS_ENABLED
+#include "class_db_api_json.h"
+#endif
+
+#ifdef TOOLS_ENABLED
+#include "editor/editor_internal_calls.h"
+#include "editor/script_templates/templates.gen.h"
+#endif
#include "core/config/project_settings.h"
#include "core/debugger/engine_debugger.h"
@@ -39,36 +55,22 @@
#include "core/os/mutex.h"
#include "core/os/os.h"
#include "core/os/thread.h"
+#include "servers/text_server.h"
#ifdef TOOLS_ENABLED
#include "core/os/keyboard.h"
#include "editor/editor_file_system.h"
-#include "editor/editor_internal_calls.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/inspector_dock.h"
#include "editor/node_dock.h"
-#include "editor/script_templates/templates.gen.h"
#endif
-#ifdef DEBUG_METHODS_ENABLED
-#include "class_db_api_json.h"
-#endif
-
-#include "godotsharp_dirs.h"
-#include "managed_callable.h"
-#include "mono_gd/gd_mono_cache.h"
-#include "servers/text_server.h"
-#include "signal_awaiter_utils.h"
-#include "utils/macros.h"
-#include "utils/naming_utils.h"
-#include "utils/string_utils.h"
-
-#define CACHED_STRING_NAME(m_var) (CSharpLanguage::get_singleton()->get_string_names().m_var)
+#include <stdint.h>
// Types that will be skipped over (in favor of their base types) when setting up instance bindings.
// This must be a superset of `ignored_types` in bindings_generator.cpp.
-const Vector<String> ignored_types = { "PhysicsServer2DExtension", "PhysicsServer3DExtension" };
+const Vector<String> ignored_types = {};
#ifdef TOOLS_ENABLED
static bool _create_project_solution_if_needed() {
@@ -116,18 +118,19 @@ void CSharpLanguage::init() {
GLOBAL_DEF("dotnet/project/assembly_name", "");
#ifdef TOOLS_ENABLED
GLOBAL_DEF("dotnet/project/solution_directory", "");
+ GLOBAL_DEF(PropertyInfo(Variant::INT, "dotnet/project/assembly_reload_attempts", PROPERTY_HINT_RANGE, "1,16,1,or_greater"), 3);
#endif
- gdmono = memnew(GDMono);
- gdmono->initialize();
-
#ifdef TOOLS_ENABLED
- if (gdmono->is_runtime_initialized()) {
- gdmono->initialize_load_assemblies();
- }
-
EditorNode::add_init_callback(&_editor_init_callback);
#endif
+
+ gdmono = memnew(GDMono);
+
+ // Initialize only if the project uses C#.
+ if (gdmono->should_initialize()) {
+ gdmono->initialize();
+ }
}
void CSharpLanguage::finish() {
@@ -329,6 +332,11 @@ void CSharpLanguage::get_comment_delimiters(List<String> *p_delimiters) const {
p_delimiters->push_back("/* */"); // delimited comment
}
+void CSharpLanguage::get_doc_comment_delimiters(List<String> *p_delimiters) const {
+ p_delimiters->push_back("///"); // single-line doc comment
+ p_delimiters->push_back("/** */"); // delimited doc comment
+}
+
void CSharpLanguage::get_string_delimiters(List<String> *p_delimiters) const {
p_delimiters->push_back("' '"); // character literal
p_delimiters->push_back("\" \""); // regular string literal
@@ -393,10 +401,6 @@ Script *CSharpLanguage::create_script() const {
return memnew(CSharpScript);
}
-bool CSharpLanguage::has_named_classes() const {
- return false;
-}
-
bool CSharpLanguage::supports_builtin_mode() const {
return false;
}
@@ -436,6 +440,11 @@ static String variant_type_to_managed_name(const String &p_var_type_name) {
return "Collections.Dictionary";
}
+ if (p_var_type_name.begins_with(Variant::get_type_name(Variant::ARRAY) + "[")) {
+ String element_type = p_var_type_name.trim_prefix(Variant::get_type_name(Variant::ARRAY) + "[").trim_suffix("]");
+ return "Collections.Array<" + variant_type_to_managed_name(element_type) + ">";
+ }
+
if (p_var_type_name == Variant::get_type_name(Variant::ARRAY)) {
return "Collections.Array";
}
@@ -550,13 +559,13 @@ bool CSharpLanguage::handles_global_class_type(const String &p_type) const {
String CSharpLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const {
Ref<CSharpScript> scr = ResourceLoader::load(p_path, get_type());
- if (!scr.is_valid() || !scr->valid || !scr->global_class) {
- // Invalid script or the script is not a global class.
- return String();
- }
+ // Always assign r_base_type and r_icon_path, even if the script
+ // is not a global one. In the case that it is not a global script,
+ // return an empty string AFTER assigning the return parameters.
+ // See GDScriptLanguage::get_global_class_name() in modules/gdscript/gdscript.cpp
- String name = scr->class_name;
- if (unlikely(name.is_empty())) {
+ if (!scr.is_valid() || !scr->valid) {
+ // Invalid script.
return String();
}
@@ -583,7 +592,8 @@ String CSharpLanguage::get_global_class_name(const String &p_path, String *r_bas
*r_base_type = scr->get_instance_base_type();
}
}
- return name;
+
+ return scr->global_class ? scr->class_name : String();
}
String CSharpLanguage::debug_get_error() const {
@@ -744,11 +754,7 @@ bool CSharpLanguage::is_assembly_reloading_needed() {
return false; // Already up to date
}
} else {
- String assembly_name = GLOBAL_GET("dotnet/project/assembly_name");
-
- if (assembly_name.is_empty()) {
- assembly_name = ProjectSettings::get_singleton()->get_safe_project_name();
- }
+ String assembly_name = path::get_csharp_project_name();
assembly_path = GodotSharpDirs::get_res_temp_assemblies_dir()
.path_join(assembly_name + ".dll");
@@ -774,10 +780,6 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
return;
}
- // TODO:
- // Currently, this reloads all scripts, including those whose class is not part of the
- // assembly load context being unloaded. As such, we unnecessarily reload GodotTools.
-
print_verbose(".NET: Reloading assemblies...");
// There is no soft reloading with Mono. It's always hard reloading.
@@ -788,8 +790,20 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
MutexLock lock(script_instances_mutex);
for (SelfList<CSharpScript> *elem = script_list.first(); elem; elem = elem->next()) {
- // Cast to CSharpScript to avoid being erased by accident
- scripts.push_back(Ref<CSharpScript>(elem->self()));
+ // Do not reload scripts with only non-collectible instances to avoid disrupting event subscriptions and such.
+ bool is_reloadable = elem->self()->instances.size() == 0;
+ for (Object *obj : elem->self()->instances) {
+ ERR_CONTINUE(!obj->get_script_instance());
+ CSharpInstance *csi = static_cast<CSharpInstance *>(obj->get_script_instance());
+ if (GDMonoCache::managed_callbacks.GCHandleBridge_GCHandleIsTargetCollectible(csi->get_gchandle_intptr())) {
+ is_reloadable = true;
+ break;
+ }
+ }
+ if (is_reloadable) {
+ // Cast to CSharpScript to avoid being erased by accident.
+ scripts.push_back(Ref<CSharpScript>(elem->self()));
+ }
}
}
@@ -804,6 +818,10 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
ERR_CONTINUE(managed_callable->delegate_handle.value == nullptr);
+ if (!GDMonoCache::managed_callbacks.GCHandleBridge_GCHandleIsTargetCollectible(managed_callable->delegate_handle)) {
+ continue;
+ }
+
Array serialized_data;
bool success = GDMonoCache::managed_callbacks.DelegateUtils_TrySerializeDelegateWithGCHandle(
@@ -911,6 +929,15 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
scr->_clear();
}
+ // Release the delegates that were serialized earlier.
+ {
+ MutexLock lock(ManagedCallable::instances_mutex);
+
+ for (KeyValue<ManagedCallable *, Array> &kv : ManagedCallable::instances_pending_reload) {
+ kv.key->release_delegate_handle();
+ }
+ }
+
// Do domain reload
if (gdmono->reload_project_assemblies() != OK) {
// Failed to reload the scripts domain
@@ -1162,19 +1189,6 @@ bool CSharpLanguage::debug_break(const String &p_error, bool p_allow_continue) {
}
}
-void CSharpLanguage::_on_scripts_domain_about_to_unload() {
-#ifdef GD_MONO_HOT_RELOAD
- {
- MutexLock lock(ManagedCallable::instances_mutex);
-
- for (SelfList<ManagedCallable> *elem = ManagedCallable::instances.first(); elem; elem = elem->next()) {
- ManagedCallable *managed_callable = elem->self();
- managed_callable->release_delegate_handle();
- }
- }
-#endif
-}
-
#ifdef TOOLS_ENABLED
void CSharpLanguage::_editor_init_callback() {
// Load GodotTools and initialize GodotSharpEditor
@@ -1192,8 +1206,6 @@ void CSharpLanguage::_editor_init_callback() {
// Add plugin to EditorNode and enable it
EditorNode::add_editor_plugin(godotsharp_editor);
- ED_SHORTCUT("mono/build_solution", TTR("Build Solution"), KeyModifierMask::ALT | Key::B);
- ED_SHORTCUT_OVERRIDE("mono/build_solution", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::B);
godotsharp_editor->enable_plugin();
get_singleton()->godotsharp_editor = godotsharp_editor;
@@ -1653,7 +1665,8 @@ void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const {
}
}
- for (const PropertyInfo &prop : props) {
+ for (PropertyInfo &prop : props) {
+ validate_property(prop);
p_properties->push_back(prop);
}
}
@@ -1682,7 +1695,7 @@ bool CSharpInstance::property_can_revert(const StringName &p_name) const {
Variant ret;
Callable::CallError call_error;
GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call(
- gchandle.get_intptr(), &CACHED_STRING_NAME(_property_can_revert), args, 1, &call_error, &ret);
+ gchandle.get_intptr(), &SNAME("_property_can_revert"), args, 1, &call_error, &ret);
if (call_error.error != Callable::CallError::CALL_OK) {
return false;
@@ -1691,6 +1704,24 @@ bool CSharpInstance::property_can_revert(const StringName &p_name) const {
return (bool)ret;
}
+void CSharpInstance::validate_property(PropertyInfo &p_property) const {
+ ERR_FAIL_COND(!script.is_valid());
+
+ Variant property_arg = (Dictionary)p_property;
+ const Variant *args[1] = { &property_arg };
+
+ Variant ret;
+ Callable::CallError call_error;
+ GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call(
+ gchandle.get_intptr(), &SNAME("_validate_property"), args, 1, &call_error, &ret);
+
+ if (call_error.error != Callable::CallError::CALL_OK) {
+ return;
+ }
+
+ p_property = PropertyInfo::from_dict(property_arg);
+}
+
bool CSharpInstance::property_get_revert(const StringName &p_name, Variant &r_ret) const {
ERR_FAIL_COND_V(!script.is_valid(), false);
@@ -1700,7 +1731,7 @@ bool CSharpInstance::property_get_revert(const StringName &p_name, Variant &r_re
Variant ret;
Callable::CallError call_error;
GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call(
- gchandle.get_intptr(), &CACHED_STRING_NAME(_property_get_revert), args, 1, &call_error, &ret);
+ gchandle.get_intptr(), &SNAME("_property_get_revert"), args, 1, &call_error, &ret);
if (call_error.error != Callable::CallError::CALL_OK) {
return false;
@@ -1956,7 +1987,7 @@ const Variant CSharpInstance::get_rpc_config() const {
return script->get_rpc_config();
}
-void CSharpInstance::notification(int p_notification) {
+void CSharpInstance::notification(int p_notification, bool p_reversed) {
if (p_notification == Object::NOTIFICATION_PREDELETE) {
// When NOTIFICATION_PREDELETE is sent, we also take the chance to call Dispose().
// It's safe to call Dispose() multiple times and NOTIFICATION_PREDELETE is guaranteed
@@ -1974,7 +2005,7 @@ void CSharpInstance::notification(int p_notification) {
return;
}
- _call_notification(p_notification);
+ _call_notification(p_notification, p_reversed);
GDMonoCache::managed_callbacks.CSharpInstanceBridge_CallDispose(
gchandle.get_intptr(), /* okIfNull */ false);
@@ -1982,19 +2013,17 @@ void CSharpInstance::notification(int p_notification) {
return;
}
- _call_notification(p_notification);
+ _call_notification(p_notification, p_reversed);
}
-void CSharpInstance::_call_notification(int p_notification) {
+void CSharpInstance::_call_notification(int p_notification, bool p_reversed) {
Variant arg = p_notification;
const Variant *args[1] = { &arg };
- StringName method_name = SNAME("_notification");
-
- Callable::CallError call_error;
Variant ret;
+ Callable::CallError call_error;
GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call(
- gchandle.get_intptr(), &method_name, args, 1, &call_error, &ret);
+ gchandle.get_intptr(), &SNAME("_notification"), args, 1, &call_error, &ret);
}
String CSharpInstance::to_string(bool *r_valid) {
@@ -2218,7 +2247,7 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda
}
bool CSharpScript::_get(const StringName &p_name, Variant &r_ret) const {
- if (p_name == CSharpLanguage::singleton->string_names._script_source) {
+ if (p_name == SNAME("script/source")) {
r_ret = get_source_code();
return true;
}
@@ -2227,7 +2256,7 @@ bool CSharpScript::_get(const StringName &p_name, Variant &r_ret) const {
}
bool CSharpScript::_set(const StringName &p_name, const Variant &p_value) {
- if (p_name == CSharpLanguage::singleton->string_names._script_source) {
+ if (p_name == SNAME("script/source")) {
set_source_code(p_value);
reload();
return true;
@@ -2237,7 +2266,7 @@ bool CSharpScript::_set(const StringName &p_name, const Variant &p_value) {
}
void CSharpScript::_get_property_list(List<PropertyInfo> *p_properties) const {
- p_properties->push_back(PropertyInfo(Variant::STRING, CSharpLanguage::singleton->string_names._script_source, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
+ p_properties->push_back(PropertyInfo(Variant::STRING, SNAME("script/source"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
}
void CSharpScript::_bind_methods() {
@@ -2267,7 +2296,7 @@ void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) {
// If the EditorFileSystem singleton is available, update the file;
// otherwise, the file will be updated when the singleton becomes available.
EditorFileSystem *efs = EditorFileSystem::get_singleton();
- if (efs) {
+ if (efs && !p_script->get_path().is_empty()) {
efs->update_file(p_script->get_path());
}
#endif
@@ -2277,6 +2306,7 @@ void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) {
void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
bool tool = false;
bool global_class = false;
+ bool abstract_class = false;
// TODO: Use GDExtension godot_dictionary
Array methods_array;
@@ -2290,12 +2320,13 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
String icon_path;
Ref<CSharpScript> base_script;
GDMonoCache::managed_callbacks.ScriptManagerBridge_UpdateScriptClassInfo(
- p_script.ptr(), &class_name, &tool, &global_class, &icon_path,
+ p_script.ptr(), &class_name, &tool, &global_class, &abstract_class, &icon_path,
&methods_array, &rpc_functions_dict, &signals_dict, &base_script);
p_script->class_name = class_name;
p_script->tool = tool;
p_script->global_class = global_class;
+ p_script->abstract_class = abstract_class;
p_script->icon_path = icon_path;
p_script->rpc_config.clear();
@@ -2324,9 +2355,14 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
Variant::Type param_type = (Variant::Type)(int)param["type"];
PropertyInfo arg_info = PropertyInfo(param_type, (String)param["name"]);
arg_info.usage = (uint32_t)param["usage"];
+ if (param.has("class_name")) {
+ arg_info.class_name = (StringName)param["class_name"];
+ }
mi.arguments.push_back(arg_info);
}
+ mi.flags = (uint32_t)method_info_dict["flags"];
+
p_script->methods.set(push_index++, CSharpMethodInfo{ name, mi });
}
@@ -2354,6 +2390,9 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
Variant::Type param_type = (Variant::Type)(int)param["type"];
PropertyInfo arg_info = PropertyInfo(param_type, (String)param["name"]);
arg_info.usage = (uint32_t)param["usage"];
+ if (param.has("class_name")) {
+ arg_info.class_name = (StringName)param["class_name"];
+ }
mi.arguments.push_back(arg_info);
}
@@ -2377,7 +2416,7 @@ bool CSharpScript::can_instantiate() const {
ERR_FAIL_V_MSG(false, "Cannot instance script because the associated class could not be found. Script: '" + get_path() + "'. Make sure the script exists and contains a class definition with a name that matches the filename of the script exactly (it's case-sensitive).");
}
- return valid && extra_cond;
+ return valid && !abstract_class && extra_cond;
}
StringName CSharpScript::get_instance_base_type() const {
@@ -2573,6 +2612,18 @@ MethodInfo CSharpScript::get_method_info(const StringName &p_method) const {
return MethodInfo();
}
+Variant CSharpScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
+ ERR_FAIL_COND_V(!valid, Variant());
+
+ Variant ret;
+ bool ok = GDMonoCache::managed_callbacks.ScriptManagerBridge_CallStatic(this, &p_method, p_args, p_argcount, &r_error, &ret);
+ if (ok) {
+ return ret;
+ }
+
+ return Script::callp(p_method, p_args, p_argcount, r_error);
+}
+
Error CSharpScript::reload(bool p_keep_state) {
if (!reload_invalidated) {
return OK;
@@ -2878,9 +2929,3 @@ void ResourceFormatSaverCSharpScript::get_recognized_extensions(const Ref<Resour
bool ResourceFormatSaverCSharpScript::recognize(const Ref<Resource> &p_resource) const {
return Object::cast_to<CSharpScript>(p_resource.ptr()) != nullptr;
}
-
-CSharpLanguage::StringNameCache::StringNameCache() {
- _property_can_revert = StaticCString::create("_property_can_revert");
- _property_get_revert = StaticCString::create("_property_get_revert");
- _script_source = StaticCString::create("script/source");
-}