created centralized UDP Listener

This commit is contained in:
tom.hempel
2025-10-25 14:58:22 +02:00
parent 4ddb89d011
commit 3f72973ff5
10 changed files with 585 additions and 534 deletions

View File

@ -2,9 +2,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
public class UDPAvatarReceiver : MonoBehaviour
@ -38,8 +35,6 @@ public class UDPAvatarReceiver : MonoBehaviour
[SerializeField] private bool showDebugInfo = false;
[SerializeField] private bool logReceivedPackets = false;
private UdpClient udpClient;
private Thread udpListenerThread;
private bool threadRunning = false;
private int listenPort;
private Dictionary<string, Transform> boneCache;
@ -205,122 +200,63 @@ public class UDPAvatarReceiver : MonoBehaviour
{
try
{
if (allowPortSharing)
// Wait for shared listener to be ready
if (Convai.Scripts.Runtime.Multiplayer.SharedUDPListener.Instance == null)
{
// Create UDP client with port reuse for local testing
udpClient = new UdpClient();
udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, listenPort));
}
else
{
// Standard UDP client binding
udpClient = new UdpClient(listenPort);
Debug.LogError("SharedUDPListener not found! Make sure it's in the scene.");
enableReceiver = false;
return;
}
// Subscribe to shared listener
Convai.Scripts.Runtime.Multiplayer.SharedUDPListener.Instance.OnPacketReceived += HandlePacketReceived;
threadRunning = true;
udpListenerThread = new Thread(new ThreadStart(UDPListenerThread));
udpListenerThread.IsBackground = true;
udpListenerThread.Start();
if (showDebugInfo)
Debug.Log($"UDP Avatar Receiver started on port {listenPort} (filtering binary avatar data only, Port sharing: {allowPortSharing})");
Debug.Log($"UDP Avatar Receiver subscribed to shared listener on port {listenPort} (filtering avatar magic 0x{AVATAR_MAGIC:X})");
}
catch (Exception e)
{
if (allowPortSharing)
Debug.LogError($"Failed to subscribe to shared UDP listener: {e.Message}");
enableReceiver = false;
}
}
void HandlePacketReceived(byte[] data, IPEndPoint senderEndPoint)
{
// Check if this is avatar data (by magic number)
if (!IsAvatarData(data)) return;
// Process avatar packet
CompactAvatarData avatarData = DeserializeCompactData(data);
// Check if this is from the target player (0 means accept from any player)
if (targetPlayerID == 0 || avatarData.playerID == targetPlayerID)
{
// Check for packet loss
if (avatarData.sequenceNumber > lastSequenceNumber + 1)
{
Debug.LogWarning($"Failed to start UDP listener with port sharing: {e.Message}");
Debug.LogWarning("Trying with different port...");
TryAlternativePort();
packetsDropped += (int)(avatarData.sequenceNumber - lastSequenceNumber - 1);
}
else
lastSequenceNumber = avatarData.sequenceNumber;
packetsReceived++;
// Store the new data (thread-safe)
lock (dataLock)
{
Debug.LogError($"Failed to start UDP listener: {e.Message}");
enableReceiver = false;
lastReceivedData = avatarData;
hasNewData = true;
}
if (logReceivedPackets && packetsReceived % 30 == 0)
{
string modeStr = avatarData.isFullDataMode ? "FULL" : "OPT";
Debug.Log($"Received {modeStr} packet #{avatarData.sequenceNumber} from player {avatarData.playerID}, size: {data.Length} bytes, bones: {avatarData.bones?.Length ?? 0}, blend shapes: {avatarData.blendShapes?.Length ?? 0}");
}
}
}
void TryAlternativePort()
{
// Try a few alternative ports for local testing
int[] alternativePorts = { 8081, 8082, 8083, 8084, 8085 };
foreach (int port in alternativePorts)
{
try
{
udpClient = new UdpClient(port);
threadRunning = true;
udpListenerThread = new Thread(new ThreadStart(UDPListenerThread));
udpListenerThread.IsBackground = true;
udpListenerThread.Start();
Debug.Log($"UDP listener started on alternative port {port}");
return;
}
catch (Exception)
{
// Try next port
continue;
}
}
Debug.LogError("Failed to start UDP listener on any available port");
enableReceiver = false;
}
void UDPListenerThread()
{
IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
while (threadRunning)
{
try
{
byte[] data = udpClient.Receive(ref remoteEndPoint);
if (data.Length > 0 && IsAvatarData(data))
{
CompactAvatarData avatarData = DeserializeCompactData(data);
// Check if this is from the target player (0 means accept from any player)
if (targetPlayerID == 0 || avatarData.playerID == targetPlayerID)
{
// Check for packet loss
if (avatarData.sequenceNumber > lastSequenceNumber + 1)
{
packetsDropped += (int)(avatarData.sequenceNumber - lastSequenceNumber - 1);
}
lastSequenceNumber = avatarData.sequenceNumber;
packetsReceived++;
// Store the new data (thread-safe)
lock (dataLock)
{
lastReceivedData = avatarData;
hasNewData = true;
}
if (logReceivedPackets && packetsReceived % 30 == 0)
{
string modeStr = avatarData.isFullDataMode ? "FULL" : "OPT";
Debug.Log($"Received {modeStr} packet #{avatarData.sequenceNumber} from player {avatarData.playerID}, size: {data.Length} bytes, bones: {avatarData.bones?.Length ?? 0}, blend shapes: {avatarData.blendShapes?.Length ?? 0}");
}
}
}
}
catch (Exception e)
{
if (threadRunning) // Only log errors if we're supposed to be running
{
Debug.LogError($"UDP receive error: {e.Message}");
}
}
}
}
bool IsAvatarData(byte[] data)
{
@ -618,17 +554,10 @@ public class UDPAvatarReceiver : MonoBehaviour
{
threadRunning = false;
if (udpClient != null)
// Unsubscribe from shared listener
if (Convai.Scripts.Runtime.Multiplayer.SharedUDPListener.Instance != null)
{
udpClient.Close();
udpClient.Dispose();
udpClient = null;
}
if (udpListenerThread != null)
{
udpListenerThread.Join(1000); // Wait up to 1 second for thread to finish
udpListenerThread = null;
Convai.Scripts.Runtime.Multiplayer.SharedUDPListener.Instance.OnPacketReceived -= HandlePacketReceived;
}
}
@ -688,9 +617,9 @@ public class UDPAvatarReceiver : MonoBehaviour
int GetActualListenPort()
{
if (udpClient?.Client?.LocalEndPoint != null)
if (Convai.Scripts.Runtime.Multiplayer.SharedUDPListener.Instance != null)
{
return ((IPEndPoint)udpClient.Client.LocalEndPoint).Port;
return Convai.Scripts.Runtime.Multiplayer.SharedUDPListener.Instance.ListenPort;
}
return listenPort;
}