- 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
@@ -9,6 +9,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.IO;
using Barotrauma.LuaCs.Events;
using Barotrauma.Steam;
using Microsoft.Xna.Framework;
@@ -48,7 +49,12 @@ namespace Barotrauma
public static ImmutableArray<RegularPackage>? Regular;
}
public static void SetCore(CorePackage newCore) => SetCoreEnumerable(newCore).Consume();
public static void SetCore(CorePackage newCore)
{
SetCoreEnumerable(newCore).Consume();
GameMain.LuaCs.EventService.PublishEvent<IEventEnabledPackageListChanged>(
sub => sub.OnEnabledPackageListChanged(Core, Regular));
}
public static IEnumerable<LoadProgress> SetCoreEnumerable(CorePackage newCore)
{
@@ -85,7 +91,11 @@ namespace Barotrauma
}
public static void SetRegular(IReadOnlyList<RegularPackage> newRegular)
=> SetRegularEnumerable(newRegular).Consume();
{
SetRegularEnumerable(newRegular).Consume();
GameMain.LuaCs.EventService.PublishEvent<IEventEnabledPackageListChanged>(
sub => sub.OnEnabledPackageListChanged(Core, Regular));
}
public static IEnumerable<LoadProgress> SetRegularEnumerable(IReadOnlyList<RegularPackage> inNewRegular)
{
@@ -327,6 +337,12 @@ namespace Barotrauma
Debug.WriteLine($"Loaded \"{newPackage.Name}\"");
}
GameMain.LuaCs.EventService.PublishEvent<IEventAllPackageListChanged>(sub =>
sub.OnAllPackageListChanged(corePackages
.Select((ContentPackage p) => p)
.Union(regularPackages.Select((ContentPackage p) => p))
.ToImmutableArray()));
}
private readonly string directory;
@@ -566,6 +582,12 @@ namespace Barotrauma
yield return p.Transform(loadingRange);
}
GameMain.LuaCs.EventService.PublishEvent<IEventAllPackageListChanged>(
sub => sub.OnAllPackageListChanged(CorePackages, RegularPackages));
GameMain.LuaCs.EventService.PublishEvent<IEventEnabledPackageListChanged>(
sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular));
yield return LoadProgress.Progress(1.0f);
}
@@ -597,4 +619,4 @@ namespace Barotrauma
}
}
}
}
}
@@ -9,6 +9,4 @@ public interface IConfigEntry<T> : IConfigBase, INetVar where T : IConvertible,
bool TrySetValue(T value);
bool IsAssignable(T value);
void Initialize(IVarId id, T defaultValue);
}
@@ -19,7 +19,7 @@ public partial record ModConfigInfo : IModConfigInfo
public ImmutableArray<CultureInfo> SupportedCultures { get; init; }
public ImmutableArray<IAssemblyResourceInfo> Assemblies { get; init; }
public ImmutableArray<ILocalizationResourceInfo> Localizations { get; init; }
public ImmutableArray<ILuaResourceInfo> LuaScripts { get; init; }
public ImmutableArray<ILuaScriptResourceInfo> LuaScripts { get; init; }
public ImmutableArray<IConfigResourceInfo> Configs { get; init; }
public ImmutableArray<IConfigProfileResourceInfo> ConfigProfiles { get; init; }
}
@@ -160,7 +160,7 @@ public record LocalizationResourceInfo : ILocalizationResourceInfo
public bool Optional { get; init; }
}
public readonly struct LuaScriptResourceInfo : ILuaResourceInfo
public readonly struct LuaScriptScriptResourceInfo : ILuaScriptResourceInfo
{
public ContentPackage OwnerPackage { get; init; }
public string FallbackPackageName { get; init; }
@@ -10,7 +10,7 @@ public interface ILocalizationResourceInfo : IResourceInfo, IResourceCultureInfo
/// <summary>
/// Represents loadable Lua files.
/// </summary>
public interface ILuaResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo { }
public interface ILuaScriptResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo { }
public interface IAssemblyResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo
{
/// <summary>
@@ -39,7 +39,7 @@ public interface ILocalizationsResourcesInfo
public interface ILuaScriptsResourcesInfo
{
ImmutableArray<ILuaResourceInfo> LuaScripts { get; }
ImmutableArray<ILuaScriptResourceInfo> LuaScripts { get; }
}
public interface IConfigsResourcesInfo
@@ -29,6 +29,35 @@ public interface IEvent<out T> : IEvent where T : IEvent<T>
}
}
#region RuntimeEvents
/// <summary>
/// Called when the current <see cref="Screen"/> (game state) changes. Upstream Type 'Screen' is internal.
/// </summary>
internal interface IEventScreenSelected : IEvent<IEventScreenSelected>
{
void OnScreenSelected(Screen screen);
}
/// <summary>
/// Called whenever the list of all <see cref="ContentPackage"/> (enabled and disabled) on disk has changed.
/// </summary>
internal interface IEventAllPackageListChanged : IEvent<IEventAllPackageListChanged>
{
void OnAllPackageListChanged(IEnumerable<CorePackage> corePackages, IEnumerable<RegularPackage> regularPackages);
}
/// <summary>
/// Called whenever the list of enabled <see cref="ContentPackage"/> has changed.
/// </summary>
internal interface IEventEnabledPackageListChanged : IEvent<IEventEnabledPackageListChanged>
{
void OnEnabledPackageListChanged(CorePackage package, IEnumerable<RegularPackage> regularPackages);
}
#endregion
#region GameEvents
/// <summary>
@@ -62,11 +91,11 @@ public interface IEventRoundStarted : IEvent<IEventRoundStarted>
/// </summary>
public interface IEventUpdate : IEvent<IEventUpdate>
{
void OnUpdate(float fixedDeltaTime);
void OnUpdate(double fixedDeltaTime);
static IEventUpdate IEvent<IEventUpdate>.GetLuaRunner(IDictionary<string, LuaCsFunc> luaFunc) => new
{
IsLuaRunner = Return<bool>.Arguments(() => true),
OnUpdate = ReturnVoid.Arguments<float>((fixedDeltaTime) => luaFunc[nameof(OnUpdate)](fixedDeltaTime))
OnUpdate = ReturnVoid.Arguments<double>((fixedDeltaTime) => luaFunc[nameof(OnUpdate)](fixedDeltaTime))
}.ActLike<IEventUpdate>();
}
@@ -75,11 +104,11 @@ public interface IEventUpdate : IEvent<IEventUpdate>
/// </summary>
public interface IEventDrawUpdate : IEvent<IEventDrawUpdate>
{
void OnDrawUpdate(float deltaTime);
void OnDrawUpdate(double deltaTime);
static IEventDrawUpdate IEvent<IEventDrawUpdate>.GetLuaRunner(IDictionary<string, LuaCsFunc> luaFunc) => new
{
IsLuaRunner = Return<bool>.Arguments(() => true),
OnDrawUpdate = ReturnVoid.Arguments<float>((deltaTime) => luaFunc[nameof(OnDrawUpdate)](deltaTime))
OnDrawUpdate = ReturnVoid.Arguments<double>((deltaTime) => luaFunc[nameof(OnDrawUpdate)](deltaTime))
}.ActLike<IEventDrawUpdate>();
}
@@ -1,130 +1,77 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Barotrauma.LuaCs.Configuration;
using Barotrauma.LuaCs.Data;
using Barotrauma.LuaCs.Events;
using Barotrauma.LuaCs.Services;
using Barotrauma.LuaCs.Services.Compatibility;
using Barotrauma.LuaCs.Services.Processing;
using Barotrauma.Networking;
using FluentResults;
using ImpromptuInterface;
namespace Barotrauma
{
class LuaCsSetupConfig
{
public bool EnableCsScripting = false;
public bool TreatForcedModsAsNormal = true;
public bool PreferToUseWorkshopLuaSetup = false;
public bool DisableErrorGUIOverlay = false;
public bool HideUserNames
{
get { return LuaCsLogger.HideUserNames; }
set { LuaCsLogger.HideUserNames = value; }
}
public LuaCsSetupConfig() { }
public LuaCsSetupConfig(LuaCsSetupConfig config)
{
EnableCsScripting = config.EnableCsScripting;
TreatForcedModsAsNormal = config.TreatForcedModsAsNormal;
PreferToUseWorkshopLuaSetup = config.PreferToUseWorkshopLuaSetup;
DisableErrorGUIOverlay = config.DisableErrorGUIOverlay;
}
}
internal delegate void LuaCsMessageLogger(string message);
internal delegate void LuaCsErrorHandler(Exception ex, LuaCsMessageOrigin origin);
internal delegate void LuaCsExceptionHandler(Exception ex, LuaCsMessageOrigin origin);
partial class LuaCsSetup : IDisposable
partial class LuaCsSetup : IDisposable, IEventScreenSelected, IEventAllPackageListChanged, IEventEnabledPackageListChanged
{
public LuaCsSetup()
{
// load services
// == startup
_servicesProvider = new ServicesProvider();
RegisterServices();
// load manifest
if (!_servicesProvider.TryGetService(out IModConfigCreatorService modConfigSvc))
throw new NullReferenceException("LuaCsSetup: Failed to get mod config parser service!"); // we should crash here
var luaConfig = modConfigSvc.BuildConfigFromManifest(Directory.GetCurrentDirectory() + LuaCsConfigFile);
if (!luaConfig.IsSuccess)
{
Logger.LogResults(luaConfig.ToResult());
throw new FileLoadException("LuaCsSetup: Failed to load config file!");
}
// load resources
RegisterLocalizations();
RegisterConfigs();
LuaForBarotraumaId = new SteamWorkshopId(LuaForBarotraumaSteamId.Value);
ValidateLuaCsContent();
SubscribeToLuaCsEvents();
return;
//---
// == end
// == helpers
void RegisterServices()
{
_servicesProvider.RegisterServiceType<ILoggerService, LoggerService>(ServiceLifetime.Singleton);
_servicesProvider.RegisterServiceType<PerformanceCounterService, PerformanceCounterService>(ServiceLifetime.Singleton);
_servicesProvider.RegisterServiceType<IPackageService, PackageService>(ServiceLifetime.Singleton);
_servicesProvider.RegisterServiceType<IStorageService, StorageService>(ServiceLifetime.Transient);
_servicesProvider.RegisterServiceType<IEventService, EventService>(ServiceLifetime.Singleton);
#if CLIENT
_servicesProvider.RegisterServiceType<IStylesService, StylesService>(ServiceLifetime.Transient);
_servicesProvider.RegisterServiceType<IStylesManagementService, StylesManagementService>(ServiceLifetime.Singleton);
#endif
_servicesProvider.RegisterServiceType<IPackageManagementService, PackageManagementService>(ServiceLifetime.Singleton);
_servicesProvider.RegisterServiceType<ILuaScriptService, LuaScriptService>(ServiceLifetime.Singleton);
_servicesProvider.RegisterServiceType<ILuaScriptManagementService, LuaScriptService>(ServiceLifetime.Singleton);
_servicesProvider.RegisterServiceType<IPluginManagementService, PluginManagementService>(ServiceLifetime.Singleton);
_servicesProvider.RegisterServiceType<ILuaScriptManagementService, LuaScriptManagementService>(ServiceLifetime.Singleton);
// TODO: ILocalizationService
// TODO: IConfigService
// TODO: INetworkingService
// TODO: [Resource Converter/Parser Services]
// TODO: ILocalizationService
// TODO: IEventService
_servicesProvider.Compile();
}
void RegisterLocalizations()
// Validates LuaCs assets in /Content are valid and ready to use.
void ValidateLuaCsContent()
{
LocalizationService.LoadLocalizations(luaConfig.Value.Localizations);
throw new NotImplementedException();
}
void RegisterConfigs()
{
if (ConfigService.AddConfigs(luaConfig.Value.Configs) is { IsSuccess: false } res1)
{
Logger.LogResults(res1);
throw new Exception("LuaCsSetup: Failed to load config!");
}
if (ConfigService.AddConfigsProfiles(luaConfig.Value.ConfigProfiles) is { IsSuccess: false } res2)
{
Logger.LogResults(res2);
throw new Exception("LuaCsSetup: Failed to load config profiles!");
}
IsCsEnabled = GetOrThrowForConfig<bool>(luaConfig.Value.PackageName, "IsCsEnabled");
TreatForcedModsAsNormal = GetOrThrowForConfig<bool>(luaConfig.Value.PackageName, "TreatForcedModsAsNormal");
PreferToUseWorkshopLuaSetup = GetOrThrowForConfig<bool>(luaConfig.Value.PackageName, "PreferToUseWorkshopLuaSetup");
DisableErrorGUIOverlay = GetOrThrowForConfig<bool>(luaConfig.Value.PackageName, "DisableErrorGUIOverlay");
EnableThreadedLoading = GetOrThrowForConfig<bool>(luaConfig.Value.PackageName, "EnableThreadedLoading");
HideUserNamesInLogs = GetOrThrowForConfig<bool>(luaConfig.Value.PackageName, "HideUserNamesInLogs");
LuaForBarotraumaSteamId = GetOrThrowForConfig<ulong>(luaConfig.Value.PackageName, "LuaForBarotraumaSteamId");
return;
//---
IConfigEntry<T> GetOrThrowForConfig<T>(string packName, string internalName) where T : IConvertible, IEquatable<T>
{
var cfgRes = ConfigService.GetConfig<IConfigEntry<T>>(packName, internalName);
if (cfgRes.IsSuccess)
{
return cfgRes.Value;
}
Logger.LogResults(cfgRes.ToResult());
throw new Exception($"LuaCsSetup: Failed to load config for {internalName}!");
}
}
}
void SubscribeToLuaCsEvents()
{
EventService.Subscribe<IEventScreenSelected>(this); // game state hook in
EventService.Subscribe<IEventAllPackageListChanged>(this);
EventService.Subscribe<IEventEnabledPackageListChanged>(this);
}
#region CONST_DEF
public const string LuaCsConfigFile = "LuaCsConfig.xml";
#if SERVER
public const bool IsServer = true;
@@ -154,7 +101,7 @@ namespace Barotrauma
? svc : throw new NullReferenceException("Package Manager service not found!");
public IPluginManagementService PluginManagementService => _servicesProvider.TryGetService<IPluginManagementService>(out var svc)
? svc : throw new NullReferenceException("Plugin Manager service not found!");
public ILuaScriptManagementService LuaScriptService => _servicesProvider.TryGetService<ILuaScriptManagementService>(out var svc)
public ILuaScriptManagementService LuaScriptManagementService => _servicesProvider.TryGetService<ILuaScriptManagementService>(out var svc)
? svc : throw new NullReferenceException("Lua Script Manager service not found!");
public ILocalizationService LocalizationService => _servicesProvider.TryGetService<ILocalizationService>(out var svc)
? svc : throw new NullReferenceException("Localization Manager service not found!");
@@ -187,17 +134,42 @@ namespace Barotrauma
/// </summary>
public IConfigEntry<bool> DisableErrorGUIOverlay { get; private set; }
/// <summary>
/// [Experimental] Whether multithreading should be used for loading.
/// </summary>
public IConfigEntry<bool> EnableThreadedLoading { get; private set; }
/// <summary>
/// Whether usernames are anonymized or show in logs.
/// </summary>
public IConfigEntry<bool> HideUserNamesInLogs { get; private set; }
private IConfigEntry<ulong> LuaForBarotraumaSteamId { get; set; }
/// <summary>
/// The SteamId of the Workshop LuaCs CPackage in use, if available.
/// </summary>
public IConfigEntry<ulong> LuaForBarotraumaSteamId { get; private set; }
/// <summary>
/// The SteamId of the Workshop LuaCs CsForBarotrauma add-on, if available.
/// </summary>
public IConfigEntry<ulong> CsForBarotraumaSteamId { get; private set; }
/// <summary>
/// Whether to (re)load all package assets when a lobby starts/code session begins.
/// Intended for development use, or when packages are expected to change outside of External Updates (ie. Steam Workshop).
/// </summary>
public IConfigEntry<bool> ReloadPackagesOnLobbyStart { get; private set; }
/**
* == Ops Vars
*/
private RunState _runState;
/// <summary>
/// The current run state of all services managed by LuaCs.
/// </summary>
public RunState CurrentRunState => _runState;
private bool CPacksParsed => CurrentRunState >= RunState.Parsed;
private bool IsStaticAssetsLoaded => CurrentRunState >= RunState.Configuration;
private bool IsCodeRunning => CurrentRunState >= RunState.Running;
private readonly ConcurrentQueue<ContentPackage> _toLoad = new();
private readonly ConcurrentQueue<ContentPackage> _toUnload = new();
#endregion
@@ -208,13 +180,6 @@ namespace Barotrauma
#endregion
/// <summary>
/// Whether mod content is loaded and being executed.
/// </summary>
public bool IsModContentRunning { get; private set; }
public readonly ContentPackageId LuaForBarotraumaId;
public static bool IsRunningInsideWorkshop
{
get
@@ -227,84 +192,16 @@ namespace Barotrauma
}
}
/*public Script Lua { get; private set; }
public LuaScriptLoader LuaScriptLoader { get; private set; }
public LuaGame Game { get; private set; }
public LuaCsHook Hook { get; private set; }
public LuaCsTimer Timer { get; private set; }
public LuaCsNetworking Networking { get; private set; }
public LuaCsSteam Steam { get; private set; }
// must be available at anytime
private static AssemblyManager _assemblyManager;
public static AssemblyManager AssemblyManager => _assemblyManager ??= new AssemblyManager();
private CsPackageManager _pluginPackageManager;
public CsPackageManager PluginPackageManager => _pluginPackageManager ??= new CsPackageManager(AssemblyManager, this);
private LuaRequire Require { get; set; }
public LuaCsSetupConfig Config { get; private set; }
public MoonSharpVsCodeDebugServer DebugServer { get; private set; }
public bool IsInitialized { get; private set; }*/
private bool ShouldRunCs
{
get
{
#if SERVER
if (GetPackage(CsForBarotraumaId, false, false) != null && GameMain.Server.ServerPeer is LidgrenServerPeer) { return true; }
#endif
return IsCsEnabled.Value;
}
}
private partial bool ShouldRunCs();
[Obsolete("Use AssemblyManager::GetTypesByName()")]
// TODO: Rework
[Obsolete("Use IPluginManagementService::GetTypesByName()")]
public static Type GetType(string typeName, bool throwOnError = false, bool ignoreCase = false)
{
throw new NotImplementedException();
//return AssemblyManager.GetTypesByName(typeName).FirstOrDefault((Type)null);
}
// Old config ref
/*public void ReadSettings()
{
Config = new LuaCsSetupConfig();
if (File.Exists(configFileName))
{
try
{
using (var file = File.Open(configFileName, FileMode.Open, FileAccess.Read))
{
XDocument document = XDocument.Load(file);
Config.EnableCsScripting = document.Root.GetAttributeBool("EnableCsScripting", Config.EnableCsScripting);
Config.TreatForcedModsAsNormal = document.Root.GetAttributeBool("TreatForcedModsAsNormal", Config.TreatForcedModsAsNormal);
Config.PreferToUseWorkshopLuaSetup = document.Root.GetAttributeBool("PreferToUseWorkshopLuaSetup", Config.PreferToUseWorkshopLuaSetup);
Config.DisableErrorGUIOverlay = document.Root.GetAttributeBool("DisableErrorGUIOverlay", Config.DisableErrorGUIOverlay);
Config.HideUserNames = document.Root.GetAttributeBool("HideUserNames", Config.HideUserNames);
}
}
catch (Exception e)
{
LuaCsLogger.HandleException(e, LuaCsMessageOrigin.LuaCs);
}
}
}
public void WriteSettings()
{
XDocument document = new XDocument();
document.Add(new XElement("LuaCsSetupConfig"));
document.Root.SetAttributeValue("EnableCsScripting", Config.EnableCsScripting);
document.Root.SetAttributeValue("EnableCsScripting", Config.EnableCsScripting);
document.Root.SetAttributeValue("TreatForcedModsAsNormal", Config.TreatForcedModsAsNormal);
document.Root.SetAttributeValue("PreferToUseWorkshopLuaSetup", Config.PreferToUseWorkshopLuaSetup);
document.Root.SetAttributeValue("DisableErrorGUIOverlay", Config.DisableErrorGUIOverlay);
document.Root.SetAttributeValue("HideUserNames", Config.HideUserNames);
document.Save(configFileName);
}*/
public static ContentPackage GetPackage(ContentPackageId id, bool fallbackToAll = true, bool useBackup = false)
{
@@ -349,111 +246,495 @@ namespace Barotrauma
return null;
}
// Old code ref
/*private DynValue DoFile(string file, Table globalContext = null, string codeStringFriendly = null)
{
if (!LuaCsFile.CanReadFromPath(file))
{
throw new ScriptRuntimeException($"dofile: File access to {file} not allowed.");
}
if (!LuaCsFile.Exists(file))
{
throw new ScriptRuntimeException($"dofile: File {file} not found.");
}
return Lua.DoFile(file, globalContext, codeStringFriendly);
}
private DynValue LoadFile(string file, Table globalContext = null, string codeStringFriendly = null)
{
if (!LuaCsFile.CanReadFromPath(file))
{
throw new ScriptRuntimeException($"loadfile: File access to {file} not allowed.");
}
if (!LuaCsFile.Exists(file))
{
throw new ScriptRuntimeException($"loadfile: File {file} not found.");
}
return Lua.LoadFile(file, globalContext, codeStringFriendly);
}
public DynValue CallLuaFunction(object function, params object[] args)
{
// XXX: `lua` might be null if `LuaCsSetup.Stop()` is called while
// a patched function is still running.
if (Lua == null) { return null; }
lock (Lua)
{
try
{
return Lua.Call(function, args);
}
catch (Exception e)
{
LuaCsLogger.HandleException(e, LuaCsMessageOrigin.LuaMod);
}
return null;
}
}
private void SetModulePaths(string[] str)
{
LuaScriptLoader.ModulePaths = str;
}
public void Update()
{
Timer?.Update();
Steam?.Update();
#if CLIENT
Stopwatch luaSw = new Stopwatch();
luaSw.Start();
#endif
Hook?.Call("think");
#if CLIENT
luaSw.Stop();
GameMain.PerformanceCounter.AddElapsedTicks("Think Hook", luaSw.ElapsedTicks);
#endif
}
*/
public void Stop()
{
IsInitialized = false;
}
public void Initialize(bool forceEnableCs = false)
{
if (IsInitialized)
{
Stop();
}
IsInitialized = true;
Logger.Log($"Initializing LuaCs, git revision = {AssemblyInfo.GitRevision}");
}
public void Update()
{
throw new NotImplementedException();
}
public void Reset()
{
throw new NotImplementedException();
}
public void Dispose()
{
// TODO release managed resources here
try
{
SetRunState(RunState.Unloaded);
}
catch (Exception e)
{
Logger.LogError(e.Message);
}
try
{
DisposeLuaCsConfig();
PluginManagementService.Dispose();
LuaScriptManagementService.Dispose();
#if CLIENT
StylesManagementService.Dispose();
#endif
ConfigService.Dispose();
LocalizationService.Dispose();
PackageManagementService.Dispose();
// TODO: Add all missing services.
//NetworkingService.Dispose();
EventService.Dispose();
_servicesProvider.DisposeAndReset();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
GC.SuppressFinalize(this);
}
/// <summary>
/// Handles changes in game states tracked by screen changes.
/// </summary>
/// <param name="screen">The new game screen.</param>
public partial void OnScreenSelected(Screen screen);
public void OnAllPackageListChanged(IEnumerable<CorePackage> corePackages, IEnumerable<RegularPackage> regularPackages)
{
UpdateLoadedPackagesList();
}
public void OnEnabledPackageListChanged(CorePackage corePackage, IEnumerable<RegularPackage> regularPackages)
{
UpdateLoadedPackagesList();
}
private void UpdateLoadedPackagesList()
{
var newPackSet = ContentPackageManager.AllPackages
.Union(ContentPackageManager.EnabledPackages.All)
.ToHashSet();
var currPackSet = PackageManagementService.GetAllLoadedPackages().ToHashSet();
var toAdd = newPackSet.Except(currPackSet);
var toRemove = currPackSet.Except(newPackSet);
foreach (var package in toAdd)
_toLoad.Enqueue(package);
foreach (var package in toRemove)
_toUnload.Enqueue(package);
ProcessPackagesListDifferences();
}
void ProcessPackagesListDifferences()
{
if (IsCodeRunning)
return;
// no reason to do anything if we're fully unloaded.
if (!CPacksParsed)
{
_toLoad.Clear();
_toUnload.Clear();
}
while (_toUnload.TryDequeue(out var cp))
{
LuaScriptManagementService.DisposePackageResources(cp);
ConfigService.DisposeConfigsProfiles(cp);
ConfigService.DisposeConfigs(cp);
#if CLIENT
StylesManagementService.DisposeStylesForPackage(cp);
#endif
LocalizationService.DisposePackage(cp);
PackageManagementService.DisposePackageInfos(cp);
}
var ls = new List<ContentPackage>();
while (_toLoad.TryDequeue(out var cp))
{
if (PackageManagementService.LoadPackageInfosAsync(cp).GetAwaiter().GetResult() is
{ IsFailed: true } failure)
{
Logger.LogError($"Failed to load package infos for {cp.Name}");
Logger.LogResults(failure);
continue;
}
ls.Add(cp);
}
if (ls.Any())
{
LoadStaticAssetsAsync(ls).GetAwaiter().GetResult();
}
}
void SetRunState(RunState runState)
{
if (CurrentRunState == runState)
return;
if (runState > CurrentRunState)
{
if (CurrentRunState < RunState.Parsed)
LoadCurrentContentPackageInfos();
if (runState <= CurrentRunState)
return;
if (CurrentRunState < RunState.Configuration)
LoadStaticAssets();
if (runState <= CurrentRunState)
return;
if (CurrentRunState < RunState.Running)
RunScripts();
}
else if (runState < CurrentRunState)
{
if (CurrentRunState >= RunState.Running)
{
StopScripts();
ProcessPackagesListDifferences();
_runState = RunState.Configuration;
}
if (runState >= CurrentRunState)
return;
if (CurrentRunState == RunState.Configuration)
{
UnloadStaticAssets();
_runState = RunState.Parsed;
}
if (runState >= CurrentRunState)
return;
if (CurrentRunState == RunState.Parsed)
{
UnloadContentPackageInfos();
_runState = RunState.Unloaded;
}
// we should be unloaded completely now | RunState.Unloaded
}
}
void LoadCurrentContentPackageInfos()
{
if (CurrentRunState >= RunState.Parsed)
return;
// load core
var result1 = PackageManagementService.LoadPackageInfosAsync(ContentPackageManager.VanillaCorePackage)
.GetAwaiter().GetResult();
if (result1.IsFailed)
{
Logger.LogError($"Unable to load LuaCs CorePackage resources! Running in degraded mode.");
Logger.LogResults(result1);
}
// load regular
var list = ContentPackageManager.RegularPackages
.Union(ContentPackageManager.EnabledPackages.All)
.ToImmutableList();
LoadContentPackagesInfos(list);
if (CurrentRunState < RunState.Parsed)
_runState = RunState.Parsed;
}
void LoadContentPackagesInfos(IReadOnlyList<ContentPackage> packages)
{
var result2 = PackageManagementService.LoadPackagesInfosAsync(packages)
.GetAwaiter().GetResult();
foreach (var entry in result2)
{
if (entry.Item2.IsSuccess)
Logger.LogMessage($"Successfully parsed package: {entry.Item1.Name}");
else if (entry.Item2.IsFailed)
Logger.LogResults(entry.Item2);
}
}
void LoadStaticAssets()
{
if (CurrentRunState < RunState.Parsed)
{
throw new InvalidOperationException($"{nameof(LoadStaticAssets)} cannot load assets in the '{CurrentRunState}' state.");
}
if (CurrentRunState >= RunState.Configuration)
return;
while (_toUnload.TryDequeue(out var cp))
PackageManagementService.DisposePackageInfos(cp);
LoadStaticAssetsAsync(PackageManagementService.GetAllLoadedPackages()).GetAwaiter().GetResult();
LoadLuaCsConfig();
if (CurrentRunState < RunState.Configuration)
_runState = RunState.Configuration;
}
void LoadLuaCsConfig()
{
IsCsEnabled = ConfigService.GetConfig<IConfigEntry<bool>>(ContentPackageManager.VanillaCorePackage, "IsCsEnabled")
?? throw new NullReferenceException($"{nameof(IsCsEnabled)} cannot be loaded.");
TreatForcedModsAsNormal = ConfigService.GetConfig<IConfigEntry<bool>>(ContentPackageManager.VanillaCorePackage, "TreatForcedModsAsNormal")
?? throw new NullReferenceException($"{nameof(TreatForcedModsAsNormal)} cannot be loaded.");
DisableErrorGUIOverlay = ConfigService.GetConfig<IConfigEntry<bool>>(ContentPackageManager.VanillaCorePackage, "DisableErrorGUIOverlay")
?? throw new NullReferenceException($"{nameof(DisableErrorGUIOverlay)} cannot be loaded.");
HideUserNamesInLogs = ConfigService.GetConfig<IConfigEntry<bool>>(ContentPackageManager.VanillaCorePackage, "HideUserNamesInLogs")
?? throw new NullReferenceException($"{nameof(HideUserNamesInLogs)} cannot be loaded.");
LuaForBarotraumaSteamId = ConfigService.GetConfig<IConfigEntry<ulong>>(ContentPackageManager.VanillaCorePackage, "LuaForBarotraumaSteamId")
?? throw new NullReferenceException($"{nameof(LuaForBarotraumaSteamId)} cannot be loaded.");
CsForBarotraumaSteamId = ConfigService.GetConfig<IConfigEntry<ulong>>(ContentPackageManager.VanillaCorePackage, "CsForBarotraumaSteamId")
?? throw new NullReferenceException($"{nameof(CsForBarotraumaSteamId)} cannot be loaded.");
}
void DisposeLuaCsConfig()
{
IsCsEnabled = null;
TreatForcedModsAsNormal = null;
DisableErrorGUIOverlay = null;
HideUserNamesInLogs = null;
LuaForBarotraumaSteamId = null;
CsForBarotraumaSteamId = null;
}
async Task LoadStaticAssetsAsync(IReadOnlyList<ContentPackage> packages)
{
var locRes = ImmutableArray<ILocalizationResourceInfo>.Empty;
var cfgRes = ImmutableArray<IConfigResourceInfo>.Empty;
var cfpRes = ImmutableArray<IConfigProfileResourceInfo>.Empty;
var luaRes = ImmutableArray<ILuaScriptResourceInfo>.Empty;
#if CLIENT
var styleRes = ImmutableArray<IStylesResourceInfo>.Empty;
#endif
var tasksBuilder = ImmutableArray.CreateBuilder<Task>();
//---- get resource infos
tasksBuilder.AddRange(new Func<Task>(async () =>
{
var res = await PackageManagementService.GetLocalizationsInfosAsync(packages);
if (res.IsSuccess)
locRes = res.Value.Localizations;
if (res.Errors.Any())
ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state),
res.ToResult());
})(),
new Func<Task>(async () =>
{
var res = await PackageManagementService.GetConfigsInfosAsync(packages);
if (res.IsSuccess)
cfgRes = res.Value.Configs;
if (res.Errors.Any())
ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state),
res.ToResult());
})(),
new Func<Task>(async () =>
{
var res = await PackageManagementService.GetConfigProfilesInfosAsync(packages);
if (res.IsSuccess)
cfpRes = res.Value.ConfigProfiles;
if (res.Errors.Any())
ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state),
res.ToResult());
})(),
new Func<Task>(async () =>
{
var res = await PackageManagementService.GetLuaScriptsInfosAsync(packages);
if (res.IsSuccess)
luaRes = res.Value.LuaScripts;
if (res.Errors.Any())
ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state),
res.ToResult());
})());
#if CLIENT
tasksBuilder.Add(new Func<Task>(async () =>
{
var res = await PackageManagementService.GetStylesInfosAsync(packages);
if (res.IsSuccess)
styleRes = res.Value.StylesResourceInfos;
if (res.Errors.Any())
ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state),
res.ToResult());
})());
#endif
await Task.WhenAll(tasksBuilder.MoveToImmutable());
tasksBuilder.Clear();
//---- load resources
tasksBuilder.AddRange(new Func<Task>(async () =>
{
var res = await ConfigService.LoadConfigsAsync(cfgRes);
if (res.Errors.Any())
Logger.LogResults(res);
res = await ConfigService.LoadConfigsProfilesAsync(cfpRes);
if (res.Errors.Any())
Logger.LogResults(res);
})(),
new Func<Task>(async () =>
{
var res = await LuaScriptManagementService.LoadScriptResourcesAsync(luaRes);
if (res.Errors.Any())
Logger.LogResults(res);
})());
#if CLIENT
tasksBuilder.Add(new Func<Task>(async () =>
{
var res = await StylesManagementService.LoadStylesAsync(styleRes);
if (res.Errors.Any())
Logger.LogResults(res);
})());
#endif
// load localizations first
if (!locRes.IsDefaultOrEmpty)
{
var res = await LocalizationService.LoadLocalizations(locRes);
if (res.Errors.Any())
Logger.LogResults(res);
}
await Task.WhenAll(tasksBuilder.MoveToImmutable());
}
void RunScripts()
{
if (!IsStaticAssetsLoaded)
{
throw new InvalidOperationException($"{nameof(RunScripts)} cannot load assets in the '{CurrentRunState}' state.");
}
if (CurrentRunState >= RunState.Running)
return;
if (ShouldRunCs())
{
var asmRes =
PackageManagementService.GetAssembliesInfos(PackageManagementService
.GetAllLoadedPackages()
.Where(ContentPackageManager.EnabledPackages.All.Contains)
.ToList());
if (asmRes.IsFailed)
{
Logger.LogError($"{nameof(RunScripts)}: Errors will retrieving assembly resources, cannot load scripts!");
Logger.LogResults(asmRes.ToResult());
return;
}
var res = PluginManagementService.LoadAssemblyResources(asmRes.Value.Assemblies);
if (res.IsFailed)
{
Logger.LogError($"{nameof(RunScripts)}: Failed to initialize scripts!");
Logger.LogResults(res.ToResult());
}
else
{
if (res.Errors.Any())
Logger.LogResults(res.ToResult());
if (PluginManagementService.GetImplementingTypes<IAssemblyPlugin>() is {IsSuccess: true} types)
{
var typeInst = PluginManagementService.ActivateTypeInstances<IAssemblyPlugin>(types.Value, true, true);
foreach (var loadRes in typeInst)
{
if (loadRes is { IsSuccess: true, Value: { Item2: { } pluginInstance } })
{
EventService.Subscribe<IEventPluginPreInitialize>(pluginInstance);
EventService.Subscribe<IEventPluginInitialize>(pluginInstance);
EventService.Subscribe<IEventPluginLoadCompleted>(pluginInstance);
}
else
{
Logger.LogResults(loadRes.ToResult());
}
}
EventService.PublishEvent<IEventPluginPreInitialize>(sub => sub.PreInitPatching());
EventService.PublishEvent<IEventPluginInitialize>(sub => sub.Initialize());
EventService.PublishEvent<IEventPluginLoadCompleted>(sub => sub.OnLoadCompleted());
}
}
}
//lua
var luaRes = PackageManagementService.GetLuaScriptsInfos(PackageManagementService
.GetAllLoadedPackages()
.Where(ContentPackageManager.EnabledPackages.All.Contains)
.ToList());
if (luaRes.IsFailed)
{
Logger.LogError($"{nameof(RunScripts)}: Failed to get enabled lua script resources!");
Logger.LogResults(luaRes.ToResult());
return;
}
if (luaRes.Errors.Any())
Logger.LogResults(luaRes.ToResult());
LuaScriptManagementService.ExecuteLoadedScripts(luaRes.Value.LuaScripts);
if (CurrentRunState < RunState.Running)
_runState = RunState.Running;
}
void UnloadContentPackageInfos()
{
if (IsStaticAssetsLoaded)
{
throw new InvalidOperationException($"{nameof(UnloadStaticAssets)}: Cannot unload static assets when the current run state is {CurrentRunState}.");
}
PackageManagementService.Reset();
_toUnload.Clear();
}
void UnloadStaticAssets()
{
if (IsCodeRunning)
{
throw new InvalidOperationException($"{nameof(UnloadStaticAssets)}: Cannot unload static assets when the current run state is {CurrentRunState}.");
}
PluginManagementService.Reset();
LuaScriptManagementService.Reset();
ConfigService.Reset();
#if CLIENT
StylesManagementService.Reset();
#endif
LocalizationService.Reset();
if (CurrentRunState >= RunState.Configuration)
{
_runState = RunState.Parsed;
}
}
void StopScripts()
{
EventService.ClearAllSubscribers();
LuaScriptManagementService.UnloadActiveScripts();
PluginManagementService.UnloadAllAssemblyResources();
SubscribeToLuaCsEvents();
if (IsCodeRunning)
{
_runState = RunState.Configuration;
}
}
}
/// <summary>
/// Specifies the current run state of the LuaCs Modding System.
/// <b>[Important]Enum State values ordering must be in the form of (lower state) === (higher state)</b>
/// </summary>
public enum RunState : byte
{
Unloaded = 0, // No asset data loaded.
Parsed, // CPacks' ResourceInfos are parsed.
Configuration, // localization and configuration assets loaded.
Running // all assets loaded, code running.
}
}
@@ -60,7 +60,8 @@ namespace Barotrauma
foreach (var package in ContentPackageManager.AllPackages)
{
if (package.UgcId.ValueEquals(LuaCsSetup.LuaForBarotraumaId) && pathStartsWith(getFullPath(package.Path)))
if (package.UgcId.ValueEquals(new SteamWorkshopId(GameMain.LuaCs.LuaForBarotraumaSteamId?.Value ?? 0ul))
&& pathStartsWith(getFullPath(package.Path)))
{
return false;
}
@@ -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();
}
@@ -43,6 +43,7 @@
GUI.SettingsMenuOpen = false;
#endif
Selected = this;
GameMain.LuaCs.EventService.PublishEvent<LuaCs.Events.IEventScreenSelected>(sub => sub.OnScreenSelected(this));
}
public virtual Camera Cam => null;