Skip to content

Commit 61cf6f7

Browse files
Merge branch 'add-connect-async' into pre-master
2 parents e7fd8e1 + cd494b6 commit 61cf6f7

7 files changed

Lines changed: 164 additions & 71 deletions

File tree

UnityProject/Assets/LoomSDK/Source/Runtime/DAppChainClient.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,21 @@ public class DAppChainClient : IDisposable
2828
private IRpcClient readClient;
2929
private ILogger logger = NullLogger.Instance;
3030

31+
public IRpcClient WriteClient => this.writeClient;
32+
33+
public IRpcClient ReadClient => this.readClient;
34+
3135
/// <summary>
3236
/// Middleware to apply when committing transactions.
3337
/// </summary>
3438
public TxMiddleware TxMiddleware { get; set; }
3539

40+
/// <summary>
41+
/// Whether clients will attempt to connect automatically when in Disconnected state
42+
/// before communicating.
43+
/// </summary>
44+
public bool AutoReconnect { get; set; } = true;
45+
3646
/// <summary>
3747
/// Logger to be used for logging, defaults to <see cref="NullLogger"/>.
3848
/// </summary>
@@ -103,6 +113,10 @@ public void Dispose()
103113
/// <returns>The nonce.</returns>
104114
public async Task<ulong> GetNonceAsync(string key)
105115
{
116+
if (this.readClient == null)
117+
throw new InvalidOperationException("Read client is not set");
118+
119+
await EnsureConnected();
106120
string nonce = await this.readClient.SendAsync<string, NonceParams>(
107121
"nonce", new NonceParams { Key = key }
108122
);
@@ -116,6 +130,10 @@ public async Task<ulong> GetNonceAsync(string key)
116130
/// <exception cref="Exception">If a contract matching the given name wasn't found</exception>
117131
public async Task<Address> ResolveContractAddressAsync(string contractName)
118132
{
133+
if (this.readClient == null)
134+
throw new InvalidOperationException("Read client is not set");
135+
136+
await EnsureConnected();
119137
var addrStr = await this.readClient.SendAsync<string, ResolveParams>(
120138
"resolve", new ResolveParams { ContractName = contractName }
121139
);
@@ -184,6 +202,9 @@ internal async Task<BroadcastTxResult> CommitTxAsync(IMessage tx)
184202
/// <returns>Deserialized response.</returns>
185203
internal async Task<T> QueryAsync<T>(Address contract, byte[] query, Address caller = default(Address), VMType vmType = VMType.Plugin)
186204
{
205+
if (this.readClient == null)
206+
throw new InvalidOperationException("Read client is not set");
207+
187208
var queryParams = new QueryParams
188209
{
189210
ContractAddress = contract.LocalAddress,
@@ -194,6 +215,7 @@ internal async Task<BroadcastTxResult> CommitTxAsync(IMessage tx)
194215
{
195216
queryParams.CallerAddress = caller.QualifiedAddress;
196217
}
218+
await EnsureConnected();
197219
return await this.readClient.SendAsync<T, QueryParams>("query", queryParams);
198220
}
199221

@@ -205,6 +227,10 @@ internal async Task<BroadcastTxResult> CommitTxAsync(IMessage tx)
205227
/// <exception cref="InvalidTxNonceException">Thrown when transaction is rejected by the DAppChain due to a bad nonce.</exception>
206228
private async Task<BroadcastTxResult> TryCommitTxAsync(IMessage tx)
207229
{
230+
if (this.writeClient == null)
231+
throw new InvalidOperationException("Write client was not set");
232+
233+
await EnsureConnected();
208234
byte[] txBytes = tx.ToByteArray();
209235
if (this.TxMiddleware != null)
210236
{
@@ -232,8 +258,12 @@ private async Task<BroadcastTxResult> TryCommitTxAsync(IMessage tx)
232258

233259
private async void SubReadClient(EventHandler<RawChainEventArgs> handler)
234260
{
261+
if (this.readClient == null)
262+
throw new InvalidOperationException("Read client is not set");
263+
235264
try
236265
{
266+
await EnsureConnected();
237267
EventHandler<JsonRpcEventData> wrapper = (sender, e) =>
238268
{
239269
handler(this, new RawChainEventArgs
@@ -256,6 +286,9 @@ private async void SubReadClient(EventHandler<RawChainEventArgs> handler)
256286

257287
private async void UnsubReadClient(EventHandler<RawChainEventArgs> handler)
258288
{
289+
if (this.readClient == null)
290+
throw new InvalidOperationException("Read client is not set");
291+
259292
try
260293
{
261294
EventHandler<JsonRpcEventData> wrapper = this.eventSubs[handler];
@@ -266,6 +299,30 @@ private async void UnsubReadClient(EventHandler<RawChainEventArgs> handler)
266299
Logger.Log(LogTag, e.Message);
267300
}
268301
}
302+
303+
private async Task EnsureConnected()
304+
{
305+
if (!this.AutoReconnect)
306+
return;
307+
308+
if (this.readClient != null)
309+
{
310+
await EnsureConnected(this.readClient);
311+
}
312+
313+
if (this.writeClient != null)
314+
{
315+
await EnsureConnected(this.writeClient);
316+
}
317+
}
318+
319+
private async Task EnsureConnected(IRpcClient rpcClient) {
320+
// TODO: handle edge-case when ConnectionState == RpcConnectionState.Connecting
321+
if (rpcClient.ConnectionState != RpcConnectionState.Connected)
322+
{
323+
await rpcClient.ConnectAsync();
324+
}
325+
}
269326

270327
private struct NonceParams
271328
{

UnityProject/Assets/LoomSDK/Source/Runtime/IRpcClient.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public interface IRpcClient : IDisposable
1111

1212
RpcConnectionState ConnectionState { get; }
1313
Task<TResult> SendAsync<TResult, TArgs>(string method, TArgs args);
14+
Task ConnectAsync();
1415
Task DisconnectAsync();
1516
Task SubscribeAsync(EventHandler<JsonRpcEventData> handler);
1617
Task UnsubscribeAsync(EventHandler<JsonRpcEventData> handler);

UnityProject/Assets/LoomSDK/Source/Runtime/Internal/BaseRpcClient.cs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Runtime.CompilerServices;
45
using System.Text;
56
using System.Threading.Tasks;
67
using UnityEngine;
@@ -19,7 +20,7 @@ internal abstract class BaseRpcClient : IRpcClient, ILogProducer
1920
/// </summary>
2021
public virtual ILogger Logger {
2122
get {
22-
return logger;
23+
return this.logger;
2324
}
2425
set {
2526
if (value == null)
@@ -40,6 +41,7 @@ public void Dispose()
4041
public virtual event RpcClientConnectionStateChangedHandler ConnectionStateChanged;
4142
public abstract RpcConnectionState ConnectionState { get; }
4243
public abstract Task<TResult> SendAsync<TResult, TArgs>(string method, TArgs args);
44+
public abstract Task ConnectAsync();
4345
public abstract Task DisconnectAsync();
4446
public abstract Task SubscribeAsync(EventHandler<JsonRpcEventData> handler);
4547
public abstract Task UnsubscribeAsync(EventHandler<JsonRpcEventData> handler);
@@ -48,14 +50,38 @@ public void Dispose()
4850

4951
protected void NotifyConnectionStateChanged()
5052
{
51-
RpcConnectionState state = ConnectionState;
53+
RpcConnectionState state = this.ConnectionState;
5254
if (this.lastConnectionState != null && this.lastConnectionState == state)
5355
return;
5456

5557
this.lastConnectionState = state;
5658
ConnectionStateChanged?.Invoke(this, state);
5759
}
5860

61+
protected void AssertIsConnected() {
62+
RpcConnectionState connectionState = this.ConnectionState;
63+
if (connectionState == RpcConnectionState.Connected)
64+
return;
65+
66+
throw new RpcClientException(
67+
$"Client must be in {nameof(RpcConnectionState.Connected)} state, " +
68+
$"current state is {connectionState}");
69+
}
70+
71+
protected void AssertNotAlreadyConnectedOrConnecting() {
72+
RpcConnectionState connectionState = this.ConnectionState;
73+
74+
if (connectionState == RpcConnectionState.Connecting)
75+
{
76+
throw new RpcClientException("An attempt to connect while in process of connecting");
77+
}
78+
79+
if (connectionState == RpcConnectionState.Connected)
80+
{
81+
throw new RpcClientException("An attempt to connect when already connected");
82+
}
83+
}
84+
5985
~BaseRpcClient()
6086
{
6187
Dispose(false);

UnityProject/Assets/LoomSDK/Source/Runtime/Internal/HttpRpcClient.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ protected override void Dispose(bool disposing)
4141
{
4242
}
4343

44+
public override Task ConnectAsync()
45+
{
46+
return Task.CompletedTask;
47+
}
48+
4449
public override Task DisconnectAsync()
4550
{
4651
return Task.CompletedTask;

UnityProject/Assets/LoomSDK/Source/Runtime/Internal/WebSocketRpcClient.cs

Lines changed: 44 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,19 @@ internal class WebSocketRpcClient : BaseRpcClient
1919
private readonly WebSocket webSocket;
2020
private readonly Uri url;
2121
private event EventHandler<JsonRpcEventData> eventReceived;
22+
private bool anyConnectionStateChangesReceived;
2223

2324
public override RpcConnectionState ConnectionState
2425
{
2526
get
2627
{
28+
// HACK: WebSocket default ReadyState value is Connecting,
29+
// which makes it impossible to distinguish the real Connecting state
30+
// and state when no connection-related actions have been done.
31+
// Just return Disconnected until we know anything better for sure.
32+
if (!anyConnectionStateChangesReceived)
33+
return RpcConnectionState.Disconnected;
34+
2735
WebSocketState state = this.webSocket.ReadyState;
2836
switch (state)
2937
{
@@ -83,6 +91,38 @@ protected override void Dispose(bool disposing)
8391

8492
this.disposed = true;
8593
}
94+
95+
public override Task ConnectAsync()
96+
{
97+
AssertNotAlreadyConnectedOrConnecting();
98+
var tcs = new TaskCompletionSource<object>();
99+
EventHandler openHandler = null;
100+
EventHandler<CloseEventArgs> closeHandler = null;
101+
openHandler = (sender, e) =>
102+
{
103+
this.webSocket.OnOpen -= openHandler;
104+
this.webSocket.OnClose -= closeHandler;
105+
tcs.TrySetResult(null);
106+
this.Logger.Log(LogTag, "Connected to " + this.url.AbsoluteUri);
107+
};
108+
closeHandler = (sender, e) =>
109+
{
110+
tcs.SetException(new RpcClientException($"WebSocket closed unexpectedly with error {e.Code}: {e.Reason}"));
111+
};
112+
this.webSocket.OnOpen += openHandler;
113+
this.webSocket.OnClose += closeHandler;
114+
try
115+
{
116+
this.webSocket.ConnectAsync();
117+
}
118+
catch (Exception)
119+
{
120+
this.webSocket.OnOpen -= openHandler;
121+
this.webSocket.OnClose -= closeHandler;
122+
throw;
123+
}
124+
return tcs.Task;
125+
}
86126

87127
public override Task DisconnectAsync()
88128
{
@@ -187,59 +227,27 @@ public override async Task<T> SendAsync<T, U>(string method, U args)
187227

188228
private void WebSocketOnClose(object sender, CloseEventArgs e)
189229
{
230+
anyConnectionStateChangesReceived = true;
190231
NotifyConnectionStateChanged();
191232
}
192233

193234
private void WebSocketOnOpen(object sender, EventArgs e)
194235
{
236+
anyConnectionStateChangesReceived = true;
195237
NotifyConnectionStateChanged();
196238
}
197239

198240
private void WebSocketOnError(object sender, ErrorEventArgs e)
199241
{
242+
anyConnectionStateChangesReceived = true;
200243
this.Logger.Log(LogTag, "Error: " + e.Message);
201244
NotifyConnectionStateChanged();
202245
}
203246

204-
private Task EnsureConnectionAsync()
205-
{
206-
if (this.webSocket.ReadyState == WebSocketState.Open)
207-
{
208-
return Task.CompletedTask;
209-
}
210-
var tcs = new TaskCompletionSource<object>();
211-
EventHandler openHandler = null;
212-
EventHandler<CloseEventArgs> closeHandler = null;
213-
openHandler = (sender, e) =>
214-
{
215-
this.webSocket.OnOpen -= openHandler;
216-
this.webSocket.OnClose -= closeHandler;
217-
tcs.TrySetResult(null);
218-
this.Logger.Log(LogTag, "Connected to " + this.url.AbsoluteUri);
219-
};
220-
closeHandler = (sender, e) =>
221-
{
222-
tcs.SetException(new RpcClientException($"WebSocket closed unexpectedly with error {e.Code}: {e.Reason}"));
223-
};
224-
this.webSocket.OnOpen += openHandler;
225-
this.webSocket.OnClose += closeHandler;
226-
try
227-
{
228-
this.webSocket.ConnectAsync();
229-
}
230-
catch (Exception)
231-
{
232-
this.webSocket.OnOpen -= openHandler;
233-
this.webSocket.OnClose -= closeHandler;
234-
throw;
235-
}
236-
return tcs.Task;
237-
}
238-
239247
private async Task SendAsync<T>(string method, T args, string msgId)
240248
{
249+
AssertIsConnected();
241250
var tcs = new TaskCompletionSource<object>();
242-
await EnsureConnectionAsync();
243251
var reqMsg = new JsonRpcRequest<T>(method, args, msgId);
244252
var reqMsgBody = JsonConvert.SerializeObject(reqMsg);
245253
this.Logger.Log(LogTag, "[Request Body] " + reqMsgBody);

UnityProject/Assets/LoomSDK/Source/Runtime/RpcConnectionState.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
{
33
public enum RpcConnectionState
44
{
5-
Undefined = 0,
5+
Invalid,
66
Connecting,
77
Connected,
88
Disconnecting,

0 commit comments

Comments
 (0)