Merge branch 'event-mgr' into staging
This commit is contained in:
28
Torch.API/Event/EventHandlerAttribute.cs
Normal file
28
Torch.API/Event/EventHandlerAttribute.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Torch.API.Event
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Attribute indicating that a method should be invoked when the event occurs.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public class EventHandlerAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Events are executed from low priority to high priority.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// While this may seem unintuitive this gives the high priority events the final say on changing/canceling events.
|
||||||
|
/// </remarks>
|
||||||
|
public int Priority { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies if this handler should ignore a consumed event.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If <see cref="SkipCancelled"/> is <em>true</em> and the event is cancelled by a lower priority handler this handler won't be invoked.
|
||||||
|
/// </remarks>
|
||||||
|
/// <seealso cref="IEvent.Cancelled"/>
|
||||||
|
public bool SkipCancelled { get; set; } = false;
|
||||||
|
}
|
||||||
|
}
|
11
Torch.API/Event/IEvent.cs
Normal file
11
Torch.API/Event/IEvent.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Torch.API.Event
|
||||||
|
{
|
||||||
|
public interface IEvent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An event that has been cancelled will no be processed in the default manner.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="EventHandlerAttribute.SkipCancelled"/>
|
||||||
|
bool Cancelled { get; }
|
||||||
|
}
|
||||||
|
}
|
9
Torch.API/Event/IEventHandler.cs
Normal file
9
Torch.API/Event/IEventHandler.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Torch.API.Event
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface used to tag an event handler. This does <b>not</b> register it with the event manager.
|
||||||
|
/// </summary>
|
||||||
|
public interface IEventHandler
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
27
Torch.API/Event/IEventManager.cs
Normal file
27
Torch.API/Event/IEventManager.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Torch.API.Event
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Manager class responsible for registration of event handlers.
|
||||||
|
/// </summary>
|
||||||
|
public interface IEventManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Registers all event handler methods contained in the given instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler">Instance to register</param>
|
||||||
|
/// <returns><b>true</b> if added, <b>false</b> otherwise</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
bool RegisterHandler(IEventHandler handler);
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregisters all event handler methods contained in the given instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler">Instance to unregister</param>
|
||||||
|
/// <returns><b>true</b> if removed, <b>false</b> otherwise</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
bool UnregisterHandler(IEventHandler handler);
|
||||||
|
}
|
||||||
|
}
|
@@ -120,6 +120,11 @@ namespace Torch.API
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ITorchServer : ITorchBase
|
public interface ITorchServer : ITorchBase
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The current <see cref="ServerState"/>
|
||||||
|
/// </summary>
|
||||||
|
ServerState State { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Path of the dedicated instance folder.
|
/// Path of the dedicated instance folder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@@ -163,10 +163,14 @@
|
|||||||
<Compile Include="ITorchConfig.cs" />
|
<Compile Include="ITorchConfig.cs" />
|
||||||
<Compile Include="Managers\DependencyManagerExtensions.cs" />
|
<Compile Include="Managers\DependencyManagerExtensions.cs" />
|
||||||
<Compile Include="Managers\DependencyProviderExtensions.cs" />
|
<Compile Include="Managers\DependencyProviderExtensions.cs" />
|
||||||
|
<Compile Include="Event\EventHandlerAttribute.cs" />
|
||||||
|
<Compile Include="Event\IEvent.cs" />
|
||||||
|
<Compile Include="Event\IEventHandler.cs" />
|
||||||
<Compile Include="Managers\IChatManagerClient.cs" />
|
<Compile Include="Managers\IChatManagerClient.cs" />
|
||||||
<Compile Include="Managers\IChatManagerServer.cs" />
|
<Compile Include="Managers\IChatManagerServer.cs" />
|
||||||
<Compile Include="Managers\IDependencyManager.cs" />
|
<Compile Include="Managers\IDependencyManager.cs" />
|
||||||
<Compile Include="Managers\IDependencyProvider.cs" />
|
<Compile Include="Managers\IDependencyProvider.cs" />
|
||||||
|
<Compile Include="Event\IEventManager.cs" />
|
||||||
<Compile Include="Managers\IManager.cs" />
|
<Compile Include="Managers\IManager.cs" />
|
||||||
<Compile Include="Managers\IMultiplayerManagerClient.cs" />
|
<Compile Include="Managers\IMultiplayerManagerClient.cs" />
|
||||||
<Compile Include="Managers\IMultiplayerManagerBase.cs" />
|
<Compile Include="Managers\IMultiplayerManagerBase.cs" />
|
||||||
|
@@ -26,8 +26,7 @@ namespace Torch.Server
|
|||||||
login anonymous
|
login anonymous
|
||||||
app_update 298740
|
app_update 298740
|
||||||
quit";
|
quit";
|
||||||
|
|
||||||
private TorchAssemblyResolver _resolver;
|
|
||||||
private TorchConfig _config;
|
private TorchConfig _config;
|
||||||
private TorchServer _server;
|
private TorchServer _server;
|
||||||
private string _basePath;
|
private string _basePath;
|
||||||
@@ -50,7 +49,6 @@ quit";
|
|||||||
if (!args.Contains("-noupdate"))
|
if (!args.Contains("-noupdate"))
|
||||||
RunSteamCmd();
|
RunSteamCmd();
|
||||||
|
|
||||||
_resolver = new TorchAssemblyResolver(Path.Combine(_basePath, "DedicatedServer64"));
|
|
||||||
_config = InitConfig();
|
_config = InitConfig();
|
||||||
if (!_config.Parse(args))
|
if (!_config.Parse(args))
|
||||||
return false;
|
return false;
|
||||||
@@ -94,8 +92,6 @@ quit";
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
_server.Start();
|
_server.Start();
|
||||||
|
|
||||||
_resolver?.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TorchConfig InitConfig()
|
private TorchConfig InitConfig()
|
||||||
|
@@ -44,13 +44,16 @@ namespace Torch.Server
|
|||||||
var binDir = Path.Combine(workingDir, "DedicatedServer64");
|
var binDir = Path.Combine(workingDir, "DedicatedServer64");
|
||||||
Directory.SetCurrentDirectory(workingDir);
|
Directory.SetCurrentDirectory(workingDir);
|
||||||
|
|
||||||
|
if (!TorchLauncher.IsTorchWrapped())
|
||||||
|
{
|
||||||
|
TorchLauncher.Launch(Assembly.GetEntryAssembly().FullName,args, binDir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!Environment.UserInteractive)
|
if (!Environment.UserInteractive)
|
||||||
{
|
{
|
||||||
using (var service = new TorchService())
|
using (var service = new TorchService())
|
||||||
using (new TorchAssemblyResolver(binDir))
|
|
||||||
{
|
|
||||||
ServiceBase.Run(service);
|
ServiceBase.Run(service);
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,12 +3,14 @@ using Sandbox.Engine.Utils;
|
|||||||
using Sandbox.Game;
|
using Sandbox.Game;
|
||||||
using Sandbox.Game.World;
|
using Sandbox.Game.World;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Xml.Serialization.GeneratedAssembly;
|
using Microsoft.Xml.Serialization.GeneratedAssembly;
|
||||||
@@ -194,16 +196,62 @@ namespace Torch.Server
|
|||||||
((TorchServer)state).Invoke(() => mre.Set());
|
((TorchServer)state).Invoke(() => mre.Set());
|
||||||
if (!mre.WaitOne(TimeSpan.FromSeconds(Instance.Config.TickTimeout)))
|
if (!mre.WaitOne(TimeSpan.FromSeconds(Instance.Config.TickTimeout)))
|
||||||
{
|
{
|
||||||
var mainThread = MySandboxGame.Static.UpdateThread;
|
Log.Error(DumpFrozenThread(MySandboxGame.Static.UpdateThread));
|
||||||
if (mainThread.IsAlive)
|
throw new TimeoutException($"Server watchdog detected that the server was frozen for at least {((TorchServer)state).Config.TickTimeout} seconds.");
|
||||||
mainThread.Suspend();
|
|
||||||
var stackTrace = new StackTrace(mainThread, true);
|
|
||||||
throw new TimeoutException($"Server watchdog detected that the server was frozen for at least {((TorchServer)state).Config.TickTimeout} seconds.\n{stackTrace}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Debug("Server watchdog responded");
|
Log.Debug("Server watchdog responded");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string DumpFrozenThread(Thread thread, int traces = 3, int pause = 5000)
|
||||||
|
{
|
||||||
|
var stacks = new List<string>(traces);
|
||||||
|
var totalSize = 0;
|
||||||
|
for (var i = 0; i < traces; i++)
|
||||||
|
{
|
||||||
|
string dump = DumpStack(thread).ToString();
|
||||||
|
totalSize += dump.Length;
|
||||||
|
stacks.Add(dump);
|
||||||
|
Thread.Sleep(pause);
|
||||||
|
}
|
||||||
|
string commonPrefix = StringUtils.CommonSuffix(stacks);
|
||||||
|
// Advance prefix to include the line terminator.
|
||||||
|
commonPrefix = commonPrefix.Substring(commonPrefix.IndexOf('\n') + 1);
|
||||||
|
|
||||||
|
var result = new StringBuilder(totalSize - (stacks.Count - 1) * commonPrefix.Length);
|
||||||
|
result.AppendLine($"Frozen thread dump {thread.Name}");
|
||||||
|
result.AppendLine("Common prefix:").AppendLine(commonPrefix);
|
||||||
|
for (var i = 0; i < stacks.Count; i++)
|
||||||
|
if (stacks[i].Length > commonPrefix.Length)
|
||||||
|
{
|
||||||
|
result.AppendLine($"Suffix {i}");
|
||||||
|
result.AppendLine(stacks[i].Substring(0, stacks[i].Length - commonPrefix.Length));
|
||||||
|
}
|
||||||
|
return result.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StackTrace DumpStack(Thread thread)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
thread.Suspend();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
var stack = new StackTrace(thread, true);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
thread.Resume();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Stop()
|
public override void Stop()
|
||||||
{
|
{
|
||||||
@@ -253,28 +301,32 @@ namespace Torch.Server
|
|||||||
/// <param name="callerId">Caller of the save operation</param>
|
/// <param name="callerId">Caller of the save operation</param>
|
||||||
private void SaveCompleted(SaveGameStatus statusCode, long callerId = 0)
|
private void SaveCompleted(SaveGameStatus statusCode, long callerId = 0)
|
||||||
{
|
{
|
||||||
|
string response = null;
|
||||||
switch (statusCode)
|
switch (statusCode)
|
||||||
{
|
{
|
||||||
case SaveGameStatus.Success:
|
case SaveGameStatus.Success:
|
||||||
Log.Info("Save completed.");
|
Log.Info("Save completed.");
|
||||||
// TODO
|
response = "Saved game.";
|
||||||
// Multiplayer.SendMessage("Saved game.", playerId: callerId);
|
|
||||||
break;
|
break;
|
||||||
case SaveGameStatus.SaveInProgress:
|
case SaveGameStatus.SaveInProgress:
|
||||||
Log.Error("Save failed, a save is already in progress.");
|
Log.Error("Save failed, a save is already in progress.");
|
||||||
// Multiplayer.SendMessage("Save failed, a save is already in progress.", playerId: callerId, font: MyFontEnum.Red);
|
response = "Save failed, a save is already in progress.";
|
||||||
break;
|
break;
|
||||||
case SaveGameStatus.GameNotReady:
|
case SaveGameStatus.GameNotReady:
|
||||||
Log.Error("Save failed, game was not ready.");
|
Log.Error("Save failed, game was not ready.");
|
||||||
// Multiplayer.SendMessage("Save failed, game was not ready.", playerId: callerId, font: MyFontEnum.Red);
|
response = "Save failed, game was not ready.";
|
||||||
break;
|
break;
|
||||||
case SaveGameStatus.TimedOut:
|
case SaveGameStatus.TimedOut:
|
||||||
Log.Error("Save failed, save timed out.");
|
Log.Error("Save failed, save timed out.");
|
||||||
// Multiplayer.SendMessage("Save failed, save timed out.", playerId: callerId, font: MyFontEnum.Red);
|
response = "Save failed, save timed out.";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (MySession.Static.Players.TryGetPlayerId(callerId, out MyPlayer.PlayerId result))
|
||||||
|
{
|
||||||
|
Managers.GetManager<IChatManagerServer>()?.SendMessageAsOther("Server", response, statusCode == SaveGameStatus.Success ? MyFontEnum.Green : MyFontEnum.Red, result.SteamId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
147
Torch/Event/EventList.cs
Normal file
147
Torch/Event/EventList.cs
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading;
|
||||||
|
using Torch.API.Event;
|
||||||
|
|
||||||
|
namespace Torch.Event
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an ordered list of callbacks.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Event type</typeparam>
|
||||||
|
public class EventList<T> : IEventList where T : IEvent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate type for this event list
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="evt">Event</param>
|
||||||
|
public delegate void DelEventHandler(ref T evt);
|
||||||
|
|
||||||
|
private struct EventHandlerData
|
||||||
|
{
|
||||||
|
internal readonly DelEventHandler _event;
|
||||||
|
internal readonly EventHandlerAttribute _attribute;
|
||||||
|
|
||||||
|
internal EventHandlerData(MethodInfo method, object instance)
|
||||||
|
{
|
||||||
|
_event = (DelEventHandler)Delegate.CreateDelegate(typeof(DelEventHandler), instance, method, true);
|
||||||
|
_attribute = method.GetCustomAttribute<EventHandlerAttribute>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal void Raise(ref T evt)
|
||||||
|
{
|
||||||
|
if (!_attribute.SkipCancelled || !evt.Cancelled)
|
||||||
|
_event(ref evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _dispatchersDirty = false;
|
||||||
|
private readonly List<EventHandlerData> _dispatchers = new List<EventHandlerData>();
|
||||||
|
|
||||||
|
private int _bakedCount;
|
||||||
|
private EventHandlerData[] _bakedDispatcher;
|
||||||
|
|
||||||
|
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void AddHandler(MethodInfo method, IEventHandler instance)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_lock.EnterWriteLock();
|
||||||
|
_dispatchers.Add(new EventHandlerData(method, instance));
|
||||||
|
_dispatchersDirty = true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int RemoveHandlers(IEventHandler instance)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_lock.EnterWriteLock();
|
||||||
|
var removeCount = 0;
|
||||||
|
for (var i = 0; i < _dispatchers.Count; i++)
|
||||||
|
if (_dispatchers[i]._event.Target == instance)
|
||||||
|
{
|
||||||
|
_dispatchers.RemoveAtFast(i);
|
||||||
|
removeCount++;
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
if (removeCount > 0)
|
||||||
|
{
|
||||||
|
_dispatchersDirty = true;
|
||||||
|
_dispatchers.RemoveRange(_dispatchers.Count - removeCount, removeCount);
|
||||||
|
}
|
||||||
|
return removeCount;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void Bake()
|
||||||
|
{
|
||||||
|
if (!_dispatchersDirty && _bakedDispatcher != null)
|
||||||
|
return;
|
||||||
|
if (_bakedDispatcher == null || _dispatchers.Count > _bakedDispatcher.Length
|
||||||
|
|| _bakedDispatcher.Length * 5 / 4 < _dispatchers.Count)
|
||||||
|
_bakedDispatcher = new EventHandlerData[_dispatchers.Count];
|
||||||
|
_bakedCount = _dispatchers.Count;
|
||||||
|
for (var i = 0; i < _dispatchers.Count; i++)
|
||||||
|
_bakedDispatcher[i] = _dispatchers[i];
|
||||||
|
Array.Sort(_bakedDispatcher, 0, _bakedCount, EventHandlerDataComparer.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raises this event for all event handlers, passing the reference to all of them
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="evt">event to raise</param>
|
||||||
|
public void RaiseEvent(ref T evt)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_lock.EnterUpgradeableReadLock();
|
||||||
|
if (_dispatchersDirty)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_lock.EnterWriteLock();
|
||||||
|
Bake();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitWriteLock();
|
||||||
|
}
|
||||||
|
for (var i = 0; i < _bakedCount; i++)
|
||||||
|
_bakedDispatcher[i].Raise(ref evt);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitUpgradeableReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class EventHandlerDataComparer : IComparer<EventHandlerData>
|
||||||
|
{
|
||||||
|
internal static readonly EventHandlerDataComparer Instance = new EventHandlerDataComparer();
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IComparer{EventHandlerData}.Compare"/>
|
||||||
|
/// <remarks>
|
||||||
|
/// This sorts event handlers with ascending priority order.
|
||||||
|
/// </remarks>
|
||||||
|
public int Compare(EventHandlerData x, EventHandlerData y)
|
||||||
|
{
|
||||||
|
return x._attribute.Priority.CompareTo(y._attribute.Priority);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
191
Torch/Event/EventManager.cs
Normal file
191
Torch/Event/EventManager.cs
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using NLog;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.API.Event;
|
||||||
|
using Torch.Managers;
|
||||||
|
|
||||||
|
namespace Torch.Event
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Manager class responsible for managing registration and dispatching of events.
|
||||||
|
/// </summary>
|
||||||
|
public class EventManager : Manager, IEventManager
|
||||||
|
{
|
||||||
|
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
private static readonly Dictionary<Type, IEventList> _eventLists = new Dictionary<Type, IEventList>();
|
||||||
|
|
||||||
|
internal static void AddDispatchShims(Assembly asm)
|
||||||
|
{
|
||||||
|
foreach (Type type in asm.GetTypes())
|
||||||
|
if (type.HasAttribute<EventShimAttribute>())
|
||||||
|
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;
|
||||||
|
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<>))
|
||||||
|
{
|
||||||
|
Type eventType = field.FieldType.GenericTypeArguments[0];
|
||||||
|
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));
|
||||||
|
listsFound++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (listsFound == 0)
|
||||||
|
_log.Warn($"Registering type {type.FullName} as an event dispatch type, even though it has no event lists.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all event handler methods declared by the given type and its base types.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="exploreType">Type to explore</param>
|
||||||
|
/// <returns>All event handler methods</returns>
|
||||||
|
private static IEnumerable<MethodInfo> EventHandlers(Type exploreType)
|
||||||
|
{
|
||||||
|
IEnumerable<MethodInfo> enumerable = exploreType.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
|
||||||
|
.Where(x =>
|
||||||
|
{
|
||||||
|
var attr = x.GetCustomAttribute<EventHandlerAttribute>();
|
||||||
|
if (attr == null)
|
||||||
|
return false;
|
||||||
|
ParameterInfo[] ps = x.GetParameters();
|
||||||
|
if (ps.Length != 1)
|
||||||
|
return false;
|
||||||
|
return ps[0].ParameterType.IsByRef && typeof(IEvent).IsAssignableFrom(ps[0].ParameterType);
|
||||||
|
});
|
||||||
|
return exploreType.BaseType != null ? enumerable.Concat(EventHandlers(exploreType.BaseType)) : enumerable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
private static void RegisterHandlerInternal(IEventHandler instance)
|
||||||
|
{
|
||||||
|
foreach (MethodInfo handler in EventHandlers(instance.GetType()))
|
||||||
|
{
|
||||||
|
Type eventType = handler.GetParameters()[0].ParameterType;
|
||||||
|
if (eventType.IsInterface)
|
||||||
|
{
|
||||||
|
var foundList = false;
|
||||||
|
foreach (KeyValuePair<Type, IEventList> kv in _eventLists)
|
||||||
|
if (eventType.IsAssignableFrom(kv.Key))
|
||||||
|
{
|
||||||
|
kv.Value.AddHandler(handler, instance);
|
||||||
|
foundList = true;
|
||||||
|
}
|
||||||
|
if (foundList)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (_eventLists.TryGetValue(eventType, out IEventList list))
|
||||||
|
{
|
||||||
|
list.AddHandler(handler, instance);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_log.Error($"Unable to find event handler list for event type {eventType.FullName}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregisters all handlers owned by the given instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="instance">Instance</param>
|
||||||
|
private static void UnregisterHandlerInternal(IEventHandler instance)
|
||||||
|
{
|
||||||
|
foreach (IEventList list in _eventLists.Values)
|
||||||
|
list.RemoveHandlers(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<Assembly, HashSet<IEventHandler>> _registeredHandlers = new Dictionary<Assembly, HashSet<IEventHandler>>();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public EventManager(ITorchBase torchInstance) : base(torchInstance)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers all event handler methods contained in the given instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler">Instance to register</param>
|
||||||
|
/// <returns><b>true</b> if added, <b>false</b> otherwise</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
public bool RegisterHandler(IEventHandler handler)
|
||||||
|
{
|
||||||
|
Assembly caller = Assembly.GetCallingAssembly();
|
||||||
|
lock (_registeredHandlers)
|
||||||
|
{
|
||||||
|
if (!_registeredHandlers.TryGetValue(caller, out HashSet<IEventHandler> handlers))
|
||||||
|
_registeredHandlers.Add(caller, handlers = new HashSet<IEventHandler>());
|
||||||
|
if (handlers.Add(handler))
|
||||||
|
{
|
||||||
|
RegisterHandlerInternal(handler);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregisters all event handler methods contained in the given instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler">Instance to unregister</param>
|
||||||
|
/// <returns><b>true</b> if removed, <b>false</b> otherwise</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
public bool UnregisterHandler(IEventHandler handler)
|
||||||
|
{
|
||||||
|
Assembly caller = Assembly.GetCallingAssembly();
|
||||||
|
lock (_registeredHandlers)
|
||||||
|
{
|
||||||
|
if (!_registeredHandlers.TryGetValue(caller, out HashSet<IEventHandler> handlers))
|
||||||
|
return false;
|
||||||
|
if (handlers.Remove(handler))
|
||||||
|
{
|
||||||
|
UnregisterHandlerInternal(handler);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregisters all handlers owned by the given assembly.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="asm">Assembly to unregister</param>
|
||||||
|
/// <param name="callback">Optional callback invoked before a handler is unregistered. Ignored if null</param>
|
||||||
|
/// <returns>the number of handlers that were unregistered</returns>
|
||||||
|
internal int UnregisterAllHandlers(Assembly asm, Action<IEventHandler> callback = null)
|
||||||
|
{
|
||||||
|
lock (_registeredHandlers)
|
||||||
|
{
|
||||||
|
if (!_registeredHandlers.TryGetValue(asm, out HashSet<IEventHandler> handlers))
|
||||||
|
return 0;
|
||||||
|
foreach (IEventHandler k in handlers)
|
||||||
|
{
|
||||||
|
callback?.Invoke(k);
|
||||||
|
UnregisterHandlerInternal(k);
|
||||||
|
}
|
||||||
|
int count = handlers.Count;
|
||||||
|
handlers.Clear();
|
||||||
|
_registeredHandlers.Remove(asm);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
Torch/Event/EventShimAttribute.cs
Normal file
20
Torch/Event/EventShimAttribute.cs
Normal 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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
25
Torch/Event/IEventList.cs
Normal file
25
Torch/Event/IEventList.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using Torch.API.Event;
|
||||||
|
|
||||||
|
namespace Torch.Event
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the interface for adding and removing from an ordered list of callbacks.
|
||||||
|
/// </summary>
|
||||||
|
public interface IEventList
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds an event handler for the given method, on the given instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="method">Handler method</param>
|
||||||
|
/// <param name="instance">Instance to invoke the handler on</param>
|
||||||
|
void AddHandler(MethodInfo method, IEventHandler instance);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all event handlers invoked on the given instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="instance">Instance to remove event handlers for</param>
|
||||||
|
/// <returns>The number of event handlers removed</returns>
|
||||||
|
int RemoveHandlers(IEventHandler instance);
|
||||||
|
}
|
||||||
|
}
|
@@ -79,8 +79,9 @@ namespace Torch.Managers.PatchManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private const string INSTANCE_PARAMETER = "__instance";
|
public const string INSTANCE_PARAMETER = "__instance";
|
||||||
private const string RESULT_PARAMETER = "__result";
|
public const string RESULT_PARAMETER = "__result";
|
||||||
|
public const string PREFIX_SKIPPED_PARAMETER = "__prefixSkipped";
|
||||||
|
|
||||||
#pragma warning disable 649
|
#pragma warning disable 649
|
||||||
[ReflectedStaticMethod(Type = typeof(RuntimeHelpers), Name = "_CompileMethod", OverrideTypeNames = new[] { "System.IRuntimeMethodInfo" })]
|
[ReflectedStaticMethod(Type = typeof(RuntimeHelpers), Name = "_CompileMethod", OverrideTypeNames = new[] { "System.IRuntimeMethodInfo" })]
|
||||||
@@ -118,7 +119,7 @@ namespace Torch.Managers.PatchManager
|
|||||||
var specialVariables = new Dictionary<string, LocalBuilder>();
|
var specialVariables = new Dictionary<string, LocalBuilder>();
|
||||||
|
|
||||||
Label labelAfterOriginalContent = target.DefineLabel();
|
Label labelAfterOriginalContent = target.DefineLabel();
|
||||||
Label labelAfterOriginalReturn = target.DefineLabel();
|
Label labelSkipMethodContent = target.DefineLabel();
|
||||||
|
|
||||||
|
|
||||||
Type returnType = _method is MethodInfo meth ? meth.ReturnType : typeof(void);
|
Type returnType = _method is MethodInfo meth ? meth.ReturnType : typeof(void);
|
||||||
@@ -131,6 +132,13 @@ namespace Torch.Managers.PatchManager
|
|||||||
resultVariable = target.DeclareLocal(returnType);
|
resultVariable = target.DeclareLocal(returnType);
|
||||||
}
|
}
|
||||||
resultVariable?.SetToDefault(target);
|
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)
|
if (resultVariable != null)
|
||||||
specialVariables.Add(RESULT_PARAMETER, resultVariable);
|
specialVariables.Add(RESULT_PARAMETER, resultVariable);
|
||||||
@@ -140,7 +148,7 @@ namespace Torch.Managers.PatchManager
|
|||||||
{
|
{
|
||||||
EmitMonkeyCall(target, prefix, specialVariables);
|
EmitMonkeyCall(target, prefix, specialVariables);
|
||||||
if (prefix.ReturnType == typeof(bool))
|
if (prefix.ReturnType == typeof(bool))
|
||||||
target.Emit(OpCodes.Brfalse, labelAfterOriginalReturn);
|
target.Emit(OpCodes.Brfalse, labelSkipMethodContent);
|
||||||
else if (prefix.ReturnType != typeof(void))
|
else if (prefix.ReturnType != typeof(void))
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
$"Prefixes must return void or bool. {prefix.DeclaringType?.FullName}.{prefix.Name} returns {prefix.ReturnType}");
|
$"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);
|
target.MarkLabel(labelAfterOriginalContent);
|
||||||
if (resultVariable != null)
|
if (resultVariable != null)
|
||||||
target.Emit(OpCodes.Stloc, resultVariable);
|
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");
|
target.EmitComment("Suffixes Begin");
|
||||||
foreach (MethodInfo suffix in Suffixes)
|
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");
|
throw new Exception("Can't use an instance parameter for a static method");
|
||||||
target.Emit(OpCodes.Ldarg_0);
|
target.Emit(OpCodes.Ldarg_0);
|
||||||
break;
|
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:
|
case RESULT_PARAMETER:
|
||||||
var retType = param.ParameterType.IsByRef
|
Type retType = param.ParameterType.IsByRef
|
||||||
? param.ParameterType.GetElementType()
|
? param.ParameterType.GetElementType()
|
||||||
: param.ParameterType;
|
: param.ParameterType;
|
||||||
if (retType == null || !retType.IsAssignableFrom(specialVariables[RESULT_PARAMETER].LocalType))
|
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]);
|
target.Emit(param.ParameterType.IsByRef ? OpCodes.Ldloca : OpCodes.Ldloc, specialVariables[RESULT_PARAMETER]);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
var declParam = _method.GetParameters().FirstOrDefault(x => x.Name == param.Name);
|
ParameterInfo declParam = _method.GetParameters().FirstOrDefault(x => x.Name == param.Name);
|
||||||
if (declParam == null)
|
if (declParam == null)
|
||||||
throw new Exception($"Parameter name {param.Name} not found");
|
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;
|
bool patchByRef = param.IsOut || param.ParameterType.IsByRef;
|
||||||
var declByRef = declParam.IsOut || declParam.ParameterType.IsByRef;
|
bool declByRef = declParam.IsOut || declParam.ParameterType.IsByRef;
|
||||||
if (patchByRef == declByRef)
|
if (patchByRef == declByRef)
|
||||||
target.Emit(OpCodes.Ldarg, paramIdx);
|
target.Emit(OpCodes.Ldarg, paramIdx);
|
||||||
else if (patchByRef)
|
else if (patchByRef)
|
||||||
|
@@ -7,7 +7,6 @@ using System.Reflection.Emit;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Torch.Managers.PatchManager.Transpile;
|
using Torch.Managers.PatchManager.Transpile;
|
||||||
using Torch.Utils;
|
using Torch.Utils;
|
||||||
using Label = System.Windows.Controls.Label;
|
|
||||||
|
|
||||||
namespace Torch.Managers.PatchManager.MSIL
|
namespace Torch.Managers.PatchManager.MSIL
|
||||||
{
|
{
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Torch.Managers.PatchManager
|
namespace Torch.Managers.PatchManager
|
||||||
@@ -6,14 +7,12 @@ namespace Torch.Managers.PatchManager
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a set of common patches that can all be reversed in a single step.
|
/// Represents a set of common patches that can all be reversed in a single step.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PatchContext
|
public sealed class PatchContext
|
||||||
{
|
{
|
||||||
private readonly PatchManager _replacer;
|
|
||||||
private readonly Dictionary<MethodBase, MethodRewritePattern> _rewritePatterns = new Dictionary<MethodBase, MethodRewritePattern>();
|
private readonly Dictionary<MethodBase, MethodRewritePattern> _rewritePatterns = new Dictionary<MethodBase, MethodRewritePattern>();
|
||||||
|
|
||||||
internal PatchContext(PatchManager replacer)
|
internal PatchContext()
|
||||||
{
|
{
|
||||||
_replacer = replacer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -25,12 +24,22 @@ namespace Torch.Managers.PatchManager
|
|||||||
{
|
{
|
||||||
if (_rewritePatterns.TryGetValue(method, out MethodRewritePattern pattern))
|
if (_rewritePatterns.TryGetValue(method, out MethodRewritePattern pattern))
|
||||||
return pattern;
|
return pattern;
|
||||||
MethodRewritePattern parent = _replacer.GetPattern(method);
|
MethodRewritePattern parent = PatchManager.GetPatternInternal(method);
|
||||||
var res = new MethodRewritePattern(parent);
|
var res = new MethodRewritePattern(parent);
|
||||||
_rewritePatterns.Add(method, res);
|
_rewritePatterns.Add(method, res);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all methods that this context has patched
|
||||||
|
/// </summary>
|
||||||
|
internal IEnumerable<MethodBase> PatchedMethods => _rewritePatterns
|
||||||
|
.Where(x => x.Value.Prefixes.Count > 0 || x.Value.Suffixes.Count > 0 || x.Value.Transpilers.Count > 0)
|
||||||
|
.Select(x => x.Key);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all patches in this context
|
||||||
|
/// </summary>
|
||||||
internal void RemoveAll()
|
internal void RemoveAll()
|
||||||
{
|
{
|
||||||
foreach (MethodRewritePattern pattern in _rewritePatterns.Values)
|
foreach (MethodRewritePattern pattern in _rewritePatterns.Values)
|
||||||
|
@@ -1,6 +1,10 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using NLog;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
|
using Torch.Managers.PatchManager.Transpile;
|
||||||
|
|
||||||
namespace Torch.Managers.PatchManager
|
namespace Torch.Managers.PatchManager
|
||||||
{
|
{
|
||||||
@@ -9,16 +13,69 @@ namespace Torch.Managers.PatchManager
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class PatchManager : Manager
|
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 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);
|
||||||
|
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>
|
/// <summary>
|
||||||
/// Creates a new patch manager. Only have one active at a time.
|
/// Creates a new patch manager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="torchInstance"></param>
|
/// <param name="torchInstance"></param>
|
||||||
public PatchManager(ITorchBase torchInstance) : base(torchInstance)
|
public PatchManager(ITorchBase torchInstance) : base(torchInstance)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Dictionary<MethodBase, DecoratedMethod> _rewritePatterns = new Dictionary<MethodBase, DecoratedMethod>();
|
private static readonly Dictionary<MethodBase, DecoratedMethod> _rewritePatterns = new Dictionary<MethodBase, DecoratedMethod>();
|
||||||
private readonly HashSet<PatchContext> _contexts = new HashSet<PatchContext>();
|
private static readonly Dictionary<Assembly, List<PatchContext>> _contexts = new Dictionary<Assembly, List<PatchContext>>();
|
||||||
|
// ReSharper disable once CollectionNeverQueried.Local because we may want this in the future.
|
||||||
|
private static readonly List<PatchContext> _coreContexts = new List<PatchContext>();
|
||||||
|
|
||||||
|
/// <inheritdoc cref="GetPattern"/>
|
||||||
|
internal static MethodRewritePattern GetPatternInternal(MethodBase method)
|
||||||
|
{
|
||||||
|
lock (_rewritePatterns)
|
||||||
|
{
|
||||||
|
if (_rewritePatterns.TryGetValue(method, out DecoratedMethod pattern))
|
||||||
|
return pattern;
|
||||||
|
var res = new DecoratedMethod(method);
|
||||||
|
_rewritePatterns.Add(method, res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the rewrite pattern for the given method, creating one if it doesn't exist.
|
/// Gets the rewrite pattern for the given method, creating one if it doesn't exist.
|
||||||
@@ -27,21 +84,24 @@ namespace Torch.Managers.PatchManager
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public MethodRewritePattern GetPattern(MethodBase method)
|
public MethodRewritePattern GetPattern(MethodBase method)
|
||||||
{
|
{
|
||||||
if (_rewritePatterns.TryGetValue(method, out DecoratedMethod pattern))
|
return GetPatternInternal(method);
|
||||||
return pattern;
|
|
||||||
var res = new DecoratedMethod(method);
|
|
||||||
_rewritePatterns.Add(method, res);
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="PatchContext"/> used for tracking changes. A call to <see cref="Commit"/> will apply the patches.
|
/// Creates a new <see cref="PatchContext"/> used for tracking changes. A call to <see cref="Commit"/> will apply the patches.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public PatchContext AcquireContext()
|
public PatchContext AcquireContext()
|
||||||
{
|
{
|
||||||
var context = new PatchContext(this);
|
Assembly assembly = Assembly.GetCallingAssembly();
|
||||||
_contexts.Add(context);
|
var context = new PatchContext();
|
||||||
|
lock (_contexts)
|
||||||
|
{
|
||||||
|
if (!_contexts.TryGetValue(assembly, out List<PatchContext> localContexts))
|
||||||
|
_contexts.Add(assembly, localContexts = new List<PatchContext>());
|
||||||
|
localContexts.Add(context);
|
||||||
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,10 +109,51 @@ namespace Torch.Managers.PatchManager
|
|||||||
/// Frees the given context, and unregister all patches from it. A call to <see cref="Commit"/> will apply the unpatching operation.
|
/// Frees the given context, and unregister all patches from it. A call to <see cref="Commit"/> will apply the unpatching operation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context">Context to remove</param>
|
/// <param name="context">Context to remove</param>
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public void FreeContext(PatchContext context)
|
public void FreeContext(PatchContext context)
|
||||||
{
|
{
|
||||||
|
Assembly assembly = Assembly.GetCallingAssembly();
|
||||||
context.RemoveAll();
|
context.RemoveAll();
|
||||||
_contexts.Remove(context);
|
lock (_contexts)
|
||||||
|
{
|
||||||
|
if (_contexts.TryGetValue(assembly, out List<PatchContext> localContexts))
|
||||||
|
localContexts.Remove(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Frees all contexts owned by the given assembly. A call to <see cref="Commit"/> will apply the unpatching operation.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assembly">Assembly to retrieve owned contexts for</param>
|
||||||
|
/// <param name="callback">Callback to run for before each context is freed, ignored if null.</param>
|
||||||
|
/// <returns>number of contexts freed</returns>
|
||||||
|
internal int FreeAllContexts(Assembly assembly, Action<PatchContext> callback = null)
|
||||||
|
{
|
||||||
|
List<PatchContext> localContexts;
|
||||||
|
lock (_contexts)
|
||||||
|
{
|
||||||
|
if (!_contexts.TryGetValue(assembly, out localContexts))
|
||||||
|
return 0;
|
||||||
|
_contexts.Remove(assembly);
|
||||||
|
}
|
||||||
|
if (localContexts == null)
|
||||||
|
return 0;
|
||||||
|
int count = localContexts.Count;
|
||||||
|
foreach (PatchContext k in localContexts)
|
||||||
|
{
|
||||||
|
callback?.Invoke(k);
|
||||||
|
k.RemoveAll();
|
||||||
|
}
|
||||||
|
localContexts.Clear();
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Commit"/>
|
||||||
|
internal static void CommitInternal()
|
||||||
|
{
|
||||||
|
lock (_rewritePatterns)
|
||||||
|
foreach (DecoratedMethod m in _rewritePatterns.Values)
|
||||||
|
m.Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -60,8 +161,7 @@ namespace Torch.Managers.PatchManager
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Commit()
|
public void Commit()
|
||||||
{
|
{
|
||||||
foreach (DecoratedMethod m in _rewritePatterns.Values)
|
CommitInternal();
|
||||||
m.Commit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -77,10 +177,15 @@ namespace Torch.Managers.PatchManager
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public override void Detach()
|
public override void Detach()
|
||||||
{
|
{
|
||||||
foreach (DecoratedMethod m in _rewritePatterns.Values)
|
lock (_contexts)
|
||||||
m.Revert();
|
{
|
||||||
_rewritePatterns.Clear();
|
foreach (List<PatchContext> set in _contexts.Values)
|
||||||
_contexts.Clear();
|
foreach (PatchContext ctx in set)
|
||||||
|
ctx.RemoveAll();
|
||||||
|
_contexts.Clear();
|
||||||
|
foreach (DecoratedMethod m in _rewritePatterns.Values)
|
||||||
|
m.Revert();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
Torch/Managers/PatchManager/PatchShimAttribute.cs
Normal file
20
Torch/Managers/PatchManager/PatchShimAttribute.cs
Normal 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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
75
Torch/Patches/GameStatePatchShim.cs
Normal file
75
Torch/Patches/GameStatePatchShim.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -239,7 +239,9 @@ namespace Torch.Managers
|
|||||||
{
|
{
|
||||||
var data = new byte[stream.Length];
|
var data = new byte[stream.Length];
|
||||||
stream.Read(data, 0, data.Length);
|
stream.Read(data, 0, data.Length);
|
||||||
assemblies.Add(Assembly.Load(data));
|
Assembly asm = Assembly.Load(data);
|
||||||
|
assemblies.Add(asm);
|
||||||
|
TorchBase.RegisterAuxAssembly(asm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,7 +270,8 @@ namespace Torch.Managers
|
|||||||
{
|
{
|
||||||
var data = new byte[entry.Length];
|
var data = new byte[entry.Length];
|
||||||
stream.Read(data, 0, data.Length);
|
stream.Read(data, 0, data.Length);
|
||||||
assemblies.Add(Assembly.Load(data));
|
Assembly asm = Assembly.Load(data);
|
||||||
|
TorchBase.RegisterAuxAssembly(asm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -155,11 +155,15 @@
|
|||||||
<Link>Properties\AssemblyVersion.cs</Link>
|
<Link>Properties\AssemblyVersion.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Collections\ObservableList.cs" />
|
<Compile Include="Collections\ObservableList.cs" />
|
||||||
|
<Compile Include="Event\EventShimAttribute.cs" />
|
||||||
<Compile Include="Managers\ChatManager\ChatManagerClient.cs" />
|
<Compile Include="Managers\ChatManager\ChatManagerClient.cs" />
|
||||||
<Compile Include="Managers\ChatManager\ChatManagerServer.cs" />
|
<Compile Include="Managers\ChatManager\ChatManagerServer.cs" />
|
||||||
<Compile Include="Extensions\DispatcherExtensions.cs" />
|
<Compile Include="Extensions\DispatcherExtensions.cs" />
|
||||||
<Compile Include="Extensions\ICollectionExtensions.cs" />
|
<Compile Include="Extensions\ICollectionExtensions.cs" />
|
||||||
<Compile Include="Managers\DependencyManager.cs" />
|
<Compile Include="Managers\DependencyManager.cs" />
|
||||||
|
<Compile Include="Event\EventList.cs" />
|
||||||
|
<Compile Include="Event\EventManager.cs" />
|
||||||
|
<Compile Include="Event\IEventList.cs" />
|
||||||
<Compile Include="Managers\KeenLogManager.cs" />
|
<Compile Include="Managers\KeenLogManager.cs" />
|
||||||
<Compile Include="Managers\PatchManager\AssemblyMemory.cs" />
|
<Compile Include="Managers\PatchManager\AssemblyMemory.cs" />
|
||||||
<Compile Include="Managers\PatchManager\DecoratedMethod.cs" />
|
<Compile Include="Managers\PatchManager\DecoratedMethod.cs" />
|
||||||
@@ -175,12 +179,14 @@
|
|||||||
<Compile Include="Managers\PatchManager\MSIL\MsilOperandInline.cs" />
|
<Compile Include="Managers\PatchManager\MSIL\MsilOperandInline.cs" />
|
||||||
<Compile Include="Managers\PatchManager\MSIL\MsilOperandSwitch.cs" />
|
<Compile Include="Managers\PatchManager\MSIL\MsilOperandSwitch.cs" />
|
||||||
<Compile Include="Managers\PatchManager\MethodRewritePattern.cs" />
|
<Compile Include="Managers\PatchManager\MethodRewritePattern.cs" />
|
||||||
|
<Compile Include="Managers\PatchManager\PatchShimAttribute.cs" />
|
||||||
<Compile Include="Managers\PatchManager\PatchContext.cs" />
|
<Compile Include="Managers\PatchManager\PatchContext.cs" />
|
||||||
<Compile Include="Managers\PatchManager\PatchManager.cs" />
|
<Compile Include="Managers\PatchManager\PatchManager.cs" />
|
||||||
<Compile Include="Managers\PatchManager\PatchPriorityAttribute.cs" />
|
<Compile Include="Managers\PatchManager\PatchPriorityAttribute.cs" />
|
||||||
<Compile Include="Managers\PatchManager\Transpile\LoggingILGenerator.cs" />
|
<Compile Include="Managers\PatchManager\Transpile\LoggingILGenerator.cs" />
|
||||||
<Compile Include="Managers\PatchManager\Transpile\MethodContext.cs" />
|
<Compile Include="Managers\PatchManager\Transpile\MethodContext.cs" />
|
||||||
<Compile Include="Managers\PatchManager\Transpile\MethodTranspiler.cs" />
|
<Compile Include="Managers\PatchManager\Transpile\MethodTranspiler.cs" />
|
||||||
|
<Compile Include="Patches\GameStatePatchShim.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="SaveGameStatus.cs" />
|
<Compile Include="SaveGameStatus.cs" />
|
||||||
<Compile Include="Collections\KeyTree.cs" />
|
<Compile Include="Collections\KeyTree.cs" />
|
||||||
@@ -208,6 +214,7 @@
|
|||||||
<Compile Include="Plugins\PluginManifest.cs" />
|
<Compile Include="Plugins\PluginManifest.cs" />
|
||||||
<Compile Include="Utils\Reflection.cs" />
|
<Compile Include="Utils\Reflection.cs" />
|
||||||
<Compile Include="Managers\ScriptingManager.cs" />
|
<Compile Include="Managers\ScriptingManager.cs" />
|
||||||
|
<Compile Include="Utils\StringUtils.cs" />
|
||||||
<Compile Include="Utils\TorchAssemblyResolver.cs" />
|
<Compile Include="Utils\TorchAssemblyResolver.cs" />
|
||||||
<Compile Include="Utils\ReflectedManager.cs" />
|
<Compile Include="Utils\ReflectedManager.cs" />
|
||||||
<Compile Include="Session\TorchSessionManager.cs" />
|
<Compile Include="Session\TorchSessionManager.cs" />
|
||||||
@@ -215,6 +222,7 @@
|
|||||||
<Compile Include="SteamService.cs" />
|
<Compile Include="SteamService.cs" />
|
||||||
<Compile Include="TorchPluginBase.cs" />
|
<Compile Include="TorchPluginBase.cs" />
|
||||||
<Compile Include="Session\TorchSession.cs" />
|
<Compile Include="Session\TorchSession.cs" />
|
||||||
|
<Compile Include="Utils\TorchLauncher.cs" />
|
||||||
<Compile Include="ViewModels\ModViewModel.cs" />
|
<Compile Include="ViewModels\ModViewModel.cs" />
|
||||||
<Compile Include="Collections\MTObservableCollection.cs" />
|
<Compile Include="Collections\MTObservableCollection.cs" />
|
||||||
<Compile Include="Extensions\MyPlayerCollectionExtensions.cs" />
|
<Compile Include="Extensions\MyPlayerCollectionExtensions.cs" />
|
||||||
|
@@ -22,9 +22,11 @@ using Torch.API.Managers;
|
|||||||
using Torch.API.ModAPI;
|
using Torch.API.ModAPI;
|
||||||
using Torch.API.Session;
|
using Torch.API.Session;
|
||||||
using Torch.Commands;
|
using Torch.Commands;
|
||||||
|
using Torch.Event;
|
||||||
using Torch.Managers;
|
using Torch.Managers;
|
||||||
using Torch.Managers.ChatManager;
|
using Torch.Managers.ChatManager;
|
||||||
using Torch.Managers.PatchManager;
|
using Torch.Managers.PatchManager;
|
||||||
|
using Torch.Patches;
|
||||||
using Torch.Utils;
|
using Torch.Utils;
|
||||||
using Torch.Session;
|
using Torch.Session;
|
||||||
using VRage.Collections;
|
using VRage.Collections;
|
||||||
@@ -45,8 +47,13 @@ namespace Torch
|
|||||||
{
|
{
|
||||||
static TorchBase()
|
static TorchBase()
|
||||||
{
|
{
|
||||||
// We can safely never detach this since we don't reload assemblies.
|
ReflectedManager.Process(typeof(TorchBase).Assembly);
|
||||||
new ReflectedManager().Attach();
|
ReflectedManager.Process(typeof(ITorchBase).Assembly);
|
||||||
|
PatchManager.AddPatchShim(typeof(GameStatePatchShim));
|
||||||
|
PatchManager.CommitInternal();
|
||||||
|
RegisterCoreAssembly(typeof(ITorchBase).Assembly);
|
||||||
|
RegisterCoreAssembly(typeof(TorchBase).Assembly);
|
||||||
|
RegisterCoreAssembly(Assembly.GetEntryAssembly());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -100,6 +107,7 @@ namespace Torch
|
|||||||
/// <exception cref="InvalidOperationException">Thrown if a TorchBase instance already exists.</exception>
|
/// <exception cref="InvalidOperationException">Thrown if a TorchBase instance already exists.</exception>
|
||||||
protected TorchBase()
|
protected TorchBase()
|
||||||
{
|
{
|
||||||
|
RegisterCoreAssembly(GetType().Assembly);
|
||||||
if (Instance != null)
|
if (Instance != null)
|
||||||
throw new InvalidOperationException("A TorchBase instance already exists.");
|
throw new InvalidOperationException("A TorchBase instance already exists.");
|
||||||
|
|
||||||
@@ -120,20 +128,11 @@ namespace Torch
|
|||||||
sessionManager.AddFactory((x) => new EntityManager(this));
|
sessionManager.AddFactory((x) => new EntityManager(this));
|
||||||
|
|
||||||
Managers.AddManager(sessionManager);
|
Managers.AddManager(sessionManager);
|
||||||
var patcher = new PatchManager(this);
|
Managers.AddManager(new PatchManager(this));
|
||||||
GameStateInjector.Inject(patcher.AcquireContext());
|
|
||||||
Managers.AddManager(patcher);
|
|
||||||
// Managers.AddManager(new KeenLogManager(this));
|
|
||||||
Managers.AddManager(new FilesystemManager(this));
|
Managers.AddManager(new FilesystemManager(this));
|
||||||
Managers.AddManager(new UpdateManager(this));
|
Managers.AddManager(new UpdateManager(this));
|
||||||
|
Managers.AddManager(new EventManager(this));
|
||||||
Managers.AddManager(Plugins);
|
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;
|
TorchAPI.Instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,7 +389,7 @@ namespace Torch
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual void Update()
|
public virtual void Update()
|
||||||
{
|
{
|
||||||
GetManager<IPluginManager>().UpdatePlugins();
|
Managers.GetManager<IPluginManager>().UpdatePlugins();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -400,7 +399,7 @@ namespace Torch
|
|||||||
public TorchGameState GameState
|
public TorchGameState GameState
|
||||||
{
|
{
|
||||||
get => _gameState;
|
get => _gameState;
|
||||||
private set
|
internal set
|
||||||
{
|
{
|
||||||
_gameState = value;
|
_gameState = value;
|
||||||
GameStateChanged?.Invoke(MySandboxGame.Static, _gameState);
|
GameStateChanged?.Invoke(MySandboxGame.Static, _gameState);
|
||||||
@@ -410,71 +409,37 @@ namespace Torch
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event TorchGameStateChangedDel GameStateChanged;
|
public event TorchGameStateChangedDel GameStateChanged;
|
||||||
|
|
||||||
#region GameStateInjecting
|
private static readonly HashSet<Assembly> _registeredCoreAssemblies = new HashSet<Assembly>();
|
||||||
private static class GameStateInjector
|
/// <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
|
lock (_registeredCoreAssemblies)
|
||||||
[ReflectedMethodInfo(typeof(MySandboxGame), nameof(MySandboxGame.Dispose))]
|
if (_registeredCoreAssemblies.Add(asm))
|
||||||
private static MethodInfo _sandboxGameDispose;
|
{
|
||||||
[ReflectedMethodInfo(typeof(MySandboxGame), "Initialize")]
|
ReflectedManager.Process(asm);
|
||||||
private static MethodInfo _sandboxGameInit;
|
EventManager.AddDispatchShims(asm);
|
||||||
#pragma warning restore 649
|
PatchManager.AddPatchShims(asm);
|
||||||
|
}
|
||||||
internal static void Inject(PatchContext target)
|
}
|
||||||
{
|
|
||||||
ConstructorInfo ctor = typeof(MySandboxGame).GetConstructor(new[] {typeof(string[])});
|
private static readonly HashSet<Assembly> _registeredAuxAssemblies = new HashSet<Assembly>();
|
||||||
if (ctor == null)
|
|
||||||
throw new ArgumentException("Can't find constructor MySandboxGame(string[])");
|
/// <summary>
|
||||||
target.GetPattern(ctor).Prefixes.Add(MethodRef(nameof(PrefixConstructor)));
|
/// Registers an auxillary (plugin) assembly with the system, including its
|
||||||
target.GetPattern(ctor).Suffixes.Add(MethodRef(nameof(SuffixConstructor)));
|
/// <see cref="ReflectedManager"/> related components.
|
||||||
target.GetPattern(_sandboxGameInit).Prefixes.Add(MethodRef(nameof(PrefixInit)));
|
/// </summary>
|
||||||
target.GetPattern(_sandboxGameInit).Suffixes.Add(MethodRef(nameof(SuffixInit)));
|
/// <param name="asm">Assembly to register</param>
|
||||||
target.GetPattern(_sandboxGameDispose).Prefixes.Add(MethodRef(nameof(PrefixDispose)));
|
internal static void RegisterAuxAssembly(Assembly asm)
|
||||||
target.GetPattern(_sandboxGameDispose).Suffixes.Add(MethodRef(nameof(SuffixDispose)));
|
{
|
||||||
}
|
lock (_registeredAuxAssemblies)
|
||||||
|
if (_registeredAuxAssemblies.Add(asm))
|
||||||
private static MethodInfo MethodRef(string name)
|
{
|
||||||
{
|
ReflectedManager.Process(asm);
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -398,36 +398,9 @@ namespace Torch.Utils
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Automatically calls <see cref="ReflectedManager.Process(Assembly)"/> for every assembly already loaded, and every assembly that is loaded in the future.
|
/// Automatically calls <see cref="ReflectedManager.Process(Assembly)"/> for every assembly already loaded, and every assembly that is loaded in the future.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ReflectedManager
|
public static class ReflectedManager
|
||||||
{
|
{
|
||||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
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>();
|
private static readonly HashSet<Type> _processedTypes = new HashSet<Type>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -438,11 +411,6 @@ namespace Torch.Utils
|
|||||||
{
|
{
|
||||||
if (_processedTypes.Add(t))
|
if (_processedTypes.Add(t))
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(t.Namespace))
|
|
||||||
return;
|
|
||||||
foreach (string ns in _namespaceBlacklist)
|
|
||||||
if (t.FullName.StartsWith(ns))
|
|
||||||
return;
|
|
||||||
foreach (FieldInfo field in t.GetFields(BindingFlags.Static | BindingFlags.Instance |
|
foreach (FieldInfo field in t.GetFields(BindingFlags.Static | BindingFlags.Instance |
|
||||||
BindingFlags.Public | BindingFlags.NonPublic))
|
BindingFlags.Public | BindingFlags.NonPublic))
|
||||||
{
|
{
|
||||||
@@ -636,6 +604,7 @@ namespace Torch.Utils
|
|||||||
field.SetValue(null,
|
field.SetValue(null,
|
||||||
Expression.Lambda(Expression.Call(paramExp[0], methodInstance, argExp), paramExp)
|
Expression.Lambda(Expression.Call(paramExp[0], methodInstance, argExp), paramExp)
|
||||||
.Compile());
|
.Compile());
|
||||||
|
_log.Trace($"Reflecting field {field.DeclaringType?.FullName}#{field.Name} with {methodInstance.DeclaringType?.FullName}#{methodInstance.Name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -721,6 +690,7 @@ namespace Torch.Utils
|
|||||||
}
|
}
|
||||||
|
|
||||||
field.SetValue(null, Expression.Lambda(impl, paramExp).Compile());
|
field.SetValue(null, Expression.Lambda(impl, paramExp).Compile());
|
||||||
|
_log.Trace($"Reflecting field {field.DeclaringType?.FullName}#{field.Name} with {field.DeclaringType?.FullName}#{field.Name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
64
Torch/Utils/StringUtils.cs
Normal file
64
Torch/Utils/StringUtils.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Torch.Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Utility methods for strings
|
||||||
|
/// </summary>
|
||||||
|
public static class StringUtils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Determines a common prefix for the given set of strings
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="set">Set of strings</param>
|
||||||
|
/// <returns>Common prefix</returns>
|
||||||
|
public static string CommonPrefix(IEnumerable<string> set)
|
||||||
|
{
|
||||||
|
StringBuilder builder = null;
|
||||||
|
foreach (string other in set)
|
||||||
|
{
|
||||||
|
if (builder == null)
|
||||||
|
builder = new StringBuilder(other);
|
||||||
|
if (builder.Length > other.Length)
|
||||||
|
builder.Remove(other.Length, builder.Length - other.Length);
|
||||||
|
for (var i = 0; i < builder.Length; i++)
|
||||||
|
if (builder[i] != other[i])
|
||||||
|
{
|
||||||
|
builder.Remove(i, builder.Length - i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder?.ToString() ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines a common suffix for the given set of strings
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="set">Set of strings</param>
|
||||||
|
/// <returns>Common suffix</returns>
|
||||||
|
public static string CommonSuffix(IEnumerable<string> set)
|
||||||
|
{
|
||||||
|
StringBuilder builder = null;
|
||||||
|
foreach (string other in set)
|
||||||
|
{
|
||||||
|
if (builder == null)
|
||||||
|
builder = new StringBuilder(other);
|
||||||
|
if (builder.Length > other.Length)
|
||||||
|
builder.Remove(0, builder.Length - other.Length);
|
||||||
|
for (var i = 0; i < builder.Length; i++)
|
||||||
|
{
|
||||||
|
if (builder[builder.Length - 1 - i] != other[other.Length - 1 - i])
|
||||||
|
{
|
||||||
|
builder.Remove(0, builder.Length - i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder?.ToString() ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -42,23 +42,16 @@ namespace Torch.Utils
|
|||||||
{
|
{
|
||||||
string assemblyName = new AssemblyName(args.Name).Name;
|
string assemblyName = new AssemblyName(args.Name).Name;
|
||||||
lock (_assemblies)
|
lock (_assemblies)
|
||||||
{
|
|
||||||
if (_assemblies.TryGetValue(assemblyName, out Assembly asm))
|
if (_assemblies.TryGetValue(assemblyName, out Assembly asm))
|
||||||
return asm;
|
return asm;
|
||||||
}
|
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||||
lock (AppDomain.CurrentDomain)
|
if (asm.GetName().Name.Equals(assemblyName))
|
||||||
{
|
{
|
||||||
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
|
lock (_assemblies)
|
||||||
if (asm.GetName().Name.Equals(assemblyName))
|
_assemblies.Add(assemblyName, asm);
|
||||||
{
|
return asm;
|
||||||
lock (this)
|
}
|
||||||
{
|
lock (_assemblies)
|
||||||
_assemblies.Add(assemblyName, asm);
|
|
||||||
return asm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lock (this)
|
|
||||||
{
|
{
|
||||||
foreach (string path in _paths)
|
foreach (string path in _paths)
|
||||||
{
|
{
|
||||||
|
51
Torch/Utils/TorchLauncher.cs
Normal file
51
Torch/Utils/TorchLauncher.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Torch.API;
|
||||||
|
|
||||||
|
namespace Torch.Utils
|
||||||
|
{
|
||||||
|
public class TorchLauncher
|
||||||
|
{
|
||||||
|
private const string TorchKey = "TorchWrapper";
|
||||||
|
|
||||||
|
public static bool IsTorchWrapped()
|
||||||
|
{
|
||||||
|
return AppDomain.CurrentDomain.GetData(TorchKey) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Launch(string entryPoint, string[] args, params string[] binaryPaths)
|
||||||
|
{
|
||||||
|
if (IsTorchWrapped())
|
||||||
|
throw new Exception("Can't wrap torch twice");
|
||||||
|
string exePath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)?.ToLower().Replace('/', '\\');
|
||||||
|
if (exePath == null)
|
||||||
|
throw new ArgumentException("Unable to determine executing assembly's path");
|
||||||
|
var allPaths = new HashSet<string> { exePath };
|
||||||
|
foreach (string other in binaryPaths)
|
||||||
|
allPaths.Add(other.ToLower().Replace('/', '\\'));
|
||||||
|
var pathPrefix = StringUtils.CommonPrefix(allPaths);
|
||||||
|
AppDomain.CurrentDomain.AppendPrivatePath(String.Join(Path.PathSeparator.ToString(), allPaths));
|
||||||
|
AppDomain.CurrentDomain.SetData(TorchKey, true);
|
||||||
|
AppDomain.CurrentDomain.ExecuteAssemblyByName(entryPoint, args);
|
||||||
|
return;
|
||||||
|
// this would be way better but HAVOK IS UNMANAGED :clang:
|
||||||
|
// exclude application base from probing
|
||||||
|
var setup = new AppDomainSetup
|
||||||
|
{
|
||||||
|
ApplicationBase = pathPrefix.ToString(),
|
||||||
|
PrivateBinPathProbe = "",
|
||||||
|
PrivateBinPath = string.Join(";", allPaths)
|
||||||
|
};
|
||||||
|
AppDomain domain = AppDomain.CreateDomain($"TorchDomain-{Assembly.GetEntryAssembly().GetName().Name}-{new Random().Next():X}", null, setup);
|
||||||
|
domain.SetData(TorchKey, true);
|
||||||
|
domain.ExecuteAssemblyByName(entryPoint, args);
|
||||||
|
AppDomain.Unload(domain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user