@alpsckr/unitycli 0.4.3 → 0.4.4

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.
Files changed (420) hide show
  1. package/README.md +5 -5
  2. package/contracts/command-contract.json +109 -217
  3. package/contracts/package-files.json +374 -294
  4. package/contracts/stable-session/direct-call-semantics.json +8 -2
  5. package/contracts/tool-completion-contract.json +148 -672
  6. package/dist/bridge/admission.d.ts +17 -1
  7. package/dist/bridge/admission.js +2 -0
  8. package/dist/bridge/admission.js.map +1 -1
  9. package/dist/bridge/domains/asset.js +3 -1
  10. package/dist/bridge/domains/asset.js.map +1 -1
  11. package/dist/bridge/domains/camera.js +39 -7
  12. package/dist/bridge/domains/camera.js.map +1 -1
  13. package/dist/bridge/domains/console.js +2 -1
  14. package/dist/bridge/domains/console.js.map +1 -1
  15. package/dist/bridge/domains/doctor.d.ts +3 -0
  16. package/dist/bridge/domains/doctor.js +8 -0
  17. package/dist/bridge/domains/doctor.js.map +1 -0
  18. package/dist/bridge/domains/editor.js +0 -4
  19. package/dist/bridge/domains/editor.js.map +1 -1
  20. package/dist/bridge/domains/game-object.js +4 -12
  21. package/dist/bridge/domains/game-object.js.map +1 -1
  22. package/dist/bridge/domains/graphics.js +1 -16
  23. package/dist/bridge/domains/graphics.js.map +1 -1
  24. package/dist/bridge/domains/object.js +3 -0
  25. package/dist/bridge/domains/object.js.map +1 -1
  26. package/dist/bridge/domains/physics.js +1 -2
  27. package/dist/bridge/domains/physics.js.map +1 -1
  28. package/dist/bridge/domains/profiler.js +18 -7
  29. package/dist/bridge/domains/profiler.js.map +1 -1
  30. package/dist/bridge/domains/project-tool.js +4 -0
  31. package/dist/bridge/domains/project-tool.js.map +1 -1
  32. package/dist/bridge/domains/reflection.js +10 -2
  33. package/dist/bridge/domains/reflection.js.map +1 -1
  34. package/dist/bridge/domains/runtime-tool.js +3 -0
  35. package/dist/bridge/domains/runtime-tool.js.map +1 -1
  36. package/dist/bridge/domains/scene.js +3 -8
  37. package/dist/bridge/domains/scene.js.map +1 -1
  38. package/dist/bridge/domains/screenshot.js +30 -6
  39. package/dist/bridge/domains/screenshot.js.map +1 -1
  40. package/dist/bridge/domains/ui.js +3 -1
  41. package/dist/bridge/domains/ui.js.map +1 -1
  42. package/dist/bridge/index.d.ts +5 -5
  43. package/dist/bridge/index.js +6 -9
  44. package/dist/bridge/index.js.map +1 -1
  45. package/dist/bridge/registry.js +3 -5
  46. package/dist/bridge/registry.js.map +1 -1
  47. package/dist/bridge/session-supervisor.d.ts +6 -3
  48. package/dist/bridge/session-supervisor.js +122 -34
  49. package/dist/bridge/session-supervisor.js.map +1 -1
  50. package/dist/bridge/types.d.ts +6 -5
  51. package/dist/bridge/types.js +16 -2
  52. package/dist/bridge/types.js.map +1 -1
  53. package/dist/catalog-protocol.d.ts +64 -0
  54. package/dist/catalog-protocol.js +460 -0
  55. package/dist/catalog-protocol.js.map +1 -0
  56. package/dist/cli-commands/command.js +84 -61
  57. package/dist/cli-commands/command.js.map +1 -1
  58. package/dist/cli-commands/extensions.js +70 -50
  59. package/dist/cli-commands/extensions.js.map +1 -1
  60. package/dist/cli-commands/instances.js +1 -1
  61. package/dist/cli-commands/instances.js.map +1 -1
  62. package/dist/cli-process-owners.d.ts +6 -0
  63. package/dist/cli-process-owners.js +27 -0
  64. package/dist/cli-process-owners.js.map +1 -0
  65. package/dist/cli.d.ts +1 -1
  66. package/dist/cli.js +20 -5
  67. package/dist/cli.js.map +1 -1
  68. package/dist/contracts/command-contract.js +14 -4
  69. package/dist/contracts/command-contract.js.map +1 -1
  70. package/dist/doctor.d.ts +4 -6
  71. package/dist/doctor.js +429 -398
  72. package/dist/doctor.js.map +1 -1
  73. package/dist/editor-blocker-observer.d.ts +2 -1
  74. package/dist/editor-blocker-observer.js +40 -13
  75. package/dist/editor-blocker-observer.js.map +1 -1
  76. package/dist/extension-availability.d.ts +27 -9
  77. package/dist/extension-availability.js +96 -56
  78. package/dist/extension-availability.js.map +1 -1
  79. package/dist/extensions/descriptor-loader.d.ts +2 -2
  80. package/dist/extensions/descriptor-loader.js +3 -4
  81. package/dist/extensions/descriptor-loader.js.map +1 -1
  82. package/dist/extensions/index.d.ts +1 -1
  83. package/dist/extensions/index.js +1 -1
  84. package/dist/extensions/index.js.map +1 -1
  85. package/dist/extensions/installer.d.ts +24 -7
  86. package/dist/extensions/installer.js +142 -66
  87. package/dist/extensions/installer.js.map +1 -1
  88. package/dist/extensions/registry.js +3 -5
  89. package/dist/extensions/registry.js.map +1 -1
  90. package/dist/extensions/state-contract.d.ts +11 -0
  91. package/dist/extensions/state-contract.js +50 -0
  92. package/dist/extensions/state-contract.js.map +1 -0
  93. package/dist/instances.d.ts +13 -1
  94. package/dist/instances.js +26 -0
  95. package/dist/instances.js.map +1 -1
  96. package/dist/package-manager.d.ts +0 -24
  97. package/dist/package-manager.js +5 -58
  98. package/dist/package-manager.js.map +1 -1
  99. package/dist/project-tool-discovery.d.ts +5 -2
  100. package/dist/project-tool-discovery.js +22 -18
  101. package/dist/project-tool-discovery.js.map +1 -1
  102. package/dist/runtime-code.js +1 -1
  103. package/dist/runtime-code.js.map +1 -1
  104. package/dist/tool-name.d.ts +4 -0
  105. package/dist/tool-name.js +15 -0
  106. package/dist/tool-name.js.map +1 -0
  107. package/dist/tools/.generated-completion-registry.d.ts +2 -0
  108. package/dist/tools/.generated-completion-registry.js +763 -325
  109. package/dist/tools/.generated-completion-registry.js.map +1 -1
  110. package/dist/tools/.generated-registry.d.ts +6 -11
  111. package/dist/tools/.generated-registry.js +6 -11
  112. package/dist/tools/.generated-registry.js.map +1 -1
  113. package/dist/tools/.generated-schemas.js +11 -16
  114. package/dist/tools/.generated-schemas.js.map +1 -1
  115. package/dist/tools/asset/refresh-status.d.ts +3 -0
  116. package/dist/tools/asset/refresh-status.js +12 -0
  117. package/dist/tools/asset/refresh-status.js.map +1 -0
  118. package/dist/tools/asset/refresh.js +7 -3
  119. package/dist/tools/asset/refresh.js.map +1 -1
  120. package/dist/tools/build/player-status.d.ts +3 -0
  121. package/dist/tools/build/player-status.js +12 -0
  122. package/dist/tools/build/player-status.js.map +1 -0
  123. package/dist/tools/build/run.js +1 -1
  124. package/dist/tools/build/run.js.map +1 -1
  125. package/dist/tools/catalog-control.d.ts +22 -0
  126. package/dist/tools/catalog-control.js +335 -0
  127. package/dist/tools/catalog-control.js.map +1 -0
  128. package/dist/tools/catalog.d.ts +7 -9
  129. package/dist/tools/catalog.js +66 -45
  130. package/dist/tools/catalog.js.map +1 -1
  131. package/dist/tools/commands.d.ts +1 -1
  132. package/dist/tools/commands.js +44 -11
  133. package/dist/tools/commands.js.map +1 -1
  134. package/dist/tools/define.js +3 -2
  135. package/dist/tools/define.js.map +1 -1
  136. package/dist/tools/dispatcher.d.ts +53 -6
  137. package/dist/tools/dispatcher.js +277 -108
  138. package/dist/tools/dispatcher.js.map +1 -1
  139. package/dist/tools/editor/compile.js +1 -1
  140. package/dist/tools/editor/compile.js.map +1 -1
  141. package/dist/tools/editor/play-mode-status.d.ts +3 -0
  142. package/dist/tools/editor/play-mode-status.js +12 -0
  143. package/dist/tools/editor/play-mode-status.js.map +1 -0
  144. package/dist/tools/editor/readiness.d.ts +0 -1
  145. package/dist/tools/editor/readiness.js +0 -1
  146. package/dist/tools/editor/readiness.js.map +1 -1
  147. package/dist/tools/editor/refresh.d.ts +7 -3
  148. package/dist/tools/editor/refresh.js +20 -15
  149. package/dist/tools/editor/refresh.js.map +1 -1
  150. package/dist/tools/editor/state.d.ts +2 -2
  151. package/dist/tools/editor/state.js +13 -12
  152. package/dist/tools/editor/state.js.map +1 -1
  153. package/dist/tools/metadata/derived.js +1 -7
  154. package/dist/tools/metadata/derived.js.map +1 -1
  155. package/dist/tools/metadata/status.js +1 -12
  156. package/dist/tools/metadata/status.js.map +1 -1
  157. package/dist/tools/metadata/types.d.ts +0 -2
  158. package/dist/tools/navmesh/live.js +1 -1
  159. package/dist/tools/navmesh/live.js.map +1 -1
  160. package/dist/tools/occlusion/live.js +1 -1
  161. package/dist/tools/occlusion/live.js.map +1 -1
  162. package/dist/tools/package/deploy.js +2 -2
  163. package/dist/tools/package/deploy.js.map +1 -1
  164. package/dist/tools/package/embed.js +1 -1
  165. package/dist/tools/package/embed.js.map +1 -1
  166. package/dist/tools/package/install-status.d.ts +3 -0
  167. package/dist/tools/package/install-status.js +12 -0
  168. package/dist/tools/package/install-status.js.map +1 -0
  169. package/dist/tools/package/install.js +3 -3
  170. package/dist/tools/package/install.js.map +1 -1
  171. package/dist/tools/package/live-resolve.js +45 -12
  172. package/dist/tools/package/live-resolve.js.map +1 -1
  173. package/dist/tools/package/registry-clear.js.map +1 -1
  174. package/dist/tools/package/registry-set.js.map +1 -1
  175. package/dist/tools/package/remove-status.d.ts +3 -0
  176. package/dist/tools/package/remove-status.js +12 -0
  177. package/dist/tools/package/remove-status.js.map +1 -0
  178. package/dist/tools/package/remove.js +3 -3
  179. package/dist/tools/package/remove.js.map +1 -1
  180. package/dist/tools/package/resolve.js +3 -3
  181. package/dist/tools/package/resolve.js.map +1 -1
  182. package/dist/tools/package/restore.js +2 -2
  183. package/dist/tools/package/restore.js.map +1 -1
  184. package/dist/tools/package/scoped-registry-add.js.map +1 -1
  185. package/dist/tools/status/support.d.ts +13 -0
  186. package/dist/tools/status/support.js +28 -0
  187. package/dist/tools/status/support.js.map +1 -0
  188. package/dist/tools/test/cancel.js +2 -2
  189. package/dist/tools/test/cancel.js.map +1 -1
  190. package/dist/tools/test/run-status.d.ts +3 -0
  191. package/dist/tools/test/run-status.js +12 -0
  192. package/dist/tools/test/run-status.js.map +1 -0
  193. package/dist/tools/test/run.js +7 -4
  194. package/dist/tools/test/run.js.map +1 -1
  195. package/dist/tools/tools/group-summaries.js +10 -14
  196. package/dist/tools/tools/group-summaries.js.map +1 -1
  197. package/docs/architecture.md +4 -4
  198. package/docs/availability-differences.md +4 -4
  199. package/docs/command-execution.md +4 -3
  200. package/docs/error-codes.md +13 -5
  201. package/docs/extensions.md +1 -7
  202. package/docs/risk-and-side-effects.md +4 -3
  203. package/docs/safety-boundaries.md +3 -1
  204. package/docs/target-and-instance.md +1 -1
  205. package/docs/tool-discovery.md +17 -10
  206. package/docs/tool-execution.md +18 -7
  207. package/extensions/cinemachine/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  208. package/extensions/hdrp/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  209. package/extensions/probuilder/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  210. package/extensions/urp/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  211. package/extensions/vfx-graph/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  212. package/package.json +2 -4
  213. package/skills/unitycli/SKILL.md +11 -2
  214. package/skills/unitycli/manifest.json +1 -1
  215. package/skills/unitycli/references/operation-protocol.md +3 -3
  216. package/unitypkg/Editor/AssemblyInfo.cs +5 -0
  217. package/unitypkg/Editor/Bridge/BridgeCommandRegistry.cs +49 -14
  218. package/unitypkg/Editor/Bridge/BridgeHttpServer.cs +9 -35
  219. package/unitypkg/Editor/Bridge/BridgeQueueState.cs +672 -0
  220. package/unitypkg/Editor/Bridge/BridgeWorkQueue.cs +970 -120
  221. package/unitypkg/Editor/BridgeStartupStateMachine.cs +59 -1
  222. package/unitypkg/Editor/Commands/ProjectToolCommands.cs +4 -0
  223. package/unitypkg/Editor/Commands/RuntimeToolCommands.cs +3 -0
  224. package/unitypkg/Editor/Generated/UnityCliToolCompletionRegistry.g.cs +379 -14
  225. package/unitypkg/Editor/Infrastructure/CodeExecSupport.cs +28 -13
  226. package/unitypkg/Editor/Infrastructure/RegistryOwnershipResolver.cs +132 -105
  227. package/unitypkg/Editor/Infrastructure/UnityCliActiveOperation.cs +138 -29
  228. package/unitypkg/Editor/Infrastructure/UnityCliAdmission.cs +1856 -237
  229. package/unitypkg/Editor/Infrastructure/UnityCliDoctorDiagnostics.cs +220 -0
  230. package/unitypkg/Editor/Infrastructure/UnityCliDurableStatusCoordinator.cs +118 -0
  231. package/unitypkg/Editor/Infrastructure/UnityCliEditorStateSnapshot.cs +2 -1
  232. package/unitypkg/Editor/Infrastructure/UnityCliHierarchyPager.cs +203 -0
  233. package/unitypkg/Editor/Infrastructure/UnityCliInstanceCleanup.cs +2 -1
  234. package/unitypkg/Editor/Infrastructure/UnityCliInstanceRegistry.cs +201 -63
  235. package/unitypkg/Editor/Infrastructure/UnityCliJson.cs +2 -3
  236. package/unitypkg/Editor/Infrastructure/UnityCliLiveBuildCoordinator.cs +289 -46
  237. package/unitypkg/Editor/Infrastructure/UnityCliLiveRefreshCoordinator.cs +87 -53
  238. package/unitypkg/Editor/Infrastructure/UnityCliNavMeshBakeCoordinator.cs +4 -4
  239. package/unitypkg/Editor/Infrastructure/UnityCliObjectRef.cs +128 -25
  240. package/unitypkg/Editor/Infrastructure/UnityCliObjectSerializer.cs +435 -327
  241. package/unitypkg/Editor/Infrastructure/UnityCliOcclusionBakeCoordinator.cs +4 -4
  242. package/unitypkg/Editor/Infrastructure/UnityCliOperationCoordinator.cs +351 -53
  243. package/unitypkg/Editor/Infrastructure/UnityCliPackageResolveCoordinator.cs +118 -18
  244. package/unitypkg/Editor/Infrastructure/UnityCliPackageResolveState.cs +20 -1
  245. package/unitypkg/Editor/Infrastructure/UnityCliReadGeneration.cs +97 -0
  246. package/unitypkg/Editor/Infrastructure/UnityCliReflection.cs +644 -133
  247. package/unitypkg/Editor/Infrastructure/UnityCliReloadOperation.cs +128 -41
  248. package/unitypkg/Editor/Infrastructure/UnityCliRuntimeExecutor.cs +14 -84
  249. package/unitypkg/Editor/Infrastructure/UnityCliSerializer.cs +220 -0
  250. package/unitypkg/Editor/Infrastructure/UnityCliSessionMutationLease.cs +69 -29
  251. package/unitypkg/Editor/Infrastructure/UnityCliTestRunCoordinator.cs +440 -54
  252. package/unitypkg/Editor/Infrastructure/UnityCliWaiter.cs +128 -35
  253. package/unitypkg/Editor/ProjectTools/IUnityCliTool.cs +9 -1
  254. package/unitypkg/Editor/ProjectTools/OfficialAttributeToolRegistry.cs +32 -31
  255. package/unitypkg/Editor/ProjectTools/ProjectToolRegistry.cs +607 -458
  256. package/unitypkg/Editor/ProjectTools/ProjectToolSchemaValidator.cs +35 -7
  257. package/unitypkg/Editor/ProjectTools/RegisteredTool.cs +501 -60
  258. package/unitypkg/Editor/ProjectTools/UnityCliProjectToolContext.cs +21 -0
  259. package/unitypkg/Editor/ProjectTools/UnityCliSchemaDeriver.cs +36 -7
  260. package/unitypkg/Editor/ProjectTools/UnityCliToolName.cs +30 -0
  261. package/unitypkg/Editor/Properties/AssemblyInfo.cs +3 -0
  262. package/unitypkg/Editor/Tools/asset/get-data.cs +16 -7
  263. package/unitypkg/Editor/Tools/build/BuildSceneStateTool.cs +23 -3
  264. package/unitypkg/Editor/Tools/build/BuildToolDtos.cs +2 -1
  265. package/unitypkg/Editor/Tools/build/scene/add.cs +6 -3
  266. package/unitypkg/Editor/Tools/build/scene/disable.cs +1 -1
  267. package/unitypkg/Editor/Tools/build/scene/enable.cs +1 -1
  268. package/unitypkg/Editor/Tools/build/scene/list.cs +1 -1
  269. package/unitypkg/Editor/Tools/build/scene/remove.cs +13 -3
  270. package/unitypkg/Editor/Tools/camera/CameraToolSupport.cs +38 -38
  271. package/unitypkg/Editor/Tools/camera/multiview.cs +13 -6
  272. package/unitypkg/Editor/Tools/camera/screenshot.cs +14 -7
  273. package/unitypkg/Editor/Tools/component/get.cs +16 -9
  274. package/unitypkg/Editor/Tools/component/modify.cs +6 -4
  275. package/unitypkg/Editor/Tools/console/clear.cs +7 -6
  276. package/unitypkg/Editor/Tools/console/get.cs +48 -16
  277. package/unitypkg/Editor/Tools/editor/EditorToolSupport.cs +143 -28
  278. package/unitypkg/Editor/Tools/editor/menu-items.cs +1 -1
  279. package/unitypkg/Editor/Tools/editor/play.cs +2 -11
  280. package/unitypkg/Editor/Tools/editor/selection.cs +1 -1
  281. package/unitypkg/Editor/Tools/editor/stop.cs +2 -11
  282. package/unitypkg/Editor/Tools/editor/windows.cs +1 -1
  283. package/unitypkg/Editor/Tools/go/GameObjectToolSupport.cs +54 -4
  284. package/unitypkg/Editor/Tools/go/delete.cs +1 -1
  285. package/unitypkg/Editor/Tools/go/find.cs +167 -70
  286. package/unitypkg/Editor/Tools/graphics/GraphicsToolSupport.cs +25 -0
  287. package/unitypkg/Editor/Tools/graphics/light-probe-group.cs +9 -3
  288. package/unitypkg/Editor/Tools/graphics/pipeline-info.cs +1 -1
  289. package/unitypkg/Editor/Tools/graphics/pipeline-settings.cs +1 -1
  290. package/unitypkg/Editor/Tools/graphics/quality.cs +2 -2
  291. package/unitypkg/Editor/Tools/graphics/reflection-probe.cs +3 -3
  292. package/unitypkg/Editor/Tools/graphics/stats.cs +1 -1
  293. package/unitypkg/Editor/Tools/object/ObjectToolSupport.cs +4 -4
  294. package/unitypkg/Editor/Tools/object/get-data.cs +9 -7
  295. package/unitypkg/Editor/Tools/object/modify.cs +7 -5
  296. package/unitypkg/Editor/Tools/physics/raycast.cs +24 -6
  297. package/unitypkg/Editor/Tools/prefab/stage.cs +1 -1
  298. package/unitypkg/Editor/Tools/profiler/ProfilerFrameDebuggerSupport.cs +94 -21
  299. package/unitypkg/Editor/Tools/profiler/ProfilerMemorySnapshotSupport.cs +397 -10
  300. package/unitypkg/Editor/Tools/profiler/ProfilerToolSupport.cs +18 -4
  301. package/unitypkg/Editor/Tools/profiler/frame-debugger/enable.cs +1 -1
  302. package/unitypkg/Editor/Tools/profiler/frame-debugger/events.cs +88 -87
  303. package/unitypkg/Editor/Tools/profiler/hierarchy.cs +278 -112
  304. package/unitypkg/Editor/Tools/profiler/memory-snapshot/compare.cs +9 -5
  305. package/unitypkg/Editor/Tools/profiler/memory-snapshot/list.cs +37 -35
  306. package/unitypkg/Editor/Tools/profiler/memory-snapshot/take.cs +99 -97
  307. package/unitypkg/Editor/Tools/project/info.cs +1 -1
  308. package/unitypkg/Editor/Tools/project/layers.cs +1 -1
  309. package/unitypkg/Editor/Tools/project/tags.cs +1 -1
  310. package/unitypkg/Editor/Tools/reflection/method/call.cs +9 -7
  311. package/unitypkg/Editor/Tools/reflection/method/find.cs +27 -37
  312. package/unitypkg/Editor/Tools/reflection/type/schema.cs +24 -28
  313. package/unitypkg/Editor/Tools/scene/gameobject/component.cs +8 -5
  314. package/unitypkg/Editor/Tools/scene/gameobject/components.cs +10 -7
  315. package/unitypkg/Editor/Tools/scene/gameobject-support.cs +104 -8
  316. package/unitypkg/Editor/Tools/scene/gameobject.cs +37 -16
  317. package/unitypkg/Editor/Tools/scene/hierarchy.cs +146 -142
  318. package/unitypkg/Editor/Tools/screenshot/EditorElementCaptureToolSupport.cs +77 -25
  319. package/unitypkg/Editor/Tools/screenshot/ScreenshotToolSupport.cs +315 -95
  320. package/unitypkg/Editor/Tools/screenshot/editor-element.cs +7 -5
  321. package/unitypkg/Editor/Tools/screenshot/game-view.cs +35 -21
  322. package/unitypkg/Editor/Tools/screenshot/isolated-object.cs +29 -23
  323. package/unitypkg/Editor/Tools/screenshot/orbit.cs +37 -25
  324. package/unitypkg/Editor/Tools/screenshot/scene-view.cs +25 -18
  325. package/unitypkg/Editor/Tools/screenshot/screen.cs +177 -17
  326. package/unitypkg/Editor/Tools/screenshot/surround.cs +83 -79
  327. package/unitypkg/Editor/Tools/ui/render-preview.cs +16 -11
  328. package/unitypkg/Editor/UnityCliBridge.cs +97 -34
  329. package/unitypkg/Runtime/Infrastructure/UnityCliRuntimeBridge.cs +753 -122
  330. package/unitypkg/Runtime/Properties/AssemblyInfo.cs +5 -0
  331. package/unitypkg/Runtime/UnityCli.Runtime.asmdef +1 -1
  332. package/unitypkg/Shared/AssemblyInfo.cs +4 -0
  333. package/unitypkg/Shared/UnityCliAdmissionPool.cs +265 -75
  334. package/unitypkg/Shared/UnityCliBridgeQueuePolicy.cs +410 -0
  335. package/unitypkg/Shared/UnityCliBridgeWorkItemState.cs +47 -0
  336. package/unitypkg/Shared/UnityCliExceptionProjection.cs +151 -0
  337. package/unitypkg/Shared/UnityCliQueuePublication.cs +30 -0
  338. package/unitypkg/Shared/UnityCliRequestBudget.cs +352 -0
  339. package/unitypkg/Shared/UnityCliRuntimeRequestPublication.cs +107 -0
  340. package/unitypkg/Tests/Editor/UnityCli.Editor.Tests.asmdef +2 -2
  341. package/unitypkg/Tests/Editor/UnityCliAdmissionCoordinatorIntegrationTests.cs +1329 -0
  342. package/unitypkg/Tests/Editor/UnityCliAdmissionPoolTests.cs +311 -114
  343. package/unitypkg/Tests/Editor/UnityCliAdmissionRepairStateTests.cs +94 -0
  344. package/unitypkg/Tests/Editor/UnityCliBridgeAdmissionIntegrationTests.cs +1229 -0
  345. package/unitypkg/Tests/Editor/UnityCliBridgeQueuePolicyTests.cs +412 -0
  346. package/unitypkg/Tests/Editor/UnityCliContinuationIntegrationTests.cs +292 -0
  347. package/unitypkg/Tests/Editor/UnityCliDiagnosticBoundaryTests.cs +120 -0
  348. package/unitypkg/Tests/Editor/UnityCliDurableStatusTests.cs +84 -0
  349. package/unitypkg/Tests/Editor/UnityCliExceptionProjectionTests.cs +72 -0
  350. package/unitypkg/Tests/Editor/UnityCliInstancePublicationRetryTests.cs +180 -97
  351. package/unitypkg/Tests/Editor/UnityCliMemoryReflectionIntegrationTests.cs +434 -0
  352. package/unitypkg/Tests/Editor/UnityCliRequestBudgetTests.cs +615 -0
  353. package/unitypkg/Tests/Editor/UnityCliRuntimeBridgeIntegrationTests.cs +520 -0
  354. package/unitypkg/Tests/Editor/UnityCliToolCatalogTests.cs +562 -0
  355. package/unitypkg/Tests/Runtime/UnityCli.Runtime.Tests.asmdef +23 -0
  356. package/unitypkg/Tests/Runtime/UnityCliRuntimeCatalogValidationTests.cs +29 -0
  357. package/unitypkg/package.json +1 -1
  358. package/contracts/static-capability-catalog.json +0 -48226
  359. package/dist/bridge/domains/diagnose.d.ts +0 -2
  360. package/dist/bridge/domains/diagnose.js +0 -11
  361. package/dist/bridge/domains/diagnose.js.map +0 -1
  362. package/dist/bridge/domains/extension.d.ts +0 -8
  363. package/dist/bridge/domains/extension.js +0 -20
  364. package/dist/bridge/domains/extension.js.map +0 -1
  365. package/dist/capabilities.d.ts +0 -5
  366. package/dist/capabilities.js +0 -84
  367. package/dist/capabilities.js.map +0 -1
  368. package/dist/static-capability-catalog.d.ts +0 -13
  369. package/dist/static-capability-catalog.js +0 -41
  370. package/dist/static-capability-catalog.js.map +0 -1
  371. package/dist/tools/diagnose/capabilities.d.ts +0 -13
  372. package/dist/tools/diagnose/capabilities.js +0 -34
  373. package/dist/tools/diagnose/capabilities.js.map +0 -1
  374. package/dist/tools/package/info.d.ts +0 -19
  375. package/dist/tools/package/info.js +0 -27
  376. package/dist/tools/package/info.js.map +0 -1
  377. package/dist/tools/package/list.d.ts +0 -27
  378. package/dist/tools/package/list.js +0 -21
  379. package/dist/tools/package/list.js.map +0 -1
  380. package/dist/tools/package/registry-get.d.ts +0 -10
  381. package/dist/tools/package/registry-get.js +0 -22
  382. package/dist/tools/package/registry-get.js.map +0 -1
  383. package/dist/tools/package/scoped-registry-list.d.ts +0 -14
  384. package/dist/tools/package/scoped-registry-list.js +0 -22
  385. package/dist/tools/package/scoped-registry-list.js.map +0 -1
  386. package/dist/tools/package/search.d.ts +0 -17
  387. package/dist/tools/package/search.js +0 -39
  388. package/dist/tools/package/search.js.map +0 -1
  389. package/dist/tools/package/status.d.ts +0 -21
  390. package/dist/tools/package/status.js +0 -37
  391. package/dist/tools/package/status.js.map +0 -1
  392. package/dist/tools/tools/describe.d.ts +0 -27
  393. package/dist/tools/tools/describe.js +0 -35
  394. package/dist/tools/tools/describe.js.map +0 -1
  395. package/dist/tools/tools/group-list.d.ts +0 -16
  396. package/dist/tools/tools/group-list.js +0 -38
  397. package/dist/tools/tools/group-list.js.map +0 -1
  398. package/dist/tools/tools/group.d.ts +0 -13
  399. package/dist/tools/tools/group.js +0 -34
  400. package/dist/tools/tools/group.js.map +0 -1
  401. package/dist/tools/tools/pagination.d.ts +0 -17
  402. package/dist/tools/tools/pagination.js +0 -28
  403. package/dist/tools/tools/pagination.js.map +0 -1
  404. package/dist/tools/tools/search.d.ts +0 -15
  405. package/dist/tools/tools/search.js +0 -42
  406. package/dist/tools/tools/search.js.map +0 -1
  407. package/unitypkg/Editor/Tools/diagnose/preflight.cs +0 -269
  408. package/unitypkg/Editor/Tools/editor/console.cs +0 -26
  409. package/unitypkg/Editor/Tools/extension/status.cs +0 -87
  410. package/unitypkg/Editor/Tools/go/inspect.cs +0 -49
  411. package/unitypkg/Editor/Tools/graphics/pipeline-quality.cs +0 -24
  412. package/unitypkg/Editor/Tools/graphics/probe-positions.cs +0 -58
  413. package/unitypkg/Editor/Tools/graphics/settings.cs +0 -104
  414. package/unitypkg/Editor/Tools/graphics/stats-counters.cs +0 -28
  415. package/unitypkg/Editor/Tools/graphics/stats-memory.cs +0 -21
  416. package/unitypkg/Editor/Tools/instances/list.cs +0 -71
  417. package/unitypkg/Editor/Tools/physics/raycast-all.cs +0 -67
  418. package/unitypkg/Editor/Tools/scene/build-settings.cs +0 -69
  419. package/unitypkg/Editor/Tools/scene/data.cs +0 -125
  420. package/unitypkg/Editor/Tools/tests/list.cs +0 -98
@@ -1,182 +1,348 @@
1
1
  #if UNITY_EDITOR
2
2
  using System;
3
3
  using System.Collections.Generic;
4
+ using System.Globalization;
4
5
  using System.Reflection;
5
6
 
6
7
  namespace UnityCli
7
8
  {
9
+ /// <summary>profiler.hierarchy — 使用 RawFrameDataView 扁平先序索引做全局节点续页。</summary>
8
10
  public class ProfilerHierarchyTool : IUnityCliTool<ProfilerHierarchyTool.Request, ProfilerHierarchyTool.Result>
9
11
  {
10
12
  public sealed class Request
11
13
  {
12
- public int? Frame { get; set; }
13
- public int? Thread { get; set; }
14
- public int? Top { get; set; }
15
- public int? Depth { get; set; }
14
+ [UnityCliRange(-1L, int.MaxValue)] public int? Frame { get; set; }
15
+ [UnityCliRange(0L, 1024L)] public int? Thread { get; set; }
16
+ [UnityCliRange(1L, 500L)] public int? PageSize { get; set; }
17
+ [UnityCliRange(0L, 32L)] public int? Depth { get; set; }
18
+ public string Cursor { get; set; }
19
+ public UnityCliReadBudgetInput Budget { get; set; }
16
20
  }
17
21
 
18
22
  public sealed class Result
19
23
  {
20
- public List<HierarchyItem> RootItems { get; set; }
24
+ public long Frame { get; set; }
25
+ public long Thread { get; set; }
26
+ [UnityCliItemsRange(0, 500)] public List<HierarchyItem> Items { get; set; }
27
+ public PageInfo PageInfo { get; set; }
28
+ public UnityCliRequestBudgetUsage Budget { get; set; }
21
29
  }
22
30
 
23
31
  public sealed class HierarchyItem
24
32
  {
25
33
  public long Id { get; set; }
34
+ public long ParentId { get; set; }
26
35
  public string Name { get; set; }
27
36
  public long Depth { get; set; }
28
37
  public double Ms { get; set; }
29
- public long SiblingCount { get; set; }
30
38
  public long ChildCount { get; set; }
31
39
  public bool ChildrenTruncated { get; set; }
32
- public List<HierarchyItem> Children { get; set; }
40
+ }
41
+
42
+ public sealed class PageInfo
43
+ {
44
+ public long PageSize { get; set; }
45
+ public long ReturnedCount { get; set; }
46
+ public string Cursor { get; set; }
47
+ public string NextCursor { get; set; }
48
+ public bool Complete { get; set; }
49
+ public bool Truncated { get; set; }
50
+ public string Generation { get; set; }
33
51
  }
34
52
 
35
53
  public ToolDescriptor Describe() => new()
36
54
  {
37
55
  Name = "profiler.hierarchy",
38
- Summary = "Read the per-function CPU call hierarchy (marker tree) for one profiler frame/thread.",
39
- Description = "Read the CPU call hierarchy for a single profiler frame and thread via ProfilerDriver.GetHierarchyFrameDataView, recursing GetItemChildren to emit a structured marker tree. Defaults are bounded (top=20, depth=5); each node reports childCount and childrenTruncated so AI can see when detail was omitted.",
40
- Group = "profiler",DestructiveHint = false, Target = "editor", PlayModeSupport = "supported",
56
+ Summary = "Page one profiler frame/thread call hierarchy with a global raw-node budget and stable continuation.",
57
+ Description = "Read a flat preorder page from Unity RawFrameDataView without copying the full hierarchy. frame defaults to the latest recorded frame, thread is 0..1024, pageSize is 1..500 (default 50), and depth is 0..32 (default 5). The DFS stack is carried by opaque nextCursor, so later pages resume in O(depth). All nodes share request scan/node/reflection/time/UTF-8 budgets; defaults are 10000/4096/4096/250 ms/1 MiB and public maxima are 100000/20000/20000/2000 ms/8 MiB. complete=false identifies truncation. Read-only in Edit and Play Mode.",
58
+ Group = "profiler", DestructiveHint = false, Target = "editor", PlayModeSupport = "supported",
41
59
  DefaultTimeoutMs = 30000, MaxTimeoutMs = 600000,
42
- Examples = new[]
43
- {
44
- new ToolExample { Description = "Latest frame main-thread top markers", Input = new Request { Top = 20, Depth = 5 } },
45
- new ToolExample { Description = "Latest frame on the main thread", Input = new Request { Frame = -1, Thread = 0 } }
46
- }
60
+ Examples = new[] { new ToolExample { Description = "Read latest-frame nodes", Input = new Request { PageSize = 50, Depth = 5 } } }
47
61
  };
48
62
 
49
63
  public System.Threading.Tasks.ValueTask<Result> ExecuteAsync(Request request, UnityCliProjectToolContext ctx, System.Threading.CancellationToken ct = default)
50
64
  {
51
- var driverType = ProfilerToolSupport.FindType("UnityEditorInternal.ProfilerDriver");
52
- if (driverType == null)
53
- {
54
- throw new UnityCliBridge.BridgeCommandException("E_PROFILER_HIERARCHY_UNAVAILABLE",
55
- "UnityEditorInternal.ProfilerDriver is not available; hierarchy requires the Editor Profiler.",
56
- "Capture Profiler data in the selected Live Editor, then retry.");
57
- }
58
-
59
- // 省略 frame 时读取最新已采集帧
65
+ request = request ?? new Request();
66
+ var budget = ctx.UseReadBudget(request.Budget);
67
+ if (!request.Frame.HasValue && (!budget.TryConsumeReflectionCall() || !budget.TryConsumeGetter())) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
60
68
  var frame = request.Frame ?? ProfilerToolSupport.ProfilerDriverIntProperty("lastFrameIndex", -1);
61
69
  var threadIndex = request.Thread ?? 0;
62
- var top = Math.Max(1, Math.Min(request.Top ?? 20, 5000));
63
- var maxDepth = Math.Max(1, Math.Min(request.Depth ?? 5, 32));
64
-
65
- var hierarchyViewType = ProfilerToolSupport.FindType("UnityEditor.Profiling.HierarchyFrameDataView")
66
- ?? ProfilerToolSupport.FindType("UnityEditorInternal.HierarchyFrameDataView")
67
- ?? ProfilerToolSupport.FindType("UnityEngine.Profiling.HierarchyFrameDataView");
68
- if (hierarchyViewType == null)
70
+ var pageSize = request.PageSize ?? 50;
71
+ var maxDepth = request.Depth ?? 5;
72
+ var accessor = RawHierarchyAccessor.Open(frame, threadIndex, budget);
73
+ try
69
74
  {
70
- throw new UnityCliBridge.BridgeCommandException("E_PROFILER_HIERARCHY_UNAVAILABLE",
71
- "HierarchyFrameDataView type not available in this Unity version.",
72
- "Use an Editor version that exposes UnityEditor.Profiling.HierarchyFrameDataView, then record profiler frames before retrying.");
75
+ var generation = UnityCliContinuationToken.Fingerprint(UnityCliReadGeneration.Session("profiler.hierarchy") + "\0" + accessor.CaptureIdentity);
76
+ var stack = string.IsNullOrEmpty(request.Cursor)
77
+ ? new List<TraversalFrame> { new TraversalFrame { ParentId = 0, NextSample = 1, Depth = 0 } }
78
+ : ParseCursor(request.Cursor, generation, accessor, maxDepth, budget);
79
+ var items = new List<HierarchyItem>();
80
+ while (stack.Count > 0 && items.Count < pageSize)
81
+ {
82
+ var before = EncodeStack(stack);
83
+ if (!TryNext(accessor, stack, maxDepth, budget, out var item))
84
+ {
85
+ if (budget.IsExhausted) stack = DecodeStack(before);
86
+ break;
87
+ }
88
+ items.Add(item);
89
+ }
90
+ if (items.Count == 0 && stack.Count > 0) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension ?? "scannedRecords");
91
+ var nextCursor = stack.Count == 0 ? null : UnityCliContinuationToken.Create("profiler.hierarchy", generation, EncodeStack(stack));
92
+ return new System.Threading.Tasks.ValueTask<Result>(new Result
93
+ {
94
+ Frame = frame,
95
+ Thread = threadIndex,
96
+ Items = items,
97
+ PageInfo = new PageInfo
98
+ {
99
+ PageSize = pageSize, ReturnedCount = items.Count, Cursor = request.Cursor, NextCursor = nextCursor,
100
+ Complete = nextCursor == null, Truncated = nextCursor != null, Generation = generation
101
+ },
102
+ Budget = budget.Usage()
103
+ });
73
104
  }
105
+ finally { accessor.Dispose(); }
106
+ }
74
107
 
75
- var viewModesType = hierarchyViewType.GetTypeInfo().GetDeclaredNestedType("ViewModes")?.AsType();
76
- var getHierarchy = viewModesType == null
77
- ? null
78
- : driverType.GetMethod("GetHierarchyFrameDataView", new[] { typeof(int), typeof(int), viewModesType, typeof(int), typeof(bool) });
79
- // 不同 Unity 版本签名不同;退回 (int,int) 最简重载
80
- if (getHierarchy == null)
81
- {
82
- getHierarchy = driverType.GetMethod("GetHierarchyFrameDataView", new[] { typeof(int), typeof(int) });
83
- }
84
- if (getHierarchy == null)
108
+ /// <summary>扁平先序数组中,当前子树后的首个样本就是下一兄弟;栈只保留每层的下一个样本。</summary>
109
+ internal static bool TryNext(RawHierarchyAccessor accessor, List<TraversalFrame> stack, int maxDepth, UnityCliRequestBudget budget, out HierarchyItem item)
110
+ {
111
+ item = null;
112
+ while (stack.Count > 0)
85
113
  {
86
- throw new UnityCliBridge.BridgeCommandException("E_PROFILER_HIERARCHY_UNAVAILABLE",
87
- "ProfilerDriver.GetHierarchyFrameDataView overload not found.",
88
- "Unity version may not expose this API via reflection.");
114
+ var frame = stack[stack.Count - 1];
115
+ var parentEnd = accessor.SubtreeEndExclusive(frame.ParentId, budget);
116
+ if (budget.IsExhausted) return false;
117
+ if (frame.NextSample >= parentEnd) { stack.RemoveAt(stack.Count - 1); continue; }
118
+ if (!budget.TryConsumeScannedRecord() || !budget.TryConsumeNode()) return false;
119
+ var child = frame.NextSample;
120
+ var childEnd = accessor.SubtreeEndExclusive(child, budget);
121
+ if (budget.IsExhausted) return false;
122
+ var grandChildren = accessor.ChildCount(child, budget);
123
+ if (budget.IsExhausted) return false;
124
+ if ((grandChildren > 0) != (childEnd > child + 1)) throw RawHierarchyAccessor.InvalidPreorder();
125
+ var name = accessor.Name(child, budget) ?? "(unknown)";
126
+ var milliseconds = accessor.Milliseconds(child, budget);
127
+ if (budget.IsExhausted || !budget.TryReserveSerializedUtf8(name)) return false;
128
+ frame.NextSample = childEnd;
129
+ item = new HierarchyItem
130
+ {
131
+ Id = child, ParentId = frame.ParentId, Name = name, Depth = frame.Depth,
132
+ Ms = milliseconds, ChildCount = grandChildren,
133
+ ChildrenTruncated = grandChildren > 0 && frame.Depth >= maxDepth
134
+ };
135
+ if (grandChildren > 0 && frame.Depth < maxDepth)
136
+ stack.Add(new TraversalFrame { ParentId = child, NextSample = child + 1, Depth = frame.Depth + 1 });
137
+ return true;
89
138
  }
139
+ return false;
140
+ }
90
141
 
91
- object view = null;
142
+ private static List<TraversalFrame> ParseCursor(string cursor, string generation, RawHierarchyAccessor accessor, int maxDepth, UnityCliRequestBudget budget)
143
+ {
144
+ string position;
145
+ if (!UnityCliContinuationToken.TryParse(cursor, "profiler.hierarchy", generation, out position)) throw Stale();
92
146
  try
93
147
  {
94
- view = getHierarchy.GetParameters().Length == 2
95
- ? getHierarchy.Invoke(null, new object[] { frame, threadIndex })
96
- : getHierarchy.Invoke(null, new object[] { frame, threadIndex, 0, 0, false });
148
+ var stack = DecodeStack(position);
149
+ if (stack.Count == 0 || stack.Count > maxDepth + 1) throw Stale();
150
+ for (var index = 0; index < stack.Count; index++)
151
+ {
152
+ var frame = stack[index];
153
+ if (frame.Depth != index || frame.ParentId < 0 || frame.ParentId >= accessor.SampleCount) throw Stale();
154
+ var parentEnd = accessor.SubtreeEndExclusive(frame.ParentId, budget);
155
+ if (budget.IsExhausted) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
156
+ if (frame.NextSample < frame.ParentId + 1 || frame.NextSample > parentEnd) throw Stale();
157
+ if (index + 1 < stack.Count)
158
+ {
159
+ var activeChild = stack[index + 1].ParentId;
160
+ if (activeChild <= frame.ParentId || activeChild >= parentEnd) throw Stale();
161
+ var activeChildEnd = accessor.SubtreeEndExclusive(activeChild, budget);
162
+ if (budget.IsExhausted) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
163
+ if (activeChildEnd != frame.NextSample) throw Stale();
164
+ }
165
+ }
166
+ return stack;
97
167
  }
98
- catch (Exception exception)
168
+ catch (UnityCliRequestBudgetExceededException) { throw; }
169
+ catch (UnityCliBridge.BridgeCommandException) { throw; }
170
+ catch { throw Stale(); }
171
+ }
172
+
173
+ private static string EncodeStack(List<TraversalFrame> stack)
174
+ {
175
+ var values = new List<string>();
176
+ foreach (var frame in stack) values.Add(frame.ParentId.ToString(CultureInfo.InvariantCulture) + "," + frame.NextSample.ToString(CultureInfo.InvariantCulture) + "," + frame.Depth.ToString(CultureInfo.InvariantCulture));
177
+ return string.Join(";", values.ToArray());
178
+ }
179
+
180
+ private static List<TraversalFrame> DecodeStack(string value)
181
+ {
182
+ var result = new List<TraversalFrame>();
183
+ if (string.IsNullOrEmpty(value)) return result;
184
+ foreach (var token in value.Split(';'))
99
185
  {
100
- throw new UnityCliBridge.BridgeCommandException("E_PROFILER_HIERARCHY_UNAVAILABLE",
101
- "Failed to acquire hierarchy frame data: " + exception.Message,
102
- HierarchyDetails(frame, threadIndex),
103
- new[] { "Make sure the Profiler has recorded frames; pass profiler.start then profiler.stop, then query a recorded frame (or omit frame to use the latest)." });
186
+ var parts = token.Split(',');
187
+ if (parts.Length != 3) throw new FormatException();
188
+ result.Add(new TraversalFrame
189
+ {
190
+ ParentId = int.Parse(parts[0], CultureInfo.InvariantCulture),
191
+ NextSample = int.Parse(parts[1], CultureInfo.InvariantCulture),
192
+ Depth = int.Parse(parts[2], CultureInfo.InvariantCulture)
193
+ });
104
194
  }
105
- if (view == null)
195
+ if (result.Count > 33) throw new FormatException();
196
+ return result;
197
+ }
198
+
199
+ private static UnityCliBridge.BridgeCommandException Stale()
200
+ {
201
+ return new UnityCliBridge.BridgeCommandException("E_CONTINUATION_STALE", "The profiler hierarchy continuation does not match the same frame/thread generation.", "Start a new profiler.hierarchy read without cursor.");
202
+ }
203
+
204
+ internal sealed class TraversalFrame { internal int ParentId; internal int NextSample; internal int Depth; }
205
+
206
+ internal sealed class RawHierarchyAccessor : IDisposable
207
+ {
208
+ private readonly object view;
209
+ private readonly PropertyInfo validProperty;
210
+ private readonly int sampleCount;
211
+ private readonly MethodInfo childCountMethod;
212
+ private readonly MethodInfo recursiveChildCountMethod;
213
+ private readonly MethodInfo nameMethod;
214
+ private readonly MethodInfo millisecondsMethod;
215
+ private readonly string captureIdentity;
216
+
217
+ private RawHierarchyAccessor(object view, Type type, UnityCliRequestBudget budget)
106
218
  {
107
- throw new UnityCliBridge.BridgeCommandException("E_PROFILER_HIERARCHY_NO_DATA",
108
- "No hierarchy data available for the requested frame/thread.",
109
- "Record profiler frames first (profiler.start → play → profiler.stop), then query a valid frame.");
219
+ this.view = view;
220
+ validProperty = ReflectProperty(type, "valid", budget);
221
+ var sampleCountProperty = ReflectProperty(type, "sampleCount", budget);
222
+ var frameIndexProperty = ReflectProperty(type, "frameIndex", budget);
223
+ var threadIndexProperty = ReflectProperty(type, "threadIndex", budget);
224
+ var frameStartTimeProperty = ReflectProperty(type, "frameStartTimeMs", budget);
225
+ var frameTimeProperty = ReflectProperty(type, "frameTimeMs", budget);
226
+ childCountMethod = ReflectMethod(type, "GetSampleChildrenCount", new[] { typeof(int) }, budget);
227
+ recursiveChildCountMethod = ReflectMethod(type, "GetSampleChildrenCountRecursive", new[] { typeof(int) }, budget);
228
+ nameMethod = ReflectMethod(type, "GetSampleName", new[] { typeof(int) }, budget);
229
+ millisecondsMethod = ReflectMethod(type, "GetSampleTimeMs", new[] { typeof(int) }, budget);
230
+ if (sampleCountProperty == null || frameIndexProperty == null || threadIndexProperty == null || frameStartTimeProperty == null || frameTimeProperty == null
231
+ || childCountMethod == null || recursiveChildCountMethod == null || nameMethod == null || millisecondsMethod == null)
232
+ throw Unavailable("RawFrameDataView capture identity or preorder hierarchy members are unavailable.");
233
+ if (validProperty != null && !ReadBool(validProperty, view, budget)) throw NoData();
234
+ var identityValues = new object[5];
235
+ var identityProperties = new[] { frameIndexProperty, threadIndexProperty, frameStartTimeProperty, frameTimeProperty, sampleCountProperty };
236
+ for (var index = 0; index < identityProperties.Length; index++)
237
+ {
238
+ if (!budget.TryConsumeGetter() || !budget.TryConsumeReflectionCall()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
239
+ identityValues[index] = identityProperties[index].GetValue(view, null);
240
+ }
241
+ sampleCount = Convert.ToInt32(identityValues[4], CultureInfo.InvariantCulture);
242
+ if (sampleCount <= 0) throw NoData();
243
+ captureIdentity = string.Join("\0", Array.ConvertAll(identityValues, value => Convert.ToString(value, CultureInfo.InvariantCulture)));
244
+ if (!budget.TryReserveSerializedUtf8(captureIdentity)) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
110
245
  }
111
246
 
112
- var validProp = hierarchyViewType.GetProperty("valid");
113
- if (validProp != null && !(bool)validProp.GetValue(view, null))
247
+ internal int SampleCount { get { return sampleCount; } }
248
+ internal string CaptureIdentity { get { return captureIdentity; } }
249
+
250
+ internal static RawHierarchyAccessor Open(int frame, int thread, UnityCliRequestBudget budget)
114
251
  {
115
- throw new UnityCliBridge.BridgeCommandException("E_PROFILER_HIERARCHY_NO_DATA",
116
- "HierarchyFrameDataView is invalid for the requested frame/thread.",
117
- "Record profiler frames first, then query a valid frame.");
252
+ var driver = UnityCliBridge.TypeSchemaGenerator.ResolveType("UnityEditorInternal.ProfilerDriver", budget);
253
+ if (driver == null) throw Unavailable("ProfilerDriver is unavailable.");
254
+ if (!budget.TryConsumeReflectionCall()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
255
+ var method = driver.GetMethod("GetRawFrameDataView", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new[] { typeof(int), typeof(int) }, null);
256
+ if (method == null) throw Unavailable("ProfilerDriver.GetRawFrameDataView is unavailable.");
257
+ if (!budget.TryConsumeReflectionCall()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
258
+ object view;
259
+ try { view = method.Invoke(null, new object[] { frame, thread }); }
260
+ catch (Exception exception) { throw Unavailable("Failed to open RawFrameDataView: " + exception.Message); }
261
+ return InitializeOwnedView(view, value => new RawHierarchyAccessor(value, value.GetType(), budget));
118
262
  }
119
263
 
120
- return new System.Threading.Tasks.ValueTask<Result>(new Result
264
+ /// <summary>raw view 初始化成功才转移 ownership;成员绑定失败必须立即释放原 view。</summary>
265
+ internal static T InitializeOwnedView<T>(object view, Func<object, T> initialize)
121
266
  {
122
- RootItems = BuildHierarchyItems(view, hierarchyViewType, 0, top, maxDepth, 0)
123
- });
124
- }
125
-
126
- // 递归构建调用树节点:按 top 限制每层子项数量、depth 限制最大深度,避免单帧树过大
127
- private static List<HierarchyItem> BuildHierarchyItems(object view, Type viewType, int itemId, int top, int maxDepth, int depth)
128
- {
129
- var result = new List<HierarchyItem>();
130
- if (depth > maxDepth) return result;
267
+ if (view == null) throw NoData();
268
+ if (initialize == null) throw new ArgumentNullException("initialize");
269
+ try { return initialize(view); }
270
+ catch
271
+ {
272
+ (view as IDisposable)?.Dispose();
273
+ throw;
274
+ }
275
+ }
131
276
 
132
- var childrenMethod = viewType.GetMethod("GetItemChildren", new[] { typeof(int), typeof(List<int>) });
133
- var nameMethod = viewType.GetMethod("GetItemName", new[] { typeof(int) });
134
- var msMethod = viewType.GetMethod("GetItemThreadMS", new[] { typeof(int) });
277
+ internal static RawHierarchyAccessor InitializeForTests(object view, UnityCliRequestBudget budget)
278
+ {
279
+ return InitializeOwnedView(view, value => new RawHierarchyAccessor(value, value.GetType(), budget));
280
+ }
135
281
 
136
- var childIds = new List<int>();
137
- try { if (childrenMethod != null) childrenMethod.Invoke(view, new object[] { itemId, childIds }); }
138
- catch { return result; }
282
+ internal int ChildCount(int sample, UnityCliRequestBudget budget) { return Convert.ToInt32(Invoke(childCountMethod, new object[] { sample }, budget), CultureInfo.InvariantCulture); }
139
283
 
140
- var emitted = 0;
141
- foreach (var childId in childIds)
284
+ /// <summary>Unity raw samples 是先序扁平数组,递归后代数可直接确定子树右边界。</summary>
285
+ internal int SubtreeEndExclusive(int sample, UnityCliRequestBudget budget)
142
286
  {
143
- if (emitted >= top) break;
144
- string name = null;
145
- double ms = 0;
146
- try { if (nameMethod != null) name = nameMethod.Invoke(view, new object[] { childId }) as string; } catch { }
147
- try { if (msMethod != null) { var v = msMethod.Invoke(view, new object[] { childId }); if (v is double d) ms = d; else if (v is float f) ms = f; } } catch { }
287
+ var recursiveChildren = Convert.ToInt32(Invoke(recursiveChildCountMethod, new object[] { sample }, budget), CultureInfo.InvariantCulture);
288
+ if (budget.IsExhausted) return sample + 1;
289
+ var end = (long)sample + recursiveChildren + 1L;
290
+ if (sample < 0 || sample >= sampleCount || recursiveChildren < 0 || end > sampleCount) throw InvalidPreorder();
291
+ return (int)end;
292
+ }
293
+
294
+ internal string Name(int sample, UnityCliRequestBudget budget) { return Invoke(nameMethod, new object[] { sample }, budget) as string; }
295
+ internal double Milliseconds(int sample, UnityCliRequestBudget budget) { return Convert.ToDouble(Invoke(millisecondsMethod, new object[] { sample }, budget), CultureInfo.InvariantCulture); }
148
296
 
149
- var grandChildren = new List<int>();
150
- try { if (childrenMethod != null) childrenMethod.Invoke(view, new object[] { childId, grandChildren }); } catch { }
297
+ public void Dispose() { (view as IDisposable)?.Dispose(); }
151
298
 
152
- var item = new HierarchyItem
299
+ private object Invoke(MethodInfo method, object[] args, UnityCliRequestBudget budget)
300
+ {
301
+ if (!budget.TryConsumeReflectionCall()) return null;
302
+ try { return method.Invoke(view, args); }
303
+ catch (TargetInvocationException exception)
153
304
  {
154
- Id = childId,
155
- Name = name ?? "(unknown)",
156
- Depth = depth,
157
- Ms = ms,
158
- SiblingCount = childIds.Count,
159
- ChildCount = grandChildren.Count,
160
- ChildrenTruncated = grandChildren.Count > top || (grandChildren.Count > 0 && depth + 1 > maxDepth)
161
- };
162
- if (grandChildren.Count > 0 && depth + 1 <= maxDepth)
305
+ var inner = exception.InnerException ?? exception;
306
+ throw new UnityCliBridge.BridgeCommandException("E_PROFILER_HIERARCHY_READ_FAILED", "Raw profiler hierarchy read failed: " + inner.GetType().Name + ".", "Record a fresh frame and restart pagination.");
307
+ }
308
+ catch (Exception exception)
163
309
  {
164
- item.Children = BuildHierarchyItems(view, viewType, childId, top, maxDepth, depth + 1);
310
+ throw new UnityCliBridge.BridgeCommandException("E_PROFILER_HIERARCHY_READ_FAILED", "Raw profiler hierarchy read failed: " + exception.GetType().Name + ".", "Record a fresh frame and restart pagination.");
165
311
  }
166
- result.Add(item);
167
- emitted++;
168
312
  }
169
- return result;
170
- }
171
313
 
172
- private static ProfilerHierarchyDetails HierarchyDetails(int frame, int threadIndex)
173
- {
174
- return new ProfilerHierarchyDetails
314
+ private static PropertyInfo ReflectProperty(Type type, string name, UnityCliRequestBudget budget)
315
+ {
316
+ if (!budget.TryConsumeReflectionCall()) return null;
317
+ return type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
318
+ }
319
+
320
+ private static MethodInfo ReflectMethod(Type type, string name, Type[] parameters, UnityCliRequestBudget budget)
321
+ {
322
+ if (!budget.TryConsumeReflectionCall()) return null;
323
+ return type.GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, parameters, null);
324
+ }
325
+
326
+ private static bool ReadBool(PropertyInfo property, object target, UnityCliRequestBudget budget)
327
+ {
328
+ if (!budget.TryConsumeGetter() || !budget.TryConsumeReflectionCall()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
329
+ return Convert.ToBoolean(property.GetValue(target, null), CultureInfo.InvariantCulture);
330
+ }
331
+
332
+ private static UnityCliBridge.BridgeCommandException Unavailable(string message)
333
+ {
334
+ return new UnityCliBridge.BridgeCommandException("E_PROFILER_HIERARCHY_UNAVAILABLE", message, "Use an Editor exposing RawFrameDataView and record profiler frames before retrying.");
335
+ }
336
+
337
+ internal static UnityCliBridge.BridgeCommandException InvalidPreorder()
175
338
  {
176
- Frame = frame,
177
- Thread = threadIndex,
178
- ProfilerRecording = ProfilerToolSupport.GetProfilerSessionEnabled()
179
- };
339
+ return new UnityCliBridge.BridgeCommandException("E_PROFILER_HIERARCHY_READ_FAILED", "Raw profiler hierarchy preorder ranges are inconsistent.", "Record a fresh frame and restart pagination.");
340
+ }
341
+
342
+ private static UnityCliBridge.BridgeCommandException NoData()
343
+ {
344
+ return new UnityCliBridge.BridgeCommandException("E_PROFILER_HIERARCHY_NO_DATA", "No raw hierarchy data is available for the requested frame/thread.", "Record profiler frames, then query a valid frame and thread.");
345
+ }
180
346
  }
181
347
  }
182
348
  }
@@ -16,6 +16,7 @@ namespace UnityCli
16
16
  {
17
17
  [UnityCliRequired] public string SnapshotA { get; set; }
18
18
  [UnityCliRequired] public string SnapshotB { get; set; }
19
+ public UnityCliReadBudgetInput Budget { get; set; }
19
20
  }
20
21
 
21
22
  public sealed class Result
@@ -30,7 +31,7 @@ namespace UnityCli
30
31
  {
31
32
  Name = "profiler.memory-snapshot.compare",
32
33
  Summary = "Compare two .snap memory snapshots at file-level metadata (size delta, time delta).",
33
- Description = "Compare two .snap memory snapshots at file-level metadata (size delta, time delta). For object-level diff, open both in the Memory Profiler window.",
34
+ Description = "Compare file metadata for two snapshot file names under Library/UnityCli/memory-snapshots. Arbitrary paths are rejected. Reads share the request node/getter/time/UTF-8 budget. For object-level diff, open both governed artifacts in the Memory Profiler window. Read-only in Edit and Play Mode.",
34
35
  Group = "profiler",DestructiveHint = false, Target = "editor", PlayModeSupport = "supported",
35
36
  DefaultTimeoutMs = 30000, MaxTimeoutMs = 600000,
36
37
  Examples = new[] { new ToolExample { Description = "Compare two snapshots", Input = new Request { SnapshotA = "snap1.snap", SnapshotB = "snap2.snap" } } }
@@ -38,14 +39,14 @@ namespace UnityCli
38
39
 
39
40
  public System.Threading.Tasks.ValueTask<Result> ExecuteAsync(Request request, UnityCliProjectToolContext ctx, System.Threading.CancellationToken ct = default)
40
41
  {
41
- var snapshotAPath = request.SnapshotA;
42
- var snapshotBPath = request.SnapshotB;
43
- if (string.IsNullOrEmpty(snapshotAPath) || string.IsNullOrEmpty(snapshotBPath))
42
+ if (string.IsNullOrEmpty(request.SnapshotA) || string.IsNullOrEmpty(request.SnapshotB))
44
43
  {
45
44
  throw new UnityCliBridge.BridgeCommandException("E_PROFILER_SNAPSHOT_PATH_REQUIRED",
46
45
  "profiler.memory-snapshot.compare requires --snapshot-a and --snapshot-b.",
47
- "Pass two .snap file paths.");
46
+ "Pass two .snap file names.");
48
47
  }
48
+ var snapshotAPath = ProfilerMemorySnapshotSupport.ResolveFinalPath(request.SnapshotA, ctx.RequestId);
49
+ var snapshotBPath = ProfilerMemorySnapshotSupport.ResolveFinalPath(request.SnapshotB, ctx.RequestId);
49
50
  if (!File.Exists(snapshotAPath))
50
51
  {
51
52
  throw new UnityCliBridge.BridgeCommandException("E_PROFILER_SNAPSHOT_NOT_FOUND",
@@ -59,6 +60,9 @@ namespace UnityCli
59
60
  "Check the path or run profiler.memory-snapshot.list.");
60
61
  }
61
62
 
63
+ var budget = ctx.UseReadBudget(request.Budget);
64
+ if (!budget.TryConsumeNode() || !budget.TryConsumeNode() || !budget.TryConsumeGetter() || !budget.TryConsumeGetter())
65
+ throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
62
66
  var fiA = new FileInfo(snapshotAPath);
63
67
  var fiB = new FileInfo(snapshotBPath);
64
68
  return new System.Threading.Tasks.ValueTask<Result>(new Result
@@ -2,70 +2,72 @@
2
2
  using System;
3
3
  using System.Collections.Generic;
4
4
  using System.Globalization;
5
- using System.IO;
6
- using System.Reflection;
7
- using UnityEditor;
8
- using UnityEngine;
9
- using UnityEngine.Profiling;
10
5
 
11
6
  namespace UnityCli
12
7
  {
8
+ /// <summary>profiler.memory-snapshot.list — governed latest-100 catalog 的稳定续页。</summary>
13
9
  public class ProfilerMemorySnapshotListTool : IUnityCliTool<ProfilerMemorySnapshotListTool.Request, ProfilerMemorySnapshotListTool.Result>
14
10
  {
15
11
  public sealed class Request
16
12
  {
17
- public string Path { get; set; }
13
+ [UnityCliRange(1L, 100L)] public int? PageSize { get; set; }
14
+ public string Cursor { get; set; }
15
+ public UnityCliReadBudgetInput Budget { get; set; }
18
16
  }
19
17
 
20
18
  public sealed class Result
21
19
  {
22
20
  public List<MemorySnapshotEntry> Snapshots { get; set; }
23
- public List<string> SearchedDirs { get; set; }
21
+ public string Root { get; set; }
22
+ public long Total { get; set; }
23
+ public string NextCursor { get; set; }
24
+ public bool Truncated { get; set; }
25
+ public string Generation { get; set; }
26
+ public UnityCliRequestBudgetUsage Budget { get; set; }
24
27
  }
25
28
 
26
29
  public ToolDescriptor Describe() => new()
27
30
  {
28
31
  Name = "profiler.memory-snapshot.list",
29
- Summary = "List .snap memory snapshot files in the default capture directories or a custom path.",
30
- Description = "List .snap memory snapshot files in the default capture directories or a custom path. This reads file metadata only and does not open or modify snapshots.",
31
- Group = "profiler",DestructiveHint = false, Target = "editor", PlayModeSupport = "supported",
32
+ Summary = "Page the governed latest-100 memory snapshot catalog with stable generation continuation.",
33
+ Description = "List only .snap artifacts under Library/UnityCli/memory-snapshots. pageSize is 1..100 (default 25); nextCursor binds the exact bounded catalog generation and stale cursors return E_CONTINUATION_STALE. total is exact because the product catalog is capped at 100 snapshots and 8 GiB. Metadata reads and UTF-8 output use the shared request budget. Read-only in Edit and Play Mode and never scans arbitrary directories.",
34
+ Group = "profiler", DestructiveHint = false, Target = "editor", PlayModeSupport = "supported",
32
35
  DefaultTimeoutMs = 30000, MaxTimeoutMs = 600000,
33
- Examples = new[] { new ToolExample { Description = "List snapshots in default dirs", Input = new Request { } } }
36
+ Examples = new[] { new ToolExample { Description = "List the first snapshot page", Input = new Request { PageSize = 25 } } }
34
37
  };
35
38
 
36
39
  public System.Threading.Tasks.ValueTask<Result> ExecuteAsync(Request request, UnityCliProjectToolContext ctx, System.Threading.CancellationToken ct = default)
37
40
  {
38
- var searchPath = request.Path;
39
- var dirs = new List<string>();
40
- if (!string.IsNullOrEmpty(searchPath))
41
+ request = request ?? new Request();
42
+ var budget = ctx.UseReadBudget(request.Budget);
43
+ var files = ProfilerMemorySnapshotSupport.Catalog(budget);
44
+ var generation = ProfilerMemorySnapshotSupport.CatalogGeneration(files, budget);
45
+ var offset = 0;
46
+ if (!string.IsNullOrEmpty(request.Cursor))
41
47
  {
42
- dirs.Add(searchPath);
48
+ string position;
49
+ if (!UnityCliContinuationToken.TryParse(request.Cursor, "profiler.memory-snapshot.list", generation, out position)
50
+ || !int.TryParse(position, NumberStyles.Integer, CultureInfo.InvariantCulture, out offset) || offset < 0 || offset > files.Count)
51
+ throw new UnityCliBridge.BridgeCommandException("E_CONTINUATION_STALE", "The memory snapshot continuation does not match the current catalog generation.", "Start a new snapshot list without cursor.");
43
52
  }
44
- else
45
- {
46
- dirs.Add(Path.Combine(Application.temporaryCachePath, "MemoryCaptures"));
47
- dirs.Add(Path.Combine(Application.dataPath, "..", "MemoryCaptures"));
48
- }
49
-
53
+ var pageSize = request.PageSize ?? 25;
50
54
  var snapshots = new List<MemorySnapshotEntry>();
51
- foreach (var dir in dirs)
55
+ var index = offset;
56
+ while (index < files.Count && snapshots.Count < pageSize)
52
57
  {
53
- if (!Directory.Exists(dir)) continue;
54
- foreach (var file in Directory.GetFiles(dir, "*.snap"))
55
- {
56
- var fi = new FileInfo(file);
57
- snapshots.Add(new MemorySnapshotEntry
58
- {
59
- Path = fi.FullName,
60
- SizeBytes = fi.Length,
61
- Created = fi.CreationTimeUtc.ToString("O", CultureInfo.InvariantCulture)
62
- });
63
- }
58
+ if (!budget.TryConsumeScannedRecord() || !budget.TryConsumeNode() || !budget.TryConsumeGetter() || !budget.TryConsumeGetter()) break;
59
+ var item = MemorySnapshotEntry.FromFileInfo(files[index]);
60
+ if (!budget.TryReserveSerializedUtf8(item.Path) || !budget.TryReserveSerializedUtf8(item.Created)) break;
61
+ snapshots.Add(item);
62
+ index++;
64
63
  }
64
+ if (index == offset && index < files.Count)
65
+ throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension ?? "scannedRecords");
66
+ var next = index < files.Count ? UnityCliContinuationToken.Create("profiler.memory-snapshot.list", generation, index.ToString(CultureInfo.InvariantCulture)) : null;
65
67
  return new System.Threading.Tasks.ValueTask<Result>(new Result
66
68
  {
67
- Snapshots = snapshots,
68
- SearchedDirs = dirs
69
+ Snapshots = snapshots, Root = ProfilerMemorySnapshotSupport.Root, Total = files.Count,
70
+ NextCursor = next, Truncated = next != null, Generation = generation, Budget = budget.Usage()
69
71
  });
70
72
  }
71
73
  }