From 7436ea3e8c2dcc9ebc327d60eeec770b0176e5dd Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Fri, 14 Feb 2025 12:34:59 -0500 Subject: [PATCH] - Finished most of LuaCsSetup top-level functionality. - Removed some unneeded interface definitions. - Clean-slated some Services that need to be re-written. --- .../BarotraumaClient/ClientSource/GameMain.cs | 10 +- .../GameModes/Tutorials/Tutorial.cs | 2 - .../LuaCs/Data/DataInterfaceDefinitions.cs | 1 + .../ClientSource/LuaCs/LuaCsInstaller.cs | 2 +- .../ClientSource/LuaCs/LuaCsSettingsMenu.cs | 31 +- .../ClientSource/LuaCs/LuaCsSetup.cs | 86 +- .../Services/IStylesManagementService.cs | 13 + .../LuaCs/Services/IStylesService.cs | 25 +- .../LuaCs/Services/PackageService.cs | 42 - .../Processing/StylesManagementService.cs | 6 + .../LuaCs/Services/StylesService.cs | 46 +- .../ClientSource/Screens/ModDownloadScreen.cs | 5 +- .../BarotraumaServer/ServerSource/GameMain.cs | 6 +- .../ServerSource/LuaCs/LuaCsSetup.cs | 22 + .../LuaCs/Services/PackageService.cs | 29 - .../ContentPackageManager.cs | 28 +- .../LuaCs/Configuration/IConfigEntry.cs | 2 - .../LuaCs/Data/DataInterfaceDefinitions.cs | 4 +- .../LuaCs/Data/IResourceInfoDeclarations.cs | 4 +- .../SharedSource/LuaCs/IEvents.cs | 37 +- .../SharedSource/LuaCs/LuaCsSetup.cs | 835 ++++++++++++------ .../SharedSource/LuaCs/LuaCsUtility.cs | 3 +- .../LuaCs/Services/EventService.cs | 16 +- .../LuaCs/Services/LoggerService.cs | 2 + .../Services/LuaScriptManagementService.cs | 14 + .../LuaCs/Services/LuaScriptService.cs | 176 ---- .../Services/PackageManagementService.cs | 450 +--------- .../LuaCs/Services/PackageService.cs | 686 -------------- .../LuaCs/Services/PluginManagementService.cs | 16 + .../IConverterServiceDefinitions.cs | 43 +- .../LuaCs/Services/StorageService.cs | 27 +- .../Services/_Interfaces/IConfigService.cs | 25 +- .../_Interfaces/ILocalizationService.cs | 4 +- .../ILuaScriptManagementService.cs | 88 ++ .../Services/_Interfaces/ILuaScriptService.cs | 85 -- .../_Interfaces/IPackageManagementService.cs | 120 +-- .../Services/_Interfaces/IPackageService.cs | 38 - .../_Interfaces/IPluginManagementService.cs | 24 +- .../SharedSource/Screens/Screen.cs | 1 + 39 files changed, 1009 insertions(+), 2045 deletions(-) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesManagementService.cs delete mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageService.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/StylesManagementService.cs create mode 100644 Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs delete mode 100644 Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageService.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index 953062de9..1126a166a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -18,12 +18,14 @@ using System.Reflection; using System.Threading; using Barotrauma.Extensions; using System.Collections.Immutable; +using Barotrauma.LuaCs.Events; namespace Barotrauma { class GameMain : Game { - public static LuaCsSetup LuaCs; + private static LuaCsSetup _luaCs; + public static LuaCsSetup LuaCs => _luaCs ??= new LuaCsSetup(); public static bool ShowFPS; public static bool ShowPerf; public static bool DebugDraw; @@ -244,8 +246,6 @@ namespace Barotrauma throw new Exception("Content folder not found. If you are trying to compile the game from the source code and own a legal copy of the game, you can copy the Content folder from the game's files to BarotraumaShared/Content."); } - LuaCs = new LuaCsSetup(); - GameSettings.Init(); CreatureMetrics.Init(); @@ -1054,7 +1054,7 @@ namespace Barotrauma SoundManager?.Update(); - GameMain.LuaCs.Update(); + LuaCs.EventService.PublishEvent(sub => sub.OnUpdate(Timing.Step)); Timing.Accumulator -= Timing.Step; @@ -1237,8 +1237,6 @@ namespace Barotrauma GUIMessageBox.CloseAll(); MainMenuScreen.Select(); GameSession = null; - - GameMain.LuaCs.Stop(); } public void ShowBugReporter() diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs index 4dfea9e86..3b6ac9488 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs @@ -179,8 +179,6 @@ namespace Barotrauma.Tutorials public void Start() { - GameMain.LuaCs.CheckInitialize(); - GameMain.Instance.ShowLoading(Loading()); ObjectiveManager.ResetObjectives(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs index d7be1b0cf..60e4ec227 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs @@ -18,5 +18,6 @@ public record StylesResourceInfo : IStylesResourceInfo public ImmutableArray SupportedCultures { get; init; } public string InternalName { get; init; } public ContentPackage OwnerPackage { get; init; } + public string FallbackPackageName { get; } public ImmutableArray Dependencies { get; init; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsInstaller.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsInstaller.cs index 422147beb..c18660d89 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsInstaller.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsInstaller.cs @@ -59,7 +59,7 @@ namespace Barotrauma { if (!File.Exists(LuaCsSetup.VersionFile)) { return; } - ContentPackage luaPackage = LuaCsSetup.GetPackage(LuaCsSetup.LuaForBarotraumaId); + ContentPackage luaPackage = LuaCsSetup.GetPackage(new SteamWorkshopId(GameMain.LuaCs.LuaForBarotraumaSteamId?.Value ?? 0)); if (luaPackage == null) { return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs index 613a863d9..f1e5985bb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs @@ -19,65 +19,55 @@ namespace Barotrauma new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Enable CSharp Scripting") { - Selected = GameMain.LuaCs.Config.EnableCsScripting, + Selected = GameMain.LuaCs.IsCsEnabled?.Value ?? false, ToolTip = "This enables CSharp Scripting for mods to use, WARNING: CSharp is NOT sandboxed, be careful with what mods you download.", OnSelected = (GUITickBox tick) => { - GameMain.LuaCs.Config.EnableCsScripting = tick.Selected; - GameMain.LuaCs.WriteSettings(); - + GameMain.LuaCs.IsCsEnabled?.TrySetValue(tick.Selected); return true; } }; new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Treat Forced Mods As Normal") { - Selected = GameMain.LuaCs.Config.TreatForcedModsAsNormal, + Selected = GameMain.LuaCs.TreatForcedModsAsNormal?.Value ?? false, ToolTip = "This makes mods that were setup to run even when disabled to only run when enabled.", OnSelected = (GUITickBox tick) => { - GameMain.LuaCs.Config.TreatForcedModsAsNormal = tick.Selected; - GameMain.LuaCs.WriteSettings(); - + GameMain.LuaCs.TreatForcedModsAsNormal?.TrySetValue(tick.Selected); return true; } }; new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Prefer To Use Workshop Lua Setup") { - Selected = GameMain.LuaCs.Config.PreferToUseWorkshopLuaSetup, + Selected = GameMain.LuaCs.PreferToUseWorkshopLuaSetup?.Value ?? false, ToolTip = "This makes Lua look first for the Lua/LuaSetup.lua located in the Workshop package instead of the one located locally.", OnSelected = (GUITickBox tick) => { - GameMain.LuaCs.Config.PreferToUseWorkshopLuaSetup = tick.Selected; - GameMain.LuaCs.WriteSettings(); - + GameMain.LuaCs.PreferToUseWorkshopLuaSetup?.TrySetValue(tick.Selected); return true; } }; new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Disable Error GUI Overlay") { - Selected = GameMain.LuaCs.Config.DisableErrorGUIOverlay, + Selected = GameMain.LuaCs.DisableErrorGUIOverlay?.Value ?? false, ToolTip = "", OnSelected = (GUITickBox tick) => { - GameMain.LuaCs.Config.DisableErrorGUIOverlay = tick.Selected; - GameMain.LuaCs.WriteSettings(); - + GameMain.LuaCs.DisableErrorGUIOverlay?.TrySetValue(tick.Selected); return true; } }; new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Hide usernames In Error Logs") { - Selected = GameMain.LuaCs.Config.HideUserNames, + Selected = GameMain.LuaCs.HideUserNamesInLogs?.Value ?? false, ToolTip = "Hides the operating system username when displaying error logs (eg your username on windows).", OnSelected = (GUITickBox tick) => { - GameMain.LuaCs.Config.HideUserNames = tick.Selected; - GameMain.LuaCs.WriteSettings(); - + GameMain.LuaCs.HideUserNamesInLogs?.TrySetValue(tick.Selected); return true; } }; @@ -100,7 +90,6 @@ namespace Barotrauma OnClicked = (GUIButton button, object obj) => { Close(); - return true; } }; diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index b85175ded..19649aea8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -1,6 +1,13 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; +using System.Linq; using System.Text; +using Barotrauma.CharacterEditor; +using Barotrauma.LuaCs.Services; + +// ReSharper disable ObjectCreationAsStatement namespace Barotrauma { @@ -8,47 +15,42 @@ namespace Barotrauma { public void AddToGUIUpdateList() { - if (!GameMain.LuaCs.Config.DisableErrorGUIOverlay) + if (!DisableErrorGUIOverlay.Value) { LuaCsLogger.AddToGUIUpdateList(); } } - public void CheckInitialize() - { - List csharpMods = new List(); - foreach (ContentPackage cp in ContentPackageManager.EnabledPackages.All) - { - if (Directory.Exists(cp.Dir + "/CSharp") || Directory.Exists(cp.Dir + "/bin")) - { - csharpMods.Add(cp); - } - } + private partial bool ShouldRunCs() => IsCsEnabled.Value; + + public IStylesManagementService StylesManagementService => _servicesProvider.TryGetService(out var svc) + ? svc : throw new NullReferenceException("Networking Manager service not found!"); - if (csharpMods.Count == 0 || ShouldRunCs) - { - Initialize(); + public void CheckCsEnabled() + { + + var csharpMods = PackageManagementService.Assemblies + .GroupBy(ass => ass.OwnerPackage) + .Select(grp => grp.Key) + .Where(ContentPackageManager.EnabledPackages.All.Contains) + .ToImmutableArray(); + + if (!csharpMods.Any()) return; - } StringBuilder sb = new StringBuilder(); foreach (ContentPackage cp in csharpMods) { if (cp.UgcId.TryUnwrap(out ContentPackageId id)) - { sb.AppendLine($"- {cp.Name} ({id})"); - } else - { sb.AppendLine($"- {cp.Name} (Not On Workshop)"); - } } if (GameMain.Client == null || GameMain.Client.IsServerOwner) { new GUIMessageBox("", $"You have CSharp mods enabled but don't have the CSharp Scripting enabled, those mods might not work, go to the Main Menu, click on LuaCs Settings and check Enable CSharp Scripting.\n\n{sb}"); - Initialize(); return; } @@ -59,17 +61,51 @@ namespace Barotrauma msg.Buttons[0].OnClicked = (GUIButton button, object obj) => { - Initialize(true); - msg.Close(); + this.IsCsEnabled.TrySetValue(true); return true; }; msg.Buttons[1].OnClicked = (GUIButton button, object obj) => { - Initialize(); - msg.Close(); + this.IsCsEnabled.TrySetValue(false); return true; }; } + + /// + /// Handles changes in game states tracked by screen changes. + /// + /// The new game screen. + public partial void OnScreenSelected(Screen screen) + { + switch (screen) + { + // menus and navigation states + case MainMenuScreen: + case ModDownloadScreen: + case ServerListScreen: + SetRunState(RunState.Configuration); + 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. + CheckCsEnabled(); + 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; + } + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesManagementService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesManagementService.cs new file mode 100644 index 000000000..dc5dfc69a --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesManagementService.cs @@ -0,0 +1,13 @@ +using System.Collections.Immutable; +using System.Threading.Tasks; +using Barotrauma.LuaCs.Data; + +namespace Barotrauma.LuaCs.Services; + +public interface IStylesManagementService : IReusableService +{ + Task LoadStylesAsync(ImmutableArray styles); + FluentResults.Result GetStylesService(ContentPackage package); + Task DisposeAllStyles(); + Task DisposeStylesForPackage(ContentPackage package); +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs index 05cde12b4..6b07dbc21 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs @@ -1,4 +1,6 @@ -namespace Barotrauma.LuaCs.Services; +using System.Threading.Tasks; + +namespace Barotrauma.LuaCs.Services; // TODO: Rework interface to support resource infos. /// @@ -7,43 +9,48 @@ public interface IStylesService : IReusableService { /// - /// Tries to load the styles file for the given contentpackage and path into a new UIStylesProcessor instance. + /// Tries to load the styles file for the given and path into a new instance. /// /// /// /// - FluentResults.Result LoadStylesFile(ContentPackage package, ContentPath path); + Task LoadStylesFileAsync(ContentPackage package, ContentPath path); + /// - /// Unloads all styles assets and UIStyleProcessor instances. + /// Unloads all styles assets and instances. /// FluentResults.Result UnloadAllStyles(); /// - /// Tries to the get the font asset by xml asset name, returns null on failure. + /// Tries to the get the asset by xml asset name, returns null on failure. /// /// XML Name of the asset. /// The asset or null if none are found. GUIFont GetFont(string fontName); + /// - /// Tries to the get the sprite asset by xml asset name, returns null on failure. + /// Tries to the get the asset by xml asset name, returns null on failure. /// /// XML Name of the asset. /// The asset or null if none are found. GUISprite GetSprite(string spriteName); + /// - /// Tries to the get the sprite sheet asset by xml asset name, returns null on failure. + /// Tries to the get the asset by xml asset name, returns null on failure. /// /// XML Name of the asset. /// The asset or null if none are found. GUISpriteSheet GetSpriteSheet(string spriteSheetName); + /// - /// Tries to the get the cursor asset by xml asset name, returns null on failure. + /// Tries to the get the asset by xml asset name, returns null on failure. /// /// XML Name of the asset. /// The asset or null if none are found. GUICursor GetCursor(string cursorName); + /// - /// Tries to the get the color asset by xml asset name, returns null on failure. + /// Tries to the get the asset by xml asset name, returns null on failure. /// /// XML Name of the asset. /// The asset or null if none are found. diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageService.cs deleted file mode 100644 index 96c92093b..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageService.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Services.Processing; - -namespace Barotrauma.LuaCs.Services; - -public partial class PackageService : IStylesResourcesInfo -{ - private readonly Lazy _stylesService; - public IStylesService Styles => _stylesService.Value; - - public PackageService( - Lazy configParserService, - Lazy luaScriptService, - Lazy localizationService, - Lazy pluginService, - Lazy stylesService, - Lazy configService, - IPackageManagementService packageManagementService, - IStorageService storageService, - ILoggerService loggerService) - { - _configParserService = configParserService; - _luaScriptService = luaScriptService; - _localizationService = localizationService; - _pluginService = pluginService; - _stylesService = stylesService; - _configService = configService; - _packageManagementService = packageManagementService; - _storageService = storageService; - _loggerService = loggerService; - } - - public ImmutableArray StylesResourceInfos => ModConfigInfo?.StylesResourceInfos ?? ImmutableArray.Empty; - - public FluentResults.Result LoadStyles([NotNull]IStylesResourcesInfo stylesInfo) - { - throw new NotImplementedException(); - } -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/StylesManagementService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/StylesManagementService.cs new file mode 100644 index 000000000..49bd12eb2 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/StylesManagementService.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs.Services.Processing; + +public class StylesManagementService : IStylesManagementService +{ + +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs index 16c384157..29cb65429 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs @@ -1,16 +1,19 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using System.Xml.Linq; using FluentResults; using FluentResults.LuaCs; namespace Barotrauma.LuaCs.Services; +// TODO: Complete rewrite public class StylesService : IStylesService { - private readonly Dictionary _loadedProcessors = new(); + private readonly ConcurrentDictionary _loadedProcessors = new(); private readonly IStorageService _storageService; private readonly ILoggerService _loggerService; @@ -19,40 +22,11 @@ public class StylesService : IStylesService _storageService = storageService; _loggerService = loggerService; } - - public FluentResults.Result LoadStylesFile(ContentPackage package, ContentPath path) + + + public async Task LoadStylesFileAsync(ContentPackage package, ContentPath path) { - //check if file already in dict - if (_loadedProcessors.ContainsKey(path.FullPath)) - { - return FluentResults.Result.Ok(); - } - //check if file exists - if (_storageService.FileExists(path.FullPath) is {} result - && result.IsFailed | (result.IsSuccess & result.Value == false)) - { - return FluentResults.Result.Fail(result.Errors) - .WithError(new Error($"{nameof(StylesService)}.{nameof(LoadStylesFile)} file does not exist!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, package)); - } - - try - { - var styleProcessor = new UIStyleProcessor(package, path); - styleProcessor.LoadFile(); - _loadedProcessors.Add(path.FullPath, styleProcessor); - } - catch (InvalidDataException exception) - { - return FluentResults.Result.Fail(new Error($"{nameof(StylesService)}.{nameof(LoadStylesFile)} failed for ContentPackage {package.Name}: Exception: {exception.Message}") - .WithMetadata(MetadataType.ExceptionDetails, exception.Message) - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, package) - .WithMetadata(MetadataType.StackTrace, exception.StackTrace)); - } - - return FluentResults.Result.Ok(); + throw new NotImplementedException(); } public FluentResults.Result UnloadAllStyles() @@ -134,7 +108,7 @@ public class StylesService : IStylesService return null; } - private bool NoProcessorsLoaded => _loadedProcessors.Count < 1; + private bool NoProcessorsLoaded => _loadedProcessors.IsEmpty; public void Dispose() { @@ -146,4 +120,6 @@ public class StylesService : IStylesService { return UnloadAllStyles(); } + + public bool IsDisposed { get; private set; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/ModDownloadScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/ModDownloadScreen.cs index e3d878e81..8c0889e6e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/ModDownloadScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/ModDownloadScreen.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Barotrauma.Extensions; using Barotrauma.IO; +using Barotrauma.LuaCs.Events; using Barotrauma.Networking; using Barotrauma.Steam; using Microsoft.Xna.Framework; @@ -118,7 +119,6 @@ namespace Barotrauma ContentPackageManager.EnabledPackages.SetRegular(regularPackages); } GameMain.NetLobbyScreen.Select(); - GameMain.LuaCs.CheckInitialize(); return; } @@ -366,7 +366,7 @@ namespace Barotrauma ContentPackageManager.EnabledPackages.BackUp(); ContentPackageManager.EnabledPackages.SetCore(corePackage); ContentPackageManager.EnabledPackages.SetRegular(regularPackages); - + //see if any of the packages we enabled contain subs that we were missing previously, and update their paths foreach (var serverSub in GameMain.Client.ServerSubmarines) { @@ -379,7 +379,6 @@ namespace Barotrauma } GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.SubList, GameMain.Client.ServerSubmarines); GameMain.NetLobbyScreen.Select(); - GameMain.LuaCs.CheckInitialize(); } } else if (GameMain.Client.FileReceiver.ActiveTransfers.None()) diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs index b95af9c57..18bce7393 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs @@ -13,6 +13,7 @@ using System.Xml.Linq; using MoonSharp.Interpreter; using System.Net; using Barotrauma.Extensions; +using Barotrauma.LuaCs.Events; namespace Barotrauma { @@ -34,7 +35,8 @@ namespace Barotrauma set { world = value; } } - public static LuaCsSetup LuaCs; + private static LuaCsSetup _luaCs; + public static LuaCsSetup LuaCs => _luaCs ??= new LuaCsSetup(); public static GameServer Server; public static NetworkMember NetworkMember @@ -364,7 +366,7 @@ namespace Barotrauma TaskPool.Update(); CoroutineManager.Update(paused: false, (float)Timing.Step); - GameMain.LuaCs.Update(); + LuaCs.EventService.PublishEvent(sub => sub.OnUpdate(Timing.Step)); performanceCounterTimer.Stop(); if (GameMain.LuaCs.PerformanceCounter.EnablePerformanceCounter) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs new file mode 100644 index 000000000..9eaca6c7d --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs @@ -0,0 +1,22 @@ +using Barotrauma.Networking; + +namespace Barotrauma; + +partial class LuaCsSetup +{ + /// + /// Handles changes in game states tracked by screen changes. + /// + /// The new game screen. + public partial void OnScreenSelected(Screen screen) + { + // the server is always in the running state unless explicitly stopped. + if (screen == UnimplementedScreen.Instance) + SetRunState(RunState.Unloaded); + SetRunState(RunState.Running); + } + + private partial bool ShouldRunCs() => IsCsEnabled.Value || + (GetPackage(new SteamWorkshopId(CsForBarotraumaSteamId.Value), false, false) is { } + && GameMain.Server.ServerPeer is LidgrenServerPeer); +} diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageService.cs deleted file mode 100644 index d64643ebb..000000000 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageService.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using Barotrauma.LuaCs.Services.Processing; - -// ReSharper disable once CheckNamespace -namespace Barotrauma.LuaCs.Services; - -public partial class PackageService -{ - public PackageService( - Lazy configParserService, - Lazy luaScriptService, - Lazy localizationService, - Lazy pluginService, - Lazy configService, - IPackageManagementService packageManagementService, - IStorageService storageService, - ILoggerService loggerService) - { - _configParserService = configParserService; - _luaScriptService = luaScriptService; - _localizationService = localizationService; - _pluginService = pluginService; - _configService = configService; - _packageManagementService = packageManagementService; - _storageService = storageService; - _loggerService = loggerService; - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs index 79ce7be3f..20f0a7970 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs @@ -9,6 +9,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Xml.Linq; using Barotrauma.IO; +using Barotrauma.LuaCs.Events; using Barotrauma.Steam; using Microsoft.Xna.Framework; @@ -48,7 +49,12 @@ namespace Barotrauma public static ImmutableArray? Regular; } - public static void SetCore(CorePackage newCore) => SetCoreEnumerable(newCore).Consume(); + public static void SetCore(CorePackage newCore) + { + SetCoreEnumerable(newCore).Consume(); + GameMain.LuaCs.EventService.PublishEvent( + sub => sub.OnEnabledPackageListChanged(Core, Regular)); + } public static IEnumerable SetCoreEnumerable(CorePackage newCore) { @@ -85,7 +91,11 @@ namespace Barotrauma } public static void SetRegular(IReadOnlyList newRegular) - => SetRegularEnumerable(newRegular).Consume(); + { + SetRegularEnumerable(newRegular).Consume(); + GameMain.LuaCs.EventService.PublishEvent( + sub => sub.OnEnabledPackageListChanged(Core, Regular)); + } public static IEnumerable SetRegularEnumerable(IReadOnlyList inNewRegular) { @@ -327,6 +337,12 @@ namespace Barotrauma Debug.WriteLine($"Loaded \"{newPackage.Name}\""); } + + GameMain.LuaCs.EventService.PublishEvent(sub => + sub.OnAllPackageListChanged(corePackages + .Select((ContentPackage p) => p) + .Union(regularPackages.Select((ContentPackage p) => p)) + .ToImmutableArray())); } private readonly string directory; @@ -566,6 +582,12 @@ namespace Barotrauma yield return p.Transform(loadingRange); } + GameMain.LuaCs.EventService.PublishEvent( + sub => sub.OnAllPackageListChanged(CorePackages, RegularPackages)); + + GameMain.LuaCs.EventService.PublishEvent( + sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); + yield return LoadProgress.Progress(1.0f); } @@ -597,4 +619,4 @@ namespace Barotrauma } } } -} \ No newline at end of file +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs index 8f5325eca..30ba129c6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs @@ -9,6 +9,4 @@ public interface IConfigEntry : IConfigBase, INetVar where T : IConvertible, bool TrySetValue(T value); bool IsAssignable(T value); void Initialize(IVarId id, T defaultValue); - - } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs index f620bbbce..f5e169005 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs @@ -19,7 +19,7 @@ public partial record ModConfigInfo : IModConfigInfo public ImmutableArray SupportedCultures { get; init; } public ImmutableArray Assemblies { get; init; } public ImmutableArray Localizations { get; init; } - public ImmutableArray LuaScripts { get; init; } + public ImmutableArray LuaScripts { get; init; } public ImmutableArray Configs { get; init; } public ImmutableArray ConfigProfiles { get; init; } } @@ -160,7 +160,7 @@ public record LocalizationResourceInfo : ILocalizationResourceInfo public bool Optional { get; init; } } -public readonly struct LuaScriptResourceInfo : ILuaResourceInfo +public readonly struct LuaScriptScriptResourceInfo : ILuaScriptResourceInfo { public ContentPackage OwnerPackage { get; init; } public string FallbackPackageName { get; init; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs index c92fc1ec0..281b20b04 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs @@ -10,7 +10,7 @@ public interface ILocalizationResourceInfo : IResourceInfo, IResourceCultureInfo /// /// Represents loadable Lua files. /// -public interface ILuaResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo { } +public interface ILuaScriptResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo { } public interface IAssemblyResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo { /// @@ -39,7 +39,7 @@ public interface ILocalizationsResourcesInfo public interface ILuaScriptsResourcesInfo { - ImmutableArray LuaScripts { get; } + ImmutableArray LuaScripts { get; } } public interface IConfigsResourcesInfo diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs index b82eef22f..1e7bc2620 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs @@ -29,6 +29,35 @@ public interface IEvent : IEvent where T : IEvent } } +#region RuntimeEvents + +/// +/// Called when the current (game state) changes. Upstream Type 'Screen' is internal. +/// +internal interface IEventScreenSelected : IEvent +{ + void OnScreenSelected(Screen screen); +} + +/// +/// Called whenever the list of all (enabled and disabled) on disk has changed. +/// +internal interface IEventAllPackageListChanged : IEvent +{ + void OnAllPackageListChanged(IEnumerable corePackages, IEnumerable regularPackages); +} + +/// +/// Called whenever the list of enabled has changed. +/// +internal interface IEventEnabledPackageListChanged : IEvent +{ + void OnEnabledPackageListChanged(CorePackage package, IEnumerable regularPackages); +} + + +#endregion + #region GameEvents /// @@ -62,11 +91,11 @@ public interface IEventRoundStarted : IEvent /// public interface IEventUpdate : IEvent { - void OnUpdate(float fixedDeltaTime); + void OnUpdate(double fixedDeltaTime); static IEventUpdate IEvent.GetLuaRunner(IDictionary luaFunc) => new { IsLuaRunner = Return.Arguments(() => true), - OnUpdate = ReturnVoid.Arguments((fixedDeltaTime) => luaFunc[nameof(OnUpdate)](fixedDeltaTime)) + OnUpdate = ReturnVoid.Arguments((fixedDeltaTime) => luaFunc[nameof(OnUpdate)](fixedDeltaTime)) }.ActLike(); } @@ -75,11 +104,11 @@ public interface IEventUpdate : IEvent /// public interface IEventDrawUpdate : IEvent { - void OnDrawUpdate(float deltaTime); + void OnDrawUpdate(double deltaTime); static IEventDrawUpdate IEvent.GetLuaRunner(IDictionary luaFunc) => new { IsLuaRunner = Return.Arguments(() => true), - OnDrawUpdate = ReturnVoid.Arguments((deltaTime) => luaFunc[nameof(OnDrawUpdate)](deltaTime)) + OnDrawUpdate = ReturnVoid.Arguments((deltaTime) => luaFunc[nameof(OnDrawUpdate)](deltaTime)) }.ActLike(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 232ea11bc..cf3250c2a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -1,130 +1,77 @@ using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Barotrauma.LuaCs.Configuration; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Events; using Barotrauma.LuaCs.Services; using Barotrauma.LuaCs.Services.Compatibility; using Barotrauma.LuaCs.Services.Processing; using Barotrauma.Networking; +using FluentResults; +using ImpromptuInterface; namespace Barotrauma { - class LuaCsSetupConfig - { - public bool EnableCsScripting = false; - public bool TreatForcedModsAsNormal = true; - public bool PreferToUseWorkshopLuaSetup = false; - public bool DisableErrorGUIOverlay = false; - public bool HideUserNames - { - get { return LuaCsLogger.HideUserNames; } - set { LuaCsLogger.HideUserNames = value; } - } - - public LuaCsSetupConfig() { } - public LuaCsSetupConfig(LuaCsSetupConfig config) - { - EnableCsScripting = config.EnableCsScripting; - TreatForcedModsAsNormal = config.TreatForcedModsAsNormal; - PreferToUseWorkshopLuaSetup = config.PreferToUseWorkshopLuaSetup; - DisableErrorGUIOverlay = config.DisableErrorGUIOverlay; - } - } - internal delegate void LuaCsMessageLogger(string message); internal delegate void LuaCsErrorHandler(Exception ex, LuaCsMessageOrigin origin); internal delegate void LuaCsExceptionHandler(Exception ex, LuaCsMessageOrigin origin); - partial class LuaCsSetup : IDisposable + partial class LuaCsSetup : IDisposable, IEventScreenSelected, IEventAllPackageListChanged, IEventEnabledPackageListChanged { public LuaCsSetup() { - // load services + // == startup _servicesProvider = new ServicesProvider(); RegisterServices(); - - // load manifest - if (!_servicesProvider.TryGetService(out IModConfigCreatorService modConfigSvc)) - throw new NullReferenceException("LuaCsSetup: Failed to get mod config parser service!"); // we should crash here - var luaConfig = modConfigSvc.BuildConfigFromManifest(Directory.GetCurrentDirectory() + LuaCsConfigFile); - if (!luaConfig.IsSuccess) - { - Logger.LogResults(luaConfig.ToResult()); - throw new FileLoadException("LuaCsSetup: Failed to load config file!"); - } - - // load resources - RegisterLocalizations(); - RegisterConfigs(); - - LuaForBarotraumaId = new SteamWorkshopId(LuaForBarotraumaSteamId.Value); + ValidateLuaCsContent(); + SubscribeToLuaCsEvents(); return; - //--- + // == end + // == helpers void RegisterServices() { _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); + _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); +#if CLIENT + _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); + _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); +#endif _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + // TODO: ILocalizationService // TODO: IConfigService // TODO: INetworkingService // TODO: [Resource Converter/Parser Services] - // TODO: ILocalizationService - // TODO: IEventService + _servicesProvider.Compile(); } - void RegisterLocalizations() + // Validates LuaCs assets in /Content are valid and ready to use. + void ValidateLuaCsContent() { - LocalizationService.LoadLocalizations(luaConfig.Value.Localizations); + throw new NotImplementedException(); } - - void RegisterConfigs() - { - if (ConfigService.AddConfigs(luaConfig.Value.Configs) is { IsSuccess: false } res1) - { - Logger.LogResults(res1); - throw new Exception("LuaCsSetup: Failed to load config!"); - } - - if (ConfigService.AddConfigsProfiles(luaConfig.Value.ConfigProfiles) is { IsSuccess: false } res2) - { - Logger.LogResults(res2); - throw new Exception("LuaCsSetup: Failed to load config profiles!"); - } - - IsCsEnabled = GetOrThrowForConfig(luaConfig.Value.PackageName, "IsCsEnabled"); - TreatForcedModsAsNormal = GetOrThrowForConfig(luaConfig.Value.PackageName, "TreatForcedModsAsNormal"); - PreferToUseWorkshopLuaSetup = GetOrThrowForConfig(luaConfig.Value.PackageName, "PreferToUseWorkshopLuaSetup"); - DisableErrorGUIOverlay = GetOrThrowForConfig(luaConfig.Value.PackageName, "DisableErrorGUIOverlay"); - EnableThreadedLoading = GetOrThrowForConfig(luaConfig.Value.PackageName, "EnableThreadedLoading"); - HideUserNamesInLogs = GetOrThrowForConfig(luaConfig.Value.PackageName, "HideUserNamesInLogs"); - LuaForBarotraumaSteamId = GetOrThrowForConfig(luaConfig.Value.PackageName, "LuaForBarotraumaSteamId"); - - return; - //--- - - IConfigEntry GetOrThrowForConfig(string packName, string internalName) where T : IConvertible, IEquatable - { - var cfgRes = ConfigService.GetConfig>(packName, internalName); - if (cfgRes.IsSuccess) - { - return cfgRes.Value; - } - Logger.LogResults(cfgRes.ToResult()); - throw new Exception($"LuaCsSetup: Failed to load config for {internalName}!"); - } - } - - + } + + void SubscribeToLuaCsEvents() + { + EventService.Subscribe(this); // game state hook in + EventService.Subscribe(this); + EventService.Subscribe(this); } #region CONST_DEF - - public const string LuaCsConfigFile = "LuaCsConfig.xml"; #if SERVER public const bool IsServer = true; @@ -154,7 +101,7 @@ namespace Barotrauma ? svc : throw new NullReferenceException("Package Manager service not found!"); public IPluginManagementService PluginManagementService => _servicesProvider.TryGetService(out var svc) ? svc : throw new NullReferenceException("Plugin Manager service not found!"); - public ILuaScriptManagementService LuaScriptService => _servicesProvider.TryGetService(out var svc) + public ILuaScriptManagementService LuaScriptManagementService => _servicesProvider.TryGetService(out var svc) ? svc : throw new NullReferenceException("Lua Script Manager service not found!"); public ILocalizationService LocalizationService => _servicesProvider.TryGetService(out var svc) ? svc : throw new NullReferenceException("Localization Manager service not found!"); @@ -187,17 +134,42 @@ namespace Barotrauma /// public IConfigEntry DisableErrorGUIOverlay { get; private set; } - /// - /// [Experimental] Whether multithreading should be used for loading. - /// - public IConfigEntry EnableThreadedLoading { get; private set; } - /// /// Whether usernames are anonymized or show in logs. /// public IConfigEntry HideUserNamesInLogs { get; private set; } - private IConfigEntry LuaForBarotraumaSteamId { get; set; } + /// + /// The SteamId of the Workshop LuaCs CPackage in use, if available. + /// + public IConfigEntry LuaForBarotraumaSteamId { get; private set; } + + /// + /// The SteamId of the Workshop LuaCs CsForBarotrauma add-on, if available. + /// + public IConfigEntry CsForBarotraumaSteamId { get; private set; } + + /// + /// Whether to (re)load all package assets when a lobby starts/code session begins. + /// Intended for development use, or when packages are expected to change outside of External Updates (ie. Steam Workshop). + /// + public IConfigEntry ReloadPackagesOnLobbyStart { get; private set; } + + /** + * == Ops Vars + */ + private RunState _runState; + /// + /// The current run state of all services managed by LuaCs. + /// + public RunState CurrentRunState => _runState; + + private bool CPacksParsed => CurrentRunState >= RunState.Parsed; + private bool IsStaticAssetsLoaded => CurrentRunState >= RunState.Configuration; + private bool IsCodeRunning => CurrentRunState >= RunState.Running; + + private readonly ConcurrentQueue _toLoad = new(); + private readonly ConcurrentQueue _toUnload = new(); #endregion @@ -208,13 +180,6 @@ namespace Barotrauma #endregion - /// - /// Whether mod content is loaded and being executed. - /// - public bool IsModContentRunning { get; private set; } - - public readonly ContentPackageId LuaForBarotraumaId; - public static bool IsRunningInsideWorkshop { get @@ -227,84 +192,16 @@ namespace Barotrauma } } - /*public Script Lua { get; private set; } - public LuaScriptLoader LuaScriptLoader { get; private set; } - - public LuaGame Game { get; private set; } - public LuaCsHook Hook { get; private set; } - public LuaCsTimer Timer { get; private set; } - public LuaCsNetworking Networking { get; private set; } - public LuaCsSteam Steam { get; private set; } - - // must be available at anytime - private static AssemblyManager _assemblyManager; - public static AssemblyManager AssemblyManager => _assemblyManager ??= new AssemblyManager(); - - private CsPackageManager _pluginPackageManager; - public CsPackageManager PluginPackageManager => _pluginPackageManager ??= new CsPackageManager(AssemblyManager, this); - private LuaRequire Require { get; set; } - public LuaCsSetupConfig Config { get; private set; } - public MoonSharpVsCodeDebugServer DebugServer { get; private set; } - public bool IsInitialized { get; private set; }*/ - - private bool ShouldRunCs - { - get - { -#if SERVER - if (GetPackage(CsForBarotraumaId, false, false) != null && GameMain.Server.ServerPeer is LidgrenServerPeer) { return true; } -#endif - return IsCsEnabled.Value; - } - } - + private partial bool ShouldRunCs(); - [Obsolete("Use AssemblyManager::GetTypesByName()")] + // TODO: Rework + [Obsolete("Use IPluginManagementService::GetTypesByName()")] public static Type GetType(string typeName, bool throwOnError = false, bool ignoreCase = false) { throw new NotImplementedException(); //return AssemblyManager.GetTypesByName(typeName).FirstOrDefault((Type)null); } - - // Old config ref - /*public void ReadSettings() - { - Config = new LuaCsSetupConfig(); - - if (File.Exists(configFileName)) - { - try - { - using (var file = File.Open(configFileName, FileMode.Open, FileAccess.Read)) - { - XDocument document = XDocument.Load(file); - Config.EnableCsScripting = document.Root.GetAttributeBool("EnableCsScripting", Config.EnableCsScripting); - Config.TreatForcedModsAsNormal = document.Root.GetAttributeBool("TreatForcedModsAsNormal", Config.TreatForcedModsAsNormal); - Config.PreferToUseWorkshopLuaSetup = document.Root.GetAttributeBool("PreferToUseWorkshopLuaSetup", Config.PreferToUseWorkshopLuaSetup); - Config.DisableErrorGUIOverlay = document.Root.GetAttributeBool("DisableErrorGUIOverlay", Config.DisableErrorGUIOverlay); - Config.HideUserNames = document.Root.GetAttributeBool("HideUserNames", Config.HideUserNames); - } - } - catch (Exception e) - { - LuaCsLogger.HandleException(e, LuaCsMessageOrigin.LuaCs); - } - } - } - - public void WriteSettings() - { - XDocument document = new XDocument(); - document.Add(new XElement("LuaCsSetupConfig")); - document.Root.SetAttributeValue("EnableCsScripting", Config.EnableCsScripting); - document.Root.SetAttributeValue("EnableCsScripting", Config.EnableCsScripting); - document.Root.SetAttributeValue("TreatForcedModsAsNormal", Config.TreatForcedModsAsNormal); - document.Root.SetAttributeValue("PreferToUseWorkshopLuaSetup", Config.PreferToUseWorkshopLuaSetup); - document.Root.SetAttributeValue("DisableErrorGUIOverlay", Config.DisableErrorGUIOverlay); - document.Root.SetAttributeValue("HideUserNames", Config.HideUserNames); - document.Save(configFileName); - }*/ public static ContentPackage GetPackage(ContentPackageId id, bool fallbackToAll = true, bool useBackup = false) { @@ -349,111 +246,495 @@ namespace Barotrauma return null; } - // Old code ref - /*private DynValue DoFile(string file, Table globalContext = null, string codeStringFriendly = null) - { - if (!LuaCsFile.CanReadFromPath(file)) - { - throw new ScriptRuntimeException($"dofile: File access to {file} not allowed."); - } - - if (!LuaCsFile.Exists(file)) - { - throw new ScriptRuntimeException($"dofile: File {file} not found."); - } - - return Lua.DoFile(file, globalContext, codeStringFriendly); - } - - private DynValue LoadFile(string file, Table globalContext = null, string codeStringFriendly = null) - { - if (!LuaCsFile.CanReadFromPath(file)) - { - throw new ScriptRuntimeException($"loadfile: File access to {file} not allowed."); - } - - if (!LuaCsFile.Exists(file)) - { - throw new ScriptRuntimeException($"loadfile: File {file} not found."); - } - - return Lua.LoadFile(file, globalContext, codeStringFriendly); - } - - public DynValue CallLuaFunction(object function, params object[] args) - { - // XXX: `lua` might be null if `LuaCsSetup.Stop()` is called while - // a patched function is still running. - if (Lua == null) { return null; } - - lock (Lua) - { - try - { - return Lua.Call(function, args); - } - catch (Exception e) - { - LuaCsLogger.HandleException(e, LuaCsMessageOrigin.LuaMod); - } - return null; - } - } - - private void SetModulePaths(string[] str) - { - LuaScriptLoader.ModulePaths = str; - } - - public void Update() - { - Timer?.Update(); - Steam?.Update(); - -#if CLIENT - Stopwatch luaSw = new Stopwatch(); - luaSw.Start(); -#endif - Hook?.Call("think"); -#if CLIENT - luaSw.Stop(); - GameMain.PerformanceCounter.AddElapsedTicks("Think Hook", luaSw.ElapsedTicks); -#endif - } - - */ - public void Stop() - { - - - IsInitialized = false; - } - - public void Initialize(bool forceEnableCs = false) - { - if (IsInitialized) - { - Stop(); - } - - IsInitialized = true; - - Logger.Log($"Initializing LuaCs, git revision = {AssemblyInfo.GitRevision}"); - } - - public void Update() - { - throw new NotImplementedException(); - } - - public void Reset() - { - throw new NotImplementedException(); - } - public void Dispose() { - // TODO release managed resources here + try + { + SetRunState(RunState.Unloaded); + } + catch (Exception e) + { + Logger.LogError(e.Message); + } + + try + { + DisposeLuaCsConfig(); + + PluginManagementService.Dispose(); + LuaScriptManagementService.Dispose(); +#if CLIENT + StylesManagementService.Dispose(); +#endif + ConfigService.Dispose(); + LocalizationService.Dispose(); + PackageManagementService.Dispose(); + // TODO: Add all missing services. + //NetworkingService.Dispose(); + EventService.Dispose(); + + _servicesProvider.DisposeAndReset(); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + + GC.SuppressFinalize(this); + } + + /// + /// Handles changes in game states tracked by screen changes. + /// + /// The new game screen. + public partial void OnScreenSelected(Screen screen); + + public void OnAllPackageListChanged(IEnumerable corePackages, IEnumerable regularPackages) + { + UpdateLoadedPackagesList(); + } + + public void OnEnabledPackageListChanged(CorePackage corePackage, IEnumerable regularPackages) + { + UpdateLoadedPackagesList(); + } + + private void UpdateLoadedPackagesList() + { + var newPackSet = ContentPackageManager.AllPackages + .Union(ContentPackageManager.EnabledPackages.All) + .ToHashSet(); + var currPackSet = PackageManagementService.GetAllLoadedPackages().ToHashSet(); + var toAdd = newPackSet.Except(currPackSet); + var toRemove = currPackSet.Except(newPackSet); + foreach (var package in toAdd) + _toLoad.Enqueue(package); + foreach (var package in toRemove) + _toUnload.Enqueue(package); + + + ProcessPackagesListDifferences(); + } + + void ProcessPackagesListDifferences() + { + if (IsCodeRunning) + return; + + // no reason to do anything if we're fully unloaded. + if (!CPacksParsed) + { + _toLoad.Clear(); + _toUnload.Clear(); + } + + while (_toUnload.TryDequeue(out var cp)) + { + LuaScriptManagementService.DisposePackageResources(cp); + ConfigService.DisposeConfigsProfiles(cp); + ConfigService.DisposeConfigs(cp); +#if CLIENT + StylesManagementService.DisposeStylesForPackage(cp); +#endif + LocalizationService.DisposePackage(cp); + PackageManagementService.DisposePackageInfos(cp); + } + + var ls = new List(); + + while (_toLoad.TryDequeue(out var cp)) + { + if (PackageManagementService.LoadPackageInfosAsync(cp).GetAwaiter().GetResult() is + { IsFailed: true } failure) + { + Logger.LogError($"Failed to load package infos for {cp.Name}"); + Logger.LogResults(failure); + continue; + } + + ls.Add(cp); + } + + if (ls.Any()) + { + LoadStaticAssetsAsync(ls).GetAwaiter().GetResult(); + } + } + + void SetRunState(RunState runState) + { + if (CurrentRunState == runState) + return; + if (runState > CurrentRunState) + { + if (CurrentRunState < RunState.Parsed) + LoadCurrentContentPackageInfos(); + + if (runState <= CurrentRunState) + return; + + if (CurrentRunState < RunState.Configuration) + LoadStaticAssets(); + + if (runState <= CurrentRunState) + return; + + if (CurrentRunState < RunState.Running) + RunScripts(); + } + else if (runState < CurrentRunState) + { + if (CurrentRunState >= RunState.Running) + { + StopScripts(); + ProcessPackagesListDifferences(); + _runState = RunState.Configuration; + } + + if (runState >= CurrentRunState) + return; + + if (CurrentRunState == RunState.Configuration) + { + UnloadStaticAssets(); + _runState = RunState.Parsed; + } + + if (runState >= CurrentRunState) + return; + + if (CurrentRunState == RunState.Parsed) + { + UnloadContentPackageInfos(); + _runState = RunState.Unloaded; + } + + // we should be unloaded completely now | RunState.Unloaded + } + } + + void LoadCurrentContentPackageInfos() + { + if (CurrentRunState >= RunState.Parsed) + return; + + // load core + var result1 = PackageManagementService.LoadPackageInfosAsync(ContentPackageManager.VanillaCorePackage) + .GetAwaiter().GetResult(); + if (result1.IsFailed) + { + Logger.LogError($"Unable to load LuaCs CorePackage resources! Running in degraded mode."); + Logger.LogResults(result1); + } + + // load regular + var list = ContentPackageManager.RegularPackages + .Union(ContentPackageManager.EnabledPackages.All) + .ToImmutableList(); + + LoadContentPackagesInfos(list); + + if (CurrentRunState < RunState.Parsed) + _runState = RunState.Parsed; + } + + void LoadContentPackagesInfos(IReadOnlyList packages) + { + var result2 = PackageManagementService.LoadPackagesInfosAsync(packages) + .GetAwaiter().GetResult(); + + foreach (var entry in result2) + { + if (entry.Item2.IsSuccess) + Logger.LogMessage($"Successfully parsed package: {entry.Item1.Name}"); + else if (entry.Item2.IsFailed) + Logger.LogResults(entry.Item2); + } + } + + void LoadStaticAssets() + { + if (CurrentRunState < RunState.Parsed) + { + throw new InvalidOperationException($"{nameof(LoadStaticAssets)} cannot load assets in the '{CurrentRunState}' state."); + } + + if (CurrentRunState >= RunState.Configuration) + return; + + while (_toUnload.TryDequeue(out var cp)) + PackageManagementService.DisposePackageInfos(cp); + + LoadStaticAssetsAsync(PackageManagementService.GetAllLoadedPackages()).GetAwaiter().GetResult(); + LoadLuaCsConfig(); + + if (CurrentRunState < RunState.Configuration) + _runState = RunState.Configuration; + } + + void LoadLuaCsConfig() + { + IsCsEnabled = ConfigService.GetConfig>(ContentPackageManager.VanillaCorePackage, "IsCsEnabled") + ?? throw new NullReferenceException($"{nameof(IsCsEnabled)} cannot be loaded."); + TreatForcedModsAsNormal = ConfigService.GetConfig>(ContentPackageManager.VanillaCorePackage, "TreatForcedModsAsNormal") + ?? throw new NullReferenceException($"{nameof(TreatForcedModsAsNormal)} cannot be loaded."); + DisableErrorGUIOverlay = ConfigService.GetConfig>(ContentPackageManager.VanillaCorePackage, "DisableErrorGUIOverlay") + ?? throw new NullReferenceException($"{nameof(DisableErrorGUIOverlay)} cannot be loaded."); + HideUserNamesInLogs = ConfigService.GetConfig>(ContentPackageManager.VanillaCorePackage, "HideUserNamesInLogs") + ?? throw new NullReferenceException($"{nameof(HideUserNamesInLogs)} cannot be loaded."); + LuaForBarotraumaSteamId = ConfigService.GetConfig>(ContentPackageManager.VanillaCorePackage, "LuaForBarotraumaSteamId") + ?? throw new NullReferenceException($"{nameof(LuaForBarotraumaSteamId)} cannot be loaded."); + CsForBarotraumaSteamId = ConfigService.GetConfig>(ContentPackageManager.VanillaCorePackage, "CsForBarotraumaSteamId") + ?? throw new NullReferenceException($"{nameof(CsForBarotraumaSteamId)} cannot be loaded."); + } + + void DisposeLuaCsConfig() + { + IsCsEnabled = null; + TreatForcedModsAsNormal = null; + DisableErrorGUIOverlay = null; + HideUserNamesInLogs = null; + LuaForBarotraumaSteamId = null; + CsForBarotraumaSteamId = null; + } + + async Task LoadStaticAssetsAsync(IReadOnlyList packages) + { + var locRes = ImmutableArray.Empty; + var cfgRes = ImmutableArray.Empty; + var cfpRes = ImmutableArray.Empty; + var luaRes = ImmutableArray.Empty; + +#if CLIENT + var styleRes = ImmutableArray.Empty; +#endif + + var tasksBuilder = ImmutableArray.CreateBuilder(); + + //---- get resource infos + tasksBuilder.AddRange(new Func(async () => + { + var res = await PackageManagementService.GetLocalizationsInfosAsync(packages); + if (res.IsSuccess) + locRes = res.Value.Localizations; + if (res.Errors.Any()) + ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state), + res.ToResult()); + })(), + new Func(async () => + { + var res = await PackageManagementService.GetConfigsInfosAsync(packages); + if (res.IsSuccess) + cfgRes = res.Value.Configs; + if (res.Errors.Any()) + ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state), + res.ToResult()); + })(), + new Func(async () => + { + var res = await PackageManagementService.GetConfigProfilesInfosAsync(packages); + if (res.IsSuccess) + cfpRes = res.Value.ConfigProfiles; + if (res.Errors.Any()) + ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state), + res.ToResult()); + })(), + new Func(async () => + { + var res = await PackageManagementService.GetLuaScriptsInfosAsync(packages); + if (res.IsSuccess) + luaRes = res.Value.LuaScripts; + if (res.Errors.Any()) + ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state), + res.ToResult()); + })()); + +#if CLIENT + tasksBuilder.Add(new Func(async () => + { + var res = await PackageManagementService.GetStylesInfosAsync(packages); + if (res.IsSuccess) + styleRes = res.Value.StylesResourceInfos; + if (res.Errors.Any()) + ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state), + res.ToResult()); + })()); +#endif + await Task.WhenAll(tasksBuilder.MoveToImmutable()); + tasksBuilder.Clear(); + + //---- load resources + tasksBuilder.AddRange(new Func(async () => + { + var res = await ConfigService.LoadConfigsAsync(cfgRes); + if (res.Errors.Any()) + Logger.LogResults(res); + res = await ConfigService.LoadConfigsProfilesAsync(cfpRes); + if (res.Errors.Any()) + Logger.LogResults(res); + })(), + new Func(async () => + { + var res = await LuaScriptManagementService.LoadScriptResourcesAsync(luaRes); + if (res.Errors.Any()) + Logger.LogResults(res); + })()); + +#if CLIENT + tasksBuilder.Add(new Func(async () => + { + var res = await StylesManagementService.LoadStylesAsync(styleRes); + if (res.Errors.Any()) + Logger.LogResults(res); + })()); +#endif + + // load localizations first + if (!locRes.IsDefaultOrEmpty) + { + var res = await LocalizationService.LoadLocalizations(locRes); + if (res.Errors.Any()) + Logger.LogResults(res); + } + + await Task.WhenAll(tasksBuilder.MoveToImmutable()); + } + + void RunScripts() + { + if (!IsStaticAssetsLoaded) + { + throw new InvalidOperationException($"{nameof(RunScripts)} cannot load assets in the '{CurrentRunState}' state."); + } + + if (CurrentRunState >= RunState.Running) + return; + + if (ShouldRunCs()) + { + var asmRes = + PackageManagementService.GetAssembliesInfos(PackageManagementService + .GetAllLoadedPackages() + .Where(ContentPackageManager.EnabledPackages.All.Contains) + .ToList()); + if (asmRes.IsFailed) + { + Logger.LogError($"{nameof(RunScripts)}: Errors will retrieving assembly resources, cannot load scripts!"); + Logger.LogResults(asmRes.ToResult()); + return; + } + var res = PluginManagementService.LoadAssemblyResources(asmRes.Value.Assemblies); + if (res.IsFailed) + { + Logger.LogError($"{nameof(RunScripts)}: Failed to initialize scripts!"); + Logger.LogResults(res.ToResult()); + } + else + { + if (res.Errors.Any()) + Logger.LogResults(res.ToResult()); + if (PluginManagementService.GetImplementingTypes() is {IsSuccess: true} types) + { + var typeInst = PluginManagementService.ActivateTypeInstances(types.Value, true, true); + foreach (var loadRes in typeInst) + { + if (loadRes is { IsSuccess: true, Value: { Item2: { } pluginInstance } }) + { + EventService.Subscribe(pluginInstance); + EventService.Subscribe(pluginInstance); + EventService.Subscribe(pluginInstance); + } + else + { + Logger.LogResults(loadRes.ToResult()); + } + } + + EventService.PublishEvent(sub => sub.PreInitPatching()); + EventService.PublishEvent(sub => sub.Initialize()); + EventService.PublishEvent(sub => sub.OnLoadCompleted()); + } + } + } + + //lua + var luaRes = PackageManagementService.GetLuaScriptsInfos(PackageManagementService + .GetAllLoadedPackages() + .Where(ContentPackageManager.EnabledPackages.All.Contains) + .ToList()); + if (luaRes.IsFailed) + { + Logger.LogError($"{nameof(RunScripts)}: Failed to get enabled lua script resources!"); + Logger.LogResults(luaRes.ToResult()); + return; + } + + if (luaRes.Errors.Any()) + Logger.LogResults(luaRes.ToResult()); + + + LuaScriptManagementService.ExecuteLoadedScripts(luaRes.Value.LuaScripts); + + if (CurrentRunState < RunState.Running) + _runState = RunState.Running; + } + + void UnloadContentPackageInfos() + { + if (IsStaticAssetsLoaded) + { + throw new InvalidOperationException($"{nameof(UnloadStaticAssets)}: Cannot unload static assets when the current run state is {CurrentRunState}."); + } + + PackageManagementService.Reset(); + _toUnload.Clear(); + } + + void UnloadStaticAssets() + { + if (IsCodeRunning) + { + throw new InvalidOperationException($"{nameof(UnloadStaticAssets)}: Cannot unload static assets when the current run state is {CurrentRunState}."); + } + + PluginManagementService.Reset(); + LuaScriptManagementService.Reset(); + ConfigService.Reset(); +#if CLIENT + StylesManagementService.Reset(); +#endif + LocalizationService.Reset(); + + if (CurrentRunState >= RunState.Configuration) + { + _runState = RunState.Parsed; + } + } + + void StopScripts() + { + EventService.ClearAllSubscribers(); + LuaScriptManagementService.UnloadActiveScripts(); + PluginManagementService.UnloadAllAssemblyResources(); + SubscribeToLuaCsEvents(); + + if (IsCodeRunning) + { + _runState = RunState.Configuration; + } } } + + /// + /// Specifies the current run state of the LuaCs Modding System. + /// [Important]Enum State values ordering must be in the form of (lower state) === (higher state) + /// + public enum RunState : byte + { + Unloaded = 0, // No asset data loaded. + Parsed, // CPacks' ResourceInfos are parsed. + Configuration, // localization and configuration assets loaded. + Running // all assets loaded, code running. + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsUtility.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsUtility.cs index 6ed7c87de..f801a23ef 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsUtility.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsUtility.cs @@ -60,7 +60,8 @@ namespace Barotrauma foreach (var package in ContentPackageManager.AllPackages) { - if (package.UgcId.ValueEquals(LuaCsSetup.LuaForBarotraumaId) && pathStartsWith(getFullPath(package.Path))) + if (package.UgcId.ValueEquals(new SteamWorkshopId(GameMain.LuaCs.LuaForBarotraumaSteamId?.Value ?? 0ul)) + && pathStartsWith(getFullPath(package.Path))) { return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs index 59a1e983f..c0cde07dc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs @@ -83,6 +83,7 @@ public class EventService : IEventService, IEventAssemblyContextUnloading public EventService(Lazy pluginManagementService) { _pluginManagementService = pluginManagementService ?? throw new ArgumentNullException(nameof(pluginManagementService)); + this.Subscribe(this); } public bool IsDisposed { get; private set; } = false; @@ -301,8 +302,19 @@ public class EventService : IEventService, IEventAssemblyContextUnloading dict.Remove(OneOf.FromT1(subscriber)); } - public void ClearAllEventSubscribers() where T : IEvent => _subscriptions.Remove(typeof(T)); - public void ClearAllSubscribers() => _subscriptions.Clear(); + public void ClearAllEventSubscribers() where T : IEvent + { + _subscriptions.Remove(typeof(T)); + if (typeof(IEventAssemblyContextUnloading) == typeof(T)) + { + this.Subscribe(this); + } + } + public void ClearAllSubscribers() + { + _subscriptions.Clear(); + this.Subscribe(this); + } public FluentResults.Result PublishEvent(Action action) where T : IEvent { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs index a551aca9f..7ad55c0e7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs @@ -151,4 +151,6 @@ public partial class LoggerService : ILoggerService public void Dispose() { } public FluentResults.Result Reset() => FluentResults.Result.Ok(); + + public bool IsDisposed { get; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs new file mode 100644 index 000000000..aa0c74610 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs @@ -0,0 +1,14 @@ +using Barotrauma.LuaCs.Data; +using MoonSharp.Interpreter; +using MoonSharp.Interpreter.Interop; +using System; +using System.Collections.Immutable; +using System.Reflection; +using System.Threading.Tasks; + +namespace Barotrauma.LuaCs.Services; + +public class LuaScriptManagementService : ILuaScriptManagementService +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptService.cs deleted file mode 100644 index eef266305..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptService.cs +++ /dev/null @@ -1,176 +0,0 @@ -using Barotrauma.LuaCs.Data; -using MoonSharp.Interpreter; -using MoonSharp.Interpreter.Interop; -using System; -using System.Collections.Immutable; -using System.Reflection; - -namespace Barotrauma.LuaCs.Services; - -public class LuaScriptService : ILuaScriptService, ILuaScriptManagementService -{ - public void AddField(IUserDataDescriptor descriptor, string fieldName, DynValue value) - { - throw new NotImplementedException(); - } - - public void AddMethod(IUserDataDescriptor descriptor, string methodName, object function) - { - throw new NotImplementedException(); - } - - public FluentResults.Result AddScriptFiles(ImmutableArray luaResource) - { - throw new System.NotImplementedException(); - } - - public object CreateEnumTable(string typeName) - { - throw new NotImplementedException(); - } - - public object CreateStatic(string typeName) - { - throw new NotImplementedException(); - } - - public DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor descriptor) - { - throw new NotImplementedException(); - } - - public DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType) - { - throw new NotImplementedException(); - } - - public void Dispose() - { - throw new System.NotImplementedException(); - } - - public FluentResults.Result ExecuteLoadedScripts(ContentPackage package, bool pauseExecutionOnError = false, bool verboseLogging = false) - { - throw new NotImplementedException(); - } - - public FluentResults.Result ExecuteLoadedScripts(ImmutableArray scripts, bool pauseExecutionOnError = false, bool verboseLogging = false) - { - throw new NotImplementedException(); - } - - public FluentResults.Result ExecuteLoadedScripts(bool pauseExecutionOnError = false, bool verboseLogging = false) - { - throw new NotImplementedException(); - } - - public FluentResults.Result ExecuteScripts(bool pauseExecutionOnScriptError = false, bool verboseLogging = false) - { - throw new System.NotImplementedException(); - } - - public FieldInfo FindFieldRecursively(Type type, string fieldName) - { - throw new NotImplementedException(); - } - - public MethodInfo FindMethodRecursively(Type type, string methodName, Type[] types = null) - { - throw new NotImplementedException(); - } - - public PropertyInfo FindPropertyRecursively(Type type, string propertyName) - { - throw new NotImplementedException(); - } - - public ImmutableArray GetScriptResources() - { - throw new System.NotImplementedException(); - } - - public bool HasMember(object obj, string memberName) - { - throw new NotImplementedException(); - } - - public bool IsRegistered(Type type) - { - throw new NotImplementedException(); - } - - public bool IsTargetType(object obj, string typeName) - { - throw new NotImplementedException(); - } - - public void MakeFieldAccessible(IUserDataDescriptor descriptor, string fieldName) - { - throw new NotImplementedException(); - } - - public void MakeMethodAccessible(IUserDataDescriptor descriptor, string methodName, string[] parameters = null) - { - throw new NotImplementedException(); - } - - public void MakePropertyAccessible(IUserDataDescriptor descriptor, string propertyName) - { - throw new NotImplementedException(); - } - - public IUserDataDescriptor RegisterGenericType(Type type) - { - throw new NotImplementedException(); - } - - public IUserDataDescriptor RegisterGenericType(string typeName, params string[] typeNameArgs) - { - throw new NotImplementedException(); - } - - public IUserDataDescriptor RegisterType(Type type) - { - throw new NotImplementedException(); - } - - public IUserDataDescriptor RegisterType(string typeName) - { - throw new NotImplementedException(); - } - - public void RemoveMember(IUserDataDescriptor descriptor, string memberName) - { - throw new NotImplementedException(); - } - - public void RemoveScriptFiles(ImmutableArray luaResource) - { - throw new System.NotImplementedException(); - } - - public FluentResults.Result Reset() - { - throw new System.NotImplementedException(); - } - - public string TypeOf(object obj) - { - throw new NotImplementedException(); - } - - public void UnregisterAllTypes() - { - throw new NotImplementedException(); - } - - public void UnregisterType(Type type) - { - throw new NotImplementedException(); - } - - public void UnregisterType(string typeName) - { - throw new NotImplementedException(); - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs index 8fda053e1..c9d31605e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs @@ -1,461 +1,13 @@ -using System; -using System.Collections.Concurrent; + using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; using System.Threading.Tasks; -using Barotrauma.Extensions; using Barotrauma.LuaCs.Data; -using Barotrauma.Steam; using FluentResults; -using FluentResults.LuaCs; -using QuikGraph; namespace Barotrauma.LuaCs.Services; public class PackageManagementService : IPackageManagementService { - private readonly Func _contentPackageServiceFactory; - private readonly Lazy _assemblyManagementService; - private readonly ConcurrentDictionary _contentPackages = new(); - private readonly ConcurrentQueue _queuedPackages = new(); - private readonly ConcurrentDictionary _packageDependencyInfos = new(); - - /// - /// ConcurrentDictionary handles access/read synchronization. This is to ensure that we are not trying to - /// access the collection during a load/unload/modify operation. - /// - private readonly ReaderWriterLockSlim _contentPackagesModificationsLock = new(); - /// - /// This lock ensures that we are not adding new entries to the queue between when we read the contents and - /// empty the buffer. - /// - private readonly ReaderWriterLockSlim _packageQueueProcessingLock = new(); - - public PackageManagementService( - Func getPackageService, - Lazy assemblyManagementService) - { - this._contentPackageServiceFactory = getPackageService; - this._assemblyManagementService = assemblyManagementService; - } - - #region STATE_RESET - - public void Dispose() - { - // TODO release managed resources here - } - - public FluentResults.Result Reset() - { - throw new NotImplementedException(); - } - - #endregion - - public void QueuePackages(ImmutableArray packages) - { - _packageQueueProcessingLock.EnterReadLock(); - try - { - foreach (LoadablePackage package in packages) - _queuedPackages.Enqueue(package); - } - finally - { - _packageQueueProcessingLock.ExitReadLock(); - } - } - - public FluentResults.Result ParseQueuedPackages(bool loadParallel = true, bool reportFailOnDuplicates = false) - { - if (!ModUtils.Environment.IsMainThread) - throw new InvalidOperationException($"{nameof(ParseQueuedPackages)}: This method can only be called on the main thread."); - - ImmutableArray packagesToProcess = ImmutableArray.Empty; - - _packageQueueProcessingLock.EnterWriteLock(); - try - { - Interlocked.MemoryBarrier(); - if (_queuedPackages.IsEmpty) - return FluentResults.Result.Ok().WithSuccess($"{nameof(ParseQueuedPackages)}: The Queue is empty."); - packagesToProcess = _queuedPackages.Where(p => p.Package is not null) - .Distinct().ToImmutableArray(); - _queuedPackages.Clear(); - } - finally - { - _packageQueueProcessingLock.ExitWriteLock(); - } - - FluentResults.Result[] loadResults = new FluentResults.Result[packagesToProcess.Length]; - FluentResults.Result res = new FluentResults.Result(); - - // Load ModConfigInfo - _contentPackagesModificationsLock.EnterWriteLock(); - try - { - Stopwatch stopwatch = new Stopwatch(); - stopwatch.Start(); - - Interlocked.MemoryBarrier(); - if (loadParallel) - { - Parallel.For(0, loadResults.Length, new ParallelOptions() - { - /* - * This is an IO-bound operation. The purpose of parallelism here is to allow loaded package - * data to be processed while another package is waiting on the storage device for its info. - */ - MaxDegreeOfParallelism = 2 - },i => - { - loadResults[i] = LoadPackageInfo(packagesToProcess[i]); - }); - } - else - { - for (int i = 0; i < loadResults.Length; i++) - { - loadResults[i] = LoadPackageInfo(packagesToProcess[i]); - } - } - - stopwatch.Stop(); - - res.WithSuccess(new Success( - $"Completed parsing of {loadResults.Length} packages in {stopwatch.ElapsedMilliseconds} milliseconds.")); - - for (int i = 0; i < loadResults.Length; i++) - { - res = loadResults[i].IsSuccess - ? res.WithSuccesses(loadResults[i].Successes) - : res.WithErrors(loadResults[i].Errors); - } - - return res; - } - catch (AggregateException ae) - { - return FluentResults.Result.Fail(new Error($"{nameof(ParseQueuedPackages)}: Failed to load packages! AE.") - .WithMetadata(MetadataType.ExceptionDetails, ae.InnerException?.Message ?? ae.Message) - .WithMetadata(MetadataType.StackTrace, ae.StackTrace) - .WithMetadata(MetadataType.ExceptionObject, this)); - } - catch (ArgumentNullException ane) - { - return FluentResults.Result.Fail( - new Error($"{nameof(ParseQueuedPackages)}: Failed to load packages! ANE.") - .WithMetadata(MetadataType.ExceptionDetails, ane.InnerException?.Message ?? ane.Message) - .WithMetadata(MetadataType.StackTrace, ane.StackTrace) - .WithMetadata(MetadataType.ExceptionObject, this)); - } - finally - { - _contentPackagesModificationsLock.ExitWriteLock(); - } - - - /* - * Helper functions - */ - - // register in the list so we can check against it. - FluentResults.Result LoadPackageInfo(LoadablePackage package) - { - try - { - if (package.Package == null) - { - return FluentResults.Result.Fail( - new Error($"{nameof(LoadPackageInfo)}: Package is null!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, package)); - } - - if (_contentPackages.TryGetValue(package.Package, out var packageService)) - { - if (reportFailOnDuplicates) - { - return FluentResults.Result.Fail(new Error($"The package {package.Package?.Name} is already loaded.") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, package.Package)); - } - - return FluentResults.Result.Ok(); - } - - packageService = _contentPackageServiceFactory.Invoke(); - _contentPackages[package.Package] = packageService; - return packageService.LoadResourcesInfo(package); - } - catch (NullReferenceException nre) - { - return FluentResults.Result.Fail(new Error($"{nameof(LoadPackageInfo)}: NRE while loading package {package.Package?.Name}!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.StackTrace, nre.StackTrace ?? "StackTrace not available") - .WithMetadata(MetadataType.ExceptionDetails, nre.InnerException?.Message ?? nre.Message) - .WithMetadata(MetadataType.RootObject, package)); - } - } - } - - public FluentResults.Result LoadPackageConfigsResourcesGroup(bool loadParallel = true) - { - throw new NotImplementedException(); - } - - public FluentResults.Result LoadAllPackageResources(bool loadParallel = true, bool safeResourcesOnly = true) - { - throw new NotImplementedException(); - } - - public FluentResults.Result UnloadPackages() - { - if (!ModUtils.Environment.IsMainThread) - { - return FluentResults.Result.Fail( - new ExceptionalError(new InvalidOperationException($"{nameof(UnloadPackages)}: This method can only be called on the main thread.")) - .WithMetadata(MetadataType.ExceptionObject, this)); - } - - var res = new FluentResults.Result(); - _contentPackagesModificationsLock.EnterWriteLock(); - try - { - // TODO: Finish him - } - finally - { - _contentPackagesModificationsLock.ExitWriteLock(); - } - - throw new NotImplementedException(); - } - - public bool IsPackageLoaded(ContentPackage package) => package is not null && _contentPackages.ContainsKey(package); - - public bool CheckDependencyLoaded(IPackageDependencyInfo info) => - info is not null && IsPackageLoaded(info.DependencyPackage); - - public bool CheckDependenciesLoaded([NotNull]IEnumerable infos, out ImmutableArray missingPackages) - { - var missing = ImmutableArray.CreateBuilder(); - missing.AddRange(infos - .Where(i => i.DependencyPackage is not null) - .DistinctBy(i => i.DependencyPackage) - .Where(i => !CheckDependencyLoaded(i))); - missingPackages = missing.MoveToImmutable(); - return missingPackages.Length == 0; - } - - public bool CheckEnvironmentSupported(IPlatformInfo platform) - { - return (platform.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 - && (platform.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0; - } - - public Result GetPackageDependencyInfoRecord(ContentPackage package, bool addIfMissing = false) - { - if (package is null) - { - return new FluentResults.Result() - .WithError(new Error($"{nameof(GetPackageDependencyInfoRecord)}: Package is null!") - .WithMetadata(MetadataType.ExceptionObject, this)); - } - - if (_packageDependencyInfos.TryGetValue(package, out var result)) - { - return new FluentResults.Result() - .WithValue(result); - } - - if (addIfMissing) - { - return AddDependencyRecord(package, package.Name, package.Path, - package.TryExtractSteamWorkshopId(out var id) ? id.Value : 0, - false); - } - - return FluentResults.Result.Fail(new Error($"Could not find package {package.Name}!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, package)); - } - - public Result GetPackageDependencyInfoRecord(ulong steamWorkshopId, string packageName, string folderPath = null, - bool addIfMissing = false) - { - if (packageName.IsNullOrWhiteSpace() || folderPath.IsNullOrWhiteSpace()) - { - return new FluentResults.Result() - .WithError(new Error($"{nameof(GetPackageDependencyInfoRecord)}: folder path and/or package name are null!") - .WithMetadata(MetadataType.ExceptionObject, this)); - } - - if (_packageDependencyInfos.TryGetValue((packageName,steamWorkshopId,folderPath), out var result)) - { - return new FluentResults.Result() - .WithValue(result); - } - - // TODO: Finish this - throw new NotImplementedException(); - } - - public Result GetPackageDependencyInfoRecord(string folderPath) - { - throw new NotImplementedException(); - } - - - public IPackageDependencyInfo CreateOrphanPackageDependencyInfoRecord( - string packageName, - string packagePath, - ulong steamWorkshopId) - { - return new DependencyInfo() - { - DependencyPackage = null, - FallbackPackageName = packageName, - FolderPath = packagePath.IsNullOrWhiteSpace() ? null : System.IO.Path.GetFullPath(packagePath), - SteamWorkshopId = steamWorkshopId, - IsMissing = true, - IsWorkshopInstallation = false - }; - } - - private Result AddDependencyRecord( - ContentPackage package, - string packageName, - string folderPath, - ulong steamWorkshopId, - bool isMissing) - { - // TODO: Redo - try - { - var dependencyInfo = new DependencyInfo() - { - DependencyPackage = package, - FallbackPackageName = packageName, - FolderPath = System.IO.Path.GetFullPath(folderPath), - SteamWorkshopId = steamWorkshopId, - IsMissing = isMissing, - IsWorkshopInstallation = steamWorkshopId != 0 - }; - if (package is not null) - { - _packageDependencyInfos.AddOrUpdate(package, pack => dependencyInfo, - (pack, dep) => dependencyInfo); - } - return new FluentResults.Result() - .WithValue(dependencyInfo) - .WithSuccess($"New value created."); - } - catch (Exception ex) - { - return new FluentResults.Result() - .WithError(new ExceptionalError(ex) - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.ExceptionDetails, ex.Message) - .WithMetadata(MetadataType.RootObject, package) - .WithMetadata(MetadataType.StackTrace, ex.StackTrace ?? "StackTrace not available")); - } - } - - private readonly record struct DependencyEntryKey : IEqualityComparer, IEquatable - { - public ContentPackage Package { get; init; } - public string FolderPath { get; init; } - public string PackageName { get; init; } - public ulong SteamWorkshopId { get; init; } - - public DependencyEntryKey(ContentPackage package) - { - Package = package ?? throw new ArgumentNullException(nameof(package), $"{nameof(DependencyEntryKey)}.ctor: Package cannot be null!"); - PackageName = package.Name; - SteamWorkshopId = package.TryExtractSteamWorkshopId(out var id) ? id.Value : (ulong)0; - FolderPath = package.Path; - } - - public DependencyEntryKey(string packageName, string folderPath, ulong steamWorkshopId) - { - PackageName = packageName; - SteamWorkshopId = steamWorkshopId; - FolderPath = folderPath; - Package = null; - } - - public DependencyEntryKey(string packageName, ulong steamWorkshopId) - { - PackageName = packageName; - SteamWorkshopId = steamWorkshopId; - FolderPath = null; - Package = null; - } - - public bool Equals(DependencyEntryKey other) - { - return Equals(this, other); - } - - public override int GetHashCode() - { - return GetHashCode(this); - } - - public bool Equals(DependencyEntryKey x, DependencyEntryKey y) - { - if (x == y) - return true; - - if (x.Package is not null && y.Package is not null && x.Package == Package) - return true; - - // folder should be a unique key if not unset. - if (!x.FolderPath.IsNullOrWhiteSpace() && !y.FolderPath.IsNullOrWhiteSpace() && - x.FolderPath == FolderPath) - return true; - - if (!x.PackageName.IsNullOrWhiteSpace() && !y.PackageName.IsNullOrWhiteSpace() - && x.SteamWorkshopId != 0 && y.SteamWorkshopId != 0) - return x.PackageName == y.PackageName && x.SteamWorkshopId == y.SteamWorkshopId; - - if (!x.PackageName.IsNullOrWhiteSpace() && !y.PackageName.IsNullOrWhiteSpace() && x.PackageName == PackageName) - return true; - - if (x.SteamWorkshopId != 0 && y.SteamWorkshopId != 0 && - x.SteamWorkshopId == y.SteamWorkshopId) - return true; - - return false; - } - - public int GetHashCode(DependencyEntryKey obj) - { - if (!obj.PackageName.IsNullOrWhiteSpace()) - return obj.PackageName.GetHashCode(); - if (obj.SteamWorkshopId != 0) - return obj.SteamWorkshopId.GetHashCode(); - if (obj.Package is not null) - return obj.Package.GetHashCode(); - // We don't want to check the FolderPath because we want to resolve dependencies using packages - // that might be local instead in the workshop folder. - return 2342568; // random const value: collisions are fine as we want to call Equals() - } - - public static implicit operator DependencyEntryKey(ContentPackage package) => new(package); - public static implicit operator DependencyEntryKey((string packageName, ulong steamWorkshopId) tuple1) => - new (tuple1.packageName, tuple1.steamWorkshopId); - public static implicit operator DependencyEntryKey((string packageName, ulong steamWorkshopId, string folderPath) tuple1) => - new (tuple1.packageName, tuple1.folderPath, tuple1.steamWorkshopId); - } - } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageService.cs deleted file mode 100644 index 83f133fc2..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageService.cs +++ /dev/null @@ -1,686 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using Barotrauma.Extensions; -using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Services.Processing; -using FluentResults; -using FluentResults.LuaCs; -using OneOf; - -namespace Barotrauma.LuaCs.Services; - -public partial class PackageService : IPackageService -{ - private readonly ReaderWriterLockSlim _operationsUsageLock = new(); - // only stops race conditions for pointer access - - - // mod config / package scanners/parsers - private readonly Lazy _configParserService; - private readonly Lazy _luaScriptService; - private readonly Lazy _localizationService; - private readonly Lazy _pluginService; - private readonly Lazy _configService; - private readonly IPackageManagementService _packageManagementService; - private readonly IStorageService _storageService; - private readonly ILoggerService _loggerService; - - // .ctor in server source and client source - - // state monitors - private int _configsLoaded, _localizationsLoaded, _luaScriptsLoaded, _pluginsLoaded, _isDisposed; - private int _loadingOperationsRunning; - private int _isEnabledInModList; - - public bool ConfigsLoaded - { - get => ModUtils.Threading.GetBool(ref _configsLoaded); - private set => ModUtils.Threading.SetBool(ref _configsLoaded, value); - } - public bool LocalizationsLoaded - { - get => ModUtils.Threading.GetBool(ref _localizationsLoaded); - private set => ModUtils.Threading.SetBool(ref _localizationsLoaded, value); - } - public bool LuaScriptsLoaded - { - get => ModUtils.Threading.GetBool(ref _luaScriptsLoaded); - private set => ModUtils.Threading.SetBool(ref _luaScriptsLoaded, value); - } - public bool PluginsLoaded - { - get => ModUtils.Threading.GetBool(ref _pluginsLoaded); - private set => ModUtils.Threading.SetBool(ref _pluginsLoaded, value); - } - public bool IsDisposed - { - get => ModUtils.Threading.GetBool(ref _isDisposed); - private set => ModUtils.Threading.SetBool(ref _isDisposed, value); - } - - private bool LoadingOperationsRunning - { - get => Interlocked.CompareExchange(ref _loadingOperationsRunning, 0, 0) > 0; - set // we use the set as our inc/decr - { - if (value) - { - Interlocked.Add(ref _loadingOperationsRunning, 1); - } - else - { - Interlocked.Add(ref _loadingOperationsRunning, -1); - } - } - } - - #region Member: ContentPackage - - private readonly ReaderWriterLockSlim _packageAccessLock = new(); - private ContentPackage _package; - public ContentPackage Package - { - get - { - _packageAccessLock.EnterReadLock(); - try - { - return _package; - } - finally - { - _packageAccessLock.ExitReadLock(); - } - } - private set - { - _packageAccessLock.EnterWriteLock(); - try - { - _package = value; - } - finally - { - _packageAccessLock.ExitWriteLock(); - } - } - } - - #endregion - - #region DataContracts - - #region Member: ModConfigInfo - - private readonly ReaderWriterLockSlim _modConfigUsageLock = new(); - private IModConfigInfo _modConfigInfo; - public IModConfigInfo ModConfigInfo - { - get - { - _modConfigUsageLock.EnterReadLock(); - try - { - return _modConfigInfo; - } - finally - { - _modConfigUsageLock.ExitReadLock(); - } - } - private set - { - _modConfigUsageLock.EnterWriteLock(); - try - { - _modConfigInfo = value; - } - finally - { - _modConfigUsageLock.ExitWriteLock(); - } - } - } - - public bool IsEnabledInModList - { - get => ModUtils.Threading.GetBool(ref _isEnabledInModList); - private set => ModUtils.Threading.SetBool(ref _isEnabledInModList, value); - } - - #endregion - - public ImmutableArray SupportedCultures => ModConfigInfo?.SupportedCultures ?? ImmutableArray.Empty; - public ImmutableArray Assemblies => ModConfigInfo?.Assemblies ?? ImmutableArray.Empty; - public ImmutableArray Localizations => ModConfigInfo?.Localizations ?? ImmutableArray.Empty; - public ImmutableArray LuaScripts => ModConfigInfo?.LuaScripts ?? ImmutableArray.Empty; - public ImmutableArray Configs => ModConfigInfo?.Configs ?? ImmutableArray.Empty; - public ImmutableArray ConfigProfiles => ModConfigInfo?.ConfigProfiles ?? ImmutableArray.Empty; - - #endregion - - #region PublicAPI - - public FluentResults.Result LoadResourcesInfo(LoadablePackage cpackage) - { - if (cpackage.Package == null) - { - return FluentResults.Result.Fail(new Error($"{nameof(LoadResourcesInfo)}: Package is null!") - .WithMetadata(MetadataType.ExceptionObject,this) - .WithMetadata(MetadataType.RootObject, cpackage)); - } - ContentPackage package = cpackage.Package; - - _operationsUsageLock.EnterWriteLock(); - LoadingOperationsRunning = true; - try - { - if (IsDisposed) - { - return FluentResults.Result.Fail( - new Error("Service is disposed.") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, package)); - } - - var res = _configParserService.Value.BuildConfigForPackage(package); - - if (res.IsFailed) - { - return FluentResults.Result.Fail(res.Errors) - .WithError(new Error("PackageService failed to load ModConfigInfo") - .WithMetadata(MetadataType.ExceptionObject, _configParserService) - .WithMetadata(MetadataType.RootObject, package)); - } - - this.ModConfigInfo = res.Value; - this.IsEnabledInModList = cpackage.IsEnabled; - return FluentResults.Result.Ok(); - } - catch (Exception e) - { - return FluentResults.Result.Fail(new Error(e.Message) - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, package) - .WithMetadata(MetadataType.StackTrace, e.StackTrace)); - } - finally - { - LoadingOperationsRunning = false; - _operationsUsageLock.ExitWriteLock(); - } - } - - public FluentResults.Result LoadPlugins([NotNull]IAssembliesResourcesInfo assembliesInfo, bool ignoreDependencySorting = false) - { - _operationsUsageLock.EnterReadLock(); - LoadingOperationsRunning = true; - try - { - if (CheckResourceSanitation(OneOf - .FromT0(assembliesInfo)) is { IsFailed: true } failed) - { - return failed; - } - - // Order these assemblies by internal dependencies - ImmutableArray resources; - if (ignoreDependencySorting) - { - resources = assembliesInfo.Assemblies; - } - else // sort by load order - { - resources = assembliesInfo.Assemblies - .OrderByDescending(a => a.LoadPriority) - .ToImmutableArray(); - } - - // Try loading them, throw on failure. - if (_pluginService.Value.LoadAndInstanceTypes(resources, true, out var instancedTypes) is { IsFailed: true} failed2) - { - return failed2.WithError(new Error($"{nameof(LoadPlugins)}: Failed to load plugins for {this.Package.Name}") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, assembliesInfo)); - } - - PluginsLoaded = true; - return FluentResults.Result.Ok(); - } - finally - { - LoadingOperationsRunning = false; - _operationsUsageLock.ExitReadLock(); - } - } - - public FluentResults.Result LoadLocalizations([NotNull]ILocalizationsResourcesInfo localizationsInfo) - { - _operationsUsageLock.EnterReadLock(); - LoadingOperationsRunning = true; - try - { - if (CheckResourceSanitation(OneOf - .FromT1(localizationsInfo)) is { IsFailed: true } failed) - { - return failed; - } - - if (_localizationService.Value.LoadLocalizations(localizationsInfo.Localizations) is { IsFailed: true} failed2) - { - return failed2.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load localizations") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, localizationsInfo)); - } - - LocalizationsLoaded = true; - return FluentResults.Result.Ok(); - } - finally - { - LoadingOperationsRunning = false; - _operationsUsageLock.ExitReadLock(); - } - } - - public FluentResults.Result AddLuaScripts([NotNull]ILuaScriptsResourcesInfo luaScriptsInfo) - { - _operationsUsageLock.EnterReadLock(); - LoadingOperationsRunning = true; - try - { - if (CheckResourceSanitation(OneOf - .FromT4(luaScriptsInfo)) is { IsFailed: true } failed) - { - return failed; - } - - if (_luaScriptService.Value.AddScriptFiles(luaScriptsInfo.LuaScripts) is { IsFailed: true} failed2) - { - return failed2.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load lua scripts.") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, luaScriptsInfo)); - } - - LuaScriptsLoaded = true; - return FluentResults.Result.Ok(); - } - finally - { - LoadingOperationsRunning = false; - _operationsUsageLock.ExitReadLock(); - } - } - - public FluentResults.Result LoadConfig( - [NotNull]IConfigsResourcesInfo configsResourcesInfo, - [NotNull]IConfigProfilesResourcesInfo configProfilesResourcesInfo) - { - _operationsUsageLock.EnterReadLock(); - LoadingOperationsRunning = true; - try - { - // register configs - if (CheckResourceSanitation(OneOf - .FromT2(configsResourcesInfo)) is { IsFailed: true } failed) - { - return failed; - } - - if (_configService.Value.AddConfigs(configsResourcesInfo.Configs) is { IsFailed: true} failed2) - { - return failed2.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load configs.") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, configsResourcesInfo)); - } - - // register config profiles - if (CheckResourceSanitation(OneOf - .FromT3(configProfilesResourcesInfo)) is { IsFailed: true } failed3) - { - return failed3; - } - - if (_configService.Value.AddConfigsProfiles(configProfilesResourcesInfo.ConfigProfiles) is { IsFailed: true} failed4) - { - return failed4.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load config profiles.") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, configProfilesResourcesInfo)); - } - - ConfigsLoaded = true; - return FluentResults.Result.Ok(); - } - finally - { - LoadingOperationsRunning = false; - _operationsUsageLock.ExitReadLock(); - } - } - - public void Dispose() - { - /* - * Notes: we need to unload this package from services in the order that the services are dependent on each other. - * Unloading Order: Lua Scripts > Assemblies > Config Profiles > Configs > Styles > Localizations - */ - _operationsUsageLock.EnterWriteLock(); - try - { - if (this.Package is null) - { - _loggerService.LogError( - $"Package Service: cannot Dispose of service as ContentPackage and info is not set!"); - return; - } - - if (this.ModConfigInfo is null) - { - _loggerService.LogError($"Package Service: cannot Dispose of service as ModConfigInfo is not loaded!"); - return; - } - - /* - * To be graceful, we want to ensure that any async calls and other threads are allowed to be processed before we begin - * disposal to reduce friction with other thread operations, so we release the lock and periodically check it - * to see of other threads have finished operations before cleaning everything up. - */ - - IsDisposed = true; // set stop flag, callers should handle exception cases - Interlocked.MemoryBarrier(); //ensure cache states - - DateTime timeoutLimit = DateTime.Now.AddSeconds(10); - while (LoadingOperationsRunning) - { - _operationsUsageLock.ExitWriteLock(); - Thread.Sleep(1); - _operationsUsageLock.EnterWriteLock(); - if (timeoutLimit < DateTime.Now) - { - _loggerService.LogError($"Package Service: Dispose() time out reached while waiting for other operations. Continuing."); - break; - } - } - - GC.SuppressFinalize(this); - - _luaScriptService.Value.RemoveScriptFiles(this.LuaScripts); - _pluginService.Value.DisposePlugins(); - _configService.Value.RemoveConfigsProfiles(this.ConfigProfiles); - _configService.Value.RemoveConfigs(this.Configs); -#if CLIENT - _stylesService.Value.UnloadAllStyles(); -#endif - _localizationService.Value.Remove(this.Localizations); - - ModConfigInfo = null; - Package = null; - } - catch - { - _loggerService.LogError($"Package Service: exception while running Dispose()."); - throw; - } - finally - { - _operationsUsageLock.ExitWriteLock(); - } - } - - public FluentResults.Result Reset() - { - _operationsUsageLock.EnterWriteLock(); - - try - { - if (this.Package is null) - { - return FluentResults.Result.Fail(new Error($"Package Service: cannot Dispose of service as ContentPackage and info is not set!") - .WithMetadata(MetadataType.ExceptionDetails, nameof(Reset)) - .WithMetadata(MetadataType.ExceptionObject, this)); - } - - if (this.ModConfigInfo is null) - { - return FluentResults.Result.Fail(new Error($"Package Service: cannot Dispose of service as ModConfigInfo is not set!") - .WithMetadata(MetadataType.ExceptionDetails, nameof(Reset)) - .WithMetadata(MetadataType.ExceptionObject, this)); - } - - Interlocked.MemoryBarrier(); //ensure cache states - - DateTime timeoutLimit = DateTime.Now.AddSeconds(10); - while (LoadingOperationsRunning) - { - _operationsUsageLock.ExitWriteLock(); - Thread.Sleep(1); - _operationsUsageLock.EnterWriteLock(); - if (timeoutLimit < DateTime.Now) - { - _loggerService.LogError($"Package Service: Dispose() grace time-out reached while waiting for other operations. Continuing."); - break; - } - } - - if (LuaScriptsLoaded) - { - _luaScriptService.Value.RemoveScriptFiles(this.LuaScripts); - LuaScriptsLoaded = false; - } - - if (PluginsLoaded) - { - _pluginService.Value.DisposePlugins(); - PluginsLoaded = false; - } - - if (ConfigsLoaded) - { - _configService.Value.RemoveConfigsProfiles(this.ConfigProfiles); - _configService.Value.RemoveConfigs(this.Configs); - ConfigsLoaded = false; - } - - if (LocalizationsLoaded) - { - _localizationService.Value.Remove(this.Localizations); - LocalizationsLoaded = false; - } - return FluentResults.Result.Ok(); - } - finally - { - _operationsUsageLock.ExitWriteLock(); - } - } - - #endregion - - #region INTERNAL - - /// - /// [Thread Unsafe] Performs sanitation and null checks on resources and returns the results. - /// NOTE: Requires that resource locks be set by the caller. - /// - /// - /// - private FluentResults.Result CheckResourceSanitation( - OneOf.OneOf resourcesInfos) - { - // execute checks based on known types - return resourcesInfos.Match( - ass => ChecksDispatcher(ass, nameof(ass.Assemblies), nameof(LoadPlugins), - ass.Assemblies, this.Assemblies), - loc => ChecksDispatcher(loc, nameof(loc.Localizations), nameof(LoadLocalizations), - loc.Localizations, this.Localizations), - cfg => ChecksDispatcher(cfg, nameof(cfg.Configs), nameof(LoadConfig), - cfg.Configs, this.Configs), - cfp => ChecksDispatcher(cfp, nameof(cfp.ConfigProfiles), nameof(LoadConfig), - cfp.ConfigProfiles, this.ConfigProfiles), - lua => ChecksDispatcher(lua, nameof(lua.LuaScripts), nameof(AddLuaScripts), - lua.LuaScripts, this.LuaScripts)); - - - /* - * Helper functions - */ - FluentResults.Result ChecksDispatcher(object obj, string resName, string callerName, - ImmutableArray resList, ImmutableArray compareList) - where T : class, IPackageInfo, IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo - { - string errMsg = $"{callerName}: Failed to load {resName}."; - if (DisposeCheck(obj) is { IsFailed: true } failed) - return failed; - if (SanitationChecksCore(obj, resName, callerName) is { IsFailed: true } failed1) - return failed1.WithError(new Error(errMsg)); - if (SanitationChecksEnumerable(resList, resName, callerName) is { IsFailed: true } failed2) - return failed2.WithError(new Error(errMsg)); - if (DebugCheck(resList, compareList, resName) is {IsFailed: true} failed3) - return failed3.WithError(new Error(errMsg)); - return FluentResults.Result.Ok(); - } - - FluentResults.Result DisposeCheck(object obj) - { - if (IsDisposed) - { - return FluentResults.Result.Fail(new Error($"{nameof(PackageService)}: Tried to load resources when disposed.") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, obj)); - } - return FluentResults.Result.Ok(); - } - - FluentResults.Result DebugCheck(ImmutableArray resList, ImmutableArray compareList, string resName) - where T : class, IPackageInfo - { -#if DEBUG - Stack errors = new(); - resList.ForEach(res => - { - if (!compareList.Contains(res)) - { - errors.Push(new Error($"Failed to load {resName} for: {this.Package.Name}") - .WithMetadata(MetadataType.ExceptionDetails, $"Tries to load {resName} resource {res.InternalName} but it is not from this package!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, res)); - } - }); - if (errors.Count > 0) - { - return FluentResults.Result.Fail(errors).WithError( - new Error($"{nameof(LoadPlugins)}: errors in {resName} resources.") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, this.Package)); - } -#endif - return FluentResults.Result.Ok(); - } - } - - private FluentResults.Result SanitationChecksCore(object obj, string resTypeInfoName, string callerName) - { - Error e = null; - - if (obj is null) - { - e = new Error($"{nameof(SanitationChecksCore)}: null checks failed!") - .WithMetadata(MetadataType.ExceptionDetails, "Object is null!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.Sources, new List() { resTypeInfoName, callerName }); - } - - if (this.Package is null) - { - e = (e ?? new Error($"{nameof(SanitationChecksCore)}: null checks failed!")) - .WithMetadata(MetadataType.ExceptionDetails, "The Package is null!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.Sources, new List() { resTypeInfoName, callerName }); - } - - return e is null ? FluentResults.Result.Ok() : FluentResults.Result.Fail(e); - } - - private FluentResults.Result SanitationChecksEnumerable(ImmutableArray resourceInfos, string resTypeInfoName, string callerName) where T : IResourceInfo, IResourceCultureInfo, IPackageInfo, IPackageDependenciesInfo - { - // Check if list is empty. Nothing more to do. - if (resourceInfos.IsDefaultOrEmpty) - return FluentResults.Result.Ok(); - - Stack errors = new(); - - // Check if all resources in the list are registered to this package, throw if not. - foreach (var resourceInfo in resourceInfos) - { - // ownership checks - if (resourceInfo.OwnerPackage is null) - { - errors.Push(new Error($"Error for resource: {resTypeInfoName}. OwnerPackage is null!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, resourceInfo)); - continue; - } - - if (resourceInfo.OwnerPackage != this.Package) - { - errors.Push(new Error($"Error for resource: {resTypeInfoName}. $\"OwnerPackage {{resourceInfo.OwnerPackage?.Name}} is not the same as this package: {{this.Package}}") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, resourceInfo)); - continue; - } - - if (resourceInfo.Dependencies.IsDefaultOrEmpty) - continue; - - // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator - foreach (var pdi in resourceInfo.Dependencies) - { - // for clarification: all resources passed to the function should always be loaded. - // unneeded optional resources should be filtered out before the list is sent. - // left this as a reminder :) - /*if (pdi.Optional) - return;*/ - if (!_packageManagementService.CheckDependencyLoaded(pdi)) - { - errors.Push(new Error($"Dependency missing for resource: {resourceInfo.OwnerPackage.Name}") - .WithMetadata(MetadataType.ExceptionDetails, $"Missing dependency: {pdi.DependencyPackage?.Name ?? (pdi.FallbackPackageName.IsNullOrWhiteSpace() ? pdi.SteamWorkshopId.ToString() : pdi.FallbackPackageName)}") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, resourceInfo)); - } - } - - // check runtime platform - if (!_packageManagementService.CheckEnvironmentSupported(resourceInfo)) - { - errors.Push(new Error($"The resource {resourceInfo.OwnerPackage?.Name} does not support the current platform!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, resourceInfo)); - } - - // check local culture - if (!_localizationService.Value.IsCurrentCultureSupported(resourceInfo)) - { - errors.Push(new Error($"The resource {resourceInfo.OwnerPackage?.Name} does not support the current culture/region!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, resourceInfo)); - } - } - - return errors.Count > 0 ? FluentResults.Result.Fail(errors) : FluentResults.Result.Ok(); - } - - #endregion -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 6f46b99e1..5289e946a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -107,6 +107,22 @@ public class PluginManagementService : IPluginManagementService, IAssemblyManage throw new NotImplementedException(); } + public IReadOnlyList> ActivateTypeInstances(ImmutableArray types, bool serviceInjection = true, + bool hostInstanceReference = false) where T : IDisposable + { + throw new NotImplementedException(); + } + + public FluentResults.Result UnloadHostedReferences() + { + throw new NotImplementedException(); + } + + public FluentResults.Result UnloadAllAssemblyResources() + { + throw new NotImplementedException(); + } + public Result GetLoadedAssembly(string assemblyName, in Guid[] excludedContexts) { ((IService)this).CheckDisposed(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs index 0b8c8ad70..a71445714 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading.Tasks; using System.Xml.Linq; using Barotrauma.LuaCs.Data; using FluentResults; @@ -7,46 +8,16 @@ namespace Barotrauma.LuaCs.Services.Processing; #region TypeDef -// ReSharper disable once TypeParameterCanBeVariant -public interface IConverterService : IReusableService +public interface IConverterService : IReusableService { Result TryParseResource(TSrc src); Result TryParseResources(IEnumerable sources); } -public interface IXmlResourceConverterService : IConverterService { } -public interface IResourceToXmlConverterService : IConverterService { } - -#endregion - -/// -/// Parses Xml to produce loading metadata info for linked loadable files. -/// -#region XmlToResourceInfoParsers - -public interface IXmlAssemblyResConverter : IXmlResourceConverterService { } -public interface IXmlConfigResConverterService : IXmlResourceConverterService { } -public interface IXmlLocalizationResConverterService : IXmlResourceConverterService { } - -#endregion - -/// -/// Parses Xml to produce ready-to-use info/data without any additional file/data loading. -/// -#region XmlToInfoParsers -public interface IXmlDependencyConverterService : IXmlResourceConverterService { } -public interface IXmlModConfigConverterService : IXmlResourceConverterService { } -/// -/// Parses legacy packages that make use of the RunConfig.xml structure to produce a ModConfig. -/// -public interface IXmlLegacyModConfigConverterService : IXmlResourceConverterService { } - -#endregion - - -#region ResToInfoParsers -public interface ILocalizationResToInfoParser : IConverterService { } -public interface IConfigResConverterService : IConverterService { } -public interface IConfigProfileResConverterService : IConverterService { } +public interface IConverterServiceAsync : IReusableService +{ + Task> TryParseResourceAsync(TSrc src); + Task> TryParseResourcesAsync(IEnumerable sources); +} #endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs index d20d77942..d812e767d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs @@ -33,26 +33,25 @@ public class StorageService : IStorageService private IConfigEntry _kLocalFilePathRules = null; private const string _packagePathKeyword = ""; private readonly string _runLocation = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location.CleanUpPath()); + + // TODO: Rewrite the config to get info from .ctor. private IConfigEntry LocalStoragePath => _kLocalStoragePath ??= GetOrCreateConfig(nameof(LocalStoragePath), "/Data/Mods"); private IConfigEntry LocalFilePathRule => _kLocalFilePathRules ??= GetOrCreateConfig(nameof(LocalFilePathRule), _packagePathKeyword); private IConfigEntry GetOrCreateConfig(string name, string defaultValue) { var c = _configService.Value .GetConfig>(ModUtils.Definitions.LuaCsForBarotrauma, name); - if (c.IsSuccess) - { - return c.Value; - } - else - { - c = _configService.Value.AddConfigEntry( - ModUtils.Definitions.LuaCsForBarotrauma, - name, defaultValue, NetSync.None, valueChangePredicate: (value) => false); - if (c.IsSuccess) - return c.Value; - else - throw new KeyNotFoundException("Cannot find storage value for key: " + name); - } + if (c is not null) + return c; + + var c1 = _configService.Value.AddConfigEntry( + ModUtils.Definitions.LuaCsForBarotrauma, + name, defaultValue, NetSync.None, valueChangePredicate: (value) => false); + if (c1.IsSuccess) + return c1.Value; + + throw new KeyNotFoundException("Cannot find storage value for key: " + name); + } public bool IsDisposed { get; private set; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs index b40a1da69..c540f3b78 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; using Barotrauma.LuaCs.Configuration; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Networking; @@ -15,20 +16,14 @@ public partial interface IConfigService : IReusableService, ILuaConfigService /* * Resource Files. */ - FluentResults.Result AddConfigs(ImmutableArray configResources); - FluentResults.Result AddConfigsProfiles(ImmutableArray configProfileResources); - FluentResults.Result RemoveConfigs(ImmutableArray configResources); - FluentResults.Result RemoveConfigsProfiles(ImmutableArray configProfilesResources); + Task LoadConfigsAsync(ImmutableArray configResources); + Task LoadConfigsProfilesAsync(ImmutableArray configProfileResources); + FluentResults.Result DisposeConfigs(ImmutableArray configResources); + FluentResults.Result DisposeConfigsProfiles(ImmutableArray configProfilesResources); + FluentResults.Result DisposeConfigs(ContentPackage package); + FluentResults.Result DisposeConfigsProfiles(ContentPackage package); - /* - * From resources - */ - FluentResults.Result AddConfigs(ImmutableArray configs); - FluentResults.Result AddConfigsProfiles(ImmutableArray configProfiles); - FluentResults.Result RemoveConfigs(ImmutableArray configs); - FluentResults.Result RemoveConfigsProfiles(ImmutableArray configProfiles); - /* * Immediate mode */ @@ -79,8 +74,6 @@ public partial interface IConfigService : IReusableService, ILuaConfigService FluentResults.Result> GetConfigsForPackage(ContentPackage package); FluentResults.Result> GetConfigsForPackage(string packageName); IReadOnlyDictionary<(ContentPackage, string), IConfigBase> GetAllConfigs(); - FluentResults.Result GetConfig(ContentPackage package, string name); - FluentResults.Result GetConfig(string packageName, string name); - FluentResults.Result GetConfig(ContentPackage package, string name) where T : IConfigBase; - FluentResults.Result GetConfig(string packageName, string name) where T : IConfigBase; + T GetConfig(ContentPackage package, string name) where T : IConfigBase; + T GetConfig(string packageName, string name) where T : IConfigBase; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILocalizationService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILocalizationService.cs index 26575f720..59abcfe90 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILocalizationService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILocalizationService.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.Collections.Generic; using System.Collections.Immutable; +using System.Threading.Tasks; using Barotrauma.LuaCs.Data; namespace Barotrauma.LuaCs.Services; @@ -10,9 +11,10 @@ public interface ILocalizationService : IReusableService { IReadOnlyCollection GetLoadedLocales(); void Remove(ImmutableArray localizations); + void DisposePackage(ContentPackage package); FluentResults.Result SetCurrentCulture(CultureInfo culture); FluentResults.Result SetCurrentCulture(string cultureName); - FluentResults.Result LoadLocalizations(ImmutableArray localizationResources); + Task LoadLocalizations(ImmutableArray localizationResources); /// /// Tries to get a localized string without a fallback. Returns success/failure and associated data. diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs new file mode 100644 index 000000000..88e432400 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Reflection; +using System.Threading.Tasks; +using Barotrauma.LuaCs.Data; +using MoonSharp.Interpreter; +using MoonSharp.Interpreter.Interop; + +namespace Barotrauma.LuaCs.Services; + +public interface ILuaScriptManagementService : IReusableService +{ + #region Script_Ops + + Task LoadScriptResourcesAsync(ImmutableArray resourcesInfo); + + FluentResults.Result ExecuteLoadedScripts(ContentPackage package, bool pauseExecutionOnError = false, bool verboseLogging = false); + FluentResults.Result ExecuteLoadedScripts(ImmutableArray scripts, bool pauseExecutionOnError = false, bool verboseLogging = false); + FluentResults.Result ExecuteLoadedScripts(bool pauseExecutionOnError = false, bool verboseLogging = false); + FluentResults.Result DisposePackageResources(ContentPackage package); + FluentResults.Result UnloadActiveScripts(); + FluentResults.Result DisposeAllPackageResources(); + + #endregion + + #region Type_Registration + + IUserDataDescriptor RegisterType(Type type); + /// + /// [Deprecated]
+ /// Use () instead. + /// Gets the type information for an already registered type. + ///
+ /// The fully qualified name of the type and namespace. + /// The for the type, if registered. Null if none is found. + [Obsolete($"Use {nameof(GetTypeInfo)} instead.")] + IUserDataDescriptor RegisterType(string typeName) => GetTypeInfo(typeName); + IUserDataDescriptor RegisterGenericType(Type type); + /// + /// [Deprecated]
+ /// Use () instead. + /// Gets the generic type information for an already registered type. + ///
+ /// The fully qualified name of the generic type and namespace. + /// The fully qualified name of the template types. + /// The for the type, if registered. Null if none is found. + [Obsolete($"Use {nameof(GetGenericTypeInfo)} instead.")] + IUserDataDescriptor RegisterGenericType(string typeName, params string[] typeNameArgs) => GetGenericTypeInfo(typeName, typeNameArgs); + /// + /// Gets the type information for an already registered type. + /// + /// The fully qualified name of the type and namespace. + /// The for the type, if registered. Null if none is found. + IUserDataDescriptor GetTypeInfo(string typeName); + /// + /// Gets the generic type information for an already registered type. + /// + /// The fully qualified name of the generic type and namespace. + /// The fully qualified name of the template types. + /// The for the type, if registered. Null if none is found. + IUserDataDescriptor GetGenericTypeInfo(string typeName, params string[] typeNameArgs); + void UnregisterType(Type type); + + #endregion + + #region Type_Checks_&Utilities + + bool IsRegistered(Type type); + bool IsTargetType(object obj, string typeName); + string TypeOf(object obj); + object CreateStatic(string typeName); + object CreateEnumTable(string typeName); + FieldInfo FindFieldRecursively(Type type, string fieldName); + void MakeFieldAccessible(IUserDataDescriptor descriptor, string fieldName); + MethodInfo FindMethodRecursively(Type type, string methodName, Type[] types = null); + void MakeMethodAccessible(IUserDataDescriptor descriptor, string methodName, string[] parameters = null); + PropertyInfo FindPropertyRecursively(Type type, string propertyName); + void MakePropertyAccessible(IUserDataDescriptor descriptor, string propertyName); + void AddMethod(IUserDataDescriptor descriptor, string methodName, object function); + void AddField(IUserDataDescriptor descriptor, string fieldName, DynValue value); + void RemoveMember(IUserDataDescriptor descriptor, string memberName); + bool HasMember(object obj, string memberName); + DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor descriptor); + DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType); + + #endregion +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptService.cs deleted file mode 100644 index 99f9fa516..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptService.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Reflection; -using Barotrauma.LuaCs.Data; -using MoonSharp.Interpreter; -using MoonSharp.Interpreter.Interop; - -namespace Barotrauma.LuaCs.Services; - -public interface ILuaScriptService : IReusableService -{ - #region Script_File_Collector - - /// - /// Adds the script files to the runner but does not execute them. - /// - /// - /// - FluentResults.Result AddScriptFiles(ImmutableArray luaResource); - - /// - /// Removes the specific resources from the script runner. Important: Does not stop the - /// execution of any code related to the files nor guarantee cleanup of resources! - /// - /// - void RemoveScriptFiles(ImmutableArray luaResource); - - /// - /// Executes loaded script files on the management service. - /// - /// - /// - /// - FluentResults.Result ExecuteScripts(bool pauseExecutionOnScriptError = false, bool verboseLogging = false); - - ImmutableArray GetScriptResources(); - - #endregion -} - -public interface ILuaScriptManagementService : IReusableService -{ - #region Script_File_Execution - - FluentResults.Result ExecuteLoadedScripts(ContentPackage package, bool pauseExecutionOnError = false, bool verboseLogging = false); - FluentResults.Result ExecuteLoadedScripts(ImmutableArray scripts, bool pauseExecutionOnError = false, bool verboseLogging = false); - FluentResults.Result ExecuteLoadedScripts(bool pauseExecutionOnError = false, bool verboseLogging = false); - - #endregion - - #region Type_Registration - - IUserDataDescriptor RegisterType(Type type); - IUserDataDescriptor RegisterType(string typeName); - IUserDataDescriptor RegisterGenericType(Type type); - IUserDataDescriptor RegisterGenericType(string typeName, params string[] typeNameArgs); - void UnregisterType(Type type); - void UnregisterType(string typeName); - void UnregisterAllTypes(); - - #endregion - - #region Type_Checks_&Utilities - - bool IsRegistered(Type type); - bool IsTargetType(object obj, string typeName); - string TypeOf(object obj); - object CreateStatic(string typeName); - object CreateEnumTable(string typeName); - FieldInfo FindFieldRecursively(Type type, string fieldName); - void MakeFieldAccessible(IUserDataDescriptor descriptor, string fieldName); - MethodInfo FindMethodRecursively(Type type, string methodName, Type[] types = null); - void MakeMethodAccessible(IUserDataDescriptor descriptor, string methodName, string[] parameters = null); - PropertyInfo FindPropertyRecursively(Type type, string propertyName); - void MakePropertyAccessible(IUserDataDescriptor descriptor, string propertyName); - void AddMethod(IUserDataDescriptor descriptor, string methodName, object function); - void AddField(IUserDataDescriptor descriptor, string fieldName, DynValue value); - void RemoveMember(IUserDataDescriptor descriptor, string memberName); - bool HasMember(object obj, string memberName); - DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor descriptor); - DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType); - - #endregion -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs index 397a871b4..e3013afe9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs @@ -3,89 +3,61 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Threading.Tasks; using Barotrauma.Extensions; using Barotrauma.LuaCs.Data; namespace Barotrauma.LuaCs.Services; -public interface IPackageManagementService : IReusableService +public interface IPackageManagementService : IReusableService, ILocalizationsResourcesInfo, IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo, IAssembliesResourcesInfo +#if CLIENT + ,IStylesResourcesInfo +#endif + { /// - /// Adds packages to the queue of loadable packages without initializing them. + /// Loads and parses the provided for supported by the current runtime environment. /// /// - void QueuePackages(ImmutableArray packages); + /// + Task LoadPackageInfosAsync(ContentPackage packages); + /// + /// Loads and parses the provided collection for supported by the current runtime environment. + /// + /// + /// + Task> LoadPackagesInfosAsync(IReadOnlyList packages); + IReadOnlyList GetAllLoadedPackages(); + void DisposePackageInfos(ContentPackage package); + void DisposePackagesInfos(IReadOnlyList packages); + void DisposeAllPackagesInfos(); - /// - /// Generates the ModConfigInfo for all queued packages and adds them to the store. - /// - /// Use multithreaded loading. - /// Whether duplicate packages should be reported as errors. - /// Failure/Success records for each package. - FluentResults.Result ParseQueuedPackages(bool loadParallel = true, bool reportFailOnDuplicates = false); - /// - /// Loads only the localizations, configs, and config profiles for stored packages. - /// - /// - /// - FluentResults.Result LoadPackageConfigsResourcesGroup(bool loadParallel = true); - /// - /// Loads all resources for stored packages. - /// - /// Use multithreaded loading. - /// Only load safe scripting resources, such as Lua. C# plugins disabled. - /// - FluentResults.Result LoadAllPackageResources(bool loadParallel = true, bool safeResourcesOnly = true); - FluentResults.Result UnloadPackages(); - bool IsPackageLoaded(ContentPackage package); - bool CheckDependencyLoaded(IPackageDependencyInfo info); - bool CheckDependenciesLoaded([NotNull]IEnumerable infos, out ImmutableArray missingPackages); - bool CheckEnvironmentSupported(IPlatformInfo platform); - /// - /// Tries to get the package dependency record to refer to that specific package if it exists, optionally create it. - /// - /// ContentPackage reference - /// Register a new IPackageDependencyInfo reference. - /// - FluentResults.Result GetPackageDependencyInfoRecord(ContentPackage package, - bool addIfMissing = false); - /// - /// Tries to get the package dependency record to refer to that specific package if it exists, optionally create it. - /// - /// The Steam Workshop ID, if available, if not enter zero ('0'). - /// The name of the package. - /// The folder path, as formatted in [ContentPackage.Path]. - /// Register a new IPackageDependencyInfo reference. - /// - FluentResults.Result GetPackageDependencyInfoRecord(ulong steamWorkshopId, - string packageName, string folderPath = null, bool addIfMissing = false); - /// - /// Tries to get the package dependency record to refer to that specific package if it exists. - /// Note: This overload does not allow the registration of a new dependency. - /// - /// The folder path, as formatted in [ContentPackage.Path]. - /// - FluentResults.Result GetPackageDependencyInfoRecord(string folderPath); - - IPackageDependencyInfo CreateOrphanPackageDependencyInfoRecord(string packageName, - string packagePath, ulong steamWorkshopId); -} - -public readonly record struct LoadablePackage -{ - public ContentPackage Package { get; } - public bool IsEnabled { get; } - - public LoadablePackage(ContentPackage package, bool isEnabled) - { - Package = package; - IsEnabled = isEnabled; - } + // single + FluentResults.Result GetAssembliesInfos(ContentPackage package, bool onlySupportedResources = true); + FluentResults.Result GetConfigsInfos(ContentPackage package, bool onlySupportedResources = true); + FluentResults.Result GetConfigProfilesInfos(ContentPackage package, bool onlySupportedResources = true); + FluentResults.Result GetLocalizationsInfos(ContentPackage package, bool onlySupportedResources = true); + FluentResults.Result GetLuaScriptsInfos(ContentPackage package, bool onlySupportedResources = true); +#if CLIENT + FluentResults.Result GetStylesInfos(ContentPackage package, bool onlySupportedResources = true); +#endif + // collection + FluentResults.Result GetAssembliesInfos(IReadOnlyList packages, bool onlySupportedResources = true); + FluentResults.Result GetConfigsInfos(IReadOnlyList packages, bool onlySupportedResources = true); + FluentResults.Result GetConfigProfilesInfos(IReadOnlyList packages, bool onlySupportedResources = true); + FluentResults.Result GetLocalizationsInfos(IReadOnlyList packages, bool onlySupportedResources = true); + FluentResults.Result GetLuaScriptsInfos(IReadOnlyList packages, bool onlySupportedResources = true); +#if CLIENT + FluentResults.Result GetStylesInfos(IReadOnlyList packages, bool onlySupportedResources = true); +#endif + + Task> GetAssembliesInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); + Task> GetConfigsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); + Task> GetConfigProfilesInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); + Task> GetLocalizationsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); + Task> GetLuaScriptsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); +#if CLIENT + Task> GetStylesInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); +#endif - public static ImmutableArray FromEnumerable(IEnumerable packages, bool isEnabled) - { - var builder = ImmutableArray.CreateBuilder(); - packages.ForEach(p => builder.Add(new LoadablePackage(p, isEnabled))); - return builder.ToImmutable(); - } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageService.cs deleted file mode 100644 index 3f1189250..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageService.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using Barotrauma.LuaCs.Data; -using FluentResults; - -namespace Barotrauma.LuaCs.Services; - -public interface IPackageService : IReusableService, - // These allow us the pass the IContentPackageService to anything that needs the data without having to directly reference the member - IResourceCultureInfo, IAssembliesResourcesInfo, ILocalizationsResourcesInfo, ILuaScriptsResourcesInfo -{ - ContentPackage Package { get; } - IModConfigInfo ModConfigInfo { get; } - bool IsEnabledInModList { get; } - /// - /// Try to load the XML config and resources information from the given package. - /// - /// - /// Whether the package was parsed without errors. - FluentResults.Result LoadResourcesInfo([NotNull]LoadablePackage package); - /// - /// Tries to load all assemblies and instance plugins for the given resources list, regardless whether they're marked as optional and/or lazy load. - /// Will sort by load priority unless overriden/bypassed. - /// - /// - /// - /// Whether loading is successful. Returns true on an empty list. - FluentResults.Result LoadPlugins([NotNull]IAssembliesResourcesInfo assembliesInfo, bool ignoreDependencySorting = false); - FluentResults.Result LoadLocalizations([NotNull]ILocalizationsResourcesInfo localizationsInfo); - FluentResults.Result AddLuaScripts([NotNull]ILuaScriptsResourcesInfo luaScriptsInfo); -#if CLIENT - FluentResults.Result LoadStyles([NotNull]IStylesResourcesInfo stylesInfo); -#endif - FluentResults.Result LoadConfig([NotNull]IConfigsResourcesInfo configsResourcesInfo, [NotNull]IConfigProfilesResourcesInfo configProfilesResourcesInfo); -} - diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs index d81ef4dac..6319573bb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Reflection; using Barotrauma.LuaCs.Data; @@ -31,16 +32,35 @@ public interface IPluginManagementService : IReusableService bool includeDefaultContext = true); /// - /// Tries to get the + /// Tries to get the Type given the fully qualified name. /// /// /// Type GetType(string typeName); /// - /// + /// Loads the provided assembly resources in the order of their dependencies and intra-mod priority load order. /// /// /// Success/Failure and list of failed resources, if any. FluentResults.Result> LoadAssemblyResources(ImmutableArray resource); + + /// + /// Creates instances of the given type and provides Property Injection and instance reference caching. Disposes of + /// all references that throw errors on + /// + /// List of Types + /// + /// + /// + IReadOnlyList> ActivateTypeInstances(ImmutableArray types, bool serviceInjection = true, + bool hostInstanceReference = false) where T : IDisposable; + + FluentResults.Result UnloadHostedReferences(); + + /// + /// Tries to gracefully unload all hosted plugin references + /// + /// + FluentResults.Result UnloadAllAssemblyResources(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs index 622a47591..abe2c0f3d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs @@ -43,6 +43,7 @@ GUI.SettingsMenuOpen = false; #endif Selected = this; + GameMain.LuaCs.EventService.PublishEvent(sub => sub.OnScreenSelected(this)); } public virtual Camera Cam => null;