Event dispatch and registration service

This commit is contained in:
Westin Miller
2017-10-08 05:32:16 -07:00
parent 6f650c8bbd
commit 716e6cbc04
7 changed files with 428 additions and 0 deletions

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

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

View 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)
{
}
}
}

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

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

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

View File

@@ -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" />