using System;
using System.Collections.Generic;
using System.Linq;
using Codely.Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityTcp.Editor.Helpers;
namespace UnityTcp.Editor.Notifier
{
///
/// Broadcasts the active scene's hierarchy to notifier clients whenever it
/// changes. The emitted payload mirrors the structure returned by
/// 's get_hierarchy action (including the
/// roots_only fallback for large scenes).
///
// [InitializeOnLoad] // disable this notifier
public static class SceneHierarchyNotifier
{
private const string EventType = "scene_hierarchy_changed";
// Matches ManageScene.GetSceneHierarchy cap.
private const int LargeSceneThreshold = 500;
// Debounce window: every event extends the deadline; flush only after this much idle time.
// Keeps flush rate sane during bursts of edits (drag, multi-select rename, scripted spawns).
private const double DebounceSeconds = 0.25;
private static double _flushDeadline;
private static bool _tickRegistered;
static SceneHierarchyNotifier()
{
EditorApplication.hierarchyChanged += OnHierarchyChanged;
EditorSceneManager.sceneOpened += OnSceneOpened;
EditorSceneManager.sceneClosed += OnSceneClosed;
EditorSceneManager.sceneSaved += OnSceneSaved;
EditorSceneManager.activeSceneChangedInEditMode += OnActiveSceneChanged;
}
// ---- Event hooks (debounced) -------------------------------------- //
private static void OnHierarchyChanged() => ScheduleFlush();
private static void OnSceneOpened(Scene s, OpenSceneMode mode) => ScheduleFlush();
private static void OnSceneClosed(Scene s) => ScheduleFlush();
private static void OnSceneSaved(Scene s) => ScheduleFlush();
private static void OnActiveSceneChanged(Scene previous, Scene current) => ScheduleFlush();
private static void ScheduleFlush()
{
_flushDeadline = EditorApplication.timeSinceStartup + DebounceSeconds;
if (_tickRegistered) return;
_tickRegistered = true;
EditorApplication.update += DebounceTick;
}
private static void DebounceTick()
{
if (EditorApplication.timeSinceStartup < _flushDeadline) return;
EditorApplication.update -= DebounceTick;
_tickRegistered = false;
Flush();
}
private static void Flush()
{
try
{
if (!UnityTcpBridge.IsRunning) return;
JObject payload = BuildHierarchyPayload();
if (payload == null) return;
UnityTcpBridge.NotifyAll(EventType, payload);
}
catch (Exception e)
{
try { CodelyLogger.LogError($"[SceneHierarchyNotifier] flush failed: {e.Message}"); } catch { }
}
}
// ---- Payload builder (shape mirrors ManageScene.GetSceneHierarchy) -- //
private static JObject BuildHierarchyPayload()
{
Scene activeScene = EditorSceneManager.GetActiveScene();
if (!activeScene.IsValid() || !activeScene.isLoaded)
{
return JObject.FromObject(new
{
sceneName = string.Empty,
scenePath = string.Empty,
partial = false,
mode = "empty",
totalObjectCount = 0,
rootCount = 0,
hierarchy = new List