@mflrevan/ucp 0.5.1 → 0.5.2
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/Editor/Bridge/BridgeServer.cs +7 -3
- package/bridge/com.ucp.bridge/Editor/Controllers/AssetController.cs +170 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/CompilationController.cs +88 -1
- package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs +325 -13
- package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs +13 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ShaderController.cs +151 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ShaderController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs +303 -8
- package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs +72 -0
- package/bridge/com.ucp.bridge/package.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @mflrevan/ucp
|
|
2
2
|
|
|
3
|
-
Version `0.5.
|
|
3
|
+
Version `0.5.2` of the Unity Control Protocol CLI.
|
|
4
4
|
|
|
5
5
|
This package installs the `ucp` command, downloads the matching published binary for your platform during `postinstall`, and ships the matching Unity bridge payload inside the npm package.
|
|
6
6
|
|
|
@@ -26,7 +26,7 @@ namespace UCP.Bridge
|
|
|
26
26
|
private const int DefaultPort = 21342;
|
|
27
27
|
private const int MaxPort = 21352;
|
|
28
28
|
private const int MaxConnections = 4;
|
|
29
|
-
private const string ProtocolVersion = "0.5.
|
|
29
|
+
private const string ProtocolVersion = "0.5.2";
|
|
30
30
|
|
|
31
31
|
private static TcpListener s_listener;
|
|
32
32
|
private static CancellationTokenSource s_cts;
|
|
@@ -72,11 +72,12 @@ namespace UCP.Bridge
|
|
|
72
72
|
try
|
|
73
73
|
{
|
|
74
74
|
RegisterHandlers();
|
|
75
|
+
LogsController.SeedHistoryFromConsole();
|
|
75
76
|
|
|
76
77
|
EditorApplication.update += PumpMainThread;
|
|
77
78
|
EditorApplication.quitting += Shutdown;
|
|
78
79
|
AssemblyReloadEvents.beforeAssemblyReload += Shutdown;
|
|
79
|
-
Application.
|
|
80
|
+
Application.logMessageReceivedThreaded += OnLogMessage;
|
|
80
81
|
|
|
81
82
|
s_token = Guid.NewGuid().ToString("N").Substring(0, 16);
|
|
82
83
|
StartServer();
|
|
@@ -165,6 +166,9 @@ namespace UCP.Bridge
|
|
|
165
166
|
|
|
166
167
|
// Reference search (bridge fallback)
|
|
167
168
|
ReferenceController.Register(s_router);
|
|
169
|
+
|
|
170
|
+
// Shader diagnostics
|
|
171
|
+
ShaderController.Register(s_router);
|
|
168
172
|
}
|
|
169
173
|
|
|
170
174
|
private static void StartServer()
|
|
@@ -555,7 +559,7 @@ namespace UCP.Bridge
|
|
|
555
559
|
CleanLockFile();
|
|
556
560
|
|
|
557
561
|
EditorApplication.update -= PumpMainThread;
|
|
558
|
-
Application.
|
|
562
|
+
Application.logMessageReceivedThreaded -= OnLogMessage;
|
|
559
563
|
}
|
|
560
564
|
|
|
561
565
|
private static Dictionary<string, object> ResponseToDict(JsonRpcResponse r)
|
|
@@ -5,6 +5,7 @@ using System.Reflection;
|
|
|
5
5
|
using System.Text.RegularExpressions;
|
|
6
6
|
using UnityEditor;
|
|
7
7
|
using UnityEngine;
|
|
8
|
+
using UnityEngine.Rendering;
|
|
8
9
|
|
|
9
10
|
namespace UCP.Bridge
|
|
10
11
|
{
|
|
@@ -14,6 +15,7 @@ namespace UCP.Bridge
|
|
|
14
15
|
{
|
|
15
16
|
router.Register("asset/search", HandleSearch);
|
|
16
17
|
router.Register("asset/info", HandleInfo);
|
|
18
|
+
router.Register("asset/inspect", HandleInspect);
|
|
17
19
|
router.Register("asset/read", HandleReadScriptableObject);
|
|
18
20
|
router.Register("asset/write", HandleWriteScriptableObject);
|
|
19
21
|
router.Register("asset/write-batch", HandleWriteScriptableObjectBatch);
|
|
@@ -152,6 +154,47 @@ namespace UCP.Bridge
|
|
|
152
154
|
return info;
|
|
153
155
|
}
|
|
154
156
|
|
|
157
|
+
private static object HandleInspect(string paramsJson)
|
|
158
|
+
{
|
|
159
|
+
var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
|
|
160
|
+
if (p == null || !p.TryGetValue("path", out var pathObj))
|
|
161
|
+
throw new ArgumentException("Missing 'path' parameter");
|
|
162
|
+
|
|
163
|
+
string assetPath = pathObj.ToString();
|
|
164
|
+
int maxFields = 80;
|
|
165
|
+
if (p.TryGetValue("maxFields", out var maxObj) && maxObj != null)
|
|
166
|
+
maxFields = Math.Max(1, Convert.ToInt32(maxObj));
|
|
167
|
+
|
|
168
|
+
var asset = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(assetPath);
|
|
169
|
+
if (asset == null)
|
|
170
|
+
throw new ArgumentException($"Asset not found: {assetPath}");
|
|
171
|
+
|
|
172
|
+
var result = HandleInfo(paramsJson) as Dictionary<string, object>;
|
|
173
|
+
result["inspectedAtUtc"] = DateTime.UtcNow.ToString("o");
|
|
174
|
+
|
|
175
|
+
var importer = AssetImporter.GetAtPath(assetPath);
|
|
176
|
+
if (importer != null)
|
|
177
|
+
result["importer"] = InspectSerializedObject(importer, maxFields);
|
|
178
|
+
|
|
179
|
+
if (asset is Material material)
|
|
180
|
+
{
|
|
181
|
+
result["shader"] = material.shader != null ? material.shader.name : string.Empty;
|
|
182
|
+
result["shaderPath"] = material.shader != null ? AssetDatabase.GetAssetPath(material.shader) : string.Empty;
|
|
183
|
+
result["keywords"] = InspectMaterialKeywords(material);
|
|
184
|
+
result["properties"] = InspectMaterialProperties(material, maxFields);
|
|
185
|
+
}
|
|
186
|
+
else if (asset is GameObject gameObject)
|
|
187
|
+
{
|
|
188
|
+
result["renderers"] = InspectPrefabRenderers(gameObject);
|
|
189
|
+
}
|
|
190
|
+
else
|
|
191
|
+
{
|
|
192
|
+
result["fields"] = InspectSerializedObject(asset, maxFields);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
|
|
155
198
|
private static object HandleReadScriptableObject(string paramsJson)
|
|
156
199
|
{
|
|
157
200
|
var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
|
|
@@ -211,6 +254,133 @@ namespace UCP.Bridge
|
|
|
211
254
|
};
|
|
212
255
|
}
|
|
213
256
|
|
|
257
|
+
private static List<object> InspectSerializedObject(UnityEngine.Object target, int maxFields)
|
|
258
|
+
{
|
|
259
|
+
var serializedObject = new SerializedObject(target);
|
|
260
|
+
var fields = new List<object>();
|
|
261
|
+
try
|
|
262
|
+
{
|
|
263
|
+
var iterator = serializedObject.GetIterator();
|
|
264
|
+
if (iterator.NextVisible(true))
|
|
265
|
+
{
|
|
266
|
+
do
|
|
267
|
+
{
|
|
268
|
+
if (iterator.name == "m_Script") continue;
|
|
269
|
+
fields.Add(SerializedPropertyControllerSupport.Describe(iterator));
|
|
270
|
+
}
|
|
271
|
+
while (fields.Count < maxFields && iterator.NextVisible(false));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
finally
|
|
275
|
+
{
|
|
276
|
+
serializedObject.Dispose();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return fields;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private static List<object> InspectMaterialKeywords(Material material)
|
|
283
|
+
{
|
|
284
|
+
var keywords = new List<object>();
|
|
285
|
+
foreach (var keyword in material.enabledKeywords)
|
|
286
|
+
keywords.Add(keyword.name);
|
|
287
|
+
return keywords;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private static List<object> InspectMaterialProperties(Material material, int maxFields)
|
|
291
|
+
{
|
|
292
|
+
var properties = new List<object>();
|
|
293
|
+
var shader = material.shader;
|
|
294
|
+
if (shader == null)
|
|
295
|
+
return properties;
|
|
296
|
+
|
|
297
|
+
var count = Math.Min(shader.GetPropertyCount(), maxFields);
|
|
298
|
+
for (int i = 0; i < count; i++)
|
|
299
|
+
{
|
|
300
|
+
var name = shader.GetPropertyName(i);
|
|
301
|
+
var type = shader.GetPropertyType(i);
|
|
302
|
+
properties.Add(new Dictionary<string, object>
|
|
303
|
+
{
|
|
304
|
+
["name"] = name,
|
|
305
|
+
["description"] = shader.GetPropertyDescription(i),
|
|
306
|
+
["type"] = type.ToString(),
|
|
307
|
+
["value"] = ReadMaterialValue(material, name, type)
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return properties;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private static object ReadMaterialValue(Material material, string name, ShaderPropertyType type)
|
|
315
|
+
{
|
|
316
|
+
switch (type)
|
|
317
|
+
{
|
|
318
|
+
case ShaderPropertyType.Color:
|
|
319
|
+
var color = material.GetColor(name);
|
|
320
|
+
return new List<object> { color.r, color.g, color.b, color.a };
|
|
321
|
+
case ShaderPropertyType.Vector:
|
|
322
|
+
var vector = material.GetVector(name);
|
|
323
|
+
return new List<object> { vector.x, vector.y, vector.z, vector.w };
|
|
324
|
+
case ShaderPropertyType.Float:
|
|
325
|
+
case ShaderPropertyType.Range:
|
|
326
|
+
return material.GetFloat(name);
|
|
327
|
+
case ShaderPropertyType.Texture:
|
|
328
|
+
var texture = material.GetTexture(name);
|
|
329
|
+
return texture != null
|
|
330
|
+
? new Dictionary<string, object>
|
|
331
|
+
{
|
|
332
|
+
["name"] = texture.name,
|
|
333
|
+
["path"] = AssetDatabase.GetAssetPath(texture),
|
|
334
|
+
["type"] = texture.GetType().Name
|
|
335
|
+
}
|
|
336
|
+
: null;
|
|
337
|
+
default:
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private static List<object> InspectPrefabRenderers(GameObject gameObject)
|
|
343
|
+
{
|
|
344
|
+
var renderers = new List<object>();
|
|
345
|
+
foreach (var renderer in gameObject.GetComponentsInChildren<Renderer>(true))
|
|
346
|
+
{
|
|
347
|
+
var materials = new List<object>();
|
|
348
|
+
foreach (var material in renderer.sharedMaterials)
|
|
349
|
+
{
|
|
350
|
+
materials.Add(material != null
|
|
351
|
+
? new Dictionary<string, object>
|
|
352
|
+
{
|
|
353
|
+
["name"] = material.name,
|
|
354
|
+
["path"] = AssetDatabase.GetAssetPath(material),
|
|
355
|
+
["shader"] = material.shader != null ? material.shader.name : string.Empty
|
|
356
|
+
}
|
|
357
|
+
: null);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
renderers.Add(new Dictionary<string, object>
|
|
361
|
+
{
|
|
362
|
+
["path"] = GetTransformPath(renderer.transform),
|
|
363
|
+
["type"] = renderer.GetType().Name,
|
|
364
|
+
["enabled"] = renderer.enabled,
|
|
365
|
+
["materials"] = materials
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return renderers;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
private static string GetTransformPath(Transform transform)
|
|
373
|
+
{
|
|
374
|
+
var names = new List<string>();
|
|
375
|
+
var current = transform;
|
|
376
|
+
while (current != null)
|
|
377
|
+
{
|
|
378
|
+
names.Insert(0, current.name);
|
|
379
|
+
current = current.parent;
|
|
380
|
+
}
|
|
381
|
+
return string.Join("/", names);
|
|
382
|
+
}
|
|
383
|
+
|
|
214
384
|
private static object HandleWriteScriptableObject(string paramsJson)
|
|
215
385
|
{
|
|
216
386
|
var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
using UnityEditor;
|
|
2
2
|
using UnityEditor.Compilation;
|
|
3
|
+
using System;
|
|
4
|
+
using System.Collections.Generic;
|
|
5
|
+
using System.IO;
|
|
6
|
+
using System.Text.RegularExpressions;
|
|
3
7
|
|
|
4
8
|
namespace UCP.Bridge
|
|
5
9
|
{
|
|
@@ -9,12 +13,15 @@ namespace UCP.Bridge
|
|
|
9
13
|
{
|
|
10
14
|
router.Register("compile", HandleCompile);
|
|
11
15
|
router.Register("refresh-assets", HandleRefresh);
|
|
16
|
+
router.Register("script/doctor", HandleScriptDoctor);
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
private static object HandleCompile(string paramsJson)
|
|
15
20
|
{
|
|
21
|
+
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
|
|
16
22
|
CompilationPipeline.RequestScriptCompilation();
|
|
17
|
-
|
|
23
|
+
TrySyncSolution();
|
|
24
|
+
return new { status = "ok", message = "Asset database refreshed and compilation requested" };
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
private static object HandleRefresh(string paramsJson)
|
|
@@ -22,5 +29,85 @@ namespace UCP.Bridge
|
|
|
22
29
|
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
|
|
23
30
|
return new { status = "ok", message = "Asset database refreshed" };
|
|
24
31
|
}
|
|
32
|
+
|
|
33
|
+
private static object HandleScriptDoctor(string paramsJson)
|
|
34
|
+
{
|
|
35
|
+
var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
|
|
36
|
+
var fix = p != null && p.TryGetValue("fix", out var fixObj) && fixObj != null && Convert.ToBoolean(fixObj);
|
|
37
|
+
var projectRoot = Directory.GetParent(UnityEngine.Application.dataPath).FullName;
|
|
38
|
+
var projects = new List<object>();
|
|
39
|
+
var staleProjectCount = 0;
|
|
40
|
+
var missingFileCount = 0;
|
|
41
|
+
var deletedProjectCount = 0;
|
|
42
|
+
|
|
43
|
+
foreach (var csproj in Directory.GetFiles(projectRoot, "*.csproj", SearchOption.TopDirectoryOnly))
|
|
44
|
+
{
|
|
45
|
+
var missing = FindMissingCompileItems(projectRoot, csproj);
|
|
46
|
+
if (missing.Count > 0)
|
|
47
|
+
{
|
|
48
|
+
staleProjectCount++;
|
|
49
|
+
missingFileCount += missing.Count;
|
|
50
|
+
if (fix)
|
|
51
|
+
{
|
|
52
|
+
File.Delete(csproj);
|
|
53
|
+
deletedProjectCount++;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
projects.Add(new Dictionary<string, object>
|
|
58
|
+
{
|
|
59
|
+
["path"] = csproj,
|
|
60
|
+
["missingCompileItems"] = missing.ConvertAll<object>(item => item),
|
|
61
|
+
["stale"] = missing.Count > 0
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (fix)
|
|
66
|
+
{
|
|
67
|
+
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
|
|
68
|
+
TrySyncSolution();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return new Dictionary<string, object>
|
|
72
|
+
{
|
|
73
|
+
["status"] = "ok",
|
|
74
|
+
["projectRoot"] = projectRoot,
|
|
75
|
+
["projectCount"] = projects.Count,
|
|
76
|
+
["staleProjectCount"] = staleProjectCount,
|
|
77
|
+
["missingFileCount"] = missingFileCount,
|
|
78
|
+
["deletedProjectCount"] = deletedProjectCount,
|
|
79
|
+
["fixed"] = fix,
|
|
80
|
+
["projects"] = projects
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private static List<string> FindMissingCompileItems(string projectRoot, string csproj)
|
|
85
|
+
{
|
|
86
|
+
var missing = new List<string>();
|
|
87
|
+
var content = File.ReadAllText(csproj);
|
|
88
|
+
foreach (Match match in Regex.Matches(content, "<Compile Include=\"([^\"]+\\.cs)\""))
|
|
89
|
+
{
|
|
90
|
+
var include = match.Groups[1].Value.Replace('\\', Path.DirectorySeparatorChar);
|
|
91
|
+
var fullPath = Path.GetFullPath(Path.Combine(projectRoot, include));
|
|
92
|
+
if (!File.Exists(fullPath))
|
|
93
|
+
missing.Add(include.Replace('\\', '/'));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return missing;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private static void TrySyncSolution()
|
|
100
|
+
{
|
|
101
|
+
try
|
|
102
|
+
{
|
|
103
|
+
var syncVs = typeof(Editor).Assembly.GetType("UnityEditor.SyncVS");
|
|
104
|
+
var method = syncVs?.GetMethod("SyncSolution", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic, null, Type.EmptyTypes, null);
|
|
105
|
+
method?.Invoke(null, null);
|
|
106
|
+
}
|
|
107
|
+
catch
|
|
108
|
+
{
|
|
109
|
+
// Best-effort only; Unity may regenerate solution files asynchronously.
|
|
110
|
+
}
|
|
111
|
+
}
|
|
25
112
|
}
|
|
26
113
|
}
|