mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-02-17 13:27:43 +01:00
Merge remote-tracking branch 'origin/master' into net8-rollup
This commit is contained in:
commit
b68da56e74
99 changed files with 6308 additions and 1218 deletions
|
|
@ -97,4 +97,76 @@ internal static class ArrayExtensions
|
|||
/// <returns><paramref name="array"/> casted as a <see cref="IReadOnlyCollection{T}"/> if it is one; otherwise the result of <see cref="Enumerable.ToArray{TSource}"/>.</returns>
|
||||
public static IReadOnlyCollection<T> AsReadOnlyCollection<T>(this IEnumerable<T> array) =>
|
||||
array as IReadOnlyCollection<T> ?? array.ToArray();
|
||||
|
||||
/// <inheritdoc cref="List{T}.FindIndex(System.Predicate{T})"/>
|
||||
public static int FindIndex<T>(this IReadOnlyList<T> list, Predicate<T> match)
|
||||
=> list.FindIndex(0, list.Count, match);
|
||||
|
||||
/// <inheritdoc cref="List{T}.FindIndex(int,System.Predicate{T})"/>
|
||||
public static int FindIndex<T>(this IReadOnlyList<T> list, int startIndex, Predicate<T> match)
|
||||
=> list.FindIndex(startIndex, list.Count - startIndex, match);
|
||||
|
||||
/// <inheritdoc cref="List{T}.FindIndex(int,int,System.Predicate{T})"/>
|
||||
public static int FindIndex<T>(this IReadOnlyList<T> list, int startIndex, int count, Predicate<T> match)
|
||||
{
|
||||
if ((uint)startIndex > (uint)list.Count)
|
||||
throw new ArgumentOutOfRangeException(nameof(startIndex), startIndex, null);
|
||||
|
||||
if (count < 0 || startIndex > list.Count - count)
|
||||
throw new ArgumentOutOfRangeException(nameof(count), count, null);
|
||||
|
||||
if (match == null)
|
||||
throw new ArgumentNullException(nameof(match));
|
||||
|
||||
var endIndex = startIndex + count;
|
||||
for (var i = startIndex; i < endIndex; i++)
|
||||
{
|
||||
if (match(list[i])) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="List{T}.FindLastIndex(System.Predicate{T})"/>
|
||||
public static int FindLastIndex<T>(this IReadOnlyList<T> list, Predicate<T> match)
|
||||
=> list.FindLastIndex(list.Count - 1, list.Count, match);
|
||||
|
||||
/// <inheritdoc cref="List{T}.FindLastIndex(int,System.Predicate{T})"/>
|
||||
public static int FindLastIndex<T>(this IReadOnlyList<T> list, int startIndex, Predicate<T> match)
|
||||
=> list.FindLastIndex(startIndex, startIndex + 1, match);
|
||||
|
||||
/// <inheritdoc cref="List{T}.FindLastIndex(int,int,System.Predicate{T})"/>
|
||||
public static int FindLastIndex<T>(this IReadOnlyList<T> list, int startIndex, int count, Predicate<T> match)
|
||||
{
|
||||
if (match == null)
|
||||
throw new ArgumentNullException(nameof(match));
|
||||
|
||||
if (list.Count == 0)
|
||||
{
|
||||
// Special case for 0 length List
|
||||
if (startIndex != -1)
|
||||
throw new ArgumentOutOfRangeException(nameof(startIndex), startIndex, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make sure we're not out of range
|
||||
if ((uint)startIndex >= (uint)list.Count)
|
||||
throw new ArgumentOutOfRangeException(nameof(startIndex), startIndex, null);
|
||||
}
|
||||
|
||||
// 2nd have of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0.
|
||||
if (count < 0 || startIndex - count + 1 < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(count), count, null);
|
||||
|
||||
var endIndex = startIndex - count;
|
||||
for (var i = startIndex; i > endIndex; i--)
|
||||
{
|
||||
if (match(list[i]))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,21 +39,23 @@ public static class DisposeSafety
|
|||
public static IDisposable ToDisposableIgnoreExceptions<T>(this Task<T> task)
|
||||
where T : IDisposable
|
||||
{
|
||||
return Disposable.Create(() => task.ContinueWith(r =>
|
||||
{
|
||||
_ = r.Exception;
|
||||
if (r.IsCompleted)
|
||||
{
|
||||
try
|
||||
return Disposable.Create(
|
||||
() => task.ContinueWith(
|
||||
r =>
|
||||
{
|
||||
r.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}));
|
||||
_ = r.Exception;
|
||||
if (r.IsCompleted)
|
||||
{
|
||||
try
|
||||
{
|
||||
r.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -102,25 +104,26 @@ public static class DisposeSafety
|
|||
if (disposables is not T[] array)
|
||||
array = disposables?.ToArray() ?? Array.Empty<T>();
|
||||
|
||||
return Disposable.Create(() =>
|
||||
{
|
||||
List<Exception?> exceptions = null;
|
||||
foreach (var d in array)
|
||||
return Disposable.Create(
|
||||
() =>
|
||||
{
|
||||
try
|
||||
List<Exception?> exceptions = null;
|
||||
foreach (var d in array)
|
||||
{
|
||||
d?.Dispose();
|
||||
try
|
||||
{
|
||||
d?.Dispose();
|
||||
}
|
||||
catch (Exception de)
|
||||
{
|
||||
exceptions ??= new();
|
||||
exceptions.Add(de);
|
||||
}
|
||||
}
|
||||
catch (Exception de)
|
||||
{
|
||||
exceptions ??= new();
|
||||
exceptions.Add(de);
|
||||
}
|
||||
}
|
||||
|
||||
if (exceptions is not null)
|
||||
throw new AggregateException(exceptions);
|
||||
});
|
||||
if (exceptions is not null)
|
||||
throw new AggregateException(exceptions);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -137,7 +140,11 @@ public static class DisposeSafety
|
|||
public event Action<IDisposeCallback, Exception?>? AfterDispose;
|
||||
|
||||
/// <inheritdoc cref="Stack{T}.EnsureCapacity"/>
|
||||
public void EnsureCapacity(int capacity) => this.objects.EnsureCapacity(capacity);
|
||||
public void EnsureCapacity(int capacity)
|
||||
{
|
||||
lock (this.objects)
|
||||
this.objects.EnsureCapacity(capacity);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Stack{T}.Push"/>
|
||||
/// <returns>The parameter.</returns>
|
||||
|
|
@ -145,7 +152,10 @@ public static class DisposeSafety
|
|||
public T? Add<T>(T? d) where T : IDisposable
|
||||
{
|
||||
if (d is not null)
|
||||
this.objects.Add(this.CheckAdd(d));
|
||||
{
|
||||
lock (this.objects)
|
||||
this.objects.Add(this.CheckAdd(d));
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
|
@ -155,7 +165,10 @@ public static class DisposeSafety
|
|||
public Action? Add(Action? d)
|
||||
{
|
||||
if (d is not null)
|
||||
this.objects.Add(this.CheckAdd(d));
|
||||
{
|
||||
lock (this.objects)
|
||||
this.objects.Add(this.CheckAdd(d));
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
|
@ -165,7 +178,10 @@ public static class DisposeSafety
|
|||
public Func<Task>? Add(Func<Task>? d)
|
||||
{
|
||||
if (d is not null)
|
||||
this.objects.Add(this.CheckAdd(d));
|
||||
{
|
||||
lock (this.objects)
|
||||
this.objects.Add(this.CheckAdd(d));
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
|
@ -174,7 +190,10 @@ public static class DisposeSafety
|
|||
public GCHandle Add(GCHandle d)
|
||||
{
|
||||
if (d != default)
|
||||
this.objects.Add(this.CheckAdd(d));
|
||||
{
|
||||
lock (this.objects)
|
||||
this.objects.Add(this.CheckAdd(d));
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
|
@ -183,29 +202,41 @@ public static class DisposeSafety
|
|||
/// Queue all the given <see cref="IDisposable"/> to be disposed later.
|
||||
/// </summary>
|
||||
/// <param name="ds">Disposables.</param>
|
||||
public void AddRange(IEnumerable<IDisposable?> ds) =>
|
||||
this.objects.AddRange(ds.Where(d => d is not null).Select(d => (object)this.CheckAdd(d)));
|
||||
public void AddRange(IEnumerable<IDisposable?> ds)
|
||||
{
|
||||
lock (this.objects)
|
||||
this.objects.AddRange(ds.Where(d => d is not null).Select(d => (object)this.CheckAdd(d)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue all the given <see cref="IDisposable"/> to be run later.
|
||||
/// </summary>
|
||||
/// <param name="ds">Actions.</param>
|
||||
public void AddRange(IEnumerable<Action?> ds) =>
|
||||
this.objects.AddRange(ds.Where(d => d is not null).Select(d => (object)this.CheckAdd(d)));
|
||||
public void AddRange(IEnumerable<Action?> ds)
|
||||
{
|
||||
lock (this.objects)
|
||||
this.objects.AddRange(ds.Where(d => d is not null).Select(d => (object)this.CheckAdd(d)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue all the given <see cref="Func{T}"/> returning <see cref="Task"/> to be run later.
|
||||
/// </summary>
|
||||
/// <param name="ds">Func{Task}s.</param>
|
||||
public void AddRange(IEnumerable<Func<Task>?> ds) =>
|
||||
this.objects.AddRange(ds.Where(d => d is not null).Select(d => (object)this.CheckAdd(d)));
|
||||
public void AddRange(IEnumerable<Func<Task>?> ds)
|
||||
{
|
||||
lock (this.objects)
|
||||
this.objects.AddRange(ds.Where(d => d is not null).Select(d => (object)this.CheckAdd(d)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue all the given <see cref="GCHandle"/> to be disposed later.
|
||||
/// </summary>
|
||||
/// <param name="ds">GCHandles.</param>
|
||||
public void AddRange(IEnumerable<GCHandle> ds) =>
|
||||
this.objects.AddRange(ds.Select(d => (object)this.CheckAdd(d)));
|
||||
public void AddRange(IEnumerable<GCHandle> ds)
|
||||
{
|
||||
lock (this.objects)
|
||||
this.objects.AddRange(ds.Select(d => (object)this.CheckAdd(d)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel all pending disposals.
|
||||
|
|
@ -213,9 +244,12 @@ public static class DisposeSafety
|
|||
/// <remarks>Use this after successful initialization of multiple disposables.</remarks>
|
||||
public void Cancel()
|
||||
{
|
||||
foreach (var o in this.objects)
|
||||
this.CheckRemove(o);
|
||||
this.objects.Clear();
|
||||
lock (this.objects)
|
||||
{
|
||||
foreach (var o in this.objects)
|
||||
this.CheckRemove(o);
|
||||
this.objects.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Stack{T}.EnsureCapacity"/>
|
||||
|
|
@ -264,11 +298,17 @@ public static class DisposeSafety
|
|||
this.BeforeDispose?.InvokeSafely(this);
|
||||
|
||||
List<Exception>? exceptions = null;
|
||||
while (this.objects.Any())
|
||||
while (true)
|
||||
{
|
||||
var obj = this.objects[^1];
|
||||
this.objects.RemoveAt(this.objects.Count - 1);
|
||||
|
||||
object obj;
|
||||
lock (this.objects)
|
||||
{
|
||||
if (this.objects.Count == 0)
|
||||
break;
|
||||
obj = this.objects[^1];
|
||||
this.objects.RemoveAt(this.objects.Count - 1);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
switch (obj)
|
||||
|
|
@ -294,7 +334,8 @@ public static class DisposeSafety
|
|||
}
|
||||
}
|
||||
|
||||
this.objects.TrimExcess();
|
||||
lock (this.objects)
|
||||
this.objects.TrimExcess();
|
||||
|
||||
if (exceptions is not null)
|
||||
{
|
||||
|
|
@ -318,10 +359,16 @@ public static class DisposeSafety
|
|||
this.BeforeDispose?.InvokeSafely(this);
|
||||
|
||||
List<Exception>? exceptions = null;
|
||||
while (this.objects.Any())
|
||||
while (true)
|
||||
{
|
||||
var obj = this.objects[^1];
|
||||
this.objects.RemoveAt(this.objects.Count - 1);
|
||||
object obj;
|
||||
lock (this.objects)
|
||||
{
|
||||
if (this.objects.Count == 0)
|
||||
break;
|
||||
obj = this.objects[^1];
|
||||
this.objects.RemoveAt(this.objects.Count - 1);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -351,7 +398,8 @@ public static class DisposeSafety
|
|||
}
|
||||
}
|
||||
|
||||
this.objects.TrimExcess();
|
||||
lock (this.objects)
|
||||
this.objects.TrimExcess();
|
||||
|
||||
if (exceptions is not null)
|
||||
{
|
||||
|
|
@ -386,7 +434,8 @@ public static class DisposeSafety
|
|||
private void OnItemDisposed(IDisposeCallback obj)
|
||||
{
|
||||
obj.BeforeDispose -= this.OnItemDisposed;
|
||||
this.objects.Remove(obj);
|
||||
lock (this.objects)
|
||||
this.objects.Remove(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Linq;
|
||||
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.Gui.ContextMenu;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Serilog;
|
||||
|
||||
|
|
@ -99,6 +100,23 @@ internal static class EventHandlerExtensions
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replacement for Invoke() on OnMenuOpenedDelegate to catch exceptions that stop event propagation in case
|
||||
/// of a thrown Exception inside of an invocation.
|
||||
/// </summary>
|
||||
/// <param name="openedDelegate">The OnMenuOpenedDelegate in question.</param>
|
||||
/// <param name="argument">Templated argument for Action.</param>
|
||||
public static void InvokeSafely(this IContextMenu.OnMenuOpenedDelegate? openedDelegate, MenuOpenedArgs argument)
|
||||
{
|
||||
if (openedDelegate == null)
|
||||
return;
|
||||
|
||||
foreach (var action in openedDelegate.GetInvocationList().Cast<IContextMenu.OnMenuOpenedDelegate>())
|
||||
{
|
||||
HandleInvoke(() => action(argument));
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleInvoke(Action act)
|
||||
{
|
||||
try
|
||||
|
|
|
|||
12
Dalamud/Utility/IDeferredDisposable.cs
Normal file
12
Dalamud/Utility/IDeferredDisposable.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
namespace Dalamud.Utility;
|
||||
|
||||
/// <summary>
|
||||
/// An extension of <see cref="IDisposable"/> which makes <see cref="IDisposable.Dispose"/> queue
|
||||
/// <see cref="RealDispose"/> to be called at a later time.
|
||||
/// </summary>
|
||||
internal interface IDeferredDisposable : IDisposable
|
||||
{
|
||||
/// <summary>Actually dispose the object.</summary>
|
||||
/// <remarks>Not to be called from the code that uses the end object.</remarks>
|
||||
void RealDispose();
|
||||
}
|
||||
234
Dalamud/Utility/RollingList.cs
Normal file
234
Dalamud/Utility/RollingList.cs
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Dalamud.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// A list with limited capacity holding items of type <typeparamref name="T"/>.
|
||||
/// Adding further items will result in the list rolling over.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Item type.</typeparam>
|
||||
/// <remarks>
|
||||
/// Implemented as a circular list using a <see cref="List{T}"/> internally.
|
||||
/// Insertions and Removals are not supported.
|
||||
/// Not thread-safe.
|
||||
/// </remarks>
|
||||
internal class RollingList<T> : IList<T>
|
||||
{
|
||||
private List<T> items;
|
||||
private int size;
|
||||
private int firstIndex;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="RollingList{T}"/> class.</summary>
|
||||
/// <param name="size"><see cref="RollingList{T}"/> size.</param>
|
||||
/// <param name="capacity">Internal <see cref="List{T}"/> initial capacity.</param>
|
||||
public RollingList(int size, int capacity)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionIfLessThan(nameof(size), size, 0);
|
||||
capacity = Math.Min(capacity, size);
|
||||
this.size = size;
|
||||
this.items = new List<T>(capacity);
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="RollingList{T}"/> class.</summary>
|
||||
/// <param name="size"><see cref="RollingList{T}"/> size.</param>
|
||||
public RollingList(int size)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionIfLessThan(nameof(size), size, 0);
|
||||
this.size = size;
|
||||
this.items = new();
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="RollingList{T}"/> class.</summary>
|
||||
/// <param name="items">Collection where elements are copied from.</param>
|
||||
/// <param name="size"><see cref="RollingList{T}"/> size.</param>
|
||||
public RollingList(IEnumerable<T> items, int size)
|
||||
{
|
||||
if (!items.TryGetNonEnumeratedCount(out var capacity)) capacity = 4;
|
||||
capacity = Math.Min(capacity, size);
|
||||
this.size = size;
|
||||
this.items = new List<T>(capacity);
|
||||
this.AddRange(items);
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="RollingList{T}"/> class.</summary>
|
||||
/// <param name="items">Collection where elements are copied from.</param>
|
||||
/// <param name="size"><see cref="RollingList{T}"/> size.</param>
|
||||
/// <param name="capacity">Internal <see cref="List{T}"/> initial capacity.</param>
|
||||
public RollingList(IEnumerable<T> items, int size, int capacity)
|
||||
{
|
||||
if (items.TryGetNonEnumeratedCount(out var count) && count > capacity) capacity = count;
|
||||
capacity = Math.Min(capacity, size);
|
||||
this.size = size;
|
||||
this.items = new List<T>(capacity);
|
||||
this.AddRange(items);
|
||||
}
|
||||
|
||||
/// <summary>Gets item count.</summary>
|
||||
public int Count => this.items.Count;
|
||||
|
||||
/// <summary>Gets or sets the internal list capacity.</summary>
|
||||
public int Capacity
|
||||
{
|
||||
get => this.items.Capacity;
|
||||
set => this.items.Capacity = Math.Min(value, this.size);
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets rolling list size.</summary>
|
||||
public int Size
|
||||
{
|
||||
get => this.size;
|
||||
set
|
||||
{
|
||||
if (value == this.size) return;
|
||||
if (value > this.size)
|
||||
{
|
||||
if (this.firstIndex > 0)
|
||||
{
|
||||
this.items = new List<T>(this);
|
||||
this.firstIndex = 0;
|
||||
}
|
||||
}
|
||||
else // value < this._size
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionIfLessThan(nameof(value), value, 0);
|
||||
if (value < this.Count)
|
||||
{
|
||||
this.items = new List<T>(this.TakeLast(value));
|
||||
this.firstIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
this.size = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets a value indicating whether the item is read only.</summary>
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
/// <summary>Gets or sets an item by index.</summary>
|
||||
/// <param name="index">Item index.</param>
|
||||
/// <returns>Item at specified index.</returns>
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionIfGreaterThanOrEqual(nameof(index), index, this.Count);
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionIfLessThan(nameof(index), index, 0);
|
||||
return this.items[this.GetRealIndex(index)];
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionIfGreaterThanOrEqual(nameof(index), index, this.Count);
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionIfLessThan(nameof(index), index, 0);
|
||||
this.items[this.GetRealIndex(index)] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Adds an item to this <see cref="RollingList{T}"/>.</summary>
|
||||
/// <param name="item">Item to add.</param>
|
||||
public void Add(T item)
|
||||
{
|
||||
if (this.size == 0) return;
|
||||
if (this.items.Count >= this.size)
|
||||
{
|
||||
this.items[this.firstIndex] = item;
|
||||
this.firstIndex = (this.firstIndex + 1) % this.size;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.items.Count == this.items.Capacity)
|
||||
{
|
||||
// Manual list capacity resize
|
||||
var newCapacity = Math.Max(Math.Min(this.size, this.items.Capacity * 2), this.items.Capacity);
|
||||
this.items.Capacity = newCapacity;
|
||||
}
|
||||
|
||||
this.items.Add(item);
|
||||
}
|
||||
|
||||
Debug.Assert(this.items.Count <= this.size, "Item count should be less than Size");
|
||||
}
|
||||
|
||||
/// <summary>Add items to this <see cref="RollingList{T}"/>.</summary>
|
||||
/// <param name="items">Items to add.</param>
|
||||
public void AddRange(IEnumerable<T> items)
|
||||
{
|
||||
if (this.size == 0) return;
|
||||
foreach (var item in items) this.Add(item);
|
||||
}
|
||||
|
||||
/// <summary>Removes all elements from the <see cref="RollingList{T}"/></summary>
|
||||
public void Clear()
|
||||
{
|
||||
this.items.Clear();
|
||||
this.firstIndex = 0;
|
||||
}
|
||||
|
||||
/// <summary>Find the index of a specific item.</summary>
|
||||
/// <param name="item">item to find.</param>
|
||||
/// <returns>Index where <paramref name="item"/> is found. -1 if not found.</returns>
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
var index = this.items.IndexOf(item);
|
||||
if (index == -1) return -1;
|
||||
return this.GetVirtualIndex(index);
|
||||
}
|
||||
|
||||
/// <summary>Not supported.</summary>
|
||||
[SuppressMessage("Documentation Rules", "SA1611", Justification = "Not supported")]
|
||||
void IList<T>.Insert(int index, T item) => throw new NotSupportedException();
|
||||
|
||||
/// <summary>Not supported.</summary>
|
||||
[SuppressMessage("Documentation Rules", "SA1611", Justification = "Not supported")]
|
||||
void IList<T>.RemoveAt(int index) => throw new NotSupportedException();
|
||||
|
||||
/// <summary>Find wether an item exists.</summary>
|
||||
/// <param name="item">item to find.</param>
|
||||
/// <returns>Wether <paramref name="item"/> is found.</returns>
|
||||
public bool Contains(T item) => this.items.Contains(item);
|
||||
|
||||
/// <summary>Copies the content of this list into an array.</summary>
|
||||
/// <param name="array">Array to copy into.</param>
|
||||
/// <param name="arrayIndex"><paramref name="array"/> index to start coping into.</param>
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionIfLessThan(nameof(arrayIndex), arrayIndex, 0);
|
||||
if (array.Length - arrayIndex < this.Count) ThrowHelper.ThrowArgumentException("Not enough space");
|
||||
for (var index = 0; index < this.Count; index++)
|
||||
{
|
||||
array[arrayIndex++] = this[index];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Not supported.</summary>
|
||||
[SuppressMessage("Documentation Rules", "SA1611", Justification = "Not supported")]
|
||||
[SuppressMessage("Documentation Rules", "SA1615", Justification = "Not supported")]
|
||||
bool ICollection<T>.Remove(T item) => throw new NotSupportedException();
|
||||
|
||||
/// <summary>Gets an enumerator for this <see cref="RollingList{T}"/>.</summary>
|
||||
/// <returns><see cref="RollingList{T}"/> enumerator.</returns>
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
for (var index = 0; index < this.items.Count; index++)
|
||||
{
|
||||
yield return this.items[this.GetRealIndex(index)];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets an enumerator for this <see cref="RollingList{T}"/>.</summary>
|
||||
/// <returns><see cref="RollingList{T}"/> enumerator.</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private int GetRealIndex(int index) => this.size > 0 ? (index + this.firstIndex) % this.size : 0;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private int GetVirtualIndex(int index) => this.size > 0 ? (this.size + index - this.firstIndex) % this.size : 0;
|
||||
}
|
||||
}
|
||||
52
Dalamud/Utility/ThrowHelper.cs
Normal file
52
Dalamud/Utility/ThrowHelper.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Dalamud.Utility
|
||||
{
|
||||
/// <summary>Helper methods for throwing exceptions.</summary>
|
||||
internal static class ThrowHelper
|
||||
{
|
||||
/// <summary>Throws a <see cref="ArgumentException"/> with a specified <paramref name="message"/>.</summary>
|
||||
/// <param name="message">Message for the exception.</param>
|
||||
/// <exception cref="ArgumentException">Thrown by this method.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentException(string message) => throw new ArgumentException(message);
|
||||
|
||||
/// <summary>Throws a <see cref="ArgumentOutOfRangeException"/> with a specified <paramref name="message"/> for a specified <paramref name="paramName"/>.</summary>
|
||||
/// <param name="paramName">Parameter name.</param>
|
||||
/// <param name="message">Message for the exception.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown by this method.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeException(string paramName, string message) => throw new ArgumentOutOfRangeException(paramName, message);
|
||||
|
||||
/// <summary>Throws a <see cref="ArgumentOutOfRangeException"/> if the specified <paramref name="value"/> is less than <paramref name="comparand"/>.</summary>
|
||||
/// <typeparam name="T"><see cref="IComparable{T}"/> value type.</typeparam>
|
||||
/// <param name="paramName">Parameter name.</param>
|
||||
/// <param name="value">Value to compare from.</param>
|
||||
/// <param name="comparand">Value to compare with.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown by this method if <paramref name="value"/> is less than <paramref name="comparand"/>.</exception>
|
||||
public static void ThrowArgumentOutOfRangeExceptionIfLessThan<T>(string paramName, T value, T comparand) where T : IComparable<T>
|
||||
{
|
||||
#if NET8_0_OR_GREATER
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(value, comparand);
|
||||
#else
|
||||
if (Comparer<T>.Default.Compare(value, comparand) <= -1) ThrowArgumentOutOfRangeException(paramName, $"{paramName} must be greater than or equal {comparand}");
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Throws a <see cref="ArgumentOutOfRangeException"/> if the specified <paramref name="value"/> is greater than or equal to <paramref name="comparand"/>.</summary>
|
||||
/// <typeparam name="T"><see cref="IComparable{T}"/> value type.</typeparam>
|
||||
/// <param name="paramName">Parameter name.</param>
|
||||
/// <param name="value">Value to compare from.</param>
|
||||
/// <param name="comparand">Value to compare with.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown by this method if <paramref name="value"/> is greater than or equal to<paramref name="comparand"/>.</exception>
|
||||
public static void ThrowArgumentOutOfRangeExceptionIfGreaterThanOrEqual<T>(string paramName, T value, T comparand) where T : IComparable<T>
|
||||
{
|
||||
#if NET8_0_OR_GREATER
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(value, comparand);
|
||||
#else
|
||||
if (Comparer<T>.Default.Compare(value, comparand) >= 0) ThrowArgumentOutOfRangeException(paramName, $"{paramName} must be less than {comparand}");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ using System.Reflection.Emit;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Data;
|
||||
|
|
@ -22,6 +23,9 @@ using Dalamud.Logging.Internal;
|
|||
using ImGuiNET;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Serilog;
|
||||
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
using Windows.Win32.Storage.FileSystem;
|
||||
|
||||
namespace Dalamud.Utility;
|
||||
|
|
@ -684,6 +688,55 @@ public static class Util
|
|||
return names.ElementAt(rng.Next(0, names.Count() - 1)).Singular.RawString;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a corresponding exception if <see cref="HRESULT.FAILED"/> is true.
|
||||
/// </summary>
|
||||
/// <param name="hr">The result value.</param>
|
||||
internal static void ThrowOnError(this HRESULT hr)
|
||||
{
|
||||
if (hr.FAILED)
|
||||
Marshal.ThrowExceptionForHR(hr.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls <see cref="TaskCompletionSource.SetException(System.Exception)"/> if the task is incomplete.
|
||||
/// </summary>
|
||||
/// <param name="t">The task.</param>
|
||||
/// <param name="ex">The exception to set.</param>
|
||||
internal static void SetExceptionIfIncomplete(this TaskCompletionSource t, Exception ex)
|
||||
{
|
||||
if (t.Task.IsCompleted)
|
||||
return;
|
||||
try
|
||||
{
|
||||
t.SetException(ex);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls <see cref="TaskCompletionSource.SetException(System.Exception)"/> if the task is incomplete.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the result.</typeparam>
|
||||
/// <param name="t">The task.</param>
|
||||
/// <param name="ex">The exception to set.</param>
|
||||
internal static void SetExceptionIfIncomplete<T>(this TaskCompletionSource<T> t, Exception ex)
|
||||
{
|
||||
if (t.Task.IsCompleted)
|
||||
return;
|
||||
try
|
||||
{
|
||||
t.SetException(ex);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Print formatted GameObject Information to ImGui.
|
||||
/// </summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue