|
22 | 22 | private static Thread _listenerThread; |
23 | 23 | private static readonly object MainThreadQueueLock = new object(); |
24 | 24 | private static readonly Queue<Action> MainThreadQueue = new Queue<Action>(); |
| 25 | + private static readonly object SelectionStateLock = new object(); |
| 26 | + private static long _selectionVersion = 0; |
| 27 | + private static string _selectionHierarchyPath = ""; |
25 | 28 |
|
26 | 29 | [Serializable] |
27 | 30 | private class SelectionPayload |
28 | 31 | { |
29 | 32 | public bool ok; |
30 | 33 | public string hierarchy_path; |
| 34 | + public long selection_version; |
31 | 35 | public string error; |
32 | 36 | } |
33 | 37 |
|
|
43 | 47 | EditorApplication.update += PumpMainThreadQueue; |
44 | 48 | AssemblyReloadEvents.beforeAssemblyReload += StopBridge; |
45 | 49 | EditorApplication.quitting += StopBridge; |
| 50 | + Selection.selectionChanged += OnSelectionChanged; |
46 | 51 | } |
47 | 52 |
|
48 | 53 | private static void StartBridge() |
|
52 | 57 | return; |
53 | 58 | } |
54 | 59 |
|
| 60 | + UpdateSelectionState(); |
| 61 | +
|
55 | 62 | try |
56 | 63 | { |
57 | 64 | _listener = new HttpListener(); |
|
146 | 153 | } |
147 | 154 | } |
148 | 155 |
|
| 156 | + private static void OnSelectionChanged() |
| 157 | + { |
| 158 | + UpdateSelectionState(); |
| 159 | + } |
| 160 | +
|
| 161 | + private static void UpdateSelectionState() |
| 162 | + { |
| 163 | + var hierarchyPath = GetSelectedHierarchyPath(); |
| 164 | + lock (SelectionStateLock) |
| 165 | + { |
| 166 | + _selectionVersion += 1; |
| 167 | + _selectionHierarchyPath = hierarchyPath ?? ""; |
| 168 | + Monitor.PulseAll(SelectionStateLock); |
| 169 | + } |
| 170 | + } |
| 171 | +
|
149 | 172 | private static bool ExecuteOnMainThread<T>( |
150 | 173 | Func<T> function, |
151 | 174 | int timeoutMs, |
|
216 | 239 | var method = context.Request.HttpMethod.ToUpperInvariant(); |
217 | 240 | var path = context.Request.Url?.AbsolutePath ?? "/"; |
218 | 241 |
|
219 | | - if (method == "GET" && path == "/v1/selection") |
| 242 | + if (method == "GET" && path == "/v1/selection/wait") |
220 | 243 | { |
221 | | - if ( |
222 | | - !ExecuteOnMainThread( |
223 | | - GetSelectedHierarchyPath, |
224 | | - RequestDispatchTimeoutMs, |
225 | | - out string hierarchyPath, |
226 | | - out string dispatchError |
227 | | - ) |
228 | | - ) |
| 244 | + long afterVersion = 0; |
| 245 | + var afterRaw = context.Request.QueryString["after_version"] ?? ""; |
| 246 | + long.TryParse(afterRaw, out afterVersion); |
| 247 | +
|
| 248 | + var timeoutMs = 350; |
| 249 | + var timeoutRaw = context.Request.QueryString["timeout_ms"] ?? ""; |
| 250 | + if (int.TryParse(timeoutRaw, out int parsedTimeoutMs)) |
229 | 251 | { |
230 | | - WriteJson( |
231 | | - context.Response, |
232 | | - 503, |
233 | | - new SelectionPayload |
| 252 | + timeoutMs = parsedTimeoutMs; |
| 253 | + } |
| 254 | + timeoutMs = Math.Max(0, Math.Min(15000, timeoutMs)); |
| 255 | +
|
| 256 | + var deadline = DateTime.UtcNow.AddMilliseconds(timeoutMs); |
| 257 | + SelectionPayload payload; |
| 258 | + lock (SelectionStateLock) |
| 259 | + { |
| 260 | + while (_selectionVersion <= afterVersion) |
| 261 | + { |
| 262 | + var remainingMs = (int)Math.Ceiling( |
| 263 | + (deadline - DateTime.UtcNow).TotalMilliseconds |
| 264 | + ); |
| 265 | + if (remainingMs <= 0) |
234 | 266 | { |
235 | | - ok = false, |
236 | | - hierarchy_path = "", |
237 | | - error = dispatchError |
| 267 | + break; |
238 | 268 | } |
239 | | - ); |
240 | | - return; |
| 269 | + Monitor.Wait(SelectionStateLock, remainingMs); |
| 270 | + } |
| 271 | + payload = new SelectionPayload |
| 272 | + { |
| 273 | + ok = true, |
| 274 | + hierarchy_path = _selectionHierarchyPath ?? "", |
| 275 | + selection_version = _selectionVersion, |
| 276 | + error = "" |
| 277 | + }; |
241 | 278 | } |
242 | 279 |
|
243 | | - WriteJson( |
244 | | - context.Response, |
245 | | - 200, |
246 | | - new SelectionPayload { ok = true, hierarchy_path = hierarchyPath, error = "" } |
247 | | - ); |
| 280 | + WriteJson(context.Response, 200, payload); |
| 281 | + return; |
| 282 | + } |
| 283 | +
|
| 284 | + if (method == "GET" && path == "/v1/selection") |
| 285 | + { |
| 286 | + SelectionPayload payload; |
| 287 | + lock (SelectionStateLock) |
| 288 | + { |
| 289 | + payload = new SelectionPayload |
| 290 | + { |
| 291 | + ok = true, |
| 292 | + hierarchy_path = _selectionHierarchyPath ?? "", |
| 293 | + selection_version = _selectionVersion, |
| 294 | + error = "" |
| 295 | + }; |
| 296 | + } |
| 297 | + WriteJson(context.Response, 200, payload); |
248 | 298 | return; |
249 | 299 | } |
250 | 300 |
|
|
322 | 372 | WriteJson( |
323 | 373 | context.Response, |
324 | 374 | 200, |
325 | | - new SelectionPayload { ok = true, hierarchy_path = normalized, error = "" } |
| 375 | + new SelectionPayload |
| 376 | + { |
| 377 | + ok = true, |
| 378 | + hierarchy_path = normalized, |
| 379 | + selection_version = _selectionVersion, |
| 380 | + error = "" |
| 381 | + } |
326 | 382 | ); |
327 | 383 | return; |
328 | 384 | } |
|
334 | 390 | { |
335 | 391 | ok = false, |
336 | 392 | hierarchy_path = "", |
| 393 | + selection_version = _selectionVersion, |
337 | 394 | error = "Endpoint not found." |
338 | 395 | } |
339 | 396 | ); |
|
343 | 400 | WriteJson( |
344 | 401 | context.Response, |
345 | 402 | 500, |
346 | | - new SelectionPayload { ok = false, hierarchy_path = "", error = ex.Message } |
| 403 | + new SelectionPayload |
| 404 | + { |
| 405 | + ok = false, |
| 406 | + hierarchy_path = "", |
| 407 | + selection_version = _selectionVersion, |
| 408 | + error = ex.Message |
| 409 | + } |
347 | 410 | ); |
348 | 411 | } |
349 | 412 | } |
|
0 commit comments