Files
aibis-dream/Assets/Editor/HandlerNameCollector/HandlerNameCache.cs
T

222 lines
7.2 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
namespace AibisDream.EditorTools
{
public enum HandlerEntryKind
{
DirectorHandler = 0,
AnimatorHandler = 1
}
[Serializable]
public class HandlerNameCacheEntry
{
public string guid;
public string assetPath;
public string fileHash;
public string lastModified;
public string registeredName;
public HandlerEntryKind kind;
public string gameObjectPath;
public bool isInScene;
public int lineNumber;
}
[Serializable]
public class HandlerNameCache
{
public const int CurrentVersion = 2;
public int version = CurrentVersion;
public string lastFullScanTime;
public List<HandlerNameCacheEntry> entries = new List<HandlerNameCacheEntry>();
private static readonly string CacheFileName = "handler_name_cache.json";
public static string CacheFolderFullPath =>
Path.Combine(Application.dataPath, "Editor/HandlerNameCollector/Cache");
public static string CacheFileFullPath =>
Path.Combine(CacheFolderFullPath, CacheFileName);
/// <summary>迁移用:旧版 TimelineNameCollector 缓存路径。</summary>
private static string LegacyTimelineCacheFileFullPath =>
Path.Combine(Application.dataPath, "Editor/TimelineNameCollector/Cache/timeline_name_cache.json");
public static HandlerNameCache Load()
{
try
{
string json = null;
if (File.Exists(CacheFileFullPath))
json = File.ReadAllText(CacheFileFullPath, Encoding.UTF8);
else if (File.Exists(LegacyTimelineCacheFileFullPath))
json = File.ReadAllText(LegacyTimelineCacheFileFullPath, Encoding.UTF8);
if (string.IsNullOrEmpty(json))
{
return NewEmpty();
}
json = MigrateLegacyCacheJson(json);
var cache = JsonConvert.DeserializeObject<HandlerNameCache>(json);
if (cache == null)
cache = NewEmpty();
cache.version = CurrentVersion;
cache.entries ??= new List<HandlerNameCacheEntry>();
return cache;
}
catch (Exception e)
{
Debug.LogError($"[HandlerNameCache] Failed to load cache: {e}");
return NewEmpty();
}
}
private static HandlerNameCache NewEmpty()
{
return new HandlerNameCache
{
version = CurrentVersion,
lastFullScanTime = null,
entries = new List<HandlerNameCacheEntry>()
};
}
/// <summary>
/// 将旧版 timelineName 字段、缺省 kind 等合并为当前 JSON 结构。
/// </summary>
private static string MigrateLegacyCacheJson(string json)
{
try
{
var jo = JObject.Parse(json);
if (jo["entries"] is JArray arr)
{
foreach (var token in arr)
{
if (token is not JObject item)
continue;
var reg = item["registeredName"]?.ToString();
if (string.IsNullOrWhiteSpace(reg))
{
var legacy = item["timelineName"]?.ToString();
if (!string.IsNullOrEmpty(legacy))
item["registeredName"] = legacy;
}
item.Remove("timelineName");
if (item["kind"] == null)
item["kind"] = (int)HandlerEntryKind.DirectorHandler;
}
}
jo["version"] = CurrentVersion;
return jo.ToString(Formatting.None);
}
catch (Exception e)
{
Debug.LogWarning($"[HandlerNameCache] JSON migration fallback: {e}");
return json;
}
}
public void Save()
{
try
{
if (!Directory.Exists(CacheFolderFullPath))
Directory.CreateDirectory(CacheFolderFullPath);
entries ??= new List<HandlerNameCacheEntry>();
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
StringEscapeHandling = StringEscapeHandling.Default
};
var json = JsonConvert.SerializeObject(this, settings);
File.WriteAllText(CacheFileFullPath, json, Encoding.UTF8);
}
catch (Exception e)
{
Debug.LogError($"[HandlerNameCache] Failed to save cache: {e}");
}
}
public void SetLastFullScanNow()
{
lastFullScanTime = DateTime.UtcNow.ToString("o");
}
public static string ComputeFileHash(string fullPath)
{
try
{
if (!File.Exists(fullPath))
return null;
using (var stream = File.OpenRead(fullPath))
using (var md5 = MD5.Create())
{
var hash = md5.ComputeHash(stream);
var sb = new StringBuilder(hash.Length * 2);
foreach (var b in hash)
sb.Append(b.ToString("x2"));
return sb.ToString();
}
}
catch (Exception e)
{
Debug.LogError($"[HandlerNameCache] Failed to compute file hash for '{fullPath}': {e}");
return null;
}
}
public static string GetAssetFullPath(string assetPath)
{
if (string.IsNullOrEmpty(assetPath))
return null;
if (!assetPath.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase) &&
!assetPath.Equals("Assets", StringComparison.OrdinalIgnoreCase))
return null;
var relative = assetPath.Substring("Assets".Length)
.TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
return Path.Combine(Application.dataPath, relative);
}
public void RemoveEntriesForAsset(string assetPath)
{
if (entries == null || string.IsNullOrEmpty(assetPath))
return;
entries.RemoveAll(e => e != null && e.assetPath == assetPath);
}
public void CleanupDeletedAssets()
{
if (entries == null)
return;
entries.RemoveAll(e =>
{
if (e == null || string.IsNullOrEmpty(e.assetPath))
return true;
return AssetDatabase.LoadMainAssetAtPath(e.assetPath) == null;
});
}
}
}