- Finished most of LuaCsSetup top-level functionality.

- Removed some unneeded interface definitions.
- Clean-slated some Services that need to be re-written.
This commit is contained in:
MapleWheels
2025-02-14 12:34:59 -05:00
committed by Maplewheels
parent d2b9ca4c1b
commit 7436ea3e8c
39 changed files with 1009 additions and 2045 deletions
@@ -83,6 +83,7 @@ public class EventService : IEventService, IEventAssemblyContextUnloading
public EventService(Lazy<IPluginManagementService> pluginManagementService)
{
_pluginManagementService = pluginManagementService ?? throw new ArgumentNullException(nameof(pluginManagementService));
this.Subscribe<IEventAssemblyContextUnloading>(this);
}
public bool IsDisposed { get; private set; } = false;
@@ -301,8 +302,19 @@ public class EventService : IEventService, IEventAssemblyContextUnloading
dict.Remove(OneOf<string, IEvent>.FromT1(subscriber));
}
public void ClearAllEventSubscribers<T>() where T : IEvent => _subscriptions.Remove(typeof(T));
public void ClearAllSubscribers() => _subscriptions.Clear();
public void ClearAllEventSubscribers<T>() where T : IEvent
{
_subscriptions.Remove(typeof(T));
if (typeof(IEventAssemblyContextUnloading) == typeof(T))
{
this.Subscribe<IEventAssemblyContextUnloading>(this);
}
}
public void ClearAllSubscribers()
{
_subscriptions.Clear();
this.Subscribe<IEventAssemblyContextUnloading>(this);
}
public FluentResults.Result PublishEvent<T>(Action<T> action) where T : IEvent<T>
{
@@ -151,4 +151,6 @@ public partial class LoggerService : ILoggerService
public void Dispose() { }
public FluentResults.Result Reset() => FluentResults.Result.Ok();
public bool IsDisposed { get; }
}
@@ -0,0 +1,14 @@
using Barotrauma.LuaCs.Data;
using MoonSharp.Interpreter;
using MoonSharp.Interpreter.Interop;
using System;
using System.Collections.Immutable;
using System.Reflection;
using System.Threading.Tasks;
namespace Barotrauma.LuaCs.Services;
public class LuaScriptManagementService : ILuaScriptManagementService
{
}
@@ -1,176 +0,0 @@
using Barotrauma.LuaCs.Data;
using MoonSharp.Interpreter;
using MoonSharp.Interpreter.Interop;
using System;
using System.Collections.Immutable;
using System.Reflection;
namespace Barotrauma.LuaCs.Services;
public class LuaScriptService : ILuaScriptService, ILuaScriptManagementService
{
public void AddField(IUserDataDescriptor descriptor, string fieldName, DynValue value)
{
throw new NotImplementedException();
}
public void AddMethod(IUserDataDescriptor descriptor, string methodName, object function)
{
throw new NotImplementedException();
}
public FluentResults.Result AddScriptFiles(ImmutableArray<ILuaResourceInfo> luaResource)
{
throw new System.NotImplementedException();
}
public object CreateEnumTable(string typeName)
{
throw new NotImplementedException();
}
public object CreateStatic(string typeName)
{
throw new NotImplementedException();
}
public DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor descriptor)
{
throw new NotImplementedException();
}
public DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType)
{
throw new NotImplementedException();
}
public void Dispose()
{
throw new System.NotImplementedException();
}
public FluentResults.Result ExecuteLoadedScripts(ContentPackage package, bool pauseExecutionOnError = false, bool verboseLogging = false)
{
throw new NotImplementedException();
}
public FluentResults.Result ExecuteLoadedScripts(ImmutableArray<ILuaResourceInfo> scripts, bool pauseExecutionOnError = false, bool verboseLogging = false)
{
throw new NotImplementedException();
}
public FluentResults.Result ExecuteLoadedScripts(bool pauseExecutionOnError = false, bool verboseLogging = false)
{
throw new NotImplementedException();
}
public FluentResults.Result ExecuteScripts(bool pauseExecutionOnScriptError = false, bool verboseLogging = false)
{
throw new System.NotImplementedException();
}
public FieldInfo FindFieldRecursively(Type type, string fieldName)
{
throw new NotImplementedException();
}
public MethodInfo FindMethodRecursively(Type type, string methodName, Type[] types = null)
{
throw new NotImplementedException();
}
public PropertyInfo FindPropertyRecursively(Type type, string propertyName)
{
throw new NotImplementedException();
}
public ImmutableArray<ILuaResourceInfo> GetScriptResources()
{
throw new System.NotImplementedException();
}
public bool HasMember(object obj, string memberName)
{
throw new NotImplementedException();
}
public bool IsRegistered(Type type)
{
throw new NotImplementedException();
}
public bool IsTargetType(object obj, string typeName)
{
throw new NotImplementedException();
}
public void MakeFieldAccessible(IUserDataDescriptor descriptor, string fieldName)
{
throw new NotImplementedException();
}
public void MakeMethodAccessible(IUserDataDescriptor descriptor, string methodName, string[] parameters = null)
{
throw new NotImplementedException();
}
public void MakePropertyAccessible(IUserDataDescriptor descriptor, string propertyName)
{
throw new NotImplementedException();
}
public IUserDataDescriptor RegisterGenericType(Type type)
{
throw new NotImplementedException();
}
public IUserDataDescriptor RegisterGenericType(string typeName, params string[] typeNameArgs)
{
throw new NotImplementedException();
}
public IUserDataDescriptor RegisterType(Type type)
{
throw new NotImplementedException();
}
public IUserDataDescriptor RegisterType(string typeName)
{
throw new NotImplementedException();
}
public void RemoveMember(IUserDataDescriptor descriptor, string memberName)
{
throw new NotImplementedException();
}
public void RemoveScriptFiles(ImmutableArray<ILuaResourceInfo> luaResource)
{
throw new System.NotImplementedException();
}
public FluentResults.Result Reset()
{
throw new System.NotImplementedException();
}
public string TypeOf(object obj)
{
throw new NotImplementedException();
}
public void UnregisterAllTypes()
{
throw new NotImplementedException();
}
public void UnregisterType(Type type)
{
throw new NotImplementedException();
}
public void UnregisterType(string typeName)
{
throw new NotImplementedException();
}
}
@@ -1,461 +1,13 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Barotrauma.Extensions;
using Barotrauma.LuaCs.Data;
using Barotrauma.Steam;
using FluentResults;
using FluentResults.LuaCs;
using QuikGraph;
namespace Barotrauma.LuaCs.Services;
public class PackageManagementService : IPackageManagementService
{
private readonly Func<IPackageService> _contentPackageServiceFactory;
private readonly Lazy<IAssemblyManagementService> _assemblyManagementService;
private readonly ConcurrentDictionary<ContentPackage, IPackageService> _contentPackages = new();
private readonly ConcurrentQueue<LoadablePackage> _queuedPackages = new();
private readonly ConcurrentDictionary<DependencyEntryKey, IPackageDependencyInfo> _packageDependencyInfos = new();
/// <summary>
/// ConcurrentDictionary handles access/read synchronization. This is to ensure that we are not trying to
/// access the collection during a load/unload/modify operation.
/// </summary>
private readonly ReaderWriterLockSlim _contentPackagesModificationsLock = new();
/// <summary>
/// This lock ensures that we are not adding new entries to the queue between when we read the contents and
/// empty the buffer.
/// </summary>
private readonly ReaderWriterLockSlim _packageQueueProcessingLock = new();
public PackageManagementService(
Func<IPackageService> getPackageService,
Lazy<IAssemblyManagementService> assemblyManagementService)
{
this._contentPackageServiceFactory = getPackageService;
this._assemblyManagementService = assemblyManagementService;
}
#region STATE_RESET
public void Dispose()
{
// TODO release managed resources here
}
public FluentResults.Result Reset()
{
throw new NotImplementedException();
}
#endregion
public void QueuePackages(ImmutableArray<LoadablePackage> packages)
{
_packageQueueProcessingLock.EnterReadLock();
try
{
foreach (LoadablePackage package in packages)
_queuedPackages.Enqueue(package);
}
finally
{
_packageQueueProcessingLock.ExitReadLock();
}
}
public FluentResults.Result ParseQueuedPackages(bool loadParallel = true, bool reportFailOnDuplicates = false)
{
if (!ModUtils.Environment.IsMainThread)
throw new InvalidOperationException($"{nameof(ParseQueuedPackages)}: This method can only be called on the main thread.");
ImmutableArray<LoadablePackage> packagesToProcess = ImmutableArray<LoadablePackage>.Empty;
_packageQueueProcessingLock.EnterWriteLock();
try
{
Interlocked.MemoryBarrier();
if (_queuedPackages.IsEmpty)
return FluentResults.Result.Ok().WithSuccess($"{nameof(ParseQueuedPackages)}: The Queue is empty.");
packagesToProcess = _queuedPackages.Where(p => p.Package is not null)
.Distinct().ToImmutableArray();
_queuedPackages.Clear();
}
finally
{
_packageQueueProcessingLock.ExitWriteLock();
}
FluentResults.Result[] loadResults = new FluentResults.Result[packagesToProcess.Length];
FluentResults.Result res = new FluentResults.Result();
// Load ModConfigInfo
_contentPackagesModificationsLock.EnterWriteLock();
try
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
Interlocked.MemoryBarrier();
if (loadParallel)
{
Parallel.For(0, loadResults.Length, new ParallelOptions()
{
/*
* This is an IO-bound operation. The purpose of parallelism here is to allow loaded package
* data to be processed while another package is waiting on the storage device for its info.
*/
MaxDegreeOfParallelism = 2
},i =>
{
loadResults[i] = LoadPackageInfo(packagesToProcess[i]);
});
}
else
{
for (int i = 0; i < loadResults.Length; i++)
{
loadResults[i] = LoadPackageInfo(packagesToProcess[i]);
}
}
stopwatch.Stop();
res.WithSuccess(new Success(
$"Completed parsing of {loadResults.Length} packages in {stopwatch.ElapsedMilliseconds} milliseconds."));
for (int i = 0; i < loadResults.Length; i++)
{
res = loadResults[i].IsSuccess
? res.WithSuccesses(loadResults[i].Successes)
: res.WithErrors(loadResults[i].Errors);
}
return res;
}
catch (AggregateException ae)
{
return FluentResults.Result.Fail(new Error($"{nameof(ParseQueuedPackages)}: Failed to load packages! AE.")
.WithMetadata(MetadataType.ExceptionDetails, ae.InnerException?.Message ?? ae.Message)
.WithMetadata(MetadataType.StackTrace, ae.StackTrace)
.WithMetadata(MetadataType.ExceptionObject, this));
}
catch (ArgumentNullException ane)
{
return FluentResults.Result.Fail(
new Error($"{nameof(ParseQueuedPackages)}: Failed to load packages! ANE.")
.WithMetadata(MetadataType.ExceptionDetails, ane.InnerException?.Message ?? ane.Message)
.WithMetadata(MetadataType.StackTrace, ane.StackTrace)
.WithMetadata(MetadataType.ExceptionObject, this));
}
finally
{
_contentPackagesModificationsLock.ExitWriteLock();
}
/*
* Helper functions
*/
// register in the list so we can check against it.
FluentResults.Result LoadPackageInfo(LoadablePackage package)
{
try
{
if (package.Package == null)
{
return FluentResults.Result.Fail(
new Error($"{nameof(LoadPackageInfo)}: Package is null!")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, package));
}
if (_contentPackages.TryGetValue(package.Package, out var packageService))
{
if (reportFailOnDuplicates)
{
return FluentResults.Result.Fail(new Error($"The package {package.Package?.Name} is already loaded.")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, package.Package));
}
return FluentResults.Result.Ok();
}
packageService = _contentPackageServiceFactory.Invoke();
_contentPackages[package.Package] = packageService;
return packageService.LoadResourcesInfo(package);
}
catch (NullReferenceException nre)
{
return FluentResults.Result.Fail(new Error($"{nameof(LoadPackageInfo)}: NRE while loading package {package.Package?.Name}!")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.StackTrace, nre.StackTrace ?? "StackTrace not available")
.WithMetadata(MetadataType.ExceptionDetails, nre.InnerException?.Message ?? nre.Message)
.WithMetadata(MetadataType.RootObject, package));
}
}
}
public FluentResults.Result LoadPackageConfigsResourcesGroup(bool loadParallel = true)
{
throw new NotImplementedException();
}
public FluentResults.Result LoadAllPackageResources(bool loadParallel = true, bool safeResourcesOnly = true)
{
throw new NotImplementedException();
}
public FluentResults.Result UnloadPackages()
{
if (!ModUtils.Environment.IsMainThread)
{
return FluentResults.Result.Fail(
new ExceptionalError(new InvalidOperationException($"{nameof(UnloadPackages)}: This method can only be called on the main thread."))
.WithMetadata(MetadataType.ExceptionObject, this));
}
var res = new FluentResults.Result();
_contentPackagesModificationsLock.EnterWriteLock();
try
{
// TODO: Finish him
}
finally
{
_contentPackagesModificationsLock.ExitWriteLock();
}
throw new NotImplementedException();
}
public bool IsPackageLoaded(ContentPackage package) => package is not null && _contentPackages.ContainsKey(package);
public bool CheckDependencyLoaded(IPackageDependencyInfo info) =>
info is not null && IsPackageLoaded(info.DependencyPackage);
public bool CheckDependenciesLoaded([NotNull]IEnumerable<IPackageDependencyInfo> infos, out ImmutableArray<IPackageDependencyInfo> missingPackages)
{
var missing = ImmutableArray.CreateBuilder<IPackageDependencyInfo>();
missing.AddRange(infos
.Where(i => i.DependencyPackage is not null)
.DistinctBy(i => i.DependencyPackage)
.Where(i => !CheckDependencyLoaded(i)));
missingPackages = missing.MoveToImmutable();
return missingPackages.Length == 0;
}
public bool CheckEnvironmentSupported(IPlatformInfo platform)
{
return (platform.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0
&& (platform.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0;
}
public Result<IPackageDependencyInfo> GetPackageDependencyInfoRecord(ContentPackage package, bool addIfMissing = false)
{
if (package is null)
{
return new FluentResults.Result<IPackageDependencyInfo>()
.WithError(new Error($"{nameof(GetPackageDependencyInfoRecord)}: Package is null!")
.WithMetadata(MetadataType.ExceptionObject, this));
}
if (_packageDependencyInfos.TryGetValue(package, out var result))
{
return new FluentResults.Result<IPackageDependencyInfo>()
.WithValue(result);
}
if (addIfMissing)
{
return AddDependencyRecord(package, package.Name, package.Path,
package.TryExtractSteamWorkshopId(out var id) ? id.Value : 0,
false);
}
return FluentResults.Result.Fail<IPackageDependencyInfo>(new Error($"Could not find package {package.Name}!")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, package));
}
public Result<IPackageDependencyInfo> GetPackageDependencyInfoRecord(ulong steamWorkshopId, string packageName, string folderPath = null,
bool addIfMissing = false)
{
if (packageName.IsNullOrWhiteSpace() || folderPath.IsNullOrWhiteSpace())
{
return new FluentResults.Result<IPackageDependencyInfo>()
.WithError(new Error($"{nameof(GetPackageDependencyInfoRecord)}: folder path and/or package name are null!")
.WithMetadata(MetadataType.ExceptionObject, this));
}
if (_packageDependencyInfos.TryGetValue((packageName,steamWorkshopId,folderPath), out var result))
{
return new FluentResults.Result<IPackageDependencyInfo>()
.WithValue(result);
}
// TODO: Finish this
throw new NotImplementedException();
}
public Result<IPackageDependencyInfo> GetPackageDependencyInfoRecord(string folderPath)
{
throw new NotImplementedException();
}
public IPackageDependencyInfo CreateOrphanPackageDependencyInfoRecord(
string packageName,
string packagePath,
ulong steamWorkshopId)
{
return new DependencyInfo()
{
DependencyPackage = null,
FallbackPackageName = packageName,
FolderPath = packagePath.IsNullOrWhiteSpace() ? null : System.IO.Path.GetFullPath(packagePath),
SteamWorkshopId = steamWorkshopId,
IsMissing = true,
IsWorkshopInstallation = false
};
}
private Result<IPackageDependencyInfo> AddDependencyRecord(
ContentPackage package,
string packageName,
string folderPath,
ulong steamWorkshopId,
bool isMissing)
{
// TODO: Redo
try
{
var dependencyInfo = new DependencyInfo()
{
DependencyPackage = package,
FallbackPackageName = packageName,
FolderPath = System.IO.Path.GetFullPath(folderPath),
SteamWorkshopId = steamWorkshopId,
IsMissing = isMissing,
IsWorkshopInstallation = steamWorkshopId != 0
};
if (package is not null)
{
_packageDependencyInfos.AddOrUpdate(package, pack => dependencyInfo,
(pack, dep) => dependencyInfo);
}
return new FluentResults.Result<IPackageDependencyInfo>()
.WithValue(dependencyInfo)
.WithSuccess($"New value created.");
}
catch (Exception ex)
{
return new FluentResults.Result<IPackageDependencyInfo>()
.WithError(new ExceptionalError(ex)
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.ExceptionDetails, ex.Message)
.WithMetadata(MetadataType.RootObject, package)
.WithMetadata(MetadataType.StackTrace, ex.StackTrace ?? "StackTrace not available"));
}
}
private readonly record struct DependencyEntryKey : IEqualityComparer<DependencyEntryKey>, IEquatable<DependencyEntryKey>
{
public ContentPackage Package { get; init; }
public string FolderPath { get; init; }
public string PackageName { get; init; }
public ulong SteamWorkshopId { get; init; }
public DependencyEntryKey(ContentPackage package)
{
Package = package ?? throw new ArgumentNullException(nameof(package), $"{nameof(DependencyEntryKey)}.ctor: Package cannot be null!");
PackageName = package.Name;
SteamWorkshopId = package.TryExtractSteamWorkshopId(out var id) ? id.Value : (ulong)0;
FolderPath = package.Path;
}
public DependencyEntryKey(string packageName, string folderPath, ulong steamWorkshopId)
{
PackageName = packageName;
SteamWorkshopId = steamWorkshopId;
FolderPath = folderPath;
Package = null;
}
public DependencyEntryKey(string packageName, ulong steamWorkshopId)
{
PackageName = packageName;
SteamWorkshopId = steamWorkshopId;
FolderPath = null;
Package = null;
}
public bool Equals(DependencyEntryKey other)
{
return Equals(this, other);
}
public override int GetHashCode()
{
return GetHashCode(this);
}
public bool Equals(DependencyEntryKey x, DependencyEntryKey y)
{
if (x == y)
return true;
if (x.Package is not null && y.Package is not null && x.Package == Package)
return true;
// folder should be a unique key if not unset.
if (!x.FolderPath.IsNullOrWhiteSpace() && !y.FolderPath.IsNullOrWhiteSpace() &&
x.FolderPath == FolderPath)
return true;
if (!x.PackageName.IsNullOrWhiteSpace() && !y.PackageName.IsNullOrWhiteSpace()
&& x.SteamWorkshopId != 0 && y.SteamWorkshopId != 0)
return x.PackageName == y.PackageName && x.SteamWorkshopId == y.SteamWorkshopId;
if (!x.PackageName.IsNullOrWhiteSpace() && !y.PackageName.IsNullOrWhiteSpace() && x.PackageName == PackageName)
return true;
if (x.SteamWorkshopId != 0 && y.SteamWorkshopId != 0 &&
x.SteamWorkshopId == y.SteamWorkshopId)
return true;
return false;
}
public int GetHashCode(DependencyEntryKey obj)
{
if (!obj.PackageName.IsNullOrWhiteSpace())
return obj.PackageName.GetHashCode();
if (obj.SteamWorkshopId != 0)
return obj.SteamWorkshopId.GetHashCode();
if (obj.Package is not null)
return obj.Package.GetHashCode();
// We don't want to check the FolderPath because we want to resolve dependencies using packages
// that might be local instead in the workshop folder.
return 2342568; // random const value: collisions are fine as we want to call Equals()
}
public static implicit operator DependencyEntryKey(ContentPackage package) => new(package);
public static implicit operator DependencyEntryKey((string packageName, ulong steamWorkshopId) tuple1) =>
new (tuple1.packageName, tuple1.steamWorkshopId);
public static implicit operator DependencyEntryKey((string packageName, ulong steamWorkshopId, string folderPath) tuple1) =>
new (tuple1.packageName, tuple1.folderPath, tuple1.steamWorkshopId);
}
}
@@ -1,686 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using Barotrauma.Extensions;
using Barotrauma.LuaCs.Data;
using Barotrauma.LuaCs.Services.Processing;
using FluentResults;
using FluentResults.LuaCs;
using OneOf;
namespace Barotrauma.LuaCs.Services;
public partial class PackageService : IPackageService
{
private readonly ReaderWriterLockSlim _operationsUsageLock = new();
// only stops race conditions for pointer access
// mod config / package scanners/parsers
private readonly Lazy<IModConfigCreatorService> _configParserService;
private readonly Lazy<ILuaScriptService> _luaScriptService;
private readonly Lazy<ILocalizationService> _localizationService;
private readonly Lazy<IPluginService> _pluginService;
private readonly Lazy<IConfigService> _configService;
private readonly IPackageManagementService _packageManagementService;
private readonly IStorageService _storageService;
private readonly ILoggerService _loggerService;
// .ctor in server source and client source
// state monitors
private int _configsLoaded, _localizationsLoaded, _luaScriptsLoaded, _pluginsLoaded, _isDisposed;
private int _loadingOperationsRunning;
private int _isEnabledInModList;
public bool ConfigsLoaded
{
get => ModUtils.Threading.GetBool(ref _configsLoaded);
private set => ModUtils.Threading.SetBool(ref _configsLoaded, value);
}
public bool LocalizationsLoaded
{
get => ModUtils.Threading.GetBool(ref _localizationsLoaded);
private set => ModUtils.Threading.SetBool(ref _localizationsLoaded, value);
}
public bool LuaScriptsLoaded
{
get => ModUtils.Threading.GetBool(ref _luaScriptsLoaded);
private set => ModUtils.Threading.SetBool(ref _luaScriptsLoaded, value);
}
public bool PluginsLoaded
{
get => ModUtils.Threading.GetBool(ref _pluginsLoaded);
private set => ModUtils.Threading.SetBool(ref _pluginsLoaded, value);
}
public bool IsDisposed
{
get => ModUtils.Threading.GetBool(ref _isDisposed);
private set => ModUtils.Threading.SetBool(ref _isDisposed, value);
}
private bool LoadingOperationsRunning
{
get => Interlocked.CompareExchange(ref _loadingOperationsRunning, 0, 0) > 0;
set // we use the set as our inc/decr
{
if (value)
{
Interlocked.Add(ref _loadingOperationsRunning, 1);
}
else
{
Interlocked.Add(ref _loadingOperationsRunning, -1);
}
}
}
#region Member: ContentPackage
private readonly ReaderWriterLockSlim _packageAccessLock = new();
private ContentPackage _package;
public ContentPackage Package
{
get
{
_packageAccessLock.EnterReadLock();
try
{
return _package;
}
finally
{
_packageAccessLock.ExitReadLock();
}
}
private set
{
_packageAccessLock.EnterWriteLock();
try
{
_package = value;
}
finally
{
_packageAccessLock.ExitWriteLock();
}
}
}
#endregion
#region DataContracts
#region Member: ModConfigInfo
private readonly ReaderWriterLockSlim _modConfigUsageLock = new();
private IModConfigInfo _modConfigInfo;
public IModConfigInfo ModConfigInfo
{
get
{
_modConfigUsageLock.EnterReadLock();
try
{
return _modConfigInfo;
}
finally
{
_modConfigUsageLock.ExitReadLock();
}
}
private set
{
_modConfigUsageLock.EnterWriteLock();
try
{
_modConfigInfo = value;
}
finally
{
_modConfigUsageLock.ExitWriteLock();
}
}
}
public bool IsEnabledInModList
{
get => ModUtils.Threading.GetBool(ref _isEnabledInModList);
private set => ModUtils.Threading.SetBool(ref _isEnabledInModList, value);
}
#endregion
public ImmutableArray<CultureInfo> SupportedCultures => ModConfigInfo?.SupportedCultures ?? ImmutableArray<CultureInfo>.Empty;
public ImmutableArray<IAssemblyResourceInfo> Assemblies => ModConfigInfo?.Assemblies ?? ImmutableArray<IAssemblyResourceInfo>.Empty;
public ImmutableArray<ILocalizationResourceInfo> Localizations => ModConfigInfo?.Localizations ?? ImmutableArray<ILocalizationResourceInfo>.Empty;
public ImmutableArray<ILuaResourceInfo> LuaScripts => ModConfigInfo?.LuaScripts ?? ImmutableArray<ILuaResourceInfo>.Empty;
public ImmutableArray<IConfigResourceInfo> Configs => ModConfigInfo?.Configs ?? ImmutableArray<IConfigResourceInfo>.Empty;
public ImmutableArray<IConfigProfileResourceInfo> ConfigProfiles => ModConfigInfo?.ConfigProfiles ?? ImmutableArray<IConfigProfileResourceInfo>.Empty;
#endregion
#region PublicAPI
public FluentResults.Result LoadResourcesInfo(LoadablePackage cpackage)
{
if (cpackage.Package == null)
{
return FluentResults.Result.Fail(new Error($"{nameof(LoadResourcesInfo)}: Package is null!")
.WithMetadata(MetadataType.ExceptionObject,this)
.WithMetadata(MetadataType.RootObject, cpackage));
}
ContentPackage package = cpackage.Package;
_operationsUsageLock.EnterWriteLock();
LoadingOperationsRunning = true;
try
{
if (IsDisposed)
{
return FluentResults.Result.Fail(
new Error("Service is disposed.")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, package));
}
var res = _configParserService.Value.BuildConfigForPackage(package);
if (res.IsFailed)
{
return FluentResults.Result.Fail(res.Errors)
.WithError(new Error("PackageService failed to load ModConfigInfo")
.WithMetadata(MetadataType.ExceptionObject, _configParserService)
.WithMetadata(MetadataType.RootObject, package));
}
this.ModConfigInfo = res.Value;
this.IsEnabledInModList = cpackage.IsEnabled;
return FluentResults.Result.Ok();
}
catch (Exception e)
{
return FluentResults.Result.Fail(new Error(e.Message)
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, package)
.WithMetadata(MetadataType.StackTrace, e.StackTrace));
}
finally
{
LoadingOperationsRunning = false;
_operationsUsageLock.ExitWriteLock();
}
}
public FluentResults.Result LoadPlugins([NotNull]IAssembliesResourcesInfo assembliesInfo, bool ignoreDependencySorting = false)
{
_operationsUsageLock.EnterReadLock();
LoadingOperationsRunning = true;
try
{
if (CheckResourceSanitation(OneOf<IAssembliesResourcesInfo, ILocalizationsResourcesInfo,
IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo>
.FromT0(assembliesInfo)) is { IsFailed: true } failed)
{
return failed;
}
// Order these assemblies by internal dependencies
ImmutableArray<IAssemblyResourceInfo> resources;
if (ignoreDependencySorting)
{
resources = assembliesInfo.Assemblies;
}
else // sort by load order
{
resources = assembliesInfo.Assemblies
.OrderByDescending(a => a.LoadPriority)
.ToImmutableArray();
}
// Try loading them, throw on failure.
if (_pluginService.Value.LoadAndInstanceTypes<IAssemblyPlugin>(resources, true, out var instancedTypes) is { IsFailed: true} failed2)
{
return failed2.WithError(new Error($"{nameof(LoadPlugins)}: Failed to load plugins for {this.Package.Name}")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, assembliesInfo));
}
PluginsLoaded = true;
return FluentResults.Result.Ok();
}
finally
{
LoadingOperationsRunning = false;
_operationsUsageLock.ExitReadLock();
}
}
public FluentResults.Result LoadLocalizations([NotNull]ILocalizationsResourcesInfo localizationsInfo)
{
_operationsUsageLock.EnterReadLock();
LoadingOperationsRunning = true;
try
{
if (CheckResourceSanitation(OneOf<IAssembliesResourcesInfo, ILocalizationsResourcesInfo,
IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo>
.FromT1(localizationsInfo)) is { IsFailed: true } failed)
{
return failed;
}
if (_localizationService.Value.LoadLocalizations(localizationsInfo.Localizations) is { IsFailed: true} failed2)
{
return failed2.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load localizations")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, localizationsInfo));
}
LocalizationsLoaded = true;
return FluentResults.Result.Ok();
}
finally
{
LoadingOperationsRunning = false;
_operationsUsageLock.ExitReadLock();
}
}
public FluentResults.Result AddLuaScripts([NotNull]ILuaScriptsResourcesInfo luaScriptsInfo)
{
_operationsUsageLock.EnterReadLock();
LoadingOperationsRunning = true;
try
{
if (CheckResourceSanitation(OneOf<IAssembliesResourcesInfo, ILocalizationsResourcesInfo,
IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo>
.FromT4(luaScriptsInfo)) is { IsFailed: true } failed)
{
return failed;
}
if (_luaScriptService.Value.AddScriptFiles(luaScriptsInfo.LuaScripts) is { IsFailed: true} failed2)
{
return failed2.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load lua scripts.")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, luaScriptsInfo));
}
LuaScriptsLoaded = true;
return FluentResults.Result.Ok();
}
finally
{
LoadingOperationsRunning = false;
_operationsUsageLock.ExitReadLock();
}
}
public FluentResults.Result LoadConfig(
[NotNull]IConfigsResourcesInfo configsResourcesInfo,
[NotNull]IConfigProfilesResourcesInfo configProfilesResourcesInfo)
{
_operationsUsageLock.EnterReadLock();
LoadingOperationsRunning = true;
try
{
// register configs
if (CheckResourceSanitation(OneOf<IAssembliesResourcesInfo, ILocalizationsResourcesInfo,
IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo>
.FromT2(configsResourcesInfo)) is { IsFailed: true } failed)
{
return failed;
}
if (_configService.Value.AddConfigs(configsResourcesInfo.Configs) is { IsFailed: true} failed2)
{
return failed2.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load configs.")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, configsResourcesInfo));
}
// register config profiles
if (CheckResourceSanitation(OneOf<IAssembliesResourcesInfo, ILocalizationsResourcesInfo,
IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo>
.FromT3(configProfilesResourcesInfo)) is { IsFailed: true } failed3)
{
return failed3;
}
if (_configService.Value.AddConfigsProfiles(configProfilesResourcesInfo.ConfigProfiles) is { IsFailed: true} failed4)
{
return failed4.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load config profiles.")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, configProfilesResourcesInfo));
}
ConfigsLoaded = true;
return FluentResults.Result.Ok();
}
finally
{
LoadingOperationsRunning = false;
_operationsUsageLock.ExitReadLock();
}
}
public void Dispose()
{
/*
* Notes: we need to unload this package from services in the order that the services are dependent on each other.
* Unloading Order: Lua Scripts > Assemblies > Config Profiles > Configs > Styles > Localizations
*/
_operationsUsageLock.EnterWriteLock();
try
{
if (this.Package is null)
{
_loggerService.LogError(
$"Package Service: cannot Dispose of service as ContentPackage and info is not set!");
return;
}
if (this.ModConfigInfo is null)
{
_loggerService.LogError($"Package Service: cannot Dispose of service as ModConfigInfo is not loaded!");
return;
}
/*
* To be graceful, we want to ensure that any async calls and other threads are allowed to be processed before we begin
* disposal to reduce friction with other thread operations, so we release the lock and periodically check it
* to see of other threads have finished operations before cleaning everything up.
*/
IsDisposed = true; // set stop flag, callers should handle exception cases
Interlocked.MemoryBarrier(); //ensure cache states
DateTime timeoutLimit = DateTime.Now.AddSeconds(10);
while (LoadingOperationsRunning)
{
_operationsUsageLock.ExitWriteLock();
Thread.Sleep(1);
_operationsUsageLock.EnterWriteLock();
if (timeoutLimit < DateTime.Now)
{
_loggerService.LogError($"Package Service: Dispose() time out reached while waiting for other operations. Continuing.");
break;
}
}
GC.SuppressFinalize(this);
_luaScriptService.Value.RemoveScriptFiles(this.LuaScripts);
_pluginService.Value.DisposePlugins();
_configService.Value.RemoveConfigsProfiles(this.ConfigProfiles);
_configService.Value.RemoveConfigs(this.Configs);
#if CLIENT
_stylesService.Value.UnloadAllStyles();
#endif
_localizationService.Value.Remove(this.Localizations);
ModConfigInfo = null;
Package = null;
}
catch
{
_loggerService.LogError($"Package Service: exception while running Dispose().");
throw;
}
finally
{
_operationsUsageLock.ExitWriteLock();
}
}
public FluentResults.Result Reset()
{
_operationsUsageLock.EnterWriteLock();
try
{
if (this.Package is null)
{
return FluentResults.Result.Fail(new Error($"Package Service: cannot Dispose of service as ContentPackage and info is not set!")
.WithMetadata(MetadataType.ExceptionDetails, nameof(Reset))
.WithMetadata(MetadataType.ExceptionObject, this));
}
if (this.ModConfigInfo is null)
{
return FluentResults.Result.Fail(new Error($"Package Service: cannot Dispose of service as ModConfigInfo is not set!")
.WithMetadata(MetadataType.ExceptionDetails, nameof(Reset))
.WithMetadata(MetadataType.ExceptionObject, this));
}
Interlocked.MemoryBarrier(); //ensure cache states
DateTime timeoutLimit = DateTime.Now.AddSeconds(10);
while (LoadingOperationsRunning)
{
_operationsUsageLock.ExitWriteLock();
Thread.Sleep(1);
_operationsUsageLock.EnterWriteLock();
if (timeoutLimit < DateTime.Now)
{
_loggerService.LogError($"Package Service: Dispose() grace time-out reached while waiting for other operations. Continuing.");
break;
}
}
if (LuaScriptsLoaded)
{
_luaScriptService.Value.RemoveScriptFiles(this.LuaScripts);
LuaScriptsLoaded = false;
}
if (PluginsLoaded)
{
_pluginService.Value.DisposePlugins();
PluginsLoaded = false;
}
if (ConfigsLoaded)
{
_configService.Value.RemoveConfigsProfiles(this.ConfigProfiles);
_configService.Value.RemoveConfigs(this.Configs);
ConfigsLoaded = false;
}
if (LocalizationsLoaded)
{
_localizationService.Value.Remove(this.Localizations);
LocalizationsLoaded = false;
}
return FluentResults.Result.Ok();
}
finally
{
_operationsUsageLock.ExitWriteLock();
}
}
#endregion
#region INTERNAL
/// <summary>
/// [Thread Unsafe] Performs sanitation and null checks on resources and returns the results.
/// NOTE: Requires that resource locks be set by the caller.
/// </summary>
/// <param name="resourcesInfos"></param>
/// <returns></returns>
private FluentResults.Result CheckResourceSanitation(
OneOf.OneOf<IAssembliesResourcesInfo, ILocalizationsResourcesInfo,
IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo> resourcesInfos)
{
// execute checks based on known types
return resourcesInfos.Match<FluentResults.Result>(
ass => ChecksDispatcher(ass, nameof(ass.Assemblies), nameof(LoadPlugins),
ass.Assemblies, this.Assemblies),
loc => ChecksDispatcher(loc, nameof(loc.Localizations), nameof(LoadLocalizations),
loc.Localizations, this.Localizations),
cfg => ChecksDispatcher(cfg, nameof(cfg.Configs), nameof(LoadConfig),
cfg.Configs, this.Configs),
cfp => ChecksDispatcher(cfp, nameof(cfp.ConfigProfiles), nameof(LoadConfig),
cfp.ConfigProfiles, this.ConfigProfiles),
lua => ChecksDispatcher(lua, nameof(lua.LuaScripts), nameof(AddLuaScripts),
lua.LuaScripts, this.LuaScripts));
/*
* Helper functions
*/
FluentResults.Result ChecksDispatcher<T>(object obj, string resName, string callerName,
ImmutableArray<T> resList, ImmutableArray<T> compareList)
where T : class, IPackageInfo, IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo
{
string errMsg = $"{callerName}: Failed to load {resName}.";
if (DisposeCheck(obj) is { IsFailed: true } failed)
return failed;
if (SanitationChecksCore(obj, resName, callerName) is { IsFailed: true } failed1)
return failed1.WithError(new Error(errMsg));
if (SanitationChecksEnumerable(resList, resName, callerName) is { IsFailed: true } failed2)
return failed2.WithError(new Error(errMsg));
if (DebugCheck(resList, compareList, resName) is {IsFailed: true} failed3)
return failed3.WithError(new Error(errMsg));
return FluentResults.Result.Ok();
}
FluentResults.Result DisposeCheck(object obj)
{
if (IsDisposed)
{
return FluentResults.Result.Fail(new Error($"{nameof(PackageService)}: Tried to load resources when disposed.")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, obj));
}
return FluentResults.Result.Ok();
}
FluentResults.Result DebugCheck<T>(ImmutableArray<T> resList, ImmutableArray<T> compareList, string resName)
where T : class, IPackageInfo
{
#if DEBUG
Stack<Error> errors = new();
resList.ForEach(res =>
{
if (!compareList.Contains(res))
{
errors.Push(new Error($"Failed to load {resName} for: {this.Package.Name}")
.WithMetadata(MetadataType.ExceptionDetails, $"Tries to load {resName} resource {res.InternalName} but it is not from this package!")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, res));
}
});
if (errors.Count > 0)
{
return FluentResults.Result.Fail(errors).WithError(
new Error($"{nameof(LoadPlugins)}: errors in {resName} resources.")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, this.Package));
}
#endif
return FluentResults.Result.Ok();
}
}
private FluentResults.Result SanitationChecksCore(object obj, string resTypeInfoName, string callerName)
{
Error e = null;
if (obj is null)
{
e = new Error($"{nameof(SanitationChecksCore)}: null checks failed!")
.WithMetadata(MetadataType.ExceptionDetails, "Object is null!")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.Sources, new List<string>() { resTypeInfoName, callerName });
}
if (this.Package is null)
{
e = (e ?? new Error($"{nameof(SanitationChecksCore)}: null checks failed!"))
.WithMetadata(MetadataType.ExceptionDetails, "The Package is null!")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.Sources, new List<string>() { resTypeInfoName, callerName });
}
return e is null ? FluentResults.Result.Ok() : FluentResults.Result.Fail(e);
}
private FluentResults.Result SanitationChecksEnumerable<T>(ImmutableArray<T> resourceInfos, string resTypeInfoName, string callerName) where T : IResourceInfo, IResourceCultureInfo, IPackageInfo, IPackageDependenciesInfo
{
// Check if list is empty. Nothing more to do.
if (resourceInfos.IsDefaultOrEmpty)
return FluentResults.Result.Ok();
Stack<Error> errors = new();
// Check if all resources in the list are registered to this package, throw if not.
foreach (var resourceInfo in resourceInfos)
{
// ownership checks
if (resourceInfo.OwnerPackage is null)
{
errors.Push(new Error($"Error for resource: {resTypeInfoName}. OwnerPackage is null!")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, resourceInfo));
continue;
}
if (resourceInfo.OwnerPackage != this.Package)
{
errors.Push(new Error($"Error for resource: {resTypeInfoName}. $\"OwnerPackage {{resourceInfo.OwnerPackage?.Name}} is not the same as this package: {{this.Package}}")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, resourceInfo));
continue;
}
if (resourceInfo.Dependencies.IsDefaultOrEmpty)
continue;
// ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
foreach (var pdi in resourceInfo.Dependencies)
{
// for clarification: all resources passed to the function should always be loaded.
// unneeded optional resources should be filtered out before the list is sent.
// left this as a reminder :)
/*if (pdi.Optional)
return;*/
if (!_packageManagementService.CheckDependencyLoaded(pdi))
{
errors.Push(new Error($"Dependency missing for resource: {resourceInfo.OwnerPackage.Name}")
.WithMetadata(MetadataType.ExceptionDetails, $"Missing dependency: {pdi.DependencyPackage?.Name ?? (pdi.FallbackPackageName.IsNullOrWhiteSpace() ? pdi.SteamWorkshopId.ToString() : pdi.FallbackPackageName)}")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, resourceInfo));
}
}
// check runtime platform
if (!_packageManagementService.CheckEnvironmentSupported(resourceInfo))
{
errors.Push(new Error($"The resource {resourceInfo.OwnerPackage?.Name} does not support the current platform!")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, resourceInfo));
}
// check local culture
if (!_localizationService.Value.IsCurrentCultureSupported(resourceInfo))
{
errors.Push(new Error($"The resource {resourceInfo.OwnerPackage?.Name} does not support the current culture/region!")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, resourceInfo));
}
}
return errors.Count > 0 ? FluentResults.Result.Fail(errors) : FluentResults.Result.Ok();
}
#endregion
}
@@ -107,6 +107,22 @@ public class PluginManagementService : IPluginManagementService, IAssemblyManage
throw new NotImplementedException();
}
public IReadOnlyList<Result<(Type, T)>> ActivateTypeInstances<T>(ImmutableArray<Type> types, bool serviceInjection = true,
bool hostInstanceReference = false) where T : IDisposable
{
throw new NotImplementedException();
}
public FluentResults.Result UnloadHostedReferences()
{
throw new NotImplementedException();
}
public FluentResults.Result UnloadAllAssemblyResources()
{
throw new NotImplementedException();
}
public Result<Assembly> GetLoadedAssembly(string assemblyName, in Guid[] excludedContexts)
{
((IService)this).CheckDisposed();
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Xml.Linq;
using Barotrauma.LuaCs.Data;
using FluentResults;
@@ -7,46 +8,16 @@ namespace Barotrauma.LuaCs.Services.Processing;
#region TypeDef
// ReSharper disable once TypeParameterCanBeVariant
public interface IConverterService<TSrc, TOut> : IReusableService
public interface IConverterService<in TSrc, TOut> : IReusableService
{
Result<TOut> TryParseResource(TSrc src);
Result<TOut> TryParseResources(IEnumerable<TSrc> sources);
}
public interface IXmlResourceConverterService<TOut> : IConverterService<XElement, TOut> { }
public interface IResourceToXmlConverterService<TSrc> : IConverterService<TSrc, XElement> { }
#endregion
/// <summary>
/// Parses Xml to produce loading metadata info for linked loadable files.
/// </summary>
#region XmlToResourceInfoParsers
public interface IXmlAssemblyResConverter : IXmlResourceConverterService<IAssemblyResourceInfo> { }
public interface IXmlConfigResConverterService : IXmlResourceConverterService<IConfigResourceInfo> { }
public interface IXmlLocalizationResConverterService : IXmlResourceConverterService<ILocalizationResourceInfo> { }
#endregion
/// <summary>
/// Parses Xml to produce ready-to-use info/data without any additional file/data loading.
/// </summary>
#region XmlToInfoParsers
public interface IXmlDependencyConverterService : IXmlResourceConverterService<IPackageDependencyInfo> { }
public interface IXmlModConfigConverterService : IXmlResourceConverterService<IModConfigInfo> { }
/// <summary>
/// Parses legacy packages that make use of the RunConfig.xml structure to produce a ModConfig.
/// </summary>
public interface IXmlLegacyModConfigConverterService : IXmlResourceConverterService<IModConfigInfo> { }
#endregion
#region ResToInfoParsers
public interface ILocalizationResToInfoParser : IConverterService<ILocalizationResourceInfo, ILocalizationInfo> { }
public interface IConfigResConverterService : IConverterService<IConfigResourceInfo, IConfigInfo> { }
public interface IConfigProfileResConverterService : IConverterService<IConfigProfileResourceInfo, IConfigProfileInfo> { }
public interface IConverterServiceAsync<in TSrc, TOut> : IReusableService
{
Task<Result<TOut>> TryParseResourceAsync(TSrc src);
Task<Result<TOut>> TryParseResourcesAsync(IEnumerable<TSrc> sources);
}
#endregion
@@ -33,26 +33,25 @@ public class StorageService : IStorageService
private IConfigEntry<string> _kLocalFilePathRules = null;
private const string _packagePathKeyword = "<PACKNAME>";
private readonly string _runLocation = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location.CleanUpPath());
// TODO: Rewrite the config to get info from .ctor.
private IConfigEntry<string> LocalStoragePath => _kLocalStoragePath ??= GetOrCreateConfig(nameof(LocalStoragePath), "/Data/Mods");
private IConfigEntry<string> LocalFilePathRule => _kLocalFilePathRules ??= GetOrCreateConfig(nameof(LocalFilePathRule), _packagePathKeyword);
private IConfigEntry<string> GetOrCreateConfig(string name, string defaultValue)
{
var c = _configService.Value
.GetConfig<IConfigEntry<string>>(ModUtils.Definitions.LuaCsForBarotrauma, name);
if (c.IsSuccess)
{
return c.Value;
}
else
{
c = _configService.Value.AddConfigEntry(
ModUtils.Definitions.LuaCsForBarotrauma,
name, defaultValue, NetSync.None, valueChangePredicate: (value) => false);
if (c.IsSuccess)
return c.Value;
else
throw new KeyNotFoundException("Cannot find storage value for key: " + name);
}
if (c is not null)
return c;
var c1 = _configService.Value.AddConfigEntry(
ModUtils.Definitions.LuaCsForBarotrauma,
name, defaultValue, NetSync.None, valueChangePredicate: (value) => false);
if (c1.IsSuccess)
return c1.Value;
throw new KeyNotFoundException("Cannot find storage value for key: " + name);
}
public bool IsDisposed { get; private set; }
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Barotrauma.LuaCs.Configuration;
using Barotrauma.LuaCs.Data;
using Barotrauma.LuaCs.Networking;
@@ -15,20 +16,14 @@ public partial interface IConfigService : IReusableService, ILuaConfigService
/*
* Resource Files.
*/
FluentResults.Result AddConfigs(ImmutableArray<IConfigResourceInfo> configResources);
FluentResults.Result AddConfigsProfiles(ImmutableArray<IConfigProfileResourceInfo> configProfileResources);
FluentResults.Result RemoveConfigs(ImmutableArray<IConfigResourceInfo> configResources);
FluentResults.Result RemoveConfigsProfiles(ImmutableArray<IConfigProfileResourceInfo> configProfilesResources);
Task<FluentResults.Result> LoadConfigsAsync(ImmutableArray<IConfigResourceInfo> configResources);
Task<FluentResults.Result> LoadConfigsProfilesAsync(ImmutableArray<IConfigProfileResourceInfo> configProfileResources);
FluentResults.Result DisposeConfigs(ImmutableArray<IConfigResourceInfo> configResources);
FluentResults.Result DisposeConfigsProfiles(ImmutableArray<IConfigProfileResourceInfo> configProfilesResources);
FluentResults.Result DisposeConfigs(ContentPackage package);
FluentResults.Result DisposeConfigsProfiles(ContentPackage package);
/*
* From resources
*/
FluentResults.Result AddConfigs(ImmutableArray<IConfigInfo> configs);
FluentResults.Result AddConfigsProfiles(ImmutableArray<IConfigProfileInfo> configProfiles);
FluentResults.Result RemoveConfigs(ImmutableArray<IConfigInfo> configs);
FluentResults.Result RemoveConfigsProfiles(ImmutableArray<IConfigProfileInfo> configProfiles);
/*
* Immediate mode
*/
@@ -79,8 +74,6 @@ public partial interface IConfigService : IReusableService, ILuaConfigService
FluentResults.Result<IReadOnlyDictionary<string, IConfigBase>> GetConfigsForPackage(ContentPackage package);
FluentResults.Result<IReadOnlyDictionary<string, IConfigBase>> GetConfigsForPackage(string packageName);
IReadOnlyDictionary<(ContentPackage, string), IConfigBase> GetAllConfigs();
FluentResults.Result<IConfigBase> GetConfig(ContentPackage package, string name);
FluentResults.Result<IConfigBase> GetConfig(string packageName, string name);
FluentResults.Result<T> GetConfig<T>(ContentPackage package, string name) where T : IConfigBase;
FluentResults.Result<T> GetConfig<T>(string packageName, string name) where T : IConfigBase;
T GetConfig<T>(ContentPackage package, string name) where T : IConfigBase;
T GetConfig<T>(string packageName, string name) where T : IConfigBase;
}
@@ -2,6 +2,7 @@
using System.Globalization;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Barotrauma.LuaCs.Data;
namespace Barotrauma.LuaCs.Services;
@@ -10,9 +11,10 @@ public interface ILocalizationService : IReusableService
{
IReadOnlyCollection<CultureInfo> GetLoadedLocales();
void Remove(ImmutableArray<ILocalizationResourceInfo> localizations);
void DisposePackage(ContentPackage package);
FluentResults.Result SetCurrentCulture(CultureInfo culture);
FluentResults.Result SetCurrentCulture(string cultureName);
FluentResults.Result LoadLocalizations(ImmutableArray<ILocalizationResourceInfo> localizationResources);
Task<FluentResults.Result> LoadLocalizations(ImmutableArray<ILocalizationResourceInfo> localizationResources);
/// <summary>
/// Tries to get a localized string without a fallback. Returns success/failure and associated data.
@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Reflection;
using System.Threading.Tasks;
using Barotrauma.LuaCs.Data;
using MoonSharp.Interpreter;
using MoonSharp.Interpreter.Interop;
namespace Barotrauma.LuaCs.Services;
public interface ILuaScriptManagementService : IReusableService
{
#region Script_Ops
Task<FluentResults.Result> LoadScriptResourcesAsync(ImmutableArray<ILuaScriptResourceInfo> resourcesInfo);
FluentResults.Result ExecuteLoadedScripts(ContentPackage package, bool pauseExecutionOnError = false, bool verboseLogging = false);
FluentResults.Result ExecuteLoadedScripts(ImmutableArray<ILuaScriptResourceInfo> scripts, bool pauseExecutionOnError = false, bool verboseLogging = false);
FluentResults.Result ExecuteLoadedScripts(bool pauseExecutionOnError = false, bool verboseLogging = false);
FluentResults.Result DisposePackageResources(ContentPackage package);
FluentResults.Result UnloadActiveScripts();
FluentResults.Result DisposeAllPackageResources();
#endregion
#region Type_Registration
IUserDataDescriptor RegisterType(Type type);
/// <summary>
/// <b>[Deprecated]</b><br/>
/// Use <see cref="GetTypeInfo"/>() instead.
/// Gets the type information for an already registered type.
/// </summary>
/// <param name="typeName">The fully qualified name of the type and namespace.</param>
/// <returns>The <see cref="IUserDataDescriptor"/> for the type, if registered. Null if none is found.</returns>
[Obsolete($"Use {nameof(GetTypeInfo)} instead.")]
IUserDataDescriptor RegisterType(string typeName) => GetTypeInfo(typeName);
IUserDataDescriptor RegisterGenericType(Type type);
/// <summary>
/// <b>[Deprecated]</b><br/>
/// Use <see cref="GetTypeInfo"/>() instead.
/// Gets the generic type information for an already registered type.
/// </summary>
/// <param name="typeName">The fully qualified name of the generic type and namespace.</param>
/// <param name="typeNameArgs">The fully qualified name of the template types.</param>
/// <returns>The <see cref="IUserDataDescriptor"/> for the type, if registered. Null if none is found.</returns>
[Obsolete($"Use {nameof(GetGenericTypeInfo)} instead.")]
IUserDataDescriptor RegisterGenericType(string typeName, params string[] typeNameArgs) => GetGenericTypeInfo(typeName, typeNameArgs);
/// <summary>
/// Gets the type information for an already registered type.
/// </summary>
/// <param name="typeName">The fully qualified name of the type and namespace.</param>
/// <returns>The <see cref="IUserDataDescriptor"/> for the type, if registered. Null if none is found.</returns>
IUserDataDescriptor GetTypeInfo(string typeName);
/// <summary>
/// Gets the generic type information for an already registered type.
/// </summary>
/// <param name="typeName">The fully qualified name of the generic type and namespace.</param>
/// <param name="typeNameArgs">The fully qualified name of the template types.</param>
/// <returns>The <see cref="IUserDataDescriptor"/> for the type, if registered. Null if none is found.</returns>
IUserDataDescriptor GetGenericTypeInfo(string typeName, params string[] typeNameArgs);
void UnregisterType(Type type);
#endregion
#region Type_Checks_&Utilities
bool IsRegistered(Type type);
bool IsTargetType(object obj, string typeName);
string TypeOf(object obj);
object CreateStatic(string typeName);
object CreateEnumTable(string typeName);
FieldInfo FindFieldRecursively(Type type, string fieldName);
void MakeFieldAccessible(IUserDataDescriptor descriptor, string fieldName);
MethodInfo FindMethodRecursively(Type type, string methodName, Type[] types = null);
void MakeMethodAccessible(IUserDataDescriptor descriptor, string methodName, string[] parameters = null);
PropertyInfo FindPropertyRecursively(Type type, string propertyName);
void MakePropertyAccessible(IUserDataDescriptor descriptor, string propertyName);
void AddMethod(IUserDataDescriptor descriptor, string methodName, object function);
void AddField(IUserDataDescriptor descriptor, string fieldName, DynValue value);
void RemoveMember(IUserDataDescriptor descriptor, string memberName);
bool HasMember(object obj, string memberName);
DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor descriptor);
DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType);
#endregion
}
@@ -1,85 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Reflection;
using Barotrauma.LuaCs.Data;
using MoonSharp.Interpreter;
using MoonSharp.Interpreter.Interop;
namespace Barotrauma.LuaCs.Services;
public interface ILuaScriptService : IReusableService
{
#region Script_File_Collector
/// <summary>
/// Adds the script files to the runner but does not execute them.
/// </summary>
/// <param name="luaResource"></param>
/// <returns></returns>
FluentResults.Result AddScriptFiles(ImmutableArray<ILuaResourceInfo> luaResource);
/// <summary>
/// Removes the specific resources from the script runner. Important: Does not stop the
/// execution of any code related to the files nor guarantee cleanup of resources!
/// </summary>
/// <param name="luaResource"></param>
void RemoveScriptFiles(ImmutableArray<ILuaResourceInfo> luaResource);
/// <summary>
/// Executes loaded script files on the management service.
/// </summary>
/// <param name="pauseExecutionOnScriptError"></param>
/// <param name="verboseLogging"></param>
/// <returns></returns>
FluentResults.Result ExecuteScripts(bool pauseExecutionOnScriptError = false, bool verboseLogging = false);
ImmutableArray<ILuaResourceInfo> GetScriptResources();
#endregion
}
public interface ILuaScriptManagementService : IReusableService
{
#region Script_File_Execution
FluentResults.Result ExecuteLoadedScripts(ContentPackage package, bool pauseExecutionOnError = false, bool verboseLogging = false);
FluentResults.Result ExecuteLoadedScripts(ImmutableArray<ILuaResourceInfo> scripts, bool pauseExecutionOnError = false, bool verboseLogging = false);
FluentResults.Result ExecuteLoadedScripts(bool pauseExecutionOnError = false, bool verboseLogging = false);
#endregion
#region Type_Registration
IUserDataDescriptor RegisterType(Type type);
IUserDataDescriptor RegisterType(string typeName);
IUserDataDescriptor RegisterGenericType(Type type);
IUserDataDescriptor RegisterGenericType(string typeName, params string[] typeNameArgs);
void UnregisterType(Type type);
void UnregisterType(string typeName);
void UnregisterAllTypes();
#endregion
#region Type_Checks_&Utilities
bool IsRegistered(Type type);
bool IsTargetType(object obj, string typeName);
string TypeOf(object obj);
object CreateStatic(string typeName);
object CreateEnumTable(string typeName);
FieldInfo FindFieldRecursively(Type type, string fieldName);
void MakeFieldAccessible(IUserDataDescriptor descriptor, string fieldName);
MethodInfo FindMethodRecursively(Type type, string methodName, Type[] types = null);
void MakeMethodAccessible(IUserDataDescriptor descriptor, string methodName, string[] parameters = null);
PropertyInfo FindPropertyRecursively(Type type, string propertyName);
void MakePropertyAccessible(IUserDataDescriptor descriptor, string propertyName);
void AddMethod(IUserDataDescriptor descriptor, string methodName, object function);
void AddField(IUserDataDescriptor descriptor, string fieldName, DynValue value);
void RemoveMember(IUserDataDescriptor descriptor, string memberName);
bool HasMember(object obj, string memberName);
DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor descriptor);
DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType);
#endregion
}
@@ -3,89 +3,61 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Threading.Tasks;
using Barotrauma.Extensions;
using Barotrauma.LuaCs.Data;
namespace Barotrauma.LuaCs.Services;
public interface IPackageManagementService : IReusableService
public interface IPackageManagementService : IReusableService, ILocalizationsResourcesInfo, IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo, IAssembliesResourcesInfo
#if CLIENT
,IStylesResourcesInfo
#endif
{
/// <summary>
/// Adds packages to the queue of loadable packages without initializing them.
/// Loads and parses the provided <see cref="ContentPackage"/> for <see cref="IResourceInfo"/> supported by the current runtime environment.
/// </summary>
/// <param name="packages"></param>
void QueuePackages(ImmutableArray<LoadablePackage> packages);
/// <returns></returns>
Task<FluentResults.Result> LoadPackageInfosAsync(ContentPackage packages);
/// <summary>
/// Loads and parses the provided <see cref="ContentPackage"/> collection for <see cref="IResourceInfo"/> supported by the current runtime environment.
/// </summary>
/// <param name="packages"></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();
/// <summary>
/// Generates the ModConfigInfo for all queued packages and adds them to the store.
/// </summary>
/// <param name="loadParallel">Use multithreaded loading.</param>
/// <param name="reportFailOnDuplicates">Whether duplicate packages should be reported as errors.</param>
/// <returns>Failure/Success records for each package.</returns>
FluentResults.Result ParseQueuedPackages(bool loadParallel = true, bool reportFailOnDuplicates = false);
/// <summary>
/// Loads only the localizations, configs, and config profiles for stored packages.
/// </summary>
/// <param name="loadParallel"></param>
/// <returns></returns>
FluentResults.Result LoadPackageConfigsResourcesGroup(bool loadParallel = true);
/// <summary>
/// Loads all resources for stored packages.
/// </summary>
/// <param name="loadParallel">Use multithreaded loading.</param>
/// <param name="safeResourcesOnly">Only load safe scripting resources, such as Lua. C# plugins disabled.</param>
/// <returns></returns>
FluentResults.Result LoadAllPackageResources(bool loadParallel = true, bool safeResourcesOnly = true);
FluentResults.Result UnloadPackages();
bool IsPackageLoaded(ContentPackage package);
bool CheckDependencyLoaded(IPackageDependencyInfo info);
bool CheckDependenciesLoaded([NotNull]IEnumerable<IPackageDependencyInfo> infos, out ImmutableArray<IPackageDependencyInfo> missingPackages);
bool CheckEnvironmentSupported(IPlatformInfo platform);
/// <summary>
/// Tries to get the package dependency record to refer to that specific package if it exists, optionally create it.
/// </summary>
/// <param name="package">ContentPackage reference</param>
/// <param name="addIfMissing">Register a new IPackageDependencyInfo reference.</param>
/// <returns></returns>
FluentResults.Result<IPackageDependencyInfo> GetPackageDependencyInfoRecord(ContentPackage package,
bool addIfMissing = false);
/// <summary>
/// Tries to get the package dependency record to refer to that specific package if it exists, optionally create it.
/// </summary>
/// <param name="steamWorkshopId">The Steam Workshop ID, if available, if not enter zero ('0').</param>
/// <param name="packageName">The name of the package.</param>
/// <param name="folderPath">The folder path, as formatted in [ContentPackage.Path].</param>
/// <param name="addIfMissing">Register a new IPackageDependencyInfo reference.</param>
/// <returns></returns>
FluentResults.Result<IPackageDependencyInfo> GetPackageDependencyInfoRecord(ulong steamWorkshopId,
string packageName, string folderPath = null, bool addIfMissing = false);
/// <summary>
/// Tries to get the package dependency record to refer to that specific package if it exists.
/// Note: This overload does not allow the registration of a new dependency.
/// </summary>
/// <param name="folderPath">The folder path, as formatted in [ContentPackage.Path].</param>
/// <returns></returns>
FluentResults.Result<IPackageDependencyInfo> GetPackageDependencyInfoRecord(string folderPath);
IPackageDependencyInfo CreateOrphanPackageDependencyInfoRecord(string packageName,
string packagePath, ulong steamWorkshopId);
}
public readonly record struct LoadablePackage
{
public ContentPackage Package { get; }
public bool IsEnabled { get; }
public LoadablePackage(ContentPackage package, bool isEnabled)
{
Package = package;
IsEnabled = isEnabled;
}
// single
FluentResults.Result<IAssembliesResourcesInfo> GetAssembliesInfos(ContentPackage package, bool onlySupportedResources = true);
FluentResults.Result<IConfigsResourcesInfo> GetConfigsInfos(ContentPackage package, bool onlySupportedResources = true);
FluentResults.Result<IConfigProfilesResourcesInfo> GetConfigProfilesInfos(ContentPackage package, bool onlySupportedResources = true);
FluentResults.Result<ILocalizationsResourcesInfo> GetLocalizationsInfos(ContentPackage package, bool onlySupportedResources = true);
FluentResults.Result<ILuaScriptsResourcesInfo> GetLuaScriptsInfos(ContentPackage package, bool onlySupportedResources = true);
#if CLIENT
FluentResults.Result<IStylesResourcesInfo> GetStylesInfos(ContentPackage package, bool onlySupportedResources = true);
#endif
// collection
FluentResults.Result<IAssembliesResourcesInfo> GetAssembliesInfos(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true);
FluentResults.Result<IConfigsResourcesInfo> GetConfigsInfos(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true);
FluentResults.Result<IConfigProfilesResourcesInfo> GetConfigProfilesInfos(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true);
FluentResults.Result<ILocalizationsResourcesInfo> GetLocalizationsInfos(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true);
FluentResults.Result<ILuaScriptsResourcesInfo> GetLuaScriptsInfos(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true);
#if CLIENT
FluentResults.Result<IStylesResourcesInfo> GetStylesInfos(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true);
#endif
Task<FluentResults.Result<IAssembliesResourcesInfo>> GetAssembliesInfosAsync(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true);
Task<FluentResults.Result<IConfigsResourcesInfo>> GetConfigsInfosAsync(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true);
Task<FluentResults.Result<IConfigProfilesResourcesInfo>> GetConfigProfilesInfosAsync(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true);
Task<FluentResults.Result<ILocalizationsResourcesInfo>> GetLocalizationsInfosAsync(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true);
Task<FluentResults.Result<ILuaScriptsResourcesInfo>> GetLuaScriptsInfosAsync(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true);
#if CLIENT
Task<FluentResults.Result<IStylesResourcesInfo>> GetStylesInfosAsync(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true);
#endif
public static ImmutableArray<LoadablePackage> FromEnumerable(IEnumerable<ContentPackage> packages, bool isEnabled)
{
var builder = ImmutableArray.CreateBuilder<LoadablePackage>();
packages.ForEach(p => builder.Add(new LoadablePackage(p, isEnabled)));
return builder.ToImmutable();
}
}
@@ -1,38 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using Barotrauma.LuaCs.Data;
using FluentResults;
namespace Barotrauma.LuaCs.Services;
public interface IPackageService : IReusableService,
// These allow us the pass the IContentPackageService to anything that needs the data without having to directly reference the member
IResourceCultureInfo, IAssembliesResourcesInfo, ILocalizationsResourcesInfo, ILuaScriptsResourcesInfo
{
ContentPackage Package { get; }
IModConfigInfo ModConfigInfo { get; }
bool IsEnabledInModList { get; }
/// <summary>
/// Try to load the XML config and resources information from the given package.
/// </summary>
/// <param name="package"></param>
/// <returns>Whether the package was parsed without errors.</returns>
FluentResults.Result LoadResourcesInfo([NotNull]LoadablePackage package);
/// <summary>
/// Tries to load all assemblies and instance plugins for the given resources list, regardless whether they're marked as optional and/or lazy load.
/// Will sort by load priority unless overriden/bypassed.
/// </summary>
/// <param name="assembliesInfo"></param>
/// <param name="ignoreDependencySorting"></param>
/// <returns>Whether loading is successful. Returns true on an empty list.</returns>
FluentResults.Result LoadPlugins([NotNull]IAssembliesResourcesInfo assembliesInfo, bool ignoreDependencySorting = false);
FluentResults.Result LoadLocalizations([NotNull]ILocalizationsResourcesInfo localizationsInfo);
FluentResults.Result AddLuaScripts([NotNull]ILuaScriptsResourcesInfo luaScriptsInfo);
#if CLIENT
FluentResults.Result LoadStyles([NotNull]IStylesResourcesInfo stylesInfo);
#endif
FluentResults.Result LoadConfig([NotNull]IConfigsResourcesInfo configsResourcesInfo, [NotNull]IConfigProfilesResourcesInfo configProfilesResourcesInfo);
}
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Reflection;
using Barotrauma.LuaCs.Data;
@@ -31,16 +32,35 @@ public interface IPluginManagementService : IReusableService
bool includeDefaultContext = true);
/// <summary>
/// Tries to get the
/// Tries to get the Type given the fully qualified name.
/// </summary>
/// <param name="typeName"></param>
/// <returns></returns>
Type GetType(string typeName);
/// <summary>
///
/// Loads the provided assembly resources in the order of their dependencies and intra-mod priority load order.
/// </summary>
/// <param name="resource"></param>
/// <returns>Success/Failure and list of failed resources, if any.</returns>
FluentResults.Result<ImmutableArray<IAssemblyResourceInfo>> LoadAssemblyResources(ImmutableArray<IAssemblyResourceInfo> resource);
/// <summary>
/// Creates instances of the given type and provides Property Injection and instance reference caching. Disposes of
/// all references that throw errors on
/// </summary>
/// <param name="types">List of Types</param>
/// <param name="serviceInjection"></param>
/// <param name="hostInstanceReference"></param>
/// <returns></returns>
IReadOnlyList<FluentResults.Result<(Type, T)>> ActivateTypeInstances<T>(ImmutableArray<Type> types, bool serviceInjection = true,
bool hostInstanceReference = false) where T : IDisposable;
FluentResults.Result UnloadHostedReferences();
/// <summary>
/// Tries to gracefully unload all hosted plugin references
/// </summary>
/// <returns></returns>
FluentResults.Result UnloadAllAssemblyResources();
}