Skip to content

UIDocument component causes infinite loop when serializing components #585

@MarkSpectarium

Description

@MarkSpectarium

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:

  1. Create a GameObject
  2. Add UIDocument component
  3. Assign a visualTreeAsset (.uxml file)
  4. Assign a panelSettings asset
  5. Call: mcpforunity://scene/gameobject/{instanceID}/components
  6. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions