Skip to content

Commit b4bd384

Browse files
committed
Merge pull request #98545 from juanjp600/dotnet-generic-collections-set-typed
Fix generic arrays and dictionaries in .NET not calling `set_typed`
2 parents 09dd5e6 + 8ab27a7 commit b4bd384

File tree

11 files changed

+260
-26
lines changed

11 files changed

+260
-26
lines changed

modules/mono/csharp_script.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2344,9 +2344,7 @@ bool CSharpScript::can_instantiate() const {
23442344
}
23452345

23462346
StringName CSharpScript::get_instance_base_type() const {
2347-
StringName native_name;
2348-
GDMonoCache::managed_callbacks.ScriptManagerBridge_GetScriptNativeName(this, &native_name);
2349-
return native_name;
2347+
return type_info.native_base_name;
23502348
}
23512349

23522350
CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error) {

modules/mono/csharp_script.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ class CSharpScript : public Script {
6868
*/
6969
String class_name;
7070

71+
/**
72+
* Name of the native class this script derives from.
73+
*/
74+
StringName native_base_name;
75+
7176
/**
7277
* Path to the icon that will be used for this class by the editor.
7378
*/

modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,6 +1060,22 @@ private static godot_variant ToVariantFunc(in Array<T> godotArray) =>
10601060
private static Array<T> FromVariantFunc(in godot_variant variant) =>
10611061
VariantUtils.ConvertToArray<T>(variant);
10621062

1063+
private void SetTypedForUnderlyingArray()
1064+
{
1065+
Marshaling.GetTypedCollectionParameterInfo<T>(out var elemVariantType, out var elemClassName, out var elemScriptRef);
1066+
1067+
var self = (godot_array)NativeValue;
1068+
1069+
using (elemScriptRef)
1070+
{
1071+
NativeFuncs.godotsharp_array_set_typed(
1072+
ref self,
1073+
(uint)elemVariantType,
1074+
elemClassName,
1075+
elemScriptRef);
1076+
}
1077+
}
1078+
10631079
static unsafe Array()
10641080
{
10651081
VariantUtils.GenericConversion<Array<T>>.ToVariantCb = &ToVariantFunc;
@@ -1083,6 +1099,7 @@ internal ref godot_array.movable NativeValue
10831099
public Array()
10841100
{
10851101
_underlyingArray = new Array();
1102+
SetTypedForUnderlyingArray();
10861103
}
10871104

10881105
/// <summary>
@@ -1099,6 +1116,7 @@ public Array(IEnumerable<T> collection)
10991116
throw new ArgumentNullException(nameof(collection));
11001117

11011118
_underlyingArray = new Array();
1119+
SetTypedForUnderlyingArray();
11021120

11031121
foreach (T element in collection)
11041122
Add(element);
@@ -1118,6 +1136,7 @@ public Array(T[] array)
11181136
throw new ArgumentNullException(nameof(array));
11191137

11201138
_underlyingArray = new Array();
1139+
SetTypedForUnderlyingArray();
11211140

11221141
foreach (T element in array)
11231142
Add(element);

modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -182,18 +182,7 @@ internal static unsafe void GetScriptNativeName(IntPtr scriptPtr, godot_string_n
182182
return;
183183
}
184184

185-
var native = GodotObject.InternalGetClassNativeBase(scriptType);
186-
187-
var field = native.GetField("NativeName", BindingFlags.DeclaredOnly | BindingFlags.Static |
188-
BindingFlags.Public | BindingFlags.NonPublic);
189-
190-
if (field == null)
191-
{
192-
*outRes = default;
193-
return;
194-
}
195-
196-
var nativeName = (StringName?)field.GetValue(null);
185+
var nativeName = GodotObject.InternalGetClassNativeBaseName(scriptType);
197186

198187
if (nativeName == null)
199188
{
@@ -658,10 +647,14 @@ internal static godot_bool TryReloadRegisteredScriptWithClass(IntPtr scriptPtr)
658647

659648
private static unsafe void GetScriptTypeInfo(Type scriptType, godot_csharp_type_info* outTypeInfo)
660649
{
661-
Type native = GodotObject.InternalGetClassNativeBase(scriptType);
662-
663650
godot_string className = Marshaling.ConvertStringToNative(ReflectionUtils.ConstructTypeName(scriptType));
664651

652+
StringName? nativeBase = GodotObject.InternalGetClassNativeBaseName(scriptType);
653+
654+
godot_string_name nativeBaseName = nativeBase != null
655+
? NativeFuncs.godotsharp_string_name_new_copy((godot_string_name)nativeBase.NativeValue)
656+
: default;
657+
665658
bool isTool = scriptType.IsDefined(typeof(ToolAttribute), inherit: false);
666659

667660
// If the type is nested and the parent type is a tool script,
@@ -686,6 +679,7 @@ private static unsafe void GetScriptTypeInfo(Type scriptType, godot_csharp_type_
686679
godot_string iconPath = Marshaling.ConvertStringToNative(iconAttr?.Path);
687680

688681
outTypeInfo->ClassName = className;
682+
outTypeInfo->NativeBaseName = nativeBaseName;
689683
outTypeInfo->IconPath = iconPath;
690684
outTypeInfo->IsTool = isTool.ToGodotBool();
691685
outTypeInfo->IsGlobalClass = isGlobalClass.ToGodotBool();

modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,27 @@ private static godot_variant ToVariantFunc(in Dictionary<TKey, TValue> godotDict
496496
private static Dictionary<TKey, TValue> FromVariantFunc(in godot_variant variant) =>
497497
VariantUtils.ConvertToDictionary<TKey, TValue>(variant);
498498

499+
private void SetTypedForUnderlyingDictionary()
500+
{
501+
Marshaling.GetTypedCollectionParameterInfo<TKey>(out var keyVariantType, out var keyClassName, out var keyScriptRef);
502+
Marshaling.GetTypedCollectionParameterInfo<TValue>(out var valueVariantType, out var valueClassName, out var valueScriptRef);
503+
504+
var self = (godot_dictionary)NativeValue;
505+
506+
using (keyScriptRef)
507+
using (valueScriptRef)
508+
{
509+
NativeFuncs.godotsharp_dictionary_set_typed(
510+
ref self,
511+
(uint)keyVariantType,
512+
keyClassName,
513+
keyScriptRef,
514+
(uint)valueVariantType,
515+
valueClassName,
516+
valueScriptRef);
517+
}
518+
}
519+
499520
static unsafe Dictionary()
500521
{
501522
VariantUtils.GenericConversion<Dictionary<TKey, TValue>>.ToVariantCb = &ToVariantFunc;
@@ -519,6 +540,7 @@ internal ref godot_dictionary.movable NativeValue
519540
public Dictionary()
520541
{
521542
_underlyingDict = new Dictionary();
543+
SetTypedForUnderlyingDictionary();
522544
}
523545

524546
/// <summary>
@@ -535,6 +557,7 @@ public Dictionary(IDictionary<TKey, TValue> dictionary)
535557
throw new ArgumentNullException(nameof(dictionary));
536558

537559
_underlyingDict = new Dictionary();
560+
SetTypedForUnderlyingDictionary();
538561

539562
foreach (KeyValuePair<TKey, TValue> entry in dictionary)
540563
Add(entry.Key, entry.Value);

modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics;
4+
using System.Reflection;
35
using System.Runtime.InteropServices;
46
using Godot.Bridge;
57
using Godot.NativeInterop;
@@ -13,6 +15,8 @@ public partial class GodotObject : IDisposable
1315
private bool _disposed;
1416
private static readonly Type _cachedType = typeof(GodotObject);
1517

18+
private static readonly Dictionary<Type, StringName?> _nativeNames = new Dictionary<Type, StringName?>();
19+
1620
internal IntPtr NativePtr;
1721
private bool _memoryOwn;
1822

@@ -191,16 +195,56 @@ public SignalAwaiter ToSignal(GodotObject source, StringName signal)
191195
return new SignalAwaiter(source, signal, this);
192196
}
193197

198+
internal static bool IsNativeClass(Type t)
199+
{
200+
if (ReferenceEquals(t.Assembly, typeof(GodotObject).Assembly))
201+
{
202+
return true;
203+
}
204+
205+
if (ReflectionUtils.IsEditorHintCached)
206+
{
207+
return t.Assembly.GetName().Name == "GodotSharpEditor";
208+
}
209+
210+
return false;
211+
}
212+
194213
internal static Type InternalGetClassNativeBase(Type t)
195214
{
196-
var name = t.Assembly.GetName().Name;
215+
while (!IsNativeClass(t))
216+
{
217+
Debug.Assert(t.BaseType is not null, "Script types must derive from a native Godot type.");
218+
219+
t = t.BaseType;
220+
}
221+
222+
return t;
223+
}
224+
225+
internal static StringName? InternalGetClassNativeBaseName(Type t)
226+
{
227+
if (_nativeNames.TryGetValue(t, out var name))
228+
{
229+
return name;
230+
}
231+
232+
var baseType = InternalGetClassNativeBase(t);
233+
234+
if (_nativeNames.TryGetValue(baseType, out name))
235+
{
236+
return name;
237+
}
238+
239+
var field = baseType.GetField("NativeName",
240+
BindingFlags.DeclaredOnly | BindingFlags.Static |
241+
BindingFlags.Public | BindingFlags.NonPublic);
197242

198-
if (name == "GodotSharp" || name == "GodotSharpEditor")
199-
return t;
243+
name = field?.GetValue(null) as StringName;
200244

201-
Debug.Assert(t.BaseType is not null, "Script types must derive from a native Godot type.");
245+
_nativeNames[baseType] = name;
202246

203-
return InternalGetClassNativeBase(t.BaseType);
247+
return name;
204248
}
205249

206250
// ReSharper disable once VirtualMemberNeverOverridden.Global

modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ public Godot.Variant.Type Expected
109109
public ref struct godot_csharp_type_info
110110
{
111111
private godot_string _className;
112+
private godot_string_name _nativeBaseName;
112113
private godot_string _iconPath;
113114
private godot_bool _isTool;
114115
private godot_bool _isGlobalClass;
@@ -122,6 +123,12 @@ public godot_string ClassName
122123
set => _className = value;
123124
}
124125

126+
public godot_string_name NativeBaseName
127+
{
128+
readonly get => _nativeBaseName;
129+
set => _nativeBaseName = value;
130+
}
131+
125132
public godot_string IconPath
126133
{
127134
readonly get => _iconPath;

modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,44 @@ internal static Variant.Type ConvertManagedTypeToVariantType(Type type, out bool
199199
return Variant.Type.Nil;
200200
}
201201

202+
internal static void GetTypedCollectionParameterInfo<T>(
203+
out Variant.Type variantType,
204+
out godot_string_name className,
205+
out godot_ref script)
206+
{
207+
variantType = ConvertManagedTypeToVariantType(typeof(T), out _);
208+
209+
if (variantType != Variant.Type.Object)
210+
{
211+
className = default;
212+
script = default;
213+
return;
214+
}
215+
216+
godot_ref scriptRef = default;
217+
218+
if (!GodotObject.IsNativeClass(typeof(T)))
219+
{
220+
unsafe
221+
{
222+
Godot.Bridge.ScriptManagerBridge.GetOrLoadOrCreateScriptForType(typeof(T), &scriptRef);
223+
}
224+
225+
// Don't call GodotObject.InternalGetClassNativeBaseName here!
226+
// godot_dictionary_set_typed and godot_array_set_typed will call CSharpScript::get_instance_base_type
227+
// when a script is passed, because this is better for performance than using reflection to find the
228+
// native base type.
229+
className = default;
230+
}
231+
else
232+
{
233+
StringName? nativeBaseName = GodotObject.InternalGetClassNativeBaseName(typeof(T));
234+
className = nativeBaseName != null ? (godot_string_name)nativeBaseName.NativeValue : default;
235+
}
236+
237+
script = scriptRef;
238+
}
239+
202240
// String
203241

204242
public static unsafe godot_string ConvertStringToNative(string? p_mono_string)

modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,14 @@ public static partial void
402402

403403
public static partial void godotsharp_array_make_read_only(ref godot_array p_self);
404404

405+
public static partial void godotsharp_array_set_typed(
406+
ref godot_array p_self,
407+
uint p_elem_type,
408+
in godot_string_name p_elem_class_name,
409+
in godot_ref p_elem_script);
410+
411+
public static partial godot_bool godotsharp_array_is_typed(ref godot_array p_self);
412+
405413
public static partial void godotsharp_array_max(ref godot_array p_self, out godot_variant r_value);
406414

407415
public static partial void godotsharp_array_min(ref godot_array p_self, out godot_variant r_value);
@@ -463,6 +471,31 @@ public static partial godot_bool godotsharp_dictionary_remove_key(ref godot_dict
463471

464472
public static partial void godotsharp_dictionary_make_read_only(ref godot_dictionary p_self);
465473

474+
public static partial void godotsharp_dictionary_set_typed(
475+
ref godot_dictionary p_self,
476+
uint p_key_type,
477+
in godot_string_name p_key_class_name,
478+
in godot_ref p_key_script,
479+
uint p_value_type,
480+
in godot_string_name p_value_class_name,
481+
in godot_ref p_value_script);
482+
483+
public static partial godot_bool godotsharp_dictionary_is_typed_key(ref godot_dictionary p_self);
484+
485+
public static partial godot_bool godotsharp_dictionary_is_typed_value(ref godot_dictionary p_self);
486+
487+
public static partial uint godotsharp_dictionary_get_typed_key_builtin(ref godot_dictionary p_self);
488+
489+
public static partial uint godotsharp_dictionary_get_typed_value_builtin(ref godot_dictionary p_self);
490+
491+
public static partial void godotsharp_dictionary_get_typed_key_class_name(ref godot_dictionary p_self, out godot_string_name r_dest);
492+
493+
public static partial void godotsharp_dictionary_get_typed_value_class_name(ref godot_dictionary p_self, out godot_string_name r_dest);
494+
495+
public static partial void godotsharp_dictionary_get_typed_key_script(ref godot_dictionary p_self, out godot_variant r_dest);
496+
497+
public static partial void godotsharp_dictionary_get_typed_value_script(ref godot_dictionary p_self, out godot_variant r_dest);
498+
466499
public static partial void godotsharp_dictionary_to_string(ref godot_dictionary p_self, out godot_string r_str);
467500

468501
// StringExtensions

modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ internal class ReflectionUtils
1212
{
1313
private static readonly HashSet<Type>? _tupleTypeSet;
1414
private static readonly Dictionary<Type, string>? _builtinTypeNameDictionary;
15-
private static readonly bool _isEditorHintCached;
15+
internal static readonly bool IsEditorHintCached;
1616

1717
static ReflectionUtils()
1818
{
19-
_isEditorHintCached = Engine.IsEditorHint();
20-
if (!_isEditorHintCached)
19+
IsEditorHintCached = Engine.IsEditorHint();
20+
if (!IsEditorHintCached)
2121
{
2222
return;
2323
}
@@ -66,7 +66,7 @@ static ReflectionUtils()
6666

6767
public static string ConstructTypeName(Type type)
6868
{
69-
if (!_isEditorHintCached)
69+
if (!IsEditorHintCached)
7070
{
7171
return type.Name;
7272
}

0 commit comments

Comments
 (0)