Skip to content

Commit

Permalink
Merge branch 'dev' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
VolcanicArts committed Jun 1, 2023
2 parents 759eb8e + 2eda44a commit bc6f60e
Show file tree
Hide file tree
Showing 26 changed files with 451 additions and 203 deletions.
4 changes: 2 additions & 2 deletions VRCOSC.Desktop/VRCOSC.Desktop.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
<ApplicationIcon>game.ico</ApplicationIcon>
<ApplicationManifest>app.manifest</ApplicationManifest>
<Version>0.0.0</Version>
<FileVersion>2023.531.0</FileVersion>
<FileVersion>2023.601.0</FileVersion>
<Title>VRCOSC</Title>
<Authors>VolcanicArts</Authors>
<Company>VolcanicArts</Company>
<Nullable>enable</Nullable>
<AssemblyVersion>2023.531.0</AssemblyVersion>
<AssemblyVersion>2023.601.0</AssemblyVersion>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\VRCOSC.Game\VRCOSC.Game.csproj" />
Expand Down
2 changes: 1 addition & 1 deletion VRCOSC.Game.Tests/VRCOSC.Game.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
</ItemGroup>
</Project>
9 changes: 8 additions & 1 deletion VRCOSC.Game/Managers/ModuleManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,14 @@ private void loadExternalModules()

private void loadModulesFromAssembly(Assembly assembly)
{
assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(Module)) && !type.IsAbstract).ForEach(type => registerModule(assembly, type));
try
{
assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(Module)) && !type.IsAbstract).ForEach(type => registerModule(assembly, type));
}
catch (Exception)
{
notification.Notify(new ExceptionNotification($"{assembly.GetAssemblyAttribute<AssemblyProductAttribute>()?.Product} could not be loaded. It may require an update"));
}
}

private void registerModule(Assembly assembly, Type type)
Expand Down
2 changes: 0 additions & 2 deletions VRCOSC.Game/Managers/RouterManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using osu.Framework.Platform;
using VRCOSC.Game.Graphics.Notifications;
using VRCOSC.Game.OSC;
using VRCOSC.Game.Router.Serialisation.Legacy;
using VRCOSC.Game.Router.Serialisation.V1;
using VRCOSC.Game.Serialisation;

Expand All @@ -20,7 +19,6 @@ public class RouterManager
public RouterManager(Storage storage, NotificationContainer notification)
{
serialisationManager = new SerialisationManager();
serialisationManager.RegisterSerialiser(0, new LegacyRouterSerialiser(storage, notification, this));
serialisationManager.RegisterSerialiser(1, new RouterSerialiser(storage, notification, this));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License.
// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License.
// See the LICENSE file in the repository root for full license text.

using VRCOSC.Game.Modules;
using System;
using System.Linq;
using System.Threading;
using VRCOSC.Game.Modules.ChatBox;

namespace VRCOSC.Modules.Heartrate;
namespace VRCOSC.Game.Modules.Bases.Heartrate;

public abstract class HeartRateModule : ChatBoxModule
public abstract class HeartrateModule<T> : ChatBoxModule where T : HeartrateProvider
{
private static readonly TimeSpan heartrate_timeout = TimeSpan.FromSeconds(30);

public override string Author => @"VolcanicArts";
public override string Prefab => @"VRCOSC-Heartrate";
public override ModuleType Type => ModuleType.Health;

protected HeartRateProvider? HeartRateProvider;
protected T? HeartrateProvider;
private int currentHeartrate;
private int targetHeartrate;
private int connectionCount;
private TimeSpan targetInterval;
private DateTimeOffset lastIntervalUpdate;
private DateTimeOffset lastHeartrateTime;

protected abstract HeartRateProvider CreateHeartRateProvider();
protected abstract T CreateProvider();

protected override void CreateAttributes()
{
Expand All @@ -48,7 +46,6 @@ protected override void OnModuleStart()
targetHeartrate = 0;
connectionCount = 0;
targetInterval = TimeSpan.Zero;
lastHeartrateTime = DateTimeOffset.MinValue;
lastIntervalUpdate = DateTimeOffset.MinValue;
ChangeStateTo(HeartrateState.Default);
attemptConnection();
Expand All @@ -63,12 +60,12 @@ private void attemptConnection()
}

connectionCount++;
HeartRateProvider = CreateHeartRateProvider();
HeartRateProvider.OnHeartRateUpdate += handleHeartRateUpdate;
HeartRateProvider.OnConnected += () => connectionCount = 0;
HeartRateProvider.OnDisconnected += attemptReconnection;
HeartRateProvider.Initialise();
HeartRateProvider.Connect();
HeartrateProvider = CreateProvider();
HeartrateProvider.OnHeartrateUpdate += handleHeartRateUpdate;
HeartrateProvider.OnConnected += () => connectionCount = 0;
HeartrateProvider.OnDisconnected += attemptReconnection;
HeartrateProvider.OnLog += Log;
HeartrateProvider.Initialise();
}

private void attemptReconnection()
Expand All @@ -82,9 +79,9 @@ private void attemptReconnection()

protected override void OnModuleStop()
{
if (HeartRateProvider is null) return;
HeartrateProvider?.Teardown();
HeartrateProvider = null;

if (HeartRateProvider.IsConnected) HeartRateProvider.Disconnect();
SendParameter(HeartrateParameter.Enabled, false);
}

Expand All @@ -109,7 +106,6 @@ protected override void OnFixedUpdate()
private void handleHeartRateUpdate(int heartrate)
{
targetHeartrate = heartrate;
lastHeartrateTime = DateTimeOffset.Now;

try
{
Expand All @@ -121,10 +117,10 @@ private void handleHeartRateUpdate(int heartrate)
}
}

private bool isReceiving => (HeartRateProvider?.IsConnected ?? false) && lastHeartrateTime + heartrate_timeout >= DateTimeOffset.Now;

private void sendParameters()
{
var isReceiving = HeartrateProvider?.IsReceiving ?? false;

SendParameter(HeartrateParameter.Enabled, isReceiving);

if (isReceiving)
Expand Down
77 changes: 77 additions & 0 deletions VRCOSC.Game/Modules/Bases/Heartrate/HeartrateProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License.
// See the LICENSE file in the repository root for full license text.

using System;
using System.Threading.Tasks;

namespace VRCOSC.Game.Modules.Bases.Heartrate;

/// <summary>
/// The base class for anything looking to gather heartrate data from a source
/// </summary>
public abstract class HeartrateProvider
{
private DateTimeOffset lastHeartrateDateTime;

/// <summary>
/// Used to decide the connection state of this <see cref="HeartrateProvider"/>
/// </summary>
public virtual bool IsConnected => false;

/// <summary>
/// Used decide if this <see cref="HeartrateProvider"/> is receiving heartrate values.
/// For example, <see cref="IsConnected"/> could be true but values may not be being received
/// </summary>
public virtual bool IsReceiving => IsConnected && lastHeartrateDateTime + IsReceivingTimeout >= DateTimeOffset.Now;

/// <summary>
/// Called when this <see cref="HeartrateProvider"/> is broadcasting information.
/// By default, <see cref="HeartrateModule{T}"/> captures this and automatically logs the message
/// </summary>
public Action<string>? OnLog;

/// <summary>
/// Called when this <see cref="HeartrateProvider"/> is connected and ready to send data to <see cref="OnHeartrateUpdate"/>
/// </summary>
public Action? OnConnected;

/// <summary>
/// Called when this <see cref="HeartrateProvider"/> is disconnected and will no longer send data to <see cref="OnHeartrateUpdate"/>
/// </summary>
public Action? OnDisconnected;

/// <summary>
/// Called when this <see cref="HeartrateProvider"/> receives a new heartrate value.
/// This is listened on internally so DO NOT set directly. Use the +=/-= syntax instead if you want to bind/unbind
/// </summary>
public Action<int>? OnHeartrateUpdate;

/// <summary>
/// How long should we wait after the last <see cref="OnHeartrateUpdate"/> call until <see cref="IsReceiving"/> resolves to false
/// </summary>
protected virtual TimeSpan IsReceivingTimeout => TimeSpan.FromSeconds(30);

/// <summary>
/// A stub method that forwards <paramref name="message"/> to <see cref="OnLog"/>
/// </summary>
/// <param name="message">The message to forward to <see cref="OnLog"/></param>
protected void Log(string message) => OnLog?.Invoke(message);

protected HeartrateProvider()
{
OnHeartrateUpdate += _ => lastHeartrateDateTime = DateTimeOffset.Now;
}

/// <summary>
/// Initialises this <see cref="HeartrateProvider"/>
/// </summary>
public virtual void Initialise()
{
lastHeartrateDateTime = DateTimeOffset.MinValue;
}

/// <summary>
/// Tears down this <see cref="HeartrateProvider"/>
/// </summary>
public abstract Task Teardown();
}
84 changes: 84 additions & 0 deletions VRCOSC.Game/Modules/Bases/Heartrate/WebSocketHeartrateProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License.
// See the LICENSE file in the repository root for full license text.

using System;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace VRCOSC.Game.Modules.Bases.Heartrate;

/// <inheritdoc />
/// <summary>
/// Stub class that provides handling for a <see cref="WebSocketClient"/> and adds virtual methods for <see cref="WebSocketClient"/> management
/// </summary>
public abstract class WebSocketHeartrateProvider : HeartrateProvider
{
public override bool IsConnected => client?.IsConnected ?? false;

private WebSocketClient? client;

protected virtual Uri? WebsocketUri => null;

/// <inheritdoc />
/// <summary>
/// Initialises this <see cref="WebSocketHeartrateProvider"/> and connects to the websocket
/// <exception cref="InvalidOperationException">If <see cref="Initialise"/> is called while this <see cref="WebSocketHeartrateProvider"/> is already initialised, or if <see cref="WebsocketUri"/> is null</exception>
/// </summary>
public override void Initialise()
{
if (client is not null) throw new InvalidOperationException("Call Teardown before re-initialising");
if (WebsocketUri is null) throw new InvalidOperationException("WebsocketUri is null");

client = new WebSocketClient(WebsocketUri.ToString());

client.OnWsConnected += () =>
{
OnWebSocketConnected();
OnConnected?.Invoke();
};

client.OnWsDisconnected += () =>
{
OnWebSocketDisconnected();
OnDisconnected?.Invoke();
};

client.OnWsMessage += OnWebSocketMessage;

client.Connect();
}

/// <inheritdoc />
/// <summary>
/// Tears this <see cref="WebSocketHeartrateProvider"/> down and disconnects the <see cref="WebSocketClient"/> if it's connected
/// </summary>
public override async Task Teardown()
{
if (client is null) return;

if (IsConnected) await client.Disconnect();

client = null;
}

/// <summary>
/// Sends an object by first serialising it using <see cref="JsonConvert"/>
/// </summary>
/// <param name="data">The data to serialise and then send</param>
protected void SendDataAsJson(object data) => SendData(JsonConvert.SerializeObject(data));

/// <summary>
/// Sends a raw string
/// </summary>
/// <param name="data">The data to send</param>
protected void SendData(string data)
{
if (!IsConnected) return;

client?.Send(data);
}

protected virtual void OnWebSocketConnected() { }
protected virtual void OnWebSocketDisconnected() { }
protected virtual void OnWebSocketMessage(string message) { }
}
1 change: 1 addition & 0 deletions VRCOSC.Game/Modules/ChatBox/ChatBoxModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public abstract class ChatBoxModule : Module

protected void SetVariableValue(Enum lookup, string? value, string suffix = "") => ChatBoxManager.SetVariable(SerialisedName, lookup.ToLookup(), value, suffix);
protected string GetVariableFormat(Enum lookup) => ChatBoxManager.VariableMetadata[SerialisedName][lookup.ToLookup()].DisplayableFormat;
protected string GetVariableFormat(Enum lookup, string suffix) => ChatBoxManager.VariableMetadata[SerialisedName][lookup.ToLookup()].DisplayableFormatWithSuffix(suffix);

protected void SetAllVariableValues<T>(string? value) where T : Enum => setAllVariableValues(typeof(T), value);

Expand Down
Loading

0 comments on commit bc6f60e

Please sign in to comment.