Initial Client/Server Communication

#1
Followed the MagicOnion quickstart guide, borrowing code from the chat sample app to get something working
This commit is contained in:
hyperion51 2020-02-26 17:48:40 +00:00
commit a0c8dbee72
493 changed files with 57790 additions and 3 deletions

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: aca11e9577c2fb24c9ff99ccac34dbba
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,22 @@
using Grpc.Core;
using MessagePack;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace MagicOnion.Client
{
public interface IResponseContext : IDisposable
{
Task<Metadata> ResponseHeadersAsync { get; }
Status GetStatus();
Metadata GetTrailers();
Type ResponseType { get; }
}
public interface IResponseContext<T> : IResponseContext
{
Task<T> ResponseAsync { get; }
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d1954ea03abc7c34fba72fdd2cd9a8bf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,132 @@
using Grpc.Core;
using MessagePack;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace MagicOnion
{
/// <summary>
/// Wrapped AsyncClientStreamingCall.
/// </summary>
public struct ClientStreamingResult<TRequest, TResponse> : IDisposable
{
internal readonly TResponse rawValue;
internal readonly bool hasRawValue;
readonly AsyncClientStreamingCall<byte[], byte[]> inner;
readonly IClientStreamWriter<TRequest> requestStream;
readonly MessagePackSerializerOptions serializerOptions;
public ClientStreamingResult(TResponse rawValue)
{
this.hasRawValue = true;
this.rawValue = rawValue;
this.inner = null;
this.requestStream = null;
this.serializerOptions = null;
}
public ClientStreamingResult(AsyncClientStreamingCall<byte[], byte[]> inner, IClientStreamWriter<TRequest> requestStream, MessagePackSerializerOptions serializerOptions)
{
this.hasRawValue = false;
this.rawValue = default(TResponse);
this.inner = inner;
this.requestStream = requestStream;
this.serializerOptions = serializerOptions;
}
async Task<TResponse> Deserialize()
{
var bytes = await inner.ResponseAsync.ConfigureAwait(false);
return MessagePackSerializer.Deserialize<TResponse>(bytes, serializerOptions);
}
/// <summary>
/// Asynchronous call result.
/// </summary>
public Task<TResponse> ResponseAsync
{
get
{
if (hasRawValue)
{
return Task.FromResult(rawValue);
}
else
{
return Deserialize();
}
}
}
/// <summary>
/// Asynchronous access to response headers.
/// </summary>
public Task<Metadata> ResponseHeadersAsync
{
get
{
return this.inner.ResponseHeadersAsync;
}
}
/// <summary>
/// Async stream to send streaming requests.
/// </summary>
public IClientStreamWriter<TRequest> RequestStream
{
get
{
return this.requestStream;
}
}
/// <summary>
/// Allows awaiting this object directly.
/// </summary>
/// <returns></returns>
public TaskAwaiter<TResponse> GetAwaiter()
{
return ResponseAsync.GetAwaiter();
}
/// <summary>
/// Gets the call status if the call has already finished.
/// Throws InvalidOperationException otherwise.
/// </summary>
public Status GetStatus()
{
return this.inner.GetStatus();
}
/// <summary>
/// Gets the call trailing metadata if the call has already finished.
/// Throws InvalidOperationException otherwise.
/// </summary>
public Metadata GetTrailers()
{
return this.inner.GetTrailers();
}
/// <summary>
/// Provides means to cleanup after the call.
/// If the call has already finished normally (request stream has been completed and call result has been received), doesn't do anything.
/// Otherwise, requests cancellation of the call which should terminate all pending async operations associated with the call.
/// As a result, all resources being used by the call should be released eventually.
/// </summary>
/// <remarks>
/// Normally, there is no need for you to dispose the call unless you want to utilize the
/// "Cancel" semantics of invoking <c>Dispose</c>.
/// </remarks>
public void Dispose()
{
if (this.inner != null)
{
this.inner.Dispose();
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1e92d02db742a454fa25774f217806cb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cee65f2126c31d0418b29c422566e23b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,80 @@
using System;
using System.Runtime.CompilerServices;
using System.Security;
namespace MagicOnion.CompilerServices
{
public struct AsyncUnaryResultMethodBuilder<T>
{
private AsyncTaskMethodBuilder<T> methodBuilder;
private T result;
private bool haveResult;
private bool useBuilder;
public static AsyncUnaryResultMethodBuilder<T> Create()
{
return new AsyncUnaryResultMethodBuilder<T>() { methodBuilder = AsyncTaskMethodBuilder<T>.Create() };
}
public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
methodBuilder.Start(ref stateMachine); // will provide the right ExecutionContext semantics
}
public void SetStateMachine(IAsyncStateMachine stateMachine)
{
methodBuilder.SetStateMachine(stateMachine);
}
public void SetResult(T result)
{
if (useBuilder)
{
methodBuilder.SetResult(result);
}
else
{
this.result = result;
haveResult = true;
}
}
public void SetException(Exception exception)
{
methodBuilder.SetException(exception);
}
public UnaryResult<T> Task
{
get
{
if (haveResult)
{
return new UnaryResult<T>(result);
}
else
{
useBuilder = true;
return new UnaryResult<T>(methodBuilder.Task);
}
}
}
public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : INotifyCompletion
where TStateMachine : IAsyncStateMachine
{
useBuilder = true;
methodBuilder.AwaitOnCompleted(ref awaiter, ref stateMachine);
}
[SecuritySafeCritical]
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : ICriticalNotifyCompletion
where TStateMachine : IAsyncStateMachine
{
useBuilder = true;
methodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 975c42469777f414fae46186971f0dc8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,95 @@
using Grpc.Core;
using MessagePack;
using System;
using System.Threading.Tasks;
namespace MagicOnion
{
/// <summary>
/// Wrapped AsyncDuplexStreamingCall.
/// </summary>
public struct DuplexStreamingResult<TRequest, TResponse> : IDisposable
{
readonly AsyncDuplexStreamingCall<byte[], byte[]> inner;
readonly IClientStreamWriter<TRequest> requestStream;
readonly IAsyncStreamReader<TResponse> responseStream;
public DuplexStreamingResult(AsyncDuplexStreamingCall<byte[], byte[]> inner, IClientStreamWriter<TRequest> requestStream, IAsyncStreamReader<TResponse> responseStream, MessagePackSerializerOptions serializerOptions)
{
this.inner = inner;
this.requestStream = requestStream;
this.responseStream = responseStream;
}
public AsyncDuplexStreamingCall<byte[], byte[]> RawStreamingCall => inner;
/// <summary>
/// Async stream to read streaming responses.
/// </summary>
public IAsyncStreamReader<TResponse> ResponseStream
{
get
{
return responseStream;
}
}
/// <summary>
/// Async stream to send streaming requests.
/// </summary>
public IClientStreamWriter<TRequest> RequestStream
{
get
{
return requestStream;
}
}
/// <summary>
/// Asynchronous access to response headers.
/// </summary>
public Task<Metadata> ResponseHeadersAsync
{
get
{
return this.inner.ResponseHeadersAsync;
}
}
/// <summary>
/// Gets the call status if the call has already finished.
/// Throws InvalidOperationException otherwise.
/// </summary>
public Status GetStatus()
{
return this.inner.GetStatus();
}
/// <summary>
/// Gets the call trailing metadata if the call has already finished.
/// Throws InvalidOperationException otherwise.
/// </summary>
public Metadata GetTrailers()
{
return this.inner.GetTrailers();
}
/// <summary>
/// Provides means to cleanup after the call.
/// If the call has already finished normally (request stream has been completed and response stream has been fully read), doesn't do anything.
/// Otherwise, requests cancellation of the call which should terminate all pending async operations associated with the call.
/// As a result, all resources being used by the call should be released eventually.
/// </summary>
/// <remarks>
/// Normally, there is no need for you to dispose the call unless you want to utilize the
/// "Cancel" semantics of invoking <c>Dispose</c>.
/// </remarks>
public void Dispose()
{
if (this.inner != null)
{
this.inner.Dispose();
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 383e96f82d4d6244b951f0fc83d49b63
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MagicOnion
{
/// <summary>
/// instruction for moc.exe, surround #if symbol with output code.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Interface, AllowMultiple = false, Inherited = false)]
public class GenerateDefineDebugAttribute : Attribute
{
public GenerateDefineDebugAttribute()
{
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ab20e13a7e0b6b644a42dbfa3b44afd3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,21 @@
using Grpc.Core;
using System;
using System.Threading;
namespace MagicOnion
{
// used for MagicOnionEngine assembly scan for boostup analyze speed.
public interface IServiceMarker
{
}
public interface IService<TSelf> : IServiceMarker
{
TSelf WithOptions(CallOptions option);
TSelf WithHeaders(Metadata headers);
TSelf WithDeadline(DateTime deadline);
TSelf WithCancellationToken(CancellationToken cancellationToken);
TSelf WithHost(string host);
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 56677a2edb7ea644faf4846ad283b007
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,15 @@
using System.Threading.Tasks;
namespace MagicOnion
{
public interface IStreamingHubMarker
{
}
public interface IStreamingHub<TSelf, TReceiver> : IStreamingHubMarker, IServiceMarker
{
TSelf FireAndForget(); // if changed to get-only property, intellisense does not work on VS2017.
Task DisposeAsync();
Task WaitForDisconnect();
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5d0a5fcfa0834b84ea6ff4d77e478fb9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MagicOnion
{
/// <summary>
/// Don't register on MagicOnionEngine.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class,AllowMultiple = false,Inherited = false)]
public class IgnoreAttribute : Attribute
{
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0d13f2ed066fd874b8749ca248f42c51
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,14 @@
{
"name": "MagicOnion.Abstractions",
"references": [
"MessagePack"
],
"optionalUnityReferences": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 62b9d94cd08d5d047b266d42e8684cb8
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c1f828ada0daa0843bfef8cc6ebda1bf
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 33588a48f556fba45b238f907a8ebc38
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MagicOnion.Server.Hubs
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class MethodIdAttribute : Attribute
{
public readonly int MethodId;
public MethodIdAttribute(int methodId)
{
MethodId = methodId;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1752261f18dd5f749b27216b7cbe81e3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,80 @@
using Grpc.Core;
using MessagePack;
using System;
using System.Threading.Tasks;
namespace MagicOnion
{
/// <summary>
/// Wrapped AsyncServerStreamingCall.
/// </summary>
public struct ServerStreamingResult<TResponse> : IDisposable
{
readonly AsyncServerStreamingCall<byte[]> inner;
readonly IAsyncStreamReader<TResponse> responseStream;
public ServerStreamingResult(AsyncServerStreamingCall<byte[]> inner, IAsyncStreamReader<TResponse> responseStream, MessagePackSerializerOptions serializerOptions)
{
this.inner = inner;
this.responseStream = responseStream;
}
/// <summary>
/// Async stream to read streaming responses.
/// </summary>
public IAsyncStreamReader<TResponse> ResponseStream
{
get
{
return responseStream;
}
}
/// <summary>
/// Asynchronous access to response headers.
/// </summary>
public Task<Metadata> ResponseHeadersAsync
{
get
{
return this.inner.ResponseHeadersAsync;
}
}
/// <summary>
/// Gets the call status if the call has already finished.
/// Throws InvalidOperationException otherwise.
/// </summary>
public Status GetStatus()
{
return this.inner.GetStatus();
}
/// <summary>
/// Gets the call trailing metadata if the call has already finished.
/// Throws InvalidOperationException otherwise.
/// </summary>
public Metadata GetTrailers()
{
return this.inner.GetTrailers();
}
/// <summary>
/// Provides means to cleanup after the call.
/// If the call has already finished normally (response stream has been fully read), doesn't do anything.
/// Otherwise, requests cancellation of the call which should terminate all pending async operations associated with the call.
/// As a result, all resources being used by the call should be released eventually.
/// </summary>
/// <remarks>
/// Normally, there is no need for you to dispose the call unless you want to utilize the
/// "Cancel" semantics of invoking <c>Dispose</c>.
/// </remarks>
public void Dispose()
{
if (this.inner != null)
{
this.inner.Dispose();
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 46a21c751ab9a4f4caf74e806bfe2bea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,163 @@
using Grpc.Core;
using MagicOnion.Client;
using MagicOnion.CompilerServices; // require this using in AsyncMethodBuilder
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace MagicOnion
{
/// <summary>
/// Wrapped AsyncUnaryCall.
/// </summary>
#if NON_UNITY || (CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6)))
[AsyncMethodBuilder(typeof(AsyncUnaryResultMethodBuilder<>))]
#endif
public struct UnaryResult<TResponse>
{
internal readonly bool hasRawValue; // internal
internal readonly TResponse rawValue; // internal
internal readonly Task<TResponse> rawTaskValue; // internal
readonly Task<IResponseContext<TResponse>> response;
public UnaryResult(TResponse rawValue)
{
this.hasRawValue = true;
this.rawValue = rawValue;
this.rawTaskValue = null;
this.response = null;
}
public UnaryResult(Task<TResponse> rawTaskValue)
{
this.hasRawValue = true;
this.rawValue = default(TResponse);
this.rawTaskValue = rawTaskValue;
this.response = null;
}
public UnaryResult(Task<IResponseContext<TResponse>> response)
{
this.hasRawValue = false;
this.rawValue = default(TResponse);
this.rawTaskValue = null;
this.response = response;
}
/// <summary>
/// Asynchronous call result.
/// </summary>
public Task<TResponse> ResponseAsync
{
get
{
if (!hasRawValue)
{
return UnwrapResponse();
}
else if (rawTaskValue != null)
{
return rawTaskValue;
}
else
{
return Task.FromResult(rawValue);
}
}
}
/// <summary>
/// Asynchronous access to response headers.
/// </summary>
public Task<Metadata> ResponseHeadersAsync
{
get
{
return UnwrapResponseHeaders();
}
}
async Task<TResponse> UnwrapResponse()
{
var ctx = await response.ConfigureAwait(false);
return await ctx.ResponseAsync.ConfigureAwait(false);
}
async Task<Metadata> UnwrapResponseHeaders()
{
var ctx = await response.ConfigureAwait(false);
return await ctx.ResponseHeadersAsync.ConfigureAwait(false);
}
async void UnwrapDispose()
{
try
{
var ctx = await response.ConfigureAwait(false);
ctx.Dispose();
}
catch
{
}
}
IResponseContext<TResponse> TryUnwrap()
{
if (!response.IsCompleted)
{
throw new InvalidOperationException("UnaryResult request is not yet completed, please await before call this.");
}
return response.Result;
}
/// <summary>
/// Allows awaiting this object directly.
/// </summary>
public TaskAwaiter<TResponse> GetAwaiter()
{
return ResponseAsync.GetAwaiter();
}
/// <summary>
/// Gets the call status if the call has already finished.
/// Throws InvalidOperationException otherwise.
/// </summary>
public Status GetStatus()
{
return TryUnwrap().GetStatus();
}
/// <summary>
/// Gets the call trailing metadata if the call has already finished.
/// Throws InvalidOperationException otherwise.
/// </summary>
public Metadata GetTrailers()
{
return TryUnwrap().GetTrailers();
}
/// <summary>
/// Provides means to cleanup after the call.
/// If the call has already finished normally (request stream has been completed and call result has been received), doesn't do anything.
/// Otherwise, requests cancellation of the call which should terminate all pending async operations associated with the call.
/// As a result, all resources being used by the call should be released eventually.
/// </summary>
/// <remarks>
/// Normally, there is no need for you to dispose the call unless you want to utilize the
/// "Cancel" semantics of invoking <c>Dispose</c>.
/// </remarks>
public void Dispose()
{
if (!response.IsCompleted)
{
UnwrapDispose();
}
else
{
response.Result.Dispose();
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cc0fe0c16ad63fd489a131a76e925b08
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: