using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Threading.Tasks; using Damntry.Utils.Collections.Queues.Interfaces; using Damntry.Utils.ExtensionMethods; using Damntry.Utils.Logging; using Damntry.Utils.Reflection; using Damntry.Utils.Tasks; using Damntry.Utils.Tasks.AsyncDelay; using Damntry.Utils.Tasks.TaskTimeout; using Damntry.Utils.Timers.StopwatchImpl; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Damntry Globals")] [assembly: AssemblyDescription("Utils usable in any type of project")] [assembly: AssemblyCompany("Damntry")] [assembly: AssemblyProduct("Damntry Globals")] [assembly: AssemblyCopyright("Copyright © Damntry 2026")] [assembly: AssemblyFileVersion("0.4.9.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8.1", FrameworkDisplayName = ".NET Framework 4.8.1")] [assembly: AssemblyVersion("0.4.9.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace HighResolutionTimer { public class HighResolutionTimer { public static readonly double TickLength = 1000f / (float)Stopwatch.Frequency; public static readonly double Frequency = Stopwatch.Frequency; public static bool IsHighResolution = Stopwatch.IsHighResolution; private volatile float _interval; private volatile bool _isRunning; private Thread _thread; public float Interval { get { return _interval; } set { if (value < 0f || float.IsNaN(value)) { throw new ArgumentOutOfRangeException("value"); } _interval = value; } } public bool IsRunning => _isRunning; public bool UseHighPriorityThread { get; set; } public event EventHandler Elapsed; public HighResolutionTimer() : this(1f) { } public HighResolutionTimer(float interval) { Interval = interval; } public void Start() { if (!_isRunning) { _isRunning = true; _thread = new Thread(ExecuteTimer) { IsBackground = true }; if (UseHighPriorityThread) { _thread.Priority = ThreadPriority.Highest; } _thread.Start(); } } public void Stop(bool joinThread = true) { _isRunning = false; if (joinThread && Thread.CurrentThread != _thread) { _thread.Join(); } } private void ExecuteTimer() { float num = 0f; Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); while (_isRunning) { num += _interval; double num2; while (true) { num2 = ElapsedHiRes(stopwatch); double num3 = (double)num - num2; if (num3 <= 0.0) { break; } if (num3 < 1.0) { Thread.SpinWait(10); } else if (num3 < 5.0) { Thread.SpinWait(100); } else if (num3 < 15.0) { Thread.Sleep(1); } else { Thread.Sleep(10); } if (!_isRunning) { return; } } double delay = num2 - (double)num; this.Elapsed?.Invoke(this, new HighResolutionTimerElapsedEventArgs(delay)); if (!_isRunning) { return; } if (stopwatch.Elapsed.TotalHours >= 1.0) { stopwatch.Restart(); num = 0f; } } stopwatch.Stop(); } private static double ElapsedHiRes(Stopwatch stopwatch) { return (double)stopwatch.ElapsedTicks * TickLength; } } public class HighResolutionTimerElapsedEventArgs : EventArgs { public double Delay { get; } internal HighResolutionTimerElapsedEventArgs(double delay) { Delay = delay; } } } namespace System.Runtime.CompilerServices { public class IsExternalInit { } } namespace Damntry.Utils { public static class EmbeddedReferenceResolve { public static Assembly LoadEmbeddedResource(byte[] resourceBytes) { if (resourceBytes == null || resourceBytes.Length == 0) { throw new ArgumentException("Argument resourceBytes cant be null or empty"); } Assembly assembly = Assembly.Load(resourceBytes); if (assembly == null) { throw new InvalidOperationException("A dll resource could not be loaded."); } return assembly; } public static void LoadAssemblyFromResources(params string[] resourceNames) { if (resourceNames == null || resourceNames.Length == 0) { throw new ArgumentException("Argument resourceNames cant be null or empty"); } foreach (string text in resourceNames) { if (ResolveFromResourceName(Assembly.GetCallingAssembly(), text) == null) { throw new InvalidOperationException("The dll resource '" + text + "' could not be loaded. Make sure it is added to the resources of the assembly you are calling from."); } } } private static Assembly ResolveFromResourceName(Assembly assembly, string resourceName) { using Stream stream = assembly.GetManifestResourceStream(resourceName); byte[] array = new byte[(int)stream.Length]; stream.Read(array, 0, (int)stream.Length); return Assembly.Load(array); } } } namespace Damntry.Utils.Timers { public class PeriodicTimeLimitedCounter where T : IStopwatch, new() { private IStopwatch swLimitHit; private bool constantPeriodTimer; private int hitCounter; private int hitCounterMax; private double maxPeriodTimeMillis; private bool triggerOncePerPeriod; public PeriodicTimeLimitedCounter(bool constantPeriodTimer, int hitCounterMax, double maxPeriodTimeMillis, bool ignoreFirstHit, bool triggerOncePerPeriod = true) { this.constantPeriodTimer = constantPeriodTimer; this.hitCounterMax = hitCounterMax; this.maxPeriodTimeMillis = maxPeriodTimeMillis; hitCounter = (ignoreFirstHit ? (-1) : 0); this.triggerOncePerPeriod = triggerOncePerPeriod; swLimitHit = new T(); } public bool TryIncreaseCounter() { bool flag = false; bool flag2 = false; hitCounter++; if (swLimitHit.ElapsedMillisecondsPrecise <= maxPeriodTimeMillis) { if (!constantPeriodTimer) { flag = true; } flag2 = (triggerOncePerPeriod ? (hitCounter == hitCounterMax) : (hitCounter >= hitCounterMax)); } else { flag = true; } if (flag) { swLimitHit.Restart(); hitCounter = 0; } if (!swLimitHit.IsRunning) { swLimitHit.Start(); } return !flag2; } public void StartTime() { swLimitHit.Restart(); } public void ResetAll() { hitCounter = 0; swLimitHit.Reset(); } } } namespace Damntry.Utils.Timers.StopwatchImpl { public interface IStopwatch { bool IsRunning { get; } long ElapsedMilliseconds { get; } long ElapsedSeconds { get; } double ElapsedMillisecondsPrecise { get; } double ElapsedSecondsPrecise { get; } void Start(); void Stop(); void Reset(); void Restart(); } public class StopwatchDiag : Stopwatch, IStopwatch { public long ElapsedSeconds => base.ElapsedMilliseconds * 1000; public double ElapsedMillisecondsPrecise => base.Elapsed.TotalMilliseconds; public double ElapsedSecondsPrecise => base.Elapsed.TotalSeconds; public new static StopwatchDiag StartNew() { StopwatchDiag stopwatchDiag = new StopwatchDiag(); stopwatchDiag.Start(); return stopwatchDiag; } bool IStopwatch.get_IsRunning() { return base.IsRunning; } long IStopwatch.get_ElapsedMilliseconds() { return base.ElapsedMilliseconds; } void IStopwatch.Start() { Start(); } void IStopwatch.Stop() { Stop(); } void IStopwatch.Reset() { Reset(); } void IStopwatch.Restart() { Restart(); } } } namespace Damntry.Utils.Tasks { public class CancellableSingleTask where T : AsyncDelayBase { private SemaphoreSlim semaphoreLock; private Task task; private string taskLogName; private CancellationTokenSource cancelTokenSource; private bool isSemaphoreAcquiredManually; private bool logEvents; private int maxExternalSemaphoreAcquireTimeMillis; public bool IsTaskRunning { get { if (task != null) { return task.Status < TaskStatus.RanToCompletion; } return false; } } public bool IsCancellationRequested { get { if (cancelTokenSource != null) { return cancelTokenSource.IsCancellationRequested; } return false; } } public CancellationToken CancellationToken { get { if (cancelTokenSource != null) { return cancelTokenSource.Token; } throw new InvalidOperationException("There is no cancellation token as no task has been started."); } } public CancellableSingleTask(bool logEvents = true) { this.logEvents = logEvents; semaphoreLock = new SemaphoreSlim(1, 1); isSemaphoreAcquiredManually = false; } public async Task StartTaskAsync(Func asyncWorkerFunction, string taskLogName, bool throwExceptionIfRunning) { await StartTaskAsync(asyncWorkerFunction, taskLogName, awaitTask: false, throwExceptionIfRunning, newThread: false); } public async Task StartThreadedTaskAsync(Func asyncWorkerFunction, string taskLogName, bool throwExceptionIfRunning) { await StartTaskAsync(asyncWorkerFunction, taskLogName, awaitTask: false, throwExceptionIfRunning, newThread: true); } public async Task StartThreadedTaskAsync(Action workerFunction, string taskLogName, bool throwExceptionIfRunning) { await StartTaskAsync(delegate { workerFunction(); return Task.CompletedTask; }, taskLogName, awaitTask: false, throwExceptionIfRunning, newThread: true); } public async Task StartAwaitableTaskAsync(Func asyncWorkerFunction, string taskLogName, bool throwExceptionIfRunning) { await StartTaskAsync(asyncWorkerFunction, taskLogName, awaitTask: true, throwExceptionIfRunning, newThread: false); } public async Task StartAwaitableThreadedTaskAsync(Func asyncWorkerFunction, string taskLogName, bool throwExceptionIfRunning) { await StartTaskAsync(asyncWorkerFunction, taskLogName, awaitTask: true, throwExceptionIfRunning, newThread: true); } private async Task StartTaskAsync(Func asyncWorkerFunction, string taskLogName, bool awaitTask, bool throwIfAlreadyRunning, bool newThread) { await GetSemaphoreLock(); try { if (task != null && !task.IsCompleted) { if (throwIfAlreadyRunning) { throw new InvalidOperationException(GetTextAlreadyRunningTask(taskLogName)); } if (logEvents) { TimeLogger.Logger.LogDebugFunc(() => GetTextAlreadyRunningTask(taskLogName), LogCategories.Task); } return; } this.taskLogName = taskLogName; cancelTokenSource = new CancellationTokenSource(); if (logEvents) { TimeLogger.Logger.LogDebugFunc(() => "Task \"" + taskLogName + "\" is now going to run " + (newThread ? "in a new thread" : "asynchronously") + ".", LogCategories.Task); } if (newThread) { task = Task.Run(() => asyncWorkerFunction()); } else { task = asyncWorkerFunction(); } } finally { semaphoreLock.Release(); } if (awaitTask) { await task; } else { task.FireAndForgetCancels(LogCategories.Task); } } private string GetTextAlreadyRunningTask(string taskLogName) { StringBuilder stringBuilder = new StringBuilder(25); if (this.taskLogName != taskLogName) { stringBuilder.Append("Cant start task \""); stringBuilder.Append(taskLogName); stringBuilder.Append("\": "); } stringBuilder.Append("Task \""); stringBuilder.Append(this.taskLogName); stringBuilder.Append("\" is already running."); return stringBuilder.ToString(); } public async Task StopTaskAndWaitAsync() { await StopTaskAndWaitAsync(null, null, -1); } public async Task StopTaskAndWaitAsync(int maxStopTimeMillis) { await StopTaskAndWaitAsync(null, null, maxStopTimeMillis); } public async Task StopTaskAndWaitAsync(Action onTaskStopped, int maxStopTimeMillis) { await StopTaskAndWaitAsync(onTaskStopped, null, maxStopTimeMillis); } public async Task StopTaskAndWaitAsync(Func onTaskStoppedAsync, int maxStopTimeMillis) { await StopTaskAndWaitAsync(null, onTaskStoppedAsync, maxStopTimeMillis); } private async Task StopTaskAndWaitAsync(Action onTaskStopped, Func onTaskStoppedAsync, int maxStopTimeMillis) { await GetSemaphoreLock(); try { Task obj = new Task(async delegate { if (task == null) { if (logEvents) { TimeLogger.Logger.LogDebugFunc(() => "Cant stop task \"" + taskLogName + "\". It was never started, or already stopped.", LogCategories.Task); } } else { if (cancelTokenSource != null && !task.IsTaskEnded()) { if (logEvents) { TimeLogger.Logger.LogDebugFunc(() => "Canceling task \"" + taskLogName + "\"", LogCategories.Task); } cancelTokenSource.Cancel(); } else if (logEvents) { TimeLogger.Logger.LogDebugFunc(() => "Cant stop task \"" + taskLogName + "\". It is already finished.", LogCategories.Task); } try { await task; } catch (Exception ex) { if (!(ex is TaskCanceledException) && !(ex is OperationCanceledException)) { throw; } if (logEvents) { TimeLogger.Logger.LogDebugFunc(() => "Task \"" + taskLogName + "\" successfully canceled.", LogCategories.Task); } } task.Dispose(); task = null; if (onTaskStopped != null) { onTaskStopped(); } if (onTaskStoppedAsync != null) { await onTaskStoppedAsync(); } } }); obj.Start(); await TaskTimeoutMethods.AwaitTaskWithTimeoutAsync(obj, taskLogName, maxStopTimeMillis, throwTimeoutException: true); } finally { semaphoreLock.Release(); } } private async Task GetSemaphoreLock() { int millisecondsTimeout = -1; if (isSemaphoreAcquiredManually) { millisecondsTimeout = maxExternalSemaphoreAcquireTimeMillis; } if (!(await semaphoreLock.WaitAsync(millisecondsTimeout))) { throw new ExternalSemaphoreHeldException("The operation could not complete because the semaphore was held externally for too long."); } } public async Task WaitSemaphoreAsync(int maxSemaphoreAcquireTimeMillis) { await WaitSemaphoreTimeoutAsync(-1, maxSemaphoreAcquireTimeMillis); } public async Task WaitSemaphoreTimeoutAsync(int millisecondTimeout, int maxSemaphoreAcquireTimeMillis) { if (isSemaphoreAcquiredManually) { throw new InvalidOperationException("Semaphore lock has already been manually acquired. You must release it first by calling ReleaseSemaphore()."); } maxExternalSemaphoreAcquireTimeMillis = maxSemaphoreAcquireTimeMillis; isSemaphoreAcquiredManually = await semaphoreLock.WaitAsync(millisecondTimeout); return isSemaphoreAcquiredManually; } public void ReleaseSemaphore() { if (isSemaphoreAcquiredManually) { isSemaphoreAcquiredManually = false; semaphoreLock.Release(); return; } throw new InvalidOperationException("No lock for the semaphore was acquired manually. Call WaitSemaphoreAsync() first."); } } public class ExternalSemaphoreHeldException : Exception { public ExternalSemaphoreHeldException() { } public ExternalSemaphoreHeldException(string message) : base(message) { } public ExternalSemaphoreHeldException(string message, Exception inner) : base(message, inner) { } } public class DelayedSingleTask where T : AsyncDelayBase { private Task delayedTask; private CancellationTokenSource taskCancel; private Action actionTask; public DelayedSingleTask(Action actionTask) { if (actionTask == null) { throw new ArgumentNullException("actionTask"); } this.actionTask = actionTask; taskCancel = new CancellationTokenSource(); } public async void Start(int delayMillis) { if (delayedTask != null && !delayedTask.IsCompleted && !delayedTask.IsCanceled) { taskCancel.Cancel(); try { await delayedTask; } catch (Exception ex) { if (!(ex is TaskCanceledException) && !(ex is OperationCanceledException) && !(ex is ThreadAbortException)) { TimeLogger.Logger.LogExceptionWithMessage("Exception while starting and executing delayed task.", ex, LogCategories.Task); } } taskCancel = new CancellationTokenSource(); } delayedTask = StartDelayedCancellableTask(delayMillis); delayedTask.FireAndForgetCancels(LogCategories.Task, dismissCancelLog: true); } private async Task StartDelayedCancellableTask(int delayMillis) { await AsyncDelayBase.Instance.Delay(delayMillis, taskCancel.Token); if (!taskCancel.IsCancellationRequested) { actionTask(); } } } } namespace Damntry.Utils.Tasks.TaskTimeout { public class TaskTimeout where T : AsyncDelayBase { public static async Task StartAwaitableTaskWithTimeoutAsync(Func asyncWorkerFunction, string taskLogName, int maxCompletionTimeMillis) { await StartTaskStaticAndWaitWithTimeoutAsync(asyncWorkerFunction, taskLogName, newThread: false, maxCompletionTimeMillis); } public static async Task StartAwaitableThreadedTaskWithTimeoutAsync(Func asyncWorkerFunction, string taskLogName, int maxCompletionTimeMillis) { await StartTaskStaticAndWaitWithTimeoutAsync(asyncWorkerFunction, taskLogName, newThread: true, maxCompletionTimeMillis); } private static async Task StartTaskStaticAndWaitWithTimeoutAsync(Func asyncWorkerFunction, string taskLogName, bool newThread, int maxCompletionTimeMillis) { TimeLogger.Logger.LogDebugFunc(() => "Task \"" + taskLogName + "\" is now going to run " + (newThread ? "in a new thread" : "asynchronously") + ".", LogCategories.Task); CancellationTokenSource cancelWorker = new CancellationTokenSource(); Task workTask = ((!newThread) ? asyncWorkerFunction(cancelWorker.Token) : Task.Run(() => asyncWorkerFunction(cancelWorker.Token))); if (!(await TaskTimeoutMethods.AwaitTaskWithTimeoutAsync(workTask, taskLogName, maxCompletionTimeMillis, throwTimeoutException: false))) { cancelWorker.Cancel(); await workTask; throw new TimeoutException("Task \"" + taskLogName + "\" is finished but it took longer than " + maxCompletionTimeMillis + "ms."); } } } internal class TaskTimeoutMethods where T : AsyncDelayBase { internal static async Task AwaitTaskWithTimeoutAsync(Task task, string taskLogName, int maxStopTimeMillis, bool throwTimeoutException) { CancellationTokenSource cancelDelay = new CancellationTokenSource(); try { await AwaitTaskWithTimeoutAsync(task, taskLogName, maxStopTimeMillis, cancelDelay.Token); if (!task.IsCompleted) { if (throwTimeoutException) { throw new TimeoutException($"Task \"{taskLogName}\" took longer than the specified {maxStopTimeMillis} ms to stop."); } return false; } return true; } finally { cancelDelay.Cancel(); } } private static async Task AwaitTaskWithTimeoutAsync(Task task, string taskLogName, int maxStopTimeMillis, CancellationToken cancelToken) { try { await Task.WhenAny(new Task[2] { task, AsyncDelayBase.Instance.Delay(maxStopTimeMillis, cancelToken) }); } catch (Exception ex) { if (ex is TaskCanceledException || ex is OperationCanceledException) { TimeLogger.Logger.LogDebugFunc(() => "Task \"" + taskLogName + "\" successfully canceled.", LogCategories.Task); return; } throw; } } } } namespace Damntry.Utils.Tasks.AsyncDelay { public class AsyncDelay : AsyncDelayBase { public override Task Delay(int millisecondsDelay) { return Task.Delay(millisecondsDelay); } public override Task Delay(int millisecondsDelay, CancellationToken cancellationToken) { return Task.Delay(millisecondsDelay, cancellationToken); } } public abstract class AsyncDelayBase where T : AsyncDelayBase { private static AsyncDelayBase instance = Activator.CreateInstance(); public static AsyncDelayBase Instance => instance; public abstract Task Delay(int millisecondsDelay); public abstract Task Delay(int millisecondsDelay, CancellationToken cancellationToken); } } namespace Damntry.Utils.Reflection { public static class AssemblyUtils { private static Assembly[] assemblyCache; public static MethodInfo GetMethodFromLoadedAssembly(string fullTypeName, string methodName, bool refreshCache = true) { return GetTypeFromLoadedAssemblies(fullTypeName, refreshCache).GetMethod(methodName, ReflectionHelper.AllBindings); } public static Type GetTypeFromLoadedAssemblies(string fullTypeName, bool refreshCache = true) { if (refreshCache || assemblyCache == null) { assemblyCache = AppDomain.CurrentDomain.GetAssemblies(); } return (from a in assemblyCache select a.GetType(fullTypeName, throwOnError: false) into t where t != null select t).FirstOrDefault(); } public static Type[] GetTypesFromLoadedAssemblies(bool refreshCache, params string[] argumentFullTypeNames) { if (argumentFullTypeNames == null) { return null; } bool flag = true; List list = new List(argumentFullTypeNames.Length); foreach (string text in argumentFullTypeNames) { Type typeFromLoadedAssemblies = GetTypeFromLoadedAssemblies(text, flag && refreshCache); if (typeFromLoadedAssemblies == null) { throw new ArgumentException("The type with value \"" + text + "\" couldnt be found in the assembly."); } list.Add(typeFromLoadedAssemblies); flag = false; } return list?.ToArray(); } public static string GetAssemblyDllFilePath(Type assemblyType) { return Assembly.GetAssembly(assemblyType).Location; } public static string GetAssemblyDllFolderPath(Type assemblyType) { return Path.GetDirectoryName(GetAssemblyDllFilePath(assemblyType)); } public static string GetCombinedPathFromAssemblyFolder(Type assemblyType, string addedPath) { string text = GetAssemblyDllFolderPath(assemblyType); char directorySeparatorChar = Path.DirectorySeparatorChar; if (!addedPath.StartsWith(directorySeparatorChar.ToString())) { string text2 = text; directorySeparatorChar = Path.DirectorySeparatorChar; text = text2 + directorySeparatorChar; } return text + addedPath; } } public class MemberInfoHelper : MemberInfo { private readonly MemberInfo member; private FieldInfo Field => (FieldInfo)member; private PropertyInfo Property => (PropertyInfo)member; public bool IsStatic => member.MemberType switch { MemberTypes.Field => Field.IsStatic, MemberTypes.Property => Property.IsStatic(), _ => throw new NotImplementedException(), }; public Type MemberInfoType => member.MemberType switch { MemberTypes.Field => Field.FieldType, MemberTypes.Property => Property.PropertyType, _ => throw new NotImplementedException(), }; public override MemberTypes MemberType => member.MemberType; public override string Name => member.Name; public override Type DeclaringType => member.DeclaringType; public override Type ReflectedType => member.ReflectedType; public MemberInfoHelper(MemberInfo memberInfo) { if (memberInfo == null) { throw new ArgumentNullException("member"); } if (!(memberInfo is FieldInfo) && !(memberInfo is PropertyInfo)) { throw new NotSupportedException("Only FieldInfo and PropertyInfo members are supported."); } member = memberInfo; } public void SetValue(object obj, object value) { if (member.MemberType == MemberTypes.Field) { Field.SetValue(obj, value); } else if (member.MemberType == MemberTypes.Property) { Property.SetValue(obj, value); } } public object GetValue(object obj) { if (member.MemberType == MemberTypes.Field) { return Field.GetValue(obj); } if (member.MemberType == MemberTypes.Property) { return Property.GetValue(obj); } throw new NotImplementedException(); } public object GetValueStaticAgnostic(object obj) { obj = (IsStatic ? null : obj); if (member.MemberType == MemberTypes.Field) { return Field.GetValue(obj); } if (member.MemberType == MemberTypes.Property) { return Property.GetValue(obj); } throw new NotImplementedException(); } public override object[] GetCustomAttributes(bool inherit) { return member.GetCustomAttributes(inherit); } public override object[] GetCustomAttributes(Type attributeType, bool inherit) { return member.GetCustomAttributes(attributeType, inherit); } public override bool IsDefined(Type attributeType, bool inherit) { return member.IsDefined(attributeType, inherit); } } public static class ReflectionHelper { public enum MemberType { Property, Field, Both } public static BindingFlags AllBindings { get; } = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.SetField | BindingFlags.GetProperty | BindingFlags.SetProperty; public static void CallMethod(object classInstance, string methodName, object[] args = null) { CallMethod(classInstance, methodName, args); } public static R CallStaticMethod(Type classType, string methodName, object[] args = null) { return CallMethod(null, classType, methodName, args); } public static R CallMethod(object classInstance, string methodName, object[] args = null) { return CallMethod(classInstance, classInstance.GetType(), methodName, args); } private static R CallMethod(object classInstance, Type classType, string methodName, object[] args = null) { MethodInfo method = classType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); return CallMethod(classInstance, method, args); } public static R CallMethod(object classInstance, MethodInfo methodInfo, object[] args = null) { try { return (R)methodInfo.Invoke(classInstance, args); } catch (TargetParameterCountException e) { TimeLogger.Logger.LogExceptionWithMessage("Parameter count error while calling method " + methodInfo.Name, e, LogCategories.Reflect); throw; } } public static string ListMemberValues(object target, MemberType memberType, bool showPrivate = true) { BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public; if (showPrivate) { bindingFlags |= BindingFlags.NonPublic; } StringBuilder sbMembers = new StringBuilder(); MemberInfo[] array = Array.Empty(); if (memberType == MemberType.Both || memberType == MemberType.Property) { MemberInfo[] properties = target.GetType().GetProperties(bindingFlags); array = properties; } if (memberType == MemberType.Both || memberType == MemberType.Field) { if (array.Length != 0) { array = array.Concat(target.GetType().GetFields(bindingFlags)).ToArray(); } else { MemberInfo[] properties = target.GetType().GetFields(bindingFlags); array = properties; } } Array.ForEach(array, delegate(MemberInfo minfo) { sbMembers.Append(minfo.Name); sbMembers.Append(" = "); if (minfo is PropertyInfo) { sbMembers.Append(((PropertyInfo)minfo).GetValue(target, null)); } else if (minfo is FieldInfo) { sbMembers.Append(((FieldInfo)minfo).GetValue(target)); } sbMembers.AppendLine(); }); return sbMembers.ToString(); } public static string ConvertFullTypeToNormal(string type) { if (string.IsNullOrEmpty(type) || !type.Contains(".")) { return type; } return type.Substring(type.LastIndexOf(".") + 1); } public static string[] ConvertFullTypesToNormal(params string[] types) { string[] result = null; if (types == null || !types.Any()) { result = types.Select((string s) => ConvertFullTypeToNormal(s)).ToArray(); } return result; } public static object GetDefaultValue(this Type type) { return FormatterServices.GetUninitializedObject(type); } } } namespace Damntry.Utils.Numerics { public class RollingIntegerRange { [CompilerGenerated] private int P; [CompilerGenerated] private int P; [CompilerGenerated] private int P; private int currentValue; public RollingIntegerRange(int minValueInclusive, int maxValueInclusive, int step = 1) { P = minValueInclusive; P = maxValueInclusive; P = step; base..ctor(); } public int GetNextValue() { currentValue += P; if (currentValue > P) { currentValue = P; } else if (currentValue < P) { currentValue = P; } return currentValue; } } } namespace Damntry.Utils.Maths { public static class MathMethods { public static ulong GreatestCommonDivisor(ulong n1, ulong n2) { while (n1 != 0L && n2 != 0L) { if (n1 > n2) { n1 %= n2; } else { n2 %= n1; } } return n1 | n2; } public static int CountDigits(uint num) { if ((long)num != 0L) { return (((long)num > 0L) ? 1 : 2) + (int)Math.Log10(Math.Abs((double)num)); } return 1; } public static int CountDigits(int num) { return CountDigits((uint)num); } } } namespace Damntry.Utils.Logging { public class LOG { public static void TEMPDEBUG(string message, bool onlyIfTrue = true) { } public static void TEMPDEBUG_FUNC(Func textLambda, bool onlyIfTrue = true) { } public static void Debug(string message, LogCategories logCategory, bool onlyIfTrue = true) { } public static void Debug_func(Func textLambda, LogCategories logCategory, bool onlyIfTrue = true) { } public static void TEMPWARNING(string message, bool onlyIfTrue = true) { } public static void TEMPWARNING_FUNC(Func textLambda, bool onlyIfTrue = true) { } public static void TEMPFATAL(string message, bool onlyIfTrue = true) { } public static void TEMP(LogTier logLevel, string message, bool onlyIfTrue = true) { } private static void Log(LogTier logLevel, string message, bool onlyIfTrue = true, LogCategories logCategory = LogCategories.TempTest) { } private static void LogFunc(LogTier logLevel, Func textLambda, bool onlyIfTrue = true, LogCategories logCategory = LogCategories.TempTest) { } } public sealed class DefaultTimeLogger : TimeLogger { protected override void InitializeLogger(params object[] args) { } protected override void LogMessage(string message, LogTier logLevel) { ConsoleColor consoleColor = ConsoleColor.Gray; switch (logLevel) { case LogTier.All: consoleColor = ConsoleColor.White; break; case LogTier.Debug: consoleColor = ConsoleColor.DarkGray; break; case LogTier.Info: consoleColor = ConsoleColor.Gray; break; case LogTier.Warning: consoleColor = ConsoleColor.Yellow; break; case LogTier.Error: consoleColor = ConsoleColor.DarkRed; break; case LogTier.Fatal: consoleColor = ConsoleColor.Red; break; case LogTier.Message: consoleColor = ConsoleColor.Gray; break; case LogTier.None: return; default: throw new InvalidOperationException("Invalid log level."); } Console.ForegroundColor = consoleColor; Console.WriteLine(message); Console.ResetColor(); } } public static class Performance { private enum StopResetAction { Stop, Reset, Record, Log } public static class PerformanceFileLoggingMethods { public static async Task BeginOrResumePerfFileLoggingWithTextFunc(Func getFirstPerfLogLineFunc) { if (TimeLogger.DebugEnabled) { string firstPerfLogText = null; if (getFirstPerfLogLineFunc != null) { firstPerfLogText = getFirstPerfLogLineFunc(); } await PerfLogger.DLog.BeginPerfFileLoggingWithText(firstPerfLogText); } } public static async Task BeginOrResumePerfFileLoggingWithText(string firstPerfLogLine) { if (TimeLogger.DebugEnabled) { await PerfLogger.DLog.BeginPerfFileLoggingWithText(firstPerfLogLine); } } public static async Task BeginOrResumePerfFileLogging() { if (TimeLogger.DebugEnabled) { await PerfLogger.DLog.BeginPerfFileLoggingWithText(null); } } public static async Task StopPerfFileLogging() { if (PerfLogger.IsInstanced) { await PerfLogger.DLog.StopPerfFileLogging(); } } } public static class PerformanceTableLoggingMethods { public static async Task StartLogPerformanceTableNewThread(int logTableIntervalMillis) { await logTableTask.StartThreadedTaskAsync(() => LogPerformanceTableInterval(logTableIntervalMillis), "Logger performance table", throwExceptionIfRunning: true); } public static async Task StopThreadLogPerformanceTable() { await logTableTask.StopTaskAndWaitAsync(5000); } public static void LogPerformanceTable() { Performance.LogPerformanceTable(); } } private class StopwatchMeasure : Stopwatch { private string name; private bool showTotals; private double lastRunMilli; private int runCount; private double min; private double max; private TrimmedMean trimmedMean; public double TotalMilli { get; private set; } public StopwatchMeasure(string measureName, bool showTotals) { this.showTotals = showTotals; name = measureName; trimmedMean = new TrimmedMean(); initializeMeasure(); } private void initializeMeasure() { TotalMilli = 0.0; runCount = 0; min = 2147483647.0; max = 0.0; trimmedMean.Initialize(); } [Obsolete("Use Start(resetRunValues) instead.", true)] public new void Start() { } public void Start(bool resetRunValues) { if (resetRunValues) { initializeMeasure(); } base.Start(); } public void RecordRun() { lastRunMilli = base.Elapsed.TotalMilliseconds; if (showTotals) { TotalMilli += lastRunMilli; runCount++; if (lastRunMilli < min) { min = lastRunMilli; } if (lastRunMilli > max) { max = lastRunMilli; } trimmedMean.addNewValue(lastRunMilli); } Reset(); } public string GetLogString() { string text = $"{name} has taken {lastRunMilli:F3} ms"; if (showTotals) { text += $", with a total of {TotalMilli:F3}ms spent in {runCount} run/s. Avg run: {GetAverageRun(formatTiming: false)}ms, Min: {min:F3}ms, Max: {max:F3}ms"; } return text + "."; } public (string name, string total, string runs, string avg, string meanTrim, string min, string max) GetFormattedRunValues(int measureNameMaxLength) { return (name.PadRight(measureNameMaxLength), FormatTiming(TotalMilli), ((base.IsRunning ? "*" : "") + runCount).PadLeft(RunCountPadding), GetAverageRun(formatTiming: true), GetMeanTrimmedRun(), FormatTiming(min), FormatTiming(max)); } private string GetAverageRun(bool formatTiming) { double number = Math.Round(TotalMilli / (double)runCount, MeasureDecimals); if (!formatTiming) { return number.ToString(); } return FormatTiming(number); } private string GetMeanTrimmedRun() { string result = ""; (double, TrimmedMean.CalculationResult) tuple = trimmedMean.GetTrimmedMean(); if (tuple.Item2 == TrimmedMean.CalculationResult.NotEnoughData) { result = "*Collecting*".PadSides(MeasureTimingsPadding); } else if (tuple.Item2 == TrimmedMean.CalculationResult.Partial) { result = FormatTiming(tuple.Item1, "~"); } else if (tuple.Item2 == TrimmedMean.CalculationResult.Full) { result = FormatTiming(tuple.Item1); } return result; } private string FormatTiming(double number, string leftSymbol = null) { string text = string.Format($"{{0:F{MeasureDecimals}}}ms", number); return (((leftSymbol != null) ? leftSymbol : "") + text).PadLeft(MeasureTimingsPadding); } } private class TrimmedMean { public enum CalculationResult { NotEnoughData, Partial, Full } private List historyValues; private int rollingIndex; private readonly int maxNumValues; private readonly int minNumValues; private readonly int trimPercentage; public TrimmedMean(int minValuesHistoric = 5, int maxValuesHistoric = 30, int trimPercentage = 5) { if (minValuesHistoric > maxValuesHistoric) { throw new ArgumentException("minValuesHistoric must be less or equal than maxValuesHistoric."); } minNumValues = minValuesHistoric.ClampReturn(5, int.MaxValue); maxNumValues = maxValuesHistoric.ClampReturn(5, int.MaxValue); this.trimPercentage = trimPercentage.ClampReturn(0, 99); Initialize(); } public void Initialize() { if (historyValues == null) { historyValues = new List(maxNumValues); } else { historyValues.Clear(); } rollingIndex = 0; } public void addNewValue(double value) { if (historyValues.Count > maxNumValues) { throw new InvalidOperationException("The trimmed mean cant contain more values that the specified limit."); } if (historyValues.Count == maxNumValues) { historyValues[rollingIndex] = value; } else { historyValues.Add(value); } rollingIndex++; if (rollingIndex >= maxNumValues) { rollingIndex = 0; } } public (double trimmedMean, CalculationResult calcResult) GetTrimmedMean() { CalculationResult item = CalculationResult.NotEnoughData; if (historyValues.Count < minNumValues) { return (0.0, item); } item = ((historyValues.Count < maxNumValues) ? CalculationResult.Partial : CalculationResult.Full); List list = new List(historyValues); list.Sort(); int num = (int)Math.Round((double)maxNumValues * ((double)trimPercentage / 100.0), 0); double num2 = 0.0; for (int i = num; i < list.Count - num; i++) { num2 += list[i]; } return (num2 / (double)(list.Count - num * 2), item); } } private class PerfLogger { private static readonly Lazy instance = new Lazy(() => new PerfLogger()); private static readonly Lazy pathLogFile = new Lazy(() => GetLogPathFile()); private static readonly string folderPerformanceLogs = "PerformanceLogs"; private static readonly int logIntervalTime = 5000; private static Queue logQueue; private static object queueLock; private static CancellableSingleTask threadedTask; private StringBuilder sbLogText; public static PerfLogger DLog => instance.Value; public static bool IsInstanced => instance != null; private PerfLogger() { logQueue = new Queue(); queueLock = new object(); threadedTask = new CancellableSingleTask(); sbLogText = new StringBuilder(); } public async Task BeginPerfFileLoggingWithText(string firstPerfLogText) { if (firstPerfLogText != null && firstPerfLogText != "") { QueueAtFront(firstPerfLogText); } await threadedTask.StartThreadedTaskAsync((Func)LogConsumer, "Write performance log", throwExceptionIfRunning: true); } public async Task StopPerfFileLogging() { await threadedTask.StopTaskAndWaitAsync(5000); WriteBacklogToDisk(checkCancellation: false); } private void QueueAtFront(string firstPerfLogText) { lock (queueLock) { string[] array = logQueue.ToArray(); logQueue.Clear(); logQueue.Enqueue(firstPerfLogText); string[] array2 = array; foreach (string item in array2) { logQueue.Enqueue(item); } } } public void LogPerformance(string message) { lock (queueLock) { logQueue.Enqueue(message); } } private async Task LogConsumer() { if (TimeLogger.DebugEnabled) { TimeLogger.Logger.LogDebugFunc(() => "Beginning performance logging on file \"" + pathLogFile.Value + "\"", LogCategories.Loading); while (!threadedTask.IsCancellationRequested) { WriteBacklogToDisk(checkCancellation: true); await Task.Delay(logIntervalTime, threadedTask.CancellationToken); } TimeLogger.Logger.LogDebug("Performance logging stopped.", LogCategories.Loading); } } private void WriteBacklogToDisk(bool checkCancellation) { lock (queueLock) { if (logQueue.Count > 0) { do { sbLogText.AppendLine(logQueue.Dequeue()); } while (logQueue.Count > 0); } } if ((!checkCancellation || !threadedTask.IsCancellationRequested) && sbLogText.Length > 0) { LogToDisk(sbLogText.ToString()); sbLogText.Clear(); } } private void LogToDisk(string text) { string directoryName = Path.GetDirectoryName(pathLogFile.Value); if (!Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } using StreamWriter streamWriter = new StreamWriter(pathLogFile.Value, append: true); streamWriter.Write(text); } private static string GetLogPathFile() { return Path.Combine(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), folderPerformanceLogs), "PerformanceLog_" + DateTime.Now.ToString("yyyy.MM.dd-HH.mm.ss") + ".log"); } } private static Dictionary mapMeasures = new Dictionary(); private static int measureNameMaxLength; private static readonly int MeasureDecimals = 3; private static readonly int MeasureTimingsPadding = MeasureDecimals + 9; private static readonly int MeasureTotalTimingPadding = MeasureTimingsPadding + 2; private static readonly int RunCountPadding = 8; private static Stopwatch swPerfTotalRunTime; private static readonly Lazy StaticHeaderText = new Lazy(() => GetStaticHeaderText()); private static readonly Lazy StaticHorizontalSeparatorText = new Lazy(() => GetStaticHorizontalSeparatorText()); private static Action logPerfAction = delegate(string text) { PerfLogger.DLog.LogPerformance(text); }; private static CancellableSingleTask logTableTask = new CancellableSingleTask(); public static void Start(string measureName, bool logTotals = true) { GetCreateMeasure(measureName, logTotals).Start(resetRunValues: false); } public static void StartOver(string measureName, bool logTotals = true) { GetCreateMeasure(measureName, logTotals).Start(resetRunValues: true); } public static void Reset(string measureName, bool doNotWarn = false) { StopOrReset(measureName, StopResetAction.Reset, doNotWarn); } public static void Stop(string measureName, bool doNotWarn = false) { StopOrReset(measureName, StopResetAction.Stop, doNotWarn); } public static void StopAndLog(string measureName, bool doNotWarn = false) { StopOrReset(measureName, StopResetAction.Log, doNotWarn); } public static void StopAndRecord(string measureName, bool doNotWarn = false) { StopOrReset(measureName, StopResetAction.Record, doNotWarn); } private static void StopOrReset(string measureName, StopResetAction stopResetAction, bool doNotWarn) { StopwatchMeasure swMeasure = GetMeasure(measureName, doNotWarn); if (swMeasure == null) { return; } if (stopResetAction == StopResetAction.Reset) { swMeasure.Reset(); return; } swMeasure.Stop(); if (stopResetAction != StopResetAction.Record && stopResetAction != StopResetAction.Log) { return; } swMeasure.RecordRun(); if (stopResetAction == StopResetAction.Log) { LogPerformance(() => swMeasure.GetLogString()); } } private static StopwatchMeasure GetCreateMeasure(string measureName, bool logTotals) { mapMeasures.TryGetValue(measureName, out var value); if (value == null) { value = new StopwatchMeasure(measureName, logTotals); mapMeasures.Add(measureName, value); measureNameMaxLength = Math.Max(measureNameMaxLength, measureName.Length); if (swPerfTotalRunTime == null) { swPerfTotalRunTime = Stopwatch.StartNew(); } } return value; } private static StopwatchMeasure GetMeasure(string measureName, bool doNotWarn) { if (!mapMeasures.TryGetValue(measureName, out var value) && !doNotWarn) { TimeLogger.Logger.LogWarning("The specified stopwatch \"" + measureName + "\" doesnt exist.", LogCategories.PerfTest); } return value; } private static async Task LogPerformanceTableInterval(int logTableIntervalMillis) { while (!logTableTask.IsCancellationRequested) { await Task.Delay(logTableIntervalMillis, logTableTask.CancellationToken); LogPerformanceTable(); } } private static void LogPerformanceTable() { LogPerformance(() => GetAllMeasuresTotalsSorted()); } private static void LogPerformance(Func logTextFunc) { TimeLogger.Logger.LogDebugFunc(logTextFunc, LogCategories.PerfTest, (logPerfAction, true)); } private static string GetAllMeasuresTotalsSorted() { List list = (from x in mapMeasures.Values.ToList() orderby x.TotalMilli descending select x).ToList(); string value = "".PadRight(5, '\t'); string logStringSummaryHorizontalSeparator = GetLogStringSummaryHorizontalSeparator(); string logStringSummaryHeader = GetLogStringSummaryHeader(); StringBuilder stringBuilder = new StringBuilder(100 + 125 * list.Count); stringBuilder.Append("Performance table, sorted by total time. "); if (swPerfTotalRunTime != null) { stringBuilder.AppendLine("The total run time since the Start of the first measure is " + swPerfTotalRunTime.Elapsed.ToString("hh':'mm':'ss'.'fff")); stringBuilder.Append(value); stringBuilder.AppendLine(logStringSummaryHorizontalSeparator); stringBuilder.Append(value); stringBuilder.AppendLine(logStringSummaryHeader); stringBuilder.Append(value); stringBuilder.Append(logStringSummaryHorizontalSeparator); foreach (StopwatchMeasure item in list) { stringBuilder.AppendLine(); stringBuilder.Append(value); stringBuilder.Append(GetLogStringSummaryRunValues(item)); } stringBuilder.AppendLine(); stringBuilder.Append(value); stringBuilder.AppendLine(logStringSummaryHorizontalSeparator); return stringBuilder.ToString(); } stringBuilder.AppendLine("No measures have been created yet."); return stringBuilder.ToString(); } private static string GetLogStringSummaryHeader() { return "| " + "Measure name".PadSides(measureNameMaxLength) + " " + StaticHeaderText.Value; } private static string GetLogStringSummaryHorizontalSeparator() { return "|-" + "".PadSides(measureNameMaxLength, '-') + "-" + StaticHorizontalSeparatorText.Value; } private static string GetLogStringSummaryRunValues(StopwatchMeasure measure) { (string, string, string, string, string, string, string) formattedRunValues = measure.GetFormattedRunValues(measureNameMaxLength); return "| " + formattedRunValues.Item1 + " | " + formattedRunValues.Item2 + " | " + formattedRunValues.Item3 + " | " + formattedRunValues.Item4 + " | " + formattedRunValues.Item5 + " | " + formattedRunValues.Item6 + " | " + formattedRunValues.Item7 + " |"; } private static string GetStaticHeaderText() { return "| " + "Total".PadSides(MeasureTotalTimingPadding) + " | " + "Runs".PadSides(RunCountPadding) + " | " + "Average".PadSides(MeasureTimingsPadding) + " | " + "Mean Trimmed".PadSides(MeasureTimingsPadding) + " | " + "Min".PadSides(MeasureTimingsPadding) + " | " + "Max".PadSides(MeasureTimingsPadding) + " |"; } private static string GetStaticHorizontalSeparatorText() { return "|-" + "".PadSides(MeasureTotalTimingPadding, '-') + "-|-" + "".PadSides(RunCountPadding, '-') + "-|-" + "".PadSides(MeasureTimingsPadding, '-') + "-|-" + "".PadSides(MeasureTimingsPadding, '-') + "-|-" + "".PadSides(MeasureTimingsPadding, '-') + "-|-" + "".PadSides(MeasureTimingsPadding, '-') + "-|"; } } public static class StringExtensions { public static string PadSides(this string str, int totalWidth, char paddingChar = ' ', bool padLeftOnUneven = true) { int num = totalWidth - str.Length; if (num % 2 == 1) { str = (padLeftOnUneven ? str.PadLeft(str.Length + 1, paddingChar) : str.PadRight(str.Length + 1, paddingChar)); num--; } if (num < 1) { return str; } int totalWidth2 = num / 2 + str.Length; return str.PadLeft(totalWidth2, paddingChar).PadRight(totalWidth, paddingChar); } } public enum PreprocessType { FileLogging, GameNotification } [Flags] public enum LogTier { None = 0, Fatal = 1, Error = 2, Warning = 4, Message = 8, Info = 0x10, Debug = 0x20, All = -1 } [Flags] public enum LogCategories { Null = 0, TempTest = 1, Vanilla = 2, PerfTest = 4, Loading = 8, Task = 0x10, Reflect = 0x20, Config = 0x40, PerfCheck = 0x80, Events = 0x100, Network = 0x200, Cache = 0x400, UI = 0x800, Notifs = 0x100000, AutoPatch = 0x200000, MethodChk = 0x400000, OtherMod = 0x800000, JobSched = 0x1000000, KeyMouse = 0x2000000, Highlight = 0x4000000, AI = 0x8000000, Visuals = 0x10000000, Audio = 0x20000000, Other = int.MinValue, All = -1 } public abstract class TimeLogger { public delegate void NotificationAction(string notificationText, LogTier logTier, bool skipQueue); public delegate string PreprocessMessageFunc(string message, LogTier logLevel, LogCategories category, bool showInGameNotification, PreprocessType preprocType); private static TimeLogger instance; private Dictionary logCategoryStringCache; private static string notificationMsgPrefix; private static NotificationAction notificationAction; private static PreprocessMessageFunc globalPreprocessMessageFunc; private int maxCategoryLength = Enum.GetNames(typeof(LogCategories)).Aggregate("", (string max, string cur) => (max.Length <= cur.Length) ? cur : max).Length; private LogCategories AllowedCategories; public static TimeLogger Logger { get { if (instance == null) { InitializeTimeLogger(debugEnabled: false, Array.Empty()); instance.LogWarning("TimeLogger has been automatically initialized with a DefaultTimeLogger. If you want to use a different custom logger, call a InitializeTimeLogger...() method earlier.", LogCategories.Loading); } return instance; } } public static bool DebugEnabled { get; set; } protected TimeLogger() { logCategoryStringCache = new Dictionary(); } protected abstract void LogMessage(string logMessage, LogTier logLevel); protected abstract void InitializeLogger(params object[] argsT); public static void InitializeTimeLogger(bool debugEnabled = false, params object[] argsT) where T : TimeLogger { InitializeTimeLogger(null, debugEnabled, argsT); } public static void InitializeTimeLogger(PreprocessMessageFunc preprocessMessageFunc, bool debugEnabled = false, params object[] argsT) where T : TimeLogger { DebugEnabled = debugEnabled; globalPreprocessMessageFunc = preprocessMessageFunc; instance = Activator.CreateInstance(); instance.InitializeLogger(argsT); } public static void InitializeTimeLoggerWithGameNotifications(NotificationAction notificationAction, string notificationMsgPrefix, bool debugEnabled = false, params object[] argsT) where T : TimeLogger { InitializeTimeLoggerWithGameNotifications(null, notificationAction, notificationMsgPrefix, debugEnabled, argsT); } public static void InitializeTimeLoggerWithGameNotifications(PreprocessMessageFunc preprocessMessageFunc, NotificationAction notificationAction, string notificationMsgPrefix, bool debugEnabled = false, params object[] argsT) where T : TimeLogger { if (notificationAction == null) { throw new ArgumentNullException("notificationAction", "The argument notificationAction cannot be null. Call InitializeTimeLogger(...) instead."); } InitializeTimeLogger(preprocessMessageFunc, debugEnabled, argsT); AddGameNotificationSupport(notificationAction, notificationMsgPrefix); } public static void AddGameNotificationSupport(NotificationAction notificationAction, string notificationMsgPrefix) { if (notificationAction == null) { throw new ArgumentNullException("notificationAction"); } TimeLogger.notificationAction = notificationAction; TimeLogger.notificationMsgPrefix = ((notificationMsgPrefix != null) ? notificationMsgPrefix : ""); } public static void RemoveGameNotificationSupport() { notificationAction = null; notificationMsgPrefix = null; } public void LogInfo(string text, LogCategories category) { LogInternal(LogTier.Info, text, category, showInGameNotification: false, null); } public void LogInfoShowInGame(string text, LogCategories category) { LogInternal(LogTier.Info, text, category, showInGameNotification: true, null); } public void LogMessage(string text, LogCategories category) { LogInternal(LogTier.Message, text, category, showInGameNotification: false, null); } public void LogMessageShowInGame(string text, LogCategories category) { LogInternal(LogTier.Message, text, category, showInGameNotification: true, null); } public void LogDebug(string text, LogCategories category) { LogInternal(LogTier.Debug, text, category, showInGameNotification: false, null); } public void LogDebugShowInGame(string text, LogCategories category) { LogInternal(LogTier.Debug, text, category, showInGameNotification: true, null); } public void LogDebug(string text, LogCategories category, bool showInGameNotification) { LogInternal(LogTier.Debug, text, category, showInGameNotification, null); } public void LogDebugFunc(Func textLambda, LogCategories category) { LogInternalFunc(LogTier.Debug, textLambda, category, false); } public void LogDebugFunc(Func textLambda, LogCategories category, params (Action action, bool useFullLog)[] actionsArgs) { LogInternalFunc(LogTier.Debug, textLambda, category, showInGameNotification: false, actionsArgs); } public void LogDebugFuncShowInGame(Func textLambda, LogCategories category) { LogInternalFunc(LogTier.Debug, textLambda, category, true); } public void LogDebugFunc(Func textLambda, LogCategories category, bool showInGameNotification) { LogInternalFunc(LogTier.Debug, textLambda, category, showInGameNotification); } public void LogDebugFunc(Func textLambda, LogCategories category, bool showInGameNotification, params (Action action, bool useFullLog)[] actionsArgs) { LogInternalFunc(LogTier.Debug, textLambda, category, showInGameNotification, actionsArgs); } public void LogWarning(string text, LogCategories category) { LogInternal(LogTier.Warning, text, category, showInGameNotification: false, null); } public void LogWarningShowInGame(string text, LogCategories category) { LogInternal(LogTier.Warning, text, category, showInGameNotification: true, null); } public void LogException(Exception e, LogCategories category) { LogExceptionInternal(null, e, category, showInGame: false); } public void LogExceptionWithMessage(string text, Exception e, LogCategories category) { LogExceptionInternal(text, e, category, showInGame: false); } public void LogExceptionShowInGame(Exception e, LogCategories category) { LogExceptionInternal(null, e, category, showInGame: true); } public void LogExceptionShowInGameWithMessage(string text, Exception e, LogCategories category) { LogExceptionInternal(text, e, category, showInGame: true); } private void LogExceptionInternal(string text, Exception e, LogCategories category, bool showInGame) { PreprocessMessageFunc preprocessMsgFunc = (string msg, LogTier _, LogCategories _, bool _, PreprocessType prepType) => prepType switch { PreprocessType.FileLogging => FormatException(e, msg), PreprocessType.GameNotification => msg ?? e.Message, _ => null, }; LogInternal(LogTier.Fatal, text, category, preprocessMsgFunc, showInGame, false); } public void LogFatal(string text, LogCategories category) { LogInternal(LogTier.Fatal, text, category, showInGameNotification: false, null); } public void LogFatalShowInGame(string text, LogCategories category) { LogInternal(LogTier.Fatal, text, category, showInGameNotification: true, null); } public void LogError(string text, LogCategories category) { LogInternal(LogTier.Error, text, category, showInGameNotification: false, null); } public void LogErrorShowInGame(string text, LogCategories category) { LogInternal(LogTier.Error, text, category, showInGameNotification: true, null); } public void LogFunc(LogTier logLevel, Func textLambda, LogCategories category, bool showInGameNotification, params (Action action, bool useFullLog)[] actionsArgs) { LogInternalFunc(logLevel, textLambda, category, showInGameNotification, actionsArgs); } private void LogInternalFunc(LogTier logLevel, Func textLambda, LogCategories category, bool showInGameNotification, params (Action action, bool useFullLog)[] actionsArgs) { if (logLevel != LogTier.Debug || DebugEnabled) { LogInternal(logLevel, textLambda(), category, showInGameNotification, actionsArgs); } } public void Log(LogTier logLevel, string text, LogCategories category) { LogInternal(logLevel, text, category, showInGameNotification: false, null); } public void Log(LogTier logLevel, string text, LogCategories category, bool showInGameNotification) { LogInternal(logLevel, text, category, showInGameNotification, null); } public void Log(LogTier logLevel, string text, LogCategories category, PreprocessMessageFunc preprocessMsgFunc, bool showInGameNotification, bool skipQueue = false, params (Action action, bool useFullLog)[] actionsArgs) { if (preprocessMsgFunc == null) { preprocessMsgFunc = globalPreprocessMessageFunc; } LogInternal(logLevel, text, category, preprocessMsgFunc, showInGameNotification, skipQueue, actionsArgs); } private void LogInternal(LogTier logLevel, string originalText, LogCategories category, bool showInGameNotification, params (Action action, bool useFullLog)[] actionsArgs) { LogInternal(logLevel, originalText, category, globalPreprocessMessageFunc, showInGameNotification, skipQueue: false, actionsArgs); } private void LogInternal(LogTier logLevel, string originalText, LogCategories category, PreprocessMessageFunc preprocessMsgFunc, bool showInGameNotification, bool skipQueue = false, params (Action action, bool useFullLog)[] actionsArgs) { if ((logLevel == LogTier.Debug && !DebugEnabled) || (AllowedCategories != 0 && !AllowedCategories.HasFlag(category))) { return; } string text = PreprocessMessage(originalText, logLevel, category, showInGameNotification, preprocessMsgFunc, PreprocessType.FileLogging); string text2 = FormatLogMessageWithTime(text, category); LogMessage(text2, logLevel); if (showInGameNotification) { string message = PreprocessMessage(originalText, logLevel, category, showInGameNotification: true, preprocessMsgFunc, PreprocessType.GameNotification); SendMessageNotification(logLevel, message, skipQueue); } if (actionsArgs != null) { for (int i = 0; i < actionsArgs.Length; i++) { (Action, bool) tuple = actionsArgs[i]; tuple.Item1(tuple.Item2 ? text2 : text); } } } private string PreprocessMessage(string text, LogTier logLevel, LogCategories category, bool showInGameNotification, PreprocessMessageFunc preprocessMsgFunc, PreprocessType prepType) { if (preprocessMsgFunc != null) { return preprocessMsgFunc(text, logLevel, category, showInGameNotification, prepType) ?? text; } return text; } public string GetCategoryStringFromCache(LogCategories category) { if (!logCategoryStringCache.TryGetValue(category, out var value)) { value = category.ToString(); logCategoryStringCache.Add(category, value); } return value; } private string FormatLogMessageWithTime(string text, LogCategories category) { StringBuilder stringBuilder = new StringBuilder(100); string text2 = ((category == LogCategories.Null) ? "" : GetCategoryStringFromCache(category)); stringBuilder.Append("["); stringBuilder.Append(text2.PadRight(maxCategoryLength, ' ')); stringBuilder.Append("] "); stringBuilder.Append(DateTime.Now.ToString("HH:mm:ss.fff")); stringBuilder.Append(" - "); stringBuilder.Append(text); return stringBuilder.ToString(); } public static string FormatException(Exception e, string text = null) { string text2 = ((text != null) ? (text + "\n" + e.Message) : e.Message); text2 += $"(In {e.TargetSite})\n{e.StackTrace}"; if (e.InnerException != null) { text2 = text2 + "\n * InnerException: " + e.InnerException.Message + " " + $"(In {e.InnerException.TargetSite})\n{e.InnerException.StackTrace}"; } return text2; } public void SendMessageNotificationError(string message, bool skipQueue) { SendMessageNotification(LogTier.Error, message, skipQueue); } public void SendMessageNotification(LogTier logLevel, string message, bool skipQueue) { if (logLevel != LogTier.Debug || DebugEnabled) { notificationAction?.Invoke(message, logLevel, skipQueue); } } } } namespace Damntry.Utils.ExtensionMethods { public static class EnumExtension { private static class EnumLambdaCompiled { public static Func EnumToLongFunc = GenerateEnumToLongFunc(); public static Func LongToEnumFunc = GenerateLongToEnumFunc(); private static Func GenerateEnumToLongFunc() { ParameterExpression parameterExpression = Expression.Parameter(typeof(TEnum)); return Expression.Lambda>(Expression.Convert(parameterExpression, typeof(long)), new ParameterExpression[1] { parameterExpression }).Compile(); } private static Func GenerateLongToEnumFunc() { ParameterExpression parameterExpression = Expression.Parameter(typeof(long)); return Expression.Lambda>(Expression.Convert(parameterExpression, typeof(TEnum)), new ParameterExpression[1] { parameterExpression }).Compile(); } } public static string GetDescription(this Enum enumValue) { FieldInfo field = enumValue.GetType().GetField(enumValue.ToString()); if (field == null) { return enumValue.ToString(); } field.GetCustomAttributes(typeof(DescriptionAttribute), inherit: false); if (Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) is DescriptionAttribute descriptionAttribute) { return descriptionAttribute.Description; } return enumValue.ToString(); } public static long ToLong(this TEnum enumValue) where TEnum : struct, Enum { return EnumLambdaCompiled.EnumToLongFunc(enumValue); } public static long EnumToLong(T enumValue) { if (typeof(T).IsEnum) { return EnumLambdaCompiled.EnumToLongFunc(enumValue); } throw new Exception("The type is not an enum."); } public static TEnum LongToEnum(long value) where TEnum : Enum { return EnumLambdaCompiled.LongToEnumFunc(value); } public static T LongToEnumUnconstrained(long value) { if (typeof(T).IsEnum) { return EnumLambdaCompiled.LongToEnumFunc(value); } throw new Exception("The type is not an enum."); } } public static class NumberExtensionMethods { public const double EpsilonTolerance = 1E-09; private static readonly HashSet NumericTypes = new HashSet { typeof(int), typeof(double), typeof(decimal), typeof(long), typeof(short), typeof(sbyte), typeof(byte), typeof(ulong), typeof(ushort), typeof(uint), typeof(float) }; public static bool IsNumeric(this Type myType) { return NumericTypes.Contains(Nullable.GetUnderlyingType(myType) ?? myType); } public static bool IsInteger(this float num) { return (double)Math.Abs(num - (float)(int)num) < 1E-09; } public static bool IsInteger(this double num) { return Math.Abs(num - (double)(long)num) < 1E-09; } public static bool IsInteger(this float num, float epsilonTolerance) { return Math.Abs(num - (float)(int)num) < epsilonTolerance; } public static bool IsInteger(this double num, float epsilonTolerance) { return Math.Abs(num - (double)(long)num) < (double)epsilonTolerance; } public static bool Clamp(this ref float value, float min, float max) { return ClampIComparable(ref value, min, max); } public static bool Clamp(this ref double value, double min, double max) { return ClampIComparable(ref value, min, max); } public static bool Clamp(this ref int value, int min, int max) { return ClampIComparable(ref value, min, max); } public static bool Clamp(this ref byte value, byte min, byte max) { return ClampIComparable(ref value, min, max); } public static float ClampReturn(this float value, float min, float max) { return ClampIComparable(value, min, max); } public static double ClampReturn(this double value, double min, double max) { return ClampIComparable(value, min, max); } public static int ClampReturn(this int value, int min, int max) { return ClampIComparable(value, min, max); } public static byte ClampReturn(this byte value, byte min, byte max) { return ClampIComparable(value, min, max); } private static T ClampIComparable(T value, T min, T max) where T : IComparable { if (min.CompareTo(min) > 0) { throw new ArgumentException("Clamp: The max value is lower than the min value."); } if (value.CompareTo(min) < 0) { return min; } if (value.CompareTo(max) > 0) { return max; } return value; } private static bool ClampIComparable(ref T value, T min, T max) where T : IComparable { if (min.CompareTo(min) > 0) { throw new ArgumentException("Clamp: The max value is lower than the min value."); } if (value.CompareTo(min) < 0) { value = min; return false; } if (value.CompareTo(max) > 0) { value = max; return false; } return true; } } public static class ReflectionExtension { public static bool IsSubclassOfRawGeneric(this Type toCheck, Type baseType) { while (toCheck != typeof(object)) { Type type = (toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck); if (baseType == type) { return true; } toCheck = toCheck.BaseType; } return false; } public static bool HasCustomAttribute(this MemberInfo memberInfo) where T : Attribute { return (T)Attribute.GetCustomAttribute(memberInfo, typeof(T)) != null; } public static bool IsStatic(this PropertyInfo source, bool nonPublic = true) { return source.GetAccessors(nonPublic).Any((MethodInfo x) => x.IsStatic); } } public static class TaskExtensionMethods { public static bool IsTaskEnded(this Task task) { if (!task.IsCompleted) { return task.IsFaulted; } return true; } public static async void FireAndForgetCancels(this Task task, LogCategories category, bool dismissCancelLog = false) { try { if (!task.IsCompleted || task.IsFaulted) { await task.ConfigureAwait(continueOnCapturedContext: false); } } catch (Exception ex) { if (ex is TaskCanceledException || ex is OperationCanceledException || ex is ThreadAbortException) { if (!dismissCancelLog) { TimeLogger.Logger.LogDebug("\"Fire and Forget\" task successfully canceled.", category); } } else { TimeLogger.Logger.LogExceptionWithMessage("Error while awaiting \"Fire and Forget\" type of task:", ex, category); } } } public static async void FireAndForget(this Task task, LogCategories category) { try { if (!task.IsCompleted || task.IsFaulted) { await task.ConfigureAwait(continueOnCapturedContext: false); } } catch (Exception e) { TimeLogger.Logger.LogExceptionWithMessage("Error while awaiting \"Fire and Forget\" type of task:", e, category); } } } } namespace Damntry.Utils.Events { public static class EventMethods { public record struct EventData(Type ClassType, object Instance, string FieldName); public static void ExecuteBypassingEvent(Action eventTriggerAction, params EventData[] eventsData) { if (eventTriggerAction == null) { throw new ArgumentNullException("eventTriggerAction"); } if (eventsData == null) { throw new ArgumentNullException("eventsData"); } List<(FieldInfo, object, object)> list = new List<(FieldInfo, object, object)>(); for (int i = 0; i < eventsData.Length; i++) { EventData eventData = eventsData[i]; FieldInfo field = eventData.ClassType.GetField(eventData.FieldName, ReflectionHelper.AllBindings); if (field == null) { throw new MemberAccessException("Field '" + eventData.FieldName + "' " + $"of type '{eventData.ClassType} couldnt be found.'"); } object obj = (field.IsStatic ? null : eventData.Instance); object value = field.GetValue(obj); list.Add((field, obj, value)); } if (list.Count == 0) { eventTriggerAction(); return; } foreach (var item in list) { item.Item1.SetValue(item.Item2, null); } try { eventTriggerAction(); } finally { foreach (var item2 in list) { item2.Item1.SetValue(item2.Item2, item2.Item3); } } } public static bool TryTriggerEvents(Action ev) { bool result = true; if (ev != null) { Delegate[] invocationList = ev.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action action = (Action)invocationList[i]; try { action(); } catch (Exception e) { result = false; TimeLogger.Logger.LogExceptionWithMessage("Exception while invoking method \"" + action.Method.Name + "\" " + $"from event {ev.GetType()}", e, LogCategories.Events); } } } return result; } public static bool TryTriggerEvents(Action ev, T arg1) { bool result = true; if (ev != null) { Delegate[] invocationList = ev.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action action = (Action)invocationList[i]; try { action(arg1); } catch (Exception e) { result = false; TimeLogger.Logger.LogExceptionWithMessage("Exception while invoking method \"" + action.Method.Name + "\" " + $"from event {ev.GetType()}", e, LogCategories.Events); } } } return result; } public static bool TryTriggerEvents(Action ev, T1 arg1, T2 arg2) { bool result = true; if (ev != null) { Delegate[] invocationList = ev.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action action = (Action)invocationList[i]; try { action(arg1, arg2); } catch (Exception e) { result = false; TimeLogger.Logger.LogExceptionWithMessage("Exception while invoking method \"" + action.Method.Name + "\" " + $"from event {ev.GetType()}", e, LogCategories.Events); } } } return result; } public static bool TryTriggerEvents(Action ev, T1 arg1, T2 arg2, T3 arg3) { bool result = true; if (ev != null) { Delegate[] invocationList = ev.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action action = (Action)invocationList[i]; try { action(arg1, arg2, arg3); } catch (Exception e) { result = false; TimeLogger.Logger.LogExceptionWithMessage("Exception while invoking method \"" + action.Method.Name + "\" " + $"from event {ev.GetType()}", e, LogCategories.Events); } } } return result; } public static bool TryTriggerEvents(Action ev, T1 arg1, T2 arg2, T3 arg3, T4 arg4) { bool result = true; if (ev != null) { Delegate[] invocationList = ev.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action action = (Action)invocationList[i]; try { action(arg1, arg2, arg3, arg4); } catch (Exception e) { result = false; TimeLogger.Logger.LogExceptionWithMessage("Exception while invoking method \"" + action.Method.Name + "\" " + $"from event {ev.GetType()}", e, LogCategories.Events); } } } return result; } public static bool TryTriggerEvents(Action ev, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { bool result = true; if (ev != null) { Delegate[] invocationList = ev.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action action = (Action)invocationList[i]; try { action(arg1, arg2, arg3, arg4, arg5); } catch (Exception e) { result = false; TimeLogger.Logger.LogExceptionWithMessage("Exception while invoking method \"" + action.Method.Name + "\" " + $"from event {ev.GetType()}", e, LogCategories.Events); } } } return result; } public static bool TryTriggerEvents(Action ev, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) { bool result = true; if (ev != null) { Delegate[] invocationList = ev.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action action = (Action)invocationList[i]; try { action(arg1, arg2, arg3, arg4, arg5, arg6); } catch (Exception e) { result = false; TimeLogger.Logger.LogExceptionWithMessage("Exception while invoking method \"" + action.Method.Name + "\" " + $"from event {ev.GetType()}", e, LogCategories.Events); } } } return result; } [Obsolete("Barely faster than TryTriggerEventsDelegate. Use TryTriggerEvents instead.")] private static bool TryTriggerEventsLambda(Action ev, T arg1) { return TryTriggerEventsLambda(ev, delegate(Delegate del) { ((Action)del)(arg1); }); } private static bool TryTriggerEventsLambda(Delegate ev, Action action) { bool result = true; if ((object)ev != null) { Delegate[] invocationList = ev.GetInvocationList(); foreach (Delegate @delegate in invocationList) { try { action(@delegate); } catch (Exception e) { result = false; TimeLogger.Logger.LogExceptionWithMessage("Exception while invoking method \"" + @delegate.Method.Name + "\" " + $"from event {ev.GetType()}", e, LogCategories.Events); } } } return result; } [Obsolete("Too slow.")] private static bool TryTriggerEventsDelegate(Delegate ev, params object[] args) { bool result = true; if ((object)ev != null) { Delegate[] invocationList = ev.GetInvocationList(); foreach (Delegate @delegate in invocationList) { try { @delegate?.Method.Invoke(@delegate.Target, args); } catch (Exception e) { result = false; TimeLogger.Logger.LogExceptionWithMessage("Exception while invoking method \"" + @delegate.Method.Name + "\" " + $"from event {ev.GetType()}", e, LogCategories.Events); } } } return result; } } } namespace Damntry.Utils.Collections { public class TreeNode { private readonly T _value; private readonly List> _children = new List>(); public TreeNode this[int i] => _children[i]; public TreeNode Parent { get; private set; } public T Value => _value; public ReadOnlyCollection> Children => _children.AsReadOnly(); public TreeNode(T value) { _value = value; } private TreeNode(T value, TreeNode parent) { _value = value; Parent = parent; } public TreeNode AddChild(T value) { TreeNode treeNode = new TreeNode(value, this); _children.Add(treeNode); return treeNode; } public TreeNode[] AddChildren(params T[] values) { return values.Select(AddChild).ToArray(); } public bool RemoveChild(TreeNode node) { return _children.Remove(node); } public void TransverseAndDo(Action action) { action(Value); foreach (TreeNode child in _children) { child.TransverseAndDo(action); } } public IEnumerable Flatten() { return new T[1] { Value }.Concat(_children.SelectMany((TreeNode x) => x.Flatten())); } } } namespace Damntry.Utils.Collections.Queues { public class CommonConcurrentQueue : ConcurrentQueue, ICommonQueue { public bool TryEnqueue(T item) { Enqueue(item); return true; } public new bool TryDequeue(out T item) { return base.TryDequeue(out item); } } public class CommonQueue : Queue, ICommonQueue { public bool TryEnqueue(T item) { Enqueue(item); return true; } public new bool TryDequeue(out T item) { item = Dequeue(); return true; } } public class ConcurrentFixedCapacityQueue : ConcurrentQueue, ICommonQueue { private readonly int maxCapacity; private readonly bool keepOld; private readonly object queueLock; public ConcurrentFixedCapacityQueue(int maxCapacity, bool keepOld) { if (maxCapacity <= 0) { throw new ArgumentOutOfRangeException("Only values over zero are allowed."); } this.maxCapacity = maxCapacity; this.keepOld = keepOld; queueLock = new object(); } [Obsolete("Use \"Enqueue(T item, out T dequeuedItem)\" instead.")] public new T Enqueue(T item) { throw new NotImplementedException("Use \"Enqueue(T item, out T dequeuedItem)\" instead."); } public bool TryEnqueue(T item) { T dequeuedItem; return TryEnqueue(item, out dequeuedItem); } public bool TryEnqueue(T item, out T dequeuedItem) { dequeuedItem = default(T); lock (queueLock) { if (base.Count > maxCapacity) { throw new InvalidOperationException("This LimitedQueue contains more items than allowed. Avoid casting this FixedCapacityQueue instance into a Queue to call the base Enqueue(T) method."); } if (!keepOld && base.Count == maxCapacity) { base.TryDequeue(out dequeuedItem); } if (base.Count >= maxCapacity) { return false; } base.Enqueue(item); } return true; } public new bool TryDequeue(out T item) { lock (queueLock) { return base.TryDequeue(out item); } } } public class FixedCapacityQueue : Queue, ICommonQueue { private readonly int maxCapacity; private readonly bool keepOld; public FixedCapacityQueue(int maxCapacity, bool keepOld) : base(maxCapacity) { if (maxCapacity <= 0) { throw new ArgumentOutOfRangeException("Only values over zero are allowed."); } this.maxCapacity = maxCapacity; this.keepOld = keepOld; } [Obsolete("Use \"Enqueue(T item, out T dequeuedItem)\" instead.")] public new T Enqueue(T item) { throw new NotImplementedException("Use \"Enqueue(T item, out T dequeuedItem)\" instead."); } public bool TryEnqueue(T item) { T dequeuedItem; return TryEnqueue(item, out dequeuedItem); } public bool TryEnqueue(T item, out T dequeuedItem) { dequeuedItem = default(T); if (base.Count > maxCapacity) { throw new InvalidOperationException("This LimitedQueue contains more items than allowed. Avoid casting this FixedCapacityQueue instance into a Queue to call the base Enqueue(T) method."); } if (!keepOld && base.Count == maxCapacity) { dequeuedItem = Dequeue(); } if (base.Count >= maxCapacity) { return false; } base.Enqueue(item); return true; } public new bool TryDequeue(out T item) { item = Dequeue(); return true; } } public class FixedCapacityUniqueQueue : FixedCapacityQueue { public FixedCapacityUniqueQueue(int maxCapacity, bool keepOld) : base(maxCapacity, keepOld) { } [Obsolete("Use \"Enqueue(T item, out T dequeuedItem)\" instead.")] public new T Enqueue(T item) { throw new NotImplementedException("Use \"Enqueue(T item, out T dequeuedItem)\" instead."); } public new bool TryEnqueue(T item, out T dequeuedItem) { dequeuedItem = default(T); if (base.Count > 0 && Contains(item)) { return false; } return base.TryEnqueue(item, out dequeuedItem); } } } namespace Damntry.Utils.Collections.Queues.Interfaces { public interface ICommonQueue { int Count { get; } bool TryEnqueue(T item); bool TryDequeue(out T item); } }