Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs

524 lines
23 KiB
C#

using Barotrauma.LuaCs;
using Barotrauma.LuaCs.Compatibility;
using Barotrauma.LuaCs.Data;
using Barotrauma.LuaCs.Events;
using LightInject;
using MoonSharp.Interpreter;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using AssemblyLoader = Barotrauma.LuaCs.AssemblyLoader;
[assembly: InternalsVisibleTo("ImpromptuInterfaceDynamicAssembly")]
[assembly: InternalsVisibleTo("Dynamitey")]
namespace Barotrauma
{
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, IEventScreenSelected, IEventEnabledPackageListChanged,
IEventReloadAllPackages
{
public const string PackageName = "LuaCsForBarotrauma";
private static LuaCsSetup _luaCsSetup;
public static LuaCsSetup Instance => _luaCsSetup ??= new LuaCsSetup();
/// <summary>
/// The index of the last Vanilla command.
/// </summary>
public static int DebugConsoleCommandVanillaIndex { get; private set; }
private LuaCsSetup()
{
if (_luaCsSetup != null)
{
throw new Exception("Tried to create another LuaCsSetup instance");
}
DebugConsoleCommandVanillaIndex = DebugConsole.Commands.Count;
// == startup
_servicesProvider = SetupServicesProvider();
_runStateMachine = SetupStateMachine();
SubscribeToLuaCsEvents();
}
private void SubscribeToLuaCsEvents()
{
EventService.Subscribe<IEventScreenSelected>(this); // game state hook in
EventService.Subscribe<IEventEnabledPackageListChanged>(this);
EventService.Subscribe<IEventReloadAllPackages>(this);
}
#region CONST_DEF
#if SERVER
public const bool IsServer = true;
#else
public const bool IsServer = false;
#endif
public const bool IsClient = !IsServer;
#endregion
#region Services_CVars
/*
* === Singleton Services
*/
private readonly IServicesProvider _servicesProvider;
private PerformanceCounterService _performanceCounterService;
public PerformanceCounterService PerformanceCounterService => _performanceCounterService ??= _servicesProvider.GetService<PerformanceCounterService>();
public ILoggerService Logger => _servicesProvider.GetService<ILoggerService>();
public IConfigService ConfigService => _servicesProvider.GetService<IConfigService>();
public IPackageManagementService PackageManagementService => _servicesProvider.GetService<IPackageManagementService>();
public IPluginManagementService PluginManagementService => _servicesProvider.GetService<IPluginManagementService>();
public ILuaScriptManagementService LuaScriptManagementService => _servicesProvider.GetService<ILuaScriptManagementService>();
public INetworkingService NetworkingService => _servicesProvider.GetService<INetworkingService>();
// hotpath performance ref cache
private IEventService _eventService = null;
public IEventService EventService => _eventService ??= _servicesProvider.GetService<IEventService>();
// hotpath performance ref cache
private LuaGame _game;
public LuaGame Game => _game ??= _servicesProvider.GetService<LuaGame>();
public Script Lua => LuaScriptManagementService.InternalScript;
private ISettingBase<bool> _isCsEnabledForSession;
public bool IsCsEnabledForSession
{
get => _isCsEnabledForSession?.Value ?? false;
internal set
{
_isCsEnabledForSession?.TrySetValue(value);
if (_isCsEnabledForSession != null)
{
if (_isCsEnabledForSession.GetConfigInfo() == null)
{
Logger.LogError($"Config info was nil while trying to save {IsCsEnabledForSession}");
return;
}
ConfigService.SaveConfigValue(_isCsEnabledForSession);
}
}
}
/// <summary>
/// Whether C# plugin code is enabled.
/// </summary>
public bool IsCsEnabled
{
#if CLIENT
get => _csRunPolicy?.Value == "Enabled" || IsCsEnabledForSession;
#elif SERVER
// cs settings cannot be changed on the server after launch
get => _csRunPolicy?.Value is "Enabled" or "Prompt";
#endif
}
private ISettingList<string> _csRunPolicy;
public string CsRunPolicyValue => _csRunPolicy?.Value ?? "Prompt";
/// <summary>
/// Whether usernames are anonymized or show in logs.
/// </summary>
public bool HideUserNamesInLogs
{
get => _hideUserNamesInLogs?.Value ?? false;
internal set => _hideUserNamesInLogs?.TrySetValue(value);
}
private ISettingBase<bool> _hideUserNamesInLogs;
public bool UseCaching
{
get => _useCaching?.Value ?? true;
}
private ISettingBase<bool> _useCaching;
public static ContentPackage GetLuaCsPackage()
{
return ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches(PackageName), null)
?? ContentPackageManager.LocalPackages.FirstOrDefault(cp => cp.NameMatches(PackageName))
?? ContentPackageManager.WorkshopPackages.FirstOrDefault(cp => cp.NameMatches(PackageName));
}
void LoadLuaCsConfig()
{
var luaCsPackage = GetLuaCsPackage();
_csRunPolicy =
ConfigService.TryGetConfig<ISettingList<string>>(luaCsPackage, "CsRunPolicy", out var val1)
? val1
: null;
_hideUserNamesInLogs =
ConfigService.TryGetConfig<ISettingBase<bool>>(luaCsPackage, "HideUserNamesInLogs", out var val4)
? val4
: null;
_useCaching =
ConfigService.TryGetConfig<ISettingBase<bool>>(luaCsPackage, "UseCaching", out var val5)
? val5
: null;
_isCsEnabledForSession =
ConfigService.TryGetConfig<ISettingBase<bool>>(luaCsPackage, "IsCsEnabledForSession", out var val6)
? val6
: null;
if (!ContentPackageManager.EnabledPackages.All.Contains(luaCsPackage))
{
// sorry perfidius (not sorry)
luaCsPackage.UnloadFilesOfType<TextFile>();
luaCsPackage.LoadFilesOfType<TextFile>();
}
}
private IServicesProvider SetupServicesProvider()
{
var servicesProvider = new ServicesProvider();
// Base Service
servicesProvider.RegisterServiceType<ILoggerService, LoggerService>(ServiceLifetime.Singleton);
servicesProvider.RegisterServiceType<PerformanceCounterService, PerformanceCounterService>(ServiceLifetime.Singleton);
servicesProvider.RegisterServiceType<IStorageService, StorageService>(ServiceLifetime.Transient);
servicesProvider.RegisterServiceType<ISafeStorageService, SafeStorageService>(ServiceLifetime.Transient);
servicesProvider.RegisterServiceType<IEventService, EventService>(ServiceLifetime.Singleton);
servicesProvider.RegisterServiceResolver<ILuaCsHook>(factory => factory.GetInstance<IEventService>() as ILuaCsHook);
servicesProvider.RegisterServiceType<IPackageManagementService, PackageManagementService>(ServiceLifetime.Singleton);
servicesProvider.RegisterServiceType<IAssemblyManagementService, PluginManagementService>(ServiceLifetime.Singleton);
servicesProvider.RegisterServiceResolver<IPluginManagementService>(factory => factory.GetInstance<IAssemblyManagementService>());
servicesProvider.RegisterServiceType<ILuaScriptManagementService, LuaScriptManagementService>(ServiceLifetime.Singleton);
servicesProvider.RegisterServiceType<IConfigService, ConfigService>(ServiceLifetime.Singleton);
servicesProvider.RegisterServiceType<INetworkingService, NetworkingService>(ServiceLifetime.Singleton);
servicesProvider.RegisterServiceType<INetworkIdProvider, NetworkingIdProvider>(ServiceLifetime.Transient);
servicesProvider.RegisterServiceType<HarmonyEventPatchesService, HarmonyEventPatchesService>(ServiceLifetime.Singleton);
servicesProvider.RegisterServiceType<IConsoleCommandsService, ConsoleCommandsService>(ServiceLifetime.Transient);
servicesProvider.RegisterServiceType<MainMenuPatch, MainMenuPatch>(ServiceLifetime.Singleton);
servicesProvider.RegisterServiceResolver<ILuaConfigService>(factory => factory.GetInstance<IConfigService>() as ILuaConfigService);
// Extension/Sub Services
servicesProvider.RegisterServiceType<IAssemblyLoaderService.IFactory, AssemblyLoader.Factory>(ServiceLifetime.Transient);
servicesProvider.RegisterServiceType<ISettingsRegistrationProvider, SettingsEntryRegistrar>(ServiceLifetime.Transient);
servicesProvider.RegisterServiceType<IModConfigService, ModConfigService>(ServiceLifetime.Transient);
servicesProvider.RegisterServiceType<IParserServiceAsync<ResourceParserInfo, IAssemblyResourceInfo>, ModConfigFileParserService>(ServiceLifetime.Transient);
servicesProvider.RegisterServiceType<IParserServiceAsync<ResourceParserInfo, ILuaScriptResourceInfo>, ModConfigFileParserService>(ServiceLifetime.Transient);
servicesProvider.RegisterServiceType<IParserServiceAsync<ResourceParserInfo, IConfigResourceInfo>, ModConfigFileParserService>(ServiceLifetime.Transient);
servicesProvider.RegisterServiceType<IParserServiceOneToManyAsync<IConfigResourceInfo, IConfigInfo>, SettingsFileParserService>(ServiceLifetime.Transient);
servicesProvider.RegisterServiceType<IParserServiceOneToManyAsync<IConfigResourceInfo, IConfigProfileInfo>, SettingsFileParserService>(ServiceLifetime.Transient);
servicesProvider.RegisterServiceType<INetworkIdProvider, NetworkingIdProvider>(ServiceLifetime.Transient);
// All Lua Extras
servicesProvider.RegisterServiceType<IDefaultLuaRegistrar, DefaultLuaRegistrar>(ServiceLifetime.Singleton);
servicesProvider.RegisterServiceType<ILuaPatcher, LuaPatcherService>(ServiceLifetime.Singleton);
servicesProvider.RegisterServiceType<ILuaUserDataService, LuaUserDataService>(ServiceLifetime.Singleton);
servicesProvider.RegisterServiceType<ISafeLuaUserDataService, SafeLuaUserDataService>(ServiceLifetime.Singleton);
servicesProvider.RegisterServiceType<ILuaCsInfoProvider, LuaCsInfoProvider>(ServiceLifetime.Transient);
servicesProvider.RegisterServiceType<ILuaScriptLoader, LuaScriptLoader>(ServiceLifetime.Transient);
servicesProvider.RegisterServiceType<LuaGame, LuaGame>(ServiceLifetime.Singleton);
servicesProvider.RegisterServiceType<ILuaCsTimer, LuaCsTimer>(ServiceLifetime.Singleton);
// service config data
servicesProvider.RegisterServiceType<IStorageServiceConfig, StorageServiceConfig>(ServiceLifetime.Singleton);
servicesProvider.RegisterServiceType<ILuaScriptServicesConfig, LuaScriptServicesConfig>(ServiceLifetime.Singleton);
servicesProvider.RegisterServiceType<IConfigServiceConfig, ConfigServiceConfig>(ServiceLifetime.Singleton);
servicesProvider.RegisterServiceType<IPackageManagementServiceConfig, PackageManagementServiceConfig>(ServiceLifetime.Singleton);
#if CLIENT
SetupServicesProviderClient(servicesProvider);
#endif
// gen IL
servicesProvider.CompileAndRun();
return servicesProvider;
}
#endregion
#region StateMachine
private RunState _runState;
/// <summary>
/// The current run state of all services managed by LuaCs.
/// </summary>
public RunState CurrentRunState
{
get => _runState;
private set => _runState = value;
}
private readonly StateMachine<RunState> _runStateMachine;
public void OnEnabledPackageListChanged(CorePackage package, IEnumerable<RegularPackage> regularPackages)
{
ProcessEnabledPackageChanges(new []{ package }.Concat<ContentPackage>(regularPackages).ToImmutableArray());
}
public void OnReloadAllPackages()
{
CoroutineManager.Invoke(() =>
{
SetRunState(RunState.Unloaded);
CoroutineManager.Invoke(() =>
{
SetRunState(RunState.Running);
},0.25f);
});
}
private void ProcessEnabledPackageChanges(ImmutableArray<ContentPackage> packages)
{
if (CurrentRunState < RunState.LoadedNoExec)
{
return;
}
var state = CurrentRunState;
if (CurrentRunState > RunState.LoadedNoExec)
{
SetRunState(RunState.LoadedNoExec);
}
this.Logger.LogResults(PackageManagementService.SyncLoadedPackagesList(GetLuaCsEnabledPackagesList(packages)));
ConfigService.LoadSavedConfigsValues();
SetRunState(state); // restore
}
public void SetRunState(RunState targetRunState)
{
if (CurrentRunState == targetRunState)
{
return;
}
_runStateMachine.GotoState(targetRunState);
}
private ImmutableArray<ContentPackage> GetEnabledPackagesList()
=> GetLuaCsEnabledPackagesList(ContentPackageManager.EnabledPackages.Regular
.ToImmutableArray<ContentPackage>());
private ImmutableArray<ContentPackage> GetLuaCsEnabledPackagesList(ImmutableArray<ContentPackage> enabledRegular)
{
if (!enabledRegular.Any(p => p.Name.Equals(PackageName, StringComparison.InvariantCultureIgnoreCase)))
{
var luaCs = ContentPackageManager.AllPackages.FirstOrDefault(p => p.Name.Equals(PackageName, StringComparison.InvariantCultureIgnoreCase));
if (luaCs is null)
{
DebugConsole.ThrowError($"The '{PackageName}' mod could not be found. Please subscribe to it and add it to the EnabledPackages List!",
new NullReferenceException($"The '{PackageName}' mod could not be found. Please subscribe to it and add it to the EnabledPackages List!"),
createMessageBox: true);
return enabledRegular;
}
enabledRegular = new[] { luaCs }.Concat(enabledRegular).ToImmutableArray();
}
return enabledRegular;
}
private StateMachine<RunState> SetupStateMachine()
{
return new StateMachine<RunState>(false, RunState.Unloaded, onEnter: RunStateUnloaded_OnEnter, null)
.AddState(RunState.LoadedNoExec, onEnter: RunStateLoadedNoExec_OnEnter, null)
.AddState(RunState.Running, onEnter: RunStateRunning_OnEnter, RunStateRunning_OnExit);
// ReSharper disable InconsistentNaming
void RunStateUnloaded_OnEnter(State<RunState> currentState)
{
Logger.LogMessage("LuaCs unloaded state entered");
Logger.LogResults(PackageManagementService.StopRunningPackages());
DisposeLuaCsConfig();
Logger.LogResults(PackageManagementService.UnloadAllPackages());
EventService.Reset();
ConfigService.Reset();
LuaScriptManagementService.Reset();
PackageManagementService.Reset();
NetworkingService.Reset();
Game.Reset();
_servicesProvider.GetService<MainMenuPatch>().Reset();
Logger.LogMessage("Services have been reset");
SubscribeToLuaCsEvents();
CurrentRunState = RunState.Unloaded;
}
void RunStateLoadedNoExec_OnEnter(State<RunState> currentState)
{
Logger.LogMessage("LuaCs no execution state entered");
Logger.LogResults(PackageManagementService.StopRunningPackages());
if (!PackageManagementService.IsAnyPackageLoaded())
{
foreach (var registrationProvider in _servicesProvider.GetAllServices<ISettingsRegistrationProvider>())
{
registrationProvider.RegisterTypeProviders(ConfigService, null);
}
Logger.LogResults(PackageManagementService.LoadPackagesInfo(GetEnabledPackagesList()));
Logger.LogResults(ConfigService.LoadSavedConfigsValues());
LoadLuaCsConfig();
}
CurrentRunState = RunState.LoadedNoExec;
}
void RunStateRunning_OnEnter(State<RunState> currentState)
{
if (!PackageManagementService.IsAnyPackageLoaded())
{
foreach (var registrationProvider in _servicesProvider.GetAllServices<ISettingsRegistrationProvider>())
{
registrationProvider.RegisterTypeProviders(ConfigService, null);
}
Logger.LogResults(PackageManagementService.LoadPackagesInfo(GetEnabledPackagesList()));
Logger.LogResults(ConfigService.LoadSavedConfigsValues());
LoadLuaCsConfig();
}
string csEnabled = IsCsEnabled ? "enabled" : "disabled";
Logger.LogMessage($"LuaCs running state entered. Running under commit {AssemblyInfo.GitRevision}, CSharp is {csEnabled}");
if (!PackageManagementService.IsAnyPackageRunning())
{
Logger.LogResults(PackageManagementService.ExecuteLoadedPackages(GetEnabledPackagesList(), IsCsEnabled));
}
#if CLIENT
// Technically not very accurate, but we want to call after we run mods anyway
if (GameMain.Client != null)
{
EventService.PublishEvent<IEventServerConnected>(static p => p.OnServerConnected());
}
#endif
#if SERVER
GameMain.Server.ServerSettings.LoadClientPermissions();
#endif
CurrentRunState = RunState.Running;
}
void RunStateRunning_OnExit(State<RunState> currentState)
{
EventService.Call("stop");
Logger.LogResults(PackageManagementService.StopRunningPackages());
Logger.LogMessage("LuaCs running state exited");
}
// ReSharper restore InconsistentNaming
}
#endregion
/// <summary>
/// Checks for Cs Execution Policy (ie. prompting the user) and then calls the delegate once completed.
/// </summary>
/// <param name="onReadyToRun"></param>
partial void CheckReadyToRun(Action onReadyToRun);
#region LegacyRedirects
// --- Compatibility
/// <summary>
/// <b>[Obsolete]</b> Legacy support only.
/// </summary>
[Obsolete]
public LuaCsPerformanceCounter PerformanceCounter { get; private set; } = new LuaCsPerformanceCounter();
/// <summary>
/// <b>[Obsolete] Use <see cref="IPluginManagementService"/> instead.</b>
/// </summary>
[Obsolete($"Use {nameof(PluginManagementService)} instead.")]
public IPluginManagementService PluginPackageManager => this.PluginManagementService;
public ILuaCsHook Hook => this.EventService;
public INetworkingService Networking => this.NetworkingService;
public ILuaCsTimer Timer => _servicesProvider.GetService<ILuaCsTimer>();
public DynValue CallLuaFunction(object function, params object[] args) => LuaScriptManagementService.CallFunctionSafe(function, args);
#endregion
public void Dispose()
{
try
{
SetRunState(RunState.Unloaded);
}
catch (Exception e)
{
Logger.LogError(e.Message);
}
try
{
DisposeLuaCsConfig();
PluginManagementService.Dispose();
LuaScriptManagementService.Dispose();
ConfigService.Dispose();
PackageManagementService.Dispose();
// TODO: Add all missing services.
//NetworkingService.Dispose();
EventService.Dispose();
_eventService = null;
_game = null;
PerformanceCounter = null;
_servicesProvider.DisposeAndReset();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
_luaCsSetup = null;
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);
void DisposeLuaCsConfig()
{
_csRunPolicy = null;
_hideUserNamesInLogs = null;
}
}
/// <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
{
/// <summary>
/// No assets are loaded, code execution suspended.
/// </summary>
Unloaded = 0,
/// <summary>
/// Loaded mod configs, settings and assets. No code execution.
/// </summary>
LoadedNoExec = 1,
/// <summary>
/// All assets loaded, code execution is active.
/// </summary>
Running = 2
}
}