-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add dynamic Event source / handler system
Co-authored-by: Colin Barndt <[email protected]>
- Loading branch information
1 parent
bb471b3
commit a9b4b11
Showing
14 changed files
with
565 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace MonkeyLoader | ||
{ | ||
/// <summary> | ||
/// Represents a type safe dictionary of Types to objects of the type. | ||
/// </summary> | ||
public sealed class AnyMap | ||
{ | ||
private readonly Dictionary<Type, object?> _dict = new(); | ||
|
||
public IEnumerable<Type> Keys => _dict.Keys; | ||
|
||
public void Add<T>(T value) => _dict.Add(typeof(T), value); | ||
|
||
public void Clear() => _dict.Clear(); | ||
|
||
public bool ContainsKey<T>() => _dict.ContainsKey(typeof(T)); | ||
|
||
public IEnumerable<T> GetCastableValues<T>() | ||
=> _dict.Values.SelectCastable<object, T>(); | ||
|
||
public T GetOrCreateValue<T>() where T : new() | ||
{ | ||
if (TryGetValue<T>(out var value)) | ||
return value!; | ||
|
||
value = new T(); | ||
Add(value); | ||
|
||
return value; | ||
} | ||
|
||
public T GetOrCreateValue<T>(Func<T> valueFactory) | ||
{ | ||
if (TryGetValue<T>(out var value)) | ||
return value!; | ||
|
||
value = valueFactory(); | ||
Add(value); | ||
|
||
return value; | ||
} | ||
|
||
public T GetValue<T>() => (T)_dict[typeof(T)]!; | ||
|
||
public void Remove<T>() => _dict.Remove(typeof(T)); | ||
|
||
public bool TryGetValue<T>(out T? value) | ||
{ | ||
if (_dict.TryGetValue(typeof(T), out var obj)) | ||
{ | ||
value = (T)obj!; | ||
return true; | ||
} | ||
|
||
value = default; | ||
return false; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
using MonkeyLoader.Logging; | ||
using MonkeyLoader.Meta; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace MonkeyLoader.Events | ||
{ | ||
internal sealed class CancelableEventDispatcher<TEvent, TTarget> | ||
: EventDispatcherBase<ICancelableEventSource<TEvent, TTarget>, ICancelableEventHandler<TEvent, TTarget>> | ||
where TEvent : ICancelableEvent<TTarget> | ||
{ | ||
public CancelableEventDispatcher(EventManager manager) : base(manager) | ||
{ } | ||
|
||
protected override void AddSource(ICancelableEventSource<TEvent, TTarget> eventSource) | ||
=> eventSource.Dispatched += DispatchEvents; | ||
|
||
protected override void RemoveSource(ICancelableEventSource<TEvent, TTarget> eventSource) | ||
=> eventSource.Dispatched -= DispatchEvents; | ||
|
||
private void DispatchEvents(TEvent eventArgs) | ||
{ | ||
foreach (var handler in handlers) | ||
{ | ||
if (eventArgs.Canceled && handler.SkipCanceled) | ||
{ | ||
Logger.Trace(() => $"Skipping event handler [{handler.GetType()}] for canceled event [{eventArgs}]!"); | ||
continue; | ||
} | ||
|
||
try | ||
{ | ||
handler.Handle(eventArgs); | ||
} | ||
catch (Exception ex) | ||
{ | ||
Logger.Warn(() => ex.Format($"Event handler [{handler.GetType()}] threw an exception for event [{eventArgs}]:")); | ||
} | ||
} | ||
} | ||
} | ||
|
||
internal sealed class EventDispatcher<TEvent, TTarget> | ||
: EventDispatcherBase<IEventSource<TEvent, TTarget>, IEventHandler<TEvent, TTarget>> | ||
where TEvent : IEvent<TTarget> | ||
{ | ||
public EventDispatcher(EventManager manager) : base(manager) | ||
{ } | ||
|
||
protected override void AddSource(IEventSource<TEvent, TTarget> eventSource) | ||
=> eventSource.Dispatched += DispatchEvents; | ||
|
||
protected override void RemoveSource(IEventSource<TEvent, TTarget> eventSource) | ||
=> eventSource.Dispatched -= DispatchEvents; | ||
|
||
private void DispatchEvents(TEvent eventArgs) | ||
{ | ||
foreach (var handler in handlers) | ||
{ | ||
try | ||
{ | ||
handler.Handle(eventArgs); | ||
} | ||
catch (Exception ex) | ||
{ | ||
Logger.Warn(() => ex.Format($"Event handler [{handler.GetType()}] threw an exception for event [{eventArgs}]:")); | ||
} | ||
} | ||
} | ||
} | ||
|
||
internal abstract class EventDispatcherBase<TSource, THandler> : IEventDispatcher | ||
where THandler : IPrioritizable | ||
{ | ||
protected readonly SortedCollection<THandler> handlers = new((IComparer<THandler>)PriorityHelper.Comparer); | ||
|
||
private readonly Dictionary<Mod, HashSet<THandler>> _handlersByMod = new(); | ||
private readonly EventManager _manager; | ||
private readonly Dictionary<Mod, HashSet<TSource>> _sourcesByMod = new(); | ||
|
||
protected Logger Logger => _manager.Logger; | ||
|
||
protected EventDispatcherBase(EventManager manager) | ||
{ | ||
_manager = manager; | ||
} | ||
|
||
public bool AddHandler(Mod mod, THandler handler) | ||
{ | ||
if (_handlersByMod.GetOrCreateValue(mod).Add(handler)) | ||
{ | ||
handlers.Add(handler); | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
public bool AddSource(Mod mod, TSource source) | ||
{ | ||
if (_sourcesByMod.GetOrCreateValue(mod).Add(source)) | ||
{ | ||
AddSource(source); | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
public bool RemoveHandler(Mod mod, THandler handler) | ||
{ | ||
if (_handlersByMod.TryGetValue(mod, out var modHandlers)) | ||
{ | ||
modHandlers.Remove(handler); | ||
handlers.Remove(handler); | ||
|
||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
public bool RemoveSource(Mod mod, TSource source) | ||
{ | ||
if (_sourcesByMod.TryGetValue(mod, out var modSources)) | ||
{ | ||
modSources.Remove(source); | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
public void UnregisterMod(Mod mod) | ||
{ | ||
if (_sourcesByMod.TryGetValue(mod, out var modSources)) | ||
{ | ||
foreach (var source in modSources) | ||
RemoveSource(source); | ||
} | ||
|
||
_sourcesByMod.Remove(mod); | ||
|
||
if (_handlersByMod.TryGetValue(mod, out var modHandlers)) | ||
{ | ||
foreach (var handler in modHandlers) | ||
handlers.Remove(handler); | ||
} | ||
|
||
_handlersByMod.Remove(mod); | ||
} | ||
|
||
protected abstract void AddSource(TSource eventSource); | ||
|
||
protected abstract void RemoveSource(TSource eventSource); | ||
} | ||
|
||
internal interface IEventDispatcher | ||
{ | ||
public void UnregisterMod(Mod mod); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
using MonkeyLoader.Logging; | ||
using MonkeyLoader.Meta; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics.Tracing; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace MonkeyLoader.Events | ||
{ | ||
internal sealed class EventManager | ||
{ | ||
private readonly AnyMap _eventDispatchers = new(); | ||
private readonly MonkeyLoader _loader; | ||
internal Logger Logger { get; } | ||
|
||
internal EventManager(MonkeyLoader loader) | ||
{ | ||
_loader = loader; | ||
Logger = new(loader.Logger, "EventManager"); | ||
} | ||
|
||
internal void RegisterEventHandler<TEvent, TTarget>(Mod mod, IEventHandler<TEvent, TTarget> eventHandler) | ||
where TEvent : IEvent<TTarget> | ||
{ | ||
ValidateLoader(mod); | ||
|
||
_eventDispatchers.GetOrCreateValue(CreateDispatcher<TEvent, TTarget>).AddHandler(mod, eventHandler); | ||
} | ||
|
||
internal void RegisterEventHandler<TEvent, TTarget>(Mod mod, ICancelableEventHandler<TEvent, TTarget> cancelableEventHandler) | ||
where TEvent : ICancelableEvent<TTarget> | ||
{ | ||
ValidateLoader(mod); | ||
|
||
_eventDispatchers.GetOrCreateValue(CreateCancelableDispatcher<TEvent, TTarget>).AddHandler(mod, cancelableEventHandler); | ||
} | ||
|
||
internal void RegisterEventSource<TEvent, TTarget>(Mod mod, IEventSource<TEvent, TTarget> eventSource) | ||
where TEvent : IEvent<TTarget> | ||
{ | ||
ValidateLoader(mod); | ||
|
||
_eventDispatchers.GetOrCreateValue(CreateDispatcher<TEvent, TTarget>).AddSource(mod, eventSource); | ||
} | ||
|
||
internal void RegisterEventSource<TEvent, TTarget>(Mod mod, ICancelableEventSource<TEvent, TTarget> cancelableEventSource) | ||
where TEvent : ICancelableEvent<TTarget> | ||
{ | ||
ValidateLoader(mod); | ||
|
||
_eventDispatchers.GetOrCreateValue(CreateCancelableDispatcher<TEvent, TTarget>).AddSource(mod, cancelableEventSource); | ||
} | ||
|
||
internal void UnregisterMod(Mod mod) | ||
{ | ||
ValidateLoader(mod); | ||
|
||
foreach (var eventDispatcher in _eventDispatchers.GetCastableValues<IEventDispatcher>()) | ||
eventDispatcher.UnregisterMod(mod); | ||
} | ||
|
||
private CancelableEventDispatcher<TEvent, TTarget> CreateCancelableDispatcher<TEvent, TTarget>() | ||
where TEvent : ICancelableEvent<TTarget> => new(this); | ||
|
||
private EventDispatcher<TEvent, TTarget> CreateDispatcher<TEvent, TTarget>() | ||
where TEvent : IEvent<TTarget> => new(this); | ||
|
||
private void ValidateLoader(Mod mod) | ||
{ | ||
if (mod.Loader != _loader) | ||
throw new InvalidOperationException("Can't register event handler of mod from another loader!"); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace MonkeyLoader.Events | ||
{ | ||
public interface ICancelableEvent<out TTarget> : IEvent<TTarget> | ||
{ | ||
public bool Canceled { get; set; } | ||
} | ||
|
||
public interface IEvent<out TTarget> | ||
{ | ||
public TTarget Target { get; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace MonkeyLoader.Events | ||
{ | ||
public interface ICancelableEventHandler<in TEvent, out TTarget> : IPrioritizable | ||
where TEvent : ICancelableEvent<TTarget> | ||
{ | ||
public bool SkipCanceled { get; } | ||
|
||
public void Handle(TEvent eventArgs); | ||
} | ||
|
||
public interface IEventHandler<in TEvent, out TTarget> : IPrioritizable | ||
where TEvent : IEvent<TTarget> | ||
{ | ||
public void Handle(TEvent eventArgs); | ||
} | ||
} |
Oops, something went wrong.