#if PRIME_TWEEN_SAFETY_CHECKS && UNITY_ASSERTIONS using System; using System.Collections.Generic; using System.Linq; using System.Text; using JetBrains.Annotations; using UnityEngine; namespace PrimeTween { internal static class StackTraces { static readonly List idToHash = new List(1000); static readonly Dictionary> hashToTraces = new Dictionary>(100); static bool didWarn; /// https://github.com/Unity-Technologies/UnityCsReference/blob/6230ef8f9bed142ddf6a5e338d6e0faf3368d313/Runtime/Export/Scripting/StackTrace.cs#L31-L47 internal static unsafe void Record(long id) { if (!didWarn && ENABLE_IL2CPP) { didWarn = true; Debug.LogWarning("PRIME_TWEEN_SAFETY_CHECKS is used in IL2CPP build, which has negative performance impact in Development Builds. " + "Please remove the PRIME_TWEEN_SAFETY_CHECKS from 'Project Settings/Player/Scripting Define Symbols' if you no longer need deep debugging support."); } if (id == 1) { idToHash.Clear(); idToHash.Add(0); } const int bufLength = 16 * 1024; byte* buf = stackalloc byte[bufLength]; int len; #if UNITY_2019_4_OR_NEWER len = Debug.ExtractStackTraceNoAlloc(buf, bufLength, Application.dataPath); #else Fallback(); #endif if (len <= 0) { // ExtractStackTraceNoAlloc() doesn't work with IL2CPP, so use the allocating version instead Fallback(); } void Fallback() { string trace = StackTraceUtility.ExtractStackTrace(); fixed (char* chars = trace) { Encoding.UTF8.GetEncoder().Convert(chars, trace.Length, buf, bufLength, true, out _, out len, out _); } } int hash = ComputeHash(buf, len); Assert.AreEqual(id, idToHash.Count); idToHash.Add(hash); if (hashToTraces.TryGetValue(hash, out var traces)) { if (!Contains(traces, buf, len)) { traces.Add(BufToArray()); } } else { hashToTraces.Add(hash, new List { BufToArray() }); } byte[] BufToArray() { var result = new byte[len]; for (int i = 0; i < len; i++) { result[i] = buf[i]; } return result; } } static bool ENABLE_IL2CPP { get { #if ENABLE_IL2CPP return true; #else return false; #endif } } static unsafe bool Contains([NotNull] List arrays, byte* data, int length) { foreach (var arr in arrays) { if (arr.Length == length) { if (SequenceEqual()) { return true; } bool SequenceEqual() { for (int i = 0; i < length; i++) { if (arr[i] != data[i]) { return false; } } return true; } } } return false; } /// https://stackoverflow.com/a/468084 static unsafe int ComputeHash(byte* data, int length) { unchecked { const int p = 16777619; int hash = (int)2166136261; for (int i = 0; i < length; i++) { hash = (hash ^ data[i]) * p; } return hash; } } [NotNull] internal static string Get(long id) { Assert.IsTrue(id < idToHash.Count); // todo limit the max number of stack traces or wrap them around max value bool isSuccess = hashToTraces.TryGetValue(idToHash[(int)id], out var traces); Assert.IsTrue(isSuccess); Assert.IsNotNull(traces); return string.Join("\n\n", traces.Select(bytes => { string str = Encoding.UTF8.GetString(bytes); Assert.IsFalse(string.IsNullOrEmpty(str)); int i = 0; while (true) { var prev = i; i = str.IndexOf('\n', i); if (i == -1) { return str; } i++; int j = str.IndexOf("PrimeTween.", i, StringComparison.Ordinal); if (j != i) { return str.Substring(prev); } } })); } internal static void logDebugInfo() { Debug.Log($"idToHash.Count: {idToHash.Count}, hashToTraces.Count: {hashToTraces.Count}"); } } } #endif