From 6ac49a10f48c2d151ce06d5da1342afbba43393a Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 15 Feb 2026 06:01:59 -0500 Subject: [PATCH] - XML GUI Asset service implemented (alpha, requires testing). --- .../LuaCs/Data/StylesResources.cs | 17 + .../ClientSource/LuaCs/LuaCsSetup.cs | 10 + .../LuaCs/Services/IUIStylesCollection.cs | 75 ++++ .../LuaCs/Services/IUIStylesService.cs | 57 +++ .../ModConfigStylesFileParserService.cs | 44 +++ .../LuaCs/Services/UIStylesCollection.cs | 239 ++++++++++++ .../LuaCs/Services/UIStylesService.cs | 350 ++++++++++++++++++ .../WindowsClient.csproj.DotSettings | 2 + .../Data/DataInterfaceImplementations.cs | 2 +- .../SharedSource/LuaCs/Data/IModConfigInfo.cs | 2 +- .../SharedSource/LuaCs/LuaCsSetup.cs | 4 + .../_Services/ModConfigFileParserService.cs | 2 +- .../LuaCs/_Services/ModConfigService.cs | 39 +- .../_Services/PackageManagementService.cs | 45 ++- 14 files changed, 879 insertions(+), 9 deletions(-) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/StylesResources.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IUIStylesCollection.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IUIStylesService.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModConfigStylesFileParserService.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStylesCollection.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStylesService.cs create mode 100644 Barotrauma/BarotraumaClient/WindowsClient.csproj.DotSettings diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/StylesResources.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/StylesResources.cs new file mode 100644 index 000000000..a56d11112 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/StylesResources.cs @@ -0,0 +1,17 @@ +using System.Collections.Immutable; + +namespace Barotrauma.LuaCs.Data; + +public interface IStylesResourceInfo : IBaseResourceInfo { } + +public record StylesResourceInfo : BaseResourceInfo, IStylesResourceInfo { } + +public partial interface IModConfigInfo +{ + public ImmutableArray Styles { get; } +} + +public partial record ModConfigInfo +{ + public ImmutableArray Styles { get; init; } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index b24a584eb..d1fb680a0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text; using Barotrauma.CharacterEditor; using Barotrauma.LuaCs; +using Barotrauma.LuaCs.Data; // ReSharper disable ObjectCreationAsStatement @@ -66,6 +67,15 @@ namespace Barotrauma return isCsValueChanged; } + private void SetupServicesProviderClient(IServicesProvider serviceProvider) + { + serviceProvider.RegisterServiceType(ServiceLifetime.Singleton); + // supplied via factory + //serviceProvider.RegisterServiceType(ServiceLifetime.Transient); + serviceProvider.RegisterServiceType, ModConfigFileParserService>(ServiceLifetime.Transient); + serviceProvider.RegisterServiceType(ServiceLifetime.Transient); + } + /// /// Handles changes in game states tracked by screen changes. /// diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IUIStylesCollection.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IUIStylesCollection.cs new file mode 100644 index 000000000..3a8ec99d8 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IUIStylesCollection.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using Barotrauma.LuaCs.Data; +using FluentResults; + +namespace Barotrauma.LuaCs; + +public interface IUIStylesCollection : IService +{ + public interface IFactory : IService + { + /// + /// Returns a new for-each in the given + /// or empty is none. + /// + /// + /// + /// + IEnumerable CreateInstance(IStylesResourceInfo info, IStorageService storageService); + } + + /// + /// The assigned/target for this collection. + /// + public ContentPath Path { get; } + + /// + /// Gets the with the given name. + /// + /// + /// + public Result GetFont(string name); + /// + /// Gets the with the given name. + /// + /// + /// + public Result GetSprite(string name); + /// + /// Gets the with the given name. + /// + /// + /// + public Result GetSpriteSheet(string name); + /// + /// Gets the with the given name. + /// + /// + /// + public Result GetCursor(string name); + /// + /// Gets the with the given name. + /// + /// + /// + public Result GetColor(string name); + + #region BAROTRAUMA.UISTYLEFILE + + /// + /// Definition of + /// + internal void LoadFile(); + /// + /// Definition of + /// + internal void UnloadFile(); + /// + /// Definition of + /// + internal void Sort(); + + #endregion + +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IUIStylesService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IUIStylesService.cs new file mode 100644 index 000000000..4d09f1284 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IUIStylesService.cs @@ -0,0 +1,57 @@ +using System.Collections.Immutable; +using Barotrauma.LuaCs.Data; +using FluentResults; + +namespace Barotrauma.LuaCs; + +public interface IUIStylesService : IReusableService +{ + /// + /// Gets the first loaded . + /// + /// The target + /// The targets as specified in the ModConfig.xml. + /// The asset's name as specified in the styles XML file. + /// A indicating success, and the target if succeeded. + public Result GetColor(ContentPackage package, string internalName, string assetName); + /// + /// Gets the loaded . + /// + /// The target + /// The targets as specified in the ModConfig.xml. + /// The asset's name as specified in the styles XML file. + /// A indicating success, and the target if succeeded. + public Result GetCursor(ContentPackage package, string internalName, string assetName); + /// + /// Gets the loaded . + /// + /// The target + /// The targets as specified in the ModConfig.xml. + /// The asset's name as specified in the styles XML file. + /// A indicating success, and the target if succeeded. + public Result GetFont(ContentPackage package, string internalName, string assetName); + /// + /// Gets the loaded . + /// + /// The target + /// The targets as specified in the ModConfig.xml. + /// The asset's name as specified in the styles XML file. + /// A indicating success, and the target if succeeded. + public Result GetSprite(ContentPackage package, string internalName, string assetName); + /// + /// Gets the loaded . + /// + /// The target + /// The targets as specified in the ModConfig.xml. + /// The asset's name as specified in the styles XML file. + /// A indicating success, and the target if succeeded. + public Result GetSpriteSheet(ContentPackage package, string internalName, string assetName); + + public FluentResults.Result LoadAssets(ImmutableArray resources); + + public FluentResults.Result UnloadPackages(ImmutableArray packages); + + public FluentResults.Result UnloadPackage(ContentPackage package); + + public FluentResults.Result UnloadAllPackages(); +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModConfigStylesFileParserService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModConfigStylesFileParserService.cs new file mode 100644 index 000000000..ade7f4305 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModConfigStylesFileParserService.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; +using Barotrauma.LuaCs.Data; +using FluentResults; + +namespace Barotrauma.LuaCs; + +public sealed partial class ModConfigFileParserService : + IParserServiceAsync +{ + async Task> IParserServiceAsync.TryParseResourceAsync(ResourceParserInfo src) + { + using var lck = await _operationsLock.AcquireReaderLock(); + IService.CheckDisposed(this); + + if (CheckThrowNullRefs(src, "Style") is { IsFailed: true } fail) + return fail; + + var runtimeEnv = GetRuntimeEnvironment(src.Element); + var fileResults = await UnsafeGetCheckedFiles(src.Element, src.Owner, ".xml"); + + if (fileResults.IsFailed) + return FluentResults.Result.Fail(fileResults.Errors); + + return new StylesResourceInfo() + { + SupportedPlatforms = runtimeEnv.Platform, + SupportedTargets = Target.Client, // clientside only + LoadPriority = src.Element.GetAttributeInt("LoadPriority", 0), + FilePaths = fileResults.Value, + Optional = src.Element.GetAttributeBool("Optional", false), + InternalName = src.Element.GetAttributeString("Name", string.Empty), + OwnerPackage = src.Owner, + RequiredPackages = src.Required, + IncompatiblePackages = src.Incompatible + }; + } + + public async Task>> TryParseResourcesAsync(IEnumerable sources) + { + return await this.TryParseGenericResourcesAsync(sources); + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStylesCollection.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStylesCollection.cs new file mode 100644 index 000000000..6bf134d8c --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStylesCollection.cs @@ -0,0 +1,239 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using Barotrauma.Extensions; +using Barotrauma.LuaCs.Data; +using FluentResults; +using Microsoft.Toolkit.Diagnostics; + +namespace Barotrauma.LuaCs; + +public class UIStylesCollection : HashlessFile, IUIStylesCollection +{ + public class Factory : IUIStylesCollection.IFactory + { + public IEnumerable CreateInstance(IStylesResourceInfo info, IStorageService storageService) + { + Guard.IsNotNull(info, nameof(info)); + Guard.IsNotNull(info.OwnerPackage, nameof(info.OwnerPackage)); + if (info.FilePaths.IsDefaultOrEmpty) + { + return ImmutableArray.Empty; + } + + var builder = ImmutableArray.CreateBuilder(); + foreach (var contentPath in info.FilePaths) + { + builder.Add(new UIStylesCollection(contentPath, storageService)); + } + return builder.ToImmutable(); + } + + public void Dispose() + { + //ignore, stateless service + } + + public bool IsDisposed => false; + } + + private readonly ConcurrentDictionary _fonts = new(); + private readonly ConcurrentDictionary _sprites = new(); + private readonly ConcurrentDictionary _spriteSheets = new(); + private readonly ConcurrentDictionary _cursors = new(); + private readonly ConcurrentDictionary _colors = new(); + + /// + /// Only for internal reference. + /// + private UIStyleFile _fakeFile; + + private IStorageService _storageService; + + public UIStylesCollection(ContentPath path, IStorageService storageService) : base(path.ContentPackage, path) + { + Guard.IsNotNull(path, nameof(path)); + Guard.IsNotNull(path.ContentPackage, nameof(path.ContentPackage)); + _storageService = storageService; + _fakeFile = new UIStyleFile(path.ContentPackage, path); + } + + public new ContentPath Path => base.Path; + + public Result GetFont(string name) + { + using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + if (_fonts.TryGetValue(name, out var asset)) + { + return asset; + } + + return FluentResults.Result.Fail($"{nameof(GetFont)}: Failed to find the font with the name '{name}'"); + } + + public Result GetSprite(string name) + { + using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + if (_sprites.TryGetValue(name, out var asset)) + { + return asset; + } + + return FluentResults.Result.Fail($"{nameof(GetSprite)}: Failed to find the sprite with the name '{name}'"); + } + + public Result GetSpriteSheet(string name) + { + using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + if (_spriteSheets.TryGetValue(name, out var asset)) + { + return asset; + } + + return FluentResults.Result.Fail($"{nameof(GetSpriteSheet)}: Failed to find the spritesheet with the name '{name}'"); + } + + public Result GetCursor(string name) + { + using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + if (_cursors.TryGetValue(name, out var asset)) + { + return asset; + } + + return FluentResults.Result.Fail($"{nameof(GetCursor)}: Failed to find the cursor with the name '{name}'"); + } + + public Result GetColor(string name) + { + using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + if (_colors.TryGetValue(name, out var asset)) + { + return asset; + } + + return FluentResults.Result.Fail($"{nameof(GetColor)}: Failed to find the color with the name '{name}'"); + } + + public override void LoadFile() + { + using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + if (_storageService.LoadPackageXml(Path) is not { IsSuccess: true } result) + { + DebugConsole.LogError($"Failed to load xml from {Path.FullPath}."); + ThrowHelper.ThrowArgumentException($"Failed to load xml from {Path.FullPath}."); + return; + } + + var root = result.Value.Root?.FromPackage(Path.ContentPackage); + if (root is null) + { + return; + } + + var styleElement = root.Name.LocalName.ToLowerInvariant() == "style" ? root : root.GetChildElement("style"); + if (styleElement is null) + return; + + var childElements = styleElement.GetChildElements("Font"); + if (childElements is not null) + AddToList(_fonts, childElements, _fakeFile); + + childElements = styleElement.GetChildElements("Sprite"); + if (childElements is not null) + AddToList(_sprites, childElements, _fakeFile); + + childElements = styleElement.GetChildElements("Spritesheet"); + if (childElements is not null) + AddToList(_spriteSheets, childElements, _fakeFile); + + childElements = styleElement.GetChildElements("Cursor"); + if (childElements is not null) + AddToList(_cursors, childElements, _fakeFile); + + childElements = styleElement.GetChildElements("Color"); + if (childElements is not null) + AddToList(_colors, childElements, _fakeFile); + + void AddToList(ConcurrentDictionary dict, IEnumerable elem, UIStyleFile file) where T1 : GUISelector where T2 : GUIPrefab + { + foreach (ContentXElement prefabElement in elem) + { + string name = prefabElement.GetAttributeString("name", string.Empty); + if (name != string.Empty) + { + var prefab = (T2)Activator.CreateInstance(typeof(T2), new object[]{ prefabElement, file })!; + if (!dict.ContainsKey(name)) + dict[name] = (T1)Activator.CreateInstance(typeof(T1), new object[] { name })!; + dict[name].Prefabs.Add(prefab, false); + } + } + } + } + + public override void UnloadFile() + { + using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + _fonts.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile)); + _sprites.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile)); + _spriteSheets.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile)); + _cursors.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile)); + _colors.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile)); + } + + public override void Sort() + { + using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + _fonts.Values.ForEach(p => p.Prefabs.Sort()); + _sprites.Values.ForEach(p => p.Prefabs.Sort()); + _spriteSheets.Values.ForEach(p => p.Prefabs.Sort()); + _cursors.Values.ForEach(p => p.Prefabs.Sort()); + _colors.Values.ForEach(p => p.Prefabs.Sort()); + } + + #region INTERNAL_DISPOSE + + private readonly AsyncReaderWriterLock _lock = new(); + + public void Dispose() + { + using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) + { + return; + } + + _fonts.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile)); + _sprites.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile)); + _spriteSheets.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile)); + _cursors.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile)); + _colors.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile)); + + _fonts.Clear(); + _sprites.Clear(); + _spriteSheets.Clear(); + _cursors.Clear(); + _colors.Clear(); + } + + private int _isDisposed; + public bool IsDisposed + { + get => ModUtils.Threading.GetBool(ref _isDisposed); + private set => ModUtils.Threading.SetBool(ref _isDisposed, value); + } + + #endregion +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStylesService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStylesService.cs new file mode 100644 index 000000000..2508d7376 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStylesService.cs @@ -0,0 +1,350 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Immutable; +using System.Linq; +using Barotrauma.LuaCs.Data; +using FluentResults; +using Microsoft.Toolkit.Diagnostics; + +namespace Barotrauma.LuaCs; + +public class UIStylesService : IUIStylesService +{ + #region DISPOSAL + + public void Dispose() + { + using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) + { + return; + } + + foreach (var collection in _stylesCollections.Values.SelectMany(c => c)) + { + try + { + collection.Dispose(); + } + catch + { + //ignored + } + } + + _stylesCollections.Clear(); + _storageService.Dispose(); + _stylesCollectionFactory.Dispose(); + + _storageService = null; + _stylesCollectionFactory = null; + } + + private int _isDisposed = 0; + public bool IsDisposed + { + get => ModUtils.Threading.GetBool(ref _isDisposed); + private set => ModUtils.Threading.SetBool(ref _isDisposed, value); + } + public FluentResults.Result Reset() + { + using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + var result = FluentResults.Result.Ok(); + + foreach (var collection in _stylesCollections.Values.SelectMany(c => c)) + { + try + { + collection.Dispose(); + } + catch (Exception e) + { + result.WithError(new ExceptionalError(e)); + } + } + + _stylesCollections.Clear(); + + return result; + } + + private readonly AsyncReaderWriterLock _lock = new(); + + #endregion + + private IStorageService _storageService; + private IUIStylesCollection.IFactory _stylesCollectionFactory; + + private ConcurrentDictionary<(ContentPackage Package, string InternalName), ImmutableArray> + _stylesCollections = new(); + + public UIStylesService(IUIStylesCollection.IFactory stylesCollectionFactory, IStorageService storageService) + { + _stylesCollectionFactory = stylesCollectionFactory; + _storageService = storageService; + } + + public Result GetColor(ContentPackage package, string internalName, string assetName) + { + using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + Guard.IsNotNull(package, nameof(package)); + Guard.IsNotNullOrWhiteSpace(internalName, nameof(internalName)); + Guard.IsNotNullOrWhiteSpace(assetName, nameof(assetName)); + + if (!_stylesCollections.TryGetValue((package, internalName), out var collection) + || collection.IsDefaultOrEmpty) + { + return FluentResults.Result.Fail( + $"{nameof(UIStylesService)}: No styles loaded for [ContentPackage].[InternalName] of: [{package.Name}].[{internalName}]"); + } + + var failedResult = new FluentResults.Result(); + + foreach (var stylesCollection in collection) + { + var res = stylesCollection.GetColor(assetName); + if (res.IsSuccess) + { + return res; + } + + failedResult.WithErrors(res.Errors); + } + + return failedResult; + } + + public Result GetCursor(ContentPackage package, string internalName, string assetName) + { + using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + Guard.IsNotNull(package, nameof(package)); + Guard.IsNotNullOrWhiteSpace(internalName, nameof(internalName)); + Guard.IsNotNullOrWhiteSpace(assetName, nameof(assetName)); + + if (!_stylesCollections.TryGetValue((package, internalName), out var collection) + || collection.IsDefaultOrEmpty) + { + return FluentResults.Result.Fail( + $"{nameof(UIStylesService)}: No styles loaded for [ContentPackage].[InternalName] of: [{package.Name}].[{internalName}]"); + } + + var failedResult = new FluentResults.Result(); + + foreach (var stylesCollection in collection) + { + var res = stylesCollection.GetCursor(assetName); + if (res.IsSuccess) + { + return res; + } + + failedResult.WithErrors(res.Errors); + } + + return failedResult; + } + + public Result GetFont(ContentPackage package, string internalName, string assetName) + { + using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + Guard.IsNotNull(package, nameof(package)); + Guard.IsNotNullOrWhiteSpace(internalName, nameof(internalName)); + Guard.IsNotNullOrWhiteSpace(assetName, nameof(assetName)); + + if (!_stylesCollections.TryGetValue((package, internalName), out var collection) + || collection.IsDefaultOrEmpty) + { + return FluentResults.Result.Fail( + $"{nameof(UIStylesService)}: No styles loaded for [ContentPackage].[InternalName] of: [{package.Name}].[{internalName}]"); + } + + var failedResult = new FluentResults.Result(); + + foreach (var stylesCollection in collection) + { + var res = stylesCollection.GetFont(assetName); + if (res.IsSuccess) + { + return res; + } + + failedResult.WithErrors(res.Errors); + } + + return failedResult; + } + + public Result GetSprite(ContentPackage package, string internalName, string assetName) + { + using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + Guard.IsNotNull(package, nameof(package)); + Guard.IsNotNullOrWhiteSpace(internalName, nameof(internalName)); + Guard.IsNotNullOrWhiteSpace(assetName, nameof(assetName)); + + if (!_stylesCollections.TryGetValue((package, internalName), out var collection) + || collection.IsDefaultOrEmpty) + { + return FluentResults.Result.Fail( + $"{nameof(UIStylesService)}: No styles loaded for [ContentPackage].[InternalName] of: [{package.Name}].[{internalName}]"); + } + + var failedResult = new FluentResults.Result(); + + foreach (var stylesCollection in collection) + { + var res = stylesCollection.GetSprite(assetName); + if (res.IsSuccess) + { + return res; + } + + failedResult.WithErrors(res.Errors); + } + + return failedResult; + } + + public Result GetSpriteSheet(ContentPackage package, string internalName, string assetName) + { + using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + Guard.IsNotNull(package, nameof(package)); + Guard.IsNotNullOrWhiteSpace(internalName, nameof(internalName)); + Guard.IsNotNullOrWhiteSpace(assetName, nameof(assetName)); + + if (!_stylesCollections.TryGetValue((package, internalName), out var collection) + || collection.IsDefaultOrEmpty) + { + return FluentResults.Result.Fail( + $"{nameof(UIStylesService)}: No styles loaded for [ContentPackage].[InternalName] of: [{package.Name}].[{internalName}]"); + } + + var failedResult = new FluentResults.Result(); + + foreach (var stylesCollection in collection) + { + var res = stylesCollection.GetSpriteSheet(assetName); + if (res.IsSuccess) + { + return res; + } + + failedResult.WithErrors(res.Errors); + } + + return failedResult; + } + + public FluentResults.Result LoadAssets(ImmutableArray resources) + { + using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + if (resources.IsDefaultOrEmpty) + { + ThrowHelper.ThrowArgumentNullException(nameof(resources)); + } + + var operationSuccess = FluentResults.Result.Ok(); + + foreach (var resource in resources) + { + var builder = ImmutableArray.CreateBuilder(); + if (_stylesCollections.TryGetValue((resource.OwnerPackage, resource.InternalName), out var collection)) + { + builder.AddRange(collection); + } + + try + { + var newCollections = _stylesCollectionFactory.CreateInstance(resource, _storageService).ToImmutableArray(); + foreach (var stylesCollection in newCollections) + { + stylesCollection.LoadFile(); + } + builder.AddRange(newCollections); + } + catch (Exception e) + { + operationSuccess.WithError(new ExceptionalError(e)); + continue; + } + + _stylesCollections[(resource.OwnerPackage, resource.InternalName)] = builder.ToImmutable(); + } + + return operationSuccess; + } + + public FluentResults.Result UnloadPackages(ImmutableArray packages) + { + using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + var toRemove = _stylesCollections + .Select(c => c.Key) + .Where(c => packages.Contains(c.Package)) + .ToImmutableArray(); + + var result = FluentResults.Result.Ok(); + + foreach (var key in toRemove) + { + if (_stylesCollections.TryRemove(key, out var collection) && !collection.IsDefaultOrEmpty) + { + foreach (var stylesCollection in collection) + { + try + { + stylesCollection.UnloadFile(); + } + catch (Exception e) + { + result.WithError(new ExceptionalError(e)); + } + } + } + } + + return result; + } + + public FluentResults.Result UnloadPackage(ContentPackage package) + { + // Yes, this is very cursed/inefficient. We don't care. + return UnloadPackages(new [] { package }.ToImmutableArray()); + } + + public FluentResults.Result UnloadAllPackages() + { + using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + var result = FluentResults.Result.Ok(); + + foreach (var key in _stylesCollections.Keys.ToImmutableArray()) + { + if (_stylesCollections.TryRemove(key, out var collection) && !collection.IsDefaultOrEmpty) + { + foreach (var stylesCollection in collection) + { + try + { + stylesCollection.UnloadFile(); + } + catch (Exception e) + { + result.WithError(new ExceptionalError(e)); + } + } + } + } + + return result; + } +} diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj.DotSettings b/Barotrauma/BarotraumaClient/WindowsClient.csproj.DotSettings new file mode 100644 index 000000000..051269cc3 --- /dev/null +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs index 4a3ec7268..79c30579b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs @@ -15,7 +15,7 @@ namespace Barotrauma.LuaCs.Data; #region ModConfigurationInfo -public record ModConfigInfo : IModConfigInfo +public partial record ModConfigInfo : IModConfigInfo { public ContentPackage Package { get; init; } public ImmutableArray Assemblies { get; init; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs index 70cb2d6d7..c8740e51d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs @@ -4,7 +4,7 @@ using System.Xml.Linq; namespace Barotrauma.LuaCs.Data; -public interface IModConfigInfo : IAssembliesResourcesInfo, +public partial interface IModConfigInfo : IAssembliesResourcesInfo, ILuaScriptsResourcesInfo, IConfigsResourcesInfo { // package info diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 1946fba81..b7e5b1f3b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -209,6 +209,10 @@ namespace Barotrauma servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); +#if CLIENT + SetupServicesProviderClient(servicesProvider); +#endif + // gen IL servicesProvider.CompileAndRun(); return servicesProvider; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigFileParserService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigFileParserService.cs index 33569f85b..530632e22 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigFileParserService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigFileParserService.cs @@ -10,7 +10,7 @@ using Microsoft.Toolkit.Diagnostics; namespace Barotrauma.LuaCs; -public sealed class ModConfigFileParserService : +public sealed partial class ModConfigFileParserService : IParserServiceAsync, IParserServiceAsync, IParserServiceAsync diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs index e4bc8897a..6150ec52b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs @@ -23,12 +23,18 @@ public sealed class ModConfigService : IModConfigService private IParserServiceAsync _assemblyParserService; private IParserServiceAsync _luaScriptParserService; private IParserServiceAsync _configParserService; +#if CLIENT + private IParserServiceAsync _stylesParserService; +#endif private readonly AsyncReaderWriterLock _operationsLock = new(); public ModConfigService(IStorageService storageService, IParserServiceAsync assemblyParserService, IParserServiceAsync luaScriptParserService, - IParserServiceAsync configParserService, + IParserServiceAsync configParserService, +#if CLIENT + IParserServiceAsync stylesParserService, +#endif ILoggerService logger) { _storageService = storageService; @@ -36,6 +42,9 @@ public sealed class ModConfigService : IModConfigService _luaScriptParserService = luaScriptParserService; _configParserService = configParserService; _logger = logger; +#if CLIENT + _stylesParserService = stylesParserService; +#endif } #region Dispose @@ -59,6 +68,11 @@ public sealed class ModConfigService : IModConfigService _assemblyParserService = null; _luaScriptParserService = null; _configParserService = null; + +#if CLIENT + _stylesParserService.Dispose(); + _stylesParserService = null; +#endif } catch { @@ -130,14 +144,26 @@ public sealed class ModConfigService : IModConfigService var asmTask = Task.Factory.StartNew(async () => await GetAssembliesFromXml(owner, src)); var cfgTask = Task.Factory.StartNew(async () => await GetConfigsFromXml(owner, src)); var luaTask = Task.Factory.StartNew(async () => await GetLuaScriptsFromXml(owner, src)); +#if CLIENT + var styleTask = Task.Factory.StartNew(async () => await GetStylesFromXml(owner, src)); +#endif - await Task.WhenAll(asmTask, cfgTask, luaTask); + await Task.WhenAll( + asmTask, + cfgTask, +#if CLIENT + styleTask, +#endif + luaTask); return FluentResults.Result.Ok(new ModConfigInfo() { Package = owner, Assemblies = await await asmTask, Configs = await await cfgTask, +#if CLIENT + Styles = await await styleTask, +#endif LuaScripts = await await luaTask }); @@ -151,7 +177,6 @@ public sealed class ModConfigService : IModConfigService XElement cfgElement) { return await GetResourceFromXml(contentPackage, cfgElement, "Config", "FileGroup", _configParserService); - } async Task> GetAssembliesFromXml(ContentPackage contentPackage, @@ -159,6 +184,14 @@ public sealed class ModConfigService : IModConfigService { return await GetResourceFromXml(contentPackage, cfgElement, "Assembly", "FileGroup", _assemblyParserService); } + +#if CLIENT + async Task> GetStylesFromXml(ContentPackage contentPackage, + XElement cfgElement) + { + return await GetResourceFromXml(contentPackage, cfgElement, "Style", "FileGroup", _stylesParserService); + } +#endif async Task> GetResourceFromXml(ContentPackage contentPackage, XElement cfgElement, string elemName, string fileGroupName, IParserServiceAsync resourceService) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs index 5483fab56..a5e8218be 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs @@ -20,6 +20,9 @@ public sealed class PackageManagementService : IPackageManagementService private IConfigService _configService; private ILuaScriptManagementService _luaScriptManagementService; private IPluginManagementService _pluginManagementService; +#if CLIENT + private IUIStylesService _uiStylesService; +#endif private IPackageManagementServiceConfig _runConfig; // state private readonly ConcurrentDictionary _loadedPackages = new(); @@ -40,7 +43,11 @@ public sealed class PackageManagementService : IPackageManagementService IModConfigService modConfigService, ILuaScriptManagementService luaScriptManagementService, IPluginManagementService pluginManagementService, - IConfigService configService, IPackageManagementServiceConfig runConfig) + IConfigService configService, +#if CLIENT + IUIStylesService uiStylesService, +#endif + IPackageManagementServiceConfig runConfig) { _logger = logger; _modConfigService = modConfigService; @@ -48,6 +55,9 @@ public sealed class PackageManagementService : IPackageManagementService _pluginManagementService = pluginManagementService; _configService = configService; _runConfig = runConfig; +#if CLIENT + _uiStylesService = uiStylesService; +#endif } public void Dispose() @@ -61,11 +71,19 @@ public sealed class PackageManagementService : IPackageManagementService _pluginManagementService.Dispose(); _modConfigService.Dispose(); _logger.Dispose(); +#if CLIENT + _uiStylesService.Dispose(); +#endif _logger = null; _luaScriptManagementService = null; _pluginManagementService = null; _modConfigService = null; +#if CLIENT + _uiStylesService = null; +#endif + + _loadedPackages.Clear(); _runningPackages.Clear(); } @@ -86,9 +104,13 @@ public sealed class PackageManagementService : IPackageManagementService try { var operationResult = new FluentResults.Result(); - operationResult.WithReasons(_configService.Reset().Reasons); + operationResult.WithReasons(_luaScriptManagementService.Reset().Reasons); operationResult.WithReasons(_pluginManagementService.Reset().Reasons); + operationResult.WithReasons(_configService.Reset().Reasons); +#if CLIENT + operationResult.WithReasons(_uiStylesService.Reset().Reasons); +#endif _runningPackages.Clear(); _loadedPackages.Clear(); return operationResult; @@ -205,11 +227,19 @@ public sealed class PackageManagementService : IPackageManagementService { return FluentResults.Result.Ok(); } - + +#if CLIENT + if (!config.Styles.IsDefaultOrEmpty) + { + res.WithReasons(_uiStylesService.LoadAssets(config.Styles).Reasons); + } +#endif var r = Task.WhenAll(tasks.ToArray()).ConfigureAwait(false).GetAwaiter().GetResult(); foreach (var task in r) + { res.WithReasons(task.ConfigureAwait(false).GetAwaiter().GetResult().Reasons); + } return res; } catch (Exception e) @@ -363,12 +393,19 @@ public sealed class PackageManagementService : IPackageManagementService IService.CheckDisposed(this); if (!_loadedPackages.ContainsKey(package)) + { return FluentResults.Result.Fail($"{nameof(UnloadPackage)}: The package is not loaded."); + } if (!_runningPackages.IsEmpty) + { return FluentResults.Result.Fail($"{nameof(UnloadPackage)}: Packages are currently executing."); + } var result = new FluentResults.Result(); result.WithReasons(_luaScriptManagementService.DisposePackageResources(package).Reasons); result.WithReasons(_configService.DisposePackageData(package).Reasons); +#if CLIENT + result.WithReasons(_uiStylesService.UnloadPackage(package).Reasons); +#endif _loadedPackages.TryRemove(package, out _); return result; } @@ -384,7 +421,9 @@ public sealed class PackageManagementService : IPackageManagementService var result = new FluentResults.Result(); foreach (var package in packages) + { result.WithReasons(UnloadPackage(package).Reasons); + } return result; }