|
|
|
|
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.Globalization;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
using System.Runtime.CompilerServices;
|
|
|
|
|
using System.Security;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
namespace Cysharp.Threading.Tasks.Internal
|
|
|
|
|
{
|
|
|
|
|
internal static class DiagnosticsExtensions
|
|
|
|
|
{
|
|
|
|
|
static bool displayFilenames = true;
|
|
|
|
|
|
|
|
|
|
static readonly Regex typeBeautifyRegex = new Regex("`.+$", RegexOptions.Compiled);
|
|
|
|
|
|
|
|
|
|
static readonly Dictionary<Type, string> builtInTypeNames = new Dictionary<Type, string>
|
|
|
|
|
{
|
|
|
|
|
{ typeof(void), "void" },
|
|
|
|
|
{ typeof(bool), "bool" },
|
|
|
|
|
{ typeof(byte), "byte" },
|
|
|
|
|
{ typeof(char), "char" },
|
|
|
|
|
{ typeof(decimal), "decimal" },
|
|
|
|
|
{ typeof(double), "double" },
|
|
|
|
|
{ typeof(float), "float" },
|
|
|
|
|
{ typeof(int), "int" },
|
|
|
|
|
{ typeof(long), "long" },
|
|
|
|
|
{ typeof(object), "object" },
|
|
|
|
|
{ typeof(sbyte), "sbyte" },
|
|
|
|
|
{ typeof(short), "short" },
|
|
|
|
|
{ typeof(string), "string" },
|
|
|
|
|
{ typeof(uint), "uint" },
|
|
|
|
|
{ typeof(ulong), "ulong" },
|
|
|
|
|
{ typeof(ushort), "ushort" },
|
|
|
|
|
{ typeof(Task), "Task" },
|
|
|
|
|
{ typeof(UniTask), "UniTask" },
|
|
|
|
|
{ typeof(UniTaskVoid), "UniTaskVoid" }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
public static string CleanupAsyncStackTrace(this StackTrace stackTrace)
|
|
|
|
|
{
|
|
|
|
|
if (stackTrace == null) return "";
|
|
|
|
|
|
|
|
|
|
var sb = new StringBuilder();
|
|
|
|
|
for (int i = 0; i < stackTrace.FrameCount; i++)
|
|
|
|
|
{
|
|
|
|
|
var sf = stackTrace.GetFrame(i);
|
|
|
|
|
|
|
|
|
|
var mb = sf.GetMethod();
|
|
|
|
|
|
|
|
|
|
if (IgnoreLine(mb)) continue;
|
|
|
|
|
if (IsAsync(mb))
|
|
|
|
|
{
|
|
|
|
|
sb.Append("async ");
|
|
|
|
|
TryResolveStateMachineMethod(ref mb, out var decType);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// return type
|
|
|
|
|
if (mb is MethodInfo mi)
|
|
|
|
|
{
|
|
|
|
|
sb.Append(BeautifyType(mi.ReturnType, false));
|
|
|
|
|
sb.Append(" ");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// method name
|
|
|
|
|
sb.Append(BeautifyType(mb.DeclaringType, false));
|
|
|
|
|
if (!mb.IsConstructor)
|
|
|
|
|
{
|
|
|
|
|
sb.Append(".");
|
|
|
|
|
}
|
|
|
|
|
sb.Append(mb.Name);
|
|
|
|
|
if (mb.IsGenericMethod)
|
|
|
|
|
{
|
|
|
|
|
sb.Append("<");
|
|
|
|
|
foreach (var item in mb.GetGenericArguments())
|
|
|
|
|
{
|
|
|
|
|
sb.Append(BeautifyType(item, true));
|
|
|
|
|
}
|
|
|
|
|
sb.Append(">");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// parameter
|
|
|
|
|
sb.Append("(");
|
|
|
|
|
sb.Append(string.Join(", ", mb.GetParameters().Select(p => BeautifyType(p.ParameterType, true) + " " + p.Name)));
|
|
|
|
|
sb.Append(")");
|
|
|
|
|
|
|
|
|
|
// file name
|
|
|
|
|
if (displayFilenames && (sf.GetILOffset() != -1))
|
|
|
|
|
{
|
|
|
|
|
String fileName = null;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
fileName = sf.GetFileName();
|
|
|
|
|
}
|
|
|
|
|
catch (NotSupportedException)
|
|
|
|
|
{
|
|
|
|
|
displayFilenames = false;
|
|
|
|
|
}
|
|
|
|
|
catch (SecurityException)
|
|
|
|
|
{
|
|
|
|
|
displayFilenames = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fileName != null)
|
|
|
|
|
{
|
|
|
|
|
sb.Append(' ');
|
|
|
|
|
sb.AppendFormat(CultureInfo.InvariantCulture, "(at {0})", AppendHyperLink(fileName, sf.GetFileLineNumber().ToString()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sb.AppendLine();
|
|
|
|
|
}
|
|
|
|
|
return sb.ToString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool IsAsync(MethodBase methodInfo)
|
|
|
|
|
{
|
|
|
|
|
var declareType = methodInfo.DeclaringType;
|
|
|
|
|
return typeof(IAsyncStateMachine).IsAssignableFrom(declareType);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// code from Ben.Demystifier/EnhancedStackTrace.Frame.cs
|
|
|
|
|
static bool TryResolveStateMachineMethod(ref MethodBase method, out Type declaringType)
|
|
|
|
|
{
|
|
|
|
|
declaringType = method.DeclaringType;
|
|
|
|
|
|
|
|
|
|
var parentType = declaringType.DeclaringType;
|
|
|
|
|
if (parentType == null)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var methods = parentType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
|
|
|
|
if (methods == null)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var candidateMethod in methods)
|
|
|
|
|
{
|
|
|
|
|
var attributes = candidateMethod.GetCustomAttributes<StateMachineAttribute>();
|
|
|
|
|
if (attributes == null)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var asma in attributes)
|
|
|
|
|
{
|
|
|
|
|
if (asma.StateMachineType == declaringType)
|
|
|
|
|
{
|
|
|
|
|
method = candidateMethod;
|
|
|
|
|
declaringType = candidateMethod.DeclaringType;
|
|
|
|
|
// Mark the iterator as changed; so it gets the + annotation of the original method
|
|
|
|
|
// async statemachines resolve directly to their builder methods so aren't marked as changed
|
|
|
|
|
return asma is IteratorStateMachineAttribute;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static string BeautifyType(Type t, bool shortName)
|
|
|
|
|
{
|
|
|
|
|
if (builtInTypeNames.TryGetValue(t, out var builtin))
|
|
|
|
|
{
|
|
|
|
|
return builtin;
|
|
|
|
|
}
|
|
|
|
|
if (t.IsGenericParameter) return t.Name;
|
|
|
|
|
if (t.IsArray) return BeautifyType(t.GetElementType(), shortName) + "[]";
|
|
|
|
|
if (t.FullName?.StartsWith("System.ValueTuple") ?? false)
|
|
|
|
|
{
|
|
|
|
|
return "(" + string.Join(", ", t.GetGenericArguments().Select(x => BeautifyType(x, true))) + ")";
|
|
|
|
|
}
|
|
|
|
|
if (!t.IsGenericType) return shortName ? t.Name : t.FullName.Replace("Cysharp.Threading.Tasks.Triggers.", "").Replace("Cysharp.Threading.Tasks.Internal.", "").Replace("Cysharp.Threading.Tasks.", "") ?? t.Name;
|
|
|
|
|
|
|
|
|
|
var innerFormat = string.Join(", ", t.GetGenericArguments().Select(x => BeautifyType(x, true)));
|
|
|
|
|
|
|
|
|
|
var genericType = t.GetGenericTypeDefinition().FullName;
|
|
|
|
|
if (genericType == "System.Threading.Tasks.Task`1")
|
|
|
|
|
{
|
|
|
|
|
genericType = "Task";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return typeBeautifyRegex.Replace(genericType, "").Replace("Cysharp.Threading.Tasks.Triggers.", "").Replace("Cysharp.Threading.Tasks.Internal.", "").Replace("Cysharp.Threading.Tasks.", "") + "<" + innerFormat + ">";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool IgnoreLine(MethodBase methodInfo)
|
|
|
|
|
{
|
|
|
|
|
var declareType = methodInfo.DeclaringType.FullName;
|
|
|
|
|
if (declareType == "System.Threading.ExecutionContext")
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else if (declareType.StartsWith("System.Runtime.CompilerServices"))
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else if (declareType.StartsWith("Cysharp.Threading.Tasks.CompilerServices"))
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else if (declareType == "System.Threading.Tasks.AwaitTaskContinuation")
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else if (declareType.StartsWith("System.Threading.Tasks.Task"))
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else if (declareType.StartsWith("Cysharp.Threading.Tasks.UniTaskCompletionSourceCore"))
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else if (declareType.StartsWith("Cysharp.Threading.Tasks.AwaiterActions"))
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static string AppendHyperLink(string path, string line)
|
|
|
|
|
{
|
|
|
|
|
var fi = new FileInfo(path);
|
|
|
|
|
if (fi.Directory == null)
|
|
|
|
|
{
|
|
|
|
|
return fi.Name;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var fname = fi.FullName.Replace(Path.DirectorySeparatorChar, '/').Replace(PlayerLoopHelper.ApplicationDataPath, "");
|
|
|
|
|
var withAssetsPath = "Assets/" + fname;
|
|
|
|
|
return "<a href=\"" + withAssetsPath + "\" line=\"" + line + "\">" + withAssetsPath + ":" + line + "</a>";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|