mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-01-03 06:13:40 +01:00
Merge branch 'apiX' into feature/itextureprovider-updates
This commit is contained in:
commit
8c7771bf7d
2213 changed files with 10372 additions and 1088868 deletions
|
|
@ -11,9 +11,19 @@ internal sealed class Api10ToDoAttribute : Attribute
|
|||
/// </summary>
|
||||
public const string DeleteCompatBehavior = "Delete. This is for making API 9 plugins work.";
|
||||
|
||||
/// <summary>
|
||||
/// Marks that this should be moved to an another namespace.
|
||||
/// </summary>
|
||||
public const string MoveNamespace = "Move to another namespace.";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Api10ToDoAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="what">The explanation.</param>
|
||||
public Api10ToDoAttribute(string what) => _ = what;
|
||||
/// <param name="what2">The explanation 2.</param>
|
||||
public Api10ToDoAttribute(string what, string what2 = "")
|
||||
{
|
||||
_ = what;
|
||||
_ = what2;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
149
Dalamud/Utility/DateTimeSpanExtensions.cs
Normal file
149
Dalamud/Utility/DateTimeSpanExtensions.cs
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
|
||||
using CheapLoc;
|
||||
|
||||
using Dalamud.Logging.Internal;
|
||||
|
||||
namespace Dalamud.Utility;
|
||||
|
||||
/// <summary>
|
||||
/// Utility functions for <see cref="DateTime"/> and <see cref="TimeSpan"/>.
|
||||
/// </summary>
|
||||
public static class DateTimeSpanExtensions
|
||||
{
|
||||
private static readonly ModuleLog Log = new(nameof(DateTimeSpanExtensions));
|
||||
|
||||
private static readonly CultureInfo Region0CultureInfo = CultureInfo.GetCultureInfo("ja-jp");
|
||||
private static readonly CultureInfo Region1CultureInfo = CultureInfo.GetCultureInfo("en-us");
|
||||
private static readonly CultureInfo Region2CultureInfo = CultureInfo.GetCultureInfo("en-gb");
|
||||
|
||||
private static ParsedRelativeFormatStrings? relativeFormatStringLong;
|
||||
|
||||
private static ParsedRelativeFormatStrings? relativeFormatStringShort;
|
||||
|
||||
/// <summary>Formats an instance of <see cref="DateTime"/> as a localized absolute time.</summary>
|
||||
/// <param name="when">When.</param>
|
||||
/// <returns>The formatted string.</returns>
|
||||
/// <remarks>The string will be formatted according to Square Enix Account region settings, if Dalamud default
|
||||
/// language is English.</remarks>
|
||||
public static unsafe string LocAbsolute(this DateTime when)
|
||||
{
|
||||
var culture = Service<Localization>.GetNullable()?.DalamudLanguageCultureInfo ?? CultureInfo.InvariantCulture;
|
||||
if (Equals(culture, CultureInfo.InvariantCulture))
|
||||
{
|
||||
var framework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance();
|
||||
var region = 0;
|
||||
if (framework is not null)
|
||||
region = framework->Region;
|
||||
culture = region switch
|
||||
{
|
||||
1 => Region1CultureInfo,
|
||||
2 => Region2CultureInfo,
|
||||
_ => Region0CultureInfo,
|
||||
};
|
||||
}
|
||||
|
||||
return when.ToString("G", culture);
|
||||
}
|
||||
|
||||
/// <summary>Formats an instance of <see cref="DateTime"/> as a localized relative time.</summary>
|
||||
/// <param name="when">When.</param>
|
||||
/// <param name="floorBy">The alignment unit of time span.</param>
|
||||
/// <returns>The formatted string.</returns>
|
||||
public static string LocRelativePastLong(this DateTime when, TimeSpan floorBy)
|
||||
{
|
||||
var loc = Loc.Localize(
|
||||
"DateTimeSpanExtensions.RelativeFormatStringsLong",
|
||||
"172800,{0:%d} days ago\n86400,yesterday\n7200,{0:%h} hours ago\n3600,an hour ago\n120,{0:%m} minutes ago\n60,a minute ago\n2,{0:%s} seconds ago\n1,a second ago\n-Infinity,just now");
|
||||
Debug.Assert(loc != null, "loc != null");
|
||||
|
||||
if (relativeFormatStringLong?.FormatStringLoc != loc)
|
||||
relativeFormatStringLong ??= new(loc);
|
||||
|
||||
return
|
||||
floorBy == default
|
||||
? relativeFormatStringLong.Format(DateTime.Now - when)
|
||||
: relativeFormatStringLong.Format(Math.Floor((DateTime.Now - when) / floorBy) * floorBy);
|
||||
}
|
||||
|
||||
/// <summary>Formats an instance of <see cref="DateTime"/> as a localized relative time.</summary>
|
||||
/// <param name="when">When.</param>
|
||||
/// <param name="floorBy">The alignment unit of time span.</param>
|
||||
/// <returns>The formatted string.</returns>
|
||||
public static string LocRelativePastShort(this DateTime when, TimeSpan floorBy)
|
||||
{
|
||||
var loc = Loc.Localize(
|
||||
"DateTimeSpanExtensions.RelativeFormatStringsShort",
|
||||
"86400,{0:%d}d\n3600,{0:%h}h\n60,{0:%m}m\n1,{0:%s}s\n-Infinity,now");
|
||||
Debug.Assert(loc != null, "loc != null");
|
||||
|
||||
if (relativeFormatStringShort?.FormatStringLoc != loc)
|
||||
relativeFormatStringShort = new(loc);
|
||||
|
||||
return
|
||||
floorBy == default
|
||||
? relativeFormatStringShort.Format(DateTime.Now - when)
|
||||
: relativeFormatStringShort.Format(Math.Floor((DateTime.Now - when) / floorBy) * floorBy);
|
||||
}
|
||||
|
||||
/// <summary>Formats an instance of <see cref="DateTime"/> as a localized relative time.</summary>
|
||||
/// <param name="when">When.</param>
|
||||
/// <returns>The formatted string.</returns>
|
||||
public static string LocRelativePastLong(this DateTime when) => when.LocRelativePastLong(TimeSpan.FromSeconds(1));
|
||||
|
||||
/// <summary>Formats an instance of <see cref="DateTime"/> as a localized relative time.</summary>
|
||||
/// <param name="when">When.</param>
|
||||
/// <returns>The formatted string.</returns>
|
||||
public static string LocRelativePastShort(this DateTime when) => when.LocRelativePastShort(TimeSpan.FromSeconds(1));
|
||||
|
||||
private sealed class ParsedRelativeFormatStrings
|
||||
{
|
||||
private readonly List<(float MinSeconds, string FormatString)> formatStrings = new();
|
||||
|
||||
public ParsedRelativeFormatStrings(string value)
|
||||
{
|
||||
this.FormatStringLoc = value;
|
||||
foreach (var line in value.Split("\n"))
|
||||
{
|
||||
var sep = line.IndexOf(',');
|
||||
if (sep < 0)
|
||||
{
|
||||
Log.Error("A line without comma has been found: {line}", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!float.TryParse(
|
||||
line.AsSpan(0, sep),
|
||||
NumberStyles.Float,
|
||||
CultureInfo.InvariantCulture,
|
||||
out var seconds))
|
||||
{
|
||||
Log.Error("Could not parse the duration: {line}", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.formatStrings.Add((seconds, line[(sep + 1)..]));
|
||||
}
|
||||
|
||||
this.formatStrings.Sort((a, b) => b.MinSeconds.CompareTo(a.MinSeconds));
|
||||
}
|
||||
|
||||
public string FormatStringLoc { get; }
|
||||
|
||||
/// <summary>Formats an instance of <see cref="TimeSpan"/> as a localized string.</summary>
|
||||
/// <param name="ts">The duration.</param>
|
||||
/// <returns>The formatted string.</returns>
|
||||
public string Format(TimeSpan ts)
|
||||
{
|
||||
foreach (var (minSeconds, formatString) in this.formatStrings)
|
||||
{
|
||||
if (ts.TotalSeconds >= minSeconds)
|
||||
return string.Format(formatString, ts);
|
||||
}
|
||||
|
||||
return this.formatStrings[^1].FormatString.Format(ts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -70,7 +70,16 @@ public static class DisposeSafety
|
|||
r =>
|
||||
{
|
||||
if (!r.IsCompletedSuccessfully)
|
||||
return ignoreAllExceptions ? Task.CompletedTask : r;
|
||||
{
|
||||
if (ignoreAllExceptions)
|
||||
{
|
||||
_ = r.Exception;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
r.Result.Dispose();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
using System.Numerics;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
|
||||
using Lumina;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Dalamud.Utility;
|
||||
|
|
@ -128,4 +134,32 @@ public static class MapUtil
|
|||
{
|
||||
return WorldToMap(worldCoordinates, map.OffsetX, map.OffsetY, map.SizeFactor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension method to get the current position of a GameObject in Map Coordinates (visible to players in the
|
||||
/// minimap or chat). A Z (height) value will always be returned, even on maps that do not natively show one.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Thrown if ClientState is unavailable.</exception>
|
||||
/// <param name="go">The GameObject to get the position for.</param>
|
||||
/// <param name="correctZOffset">Whether to "correct" a Z offset to sane values for maps that don't have one.</param>
|
||||
/// <returns>A Vector3 that represents the X (east/west), Y (north/south), and Z (height) position of this object.</returns>
|
||||
public static unsafe Vector3 GetMapCoordinates(this GameObject go, bool correctZOffset = false)
|
||||
{
|
||||
var agentMap = AgentMap.Instance();
|
||||
|
||||
if (agentMap == null || agentMap->CurrentMapId == 0)
|
||||
throw new InvalidOperationException("Could not determine active map - data may not be loaded yet?");
|
||||
|
||||
var territoryTransient = Service<DataManager>.Get()
|
||||
.GetExcelSheet<TerritoryTypeTransient>()!
|
||||
.GetRow(agentMap->CurrentTerritoryId);
|
||||
|
||||
return WorldToMap(
|
||||
go.Position,
|
||||
agentMap->CurrentOffsetX,
|
||||
agentMap->CurrentOffsetY,
|
||||
territoryTransient?.OffsetZ ?? 0,
|
||||
(uint)agentMap->CurrentMapSizeFactor,
|
||||
correctZOffset);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,8 +93,9 @@ namespace Dalamud.Utility
|
|||
this.firstIndex = 0;
|
||||
}
|
||||
}
|
||||
else // value < this._size
|
||||
else
|
||||
{
|
||||
// value < this._size
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionIfLessThan(nameof(value), value, 0);
|
||||
if (value < this.Count)
|
||||
{
|
||||
|
|
@ -156,14 +157,14 @@ namespace Dalamud.Utility
|
|||
}
|
||||
|
||||
/// <summary>Add items to this <see cref="RollingList{T}"/>.</summary>
|
||||
/// <param name="items">Items to add.</param>
|
||||
public void AddRange(IEnumerable<T> items)
|
||||
/// <param name="range">Items to add.</param>
|
||||
public void AddRange(IEnumerable<T> range)
|
||||
{
|
||||
if (this.size == 0) return;
|
||||
foreach (var item in items) this.Add(item);
|
||||
foreach (var item in range) this.Add(item);
|
||||
}
|
||||
|
||||
/// <summary>Removes all elements from the <see cref="RollingList{T}"/></summary>
|
||||
/// <summary>Removes all elements from the <see cref="RollingList{T}"/>.</summary>
|
||||
public void Clear()
|
||||
{
|
||||
this.items.Clear();
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ internal static class SignatureHelper
|
|||
/// <see cref="SignatureAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="self">The object to initialize.</param>
|
||||
/// <param name="log">If warnings should be logged using <see cref="PluginLog"/>.</param>
|
||||
/// <param name="log">If warnings should be logged.</param>
|
||||
/// <returns>Collection of created IDalamudHooks.</returns>
|
||||
internal static IEnumerable<IDalamudHook> Initialize(object self, bool log = true)
|
||||
{
|
||||
|
|
@ -90,7 +90,7 @@ internal static class SignatureHelper
|
|||
|
||||
switch (sig.UseFlags)
|
||||
{
|
||||
case SignatureUseFlags.Auto when actualType == typeof(IntPtr) || actualType.IsPointer || actualType.IsAssignableTo(typeof(Delegate)):
|
||||
case SignatureUseFlags.Auto when actualType == typeof(IntPtr) || actualType.IsFunctionPointer || actualType.IsUnmanagedFunctionPointer || actualType.IsPointer || actualType.IsAssignableTo(typeof(Delegate)):
|
||||
case SignatureUseFlags.Pointer:
|
||||
{
|
||||
if (actualType.IsAssignableTo(typeof(Delegate)))
|
||||
|
|
|
|||
90
Dalamud/Utility/ThreadBoundTaskScheduler.cs
Normal file
90
Dalamud/Utility/ThreadBoundTaskScheduler.cs
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Dalamud.Utility;
|
||||
|
||||
/// <summary>
|
||||
/// A task scheduler that runs tasks on a specific thread.
|
||||
/// </summary>
|
||||
internal class ThreadBoundTaskScheduler : TaskScheduler
|
||||
{
|
||||
private const byte Scheduled = 0;
|
||||
private const byte Running = 1;
|
||||
|
||||
private readonly ConcurrentDictionary<Task, byte> scheduledTasks = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ThreadBoundTaskScheduler"/> class.
|
||||
/// </summary>
|
||||
/// <param name="boundThread">The thread to bind this task scheduelr to.</param>
|
||||
public ThreadBoundTaskScheduler(Thread? boundThread = null)
|
||||
{
|
||||
this.BoundThread = boundThread;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the thread this task scheduler is bound to.
|
||||
/// </summary>
|
||||
public Thread? BoundThread { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether we're on the bound thread.
|
||||
/// </summary>
|
||||
public bool IsOnBoundThread => Thread.CurrentThread == this.BoundThread;
|
||||
|
||||
/// <summary>
|
||||
/// Runs queued tasks.
|
||||
/// </summary>
|
||||
public void Run()
|
||||
{
|
||||
foreach (var task in this.scheduledTasks.Keys)
|
||||
{
|
||||
if (!this.scheduledTasks.TryUpdate(task, Running, Scheduled))
|
||||
continue;
|
||||
|
||||
_ = this.TryExecuteTask(task);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Task> GetScheduledTasks()
|
||||
{
|
||||
return this.scheduledTasks.Keys;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void QueueTask(Task task)
|
||||
{
|
||||
this.scheduledTasks[task] = Scheduled;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool TryDequeue(Task task)
|
||||
{
|
||||
if (!this.scheduledTasks.TryRemove(task, out _))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
|
||||
{
|
||||
if (!this.IsOnBoundThread)
|
||||
return false;
|
||||
|
||||
if (taskWasPreviouslyQueued && !this.scheduledTasks.TryUpdate(task, Running, Scheduled))
|
||||
return false;
|
||||
|
||||
_ = this.TryExecuteTask(task);
|
||||
return true;
|
||||
}
|
||||
|
||||
private new bool TryExecuteTask(Task task)
|
||||
{
|
||||
var r = base.TryExecuteTask(task);
|
||||
this.scheduledTasks.Remove(task, out _);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Dalamud.Utility;
|
||||
|
||||
|
|
@ -19,6 +20,7 @@ public static class ThreadSafety
|
|||
/// Throws an exception when the current thread is not the main thread.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Thrown when the current thread is not the main thread.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void AssertMainThread()
|
||||
{
|
||||
if (!threadStaticIsMainThread)
|
||||
|
|
@ -31,6 +33,7 @@ public static class ThreadSafety
|
|||
/// Throws an exception when the current thread is the main thread.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Thrown when the current thread is the main thread.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void AssertNotMainThread()
|
||||
{
|
||||
if (threadStaticIsMainThread)
|
||||
|
|
@ -39,6 +42,15 @@ public static class ThreadSafety
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary><see cref="AssertMainThread"/>, but only on debug compilation mode.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void DebugAssertMainThread()
|
||||
{
|
||||
#if DEBUG
|
||||
AssertMainThread();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks a thread as the main thread.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ using Dalamud.Game.ClientState.Objects.SubKinds;
|
|||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Logging.Internal;
|
||||
using ImGuiNET;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Serilog;
|
||||
|
|
@ -345,7 +344,10 @@ public static class Util
|
|||
_ = NativeFunctions.MessageBoxW(Process.GetCurrentProcess().MainWindowHandle, message, caption, flags);
|
||||
|
||||
if (exit)
|
||||
{
|
||||
Log.CloseAndFlush();
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -638,42 +640,6 @@ public static class Util
|
|||
if (!Windows.Win32.PInvoke.MoveFileEx(tempPath, path, MOVE_FILE_FLAGS.MOVEFILE_REPLACE_EXISTING | MOVE_FILE_FLAGS.MOVEFILE_WRITE_THROUGH))
|
||||
throw new Win32Exception();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose this object.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to dispose.</param>
|
||||
/// <typeparam name="T">The type of object to dispose.</typeparam>
|
||||
internal static void ExplicitDispose<T>(this T obj) where T : IDisposable
|
||||
{
|
||||
obj.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose this object.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to dispose.</param>
|
||||
/// <param name="logMessage">Log message to print, if specified and an error occurs.</param>
|
||||
/// <param name="moduleLog">Module logger, if any.</param>
|
||||
/// <typeparam name="T">The type of object to dispose.</typeparam>
|
||||
internal static void ExplicitDisposeIgnoreExceptions<T>(
|
||||
this T obj, string? logMessage = null, ModuleLog? moduleLog = null) where T : IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
obj.Dispose();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (logMessage == null)
|
||||
return;
|
||||
|
||||
if (moduleLog != null)
|
||||
moduleLog.Error(e, logMessage);
|
||||
else
|
||||
Log.Error(e, logMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a random, inoffensive, human-friendly string.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue