Fixed issue involving stale scripting assemblies in FlaxEngine.Json dynamic type resolution

Added new ExtendedSerializationBinder
Added callback to clear serializer cache on scripting assembly reload
Added low-cost mechanism to invalidate the SerializerCache after domain reload
This commit is contained in:
SS
2023-11-06 18:49:30 -07:00
parent c025b4414c
commit d6e93a7fab
4 changed files with 381 additions and 57 deletions

View File

@@ -1022,6 +1022,8 @@ namespace FlaxEngine.Interop
pair.Value.Free();
classAttributesCacheCollectible.Clear();
FlaxEngine.Json.JsonSerializer.ResetCache();
// Unload the ALC
bool unloading = true;
scriptingAssemblyLoadContext.Unloading += (alc) => { unloading = false; };

View File

@@ -278,44 +278,65 @@ namespace FlaxEngine.Interop
if (typeCache.TryGetValue(typeName, out Type type))
return type;
type = Type.GetType(typeName, ResolveAssemblyByName, null);
type = Type.GetType(typeName, ResolveAssembly, null);
if (type == null)
{
foreach (var assembly in scriptingAssemblyLoadContext.Assemblies)
{
type = assembly.GetType(typeName);
if (type != null)
break;
}
}
type = ResolveSlow(typeName);
if (type == null)
{
string oldTypeName = typeName;
string fullTypeName = typeName;
typeName = typeName.Substring(0, typeName.IndexOf(','));
type = Type.GetType(typeName, ResolveAssemblyByName, null);
type = Type.GetType(typeName, ResolveAssembly, null);
if (type == null)
{
foreach (var assembly in scriptingAssemblyLoadContext.Assemblies)
{
type = assembly.GetType(typeName);
if (type != null)
break;
}
}
typeName = oldTypeName;
type = ResolveSlow(typeName);
typeName = fullTypeName;
}
typeCache.Add(typeName, type);
return type;
/// <summary>Resolve the type by manually checking every scripting assembly</summary>
static Type ResolveSlow(string typeName) {
foreach (var assembly in scriptingAssemblyLoadContext.Assemblies) {
var type = assembly.GetType(typeName);
if (type != null)
return type;
}
return null;
}
/// <summary>Resolve the assembly by name</summary>
static Assembly ResolveAssembly(AssemblyName name) => ResolveScriptingAssemblyByName(name, allowPartial: false);
}
private static Assembly ResolveAssemblyByName(AssemblyName assemblyName)
/// <summary>Find <paramref name="assemblyName"/> among the scripting assemblies.</summary>
/// <param name="assemblyName">The name to find</param>
/// <param name="allowPartial">If true, partial names should be allowed to be resolved.</param>
/// <returns>The resolved assembly, or null if none could be found.</returns>
internal static Assembly ResolveScriptingAssemblyByName(AssemblyName assemblyName, bool allowPartial = false)
{
foreach (Assembly assembly in scriptingAssemblyLoadContext.Assemblies)
if (assembly.GetName() == assemblyName)
{
var curName = assembly.GetName();
if (curName == assemblyName || (allowPartial && curName.Name == assemblyName.Name))
return assembly;
}
if (allowPartial) // Check partial names if full name isn't found
{
string partialName = assemblyName.Name;
foreach (Assembly assembly in scriptingAssemblyLoadContext.Assemblies)
{
var curName = assembly.GetName();
if (curName.Name == partialName)
return assembly;
}
}
return null;
}