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 Aug 14, 2023
2 parents b39e373 + ecc9709 commit 7b90b37
Show file tree
Hide file tree
Showing 28 changed files with 217 additions and 204 deletions.
6 changes: 3 additions & 3 deletions VRCOSC.Desktop/VRCOSC.Desktop.csproj
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Project">
<TargetFramework>net6.0-windows10.0.22621.0</TargetFramework>
<OutputType>Exe</OutputType>
<OutputType>WinExe</OutputType>
<AssemblyName>VRCOSC</AssemblyName>
<ApplicationIcon>game.ico</ApplicationIcon>
<ApplicationManifest>app.manifest</ApplicationManifest>
<Version>0.0.0</Version>
<FileVersion>2023.805.0</FileVersion>
<FileVersion>2023.814.0</FileVersion>
<Title>VRCOSC</Title>
<Authors>VolcanicArts</Authors>
<Company>VolcanicArts</Company>
<Nullable>enable</Nullable>
<AssemblyVersion>2023.805.0</AssemblyVersion>
<AssemblyVersion>2023.814.0</AssemblyVersion>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\VRCOSC.Game\VRCOSC.Game.csproj" />
Expand Down
29 changes: 9 additions & 20 deletions VRCOSC.Game/App/AppManager.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// 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 System;
Expand All @@ -22,7 +22,6 @@
using VRCOSC.Game.OpenVR;
using VRCOSC.Game.OpenVR.Metadata;
using VRCOSC.Game.OSC;
using VRCOSC.Game.OSC.Client;
using VRCOSC.Game.OSC.VRChat;

namespace VRCOSC.Game.App;
Expand Down Expand Up @@ -215,7 +214,10 @@ private void sendModuleRunningState(Module module, bool running)

public void Start()
{
if (State.Value is AppManagerState.Starting or AppManagerState.Started) return;

if (!initialiseOSCClient()) return;
if (!OSCClient.EnableSend() || !OSCClient.EnableReceive()) return;

State.Value = AppManagerState.Starting;

Expand All @@ -224,12 +226,10 @@ public void Start()
{
ChatBoxManager.Start();
StartupManager.Start();
enableOSCFlag(OscClientFlag.Send);
ModuleManager.Start();
scheduleModuleEnabledParameters();
sendControlParameters();
startOSCRouter();
enableOSCFlag(OscClientFlag.Receive);
State.Value = AppManagerState.Started;
});
Expand All @@ -253,19 +253,6 @@ private bool initialiseOSCClient()
}
}

private void enableOSCFlag(OscClientFlag flag)
{
try
{
OSCClient.Enable(flag);
}
catch (Exception e)
{
Notifications.Notify(new PortInUseNotification(flag == OscClientFlag.Send ? configManager.Get<int>(VRCOSCSetting.SendPort) : configManager.Get<int>(VRCOSCSetting.ReceivePort)));
Logger.Error(e, $"{nameof(AppManager)} experienced an exception");
}
}

private void startOSCRouter()
{
try
Expand All @@ -288,7 +275,7 @@ private void startOSCRouter()
public async Task RestartAsync()
{
await StopAsync();
await Task.Delay(250);
await Task.Delay(500);
Start();
}

Expand All @@ -300,15 +287,17 @@ public async Task RestartAsync()

public async Task StopAsync()
{
if (State.Value is AppManagerState.Stopping or AppManagerState.Stopped) return;

State.Value = AppManagerState.Stopping;

await OSCClient.Disable(OscClientFlag.Receive);
await OSCClient.DisableReceive();
await OSCRouter.Disable();
cancelRunningModulesDelegate();
ModuleManager.Stop();
ChatBoxManager.Teardown();
VRChat.Teardown();
await OSCClient.Disable(OscClientFlag.Send);
OSCClient.DisableSend();
oscMessageQueue.Clear();

State.Value = AppManagerState.Stopped;
Expand Down
20 changes: 10 additions & 10 deletions VRCOSC.Game/ChatBox/Serialisation/V1/TimelineSerialiser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,23 @@ public TimelineSerialiser(Storage storage, AppManager appManager)
{
}

protected override bool ExecuteAfterDeserialisation(AppManager appManager, SerialisableTimeline data)
protected override bool ExecuteAfterDeserialisation(SerialisableTimeline data)
{
var createdClips = new List<Clip>();

var migrationOccurred = false;

data.Clips.ForEach(clip =>
{
var newClip = appManager.ChatBoxManager.CreateClip();
var newClip = Reference.ChatBoxManager.CreateClip();
newClip.Enabled.Value = clip.Enabled;
newClip.Name.Value = clip.Name;
newClip.Priority.Value = clip.Priority;
newClip.Start.Value = clip.Start;
newClip.End.Value = clip.End;
var migrationList = appManager.ModuleManager.GetMigrations();
var migrationList = Reference.ModuleManager.GetMigrations();
migrationList.ForEach(migration =>
{
Expand Down Expand Up @@ -70,11 +70,11 @@ protected override bool ExecuteAfterDeserialisation(AppManager appManager, Seria
migrationOccurred = true;
});
newClip.AssociatedModules.AddRange(clip.AssociatedModules.Where(serialisedModuleName => appManager.ModuleManager.DoesModuleExist(serialisedModuleName)));
newClip.AssociatedModules.AddRange(clip.AssociatedModules.Where(serialisedModuleName => Reference.ModuleManager.DoesModuleExist(serialisedModuleName)));
clip.States.Where(clipState => clipState.States.All(pair => appManager.ModuleManager.DoesModuleExist(pair.Module))).ForEach(clipState =>
clip.States.Where(clipState => clipState.States.All(pair => Reference.ModuleManager.DoesModuleExist(pair.Module))).ForEach(clipState =>
{
if (clipState.States.All(pair => appManager.ModuleManager.IsModuleLoaded(pair.Module)))
if (clipState.States.All(pair => Reference.ModuleManager.IsModuleLoaded(pair.Module)))
{
var stateData = newClip.GetStateFor(clipState.States.Select(state => state.Module), clipState.States.Select(state => state.Lookup));
if (stateData is null) return;
Expand All @@ -94,9 +94,9 @@ protected override bool ExecuteAfterDeserialisation(AppManager appManager, Seria
}
});
clip.Events.Where(clipEvent => appManager.ModuleManager.DoesModuleExist(clipEvent.Module)).ForEach(clipEvent =>
clip.Events.Where(clipEvent => Reference.ModuleManager.DoesModuleExist(clipEvent.Module)).ForEach(clipEvent =>
{
if (appManager.ModuleManager.IsModuleLoaded(clipEvent.Module))
if (Reference.ModuleManager.IsModuleLoaded(clipEvent.Module))
{
var eventData = newClip.GetEventFor(clipEvent.Module, clipEvent.Lookup);
if (eventData is null) return;
Expand All @@ -122,8 +122,8 @@ protected override bool ExecuteAfterDeserialisation(AppManager appManager, Seria
createdClips.Add(newClip);
});

appManager.ChatBoxManager.Clips.ReplaceItems(createdClips);
appManager.ChatBoxManager.SetTimelineLength(TimeSpan.FromTicks(data.Ticks));
Reference.ChatBoxManager.Clips.ReplaceItems(createdClips);
Reference.ChatBoxManager.SetTimelineLength(TimeSpan.FromTicks(data.Ticks));

return migrationOccurred;
}
Expand Down
15 changes: 13 additions & 2 deletions VRCOSC.Game/Managers/ModuleManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,21 @@ public void Load()
loadModules();
}

public bool DoesModuleExist(string serialisedName) => assemblyContexts.Any(context => context.Assemblies.Any(assembly => assembly.ExportedTypes.Where(type => type.IsSubclassOf(typeof(Module)) && !type.IsAbstract).Any(type => type.Name.ToLowerInvariant() == serialisedName)));
public bool DoesModuleExist(string serialisedName)
{
try
{
return assemblyContexts.Any(context => context.Assemblies.Any(assembly => assembly.ExportedTypes.Where(type => type.IsSubclassOf(typeof(Module)) && !type.IsAbstract).Any(type => type.Name.ToLowerInvariant() == serialisedName)));
}
catch (Exception)
{
return false;
}
}

public bool IsModuleLoaded(string serialisedName) => GetModule(serialisedName) is not null;
public bool IsModuleEnabled(string serialisedName) => GetModule(serialisedName)?.Enabled.Value ?? false;
public List<(string, string)> GetMigrations() => (from module in Modules where module.LegacySerialisedName is not null select (module.LegacySerialisedName, module.SerialisedName)).ToList();
public List<(string, string)> GetMigrations() => Modules.Where(module => module.LegacySerialisedName is not null && IsModuleLoaded(module.SerialisedName)).Select(module => (module.LegacySerialisedName!, module.SerialisedName)).ToList();

private void loadModules()
{
Expand Down
2 changes: 1 addition & 1 deletion VRCOSC.Game/Modules/Attributes/ModuleAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public abstract class ModuleAttributeList<T> : ModuleAttribute
protected abstract IEnumerable<T> GetClonedDefaults();
protected abstract IEnumerable<T> JArrayToType(JArray array);

public override bool IsDefault() => Attribute.Count == Default.Count && Attribute.SequenceEqual(Default);
public override bool IsDefault() => Attribute.SequenceEqual(Default);
public override void SetDefault() => Attribute.ReplaceItems(GetClonedDefaults());
public override void DeserialiseValue(object value) => Attribute.ReplaceItems(JArrayToType((JArray)value));
}
Expand Down
93 changes: 52 additions & 41 deletions VRCOSC.Game/Modules/Bases/Heartrate/HeartrateModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ namespace VRCOSC.Game.Modules.Bases.Heartrate;
[ModulePrefab("VRCOSC-Heartrate", "https://github.com/VolcanicArts/VRCOSC/releases/download/latest/VRCOSC-Heartrate.unitypackage")]
public abstract class HeartrateModule<T> : ChatBoxModule where T : HeartrateProvider
{
private const int reconnection_delay = 2000;
private const int reconnection_limit = 3;

protected T? HeartrateProvider;

private float currentHeartrate;
Expand All @@ -23,66 +26,73 @@ public abstract class HeartrateModule<T> : ChatBoxModule where T : HeartrateProv

protected override void CreateAttributes()
{
CreateSetting(HeartrateSetting.Smoothed, @"Smoothed", @"Whether the current heartrate should jump or smoothly converge to the target heartrate", false);
CreateSetting(HeartrateSetting.SmoothingLength, @"Smoothing Length", @"The length of time (in milliseconds) the current heartrate should take to converge to the target heartrate", 1000, () => GetSetting<bool>(HeartrateSetting.Smoothed));
CreateSetting(HeartrateSetting.NormalisedLowerbound, @"Normalised Lowerbound", @"The lower bound BPM the normalised parameter should use", 0);
CreateSetting(HeartrateSetting.NormalisedUpperbound, @"Normalised Upperbound", @"The upper bound BPM the normalised parameter should use", 240);
CreateSetting(HeartrateSetting.Smoothed, "Smoothed", "Whether the current heartrate should jump or smoothly converge to the target heartrate", false);
CreateSetting(HeartrateSetting.SmoothingLength, "Smoothing Length", "The length of time (in milliseconds) the current heartrate should take to converge to the target heartrate", 1000, () => GetSetting<bool>(HeartrateSetting.Smoothed));
CreateSetting(HeartrateSetting.NormalisedLowerbound, "Normalised Lowerbound", "The lower bound BPM the normalised parameter should use", 0);
CreateSetting(HeartrateSetting.NormalisedUpperbound, "Normalised Upperbound", "The upper bound BPM the normalised parameter should use", 240);

CreateParameter<bool>(HeartrateParameter.Enabled, ParameterMode.Write, @"VRCOSC/Heartrate/Enabled", @"Enabled", @"Whether this module is connected and receiving values");
CreateParameter<int>(HeartrateParameter.Value, ParameterMode.Write, @"VRCOSC/Heartrate/Value", @"Value", @"The raw value of your heartrate");
CreateParameter<float>(HeartrateParameter.Normalised, ParameterMode.Write, @"VRCOSC/Heartrate/Normalised", @"Normalised", @"The heartrate value normalised to the set bounds");
CreateParameter<float>(HeartrateParameter.Units, ParameterMode.Write, @"VRCOSC/Heartrate/Units", @"Units", @"The units digit 0-9 mapped to a float");
CreateParameter<float>(HeartrateParameter.Tens, ParameterMode.Write, @"VRCOSC/Heartrate/Tens", @"Tens", @"The tens digit 0-9 mapped to a float");
CreateParameter<float>(HeartrateParameter.Hundreds, ParameterMode.Write, @"VRCOSC/Heartrate/Hundreds", @"Hundreds", @"The hundreds digit 0-9 mapped to a float");
CreateParameter<bool>(HeartrateParameter.Enabled, ParameterMode.Write, "VRCOSC/Heartrate/Enabled", "Enabled", "Whether this module is connected and receiving values");
CreateParameter<int>(HeartrateParameter.Value, ParameterMode.Write, "VRCOSC/Heartrate/Value", "Value", "The raw value of your heartrate");
CreateParameter<float>(HeartrateParameter.Normalised, ParameterMode.Write, "VRCOSC/Heartrate/Normalised", "Normalised", "The heartrate value normalised to the set bounds");
CreateParameter<float>(HeartrateParameter.Units, ParameterMode.Write, "VRCOSC/Heartrate/Units", "Units", "The units digit 0-9 mapped to a float");
CreateParameter<float>(HeartrateParameter.Tens, ParameterMode.Write, "VRCOSC/Heartrate/Tens", "Tens", "The tens digit 0-9 mapped to a float");
CreateParameter<float>(HeartrateParameter.Hundreds, ParameterMode.Write, "VRCOSC/Heartrate/Hundreds", "Hundreds", "The hundreds digit 0-9 mapped to a float");

CreateVariable(HeartrateVariable.Heartrate, @"Heartrate", @"hr");
CreateVariable(HeartrateVariable.Heartrate, "Heartrate", "hr");

CreateState(HeartrateState.Default, @"Default", $@"Heartrate/v{GetVariableFormat(HeartrateVariable.Heartrate)} bpm");
CreateState(HeartrateState.Default, "Default", $"Heartrate/v{GetVariableFormat(HeartrateVariable.Heartrate)} bpm");
}

protected override void OnModuleStart()
{
currentHeartrate = 0;
targetHeartrate = 0;
connectionCount = 0;
ChangeStateTo(HeartrateState.Default);
attemptConnection();
}

private void attemptConnection()
{
if (connectionCount >= 3)
{
Log(@"Connection cannot be established");
return;
}
connectionCount = 1;

connectionCount++;
HeartrateProvider = CreateProvider();
HeartrateProvider.OnHeartrateUpdate += newHeartrate => targetHeartrate = newHeartrate;
HeartrateProvider.OnConnected += () => connectionCount = 0;
HeartrateProvider.OnDisconnected += attemptReconnection;
HeartrateProvider.OnLog += Log;
HeartrateProvider.Initialise();

ChangeStateTo(HeartrateState.Default);
}

private void attemptReconnection()
{
if (connectionCount >= reconnection_limit)
{
Log("Connection cannot be established");
return;
}

Log("Attempting reconnection...");
Thread.Sleep(2000);
attemptConnection();
Thread.Sleep(reconnection_delay);

HeartrateProvider?.Teardown();
HeartrateProvider?.Initialise();
connectionCount++;
}

protected override void OnModuleStop()
{
HeartrateProvider?.Teardown();
HeartrateProvider = null;
if (HeartrateProvider is not null)
{
HeartrateProvider.OnHeartrateUpdate = null;
HeartrateProvider.OnConnected = null;
HeartrateProvider.OnDisconnected = null;
HeartrateProvider.OnLog = null;

HeartrateProvider.Teardown();
HeartrateProvider = null;
}

SendParameter(HeartrateParameter.Enabled, false);
}

[ModuleUpdate(ModuleUpdateMode.Custom)]
private void updateParameters()
private void updateCurrentHeartrate()
{
if (GetSetting<bool>(HeartrateSetting.Smoothed))
{
Expand All @@ -92,11 +102,10 @@ private void updateParameters()
{
currentHeartrate = targetHeartrate;
}

sendParameters();
}

private void sendParameters()
[ModuleUpdate(ModuleUpdateMode.Custom)]
private void updateParameters()
{
var isReceiving = HeartrateProvider?.IsReceiving ?? false;

Expand All @@ -114,24 +123,26 @@ private void sendParameters()
SendParameter(HeartrateParameter.Units, individualValues[2] / 10f);
SendParameter(HeartrateParameter.Tens, individualValues[1] / 10f);
SendParameter(HeartrateParameter.Hundreds, individualValues[0] / 10f);
SetVariableValue(HeartrateVariable.Heartrate, intHeartrate.ToString());
}
else
{
SendParameter(HeartrateParameter.Normalised, 0);
SendParameter(HeartrateParameter.Normalised, 0f);
SendParameter(HeartrateParameter.Value, 0);
SendParameter(HeartrateParameter.Units, 0);
SendParameter(HeartrateParameter.Tens, 0);
SendParameter(HeartrateParameter.Hundreds, 0);
SetVariableValue(HeartrateVariable.Heartrate, @"0");
SendParameter(HeartrateParameter.Units, 0f);
SendParameter(HeartrateParameter.Tens, 0f);
SendParameter(HeartrateParameter.Hundreds, 0f);
}
}

private static int[] toDigitArray(int num, int totalWidth)
[ModuleUpdate(ModuleUpdateMode.ChatBox)]
private void updateVariables()
{
return num.ToString().PadLeft(totalWidth, '0').Select(digit => int.Parse(digit.ToString())).ToArray();
var isReceiving = HeartrateProvider?.IsReceiving ?? false;
SetVariableValue(HeartrateVariable.Heartrate, isReceiving ? currentHeartrate.ToString("##0") : "0");
}

private static int[] toDigitArray(int num, int totalWidth) => num.ToString().PadLeft(totalWidth, '0').Select(digit => int.Parse(digit.ToString())).ToArray();

private enum HeartrateSetting
{
Smoothed,
Expand Down
1 change: 1 addition & 0 deletions VRCOSC.Game/Modules/Module.cs
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ protected void SendParameter<T>(Enum lookup, T value) where T : struct

var data = Parameters[lookup];
if (!data.Mode.HasFlagFast(ParameterMode.Write)) throw new InvalidOperationException($"Parameter {lookup.GetType().Name}.{lookup} is a read-only parameter and therefore can't be sent!");
if (data.ExpectedType != typeof(T)) throw new InvalidOperationException($"Parameter {lookup.GetType().Name}.{lookup} expects type {data.ExpectedType.ToReadableName()} but you tried to send {typeof(T).ToReadableName()}");

scheduler.Add(() => oscClient.SendValue(data.ParameterAddress, value));
}
Expand Down
2 changes: 1 addition & 1 deletion VRCOSC.Game/Modules/ModuleMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public class ModuleUpdateAttribute : Attribute
/// <param name="mode">The mode this update is defined as</param>
/// <param name="updateImmediately">Whether this method should be called immediately after <see cref="M:VRCOSC.Game.Modules.Module.OnModuleStart" />. This is only used when <paramref name="mode" /> is <see cref="F:VRCOSC.Game.Modules.ModuleUpdateMode.Custom" /></param>
/// <param name="deltaMilliseconds">The time between this method being called in milliseconds. This is only used when <paramref name="mode" /> is <see cref="F:VRCOSC.Game.Modules.ModuleUpdateMode.Custom" /></param>
/// <remarks><paramref name="deltaMilliseconds" /> defaults to the fastest update rate you should need for sending parameters</remarks>
/// <remarks><paramref name="deltaMilliseconds" /> defaults to the fastest update rate you should need for sending parameters. If multiple of this attribute that have the same <see cref="ModuleUpdateMode"/> are defined in a class they will execute top to bottom</remarks>
public ModuleUpdateAttribute(ModuleUpdateMode mode, bool updateImmediately = true, double deltaMilliseconds = VRChatOscConstants.UPDATE_DELTA_MILLISECONDS)
{
Mode = mode;
Expand Down
Loading

0 comments on commit 7b90b37

Please sign in to comment.