fr.jeanf.eventsystem 0.1.126 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/.attestation.p7m +0 -0
  2. package/Documentation~/images/.gitkeep +0 -0
  3. package/Documentation~/images/debugger-channels.png +0 -0
  4. package/Documentation~/images/debugger-eventlog.png +0 -0
  5. package/Editor/EventSystemDebuggerWindow.cs +562 -0
  6. package/Editor/EventSystemDebuggerWindow.cs.meta +2 -0
  7. package/Editor/jeanf.eventsystem.editor.asmdef +18 -0
  8. package/Editor/jeanf.eventsystem.editor.asmdef.meta +7 -0
  9. package/Editor.meta +8 -0
  10. package/README.md +122 -10
  11. package/Runtime/BaseClasses/CanonicalChannelResolver.cs +60 -0
  12. package/Runtime/BaseClasses/CanonicalChannelResolver.cs.meta +11 -0
  13. package/Runtime/Diagnostics/EventDiagnostics.cs +395 -0
  14. package/Runtime/Diagnostics/EventDiagnostics.cs.meta +2 -0
  15. package/Runtime/Diagnostics.meta +8 -0
  16. package/Runtime/PingSystem/PingableScriptableObject.cs +3 -3
  17. package/Runtime/ScriptableObjects/ActionRebindEventChannelSO.cs +12 -5
  18. package/Runtime/ScriptableObjects/Advanced/DecimalBoolEventChannelSO.cs +12 -5
  19. package/Runtime/ScriptableObjects/Advanced/DecimalDecimalEventChannelSO.cs +12 -5
  20. package/Runtime/ScriptableObjects/Advanced/DecimalFloatEventChannelSO.cs +12 -5
  21. package/Runtime/ScriptableObjects/Advanced/DecimalGameObjectEventChannelSO.cs +11 -4
  22. package/Runtime/ScriptableObjects/Advanced/DecimalIntEventChannelSO.cs +12 -5
  23. package/Runtime/ScriptableObjects/Advanced/DecimalStringEventChannelSO.cs +12 -5
  24. package/Runtime/ScriptableObjects/Advanced/InputActionIntEventChannelSO.cs +10 -4
  25. package/Runtime/ScriptableObjects/Advanced/IntBoolEventChannelSO.cs +12 -5
  26. package/Runtime/ScriptableObjects/Advanced/IntEnumEventChannelSO.cs +10 -4
  27. package/Runtime/ScriptableObjects/Advanced/IntFloatEventChannelSO.cs +12 -5
  28. package/Runtime/ScriptableObjects/Advanced/IntGameObjectEventChannelSO.cs +11 -4
  29. package/Runtime/ScriptableObjects/Advanced/IntIntEventChannelSO.cs +12 -5
  30. package/Runtime/ScriptableObjects/Advanced/IntStringEventChannelSO.cs +12 -5
  31. package/Runtime/ScriptableObjects/Advanced/StringBoolEventChannelSO.cs +10 -4
  32. package/Runtime/ScriptableObjects/Advanced/StringFloatEventChannelSO.cs +13 -6
  33. package/Runtime/ScriptableObjects/Advanced/StringStringtEventChannelSO.cs +12 -5
  34. package/Runtime/ScriptableObjects/Advanced/TimelineTriggerEventChannelSO.cs +12 -5
  35. package/Runtime/ScriptableObjects/BoolEventChannelSO.cs +13 -5
  36. package/Runtime/ScriptableObjects/BoolFloatEventChannelSO.cs +10 -4
  37. package/Runtime/ScriptableObjects/DecimalEventChannelSO.cs +13 -7
  38. package/Runtime/ScriptableObjects/FloatEventChannelSO.cs +12 -5
  39. package/Runtime/ScriptableObjects/GameObjectEventChannelSO.cs +10 -2
  40. package/Runtime/ScriptableObjects/GameObjectIntBoolEventChannelSO.cs +10 -7
  41. package/Runtime/ScriptableObjects/InputActionEventChannelSO.cs +10 -5
  42. package/Runtime/ScriptableObjects/IntEventChannelSO.cs +12 -5
  43. package/Runtime/ScriptableObjects/QuaternionEventChannelSO.cs +10 -7
  44. package/Runtime/ScriptableObjects/ScriptableObjectEventChannelSO.cs +19 -5
  45. package/Runtime/ScriptableObjects/StringEventChannelSO.cs +15 -4
  46. package/Runtime/ScriptableObjects/StringListEventChannelSO.cs +10 -5
  47. package/Runtime/ScriptableObjects/TeleportEventChannelSO.cs +12 -4
  48. package/Runtime/ScriptableObjects/TransformEventChannelSO.cs +12 -4
  49. package/Runtime/ScriptableObjects/Vector2EventChannelSO.cs +10 -7
  50. package/Runtime/ScriptableObjects/Vector3EventChannelSO.cs +10 -7
  51. package/Runtime/ScriptableObjects/VoidEventChannelSO.cs +10 -3
  52. package/Runtime/SenderInterfaces/Advanced/DecimalBoolEventSender.cs +3 -2
  53. package/Runtime/SenderInterfaces/Advanced/DecimalDecimalEventSender.cs +3 -2
  54. package/Runtime/SenderInterfaces/Advanced/DecimalFloatEventSender.cs +3 -2
  55. package/Runtime/SenderInterfaces/Advanced/DecimalGameObjectEventSender.cs +3 -2
  56. package/Runtime/SenderInterfaces/Advanced/DecimalIntEventSender.cs +3 -2
  57. package/Runtime/SenderInterfaces/Advanced/DecimalStringEventSender.cs +3 -2
  58. package/Runtime/SenderInterfaces/Advanced/IntBoolEventSender.cs +3 -2
  59. package/Runtime/SenderInterfaces/Advanced/IntEnumEventSender.cs +3 -2
  60. package/Runtime/SenderInterfaces/Advanced/IntFloatEventSender.cs +3 -2
  61. package/Runtime/SenderInterfaces/Advanced/IntGameObjectEventSender.cs +3 -2
  62. package/Runtime/SenderInterfaces/Advanced/IntIntEventSender.cs +3 -2
  63. package/Runtime/SenderInterfaces/Advanced/IntStringEventSender.cs +3 -2
  64. package/Runtime/SenderInterfaces/Advanced/StringBoolEventSender.cs +3 -2
  65. package/Runtime/SenderInterfaces/Advanced/StringFloatEventSender.cs +3 -2
  66. package/Runtime/SenderInterfaces/Advanced/StringStringEventSender.cs +3 -2
  67. package/Runtime/SenderInterfaces/BoolEventSender.cs +3 -2
  68. package/Runtime/SenderInterfaces/DecimalEventSender.cs +3 -2
  69. package/Runtime/SenderInterfaces/FloatEventSender.cs +3 -2
  70. package/Runtime/SenderInterfaces/GameObjectEventSender.cs +3 -2
  71. package/Runtime/SenderInterfaces/IEventPublisher.cs +19 -0
  72. package/Runtime/SenderInterfaces/IEventPublisher.cs.meta +2 -0
  73. package/Runtime/SenderInterfaces/IInputActionIntEventSender.cs +3 -2
  74. package/Runtime/SenderInterfaces/IntEventSender.cs +3 -2
  75. package/Runtime/SenderInterfaces/ScriptableObjectEventSender.cs +3 -2
  76. package/Runtime/SenderInterfaces/StringEventSender.cs +3 -2
  77. package/Runtime/SenderInterfaces/StringListEventSender.cs +3 -2
  78. package/Runtime/SenderInterfaces/TimelineBoolEventSender.cs +3 -2
  79. package/Runtime/SenderInterfaces/TransformEventSender.cs +3 -2
  80. package/Runtime/SenderInterfaces/VoidEventSender.cs +3 -2
  81. package/package.json +2 -2
package/.attestation.p7m CHANGED
Binary file
File without changes
@@ -0,0 +1,562 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.Reflection;
4
+ using System.Runtime.CompilerServices;
5
+ using UnityEditor;
6
+ using UnityEngine;
7
+ using Object = UnityEngine.Object;
8
+
9
+ namespace jeanf.EventSystem.EditorTools
10
+ {
11
+ public class EventSystemDebuggerWindow : EditorWindow
12
+ {
13
+ public const string PrefAutoSender = "jeanf.evtdbg.autoSender";
14
+ public const string PrefAutoReceiver = "jeanf.evtdbg.autoReceiver";
15
+
16
+ enum Tab { Channels, EventLog }
17
+
18
+ Tab _tab = Tab.Channels;
19
+ Vector2 _channelScroll;
20
+ Vector2 _logScroll;
21
+ string _channelSearch = "";
22
+ string _logSearch = "";
23
+ bool _duplicatesOnly;
24
+ readonly HashSet<int> _expanded = new HashSet<int>();
25
+
26
+ EventDiagnostics.RaiseRecord[] _logBuffer = new EventDiagnostics.RaiseRecord[EventDiagnostics.BufferCapacity];
27
+ int _logCount;
28
+
29
+ readonly Dictionary<string, List<Component>> _senderScan = new Dictionary<string, List<Component>>();
30
+ double _nextScan;
31
+
32
+ const BindingFlags FieldFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
33
+
34
+ [MenuItem("Window/jeanf/Event System Debugger")]
35
+ static void Open()
36
+ {
37
+ var w = GetWindow<EventSystemDebuggerWindow>();
38
+ w.titleContent = new GUIContent("Event Debugger");
39
+ w.minSize = new Vector2(540, 320);
40
+ w.Show();
41
+ }
42
+
43
+ void OnEnable()
44
+ {
45
+ EditorApplication.update += OnEditorUpdate;
46
+ }
47
+
48
+ void OnDisable()
49
+ {
50
+ EditorApplication.update -= OnEditorUpdate;
51
+ }
52
+
53
+ double _nextRepaint;
54
+
55
+ void OnEditorUpdate()
56
+ {
57
+ if (!EditorApplication.isPlaying) return;
58
+ if (EditorApplication.timeSinceStartup >= _nextScan)
59
+ {
60
+ _nextScan = EditorApplication.timeSinceStartup + 1.0;
61
+ RebuildSenderScan();
62
+ }
63
+ if (EditorApplication.timeSinceStartup < _nextRepaint) return;
64
+ _nextRepaint = EditorApplication.timeSinceStartup + 0.2;
65
+ Repaint();
66
+ }
67
+
68
+ void RebuildSenderScan()
69
+ {
70
+ _senderScan.Clear();
71
+ var comps = Object.FindObjectsOfType<MonoBehaviour>(true);
72
+ foreach (var comp in comps)
73
+ {
74
+ if (comp == null) continue;
75
+ foreach (var itf in comp.GetType().GetInterfaces())
76
+ {
77
+ if (itf.Namespace != "jeanf.EventSystem") continue;
78
+ if (!itf.Name.EndsWith("EventSender", StringComparison.Ordinal)) continue;
79
+ foreach (var p in itf.GetProperties())
80
+ {
81
+ if (!typeof(DescriptionBaseSO).IsAssignableFrom(p.PropertyType)) continue;
82
+ if (!(p.GetValue(comp) is DescriptionBaseSO ch)) continue;
83
+ string gk = EventDiagnostics.GroupKey(ch.Guid, RuntimeHelpers.GetHashCode(ch));
84
+ if (!_senderScan.TryGetValue(gk, out var list))
85
+ {
86
+ list = new List<Component>();
87
+ _senderScan[gk] = list;
88
+ }
89
+ if (!list.Contains(comp)) list.Add(comp);
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ void OnGUI()
96
+ {
97
+ DrawToolbar();
98
+ if (_tab == Tab.Channels) DrawChannels();
99
+ else DrawEventLog();
100
+ }
101
+
102
+ void DrawToolbar()
103
+ {
104
+ using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar))
105
+ {
106
+ if (GUILayout.Toggle(_tab == Tab.Channels, "Channels", EditorStyles.toolbarButton)) _tab = Tab.Channels;
107
+ if (GUILayout.Toggle(_tab == Tab.EventLog, "Event Log", EditorStyles.toolbarButton)) _tab = Tab.EventLog;
108
+ GUILayout.FlexibleSpace();
109
+ bool resolver = CanonicalChannelResolver.Resolve != null;
110
+ var c = GUI.color;
111
+ GUI.color = resolver ? new Color(0.5f, 1f, 0.5f) : new Color(1f, 0.6f, 0.4f);
112
+ GUILayout.Label(resolver ? "● resolver installed" : "○ no resolver", EditorStyles.miniLabel);
113
+ GUI.color = c;
114
+ }
115
+
116
+ if (!EditorApplication.isPlaying)
117
+ EditorGUILayout.HelpBox("Enter Play mode to capture live channels and events.", MessageType.Info);
118
+ }
119
+
120
+ void DrawChannels()
121
+ {
122
+ using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar))
123
+ {
124
+ _channelSearch = GUILayout.TextField(_channelSearch, EditorStyles.toolbarSearchField, GUILayout.MinWidth(120));
125
+ _duplicatesOnly = GUILayout.Toggle(_duplicatesOnly, "Duplicates only", EditorStyles.toolbarButton, GUILayout.Width(110));
126
+ }
127
+
128
+ var groups = BuildGroups();
129
+
130
+ GUILayout.Label($"Live channels: {groups.Count}", EditorStyles.miniBoldLabel);
131
+
132
+ _channelScroll = EditorGUILayout.BeginScrollView(_channelScroll);
133
+ foreach (var g in groups)
134
+ {
135
+ if (!string.IsNullOrEmpty(_channelSearch) &&
136
+ g.displayName.IndexOf(_channelSearch, StringComparison.OrdinalIgnoreCase) < 0)
137
+ continue;
138
+ if (_duplicatesOnly && g.instances.Count <= 1) continue;
139
+ DrawGroup(g);
140
+ }
141
+ EditorGUILayout.EndScrollView();
142
+ }
143
+
144
+ class Group
145
+ {
146
+ public string groupKey;
147
+ public string displayName;
148
+ public string typeName;
149
+ public List<(EventDiagnostics.ChannelRecord rec, ScriptableObject so)> instances =
150
+ new List<(EventDiagnostics.ChannelRecord, ScriptableObject)>();
151
+ }
152
+
153
+ List<Group> BuildGroups()
154
+ {
155
+ var byKey = new Dictionary<string, Group>();
156
+ var records = EventDiagnostics.SnapshotChannels();
157
+ foreach (var rec in records)
158
+ {
159
+ ScriptableObject so = null;
160
+ rec.instance?.TryGetTarget(out so);
161
+ if (so == null)
162
+ {
163
+ EventDiagnostics.PruneChannel(rec.key);
164
+ continue;
165
+ }
166
+
167
+ string gk = EventDiagnostics.GroupKey(rec.guid, rec.key);
168
+ if (!byKey.TryGetValue(gk, out var g))
169
+ {
170
+ g = new Group { groupKey = gk, displayName = so.name, typeName = rec.typeName };
171
+ byKey[gk] = g;
172
+ }
173
+ g.instances.Add((rec, so));
174
+ }
175
+
176
+ var list = new List<Group>(byKey.Values);
177
+ list.Sort((a, b) => string.Compare(a.displayName, b.displayName, StringComparison.OrdinalIgnoreCase));
178
+ return list;
179
+ }
180
+
181
+ void DrawGroup(Group g)
182
+ {
183
+ bool dup = g.instances.Count > 1;
184
+ int subTotal = 0;
185
+ foreach (var inst in g.instances) subTotal += CountSubscribers(inst.so);
186
+
187
+ int gkHash = g.groupKey.GetHashCode();
188
+ bool open = _expanded.Contains(gkHash);
189
+
190
+ using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox))
191
+ {
192
+ bool nowOpen = EditorGUILayout.Foldout(open, GUIContent.none, true, EditorStyles.foldout);
193
+ if (nowOpen != open)
194
+ {
195
+ if (nowOpen) _expanded.Add(gkHash); else _expanded.Remove(gkHash);
196
+ }
197
+
198
+ GUILayout.Label(g.displayName, EditorStyles.boldLabel, GUILayout.Width(200));
199
+ GUILayout.Label(g.typeName, EditorStyles.miniLabel, GUILayout.Width(150));
200
+ GUILayout.Label($"inst {g.instances.Count}", EditorStyles.miniLabel, GUILayout.Width(54));
201
+ GUILayout.Label($"subs {subTotal}", EditorStyles.miniLabel, GUILayout.Width(54));
202
+
203
+ GUILayout.FlexibleSpace();
204
+ if (dup)
205
+ {
206
+ var c = GUI.color; GUI.color = new Color(1f, 0.55f, 0.4f);
207
+ GUILayout.Label("▲ DUPLICATE", EditorStyles.miniBoldLabel, GUILayout.Width(90));
208
+ GUI.color = c;
209
+ }
210
+ else if (subTotal == 0)
211
+ {
212
+ GUILayout.Label("◌ no listeners", EditorStyles.miniLabel, GUILayout.Width(90));
213
+ }
214
+
215
+ if (GUILayout.Button("Select GOs", EditorStyles.miniButton, GUILayout.Width(80)))
216
+ SelectAllListeners(g);
217
+ }
218
+
219
+ if (open) DrawGroupDetails(g);
220
+ }
221
+
222
+ void DrawGroupDetails(Group g)
223
+ {
224
+ EditorGUI.indentLevel++;
225
+ ScriptableObject canonical = null;
226
+ try { canonical = CanonicalChannelResolver.GetCanonical((ScriptableObject)g.instances[0].so); }
227
+ catch { }
228
+
229
+ foreach (var inst in g.instances)
230
+ {
231
+ var so = inst.so;
232
+ var rec = inst.rec;
233
+ bool isCanonical = canonical != null && ReferenceEquals(canonical, so);
234
+
235
+ using (new EditorGUILayout.HorizontalScope())
236
+ {
237
+ GUILayout.Space(16);
238
+ GUILayout.Label(isCanonical ? "[CANONICAL]" : "[duplicate]",
239
+ isCanonical ? EditorStyles.miniBoldLabel : EditorStyles.miniLabel, GUILayout.Width(86));
240
+ GUILayout.Label($"id {so.GetInstanceID()}", EditorStyles.miniLabel, GUILayout.Width(96));
241
+ GUILayout.Label($"thread {rec.firstSeenThreadId}" + (rec.seenOffMainThread ? " ⚠" : ""),
242
+ EditorStyles.miniLabel, GUILayout.Width(90));
243
+ if (GUILayout.Button("Ping asset", EditorStyles.miniButton, GUILayout.Width(74)))
244
+ EditorGUIUtility.PingObject(so);
245
+ }
246
+
247
+ var subs = GetSubscribers(so);
248
+ using (new EditorGUILayout.HorizontalScope())
249
+ {
250
+ GUILayout.Space(22);
251
+ RoleBadge("RECV", RecvColor);
252
+ GUILayout.Label($"receivers ({subs.Count})", EditorStyles.miniBoldLabel);
253
+ }
254
+ foreach (var s in subs)
255
+ {
256
+ using (new EditorGUILayout.HorizontalScope())
257
+ {
258
+ GUILayout.Space(40);
259
+ GUILayout.Label("↳ " + s.label, EditorStyles.miniLabel, GUILayout.Width(320));
260
+ GUILayout.FlexibleSpace();
261
+ using (new EditorGUI.DisabledScope(s.target == null))
262
+ {
263
+ if (GUILayout.Button("Select", EditorStyles.miniButton, GUILayout.Width(60)))
264
+ {
265
+ Selection.activeObject = s.SelectionTarget;
266
+ EditorGUIUtility.PingObject(s.SelectionTarget);
267
+ }
268
+ if (GUILayout.Button("Ping", EditorStyles.miniButton, GUILayout.Width(50)))
269
+ EditorGUIUtility.PingObject(s.SelectionTarget);
270
+ }
271
+ }
272
+ }
273
+ }
274
+
275
+ var publishers = GetPublishers(g);
276
+ var stackSenders = EventDiagnostics.SnapshotStackSenders(g.groupKey);
277
+ int senderTotal = publishers.Count + stackSenders.Length;
278
+ using (new EditorGUILayout.HorizontalScope())
279
+ {
280
+ GUILayout.Space(16);
281
+ RoleBadge("SEND", SendColor);
282
+ GUILayout.Label($"senders ({senderTotal})", EditorStyles.miniBoldLabel);
283
+ if (senderTotal == 0)
284
+ GUILayout.Label("— none yet (enable “Track sender” to auto-detect by class, or this.Publish(…) for exact GameObject)", EditorStyles.miniLabel);
285
+ }
286
+ foreach (var ss in stackSenders)
287
+ {
288
+ using (new EditorGUILayout.HorizontalScope())
289
+ {
290
+ GUILayout.Space(40);
291
+ string m = string.IsNullOrEmpty(ss.method) ? "" : "." + ss.method + "()";
292
+ var prev = GUI.color;
293
+ GUI.color = new Color(prev.r, prev.g, prev.b, 0.75f);
294
+ GUILayout.Label($"⇧ {ss.typeName}{m} ×{ss.count} (by stack, no GameObject)", EditorStyles.miniLabel, GUILayout.Width(420));
295
+ GUI.color = prev;
296
+ GUILayout.FlexibleSpace();
297
+ }
298
+ }
299
+ foreach (var p in publishers)
300
+ {
301
+ using (new EditorGUILayout.HorizontalScope())
302
+ {
303
+ GUILayout.Space(40);
304
+ string name = p.comp != null ? p.comp.GetType().Name + " ▸ \"" + p.comp.gameObject.name + "\"" : p.label;
305
+ GUILayout.Label("⇧ " + name, EditorStyles.miniLabel, GUILayout.Width(320));
306
+ GUILayout.FlexibleSpace();
307
+ using (new EditorGUI.DisabledScope(p.comp == null))
308
+ {
309
+ if (GUILayout.Button("Select", EditorStyles.miniButton, GUILayout.Width(60)))
310
+ {
311
+ Selection.activeObject = p.comp != null ? p.comp.gameObject : null;
312
+ if (p.comp != null) EditorGUIUtility.PingObject(p.comp.gameObject);
313
+ }
314
+ if (GUILayout.Button("Ping", EditorStyles.miniButton, GUILayout.Width(50)))
315
+ if (p.comp != null) EditorGUIUtility.PingObject(p.comp.gameObject);
316
+ }
317
+ }
318
+ }
319
+
320
+ EditorGUI.indentLevel--;
321
+ GUILayout.Space(4);
322
+ }
323
+
324
+ static readonly Color RecvColor = new Color(0.45f, 0.8f, 1f);
325
+ static readonly Color SendColor = new Color(1f, 0.72f, 0.4f);
326
+
327
+ static void RoleBadge(string text, Color color)
328
+ {
329
+ var prev = GUI.color;
330
+ GUI.color = color;
331
+ GUILayout.Label(text, EditorStyles.miniBoldLabel, GUILayout.Width(38));
332
+ GUI.color = prev;
333
+ }
334
+
335
+ struct PubInfo
336
+ {
337
+ public string label;
338
+ public Component comp;
339
+ }
340
+
341
+ List<PubInfo> GetPublishers(Group g)
342
+ {
343
+ var list = new List<PubInfo>();
344
+ var seen = new HashSet<int>();
345
+
346
+ foreach (var pr in EventDiagnostics.SnapshotPublishers(g.groupKey))
347
+ {
348
+ Component c = null;
349
+ pr.component?.TryGetTarget(out c);
350
+ if (c == null) continue;
351
+ if (!seen.Add(c.GetInstanceID())) continue;
352
+ list.Add(new PubInfo { comp = c, label = pr.typeName });
353
+ }
354
+
355
+ if (_senderScan.TryGetValue(g.groupKey, out var scanned))
356
+ {
357
+ foreach (var c in scanned)
358
+ {
359
+ if (c == null) continue;
360
+ if (!seen.Add(c.GetInstanceID())) continue;
361
+ list.Add(new PubInfo { comp = c, label = c.GetType().Name });
362
+ }
363
+ }
364
+ return list;
365
+ }
366
+
367
+ struct SubInfo
368
+ {
369
+ public string label;
370
+ public Object target;
371
+ public GameObject go;
372
+ public Object SelectionTarget => go != null ? go : target;
373
+ }
374
+
375
+ int CountSubscribers(ScriptableObject so)
376
+ {
377
+ int n = 0;
378
+ ForEachSubscriber(so, (_, __) => n++);
379
+ return n;
380
+ }
381
+
382
+ List<SubInfo> GetSubscribers(ScriptableObject so)
383
+ {
384
+ var list = new List<SubInfo>();
385
+ ForEachSubscriber(so, (target, method) =>
386
+ {
387
+ var go = (target as Component)?.gameObject;
388
+ string typeName = method?.DeclaringType?.Name ?? target?.GetType().Name ?? "static";
389
+ string goName = go != null ? " ▸ \"" + go.name + "\"" : "";
390
+ list.Add(new SubInfo
391
+ {
392
+ label = $"{typeName}.{method?.Name}(){goName}",
393
+ target = target as Object,
394
+ go = go,
395
+ });
396
+ });
397
+ return list;
398
+ }
399
+
400
+ static void ForEachSubscriber(ScriptableObject so, Action<object, MethodInfo> action)
401
+ {
402
+ if (so == null) return;
403
+ var t = so.GetType();
404
+ while (t != null && t != typeof(ScriptableObject))
405
+ {
406
+ foreach (var f in t.GetFields(FieldFlags))
407
+ {
408
+ if (!typeof(Delegate).IsAssignableFrom(f.FieldType)) continue;
409
+ if (!(f.GetValue(so) is Delegate del)) continue;
410
+ foreach (var d in del.GetInvocationList())
411
+ action(d.Target, d.Method);
412
+ }
413
+ t = t.BaseType;
414
+ }
415
+ }
416
+
417
+ void SelectAllListeners(Group g)
418
+ {
419
+ var objs = new List<Object>();
420
+ foreach (var inst in g.instances)
421
+ foreach (var s in GetSubscribers(inst.so))
422
+ if (s.SelectionTarget != null) objs.Add(s.SelectionTarget);
423
+ if (objs.Count > 0)
424
+ {
425
+ Selection.objects = objs.ToArray();
426
+ EditorGUIUtility.PingObject(objs[0]);
427
+ }
428
+ }
429
+
430
+ void DrawEventLog()
431
+ {
432
+ using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar))
433
+ {
434
+ GUILayout.Label("On play:", EditorStyles.miniLabel, GUILayout.Width(54));
435
+ DrawPrefToggle(PrefAutoSender, "Track sender", v =>
436
+ {
437
+ EventDiagnostics.CaptureSenderStack = v;
438
+ EventDiagnostics.TrackingEnabled = v || EventDiagnostics.CaptureReceivers;
439
+ });
440
+ DrawPrefToggle(PrefAutoReceiver, "Track receiver", v =>
441
+ {
442
+ EventDiagnostics.CaptureReceivers = v;
443
+ EventDiagnostics.TrackingEnabled = v || EventDiagnostics.CaptureSenderStack;
444
+ });
445
+ if (GUILayout.Button("Clear", EditorStyles.toolbarButton, GUILayout.Width(50)))
446
+ EventDiagnostics.Clear();
447
+ GUILayout.FlexibleSpace();
448
+ _logSearch = GUILayout.TextField(_logSearch, EditorStyles.toolbarSearchField, GUILayout.MinWidth(120));
449
+ GUILayout.Label($"{EventDiagnostics.RaiseCount}/{EventDiagnostics.BufferCapacity}", EditorStyles.miniLabel);
450
+ }
451
+
452
+ _logCount = EventDiagnostics.SnapshotRaises(_logBuffer);
453
+
454
+ DrawCounts();
455
+
456
+ using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox))
457
+ {
458
+ GUILayout.Label("Seq", EditorStyles.miniBoldLabel, GUILayout.Width(54));
459
+ GUILayout.Label("Time", EditorStyles.miniBoldLabel, GUILayout.Width(64));
460
+ GUILayout.Label("Channel", EditorStyles.miniBoldLabel, GUILayout.Width(180));
461
+ GUILayout.Label("Payload", EditorStyles.miniBoldLabel, GUILayout.Width(110));
462
+ GUILayout.Label("Sender", EditorStyles.miniBoldLabel, GUILayout.Width(180));
463
+ GUILayout.Label("Receiver(s)", EditorStyles.miniBoldLabel);
464
+ }
465
+
466
+ _logScroll = EditorGUILayout.BeginScrollView(_logScroll);
467
+ for (int i = _logCount - 1; i >= 0; i--)
468
+ {
469
+ var r = _logBuffer[i];
470
+ if (!string.IsNullOrEmpty(_logSearch) &&
471
+ (r.typeName?.IndexOf(_logSearch, StringComparison.OrdinalIgnoreCase) ?? -1) < 0 &&
472
+ (r.payload?.IndexOf(_logSearch, StringComparison.OrdinalIgnoreCase) ?? -1) < 0)
473
+ continue;
474
+
475
+ using (new EditorGUILayout.HorizontalScope())
476
+ {
477
+ GUILayout.Label("#" + r.seq, EditorStyles.miniLabel, GUILayout.Width(54));
478
+ GUILayout.Label(r.time.ToString("0.000"), EditorStyles.miniLabel, GUILayout.Width(64));
479
+ GUILayout.Label(r.typeName, EditorStyles.miniLabel, GUILayout.Width(180));
480
+ GUILayout.Label(r.payload, EditorStyles.miniLabel, GUILayout.Width(110));
481
+ string sender = r.senderObject != null
482
+ ? r.senderType
483
+ : r.senderType != null ? r.senderType + "." + r.senderMethod + "()" : "—";
484
+ if (r.offMainThread) sender = "⚠ t" + r.threadId + " " + sender;
485
+ GUILayout.Label(sender, EditorStyles.miniLabel, GUILayout.Width(146));
486
+ using (new EditorGUI.DisabledScope(r.senderObject == null))
487
+ {
488
+ if (GUILayout.Button("Sel", EditorStyles.miniButton, GUILayout.Width(32)) && r.senderObject != null)
489
+ {
490
+ Selection.activeObject = r.senderObject;
491
+ EditorGUIUtility.PingObject(r.senderObject);
492
+ }
493
+ }
494
+ GUILayout.Label(string.IsNullOrEmpty(r.receivers) ? "—" : r.receivers, EditorStyles.miniLabel);
495
+ }
496
+ }
497
+ EditorGUILayout.EndScrollView();
498
+ }
499
+
500
+ static void DrawPrefToggle(string key, string label, Action<bool> applyLive)
501
+ {
502
+ bool cur = EditorPrefs.GetBool(key, false);
503
+ bool next = GUILayout.Toggle(cur, label, EditorStyles.toolbarButton, GUILayout.Width(100));
504
+ if (next != cur)
505
+ {
506
+ EditorPrefs.SetBool(key, next);
507
+ if (EditorApplication.isPlaying) applyLive?.Invoke(next);
508
+ }
509
+ }
510
+
511
+ void DrawCounts()
512
+ {
513
+ if (_logCount == 0) return;
514
+ var counts = new Dictionary<string, int>();
515
+ int max = 1;
516
+ for (int i = 0; i < _logCount; i++)
517
+ {
518
+ var name = _logBuffer[i].typeName ?? "?";
519
+ counts.TryGetValue(name, out int n);
520
+ n++;
521
+ counts[name] = n;
522
+ if (n > max) max = n;
523
+ }
524
+
525
+ using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
526
+ {
527
+ foreach (var kv in counts)
528
+ {
529
+ using (new EditorGUILayout.HorizontalScope())
530
+ {
531
+ GUILayout.Label(kv.Key, EditorStyles.miniLabel, GUILayout.Width(180));
532
+ var rect = GUILayoutUtility.GetRect(120, 10);
533
+ EditorGUI.DrawRect(new Rect(rect.x, rect.y + 1, 120f * kv.Value / max, 8),
534
+ new Color(0.4f, 0.7f, 1f));
535
+ GUILayout.Label(kv.Value.ToString(), EditorStyles.miniLabel, GUILayout.Width(50));
536
+ }
537
+ }
538
+ }
539
+ }
540
+ }
541
+
542
+ [InitializeOnLoad]
543
+ static class EventDiagnosticsAutoStart
544
+ {
545
+ static EventDiagnosticsAutoStart()
546
+ {
547
+ EditorApplication.playModeStateChanged += OnPlayModeChanged;
548
+ }
549
+
550
+ static void OnPlayModeChanged(PlayModeStateChange state)
551
+ {
552
+ if (state != PlayModeStateChange.EnteredPlayMode) return;
553
+
554
+ bool sender = EditorPrefs.GetBool(EventSystemDebuggerWindow.PrefAutoSender, false);
555
+ bool receiver = EditorPrefs.GetBool(EventSystemDebuggerWindow.PrefAutoReceiver, false);
556
+
557
+ EventDiagnostics.CaptureSenderStack = sender;
558
+ EventDiagnostics.CaptureReceivers = receiver;
559
+ EventDiagnostics.TrackingEnabled = sender || receiver;
560
+ }
561
+ }
562
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: 8e75bdf971db2cc4b81bd1a830498579
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "jeanf.eventsystem.editor",
3
+ "rootNamespace": "",
4
+ "references": [
5
+ "jeanf.eventsystem"
6
+ ],
7
+ "includePlatforms": [
8
+ "Editor"
9
+ ],
10
+ "excludePlatforms": [],
11
+ "allowUnsafeCode": false,
12
+ "overrideReferences": false,
13
+ "precompiledReferences": [],
14
+ "autoReferenced": true,
15
+ "defineConstraints": [],
16
+ "versionDefines": [],
17
+ "noEngineReferences": false
18
+ }
@@ -0,0 +1,7 @@
1
+ fileFormatVersion: 2
2
+ guid: 96dd44eca317c644c871f5a70169b42c
3
+ AssemblyDefinitionImporter:
4
+ externalObjects: {}
5
+ userData:
6
+ assetBundleName:
7
+ assetBundleVariant:
package/Editor.meta ADDED
@@ -0,0 +1,8 @@
1
+ fileFormatVersion: 2
2
+ guid: 37e4eed8419248f4d8dc96bc6c9414ac
3
+ folderAsset: yes
4
+ DefaultImporter:
5
+ externalObjects: {}
6
+ userData:
7
+ assetBundleName:
8
+ assetBundleVariant: