@mflrevan/ucp 0.4.3 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bridge/com.ucp.bridge/CHANGELOG.md +145 -0
- package/bridge/com.ucp.bridge/CHANGELOG.md.meta +7 -0
- package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs +583 -0
- package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Bridge.meta +8 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/AssetController.cs +425 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/AssetController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/AssetImportSupport.cs +355 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/AssetImportSupport.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/BuildController.cs +233 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/BuildController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/CompilationController.cs +26 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/CompilationController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorController.cs +31 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorSettingsController.cs +527 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorSettingsController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/FileController.cs +141 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/FileController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs +326 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ImporterController.cs +209 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ImporterController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs +409 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs +354 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ObjectReferenceResolver.cs +93 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ObjectReferenceResolver.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PackagesController.cs +503 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PackagesController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs +188 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs +260 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ProfilerController.cs +1679 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ProfilerController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs +563 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneChangeTracker.cs +166 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneChangeTracker.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs +318 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ScreenshotController.cs +125 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ScreenshotController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ScriptController.cs +104 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ScriptController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs +227 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs +240 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/VcsController.cs +611 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/VcsController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers.meta +8 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/CommandRouter.cs +53 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/CommandRouter.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/MessageTypes.cs +80 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/MessageTypes.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs +358 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Protocol.meta +8 -0
- package/bridge/com.ucp.bridge/Editor/Scripts/IUCPScript.cs +37 -0
- package/bridge/com.ucp.bridge/Editor/Scripts/IUCPScript.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Scripts.meta +8 -0
- package/bridge/com.ucp.bridge/Editor/UCP.Bridge.Editor.asmdef +16 -0
- package/bridge/com.ucp.bridge/Editor/UCP.Bridge.Editor.asmdef.meta +7 -0
- package/bridge/com.ucp.bridge/Editor.meta +8 -0
- package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef +14 -0
- package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef.meta +7 -0
- package/bridge/com.ucp.bridge/Runtime.meta +8 -0
- package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs +1085 -0
- package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Tests/Editor/UCP.Bridge.Editor.Tests.asmdef +12 -0
- package/bridge/com.ucp.bridge/Tests/Editor/UCP.Bridge.Editor.Tests.asmdef.meta +7 -0
- package/bridge/com.ucp.bridge/Tests/Editor.meta +8 -0
- package/bridge/com.ucp.bridge/Tests.meta +8 -0
- package/bridge/com.ucp.bridge/package.json +27 -0
- package/bridge/com.ucp.bridge/package.json.meta +7 -0
- package/package.json +2 -2
- 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,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
|
+
}
|