Skip to content

Commit

Permalink
Clientside TPS loop and what not
Browse files Browse the repository at this point in the history
  • Loading branch information
LucHeart committed Aug 13, 2024
1 parent 53863e1 commit aab42ea
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 31 deletions.
77 changes: 64 additions & 13 deletions SDK.CSharp.Example/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,79 @@
using OpenShock.SDK.CSharp;
using OpenShock.SDK.CSharp.Hub;
using OpenShock.SDK.CSharp.Live;
using OpenShock.SDK.CSharp.Models;
using Serilog;

const string apiToken = "";
var deviceId = Guid.Parse("bc849182-89e0-43ff-817b-32400be3f97d");

var hostBuilder = Host.CreateDefaultBuilder();

var loggerConfiguration = new LoggerConfiguration()
.MinimumLevel.Verbose()
.MinimumLevel.Override("Microsoft", Serilog.Events.LogEventLevel.Information)
.WriteTo.Console(
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}");

Log.Logger = loggerConfiguration.CreateLogger();

hostBuilder.UseSerilog(Log.Logger);

var host = hostBuilder.Build();

// var apiClient = new OpenShockApiClient(new ApiClientOptions()
// {
// Token = "vYqcHzz0XeALfo3vzQD4Wh7KjqbJeuvZsPz8jlJrtBlfGTF9qKhxtKSrzvZO1A53"
// });
//
// var a = await apiClient.GetOwnShockers();
//
// var b = await apiClient.GetDeviceGateway(Guid.Parse("bc849182-89e0-43ff-817b-32400be3f97d"));
var logger = host.Services.GetRequiredService<ILogger<Program>>();

var apiClient = new OpenShockApiClient(new ApiClientOptions
{
Token = apiToken
});

var shockers = await apiClient.GetOwnShockers();

if (!shockers.IsT0)
{
logger.LogError("Failed to get own shockers, make sure you used a valid api token");
return;
}

var apiLiveClient = new OpenShockHubClient(new HubClientOptions()
var apiSignalRHubClient = new OpenShockHubClient(new HubClientOptions
{
Token = "71WZxCwCAIBJNgNG2pgdaHxHdaipUKmA6MalZUXNZhv3IkV7GB1ObxA35ud4tkPz"
Token = apiToken,
ConfigureLogging = builder => builder.AddSerilog(Log.Logger)
});

await apiLiveClient.StartAsync();
await apiSignalRHubClient.StartAsync();

OpenShockLiveControlClient controlClient = new("de1-gateway.shocklink.net", Guid.Parse("bc849182-89e0-43ff-817b-32400be3f97d"), "71WZxCwCAIBJNgNG2pgdaHxHdaipUKmA6MalZUXNZhv3IkV7GB1ObxA35ud4tkPz", host.Services.GetRequiredService<ILogger<OpenShockLiveControlClient>>());
var gatewayRequest = await apiClient.GetDeviceGateway(deviceId);

if (gatewayRequest.IsT1)
{
logger.LogError("Failed to get gateway, make sure you used a valid device id");
return;
}

if (gatewayRequest.IsT2)
{
logger.LogError("Device is offline");
return;
}

if (gatewayRequest.IsT3)
{
logger.LogError("Device is not connected to a gateway");
return;
}

var gateway = gatewayRequest.AsT0.Value;

logger.LogInformation("Device is connected to gateway {GatewayId} in region {Region}", gateway.Gateway, gateway.Country);

OpenShockLiveControlClient controlClient = new(gateway.Gateway, deviceId, apiToken, host.Services.GetRequiredService<ILogger<OpenShockLiveControlClient>>());
await controlClient.InitializeAsync();

await Task.Delay(-1);
while (true)
{
Console.ReadLine();
controlClient.IntakeFrame(Guid.Parse("d9267ca6-d69b-4b7a-b482-c455f75a4408"), ControlType.Vibrate, 100);
Console.WriteLine("Sent frame");
}
3 changes: 3 additions & 0 deletions SDK.CSharp.Example/SDK.CSharp.Example.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Serilog" Version="4.0.1" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
</ItemGroup>

</Project>
9 changes: 8 additions & 1 deletion SDK.CSharp.Live/IOpenShockLiveControlClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using OpenShock.SDK.CSharp.Live.LiveControlModels;
using OpenShock.SDK.CSharp.Models;

Check failure on line 2 in SDK.CSharp.Live/IOpenShockLiveControlClient.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'Models' does not exist in the namespace 'OpenShock.SDK.CSharp' (are you missing an assembly reference?)
using OpenShock.SDK.CSharp.Updatables;

Check failure on line 3 in SDK.CSharp.Live/IOpenShockLiveControlClient.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'Updatables' does not exist in the namespace 'OpenShock.SDK.CSharp' (are you missing an assembly reference?)

namespace OpenShock.SDK.CSharp.Live;
Expand All @@ -21,7 +22,13 @@ public interface IOpenShockLiveControlClient

#region Send Methods

public Task SendFrame(ClientLiveFrame frame);
/// <summary>
/// Intake a shocker frame, and send it to the server whenever a tick happens.
/// </summary>
/// <param name="shocker"></param>
/// <param name="type"></param>
/// <param name="intensity"></param>
public void IntakeFrame(Guid shocker, ControlType type, byte intensity);

Check failure on line 31 in SDK.CSharp.Live/IOpenShockLiveControlClient.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'ControlType' could not be found (are you missing a using directive or an assembly reference?)

#endregion
}
1 change: 1 addition & 0 deletions SDK.CSharp.Live/LiveControlModels/LiveRequestType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
public enum LiveRequestType
{
Frame = 0,
BulkFrame = 1,

Pong = 1000
}
9 changes: 9 additions & 0 deletions SDK.CSharp.Live/LiveControlModels/TpsData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace OpenShock.SDK.CSharp.Live.LiveControlModels;

/// <summary>
/// TPS information
/// </summary>
public sealed class TpsData
{
public required byte Client { get; set; }
}
138 changes: 123 additions & 15 deletions SDK.CSharp.Live/OpenShockLiveControlClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Net.WebSockets;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using System.Net.WebSockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
Expand All @@ -9,6 +11,7 @@
using OneOf.Types;
using OpenShock.SDK.CSharp.Live.LiveControlModels;
using OpenShock.SDK.CSharp.Live.Utils;
using OpenShock.SDK.CSharp.Models;

Check failure on line 14 in SDK.CSharp.Live/OpenShockLiveControlClient.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'Models' does not exist in the namespace 'OpenShock.SDK.CSharp' (are you missing an assembly reference?)
using OpenShock.SDK.CSharp.Serialization;

Check failure on line 15 in SDK.CSharp.Live/OpenShockLiveControlClient.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'Serialization' does not exist in the namespace 'OpenShock.SDK.CSharp' (are you missing an assembly reference?)
using OpenShock.SDK.CSharp.Updatables;

Check failure on line 16 in SDK.CSharp.Live/OpenShockLiveControlClient.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'Updatables' does not exist in the namespace 'OpenShock.SDK.CSharp' (are you missing an assembly reference?)
using OpenShock.SDK.CSharp.Utils;

Check failure on line 17 in SDK.CSharp.Live/OpenShockLiveControlClient.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'Utils' does not exist in the namespace 'OpenShock.SDK.CSharp' (are you missing an assembly reference?)
Expand All @@ -25,12 +28,49 @@ public sealed class OpenShockLiveControlClient : IOpenShockLiveControlClient, IA

public string Gateway { get; }
public Guid DeviceId { get; }

private readonly string _authToken;
private readonly ILogger<OpenShockLiveControlClient> _logger;
private readonly ApiClientOptions.ProgramInfo? _programInfo;
private ClientWebSocket? _clientWebSocket = null;

private sealed class ShockerState
{
public ControlType LastType { get; set; } = ControlType.Stop;
[Range(0, 100)] public byte LastIntensity { get; set; } = 0;

/// <summary>
/// Active until time for the shocker, determined by client TPS interval + current time
/// </summary>
public DateTimeOffset ActiveUntil = DateTimeOffset.MinValue;
}

private Timer _managedFrameTimer;

private ConcurrentDictionary<Guid, ShockerState> _shockerStates = new();

private byte _tps = 0;

public byte Tps
{
get => _tps;
private set
{
_tps = value;

if (_tps == 0)
{
_managedFrameTimer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
return;
}

var interval = TimeSpan.FromMilliseconds(1000d / _tps);
_managedFrameTimer.Change(interval, interval);
_logger.LogDebug("Managed frame timer interval set {Tps} TPS / {Interval}ms interval", _tps,
interval.Milliseconds);
}
}

public event Func<Task>? OnDeviceNotConnected;
public event Func<Task>? OnDeviceConnected;
public event Func<Task>? OnDispose;
Expand All @@ -53,6 +93,8 @@ public OpenShockLiveControlClient(string gateway, Guid deviceId, string authToke

_dispose = new CancellationTokenSource();
_linked = _dispose;

_managedFrameTimer = new Timer(FrameTimerTick);
}

public Task InitializeAsync() => ConnectAsync();
Expand Down Expand Up @@ -155,7 +197,7 @@ private string GetUserAgent()

string programName;
Version programVersion;

if (_programInfo == null)
{
(programName, programVersion) = UserAgentUtils.GetAssemblyInfo();
Expand Down Expand Up @@ -189,8 +231,9 @@ private async Task ReceiveLoop()
}

var message =
await JsonWebSocketUtils.ReceiveFullMessageAsyncNonAlloc<BaseResponse<LiveResponseType>>(
_clientWebSocket, _linked.Token, JsonSerializerOptions);
await JsonWebSocketUtils
.ReceiveFullMessageAsyncNonAlloc<LiveControlModels.BaseResponse<LiveResponseType>>(
_clientWebSocket, _linked.Token, JsonSerializerOptions);

if (message.IsT2)
{
Expand Down Expand Up @@ -263,7 +306,7 @@ await _clientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Normal cl
}


private async Task HandleMessage(BaseResponse<LiveResponseType>? wsRequest)
private async Task HandleMessage(LiveControlModels.BaseResponse<LiveResponseType>? wsRequest)
{
if (wsRequest == null) return;
switch (wsRequest.ResponseType)
Expand Down Expand Up @@ -311,9 +354,83 @@ await QueueMessage(new BaseRequest<LiveRequestType>
case LiveResponseType.DeviceConnected:
await OnDeviceConnected.Raise();
break;

case LiveResponseType.TPS:
if (wsRequest.Data == null)
{
_logger.LogWarning("TPS response data is null");
return;
}

var tpsDataResponse = wsRequest.Data.Deserialize<TpsData>(JsonSerializerOptions);
if (tpsDataResponse == null)
{
_logger.LogWarning("TPS response data failed to deserialize");
return;
}

_logger.LogDebug("Received TPS: {Tps}", Tps);

Tps = tpsDataResponse.Client;
break;
}
}

private async void FrameTimerTick(object state)
{
try
{
if (_clientWebSocket is not { State: WebSocketState.Open })
{
_logger.LogWarning("Frame timer ticked, but websocket is not open");
_managedFrameTimer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
return;
}

await QueueMessage(new BaseRequest<LiveRequestType>()
{
RequestType = LiveRequestType.BulkFrame,
Data = _shockerStates.Where(x => x.Value.ActiveUntil > DateTimeOffset.UtcNow)
.Select(x => new ClientLiveFrame
{
Shocker = x.Key,
Type = x.Value.LastType,
Intensity = x.Value.LastIntensity
})
});
}
catch (Exception e)
{
_logger.LogError(e, "Error in managed frame timer callback");
}
}

/// <inheritdoc />
public void IntakeFrame(Guid shocker, ControlType type, byte intensity)
{
if (_tps == 0)
{
_logger.LogWarning("Intake frame called, but TPS is 0");
return;
}

var activeUntil = DateTimeOffset.UtcNow.AddMilliseconds(1000d / Tps * 2.5);

_shockerStates.AddOrUpdate(shocker, new ShockerState()
{
LastIntensity = intensity,
ActiveUntil = activeUntil,
LastType = type
}, (guid, shockerState) =>
{
shockerState.LastIntensity = intensity;
shockerState.ActiveUntil = activeUntil;
shockerState.LastType = type;
return shockerState;
});
}


private bool _disposed = false;

public async ValueTask DisposeAsync()
Expand Down Expand Up @@ -360,13 +477,4 @@ public Task Run(Task? function, CancellationToken cancellationToken = default, [
private readonly AsyncUpdatableVariable<ulong> _latency = new(0);

public IAsyncUpdatable<ulong> Latency => _latency;

public async Task SendFrame(ClientLiveFrame frame)
{
await QueueMessage(new BaseRequest<LiveRequestType>()
{
RequestType = LiveRequestType.Frame,
Data = frame
});
}
}
4 changes: 2 additions & 2 deletions SDK.CSharp.Live/SDK.CSharp.Live.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
<AssemblyName>OpenShock.SDK.CSharp.Live</AssemblyName>
<RootNamespace>OpenShock.SDK.CSharp.Live</RootNamespace>
<Company>OpenShock</Company>
<AssemblyVersion>0.0.24</AssemblyVersion>
<Version>0.0.24</Version>
<AssemblyVersion>0.0.25</AssemblyVersion>
<Version>0.0.25</Version>
<Title>SDK.DotNet.Live</Title>
<Authors>OpenShock</Authors>
<Description>Extension for OpenShock.SDK.CSharp</Description>
Expand Down

0 comments on commit aab42ea

Please sign in to comment.