feat(cordis): 完成阶段5配置与调试工具
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "ShrinkContext.AppAdapter.Editor",
|
||||
"rootNamespace": "ShrinkContext.AppAdapter.Editor",
|
||||
"references": [
|
||||
"ShrinkContext.AppAdapter.Runtime",
|
||||
"ShrinkContext.Core.Runtime",
|
||||
"UniTask"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 71b936124873ba345a9430e664e7cfcd
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,189 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace ShrinkContext.AppAdapter.Editor
|
||||
{
|
||||
public sealed class ShrinkContextBenchmarkReport
|
||||
{
|
||||
internal ShrinkContextBenchmarkReport(int unrelatedFiberCount, int reloadIterations,
|
||||
double notifyMilliseconds, long notificationDispatchCount, long notificationCandidateVisits,
|
||||
long naiveFiberVisits, int consumerApplyCount, int failureIterations,
|
||||
int restoredFailureCount, double restoreMilliseconds, long managedMemoryDeltaBytes)
|
||||
{
|
||||
UnrelatedFiberCount = unrelatedFiberCount;
|
||||
ReloadIterations = reloadIterations;
|
||||
NotifyMilliseconds = notifyMilliseconds;
|
||||
NotificationDispatchCount = notificationDispatchCount;
|
||||
NotificationCandidateVisits = notificationCandidateVisits;
|
||||
NaiveFiberVisits = naiveFiberVisits;
|
||||
ConsumerApplyCount = consumerApplyCount;
|
||||
FailureIterations = failureIterations;
|
||||
RestoredFailureCount = restoredFailureCount;
|
||||
RestoreMilliseconds = restoreMilliseconds;
|
||||
ManagedMemoryDeltaBytes = managedMemoryDeltaBytes;
|
||||
}
|
||||
|
||||
public int UnrelatedFiberCount { get; }
|
||||
public int ReloadIterations { get; }
|
||||
public double NotifyMilliseconds { get; }
|
||||
public long NotificationDispatchCount { get; }
|
||||
public long NotificationCandidateVisits { get; }
|
||||
public long NaiveFiberVisits { get; }
|
||||
public int ConsumerApplyCount { get; }
|
||||
public int FailureIterations { get; }
|
||||
public int RestoredFailureCount { get; }
|
||||
public double RestoreMilliseconds { get; }
|
||||
public long ManagedMemoryDeltaBytes { get; }
|
||||
}
|
||||
|
||||
/// <summary>Editor 可重复容量基准;耗时仅报告,不作为跨机器通过阈值。</summary>
|
||||
public static class ShrinkContextBenchmarkRunner
|
||||
{
|
||||
public static async UniTask<ShrinkContextBenchmarkReport> RunAsync(
|
||||
int unrelatedFiberCount = 1000, int reloadIterations = 100, int failureIterations = 25)
|
||||
{
|
||||
if (unrelatedFiberCount < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(unrelatedFiberCount));
|
||||
if (reloadIterations <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(reloadIterations));
|
||||
if (failureIterations <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(failureIterations));
|
||||
|
||||
var memoryBefore = GC.GetTotalMemory(false);
|
||||
var runtime = new ShrinkContextRuntime();
|
||||
var targetConsumer = new BenchmarkConsumer("benchmark.target");
|
||||
runtime.Use(targetConsumer);
|
||||
for (var i = 0; i < unrelatedFiberCount; i++)
|
||||
runtime.Use(new BenchmarkConsumer("benchmark.unrelated." + i));
|
||||
|
||||
var notifyStopwatch = Stopwatch.StartNew();
|
||||
for (var i = 0; i < reloadIterations; i++)
|
||||
{
|
||||
var provider = runtime.Use(new BenchmarkProvider("provider-" + i, "benchmark.target"));
|
||||
if (provider.LastError != null)
|
||||
throw new InvalidOperationException("Benchmark provider failed.", provider.LastError);
|
||||
await runtime.RetireAsync(provider);
|
||||
}
|
||||
notifyStopwatch.Stop();
|
||||
|
||||
var notifySnapshot = runtime.CaptureDiagnostic();
|
||||
var naiveFiberVisits = notifySnapshot.Notifications.DispatchCount * runtime.Fibers.Count;
|
||||
|
||||
var catalog = new ShrinkComponentCatalog();
|
||||
catalog.Register("stable", () => new BenchmarkProvider("stable", "benchmark.restore"));
|
||||
catalog.Register("failing", () => new BenchmarkFailingProvider("benchmark.restore"));
|
||||
var restoreRuntime = new ShrinkContextRuntime();
|
||||
var loader = new ShrinkContextLoader(restoreRuntime, catalog);
|
||||
await loader.ApplyAsync(new[] { new ShrinkLoaderEntry("provider", "stable") });
|
||||
|
||||
var restoredFailures = 0;
|
||||
var restoreStopwatch = Stopwatch.StartNew();
|
||||
for (var i = 0; i < failureIterations; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
await loader.ApplyAsync(new[] { new ShrinkLoaderEntry("provider", "failing") });
|
||||
}
|
||||
catch (ShrinkLoaderException)
|
||||
{
|
||||
if (loader.LastTransaction?.PreviousCompositionRestored == true &&
|
||||
loader.TryGetFiber("provider", out var restored) &&
|
||||
restored.State == ShrinkFiberState.Active)
|
||||
{
|
||||
restoredFailures++;
|
||||
}
|
||||
}
|
||||
}
|
||||
restoreStopwatch.Stop();
|
||||
|
||||
await runtime.ShutdownAsync();
|
||||
await restoreRuntime.ShutdownAsync();
|
||||
var memoryAfter = GC.GetTotalMemory(false);
|
||||
|
||||
return new ShrinkContextBenchmarkReport(
|
||||
unrelatedFiberCount,
|
||||
reloadIterations,
|
||||
notifyStopwatch.Elapsed.TotalMilliseconds,
|
||||
notifySnapshot.Notifications.DispatchCount,
|
||||
notifySnapshot.Notifications.CandidateVisitCount,
|
||||
naiveFiberVisits,
|
||||
targetConsumer.ApplyCount,
|
||||
failureIterations,
|
||||
restoredFailures,
|
||||
restoreStopwatch.Elapsed.TotalMilliseconds,
|
||||
memoryAfter - memoryBefore);
|
||||
}
|
||||
|
||||
private sealed class BenchmarkProvider : IShrinkComponent
|
||||
{
|
||||
private readonly string[] _provide;
|
||||
|
||||
public BenchmarkProvider(string name, string key)
|
||||
{
|
||||
Name = name;
|
||||
Key = key;
|
||||
_provide = new[] { key };
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
private string Key { get; }
|
||||
public IReadOnlyList<string> Inject => Array.Empty<string>();
|
||||
public IReadOnlyList<string> Provide => _provide;
|
||||
|
||||
public UniTask ApplyAsync(ShrinkCtx ctx, object? config)
|
||||
{
|
||||
ctx.Set(Key, Name);
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class BenchmarkConsumer : IShrinkComponent
|
||||
{
|
||||
private readonly string[] _inject;
|
||||
|
||||
public BenchmarkConsumer(string key)
|
||||
{
|
||||
Key = key;
|
||||
_inject = new[] { key };
|
||||
}
|
||||
|
||||
private string Key { get; }
|
||||
public string Name => "consumer:" + Key;
|
||||
public int ApplyCount { get; private set; }
|
||||
public IReadOnlyList<string> Inject => _inject;
|
||||
public IReadOnlyList<string> Provide => Array.Empty<string>();
|
||||
|
||||
public UniTask ApplyAsync(ShrinkCtx ctx, object? config)
|
||||
{
|
||||
ctx.Get<string>(Key);
|
||||
ApplyCount++;
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class BenchmarkFailingProvider : IShrinkComponent
|
||||
{
|
||||
private readonly string[] _provide;
|
||||
|
||||
public BenchmarkFailingProvider(string key)
|
||||
{
|
||||
Key = key;
|
||||
_provide = new[] { key };
|
||||
}
|
||||
|
||||
private string Key { get; }
|
||||
public string Name => "benchmark-failing";
|
||||
public IReadOnlyList<string> Inject => Array.Empty<string>();
|
||||
public IReadOnlyList<string> Provide => _provide;
|
||||
|
||||
public UniTask ApplyAsync(ShrinkCtx ctx, object? config)
|
||||
{
|
||||
ctx.Set(Key, Name);
|
||||
throw new InvalidOperationException("Expected benchmark replacement failure.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: db4f7dd3ed1ecab4e8a04fd83ca0b0fb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,370 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ShrinkContext.AppAdapter.Editor
|
||||
{
|
||||
public sealed class ShrinkContextDiagnosticsWindow : EditorWindow
|
||||
{
|
||||
private readonly string[] _tabs = { "运行时", "组合配置", "容量基准" };
|
||||
private int _selectedTab;
|
||||
private Vector2 _runtimeScroll;
|
||||
private Vector2 _jsonScroll;
|
||||
private Vector2 _benchmarkScroll;
|
||||
private bool _autoRefresh = true;
|
||||
private ShrinkAppCompositionProfile? _profile;
|
||||
private string _compositionJson = string.Empty;
|
||||
private string? _compositionError;
|
||||
private int _unrelatedFibers = 1000;
|
||||
private int _reloadIterations = 100;
|
||||
private int _failureIterations = 25;
|
||||
private bool _benchmarkRunning;
|
||||
private ShrinkContextBenchmarkReport? _benchmarkReport;
|
||||
private string? _benchmarkError;
|
||||
|
||||
[MenuItem("ShrinkSDK/Cordis/诊断与组合")]
|
||||
public static void ShowWindow()
|
||||
{
|
||||
var window = GetWindow<ShrinkContextDiagnosticsWindow>("Cordis Diagnostics");
|
||||
window.minSize = new Vector2(720f, 520f);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_profile = ShrinkAppCompositionProfile.LoadDefault();
|
||||
RefreshCompositionJson();
|
||||
}
|
||||
|
||||
private void OnInspectorUpdate()
|
||||
{
|
||||
if (_autoRefresh && _selectedTab == 0)
|
||||
Repaint();
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
DrawToolbar();
|
||||
switch (_selectedTab)
|
||||
{
|
||||
case 0:
|
||||
DrawRuntime();
|
||||
break;
|
||||
case 1:
|
||||
DrawComposition();
|
||||
break;
|
||||
case 2:
|
||||
DrawBenchmarks();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawToolbar()
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||
_selectedTab = GUILayout.Toolbar(_selectedTab, _tabs, EditorStyles.toolbarButton);
|
||||
GUILayout.FlexibleSpace();
|
||||
if (_selectedTab == 0)
|
||||
_autoRefresh = GUILayout.Toggle(_autoRefresh, "自动刷新", EditorStyles.toolbarButton);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void DrawRuntime()
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
EditorGUILayout.HelpBox("进入 Play Mode 后显示真实 ShrinkAppLoaderHost 状态。", MessageType.Info);
|
||||
DrawModDiagnostics();
|
||||
return;
|
||||
}
|
||||
|
||||
var bootstrap = ShrinkAppLoaderBootstrapper.Instance;
|
||||
if (bootstrap == null || bootstrap.Host == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("当前 Play Mode 没有 ShrinkAppLoaderBootstrapper。", MessageType.Warning);
|
||||
DrawModDiagnostics();
|
||||
return;
|
||||
}
|
||||
|
||||
var host = bootstrap.Host;
|
||||
var snapshot = host.Context.CaptureDiagnostic();
|
||||
EditorGUILayout.LabelField("ContextLoader", EditorStyles.boldLabel);
|
||||
EditorGUILayout.LabelField("状态", host.IsRunning ? "运行中" : "未启动");
|
||||
EditorGUILayout.LabelField("模块", $"active={host.ActiveModuleIds.Count}, waiting={host.WaitingModuleIds.Count}");
|
||||
EditorGUILayout.LabelField("余效应", $"bindings={snapshot.BindingCount}, indexedKeys={snapshot.Notifications.IndexedKeyCount}");
|
||||
EditorGUILayout.LabelField("通知", $"dispatch={snapshot.Notifications.DispatchCount}, candidateVisits={snapshot.Notifications.CandidateVisitCount}");
|
||||
|
||||
DrawTransaction(host.Loader.LastTransaction);
|
||||
EditorGUILayout.Space(6f);
|
||||
EditorGUILayout.LabelField($"Fibers ({snapshot.Fibers.Count})", EditorStyles.boldLabel);
|
||||
_runtimeScroll = EditorGUILayout.BeginScrollView(_runtimeScroll);
|
||||
foreach (var fiber in snapshot.Fibers)
|
||||
{
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField($"#{fiber.Uid} {fiber.Name}", EditorStyles.boldLabel);
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.LabelField(fiber.Status.ToString(), GUILayout.Width(90f));
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.LabelField("target", fiber.TargetProviderUids.Count == 0
|
||||
? "-"
|
||||
: string.Join(", ", fiber.TargetProviderUids));
|
||||
EditorGUILayout.LabelField("inject", fiber.Inject.Count == 0 ? "-" : string.Join(", ", fiber.Inject));
|
||||
EditorGUILayout.LabelField("provide", fiber.Provide.Count == 0 ? "-" : string.Join(", ", fiber.Provide));
|
||||
if (fiber.InterceptKeys.Count > 0)
|
||||
EditorGUILayout.LabelField("intercept", string.Join(", ", fiber.InterceptKeys));
|
||||
if (!string.IsNullOrWhiteSpace(fiber.ErrorMessage))
|
||||
EditorGUILayout.HelpBox($"{fiber.ErrorType}: {fiber.ErrorMessage}", MessageType.Error);
|
||||
foreach (var dependency in fiber.Dependencies.Where(item => !item.IsSatisfied))
|
||||
{
|
||||
EditorGUILayout.LabelField("waiting",
|
||||
$"{dependency.Key} @ {dependency.Realm}; potential=[{string.Join(", ", dependency.PotentialProviderUids)}]");
|
||||
}
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
EditorGUILayout.EndScrollView();
|
||||
DrawModDiagnostics();
|
||||
}
|
||||
|
||||
private static void DrawTransaction(ShrinkLoaderTransactionDiagnostic? transaction)
|
||||
{
|
||||
EditorGUILayout.Space(6f);
|
||||
EditorGUILayout.LabelField("最近事务", EditorStyles.boldLabel);
|
||||
if (transaction == null)
|
||||
{
|
||||
EditorGUILayout.LabelField("尚无事务。");
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("阶段", $"#{transaction.Generation} {transaction.Phase}");
|
||||
if (!string.IsNullOrWhiteSpace(transaction.CurrentEntryId))
|
||||
EditorGUILayout.LabelField("条目", transaction.CurrentEntryId);
|
||||
if (!string.IsNullOrWhiteSpace(transaction.ErrorMessage))
|
||||
{
|
||||
var restore = transaction.RestoreAttempted
|
||||
? $"restore={transaction.PreviousCompositionRestored}"
|
||||
: "restore=not-attempted";
|
||||
EditorGUILayout.HelpBox($"{transaction.ErrorType}: {transaction.ErrorMessage}\n{restore}",
|
||||
transaction.PreviousCompositionRestored ? MessageType.Warning : MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawComposition()
|
||||
{
|
||||
var selected = (ShrinkAppCompositionProfile?)EditorGUILayout.ObjectField(
|
||||
"Profile", _profile, typeof(ShrinkAppCompositionProfile), false);
|
||||
if (selected != _profile)
|
||||
{
|
||||
_profile = selected;
|
||||
RefreshCompositionJson();
|
||||
}
|
||||
|
||||
if (_profile == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox(
|
||||
"未选择 Composition Profile。可通过 Assets/Create/ShrinkSDK/Cordis/Composition Profile 创建。",
|
||||
MessageType.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("条目", _profile.Entries.Count.ToString());
|
||||
EditorGUILayout.LabelField("未列出条目", _profile.IncludeUnlistedEntries ? "保留" : "排除");
|
||||
if (Application.isPlaying)
|
||||
EditorGUILayout.HelpBox("修改资产只影响下一次宿主启动,不会重写当前运行中的组合。", MessageType.Info);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("从资产刷新 JSON"))
|
||||
RefreshCompositionJson();
|
||||
if (GUILayout.Button("将 JSON 写回资产"))
|
||||
ApplyCompositionJson();
|
||||
if (GUILayout.Button("导入 JSON..."))
|
||||
ImportCompositionJson();
|
||||
if (GUILayout.Button("导出 JSON..."))
|
||||
ExportCompositionJson();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_compositionError))
|
||||
EditorGUILayout.HelpBox(_compositionError, MessageType.Error);
|
||||
|
||||
_jsonScroll = EditorGUILayout.BeginScrollView(_jsonScroll);
|
||||
_compositionJson = EditorGUILayout.TextArea(_compositionJson, GUILayout.ExpandHeight(true));
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
private void RefreshCompositionJson()
|
||||
{
|
||||
try
|
||||
{
|
||||
_compositionJson = _profile != null ? _profile.ToJson() : string.Empty;
|
||||
_compositionError = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_compositionError = ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyCompositionJson()
|
||||
{
|
||||
if (_profile == null)
|
||||
return;
|
||||
try
|
||||
{
|
||||
var document = ShrinkAppCompositionDocument.FromJson(_compositionJson);
|
||||
Undo.RecordObject(_profile, "Apply Cordis composition JSON");
|
||||
_profile.SetDocument(document);
|
||||
EditorUtility.SetDirty(_profile);
|
||||
AssetDatabase.SaveAssets();
|
||||
_compositionError = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_compositionError = ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
private void ImportCompositionJson()
|
||||
{
|
||||
var path = EditorUtility.OpenFilePanel("导入 Cordis Composition", string.Empty, "json");
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return;
|
||||
try
|
||||
{
|
||||
_compositionJson = File.ReadAllText(path);
|
||||
ShrinkAppCompositionDocument.FromJson(_compositionJson);
|
||||
_compositionError = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_compositionError = ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
private void ExportCompositionJson()
|
||||
{
|
||||
var path = EditorUtility.SaveFilePanel("导出 Cordis Composition", string.Empty,
|
||||
"ShrinkAppComposition", "json");
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return;
|
||||
try
|
||||
{
|
||||
ShrinkAppCompositionDocument.FromJson(_compositionJson);
|
||||
File.WriteAllText(path, _compositionJson);
|
||||
_compositionError = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_compositionError = ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawBenchmarks()
|
||||
{
|
||||
EditorGUILayout.HelpBox(
|
||||
"耗时只用于本机前后对比;自动测试验证倒排索引候选规模和失败恢复次数,不设置毫秒阈值。",
|
||||
MessageType.Info);
|
||||
_unrelatedFibers = Mathf.Max(0, EditorGUILayout.IntField("无关 fibers", _unrelatedFibers));
|
||||
_reloadIterations = Mathf.Max(1, EditorGUILayout.IntField("provider 替换次数", _reloadIterations));
|
||||
_failureIterations = Mathf.Max(1, EditorGUILayout.IntField("失败恢复次数", _failureIterations));
|
||||
|
||||
EditorGUI.BeginDisabledGroup(_benchmarkRunning || Application.isPlaying);
|
||||
if (GUILayout.Button(_benchmarkRunning ? "运行中..." : "运行基准"))
|
||||
RunBenchmarksAsync().Forget();
|
||||
EditorGUI.EndDisabledGroup();
|
||||
if (Application.isPlaying)
|
||||
EditorGUILayout.HelpBox("容量基准仅在 Edit Mode 运行,避免污染当前游戏上下文。", MessageType.Warning);
|
||||
if (!string.IsNullOrWhiteSpace(_benchmarkError))
|
||||
EditorGUILayout.HelpBox(_benchmarkError, MessageType.Error);
|
||||
|
||||
_benchmarkScroll = EditorGUILayout.BeginScrollView(_benchmarkScroll);
|
||||
if (_benchmarkReport != null)
|
||||
{
|
||||
EditorGUILayout.LabelField("Notify / Reload", EditorStyles.boldLabel);
|
||||
EditorGUILayout.LabelField("耗时", $"{_benchmarkReport.NotifyMilliseconds:F3} ms");
|
||||
EditorGUILayout.LabelField("dispatch", _benchmarkReport.NotificationDispatchCount.ToString());
|
||||
EditorGUILayout.LabelField("indexed candidate visits", _benchmarkReport.NotificationCandidateVisits.ToString());
|
||||
EditorGUILayout.LabelField("naive all-fiber visits", _benchmarkReport.NaiveFiberVisits.ToString());
|
||||
EditorGUILayout.LabelField("consumer apply", $"{_benchmarkReport.ConsumerApplyCount}/{_benchmarkReport.ReloadIterations}");
|
||||
EditorGUILayout.Space(6f);
|
||||
EditorGUILayout.LabelField("失败恢复", EditorStyles.boldLabel);
|
||||
EditorGUILayout.LabelField("耗时", $"{_benchmarkReport.RestoreMilliseconds:F3} ms");
|
||||
EditorGUILayout.LabelField("恢复成功", $"{_benchmarkReport.RestoredFailureCount}/{_benchmarkReport.FailureIterations}");
|
||||
EditorGUILayout.LabelField("GC 管理堆差值(噪声参考)", $"{_benchmarkReport.ManagedMemoryDeltaBytes} bytes");
|
||||
}
|
||||
EditorGUILayout.EndScrollView();
|
||||
DrawModDiagnostics();
|
||||
}
|
||||
|
||||
private async UniTaskVoid RunBenchmarksAsync()
|
||||
{
|
||||
_benchmarkRunning = true;
|
||||
_benchmarkError = null;
|
||||
try
|
||||
{
|
||||
_benchmarkReport = await ShrinkContextBenchmarkRunner.RunAsync(
|
||||
_unrelatedFibers, _reloadIterations, _failureIterations);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_benchmarkError = ex.ToString();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_benchmarkRunning = false;
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawModDiagnostics()
|
||||
{
|
||||
EditorGUILayout.Space(8f);
|
||||
EditorGUILayout.LabelField("外部程序集常驻", EditorStyles.boldLabel);
|
||||
var snapshot = CaptureOptionalModDiagnostic();
|
||||
if (snapshot == null)
|
||||
{
|
||||
EditorGUILayout.LabelField("ShrinkModFramework 未载入或未提供诊断 API。");
|
||||
return;
|
||||
}
|
||||
|
||||
var current = ReadProperty(snapshot, "CurrentRevisionCount");
|
||||
var resident = ReadProperty(snapshot, "ResidentRevisionCount");
|
||||
var bytes = ReadProperty(snapshot, "EstimatedResidentBytes");
|
||||
var limit = ReadProperty(snapshot, "SoftLimit");
|
||||
var exceeded = ReadProperty(snapshot, "IsSoftLimitExceeded");
|
||||
EditorGUILayout.LabelField("汇总",
|
||||
$"current={current}, resident={resident}, bytes={bytes}, softLimit={limit}, exceeded={exceeded}");
|
||||
|
||||
if (ReadProperty(snapshot, "ResidentRevisions") is not IEnumerable revisions)
|
||||
return;
|
||||
foreach (var revision in revisions)
|
||||
{
|
||||
if (revision == null)
|
||||
continue;
|
||||
var marker = Equals(ReadProperty(revision, "IsCurrent"), true) ? "current" : "resident";
|
||||
var name = ReadProperty(revision, "AssemblyName");
|
||||
var hash = ReadProperty(revision, "Revision")?.ToString() ?? string.Empty;
|
||||
if (hash.Length > 12)
|
||||
hash = hash.Substring(0, 12);
|
||||
EditorGUILayout.LabelField($"{marker}: {name}",
|
||||
$"{hash}, {ReadProperty(revision, "LoadedBytes")} bytes");
|
||||
}
|
||||
}
|
||||
|
||||
private static object? CaptureOptionalModDiagnostic()
|
||||
{
|
||||
var type = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.Select(assembly => assembly.GetType("ShrinkModFramework.ShrinkModDiagnostics", false))
|
||||
.FirstOrDefault(candidate => candidate != null);
|
||||
var method = type?.GetMethod("CaptureExternalAssemblies", BindingFlags.Public | BindingFlags.Static);
|
||||
return method?.Invoke(null, new object?[] { null });
|
||||
}
|
||||
|
||||
private static object? ReadProperty(object instance, string name) =>
|
||||
instance.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(instance);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b6ea571e4b10e2488d1024c473528b1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user