- XML GUI Asset service implemented (alpha, requires testing).
This commit is contained in:
@@ -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<IStylesResourceInfo> Styles { get; }
|
||||
}
|
||||
|
||||
public partial record ModConfigInfo
|
||||
{
|
||||
public ImmutableArray<IStylesResourceInfo> Styles { get; init; }
|
||||
}
|
||||
@@ -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<IUIStylesService, UIStylesService>(ServiceLifetime.Singleton);
|
||||
// supplied via factory
|
||||
//serviceProvider.RegisterServiceType<IUIStylesCollection, UIStylesCollection>(ServiceLifetime.Transient);
|
||||
serviceProvider.RegisterServiceType<IParserServiceAsync<ResourceParserInfo, IStylesResourceInfo>, ModConfigFileParserService>(ServiceLifetime.Transient);
|
||||
serviceProvider.RegisterServiceType<IUIStylesCollection.IFactory, UIStylesCollection.Factory>(ServiceLifetime.Transient);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles changes in game states tracked by screen changes.
|
||||
/// </summary>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a new <see cref="IUIStylesCollection"/> for-each <see cref="ContentPath"/> in the given
|
||||
/// <see cref="IStylesResourceInfo.FilePaths"/> or empty is none.
|
||||
/// </summary>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="storageService"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<IUIStylesCollection> CreateInstance(IStylesResourceInfo info, IStorageService storageService);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The assigned/target <see cref="ContentPath"/> for this collection.
|
||||
/// </summary>
|
||||
public ContentPath Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="GUIFont"/> with the given name.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public Result<GUIFont> GetFont(string name);
|
||||
/// <summary>
|
||||
/// Gets the <see cref="GUISprite"/> with the given name.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public Result<GUISprite> GetSprite(string name);
|
||||
/// <summary>
|
||||
/// Gets the <see cref="GUISpriteSheet"/> with the given name.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public Result<GUISpriteSheet> GetSpriteSheet(string name);
|
||||
/// <summary>
|
||||
/// Gets the <see cref="GUICursor"/> with the given name.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public Result<GUICursor> GetCursor(string name);
|
||||
/// <summary>
|
||||
/// Gets the <see cref="GUIColor"/> with the given name.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public Result<GUIColor> GetColor(string name);
|
||||
|
||||
#region BAROTRAUMA.UISTYLEFILE
|
||||
|
||||
/// <summary>
|
||||
/// Definition of <see cref="HashlessFile.LoadFile"/>
|
||||
/// </summary>
|
||||
internal void LoadFile();
|
||||
/// <summary>
|
||||
/// Definition of <see cref="HashlessFile.UnloadFile"/>
|
||||
/// </summary>
|
||||
internal void UnloadFile();
|
||||
/// <summary>
|
||||
/// Definition of <see cref="HashlessFile.Sort"/>
|
||||
/// </summary>
|
||||
internal void Sort();
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Collections.Immutable;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using FluentResults;
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public interface IUIStylesService : IReusableService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the first loaded <see cref="GUIColor"/>.
|
||||
/// </summary>
|
||||
/// <param name="package">The target <see cref="ContentPackage"/></param>
|
||||
/// <param name="internalName">The targets <see cref="IDataInfo.InternalName"/> as specified in the ModConfig.xml.</param>
|
||||
/// <param name="assetName">The asset's name as specified in the styles XML file.</param>
|
||||
/// <returns>A <see cref="FluentResults.Result"/> indicating success, and the target if succeeded.</returns>
|
||||
public Result<GUIColor> GetColor(ContentPackage package, string internalName, string assetName);
|
||||
/// <summary>
|
||||
/// Gets the loaded <see cref="GUICursor"/>.
|
||||
/// </summary>
|
||||
/// <param name="package">The target <see cref="ContentPackage"/></param>
|
||||
/// <param name="internalName">The targets <see cref="IDataInfo.InternalName"/> as specified in the ModConfig.xml.</param>
|
||||
/// <param name="assetName">The asset's name as specified in the styles XML file.</param>
|
||||
/// <returns>A <see cref="FluentResults.Result"/> indicating success, and the target if succeeded.</returns>
|
||||
public Result<GUICursor> GetCursor(ContentPackage package, string internalName, string assetName);
|
||||
/// <summary>
|
||||
/// Gets the loaded <see cref="GUIFont"/>.
|
||||
/// </summary>
|
||||
/// <param name="package">The target <see cref="ContentPackage"/></param>
|
||||
/// <param name="internalName">The targets <see cref="IDataInfo.InternalName"/> as specified in the ModConfig.xml.</param>
|
||||
/// <param name="assetName">The asset's name as specified in the styles XML file.</param>
|
||||
/// <returns>A <see cref="FluentResults.Result"/> indicating success, and the target if succeeded.</returns>
|
||||
public Result<GUIFont> GetFont(ContentPackage package, string internalName, string assetName);
|
||||
/// <summary>
|
||||
/// Gets the loaded <see cref="GUISprite"/>.
|
||||
/// </summary>
|
||||
/// <param name="package">The target <see cref="ContentPackage"/></param>
|
||||
/// <param name="internalName">The targets <see cref="IDataInfo.InternalName"/> as specified in the ModConfig.xml.</param>
|
||||
/// <param name="assetName">The asset's name as specified in the styles XML file.</param>
|
||||
/// <returns>A <see cref="FluentResults.Result"/> indicating success, and the target if succeeded.</returns>
|
||||
public Result<GUISprite> GetSprite(ContentPackage package, string internalName, string assetName);
|
||||
/// <summary>
|
||||
/// Gets the loaded <see cref="GUISpriteSheet"/>.
|
||||
/// </summary>
|
||||
/// <param name="package">The target <see cref="ContentPackage"/></param>
|
||||
/// <param name="internalName">The targets <see cref="IDataInfo.InternalName"/> as specified in the ModConfig.xml.</param>
|
||||
/// <param name="assetName">The asset's name as specified in the styles XML file.</param>
|
||||
/// <returns>A <see cref="FluentResults.Result"/> indicating success, and the target if succeeded.</returns>
|
||||
public Result<GUISpriteSheet> GetSpriteSheet(ContentPackage package, string internalName, string assetName);
|
||||
|
||||
public FluentResults.Result LoadAssets(ImmutableArray<IStylesResourceInfo> resources);
|
||||
|
||||
public FluentResults.Result UnloadPackages(ImmutableArray<ContentPackage> packages);
|
||||
|
||||
public FluentResults.Result UnloadPackage(ContentPackage package);
|
||||
|
||||
public FluentResults.Result UnloadAllPackages();
|
||||
}
|
||||
@@ -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<ResourceParserInfo, IStylesResourceInfo>
|
||||
{
|
||||
async Task<Result<IStylesResourceInfo>> IParserServiceAsync<ResourceParserInfo, IStylesResourceInfo>.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<ImmutableArray<Result<IStylesResourceInfo>>> TryParseResourcesAsync(IEnumerable<ResourceParserInfo> sources)
|
||||
{
|
||||
return await this.TryParseGenericResourcesAsync<IStylesResourceInfo>(sources);
|
||||
}
|
||||
}
|
||||
@@ -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<IUIStylesCollection> CreateInstance(IStylesResourceInfo info, IStorageService storageService)
|
||||
{
|
||||
Guard.IsNotNull(info, nameof(info));
|
||||
Guard.IsNotNull(info.OwnerPackage, nameof(info.OwnerPackage));
|
||||
if (info.FilePaths.IsDefaultOrEmpty)
|
||||
{
|
||||
return ImmutableArray<IUIStylesCollection>.Empty;
|
||||
}
|
||||
|
||||
var builder = ImmutableArray.CreateBuilder<IUIStylesCollection>();
|
||||
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<string, GUIFont> _fonts = new();
|
||||
private readonly ConcurrentDictionary<string, GUISprite> _sprites = new();
|
||||
private readonly ConcurrentDictionary<string, GUISpriteSheet> _spriteSheets = new();
|
||||
private readonly ConcurrentDictionary<string, GUICursor> _cursors = new();
|
||||
private readonly ConcurrentDictionary<string, GUIColor> _colors = new();
|
||||
|
||||
/// <summary>
|
||||
/// Only for internal reference.
|
||||
/// </summary>
|
||||
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<GUIFont> 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<GUISprite> 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<GUISpriteSheet> 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<GUICursor> 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<GUIColor> 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<GUIFont, GUIFontPrefab>(_fonts, childElements, _fakeFile);
|
||||
|
||||
childElements = styleElement.GetChildElements("Sprite");
|
||||
if (childElements is not null)
|
||||
AddToList<GUISprite, GUISpritePrefab>(_sprites, childElements, _fakeFile);
|
||||
|
||||
childElements = styleElement.GetChildElements("Spritesheet");
|
||||
if (childElements is not null)
|
||||
AddToList<GUISpriteSheet, GUISpriteSheetPrefab>(_spriteSheets, childElements, _fakeFile);
|
||||
|
||||
childElements = styleElement.GetChildElements("Cursor");
|
||||
if (childElements is not null)
|
||||
AddToList<GUICursor, GUICursorPrefab>(_cursors, childElements, _fakeFile);
|
||||
|
||||
childElements = styleElement.GetChildElements("Color");
|
||||
if (childElements is not null)
|
||||
AddToList<GUIColor, GUIColorPrefab>(_colors, childElements, _fakeFile);
|
||||
|
||||
void AddToList<T1, T2>(ConcurrentDictionary<string, T1> dict, IEnumerable<ContentXElement> elem, UIStyleFile file) where T1 : GUISelector<T2> 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
|
||||
}
|
||||
@@ -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<IUIStylesCollection>>
|
||||
_stylesCollections = new();
|
||||
|
||||
public UIStylesService(IUIStylesCollection.IFactory stylesCollectionFactory, IStorageService storageService)
|
||||
{
|
||||
_stylesCollectionFactory = stylesCollectionFactory;
|
||||
_storageService = storageService;
|
||||
}
|
||||
|
||||
public Result<GUIColor> 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<GUICursor> 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<GUIFont> 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<GUISprite> 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<GUISpriteSheet> 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<IStylesResourceInfo> 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<IUIStylesCollection>();
|
||||
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<ContentPackage> 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=clientsource/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -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<IAssemblyResourceInfo> Assemblies { get; init; }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -209,6 +209,10 @@ namespace Barotrauma
|
||||
servicesProvider.RegisterServiceType<IConfigServiceConfig, ConfigServiceConfig>(ServiceLifetime.Singleton);
|
||||
servicesProvider.RegisterServiceType<IPackageManagementServiceConfig, PackageManagementServiceConfig>(ServiceLifetime.Singleton);
|
||||
|
||||
#if CLIENT
|
||||
SetupServicesProviderClient(servicesProvider);
|
||||
#endif
|
||||
|
||||
// gen IL
|
||||
servicesProvider.CompileAndRun();
|
||||
return servicesProvider;
|
||||
|
||||
@@ -10,7 +10,7 @@ using Microsoft.Toolkit.Diagnostics;
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public sealed class ModConfigFileParserService :
|
||||
public sealed partial class ModConfigFileParserService :
|
||||
IParserServiceAsync<ResourceParserInfo, IAssemblyResourceInfo>,
|
||||
IParserServiceAsync<ResourceParserInfo, ILuaScriptResourceInfo>,
|
||||
IParserServiceAsync<ResourceParserInfo, IConfigResourceInfo>
|
||||
|
||||
@@ -23,12 +23,18 @@ public sealed class ModConfigService : IModConfigService
|
||||
private IParserServiceAsync<ResourceParserInfo, IAssemblyResourceInfo> _assemblyParserService;
|
||||
private IParserServiceAsync<ResourceParserInfo, ILuaScriptResourceInfo> _luaScriptParserService;
|
||||
private IParserServiceAsync<ResourceParserInfo, IConfigResourceInfo> _configParserService;
|
||||
#if CLIENT
|
||||
private IParserServiceAsync<ResourceParserInfo, IStylesResourceInfo> _stylesParserService;
|
||||
#endif
|
||||
private readonly AsyncReaderWriterLock _operationsLock = new();
|
||||
|
||||
public ModConfigService(IStorageService storageService,
|
||||
IParserServiceAsync<ResourceParserInfo, IAssemblyResourceInfo> assemblyParserService,
|
||||
IParserServiceAsync<ResourceParserInfo, ILuaScriptResourceInfo> luaScriptParserService,
|
||||
IParserServiceAsync<ResourceParserInfo, IConfigResourceInfo> configParserService,
|
||||
IParserServiceAsync<ResourceParserInfo, IConfigResourceInfo> configParserService,
|
||||
#if CLIENT
|
||||
IParserServiceAsync<ResourceParserInfo, IStylesResourceInfo> 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<IModConfigInfo>(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<IConfigResourceInfo>(contentPackage, cfgElement, "Config", "FileGroup", _configParserService);
|
||||
|
||||
}
|
||||
|
||||
async Task<ImmutableArray<IAssemblyResourceInfo>> GetAssembliesFromXml(ContentPackage contentPackage,
|
||||
@@ -159,6 +184,14 @@ public sealed class ModConfigService : IModConfigService
|
||||
{
|
||||
return await GetResourceFromXml<IAssemblyResourceInfo>(contentPackage, cfgElement, "Assembly", "FileGroup", _assemblyParserService);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
async Task<ImmutableArray<IStylesResourceInfo>> GetStylesFromXml(ContentPackage contentPackage,
|
||||
XElement cfgElement)
|
||||
{
|
||||
return await GetResourceFromXml<IStylesResourceInfo>(contentPackage, cfgElement, "Style", "FileGroup", _stylesParserService);
|
||||
}
|
||||
#endif
|
||||
|
||||
async Task<ImmutableArray<T>> GetResourceFromXml<T>(ContentPackage contentPackage, XElement cfgElement, string elemName, string fileGroupName, IParserServiceAsync<ResourceParserInfo, T> resourceService)
|
||||
{
|
||||
|
||||
@@ -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<ContentPackage, IModConfigInfo> _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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user