ThreadLog.cs 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. // threaded Debug.Log support (mischa 2022)
  2. //
  3. // Editor shows Debug.Logs from different threads.
  4. // Builds don't show Debug.Logs from different threads.
  5. //
  6. // need to hook into logMessageReceivedThreaded to receive them in builds too.
  7. using System.Collections.Concurrent;
  8. using System.Threading;
  9. using UnityEngine;
  10. namespace Mirror
  11. {
  12. public static class ThreadLog
  13. {
  14. // queue log messages from threads
  15. struct LogEntry
  16. {
  17. public int threadId;
  18. public LogType type;
  19. public string message;
  20. public string stackTrace;
  21. public LogEntry(int threadId, LogType type, string message, string stackTrace)
  22. {
  23. this.threadId = threadId;
  24. this.type = type;
  25. this.message = message;
  26. this.stackTrace = stackTrace;
  27. }
  28. }
  29. // ConcurrentQueue allocations are fine here.
  30. // logs allocate anywway.
  31. static readonly ConcurrentQueue<LogEntry> logs =
  32. new ConcurrentQueue<LogEntry>();
  33. // main thread id
  34. static int mainThreadId;
  35. #if !UNITY_EDITOR
  36. // Editor as of Unity 2021 does log threaded messages.
  37. // only builds don't.
  38. // do nothing in editor, otherwise we would log twice.
  39. // before scene load ensures thread logs are all caught.
  40. // otherwise some component's Awake may be called before we hooked it up.
  41. // for example, ThreadedTransport's early logs wouldn't be caught.
  42. [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
  43. static void Initialize()
  44. {
  45. // set main thread id
  46. mainThreadId = Thread.CurrentThread.ManagedThreadId;
  47. // receive threaded log calls
  48. Application.logMessageReceivedThreaded -= OnLog; // remove old first. TODO unnecessary?
  49. Application.logMessageReceivedThreaded += OnLog;
  50. // process logs on main thread Update
  51. NetworkLoop.OnLateUpdate -= OnLateUpdate; // remove old first. TODO unnecessary?
  52. NetworkLoop.OnLateUpdate += OnLateUpdate;
  53. // log for debugging
  54. Debug.Log("ThreadLog initialized.");
  55. }
  56. #endif
  57. static bool IsMainThread() =>
  58. Thread.CurrentThread.ManagedThreadId == mainThreadId;
  59. // callback runs on the same thread where the Debug.Log is called.
  60. // we can use this to buffer messages for main thread here.
  61. static void OnLog(string message, string stackTrace, LogType type)
  62. {
  63. // only enqueue messages from other threads.
  64. // otherwise OnLateUpdate main thread logging would be enqueued
  65. // as well, causing deadlock.
  66. if (IsMainThread()) return;
  67. // queue for logging from main thread later
  68. logs.Enqueue(new LogEntry(Thread.CurrentThread.ManagedThreadId, type, message, stackTrace));
  69. }
  70. static void OnLateUpdate()
  71. {
  72. // process queued logs on main thread
  73. while (logs.TryDequeue(out LogEntry entry))
  74. {
  75. switch (entry.type)
  76. {
  77. case LogType.Log:
  78. Debug.Log($"[T{entry.threadId}] {entry.message}\n{entry.stackTrace}");
  79. break;
  80. case LogType.Warning:
  81. Debug.LogWarning($"[T{entry.threadId}] {entry.message}\n{entry.stackTrace}");
  82. break;
  83. case LogType.Error:
  84. Debug.LogError($"[T{entry.threadId}] {entry.message}\n{entry.stackTrace}");
  85. break;
  86. case LogType.Exception:
  87. Debug.LogError($"[T{entry.threadId}] {entry.message}\n{entry.stackTrace}");
  88. break;
  89. case LogType.Assert:
  90. Debug.LogAssertion($"[T{entry.threadId}] {entry.message}\n{entry.stackTrace}");
  91. break;
  92. }
  93. }
  94. }
  95. }
  96. }