| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- using System;
- using LightGlue.Unity.Bridge;
- using LightGlue.Unity.Config;
- using LightGlue.Unity.Networking;
- using LightGlue.Unity.Python;
- using UnityEngine;
- namespace LightGlue.Unity.Game
- {
- /// <summary>
- /// LightGlue 全局管理器(跨场景单例)。
- /// - 负责管理 HardwareToPythonUdpBridge 与 PythonProcessController 的生命周期(DontDestroyOnLoad)。
- /// - Bridge 统一管理:硬件 JPEG 接收、转发 Python、硬件 0x40 控制、Python 结果接收。
- /// - 提供统一的结果访问接口(带丢包平滑处理)及暂停/恢复图像传输、启动/停止 Python 的高层封装。
- /// </summary>
- public sealed class LightGlueManager : MonoBehaviour
- {
- /// <summary>
- /// 全局实例(跨场景唯一)。
- /// </summary>
- public static LightGlueManager Instance { get; private set; }
- [Header("核心组件引用")]
- [Tooltip("Bridge(硬件接收/转发Python/硬件控制/结果接收)")]
- public HardwareToPythonUdpBridge bridge;
- [Tooltip("Python 进程控制器(启动/停止 Python 子进程)")]
- public PythonProcessController pythonController;
- [Header("结果丢包与平滑处理")]
- [Tooltip("当当前帧没有新结果或结果无效时,是否保留上一帧有效结果以减缓UDP丢包带来的抖动。")]
- public bool holdLastValidOnPacketLoss = true;
- [Tooltip("在未收到新结果的情况下,最多保留上一帧有效结果的时间(秒)。0 表示永不过期。")]
- [Min(0f)]
- public float maxHoldDurationSeconds = 0.5f;
- [Header("图像传输控制(可选)")]
- [Tooltip("初始默认图传配置(仅 Awake 时推送给 Bridge 一次)。若场景中有 ImageTransmissionUIController,其 Start 会再覆盖为 UI 的配置;运行时以 Bridge.transmissionConfig 为准,此处仅作缺省值。")]
- public ImageTransmissionConfig defaultTransmissionConfig;
- // Bridge 级别的“上一帧有效结果”记录(供丢包平滑使用)
- private LightGlueResult _lastValidResult;
- private bool _hasLastValidResult;
- private float _lastValidTime;
- // Manager 统一拉取并分发的“当前可用结果”缓存
- private LightGlueResult _latestResultFromManager;
- private bool _hasLatestResultFromManager;
- /// <summary>
- /// 当 Manager 成功获取到一个可用结果(包括保留的上一帧有效结果)时触发。
- /// 由 Manager 在 Update 中统一从 Bridge 拉取一次结果并分发,订阅方无需也不应再直接调用 bridge.TryGetLatestResult。
- /// </summary>
- public event Action<LightGlueResult> OnResultUpdated;
- /// <summary>
- /// 最近一次由 Manager 拉取并确认可用的算法结果是否存在。
- /// </summary>
- public bool HasLatestResult => _hasLatestResultFromManager;
- /// <summary>
- /// 最近一次由 Manager 拉取并确认可用的算法结果内容。
- /// </summary>
- public LightGlueResult LatestResult => _latestResultFromManager;
- private void Awake()
- {
- // 单例 + 跨场景常驻:若已有实例则视为重复,先禁用再销毁,避免同帧内 OnEnable 再次绑定端口导致 SocketException
- if (Instance != null && Instance != this)
- {
- gameObject.SetActive(false);
- Destroy(gameObject);
- return;
- }
- Instance = this;
- DontDestroyOnLoad(gameObject);
- // 自动补齐组件引用(推荐把 Bridge、PythonController 放在同一 GameObject 上)
- if (bridge == null)
- bridge = GetComponent<HardwareToPythonUdpBridge>();
- if (pythonController == null)
- pythonController = GetComponent<PythonProcessController>();
- if (bridge == null)
- {
- Debug.LogWarning("[LightGlueManager] HardwareToPythonUdpBridge 未设置或未挂载在同一 GameObject 上。");
- }
- // 可选:在启动时将默认图像传输配置同步到 Bridge
- //if (defaultTransmissionConfig != null && bridge != null)
- //{
- // bridge.SetTransmissionConfig(defaultTransmissionConfig);
- //}
- }
- private void OnDestroy()
- {
- if (Instance == this)
- {
- Instance = null;
- }
- }
- private void OnApplicationQuit()
- {
- // 应用退出时统一停止 Python 与相关 UDP 组件
- try
- {
- if (bridge != null && bridge.enabled)
- {
- bridge.enabled = false;
- }
- }
- catch
- {
- // ignore
- }
- try
- {
- if (pythonController != null && pythonController.IsRunning)
- {
- pythonController.StopPython();
- }
- }
- catch
- {
- // ignore
- }
- }
- /// <summary>
- /// 统一的结果获取接口(在 Bridge 基础上增加“上一帧有效结果保留”逻辑)。
- /// - 优先返回本帧最新有效结果;
- /// - 若没有新结果或结果无效,在启用 holdLastValidOnPacketLoss 时返回上一帧有效结果(在 maxHoldDurationSeconds 时间窗口内)。
- /// </summary>
- public bool TryGetLatestResult(out LightGlueResult result)
- {
- result = LightGlueResult.CreateInvalid();
- if (bridge == null)
- {
- return false;
- }
- // 从 Bridge 获取原始结果(队列非阻塞)
- bool hasNew = bridge.TryGetLatestResult(out var raw);
- if (hasNew)
- {
- if (raw.IsValid)
- {
- // 记录最新有效结果
- _lastValidResult = raw;
- _hasLastValidResult = true;
- _lastValidTime = Time.time;
- result = raw;
- return true;
- }
- // 有新结果但标记为无效:根据配置决定是否退回到上一帧有效结果
- }
- // 没有新结果或新结果无效时,按需返回上一帧有效结果
- if (holdLastValidOnPacketLoss && _hasLastValidResult)
- {
- if (maxHoldDurationSeconds <= 0f || (Time.time - _lastValidTime) <= maxHoldDurationSeconds)
- {
- result = _lastValidResult;
- return true;
- }
- }
- // 没有可用的历史有效结果,则返回原始结果(可能是无效结果或完全无数据)
- if (hasNew)
- {
- result = raw;
- return true;
- }
- return false;
- }
- private void Update()
- {
- // 由 Manager 统一从 Bridge 拉取一次结果,并通过事件分发给所有订阅方,
- // 避免多个脚本分别消费 UDP 队列导致拿到的帧不一致。
- if (bridge == null)
- return;
- if (TryGetLatestResult(out var result))
- {
- _latestResultFromManager = result;
- _hasLatestResultFromManager = true;
- OnResultUpdated?.Invoke(result);
- }
- }
- /// <summary>
- /// 暂停图像传输(推荐在主菜单/暂停菜单调用:只关闭 Unity->Python 图像转发,保留进程与端口)。
- /// 修改的是 Bridge.transmissionConfig.enableImageTransmission;若存在图传 UI,其显示不会自动刷新。
- /// </summary>
- public void PauseImageTransmission()
- {
- if (bridge == null) return;
- var config = bridge.transmissionConfig;
- if (config == null)
- {
- // 若尚未有配置,创建一个默认配置并关闭传输
- config = new ImageTransmissionConfig();
- }
- config.enableImageTransmission = false;
- bridge.SetTransmissionConfig(config);
- }
- /// <summary>
- /// 恢复图像传输(重新开启 Unity->Python 图像转发)。
- /// 修改的是 Bridge.transmissionConfig.enableImageTransmission;若存在图传 UI,其显示不会自动刷新。
- /// </summary>
- public void ResumeImageTransmission()
- {
- if (bridge == null) return;
- var config = bridge.transmissionConfig ?? new ImageTransmissionConfig();
- config.enableImageTransmission = true;
- bridge.SetTransmissionConfig(config);
- }
- /// <summary>
- /// 启动 Python 进程(资源优先模式下,可在进入游戏场景时显式调用)。
- /// </summary>
- public void StartPythonProcess()
- {
- if (pythonController == null)
- {
- Debug.LogWarning("[LightGlueManager] PythonProcessController 未设置,无法启动 Python 进程。");
- return;
- }
- pythonController.StartPython();
- }
- /// <summary>
- /// 停止 Python 进程(例如在主菜单或退出点释放资源)。
- /// </summary>
- public void StopPythonProcess()
- {
- if (pythonController == null)
- {
- return;
- }
- pythonController.StopPython();
- }
- }
- }
|