diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index ffe87afab..6c5be4e53 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -80,38 +80,45 @@ namespace Barotrauma /// The new game screen. public partial void OnScreenSelected(Screen screen) { - switch (screen) + /*Note: This logic needs to be run after the triggering event so that recursion scenarios (ie. resetting the EventService) + do not occur, so we delay it by one game tick.*/ + CoroutineManager.Invoke(() => { - // menus and navigation states - case MainMenuScreen: - case ModDownloadScreen: - case ServerListScreen: - SetRunState(RunState.Unloaded); - SetRunState(RunState.LoadedNoExec); - break; - // running lobby or editor states - case CampaignEndScreen: - case CharacterEditorScreen: - case EventEditorScreen: - case GameScreen: - case LevelEditorScreen: - case NetLobbyScreen: - case ParticleEditorScreen: - case RoundSummaryScreen: - case SpriteEditorScreen: - case SubEditorScreen: - case TestScreen: // notes: TestScreen is a Linux edge case editor screen and is deprecated. - if (CheckCsEnabled() && this.CurrentRunState >= RunState.Running) - { + switch (screen) + { + // menus and navigation states + case MainMenuScreen: + case ModDownloadScreen: + case ServerListScreen: + SetRunState(RunState.Unloaded); SetRunState(RunState.LoadedNoExec); - } - SetRunState(RunState.Running); - break; - default: - Logger.LogError($"{nameof(LuaCsSetup)}: Received an unknown screen {screen?.GetType().Name ?? "'null screen'"}. Retarding load state to 'unloaded'."); - SetRunState(RunState.Unloaded); - break; - } + break; + // running lobby or editor states + case CampaignEndScreen: + case CharacterEditorScreen: + case EventEditorScreen: + case GameScreen: + case LevelEditorScreen: + case NetLobbyScreen: + case ParticleEditorScreen: + case RoundSummaryScreen: + case SpriteEditorScreen: + case SubEditorScreen: + case TestScreen: // notes: TestScreen is a Linux edge case editor screen and is deprecated. + if (CheckCsEnabled() && this.CurrentRunState >= RunState.Running) + { + SetRunState(RunState.LoadedNoExec); + } + + SetRunState(RunState.Running); + break; + default: + Logger.LogError( + $"{nameof(LuaCsSetup)}: Received an unknown screen {screen?.GetType().Name ?? "'null screen'"}. Retarding load state to 'unloaded'."); + SetRunState(RunState.Unloaded); + break; + } + }, delay: 0f); // min is one tick delay. } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 11814a326..9ab837140 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -9,6 +9,7 @@ using System.Runtime.CompilerServices; using System.Runtime.Loader; using System.Text; using System.Threading; +using System.Xml.Serialization; using Barotrauma.Extensions; using Barotrauma.IO; using Barotrauma.LuaCs.Data; @@ -92,13 +93,75 @@ public class PluginManagementService : IAssemblyManagementService public void Dispose() { - throw new NotImplementedException(); + using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) + { + return; + } + + UnsafeDisposeResourcesInternal(); + _assemblyLoaderFactory = null; + _storageService = null; + _eventService = null; + _logger = null; + _configService = null; + _luaScriptManagementService = null; + + GC.SuppressFinalize(this); } - public bool IsDisposed { get; } + private void UnsafeDisposeResourcesInternal() + { + foreach (var packPlugin in _pluginInstances.SelectMany(kvp => kvp.Value.Select(pluginInst => (kvp.Key, pluginInst)))) + { + try + { + packPlugin.pluginInst.Dispose(); + } + catch (Exception e) + { + _logger.LogError($"Error while disposing plugin for ContentPackage {packPlugin.Key.Name}: \n{e.Message}"); + } + } + _pluginInstances.Clear(); + _pluginInjectorContainer.Dispose(); + _pluginInjectorContainer = null; + + foreach (var loader in _assemblyLoaders) + { + try + { + loader.Value.Dispose(); + _unloadingAssemblyLoaders.Add(loader.Value, loader.Key); + } + 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(); + } + + private int _isDisposed = 0; + public bool IsDisposed + { + get => ModUtils.Threading.GetBool(ref _isDisposed); + private set => ModUtils.Threading.SetBool(ref _isDisposed, value); + } public FluentResults.Result Reset() { - return FluentResults.Result.Fail("Not implemented"); + using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + UnsafeDisposeResourcesInternal(); + return FluentResults.Result.Ok(); } #endregion @@ -536,8 +599,6 @@ public class PluginManagementService : IAssemblyManagementService private void OnAssemblyLoaderUnloading(IAssemblyLoaderService loader) { - using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); - if (!loader.Assemblies.Any()) { return; @@ -545,7 +606,7 @@ public class PluginManagementService : IAssemblyManagementService foreach (var assembly in loader.Assemblies) { - _eventService.Value.PublishEvent(sub => sub.OnAssemblyUnloading(assembly)); + _eventService?.Value?.PublishEvent(sub => sub.OnAssemblyUnloading(assembly)); } } @@ -564,20 +625,11 @@ public class PluginManagementService : IAssemblyManagementService results.WithReasons(UnsafeDisposeManagedTypeInstances().Reasons); ReflectionUtils.ResetCache(); - - bool[] targetGcGeneration = new bool[GC.MaxGeneration]; - - for (int i = 0; i < targetGcGeneration.Length; i++) - { - targetGcGeneration[i] = false; - } - foreach (var loaderService in _assemblyLoaders) { try { loaderService.Value.Dispose(); - targetGcGeneration[GC.GetGeneration(loaderService.Value)] = true; _unloadingAssemblyLoaders.Add(loaderService.Value, loaderService.Key); } catch (Exception e) @@ -587,14 +639,7 @@ public class PluginManagementService : IAssemblyManagementService } _assemblyLoaders.Clear(); - - for (int i = 0; i < targetGcGeneration.Length; i++) - { - if (!targetGcGeneration[i]) - { - GC.Collect(i, GCCollectionMode.Aggressive, true); - } - } + GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true); return results; }