diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index 08f5522c3..a60b0c8c5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -25,16 +25,7 @@ namespace Barotrauma public void CheckCsEnabled() { - throw new NotImplementedException($"Replace PMS.Assemblies with checks on ContentPackageManager.EnabledPackages"); - - /*var csharpMods = PackageManagementService.Assemblies - .GroupBy(ass => ass.OwnerPackage) - .Select(grp => grp.Key) - .Where(ContentPackageManager.EnabledPackages.All.Contains) - .ToImmutableArray(); - - if (!csharpMods.Any()) - return; + var csharpMods = PackageManagementService.GetLoadedAssemblyPackages(); StringBuilder sb = new StringBuilder(); @@ -67,7 +58,7 @@ namespace Barotrauma { this.IsCsEnabled.TrySetValue(false); return true; - };*/ + }; } /// diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs index 9eaca6c7d..ae63d3cc8 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs @@ -1,4 +1,6 @@ -using Barotrauma.Networking; +using System; +using System.IO; +using Barotrauma.Networking; namespace Barotrauma; @@ -19,4 +21,10 @@ partial class LuaCsSetup private partial bool ShouldRunCs() => IsCsEnabled.Value || (GetPackage(new SteamWorkshopId(CsForBarotraumaSteamId.Value), false, false) is { } && GameMain.Server.ServerPeer is LidgrenServerPeer); + + // ReSharper disable once InconsistentNaming + private static readonly Lazy isRunningInsideWorkshop = new Lazy(() => + Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly()!.Location) != + Directory.GetCurrentDirectory()); + public static bool IsRunningInsideWorkshop => isRunningInsideWorkshop.Value; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 0ea4484cf..cf3d11c58 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -16,6 +16,7 @@ using Barotrauma.LuaCs.Services.Safe; using Barotrauma.Networking; using FluentResults; using ImpromptuInterface; +using Microsoft.Toolkit.Diagnostics; namespace Barotrauma { @@ -39,7 +40,6 @@ namespace Barotrauma // == sub processes void RegisterServices() { - _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); @@ -194,18 +194,7 @@ namespace Barotrauma public ILuaCsHook Hook => this.EventService; #endregion - - public static bool IsRunningInsideWorkshop - { - get - { -#if SERVER - return Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location) != Directory.GetCurrentDirectory(); -#else - return false; // unnecessary but just keeps things clear that this is NOT for client stuff -#endif - } - } + private partial bool ShouldRunCs(); @@ -329,6 +318,8 @@ namespace Barotrauma private void UpdateLoadedPackagesList() { + throw new NotImplementedException($"Rewrite the loading state system."); + var newPackSet = ContentPackageManager.AllPackages .Union(ContentPackageManager.EnabledPackages.All) .ToHashSet(); @@ -340,10 +331,10 @@ namespace Barotrauma foreach (var package in toRemove) _toUnload.Enqueue(package); - ProcessPackagesListDifferences(); + //ProcessPackagesListDifferences(); } - void ProcessPackagesListDifferences() + /*void ProcessPackagesListDifferences() { if (IsCodeRunning) return; @@ -379,13 +370,13 @@ namespace Barotrauma { LoadStaticAssetsAsync(ls).GetAwaiter().GetResult(); } - } + }*/ void SetRunState(RunState runState) { if (CurrentRunState == runState) return; - if (runState > CurrentRunState) + /*if (runState > CurrentRunState) { if (CurrentRunState <= RunState.Parsed) { @@ -444,58 +435,7 @@ namespace Barotrauma } // we should be unloaded completely now | RunState.Unloaded - } - } - - void LoadCurrentContentPackageInfos() - { - if (CurrentRunState >= RunState.Parsed) - return; - - // load core - var result1 = PackageManagementService.LoadPackageInfo(ContentPackageManager.VanillaCorePackage); - 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.LoadPackagesInfo([..packages]); - if (result2.IsFailed) - Logger.LogResults(result2); - } - - 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.UnloadPackage(cp); - - LoadStaticAssetsAsync(PackageManagementService.GetAllLoadedPackages()).GetAwaiter().GetResult(); - LoadLuaCsConfig(); - - if (CurrentRunState < RunState.Configuration) - _runState = RunState.Configuration; + }*/ } void LoadLuaCsConfig() @@ -530,172 +470,6 @@ namespace Barotrauma ReloadPackagesOnLobbyStart = null; } - async Task LoadStaticAssetsAsync(IReadOnlyList packages) - { - throw new NotImplementedException(); - - /*var cfgRes = ImmutableArray.Empty; - var luaRes = ImmutableArray.Empty; - - var tasksBuilder = ImmutableArray.CreateBuilder(); - - //---- get resource infos - tasksBuilder.AddRange( - 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.GetLuaScriptsInfosAsync(packages); - if (res.IsSuccess) - luaRes = res.Value.LuaScripts; - if (res.Errors.Any()) - ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state), - res.ToResult()); - })()); - - 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); - })(), - new Func(async () => - { - var res = await LuaScriptManagementService.LoadScriptResourcesAsync(luaRes); - 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.LuaScripts - .Select(ls => ls.OwnerPackage) - .Where(p => p is not null) - .Where(ContentPackageManager.EnabledPackages.All.Contains) - .ToImmutableArray(); - if (luaRes.IsDefaultOrEmpty) - { - Logger.LogError($"{nameof(RunScripts)}: Failed to get enabled lua script resources!"); - return; - } - - LuaScriptManagementService.ExecuteLoadedScripts(); - - 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 (CurrentRunState >= RunState.Configuration) - { - _runState = RunState.Parsed; - }*/ - } - - void StopScripts() - { - /*EventService.ClearAllSubscribers(); - LuaScriptManagementService.UnloadActiveScripts(); - PluginManagementService.UnloadManagedAssemblies(); - SubscribeToLuaCsEvents(); - - if (IsCodeRunning) - { - _runState = RunState.Configuration; - }*/ - } - } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs index 17b2c13c1..ac523f836 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs @@ -23,6 +23,7 @@ using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.Toolkit.Diagnostics; using static Barotrauma.GameSettings; namespace Barotrauma.LuaCs.Services; @@ -34,6 +35,8 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService [MemberNotNullWhen(true, nameof(_script))] public bool IsRunning => _isRunning; private List _resourcesInfo = new List(); + + private readonly AsyncReaderWriterLock _operationsLock = new (); private readonly ILuaScriptLoader _luaScriptLoader; private readonly ILuaScriptServicesConfig _luaScriptServicesConfig; @@ -67,13 +70,30 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService public bool IsDisposed { get; private set; } - public Task LoadScriptResourcesAsync(ImmutableArray resourcesInfo) + public async Task LoadScriptResourcesAsync(ImmutableArray resourcesInfo) { + // Do any exception checks you can before acquiring a lock to avoid needlessly holding up resources. + if (resourcesInfo.IsDefaultOrEmpty) + ThrowHelper.ThrowArgumentNullException($"{nameof(LoadScriptResourcesAsync)}: The parameter is empty!"); + + // Acquire a lock: + // Reader = Allow parallel operations (try to avoid nesting acquiring the lock when possible) + // Writer = Exclusive use (ie. executing scripts or Dispose()) + using var lck = await _operationsLock.AcquireWriterLock(); // IDisposable using with generate a try-finally and release for you. + IService.CheckDisposed(this); // Check disposed after you have the lock + + // If you use a ConcurrentDictionary instead of a List, it will handle threading issues for you. _resourcesInfo.AddRange(resourcesInfo.OrderBy(static r => r.LoadPriority)); - // TODO disk caching - - return Task.FromResult(FluentResults.Result.Ok()); + // Use the StorageService's caching function by just loading the file with caching turned on. + // Right now the LuaScriptLoader has this on by default. + var cacheRes = await _luaScriptLoader.CacheResourcesAsync(resourcesInfo); + + // Aggregate and return results to the caller to deal with. Optionally, log here if you want. + // Automatically converted to a Task when 'async' is in the method declaration. + if (cacheRes.IsFailed) + return cacheRes.ToResult(); + return new FluentResults.Result().WithReasons(cacheRes.Value.SelectMany(cr => cr.Item2.Reasons)); } private void SetupEnvironment() diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageListRetrievalService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageListRetrievalService.cs deleted file mode 100644 index 37e1c2bfc..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageListRetrievalService.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Collections.Generic; - -namespace Barotrauma.LuaCs.Services; - -public sealed class PackageListRetrievalService : IPackageListRetrievalService -{ - public void Dispose() - { - // stateless service - return; - } - - public void CheckDisposed() - { - // stateless service - return; - } - - public bool IsDisposed => false; - - public IEnumerable GetEnabledContentPackages() - { - return ContentPackageManager.EnabledPackages.All; - } - - public IEnumerable GetAllContentPackages() - { - return ContentPackageManager.AllPackages; - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs index f8f92afaf..0a518281a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs @@ -141,16 +141,31 @@ public sealed class PackageManagementService : IPackageManagementService private FluentResults.Result UnsafeAddPackageInternal(ContentPackage package, IModConfigInfo config) { - if (_loadedPackages.TryGetValue(package, out var result)) + if (_loadedPackages.TryGetValue(package, out _)) { _logger.LogWarning($"Tried to load already-loaded package {package.Name}."); return FluentResults.Result.Ok(); } _loadedPackages[package] = config; - var res = new FluentResults.Result(); - res.WithReasons(_luaScriptManagementService.LoadScriptResourcesAsync(config.LuaScripts).ConfigureAwait(false).GetAwaiter().GetResult().Reasons); - return res; + try + { + var res = new FluentResults.Result(); + var r = Task.WhenAll( + new Task>(async Task () => new FluentResults.Result() + .WithReasons((await _configService.LoadConfigsAsync(config.Configs)).Reasons) + .WithReasons((await _configService.LoadConfigsProfilesAsync(config.Configs)).Reasons)), + new Task>(async () => await _luaScriptManagementService.LoadScriptResourcesAsync(config.LuaScripts)) + ).ConfigureAwait(false).GetAwaiter().GetResult(); + + foreach (var task in r) + res.WithReasons(task.ConfigureAwait(false).GetAwaiter().GetResult().Reasons); + return res; + } + catch (Exception e) + { + return FluentResults.Result.Fail(new ExceptionalError(e)); + } } public FluentResults.Result ExecuteLoadedPackages(ImmutableArray executionOrder) @@ -292,4 +307,15 @@ public sealed class PackageManagementService : IPackageManagementService IService.CheckDisposed(this); return _runningPackages.ContainsKey(package); } + + public ImmutableArray GetLoadedAssemblyPackages() + { + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + if (_loadedPackages.IsEmpty) + return ImmutableArray.Empty; + return [.._loadedPackages.Values + .Where(cfg => !cfg.Assemblies.IsDefaultOrEmpty) + .Select(cfg => cfg.Package)]; + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaScriptLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaScriptLoader.cs index 4f6475d06..f86dcd965 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaScriptLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaScriptLoader.cs @@ -1,8 +1,13 @@ -using MoonSharp.Interpreter.Loaders; +using System.Collections.Immutable; +using System.Threading.Tasks; +using Barotrauma.LuaCs.Data; +using FluentResults; +using MoonSharp.Interpreter.Loaders; namespace Barotrauma.LuaCs.Services.Safe; public interface ILuaScriptLoader : IService, IScriptLoader { void ClearCaches(); + Task)>>> CacheResourcesAsync(ImmutableArray resourceInfos); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs index ab1e19553..a2e23af47 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Text; using System.IO; using MoonSharp.Interpreter; using MoonSharp.Interpreter.Loaders; using System.Linq; +using System.Threading.Tasks; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Services.Safe; +using FluentResults; namespace Barotrauma.LuaCs.Services.Safe { @@ -59,6 +62,15 @@ namespace Barotrauma.LuaCs.Services.Safe _storageService?.PurgeCache(); } + public async Task)>>> CacheResourcesAsync(ImmutableArray resourceInfos) + { + // TODO: Needs an async lock? + IService.CheckDisposed(this); + if (!_storageService.UseCaching) + return FluentResults.Result.Fail($"Caching is not enabled."); + return await this._storageService.LoadPackageTextFilesAsync([..resourceInfos.SelectMany(ri => ri.FilePaths)]); + } + public override bool ScriptFileExists(string file) { ((IService)this).CheckDisposed(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageListRetrievalService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageListRetrievalService.cs deleted file mode 100644 index c534047a5..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageListRetrievalService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -namespace Barotrauma.LuaCs.Services; - -public interface IPackageListRetrievalService : IService -{ - IEnumerable GetEnabledContentPackages(); - IEnumerable GetAllContentPackages(); -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs index 3979ce69d..820613c41 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Threading.Tasks; using Barotrauma.Extensions; using Barotrauma.LuaCs.Data; +using FluentResults; namespace Barotrauma.LuaCs.Services; @@ -20,4 +21,5 @@ public interface IPackageManagementService : IReusableService public FluentResults.Result UnloadAllPackages(); public ImmutableArray GetAllLoadedPackages(); public bool IsPackageRunning(ContentPackage package); + public ImmutableArray GetLoadedAssemblyPackages(); }