- Reduced console spam when failing to load config file in release builds.

- Added console logging for failing to unload assembly load ctx.
- Removed unused StorageService instance.
- Removed unused luacs settings.
- Added plugin event service and event service dispatcher api.
This commit is contained in:
MapleWheels
2026-03-22 18:01:19 -04:00
parent e62450c763
commit ac329a70a4
11 changed files with 123 additions and 219 deletions

View File

@@ -1,82 +0,0 @@
using Microsoft.Xna.Framework;
namespace Barotrauma
{
static class LuaCsSettingsMenu
{
private static GUIFrame frame;
public static void Open(RectTransform rectTransform)
{
Close();
frame = new GUIFrame(new RectTransform(new Vector2(0.4f, 0.6f), rectTransform, Anchor.Center));
GUIListBox list = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.95f), frame.RectTransform, Anchor.Center), false);
new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), list.Content.RectTransform), "LuaCs Settings", textAlignment: Alignment.Center);
new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Enable CSharp Scripting")
{
Selected = LuaCsSetup.Instance.IsCsEnabled,
ToolTip = "This enables CSharp Scripting for mods to use, WARNING: CSharp is NOT sandboxed, be careful with what mods you download.",
OnSelected = (GUITickBox tick) =>
{
LuaCsSetup.Instance.IsCsEnabled = tick.Selected;
return true;
}
};
new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Disable Error GUI Overlay")
{
Selected = LuaCsSetup.Instance.DisableErrorGUIOverlay,
ToolTip = "",
OnSelected = (GUITickBox tick) =>
{
LuaCsSetup.Instance.DisableErrorGUIOverlay = tick.Selected;
return true;
}
};
new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Hide usernames In Error Logs")
{
Selected = LuaCsSetup.Instance.HideUserNamesInLogs,
ToolTip = "Hides the operating system username when displaying error logs (eg your username on windows).",
OnSelected = (GUITickBox tick) =>
{
LuaCsSetup.Instance.HideUserNamesInLogs = tick.Selected;
return true;
}
};
new GUIButton(new RectTransform(new Vector2(1f, 0.1f), list.Content.RectTransform), $"Remove Client-Side LuaCs", style: "GUIButtonSmall")
{
ToolTip = "Remove Client-Side LuaCs.",
OnClicked = (tb, userdata) =>
{
LuaCsInstaller.Uninstall();
return true;
}
};
new GUIButton(new RectTransform(new Vector2(0.8f, 0.01f), frame.RectTransform, Anchor.BottomCenter)
{
RelativeOffset = new Vector2(0f, 0.05f)
}, "Close")
{
OnClicked = (GUIButton button, object obj) =>
{
Close();
return true;
}
};
}
public static void Close()
{
frame?.Parent.RemoveChild(frame);
frame = null;
}
}
}

View File

@@ -1,11 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Configuration>
<Settings>
<Setting Name="DisableErrorGUIOverlay" Type="bool" Value="true"/>
<Setting Name="HideUserNamesInLogs" Type="bool" Value="true"/>
<Setting Name="LocalDataSavePath" Type="string" Value="/Data/Mods" ShowInMenus="false" ReadOnly="true"/>
<Setting Name="LuaForBarotraumaSteamId" Type="ulong" Value="2559634234" ReadOnly="true"/>
<Setting Name="IsCsEnabled" Type="bool" Value="false" AllowChangesWhileExecuting="false"/>
<Setting Name="RestrictMessageSize" Type="bool" Value="true"/>
</Settings>
</Configuration>

View File

@@ -82,7 +82,6 @@ namespace Barotrauma
private LuaGame _game;
public LuaGame Game => _game ??= _servicesProvider.GetService<LuaGame>();
internal IStorageService StorageService => _servicesProvider.GetService<IStorageService>();
/// <summary>
/// Whether C# plugin code is enabled.
@@ -95,16 +94,6 @@ namespace Barotrauma
private ISettingBase<bool> _isCsEnabled;
/// <summary>
/// Whether the popup error GUI should be hidden/suppressed.
/// </summary>
public bool DisableErrorGUIOverlay
{
get => _disableErrorGUIOverlay?.Value ?? false;
internal set => _disableErrorGUIOverlay?.TrySetValue(value);
}
private ISettingBase<bool> _disableErrorGUIOverlay;
/// <summary>
/// Whether usernames are anonymized or show in logs.
/// </summary>
@@ -115,66 +104,25 @@ namespace Barotrauma
}
private ISettingBase<bool> _hideUserNamesInLogs;
/// <summary>
/// The SteamId of the Workshop LuaCs CPackage in use, if available.
/// </summary>
public ulong LuaForBarotraumaSteamId
public static ContentPackage GetLuaCsPackage()
{
get => _luaForBarotraumaSteamId?.Value ?? 0;
internal set => _luaForBarotraumaSteamId?.TrySetValue(value);
}
private ISettingBase<ulong> _luaForBarotraumaSteamId;
/// <summary>
/// Whether the maximum message size over the network should be restricted.
/// </summary>
public bool RestrictMessageSize
{
get => _restrictMessageSize?.Value ?? false;
internal set => _restrictMessageSize?.TrySetValue(value);
}
private ISettingBase<bool> _restrictMessageSize;
/// <summary>
/// The local save path for all local data storage for mods.
/// </summary>
public string LocalDataSavePath
{
get => _localDataSavePath?.Value ?? Path.Combine(Directory.GetCurrentDirectory(), "/Data/Mods");
internal set => _localDataSavePath?.TrySetValue(value);
}
private ISettingBase<string> _localDataSavePath;
void LoadLuaCsConfig()
{
var luaCsPackage = ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches(PackageId), null)
return ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches(PackageId), null)
?? ContentPackageManager.LocalPackages.FirstOrDefault(cp => cp.NameMatches(PackageId))
?? ContentPackageManager.WorkshopPackages.FirstOrDefault(cp => cp.NameMatches(PackageId));
}
void LoadLuaCsConfig()
{
var luaCsPackage = GetLuaCsPackage();
_isCsEnabled =
ConfigService.TryGetConfig<ISettingBase<bool>>(luaCsPackage, "IsCsEnabled", out var val1)
? val1
: null;
_disableErrorGUIOverlay =
ConfigService.TryGetConfig<ISettingBase<bool>>(luaCsPackage, "DisableErrorGUIOverlay", out var val3)
? val3
: null;
_hideUserNamesInLogs =
ConfigService.TryGetConfig<ISettingBase<bool>>(luaCsPackage, "HideUserNamesInLogs", out var val4)
? val4
: null;
_luaForBarotraumaSteamId =
ConfigService.TryGetConfig<ISettingBase<ulong>>(luaCsPackage, "LuaForBarotraumaSteamId", out var val5)
? val5
: null;
_restrictMessageSize =
ConfigService.TryGetConfig<ISettingBase<bool>>(luaCsPackage, "RestrictMessageSize", out var val7)
? val7
: null;
_localDataSavePath =
ConfigService.TryGetConfig<ISettingBase<string>>(luaCsPackage, "LocalDataSavePath", out var val8)
? val8
: null;
}
private IServicesProvider SetupServicesProvider()
@@ -485,10 +433,7 @@ namespace Barotrauma
void DisposeLuaCsConfig()
{
_isCsEnabled = null;
_disableErrorGUIOverlay = null;
_hideUserNamesInLogs = null;
_luaForBarotraumaSteamId = null;
_restrictMessageSize = null;
}
}

View File

@@ -450,17 +450,21 @@ public sealed partial class ConfigService : IConfigService
if (_storageService.LoadLocalXml(setting.OwnerPackage, SaveDataFileName) is not { } saveFileResult)
{
#if DEBUG
return FluentResults.Result.Fail(
$"{nameof(LoadSavedValueForConfig)}: Could not open save file for setting [{setting.OwnerPackage.Name}.{setting.InternalName}]");
#endif
return FluentResults.Result.Ok();
}
if (saveFileResult is { IsFailed: true })
{
#if DEBUG
_logger.LogResults(saveFileResult.ToResult());
#endif
return FluentResults.Result.Fail(
$"{nameof(LoadSavedValueForConfig)}: Could not open save file for setting [{setting.OwnerPackage.Name}.{setting.InternalName}]");
#endif
return FluentResults.Result.Ok();
}
if (saveFileResult.Value.Root is not {} rootElement
@@ -472,7 +476,10 @@ public sealed partial class ConfigService : IConfigService
if (rootElement.GetChildElement(XmlConvert.EncodeLocalName(setting.OwnerPackage.Name.Trim()), StringComparison.InvariantCultureIgnoreCase)
?.GetChildElement(setting.InternalName, StringComparison.InvariantCultureIgnoreCase) is not {} cfgValueElement)
{
#if DEBUG
return FluentResults.Result.Fail($"{nameof(LoadSavedValueForConfig)}: Could not find saved value for setting:[{setting.OwnerPackage.Name}.{setting.InternalName}]");
#endif
return FluentResults.Result.Ok();
}
return FluentResults.Result.OkIf(setting.TrySetValue(cfgValueElement), new Error($"Failed to set value for [{setting.OwnerPackage.Name}.{setting.InternalName}]"));

View File

@@ -6,6 +6,8 @@ using OneOf;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
@@ -55,6 +57,7 @@ public partial class EventService : IEventService
private readonly ConcurrentDictionary<TypeStringKey, ConcurrentDictionary<OneOf<IEvent, string>, IEvent>> _subscribers = new();
private readonly ConcurrentDictionary<TypeStringKey, (TypeStringKey Event, Func<LuaCsFunc, IEvent> RunnerFactory)> _luaAliasEventFactory = new();
private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, LuaCsFunc>> _luaLegacyEventsSubscribers = new();
private readonly ConcurrentDictionary<IEventService, IEventService> _subscribedEventDispatchers = new();
#region LifeCycle
@@ -316,9 +319,30 @@ public partial class EventService : IEventService
}
}
foreach (var dispatchers in _subscribedEventDispatchers.ToImmutableArray())
{
dispatchers.Value.PublishEvent(action);
}
return results;
}
public void AddDispatcherEventService(IEventService eventService)
{
using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
IService.CheckDisposed(this);
_subscribedEventDispatchers.TryAdd(eventService, eventService);
}
public void RemoveDispatcherEventService(IEventService eventService)
{
using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
IService.CheckDisposed(this);
_subscribedEventDispatchers.TryRemove(eventService, out _);
}
#region LuaPatcherAdapter
public string Patch(string identifier, string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, LuaCsHook.HookMethodType hookType = LuaCsHook.HookMethodType.Before)
{

View File

@@ -13,35 +13,15 @@ public sealed class LuaCsInfoProvider : ILuaCsInfoProvider
public bool IsDisposed => false;
public bool IsCsEnabled => LuaCsSetup.Instance.IsCsEnabled;
public bool DisableErrorGUIOverlay => LuaCsSetup.Instance.DisableErrorGUIOverlay;
public bool HideUserNamesInLogs => LuaCsSetup.Instance.HideUserNamesInLogs;
public ulong LuaForBarotraumaSteamId => LuaCsSetup.Instance.LuaForBarotraumaSteamId;
public bool RestrictMessageSize => LuaCsSetup.Instance.RestrictMessageSize;
public string LocalDataSavePath => LuaCsSetup.Instance.LocalDataSavePath;
public RunState CurrentRunState => LuaCsSetup.Instance.CurrentRunState;
public ContentPackage LuaCsForBarotraumaPackage
{
get
{
var luaCs = FirstOrDefaultLua(ContentPackageManager.EnabledPackages.All);
if (luaCs == null)
{
luaCs = FirstOrDefaultLua(ContentPackageManager.LocalPackages.Regular);
}
if (luaCs == null)
{
luaCs = FirstOrDefaultLua(ContentPackageManager.WorkshopPackages.Regular);
}
return luaCs;
ContentPackage FirstOrDefaultLua(IEnumerable<ContentPackage> packages)
{
return packages.FirstOrDefault(p =>
p.Name.Equals("LuaCsForBarotrauma", StringComparison.InvariantCultureIgnoreCase)
|| p.Name.Equals("Lua for Barotrauma", StringComparison.InvariantCultureIgnoreCase));
}
return ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches(LuaCsSetup.PackageId), null)
?? ContentPackageManager.LocalPackages.FirstOrDefault(cp => cp.NameMatches(LuaCsSetup.PackageId))
?? ContentPackageManager.WorkshopPackages.FirstOrDefault(cp => cp.NameMatches(LuaCsSetup.PackageId));
}
}
}

View File

@@ -214,11 +214,13 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService,
if (!LuaCsFile.CanReadFromPath(file))
{
// TODO: Replace with LuaScriptLoader IsFileAccessible.
throw new ScriptRuntimeException($"dofile: File access to {file} not allowed.");
}
if (!LuaCsFile.Exists(file))
{
// TODO: Replace with LuaScriptLoader IsFileAccessible.
throw new ScriptRuntimeException($"dofile: File {file} not found.");
}

View File

@@ -195,6 +195,8 @@ public class PluginManagementService : IAssemblyManagementService
private Lazy<IEventService> _eventService;
private Lazy<IConfigService> _configService;
private Lazy<ILuaScriptManagementService> _luaScriptManagementService;
private IEventService _pluginEventService;
private Lazy<ILuaPatcher> _pluginLuaPatcherService;
private readonly ConcurrentDictionary<ContentPackage, IAssemblyLoaderService> _assemblyLoaders = new();
private readonly ConcurrentDictionary<Type, ContentPackage> _pluginPackageLookup = new();
private readonly ConcurrentDictionary<ContentPackage, ImmutableArray<IAssemblyPlugin>> _pluginInstances = new();
@@ -208,7 +210,8 @@ public class PluginManagementService : IAssemblyManagementService
ILoggerService logger,
Lazy<IEventService> eventService,
Lazy<ILuaScriptManagementService> luaScriptManagementService,
Lazy<IConfigService> configService)
Lazy<IConfigService> configService,
Lazy<ILuaPatcher> pluginLuaPatcherService)
{
_assemblyLoaderFactory = assemblyLoaderFactory;
_storageService = storageService;
@@ -216,19 +219,22 @@ public class PluginManagementService : IAssemblyManagementService
_eventService = eventService;
_luaScriptManagementService = luaScriptManagementService;
_configService = configService;
_pluginLuaPatcherService = pluginLuaPatcherService;
}
private ServiceContainer CreatePluginServiceContainer()
{
var container = new ServiceContainer(new ContainerOptions()
{
EnablePropertyInjection = true,
EnablePropertyInjection = true
});
_pluginEventService ??= new EventService(_logger, _pluginLuaPatcherService.Value);
_eventService.Value.AddDispatcherEventService(_pluginEventService);
container.Register<ILoggerService>(fac => _logger);
container.Register<IStorageService>(fac => _storageService);
container.Register<IEventService>(fac => _eventService.Value);
container.Register<IEventService>(fac => _pluginEventService);
container.Register<IPluginManagementService>(fac => this);
container.Register<ILuaScriptManagementService>(fac => _luaScriptManagementService.Value);
container.Register<IConfigService>(fac => _configService.Value);
@@ -707,6 +713,37 @@ public class PluginManagementService : IAssemblyManagementService
_assemblyLoaders.Clear();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true);
#if DEBUG
// Print still loaded assembly load ctx after giving some time
CoroutineManager.Invoke(() =>
{
if (!_unloadingAssemblyLoaders.Any())
{
return;
}
StringBuilder sb = new StringBuilder();
sb.AppendLine("The following ContentPackages have not unloaded their assemblies:");
foreach (var kvp in _unloadingAssemblyLoaders.ToImmutableArray())
{
sb.AppendLine($"- '{kvp.Value.Name}'");
}
// Use DebugConsole in case logger is null by the time this executes.
if (_logger is null)
{
DebugConsole.LogError(sb.ToString());
}
else
{
_logger.LogWarning(sb.ToString());
}
}, 3.0f);
#endif
return results;
}
@@ -714,24 +751,29 @@ public class PluginManagementService : IAssemblyManagementService
private FluentResults.Result UnsafeDisposeManagedTypeInstances()
{
var results = new FluentResults.Result();
if (!_pluginInstances.IsEmpty)
{
foreach (var instance in _pluginInstances.SelectMany(kvp => kvp.Value))
{
try
{
instance.Dispose();
}
catch (Exception e)
{
results.WithError(new ExceptionalError(e));
continue;
}
}
}
if (_pluginEventService is not null)
{
_eventService.Value.RemoveDispatcherEventService(_pluginEventService);
_pluginEventService = null;
}
_pluginInjectorContainer = null;
if (_pluginInstances.IsEmpty)
{
return FluentResults.Result.Ok();
}
foreach (var instance in _pluginInstances.SelectMany(kvp => kvp.Value))
{
try
{
instance.Dispose();
}
catch (Exception e)
{
results.WithError(new ExceptionalError(e));
continue;
}
}
_pluginInstances.Clear();
_pluginPackageLookup.Clear();

View File

@@ -37,4 +37,16 @@ public interface IEventService : IReusableService, ILuaEventService
/// <typeparam name="T"></typeparam>
/// <returns></returns>
FluentResults.Result PublishEvent<T>(Action<T> action) where T : class, IEvent<T>;
/// <summary>
/// Adds an event service that will receive all published events.
/// </summary>
/// <param name="eventService"></param>
void AddDispatcherEventService(IEventService eventService);
/// <summary>
/// Removes an event service from the dispatcher list.
/// </summary>
/// <param name="eventService"></param>
void RemoveDispatcherEventService(IEventService eventService);
}

View File

@@ -10,30 +10,10 @@ public interface ILuaCsInfoProvider : IService
/// </summary>
public bool IsCsEnabled { get; }
/// <summary>
/// Whether the popup error GUI should be hidden/suppressed.
/// </summary>
public bool DisableErrorGUIOverlay { get; }
/// <summary>
/// Whether usernames are anonymized or show in logs.
/// </summary>
public bool HideUserNamesInLogs { get; }
/// <summary>
/// The SteamId of the Workshop LuaCs CPackage in use, if available.
/// </summary>
public ulong LuaForBarotraumaSteamId { get; }
/// <summary>
/// Restrict the maximum size of messages sent over the network.
/// </summary>
public bool RestrictMessageSize { get; }
/// <summary>
/// The local save path for all local data storage for mods.
/// </summary>
public string LocalDataSavePath { get; }
/// <summary>
/// The current state of the Execution State Machine.

View File

@@ -52,19 +52,17 @@ namespace Barotrauma
public static bool CanWriteToPath(string path)
{
const long LuaCsPackageId = 2559634234;
string getFullPath(string p) => System.IO.Path.GetFullPath(p).CleanUpPath();
path = getFullPath(path);
bool pathStartsWith(string prefix) => path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase);
foreach (var package in ContentPackageManager.AllPackages)
if (pathStartsWith(getFullPath(LuaCsSetup.GetLuaCsPackage().Path)))
{
if (package.UgcId.ValueEquals(new SteamWorkshopId(LuaCsSetup.Instance.LuaForBarotraumaSteamId))
&& pathStartsWith(getFullPath(package.Path)))
{
return false;
}
return false;
}
if (pathStartsWith(getFullPath(string.IsNullOrEmpty(GameSettings.CurrentConfig.SavePath) ? SaveUtil.DefaultSaveFolder : GameSettings.CurrentConfig.SavePath)))