@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @mflrevan/ucp
2
2
 
3
- Version `0.5.1` of the Unity Control Protocol CLI.
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.1";
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.logMessageReceived += OnLogMessage;
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.logMessageReceived -= OnLogMessage;
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
- return new { status = "ok", message = "Compilation requested" };
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
  }