using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using UnityEngine;
///
/// ¾îÇø®ÄÉÀ̼ÇÀÇ ¿£Æ®¸® Æ÷ÀÎÆ®ÀÌÀÚ Àüü °ü¸®ÀÚ Å¬·¡½º
/// ½Ì±ÛÅæ(Singleton) ÆÐÅÏÀ» »ç¿ëÇϸç, Model, View, Presenter °£ÀÇ ¿¬°á(ÀÇÁ¸¼º ÁÖÀÔ)À» ´ã´ç
/// ¾Û ½ÃÀÛ ½Ã ¼³Á¤ ÆÄÀÏ ·Îµå, Åë½Å ¸ðµâ ÃʱâÈ, ¸ÞÀÎ ·çÇÁ ½ÃÀÛ µîÀ» ¼öÇà
///
public class AppManager : MonoBehaviour
{
public static AppManager Instance { get; private set; }
// --- ÀνºÆåÅÍ ÇÒ´ç ºä (Static Views) ---
[Header("UI Views")]
[SerializeField] private ProgramView view; // ÇÁ·Î±×·¥ °ü¸® UI
[SerializeField] private TCPView tcpView; // ÁÂÇ¥ Ç¥½Ã UI
[SerializeField] private RobotInfoView robotInfoView;// ·Îº¿ »óÅ UI
[SerializeField] private ProgramInfoView programInfoView; // ½ÇÇà Á¦¾î UI
[SerializeField] private EnvView envView; // ¸ÞÀÎ ¼³Á¤ UI
[SerializeField] private PopupView popupView; // ÆË¾÷ UI
[Header("Scene Views & Controllers")]
[SerializeField] private GripperCollide gripperCollide;
[SerializeField] private RobotController robotController; // 3D ·Îº¿ Á¦¾î
[SerializeField] private PointManagerView pointManagerView; // Æ÷ÀÎÆ® ¸¶Ä¿ °ü¸®
[SerializeField] private PathLineView pathLineView; // À̵¿ °æ·Î ¶óÀÎ
[SerializeField] private AudioSource robotAudio; // ·Îº¿ È¿°úÀ½ ¼Ò½º
// --- ·±Å¸ÀÓ µî·Ï ºä (Interaction Views) ---
private InteractionView leftInteractionView;
private InteractionView rightInteractionView;
// --- ¼³Á¤ ¹× »óÅ ---
[SerializeField] private float motorStatePollInterval = 1.0f; // ¸ðÅÍ »óÅ ȮÀÎ ÁÖ±â
public CancellationTokenSource cancellationTokenSource; // ºñµ¿±â ÀÛ¾÷ Ãë¼Ò¿ë ÅäÅ«
private bool isModelAndStaticViewsReady = false; // ÃʱâÈ ¿Ï·á ¿©ºÎ
// --- MVP ÇÙ½É ¸ðµâ ---
private ProgramModel model; // µ¥ÀÌÅÍ ¹× Åë½Å ·ÎÁ÷
private ProgramPresenter presenter; // ºä¿Í ¸ðµ¨ÀÇ ÁßÀçÀÚ
// --- ³×Æ®¿öÅ© ¼³Á¤ ---
private string hostip;
private int tcpPort;
private int udpPort;
private string configFileName = "config.cfg"; // ¼³Á¤ ÆÄÀÏ À̸§
void Awake()
{
// ½Ì±ÛÅæ ÃʱâÈ
if (Instance != null && Instance != this)
{
Destroy(gameObject);
}
else
{
Instance = this;
}
// --- ³×Æ®¿öÅ© ÃÖÀûÈ ¼³Á¤ (RTT °¨¼Ò) ---
// Nagle ¾Ë°í¸®Áò ºñȰ¼ºÈ:
// ÀÛÀº µ¥ÀÌÅÍ¶óµµ ¹öÆÛ¿¡ ¸ðÀ¸Áö ¾Ê°í Áï½Ã Àü¼ÛÇÏ¿© Áö¿¬ ½Ã°£(Latency)À» ÁÙÀÓ
System.Net.ServicePointManager.UseNagleAlgorithm = false;
// 100-Continue ´ë±â Á¦°Å:
// POST ¿äû ½Ã Çì´õ¸¦ ¸ÕÀú º¸³»°í ¼¹ö ½ÂÀÎÀ» ±â´Ù¸®´Â ºÒÇÊ¿äÇÑ ¿Õº¹ ½Ã°£À» Á¦°Å
System.Net.ServicePointManager.Expect100Continue = false;
// µ¿½Ã ¿¬°á ¼ö Áõ°¡:
// ±âº»°ªÀ» ´Ã·Á HTTP ¿äûÀÌ º´¸ñ Çö»ó ¾øÀÌ º´·Ä 󸮵ǵµ·Ï ÇÔ
System.Net.ServicePointManager.DefaultConnectionLimit = 10;
}
async void Start()
{
// 1. ¼³Á¤ ÆÄÀÏ ·Îµå (IP/Port)
LoadConfig();
// 2. ¸ðµ¨ »ý¼º ¹× ÃʱâÈ (³×Æ®¿öÅ© ¿¬°á ½ÃÀÛ)
model = new ProgramModel(hostip, tcpPort, udpPort, robotController);
await model.InitializeAsync();
// 3. Çʼö View ÇÒ´ç È®ÀÎ
if (view == null || tcpView == null || robotInfoView == null || programInfoView == null || envView == null || robotController == null ||
pointManagerView == null || popupView == null || pathLineView == null)
{
Debug.LogError("AppManagerÀÇ ÀνºÆåÅÍ¿¡ [Static Views]°¡ ¸ðµÎ ÇÒ´çµÇÁö ¾ÊÀ½", this);
return;
}
isModelAndStaticViewsReady = true;
// 4. Presenter »ý¼º ½Ãµµ (InteractionView°¡ µî·ÏµÉ ¶§±îÁö ´ë±âÇÒ ¼öµµ ÀÖÀ½
TryCreatePresenter();
}
///
/// InteractionView(ÄÁÆ®·Ñ·¯)°¡ Ãʱâ鵃 ¶§ È£ÃâÇÏ¿© ÀÚ½ÅÀ» AppManager¿¡ µî·Ï
///
public void RegisterView(InteractionView Iview)
{
if (Iview.handSide == HandSide.Left)
{
this.leftInteractionView = Iview;
}
else if (Iview.handSide == HandSide.Right)
{
this.rightInteractionView = Iview;
}
// ¾ç¼Õ ÄÁÆ®·Ñ·¯°¡ ÁغñµÇ¸é Presenter »ý¼ºÀ» ´Ù½Ã ½Ãµµ
TryCreatePresenter();
}
///
/// ¸ðµç ÀÇÁ¸¼º(Model, Static Views, Interaction Views)ÀÌ ÁغñµÇ¾ú´ÂÁö È®ÀÎÇϰí,
/// ÁغñµÇ¾ú´Ù¸é Presenter¸¦ »ý¼ºÇÏ¿© MVP ±¸Á¶¸¦ ¿Ï¼º
///
private void TryCreatePresenter()
{
// ÀÌ¹Ì »ý¼ºµÇ¾ú°Å³ª, Çʼö ¿ä¼Ò°¡ ºÎÁ·Çϸé Áß´Ü
if (presenter != null) return;
if (!isModelAndStaticViewsReady || leftInteractionView == null || rightInteractionView == null)
{
return;
}
cancellationTokenSource = new CancellationTokenSource();
try
{
presenter = new ProgramPresenter(
model,
view,
tcpView,
robotInfoView,
programInfoView,
envView,
gripperCollide,
leftInteractionView, rightInteractionView,
pointManagerView,
popupView,
pathLineView,
robotAudio,
cancellationTokenSource
);
}
catch (Exception e)
{
Debug.LogError($"Presenter »ý¼ºÀÚ¿¡¼ ¿À·ù ¹ß»ý: {e.Message}\n{e.StackTrace}");
return; // »ý¼º ½ÇÆÐ
}
// ÃʱâÈ ÈÄ¼Ó ÀÛ¾÷
presenter.RegisterControlledRobot(robotController);
_ = presenter.UpdateMotorStateAsync(); // ¸ðÅÍ »óÅ Ãʱâ È®ÀÎ
// ¹é±×¶ó¿îµå ÀÛ¾÷ ½ÃÀÛ (À§Ä¡ µ¿±âÈ, ¿¡·¯ üũ µî)
_ = model.GetTCPAsync(cancellationTokenSource.Token);
_ = model.StartMovementCheckLoopAsync(cancellationTokenSource.Token);
_ = model.GetMovementState(cancellationTokenSource.Token);
view.DisplayProgram(null);
// ÁÖ±âÀûÀÎ ¸ðÅÍ »óÅ ȮÀÎ ·çƾ ½ÃÀÛ
StartCoroutine(PollMotorStateCoroutine());
}
private void OnDestroy()
{
if (Instance == this)
{
cancellationTokenSource?.Cancel();
cancellationTokenSource?.Dispose();
}
}
///
/// StreamingAssets Æú´õ¿¡¼ config.cfg ÆÄÀÏÀ» Àоî IP¿Í Port Á¤º¸¸¦ ·Îµå
/// ÆÄÀÏÀÌ ¾øÀ¸¸é ±âº»°ª(127.0.0.1:8888)À» »ç¿ë
///
private void LoadConfig()
{
// ±âº»°ª ¼³Á¤ (ÆÄÀÏÀ» ¸ø ãÀ» °æ¿ì ´ëºñ)
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[] 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 ÆÄÀÏ¿¡ IP_ADDRESS ۰¡ ¾ø½À´Ï´Ù. ±âº»°ª({defaultIp}) »ç¿ë.");
}
if (config.ContainsKey("TCP_PORT"))
{
if (int.TryParse(config["TCP_PORT"], out int parsedPort))
{
tcpPort = parsedPort;
}
else
{
tcpPort = defaultPort;
Debug.LogWarning($"config ÆÄÀÏÀÇ TCP_PORT °ªÀÌ À߸øµÇ¾ú½À´Ï´Ù. ±âº»°ª({defaultPort}) »ç¿ë.");
}
}
else
{
tcpPort = defaultPort;
Debug.LogWarning($"config ÆÄÀÏ¿¡ TCP_PORT ۰¡ ¾ø½À´Ï´Ù. ±âº»°ª({defaultPort}) »ç¿ë.");
}
if (config.ContainsKey("UDP_PORT"))
{
if (int.TryParse(config["UDP_PORT"], out int parsedPort))
{
udpPort = parsedPort;
}
else
{
udpPort = defaultPort;
Debug.LogWarning($"config ÆÄÀÏÀÇ UDP_PORT °ªÀÌ À߸øµÇ¾ú½À´Ï´Ù. ±âº»°ª({defaultPort}) »ç¿ë.");
}
}
else
{
udpPort = defaultPort;
Debug.LogWarning($"config ÆÄÀÏ¿¡ UDP_PORT ۰¡ ¾ø½À´Ï´Ù. ±âº»°ª({defaultPort}) »ç¿ë.");
}
Debug.Log($"Config ·Îµå ¼º°ø: {hostip}:{tcpPort}/{udpPort}");
}
catch (System.Exception e)
{
Debug.LogError($"Config ÆÄÀÏ ·Îµå Áß ¿À·ù ¹ß»ý: {e.Message}. ±âº»°ª »ç¿ë.");
hostip = defaultIp;
tcpPort = defaultPort;
udpPort = defaultPort;
}
}
else
{
Debug.LogWarning($"{configFileName} ÆÄÀÏÀ» ãÀ» ¼ö ¾ø½À´Ï´Ù. ±âº»°ª({defaultIp}:{defaultPort}) »ç¿ë.");
hostip = defaultIp;
tcpPort = defaultPort;
udpPort = defaultPort;
}
}
///
/// ÁÖ±âÀûÀ¸·Î ·Îº¿ÀÇ ¸ðÅÍ »óÅÂ(ON/OFF)¸¦ È®ÀÎÇÏ¿© UI¸¦ °»½ÅÇÏ´Â ÄÚ·çÆ¾
///
private IEnumerator PollMotorStateCoroutine()
{
while (true)
{
yield return new WaitForSeconds(motorStatePollInterval);
_ = presenter.UpdateMotorStateAsync();
}
}
}