Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add nuget package build and pull in most refactors from ShockOSC's internal OSC Query Server #2

Merged
merged 4 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -347,3 +347,4 @@ healthchecksdb

# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
.idea
13 changes: 0 additions & 13 deletions .idea/.idea.OscQueryLibrary/.idea/.gitignore

This file was deleted.

8 changes: 0 additions & 8 deletions .idea/.idea.OscQueryLibrary/.idea/indexLayout.xml

This file was deleted.

7 changes: 0 additions & 7 deletions .idea/.idea.OscQueryLibrary/.idea/vcs.xml

This file was deleted.

4 changes: 3 additions & 1 deletion OscQueryExample/OscQueryExample.csproj
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>Exe</OutputType>
<LangVersion>12</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="LucHeart.CoreOSC" Version="1.2.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
125 changes: 64 additions & 61 deletions OscQueryExample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Text.Json;
using LucHeart.CoreOSC;
using OscQueryLibrary;
using OscQueryLibrary.Utils;
using Serilog;
using Serilog.Events;
using Swan.Logging;
Expand All @@ -11,7 +12,6 @@ namespace OscQueryExample;

public static class Program
{
private static bool _oscServerActive;
private static OscDuplex? _gameConnection = null;

private static readonly HashSet<string> AvailableParameters = new();
Expand All @@ -24,92 +24,93 @@ public static class Program
};

private static bool _isMuted;

private static readonly Serilog.ILogger Logger = Log.ForContext(typeof(Program));

public static void Main(string[] args)
private static Serilog.ILogger _logger = null!;

public static async Task Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.Filter.ByExcluding(ev =>
ev.Exception is InvalidDataException a && a.Message.StartsWith("Invocation provides"))
.WriteTo.Console(LogEventLevel.Information,
.MinimumLevel.Debug()
.WriteTo.Console(LogEventLevel.Verbose,
"[{Timestamp:HH:mm:ss} {Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}")
.CreateLogger();

// ReSharper disable once RedundantAssignment
var isDebug = false;
#if DEBUG
isDebug = true;
#endif
if ((args.Length > 0 && args[0] == "--debug") || isDebug)
{
Log.Information("Debug logging enabled");
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.Filter.ByExcluding(ev =>
ev.Exception is InvalidDataException a && a.Message.StartsWith("Invocation provides"))
.WriteTo.Console(LogEventLevel.Debug,
"[{Timestamp:HH:mm:ss} {Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}")
.CreateLogger();
}

// listen for VRC on every network interface
var host = Dns.GetHostEntry(Dns.GetHostName());
foreach (var ip in host.AddressList)
{
if (ip.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork)
continue;

var ipAddress = ip.ToString();
_ = new OscQueryServer(
"HelloWorld", // service name
ipAddress, // ip address for udp and http server
FoundVrcClient, // optional callback on vrc discovery
UpdateAvailableParameters // parameter list callback on vrc discovery
);
}
_ = new OscQueryServer(
_logger = Log.ForContext(typeof(Program));

var oscQueryServers = new List<OscQueryServer>();

//listen for VRC on every network interface
// var host = await Dns.GetHostEntryAsync(Dns.GetHostName());
// foreach (var ip in host.AddressList)
// {
// if (ip.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork)
// continue;
//
// var server = new OscQueryServer(
// "HelloWorld", // service name
// ip
// );
//
// server.FoundVrcClient += FoundVrcClient; // event on vrc discovery
// server.ParameterUpdate += UpdateAvailableParameters; // event on parameter list update
//
// oscQueryServers.Add(server);
//
// server.Start();
// }

var localHostServer = new OscQueryServer(
"HelloWorld", // service name
"127.0.0.1", // ip address for udp and http server
FoundVrcClient, // optional callback on vrc discovery
UpdateAvailableParameters // parameter list callback on vrc discovery
IPAddress.Loopback // ip address for udp and http server
);
localHostServer.FoundVrcClient += FoundVrcClient; // event on vrc discovery
localHostServer.ParameterUpdate += UpdateAvailableParameters; // event on parameter list update

oscQueryServers.Add(localHostServer);

localHostServer.Start();

while (true)
{
Thread.Sleep(10000);
// ToggleMute();
Console.ReadLine();
await ToggleMute();
}
// ReSharper disable once FunctionNeverReturns
}

private static void FoundVrcClient()
private static CancellationTokenSource _loopCancellationToken = new CancellationTokenSource();
private static OscQueryServer? _currentOscQueryServer = null;

private static Task FoundVrcClient(OscQueryServer oscQueryServer, IPEndPoint ipEndPoint)
{
// stop tasks
_oscServerActive = false;
Task.Delay(1000).Wait(); // wait for tasks to stop
_loopCancellationToken.Cancel();
_loopCancellationToken = new CancellationTokenSource();
_gameConnection?.Dispose();
_gameConnection = null;

_logger.Information("Found VRC client at {EndPoint}", ipEndPoint);
_logger.Information("Starting listening for VRC client at {Port}", oscQueryServer.OscReceivePort);

_gameConnection = new OscDuplex(
new IPEndPoint(IPAddress.Parse(OscQueryServer.OscIpAddress), OscQueryServer.OscReceivePort),
new IPEndPoint(IPAddress.Parse(OscQueryServer.OscIpAddress), OscQueryServer.OscSendPort)
);
_oscServerActive = true;
Task.Run(ReceiverLoopAsync);
new IPEndPoint(ipEndPoint.Address, oscQueryServer.OscReceivePort),
ipEndPoint);
_currentOscQueryServer = oscQueryServer;
ErrorHandledTask.Run(ReceiverLoopAsync);
return Task.CompletedTask;
}

private static async Task ReceiverLoopAsync()
{
while (_oscServerActive)
var currentCancellationToken = _loopCancellationToken.Token;
while (!currentCancellationToken.IsCancellationRequested)
{
try
{
await ReceiveLogic();
}
catch (Exception e)
{
Debug.WriteLine(e, "Error in receiver loop");
_logger.Error(e, "Error in receiver loop");
}
}
// ReSharper disable once FunctionNeverReturns
Expand All @@ -128,15 +129,16 @@ private static async Task ReceiveLogic()
Debug.WriteLine(e, "Error receiving message");
return;
}
var addr = received.Address;

var addr = received.Address;

switch (addr)
{
case "/avatar/change":
{
var avatarId = received.Arguments.ElementAtOrDefault(0);
Console.WriteLine($"Avatar changed: {avatarId}");
await OscQueryServer.GetParameters();
await _currentOscQueryServer!.GetParameters();
break;
}
case "/avatar/parameters/MuteSelf":
Expand All @@ -157,15 +159,15 @@ private static async Task SendGameMessage(string address, params object?[]? argu
await _gameConnection.SendAsync(new OscMessage(address, arguments));
}

private static void UpdateAvailableParameters(Dictionary<string, object?> parameterList)
private static Task UpdateAvailableParameters(Dictionary<string, object?> parameterList, string s)
{
AvailableParameters.Clear();
foreach (var parameter in parameterList)
{
var parameterName = parameter.Key.Replace("/avatar/parameters/", "");
if (ParameterList.Contains(parameterName))
AvailableParameters.Add(parameterName);

if (parameterName == "MuteSelf" && parameter.Value != null)
{
_isMuted = ((JsonElement)parameter.Value).GetBoolean();
Expand All @@ -174,6 +176,7 @@ private static void UpdateAvailableParameters(Dictionary<string, object?> parame
}

Console.WriteLine($"Found {AvailableParameters.Count} parameters");
return Task.CompletedTask;
}

private static async Task ToggleMute()
Expand All @@ -182,7 +185,7 @@ private static async Task ToggleMute()
Debug.WriteLine("Unmuting...");
else
Debug.WriteLine("Muting...");

await SendGameMessage("/input/Voice", false);
await Task.Delay(50);
await SendGameMessage("/input/Voice", true);
Expand Down
51 changes: 51 additions & 0 deletions OscQueryLibrary/Models/HostInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Net;
using System.Text.Json.Serialization;
using OscQueryLibrary.Utils;

// ReSharper disable InconsistentNaming

namespace OscQueryLibrary.Models;

public sealed class HostInfo
{
[JsonPropertyName("NAME")]
public required string Name { get; set; }

[JsonPropertyName("OSC_IP")]
[JsonConverter(typeof(JsonIPAddressConverter))]
public required IPAddress OscIp { get; set; }

[JsonPropertyName("OSC_PORT")]
public required ushort OscPort { get; set; }

[JsonPropertyName("OSC_TRANSPORT")]
[JsonConverter(typeof(JsonStringEnumConverter<OscTransportType>))]
public required OscTransportType OscTransport { get; set; }

[JsonPropertyName("EXTENSIONS")]
public required ExtensionsNode Extensions { get; set; }

public enum OscTransportType
{
TCP,
UDP
}

public sealed class ExtensionsNode
{
[JsonPropertyName("ACCESS")]
public required bool Access { get; set; }

[JsonPropertyName("CLIPMODE")]
public required bool ClipMode { get; set; }

[JsonPropertyName("RANGE")]
public required bool Range { get; set; }

[JsonPropertyName("TYPE")]
public required bool Type { get; set; }

[JsonPropertyName("VALUE")]
public required bool Value { get; set; }
}
}
28 changes: 28 additions & 0 deletions OscQueryLibrary/Models/Node.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Text.Json.Serialization;

namespace OscQueryLibrary.Models;

// technically every class in the JSON is this "Node" class but that's gross
public class Node
{
[JsonPropertyOrder(-4)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("DESCRIPTION")]
public string? Description { get; set; }

[JsonPropertyOrder(-3)]
[JsonPropertyName("FULL_PATH")]
public required string FullPath { get; set; }

[JsonPropertyOrder(-2)]
[JsonPropertyName("ACCESS")]
public required int Access { get; set; }
}

public class Node<T> : Node
{
[JsonPropertyOrder(-1)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("CONTENTS")]
public T? Contents { get; set; }
}
32 changes: 32 additions & 0 deletions OscQueryLibrary/Models/OscQueryModels.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Text.Json.Serialization;

namespace OscQueryLibrary.Models;

public sealed class RootNode : Node<RootNode.RootContents>
{
public sealed class RootContents
{
[JsonPropertyName("avatar")] public Node<AvatarContents>? Avatar { get; set; }
}
}

public sealed class AvatarContents
{
[JsonPropertyName("change")] public required OscParameterNodeEnd<string> Change { get; set; }

[JsonPropertyName("parameters")] public Node<IDictionary<string, OscParameterNode>>? Parameters { get; set; }
}

public sealed class OscParameterNode : Node<IDictionary<string, OscParameterNode>>
{
[JsonPropertyName("TYPE")] public string? Type { get; set; }

[JsonPropertyName("VALUE")] public IEnumerable<object>? Value { get; set; }
}

public sealed class OscParameterNodeEnd<T> : Node
{
[JsonPropertyName("TYPE")] public required string Type { get; set; }

[JsonPropertyName("VALUE")] public required IEnumerable<T> Value { get; set; }
}
Loading