using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
namespace Godot.Bridge;
#nullable enable
public static partial class ScriptManagerBridge
{
private class ScriptTypeBiMap
{
public readonly object ReadWriteLock = new();
private System.Collections.Generic.Dictionary<IntPtr, Type> _scriptTypeMap = new();
private System.Collections.Generic.Dictionary<Type, IntPtr> _typeScriptMap = new();
public void Add(IntPtr scriptPtr, Type scriptType)
{
// TODO: What if this is called while unloading a load context, but after we already did cleanup in preparation for unloading?
Debug.Assert(!scriptType.IsGenericTypeDefinition, $"A generic type definition must never be added to the script type map. Type: {scriptType}.");
_scriptTypeMap.Add(scriptPtr, scriptType);
_typeScriptMap.Add(scriptType, scriptPtr);
if (AlcReloadCfg.IsAlcReloadingEnabled)
{
AddTypeForAlcReloading(scriptType);
}
}
public void Remove(IntPtr scriptPtr)
{
if (_scriptTypeMap.Remove(scriptPtr, out Type? scriptType))
_ = _typeScriptMap.Remove(scriptType);
}
public bool RemoveByScriptType(Type scriptType, out IntPtr scriptPtr)
{
if (_typeScriptMap.Remove(scriptType, out scriptPtr))
return _scriptTypeMap.Remove(scriptPtr);
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Type GetScriptType(IntPtr scriptPtr) => _scriptTypeMap[scriptPtr];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetScriptType(IntPtr scriptPtr, [MaybeNullWhen(false)] out Type scriptType) =>
_scriptTypeMap.TryGetValue(scriptPtr, out scriptType);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetScriptPtr(Type scriptType, out IntPtr scriptPtr) =>
_typeScriptMap.TryGetValue(scriptType, out scriptPtr);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsScriptRegistered(IntPtr scriptPtr) => _scriptTypeMap.ContainsKey(scriptPtr);
}
private class PathScriptTypeBiMap
{
private System.Collections.Generic.Dictionary<string, Type> _pathTypeMap = new();
private System.Collections.Generic.Dictionary<Type, string> _typePathMap = new();
public IReadOnlyCollection<string> Paths => _pathTypeMap.Keys;
public void Add(string scriptPath, Type scriptType)
{
_pathTypeMap.Add(scriptPath, scriptType);
// Due to partial classes, more than one file can point to the same type, so
// there could be duplicate keys in this case. We only add a type as key once.
_typePathMap.TryAdd(scriptType, scriptPath);
}
public void RemoveByScriptType(Type scriptType)
{
foreach (var pair in _pathTypeMap
.Where(p => p.Value == scriptType).ToArray())
{
_pathTypeMap.Remove(pair.Key);
}
_typePathMap.Remove(scriptType);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetScriptType(string scriptPath, [MaybeNullWhen(false)] out Type scriptType) =>
// This must never return true for a generic type definition, we only consider script types
// the types that can be attached to a Node/Resource (non-generic or constructed generic types).
_pathTypeMap.TryGetValue(scriptPath, out scriptType) && !scriptType.IsGenericTypeDefinition;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetScriptPath(Type scriptType, [MaybeNullWhen(false)] out string scriptPath)
{
if (scriptType.IsGenericTypeDefinition)
{
// This must never return true for a generic type definition, we only consider script types
// the types that can be attached to a Node/Resource (non-generic or constructed generic types).
scriptPath = null;
return false;
}
return _typePathMap.TryGetValue(scriptType, out scriptPath);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetGenericTypeDefinitionPath(Type genericTypeDefinition, [MaybeNullWhen(false)] out string scriptPath)
{
Debug.Assert(genericTypeDefinition.IsGenericTypeDefinition);
return _typePathMap.TryGetValue(genericTypeDefinition, out scriptPath);
}
}
}