Files
HDRobotics/Assets/Scripts/AppManager.cs

292 lines
9.8 KiB
C#
Raw Normal View History

2025-11-06 15:28:16 +09:00
using System;
2025-10-24 11:55:43 +09:00
using System.Collections;
using System.Collections.Generic;
using System.IO;
2025-10-30 09:31:05 +09:00
using System.Threading;
using UnityEngine;
/// <summary>
/// <20><><EFBFBD>ø<EFBFBD><C3B8><EFBFBD><EFBFBD>̼<EFBFBD><CCBC><EFBFBD> <20><>Ʈ<EFBFBD><C6AE> <20><><EFBFBD><EFBFBD>Ʈ<EFBFBD><C6AE><EFBFBD><EFBFBD> <20><>ü <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Ŭ<><C5AC><EFBFBD><EFBFBD>
/// <20>̱<EFBFBD><CCB1><EFBFBD>(Singleton) <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ϸ<EFBFBD>, Model, View, Presenter <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>(<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>)<29><> <20><><EFBFBD><EFBFBD>
/// <20><> <20><><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20>ε<EFBFBD>, <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20>ʱ<EFBFBD>ȭ, <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
/// </summary>
public class AppManager : MonoBehaviour
{
public static AppManager Instance { get; private set; }
// --- <20>ν<EFBFBD><CEBD><EFBFBD><EFBFBD><EFBFBD> <20>Ҵ<EFBFBD> <20><> (Static Views) ---
[Header("UI Views")]
[SerializeField] private ProgramView view; // <20><><EFBFBD>α׷<CEB1> <20><><EFBFBD><EFBFBD> UI
[SerializeField] private TCPView tcpView; // <20><>ǥ ǥ<><C7A5> UI
[SerializeField] private RobotInfoView robotInfoView;// <20>κ<EFBFBD> <20><><EFBFBD><EFBFBD> UI
[SerializeField] private ProgramInfoView programInfoView; // <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> UI
[SerializeField] private EnvView envView; // <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> UI
[SerializeField] private PopupView popupView; // <20>˾<EFBFBD> UI
[Header("Scene Views & Controllers")]
[SerializeField] private GripperCollide gripperCollide;
[SerializeField] private RobotController robotController; // 3D <20>κ<EFBFBD> <20><><EFBFBD><EFBFBD>
[SerializeField] private PointManagerView pointManagerView; // <20><><EFBFBD><EFBFBD>Ʈ <20><>Ŀ <20><><EFBFBD><EFBFBD>
[SerializeField] private PathLineView pathLineView; // <20>̵<EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
[SerializeField] private AudioSource robotAudio; // <20>κ<EFBFBD> ȿ<><C8BF><EFBFBD><EFBFBD> <20>ҽ<EFBFBD>
2025-10-30 09:31:05 +09:00
// --- <20><>Ÿ<EFBFBD><C5B8> <20><><EFBFBD><EFBFBD> <20><> (Interaction Views) ---
private InteractionView leftInteractionView;
private InteractionView rightInteractionView;
// --- <20><><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD> ---
[SerializeField] private float motorStatePollInterval = 1.0f; // <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> Ȯ<><C8AE> <20>ֱ<EFBFBD>
public CancellationTokenSource cancellationTokenSource; // <20>񵿱<EFBFBD> <20>۾<EFBFBD> <20><><EFBFBD>ҿ<EFBFBD> <20><>ū
private bool isModelAndStaticViewsReady = false; // <20>ʱ<EFBFBD>ȭ <20>Ϸ<EFBFBD> <20><><EFBFBD><EFBFBD>
// --- MVP <20>ٽ<EFBFBD> <20><><EFBFBD><EFBFBD> ---
private ProgramModel model; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
private ProgramPresenter presenter; // <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// --- <20><>Ʈ<EFBFBD><C6AE>ũ <20><><EFBFBD><EFBFBD> ---
private string hostip;
private int tcpPort;
private int udpPort;
private string configFileName = "config.cfg"; // <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20≯<EFBFBD>
void Awake()
{
// <20>̱<EFBFBD><CCB1><EFBFBD> <20>ʱ<EFBFBD>ȭ
if (Instance != null && Instance != this)
{
Destroy(gameObject);
}
else
{
Instance = this;
}
// --- <20><>Ʈ<EFBFBD><C6AE>ũ <20><><EFBFBD><EFBFBD>ȭ <20><><EFBFBD><EFBFBD> (RTT <20><><EFBFBD><EFBFBD>) ---
// Nagle <20>˰<EFBFBD><CBB0><EFBFBD><EFBFBD><EFBFBD> <20><>Ȱ<EFBFBD><C8B0>ȭ:
// <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>Ͷ<EFBFBD><CDB6><EFBFBD> <20><><EFBFBD>ۿ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ʰ<EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>Ͽ<EFBFBD> <20><><EFBFBD><EFBFBD> <20>ð<EFBFBD>(Latency)<29><> <20><><EFBFBD><EFBFBD>
System.Net.ServicePointManager.UseNagleAlgorithm = false;
// 100-Continue <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>:
// POST <20><>û <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD>ٸ<EFBFBD><D9B8><EFBFBD> <20><><EFBFBD>ʿ<EFBFBD><CABF><EFBFBD> <20>պ<EFBFBD> <20>ð<EFBFBD><C3B0><EFBFBD> <20><><EFBFBD><EFBFBD>
System.Net.ServicePointManager.Expect100Continue = false;
// <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD>:
// <20><EFBFBD><E2BABB><EFBFBD><EFBFBD> <20>÷<EFBFBD> HTTP <20><>û<EFBFBD><C3BB> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> ó<><C3B3><EFBFBD>ǵ<EFBFBD><C7B5><EFBFBD> <20><>
System.Net.ServicePointManager.DefaultConnectionLimit = 10;
}
async void Start()
{
// 1. <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20>ε<EFBFBD> (IP/Port)
LoadConfig();
// 2. <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><> <20>ʱ<EFBFBD>ȭ (<28><>Ʈ<EFBFBD><C6AE>ũ <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>)
model = new ProgramModel(hostip, tcpPort, udpPort, robotController);
await model.InitializeAsync();
// 3. <20>ʼ<EFBFBD> View <20>Ҵ<EFBFBD> Ȯ<><C8AE>
2025-11-14 13:50:31 +09:00
if (view == null || tcpView == null || robotInfoView == null || programInfoView == null || envView == null || robotController == null ||
2025-11-06 15:28:16 +09:00
pointManagerView == null || popupView == null || pathLineView == null)
{
Debug.LogError("AppManager<65><72> <20>ν<EFBFBD><CEBD><EFBFBD><EFBFBD>Ϳ<EFBFBD> [Static Views]<5D><> <20><><EFBFBD><EFBFBD> <20>Ҵ<EFBFBD><D2B4><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>", this);
return;
}
isModelAndStaticViewsReady = true;
// 4. Presenter <20><><EFBFBD><EFBFBD> <20>õ<EFBFBD> (InteractionView<65><77> <20><><EFBFBD>ϵ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
TryCreatePresenter();
}
/// <summary>
/// InteractionView(<28><>Ʈ<EFBFBD>ѷ<EFBFBD>)<29><> <20>ʱ<EFBFBD>ȭ<EFBFBD><C8AD> <20><> ȣ<><C8A3><EFBFBD>Ͽ<EFBFBD> <20>ڽ<EFBFBD><DABD><EFBFBD> AppManager<65><72> <20><><EFBFBD><EFBFBD>
/// </summary>
public void RegisterView(InteractionView Iview)
{
if (Iview.handSide == HandSide.Left)
{
this.leftInteractionView = Iview;
}
else if (Iview.handSide == HandSide.Right)
{
this.rightInteractionView = Iview;
}
// <20><><EFBFBD><EFBFBD> <20><>Ʈ<EFBFBD>ѷ<EFBFBD><D1B7><EFBFBD> <20>غ<EFBFBD><D8BA>Ǹ<EFBFBD> Presenter <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ٽ<EFBFBD> <20>õ<EFBFBD>
TryCreatePresenter();
}
/// <summary>
/// <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>(Model, Static Views, Interaction Views)<29><> <20>غ<EFBFBD><D8BA>Ǿ<EFBFBD><C7BE><EFBFBD><EFBFBD><EFBFBD> Ȯ<><C8AE><EFBFBD>ϰ<EFBFBD>,
/// <20>غ<EFBFBD><D8BA>Ǿ<EFBFBD><C7BE>ٸ<EFBFBD> Presenter<65><72> <20><><EFBFBD><EFBFBD><EFBFBD>Ͽ<EFBFBD> MVP <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ϼ<EFBFBD>
/// </summary>
private void TryCreatePresenter()
{
// <20>̹<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>Ǿ<EFBFBD><C7BE>ų<EFBFBD>, <20>ʼ<EFBFBD> <20><><EFBFBD>Ұ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ϸ<EFBFBD> <20>ߴ<EFBFBD>
if (presenter != null) return;
if (!isModelAndStaticViewsReady || leftInteractionView == null || rightInteractionView == null)
2025-11-06 15:28:16 +09:00
{
return;
}
2025-11-19 18:38:48 +09:00
cancellationTokenSource = new CancellationTokenSource();
2025-11-06 15:28:16 +09:00
try
{
presenter = new ProgramPresenter(
model,
view,
tcpView,
robotInfoView,
2025-11-13 21:23:28 +09:00
programInfoView,
2025-11-14 13:50:31 +09:00
envView,
gripperCollide,
leftInteractionView, rightInteractionView,
2025-11-06 15:28:16 +09:00
pointManagerView,
popupView,
2025-11-19 18:38:48 +09:00
pathLineView,
robotAudio,
cancellationTokenSource
2025-11-06 15:28:16 +09:00
);
}
catch (Exception e)
{
Debug.LogError($"Presenter <20><><EFBFBD><EFBFBD><EFBFBD>ڿ<EFBFBD><DABF><EFBFBD> <20><><EFBFBD><EFBFBD> <20>߻<EFBFBD>: {e.Message}\n{e.StackTrace}");
return; // <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
}
// <20>ʱ<EFBFBD>ȭ <20>ļ<EFBFBD> <20>۾<EFBFBD>
presenter.RegisterControlledRobot(robotController);
_ = presenter.UpdateMotorStateAsync(); // <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20>ʱ<EFBFBD> Ȯ<><C8AE>
// <20><><EFBFBD>׶<EFBFBD><D7B6><EFBFBD><EFBFBD><EFBFBD> <20>۾<EFBFBD> <20><><EFBFBD><EFBFBD> (<28><>ġ <20><><EFBFBD><EFBFBD>ȭ, <20><><EFBFBD><EFBFBD> üũ <20><>)
_ = model.GetTCPAsync(cancellationTokenSource.Token);
_ = model.StartMovementCheckLoopAsync(cancellationTokenSource.Token);
2025-11-19 18:38:48 +09:00
_ = model.GetMovementState(cancellationTokenSource.Token);
view.DisplayProgram(null);
// <20>ֱ<EFBFBD><D6B1><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> Ȯ<><C8AE> <20><>ƾ <20><><EFBFBD><EFBFBD>
2025-10-24 11:55:43 +09:00
StartCoroutine(PollMotorStateCoroutine());
}
2025-11-19 18:38:48 +09:00
private void OnDestroy()
{
if (Instance == this)
{
cancellationTokenSource?.Cancel();
cancellationTokenSource?.Dispose();
}
}
/// <summary>
/// StreamingAssets <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> config.cfg <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>о<EFBFBD> IP<49><50> Port <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ε<EFBFBD>
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><EFBFBD><E2BABB>(127.0.0.1:8888)<29><> <20><><EFBFBD><EFBFBD>
/// </summary>
private void LoadConfig()
{
// <20><EFBFBD><E2BABB> <20><><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> ã<><C3A3> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>)
string defaultIp = "127.0.0.1";
int defaultPort = 8888;
string path = Path.Combine(Application.streamingAssetsPath, configFileName);
if (File.Exists(path))
{
try
{
var config = new Dictionary<string, string>();
string[] lines = File.ReadAllLines(path);
foreach (string line in lines)
{
if (string.IsNullOrWhiteSpace(line) || line.Trim().StartsWith("#"))
continue;
string[] parts = line.Split('=');
if (parts.Length == 2)
{
config[parts[0].Trim()] = parts[1].Trim();
}
}
if (config.ContainsKey("IP_ADDRESS"))
{
hostip = config["IP_ADDRESS"];
}
else
{
hostip = defaultIp;
Debug.LogWarning($"config <20><><EFBFBD>Ͽ<EFBFBD> IP_ADDRESS Ű<><C5B0> <20><><EFBFBD><EFBFBD><EFBFBD>ϴ<EFBFBD>. <20><EFBFBD><E2BABB>({defaultIp}) <20><><EFBFBD><EFBFBD>.");
}
if (config.ContainsKey("TCP_PORT"))
{
if (int.TryParse(config["TCP_PORT"], out int parsedPort))
{
tcpPort = parsedPort;
}
else
{
tcpPort = defaultPort;
Debug.LogWarning($"config <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> TCP_PORT <20><><EFBFBD><EFBFBD> <20>߸<EFBFBD><DFB8>Ǿ<EFBFBD><C7BE><EFBFBD><EFBFBD>ϴ<EFBFBD>. <20><EFBFBD><E2BABB>({defaultPort}) <20><><EFBFBD><EFBFBD>.");
}
}
else
{
tcpPort = defaultPort;
Debug.LogWarning($"config <20><><EFBFBD>Ͽ<EFBFBD> TCP_PORT Ű<><C5B0> <20><><EFBFBD><EFBFBD><EFBFBD>ϴ<EFBFBD>. <20><EFBFBD><E2BABB>({defaultPort}) <20><><EFBFBD><EFBFBD>.");
}
if (config.ContainsKey("UDP_PORT"))
{
if (int.TryParse(config["UDP_PORT"], out int parsedPort))
{
udpPort = parsedPort;
}
else
{
udpPort = defaultPort;
Debug.LogWarning($"config <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> UDP_PORT <20><><EFBFBD><EFBFBD> <20>߸<EFBFBD><DFB8>Ǿ<EFBFBD><C7BE><EFBFBD><EFBFBD>ϴ<EFBFBD>. <20><EFBFBD><E2BABB>({defaultPort}) <20><><EFBFBD><EFBFBD>.");
}
}
else
{
udpPort = defaultPort;
Debug.LogWarning($"config <20><><EFBFBD>Ͽ<EFBFBD> UDP_PORT Ű<><C5B0> <20><><EFBFBD><EFBFBD><EFBFBD>ϴ<EFBFBD>. <20><EFBFBD><E2BABB>({defaultPort}) <20><><EFBFBD><EFBFBD>.");
}
Debug.Log($"Config <20>ε<EFBFBD> <20><><EFBFBD><EFBFBD>: {hostip}:{tcpPort}/{udpPort}");
}
catch (System.Exception e)
{
Debug.LogError($"Config <20><><EFBFBD><EFBFBD> <20>ε<EFBFBD> <20><> <20><><EFBFBD><EFBFBD> <20>߻<EFBFBD>: {e.Message}. <20><EFBFBD><E2BABB> <20><><EFBFBD><EFBFBD>.");
hostip = defaultIp;
tcpPort = defaultPort;
udpPort = defaultPort;
}
}
else
{
Debug.LogWarning($"{configFileName} <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ã<><C3A3> <20><> <20><><EFBFBD><EFBFBD><EFBFBD>ϴ<EFBFBD>. <20><EFBFBD><E2BABB>({defaultIp}:{defaultPort}) <20><><EFBFBD><EFBFBD>.");
hostip = defaultIp;
tcpPort = defaultPort;
udpPort = defaultPort;
}
}
2025-10-24 11:55:43 +09:00
/// <summary>
/// <20>ֱ<EFBFBD><D6B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>κ<EFBFBD><CEBA><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>(ON/OFF)<29><> Ȯ<><C8AE><EFBFBD>Ͽ<EFBFBD> UI<55><49> <20><><EFBFBD><EFBFBD><EFBFBD>ϴ<EFBFBD> <20>ڷ<EFBFBD>ƾ
/// </summary>
2025-10-24 11:55:43 +09:00
private IEnumerator PollMotorStateCoroutine()
{
while (true)
{
yield return new WaitForSeconds(motorStatePollInterval);
_ = presenter.UpdateMotorStateAsync();
}
}
}