diff --git a/TestPlugin/Class1.cs b/TestPlugin/Class1.cs
new file mode 100644
index 0000000..3782fac
--- /dev/null
+++ b/TestPlugin/Class1.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Torch;
+using Torch.API;
+using VRage.Plugins;
+
+namespace TestPlugin
+{
+ public class Plugin : TorchPluginBase
+ {
+ ///
+ public override void Init(ITorchBase torch)
+ {
+ base.Init(torch);
+ Torch.Log.Write($"Plugin init {Name}");
+ }
+
+ ///
+ public override void Update()
+ {
+ Torch.Log.Write($"Plugin update {Name}");
+ }
+
+ ///
+ public override void Unload()
+ {
+ Torch.Log.Write($"Plugin unload {Name}");
+ }
+ }
+}
diff --git a/TestPlugin/Properties/AssemblyInfo.cs b/TestPlugin/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..95ea997
--- /dev/null
+++ b/TestPlugin/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("TestPlugin")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("TestPlugin")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("abd18a6c-f638-44e9-8e55-dedea321c600")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/TestPlugin/TestPlugin.csproj b/TestPlugin/TestPlugin.csproj
new file mode 100644
index 0000000..944eaf4
--- /dev/null
+++ b/TestPlugin/TestPlugin.csproj
@@ -0,0 +1,61 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {ABD18A6C-F638-44E9-8E55-DEDEA321C600}
+ Library
+ Properties
+ TestPlugin
+ TestPlugin
+ v4.6.1
+ 512
+
+
+ true
+ bin\x64\Debug\
+ DEBUG;TRACE
+ full
+ x64
+ prompt
+ MinimumRecommendedRules.ruleset
+
+
+ bin\x64\Release\
+ TRACE
+ true
+ pdbonly
+ x64
+ prompt
+ MinimumRecommendedRules.ruleset
+
+
+
+
+
+
+
+
+
+
+
+ ..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.dll
+
+
+
+
+
+
+
+
+ {FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}
+ Torch.API
+
+
+ {7e01635c-3b67-472e-bcd6-c5539564f214}
+ Torch
+
+
+
+
\ No newline at end of file
diff --git a/Torch/TorchPlugin.cs b/Torch.API/ILogger.cs
similarity index 51%
rename from Torch/TorchPlugin.cs
rename to Torch.API/ILogger.cs
index 0138c1a..ca2e01d 100644
--- a/Torch/TorchPlugin.cs
+++ b/Torch.API/ILogger.cs
@@ -4,10 +4,11 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
-namespace Torch
+namespace Torch.API
{
- public class TorchPlugin
+ public interface ILogger
{
-
+ void Write(string message);
+ void WriteException(Exception e);
}
}
diff --git a/Torch.API/IPluginManager.cs b/Torch.API/IPluginManager.cs
index 142976f..32e30e9 100644
--- a/Torch.API/IPluginManager.cs
+++ b/Torch.API/IPluginManager.cs
@@ -1,21 +1,12 @@
using System;
+using System.Collections.Generic;
using VRage.Collections;
using VRage.Plugins;
namespace Torch.API
{
- public interface IPluginManager
+ public interface IPluginManager : IEnumerable
{
- ListReader Plugins { get; }
- string[] GetPluginFolders();
- string GetPluginName(Type pluginType);
- void LoadAllPlugins();
- void LoadPlugin(IPlugin plugin);
- void LoadPluginFolder(string folderName);
- void ReloadAll();
- void ReloadPlugin(IPlugin plugin, bool forceNonPiston = false);
- bool UnblockDll(string fileName);
- void UnloadPlugin(IPlugin plugin);
}
}
\ No newline at end of file
diff --git a/Torch.API/ITorchBase.cs b/Torch.API/ITorchBase.cs
index 140bd51..dc56bbe 100644
--- a/Torch.API/ITorchBase.cs
+++ b/Torch.API/ITorchBase.cs
@@ -11,8 +11,10 @@ namespace Torch.API
event Action SessionLoaded;
IMultiplayer Multiplayer { get; }
IPluginManager Plugins { get; }
- void DoGameAction(Action action);
- Task DoGameActionAsync(Action action);
+ ILogger Log { get; set; }
+ void Invoke(Action action);
+ void InvokeBlocking(Action action);
+ Task InvokeAsync(Action action);
string[] RunArgs { get; set; }
void Start();
void Stop();
diff --git a/Torch.API/ITorchPlugin.cs b/Torch.API/ITorchPlugin.cs
index 55d7af2..21d7026 100644
--- a/Torch.API/ITorchPlugin.cs
+++ b/Torch.API/ITorchPlugin.cs
@@ -4,13 +4,18 @@ using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
-using VRage.Plugins;
namespace Torch.API
{
- public interface ITorchPlugin : IPlugin
+ public interface ITorchPlugin
{
- void Init(ITorchBase torch);
- void Reload();
+ Guid Id { get; }
+ Version Version { get; }
+ string Name { get; }
+ bool Enabled { get; set; }
+
+ void Init(ITorchBase torchBase);
+ void Update();
+ void Unload();
}
}
diff --git a/Torch.API/PluginAttribute.cs b/Torch.API/PluginAttribute.cs
index e172736..d5b3984 100644
--- a/Torch.API/PluginAttribute.cs
+++ b/Torch.API/PluginAttribute.cs
@@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
-namespace PistonAPI
+namespace Torch.API
{
public class PluginAttribute : Attribute
{
diff --git a/Torch.API/Torch.API.csproj b/Torch.API/Torch.API.csproj
index 840aed1..58ff30e 100644
--- a/Torch.API/Torch.API.csproj
+++ b/Torch.API/Torch.API.csproj
@@ -53,6 +53,7 @@
+
diff --git a/Torch.Client/TorchClient.cs b/Torch.Client/TorchClient.cs
index e0a331b..adb6a03 100644
--- a/Torch.Client/TorchClient.cs
+++ b/Torch.Client/TorchClient.cs
@@ -15,7 +15,7 @@ using VRageRender;
namespace Torch.Client
{
- class TorchClient : TorchBase, ITorchClient
+ public class TorchClient : TorchBase, ITorchClient
{
private MyCommonProgramStartup _startup;
private IMyRender _renderer;
@@ -24,6 +24,8 @@ namespace Torch.Client
public override void Init()
{
+ base.Init();
+
if (!File.Exists("steam_appid.txt"))
{
Directory.SetCurrentDirectory(Path.GetDirectoryName(typeof(VRage.FastResourceLock).Assembly.Location) + "\\..");
@@ -83,11 +85,18 @@ namespace Torch.Client
{
using (var spaceEngineersGame = new SpaceEngineersGame(_services, RunArgs))
{
- Logger.Write("Starting client...");
+ Log.Write("Starting client...");
+ spaceEngineersGame.OnGameLoaded += SpaceEngineersGame_OnGameLoaded;
spaceEngineersGame.Run();
}
}
+ private void SpaceEngineersGame_OnGameLoaded(object sender, EventArgs e)
+ {
+ Log.Write("Loading plugins");
+ Plugins.LoadAllPlugins();
+ }
+
public override void Stop()
{
MySandboxGame.ExitThreadSafe();
diff --git a/Torch.Client/TorchSettingsScreen.cs b/Torch.Client/TorchSettingsScreen.cs
index e2346ed..1a58081 100644
--- a/Torch.Client/TorchSettingsScreen.cs
+++ b/Torch.Client/TorchSettingsScreen.cs
@@ -23,9 +23,16 @@ namespace Torch.Client
{
base.RecreateControls(constructor);
AddCaption(MyStringId.GetOrCompute("Torch Settings"), null, new Vector2(0, 0), 1.2f);
- var pluginList = new MyGuiControlListbox {VisibleRowsCount = 20};
- foreach (var name in TorchBase.Instance.Plugins.GetPluginFolders())
+ var pluginList = new MyGuiControlListbox
+ {
+ VisibleRowsCount = 10,
+ };
+
+ foreach (var plugin in TorchBase.Instance.Plugins.Plugins)
+ {
+ var name = TorchBase.Instance.Plugins.GetPluginName(plugin.GetType());
pluginList.Items.Add(new MyGuiControlListbox.Item(new StringBuilder(name)));
+ }
Controls.Add(pluginList);
}
}
diff --git a/Torch.Server/Program.cs b/Torch.Server/Program.cs
index f07646b..b98f54b 100644
--- a/Torch.Server/Program.cs
+++ b/Torch.Server/Program.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
+using System.ServiceProcess;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -15,38 +16,23 @@ namespace Torch.Server
{
public static class Program
{
- private static readonly ITorchServer _server = new TorchServer();
- private static TorchUI _ui;
+ private static ITorchServer _server;
- [STAThread]
public static void Main(string[] args)
{
- _server.Init();
- _server.RunArgs = new[] { "-console" };
- _ui = new TorchUI(_server);
-
- if (args.Contains("-nogui"))
- _server.Start();
+ if (!Environment.UserInteractive)
+ {
+ using (var service = new TorchService())
+ {
+ ServiceBase.Run(service);
+ }
+ }
else
- StartUI();
-
- if (args.Contains("-autostart") && !_server.IsRunning)
- new Thread(() => _server.Start()).Start();
-
- Dispatcher.Run();
- }
-
- public static void StartUI()
- {
- Thread.CurrentThread.Name = "UI Thread";
- _ui.Show();
- }
-
- public static void FullRestart()
- {
- _server.Stop();
- Process.Start("TorchServer.exe", "-autostart");
- Environment.Exit(1);
+ {
+ _server = new TorchServer();
+ _server.Init();
+ _server.Start();
+ }
}
}
}
diff --git a/Torch.Server/Torch.Server.csproj b/Torch.Server/Torch.Server.csproj
index 694f13f..d11058d 100644
--- a/Torch.Server/Torch.Server.csproj
+++ b/Torch.Server/Torch.Server.csproj
@@ -67,8 +67,10 @@
C:\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\SteamSDK.dll
+
+
@@ -104,6 +106,12 @@
+
+ Component
+
+
+ Component
+
AddWorkshopItemsDialog.xaml
diff --git a/Torch.Server/TorchServer.cs b/Torch.Server/TorchServer.cs
index 08c4a4b..027f6d5 100644
--- a/Torch.Server/TorchServer.cs
+++ b/Torch.Server/TorchServer.cs
@@ -33,11 +33,12 @@ namespace Torch.Server
internal TorchServer()
{
MySession.OnLoading += OnSessionLoading;
- Plugins = new PluginManager();
}
public override void Init()
{
+ base.Init();
+
SpaceEngineersGame.SetupBasicGameInfo();
SpaceEngineersGame.SetupPerGameSettings();
MyPerGameSettings.SendLogToKeen = false;
@@ -78,7 +79,7 @@ namespace Torch.Server
throw new InvalidOperationException("Server is already running.");
IsRunning = true;
- Logger.Write("Starting server.");
+ Log.Write("Starting server.");
if (MySandboxGame.Log.LogEnabled)
MySandboxGame.Log.Close();
@@ -93,13 +94,13 @@ namespace Torch.Server
{
if (Thread.CurrentThread.ManagedThreadId != ServerThread?.ManagedThreadId)
{
- Logger.Write("Requesting server stop.");
+ Log.Write("Requesting server stop.");
MySandboxGame.Static.Invoke(Stop);
_stopHandle.WaitOne();
return;
}
- Logger.Write("Stopping server.");
+ Log.Write("Stopping server.");
MySession.Static.Save();
MySession.Static.Unload();
MySandboxGame.Static.Exit();
@@ -111,7 +112,7 @@ namespace Torch.Server
VRage.Input.MyInput.UnloadData();
CleanupProfilers();
- Logger.Write("Server stopped.");
+ Log.Write("Server stopped.");
_stopHandle.Set();
IsRunning = false;
}
diff --git a/Torch.Server/TorchService.cs b/Torch.Server/TorchService.cs
new file mode 100644
index 0000000..18ca189
--- /dev/null
+++ b/Torch.Server/TorchService.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.ServiceProcess;
+using Torch.API;
+
+namespace Torch.Server
+{
+ class TorchService : ServiceBase
+ {
+ public const string Name = "Torch (SEDS)";
+ private readonly ITorchServer _server = new TorchServer();
+
+ public TorchService()
+ {
+ ServiceName = Name;
+ EventLog.Log = "Application";
+
+ CanHandlePowerEvent = true;
+ CanHandleSessionChangeEvent = false;
+ CanPauseAndContinue = false;
+ CanStop = true;
+ }
+
+ ///
+ protected override void OnStart(string[] args)
+ {
+ base.OnStart(args);
+ _server.Init();
+ _server.Start();
+ }
+
+ ///
+ protected override void OnStop()
+ {
+ _server.Stop();
+ base.OnStop();
+ }
+
+ ///
+ protected override void OnShutdown()
+ {
+ base.OnShutdown();
+ }
+
+ ///
+ protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus)
+ {
+ return base.OnPowerEvent(powerStatus);
+ }
+ }
+}
diff --git a/Torch.Server/TorchServiceInstaller.cs b/Torch.Server/TorchServiceInstaller.cs
new file mode 100644
index 0000000..40ad73c
--- /dev/null
+++ b/Torch.Server/TorchServiceInstaller.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Configuration.Install;
+using System.Linq;
+using System.ServiceProcess;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Torch.Server
+{
+ [RunInstaller(true)]
+ class TorchServiceInstaller : Installer
+ {
+ private ServiceInstaller _serviceInstaller;
+
+ public TorchServiceInstaller()
+ {
+ var serviceProcessInstaller = new ServiceProcessInstaller();
+ _serviceInstaller = new ServiceInstaller();
+
+ serviceProcessInstaller.Account = ServiceAccount.LocalSystem;
+ serviceProcessInstaller.Username = null;
+ serviceProcessInstaller.Password = null;
+
+ _serviceInstaller.DisplayName = "Torch (SEDS)";
+ _serviceInstaller.Description = "Service for Torch (SE Dedicated Server)";
+ _serviceInstaller.StartType = ServiceStartMode.Manual;
+
+ _serviceInstaller.ServiceName = TorchService.Name;
+
+ Installers.Add(serviceProcessInstaller);
+ Installers.Add(_serviceInstaller);
+ }
+
+ ///
+ public override void Install(IDictionary stateSaver)
+ {
+ GetServiceName();
+ base.Install(stateSaver);
+ }
+
+ ///
+ public override void Uninstall(IDictionary savedState)
+ {
+ GetServiceName();
+ base.Uninstall(savedState);
+ }
+
+ private void GetServiceName()
+ {
+ var name = Context.Parameters["name"];
+ if (string.IsNullOrEmpty(name))
+ return;
+
+ _serviceInstaller.DisplayName = name;
+ _serviceInstaller.ServiceName = name;
+ }
+ }
+}
diff --git a/Torch.Server/Views/TorchUI.xaml.cs b/Torch.Server/Views/TorchUI.xaml.cs
index 20aa003..e70e3ef 100644
--- a/Torch.Server/Views/TorchUI.xaml.cs
+++ b/Torch.Server/Views/TorchUI.xaml.cs
@@ -88,7 +88,7 @@ namespace Torch.Server
private void BtnRestart_Click(object sender, RoutedEventArgs e)
{
- Program.FullRestart();
+ //Program.FullRestart();
}
}
}
diff --git a/Torch.sln b/Torch.sln
index b060048..0fa7473 100644
--- a/Torch.sln
+++ b/Torch.sln
@@ -13,32 +13,52 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch.Server", "Torch.Serve
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch.Launcher", "Torch.Launcher\Torch.Launcher.csproj", "{19292801-5B9C-4EE0-961F-0FA37B3A6C3D}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestPlugin", "TestPlugin\TestPlugin.csproj", "{ABD18A6C-F638-44E9-8E55-DEDEA321C600}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
+ Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {7E01635C-3B67-472E-BCD6-C5539564F214}.Debug|Any CPU.ActiveCfg = Debug|x64
{7E01635C-3B67-472E-BCD6-C5539564F214}.Debug|x64.ActiveCfg = Debug|x64
{7E01635C-3B67-472E-BCD6-C5539564F214}.Debug|x64.Build.0 = Debug|x64
+ {7E01635C-3B67-472E-BCD6-C5539564F214}.Release|Any CPU.ActiveCfg = Release|x64
{7E01635C-3B67-472E-BCD6-C5539564F214}.Release|x64.ActiveCfg = Release|x64
{7E01635C-3B67-472E-BCD6-C5539564F214}.Release|x64.Build.0 = Release|x64
+ {FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Debug|Any CPU.ActiveCfg = Debug|x64
{FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Debug|x64.ActiveCfg = Debug|x64
{FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Debug|x64.Build.0 = Debug|x64
+ {FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Release|Any CPU.ActiveCfg = Release|x64
{FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Release|x64.ActiveCfg = Release|x64
{FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Release|x64.Build.0 = Release|x64
+ {E36DF745-260B-4956-A2E8-09F08B2E7161}.Debug|Any CPU.ActiveCfg = Debug|x64
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Debug|x64.ActiveCfg = Debug|x64
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Debug|x64.Build.0 = Debug|x64
+ {E36DF745-260B-4956-A2E8-09F08B2E7161}.Release|Any CPU.ActiveCfg = Release|x64
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Release|x64.ActiveCfg = Release|x64
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Release|x64.Build.0 = Release|x64
+ {CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Debug|Any CPU.ActiveCfg = Debug|x64
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Debug|x64.ActiveCfg = Debug|x64
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Debug|x64.Build.0 = Debug|x64
+ {CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Release|Any CPU.ActiveCfg = Release|x64
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Release|x64.ActiveCfg = Release|x64
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Release|x64.Build.0 = Release|x64
+ {19292801-5B9C-4EE0-961F-0FA37B3A6C3D}.Debug|Any CPU.ActiveCfg = Debug|x64
{19292801-5B9C-4EE0-961F-0FA37B3A6C3D}.Debug|x64.ActiveCfg = Debug|x64
{19292801-5B9C-4EE0-961F-0FA37B3A6C3D}.Debug|x64.Build.0 = Debug|x64
+ {19292801-5B9C-4EE0-961F-0FA37B3A6C3D}.Release|Any CPU.ActiveCfg = Release|x64
{19292801-5B9C-4EE0-961F-0FA37B3A6C3D}.Release|x64.ActiveCfg = Release|x64
{19292801-5B9C-4EE0-961F-0FA37B3A6C3D}.Release|x64.Build.0 = Release|x64
+ {ABD18A6C-F638-44E9-8E55-DEDEA321C600}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {ABD18A6C-F638-44E9-8E55-DEDEA321C600}.Debug|x64.ActiveCfg = Debug|x64
+ {ABD18A6C-F638-44E9-8E55-DEDEA321C600}.Debug|x64.Build.0 = Debug|x64
+ {ABD18A6C-F638-44E9-8E55-DEDEA321C600}.Release|Any CPU.ActiveCfg = Release|x64
+ {ABD18A6C-F638-44E9-8E55-DEDEA321C600}.Release|x64.ActiveCfg = Release|x64
+ {ABD18A6C-F638-44E9-8E55-DEDEA321C600}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Torch/Commands/CategoryAttribute.cs b/Torch/Commands/CategoryAttribute.cs
new file mode 100644
index 0000000..eea96f6
--- /dev/null
+++ b/Torch/Commands/CategoryAttribute.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Torch.Commands
+{
+ public class CategoryAttribute : Attribute
+ {
+ public string[] Path { get; }
+
+ ///
+ /// Specifies where to add the class's commands in the command tree.
+ ///
+ /// Command path, e.g. "/admin config" -> "admin, config"
+ public CategoryAttribute(params string[] path)
+ {
+ Path = path;
+ }
+ }
+}
diff --git a/Torch/Commands/ChatCommandAttribute.cs b/Torch/Commands/ChatCommandAttribute.cs
deleted file mode 100644
index b10f5eb..0000000
--- a/Torch/Commands/ChatCommandAttribute.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System;
-
-namespace Torch.Commands
-{
- public class ChatCommandAttribute : Attribute
- {
- public string Name { get; }
- public ChatCommandAttribute(string name)
- {
- Name = name;
- }
- }
-}
\ No newline at end of file
diff --git a/Torch/Commands/ChatCommand.cs b/Torch/Commands/Command.cs
similarity index 58%
rename from Torch/Commands/ChatCommand.cs
rename to Torch/Commands/Command.cs
index 81b16c3..7f196fc 100644
--- a/Torch/Commands/ChatCommand.cs
+++ b/Torch/Commands/Command.cs
@@ -2,10 +2,11 @@
namespace Torch.Commands
{
- public class ChatCommand
+ public class Command
{
- public ChatCommandModule Module;
+ public CommandModule Module;
public string Name;
+ public string[] Path;
public Action Invoke;
}
}
\ No newline at end of file
diff --git a/Torch/Commands/CommandAttribute.cs b/Torch/Commands/CommandAttribute.cs
new file mode 100644
index 0000000..5c982cd
--- /dev/null
+++ b/Torch/Commands/CommandAttribute.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace Torch.Commands
+{
+ public class CommandAttribute : Attribute
+ {
+ public string Name { get; }
+
+ ///
+ /// Specifies a command to add to the command tree.
+ ///
+ ///
+ public CommandAttribute(string name)
+ {
+ Name = name;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Torch/Commands/CommandContext.cs b/Torch/Commands/CommandContext.cs
index f711afd..23ae0cb 100644
--- a/Torch/Commands/CommandContext.cs
+++ b/Torch/Commands/CommandContext.cs
@@ -5,22 +5,26 @@ namespace Torch.Commands
{
public struct CommandContext
{
- public string Argument;
+ public string[] Args;
public ulong SteamId;
///
/// Splits the argument by single words and quoted blocks.
///
///
- public string[] SplitArgument()
+ public CommandContext(ulong steamId, params string[] args)
{
- var split = Regex.Split(Argument, "(\"[^\"]+\"|\\S+)");
+ SteamId = steamId;
+ Args = args;
+
+ /*
+ var split = Regex.Split(Args, "(\"[^\"]+\"|\\S+)");
for (var i = 0; i < split.Length; i++)
{
split[i] = Regex.Replace(split[i], "\"", "");
}
- return split;
+ return split;*/
}
}
}
\ No newline at end of file
diff --git a/Torch/Commands/ChatCommandModule.cs b/Torch/Commands/CommandModule.cs
similarity index 81%
rename from Torch/Commands/ChatCommandModule.cs
rename to Torch/Commands/CommandModule.cs
index 53f9b94..2af21ac 100644
--- a/Torch/Commands/ChatCommandModule.cs
+++ b/Torch/Commands/CommandModule.cs
@@ -2,7 +2,7 @@
namespace Torch.Commands
{
- public class ChatCommandModule
+ public class CommandModule
{
public ITorchPlugin Plugin { get; set; }
public ITorchBase Server { get; set; }
diff --git a/Torch/Commands/CommandSystem.cs b/Torch/Commands/CommandSystem.cs
index 5891642..25dead6 100644
--- a/Torch/Commands/CommandSystem.cs
+++ b/Torch/Commands/CommandSystem.cs
@@ -10,7 +10,7 @@ namespace Torch.Commands
public ITorchBase Server { get; }
public char Prefix { get; set; }
- public Dictionary Commands { get; } = new Dictionary();
+ public Dictionary Commands { get; } = new Dictionary();
public CommandSystem(ITorchBase server, char prefix = '/')
{
@@ -28,15 +28,15 @@ namespace Torch.Commands
var assembly = plugin.GetType().Assembly;
foreach (var type in assembly.ExportedTypes)
{
- if (!type.IsSubclassOf(typeof(ChatCommandModule)))
+ if (!type.IsSubclassOf(typeof(CommandModule)))
continue;
- var module = (ChatCommandModule)Activator.CreateInstance(type);
+ var module = (CommandModule)Activator.CreateInstance(type);
module.Server = Server;
module.Plugin = plugin;
foreach (var method in type.GetMethods())
{
- var commandAttrib = method.GetCustomAttribute();
+ var commandAttrib = method.GetCustomAttribute();
if (commandAttrib == null)
continue;
@@ -54,7 +54,7 @@ namespace Torch.Commands
continue;
}
- var command = new ChatCommand
+ var command = new Command
{
Module = module,
Name = commandAttrib.Name,
@@ -82,7 +82,7 @@ namespace Torch.Commands
var context = new CommandContext
{
- Argument = arg,
+ Args = arg,
SteamId = steamId
};
diff --git a/Torch/Commands/CommandTree.cs b/Torch/Commands/CommandTree.cs
new file mode 100644
index 0000000..c7f72ee
--- /dev/null
+++ b/Torch/Commands/CommandTree.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using VRage.Library.Collections;
+
+namespace Torch.Commands
+{
+ public class CommandTree
+ {
+ private Dictionary RootNodes { get; } = new Dictionary();
+
+ public bool AddCommand(Command command)
+ {
+ var root = command.Path.First();
+ var node = RootNodes.ContainsKey(root) ? RootNodes[root] : new Node(root);
+
+ for (var i = 1; i < command.Path.Length; i++)
+ {
+ var current = command.Path[i];
+ if (node.Nodes.ContainsKey(current))
+ {
+ node = node.Nodes[current];
+ continue;
+ }
+
+ var newNode = new Node(current);
+ node.AddNode(newNode);
+ node = newNode;
+ }
+
+ if (!node.IsEmpty)
+ return false;
+
+ node.Command = command;
+ return true;
+ }
+
+ public InvokeResult Invoke(ulong steamId, string[] command)
+ {
+ var root = command.First();
+ if (!RootNodes.ContainsKey(root))
+ return InvokeResult.NoCommand;
+
+ var node = RootNodes[root];
+ var args = new string[0];
+ for (var i = 1; i < command.Length; i++)
+ {
+ var current = command[i];
+ if (node.Nodes.ContainsKey(current))
+ {
+ node = node.Nodes[current];
+ continue;
+ }
+
+ args = new string[command.Length - i];
+ Array.Copy(command, i, args, 0, args.Length);
+ }
+
+ if (!node.IsCommand)
+ return InvokeResult.NoCommand;
+
+ //check permission here
+
+ var context = new CommandContext(steamId, args);
+ node.Command.Invoke(context);
+ return InvokeResult.Success;
+ }
+
+ private class Node
+ {
+ public Dictionary Nodes { get; } = new Dictionary();
+
+ public string Name { get; }
+ public Command Command { get; set; }
+ public bool IsCommand => Command != null;
+ public bool IsEmpty => !IsCommand && Nodes.Count == 0;
+
+ public Node(string name, Command command = null)
+ {
+ Name = name;
+ Command = command;
+ }
+
+ public void AddNode(Node node)
+ {
+ Nodes.Add(node.Name, node);
+ }
+ }
+
+ public enum InvokeResult
+ {
+ Success,
+ NoCommand,
+ NoPermission
+ }
+ }
+}
diff --git a/Torch/Logger.cs b/Torch/Logger.cs
index 292695a..6b435c0 100644
--- a/Torch/Logger.cs
+++ b/Torch/Logger.cs
@@ -1,21 +1,64 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
+using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Sandbox;
+using Torch.API;
using VRage.Utils;
namespace Torch
{
- public static class Logger
+ public class Logger : ILogger
{
- public const string Prefix = "[TORCH]";
+ public string Prefix = "[TORCH]";
+ private StringBuilder _sb = new StringBuilder();
+ private string _path;
- public static void Write(string message)
+ public Logger(string path)
{
- var msg = $"{Prefix}: {message}";
- MyLog.Default.WriteLineAndConsole(msg);
+ _path = path;
+ if (File.Exists(_path))
+ File.Delete(_path);
+ }
+
+ public void Write(string message)
+ {
+ var msg = $"{GetInfo()}: {message}";
+ Console.WriteLine(msg);
+ _sb.AppendLine(msg);
+ }
+
+ public void WriteExceptionAndThrow(Exception e)
+ {
+ WriteException(e);
+ throw e;
+ }
+
+ public void WriteException(Exception e)
+ {
+ _sb.AppendLine($"{GetInfo()}: {e.Message}");
+
+ foreach (var line in e.StackTrace.Split('\n'))
+ _sb.AppendLine($"\t{line}");
+ }
+
+ private string GetInfo()
+ {
+ return $"{DateTime.Now.ToShortDateString()} {DateTime.Now.ToShortTimeString()} {Prefix}";
+ }
+
+ public void Flush()
+ {
+ File.AppendAllText(_path, _sb.ToString());
+ _sb.Clear();
+ }
+
+ ~Logger()
+ {
+ Flush();
}
}
}
diff --git a/Torch/MultiplayerManager.cs b/Torch/MultiplayerManager.cs
index f7517b8..7d345f1 100644
--- a/Torch/MultiplayerManager.cs
+++ b/Torch/MultiplayerManager.cs
@@ -50,11 +50,11 @@ namespace Torch
_torch.SessionLoaded += OnSessionLoaded;
}
- public void KickPlayer(ulong steamId) => _torch.DoGameActionAsync(() => MyMultiplayer.Static.KickClient(steamId));
+ public void KickPlayer(ulong steamId) => _torch.InvokeAsync(() => MyMultiplayer.Static.KickClient(steamId));
public void BanPlayer(ulong steamId, bool banned = true)
{
- _torch.DoGameActionAsync(() =>
+ _torch.InvokeAsync(() =>
{
MyMultiplayer.Static.BanClient(steamId, banned);
if (_gameOwnerIds.ContainsKey(steamId))
@@ -118,7 +118,7 @@ namespace Torch
player.SetConnectionState(ConnectionState.Connected);
}
- Logger.Write($"{player.Name} connected.");
+ _torch.Log.Write($"{player.Name} connected.");
PlayerJoined?.Invoke(player);
}
@@ -133,7 +133,7 @@ namespace Torch
return;
var player = Players[steamId];
- Logger.Write($"{player.Name} disconnected ({(ConnectionState)stateChange}).");
+ _torch.Log.Write($"{player.Name} disconnected ({(ConnectionState)stateChange}).");
player.SetConnectionState((ConnectionState)stateChange);
PlayerLeft?.Invoke(player);
}
@@ -179,16 +179,16 @@ namespace Torch
//Largely copied from SE
private void ValidateAuthTicketResponse(ulong steamID, AuthSessionResponseEnum response, ulong ownerSteamID)
{
- Logger.Write($"Server ValidateAuthTicketResponse ({response}), owner: {ownerSteamID}");
+ _torch.Log.Write($"Server ValidateAuthTicketResponse ({response}), owner: {ownerSteamID}");
if (steamID != ownerSteamID)
{
- Logger.Write($"User {steamID} is using a game owned by {ownerSteamID}. Tracking...");
+ _torch.Log.Write($"User {steamID} is using a game owned by {ownerSteamID}. Tracking...");
_gameOwnerIds[steamID] = ownerSteamID;
if (MySandboxGame.ConfigDedicated.Banned.Contains(ownerSteamID))
{
- Logger.Write($"Game owner {ownerSteamID} is banned. Banning and rejecting client {steamID}...");
+ _torch.Log.Write($"Game owner {ownerSteamID} is banned. Banning and rejecting client {steamID}...");
UserRejected(steamID, JoinResult.BannedByAdmins);
BanPlayer(steamID, true);
}
diff --git a/Torch/PluginManager.cs b/Torch/PluginManager.cs
index 0834f2c..a55decb 100644
--- a/Torch/PluginManager.cs
+++ b/Torch/PluginManager.cs
@@ -1,12 +1,14 @@
using System;
+using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
-using PistonAPI;
using Sandbox;
using Torch.API;
using VRage.Plugins;
@@ -17,142 +19,60 @@ namespace Torch
{
public class PluginManager : IPluginManager
{
- //TODO: Disable reloading if the plugin has static elements because they prevent a full reload.
-
- public ListReader Plugins => MyPlugins.Plugins;
-
- private List _plugins;
+ private readonly ITorchBase _torch;
public const string PluginDir = "Plugins";
- public PluginManager()
+ private readonly List _plugins = new List();
+
+ public PluginManager(ITorchBase torch)
{
+ _torch = torch;
+
if (!Directory.Exists(PluginDir))
Directory.CreateDirectory(PluginDir);
-
- GetPluginList();
- }
-
- ///
- /// Get a reference to the internal VRage plugin list.
- ///
- private void GetPluginList()
- {
- _plugins = typeof(MyPlugins).GetField("m_plugins", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null) as List;
- }
-
- ///
- /// Get a plugin's name from its or its type name.
- ///
- public string GetPluginName(Type pluginType)
- {
- var attr = pluginType.GetCustomAttribute();
- return attr?.Name ?? pluginType.Name;
}
///
/// Load all plugins in the folder.
///
- public void LoadAllPlugins()
+ public void LoadPlugins()
{
- var pluginFolders = GetPluginFolders();
- foreach (var folder in pluginFolders)
+ var pluginsPath = Path.Combine(Directory.GetCurrentDirectory(), PluginDir);
+ var dlls = Directory.GetFiles(pluginsPath, "*.dll", SearchOption.AllDirectories);
+ foreach (var dllPath in dlls)
{
- LoadPluginFolder(folder);
- }
- }
+ UnblockDll(dllPath);
+ var asm = Assembly.LoadFrom(dllPath);
- ///
- /// Load a plugin into the game.
- ///
- ///
- public void LoadPlugin(IPlugin plugin)
- {
- Logger.Write($"Loading plugin: {GetPluginName(plugin.GetType())}");
- plugin.Init(MySandboxGame.Static);
- _plugins.Add(plugin);
- }
-
- ///
- /// Get the names of all the subfolders in the Plugins directory.
- ///
- ///
- public string[] GetPluginFolders()
- {
- var dirs = Directory.GetDirectories(PluginDir);
- for (var i = 0; i < dirs.Length; i++)
- {
- dirs[i] = dirs[i].Substring(PluginDir.Length + 1);
- }
-
- return dirs;
- }
-
- ///
- /// Load all plugins in the specified folder.
- ///
- /// Folder in the directory
- public void LoadPluginFolder(string folderName)
- {
- var relativeDir = Path.Combine(PluginDir, folderName);
- if (!Directory.Exists(relativeDir))
- {
- Logger.Write($"Plugin {folderName} does not exist in the Plugins folder.");
- return;
- }
-
- var fileNames = Directory.GetFiles(relativeDir, "*.dll");
-
- foreach (var fileName in fileNames)
- {
- var fullPath = Path.Combine(Directory.GetCurrentDirectory(), fileName);
- UnblockDll(fullPath);
- var asm = Assembly.LoadFrom(fullPath);
-
- foreach (var type in asm.GetTypes())
+ foreach (var type in asm.GetExportedTypes())
{
- if (type.GetInterfaces().Contains(typeof(IPlugin)))
- {
- var inst = (IPlugin)Activator.CreateInstance(type);
- MySandboxGame.Static.Invoke(() => LoadPlugin(inst));
- }
+ if (type.IsSubclassOf(typeof(TorchPluginBase)))
+ _plugins.Add((TorchPluginBase)Activator.CreateInstance(type));
}
}
}
- ///
- /// Unload a plugin from the game.
- ///
- public void UnloadPlugin(IPlugin plugin)
+ public async void ReloadPluginAsync(ITorchPlugin plugin)
{
- _plugins.Remove(plugin);
- plugin.Dispose();
+ var p = plugin as TorchPluginBase;
+ if (p == null)
+ return;
+
+ var newPlugin = (TorchPluginBase)Activator.CreateInstance(p.GetType());
+ _plugins.Add(newPlugin);
+
+ await p.StopAsync();
+ _plugins.Remove(p);
+
+ newPlugin.Run(_torch, true);
}
- ///
- /// Reload a plugin.
- ///
- ///
- /// Reload a non-Piston plugin
- public void ReloadPlugin(IPlugin plugin, bool forceNonPiston = false)
- {
- var p = plugin as ITorchPlugin;
- if (p == null && forceNonPiston)
- {
- plugin.Dispose();
- plugin.Init(MySandboxGame.Static);
- }
- else
- {
- p?.Reload();
- }
- }
-
- public void ReloadAll()
+ public void StartEnabledPlugins()
{
foreach (var plugin in _plugins)
{
- var p = plugin as ITorchPlugin;
- p?.Reload();
+ if (plugin.Enabled)
+ plugin.Run(_torch);
}
}
@@ -161,6 +81,16 @@ namespace Torch
return DeleteFile(fileName + ":Zone.Identifier");
}
+ public IEnumerator GetEnumerator()
+ {
+ return _plugins.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DeleteFile(string name);
diff --git a/Torch/SteamHelper.cs b/Torch/SteamHelper.cs
index 13496d0..ab78053 100644
--- a/Torch/SteamHelper.cs
+++ b/Torch/SteamHelper.cs
@@ -8,6 +8,7 @@ using Sandbox;
using Sandbox.Engine.Networking;
using Sandbox.Engine.Platform;
using SteamSDK;
+using Torch.API;
using VRage.Game;
namespace Torch
@@ -15,9 +16,12 @@ namespace Torch
public static class SteamHelper
{
private static Thread _callbackThread;
+ private static ILogger _log;
- public static void Init()
+ public static void Init(ILogger log)
{
+ _log = log;
+
_callbackThread = new Thread(() =>
{
while (true)
@@ -52,7 +56,7 @@ namespace Torch
}
else
{
- Logger.Write($"Failed to get item info for {itemId}");
+ _log.Write($"Failed to get item info for {itemId}");
}
mre.Set();
@@ -68,20 +72,19 @@ namespace Torch
public static SteamUGCDetails GetItemDetails(ulong itemId)
{
SteamUGCDetails details = default(SteamUGCDetails);
- using (var mre = new ManualResetEvent(false))
+ using (var re = new AutoResetEvent(false))
{
SteamAPI.Instance.UGC.RequestUGCDetails(itemId, 0, (b, result) =>
{
if (!b && result.Details.Result == Result.OK)
details = result.Details;
else
- Logger.Write($"Failed to get item details for {itemId}");
+ _log.Write($"Failed to get item details for {itemId}");
- mre.Set();
+ re.Set();
});
- mre.WaitOne();
- mre.Reset();
+ re.WaitOne();
}
return details;
diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj
index 9b6085b..25dab89 100644
--- a/Torch/Torch.csproj
+++ b/Torch/Torch.csproj
@@ -121,18 +121,20 @@
-
-
-
+
+
+
+
+
-
+
diff --git a/Torch/TorchBase.cs b/Torch/TorchBase.cs
index b0cce23..b98f102 100644
--- a/Torch/TorchBase.cs
+++ b/Torch/TorchBase.cs
@@ -1,25 +1,32 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Sandbox;
using Torch.API;
+using VRage.Scripting;
namespace Torch
{
public abstract class TorchBase : ITorchBase
{
+ ///
+ /// Dirty hack because *keen*
+ /// Use only if absolutely necessary.
+ ///
public static ITorchBase Instance { get; private set; }
-
public string[] RunArgs { get; set; }
public IPluginManager Plugins { get; protected set; }
public IMultiplayer Multiplayer { get; protected set; }
-
+ public ILogger Log { get; set; }
public event Action SessionLoaded;
+ private bool _init;
+
protected void InvokeSessionLoaded()
{
SessionLoaded?.Invoke();
@@ -27,13 +34,22 @@ namespace Torch
protected TorchBase()
{
- RunArgs = new string[0];
+ if (Instance != null)
+ throw new InvalidOperationException("A TorchBase instance already exists.");
+
Instance = this;
- Plugins = new PluginManager();
+
+ Log = new Logger(Path.Combine(Directory.GetCurrentDirectory(), "TorchLog.log"));
+ RunArgs = new string[0];
+ Plugins = new PluginManager(this);
Multiplayer = new MultiplayerManager(this);
}
- public void DoGameAction(Action action)
+ ///
+ /// Invokes an action on the game thread.
+ ///
+ ///
+ public void Invoke(Action action)
{
MySandboxGame.Static.Invoke(action);
}
@@ -42,43 +58,67 @@ namespace Torch
/// Invokes an action on the game thread asynchronously.
///
///
- public Task DoGameActionAsync(Action action)
+ public Task InvokeAsync(Action action)
{
if (Thread.CurrentThread == MySandboxGame.Static.UpdateThread)
{
- Debug.Assert(false, $"{nameof(DoGameActionAsync)} should not be called on the game thread.");
+ Debug.Assert(false, $"{nameof(InvokeAsync)} should not be called on the game thread.");
action?.Invoke();
return Task.CompletedTask;
}
- return Task.Run(() =>
+ 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)
{
- var e = new AutoResetEvent(false);
+ Debug.Assert(false, $"{nameof(InvokeBlocking)} should not be called on the game thread.");
+ action.Invoke();
+ return;
+ }
- MySandboxGame.Static.Invoke(() =>
+ var e = new AutoResetEvent(false);
+
+ MySandboxGame.Static.Invoke(() =>
+ {
+ try
{
- try
- {
- action?.Invoke();
- }
- catch (Exception ex)
- {
- //log
- }
- finally
- {
- e.Set();
- }
- });
-
- if(!e.WaitOne(60000))
- throw new TimeoutException("The game action timed out.");
-
+ action.Invoke();
+ }
+ catch (Exception ex)
+ {
+ //log
+ }
+ finally
+ {
+ e.Set();
+ }
});
+
+ if (!e.WaitOne(60000))
+ throw new TimeoutException("The game action timed out.");
+ }
+
+ public virtual void Init()
+ {
+ Debug.Assert(!_init, "Torch instance is already initialized.");
+
+ _init = true;
+ MyScriptCompiler.Static.AddConditionalCompilationSymbols("TORCH");
+ MyScriptCompiler.Static.AddReferencedAssemblies(typeof(ITorchBase).Assembly.Location);
+ MyScriptCompiler.Static.AddReferencedAssemblies(typeof(TorchBase).Assembly.Location);
}
public abstract void Start();
public abstract void Stop();
- public abstract void Init();
}
}
diff --git a/Torch/TorchPluginBase.cs b/Torch/TorchPluginBase.cs
new file mode 100644
index 0000000..97540fa
--- /dev/null
+++ b/Torch/TorchPluginBase.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Torch.API;
+
+namespace Torch
+{
+ public abstract class TorchPluginBase : ITorchPlugin
+ {
+ public Guid Id { get; }
+ public Version Version { get; }
+ public string Name { get; }
+ public bool Enabled { get; set; } = true;
+ public bool IsRunning => !Loop.IsCompleted;
+ public ITorchBase Torch { get; private set; }
+
+ protected TorchPluginBase()
+ {
+ var asm = Assembly.GetCallingAssembly();
+
+ var id = asm.GetCustomAttribute()?.Value;
+ if (id == null)
+ throw new InvalidOperationException($"{asm.FullName} has no Guid attribute.");
+
+ Id = new Guid(id);
+
+ var ver = asm.GetCustomAttribute()?.Version;
+ if (ver == null)
+ throw new InvalidOperationException($"{asm.FullName} has no AssemblyVersion attribute.");
+
+ Version = new Version(ver);
+
+ var name = asm.GetCustomAttribute()?.Title;
+ if (name == null)
+ throw new InvalidOperationException($"{asm.FullName} has no AssemblyTitle attribute.");
+
+ Name = name;
+ }
+
+ public virtual void Init(ITorchBase torch)
+ {
+ Torch = torch;
+ }
+
+ public abstract void Update();
+ public abstract void Unload();
+
+ #region Internal Loop Code
+
+ internal CancellationTokenSource ctSource = new CancellationTokenSource();
+
+ internal Task Loop { get; private set; } = Task.CompletedTask;
+ private readonly TimeSpan _loopInterval = TimeSpan.FromSeconds(1d / 60d);
+ private bool _runLoop;
+ internal Task Run(ITorchBase torch, bool enable = false)
+ {
+ if (IsRunning)
+ throw new InvalidOperationException($"Plugin {Name} is already running.");
+
+ if (!Enabled)
+ return Loop = Task.CompletedTask;
+
+ _runLoop = true;
+ return Loop = Task.Run(() =>
+ {
+ try
+ {
+ Init(torch);
+
+ while (Enabled && !ctSource.Token.IsCancellationRequested)
+ {
+ ctSource.Token.ThrowIfCancellationRequested();
+ var ts = Stopwatch.GetTimestamp();
+ Update();
+ var time = TimeSpan.FromTicks(Stopwatch.GetTimestamp() - ts);
+
+ if (time < _loopInterval)
+ Task.Delay(_loopInterval - time);
+ }
+
+ Unload();
+ }
+ catch (Exception e)
+ {
+ torch.Log.Write($"Plugin {Name} threw an exception.");
+ torch.Log.WriteException(e);
+ throw;
+ }
+ });
+ }
+
+ internal async Task StopAsync()
+ {
+ ctSource.Cancel();
+ await Loop;
+ }
+
+ #endregion
+ }
+}