using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; using NLog; using Sandbox; using Sandbox.Game; using Sandbox.Game.Multiplayer; using Sandbox.Game.Screens.Helpers; using Sandbox.Game.World; using Sandbox.ModAPI; using SpaceEngineers.Game; using Torch.API; using Torch.API.Managers; using Torch.API.ModAPI; using Torch.Commands; using Torch.Managers; using VRage.Collections; using VRage.FileSystem; using VRage.Game.ObjectBuilder; using VRage.ObjectBuilders; using VRage.Plugins; using VRage.Scripting; using VRage.Utils; namespace Torch { /// /// Base class for code shared between the Torch client and server. /// public abstract class TorchBase : ViewModel, ITorchBase, IPlugin { /// /// Hack because *keen*. /// Use only if necessary, prefer dependency injection. /// public static ITorchBase Instance { get; private set; } /// public ITorchConfig Config { get; protected set; } /// public Version TorchVersion { get; protected set; } /// public Version GameVersion { get; private set; } /// public string[] RunArgs { get; set; } /// public IPluginManager Plugins { get; protected set; } /// public IMultiplayerManager Multiplayer { get; protected set; } /// public EntityManager Entities { get; protected set; } /// public INetworkManager Network { get; protected set; } /// public CommandManager Commands { get; protected set; } /// public event Action SessionLoading; /// public event Action SessionLoaded; /// public event Action SessionUnloading; /// public event Action SessionUnloaded; /// /// Common log for the Torch instance. /// protected static Logger Log { get; } = LogManager.GetLogger("Torch"); private readonly DependencyManager _managers; private bool _init; /// /// /// /// Thrown if a TorchBase instance already exists. protected TorchBase() { if (Instance != null) throw new InvalidOperationException("A TorchBase instance already exists."); Instance = this; TorchVersion = Assembly.GetExecutingAssembly().GetName().Version; RunArgs = new string[0]; _managers = new DependencyManager(); Plugins = new PluginManager(this); Multiplayer = new MultiplayerManager(this); Entities = new EntityManager(this); Network = new NetworkManager(this); Commands = new CommandManager(this); _managers.AddManager(new FilesystemManager(this)); _managers.AddManager(new UpdateManager(this)); _managers.AddManager(Network); _managers.AddManager(Commands); _managers.AddManager(Plugins); _managers.AddManager(Multiplayer); _managers.AddManager(Entities); _managers.AddManager(new ChatManager(this)); TorchAPI.Instance = this; } /// public T GetManager() where T : class, IManager { return _managers.GetManager(); } /// public bool AddManager(T manager) where T : class, IManager { return _managers.AddManager(manager); } public bool IsOnGameThread() { return Thread.CurrentThread.ManagedThreadId == MySandboxGame.Static.UpdateThread.ManagedThreadId; } public Task SaveGameAsync(Action callback) { Log.Info("Saving game"); if (!MySandboxGame.IsGameReady) { callback?.Invoke(SaveGameStatus.GameNotReady); } else if(MyAsyncSaving.InProgress) { callback?.Invoke(SaveGameStatus.SaveInProgress); } else { var e = new AutoResetEvent(false); MyAsyncSaving.Start(() => e.Set()); return Task.Run(() => { callback?.Invoke(e.WaitOne(5000) ? SaveGameStatus.Success : SaveGameStatus.TimedOut); e.Dispose(); }); } return Task.CompletedTask; } #region Game Actions /// /// Invokes an action on the game thread. /// /// public void Invoke(Action action) { MySandboxGame.Static.Invoke(action); } /// /// Invokes an action on the game thread asynchronously. /// /// public Task InvokeAsync(Action action) { if (Thread.CurrentThread == MySandboxGame.Static.UpdateThread) { Debug.Assert(false, $"{nameof(InvokeAsync)} should not be called on the game thread."); action?.Invoke(); return Task.CompletedTask; } return Task.Run(() => InvokeBlocking(action)); } /// /// Invokes an action on the game thread and blocks until it is completed. /// /// public void InvokeBlocking(Action action) { if (action == null) return; if (Thread.CurrentThread == MySandboxGame.Static.UpdateThread) { Debug.Assert(false, $"{nameof(InvokeBlocking)} should not be called on the game thread."); action.Invoke(); return; } var e = new AutoResetEvent(false); MySandboxGame.Static.Invoke(() => { try { action.Invoke(); } finally { e.Set(); } }); if (!e.WaitOne(60000)) throw new TimeoutException("The game action timed out."); } #endregion /// public virtual void Init() { Debug.Assert(!_init, "Torch instance is already initialized."); SpaceEngineersGame.SetupBasicGameInfo(); SpaceEngineersGame.SetupPerGameSettings(); TorchVersion = Assembly.GetEntryAssembly().GetName().Version; GameVersion = new Version(new MyVersion(MyPerGameSettings.BasicGameInfo.GameVersion.Value).FormattedText.ToString().Replace("_", ".")); var verInfo = $"{Config.InstanceName} - Torch {TorchVersion}, SE {GameVersion}"; try { Console.Title = verInfo; } catch { ///Running as service } #if DEBUG Log.Info("DEBUG"); #else Log.Info("RELEASE"); #endif Log.Info(verInfo); Log.Info($"Executing assembly: {Assembly.GetEntryAssembly().FullName}"); Log.Info($"Executing directory: {AppDomain.CurrentDomain.BaseDirectory}"); MySession.OnLoading += OnSessionLoading; MySession.AfterLoading += OnSessionLoaded; MySession.OnUnloading += OnSessionUnloading; MySession.OnUnloaded += OnSessionUnloaded; RegisterVRagePlugin(); _managers.Init(); _init = true; } private void OnSessionLoading() { Log.Debug("Session loading"); SessionLoading?.Invoke(); } private void OnSessionLoaded() { Log.Debug("Session loaded"); SessionLoaded?.Invoke(); } private void OnSessionUnloading() { Log.Debug("Session unloading"); SessionUnloading?.Invoke(); } private void OnSessionUnloaded() { Log.Debug("Session unloaded"); SessionUnloaded?.Invoke(); } /// /// Hook into the VRage plugin system for updates. /// private void RegisterVRagePlugin() { var fieldName = "m_plugins"; var pluginList = typeof(MyPlugins).GetField(fieldName, BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null) as List; if (pluginList == null) throw new TypeLoadException($"{fieldName} field not found in {nameof(MyPlugins)}"); pluginList.Add(this); } /// public virtual Task Save(long callerId) { return Task.CompletedTask; } /// public virtual void Start() { } /// public virtual void Stop() { } /// public virtual void Restart() { } /// public virtual void Dispose() { Plugins.DisposePlugins(); } /// public virtual void Init(object gameInstance) { } /// public virtual void Update() { Plugins.UpdatePlugins(); } } }