From 3307d2d23da089a582cbf785a50f030f5a1da1f7 Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Tue, 10 Oct 2017 02:47:45 -0700 Subject: [PATCH] Patch manager supports communicating method cancel state Game state patch split out into a patch shim Special NS for events Core and auxillary assembly concepts Reflected manager stops working for mods/scripts --- Torch.API/Torch.API.csproj | 8 +- Torch/Event/EventManager.cs | 8 +- .../Managers/PatchManager/DecoratedMethod.cs | 46 +++++-- Torch/Managers/PatchManager/PatchManager.cs | 31 +++-- Torch/Patches/GameStatePatchShim.cs | 75 +++++++++++ Torch/Plugins/PluginManager.cs | 3 +- Torch/Torch.csproj | 11 +- Torch/TorchBase.cs | 120 +++++------------- Torch/Utils/ReflectedManager.cs | 29 +---- 9 files changed, 187 insertions(+), 144 deletions(-) create mode 100644 Torch/Patches/GameStatePatchShim.cs diff --git a/Torch.API/Torch.API.csproj b/Torch.API/Torch.API.csproj index c3d2823..bd5e5f4 100644 --- a/Torch.API/Torch.API.csproj +++ b/Torch.API/Torch.API.csproj @@ -163,14 +163,14 @@ - - - + + + - + diff --git a/Torch/Event/EventManager.cs b/Torch/Event/EventManager.cs index adaa565..e752322 100644 --- a/Torch/Event/EventManager.cs +++ b/Torch/Event/EventManager.cs @@ -26,8 +26,12 @@ namespace Torch.Event AddDispatchShim(type); } + private static readonly HashSet _dispatchShims = new HashSet(); private static void AddDispatchShim(Type type) { + lock (_dispatchShims) + if (!_dispatchShims.Add(type)) + return; 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; @@ -40,7 +44,7 @@ namespace Torch.Event _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++; } @@ -49,7 +53,7 @@ namespace Torch.Event _log.Warn($"Registering type {type.FullName} as an event dispatch type, even though it has no event lists."); } - + /// /// Gets all event handler methods declared by the given type and its base types. /// diff --git a/Torch/Managers/PatchManager/DecoratedMethod.cs b/Torch/Managers/PatchManager/DecoratedMethod.cs index 8ca6a47..ec0814c 100644 --- a/Torch/Managers/PatchManager/DecoratedMethod.cs +++ b/Torch/Managers/PatchManager/DecoratedMethod.cs @@ -79,8 +79,9 @@ namespace Torch.Managers.PatchManager } - private const string INSTANCE_PARAMETER = "__instance"; - private const string RESULT_PARAMETER = "__result"; + public const string INSTANCE_PARAMETER = "__instance"; + public const string RESULT_PARAMETER = "__result"; + public const string PREFIX_SKIPPED_PARAMETER = "__prefixSkipped"; #pragma warning disable 649 [ReflectedStaticMethod(Type = typeof(RuntimeHelpers), Name = "_CompileMethod", OverrideTypeNames = new[] { "System.IRuntimeMethodInfo" })] @@ -118,7 +119,7 @@ namespace Torch.Managers.PatchManager var specialVariables = new Dictionary(); Label labelAfterOriginalContent = target.DefineLabel(); - Label labelAfterOriginalReturn = target.DefineLabel(); + Label labelSkipMethodContent = target.DefineLabel(); Type returnType = _method is MethodInfo meth ? meth.ReturnType : typeof(void); @@ -131,6 +132,13 @@ namespace Torch.Managers.PatchManager resultVariable = target.DeclareLocal(returnType); } resultVariable?.SetToDefault(target); + LocalBuilder prefixSkippedVariable = null; + if (Prefixes.Count > 0 && Suffixes.Any(x => x.GetParameters() + .Any(y => y.Name.Equals(PREFIX_SKIPPED_PARAMETER)))) + { + prefixSkippedVariable = target.DeclareLocal(typeof(bool)); + specialVariables.Add(PREFIX_SKIPPED_PARAMETER, prefixSkippedVariable); + } if (resultVariable != null) specialVariables.Add(RESULT_PARAMETER, resultVariable); @@ -140,7 +148,7 @@ namespace Torch.Managers.PatchManager { EmitMonkeyCall(target, prefix, specialVariables); if (prefix.ReturnType == typeof(bool)) - target.Emit(OpCodes.Brfalse, labelAfterOriginalReturn); + target.Emit(OpCodes.Brfalse, labelSkipMethodContent); else if (prefix.ReturnType != typeof(void)) throw new Exception( $"Prefixes must return void or bool. {prefix.DeclaringType?.FullName}.{prefix.Name} returns {prefix.ReturnType}"); @@ -154,7 +162,15 @@ namespace Torch.Managers.PatchManager target.MarkLabel(labelAfterOriginalContent); if (resultVariable != null) target.Emit(OpCodes.Stloc, resultVariable); - target.MarkLabel(labelAfterOriginalReturn); + Label notSkip = target.DefineLabel(); + target.Emit(OpCodes.Br, notSkip); + target.MarkLabel(labelSkipMethodContent); + if (prefixSkippedVariable != null) + { + target.Emit(OpCodes.Ldc_I4_1); + target.Emit(OpCodes.Stloc, prefixSkippedVariable); + } + target.MarkLabel(notSkip); target.EmitComment("Suffixes Begin"); foreach (MethodInfo suffix in Suffixes) @@ -182,8 +198,18 @@ namespace Torch.Managers.PatchManager throw new Exception("Can't use an instance parameter for a static method"); target.Emit(OpCodes.Ldarg_0); break; + case PREFIX_SKIPPED_PARAMETER: + if (param.ParameterType != typeof(bool)) + throw new Exception($"Prefix skipped parameter {param.ParameterType} must be of type bool"); + if (param.ParameterType.IsByRef || param.IsOut) + throw new Exception($"Prefix skipped parameter {param.ParameterType} can't be a reference type"); + if (specialVariables.TryGetValue(PREFIX_SKIPPED_PARAMETER, out LocalBuilder prefixSkip)) + target.Emit(OpCodes.Ldloc, prefixSkip); + else + target.Emit(OpCodes.Ldc_I4_0); + break; case RESULT_PARAMETER: - var retType = param.ParameterType.IsByRef + Type retType = param.ParameterType.IsByRef ? param.ParameterType.GetElementType() : param.ParameterType; if (retType == null || !retType.IsAssignableFrom(specialVariables[RESULT_PARAMETER].LocalType)) @@ -191,13 +217,13 @@ namespace Torch.Managers.PatchManager target.Emit(param.ParameterType.IsByRef ? OpCodes.Ldloca : OpCodes.Ldloc, specialVariables[RESULT_PARAMETER]); break; default: - var declParam = _method.GetParameters().FirstOrDefault(x => x.Name == param.Name); + ParameterInfo declParam = _method.GetParameters().FirstOrDefault(x => x.Name == param.Name); if (declParam == null) throw new Exception($"Parameter name {param.Name} not found"); - var paramIdx = (_method.IsStatic ? 0 : 1) + declParam.Position; + int paramIdx = (_method.IsStatic ? 0 : 1) + declParam.Position; - var patchByRef = param.IsOut || param.ParameterType.IsByRef; - var declByRef = declParam.IsOut || declParam.ParameterType.IsByRef; + bool patchByRef = param.IsOut || param.ParameterType.IsByRef; + bool declByRef = declParam.IsOut || declParam.ParameterType.IsByRef; if (patchByRef == declByRef) target.Emit(OpCodes.Ldarg, paramIdx); else if (patchByRef) diff --git a/Torch/Managers/PatchManager/PatchManager.cs b/Torch/Managers/PatchManager/PatchManager.cs index cc9a984..fce9991 100644 --- a/Torch/Managers/PatchManager/PatchManager.cs +++ b/Torch/Managers/PatchManager/PatchManager.cs @@ -22,8 +22,14 @@ namespace Torch.Managers.PatchManager AddPatchShim(t); } - private static void AddPatchShim(Type type) + private static readonly HashSet _patchShims = new HashSet(); + // Internal, not static, so the static cctor of TorchBase can hookup the GameStatePatchShim which tells us when + // its safe to patch the rest of the game. + internal static void AddPatchShim(Type type) { + lock (_patchShims) + if (!_patchShims.Add(type)) + return; 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); @@ -34,7 +40,7 @@ namespace Torch.Managers.PatchManager } 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)) + 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; @@ -55,13 +61,10 @@ namespace Torch.Managers.PatchManager private static readonly Dictionary _rewritePatterns = new Dictionary(); private static readonly Dictionary> _contexts = new Dictionary>(); - private static List _coreContexts = new List(); + // ReSharper disable once CollectionNeverQueried.Local because we may want this in the future. + private static readonly 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 - /// + /// internal static MethodRewritePattern GetPatternInternal(MethodBase method) { lock (_rewritePatterns) @@ -145,14 +148,20 @@ namespace Torch.Managers.PatchManager return count; } + /// + internal static void CommitInternal() + { + lock (_rewritePatterns) + foreach (DecoratedMethod m in _rewritePatterns.Values) + m.Commit(); + } + /// /// Commits all method decorations into IL. /// public void Commit() { - lock (_rewritePatterns) - foreach (DecoratedMethod m in _rewritePatterns.Values) - m.Commit(); + CommitInternal(); } /// diff --git a/Torch/Patches/GameStatePatchShim.cs b/Torch/Patches/GameStatePatchShim.cs new file mode 100644 index 0000000..b2af4a7 --- /dev/null +++ b/Torch/Patches/GameStatePatchShim.cs @@ -0,0 +1,75 @@ +using System; +using System.Reflection; +using Sandbox; +using Torch.API; +using Torch.Managers.PatchManager; +using Torch.Utils; + +namespace Torch.Patches +{ + [PatchShim] + internal static class GameStatePatchShim + { +#pragma warning disable 649 + [ReflectedMethodInfo(typeof(MySandboxGame), nameof(MySandboxGame.Dispose))] + private static MethodInfo _sandboxGameDispose; + [ReflectedMethodInfo(typeof(MySandboxGame), "Initialize")] + private static MethodInfo _sandboxGameInit; +#pragma warning restore 649 + + internal static void Patch(PatchContext target) + { + 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(PrefixConstructor)); + target.GetPattern(ctor).Suffixes.Add(MethodRef(SuffixConstructor)); + target.GetPattern(_sandboxGameInit).Prefixes.Add(MethodRef(PrefixInit)); + target.GetPattern(_sandboxGameInit).Suffixes.Add(MethodRef(SuffixInit)); + target.GetPattern(_sandboxGameDispose).Prefixes.Add(MethodRef(PrefixDispose)); + target.GetPattern(_sandboxGameDispose).Suffixes.Add(MethodRef(SuffixDispose)); + } + + private static MethodInfo MethodRef(Action a ) + { + return a.Method; + } + + private static void PrefixConstructor() + { + if (TorchBase.Instance is TorchBase tb) + tb.GameState = TorchGameState.Creating; + } + + private static void SuffixConstructor() + { + PatchManager.CommitInternal(); + if (TorchBase.Instance is TorchBase tb) + tb.GameState = TorchGameState.Created; + } + + private static void PrefixInit() + { + if (TorchBase.Instance is TorchBase tb) + tb.GameState = TorchGameState.Loading; + } + + private static void SuffixInit() + { + if (TorchBase.Instance is TorchBase tb) + tb.GameState = TorchGameState.Loaded; + } + + private static void PrefixDispose() + { + if (TorchBase.Instance is TorchBase tb) + tb.GameState = TorchGameState.Unloading; + } + + private static void SuffixDispose() + { + if (TorchBase.Instance is TorchBase tb) + tb.GameState = TorchGameState.Unloaded; + } + } +} diff --git a/Torch/Plugins/PluginManager.cs b/Torch/Plugins/PluginManager.cs index cf2e724..ceadbfc 100644 --- a/Torch/Plugins/PluginManager.cs +++ b/Torch/Plugins/PluginManager.cs @@ -268,7 +268,8 @@ namespace Torch.Managers { var data = new byte[entry.Length]; stream.Read(data, 0, data.Length); - assemblies.Add(Assembly.Load(data)); + Assembly asm = Assembly.Load(data); + TorchBase.RegisterAuxAssembly(asm); } } } diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj index a39572e..0f59e6f 100644 --- a/Torch/Torch.csproj +++ b/Torch/Torch.csproj @@ -155,15 +155,16 @@ Properties\AssemblyVersion.cs + - - - - + + + + @@ -179,12 +180,14 @@ + + diff --git a/Torch/TorchBase.cs b/Torch/TorchBase.cs index 599f098..c5f3c7d 100644 --- a/Torch/TorchBase.cs +++ b/Torch/TorchBase.cs @@ -26,6 +26,7 @@ using Torch.Event; using Torch.Managers; using Torch.Managers.ChatManager; using Torch.Managers.PatchManager; +using Torch.Patches; using Torch.Utils; using Torch.Session; using VRage.Collections; @@ -46,10 +47,12 @@ namespace Torch { static TorchBase() { + RuntimeHelpers.RunClassConstructor(typeof(ReflectedManager).TypeHandle); + PatchManager.AddPatchShim(typeof(GameStatePatchShim)); + PatchManager.CommitInternal(); RegisterCoreAssembly(typeof(ITorchBase).Assembly); RegisterCoreAssembly(typeof(TorchBase).Assembly); - // We can safely never detach this since we don't reload assemblies. - new ReflectedManager().Attach(); + RegisterCoreAssembly(Assembly.GetEntryAssembly()); } /// @@ -124,21 +127,11 @@ namespace Torch sessionManager.AddFactory((x) => new EntityManager(this)); Managers.AddManager(sessionManager); - var patcher = new PatchManager(this); - GameStateInjector.Inject(patcher.AcquireContext()); - Managers.AddManager(patcher); - // Managers.AddManager(new KeenLogManager(this)); + Managers.AddManager(new PatchManager(this)); Managers.AddManager(new FilesystemManager(this)); Managers.AddManager(new UpdateManager(this)); Managers.AddManager(new EventManager(this)); Managers.AddManager(Plugins); - GameStateChanged += (game, state) => - { - if (state != TorchGameState.Created) - return; - // At this point flush the patches; it's safe. - patcher.Commit(); - }; TorchAPI.Instance = this; } @@ -395,7 +388,7 @@ namespace Torch /// public virtual void Update() { - GetManager().UpdatePlugins(); + Managers.GetManager().UpdatePlugins(); } @@ -405,7 +398,7 @@ namespace Torch public TorchGameState GameState { get => _gameState; - private set + internal set { _gameState = value; GameStateChanged?.Invoke(MySandboxGame.Static, _gameState); @@ -415,81 +408,36 @@ namespace Torch /// public event TorchGameStateChangedDel GameStateChanged; - #region GameStateInjecting - private static class GameStateInjector + private static readonly HashSet _registeredCoreAssemblies = new HashSet(); + /// + /// Registers a core (Torch) assembly with the system, including its + /// shims, shims, and components. + /// + /// Assembly to register + internal static void RegisterCoreAssembly(Assembly asm) { -#pragma warning disable 649 - [ReflectedMethodInfo(typeof(MySandboxGame), nameof(MySandboxGame.Dispose))] - private static MethodInfo _sandboxGameDispose; - [ReflectedMethodInfo(typeof(MySandboxGame), "Initialize")] - private static MethodInfo _sandboxGameInit; -#pragma warning restore 649 - - internal static void Inject(PatchContext target) - { - 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))); - target.GetPattern(ctor).Suffixes.Add(MethodRef(nameof(SuffixConstructor))); - target.GetPattern(_sandboxGameInit).Prefixes.Add(MethodRef(nameof(PrefixInit))); - target.GetPattern(_sandboxGameInit).Suffixes.Add(MethodRef(nameof(SuffixInit))); - target.GetPattern(_sandboxGameDispose).Prefixes.Add(MethodRef(nameof(PrefixDispose))); - target.GetPattern(_sandboxGameDispose).Suffixes.Add(MethodRef(nameof(SuffixDispose))); - } - - private static MethodInfo MethodRef(string name) - { - return typeof(GameStateInjector).GetMethod(name, - BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); - } - - private static void PrefixConstructor() - { - if (Instance is TorchBase tb) - tb.GameState = TorchGameState.Creating; - } - - private static void SuffixConstructor() - { - if (Instance is TorchBase tb) - tb.GameState = TorchGameState.Created; - } - - private static void PrefixInit() - { - if (Instance is TorchBase tb) - tb.GameState = TorchGameState.Loading; - } - - private static void SuffixInit() - { - if (Instance is TorchBase tb) - tb.GameState = TorchGameState.Loaded; - } - - private static void PrefixDispose() - { - if (Instance is TorchBase tb) - tb.GameState = TorchGameState.Unloading; - } - - private static void SuffixDispose() - { - if (Instance is TorchBase tb) - tb.GameState = TorchGameState.Unloaded; - } - } - #endregion - - private static readonly HashSet _registeredAssemblies = new HashSet(); - private static void RegisterCoreAssembly(Assembly asm) - { - lock (_registeredAssemblies) - if (_registeredAssemblies.Add(asm)) + lock (_registeredCoreAssemblies) + if (_registeredCoreAssemblies.Add(asm)) { EventManager.AddDispatchShims(asm); PatchManager.AddPatchShims(asm); + ReflectedManager.Process(asm); + } + } + + private static readonly HashSet _registeredAuxAssemblies = new HashSet(); + + /// + /// Registers an auxillary (plugin) assembly with the system, including its + /// related components. + /// + /// Assembly to register + internal static void RegisterAuxAssembly(Assembly asm) + { + lock (_registeredAuxAssemblies) + if (_registeredAuxAssemblies.Add(asm)) + { + ReflectedManager.Process(asm); } } } diff --git a/Torch/Utils/ReflectedManager.cs b/Torch/Utils/ReflectedManager.cs index 9687cf1..60c1ea6 100644 --- a/Torch/Utils/ReflectedManager.cs +++ b/Torch/Utils/ReflectedManager.cs @@ -398,43 +398,20 @@ namespace Torch.Utils /// /// Automatically calls for every assembly already loaded, and every assembly that is loaded in the future. /// - public class ReflectedManager + public static class ReflectedManager { private static readonly Logger _log = LogManager.GetCurrentClassLogger(); private static readonly string[] _namespaceBlacklist = new[] { "System", "VRage", "Sandbox", "SpaceEngineers", "Microsoft" }; - /// - /// Registers the assembly load event and loads every already existing assembly. - /// - public void Attach() - { - foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies()) - Process(asm); - AppDomain.CurrentDomain.AssemblyLoad += CurrentDomain_AssemblyLoad; - } - - /// - /// Deregisters the assembly load event - /// - public void Detach() - { - AppDomain.CurrentDomain.AssemblyLoad -= CurrentDomain_AssemblyLoad; - } - - private void CurrentDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args) - { - Process(args.LoadedAssembly); - } - private static readonly HashSet _processedTypes = new HashSet(); /// /// Ensures all reflected fields and methods contained in the given type are initialized /// /// Type to process - public static void Process(Type t) + internal static void Process(Type t) { if (_processedTypes.Add(t)) { @@ -467,7 +444,7 @@ namespace Torch.Utils /// Ensures all types in the given assembly are initialized using /// /// Assembly to process - public static void Process(Assembly asm) + internal static void Process(Assembly asm) { foreach (Type type in asm.GetTypes()) Process(type);