@atelierai/uco 1.0.8 → 1.0.9
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/CHANGELOG.md +18 -8
- package/package.json +2 -2
- package/vendor/plugin/com.atelierai.unity.copilot/CHANGELOG.md +4 -4
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/ToolGroups.SetEnabled.cs +92 -92
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/CopilotServerManager.cs +0 -147
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.CopilotServer.cs +3 -3
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/ToolGroupRegistry.cs +461 -460
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uss/CopilotPromptsWindow.uss +11 -11
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uss/CopilotResourcesWindow.uss +16 -16
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uss/CopilotToolsWindow.uss +36 -36
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uss/MainWindow.uss +183 -183
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uss/common/{_mcp-list-window.uss → _copilot-list-window.uss} +18 -18
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uss/common/_list-view.uss +77 -77
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uxml/CopilotPromptsWindow.uxml +18 -18
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uxml/CopilotResourcesWindow.uxml +18 -18
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uxml/CopilotToolsWindow.uxml +18 -18
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uxml/MainWindow.uxml +115 -115
- package/vendor/plugin/com.atelierai.unity.copilot/Plugins/ReflectorNet.dll +0 -0
- package/vendor/plugin/com.atelierai.unity.copilot/Plugins/Uco.Framework.Common.dll +0 -0
- package/vendor/plugin/com.atelierai.unity.copilot/Plugins/Uco.Framework.dll +0 -0
- package/vendor/plugin/com.atelierai.unity.copilot/README.md +88 -88
- package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.cs +1 -1
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/DependencyResolver/NuGetExtractorDevelopmentDependencyTests.cs +148 -148
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/DependencyResolver/NuGetExtractorFlatLayoutTests.cs +166 -166
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/DependencyResolver/NuGetInstallManifestTests.cs +436 -436
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/DependencyResolver/NuGetLegacyMigrationTests.cs +1 -1
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/DependencyResolver/NuGetPackageInstallerStaleVersionCleanupTests.cs +200 -200
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Environment/EnvironmentUtilsTests.cs +1 -1
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/GameObject/TestJsonSchema.cs +199 -199
- package/vendor/plugin/com.atelierai.unity.copilot/package.json +1 -1
- /package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uss/common/{_mcp-list-window.uss.meta → _copilot-list-window.uss.meta} +0 -0
|
@@ -1,460 +1,461 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Design inspired by MCP for Unity (CoplayDev/unity-mcp), Copyright (c) Coplay Inc., MIT License.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/*
|
|
6
|
-
┌──────────────────────────────────────────────────────────────────┐
|
|
7
|
-
│ Author: Ivan Murzak (https://github.com/IvanMurzak) │
|
|
8
|
-
│ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │
|
|
9
|
-
│ Copyright (c) 2025 Ivan Murzak │
|
|
10
|
-
│ Licensed under the Apache License, Version 2.0. │
|
|
11
|
-
│ See the LICENSE file in the project root for more information. │
|
|
12
|
-
└──────────────────────────────────────────────────────────────────┘
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
#nullable enable
|
|
16
|
-
using System;
|
|
17
|
-
using System.Collections.Generic;
|
|
18
|
-
using System.Linq;
|
|
19
|
-
using System.Reflection;
|
|
20
|
-
using System.Text;
|
|
21
|
-
using com.AtelierAI.Unity.Copilot.Runtime.Attributes;
|
|
22
|
-
using com.AtelierAI.
|
|
23
|
-
using
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
///
|
|
29
|
-
///
|
|
30
|
-
///
|
|
31
|
-
///
|
|
32
|
-
///
|
|
33
|
-
/// <
|
|
34
|
-
///
|
|
35
|
-
/// <c>
|
|
36
|
-
///
|
|
37
|
-
///
|
|
38
|
-
///
|
|
39
|
-
///
|
|
40
|
-
///
|
|
41
|
-
///
|
|
42
|
-
///
|
|
43
|
-
///
|
|
44
|
-
///
|
|
45
|
-
/// <
|
|
46
|
-
///
|
|
47
|
-
///
|
|
48
|
-
///
|
|
49
|
-
/// <
|
|
50
|
-
///
|
|
51
|
-
///
|
|
52
|
-
///
|
|
53
|
-
///
|
|
54
|
-
///
|
|
55
|
-
/// </
|
|
56
|
-
/// </
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
["
|
|
81
|
-
["
|
|
82
|
-
["
|
|
83
|
-
["
|
|
84
|
-
["
|
|
85
|
-
["
|
|
86
|
-
["
|
|
87
|
-
["
|
|
88
|
-
["
|
|
89
|
-
["
|
|
90
|
-
["
|
|
91
|
-
["
|
|
92
|
-
["
|
|
93
|
-
["
|
|
94
|
-
["
|
|
95
|
-
["
|
|
96
|
-
["
|
|
97
|
-
["
|
|
98
|
-
["
|
|
99
|
-
["
|
|
100
|
-
["
|
|
101
|
-
["
|
|
102
|
-
["
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
["
|
|
112
|
-
["
|
|
113
|
-
["
|
|
114
|
-
["
|
|
115
|
-
["
|
|
116
|
-
["
|
|
117
|
-
["
|
|
118
|
-
["
|
|
119
|
-
["
|
|
120
|
-
["
|
|
121
|
-
["
|
|
122
|
-
["
|
|
123
|
-
["
|
|
124
|
-
["
|
|
125
|
-
["
|
|
126
|
-
["
|
|
127
|
-
["
|
|
128
|
-
["
|
|
129
|
-
["
|
|
130
|
-
["
|
|
131
|
-
["
|
|
132
|
-
["
|
|
133
|
-
["
|
|
134
|
-
["
|
|
135
|
-
["
|
|
136
|
-
["
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
///
|
|
141
|
-
///
|
|
142
|
-
///
|
|
143
|
-
///
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
///
|
|
194
|
-
///
|
|
195
|
-
///
|
|
196
|
-
///
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
///
|
|
215
|
-
///
|
|
216
|
-
///
|
|
217
|
-
///
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
///
|
|
226
|
-
///
|
|
227
|
-
///
|
|
228
|
-
///
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
///
|
|
262
|
-
///
|
|
263
|
-
///
|
|
264
|
-
///
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
var
|
|
294
|
-
var
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
$"'{
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
var
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
///
|
|
363
|
-
///
|
|
364
|
-
///
|
|
365
|
-
///
|
|
366
|
-
///
|
|
367
|
-
///
|
|
368
|
-
///
|
|
369
|
-
///
|
|
370
|
-
///
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
var
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
var
|
|
434
|
-
|
|
435
|
-
if (
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
var
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
}
|
|
1
|
+
/*
|
|
2
|
+
* Design inspired by MCP for Unity (CoplayDev/unity-mcp), Copyright (c) Coplay Inc., MIT License.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
┌──────────────────────────────────────────────────────────────────┐
|
|
7
|
+
│ Author: Ivan Murzak (https://github.com/IvanMurzak) │
|
|
8
|
+
│ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │
|
|
9
|
+
│ Copyright (c) 2025 Ivan Murzak │
|
|
10
|
+
│ Licensed under the Apache License, Version 2.0. │
|
|
11
|
+
│ See the LICENSE file in the project root for more information. │
|
|
12
|
+
└──────────────────────────────────────────────────────────────────┘
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
#nullable enable
|
|
16
|
+
using System;
|
|
17
|
+
using System.Collections.Generic;
|
|
18
|
+
using System.Linq;
|
|
19
|
+
using System.Reflection;
|
|
20
|
+
using System.Text;
|
|
21
|
+
using com.AtelierAI.Unity.Copilot.Runtime.Attributes;
|
|
22
|
+
using com.AtelierAI.Unity.Copilot.Editor.Branding;
|
|
23
|
+
using com.AtelierAI.Uco.Framework;
|
|
24
|
+
using UnityEditor;
|
|
25
|
+
|
|
26
|
+
namespace com.AtelierAI.Unity.Copilot.Editor.Utils
|
|
27
|
+
{
|
|
28
|
+
/// <summary>
|
|
29
|
+
/// Editor-side static registry that indexes <c>Tool_*</c> partial-class roots
|
|
30
|
+
/// decorated with <see cref="ToolGroupAttribute"/> and tracks the
|
|
31
|
+
/// enabled state for each discovered group.
|
|
32
|
+
/// </summary>
|
|
33
|
+
/// <remarks>
|
|
34
|
+
/// <para>
|
|
35
|
+
/// Initialized on <c>[InitializeOnLoad]</c> domain reload. The scan walks
|
|
36
|
+
/// <c>TypeCache.GetTypesWithAttribute<ToolGroupAttribute>()</c>,
|
|
37
|
+
/// and for each marked class enumerates public/non-public instance and static
|
|
38
|
+
/// methods, reflecting any attribute whose simple type-name is
|
|
39
|
+
/// <c>UcoToolAttribute</c> (the external NuGet attribute we are
|
|
40
|
+
/// deliberately *not* taking a hard compile-time dependency on, so the
|
|
41
|
+
/// upstream package can evolve without breaking this file).
|
|
42
|
+
/// </para>
|
|
43
|
+
/// <para>
|
|
44
|
+
/// Enabled state is persisted to <see cref="EditorPrefs"/> as a compact
|
|
45
|
+
/// pipe-separated <c>group=0|group=1</c> string under
|
|
46
|
+
/// <see cref="EditorPrefsKey"/>. We avoid JSON here to keep this file
|
|
47
|
+
/// dependency-free; the schema is tiny and fully under our control.
|
|
48
|
+
/// </para>
|
|
49
|
+
/// <para>
|
|
50
|
+
/// <b>Enforcement note.</b> This class is purely a registry +
|
|
51
|
+
/// persistence layer. Currently <c>IsToolEnabled</c> is informational —
|
|
52
|
+
/// the upstream <c>IRunTool.RunCallTool</c> pipeline is *not* hooked,
|
|
53
|
+
/// so soft-disabled tools remain callable. Enforcement would require
|
|
54
|
+
/// either a request-pipeline interceptor or hooking the
|
|
55
|
+
/// <c>ToolDisabled</c> branch on the upstream tool-manager.
|
|
56
|
+
/// </para>
|
|
57
|
+
/// </remarks>
|
|
58
|
+
[InitializeOnLoad]
|
|
59
|
+
public static class ToolGroupRegistry
|
|
60
|
+
{
|
|
61
|
+
// ---------------------------------------------------------------------
|
|
62
|
+
// Indices (rebuilt every domain reload via the static ctor).
|
|
63
|
+
// ---------------------------------------------------------------------
|
|
64
|
+
static readonly Dictionary<string, List<string>> s_groupToTools =
|
|
65
|
+
new(StringComparer.Ordinal);
|
|
66
|
+
static readonly Dictionary<string, string> s_toolToGroup =
|
|
67
|
+
new(StringComparer.Ordinal);
|
|
68
|
+
static readonly Dictionary<string, bool> s_enabled =
|
|
69
|
+
new(StringComparer.Ordinal);
|
|
70
|
+
static readonly Dictionary<string, bool> s_defaultEnabled =
|
|
71
|
+
new(StringComparer.Ordinal);
|
|
72
|
+
static readonly Dictionary<string, string?> s_descriptions =
|
|
73
|
+
new(StringComparer.Ordinal);
|
|
74
|
+
static readonly HashSet<string> s_knownGroups =
|
|
75
|
+
new(StringComparer.Ordinal);
|
|
76
|
+
|
|
77
|
+
static readonly Dictionary<string, string[]> s_aliasesByCanonical =
|
|
78
|
+
new(StringComparer.Ordinal)
|
|
79
|
+
{
|
|
80
|
+
["assets"] = new[] { "asset" },
|
|
81
|
+
["automation"] = new[] { "batch" },
|
|
82
|
+
["build"] = new[] { "builds" },
|
|
83
|
+
["camera"] = new[] { "cameras", "cinemachine" },
|
|
84
|
+
["diagnostics"] = new[] { "console", "logs" },
|
|
85
|
+
["docs"] = new[] { "documentation" },
|
|
86
|
+
["editor"] = new[] { "application" },
|
|
87
|
+
["gameobject"] = new[] { "game-object", "gameobjects" },
|
|
88
|
+
["graphics"] = new[] { "rendering", "frame-debugger" },
|
|
89
|
+
["instances"] = new[] { "instance" },
|
|
90
|
+
["objects"] = new[] { "object" },
|
|
91
|
+
["operations"] = new[] { "jobs", "editor-operations" },
|
|
92
|
+
["packages"] = new[] { "package" },
|
|
93
|
+
["physics"] = Array.Empty<string>(),
|
|
94
|
+
["profiler"] = new[] { "profiling" },
|
|
95
|
+
["reflection"] = new[] { "reflect", "type", "types", "schema" },
|
|
96
|
+
["scene"] = new[] { "scenes" },
|
|
97
|
+
["screenshot"] = new[] { "screenshots", "capture" },
|
|
98
|
+
["scripting"] = new[] { "script", "scripts" },
|
|
99
|
+
["tests"] = new[] { "test", "testing" },
|
|
100
|
+
["texture"] = new[] { "textures" },
|
|
101
|
+
["tools"] = new[] { "tool", "core" },
|
|
102
|
+
["ui"] = new[] { "user-interface" },
|
|
103
|
+
["vfx"] = new[] { "visual-effects" }
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
static readonly Dictionary<string, string> s_aliasToCanonical = BuildAliasIndex();
|
|
107
|
+
|
|
108
|
+
static readonly Dictionary<string, string> s_prefixToCanonical =
|
|
109
|
+
new(StringComparer.OrdinalIgnoreCase)
|
|
110
|
+
{
|
|
111
|
+
["assets"] = "assets",
|
|
112
|
+
["batch"] = "automation",
|
|
113
|
+
["build"] = "build",
|
|
114
|
+
["camera"] = "camera",
|
|
115
|
+
["console"] = "diagnostics",
|
|
116
|
+
["docs"] = "docs",
|
|
117
|
+
["editor"] = "editor",
|
|
118
|
+
["frame"] = "graphics",
|
|
119
|
+
["gameobject"] = "gameobject",
|
|
120
|
+
["graphics"] = "graphics",
|
|
121
|
+
["instance"] = "instances",
|
|
122
|
+
["object"] = "objects",
|
|
123
|
+
["package"] = "packages",
|
|
124
|
+
["physics"] = "physics",
|
|
125
|
+
["profiler"] = "profiler",
|
|
126
|
+
["reflection"] = "reflection",
|
|
127
|
+
["scene"] = "scene",
|
|
128
|
+
["screenshot"] = "screenshot",
|
|
129
|
+
["script"] = "scripting",
|
|
130
|
+
["tests"] = "tests",
|
|
131
|
+
["texture"] = "texture",
|
|
132
|
+
["tool"] = "tools",
|
|
133
|
+
["tools"] = "tools",
|
|
134
|
+
["type"] = "reflection",
|
|
135
|
+
["ui"] = "ui",
|
|
136
|
+
["unity"] = "tools",
|
|
137
|
+
["vfx"] = "vfx"
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/// <summary>
|
|
141
|
+
/// EditorPrefs key used to persist the per-group enabled flags.
|
|
142
|
+
/// The stored value is a pipe-separated list of <c>group=0|group=1</c>
|
|
143
|
+
/// tokens. Unknown groups in the stored value are ignored on load.
|
|
144
|
+
/// </summary>
|
|
145
|
+
public const string EditorPrefsKey = ProductInfo.EditorPrefsPrefix + ".ToolGroups.Enabled";
|
|
146
|
+
|
|
147
|
+
// ---------------------------------------------------------------------
|
|
148
|
+
// Static initialization.
|
|
149
|
+
// ---------------------------------------------------------------------
|
|
150
|
+
static ToolGroupRegistry()
|
|
151
|
+
{
|
|
152
|
+
try
|
|
153
|
+
{
|
|
154
|
+
Scan();
|
|
155
|
+
LoadEnabledFromPrefs();
|
|
156
|
+
}
|
|
157
|
+
catch (Exception ex)
|
|
158
|
+
{
|
|
159
|
+
// Never let registry construction take down a domain reload.
|
|
160
|
+
// Log via UnityEngine.Debug only — the plugin logger is not
|
|
161
|
+
// guaranteed to be initialized this early in startup.
|
|
162
|
+
UnityEngine.Debug.LogWarning(
|
|
163
|
+
$"[ToolGroupRegistry] initialization failed: {ex.GetType().Name}: {ex.Message}");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ---------------------------------------------------------------------
|
|
168
|
+
// Public API.
|
|
169
|
+
// ---------------------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
/// <summary>All known group names, sorted ascending.</summary>
|
|
172
|
+
public static IReadOnlyList<string> AllGroups()
|
|
173
|
+
=> s_knownGroups.OrderBy(s => s, StringComparer.Ordinal).ToList();
|
|
174
|
+
|
|
175
|
+
public static string? ResolveCanonicalGroup(string? group)
|
|
176
|
+
{
|
|
177
|
+
if (string.IsNullOrWhiteSpace(group)) return null;
|
|
178
|
+
var normalized = group.Trim();
|
|
179
|
+
if (s_aliasToCanonical.TryGetValue(normalized, out var canonical) &&
|
|
180
|
+
s_knownGroups.Contains(canonical))
|
|
181
|
+
return canonical;
|
|
182
|
+
return s_knownGroups.Contains(normalized) ? normalized : null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
public static IReadOnlyList<string> AliasesForGroup(string group)
|
|
186
|
+
{
|
|
187
|
+
var canonical = ResolveCanonicalGroup(group) ?? group;
|
|
188
|
+
return s_aliasesByCanonical.TryGetValue(canonical, out var aliases)
|
|
189
|
+
? aliases.ToArray()
|
|
190
|
+
: Array.Empty<string>();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/// <summary>
|
|
194
|
+
/// True if the tool is in an enabled group, or is ungrouped (ungrouped
|
|
195
|
+
/// tools are always considered enabled — they were not opted into the
|
|
196
|
+
/// group system).
|
|
197
|
+
/// </summary>
|
|
198
|
+
public static bool IsToolEnabled(string toolName)
|
|
199
|
+
{
|
|
200
|
+
if (string.IsNullOrEmpty(toolName))
|
|
201
|
+
return true;
|
|
202
|
+
if (!s_toolToGroup.TryGetValue(toolName, out var group))
|
|
203
|
+
return true;
|
|
204
|
+
return s_enabled.TryGetValue(group, out var enabled) ? enabled : true;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/// <summary>True if the group is currently enabled.</summary>
|
|
208
|
+
public static bool IsGroupEnabled(string group)
|
|
209
|
+
{
|
|
210
|
+
var canonical = ResolveCanonicalGroup(group) ?? group;
|
|
211
|
+
return s_enabled.TryGetValue(canonical, out var enabled) ? enabled : true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/// <summary>
|
|
215
|
+
/// Actual callability at the current request boundary. Group state is still a
|
|
216
|
+
/// soft preference, so a populated group remains effectively callable even
|
|
217
|
+
/// when its requested state is disabled.
|
|
218
|
+
/// </summary>
|
|
219
|
+
public static bool IsGroupEffectivelyEnabled(string group)
|
|
220
|
+
{
|
|
221
|
+
var canonical = ResolveCanonicalGroup(group);
|
|
222
|
+
return canonical != null && s_groupToTools.TryGetValue(canonical, out var tools) && tools.Count > 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/// <summary>
|
|
226
|
+
/// Toggle a group's enabled state and persist the new state to
|
|
227
|
+
/// EditorPrefs. Throws <see cref="ArgumentException"/> if the group
|
|
228
|
+
/// name is not known to the registry.
|
|
229
|
+
/// </summary>
|
|
230
|
+
public static void SetGroupEnabled(string group, bool enabled)
|
|
231
|
+
{
|
|
232
|
+
if (string.IsNullOrWhiteSpace(group))
|
|
233
|
+
throw new ArgumentException("Group name must be non-empty.", nameof(group));
|
|
234
|
+
var canonical = ResolveCanonicalGroup(group);
|
|
235
|
+
if (canonical == null)
|
|
236
|
+
throw new ArgumentException(
|
|
237
|
+
$"Unknown group '{group}'. Known: {string.Join(", ", s_knownGroups)}",
|
|
238
|
+
nameof(group));
|
|
239
|
+
|
|
240
|
+
s_enabled[canonical] = enabled;
|
|
241
|
+
SaveEnabledToPrefs();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/// <summary>Tool ids that belong to <paramref name="group"/>, or empty when unknown.</summary>
|
|
245
|
+
public static IReadOnlyList<string> ToolsInGroup(string group)
|
|
246
|
+
=> s_groupToTools.TryGetValue(ResolveCanonicalGroup(group) ?? group, out var list)
|
|
247
|
+
? list.ToList()
|
|
248
|
+
: new List<string>();
|
|
249
|
+
|
|
250
|
+
/// <summary>True when the group is known to the registry.</summary>
|
|
251
|
+
public static bool IsKnownGroup(string group) => ResolveCanonicalGroup(group) != null;
|
|
252
|
+
|
|
253
|
+
/// <summary>The default-enabled flag declared by the attribute. Unknown groups → true.</summary>
|
|
254
|
+
public static bool GetDefaultEnabled(string group)
|
|
255
|
+
=> s_defaultEnabled.TryGetValue(ResolveCanonicalGroup(group) ?? group, out var v) ? v : true;
|
|
256
|
+
|
|
257
|
+
/// <summary>Optional human-readable description from the attribute, or null.</summary>
|
|
258
|
+
public static string? GetDescription(string group)
|
|
259
|
+
=> s_descriptions.TryGetValue(ResolveCanonicalGroup(group) ?? group, out var d) ? d : null;
|
|
260
|
+
|
|
261
|
+
/// <summary>
|
|
262
|
+
/// Force a re-scan of the loaded assemblies. Useful in tests; the
|
|
263
|
+
/// registry self-initializes on domain reload so production code does
|
|
264
|
+
/// not need to call this.
|
|
265
|
+
/// </summary>
|
|
266
|
+
public static void Rebuild()
|
|
267
|
+
{
|
|
268
|
+
s_groupToTools.Clear();
|
|
269
|
+
s_toolToGroup.Clear();
|
|
270
|
+
s_enabled.Clear();
|
|
271
|
+
s_defaultEnabled.Clear();
|
|
272
|
+
s_descriptions.Clear();
|
|
273
|
+
s_knownGroups.Clear();
|
|
274
|
+
Scan();
|
|
275
|
+
LoadEnabledFromPrefs();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ---------------------------------------------------------------------
|
|
279
|
+
// Implementation.
|
|
280
|
+
// ---------------------------------------------------------------------
|
|
281
|
+
|
|
282
|
+
static void Scan()
|
|
283
|
+
{
|
|
284
|
+
// Production tool declarations predate ToolGroupAttribute, so discover the
|
|
285
|
+
// actual UcoTool methods and classify their stable tool IDs. An explicit
|
|
286
|
+
// ToolGroupAttribute on a declaring type remains the override seam.
|
|
287
|
+
var methods = TypeCache.GetMethodsWithAttribute<UcoToolAttribute>();
|
|
288
|
+
foreach (var method in methods)
|
|
289
|
+
{
|
|
290
|
+
var toolName = ExtractToolName(method);
|
|
291
|
+
if (string.IsNullOrWhiteSpace(toolName)) continue;
|
|
292
|
+
|
|
293
|
+
var groupAttr = method.DeclaringType?.GetCustomAttribute<ToolGroupAttribute>(inherit: false);
|
|
294
|
+
var explicitGroup = groupAttr == null ? null : ResolveAliasWithoutKnownCheck(groupAttr.Group);
|
|
295
|
+
var groupName = !string.IsNullOrWhiteSpace(explicitGroup)
|
|
296
|
+
? explicitGroup!
|
|
297
|
+
: ClassifyTool(toolName!);
|
|
298
|
+
RegisterGroup(groupName, groupAttr?.DefaultEnabled ?? true, groupAttr?.Description);
|
|
299
|
+
RegisterTool(toolName!, groupName);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
static void RegisterGroup(string groupName, bool defaultEnabled, string? description)
|
|
304
|
+
{
|
|
305
|
+
s_knownGroups.Add(groupName);
|
|
306
|
+
s_defaultEnabled[groupName] = defaultEnabled;
|
|
307
|
+
if (description != null) s_descriptions[groupName] = description;
|
|
308
|
+
else if (!s_descriptions.ContainsKey(groupName))
|
|
309
|
+
s_descriptions[groupName] = $"Production tools in the '{groupName}' capability group.";
|
|
310
|
+
if (!s_enabled.ContainsKey(groupName)) s_enabled[groupName] = defaultEnabled;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
static void RegisterTool(string toolName, string groupName)
|
|
314
|
+
{
|
|
315
|
+
if (!s_groupToTools.TryGetValue(groupName, out var list))
|
|
316
|
+
{
|
|
317
|
+
list = new List<string>();
|
|
318
|
+
s_groupToTools[groupName] = list;
|
|
319
|
+
}
|
|
320
|
+
if (!list.Contains(toolName)) list.Add(toolName);
|
|
321
|
+
|
|
322
|
+
if (s_toolToGroup.TryGetValue(toolName, out var existing) && existing != groupName)
|
|
323
|
+
{
|
|
324
|
+
UnityEngine.Debug.LogWarning(
|
|
325
|
+
$"[ToolGroupRegistry] tool '{toolName}' already mapped to group " +
|
|
326
|
+
$"'{existing}'; ignoring duplicate mapping to '{groupName}'.");
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
s_toolToGroup[toolName] = groupName;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
static string ClassifyTool(string toolName)
|
|
333
|
+
{
|
|
334
|
+
if (toolName.StartsWith("editor-operation-", StringComparison.OrdinalIgnoreCase))
|
|
335
|
+
return "operations";
|
|
336
|
+
var separator = toolName.IndexOf('-');
|
|
337
|
+
var prefix = separator < 0 ? toolName : toolName.Substring(0, separator);
|
|
338
|
+
return s_prefixToCanonical.TryGetValue(prefix, out var canonical)
|
|
339
|
+
? canonical
|
|
340
|
+
: prefix.ToLowerInvariant();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
static Dictionary<string, string> BuildAliasIndex()
|
|
344
|
+
{
|
|
345
|
+
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
346
|
+
foreach (var pair in s_aliasesByCanonical)
|
|
347
|
+
{
|
|
348
|
+
result[pair.Key] = pair.Key;
|
|
349
|
+
foreach (var alias in pair.Value) result[alias] = pair.Key;
|
|
350
|
+
}
|
|
351
|
+
return result;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
static string ResolveAliasWithoutKnownCheck(string group)
|
|
355
|
+
{
|
|
356
|
+
if (string.IsNullOrWhiteSpace(group)) return string.Empty;
|
|
357
|
+
return s_aliasToCanonical.TryGetValue(group.Trim(), out var canonical)
|
|
358
|
+
? canonical
|
|
359
|
+
: group.Trim().ToLowerInvariant();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/// <summary>
|
|
363
|
+
/// Reflect a method's custom attributes looking for one whose simple
|
|
364
|
+
/// type name is <c>UcoToolAttribute</c>, and pull out the tool
|
|
365
|
+
/// id via its <c>Name</c> property. We intentionally avoid a typed
|
|
366
|
+
/// reference to the external NuGet attribute so this file does not
|
|
367
|
+
/// require linking against <c>Uco.Framework.dll</c> at compile time —
|
|
368
|
+
/// the asmdef already references it for the rest of the codebase,
|
|
369
|
+
/// but loose coupling here keeps the registry resilient to upstream
|
|
370
|
+
/// renames of the property surface.
|
|
371
|
+
/// </summary>
|
|
372
|
+
static string? ExtractToolName(MethodInfo method)
|
|
373
|
+
{
|
|
374
|
+
object[] attrs;
|
|
375
|
+
try { attrs = method.GetCustomAttributes(inherit: false); }
|
|
376
|
+
catch { return null; }
|
|
377
|
+
|
|
378
|
+
foreach (var attr in attrs)
|
|
379
|
+
{
|
|
380
|
+
if (attr == null) continue;
|
|
381
|
+
var typeName = attr.GetType().Name;
|
|
382
|
+
if (typeName != "UcoToolAttribute")
|
|
383
|
+
continue;
|
|
384
|
+
|
|
385
|
+
// Probe a small set of likely member names. The upstream
|
|
386
|
+
// attribute uses 'Name' (verified via TestProject), but we
|
|
387
|
+
// try a fallback 'Id' to stay forward-compatible.
|
|
388
|
+
var nameValue = ReadStringMember(attr, "Name")
|
|
389
|
+
?? ReadStringMember(attr, "Id");
|
|
390
|
+
if (!string.IsNullOrWhiteSpace(nameValue))
|
|
391
|
+
return nameValue;
|
|
392
|
+
}
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
static string? ReadStringMember(object instance, string memberName)
|
|
397
|
+
{
|
|
398
|
+
var type = instance.GetType();
|
|
399
|
+
var prop = type.GetProperty(memberName,
|
|
400
|
+
BindingFlags.Public | BindingFlags.Instance);
|
|
401
|
+
if (prop != null)
|
|
402
|
+
{
|
|
403
|
+
try { return prop.GetValue(instance) as string; }
|
|
404
|
+
catch { return null; }
|
|
405
|
+
}
|
|
406
|
+
var field = type.GetField(memberName,
|
|
407
|
+
BindingFlags.Public | BindingFlags.Instance);
|
|
408
|
+
if (field != null)
|
|
409
|
+
{
|
|
410
|
+
try { return field.GetValue(instance) as string; }
|
|
411
|
+
catch { return null; }
|
|
412
|
+
}
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ---------------------------------------------------------------------
|
|
417
|
+
// Persistence.
|
|
418
|
+
// ---------------------------------------------------------------------
|
|
419
|
+
|
|
420
|
+
static void LoadEnabledFromPrefs()
|
|
421
|
+
{
|
|
422
|
+
var raw = EditorPrefs.GetString(EditorPrefsKey, string.Empty);
|
|
423
|
+
if (string.IsNullOrEmpty(raw))
|
|
424
|
+
return;
|
|
425
|
+
|
|
426
|
+
// Format: group=0|group=1|...
|
|
427
|
+
foreach (var entry in raw.Split('|'))
|
|
428
|
+
{
|
|
429
|
+
if (string.IsNullOrWhiteSpace(entry)) continue;
|
|
430
|
+
var sep = entry.IndexOf('=');
|
|
431
|
+
if (sep <= 0 || sep >= entry.Length - 1) continue;
|
|
432
|
+
|
|
433
|
+
var name = entry.Substring(0, sep);
|
|
434
|
+
var flag = entry.Substring(sep + 1);
|
|
435
|
+
if (!s_knownGroups.Contains(name)) continue; // ignore stale
|
|
436
|
+
if (flag == "1") s_enabled[name] = true;
|
|
437
|
+
else if (flag == "0") s_enabled[name] = false;
|
|
438
|
+
// anything else: ignore (use the attribute default)
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
static void SaveEnabledToPrefs()
|
|
443
|
+
{
|
|
444
|
+
if (s_enabled.Count == 0)
|
|
445
|
+
{
|
|
446
|
+
EditorPrefs.DeleteKey(EditorPrefsKey);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
var sb = new StringBuilder(s_enabled.Count * 12);
|
|
451
|
+
var first = true;
|
|
452
|
+
foreach (var kv in s_enabled.OrderBy(p => p.Key, StringComparer.Ordinal))
|
|
453
|
+
{
|
|
454
|
+
if (!first) sb.Append('|');
|
|
455
|
+
sb.Append(kv.Key).Append('=').Append(kv.Value ? '1' : '0');
|
|
456
|
+
first = false;
|
|
457
|
+
}
|
|
458
|
+
EditorPrefs.SetString(EditorPrefsKey, sb.ToString());
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|