Moved event stuff to a non-manager NS.

Core assembly concept: Assemblies that will never need to get unloaded (Torch.API, Torch, Torch.Server)
Event shims and patch shims on the core assemblies.
This commit is contained in:
Westin Miller
2017-10-09 20:52:22 -07:00
parent 2004f71290
commit 62d73cbf96
13 changed files with 145 additions and 39 deletions

View File

@@ -1,6 +1,6 @@
using System;
namespace Torch.API.Managers.Event
namespace Torch.API.Event
{
/// <summary>
/// Attribute indicating that a method should be invoked when the event occurs.

View File

@@ -1,4 +1,4 @@
namespace Torch.API.Managers.Event
namespace Torch.API.Event
{
public interface IEvent
{

View File

@@ -1,4 +1,4 @@
namespace Torch.API.Managers.Event
namespace Torch.API.Event
{
/// <summary>
/// Interface used to tag an event handler. This does <b>not</b> register it with the event manager.

View File

@@ -1,6 +1,6 @@
using System.Runtime.CompilerServices;
namespace Torch.API.Managers.Event
namespace Torch.API.Event
{
/// <summary>
/// Manager class responsible for registration of event handlers.

View File

@@ -3,10 +3,9 @@ using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using Torch.API.Managers.Event;
using Torch.Managers.EventManager;
using Torch.API.Event;
namespace Torch.Managers.Event
namespace Torch.Event
{
/// <summary>
/// Represents an ordered list of callbacks.

View File

@@ -5,10 +5,10 @@ using System.Reflection;
using System.Runtime.CompilerServices;
using NLog;
using Torch.API;
using Torch.API.Managers.Event;
using Torch.Managers.EventManager;
using Torch.API.Event;
using Torch.Managers;
namespace Torch.Managers.Event
namespace Torch.Event
{
/// <summary>
/// Manager class responsible for managing registration and dispatching of events.
@@ -19,13 +19,18 @@ namespace Torch.Managers.Event
private static readonly Dictionary<Type, IEventList> _eventLists = new Dictionary<Type, IEventList>();
static EventManager()
internal static void AddDispatchShims(Assembly asm)
{
AddDispatchShim(typeof(EventShimProgrammableBlock));
foreach (Type type in asm.GetTypes())
if (type.HasAttribute<EventShimAttribute>())
AddDispatchShim(type);
}
private static void AddDispatchShim(Type type)
{
if (!type.IsSealed || !type.IsAbstract)
_log.Warn($"Registering type {type.FullName} as an event dispatch type, even though it isn't declared singleton");
var listsFound = 0;
RuntimeHelpers.RunClassConstructor(type.TypeHandle);
foreach (FieldInfo field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(EventList<>))
@@ -34,9 +39,14 @@ namespace Torch.Managers.Event
if (_eventLists.ContainsKey(eventType))
_log.Error($"Ignore event dispatch list {type.FullName}#{field.Name}; we already have one.");
else
_eventLists.Add(eventType, (IEventList)field.GetValue(null));
{
_eventLists.Add(eventType, (IEventList) field.GetValue(null));
listsFound++;
}
}
if (listsFound == 0)
_log.Warn($"Registering type {type.FullName} as an event dispatch type, even though it has no event lists.");
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Torch.Event
{
/// <summary>
/// Tagging class used to indicate that the class should be treated as an event shim.
/// Only works for core assemblies loaded by Torch (non-plugins).
/// </summary>
/// <remarks>
/// Event shims should be singleton, and have one (or more) fields that are of type <see cref="EventList{T}"/>.
/// </remarks>
[AttributeUsage(AttributeTargets.Class)]
internal class EventShimAttribute : Attribute
{
}
}

View File

@@ -1,17 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using Sandbox.Game.Entities;
using Sandbox.Game.Entities.Blocks;
using Torch.API.Managers.Event;
using Torch.Managers.Event;
using Torch.API.Event;
using Torch.Managers.PatchManager;
using Torch.Utils;
namespace Torch.Managers.EventManager
namespace Torch.Event
{
internal static class EventShimProgrammableBlock
{

View File

@@ -1,7 +1,7 @@
using System.Reflection;
using Torch.API.Managers.Event;
using Torch.API.Event;
namespace Torch.Managers.Event
namespace Torch.Event
{
/// <summary>
/// Represents the interface for adding and removing from an ordered list of callbacks.

View File

@@ -7,14 +7,12 @@ namespace Torch.Managers.PatchManager
/// <summary>
/// Represents a set of common patches that can all be reversed in a single step.
/// </summary>
public class PatchContext
public sealed class PatchContext
{
private readonly PatchManager _replacer;
private readonly Dictionary<MethodBase, MethodRewritePattern> _rewritePatterns = new Dictionary<MethodBase, MethodRewritePattern>();
internal PatchContext(PatchManager replacer)
internal PatchContext()
{
_replacer = replacer;
}
/// <summary>
@@ -26,7 +24,7 @@ namespace Torch.Managers.PatchManager
{
if (_rewritePatterns.TryGetValue(method, out MethodRewritePattern pattern))
return pattern;
MethodRewritePattern parent = _replacer.GetPattern(method);
MethodRewritePattern parent = PatchManager.GetPatternInternal(method);
var res = new MethodRewritePattern(parent);
_rewritePatterns.Add(method, res);
return res;

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using NLog;
using Torch.API;
using Torch.Managers.PatchManager.Transpile;
@@ -12,8 +13,40 @@ namespace Torch.Managers.PatchManager
/// </summary>
public class PatchManager : Manager
{
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
internal static void AddPatchShims(Assembly asm)
{
foreach (Type t in asm.GetTypes())
if (t.HasAttribute<PatchShimAttribute>())
AddPatchShim(t);
}
private static void AddPatchShim(Type type)
{
if (!type.IsSealed || !type.IsAbstract)
_log.Warn($"Registering type {type.FullName} as a patch shim type, even though it isn't declared singleton");
MethodInfo method = type.GetMethod("Patch", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
if (method == null)
{
_log.Error($"Patch shim type {type.FullName} doesn't have a static Patch method.");
return;
}
ParameterInfo[] ps = method.GetParameters();
if (ps.Length != 1 || ps[0].IsOut || ps[0].IsOptional || ps[0].ParameterType.IsByRef ||
ps[0].ParameterType == typeof(PatchContext) || method.ReturnType == typeof(void))
{
_log.Error($"Patch shim type {type.FullName} doesn't have a method with signature `void Patch(PatchContext)`");
return;
}
var context = new PatchContext();
lock (_coreContexts)
_coreContexts.Add(context);
method.Invoke(null, new object[] { context });
}
/// <summary>
/// Creates a new patch manager. Only have one active at a time.
/// Creates a new patch manager.
/// </summary>
/// <param name="torchInstance"></param>
public PatchManager(ITorchBase torchInstance) : base(torchInstance)
@@ -21,14 +54,15 @@ namespace Torch.Managers.PatchManager
}
private static readonly Dictionary<MethodBase, DecoratedMethod> _rewritePatterns = new Dictionary<MethodBase, DecoratedMethod>();
private readonly Dictionary<Assembly, List<PatchContext>> _contexts = new Dictionary<Assembly, List<PatchContext>>();
private static readonly Dictionary<Assembly, List<PatchContext>> _contexts = new Dictionary<Assembly, List<PatchContext>>();
private static List<PatchContext> _coreContexts = new List<PatchContext>();
/// <summary>
/// Gets the rewrite pattern for the given method, creating one if it doesn't exist.
/// </summary>
/// <param name="method">Method to get the pattern for</param>
/// <returns></returns>
public MethodRewritePattern GetPattern(MethodBase method)
internal static MethodRewritePattern GetPatternInternal(MethodBase method)
{
lock (_rewritePatterns)
{
@@ -40,6 +74,16 @@ namespace Torch.Managers.PatchManager
}
}
/// <summary>
/// Gets the rewrite pattern for the given method, creating one if it doesn't exist.
/// </summary>
/// <param name="method">Method to get the pattern for</param>
/// <returns></returns>
public MethodRewritePattern GetPattern(MethodBase method)
{
return GetPatternInternal(method);
}
/// <summary>
/// Creates a new <see cref="PatchContext"/> used for tracking changes. A call to <see cref="Commit"/> will apply the patches.
@@ -48,7 +92,7 @@ namespace Torch.Managers.PatchManager
public PatchContext AcquireContext()
{
Assembly assembly = Assembly.GetCallingAssembly();
var context = new PatchContext(this);
var context = new PatchContext();
lock (_contexts)
{
if (!_contexts.TryGetValue(assembly, out List<PatchContext> localContexts))
@@ -106,8 +150,9 @@ namespace Torch.Managers.PatchManager
/// </summary>
public void Commit()
{
foreach (DecoratedMethod m in _rewritePatterns.Values)
m.Commit();
lock (_rewritePatterns)
foreach (DecoratedMethod m in _rewritePatterns.Values)
m.Commit();
}
/// <summary>
@@ -123,11 +168,15 @@ namespace Torch.Managers.PatchManager
/// </summary>
public override void Detach()
{
foreach (DecoratedMethod m in _rewritePatterns.Values)
m.Revert();
_rewritePatterns.Clear();
lock (_contexts)
{
foreach (List<PatchContext> set in _contexts.Values)
foreach (PatchContext ctx in set)
ctx.RemoveAll();
_contexts.Clear();
foreach (DecoratedMethod m in _rewritePatterns.Values)
m.Revert();
}
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Torch.Managers.PatchManager
{
/// <summary>
/// Tagging class used to indicate that the class should be treated as supplying patch rules.
/// Only works for core assemblies loaded by Torch (non-plugins).
/// </summary>
/// <remarks>
/// Event shims should be singleton, and have one method of signature <i>void Patch(PatchContext)</i>
/// </remarks>
[AttributeUsage(AttributeTargets.Class)]
internal class PatchShimAttribute : Attribute
{
}
}

View File

@@ -22,6 +22,7 @@ using Torch.API.Managers;
using Torch.API.ModAPI;
using Torch.API.Session;
using Torch.Commands;
using Torch.Event;
using Torch.Managers;
using Torch.Managers.ChatManager;
using Torch.Managers.PatchManager;
@@ -45,6 +46,8 @@ namespace Torch
{
static TorchBase()
{
RegisterCoreAssembly(typeof(ITorchBase).Assembly);
RegisterCoreAssembly(typeof(TorchBase).Assembly);
// We can safely never detach this since we don't reload assemblies.
new ReflectedManager().Attach();
}
@@ -100,6 +103,7 @@ namespace Torch
/// <exception cref="InvalidOperationException">Thrown if a TorchBase instance already exists.</exception>
protected TorchBase()
{
RegisterCoreAssembly(GetType().Assembly);
if (Instance != null)
throw new InvalidOperationException("A TorchBase instance already exists.");
@@ -126,6 +130,7 @@ namespace Torch
// Managers.AddManager(new KeenLogManager(this));
Managers.AddManager(new FilesystemManager(this));
Managers.AddManager(new UpdateManager(this));
Managers.AddManager(new EventManager(this));
Managers.AddManager(Plugins);
GameStateChanged += (game, state) =>
{
@@ -422,7 +427,7 @@ namespace Torch
internal static void Inject(PatchContext target)
{
ConstructorInfo ctor = typeof(MySandboxGame).GetConstructor(new[] {typeof(string[])});
ConstructorInfo ctor = typeof(MySandboxGame).GetConstructor(new[] { typeof(string[]) });
if (ctor == null)
throw new ArgumentException("Can't find constructor MySandboxGame(string[])");
target.GetPattern(ctor).Prefixes.Add(MethodRef(nameof(PrefixConstructor)));
@@ -476,5 +481,16 @@ namespace Torch
}
}
#endregion
private static readonly HashSet<Assembly> _registeredAssemblies = new HashSet<Assembly>();
private static void RegisterCoreAssembly(Assembly asm)
{
lock (_registeredAssemblies)
if (_registeredAssemblies.Add(asm))
{
EventManager.AddDispatchShims(asm);
PatchManager.AddPatchShims(asm);
}
}
}
}