using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
#endif
namespace BaseGames.Editor
{
///
/// Editor 工具:验证 中所有常量
/// 是否与 Addressable 分组中实际存在的地址同步(架构 13_AssetPoolModule §10)。
///
/// 菜单:BaseGames → Tools → Validate Address Keys
/// Build 回调顺序 = 0(在 SOValidationRunner callbackOrder = 1 之前执行)
///
public class AddressKeyValidatorBuildHook : IPreprocessBuildWithReport
{
public int callbackOrder => 0;
public void OnPreprocessBuild(BuildReport report)
{
var results = AddressKeyValidator.RunValidation();
int missing = results.Count(r => !r.ExistsInAddressables);
if (missing > 0)
{
var orphans = results
.Where(r => !r.ExistsInAddressables)
.Select(r => $"AddressKeys.{r.FieldName} = \"{r.Value}\"");
throw new BuildFailedException(
$"[AddressKeyValidator] {missing} 个孤儿 AddressKey,构建中止:\n"
+ string.Join("\n", orphans));
}
}
}
///
/// Editor 静态工具类:验证逻辑和 MenuItem 入口。
///
public static class AddressKeyValidator
{
[MenuItem("BaseGames/Tools/Validate Address Keys")]
public static void ValidateAll()
{
var results = RunValidation();
LogResults(results);
}
///
/// 执行验证,返回每个 key 的验证结果。供 Build Pre-process 或测试调用。
///
public static List RunValidation()
{
var results = new List();
var registeredAddresses = GetAllAddressableAddresses();
// 通过反射取出 AddressKeys 中所有 public const string 字段
var keyType = typeof(BaseGames.Core.Assets.AddressKeys);
var fields = keyType.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)
.Where(f => f.IsLiteral && !f.IsInitOnly && f.FieldType == typeof(string));
foreach (var field in fields)
{
var value = (string)field.GetRawConstantValue();
var exists = registeredAddresses.Contains(value);
results.Add(new ValidationResult(field.Name, value, exists));
}
return results;
}
// ── Internal ──────────────────────────────────────────────────────────
private static HashSet GetAllAddressableAddresses()
{
var addresses = new HashSet(StringComparer.Ordinal);
#if UNITY_EDITOR
var settings = AddressableAssetSettingsDefaultObject.Settings;
if (settings == null)
{
Debug.LogWarning("[AddressKeyValidator] Addressable Settings 未找到,请先初始化 Addressables。");
return addresses;
}
foreach (var group in settings.groups)
{
if (group == null) continue;
foreach (var entry in group.entries)
{
if (entry != null)
addresses.Add(entry.address);
}
}
#endif
return addresses;
}
private static void LogResults(List results)
{
int missing = 0;
foreach (var r in results)
{
if (!r.ExistsInAddressables)
{
Debug.LogWarning($"[AddressKeyValidator] ⚠ 孤儿 Key:AddressKeys.{r.FieldName} = \"{r.Value}\" — 未在 Addressable 分组中找到对应地址。");
missing++;
}
}
if (missing == 0)
Debug.Log($"[AddressKeyValidator] ✓ 所有 {results.Count} 个 AddressKeys 常量均在 Addressable 分组中存在。");
else
Debug.LogWarning($"[AddressKeyValidator] 共 {results.Count} 个常量,发现 {missing} 个孤儿 Key。" +
$"尚未创建的 Prefab/Scene 资产请在创建后添加至 Addressables 分组。");
}
// ── 结果结构 ──────────────────────────────────────────────────────────
public readonly struct ValidationResult
{
public readonly string FieldName;
public readonly string Value;
public readonly bool ExistsInAddressables;
public ValidationResult(string fieldName, string value, bool exists)
{
FieldName = fieldName;
Value = value;
ExistsInAddressables = exists;
}
}
}
///
/// 资产导入后自动触发 AddressKey 验证(架构 13_AssetPoolModule §10)。
/// 仅在 Addressable Group 资产发生变更时触发,避免每次导入都验证。
///
public class AddressKeyImportWatcher : AssetPostprocessor
{
private const string AddressableGroupAssetExt = ".asset";
private const string AddressableGroupFolder = "Assets/AddressableAssetsData";
private static void OnPostprocessAllAssets(
string[] importedAssets,
string[] deletedAssets,
string[] movedAssets,
string[] movedFromAssetPaths)
{
bool addressablesChanged = false;
foreach (var path in importedAssets)
{
if (path.StartsWith(AddressableGroupFolder, StringComparison.OrdinalIgnoreCase)
&& path.EndsWith(AddressableGroupAssetExt, StringComparison.OrdinalIgnoreCase))
{
addressablesChanged = true;
break;
}
}
if (!addressablesChanged)
{
foreach (var path in deletedAssets)
{
if (path.StartsWith(AddressableGroupFolder, StringComparison.OrdinalIgnoreCase))
{
addressablesChanged = true;
break;
}
}
}
if (addressablesChanged)
{
// 延迟一帧执行,等待 AssetDatabase 完全刷新
EditorApplication.delayCall += () =>
{
Debug.Log("[AddressKeyImportWatcher] 检测到 Addressable 分组变更,自动触发 Key 验证...");
AddressKeyValidator.ValidateAll();
};
}
}
}
}