@@ -139,7 +139,7 @@ def read_status_file() -> dict | None:
139139 return {
140140 "success" : False ,
141141 "state" : "reloading" ,
142- "retry_after_ms" : int (250 ),
142+ "retry_after_ms" : int (config . reload_retry_ms ),
143143 "error" : "Unity domain reload in progress" ,
144144 "message" : "Unity is reloading scripts; please retry shortly"
145145 }
@@ -278,3 +278,54 @@ def get_unity_connection() -> UnityConnection:
278278 pass
279279 _unity_connection = None
280280 raise ConnectionError (f"Could not establish valid Unity connection: { str (e )} " )
281+
282+
283+ # -----------------------------
284+ # Centralized retry helpers
285+ # -----------------------------
286+
287+ def _is_reloading_response (resp : dict ) -> bool :
288+ """Return True if the Unity response indicates the editor is reloading."""
289+ if not isinstance (resp , dict ):
290+ return False
291+ if resp .get ("state" ) == "reloading" :
292+ return True
293+ message_text = (resp .get ("message" ) or resp .get ("error" ) or "" ).lower ()
294+ return "reload" in message_text
295+
296+
297+ def send_command_with_retry (command_type : str , params : Dict [str , Any ], * , max_retries : int | None = None , retry_ms : int | None = None ) -> Dict [str , Any ]:
298+ """Send a command via the shared connection, waiting politely through Unity reloads.
299+
300+ Uses config.reload_retry_ms and config.reload_max_retries by default. Preserves the
301+ structured failure if retries are exhausted.
302+ """
303+ conn = get_unity_connection ()
304+ if max_retries is None :
305+ max_retries = getattr (config , "reload_max_retries" , 40 )
306+ if retry_ms is None :
307+ retry_ms = getattr (config , "reload_retry_ms" , 250 )
308+
309+ response = conn .send_command (command_type , params )
310+ retries = 0
311+ while _is_reloading_response (response ) and retries < max_retries :
312+ delay_ms = int (response .get ("retry_after_ms" , retry_ms )) if isinstance (response , dict ) else retry_ms
313+ time .sleep (max (0.0 , delay_ms / 1000.0 ))
314+ retries += 1
315+ response = conn .send_command (command_type , params )
316+ return response
317+
318+
319+ async def async_send_command_with_retry (command_type : str , params : Dict [str , Any ], * , loop = None , max_retries : int | None = None , retry_ms : int | None = None ) -> Dict [str , Any ]:
320+ """Async wrapper that runs the blocking retry helper in a thread pool."""
321+ try :
322+ import asyncio # local import to avoid mandatory asyncio dependency for sync callers
323+ if loop is None :
324+ loop = asyncio .get_running_loop ()
325+ return await loop .run_in_executor (
326+ None ,
327+ lambda : send_command_with_retry (command_type , params , max_retries = max_retries , retry_ms = retry_ms ),
328+ )
329+ except Exception as e :
330+ # Return a structured error dict for consistency with other responses
331+ return {"success" : False , "error" : f"Python async retry helper failed: { str (e )} " }
0 commit comments