using UnityEngine; using Unity.Pipeline.CodeReload; namespace Unity.Pipeline { /// /// Minimal on-screen code-reload status for players: what is happening now (compiling → applied, /// or failed) and how many method overrides are active. Deliberately IMGUI — no canvas, /// EventSystem or render-pipeline dependency, and it is development-only anyway (the body /// compiles out of release players; the type itself stays so AddComponent call sites compile). /// Added automatically by when its /// reload-overlay flag is on. Hidden entirely while idle with no overrides. /// [DisallowMultipleComponent] class CodeReloadStatusOverlay : MonoBehaviour { #if UNITY_EDITOR || DEVELOPMENT_BUILD || ENABLE_RUNTIME_PIPELINE // How long the "applied" confirmation stays up before falling back to the count line. private const int AppliedFlashMs = 3500; // A "compiling…" with no follow-up payload or failure notice goes stale (editor died, // connection dropped) — stop showing it after this. private const int CompilingStaleMs = 15000; private const float StatsPollSeconds = 1f; private const int MaxMessageLength = 96; private int m_OverrideCount; private int m_CallsPerSecond; private long m_LastCallCount; private float m_LastPollTime; private float m_NextStatsPoll; private GUIStyle m_Style; private void Update() { // GetStats() allocates; poll at 1 Hz instead of per frame. if (Time.unscaledTime >= m_NextStatsPoll) { m_NextStatsPoll = Time.unscaledTime + StatsPollSeconds; m_OverrideCount = CodeReloadRegistry.GetStats().ActiveOverrideCount; long calls = CodeReloadActivity.OverrideCallCount; float elapsed = Time.unscaledTime - m_LastPollTime; if (m_LastPollTime > 0 && elapsed > 0) m_CallsPerSecond = (int)((calls - m_LastCallCount) / elapsed); m_LastCallCount = calls; m_LastPollTime = Time.unscaledTime; } } private void OnGUI() { var phase = CodeReloadActivity.Snapshot(out var message, out var ageMs); string activity = null; Color activityColor = Color.white; switch (phase) { case CodeReloadActivity.Phase.Compiling when ageMs < CompilingStaleMs: activity = $"compiling {Truncate(message)}…"; activityColor = new Color(1f, 0.8f, 0.25f); // amber break; case CodeReloadActivity.Phase.Applied when ageMs < AppliedFlashMs: activity = Truncate(message); activityColor = new Color(0.45f, 1f, 0.45f); // green break; case CodeReloadActivity.Phase.Failed: activity = Truncate(message); // sticky until the next reload attempt activityColor = new Color(1f, 0.45f, 0.45f); // red break; } if (activity == null && m_OverrideCount == 0) return; // nothing to show m_Style ??= new GUIStyle(GUI.skin.label) { fontSize = 12, wordWrap = false }; var lines = activity != null ? new[] { ($"CodeReload: {activity}", activityColor), (CountLine(), Color.white) } : new[] { (CountLine(), Color.white) }; float width = 0, height = 4; foreach (var l in lines) { var s = m_Style.CalcSize(new GUIContent(l.Item1)); if (s.x > width) width = s.x; height += s.y; } var box = new Rect(8, 8, width + 12, height + 4); GUI.color = new Color(0f, 0f, 0f, 0.6f); GUI.DrawTexture(box, Texture2D.whiteTexture); float y = box.y + 4; foreach (var l in lines) { var size = m_Style.CalcSize(new GUIContent(l.Item1)); var r = new Rect(box.x + 6, y, size.x, size.y); // 1px shadow keeps it readable over bright scenes. GUI.color = new Color(0f, 0f, 0f, 0.9f); GUI.Label(new Rect(r.x + 1, r.y + 1, r.width, r.height), l.Item1, m_Style); GUI.color = l.Item2; GUI.Label(r, l.Item1, m_Style); y += size.y; } GUI.color = Color.white; } // "N overrides · 0 calls/s" is a diagnostic in itself: bound but never dispatched. private string CountLine() => $"{m_OverrideCount} override(s) active · {m_CallsPerSecond} calls/s"; private static string Truncate(string s) { if (string.IsNullOrEmpty(s)) return ""; int nl = s.IndexOf('\n'); if (nl >= 0) s = s.Substring(0, nl); // failures can carry multi-line diagnostics return s.Length <= MaxMessageLength ? s : s.Substring(0, MaxMessageLength) + "…"; } #endif } }