138 lines
5.0 KiB
C#

#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<int> idToHash = new List<int>(1000);
static readonly Dictionary<int, List<byte[]>> hashToTraces = new Dictionary<int, List<byte[]>>(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<byte[]> { 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<byte[]> 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