@mflrevan/ucp 0.1.1 → 0.2.3

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 (66) hide show
  1. package/README.md +31 -38
  2. package/bridge/com.ucp.bridge/CHANGELOG.md +56 -0
  3. package/bridge/com.ucp.bridge/CHANGELOG.md.meta +7 -0
  4. package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs +573 -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/Controllers/AssetController.cs +499 -0
  8. package/bridge/com.ucp.bridge/Editor/Controllers/AssetController.cs.meta +2 -0
  9. package/bridge/com.ucp.bridge/Editor/Controllers/BuildController.cs +230 -0
  10. package/bridge/com.ucp.bridge/Editor/Controllers/BuildController.cs.meta +2 -0
  11. package/bridge/com.ucp.bridge/Editor/Controllers/CompilationController.cs +26 -0
  12. package/bridge/com.ucp.bridge/Editor/Controllers/CompilationController.cs.meta +2 -0
  13. package/bridge/com.ucp.bridge/Editor/Controllers/EditorSettingsController.cs +435 -0
  14. package/bridge/com.ucp.bridge/Editor/Controllers/EditorSettingsController.cs.meta +2 -0
  15. package/bridge/com.ucp.bridge/Editor/Controllers/FileController.cs +130 -0
  16. package/bridge/com.ucp.bridge/Editor/Controllers/FileController.cs.meta +2 -0
  17. package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs +319 -0
  18. package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs.meta +2 -0
  19. package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs +291 -0
  20. package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs.meta +2 -0
  21. package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs +295 -0
  22. package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs.meta +2 -0
  23. package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs +38 -0
  24. package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs.meta +2 -0
  25. package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs +242 -0
  26. package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs.meta +2 -0
  27. package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs +551 -0
  28. package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs.meta +2 -0
  29. package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs +70 -0
  30. package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs.meta +2 -0
  31. package/bridge/com.ucp.bridge/Editor/Controllers/ScreenshotController.cs +125 -0
  32. package/bridge/com.ucp.bridge/Editor/Controllers/ScreenshotController.cs.meta +2 -0
  33. package/bridge/com.ucp.bridge/Editor/Controllers/ScriptController.cs +104 -0
  34. package/bridge/com.ucp.bridge/Editor/Controllers/ScriptController.cs.meta +2 -0
  35. package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs +227 -0
  36. package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs.meta +2 -0
  37. package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs +180 -0
  38. package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs.meta +2 -0
  39. package/bridge/com.ucp.bridge/Editor/Controllers/VcsController.cs +611 -0
  40. package/bridge/com.ucp.bridge/Editor/Controllers/VcsController.cs.meta +2 -0
  41. package/bridge/com.ucp.bridge/Editor/Controllers.meta +8 -0
  42. package/bridge/com.ucp.bridge/Editor/Protocol/CommandRouter.cs +45 -0
  43. package/bridge/com.ucp.bridge/Editor/Protocol/CommandRouter.cs.meta +2 -0
  44. package/bridge/com.ucp.bridge/Editor/Protocol/MessageTypes.cs +80 -0
  45. package/bridge/com.ucp.bridge/Editor/Protocol/MessageTypes.cs.meta +2 -0
  46. package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs +358 -0
  47. package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs.meta +2 -0
  48. package/bridge/com.ucp.bridge/Editor/Protocol.meta +8 -0
  49. package/bridge/com.ucp.bridge/Editor/Scripts/IUCPScript.cs +37 -0
  50. package/bridge/com.ucp.bridge/Editor/Scripts/IUCPScript.cs.meta +2 -0
  51. package/bridge/com.ucp.bridge/Editor/Scripts.meta +8 -0
  52. package/bridge/com.ucp.bridge/Editor/UCP.Bridge.Editor.asmdef +16 -0
  53. package/bridge/com.ucp.bridge/Editor/UCP.Bridge.Editor.asmdef.meta +7 -0
  54. package/bridge/com.ucp.bridge/Editor.meta +8 -0
  55. package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef +14 -0
  56. package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef.meta +7 -0
  57. package/bridge/com.ucp.bridge/Runtime.meta +8 -0
  58. package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs +194 -0
  59. package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs.meta +2 -0
  60. package/bridge/com.ucp.bridge/Tests/Editor/UCP.Bridge.Editor.Tests.asmdef +12 -0
  61. package/bridge/com.ucp.bridge/Tests/Editor/UCP.Bridge.Editor.Tests.asmdef.meta +7 -0
  62. package/bridge/com.ucp.bridge/Tests/Editor.meta +8 -0
  63. package/bridge/com.ucp.bridge/Tests.meta +8 -0
  64. package/bridge/com.ucp.bridge/package.json +27 -0
  65. package/bridge/com.ucp.bridge/package.json.meta +7 -0
  66. package/package.json +1 -1
@@ -0,0 +1,573 @@
1
+ using System;
2
+ using System.Collections.Concurrent;
3
+ using System.Collections.Generic;
4
+ using System.IO;
5
+ using System.Net;
6
+ using System.Net.Sockets;
7
+ using System.Net.WebSockets;
8
+ using System.Security.Cryptography;
9
+ using System.Text;
10
+ using System.Text.RegularExpressions;
11
+ using System.Threading;
12
+ using System.Threading.Tasks;
13
+ using UnityEditor;
14
+ using UnityEngine;
15
+
16
+ namespace UCP.Bridge
17
+ {
18
+ /// <summary>
19
+ /// WebSocket server that runs inside the Unity Editor.
20
+ /// Uses raw TcpListener + manual WebSocket upgrade for maximum compatibility.
21
+ /// Starts automatically via [InitializeOnLoad] and handles JSON-RPC commands.
22
+ /// </summary>
23
+ [InitializeOnLoad]
24
+ public static class BridgeServer
25
+ {
26
+ private const int DefaultPort = 21342;
27
+ private const int MaxPort = 21352;
28
+ private const int MaxConnections = 4;
29
+ private const string ProtocolVersion = "0.2.3";
30
+
31
+ private static TcpListener s_listener;
32
+ private static CancellationTokenSource s_cts;
33
+ private static readonly List<WebSocket> s_clients = new();
34
+ private static readonly object s_clientLock = new();
35
+ private static int s_port;
36
+ private static string s_token;
37
+ private static bool s_running;
38
+
39
+ // Main-thread action queue
40
+ private static readonly ConcurrentQueue<Action> s_mainThreadQueue = new();
41
+
42
+ // Command router
43
+ private static readonly CommandRouter s_router = new();
44
+
45
+ // Log subscribers
46
+ private static readonly HashSet<WebSocket> s_logSubscribers = new();
47
+
48
+ static BridgeServer()
49
+ {
50
+ // Use delayCall + update fallback to ensure reliable startup after domain reload
51
+ EditorApplication.delayCall += Initialize;
52
+ EditorApplication.update += EnsureRunning;
53
+ }
54
+
55
+ private static void EnsureRunning()
56
+ {
57
+ if (!s_running)
58
+ {
59
+ EditorApplication.update -= EnsureRunning;
60
+ Initialize();
61
+ }
62
+ else
63
+ {
64
+ EditorApplication.update -= EnsureRunning;
65
+ }
66
+ }
67
+
68
+ private static void Initialize()
69
+ {
70
+ if (s_running) return;
71
+
72
+ try
73
+ {
74
+ RegisterHandlers();
75
+
76
+ EditorApplication.update += PumpMainThread;
77
+ EditorApplication.quitting += Shutdown;
78
+ AssemblyReloadEvents.beforeAssemblyReload += Shutdown;
79
+ Application.logMessageReceived += OnLogMessage;
80
+
81
+ s_token = Guid.NewGuid().ToString("N").Substring(0, 16);
82
+ StartServer();
83
+ }
84
+ catch (Exception ex)
85
+ {
86
+ Debug.LogError($"[UCP] Failed to initialize bridge: {ex}");
87
+ }
88
+ }
89
+
90
+ private static void RegisterHandlers()
91
+ {
92
+ // Handshake
93
+ s_router.Register("handshake", (paramsJson) =>
94
+ {
95
+ return new
96
+ {
97
+ serverVersion = ProtocolVersion,
98
+ protocolVersion = ProtocolVersion,
99
+ unityVersion = Application.unityVersion,
100
+ projectName = Application.productName,
101
+ projectPath = Path.GetDirectoryName(Application.dataPath)
102
+ };
103
+ });
104
+
105
+ // Play mode
106
+ PlayModeController.Register(s_router);
107
+
108
+ // Compilation
109
+ CompilationController.Register(s_router);
110
+
111
+ // Scenes
112
+ SceneController.Register(s_router);
113
+
114
+ // Snapshots
115
+ SnapshotController.Register(s_router);
116
+
117
+ // Screenshots
118
+ ScreenshotController.Register(s_router);
119
+
120
+ // Logs
121
+ LogsController.Register(s_router);
122
+
123
+ // Tests
124
+ TestRunnerController.Register(s_router);
125
+
126
+ // Scripts (exec)
127
+ ScriptController.Register(s_router);
128
+
129
+ // Files
130
+ FileController.Register(s_router);
131
+
132
+ // Version Control (Unity VCS / Plastic SCM)
133
+ VcsController.Register(s_router);
134
+
135
+ // Object Properties
136
+ PropertyController.Register(s_router);
137
+
138
+ // Hierarchy Operations
139
+ HierarchyController.Register(s_router);
140
+
141
+ // Asset Management
142
+ AssetController.Register(s_router);
143
+
144
+ // Editor Settings (Player, Quality, Physics, Lighting, Tags/Layers)
145
+ EditorSettingsController.Register(s_router);
146
+
147
+ // Material Properties
148
+ MaterialController.Register(s_router);
149
+
150
+ // Prefab Operations
151
+ PrefabController.Register(s_router);
152
+
153
+ // Build Pipeline
154
+ BuildController.Register(s_router);
155
+ }
156
+
157
+ private static void StartServer()
158
+ {
159
+ s_cts = new CancellationTokenSource();
160
+
161
+ for (int port = DefaultPort; port <= MaxPort; port++)
162
+ {
163
+ try
164
+ {
165
+ s_listener = new TcpListener(IPAddress.Loopback, port);
166
+ s_listener.Start();
167
+ s_port = port;
168
+ s_running = true;
169
+ Debug.Log($"[UCP] Bridge server started on port {port}");
170
+ break;
171
+ }
172
+ catch (Exception)
173
+ {
174
+ s_listener?.Stop();
175
+ s_listener = null;
176
+ }
177
+ }
178
+
179
+ if (!s_running)
180
+ {
181
+ Debug.LogError("[UCP] Failed to start bridge server — all ports in use");
182
+ return;
183
+ }
184
+
185
+ WriteLockFile();
186
+ Task.Run(() => AcceptLoop(s_cts.Token));
187
+ }
188
+
189
+ private static async Task AcceptLoop(CancellationToken ct)
190
+ {
191
+ while (!ct.IsCancellationRequested && s_running)
192
+ {
193
+ try
194
+ {
195
+ var tcp = await s_listener.AcceptTcpClientAsync();
196
+ var stream = tcp.GetStream();
197
+
198
+ // Read HTTP upgrade request (may arrive in multiple segments)
199
+ var requestBuilder = new StringBuilder();
200
+ var buffer = new byte[4096];
201
+ do
202
+ {
203
+ int read = await stream.ReadAsync(buffer, 0, buffer.Length, ct);
204
+ if (read == 0) { tcp.Close(); continue; }
205
+ requestBuilder.Append(Encoding.UTF8.GetString(buffer, 0, read));
206
+ }
207
+ while (!requestBuilder.ToString().Contains("\r\n\r\n") && stream.DataAvailable);
208
+
209
+ var request = requestBuilder.ToString();
210
+
211
+ // Check if it's a WebSocket upgrade
212
+ if (!request.Contains("Upgrade: websocket", StringComparison.OrdinalIgnoreCase))
213
+ {
214
+ var resp = "HTTP/1.1 426 Upgrade Required\r\nContent-Length: 0\r\n\r\n";
215
+ var respBytes = Encoding.UTF8.GetBytes(resp);
216
+ await stream.WriteAsync(respBytes, 0, respBytes.Length, ct);
217
+ tcp.Close();
218
+ continue;
219
+ }
220
+
221
+ // Extract Sec-WebSocket-Key
222
+ var keyMatch = Regex.Match(request, @"Sec-WebSocket-Key:\s*(\S+)",
223
+ RegexOptions.IgnoreCase);
224
+ if (!keyMatch.Success)
225
+ {
226
+ Debug.LogError("[UCP] No Sec-WebSocket-Key found in request");
227
+ tcp.Close();
228
+ continue;
229
+ }
230
+
231
+ var wsKey = keyMatch.Groups[1].Value.Trim();
232
+ var acceptKey = ComputeWebSocketAcceptKey(wsKey);
233
+
234
+ // Send upgrade response
235
+ var upgradeResponse =
236
+ "HTTP/1.1 101 Switching Protocols\r\n" +
237
+ "Upgrade: websocket\r\n" +
238
+ "Connection: Upgrade\r\n" +
239
+ $"Sec-WebSocket-Accept: {acceptKey}\r\n\r\n";
240
+ var upgradeBytes = Encoding.UTF8.GetBytes(upgradeResponse);
241
+ await stream.WriteAsync(upgradeBytes, 0, upgradeBytes.Length, ct);
242
+
243
+ // Create WebSocket from stream
244
+ var ws = WebSocket.CreateFromStream(stream, true, null, TimeSpan.FromSeconds(30));
245
+
246
+ lock (s_clientLock)
247
+ {
248
+ if (s_clients.Count >= MaxConnections)
249
+ {
250
+ _ = ws.CloseAsync(WebSocketCloseStatus.PolicyViolation,
251
+ "Max connections reached", CancellationToken.None);
252
+ continue;
253
+ }
254
+ s_clients.Add(ws);
255
+ }
256
+
257
+ _ = Task.Run(() => HandleClient(ws, ct));
258
+ }
259
+ catch (ObjectDisposedException) { break; }
260
+ catch (SocketException) { break; }
261
+ catch (Exception ex)
262
+ {
263
+ if (!ct.IsCancellationRequested)
264
+ Debug.LogError($"[UCP] Accept error: {ex.Message}");
265
+ }
266
+ }
267
+ }
268
+
269
+ private static string ComputeWebSocketAcceptKey(string key)
270
+ {
271
+ const string magic = "258EAFA5-E914-47DA-95CA-5AB5DC85B11B";
272
+ var combined = key + magic;
273
+ var hash = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(combined));
274
+ return Convert.ToBase64String(hash);
275
+ }
276
+
277
+ private static async Task HandleClient(WebSocket ws, CancellationToken ct)
278
+ {
279
+ var buffer = new byte[64 * 1024]; // 64KB buffer
280
+
281
+ try
282
+ {
283
+ while (ws.State == WebSocketState.Open && !ct.IsCancellationRequested)
284
+ {
285
+ var segment = new ArraySegment<byte>(buffer);
286
+ WebSocketReceiveResult received;
287
+ var messageBuilder = new StringBuilder();
288
+
289
+ do
290
+ {
291
+ received = await ws.ReceiveAsync(segment, ct);
292
+ if (received.MessageType == WebSocketMessageType.Close)
293
+ {
294
+ await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
295
+ return;
296
+ }
297
+ messageBuilder.Append(Encoding.UTF8.GetString(buffer, 0, received.Count));
298
+ }
299
+ while (!received.EndOfMessage);
300
+
301
+ var message = messageBuilder.ToString();
302
+ ProcessMessage(ws, message);
303
+ }
304
+ }
305
+ catch (OperationCanceledException) { }
306
+ catch (WebSocketException) { }
307
+ catch (Exception ex)
308
+ {
309
+ Debug.LogError($"[UCP] Client error: {ex.Message}");
310
+ }
311
+ finally
312
+ {
313
+ lock (s_clientLock)
314
+ {
315
+ s_clients.Remove(ws);
316
+ s_logSubscribers.Remove(ws);
317
+ }
318
+ }
319
+ }
320
+
321
+ private static void ProcessMessage(WebSocket ws, string message)
322
+ {
323
+ // Parse JSON-RPC request
324
+ long id = 0;
325
+ string method = null;
326
+ string paramsJson = "{}";
327
+
328
+ try
329
+ {
330
+ // Minimal JSON parsing using Unity's JsonUtility won't work well for dynamic JSON.
331
+ // Use a simple manual parse for the top-level fields.
332
+ var json = MiniJson.Deserialize(message) as Dictionary<string, object>;
333
+ if (json == null)
334
+ {
335
+ SendError(ws, 0, ErrorCodes.ParseError, "Invalid JSON");
336
+ return;
337
+ }
338
+
339
+ if (json.TryGetValue("id", out var idVal))
340
+ id = Convert.ToInt64(idVal);
341
+
342
+ if (json.TryGetValue("method", out var methodVal))
343
+ method = methodVal as string;
344
+
345
+ if (json.TryGetValue("params", out var paramsVal))
346
+ paramsJson = MiniJson.Serialize(paramsVal);
347
+ }
348
+ catch (Exception ex)
349
+ {
350
+ SendError(ws, 0, ErrorCodes.ParseError, $"Parse error: {ex.Message}");
351
+ return;
352
+ }
353
+
354
+ if (string.IsNullOrEmpty(method))
355
+ {
356
+ SendError(ws, id, ErrorCodes.InvalidRequest, "Missing 'method' field");
357
+ return;
358
+ }
359
+
360
+ // Handle log subscription specially
361
+ if (method == "logs/subscribe")
362
+ {
363
+ lock (s_clientLock) { s_logSubscribers.Add(ws); }
364
+ }
365
+ else if (method == "logs/unsubscribe")
366
+ {
367
+ lock (s_clientLock) { s_logSubscribers.Remove(ws); }
368
+ }
369
+
370
+ // Dispatch on main thread
371
+ var capturedId = id;
372
+ var capturedMethod = method;
373
+ var capturedParams = paramsJson;
374
+ var capturedWs = ws;
375
+
376
+ s_mainThreadQueue.Enqueue(() =>
377
+ {
378
+ var response = s_router.Dispatch(capturedMethod, capturedId, capturedParams);
379
+ SendResponse(capturedWs, response);
380
+ });
381
+ }
382
+
383
+ private static void SendResponse(WebSocket ws, JsonRpcResponse response)
384
+ {
385
+ try
386
+ {
387
+ var json = MiniJson.Serialize(ResponseToDict(response));
388
+ var bytes = Encoding.UTF8.GetBytes(json);
389
+ _ = ws.SendAsync(new ArraySegment<byte>(bytes),
390
+ WebSocketMessageType.Text, true, CancellationToken.None);
391
+ }
392
+ catch (Exception ex)
393
+ {
394
+ Debug.LogError($"[UCP] Send error: {ex.Message}");
395
+ }
396
+ }
397
+
398
+ private static void SendError(WebSocket ws, long id, int code, string message)
399
+ {
400
+ SendResponse(ws, JsonRpcResponse.Error(id, code, message));
401
+ }
402
+
403
+ /// <summary>
404
+ /// Send a notification to log subscribers only.
405
+ /// </summary>
406
+ public static void SendNotification(string method, object data)
407
+ {
408
+ SendNotificationTo(method, data, false);
409
+ }
410
+
411
+ /// <summary>
412
+ /// Broadcast a notification to ALL connected clients.
413
+ /// Use for test results and other non-log notifications.
414
+ /// </summary>
415
+ public static void BroadcastNotification(string method, object data)
416
+ {
417
+ SendNotificationTo(method, data, true);
418
+ }
419
+
420
+ private static void SendNotificationTo(string method, object data, bool toAll)
421
+ {
422
+ var dict = new Dictionary<string, object>
423
+ {
424
+ ["jsonrpc"] = "2.0",
425
+ ["method"] = method,
426
+ ["params"] = data
427
+ };
428
+ var json = MiniJson.Serialize(dict);
429
+ var bytes = Encoding.UTF8.GetBytes(json);
430
+ var segment = new ArraySegment<byte>(bytes);
431
+
432
+ List<WebSocket> targets;
433
+ lock (s_clientLock)
434
+ {
435
+ targets = toAll
436
+ ? new List<WebSocket>(s_clients)
437
+ : new List<WebSocket>(s_logSubscribers);
438
+ }
439
+
440
+ foreach (var ws in targets)
441
+ {
442
+ try
443
+ {
444
+ if (ws.State == WebSocketState.Open)
445
+ {
446
+ _ = ws.SendAsync(segment, WebSocketMessageType.Text, true, CancellationToken.None);
447
+ }
448
+ }
449
+ catch { /* ignore send failures for notifications */ }
450
+ }
451
+ }
452
+
453
+ private static void OnLogMessage(string message, string stackTrace, LogType type)
454
+ {
455
+ // Don't forward our own log messages to avoid infinite recursion
456
+ if (message.StartsWith("[UCP]")) return;
457
+
458
+ SendNotification("log", LogsController.RecordLog(message, stackTrace, type));
459
+ }
460
+
461
+ private static void PumpMainThread()
462
+ {
463
+ int processed = 0;
464
+ while (s_mainThreadQueue.TryDequeue(out var action) && processed < 50)
465
+ {
466
+ try { action(); }
467
+ catch (Exception ex) { Debug.LogError($"[UCP] Main thread error: {ex}"); }
468
+ processed++;
469
+ }
470
+ }
471
+
472
+ private static void WriteLockFile()
473
+ {
474
+ try
475
+ {
476
+ var projectPath = Path.GetDirectoryName(Application.dataPath);
477
+ var ucpDir = Path.Combine(projectPath, ".ucp");
478
+ Directory.CreateDirectory(ucpDir);
479
+
480
+ var lockData = new Dictionary<string, object>
481
+ {
482
+ ["pid"] = System.Diagnostics.Process.GetCurrentProcess().Id,
483
+ ["port"] = s_port,
484
+ ["protocolVersion"] = ProtocolVersion,
485
+ ["unityVersion"] = Application.unityVersion,
486
+ ["projectPath"] = projectPath,
487
+ ["startedAt"] = DateTime.UtcNow.ToString("o"),
488
+ ["token"] = s_token
489
+ };
490
+
491
+ File.WriteAllText(
492
+ Path.Combine(ucpDir, "bridge.lock"),
493
+ MiniJson.Serialize(lockData)
494
+ );
495
+ }
496
+ catch (Exception ex)
497
+ {
498
+ Debug.LogError($"[UCP] Failed to write lock file: {ex.Message}");
499
+ }
500
+ }
501
+
502
+ private static void CleanLockFile()
503
+ {
504
+ try
505
+ {
506
+ var projectPath = Path.GetDirectoryName(Application.dataPath);
507
+ var lockPath = Path.Combine(projectPath, ".ucp", "bridge.lock");
508
+ if (File.Exists(lockPath))
509
+ File.Delete(lockPath);
510
+ }
511
+ catch { }
512
+ }
513
+
514
+ private static void Shutdown()
515
+ {
516
+ if (!s_running) return;
517
+ s_running = false;
518
+
519
+ Debug.Log("[UCP] Bridge server shutting down");
520
+
521
+ s_cts?.Cancel();
522
+
523
+ // Stop listener first to release port immediately
524
+ try { s_listener?.Stop(); }
525
+ catch { }
526
+ s_listener = null;
527
+
528
+ lock (s_clientLock)
529
+ {
530
+ foreach (var ws in s_clients)
531
+ {
532
+ try { ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Shutting down", CancellationToken.None); }
533
+ catch { }
534
+ }
535
+ s_clients.Clear();
536
+ s_logSubscribers.Clear();
537
+ }
538
+
539
+ s_cts?.Dispose();
540
+ s_cts = null;
541
+
542
+ CleanLockFile();
543
+
544
+ EditorApplication.update -= PumpMainThread;
545
+ Application.logMessageReceived -= OnLogMessage;
546
+ }
547
+
548
+ private static Dictionary<string, object> ResponseToDict(JsonRpcResponse r)
549
+ {
550
+ var dict = new Dictionary<string, object>
551
+ {
552
+ ["jsonrpc"] = "2.0",
553
+ ["id"] = r.id
554
+ };
555
+
556
+ if (r.error != null)
557
+ {
558
+ dict["error"] = new Dictionary<string, object>
559
+ {
560
+ ["code"] = r.error.code,
561
+ ["message"] = r.error.message,
562
+ ["data"] = r.error.data
563
+ };
564
+ }
565
+ else
566
+ {
567
+ dict["result"] = r.result;
568
+ }
569
+
570
+ return dict;
571
+ }
572
+ }
573
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: b9a2c654603efd4489cde1f7449bdc4c
@@ -0,0 +1,8 @@
1
+ fileFormatVersion: 2
2
+ guid: f6c77174590222842b21f1d3b792cb65
3
+ folderAsset: yes
4
+ DefaultImporter:
5
+ externalObjects: {}
6
+ userData:
7
+ assetBundleName:
8
+ assetBundleVariant: