From 6362a9c34f905d90a23ca258b45ff9bf4a0fc5f4 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Fri, 16 Jan 2026 15:06:25 -0500 Subject: [PATCH] - Work on LuaCs system state machine. --- .../ClientSource/LuaCs/LuaCsSetup.cs | 2 +- .../ServerSource/LuaCs/LuaCsSetup.cs | 10 - .../LuaCs/Lua/LuaClasses/LuaUserData.cs | 9 +- .../SharedSource/LuaCs/LuaCsSetup.cs | 364 ++++++------------ .../SharedSource/LuaCs/StateMachine.cs | 107 +++++ 5 files changed, 240 insertions(+), 252 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/StateMachine.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index a60b0c8c5..d34a3d308 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -73,7 +73,7 @@ namespace Barotrauma case MainMenuScreen: case ModDownloadScreen: case ServerListScreen: - SetRunState(RunState.Configuration); + SetRunState(RunState.LoadedNoExec); break; // running lobby or editor states case CampaignEndScreen: diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs index ae63d3cc8..3e39a4b72 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs @@ -17,14 +17,4 @@ partial class LuaCsSetup SetRunState(RunState.Unloaded); SetRunState(RunState.Running); } - - private partial bool ShouldRunCs() => IsCsEnabled.Value || - (GetPackage(new SteamWorkshopId(CsForBarotraumaSteamId.Value), false, false) is { } - && GameMain.Server.ServerPeer is LidgrenServerPeer); - - // ReSharper disable once InconsistentNaming - private static readonly Lazy isRunningInsideWorkshop = new Lazy(() => - Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly()!.Location) != - Directory.GetCurrentDirectory()); - public static bool IsRunningInsideWorkshop => isRunningInsideWorkshop.Value; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs index dd5ae0469..61d60b65e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs @@ -1,4 +1,4 @@ -/* + using System; using System.Collections.Generic; using System.Linq; @@ -8,9 +8,10 @@ using MoonSharp.Interpreter.Interop; namespace Barotrauma { - partial class LuaUserData + internal class LuaUserData { - public static Type GetType(string typeName) => LuaCsSetup.GetType(typeName); + [Obsolete("Use IPluginManagementService::GetTypesByName()")] + public static Type GetType(string typeName) => throw new NotImplementedException(); //LuaCsSetup.GetType(typeName); public static IUserDataDescriptor RegisterType(string typeName) { @@ -362,4 +363,4 @@ namespace Barotrauma } } } -*/ + diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index cf3d11c58..4a7059455 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -4,8 +4,10 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; +using System.Net.Mime; using System.Threading; using System.Threading.Tasks; +using Barotrauma.LuaCs; using Barotrauma.LuaCs.Configuration; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Events; @@ -14,6 +16,7 @@ using Barotrauma.LuaCs.Services.Compatibility; using Barotrauma.LuaCs.Services.Processing; using Barotrauma.LuaCs.Services.Safe; using Barotrauma.Networking; +using Barotrauma.Steam; using FluentResults; using ImpromptuInterface; using Microsoft.Toolkit.Diagnostics; @@ -24,56 +27,101 @@ namespace Barotrauma internal delegate void LuaCsErrorHandler(Exception ex, LuaCsMessageOrigin origin); internal delegate void LuaCsExceptionHandler(Exception ex, LuaCsMessageOrigin origin); - partial class LuaCsSetup : IDisposable, IEventScreenSelected, IEventAllPackageListChanged, IEventEnabledPackageListChanged, IEventReloadAllPackages + partial class LuaCsSetup : IDisposable, IEventScreenSelected, IEventAllPackageListChanged, IEventEnabledPackageListChanged, + IEventReloadAllPackages { public LuaCsSetup() { // == startup _servicesProvider = new ServicesProvider(); - RegisterServices(); - ValidateLuaCsContent(); + RegisterServices(_servicesProvider); + if (!ValidateLuaCsContent()) + { + Logger.LogError($"{nameof(LuaCsSetup)}: ModConfigXml missing. Unable to continue."); + throw new ApplicationException($"{nameof(LuaCsSetup)}: Lua ModConfig.xml is missing. Unable to continue."); + } SubscribeToLuaCsEvents(); + _runStateMachine = SetupStateMachine(); + //LoadLuaCsConfig(); return; // == end - // == sub processes - void RegisterServices() + StateMachine SetupStateMachine() { - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - - // TODO: IConfigService - // TODO: INetworkingService - // TODO: [Resource Converter/Parser Services] - - _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); - - // service config data - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - - // gen IL - _servicesProvider.Compile(); - } + return new StateMachine(false, RunState.Unloaded, RunStateUnloaded_OnEnter, null) + .AddState(RunState.LoadedNoExec, RunStateLoadedNoExec_OnEnter, RunStateLoadedNoExec_OnExit) + .AddState(RunState.Running, RunStateRunning_OnEnter, RunStateRunning_OnExit); - // Validates LuaCs assets in /Content are valid and ready to use. - void ValidateLuaCsContent() - { - // check if /Content/Lua/ModConfig.xml exists - // if not, try to copy it from the Workshop Mod (ie. installation mode) - // if that fails, throw an error and exit. + // ReSharper disable InconsistentNaming + void RunStateUnloaded_OnEnter(State currentState) + { + + } + + void RunStateLoadedNoExec_OnEnter(State currentState) + { + + } + + void RunStateLoadedNoExec_OnExit(State currentState) + { + + } + + void RunStateRunning_OnEnter(State currentState) + { + + } + + void RunStateRunning_OnExit(State currentState) + { + + } + // ReSharper restore InconsistentNaming } } + bool ValidateLuaCsContent() + { +#if DEBUG + // TODO: we just wanna boot for now + return true; +#endif + // check if /Content/Lua/ModConfig.xml exists + // if not, try to copy it from the Local Mods folder + // if not, try to copy it from the Workshop Mods folder + // if that fails, throw an error and exit. + throw new NotImplementedException(); + } + + void RegisterServices(IServicesProvider servicesProvider) + { + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + + // TODO: IConfigService + // TODO: INetworkingService + // TODO: [Resource Converter/Parser Services] + + servicesProvider.RegisterServiceType(ServiceLifetime.Transient); + + // service config data + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + + // gen IL + servicesProvider.Compile(); + } + void SubscribeToLuaCsEvents() { EventService.Subscribe(this); // game state hook in @@ -86,104 +134,97 @@ namespace Barotrauma #if SERVER public const bool IsServer = true; - public const bool IsClient = false; #else public const bool IsServer = false; - public const bool IsClient = true; #endif + public const bool IsClient = !IsServer; #endregion #region Services_ConfigVars - + /* * === Singleton Services */ private readonly IServicesProvider _servicesProvider; - public PerformanceCounterService PerformanceCounter => - _servicesProvider.GetService(); + public PerformanceCounterService PerformanceCounter => _servicesProvider.GetService(); public ILoggerService Logger => _servicesProvider.GetService(); public IConfigService ConfigService => _servicesProvider.GetService(); - public IPackageManagementService PackageManagementService => - _servicesProvider.GetService(); - public IPluginManagementService PluginManagementService => - _servicesProvider.GetService(); - public ILuaScriptManagementService LuaScriptManagementService => - _servicesProvider.GetService(); + public IPackageManagementService PackageManagementService => _servicesProvider.GetService(); + public IPluginManagementService PluginManagementService => _servicesProvider.GetService(); + public ILuaScriptManagementService LuaScriptManagementService => _servicesProvider.GetService(); public INetworkingService NetworkingService => _servicesProvider.GetService(); public IEventService EventService => _servicesProvider.GetService(); public LuaGame Game => _servicesProvider.GetService(); + + #region LuaCsInternal + + /* - * === Config Vars + * === Config Vars */ /// /// Whether C# plugin code is enabled. /// - public IConfigEntry IsCsEnabled { get; private set; } + internal IConfigEntry IsCsEnabled { get; private set; } /// /// Whether mods marked as 'forced' or 'always load' should only be loaded if they're in the enabled mods list. /// - public IConfigEntry TreatForcedModsAsNormal { get; private set; } + internal IConfigEntry TreatForcedModsAsNormal { get; private set; } /// /// Whether the lua script runner from Workshop package should be used over the in-built version. /// - public IConfigEntry PreferToUseWorkshopLuaSetup { get; private set; } + internal IConfigEntry PreferToUseWorkshopLuaSetup { get; private set; } /// /// Whether the popup error GUI should be hidden/suppressed. /// - public IConfigEntry DisableErrorGUIOverlay { get; private set; } + internal IConfigEntry DisableErrorGUIOverlay { get; private set; } /// /// Whether usernames are anonymized or show in logs. /// - public IConfigEntry HideUserNamesInLogs { get; private set; } + internal IConfigEntry HideUserNamesInLogs { get; private set; } /// /// The SteamId of the Workshop LuaCs CPackage in use, if available. /// - public IConfigEntry LuaForBarotraumaSteamId { get; private set; } - - /// - /// The SteamId of the Workshop LuaCs CsForBarotrauma add-on, if available. - /// - public IConfigEntry CsForBarotraumaSteamId { get; private set; } - - /// - /// 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). - /// - public IConfigEntry ReloadPackagesOnLobbyStart { get; private set; } + internal IConfigEntry LuaForBarotraumaSteamId { get; private set; } /// /// TODO: @evilfactory@users.noreply.github.com /// - public IConfigEntry RestrictMessageSize { get; private set; } + internal IConfigEntry RestrictMessageSize { get; private set; } /// /// The local save path for all local data storage for mods. /// - public IConfigEntry LocalDataSavePath { get; private set; } + internal IConfigEntry LocalDataSavePath { get; private set; } + + #endregion /** * == Ops Vars */ private RunState _runState; + /// /// The current run state of all services managed by LuaCs. /// - public RunState CurrentRunState => _runState; + public RunState CurrentRunState + { + get => _runState; + private set => _runState = value; + } - private bool CPacksParsed => CurrentRunState >= RunState.Parsed; - private bool IsStaticAssetsLoaded => CurrentRunState >= RunState.Configuration; - private bool IsCodeRunning => CurrentRunState >= RunState.Running; + private readonly StateMachine _runStateMachine; private readonly ConcurrentQueue _toLoad = new(); private readonly ConcurrentQueue _toUnload = new(); @@ -194,61 +235,6 @@ namespace Barotrauma public ILuaCsHook Hook => this.EventService; #endregion - - - private partial bool ShouldRunCs(); - - - // 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); - } - - public static ContentPackage GetPackage(ContentPackageId id, bool fallbackToAll = true, bool useBackup = false) - { - foreach (ContentPackage package in ContentPackageManager.EnabledPackages.All) - { - if (package.UgcId.ValueEquals(id)) - { - return package; - } - } - - if (fallbackToAll) - { - foreach (ContentPackage package in ContentPackageManager.LocalPackages) - { - if (package.UgcId.ValueEquals(id)) - { - return package; - } - } - - foreach (ContentPackage package in ContentPackageManager.AllPackages) - { - if (package.UgcId.ValueEquals(id)) - { - return package; - } - } - } - - if (useBackup && ContentPackageManager.EnabledPackages.BackupPackages.Regular != null) - { - foreach (ContentPackage package in ContentPackageManager.EnabledPackages.BackupPackages.Regular.Value) - { - if (package.UgcId.ValueEquals(id)) - { - return package; - } - } - } - - return null; - } public void Dispose() { @@ -333,109 +319,12 @@ namespace Barotrauma //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)) - { - - } - - var ls = new List(); - - while (_toLoad.TryDequeue(out var cp)) - { - if (PackageManagementService.LoadPackageInfo(cp) 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) + void SetRunState(RunState newRunState) { - if (CurrentRunState == runState) + if (CurrentRunState == newRunState) 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 LoadLuaCsConfig() @@ -450,12 +339,8 @@ namespace Barotrauma : throw new NullReferenceException($"{nameof(HideUserNamesInLogs)} cannot be loaded."); LuaForBarotraumaSteamId = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "LuaForBarotraumaSteamId", out var val5) ? val5 : throw new NullReferenceException($"{nameof(LuaForBarotraumaSteamId)} cannot be loaded."); - CsForBarotraumaSteamId = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "CsForBarotraumaSteamId", out var val6) ? val6 - : throw new NullReferenceException($"{nameof(CsForBarotraumaSteamId)} cannot be loaded."); RestrictMessageSize = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "RestrictMessageSize", out var val7) ? val7 : throw new NullReferenceException($"{nameof(RestrictMessageSize)} cannot be loaded."); - ReloadPackagesOnLobbyStart = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "ReloadPackagesOnLobbyStart", out var val8) ? val8 - : throw new NullReferenceException($"{nameof(ReloadPackagesOnLobbyStart)} cannot be loaded."); } void DisposeLuaCsConfig() @@ -465,12 +350,8 @@ namespace Barotrauma DisableErrorGUIOverlay = null; HideUserNamesInLogs = null; LuaForBarotraumaSteamId = null; - CsForBarotraumaSteamId = null; RestrictMessageSize = null; - ReloadPackagesOnLobbyStart = null; } - - } /// @@ -479,9 +360,18 @@ namespace Barotrauma /// 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. + /// + /// No assets are loaded, code execution suspended. + /// + Unloaded = 0, + /// + /// Loaded mod configs, settings and assets. No code execution. + /// + LoadedNoExec = 1, + /// + /// All assets loaded, code execution is active. + /// + Running = 2 } } + diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/StateMachine.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/StateMachine.cs new file mode 100644 index 000000000..79dc5779f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/StateMachine.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using Microsoft.Toolkit.Diagnostics; + +namespace Barotrauma.LuaCs; + +public class StateMachine where T : Enum +{ + private readonly ConcurrentDictionary> _states; + private State _currentState; + public T CurrentState => _currentState.StateId; + private bool _errorOnSameStateSelected; + private readonly AsyncReaderWriterLock _operationsLock = new(); + + public StateMachine(bool errorOnSameState, T defaultState, Action> onEnter, Action> onExit) + { + _errorOnSameStateSelected = errorOnSameState; + _states = new ConcurrentDictionary>(); + var defState = new State(defaultState, onEnter, onExit); + _currentState = defState; + _states[defaultState] = defState; + } + + public StateMachine AddState(T stateId, Action> onEnter, Action> onExit) + { + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + if (_states.TryGetValue(stateId, out _)) + { + ThrowHelper.ThrowArgumentException($"State with id {stateId} already exists."); + } + + _states[stateId] = new State(stateId, onEnter, onExit); + return this; + } + + public StateMachine RemoveState(T stateId) + { + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + if (EqualityComparer.Default.Equals(stateId, CurrentState)) + { + ThrowHelper.ThrowInvalidOperationException($"State with id {CurrentState} is active. Cannot remove."); + } + + _states.TryRemove(stateId, out _); + return this; + } + + public StateMachine AddOrReplaceState(T oldStateId, T newStateId, Action> onEnter, Action> onExit) + { + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + if (EqualityComparer.Default.Equals(oldStateId, CurrentState)) + { + ThrowHelper.ThrowInvalidOperationException($"State with id {CurrentState} is active. Cannot replace."); + } + + _states[oldStateId] = new State(newStateId, onEnter, onExit); + return this; + } + + public StateMachine GotoState(T stateId) + { + using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + if (EqualityComparer.Default.Equals(stateId, CurrentState)) + { + if (_errorOnSameStateSelected) + { + ThrowHelper.ThrowInvalidOperationException($"State with id {stateId} is already selected."); + } + + return this; + } + + if (!_states.TryGetValue(stateId, out var newState)) + { + ThrowHelper.ThrowArgumentNullException($"Target state with id {stateId} does not exist."); + } + + _currentState.OnExit(); + _currentState = newState; + _currentState.OnEnter(); + return this; + } +} + +public class State where T : Enum +{ + public T StateId; + private Action> _onEnter, _onExit; + public State(T stateId, Action> onExitState, Action> onEnterState) + { + StateId = stateId; + _onEnter = onEnterState; + _onExit = onExitState; + } + + public virtual void OnEnter() + { + _onEnter?.Invoke(this); + } + + public virtual void OnExit() + { + _onExit?.Invoke(this); + } +} +