- Weird LuaCs Settings Menu bug present: package is loaded on startup but is then unloaded if the settingsmenu is opened and the package is not in the enabled list.

This commit is contained in:
Maplewheels
2026-02-28 22:10:29 -05:00
parent 28b355911d
commit 09bc2d0891
9 changed files with 52 additions and 191 deletions
@@ -1,142 +0,0 @@
using System;
using System.Collections.Generic;
using Barotrauma.LuaCs.Data;
namespace Barotrauma.LuaCs.Configuration;
/// <summary>
/// Base type of all menu displayable types.
/// </summary>
public interface IDisplayableConfigBase : IDataInfo, IConfigDisplayInfo
{
/// <summary>
/// Whether the current config is editable.
/// </summary>
bool IsEditable { get; }
/// <summary>
/// Used to indicate the implemented interface and targeted display logic.
/// </summary>
static virtual DisplayType DisplayOption => DisplayType.Undefined;
}
public interface IDisplayableConfigBase<out TDisplay, in TValue> : IDisplayableConfigBase
{
void SetValue(TValue value);
TDisplay GetDisplayValue();
}
public interface IDisplayableConfigBool : IDisplayableConfigBase<bool, bool>
{
static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.Boolean;
}
public interface IDisplayableConfigText : IDisplayableConfigBase<string, string>
{
static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.Text;
}
public interface IDisplayableConfigInt : IDisplayableConfigBase<int, int>
{
static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.Integer;
}
public interface IDisplayableConfigFloat : IDisplayableConfigBase<float, float>
{
static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.Float;
}
public interface IDisplayableConfigSliderInt : IDisplayableConfigBase<(int Min, int Max, int Value, int Steps), int>
{
static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.SliderInt;
}
public interface IDisplayableConfigSliderFloat : IDisplayableConfigBase<(float Min, float Max, float Value, int Steps), float>
{
static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.SliderFloat;
}
public interface IDisplayableConfigDropdown : IDisplayableConfigBase<List<string>, string>
{
static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.Dropdown;
}
/// <summary>
/// Allows completely custom-designed UI for this configuration component.
/// </summary>
public interface IDisplayableConfigCustom : IDisplayableConfigBase
{
static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.Custom;
/// <summary>
/// Draw your menu settings option.
/// </summary>
/// <param name="layoutGroup">Parent layout component.</param>
void DrawComponent(GUILayoutGroup layoutGroup);
/// <summary>
/// Called when the config element is set to be disposed to allow for cleanup.
/// </summary>
void DisposeGUI();
/// <summary>
/// Called when the UI indicates to save the current value as permanent.
/// </summary>
void OnValueSaved();
/// <summary>
/// Called when the UI indicates to discard the currently displayed value and revert to the last saved value.
/// </summary>
void OnValueDiscarded();
}
/// <summary>
/// Indicates the intended display and feedback logic to be used by the <see cref="SettingsMenu"/>.
/// <br/><b>[Important]</b>
/// <br/>The type must implement the indicated interface for the selected option, or it will not be displayed.
/// </summary>
public enum DisplayType
{
/// <summary>
/// Will not be displayed in menus.
/// </summary>
Undefined,
/// <summary>
/// Will be shown as a checkbox.
/// <br/><b>[Requires(<see cref="IDisplayableConfigBool"/>)]</b>
/// </summary>
Boolean,
/// <summary>
/// Shown as an editable text input.
/// <br/><b>[Requires(<see cref="IDisplayableConfigText"/>)]</b>
/// </summary>
Text,
/// <summary>
/// Shown as number input (no decimal input).
/// <br/><b>[Requires(<see cref="IDisplayableConfigInt"/>)]</b>
/// </summary>
Integer,
/// <summary>
/// Shown as a number input.
/// <br/><b>[Requires(<see cref="IDisplayableConfigFloat"/>)]</b>
/// </summary>
Float,
/// <summary>
/// Shown as a slider, values parsed as integers.
/// <br/><b>[Requires(<see cref="IDisplayableConfigSliderInt"/>)]</b>
/// </summary>
SliderInt,
/// <summary>
/// Shown as a slider, values parsed as single-precision decimal numbers.
/// <br/><b>[Requires(<see cref="IDisplayableConfigSliderFloat"/>)]</b>
/// </summary>
SliderFloat,
/// <summary>
/// Shown as a <see cref="GUIDropDown"/> menu, values parsed as strings.
/// <br/><b>[Requires(<see cref="IDisplayableConfigDropdown"/>)]</b>
/// </summary>
Dropdown,
/// <summary>
/// UI Display is implemented by inheritor and actioned by a call to <see cref="IDisplayableConfigCustom.DrawComponent"/>.
/// <br/><b>[Requires(<see cref="IDisplayableConfigCustom"/>)]</b>
/// </summary>
Custom
}
@@ -8,31 +8,23 @@ using OneOf;
namespace Barotrauma.LuaCs.Configuration; namespace Barotrauma.LuaCs.Configuration;
public class SettingControl : ISettingControl public class SettingControl : SettingBase, ISettingControl
{ {
public string InternalName { get; } public SettingControl(IConfigInfo configInfo) : base(configInfo)
public ContentPackage OwnerPackage { get; }
public bool Equals(ISettingBase other)
{ {
throw new NotImplementedException();
} }
public void Dispose() protected override void OnDispose()
{ {
throw new NotImplementedException(); OnValueChanged = null;
} }
public IConfigDisplayInfo GetDisplayInfo() public override Type GetValueType() => typeof(KeyOrMouse);
{ public override string GetStringValue() => Value.ToString();
throw new NotImplementedException();
}
public Type GetValueType() => typeof(KeyOrMouse); public override string GetDefaultStringValue() => new KeyOrMouse(Keys.NumLock).ToString();
public string GetStringValue() => Value.ToString();
public string GetDefaultStringValue() => new KeyOrMouse(Keys.NumLock).ToString(); public override bool TrySetValue(OneOf<string, XElement> value)
public bool TrySetValue(OneOf<string, XElement> value)
{ {
var newVal = value.Match<KeyOrMouse>( var newVal = value.Match<KeyOrMouse>(
(string v) => GetKeyOrMouse(v), (string v) => GetKeyOrMouse(v),
@@ -77,12 +69,8 @@ public class SettingControl : ISettingControl
} }
public event Action<ISettingBase> OnValueChanged; public override event Action<ISettingBase> OnValueChanged;
public OneOf<string, XElement> GetSerializableValue() public override OneOf<string, XElement> GetSerializableValue() => Value.ToString();
{
return Value.ToString();
}
public KeyOrMouse Value { get; private set; } = new KeyOrMouse(Keys.NumLock); public KeyOrMouse Value { get; private set; } = new KeyOrMouse(Keys.NumLock);
public bool TrySetValue(KeyOrMouse value) public bool TrySetValue(KeyOrMouse value)
@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq;
using Barotrauma.LuaCs.Configuration; using Barotrauma.LuaCs.Configuration;
using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Data;
using Barotrauma.Networking; using Barotrauma.Networking;
@@ -9,18 +10,14 @@ namespace Barotrauma.LuaCs;
public sealed partial class ConfigService public sealed partial class ConfigService
{ {
public ImmutableArray<IDisplayableConfigBase> GetDisplayableConfigs() public ImmutableArray<ISettingBase> GetDisplayableConfigs()
{ {
throw new NotImplementedException(); using var _ = _operationLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
} IService.CheckDisposed(this);
public ImmutableArray<IDisplayableConfigBase> GetDisplayableConfigsForPackage(ContentPackage package) return _settingsInstances.Values
{ .Where(s => !s.IsDisposed)
throw new NotImplementedException(); .Where(s => s.GetDisplayInfo().ShowInMenus)
} .ToImmutableArray();
public Result<ISettingControl> AddConfigControl(IConfigInfo configInfo)
{
throw new NotImplementedException();
} }
} }
@@ -1,15 +1,25 @@
using Microsoft.Xna.Framework; using System.Collections.Immutable;
using Microsoft.Xna.Framework;
using System.Linq; using System.Linq;
using Barotrauma.LuaCs.Data;
namespace Barotrauma.LuaCs; namespace Barotrauma.LuaCs;
internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenu internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenu
{ {
private readonly ImmutableArray<ISettingBase> _settingsInstancesGameplay;
public ModsGameplaySettingsMenu(GUIFrame contentFrame, public ModsGameplaySettingsMenu(GUIFrame contentFrame,
IPackageManagementService packageManagementService, IPackageManagementService packageManagementService,
IConfigService configService, IConfigService configService,
SettingsMenu settingsMenuInstance) : base(contentFrame, packageManagementService, configService, settingsMenuInstance) SettingsMenu settingsMenuInstance) : base(contentFrame, packageManagementService, configService, settingsMenuInstance)
{ {
_settingsInstancesGameplay = configService.GetDisplayableConfigs()
.Where(s => s is not ISettingControl)
.ToImmutableArray();
var mainLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), contentFrame.RectTransform, Anchor.Center), false, Anchor.TopLeft); var mainLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), contentFrame.RectTransform, Anchor.Center), false, Anchor.TopLeft);
// page title // page title
var menuTitleLayoutGroup = new GUILayoutGroup( var menuTitleLayoutGroup = new GUILayoutGroup(
@@ -30,7 +40,7 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenu
{ {
OnTextChangedDelegate = (btn, txt) => OnTextChangedDelegate = (btn, txt) =>
{ {
// TODO: Execute filter here GenerateDisplayFromFilter(txt);
return true; return true;
} }
}; };
@@ -45,21 +55,20 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenu
var cpList = packageManagementService.GetAllLoadedPackages().OrderBy(cp => cp.Name == "Vanilla" ? 0 : 1).ThenBy(cp => cp.Name).ToList(); var cpList = packageManagementService.GetAllLoadedPackages().OrderBy(cp => cp.Name == "Vanilla" ? 0 : 1).ThenBy(cp => cp.Name).ToList();
var modSelectDropDown = GUIUtil.Dropdown<ContentPackage>(modCategoryDisplayGroup, cp => cp.Name == "Vanilla" ? "All" : cp.Name, null, cpList, cpList[0], cp => var modSelectDropDown = GUIUtil.Dropdown<ContentPackage>(modCategoryDisplayGroup, cp => cp.Name == "Vanilla" ? "All" : cp.Name, null, cpList, cpList[0], cp =>
{ {
// TODO: filter selections by adding it to the search bar // TODO: apply filter text
}, Vector2.One, 2); }, Vector2.One, 2);
void GenerateDisplayFromFilter(string text) void GenerateDisplayFromFilter(string text)
{ {
} }
void GenerateCategoryListDisplay(GUILayoutGroup layoutGroup) void GenerateCategoryListDisplay(GUILayoutGroup layoutGroup, ImmutableArray<ISettingBase> settings)
{ {
} }
void GenerateSettingsListDisplay(GUILayoutGroup layoutGroup) void GenerateSettingsListDisplay(GUILayoutGroup layoutGroup, ImmutableArray<ISettingBase> settings)
{ {
} }
@@ -10,8 +10,5 @@ namespace Barotrauma.LuaCs;
public partial interface IConfigService public partial interface IConfigService
{ {
ImmutableArray<IDisplayableConfigBase> GetDisplayableConfigs(); ImmutableArray<ISettingBase> GetDisplayableConfigs();
ImmutableArray<IDisplayableConfigBase> GetDisplayableConfigsForPackage(ContentPackage package);
FluentResults.Result<ISettingControl> AddConfigControl(IConfigInfo configInfo);
} }
@@ -29,6 +29,7 @@ public interface ISettingBase : IDataInfo, IEquatable<ISettingBase>, IDisposable
#if CLIENT #if CLIENT
IConfigDisplayInfo GetDisplayInfo(); IConfigDisplayInfo GetDisplayInfo();
#endif #endif
bool IsDisposed { get; }
Type GetValueType(); Type GetValueType();
string GetStringValue(); string GetStringValue();
string GetDefaultStringValue(); string GetDefaultStringValue();
@@ -30,12 +30,14 @@ public abstract class SettingBase : ISettingBase
} }
private int _isDisposed = 0; private int _isDisposed = 0;
protected virtual bool IsDisposed public virtual bool IsDisposed
{ {
get => ModUtils.Threading.GetBool(ref _isDisposed); get => ModUtils.Threading.GetBool(ref _isDisposed);
private set => ModUtils.Threading.SetBool(ref _isDisposed, value); private set => ModUtils.Threading.SetBool(ref _isDisposed, value);
} }
protected abstract void OnDispose();
public virtual void Dispose() public virtual void Dispose()
{ {
if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed))
@@ -43,8 +45,8 @@ public abstract class SettingBase : ISettingBase
return; return;
} }
OnDispose();
ConfigInfo = null; ConfigInfo = null;
OnValueChanged = null;
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
@@ -55,6 +57,6 @@ public abstract class SettingBase : ISettingBase
public abstract string GetDefaultStringValue(); public abstract string GetDefaultStringValue();
public abstract bool TrySetValue(OneOf<string, XElement> value); public abstract bool TrySetValue(OneOf<string, XElement> value);
public event Action<ISettingBase> OnValueChanged; public abstract event Action<ISettingBase> OnValueChanged;
public abstract OneOf<string, XElement> GetSerializableValue(); public abstract OneOf<string, XElement> GetSerializableValue();
} }
@@ -65,7 +65,7 @@ public class SettingEntry<T> : SettingBase, ISettingBase<T>, INetworkSyncVar whe
{ {
return false; return false;
} }
OnValueChanged?.Invoke(this);
#if CLIENT #if CLIENT
if (GameMain.IsMultiplayer && SyncType is NetSync.ClientOneWay or NetSync.TwoWay) if (GameMain.IsMultiplayer && SyncType is NetSync.ClientOneWay or NetSync.TwoWay)
{ {
@@ -96,6 +96,11 @@ public class SettingEntry<T> : SettingBase, ISettingBase<T>, INetworkSyncVar whe
return true; return true;
} }
protected override void OnDispose()
{
ValueChangePredicate = null;
}
public override Type GetValueType() => typeof(T); public override Type GetValueType() => typeof(T);
public override string GetStringValue() => Value.ToString(); public override string GetStringValue() => Value.ToString();
@@ -135,6 +140,8 @@ public class SettingEntry<T> : SettingBase, ISettingBase<T>, INetworkSyncVar whe
return !isFailed && TrySetValue(typeConvertedValue); return !isFailed && TrySetValue(typeConvertedValue);
} }
public override event Action<ISettingBase> OnValueChanged;
public override OneOf<string, XElement> GetSerializableValue() => Value.ToString(); public override OneOf<string, XElement> GetSerializableValue() => Value.ToString();
// -- Networking // -- Networking
@@ -339,6 +339,7 @@ public sealed partial class ConfigService : IConfigService
var toProcessDocs = taskResults var toProcessDocs = taskResults
.Where(tr => !tr.IsDefaultOrEmpty) .Where(tr => !tr.IsDefaultOrEmpty)
.SelectMany(tr => tr) .SelectMany(tr => tr)
.Where(icf => icf is not null)
.ToImmutableArray(); .ToImmutableArray();
var instanceQueue = new Queue<(IConfigInfo configInfo, Func<(IConfigService ConfigService, IConfigInfo Info), ISettingBase> factory)>(); var instanceQueue = new Queue<(IConfigInfo configInfo, Func<(IConfigService ConfigService, IConfigInfo Info), ISettingBase> factory)>();
@@ -604,6 +605,7 @@ public sealed partial class ConfigService : IConfigService
result.WithReasons(_eventService.PublishEvent<IEventSettingInstanceLifetime>(sub => sub.OnSettingInstanceDisposed(setting)).Reasons); result.WithReasons(_eventService.PublishEvent<IEventSettingInstanceLifetime>(sub => sub.OnSettingInstanceDisposed(setting)).Reasons);
try try
{ {
_settingsInstances.TryRemove((setting.OwnerPackage, setting.InternalName), out _);
setting.Dispose(); setting.Dispose();
} }
catch (Exception e) catch (Exception e)