195 lines
5.8 KiB
C#
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
|
|
}
|