250 lines
7.3 KiB
C#
250 lines
7.3 KiB
C#
using UnityEngine;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Text;
|
|
using System.Threading;
|
|
|
|
/// <summary>
|
|
/// 중앙 UDP 수신 매니저. 하나의 포트에서 수신한 데이터를 여러 StreamingleFacialReceiver에 분배.
|
|
/// 싱글톤으로 동작하며, Receiver가 Register 시 자동 생성됨.
|
|
///
|
|
/// 사용법:
|
|
/// StreamingleFacialReceiver에서 useSharedPort = true로 설정하면
|
|
/// 자동으로 이 마스터를 통해 UDP 데이터를 공유 수신합니다.
|
|
/// 같은 포트에 여러 Receiver를 등록하면 동일한 데이터를 모든 Receiver가 받습니다.
|
|
/// </summary>
|
|
public class StreamingleFacialUdpMaster : MonoBehaviour
|
|
{
|
|
private static StreamingleFacialUdpMaster _instance;
|
|
public static StreamingleFacialUdpMaster Instance
|
|
{
|
|
get
|
|
{
|
|
if (_instance == null)
|
|
{
|
|
var go = new GameObject("[StreamingleFacialUdpMaster]");
|
|
go.hideFlags = HideFlags.HideInHierarchy;
|
|
DontDestroyOnLoad(go);
|
|
_instance = go.AddComponent<StreamingleFacialUdpMaster>();
|
|
}
|
|
return _instance;
|
|
}
|
|
}
|
|
|
|
private class PortListener
|
|
{
|
|
public int port;
|
|
public UdpClient udp;
|
|
public Thread thread;
|
|
public volatile bool isRunning;
|
|
public volatile string latestMessage = "";
|
|
public string lastDistributedMessage = "";
|
|
public readonly HashSet<StreamingleFacialReceiver> receivers = new HashSet<StreamingleFacialReceiver>();
|
|
public readonly object lockObj = new object();
|
|
}
|
|
|
|
private readonly Dictionary<int, PortListener> listeners = new Dictionary<int, PortListener>();
|
|
|
|
/// <summary>
|
|
/// Receiver를 지정 포트에 등록. 해당 포트의 리스너가 없으면 자동 생성.
|
|
/// </summary>
|
|
public void Register(int port, StreamingleFacialReceiver receiver)
|
|
{
|
|
if (!listeners.TryGetValue(port, out var listener))
|
|
{
|
|
listener = new PortListener { port = port };
|
|
listeners[port] = listener;
|
|
StartListener(listener);
|
|
}
|
|
|
|
lock (listener.lockObj)
|
|
{
|
|
listener.receivers.Add(receiver);
|
|
}
|
|
|
|
Debug.Log($"[FacialUdpMaster] Port {port} 등록 (총 {listener.receivers.Count}개 Receiver)");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Receiver를 지정 포트에서 해제. 해당 포트에 Receiver가 없으면 리스너 종료.
|
|
/// </summary>
|
|
public void Unregister(int port, StreamingleFacialReceiver receiver)
|
|
{
|
|
if (!listeners.TryGetValue(port, out var listener)) return;
|
|
|
|
bool shouldStop = false;
|
|
lock (listener.lockObj)
|
|
{
|
|
listener.receivers.Remove(receiver);
|
|
shouldStop = listener.receivers.Count == 0;
|
|
}
|
|
|
|
if (shouldStop)
|
|
{
|
|
StopListener(listener);
|
|
listeners.Remove(port);
|
|
Debug.Log($"[FacialUdpMaster] Port {port} 리스너 종료 (Receiver 없음)");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 포트 변경 시 호출. 기존 포트 해제 → 새 포트 등록.
|
|
/// </summary>
|
|
public void SwitchPort(int oldPort, int newPort, StreamingleFacialReceiver receiver)
|
|
{
|
|
Unregister(oldPort, receiver);
|
|
Register(newPort, receiver);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 특정 포트의 리스너를 재시작 (포트 문제 시 복구용)
|
|
/// </summary>
|
|
public void RestartPort(int port)
|
|
{
|
|
if (!listeners.TryGetValue(port, out var listener)) return;
|
|
|
|
StopListener(listener);
|
|
|
|
// 리스너 상태 리셋 후 재시작
|
|
listener.latestMessage = "";
|
|
listener.lastDistributedMessage = "";
|
|
StartListener(listener);
|
|
|
|
Debug.Log($"[FacialUdpMaster] Port {port} 리스너 재시작");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 현재 등록된 포트와 각 포트의 Receiver 수를 반환 (디버그용)
|
|
/// </summary>
|
|
public Dictionary<int, int> GetPortStatus()
|
|
{
|
|
var status = new Dictionary<int, int>();
|
|
foreach (var kvp in listeners)
|
|
{
|
|
status[kvp.Key] = kvp.Value.receivers.Count;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
private void StartListener(PortListener listener)
|
|
{
|
|
try
|
|
{
|
|
listener.udp = new UdpClient(listener.port);
|
|
listener.udp.Client.ReceiveTimeout = 5;
|
|
listener.isRunning = true;
|
|
listener.thread = new Thread(() => ListenThread(listener))
|
|
{
|
|
IsBackground = true
|
|
};
|
|
listener.thread.Start();
|
|
Debug.Log($"[FacialUdpMaster] Port {listener.port} 수신 시작");
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"[FacialUdpMaster] Port {listener.port} 시작 실패: {e.Message}");
|
|
}
|
|
}
|
|
|
|
private void StopListener(PortListener listener)
|
|
{
|
|
listener.isRunning = false;
|
|
|
|
try
|
|
{
|
|
if (listener.udp != null)
|
|
{
|
|
listener.udp.Close();
|
|
listener.udp.Dispose();
|
|
listener.udp = null;
|
|
}
|
|
}
|
|
catch (Exception) { }
|
|
|
|
if (listener.thread != null && listener.thread.IsAlive)
|
|
{
|
|
listener.thread.Join(300);
|
|
listener.thread = null;
|
|
}
|
|
}
|
|
|
|
private void ListenThread(PortListener listener)
|
|
{
|
|
while (listener.isRunning)
|
|
{
|
|
try
|
|
{
|
|
IPEndPoint remoteEP = null;
|
|
byte[] data = listener.udp.Receive(ref remoteEP);
|
|
if (data != null && data.Length > 0)
|
|
{
|
|
listener.latestMessage = Encoding.ASCII.GetString(data);
|
|
}
|
|
}
|
|
catch (SocketException e)
|
|
{
|
|
if (!listener.isRunning) break;
|
|
if (e.SocketErrorCode != SocketError.TimedOut)
|
|
{
|
|
Debug.LogError($"[FacialUdpMaster] Port {listener.port} 수신 오류: {e.Message}");
|
|
}
|
|
}
|
|
catch (ObjectDisposedException)
|
|
{
|
|
break;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (!listener.isRunning) break;
|
|
Debug.LogError($"[FacialUdpMaster] Port {listener.port} 오류: {e.Message}");
|
|
}
|
|
|
|
Thread.Sleep(5);
|
|
}
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
foreach (var kvp in listeners)
|
|
{
|
|
var listener = kvp.Value;
|
|
string msg = listener.latestMessage;
|
|
|
|
if (string.IsNullOrEmpty(msg) || msg == listener.lastDistributedMessage)
|
|
continue;
|
|
|
|
listener.lastDistributedMessage = msg;
|
|
|
|
lock (listener.lockObj)
|
|
{
|
|
foreach (var receiver in listener.receivers)
|
|
{
|
|
if (receiver != null && receiver.isActiveAndEnabled)
|
|
{
|
|
receiver.SetMessageFromMaster(msg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnDestroy()
|
|
{
|
|
foreach (var kvp in listeners)
|
|
{
|
|
StopListener(kvp.Value);
|
|
}
|
|
listeners.Clear();
|
|
|
|
if (_instance == this)
|
|
_instance = null;
|
|
}
|
|
|
|
void OnApplicationQuit()
|
|
{
|
|
OnDestroy();
|
|
}
|
|
}
|