@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
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
using System;
|
|
2
2
|
using System.Collections.Generic;
|
|
3
|
+
using System.IO;
|
|
3
4
|
using System.Linq;
|
|
5
|
+
using System.Reflection;
|
|
4
6
|
using System.Text.RegularExpressions;
|
|
7
|
+
using UnityEditor;
|
|
5
8
|
using UnityEngine;
|
|
6
9
|
|
|
7
10
|
namespace UCP.Bridge
|
|
@@ -14,7 +17,10 @@ namespace UCP.Bridge
|
|
|
14
17
|
|
|
15
18
|
private static readonly object s_historyLock = new object();
|
|
16
19
|
private static readonly List<LogRecord> s_history = new List<LogRecord>();
|
|
20
|
+
private static Func<List<ConsoleBackfillEntry>> s_consoleBackfillProvider = CaptureRecentConsoleEntries;
|
|
17
21
|
private static long s_nextId = 1;
|
|
22
|
+
private static StreamWriter s_captureWriter;
|
|
23
|
+
private static string s_capturePath;
|
|
18
24
|
|
|
19
25
|
public static void Register(CommandRouter router)
|
|
20
26
|
{
|
|
@@ -24,6 +30,8 @@ namespace UCP.Bridge
|
|
|
24
30
|
router.Register("logs/search", HandleSearch);
|
|
25
31
|
router.Register("logs/get", HandleGet);
|
|
26
32
|
router.Register("logs/status", HandleStatus);
|
|
33
|
+
router.Register("logs/capture/start", HandleCaptureStart);
|
|
34
|
+
router.Register("logs/capture/stop", HandleCaptureStop);
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
public static Dictionary<string, object> RecordLog(string message, string stackTrace, LogType type)
|
|
@@ -37,6 +45,7 @@ namespace UCP.Bridge
|
|
|
37
45
|
{
|
|
38
46
|
s_history.Clear();
|
|
39
47
|
s_nextId = 1;
|
|
48
|
+
s_consoleBackfillProvider = CaptureRecentConsoleEntries;
|
|
40
49
|
}
|
|
41
50
|
}
|
|
42
51
|
|
|
@@ -45,8 +54,64 @@ namespace UCP.Bridge
|
|
|
45
54
|
return RecordLog(level, message, stackTrace);
|
|
46
55
|
}
|
|
47
56
|
|
|
57
|
+
public static void SetConsoleBackfillProviderForTests(Func<List<ConsoleBackfillEntry>> provider)
|
|
58
|
+
{
|
|
59
|
+
lock (s_historyLock)
|
|
60
|
+
{
|
|
61
|
+
s_consoleBackfillProvider = provider ?? CaptureRecentConsoleEntries;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public static void SeedHistoryFromConsole()
|
|
66
|
+
{
|
|
67
|
+
lock (s_historyLock)
|
|
68
|
+
{
|
|
69
|
+
SeedHistoryFromConsoleLocked();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public static Dictionary<string, object> StartFileCapture(string path)
|
|
74
|
+
{
|
|
75
|
+
lock (s_historyLock)
|
|
76
|
+
{
|
|
77
|
+
StopFileCaptureLocked();
|
|
78
|
+
|
|
79
|
+
var resolvedPath = ResolveCapturePath(path);
|
|
80
|
+
var directory = Path.GetDirectoryName(resolvedPath);
|
|
81
|
+
if (!string.IsNullOrEmpty(directory))
|
|
82
|
+
Directory.CreateDirectory(directory);
|
|
83
|
+
|
|
84
|
+
s_captureWriter = new StreamWriter(resolvedPath, false);
|
|
85
|
+
s_captureWriter.AutoFlush = true;
|
|
86
|
+
s_capturePath = resolvedPath;
|
|
87
|
+
s_captureWriter.WriteLine($"# UCP play-mode log capture started {DateTime.UtcNow:o}");
|
|
88
|
+
|
|
89
|
+
return new Dictionary<string, object>
|
|
90
|
+
{
|
|
91
|
+
["status"] = "ok",
|
|
92
|
+
["path"] = resolvedPath
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public static Dictionary<string, object> StopFileCapture()
|
|
98
|
+
{
|
|
99
|
+
lock (s_historyLock)
|
|
100
|
+
{
|
|
101
|
+
var path = s_capturePath;
|
|
102
|
+
StopFileCaptureLocked();
|
|
103
|
+
return new Dictionary<string, object>
|
|
104
|
+
{
|
|
105
|
+
["status"] = "ok",
|
|
106
|
+
["path"] = path ?? string.Empty,
|
|
107
|
+
["active"] = false
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
48
112
|
public static long GetLatestId()
|
|
49
113
|
{
|
|
114
|
+
SeedHistoryFromConsole();
|
|
50
115
|
lock (s_historyLock)
|
|
51
116
|
{
|
|
52
117
|
return s_history.Count == 0 ? 0 : s_history[s_history.Count - 1].Id;
|
|
@@ -55,6 +120,7 @@ namespace UCP.Bridge
|
|
|
55
120
|
|
|
56
121
|
public static Dictionary<string, object> BuildStatusSummary(long afterId = 0)
|
|
57
122
|
{
|
|
123
|
+
SeedHistoryFromConsole();
|
|
58
124
|
lock (s_historyLock)
|
|
59
125
|
{
|
|
60
126
|
var ordered = s_history
|
|
@@ -86,6 +152,7 @@ namespace UCP.Bridge
|
|
|
86
152
|
|
|
87
153
|
long id = Convert.ToInt64(idObj);
|
|
88
154
|
|
|
155
|
+
SeedHistoryFromConsole();
|
|
89
156
|
lock (s_historyLock)
|
|
90
157
|
{
|
|
91
158
|
var entry = s_history.FirstOrDefault(record => record.Id == id);
|
|
@@ -106,28 +173,74 @@ namespace UCP.Bridge
|
|
|
106
173
|
return BuildStatusSummary(afterId);
|
|
107
174
|
}
|
|
108
175
|
|
|
176
|
+
private static object HandleCaptureStart(string paramsJson)
|
|
177
|
+
{
|
|
178
|
+
var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
|
|
179
|
+
if (p == null || !p.TryGetValue("path", out var pathObj) || pathObj == null)
|
|
180
|
+
throw new ArgumentException("Missing 'path' parameter");
|
|
181
|
+
|
|
182
|
+
return StartFileCapture(pathObj.ToString());
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private static object HandleCaptureStop(string paramsJson)
|
|
186
|
+
{
|
|
187
|
+
return StopFileCapture();
|
|
188
|
+
}
|
|
189
|
+
|
|
109
190
|
private static Dictionary<string, object> RecordLog(string level, string message, string stackTrace)
|
|
110
191
|
{
|
|
111
192
|
lock (s_historyLock)
|
|
112
193
|
{
|
|
113
|
-
var entry =
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
};
|
|
121
|
-
entry.Timestamp = entry.TimestampUtc.ToString("o");
|
|
122
|
-
|
|
123
|
-
s_history.Add(entry);
|
|
124
|
-
if (s_history.Count > MaxHistoryEntries)
|
|
125
|
-
s_history.RemoveAt(0);
|
|
194
|
+
var entry = AppendRecordLocked(
|
|
195
|
+
NormalizeLevel(level),
|
|
196
|
+
message ?? string.Empty,
|
|
197
|
+
stackTrace ?? string.Empty,
|
|
198
|
+
DateTime.UtcNow
|
|
199
|
+
);
|
|
200
|
+
WriteCaptureLineLocked(entry);
|
|
126
201
|
|
|
127
202
|
return SerializeFull(entry);
|
|
128
203
|
}
|
|
129
204
|
}
|
|
130
205
|
|
|
206
|
+
private static void WriteCaptureLineLocked(LogRecord entry)
|
|
207
|
+
{
|
|
208
|
+
if (s_captureWriter == null)
|
|
209
|
+
return;
|
|
210
|
+
|
|
211
|
+
var message = (entry.Message ?? string.Empty).Replace("\r", "\\r").Replace("\n", "\\n");
|
|
212
|
+
s_captureWriter.WriteLine($"{entry.Timestamp} [{entry.Level}] {message}");
|
|
213
|
+
if (!string.IsNullOrEmpty(entry.StackTrace))
|
|
214
|
+
{
|
|
215
|
+
var stack = entry.StackTrace.Replace("\r", "\\r").Replace("\n", "\\n");
|
|
216
|
+
s_captureWriter.WriteLine($"{entry.Timestamp} [stack] {stack}");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private static void StopFileCaptureLocked()
|
|
221
|
+
{
|
|
222
|
+
if (s_captureWriter != null)
|
|
223
|
+
{
|
|
224
|
+
s_captureWriter.WriteLine($"# UCP log capture stopped {DateTime.UtcNow:o}");
|
|
225
|
+
s_captureWriter.Dispose();
|
|
226
|
+
s_captureWriter = null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
s_capturePath = null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private static string ResolveCapturePath(string path)
|
|
233
|
+
{
|
|
234
|
+
if (string.IsNullOrEmpty(path))
|
|
235
|
+
throw new ArgumentException("Log capture path cannot be empty");
|
|
236
|
+
|
|
237
|
+
if (Path.IsPathRooted(path))
|
|
238
|
+
return Path.GetFullPath(path);
|
|
239
|
+
|
|
240
|
+
var projectRoot = Path.GetDirectoryName(Application.dataPath);
|
|
241
|
+
return Path.GetFullPath(Path.Combine(projectRoot, path));
|
|
242
|
+
}
|
|
243
|
+
|
|
131
244
|
private static LogQuery ParseQuery(string paramsJson, bool includePattern)
|
|
132
245
|
{
|
|
133
246
|
var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
|
|
@@ -137,6 +250,8 @@ namespace UCP.Bridge
|
|
|
137
250
|
{
|
|
138
251
|
if (p.TryGetValue("level", out var levelObj) && levelObj != null)
|
|
139
252
|
query.Level = NormalizeLevel(levelObj.ToString());
|
|
253
|
+
if (p.TryGetValue("channel", out var channelObj) && channelObj != null)
|
|
254
|
+
query.Channel = channelObj.ToString();
|
|
140
255
|
if (p.TryGetValue("count", out var countObj) && countObj != null)
|
|
141
256
|
query.Count = Math.Max(1, Convert.ToInt32(countObj));
|
|
142
257
|
if (p.TryGetValue("beforeId", out var beforeObj) && beforeObj != null)
|
|
@@ -166,6 +281,7 @@ namespace UCP.Bridge
|
|
|
166
281
|
|
|
167
282
|
private static LogQueryResult QueryHistory(LogQuery query)
|
|
168
283
|
{
|
|
284
|
+
SeedHistoryFromConsole();
|
|
169
285
|
lock (s_historyLock)
|
|
170
286
|
{
|
|
171
287
|
IEnumerable<LogRecord> candidates = s_history;
|
|
@@ -176,6 +292,10 @@ namespace UCP.Bridge
|
|
|
176
292
|
candidates = candidates.Where(entry => entry.Id > query.AfterId.Value);
|
|
177
293
|
if (!string.IsNullOrEmpty(query.Level))
|
|
178
294
|
candidates = candidates.Where(entry => PassesLevel(entry.Level, query.Level));
|
|
295
|
+
if (!string.IsNullOrEmpty(query.Channel))
|
|
296
|
+
candidates = candidates.Where(entry =>
|
|
297
|
+
(entry.Message ?? string.Empty).IndexOf(query.Channel, StringComparison.OrdinalIgnoreCase) >= 0
|
|
298
|
+
|| (entry.StackTrace ?? string.Empty).IndexOf(query.Channel, StringComparison.OrdinalIgnoreCase) >= 0);
|
|
179
299
|
|
|
180
300
|
if (query.Regex != null)
|
|
181
301
|
{
|
|
@@ -361,6 +481,184 @@ namespace UCP.Bridge
|
|
|
361
481
|
}
|
|
362
482
|
}
|
|
363
483
|
|
|
484
|
+
private static void SeedHistoryFromConsoleLocked()
|
|
485
|
+
{
|
|
486
|
+
if (s_history.Count > 0)
|
|
487
|
+
return;
|
|
488
|
+
|
|
489
|
+
List<ConsoleBackfillEntry> consoleEntries;
|
|
490
|
+
try
|
|
491
|
+
{
|
|
492
|
+
consoleEntries = s_consoleBackfillProvider != null
|
|
493
|
+
? s_consoleBackfillProvider.Invoke()
|
|
494
|
+
: new List<ConsoleBackfillEntry>();
|
|
495
|
+
}
|
|
496
|
+
catch
|
|
497
|
+
{
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (consoleEntries == null || consoleEntries.Count == 0)
|
|
502
|
+
return;
|
|
503
|
+
|
|
504
|
+
var baseTimestamp = DateTime.UtcNow;
|
|
505
|
+
for (var index = 0; index < consoleEntries.Count; index++)
|
|
506
|
+
{
|
|
507
|
+
var entry = consoleEntries[index];
|
|
508
|
+
if (entry == null || string.IsNullOrEmpty(entry.Message) || entry.Message.StartsWith("[UCP]", StringComparison.Ordinal))
|
|
509
|
+
continue;
|
|
510
|
+
|
|
511
|
+
AppendRecordLocked(
|
|
512
|
+
entry.Level,
|
|
513
|
+
entry.Message,
|
|
514
|
+
entry.StackTrace,
|
|
515
|
+
baseTimestamp.AddMilliseconds(index)
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
private static LogRecord AppendRecordLocked(string level, string message, string stackTrace, DateTime timestampUtc)
|
|
521
|
+
{
|
|
522
|
+
var entry = new LogRecord
|
|
523
|
+
{
|
|
524
|
+
Id = s_nextId++,
|
|
525
|
+
Level = NormalizeLevel(level),
|
|
526
|
+
Message = message ?? string.Empty,
|
|
527
|
+
StackTrace = stackTrace ?? string.Empty,
|
|
528
|
+
TimestampUtc = timestampUtc
|
|
529
|
+
};
|
|
530
|
+
entry.Timestamp = entry.TimestampUtc.ToString("o");
|
|
531
|
+
|
|
532
|
+
s_history.Add(entry);
|
|
533
|
+
if (s_history.Count > MaxHistoryEntries)
|
|
534
|
+
s_history.RemoveAt(0);
|
|
535
|
+
|
|
536
|
+
return entry;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
private static List<ConsoleBackfillEntry> CaptureRecentConsoleEntries()
|
|
540
|
+
{
|
|
541
|
+
try
|
|
542
|
+
{
|
|
543
|
+
var assembly = typeof(Editor).Assembly;
|
|
544
|
+
var logEntriesType = assembly.GetType("UnityEditor.LogEntries");
|
|
545
|
+
var logEntryType = assembly.GetType("UnityEditor.LogEntry");
|
|
546
|
+
if (logEntriesType == null || logEntryType == null)
|
|
547
|
+
return new List<ConsoleBackfillEntry>();
|
|
548
|
+
|
|
549
|
+
var getCount = logEntriesType.GetMethod("GetCount", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
|
|
550
|
+
var getEntry = logEntriesType
|
|
551
|
+
.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)
|
|
552
|
+
.FirstOrDefault(method => method.Name == "GetEntryInternal");
|
|
553
|
+
var messageField = logEntryType.GetField("message", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
554
|
+
var modeField = logEntryType.GetField("mode", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
555
|
+
if (getCount == null || getEntry == null || messageField == null || modeField == null)
|
|
556
|
+
return new List<ConsoleBackfillEntry>();
|
|
557
|
+
|
|
558
|
+
var count = Convert.ToInt32(getCount.Invoke(null, null));
|
|
559
|
+
if (count <= 0)
|
|
560
|
+
return new List<ConsoleBackfillEntry>();
|
|
561
|
+
|
|
562
|
+
var captured = new List<ConsoleBackfillEntry>();
|
|
563
|
+
var start = Math.Max(0, count - MaxHistoryEntries);
|
|
564
|
+
for (var index = start; index < count; index++)
|
|
565
|
+
{
|
|
566
|
+
var args = new object[] { index, Activator.CreateInstance(logEntryType) };
|
|
567
|
+
if (!Convert.ToBoolean(getEntry.Invoke(null, args)))
|
|
568
|
+
continue;
|
|
569
|
+
|
|
570
|
+
var entry = CreateConsoleBackfillEntry(
|
|
571
|
+
messageField.GetValue(args[1]) as string,
|
|
572
|
+
Convert.ToInt32(modeField.GetValue(args[1]))
|
|
573
|
+
);
|
|
574
|
+
if (entry != null)
|
|
575
|
+
captured.Add(entry);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return captured;
|
|
579
|
+
}
|
|
580
|
+
catch
|
|
581
|
+
{
|
|
582
|
+
return new List<ConsoleBackfillEntry>();
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
private static ConsoleBackfillEntry CreateConsoleBackfillEntry(string rawMessage, int mode)
|
|
587
|
+
{
|
|
588
|
+
var normalized = (rawMessage ?? string.Empty).Replace("\r\n", "\n").TrimEnd();
|
|
589
|
+
if (string.IsNullOrEmpty(normalized) || normalized.StartsWith("[UCP]", StringComparison.Ordinal))
|
|
590
|
+
return null;
|
|
591
|
+
|
|
592
|
+
var split = SplitConsoleMessage(normalized);
|
|
593
|
+
return new ConsoleBackfillEntry
|
|
594
|
+
{
|
|
595
|
+
Level = InferConsoleLevel(mode, split.Message, split.StackTrace),
|
|
596
|
+
Message = split.Message,
|
|
597
|
+
StackTrace = split.StackTrace
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
private static SplitLogMessage SplitConsoleMessage(string rawMessage)
|
|
602
|
+
{
|
|
603
|
+
var firstNewline = rawMessage.IndexOf('\n');
|
|
604
|
+
if (firstNewline < 0)
|
|
605
|
+
{
|
|
606
|
+
return new SplitLogMessage
|
|
607
|
+
{
|
|
608
|
+
Message = rawMessage,
|
|
609
|
+
StackTrace = string.Empty
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
var firstLine = rawMessage.Substring(0, firstNewline).TrimEnd();
|
|
614
|
+
var remainder = rawMessage.Substring(firstNewline + 1).Trim();
|
|
615
|
+
if (LooksLikeStackTrace(remainder))
|
|
616
|
+
{
|
|
617
|
+
return new SplitLogMessage
|
|
618
|
+
{
|
|
619
|
+
Message = firstLine,
|
|
620
|
+
StackTrace = remainder
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
return new SplitLogMessage
|
|
625
|
+
{
|
|
626
|
+
Message = rawMessage,
|
|
627
|
+
StackTrace = string.Empty
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
private static bool LooksLikeStackTrace(string value)
|
|
632
|
+
{
|
|
633
|
+
if (string.IsNullOrEmpty(value))
|
|
634
|
+
return false;
|
|
635
|
+
|
|
636
|
+
return value.StartsWith("UnityEngine.", StringComparison.Ordinal)
|
|
637
|
+
|| value.StartsWith("UnityEditor.", StringComparison.Ordinal)
|
|
638
|
+
|| value.StartsWith("System.", StringComparison.Ordinal)
|
|
639
|
+
|| value.StartsWith("---", StringComparison.Ordinal)
|
|
640
|
+
|| value.Contains(" (at ")
|
|
641
|
+
|| value.IndexOf(":Log", StringComparison.Ordinal) >= 0
|
|
642
|
+
|| value.IndexOf(":LogWarning", StringComparison.Ordinal) >= 0
|
|
643
|
+
|| value.IndexOf(":LogError", StringComparison.Ordinal) >= 0
|
|
644
|
+
|| value.IndexOf(":LogException", StringComparison.Ordinal) >= 0;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
private static string InferConsoleLevel(int mode, string message, string stackTrace)
|
|
648
|
+
{
|
|
649
|
+
var combined = (message ?? string.Empty) + "\n" + (stackTrace ?? string.Empty);
|
|
650
|
+
if ((mode & 0x100) != 0 || combined.IndexOf("Exception", StringComparison.OrdinalIgnoreCase) >= 0)
|
|
651
|
+
return combined.IndexOf("Exception", StringComparison.OrdinalIgnoreCase) >= 0 ? "exception" : "error";
|
|
652
|
+
if ((mode & 0x200) != 0)
|
|
653
|
+
return "warning";
|
|
654
|
+
if (Regex.IsMatch(combined, @"\bwarning\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))
|
|
655
|
+
return "warning";
|
|
656
|
+
if (Regex.IsMatch(combined, @"\berror\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))
|
|
657
|
+
return combined.IndexOf("Exception", StringComparison.OrdinalIgnoreCase) >= 0 ? "exception" : "error";
|
|
658
|
+
|
|
659
|
+
return "info";
|
|
660
|
+
}
|
|
661
|
+
|
|
364
662
|
private static string Preview(string value, int maxChars)
|
|
365
663
|
{
|
|
366
664
|
if (string.IsNullOrEmpty(value) || value.Length <= maxChars)
|
|
@@ -417,9 +715,23 @@ namespace UCP.Bridge
|
|
|
417
715
|
public DateTime TimestampUtc;
|
|
418
716
|
}
|
|
419
717
|
|
|
718
|
+
public sealed class ConsoleBackfillEntry
|
|
719
|
+
{
|
|
720
|
+
public string Level;
|
|
721
|
+
public string Message;
|
|
722
|
+
public string StackTrace;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
private sealed class SplitLogMessage
|
|
726
|
+
{
|
|
727
|
+
public string Message;
|
|
728
|
+
public string StackTrace;
|
|
729
|
+
}
|
|
730
|
+
|
|
420
731
|
private sealed class LogQuery
|
|
421
732
|
{
|
|
422
733
|
public string Level;
|
|
734
|
+
public string Channel;
|
|
423
735
|
public string Pattern;
|
|
424
736
|
public Regex Regex;
|
|
425
737
|
public int Count;
|
|
@@ -64,7 +64,10 @@ namespace UCP.Bridge
|
|
|
64
64
|
|
|
65
65
|
var saveDirtyScenes = GetBoolParam(paramsJson, "saveDirtyScenes", true);
|
|
66
66
|
var discardUntitled = GetBoolParam(paramsJson, "discardUntitled", true);
|
|
67
|
+
var logFile = GetStringParam(paramsJson, "logFile");
|
|
67
68
|
SaveDirtyScenesIfRequested(saveDirtyScenes, discardUntitled);
|
|
69
|
+
if (!string.IsNullOrEmpty(logFile))
|
|
70
|
+
LogsController.StartFileCapture(logFile);
|
|
68
71
|
|
|
69
72
|
lock (s_sessionLock)
|
|
70
73
|
{
|
|
@@ -151,11 +154,21 @@ namespace UCP.Bridge
|
|
|
151
154
|
break;
|
|
152
155
|
case PlayModeStateChange.EnteredEditMode:
|
|
153
156
|
s_lastExitedPlayAtUtc = DateTime.UtcNow;
|
|
157
|
+
LogsController.StopFileCapture();
|
|
154
158
|
break;
|
|
155
159
|
}
|
|
156
160
|
}
|
|
157
161
|
}
|
|
158
162
|
|
|
163
|
+
private static string GetStringParam(string paramsJson, string key)
|
|
164
|
+
{
|
|
165
|
+
var parameters = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
|
|
166
|
+
if (parameters != null && parameters.TryGetValue(key, out var valueObj) && valueObj != null)
|
|
167
|
+
return valueObj.ToString();
|
|
168
|
+
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
159
172
|
private static object SerializeSessionSnapshot(SessionSnapshot snapshot)
|
|
160
173
|
{
|
|
161
174
|
var now = DateTime.UtcNow;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections.Generic;
|
|
3
|
+
using System.Reflection;
|
|
4
|
+
using UnityEditor;
|
|
5
|
+
using UnityEngine;
|
|
6
|
+
|
|
7
|
+
namespace UCP.Bridge
|
|
8
|
+
{
|
|
9
|
+
public static class ShaderController
|
|
10
|
+
{
|
|
11
|
+
public static void Register(CommandRouter router)
|
|
12
|
+
{
|
|
13
|
+
router.Register("shader/errors", HandleErrors);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
private static object HandleErrors(string paramsJson)
|
|
17
|
+
{
|
|
18
|
+
var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
|
|
19
|
+
var errorsOnly = p != null && p.TryGetValue("errorsOnly", out var errorsOnlyObj) && errorsOnlyObj != null && Convert.ToBoolean(errorsOnlyObj);
|
|
20
|
+
var filter = p != null && p.TryGetValue("filter", out var filterObj) && filterObj != null ? filterObj.ToString() : null;
|
|
21
|
+
|
|
22
|
+
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
|
|
23
|
+
|
|
24
|
+
var diagnostics = new List<object>();
|
|
25
|
+
var scanned = 0;
|
|
26
|
+
foreach (var guid in AssetDatabase.FindAssets("t:Shader"))
|
|
27
|
+
{
|
|
28
|
+
var path = AssetDatabase.GUIDToAssetPath(guid);
|
|
29
|
+
var shader = AssetDatabase.LoadAssetAtPath<Shader>(path);
|
|
30
|
+
if (shader == null)
|
|
31
|
+
continue;
|
|
32
|
+
if (!MatchesFilter(shader.name, path, filter))
|
|
33
|
+
continue;
|
|
34
|
+
|
|
35
|
+
scanned++;
|
|
36
|
+
foreach (var diagnostic in ReadShaderDiagnostics(shader, path, errorsOnly))
|
|
37
|
+
diagnostics.Add(diagnostic);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return new Dictionary<string, object>
|
|
41
|
+
{
|
|
42
|
+
["status"] = "ok",
|
|
43
|
+
["capability"] = FindShaderMessageMethod() != null ? "shader-messages" : "fallback",
|
|
44
|
+
["scanned"] = scanned,
|
|
45
|
+
["count"] = diagnostics.Count,
|
|
46
|
+
["diagnostics"] = diagnostics
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private static IEnumerable<object> ReadShaderDiagnostics(Shader shader, string path, bool errorsOnly)
|
|
51
|
+
{
|
|
52
|
+
var method = FindShaderMessageMethod();
|
|
53
|
+
if (method == null)
|
|
54
|
+
yield break;
|
|
55
|
+
|
|
56
|
+
var messages = method.Invoke(null, new object[] { shader }) as Array;
|
|
57
|
+
if (messages == null)
|
|
58
|
+
yield break;
|
|
59
|
+
|
|
60
|
+
foreach (var message in messages)
|
|
61
|
+
{
|
|
62
|
+
var isWarning = ReadBoolMember(message, "warning", "isWarning");
|
|
63
|
+
if (errorsOnly && isWarning)
|
|
64
|
+
continue;
|
|
65
|
+
|
|
66
|
+
yield return new Dictionary<string, object>
|
|
67
|
+
{
|
|
68
|
+
["shader"] = shader.name,
|
|
69
|
+
["path"] = path,
|
|
70
|
+
["severity"] = isWarning ? "warning" : "error",
|
|
71
|
+
["message"] = ReadStringMember(message, "message", "messageDetails"),
|
|
72
|
+
["line"] = ReadIntMember(message, "line"),
|
|
73
|
+
["platform"] = ReadStringMember(message, "platform"),
|
|
74
|
+
["file"] = ReadStringMember(message, "file")
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private static MethodInfo FindShaderMessageMethod()
|
|
80
|
+
{
|
|
81
|
+
var shaderUtil = typeof(Editor).Assembly.GetType("UnityEditor.ShaderUtil");
|
|
82
|
+
return FindShaderMethod(shaderUtil, "GetShaderMessages")
|
|
83
|
+
?? FindShaderMethod(shaderUtil, "GetShaderErrors");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private static MethodInfo FindShaderMethod(Type shaderUtil, string name)
|
|
87
|
+
{
|
|
88
|
+
if (shaderUtil == null)
|
|
89
|
+
return null;
|
|
90
|
+
|
|
91
|
+
foreach (var method in shaderUtil.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
|
|
92
|
+
{
|
|
93
|
+
if (method.Name != name)
|
|
94
|
+
continue;
|
|
95
|
+
var parameters = method.GetParameters();
|
|
96
|
+
if (parameters.Length == 1 && parameters[0].ParameterType == typeof(Shader))
|
|
97
|
+
return method;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private static bool MatchesFilter(string shaderName, string path, string filter)
|
|
104
|
+
{
|
|
105
|
+
if (string.IsNullOrEmpty(filter))
|
|
106
|
+
return true;
|
|
107
|
+
return shaderName.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0
|
|
108
|
+
|| path.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private static string ReadStringMember(object target, params string[] names)
|
|
112
|
+
{
|
|
113
|
+
foreach (var name in names)
|
|
114
|
+
{
|
|
115
|
+
var value = ReadMember(target, name);
|
|
116
|
+
if (value != null)
|
|
117
|
+
return value.ToString();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return string.Empty;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private static int ReadIntMember(object target, string name)
|
|
124
|
+
{
|
|
125
|
+
var value = ReadMember(target, name);
|
|
126
|
+
return value != null ? Convert.ToInt32(value) : 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private static bool ReadBoolMember(object target, params string[] names)
|
|
130
|
+
{
|
|
131
|
+
foreach (var name in names)
|
|
132
|
+
{
|
|
133
|
+
var value = ReadMember(target, name);
|
|
134
|
+
if (value != null)
|
|
135
|
+
return Convert.ToBoolean(value);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private static object ReadMember(object target, string name)
|
|
142
|
+
{
|
|
143
|
+
var type = target.GetType();
|
|
144
|
+
var field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
|
145
|
+
if (field != null)
|
|
146
|
+
return field.GetValue(target);
|
|
147
|
+
var property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
|
148
|
+
return property?.GetValue(target);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|