138 lines
5.0 KiB
C#
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
|