🎮 GameObject Toolset Redesign and Streamlining (#518)
* feat: Redesign GameObject API for better LLM ergonomics
## New Tools
- find_gameobjects: Search GameObjects, returns paginated instance IDs only
- manage_components: Component lifecycle (add, remove, set_property)
## New Resources
- unity://scene/gameobject/{id}: Single GameObject data (no component serialization)
- unity://scene/gameobject/{id}/components: All components (paginated)
- unity://scene/gameobject/{id}/component/{name}: Single component by type
## Updated
- manage_scene get_hierarchy: Now includes componentTypes array
- manage_gameobject: Slimmed to lifecycle only (create, modify, delete)
- Legacy actions (find, get_components, etc.) log deprecation warnings
## Extracted Utilities
- ParamCoercion: Centralized int/bool/float/string coercion
- VectorParsing: Vector3/Vector2/Quaternion/Color parsing
- GameObjectLookup: Centralized GameObject search logic
## Test Coverage
- 76 new Unity EditMode tests for ManageGameObject actions
- 21 new pytest tests for Python tools/resources
- New NL/T CI suite for GameObject API (GO-0 to GO-5)
Addresses LLM confusion with parameter overload by splitting into
focused tools and read-only resources.
* feat: Add static gameobject_api helper resource for UI discoverability
Adds unity://scene/gameobject-api resource that:
- Shows in Cursor's resource list UI (no parameters needed)
- Documents the parameterized gameobject resources
- Explains the workflow: find_gameobjects → read resource
- Lists examples and related tools
* feat: Add GO tests to main NL/T CI workflow
- Adds GO pass (GO-0 to GO-5) after T pass in claude-nl-suite.yml
- Includes retry logic for incomplete GO tests
- Updates all regex patterns to recognize GO-* test IDs
- Updates DESIRED lists to include all 21 tests (NL-0..4, T-A..J, GO-0..5)
- Updates default_titles for GO tests in markdown summary
- Keeps separate claude-gameobject-suite.yml for standalone runs
* feat: Add GameObject API stress tests and NL/T suite updates
Stress Tests (12 new tests):
- BulkCreate small/medium batches
- FindGameObjects pagination with by_component search
- AddComponents to single object
- GetComponents with full serialization
- SetComponentProperties (complex Rigidbody)
- Deep hierarchy creation and path lookup
- GetHierarchy with large scenes
- Resource read performance tests
- RapidFire create-modify-delete cycles
NL/T Suite Updates:
- Added GO-0..GO-10 tests in nl-gameobject-suite.md
- Fixed tool naming: mcp__unity__ → mcp__UnityMCP__
Other:
- Fixed LongUnityScriptClaudeTest.cs compilation errors
- Added reports/, .claude/local/, scripts/local-test/ to .gitignore
All 254 EditMode tests pass (250 run, 4 explicit skips)
* fix: Address code review feedback
- ParamCoercion: Use CultureInfo.InvariantCulture for float parsing
- ManageComponents: Move Transform removal check before GetComponent
- ManageGameObjectFindTests: Use try-finally for LogAssert.ignoreFailingMessages
- VectorParsing: Document that quaternions are not auto-normalized
- gameobject.py: Prefix unused ctx parameter with underscore
* fix: Address additional code review feedback
- ManageComponents: Reuse GameObjectLookup.FindComponentType instead of duplicate
- ManageComponents: Log warnings when SetPropertiesOnComponent fails
- GameObjectLookup: Make FindComponentType public for reuse
- gameobject.py: Extract _normalize_response helper to reduce duplication
- gameobject.py: Add TODO comment for unused typed response classes
* fix: Address more code review feedback
NL/T Prompt Fixes:
- nl-gameobject-suite.md: Remove non-existent list_resources/read_resource from AllowedTools
- nl-gameobject-suite.md: Fix parameter names (component_type, properties)
- nl-unity-suite-nl.md: Remove unused manage_editor from AllowedTools
Test Fixes:
- GameObjectAPIStressTests: Add null check to ToJObject helper
- GameObjectAPIStressTests: Clarify AudioSource usage comment
- ManageGameObjectFindTests: Use built-in 'UI' layer instead of 'Water'
- LongUnityScriptClaudeTest: Clean up NL/T test artifacts (Counte42 typo, HasTarget)
* docs: Add documentation for API limitations and behaviors
- GameObjectLookup.SearchByPath: Document and warn that includeInactive
has no effect (Unity API limitation)
- ManageComponents.TrySetProperty: Document case-insensitive lookup behavior
* More test fixes and tighten parameters on python tools
* fix: Align test expectation with implementation error message case
* docs: update README tools and resources lists
- Add missing tools: manage_components, batch_execute, find_gameobjects, refresh_unity
- Add missing resources: gameobject_api, editor_state_v2
- Make descriptions more concise across all tools and resources
- Ensure documentation matches current MCP server functionality
* fix: Address code review feedback
- ParamCoercion: Use InvariantCulture for int/double parsing consistency
- ManageComponents: Remove redundant Undo.RecordObject (AddComponent handles undo)
- ManageScene: Replace deprecated FindObjectsOfType with FindObjectsByType
- GameObjectLookup: Add explanatory comment to empty catch block
- gameobject.py: Extract _validate_instance_id helper to reduce duplication
- Tests: Fix assertion for instanceID (Unity IDs can be negative)
* chore: Remove accidentally committed test artifacts
- Remove Materials folder (40 .mat files from interactive testing)
- Remove Shaders folder (5 noise shaders from testing)
- Remove test scripts (Bounce*, CylinderBounce* from testing)
- Remove Temp.meta and commit.sh
* test: Improve delete tests to verify actual deletion
- Delete_ByTag_DeletesMatchingObjects: Verify objects are actually destroyed
- Delete_ByLayer_DeletesMatchingObjects: Assert deletion using Unity null check
- Delete_MultipleObjectsSameName_DeletesCorrectly: Document first-match behavior
- Delete_Success_ReturnsDeletedCount: Verify count value if present
All tests now verify deletion occurred rather than just checking for a result.
* refactor: remove deprecated manage_gameobject actions
- Remove deprecated switch cases: find, get_components, get_component, add_component, remove_component, set_component_property
- Remove deprecated wrapper methods (423 lines deleted from ManageGameObject.cs)
- Delete ManageGameObjectFindTests.cs (tests deprecated 'find' action)
- Remove deprecated test methods from ManageGameObjectTests.cs
- Add GameObject resource URIs to README documentation
- Add batch_execute performance tips to README, tool description, and gameobject_api resource
- Enhance batch_execute description to emphasize 10-100x performance gains
Total: ~1200 lines removed. New API (find_gameobjects, manage_components, resources) is the recommended path forward.
* fix: Remove starlette stubs from conftest.py
Starlette is now a proper dependency via the mcp package, so we don't need
to stub it anymore. The real package handles all HTTP transport needs.
2026-01-07 02:13:45 +08:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
|
using NUnit.Framework;
|
|
|
|
|
using UnityEditor;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
using MCPForUnity.Editor.Tools;
|
|
|
|
|
using MCPForUnity.Editor.Helpers;
|
|
|
|
|
using MCPForUnity.Editor.Resources.Scene;
|
|
|
|
|
using UnityEngine.TestTools;
|
|
|
|
|
using Debug = UnityEngine.Debug;
|
|
|
|
|
|
|
|
|
|
namespace MCPForUnityTests.Editor.Tools
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Stress tests for the GameObject API redesign.
|
|
|
|
|
/// Tests volume operations, pagination, and performance with large datasets.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[TestFixture]
|
|
|
|
|
public class GameObjectAPIStressTests
|
|
|
|
|
{
|
|
|
|
|
private List<GameObject> _createdObjects = new List<GameObject>();
|
|
|
|
|
private const int SMALL_BATCH = 10;
|
|
|
|
|
private const int MEDIUM_BATCH = 50;
|
|
|
|
|
private const int LARGE_BATCH = 100;
|
|
|
|
|
|
|
|
|
|
[SetUp]
|
|
|
|
|
public void SetUp()
|
|
|
|
|
{
|
|
|
|
|
_createdObjects.Clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[TearDown]
|
|
|
|
|
public void TearDown()
|
|
|
|
|
{
|
|
|
|
|
foreach (var go in _createdObjects)
|
|
|
|
|
{
|
|
|
|
|
if (go != null)
|
|
|
|
|
{
|
|
|
|
|
UnityEngine.Object.DestroyImmediate(go);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_createdObjects.Clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private GameObject CreateTestObject(string name)
|
|
|
|
|
{
|
|
|
|
|
var go = new GameObject(name);
|
|
|
|
|
_createdObjects.Add(go);
|
|
|
|
|
return go;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static JObject ToJObject(object result)
|
|
|
|
|
{
|
|
|
|
|
if (result == null) return new JObject();
|
🔧 Clean up & Consolidate Shared Services Across MCP Tools (#519)
* feat: Redesign GameObject API for better LLM ergonomics
- find_gameobjects: Search GameObjects, returns paginated instance IDs only
- manage_components: Component lifecycle (add, remove, set_property)
- unity://scene/gameobject/{id}: Single GameObject data (no component serialization)
- unity://scene/gameobject/{id}/components: All components (paginated)
- unity://scene/gameobject/{id}/component/{name}: Single component by type
- manage_scene get_hierarchy: Now includes componentTypes array
- manage_gameobject: Slimmed to lifecycle only (create, modify, delete)
- Legacy actions (find, get_components, etc.) log deprecation warnings
- ParamCoercion: Centralized int/bool/float/string coercion
- VectorParsing: Vector3/Vector2/Quaternion/Color parsing
- GameObjectLookup: Centralized GameObject search logic
- 76 new Unity EditMode tests for ManageGameObject actions
- 21 new pytest tests for Python tools/resources
- New NL/T CI suite for GameObject API (GO-0 to GO-5)
Addresses LLM confusion with parameter overload by splitting into
focused tools and read-only resources.
* feat: Add GameObject API stress tests and NL/T suite updates
Stress Tests (12 new tests):
- BulkCreate small/medium batches
- FindGameObjects pagination with by_component search
- AddComponents to single object
- GetComponents with full serialization
- SetComponentProperties (complex Rigidbody)
- Deep hierarchy creation and path lookup
- GetHierarchy with large scenes
- Resource read performance tests
- RapidFire create-modify-delete cycles
NL/T Suite Updates:
- Added GO-0..GO-10 tests in nl-gameobject-suite.md
- Fixed tool naming: mcp__unity__ → mcp__UnityMCP__
Other:
- Fixed LongUnityScriptClaudeTest.cs compilation errors
- Added reports/, .claude/local/, scripts/local-test/ to .gitignore
All 254 EditMode tests pass (250 run, 4 explicit skips)
* fix: Address code review feedback
- ParamCoercion: Use CultureInfo.InvariantCulture for float parsing
- ManageComponents: Move Transform removal check before GetComponent
- ManageGameObjectFindTests: Use try-finally for LogAssert.ignoreFailingMessages
- VectorParsing: Document that quaternions are not auto-normalized
- gameobject.py: Prefix unused ctx parameter with underscore
* fix: Address more code review feedback
NL/T Prompt Fixes:
- nl-gameobject-suite.md: Remove non-existent list_resources/read_resource from AllowedTools
- nl-gameobject-suite.md: Fix parameter names (component_type, properties)
- nl-unity-suite-nl.md: Remove unused manage_editor from AllowedTools
Test Fixes:
- GameObjectAPIStressTests: Add null check to ToJObject helper
- GameObjectAPIStressTests: Clarify AudioSource usage comment
- ManageGameObjectFindTests: Use built-in 'UI' layer instead of 'Water'
- LongUnityScriptClaudeTest: Clean up NL/T test artifacts (Counte42 typo, HasTarget)
* docs: update README tools and resources lists
- Add missing tools: manage_components, batch_execute, find_gameobjects, refresh_unity
- Add missing resources: gameobject_api, editor_state_v2
- Make descriptions more concise across all tools and resources
- Ensure documentation matches current MCP server functionality
* chore: Remove accidentally committed test artifacts
- Remove Materials folder (40 .mat files from interactive testing)
- Remove Shaders folder (5 noise shaders from testing)
- Remove test scripts (Bounce*, CylinderBounce* from testing)
- Remove Temp.meta and commit.sh
* refactor: remove deprecated manage_gameobject actions
- Remove deprecated switch cases: find, get_components, get_component, add_component, remove_component, set_component_property
- Remove deprecated wrapper methods (423 lines deleted from ManageGameObject.cs)
- Delete ManageGameObjectFindTests.cs (tests deprecated 'find' action)
- Remove deprecated test methods from ManageGameObjectTests.cs
- Add GameObject resource URIs to README documentation
- Add batch_execute performance tips to README, tool description, and gameobject_api resource
- Enhance batch_execute description to emphasize 10-100x performance gains
Total: ~1200 lines removed. New API (find_gameobjects, manage_components, resources) is the recommended path forward.
* refactor: consolidate shared services across MCP tools
Major architectural improvements:
- Create UnityJsonSerializer for shared JSON/Unity type conversion
- Create ObjectResolver for unified object resolution (GameObjects, Components, Assets)
- Create UnityTypeResolver for consolidated type resolution with caching
- Create PropertyConversion for unified JSON→Unity property conversion
- Create ComponentOps for low-level component operations
- Create Pagination helpers for standardized pagination across tools
Tool simplifications:
- ManageGameObject: Remove 68-line prefab redirect anti-pattern, delegate to helpers
- ManageAsset: Remove ~80 lines duplicate ConvertJTokenToType
- ManageScriptableObject: Remove ~40 lines duplicate ResolveType
- ManageComponents: Use ComponentOps, UnityTypeResolver (~90 lines saved)
- ManageMaterial: Standardize to SuccessResponse/ErrorResponse patterns
- FindGameObjects: Use PaginationRequest/PaginationResponse
- GameObjectLookup: FindComponentType delegates to UnityTypeResolver
Tests: 242/246 passed, 4 skipped (expected)
* Apply code review feedback: consolidate utilities and improve compatibility
Python Server:
- Extract normalize_properties() to shared utils.py (removes duplication)
- Move search_term validation before preflight() for fail-fast
- Fix manage_script.py documentation (remove incorrect 'update' reference)
- Remove stale comments in execute_menu_item.py, manage_editor.py
- Remove misleading destructiveHint from manage_shader.py
C# Unity:
- Add Vector4Converter (commonly used, was missing)
- Fix Unity 2021 compatibility: replace FindObjectsByType with FindObjectsOfType
- Add path normalization in ObjectResolver before StartsWith check
- Improve ComponentOps.SetProperty conversion error detection
- Add Undo.RecordObject in ManageComponents before property modifications
- Improve error message clarity in ManageMaterial.cs
- Add defensive error handling to stress test ToJObject helper
- Increase CI timeout thresholds for test stability
GitHub Workflows:
- Fix GO test sorting in markdown output (GO-10 now sorts after GO-9)
- Add warning logging for fragment parsing errors
* Fix animator hash names in test fixture to match parameter names
BlendXHash/BlendYHash now use 'reachX'/'reachY' to match the
actual animator parameter names.
* fix(windows): improve HTTP server detection and auto-start reliability
- Fix netstat detection on Windows by running netstat.exe directly instead
of piping through findstr (findstr returns exit code 1 when no matches,
causing false detection failures)
- Increase auto-start retry attempts (20→30) and delays (2s→3s) to handle
slow server starts during first install, version upgrades, and dev mode
- Only attempt blind connection after 20 failed detection attempts to reduce
connection error spam during server startup
- Remove verbose debug logs that were spamming the console every frame
* fix: auto-create tags and remove deprecated manage_gameobject actions
- ManageGameObject.cs: Check tag existence before setting; auto-create
undefined tags using InternalEditorUtility.AddTag() instead of relying
on exception handling (Unity logs warning, doesn't throw)
- manage_gameobject.py: Remove deprecated actions (find, get_components,
add_component, remove_component, set_component_property, get_component)
from Literal type - these are now handled by find_gameobjects and
manage_components tools
- Update test suite and unit tests to reflect new auto-create behavior
* fix: address code review feedback
Bug fixes:
- Fix searchInactive flag ignored in FindObjectsOfType (use includeInactive overload)
- Fix property lookup to try both original and normalized names for backwards compat
- Remove dead code for deprecated 'find' action validation
- Update error message to list only valid actions
Improvements:
- Add destructiveHint=True to manage_shader tool
- Limit fallback connection attempts (every 3rd attempt) to avoid spamming errors
- Consolidate PropertyConversion exception handlers to single catch block
- Add tag existence assertion and cleanup in tag auto-creation tests
Test fixes:
- Update SetComponentProperties_ContinuesAfterException log regex for new error format
- Update test_manage_gameobject_param_coercion to test valid actions only
2026-01-07 04:58:17 +08:00
|
|
|
if (result is JObject jobj) return jobj;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return JObject.FromObject(result);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning($"[ToJObject] Failed to convert result: {ex.Message}");
|
|
|
|
|
return new JObject { ["error"] = ex.Message };
|
|
|
|
|
}
|
🎮 GameObject Toolset Redesign and Streamlining (#518)
* feat: Redesign GameObject API for better LLM ergonomics
## New Tools
- find_gameobjects: Search GameObjects, returns paginated instance IDs only
- manage_components: Component lifecycle (add, remove, set_property)
## New Resources
- unity://scene/gameobject/{id}: Single GameObject data (no component serialization)
- unity://scene/gameobject/{id}/components: All components (paginated)
- unity://scene/gameobject/{id}/component/{name}: Single component by type
## Updated
- manage_scene get_hierarchy: Now includes componentTypes array
- manage_gameobject: Slimmed to lifecycle only (create, modify, delete)
- Legacy actions (find, get_components, etc.) log deprecation warnings
## Extracted Utilities
- ParamCoercion: Centralized int/bool/float/string coercion
- VectorParsing: Vector3/Vector2/Quaternion/Color parsing
- GameObjectLookup: Centralized GameObject search logic
## Test Coverage
- 76 new Unity EditMode tests for ManageGameObject actions
- 21 new pytest tests for Python tools/resources
- New NL/T CI suite for GameObject API (GO-0 to GO-5)
Addresses LLM confusion with parameter overload by splitting into
focused tools and read-only resources.
* feat: Add static gameobject_api helper resource for UI discoverability
Adds unity://scene/gameobject-api resource that:
- Shows in Cursor's resource list UI (no parameters needed)
- Documents the parameterized gameobject resources
- Explains the workflow: find_gameobjects → read resource
- Lists examples and related tools
* feat: Add GO tests to main NL/T CI workflow
- Adds GO pass (GO-0 to GO-5) after T pass in claude-nl-suite.yml
- Includes retry logic for incomplete GO tests
- Updates all regex patterns to recognize GO-* test IDs
- Updates DESIRED lists to include all 21 tests (NL-0..4, T-A..J, GO-0..5)
- Updates default_titles for GO tests in markdown summary
- Keeps separate claude-gameobject-suite.yml for standalone runs
* feat: Add GameObject API stress tests and NL/T suite updates
Stress Tests (12 new tests):
- BulkCreate small/medium batches
- FindGameObjects pagination with by_component search
- AddComponents to single object
- GetComponents with full serialization
- SetComponentProperties (complex Rigidbody)
- Deep hierarchy creation and path lookup
- GetHierarchy with large scenes
- Resource read performance tests
- RapidFire create-modify-delete cycles
NL/T Suite Updates:
- Added GO-0..GO-10 tests in nl-gameobject-suite.md
- Fixed tool naming: mcp__unity__ → mcp__UnityMCP__
Other:
- Fixed LongUnityScriptClaudeTest.cs compilation errors
- Added reports/, .claude/local/, scripts/local-test/ to .gitignore
All 254 EditMode tests pass (250 run, 4 explicit skips)
* fix: Address code review feedback
- ParamCoercion: Use CultureInfo.InvariantCulture for float parsing
- ManageComponents: Move Transform removal check before GetComponent
- ManageGameObjectFindTests: Use try-finally for LogAssert.ignoreFailingMessages
- VectorParsing: Document that quaternions are not auto-normalized
- gameobject.py: Prefix unused ctx parameter with underscore
* fix: Address additional code review feedback
- ManageComponents: Reuse GameObjectLookup.FindComponentType instead of duplicate
- ManageComponents: Log warnings when SetPropertiesOnComponent fails
- GameObjectLookup: Make FindComponentType public for reuse
- gameobject.py: Extract _normalize_response helper to reduce duplication
- gameobject.py: Add TODO comment for unused typed response classes
* fix: Address more code review feedback
NL/T Prompt Fixes:
- nl-gameobject-suite.md: Remove non-existent list_resources/read_resource from AllowedTools
- nl-gameobject-suite.md: Fix parameter names (component_type, properties)
- nl-unity-suite-nl.md: Remove unused manage_editor from AllowedTools
Test Fixes:
- GameObjectAPIStressTests: Add null check to ToJObject helper
- GameObjectAPIStressTests: Clarify AudioSource usage comment
- ManageGameObjectFindTests: Use built-in 'UI' layer instead of 'Water'
- LongUnityScriptClaudeTest: Clean up NL/T test artifacts (Counte42 typo, HasTarget)
* docs: Add documentation for API limitations and behaviors
- GameObjectLookup.SearchByPath: Document and warn that includeInactive
has no effect (Unity API limitation)
- ManageComponents.TrySetProperty: Document case-insensitive lookup behavior
* More test fixes and tighten parameters on python tools
* fix: Align test expectation with implementation error message case
* docs: update README tools and resources lists
- Add missing tools: manage_components, batch_execute, find_gameobjects, refresh_unity
- Add missing resources: gameobject_api, editor_state_v2
- Make descriptions more concise across all tools and resources
- Ensure documentation matches current MCP server functionality
* fix: Address code review feedback
- ParamCoercion: Use InvariantCulture for int/double parsing consistency
- ManageComponents: Remove redundant Undo.RecordObject (AddComponent handles undo)
- ManageScene: Replace deprecated FindObjectsOfType with FindObjectsByType
- GameObjectLookup: Add explanatory comment to empty catch block
- gameobject.py: Extract _validate_instance_id helper to reduce duplication
- Tests: Fix assertion for instanceID (Unity IDs can be negative)
* chore: Remove accidentally committed test artifacts
- Remove Materials folder (40 .mat files from interactive testing)
- Remove Shaders folder (5 noise shaders from testing)
- Remove test scripts (Bounce*, CylinderBounce* from testing)
- Remove Temp.meta and commit.sh
* test: Improve delete tests to verify actual deletion
- Delete_ByTag_DeletesMatchingObjects: Verify objects are actually destroyed
- Delete_ByLayer_DeletesMatchingObjects: Assert deletion using Unity null check
- Delete_MultipleObjectsSameName_DeletesCorrectly: Document first-match behavior
- Delete_Success_ReturnsDeletedCount: Verify count value if present
All tests now verify deletion occurred rather than just checking for a result.
* refactor: remove deprecated manage_gameobject actions
- Remove deprecated switch cases: find, get_components, get_component, add_component, remove_component, set_component_property
- Remove deprecated wrapper methods (423 lines deleted from ManageGameObject.cs)
- Delete ManageGameObjectFindTests.cs (tests deprecated 'find' action)
- Remove deprecated test methods from ManageGameObjectTests.cs
- Add GameObject resource URIs to README documentation
- Add batch_execute performance tips to README, tool description, and gameobject_api resource
- Enhance batch_execute description to emphasize 10-100x performance gains
Total: ~1200 lines removed. New API (find_gameobjects, manage_components, resources) is the recommended path forward.
* fix: Remove starlette stubs from conftest.py
Starlette is now a proper dependency via the mcp package, so we don't need
to stub it anymore. The real package handles all HTTP transport needs.
2026-01-07 02:13:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#region Bulk GameObject Creation
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void BulkCreate_SmallBatch_AllSucceed()
|
|
|
|
|
{
|
|
|
|
|
var sw = Stopwatch.StartNew();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < SMALL_BATCH; i++)
|
|
|
|
|
{
|
|
|
|
|
var result = ToJObject(ManageGameObject.HandleCommand(new JObject
|
|
|
|
|
{
|
|
|
|
|
["action"] = "create",
|
|
|
|
|
["name"] = $"BulkTest_{i}"
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
Assert.IsTrue(result["success"]?.Value<bool>() ?? false, $"Failed to create object {i}");
|
|
|
|
|
|
|
|
|
|
// Track for cleanup
|
|
|
|
|
int instanceId = result["data"]?["instanceID"]?.Value<int>() ?? 0;
|
|
|
|
|
if (instanceId != 0)
|
|
|
|
|
{
|
|
|
|
|
var go = EditorUtility.InstanceIDToObject(instanceId) as GameObject;
|
|
|
|
|
if (go != null) _createdObjects.Add(go);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sw.Stop();
|
|
|
|
|
Debug.Log($"[BulkCreate] Created {SMALL_BATCH} objects in {sw.ElapsedMilliseconds}ms");
|
🔧 Clean up & Consolidate Shared Services Across MCP Tools (#519)
* feat: Redesign GameObject API for better LLM ergonomics
- find_gameobjects: Search GameObjects, returns paginated instance IDs only
- manage_components: Component lifecycle (add, remove, set_property)
- unity://scene/gameobject/{id}: Single GameObject data (no component serialization)
- unity://scene/gameobject/{id}/components: All components (paginated)
- unity://scene/gameobject/{id}/component/{name}: Single component by type
- manage_scene get_hierarchy: Now includes componentTypes array
- manage_gameobject: Slimmed to lifecycle only (create, modify, delete)
- Legacy actions (find, get_components, etc.) log deprecation warnings
- ParamCoercion: Centralized int/bool/float/string coercion
- VectorParsing: Vector3/Vector2/Quaternion/Color parsing
- GameObjectLookup: Centralized GameObject search logic
- 76 new Unity EditMode tests for ManageGameObject actions
- 21 new pytest tests for Python tools/resources
- New NL/T CI suite for GameObject API (GO-0 to GO-5)
Addresses LLM confusion with parameter overload by splitting into
focused tools and read-only resources.
* feat: Add GameObject API stress tests and NL/T suite updates
Stress Tests (12 new tests):
- BulkCreate small/medium batches
- FindGameObjects pagination with by_component search
- AddComponents to single object
- GetComponents with full serialization
- SetComponentProperties (complex Rigidbody)
- Deep hierarchy creation and path lookup
- GetHierarchy with large scenes
- Resource read performance tests
- RapidFire create-modify-delete cycles
NL/T Suite Updates:
- Added GO-0..GO-10 tests in nl-gameobject-suite.md
- Fixed tool naming: mcp__unity__ → mcp__UnityMCP__
Other:
- Fixed LongUnityScriptClaudeTest.cs compilation errors
- Added reports/, .claude/local/, scripts/local-test/ to .gitignore
All 254 EditMode tests pass (250 run, 4 explicit skips)
* fix: Address code review feedback
- ParamCoercion: Use CultureInfo.InvariantCulture for float parsing
- ManageComponents: Move Transform removal check before GetComponent
- ManageGameObjectFindTests: Use try-finally for LogAssert.ignoreFailingMessages
- VectorParsing: Document that quaternions are not auto-normalized
- gameobject.py: Prefix unused ctx parameter with underscore
* fix: Address more code review feedback
NL/T Prompt Fixes:
- nl-gameobject-suite.md: Remove non-existent list_resources/read_resource from AllowedTools
- nl-gameobject-suite.md: Fix parameter names (component_type, properties)
- nl-unity-suite-nl.md: Remove unused manage_editor from AllowedTools
Test Fixes:
- GameObjectAPIStressTests: Add null check to ToJObject helper
- GameObjectAPIStressTests: Clarify AudioSource usage comment
- ManageGameObjectFindTests: Use built-in 'UI' layer instead of 'Water'
- LongUnityScriptClaudeTest: Clean up NL/T test artifacts (Counte42 typo, HasTarget)
* docs: update README tools and resources lists
- Add missing tools: manage_components, batch_execute, find_gameobjects, refresh_unity
- Add missing resources: gameobject_api, editor_state_v2
- Make descriptions more concise across all tools and resources
- Ensure documentation matches current MCP server functionality
* chore: Remove accidentally committed test artifacts
- Remove Materials folder (40 .mat files from interactive testing)
- Remove Shaders folder (5 noise shaders from testing)
- Remove test scripts (Bounce*, CylinderBounce* from testing)
- Remove Temp.meta and commit.sh
* refactor: remove deprecated manage_gameobject actions
- Remove deprecated switch cases: find, get_components, get_component, add_component, remove_component, set_component_property
- Remove deprecated wrapper methods (423 lines deleted from ManageGameObject.cs)
- Delete ManageGameObjectFindTests.cs (tests deprecated 'find' action)
- Remove deprecated test methods from ManageGameObjectTests.cs
- Add GameObject resource URIs to README documentation
- Add batch_execute performance tips to README, tool description, and gameobject_api resource
- Enhance batch_execute description to emphasize 10-100x performance gains
Total: ~1200 lines removed. New API (find_gameobjects, manage_components, resources) is the recommended path forward.
* refactor: consolidate shared services across MCP tools
Major architectural improvements:
- Create UnityJsonSerializer for shared JSON/Unity type conversion
- Create ObjectResolver for unified object resolution (GameObjects, Components, Assets)
- Create UnityTypeResolver for consolidated type resolution with caching
- Create PropertyConversion for unified JSON→Unity property conversion
- Create ComponentOps for low-level component operations
- Create Pagination helpers for standardized pagination across tools
Tool simplifications:
- ManageGameObject: Remove 68-line prefab redirect anti-pattern, delegate to helpers
- ManageAsset: Remove ~80 lines duplicate ConvertJTokenToType
- ManageScriptableObject: Remove ~40 lines duplicate ResolveType
- ManageComponents: Use ComponentOps, UnityTypeResolver (~90 lines saved)
- ManageMaterial: Standardize to SuccessResponse/ErrorResponse patterns
- FindGameObjects: Use PaginationRequest/PaginationResponse
- GameObjectLookup: FindComponentType delegates to UnityTypeResolver
Tests: 242/246 passed, 4 skipped (expected)
* Apply code review feedback: consolidate utilities and improve compatibility
Python Server:
- Extract normalize_properties() to shared utils.py (removes duplication)
- Move search_term validation before preflight() for fail-fast
- Fix manage_script.py documentation (remove incorrect 'update' reference)
- Remove stale comments in execute_menu_item.py, manage_editor.py
- Remove misleading destructiveHint from manage_shader.py
C# Unity:
- Add Vector4Converter (commonly used, was missing)
- Fix Unity 2021 compatibility: replace FindObjectsByType with FindObjectsOfType
- Add path normalization in ObjectResolver before StartsWith check
- Improve ComponentOps.SetProperty conversion error detection
- Add Undo.RecordObject in ManageComponents before property modifications
- Improve error message clarity in ManageMaterial.cs
- Add defensive error handling to stress test ToJObject helper
- Increase CI timeout thresholds for test stability
GitHub Workflows:
- Fix GO test sorting in markdown output (GO-10 now sorts after GO-9)
- Add warning logging for fragment parsing errors
* Fix animator hash names in test fixture to match parameter names
BlendXHash/BlendYHash now use 'reachX'/'reachY' to match the
actual animator parameter names.
* fix(windows): improve HTTP server detection and auto-start reliability
- Fix netstat detection on Windows by running netstat.exe directly instead
of piping through findstr (findstr returns exit code 1 when no matches,
causing false detection failures)
- Increase auto-start retry attempts (20→30) and delays (2s→3s) to handle
slow server starts during first install, version upgrades, and dev mode
- Only attempt blind connection after 20 failed detection attempts to reduce
connection error spam during server startup
- Remove verbose debug logs that were spamming the console every frame
* fix: auto-create tags and remove deprecated manage_gameobject actions
- ManageGameObject.cs: Check tag existence before setting; auto-create
undefined tags using InternalEditorUtility.AddTag() instead of relying
on exception handling (Unity logs warning, doesn't throw)
- manage_gameobject.py: Remove deprecated actions (find, get_components,
add_component, remove_component, set_component_property, get_component)
from Literal type - these are now handled by find_gameobjects and
manage_components tools
- Update test suite and unit tests to reflect new auto-create behavior
* fix: address code review feedback
Bug fixes:
- Fix searchInactive flag ignored in FindObjectsOfType (use includeInactive overload)
- Fix property lookup to try both original and normalized names for backwards compat
- Remove dead code for deprecated 'find' action validation
- Update error message to list only valid actions
Improvements:
- Add destructiveHint=True to manage_shader tool
- Limit fallback connection attempts (every 3rd attempt) to avoid spamming errors
- Consolidate PropertyConversion exception handlers to single catch block
- Add tag existence assertion and cleanup in tag auto-creation tests
Test fixes:
- Update SetComponentProperties_ContinuesAfterException log regex for new error format
- Update test_manage_gameobject_param_coercion to test valid actions only
2026-01-07 04:58:17 +08:00
|
|
|
// Use generous threshold for CI variability
|
|
|
|
|
Assert.Less(sw.ElapsedMilliseconds, 10000, "Bulk create took too long (CI threshold)");
|
🎮 GameObject Toolset Redesign and Streamlining (#518)
* feat: Redesign GameObject API for better LLM ergonomics
## New Tools
- find_gameobjects: Search GameObjects, returns paginated instance IDs only
- manage_components: Component lifecycle (add, remove, set_property)
## New Resources
- unity://scene/gameobject/{id}: Single GameObject data (no component serialization)
- unity://scene/gameobject/{id}/components: All components (paginated)
- unity://scene/gameobject/{id}/component/{name}: Single component by type
## Updated
- manage_scene get_hierarchy: Now includes componentTypes array
- manage_gameobject: Slimmed to lifecycle only (create, modify, delete)
- Legacy actions (find, get_components, etc.) log deprecation warnings
## Extracted Utilities
- ParamCoercion: Centralized int/bool/float/string coercion
- VectorParsing: Vector3/Vector2/Quaternion/Color parsing
- GameObjectLookup: Centralized GameObject search logic
## Test Coverage
- 76 new Unity EditMode tests for ManageGameObject actions
- 21 new pytest tests for Python tools/resources
- New NL/T CI suite for GameObject API (GO-0 to GO-5)
Addresses LLM confusion with parameter overload by splitting into
focused tools and read-only resources.
* feat: Add static gameobject_api helper resource for UI discoverability
Adds unity://scene/gameobject-api resource that:
- Shows in Cursor's resource list UI (no parameters needed)
- Documents the parameterized gameobject resources
- Explains the workflow: find_gameobjects → read resource
- Lists examples and related tools
* feat: Add GO tests to main NL/T CI workflow
- Adds GO pass (GO-0 to GO-5) after T pass in claude-nl-suite.yml
- Includes retry logic for incomplete GO tests
- Updates all regex patterns to recognize GO-* test IDs
- Updates DESIRED lists to include all 21 tests (NL-0..4, T-A..J, GO-0..5)
- Updates default_titles for GO tests in markdown summary
- Keeps separate claude-gameobject-suite.yml for standalone runs
* feat: Add GameObject API stress tests and NL/T suite updates
Stress Tests (12 new tests):
- BulkCreate small/medium batches
- FindGameObjects pagination with by_component search
- AddComponents to single object
- GetComponents with full serialization
- SetComponentProperties (complex Rigidbody)
- Deep hierarchy creation and path lookup
- GetHierarchy with large scenes
- Resource read performance tests
- RapidFire create-modify-delete cycles
NL/T Suite Updates:
- Added GO-0..GO-10 tests in nl-gameobject-suite.md
- Fixed tool naming: mcp__unity__ → mcp__UnityMCP__
Other:
- Fixed LongUnityScriptClaudeTest.cs compilation errors
- Added reports/, .claude/local/, scripts/local-test/ to .gitignore
All 254 EditMode tests pass (250 run, 4 explicit skips)
* fix: Address code review feedback
- ParamCoercion: Use CultureInfo.InvariantCulture for float parsing
- ManageComponents: Move Transform removal check before GetComponent
- ManageGameObjectFindTests: Use try-finally for LogAssert.ignoreFailingMessages
- VectorParsing: Document that quaternions are not auto-normalized
- gameobject.py: Prefix unused ctx parameter with underscore
* fix: Address additional code review feedback
- ManageComponents: Reuse GameObjectLookup.FindComponentType instead of duplicate
- ManageComponents: Log warnings when SetPropertiesOnComponent fails
- GameObjectLookup: Make FindComponentType public for reuse
- gameobject.py: Extract _normalize_response helper to reduce duplication
- gameobject.py: Add TODO comment for unused typed response classes
* fix: Address more code review feedback
NL/T Prompt Fixes:
- nl-gameobject-suite.md: Remove non-existent list_resources/read_resource from AllowedTools
- nl-gameobject-suite.md: Fix parameter names (component_type, properties)
- nl-unity-suite-nl.md: Remove unused manage_editor from AllowedTools
Test Fixes:
- GameObjectAPIStressTests: Add null check to ToJObject helper
- GameObjectAPIStressTests: Clarify AudioSource usage comment
- ManageGameObjectFindTests: Use built-in 'UI' layer instead of 'Water'
- LongUnityScriptClaudeTest: Clean up NL/T test artifacts (Counte42 typo, HasTarget)
* docs: Add documentation for API limitations and behaviors
- GameObjectLookup.SearchByPath: Document and warn that includeInactive
has no effect (Unity API limitation)
- ManageComponents.TrySetProperty: Document case-insensitive lookup behavior
* More test fixes and tighten parameters on python tools
* fix: Align test expectation with implementation error message case
* docs: update README tools and resources lists
- Add missing tools: manage_components, batch_execute, find_gameobjects, refresh_unity
- Add missing resources: gameobject_api, editor_state_v2
- Make descriptions more concise across all tools and resources
- Ensure documentation matches current MCP server functionality
* fix: Address code review feedback
- ParamCoercion: Use InvariantCulture for int/double parsing consistency
- ManageComponents: Remove redundant Undo.RecordObject (AddComponent handles undo)
- ManageScene: Replace deprecated FindObjectsOfType with FindObjectsByType
- GameObjectLookup: Add explanatory comment to empty catch block
- gameobject.py: Extract _validate_instance_id helper to reduce duplication
- Tests: Fix assertion for instanceID (Unity IDs can be negative)
* chore: Remove accidentally committed test artifacts
- Remove Materials folder (40 .mat files from interactive testing)
- Remove Shaders folder (5 noise shaders from testing)
- Remove test scripts (Bounce*, CylinderBounce* from testing)
- Remove Temp.meta and commit.sh
* test: Improve delete tests to verify actual deletion
- Delete_ByTag_DeletesMatchingObjects: Verify objects are actually destroyed
- Delete_ByLayer_DeletesMatchingObjects: Assert deletion using Unity null check
- Delete_MultipleObjectsSameName_DeletesCorrectly: Document first-match behavior
- Delete_Success_ReturnsDeletedCount: Verify count value if present
All tests now verify deletion occurred rather than just checking for a result.
* refactor: remove deprecated manage_gameobject actions
- Remove deprecated switch cases: find, get_components, get_component, add_component, remove_component, set_component_property
- Remove deprecated wrapper methods (423 lines deleted from ManageGameObject.cs)
- Delete ManageGameObjectFindTests.cs (tests deprecated 'find' action)
- Remove deprecated test methods from ManageGameObjectTests.cs
- Add GameObject resource URIs to README documentation
- Add batch_execute performance tips to README, tool description, and gameobject_api resource
- Enhance batch_execute description to emphasize 10-100x performance gains
Total: ~1200 lines removed. New API (find_gameobjects, manage_components, resources) is the recommended path forward.
* fix: Remove starlette stubs from conftest.py
Starlette is now a proper dependency via the mcp package, so we don't need
to stub it anymore. The real package handles all HTTP transport needs.
2026-01-07 02:13:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void BulkCreate_MediumBatch_AllSucceed()
|
|
|
|
|
{
|
|
|
|
|
var sw = Stopwatch.StartNew();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < MEDIUM_BATCH; i++)
|
|
|
|
|
{
|
|
|
|
|
var result = ToJObject(ManageGameObject.HandleCommand(new JObject
|
|
|
|
|
{
|
|
|
|
|
["action"] = "create",
|
|
|
|
|
["name"] = $"MediumBulk_{i}"
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
Assert.IsTrue(result["success"]?.Value<bool>() ?? false, $"Failed to create object {i}");
|
|
|
|
|
|
|
|
|
|
int instanceId = result["data"]?["instanceID"]?.Value<int>() ?? 0;
|
|
|
|
|
if (instanceId != 0)
|
|
|
|
|
{
|
|
|
|
|
var go = EditorUtility.InstanceIDToObject(instanceId) as GameObject;
|
|
|
|
|
if (go != null) _createdObjects.Add(go);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sw.Stop();
|
|
|
|
|
Debug.Log($"[BulkCreate] Created {MEDIUM_BATCH} objects in {sw.ElapsedMilliseconds}ms");
|
|
|
|
|
Assert.Less(sw.ElapsedMilliseconds, 15000, "Medium batch create took too long");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Find GameObjects Pagination
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void FindGameObjects_LargeBatch_PaginatesCorrectly()
|
|
|
|
|
{
|
|
|
|
|
// Create many objects with a unique marker component for reliable search
|
|
|
|
|
for (int i = 0; i < LARGE_BATCH; i++)
|
|
|
|
|
{
|
|
|
|
|
var go = CreateTestObject($"Searchable_{i:D3}");
|
|
|
|
|
go.AddComponent<GameObjectAPIStressTestMarker>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find by searching for a specific object first
|
|
|
|
|
var firstResult = ToJObject(FindGameObjects.HandleCommand(new JObject
|
|
|
|
|
{
|
|
|
|
|
["searchTerm"] = "Searchable_000",
|
|
|
|
|
["searchMethod"] = "by_name",
|
|
|
|
|
["pageSize"] = 10
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
Assert.IsTrue(firstResult["success"]?.Value<bool>() ?? false, "Should find specific named object");
|
|
|
|
|
var firstData = firstResult["data"] as JObject;
|
|
|
|
|
var firstIds = firstData?["instanceIDs"] as JArray;
|
|
|
|
|
Assert.IsNotNull(firstIds);
|
|
|
|
|
Assert.AreEqual(1, firstIds.Count, "Should find exactly one object with exact name match");
|
|
|
|
|
|
|
|
|
|
Debug.Log($"[FindGameObjects] Found object by exact name. Testing pagination with a unique marker component.");
|
|
|
|
|
|
|
|
|
|
// Now test pagination by searching for only the objects created by this test
|
|
|
|
|
var result = ToJObject(FindGameObjects.HandleCommand(new JObject
|
|
|
|
|
{
|
|
|
|
|
["searchTerm"] = typeof(GameObjectAPIStressTestMarker).FullName,
|
|
|
|
|
["searchMethod"] = "by_component",
|
|
|
|
|
["pageSize"] = 25
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
Assert.IsTrue(result["success"]?.Value<bool>() ?? false);
|
|
|
|
|
var data = result["data"] as JObject;
|
|
|
|
|
Assert.IsNotNull(data);
|
|
|
|
|
|
|
|
|
|
var instanceIds = data["instanceIDs"] as JArray;
|
|
|
|
|
Assert.IsNotNull(instanceIds);
|
|
|
|
|
Assert.AreEqual(25, instanceIds.Count, "First page should have 25 items");
|
|
|
|
|
|
|
|
|
|
int totalCount = data["totalCount"]?.Value<int>() ?? 0;
|
|
|
|
|
Assert.AreEqual(LARGE_BATCH, totalCount, $"Should find exactly {LARGE_BATCH} objects created by this test");
|
|
|
|
|
|
|
|
|
|
bool hasMore = data["hasMore"]?.Value<bool>() ?? false;
|
|
|
|
|
Assert.IsTrue(hasMore, "Should have more pages");
|
|
|
|
|
|
|
|
|
|
Debug.Log($"[FindGameObjects] Found {totalCount} objects, first page has {instanceIds.Count}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void FindGameObjects_PaginateThroughAll()
|
|
|
|
|
{
|
|
|
|
|
// Create objects - all will have a unique marker component
|
|
|
|
|
for (int i = 0; i < MEDIUM_BATCH; i++)
|
|
|
|
|
{
|
|
|
|
|
var go = CreateTestObject($"Paginate_{i:D3}");
|
|
|
|
|
go.AddComponent<GameObjectAPIStressTestMarker>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Track IDs we've created for verification
|
|
|
|
|
var createdIds = new HashSet<int>();
|
|
|
|
|
foreach (var go in _createdObjects)
|
|
|
|
|
{
|
|
|
|
|
if (go != null && go.name.StartsWith("Paginate_"))
|
|
|
|
|
{
|
|
|
|
|
createdIds.Add(go.GetInstanceID());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int pageSize = 10;
|
|
|
|
|
int cursor = 0;
|
|
|
|
|
int foundFromCreated = 0;
|
|
|
|
|
int pageCount = 0;
|
|
|
|
|
|
|
|
|
|
// Search by the unique marker component and check our created objects
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
var result = ToJObject(FindGameObjects.HandleCommand(new JObject
|
|
|
|
|
{
|
|
|
|
|
["searchTerm"] = typeof(GameObjectAPIStressTestMarker).FullName,
|
|
|
|
|
["searchMethod"] = "by_component",
|
|
|
|
|
["pageSize"] = pageSize,
|
|
|
|
|
["cursor"] = cursor
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
Assert.IsTrue(result["success"]?.Value<bool>() ?? false);
|
|
|
|
|
var data = result["data"] as JObject;
|
|
|
|
|
var instanceIds = data["instanceIDs"] as JArray;
|
|
|
|
|
|
|
|
|
|
// Count how many of our created objects are in this page
|
|
|
|
|
foreach (var id in instanceIds)
|
|
|
|
|
{
|
|
|
|
|
if (createdIds.Contains(id.Value<int>()))
|
|
|
|
|
{
|
|
|
|
|
foundFromCreated++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pageCount++;
|
|
|
|
|
|
|
|
|
|
bool hasMore = data["hasMore"]?.Value<bool>() ?? false;
|
|
|
|
|
if (!hasMore) break;
|
|
|
|
|
|
|
|
|
|
cursor = data["nextCursor"]?.Value<int>() ?? cursor + pageSize;
|
|
|
|
|
|
|
|
|
|
// Safety limit
|
|
|
|
|
if (pageCount > 50) break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Assert.AreEqual(MEDIUM_BATCH, foundFromCreated, $"Should find all {MEDIUM_BATCH} created objects across pages");
|
|
|
|
|
Debug.Log($"[Pagination] Found {foundFromCreated} created objects across {pageCount} pages");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Component Operations at Scale
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void AddComponents_MultipleToSingleObject()
|
|
|
|
|
{
|
|
|
|
|
var go = CreateTestObject("ComponentHost");
|
|
|
|
|
|
|
|
|
|
string[] componentTypeNames = new[]
|
|
|
|
|
{
|
|
|
|
|
"BoxCollider",
|
|
|
|
|
"Rigidbody",
|
|
|
|
|
"Light",
|
|
|
|
|
"Camera"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var sw = Stopwatch.StartNew();
|
|
|
|
|
|
|
|
|
|
foreach (var compType in componentTypeNames)
|
|
|
|
|
{
|
|
|
|
|
var result = ToJObject(ManageComponents.HandleCommand(new JObject
|
|
|
|
|
{
|
|
|
|
|
["action"] = "add",
|
|
|
|
|
["target"] = go.GetInstanceID().ToString(),
|
|
|
|
|
["searchMethod"] = "by_id",
|
|
|
|
|
["componentType"] = compType // Correct parameter name
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
Assert.IsTrue(result["success"]?.Value<bool>() ?? false, $"Failed to add {compType}: {result["message"]}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sw.Stop();
|
|
|
|
|
Debug.Log($"[AddComponents] Added {componentTypeNames.Length} components in {sw.ElapsedMilliseconds}ms");
|
|
|
|
|
|
|
|
|
|
// Verify all components present
|
|
|
|
|
Assert.AreEqual(componentTypeNames.Length + 1, go.GetComponents<Component>().Length); // +1 for Transform
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void GetComponents_ObjectWithManyComponents()
|
|
|
|
|
{
|
|
|
|
|
var go = CreateTestObject("HeavyComponents");
|
|
|
|
|
|
|
|
|
|
// Add many components - but skip AudioSource as it triggers deprecated API warnings
|
|
|
|
|
go.AddComponent<BoxCollider>();
|
|
|
|
|
go.AddComponent<SphereCollider>();
|
|
|
|
|
go.AddComponent<CapsuleCollider>();
|
|
|
|
|
go.AddComponent<MeshCollider>();
|
|
|
|
|
go.AddComponent<Rigidbody>();
|
|
|
|
|
go.AddComponent<Light>();
|
|
|
|
|
go.AddComponent<Camera>();
|
|
|
|
|
go.AddComponent<AudioListener>();
|
|
|
|
|
|
|
|
|
|
var sw = Stopwatch.StartNew();
|
|
|
|
|
|
|
|
|
|
// Use the resource handler for getting components
|
|
|
|
|
var result = ToJObject(GameObjectComponentsResource.HandleCommand(new JObject
|
|
|
|
|
{
|
|
|
|
|
["instanceID"] = go.GetInstanceID(),
|
|
|
|
|
["includeProperties"] = true,
|
|
|
|
|
["pageSize"] = 50
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
sw.Stop();
|
|
|
|
|
|
|
|
|
|
Assert.IsTrue(result["success"]?.Value<bool>() ?? false, $"GetComponents failed: {result["message"]}");
|
|
|
|
|
var data = result["data"] as JObject;
|
|
|
|
|
var components = data?["components"] as JArray;
|
|
|
|
|
|
|
|
|
|
Assert.IsNotNull(components);
|
|
|
|
|
Assert.AreEqual(9, components.Count); // 8 added + Transform
|
|
|
|
|
|
|
|
|
|
Debug.Log($"[GetComponents] Retrieved {components.Count} components with properties in {sw.ElapsedMilliseconds}ms");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void SetComponentProperties_ComplexRigidbody()
|
|
|
|
|
{
|
|
|
|
|
var go = CreateTestObject("RigidbodyTest");
|
|
|
|
|
go.AddComponent<Rigidbody>();
|
|
|
|
|
|
|
|
|
|
var result = ToJObject(ManageComponents.HandleCommand(new JObject
|
|
|
|
|
{
|
|
|
|
|
["action"] = "set_property",
|
|
|
|
|
["target"] = go.GetInstanceID().ToString(),
|
|
|
|
|
["searchMethod"] = "by_id",
|
|
|
|
|
["componentType"] = "Rigidbody", // Correct parameter name
|
|
|
|
|
["properties"] = new JObject // Correct parameter name
|
|
|
|
|
{
|
|
|
|
|
["mass"] = 10.5f,
|
|
|
|
|
["drag"] = 0.5f,
|
|
|
|
|
["angularDrag"] = 0.1f,
|
|
|
|
|
["useGravity"] = false,
|
|
|
|
|
["isKinematic"] = true
|
|
|
|
|
}
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
Assert.IsTrue(result["success"]?.Value<bool>() ?? false, $"Set property failed: {result["message"]}");
|
|
|
|
|
|
|
|
|
|
var rb = go.GetComponent<Rigidbody>();
|
|
|
|
|
Assert.AreEqual(10.5f, rb.mass, 0.01f);
|
|
|
|
|
Assert.AreEqual(0.5f, rb.drag, 0.01f);
|
|
|
|
|
Assert.AreEqual(0.1f, rb.angularDrag, 0.01f);
|
|
|
|
|
Assert.IsFalse(rb.useGravity);
|
|
|
|
|
Assert.IsTrue(rb.isKinematic);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Deep Hierarchy Operations
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void CreateDeepHierarchy_FindByPath()
|
|
|
|
|
{
|
|
|
|
|
// Create a deep hierarchy: Root/Level1/Level2/Level3/Target
|
|
|
|
|
var root = CreateTestObject("DeepRoot");
|
|
|
|
|
var current = root;
|
|
|
|
|
|
|
|
|
|
for (int i = 1; i <= 5; i++)
|
|
|
|
|
{
|
|
|
|
|
var child = CreateTestObject($"Level{i}");
|
|
|
|
|
child.transform.SetParent(current.transform);
|
|
|
|
|
current = child;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var target = CreateTestObject("DeepTarget");
|
|
|
|
|
target.transform.SetParent(current.transform);
|
|
|
|
|
|
|
|
|
|
// Find by path
|
|
|
|
|
var result = ToJObject(FindGameObjects.HandleCommand(new JObject
|
|
|
|
|
{
|
|
|
|
|
["searchTerm"] = "DeepRoot/Level1/Level2/Level3/Level4/Level5/DeepTarget",
|
|
|
|
|
["searchMethod"] = "by_path"
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
Assert.IsTrue(result["success"]?.Value<bool>() ?? false);
|
|
|
|
|
var data = result["data"] as JObject;
|
|
|
|
|
var ids = data?["instanceIDs"] as JArray;
|
|
|
|
|
|
|
|
|
|
Assert.IsNotNull(ids);
|
|
|
|
|
Assert.AreEqual(1, ids.Count);
|
|
|
|
|
Assert.AreEqual(target.GetInstanceID(), ids[0].Value<int>());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void GetHierarchy_LargeScene_Paginated()
|
|
|
|
|
{
|
|
|
|
|
// Create flat hierarchy with many objects
|
|
|
|
|
for (int i = 0; i < MEDIUM_BATCH; i++)
|
|
|
|
|
{
|
|
|
|
|
CreateTestObject($"HierarchyItem_{i:D3}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var result = ToJObject(ManageScene.HandleCommand(new JObject
|
|
|
|
|
{
|
|
|
|
|
["action"] = "get_hierarchy",
|
|
|
|
|
["pageSize"] = 20,
|
|
|
|
|
["maxNodes"] = 100
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
Assert.IsTrue(result["success"]?.Value<bool>() ?? false);
|
|
|
|
|
var data = result["data"] as JObject;
|
|
|
|
|
var items = data?["items"] as JArray;
|
|
|
|
|
|
|
|
|
|
Assert.IsNotNull(items);
|
|
|
|
|
Assert.GreaterOrEqual(items.Count, 1);
|
|
|
|
|
|
|
|
|
|
// Verify componentTypes is included
|
|
|
|
|
var firstItem = items[0] as JObject;
|
|
|
|
|
Assert.IsNotNull(firstItem?["componentTypes"], "Should include componentTypes");
|
|
|
|
|
|
|
|
|
|
Debug.Log($"[GetHierarchy] Retrieved {items.Count} items from hierarchy");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Resource Read Performance
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void GameObjectResource_ReadComplexObject()
|
|
|
|
|
{
|
|
|
|
|
var go = CreateTestObject("ComplexObject");
|
|
|
|
|
go.tag = "Player";
|
|
|
|
|
go.layer = 8;
|
|
|
|
|
go.isStatic = true;
|
|
|
|
|
|
|
|
|
|
// Add components - AudioSource is OK here since we're only reading component types, not serializing properties
|
|
|
|
|
go.AddComponent<Rigidbody>();
|
|
|
|
|
go.AddComponent<BoxCollider>();
|
|
|
|
|
go.AddComponent<AudioSource>();
|
|
|
|
|
|
|
|
|
|
// Add children
|
|
|
|
|
for (int i = 0; i < 5; i++)
|
|
|
|
|
{
|
|
|
|
|
var child = CreateTestObject($"Child_{i}");
|
|
|
|
|
child.transform.SetParent(go.transform);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var sw = Stopwatch.StartNew();
|
|
|
|
|
|
|
|
|
|
// Call the resource directly (no action param needed)
|
|
|
|
|
var result = ToJObject(GameObjectResource.HandleCommand(new JObject
|
|
|
|
|
{
|
|
|
|
|
["instanceID"] = go.GetInstanceID()
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
sw.Stop();
|
|
|
|
|
|
|
|
|
|
Assert.IsTrue(result["success"]?.Value<bool>() ?? false);
|
|
|
|
|
var data = result["data"] as JObject;
|
|
|
|
|
|
|
|
|
|
Assert.AreEqual("ComplexObject", data?["name"]?.Value<string>());
|
|
|
|
|
Assert.AreEqual("Player", data?["tag"]?.Value<string>());
|
|
|
|
|
Assert.AreEqual(8, data?["layer"]?.Value<int>());
|
|
|
|
|
|
|
|
|
|
var componentTypes = data?["componentTypes"] as JArray;
|
|
|
|
|
Assert.IsNotNull(componentTypes);
|
|
|
|
|
Assert.AreEqual(4, componentTypes.Count); // Transform + 3 added
|
|
|
|
|
|
|
|
|
|
var children = data?["children"] as JArray;
|
|
|
|
|
Assert.IsNotNull(children);
|
|
|
|
|
Assert.AreEqual(5, children.Count);
|
|
|
|
|
|
|
|
|
|
Debug.Log($"[GameObjectResource] Read complex object in {sw.ElapsedMilliseconds}ms");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void ComponentsResource_ReadAllWithFullSerialization()
|
|
|
|
|
{
|
|
|
|
|
var go = CreateTestObject("FullSerialize");
|
|
|
|
|
|
|
|
|
|
var rb = go.AddComponent<Rigidbody>();
|
|
|
|
|
rb.mass = 5.5f;
|
|
|
|
|
rb.drag = 1.2f;
|
|
|
|
|
|
|
|
|
|
var col = go.AddComponent<BoxCollider>();
|
|
|
|
|
col.size = new Vector3(2, 3, 4);
|
|
|
|
|
col.center = new Vector3(0.5f, 0.5f, 0.5f);
|
|
|
|
|
|
|
|
|
|
// Skip AudioSource to avoid deprecated API warnings
|
|
|
|
|
|
|
|
|
|
var sw = Stopwatch.StartNew();
|
|
|
|
|
|
|
|
|
|
// Use the components resource handler
|
|
|
|
|
var result = ToJObject(GameObjectComponentsResource.HandleCommand(new JObject
|
|
|
|
|
{
|
|
|
|
|
["instanceID"] = go.GetInstanceID(),
|
|
|
|
|
["includeProperties"] = true
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
sw.Stop();
|
|
|
|
|
|
|
|
|
|
Assert.IsTrue(result["success"]?.Value<bool>() ?? false);
|
|
|
|
|
var data = result["data"] as JObject;
|
|
|
|
|
var components = data?["components"] as JArray;
|
|
|
|
|
|
|
|
|
|
Assert.IsNotNull(components);
|
|
|
|
|
Assert.AreEqual(3, components.Count); // Transform + Rigidbody + BoxCollider
|
|
|
|
|
|
|
|
|
|
Debug.Log($"[ComponentsResource] Full serialization of {components.Count} components in {sw.ElapsedMilliseconds}ms");
|
|
|
|
|
|
|
|
|
|
// Verify serialized data includes properties
|
|
|
|
|
bool foundRigidbody = false;
|
|
|
|
|
foreach (JObject comp in components)
|
|
|
|
|
{
|
|
|
|
|
var typeName = comp["typeName"]?.Value<string>();
|
|
|
|
|
if (typeName != null && typeName.Contains("Rigidbody"))
|
|
|
|
|
{
|
|
|
|
|
foundRigidbody = true;
|
|
|
|
|
// GameObjectSerializer puts properties inside a "properties" nested object
|
|
|
|
|
var props = comp["properties"] as JObject;
|
|
|
|
|
Assert.IsNotNull(props, $"Rigidbody should have properties. Component data: {comp}");
|
|
|
|
|
float massValue = props["mass"]?.Value<float>() ?? 0;
|
|
|
|
|
Assert.AreEqual(5.5f, massValue, 0.01f, $"Mass should be 5.5");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Assert.IsTrue(foundRigidbody, "Should find Rigidbody with serialized properties");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Concurrent-Like Operations
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void RapidFireOperations_CreateModifyDelete()
|
|
|
|
|
{
|
|
|
|
|
var sw = Stopwatch.StartNew();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < SMALL_BATCH; i++)
|
|
|
|
|
{
|
|
|
|
|
// Create
|
|
|
|
|
var createResult = ToJObject(ManageGameObject.HandleCommand(new JObject
|
|
|
|
|
{
|
|
|
|
|
["action"] = "create",
|
|
|
|
|
["name"] = $"RapidFire_{i}"
|
|
|
|
|
}));
|
|
|
|
|
Assert.IsTrue(createResult["success"]?.Value<bool>() ?? false, $"Create failed: {createResult["message"]}");
|
|
|
|
|
|
|
|
|
|
int instanceId = createResult["data"]?["instanceID"]?.Value<int>() ?? 0;
|
|
|
|
|
Assert.AreNotEqual(0, instanceId, "Instance ID should not be 0");
|
|
|
|
|
|
|
|
|
|
// Modify - use layer 0 (Default) to avoid layer name issues
|
|
|
|
|
var modifyResult = ToJObject(ManageGameObject.HandleCommand(new JObject
|
|
|
|
|
{
|
|
|
|
|
["action"] = "modify",
|
|
|
|
|
["target"] = instanceId.ToString(),
|
|
|
|
|
["searchMethod"] = "by_id",
|
|
|
|
|
["name"] = $"RapidFire_Modified_{i}", // Use name modification instead
|
|
|
|
|
["setActive"] = true
|
|
|
|
|
}));
|
|
|
|
|
Assert.IsTrue(modifyResult["success"]?.Value<bool>() ?? false, $"Modify failed: {modifyResult["message"]}");
|
|
|
|
|
|
|
|
|
|
// Delete
|
|
|
|
|
var deleteResult = ToJObject(ManageGameObject.HandleCommand(new JObject
|
|
|
|
|
{
|
|
|
|
|
["action"] = "delete",
|
|
|
|
|
["target"] = instanceId.ToString(),
|
|
|
|
|
["searchMethod"] = "by_id"
|
|
|
|
|
}));
|
|
|
|
|
Assert.IsTrue(deleteResult["success"]?.Value<bool>() ?? false, $"Delete failed: {deleteResult["message"]}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sw.Stop();
|
|
|
|
|
Debug.Log($"[RapidFire] {SMALL_BATCH} create-modify-delete cycles in {sw.ElapsedMilliseconds}ms");
|
|
|
|
|
Assert.Less(sw.ElapsedMilliseconds, 10000, "Rapid fire operations took too long");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Marker component used for isolating component-based searches to objects created by this test fixture.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed class GameObjectAPIStressTestMarker : MonoBehaviour { }
|
|
|
|
|
}
|
|
|
|
|
|