123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 |
- using System;
- using System.Diagnostics;
- using System.Reflection;
- using System.Runtime.CompilerServices;
- using System.Runtime.InteropServices;
- using System.Text;
- using Godot.NativeInterop;
- #nullable enable
- namespace Godot
- {
- internal static class DebuggingUtils
- {
- private static void AppendTypeName(this StringBuilder sb, Type type)
- {
- // Use the C# type keyword for built-in types.
- // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/built-in-types
- if (type == typeof(void))
- sb.Append("void");
- else if (type == typeof(bool))
- sb.Append("bool");
- else if (type == typeof(byte))
- sb.Append("byte");
- else if (type == typeof(sbyte))
- sb.Append("sbyte");
- else if (type == typeof(char))
- sb.Append("char");
- else if (type == typeof(decimal))
- sb.Append("decimal");
- else if (type == typeof(double))
- sb.Append("double");
- else if (type == typeof(float))
- sb.Append("float");
- else if (type == typeof(int))
- sb.Append("int");
- else if (type == typeof(uint))
- sb.Append("uint");
- else if (type == typeof(nint))
- sb.Append("nint");
- else if (type == typeof(nuint))
- sb.Append("nuint");
- else if (type == typeof(long))
- sb.Append("long");
- else if (type == typeof(ulong))
- sb.Append("ulong");
- else if (type == typeof(short))
- sb.Append("short");
- else if (type == typeof(ushort))
- sb.Append("ushort");
- else if (type == typeof(object))
- sb.Append("object");
- else if (type == typeof(string))
- sb.Append("string");
- else
- sb.Append(type);
- }
- internal static void InstallTraceListener()
- {
- Trace.Listeners.Clear();
- Trace.Listeners.Add(new GodotTraceListener());
- }
- [StructLayout(LayoutKind.Sequential)]
- // ReSharper disable once InconsistentNaming
- internal ref struct godot_stack_info
- {
- public godot_string File;
- public godot_string Func;
- public int Line;
- }
- [StructLayout(LayoutKind.Sequential)]
- // ReSharper disable once InconsistentNaming
- internal ref struct godot_stack_info_vector
- {
- private IntPtr _writeProxy;
- private unsafe godot_stack_info* _ptr;
- public readonly unsafe godot_stack_info* Elements
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => _ptr;
- }
- public void Resize(int size)
- {
- if (size < 0)
- throw new ArgumentOutOfRangeException(nameof(size));
- var err = NativeFuncs.godotsharp_stack_info_vector_resize(ref this, size);
- if (err != Error.Ok)
- throw new InvalidOperationException("Failed to resize vector. Error code is: " + err.ToString());
- }
- public unsafe void Dispose()
- {
- if (_ptr == null)
- return;
- NativeFuncs.godotsharp_stack_info_vector_destroy(ref this);
- _ptr = null;
- }
- }
- internal static unsafe StackFrame? GetCurrentStackFrame(int skipFrames = 0)
- {
- // We skip 2 frames:
- // The first skipped frame is the current method.
- // The second skipped frame is a method in NativeInterop.NativeFuncs.
- var stackTrace = new StackTrace(skipFrames: 2 + skipFrames, fNeedFileInfo: true);
- return stackTrace.GetFrame(0);
- }
- [UnmanagedCallersOnly]
- internal static unsafe void GetCurrentStackInfo(void* destVector)
- {
- try
- {
- var vector = (godot_stack_info_vector*)destVector;
- // We skip 2 frames:
- // The first skipped frame is the current method.
- // The second skipped frame is a method in NativeInterop.NativeFuncs.
- var stackTrace = new StackTrace(skipFrames: 2, fNeedFileInfo: true);
- int frameCount = stackTrace.FrameCount;
- if (frameCount == 0)
- return;
- vector->Resize(frameCount);
- int i = 0;
- foreach (StackFrame frame in stackTrace.GetFrames())
- {
- var method = frame.GetMethod();
- if (method is MethodInfo methodInfo && methodInfo.IsDefined(typeof(StackTraceHiddenAttribute)))
- {
- // Skip methods marked hidden from the stack trace.
- continue;
- }
- string? fileName = frame.GetFileName();
- int fileLineNumber = frame.GetFileLineNumber();
- GetStackFrameMethodDecl(frame, out string methodDecl);
- godot_stack_info* stackInfo = &vector->Elements[i];
- // Assign directly to element in Vector. This way we don't need to worry
- // about disposal if an exception is thrown. The Vector takes care of it.
- stackInfo->File = Marshaling.ConvertStringToNative(fileName);
- stackInfo->Func = Marshaling.ConvertStringToNative(methodDecl);
- stackInfo->Line = fileLineNumber;
- i++;
- }
- // Resize the vector again in case we skipped some frames.
- vector->Resize(i);
- }
- catch (Exception e)
- {
- ExceptionUtils.LogException(e);
- }
- }
- internal static void GetStackFrameMethodDecl(StackFrame frame, out string methodDecl)
- {
- MethodBase? methodBase = frame.GetMethod();
- if (methodBase == null)
- {
- methodDecl = string.Empty;
- return;
- }
- var sb = new StringBuilder();
- if (methodBase is MethodInfo methodInfo)
- {
- sb.AppendTypeName(methodInfo.ReturnType);
- sb.Append(' ');
- }
- sb.Append(methodBase.DeclaringType?.FullName ?? "<unknown>");
- sb.Append('.');
- sb.Append(methodBase.Name);
- if (methodBase.IsGenericMethod)
- {
- Type[] genericParams = methodBase.GetGenericArguments();
- sb.Append('<');
- for (int j = 0; j < genericParams.Length; j++)
- {
- if (j > 0)
- sb.Append(", ");
- sb.AppendTypeName(genericParams[j]);
- }
- sb.Append('>');
- }
- sb.Append('(');
- bool varArgs = (methodBase.CallingConvention & CallingConventions.VarArgs) != 0;
- ParameterInfo[] parameter = methodBase.GetParameters();
- for (int i = 0; i < parameter.Length; i++)
- {
- if (i > 0)
- sb.Append(", ");
- if (i == parameter.Length - 1 && varArgs)
- sb.Append("params ");
- sb.AppendTypeName(parameter[i].ParameterType);
- }
- sb.Append(')');
- methodDecl = sb.ToString();
- }
- }
- }
|