Files
2026-07-29 09:38:54 +02:00

109 lines
3.7 KiB
C#

#if TOOLS
using System;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Godot;
namespace GodotHostTest.AetherBind.Editor;
/// <summary>
/// Taken from https://github.com/godotengine/godot-proposals/issues/12294
/// </summary>
public static class GodotScriptPathCache
{
// private static readonly ConcurrentDictionary<Type, string> TypeToPath = new();
private static readonly ConcurrentDictionary<string, Type> PathToType = new();
private static readonly Type GodotObject = typeof(GodotObject);
private static readonly Type ScriptPathAttribute = typeof(ScriptPathAttribute);
private static AssemblyLoadEventHandler? _assemblyLoadEventHandler;
private static void InitializeIfNeeded()
{
if (_assemblyLoadEventHandler != null) return;
Initialize();
AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly())!
.Unloading += _ => { Deinitialize(); };
}
private static void Initialize()
{
GD.Print($"Initializing {nameof(GodotScriptPathCache)}");
// TypeToPath.Clear();
PathToType.Clear();
_assemblyLoadEventHandler = (_, args) => CacheScriptsInAssembly(args.LoadedAssembly);
AppDomain.CurrentDomain.AssemblyLoad += _assemblyLoadEventHandler;
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
CacheScriptsInAssembly(assembly);
return;
void CacheScriptsInAssembly(Assembly assembly)
{
foreach (var type in assembly.GetTypes()
.Where(t => t.IsClass)
.Where(t => t.IsSubclassOf(GodotObject)))
// get script path attribute
if (type.GetCustomAttributes(ScriptPathAttribute, false).FirstOrDefault() is ScriptPathAttribute
scriptPath)
{
// TypeToPath[type] = scriptPath.Path;
PathToType[scriptPath.Path] = type;
}
}
}
private static void Deinitialize()
{
GD.Print($"Deinitializing {nameof(GodotScriptPathCache)}");
AppDomain.CurrentDomain.AssemblyLoad -= _assemblyLoadEventHandler;
_assemblyLoadEventHandler = null;
// TypeToPath.Clear();
PathToType.Clear();
}
// public static bool TryGetScriptPath(Type type, [MaybeNullWhen(false)] out string path)
// {
// InitializeIfNeeded();
// return TypeToPath.TryGetValue(type, out path);
// }
// public static Script GetScriptFromType(Type type)
// {
// InitializeIfNeeded();
// return TryGetScriptPath(type, out string path)
// ? GD.Load<Script>(path)
// : throw new InvalidOperationException("Script path not found in cache.");
// }
// public static Script GetScriptFromType<T>()
// {
// InitializeIfNeeded();
// return GetScriptFromType(typeof(T));
// }
private static bool TryGetTypeFromPath(string path, [MaybeNullWhen(false)] out Type type)
{
InitializeIfNeeded();
return PathToType.TryGetValue(path, out type);
}
public static Type? GetCSharpScriptType(this GodotObject node)
{
if (node.GetScript().AsGodotObject() is CSharpScript script &&
TryGetTypeFromPath(script.ResourcePath, out var type))
return type;
return null;
}
public static bool IsServiceSource(this GodotObject node)
{
return node.GetScript().AsGodotObject() is CSharpScript script &&
TryGetTypeFromPath(script.ResourcePath, out var type) &&
type.IsAssignableTo(typeof(ServiceSource));
}
}
#endif