Custom Events

So far, several types of predefined events have been mentioned. It is also possible to create custom events that can be raised and handled by your application. Visualize processes custom events no differently from predefined events. However, the setup requires an additional step - you must select a channel for your event type. The remainder of the logic is identical to the built-in event handling mechanism, as shown below:

        MyEventHandler* myEventHandler = new MyEventHandler();
        HPS::EventDispatcher dispatcher = myCanvas.GetWindowKey().GetEventDispatcher();

        // set the channel number to this object's ClassID
        dispatcher.Subscribe(*myEventHandler, HPS::Object::ClassID<MyCustomEvent>());
        MyCustomEvent myEvent = MyCustomEvent();

        // inject this new event into the dispatcher
        dispatcher.InjectEvent(myEvent);

        // cleanup
        dispatcher.UnSubscribe(*myEventHandler, Object::ClassID<MyCustomEvent>());
        delete myEventHandler;

A handler instance can subscribe to any number of channels. In this case, the event dispatcher informs the MyEventHandler instance that it needs to listen for events on the channel number corresponding to the Object::ClassID of our custom event object – i.e., Object::ClassID of MyCustomEvent in this sample. You can choose any channel number you’d like, but we’re using the Object::ClassID as our channel number to listen specifically for events of this type. (If we selected a channel number of, say, 1 we might encounter other types of events mixed into the channel.)

The channel number is passed as a parameter to the HPS::EventDispatcher::Subscribe() function. Next, the event is instantiated. Lastly, the event dispatcher injects the event into the event queue with a call to HPS::EventDispatcher::InjectEvent().

Because events are each handled in their own separate thread, it is important that event handlers do not throw exceptions above their scope since there would be no way to catch the exception.

In the next section, we’ll discuss setting up a sub-class of HPS::Event to define our own custom event.

Dropping Unimportant Events

Sometimes, events are generated so quickly that they cannot be processed in real time. Often, these quickly generated events are extraneous or unimportant. For instance, if a user moves the mouse across the screen very quickly, we usually don’t care about the cursor’s intermediate locations, just the most recent position. On these occasions, Visualize will try to determine whether the event can be ignored based on the event’s internal logic.

For custom events, it is possible to write this logic yourself. This is especially important when processing an event that takes a lot of time. The drop attempt takes place automatically when another event enters the queue before the current event is handled.

When multiple events of the same type are queued in rapid succession, Visualize will call HPS::Event::Drop() to determine whether each event is extraneous. HPS::Event::Drop() only gets invoked if you have two or more events next to each other in the same channel (meaning they are stacking up faster than they can be processed).

In this situation, HPS::Event::Drop() is invoked on the older event (this inside the Event::Drop() function is the old HPS::Event object), and the next newer Event will be passed as a parameter to the HPS::Event::Drop() function. Prior to injecting an Event, you’ll need to ensure the Event has any necessary state information that could be used in the logic for the Event::Drop() function.

If HPS::Event::Drop() returns false (meaning the event is important and should not be dropped), then the event stays in the queue and `` HPS::EventHandler::Handle()`` will eventually be called. If HPS::Event::Drop() returns true (meaning the event is not important), then the event is removed from the queue and HPS::EventHandler::Handle() will not be called.

Below is an example of setting up a custom event with drop logic. Here, we’re defining a sub-class of HPS::Event. For demonstration purposes, we’ll create some arbitrary logic for the Drop() function:

    class MyCustomEvent: public HPS::Event {
      public:
        int myRandomNumber; // this member is just for demonstration purposes

        MyCustomEvent(): HPS::Event() { channel = GetClassID(); }

        MyCustomEvent(int randomNumber): HPS::Event()
        {
            channel = GetClassID();
            myRandomNumber = randomNumber;
        }

        Event* Clone() const override
        {
            MyCustomEvent* new_event = new MyCustomEvent(*this);
            return new_event;
        }

        bool Drop(Event const* in_that_event) const override
        {
            bool drop_status = false;
            // add logic here to determine if event can be dropped

            // let's drop the Event if its randomNumber member is divisible by 3
            if ((myRandomNumber % 3) == 0)
                drop_status = true;

            return drop_status;
        }
    };

    class MyEventHandler: public HPS::EventHandler {
      public:
        ~MyEventHandler() { Shutdown(); } // IMPORTANT

        HandleResult Handle(HPS::Event const* in_event)
        {
            MyCustomEvent const* myEvent = (MyCustomEvent const*)in_event;
            // ... do some logic

            return HandleResult::Handled; // or return NotHandled, as the case may be
        }
    };

As shown in the example, C# users should call the base constructor with the object’s class ID in order to register the event in the correct channel.

In order to test out the drop behavior, in our main application code we’ll create a for loop that will inject 100 separate events in rapid succession into the Event dispatcher. Some of these events will be injected while a previous event is still being processed, causing Visualize to call the Drop() function internally:

        MyEventHandler* myEventHandler = new MyEventHandler();
        HPS::EventDispatcher dispatcher = myCanvas.GetWindowKey().GetEventDispatcher();
        dispatcher.Subscribe(*myEventHandler, HPS::Object::ClassID<MyCustomEvent>());

        // To demonstrate the Drop() function, let's quickly load 100 Events into the the Event pipeline.
        int randomNumber;
        for (int i = 0; i < 100; i++) {
            randomNumber = rand();
            dispatcher.InjectEvent(MyCustomEvent(randomNumber)); // puts this event into the event handling system
        }

        // cleanup
        dispatcher.UnSubscribe(*myEventHandler, Object::ClassID<MyCustomEvent>());
        delete myEventHandler;

While running this code in debug mode, if you set a single breakpoint in your Drop() function, you’ll likely see a handful of calls to Drop() during the execution of the for loop.

For more information on custom events, please see the custom_event sample in the Visualize package, which includes a demonstration of how to use the HPS::EventNotifier class.

Full Source

    class MyCustomEvent: public HPS::Event {
      public:
        int myRandomNumber; // this member is just for demonstration purposes

        MyCustomEvent(): HPS::Event() { channel = GetClassID(); }

        MyCustomEvent(int randomNumber): HPS::Event()
        {
            channel = GetClassID();
            myRandomNumber = randomNumber;
        }

        Event* Clone() const override
        {
            MyCustomEvent* new_event = new MyCustomEvent(*this);
            return new_event;
        }

        bool Drop(Event const* in_that_event) const override
        {
            bool drop_status = false;
            // add logic here to determine if event can be dropped

            // let's drop the Event if its randomNumber member is divisible by 3
            if ((myRandomNumber % 3) == 0)
                drop_status = true;

            return drop_status;
        }
    };

    class MyEventHandler: public HPS::EventHandler {
      public:
        ~MyEventHandler() { Shutdown(); } // IMPORTANT

        HandleResult Handle(HPS::Event const* in_event)
        {
            MyCustomEvent const* myEvent = (MyCustomEvent const*)in_event;
            // ... do some logic

            return HandleResult::Handled; // or return NotHandled, as the case may be
        }
    };

    void injectSingleEvent(HPS::Canvas& canvas)
    {
        HPS::Canvas myCanvas = canvas;

        MyEventHandler* myEventHandler = new MyEventHandler();
        HPS::EventDispatcher dispatcher = myCanvas.GetWindowKey().GetEventDispatcher();

        // set the channel number to this object's ClassID
        dispatcher.Subscribe(*myEventHandler, HPS::Object::ClassID<MyCustomEvent>());
        MyCustomEvent myEvent = MyCustomEvent();

        // inject this new event into the dispatcher
        dispatcher.InjectEvent(myEvent);

        // cleanup
        dispatcher.UnSubscribe(*myEventHandler, Object::ClassID<MyCustomEvent>());
        delete myEventHandler;
    }

    void injectManyEvents(HPS::Canvas& canvas)
    {
        HPS::Canvas myCanvas = canvas;

        MyEventHandler* myEventHandler = new MyEventHandler();
        HPS::EventDispatcher dispatcher = myCanvas.GetWindowKey().GetEventDispatcher();
        dispatcher.Subscribe(*myEventHandler, HPS::Object::ClassID<MyCustomEvent>());

        // To demonstrate the Drop() function, let's quickly load 100 Events into the the Event pipeline.
        int randomNumber;
        for (int i = 0; i < 100; i++) {
            randomNumber = rand();
            dispatcher.InjectEvent(MyCustomEvent(randomNumber)); // puts this event into the event handling system
        }

        // cleanup
        dispatcher.UnSubscribe(*myEventHandler, Object::ClassID<MyCustomEvent>());
        delete myEventHandler;
    }