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