Skip to content

Commit

Permalink
chore: Convert client message handling to its own class
Browse files Browse the repository at this point in the history
  • Loading branch information
qdot committed Jun 25, 2023
1 parent a8999cb commit e3ea88a
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 129 deletions.
4 changes: 2 additions & 2 deletions Buttplug.Test/Client/SystemMessageSendingClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ public SystemMessageSendingClient(string clientName)

public async Task SendSystemIdMessage()
{
await SendMessageAsync(new StartScanning(ButtplugConsts.SystemMsgId));
await _handler.SendMessageAsync(new StartScanning(ButtplugConsts.SystemMsgId));
}

public async Task SendOutgoingOnlyMessage()
{
await SendMessageAsync(new Ok(ButtplugConsts.DefaultMsgId));
await _handler.SendMessageAsync(new Ok(ButtplugConsts.DefaultMsgId));
}
}
}
70 changes: 11 additions & 59 deletions Buttplug/Client/ButtplugClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Buttplug.Core;

using Buttplug.Core.Messages;

namespace Buttplug.Client
Expand Down Expand Up @@ -85,6 +85,8 @@ public class ButtplugClient :
/// </remarks>
protected Timer _pingTimer;

internal ButtplugClientMessageHandler _handler;

/// <summary>
/// Stores information about devices currently connected to the server.
/// </summary>
Expand Down Expand Up @@ -121,11 +123,12 @@ public async Task ConnectAsync(IButtplugClientConnector connector, CancellationT
_connector.InvalidMessageReceived += ConnectorErrorHandler;
_connector.MessageReceived += MessageReceivedHandler;
_devices.Clear();
_handler = new ButtplugClientMessageHandler(connector);


await _connector.ConnectAsync(token).ConfigureAwait(false);

var res = await SendMessageAsync(new RequestServerInfo(Name), token).ConfigureAwait(false);
var res = await _handler.SendMessageAsync(new RequestServerInfo(Name), token).ConfigureAwait(false);
switch (res)
{
case ServerInfo si:
Expand All @@ -145,7 +148,7 @@ public async Task ConnectAsync(IButtplugClientConnector connector, CancellationT
}

// Get full device list and populate internal list
var resp = await SendMessageAsync(new RequestDeviceList()).ConfigureAwait(false);
var resp = await _handler.SendMessageAsync(new RequestDeviceList()).ConfigureAwait(false);
if (resp is DeviceList list)
{
foreach (var d in list.Devices)
Expand All @@ -155,7 +158,7 @@ public async Task ConnectAsync(IButtplugClientConnector connector, CancellationT
continue;
}

var device = new ButtplugClientDevice(this, SendDeviceMessageAsync, d);
var device = new ButtplugClientDevice(_handler, d);
_devices[d.DeviceIndex] = device;
DeviceAdded?.Invoke(this, new DeviceAddedEventArgs(device));
}
Expand Down Expand Up @@ -208,7 +211,7 @@ public async Task DisconnectAsync()
// ReSharper disable once UnusedMember.Global
public async Task StartScanningAsync(CancellationToken token = default)
{
await SendMessageExpectOk(new StartScanning(), token).ConfigureAwait(false);
await _handler.SendMessageExpectOk(new StartScanning(), token).ConfigureAwait(false);
}

/// <summary>
Expand All @@ -222,38 +225,7 @@ public async Task StartScanningAsync(CancellationToken token = default)
// ReSharper disable once UnusedMember.Global
public async Task StopScanningAsync(CancellationToken token = default)
{
await SendMessageExpectOk(new StopScanning(), token).ConfigureAwait(false);
}

/// <summary>
/// Sends a DeviceMessage (e.g. <see cref="VibrateCmd"/> or <see cref="LinearCmd"/>). Handles
/// constructing some parts of the message for the user.
/// </summary>
/// <param name="device">The device to be controlled by the message.</param>
/// <param name="deviceMsg">The device message (Id and DeviceIndex will be overriden).</param>
/// <param name="token">Cancellation token, for cancelling action externally if it is not yet finished.</param>
/// <returns>
/// Void on success, throws <see cref="ButtplugClientException" /> otherwise.
/// </returns>
protected async Task<ButtplugMessage> SendDeviceMessageAsync(ButtplugClientDevice device, ButtplugDeviceMessage deviceMsg, CancellationToken token = default)
{
return await SendMessageAsync(deviceMsg, token).ConfigureAwait(false);
}

/// <summary>
/// Sends a message to the server, and handles asynchronously waiting for the reply from the server.
/// </summary>
/// <param name="msg">Message to send.</param>
/// <param name="token">Cancellation token, for cancelling action externally if it is not yet finished.</param>
/// <returns>The response, which will derive from <see cref="ButtplugMessage"/>.</returns>
protected async Task<ButtplugMessage> SendMessageAsync(ButtplugMessage msg, CancellationToken token = default)
{
if (!Connected)
{
throw new ButtplugClientConnectorException("Client not connected.");
}

return await _connector.SendAsync(msg, token).ConfigureAwait(false);
await _handler.SendMessageExpectOk(new StopScanning(), token).ConfigureAwait(false);
}

private void ConnectorErrorHandler(object sender, ButtplugExceptionEventArgs exception)
Expand All @@ -275,7 +247,7 @@ private async void MessageReceivedHandler(object sender, MessageReceivedEventArg
switch (msg)
{
case DeviceAdded d:
var dev = new ButtplugClientDevice(this, SendDeviceMessageAsync, d);
var dev = new ButtplugClientDevice(_handler, d);
_devices.AddOrUpdate(d.DeviceIndex, dev, (u, device) => dev);
DeviceAdded?.Invoke(this, new DeviceAddedEventArgs(dev));
break;
Expand Down Expand Up @@ -335,7 +307,7 @@ private async void OnPingTimer(object state)
{
try
{
await SendMessageExpectOk(new Ping()).ConfigureAwait(false);
await _handler.SendMessageExpectOk(new Ping()).ConfigureAwait(false);
}
catch (Exception e)
{
Expand All @@ -346,26 +318,6 @@ private async void OnPingTimer(object state)
}
}

/// <summary>
/// Sends a message, expecting a response of message type <see cref="Ok"/>.
/// </summary>
/// <param name="msg">Message to send.</param>
/// <param name="token">Cancellation token, for cancelling action externally if it is not yet finished.</param>
/// <returns>True if successful.</returns>
private async Task SendMessageExpectOk(ButtplugMessage msg, CancellationToken token = default)
{
var result = await SendMessageAsync(msg, token).ConfigureAwait(false);
switch (result)
{
case Ok _:
return;
case Error err:
throw ButtplugException.FromError(err);
default:
throw new ButtplugMessageException($"Message type {msg.Name} not handled by SendMessageExpectOk", msg.Id);
}
}

protected virtual void Dispose(bool disposing)
{
DisconnectAsync().GetAwaiter().GetResult();
Expand Down
93 changes: 25 additions & 68 deletions Buttplug/Client/ButtplugClientDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace Buttplug.Client
/// <summary>
/// The Buttplug Client representation of a Buttplug Device connected to a server.
/// </summary>
public class ButtplugClientDevice : IEquatable<ButtplugClientDevice>
public class ButtplugClientDevice
{
/// <summary>
/// The device index, which uniquely identifies the device on the server.
Expand All @@ -43,9 +43,7 @@ public class ButtplugClientDevice : IEquatable<ButtplugClientDevice>
/// </summary>
public DeviceMessageAttributes MessageAttributes { get; }

private readonly ButtplugClient _owningClient;

private readonly Func<ButtplugClientDevice, ButtplugDeviceMessage, CancellationToken, Task<ButtplugMessage>> _sendClosure;
private readonly ButtplugClientMessageHandler _handler;

/// <summary>
/// Initializes a new instance of the <see cref="ButtplugClientDevice"/> class, using
Expand All @@ -54,11 +52,10 @@ public class ButtplugClientDevice : IEquatable<ButtplugClientDevice>
/// <param name="devInfo">
/// A Buttplug protocol message implementing the IButtplugDeviceInfoMessage interface.
/// </param>
public ButtplugClientDevice(
ButtplugClient owningClient,
Func<ButtplugClientDevice, ButtplugDeviceMessage, CancellationToken, Task<ButtplugMessage>> sendClosure,
internal ButtplugClientDevice(
ButtplugClientMessageHandler handler,
IButtplugDeviceInfoMessage devInfo)
: this(owningClient, sendClosure, devInfo.DeviceIndex, devInfo.DeviceName, devInfo.DeviceMessages, devInfo.DeviceDisplayName, devInfo.DeviceMessageTimingGap)
: this(handler, devInfo.DeviceIndex, devInfo.DeviceName, devInfo.DeviceMessages, devInfo.DeviceDisplayName, devInfo.DeviceMessageTimingGap)
{
ButtplugUtils.ArgumentNotNull(devInfo, nameof(devInfo));
}
Expand All @@ -70,71 +67,23 @@ public class ButtplugClientDevice : IEquatable<ButtplugClientDevice>
/// <param name="index">The device index.</param>
/// <param name="name">The device name.</param>
/// <param name="messages">The device allowed message list, with corresponding attributes.</param>
public ButtplugClientDevice(
ButtplugClient owningClient,
Func<ButtplugClientDevice, ButtplugDeviceMessage, CancellationToken, Task<ButtplugMessage>> sendClosure,
internal ButtplugClientDevice(
ButtplugClientMessageHandler handler,
uint index,
string name,
DeviceMessageAttributes messages,
string displayName,
uint messageTimingGap)
{
ButtplugUtils.ArgumentNotNull(owningClient, nameof(owningClient));
ButtplugUtils.ArgumentNotNull(sendClosure, nameof(sendClosure));
_owningClient = owningClient;
_sendClosure = sendClosure;
ButtplugUtils.ArgumentNotNull(handler, nameof(handler));
_handler = handler;
Index = index;
Name = name;
MessageAttributes = messages;
DisplayName = displayName;
MessageTimingGap = messageTimingGap;
}

/// <summary>
/// Sends a message, expecting a response of message type <see cref="Ok"/>.
/// </summary>
/// <param name="msg">Message to send.</param>
/// <param name="token">Cancellation token, for cancelling action externally if it is not yet finished.</param>
/// <returns>True if successful.</returns>
private async Task SendMessageExpectOk(ButtplugDeviceMessage msg, CancellationToken token = default)
{
var result = await SendMessageAsync(msg, token).ConfigureAwait(false);
switch (result)
{
case Ok _:
return;
case Error err:
throw ButtplugException.FromError(err);
default:
throw new ButtplugMessageException($"Message type {msg.Name} not handled by SendMessageExpectOk", msg.Id);
}
}

public async Task<ButtplugMessage> SendMessageAsync(ButtplugDeviceMessage msg, CancellationToken token = default)
{
ButtplugUtils.ArgumentNotNull(msg, nameof(msg));

if (!_owningClient.Connected)
{
throw new ButtplugClientConnectorException("Client that owns device is not connected");
}

if (!_owningClient.Devices.Contains(this))
{
throw new ButtplugDeviceException("Device no longer connected or valid");
}

msg.DeviceIndex = Index;

return await _sendClosure(this, msg, token).ConfigureAwait(false);
}

public bool Equals(ButtplugClientDevice device)
{
// We never reuse indexes within a session, so if the client and index are the same, assume it's the same device.
return _owningClient != device._owningClient && Index != device.Index;
}

public List<GenericDeviceMessageAttributes> GenericAcutatorAttributes(ActuatorType actuator)
{
if (MessageAttributes.ScalarCmd != null)
Expand All @@ -153,7 +102,7 @@ public async Task ScalarAsync(ScalarCmd.ScalarSubcommand command)
{
throw new ButtplugDeviceException($"Scalar command for device {Name} did not generate any commands. Are you sure the device supports the ActuatorType sent?");
}
await SendMessageExpectOk(new ScalarCmd(scalars)).ConfigureAwait(false);
await _handler.SendMessageExpectOk(new ScalarCmd(Index, scalars)).ConfigureAwait(false);
}

public async Task ScalarAsync(List<ScalarCmd.ScalarSubcommand> command)
Expand All @@ -162,7 +111,7 @@ public async Task ScalarAsync(List<ScalarCmd.ScalarSubcommand> command)
{
throw new ArgumentException($"Command List for ScalarAsync must have at least 1 command.");
}
await SendMessageExpectOk(new ScalarCmd(command)).ConfigureAwait(false);
await _handler.SendMessageExpectOk(new ScalarCmd(Index, command)).ConfigureAwait(false);
}

public List<GenericDeviceMessageAttributes> VibrateAttributes
Expand Down Expand Up @@ -239,7 +188,9 @@ public async Task RotateAsync(double speed, bool clockwise)
{
throw new ButtplugDeviceException($"Device {Name} does not support rotation");
}
await SendMessageExpectOk(RotateCmd.Create(speed, clockwise, (uint)RotateAttributes.Count)).ConfigureAwait(false);
var msg = RotateCmd.Create(speed, clockwise, (uint)RotateAttributes.Count);
msg.DeviceIndex = Index;
await _handler.SendMessageExpectOk(msg).ConfigureAwait(false);
}

public async Task RotateAsync(IEnumerable<(double, bool)> cmds)
Expand All @@ -248,7 +199,9 @@ public async Task RotateAsync(IEnumerable<(double, bool)> cmds)
{
throw new ButtplugDeviceException($"Device {Name} does not support rotation");
}
await SendMessageExpectOk(RotateCmd.Create(cmds)).ConfigureAwait(false);
var msg = RotateCmd.Create(cmds);
msg.DeviceIndex = Index;
await _handler.SendMessageExpectOk(msg).ConfigureAwait(false);
}

public List<GenericDeviceMessageAttributes> LinearAttributes
Expand All @@ -269,7 +222,9 @@ public async Task LinearAsync(uint duration, double position)
{
throw new ButtplugDeviceException($"Device {Name} does not support linear position");
}
await SendMessageExpectOk(LinearCmd.Create(duration, position, (uint)LinearAttributes.Count)).ConfigureAwait(false);
var msg = LinearCmd.Create(duration, position, (uint)LinearAttributes.Count);
msg.DeviceIndex = Index;
await _handler.SendMessageExpectOk(msg).ConfigureAwait(false);
}

public async Task LinearAsync(IEnumerable<(uint, double)> cmds)
Expand All @@ -278,7 +233,9 @@ public async Task LinearAsync(IEnumerable<(uint, double)> cmds)
{
throw new ButtplugDeviceException($"Device {Name} does not support linear position");
}
await SendMessageExpectOk(LinearCmd.Create(cmds)).ConfigureAwait(false);
var msg = LinearCmd.Create(cmds);
msg.DeviceIndex = Index;
await _handler.SendMessageExpectOk(msg).ConfigureAwait(false);
}

public List<SensorDeviceMessageAttributes> SensorReadAttributes(SensorType sensor)
Expand Down Expand Up @@ -306,7 +263,7 @@ public async Task<double> BatteryAsync()
throw new ButtplugDeviceException($"Device {Name} does not have battery capabilities.");
}

var result = await SendMessageAsync(new SensorReadCmd(SensorReadAttributes(SensorType.Battery).ElementAt(0).Index, SensorType.Battery)).ConfigureAwait(false);
var result = await _handler.SendMessageAsync(new SensorReadCmd(Index, SensorReadAttributes(SensorType.Battery).ElementAt(0).Index, SensorType.Battery)).ConfigureAwait(false);
switch (result)
{
case SensorReading response:
Expand All @@ -320,7 +277,7 @@ public async Task<double> BatteryAsync()

public async Task Stop()
{
await SendMessageAsync(new StopDeviceCmd(Index)).ConfigureAwait(false);
await _handler.SendMessageExpectOk(new StopDeviceCmd(Index)).ConfigureAwait(false);
}
}
}
Loading

0 comments on commit e3ea88a

Please sign in to comment.