LightGlueManager.cs 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. using System;
  2. using LightGlue.Unity.Bridge;
  3. using LightGlue.Unity.Config;
  4. using LightGlue.Unity.Networking;
  5. using LightGlue.Unity.Python;
  6. using UnityEngine;
  7. namespace LightGlue.Unity.Game
  8. {
  9. /// <summary>
  10. /// LightGlue 全局管理器(跨场景单例)。
  11. /// - 负责管理 HardwareToPythonUdpBridge 与 PythonProcessController 的生命周期(DontDestroyOnLoad)。
  12. /// - Bridge 统一管理:硬件 JPEG 接收、转发 Python、硬件 0x40 控制、Python 结果接收。
  13. /// - 提供统一的结果访问接口(带丢包平滑处理)及暂停/恢复图像传输、启动/停止 Python 的高层封装。
  14. /// </summary>
  15. public sealed class LightGlueManager : MonoBehaviour
  16. {
  17. /// <summary>
  18. /// 全局实例(跨场景唯一)。
  19. /// </summary>
  20. public static LightGlueManager Instance { get; private set; }
  21. [Header("核心组件引用")]
  22. [Tooltip("Bridge(硬件接收/转发Python/硬件控制/结果接收)")]
  23. public HardwareToPythonUdpBridge bridge;
  24. [Tooltip("Python 进程控制器(启动/停止 Python 子进程)")]
  25. public PythonProcessController pythonController;
  26. [Header("结果丢包与平滑处理")]
  27. [Tooltip("当当前帧没有新结果或结果无效时,是否保留上一帧有效结果以减缓UDP丢包带来的抖动。")]
  28. public bool holdLastValidOnPacketLoss = true;
  29. [Tooltip("在未收到新结果的情况下,最多保留上一帧有效结果的时间(秒)。0 表示永不过期。")]
  30. [Min(0f)]
  31. public float maxHoldDurationSeconds = 0.5f;
  32. [Header("图像传输控制(可选)")]
  33. [Tooltip("初始默认图传配置(仅 Awake 时推送给 Bridge 一次)。若场景中有 ImageTransmissionUIController,其 Start 会再覆盖为 UI 的配置;运行时以 Bridge.transmissionConfig 为准,此处仅作缺省值。")]
  34. public ImageTransmissionConfig defaultTransmissionConfig;
  35. // Bridge 级别的“上一帧有效结果”记录(供丢包平滑使用)
  36. private LightGlueResult _lastValidResult;
  37. private bool _hasLastValidResult;
  38. private float _lastValidTime;
  39. // Manager 统一拉取并分发的“当前可用结果”缓存
  40. private LightGlueResult _latestResultFromManager;
  41. private bool _hasLatestResultFromManager;
  42. /// <summary>
  43. /// 当 Manager 成功获取到一个可用结果(包括保留的上一帧有效结果)时触发。
  44. /// 由 Manager 在 Update 中统一从 Bridge 拉取一次结果并分发,订阅方无需也不应再直接调用 bridge.TryGetLatestResult。
  45. /// </summary>
  46. public event Action<LightGlueResult> OnResultUpdated;
  47. /// <summary>
  48. /// 最近一次由 Manager 拉取并确认可用的算法结果是否存在。
  49. /// </summary>
  50. public bool HasLatestResult => _hasLatestResultFromManager;
  51. /// <summary>
  52. /// 最近一次由 Manager 拉取并确认可用的算法结果内容。
  53. /// </summary>
  54. public LightGlueResult LatestResult => _latestResultFromManager;
  55. private void Awake()
  56. {
  57. // 单例 + 跨场景常驻:若已有实例则视为重复,先禁用再销毁,避免同帧内 OnEnable 再次绑定端口导致 SocketException
  58. if (Instance != null && Instance != this)
  59. {
  60. gameObject.SetActive(false);
  61. Destroy(gameObject);
  62. return;
  63. }
  64. Instance = this;
  65. DontDestroyOnLoad(gameObject);
  66. // 自动补齐组件引用(推荐把 Bridge、PythonController 放在同一 GameObject 上)
  67. if (bridge == null)
  68. bridge = GetComponent<HardwareToPythonUdpBridge>();
  69. if (pythonController == null)
  70. pythonController = GetComponent<PythonProcessController>();
  71. if (bridge == null)
  72. {
  73. Debug.LogWarning("[LightGlueManager] HardwareToPythonUdpBridge 未设置或未挂载在同一 GameObject 上。");
  74. }
  75. // 可选:在启动时将默认图像传输配置同步到 Bridge
  76. //if (defaultTransmissionConfig != null && bridge != null)
  77. //{
  78. // bridge.SetTransmissionConfig(defaultTransmissionConfig);
  79. //}
  80. }
  81. private void OnDestroy()
  82. {
  83. if (Instance == this)
  84. {
  85. Instance = null;
  86. }
  87. }
  88. private void OnApplicationQuit()
  89. {
  90. // 应用退出时统一停止 Python 与相关 UDP 组件
  91. try
  92. {
  93. if (bridge != null && bridge.enabled)
  94. {
  95. bridge.enabled = false;
  96. }
  97. }
  98. catch
  99. {
  100. // ignore
  101. }
  102. try
  103. {
  104. if (pythonController != null && pythonController.IsRunning)
  105. {
  106. pythonController.StopPython();
  107. }
  108. }
  109. catch
  110. {
  111. // ignore
  112. }
  113. }
  114. /// <summary>
  115. /// 统一的结果获取接口(在 Bridge 基础上增加“上一帧有效结果保留”逻辑)。
  116. /// - 优先返回本帧最新有效结果;
  117. /// - 若没有新结果或结果无效,在启用 holdLastValidOnPacketLoss 时返回上一帧有效结果(在 maxHoldDurationSeconds 时间窗口内)。
  118. /// </summary>
  119. public bool TryGetLatestResult(out LightGlueResult result)
  120. {
  121. result = LightGlueResult.CreateInvalid();
  122. if (bridge == null)
  123. {
  124. return false;
  125. }
  126. // 从 Bridge 获取原始结果(队列非阻塞)
  127. bool hasNew = bridge.TryGetLatestResult(out var raw);
  128. if (hasNew)
  129. {
  130. if (raw.IsValid)
  131. {
  132. // 记录最新有效结果
  133. _lastValidResult = raw;
  134. _hasLastValidResult = true;
  135. _lastValidTime = Time.time;
  136. result = raw;
  137. return true;
  138. }
  139. // 有新结果但标记为无效:根据配置决定是否退回到上一帧有效结果
  140. }
  141. // 没有新结果或新结果无效时,按需返回上一帧有效结果
  142. if (holdLastValidOnPacketLoss && _hasLastValidResult)
  143. {
  144. if (maxHoldDurationSeconds <= 0f || (Time.time - _lastValidTime) <= maxHoldDurationSeconds)
  145. {
  146. result = _lastValidResult;
  147. return true;
  148. }
  149. }
  150. // 没有可用的历史有效结果,则返回原始结果(可能是无效结果或完全无数据)
  151. if (hasNew)
  152. {
  153. result = raw;
  154. return true;
  155. }
  156. return false;
  157. }
  158. private void Update()
  159. {
  160. // 由 Manager 统一从 Bridge 拉取一次结果,并通过事件分发给所有订阅方,
  161. // 避免多个脚本分别消费 UDP 队列导致拿到的帧不一致。
  162. if (bridge == null)
  163. return;
  164. if (TryGetLatestResult(out var result))
  165. {
  166. _latestResultFromManager = result;
  167. _hasLatestResultFromManager = true;
  168. OnResultUpdated?.Invoke(result);
  169. }
  170. }
  171. /// <summary>
  172. /// 暂停图像传输(推荐在主菜单/暂停菜单调用:只关闭 Unity->Python 图像转发,保留进程与端口)。
  173. /// 修改的是 Bridge.transmissionConfig.enableImageTransmission;若存在图传 UI,其显示不会自动刷新。
  174. /// </summary>
  175. public void PauseImageTransmission()
  176. {
  177. if (bridge == null) return;
  178. var config = bridge.transmissionConfig;
  179. if (config == null)
  180. {
  181. // 若尚未有配置,创建一个默认配置并关闭传输
  182. config = new ImageTransmissionConfig();
  183. }
  184. config.enableImageTransmission = false;
  185. bridge.SetTransmissionConfig(config);
  186. }
  187. /// <summary>
  188. /// 恢复图像传输(重新开启 Unity->Python 图像转发)。
  189. /// 修改的是 Bridge.transmissionConfig.enableImageTransmission;若存在图传 UI,其显示不会自动刷新。
  190. /// </summary>
  191. public void ResumeImageTransmission()
  192. {
  193. if (bridge == null) return;
  194. var config = bridge.transmissionConfig ?? new ImageTransmissionConfig();
  195. config.enableImageTransmission = true;
  196. bridge.SetTransmissionConfig(config);
  197. }
  198. /// <summary>
  199. /// 启动 Python 进程(资源优先模式下,可在进入游戏场景时显式调用)。
  200. /// </summary>
  201. public void StartPythonProcess()
  202. {
  203. if (pythonController == null)
  204. {
  205. Debug.LogWarning("[LightGlueManager] PythonProcessController 未设置,无法启动 Python 进程。");
  206. return;
  207. }
  208. pythonController.StartPython();
  209. }
  210. /// <summary>
  211. /// 停止 Python 进程(例如在主菜单或退出点释放资源)。
  212. /// </summary>
  213. public void StopPythonProcess()
  214. {
  215. if (pythonController == null)
  216. {
  217. return;
  218. }
  219. pythonController.StopPython();
  220. }
  221. }
  222. }