Rewrite plugin system to update plugins in parallel after each game tick.

This commit is contained in:
John Gross
2017-01-03 02:49:33 -08:00
parent 090ebd5ef5
commit a3e29fff4e
7 changed files with 66 additions and 100 deletions

View File

@@ -7,6 +7,7 @@ namespace Torch.API
{
public interface IPluginManager : IEnumerable<ITorchPlugin>
{
void UpdatePlugins();
void LoadPlugins();
}
}

View File

@@ -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();

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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
{

View File

@@ -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
}
}