using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace BaseGames.Editor.Modules
{
///
/// DataHub 事件频道反查模块 —— 列出项目中所有 EventChannelSO 子类资产;
/// 选中后可扫描全项目中引用该频道的 Prefab / ScriptableObject,
/// 解决"谁发布/谁订阅了某条事件"在大型项目中难以追踪的问题。
///
/// 反查实现:遍历所有 Prefab 与 SO 资产,用 AssetDatabase.GetDependencies
/// 检查依赖链是否包含目标频道资产路径。首次扫描会耗时较长(取决于资产规模),
/// 之后按选中频道增量计算。
///
public class EventChannelModule : IDataModule, IDataModuleOrdered
{
public string ModuleId => "eventchannel";
public string DisplayName => "事件频道";
public string IconName => "d_AudioMixerController Icon";
public int DisplayOrder => 200;
private ListView _channelList;
private readonly List _allChannels = new();
private ScriptableObject _selected;
private VisualElement _refsContainer;
private struct EventChannelEntry
{
public string TypeName;
public string AssetPath;
public ScriptableObject Asset;
}
public void Initialize() => Refresh();
public void OnActivated() => Refresh();
private void Refresh()
{
_allChannels.Clear();
// 扫描所有 ScriptableObject 子类,筛选名称以 "EventChannelSO" 结尾的(项目约定)。
var types = TypeCache.GetTypesDerivedFrom();
foreach (var t in types)
{
if (t.IsAbstract) continue;
if (!t.Name.EndsWith("EventChannelSO", StringComparison.Ordinal)) continue;
var guids = AssetDatabase.FindAssets($"t:{t.Name}");
foreach (var g in guids)
{
var path = AssetDatabase.GUIDToAssetPath(g);
var asset = AssetDatabase.LoadAssetAtPath(path);
if (asset == null) continue;
_allChannels.Add(new EventChannelEntry
{
TypeName = t.Name,
AssetPath = path,
Asset = asset
});
}
}
_allChannels.Sort((a, b) =>
{
int c = string.Compare(a.TypeName, b.TypeName, StringComparison.Ordinal);
return c != 0 ? c : string.Compare(a.AssetPath, b.AssetPath, StringComparison.Ordinal);
});
_channelList?.Rebuild();
}
public void BuildListPane(VisualElement container, Action onSelected)
{
var header = new Label($"频道总数:{_allChannels.Count}");
header.style.unityFontStyleAndWeight = FontStyle.Bold;
header.style.paddingLeft = 6;
header.style.paddingTop = 4;
container.Add(header);
_channelList = new ListView(_allChannels, 20,
() => new Label { style = { paddingLeft = 6 } },
(ve, i) =>
{
var entry = _allChannels[i];
var label = (Label)ve;
label.text = $"[{entry.TypeName}] {System.IO.Path.GetFileNameWithoutExtension(entry.AssetPath)}";
label.tooltip = entry.AssetPath;
});
_channelList.style.flexGrow = 1;
_channelList.selectionChanged += sel =>
{
var first = sel.FirstOrDefault();
if (first is EventChannelEntry e)
{
_selected = e.Asset;
onSelected?.Invoke(e.Asset);
}
};
container.Add(_channelList);
}
public void BuildDetailPane(VisualElement container, UnityEngine.Object selected)
{
_selected = selected as ScriptableObject;
container.Clear();
if (_selected == null)
{
var hint = new Label("请在左侧选择一条频道资产。");
hint.style.paddingLeft = 8; hint.style.paddingTop = 8;
container.Add(hint);
return;
}
// Header
var title = new Label($"{_selected.GetType().Name} · {_selected.name}");
title.style.unityFontStyleAndWeight = FontStyle.Bold;
title.style.fontSize = 14;
title.style.paddingLeft = 8; title.style.paddingTop = 6;
container.Add(title);
var pathLabel = new Label(AssetDatabase.GetAssetPath(_selected))
{
style = { paddingLeft = 8, paddingBottom = 6, color = new StyleColor(new Color(0.7f, 0.7f, 0.7f)) }
};
container.Add(pathLabel);
var scanBtn = new Button(() => ScanReferences(_selected))
{ text = "扫描全项目引用(Prefab / ScriptableObject)" };
scanBtn.style.marginLeft = 8; scanBtn.style.marginRight = 8;
container.Add(scanBtn);
_refsContainer = new VisualElement();
_refsContainer.style.paddingLeft = 8; _refsContainer.style.paddingTop = 6;
container.Add(_refsContainer);
}
private void ScanReferences(ScriptableObject channel)
{
if (channel == null || _refsContainer == null) return;
_refsContainer.Clear();
var targetPath = AssetDatabase.GetAssetPath(channel);
if (string.IsNullOrEmpty(targetPath)) return;
var found = new List();
try
{
EditorUtility.DisplayProgressBar("扫描引用", "正在扫描 Prefab 与 SO …", 0f);
var prefabGuids = AssetDatabase.FindAssets("t:Prefab");
var soGuids = AssetDatabase.FindAssets("t:ScriptableObject");
var all = prefabGuids.Concat(soGuids).ToArray();
for (int i = 0; i < all.Length; i++)
{
var p = AssetDatabase.GUIDToAssetPath(all[i]);
if (p == targetPath) continue;
if (i % 64 == 0)
EditorUtility.DisplayProgressBar("扫描引用", p, (float)i / all.Length);
var deps = AssetDatabase.GetDependencies(p, recursive: false);
if (System.Array.IndexOf(deps, targetPath) >= 0)
found.Add(p);
}
}
finally { EditorUtility.ClearProgressBar(); }
var summary = new Label($"找到 {found.Count} 个直接引用:");
summary.style.unityFontStyleAndWeight = FontStyle.Bold;
_refsContainer.Add(summary);
foreach (var path in found)
{
var btn = new Button(() =>
{
var obj = AssetDatabase.LoadMainAssetAtPath(path);
EditorGUIUtility.PingObject(obj);
Selection.activeObject = obj;
})
{ text = path };
btn.style.unityTextAlign = TextAnchor.MiddleLeft;
btn.style.marginTop = 2;
_refsContainer.Add(btn);
}
}
}
}