Event dispatch and registration service
This commit is contained in:
32
Torch/Managers/EventManager/EventHandlerAttribute.cs
Normal file
32
Torch/Managers/EventManager/EventHandlerAttribute.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Torch.Managers.EventManager
|
||||
{
|
||||
/// <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;
|
||||
}
|
||||
}
|
149
Torch/Managers/EventManager/EventList.cs
Normal file
149
Torch/Managers/EventManager/EventList.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Torch.Managers.EventManager
|
||||
{
|
||||
/// <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, object instance)
|
||||
{
|
||||
try
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
_dispatchers.Add(new EventHandlerData(method, instance));
|
||||
_dispatchersDirty = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int RemoveHandlers(object 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
110
Torch/Managers/EventManager/EventManager.cs
Normal file
110
Torch/Managers/EventManager/EventManager.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using VRage.Game.ModAPI;
|
||||
|
||||
namespace Torch.Managers.EventManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Manager class responsible for managing registration and dispatching of events.
|
||||
/// </summary>
|
||||
public class EventManager : Manager
|
||||
{
|
||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private static Dictionary<Type, IEventList> _eventLists = new Dictionary<Type, IEventList>();
|
||||
|
||||
static EventManager()
|
||||
{
|
||||
AddDispatchShim(typeof(EventShimProgrammableBlock));
|
||||
}
|
||||
|
||||
private static void AddDispatchShim(Type type)
|
||||
{
|
||||
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));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all event handlers in the given type, and its base types
|
||||
/// </summary>
|
||||
/// <param name="exploreType">Type to explore</param>
|
||||
/// <returns>All event handlers</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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers all handlers the given instance owns.
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance to register handlers from</param>
|
||||
private static void RegisterHandler(object 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 UnregisterHandlers(object instance)
|
||||
{
|
||||
foreach (IEventList list in _eventLists.Values)
|
||||
list.RemoveHandlers(instance);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public EventManager(ITorchBase torchInstance) : base(torchInstance)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
85
Torch/Managers/EventManager/EventShimProgrammableBlock.cs
Normal file
85
Torch/Managers/EventManager/EventShimProgrammableBlock.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Sandbox.Game.Entities;
|
||||
using Sandbox.Game.Entities.Blocks;
|
||||
using Torch.Managers.PatchManager;
|
||||
using Torch.Utils;
|
||||
|
||||
namespace Torch.Managers.EventManager
|
||||
{
|
||||
internal static class EventShimProgrammableBlock
|
||||
{
|
||||
[ReflectedMethodInfo(typeof(MyProgrammableBlock), nameof(MyProgrammableBlock.ExecuteCode))]
|
||||
private static MethodInfo _programmableBlockExecuteCode;
|
||||
private static readonly EventList<ProgrammableBlockTryRunEvent> _tryRunEventList = new EventList<ProgrammableBlockTryRunEvent>();
|
||||
private static readonly EventList<ProgrammableBlockWasRunEvent> _wasRunEventList = new EventList<ProgrammableBlockWasRunEvent>();
|
||||
|
||||
private static void Patch(PatchContext context)
|
||||
{
|
||||
var p = context.GetPattern(_programmableBlockExecuteCode);
|
||||
p.Prefixes.Add(
|
||||
typeof(EventShimProgrammableBlock).GetMethod(nameof(PrefixExecuteCode), BindingFlags.NonPublic));
|
||||
p.Suffixes.Add(
|
||||
typeof(EventShimProgrammableBlock).GetMethod(nameof(SuffixExecuteCode), BindingFlags.NonPublic));
|
||||
}
|
||||
|
||||
private static bool PrefixExecuteCode(MyProgrammableBlock __instance, string argument)
|
||||
{
|
||||
var evt = new ProgrammableBlockTryRunEvent(__instance, argument);
|
||||
_tryRunEventList.RaiseEvent(ref evt);
|
||||
return !evt.Cancelled;
|
||||
}
|
||||
|
||||
private static void SuffixExecuteCode(MyProgrammableBlock __instance, string argument, string response)
|
||||
{
|
||||
var evt = new ProgrammableBlockWasRunEvent(__instance, argument, response);
|
||||
_wasRunEventList.RaiseEvent(ref evt);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IBlockEvent : IEvent
|
||||
{
|
||||
MyCubeBlock Block { get; }
|
||||
}
|
||||
|
||||
public interface IProgrammableBlockEvent : IBlockEvent
|
||||
{
|
||||
new MyProgrammableBlock Block { get; }
|
||||
}
|
||||
|
||||
|
||||
public struct ProgrammableBlockTryRunEvent : IProgrammableBlockEvent
|
||||
{
|
||||
internal ProgrammableBlockTryRunEvent(MyProgrammableBlock block, string arg)
|
||||
{
|
||||
Block = block;
|
||||
Argument = arg;
|
||||
Cancelled = false;
|
||||
}
|
||||
|
||||
public bool Cancelled { get; set; }
|
||||
public string Argument { get; }
|
||||
public MyProgrammableBlock Block { get; }
|
||||
MyCubeBlock IBlockEvent.Block => Block;
|
||||
}
|
||||
|
||||
public struct ProgrammableBlockWasRunEvent : IProgrammableBlockEvent
|
||||
{
|
||||
internal ProgrammableBlockWasRunEvent(MyProgrammableBlock block, string arg, string response)
|
||||
{
|
||||
Block = block;
|
||||
Argument = arg;
|
||||
Response = response;
|
||||
}
|
||||
|
||||
public bool Cancelled => false;
|
||||
public string Argument { get; }
|
||||
public string Response { get; }
|
||||
public MyProgrammableBlock Block { get; }
|
||||
MyCubeBlock IBlockEvent.Block => Block;
|
||||
}
|
||||
}
|
17
Torch/Managers/EventManager/IEvent.cs
Normal file
17
Torch/Managers/EventManager/IEvent.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Torch.Managers.EventManager
|
||||
{
|
||||
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; }
|
||||
}
|
||||
}
|
29
Torch/Managers/EventManager/IEventList.cs
Normal file
29
Torch/Managers/EventManager/IEventList.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Torch.Managers.EventManager
|
||||
{
|
||||
/// <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, object 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(object instance);
|
||||
}
|
||||
}
|
@@ -160,6 +160,12 @@
|
||||
<Compile Include="Extensions\DispatcherExtensions.cs" />
|
||||
<Compile Include="Extensions\ICollectionExtensions.cs" />
|
||||
<Compile Include="Managers\DependencyManager.cs" />
|
||||
<Compile Include="Managers\EventManager\EventHandlerAttribute.cs" />
|
||||
<Compile Include="Managers\EventManager\EventList.cs" />
|
||||
<Compile Include="Managers\EventManager\EventManager.cs" />
|
||||
<Compile Include="Managers\EventManager\EventShimProgrammableBlock.cs" />
|
||||
<Compile Include="Managers\EventManager\IEvent.cs" />
|
||||
<Compile Include="Managers\EventManager\IEventList.cs" />
|
||||
<Compile Include="Managers\KeenLogManager.cs" />
|
||||
<Compile Include="Managers\PatchManager\AssemblyMemory.cs" />
|
||||
<Compile Include="Managers\PatchManager\DecoratedMethod.cs" />
|
||||
|
Reference in New Issue
Block a user