|
@@ -11,6 +11,7 @@ using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
using System.Runtime.InteropServices;
|
|
using System.Runtime.Loader;
|
|
using System.Runtime.Loader;
|
|
using System.Runtime.Serialization;
|
|
using System.Runtime.Serialization;
|
|
|
|
+using System.Text;
|
|
using Godot.NativeInterop;
|
|
using Godot.NativeInterop;
|
|
|
|
|
|
namespace Godot.Bridge
|
|
namespace Godot.Bridge
|
|
@@ -29,7 +30,7 @@ namespace Godot.Bridge
|
|
foreach (var type in typesInAlc.Keys)
|
|
foreach (var type in typesInAlc.Keys)
|
|
{
|
|
{
|
|
if (_scriptTypeBiMap.RemoveByScriptType(type, out IntPtr scriptPtr) &&
|
|
if (_scriptTypeBiMap.RemoveByScriptType(type, out IntPtr scriptPtr) &&
|
|
- !_pathTypeBiMap.TryGetScriptPath(type, out _))
|
|
|
|
|
|
+ (!_pathTypeBiMap.TryGetScriptPath(type, out string? scriptPath) || scriptPath.StartsWith("csharp://")))
|
|
{
|
|
{
|
|
// For scripts without a path, we need to keep the class qualified name for reloading
|
|
// For scripts without a path, we need to keep the class qualified name for reloading
|
|
_scriptDataForReload.TryAdd(scriptPtr,
|
|
_scriptDataForReload.TryAdd(scriptPtr,
|
|
@@ -220,6 +221,71 @@ namespace Godot.Bridge
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ [UnmanagedCallersOnly]
|
|
|
|
+ internal static unsafe void GetGlobalClassName(godot_string* scriptPath, godot_string* outBaseType, godot_string* outIconPath, godot_string* outClassName)
|
|
|
|
+ {
|
|
|
|
+ // This method must always return the outBaseType for every script, even if the script is
|
|
|
|
+ // not a global class. But if the script is not a global class it must return an empty
|
|
|
|
+ // outClassName string since it should not have a name.
|
|
|
|
+ string scriptPathStr = Marshaling.ConvertStringToManaged(*scriptPath);
|
|
|
|
+ Debug.Assert(!string.IsNullOrEmpty(scriptPathStr), "Script path can't be empty.");
|
|
|
|
+
|
|
|
|
+ if (!_pathTypeBiMap.TryGetScriptType(scriptPathStr, out Type? scriptType))
|
|
|
|
+ {
|
|
|
|
+ // Script at the given path does not exist, or it's not a C# type.
|
|
|
|
+ // This is fine, it may be a path to a generic script and those can't be global classes.
|
|
|
|
+ *outClassName = default;
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (outIconPath != null)
|
|
|
|
+ {
|
|
|
|
+ var iconAttr = scriptType.GetCustomAttributes(inherit: false)
|
|
|
|
+ .OfType<IconAttribute>()
|
|
|
|
+ .FirstOrDefault();
|
|
|
|
+
|
|
|
|
+ *outIconPath = Marshaling.ConvertStringToNative(iconAttr?.Path);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (outBaseType != null)
|
|
|
|
+ {
|
|
|
|
+ bool foundGlobalBaseScript = false;
|
|
|
|
+
|
|
|
|
+ Type native = GodotObject.InternalGetClassNativeBase(scriptType);
|
|
|
|
+ Type? top = scriptType.BaseType;
|
|
|
|
+
|
|
|
|
+ while (top != null && top != native)
|
|
|
|
+ {
|
|
|
|
+ if (IsGlobalClass(top))
|
|
|
|
+ {
|
|
|
|
+ *outBaseType = Marshaling.ConvertStringToNative(top.Name);
|
|
|
|
+ foundGlobalBaseScript = true;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ top = top.BaseType;
|
|
|
|
+ }
|
|
|
|
+ if (!foundGlobalBaseScript)
|
|
|
|
+ {
|
|
|
|
+ *outBaseType = Marshaling.ConvertStringToNative(native.Name);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!IsGlobalClass(scriptType))
|
|
|
|
+ {
|
|
|
|
+ // Scripts that are not global classes should not have a name.
|
|
|
|
+ // Return an empty string to prevent the class from being registered
|
|
|
|
+ // as a global class in the editor.
|
|
|
|
+ *outClassName = default;
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ *outClassName = Marshaling.ConvertStringToNative(scriptType.Name);
|
|
|
|
+
|
|
|
|
+ static bool IsGlobalClass(Type scriptType) =>
|
|
|
|
+ scriptType.IsDefined(typeof(GlobalClassAttribute), inherit: false);
|
|
|
|
+ }
|
|
|
|
+
|
|
[UnmanagedCallersOnly]
|
|
[UnmanagedCallersOnly]
|
|
internal static void SetGodotObjectPtr(IntPtr gcHandlePtr, IntPtr newPtr)
|
|
internal static void SetGodotObjectPtr(IntPtr gcHandlePtr, IntPtr newPtr)
|
|
{
|
|
{
|
|
@@ -333,7 +399,7 @@ namespace Godot.Bridge
|
|
|
|
|
|
foreach (var type in assembly.GetTypes())
|
|
foreach (var type in assembly.GetTypes())
|
|
{
|
|
{
|
|
- if (type.IsNested || type.IsGenericType)
|
|
|
|
|
|
+ if (type.IsNested)
|
|
continue;
|
|
continue;
|
|
|
|
|
|
if (!typeOfGodotObject.IsAssignableFrom(type))
|
|
if (!typeOfGodotObject.IsAssignableFrom(type))
|
|
@@ -352,9 +418,6 @@ namespace Godot.Bridge
|
|
{
|
|
{
|
|
foreach (var type in scriptTypes)
|
|
foreach (var type in scriptTypes)
|
|
{
|
|
{
|
|
- if (type.IsGenericType)
|
|
|
|
- continue;
|
|
|
|
-
|
|
|
|
LookupScriptForClass(type);
|
|
LookupScriptForClass(type);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -422,20 +485,8 @@ namespace Godot.Bridge
|
|
{
|
|
{
|
|
try
|
|
try
|
|
{
|
|
{
|
|
- lock (_scriptTypeBiMap.ReadWriteLock)
|
|
|
|
- {
|
|
|
|
- if (!_scriptTypeBiMap.IsScriptRegistered(scriptPtr))
|
|
|
|
- {
|
|
|
|
- string scriptPathStr = Marshaling.ConvertStringToManaged(*scriptPath);
|
|
|
|
-
|
|
|
|
- if (!_pathTypeBiMap.TryGetScriptType(scriptPathStr, out Type? scriptType))
|
|
|
|
- return godot_bool.False;
|
|
|
|
-
|
|
|
|
- _scriptTypeBiMap.Add(scriptPtr, scriptType);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return godot_bool.True;
|
|
|
|
|
|
+ string scriptPathStr = Marshaling.ConvertStringToManaged(*scriptPath);
|
|
|
|
+ return AddScriptBridgeCore(scriptPtr, scriptPathStr).ToGodotBool();
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
catch (Exception e)
|
|
{
|
|
{
|
|
@@ -444,6 +495,22 @@ namespace Godot.Bridge
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ private static unsafe bool AddScriptBridgeCore(IntPtr scriptPtr, string scriptPath)
|
|
|
|
+ {
|
|
|
|
+ lock (_scriptTypeBiMap.ReadWriteLock)
|
|
|
|
+ {
|
|
|
|
+ if (!_scriptTypeBiMap.IsScriptRegistered(scriptPtr))
|
|
|
|
+ {
|
|
|
|
+ if (!_pathTypeBiMap.TryGetScriptType(scriptPath, out Type? scriptType))
|
|
|
|
+ return false;
|
|
|
|
+
|
|
|
|
+ _scriptTypeBiMap.Add(scriptPtr, scriptType);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
[UnmanagedCallersOnly]
|
|
[UnmanagedCallersOnly]
|
|
internal static unsafe void GetOrCreateScriptBridgeForPath(godot_string* scriptPath, godot_ref* outScript)
|
|
internal static unsafe void GetOrCreateScriptBridgeForPath(godot_string* scriptPath, godot_ref* outScript)
|
|
{
|
|
{
|
|
@@ -455,6 +522,8 @@ namespace Godot.Bridge
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ Debug.Assert(!scriptType.IsGenericTypeDefinition, $"Cannot get or create script for a generic type definition '{scriptType.FullName}'. Path: '{scriptPathStr}'.");
|
|
|
|
+
|
|
GetOrCreateScriptBridgeForType(scriptType, outScript);
|
|
GetOrCreateScriptBridgeForType(scriptType, outScript);
|
|
}
|
|
}
|
|
|
|
|
|
@@ -494,16 +563,51 @@ namespace Godot.Bridge
|
|
if (_pathTypeBiMap.TryGetScriptPath(scriptType, out scriptPath))
|
|
if (_pathTypeBiMap.TryGetScriptPath(scriptType, out scriptPath))
|
|
return true;
|
|
return true;
|
|
|
|
|
|
|
|
+ if (scriptType.IsConstructedGenericType)
|
|
|
|
+ {
|
|
|
|
+ // If the script type is generic, also try looking for the path of the generic type definition
|
|
|
|
+ // since we can use it to create the script.
|
|
|
|
+ Type genericTypeDefinition = scriptType.GetGenericTypeDefinition();
|
|
|
|
+ if (_pathTypeBiMap.TryGetGenericTypeDefinitionPath(genericTypeDefinition, out scriptPath))
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
CreateScriptBridgeForType(scriptType, outScript);
|
|
CreateScriptBridgeForType(scriptType, outScript);
|
|
scriptPath = null;
|
|
scriptPath = null;
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ static string GetVirtualConstructedGenericTypeScriptPath(Type scriptType, string scriptPath)
|
|
|
|
+ {
|
|
|
|
+ // Constructed generic types all have the same path which is not allowed by Godot
|
|
|
|
+ // (every Resource must have a unique path). So we create a unique "virtual" path
|
|
|
|
+ // for each type.
|
|
|
|
+
|
|
|
|
+ if (!scriptPath.StartsWith("res://"))
|
|
|
|
+ {
|
|
|
|
+ throw new ArgumentException("Script path must start with 'res://'.", nameof(scriptPath));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ scriptPath = scriptPath.Substring("res://".Length);
|
|
|
|
+ return $"csharp://{scriptPath}:{scriptType}.cs";
|
|
|
|
+ }
|
|
|
|
+
|
|
if (GetPathOtherwiseGetOrCreateScript(scriptType, outScript, out string? scriptPath))
|
|
if (GetPathOtherwiseGetOrCreateScript(scriptType, outScript, out string? scriptPath))
|
|
{
|
|
{
|
|
// This path is slower, but it's only executed for the first instantiation of the type
|
|
// This path is slower, but it's only executed for the first instantiation of the type
|
|
|
|
|
|
|
|
+ if (scriptType.IsConstructedGenericType && !scriptPath.StartsWith("csharp://"))
|
|
|
|
+ {
|
|
|
|
+ // If the script type is generic it can't be loaded using the real script path.
|
|
|
|
+ // Construct a virtual path unique to this constructed generic type and add it
|
|
|
|
+ // to the path bimap so they can be found later by their virtual path.
|
|
|
|
+ // IMPORTANT: The virtual path must be added to _pathTypeBiMap before the first
|
|
|
|
+ // load of the script, otherwise the loaded script won't be added to _scriptTypeBiMap.
|
|
|
|
+ scriptPath = GetVirtualConstructedGenericTypeScriptPath(scriptType, scriptPath);
|
|
|
|
+ _pathTypeBiMap.Add(scriptPath, scriptType);
|
|
|
|
+ }
|
|
|
|
+
|
|
// This must be done outside the read-write lock, as the script resource loading can lock it
|
|
// This must be done outside the read-write lock, as the script resource loading can lock it
|
|
using godot_string scriptPathIn = Marshaling.ConvertStringToNative(scriptPath);
|
|
using godot_string scriptPathIn = Marshaling.ConvertStringToNative(scriptPath);
|
|
if (!NativeFuncs.godotsharp_internal_script_load(scriptPathIn, outScript).ToBool())
|
|
if (!NativeFuncs.godotsharp_internal_script_load(scriptPathIn, outScript).ToBool())
|
|
@@ -514,11 +618,23 @@ namespace Godot.Bridge
|
|
// with no path, as we do for types without an associated script file.
|
|
// with no path, as we do for types without an associated script file.
|
|
GetOrCreateScriptBridgeForType(scriptType, outScript);
|
|
GetOrCreateScriptBridgeForType(scriptType, outScript);
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ if (scriptType.IsConstructedGenericType)
|
|
|
|
+ {
|
|
|
|
+ // When reloading generic scripts they won't be added to the script bimap because their
|
|
|
|
+ // virtual path won't be in the path bimap yet. The current method executes when a derived type
|
|
|
|
+ // is trying to get or create the script for their base type. The code above has now added
|
|
|
|
+ // the virtual path to the path bimap and loading the script with that path should retrieve
|
|
|
|
+ // any existing script, so now we have a chance to make sure it's added to the script bimap.
|
|
|
|
+ AddScriptBridgeCore(outScript->Reference, scriptPath);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private static unsafe void CreateScriptBridgeForType(Type scriptType, godot_ref* outScript)
|
|
private static unsafe void CreateScriptBridgeForType(Type scriptType, godot_ref* outScript)
|
|
{
|
|
{
|
|
|
|
+ Debug.Assert(!scriptType.IsGenericTypeDefinition, $"Script type must be a constructed generic type or not generic at all. Type: {scriptType}.");
|
|
|
|
+
|
|
NativeFuncs.godotsharp_internal_new_csharp_script(outScript);
|
|
NativeFuncs.godotsharp_internal_new_csharp_script(outScript);
|
|
IntPtr scriptPtr = outScript->Reference;
|
|
IntPtr scriptPtr = outScript->Reference;
|
|
|
|
|
|
@@ -605,45 +721,82 @@ namespace Godot.Bridge
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- [UnmanagedCallersOnly]
|
|
|
|
- internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_string* outClassName,
|
|
|
|
- godot_bool* outTool, godot_bool* outGlobal, godot_bool* outAbstract, godot_string* outIconPath,
|
|
|
|
- godot_array* outMethodsDest, godot_dictionary* outRpcFunctionsDest,
|
|
|
|
- godot_dictionary* outEventSignalsDest, godot_ref* outBaseScript)
|
|
|
|
|
|
+ private static unsafe void GetScriptTypeInfo(Type scriptType, godot_csharp_type_info* outTypeInfo)
|
|
{
|
|
{
|
|
- try
|
|
|
|
|
|
+ Type native = GodotObject.InternalGetClassNativeBase(scriptType);
|
|
|
|
+
|
|
|
|
+ string typeName = scriptType.Name;
|
|
|
|
+ if (scriptType.IsGenericType)
|
|
{
|
|
{
|
|
- // Performance is not critical here as this will be replaced with source generators.
|
|
|
|
- var scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr);
|
|
|
|
|
|
+ var sb = new StringBuilder();
|
|
|
|
+ AppendTypeName(sb, scriptType);
|
|
|
|
+ typeName = sb.ToString();
|
|
|
|
+ }
|
|
|
|
|
|
- *outClassName = Marshaling.ConvertStringToNative(scriptType.Name);
|
|
|
|
|
|
+ godot_string className = Marshaling.ConvertStringToNative(typeName);
|
|
|
|
|
|
- *outTool = scriptType.GetCustomAttributes(inherit: false)
|
|
|
|
- .OfType<ToolAttribute>()
|
|
|
|
- .Any().ToGodotBool();
|
|
|
|
|
|
+ bool isTool = scriptType.IsDefined(typeof(ToolAttribute), inherit: false);
|
|
|
|
|
|
- if (!(*outTool).ToBool() && scriptType.IsNested)
|
|
|
|
- {
|
|
|
|
- *outTool = (scriptType.DeclaringType?.GetCustomAttributes(inherit: false)
|
|
|
|
- .OfType<ToolAttribute>()
|
|
|
|
- .Any() ?? false).ToGodotBool();
|
|
|
|
- }
|
|
|
|
|
|
+ // If the type is nested and the parent type is a tool script,
|
|
|
|
+ // consider the nested type a tool script as well.
|
|
|
|
+ if (!isTool && scriptType.IsNested)
|
|
|
|
+ {
|
|
|
|
+ isTool = scriptType.DeclaringType?.IsDefined(typeof(ToolAttribute), inherit: false) ?? false;
|
|
|
|
+ }
|
|
|
|
|
|
- if (!(*outTool).ToBool() && scriptType.Assembly.GetName().Name == "GodotTools")
|
|
|
|
- *outTool = godot_bool.True;
|
|
|
|
|
|
+ // Every script in the GodotTools assembly is a tool script.
|
|
|
|
+ if (!isTool && scriptType.Assembly.GetName().Name == "GodotTools")
|
|
|
|
+ {
|
|
|
|
+ isTool = true;
|
|
|
|
+ }
|
|
|
|
|
|
- var globalAttr = scriptType.GetCustomAttributes(inherit: false)
|
|
|
|
- .OfType<GlobalClassAttribute>()
|
|
|
|
- .FirstOrDefault();
|
|
|
|
|
|
+ bool isGlobalClass = scriptType.IsDefined(typeof(GlobalClassAttribute), inherit: false);
|
|
|
|
|
|
- *outGlobal = (globalAttr != null).ToGodotBool();
|
|
|
|
|
|
+ var iconAttr = scriptType.GetCustomAttributes(inherit: false)
|
|
|
|
+ .OfType<IconAttribute>()
|
|
|
|
+ .FirstOrDefault();
|
|
|
|
|
|
- var iconAttr = scriptType.GetCustomAttributes(inherit: false)
|
|
|
|
- .OfType<IconAttribute>()
|
|
|
|
- .FirstOrDefault();
|
|
|
|
- *outIconPath = Marshaling.ConvertStringToNative(iconAttr?.Path);
|
|
|
|
|
|
+ godot_string iconPath = Marshaling.ConvertStringToNative(iconAttr?.Path);
|
|
|
|
|
|
- *outAbstract = scriptType.IsAbstract.ToGodotBool();
|
|
|
|
|
|
+ outTypeInfo->ClassName = className;
|
|
|
|
+ outTypeInfo->IconPath = iconPath;
|
|
|
|
+ outTypeInfo->IsTool = isTool.ToGodotBool();
|
|
|
|
+ outTypeInfo->IsGlobalClass = isGlobalClass.ToGodotBool();
|
|
|
|
+ outTypeInfo->IsAbstract = scriptType.IsAbstract.ToGodotBool();
|
|
|
|
+ outTypeInfo->IsGenericTypeDefinition = scriptType.IsGenericTypeDefinition.ToGodotBool();
|
|
|
|
+ outTypeInfo->IsConstructedGenericType = scriptType.IsConstructedGenericType.ToGodotBool();
|
|
|
|
+
|
|
|
|
+ static void AppendTypeName(StringBuilder sb, Type type)
|
|
|
|
+ {
|
|
|
|
+ sb.Append(type.Name);
|
|
|
|
+ if (type.IsGenericType)
|
|
|
|
+ {
|
|
|
|
+ sb.Append('<');
|
|
|
|
+ for (int i = 0; i < type.GenericTypeArguments.Length; i++)
|
|
|
|
+ {
|
|
|
|
+ Type typeArg = type.GenericTypeArguments[i];
|
|
|
|
+ AppendTypeName(sb, typeArg);
|
|
|
|
+ if (i != type.GenericTypeArguments.Length - 1)
|
|
|
|
+ {
|
|
|
|
+ sb.Append(", ");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ sb.Append('>');
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ [UnmanagedCallersOnly]
|
|
|
|
+ internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_csharp_type_info* outTypeInfo,
|
|
|
|
+ godot_array* outMethodsDest, godot_dictionary* outRpcFunctionsDest, godot_dictionary* outEventSignalsDest, godot_ref* outBaseScript)
|
|
|
|
+ {
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ // Performance is not critical here as this will be replaced with source generators.
|
|
|
|
+ var scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr);
|
|
|
|
+ Debug.Assert(!scriptType.IsGenericTypeDefinition, $"Script type must be a constructed generic type or not generic at all. Type: {scriptType}.");
|
|
|
|
+
|
|
|
|
+ GetScriptTypeInfo(scriptType, outTypeInfo);
|
|
|
|
|
|
// Methods
|
|
// Methods
|
|
|
|
|
|
@@ -820,11 +973,7 @@ namespace Godot.Bridge
|
|
catch (Exception e)
|
|
catch (Exception e)
|
|
{
|
|
{
|
|
ExceptionUtils.LogException(e);
|
|
ExceptionUtils.LogException(e);
|
|
- *outClassName = default;
|
|
|
|
- *outTool = godot_bool.False;
|
|
|
|
- *outGlobal = godot_bool.False;
|
|
|
|
- *outAbstract = godot_bool.False;
|
|
|
|
- *outIconPath = default;
|
|
|
|
|
|
+ *outTypeInfo = default;
|
|
*outMethodsDest = NativeFuncs.godotsharp_array_new();
|
|
*outMethodsDest = NativeFuncs.godotsharp_array_new();
|
|
*outRpcFunctionsDest = NativeFuncs.godotsharp_dictionary_new();
|
|
*outRpcFunctionsDest = NativeFuncs.godotsharp_dictionary_new();
|
|
*outEventSignalsDest = NativeFuncs.godotsharp_dictionary_new();
|
|
*outEventSignalsDest = NativeFuncs.godotsharp_dictionary_new();
|