Merge branch 'master' into feature/separate-atlas-per-plugin

This commit is contained in:
srkizer 2024-01-15 18:05:23 +09:00 committed by GitHub
commit 5caed2f3b0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 129 additions and 30 deletions

View file

@ -42,7 +42,48 @@ jobs:
with:
name: dalamud-artifact
path: bin\Release
check_api_compat:
name: "Check API Compatibility"
if: ${{ github.event_name == 'pull_request' }}
needs: build
runs-on: windows-latest
steps:
- name: "Install .NET SDK"
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7
- name: "Install ApiCompat"
run: |
dotnet tool install -g Microsoft.DotNet.ApiCompat.Tool
- name: "Download Proposed Artifacts"
uses: actions/download-artifact@v2
with:
name: dalamud-artifact
path: .\right
- name: "Download Live (Stg) Artifacts"
run: |
Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile latest.zip
Expand-Archive -Force latest.zip "left"
- name: "Verify Compatibility"
run: |
$FILES_TO_VALIDATE = "Dalamud.dll","FFXIVClientStructs.dll","Lumina.dll","Lumina.Excel.dll"
$retcode = 0
foreach ($file in $FILES_TO_VALIDATE) {
$testout = ""
Write-Output "::group::=== API COMPATIBILITY CHECK: ${file} ==="
apicompat -l "left\${file}" -r "right\${file}" | Tee-Object -Variable testout
Write-Output "::endgroup::"
if ($testout -ne "APICompat ran successfully without finding any breaking changes.") {
Write-Output "::error::${file} did not pass. Please review it for problems."
$retcode = 1
}
}
exit $retcode
deploy_stg:
name: Deploy dalamud-distrib staging
if: ${{ github.repository_owner == 'goatcorp' && github.event_name == 'push' }}

View file

@ -8,7 +8,7 @@
</PropertyGroup>
<PropertyGroup Label="Feature">
<DalamudVersion>9.0.0.14</DalamudVersion>
<DalamudVersion>9.0.0.16</DalamudVersion>
<Description>XIV Launcher addon framework</Description>
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
<Version>$(DalamudVersion)</Version>

View file

@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using CheapLoc;
@ -14,9 +15,9 @@ using Dalamud.Interface.Internal;
using Dalamud.Interface.Internal.Notifications;
using Dalamud.Interface.Internal.Windows;
using Dalamud.Interface.Internal.Windows.PluginInstaller;
using Dalamud.Logging.Internal;
using Dalamud.Plugin.Internal;
using Dalamud.Utility;
using Serilog;
namespace Dalamud.Game;
@ -60,6 +61,8 @@ internal class ChatHandlers : IServiceType
// { XivChatType.Echo, Color.Gray },
// };
private static readonly ModuleLog Log = new("CHATHANDLER");
private readonly Regex rmtRegex = new(
@"4KGOLD|We have sufficient stock|VPK\.OM|[Gg]il for free|[Gg]il [Cc]heap|5GOLD|www\.so9\.com|Fast & Convenient|Cheap & Safety Guarantee|【Code|A O A U E|igfans|4KGOLD\.COM|Cheapest Gil with|pvp and bank on google|Selling Cheap GIL|ff14mogstation\.com|Cheap Gil 1000k|gilsforyou|server 1000K =|gils_selling|E A S Y\.C O M|bonus code|mins delivery guarantee|Sell cheap|Salegm\.com|cheap Mog|Off Code:|FF14Mog.com|使用する5オ|[Oo][Ff][Ff] [Cc]ode( *)[:;]|offers Fantasia",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
@ -110,6 +113,7 @@ internal class ChatHandlers : IServiceType
private bool hasSeenLoadingMsg;
private bool startedAutoUpdatingPlugins;
private CancellationTokenSource deferredAutoUpdateCts = new();
[ServiceManager.ServiceConstructor]
private ChatHandlers(ChatGui chatGui)
@ -165,16 +169,19 @@ internal class ChatHandlers : IServiceType
if (clientState == null)
return;
if (type == XivChatType.Notice && !this.hasSeenLoadingMsg)
this.PrintWelcomeMessage();
if (type == XivChatType.Notice)
{
if (!this.hasSeenLoadingMsg)
this.PrintWelcomeMessage();
if (!this.startedAutoUpdatingPlugins)
this.AutoUpdatePluginsWithRetry();
}
// For injections while logged in
if (clientState.LocalPlayer != null && clientState.TerritoryType == 0 && !this.hasSeenLoadingMsg)
this.PrintWelcomeMessage();
if (!this.startedAutoUpdatingPlugins)
this.AutoUpdatePlugins();
#if !DEBUG && false
if (!this.hasSeenLoadingMsg)
return;
@ -264,24 +271,42 @@ internal class ChatHandlers : IServiceType
this.hasSeenLoadingMsg = true;
}
private void AutoUpdatePlugins()
private void AutoUpdatePluginsWithRetry()
{
var firstAttempt = this.AutoUpdatePlugins();
if (!firstAttempt)
{
Task.Run(() =>
{
Task.Delay(30_000, this.deferredAutoUpdateCts.Token);
this.AutoUpdatePlugins();
});
}
}
private bool AutoUpdatePlugins()
{
var chatGui = Service<ChatGui>.GetNullable();
var pluginManager = Service<PluginManager>.GetNullable();
var notifications = Service<NotificationManager>.GetNullable();
if (chatGui == null || pluginManager == null || notifications == null)
return;
{
Log.Warning("Aborting auto-update because a required service was not loaded.");
return false;
}
if (!pluginManager.ReposReady || !pluginManager.InstalledPlugins.Any() || !pluginManager.AvailablePlugins.Any())
{
// Plugins aren't ready yet.
// TODO: We should retry. This sucks, because it means we won't ever get here again until another notice.
return;
Log.Warning("Aborting auto-update because plugins weren't loaded or ready.");
return false;
}
this.startedAutoUpdatingPlugins = true;
Log.Debug("Beginning plugin auto-update process...");
Task.Run(() => pluginManager.UpdatePluginsAsync(true, !this.configuration.AutoUpdatePlugins, true)).ContinueWith(task =>
{
this.IsAutoUpdateComplete = true;
@ -320,5 +345,7 @@ internal class ChatHandlers : IServiceType
}
}
});
return true;
}
}

View file

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Numerics;
@ -273,7 +273,10 @@ internal class TextureManager : IDisposable, IServiceType, ITextureProvider, ITe
this.fallbackTextureWrap?.Dispose();
this.framework.Update -= this.FrameworkOnUpdate;
Log.Verbose("Disposing {Num} left behind textures.");
if (this.activeTextures.Count == 0)
return;
Log.Verbose("Disposing {Num} left behind textures.", this.activeTextures.Count);
foreach (var activeTexture in this.activeTextures)
{

View file

@ -131,9 +131,16 @@ internal class AssemblyLoadContextBuilder
/// or the default app context.
/// </summary>
/// <param name="assemblyName">The name of the assembly.</param>
/// <param name="recursive">Pull assmeblies recursively.</param>
/// <returns>The builder.</returns>
public AssemblyLoadContextBuilder PreferDefaultLoadContextAssembly(AssemblyName assemblyName)
public AssemblyLoadContextBuilder PreferDefaultLoadContextAssembly(AssemblyName assemblyName, bool recursive)
{
if (!recursive)
{
this.defaultAssemblies.Add(assemblyName.Name);
return this;
}
var names = new Queue<AssemblyName>(new[] { assemblyName });
while (names.TryDequeue(out var name))

View file

@ -46,7 +46,7 @@ internal class LoaderConfig
/// Gets a list of assemblies which should be unified between the host and the plugin.
/// </summary>
/// <seealso href="https://github.com/natemcmaster/DotNetCorePlugins/blob/main/docs/what-are-shared-types.md">what-are-shared-types</seealso>
public ICollection<AssemblyName> SharedAssemblies { get; } = new List<AssemblyName>();
public ICollection<(AssemblyName Name, bool Recursive)> SharedAssemblies { get; } = new List<(AssemblyName Name, bool Recursive)>();
/// <summary>
/// Gets or sets a value indicating whether attempt to unify all types from a plugin with the host.

View file

@ -194,7 +194,18 @@ internal class ManagedLoadContext : AssemblyLoadContext
}
}
return null;
// https://learn.microsoft.com/en-us/dotnet/core/dependency-loading/loading-managed#algorithm
// > These assemblies are loaded (load-by-name) as needed by the runtime.
// For load-by-name assembiles, the following will happen in order:
// (1) this.Load will be called.
// (2) AssemblyLoadContext.Default's cache will be referred for lookup.
// (3) Default probing will be done from PLATFORM_RESOURCE_ROOTS and APP_PATHS.
// https://learn.microsoft.com/en-us/dotnet/core/dependency-loading/default-probing#managed-assembly-default-probing
// > TRUSTED_PLATFORM_ASSEMBLIES: List of platform and application assembly file paths.
// > APP_PATHS: is not populated by default and is omitted for most applications.
// If we return null here, if the assembly has not been already loaded, the resolution will fail.
// Therefore as the final attempt, we try loading from the default load context.
return this.defaultLoadContext.LoadFromAssemblyName(assemblyName);
}
/// <summary>

View file

@ -146,18 +146,14 @@ internal class PluginLoader : IDisposable
builder.ShadowCopyNativeLibraries();
}
foreach (var assemblyName in config.SharedAssemblies)
foreach (var (assemblyName, recursive) in config.SharedAssemblies)
{
builder.PreferDefaultLoadContextAssembly(assemblyName);
builder.PreferDefaultLoadContextAssembly(assemblyName, recursive);
}
// This allows plugins to search for dependencies in the Dalamud directory when their assembly
// load would otherwise fail, allowing them to resolve assemblies not already loaded by Dalamud
// itself yet.
builder.AddProbingPath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
// Also make sure that plugins do not load their own Dalamud assembly.
builder.PreferDefaultLoadContextAssembly(Assembly.GetExecutingAssembly().GetName());
// Note: not adding Dalamud path here as a probing path.
// It will be dealt as the last resort from ManagedLoadContext.Load.
// See there for more details.
return builder;
}

View file

@ -958,7 +958,7 @@ internal partial class PluginManager : IDisposable, IServiceType
autoUpdate ? PluginListInvalidationKind.AutoUpdate : PluginListInvalidationKind.Update,
updatedList.Select(x => x.InternalName));
Log.Information("Plugin update OK.");
Log.Information("Plugin update OK. {updateCount} plugins updated.", updatedList.Length);
return updatedList;
}
@ -1581,6 +1581,8 @@ internal partial class PluginManager : IDisposable, IServiceType
private void DetectAvailablePluginUpdates()
{
Log.Debug("Starting plugin update check...");
lock (this.pluginListLock)
{
this.updatablePluginsList.Clear();
@ -1615,10 +1617,12 @@ internal partial class PluginManager : IDisposable, IServiceType
}
}
}
Log.Debug("Update check found {updateCount} available updates.", this.updatablePluginsList.Count);
}
private void NotifyAvailablePluginsChanged()
{
{
this.DetectAvailablePluginUpdates();
this.OnAvailablePluginsChanged?.InvokeSafely();

View file

@ -627,8 +627,18 @@ internal class LocalPlugin : IDisposable
config.IsUnloadable = true;
config.LoadInMemory = true;
config.PreferSharedTypes = false;
config.SharedAssemblies.Add(typeof(Lumina.GameData).Assembly.GetName());
config.SharedAssemblies.Add(typeof(Lumina.Excel.ExcelSheetImpl).Assembly.GetName());
// Pin Lumina and its dependencies recursively (compatibility behavior).
// It currently only pulls in System.* anyway.
// TODO(api10): Remove this. We don't want to pin Lumina anymore, plugins should be able to provide their own.
config.SharedAssemblies.Add((typeof(Lumina.GameData).Assembly.GetName(), true));
config.SharedAssemblies.Add((typeof(Lumina.Excel.ExcelSheetImpl).Assembly.GetName(), true));
// Make sure that plugins do not load their own Dalamud assembly.
// We do not pin this recursively; if a plugin loads its own assembly of Dalamud, it is always wrong,
// but plugins may load other versions of assemblies that Dalamud depends on.
config.SharedAssemblies.Add((typeof(EntryPoint).Assembly.GetName(), false));
config.SharedAssemblies.Add((typeof(Common.DalamudStartInfo).Assembly.GetName(), false));
}
private void EnsureLoader()

@ -1 +1 @@
Subproject commit 97b814ca15d147911cdac3059623185a57984e0a
Subproject commit 89e713c071dae13112550d3e754193704e230b03