Update EcsDebug for thread safety

This commit is contained in:
Mikhail 2024-10-05 00:25:42 +08:00
parent a4d7e56c55
commit 89bf620611

View File

@ -22,21 +22,21 @@ namespace DCFApixels.DragonECS
public EcsProfilerMarker(string name) public EcsProfilerMarker(string name)
{ {
#if ((DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER) #if ((DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER)
id = DebugService.Instance.RegisterMark(name); id = DebugService.CurrentThreadInstance.RegisterMark(name);
#endif #endif
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Begin() public void Begin()
{ {
#if ((DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER) #if ((DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER)
DebugService.Instance.ProfilerMarkBegin(id); DebugService.CurrentThreadInstance.ProfilerMarkBegin(id);
#endif #endif
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void End() public void End()
{ {
#if ((DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER) #if ((DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER)
DebugService.Instance.ProfilerMarkEnd(id); DebugService.CurrentThreadInstance.ProfilerMarkEnd(id);
#endif #endif
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -58,14 +58,14 @@ namespace DCFApixels.DragonECS
{ {
#if ((DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER) #if ((DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER)
_id = id; _id = id;
DebugService.Instance.ProfilerMarkBegin(id); DebugService.CurrentThreadInstance.ProfilerMarkBegin(id);
#endif #endif
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose() public void Dispose()
{ {
#if ((DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER) #if ((DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER)
DebugService.Instance.ProfilerMarkEnd(_id); DebugService.CurrentThreadInstance.ProfilerMarkEnd(_id);
#endif #endif
} }
} }
@ -91,80 +91,204 @@ namespace DCFApixels.DragonECS
public static void PrintWarning(object v) public static void PrintWarning(object v)
{ {
#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER
DebugService.Instance.PrintWarning(v); DebugService.CurrentThreadInstance.PrintWarning(v);
#endif #endif
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void PrintError(object v) public static void PrintError(object v)
{ {
#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER
DebugService.Instance.PrintError(v); DebugService.CurrentThreadInstance.PrintError(v);
#endif #endif
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void PrintErrorAndBreak(object v) public static void PrintErrorAndBreak(object v)
{ {
#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER
DebugService.Instance.PrintErrorAndBreak(v); DebugService.CurrentThreadInstance.PrintErrorAndBreak(v);
#endif #endif
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void PrintPass(object v) public static void PrintPass(object v)
{ {
#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER
DebugService.Instance.PrintPass(v); DebugService.CurrentThreadInstance.PrintPass(v);
#endif #endif
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Print() public static void Print()
{ {
#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER
DebugService.Instance.Print(); DebugService.CurrentThreadInstance.Print();
#endif #endif
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Print(object v) public static void Print(object v)
{ {
#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER
DebugService.Instance.Print(v); DebugService.CurrentThreadInstance.Print(v);
#endif #endif
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Print(string tag, object v) public static void Print(string tag, object v)
{ {
#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER
DebugService.Instance.Print(tag, v); DebugService.CurrentThreadInstance.Print(tag, v);
#endif #endif
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Break() public static void Break()
{ {
#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_DEBUGGER
DebugService.Instance.Break(); DebugService.CurrentThreadInstance.Break();
#endif #endif
} }
} }
//------------------------------------------------------------------------------------------------------------//
public abstract class DebugService public abstract class DebugService
{ {
private static DebugService _instance; private static DebugService _instance;
private static object _lock = new object(); private static object _lock = new object();
public static DebugService Instance private static HashSet<DebugService> _threadServiceClonesSet = new HashSet<DebugService>();
{
[ThreadStatic]
private static DebugService _currentThreadInstanceClone;
[ThreadStatic]
private static DebugService _currentThreadInstance; // для сравнения
private static IdDispenser _idDispenser = new IdDispenser(16, 0);
private static Dictionary<string, int> _nameIdTable = new Dictionary<string, int>();
#region Properties
public static DebugService CurrentThreadInstance
{// ts завист от Set
get get
{ {
if (_instance == null) if (_currentThreadInstance != _instance)
{ {
lock (_lock) lock (_lock)
{ {
_instance = new DefaultDebugService(); if (_currentThreadInstance != _instance)
{
_currentThreadInstanceClone = _instance.Clone();
_threadServiceClonesSet.Add(_currentThreadInstanceClone);
_currentThreadInstance = _instance;
foreach (var record in _nameIdTable)
{
_currentThreadInstanceClone.OnNewProfilerMark(record.Value, record.Key);
}
}
} }
} }
return _instance; return _currentThreadInstanceClone;
}
}
public IEnumerable<MarkerInfo> MarkerInfos
{
get { return _nameIdTable.Select(o => new MarkerInfo(o.Key, o.Value)); }
}
#endregion
static DebugService()
{
Set(new DefaultDebugService());
}
#region Set
public static void Set<T>() where T : DebugService, new()
{
lock (_lock)
{
if (CurrentThreadInstance is T == false)
{
Set(new T());
}
}
}
public static void Set(DebugService service)
{// ts
lock (_lock)
{
if (service == null)
{
service = new NullDebugService();
}
if (_instance != service)
{
var oldService = _instance;
_instance = service;
if (oldService != null)
{ //TODO Так, всеже треды влияют друг на друга, скоерее всего проблема в использовании _nameIdTable/ Так вроде пофиксил, но не понял как конкретно
foreach (var record in _nameIdTable)
{
service.OnNewProfilerMark(record.Value, record.Key);
}
}
service.OnServiceSetup(oldService);
OnServiceChanged(service);
}
}
}
#endregion
#region Setup/Clone
protected virtual void OnServiceSetup(DebugService oldService) { }
protected abstract DebugService Clone();
#endregion
#region Print/Break
public abstract void Print(string tag, object v);
public abstract void Break();
#endregion
#region ProfilerMarkesrs
public int RegisterMark(string name)
{
int id;
if (_nameIdTable.TryGetValue(name, out id) == false)
{
lock (_lock)
{
if (_nameIdTable.TryGetValue(name, out id) == false)
{
id = _idDispenser.UseFree();
_nameIdTable.Add(name, id);
foreach (var service in _threadServiceClonesSet)
{
service.OnNewProfilerMark(id, name);
}
}
}
}
return id;
}
public void DeleteMark(string name)
{
lock (_lock)
{
int id = _nameIdTable[name];
_nameIdTable.Remove(name);
_idDispenser.Release(id);
foreach (var service in _threadServiceClonesSet)
{
service.OnNewProfilerMark(id, name);
}
OnDelProfilerMark(id);
} }
} }
protected abstract void OnNewProfilerMark(int id, string name);
protected abstract void OnDelProfilerMark(int id);
public abstract void ProfilerMarkBegin(int id);
public abstract void ProfilerMarkEnd(int id);
#endregion
#region Utils
protected static string AutoConvertObjectToString(object o) protected static string AutoConvertObjectToString(object o)
{ {
if (o is string str) if (o is string str)
@ -189,103 +313,28 @@ namespace DCFApixels.DragonECS
} }
public override string ToString() { return this.AutoToString(); } public override string ToString() { return this.AutoToString(); }
} }
public IEnumerable<MarkerInfo> MarkerInfos #endregion
{
get { return _nameIdTable.Select(o => new MarkerInfo(o.Key, o.Value)); }
}
public static void Set<T>() where T : DebugService, new()
{
lock (_lock)
{
if (Instance is T == false)
{
Set(new T());
}
}
}
public static void Set(DebugService service)
{
lock (_lock)
{
if (_instance != service)
{
var oldService = _instance;
_instance = service;
if (_instance != null)
{ //TODO Так, всеже треды влияют друг на друга, скоерее всего проблема в использовании _nameIdTable/ Так вроде пофиксил, но не понял как конкретно
foreach (var info in oldService.MarkerInfos)
{
service._idDispenser.Use(info.ID);
service._nameIdTable.TryAdd(info.Name, info.ID);
service.OnNewProfilerMark(info.ID, info.Name);
}
}
service.OnServiceSetup(oldService);
OnServiceChanged(service);
}
}
}
protected virtual void OnServiceSetup(DebugService oldService) { }
public static Action<DebugService> OnServiceChanged = delegate { }; public static Action<DebugService> OnServiceChanged = delegate { };
private IdDispenser _idDispenser = new IdDispenser(16, 0);
private Dictionary<string, int> _nameIdTable = new Dictionary<string, int>();
public abstract void Print(string tag, object v);
public abstract void Break();
public int RegisterMark(string name)
{
int id;
if (!_nameIdTable.TryGetValue(name, out id))
{
lock (_lock)
{
if (!_nameIdTable.TryGetValue(name, out id))
{
id = _idDispenser.UseFree();
_nameIdTable.Add(name, id);
OnNewProfilerMark(id, name);
}
}
}
return id;
}
public void DeleteMark(string name)
{
lock (_lock)
{
int id = _nameIdTable[name];
_nameIdTable.Remove(name);
_idDispenser.Release(id);
OnDelProfilerMark(id);
}
}
protected abstract void OnNewProfilerMark(int id, string name);
protected abstract void OnDelProfilerMark(int id);
public abstract void ProfilerMarkBegin(int id);
public abstract void ProfilerMarkEnd(int id);
} }
public static class DebugServiceExtensions public static class DebugServiceExtensions
{ {
public static void PrintWarning(this DebugService self, object v) public static void PrintWarning(this DebugService self, object v)
{ {
self.Print(EcsConsts.DEBUG_WARNING_TAG, v); self.Print(DEBUG_WARNING_TAG, v);
} }
public static void PrintError(this DebugService self, object v) public static void PrintError(this DebugService self, object v)
{ {
self.Print(EcsConsts.DEBUG_ERROR_TAG, v); self.Print(DEBUG_ERROR_TAG, v);
} }
public static void PrintErrorAndBreak(this DebugService self, object v) public static void PrintErrorAndBreak(this DebugService self, object v)
{ {
self.Print(EcsConsts.DEBUG_ERROR_TAG, v); self.Print(DEBUG_ERROR_TAG, v);
self.Break(); self.Break();
} }
public static void PrintPass(this DebugService self, object v) public static void PrintPass(this DebugService self, object v)
{ {
self.Print(EcsConsts.DEBUG_PASS_TAG, v); self.Print(DEBUG_PASS_TAG, v);
} }
public static void Print(this DebugService self, object v) public static void Print(this DebugService self, object v)
{ {
@ -297,40 +346,41 @@ namespace DCFApixels.DragonECS
} }
//TODO PrintJson возможно будет добавлено когда-то //TODO PrintJson возможно будет добавлено когда-то
} }
//------------------------------------------------------------------------------------------------------------//
public sealed class NullDebugService : DebugService
{
protected sealed override DebugService Clone() { return this; }
public sealed override void Break() { }
public sealed override void Print(string tag, object v) { }
public sealed override void ProfilerMarkBegin(int id) { }
public sealed override void ProfilerMarkEnd(int id) { }
protected sealed override void OnDelProfilerMark(int id) { }
protected sealed override void OnNewProfilerMark(int id, string name) { }
}
//------------------------------------------------------------------------------------------------------------//
public sealed class DefaultDebugService : DebugService public sealed class DefaultDebugService : DebugService
{ {
#if !UNITY_5_3_OR_NEWER #if !UNITY_5_3_OR_NEWER
private const string PROFILER_MARKER = "ProfilerMark"; private const string PROFILER_MARKER = "ProfilerMark";
private const string PROFILER_MARKER_CACHE = "[" + PROFILER_MARKER + "] "; private const string PROFILER_MARKER_CACHE = "[" + PROFILER_MARKER + "] ";
private readonly struct MarkerData private MarkerData[] _stopwatchs = new MarkerData[64];
{ private char[] _buffer = new char[128];
public readonly System.Diagnostics.Stopwatch Stopwatch;
public readonly string Name;
public readonly int ID;
public MarkerData(System.Diagnostics.Stopwatch stopwatch, string name, int id)
{
Stopwatch = stopwatch;
Name = name;
ID = id;
}
public override string ToString()
{
return this.AutoToString();
}
}
private MarkerData[] _stopwatchs;
[ThreadStatic]
private static char[] _buffer;
public DefaultDebugService() public DefaultDebugService()
{ {
Console.ForegroundColor = ConsoleColor.White; Console.ForegroundColor = ConsoleColor.White;
Console.BackgroundColor = ConsoleColor.Black; Console.BackgroundColor = ConsoleColor.Black;
_stopwatchs = new MarkerData[64];
} }
protected sealed override DebugService Clone()
public override void Print(string tag, object v) {
return new DefaultDebugService();
}
public sealed override void Print(string tag, object v)
{ {
if (string.IsNullOrEmpty(tag)) if (string.IsNullOrEmpty(tag))
{ {
@ -355,7 +405,7 @@ namespace DCFApixels.DragonECS
Console.ForegroundColor = color; Console.ForegroundColor = color;
} }
} }
public override void Break() public sealed override void Break()
{ {
var color = Console.ForegroundColor; var color = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Cyan; Console.ForegroundColor = ConsoleColor.Cyan;
@ -364,7 +414,7 @@ namespace DCFApixels.DragonECS
Console.ForegroundColor = color; Console.ForegroundColor = color;
} }
public override void ProfilerMarkBegin(int id) public sealed override void ProfilerMarkBegin(int id)
{ {
var color = Console.ForegroundColor; var color = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.DarkGray; Console.ForegroundColor = ConsoleColor.DarkGray;
@ -376,7 +426,7 @@ namespace DCFApixels.DragonECS
Console.ForegroundColor = color; Console.ForegroundColor = color;
} }
public override void ProfilerMarkEnd(int id) public sealed override void ProfilerMarkEnd(int id)
{ {
var color = Console.ForegroundColor; var color = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.DarkGray; Console.ForegroundColor = ConsoleColor.DarkGray;
@ -390,28 +440,27 @@ namespace DCFApixels.DragonECS
Console.Write(" s:"); Console.Write(" s:");
int written = 0; int written = 0;
if (_buffer == null) { _buffer = new char[128]; }
ConvertDoubleToText(time.TotalSeconds, _buffer, ref written); ConvertDoubleToText(time.TotalSeconds, _buffer, ref written);
Console.WriteLine(_buffer, 0, written); Console.WriteLine(_buffer, 0, written);
Console.ForegroundColor = color; Console.ForegroundColor = color;
} }
protected override void OnDelProfilerMark(int id) protected sealed override void OnDelProfilerMark(int id)
{ {
_stopwatchs[id] = default; _stopwatchs[id] = default;
} }
protected override void OnNewProfilerMark(int id, string name) protected sealed override void OnNewProfilerMark(int id, string name)
{ {
if (id >= _stopwatchs.Length) if (id >= _stopwatchs.Length)
{ {
Array.Resize(ref _stopwatchs, _stopwatchs.Length << 1); Array.Resize(ref _stopwatchs, id << 1);
} }
_stopwatchs[id] = new MarkerData(new System.Diagnostics.Stopwatch(), name, id); _stopwatchs[id] = new MarkerData(new System.Diagnostics.Stopwatch(), name, id);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
static void ConvertDoubleToText(double value, char[] stringBuffer, ref int written) private static void ConvertDoubleToText(double value, char[] stringBuffer, ref int written)
{ {
int bufferLength = stringBuffer.Length - 1; int bufferLength = stringBuffer.Length - 1;
@ -462,13 +511,31 @@ namespace DCFApixels.DragonECS
written = bufferLength - zeroPartLength; written = bufferLength - zeroPartLength;
} }
private readonly struct MarkerData
{
public readonly System.Diagnostics.Stopwatch Stopwatch;
public readonly string Name;
public readonly int ID;
public MarkerData(System.Diagnostics.Stopwatch stopwatch, string name, int id)
{
Stopwatch = stopwatch;
Name = name;
ID = id;
}
public override string ToString()
{
return this.AutoToString();
}
}
#else #else
public override void Break() { } protected sealed override DebugService Clone() { return this; }
public override void Print(string tag, object v) { } public sealed override void Break() { }
public override void ProfilerMarkBegin(int id) { } public sealed override void Print(string tag, object v) { }
public override void ProfilerMarkEnd(int id) { } public sealed override void ProfilerMarkBegin(int id) { }
protected override void OnDelProfilerMark(int id) { } public sealed override void ProfilerMarkEnd(int id) { }
protected override void OnNewProfilerMark(int id, string name) { } protected sealed override void OnDelProfilerMark(int id) { }
protected sealed override void OnNewProfilerMark(int id, string name) { }
#endif #endif
} }
} }