Rewrite plugin system to update plugins in parallel after each game tick.
This commit is contained in:
@@ -7,6 +7,7 @@ namespace Torch.API
|
||||
{
|
||||
public interface IPluginManager : IEnumerable<ITorchPlugin>
|
||||
{
|
||||
|
||||
void UpdatePlugins();
|
||||
void LoadPlugins();
|
||||
}
|
||||
}
|
@@ -12,7 +12,6 @@ namespace Torch.API
|
||||
Guid Id { get; }
|
||||
Version Version { get; }
|
||||
string Name { get; }
|
||||
bool Enabled { get; set; }
|
||||
|
||||
void Init(ITorchBase torchBase);
|
||||
void Update();
|
||||
|
@@ -23,7 +23,7 @@ namespace Torch.Server
|
||||
{
|
||||
public class TorchServer : TorchBase, ITorchServer
|
||||
{
|
||||
public Thread ServerThread { get; private set; }
|
||||
public Thread GameThread { get; private set; }
|
||||
public bool IsRunning { get; private set; }
|
||||
|
||||
public event Action SessionLoading;
|
||||
@@ -66,7 +66,7 @@ namespace Torch.Server
|
||||
|
||||
private void OnSessionReady()
|
||||
{
|
||||
Plugins.LoadAllPlugins();
|
||||
Plugins.LoadPlugins();
|
||||
InvokeSessionLoaded();
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace Torch.Server
|
||||
/// </summary>
|
||||
public override void Stop()
|
||||
{
|
||||
if (Thread.CurrentThread.ManagedThreadId != ServerThread?.ManagedThreadId)
|
||||
if (Thread.CurrentThread.ManagedThreadId != GameThread?.ManagedThreadId)
|
||||
{
|
||||
Log.Write("Requesting server stop.");
|
||||
MySandboxGame.Static.Invoke(Stop);
|
||||
|
@@ -233,7 +233,7 @@ namespace Torch.Managers
|
||||
/// <param name="args"></param>
|
||||
public void RaiseEvent(MethodInfo method, object obj, ulong steamId, params object[] args)
|
||||
{
|
||||
RaiseEvent(method,obj,new EndpointId(steamId), args);
|
||||
RaiseEvent(method, obj, new EndpointId(steamId), args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -252,7 +252,7 @@ namespace Torch.Managers
|
||||
throw new ArgumentOutOfRangeException(nameof(args), "Cannot pass more than 6 arguments!");
|
||||
|
||||
var owner = obj as IMyEventOwner;
|
||||
if (obj != null && owner == null )
|
||||
if (obj != null && owner == null)
|
||||
throw new InvalidCastException("Provided event target is not of type IMyEventOwner!");
|
||||
|
||||
if(!method.HasAttribute<EventAttribute>())
|
||||
@@ -261,7 +261,7 @@ namespace Torch.Managers
|
||||
//array to hold arguments to pass into DispatchEvent
|
||||
object[] arguments = new object[11];
|
||||
|
||||
arguments[0] = (obj == null ? TryGetStaticCallSite(method) : TryGetCallSite(method, obj));
|
||||
arguments[0] = obj == null ? TryGetStaticCallSite(method) : TryGetCallSite(method, obj);
|
||||
arguments[1] = endpoint;
|
||||
arguments[2] = 1f;
|
||||
arguments[3] = owner;
|
||||
|
@@ -10,6 +10,7 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Sandbox;
|
||||
using Sandbox.ModAPI;
|
||||
using Torch.API;
|
||||
using VRage.Plugins;
|
||||
using VRage.Collections;
|
||||
@@ -17,19 +18,63 @@ using VRage.Library.Collections;
|
||||
|
||||
namespace Torch
|
||||
{
|
||||
internal class TorchPluginUpdater : IPlugin
|
||||
{
|
||||
private readonly IPluginManager _manager;
|
||||
public TorchPluginUpdater(IPluginManager manager)
|
||||
{
|
||||
_manager = manager;
|
||||
}
|
||||
|
||||
public void Init(object obj) { }
|
||||
|
||||
public void Update()
|
||||
{
|
||||
_manager.UpdatePlugins();
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
|
||||
public class PluginManager : IPluginManager
|
||||
{
|
||||
private readonly ITorchBase _torch;
|
||||
public const string PluginDir = "Plugins";
|
||||
|
||||
private readonly List<TorchPluginBase> _plugins = new List<TorchPluginBase>();
|
||||
private readonly List<ITorchPlugin> _plugins = new List<ITorchPlugin>();
|
||||
private readonly TorchPluginUpdater _updater;
|
||||
|
||||
public PluginManager(ITorchBase torch)
|
||||
{
|
||||
_torch = torch;
|
||||
_updater = new TorchPluginUpdater(this);
|
||||
|
||||
if (!Directory.Exists(PluginDir))
|
||||
Directory.CreateDirectory(PluginDir);
|
||||
|
||||
InitUpdater();
|
||||
}
|
||||
|
||||
private void InitUpdater()
|
||||
{
|
||||
var pluginList = typeof(MyPlugins).GetField("m_plugins", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null) as List<IPlugin>;
|
||||
if (pluginList == null)
|
||||
throw new TypeLoadException($"m_plugins field not found in {nameof(MyPlugins)}");
|
||||
|
||||
pluginList.Add(_updater);
|
||||
}
|
||||
|
||||
public void UpdatePlugins()
|
||||
{
|
||||
Parallel.ForEach(_plugins, p => p.Update());
|
||||
}
|
||||
|
||||
public void UnloadPlugins()
|
||||
{
|
||||
foreach (var plugin in _plugins)
|
||||
plugin.Unload();
|
||||
|
||||
_plugins.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -46,41 +91,12 @@ namespace Torch
|
||||
|
||||
foreach (var type in asm.GetExportedTypes())
|
||||
{
|
||||
if (type.IsSubclassOf(typeof(TorchPluginBase)))
|
||||
_plugins.Add((TorchPluginBase)Activator.CreateInstance(type));
|
||||
if (type.GetInterfaces().Contains(typeof(ITorchPlugin)))
|
||||
_plugins.Add((ITorchPlugin)Activator.CreateInstance(type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async void ReloadPluginAsync(ITorchPlugin plugin)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
public void StartEnabledPlugins()
|
||||
{
|
||||
foreach (var plugin in _plugins)
|
||||
{
|
||||
if (plugin.Enabled)
|
||||
plugin.Run(_torch);
|
||||
}
|
||||
}
|
||||
|
||||
public bool UnblockDll(string fileName)
|
||||
{
|
||||
return DeleteFile(fileName + ":Zone.Identifier");
|
||||
}
|
||||
|
||||
public IEnumerator<ITorchPlugin> GetEnumerator()
|
||||
{
|
||||
return _plugins.GetEnumerator();
|
||||
@@ -91,6 +107,15 @@ namespace Torch
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the lock on a DLL downloaded from the internet.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool UnblockDll(string fileName)
|
||||
{
|
||||
return DeleteFile(fileName + ":Zone.Identifier");
|
||||
}
|
||||
|
||||
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool DeleteFile(string name);
|
||||
|
@@ -10,13 +10,8 @@ namespace Torch
|
||||
public static class Reflection
|
||||
{
|
||||
//private static readonly Logger Log = LogManager.GetLogger( "BaseLog" );
|
||||
public static bool HasMethod(Type objectType, string methodName)
|
||||
{
|
||||
return HasMethod(objectType, methodName, null);
|
||||
}
|
||||
|
||||
|
||||
public static bool HasMethod(Type objectType, string methodName, Type[] argTypes)
|
||||
public static bool HasMethod(Type objectType, string methodName, Type[] argTypes = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@@ -16,8 +16,6 @@ namespace Torch
|
||||
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()
|
||||
@@ -50,57 +48,5 @@ namespace 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
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user