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

fix: Exceptions migrate to Result<> #1897

Merged
merged 9 commits into from
Aug 30, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System;
using System.Net.Sockets;
using System.Threading;
using UnityEngine;
using Utility.Types;

namespace DCL.Multiplayer.Connections.Archipelago.LiveConnections
{
Expand Down Expand Up @@ -50,15 +50,18 @@ public async UniTask SendAsync(MemoryWrap data, CancellationToken token)
}
}

public async UniTask<MemoryWrap> ReceiveAsync(CancellationToken token)
public async UniTask<EnumResult<MemoryWrap, IArchipelagoLiveConnection.ReceiveResponse>> ReceiveAsync(CancellationToken token)
{
try { return await origin.ReceiveAsync(token); }
catch (ConnectionClosedException)
var result = await origin.ReceiveAsync(token);

if (result.Error?.State is IArchipelagoLiveConnection.ReceiveResponse.ConnectionClosed)
{
log("Connection error on receiving, ensure to reconnect...");
await EnsureReconnectAsync(token);
NickKhalow marked this conversation as resolved.
Show resolved Hide resolved
return await ReceiveAsync(token);
}

return result;
}

private async UniTask EnsureReconnectAsync(CancellationToken token)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using LiveKit.Internal.FFIClients.Pools.Memory;
using System;
using System.Net.WebSockets;
using Utility.Types;

namespace DCL.Multiplayer.Connections.Archipelago.LiveConnections
{
Expand All @@ -13,5 +15,11 @@ public ConnectionClosedException(WebSocket webSocket) : base("Connection closed"
{
this.webSocket = webSocket;
}

public static EnumResult<MemoryWrap, IArchipelagoLiveConnection.ReceiveResponse> NewErrorResult(WebSocket webSocket) =>
EnumResult<MemoryWrap, IArchipelagoLiveConnection.ReceiveResponse>.ErrorResult(
IArchipelagoLiveConnection.ReceiveResponse.ConnectionClosed,
$"WebSocket closed with state: {webSocket.State} with status: {webSocket.CloseStatus} with description: {webSocket.CloseStatusDescription} - Connection closed"
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
using DCL.Multiplayer.Connections.Messaging;
using Google.Protobuf;
using LiveKit.Internal.FFIClients.Pools.Memory;
using Nethereum.Model;
using System.Threading;
using Utility.Types;

namespace DCL.Multiplayer.Connections.Archipelago.LiveConnections
{
Expand All @@ -20,7 +20,13 @@ public interface IArchipelagoLiveConnection
/// <returns>returns a memory chunk ang gives the ownership for it</returns>
UniTask SendAsync(MemoryWrap data, CancellationToken token);

UniTask<MemoryWrap> ReceiveAsync(CancellationToken token);
UniTask<EnumResult<MemoryWrap, ReceiveResponse>> ReceiveAsync(CancellationToken token);

enum ReceiveResponse
{
MessageError,
ConnectionClosed,
}
}

public static class ArchipelagoLiveConnectionExtensions
Expand All @@ -41,17 +47,17 @@ public static async UniTask SendAsync<T>(this IArchipelagoLiveConnection connect
/// <summary>
/// Takes ownership for the data and returns the ownership for the result
/// </summary>
public static async UniTask<MemoryWrap> SendAndReceiveAsync(this IArchipelagoLiveConnection archipelagoLiveConnection, MemoryWrap data, CancellationToken token)
public static async UniTask<EnumResult<MemoryWrap, IArchipelagoLiveConnection.ReceiveResponse>> SendAndReceiveAsync(this IArchipelagoLiveConnection archipelagoLiveConnection, MemoryWrap data, CancellationToken token)
{
await archipelagoLiveConnection.SendAsync(data, token);
return await archipelagoLiveConnection.ReceiveAsync(token);
}

public static async UniTask<MemoryWrap> SendAndReceiveAsync<T>(this IArchipelagoLiveConnection connection, T message, IMemoryPool memoryPool, CancellationToken token) where T: IMessage
public static async UniTask<EnumResult<MemoryWrap, IArchipelagoLiveConnection.ReceiveResponse>> SendAndReceiveAsync<T>(this IArchipelagoLiveConnection connection, T message, IMemoryPool memoryPool, CancellationToken token) where T: IMessage
{
using MemoryWrap memory = memoryPool.Memory(message);
message.WriteTo(memory);
MemoryWrap result = await connection.SendAndReceiveAsync(memory, token);
var result = await connection.SendAndReceiveAsync(memory, token);
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using LiveKit.Internal.FFIClients.Pools.Memory;
using System;
using System.Threading;
using Utility.Types;

namespace DCL.Multiplayer.Connections.Archipelago.LiveConnections
{
Expand Down Expand Up @@ -59,11 +60,11 @@ public async UniTask SendAsync(MemoryWrap data, CancellationToken token)
log($"ArchipelagoLiveConnection SendAsync finished with size: {data.Length} and content: {data.HexReadableString()}");
}

public async UniTask<MemoryWrap> ReceiveAsync(CancellationToken token)
public async UniTask<EnumResult<MemoryWrap, IArchipelagoLiveConnection.ReceiveResponse>> ReceiveAsync(CancellationToken token)
{
log("ArchipelagoLiveConnection ReceiveAsync start");
MemoryWrap result = await origin.ReceiveAsync(token);
log($"ArchipelagoLiveConnection ReceiveAsync finished with size: {result.Length}");
var result = await origin.ReceiveAsync(token);
log($"ArchipelagoLiveConnection ReceiveAsync finished with error: {result.Error?.Message ?? "no error"}, size: {(result.Success ? result.Value.Length : 0)}");
return result;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Threading;
using Utility.Multithreading;
using Utility.Ownership;
using Utility.Types;

namespace DCL.Multiplayer.Connections.Archipelago.LiveConnections
{
Expand Down Expand Up @@ -59,10 +60,11 @@ public async UniTask SendAsync(MemoryWrap data, CancellationToken token)
await current!.Value.WebSocket.SendAsync(data.DangerousArraySegment(), WebSocketMessageType.Binary, true, token)!;
}

public async UniTask<MemoryWrap> ReceiveAsync(CancellationToken token)
public async UniTask<EnumResult<MemoryWrap, IArchipelagoLiveConnection.ReceiveResponse>> ReceiveAsync(CancellationToken token)
NickKhalow marked this conversation as resolved.
Show resolved Hide resolved
{
if (IsWebSocketInvalid())
throw new InvalidOperationException(
return EnumResult<MemoryWrap, IArchipelagoLiveConnection.ReceiveResponse>.ErrorResult(
IArchipelagoLiveConnection.ReceiveResponse.MessageError,
$"Cannot receive data, ensure that connection is correct, the connection is invalid: {current?.WebSocket.State}"
);

Expand All @@ -77,12 +79,13 @@ public async UniTask<MemoryWrap> ReceiveAsync(CancellationToken token)

return result.MessageType switch
{
WebSocketMessageType.Text => throw new NotSupportedException(
$"Expected Binary, Text messages are not supported: {AsText(result, buffer)}"
WebSocketMessageType.Text => EnumResult<MemoryWrap, IArchipelagoLiveConnection.ReceiveResponse>.ErrorResult(IArchipelagoLiveConnection.ReceiveResponse.MessageError, $"Expected Binary, Text messages are not supported: {AsText(result, buffer)}"),
WebSocketMessageType.Binary => EnumResult<MemoryWrap, IArchipelagoLiveConnection.ReceiveResponse>.SuccessResult(CopiedMemory(buffer, result.Count)),
WebSocketMessageType.Close => ConnectionClosedException.NewErrorResult(current!.Value.WebSocket),
_ => EnumResult<MemoryWrap, IArchipelagoLiveConnection.ReceiveResponse>.ErrorResult(
IArchipelagoLiveConnection.ReceiveResponse.MessageError,
$"Unknown message type: {result.MessageType}"
),
WebSocketMessageType.Binary => CopiedMemory(buffer, result.Count),
WebSocketMessageType.Close => throw new ConnectionClosedException(current!.Value.WebSocket),
_ => throw new ArgumentOutOfRangeException(),
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,15 @@ private async UniTask<LightResult<string>> WelcomePeerIdAsync(string adapterUrl,
IWeb3Identity identity = web3IdentityCache.EnsuredIdentity();
await signFlow.EnsureConnectedAsync(adapterUrl, token);
string ethereumAddress = identity.Address;
string messageForSign = await signFlow.MessageForSignAsync(ethereumAddress, token);
string signedMessage = identity.Sign(messageForSign).ToJson();
var messageForSignResult = await signFlow.MessageForSignAsync(ethereumAddress, token);

if (messageForSignResult.Success == false)
{
ReportHub.LogError(ReportCategory.ARCHIPELAGO_REQUEST, $"Cannot obtain a message to sign a welcome peer");
return LightResult<string>.FAILURE;
}

string signedMessage = identity.Sign(messageForSignResult.Result).ToJson();
ReportHub.Log(ReportCategory.ARCHIPELAGO_REQUEST, $"Signed message: {signedMessage}");
return await signFlow.WelcomePeerIdAsync(signedMessage, token);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public interface IArchipelagoSignFlow
{
UniTask EnsureConnectedAsync(string adapterUrl, CancellationToken token);

UniTask<string> MessageForSignAsync(string ethereumAddress, CancellationToken token);
UniTask<LightResult<string>> MessageForSignAsync(string ethereumAddress, CancellationToken token);

UniTask<LightResult<string>> WelcomePeerIdAsync(string signedMessageAuthChainJson, CancellationToken token);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,23 +46,25 @@ public async UniTask EnsureConnectedAsync(string adapterUrl, CancellationToken t
catch (Exception e) { ReportHub.LogException(new Exception($"Cannot ensure connection {adapterUrl}", e), ReportCategory.LIVEKIT); }
}

public async UniTask<string> MessageForSignAsync(string ethereumAddress, CancellationToken token)
public async UniTask<LightResult<string>> MessageForSignAsync(string ethereumAddress, CancellationToken token)
{
try
using SmartWrap<ChallengeRequestMessage> challenge = multiPool.TempResource<ChallengeRequestMessage>();
challenge.value.Address = ethereumAddress;
using SmartWrap<ClientPacket> clientPacket = multiPool.TempResource<ClientPacket>();
clientPacket.value.ClearMessage();
clientPacket.value.ChallengeRequest = challenge.value;
var result = await connection.SendAndReceiveAsync(clientPacket.value, memoryPool, token);

if (result.Success == false)
{
using SmartWrap<ChallengeRequestMessage> challenge = multiPool.TempResource<ChallengeRequestMessage>();
challenge.value.Address = ethereumAddress;
using SmartWrap<ClientPacket> clientPacket = multiPool.TempResource<ClientPacket>();
clientPacket.value.ClearMessage();
clientPacket.value.ChallengeRequest = challenge.value;
using MemoryWrap response = await connection.SendAndReceiveAsync(clientPacket.value, memoryPool, token);
using var serverPacket = new SmartWrap<ServerPacket>(response.AsMessageServerPacket(), multiPool);
using var challengeResponse = new SmartWrap<ChallengeResponseMessage>(serverPacket.value.ChallengeResponse!, multiPool);
return challengeResponse.value.ChallengeToSign!;
ReportHub.LogError(ReportCategory.LIVEKIT, $"Cannot message for sign for address {ethereumAddress}: {result.Error?.Message}");
return LightResult<string>.FAILURE;
}
catch (Exception e) { ReportHub.LogException(new Exception($"Cannot message for sign for address {ethereumAddress}", e), ReportCategory.LIVEKIT); }

return string.Empty;
using MemoryWrap response = result.Value;
using var serverPacket = new SmartWrap<ServerPacket>(response.AsMessageServerPacket(), multiPool);
using var challengeResponse = new SmartWrap<ChallengeResponseMessage>(serverPacket.value.ChallengeResponse!, multiPool);
return challengeResponse.value.ChallengeToSign!.AsSuccess();
}

public async UniTask<LightResult<string>> WelcomePeerIdAsync(string signedMessageAuthChainJson, CancellationToken token)
Expand All @@ -78,7 +80,7 @@ public async UniTask<LightResult<string>> WelcomePeerIdAsync(string signedMessag

var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource().Token, token);

(bool hasResultLeft, MemoryWrap result) result = await UniTask.WhenAny(
(bool hasResultLeft, EnumResult<MemoryWrap, IArchipelagoLiveConnection.ReceiveResponse> result) result = await UniTask.WhenAny(
connection.SendAndReceiveAsync(clientPacket.value, memoryPool, linkedToken.Token),
connection.WaitDisconnectAsync(linkedToken.Token)
);
Expand All @@ -87,7 +89,10 @@ public async UniTask<LightResult<string>> WelcomePeerIdAsync(string signedMessag

if (result.hasResultLeft)
{
using MemoryWrap response = result.result;
if (result.result.Success == false)
return LightResult<string>.FAILURE;

using MemoryWrap response = result.result.Value;
using var serverPacket = new SmartWrap<ServerPacket>(response.AsMessageServerPacket(), multiPool);
using var welcomeMessage = new SmartWrap<WelcomeMessage>(serverPacket.value.Welcome!, multiPool);
return welcomeMessage.value.PeerId.AsSuccess();
Expand Down Expand Up @@ -140,7 +145,15 @@ public async UniTaskVoid StartListeningForConnectionStringAsync(Action<string> o
if (connection.IsConnected == false)
throw new InvalidOperationException("Connection is not established");

MemoryWrap response = await connection.ReceiveAsync(token);
var result = await connection.ReceiveAsync(token);

if (result.Success == false)
{
ReportHub.LogError(ReportCategory.LIVEKIT, $"Cannot listen for connection string: {result.Error?.Message}");
continue;
}

using MemoryWrap response = result.Value;
using var serverPacket = new SmartWrap<ServerPacket>(response.AsMessageServerPacket(), multiPool);

if (serverPacket.value.MessageCase is ServerPacket.MessageOneofCase.IslandChanged)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ public async UniTask EnsureConnectedAsync(string adapterUrl, CancellationToken t
log($"{PREFIX} Connect finish for {adapterUrl}");
}

public async UniTask<string> MessageForSignAsync(string ethereumAddress, CancellationToken token)
public async UniTask<LightResult<string>> MessageForSignAsync(string ethereumAddress, CancellationToken token)
{
log($"{PREFIX} MessageForSignAsync start for address {ethereumAddress}");
string result = await origin.MessageForSignAsync(ethereumAddress, token);
log($"{PREFIX} MessageForSignAsync finish for address {ethereumAddress} with result {result}");
var result = await origin.MessageForSignAsync(ethereumAddress, token);
log($"{PREFIX} MessageForSignAsync finish for address {ethereumAddress} with result success: {result.Success}");
return result;
}

Expand Down
20 changes: 20 additions & 0 deletions Explorer/Assets/Scripts/Utility/Types/Result.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,24 @@ public static Result<T> SuccessResult(T value) =>
public static Result<T> ErrorResult(string errorMessage) =>
new (default(T)!, errorMessage);
}

public readonly struct EnumResult<TValue, TErrorEnum>
{
public readonly TValue Value;
public readonly (TErrorEnum State, string Message)? Error;

public bool Success => Error == null;

private EnumResult(TValue value, (TErrorEnum State, string Message)? error)
{
this.Value = value;
this.Error = error;
}

public static EnumResult<TValue, TErrorEnum> SuccessResult(TValue value) =>
new (value, null);

public static EnumResult<TValue, TErrorEnum> ErrorResult(TErrorEnum state, string errorMessage) =>
new (default(TValue)!, (state, errorMessage));
}
}
Loading