Files
Workspace/Godot/Packages/ShrinkSDK.EventBus.Godot/addons/shrink_event_bus/ShrinkEventBusDebugger.cs
T

195 lines
5.8 KiB
C#

#nullable enable
using Godot;
#if DEBUG
using System;
using System.Collections.Generic;
using System.Linq;
using ShrinkEventBus;
using GArray = Godot.Collections.Array;
using GDictionary = Godot.Collections.Dictionary;
#endif
namespace ShrinkSDK.EventBus.Godot;
public partial class ShrinkEventBusDebugger : Node
{
#if DEBUG
private const string Prefix = "shrink_event_bus";
private const int Capacity = 200;
private readonly object _gate = new();
private readonly Queue<TraceRecord> _history = new();
private IDisposable? _subscription;
private bool _recording;
private bool _registered;
private long _discarded;
private double _elapsed;
public override void _Ready()
{
if (!EngineDebugger.IsActive())
return;
ProcessMode = ProcessModeEnum.Always;
EngineDebugger.RegisterMessageCapture(Prefix, Callable.From<string, GArray, bool>(HandleMessage));
_registered = true;
SendSnapshot();
}
public override void _Process(double delta)
{
if (!_registered)
return;
if (!EngineDebugger.IsActive())
{
SetRecording(false);
return;
}
_elapsed += delta;
if (_elapsed < 0.25)
return;
_elapsed = 0;
if (_recording)
SendSnapshot();
}
public override void _ExitTree()
{
if (!_registered)
return;
SetRecording(false);
EngineDebugger.UnregisterMessageCapture(Prefix);
_registered = false;
}
private bool HandleMessage(string message, GArray data)
{
switch (message)
{
case "set_debug_record":
if (data is [{ VariantType: Variant.Type.Bool } _])
SetRecording(data[0].AsBool());
break;
case "clear_history":
lock (_gate)
{
_history.Clear();
_discarded = 0;
}
break;
case "request_snapshot":
break;
default:
return false;
}
SendSnapshot();
return true;
}
private void SetRecording(bool enabled)
{
if (_recording == enabled)
return;
lock (_gate)
_recording = enabled;
if (enabled)
{
_subscription = global::ShrinkEventBus.EventBus.ObserveDiagnostics(Capture);
}
else
{
_subscription?.Dispose();
_subscription = null;
}
}
private void Capture(ShrinkEventDiagnostic diagnostic)
{
// 回调可能发生在工作线程:只留托管元数据,不触碰 Godot 对象或保留事件实例。
lock (_gate)
{
if (!_recording)
return;
var record = new TraceRecord(
diagnostic.TimestampUtc,
diagnostic.EventType,
diagnostic.BusKey.ToString(),
diagnostic.Result,
diagnostic.ThreadId,
diagnostic.ElapsedMilliseconds,
diagnostic.IsAsync);
if (_history.Count == Capacity)
{
_history.Dequeue();
_discarded++;
}
_history.Enqueue(record);
}
}
private void SendSnapshot()
{
var groups = new GArray();
var bindingCount = 0;
var buses = global::ShrinkEventBus.EventBus.CaptureNamedBuses();
foreach (var bus in buses)
{
foreach (var group in global::ShrinkEventBus.EventBus.CaptureBindings(bus).GroupBy(binding => binding.EventType))
{
var handlers = new GArray();
foreach (var binding in group)
{
handlers.Add(new GDictionary
{
["declaring_type"] = binding.Handler.DeclaringType?.FullName ?? "",
["method_name"] = binding.Handler.Name,
["is_static"] = binding.Handler.IsStatic
});
}
bindingCount += handlers.Count;
groups.Add(new GDictionary
{
["event_name"] = $"[{bus.Key}] {group.Key.FullName}",
["handlers"] = handlers
});
}
}
TraceRecord[] history;
long discarded;
lock (_gate)
{
history = [.. _history];
discarded = _discarded;
}
var records = new GArray();
foreach (var record in history)
{
records.Add(new GDictionary
{
["event_name"] = record.EventType.FullName ?? record.EventType.Name,
["timestamp_utc"] = record.Timestamp.ToString("O"),
["bus"] = record.Bus,
["handled"] = record.Result.Handled,
["accepted"] = record.Result.Accepted,
["is_canceled"] = record.Result.Canceled,
["failure"] = record.Result.Failure.ToString(),
["thread_id"] = record.ThreadId,
["elapsed_ms"] = record.ElapsedMs,
["is_async"] = record.IsAsync
});
}
EngineDebugger.SendMessage(Prefix + ":state", new GArray { new GDictionary
{
["debug_enabled"] = _recording,
["bus_count"] = buses.Count,
["binding_count"] = bindingCount,
["discarded_count"] = discarded,
["subscriber_groups"] = groups,
["recent_logs"] = records
} });
}
private sealed record TraceRecord(DateTime Timestamp, Type EventType, string Bus,
ShrinkPostResult Result, int ThreadId, double ElapsedMs, bool IsAsync);
#endif
}