using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using UnityEditor; using UnityEngine; using UnityEngine.Networking; using Debug = UnityEngine.Debug; using Object = UnityEngine.Object; namespace Needle.Engine.Samples.Helpers { class SerializerSettings { public static JsonSerializerSettings Get(string exportRoot = null) { var allTags = AssetDatabase.FindAssets("t:Tag") .Select(AssetDatabase.GUIDToAssetPath) .Select(AssetDatabase.LoadAssetAtPath) .Where(x => x) .ToArray(); var converters = new List() { new Texture2DConverter(), new TagConverter(allTags), new ScriptableObjectConverter(), new ScriptableObjectConverter(), }; if (exportRoot != null) Texture2DConverter.RootPath = exportRoot; var resolver = new PropertyRenameAndIgnoreSerializerContractResolver(); resolver.IgnoreProperty(typeof(Object), nameof(Object.name)); resolver.IgnoreProperty(typeof(Object), nameof(Object.hideFlags)); resolver.IgnoreProperty(typeof(Vector2), nameof(Vector2.normalized)); resolver.IgnoreProperty(typeof(Vector2), nameof(Vector2.magnitude)); resolver.IgnoreProperty(typeof(Vector2), nameof(Vector2.sqrMagnitude)); resolver.IgnoreProperty(typeof(Vector3), nameof(Vector3.normalized)); resolver.IgnoreProperty(typeof(Vector3), nameof(Vector3.magnitude)); resolver.IgnoreProperty(typeof(Vector3), nameof(Vector3.sqrMagnitude)); resolver.IgnoreProperty(typeof(Quaternion), nameof(Quaternion.eulerAngles)); resolver.IgnoreProperty(typeof(Color), nameof(Color.linear)); resolver.IgnoreProperty(typeof(Color), nameof(Color.gamma)); resolver.IgnoreProperty(typeof(Color), nameof(Color.grayscale)); resolver.IgnoreProperty(typeof(Color), nameof(Color.maxColorComponent)); resolver.IgnoreAllObsolete(typeof(Component)); resolver.IgnoreProperty(typeof(MonoBehaviour), nameof(MonoBehaviour.useGUILayout)); resolver.IgnoreProperty(typeof(Behaviour), nameof(Behaviour.isActiveAndEnabled)); resolver.IgnoreProperty(typeof(MonoBehaviour), "runInEditMode"); var settings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, Converters = converters, ReferenceLoopHandling = ReferenceLoopHandling.Ignore, Formatting = Formatting.Indented, ContractResolver = resolver, }; return settings; } } class TagConverter : JsonConverter { private readonly Dictionary _allTags; public TagConverter(Tag[] allTags) { _allTags = new Dictionary(); foreach (var tag in allTags) { if (!tag || string.IsNullOrEmpty(tag.name)) continue; if (_allTags.ContainsKey(tag.name)) continue; _allTags.Add(tag.name, tag); } } public override void WriteJson(JsonWriter writer, Tag value, JsonSerializer serializer) { writer.WriteValue(value.name); } public override Tag ReadJson(JsonReader reader, Type objectType, Tag existingValue, bool hasExistingValue, JsonSerializer serializer) { var val = reader.Value as string; if (existingValue) return existingValue; if (val == null) return null; if (!_allTags.ContainsKey(val)) { var t = ScriptableObject.CreateInstance(); t.name = val; _allTags.Add(val, t); return t; } return _allTags[val]; } } class Texture2DConverter : JsonConverter { internal static string RootPath = "../../"; public static string GetPathForTexture(Texture texture) { if (!texture) return null; var path = Path.GetFullPath(AssetDatabase.GetAssetPath(texture)); var fullRootPath = Path.GetFullPath(RootPath); var relativePath = path.Replace(fullRootPath, "").Replace("\\","/").Replace("\\","/"); return relativePath; } public override void WriteJson(JsonWriter writer, Texture2D value, JsonSerializer serializer) { if (!value) { writer.WriteNull(); return; } serializer.Serialize(writer, new TextureData() { relativePath = GetPathForTexture(value), absolutePath = Constants.RepoRoot + GetPathForTexture(value) + "?v=" + value.imageContentsHash, }); } private struct TextureData { public string relativePath; public string absolutePath; } public override Texture2D ReadJson(JsonReader reader, Type objectType, Texture2D existingValue, bool hasExistingValue, JsonSerializer serializer) { var t = existingValue; var textureData = serializer.Deserialize(reader); if (string.IsNullOrEmpty(textureData.absolutePath)) { return null; } if (!hasExistingValue) { if (TextureCache.TryGetValue(textureData.absolutePath, out t) && t) { return t; } t = new Texture2D(1, 1); t.SetPixels(new Color[] { Color.clear }); } var str = textureData.relativePath; DownloadAndCacheImageAndFillTexture(t, textureData, serializer.Context.Context); t.name = string.IsNullOrEmpty(str) ? "no texture" : str; return t; } // cache for this session private static readonly Dictionary TextureCache = new Dictionary(); private async void DownloadAndCacheImageAndFillTexture(Texture2D target, TextureData textureData, object context) { var path = textureData.relativePath; if (path.StartsWith("assets")) path = path.Replace("assets/", "Packages/com.needle.engine-samples/"); if (!File.Exists(path)) path = textureData.absolutePath; var cachePath = Constants.CacheRoot + SamplesWindow.SanitizePath(Path.GetFileName(path)); if (File.Exists(cachePath)) path = cachePath; // use file:// for local files if (File.Exists(path)) path = "file://" + Path.GetFullPath(path); var request = new UnityWebRequest(path); request.downloadHandler = new DownloadHandlerBuffer(); #if UNITY_2023_3_OR_NEWER await request.SendWebRequest(); #else request.SendWebRequest(); #endif while (!request.isDone) await Task.Yield(); if (request.result == UnityWebRequest.Result.Success) { var data = request.downloadHandler.data; target.LoadImage(data, true); if (context is EditorWindow editorWindow) SamplesWindow.MarkImagesDirty(editorWindow.rootVisualElement); if (context is Editor editor) editor.Repaint(); if (!path.StartsWith("file://")) { Directory.CreateDirectory(Constants.CacheRoot); File.WriteAllBytes(cachePath, data); // cache for this session TextureCache[textureData.absolutePath] = target; } } else Debug.Log(request.error); } } class ScriptableObjectConverter : CustomCreationConverter where T : ScriptableObject { public override T Create(Type objectType) { return ScriptableObject.CreateInstance(); } } }