[Milestone] PackageManagementService completed.

- ContentPackageInfoLookup Service completed.
- Implemented ModConfigService.cs
- Implemented some of the resource processors.
This commit is contained in:
MapleWheels
2025-02-26 12:48:34 -05:00
committed by Maplewheels
parent cb88d215fa
commit 52d920d969
78 changed files with 2331 additions and 422 deletions
@@ -0,0 +1,6 @@
namespace Barotrauma.LuaCs.Services;
public class ConfigService : IConfigService
{
}
@@ -0,0 +1,383 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Barotrauma.LuaCs.Data;
using Barotrauma.LuaCs.Events;
using Barotrauma.Steam;
using FluentResults;
using OneOf;
namespace Barotrauma.LuaCs.Services;
/// <summary>
/// Provides <see cref="IPackageInfo"/> resolution for dynamically locating the best matching package at the time of consumption.
/// </summary>
public sealed class ContentPackageInfoLookup : IPackageInfoLookupService, IEventEnabledPackageListChanged, IEventAllPackageListChanged
{
#region INTERNAL
// packageinfo query data
private readonly ConcurrentDictionary<OneOf.OneOf<string, ulong, (string, ulong)>, IPackageInfo> _packageInfoMap = new();
// package query data
private readonly ConcurrentDictionary<uint, ImmutableArray<ContentPackage>> _packageIdGroups = new();
private readonly ConcurrentDictionary<ContentPackage, ImmutableArray<uint>> _reversePackageIdGroups = new();
private readonly HashSet<ContentPackage> _enabledPackages;
private readonly HashSet<ContentPackage> _allPackages;
// threading
private readonly AsyncReaderWriterLock _packageIdGroupsLock = new();
private readonly AsyncReaderWriterLock _packageSetsLock = new();
// services
private readonly IEventService _eventService;
private readonly IPackageListRetrievalService _packageListRetrievalService;
private int _isDisposed = 0;
private uint _idCounter = 0;
// returns ++_idCounter;
private uint GetNextId() => Interlocked.Increment(ref _idCounter);
private ContentPackage GetBestMatchPackage(IPackageInfo packageInfo)
{
if (packageInfo is null)
return null;
if (!_packageIdGroups.TryGetValue(packageInfo.Id, out var packageGroup)
|| packageGroup.IsDefaultOrEmpty)
return null;
if (packageGroup.Length == 1)
return packageGroup[0];
bool nameGood = !packageInfo.Name.IsNullOrWhiteSpace();
// try by enabled
var prev = packageGroup;
var packList = packageGroup;
using (_packageSetsLock.AcquireReaderLock().GetAwaiter().GetResult())
{
packList = packList
.Where(p => p is not null && _enabledPackages.Contains(p))
.ToImmutableArray();
}
if (ReturnValue())
return packList[0];
// try by steam id
if (packageInfo.SteamWorkshopId != 0)
{
packList = packList
.Where(p => p.TryExtractSteamWorkshopId(out var sId) && sId.Value == packageInfo.SteamWorkshopId)
.ToImmutableArray();
if (ReturnValue())
return packList[0];
}
// try by name
if (nameGood)
{
packList = packList
.Where(p => p.Name == packageInfo.Name)
.ToImmutableArray();
if (ReturnValue())
return packList[0];
}
// try by localmods
packList = packList.Where(p => p.Path.ToLowerInvariant().Contains("localmods"))
.ToImmutableArray();
if (ReturnValue())
return packList[0];
// get the first in the list
return packList.First();
bool ReturnValue()
{
if (packList.IsDefaultOrEmpty)
packList = prev;
else if (packList.Length == 1)
return true;
else
prev = packList;
return false;
}
}
private async Task SyncPackagesLists(IReadOnlyList<ContentPackage> enabledPackages,
IReadOnlyList<ContentPackage> allPackages)
{
if (enabledPackages is null || allPackages is null)
return;
// take all locks
using var l1 = await _packageIdGroupsLock.AcquireWriterLock();
using var l2 = await _packageSetsLock.AcquireWriterLock();
// calc diffs
var toAddAll = allPackages.Except(_allPackages).ToHashSet();
var toAddEnabled = enabledPackages.Except(_enabledPackages).ToHashSet();
var toRemoveAll = _allPackages.Except(allPackages).ToHashSet();
var toRemoveEnabled = _enabledPackages.Except(enabledPackages).ToHashSet();
// remove old
if (toRemoveAll.Any())
{
foreach (var package in toRemoveAll)
{
if (package is null)
continue;
_allPackages.Remove(package);
// try to find id lookup
if (!_reversePackageIdGroups.TryGetValue(package, out var idGroup))
continue;
// found packs
if (!idGroup.IsDefaultOrEmpty)
{
foreach (var id in idGroup)
{
if (!_packageIdGroups.TryGetValue(id, out var packageGroup)
|| packageGroup.IsDefaultOrEmpty)
continue;
_packageIdGroups[id] = packageGroup.RemoveAll(p => toRemoveAll.Contains(p));
}
}
// remove ref
_reversePackageIdGroups.Remove(package, out _);
}
}
if (toRemoveEnabled.Any())
{
foreach (var package in toRemoveEnabled)
{
if (package is null)
continue;
_enabledPackages.Remove(package);
}
}
// add new
if (toAddAll.Any())
{
foreach (var package in toAddAll)
{
if (package is null)
continue;
_allPackages.Add(package);
var steamId = package.TryExtractSteamWorkshopId(out var id) ? id.Value : 0;
IPackageInfo packageInfo;
Queue<uint> idListsToAdd = new();
if (!package.Name.IsNullOrWhiteSpace() && steamId > 0)
{
// combined key
packageInfo = GetOrCreateInfoForMap(package, (package.Name, steamId));
AddToPackageIdGroups(packageInfo.Id, package);
// string key
packageInfo = GetOrCreateInfoForMap(package, package.Name);
AddToPackageIdGroups(packageInfo.Id, package);
// steamId key
packageInfo = GetOrCreateInfoForMap(package, steamId);
AddToPackageIdGroups(packageInfo.Id, package);
}
// try find in the existing list, or make a new one
IPackageInfo GetOrCreateInfoForMap(ContentPackage package, OneOf.OneOf<string, ulong, (string, ulong)> infoKey)
{
return _packageInfoMap.TryGetValue(infoKey, out var pInfo)
? pInfo
: new PackageInfo(package, GetNextId(), GetBestMatchPackage);
}
// add to package lookups
void AddToPackageIdGroups(uint id, ContentPackage package)
{
if (_packageIdGroups.TryGetValue(id, out var packageGroup))
{
if (!packageGroup.Contains(package))
_packageIdGroups[id] = packageGroup.Add(package);
}
else
_packageIdGroups[id] = new[] { package }.ToImmutableArray();
if (_reversePackageIdGroups.TryGetValue(package, out var idGroup))
{
if (!idGroup.Contains(id))
_reversePackageIdGroups[package] = idGroup.Add(id);
}
else
_reversePackageIdGroups[package] = new[] { id }.ToImmutableArray();
}
}
}
if (toAddEnabled.Any())
{
foreach (var package in toAddEnabled)
{
if (package is null)
continue;
_enabledPackages.Add(package);
}
}
}
private async Task<Result<IPackageInfo>> LookupInternal(OneOf.OneOf<string, ulong, (string, ulong)> infoKey)
{
using (await _packageIdGroupsLock.AcquireReaderLock())
{
if (_packageInfoMap.TryGetValue(infoKey, out var packageInfo))
return FluentResults.Result.Ok(packageInfo);
}
// change to write lock
using (await _packageIdGroupsLock.AcquireWriterLock())
{
// create one
var packageInfo = infoKey.Match<IPackageInfo>(
sPackName => new PackageInfo(sPackName, GetNextId(), GetBestMatchPackage),
uSteamId => new PackageInfo(uSteamId, GetNextId(), GetBestMatchPackage),
cKey => new PackageInfo(cKey.Item1, cKey.Item2, GetNextId(), GetBestMatchPackage)
);
_packageInfoMap[infoKey] = packageInfo;
// empty array
_packageIdGroups[packageInfo.Id] = ImmutableArray<ContentPackage>.Empty;
return FluentResults.Result.Ok(packageInfo);
}
}
#endregion
public ContentPackageInfoLookup(IEventService eventService, IPackageListRetrievalService packageListRetrievalService)
{
_eventService = eventService ?? throw new ArgumentNullException(
$"{nameof(ContentPackageInfoLookup)}: {nameof(eventService)} cannot be null.");
_packageListRetrievalService = packageListRetrievalService ?? throw new ArgumentNullException(nameof(packageListRetrievalService));
this._enabledPackages = new HashSet<ContentPackage>();
this._allPackages = new HashSet<ContentPackage>();
}
public void Dispose()
{
IsDisposed = true;
// locks
using var l1 = _packageIdGroupsLock.AcquireWriterLock().GetAwaiter().GetResult();
using var l2 = _packageSetsLock.AcquireWriterLock().GetAwaiter().GetResult();
_eventService.Unsubscribe<IEventEnabledPackageListChanged>(this);
_eventService.Unsubscribe<IEventAllPackageListChanged>(this);
_packageIdGroups.Clear();
_packageInfoMap.Clear();
_reversePackageIdGroups.Clear();
}
public bool IsDisposed
{
get => ModUtils.Threading.GetBool(ref _isDisposed);
private set => ModUtils.Threading.SetBool(ref _isDisposed, value);
}
public FluentResults.Result Reset()
{
if (IsDisposed)
return FluentResults.Result.Fail($"Service is disposed.");
using var l1 = _packageIdGroupsLock.AcquireWriterLock().GetAwaiter().GetResult();
using var l2 = _packageSetsLock.AcquireWriterLock().GetAwaiter().GetResult();
_packageIdGroups.Clear();
_packageInfoMap.Clear();
_reversePackageIdGroups.Clear();
RefreshPackageLists();
return FluentResults.Result.Ok();
}
public void OnEnabledPackageListChanged(CorePackage package, IEnumerable<RegularPackage> regularPackages)
{
((IService)this).CheckDisposed();
SyncPackagesLists(
regularPackages.Select(p => (ContentPackage)p).ToImmutableArray().Add(package),
_allPackages.ToImmutableArray())
.GetAwaiter().GetResult();
}
public void OnAllPackageListChanged(IEnumerable<CorePackage> corePackages, IEnumerable<RegularPackage> regularPackages)
{
((IService)this).CheckDisposed();
SyncPackagesLists(
_enabledPackages.ToImmutableArray(),
regularPackages.Select(p => p as ContentPackage)
.Union(corePackages.Select(p => p as ContentPackage))
.ToImmutableArray()
).GetAwaiter().GetResult();
}
public async Task<Result<IPackageInfo>> Lookup(string packageName)
{
((IService)this).CheckDisposed();
if(packageName.IsNullOrWhiteSpace())
return FluentResults.Result.Fail($"Name is null or empty.");
return await LookupInternal(packageName);
}
public async Task<Result<IPackageInfo>> Lookup(string packageName, ulong steamWorkshopId)
{
((IService)this).CheckDisposed();
if (packageName.IsNullOrWhiteSpace() || steamWorkshopId == 0)
return FluentResults.Result.Fail($"Name or steam id is null or empty.");
return await LookupInternal((packageName, steamWorkshopId));
}
public async Task<Result<IPackageInfo>> Lookup(ulong steamWorkshopId)
{
((IService)this).CheckDisposed();
if (steamWorkshopId is 0)
return FluentResults.Result.Fail($"SteamId is 0.");
return await LookupInternal(steamWorkshopId);
}
public async Task<Result<IPackageInfo>> Lookup(ContentPackage package)
{
((IService)this).CheckDisposed();
if (package is null)
return FluentResults.Result.Fail($"Package is null.");
if (package.TryExtractSteamWorkshopId(out var steamWorkshopId) && steamWorkshopId.Value != 0)
{
if (!package.Name.IsNullOrWhiteSpace())
return await LookupInternal((package.Name, steamWorkshopId.Value));
else
return await LookupInternal(steamWorkshopId.Value);
}
if (!package.Name.IsNullOrWhiteSpace())
return await LookupInternal(package.Name);
return FluentResults.Result.Fail($"Package name is null and steamid is 0.");
}
public void RefreshPackageLists()
{
((IService)this).CheckDisposed();
if (Thread.CurrentThread != GameMain.MainThread)
throw new InvalidOperationException($"{nameof(ContentPackageInfoLookup)}: {nameof(RefreshPackageLists)} must be run on the main thread.");
var enabledPackages = _packageListRetrievalService.GetEnabledContentPackages().ToImmutableArray();
var allPackages = _packageListRetrievalService.GetAllContentPackages().ToImmutableArray();
SyncPackagesLists(enabledPackages, allPackages).GetAwaiter().GetResult();
}
}
@@ -1,22 +1,12 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.Specialized;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using Barotrauma.Extensions;
using Barotrauma.LuaCs.Events;
using Barotrauma.LuaCs.Services.Compatibility;
using Barotrauma.LuaCs.Services.Safe;
using Dynamitey;
using FluentResults;
using FluentResults.LuaCs;
using HarmonyLib;
using ImpromptuInterface;
using OneOf;
namespace Barotrauma.LuaCs.Services;
@@ -0,0 +1,6 @@
namespace Barotrauma.LuaCs.Services;
public interface LocalizationService
{
}
@@ -272,8 +272,9 @@ namespace Barotrauma.LuaCs.Services
public LuaGame()
{
LuaUserData.MakeFieldAccessible(UserData.RegisterType(typeof(GameSettings)), "currentConfig");
Settings = UserData.CreateStatic(typeof(GameSettings));
throw new NotImplementedException();
/*LuaUserData.MakeFieldAccessible(UserData.RegisterType(typeof(GameSettings)), "currentConfig");
Settings = UserData.CreateStatic(typeof(GameSettings));*/
}
public void OverrideTraitors(bool o)
@@ -3,7 +3,7 @@ using Barotrauma.Networking;
using System;
using System.Collections.Generic;
namespace Barotrauma.LuaCs.Networking;
namespace Barotrauma.LuaCs.Services;
internal partial class NetworkingService : INetworkingService
{
@@ -0,0 +1,30 @@
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<ContentPackage> GetEnabledContentPackages()
{
return ContentPackageManager.EnabledPackages.All;
}
public IEnumerable<ContentPackage> GetAllContentPackages()
{
return ContentPackageManager.AllPackages;
}
}
@@ -1,13 +1,391 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Barotrauma.LuaCs.Data;
using Barotrauma.LuaCs.Services.Processing;
using Barotrauma.Steam;
using FluentResults;
using OneOf;
// ReSharper disable UseCollectionExpression
namespace Barotrauma.LuaCs.Services;
public class PackageManagementService : IPackageManagementService
public partial class PackageManagementService : IPackageManagementService
{
private int _isDisposed;
private readonly ConcurrentDictionary<ContentPackage, IModConfigInfo> _modInfos = new();
// lookup caches
private readonly IPackageInfoLookupService _packageInfoLookupService;
// processors
private readonly IConverterServiceAsync<ContentPackage, IModConfigInfo> _modConfigParserService;
private readonly IProcessorService<IReadOnlyList<IAssemblyResourceInfo>, IAssembliesResourcesInfo> _assemblyInfoConverter;
private readonly IProcessorService<IReadOnlyList<IConfigResourceInfo>, IConfigsResourcesInfo> _configsInfoConverter;
private readonly IProcessorService<IReadOnlyList<IConfigProfileResourceInfo>, IConfigProfilesResourcesInfo> _configProfilesConverter;
private readonly IProcessorService<IReadOnlyList<ILocalizationResourceInfo>, ILocalizationsResourcesInfo> _localizationsConverter;
private readonly IProcessorService<IReadOnlyList<ILuaScriptResourceInfo>, ILuaScriptsResourcesInfo> _luaScriptsConverter;
public void Dispose()
{
IsDisposed = true;
_modInfos.Clear();
}
public bool IsDisposed
{
get => ModUtils.Threading.GetBool(ref _isDisposed);
private set => ModUtils.Threading.SetBool(ref _isDisposed, value);
}
public FluentResults.Result Reset()
{
try
{
((IService)this).CheckDisposed();
_modInfos.Clear();
}
catch (Exception e)
{
return FluentResults.Result.Fail(new ExceptionalError(e));
}
return FluentResults.Result.Ok();
}
public ImmutableArray<ILocalizationResourceInfo> Localizations => _modInfos.IsEmpty ? ImmutableArray<ILocalizationResourceInfo>.Empty
: _modInfos.SelectMany(kvp => kvp.Value.Localizations).ToImmutableArray();
public ImmutableArray<IConfigResourceInfo> Configs => _modInfos.IsEmpty ? ImmutableArray<IConfigResourceInfo>.Empty
: _modInfos.SelectMany(kvp => kvp.Value.Configs).ToImmutableArray();
public ImmutableArray<IConfigProfileResourceInfo> ConfigProfiles => _modInfos.IsEmpty ? ImmutableArray<IConfigProfileResourceInfo>.Empty
: _modInfos.SelectMany(kvp => kvp.Value.ConfigProfiles).ToImmutableArray();
public ImmutableArray<ILuaScriptResourceInfo> LuaScripts => _modInfos.IsEmpty ? ImmutableArray<ILuaScriptResourceInfo>.Empty
: _modInfos.SelectMany(kvp => kvp.Value.LuaScripts).ToImmutableArray();
public ImmutableArray<IAssemblyResourceInfo> Assemblies => _modInfos.IsEmpty ? ImmutableArray<IAssemblyResourceInfo>.Empty
: _modInfos.SelectMany(kvp => kvp.Value.Assemblies).ToImmutableArray();
public async Task<FluentResults.Result> LoadPackageInfosAsync(ContentPackage package)
{
((IService)this).CheckDisposed();
if (package is null)
return FluentResults.Result.Fail(new ExceptionalError(new NullReferenceException($"{nameof(LoadPackageInfosAsync)}: ContentPackage is null.")));
var result = await _modConfigParserService.TryParseResourceAsync(package);
if (result.IsFailed)
return FluentResults.Result.Fail($"$Could not parse package mod config.").WithErrors(result.Errors);
if (!_modInfos.TryAdd(package, result.Value))
return FluentResults.Result.Fail($"Failed to add ModInfo for {package.Name}.");
return FluentResults.Result.Ok();
}
public async Task<IReadOnlyList<(ContentPackage, FluentResults.Result)>> LoadPackagesInfosAsync(IReadOnlyList<ContentPackage> packages)
{
((IService)this).CheckDisposed();
if (packages is null || packages.Count == 0)
throw new ArgumentNullException(nameof(LoadPackagesInfosAsync));
ConcurrentQueue<(ContentPackage, FluentResults.Result)> results = new();
await packages.ParallelForEachAsync(async package =>
{
var res = await LoadPackageInfosAsync(package);
results.Enqueue((package, res));
}, Environment.ProcessorCount);
return results.ToImmutableArray();
}
public IReadOnlyList<ContentPackage> GetAllLoadedPackages()
{
((IService)this).CheckDisposed();
return _modInfos.IsEmpty ? ImmutableArray<ContentPackage>.Empty
: _modInfos.Select(kvp => kvp.Key).ToImmutableArray();
}
public void DisposePackageInfos(ContentPackage package)
{
_modInfos.TryRemove(package, out _);
}
public void DisposePackagesInfos(IReadOnlyList<ContentPackage> packages)
{
if (packages is null || packages.Count == 0)
return;
foreach (var package in packages)
{
DisposePackageInfos(package);
}
}
public Result<IPackageDependency> GetPackageDependencyInfo(ContentPackage ownerPackage, string packageName,
ulong steamWorkshopId)
{
((IService)this).CheckDisposed();
if (ownerPackage is null)
return FluentResults.Result.Fail($"OwnerPackage is null.");
var nameGood = !packageName.IsNullOrWhiteSpace();
if (!nameGood && steamWorkshopId == 0)
FluentResults.Result.Fail($"PackageName and SteamId cannot both be invalid.");
IPackageInfo depInfo = null;
// complex key
if (nameGood && steamWorkshopId != 0
&& _packageInfoLookupService.Lookup(packageName, steamWorkshopId).GetAwaiter().GetResult() is
{ IsSuccess: true, Value: {} dep1 })
{
depInfo = dep1;
}
// name key
else if (nameGood && _packageInfoLookupService.Lookup(packageName).GetAwaiter().GetResult() is
{ IsSuccess: true, Value: { } dep2 })
{
depInfo = dep2;
}
// steamid key
else if (_packageInfoLookupService.Lookup(steamWorkshopId).GetAwaiter().GetResult() is
{ IsSuccess: true, Value: { } dep3 })
{
depInfo = dep3;
}
// this should never be null so we return an exception
else
{
return FluentResults.Result.Fail($"Package Dependency for {ownerPackage.Name} was not found.");
}
return FluentResults.Result.Ok<IPackageDependency>(new PackageDependency(ownerPackage, depInfo, ownerPackage.Name));
}
public Result<IAssembliesResourcesInfo> GetAssembliesInfos(ContentPackage package, bool onlySupportedResources = true)
{
((IService)this).CheckDisposed();
if (package is null)
return FluentResults.Result.Fail($"{nameof(GetAssembliesInfos)}: ContentPackage is null.");
if (_modInfos.TryGetValue(package, out var result))
return FluentResults.Result.Ok<IAssembliesResourcesInfo>(_assemblyInfoConverter.Process(onlySupportedResources?
result.Assemblies.Where(r =>
(r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0
&& (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray()
: result.Assemblies
));
return FluentResults.Result.Fail(
$"{nameof(GetAssembliesInfos)}: ContentPackage {package.Name} is not registered.");
}
public Result<IConfigsResourcesInfo> GetConfigsInfos(ContentPackage package, bool onlySupportedResources = true)
{
((IService)this).CheckDisposed();
if (package is null)
return FluentResults.Result.Fail($"{nameof(GetConfigsInfos)}: ContentPackage is null.");
if (_modInfos.TryGetValue(package, out var result))
{
return FluentResults.Result.Ok<IConfigsResourcesInfo>(_configsInfoConverter.Process(onlySupportedResources?
result.Configs.Where(r =>
(r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0
&& (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray()
: result.Configs
));
}
return FluentResults.Result.Fail(
$"{nameof(GetConfigsInfos)}: ContentPackage {package.Name} is not registered.");
}
public Result<IConfigProfilesResourcesInfo> GetConfigProfilesInfos(ContentPackage package, bool onlySupportedResources = true)
{
((IService)this).CheckDisposed();
if (package is null)
return FluentResults.Result.Fail($"{nameof(GetConfigProfilesInfos)}: ContentPackage is null.");
if (_modInfos.TryGetValue(package, out var result))
{
return FluentResults.Result.Ok<IConfigProfilesResourcesInfo>(_configProfilesConverter.Process(onlySupportedResources?
result.ConfigProfiles.Where(r =>
(r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0
&& (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray()
: result.ConfigProfiles
));
}
return FluentResults.Result.Fail(
$"{nameof(GetConfigProfilesInfos)}: ContentPackage {package.Name} is not registered.");
}
public Result<ILocalizationsResourcesInfo> GetLocalizationsInfos(ContentPackage package, bool onlySupportedResources = true)
{
((IService)this).CheckDisposed();
if (package is null)
return FluentResults.Result.Fail($"{nameof(GetLocalizationsInfos)}: ContentPackage is null.");
if (_modInfos.TryGetValue(package, out var result))
{
return FluentResults.Result.Ok<ILocalizationsResourcesInfo>(_localizationsConverter.Process(onlySupportedResources?
result.Localizations.Where(r =>
(r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0
&& (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray()
: result.Localizations
));
}
return FluentResults.Result.Fail(
$"{nameof(GetLocalizationsInfos)}: ContentPackage {package.Name} is not registered.");
}
public Result<ILuaScriptsResourcesInfo> GetLuaScriptsInfos(ContentPackage package, bool onlySupportedResources = true)
{
((IService)this).CheckDisposed();
if (package is null)
return FluentResults.Result.Fail($"{nameof(GetLuaScriptsInfos)}: ContentPackage is null.");
if (_modInfos.TryGetValue(package, out var result))
{
return FluentResults.Result.Ok<ILuaScriptsResourcesInfo>(_luaScriptsConverter.Process(onlySupportedResources?
result.LuaScripts.Where(r =>
(r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0
&& (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray()
: result.LuaScripts
));
}
return FluentResults.Result.Fail(
$"{nameof(GetLuaScriptsInfos)}: ContentPackage {package.Name} is not registered.");
}
public Result<IAssembliesResourcesInfo> GetAssembliesInfos(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true)
{
((IService)this).CheckDisposed();
if (packages is null || packages.Count == 0)
return FluentResults.Result.Fail($"{nameof(GetAssembliesInfos)}: ContentPackage list is null or empty.");
var builder = ImmutableArray.CreateBuilder<IAssemblyResourceInfo>();
foreach (var package in packages)
{
if (_modInfos.TryGetValue(package, out var result) && result.Assemblies is { IsEmpty: false })
{
builder.AddRange(onlySupportedResources?
result.Assemblies.Where(r =>
(r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0
&& (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray()
: result.Assemblies);
}
}
return FluentResults.Result.Ok(_assemblyInfoConverter.Process(builder.MoveToImmutable()));
}
public Result<IConfigsResourcesInfo> GetConfigsInfos(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true)
{
((IService)this).CheckDisposed();
if (packages is null || packages.Count == 0)
return FluentResults.Result.Fail($"{nameof(GetConfigsInfos)}: ContentPackage list is null or empty.");
var builder = ImmutableArray.CreateBuilder<IConfigResourceInfo>();
foreach (var package in packages)
{
if (_modInfos.TryGetValue(package, out var result) && result.Configs is { IsEmpty: false })
{
builder.AddRange(onlySupportedResources?
result.Configs.Where(r =>
(r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0
&& (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray()
: result.Configs);
}
}
return FluentResults.Result.Ok(_configsInfoConverter.Process(builder.MoveToImmutable()));
}
public Result<IConfigProfilesResourcesInfo> GetConfigProfilesInfos(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true)
{
((IService)this).CheckDisposed();
if (packages is null || packages.Count == 0)
return FluentResults.Result.Fail($"{nameof(GetConfigProfilesInfos)}: ContentPackage list is null or empty.");
var builder = ImmutableArray.CreateBuilder<IConfigProfileResourceInfo>();
foreach (var package in packages)
{
if (_modInfos.TryGetValue(package, out var result) && result.ConfigProfiles is { IsEmpty: false })
{
builder.AddRange(onlySupportedResources?
result.ConfigProfiles.Where(r =>
(r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0
&& (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray()
: result.ConfigProfiles);
}
}
return FluentResults.Result.Ok(_configProfilesConverter.Process(builder.MoveToImmutable()));
}
public Result<ILocalizationsResourcesInfo> GetLocalizationsInfos(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true)
{
((IService)this).CheckDisposed();
if (packages is null || packages.Count == 0)
return FluentResults.Result.Fail($"{nameof(GetLocalizationsInfos)}: ContentPackage list is null or empty.");
var builder = ImmutableArray.CreateBuilder<ILocalizationResourceInfo>();
foreach (var package in packages)
{
if (_modInfos.TryGetValue(package, out var result) && result.Localizations is { IsEmpty: false })
{
builder.AddRange(onlySupportedResources?
result.Localizations.Where(r =>
(r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0
&& (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray()
: result.Localizations);
}
}
return FluentResults.Result.Ok(_localizationsConverter.Process(builder.MoveToImmutable()));
}
public Result<ILuaScriptsResourcesInfo> GetLuaScriptsInfos(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true)
{
((IService)this).CheckDisposed();
if (packages is null || packages.Count == 0)
return FluentResults.Result.Fail($"{nameof(GetLuaScriptsInfos)}: ContentPackage list is null or empty.");
var builder = ImmutableArray.CreateBuilder<ILuaScriptResourceInfo>();
foreach (var package in packages)
{
if (_modInfos.TryGetValue(package, out var result) && result.LuaScripts is { IsEmpty: false })
{
builder.AddRange(onlySupportedResources?
result.LuaScripts.Where(r =>
(r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0
&& (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray()
: result.LuaScripts);
}
}
return FluentResults.Result.Ok(_luaScriptsConverter.Process(builder.MoveToImmutable()));
}
public async Task<Result<IAssembliesResourcesInfo>> GetAssembliesInfosAsync(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true)
{
return await Task.Run(() => GetAssembliesInfos(packages, onlySupportedResources));
}
public async Task<Result<IConfigsResourcesInfo>> GetConfigsInfosAsync(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true)
{
return await Task.Run(() => GetConfigsInfos(packages, onlySupportedResources));
}
public async Task<Result<IConfigProfilesResourcesInfo>> GetConfigProfilesInfosAsync(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true)
{
return await Task.Run(() => GetConfigProfilesInfos(packages, onlySupportedResources));
}
public async Task<Result<ILocalizationsResourcesInfo>> GetLocalizationsInfosAsync(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true)
{
return await Task.Run(() => GetLocalizationsInfos(packages, onlySupportedResources));
}
public async Task<Result<ILuaScriptsResourcesInfo>> GetLuaScriptsInfosAsync(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true)
{
return await Task.Run(() => GetLuaScriptsInfos(packages, onlySupportedResources));
}
}
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
using System.Xml.Linq;
using Barotrauma.LuaCs.Data;
@@ -6,18 +7,24 @@ using FluentResults;
namespace Barotrauma.LuaCs.Services.Processing;
#region TypeDef
public interface IConverterService<in TSrc, TOut> : IReusableService
public interface IConverterService<in TSrc, TOut> : IService
{
Result<TOut> TryParseResource(TSrc src);
Result<TOut> TryParseResources(IEnumerable<TSrc> sources);
ImmutableArray<Result<TOut>> TryParseResources(IEnumerable<TSrc> sources);
}
public interface IConverterServiceAsync<in TSrc, TOut> : IReusableService
public interface IConverterServiceAsync<in TSrc, TOut> : IService
{
Task<Result<TOut>> TryParseResourceAsync(TSrc src);
Task<Result<TOut>> TryParseResourcesAsync(IEnumerable<TSrc> sources);
Task<ImmutableArray<Result<TOut>>> TryParseResourcesAsync(IEnumerable<TSrc> sources);
}
#endregion
public interface IProcessorService<in TSrc, TOut> : IService
{
TOut Process(TSrc src);
}
public interface IProcessorServiceAsync<in TSrc, TOut> : IService
{
Task<TOut> ProcessAsync(TSrc src);
}
@@ -1,9 +0,0 @@
using Barotrauma.LuaCs.Data;
namespace Barotrauma.LuaCs.Services.Processing;
public interface IModConfigCreatorService : IService
{
FluentResults.Result<IModConfigInfo> BuildConfigForPackage(ContentPackage package);
FluentResults.Result<IModConfigInfo> BuildConfigFromManifest(string manifestPath);
}
@@ -0,0 +1,661 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using Barotrauma.LuaCs.Data;
using FluentResults;
namespace Barotrauma.LuaCs.Services.Processing;
public partial class ModConfigService : IConverterServiceAsync<ContentPackage, IModConfigInfo>, IConverterService<ContentPackage, IModConfigInfo>
{
private readonly IStorageService _storageService;
private readonly Lazy<IPackageManagementService> _packageManagementService;
private int _isDisposed;
private const string ModConfigFileName = "ModConfig.xml";
private const string ModConfigRootName = "ModConfig";
public ModConfigService(IStorageService storageService, Lazy<IPackageManagementService> pms)
{
_storageService = storageService;
_packageManagementService = pms;
}
public void Dispose()
{
throw new System.NotImplementedException();
}
public bool IsDisposed
{
get => ModUtils.Threading.GetBool(ref _isDisposed);
private set => ModUtils.Threading.SetBool(ref _isDisposed, value);
}
public async Task<Result<IModConfigInfo>> TryParseResourceAsync(ContentPackage src)
{
((IService)this).CheckDisposed();
// validate package
if (src is null)
return FluentResults.Result.Fail<IModConfigInfo>("ContentPackage is null");
if (_storageService.DirectoryExists(src.Path) is { } res && (res.IsFailed || !res.Value))
return FluentResults.Result.Fail<IModConfigInfo>($"ContentPackage does not exist or cannot be accessed: {src.Path}");
// find ModConfig.xml or deep scan on fail (legacy)
if (await _storageService.LoadPackageXmlAsync(src, ModConfigFileName) is
{ IsSuccess: true, Value: var modConfigXml }
&& modConfigXml.Root is { Name.LocalName: ModConfigRootName } root)
{
return await GetModConfigInfoAsync(src, root);
}
// legacy mode
try
{
// we only supported assemblies and lua scripts
var asm = GetAssembliesLegacy(src);
var lua = GetLuaScriptsLegacy(src);
return new ModConfigInfo()
{
Assemblies = asm,
LuaScripts = lua,
Configs = ImmutableArray<IConfigResourceInfo>.Empty,
ConfigProfiles = ImmutableArray<IConfigProfileResourceInfo>.Empty,
Localizations = ImmutableArray<ILocalizationResourceInfo>.Empty,
Package = src,
PackageName = src.Name
#if CLIENT
,Styles = ImmutableArray<IStylesResourceInfo>.Empty
#endif
};
}
catch (Exception e)
{
return FluentResults.Result.Fail<IModConfigInfo>($"Unable to parse legacy content package: {src.Name}: {src.Path}");
}
}
private partial Task<Result<IModConfigInfo>> GetModConfigInfoAsync(ContentPackage package, XElement root);
private ImmutableArray<ILocalizationResourceInfo> GetLocalizations(ContentPackage src, IEnumerable<XElement> elements)
{
var builder = ImmutableArray.CreateBuilder<ILocalizationResourceInfo>();
if (GetXmlFilesList(src, elements, "Localizations")
is not { IsSuccess: true, Value: { } xmlFiles })
return ImmutableArray<ILocalizationResourceInfo>.Empty;
foreach (var file in xmlFiles)
{
// get dependencies
var deps = GetElementsDependenciesData(file.Item1, src);
// get platform, culture and target architecture
var info = GetElementsAttributesData(file.Item1, file.Item2.First());
builder.Add(new LocalizationResourceInfo()
{
Dependencies = deps,
Optional = info.IsOptional,
FilePaths = file.Item2,
InternalName = info.Name,
LoadPriority = info.LoadPriority,
OwnerPackage = src,
SupportedCultures = info.SupportedCultures,
SupportedPlatforms = info.SupportedPlatforms,
SupportedTargets = info.SupportedTargets
});
}
return builder.Count > 0
? builder.ToImmutable()
: ImmutableArray<ILocalizationResourceInfo>.Empty;
}
private ImmutableArray<IAssemblyResourceInfo> GetAssemblies(ContentPackage src, IEnumerable<XElement> elements)
{
var builder = ImmutableArray.CreateBuilder<IAssemblyResourceInfo>();
var elementsList = elements.ToImmutableArray();
if (GetFilesList(src, elementsList, "Assembly", "*.dll")
is not { IsSuccess: true, Value: { } xmlFiles })
return ImmutableArray<IAssemblyResourceInfo>.Empty;
foreach (var file in xmlFiles)
{
// get dependencies
var deps = GetElementsDependenciesData(file.Item1, src);
// get platform, culture and target architecture
var info = GetElementsAttributesData(file.Item1, file.Item2.First());
builder.Add(new AssemblyResourceInfo()
{
Dependencies = deps,
Optional = info.IsOptional,
FilePaths = file.Item2,
InternalName = info.Name,
LoadPriority = info.LoadPriority,
OwnerPackage = src,
SupportedCultures = info.SupportedCultures,
SupportedPlatforms = info.SupportedPlatforms,
SupportedTargets = info.SupportedTargets,
FriendlyName = file.Item1.GetAttributeString("Name", info.Name),
IsScript = false,
LazyLoad = !file.Item1.GetAttributeBool("RunFile", true)
});
}
if (GetFilesList(src, elementsList, "Assembly", "*.cs")
is not { IsSuccess: true, Value: { } xmlFiles2 })
return ImmutableArray<IAssemblyResourceInfo>.Empty;
foreach (var file in xmlFiles2)
{
// get dependencies
var deps = GetElementsDependenciesData(file.Item1, src);
// get platform, culture and target architecture
var info = GetElementsAttributesData(file.Item1, file.Item2.First());
builder.Add(new AssemblyResourceInfo()
{
Dependencies = deps,
Optional = info.IsOptional,
FilePaths = file.Item2,
InternalName = info.Name,
LoadPriority = info.LoadPriority,
OwnerPackage = src,
SupportedCultures = info.SupportedCultures,
SupportedPlatforms = info.SupportedPlatforms,
SupportedTargets = info.SupportedTargets,
FriendlyName = file.Item1.GetAttributeString("Name", info.Name),
IsScript = true,
LazyLoad = !file.Item1.GetAttributeBool("RunFile", true)
});
}
return builder.Count > 0
? builder.ToImmutable()
: ImmutableArray<IAssemblyResourceInfo>.Empty;
}
private ImmutableArray<IConfigResourceInfo> GetConfigs(ContentPackage src, IEnumerable<XElement> elements)
{
var builder = ImmutableArray.CreateBuilder<IConfigResourceInfo>();
if (GetXmlFilesList(src, elements, "Config")
is not { IsSuccess: true, Value: { } xmlFiles })
return ImmutableArray<IConfigResourceInfo>.Empty;
foreach (var file in xmlFiles)
{
// get dependencies
var deps = GetElementsDependenciesData(file.Item1, src);
// get platform, culture and target architecture
var info = GetElementsAttributesData(file.Item1, file.Item2.First());
builder.Add(new ConfigResourceInfo()
{
Dependencies = deps,
Optional = info.IsOptional,
FilePaths = file.Item2,
InternalName = info.Name,
LoadPriority = info.LoadPriority,
OwnerPackage = src,
SupportedCultures = info.SupportedCultures,
SupportedPlatforms = info.SupportedPlatforms,
SupportedTargets = info.SupportedTargets
});
}
return builder.Count > 0
? builder.ToImmutable()
: ImmutableArray<IConfigResourceInfo>.Empty;
}
private ImmutableArray<IConfigProfileResourceInfo> GetConfigProfiles(ContentPackage src, IEnumerable<XElement> elements)
{
var builder = ImmutableArray.CreateBuilder<IConfigProfileResourceInfo>();
if (GetXmlFilesList(src, elements, "Config")
is not { IsSuccess: true, Value: { } xmlFiles })
return ImmutableArray<IConfigProfileResourceInfo>.Empty;
foreach (var file in xmlFiles)
{
// get dependencies
var deps = GetElementsDependenciesData(file.Item1, src);
// get platform, culture and target architecture
var info = GetElementsAttributesData(file.Item1, file.Item2.First());
builder.Add(new ConfigProfileResourceInfo()
{
Dependencies = deps,
Optional = info.IsOptional,
FilePaths = file.Item2,
InternalName = info.Name,
LoadPriority = info.LoadPriority,
OwnerPackage = src,
SupportedCultures = info.SupportedCultures,
SupportedPlatforms = info.SupportedPlatforms,
SupportedTargets = info.SupportedTargets
});
}
return builder.Count > 0
? builder.ToImmutable()
: ImmutableArray<IConfigProfileResourceInfo>.Empty;
}
private ImmutableArray<ILuaScriptResourceInfo> GetLuaScripts(ContentPackage src, IEnumerable<XElement> elements)
{
var builder = ImmutableArray.CreateBuilder<ILuaScriptResourceInfo>();
if (GetXmlFilesList(src, elements, "Config")
is not { IsSuccess: true, Value: { } xmlFiles })
return ImmutableArray<ILuaScriptResourceInfo>.Empty;
foreach (var file in xmlFiles)
{
// get dependencies
var deps = GetElementsDependenciesData(file.Item1, src);
// get platform, culture and target architecture
var info = GetElementsAttributesData(file.Item1, file.Item2.First());
builder.Add(new LuaScriptScriptResourceInfo()
{
Dependencies = deps,
Optional = info.IsOptional,
FilePaths = file.Item2,
InternalName = info.Name,
LoadPriority = info.LoadPriority,
OwnerPackage = src,
SupportedCultures = info.SupportedCultures,
SupportedPlatforms = info.SupportedPlatforms,
SupportedTargets = info.SupportedTargets,
IsAutorun = file.Item1.GetAttributeBool("RunFile", true)
});
}
return builder.Count > 0
? builder.ToImmutable()
: ImmutableArray<ILuaScriptResourceInfo>.Empty;
}
private Result<ImmutableArray<(XElement, ImmutableArray<string>)>> GetXmlFilesList(ContentPackage src,
IEnumerable<XElement> elements, string elementNameCheck) =>
GetFilesList(src, elements, elementNameCheck, "*.xml");
private Result<ImmutableArray<(XElement, ImmutableArray<string>)>> GetFilesList(ContentPackage src,
IEnumerable<XElement> elements, string elementNameCheck, string filter)
{
var builder = ImmutableArray.CreateBuilder<(XElement, ImmutableArray<string>)>();
if (elementNameCheck.IsNullOrWhiteSpace())
throw new ArgumentNullException($"{nameof(GetXmlFilesList)}: The element check is null.");
foreach (var element in elements)
{
if (element.Name.LocalName != elementNameCheck)
throw new ArgumentException("Element is not a Localization element");
if (element.GetAttributeString("Folder", string.Empty) is { } str
&& !string.IsNullOrWhiteSpace(str))
{
if (_storageService.FindFilesInPackage(src, str, filter, true)
is not { IsSuccess: true, Value: var fpList } || !fpList.Any())
{
continue;
}
foreach (var fileP in fpList)
builder.Add((element, fpList.ToImmutableArray()));
}
else if (element.GetAttributeString("File", string.Empty) is { } fileStr
&& !string.IsNullOrWhiteSpace(fileStr)
&& _storageService.GetAbsFromPackage(src, fileStr) is { IsSuccess: true, Value: var fp }
&& _storageService.FileExists(fp) is { IsSuccess: true, Value: true })
{
builder.Add((element, new [] { fileStr }.ToImmutableArray()));
}
}
return builder.Count > 0
? FluentResults.Result.Ok(builder.ToImmutable())
: FluentResults.Result.Fail($"No files found");
}
private ResourceAdditionalInfo GetElementsAttributesData(XElement element, string localPath)
{
return new ResourceAdditionalInfo(
element.GetAttributeString("Name", localPath),
GetSupportedPlatforms(element.GetAttributeString("Platform", "any")),
GetSupportedTargets(element.GetAttributeString("Target", "any")),
GetSupportedCultures(element),
element.GetAttributeBool("Optional", false),
element.GetAttributeInt("Priority", 0));
Platform GetSupportedPlatforms(string platformName) => platformName.ToLowerInvariant().Trim() switch
{
"windows" => Platform.Windows,
"linux" => Platform.Linux,
"osx" => Platform.OSX,
_ => Platform.Windows | Platform.Linux | Platform.OSX
};
Target GetSupportedTargets(string targetName) => targetName.ToLowerInvariant().Trim() switch
{
"client" => Target.Client,
"server" => Target.Server,
_ => Target.Client | Target.Server,
};
ImmutableArray<CultureInfo> GetSupportedCultures(XElement element)
{
var culture = element.GetAttributeString("Culture", string.Empty);
if (string.IsNullOrWhiteSpace(culture))
return new[] { CultureInfo.InvariantCulture }.ToImmutableArray();
var builder = ImmutableArray.CreateBuilder<CultureInfo>();
var arr = culture.Split(',');
if (arr.Length == 0)
return new[] { CultureInfo.InvariantCulture }.ToImmutableArray();
foreach (var culstr in arr)
{
if (string.IsNullOrWhiteSpace(culstr))
continue;
try
{
builder.Add(
culstr.ToLowerInvariant().Trim() == "default"
? CultureInfo.InvariantCulture
: CultureInfo.GetCultureInfo(culstr));
}
catch (CultureNotFoundException e)
{
// This is the case if a culture is specified by the package that is not supported by the OS/.NET ENV.
// We ignore it since we can never use it.
continue;
}
}
return builder.Count > 0
? builder.ToImmutable()
: new[] { CultureInfo.InvariantCulture }.ToImmutableArray();
}
}
private ImmutableArray<IPackageDependency> GetElementsDependenciesData(XElement element, ContentPackage src)
{
if (element.GetChildElement("Dependencies") is not {} dependencies
|| dependencies.GetChildElements("Dependency").ToImmutableArray() is not { Length: >0 } depsList)
return ImmutableArray<IPackageDependency>.Empty;
var builder = ImmutableArray.CreateBuilder<IPackageDependency>();
foreach (var dep in depsList)
{
var packName = dep.GetAttributeString("PackageName", string.Empty);
var packId = dep.GetAttributeUInt64("PackageId", 0);
// invalid entry
if (packName.IsNullOrWhiteSpace() && packId == 0)
continue;
if (_packageManagementService.Value.GetPackageDependencyInfo(src, packName, packId) is
{ IsSuccess: true, Value: { } depsInfo })
{
builder.Add(depsInfo);
}
}
return builder.ToImmutable();
}
private ImmutableArray<IAssemblyResourceInfo> GetAssembliesLegacy(ContentPackage src)
{
var builder = ImmutableArray.CreateBuilder<IAssemblyResourceInfo>();
// server, linux
if (_storageService.FindFilesInPackage(src, "bin/Server/Linux", "*.dll", true)
is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesSrvLin})
{
builder.Add(new AssemblyResourceInfo()
{
Dependencies = ImmutableArray<IPackageDependency>.Empty,
FilePaths = filesSrvLin,
FriendlyName = "AssembliesServerLinux",
InternalName = "AssembliesServerLinux",
IsScript = false,
LazyLoad = false,
LoadPriority = 1,
Optional = false,
OwnerPackage = src,
SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(),
SupportedPlatforms = Platform.Linux,
SupportedTargets = Target.Server
});
}
// server, osx
if (_storageService.FindFilesInPackage(src, "bin/Server/OSX", "*.dll", true)
is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesSrvOsx})
{
builder.Add(new AssemblyResourceInfo()
{
Dependencies = ImmutableArray<IPackageDependency>.Empty,
FilePaths = filesSrvOsx,
FriendlyName = "AssembliesServerOSX",
InternalName = "AssembliesServerOSX",
IsScript = false,
LazyLoad = false,
LoadPriority = 1,
Optional = false,
OwnerPackage = src,
SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(),
SupportedPlatforms = Platform.OSX,
SupportedTargets = Target.Server
});
}
// server, osx
if (_storageService.FindFilesInPackage(src, "bin/Server/Windows", "*.dll", true)
is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesSrvWin})
{
builder.Add(new AssemblyResourceInfo()
{
Dependencies = ImmutableArray<IPackageDependency>.Empty,
FilePaths = filesSrvWin,
FriendlyName = "AssembliesServerWin",
InternalName = "AssembliesServerWin",
IsScript = false,
LazyLoad = false,
LoadPriority = 1,
Optional = false,
OwnerPackage = src,
SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(),
SupportedPlatforms = Platform.Windows,
SupportedTargets = Target.Server
});
}
// client, linux
if (_storageService.FindFilesInPackage(src, "bin/Client/Linux", "*.dll", true)
is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesCliLin})
{
builder.Add(new AssemblyResourceInfo()
{
Dependencies = ImmutableArray<IPackageDependency>.Empty,
FilePaths = filesCliLin,
FriendlyName = "AssembliesClientLinux",
InternalName = "AssembliesClientLinux",
IsScript = false,
LazyLoad = false,
LoadPriority = 1,
Optional = false,
OwnerPackage = src,
SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(),
SupportedPlatforms = Platform.Linux,
SupportedTargets = Target.Client
});
}
// server, osx
if (_storageService.FindFilesInPackage(src, "bin/Client/OSX", "*.dll", true)
is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesCliOsx})
{
builder.Add(new AssemblyResourceInfo()
{
Dependencies = ImmutableArray<IPackageDependency>.Empty,
FilePaths = filesCliOsx,
FriendlyName = "AssembliesClientOSX",
InternalName = "AssembliesClientOSX",
IsScript = false,
LazyLoad = false,
LoadPriority = 1,
Optional = false,
OwnerPackage = src,
SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(),
SupportedPlatforms = Platform.OSX,
SupportedTargets = Target.Client
});
}
// server, osx
if (_storageService.FindFilesInPackage(src, "bin/Client/Windows", "*.dll", true)
is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesCliWin})
{
builder.Add(new AssemblyResourceInfo()
{
Dependencies = ImmutableArray<IPackageDependency>.Empty,
FilePaths = filesCliWin,
FriendlyName = "AssembliesClientWin",
InternalName = "AssembliesClientWin",
IsScript = false,
LazyLoad = false,
LoadPriority = 1,
Optional = false,
OwnerPackage = src,
SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(),
SupportedPlatforms = Platform.Windows,
SupportedTargets = Target.Client
});
}
var sharedFound = _storageService.FindFilesInPackage(src, "CSharp/Shared", "*.cs", true)
is { IsSuccess: true, Value: { IsDefaultOrEmpty: false } filesCssShared };
// source files legacy: server
if (_storageService.FindFilesInPackage(src, "CSharp/Server", "*.cs", true)
is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesCssServer})
{
builder.Add(new AssemblyResourceInfo()
{
Dependencies = ImmutableArray<IPackageDependency>.Empty,
FilePaths = sharedFound ? filesCssServer.Concat(filesCssShared).ToImmutableArray() : filesCssServer,
FriendlyName = "CssServer",
InternalName = "CssServer",
IsScript = true,
LazyLoad = false,
LoadPriority = 1,
Optional = false,
OwnerPackage = src,
SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(),
SupportedPlatforms = Platform.Linux | Platform.OSX | Platform.Windows,
SupportedTargets = Target.Server
});
}
// source files legacy: client
if (_storageService.FindFilesInPackage(src, "CSharp/Client", "*.cs", true)
is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesCssClient})
{
builder.Add(new AssemblyResourceInfo()
{
Dependencies = ImmutableArray<IPackageDependency>.Empty,
FilePaths = sharedFound ? filesCssClient.Concat(filesCssShared).ToImmutableArray() : filesCssClient,
FriendlyName = "CssClient",
InternalName = "CssClient",
IsScript = true,
LazyLoad = false,
LoadPriority = 1,
Optional = false,
OwnerPackage = src,
SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(),
SupportedPlatforms = Platform.Linux | Platform.OSX | Platform.Windows,
SupportedTargets = Target.Client
});
}
return builder.MoveToImmutable();
}
private ImmutableArray<ILuaScriptResourceInfo> GetLuaScriptsLegacy(ContentPackage src)
{
var builder = ImmutableArray.CreateBuilder<ILuaScriptResourceInfo>();
if (_storageService.FindFilesInPackage(src, "Lua", "*.lua", true)
is { IsSuccess: true, Value: { IsDefaultOrEmpty: false } fileAll })
{
builder.Add(new LuaScriptScriptResourceInfo()
{
Dependencies = ImmutableArray<IPackageDependency>.Empty,
FilePaths = fileAll.Where(path => !path.Contains("Autorun")).ToImmutableArray(),
InternalName = "LuaScriptsNormal",
Optional = false,
IsAutorun = false,
OwnerPackage = src,
SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(),
SupportedPlatforms = Platform.Linux | Platform.OSX | Platform.Windows,
SupportedTargets = Target.Client | Target.Server
});
builder.Add(new LuaScriptScriptResourceInfo()
{
Dependencies = ImmutableArray<IPackageDependency>.Empty,
FilePaths = fileAll.Where(path => path.Contains("Autorun")).ToImmutableArray(),
InternalName = "LuaScriptsAutorun",
Optional = false,
IsAutorun = true,
OwnerPackage = src,
SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(),
SupportedPlatforms = Platform.Linux | Platform.OSX | Platform.Windows,
SupportedTargets = Target.Client | Target.Server
});
}
return builder.MoveToImmutable();
}
public async Task<ImmutableArray<Result<IModConfigInfo>>> TryParseResourcesAsync(IEnumerable<ContentPackage> sources)
{
((IService)this).CheckDisposed();
var srcs = sources.ToImmutableArray();
var results = new AsyncLocal<ConcurrentQueue<Result<IModConfigInfo>>>();
await srcs.ParallelForEachAsync(async pkg =>
{
try
{
results.Value.Enqueue(await TryParseResourceAsync(pkg));
}
catch (Exception e)
{
// this should never happen but this is to stop partial execution exit.
results.Value.Enqueue(
FluentResults.Result.Fail<IModConfigInfo>($"Failed to parse package {pkg?.Name}: {e.Message}"));
}
});
return results.Value.ToImmutableArray();
}
public Result<IModConfigInfo> TryParseResource(ContentPackage src) =>
TryParseResourceAsync(src).GetAwaiter().GetResult();
public ImmutableArray<Result<IModConfigInfo>> TryParseResources(IEnumerable<ContentPackage> sources) =>
TryParseResourcesAsync(sources.ToImmutableArray()).GetAwaiter().GetResult();
private record ResourceAdditionalInfo(
string Name,
Platform SupportedPlatforms,
Target SupportedTargets,
ImmutableArray<CultureInfo> SupportedCultures,
bool IsOptional,
int LoadPriority);
}
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Barotrauma.LuaCs.Data;
namespace Barotrauma.LuaCs.Services.Processing;
public partial class ResourceInfoArrayPacker :
IProcessorService<IReadOnlyList<IAssemblyResourceInfo>, IAssembliesResourcesInfo>,
IProcessorService<IReadOnlyList<IConfigResourceInfo>, IConfigsResourcesInfo>,
IProcessorService<IReadOnlyList<IConfigProfileResourceInfo>, IConfigProfilesResourcesInfo>,
IProcessorService<IReadOnlyList<ILocalizationResourceInfo>, ILocalizationsResourcesInfo>,
IProcessorService<IReadOnlyList<ILuaScriptResourceInfo>, ILuaScriptsResourcesInfo>
{
private bool _isDisposed;
public IAssembliesResourcesInfo Process(IReadOnlyList<IAssemblyResourceInfo> src)
{
return new AssemblyResourcesInfo(src.ToImmutableArray());
}
public IConfigsResourcesInfo Process(IReadOnlyList<IConfigResourceInfo> src)
{
return new ConfigResourcesInfo(src.ToImmutableArray());
}
public IConfigProfilesResourcesInfo Process(IReadOnlyList<IConfigProfileResourceInfo> src)
{
return new ConfigProfilesResourcesInfo(src.ToImmutableArray());
}
public ILocalizationsResourcesInfo Process(IReadOnlyList<ILocalizationResourceInfo> src)
{
return new LocalizationResourcesInfo(src.ToImmutableArray());
}
public ILuaScriptsResourcesInfo Process(IReadOnlyList<ILuaScriptResourceInfo> src)
{
return new LuaScriptsResourcesInfo(src.ToImmutableArray());
}
public void Dispose()
{
// Stateless class
GC.SuppressFinalize(this);
IsDisposed = true;
}
public bool IsDisposed
{
get => _isDisposed;
set => _isDisposed = value;
}
}
@@ -5,10 +5,11 @@ using System.IO;
using System.Reflection;
using System.Security;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using Barotrauma.LuaCs.Configuration;
using Barotrauma.LuaCs.Networking;
using Barotrauma.LuaCs.Services;
using Barotrauma.Steam;
using FluentResults;
using FluentResults.LuaCs;
@@ -293,12 +294,31 @@ public class StorageService : IStorageService
});
}
public FluentResults.Result<bool> DirectoryExists(string directoryPath)
{
((IService)this).CheckDisposed();
try
{
var di = new DirectoryInfo(directoryPath);
return di.Exists;
}
catch (Exception ex)
{
return new FluentResults.Result<bool>().WithError(ex.Message);
}
}
public async Task<FluentResults.Result<XDocument>> TryLoadXmlAsync(string filePath, Encoding encoding = null)
{
var r = await TryLoadTextAsync(filePath, encoding);
if (r is { IsSuccess: true, Value: {} value } && !value.IsNullOrWhiteSpace())
return XDocument.Parse(value);
return FluentResults.Result.Fail<XDocument>(GetGeneralError(nameof(TryLoadXml), filePath));
try
{
await using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
return await XDocument.LoadAsync(fs, LoadOptions.PreserveWhitespace, CancellationToken.None);
}
catch (Exception e)
{
return FluentResults.Result.Fail<XDocument>(GetGeneralError(nameof(TryLoadXmlAsync), filePath));
}
}
public async Task<FluentResults.Result<string>> TryLoadTextAsync(string filePath, Encoding encoding = null)
@@ -601,7 +621,7 @@ public class StorageService : IStorageService
localFilePath)));
}
private FluentResults.Result<string> GetAbsFromPackage(ContentPackage package, string localFilePath)
public FluentResults.Result<string> GetAbsFromPackage(ContentPackage package, string localFilePath)
{
if (package is null)
{
@@ -5,7 +5,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Barotrauma.LuaCs.Configuration;
using Barotrauma.LuaCs.Data;
using Barotrauma.LuaCs.Networking;
using Barotrauma.LuaCs.Services;
using Barotrauma.LuaCs.Services.Safe;
using Barotrauma.Networking;
@@ -1,6 +1,6 @@
using System;
using Barotrauma.LuaCs.Data;
using Barotrauma.LuaCs.Networking;
using Barotrauma.LuaCs.Services;
using Barotrauma.LuaCs.Services.Compatibility;
using Barotrauma.Networking;
@@ -8,7 +8,7 @@ namespace Barotrauma.LuaCs.Services;
internal delegate void NetMessageReceived(IReadMessage netMessage);
internal interface INetworkingService : IReusableService, ILuaCsNetworking
internal partial interface INetworkingService : IReusableService, ILuaCsNetworking
{
bool IsActive { get; }
bool IsSynchronized { get; }
@@ -0,0 +1,15 @@
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Barotrauma.LuaCs.Data;
using Barotrauma.LuaCs.Events;
namespace Barotrauma.LuaCs.Services;
public interface IPackageInfoLookupService : IReusableService
{
Task<FluentResults.Result<IPackageInfo>> Lookup(string packageName);
Task<FluentResults.Result<IPackageInfo>> Lookup(string packageName, ulong steamWorkshopId);
Task<FluentResults.Result<IPackageInfo>> Lookup(ulong steamWorkshopId);
Task<FluentResults.Result<IPackageInfo>> Lookup(ContentPackage package);
void RefreshPackageLists();
}
@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Barotrauma.LuaCs.Services;
public interface IPackageListRetrievalService : IService
{
IEnumerable<ContentPackage> GetEnabledContentPackages();
IEnumerable<ContentPackage> GetAllContentPackages();
}
@@ -17,20 +17,22 @@ public interface IPackageManagementService : IReusableService, ILocalizationsRes
{
/// <summary>
/// Loads and parses the provided <see cref="ContentPackage"/> for <see cref="IResourceInfo"/> supported by the current runtime environment.
/// Will overwrite any existing package data.
/// </summary>
/// <param name="packages"></param>
/// <param name="packages">Package to load.</param>
/// <returns></returns>
Task<FluentResults.Result> LoadPackageInfosAsync(ContentPackage packages);
Task<FluentResults.Result> LoadPackageInfosAsync(ContentPackage package);
/// <summary>
/// Loads and parses the provided <see cref="ContentPackage"/> collection for <see cref="IResourceInfo"/> supported by the current runtime environment.
/// Will overwrite any existing package data.
/// </summary>
/// <param name="packages"></param>
/// <param name="packages">List of packages to load.</param>
/// <returns></returns>
Task<IReadOnlyList<(ContentPackage, FluentResults.Result)>> LoadPackagesInfosAsync(IReadOnlyList<ContentPackage> packages);
IReadOnlyList<ContentPackage> GetAllLoadedPackages();
void DisposePackageInfos(ContentPackage package);
void DisposePackagesInfos(IReadOnlyList<ContentPackage> packages);
void DisposeAllPackagesInfos();
FluentResults.Result<IPackageDependency> GetPackageDependencyInfo(ContentPackage ownerPackage, string packageName, ulong steamWorkshopId);
// single
FluentResults.Result<IAssembliesResourcesInfo> GetAssembliesInfos(ContentPackage package, bool onlySupportedResources = true);
@@ -18,6 +18,7 @@ public interface IReusableService : IService
/// <summary>
/// Base interface inherited by all services.
/// </summary>
/// <exception cref="ObjectDisposedException">Throws exception if `IsDisposed` return true.</exception>
public interface IService : IDisposable
{
bool IsDisposed { get; }
@@ -32,6 +32,7 @@ public interface IStorageService : IService
ImmutableArray<(string, FluentResults.Result<byte[]>)> LoadPackageBinaryFiles(ContentPackage package, ImmutableArray<string> localFilePaths);
ImmutableArray<(string, FluentResults.Result<string>)> LoadPackageTextFiles(ContentPackage package, ImmutableArray<string> localFilePaths);
FluentResults.Result<ImmutableArray<string>> FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively);
FluentResults.Result<string> GetAbsFromPackage(ContentPackage package, string localFilePath);
// async
// singles
Task<FluentResults.Result<XDocument>> LoadPackageXmlAsync(ContentPackage package, string localFilePath);
@@ -50,6 +51,8 @@ public interface IStorageService : IService
FluentResults.Result TrySaveText(string filePath, in string text, Encoding encoding = null);
FluentResults.Result TrySaveBinary(string filePath, in byte[] bytes);
FluentResults.Result<bool> FileExists(string filePath);
FluentResults.Result<bool> DirectoryExists(string directoryPath);
//async
Task<FluentResults.Result<XDocument>> TryLoadXmlAsync(string filePath, Encoding encoding = null);
Task<FluentResults.Result<string>> TryLoadTextAsync(string filePath, Encoding encoding = null);