feat: integrate composable Godot runtime and EventBus debugger
This commit is contained in:
55 files changed
+1481
-114
No files matched your search
+194
@@ -0,0 +1,194 @@
|
||||
#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
|
||||
}
|
||||
Reference in new issue
Block a user