@alpsckr/unitycli 0.4.3 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/contracts/command-contract.json +109 -217
- package/contracts/package-files.json +374 -294
- package/contracts/stable-session/direct-call-semantics.json +8 -2
- package/contracts/tool-completion-contract.json +148 -672
- package/dist/bridge/admission.d.ts +17 -1
- package/dist/bridge/admission.js +2 -0
- package/dist/bridge/admission.js.map +1 -1
- package/dist/bridge/domains/asset.js +3 -1
- package/dist/bridge/domains/asset.js.map +1 -1
- package/dist/bridge/domains/camera.js +39 -7
- package/dist/bridge/domains/camera.js.map +1 -1
- package/dist/bridge/domains/console.js +2 -1
- package/dist/bridge/domains/console.js.map +1 -1
- package/dist/bridge/domains/doctor.d.ts +3 -0
- package/dist/bridge/domains/doctor.js +8 -0
- package/dist/bridge/domains/doctor.js.map +1 -0
- package/dist/bridge/domains/editor.js +0 -4
- package/dist/bridge/domains/editor.js.map +1 -1
- package/dist/bridge/domains/game-object.js +4 -12
- package/dist/bridge/domains/game-object.js.map +1 -1
- package/dist/bridge/domains/graphics.js +1 -16
- package/dist/bridge/domains/graphics.js.map +1 -1
- package/dist/bridge/domains/object.js +3 -0
- package/dist/bridge/domains/object.js.map +1 -1
- package/dist/bridge/domains/physics.js +1 -2
- package/dist/bridge/domains/physics.js.map +1 -1
- package/dist/bridge/domains/profiler.js +18 -7
- package/dist/bridge/domains/profiler.js.map +1 -1
- package/dist/bridge/domains/project-tool.js +4 -0
- package/dist/bridge/domains/project-tool.js.map +1 -1
- package/dist/bridge/domains/reflection.js +10 -2
- package/dist/bridge/domains/reflection.js.map +1 -1
- package/dist/bridge/domains/runtime-tool.js +3 -0
- package/dist/bridge/domains/runtime-tool.js.map +1 -1
- package/dist/bridge/domains/scene.js +3 -8
- package/dist/bridge/domains/scene.js.map +1 -1
- package/dist/bridge/domains/screenshot.js +30 -6
- package/dist/bridge/domains/screenshot.js.map +1 -1
- package/dist/bridge/domains/ui.js +3 -1
- package/dist/bridge/domains/ui.js.map +1 -1
- package/dist/bridge/index.d.ts +5 -5
- package/dist/bridge/index.js +6 -9
- package/dist/bridge/index.js.map +1 -1
- package/dist/bridge/registry.js +3 -5
- package/dist/bridge/registry.js.map +1 -1
- package/dist/bridge/session-supervisor.d.ts +6 -3
- package/dist/bridge/session-supervisor.js +145 -39
- package/dist/bridge/session-supervisor.js.map +1 -1
- package/dist/bridge/start-diagnostics.d.ts +19 -0
- package/dist/bridge/start-diagnostics.js +96 -0
- package/dist/bridge/start-diagnostics.js.map +1 -0
- package/dist/bridge/types.d.ts +6 -5
- package/dist/bridge/types.js +16 -2
- package/dist/bridge/types.js.map +1 -1
- package/dist/catalog-protocol.d.ts +64 -0
- package/dist/catalog-protocol.js +460 -0
- package/dist/catalog-protocol.js.map +1 -0
- package/dist/cli-commands/command.js +84 -61
- package/dist/cli-commands/command.js.map +1 -1
- package/dist/cli-commands/extensions.js +70 -50
- package/dist/cli-commands/extensions.js.map +1 -1
- package/dist/cli-commands/instances.js +1 -1
- package/dist/cli-commands/instances.js.map +1 -1
- package/dist/cli-commands/package.js +10 -2
- package/dist/cli-commands/package.js.map +1 -1
- package/dist/cli-process-owners.d.ts +6 -0
- package/dist/cli-process-owners.js +27 -0
- package/dist/cli-process-owners.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +20 -5
- package/dist/cli.js.map +1 -1
- package/dist/contracts/command-contract.js +14 -4
- package/dist/contracts/command-contract.js.map +1 -1
- package/dist/doctor.d.ts +4 -6
- package/dist/doctor.js +451 -398
- package/dist/doctor.js.map +1 -1
- package/dist/editor-blocker-observer.d.ts +2 -1
- package/dist/editor-blocker-observer.js +40 -13
- package/dist/editor-blocker-observer.js.map +1 -1
- package/dist/extension-availability.d.ts +27 -9
- package/dist/extension-availability.js +96 -56
- package/dist/extension-availability.js.map +1 -1
- package/dist/extensions/descriptor-loader.d.ts +2 -2
- package/dist/extensions/descriptor-loader.js +3 -4
- package/dist/extensions/descriptor-loader.js.map +1 -1
- package/dist/extensions/index.d.ts +1 -1
- package/dist/extensions/index.js +1 -1
- package/dist/extensions/index.js.map +1 -1
- package/dist/extensions/installer.d.ts +57 -5
- package/dist/extensions/installer.js +263 -64
- package/dist/extensions/installer.js.map +1 -1
- package/dist/extensions/registry.js +3 -5
- package/dist/extensions/registry.js.map +1 -1
- package/dist/extensions/state-contract.d.ts +11 -0
- package/dist/extensions/state-contract.js +50 -0
- package/dist/extensions/state-contract.js.map +1 -0
- package/dist/instances.d.ts +19 -1
- package/dist/instances.js +39 -0
- package/dist/instances.js.map +1 -1
- package/dist/package-manager.d.ts +0 -24
- package/dist/package-manager.js +5 -58
- package/dist/package-manager.js.map +1 -1
- package/dist/project-tool-discovery.d.ts +5 -2
- package/dist/project-tool-discovery.js +22 -18
- package/dist/project-tool-discovery.js.map +1 -1
- package/dist/runtime-code.js +1 -1
- package/dist/runtime-code.js.map +1 -1
- package/dist/tool-name.d.ts +4 -0
- package/dist/tool-name.js +15 -0
- package/dist/tool-name.js.map +1 -0
- package/dist/tools/.generated-completion-registry.d.ts +2 -0
- package/dist/tools/.generated-completion-registry.js +763 -325
- package/dist/tools/.generated-completion-registry.js.map +1 -1
- package/dist/tools/.generated-registry.d.ts +6 -11
- package/dist/tools/.generated-registry.js +6 -11
- package/dist/tools/.generated-registry.js.map +1 -1
- package/dist/tools/.generated-schemas.js +11 -16
- package/dist/tools/.generated-schemas.js.map +1 -1
- package/dist/tools/asset/refresh-status.d.ts +3 -0
- package/dist/tools/asset/refresh-status.js +12 -0
- package/dist/tools/asset/refresh-status.js.map +1 -0
- package/dist/tools/asset/refresh.js +7 -3
- package/dist/tools/asset/refresh.js.map +1 -1
- package/dist/tools/build/player-status.d.ts +3 -0
- package/dist/tools/build/player-status.js +12 -0
- package/dist/tools/build/player-status.js.map +1 -0
- package/dist/tools/build/run.js +1 -1
- package/dist/tools/build/run.js.map +1 -1
- package/dist/tools/catalog-control.d.ts +22 -0
- package/dist/tools/catalog-control.js +335 -0
- package/dist/tools/catalog-control.js.map +1 -0
- package/dist/tools/catalog.d.ts +7 -9
- package/dist/tools/catalog.js +66 -45
- package/dist/tools/catalog.js.map +1 -1
- package/dist/tools/commands.d.ts +1 -1
- package/dist/tools/commands.js +44 -11
- package/dist/tools/commands.js.map +1 -1
- package/dist/tools/define.js +3 -2
- package/dist/tools/define.js.map +1 -1
- package/dist/tools/dispatcher.d.ts +53 -6
- package/dist/tools/dispatcher.js +277 -108
- package/dist/tools/dispatcher.js.map +1 -1
- package/dist/tools/editor/compile.js +1 -1
- package/dist/tools/editor/compile.js.map +1 -1
- package/dist/tools/editor/play-mode-status.d.ts +3 -0
- package/dist/tools/editor/play-mode-status.js +12 -0
- package/dist/tools/editor/play-mode-status.js.map +1 -0
- package/dist/tools/editor/readiness.d.ts +0 -1
- package/dist/tools/editor/readiness.js +0 -1
- package/dist/tools/editor/readiness.js.map +1 -1
- package/dist/tools/editor/refresh.d.ts +7 -3
- package/dist/tools/editor/refresh.js +20 -15
- package/dist/tools/editor/refresh.js.map +1 -1
- package/dist/tools/editor/state.d.ts +2 -2
- package/dist/tools/editor/state.js +13 -12
- package/dist/tools/editor/state.js.map +1 -1
- package/dist/tools/metadata/derived.js +1 -7
- package/dist/tools/metadata/derived.js.map +1 -1
- package/dist/tools/metadata/status.js +1 -12
- package/dist/tools/metadata/status.js.map +1 -1
- package/dist/tools/metadata/types.d.ts +0 -2
- package/dist/tools/navmesh/live.js +1 -1
- package/dist/tools/navmesh/live.js.map +1 -1
- package/dist/tools/occlusion/live.js +1 -1
- package/dist/tools/occlusion/live.js.map +1 -1
- package/dist/tools/package/deploy.js +2 -2
- package/dist/tools/package/deploy.js.map +1 -1
- package/dist/tools/package/embed.js +1 -1
- package/dist/tools/package/embed.js.map +1 -1
- package/dist/tools/package/install-status.d.ts +3 -0
- package/dist/tools/package/install-status.js +12 -0
- package/dist/tools/package/install-status.js.map +1 -0
- package/dist/tools/package/install.js +3 -3
- package/dist/tools/package/install.js.map +1 -1
- package/dist/tools/package/live-resolve.js +45 -12
- package/dist/tools/package/live-resolve.js.map +1 -1
- package/dist/tools/package/registry-clear.js.map +1 -1
- package/dist/tools/package/registry-set.js.map +1 -1
- package/dist/tools/package/remove-status.d.ts +3 -0
- package/dist/tools/package/remove-status.js +12 -0
- package/dist/tools/package/remove-status.js.map +1 -0
- package/dist/tools/package/remove.js +3 -3
- package/dist/tools/package/remove.js.map +1 -1
- package/dist/tools/package/resolve.js +3 -3
- package/dist/tools/package/resolve.js.map +1 -1
- package/dist/tools/package/restore.js +2 -2
- package/dist/tools/package/restore.js.map +1 -1
- package/dist/tools/package/scoped-registry-add.js.map +1 -1
- package/dist/tools/status/support.d.ts +13 -0
- package/dist/tools/status/support.js +28 -0
- package/dist/tools/status/support.js.map +1 -0
- package/dist/tools/test/cancel.js +2 -2
- package/dist/tools/test/cancel.js.map +1 -1
- package/dist/tools/test/run-status.d.ts +3 -0
- package/dist/tools/test/run-status.js +12 -0
- package/dist/tools/test/run-status.js.map +1 -0
- package/dist/tools/test/run.js +7 -4
- package/dist/tools/test/run.js.map +1 -1
- package/dist/tools/tools/group-summaries.js +10 -14
- package/dist/tools/tools/group-summaries.js.map +1 -1
- package/docs/architecture.md +4 -4
- package/docs/availability-differences.md +4 -4
- package/docs/command-execution.md +4 -3
- package/docs/error-codes.md +13 -5
- package/docs/extensions.md +1 -7
- package/docs/risk-and-side-effects.md +4 -3
- package/docs/safety-boundaries.md +3 -1
- package/docs/target-and-instance.md +1 -1
- package/docs/tool-discovery.md +17 -10
- package/docs/tool-execution.md +18 -7
- package/extensions/cinemachine/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
- package/extensions/hdrp/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
- package/extensions/probuilder/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
- package/extensions/urp/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
- package/extensions/vfx-graph/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
- package/package.json +2 -4
- package/skills/unitycli/SKILL.md +11 -2
- package/skills/unitycli/manifest.json +1 -1
- package/skills/unitycli/references/operation-protocol.md +3 -3
- package/unitypkg/Editor/AssemblyInfo.cs +5 -0
- package/unitypkg/Editor/Bridge/BridgeCommandRegistry.cs +49 -14
- package/unitypkg/Editor/Bridge/BridgeHttpServer.cs +9 -35
- package/unitypkg/Editor/Bridge/BridgeQueueState.cs +672 -0
- package/unitypkg/Editor/Bridge/BridgeWorkQueue.cs +970 -120
- package/unitypkg/Editor/BridgeStartupStateMachine.cs +59 -1
- package/unitypkg/Editor/Commands/ProjectToolCommands.cs +4 -0
- package/unitypkg/Editor/Commands/RuntimeToolCommands.cs +3 -0
- package/unitypkg/Editor/Generated/UnityCliToolCompletionRegistry.g.cs +379 -14
- package/unitypkg/Editor/Infrastructure/CodeExecSupport.cs +28 -13
- package/unitypkg/Editor/Infrastructure/RegistryOwnershipResolver.cs +132 -105
- package/unitypkg/Editor/Infrastructure/UnityCliActiveOperation.cs +138 -29
- package/unitypkg/Editor/Infrastructure/UnityCliAdmission.cs +1856 -237
- package/unitypkg/Editor/Infrastructure/UnityCliDoctorDiagnostics.cs +220 -0
- package/unitypkg/Editor/Infrastructure/UnityCliDurableStatusCoordinator.cs +118 -0
- package/unitypkg/Editor/Infrastructure/UnityCliEditorStateSnapshot.cs +2 -1
- package/unitypkg/Editor/Infrastructure/UnityCliHierarchyPager.cs +203 -0
- package/unitypkg/Editor/Infrastructure/UnityCliInstanceCleanup.cs +2 -1
- package/unitypkg/Editor/Infrastructure/UnityCliInstanceRegistry.cs +201 -63
- package/unitypkg/Editor/Infrastructure/UnityCliJson.cs +2 -3
- package/unitypkg/Editor/Infrastructure/UnityCliLiveBuildCoordinator.cs +289 -46
- package/unitypkg/Editor/Infrastructure/UnityCliLiveRefreshCoordinator.cs +87 -53
- package/unitypkg/Editor/Infrastructure/UnityCliNavMeshBakeCoordinator.cs +4 -4
- package/unitypkg/Editor/Infrastructure/UnityCliObjectRef.cs +128 -25
- package/unitypkg/Editor/Infrastructure/UnityCliObjectSerializer.cs +435 -327
- package/unitypkg/Editor/Infrastructure/UnityCliOcclusionBakeCoordinator.cs +4 -4
- package/unitypkg/Editor/Infrastructure/UnityCliOperationCoordinator.cs +351 -53
- package/unitypkg/Editor/Infrastructure/UnityCliPackageResolveCoordinator.cs +118 -18
- package/unitypkg/Editor/Infrastructure/UnityCliPackageResolveState.cs +20 -1
- package/unitypkg/Editor/Infrastructure/UnityCliReadGeneration.cs +97 -0
- package/unitypkg/Editor/Infrastructure/UnityCliReflection.cs +644 -133
- package/unitypkg/Editor/Infrastructure/UnityCliReloadOperation.cs +128 -41
- package/unitypkg/Editor/Infrastructure/UnityCliRuntimeExecutor.cs +14 -84
- package/unitypkg/Editor/Infrastructure/UnityCliSerializer.cs +220 -0
- package/unitypkg/Editor/Infrastructure/UnityCliSessionMutationLease.cs +69 -29
- package/unitypkg/Editor/Infrastructure/UnityCliTestRunCoordinator.cs +440 -54
- package/unitypkg/Editor/Infrastructure/UnityCliWaiter.cs +128 -35
- package/unitypkg/Editor/ProjectTools/IUnityCliTool.cs +9 -1
- package/unitypkg/Editor/ProjectTools/OfficialAttributeToolRegistry.cs +32 -31
- package/unitypkg/Editor/ProjectTools/ProjectToolRegistry.cs +607 -458
- package/unitypkg/Editor/ProjectTools/ProjectToolSchemaValidator.cs +35 -7
- package/unitypkg/Editor/ProjectTools/RegisteredTool.cs +501 -60
- package/unitypkg/Editor/ProjectTools/UnityCliProjectToolContext.cs +21 -0
- package/unitypkg/Editor/ProjectTools/UnityCliSchemaDeriver.cs +36 -7
- package/unitypkg/Editor/ProjectTools/UnityCliToolName.cs +30 -0
- package/unitypkg/Editor/Properties/AssemblyInfo.cs +3 -0
- package/unitypkg/Editor/Tools/asset/get-data.cs +16 -7
- package/unitypkg/Editor/Tools/build/BuildSceneStateTool.cs +23 -3
- package/unitypkg/Editor/Tools/build/BuildToolDtos.cs +2 -1
- package/unitypkg/Editor/Tools/build/scene/add.cs +6 -3
- package/unitypkg/Editor/Tools/build/scene/disable.cs +1 -1
- package/unitypkg/Editor/Tools/build/scene/enable.cs +1 -1
- package/unitypkg/Editor/Tools/build/scene/list.cs +1 -1
- package/unitypkg/Editor/Tools/build/scene/remove.cs +13 -3
- package/unitypkg/Editor/Tools/camera/CameraToolSupport.cs +38 -38
- package/unitypkg/Editor/Tools/camera/multiview.cs +13 -6
- package/unitypkg/Editor/Tools/camera/screenshot.cs +14 -7
- package/unitypkg/Editor/Tools/component/get.cs +16 -9
- package/unitypkg/Editor/Tools/component/modify.cs +6 -4
- package/unitypkg/Editor/Tools/console/clear.cs +7 -6
- package/unitypkg/Editor/Tools/console/get.cs +48 -16
- package/unitypkg/Editor/Tools/editor/EditorToolSupport.cs +143 -28
- package/unitypkg/Editor/Tools/editor/menu-items.cs +1 -1
- package/unitypkg/Editor/Tools/editor/play.cs +2 -11
- package/unitypkg/Editor/Tools/editor/selection.cs +1 -1
- package/unitypkg/Editor/Tools/editor/stop.cs +2 -11
- package/unitypkg/Editor/Tools/editor/windows.cs +1 -1
- package/unitypkg/Editor/Tools/go/GameObjectToolSupport.cs +54 -4
- package/unitypkg/Editor/Tools/go/delete.cs +1 -1
- package/unitypkg/Editor/Tools/go/find.cs +167 -70
- package/unitypkg/Editor/Tools/graphics/GraphicsToolSupport.cs +25 -0
- package/unitypkg/Editor/Tools/graphics/light-probe-group.cs +9 -3
- package/unitypkg/Editor/Tools/graphics/pipeline-info.cs +1 -1
- package/unitypkg/Editor/Tools/graphics/pipeline-settings.cs +1 -1
- package/unitypkg/Editor/Tools/graphics/quality.cs +2 -2
- package/unitypkg/Editor/Tools/graphics/reflection-probe.cs +3 -3
- package/unitypkg/Editor/Tools/graphics/stats.cs +1 -1
- package/unitypkg/Editor/Tools/object/ObjectToolSupport.cs +4 -4
- package/unitypkg/Editor/Tools/object/get-data.cs +9 -7
- package/unitypkg/Editor/Tools/object/modify.cs +7 -5
- package/unitypkg/Editor/Tools/physics/raycast.cs +24 -6
- package/unitypkg/Editor/Tools/prefab/stage.cs +1 -1
- package/unitypkg/Editor/Tools/profiler/ProfilerFrameDebuggerSupport.cs +94 -21
- package/unitypkg/Editor/Tools/profiler/ProfilerMemorySnapshotSupport.cs +397 -10
- package/unitypkg/Editor/Tools/profiler/ProfilerToolSupport.cs +18 -4
- package/unitypkg/Editor/Tools/profiler/frame-debugger/enable.cs +1 -1
- package/unitypkg/Editor/Tools/profiler/frame-debugger/events.cs +88 -87
- package/unitypkg/Editor/Tools/profiler/hierarchy.cs +278 -112
- package/unitypkg/Editor/Tools/profiler/memory-snapshot/compare.cs +9 -5
- package/unitypkg/Editor/Tools/profiler/memory-snapshot/list.cs +37 -35
- package/unitypkg/Editor/Tools/profiler/memory-snapshot/take.cs +99 -97
- package/unitypkg/Editor/Tools/project/info.cs +1 -1
- package/unitypkg/Editor/Tools/project/layers.cs +1 -1
- package/unitypkg/Editor/Tools/project/tags.cs +1 -1
- package/unitypkg/Editor/Tools/reflection/method/call.cs +9 -7
- package/unitypkg/Editor/Tools/reflection/method/find.cs +27 -37
- package/unitypkg/Editor/Tools/reflection/type/schema.cs +24 -28
- package/unitypkg/Editor/Tools/scene/gameobject/component.cs +8 -5
- package/unitypkg/Editor/Tools/scene/gameobject/components.cs +10 -7
- package/unitypkg/Editor/Tools/scene/gameobject-support.cs +104 -8
- package/unitypkg/Editor/Tools/scene/gameobject.cs +37 -16
- package/unitypkg/Editor/Tools/scene/hierarchy.cs +146 -142
- package/unitypkg/Editor/Tools/screenshot/EditorElementCaptureToolSupport.cs +77 -25
- package/unitypkg/Editor/Tools/screenshot/ScreenshotToolSupport.cs +315 -95
- package/unitypkg/Editor/Tools/screenshot/editor-element.cs +7 -5
- package/unitypkg/Editor/Tools/screenshot/game-view.cs +35 -21
- package/unitypkg/Editor/Tools/screenshot/isolated-object.cs +29 -23
- package/unitypkg/Editor/Tools/screenshot/orbit.cs +37 -25
- package/unitypkg/Editor/Tools/screenshot/scene-view.cs +25 -18
- package/unitypkg/Editor/Tools/screenshot/screen.cs +177 -17
- package/unitypkg/Editor/Tools/screenshot/surround.cs +83 -79
- package/unitypkg/Editor/Tools/ui/render-preview.cs +16 -11
- package/unitypkg/Editor/UnityCliBridge.cs +97 -34
- package/unitypkg/Runtime/Infrastructure/UnityCliRuntimeBridge.cs +753 -122
- package/unitypkg/Runtime/Properties/AssemblyInfo.cs +5 -0
- package/unitypkg/Runtime/UnityCli.Runtime.asmdef +1 -1
- package/unitypkg/Shared/AssemblyInfo.cs +4 -0
- package/unitypkg/Shared/UnityCliAdmissionPool.cs +265 -75
- package/unitypkg/Shared/UnityCliBridgeQueuePolicy.cs +410 -0
- package/unitypkg/Shared/UnityCliBridgeWorkItemState.cs +47 -0
- package/unitypkg/Shared/UnityCliExceptionProjection.cs +151 -0
- package/unitypkg/Shared/UnityCliQueuePublication.cs +30 -0
- package/unitypkg/Shared/UnityCliRequestBudget.cs +352 -0
- package/unitypkg/Shared/UnityCliRuntimeRequestPublication.cs +107 -0
- package/unitypkg/Tests/Editor/UnityCli.Editor.Tests.asmdef +2 -2
- package/unitypkg/Tests/Editor/UnityCliAdmissionCoordinatorIntegrationTests.cs +1329 -0
- package/unitypkg/Tests/Editor/UnityCliAdmissionPoolTests.cs +311 -114
- package/unitypkg/Tests/Editor/UnityCliAdmissionRepairStateTests.cs +94 -0
- package/unitypkg/Tests/Editor/UnityCliBridgeAdmissionIntegrationTests.cs +1229 -0
- package/unitypkg/Tests/Editor/UnityCliBridgeQueuePolicyTests.cs +412 -0
- package/unitypkg/Tests/Editor/UnityCliContinuationIntegrationTests.cs +292 -0
- package/unitypkg/Tests/Editor/UnityCliDiagnosticBoundaryTests.cs +120 -0
- package/unitypkg/Tests/Editor/UnityCliDurableStatusTests.cs +84 -0
- package/unitypkg/Tests/Editor/UnityCliExceptionProjectionTests.cs +72 -0
- package/unitypkg/Tests/Editor/UnityCliInstancePublicationRetryTests.cs +180 -97
- package/unitypkg/Tests/Editor/UnityCliMemoryReflectionIntegrationTests.cs +434 -0
- package/unitypkg/Tests/Editor/UnityCliRequestBudgetTests.cs +615 -0
- package/unitypkg/Tests/Editor/UnityCliRuntimeBridgeIntegrationTests.cs +520 -0
- package/unitypkg/Tests/Editor/UnityCliToolCatalogTests.cs +562 -0
- package/unitypkg/Tests/Runtime/UnityCli.Runtime.Tests.asmdef +23 -0
- package/unitypkg/Tests/Runtime/UnityCliRuntimeCatalogValidationTests.cs +29 -0
- package/unitypkg/package.json +1 -1
- package/contracts/static-capability-catalog.json +0 -48226
- package/dist/bridge/domains/diagnose.d.ts +0 -2
- package/dist/bridge/domains/diagnose.js +0 -11
- package/dist/bridge/domains/diagnose.js.map +0 -1
- package/dist/bridge/domains/extension.d.ts +0 -8
- package/dist/bridge/domains/extension.js +0 -20
- package/dist/bridge/domains/extension.js.map +0 -1
- package/dist/capabilities.d.ts +0 -5
- package/dist/capabilities.js +0 -84
- package/dist/capabilities.js.map +0 -1
- package/dist/static-capability-catalog.d.ts +0 -13
- package/dist/static-capability-catalog.js +0 -41
- package/dist/static-capability-catalog.js.map +0 -1
- package/dist/tools/diagnose/capabilities.d.ts +0 -13
- package/dist/tools/diagnose/capabilities.js +0 -34
- package/dist/tools/diagnose/capabilities.js.map +0 -1
- package/dist/tools/package/info.d.ts +0 -19
- package/dist/tools/package/info.js +0 -27
- package/dist/tools/package/info.js.map +0 -1
- package/dist/tools/package/list.d.ts +0 -27
- package/dist/tools/package/list.js +0 -21
- package/dist/tools/package/list.js.map +0 -1
- package/dist/tools/package/registry-get.d.ts +0 -10
- package/dist/tools/package/registry-get.js +0 -22
- package/dist/tools/package/registry-get.js.map +0 -1
- package/dist/tools/package/scoped-registry-list.d.ts +0 -14
- package/dist/tools/package/scoped-registry-list.js +0 -22
- package/dist/tools/package/scoped-registry-list.js.map +0 -1
- package/dist/tools/package/search.d.ts +0 -17
- package/dist/tools/package/search.js +0 -39
- package/dist/tools/package/search.js.map +0 -1
- package/dist/tools/package/status.d.ts +0 -21
- package/dist/tools/package/status.js +0 -37
- package/dist/tools/package/status.js.map +0 -1
- package/dist/tools/tools/describe.d.ts +0 -27
- package/dist/tools/tools/describe.js +0 -35
- package/dist/tools/tools/describe.js.map +0 -1
- package/dist/tools/tools/group-list.d.ts +0 -16
- package/dist/tools/tools/group-list.js +0 -38
- package/dist/tools/tools/group-list.js.map +0 -1
- package/dist/tools/tools/group.d.ts +0 -13
- package/dist/tools/tools/group.js +0 -34
- package/dist/tools/tools/group.js.map +0 -1
- package/dist/tools/tools/pagination.d.ts +0 -17
- package/dist/tools/tools/pagination.js +0 -28
- package/dist/tools/tools/pagination.js.map +0 -1
- package/dist/tools/tools/search.d.ts +0 -15
- package/dist/tools/tools/search.js +0 -42
- package/dist/tools/tools/search.js.map +0 -1
- package/unitypkg/Editor/Tools/diagnose/preflight.cs +0 -269
- package/unitypkg/Editor/Tools/editor/console.cs +0 -26
- package/unitypkg/Editor/Tools/extension/status.cs +0 -87
- package/unitypkg/Editor/Tools/go/inspect.cs +0 -49
- package/unitypkg/Editor/Tools/graphics/pipeline-quality.cs +0 -24
- package/unitypkg/Editor/Tools/graphics/probe-positions.cs +0 -58
- package/unitypkg/Editor/Tools/graphics/settings.cs +0 -104
- package/unitypkg/Editor/Tools/graphics/stats-counters.cs +0 -28
- package/unitypkg/Editor/Tools/graphics/stats-memory.cs +0 -21
- package/unitypkg/Editor/Tools/instances/list.cs +0 -71
- package/unitypkg/Editor/Tools/physics/raycast-all.cs +0 -67
- package/unitypkg/Editor/Tools/scene/build-settings.cs +0 -69
- package/unitypkg/Editor/Tools/scene/data.cs +0 -125
- package/unitypkg/Editor/Tools/tests/list.cs +0 -98
|
@@ -3,8 +3,8 @@ using System;
|
|
|
3
3
|
using System.Collections;
|
|
4
4
|
using System.Collections.Generic;
|
|
5
5
|
using System.Globalization;
|
|
6
|
-
using System.Linq;
|
|
7
6
|
using System.Reflection;
|
|
7
|
+
using System.Text;
|
|
8
8
|
using System.Text.RegularExpressions;
|
|
9
9
|
using UnityEditor;
|
|
10
10
|
using UnityEngine;
|
|
@@ -13,19 +13,23 @@ namespace UnityCli
|
|
|
13
13
|
{
|
|
14
14
|
public static partial class UnityCliBridge
|
|
15
15
|
{
|
|
16
|
-
/// <summary>
|
|
17
|
-
/// 深序列化器:将任意 Unity Object / Component / C# 对象序列化为 SerializedMember 树。
|
|
18
|
-
/// 对齐 IvanMurzak SerializedMember 模型:typeName/name/value/fields/props/path/readOnly/writable/error/truncated。
|
|
19
|
-
/// 支持 maxDepth、paths 局部读取、数组/list/dictionary、Unity Object 引用、循环引用检测。
|
|
20
|
-
/// </summary>
|
|
16
|
+
/// <summary>在共享 request budget 下深读 Unity/CLR 对象;任何展开、getter 或枚举前先裁决。</summary>
|
|
21
17
|
internal static class ObjectSerializer
|
|
22
18
|
{
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
internal const int DefaultMaxDepth = 4;
|
|
20
|
+
internal const int AbsoluteMaxDepth = 16;
|
|
21
|
+
internal const int DefaultMaxArrayItems = 50;
|
|
22
|
+
internal const int AbsoluteMaxArrayItems = 1000;
|
|
23
|
+
internal const int AbsoluteMaxMembers = 256;
|
|
24
|
+
internal const int AbsoluteMaxPaths = 128;
|
|
25
|
+
private const int AbsoluteMaxMemberCandidates = 512;
|
|
26
|
+
private const int AbsoluteMaxMemberMetadataUtf8Bytes = 64 * 1024;
|
|
27
|
+
private const int AbsoluteMaxCachedMemberTypes = 128;
|
|
28
|
+
private const int AbsoluteMaxTypeHierarchyDepth = 32;
|
|
29
|
+
private static readonly object MemberCacheLock = new object();
|
|
30
|
+
private static readonly Dictionary<Type, MemberCandidate[]> MemberCandidateCache = new Dictionary<Type, MemberCandidate[]>();
|
|
31
|
+
private static string memberCacheGeneration;
|
|
27
32
|
|
|
28
|
-
/// <summary>序列化选项。</summary>
|
|
29
33
|
public sealed class Options
|
|
30
34
|
{
|
|
31
35
|
public int MaxDepth = DefaultMaxDepth;
|
|
@@ -33,59 +37,91 @@ namespace UnityCli
|
|
|
33
37
|
public bool IncludeFields = true;
|
|
34
38
|
public bool IncludeProperties = true;
|
|
35
39
|
public bool DeepSerialization = true;
|
|
36
|
-
public
|
|
40
|
+
public List<string> Paths;
|
|
37
41
|
public HashSet<string> NameFilters;
|
|
38
|
-
public HashSet<string> TypeFilters;
|
|
39
42
|
public string ViewPath;
|
|
40
43
|
public string NamePattern;
|
|
41
44
|
public string TypeFilter;
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
public static object Serialize(object obj, Options options)
|
|
47
|
+
public static object Serialize(object obj, Options options, UnityCliRequestBudget budget)
|
|
46
48
|
{
|
|
47
|
-
if (
|
|
49
|
+
if (budget == null) throw new ArgumentNullException("budget");
|
|
50
|
+
options = options ?? new Options();
|
|
51
|
+
ValidateOptions(options);
|
|
52
|
+
var active = new HashSet<object>(ReferenceComparer.Instance);
|
|
53
|
+
SerializedMember root;
|
|
48
54
|
if (options.Paths != null && options.Paths.Count > 0)
|
|
49
55
|
{
|
|
50
|
-
|
|
56
|
+
root = BuildPathReadAggregate(obj, options, active, budget);
|
|
51
57
|
}
|
|
52
|
-
|
|
58
|
+
else
|
|
53
59
|
{
|
|
54
|
-
|
|
55
|
-
if (error != null)
|
|
60
|
+
if (!string.IsNullOrEmpty(options.ViewPath))
|
|
56
61
|
{
|
|
57
|
-
|
|
62
|
+
var normalized = NormalizeReadPath(options.ViewPath);
|
|
63
|
+
obj = TryReadPath(obj, normalized, budget, out var error);
|
|
64
|
+
if (error != null) return MemberToData(ErrorMember("$." + normalized, error));
|
|
58
65
|
}
|
|
59
|
-
|
|
66
|
+
var path = string.IsNullOrEmpty(options.ViewPath) ? "$" : "$." + NormalizeReadPath(options.ViewPath);
|
|
67
|
+
root = ToMember(obj, path, obj == null ? "null" : obj.GetType().FullName, options, active, 0, budget);
|
|
60
68
|
}
|
|
61
|
-
|
|
62
|
-
return MemberToData(ToMember(obj, string.IsNullOrEmpty(options.ViewPath) ? "$" : "$" + "." + NormalizeReadPath(options.ViewPath), obj == null ? "null" : obj.GetType().FullName, options, visited, 0));
|
|
69
|
+
return MemberToData(root);
|
|
63
70
|
}
|
|
64
71
|
|
|
65
|
-
|
|
66
|
-
private static SerializedMember BuildPathReadAggregate(object obj, Options options)
|
|
72
|
+
internal static int ParseMaxDepth(object value)
|
|
67
73
|
{
|
|
68
|
-
|
|
74
|
+
try
|
|
69
75
|
{
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
var result = Convert.ToInt32(value, CultureInfo.InvariantCulture);
|
|
77
|
+
if (result < 0 || result > AbsoluteMaxDepth) throw new OverflowException();
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
catch (Exception exception) when (exception is FormatException || exception is InvalidCastException || exception is OverflowException)
|
|
74
81
|
{
|
|
82
|
+
throw new BridgeCommandException("E_OBJECT_READ_LIMIT_INVALID", "viewQuery.maxDepth must be an integer between 0 and 16.", "Pass a maxDepth in the documented range.");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private static void ValidateOptions(Options options)
|
|
87
|
+
{
|
|
88
|
+
if (options.MaxDepth < 0 || options.MaxDepth > AbsoluteMaxDepth)
|
|
89
|
+
throw new BridgeCommandException("E_OBJECT_READ_LIMIT_INVALID", "maxDepth must be between 0 and 16.", "Pass a maxDepth in the documented range.");
|
|
90
|
+
if (options.MaxArrayItems < 1 || options.MaxArrayItems > AbsoluteMaxArrayItems)
|
|
91
|
+
throw new BridgeCommandException("E_OBJECT_READ_LIMIT_INVALID", "maxArrayItems must be between 1 and 1000.", "Pass a maxArrayItems in the documented range.");
|
|
92
|
+
if (options.Paths != null && options.Paths.Count > AbsoluteMaxPaths)
|
|
93
|
+
throw new BridgeCommandException("E_OBJECT_READ_LIMIT_INVALID", "paths accepts at most 128 entries.", "Split the exact member reads across requests.");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// <summary>所有 paths 共用 active graph 和同一个 budget;输入顺序即稳定 continuation 顺序。</summary>
|
|
97
|
+
private static SerializedMember BuildPathReadAggregate(object obj, Options options, HashSet<object> active, UnityCliRequestBudget budget)
|
|
98
|
+
{
|
|
99
|
+
var aggregate = NewMember("$", "UnityCli.PathReadAggregate", budget);
|
|
100
|
+
if (aggregate.Truncated) return aggregate;
|
|
101
|
+
var childOptions = WithoutPaths(options);
|
|
102
|
+
for (var index = 0; index < options.Paths.Count; index++)
|
|
103
|
+
{
|
|
104
|
+
var rawPath = options.Paths[index];
|
|
105
|
+
if (string.IsNullOrWhiteSpace(rawPath)) continue;
|
|
106
|
+
if (!budget.TryCheckpoint()) { aggregate.Truncated = true; aggregate.NextPath = rawPath; break; }
|
|
75
107
|
var normalizedPath = NormalizeReadPath(rawPath);
|
|
76
|
-
var value = TryReadPath(obj, normalizedPath, out var error);
|
|
77
|
-
|
|
108
|
+
var value = TryReadPath(obj, normalizedPath, budget, out var error);
|
|
109
|
+
var path = string.IsNullOrEmpty(normalizedPath) ? "$" : "$." + normalizedPath;
|
|
110
|
+
var child = error == null
|
|
111
|
+
? ToMember(value, path, value == null ? "null" : value.GetType().FullName, childOptions, active, 0, budget)
|
|
112
|
+
: ErrorMember(path, error);
|
|
113
|
+
if (!budget.TryReserveSerializedUtf8(rawPath)) { aggregate.Truncated = true; aggregate.NextPath = rawPath; aggregate.Error = BudgetError(budget); break; }
|
|
114
|
+
aggregate.Fields.Add(new SerializedMember.KV { Key = rawPath, Value = child });
|
|
115
|
+
if (child.Truncated && budget.IsExhausted)
|
|
78
116
|
{
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
});
|
|
117
|
+
aggregate.Truncated = true;
|
|
118
|
+
aggregate.NextPath = rawPath;
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
84
121
|
}
|
|
85
122
|
return aggregate;
|
|
86
123
|
}
|
|
87
124
|
|
|
88
|
-
/// <summary>移除 paths 选项,避免读取到的子树再次被路径过滤。</summary>
|
|
89
125
|
private static Options WithoutPaths(Options options)
|
|
90
126
|
{
|
|
91
127
|
return new Options
|
|
@@ -96,13 +132,11 @@ namespace UnityCli
|
|
|
96
132
|
IncludeProperties = options.IncludeProperties,
|
|
97
133
|
DeepSerialization = options.DeepSerialization,
|
|
98
134
|
NameFilters = options.NameFilters,
|
|
99
|
-
TypeFilters = options.TypeFilters,
|
|
100
135
|
NamePattern = options.NamePattern,
|
|
101
136
|
TypeFilter = options.TypeFilter
|
|
102
137
|
};
|
|
103
138
|
}
|
|
104
139
|
|
|
105
|
-
/// <summary>规范化读取路径;支持 #/、$/、点路径和 slash 路径。</summary>
|
|
106
140
|
private static string NormalizeReadPath(string path)
|
|
107
141
|
{
|
|
108
142
|
if (string.IsNullOrEmpty(path)) return string.Empty;
|
|
@@ -110,441 +144,505 @@ namespace UnityCli
|
|
|
110
144
|
if (normalized.StartsWith("#/", StringComparison.Ordinal)) normalized = normalized.Substring(2);
|
|
111
145
|
if (normalized.StartsWith("$.", StringComparison.Ordinal)) normalized = normalized.Substring(2);
|
|
112
146
|
if (normalized.StartsWith("$/", StringComparison.Ordinal)) normalized = normalized.Substring(2);
|
|
113
|
-
|
|
114
|
-
return normalized.Trim('.');
|
|
147
|
+
if (normalized.StartsWith("$[", StringComparison.Ordinal)) normalized = normalized.Substring(1);
|
|
148
|
+
return normalized.Replace('/', '.').Trim('.');
|
|
115
149
|
}
|
|
116
150
|
|
|
117
|
-
|
|
118
|
-
private static object TryReadPath(object root, string path, out string error)
|
|
151
|
+
private static object TryReadPath(object root, string path, UnityCliRequestBudget budget, out string error)
|
|
119
152
|
{
|
|
120
153
|
error = null;
|
|
121
|
-
if (root == null)
|
|
122
|
-
{
|
|
123
|
-
error = "root object is null";
|
|
124
|
-
return null;
|
|
125
|
-
}
|
|
154
|
+
if (root == null) { error = "root object is null"; return null; }
|
|
126
155
|
if (string.IsNullOrEmpty(path)) return root;
|
|
127
156
|
var current = root;
|
|
128
157
|
foreach (var rawSegment in path.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries))
|
|
129
158
|
{
|
|
130
|
-
|
|
159
|
+
if (!budget.TryConsumeNode()) { error = "request-budget-exhausted:" + budget.ExhaustedDimension; return null; }
|
|
160
|
+
current = ReadPathSegment(current, rawSegment, budget, out error);
|
|
131
161
|
if (error != null) return null;
|
|
132
162
|
}
|
|
133
163
|
return current;
|
|
134
164
|
}
|
|
135
165
|
|
|
136
|
-
|
|
137
|
-
private static object ReadPathSegment(object current, string segment, out string error)
|
|
166
|
+
private static object ReadPathSegment(object current, string segment, UnityCliRequestBudget budget, out string error)
|
|
138
167
|
{
|
|
139
168
|
error = null;
|
|
140
|
-
if (current == null)
|
|
141
|
-
{
|
|
142
|
-
error = "encountered null before segment '" + segment + "'";
|
|
143
|
-
return null;
|
|
144
|
-
}
|
|
145
|
-
|
|
169
|
+
if (current == null) { error = "encountered null before segment '" + segment + "'"; return null; }
|
|
146
170
|
var name = segment;
|
|
147
171
|
string indexer = null;
|
|
148
172
|
var bracketIndex = segment.IndexOf('[');
|
|
149
|
-
if (bracketIndex >= 0)
|
|
150
|
-
{
|
|
151
|
-
name = segment.Substring(0, bracketIndex);
|
|
152
|
-
indexer = segment.Substring(bracketIndex).Trim();
|
|
153
|
-
}
|
|
154
|
-
|
|
173
|
+
if (bracketIndex >= 0) { name = segment.Substring(0, bracketIndex); indexer = segment.Substring(bracketIndex).Trim(); }
|
|
155
174
|
object value = current;
|
|
156
175
|
if (!string.IsNullOrEmpty(name))
|
|
157
176
|
{
|
|
158
177
|
var type = current.GetType();
|
|
178
|
+
if (!budget.TryConsumeReflectionCall()) { error = BudgetError(budget); return null; }
|
|
159
179
|
var field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
|
160
180
|
if (field != null)
|
|
161
181
|
{
|
|
182
|
+
if (!budget.TryConsumeGetter() || !budget.TryConsumeReflectionCall()) { error = BudgetError(budget); return null; }
|
|
162
183
|
value = field.GetValue(current);
|
|
163
184
|
}
|
|
164
185
|
else
|
|
165
186
|
{
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
{
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
value = prop.GetValue(current, null);
|
|
187
|
+
if (!budget.TryConsumeReflectionCall()) { error = BudgetError(budget); return null; }
|
|
188
|
+
var property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
|
189
|
+
if (property == null || !property.CanRead || property.GetIndexParameters().Length > 0) { error = "member not found or unreadable: " + name; return null; }
|
|
190
|
+
if (!budget.TryConsumeGetter() || !budget.TryConsumeReflectionCall()) { error = BudgetError(budget); return null; }
|
|
191
|
+
value = property.GetValue(current, null);
|
|
173
192
|
}
|
|
174
193
|
}
|
|
175
|
-
|
|
176
|
-
if (!string.IsNullOrEmpty(indexer))
|
|
177
|
-
{
|
|
178
|
-
value = ReadIndexer(value, indexer, out error);
|
|
179
|
-
}
|
|
180
|
-
return value;
|
|
194
|
+
return string.IsNullOrEmpty(indexer) ? value : ReadIndexer(value, indexer, budget, out error);
|
|
181
195
|
}
|
|
182
196
|
|
|
183
|
-
|
|
184
|
-
private static object ReadIndexer(object value, string indexer, out string error)
|
|
197
|
+
private static object ReadIndexer(object value, string indexer, UnityCliRequestBudget budget, out string error)
|
|
185
198
|
{
|
|
186
199
|
error = null;
|
|
187
|
-
if (value == null)
|
|
188
|
-
{
|
|
189
|
-
error = "cannot index null value";
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
200
|
+
if (value == null) { error = "cannot index null value"; return null; }
|
|
192
201
|
var token = indexer.Trim();
|
|
193
|
-
if (!token.StartsWith("[", StringComparison.Ordinal) || !token.EndsWith("]", StringComparison.Ordinal))
|
|
194
|
-
|
|
195
|
-
error = "invalid index syntax: " + indexer;
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
token = token.Substring(1, token.Length - 2).Trim().Trim('"');
|
|
202
|
+
if (!token.StartsWith("[", StringComparison.Ordinal) || !token.EndsWith("]", StringComparison.Ordinal)) { error = "invalid index syntax: " + indexer; return null; }
|
|
203
|
+
token = UnescapeDictionaryPathKey(token.Substring(1, token.Length - 2).Trim().Trim('"'));
|
|
199
204
|
if (value is IList list)
|
|
200
205
|
{
|
|
206
|
+
if (!budget.TryConsumeGetter()) { error = BudgetError(budget); return null; }
|
|
207
|
+
var count = list.Count;
|
|
201
208
|
int index;
|
|
202
|
-
if (!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out index) || index < 0 || index >= list
|
|
203
|
-
{
|
|
204
|
-
error = "list index out of range: " + token;
|
|
205
|
-
return null;
|
|
206
|
-
}
|
|
209
|
+
if (!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out index) || index < 0 || index >= count) { error = "list index out of range: " + token; return null; }
|
|
210
|
+
if (!budget.TryConsumeGetter()) { error = BudgetError(budget); return null; }
|
|
207
211
|
return list[index];
|
|
208
212
|
}
|
|
209
|
-
if (value is IDictionary
|
|
213
|
+
if (value is IDictionary dictionary)
|
|
210
214
|
{
|
|
211
|
-
|
|
215
|
+
var enumerator = dictionary.GetEnumerator();
|
|
216
|
+
try
|
|
212
217
|
{
|
|
213
|
-
|
|
218
|
+
while (budget.TryConsumeScannedRecord() && enumerator.MoveNext())
|
|
214
219
|
{
|
|
215
|
-
|
|
220
|
+
if (!budget.TryConsumeGetter()) { error = BudgetError(budget); return null; }
|
|
221
|
+
var entry = enumerator.Entry;
|
|
222
|
+
if (string.Equals(Convert.ToString(entry.Key, CultureInfo.InvariantCulture), token, StringComparison.Ordinal)) return entry.Value;
|
|
216
223
|
}
|
|
217
224
|
}
|
|
218
|
-
|
|
225
|
+
finally { (enumerator as IDisposable)?.Dispose(); }
|
|
226
|
+
error = budget.IsExhausted ? BudgetError(budget) : "dictionary key not found: " + token;
|
|
219
227
|
return null;
|
|
220
228
|
}
|
|
221
229
|
error = "value is not indexable";
|
|
222
230
|
return null;
|
|
223
231
|
}
|
|
224
232
|
|
|
225
|
-
private static SerializedMember ToMember(object obj, string path, string declaredType, Options options, HashSet<
|
|
233
|
+
private static SerializedMember ToMember(object obj, string path, string declaredType, Options options, HashSet<object> active, int depth, UnityCliRequestBudget budget)
|
|
226
234
|
{
|
|
227
|
-
var member =
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
TypeName = declaredType ?? "null"
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
if (obj == null)
|
|
234
|
-
{
|
|
235
|
-
member.Value = null;
|
|
236
|
-
member.IsNull = true;
|
|
237
|
-
return member;
|
|
238
|
-
}
|
|
239
|
-
|
|
235
|
+
var member = NewMember(path, declaredType ?? "null", budget);
|
|
236
|
+
if (member.Truncated) return member;
|
|
237
|
+
if (obj == null) { member.IsNull = true; return member; }
|
|
240
238
|
var type = obj.GetType();
|
|
241
239
|
member.TypeName = type.FullName;
|
|
242
|
-
|
|
243
|
-
// 基元与简单值类型直接输出
|
|
244
|
-
if (IsSimpleType(type))
|
|
240
|
+
if (IsSimpleType(type) || obj is string)
|
|
245
241
|
{
|
|
246
242
|
member.Value = obj;
|
|
243
|
+
if (!budget.TryReserveSerializedUtf8(Convert.ToString(obj, CultureInfo.InvariantCulture))) Truncate(member, budget, path);
|
|
247
244
|
return member;
|
|
248
245
|
}
|
|
249
246
|
|
|
250
|
-
//
|
|
251
|
-
if (
|
|
247
|
+
// depth 与 cycle 必须先于 Unity 引用、集合枚举和反射成员展开。
|
|
248
|
+
if (depth >= options.MaxDepth)
|
|
252
249
|
{
|
|
253
|
-
member.Value =
|
|
250
|
+
member.Value = UnsupportedValue(type, "max-depth-reached");
|
|
251
|
+
member.Truncated = true;
|
|
252
|
+
member.NextPath = path;
|
|
254
253
|
return member;
|
|
255
254
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
if (obj is UnityEngine.Object uo)
|
|
255
|
+
var trackReference = !type.IsValueType;
|
|
256
|
+
if (trackReference && !active.Add(obj))
|
|
259
257
|
{
|
|
260
|
-
member.
|
|
261
|
-
member.
|
|
262
|
-
|
|
263
|
-
if (depth < options.MaxDepth && options.DeepSerialization)
|
|
264
|
-
{
|
|
265
|
-
ExpandMembers(obj, type, member, options, visited, depth);
|
|
266
|
-
}
|
|
258
|
+
member.Error = "cycle-detected";
|
|
259
|
+
member.Truncated = true;
|
|
260
|
+
member.NextPath = path;
|
|
267
261
|
return member;
|
|
268
262
|
}
|
|
269
263
|
|
|
270
|
-
|
|
271
|
-
if (obj is IList list)
|
|
264
|
+
try
|
|
272
265
|
{
|
|
273
|
-
|
|
274
|
-
member.ArrayLength = list.Count;
|
|
275
|
-
var maxArrayItems = ClampMaxArrayItems(options.MaxArrayItems);
|
|
276
|
-
var limit = Math.Min(list.Count, maxArrayItems);
|
|
277
|
-
member.Truncated = list.Count > maxArrayItems;
|
|
278
|
-
for (var i = 0; i < limit; i++)
|
|
266
|
+
if (obj is UnityEngine.Object unityObject)
|
|
279
267
|
{
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
268
|
+
member.IsUnityObject = true;
|
|
269
|
+
member.UnityObjectRef = UnityObjectRefJson(unityObject, budget);
|
|
270
|
+
if (options.DeepSerialization) ExpandMembers(obj, type, member, options, active, depth, budget);
|
|
271
|
+
return member;
|
|
283
272
|
}
|
|
273
|
+
if (obj is IList list) { ExpandList(list, member, options, active, depth, budget); return member; }
|
|
274
|
+
if (obj is IDictionary dictionary) { ExpandDictionary(dictionary, member, options, active, depth, budget); return member; }
|
|
275
|
+
if (obj is IEnumerable enumerable && !(obj is GameObject)) { ExpandEnumerable(enumerable, member, options, active, depth, budget); return member; }
|
|
276
|
+
ExpandMembers(obj, type, member, options, active, depth, budget);
|
|
284
277
|
return member;
|
|
285
278
|
}
|
|
286
|
-
|
|
287
|
-
// IDictionary
|
|
288
|
-
if (obj is IDictionary dict)
|
|
279
|
+
finally
|
|
289
280
|
{
|
|
290
|
-
|
|
291
|
-
member.ArrayLength = dict.Count;
|
|
292
|
-
var limit = 0;
|
|
293
|
-
foreach (DictionaryEntry entry in dict)
|
|
294
|
-
{
|
|
295
|
-
if (limit >= ClampMaxArrayItems(options.MaxArrayItems)) { member.Truncated = true; break; }
|
|
296
|
-
var keyStr = Convert.ToString(entry.Key, CultureInfo.InvariantCulture);
|
|
297
|
-
var entryPath = path + "[" + keyStr + "]";
|
|
298
|
-
if (!PathAllowed(entryPath, options)) continue;
|
|
299
|
-
member.DictionaryEntries.Add(new SerializedMember.KV
|
|
300
|
-
{
|
|
301
|
-
Key = keyStr,
|
|
302
|
-
Value = ToMember(entry.Value, entryPath, null, options, visited, depth + 1)
|
|
303
|
-
});
|
|
304
|
-
limit++;
|
|
305
|
-
}
|
|
306
|
-
return member;
|
|
281
|
+
if (trackReference) active.Remove(obj);
|
|
307
282
|
}
|
|
283
|
+
}
|
|
308
284
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
285
|
+
private static SerializedMember NewMember(string path, string typeName, UnityCliRequestBudget budget)
|
|
286
|
+
{
|
|
287
|
+
if (!budget.TryConsumeNode() || !budget.TryReserveSerializedUtf8Bytes(128L) || !budget.TryReserveSerializedUtf8(path) || !budget.TryReserveSerializedUtf8(typeName))
|
|
288
|
+
return new SerializedMember { Path = path, TypeName = typeName, Truncated = true, NextPath = path, Error = BudgetError(budget) };
|
|
289
|
+
return new SerializedMember { Path = path, TypeName = typeName };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private static void ExpandList(IList list, SerializedMember member, Options options, HashSet<object> active, int depth, UnityCliRequestBudget budget)
|
|
293
|
+
{
|
|
294
|
+
member.IsArray = true;
|
|
295
|
+
if (!budget.TryConsumeGetter()) { Truncate(member, budget, member.Path + "[0]"); return; }
|
|
296
|
+
var count = list.Count;
|
|
297
|
+
member.ArrayLength = count;
|
|
298
|
+
member.HasKnownTotal = true;
|
|
299
|
+
var limit = Math.Min(count, options.MaxArrayItems);
|
|
300
|
+
for (var index = 0; index < limit; index++)
|
|
301
|
+
{
|
|
302
|
+
var itemPath = member.Path + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
|
|
303
|
+
if (!budget.TryConsumeScannedRecord() || !budget.TryConsumeGetter()) { Truncate(member, budget, itemPath); return; }
|
|
304
|
+
member.ArrayItems.Add(ToMember(list[index], itemPath, null, options, active, depth + 1, budget));
|
|
305
|
+
if (budget.IsExhausted) { Truncate(member, budget, itemPath); return; }
|
|
306
|
+
}
|
|
307
|
+
if (count > limit) { member.Truncated = true; member.NextPath = member.Path + "[" + limit.ToString(CultureInfo.InvariantCulture) + "]"; }
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private static void ExpandDictionary(IDictionary dictionary, SerializedMember member, Options options, HashSet<object> active, int depth, UnityCliRequestBudget budget)
|
|
311
|
+
{
|
|
312
|
+
member.IsDictionary = true;
|
|
313
|
+
if (!budget.TryConsumeGetter()) { TruncateNonResumableDictionary(member); return; }
|
|
314
|
+
var count = dictionary.Count;
|
|
315
|
+
member.ArrayLength = count;
|
|
316
|
+
member.HasKnownTotal = true;
|
|
317
|
+
var enumerator = dictionary.GetEnumerator();
|
|
318
|
+
var emitted = 0;
|
|
319
|
+
try
|
|
320
|
+
{
|
|
321
|
+
while (emitted < options.MaxArrayItems)
|
|
315
322
|
{
|
|
316
|
-
if (
|
|
317
|
-
|
|
318
|
-
if (
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
+
if (!budget.TryConsumeScannedRecord()) { TruncateNonResumableDictionary(member); return; }
|
|
324
|
+
if (!enumerator.MoveNext()) return;
|
|
325
|
+
if (!budget.TryConsumeGetter()) { TruncateNonResumableDictionary(member); return; }
|
|
326
|
+
var entry = enumerator.Entry;
|
|
327
|
+
var key = Convert.ToString(entry.Key, CultureInfo.InvariantCulture);
|
|
328
|
+
var entryPath = DictionaryEntryPath(member.Path, key);
|
|
329
|
+
if (!budget.TryReserveSerializedUtf8(key)) { TruncateNonResumableDictionary(member); return; }
|
|
330
|
+
member.DictionaryEntries.Add(new SerializedMember.KV { Key = key, Value = ToMember(entry.Value, entryPath, null, options, active, depth + 1, budget) });
|
|
331
|
+
emitted++;
|
|
332
|
+
if (budget.IsExhausted) { TruncateNonResumableDictionary(member); return; }
|
|
333
|
+
}
|
|
334
|
+
if (emitted < count)
|
|
335
|
+
{
|
|
336
|
+
member.Truncated = true;
|
|
337
|
+
member.NextPath = null;
|
|
338
|
+
member.Error = "non-resumable-dictionary-truncation";
|
|
323
339
|
}
|
|
324
|
-
member.ArrayLength = count;
|
|
325
|
-
return member;
|
|
326
340
|
}
|
|
341
|
+
finally { (enumerator as IDisposable)?.Dispose(); }
|
|
342
|
+
}
|
|
327
343
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
344
|
+
private static void TruncateNonResumableDictionary(SerializedMember member)
|
|
345
|
+
{
|
|
346
|
+
member.Truncated = true;
|
|
347
|
+
member.NextPath = null;
|
|
348
|
+
member.Error = "non-resumable-dictionary-truncation";
|
|
349
|
+
}
|
|
335
350
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
351
|
+
/// <summary>非 IList enumerable 只流式返回当前片段;它没有可重放的索引语义,截断时不发布 nextPath。</summary>
|
|
352
|
+
private static void ExpandEnumerable(IEnumerable enumerable, SerializedMember member, Options options, HashSet<object> active, int depth, UnityCliRequestBudget budget)
|
|
353
|
+
{
|
|
354
|
+
member.IsArray = true;
|
|
355
|
+
var enumerator = enumerable.GetEnumerator();
|
|
356
|
+
var index = 0;
|
|
357
|
+
try
|
|
339
358
|
{
|
|
340
|
-
|
|
359
|
+
while (index < options.MaxArrayItems)
|
|
360
|
+
{
|
|
361
|
+
if (!budget.TryConsumeScannedRecord()) { TruncateNonResumableEnumerable(member, budget); return; }
|
|
362
|
+
if (!enumerator.MoveNext()) { member.ArrayLength = index; return; }
|
|
363
|
+
if (!budget.TryConsumeGetter()) { TruncateNonResumableEnumerable(member, budget); return; }
|
|
364
|
+
var itemPath = member.Path + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
|
|
365
|
+
member.ArrayItems.Add(ToMember(enumerator.Current, itemPath, null, options, active, depth + 1, budget));
|
|
366
|
+
index++;
|
|
367
|
+
if (budget.IsExhausted) { TruncateNonResumableEnumerable(member, budget); return; }
|
|
368
|
+
}
|
|
369
|
+
member.ArrayLength = index;
|
|
370
|
+
if (!budget.TryConsumeScannedRecord()) { TruncateNonResumableEnumerable(member, budget); return; }
|
|
371
|
+
if (!enumerator.MoveNext()) return;
|
|
341
372
|
member.Truncated = true;
|
|
342
|
-
|
|
373
|
+
member.NextPath = null;
|
|
374
|
+
member.Error = "non-resumable-enumerable-truncation";
|
|
343
375
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
ExpandMembers(obj, type, member, options, visited, depth);
|
|
347
|
-
return member;
|
|
376
|
+
finally { (enumerator as IDisposable)?.Dispose(); }
|
|
348
377
|
}
|
|
349
378
|
|
|
350
|
-
private static void ExpandMembers(object obj, Type type, SerializedMember member, Options options, HashSet<
|
|
379
|
+
private static void ExpandMembers(object obj, Type type, SerializedMember member, Options options, HashSet<object> active, int depth, UnityCliRequestBudget budget)
|
|
351
380
|
{
|
|
352
381
|
var memberCount = 0;
|
|
353
|
-
|
|
354
|
-
{
|
|
355
|
-
|
|
382
|
+
foreach (var candidate in GetMemberCandidates(type, budget))
|
|
383
|
+
{
|
|
384
|
+
if ((candidate.IsProperty && !options.IncludeProperties) || (!candidate.IsProperty && !options.IncludeFields)) continue;
|
|
385
|
+
if (!MemberAllowed(candidate.Name, options)) continue;
|
|
386
|
+
if (memberCount >= AbsoluteMaxMembers) { member.Truncated = true; member.NextPath = member.Path + "." + candidate.Name; return; }
|
|
387
|
+
var candidatePath = member.Path + "." + candidate.Name;
|
|
388
|
+
if (!budget.TryConsumeGetter() || !budget.TryConsumeReflectionCall()) { Truncate(member, budget, candidatePath); return; }
|
|
389
|
+
try
|
|
356
390
|
{
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
391
|
+
var value = candidate.IsProperty
|
|
392
|
+
? ((PropertyInfo)candidate.Member).GetValue(obj, null)
|
|
393
|
+
: ((FieldInfo)candidate.Member).GetValue(obj);
|
|
394
|
+
var child = ToMember(value, candidatePath, candidate.ValueTypeName, options, active, depth + 1, budget);
|
|
395
|
+
if (TypeAllowed(child.TypeName, options))
|
|
360
396
|
{
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
if (!
|
|
364
|
-
member.
|
|
365
|
-
{
|
|
366
|
-
Key = field.Name,
|
|
367
|
-
Value = child
|
|
368
|
-
});
|
|
397
|
+
child.ReadOnly = candidate.IsProperty && !candidate.CanWrite;
|
|
398
|
+
child.Writable = candidate.IsProperty && candidate.CanWrite;
|
|
399
|
+
if (!budget.TryReserveSerializedUtf8(candidate.Name)) { Truncate(member, budget, candidatePath); return; }
|
|
400
|
+
var destination = candidate.IsProperty ? member.Props : member.Fields;
|
|
401
|
+
destination.Add(new SerializedMember.KV { Key = candidate.Name, Value = child });
|
|
369
402
|
memberCount++;
|
|
370
403
|
}
|
|
371
|
-
|
|
372
|
-
{
|
|
373
|
-
member.Fields.Add(new SerializedMember.KV { Key = field.Name, Value = ErrorMember(member.Path + "." + field.Name, e.Message) });
|
|
374
|
-
}
|
|
404
|
+
if (budget.IsExhausted) { Truncate(member, budget, candidatePath); return; }
|
|
375
405
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
if (options.IncludeProperties)
|
|
379
|
-
{
|
|
380
|
-
foreach (var prop in type.GetProperties(BindingFlags.Instance | BindingFlags.Public))
|
|
406
|
+
catch (UnityCliRequestBudgetExceededException) { Truncate(member, budget, candidatePath); return; }
|
|
407
|
+
catch (Exception exception)
|
|
381
408
|
{
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
try
|
|
386
|
-
{
|
|
387
|
-
var value = prop.GetValue(obj, null);
|
|
388
|
-
var child = ToMember(value, member.Path + "." + prop.Name, prop.PropertyType.FullName, options, visited, depth + 1);
|
|
389
|
-
if (!TypeAllowed(child.TypeName, options)) continue;
|
|
390
|
-
child.ReadOnly = !prop.CanWrite;
|
|
391
|
-
child.Writable = prop.CanWrite;
|
|
392
|
-
member.Props.Add(new SerializedMember.KV { Key = prop.Name, Value = child });
|
|
393
|
-
memberCount++;
|
|
394
|
-
}
|
|
395
|
-
catch (Exception e)
|
|
396
|
-
{
|
|
397
|
-
member.Props.Add(new SerializedMember.KV { Key = prop.Name, Value = ErrorMember(member.Path + "." + prop.Name, e.Message) });
|
|
398
|
-
}
|
|
409
|
+
var destination = candidate.IsProperty ? member.Props : member.Fields;
|
|
410
|
+
destination.Add(new SerializedMember.KV { Key = candidate.Name, Value = ErrorMember(candidatePath, exception.Message) });
|
|
411
|
+
memberCount++;
|
|
399
412
|
}
|
|
400
413
|
}
|
|
401
414
|
}
|
|
402
415
|
|
|
403
|
-
private static
|
|
416
|
+
private static string DictionaryEntryPath(string path, string key)
|
|
404
417
|
{
|
|
405
|
-
|
|
418
|
+
var value = key ?? string.Empty;
|
|
419
|
+
var builder = new StringBuilder(value.Length + 8);
|
|
420
|
+
foreach (var character in value)
|
|
421
|
+
{
|
|
422
|
+
if (char.IsLetterOrDigit(character) || character == '_' || character == '-') builder.Append(character);
|
|
423
|
+
else builder.Append("\\u").Append(((int)character).ToString("x4", CultureInfo.InvariantCulture));
|
|
424
|
+
}
|
|
425
|
+
return path + "[\"" + builder + "\"]";
|
|
406
426
|
}
|
|
407
427
|
|
|
408
|
-
private static
|
|
428
|
+
private static string UnescapeDictionaryPathKey(string value)
|
|
409
429
|
{
|
|
410
|
-
if (
|
|
411
|
-
|
|
430
|
+
if (string.IsNullOrEmpty(value) || value.IndexOf("\\u", StringComparison.Ordinal) < 0) return value;
|
|
431
|
+
var builder = new StringBuilder(value.Length);
|
|
432
|
+
for (var index = 0; index < value.Length; index++)
|
|
412
433
|
{
|
|
413
|
-
if (
|
|
434
|
+
if (value[index] == '\\' && index + 5 < value.Length && value[index + 1] == 'u'
|
|
435
|
+
&& int.TryParse(value.Substring(index + 2, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var code))
|
|
414
436
|
{
|
|
415
|
-
|
|
437
|
+
builder.Append((char)code);
|
|
438
|
+
index += 5;
|
|
416
439
|
}
|
|
440
|
+
else builder.Append(value[index]);
|
|
417
441
|
}
|
|
418
|
-
return
|
|
442
|
+
return builder.ToString();
|
|
419
443
|
}
|
|
420
444
|
|
|
421
|
-
|
|
445
|
+
/// <summary>逐项扫描 declared metadata,并把单次 domain reload 内的有界结果复用给后续读取。</summary>
|
|
446
|
+
private static MemberCandidate[] GetMemberCandidates(Type type, UnityCliRequestBudget budget)
|
|
422
447
|
{
|
|
423
|
-
|
|
448
|
+
MemberCandidate[] cached;
|
|
449
|
+
var generation = UnityCliReadGeneration.Reflection();
|
|
450
|
+
lock (MemberCacheLock)
|
|
424
451
|
{
|
|
425
|
-
|
|
452
|
+
if (!string.Equals(memberCacheGeneration, generation, StringComparison.Ordinal))
|
|
453
|
+
{
|
|
454
|
+
MemberCandidateCache.Clear();
|
|
455
|
+
memberCacheGeneration = generation;
|
|
456
|
+
}
|
|
457
|
+
if (MemberCandidateCache.TryGetValue(type, out cached))
|
|
458
|
+
{
|
|
459
|
+
if (!budget.TryConsumeScannedRecords(cached.Length)) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
|
|
460
|
+
return cached;
|
|
461
|
+
}
|
|
426
462
|
}
|
|
427
|
-
|
|
463
|
+
|
|
464
|
+
var candidates = new List<MemberCandidate>();
|
|
465
|
+
var names = new HashSet<string>(StringComparer.Ordinal);
|
|
466
|
+
var metadataBytes = 0;
|
|
467
|
+
var current = type;
|
|
468
|
+
for (var depth = 0; current != null && depth < AbsoluteMaxTypeHierarchyDepth; depth++)
|
|
428
469
|
{
|
|
429
|
-
|
|
470
|
+
if (!budget.TryConsumeReflectionCall()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
|
|
471
|
+
foreach (var field in UnityCliReflectionSnapshot.DeclaredFields(current, budget, AbsoluteMaxMemberCandidates))
|
|
472
|
+
{
|
|
473
|
+
ConsumeMemberCandidateBudget(budget);
|
|
474
|
+
if (!field.IsPublic || field.IsStatic || !names.Add("F:" + field.Name)) continue;
|
|
475
|
+
AddMemberCandidate(candidates, new MemberCandidate(field, false, true, field.FieldType), ref metadataBytes);
|
|
476
|
+
}
|
|
477
|
+
if (!budget.TryConsumeReflectionCall()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
|
|
478
|
+
foreach (var property in UnityCliReflectionSnapshot.DeclaredProperties(current, budget, AbsoluteMaxMemberCandidates))
|
|
479
|
+
{
|
|
480
|
+
ConsumeMemberCandidateBudget(budget);
|
|
481
|
+
var getter = property.GetMethod;
|
|
482
|
+
if (getter == null || !getter.IsPublic || getter.IsStatic || property.GetIndexParameters().Length > 0 || !names.Add("P:" + property.Name)) continue;
|
|
483
|
+
AddMemberCandidate(candidates, new MemberCandidate(property, true, property.CanWrite, property.PropertyType), ref metadataBytes);
|
|
484
|
+
}
|
|
485
|
+
if (!budget.TryConsumeReflectionCall()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
|
|
486
|
+
current = current.GetTypeInfo().BaseType;
|
|
430
487
|
}
|
|
431
|
-
|
|
488
|
+
if (current != null) throw MetadataLimit("type hierarchy depth");
|
|
489
|
+
candidates.Sort((left, right) =>
|
|
490
|
+
{
|
|
491
|
+
var nameOrder = string.CompareOrdinal(left.Name, right.Name);
|
|
492
|
+
return nameOrder != 0 ? nameOrder : left.IsProperty.CompareTo(right.IsProperty);
|
|
493
|
+
});
|
|
494
|
+
cached = candidates.ToArray();
|
|
495
|
+
lock (MemberCacheLock)
|
|
496
|
+
{
|
|
497
|
+
if (MemberCandidateCache.Count >= AbsoluteMaxCachedMemberTypes) MemberCandidateCache.Clear();
|
|
498
|
+
MemberCandidateCache[type] = cached;
|
|
499
|
+
}
|
|
500
|
+
return cached;
|
|
432
501
|
}
|
|
433
502
|
|
|
434
|
-
private static
|
|
503
|
+
private static void ConsumeMemberCandidateBudget(UnityCliRequestBudget budget)
|
|
435
504
|
{
|
|
436
|
-
|
|
437
|
-
|| (!string.IsNullOrEmpty(typeName) && typeName.IndexOf(options.TypeFilter, StringComparison.OrdinalIgnoreCase) >= 0);
|
|
505
|
+
if (!budget.TryConsumeScannedRecord() || !budget.TryConsumeReflectionCall()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
|
|
438
506
|
}
|
|
439
507
|
|
|
440
|
-
private static
|
|
508
|
+
private static void AddMemberCandidate(List<MemberCandidate> candidates, MemberCandidate candidate, ref int metadataBytes)
|
|
441
509
|
{
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
510
|
+
if (candidates.Count >= AbsoluteMaxMemberCandidates) throw MetadataLimit("member count");
|
|
511
|
+
metadataBytes += Encoding.UTF8.GetByteCount(candidate.Name ?? string.Empty);
|
|
512
|
+
metadataBytes += Encoding.UTF8.GetByteCount(candidate.ValueTypeName ?? string.Empty);
|
|
513
|
+
if (metadataBytes > AbsoluteMaxMemberMetadataUtf8Bytes) throw MetadataLimit("metadata bytes");
|
|
514
|
+
candidates.Add(candidate);
|
|
446
515
|
}
|
|
447
516
|
|
|
448
|
-
private static
|
|
517
|
+
private static BridgeCommandException MetadataLimit(string dimension)
|
|
518
|
+
{
|
|
519
|
+
return new BridgeCommandException("E_REQUEST_BUDGET_EXCEEDED", "Object member metadata exceeded its bounded " + dimension + ".", "Use paths, nameFilters, or a narrower object type.");
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
private static void Truncate(SerializedMember member, UnityCliRequestBudget budget, string nextPath)
|
|
449
523
|
{
|
|
450
|
-
|
|
524
|
+
member.Truncated = true;
|
|
525
|
+
member.NextPath = nextPath;
|
|
526
|
+
if (member.Error == null && budget.IsExhausted) member.Error = BudgetError(budget);
|
|
451
527
|
}
|
|
452
528
|
|
|
453
|
-
private static
|
|
529
|
+
private static void TruncateNonResumableEnumerable(SerializedMember member, UnityCliRequestBudget budget)
|
|
454
530
|
{
|
|
455
|
-
|
|
531
|
+
member.Truncated = true;
|
|
532
|
+
member.NextPath = null;
|
|
533
|
+
member.Error = budget.IsExhausted ? BudgetError(budget) + ";non-resumable-enumerable" : "non-resumable-enumerable-truncation";
|
|
456
534
|
}
|
|
457
535
|
|
|
458
|
-
|
|
459
|
-
|
|
536
|
+
private static string BudgetError(UnityCliRequestBudget budget) { return "request-budget-exhausted:" + (budget.ExhaustedDimension ?? "unknown"); }
|
|
537
|
+
|
|
538
|
+
private static bool MemberAllowed(string name, Options options)
|
|
539
|
+
{
|
|
540
|
+
if (options.NameFilters != null && options.NameFilters.Count > 0) return options.NameFilters.Contains(name);
|
|
541
|
+
if (string.IsNullOrEmpty(options.NamePattern)) return true;
|
|
542
|
+
try { return Regex.IsMatch(name, options.NamePattern, RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(50)); }
|
|
543
|
+
catch (RegexMatchTimeoutException) { throw new BridgeCommandException("E_OBJECT_READ_PATTERN_INVALID", "namePattern exceeded the bounded regex evaluation time.", "Use a simpler anchored namePattern."); }
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
private static bool TypeAllowed(string typeName, Options options)
|
|
547
|
+
{
|
|
548
|
+
return string.IsNullOrEmpty(options.TypeFilter) || (!string.IsNullOrEmpty(typeName) && typeName.IndexOf(options.TypeFilter, StringComparison.OrdinalIgnoreCase) >= 0);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
private static bool IsSimpleType(Type type)
|
|
460
552
|
{
|
|
553
|
+
return type.IsPrimitive || type.IsEnum || type == typeof(decimal) || type == typeof(DateTime) || type == typeof(TimeSpan)
|
|
554
|
+
|| type == typeof(Guid) || type == typeof(Vector2) || type == typeof(Vector3) || type == typeof(Vector4)
|
|
555
|
+
|| type == typeof(Quaternion) || type == typeof(Color) || type == typeof(Rect) || type == typeof(Bounds) || type == typeof(LayerMask);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
private static SerializedMember ErrorMember(string path, string message)
|
|
559
|
+
{
|
|
560
|
+
return new SerializedMember { Path = path, TypeName = "error", Error = message, Truncated = true, NextPath = path };
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
public static Dictionary<string, object> UnityObjectRefJson(UnityEngine.Object value, UnityCliRequestBudget budget)
|
|
564
|
+
{
|
|
565
|
+
if (!budget.TryConsumeGetter()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
|
|
566
|
+
var name = value.name;
|
|
567
|
+
var typeName = value.GetType().FullName;
|
|
568
|
+
if (!budget.TryReserveSerializedUtf8(name) || !budget.TryReserveSerializedUtf8(typeName)) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
|
|
461
569
|
var result = new Dictionary<string, object>
|
|
462
570
|
{
|
|
463
|
-
{ "instanceId", (long)
|
|
464
|
-
{ "name", uo.name },
|
|
465
|
-
{ "typeName", uo.GetType().FullName }
|
|
571
|
+
{ "instanceId", (long)value.GetInstanceID() }, { "name", name }, { "typeName", typeName }
|
|
466
572
|
};
|
|
467
|
-
if (
|
|
573
|
+
if (value is GameObject gameObject)
|
|
468
574
|
{
|
|
469
575
|
result["kind"] = "GameObject";
|
|
470
|
-
result["path"] = GameObjectFullPath(
|
|
576
|
+
result["path"] = GameObjectFullPath(gameObject, budget);
|
|
471
577
|
}
|
|
472
578
|
else
|
|
473
579
|
{
|
|
474
|
-
result["kind"] =
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
{
|
|
478
|
-
result["assetPath"] = NormalizeAssetPath(assetPath);
|
|
479
|
-
result["guid"] = AssetDatabase.AssetPathToGUID(assetPath);
|
|
480
|
-
}
|
|
580
|
+
result["kind"] = value is Component ? "Component" : "Asset";
|
|
581
|
+
if (!budget.TryConsumeGetter()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
|
|
582
|
+
var assetPath = AssetDatabase.GetAssetPath(value);
|
|
583
|
+
if (!string.IsNullOrEmpty(assetPath)) { result["assetPath"] = NormalizeAssetPath(assetPath); result["guid"] = AssetDatabase.AssetPathToGUID(assetPath); }
|
|
481
584
|
}
|
|
482
585
|
return result;
|
|
483
586
|
}
|
|
484
587
|
|
|
485
|
-
private static string GameObjectFullPath(GameObject
|
|
588
|
+
private static string GameObjectFullPath(GameObject gameObject, UnityCliRequestBudget budget)
|
|
486
589
|
{
|
|
487
|
-
|
|
488
|
-
var
|
|
489
|
-
|
|
590
|
+
if (!budget.TryConsumeGetter()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
|
|
591
|
+
var path = gameObject.name;
|
|
592
|
+
var current = gameObject.transform.parent;
|
|
593
|
+
while (current != null)
|
|
490
594
|
{
|
|
491
|
-
|
|
492
|
-
|
|
595
|
+
if (!budget.TryConsumeNode() || !budget.TryConsumeGetter()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
|
|
596
|
+
path = current.name + "/" + path;
|
|
597
|
+
current = current.parent;
|
|
493
598
|
}
|
|
494
599
|
return path;
|
|
495
600
|
}
|
|
496
601
|
|
|
497
|
-
// 将内部 SerializedMember 模型投影为稳定的 AnyJson 值树。
|
|
498
602
|
private static Dictionary<string, object> MemberToData(SerializedMember member)
|
|
499
603
|
{
|
|
500
|
-
var data = new Dictionary<string, object>
|
|
501
|
-
{
|
|
502
|
-
{ "path", member.Path },
|
|
503
|
-
{ "typeName", member.TypeName }
|
|
504
|
-
};
|
|
604
|
+
var data = new Dictionary<string, object> { { "path", member.Path }, { "typeName", member.TypeName } };
|
|
505
605
|
if (member.Error != null) data["error"] = member.Error;
|
|
506
606
|
if (member.Truncated) data["truncated"] = true;
|
|
607
|
+
if (member.NextPath != null) data["nextPath"] = member.NextPath;
|
|
507
608
|
if (member.IsNull) data["value"] = null;
|
|
508
609
|
else if (member.Value != null) data["value"] = ValueData(member.Value);
|
|
509
610
|
if (member.IsUnityObject) data["unityObjectRef"] = member.UnityObjectRef;
|
|
510
611
|
if (member.IsArray)
|
|
511
612
|
{
|
|
512
613
|
data["isArray"] = true;
|
|
513
|
-
data["
|
|
514
|
-
data["totalCount"] = (long)member.ArrayLength;
|
|
614
|
+
if (member.HasKnownTotal) data["totalCount"] = (long)member.ArrayLength;
|
|
515
615
|
data["returnedCount"] = (long)member.ArrayItems.Count;
|
|
516
|
-
|
|
517
|
-
|
|
616
|
+
var items = new List<object>();
|
|
617
|
+
foreach (var item in member.ArrayItems) items.Add(MemberToData(item));
|
|
618
|
+
data["items"] = items;
|
|
518
619
|
}
|
|
519
620
|
if (member.IsDictionary)
|
|
520
621
|
{
|
|
521
622
|
data["isDictionary"] = true;
|
|
522
|
-
data["totalCount"] = (long)member.ArrayLength;
|
|
623
|
+
if (member.HasKnownTotal) data["totalCount"] = (long)member.ArrayLength;
|
|
523
624
|
data["returnedCount"] = (long)member.DictionaryEntries.Count;
|
|
524
|
-
|
|
525
|
-
{
|
|
526
|
-
|
|
527
|
-
}).ToList();
|
|
625
|
+
var entries = new List<object>();
|
|
626
|
+
foreach (var entry in member.DictionaryEntries) entries.Add(new Dictionary<string, object> { { "key", entry.Key }, { "value", MemberToData(entry.Value) } });
|
|
627
|
+
data["entries"] = entries;
|
|
528
628
|
}
|
|
529
629
|
if (member.Fields.Count > 0)
|
|
530
630
|
{
|
|
531
|
-
|
|
532
|
-
{
|
|
533
|
-
|
|
534
|
-
}).ToList();
|
|
631
|
+
var fields = new List<object>();
|
|
632
|
+
foreach (var field in member.Fields) fields.Add(new Dictionary<string, object> { { "name", field.Key }, { "value", MemberToData(field.Value) } });
|
|
633
|
+
data["fields"] = fields;
|
|
535
634
|
}
|
|
536
635
|
if (member.Props.Count > 0)
|
|
537
636
|
{
|
|
538
|
-
|
|
637
|
+
var properties = new List<object>();
|
|
638
|
+
foreach (var property in member.Props)
|
|
539
639
|
{
|
|
540
|
-
var item = new Dictionary<string, object>
|
|
541
|
-
{
|
|
542
|
-
{ "name", property.Key }, { "value", MemberToData(property.Value) }
|
|
543
|
-
};
|
|
640
|
+
var item = new Dictionary<string, object> { { "name", property.Key }, { "value", MemberToData(property.Value) } };
|
|
544
641
|
if (property.Value.ReadOnly) item["readOnly"] = true;
|
|
545
642
|
if (property.Value.Writable) item["writable"] = true;
|
|
546
|
-
|
|
547
|
-
}
|
|
643
|
+
properties.Add(item);
|
|
644
|
+
}
|
|
645
|
+
data["props"] = properties;
|
|
548
646
|
}
|
|
549
647
|
return data;
|
|
550
648
|
}
|
|
@@ -555,20 +653,14 @@ namespace UnityCli
|
|
|
555
653
|
if (value is Vector2 v2) return new Dictionary<string, object> { { "x", JsonNumberData(v2.x) }, { "y", JsonNumberData(v2.y) } };
|
|
556
654
|
if (value is Vector3 v3) return new Dictionary<string, object> { { "x", JsonNumberData(v3.x) }, { "y", JsonNumberData(v3.y) }, { "z", JsonNumberData(v3.z) } };
|
|
557
655
|
if (value is Vector4 v4) return new Dictionary<string, object> { { "x", JsonNumberData(v4.x) }, { "y", JsonNumberData(v4.y) }, { "z", JsonNumberData(v4.z) }, { "w", JsonNumberData(v4.w) } };
|
|
558
|
-
if (value is Quaternion
|
|
559
|
-
if (value is Color
|
|
656
|
+
if (value is Quaternion quaternion) return new Dictionary<string, object> { { "x", JsonNumberData(quaternion.x) }, { "y", JsonNumberData(quaternion.y) }, { "z", JsonNumberData(quaternion.z) }, { "w", JsonNumberData(quaternion.w) } };
|
|
657
|
+
if (value is Color color) return new Dictionary<string, object> { { "r", JsonNumberData(color.r) }, { "g", JsonNumberData(color.g) }, { "b", JsonNumberData(color.b) }, { "a", JsonNumberData(color.a) } };
|
|
560
658
|
if (value is float single) return JsonNumberData(single);
|
|
561
659
|
if (value is double number) return JsonNumberData(number);
|
|
562
|
-
if (value is string || value is bool || value is byte || value is sbyte || value is short || value is ushort
|
|
563
|
-
|| value is int || value is uint || value is long || value is ulong || value is decimal)
|
|
564
|
-
{
|
|
565
|
-
return value;
|
|
566
|
-
}
|
|
567
|
-
// AnyJson 无法表达的 CLR 值必须显式标记,禁止 ToString() 制造可解析假象。
|
|
660
|
+
if (value is string || value is bool || value is byte || value is sbyte || value is short || value is ushort || value is int || value is uint || value is long || value is ulong || value is decimal) return value;
|
|
568
661
|
return UnsupportedValue(value.GetType(), "unsupported-dynamic-value");
|
|
569
662
|
}
|
|
570
663
|
|
|
571
|
-
/// <summary>AnyJson 中以稳定字符串表达 CLR 非有限数;有限值仍保持 JSON number。</summary>
|
|
572
664
|
private static object JsonNumberData(double value)
|
|
573
665
|
{
|
|
574
666
|
if (double.IsNaN(value)) return "NaN";
|
|
@@ -579,15 +671,34 @@ namespace UnityCli
|
|
|
579
671
|
|
|
580
672
|
private static Dictionary<string, object> UnsupportedValue(Type type, string reason)
|
|
581
673
|
{
|
|
582
|
-
return new Dictionary<string, object>
|
|
674
|
+
return new Dictionary<string, object> { { "unsupported", true }, { "reason", reason }, { "typeName", type == null ? null : type.FullName } };
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
private sealed class ReferenceComparer : IEqualityComparer<object>
|
|
678
|
+
{
|
|
679
|
+
internal static readonly ReferenceComparer Instance = new ReferenceComparer();
|
|
680
|
+
public new bool Equals(object left, object right) { return ReferenceEquals(left, right); }
|
|
681
|
+
public int GetHashCode(object value) { return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(value); }
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
private sealed class MemberCandidate
|
|
685
|
+
{
|
|
686
|
+
internal MemberCandidate(MemberInfo member, bool isProperty, bool canWrite, Type valueType)
|
|
583
687
|
{
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
688
|
+
Member = member;
|
|
689
|
+
IsProperty = isProperty;
|
|
690
|
+
CanWrite = canWrite;
|
|
691
|
+
Name = member.Name;
|
|
692
|
+
ValueTypeName = valueType == null ? null : valueType.FullName;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
internal MemberInfo Member { get; private set; }
|
|
696
|
+
internal bool IsProperty { get; private set; }
|
|
697
|
+
internal bool CanWrite { get; private set; }
|
|
698
|
+
internal string Name { get; private set; }
|
|
699
|
+
internal string ValueTypeName { get; private set; }
|
|
588
700
|
}
|
|
589
701
|
|
|
590
|
-
/// <summary>SerializedMember 内存模型。</summary>
|
|
591
702
|
private sealed class SerializedMember
|
|
592
703
|
{
|
|
593
704
|
public string Path { get; set; }
|
|
@@ -598,8 +709,10 @@ namespace UnityCli
|
|
|
598
709
|
[UnityCliAnyJson] public object UnityObjectRef { get; set; }
|
|
599
710
|
public bool IsArray { get; set; }
|
|
600
711
|
public bool IsDictionary { get; set; }
|
|
712
|
+
public bool HasKnownTotal { get; set; }
|
|
601
713
|
public int ArrayLength { get; set; }
|
|
602
714
|
public bool Truncated { get; set; }
|
|
715
|
+
public string NextPath { get; set; }
|
|
603
716
|
public string Error { get; set; }
|
|
604
717
|
public bool ReadOnly { get; set; }
|
|
605
718
|
public bool Writable { get; set; }
|
|
@@ -607,12 +720,7 @@ namespace UnityCli
|
|
|
607
720
|
public List<KV> Props { get; } = new List<KV>();
|
|
608
721
|
public List<SerializedMember> ArrayItems { get; } = new List<SerializedMember>();
|
|
609
722
|
public List<KV> DictionaryEntries { get; } = new List<KV>();
|
|
610
|
-
|
|
611
|
-
public sealed class KV
|
|
612
|
-
{
|
|
613
|
-
public string Key { get; set; }
|
|
614
|
-
public SerializedMember Value { get; set; }
|
|
615
|
-
}
|
|
723
|
+
public sealed class KV { public string Key { get; set; } public SerializedMember Value { get; set; } }
|
|
616
724
|
}
|
|
617
725
|
}
|
|
618
726
|
}
|