From 62d73cbf961f2515cce5d6da056b9b9bc2f7222c Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Mon, 9 Oct 2017 20:52:22 -0700 Subject: [PATCH] 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. --- .../Event/EventHandlerAttribute.cs | 2 +- Torch.API/{Managers => }/Event/IEvent.cs | 2 +- .../{Managers => }/Event/IEventHandler.cs | 2 +- .../{Managers => }/Event/IEventManager.cs | 2 +- Torch/{Managers => }/Event/EventList.cs | 5 +- Torch/{Managers => }/Event/EventManager.cs | 22 ++++-- Torch/Event/EventShimAttribute.cs | 20 ++++++ .../Event/EventShimProgrammableBlock.cs | 12 +--- Torch/{Managers => }/Event/IEventList.cs | 4 +- Torch/Managers/PatchManager/PatchContext.cs | 8 +-- Torch/Managers/PatchManager/PatchManager.cs | 67 ++++++++++++++++--- .../PatchManager/PatchShimAttribute.cs | 20 ++++++ Torch/TorchBase.cs | 18 ++++- 13 files changed, 145 insertions(+), 39 deletions(-) rename Torch.API/{Managers => }/Event/EventHandlerAttribute.cs (96%) rename Torch.API/{Managers => }/Event/IEvent.cs (87%) rename Torch.API/{Managers => }/Event/IEventHandler.cs (83%) rename Torch.API/{Managers => }/Event/IEventManager.cs (96%) rename Torch/{Managers => }/Event/EventList.cs (98%) rename Torch/{Managers => }/Event/EventManager.cs (89%) create mode 100644 Torch/Event/EventShimAttribute.cs rename Torch/{Managers => }/Event/EventShimProgrammableBlock.cs (92%) rename Torch/{Managers => }/Event/IEventList.cs (93%) create mode 100644 Torch/Managers/PatchManager/PatchShimAttribute.cs diff --git a/Torch.API/Managers/Event/EventHandlerAttribute.cs b/Torch.API/Event/EventHandlerAttribute.cs similarity index 96% rename from Torch.API/Managers/Event/EventHandlerAttribute.cs rename to Torch.API/Event/EventHandlerAttribute.cs index 58e3bb4..0bcf8c0 100644 --- a/Torch.API/Managers/Event/EventHandlerAttribute.cs +++ b/Torch.API/Event/EventHandlerAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Torch.API.Managers.Event +namespace Torch.API.Event { /// /// Attribute indicating that a method should be invoked when the event occurs. diff --git a/Torch.API/Managers/Event/IEvent.cs b/Torch.API/Event/IEvent.cs similarity index 87% rename from Torch.API/Managers/Event/IEvent.cs rename to Torch.API/Event/IEvent.cs index 27537d8..0021e74 100644 --- a/Torch.API/Managers/Event/IEvent.cs +++ b/Torch.API/Event/IEvent.cs @@ -1,4 +1,4 @@ -namespace Torch.API.Managers.Event +namespace Torch.API.Event { public interface IEvent { diff --git a/Torch.API/Managers/Event/IEventHandler.cs b/Torch.API/Event/IEventHandler.cs similarity index 83% rename from Torch.API/Managers/Event/IEventHandler.cs rename to Torch.API/Event/IEventHandler.cs index 58dee25..db5d7ff 100644 --- a/Torch.API/Managers/Event/IEventHandler.cs +++ b/Torch.API/Event/IEventHandler.cs @@ -1,4 +1,4 @@ -namespace Torch.API.Managers.Event +namespace Torch.API.Event { /// /// Interface used to tag an event handler. This does not register it with the event manager. diff --git a/Torch.API/Managers/Event/IEventManager.cs b/Torch.API/Event/IEventManager.cs similarity index 96% rename from Torch.API/Managers/Event/IEventManager.cs rename to Torch.API/Event/IEventManager.cs index 6daef0c..038db33 100644 --- a/Torch.API/Managers/Event/IEventManager.cs +++ b/Torch.API/Event/IEventManager.cs @@ -1,6 +1,6 @@ using System.Runtime.CompilerServices; -namespace Torch.API.Managers.Event +namespace Torch.API.Event { /// /// Manager class responsible for registration of event handlers. diff --git a/Torch/Managers/Event/EventList.cs b/Torch/Event/EventList.cs similarity index 98% rename from Torch/Managers/Event/EventList.cs rename to Torch/Event/EventList.cs index 977e5b0..28bcf33 100644 --- a/Torch/Managers/Event/EventList.cs +++ b/Torch/Event/EventList.cs @@ -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 { /// /// Represents an ordered list of callbacks. diff --git a/Torch/Managers/Event/EventManager.cs b/Torch/Event/EventManager.cs similarity index 89% rename from Torch/Managers/Event/EventManager.cs rename to Torch/Event/EventManager.cs index f6b0330..adaa565 100644 --- a/Torch/Managers/Event/EventManager.cs +++ b/Torch/Event/EventManager.cs @@ -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 { /// /// Manager class responsible for managing registration and dispatching of events. @@ -19,13 +19,18 @@ namespace Torch.Managers.Event private static readonly Dictionary _eventLists = new Dictionary(); - static EventManager() + internal static void AddDispatchShims(Assembly asm) { - AddDispatchShim(typeof(EventShimProgrammableBlock)); + foreach (Type type in asm.GetTypes()) + if (type.HasAttribute()) + 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."); } diff --git a/Torch/Event/EventShimAttribute.cs b/Torch/Event/EventShimAttribute.cs new file mode 100644 index 0000000..87e5c66 --- /dev/null +++ b/Torch/Event/EventShimAttribute.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Torch.Event +{ + /// + /// 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). + /// + /// + /// Event shims should be singleton, and have one (or more) fields that are of type . + /// + [AttributeUsage(AttributeTargets.Class)] + internal class EventShimAttribute : Attribute + { + } +} diff --git a/Torch/Managers/Event/EventShimProgrammableBlock.cs b/Torch/Event/EventShimProgrammableBlock.cs similarity index 92% rename from Torch/Managers/Event/EventShimProgrammableBlock.cs rename to Torch/Event/EventShimProgrammableBlock.cs index 87632d1..f27d327 100644 --- a/Torch/Managers/Event/EventShimProgrammableBlock.cs +++ b/Torch/Event/EventShimProgrammableBlock.cs @@ -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 { diff --git a/Torch/Managers/Event/IEventList.cs b/Torch/Event/IEventList.cs similarity index 93% rename from Torch/Managers/Event/IEventList.cs rename to Torch/Event/IEventList.cs index 2e0a3f4..b433973 100644 --- a/Torch/Managers/Event/IEventList.cs +++ b/Torch/Event/IEventList.cs @@ -1,7 +1,7 @@ using System.Reflection; -using Torch.API.Managers.Event; +using Torch.API.Event; -namespace Torch.Managers.Event +namespace Torch.Event { /// /// Represents the interface for adding and removing from an ordered list of callbacks. diff --git a/Torch/Managers/PatchManager/PatchContext.cs b/Torch/Managers/PatchManager/PatchContext.cs index 66694a3..a36225a 100644 --- a/Torch/Managers/PatchManager/PatchContext.cs +++ b/Torch/Managers/PatchManager/PatchContext.cs @@ -7,14 +7,12 @@ namespace Torch.Managers.PatchManager /// /// Represents a set of common patches that can all be reversed in a single step. /// - public class PatchContext + public sealed class PatchContext { - private readonly PatchManager _replacer; private readonly Dictionary _rewritePatterns = new Dictionary(); - internal PatchContext(PatchManager replacer) + internal PatchContext() { - _replacer = replacer; } /// @@ -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; diff --git a/Torch/Managers/PatchManager/PatchManager.cs b/Torch/Managers/PatchManager/PatchManager.cs index 7e9a205..cc9a984 100644 --- a/Torch/Managers/PatchManager/PatchManager.cs +++ b/Torch/Managers/PatchManager/PatchManager.cs @@ -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 /// 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()) + 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 }); + } + /// - /// Creates a new patch manager. Only have one active at a time. + /// Creates a new patch manager. /// /// public PatchManager(ITorchBase torchInstance) : base(torchInstance) @@ -21,14 +54,15 @@ namespace Torch.Managers.PatchManager } private static readonly Dictionary _rewritePatterns = new Dictionary(); - private readonly Dictionary> _contexts = new Dictionary>(); + private static readonly Dictionary> _contexts = new Dictionary>(); + private static List _coreContexts = new List(); /// /// Gets the rewrite pattern for the given method, creating one if it doesn't exist. /// /// Method to get the pattern for /// - public MethodRewritePattern GetPattern(MethodBase method) + internal static MethodRewritePattern GetPatternInternal(MethodBase method) { lock (_rewritePatterns) { @@ -40,6 +74,16 @@ namespace Torch.Managers.PatchManager } } + /// + /// Gets the rewrite pattern for the given method, creating one if it doesn't exist. + /// + /// Method to get the pattern for + /// + public MethodRewritePattern GetPattern(MethodBase method) + { + return GetPatternInternal(method); + } + /// /// Creates a new used for tracking changes. A call to 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 localContexts)) @@ -106,8 +150,9 @@ namespace Torch.Managers.PatchManager /// public void Commit() { - foreach (DecoratedMethod m in _rewritePatterns.Values) - m.Commit(); + lock (_rewritePatterns) + foreach (DecoratedMethod m in _rewritePatterns.Values) + m.Commit(); } /// @@ -123,11 +168,15 @@ namespace Torch.Managers.PatchManager /// public override void Detach() { - foreach (DecoratedMethod m in _rewritePatterns.Values) - m.Revert(); - _rewritePatterns.Clear(); lock (_contexts) + { + foreach (List set in _contexts.Values) + foreach (PatchContext ctx in set) + ctx.RemoveAll(); _contexts.Clear(); + foreach (DecoratedMethod m in _rewritePatterns.Values) + m.Revert(); + } } } } diff --git a/Torch/Managers/PatchManager/PatchShimAttribute.cs b/Torch/Managers/PatchManager/PatchShimAttribute.cs new file mode 100644 index 0000000..2ab1afd --- /dev/null +++ b/Torch/Managers/PatchManager/PatchShimAttribute.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Torch.Managers.PatchManager +{ + /// + /// 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). + /// + /// + /// Event shims should be singleton, and have one method of signature void Patch(PatchContext) + /// + [AttributeUsage(AttributeTargets.Class)] + internal class PatchShimAttribute : Attribute + { + } +} diff --git a/Torch/TorchBase.cs b/Torch/TorchBase.cs index 75d5612..599f098 100644 --- a/Torch/TorchBase.cs +++ b/Torch/TorchBase.cs @@ -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 /// Thrown if a TorchBase instance already exists. 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 _registeredAssemblies = new HashSet(); + private static void RegisterCoreAssembly(Assembly asm) + { + lock (_registeredAssemblies) + if (_registeredAssemblies.Add(asm)) + { + EventManager.AddDispatchShims(asm); + PatchManager.AddPatchShims(asm); + } + } } }