Merge pull request #98545 from juanjp600/dotnet-generic-collections-set-typed

Fix generic arrays and dictionaries in .NET not calling `set_typed`
This commit is contained in:
Thaddeus Crews 2024-12-09 14:33:37 -06:00
commit b4bd38444b
No known key found for this signature in database
GPG Key ID: 62181B86FE9E5D84
11 changed files with 260 additions and 26 deletions

View File

@ -2344,9 +2344,7 @@ bool CSharpScript::can_instantiate() const {
}
StringName CSharpScript::get_instance_base_type() const {
StringName native_name;
GDMonoCache::managed_callbacks.ScriptManagerBridge_GetScriptNativeName(this, &native_name);
return native_name;
return type_info.native_base_name;
}
CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error) {

View File

@ -68,6 +68,11 @@ public:
*/
String class_name;
/**
* Name of the native class this script derives from.
*/
StringName native_base_name;
/**
* Path to the icon that will be used for this class by the editor.
*/

View File

@ -1060,6 +1060,22 @@ namespace Godot.Collections
private static Array<T> FromVariantFunc(in godot_variant variant) =>
VariantUtils.ConvertToArray<T>(variant);
private void SetTypedForUnderlyingArray()
{
Marshaling.GetTypedCollectionParameterInfo<T>(out var elemVariantType, out var elemClassName, out var elemScriptRef);
var self = (godot_array)NativeValue;
using (elemScriptRef)
{
NativeFuncs.godotsharp_array_set_typed(
ref self,
(uint)elemVariantType,
elemClassName,
elemScriptRef);
}
}
static unsafe Array()
{
VariantUtils.GenericConversion<Array<T>>.ToVariantCb = &ToVariantFunc;
@ -1083,6 +1099,7 @@ namespace Godot.Collections
public Array()
{
_underlyingArray = new Array();
SetTypedForUnderlyingArray();
}
/// <summary>
@ -1099,6 +1116,7 @@ namespace Godot.Collections
throw new ArgumentNullException(nameof(collection));
_underlyingArray = new Array();
SetTypedForUnderlyingArray();
foreach (T element in collection)
Add(element);
@ -1118,6 +1136,7 @@ namespace Godot.Collections
throw new ArgumentNullException(nameof(array));
_underlyingArray = new Array();
SetTypedForUnderlyingArray();
foreach (T element in array)
Add(element);

View File

@ -182,18 +182,7 @@ namespace Godot.Bridge
return;
}
var native = GodotObject.InternalGetClassNativeBase(scriptType);
var field = native.GetField("NativeName", BindingFlags.DeclaredOnly | BindingFlags.Static |
BindingFlags.Public | BindingFlags.NonPublic);
if (field == null)
{
*outRes = default;
return;
}
var nativeName = (StringName?)field.GetValue(null);
var nativeName = GodotObject.InternalGetClassNativeBaseName(scriptType);
if (nativeName == null)
{
@ -658,10 +647,14 @@ namespace Godot.Bridge
private static unsafe void GetScriptTypeInfo(Type scriptType, godot_csharp_type_info* outTypeInfo)
{
Type native = GodotObject.InternalGetClassNativeBase(scriptType);
godot_string className = Marshaling.ConvertStringToNative(ReflectionUtils.ConstructTypeName(scriptType));
StringName? nativeBase = GodotObject.InternalGetClassNativeBaseName(scriptType);
godot_string_name nativeBaseName = nativeBase != null
? NativeFuncs.godotsharp_string_name_new_copy((godot_string_name)nativeBase.NativeValue)
: default;
bool isTool = scriptType.IsDefined(typeof(ToolAttribute), inherit: false);
// If the type is nested and the parent type is a tool script,
@ -686,6 +679,7 @@ namespace Godot.Bridge
godot_string iconPath = Marshaling.ConvertStringToNative(iconAttr?.Path);
outTypeInfo->ClassName = className;
outTypeInfo->NativeBaseName = nativeBaseName;
outTypeInfo->IconPath = iconPath;
outTypeInfo->IsTool = isTool.ToGodotBool();
outTypeInfo->IsGlobalClass = isGlobalClass.ToGodotBool();

View File

@ -496,6 +496,27 @@ namespace Godot.Collections
private static Dictionary<TKey, TValue> FromVariantFunc(in godot_variant variant) =>
VariantUtils.ConvertToDictionary<TKey, TValue>(variant);
private void SetTypedForUnderlyingDictionary()
{
Marshaling.GetTypedCollectionParameterInfo<TKey>(out var keyVariantType, out var keyClassName, out var keyScriptRef);
Marshaling.GetTypedCollectionParameterInfo<TValue>(out var valueVariantType, out var valueClassName, out var valueScriptRef);
var self = (godot_dictionary)NativeValue;
using (keyScriptRef)
using (valueScriptRef)
{
NativeFuncs.godotsharp_dictionary_set_typed(
ref self,
(uint)keyVariantType,
keyClassName,
keyScriptRef,
(uint)valueVariantType,
valueClassName,
valueScriptRef);
}
}
static unsafe Dictionary()
{
VariantUtils.GenericConversion<Dictionary<TKey, TValue>>.ToVariantCb = &ToVariantFunc;
@ -519,6 +540,7 @@ namespace Godot.Collections
public Dictionary()
{
_underlyingDict = new Dictionary();
SetTypedForUnderlyingDictionary();
}
/// <summary>
@ -535,6 +557,7 @@ namespace Godot.Collections
throw new ArgumentNullException(nameof(dictionary));
_underlyingDict = new Dictionary();
SetTypedForUnderlyingDictionary();
foreach (KeyValuePair<TKey, TValue> entry in dictionary)
Add(entry.Key, entry.Value);

View File

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using Godot.Bridge;
using Godot.NativeInterop;
@ -13,6 +15,8 @@ namespace Godot
private bool _disposed;
private static readonly Type _cachedType = typeof(GodotObject);
private static readonly Dictionary<Type, StringName?> _nativeNames = new Dictionary<Type, StringName?>();
internal IntPtr NativePtr;
private bool _memoryOwn;
@ -191,16 +195,56 @@ namespace Godot
return new SignalAwaiter(source, signal, this);
}
internal static bool IsNativeClass(Type t)
{
if (ReferenceEquals(t.Assembly, typeof(GodotObject).Assembly))
{
return true;
}
if (ReflectionUtils.IsEditorHintCached)
{
return t.Assembly.GetName().Name == "GodotSharpEditor";
}
return false;
}
internal static Type InternalGetClassNativeBase(Type t)
{
var name = t.Assembly.GetName().Name;
while (!IsNativeClass(t))
{
Debug.Assert(t.BaseType is not null, "Script types must derive from a native Godot type.");
if (name == "GodotSharp" || name == "GodotSharpEditor")
return t;
t = t.BaseType;
}
Debug.Assert(t.BaseType is not null, "Script types must derive from a native Godot type.");
return t;
}
return InternalGetClassNativeBase(t.BaseType);
internal static StringName? InternalGetClassNativeBaseName(Type t)
{
if (_nativeNames.TryGetValue(t, out var name))
{
return name;
}
var baseType = InternalGetClassNativeBase(t);
if (_nativeNames.TryGetValue(baseType, out name))
{
return name;
}
var field = baseType.GetField("NativeName",
BindingFlags.DeclaredOnly | BindingFlags.Static |
BindingFlags.Public | BindingFlags.NonPublic);
name = field?.GetValue(null) as StringName;
_nativeNames[baseType] = name;
return name;
}
// ReSharper disable once VirtualMemberNeverOverridden.Global

View File

@ -109,6 +109,7 @@ namespace Godot.NativeInterop
public ref struct godot_csharp_type_info
{
private godot_string _className;
private godot_string_name _nativeBaseName;
private godot_string _iconPath;
private godot_bool _isTool;
private godot_bool _isGlobalClass;
@ -122,6 +123,12 @@ namespace Godot.NativeInterop
set => _className = value;
}
public godot_string_name NativeBaseName
{
readonly get => _nativeBaseName;
set => _nativeBaseName = value;
}
public godot_string IconPath
{
readonly get => _iconPath;

View File

@ -199,6 +199,44 @@ namespace Godot.NativeInterop
return Variant.Type.Nil;
}
internal static void GetTypedCollectionParameterInfo<T>(
out Variant.Type variantType,
out godot_string_name className,
out godot_ref script)
{
variantType = ConvertManagedTypeToVariantType(typeof(T), out _);
if (variantType != Variant.Type.Object)
{
className = default;
script = default;
return;
}
godot_ref scriptRef = default;
if (!GodotObject.IsNativeClass(typeof(T)))
{
unsafe
{
Godot.Bridge.ScriptManagerBridge.GetOrLoadOrCreateScriptForType(typeof(T), &scriptRef);
}
// Don't call GodotObject.InternalGetClassNativeBaseName here!
// godot_dictionary_set_typed and godot_array_set_typed will call CSharpScript::get_instance_base_type
// when a script is passed, because this is better for performance than using reflection to find the
// native base type.
className = default;
}
else
{
StringName? nativeBaseName = GodotObject.InternalGetClassNativeBaseName(typeof(T));
className = nativeBaseName != null ? (godot_string_name)nativeBaseName.NativeValue : default;
}
script = scriptRef;
}
// String
public static unsafe godot_string ConvertStringToNative(string? p_mono_string)

View File

@ -402,6 +402,14 @@ namespace Godot.NativeInterop
public static partial void godotsharp_array_make_read_only(ref godot_array p_self);
public static partial void godotsharp_array_set_typed(
ref godot_array p_self,
uint p_elem_type,
in godot_string_name p_elem_class_name,
in godot_ref p_elem_script);
public static partial godot_bool godotsharp_array_is_typed(ref godot_array p_self);
public static partial void godotsharp_array_max(ref godot_array p_self, out godot_variant r_value);
public static partial void godotsharp_array_min(ref godot_array p_self, out godot_variant r_value);
@ -463,6 +471,31 @@ namespace Godot.NativeInterop
public static partial void godotsharp_dictionary_make_read_only(ref godot_dictionary p_self);
public static partial void godotsharp_dictionary_set_typed(
ref godot_dictionary p_self,
uint p_key_type,
in godot_string_name p_key_class_name,
in godot_ref p_key_script,
uint p_value_type,
in godot_string_name p_value_class_name,
in godot_ref p_value_script);
public static partial godot_bool godotsharp_dictionary_is_typed_key(ref godot_dictionary p_self);
public static partial godot_bool godotsharp_dictionary_is_typed_value(ref godot_dictionary p_self);
public static partial uint godotsharp_dictionary_get_typed_key_builtin(ref godot_dictionary p_self);
public static partial uint godotsharp_dictionary_get_typed_value_builtin(ref godot_dictionary p_self);
public static partial void godotsharp_dictionary_get_typed_key_class_name(ref godot_dictionary p_self, out godot_string_name r_dest);
public static partial void godotsharp_dictionary_get_typed_value_class_name(ref godot_dictionary p_self, out godot_string_name r_dest);
public static partial void godotsharp_dictionary_get_typed_key_script(ref godot_dictionary p_self, out godot_variant r_dest);
public static partial void godotsharp_dictionary_get_typed_value_script(ref godot_dictionary p_self, out godot_variant r_dest);
public static partial void godotsharp_dictionary_to_string(ref godot_dictionary p_self, out godot_string r_str);
// StringExtensions

View File

@ -12,12 +12,12 @@ internal class ReflectionUtils
{
private static readonly HashSet<Type>? _tupleTypeSet;
private static readonly Dictionary<Type, string>? _builtinTypeNameDictionary;
private static readonly bool _isEditorHintCached;
internal static readonly bool IsEditorHintCached;
static ReflectionUtils()
{
_isEditorHintCached = Engine.IsEditorHint();
if (!_isEditorHintCached)
IsEditorHintCached = Engine.IsEditorHint();
if (!IsEditorHintCached)
{
return;
}
@ -66,7 +66,7 @@ internal class ReflectionUtils
public static string ConstructTypeName(Type type)
{
if (!_isEditorHintCached)
if (!IsEditorHintCached)
{
return type.Name;
}

View File

@ -1098,6 +1098,20 @@ void godotsharp_array_make_read_only(Array *p_self) {
p_self->make_read_only();
}
void godotsharp_array_set_typed(Array *p_self, uint32_t p_elem_type, const StringName *p_elem_class_name, const Ref<CSharpScript> *p_elem_script) {
Variant elem_script_variant;
StringName elem_class_name = *p_elem_class_name;
if (p_elem_script && p_elem_script->is_valid()) {
elem_script_variant = Variant(p_elem_script->ptr());
elem_class_name = p_elem_script->ptr()->get_instance_base_type();
}
p_self->set_typed(p_elem_type, elem_class_name, p_elem_script->ptr());
}
bool godotsharp_array_is_typed(const Array *p_self) {
return p_self->is_typed();
}
void godotsharp_array_max(const Array *p_self, Variant *r_value) {
*r_value = p_self->max();
}
@ -1207,6 +1221,54 @@ void godotsharp_dictionary_make_read_only(Dictionary *p_self) {
p_self->make_read_only();
}
void godotsharp_dictionary_set_typed(Dictionary *p_self, uint32_t p_key_type, const StringName *p_key_class_name, const Ref<CSharpScript> *p_key_script, uint32_t p_value_type, const StringName *p_value_class_name, const Ref<CSharpScript> *p_value_script) {
Variant key_script_variant;
StringName key_class_name = *p_key_class_name;
if (p_key_script && p_key_script->is_valid()) {
key_script_variant = Variant(p_key_script->ptr());
key_class_name = p_key_script->ptr()->get_instance_base_type();
}
Variant value_script_variant;
StringName value_class_name = *p_value_class_name;
if (p_value_script && p_value_script->is_valid()) {
value_script_variant = Variant(p_value_script->ptr());
value_class_name = p_value_script->ptr()->get_instance_base_type();
}
p_self->set_typed(p_key_type, key_class_name, p_key_script->ptr(), p_value_type, value_class_name, p_value_script->ptr());
}
bool godotsharp_dictionary_is_typed_key(const Dictionary *p_self) {
return p_self->is_typed_key();
}
bool godotsharp_dictionary_is_typed_value(const Dictionary *p_self) {
return p_self->is_typed_value();
}
uint32_t godotsharp_dictionary_get_typed_key_builtin(const Dictionary *p_self) {
return p_self->get_typed_key_builtin();
}
uint32_t godotsharp_dictionary_get_typed_value_builtin(const Dictionary *p_self) {
return p_self->get_typed_value_builtin();
}
void godotsharp_dictionary_get_typed_key_class_name(const Dictionary *p_self, StringName *r_dest) {
memnew_placement(r_dest, StringName(p_self->get_typed_key_class_name()));
}
void godotsharp_dictionary_get_typed_value_class_name(const Dictionary *p_self, StringName *r_dest) {
memnew_placement(r_dest, StringName(p_self->get_typed_value_class_name()));
}
void godotsharp_dictionary_get_typed_key_script(const Dictionary *p_self, Variant *r_dest) {
memnew_placement(r_dest, Variant(p_self->get_typed_key_script()));
}
void godotsharp_dictionary_get_typed_value_script(const Dictionary *p_self, Variant *r_dest) {
memnew_placement(r_dest, Variant(p_self->get_typed_value_script()));
}
void godotsharp_dictionary_to_string(const Dictionary *p_self, String *r_str) {
*r_str = Variant(*p_self).operator String();
}
@ -1585,6 +1647,8 @@ static const void *unmanaged_callbacks[]{
(void *)godotsharp_array_insert,
(void *)godotsharp_array_last_index_of,
(void *)godotsharp_array_make_read_only,
(void *)godotsharp_array_set_typed,
(void *)godotsharp_array_is_typed,
(void *)godotsharp_array_max,
(void *)godotsharp_array_min,
(void *)godotsharp_array_pick_random,
@ -1610,6 +1674,15 @@ static const void *unmanaged_callbacks[]{
(void *)godotsharp_dictionary_recursive_equal,
(void *)godotsharp_dictionary_remove_key,
(void *)godotsharp_dictionary_make_read_only,
(void *)godotsharp_dictionary_set_typed,
(void *)godotsharp_dictionary_is_typed_key,
(void *)godotsharp_dictionary_is_typed_value,
(void *)godotsharp_dictionary_get_typed_key_builtin,
(void *)godotsharp_dictionary_get_typed_value_builtin,
(void *)godotsharp_dictionary_get_typed_key_class_name,
(void *)godotsharp_dictionary_get_typed_value_class_name,
(void *)godotsharp_dictionary_get_typed_key_script,
(void *)godotsharp_dictionary_get_typed_value_script,
(void *)godotsharp_dictionary_to_string,
(void *)godotsharp_string_simplify_path,
(void *)godotsharp_string_to_camel_case,