impl strip console stacktrace

This commit is contained in:
DCFApixels 2025-03-21 16:51:03 +08:00
parent f068c22661
commit dd328432f1
4 changed files with 177 additions and 77 deletions

View File

@ -30,27 +30,6 @@ namespace DCFApixels.DragonECS
if (Instance.GetType() == typeof(UnityDebugService)) { return; } if (Instance.GetType() == typeof(UnityDebugService)) { return; }
Set<UnityDebugService>(); Set<UnityDebugService>();
} }
protected override void OnEnableBaseService(DebugService oldService)
{
EditorGUI.hyperLinkClicked -= EditorGUI_hyperLinkClicked;
EditorGUI.hyperLinkClicked += EditorGUI_hyperLinkClicked;
Application.logMessageReceived -= Application_logMessageReceived;
Application.logMessageReceived += Application_logMessageReceived;
}
protected override void OnDisableBaseService(DebugService nextService)
{
Application.logMessageReceived -= Application_logMessageReceived;
EditorGUI.hyperLinkClicked -= EditorGUI_hyperLinkClicked;
}
private void EditorGUI_hyperLinkClicked(EditorWindow editor, HyperLinkClickedEventArgs args)
{
throw new NotImplementedException();
}
private void Application_logMessageReceived(string logString, string stackTrace, LogType type)
{
}
protected override DebugService CreateThreadInstance() protected override DebugService CreateThreadInstance()
{ {
@ -67,6 +46,7 @@ namespace DCFApixels.DragonECS
return; return;
} }
string msg = AutoConvertObjectToString(v); string msg = AutoConvertObjectToString(v);
string indexedLink = UnityDebugServiceStorage.NewIndexedLink();
bool hasTag = string.IsNullOrEmpty(tag) == false; bool hasTag = string.IsNullOrEmpty(tag) == false;
if (hasTag) if (hasTag)
{ {
@ -75,24 +55,24 @@ namespace DCFApixels.DragonECS
{ {
case "pass": case "pass":
Debug.Log( Debug.Log(
$"[<color=#00ff00>{tag}</color>] {msg}"); $"[<color=#00ff00>{tag}</color>] {msg}{indexedLink}");
break; break;
case "warning": case "warning":
Debug.LogWarning( Debug.LogWarning(
$"[<color=#ffff00>{tag}</color>] {msg}"); $"[<color=#ffff00>{tag}</color>] {msg}{indexedLink}");
break; break;
case "error": case "error":
Debug.LogError( Debug.LogError(
$"[<color=#ff4028>{tag}</color>] {msg}"); $"[<color=#ff4028>{tag}</color>] {msg}{indexedLink}");
break; break;
default: default:
Debug.Log( Debug.Log(
$"[{tag}] {msg}"); $"[{tag}] {msg}{indexedLink}");
break; break;
} }
return; return;
} }
Debug.Log(msg); Debug.Log($"{msg}{indexedLink}");
} }
public override void Break() public override void Break()
{ {

View File

@ -1,17 +1,23 @@
#if UNITY_EDITOR #if UNITY_EDITOR
using System; using System;
using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
namespace DCFApixels.DragonECS.Unity.Internal namespace DCFApixels.DragonECS.Unity.Internal
{ {
internal class UnityDebugServiceStorage : ScriptableSingleton<UnityDebugServiceStorage> using static UnityDebugServiceStorage;
using static UnityDebugServiceStorageInitializer;
[InitializeOnLoad]
internal static class UnityDebugServiceStorageInitializer
{ {
private static readonly MethodInfo _getLogsCountMethod; private static readonly MethodInfo _getLogsCountMethod;
public static readonly bool IsSupportAutoLingks; public static readonly bool IsSupportAutoLingks;
static UnityDebugServiceStorage() static UnityDebugServiceStorageInitializer()
{ {
var logEntriesType = typeof(EditorWindow).Assembly.GetType("UnityEditor.LogEntries"); var logEntriesType = typeof(EditorWindow).Assembly.GetType("UnityEditor.LogEntries");
if (logEntriesType != null) if (logEntriesType != null)
@ -23,88 +29,167 @@ namespace DCFApixels.DragonECS.Unity.Internal
EditorGUI.hyperLinkClicked -= EditorGUI_hyperLinkClicked; EditorGUI.hyperLinkClicked -= EditorGUI_hyperLinkClicked;
Application.logMessageReceived -= Application_logMessageReceived; Application.logMessageReceived -= Application_logMessageReceived;
_consoleLogCounter = -1; _consoleLogCounter = -1;
if (IsSupportAutoLingks) //if (IsSupportAutoLingks)
{ {
EditorGUI.hyperLinkClicked += EditorGUI_hyperLinkClicked; EditorGUI.hyperLinkClicked += EditorGUI_hyperLinkClicked;
Application.logMessageReceived += Application_logMessageReceived; Application.logMessageReceived += Application_logMessageReceived;
_consoleLogCounter = GetConsoleLogCount(); _consoleLogCounter = GetConsoleLogCount();
} }
} }
internal static int GetConsoleLogCount()
{
return (int)_getLogsCountMethod.Invoke(null, null);
}
}
internal class UnityDebugServiceStorage : ScriptableSingleton<UnityDebugServiceStorage>
{
public UnityDebugServiceStorage() { } public UnityDebugServiceStorage() { }
private const int IntervalChecksTicksThreshold = 100; internal const int IntervalChecksTicksThreshold = 100;
private static int _consoleLogCounter; internal static int _consoleLogCounter;
private static int _intervalChecksTicks = 0; internal static int _intervalChecksTicks = 0;
private static StructList<string> _recycledIndexes; internal static StructList<LogEntry> _logEntries = new StructList<LogEntry>(256);
private static StructList<LogEntry> _logEntries; internal static object _lock = new object();
private static object _lock = new object();
private static void EditorGUI_hyperLinkClicked(EditorWindow window, HyperLinkClickedEventArgs args) internal static void EditorGUI_hyperLinkClicked(EditorWindow window, HyperLinkClickedEventArgs args)
{ {
throw new NotImplementedException(); OnProcessClickData(args.hyperLinkData);
}
internal static void OnProcessClickData(Dictionary<string, string> infos)
{
if (infos == null) return;
if (!infos.TryGetValue("href", out var path)) return;
infos.TryGetValue("line", out var line);
for (int i = 0; i < _logEntries.Count; i++)
{
ref var e = ref _logEntries._items[i];
if (CheckLogWithIndexedLink(e.LogString))
{
int indexof = e.LogString.LastIndexOf(INDEXED_LINK_PREV) - 1 + INDEXED_LINK_PREV.Length;// откатываю символ ∆
int stringIndexLength = e.LogString.Length - (indexof + INDEXED_LINK_POST.Length);
if(stringIndexLength == path.Length)
{
bool isSkip = false;
for (int j = 1; j < stringIndexLength; j++)
{
var pathchar = path[j];
var logchar = e.LogString[indexof + j];
if (pathchar != logchar) { isSkip = true; break; }
}
if (isSkip) { continue; }
OpenIDE(e);
break;
}
}
}
} }
private static void Application_logMessageReceived(string logString, string stackTrace, LogType type) private static void OpenIDE(LogEntry entry)
{
var parsed = ParseLastCall(entry.StackTrace);
if (string.IsNullOrEmpty(parsed.path)) { return; }
UnityEditorInternal.InternalEditorUtility.OpenFileAtLineExternal(parsed.path, parsed.line); //TODO
}
public static (string path, int line) ParseLastCall(string stackTrace)
{
var debugTypeFullname = typeof(DCFApixels.DragonECS.EcsDebug).FullName;
stackTrace = stackTrace.Remove(0, stackTrace.IndexOf(debugTypeFullname));
var lines = stackTrace.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 1; i < lines.Length; i++)
{
var line = lines[i];
Match match = Regex.Match(line, @"\(at (?<path>.+?):(?<line>\d+)\)");
if (match.Success)
{
string filePath = match.Groups["path"].Value;
string lineNumber = match.Groups["line"].Value;
return (filePath, int.Parse(lineNumber));
}
}
return default;
}
internal static void Application_logMessageReceived(string logString, string stackTrace, LogType type)
{ {
if (_intervalChecksTicks >= IntervalChecksTicksThreshold || if (_intervalChecksTicks >= IntervalChecksTicksThreshold ||
_logEntries.Count >= _logEntries.Capacity - 1) _logEntries.Count >= _logEntries.Capacity - 1)
{ {
CheckConsoleClean(); CheckConsoleClean();
_intervalChecksTicks = 0;
} }
_logEntries.Add(new LogEntry(logString, stackTrace)); _logEntries.Add(new LogEntry(logString, stackTrace));
Interlocked.Increment(ref _consoleLogCounter); _consoleLogCounter++;
Interlocked.Increment(ref _intervalChecksTicks); _intervalChecksTicks++;
} }
private static bool CheckConsoleClean() private static bool CheckConsoleClean()
{ {
int currentCount = GetConsoleLogCount(); lock (_lock)
if (_consoleLogCounter > currentCount)
{ {
if (_intervalChecksTicks < IntervalChecksTicksThreshold) { return false; }
int currentCount = GetConsoleLogCount();
if (_consoleLogCounter > currentCount)
{
var l = _consoleLogCounter - currentCount;
if(l < _logEntries.Count)
{
_logEntries.FastRemoveSpan(0, l);
}
error _consoleLogCounter = currentCount;
_intervalChecksTicks = 0;
_consoleLogCounter = currentCount; return true;
return true; }
return false;
} }
return false;
}
private static int GetConsoleLogCount()
{
return (int)_getLogsCountMethod.Invoke(null, null);
} }
private const string INDEXED_LINK_PREV = "\r\n\r\n<a href=\"∆";
private const string INDEXED_LINK_POST = "\">Open line</a>";
private static string CreateIndexedLink(int index) private static string CreateIndexedLink(int index)
{ {
return $"<a href=\"{index}\">∆</a> "; return $"{INDEXED_LINK_PREV}{index}{INDEXED_LINK_POST}";
} }
//multi thread access. //multi thread access.
public static string GetHyperLink() public static string NewIndexedLink()
{ {
return instance.GetHyperLink_Internal(); return instance.GetHyperLink_Internal();
} }
private static int _hyperLinkIndex = 0;
public string GetHyperLink_Internal() public string GetHyperLink_Internal()
{ {
string hyperLink; var index = Interlocked.Increment(ref _hyperLinkIndex);
if (_recycledIndexes.Count > 0) string hyperLink = CreateIndexedLink(index);
{
hyperLink = _recycledIndexes.Dequeue();
}
else
{
hyperLink = CreateIndexedLink(_logEntries.Count);
}
return hyperLink; return hyperLink;
} }
private static bool CheckLogWithIndexedLink(string log)
{
if (log.Length < INDEXED_LINK_POST.Length) { return false; }
for (int i = 0; i < INDEXED_LINK_POST.Length; i++)
{
char constChar = INDEXED_LINK_POST[i];
char logChar = log[log.Length - INDEXED_LINK_POST.Length + i];
if (constChar != logChar) { return false; }
}
return true;
}
internal readonly struct LogEntry
private readonly struct LogEntry
{ {
public readonly string LogString; public readonly string LogString;
public readonly string StackTrace; public readonly string StackTrace;

View File

@ -154,6 +154,33 @@ namespace DCFApixels.DragonECS.Unity.Internal
return false; return false;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FastRemoveSpan(int startIndex, int length)
{
#if DEBUG
if (startIndex < 0) { Throw.ArgumentOutOfRange(); }
if (length < 0) { Throw.ArgumentOutOfRange(); }
if (startIndex + length > _count) { Throw.Argument("Invalid range specified"); }
#endif
if (length == 0 || _count == 0) { return; }
int elementsToMove = _count - (startIndex + length);
if (elementsToMove > 0)
{
Array.Copy(
sourceArray: _items,
sourceIndex: startIndex + length,
destinationArray: _items,
destinationIndex: startIndex,
length: elementsToMove
);
}
_count -= length;
//RuntimeHelpers.IsReferenceOrContainsReferences<T>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool RemoveWithOrder(T item) public bool RemoveWithOrder(T item)
{ {
int index = IndexOf(item); int index = IndexOf(item);
@ -222,17 +249,17 @@ namespace DCFApixels.DragonECS.Unity.Internal
self._items[index] = item; self._items[index] = item;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] // [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryDequeue_MultiAccess<T>(this ref StructList<T> self, T item) // public static bool TryDequeue_MultiAccess<T>(this ref StructList<T> self, T item)
{ // {
var index = Interlocked.Increment(ref self._count); // var index = Interlocked.Increment(ref self._count);
//
#if DEBUG //#if DEBUG
if (_count <= 0) { Throw.ArgumentOutOfRange(); } // if (_count <= 0) { Throw.ArgumentOutOfRange(); }
#endif //#endif
T result = _items[--_count]; // T result = _items[--_count];
_items[_count] = default; // _items[_count] = default;
return result; // return result;
} // }
} }
} }

View File

@ -8,5 +8,13 @@ namespace DCFApixels.DragonECS.Unity.Internal
{ {
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
} }
internal static void Argument(string message)
{
throw new ArgumentException(message);
}
internal static void Exception()
{
throw new Exception();
}
} }
} }