Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/modules/mono/csharp_script.cpp
20778 views
1
/**************************************************************************/
2
/* csharp_script.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "csharp_script.h"
32
33
#include "godotsharp_dirs.h"
34
#include "managed_callable.h"
35
#include "mono_gd/gd_mono_cache.h"
36
#include "signal_awaiter_utils.h"
37
#include "utils/macros.h"
38
#include "utils/naming_utils.h"
39
#include "utils/path_utils.h"
40
#include "utils/string_utils.h"
41
42
#ifdef DEBUG_ENABLED
43
#include "class_db_api_json.h"
44
#endif // DEBUG_ENABLED
45
46
#ifdef TOOLS_ENABLED
47
#include "editor/editor_internal_calls.h"
48
#include "editor/script_templates/templates.gen.h"
49
#endif
50
51
#include "core/config/project_settings.h"
52
#include "core/debugger/engine_debugger.h"
53
#include "core/debugger/script_debugger.h"
54
#include "core/io/file_access.h"
55
#include "core/os/mutex.h"
56
#include "core/os/os.h"
57
#include "core/os/thread.h"
58
#include "servers/text/text_server.h"
59
60
#ifdef TOOLS_ENABLED
61
#include "core/os/keyboard.h"
62
#include "editor/docks/inspector_dock.h"
63
#include "editor/docks/signals_dock.h"
64
#include "editor/editor_node.h"
65
#include "editor/file_system/editor_file_system.h"
66
#include "editor/settings/editor_settings.h"
67
#endif
68
69
// Types that will be skipped over (in favor of their base types) when setting up instance bindings.
70
// This must be a superset of `ignored_types` in bindings_generator.cpp.
71
const Vector<String> ignored_types = {};
72
73
#ifdef TOOLS_ENABLED
74
static bool _create_project_solution_if_needed() {
75
CRASH_COND(CSharpLanguage::get_singleton()->get_godotsharp_editor() == nullptr);
76
return CSharpLanguage::get_singleton()->get_godotsharp_editor()->call("CreateProjectSolutionIfNeeded");
77
}
78
#endif
79
80
CSharpLanguage *CSharpLanguage::singleton = nullptr;
81
82
GDExtensionInstanceBindingCallbacks CSharpLanguage::_instance_binding_callbacks = {
83
&_instance_binding_create_callback,
84
&_instance_binding_free_callback,
85
&_instance_binding_reference_callback
86
};
87
88
String CSharpLanguage::get_name() const {
89
return "C#";
90
}
91
92
String CSharpLanguage::get_type() const {
93
return "CSharpScript";
94
}
95
96
String CSharpLanguage::get_extension() const {
97
return "cs";
98
}
99
100
void CSharpLanguage::init() {
101
#ifdef TOOLS_ENABLED
102
if (OS::get_singleton()->get_cmdline_args().find("--generate-mono-glue")) {
103
print_verbose(".NET: Skipping runtime initialization because glue generation is enabled.");
104
return;
105
}
106
#endif
107
#ifdef DEBUG_ENABLED
108
if (OS::get_singleton()->get_cmdline_args().find("--class-db-json")) {
109
class_db_api_to_json("user://class_db_api.json", ClassDB::API_CORE);
110
#ifdef TOOLS_ENABLED
111
class_db_api_to_json("user://class_db_api_editor.json", ClassDB::API_EDITOR);
112
#endif
113
}
114
#endif // DEBUG_ENABLED
115
116
GLOBAL_DEF("dotnet/project/assembly_name", "");
117
#ifdef TOOLS_ENABLED
118
GLOBAL_DEF("dotnet/project/solution_directory", "");
119
GLOBAL_DEF(PropertyInfo(Variant::INT, "dotnet/project/assembly_reload_attempts", PROPERTY_HINT_RANGE, "1,16,1,or_greater"), 3);
120
#endif
121
122
#ifdef TOOLS_ENABLED
123
EditorNode::add_init_callback(&_editor_init_callback);
124
#endif
125
126
gdmono = memnew(GDMono);
127
128
// Initialize only if the project uses C#.
129
if (gdmono->should_initialize()) {
130
gdmono->initialize();
131
}
132
}
133
134
void CSharpLanguage::finish() {
135
finalize();
136
}
137
138
void CSharpLanguage::finalize() {
139
if (finalized) {
140
return;
141
}
142
143
if (gdmono && gdmono->is_runtime_initialized() && GDMonoCache::godot_api_cache_updated) {
144
GDMonoCache::managed_callbacks.DisposablesTracker_OnGodotShuttingDown();
145
}
146
147
finalizing = true;
148
149
// Make sure all script binding gchandles are released before finalizing GDMono.
150
for (KeyValue<Object *, CSharpScriptBinding> &E : script_bindings) {
151
CSharpScriptBinding &script_binding = E.value;
152
153
if (!script_binding.gchandle.is_released()) {
154
script_binding.gchandle.release();
155
script_binding.inited = false;
156
}
157
158
// Make sure we clear all the instance binding callbacks so they don't get called
159
// after finalizing the C# language.
160
script_binding.owner->free_instance_binding(this);
161
}
162
163
if (gdmono) {
164
memdelete(gdmono);
165
gdmono = nullptr;
166
}
167
168
// Clear here, after finalizing all domains to make sure there is nothing else referencing the elements.
169
script_bindings.clear();
170
171
#ifdef DEBUG_ENABLED
172
for (const KeyValue<ObjectID, int> &E : unsafe_object_references) {
173
const ObjectID &id = E.key;
174
Object *obj = ObjectDB::get_instance(id);
175
176
if (obj) {
177
ERR_PRINT("Leaked unsafe reference to object: " + obj->to_string());
178
} else {
179
ERR_PRINT("Leaked unsafe reference to deleted object: " + itos(id));
180
}
181
}
182
#endif // DEBUG_ENABLED
183
184
memdelete(managed_callable_middleman);
185
186
finalizing = false;
187
finalized = true;
188
}
189
190
Vector<String> CSharpLanguage::get_reserved_words() const {
191
static const Vector<String> ret = {
192
// Reserved keywords
193
"abstract",
194
"as",
195
"base",
196
"bool",
197
"break",
198
"byte",
199
"case",
200
"catch",
201
"char",
202
"checked",
203
"class",
204
"const",
205
"continue",
206
"decimal",
207
"default",
208
"delegate",
209
"do",
210
"double",
211
"else",
212
"enum",
213
"event",
214
"explicit",
215
"extern",
216
"false",
217
"finally",
218
"fixed",
219
"float",
220
"for",
221
"foreach",
222
"goto",
223
"if",
224
"implicit",
225
"in",
226
"int",
227
"interface",
228
"internal",
229
"is",
230
"lock",
231
"long",
232
"namespace",
233
"new",
234
"null",
235
"object",
236
"operator",
237
"out",
238
"override",
239
"params",
240
"private",
241
"protected",
242
"public",
243
"readonly",
244
"ref",
245
"return",
246
"sbyte",
247
"sealed",
248
"short",
249
"sizeof",
250
"stackalloc",
251
"static",
252
"string",
253
"struct",
254
"switch",
255
"this",
256
"throw",
257
"true",
258
"try",
259
"typeof",
260
"uint",
261
"ulong",
262
"unchecked",
263
"unsafe",
264
"ushort",
265
"using",
266
"virtual",
267
"void",
268
"volatile",
269
"while",
270
271
// Contextual keywords. Not reserved words, but I guess we should include
272
// them because this seems to be used only for syntax highlighting.
273
"add",
274
"alias",
275
"ascending",
276
"async",
277
"await",
278
"by",
279
"descending",
280
"dynamic",
281
"equals",
282
"from",
283
"get",
284
"global",
285
"group",
286
"into",
287
"join",
288
"let",
289
"nameof",
290
"on",
291
"orderby",
292
"partial",
293
"remove",
294
"select",
295
"set",
296
"value",
297
"var",
298
"when",
299
"where",
300
"yield",
301
};
302
303
return ret;
304
}
305
306
bool CSharpLanguage::is_control_flow_keyword(const String &p_keyword) const {
307
return p_keyword == "break" ||
308
p_keyword == "case" ||
309
p_keyword == "catch" ||
310
p_keyword == "continue" ||
311
p_keyword == "default" ||
312
p_keyword == "do" ||
313
p_keyword == "else" ||
314
p_keyword == "finally" ||
315
p_keyword == "for" ||
316
p_keyword == "foreach" ||
317
p_keyword == "goto" ||
318
p_keyword == "if" ||
319
p_keyword == "return" ||
320
p_keyword == "switch" ||
321
p_keyword == "throw" ||
322
p_keyword == "try" ||
323
p_keyword == "while";
324
}
325
326
Vector<String> CSharpLanguage::get_comment_delimiters() const {
327
static const Vector<String> delimiters = {
328
"//", // single-line comment
329
"/* */" // delimited comment
330
};
331
return delimiters;
332
}
333
334
Vector<String> CSharpLanguage::get_doc_comment_delimiters() const {
335
static const Vector<String> delimiters = {
336
"///", // single-line doc comment
337
"/** */" // delimited doc comment
338
};
339
return delimiters;
340
}
341
342
Vector<String> CSharpLanguage::get_string_delimiters() const {
343
static const Vector<String> delimiters = {
344
"' '", // character literal
345
"\" \"", // regular string literal
346
"@\" \"" // verbatim string literal
347
};
348
// Generic string highlighting suffices as a workaround for now.
349
return delimiters;
350
}
351
352
static String get_base_class_name(const String &p_base_class_name, const String p_class_name) {
353
String base_class = pascal_to_pascal_case(p_base_class_name);
354
if (p_class_name == base_class) {
355
base_class = "Godot." + base_class;
356
}
357
return base_class;
358
}
359
360
bool CSharpLanguage::is_using_templates() {
361
return true;
362
}
363
364
Ref<Script> CSharpLanguage::make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const {
365
Ref<CSharpScript> scr;
366
scr.instantiate();
367
368
String class_name_no_spaces = p_class_name.replace_char(' ', '_');
369
String base_class_name = get_base_class_name(p_base_class_name, class_name_no_spaces);
370
String processed_template = p_template;
371
processed_template = processed_template.replace("_BINDINGS_NAMESPACE_", BINDINGS_NAMESPACE)
372
.replace("_BASE_", base_class_name)
373
.replace("_CLASS_", class_name_no_spaces)
374
.replace("_TS_", _get_indentation());
375
scr->set_source_code(processed_template);
376
return scr;
377
}
378
379
Vector<ScriptLanguage::ScriptTemplate> CSharpLanguage::get_built_in_templates(const StringName &p_object) {
380
Vector<ScriptLanguage::ScriptTemplate> templates;
381
#ifdef TOOLS_ENABLED
382
for (int i = 0; i < TEMPLATES_ARRAY_SIZE; i++) {
383
if (TEMPLATES[i].inherit == p_object) {
384
templates.append(TEMPLATES[i]);
385
}
386
}
387
#endif
388
return templates;
389
}
390
391
String CSharpLanguage::validate_path(const String &p_path) const {
392
String class_name = p_path.get_file().get_basename();
393
if (get_reserved_words().has(class_name)) {
394
return RTR("Class name can't be a reserved keyword");
395
}
396
if (!TS->is_valid_identifier(class_name)) {
397
return RTR("Class name must be a valid identifier");
398
}
399
400
return "";
401
}
402
403
bool CSharpLanguage::supports_builtin_mode() const {
404
return false;
405
}
406
407
ScriptLanguage::ScriptNameCasing CSharpLanguage::preferred_file_name_casing() const {
408
return SCRIPT_NAME_CASING_PASCAL_CASE;
409
}
410
411
#ifdef TOOLS_ENABLED
412
String CSharpLanguage::make_function(const String &, const String &p_name, const PackedStringArray &p_args) const {
413
// The make_function() API does not work for C# scripts.
414
// It will always append the generated function at the very end of the script. In C#, it will break compilation by
415
// appending code after the final closing bracket (either the class' or the namespace's).
416
// To prevent issues, we have can_make_function() returning false, and make_function() is never implemented.
417
return String();
418
}
419
#else
420
String CSharpLanguage::make_function(const String &, const String &, const PackedStringArray &) const {
421
return String();
422
}
423
#endif
424
425
String CSharpLanguage::_get_indentation() const {
426
#ifdef TOOLS_ENABLED
427
if (Engine::get_singleton()->is_editor_hint()) {
428
bool use_space_indentation = EDITOR_GET("text_editor/behavior/indent/type");
429
430
if (use_space_indentation) {
431
int indent_size = EDITOR_GET("text_editor/behavior/indent/size");
432
return String(" ").repeat(indent_size);
433
}
434
}
435
#endif
436
return "\t";
437
}
438
439
bool CSharpLanguage::handles_global_class_type(const String &p_type) const {
440
return p_type == get_type();
441
}
442
443
String CSharpLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path, bool *r_is_abstract, bool *r_is_tool) const {
444
String class_name;
445
GDMonoCache::managed_callbacks.ScriptManagerBridge_GetGlobalClassName(&p_path, r_base_type, r_icon_path, r_is_abstract, r_is_tool, &class_name);
446
return class_name;
447
}
448
449
String CSharpLanguage::debug_get_error() const {
450
return _debug_error;
451
}
452
453
int CSharpLanguage::debug_get_stack_level_count() const {
454
if (_debug_parse_err_line >= 0) {
455
return 1;
456
}
457
458
// TODO: StackTrace
459
return 1;
460
}
461
462
int CSharpLanguage::debug_get_stack_level_line(int p_level) const {
463
if (_debug_parse_err_line >= 0) {
464
return _debug_parse_err_line;
465
}
466
467
// TODO: StackTrace
468
return 1;
469
}
470
471
String CSharpLanguage::debug_get_stack_level_function(int p_level) const {
472
if (_debug_parse_err_line >= 0) {
473
return String();
474
}
475
476
// TODO: StackTrace
477
return String();
478
}
479
480
String CSharpLanguage::debug_get_stack_level_source(int p_level) const {
481
if (_debug_parse_err_line >= 0) {
482
return _debug_parse_err_file;
483
}
484
485
// TODO: StackTrace
486
return String();
487
}
488
489
Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() {
490
// Printing an error here will result in endless recursion, so we must be careful
491
static thread_local bool _recursion_flag_ = false;
492
if (_recursion_flag_) {
493
return Vector<StackInfo>();
494
}
495
_recursion_flag_ = true;
496
SCOPE_EXIT {
497
_recursion_flag_ = false; // clang-format off
498
}; // clang-format on
499
500
if (!gdmono || !gdmono->is_runtime_initialized()) {
501
return Vector<StackInfo>();
502
}
503
504
Vector<StackInfo> si;
505
506
if (GDMonoCache::godot_api_cache_updated) {
507
GDMonoCache::managed_callbacks.DebuggingUtils_GetCurrentStackInfo(&si);
508
}
509
510
return si;
511
}
512
513
void CSharpLanguage::post_unsafe_reference(Object *p_obj) {
514
#ifdef DEBUG_ENABLED
515
MutexLock lock(unsafe_object_references_lock);
516
ObjectID id = p_obj->get_instance_id();
517
unsafe_object_references[id]++;
518
#endif // DEBUG_ENABLED
519
}
520
521
void CSharpLanguage::pre_unsafe_unreference(Object *p_obj) {
522
#ifdef DEBUG_ENABLED
523
MutexLock lock(unsafe_object_references_lock);
524
ObjectID id = p_obj->get_instance_id();
525
HashMap<ObjectID, int>::Iterator elem = unsafe_object_references.find(id);
526
ERR_FAIL_NULL(elem);
527
if (--elem->value == 0) {
528
unsafe_object_references.remove(elem);
529
}
530
#endif // DEBUG_ENABLED
531
}
532
533
void CSharpLanguage::frame() {
534
if (gdmono && gdmono->is_runtime_initialized() && GDMonoCache::godot_api_cache_updated) {
535
GDMonoCache::managed_callbacks.ScriptManagerBridge_FrameCallback();
536
}
537
}
538
539
struct CSharpScriptDepSort {
540
// Must support sorting so inheritance works properly (parent must be reloaded first)
541
bool operator()(const Ref<CSharpScript> &A, const Ref<CSharpScript> &B) const {
542
if (A == B) {
543
// Shouldn't happen but just in case...
544
return false;
545
}
546
const Script *I = B->get_base_script().ptr();
547
while (I) {
548
if (I == A.ptr()) {
549
// A is a base of B
550
return true;
551
}
552
553
I = I->get_base_script().ptr();
554
}
555
556
// A isn't a base of B
557
return false;
558
}
559
};
560
561
void CSharpLanguage::reload_all_scripts() {
562
#ifdef GD_MONO_HOT_RELOAD
563
if (is_assembly_reloading_needed()) {
564
reload_assemblies(false);
565
}
566
#endif
567
}
568
569
void CSharpLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload) {
570
#ifdef GD_MONO_HOT_RELOAD
571
if (is_assembly_reloading_needed()) {
572
reload_assemblies(p_soft_reload);
573
}
574
#endif
575
}
576
577
void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) {
578
CRASH_COND(!Engine::get_singleton()->is_editor_hint());
579
580
#ifdef TOOLS_ENABLED
581
get_godotsharp_editor()->get_node(NodePath("HotReloadAssemblyWatcher"))->call("RestartTimer");
582
#endif
583
584
#ifdef GD_MONO_HOT_RELOAD
585
if (is_assembly_reloading_needed()) {
586
reload_assemblies(p_soft_reload);
587
}
588
#endif
589
}
590
591
#ifdef GD_MONO_HOT_RELOAD
592
bool CSharpLanguage::is_assembly_reloading_needed() {
593
ERR_FAIL_NULL_V(gdmono, false);
594
if (!gdmono->is_runtime_initialized()) {
595
return false;
596
}
597
598
String assembly_path = gdmono->get_project_assembly_path();
599
600
if (!assembly_path.is_empty()) {
601
if (!FileAccess::exists(assembly_path)) {
602
return false; // No assembly to load
603
}
604
605
if (FileAccess::get_modified_time(assembly_path) <= gdmono->get_project_assembly_modified_time()) {
606
return false; // Already up to date
607
}
608
} else {
609
String assembly_name = Path::get_csharp_project_name();
610
611
assembly_path = GodotSharpDirs::get_res_temp_assemblies_dir()
612
.path_join(assembly_name + ".dll");
613
assembly_path = ProjectSettings::get_singleton()->globalize_path(assembly_path);
614
615
if (!FileAccess::exists(assembly_path)) {
616
return false; // No assembly to load
617
}
618
}
619
620
return true;
621
}
622
623
void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
624
ERR_FAIL_NULL(gdmono);
625
if (!gdmono->is_runtime_initialized()) {
626
return;
627
}
628
629
if (!Engine::get_singleton()->is_editor_hint()) {
630
// We disable collectible assemblies in the game player, because the limitations cause
631
// issues with mocking libraries. As such, we can only reload assemblies in the editor.
632
return;
633
}
634
635
print_verbose(".NET: Reloading assemblies...");
636
637
// There is no soft reloading with Mono. It's always hard reloading.
638
639
List<Ref<CSharpScript>> scripts;
640
641
{
642
MutexLock lock(script_instances_mutex);
643
644
for (SelfList<CSharpScript> *elem = script_list.first(); elem; elem = elem->next()) {
645
// Do not reload scripts with only non-collectible instances to avoid disrupting event subscriptions and such.
646
bool is_reloadable = elem->self()->instances.is_empty();
647
for (Object *obj : elem->self()->instances) {
648
ERR_CONTINUE(!obj->get_script_instance());
649
CSharpInstance *csi = static_cast<CSharpInstance *>(obj->get_script_instance());
650
if (GDMonoCache::managed_callbacks.GCHandleBridge_GCHandleIsTargetCollectible(csi->get_gchandle_intptr())) {
651
is_reloadable = true;
652
break;
653
}
654
}
655
if (is_reloadable) {
656
// Cast to CSharpScript to avoid being erased by accident.
657
scripts.push_back(Ref<CSharpScript>(elem->self()));
658
}
659
}
660
}
661
662
scripts.sort_custom<CSharpScriptDepSort>(); // Update in inheritance dependency order
663
664
// Serialize managed callables
665
{
666
MutexLock lock(ManagedCallable::instances_mutex);
667
668
for (SelfList<ManagedCallable> *elem = ManagedCallable::instances.first(); elem; elem = elem->next()) {
669
ManagedCallable *managed_callable = elem->self();
670
671
ERR_CONTINUE(managed_callable->delegate_handle.value == nullptr);
672
673
if (!GDMonoCache::managed_callbacks.GCHandleBridge_GCHandleIsTargetCollectible(managed_callable->delegate_handle)) {
674
continue;
675
}
676
677
Array serialized_data;
678
679
bool success = GDMonoCache::managed_callbacks.DelegateUtils_TrySerializeDelegateWithGCHandle(
680
managed_callable->delegate_handle, &serialized_data);
681
682
if (success) {
683
ManagedCallable::instances_pending_reload.insert(managed_callable, serialized_data);
684
} else {
685
if (OS::get_singleton()->is_stdout_verbose()) {
686
OS::get_singleton()->print("Failed to serialize delegate.\n");
687
}
688
689
// We failed to serialize the delegate but we still have to release it;
690
// otherwise, we won't be able to unload the assembly.
691
managed_callable->release_delegate_handle();
692
}
693
}
694
}
695
696
List<Ref<CSharpScript>> to_reload;
697
698
// We need to keep reference instances alive during reloading
699
List<Ref<RefCounted>> rc_instances;
700
701
for (const KeyValue<Object *, CSharpScriptBinding> &E : script_bindings) {
702
const CSharpScriptBinding &script_binding = E.value;
703
RefCounted *rc = Object::cast_to<RefCounted>(script_binding.owner);
704
if (rc) {
705
rc_instances.push_back(Ref<RefCounted>(rc));
706
}
707
}
708
709
// As scripts are going to be reloaded, must proceed without locking here
710
711
for (Ref<CSharpScript> &scr : scripts) {
712
// If someone removes a script from a node, deletes the script, builds, adds a script to the
713
// same node, then builds again, the script might have no path and also no script_class. In
714
// that case, we can't (and don't need to) reload it.
715
if (scr->get_path().is_empty() && !scr->valid) {
716
continue;
717
}
718
719
to_reload.push_back(scr);
720
721
// Script::instances are deleted during managed object disposal, which happens on domain finalize.
722
// Only placeholders are kept. Therefore we need to keep a copy before that happens.
723
724
for (Object *obj : scr->instances) {
725
scr->pending_reload_instances.insert(obj->get_instance_id());
726
727
// Since this script instance wasn't a placeholder, add it to the list of placeholders
728
// that will have to be eventually replaced with a script instance in case it turns into one.
729
// This list is not cleared after the reload and the collected instances only leave
730
// the list if the script is instantiated or if it was a tool script but becomes a
731
// non-tool script in a rebuild.
732
scr->pending_replace_placeholders.insert(obj->get_instance_id());
733
734
RefCounted *rc = Object::cast_to<RefCounted>(obj);
735
if (rc) {
736
rc_instances.push_back(Ref<RefCounted>(rc));
737
}
738
}
739
740
#ifdef TOOLS_ENABLED
741
for (PlaceHolderScriptInstance *instance : scr->placeholders) {
742
Object *obj = instance->get_owner();
743
scr->pending_reload_instances.insert(obj->get_instance_id());
744
745
RefCounted *rc = Object::cast_to<RefCounted>(obj);
746
if (rc) {
747
rc_instances.push_back(Ref<RefCounted>(rc));
748
}
749
}
750
#endif
751
752
// Save state and remove script from instances
753
RBMap<ObjectID, CSharpScript::StateBackup> &owners_map = scr->pending_reload_state;
754
755
for (Object *obj : scr->instances) {
756
ERR_CONTINUE(!obj->get_script_instance());
757
758
CSharpInstance *csi = static_cast<CSharpInstance *>(obj->get_script_instance());
759
760
// Call OnBeforeSerialize and save instance info
761
762
CSharpScript::StateBackup state;
763
764
Dictionary properties;
765
766
GDMonoCache::managed_callbacks.CSharpInstanceBridge_SerializeState(
767
csi->get_gchandle_intptr(), &properties, &state.event_signals);
768
769
for (const Variant *s = properties.next(nullptr); s != nullptr; s = properties.next(s)) {
770
StringName name = *s;
771
Variant value = properties[*s];
772
state.properties.push_back(Pair<StringName, Variant>(name, value));
773
}
774
775
owners_map[obj->get_instance_id()] = state;
776
}
777
}
778
779
// After the state of all instances is saved, clear scripts and script instances
780
for (Ref<CSharpScript> &scr : scripts) {
781
while (scr->instances.begin()) {
782
Object *obj = *scr->instances.begin();
783
obj->set_script(Ref<RefCounted>()); // Remove script and existing script instances (placeholder are not removed before domain reload)
784
}
785
786
scr->was_tool_before_reload = scr->type_info.is_tool;
787
scr->_clear();
788
}
789
790
// Release the delegates that were serialized earlier.
791
{
792
MutexLock lock(ManagedCallable::instances_mutex);
793
794
for (KeyValue<ManagedCallable *, Array> &kv : ManagedCallable::instances_pending_reload) {
795
kv.key->release_delegate_handle();
796
}
797
}
798
799
// Do domain reload
800
if (gdmono->reload_project_assemblies() != OK) {
801
// Failed to reload the scripts domain
802
// Make sure to add the scripts back to their owners before returning
803
for (Ref<CSharpScript> &scr : to_reload) {
804
for (const KeyValue<ObjectID, CSharpScript::StateBackup> &F : scr->pending_reload_state) {
805
Object *obj = ObjectDB::get_instance(F.key);
806
807
if (!obj) {
808
continue;
809
}
810
811
ObjectID obj_id = obj->get_instance_id();
812
813
// Use a placeholder for now to avoid losing the state when saving a scene
814
815
PlaceHolderScriptInstance *placeholder = scr->placeholder_instance_create(obj);
816
obj->set_script_instance(placeholder);
817
818
#ifdef TOOLS_ENABLED
819
// Even though build didn't fail, this tells the placeholder to keep properties and
820
// it allows using property_set_fallback for restoring the state without a valid script.
821
scr->placeholder_fallback_enabled = true;
822
#endif
823
824
// Restore Variant properties state, it will be kept by the placeholder until the next script reloading
825
for (const Pair<StringName, Variant> &G : scr->pending_reload_state[obj_id].properties) {
826
placeholder->property_set_fallback(G.first, G.second, nullptr);
827
}
828
829
scr->pending_reload_state.erase(obj_id);
830
}
831
832
scr->pending_reload_instances.clear();
833
scr->pending_reload_state.clear();
834
}
835
836
return;
837
}
838
839
// Add all script types to script bridge before reloading exports,
840
// so typed collections can be reconstructed correctly regardless of script load order.
841
for (Ref<CSharpScript> &scr : to_reload) {
842
if (!scr->get_path().is_empty() && !scr->get_path().begins_with("csharp://")) {
843
String script_path = scr->get_path();
844
845
bool valid = GDMonoCache::managed_callbacks.ScriptManagerBridge_AddScriptBridge(scr.ptr(), &script_path);
846
847
if (valid) {
848
scr->valid = true;
849
850
CSharpScript::update_script_class_info(scr);
851
852
// Ensure that the next call to CSharpScript::reload will refresh the exports
853
scr->reload_invalidated = true;
854
}
855
}
856
}
857
858
List<Ref<CSharpScript>> to_reload_state;
859
860
for (Ref<CSharpScript> &scr : to_reload) {
861
#ifdef TOOLS_ENABLED
862
scr->exports_invalidated = true;
863
#endif
864
865
if (!scr->get_path().is_empty() && !scr->get_path().begins_with("csharp://")) {
866
scr->reload(p_soft_reload);
867
868
if (!scr->valid) {
869
scr->pending_reload_instances.clear();
870
scr->pending_reload_state.clear();
871
continue;
872
}
873
} else {
874
bool success = GDMonoCache::managed_callbacks.ScriptManagerBridge_TryReloadRegisteredScriptWithClass(scr.ptr());
875
876
if (!success) {
877
// Couldn't reload
878
scr->pending_reload_instances.clear();
879
scr->pending_reload_state.clear();
880
continue;
881
}
882
}
883
884
StringName native_name = scr->get_instance_base_type();
885
886
{
887
for (const ObjectID &obj_id : scr->pending_reload_instances) {
888
Object *obj = ObjectDB::get_instance(obj_id);
889
890
if (!obj) {
891
scr->pending_reload_state.erase(obj_id);
892
continue;
893
}
894
895
if (!ClassDB::is_parent_class(obj->get_class_name(), native_name)) {
896
// No longer inherits the same compatible type, can't reload
897
scr->pending_reload_state.erase(obj_id);
898
continue;
899
}
900
901
ScriptInstance *si = obj->get_script_instance();
902
903
// Check if the script must be instantiated or kept as a placeholder
904
// when the script may not be a tool (see #65266)
905
bool replace_placeholder = scr->pending_replace_placeholders.has(obj->get_instance_id());
906
if (!scr->is_tool() && scr->was_tool_before_reload) {
907
// The script was a tool before the rebuild so the removal was intentional.
908
replace_placeholder = false;
909
scr->pending_replace_placeholders.erase(obj->get_instance_id());
910
}
911
912
#ifdef TOOLS_ENABLED
913
if (si) {
914
// If the script instance is not null, then it must be a placeholder.
915
// Non-placeholder script instances are removed in godot_icall_Object_Disposed.
916
CRASH_COND(!si->is_placeholder());
917
918
if (replace_placeholder || scr->is_tool() || ScriptServer::is_scripting_enabled()) {
919
// Replace placeholder with a script instance.
920
921
CSharpScript::StateBackup &state_backup = scr->pending_reload_state[obj_id];
922
923
// Backup placeholder script instance state before replacing it with a script instance.
924
si->get_property_state(state_backup.properties);
925
926
ScriptInstance *instance = scr->instance_create(obj);
927
928
if (instance) {
929
scr->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(si));
930
scr->pending_replace_placeholders.erase(obj->get_instance_id());
931
obj->set_script_instance(instance);
932
}
933
}
934
935
continue;
936
}
937
#else
938
CRASH_COND(si != nullptr);
939
#endif
940
941
// Re-create the script instance.
942
if (replace_placeholder || scr->is_tool() || ScriptServer::is_scripting_enabled()) {
943
// Create script instance or replace placeholder with a script instance.
944
ScriptInstance *instance = scr->instance_create(obj);
945
946
if (instance) {
947
scr->pending_replace_placeholders.erase(obj->get_instance_id());
948
obj->set_script_instance(instance);
949
continue;
950
}
951
}
952
// The script instance could not be instantiated or wasn't in the list of placeholders to replace.
953
obj->set_script(scr);
954
#ifdef DEBUG_ENABLED
955
// If we reached here, the instantiated script must be a placeholder.
956
CRASH_COND(!obj->get_script_instance()->is_placeholder());
957
#endif // DEBUG_ENABLED
958
}
959
}
960
961
to_reload_state.push_back(scr);
962
}
963
964
// Deserialize managed callables.
965
// This is done before reloading script's internal state, so potential callables invoked in properties work.
966
{
967
MutexLock lock(ManagedCallable::instances_mutex);
968
969
for (const KeyValue<ManagedCallable *, Array> &elem : ManagedCallable::instances_pending_reload) {
970
ManagedCallable *managed_callable = elem.key;
971
const Array &serialized_data = elem.value;
972
973
GCHandleIntPtr delegate = { nullptr };
974
975
bool success = GDMonoCache::managed_callbacks.DelegateUtils_TryDeserializeDelegateWithGCHandle(
976
&serialized_data, &delegate);
977
978
if (success) {
979
ERR_CONTINUE(delegate.value == nullptr);
980
managed_callable->delegate_handle = delegate;
981
} else if (OS::get_singleton()->is_stdout_verbose()) {
982
OS::get_singleton()->print("Failed to deserialize delegate\n");
983
}
984
}
985
986
ManagedCallable::instances_pending_reload.clear();
987
}
988
989
for (Ref<CSharpScript> &scr : to_reload_state) {
990
for (const ObjectID &obj_id : scr->pending_reload_instances) {
991
Object *obj = ObjectDB::get_instance(obj_id);
992
993
if (!obj) {
994
scr->pending_reload_state.erase(obj_id);
995
continue;
996
}
997
998
ERR_CONTINUE(!obj->get_script_instance());
999
1000
CSharpScript::StateBackup &state_backup = scr->pending_reload_state[obj_id];
1001
1002
CSharpInstance *csi = CAST_CSHARP_INSTANCE(obj->get_script_instance());
1003
1004
if (csi) {
1005
Dictionary properties;
1006
1007
for (const Pair<StringName, Variant> &G : state_backup.properties) {
1008
properties[G.first] = G.second;
1009
}
1010
1011
// Restore serialized state and call OnAfterDeserialize.
1012
GDMonoCache::managed_callbacks.CSharpInstanceBridge_DeserializeState(
1013
csi->get_gchandle_intptr(), &properties, &state_backup.event_signals);
1014
}
1015
}
1016
1017
scr->pending_reload_instances.clear();
1018
scr->pending_reload_state.clear();
1019
}
1020
1021
#ifdef TOOLS_ENABLED
1022
// FIXME: Hack to refresh editor in order to display new properties and signals. See if there is a better alternative.
1023
if (Engine::get_singleton()->is_editor_hint()) {
1024
InspectorDock::get_inspector_singleton()->update_tree();
1025
SignalsDock::get_singleton()->update_lists();
1026
}
1027
#endif
1028
}
1029
#endif
1030
1031
void CSharpLanguage::get_recognized_extensions(List<String> *p_extensions) const {
1032
p_extensions->push_back("cs");
1033
}
1034
1035
#ifdef TOOLS_ENABLED
1036
Error CSharpLanguage::open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) {
1037
return (Error)(int)get_godotsharp_editor()->call("OpenInExternalEditor", p_script, p_line, p_col);
1038
}
1039
1040
bool CSharpLanguage::overrides_external_editor() {
1041
return get_godotsharp_editor()->call("OverridesExternalEditor");
1042
}
1043
#endif
1044
1045
bool CSharpLanguage::debug_break_parse(const String &p_file, int p_line, const String &p_error) {
1046
// Not a parser error in our case, but it's still used for other type of errors
1047
if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) {
1048
_debug_parse_err_line = p_line;
1049
_debug_parse_err_file = p_file;
1050
_debug_error = p_error;
1051
EngineDebugger::get_script_debugger()->debug(this, false, true);
1052
return true;
1053
} else {
1054
return false;
1055
}
1056
}
1057
1058
bool CSharpLanguage::debug_break(const String &p_error, bool p_allow_continue) {
1059
if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) {
1060
_debug_parse_err_line = -1;
1061
_debug_parse_err_file = "";
1062
_debug_error = p_error;
1063
EngineDebugger::get_script_debugger()->debug(this, p_allow_continue);
1064
return true;
1065
} else {
1066
return false;
1067
}
1068
}
1069
1070
#ifdef TOOLS_ENABLED
1071
void CSharpLanguage::_editor_init_callback() {
1072
// Load GodotTools and initialize GodotSharpEditor
1073
1074
int32_t interop_funcs_size = 0;
1075
const void **interop_funcs = godotsharp::get_editor_interop_funcs(interop_funcs_size);
1076
1077
Object *editor_plugin_obj = GDMono::get_singleton()->get_plugin_callbacks().LoadToolsAssemblyCallback(
1078
GodotSharpDirs::get_data_editor_tools_dir().path_join("GodotTools.dll").utf16().get_data(),
1079
interop_funcs, interop_funcs_size);
1080
CRASH_COND(editor_plugin_obj == nullptr);
1081
1082
EditorPlugin *godotsharp_editor = Object::cast_to<EditorPlugin>(editor_plugin_obj);
1083
CRASH_COND(godotsharp_editor == nullptr);
1084
1085
// Add plugin to EditorNode and enable it
1086
EditorNode::add_editor_plugin(godotsharp_editor);
1087
godotsharp_editor->enable_plugin();
1088
1089
get_singleton()->godotsharp_editor = godotsharp_editor;
1090
}
1091
#endif
1092
1093
void CSharpLanguage::set_language_index(int p_idx) {
1094
ERR_FAIL_COND(lang_idx != -1);
1095
lang_idx = p_idx;
1096
}
1097
1098
void CSharpLanguage::release_script_gchandle(MonoGCHandleData &p_gchandle) {
1099
if (!p_gchandle.is_released()) { // Do not lock unnecessarily
1100
MutexLock lock(get_singleton()->script_gchandle_release_mutex);
1101
p_gchandle.release();
1102
}
1103
}
1104
1105
void CSharpLanguage::release_script_gchandle_thread_safe(GCHandleIntPtr p_gchandle_to_free, MonoGCHandleData &r_gchandle) {
1106
if (!r_gchandle.is_released() && r_gchandle.get_intptr() == p_gchandle_to_free) { // Do not lock unnecessarily
1107
MutexLock lock(get_singleton()->script_gchandle_release_mutex);
1108
if (!r_gchandle.is_released() && r_gchandle.get_intptr() == p_gchandle_to_free) {
1109
r_gchandle.release();
1110
}
1111
}
1112
}
1113
1114
void CSharpLanguage::release_binding_gchandle_thread_safe(GCHandleIntPtr p_gchandle_to_free, CSharpScriptBinding &r_script_binding) {
1115
MonoGCHandleData &gchandle = r_script_binding.gchandle;
1116
if (!gchandle.is_released() && gchandle.get_intptr() == p_gchandle_to_free) { // Do not lock unnecessarily
1117
MutexLock lock(get_singleton()->script_gchandle_release_mutex);
1118
if (!gchandle.is_released() && gchandle.get_intptr() == p_gchandle_to_free) {
1119
gchandle.release();
1120
r_script_binding.inited = false; // Here too, to be thread safe
1121
}
1122
}
1123
}
1124
1125
CSharpLanguage::CSharpLanguage() {
1126
ERR_FAIL_COND_MSG(singleton, "C# singleton already exists.");
1127
singleton = this;
1128
}
1129
1130
CSharpLanguage::~CSharpLanguage() {
1131
finalize();
1132
singleton = nullptr;
1133
}
1134
1135
bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_binding, Object *p_object) {
1136
#ifdef DEBUG_ENABLED
1137
// I don't trust you
1138
if (p_object->get_script_instance()) {
1139
CSharpInstance *csharp_instance = CAST_CSHARP_INSTANCE(p_object->get_script_instance());
1140
CRASH_COND(csharp_instance != nullptr && !csharp_instance->is_destructing_script_instance());
1141
}
1142
#endif // DEBUG_ENABLED
1143
1144
StringName type_name = p_object->get_class_name();
1145
1146
const ClassDB::ClassInfo *classinfo = ClassDB::classes.getptr(type_name);
1147
1148
// This skipping of GDExtension classes, as well as whatever classes are in this list of ignored types, is a
1149
// workaround to allow GDExtension classes to be used from C# so long as they're only used through base classes that
1150
// are registered from the engine. This will likely need to be removed whenever proper support for GDExtension
1151
// classes is added to C#. See #75955 for more details.
1152
while (classinfo && (!classinfo->exposed || classinfo->gdextension || ignored_types.has(classinfo->name))) {
1153
classinfo = classinfo->inherits_ptr;
1154
}
1155
1156
ERR_FAIL_NULL_V(classinfo, false);
1157
type_name = classinfo->name;
1158
1159
bool parent_is_object_class = ClassDB::is_parent_class(p_object->get_class_name(), type_name);
1160
ERR_FAIL_COND_V_MSG(!parent_is_object_class, false,
1161
"Type inherits from native type '" + type_name + "', so it can't be instantiated in object of type: '" + p_object->get_class() + "'.");
1162
1163
#ifdef DEBUG_ENABLED
1164
CRASH_COND(!r_script_binding.gchandle.is_released());
1165
#endif // DEBUG_ENABLED
1166
1167
GCHandleIntPtr strong_gchandle =
1168
GDMonoCache::managed_callbacks.ScriptManagerBridge_CreateManagedForGodotObjectBinding(
1169
&type_name, p_object);
1170
1171
ERR_FAIL_NULL_V(strong_gchandle.value, false);
1172
1173
r_script_binding.inited = true;
1174
r_script_binding.type_name = type_name;
1175
r_script_binding.gchandle = MonoGCHandleData(strong_gchandle, gdmono::GCHandleType::STRONG_HANDLE);
1176
r_script_binding.owner = p_object;
1177
1178
// Tie managed to unmanaged
1179
RefCounted *rc = Object::cast_to<RefCounted>(p_object);
1180
1181
if (rc) {
1182
// Unsafe refcount increment. The managed instance also counts as a reference.
1183
// This way if the unmanaged world has no references to our owner
1184
// but the managed instance is alive, the refcount will be 1 instead of 0.
1185
// See: godot_icall_RefCounted_Dtor(MonoObject *p_obj, Object *p_ptr)
1186
1187
rc->reference();
1188
CSharpLanguage::get_singleton()->post_unsafe_reference(rc);
1189
}
1190
1191
return true;
1192
}
1193
1194
RBMap<Object *, CSharpScriptBinding>::Element *CSharpLanguage::insert_script_binding(Object *p_object, const CSharpScriptBinding &p_script_binding) {
1195
return script_bindings.insert(p_object, p_script_binding);
1196
}
1197
1198
void *CSharpLanguage::_instance_binding_create_callback(void *, void *p_instance) {
1199
CSharpLanguage *csharp_lang = CSharpLanguage::get_singleton();
1200
1201
MutexLock lock(csharp_lang->language_bind_mutex);
1202
1203
RBMap<Object *, CSharpScriptBinding>::Element *match = csharp_lang->script_bindings.find((Object *)p_instance);
1204
if (match) {
1205
return (void *)match;
1206
}
1207
1208
CSharpScriptBinding script_binding;
1209
1210
return (void *)csharp_lang->insert_script_binding((Object *)p_instance, script_binding);
1211
}
1212
1213
void CSharpLanguage::_instance_binding_free_callback(void *, void *, void *p_binding) {
1214
CSharpLanguage *csharp_lang = CSharpLanguage::get_singleton();
1215
1216
if (GDMono::get_singleton() == nullptr) {
1217
#ifdef DEBUG_ENABLED
1218
CRASH_COND(csharp_lang && !csharp_lang->script_bindings.is_empty());
1219
#endif // DEBUG_ENABLED
1220
// Mono runtime finalized, all the gchandle bindings were already released
1221
return;
1222
}
1223
1224
if (csharp_lang->finalizing) {
1225
return; // inside CSharpLanguage::finish(), all the gchandle bindings are released there
1226
}
1227
1228
{
1229
MutexLock lock(csharp_lang->language_bind_mutex);
1230
1231
RBMap<Object *, CSharpScriptBinding>::Element *data = (RBMap<Object *, CSharpScriptBinding>::Element *)p_binding;
1232
1233
CSharpScriptBinding &script_binding = data->value();
1234
1235
if (script_binding.inited) {
1236
// Set the native instance field to IntPtr.Zero, if not yet garbage collected.
1237
// This is done to avoid trying to dispose the native instance from Dispose(bool).
1238
GDMonoCache::managed_callbacks.ScriptManagerBridge_SetGodotObjectPtr(
1239
script_binding.gchandle.get_intptr(), nullptr);
1240
1241
script_binding.gchandle.release();
1242
script_binding.inited = false;
1243
}
1244
1245
csharp_lang->script_bindings.erase(data);
1246
}
1247
}
1248
1249
GDExtensionBool CSharpLanguage::_instance_binding_reference_callback(void *p_token, void *p_binding, GDExtensionBool p_reference) {
1250
// Instance bindings callbacks can only be called if the C# language is available.
1251
// Failing this assert usually means that we didn't clear the instance binding in some Object
1252
// and the C# language has already been finalized.
1253
DEV_ASSERT(CSharpLanguage::get_singleton() != nullptr);
1254
1255
CRASH_COND(!p_binding);
1256
1257
CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)p_binding)->get();
1258
1259
RefCounted *rc_owner = Object::cast_to<RefCounted>(script_binding.owner);
1260
1261
#ifdef DEBUG_ENABLED
1262
CRASH_COND(!rc_owner);
1263
#endif // DEBUG_ENABLED
1264
1265
MonoGCHandleData &gchandle = script_binding.gchandle;
1266
1267
int refcount = rc_owner->get_reference_count();
1268
1269
if (!script_binding.inited) {
1270
return refcount == 0;
1271
}
1272
1273
if (p_reference) {
1274
// Refcount incremented
1275
if (refcount > 1 && gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0
1276
// The reference count was increased after the managed side was the only one referencing our owner.
1277
// This means the owner is being referenced again by the unmanaged side,
1278
// so the owner must hold the managed side alive again to avoid it from being GCed.
1279
1280
// Release the current weak handle and replace it with a strong handle.
1281
1282
GCHandleIntPtr old_gchandle = gchandle.get_intptr();
1283
gchandle.handle = { nullptr }; // No longer owns the handle (released by swap function)
1284
1285
GCHandleIntPtr new_gchandle = { nullptr };
1286
bool create_weak = false;
1287
bool target_alive = GDMonoCache::managed_callbacks.ScriptManagerBridge_SwapGCHandleForType(
1288
old_gchandle, &new_gchandle, create_weak);
1289
1290
if (!target_alive) {
1291
return false; // Called after the managed side was collected, so nothing to do here
1292
}
1293
1294
gchandle = MonoGCHandleData(new_gchandle, gdmono::GCHandleType::STRONG_HANDLE);
1295
}
1296
1297
return false;
1298
} else {
1299
// Refcount decremented
1300
if (refcount == 1 && !gchandle.is_released() && !gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0
1301
// If owner owner is no longer referenced by the unmanaged side,
1302
// the managed instance takes responsibility of deleting the owner when GCed.
1303
1304
// Release the current strong handle and replace it with a weak handle.
1305
1306
GCHandleIntPtr old_gchandle = gchandle.get_intptr();
1307
gchandle.handle = { nullptr }; // No longer owns the handle (released by swap function)
1308
1309
GCHandleIntPtr new_gchandle = { nullptr };
1310
bool create_weak = true;
1311
bool target_alive = GDMonoCache::managed_callbacks.ScriptManagerBridge_SwapGCHandleForType(
1312
old_gchandle, &new_gchandle, create_weak);
1313
1314
if (!target_alive) {
1315
return refcount == 0; // Called after the managed side was collected, so nothing to do here
1316
}
1317
1318
gchandle = MonoGCHandleData(new_gchandle, gdmono::GCHandleType::WEAK_HANDLE);
1319
1320
return false;
1321
}
1322
1323
return refcount == 0;
1324
}
1325
}
1326
1327
void *CSharpLanguage::get_instance_binding(Object *p_object) {
1328
return p_object->get_instance_binding(get_singleton(), &_instance_binding_callbacks);
1329
}
1330
1331
void *CSharpLanguage::get_instance_binding_with_setup(Object *p_object) {
1332
void *binding = get_instance_binding(p_object);
1333
1334
// Initially this was in `_instance_binding_create_callback`. However, after the new instance
1335
// binding re-write it was resulting in a deadlock in `_instance_binding_reference`, as
1336
// `setup_csharp_script_binding` may call `reference()`. It was moved here outside to fix that.
1337
1338
if (binding) {
1339
CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)binding)->value();
1340
1341
if (!script_binding.inited) {
1342
MutexLock lock(CSharpLanguage::get_singleton()->get_language_bind_mutex());
1343
1344
if (!script_binding.inited) { // Another thread may have set it up
1345
CSharpLanguage::get_singleton()->setup_csharp_script_binding(script_binding, p_object);
1346
}
1347
}
1348
}
1349
1350
return binding;
1351
}
1352
1353
void *CSharpLanguage::get_existing_instance_binding(Object *p_object) {
1354
#ifdef DEBUG_ENABLED
1355
CRASH_COND(p_object->has_instance_binding(p_object));
1356
#endif // DEBUG_ENABLED
1357
return get_instance_binding(p_object);
1358
}
1359
1360
bool CSharpLanguage::has_instance_binding(Object *p_object) {
1361
return p_object->has_instance_binding(get_singleton());
1362
}
1363
void CSharpLanguage::tie_native_managed_to_unmanaged(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged, const StringName *p_native_name, bool p_ref_counted) {
1364
// This method should not fail
1365
1366
CRASH_COND(!p_unmanaged);
1367
1368
// All mono objects created from the managed world (e.g.: 'new Player()')
1369
// need to have a CSharpScript in order for their methods to be callable from the unmanaged side
1370
1371
RefCounted *rc = Object::cast_to<RefCounted>(p_unmanaged);
1372
1373
CRASH_COND(p_ref_counted != (bool)rc);
1374
1375
MonoGCHandleData gchandle = MonoGCHandleData(p_gchandle_intptr,
1376
p_ref_counted ? gdmono::GCHandleType::WEAK_HANDLE : gdmono::GCHandleType::STRONG_HANDLE);
1377
1378
// If it's just a wrapper Godot class and not a custom inheriting class, then attach a
1379
// script binding instead. One of the advantages of this is that if a script is attached
1380
// later and it's not a C# script, then the managed object won't have to be disposed.
1381
// Another reason for doing this is that this instance could outlive CSharpLanguage, which would
1382
// be problematic when using a script. See: https://github.com/godotengine/godot/issues/25621
1383
1384
if (p_ref_counted) {
1385
// Unsafe refcount increment. The managed instance also counts as a reference.
1386
// This way if the unmanaged world has no references to our owner
1387
// but the managed instance is alive, the refcount will be 1 instead of 0.
1388
// See: godot_icall_RefCounted_Dtor(MonoObject *p_obj, Object *p_ptr)
1389
1390
// May not me referenced yet, so we must use init_ref() instead of reference()
1391
if (rc->init_ref()) {
1392
CSharpLanguage::get_singleton()->post_unsafe_reference(rc);
1393
}
1394
}
1395
1396
// The object was just created, no script instance binding should have been attached
1397
CRASH_COND(CSharpLanguage::has_instance_binding(p_unmanaged));
1398
1399
void *binding = CSharpLanguage::get_singleton()->get_instance_binding(p_unmanaged);
1400
1401
CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)binding)->value();
1402
script_binding.inited = true;
1403
script_binding.type_name = *p_native_name;
1404
script_binding.gchandle = gchandle;
1405
script_binding.owner = p_unmanaged;
1406
}
1407
1408
void CSharpLanguage::tie_user_managed_to_unmanaged(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged, Ref<CSharpScript> *p_script, bool p_ref_counted) {
1409
// This method should not fail
1410
1411
Ref<CSharpScript> script = *p_script;
1412
// We take care of destructing this reference here, so the managed code won't need to do another P/Invoke call
1413
p_script->~Ref();
1414
1415
CRASH_COND(!p_unmanaged);
1416
1417
// All mono objects created from the managed world (e.g.: 'new Player()')
1418
// need to have a CSharpScript in order for their methods to be callable from the unmanaged side
1419
1420
RefCounted *rc = Object::cast_to<RefCounted>(p_unmanaged);
1421
1422
CRASH_COND(p_ref_counted != (bool)rc);
1423
1424
MonoGCHandleData gchandle = MonoGCHandleData(p_gchandle_intptr,
1425
p_ref_counted ? gdmono::GCHandleType::WEAK_HANDLE : gdmono::GCHandleType::STRONG_HANDLE);
1426
1427
CRASH_COND(script.is_null());
1428
1429
CSharpInstance *csharp_instance = CSharpInstance::create_for_managed_type(p_unmanaged, script.ptr(), gchandle);
1430
1431
p_unmanaged->set_script_instance(csharp_instance);
1432
1433
csharp_instance->connect_event_signals();
1434
}
1435
1436
void CSharpLanguage::tie_managed_to_unmanaged_with_pre_setup(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged) {
1437
// This method should not fail
1438
1439
CRASH_COND(!p_unmanaged);
1440
1441
CSharpInstance *instance = CAST_CSHARP_INSTANCE(p_unmanaged->get_script_instance());
1442
1443
if (!instance) {
1444
// Native bindings don't need post-setup
1445
return;
1446
}
1447
1448
CRASH_COND(!instance->gchandle.is_released());
1449
1450
// Tie managed to unmanaged
1451
instance->gchandle = MonoGCHandleData(p_gchandle_intptr, gdmono::GCHandleType::STRONG_HANDLE);
1452
1453
if (instance->base_ref_counted) {
1454
instance->_reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback)
1455
}
1456
1457
{
1458
MutexLock lock(CSharpLanguage::get_singleton()->get_script_instances_mutex());
1459
// instances is a set, so it's safe to insert multiple times (e.g.: from _internal_new_managed)
1460
instance->script->instances.insert(instance->owner);
1461
}
1462
1463
instance->connect_event_signals();
1464
}
1465
1466
CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpScript *p_script, const MonoGCHandleData &p_gchandle) {
1467
CSharpInstance *instance = memnew(CSharpInstance(Ref<CSharpScript>(p_script)));
1468
1469
RefCounted *rc = Object::cast_to<RefCounted>(p_owner);
1470
1471
instance->base_ref_counted = rc != nullptr;
1472
instance->owner = p_owner;
1473
instance->gchandle = p_gchandle;
1474
1475
if (instance->base_ref_counted) {
1476
instance->_reference_owner_unsafe();
1477
}
1478
1479
{
1480
MutexLock lock(CSharpLanguage::get_singleton()->get_script_instances_mutex());
1481
p_script->instances.insert(p_owner);
1482
}
1483
1484
return instance;
1485
}
1486
1487
Object *CSharpInstance::get_owner() {
1488
return owner;
1489
}
1490
1491
bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) {
1492
ERR_FAIL_COND_V(script.is_null(), false);
1493
1494
return GDMonoCache::managed_callbacks.CSharpInstanceBridge_Set(
1495
gchandle.get_intptr(), &p_name, &p_value);
1496
}
1497
1498
bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const {
1499
ERR_FAIL_COND_V(script.is_null(), false);
1500
1501
Variant ret_value;
1502
1503
bool ret = GDMonoCache::managed_callbacks.CSharpInstanceBridge_Get(
1504
gchandle.get_intptr(), &p_name, &ret_value);
1505
1506
if (ret) {
1507
r_ret = ret_value;
1508
return true;
1509
}
1510
1511
return false;
1512
}
1513
1514
void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const {
1515
List<PropertyInfo> props;
1516
ERR_FAIL_COND(script.is_null());
1517
#ifdef TOOLS_ENABLED
1518
for (const PropertyInfo &prop : script->exported_members_cache) {
1519
props.push_back(prop);
1520
}
1521
#else
1522
for (const KeyValue<StringName, PropertyInfo> &E : script->member_info) {
1523
props.push_front(E.value);
1524
}
1525
#endif
1526
1527
for (PropertyInfo &prop : props) {
1528
validate_property(prop);
1529
p_properties->push_back(prop);
1530
}
1531
1532
// Call _get_property_list
1533
1534
StringName method = SNAME("_get_property_list");
1535
1536
Variant ret;
1537
Callable::CallError call_error;
1538
bool ok = GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call(
1539
gchandle.get_intptr(), &method, nullptr, 0, &call_error, &ret);
1540
1541
// CALL_ERROR_INVALID_METHOD would simply mean it was not overridden
1542
if (call_error.error != Callable::CallError::CALL_ERROR_INVALID_METHOD) {
1543
if (call_error.error != Callable::CallError::CALL_OK) {
1544
ERR_PRINT("Error calling '_get_property_list': " + Variant::get_call_error_text(method, nullptr, 0, call_error));
1545
} else if (!ok) {
1546
ERR_PRINT("Unexpected error calling '_get_property_list'");
1547
} else {
1548
Array array = ret;
1549
for (int i = 0, size = array.size(); i < size; i++) {
1550
p_properties->push_back(PropertyInfo::from_dict(array.get(i)));
1551
}
1552
}
1553
}
1554
1555
CSharpScript *top = script.ptr()->base_script.ptr();
1556
while (top != nullptr) {
1557
props.clear();
1558
#ifdef TOOLS_ENABLED
1559
for (const PropertyInfo &prop : top->exported_members_cache) {
1560
props.push_back(prop);
1561
}
1562
#else
1563
for (const KeyValue<StringName, PropertyInfo> &E : top->member_info) {
1564
props.push_front(E.value);
1565
}
1566
#endif
1567
1568
for (PropertyInfo &prop : props) {
1569
validate_property(prop);
1570
p_properties->push_back(prop);
1571
}
1572
1573
top = top->base_script.ptr();
1574
}
1575
}
1576
1577
Variant::Type CSharpInstance::get_property_type(const StringName &p_name, bool *r_is_valid) const {
1578
if (script->member_info.has(p_name)) {
1579
if (r_is_valid) {
1580
*r_is_valid = true;
1581
}
1582
return script->member_info[p_name].type;
1583
}
1584
1585
if (r_is_valid) {
1586
*r_is_valid = false;
1587
}
1588
1589
return Variant::NIL;
1590
}
1591
1592
bool CSharpInstance::property_can_revert(const StringName &p_name) const {
1593
ERR_FAIL_COND_V(script.is_null(), false);
1594
1595
Variant name_arg = p_name;
1596
const Variant *args[1] = { &name_arg };
1597
1598
Variant ret;
1599
Callable::CallError call_error;
1600
GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call(
1601
gchandle.get_intptr(), &SNAME("_property_can_revert"), args, 1, &call_error, &ret);
1602
1603
if (call_error.error != Callable::CallError::CALL_OK) {
1604
return false;
1605
}
1606
1607
return (bool)ret;
1608
}
1609
1610
void CSharpInstance::validate_property(PropertyInfo &p_property) const {
1611
ERR_FAIL_COND(script.is_null());
1612
1613
Variant property_arg = (Dictionary)p_property;
1614
const Variant *args[1] = { &property_arg };
1615
1616
Variant ret;
1617
Callable::CallError call_error;
1618
GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call(
1619
gchandle.get_intptr(), &SNAME("_validate_property"), args, 1, &call_error, &ret);
1620
1621
if (call_error.error != Callable::CallError::CALL_OK) {
1622
return;
1623
}
1624
1625
p_property = PropertyInfo::from_dict(property_arg);
1626
}
1627
1628
bool CSharpInstance::property_get_revert(const StringName &p_name, Variant &r_ret) const {
1629
ERR_FAIL_COND_V(script.is_null(), false);
1630
1631
Variant name_arg = p_name;
1632
const Variant *args[1] = { &name_arg };
1633
1634
Variant ret;
1635
Callable::CallError call_error;
1636
GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call(
1637
gchandle.get_intptr(), &SNAME("_property_get_revert"), args, 1, &call_error, &ret);
1638
1639
if (call_error.error != Callable::CallError::CALL_OK) {
1640
return false;
1641
}
1642
1643
r_ret = ret;
1644
return true;
1645
}
1646
1647
void CSharpInstance::get_method_list(List<MethodInfo> *p_list) const {
1648
if (!script->is_valid() || !script->valid) {
1649
return;
1650
}
1651
1652
script->get_script_method_list(p_list);
1653
}
1654
1655
bool CSharpInstance::has_method(const StringName &p_method) const {
1656
if (script.is_null()) {
1657
return false;
1658
}
1659
1660
if (!GDMonoCache::godot_api_cache_updated) {
1661
return false;
1662
}
1663
1664
return GDMonoCache::managed_callbacks.CSharpInstanceBridge_HasMethodUnknownParams(
1665
gchandle.get_intptr(), &p_method);
1666
}
1667
1668
int CSharpInstance::get_method_argument_count(const StringName &p_method, bool *r_is_valid) const {
1669
if (!script->is_valid() || !script->valid) {
1670
if (r_is_valid) {
1671
*r_is_valid = false;
1672
}
1673
return 0;
1674
}
1675
1676
const CSharpScript *top = script.ptr();
1677
while (top != nullptr) {
1678
for (const CSharpScript::CSharpMethodInfo &E : top->methods) {
1679
if (E.name == p_method) {
1680
if (r_is_valid) {
1681
*r_is_valid = true;
1682
}
1683
return E.method_info.arguments.size();
1684
}
1685
}
1686
1687
top = top->base_script.ptr();
1688
}
1689
1690
if (r_is_valid) {
1691
*r_is_valid = false;
1692
}
1693
return 0;
1694
}
1695
1696
Variant CSharpInstance::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
1697
ERR_FAIL_COND_V(script.is_null(), Variant());
1698
1699
Variant ret;
1700
GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call(
1701
gchandle.get_intptr(), &p_method, p_args, p_argcount, &r_error, &ret);
1702
1703
return ret;
1704
}
1705
1706
bool CSharpInstance::_reference_owner_unsafe() {
1707
#ifdef DEBUG_ENABLED
1708
CRASH_COND(!base_ref_counted);
1709
CRASH_COND(owner == nullptr);
1710
CRASH_COND(unsafe_referenced); // already referenced
1711
#endif // DEBUG_ENABLED
1712
1713
// Unsafe refcount increment. The managed instance also counts as a reference.
1714
// This way if the unmanaged world has no references to our owner
1715
// but the managed instance is alive, the refcount will be 1 instead of 0.
1716
// See: _unreference_owner_unsafe()
1717
1718
// May not be referenced yet, so we must use init_ref() instead of reference()
1719
if (static_cast<RefCounted *>(owner)->init_ref()) {
1720
CSharpLanguage::get_singleton()->post_unsafe_reference(owner);
1721
unsafe_referenced = true;
1722
}
1723
1724
return unsafe_referenced;
1725
}
1726
1727
bool CSharpInstance::_unreference_owner_unsafe() {
1728
#ifdef DEBUG_ENABLED
1729
CRASH_COND(!base_ref_counted);
1730
CRASH_COND(owner == nullptr);
1731
#endif // DEBUG_ENABLED
1732
1733
if (!unsafe_referenced) {
1734
return false; // Already unreferenced
1735
}
1736
1737
unsafe_referenced = false;
1738
1739
// Called from CSharpInstance::mono_object_disposed() or ~CSharpInstance()
1740
1741
// Unsafe refcount decrement. The managed instance also counts as a reference.
1742
// See: _reference_owner_unsafe()
1743
1744
// Destroying the owner here means self destructing, so we defer the owner destruction to the caller.
1745
CSharpLanguage::get_singleton()->pre_unsafe_unreference(owner);
1746
return static_cast<RefCounted *>(owner)->unreference();
1747
}
1748
1749
bool CSharpInstance::_internal_new_managed() {
1750
CSharpLanguage::get_singleton()->release_script_gchandle(gchandle);
1751
1752
ERR_FAIL_NULL_V(owner, false);
1753
ERR_FAIL_COND_V(script.is_null(), false);
1754
ERR_FAIL_COND_V(!script->can_instantiate(), false);
1755
1756
bool ok = GDMonoCache::managed_callbacks.ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance(
1757
script.ptr(), owner, nullptr, 0);
1758
1759
if (!ok) {
1760
// Important to clear this before destroying the script instance here
1761
script = Ref<CSharpScript>();
1762
owner = nullptr;
1763
1764
return false;
1765
}
1766
1767
CRASH_COND(gchandle.is_released());
1768
1769
return true;
1770
}
1771
1772
void CSharpInstance::mono_object_disposed(GCHandleIntPtr p_gchandle_to_free) {
1773
// Must make sure event signals are not left dangling
1774
disconnect_event_signals();
1775
1776
#ifdef DEBUG_ENABLED
1777
CRASH_COND(base_ref_counted);
1778
CRASH_COND(gchandle.is_released());
1779
#endif // DEBUG_ENABLED
1780
CSharpLanguage::get_singleton()->release_script_gchandle_thread_safe(p_gchandle_to_free, gchandle);
1781
}
1782
1783
void CSharpInstance::mono_object_disposed_baseref(GCHandleIntPtr p_gchandle_to_free, bool p_is_finalizer, bool &r_delete_owner, bool &r_remove_script_instance) {
1784
#ifdef DEBUG_ENABLED
1785
CRASH_COND(!base_ref_counted);
1786
CRASH_COND(gchandle.is_released());
1787
#endif // DEBUG_ENABLED
1788
1789
// Must make sure event signals are not left dangling
1790
disconnect_event_signals();
1791
1792
r_remove_script_instance = false;
1793
1794
if (_unreference_owner_unsafe()) {
1795
// Safe to self destruct here with memdelete(owner), but it's deferred to the caller to prevent future mistakes.
1796
r_delete_owner = true;
1797
} else {
1798
r_delete_owner = false;
1799
CSharpLanguage::get_singleton()->release_script_gchandle_thread_safe(p_gchandle_to_free, gchandle);
1800
1801
if (!p_is_finalizer) {
1802
// If the native instance is still alive and Dispose() was called
1803
// (instead of the finalizer), then we remove the script instance.
1804
r_remove_script_instance = true;
1805
// TODO: Last usage of 'is_finalizing_scripts_domain'. It should be replaced with a check to determine if the load context is being unloaded.
1806
} else if (!GDMono::get_singleton()->is_finalizing_scripts_domain()) {
1807
// If the native instance is still alive and this is called from the finalizer,
1808
// then it was referenced from another thread before the finalizer could
1809
// unreference and delete it, so we want to keep it.
1810
// GC.ReRegisterForFinalize(this) is not safe because the objects referenced by 'this'
1811
// could have already been collected. Instead we will create a new managed instance here.
1812
if (!_internal_new_managed()) {
1813
r_remove_script_instance = true;
1814
}
1815
}
1816
}
1817
}
1818
1819
void CSharpInstance::connect_event_signals() {
1820
const CSharpScript *top = script.ptr();
1821
while (top != nullptr && top->valid) {
1822
for (const CSharpScript::EventSignalInfo &signal : top->event_signals) {
1823
String signal_name = signal.name;
1824
1825
// TODO: Use pooling for ManagedCallable instances.
1826
EventSignalCallable *event_signal_callable = memnew(EventSignalCallable(owner, signal_name));
1827
1828
Callable callable(event_signal_callable);
1829
connected_event_signals.push_back(callable);
1830
owner->connect(signal_name, callable);
1831
}
1832
top = top->base_script.ptr();
1833
}
1834
}
1835
1836
void CSharpInstance::disconnect_event_signals() {
1837
for (const Callable &callable : connected_event_signals) {
1838
const EventSignalCallable *event_signal_callable = static_cast<const EventSignalCallable *>(callable.get_custom());
1839
owner->disconnect(event_signal_callable->get_signal(), callable);
1840
}
1841
1842
connected_event_signals.clear();
1843
}
1844
1845
void CSharpInstance::refcount_incremented() {
1846
#ifdef DEBUG_ENABLED
1847
CRASH_COND(!base_ref_counted);
1848
CRASH_COND(owner == nullptr);
1849
#endif // DEBUG_ENABLED
1850
1851
RefCounted *rc_owner = Object::cast_to<RefCounted>(owner);
1852
1853
if (rc_owner->get_reference_count() > 1 && gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0
1854
// The reference count was increased after the managed side was the only one referencing our owner.
1855
// This means the owner is being referenced again by the unmanaged side,
1856
// so the owner must hold the managed side alive again to avoid it from being GCed.
1857
1858
// Release the current weak handle and replace it with a strong handle.
1859
1860
GCHandleIntPtr old_gchandle = gchandle.get_intptr();
1861
gchandle.handle = { nullptr }; // No longer owns the handle (released by swap function)
1862
1863
GCHandleIntPtr new_gchandle = { nullptr };
1864
bool create_weak = false;
1865
bool target_alive = GDMonoCache::managed_callbacks.ScriptManagerBridge_SwapGCHandleForType(
1866
old_gchandle, &new_gchandle, create_weak);
1867
1868
if (!target_alive) {
1869
return; // Called after the managed side was collected, so nothing to do here
1870
}
1871
1872
gchandle = MonoGCHandleData(new_gchandle, gdmono::GCHandleType::STRONG_HANDLE);
1873
}
1874
}
1875
1876
bool CSharpInstance::refcount_decremented() {
1877
#ifdef DEBUG_ENABLED
1878
CRASH_COND(!base_ref_counted);
1879
CRASH_COND(owner == nullptr);
1880
#endif // DEBUG_ENABLED
1881
1882
RefCounted *rc_owner = Object::cast_to<RefCounted>(owner);
1883
1884
int refcount = rc_owner->get_reference_count();
1885
1886
if (refcount == 1 && !gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0
1887
// If owner owner is no longer referenced by the unmanaged side,
1888
// the managed instance takes responsibility of deleting the owner when GCed.
1889
1890
// Release the current strong handle and replace it with a weak handle.
1891
1892
GCHandleIntPtr old_gchandle = gchandle.get_intptr();
1893
gchandle.handle = { nullptr }; // No longer owns the handle (released by swap function)
1894
1895
GCHandleIntPtr new_gchandle = { nullptr };
1896
bool create_weak = true;
1897
bool target_alive = GDMonoCache::managed_callbacks.ScriptManagerBridge_SwapGCHandleForType(
1898
old_gchandle, &new_gchandle, create_weak);
1899
1900
if (!target_alive) {
1901
return refcount == 0; // Called after the managed side was collected, so nothing to do here
1902
}
1903
1904
gchandle = MonoGCHandleData(new_gchandle, gdmono::GCHandleType::WEAK_HANDLE);
1905
1906
return false;
1907
}
1908
1909
ref_dying = (refcount == 0);
1910
1911
return ref_dying;
1912
}
1913
1914
const Variant CSharpInstance::get_rpc_config() const {
1915
return script->get_rpc_config();
1916
}
1917
1918
void CSharpInstance::notification(int p_notification, bool p_reversed) {
1919
if (p_notification == Object::NOTIFICATION_PREDELETE) {
1920
if (base_ref_counted) {
1921
// At this point, Dispose() was already called (manually or from the finalizer).
1922
// The RefCounted wouldn't have reached 0 otherwise, since the managed side
1923
// references it and Dispose() needs to be called to release it.
1924
// However, this means C# RefCounted scripts can't receive NOTIFICATION_PREDELETE, but
1925
// this is likely the case with GDScript as well: https://github.com/godotengine/godot/issues/6784
1926
return;
1927
}
1928
} else if (p_notification == Object::NOTIFICATION_PREDELETE_CLEANUP) {
1929
// When NOTIFICATION_PREDELETE_CLEANUP is sent, we also take the chance to call Dispose().
1930
// It's safe to call Dispose() multiple times and NOTIFICATION_PREDELETE_CLEANUP is guaranteed
1931
// to be sent at least once, which happens right before the call to the destructor.
1932
1933
predelete_notified = true;
1934
1935
if (base_ref_counted) {
1936
// At this point, Dispose() was already called (manually or from the finalizer).
1937
// The RefCounted wouldn't have reached 0 otherwise, since the managed side
1938
// references it and Dispose() needs to be called to release it.
1939
return;
1940
}
1941
1942
// NOTIFICATION_PREDELETE_CLEANUP is not sent to scripts.
1943
// After calling Dispose() the C# instance can no longer be used,
1944
// so it should be the last thing we do.
1945
GDMonoCache::managed_callbacks.CSharpInstanceBridge_CallDispose(
1946
gchandle.get_intptr(), /* okIfNull */ false);
1947
1948
return;
1949
}
1950
1951
_call_notification(p_notification, p_reversed);
1952
}
1953
1954
void CSharpInstance::_call_notification(int p_notification, bool p_reversed) {
1955
Variant arg = p_notification;
1956
const Variant *args[1] = { &arg };
1957
1958
Variant ret;
1959
Callable::CallError call_error;
1960
GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call(
1961
gchandle.get_intptr(), &SNAME("_notification"), args, 1, &call_error, &ret);
1962
}
1963
1964
String CSharpInstance::to_string(bool *r_valid) {
1965
String res;
1966
bool valid;
1967
1968
GDMonoCache::managed_callbacks.CSharpInstanceBridge_CallToString(
1969
gchandle.get_intptr(), &res, &valid);
1970
1971
if (r_valid) {
1972
*r_valid = valid;
1973
}
1974
1975
return res;
1976
}
1977
1978
Ref<Script> CSharpInstance::get_script() const {
1979
return script;
1980
}
1981
1982
ScriptLanguage *CSharpInstance::get_language() {
1983
return CSharpLanguage::get_singleton();
1984
}
1985
1986
CSharpInstance::CSharpInstance(const Ref<CSharpScript> &p_script) :
1987
script(p_script) {
1988
}
1989
1990
CSharpInstance::~CSharpInstance() {
1991
destructing_script_instance = true;
1992
1993
// Must make sure event signals are not left dangling
1994
disconnect_event_signals();
1995
1996
if (!gchandle.is_released()) {
1997
if (!predelete_notified && !ref_dying) {
1998
// This destructor is not called from the owners destructor.
1999
// This could be being called from the owner's set_script_instance method,
2000
// meaning this script is being replaced with another one. If this is the case,
2001
// we must call Dispose here, because Dispose calls owner->set_script_instance(nullptr)
2002
// and that would mess up with the new script instance if called later.
2003
2004
GDMonoCache::managed_callbacks.CSharpInstanceBridge_CallDispose(
2005
gchandle.get_intptr(), /* okIfNull */ true);
2006
}
2007
2008
gchandle.release(); // Make sure the gchandle is released
2009
}
2010
2011
// If not being called from the owner's destructor, and we still hold a reference to the owner
2012
if (base_ref_counted && !ref_dying && owner && unsafe_referenced) {
2013
// The owner's script or script instance is being replaced (or removed)
2014
2015
// Transfer ownership to an "instance binding"
2016
2017
RefCounted *rc_owner = static_cast<RefCounted *>(owner);
2018
2019
// We will unreference the owner before referencing it again, so we need to keep it alive
2020
Ref<RefCounted> scope_keep_owner_alive(rc_owner);
2021
(void)scope_keep_owner_alive;
2022
2023
// Unreference the owner here, before the new "instance binding" references it.
2024
// Otherwise, the unsafe reference debug checks will incorrectly detect a bug.
2025
bool die = _unreference_owner_unsafe();
2026
CRASH_COND(die); // `owner_keep_alive` holds a reference, so it can't die
2027
2028
void *data = CSharpLanguage::get_instance_binding_with_setup(owner);
2029
CRASH_COND(data == nullptr);
2030
CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->get();
2031
CRASH_COND(!script_binding.inited);
2032
2033
#ifdef DEBUG_ENABLED
2034
// The "instance binding" holds a reference so the refcount should be at least 2 before `scope_keep_owner_alive` goes out of scope
2035
CRASH_COND(rc_owner->get_reference_count() <= 1);
2036
#endif // DEBUG_ENABLED
2037
}
2038
2039
if (script.is_valid() && owner) {
2040
MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex);
2041
2042
#ifdef DEBUG_ENABLED
2043
// CSharpInstance must not be created unless it's going to be added to the list for sure
2044
HashSet<Object *>::Iterator match = script->instances.find(owner);
2045
CRASH_COND(!match);
2046
script->instances.remove(match);
2047
#else
2048
script->instances.erase(owner);
2049
#endif // DEBUG_ENABLED
2050
}
2051
}
2052
2053
#ifdef TOOLS_ENABLED
2054
void CSharpScript::_placeholder_erased(PlaceHolderScriptInstance *p_placeholder) {
2055
placeholders.erase(p_placeholder);
2056
}
2057
#endif
2058
2059
#ifdef TOOLS_ENABLED
2060
void CSharpScript::_update_exports_values(HashMap<StringName, Variant> &values, List<PropertyInfo> &propnames) {
2061
for (const KeyValue<StringName, Variant> &E : exported_members_defval_cache) {
2062
values[E.key] = E.value;
2063
}
2064
2065
for (const PropertyInfo &prop_info : exported_members_cache) {
2066
propnames.push_back(prop_info);
2067
}
2068
2069
if (base_script.is_valid()) {
2070
base_script->_update_exports_values(values, propnames);
2071
}
2072
}
2073
#endif
2074
2075
void GD_CLR_STDCALL CSharpScript::_add_property_info_list_callback(CSharpScript *p_script, const String *p_current_class_name, void *p_props, int32_t p_count) {
2076
GDMonoCache::godotsharp_property_info *props = (GDMonoCache::godotsharp_property_info *)p_props;
2077
2078
#ifdef TOOLS_ENABLED
2079
p_script->exported_members_cache.push_back(PropertyInfo(
2080
Variant::NIL, p_script->type_info.class_name, PROPERTY_HINT_NONE,
2081
p_script->get_path(), PROPERTY_USAGE_CATEGORY));
2082
#endif
2083
2084
for (int i = 0; i < p_count; i++) {
2085
const GDMonoCache::godotsharp_property_info &prop = props[i];
2086
2087
StringName name = *reinterpret_cast<const StringName *>(&prop.name);
2088
String hint_string = *reinterpret_cast<const String *>(&prop.hint_string);
2089
2090
PropertyInfo pinfo(prop.type, name, prop.hint, hint_string, prop.usage);
2091
2092
p_script->member_info[name] = pinfo;
2093
2094
if (prop.exported) {
2095
#ifdef TOOLS_ENABLED
2096
p_script->exported_members_cache.push_back(pinfo);
2097
#endif
2098
2099
#if defined(TOOLS_ENABLED) || defined(DEBUG_ENABLED)
2100
p_script->exported_members_names.insert(name);
2101
#endif // DEBUG_ENABLED
2102
}
2103
}
2104
}
2105
2106
#ifdef TOOLS_ENABLED
2107
void GD_CLR_STDCALL CSharpScript::_add_property_default_values_callback(CSharpScript *p_script, void *p_def_vals, int32_t p_count) {
2108
GDMonoCache::godotsharp_property_def_val_pair *def_vals = (GDMonoCache::godotsharp_property_def_val_pair *)p_def_vals;
2109
2110
for (int i = 0; i < p_count; i++) {
2111
const GDMonoCache::godotsharp_property_def_val_pair &def_val_pair = def_vals[i];
2112
2113
StringName name = *reinterpret_cast<const StringName *>(&def_val_pair.name);
2114
Variant value = *reinterpret_cast<const Variant *>(&def_val_pair.value);
2115
2116
p_script->exported_members_defval_cache[name] = value;
2117
}
2118
}
2119
#endif
2120
2121
bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_update) {
2122
#ifdef TOOLS_ENABLED
2123
bool is_editor = Engine::get_singleton()->is_editor_hint();
2124
if (is_editor) {
2125
placeholder_fallback_enabled = true; // until proven otherwise
2126
}
2127
#endif
2128
if (!valid) {
2129
return false;
2130
}
2131
2132
bool changed = false;
2133
2134
#ifdef TOOLS_ENABLED
2135
if (exports_invalidated)
2136
#endif
2137
{
2138
#ifdef TOOLS_ENABLED
2139
exports_invalidated = false;
2140
#endif
2141
2142
changed = true;
2143
2144
member_info.clear();
2145
2146
#ifdef TOOLS_ENABLED
2147
exported_members_cache.clear();
2148
exported_members_defval_cache.clear();
2149
#endif
2150
2151
if (GDMonoCache::godot_api_cache_updated) {
2152
GDMonoCache::managed_callbacks.ScriptManagerBridge_GetPropertyInfoList(this, &_add_property_info_list_callback);
2153
2154
#ifdef TOOLS_ENABLED
2155
GDMonoCache::managed_callbacks.ScriptManagerBridge_GetPropertyDefaultValues(this, &_add_property_default_values_callback);
2156
#endif
2157
}
2158
}
2159
2160
#ifdef TOOLS_ENABLED
2161
if (is_editor) {
2162
placeholder_fallback_enabled = false;
2163
2164
if ((changed || p_instance_to_update) && placeholders.size()) {
2165
// Update placeholders if any
2166
HashMap<StringName, Variant> values;
2167
List<PropertyInfo> propnames;
2168
_update_exports_values(values, propnames);
2169
2170
if (changed) {
2171
for (PlaceHolderScriptInstance *instance : placeholders) {
2172
instance->update(propnames, values);
2173
}
2174
} else {
2175
p_instance_to_update->update(propnames, values);
2176
}
2177
} else if (placeholders.size()) {
2178
uint64_t script_modified_time = FileAccess::get_modified_time(get_path());
2179
uint64_t last_valid_build_time = GDMono::get_singleton()->get_project_assembly_modified_time();
2180
if (script_modified_time > last_valid_build_time) {
2181
for (PlaceHolderScriptInstance *instance : placeholders) {
2182
Object *owner = instance->get_owner();
2183
if (owner->get_script_instance() == instance) {
2184
owner->notify_property_list_changed();
2185
}
2186
}
2187
}
2188
}
2189
}
2190
#endif
2191
2192
return changed;
2193
}
2194
2195
bool CSharpScript::_get(const StringName &p_name, Variant &r_ret) const {
2196
if (p_name == SNAME("script/source")) {
2197
r_ret = get_source_code();
2198
return true;
2199
}
2200
2201
return false;
2202
}
2203
2204
bool CSharpScript::_set(const StringName &p_name, const Variant &p_value) {
2205
if (p_name == SNAME("script/source")) {
2206
set_source_code(p_value);
2207
reload();
2208
return true;
2209
}
2210
2211
return false;
2212
}
2213
2214
void CSharpScript::_get_property_list(List<PropertyInfo> *p_properties) const {
2215
p_properties->push_back(PropertyInfo(Variant::STRING, SNAME("script/source"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
2216
}
2217
2218
void CSharpScript::_bind_methods() {
2219
ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &CSharpScript::_new, MethodInfo("new"));
2220
}
2221
2222
void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) {
2223
// IMPORTANT:
2224
// This method must be called only after the CSharpScript and its associated type
2225
// have been added to the script bridge map in the ScriptManagerBridge C# class.
2226
// Other than that, it's the same as `CSharpScript::reload`.
2227
2228
// This method should not fail, only assertions allowed.
2229
2230
// Unlike `reload`, we print an error rather than silently returning,
2231
// as we can assert this won't be called a second time until invalidated.
2232
ERR_FAIL_COND(!p_script->reload_invalidated);
2233
2234
p_script->valid = true;
2235
p_script->reload_invalidated = false;
2236
2237
update_script_class_info(p_script);
2238
2239
p_script->_update_exports();
2240
2241
#ifdef TOOLS_ENABLED
2242
// If the EditorFileSystem singleton is available, update the file;
2243
// otherwise, the file will be updated when the singleton becomes available.
2244
EditorFileSystem *efs = EditorFileSystem::get_singleton();
2245
if (efs && !p_script->get_path().is_empty()) {
2246
efs->update_file(p_script->get_path());
2247
}
2248
#endif
2249
}
2250
2251
// Extract information about the script using the mono class.
2252
void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
2253
TypeInfo type_info;
2254
2255
// TODO: Use GDExtension godot_dictionary
2256
Array methods_array;
2257
methods_array.~Array();
2258
Dictionary rpc_functions_dict;
2259
rpc_functions_dict.~Dictionary();
2260
Dictionary signals_dict;
2261
signals_dict.~Dictionary();
2262
2263
Ref<CSharpScript> base_script;
2264
GDMonoCache::managed_callbacks.ScriptManagerBridge_UpdateScriptClassInfo(
2265
p_script.ptr(), &type_info,
2266
&methods_array, &rpc_functions_dict, &signals_dict, &base_script);
2267
2268
p_script->type_info = type_info;
2269
2270
p_script->rpc_config.clear();
2271
p_script->rpc_config = rpc_functions_dict;
2272
2273
// Methods
2274
2275
p_script->methods.clear();
2276
2277
p_script->methods.resize(methods_array.size());
2278
int push_index = 0;
2279
2280
for (int i = 0; i < methods_array.size(); i++) {
2281
Dictionary method_info_dict = methods_array[i];
2282
2283
StringName name = method_info_dict["name"];
2284
2285
MethodInfo mi;
2286
mi.name = name;
2287
2288
mi.return_val = PropertyInfo::from_dict(method_info_dict["return_val"]);
2289
2290
Array params = method_info_dict["params"];
2291
2292
for (int j = 0; j < params.size(); j++) {
2293
Dictionary param = params[j];
2294
2295
Variant::Type param_type = (Variant::Type)(int)param["type"];
2296
PropertyInfo arg_info = PropertyInfo(param_type, (String)param["name"]);
2297
arg_info.usage = (uint32_t)param["usage"];
2298
if (param.has("class_name")) {
2299
arg_info.class_name = (StringName)param["class_name"];
2300
}
2301
mi.arguments.push_back(arg_info);
2302
}
2303
2304
mi.flags = (uint32_t)method_info_dict["flags"];
2305
2306
p_script->methods.set(push_index++, CSharpMethodInfo{ name, mi });
2307
}
2308
2309
// Event signals
2310
2311
// Performance is not critical here as this will be replaced with source generators.
2312
2313
p_script->event_signals.clear();
2314
2315
// Sigh... can't we just have capacity?
2316
p_script->event_signals.resize(signals_dict.size());
2317
push_index = 0;
2318
2319
for (const Variant *s = signals_dict.next(nullptr); s != nullptr; s = signals_dict.next(s)) {
2320
StringName name = *s;
2321
2322
MethodInfo mi;
2323
mi.name = name;
2324
2325
Array params = signals_dict[*s];
2326
2327
for (int i = 0; i < params.size(); i++) {
2328
Dictionary param = params[i];
2329
2330
Variant::Type param_type = (Variant::Type)(int)param["type"];
2331
PropertyInfo arg_info = PropertyInfo(param_type, (String)param["name"]);
2332
arg_info.usage = (uint32_t)param["usage"];
2333
if (param.has("class_name")) {
2334
arg_info.class_name = (StringName)param["class_name"];
2335
}
2336
mi.arguments.push_back(arg_info);
2337
}
2338
2339
p_script->event_signals.set(push_index++, EventSignalInfo{ name, mi });
2340
}
2341
2342
p_script->base_script = base_script;
2343
}
2344
2345
bool CSharpScript::can_instantiate() const {
2346
#ifdef TOOLS_ENABLED
2347
bool extra_cond = (type_info.is_tool || ScriptServer::is_scripting_enabled()) && !Engine::get_singleton()->is_recovery_mode_hint();
2348
#else
2349
bool extra_cond = true;
2350
#endif
2351
2352
// FIXME Need to think this through better.
2353
// For tool scripts, this will never fire if the class is not found. That's because we
2354
// don't know if it's a tool script if we can't find the class to access the attributes.
2355
if (extra_cond && !valid) {
2356
ERR_FAIL_V_MSG(false, "Cannot instantiate C# 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).");
2357
}
2358
2359
return valid && type_info.can_instantiate() && extra_cond;
2360
}
2361
2362
StringName CSharpScript::get_instance_base_type() const {
2363
return type_info.native_base_name;
2364
}
2365
2366
CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error) {
2367
ERR_FAIL_COND_V_MSG(!type_info.can_instantiate(), nullptr, "Cannot instantiate C# script. Script: '" + get_path() + "'.");
2368
2369
/* STEP 1, CREATE */
2370
2371
Ref<RefCounted> ref;
2372
if (p_is_ref_counted) {
2373
// Hold it alive. Important if we have to dispose a script instance binding before creating the CSharpInstance.
2374
ref = Ref<RefCounted>(static_cast<RefCounted *>(p_owner));
2375
}
2376
2377
// If the object had a script instance binding, dispose it before adding the CSharpInstance
2378
if (CSharpLanguage::has_instance_binding(p_owner)) {
2379
void *data = CSharpLanguage::get_existing_instance_binding(p_owner);
2380
CRASH_COND(data == nullptr);
2381
2382
CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->get();
2383
if (script_binding.inited && !script_binding.gchandle.is_released()) {
2384
GDMonoCache::managed_callbacks.CSharpInstanceBridge_CallDispose(
2385
script_binding.gchandle.get_intptr(), /* okIfNull */ true);
2386
2387
script_binding.gchandle.release(); // Just in case
2388
script_binding.inited = false;
2389
}
2390
}
2391
2392
CSharpInstance *instance = memnew(CSharpInstance(Ref<CSharpScript>(this)));
2393
instance->base_ref_counted = p_is_ref_counted;
2394
instance->owner = p_owner;
2395
instance->owner->set_script_instance(instance);
2396
2397
/* STEP 2, INITIALIZE AND CONSTRUCT */
2398
2399
bool ok = GDMonoCache::managed_callbacks.ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance(
2400
this, p_owner, p_args, p_argcount);
2401
2402
if (!ok) {
2403
// Important to clear this before destroying the script instance here
2404
instance->script = Ref<CSharpScript>();
2405
p_owner->set_script_instance(nullptr);
2406
instance->owner = nullptr;
2407
2408
return nullptr;
2409
}
2410
2411
CRASH_COND(instance->gchandle.is_released());
2412
2413
/* STEP 3, PARTY */
2414
2415
//@TODO make thread safe
2416
return instance;
2417
}
2418
2419
Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
2420
if (!valid) {
2421
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
2422
return Variant();
2423
}
2424
2425
r_error.error = Callable::CallError::CALL_OK;
2426
2427
StringName native_name;
2428
GDMonoCache::managed_callbacks.ScriptManagerBridge_GetScriptNativeName(this, &native_name);
2429
2430
ERR_FAIL_COND_V(native_name == StringName(), Variant());
2431
2432
Object *owner = ClassDB::instantiate(native_name);
2433
2434
Ref<RefCounted> ref;
2435
RefCounted *r = Object::cast_to<RefCounted>(owner);
2436
if (r) {
2437
ref = Ref<RefCounted>(r);
2438
}
2439
2440
CSharpInstance *instance = _create_instance(p_args, p_argcount, owner, r != nullptr, r_error);
2441
if (!instance) {
2442
if (ref.is_null()) {
2443
memdelete(owner); // no owner, sorry
2444
}
2445
return Variant();
2446
}
2447
2448
if (ref.is_valid()) {
2449
return ref;
2450
} else {
2451
return owner;
2452
}
2453
}
2454
2455
ScriptInstance *CSharpScript::instance_create(Object *p_this) {
2456
#ifdef DEBUG_ENABLED
2457
CRASH_COND(!valid);
2458
#endif // DEBUG_ENABLED
2459
2460
StringName native_name;
2461
GDMonoCache::managed_callbacks.ScriptManagerBridge_GetScriptNativeName(this, &native_name);
2462
2463
ERR_FAIL_COND_V(native_name == StringName(), nullptr);
2464
2465
if (!ClassDB::is_parent_class(p_this->get_class_name(), native_name)) {
2466
if (EngineDebugger::is_active()) {
2467
CSharpLanguage::get_singleton()->debug_break_parse(get_path(), 0,
2468
"Script inherits from native type '" + String(native_name) +
2469
"', so it can't be assigned to an object of type: '" + p_this->get_class() + "'");
2470
}
2471
ERR_FAIL_V_MSG(nullptr, "Script inherits from native type '" + String(native_name) + "', so it can't be assigned to an object of type: '" + p_this->get_class() + "'.");
2472
}
2473
2474
Callable::CallError unchecked_error;
2475
return _create_instance(nullptr, 0, p_this, Object::cast_to<RefCounted>(p_this) != nullptr, unchecked_error);
2476
}
2477
2478
PlaceHolderScriptInstance *CSharpScript::placeholder_instance_create(Object *p_this) {
2479
#ifdef TOOLS_ENABLED
2480
PlaceHolderScriptInstance *si = memnew(PlaceHolderScriptInstance(CSharpLanguage::get_singleton(), Ref<Script>(this), p_this));
2481
placeholders.insert(si);
2482
_update_exports(si);
2483
return si;
2484
#else
2485
return nullptr;
2486
#endif
2487
}
2488
2489
bool CSharpScript::instance_has(const Object *p_this) const {
2490
MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex);
2491
return instances.has((Object *)p_this);
2492
}
2493
2494
bool CSharpScript::has_source_code() const {
2495
return !source.is_empty();
2496
}
2497
2498
String CSharpScript::get_source_code() const {
2499
return source;
2500
}
2501
2502
void CSharpScript::set_source_code(const String &p_code) {
2503
if (source == p_code) {
2504
return;
2505
}
2506
source = p_code;
2507
#ifdef TOOLS_ENABLED
2508
source_changed_cache = true;
2509
#endif
2510
}
2511
2512
void CSharpScript::get_script_method_list(List<MethodInfo> *p_list) const {
2513
if (!valid) {
2514
return;
2515
}
2516
2517
const CSharpScript *top = this;
2518
while (top != nullptr) {
2519
for (const CSharpMethodInfo &E : top->methods) {
2520
p_list->push_back(E.method_info);
2521
}
2522
2523
top = top->base_script.ptr();
2524
}
2525
}
2526
2527
bool CSharpScript::has_method(const StringName &p_method) const {
2528
if (!valid) {
2529
return false;
2530
}
2531
2532
for (const CSharpMethodInfo &E : methods) {
2533
if (E.name == p_method) {
2534
return true;
2535
}
2536
}
2537
2538
return false;
2539
}
2540
2541
int CSharpScript::get_script_method_argument_count(const StringName &p_method, bool *r_is_valid) const {
2542
if (!valid) {
2543
if (r_is_valid) {
2544
*r_is_valid = false;
2545
}
2546
return 0;
2547
}
2548
2549
for (const CSharpMethodInfo &E : methods) {
2550
if (E.name == p_method) {
2551
if (r_is_valid) {
2552
*r_is_valid = true;
2553
}
2554
return E.method_info.arguments.size();
2555
}
2556
}
2557
2558
if (r_is_valid) {
2559
*r_is_valid = false;
2560
}
2561
return 0;
2562
}
2563
2564
MethodInfo CSharpScript::get_method_info(const StringName &p_method) const {
2565
if (!valid) {
2566
return MethodInfo();
2567
}
2568
2569
MethodInfo mi;
2570
for (const CSharpMethodInfo &E : methods) {
2571
if (E.name == p_method) {
2572
if (mi.name == p_method) {
2573
// We already found a method with the same name before so
2574
// that means this method has overloads, the best we can do
2575
// is return an empty MethodInfo.
2576
return MethodInfo();
2577
}
2578
mi = E.method_info;
2579
}
2580
}
2581
2582
return mi;
2583
}
2584
2585
Variant CSharpScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
2586
if (valid) {
2587
Variant ret;
2588
bool ok = GDMonoCache::managed_callbacks.ScriptManagerBridge_CallStatic(this, &p_method, p_args, p_argcount, &r_error, &ret);
2589
if (ok) {
2590
return ret;
2591
}
2592
}
2593
2594
return Script::callp(p_method, p_args, p_argcount, r_error);
2595
}
2596
2597
Error CSharpScript::reload(bool p_keep_state) {
2598
if (!reload_invalidated) {
2599
return OK;
2600
}
2601
2602
// In the case of C#, reload doesn't really do any script reloading.
2603
// That's done separately via domain reloading.
2604
reload_invalidated = false;
2605
2606
String script_path = get_path();
2607
2608
valid = GDMonoCache::managed_callbacks.ScriptManagerBridge_AddScriptBridge(this, &script_path);
2609
2610
if (valid) {
2611
#ifdef DEBUG_ENABLED
2612
print_verbose("Found class for script " + get_path());
2613
#endif // DEBUG_ENABLED
2614
2615
update_script_class_info(this);
2616
2617
_update_exports();
2618
2619
#ifdef TOOLS_ENABLED
2620
// If the EditorFileSystem singleton is available, update the file;
2621
// otherwise, the file will be updated when the singleton becomes available.
2622
EditorFileSystem *efs = EditorFileSystem::get_singleton();
2623
if (efs) {
2624
efs->update_file(script_path);
2625
}
2626
#endif
2627
}
2628
2629
return OK;
2630
}
2631
2632
ScriptLanguage *CSharpScript::get_language() const {
2633
return CSharpLanguage::get_singleton();
2634
}
2635
2636
bool CSharpScript::get_property_default_value(const StringName &p_property, Variant &r_value) const {
2637
#ifdef TOOLS_ENABLED
2638
2639
HashMap<StringName, Variant>::ConstIterator E = exported_members_defval_cache.find(p_property);
2640
if (E) {
2641
r_value = E->value;
2642
return true;
2643
}
2644
2645
if (base_script.is_valid()) {
2646
return base_script->get_property_default_value(p_property, r_value);
2647
}
2648
2649
#endif
2650
return false;
2651
}
2652
2653
void CSharpScript::update_exports() {
2654
#ifdef TOOLS_ENABLED
2655
_update_exports();
2656
#endif
2657
}
2658
2659
bool CSharpScript::has_script_signal(const StringName &p_signal) const {
2660
if (!valid) {
2661
return false;
2662
}
2663
2664
if (!GDMonoCache::godot_api_cache_updated) {
2665
return false;
2666
}
2667
2668
for (const EventSignalInfo &signal : event_signals) {
2669
if (signal.name == p_signal) {
2670
return true;
2671
}
2672
}
2673
2674
if (base_script.is_valid()) {
2675
return base_script->has_script_signal(p_signal);
2676
}
2677
2678
return false;
2679
}
2680
2681
void CSharpScript::_get_script_signal_list(List<MethodInfo> *r_signals, bool p_include_base) const {
2682
if (!valid) {
2683
return;
2684
}
2685
2686
for (const EventSignalInfo &signal : event_signals) {
2687
r_signals->push_back(signal.method_info);
2688
}
2689
2690
if (!p_include_base) {
2691
return;
2692
}
2693
2694
if (base_script.is_valid()) {
2695
base_script->get_script_signal_list(r_signals);
2696
}
2697
}
2698
2699
void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const {
2700
_get_script_signal_list(r_signals, true);
2701
}
2702
2703
bool CSharpScript::inherits_script(const Ref<Script> &p_script) const {
2704
Ref<CSharpScript> cs = p_script;
2705
if (cs.is_null()) {
2706
return false;
2707
}
2708
2709
if (!valid || !cs->valid) {
2710
return false;
2711
}
2712
2713
if (!GDMonoCache::godot_api_cache_updated) {
2714
return false;
2715
}
2716
2717
return GDMonoCache::managed_callbacks.ScriptManagerBridge_ScriptIsOrInherits(this, cs.ptr());
2718
}
2719
2720
Ref<Script> CSharpScript::get_base_script() const {
2721
return base_script;
2722
}
2723
2724
StringName CSharpScript::get_global_name() const {
2725
return type_info.is_global_class ? StringName(type_info.class_name) : StringName();
2726
}
2727
2728
void CSharpScript::get_script_property_list(List<PropertyInfo> *r_list) const {
2729
#ifdef TOOLS_ENABLED
2730
const CSharpScript *top = this;
2731
while (top != nullptr) {
2732
for (const PropertyInfo &E : top->exported_members_cache) {
2733
r_list->push_back(E);
2734
}
2735
2736
top = top->base_script.ptr();
2737
}
2738
#else
2739
const CSharpScript *top = this;
2740
while (top != nullptr) {
2741
List<PropertyInfo> props;
2742
2743
for (const KeyValue<StringName, PropertyInfo> &E : top->member_info) {
2744
props.push_front(E.value);
2745
}
2746
2747
for (const PropertyInfo &prop : props) {
2748
r_list->push_back(prop);
2749
}
2750
2751
top = top->base_script.ptr();
2752
}
2753
#endif
2754
}
2755
2756
int CSharpScript::get_member_line(const StringName &p_member) const {
2757
// TODO omnisharp
2758
return -1;
2759
}
2760
2761
const Variant CSharpScript::get_rpc_config() const {
2762
return rpc_config;
2763
}
2764
2765
Error CSharpScript::load_source_code(const String &p_path) {
2766
Error ferr = read_all_file_utf8(p_path, source);
2767
2768
ERR_FAIL_COND_V_MSG(ferr != OK, ferr,
2769
ferr == ERR_INVALID_DATA
2770
? "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded."
2771
" Please ensure that scripts are saved in valid UTF-8 unicode."
2772
: "Failed to read file: '" + p_path + "'.");
2773
2774
#ifdef TOOLS_ENABLED
2775
source_changed_cache = true;
2776
#endif
2777
2778
return OK;
2779
}
2780
2781
void CSharpScript::_clear() {
2782
type_info = TypeInfo();
2783
valid = false;
2784
reload_invalidated = true;
2785
}
2786
2787
CSharpScript::CSharpScript() {
2788
_clear();
2789
2790
#ifdef DEBUG_ENABLED
2791
{
2792
MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex);
2793
CSharpLanguage::get_singleton()->script_list.add(&script_list);
2794
}
2795
#endif // DEBUG_ENABLED
2796
}
2797
2798
CSharpScript::~CSharpScript() {
2799
#ifdef DEBUG_ENABLED
2800
{
2801
MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex);
2802
CSharpLanguage::get_singleton()->script_list.remove(&script_list);
2803
}
2804
#endif // DEBUG_ENABLED
2805
2806
if (GDMonoCache::godot_api_cache_updated) {
2807
GDMonoCache::managed_callbacks.ScriptManagerBridge_RemoveScriptBridge(this);
2808
}
2809
}
2810
2811
void CSharpScript::get_members(HashSet<StringName> *p_members) {
2812
#ifdef DEBUG_ENABLED
2813
if (p_members) {
2814
for (const StringName &member_name : exported_members_names) {
2815
p_members->insert(member_name);
2816
}
2817
}
2818
#endif // DEBUG_ENABLED
2819
}
2820
2821
/*************** RESOURCE ***************/
2822
2823
Ref<Resource> ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
2824
if (r_error) {
2825
*r_error = ERR_FILE_CANT_OPEN;
2826
}
2827
2828
// TODO ignore anything inside bin/ and obj/ in tools builds?
2829
2830
String real_path = p_path;
2831
if (p_path.begins_with("csharp://")) {
2832
// This is a virtual path used by generic types, extract the real path.
2833
real_path = "res://" + p_path.trim_prefix("csharp://");
2834
real_path = real_path.substr(0, real_path.rfind_char(':'));
2835
}
2836
2837
Ref<CSharpScript> scr;
2838
2839
if (GDMonoCache::godot_api_cache_updated) {
2840
GDMonoCache::managed_callbacks.ScriptManagerBridge_GetOrCreateScriptBridgeForPath(&p_path, &scr);
2841
ERR_FAIL_COND_V_MSG(scr.is_null(), Ref<Resource>(), "Could not create C# script '" + real_path + "'.");
2842
} else {
2843
scr.instantiate();
2844
}
2845
2846
#ifdef DEBUG_ENABLED
2847
Error err = scr->load_source_code(real_path);
2848
ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot load C# script file '" + real_path + "'.");
2849
#endif // DEBUG_ENABLED
2850
2851
// Only one instance of a C# script is allowed to exist.
2852
ERR_FAIL_COND_V_MSG(!scr->get_path().is_empty() && scr->get_path() != p_original_path, Ref<Resource>(),
2853
"The C# script path is different from the path it was registered in the C# dictionary.");
2854
2855
Ref<Resource> existing = ResourceCache::get_ref(p_path);
2856
switch (p_cache_mode) {
2857
case ResourceFormatLoader::CACHE_MODE_IGNORE:
2858
case ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP:
2859
break;
2860
case ResourceFormatLoader::CACHE_MODE_REUSE:
2861
if (existing.is_null()) {
2862
scr->set_path(p_original_path);
2863
} else {
2864
scr = existing;
2865
}
2866
break;
2867
case ResourceFormatLoader::CACHE_MODE_REPLACE:
2868
case ResourceFormatLoader::CACHE_MODE_REPLACE_DEEP:
2869
scr->set_path(p_original_path, true);
2870
break;
2871
}
2872
2873
scr->reload();
2874
2875
if (r_error) {
2876
*r_error = OK;
2877
}
2878
2879
return scr;
2880
}
2881
2882
void ResourceFormatLoaderCSharpScript::get_recognized_extensions(List<String> *p_extensions) const {
2883
p_extensions->push_back("cs");
2884
}
2885
2886
bool ResourceFormatLoaderCSharpScript::handles_type(const String &p_type) const {
2887
return p_type == "Script" || p_type == CSharpLanguage::get_singleton()->get_type();
2888
}
2889
2890
String ResourceFormatLoaderCSharpScript::get_resource_type(const String &p_path) const {
2891
return p_path.has_extension("cs") ? CSharpLanguage::get_singleton()->get_type() : "";
2892
}
2893
2894
Error ResourceFormatSaverCSharpScript::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) {
2895
Ref<CSharpScript> sqscr = p_resource;
2896
ERR_FAIL_COND_V(sqscr.is_null(), ERR_INVALID_PARAMETER);
2897
2898
String source = sqscr->get_source_code();
2899
2900
#ifdef TOOLS_ENABLED
2901
if (!FileAccess::exists(p_path)) {
2902
// The file does not yet exist, let's assume the user just created this script. In such
2903
// cases we need to check whether the solution and csproj were already created or not.
2904
if (!_create_project_solution_if_needed()) {
2905
ERR_PRINT("C# project could not be created; cannot add file: '" + p_path + "'.");
2906
}
2907
}
2908
#endif
2909
2910
{
2911
Error err;
2912
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE, &err);
2913
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save C# script file '" + p_path + "'.");
2914
2915
file->store_string(source);
2916
2917
if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) {
2918
return ERR_CANT_CREATE;
2919
}
2920
}
2921
2922
#ifdef TOOLS_ENABLED
2923
if (ScriptServer::is_reload_scripts_on_save_enabled()) {
2924
CSharpLanguage::get_singleton()->reload_tool_script(p_resource, false);
2925
}
2926
#endif
2927
2928
return OK;
2929
}
2930
2931
void ResourceFormatSaverCSharpScript::get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const {
2932
if (Object::cast_to<CSharpScript>(p_resource.ptr())) {
2933
p_extensions->push_back("cs");
2934
}
2935
}
2936
2937
bool ResourceFormatSaverCSharpScript::recognize(const Ref<Resource> &p_resource) const {
2938
return Object::cast_to<CSharpScript>(p_resource.ptr()) != nullptr;
2939
}
2940
2941