feat: 事件复制
This commit is contained in:
11 files changed
+541
-111
No files matched your search
@@ -0,0 +1,15 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
本文件记录 `ShrinkEventBus` 在当前工作区中的包内变更。
|
||||||
|
|
||||||
|
## [1.1.5] - 2026-04-07
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- 修正 `PrepareForDispatch()`,现在只重置派发期状态,不再误清业务载荷。
|
||||||
|
- 修正同步 `TriggerEvent(...)` 遇到异步 handler 时的行为,改为向异步处理器分发脱离原对象的事件快照,避免直接复用原事件对象。
|
||||||
|
- `OnEventTriggered` 与编辑器追踪钩子改为在派发完成后触发,同时覆盖无监听者事件。
|
||||||
|
- `ShrinkNetworkEventBusBridge` 的异步转发改为先克隆事件,降低桥接层状态污染风险。
|
||||||
|
- 事件查看器的实时日志改为快照模型,支持关键词过滤、按“有监听者 / 无监听者”筛选、同类事件折叠聚合显示。
|
||||||
|
- 编辑器菜单迁移到 `ShrinkSDK/事件总线/事件查看器`。
|
||||||
|
- README 同步更新新的调试入口与同步/异步派发语义说明。
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9b2928da4a2426743bd762f5ebb9c232
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+352
-85
@@ -12,16 +12,61 @@ namespace ShrinkEventBus.Editor
|
|||||||
{
|
{
|
||||||
public class EventBusViewerWindow : EditorWindow
|
public class EventBusViewerWindow : EditorWindow
|
||||||
{
|
{
|
||||||
|
private enum LiveLogFilterMode
|
||||||
|
{
|
||||||
|
All,
|
||||||
|
HasListeners,
|
||||||
|
NoListeners
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class HandlerDisplayData
|
||||||
|
{
|
||||||
|
public object TargetInstance { get; set; }
|
||||||
|
public MonoBehaviour PingTarget { get; set; }
|
||||||
|
public string TargetName { get; set; }
|
||||||
|
public string MethodName { get; set; }
|
||||||
|
public object Priority { get; set; }
|
||||||
|
public string ExecType { get; set; }
|
||||||
|
public bool IsStaticHandler { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class EventLogRecord
|
||||||
|
{
|
||||||
|
public DateTime Timestamp { get; set; }
|
||||||
|
public string TimestampText { get; set; }
|
||||||
|
public string EventName { get; set; }
|
||||||
|
public string SenderInfo { get; set; }
|
||||||
|
public bool HasListeners { get; set; }
|
||||||
|
public bool IsCancelable { get; set; }
|
||||||
|
public bool IsCanceled { get; set; }
|
||||||
|
public string Result { get; set; }
|
||||||
|
public string Phase { get; set; }
|
||||||
|
public List<string> Parameters { get; set; }
|
||||||
|
public List<HandlerDisplayData> Handlers { get; set; }
|
||||||
|
public string MatchText { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class EventLogGroup
|
||||||
|
{
|
||||||
|
public string EventName { get; set; }
|
||||||
|
public List<EventLogRecord> Records { get; } = new();
|
||||||
|
public EventLogRecord LatestRecord => Records[0];
|
||||||
|
}
|
||||||
|
|
||||||
private ScrollView _subscribersScrollView;
|
private ScrollView _subscribersScrollView;
|
||||||
private ScrollView _liveTrackerScrollView;
|
private ScrollView _liveTrackerScrollView;
|
||||||
private VisualElement _subscribersTab;
|
private VisualElement _subscribersTab;
|
||||||
private VisualElement _liveTrackerTab;
|
private VisualElement _liveTrackerTab;
|
||||||
private Label _instanceCountLabel;
|
private Label _instanceCountLabel;
|
||||||
|
private Label _liveLogStatsLabel;
|
||||||
private string _searchString = "";
|
private string _searchString = "";
|
||||||
|
private string _liveLogSearchString = "";
|
||||||
|
private LiveLogFilterMode _liveLogFilterMode = LiveLogFilterMode.All;
|
||||||
|
private bool _collapseSameEventType = true;
|
||||||
private const int MaxLogCount = 100;
|
private const int MaxLogCount = 100;
|
||||||
private readonly List<VisualElement> _logElements = new();
|
private readonly List<EventLogRecord> _logRecords = new();
|
||||||
|
|
||||||
[MenuItem("Window/Shrink EventBus/事件查看器")]
|
[MenuItem("ShrinkSDK/事件总线/事件查看器")]
|
||||||
public static void ShowWindow()
|
public static void ShowWindow()
|
||||||
{
|
{
|
||||||
var window = GetWindow<EventBusViewerWindow>("EventBus 控制台");
|
var window = GetWindow<EventBusViewerWindow>("EventBus 控制台");
|
||||||
@@ -117,10 +162,65 @@ namespace ShrinkEventBus.Editor
|
|||||||
{
|
{
|
||||||
var container = new VisualElement { style = { flexGrow = 1 } };
|
var container = new VisualElement { style = { flexGrow = 1 } };
|
||||||
var toolbar = new Toolbar();
|
var toolbar = new Toolbar();
|
||||||
|
var searchField = new ToolbarSearchField { style = { flexGrow = 1 } };
|
||||||
|
searchField.RegisterValueChangedCallback(evt =>
|
||||||
|
{
|
||||||
|
_liveLogSearchString = evt.newValue ?? string.Empty;
|
||||||
|
RefreshLiveTrackerView();
|
||||||
|
});
|
||||||
|
toolbar.Add(searchField);
|
||||||
|
|
||||||
|
var filterMenu = new ToolbarMenu { text = "全部事件" };
|
||||||
|
filterMenu.menu.AppendAction("全部事件", _ =>
|
||||||
|
{
|
||||||
|
_liveLogFilterMode = LiveLogFilterMode.All;
|
||||||
|
filterMenu.text = "全部事件";
|
||||||
|
RefreshLiveTrackerView();
|
||||||
|
});
|
||||||
|
filterMenu.menu.AppendAction("仅有监听者", _ =>
|
||||||
|
{
|
||||||
|
_liveLogFilterMode = LiveLogFilterMode.HasListeners;
|
||||||
|
filterMenu.text = "仅有监听者";
|
||||||
|
RefreshLiveTrackerView();
|
||||||
|
});
|
||||||
|
filterMenu.menu.AppendAction("仅无监听者", _ =>
|
||||||
|
{
|
||||||
|
_liveLogFilterMode = LiveLogFilterMode.NoListeners;
|
||||||
|
filterMenu.text = "仅无监听者";
|
||||||
|
RefreshLiveTrackerView();
|
||||||
|
});
|
||||||
|
toolbar.Add(filterMenu);
|
||||||
|
|
||||||
|
var collapseToggle = new ToolbarToggle
|
||||||
|
{
|
||||||
|
text = "折叠同类事件",
|
||||||
|
value = _collapseSameEventType
|
||||||
|
};
|
||||||
|
collapseToggle.RegisterValueChangedCallback(evt =>
|
||||||
|
{
|
||||||
|
_collapseSameEventType = evt.newValue;
|
||||||
|
RefreshLiveTrackerView();
|
||||||
|
});
|
||||||
|
toolbar.Add(collapseToggle);
|
||||||
toolbar.Add(new ToolbarButton(ClearLogs) { text = "清空日志" });
|
toolbar.Add(new ToolbarButton(ClearLogs) { text = "清空日志" });
|
||||||
container.Add(toolbar);
|
container.Add(toolbar);
|
||||||
|
|
||||||
|
_liveLogStatsLabel = new Label
|
||||||
|
{
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
marginLeft = 8,
|
||||||
|
marginRight = 8,
|
||||||
|
marginTop = 4,
|
||||||
|
marginBottom = 4,
|
||||||
|
color = new StyleColor(Color.gray)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
container.Add(_liveLogStatsLabel);
|
||||||
|
|
||||||
_liveTrackerScrollView = new ScrollView { style = { flexGrow = 1, paddingLeft = 5, paddingRight = 5 } };
|
_liveTrackerScrollView = new ScrollView { style = { flexGrow = 1, paddingLeft = 5, paddingRight = 5 } };
|
||||||
container.Add(_liveTrackerScrollView);
|
container.Add(_liveTrackerScrollView);
|
||||||
|
RefreshLiveTrackerView();
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,76 +276,232 @@ namespace ShrinkEventBus.Editor
|
|||||||
private void RecordEventLog(EventBase evt, string eventName, string senderInfo)
|
private void RecordEventLog(EventBase evt, string eventName, string senderInfo)
|
||||||
{
|
{
|
||||||
if (!EventBus.EnableDebugRecord) return;
|
if (!EventBus.EnableDebugRecord) return;
|
||||||
|
_logRecords.Insert(0, CreateEventLogRecord(evt, eventName, senderInfo));
|
||||||
|
if (_logRecords.Count > MaxLogCount)
|
||||||
|
_logRecords.RemoveAt(_logRecords.Count - 1);
|
||||||
|
RefreshLiveTrackerView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private EventLogRecord CreateEventLogRecord(EventBase evt, string eventName, string senderInfo)
|
||||||
|
{
|
||||||
|
var timestamp = DateTime.Now;
|
||||||
var list = evt.GetListenerList();
|
var list = evt.GetListenerList();
|
||||||
var hasListeners = list != null && list.Count > 0;
|
var handlers = list?.GetHandlers() ?? Array.Empty<EventHandlerInfo>();
|
||||||
|
var hasListeners = handlers.Length > 0;
|
||||||
var logEntry = new VisualElement
|
var paramsList = DumpEventParams(evt);
|
||||||
{
|
var handlerRows = new List<HandlerDisplayData>(handlers.Length);
|
||||||
style =
|
foreach (var handler in handlers)
|
||||||
{
|
handlerRows.Add(ExtractHandlerDisplayData(handler));
|
||||||
flexDirection = FlexDirection.Column, paddingBottom = 4, paddingTop = 4, borderBottomWidth = 1,
|
|
||||||
borderBottomColor = new StyleColor(new Color(0.15f, 0.15f, 0.15f))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var headerRow = new VisualElement
|
|
||||||
{ style = { flexDirection = FlexDirection.Row, alignItems = Align.Center } };
|
|
||||||
headerRow.Add(new Label(DateTime.Now.ToString("HH:mm:ss.fff"))
|
|
||||||
{ style = { width = 80, color = new StyleColor(Color.gray) } });
|
|
||||||
headerRow.Add(new Label(eventName)
|
|
||||||
{
|
|
||||||
style = { color = new StyleColor(new Color(1f, 0.84f, 0f)), unityFontStyleAndWeight = FontStyle.Bold }
|
|
||||||
});
|
|
||||||
|
|
||||||
var senderClassName = senderInfo.Contains(".") ? senderInfo.Split('.')[0] : senderInfo;
|
|
||||||
var senderLabel = new Label($" [由 {senderInfo} 触发]")
|
|
||||||
{
|
|
||||||
style =
|
|
||||||
{
|
|
||||||
color = new StyleColor(new Color(0.5f, 0.8f, 1f)), marginLeft = 5,
|
|
||||||
unityFontStyleAndWeight = FontStyle.Italic
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (senderClassName != "未知来源" && senderClassName != "EventBus")
|
|
||||||
{
|
|
||||||
senderLabel.RegisterCallback<MouseEnterEvent>(e =>
|
|
||||||
senderLabel.style.color = new StyleColor(Color.cyan));
|
|
||||||
senderLabel.RegisterCallback<MouseLeaveEvent>(e =>
|
|
||||||
senderLabel.style.color = new StyleColor(new Color(0.5f, 0.8f, 1f)));
|
|
||||||
senderLabel.RegisterCallback<ClickEvent>(e => OpenScriptByClassName(senderClassName));
|
|
||||||
}
|
|
||||||
|
|
||||||
headerRow.Add(senderLabel);
|
|
||||||
logEntry.Add(headerRow);
|
|
||||||
|
|
||||||
var isCancelable = (GetMemberValue(evt, "IsCancelable") as bool?) ?? false;
|
var isCancelable = (GetMemberValue(evt, "IsCancelable") as bool?) ?? false;
|
||||||
var isCanceled = (GetMemberValue(evt, "IsCanceled") as bool?) ?? false;
|
var isCanceled = (GetMemberValue(evt, "IsCanceled") as bool?) ?? false;
|
||||||
var resultStr = GetMemberValue(evt, "Result")?.ToString() ?? "DEFAULT";
|
var resultStr = GetMemberValue(evt, "Result")?.ToString() ?? "DEFAULT";
|
||||||
var phaseStr = GetMemberValue(evt, "Phase")?.ToString() ?? "NORMAL";
|
var phaseStr = GetMemberValue(evt, "Phase")?.ToString() ?? "NORMAL";
|
||||||
|
|
||||||
|
return new EventLogRecord
|
||||||
|
{
|
||||||
|
Timestamp = timestamp,
|
||||||
|
TimestampText = timestamp.ToString("HH:mm:ss.fff"),
|
||||||
|
EventName = eventName,
|
||||||
|
SenderInfo = senderInfo,
|
||||||
|
HasListeners = hasListeners,
|
||||||
|
IsCancelable = isCancelable,
|
||||||
|
IsCanceled = isCanceled,
|
||||||
|
Result = resultStr,
|
||||||
|
Phase = phaseStr,
|
||||||
|
Parameters = paramsList,
|
||||||
|
Handlers = handlerRows,
|
||||||
|
MatchText = BuildLogMatchText(eventName, senderInfo, paramsList)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildLogMatchText(string eventName, string senderInfo, List<string> paramsList)
|
||||||
|
{
|
||||||
|
if (paramsList.Count == 0)
|
||||||
|
return $"{eventName}\n{senderInfo}";
|
||||||
|
return $"{eventName}\n{senderInfo}\n{string.Join("\n", paramsList)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshLiveTrackerView()
|
||||||
|
{
|
||||||
|
if (_liveTrackerScrollView == null) return;
|
||||||
|
|
||||||
|
_liveTrackerScrollView.Clear();
|
||||||
|
|
||||||
|
var filteredRecords = new List<EventLogRecord>();
|
||||||
|
foreach (var record in _logRecords)
|
||||||
|
{
|
||||||
|
if (MatchesLiveLogFilter(record))
|
||||||
|
filteredRecords.Add(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_liveLogStatsLabel != null)
|
||||||
|
{
|
||||||
|
_liveLogStatsLabel.text = _collapseSameEventType
|
||||||
|
? $"显示 {filteredRecords.Count} / {_logRecords.Count} 条,按事件类型折叠"
|
||||||
|
: $"显示 {filteredRecords.Count} / {_logRecords.Count} 条";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filteredRecords.Count == 0)
|
||||||
|
{
|
||||||
|
_liveTrackerScrollView.Add(new HelpBox("当前没有符合条件的实时事件日志。", HelpBoxMessageType.Info));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_collapseSameEventType)
|
||||||
|
{
|
||||||
|
foreach (var group in BuildCollapsedGroups(filteredRecords))
|
||||||
|
_liveTrackerScrollView.Add(CreateCollapsedGroupElement(group));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var record in filteredRecords)
|
||||||
|
_liveTrackerScrollView.Add(CreateLogEntryElement(record));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool MatchesLiveLogFilter(EventLogRecord record)
|
||||||
|
{
|
||||||
|
switch (_liveLogFilterMode)
|
||||||
|
{
|
||||||
|
case LiveLogFilterMode.HasListeners:
|
||||||
|
if (!record.HasListeners) return false;
|
||||||
|
break;
|
||||||
|
case LiveLogFilterMode.NoListeners:
|
||||||
|
if (record.HasListeners) return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(_liveLogSearchString))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return record.MatchText.IndexOf(_liveLogSearchString.Trim(), StringComparison.OrdinalIgnoreCase) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<EventLogGroup> BuildCollapsedGroups(List<EventLogRecord> records)
|
||||||
|
{
|
||||||
|
var groups = new List<EventLogGroup>();
|
||||||
|
var groupMap = new Dictionary<string, EventLogGroup>(StringComparer.Ordinal);
|
||||||
|
|
||||||
|
foreach (var record in records)
|
||||||
|
{
|
||||||
|
if (!groupMap.TryGetValue(record.EventName, out var group))
|
||||||
|
{
|
||||||
|
group = new EventLogGroup { EventName = record.EventName };
|
||||||
|
groupMap[record.EventName] = group;
|
||||||
|
groups.Add(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
group.Records.Add(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
private VisualElement CreateCollapsedGroupElement(EventLogGroup group)
|
||||||
|
{
|
||||||
|
var latest = group.LatestRecord;
|
||||||
|
var foldout = new Foldout
|
||||||
|
{
|
||||||
|
text = $"{group.EventName} × {group.Records.Count} 最近: {latest.TimestampText}",
|
||||||
|
value = group.Records.Count == 1,
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
backgroundColor = new StyleColor(new Color(0.2f, 0.2f, 0.2f, 0.4f)),
|
||||||
|
marginTop = 2,
|
||||||
|
paddingBottom = 2,
|
||||||
|
borderBottomWidth = 1,
|
||||||
|
borderBottomColor = new StyleColor(new Color(0.1f, 0.1f, 0.1f))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var summary = new Label(
|
||||||
|
$"最近来源: {latest.SenderInfo} | 监听者: {(latest.HasListeners ? latest.Handlers.Count : 0)} | 已取消: {(latest.IsCanceled ? "是" : "否")}")
|
||||||
|
{
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
marginLeft = 18,
|
||||||
|
marginBottom = 4,
|
||||||
|
color = new StyleColor(Color.gray)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
foldout.Add(summary);
|
||||||
|
|
||||||
|
foreach (var record in group.Records)
|
||||||
|
{
|
||||||
|
var row = CreateLogEntryElement(record);
|
||||||
|
row.style.marginLeft = 12;
|
||||||
|
foldout.Add(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return foldout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private VisualElement CreateLogEntryElement(EventLogRecord record)
|
||||||
|
{
|
||||||
|
var logEntry = new VisualElement
|
||||||
|
{
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
flexDirection = FlexDirection.Column,
|
||||||
|
paddingBottom = 4,
|
||||||
|
paddingTop = 4,
|
||||||
|
borderBottomWidth = 1,
|
||||||
|
borderBottomColor = new StyleColor(new Color(0.15f, 0.15f, 0.15f))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var headerRow = new VisualElement
|
||||||
|
{ style = { flexDirection = FlexDirection.Row, alignItems = Align.Center } };
|
||||||
|
headerRow.Add(new Label(record.TimestampText)
|
||||||
|
{ style = { width = 80, color = new StyleColor(Color.gray) } });
|
||||||
|
headerRow.Add(new Label(record.EventName)
|
||||||
|
{
|
||||||
|
style = { color = new StyleColor(new Color(1f, 0.84f, 0f)), unityFontStyleAndWeight = FontStyle.Bold }
|
||||||
|
});
|
||||||
|
|
||||||
|
var senderClassName = GetSenderClassName(record.SenderInfo);
|
||||||
|
var senderLabel = new Label($" [由 {record.SenderInfo} 触发]")
|
||||||
|
{
|
||||||
|
style =
|
||||||
|
{
|
||||||
|
color = new StyleColor(new Color(0.5f, 0.8f, 1f)),
|
||||||
|
marginLeft = 5,
|
||||||
|
unityFontStyleAndWeight = FontStyle.Italic
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (senderClassName != "未知来源" && senderClassName != "EventBus" && senderClassName != "Unknown")
|
||||||
|
{
|
||||||
|
senderLabel.RegisterCallback<MouseEnterEvent>(_ =>
|
||||||
|
senderLabel.style.color = new StyleColor(Color.cyan));
|
||||||
|
senderLabel.RegisterCallback<MouseLeaveEvent>(_ =>
|
||||||
|
senderLabel.style.color = new StyleColor(new Color(0.5f, 0.8f, 1f)));
|
||||||
|
senderLabel.RegisterCallback<ClickEvent>(_ => OpenScriptByClassName(senderClassName));
|
||||||
|
}
|
||||||
|
|
||||||
|
headerRow.Add(senderLabel);
|
||||||
|
logEntry.Add(headerRow);
|
||||||
|
|
||||||
var statusRow = new VisualElement
|
var statusRow = new VisualElement
|
||||||
{ style = { flexDirection = FlexDirection.Row, marginLeft = 80, marginTop = 2 } };
|
{ style = { flexDirection = FlexDirection.Row, marginLeft = 80, marginTop = 2 } };
|
||||||
statusRow.Add(new Label($"可取消: {(isCancelable ? "是" : "否")}")
|
statusRow.Add(new Label($"可取消: {(record.IsCancelable ? "是" : "否")}")
|
||||||
{ style = { color = new StyleColor(Color.gray), marginRight = 15 } });
|
{ style = { color = new StyleColor(Color.gray), marginRight = 15 } });
|
||||||
|
|
||||||
var canceledLabel = new Label($"已取消: {(isCanceled ? "是" : "否")}") { style = { marginRight = 15 } };
|
var canceledLabel = new Label($"已取消: {(record.IsCanceled ? "是" : "否")}")
|
||||||
|
{ style = { marginRight = 15 } };
|
||||||
canceledLabel.style.color =
|
canceledLabel.style.color =
|
||||||
isCanceled ? new StyleColor(new Color(1f, 0.3f, 0.3f)) : new StyleColor(Color.gray);
|
record.IsCanceled ? new StyleColor(new Color(1f, 0.3f, 0.3f)) : new StyleColor(Color.gray);
|
||||||
statusRow.Add(canceledLabel);
|
statusRow.Add(canceledLabel);
|
||||||
|
|
||||||
statusRow.Add(new Label($"Result: {resultStr}")
|
statusRow.Add(new Label($"Result: {record.Result}")
|
||||||
{ style = { color = new StyleColor(new Color(0.8f, 0.6f, 0.8f)), marginRight = 15 } });
|
{ style = { color = new StyleColor(new Color(0.8f, 0.6f, 0.8f)), marginRight = 15 } });
|
||||||
statusRow.Add(new Label($"最后阶段: {phaseStr}")
|
statusRow.Add(new Label($"最后阶段: {record.Phase}")
|
||||||
{ style = { color = new StyleColor(new Color(0.6f, 0.8f, 1f)) } });
|
{ style = { color = new StyleColor(new Color(0.6f, 0.8f, 1f)) } });
|
||||||
logEntry.Add(statusRow);
|
logEntry.Add(statusRow);
|
||||||
|
|
||||||
var paramsContainer = new VisualElement
|
var paramsContainer = new VisualElement
|
||||||
{ style = { flexDirection = FlexDirection.Column, marginLeft = 80, marginTop = 2, marginBottom = 2 } };
|
{ style = { flexDirection = FlexDirection.Column, marginLeft = 80, marginTop = 2, marginBottom = 2 } };
|
||||||
var paramsList = DumpEventParams(evt);
|
if (record.Parameters.Count == 0)
|
||||||
|
|
||||||
if (paramsList.Count == 0)
|
|
||||||
{
|
{
|
||||||
paramsContainer.Add(new Label("参数: { 无 }")
|
paramsContainer.Add(new Label("参数: { 无 }")
|
||||||
{ style = { color = new StyleColor(new Color(0.5f, 0.5f, 0.5f)) } });
|
{ style = { color = new StyleColor(new Color(0.5f, 0.5f, 0.5f)) } });
|
||||||
@@ -254,24 +510,17 @@ namespace ShrinkEventBus.Editor
|
|||||||
{
|
{
|
||||||
paramsContainer.Add(
|
paramsContainer.Add(
|
||||||
new Label("携带参数:") { style = { color = new StyleColor(new Color(0.6f, 0.8f, 1f)) } });
|
new Label("携带参数:") { style = { color = new StyleColor(new Color(0.6f, 0.8f, 1f)) } });
|
||||||
foreach (var pStr in paramsList)
|
foreach (var pStr in record.Parameters)
|
||||||
paramsContainer.Add(new Label($" • {pStr}")
|
paramsContainer.Add(new Label($" • {pStr}")
|
||||||
{ style = { color = new StyleColor(new Color(0.6f, 0.9f, 0.6f)), marginLeft = 10 } });
|
{ style = { color = new StyleColor(new Color(0.6f, 0.9f, 0.6f)), marginLeft = 10 } });
|
||||||
}
|
}
|
||||||
|
|
||||||
logEntry.Add(paramsContainer);
|
logEntry.Add(paramsContainer);
|
||||||
|
|
||||||
if (hasListeners)
|
if (record.HasListeners)
|
||||||
{
|
{
|
||||||
if (list.GetType().GetMethod("GetHandlers")?.Invoke(list, null) is Array handlers)
|
foreach (var handler in record.Handlers)
|
||||||
{
|
logEntry.Add(CreateHandlerRow(handler, 80));
|
||||||
foreach (var h in handlers)
|
|
||||||
{
|
|
||||||
var row = CreateHandlerRow(h);
|
|
||||||
row.style.marginLeft = 80;
|
|
||||||
logEntry.Add(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -281,26 +530,34 @@ namespace ShrinkEventBus.Editor
|
|||||||
logEntry.Add(noHandlerRow);
|
logEntry.Add(noHandlerRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
_liveTrackerScrollView.Insert(0, logEntry);
|
return logEntry;
|
||||||
_logElements.Insert(0, logEntry);
|
}
|
||||||
|
|
||||||
if (_logElements.Count > MaxLogCount)
|
private string GetSenderClassName(string senderInfo)
|
||||||
{
|
{
|
||||||
_liveTrackerScrollView.Remove(_logElements[^1]);
|
if (string.IsNullOrWhiteSpace(senderInfo))
|
||||||
_logElements.RemoveAt(_logElements.Count - 1);
|
return "未知来源";
|
||||||
}
|
return senderInfo.Contains(".") ? senderInfo.Split('.')[0] : senderInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private VisualElement CreateHandlerRow(object h)
|
private VisualElement CreateHandlerRow(object h)
|
||||||
{
|
{
|
||||||
ParseHandlerData(h, out var targetInstance, out var targetName, out var methodName, out var priority,
|
return CreateHandlerRow(ExtractHandlerDisplayData(h), 40);
|
||||||
out var execType);
|
}
|
||||||
|
|
||||||
|
private VisualElement CreateHandlerRow(HandlerDisplayData handlerData, float marginLeft)
|
||||||
|
{
|
||||||
|
var targetInstance = handlerData.TargetInstance;
|
||||||
|
var targetName = handlerData.TargetName;
|
||||||
|
var methodName = handlerData.MethodName;
|
||||||
|
var priority = handlerData.Priority;
|
||||||
|
var execType = handlerData.ExecType;
|
||||||
|
|
||||||
var row = new VisualElement
|
var row = new VisualElement
|
||||||
{
|
{
|
||||||
style =
|
style =
|
||||||
{
|
{
|
||||||
flexDirection = FlexDirection.Row, marginTop = 2, marginBottom = 2, marginLeft = 40,
|
flexDirection = FlexDirection.Row, marginTop = 2, marginBottom = 2, marginLeft = marginLeft,
|
||||||
alignItems = Align.Center
|
alignItems = Align.Center
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -318,16 +575,16 @@ namespace ShrinkEventBus.Editor
|
|||||||
linkLabel.style.flexGrow = 1;
|
linkLabel.style.flexGrow = 1;
|
||||||
row.Add(linkLabel);
|
row.Add(linkLabel);
|
||||||
|
|
||||||
if (targetInstance is MonoBehaviour mb && mb != null)
|
if (handlerData.PingTarget)
|
||||||
{
|
{
|
||||||
var pingBtn = new Button(() =>
|
var pingBtn = new Button(() =>
|
||||||
{
|
{
|
||||||
EditorGUIUtility.PingObject(mb.gameObject);
|
EditorGUIUtility.PingObject(handlerData.PingTarget.gameObject);
|
||||||
Selection.activeGameObject = mb.gameObject;
|
Selection.activeGameObject = handlerData.PingTarget.gameObject;
|
||||||
}) { text = "Ping", style = { width = 50, height = 18 } };
|
}) { text = "Ping", style = { width = 50, height = 18 } };
|
||||||
row.Add(pingBtn);
|
row.Add(pingBtn);
|
||||||
}
|
}
|
||||||
else if (targetInstance == null && methodName != "未知方法")
|
else if (handlerData.IsStaticHandler && methodName != "未知方法")
|
||||||
row.Add(new Label("(静态)")
|
row.Add(new Label("(静态)")
|
||||||
{
|
{
|
||||||
style = { width = 50, color = new StyleColor(Color.gray), unityTextAlign = TextAnchor.MiddleRight }
|
style = { width = 50, color = new StyleColor(Color.gray), unityTextAlign = TextAnchor.MiddleRight }
|
||||||
@@ -341,19 +598,18 @@ namespace ShrinkEventBus.Editor
|
|||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ParseHandlerData(object handlerObj, out object targetInstance, out string targetName,
|
private HandlerDisplayData ExtractHandlerDisplayData(object handlerObj)
|
||||||
out string methodName, out object priority, out string execType)
|
|
||||||
{
|
{
|
||||||
priority = GetMemberValue(handlerObj, "Priority") ?? "NORMAL";
|
var priority = GetMemberValue(handlerObj, "Priority") ?? "NORMAL";
|
||||||
var debugInfo = GetMemberValue(handlerObj, "DebugInfo") as string ?? "未知方法";
|
var debugInfo = GetMemberValue(handlerObj, "DebugInfo") as string ?? "未知方法";
|
||||||
|
|
||||||
execType = "Sync";
|
var execType = "Sync";
|
||||||
if (debugInfo.Contains("(UniTask)") || debugInfo.Contains("Async")) execType = "Async";
|
if (debugInfo.Contains("(UniTask)") || debugInfo.Contains("Async")) execType = "Async";
|
||||||
else if (debugInfo.Contains("(Task->UniTask)") || debugInfo.Contains("(Task)")) execType = "Task";
|
else if (debugInfo.Contains("(Task->UniTask)") || debugInfo.Contains("(Task)")) execType = "Task";
|
||||||
|
|
||||||
targetInstance = null;
|
object targetInstance = null;
|
||||||
targetName = "未知类";
|
var targetName = "未知类";
|
||||||
methodName = debugInfo;
|
var methodName = debugInfo;
|
||||||
|
|
||||||
var handlerDelegate = GetMemberValue(handlerObj, "Handler") as Delegate;
|
var handlerDelegate = GetMemberValue(handlerObj, "Handler") as Delegate;
|
||||||
if (handlerDelegate != null)
|
if (handlerDelegate != null)
|
||||||
@@ -379,6 +635,17 @@ namespace ShrinkEventBus.Editor
|
|||||||
methodName = methodInfo.Name;
|
methodName = methodInfo.Name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new HandlerDisplayData
|
||||||
|
{
|
||||||
|
TargetInstance = targetInstance,
|
||||||
|
PingTarget = targetInstance as MonoBehaviour,
|
||||||
|
TargetName = targetName,
|
||||||
|
MethodName = methodName,
|
||||||
|
Priority = priority,
|
||||||
|
ExecType = execType,
|
||||||
|
IsStaticHandler = targetInstance == null
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<string> DumpEventParams(EventBase evt)
|
private List<string> DumpEventParams(EventBase evt)
|
||||||
@@ -446,8 +713,8 @@ namespace ShrinkEventBus.Editor
|
|||||||
|
|
||||||
private void ClearLogs()
|
private void ClearLogs()
|
||||||
{
|
{
|
||||||
_liveTrackerScrollView.Clear();
|
_logRecords.Clear();
|
||||||
_logElements.Clear();
|
RefreshLiveTrackerView();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenScriptByClassName(string className)
|
private void OpenScriptByClassName(string className)
|
||||||
|
|||||||
@@ -122,12 +122,14 @@ EventBus.TriggerEvent(evt);
|
|||||||
|
|
||||||
## 🖼️ 追踪图形化
|
## 🖼️ 追踪图形化
|
||||||
|
|
||||||
菜单栏 → `Window(窗口)` → `Shrink EventBus` → `事件查看器`
|
菜单栏 → `ShrinkSDK` → `事件总线` → `事件查看器`
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
实时触发日志页现已支持关键词过滤、按“有监听者 / 无监听者”筛选,以及“折叠同类事件”聚合查看,适合排查高频事件刷屏场景。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📖 核心概念
|
## 📖 核心概念
|
||||||
@@ -292,14 +294,14 @@ EventBus.UnregisterAllEvents(); // 全部清空(谨慎
|
|||||||
#### 触发
|
#### 触发
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// 同步触发:async handler 会以 .Forget() 方式触发,不等待
|
// 同步触发:只同步等待 sync handler;async handler 会基于事件快照 fire-and-forget
|
||||||
bool handled = EventBus.TriggerEvent<TEvent>(TEvent eventArgs);
|
bool handled = EventBus.TriggerEvent<TEvent>(TEvent eventArgs);
|
||||||
|
|
||||||
// 异步触发:顺序 await 每个 handler
|
// 异步触发:顺序 await 每个 handler
|
||||||
bool handled = await EventBus.TriggerEventAsync<TEvent>(TEvent eventArgs);
|
bool handled = await EventBus.TriggerEventAsync<TEvent>(TEvent eventArgs);
|
||||||
```
|
```
|
||||||
|
|
||||||
> ⚠️ `TriggerEvent` 中遇到 async handler 时会调用 `.Forget()`,不会等待其完成。如果需要顺序等待,请使用 `TriggerEventAsync`。
|
> ⚠️ `TriggerEvent` 中遇到 async handler 时,不会等待其完成,而是对当前事件做一份快照后异步执行。如果你需要让 async handler 参与最终状态(如 `IsCanceled` / `Result` / 后续字段改写),请使用 `TriggerEventAsync`。
|
||||||
|
|
||||||
#### 查询
|
#### 查询
|
||||||
|
|
||||||
@@ -375,7 +377,7 @@ TriggerEvent(evt)
|
|||||||
└─ 遍历 handlers[]
|
└─ 遍历 handlers[]
|
||||||
├─ 跳过已取消 & 不接收取消的 handler
|
├─ 跳过已取消 & 不接收取消的 handler
|
||||||
├─ Action<T> → 直接调用
|
├─ Action<T> → 直接调用
|
||||||
└─ Func<T, UniTask> → .Forget()(同步路径)
|
└─ Func<T, UniTask> → 基于事件快照 .Forget()(同步路径)
|
||||||
```
|
```
|
||||||
|
|
||||||
**自动注册完整流程:**
|
**自动注册完整流程:**
|
||||||
@@ -452,7 +454,8 @@ public void Dispose()
|
|||||||
|
|
||||||
## ⚠️ 注意事项
|
## ⚠️ 注意事项
|
||||||
|
|
||||||
- **`TriggerEvent` 不等待异步 handler**:同步路径中的 UniTask handler 以 `.Forget()` 触发,执行结果和异常不会传回调用方。需要等待时请使用 `TriggerEventAsync`。
|
- **`TriggerEvent` 不等待异步 handler**:同步路径中的 UniTask handler 会基于事件快照异步执行,执行结果和异常不会传回调用方,对原事件对象的改动也不会回写。需要等待并拿到最终状态时请使用 `TriggerEventAsync`。
|
||||||
|
- **同步路径中的 async 快照是浅拷贝**:事件对象本身会复制一份,但如果载荷里挂着可变引用类型(如 `List<>`、`Dictionary<>`、自定义引用对象),内部成员仍然是共享引用。高风险数据建议改成不可变载荷,或统一走 `TriggerEventAsync`。
|
||||||
- **EventPool 归还后不要再使用**:`Release` 后对象会立即 `ResetInternal()`,继续访问属性将得到默认值。
|
- **EventPool 归还后不要再使用**:`Release` 后对象会立即 `ResetInternal()`,继续访问属性将得到默认值。
|
||||||
- **不要在 handler 内直接注册/注销 handler**:可能影响当前正在遍历的 handler 快照,会产生语义上的不确定性。
|
- **不要在 handler 内直接注册/注销 handler**:可能影响当前正在遍历的 handler 快照,会产生语义上的不确定性。
|
||||||
- **静态 handler 永远不会自动注销**:静态方法注册后持续存活直到显式调用 `UnregisterEvent`,不要在静态 handler 里持有场景对象引用。
|
- **静态 handler 永远不会自动注销**:静态方法注册后持续存活直到显式调用 `UnregisterEvent`,不要在静态 handler 里持有场景对象引用。
|
||||||
@@ -488,7 +491,7 @@ foreach (var h in handlers)
|
|||||||
|
|
||||||
**Editor 下想追踪事件流**
|
**Editor 下想追踪事件流**
|
||||||
|
|
||||||
打开事件查看器:菜单栏 → `Window` → `Shrink EventBus` → `事件查看器`
|
打开事件查看器:菜单栏 → `ShrinkSDK` → `事件总线` → `事件查看器`
|
||||||
|
|
||||||
也可以通过代码追踪:
|
也可以通过代码追踪:
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("ShrinkNetwork.Integration.EventBus")]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 43e85f78c24f1d94ba7d06a4fa37e937
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+23
-1
@@ -1,6 +1,7 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
namespace ShrinkEventBus
|
namespace ShrinkEventBus
|
||||||
{
|
{
|
||||||
@@ -10,16 +11,26 @@ namespace ShrinkEventBus
|
|||||||
private bool _isCanceled;
|
private bool _isCanceled;
|
||||||
private EventResult _result = EventResult.DEFAULT;
|
private EventResult _result = EventResult.DEFAULT;
|
||||||
|
|
||||||
|
[IgnoreDataMember]
|
||||||
public EventHandlerInfo? CurrentHandler { get; internal set; }
|
public EventHandlerInfo? CurrentHandler { get; internal set; }
|
||||||
|
|
||||||
|
[IgnoreDataMember]
|
||||||
public DateTime EventTime { get; private set; } = DateTime.UtcNow;
|
public DateTime EventTime { get; private set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
[IgnoreDataMember]
|
||||||
public Guid EventId { get; private set; } = Guid.NewGuid();
|
public Guid EventId { get; private set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
[IgnoreDataMember]
|
||||||
public bool IsCancelable { get; }
|
public bool IsCancelable { get; }
|
||||||
|
|
||||||
|
[IgnoreDataMember]
|
||||||
public bool HasResult { get; }
|
public bool HasResult { get; }
|
||||||
|
|
||||||
|
[IgnoreDataMember]
|
||||||
public EventPriority? Phase { get; private set; }
|
public EventPriority? Phase { get; private set; }
|
||||||
|
|
||||||
internal bool IsInPool { get; set; }
|
internal bool IsInPool { get; set; }
|
||||||
internal Action<EventBase> ReleaseAction { get; set; }
|
internal Action<EventBase>? ReleaseAction { get; set; }
|
||||||
|
|
||||||
protected EventBase()
|
protected EventBase()
|
||||||
{
|
{
|
||||||
@@ -44,6 +55,16 @@ namespace ShrinkEventBus
|
|||||||
OnReset();
|
OnReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void PrepareForDispatch()
|
||||||
|
{
|
||||||
|
CurrentHandler = null;
|
||||||
|
Phase = null;
|
||||||
|
EventTime = DateTime.UtcNow;
|
||||||
|
EventId = Guid.NewGuid();
|
||||||
|
_listenerList.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
[IgnoreDataMember]
|
||||||
public bool IsCanceled
|
public bool IsCanceled
|
||||||
{
|
{
|
||||||
get => _isCanceled;
|
get => _isCanceled;
|
||||||
@@ -54,6 +75,7 @@ namespace ShrinkEventBus
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[IgnoreDataMember]
|
||||||
public EventResult Result
|
public EventResult Result
|
||||||
{
|
{
|
||||||
get => _result;
|
get => _result;
|
||||||
|
|||||||
+24
-18
@@ -9,6 +9,8 @@ namespace ShrinkEventBus
|
|||||||
{
|
{
|
||||||
public static class EventBus
|
public static class EventBus
|
||||||
{
|
{
|
||||||
|
public static event Action<EventBase, Type> OnEventTriggered;
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
public static bool EnableDebugRecord { get; set; }
|
public static bool EnableDebugRecord { get; set; }
|
||||||
public static event Action<EventBase, string, string> OnEventTriggeredForEditor;
|
public static event Action<EventBase, string, string> OnEventTriggeredForEditor;
|
||||||
@@ -189,16 +191,11 @@ namespace ShrinkEventBus
|
|||||||
public static async UniTask<bool> TriggerEventAsync<TEvent>(TEvent eventArgs) where TEvent : EventBase
|
public static async UniTask<bool> TriggerEventAsync<TEvent>(TEvent eventArgs) where TEvent : EventBase
|
||||||
{
|
{
|
||||||
EnsureAutoManagerInitialized();
|
EnsureAutoManagerInitialized();
|
||||||
|
eventArgs.PrepareForDispatch();
|
||||||
var collection = EventCache<TEvent>.List;
|
var collection = EventCache<TEvent>.List;
|
||||||
if (collection == null) return false;
|
var handlers = collection?.GetHandlers() ?? Array.Empty<EventHandlerInfo>();
|
||||||
|
|
||||||
var handlers = collection.GetHandlers();
|
|
||||||
var wasHandled = false;
|
var wasHandled = false;
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
|
||||||
RecordEditorTrace(handlers, eventArgs, typeof(TEvent));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
for (var i = 0; i < handlers.Length; i++)
|
for (var i = 0; i < handlers.Length; i++)
|
||||||
{
|
{
|
||||||
var handlerInfo = handlers[i];
|
var handlerInfo = handlers[i];
|
||||||
@@ -225,6 +222,7 @@ namespace ShrinkEventBus
|
|||||||
}
|
}
|
||||||
|
|
||||||
eventArgs.CurrentHandler = null;
|
eventArgs.CurrentHandler = null;
|
||||||
|
CompleteEventDispatch(eventArgs, typeof(TEvent), handlers);
|
||||||
return wasHandled;
|
return wasHandled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,16 +230,11 @@ namespace ShrinkEventBus
|
|||||||
public static bool TriggerEvent<TEvent>(TEvent eventArgs) where TEvent : EventBase
|
public static bool TriggerEvent<TEvent>(TEvent eventArgs) where TEvent : EventBase
|
||||||
{
|
{
|
||||||
EnsureAutoManagerInitialized();
|
EnsureAutoManagerInitialized();
|
||||||
|
eventArgs.PrepareForDispatch();
|
||||||
var collection = EventCache<TEvent>.List;
|
var collection = EventCache<TEvent>.List;
|
||||||
if (collection == null) return false;
|
var handlers = collection?.GetHandlers() ?? Array.Empty<EventHandlerInfo>();
|
||||||
|
|
||||||
var handlers = collection.GetHandlers();
|
|
||||||
var wasHandled = false;
|
var wasHandled = false;
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
|
||||||
RecordEditorTrace(handlers, eventArgs, typeof(TEvent));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
for (var i = 0; i < handlers.Length; i++)
|
for (var i = 0; i < handlers.Length; i++)
|
||||||
{
|
{
|
||||||
var handlerInfo = handlers[i];
|
var handlerInfo = handlers[i];
|
||||||
@@ -256,7 +249,8 @@ namespace ShrinkEventBus
|
|||||||
}
|
}
|
||||||
else if (handlerInfo.Handler is Func<TEvent, UniTask> asyncHandler)
|
else if (handlerInfo.Handler is Func<TEvent, UniTask> asyncHandler)
|
||||||
{
|
{
|
||||||
FireAndForgetSafe(() => asyncHandler(eventArgs),
|
var detachedEvent = EventCloneUtility.CloneForDetachedDispatch(eventArgs);
|
||||||
|
FireAndForgetSafe(() => asyncHandler(detachedEvent),
|
||||||
$"{handlerInfo.DisplayDeclaringType.Name}.{handlerInfo.DisplayMethodName}");
|
$"{handlerInfo.DisplayDeclaringType.Name}.{handlerInfo.DisplayMethodName}");
|
||||||
wasHandled = true;
|
wasHandled = true;
|
||||||
}
|
}
|
||||||
@@ -269,6 +263,7 @@ namespace ShrinkEventBus
|
|||||||
}
|
}
|
||||||
|
|
||||||
eventArgs.CurrentHandler = null;
|
eventArgs.CurrentHandler = null;
|
||||||
|
CompleteEventDispatch(eventArgs, typeof(TEvent), handlers);
|
||||||
return wasHandled;
|
return wasHandled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,15 +276,26 @@ namespace ShrinkEventBus
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void CompleteEventDispatch(EventBase eventArgs, Type eventType, EventHandlerInfo[] handlers)
|
||||||
|
{
|
||||||
|
OnEventTriggered?.Invoke(eventArgs, eventType);
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
private static void RecordEditorTrace<TEvent>(EventHandlerInfo[] handlers, TEvent eventArgs, Type eventType)
|
if (!EnableDebugRecord) return;
|
||||||
where TEvent : EventBase
|
PrepareEditorTrace(handlers, eventArgs);
|
||||||
|
OnEventTriggeredForEditor?.Invoke(eventArgs, eventType.Name, GetSenderInfo());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
private static void PrepareEditorTrace(EventHandlerInfo[] handlers, EventBase eventArgs)
|
||||||
{
|
{
|
||||||
if (!EnableDebugRecord) return;
|
if (!EnableDebugRecord) return;
|
||||||
var listenerList = eventArgs.GetListenerList();
|
var listenerList = eventArgs.GetListenerList();
|
||||||
|
listenerList.Clear();
|
||||||
for (var i = 0; i < handlers.Length; i++)
|
for (var i = 0; i < handlers.Length; i++)
|
||||||
listenerList.Add(handlers[i]);
|
listenerList.Add(handlers[i]);
|
||||||
OnEventTriggeredForEditor?.Invoke(eventArgs, eventType.Name, GetSenderInfo());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetSenderInfo()
|
private static string GetSenderInfo()
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace ShrinkEventBus
|
||||||
|
{
|
||||||
|
internal static class EventCloneUtility
|
||||||
|
{
|
||||||
|
private static readonly object CacheLock = new();
|
||||||
|
private static readonly Dictionary<Type, FieldInfo[]> FieldCache = new();
|
||||||
|
|
||||||
|
public static TEvent CloneForDetachedDispatch<TEvent>(TEvent source) where TEvent : EventBase
|
||||||
|
{
|
||||||
|
if (source == null)
|
||||||
|
throw new ArgumentNullException(nameof(source));
|
||||||
|
|
||||||
|
if (Activator.CreateInstance(source.GetType()) is not TEvent clone)
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Cannot clone event type {source.GetType().FullName}. A public parameterless constructor is required.");
|
||||||
|
|
||||||
|
CopyFields(source, clone);
|
||||||
|
CopyListenerSnapshot(source, clone);
|
||||||
|
clone.ReleaseAction = null;
|
||||||
|
clone.IsInPool = false;
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CopyFields(EventBase source, EventBase target)
|
||||||
|
{
|
||||||
|
var fields = GetCopyableFields(source.GetType());
|
||||||
|
for (var i = 0; i < fields.Length; i++)
|
||||||
|
fields[i].SetValue(target, fields[i].GetValue(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CopyListenerSnapshot(EventBase source, EventBase target)
|
||||||
|
{
|
||||||
|
var targetListeners = target.GetListenerList();
|
||||||
|
targetListeners.Clear();
|
||||||
|
|
||||||
|
var sourceHandlers = source.GetListenerList().GetHandlers();
|
||||||
|
for (var i = 0; i < sourceHandlers.Length; i++)
|
||||||
|
targetListeners.Add(sourceHandlers[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FieldInfo[] GetCopyableFields(Type type)
|
||||||
|
{
|
||||||
|
lock (CacheLock)
|
||||||
|
{
|
||||||
|
if (FieldCache.TryGetValue(type, out var cached))
|
||||||
|
return cached;
|
||||||
|
|
||||||
|
var fields = new List<FieldInfo>();
|
||||||
|
var currentType = type;
|
||||||
|
while (currentType != null && currentType != typeof(object))
|
||||||
|
{
|
||||||
|
var declaredFields = currentType.GetFields(BindingFlags.Instance | BindingFlags.Public |
|
||||||
|
BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
|
||||||
|
for (var i = 0; i < declaredFields.Length; i++)
|
||||||
|
{
|
||||||
|
var field = declaredFields[i];
|
||||||
|
if (field.IsStatic)
|
||||||
|
continue;
|
||||||
|
if (ShouldSkipField(field))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
fields.Add(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentType = currentType.BaseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
cached = fields.ToArray();
|
||||||
|
FieldCache[type] = cached;
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ShouldSkipField(FieldInfo field)
|
||||||
|
{
|
||||||
|
return field.Name is "_listenerList"
|
||||||
|
or "<ReleaseAction>k__BackingField"
|
||||||
|
or "<IsInPool>k__BackingField";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 876564c111495b2489870b29ab75da5e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "com.cneicy.shrink-eventbus",
|
"name": "com.cneicy.shrink-eventbus",
|
||||||
"version": "1.1.4",
|
"version": "1.1.5",
|
||||||
"displayName": "ShrinkEventBus",
|
"displayName": "ShrinkEventBus",
|
||||||
"description": "高性能、类型安全的 Unity 事件总线,支持优先级调度与自动注册。",
|
"description": "高性能、类型安全的 Unity 事件总线,支持优先级调度与自动注册。",
|
||||||
"unity": "2022.3",
|
"unity": "2022.3",
|
||||||
|
|||||||
Reference in new issue
Block a user