WerWolfInteraktion/OSCClient.cs

392 lines
14 KiB
C#

using CoreOSC;
using CoreOSC.IO;
using CoreOSC.Types;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
namespace Wolf
{
internal class OSCClient
{
private UdpClient udpClientSend;
private UdpClient udpClientReceive;
private readonly SemaphoreSlim _requestSemaphore;
private string _lastSentAddress = "";
private object[] _lastSentArguments = Array.Empty<object>();
public OSCClient(string hostname, int port)
{
// 1. Lokalen Port binden (für Empfang)
udpClientSend = new UdpClient(hostname, port);
udpClientReceive = new UdpClient(new IPEndPoint(IPAddress.Any, port));
_requestSemaphore = new SemaphoreSlim(1, 1);
}
public async Task SendMessage(string endpoint)
{
await _requestSemaphore.WaitAsync();
try
{
var message = new OscMessage(new Address(endpoint));
_lastSentAddress = endpoint;
_lastSentArguments = Array.Empty<object>();
await udpClientSend.SendMessageAsync(message);
}
finally
{
_requestSemaphore.Release();
}
}
public async Task SpeakId(int textId)
{
await _requestSemaphore.WaitAsync();
try
{
var message = new OscMessage(
address: new Address("/speak"),
arguments: new object[]
{
textId
});
_lastSentAddress = "/speak";
_lastSentArguments = new object[] { textId };
await udpClientSend.SendMessageAsync(message);
}
finally
{
_requestSemaphore.Release();
}
}
public async Task Read(string text, bool readStory = true)
{
await _requestSemaphore.WaitAsync();
try
{
string endpoint = readStory ? "/read_story" : "/read";
var message = new OscMessage(
address: new Address(endpoint),
arguments: new object[]
{
text, 2
});
_lastSentAddress = endpoint;
_lastSentArguments = new object[] { text, 2 };
await udpClientSend.SendMessageAsync(message);
}
finally
{
_requestSemaphore.Release();
}
}
public async Task SendMessageWithArguments(string endpoint, params object[] param)
{
await _requestSemaphore.WaitAsync();
try
{
var message = new OscMessage(
address: new Address(endpoint),
arguments: param
);
_lastSentAddress = endpoint;
_lastSentArguments = param;
await udpClientSend.SendMessageAsync(message);
}
finally
{
_requestSemaphore.Release();
}
}
public async Task ReceiveMessageWithoutArguments()
{
var response = await udpClientReceive.ReceiveMessageAsync();
Console.WriteLine(response.Address.Value);
// Do something with the arguments, (depending on the address)
foreach (var argument in response.Arguments)
{
Console.WriteLine(argument.GetType());
}
}
public async Task ReceiveMessageWithArguments()
{
var response = await udpClientReceive.ReceiveMessageAsync();
if (response.Address.Value == "/example4")
{
Console.WriteLine("/example4");
var stringArgument = (string)response.Arguments.ElementAt(0);
var intArgument = (int)response.Arguments.ElementAt(1);
var falseArgument = (OscFalse)response.Arguments.ElementAt(2);
Console.WriteLine($"First string argument: {stringArgument}");
Console.WriteLine($"Second int argument: {intArgument}");
Console.WriteLine($"Third OscFalse argument:{falseArgument}");
}
}
/// <summary>
/// Generische Methode zur Ausführung von Request-Response-Kommunikation mit Retry-Logik.
/// </summary>
private async Task<T> ExecuteRequestWithRetry<T>(
string expectedAddress,
OscMessage requestMessage,
Func<OscMessage, T> responseParser,
int maxRetries = 50,
int timeoutSeconds = 20)
{
await _requestSemaphore.WaitAsync();
try
{
_lastSentAddress = requestMessage.Address.Value;
_lastSentArguments = requestMessage.Arguments.ToArray();
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
try
{
// Anfrage senden
await udpClientSend.SendMessageAsync(requestMessage);
// Auf Response warten mit Timeout
var receiveTask = udpClientReceive.ReceiveMessageAsync();
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(timeoutSeconds));
var completed = await Task.WhenAny(receiveTask, timeoutTask);
if (completed == receiveTask)
{
var response = await receiveTask;
// Prüfen, ob es die erwartete Antwort ist
if (response.Address.Value == expectedAddress)
{
try
{
return responseParser(response);
}
catch (Exception parseEx)
{
// Argument-Parsing-Fehler, ignorieren und nächste Anfrage senden
System.Diagnostics.Debug.WriteLine($"Parse error on attempt {attempt}: {parseEx.Message}");
continue;
}
}
// Falsche Antwort → ignorieren und weiter versuchen
}
// Timeout → erneut versuchen
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Request attempt {attempt} failed: {ex.Message}");
}
}
// Alle Versuche fehlgeschlagen
throw new TimeoutException($"Request to {expectedAddress} failed after {maxRetries} attempts");
}
finally
{
_requestSemaphore.Release();
}
}
public async Task<List<int>> GetAllIds()
{
var message = new OscMessage(new Address("/getAllIds"));
try
{
var result = await ExecuteRequestWithRetry(
expectedAddress: "/getAllIds",
requestMessage: message,
responseParser: response =>
{
var playerIds = new List<int>();
foreach (var argument in response.Arguments)
{
playerIds.Add((int)argument);
}
return playerIds;
});
return result;
}
catch (TimeoutException)
{
return new List<int>();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"GetAllIds error: {ex.Message}");
return new List<int>();
}
}
// < 0 = no confirmation; 0 = confirm; > 0 = player id
//public async Task<int> GetPlayerId(List<Player> targets, List<Player> pois)
//{
// //SendMessageWithArguments("/getPlayerID", targets.Select(p => p.id).ToArray(), pois.Select(p => p.id).ToArray());
// var message = new OscMessage(
// address: new Address("/getPlayerID"),
// //arguments: new object[] { targets.Select(p => p.id).ToArray(), pois.Select(p => p.id).ToArray() }
// arguments: Concat(targets.Select(p => p.id).Cast<object>().ToArray().Concat(pois.Select(p => p.id).Cast<object>()).ToArray()
// );
// await udpClientSend.SendMessageAsync(message);
// var response = await udpClientReceive.ReceiveMessageAsync();
// if (response.Address.Value == "/getPlayerID")
// {
// return (int)response.Arguments.ElementAt(0);
// }
// return -1;
//}
public async Task<int> GetPlayerId(List<Player> targets, List<Player> pois)
{
// Arrays extrahieren
var arr2 = targets.Select(p => p.id).ToArray();
var arr1 = pois.Select(p => p.id).ToArray();
// OSC-kompatibles int-Argument-Array bauen:
// Länge1 | Werte1 | Länge2 | Werte2
var args = new List<object>();
args.Add(arr1.Length);
foreach (var id in arr1)
args.Add(id);
args.Add(arr2.Length);
foreach (var id in arr2)
args.Add(id);
// Nachricht vorbereiten
var message = new OscMessage(
new Address("/getPlayerID"),
args.ToArray()
);
try
{
var result = await ExecuteRequestWithRetry(
expectedAddress: "/getPlayerID",
requestMessage: message,
responseParser: response =>
{
if (response.Arguments.Count() > 0)
{
return (int)response.Arguments.ElementAt(0);
}
throw new InvalidOperationException("No arguments in response");
},
maxRetries: 50,
timeoutSeconds: 20);
return result;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"GetPlayerId error: {ex.Message}");
return -1;
}
}
public async Task<bool> GetConfirmation(List<Player> players)
{
var args = new List<object>();
foreach (var p in players)
{
args.Add(p.id);
}
var message = new OscMessage(
new Address("/confirmation"),
args.ToArray()
);
try
{
var result = await ExecuteRequestWithRetry(
expectedAddress: "/confirmation",
requestMessage: message,
responseParser: response =>
{
if (response.Arguments.Count() > 0)
{
var firstArg = response.Arguments.ElementAt(0);
if (firstArg is OscFalse)
{
return false;
}
if (firstArg is OscTrue)
{
return true;
}
throw new InvalidOperationException($"Unexpected argument type: {firstArg.GetType().Name}");
}
throw new InvalidOperationException("No arguments in response");
},
maxRetries: 50,
timeoutSeconds: 20);
return result;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"GetConfirmation error: {ex.Message}");
return false;
}
}
public async Task<bool> GetConfirmationEveryoneAsleep()
{
var message = new OscMessage(new Address("/everyoneAsleep"));
try
{
var result = await ExecuteRequestWithRetry(
expectedAddress: "/everyoneAsleep",
requestMessage: message,
responseParser: response =>
{
if (response.Arguments.Count() > 0)
{
var firstArg = response.Arguments.ElementAt(0);
if (firstArg is OscFalse)
{
return false;
}
if (firstArg is OscTrue)
{
return true;
}
throw new InvalidOperationException($"Unexpected argument type: {firstArg.GetType().Name}");
}
throw new InvalidOperationException("No arguments in response");
},
maxRetries: 50,
timeoutSeconds: 30);
return result;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"GetConfirmationEveryoneAsleep error: {ex.Message}");
return false;
}
}
}
}