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

@@ -19,6 +19,7 @@ namespace FlaxEngine.Json
{
internal class SerializerCache
{
public readonly JsonSerializerSettings settings;
public Newtonsoft.Json.JsonSerializer JsonSerializer;
public StringBuilder StringBuilder;
public StringWriter StringWriter;
@@ -28,37 +29,35 @@ namespace FlaxEngine.Json
public StreamReader Reader;
public bool IsWriting;
public bool IsReading;
public uint cacheVersion;
public unsafe SerializerCache(JsonSerializerSettings settings)
{
JsonSerializer = Newtonsoft.Json.JsonSerializer.CreateDefault(settings);
JsonSerializer.Formatting = Formatting.Indented;
JsonSerializer.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
this.settings = settings;
StringBuilder = new StringBuilder(256);
StringWriter = new StringWriter(StringBuilder, CultureInfo.InvariantCulture);
SerializerWriter = new JsonSerializerInternalWriter(JsonSerializer);
MemoryStream = new UnmanagedMemoryStream((byte*)0, 0);
Reader = new StreamReader(MemoryStream, Encoding.UTF8, false);
JsonWriter = new JsonTextWriter(StringWriter)
lock (cacheSyncRoot)
{
IndentChar = '\t',
Indentation = 1,
Formatting = JsonSerializer.Formatting,
DateFormatHandling = JsonSerializer.DateFormatHandling,
DateTimeZoneHandling = JsonSerializer.DateTimeZoneHandling,
FloatFormatHandling = JsonSerializer.FloatFormatHandling,
StringEscapeHandling = JsonSerializer.StringEscapeHandling,
Culture = JsonSerializer.Culture,
DateFormatString = JsonSerializer.DateFormatString,
};
BuildSerializer();
BuildRead();
BuildWrite();
cacheVersion = currentCacheVersion;
}
}
public void ReadBegin()
{
CheckCacheVersionRebuild();
// TODO: Reset reading state (eg if previous deserialization got exception)
if (IsReading)
{
// TODO: Reset reading state (eg if previous deserialization got exception)
}
BuildRead();
IsWriting = false;
IsReading = true;
}
@@ -70,23 +69,12 @@ namespace FlaxEngine.Json
public void WriteBegin()
{
CheckCacheVersionRebuild();
// Reset writing state (eg if previous serialization got exception)
if (IsWriting)
{
// Reset writing state (eg if previous serialization got exception)
SerializerWriter = new JsonSerializerInternalWriter(JsonSerializer);
JsonWriter = new JsonTextWriter(StringWriter)
{
IndentChar = '\t',
Indentation = 1,
Formatting = JsonSerializer.Formatting,
DateFormatHandling = JsonSerializer.DateFormatHandling,
DateTimeZoneHandling = JsonSerializer.DateTimeZoneHandling,
FloatFormatHandling = JsonSerializer.FloatFormatHandling,
StringEscapeHandling = JsonSerializer.StringEscapeHandling,
Culture = JsonSerializer.Culture,
DateFormatString = JsonSerializer.DateFormatString,
};
}
BuildWrite();
StringBuilder.Clear();
IsWriting = true;
IsReading = false;
@@ -96,21 +84,83 @@ namespace FlaxEngine.Json
{
IsWriting = false;
}
/// <summary>Check that the cache is up to date, rebuild it if it isn't</summary>
private void CheckCacheVersionRebuild()
{
var cCV = currentCacheVersion;
if (cacheVersion == cCV)
return;
lock (cacheSyncRoot)
{
cCV = currentCacheVersion;
if (cacheVersion == cCV)
return;
BuildSerializer();
BuildRead();
BuildWrite();
cacheVersion = cCV;
}
}
/// <summary>Builds the serializer</summary>
private void BuildSerializer()
{
JsonSerializer = Newtonsoft.Json.JsonSerializer.CreateDefault(settings);
JsonSerializer.Formatting = Formatting.Indented;
JsonSerializer.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
}
/// <summary>Builds the reader state</summary>
private void BuildRead()
{
Reader = new StreamReader(MemoryStream, Encoding.UTF8, false);
}
/// <summary>Builds the writer state</summary>
private void BuildWrite()
{
SerializerWriter = new JsonSerializerInternalWriter(JsonSerializer);
JsonWriter = new JsonTextWriter(StringWriter) {
IndentChar = '\t',
Indentation = 1,
Formatting = JsonSerializer.Formatting,
DateFormatHandling = JsonSerializer.DateFormatHandling,
DateTimeZoneHandling = JsonSerializer.DateTimeZoneHandling,
FloatFormatHandling = JsonSerializer.FloatFormatHandling,
StringEscapeHandling = JsonSerializer.StringEscapeHandling,
Culture = JsonSerializer.Culture,
DateFormatString = JsonSerializer.DateFormatString,
};
}
}
internal static JsonSerializerSettings Settings = CreateDefaultSettings(false);
internal static JsonSerializerSettings SettingsManagedOnly = CreateDefaultSettings(true);
internal static ExtendedSerializationBinder SerializationBinder;
internal static FlaxObjectConverter ObjectConverter;
internal static ThreadLocal<SerializerCache> Current = new ThreadLocal<SerializerCache>();
internal static ThreadLocal<SerializerCache> Cache = new ThreadLocal<SerializerCache>(() => new SerializerCache(Settings));
internal static ThreadLocal<SerializerCache> CacheManagedOnly = new ThreadLocal<SerializerCache>(() => new SerializerCache(SettingsManagedOnly));
internal static ThreadLocal<IntPtr> CachedGuidBuffer = new ThreadLocal<IntPtr>(() => Marshal.AllocHGlobal(32 * sizeof(char)), true);
internal static string CachedGuidDigits = "0123456789abcdef";
/// <summary>The version of the cache, used to check that a cache is not out of date</summary>
internal static uint currentCacheVersion = 0;
/// <summary>Used to synchronize cache operations such as <see cref="SerializerCache"/> rebuild, and <see cref="ResetCache"/></summary>
internal static readonly object cacheSyncRoot = new();
internal static JsonSerializerSettings CreateDefaultSettings(bool isManagedOnly)
{
//Newtonsoft.Json.Utilities.MiscellaneousUtils.ValueEquals = ValueEquals;
if (SerializationBinder is null)
SerializationBinder = new();
var settings = new JsonSerializerSettings
{
ContractResolver = new ExtendedDefaultContractResolver(isManagedOnly),
@@ -118,6 +168,7 @@ namespace FlaxEngine.Json
TypeNameHandling = TypeNameHandling.Auto,
NullValueHandling = NullValueHandling.Include,
ObjectCreationHandling = ObjectCreationHandling.Auto,
SerializationBinder = SerializationBinder,
};
if (ObjectConverter == null)
ObjectConverter = new FlaxObjectConverter();
@@ -134,6 +185,18 @@ namespace FlaxEngine.Json
return settings;
}
/// <summary>Called to reset the serialization cache</summary>
internal static void ResetCache()
{
lock (cacheSyncRoot)
{
unchecked { currentCacheVersion++; }
Newtonsoft.Json.JsonSerializer.ClearCache();
SerializationBinder.ResetCache();
}
}
internal static void Dispose()
{
CachedGuidBuffer.Values.ForEach(Marshal.FreeHGlobal);