-
Notifications
You must be signed in to change notification settings - Fork 693
Description
Reading components from a GameObject with a UIDocument component causes an infinite loop that freezes the MCP client completely (cannot even cancel the operation).
Root Cause
The UIDocument.rootVisualElement property returns a VisualElement which has circular parent/child references (children[] → child elements →
parent → back to parent). The generic reflection-based serializer follows these references indefinitely.
How to Reproduce
Minimal Reproduction:
- Create a GameObject
- Add UIDocument component
- Assign a visualTreeAsset (.uxml file)
- Assign a panelSettings asset
- Call: mcpforunity://scene/gameobject/{instanceID}/components
- Result: MCP client freezes indefinitely
Note: The issue does NOT occur if either visualTreeAsset or panelSettings is unassigned.
Proposed Fix
Add special handling for UIDocument in GameObjectSerializer.GetComponentData() (similar to existing handling for Transform and Camera):
// --- Special handling for UIDocument to avoid infinite loops from VisualElement hierarchy ---
if (componentType.FullName == "UnityEngine.UIElements.UIDocument")
{
var uiDocProperties = new Dictionary<string, object>();
try
{
// Get panelSettings reference safely
var panelSettingsProp = componentType.GetProperty("panelSettings");
if (panelSettingsProp != null)
{
var panelSettings = panelSettingsProp.GetValue(c) as UnityEngine.Object;
if (panelSettings != null)
{
var assetPath = AssetDatabase.GetAssetPath(panelSettings);
uiDocProperties["panelSettings"] = string.IsNullOrEmpty(assetPath)
? panelSettings.name
: assetPath;
}
}
// Get visualTreeAsset reference safely (the UXML file)
var visualTreeAssetProp = componentType.GetProperty("visualTreeAsset");
if (visualTreeAssetProp != null)
{
var visualTreeAsset = visualTreeAssetProp.GetValue(c) as UnityEngine.Object;
if (visualTreeAsset != null)
{
var assetPath = AssetDatabase.GetAssetPath(visualTreeAsset);
uiDocProperties["visualTreeAsset"] = string.IsNullOrEmpty(assetPath)
? visualTreeAsset.name
: assetPath;
}
}
// Get sortingOrder safely
var sortingOrderProp = componentType.GetProperty("sortingOrder");
if (sortingOrderProp != null)
{
uiDocProperties["sortingOrder"] = sortingOrderProp.GetValue(c);
}
// Get enabled state
var enabledProp = componentType.GetProperty("enabled");
if (enabledProp != null)
{
uiDocProperties["enabled"] = enabledProp.GetValue(c);
}
// NOTE: rootVisualElement is intentionally skipped - it contains circular
// parent/child references that cause infinite serialization loops
uiDocProperties["_note"] = "rootVisualElement skipped to prevent circular reference infinite loop";
}
catch (Exception e)
{
McpLog.Warn($"[GetComponentData] Error reading UIDocument properties: {e.Message}");
}
return new Dictionary<string, object>
{
{ "typeName", componentType.FullName },
{ "instanceID", c.GetInstanceID() },
{ "properties", uiDocProperties },
{ "name", c.name },
{ "tag", c.tag },
{ "gameObjectInstanceID", c.gameObject?.GetInstanceID() ?? 0 }
};
}
// --- End Special handling for UIDocument ---
File: Editor/Helpers/GameObjectSerializer.cs (insert after Camera special handling, around line 221)
I added that myself and it fixed that issue but might not be 100% fitting to your own code architecture.