@mflrevan/ucp 0.4.4 → 0.4.6

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 (84) hide show
  1. package/README.md +1 -1
  2. package/bridge/com.ucp.bridge/CHANGELOG.md +145 -0
  3. package/bridge/com.ucp.bridge/CHANGELOG.md.meta +7 -0
  4. package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs +583 -0
  5. package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs.meta +2 -0
  6. package/bridge/com.ucp.bridge/Editor/Bridge.meta +8 -0
  7. package/bridge/com.ucp.bridge/Editor/Compatibility/UnityObjectCompat.cs +18 -0
  8. package/bridge/com.ucp.bridge/Editor/Compatibility/UnityObjectCompat.cs.meta +2 -0
  9. package/bridge/com.ucp.bridge/Editor/Compatibility.meta +8 -0
  10. package/bridge/com.ucp.bridge/Editor/Controllers/AssetController.cs +425 -0
  11. package/bridge/com.ucp.bridge/Editor/Controllers/AssetController.cs.meta +2 -0
  12. package/bridge/com.ucp.bridge/Editor/Controllers/AssetImportSupport.cs +355 -0
  13. package/bridge/com.ucp.bridge/Editor/Controllers/AssetImportSupport.cs.meta +2 -0
  14. package/bridge/com.ucp.bridge/Editor/Controllers/BuildController.cs +233 -0
  15. package/bridge/com.ucp.bridge/Editor/Controllers/BuildController.cs.meta +2 -0
  16. package/bridge/com.ucp.bridge/Editor/Controllers/CompilationController.cs +26 -0
  17. package/bridge/com.ucp.bridge/Editor/Controllers/CompilationController.cs.meta +2 -0
  18. package/bridge/com.ucp.bridge/Editor/Controllers/EditorController.cs +31 -0
  19. package/bridge/com.ucp.bridge/Editor/Controllers/EditorController.cs.meta +2 -0
  20. package/bridge/com.ucp.bridge/Editor/Controllers/EditorSettingsController.cs +527 -0
  21. package/bridge/com.ucp.bridge/Editor/Controllers/EditorSettingsController.cs.meta +2 -0
  22. package/bridge/com.ucp.bridge/Editor/Controllers/FileController.cs +141 -0
  23. package/bridge/com.ucp.bridge/Editor/Controllers/FileController.cs.meta +2 -0
  24. package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs +326 -0
  25. package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs.meta +2 -0
  26. package/bridge/com.ucp.bridge/Editor/Controllers/ImporterController.cs +209 -0
  27. package/bridge/com.ucp.bridge/Editor/Controllers/ImporterController.cs.meta +2 -0
  28. package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs +409 -0
  29. package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs.meta +2 -0
  30. package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs +354 -0
  31. package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs.meta +2 -0
  32. package/bridge/com.ucp.bridge/Editor/Controllers/ObjectReferenceResolver.cs +93 -0
  33. package/bridge/com.ucp.bridge/Editor/Controllers/ObjectReferenceResolver.cs.meta +2 -0
  34. package/bridge/com.ucp.bridge/Editor/Controllers/PackagesController.cs +503 -0
  35. package/bridge/com.ucp.bridge/Editor/Controllers/PackagesController.cs.meta +2 -0
  36. package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs +188 -0
  37. package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs.meta +2 -0
  38. package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs +260 -0
  39. package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs.meta +2 -0
  40. package/bridge/com.ucp.bridge/Editor/Controllers/ProfilerController.cs +1679 -0
  41. package/bridge/com.ucp.bridge/Editor/Controllers/ProfilerController.cs.meta +2 -0
  42. package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs +579 -0
  43. package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs.meta +2 -0
  44. package/bridge/com.ucp.bridge/Editor/Controllers/SceneChangeTracker.cs +166 -0
  45. package/bridge/com.ucp.bridge/Editor/Controllers/SceneChangeTracker.cs.meta +2 -0
  46. package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs +318 -0
  47. package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs.meta +2 -0
  48. package/bridge/com.ucp.bridge/Editor/Controllers/ScreenshotController.cs +125 -0
  49. package/bridge/com.ucp.bridge/Editor/Controllers/ScreenshotController.cs.meta +2 -0
  50. package/bridge/com.ucp.bridge/Editor/Controllers/ScriptController.cs +104 -0
  51. package/bridge/com.ucp.bridge/Editor/Controllers/ScriptController.cs.meta +2 -0
  52. package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs +227 -0
  53. package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs.meta +2 -0
  54. package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs +240 -0
  55. package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs.meta +2 -0
  56. package/bridge/com.ucp.bridge/Editor/Controllers/VcsController.cs +611 -0
  57. package/bridge/com.ucp.bridge/Editor/Controllers/VcsController.cs.meta +2 -0
  58. package/bridge/com.ucp.bridge/Editor/Controllers.meta +8 -0
  59. package/bridge/com.ucp.bridge/Editor/Protocol/CommandRouter.cs +53 -0
  60. package/bridge/com.ucp.bridge/Editor/Protocol/CommandRouter.cs.meta +2 -0
  61. package/bridge/com.ucp.bridge/Editor/Protocol/MessageTypes.cs +80 -0
  62. package/bridge/com.ucp.bridge/Editor/Protocol/MessageTypes.cs.meta +2 -0
  63. package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs +358 -0
  64. package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs.meta +2 -0
  65. package/bridge/com.ucp.bridge/Editor/Protocol.meta +8 -0
  66. package/bridge/com.ucp.bridge/Editor/Scripts/IUCPScript.cs +37 -0
  67. package/bridge/com.ucp.bridge/Editor/Scripts/IUCPScript.cs.meta +2 -0
  68. package/bridge/com.ucp.bridge/Editor/Scripts.meta +8 -0
  69. package/bridge/com.ucp.bridge/Editor/UCP.Bridge.Editor.asmdef +18 -0
  70. package/bridge/com.ucp.bridge/Editor/UCP.Bridge.Editor.asmdef.meta +7 -0
  71. package/bridge/com.ucp.bridge/Editor.meta +8 -0
  72. package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef +14 -0
  73. package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef.meta +7 -0
  74. package/bridge/com.ucp.bridge/Runtime.meta +8 -0
  75. package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs +1085 -0
  76. package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs.meta +2 -0
  77. package/bridge/com.ucp.bridge/Tests/Editor/UCP.Bridge.Editor.Tests.asmdef +12 -0
  78. package/bridge/com.ucp.bridge/Tests/Editor/UCP.Bridge.Editor.Tests.asmdef.meta +7 -0
  79. package/bridge/com.ucp.bridge/Tests/Editor.meta +8 -0
  80. package/bridge/com.ucp.bridge/Tests.meta +8 -0
  81. package/bridge/com.ucp.bridge/package.json +29 -0
  82. package/bridge/com.ucp.bridge/package.json.meta +7 -0
  83. package/package.json +2 -2
  84. package/scripts/install.js +4 -6
@@ -0,0 +1,503 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.IO;
4
+ using System.Linq;
5
+ using System.Threading;
6
+ using UnityEditor;
7
+ using UnityEditor.PackageManager;
8
+ using UnityEditor.PackageManager.Requests;
9
+ using UnityEngine;
10
+ using UpmPackageInfo = UnityEditor.PackageManager.PackageInfo;
11
+
12
+ namespace UCP.Bridge
13
+ {
14
+ public static class PackagesController
15
+ {
16
+ private const int DefaultTimeoutMs = 120000;
17
+
18
+ public static void Register(CommandRouter router)
19
+ {
20
+ router.Register("packages/list", HandleList);
21
+ router.Register("packages/search", HandleSearch);
22
+ router.Register("packages/info", HandleInfo);
23
+ router.Register("packages/add", HandleAdd);
24
+ router.Register("packages/remove", HandleRemove);
25
+ router.Register("packages/dependencies", HandleDependencies);
26
+ router.Register("packages/dependency/set", HandleSetDependency);
27
+ router.Register("packages/dependency/remove", HandleRemoveDependency);
28
+ router.Register("packages/registries/list", HandleListRegistries);
29
+ router.Register("packages/registries/add", HandleAddRegistry);
30
+ router.Register("packages/registries/remove", HandleRemoveRegistry);
31
+ }
32
+
33
+ private static object HandleList(string paramsJson)
34
+ {
35
+ var parameters = ParseParameters(paramsJson);
36
+ var offline = ReadOptionalBool(parameters, "offline");
37
+ var includeIndirect = ReadOptionalBool(parameters, "includeIndirect");
38
+ var directDependencies = ReadDirectDependencies();
39
+ var request = WaitForRequest(Client.List(offline, includeIndirect), DefaultTimeoutMs);
40
+ var packages = request.Result
41
+ .Select(package => SerializePackage(package, directDependencies))
42
+ .OrderByDescending(item => Convert.ToBoolean(item["directDependency"]))
43
+ .ThenBy(item => item["name"].ToString(), StringComparer.OrdinalIgnoreCase)
44
+ .Cast<object>()
45
+ .ToList();
46
+
47
+ return new Dictionary<string, object>
48
+ {
49
+ ["packages"] = packages,
50
+ ["offline"] = offline,
51
+ ["includeIndirect"] = includeIndirect,
52
+ ["count"] = packages.Count
53
+ };
54
+ }
55
+
56
+ private static object HandleSearch(string paramsJson)
57
+ {
58
+ var parameters = ParseParameters(paramsJson);
59
+ var query = ReadOptionalString(parameters, "query");
60
+ var offline = ReadOptionalBool(parameters, "offline");
61
+ var maxResults = ReadOptionalInt(parameters, "maxResults", 50);
62
+ SearchRequest request = string.IsNullOrWhiteSpace(query)
63
+ ? WaitForRequest(Client.SearchAll(offline), DefaultTimeoutMs)
64
+ : WaitForRequest(Client.Search(query, offline), DefaultTimeoutMs);
65
+
66
+ var results = request.Result
67
+ .Select(package => SerializePackage(package, null))
68
+ .OrderBy(item => item["name"].ToString(), StringComparer.OrdinalIgnoreCase)
69
+ .Take(Math.Max(1, maxResults))
70
+ .Cast<object>()
71
+ .ToList();
72
+
73
+ return new Dictionary<string, object>
74
+ {
75
+ ["query"] = query,
76
+ ["offline"] = offline,
77
+ ["results"] = results,
78
+ ["count"] = results.Count
79
+ };
80
+ }
81
+
82
+ private static object HandleInfo(string paramsJson)
83
+ {
84
+ var parameters = ParseParameters(paramsJson);
85
+ var name = RequireString(parameters, "name");
86
+ var offline = ReadOptionalBool(parameters, "offline");
87
+ var directDependencies = ReadDirectDependencies();
88
+
89
+ var installed = WaitForRequest(Client.List(offline, true), DefaultTimeoutMs)
90
+ .Result
91
+ .FirstOrDefault(package =>
92
+ package.name.Equals(name, StringComparison.OrdinalIgnoreCase)
93
+ || package.packageId.Equals(name, StringComparison.OrdinalIgnoreCase));
94
+
95
+ if (installed != null)
96
+ {
97
+ var payload = SerializePackage(installed, directDependencies);
98
+ payload["installed"] = true;
99
+ return payload;
100
+ }
101
+
102
+ var search = WaitForRequest(Client.Search(name, offline), DefaultTimeoutMs);
103
+ var found = search.Result.FirstOrDefault(package =>
104
+ package.name.Equals(name, StringComparison.OrdinalIgnoreCase)
105
+ || package.packageId.Equals(name, StringComparison.OrdinalIgnoreCase));
106
+
107
+ if (found == null)
108
+ throw new ArgumentException($"Package not found: {name}");
109
+
110
+ var result = SerializePackage(found, null);
111
+ result["installed"] = false;
112
+ return result;
113
+ }
114
+
115
+ private static object HandleAdd(string paramsJson)
116
+ {
117
+ var parameters = ParseParameters(paramsJson);
118
+ var packageId = RequireString(parameters, "packageId");
119
+ var request = WaitForRequest(Client.Add(packageId), DefaultTimeoutMs);
120
+ var directDependencies = ReadDirectDependencies();
121
+
122
+ return new Dictionary<string, object>
123
+ {
124
+ ["status"] = "ok",
125
+ ["packageId"] = packageId,
126
+ ["package"] = SerializePackage(request.Result, directDependencies)
127
+ };
128
+ }
129
+
130
+ private static object HandleRemove(string paramsJson)
131
+ {
132
+ var parameters = ParseParameters(paramsJson);
133
+ var name = RequireString(parameters, "name");
134
+ var request = WaitForRequest(Client.Remove(name), DefaultTimeoutMs);
135
+
136
+ return new Dictionary<string, object>
137
+ {
138
+ ["status"] = "ok",
139
+ ["name"] = request.PackageIdOrName
140
+ };
141
+ }
142
+
143
+ private static object HandleDependencies(string paramsJson)
144
+ {
145
+ var manifest = ReadManifest();
146
+ var dependencies = ReadDirectDependencies()
147
+ .OrderBy(entry => entry.Key, StringComparer.OrdinalIgnoreCase)
148
+ .Select(entry => (object)new Dictionary<string, object>
149
+ {
150
+ ["name"] = entry.Key,
151
+ ["reference"] = entry.Value
152
+ })
153
+ .ToList();
154
+
155
+ return new Dictionary<string, object>
156
+ {
157
+ ["dependencies"] = dependencies,
158
+ ["count"] = dependencies.Count,
159
+ ["manifestPath"] = ManifestPath
160
+ };
161
+ }
162
+
163
+ private static object HandleSetDependency(string paramsJson)
164
+ {
165
+ var parameters = ParseParameters(paramsJson);
166
+ var name = RequireString(parameters, "name");
167
+ var reference = RequireString(parameters, "reference");
168
+ var manifest = ReadManifest();
169
+ var dependencies = EnsureObject(manifest, "dependencies");
170
+ var previous = dependencies.ContainsKey(name) ? dependencies[name]?.ToString() : null;
171
+ dependencies[name] = reference;
172
+ WriteManifest(manifest);
173
+ TriggerResolve();
174
+
175
+ return new Dictionary<string, object>
176
+ {
177
+ ["status"] = "ok",
178
+ ["name"] = name,
179
+ ["reference"] = reference,
180
+ ["previousReference"] = previous,
181
+ ["changed"] = !string.Equals(previous, reference, StringComparison.Ordinal)
182
+ };
183
+ }
184
+
185
+ private static object HandleRemoveDependency(string paramsJson)
186
+ {
187
+ var parameters = ParseParameters(paramsJson);
188
+ var name = RequireString(parameters, "name");
189
+ var manifest = ReadManifest();
190
+ var dependencies = EnsureObject(manifest, "dependencies");
191
+ var previous = dependencies.ContainsKey(name) ? dependencies[name]?.ToString() : null;
192
+ var removed = dependencies.Remove(name);
193
+ if (!removed)
194
+ throw new ArgumentException($"Dependency not found: {name}");
195
+
196
+ WriteManifest(manifest);
197
+ TriggerResolve();
198
+
199
+ return new Dictionary<string, object>
200
+ {
201
+ ["status"] = "ok",
202
+ ["name"] = name,
203
+ ["previousReference"] = previous
204
+ };
205
+ }
206
+
207
+ private static object HandleListRegistries(string paramsJson)
208
+ {
209
+ var registries = ReadScopedRegistries()
210
+ .OrderBy(item => item["name"].ToString(), StringComparer.OrdinalIgnoreCase)
211
+ .Cast<object>()
212
+ .ToList();
213
+
214
+ return new Dictionary<string, object>
215
+ {
216
+ ["registries"] = registries,
217
+ ["count"] = registries.Count,
218
+ ["manifestPath"] = ManifestPath
219
+ };
220
+ }
221
+
222
+ private static object HandleAddRegistry(string paramsJson)
223
+ {
224
+ var parameters = ParseParameters(paramsJson);
225
+ var name = RequireString(parameters, "name");
226
+ var url = RequireString(parameters, "url");
227
+ var scopes = ReadScopes(parameters);
228
+ if (scopes.Count == 0)
229
+ throw new ArgumentException("At least one scope is required");
230
+
231
+ var manifest = ReadManifest();
232
+ var registries = EnsureArray(manifest, "scopedRegistries");
233
+ Dictionary<string, object> previous = null;
234
+
235
+ for (var index = registries.Count - 1; index >= 0; index--)
236
+ {
237
+ if (registries[index] is Dictionary<string, object> existing
238
+ && existing.TryGetValue("name", out var existingName)
239
+ && string.Equals(existingName?.ToString(), name, StringComparison.OrdinalIgnoreCase))
240
+ {
241
+ previous = existing;
242
+ registries.RemoveAt(index);
243
+ }
244
+ }
245
+
246
+ var registry = new Dictionary<string, object>
247
+ {
248
+ ["name"] = name,
249
+ ["url"] = url,
250
+ ["scopes"] = scopes.Cast<object>().ToList()
251
+ };
252
+
253
+ registries.Add(registry);
254
+ WriteManifest(manifest);
255
+ TriggerResolve();
256
+
257
+ return new Dictionary<string, object>
258
+ {
259
+ ["status"] = "ok",
260
+ ["registry"] = SerializeRegistry(registry),
261
+ ["previous"] = previous != null ? SerializeRegistry(previous) : null
262
+ };
263
+ }
264
+
265
+ private static object HandleRemoveRegistry(string paramsJson)
266
+ {
267
+ var parameters = ParseParameters(paramsJson);
268
+ var name = RequireString(parameters, "name");
269
+ var manifest = ReadManifest();
270
+ var registries = EnsureArray(manifest, "scopedRegistries");
271
+ Dictionary<string, object> removed = null;
272
+
273
+ for (var index = registries.Count - 1; index >= 0; index--)
274
+ {
275
+ if (registries[index] is Dictionary<string, object> existing
276
+ && existing.TryGetValue("name", out var existingName)
277
+ && string.Equals(existingName?.ToString(), name, StringComparison.OrdinalIgnoreCase))
278
+ {
279
+ removed = existing;
280
+ registries.RemoveAt(index);
281
+ break;
282
+ }
283
+ }
284
+
285
+ if (removed == null)
286
+ throw new ArgumentException($"Scoped registry not found: {name}");
287
+
288
+ WriteManifest(manifest);
289
+ TriggerResolve();
290
+
291
+ return new Dictionary<string, object>
292
+ {
293
+ ["status"] = "ok",
294
+ ["registry"] = SerializeRegistry(removed)
295
+ };
296
+ }
297
+
298
+ private static Dictionary<string, object> ParseParameters(string paramsJson)
299
+ {
300
+ return string.IsNullOrWhiteSpace(paramsJson)
301
+ ? new Dictionary<string, object>()
302
+ : MiniJson.Deserialize(paramsJson) as Dictionary<string, object>
303
+ ?? throw new ArgumentException("Invalid parameters");
304
+ }
305
+
306
+ private static string RequireString(Dictionary<string, object> parameters, string key)
307
+ {
308
+ if (!parameters.TryGetValue(key, out var value) || value == null || string.IsNullOrWhiteSpace(value.ToString()))
309
+ throw new ArgumentException($"Missing '{key}' parameter");
310
+
311
+ return value.ToString();
312
+ }
313
+
314
+ private static string ReadOptionalString(Dictionary<string, object> parameters, string key)
315
+ {
316
+ return parameters.TryGetValue(key, out var value) && value != null
317
+ ? value.ToString()
318
+ : null;
319
+ }
320
+
321
+ private static bool ReadOptionalBool(Dictionary<string, object> parameters, string key)
322
+ {
323
+ return parameters.TryGetValue(key, out var value) && value != null && Convert.ToBoolean(value);
324
+ }
325
+
326
+ private static int ReadOptionalInt(Dictionary<string, object> parameters, string key, int defaultValue)
327
+ {
328
+ return parameters.TryGetValue(key, out var value) && value != null
329
+ ? Convert.ToInt32(value)
330
+ : defaultValue;
331
+ }
332
+
333
+ private static Dictionary<string, string> ReadDirectDependencies()
334
+ {
335
+ var manifest = ReadManifest();
336
+ var dependencies = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
337
+
338
+ if (!manifest.TryGetValue("dependencies", out var dependenciesObj)
339
+ || !(dependenciesObj is Dictionary<string, object> dependencyMap))
340
+ {
341
+ return dependencies;
342
+ }
343
+
344
+ foreach (var entry in dependencyMap)
345
+ dependencies[entry.Key] = entry.Value?.ToString() ?? string.Empty;
346
+
347
+ return dependencies;
348
+ }
349
+
350
+ private static List<Dictionary<string, object>> ReadScopedRegistries()
351
+ {
352
+ var manifest = ReadManifest();
353
+ if (!manifest.TryGetValue("scopedRegistries", out var registriesObj)
354
+ || !(registriesObj is List<object> registryList))
355
+ {
356
+ return new List<Dictionary<string, object>>();
357
+ }
358
+
359
+ var result = new List<Dictionary<string, object>>();
360
+ foreach (var item in registryList)
361
+ {
362
+ if (item is Dictionary<string, object> registry)
363
+ result.Add(SerializeRegistry(registry));
364
+ }
365
+
366
+ return result;
367
+ }
368
+
369
+ private static Dictionary<string, object> ReadManifest()
370
+ {
371
+ if (!File.Exists(ManifestPath))
372
+ throw new FileNotFoundException($"manifest.json not found: {ManifestPath}");
373
+
374
+ var json = File.ReadAllText(ManifestPath);
375
+ return MiniJson.Deserialize(json) as Dictionary<string, object>
376
+ ?? throw new ArgumentException("manifest.json is not a JSON object");
377
+ }
378
+
379
+ private static void WriteManifest(Dictionary<string, object> manifest)
380
+ {
381
+ File.WriteAllText(ManifestPath, MiniJson.Serialize(manifest));
382
+ }
383
+
384
+ private static Dictionary<string, object> EnsureObject(Dictionary<string, object> root, string key)
385
+ {
386
+ if (root.TryGetValue(key, out var existing) && existing is Dictionary<string, object> existingObject)
387
+ return existingObject;
388
+
389
+ var created = new Dictionary<string, object>();
390
+ root[key] = created;
391
+ return created;
392
+ }
393
+
394
+ private static List<object> EnsureArray(Dictionary<string, object> root, string key)
395
+ {
396
+ if (root.TryGetValue(key, out var existing) && existing is List<object> existingArray)
397
+ return existingArray;
398
+
399
+ var created = new List<object>();
400
+ root[key] = created;
401
+ return created;
402
+ }
403
+
404
+ private static Dictionary<string, object> SerializePackage(UpmPackageInfo package, IDictionary<string, string> directDependencies)
405
+ {
406
+ var result = new Dictionary<string, object>
407
+ {
408
+ ["name"] = package.name,
409
+ ["displayName"] = package.displayName,
410
+ ["version"] = package.version,
411
+ ["packageId"] = package.packageId,
412
+ ["source"] = package.source.ToString(),
413
+ ["resolvedPath"] = package.resolvedPath,
414
+ ["description"] = package.description ?? string.Empty,
415
+ ["directDependency"] = directDependencies != null && directDependencies.ContainsKey(package.name),
416
+ ["dependencies"] = package.dependencies != null
417
+ ? package.dependencies.Select(dep => (object)new Dictionary<string, object>
418
+ {
419
+ ["name"] = dep.name,
420
+ ["version"] = dep.version
421
+ }).ToList()
422
+ : new List<object>()
423
+ };
424
+
425
+ if (!string.IsNullOrEmpty(package.category))
426
+ result["category"] = package.category;
427
+
428
+ if (package.versions != null)
429
+ {
430
+ result["versions"] = new Dictionary<string, object>
431
+ {
432
+ ["recommended"] = package.versions.recommended ?? string.Empty,
433
+ ["latestCompatible"] = package.versions.latestCompatible ?? string.Empty
434
+ };
435
+ }
436
+
437
+ if (package.registry != null)
438
+ {
439
+ result["registry"] = new Dictionary<string, object>
440
+ {
441
+ ["name"] = package.registry.name ?? string.Empty,
442
+ ["url"] = package.registry.url ?? string.Empty,
443
+ ["isDefault"] = package.registry.isDefault
444
+ };
445
+ }
446
+
447
+ return result;
448
+ }
449
+
450
+ private static Dictionary<string, object> SerializeRegistry(Dictionary<string, object> registry)
451
+ {
452
+ var scopes = registry.TryGetValue("scopes", out var scopesObj) && scopesObj is List<object> scopeList
453
+ ? scopeList.Select(scope => (object)scope.ToString()).ToList()
454
+ : new List<object>();
455
+
456
+ return new Dictionary<string, object>
457
+ {
458
+ ["name"] = registry.TryGetValue("name", out var name) ? name?.ToString() ?? string.Empty : string.Empty,
459
+ ["url"] = registry.TryGetValue("url", out var url) ? url?.ToString() ?? string.Empty : string.Empty,
460
+ ["scopes"] = scopes
461
+ };
462
+ }
463
+
464
+ private static List<string> ReadScopes(Dictionary<string, object> parameters)
465
+ {
466
+ if (!parameters.TryGetValue("scopes", out var scopesObj) || !(scopesObj is List<object> scopes))
467
+ return new List<string>();
468
+
469
+ return scopes
470
+ .Select(scope => scope?.ToString())
471
+ .Where(scope => !string.IsNullOrWhiteSpace(scope))
472
+ .Distinct(StringComparer.OrdinalIgnoreCase)
473
+ .ToList();
474
+ }
475
+
476
+ private static T WaitForRequest<T>(T request, int timeoutMs) where T : Request
477
+ {
478
+ var deadline = DateTime.UtcNow.AddMilliseconds(timeoutMs);
479
+ while (!request.IsCompleted)
480
+ {
481
+ if (DateTime.UtcNow > deadline)
482
+ throw new TimeoutException("Package Manager request timed out");
483
+
484
+ Thread.Sleep(50);
485
+ }
486
+
487
+ if (request.Status == StatusCode.Failure)
488
+ throw new InvalidOperationException(request.Error?.message ?? "Package Manager request failed");
489
+
490
+ return request;
491
+ }
492
+
493
+ private static void TriggerResolve()
494
+ {
495
+ Client.Resolve();
496
+ WaitForRequest(Client.List(true, true), DefaultTimeoutMs);
497
+ }
498
+
499
+ private static string ProjectRoot => Path.GetDirectoryName(Application.dataPath);
500
+
501
+ private static string ManifestPath => Path.Combine(ProjectRoot, "Packages", "manifest.json");
502
+ }
503
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: c59d4a0c292f48aa8bd526fbe42e31ba
@@ -0,0 +1,188 @@
1
+ using UnityEditor;
2
+ using UnityEditor.SceneManagement;
3
+ using UnityEngine.SceneManagement;
4
+ using System.Collections.Generic;
5
+ using System;
6
+
7
+ namespace UCP.Bridge
8
+ {
9
+ public static class PlayModeController
10
+ {
11
+ public sealed class SessionSnapshot
12
+ {
13
+ public bool Playing;
14
+ public bool Paused;
15
+ public bool WillChange;
16
+ public bool Compiling;
17
+ public DateTime? LastPlayRequestedAtUtc;
18
+ public DateTime? LastEnteredPlayAtUtc;
19
+ public DateTime? LastStopRequestedAtUtc;
20
+ public DateTime? LastExitedPlayAtUtc;
21
+ }
22
+
23
+ private static readonly object s_sessionLock = new object();
24
+ private static DateTime? s_lastPlayRequestedAtUtc;
25
+ private static DateTime? s_lastEnteredPlayAtUtc;
26
+ private static DateTime? s_lastStopRequestedAtUtc;
27
+ private static DateTime? s_lastExitedPlayAtUtc;
28
+
29
+ static PlayModeController()
30
+ {
31
+ EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
32
+ }
33
+
34
+ public static void Register(CommandRouter router)
35
+ {
36
+ router.Register("play", HandlePlay);
37
+ router.Register("play/status", HandleStatus);
38
+ router.Register("stop", HandleStop);
39
+ router.Register("pause", HandlePause);
40
+ }
41
+
42
+ public static SessionSnapshot GetSessionSnapshot()
43
+ {
44
+ lock (s_sessionLock)
45
+ {
46
+ return new SessionSnapshot
47
+ {
48
+ Playing = EditorApplication.isPlaying,
49
+ Paused = EditorApplication.isPaused,
50
+ WillChange = EditorApplication.isPlayingOrWillChangePlaymode,
51
+ Compiling = EditorApplication.isCompiling,
52
+ LastPlayRequestedAtUtc = s_lastPlayRequestedAtUtc,
53
+ LastEnteredPlayAtUtc = s_lastEnteredPlayAtUtc,
54
+ LastStopRequestedAtUtc = s_lastStopRequestedAtUtc,
55
+ LastExitedPlayAtUtc = s_lastExitedPlayAtUtc
56
+ };
57
+ }
58
+ }
59
+
60
+ private static object HandlePlay(string paramsJson)
61
+ {
62
+ if (EditorApplication.isPlaying)
63
+ return new { status = "already_playing" };
64
+
65
+ var saveDirtyScenes = GetBoolParam(paramsJson, "saveDirtyScenes", true);
66
+ var discardUntitled = GetBoolParam(paramsJson, "discardUntitled", true);
67
+ SaveDirtyScenesIfRequested(saveDirtyScenes, discardUntitled);
68
+
69
+ lock (s_sessionLock)
70
+ {
71
+ s_lastPlayRequestedAtUtc = DateTime.UtcNow;
72
+ }
73
+ EditorApplication.isPlaying = true;
74
+ return new { status = "ok" };
75
+ }
76
+
77
+ private static object HandleStatus(string paramsJson)
78
+ {
79
+ return SerializeSessionSnapshot(GetSessionSnapshot());
80
+ }
81
+
82
+ private static bool GetBoolParam(string paramsJson, string key, bool defaultValue)
83
+ {
84
+ var parameters = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
85
+ if (parameters != null && parameters.TryGetValue(key, out var valueObj) && valueObj is bool value)
86
+ return value;
87
+
88
+ return defaultValue;
89
+ }
90
+
91
+ private static void SaveDirtyScenesIfRequested(bool saveDirtyScenes, bool discardUntitled)
92
+ {
93
+ if (!saveDirtyScenes)
94
+ return;
95
+
96
+ var requiresUntitledDiscard = false;
97
+
98
+ for (var index = 0; index < SceneManager.sceneCount; index++)
99
+ {
100
+ var scene = SceneManager.GetSceneAt(index);
101
+ if (!scene.isLoaded || !scene.isDirty)
102
+ continue;
103
+
104
+ if (string.IsNullOrEmpty(scene.path))
105
+ {
106
+ if (!discardUntitled)
107
+ throw new System.InvalidOperationException("Dirty untitled scene cannot be auto-saved. Retry with discardUntitled=true.");
108
+
109
+ requiresUntitledDiscard = true;
110
+ continue;
111
+ }
112
+
113
+ if (!EditorSceneManager.SaveScene(scene))
114
+ throw new System.InvalidOperationException($"Failed to auto-save dirty scene: {scene.path}");
115
+
116
+ SceneChangeTracker.ClearScene(scene);
117
+ }
118
+
119
+ if (requiresUntitledDiscard)
120
+ EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
121
+ }
122
+
123
+ private static object HandleStop(string paramsJson)
124
+ {
125
+ if (!EditorApplication.isPlaying)
126
+ return new { status = "already_stopped" };
127
+
128
+ lock (s_sessionLock)
129
+ {
130
+ s_lastStopRequestedAtUtc = DateTime.UtcNow;
131
+ }
132
+ EditorApplication.isPlaying = false;
133
+ return new { status = "ok" };
134
+ }
135
+
136
+ private static object HandlePause(string paramsJson)
137
+ {
138
+ EditorApplication.isPaused = !EditorApplication.isPaused;
139
+ return new { status = "ok", paused = EditorApplication.isPaused };
140
+ }
141
+
142
+ private static void OnPlayModeStateChanged(PlayModeStateChange state)
143
+ {
144
+ lock (s_sessionLock)
145
+ {
146
+ switch (state)
147
+ {
148
+ case PlayModeStateChange.EnteredPlayMode:
149
+ s_lastEnteredPlayAtUtc = DateTime.UtcNow;
150
+ s_lastExitedPlayAtUtc = null;
151
+ break;
152
+ case PlayModeStateChange.EnteredEditMode:
153
+ s_lastExitedPlayAtUtc = DateTime.UtcNow;
154
+ break;
155
+ }
156
+ }
157
+ }
158
+
159
+ private static object SerializeSessionSnapshot(SessionSnapshot snapshot)
160
+ {
161
+ var now = DateTime.UtcNow;
162
+ var result = new Dictionary<string, object>
163
+ {
164
+ ["playing"] = snapshot.Playing,
165
+ ["paused"] = snapshot.Paused,
166
+ ["willChange"] = snapshot.WillChange,
167
+ ["compiling"] = snapshot.Compiling
168
+ };
169
+
170
+ if (snapshot.LastPlayRequestedAtUtc.HasValue)
171
+ result["lastPlayRequestedAt"] = snapshot.LastPlayRequestedAtUtc.Value.ToString("o");
172
+ if (snapshot.LastEnteredPlayAtUtc.HasValue)
173
+ result["lastEnteredPlayAt"] = snapshot.LastEnteredPlayAtUtc.Value.ToString("o");
174
+ if (snapshot.LastStopRequestedAtUtc.HasValue)
175
+ result["lastStopRequestedAt"] = snapshot.LastStopRequestedAtUtc.Value.ToString("o");
176
+ if (snapshot.LastExitedPlayAtUtc.HasValue)
177
+ result["lastExitedPlayAt"] = snapshot.LastExitedPlayAtUtc.Value.ToString("o");
178
+
179
+ if (snapshot.Playing && snapshot.LastEnteredPlayAtUtc.HasValue)
180
+ result["currentPlayDurationSeconds"] = Math.Max(0d, (now - snapshot.LastEnteredPlayAtUtc.Value).TotalSeconds);
181
+
182
+ if (snapshot.LastEnteredPlayAtUtc.HasValue && snapshot.LastExitedPlayAtUtc.HasValue)
183
+ result["lastPlayDurationSeconds"] = Math.Max(0d, (snapshot.LastExitedPlayAtUtc.Value - snapshot.LastEnteredPlayAtUtc.Value).TotalSeconds);
184
+
185
+ return result;
186
+ }
187
+ }
188
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: 647b22516c825e84da0f1815a6e0fa30