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
This commit is contained in:
Westin Miller
2017-10-10 02:47:45 -07:00
parent 62d73cbf96
commit 3307d2d23d
9 changed files with 187 additions and 144 deletions

View File

@@ -163,14 +163,14 @@
<Compile Include="ITorchConfig.cs" />
<Compile Include="Managers\DependencyManagerExtensions.cs" />
<Compile Include="Managers\DependencyProviderExtensions.cs" />
<Compile Include="Managers\Event\EventHandlerAttribute.cs" />
<Compile Include="Managers\Event\IEvent.cs" />
<Compile Include="Managers\Event\IEventHandler.cs" />
<Compile Include="Event\EventHandlerAttribute.cs" />
<Compile Include="Event\IEvent.cs" />
<Compile Include="Event\IEventHandler.cs" />
<Compile Include="Managers\IChatManagerClient.cs" />
<Compile Include="Managers\IChatManagerServer.cs" />
<Compile Include="Managers\IDependencyManager.cs" />
<Compile Include="Managers\IDependencyProvider.cs" />
<Compile Include="Managers\Event\IEventManager.cs" />
<Compile Include="Event\IEventManager.cs" />
<Compile Include="Managers\IManager.cs" />
<Compile Include="Managers\IMultiplayerManagerClient.cs" />
<Compile Include="Managers\IMultiplayerManagerBase.cs" />

View File

@@ -26,8 +26,12 @@ namespace Torch.Event
AddDispatchShim(type);
}
private static readonly HashSet<Type> _dispatchShims = new HashSet<Type>();
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;

View File

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

View File

@@ -22,8 +22,14 @@ namespace Torch.Managers.PatchManager
AddPatchShim(t);
}
private static void AddPatchShim(Type type)
private static readonly HashSet<Type> _patchShims = new HashSet<Type>();
// 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<MethodBase, DecoratedMethod> _rewritePatterns = new Dictionary<MethodBase, DecoratedMethod>();
private static readonly Dictionary<Assembly, List<PatchContext>> _contexts = new Dictionary<Assembly, List<PatchContext>>();
private static List<PatchContext> _coreContexts = new List<PatchContext>();
// ReSharper disable once CollectionNeverQueried.Local because we may want this in the future.
private static readonly 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>
/// <inheritdoc cref="GetPattern"/>
internal static MethodRewritePattern GetPatternInternal(MethodBase method)
{
lock (_rewritePatterns)
@@ -145,14 +148,20 @@ namespace Torch.Managers.PatchManager
return count;
}
/// <inheritdoc cref="Commit"/>
internal static void CommitInternal()
{
lock (_rewritePatterns)
foreach (DecoratedMethod m in _rewritePatterns.Values)
m.Commit();
}
/// <summary>
/// Commits all method decorations into IL.
/// </summary>
public void Commit()
{
lock (_rewritePatterns)
foreach (DecoratedMethod m in _rewritePatterns.Values)
m.Commit();
CommitInternal();
}
/// <summary>

View File

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

View File

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

View File

@@ -155,15 +155,16 @@
<Link>Properties\AssemblyVersion.cs</Link>
</Compile>
<Compile Include="Collections\ObservableList.cs" />
<Compile Include="Event\EventShimAttribute.cs" />
<Compile Include="Managers\ChatManager\ChatManagerClient.cs" />
<Compile Include="Managers\ChatManager\ChatManagerServer.cs" />
<Compile Include="Extensions\DispatcherExtensions.cs" />
<Compile Include="Extensions\ICollectionExtensions.cs" />
<Compile Include="Managers\DependencyManager.cs" />
<Compile Include="Managers\Event\EventList.cs" />
<Compile Include="Managers\Event\EventManager.cs" />
<Compile Include="Managers\Event\EventShimProgrammableBlock.cs" />
<Compile Include="Managers\Event\IEventList.cs" />
<Compile Include="Event\EventList.cs" />
<Compile Include="Event\EventManager.cs" />
<Compile Include="Event\EventShimProgrammableBlock.cs" />
<Compile Include="Event\IEventList.cs" />
<Compile Include="Managers\KeenLogManager.cs" />
<Compile Include="Managers\PatchManager\AssemblyMemory.cs" />
<Compile Include="Managers\PatchManager\DecoratedMethod.cs" />
@@ -179,12 +180,14 @@
<Compile Include="Managers\PatchManager\MSIL\MsilOperandInline.cs" />
<Compile Include="Managers\PatchManager\MSIL\MsilOperandSwitch.cs" />
<Compile Include="Managers\PatchManager\MethodRewritePattern.cs" />
<Compile Include="Managers\PatchManager\PatchShimAttribute.cs" />
<Compile Include="Managers\PatchManager\PatchContext.cs" />
<Compile Include="Managers\PatchManager\PatchManager.cs" />
<Compile Include="Managers\PatchManager\PatchPriorityAttribute.cs" />
<Compile Include="Managers\PatchManager\Transpile\LoggingILGenerator.cs" />
<Compile Include="Managers\PatchManager\Transpile\MethodContext.cs" />
<Compile Include="Managers\PatchManager\Transpile\MethodTranspiler.cs" />
<Compile Include="Patches\GameStatePatchShim.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SaveGameStatus.cs" />
<Compile Include="Collections\KeyTree.cs" />

View File

@@ -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());
}
/// <summary>
@@ -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
/// <inheritdoc />
public virtual void Update()
{
GetManager<IPluginManager>().UpdatePlugins();
Managers.GetManager<IPluginManager>().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
/// <inheritdoc/>
public event TorchGameStateChangedDel GameStateChanged;
#region GameStateInjecting
private static class GameStateInjector
private static readonly HashSet<Assembly> _registeredCoreAssemblies = new HashSet<Assembly>();
/// <summary>
/// Registers a core (Torch) assembly with the system, including its
/// <see cref="EventManager"/> shims, <see cref="PatchManager"/> shims, and <see cref="ReflectedManager"/> components.
/// </summary>
/// <param name="asm">Assembly to register</param>
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<Assembly> _registeredAssemblies = new HashSet<Assembly>();
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<Assembly> _registeredAuxAssemblies = new HashSet<Assembly>();
/// <summary>
/// Registers an auxillary (plugin) assembly with the system, including its
/// <see cref="ReflectedManager"/> related components.
/// </summary>
/// <param name="asm">Assembly to register</param>
internal static void RegisterAuxAssembly(Assembly asm)
{
lock (_registeredAuxAssemblies)
if (_registeredAuxAssemblies.Add(asm))
{
ReflectedManager.Process(asm);
}
}
}

View File

@@ -398,43 +398,20 @@ namespace Torch.Utils
/// <summary>
/// Automatically calls <see cref="ReflectedManager.Process(Assembly)"/> for every assembly already loaded, and every assembly that is loaded in the future.
/// </summary>
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"
};
/// <summary>
/// Registers the assembly load event and loads every already existing assembly.
/// </summary>
public void Attach()
{
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
Process(asm);
AppDomain.CurrentDomain.AssemblyLoad += CurrentDomain_AssemblyLoad;
}
/// <summary>
/// Deregisters the assembly load event
/// </summary>
public void Detach()
{
AppDomain.CurrentDomain.AssemblyLoad -= CurrentDomain_AssemblyLoad;
}
private void CurrentDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args)
{
Process(args.LoadedAssembly);
}
private static readonly HashSet<Type> _processedTypes = new HashSet<Type>();
/// <summary>
/// Ensures all reflected fields and methods contained in the given type are initialized
/// </summary>
/// <param name="t">Type to process</param>
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 <see cref="Process(Type)"/>
/// </summary>
/// <param name="asm">Assembly to process</param>
public static void Process(Assembly asm)
internal static void Process(Assembly asm)
{
foreach (Type type in asm.GetTypes())
Process(type);