368 lines
14 KiB
C#
368 lines
14 KiB
C#
using System;
|
|
using System.Reflection;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace io.github.hatayama.uLoopMCP
|
|
{
|
|
/// <summary>
|
|
/// Utilities to await arbitrary awaitable objects using the C# awaitable pattern.
|
|
/// Supports Task, Task<T>, ValueTask, ValueTask<T>, UniTask, UniTask<T> and any type
|
|
/// implementing GetAwaiter/OnCompleted/GetResult.
|
|
/// </summary>
|
|
internal static class AwaitableHelper
|
|
{
|
|
public static async Task<object> AwaitIfNeeded(object value)
|
|
{
|
|
if (value == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Type valueType = value.GetType();
|
|
|
|
// Task and Task<T>
|
|
if (typeof(Task).IsAssignableFrom(valueType))
|
|
{
|
|
Task task = (Task)value;
|
|
await task.ConfigureAwait(false);
|
|
|
|
if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(Task<>))
|
|
{
|
|
PropertyInfo resultProperty = valueType.GetProperty("Result");
|
|
if (resultProperty != null)
|
|
{
|
|
object taskResult = resultProperty.GetValue(value);
|
|
return taskResult;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// ValueTask and ValueTask<T> via AsTask if available
|
|
if (IsValueTask(valueType))
|
|
{
|
|
Task asTask = ConvertValueTaskToTask(value);
|
|
await asTask.ConfigureAwait(false);
|
|
return null;
|
|
}
|
|
if (IsGenericValueTask(valueType))
|
|
{
|
|
Task asTask = ConvertGenericValueTaskToTask(value);
|
|
await asTask.ConfigureAwait(false);
|
|
|
|
Type[] genericArgs = valueType.GetGenericArguments();
|
|
if (genericArgs != null && genericArgs.Length == 1)
|
|
{
|
|
Type taskType = typeof(Task<>).MakeGenericType(genericArgs[0]);
|
|
PropertyInfo resultProperty = taskType.GetProperty("Result");
|
|
if (resultProperty != null)
|
|
{
|
|
object resultValue = resultProperty.GetValue(asTask);
|
|
return resultValue;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Awaitable pattern fallback (e.g., UniTask/UniTask<T> or custom awaitables)
|
|
MethodInfo getAwaiterMethod = valueType.GetMethod("GetAwaiter", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
|
|
if (getAwaiterMethod != null)
|
|
{
|
|
object awaiter = getAwaiterMethod.Invoke(value, null);
|
|
if (awaiter == null)
|
|
{
|
|
return value;
|
|
}
|
|
|
|
Type awaiterType = awaiter.GetType();
|
|
PropertyInfo isCompletedProperty = awaiterType.GetProperty("IsCompleted", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
|
|
MethodInfo getResultMethod = awaiterType.GetMethod("GetResult", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
|
|
MethodInfo onCompletedMethod = awaiterType.GetMethod("OnCompleted", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(Action) }, null);
|
|
MethodInfo unsafeOnCompletedMethod = awaiterType.GetMethod("UnsafeOnCompleted", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(Action) }, null);
|
|
|
|
if (isCompletedProperty != null)
|
|
{
|
|
bool isCompleted = (bool)isCompletedProperty.GetValue(awaiter);
|
|
if (isCompleted)
|
|
{
|
|
object completedResult = InvokeGetResultSafely(getResultMethod, awaiter);
|
|
return completedResult;
|
|
}
|
|
}
|
|
|
|
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
|
|
|
|
Action continuation = () =>
|
|
{
|
|
try
|
|
{
|
|
object continuationResult = InvokeGetResultSafely(getResultMethod, awaiter);
|
|
tcs.TrySetResult(continuationResult);
|
|
}
|
|
catch (TargetInvocationException tie)
|
|
{
|
|
Exception inner = tie.InnerException ?? tie;
|
|
tcs.TrySetException(inner);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
tcs.TrySetException(ex);
|
|
}
|
|
};
|
|
|
|
if (unsafeOnCompletedMethod != null)
|
|
{
|
|
unsafeOnCompletedMethod.Invoke(awaiter, new object[] { continuation });
|
|
}
|
|
else if (onCompletedMethod != null)
|
|
{
|
|
onCompletedMethod.Invoke(awaiter, new object[] { continuation });
|
|
}
|
|
else
|
|
{
|
|
// No completion registration; treat as completed
|
|
object immediateResult = InvokeGetResultSafely(getResultMethod, awaiter);
|
|
return immediateResult;
|
|
}
|
|
|
|
object awaited = await tcs.Task.ConfigureAwait(false);
|
|
return awaited;
|
|
}
|
|
|
|
// Not awaitable; return as-is
|
|
return value;
|
|
}
|
|
|
|
private static bool IsValueTask(Type type)
|
|
{
|
|
return type.FullName == "System.Threading.Tasks.ValueTask";
|
|
}
|
|
|
|
private static bool IsGenericValueTask(Type type)
|
|
{
|
|
if (!type.IsGenericType)
|
|
{
|
|
return false;
|
|
}
|
|
Type generic = type.GetGenericTypeDefinition();
|
|
return generic.FullName == "System.Threading.Tasks.ValueTask`1";
|
|
}
|
|
|
|
private static Task ConvertValueTaskToTask(object valueTask)
|
|
{
|
|
Type type = valueTask.GetType();
|
|
MethodInfo asTaskMethod = type.GetMethod("AsTask", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
|
|
if (asTaskMethod != null)
|
|
{
|
|
object taskObj = asTaskMethod.Invoke(valueTask, null);
|
|
return (Task)taskObj;
|
|
}
|
|
|
|
// Fallback: build a Task from awaiter
|
|
MethodInfo getAwaiter = type.GetMethod("GetAwaiter", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
|
|
if (getAwaiter == null)
|
|
{
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
object awaiter = getAwaiter.Invoke(valueTask, null);
|
|
Type awaiterType = awaiter.GetType();
|
|
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
MethodInfo onCompleted = awaiterType.GetMethod("OnCompleted", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(Action) }, null);
|
|
MethodInfo unsafeOnCompleted = awaiterType.GetMethod("UnsafeOnCompleted", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(Action) }, null);
|
|
MethodInfo getResult = awaiterType.GetMethod("GetResult", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
|
|
PropertyInfo isCompletedProperty = awaiterType.GetProperty("IsCompleted", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
|
|
|
|
bool hasIsCompleted = false;
|
|
bool isCompleted = false;
|
|
if (isCompletedProperty != null)
|
|
{
|
|
try
|
|
{
|
|
object isCompletedValue = isCompletedProperty.GetValue(awaiter);
|
|
if (isCompletedValue is bool boolValue)
|
|
{
|
|
isCompleted = boolValue;
|
|
hasIsCompleted = true;
|
|
}
|
|
}
|
|
catch (TargetInvocationException tie)
|
|
{
|
|
Exception inner = tie.InnerException ?? tie;
|
|
tcs.TrySetException(inner);
|
|
return tcs.Task;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
tcs.TrySetException(ex);
|
|
return tcs.Task;
|
|
}
|
|
}
|
|
|
|
Action cont = () =>
|
|
{
|
|
try
|
|
{
|
|
if (getResult != null)
|
|
{
|
|
getResult.Invoke(awaiter, null);
|
|
}
|
|
tcs.TrySetResult(null);
|
|
}
|
|
catch (TargetInvocationException tie)
|
|
{
|
|
Exception inner = tie.InnerException ?? tie;
|
|
tcs.TrySetException(inner);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
tcs.TrySetException(ex);
|
|
}
|
|
};
|
|
|
|
if (hasIsCompleted && isCompleted)
|
|
{
|
|
cont();
|
|
return tcs.Task;
|
|
}
|
|
|
|
if (unsafeOnCompleted != null)
|
|
{
|
|
unsafeOnCompleted.Invoke(awaiter, new object[] { cont });
|
|
}
|
|
else if (onCompleted != null)
|
|
{
|
|
onCompleted.Invoke(awaiter, new object[] { cont });
|
|
}
|
|
else
|
|
{
|
|
if (hasIsCompleted && isCompleted)
|
|
{
|
|
cont();
|
|
}
|
|
else
|
|
{
|
|
_ = Task.Run(cont);
|
|
}
|
|
}
|
|
|
|
return tcs.Task;
|
|
}
|
|
|
|
private static Task ConvertGenericValueTaskToTask(object valueTaskT)
|
|
{
|
|
Type type = valueTaskT.GetType();
|
|
MethodInfo asTaskMethod = type.GetMethod("AsTask", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
|
|
if (asTaskMethod != null)
|
|
{
|
|
object taskObj = asTaskMethod.Invoke(valueTaskT, null);
|
|
return (Task)taskObj;
|
|
}
|
|
|
|
// Fallback through awaiter
|
|
MethodInfo getAwaiter = type.GetMethod("GetAwaiter", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
|
|
if (getAwaiter == null)
|
|
{
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
object awaiter = getAwaiter.Invoke(valueTaskT, null);
|
|
Type awaiterType = awaiter.GetType();
|
|
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
MethodInfo onCompleted = awaiterType.GetMethod("OnCompleted", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(Action) }, null);
|
|
MethodInfo unsafeOnCompleted = awaiterType.GetMethod("UnsafeOnCompleted", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(Action) }, null);
|
|
MethodInfo getResult = awaiterType.GetMethod("GetResult", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
|
|
PropertyInfo isCompletedProperty = awaiterType.GetProperty("IsCompleted", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
|
|
|
|
bool hasIsCompleted = false;
|
|
bool isCompleted = false;
|
|
if (isCompletedProperty != null)
|
|
{
|
|
try
|
|
{
|
|
object isCompletedValue = isCompletedProperty.GetValue(awaiter);
|
|
if (isCompletedValue is bool boolValue)
|
|
{
|
|
isCompleted = boolValue;
|
|
hasIsCompleted = true;
|
|
}
|
|
}
|
|
catch (TargetInvocationException tie)
|
|
{
|
|
Exception inner = tie.InnerException ?? tie;
|
|
tcs.TrySetException(inner);
|
|
return tcs.Task;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
tcs.TrySetException(ex);
|
|
return tcs.Task;
|
|
}
|
|
}
|
|
|
|
Action cont = () =>
|
|
{
|
|
try
|
|
{
|
|
if (getResult != null)
|
|
{
|
|
object _ = getResult.Invoke(awaiter, null);
|
|
}
|
|
tcs.TrySetResult(null);
|
|
}
|
|
catch (TargetInvocationException tie)
|
|
{
|
|
Exception inner = tie.InnerException ?? tie;
|
|
tcs.TrySetException(inner);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
tcs.TrySetException(ex);
|
|
}
|
|
};
|
|
|
|
if (hasIsCompleted && isCompleted)
|
|
{
|
|
cont();
|
|
return tcs.Task;
|
|
}
|
|
|
|
if (unsafeOnCompleted != null)
|
|
{
|
|
unsafeOnCompleted.Invoke(awaiter, new object[] { cont });
|
|
}
|
|
else if (onCompleted != null)
|
|
{
|
|
onCompleted.Invoke(awaiter, new object[] { cont });
|
|
}
|
|
else
|
|
{
|
|
if (hasIsCompleted && isCompleted)
|
|
{
|
|
cont();
|
|
}
|
|
else
|
|
{
|
|
_ = Task.Run(cont);
|
|
}
|
|
}
|
|
|
|
return tcs.Task;
|
|
}
|
|
|
|
private static object InvokeGetResultSafely(MethodInfo getResultMethod, object awaiter)
|
|
{
|
|
if (getResultMethod == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
object result = getResultMethod.Invoke(awaiter, null);
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
|