Files
daluandou/.codely.packages/cn.tuanjie.codely.bridge@1.0.85-47f31100/Editor/Bridge/Helpers/StateComposer.cs
T
huadexuan 13d0c80335 ```
chore(bridge): 删除桥接包的提交记录和Git属性配置

删除了 .codely-bridge-commit 文件中存储的提交哈希值
移除了 .gitattributes 文件中的行结束符配置规则
```
2026-09-24 21:41:14 +08:00

432 lines
15 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
#if UNITY_2021_2_OR_NEWER
using UnityEditor.Build;
#endif
using UnityEngine;
namespace UnityTcp.Editor.Helpers
{
/// <summary>
/// Centralized state composition for Unity Editor state.
/// State is always computed fresh from the live editor, and only when a
/// client explicitly asks for it (manage_editor.get_state). There is no
/// revision counter, dirty tracking, delta generation, or staleness
/// notification.
/// </summary>
public static class StateComposer
{
// Console state tracking (shared with ReadConsole)
private static int _consoleUnreadCount = 0;
private static readonly List<object> _lastConsoleErrors = new List<object>();
private static readonly object _consoleLock = new object();
// Touched assets tracking
private static readonly List<object> _touchedAssets = new List<object>();
private static readonly object _assetsLock = new object();
/// <summary>
/// Builds a complete, up-to-date Unity state snapshot.
/// </summary>
public static object BuildFullState()
{
var state = new
{
editor = BuildEditorState(),
project = BuildProjectState(),
scene = BuildSceneState(),
selection = BuildSelectionState(),
console = BuildConsoleState(),
assets = BuildAssetsState(),
policy = BuildPolicyState()
};
return state;
}
/// <summary>
/// Builds editor-specific state.
/// </summary>
public static object BuildEditorState()
{
var playMode = EditorApplication.isPlaying ? "playing" :
(EditorApplication.isPaused ? "paused" : "stopped");
// Get focused window
string focusedWindow = null;
if (EditorWindow.focusedWindow != null)
{
focusedWindow = EditorWindow.focusedWindow.GetType().Name;
}
// Determine if operations require focus
// This is a heuristic - some operations need the editor to be focused
bool requiresFocusForOperations = DetermineIfFocusRequired();
return new
{
playMode = playMode,
focusedWindow = focusedWindow,
requiresFocusForOperations = requiresFocusForOperations,
isCompiling = EditorApplication.isCompiling,
isUpdating = EditorApplication.isUpdating,
lastCompilation = BuildLastCompilationState(),
timeSinceStartup = (float)EditorApplication.timeSinceStartup
};
}
/// <summary>
/// Builds last compilation state.
///
/// NOTE:
/// - This is intentionally minimal and only reports whether Unity is
/// currently compiling ("started" vs "idle").
/// - It is NOT a per-compilation snapshot and does NOT expose error/
/// warning counts for any specific pipeline.
/// - For accurate diagnostics (including error/warning counts), callers
/// must use the start_compilation_pipeline response data and the
/// Unity console (read_console / unity_console).
/// </summary>
private static object BuildLastCompilationState()
{
var status = EditorApplication.isCompiling ? "started" : "idle";
return new
{
status = status
};
}
/// <summary>
/// Determines if current operations require focus.
/// </summary>
private static bool DetermineIfFocusRequired()
{
// Heuristic: Some operations need focus, especially during Play mode
// or when performing visual operations like scene manipulation
if (EditorApplication.isPlaying || EditorApplication.isPaused)
{
return true;
}
// Check if SceneView needs focus for certain operations
var sceneView = EditorWindow.focusedWindow as SceneView;
if (sceneView != null)
{
return false; // Already focused
}
return false; // Default: focus not strictly required
}
/// <summary>
/// Builds project-specific state.
/// </summary>
public static object BuildProjectState()
{
// Detect Render Pipeline
string srp = "builtin";
var currentRP = UnityEngine.Rendering.GraphicsSettings.currentRenderPipeline;
if (currentRP != null)
{
string rpName = currentRP.GetType().Name.ToLowerInvariant();
if (rpName.Contains("urp") || rpName.Contains("universal"))
{
srp = "urp";
}
else if (rpName.Contains("hdrp") || rpName.Contains("highdefinition"))
{
srp = "hdrp";
}
}
return new
{
srp = srp,
defineSymbols = GetScriptingDefineSymbols(),
packages = GetInstalledPackages(),
dirty = false // Would track if project settings are modified
};
}
private static string[] GetScriptingDefineSymbols()
{
// Get scripting define symbols for current build target
var buildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup;
#if UNITY_2021_2_OR_NEWER
var symbols = PlayerSettings.GetScriptingDefineSymbols(
NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup));
#else
var symbols = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup);
#endif
return string.IsNullOrEmpty(symbols) ?
new string[0] :
symbols.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
}
private static string[] GetInstalledPackages()
{
// Simplified - in production would use PackageManager API
return new string[0];
}
/// <summary>
/// Builds scene-specific state.
/// </summary>
public static object BuildSceneState()
{
var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
return new
{
activeScenePath = activeScene.path,
dirty = activeScene.isDirty,
hasNavMeshData = HasNavMeshData(),
hasLightingData = HasLightingData()
};
}
private static bool HasNavMeshData()
{
// Check if current scene has NavMesh data using runtime reflection
try
{
// First, try to check NavMeshSurface components (com.unity.ai.navigation package)
Type navMeshSurfaceType = Type.GetType("Unity.AI.Navigation.NavMeshSurface, Unity.AI.Navigation");
if (navMeshSurfaceType == null)
{
// Fallback: search in loaded assemblies
foreach (var assembly in System.AppDomain.CurrentDomain.GetAssemblies())
{
navMeshSurfaceType = assembly.GetType("Unity.AI.Navigation.NavMeshSurface");
if (navMeshSurfaceType != null) break;
}
}
if (navMeshSurfaceType != null)
{
// Check NavMeshSurface components for navMeshData
var activeSurfacesProperty = navMeshSurfaceType.GetProperty("activeSurfaces", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
if (activeSurfacesProperty != null)
{
var activeSurfaces = activeSurfacesProperty.GetValue(null);
if (activeSurfaces is System.Collections.IList surfaceList && surfaceList.Count > 0)
{
var navMeshDataProperty = navMeshSurfaceType.GetProperty("navMeshData");
if (navMeshDataProperty != null)
{
foreach (var surface in surfaceList)
{
if (surface != null)
{
var navMeshData = navMeshDataProperty.GetValue(surface);
if (navMeshData != null)
{
return true;
}
}
}
}
}
}
// Also check all NavMeshSurface components in the scene (including inactive)
var allSurfaces = Resources.FindObjectsOfTypeAll(navMeshSurfaceType);
if (allSurfaces != null && allSurfaces.Length > 0)
{
var navMeshDataProperty = navMeshSurfaceType.GetProperty("navMeshData");
if (navMeshDataProperty != null)
{
foreach (var surface in allSurfaces)
{
if (surface != null)
{
var navMeshData = navMeshDataProperty.GetValue(surface);
if (navMeshData != null)
{
return true;
}
}
}
}
}
}
// Fallback: Try to find NavMesh type using reflection (for built-in NavMesh)
Type navMeshType = Type.GetType("UnityEngine.AI.NavMesh, UnityEngine.AIModule");
if (navMeshType == null)
{
// Fallback: search in loaded assemblies
foreach (var assembly in System.AppDomain.CurrentDomain.GetAssemblies())
{
navMeshType = assembly.GetType("UnityEngine.AI.NavMesh");
if (navMeshType != null) break;
}
}
if (navMeshType == null)
return false;
// Get CalculateTriangulation method
MethodInfo calculateTriangulationMethod = navMeshType.GetMethod("CalculateTriangulation", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
if (calculateTriangulationMethod == null)
return false;
// Call CalculateTriangulation using reflection
var triangulation = calculateTriangulationMethod.Invoke(null, null);
if (triangulation == null)
return false;
// Get vertices property
var verticesProperty = triangulation.GetType().GetProperty("vertices");
if (verticesProperty == null)
return false;
var vertices = verticesProperty.GetValue(triangulation) as Array;
return vertices != null && vertices.Length > 0;
}
catch
{
// If any error occurs, assume no NavMesh data
return false;
}
}
private static bool HasLightingData()
{
// Check if current scene has baked lighting
return Lightmapping.lightingDataAsset != null;
}
/// <summary>
/// Builds selection state.
/// </summary>
public static object BuildSelectionState()
{
var activeObject = Selection.activeGameObject;
object activeObjectInfo = null;
if (activeObject != null)
{
activeObjectInfo = new
{
id = activeObject.GetStableInstanceId(),
name = activeObject.name,
hierarchy_path = GetHierarchyPath(activeObject)
};
}
return new
{
activeObject = activeObjectInfo
};
}
private static string GetHierarchyPath(GameObject go)
{
if (go == null) return "";
var path = go.name;
var parent = go.transform.parent;
while (parent != null)
{
path = parent.name + "/" + path;
parent = parent.parent;
}
return path;
}
/// <summary>
/// Builds console state with real tracking data.
/// </summary>
public static object BuildConsoleState()
{
lock (_consoleLock)
{
return new
{
unreadCount = _consoleUnreadCount,
lastErrors = _lastConsoleErrors.ToArray()
};
}
}
/// <summary>
/// Updates console state tracking. Called by ReadConsole.
/// </summary>
public static void UpdateConsoleState(int unreadCount = 0, object[] lastErrors = null)
{
lock (_consoleLock)
{
_consoleUnreadCount = unreadCount;
_lastConsoleErrors.Clear();
if (lastErrors != null)
{
_lastConsoleErrors.AddRange(lastErrors);
}
}
}
/// <summary>
/// Builds assets state with tracked touched assets.
/// </summary>
public static object BuildAssetsState()
{
lock (_assetsLock)
{
return new
{
touched = _touchedAssets.ToArray()
};
}
}
/// <summary>
/// Adds a touched asset to tracking. Called by asset operations.
/// </summary>
public static void AddTouchedAsset(string path, bool imported = false, bool hasMeta = true)
{
lock (_assetsLock)
{
_touchedAssets.Add(new { path, imported, hasMeta });
// Keep only last 100 entries
while (_touchedAssets.Count > 100)
{
_touchedAssets.RemoveAt(0);
}
}
}
/// <summary>
/// Clears touched assets list.
/// </summary>
public static void ClearTouchedAssets()
{
lock (_assetsLock)
{
_touchedAssets.Clear();
}
}
/// <summary>
/// Builds policy state.
/// </summary>
public static object BuildPolicyState()
{
return new
{
writeGuardInPlayMode = WriteGuard.GetPolicyString(),
// UnityStateDirtyHook coalesces reimports onto EditorApplication.delayCall
// (see ProcessPendingNotifications), so refresh is still batched.
refreshMode = "debounced",
consoleReadPolicy = "clear_then_read"
};
}
}
}