NetworkConfigUIController.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749
  1. using UnityEngine;
  2. using UnityEngine.UI;
  3. using TMPro;
  4. using LightGlue.Unity.Config;
  5. using LightGlue.Unity.Bridge;
  6. using LightGlue.Unity.Game;
  7. using LightGlue.Unity.Sdk.Unity;
  8. namespace LightGlue.Unity.UI
  9. {
  10. /// <summary>
  11. /// 网络配置UI控制器
  12. /// - 提供IP和端口配置界面
  13. /// - 自动加载和保存配置到JSON文件
  14. /// - 将配置应用到相关组件(Bridge、ControlClient等)
  15. /// </summary>
  16. public class NetworkConfigUIController : MonoBehaviour
  17. {
  18. [Header("网络配置UI组件")]
  19. [Tooltip("硬件绑定IP输入框(Unity接收硬件图像的本地IP)")]
  20. public TMP_InputField hardwareBindIpField;
  21. [Tooltip("硬件端口输入框")]
  22. public TMP_InputField hardwarePortField;
  23. [Tooltip("硬件控制IP输入框(硬件设备IP,用于发送控制指令)")]
  24. public TMP_InputField hardwareControlIpField;
  25. [Tooltip("硬件控制端口输入框")]
  26. public TMP_InputField hardwareControlPortField;
  27. [Tooltip("Python IP输入框(UDP模式使用)")]
  28. public TMP_InputField pythonIpField;
  29. [Tooltip("Python端口输入框(UDP模式使用)")]
  30. public TMP_InputField pythonPortField;
  31. [Tooltip("Python启动参数输入框(多行文本,支持 {pythonPort} 占位符)")]
  32. public TMP_InputField pythonScriptArgsField;
  33. [Tooltip("保存配置按钮")]
  34. public Button saveConfigButton;
  35. [Tooltip("加载配置按钮(从文件重新加载)")]
  36. public Button loadConfigButton;
  37. [Tooltip("应用配置按钮(将当前配置应用到运行时组件)")]
  38. public Button applyConfigButton;
  39. [Tooltip("自动启动Toggle(选中后下次Play时自动启动所有组件)")]
  40. public Toggle autoStartToggle;
  41. [Tooltip("无窗口运行Toggle(选中时 Python 加 --no_display,不弹绘制窗口,避免在游戏背后卡顿)")]
  42. public Toggle noDisplayToggle;
  43. [Header("UI面板控制")]
  44. [Tooltip("网络配置面板的根节点GameObject(用于显示/隐藏整个面板)")]
  45. public GameObject configPanelRoot;
  46. [Header("配置应用目标")]
  47. [Tooltip("Bridge(统一管理:硬件接收/转发Python/硬件控制/结果接收),应用全部网络配置")]
  48. public HardwareToPythonUdpBridge bridge;
  49. [Tooltip("LightGlue SDK 新桥接组件(可选,用于对比旧 Bridge 行为)")]
  50. public LightGlueBridgeBehaviour sdkBridge;
  51. [Tooltip("Python进程控制器(应用Python启动参数)")]
  52. public LightGlue.Unity.Python.PythonProcessController pythonProcessController;
  53. [Header("状态显示")]
  54. [Tooltip("配置状态文本(显示配置路径、保存/加载状态等)")]
  55. public Text statusText;
  56. private NetworkConfig _currentConfig;
  57. private void Start()
  58. {
  59. // 如果没有在 Inspector 指定目标组件,优先从全局 LightGlueManager 获取
  60. if (bridge == null && LightGlueManager.Instance != null)
  61. {
  62. bridge = LightGlueManager.Instance.bridge;
  63. }
  64. // sdkBridge 一般通过 Inspector 单独指定,用于对比测试;
  65. // 不从 LightGlueManager 自动获取,避免与旧 Bridge 混用。
  66. if (pythonProcessController == null && LightGlueManager.Instance != null)
  67. {
  68. pythonProcessController = LightGlueManager.Instance.pythonController;
  69. }
  70. // 初始化时加载配置
  71. LoadConfigFromFile();
  72. InitializeUI();
  73. SetupEventListeners();
  74. // 检查是否需要自动启动
  75. CheckAutoStart();
  76. }
  77. /// <summary>
  78. /// 检查是否需要自动启动组件
  79. /// </summary>
  80. private void CheckAutoStart()
  81. {
  82. if (_currentConfig != null && _currentConfig.autoStartOnPlay)
  83. {
  84. // 自动启动所有组件
  85. StartAllComponents();
  86. // 隐藏配置面板
  87. HideConfigPanel();
  88. }
  89. else
  90. {
  91. // 不自动启动,显示配置面板
  92. ShowConfigPanel();
  93. }
  94. }
  95. /// <summary>
  96. /// 初始化UI组件
  97. /// </summary>
  98. private void InitializeUI()
  99. {
  100. // 设置输入框类型
  101. if (hardwarePortField != null)
  102. {
  103. hardwarePortField.contentType = TMP_InputField.ContentType.IntegerNumber;
  104. }
  105. if (hardwareControlPortField != null)
  106. {
  107. hardwareControlPortField.contentType = TMP_InputField.ContentType.IntegerNumber;
  108. }
  109. if (pythonPortField != null)
  110. {
  111. pythonPortField.contentType = TMP_InputField.ContentType.IntegerNumber;
  112. }
  113. // Python启动参数输入框设置为多行
  114. if (pythonScriptArgsField != null)
  115. {
  116. pythonScriptArgsField.lineType = TMP_InputField.LineType.MultiLineNewline;
  117. pythonScriptArgsField.textComponent.enableWordWrapping = true;
  118. }
  119. // 初始化自动启动Toggle
  120. if (autoStartToggle != null)
  121. {
  122. autoStartToggle.isOn = _currentConfig != null ? _currentConfig.autoStartOnPlay : false;
  123. }
  124. // 初始化无窗口运行Toggle(优先从配置,否则从 PythonProcessController)
  125. if (noDisplayToggle != null)
  126. {
  127. noDisplayToggle.isOn = _currentConfig != null ? _currentConfig.pythonNoDisplay : (pythonProcessController != null && pythonProcessController.addNoDisplayWhenLaunching);
  128. }
  129. // 从配置更新UI显示(初始化时只在输入框为空时填入默认值)
  130. UpdateUIFromConfig(forceUpdate: false);
  131. }
  132. /// <summary>
  133. /// 设置事件监听器
  134. /// </summary>
  135. private void SetupEventListeners()
  136. {
  137. if (saveConfigButton != null)
  138. {
  139. saveConfigButton.onClick.AddListener(OnSaveConfigClicked);
  140. }
  141. if (loadConfigButton != null)
  142. {
  143. loadConfigButton.onClick.AddListener(OnLoadConfigClicked);
  144. }
  145. if (applyConfigButton != null)
  146. {
  147. applyConfigButton.onClick.AddListener(OnApplyConfigClicked);
  148. }
  149. // 自动启动Toggle变更事件
  150. if (autoStartToggle != null)
  151. {
  152. autoStartToggle.onValueChanged.AddListener(OnAutoStartToggleChanged);
  153. }
  154. if (noDisplayToggle != null)
  155. {
  156. noDisplayToggle.onValueChanged.AddListener(OnNoDisplayToggleChanged);
  157. }
  158. }
  159. /// <summary>
  160. /// 从文件加载配置
  161. /// </summary>
  162. public void LoadConfigFromFile()
  163. {
  164. _currentConfig = NetworkConfigManager.LoadConfig();
  165. UpdateUIFromConfig(forceUpdate: true); // 加载配置时强制更新所有输入框
  166. UpdateStatusText($"配置已从文件加载: {NetworkConfigManager.GetConfigPath()}");
  167. }
  168. /// <summary>
  169. /// 将“硬件绑定IP”设为当前获取的本机地址(可绑定到按钮“获取本机IP”等)。
  170. /// 会更新输入框和内存中的配置,不会自动保存或应用,需用户点击“保存/应用”生效。
  171. /// </summary>
  172. public void FillHardwareBindIpFromLocal()
  173. {
  174. string localIp = NetworkConfig.GetLocalBindIp();
  175. if (hardwareBindIpField != null)
  176. hardwareBindIpField.text = localIp;
  177. if (_currentConfig != null)
  178. _currentConfig.hardwareBindIp = localIp;
  179. UpdateStatusText($"硬件绑定IP已设为本机地址: {localIp}");
  180. }
  181. /// <summary>
  182. /// 从UI读取配置到内存(如果输入框为空则使用默认值)
  183. /// </summary>
  184. private NetworkConfig ReadConfigFromUI()
  185. {
  186. NetworkConfig config = NetworkConfig.CreateDefault(); // 使用默认值作为基础
  187. if (hardwareBindIpField != null && !string.IsNullOrWhiteSpace(hardwareBindIpField.text))
  188. {
  189. config.hardwareBindIp = hardwareBindIpField.text.Trim();
  190. }
  191. if (hardwarePortField != null && int.TryParse(hardwarePortField.text, out int hwPort) && hwPort > 0)
  192. {
  193. config.hardwarePort = hwPort;
  194. }
  195. if (hardwareControlIpField != null && !string.IsNullOrWhiteSpace(hardwareControlIpField.text))
  196. {
  197. config.hardwareControlIp = hardwareControlIpField.text.Trim();
  198. }
  199. if (hardwareControlPortField != null && int.TryParse(hardwareControlPortField.text, out int ctrlPort) && ctrlPort > 0)
  200. {
  201. config.hardwareControlPort = ctrlPort;
  202. }
  203. if (pythonIpField != null && !string.IsNullOrWhiteSpace(pythonIpField.text))
  204. {
  205. config.pythonIp = pythonIpField.text.Trim();
  206. }
  207. if (pythonPortField != null && int.TryParse(pythonPortField.text, out int pyPort) && pyPort > 0)
  208. {
  209. config.pythonPort = pyPort;
  210. }
  211. if (pythonScriptArgsField != null && !string.IsNullOrWhiteSpace(pythonScriptArgsField.text))
  212. {
  213. config.pythonScriptArgs = pythonScriptArgsField.text.Trim();
  214. }
  215. // 读取自动启动选项
  216. if (autoStartToggle != null)
  217. {
  218. config.autoStartOnPlay = autoStartToggle.isOn;
  219. }
  220. return config;
  221. }
  222. /// <summary>
  223. /// 将配置更新到UI显示(如果配置为空或输入框为空则使用默认值)
  224. /// </summary>
  225. /// <param name="forceUpdate">是否强制更新(true=覆盖输入框已有值,false=只在输入框为空时填入)</param>
  226. private void UpdateUIFromConfig(bool forceUpdate = false)
  227. {
  228. // 如果配置为空,创建默认配置
  229. if (_currentConfig == null)
  230. {
  231. _currentConfig = NetworkConfig.CreateDefault();
  232. }
  233. // 获取默认配置值(用于填充空输入框)
  234. NetworkConfig defaultConfig = NetworkConfig.CreateDefault();
  235. if (hardwareBindIpField != null)
  236. {
  237. // 如果强制更新或输入框为空,则填入配置值或默认值
  238. if (forceUpdate || string.IsNullOrWhiteSpace(hardwareBindIpField.text))
  239. {
  240. string value = string.IsNullOrWhiteSpace(_currentConfig.hardwareBindIp)
  241. ? defaultConfig.hardwareBindIp : _currentConfig.hardwareBindIp;
  242. hardwareBindIpField.text = value;
  243. }
  244. }
  245. if (hardwarePortField != null)
  246. {
  247. // 如果强制更新或输入框为空,则填入配置值或默认值
  248. if (forceUpdate || string.IsNullOrWhiteSpace(hardwarePortField.text))
  249. {
  250. int value = _currentConfig.hardwarePort > 0
  251. ? _currentConfig.hardwarePort : defaultConfig.hardwarePort;
  252. hardwarePortField.text = value.ToString();
  253. }
  254. }
  255. if (hardwareControlIpField != null)
  256. {
  257. // 如果强制更新或输入框为空,则填入配置值或默认值
  258. if (forceUpdate || string.IsNullOrWhiteSpace(hardwareControlIpField.text))
  259. {
  260. string value = string.IsNullOrWhiteSpace(_currentConfig.hardwareControlIp)
  261. ? defaultConfig.hardwareControlIp : _currentConfig.hardwareControlIp;
  262. hardwareControlIpField.text = value;
  263. }
  264. }
  265. if (hardwareControlPortField != null)
  266. {
  267. // 如果强制更新或输入框为空,则填入配置值或默认值
  268. if (forceUpdate || string.IsNullOrWhiteSpace(hardwareControlPortField.text))
  269. {
  270. int value = _currentConfig.hardwareControlPort > 0
  271. ? _currentConfig.hardwareControlPort : defaultConfig.hardwareControlPort;
  272. hardwareControlPortField.text = value.ToString();
  273. }
  274. }
  275. if (pythonIpField != null)
  276. {
  277. // 如果强制更新或输入框为空,则填入配置值或默认值
  278. if (forceUpdate || string.IsNullOrWhiteSpace(pythonIpField.text))
  279. {
  280. string value = string.IsNullOrWhiteSpace(_currentConfig.pythonIp)
  281. ? defaultConfig.pythonIp : _currentConfig.pythonIp;
  282. pythonIpField.text = value;
  283. }
  284. }
  285. if (pythonPortField != null)
  286. {
  287. // 如果强制更新或输入框为空,则填入配置值或默认值
  288. if (forceUpdate || string.IsNullOrWhiteSpace(pythonPortField.text))
  289. {
  290. int value = _currentConfig.pythonPort > 0
  291. ? _currentConfig.pythonPort : defaultConfig.pythonPort;
  292. pythonPortField.text = value.ToString();
  293. }
  294. }
  295. if (pythonScriptArgsField != null)
  296. {
  297. // 如果配置中的参数为空,使用默认值
  298. if (string.IsNullOrWhiteSpace(_currentConfig.pythonScriptArgs))
  299. {
  300. _currentConfig.pythonScriptArgs = defaultConfig.pythonScriptArgs;
  301. }
  302. // 如果强制更新或输入框为空,填入配置值或默认值
  303. if (forceUpdate || string.IsNullOrWhiteSpace(pythonScriptArgsField.text))
  304. {
  305. pythonScriptArgsField.text = _currentConfig.pythonScriptArgs;
  306. }
  307. }
  308. // 更新自动启动Toggle
  309. if (autoStartToggle != null)
  310. {
  311. autoStartToggle.isOn = _currentConfig.autoStartOnPlay;
  312. }
  313. if (noDisplayToggle != null)
  314. {
  315. noDisplayToggle.isOn = _currentConfig.pythonNoDisplay;
  316. }
  317. if (pythonProcessController != null && noDisplayToggle != null)
  318. {
  319. pythonProcessController.addNoDisplayWhenLaunching = noDisplayToggle.isOn;
  320. }
  321. }
  322. /// <summary>
  323. /// 保存配置按钮点击处理
  324. /// </summary>
  325. private void OnSaveConfigClicked()
  326. {
  327. NetworkConfig config = ReadConfigFromUI();
  328. if (!config.Validate())
  329. {
  330. UpdateStatusText("配置验证失败,请检查IP和端口格式");
  331. return;
  332. }
  333. // 保存自动启动与无窗口选项
  334. if (autoStartToggle != null)
  335. config.autoStartOnPlay = autoStartToggle.isOn;
  336. if (noDisplayToggle != null)
  337. config.pythonNoDisplay = noDisplayToggle.isOn;
  338. if (NetworkConfigManager.SaveConfig(config))
  339. {
  340. _currentConfig = config;
  341. UpdateStatusText($"配置已保存到: {NetworkConfigManager.GetConfigPath()} (自动启动: {config.autoStartOnPlay})");
  342. }
  343. else
  344. {
  345. UpdateStatusText("配置保存失败,请查看控制台日志");
  346. }
  347. }
  348. /// <summary>
  349. /// 加载配置按钮点击处理
  350. /// </summary>
  351. private void OnLoadConfigClicked()
  352. {
  353. LoadConfigFromFile();
  354. }
  355. /// <summary>
  356. /// 自动启动Toggle变更处理
  357. /// </summary>
  358. private void OnAutoStartToggleChanged(bool value)
  359. {
  360. if (_currentConfig != null)
  361. {
  362. _currentConfig.autoStartOnPlay = value;
  363. Debug.Log($"[NetworkConfigUI] 自动启动选项已更改为: {value}");
  364. if (NetworkConfigManager.SaveConfig(_currentConfig))
  365. Debug.Log($"[NetworkConfigUI] 自动启动选项已保存到配置文件");
  366. }
  367. }
  368. private void OnNoDisplayToggleChanged(bool value)
  369. {
  370. if (pythonProcessController != null)
  371. {
  372. pythonProcessController.addNoDisplayWhenLaunching = value;
  373. Debug.Log($"[NetworkConfigUI] 无窗口运行已更改为: " + value);
  374. }
  375. if (_currentConfig != null)
  376. {
  377. _currentConfig.pythonNoDisplay = value;
  378. if (NetworkConfigManager.SaveConfig(_currentConfig))
  379. Debug.Log($"[NetworkConfigUI] 无窗口运行选项已保存到配置文件");
  380. }
  381. }
  382. /// <summary>
  383. /// 应用配置按钮点击处理(将配置应用到运行时组件)
  384. /// </summary>
  385. private void OnApplyConfigClicked()
  386. {
  387. NetworkConfig config = ReadConfigFromUI();
  388. if (!config.Validate())
  389. {
  390. UpdateStatusText("配置验证失败,请检查IP和端口格式");
  391. return;
  392. }
  393. _currentConfig = config;
  394. if (autoStartToggle != null)
  395. config.autoStartOnPlay = autoStartToggle.isOn;
  396. if (noDisplayToggle != null)
  397. config.pythonNoDisplay = noDisplayToggle.isOn;
  398. // 应用到 PythonProcessController(无窗口选项立即生效,下次启动 Python 时使用)
  399. if (pythonProcessController != null && noDisplayToggle != null)
  400. {
  401. pythonProcessController.addNoDisplayWhenLaunching = noDisplayToggle.isOn;
  402. }
  403. // 应用到 Bridge(含硬件接收、Python 发送、硬件控制、结果接收;统一由下方全局重启生效)
  404. if (bridge != null)
  405. {
  406. bridge.hardwareBindIp = config.hardwareBindIp;
  407. bridge.hardwarePort = config.hardwarePort;
  408. bridge.hardwareTimeoutSeconds = config.hardwareTimeoutSeconds;
  409. bridge.pythonIp = config.pythonIp;
  410. bridge.pythonPort = config.pythonPort;
  411. bridge.hardwareControlIp = config.hardwareControlIp;
  412. bridge.hardwareControlPort = config.hardwareControlPort;
  413. Debug.Log("[NetworkConfigUI] 已应用配置到 Bridge(硬件/Python/硬件控制)");
  414. }
  415. // 应用到 SDK Bridge(仅更新字段,实际生效依赖其自身生命周期与 NetworkConfigManager)
  416. if (sdkBridge != null)
  417. {
  418. sdkBridge.hardwareBindIp = config.hardwareBindIp;
  419. sdkBridge.hardwarePort = config.hardwarePort;
  420. sdkBridge.hardwareTimeoutSeconds = config.hardwareTimeoutSeconds;
  421. sdkBridge.pythonIp = config.pythonIp;
  422. sdkBridge.pythonPort = config.pythonPort;
  423. sdkBridge.hardwareControlIp = config.hardwareControlIp;
  424. sdkBridge.hardwareControlPort = config.hardwareControlPort;
  425. Debug.Log("[NetworkConfigUI] 已应用配置到 SDK Bridge(字段已更新)");
  426. }
  427. // 应用到 Python 进程控制器
  428. if (pythonProcessController != null)
  429. {
  430. // 获取处理后的参数(替换占位符)
  431. string processedArgs = config.GetPythonScriptArgs();
  432. pythonProcessController.scriptArgs = processedArgs;
  433. Debug.Log($"[NetworkConfigUI] 已应用Python启动参数: {processedArgs}");
  434. }
  435. // 保存配置到文件(包括自动启动选项)
  436. if (NetworkConfigManager.SaveConfig(config))
  437. {
  438. _currentConfig = config;
  439. Debug.Log($"[NetworkConfigUI] 配置已保存到文件(包括自动启动选项: {config.autoStartOnPlay})");
  440. }
  441. else
  442. {
  443. Debug.LogWarning("[NetworkConfigUI] 配置保存失败,但已应用到运行时组件");
  444. }
  445. // 仅通过全局重启使新配置生效(关闭旧端口、重启监听与 Python)
  446. RestartAllComponents();
  447. }
  448. /// <summary>
  449. /// 启动所有相关组件(类似 Play 时的自动启动)
  450. /// </summary>
  451. public void StartAllComponents()
  452. {
  453. Debug.Log("[NetworkConfigUI] 开始启动所有相关组件...");
  454. // 1. 启动 Python 进程(若为「首帧后再启动」则仅启用组件,由 Bridge 收到首帧后启动)
  455. if (pythonProcessController != null)
  456. {
  457. if (!pythonProcessController.IsRunning)
  458. {
  459. if (!pythonProcessController.enabled)
  460. pythonProcessController.enabled = true;
  461. if (!pythonProcessController.gameObject.activeInHierarchy)
  462. pythonProcessController.gameObject.SetActive(true);
  463. bool deferToFirstFrame = bridge != null && bridge.startPythonOnFirstImageReceived && pythonProcessController.startOnFirstImageReceived;
  464. if (!deferToFirstFrame)
  465. {
  466. pythonProcessController.StartPython();
  467. Debug.Log("[NetworkConfigUI] PythonProcessController 已启动");
  468. }
  469. else
  470. Debug.Log("[NetworkConfigUI] 将等首帧图像到达后由 Bridge 启动 Python");
  471. }
  472. else
  473. Debug.Log("[NetworkConfigUI] PythonProcessController 已运行");
  474. }
  475. // 2. 启动 Bridge(含硬件接收、硬件控制、结果接收;最后启动,依赖 Python)
  476. if (bridge != null)
  477. {
  478. if (!bridge.enabled)
  479. {
  480. bridge.enabled = true;
  481. }
  482. else if (!bridge.gameObject.activeInHierarchy)
  483. {
  484. bridge.gameObject.SetActive(true);
  485. }
  486. Debug.Log("[NetworkConfigUI] HardwareToPythonUdpBridge 已启动");
  487. // 重启后重新向硬件下发 0x40 图传参数,否则硬件可能停止发图导致画面无更新
  488. if (bridge.transmissionConfig != null)
  489. {
  490. bridge.ApplyConfig(bridge.transmissionConfig);
  491. Debug.Log("[NetworkConfigUI] 已重新下发 0x40 图传参数到硬件,摄像头图像应恢复");
  492. }
  493. }
  494. // 3. 启动 SDK Bridge(如果有),用于与旧 Bridge 同场对比
  495. if (sdkBridge != null)
  496. {
  497. if (!sdkBridge.enabled)
  498. {
  499. sdkBridge.enabled = true;
  500. }
  501. else if (!sdkBridge.gameObject.activeInHierarchy)
  502. {
  503. sdkBridge.gameObject.SetActive(true);
  504. }
  505. Debug.Log("[NetworkConfigUI] LightGlueBridgeBehaviour (SDK) 已启动");
  506. }
  507. UpdateStatusText("所有相关组件已启动");
  508. HideConfigPanel();
  509. }
  510. /// <summary>
  511. /// 停止所有相关组件
  512. /// </summary>
  513. public void StopAllComponents()
  514. {
  515. Debug.Log("[NetworkConfigUI] 开始停止所有相关组件...");
  516. // 1. 停止Bridge(先停止,避免继续发送数据)
  517. if (bridge != null && bridge.enabled)
  518. {
  519. bridge.enabled = false;
  520. Debug.Log("[NetworkConfigUI] HardwareToPythonUdpBridge 已停止");
  521. }
  522. // 1b. 停止 SDK Bridge
  523. if (sdkBridge != null && sdkBridge.enabled)
  524. {
  525. sdkBridge.enabled = false;
  526. Debug.Log("[NetworkConfigUI] LightGlueBridgeBehaviour (SDK) 已停止");
  527. }
  528. // 2. 停止 Python 进程
  529. if (pythonProcessController != null && pythonProcessController.IsRunning)
  530. {
  531. pythonProcessController.StopPython();
  532. Debug.Log("[NetworkConfigUI] PythonProcessController 已停止");
  533. }
  534. // 3. 停止硬件控制由 Bridge 统一管理,Bridge 禁用时一并关闭
  535. UpdateStatusText("所有相关组件已停止");
  536. }
  537. /// <summary>
  538. /// 重启所有相关组件(先停止再启动,用于应用新配置)
  539. /// </summary>
  540. public void RestartAllComponents()
  541. {
  542. Debug.Log("[NetworkConfigUI] 开始重启所有相关组件以应用新配置...");
  543. // 先停止所有组件
  544. StopAllComponents();
  545. // 等待一帧,确保组件完全停止
  546. StartCoroutine(RestartComponentsCoroutine());
  547. }
  548. /// <summary>
  549. /// 重启组件的协程:先停止所有组件并等待端口释放,再按顺序重新启动(全局唯一重启入口)
  550. /// </summary>
  551. private System.Collections.IEnumerator RestartComponentsCoroutine()
  552. {
  553. // 等待 Python 进程退出及 UDP 端口释放后再启动(过短会导致 IsRunning 仍为 true 而不执行 StartPython)
  554. yield return new WaitForSeconds(0.5f);
  555. StartAllComponents();
  556. UpdateStatusText("所有相关组件已重启,新配置已生效");
  557. }
  558. /// <summary>
  559. /// 更新状态文本
  560. /// </summary>
  561. private void UpdateStatusText(string message)
  562. {
  563. if (statusText != null)
  564. {
  565. statusText.text = message;
  566. }
  567. Debug.Log($"[NetworkConfigUI] {message}");
  568. }
  569. /// <summary>
  570. /// 获取当前配置(供外部访问)
  571. /// </summary>
  572. public NetworkConfig GetConfig()
  573. {
  574. if (_currentConfig == null)
  575. _currentConfig = NetworkConfigManager.LoadConfig();
  576. return _currentConfig;
  577. }
  578. /// <summary>
  579. /// 显示配置面板
  580. /// </summary>
  581. public void ShowConfigPanel()
  582. {
  583. if (configPanelRoot != null)
  584. {
  585. configPanelRoot.SetActive(true);
  586. Debug.Log("[NetworkConfigUI] 配置面板已显示");
  587. }
  588. }
  589. /// <summary>
  590. /// 隐藏配置面板
  591. /// </summary>
  592. public void HideConfigPanel()
  593. {
  594. if (configPanelRoot != null)
  595. {
  596. configPanelRoot.SetActive(false);
  597. Debug.Log("[NetworkConfigUI] 配置面板已隐藏");
  598. }
  599. }
  600. /// <summary>
  601. /// 切换配置面板显示/隐藏状态
  602. /// </summary>
  603. public void ToggleConfigPanel()
  604. {
  605. if (configPanelRoot != null)
  606. {
  607. bool newState = !configPanelRoot.activeSelf;
  608. configPanelRoot.SetActive(newState);
  609. Debug.Log($"[NetworkConfigUI] 配置面板已{(newState ? "显示" : "隐藏")}");
  610. }
  611. }
  612. private void OnDestroy()
  613. {
  614. // 如果没有全局 LightGlueManager(单场景老用法),仍由本 UI 负责停止组件;
  615. // 如有 LightGlueManager,则由其统一管理 Python / Bridge 生命周期(硬件控制已合并到 Bridge),
  616. // 场景切换时不再在这里主动 StopAllComponents,避免切场景就把 Python 关掉。
  617. if (LightGlueManager.Instance == null)
  618. {
  619. StopAllComponents();
  620. }
  621. // 清理事件监听器
  622. if (saveConfigButton != null)
  623. {
  624. saveConfigButton.onClick.RemoveListener(OnSaveConfigClicked);
  625. }
  626. if (loadConfigButton != null)
  627. {
  628. loadConfigButton.onClick.RemoveListener(OnLoadConfigClicked);
  629. }
  630. if (applyConfigButton != null)
  631. {
  632. applyConfigButton.onClick.RemoveListener(OnApplyConfigClicked);
  633. }
  634. if (autoStartToggle != null)
  635. autoStartToggle.onValueChanged.RemoveListener(OnAutoStartToggleChanged);
  636. if (noDisplayToggle != null)
  637. noDisplayToggle.onValueChanged.RemoveListener(OnNoDisplayToggleChanged);
  638. }
  639. private void OnApplicationQuit()
  640. {
  641. // 如果没有全局 LightGlueManager(单场景老用法),仍然由本UI负责停止组件;
  642. // 有全局管理器时,由 LightGlueManager 统一在应用退出时关闭 Python / UDP 组件。
  643. if (LightGlueManager.Instance == null)
  644. {
  645. StopAllComponents();
  646. }
  647. }
  648. }
  649. }