Ben Smith
Ben Smith

Reputation: 153

Best way to aggregate event sources and redispatch events

I'm trying to find the best way to create a system where event sources can be added to a manager class, which will then re-dispatch their events to listeners. Specifically, I have many different input sources (keyboard input source, mouse input source, virtual keyboard input source, etc) and I'd like to allow developers to listen for, say, the KeyDown event on both the keyboard input source and the input manager itself (to catch this event from any active input source).

It's easy to brute-force a solution where I end up creating many "dispatch" functions, that simply re-dispatch events when they come through, but I end up having dozens of single line functions and I have to create new functions whenever a new event is added to an input source interface.

I've considered using lambdas, but I need a way to unhook the events if an input source is removed from the manager. I can keep the lambda in a dictionary, keyed by input source, but many of the events have different arg classes, and creating multiple dictionaries to do this starts to get ugly.

I'm wondering if I'm missing some simple way of doing this which keeps things clean and keeps the amount of additional code I need to write down.

For reference, here's a sample of the objects I'm working with:

public interface IInputSource {}

public interface IKeyboardInputSource : IInputSource
{
    event EventHandler<KeyboardEventArgs> KeyDown;
    event EventHandler<KeyboardEventArgs> KeyUp;
}

public interface IMouseInputSource : IInputSource
{
    event EventHandler<MouseEventArgs> MouseDown;
    event EventHandler<MouseEventArgs> MouseUp;
}

public class InputManager : IKeyboardInputSource, IMouseInputSource
{
    private List<IInputSource> InputSources;

    //Event declarations from IKeyboardInputSource and IMouseInputSource

    public void AddSource(IInputSource source)
    {
        InputSources.Add(source);

        if (source is IKeyboardInputSource)
        {
            var keyboardSource = source as IKeyboardInputSource;
            keyboardSource.KeyDown += SendKeyDown;
            // Listen for other keyboard events...
        }
        if (source is IMouseInputSource)
        {
            // Listen for mouse events...
        }
    }

    public void RemoveSource(IInputSource source)
    {
        if (source is IKeyboardInputSource)
        {
            var keyboardSource = source as IKeyboardInputSource;
            keyboardSource.KeyDown -= SendKeyDown;
            // Remove other keyboard events...
        }
        if (source is IMouseInputSource)
        {
            // Remove mouse events...
        }

        InputSources.Remove(source);
    }

    private void SendKeyDown(object sender, KeyboardEventArgs e)
    {
        if (KeyDown != null) 
            KeyDown(sender, e);
    }

    //Other "send" functions
}

Upvotes: 0

Views: 525

Answers (2)

mikalai
mikalai

Reputation: 1736

Probably something like this would help - it's a generic approach, with both direct event subscription or via 'sink' interface

interface IInputSource<T> where T : EventArgs
{
    event EventHandler<T> InputEvent;
}
interface IInputSink<in T> where T : EventArgs
{
    void InputMessageHandler(object sender, T eventArgs);
}

internal class InputManager
{
    private Dictionary<Type, object> _inputSources;
    private Dictionary<Type, object> _inputSinks;
    private Dictionary<Type, object> _events;

    public void AddSource<T>(IInputSource<T> source) where T : EventArgs
    {
        _inputSources[typeof(T)] = _inputSources;      //add source
        _events[typeof(T)] = (EventHandler<T>)Dispatch; //register event for subscribers

        source.InputEvent += Dispatch;
        source.InputEvent += Dispatch2;
    }


    // Dispatch trough direct event subscriptions;
    private void Dispatch<T>(object sender, T e) where T : EventArgs
    {
        var handler = _events[typeof(T)] as EventHandler<T>;
        handler.Invoke(sender, e);
    }
    // Dispatch trough IInputSink subscriptions;
    private void Dispatch2<T>(object sender, T e) where T : EventArgs
    {
        var sink = _inputSinks[typeof(T)] as IInputSink<T>;
        sink.InputMessageHandler(sender, e);
    }

    //Subscription:  Client should provide handler into Subscribe()
    //or subscribe with IInputSink<MyEvent> implementation (Subscribe2())
    public void Subscribe<T>(EventHandler<T> handler) where T : EventArgs
    {
        var @event = _events[typeof(T)] as EventHandler<T>;
        _events[typeof(T)] = @event + handler;
    }

    public void Subscribe2<T>(IInputSink<T> sink) where T : EventArgs
    {
        _inputSinks[typeof(T)] = sink;
    }
}
class XXXX : EventArgs
{

}
public class Sink: IInputSink<XXXX>
{
    #region Implementation of IInputSink<in XXXX>

    public void InputMessageHandler(object sender, XXXX eventArgs)
    {
        throw new NotImplementedException();
    }

    #endregion

    public Sink() 
    {
        var v = new InputManager();
        v.Subscribe<XXXX>(GetInputEvent);
        v.Subscribe2(this);
    }

    private void GetInputEvent(object sender, XXXX xxxx)
    {
        throw new NotImplementedException();
    }
}

Upvotes: 0

RobertMS
RobertMS

Reputation: 1155

Have you looked at the Reactive Extensions (Rx) framework? Looks like it will what you are asking for and gives you a rich functional/lambda like api to manage and process events.

The Reactive Extensions (Rx) is a library for composing asynchronous and event-based programs using observable sequences and LINQ-style query operators

Upvotes: 1

Related Questions