RomaManager.cs 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. using System;
  2. using LightGlue.Unity.Networking;
  3. using LightGlue.Unity.Python;
  4. using LightGlue.Unity.Roma.Bridge;
  5. using LightGlue.Unity.Roma.Networking;
  6. using UnityEngine;
  7. namespace LightGlue.Unity.Roma
  8. {
  9. /// <summary>
  10. /// Roma 全局管理器(跨场景单例,DontDestroyOnLoad)。
  11. /// 目的:在切换游戏场景时,Roma 的发现/图传配置/Python进程/结果接收/转发显示能持续运行。
  12. /// </summary>
  13. public sealed class RomaManager : MonoBehaviour
  14. {
  15. public static RomaManager Instance { get; private set; }
  16. [Header("Roma 核心组件(建议挂在同一 GameObject 上)")]
  17. public RomaDeviceDiscoveryListener discovery;
  18. public RomaWifiCameraControlClient wifiControl;
  19. public PythonProcessController romaPythonController;
  20. public RomaForwardedImageViewer forwardedViewer;
  21. public RomaDeviceInfoReceiver deviceInfoReceiver;
  22. [Header("启动策略(手动模式建议全部关闭)")]
  23. [Tooltip("是否在 RomaManager.OnEnable 自动开始监听广播(12345)。")]
  24. public bool autoStartDiscoveryOnEnable = false;
  25. [Tooltip("是否在 RomaManager.OnEnable 自动启动 Roma Python 进程。")]
  26. public bool autoStartPythonOnEnable = false;
  27. [Tooltip("是否在 RomaManager.OnEnable 自动启动 Python->Unity 图像转发接收(12366)。")]
  28. public bool autoStartForwardViewerOnEnable = false;
  29. [Tooltip("是否在 RomaManager.OnEnable 自动启动结果接收(12348)。")]
  30. public bool autoStartResultReceiverOnEnable = false;
  31. [Header("Roma 结果接收 (Python -> Unity)")]
  32. public bool enableResultReceiver = true;
  33. public string resultBindIp = "127.0.0.1";
  34. public int resultPort = 12348;
  35. public int maxResultQueueSize = 10;
  36. private UDPResultReceiver _resultReceiver;
  37. private RomaHardwareToPythonBridge _bridge;
  38. public UDPResultReceiver ResultReceiver => _resultReceiver;
  39. public event Action<LightGlueResult> OnResultUpdated;
  40. private LightGlueResult _latest;
  41. private bool _hasLatest;
  42. public bool HasLatestResult => _hasLatest;
  43. public LightGlueResult LatestResult => _latest;
  44. private void Awake()
  45. {
  46. if (Instance != null && Instance != this)
  47. {
  48. gameObject.SetActive(false);
  49. Destroy(gameObject);
  50. return;
  51. }
  52. Instance = this;
  53. DontDestroyOnLoad(gameObject);
  54. if (discovery == null) discovery = GetComponent<RomaDeviceDiscoveryListener>();
  55. if (wifiControl == null) wifiControl = GetComponent<RomaWifiCameraControlClient>();
  56. if (romaPythonController == null) romaPythonController = GetComponent<PythonProcessController>();
  57. if (forwardedViewer == null) forwardedViewer = GetComponent<RomaForwardedImageViewer>();
  58. if (deviceInfoReceiver == null) deviceInfoReceiver = GetComponent<RomaDeviceInfoReceiver>();
  59. if (wifiControl != null && discovery != null)
  60. wifiControl.BindDiscovery(discovery);
  61. }
  62. private void OnDestroy()
  63. {
  64. if (Instance == this) Instance = null;
  65. }
  66. private void OnEnable()
  67. {
  68. Application.runInBackground = true;
  69. // 若场景中存在 RomaHardwareToPythonBridge,则由它负责接收结果,RomaManager 仅做统一对外分发
  70. TryBindBridgeAsResultSource();
  71. // 设备信息接收器:用于 ESP32 无广播时自动填充 device_ip/device_port
  72. if (deviceInfoReceiver != null)
  73. deviceInfoReceiver.StartListening();
  74. if (autoStartDiscoveryOnEnable)
  75. StartDiscovery();
  76. if (autoStartPythonOnEnable)
  77. StartPython();
  78. if (autoStartForwardViewerOnEnable)
  79. StartForwardViewer();
  80. // 仅当没有桥接层接管结果接收时,才由 RomaManager 自己启动 UDPResultReceiver
  81. if (_bridge == null && autoStartResultReceiverOnEnable && enableResultReceiver)
  82. StartResultReceiver();
  83. }
  84. private void OnDisable()
  85. {
  86. StopResultReceiver();
  87. _hasLatest = false;
  88. if (_bridge != null)
  89. {
  90. _bridge.OnResultUpdated -= OnBridgeResultUpdated;
  91. _bridge = null;
  92. }
  93. }
  94. private void Update()
  95. {
  96. // 若桥接层存在,则结果通过桥接层事件推送,这里无需再 drain 自己的 UDP 队列
  97. if (_bridge != null)
  98. return;
  99. if (_resultReceiver == null) return;
  100. // 排空队列,只保留最新
  101. bool any = false;
  102. LightGlueResult last = default;
  103. while (_resultReceiver.TryDequeueResult(out var r))
  104. {
  105. last = r;
  106. any = true;
  107. }
  108. if (any)
  109. {
  110. _latest = last;
  111. _hasLatest = true;
  112. OnResultUpdated?.Invoke(last);
  113. }
  114. }
  115. public bool TryGetLatestResult(out LightGlueResult result)
  116. {
  117. result = default;
  118. if (!_hasLatest) return false;
  119. result = _latest;
  120. return true;
  121. }
  122. public void StartDiscovery()
  123. {
  124. if (discovery == null)
  125. discovery = GetComponent<RomaDeviceDiscoveryListener>() ?? FindObjectOfType<RomaDeviceDiscoveryListener>();
  126. if (discovery != null && !discovery.IsListening)
  127. discovery.StartListening();
  128. }
  129. public void StopDiscovery()
  130. {
  131. if (discovery == null)
  132. discovery = GetComponent<RomaDeviceDiscoveryListener>() ?? FindObjectOfType<RomaDeviceDiscoveryListener>();
  133. discovery?.StopListening();
  134. }
  135. public void StartPython()
  136. {
  137. if (romaPythonController == null)
  138. romaPythonController = GetComponent<PythonProcessController>() ?? FindObjectOfType<PythonProcessController>();
  139. if (romaPythonController != null && !romaPythonController.IsRunning)
  140. romaPythonController.StartPython();
  141. }
  142. public void StopPython()
  143. {
  144. if (romaPythonController == null)
  145. romaPythonController = GetComponent<PythonProcessController>() ?? FindObjectOfType<PythonProcessController>();
  146. if (romaPythonController != null && romaPythonController.IsRunning)
  147. romaPythonController.StopPython();
  148. }
  149. public void StartForwardViewer()
  150. {
  151. if (forwardedViewer == null)
  152. forwardedViewer = GetComponent<RomaForwardedImageViewer>() ?? FindObjectOfType<RomaForwardedImageViewer>();
  153. forwardedViewer?.StartReceiver();
  154. }
  155. public void StopForwardViewer()
  156. {
  157. if (forwardedViewer == null)
  158. forwardedViewer = GetComponent<RomaForwardedImageViewer>() ?? FindObjectOfType<RomaForwardedImageViewer>();
  159. forwardedViewer?.StopReceiver();
  160. }
  161. public void StartResultReceiver()
  162. {
  163. if (!enableResultReceiver) return;
  164. if (_resultReceiver != null) return;
  165. if (_bridge != null) return; // 避免与桥接层抢占端口
  166. TryStartResultReceiver();
  167. }
  168. public void StopResultReceiver()
  169. {
  170. try { _resultReceiver?.Stop(); } catch { /* ignore */ }
  171. _resultReceiver = null;
  172. }
  173. private void TryStartResultReceiver()
  174. {
  175. try
  176. {
  177. _resultReceiver = new UDPResultReceiver(resultBindIp, resultPort, maxResultQueueSize);
  178. _resultReceiver.Start();
  179. Debug.Log($"[RomaManager] Result receiver started on {resultBindIp}:{resultPort}");
  180. }
  181. catch (Exception ex)
  182. {
  183. Debug.LogError($"[RomaManager] Start result receiver failed on {resultBindIp}:{resultPort}: {ex.Message}");
  184. _resultReceiver = null;
  185. }
  186. }
  187. private void TryBindBridgeAsResultSource()
  188. {
  189. if (_bridge != null) return;
  190. _bridge = FindObjectOfType<RomaHardwareToPythonBridge>();
  191. if (_bridge == null) return;
  192. _bridge.OnResultUpdated -= OnBridgeResultUpdated;
  193. _bridge.OnResultUpdated += OnBridgeResultUpdated;
  194. if (_bridge.HasLatestResult)
  195. {
  196. OnBridgeResultUpdated(_bridge.LatestResult);
  197. }
  198. Debug.Log("[RomaManager] Using RomaHardwareToPythonBridge as result source.");
  199. }
  200. private void OnBridgeResultUpdated(LightGlueResult result)
  201. {
  202. _latest = result;
  203. _hasLatest = true;
  204. OnResultUpdated?.Invoke(result);
  205. }
  206. }
  207. }