[Draft] Update 0.8 (#46)

* move stuff out into file transfer manager

* obnoxious unsupported version text, adjustments to filetransfermanager

* add back file upload transfer progress

* restructure code

* cleanup some more stuff I guess

* downloadids by playername

* individual anim/sound bs

* fix migration stuff, finalize impl of individual sound/anim pause

* fixes with logging stuff

* move download manager to transient

* rework dl ui first iteration

* some refactoring and cleanup

* more code cleanup

* refactoring

* switch to hostbuilder

* some more rework I guess

* more refactoring

* clean up mediator calls and disposal

* fun code cleanup

* push error message when log level is set to anything but information in non-debug builds

* remove notificationservice

* move message to after login

* add download bars to gameworld

* fixes download progress bar

* set gpose ui min and max size

* remove unnecessary usings

* adjustments to reconnection logic

* add options to set visible/offline groups visibility

* add impl of uploading display, transfer list in settings ui

* attempt to fix issues with server selection

* add back download status to compact ui

* make dl bar fixed size based

* some fixes for upload/download handling

* adjust text from Syncing back to Uploading

---------

Co-authored-by: rootdarkarchon <root.darkarchon@outlook.com>
Co-authored-by: Stanley Dimant <stanley.dimant@varian.com>
This commit is contained in:
rootdarkarchon
2023-03-14 19:48:35 +01:00
committed by GitHub
parent 0824ba434b
commit 0c87e84f25
109 changed files with 7323 additions and 6488 deletions

View File

@@ -7,6 +7,7 @@ namespace MareSynchronos.Utils;
public static class Crypto
{
#pragma warning disable SYSLIB0021 // Type or member is obsolete
public static string GetFileHash(this string filePath)
{
using SHA1CryptoServiceProvider cryptoProvider = new();
@@ -30,5 +31,6 @@ public static class Crypto
using SHA256CryptoServiceProvider cryptoProvider = new();
return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(character.Name + character.HomeWorld.Id.ToString()))).Replace("-", "", StringComparison.Ordinal);
}
#pragma warning restore SYSLIB0021 // Type or member is obsolete
}
}

View File

@@ -1,45 +0,0 @@
using Dalamud.Logging;
using MareSynchronos.MareConfiguration;
using Microsoft.Extensions.Logging;
using System.Text;
namespace MareSynchronos.Utils;
internal class DalamudLogger : ILogger
{
private readonly string _name;
private readonly MareConfigService _mareConfigService;
public DalamudLogger(string name, MareConfigService mareConfigService)
{
this._name = name;
_mareConfigService = mareConfigService;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
if (!IsEnabled(logLevel)) return;
if ((int)logLevel <= (int)LogLevel.Information)
PluginLog.Information($"[{_name}]{{{(int)logLevel}}} {state}");
else
{
StringBuilder sb = new();
sb.AppendLine($"[{_name}]{{{(int)logLevel}}} {state}: {exception?.Message}");
sb.AppendLine(exception?.StackTrace);
if (logLevel == LogLevel.Warning)
PluginLog.Warning(sb.ToString());
else if (logLevel == LogLevel.Error)
PluginLog.Error(sb.ToString());
else
PluginLog.Fatal(sb.ToString());
}
}
public bool IsEnabled(LogLevel logLevel)
{
return (int)_mareConfigService.Current.LogLevel <= (int)logLevel;
}
public IDisposable BeginScope<TState>(TState state) => default!;
}

View File

@@ -1,38 +0,0 @@
using System.Collections.Concurrent;
using MareSynchronos.MareConfiguration;
using Microsoft.Extensions.Logging;
namespace MareSynchronos.Utils;
[ProviderAlias("Dalamud")]
public class DalamudLoggingProvider : ILoggerProvider
{
private readonly ConcurrentDictionary<string, DalamudLogger> _loggers =
new(StringComparer.OrdinalIgnoreCase);
private readonly MareConfigService _mareConfigService;
public DalamudLoggingProvider(MareConfigService mareConfigService)
{
_mareConfigService = mareConfigService;
}
public ILogger CreateLogger(string categoryName)
{
string catName = categoryName.Split(".", StringSplitOptions.RemoveEmptyEntries).Last();
if (catName.Length > 15)
{
catName = string.Join("", catName.Take(6)) + "..." + string.Join("", catName.TakeLast(6));
}
else
{
catName = string.Join("", Enumerable.Range(0, 15 - catName.Length).Select(_ => " ")) + catName;
}
return _loggers.GetOrAdd(catName, name => new DalamudLogger(catName, _mareConfigService));
}
public void Dispose()
{
_loggers.Clear();
}
}

View File

@@ -1,17 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
namespace MareSynchronos.Utils;
public static class DalamudLoggingProviderExtensions
{
public static ILoggingBuilder AddDalamudLogging(this ILoggingBuilder builder)
{
builder.ClearProviders();
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, DalamudLoggingProvider>());
return builder;
}
}

View File

@@ -1,322 +0,0 @@
using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.SubKinds;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Control;
using MareSynchronos.Mediator;
using MareSynchronos.Models;
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
namespace MareSynchronos.Utils;
public class DalamudUtil : IDisposable
{
private readonly ILogger<DalamudUtil> _logger;
private readonly ClientState _clientState;
private readonly ObjectTable _objectTable;
private readonly Framework _framework;
private readonly Condition _condition;
private readonly MareMediator _mediator;
private readonly PerformanceCollector _performanceCollector;
private uint? _classJobId = 0;
private DateTime _delayedFrameworkUpdateCheck = DateTime.Now;
private bool _sentBetweenAreas = false;
public bool IsInCutscene { get; private set; } = false;
public bool IsZoning => _condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51];
public bool IsInGpose { get; private set; } = false;
public unsafe bool IsGameObjectPresent(IntPtr key)
{
foreach (var obj in _objectTable)
{
if (obj.Address == key)
{
return true;
}
}
return false;
}
public DalamudUtil(ILogger<DalamudUtil> logger, ClientState clientState, ObjectTable objectTable, Framework framework,
Condition condition, Dalamud.Data.DataManager gameData, MareMediator mediator, PerformanceCollector performanceCollector)
{
_logger = logger;
_clientState = clientState;
_objectTable = objectTable;
_framework = framework;
_condition = condition;
_mediator = mediator;
_performanceCollector = performanceCollector;
_framework.Update += FrameworkOnUpdate;
if (IsLoggedIn)
{
_classJobId = _clientState.LocalPlayer!.ClassJob.Id;
}
WorldData = new(() =>
{
return gameData.GetExcelSheet<Lumina.Excel.GeneratedSheets.World>(Dalamud.ClientLanguage.English)!
.Where(w => w.IsPublic && !w.Name.RawData.IsEmpty)
.ToDictionary(w => (ushort)w.RowId, w => w.Name.ToString());
});
}
public Lazy<Dictionary<ushort, string>> WorldData { get; private set; }
private void FrameworkOnUpdate(Framework framework)
{
_performanceCollector.LogPerformance(this, "FrameworkOnUpdate", FrameworkOnUpdateInternal);
}
private unsafe void FrameworkOnUpdateInternal()
{
if (GposeTarget != null && !IsInGpose)
{
_logger.LogDebug("Gpose start");
IsInGpose = true;
_mediator.Publish(new GposeStartMessage());
}
else if (GposeTarget == null && IsInGpose)
{
_logger.LogDebug("Gpose end");
IsInGpose = false;
_mediator.Publish(new GposeEndMessage());
}
if (_condition[ConditionFlag.WatchingCutscene] && !IsInCutscene)
{
_logger.LogDebug("Cutscene start");
IsInCutscene = true;
_mediator.Publish(new CutsceneStartMessage());
_mediator.Publish(new HaltScanMessage("Cutscene"));
}
else if (!_condition[ConditionFlag.WatchingCutscene] && IsInCutscene)
{
_logger.LogDebug("Cutscene end");
IsInCutscene = false;
_mediator.Publish(new CutsceneEndMessage());
_mediator.Publish(new ResumeScanMessage("Cutscene"));
}
if (IsInCutscene) { _mediator.Publish(new CutsceneFrameworkUpdateMessage()); return; }
if (_condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51])
{
if (!_sentBetweenAreas)
{
_logger.LogDebug("Zone switch/Gpose start");
_sentBetweenAreas = true;
_mediator.Publish(new ZoneSwitchStartMessage());
_mediator.Publish(new HaltScanMessage("Zone switch"));
}
return;
}
if (_sentBetweenAreas)
{
_logger.LogDebug("Zone switch/Gpose end");
_sentBetweenAreas = false;
_mediator.Publish(new ZoneSwitchEndMessage());
_mediator.Publish(new ResumeScanMessage("Zone switch"));
}
_mediator.Publish(new FrameworkUpdateMessage());
if (DateTime.Now < _delayedFrameworkUpdateCheck.AddSeconds(1)) return;
var localPlayer = _clientState.LocalPlayer;
if (localPlayer != null && !IsLoggedIn)
{
_logger.LogDebug("Logged in");
IsLoggedIn = true;
_mediator.Publish(new DalamudLoginMessage());
}
else if (localPlayer == null && IsLoggedIn)
{
_logger.LogDebug("Logged out");
IsLoggedIn = false;
_mediator.Publish(new DalamudLogoutMessage());
}
if (_clientState.LocalPlayer != null && _clientState.LocalPlayer.IsValid())
{
var newclassJobId = _clientState.LocalPlayer.ClassJob.Id;
if (_classJobId != newclassJobId)
{
_classJobId = newclassJobId;
_mediator.Publish(new ClassJobChangedMessage());
}
}
_mediator.Publish(new DelayedFrameworkUpdateMessage());
_delayedFrameworkUpdateCheck = DateTime.Now;
}
public Dalamud.Game.ClientState.Objects.Types.GameObject? CreateGameObject(IntPtr reference)
{
return _objectTable.CreateObjectReference(reference);
}
public unsafe GameObject* GposeTarget => TargetSystem.Instance()->GPoseTarget;
public unsafe Dalamud.Game.ClientState.Objects.Types.GameObject? GposeTargetGameObject => GposeTarget == null ? null : _objectTable[GposeTarget->ObjectIndex];
public bool IsLoggedIn { get; private set; }
public bool IsPlayerPresent => _clientState.LocalPlayer != null && _clientState.LocalPlayer.IsValid();
public static bool IsObjectPresent(Dalamud.Game.ClientState.Objects.Types.GameObject? obj)
{
return obj != null && obj.IsValid();
}
public unsafe IntPtr GetMinion(IntPtr? playerPointer = null)
{
playerPointer ??= PlayerPointer;
return (IntPtr)((Character*)playerPointer)->CompanionObject;
}
public unsafe IntPtr GetPet(IntPtr? playerPointer = null)
{
var mgr = CharacterManager.Instance();
playerPointer ??= PlayerPointer;
return (IntPtr)mgr->LookupPetByOwnerObject((BattleChara*)playerPointer);
}
public unsafe IntPtr GetCompanion(IntPtr? playerPointer = null)
{
var mgr = CharacterManager.Instance();
playerPointer ??= PlayerPointer;
return (IntPtr)mgr->LookupBuddyByOwnerObject((BattleChara*)playerPointer);
}
public unsafe IntPtr GetMinionOrMount(IntPtr? playerPointer = null)
{
playerPointer ??= PlayerPointer;
if (playerPointer == IntPtr.Zero) return IntPtr.Zero;
return _objectTable.GetObjectAddress(((GameObject*)playerPointer)->ObjectIndex + 1);
}
public string PlayerName => _clientState.LocalPlayer?.Name.ToString() ?? "--";
public uint WorldId => _clientState.LocalPlayer!.HomeWorld.Id;
public IntPtr PlayerPointer => _clientState.LocalPlayer?.Address ?? IntPtr.Zero;
public PlayerCharacter PlayerCharacter => _clientState.LocalPlayer!;
public string PlayerNameHashed => Crypto.GetHash256(PlayerName + _clientState.LocalPlayer!.HomeWorld.Id);
public List<PlayerCharacter> GetPlayerCharacters()
{
return _objectTable.Where(obj =>
obj.ObjectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player &&
!string.Equals(obj.Name.ToString(), PlayerName, StringComparison.Ordinal)).Cast<PlayerCharacter>().ToList();
}
public Dalamud.Game.ClientState.Objects.Types.Character? GetCharacterFromObjectTableByIndex(int index)
{
var objTableObj = _objectTable[index];
if (objTableObj!.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) return null;
return (Dalamud.Game.ClientState.Objects.Types.Character)objTableObj;
}
public PlayerCharacter? GetPlayerCharacterFromObjectTableByName(string characterName)
{
foreach (var item in _objectTable)
{
if (item.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue;
if (string.Equals(item.Name.ToString(), characterName, StringComparison.Ordinal)) return (PlayerCharacter)item;
}
return null;
}
public int? GetIndexFromObjectTableByName(string characterName)
{
for (int i = 0; i < _objectTable.Length; i++)
{
if (_objectTable[i] == null) continue;
if (_objectTable[i]!.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue;
if (string.Equals(_objectTable[i]!.Name.ToString(), characterName, StringComparison.Ordinal)) return i;
}
return null;
}
public async Task RunOnFrameworkThread(Action act)
{
_logger.LogTrace("Running Action on framework thread: {act}", act);
await _framework.RunOnFrameworkThread(act).ConfigureAwait(false);
}
public async Task<T> RunOnFrameworkThread<T>(Func<T> func)
{
_logger.LogTrace("Running Func on framework thread: {func}", func);
return await _framework.RunOnFrameworkThread(func).ConfigureAwait(false);
}
public async Task WaitWhileCharacterIsDrawing(ILogger logger, GameObjectHandler handler, Guid redrawId, int timeOut = 5000, CancellationToken? ct = null)
{
if (!_clientState.IsLoggedIn || handler.Address == IntPtr.Zero) return;
logger.LogTrace($"[{redrawId}] Starting wait for {handler} to draw");
const int tick = 250;
int curWaitTime = 0;
try
{
// ReSharper disable once LoopVariableIsNeverChangedInsideLoop
while ((!ct?.IsCancellationRequested ?? true)
&& curWaitTime < timeOut
&& await handler.IsBeingDrawnRunOnFramework().ConfigureAwait(true)) // 0b100000000000 is "still rendering" or something
{
logger.LogTrace($"[{redrawId}] Waiting for {handler} to finish drawing");
curWaitTime += tick;
await Task.Delay(tick).ConfigureAwait(true);
}
logger.LogTrace($"[{redrawId}] Finished drawing after {curWaitTime}ms");
}
catch (NullReferenceException ex)
{
logger.LogWarning(ex, "Error accessing " + handler + ", object does not exist anymore?");
}
catch (AccessViolationException ex)
{
logger.LogWarning(ex, "Error accessing " + handler + ", object does not exist anymore?");
}
}
public unsafe void WaitWhileGposeCharacterIsDrawing(IntPtr characterAddress, int timeOut = 5000)
{
Thread.Sleep(500);
var obj = (GameObject*)characterAddress;
const int tick = 250;
int curWaitTime = 0;
_logger.LogTrace("RenderFlags:" + obj->RenderFlags.ToString("X"));
// ReSharper disable once LoopVariableIsNeverChangedInsideLoop
while (obj->RenderFlags != 0x00 && curWaitTime < timeOut)
{
_logger.LogTrace($"Waiting for gpose actor to finish drawing");
curWaitTime += tick;
Thread.Sleep(tick);
}
Thread.Sleep(tick * 2);
}
public void Dispose()
{
_logger.LogTrace($"Disposing {GetType()}");
_framework.Update -= FrameworkOnUpdate;
}
}

View File

@@ -1,45 +0,0 @@
using MareSynchronos.Models;
namespace MareSynchronos.Utils;
public class FileReplacementComparer : IEqualityComparer<FileReplacement>
{
public static FileReplacementComparer Instance => _instance;
private static FileReplacementComparer _instance = new();
private FileReplacementComparer() { }
public bool Equals(FileReplacement? x, FileReplacement? y)
{
if (x == null || y == null) return false;
return x.ResolvedPath.Equals(y.ResolvedPath) && CompareLists(x.GamePaths, y.GamePaths);
}
public int GetHashCode(FileReplacement obj)
{
return HashCode.Combine(obj.ResolvedPath.GetHashCode(StringComparison.OrdinalIgnoreCase), GetOrderIndependentHashCode(obj.GamePaths));
}
private static int GetOrderIndependentHashCode<T>(IEnumerable<T> source)
{
int hash = 0;
foreach (T element in source)
{
hash = unchecked(hash +
EqualityComparer<T>.Default.GetHashCode(element));
}
return hash;
}
private bool CompareLists(HashSet<string> list1, HashSet<string> list2)
{
if (list1.Count != list2.Count)
return false;
for (int i = 0; i < list1.Count; i++)
{
if (!string.Equals(list1.ElementAt(i), list2.ElementAt(i), StringComparison.OrdinalIgnoreCase))
return false;
}
return true;
}
}

View File

@@ -1,45 +0,0 @@
using MareSynchronos.API.Data;
namespace MareSynchronos.Utils;
public class FileReplacementDataComparer : IEqualityComparer<FileReplacementData>
{
public static FileReplacementDataComparer Instance => _instance;
private static FileReplacementDataComparer _instance = new();
private FileReplacementDataComparer() { }
public bool Equals(FileReplacementData? x, FileReplacementData? y)
{
if (x == null || y == null) return false;
return x.Hash.Equals(y.Hash) && CompareLists(x.GamePaths.ToHashSet(StringComparer.Ordinal), y.GamePaths.ToHashSet(StringComparer.Ordinal)) && string.Equals(x.FileSwapPath, y.FileSwapPath, StringComparison.Ordinal);
}
public int GetHashCode(FileReplacementData obj)
{
return HashCode.Combine(obj.Hash.GetHashCode(StringComparison.OrdinalIgnoreCase), GetOrderIndependentHashCode(obj.GamePaths), StringComparer.Ordinal.GetHashCode(obj.FileSwapPath));
}
private static int GetOrderIndependentHashCode<T>(IEnumerable<T> source)
{
int hash = 0;
foreach (T element in source)
{
hash = unchecked(hash +
EqualityComparer<T>.Default.GetHashCode(element));
}
return hash;
}
private bool CompareLists(HashSet<string> list1, HashSet<string> list2)
{
if (list1.Count != list2.Count)
return false;
for (int i = 0; i < list1.Count; i++)
{
if (!string.Equals(list1.ElementAt(i), list2.ElementAt(i), StringComparison.OrdinalIgnoreCase))
return false;
}
return true;
}
}

View File

@@ -1,199 +0,0 @@
using MareSynchronos.MareConfiguration;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Globalization;
using System.Text;
namespace MareSynchronos.Utils;
public class PerformanceCollector : IDisposable
{
private readonly ConcurrentDictionary<string, RollingList<Tuple<TimeOnly, long>>> _performanceCounters = new(StringComparer.Ordinal);
private readonly ILogger<PerformanceCollector> _logger;
private readonly MareConfigService _mareConfigService;
private const string _counterSplit = "=>";
private readonly CancellationTokenSource _periodicLogPruneTask = new();
public PerformanceCollector(ILogger<PerformanceCollector> logger, MareConfigService mareConfigService)
{
_logger = logger;
_mareConfigService = mareConfigService;
_ = Task.Run(PeriodicLogPrune, _periodicLogPruneTask.Token);
}
public void Dispose()
{
_logger.LogTrace("Disposing {this}", GetType());
_periodicLogPruneTask.Cancel();
}
private async Task PeriodicLogPrune()
{
while (!_periodicLogPruneTask.Token.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromMinutes(10), _periodicLogPruneTask.Token).ConfigureAwait(false);
foreach (var entries in _performanceCounters.ToList())
{
try
{
var last = entries.Value.ToList().Last();
if (last.Item1.AddMinutes(10) < TimeOnly.FromDateTime(DateTime.Now))
{
_performanceCounters.Remove(entries.Key, out _);
}
}
catch (Exception e)
{
_logger.LogDebug("Error removing performance counter {counter}", entries.Key);
}
}
}
}
public T LogPerformance<T>(object sender, string counterName, Func<T> func)
{
if (!_mareConfigService.Current.LogPerformance) return func.Invoke();
counterName = sender.GetType().Name + _counterSplit + counterName;
if (!_performanceCounters.TryGetValue(counterName, out var list))
{
list = _performanceCounters[counterName] = new(10000);
}
Stopwatch st = Stopwatch.StartNew();
try
{
return func.Invoke();
}
catch
{
throw;
}
finally
{
st.Stop();
list.Add(new(TimeOnly.FromDateTime(DateTime.Now), st.ElapsedTicks));
}
}
public void LogPerformance(object sender, string counterName, Action act)
{
if (!_mareConfigService.Current.LogPerformance) { act.Invoke(); return; }
counterName = sender.GetType().Name + _counterSplit + counterName;
if (!_performanceCounters.TryGetValue(counterName, out var list))
{
list = _performanceCounters[counterName] = new(10000);
}
Stopwatch st = Stopwatch.StartNew();
try
{
act.Invoke();
}
catch
{
throw;
}
finally
{
st.Stop();
list.Add(new(TimeOnly.FromDateTime(DateTime.Now), st.ElapsedTicks));
}
}
internal void PrintPerformanceStats(int limitBySeconds = 0)
{
if (!_mareConfigService.Current.LogPerformance)
{
_logger.LogWarning("Performance counters are disabled");
}
StringBuilder sb = new();
if (limitBySeconds > 0)
{
sb.AppendLine($"Performance Metrics over the past {limitBySeconds} seconds of each counter");
}
else
{
sb.AppendLine("Performance metrics over total lifetime of each counter");
}
var data = _performanceCounters.ToList();
var longestCounterName = data.OrderByDescending(d => d.Key.Length).First().Key.Length + 2;
sb.Append("-Last".PadRight(15, '-'));
sb.Append('|');
sb.Append("-Max".PadRight(15, '-'));
sb.Append('|');
sb.Append("-Average".PadRight(15, '-'));
sb.Append('|');
sb.Append("-Last Update".PadRight(15, '-'));
sb.Append('|');
sb.Append("-Entries".PadRight(10, '-'));
sb.Append('|');
sb.Append("-Counter Name".PadRight(longestCounterName, '-'));
sb.AppendLine();
var orderedData = data.OrderBy(k => k.Key, StringComparer.OrdinalIgnoreCase).ToList();
var previousCaller = orderedData.First().Key.Split(_counterSplit, StringSplitOptions.RemoveEmptyEntries)[0];
foreach (var entry in orderedData)
{
var newCaller = entry.Key.Split(_counterSplit, StringSplitOptions.RemoveEmptyEntries)[0];
if (!string.Equals(previousCaller, newCaller, StringComparison.Ordinal))
{
DrawSeparator(sb, longestCounterName);
}
var pastEntries = limitBySeconds > 0 ? entry.Value.Where(e => e.Item1.AddMinutes(limitBySeconds / 60.0d) >= TimeOnly.FromDateTime(DateTime.Now)).ToList() : entry.Value.ToList();
if (pastEntries.Any())
{
sb.Append((" " + TimeSpan.FromTicks(pastEntries.LastOrDefault()?.Item2 ?? 0).TotalMilliseconds.ToString("0.00000", CultureInfo.InvariantCulture)).PadRight(15));
sb.Append('|');
sb.Append((" " + TimeSpan.FromTicks(pastEntries.Max(m => m.Item2)).TotalMilliseconds.ToString("0.00000", CultureInfo.InvariantCulture)).PadRight(15));
sb.Append('|');
sb.Append((" " + TimeSpan.FromTicks((long)pastEntries.Average(m => m.Item2)).TotalMilliseconds.ToString("0.00000", CultureInfo.InvariantCulture)).PadRight(15));
}
else
{
sb.Append(" -".PadRight(15));
sb.Append('|');
sb.Append(" -".PadRight(15));
sb.Append('|');
sb.Append(" -".PadRight(15));
}
sb.Append('|');
sb.Append((" " + (pastEntries.LastOrDefault()?.Item1.ToString("HH:mm:ss.ffff", CultureInfo.InvariantCulture) ?? "-")).PadRight(15, ' '));
sb.Append('|');
sb.Append((" " + pastEntries.Count).PadRight(10));
sb.Append('|');
sb.Append(' ').Append(entry.Key);
sb.AppendLine();
previousCaller = newCaller;
}
DrawSeparator(sb, longestCounterName);
_logger.LogInformation("{perf}", sb.ToString());
}
private static void DrawSeparator(StringBuilder sb, int longestCounterName)
{
sb.Append("".PadRight(15, '-'));
sb.Append('+');
sb.Append("".PadRight(15, '-'));
sb.Append('+');
sb.Append("".PadRight(15, '-'));
sb.Append('+');
sb.Append("".PadRight(15, '-'));
sb.Append('+');
sb.Append("".PadRight(10, '-'));
sb.Append('+');
sb.Append("".PadRight(longestCounterName, '-'));
sb.AppendLine();
}
}

View File

@@ -4,8 +4,8 @@ namespace MareSynchronos.Utils;
public class RollingList<T> : IEnumerable<T>
{
private readonly LinkedList<T> _list = new();
private readonly object _addLock = new();
private readonly LinkedList<T> _list = new();
public RollingList(int maximumCount)
{
@@ -15,8 +15,19 @@ public class RollingList<T> : IEnumerable<T>
MaximumCount = maximumCount;
}
public int MaximumCount { get; }
public int Count => _list.Count;
public int MaximumCount { get; }
public T this[int index]
{
get
{
if (index < 0 || index >= Count)
throw new ArgumentOutOfRangeException(nameof(index));
return _list.Skip(index).First();
}
}
public void Add(T value)
{
@@ -30,17 +41,7 @@ public class RollingList<T> : IEnumerable<T>
}
}
public T this[int index]
{
get
{
if (index < 0 || index >= Count)
throw new ArgumentOutOfRangeException(nameof(index));
return _list.Skip(index).First();
}
}
public IEnumerator<T> GetEnumerator() => _list.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

View File

@@ -1,32 +1,11 @@
using System.Globalization;
using System.Reflection;
using Newtonsoft.Json;
using System.Text.Json;
namespace MareSynchronos.Utils;
public static class VariousExtensions
{
public static DateTime GetLinkerTime(Assembly assembly)
{
const string BuildVersionMetadataPrefix = "+build";
var attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
if (attribute?.InformationalVersion != null)
{
var value = attribute.InformationalVersion;
var index = value.IndexOf(BuildVersionMetadataPrefix, StringComparison.Ordinal);
if (index > 0)
{
value = value[(index + BuildVersionMetadataPrefix.Length)..];
return DateTime.ParseExact(value, "yyyy-MM-ddTHH:mm:ss:fffZ", CultureInfo.InvariantCulture);
}
}
return default;
}
public static T DeepClone<T>(this T obj)
{
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(obj))!;
return JsonSerializer.Deserialize<T>(JsonSerializer.Serialize(obj))!;
}
}