ThreeKingdomsSlingshot/Assets/Scripts/Runtime/UI/UiLayerProbe.cs

102 lines
5.2 KiB
C#

using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Unity.Profiling;
using UnityEngine;
using UnityEngine.UI;
namespace TKS
{
/// <summary>
/// UI 층 구조 실측(PD 2026-11-10 캔버스 분할 검증) — 지금 화면의 렌더 통계(배치 · 드로우콜 · SetPass)와
/// 켜진 캔버스마다 그래픽 수 · 등급 · 레이캐스터 · 매 프레임 움직이는 요소(UiPulse · UiPop · 스크롤 내용 · 미니맵 · 커서)를 기록한다.
/// 「매 프레임 재구성 그래픽」 = 움직이는 요소가 든 캔버스들의 그래픽 수 합(캔버스는 자식 하나만 바뀌어도 전체를 다시 묶음).
/// </summary>
public static class UiLayerProbe
{
static ProfilerRecorder batches;
static ProfilerRecorder draws;
static ProfilerRecorder setPass;
static void Begin()
{
if (!batches.Valid) batches = ProfilerRecorder.StartNew(ProfilerCategory.Render, "Batches Count");
if (!draws.Valid) draws = ProfilerRecorder.StartNew(ProfilerCategory.Render, "Draw Calls Count");
if (!setPass.Valid) setPass = ProfilerRecorder.StartNew(ProfilerCategory.Render, "SetPass Calls Count");
}
/// <summary>몇 프레임 렌더 뒤 측정 → 로그 한 줄 + `ui_layers.csv`(캔버스별 행) 추가.</summary>
public static IEnumerator Measure(string shot, System.Action<string> log, string dir)
{
Begin();
for (int i = 0; i < 4; i++) yield return null;
long b = batches.Valid ? batches.LastValue : -1;
long d = draws.Valid ? draws.LastValue : -1;
long sp = setPass.Valid ? setPass.LastValue : -1;
#if UNITY_EDITOR
if (b <= 0) b = UnityEditor.UnityStats.batches;
if (d <= 0) d = UnityEditor.UnityStats.drawCalls;
if (sp <= 0) sp = UnityEditor.UnityStats.setPassCalls;
#endif
var movers = Movers();
var frameCanvases = new HashSet<Canvas>();
foreach (var m in movers)
{
var c = m.GetComponentInParent<Canvas>();
if (c != null) frameCanvases.Add(c);
}
var rows = new StringBuilder();
int roots = 0, nested = 0, graphics = 0, frameGraphics = 0;
foreach (var c in Object.FindObjectsByType<Canvas>(FindObjectsInactive.Exclude, FindObjectsSortMode.None))
{
if (!c.isActiveAndEnabled) continue;
int n = GraphicRegistry.GetGraphicsForCanvas(c).Count;
if (c.isRootCanvas) roots++;
else nested++;
graphics += n;
bool frame = frameCanvases.Contains(c);
if (frame) frameGraphics += n;
var tag = c.GetComponent<UiLayerInfo>();
rows.Append(shot).Append(',').Append(Path(c.transform)).Append(',').Append(c.isRootCanvas ? "root" : "nested").Append(',')
.Append(tag != null ? tag.rate.ToString() : "-").Append(',').Append(n).Append(',')
.Append(c.GetComponent<GraphicRaycaster>() != null ? "ray" : "-").Append(',').Append(frame ? "frame" : "-").Append('\n');
}
log("layers " + shot + " batches=" + b + " drawcalls=" + d + " setpass=" + sp + " canvases=" + roots + "+" + nested + " graphics=" + graphics + " frameRebuildGraphics=" + frameGraphics);
try
{
string csv = System.IO.Path.Combine(dir, "ui_layers.csv");
if (!File.Exists(csv)) File.WriteAllText(csv, "shot,canvas,kind,rate,graphics,raycaster,frame_mover\n", new UTF8Encoding(true));
File.AppendAllText(csv, rows.ToString(), new UTF8Encoding(false));
}
catch
{
}
}
/// <summary>매 프레임 바뀌는 요소(켜진 것만).</summary>
static List<Transform> Movers()
{
var list = new List<Transform>();
foreach (var p in Object.FindObjectsByType<OutGame.UiPulse>(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) list.Add(p.transform);
foreach (var p in Object.FindObjectsByType<OutGame.UiPop>(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) list.Add(p.transform);
foreach (var s in Object.FindObjectsByType<ScrollRect>(FindObjectsInactive.Exclude, FindObjectsSortMode.None))
if (s.content != null) list.Add(s.content);
foreach (var t in Object.FindObjectsByType<RectTransform>(FindObjectsInactive.Exclude, FindObjectsSortMode.None))
{
// 월드맵 커서(위아래 흔들림) · 전투 미니맵 시야 사각형(카메라 이동) · 유닛 점(이동)
if (t.name == "cursor") list.Add(t);
else if (t.name == "View" && t.parent != null && (t.parent.name == "Minimap" || t.parent.name == "MinimapLive")) list.Add(t);
}
return list;
}
static string Path(Transform t)
{
var s = t.name;
for (var p = t.parent; p != null; p = p.parent) s = p.name + "/" + s;
return s.Replace(',', ' ');
}
}
}