- 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:
@@ -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
-449
@@ -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();
|
||||
|
||||
+7
-36
@@ -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; }
|
||||
|
||||
|
||||
+9
-16
@@ -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;
|
||||
}
|
||||
|
||||
+3
-1
@@ -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.
|
||||
|
||||
+88
@@ -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
|
||||
}
|
||||
-85
@@ -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
|
||||
}
|
||||
+46
-74
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
-38
@@ -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);
|
||||
}
|
||||
|
||||
+22
-2
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user