Skip to content

Conversation

dsarno
Copy link
Collaborator

@dsarno dsarno commented Oct 20, 2025

Prevent material/mesh instantiation warnings in edit mode

  • GameObjectSerializer uses sharedMaterial/sharedMesh in edit mode
  • Add comprehensive tests with instanceID checks
  • Fix test resource leaks and reflection bugs
  • Ensure no Unity instantiation warnings during serialization

Addresses #323

Summary by CodeRabbit

  • Bug Fixes

    • Fixed unintended material and mesh instantiation warnings in editor mode by optimizing property access during serialization.
  • New Features

    • Added optional parameter to control inclusion of non-public serialized fields during component data extraction.
  • Tests

    • Expanded test coverage for component data serialization, including material, mesh, and field visibility handling in editor mode.

- GameObjectSerializer: in edit mode, normalize material/mesh access
  - material -> sharedMaterial
  - materials -> sharedMaterials
  - mesh -> sharedMesh
  - also guard materials/sharedMaterial/sharedMaterials property names
- Tests: add strong instanceID equality checks for Renderer/MeshFilter
  - prove no instantiation by shared instanceID before/after
  - cover multi-material, null cases; prune brittle presence/console tests
- Clean up and relax ManageGameObject tests to avoid false negatives
…[SerializeField]; fix tests to destroy temp primitives and use dictionary access; strengthen instanceID assertions
- ManageGameObjectTests.cs: Destroy temporary primitive GameObject after extracting sharedMesh
- MaterialMeshInstantiationTests.cs: Destroy instantiated uniqueMesh after test completion

These fixes prevent resource leaks in Unity test files by properly cleaning up temporary objects created during tests.
- Replace incorrect reflection-based field access with proper dictionary key access
- GetComponentData() returns Dictionary<string, object> where 'properties' is a key, not a field
- Tests now properly access the properties dictionary and run assertions
- Fixes ineffective tests that were silently failing due to reflection returning null
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 20, 2025

Walkthrough

GameObjectSerializer.GetComponentData gains an includeNonPublicSerializedFields parameter controlling field serialization visibility. Edit-mode handling uses sharedMaterial/sharedMesh/sharedMaterials to prevent material/mesh instantiation. Caching now incorporates this flag. Comprehensive test coverage validates the new behavior across multiple Unity component scenarios.

Changes

Cohort / File(s) Summary
Core Serialization Logic
MCPForUnity/Editor/Helpers/GameObjectSerializer.cs
Updated GetComponentData method signature with includeNonPublicSerializedFields parameter (default true). Refactored field inclusion logic to respect new parameter. Added edit-mode detection to use shared material/mesh variants to avoid instantiation warnings. Updated cache key to include the new flag.
Unit Tests – Core Tests
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs
Added 6 new test methods: GetComponentData_DoesNotInstantiateMaterialsInEditMode(), GetComponentData_DoesNotInstantiateMeshesInEditMode(), GetComponentData_UsesSharedMaterialInEditMode(), GetComponentData_UsesSharedMeshInEditMode(), GetComponentData_HandlesNullMaterialsAndMeshes(), GetComponentData_WorksWithMultipleMaterials().
Unit Tests – Material/Mesh Instantiation Suite
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/MaterialMeshInstantiationTests.cs
New test class with 8 test methods and setup/teardown routines validating material and mesh handling: GetComponentData_UsesSharedMaterialInsteadOfMaterial(), GetComponentData_UsesSharedMeshInsteadOfMesh(), GetComponentData_HandlesNullSharedMaterial(), GetComponentData_HandlesNullSharedMesh(), GetComponentData_WorksWithMultipleSharedMaterials(), GetComponentData_EditModeDetectionWorks().

Sequence Diagram

sequenceDiagram
    participant Caller
    participant GetComponentData
    participant FieldLogic
    participant EditModeCheck
    participant PropertyAccess

    Caller->>GetComponentData: GetComponentData(component, includeNonPublicSerializedFields)
    GetComponentData->>FieldLogic: Determine shouldInclude based on flag
    
    alt includeNonPublicSerializedFields = true
        FieldLogic->>FieldLogic: Include public + SerializeField (non-public)
    else includeNonPublicSerializedFields = false
        FieldLogic->>FieldLogic: Include only public fields
    end
    
    GetComponentData->>EditModeCheck: Check Application.isPlaying
    
    alt In Edit Mode
        EditModeCheck->>PropertyAccess: Use sharedMaterial/sharedMesh
        PropertyAccess-->>GetComponentData: Material/Mesh reference (no instantiation)
    else In Play Mode
        EditModeCheck->>PropertyAccess: Use material/mesh
        PropertyAccess-->>GetComponentData: Material/Mesh instance
    end
    
    GetComponentData-->>Caller: Serialized component data dict
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

The changes introduce conditional logic in field serialization and add edit-mode branching for property access, requiring careful verification of caching coherence and material/mesh handling paths. Test additions are largely homogeneous, but the implementation change warrants thorough review of the visibility flag logic and edit-mode detection patterns.

Possibly related PRs

  • Autoformat code #297: Modifies the same GetComponentData method to introduce includeNonPublicSerializedFields parameter with matching caching strategy keyed by (Type, bool).
  • Feat/robust component resolver #256: Evolves the serialization code path by threading the includeNonPublicSerializedFields flag through component-property extraction and edit-mode handling.

Poem

🐰 Materials shimmer, meshes dance,
No instantiation by happenstance!
Shared variants spare the editor's cry,
While tests ensure all systems fly. ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Fix material mesh instantiation warnings" is directly aligned with the primary objective of this pull request. The changeset modifies GameObjectSerializer to use sharedMaterial/sharedMesh properties in edit mode to prevent Unity instantiation warnings, and adds comprehensive test coverage to validate this behavior. The title clearly and concisely captures this main fix without unnecessary details or vague terminology, making it immediately understandable to someone scanning the commit history.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (7)
MCPForUnity/Editor/Helpers/GameObjectSerializer.cs (1)

362-389: Centralize edit‑mode material/mesh handling

Logic is correct. Consider extracting this into a small helper to avoid drift and make future extensions (e.g., other components with shared* patterns) trivial.

-                    object value;
-                    if (!Application.isPlaying && (propName == "material" || propName == "materials" || propName == "mesh"))
+                    object value;
+                    if (!Application.isPlaying && (propName == "material" || propName == "materials" || propName == "mesh"))
                     {
-                        // In edit mode, use sharedMaterial/sharedMesh to avoid instantiation warnings
-                        if ((propName == "material" || propName == "materials") && c is Renderer renderer)
-                        {
-                            if (propName == "material")
-                                value = renderer.sharedMaterial;
-                            else // materials
-                                value = renderer.sharedMaterials;
-                        }
-                        else if (propName == "mesh" && c is MeshFilter meshFilter)
-                        {
-                            value = meshFilter.sharedMesh;
-                        }
-                        else
-                        {
-                            // Fallback to normal property access if type doesn't match
-                            value = propInfo.GetValue(c);
-                        }
+                        value = GetSharedValueForEditMode(c, propInfo, propName) ?? propInfo.GetValue(c);
                     }
                     else
                     {
                         value = propInfo.GetValue(c);
                     }

Add within this class:

private static object GetSharedValueForEditMode(Component c, PropertyInfo propInfo, string propName)
{
    if ((propName == "material" || propName == "materials") && c is Renderer r)
        return propName == "material" ? (object)r.sharedMaterial : r.sharedMaterials;
    if (propName == "mesh" && c is MeshFilter mf)
        return mf.sharedMesh;
    return null;
}
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs (3)

365-377: Remove unused MeshFilter from the materials test

The MeshFilter is created and assigned but never used in this test, adding noise and allocations.

-            var meshRenderer = testObject.AddComponent<MeshRenderer>();
-            var meshFilter = testObject.AddComponent<MeshFilter>();
+            var meshRenderer = testObject.AddComponent<MeshRenderer>();
@@
-            meshFilter.sharedMesh = testMesh;

454-456: Set HideFlags on test materials to avoid editor asset clutter if a test aborts

Minor robustness: mark throwaway materials as HideAndDontSave to prevent leaks across domain reloads on failure.

-            var testMaterial = new Material(Shader.Find("Standard"));
+            var testMaterial = new Material(Shader.Find("Standard")) { hideFlags = HideFlags.HideAndDontSave };
@@
-            var material1 = new Material(Shader.Find("Standard"));
+            var material1 = new Material(Shader.Find("Standard")) { hideFlags = HideFlags.HideAndDontSave };
             material1.name = "TestMaterial1";
-            var material2 = new Material(Shader.Find("Standard"));
+            var material2 = new Material(Shader.Find("Standard")) { hideFlags = HideFlags.HideAndDontSave };
             material2.name = "TestMaterial2";

Also applies to: 541-547


489-491: Avoid renaming shared primitive meshes

Renaming the shared primitive mesh mutates a shared asset and can affect other tests. Skip renaming or use an instantiated mesh when you need to mutate.

-            testMesh.name = "TestMesh";
             meshFilter.sharedMesh = testMesh;
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/MaterialMeshInstantiationTests.cs (3)

30-37: Harden shader choice and mark temp assets as hidden

On some pipelines, "Standard" may be missing. Add a small fallback and set HideFlags to avoid editor leaks.

-            testMaterial = new Material(Shader.Find("Standard"));
+            var shader = Shader.Find("Standard")
+                      ?? Shader.Find("Universal Render Pipeline/Lit")
+                      ?? Shader.Find("Unlit/Color");
+            Assert.IsNotNull(shader, "Could not find a fallback shader for test material.");
+            testMaterial = new Material(shader) { hideFlags = HideFlags.HideAndDontSave };

97-126: Mark instantiated meshes as hidden to avoid lingering objects on failure

You destroy the mesh—good. Adding HideFlags is a belt-and-suspenders improvement.

-            var uniqueMesh = UnityEngine.Object.Instantiate(testMesh);
+            var uniqueMesh = UnityEngine.Object.Instantiate(testMesh);
+            uniqueMesh.hideFlags = HideFlags.HideAndDontSave;

54-91: Assert edit‑mode context explicitly

Optionally assert/assume the tests are running in edit mode to make failures clearer when misconfigured.

         [Test]
         public void GetComponentData_UsesSharedMaterialInsteadOfMaterial()
         {
+            Assume.That(Application.isPlaying, Is.False, "This test must run in edit mode.");
@@
         [Test]
         public void GetComponentData_UsesSharedMeshInsteadOfMesh()
         {
+            Assume.That(Application.isPlaying, Is.False, "This test must run in edit mode.");
@@
         [Test]
         public void GetComponentData_EditModeDetectionWorks()
         {
-            // This test verifies that our edit mode detection is working
+            // This test verifies that our edit mode detection is working
+            Assume.That(Application.isPlaying, Is.False, "Expected edit mode.");

Also applies to: 93-127, 182-203

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be7ade8 and 8bd8fb9.

📒 Files selected for processing (3)
  • MCPForUnity/Editor/Helpers/GameObjectSerializer.cs (2 hunks)
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs (2 hunks)
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/MaterialMeshInstantiationTests.cs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/MaterialMeshInstantiationTests.cs (2)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs (18)
  • SetUp (17-22)
  • TearDown (24-32)
  • Test (34-41)
  • Test (43-50)
  • Test (52-71)
  • Test (73-82)
  • Test (84-111)
  • Test (113-128)
  • Test (130-139)
  • Test (141-160)
  • Test (162-175)
  • Test (177-198)
  • Test (200-290)
  • Test (292-358)
  • Test (360-404)
  • Test (406-444)
  • Test (446-476)
  • Test (478-509)
MCPForUnity/Editor/Helpers/GameObjectSerializer.cs (2)
  • GameObjectSerializer (17-556)
  • GetComponentData (122-429)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs (2)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/MaterialMeshInstantiationTests.cs (6)
  • Test (54-91)
  • Test (93-126)
  • Test (130-142)
  • Test (144-156)
  • Test (158-180)
  • Test (182-203)
MCPForUnity/Editor/Helpers/GameObjectSerializer.cs (2)
  • GameObjectSerializer (17-556)
  • GetComponentData (122-429)
🔇 Additional comments (1)
MCPForUnity/Editor/Helpers/GameObjectSerializer.cs (1)

258-283: <!-- [scratchpad_start]
[task verification_1/1 check_attributes_usage_3/3 assess_review_validity_complete]

[observations]

  • Context confirms lines 258-283 are in a field caching loop within a hierarchy traversal method
  • Purpose: serialize Component fields, respecting Unity's public/private and [SerializeField] rules
  • Current logic: includes public fields OR non-public with [SerializeField] (when includeNonPublicSerializedFields=true)
  • NO usage of [NonSerialized] or [SerializeReference] exists in the codebase currently
  • Suggested fix changes inherit: true → inherit: false AND adds [SerializeReference] check
  • The inherit parameter change is a subtle behavior shift worth noting

[analysis]
Review is architecturally sound but PROACTIVE:

  • Respecting [NonSerialized] on public fields aligns with Unity's serialization contract
  • Supporting [SerializeReference] alongside [SerializeField] is consistent
  • However, these attributes don't exist in use yet, so the concern is defensive

Concerns about the diff:

  • inherit: true → inherit: false changes BEHAVIOR (narrower attribute lookup)
  • This could affect subclasses if they or their parents use these attributes
  • The diff doesn't explain why this behavior change is necessary

[pending]
None - sufficient evidence gathered.

[conclusions]

  • Review suggestion is architecturally valid (aligns with Unity's rules)
  • But it's proactive (no current usage found)
  • The inherit parameter change needs developer verification
  • Developer should confirm: (1) is this defensive design intentional? (2) is narrower inheritance lookup correct?

[actions]

  • Provide tag with guidance to developer
  • Note that the suggestion is sound but requires manual verification of intent
  • Highlight the inherit parameter change as needing explicit review
    </scratchpad_end] -->

Verify architectural intent for attribute handling; note subtle inherit parameter change

The review's suggestion to respect [NonSerialized] and [SerializeReference] attributes is architecturally sound and aligns with Unity's serialization contract. However, neither attribute is currently used anywhere in the codebase, making this a proactive improvement.

One aspect requires your careful review: the suggested diff changes the inherit parameter from true to false for attribute lookups. This narrows the scope to only check attributes declared directly on each field, not inherited ones. Verify this behavior change is intentional and appropriate for your use case, especially if you expect [SerializeField] or [SerializeReference] to be inherited from base classes.

@dsarno dsarno merged commit f4d62e4 into CoplayDev:main Oct 21, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant