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

Non-Generic UnaryResult #579

Merged
merged 2 commits into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
#pragma warning disable 219
#pragma warning disable 168

// NOTE: Disable warnings for nullable reference types.
// `#nullable disable` causes compile error on old C# compilers (-7.3)
#pragma warning disable 8603 // Possible null reference return.
#pragma warning disable 8618 // Non-nullable variable must contain a non-null value when exiting constructor. Consider declaring it as nullable.
#pragma warning disable 8625 // Cannot convert null literal to non-nullable reference type.

namespace MagicOnion
{
using global::System;
Expand All @@ -17,7 +23,11 @@ public static partial class MagicOnionInitializer
{
static bool isRegistered = false;

#if UNITY_2019_4_OR_NEWER
[UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.BeforeSceneLoad)]
#elif NET5_0_OR_GREATER
[System.Runtime.CompilerServices.ModuleInitializer]
#endif
public static void Register()
{
if(isRegistered) return;
Expand All @@ -35,12 +45,19 @@ public static void Register()
#pragma warning restore 414
#pragma warning restore 612
#pragma warning restore 618

#pragma warning disable 618
#pragma warning disable 612
#pragma warning disable 414
#pragma warning disable 219
#pragma warning disable 168

// NOTE: Disable warnings for nullable reference types.
// `#nullable disable` causes compile error on old C# compilers (-7.3)
#pragma warning disable 8603 // Possible null reference return.
#pragma warning disable 8618 // Non-nullable variable must contain a non-null value when exiting constructor. Consider declaring it as nullable.
#pragma warning disable 8625 // Cannot convert null literal to non-nullable reference type.

namespace MagicOnion.Resolvers
{
using System;
Expand Down Expand Up @@ -113,13 +130,20 @@ internal static object GetFormatter(Type t)
#pragma warning restore 414
#pragma warning restore 612
#pragma warning restore 618

/// <auto-generated />
#pragma warning disable 618
#pragma warning disable 612
#pragma warning disable 414
#pragma warning disable 219
#pragma warning disable 168

// NOTE: Disable warnings for nullable reference types.
// `#nullable disable` causes compile error on old C# compilers (-7.3)
#pragma warning disable 8603 // Possible null reference return.
#pragma warning disable 8618 // Non-nullable variable must contain a non-null value when exiting constructor. Consider declaring it as nullable.
#pragma warning disable 8625 // Cannot convert null literal to non-nullable reference type.

namespace ChatApp.Shared.Services
{
using global::System;
Expand Down Expand Up @@ -172,6 +196,12 @@ private ChatServiceClient(MagicOnionClientOptions options, ClientCore core) : ba
#pragma warning disable 219
#pragma warning disable 168

// NOTE: Disable warnings for nullable reference types.
// `#nullable disable` causes compile error on old C# compilers (-7.3)
#pragma warning disable 8603 // Possible null reference return.
#pragma warning disable 8618 // Non-nullable variable must contain a non-null value when exiting constructor. Consider declaring it as nullable.
#pragma warning disable 8625 // Cannot convert null literal to non-nullable reference type.

namespace ChatApp.Shared.Hubs
{
using global::System;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,79 @@
using System;
using System;
using System.Runtime.CompilerServices;
using System.Security;
using System.Threading.Tasks;
using MessagePack;

namespace MagicOnion.CompilerServices
{
public struct AsyncUnaryResultMethodBuilder
{
AsyncTaskMethodBuilder<Nil> methodBuilder;
bool hasResult;
bool useBuilder;

public static AsyncUnaryResultMethodBuilder Create()
=> new AsyncUnaryResultMethodBuilder() { methodBuilder = AsyncTaskMethodBuilder<Nil>.Create() };

public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
=> methodBuilder.Start(ref stateMachine);

public void SetStateMachine(IAsyncStateMachine stateMachine)
=> methodBuilder.SetStateMachine(stateMachine);

public void SetResult()
{
if (useBuilder)
{
methodBuilder.SetResult(Nil.Default);
}
else
{
hasResult = true;
}
}

public void SetException(Exception ex)
=> methodBuilder.SetException(ex);

public UnaryResult Task
{
get
{
if (hasResult)
{
return new UnaryResult(Nil.Default);
}

useBuilder = true;
return new UnaryResult(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);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
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);
}
}

public struct AsyncUnaryResultMethodBuilder<T>
{
private AsyncTaskMethodBuilder<T> methodBuilder;
private T result;
private bool haveResult;
private bool useBuilder;
AsyncTaskMethodBuilder<T> methodBuilder;
T result;
bool haveResult;
bool useBuilder;

public static AsyncUnaryResultMethodBuilder<T> Create()
{
Expand Down Expand Up @@ -77,4 +141,4 @@ public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter
methodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
}
}
}
}
168 changes: 148 additions & 20 deletions src/MagicOnion.Abstractions/UnaryResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,150 @@ namespace MagicOnion
/// <summary>
/// Represents the result of a Unary call that wraps AsyncUnaryCall as Task-like.
/// </summary>
public static class UnaryResult
[AsyncMethodBuilder(typeof(AsyncUnaryResultMethodBuilder))]
public readonly struct UnaryResult
{
internal readonly bool hasRawValue;
internal readonly Task rawTaskValue;
internal readonly Task<IResponseContext<Nil>> response;

public UnaryResult(Nil nil)
{
this.hasRawValue = true;
this.rawTaskValue = default;
this.response = null;
}

public UnaryResult(Task<Nil> rawTaskValue)
{
this.hasRawValue = true;
this.rawTaskValue = rawTaskValue ?? throw new ArgumentNullException(nameof(rawTaskValue));
this.response = null;
}

public UnaryResult(Task<IResponseContext<Nil>> response)
{
this.hasRawValue = false;
this.rawTaskValue = default;
this.response = response ?? throw new ArgumentNullException(nameof(response));
}

/// <summary>
/// Asynchronous call result.
/// </summary>
public Task ResponseAsync
{
get
{
if (hasRawValue)
{
if (rawTaskValue != null)
{
return rawTaskValue;
}
}
else
{
// If the UnaryResult has no raw-value and no response, it is the default value of UnaryResult.
// So, we will return the default value of TResponse as Task.
if (response is null)
{
return Task.CompletedTask;
}

return UnwrapResponse();
}

return Task.CompletedTask;
}
}

/// <summary>
/// Asynchronous access to response headers.
/// </summary>
public Task<Metadata> ResponseHeadersAsync => UnwrapResponseHeaders();

async Task UnwrapResponse()
{
var ctx = await response.ConfigureAwait(false);
await ctx.ResponseAsync.ConfigureAwait(false);
}

async Task<Metadata> UnwrapResponseHeaders()
{
var ctx = await response.ConfigureAwait(false);
return await ctx.ResponseHeadersAsync.ConfigureAwait(false);
}

IResponseContext 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 GetAwaiter()
=> ResponseAsync.GetAwaiter();

/// <summary>
/// Gets the call status if the call has already finished.
/// Throws InvalidOperationException otherwise.
/// </summary>
public Status GetStatus()
=> TryUnwrap().GetStatus();

/// <summary>
/// Gets the call trailing metadata if the call has already finished.
/// Throws InvalidOperationException otherwise.
/// </summary>
public Metadata GetTrailers()
=> 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();
}
}

async void UnwrapDispose()
{
try
{
var ctx = await response.ConfigureAwait(false);
ctx.Dispose();
}
catch
{
}
}

/// <summary>
/// Gets a completed <see cref="T:MagicOnion.UnaryResult" /> with the void result.
/// </summary>
public static UnaryResult CompletedResult => default;

/// <summary>
/// Creates a <see cref="T:MagicOnion.UnaryResult`1" /> with the specified result.
/// </summary>
Expand All @@ -35,9 +177,7 @@ public static UnaryResult<Nil> Nil
/// <summary>
/// Represents the result of a Unary call that wraps AsyncUnaryCall as Task-like.
/// </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 readonly struct UnaryResult<TResponse>
{
internal readonly bool hasRawValue; // internal
Expand Down Expand Up @@ -102,13 +242,7 @@ public Task<TResponse> ResponseAsync
/// <summary>
/// Asynchronous access to response headers.
/// </summary>
public Task<Metadata> ResponseHeadersAsync
{
get
{
return UnwrapResponseHeaders();
}
}
public Task<Metadata> ResponseHeadersAsync => UnwrapResponseHeaders();

async Task<TResponse> UnwrapResponse()
{
Expand Down Expand Up @@ -148,27 +282,21 @@ IResponseContext<TResponse> TryUnwrap()
/// Allows awaiting this object directly.
/// </summary>
public TaskAwaiter<TResponse> GetAwaiter()
{
return ResponseAsync.GetAwaiter();
}
=> ResponseAsync.GetAwaiter();

/// <summary>
/// Gets the call status if the call has already finished.
/// Throws InvalidOperationException otherwise.
/// </summary>
public Status GetStatus()
{
return TryUnwrap().GetStatus();
}
=> TryUnwrap().GetStatus();

/// <summary>
/// Gets the call trailing metadata if the call has already finished.
/// Throws InvalidOperationException otherwise.
/// </summary>
public Metadata GetTrailers()
{
return TryUnwrap().GetTrailers();
}
=> TryUnwrap().GetTrailers();

/// <summary>
/// Provides means to cleanup after the call.
Expand All @@ -192,4 +320,4 @@ public void Dispose()
}
}
}
}
}
Loading