using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using Unity.VisualScripting.FullSerializer; #if UNITY_EDITOR using UnityEditor; #endif using Debug = UnityEngine.Debug; using UnityObject = UnityEngine.Object; namespace Unity.VisualScripting { public static class Serialization { static Serialization() { freeOperations = new HashSet(); busyOperations = new HashSet(); } public const string ConstructorWarning = "This parameterless constructor is only made public for serialization. Use another constructor instead."; private static readonly HashSet freeOperations; private static readonly HashSet busyOperations; private static readonly object @lock = new object(); public static bool isUnitySerializing { get; set; } public static bool isCustomSerializing => busyOperations.Count > 0; public static bool isSerializing => isUnitySerializing || isCustomSerializing; private static SerializationOperation StartOperation() { lock (@lock) { if (freeOperations.Count == 0) { freeOperations.Add(new SerializationOperation()); } var operation = freeOperations.First(); freeOperations.Remove(operation); busyOperations.Add(operation); return operation; } } private static void EndOperation(SerializationOperation operation) { lock (@lock) { if (!busyOperations.Contains(operation)) { throw new InvalidOperationException("Trying to finish an operation that isn't started."); } operation.Reset(); busyOperations.Remove(operation); freeOperations.Add(operation); } } public static T CloneViaSerialization(this T value, bool forceReflected = false) { return (T)Deserialize(Serialize(value, forceReflected), forceReflected); } public static void CloneViaSerializationInto(this TSource value, ref TDestination instance, bool forceReflected = false) where TDestination : TSource { object _instance = instance; DeserializeInto(Serialize(value, forceReflected), ref _instance, forceReflected); } public static SerializationData Serialize(this object value, bool forceReflected = false) { var operation = StartOperation(); try { var json = SerializeJson(operation.serializer, value, forceReflected); var objectReferences = operation.objectReferences.ToArray(); var data = new SerializationData(json, objectReferences); #if DEBUG_SERIALIZATION Debug.Log(data.ToString($"Serialized: {value?.GetType().Name ?? "null"} [{value?.GetHashCode().ToString() ?? "N/A"}]")); #endif return data; } catch (Exception ex) { throw new SerializationException($"Serialization of '{value?.GetType().ToString() ?? "null"}' failed.", ex); } finally { EndOperation(operation); } } public static void DeserializeInto(this SerializationData data, ref object instance, bool forceReflected = false) { try { if (string.IsNullOrEmpty(data.json)) { instance = null; return; } #if DEBUG_SERIALIZATION Debug.Log(data.ToString($"Deserializing into: {instance?.GetType().Name ?? "null"} [{instance?.GetHashCode().ToString() ?? "N/A"}]")); #endif var operation = StartOperation(); try { operation.objectReferences.AddRange(data.objectReferences); DeserializeJson(operation.serializer, data.json, ref instance, forceReflected); } finally { EndOperation(operation); } } catch (Exception ex) { try { Debug.LogWarning(data.ToString("Deserialization Failure Data"), instance as UnityObject); } catch (Exception ex2) { Debug.LogWarning("Failed to log deserialization failure data:\n" + ex2, instance as UnityObject); } throw new SerializationException($"Deserialization into '{instance?.GetType().ToString() ?? "null"}' failed.", ex); } } public static object Deserialize(this SerializationData data, bool forceReflected = false) { object instance = null; DeserializeInto(data, ref instance, forceReflected); return instance; } private static string SerializeJson(fsSerializer serializer, object instance, bool forceReflected) { using (ProfilingUtility.SampleBlock("SerializeJson")) { fsData data; fsResult result; if (forceReflected) { result = serializer.TrySerialize(instance.GetType(), typeof(fsReflectedConverter), instance, out data); } else { result = serializer.TrySerialize(instance, out data); } HandleResult("Serialization", result, instance as UnityObject); return fsJsonPrinter.CompressedJson(data); } } private static fsResult DeserializeJsonUtil(fsSerializer serializer, string json, ref object instance, bool forceReflected) { var fsData = fsJsonParser.Parse(json); fsResult result; if (forceReflected) { result = serializer.TryDeserialize(fsData, instance.GetType(), typeof(fsReflectedConverter), ref instance); } else { result = serializer.TryDeserialize(fsData, ref instance); } return result; } private static void DeserializeJson(fsSerializer serializer, string json, ref object instance, bool forceReflected) { using (ProfilingUtility.SampleBlock("DeserializeJson")) { fsResult result = DeserializeJsonUtil(serializer, json, ref instance, forceReflected); HandleResult("Deserialization", result, instance as UnityObject); } } private static void HandleResult(string label, fsResult result, UnityObject context = null) { result.AssertSuccess(); if (result.HasWarnings) { foreach (var warning in result.RawMessages) { Debug.LogWarning($"[{label}] {warning}\n", context); } } } public static string PrettyPrint(string json) { return fsJsonPrinter.PrettyJson(fsJsonParser.Parse(json)); } #region Dependencies private static readonly HashSet awaitingDependers = new HashSet(); public static void AwaitDependencies(ISerializationDepender depender) { awaitingDependers.Add(depender); CheckIfDependenciesMet(depender); } public static void NotifyDependencyDeserializing(ISerializationDependency dependency) { NotifyDependencyUnavailable(dependency); } public static void NotifyDependencyDeserialized(ISerializationDependency dependency) { NotifyDependencyAvailable(dependency); } public static void NotifyDependencyUnavailable(ISerializationDependency dependency) { dependency.IsDeserialized = false; } public static void NotifyDependencyAvailable(ISerializationDependency dependency) { dependency.IsDeserialized = true; foreach (var awaitingDepender in awaitingDependers.ToArray()) { if (!awaitingDependers.Contains(awaitingDepender)) { // In case the depender was already handled by a recursive // dependency via OnAfterDependenciesDeserialized, // we skip it. This is necessary because we duplicated // the set to safely iterate over it with removal. // // This should prevent OnAfterDependenciesDeserialized from // running twice on any given depender in a single deserialization // operation. continue; } CheckIfDependenciesMet(awaitingDepender); } } private static void CheckIfDependenciesMet(ISerializationDepender depender) { var areDependenciesMet = true; foreach (var requiredDependency in depender.deserializationDependencies) { if (!requiredDependency.IsDeserialized) { areDependenciesMet = false; break; } } if (areDependenciesMet) { awaitingDependers.Remove(depender); depender.OnAfterDependenciesDeserialized(); } } public static void LogStuckDependers() { if (awaitingDependers.Any()) { var message = awaitingDependers.Count + " awaiting dependers: \n"; foreach (var depender in awaitingDependers) { HashSet missingDependencies = new HashSet(); foreach (var requiredDependency in depender.deserializationDependencies) { if (!requiredDependency.IsDeserialized) { missingDependencies.Add(requiredDependency); break; } } message += $"{depender} is missing {missingDependencies.ToCommaSeparatedString()}\n"; } Debug.LogWarning(message); } else { Debug.Log("No stuck awaiting depender."); } } #endregion } }