OBT/1.2.1(Summer update)

Sync with upstream
This commit is contained in:
NotAlwaysTrue
2026-06-16 22:17:29 +08:00
committed by GitHub
parent 59bc21973a
commit d109c8f827
85 changed files with 1377 additions and 497 deletions
@@ -10,6 +10,7 @@ using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Serialization;
using Barotrauma.Extensions;
using Barotrauma.IO;
@@ -88,7 +89,15 @@ public class PluginManagementService : IAssemblyManagementService
private ImmutableArray<MetadataReference> _baseMetadataReferences = ImmutableArray<MetadataReference>.Empty;
private ImmutableArray<MetadataReference> _baseMetadataReferencesNonPublicized = ImmutableArray<MetadataReference>.Empty;
private Thread _backgroundGCCleanupThread = null;
private long _backgroundGCWatchdogTicks = 0;
private static readonly int
GC_TASK_COMPLETION_TIMEOUT = 5000,
GC_BACKGND_MAXITERATIONS = 2,
GC_BACKGND_INTERVAL_MILLIS = 200,
GC_BACKGND_GENERATION_WAIT_MILLIS = 100;
private IEnumerable<MetadataReference> BaseMetadataReferences
{
@@ -174,6 +183,7 @@ public class PluginManagementService : IAssemblyManagementService
_pluginInjectorContainer?.Dispose();
_pluginInjectorContainer = null;
ReflectionUtils.ResetCache();
foreach (var loader in _assemblyLoaders)
{
try
@@ -184,14 +194,6 @@ public class PluginManagementService : IAssemblyManagementService
catch (Exception e)
{
_logger?.LogError($"Failed to dispose of {nameof(IAssemblyLoaderService)} for ContentPackage {loader.Key.Name}: \n{e.Message}");
if (loader.Value.Assemblies.Any())
{
foreach (var ass in loader.Value.Assemblies)
{
_logger?.LogWarning($"{nameof(PluginManagementService)}: Fallback manual unsubscription of assemblies: {ass.GetName()}");
ReflectionUtils.RemoveAssemblyFromCache(ass);
}
}
}
}
_assemblyLoaders.Clear();
@@ -222,6 +224,7 @@ public class PluginManagementService : IAssemblyManagementService
private IEventService _pluginEventService;
private Lazy<ILuaPatcher> _pluginLuaPatcherService;
private Func<IConsoleCommandsService> _consoleCommandServiceFactory;
private readonly IConsoleCommandsService _internalConsoleCommandsService;
private ILuaCsInfoProvider _luaCsInfoProvider;
private readonly ConcurrentDictionary<ContentPackage, IAssemblyLoaderService> _assemblyLoaders = new();
private readonly ConcurrentDictionary<Type, ContentPackage> _pluginPackageLookup = new();
@@ -251,6 +254,21 @@ public class PluginManagementService : IAssemblyManagementService
_pluginLuaPatcherService = pluginLuaPatcherService;
_consoleCommandServiceFactory = consoleCommandServiceFactory;
_luaCsInfoProvider = luaCsInfoProvider;
_internalConsoleCommandsService = consoleCommandServiceFactory.Invoke();
RegisterCommands(_internalConsoleCommandsService);
}
private void RegisterCommands(IConsoleCommandsService cmdService)
{
cmdService.RegisterCommand("plugin_forcerungc", "Forces the GC to run", cmds =>
{
_logger.LogMessage("Forcing GC run.");
Task.Factory.StartNew(async () =>
{
await RunGC(true, false);
});
});
}
private ServiceContainer CreatePluginServiceContainer()
@@ -317,11 +335,13 @@ public class PluginManagementService : IAssemblyManagementService
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public bool TryGetPackageForPlugin<TPlugin>(out ContentPackage ownerPackage)
{
return _pluginPackageLookup.TryGetValue(typeof(TPlugin), out ownerPackage);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public Type GetType(string typeName, bool isByRefType = false, bool includeInterfaces = false,
bool includeDefaultContext = true)
{
@@ -370,6 +390,7 @@ public class PluginManagementService : IAssemblyManagementService
return null;
}
[MethodImpl(MethodImplOptions.NoOptimization)]
public FluentResults.Result ActivatePluginInstances(ImmutableArray<ContentPackage> executionOrder, bool excludeAlreadyRunningPackages = true)
{
if (executionOrder.IsDefaultOrEmpty)
@@ -488,6 +509,7 @@ public class PluginManagementService : IAssemblyManagementService
return results;
// helper
[MethodImpl(MethodImplOptions.NoOptimization)]
FluentResults.Result PluginInitRunner(IAssemblyPlugin plugin, Action<IAssemblyPlugin> action)
{
try
@@ -502,7 +524,7 @@ public class PluginManagementService : IAssemblyManagementService
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public FluentResults.Result LoadAssemblyResources(ImmutableArray<IAssemblyResourceInfo> resources)
{
if (resources.IsDefaultOrEmpty)
@@ -705,7 +727,7 @@ public class PluginManagementService : IAssemblyManagementService
{
builder.AddRange(BaseMetadataReferencesWithBarotrauma);
foreach (var loaderService in _assemblyLoaders
.Where(asl => !asl.Key.Name.Equals("LuaCsForBarotrauma", StringComparison.InvariantCultureIgnoreCase))
.Where(asl => !asl.Key.Name.Equals(LuaCsSetup.PackageName, StringComparison.InvariantCultureIgnoreCase))
.ToImmutableArray())
{
builder.AddRange(loaderService.Value.AssemblyReferences.Where(ar => ar is not null));
@@ -732,7 +754,7 @@ public class PluginManagementService : IAssemblyManagementService
.Replace(" Barotrauma.Networking.Client.ClientList", " ModUtils.Client.ClientList")
.Replace("ItemPrefab.GetItemPrefab", "ModUtils.ItemPrefab.GetItemPrefab");
}
private IntPtr OnAssemblyLoaderResolvingUnmanaged(Assembly callerAssembly, string targetAssemblyName)
{
Guard.IsNull(callerAssembly, nameof(callerAssembly));
@@ -805,21 +827,58 @@ public class PluginManagementService : IAssemblyManagementService
{
_eventService?.Value?.PublishEvent<IEventAssemblyUnloading>(sub => sub.OnAssemblyUnloading(assembly));
}
_unloadingAssemblyLoaders.Add(loader, loader.OwnerPackage);
}
[MethodImpl(MethodImplOptions.NoOptimization)]
public FluentResults.Result UnloadManagedAssemblies()
{
using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
IService.CheckDisposed(this);
if (_assemblyLoaders.Count == 0)
{
return FluentResults.Result.Ok();
}
var results = new FluentResults.Result();
results.WithReasons(UnsafeDisposeManagedTypeInstances().Reasons);
if (!_pluginInstances.IsEmpty)
{
foreach (var instance in _pluginInstances.SelectMany(kvp => kvp.Value))
{
try
{
instance.Dispose();
}
catch (Exception e)
{
results.WithError(new ExceptionalError(e));
continue;
}
}
_pluginInstances.Clear();
}
if (_pluginEventService is not null)
{
_eventService.Value.RemoveDispatcherEventService(_pluginEventService);
try
{
_pluginEventService.Dispose();
}
catch (Exception e)
{
results.WithError(new ExceptionalError(e));
}
_pluginEventService = null;
}
try
{
_pluginInjectorContainer?.Dispose();
}
catch (Exception e)
{
results.WithError(new ExceptionalError(e));
}
_pluginInjectorContainer = null;
ReflectionUtils.ResetCache();
foreach (var loaderService in _assemblyLoaders)
@@ -827,7 +886,6 @@ public class PluginManagementService : IAssemblyManagementService
try
{
loaderService.Value.Dispose();
_unloadingAssemblyLoaders.Add(loaderService.Value, loaderService.Key);
}
catch (Exception e)
{
@@ -837,38 +895,7 @@ public class PluginManagementService : IAssemblyManagementService
_assemblyLoaders.Clear();
_storageService.PurgeCache();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true);
#if DEBUG
// Print still loaded assembly load ctx after giving some time
CoroutineManager.Invoke(() =>
{
if (!_unloadingAssemblyLoaders.Any())
{
return;
}
StringBuilder sb = new StringBuilder();
sb.AppendLine("The following ContentPackages have not unloaded their assemblies:");
foreach (var kvp in _unloadingAssemblyLoaders.ToImmutableArray())
{
sb.AppendLine($"- '{kvp.Value.Name}'");
}
// Use DebugConsole in case logger is null by the time this executes.
if (_logger is null)
{
DebugConsole.LogError(sb.ToString());
}
else
{
_logger.LogWarning(sb.ToString());
}
}, 3.0f);
#endif
_pluginPackageLookup.Clear();
// clear native libraries
if (_loadedNativeLibraries.Any())
@@ -888,41 +915,109 @@ public class PluginManagementService : IAssemblyManagementService
_loadedNativeLibraries.Clear();
}
Task.Factory.StartNew(async () =>
{
await RunGC(true, false);
});
return results;
}
private FluentResults.Result UnsafeDisposeManagedTypeInstances()
private void SafeLogUnloadingPackages()
{
var results = new FluentResults.Result();
if (!_pluginInstances.IsEmpty)
if (!_unloadingAssemblyLoaders.Any())
{
foreach (var instance in _pluginInstances.SelectMany(kvp => kvp.Value))
return;
}
StringBuilder sb = new StringBuilder();
sb.AppendLine("The following ContentPackages have not unloaded their assemblies:");
foreach (var kvp in _unloadingAssemblyLoaders.ToImmutableArray())
{
sb.AppendLine($"- '{kvp.Value.Name}'");
}
// Use DebugConsole in case logger is null by the time this executes.
if (_logger is null)
{
DebugConsole.Log(sb.ToString());
}
else
{
_logger.LogWarning(sb.ToString());
}
}
private void GCCleanupTask(TaskCompletionSource<bool> completionSuccess)
{
GC.RegisterForFullGCNotification(1, 1);
try
{
for (int iter = 0; iter < GC_BACKGND_MAXITERATIONS; iter++)
{
try
int maxGen = GC.MaxGeneration;
for (int currGen = 0; currGen < maxGen; currGen++)
{
instance.Dispose();
}
catch (Exception e)
{
results.WithError(new ExceptionalError(e));
continue;
GC.Collect(currGen, GCCollectionMode.Forced, false, false); // marking pass
GC.WaitForFullGCComplete(GC_BACKGND_GENERATION_WAIT_MILLIS);
GC.Collect(currGen); // generation cleanup
}
Thread.Sleep(GC_BACKGND_INTERVAL_MILLIS);
}
completionSuccess.SetResult(true);
}
if (_pluginEventService is not null)
catch (ThreadInterruptedException tie)
{
_eventService.Value.RemoveDispatcherEventService(_pluginEventService);
_pluginEventService = null;
completionSuccess.SetResult(false);
}
catch (Exception e)
{
completionSuccess.SetException(e);
}
finally
{
GC.CancelFullGCNotification();
}
}
private async Task RunGC(bool logResults, bool runOnMainThread)
{
var gcCompletionSuccess = new TaskCompletionSource<bool>();
if (runOnMainThread)
{
GCCleanupTask(gcCompletionSuccess);
if (logResults)
{
SafeLogUnloadingPackages();
}
return;
}
_pluginInjectorContainer = null;
_pluginInstances.Clear();
_pluginPackageLookup.Clear();
return results;
var gcThread = new Thread(() =>
{
GCCleanupTask(gcCompletionSuccess);
}) { IsBackground = true };
gcThread.Start();
try
{
await gcCompletionSuccess.Task.WaitAsync(TimeSpan.FromMilliseconds(GC_TASK_COMPLETION_TIMEOUT));
}
catch (TimeoutException te)
{
_logger.LogError($"{nameof(RunGC)}: The GC task thread has timed out.");
gcThread.Interrupt();
gcThread.Join();
}
if (logResults)
{
SafeLogUnloadingPackages();
}
}
public Result<Assembly> GetLoadedAssembly(OneOf<AssemblyName, string> assemblyName, in Guid[] excludedContexts)