Event Handling

Visualize uses its event handling system as an asynchronous way to respond to various types of events raised within the application. Key presses, mouse motion, errors, and warnings are all examples of asynchronous events. This section will describe how you can intercept events and process them so that your application responds appropriately.

Event Handling System Anatomy

The event handler system consists of four main parts:

  • HPS::Event objects, or a subclass thereof

  • A number of channels, which direct events to the event handlers that are able to process them.

  • The HPS::EventDispatcher, which injects the event into the event queue based on the event’s assigned channel.

  • The HPS::EventHandler object which responds to the event. Event handlers listen for events on a particular channel.

The sequence below shows how these parts work together to handle events:

1 - Something happens in the execution environment that requires a response from your application. This could be anything from a mouse click to a run-time error. You also have the ability to generate custom events at any time you require. For this example, we'll imagine Visualize has detected a mouse click in the main window.
2 - In order to process the mouse click, Visualize builds an HPS::Event object which includes information about what happened. At construction time, the event is assigned a channel which will later be used to get the event to a handler that is able to process it.
3 - Event is dispatched by the HPS::EventDispatcher. The dispatcher injects the event into the channel it was assigned to from the previous step. Visualize maintains a few default channels - examples include (but are not limited to) the error channel, warning channel, and keyboard event channel.
4 - In our example, the mouse event channel (highlighted in green) is chosen. A single event instance can only go into one channel, however, there are no resistrictions on what type of event can go into which channel. Be aware that placing an event on a channel that is not equipped to handle it will likely result in your event being ignored.
5 - Each line of small dots represents a stack of event handlers. Event handlers are associated with one or more channels. In this case, the orange dots represent a group of handlers that are able to process mouse click events. As the event is placed in the channel, the handler at the top of the stack gets to process the event first. The handler can consume the event or pass it on to the next handler in the stack. Our imaginary example ends with the first handler interpreting the mouse click as a selection event and highlighting a piece of geometry.


The Event Handler Object

The first step to handling events is to create your own event handler by subclassing HPS::EventHandler. The Handle method is called when an event is dispatched, so any processing or recovery logic should be placed in that function, as shown below:

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

        EventHandler::HandleResult Handle(Event const* in_event)
        {
            // do any processing or recovery logic here

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

Note that the example above returns Handled. You may also return NotHandled if for some reason this handler does not consume the event. In this case, the event would be passed to the next handler that is subscribed to handle events on that channel. See the next subsection for a discussion on the event stack.

IMPORTANT: Your event handler must call HPS::EventHandler::Shutdown in its destructor. Failure to do so may result in undefined behavior, including crashes or deadlocks.

The Event Dispatcher

The next step to handling events is to instantiate your handler and subscribe it to an HPS::EventDispatcher. There are several event dispatchers - the Database and application window each have their own, as well as any dispatchers for custom events that you create.

All handlers live in a stack with others of the same type. By defaut, if one handler can’t process the event, (as indicated by returning NotHandled from EventHandler::Handle()), the handler below it will receive the event. There can be any number of handlers in a stack. The code below demonstrates how to add an event handler for errors.

        MyEventHandler* myHandler = new MyEventHandler();

        HPS::EventDispatcher dispatcher = HPS::Database::GetEventDispatcher();

        // adds handler to dispatcher's subscription stack
        dispatcher.Subscribe(*myHandler, HPS::Object::ClassID<ErrorEvent>());

        // removes this handler from the subscription stack
        dispatcher.UnSubscribe(*myHandler, HPS::Object::ClassID<ErrorEvent>());

IMPORTANT: When your handler is destroyed, it will be automatically unsubscribed. Therefore, in order for the handler to work, it must not go out of scope. You are responsible for maintaining the pointer.

Note the second parameter to Subscribe in the code snippet. This parameter indicates which channel the handler will be subscribing to. You can associate any type of event with any channel.

As stated above, the default behavior of the event subsystem will cause an event to be consumed when the HPS::EventHandler::Handle function returns Handled. However, this logic can be configured in different ways depending on your needs. For example, a channel can be set to never consume an event, or it could be set so that only the first handler in the stack processes the event, regardless of the result of HPS::EventHandler::Handle.

Event objects

As mentioned previously, the error event is just one type of event. Warnings, errors, timer ticks, and key presses are all examples of Visualize events, and are typically injected on their own channel. Your handler can subscribe to any channel, or all of them. The HPS::Event class is abstract. In order to raise an event, you must subclass it…

    class MyEvent: public Event {
      public:
        MyEvent(): Event() {}

        MyEvent(intptr_t type): Event(type) {}

        virtual char const* ClassName() const override { return "MyEvent"; }

        // called internally to copy an event. in the case your event contains pointers or dynamically
        // allocated data, this function needs to be set up to handle the cloning
        virtual Event* Clone() const { return new MyEvent(GetChannel()); }
    };

…before injecting your new event into the system.

        MyEvent myEvent(HPS::Object::ClassID<MyEvent>());
        dispatcher.InjectEvent(myEvent); // this event will be sent to the handler

Notifiers

You can use a notifier to learn that an event has been processed. A notifier is an object that is associated with an event injection and can be used to track its progress. Querying the notifier will tell you the status of the event:

        HPS::EventDispatcher dispatcher = HPS::Database::GetEventDispatcher();
        HPS::EventNotifier eventNotifier = dispatcher.InjectEventWithNotifier(myEvent);

        if (eventNotifier.Status() == HPS::Event::Status::InProgress) {
            // the event has not yet been processed
        }

        if (eventNotifier.Status() == HPS::Event::Status::Completed) {
            // the event has been processed
        }

It is also possible to simply wait until an event is handled using an HPS::EventNotifier. The following code will cause its thread to sleep until the event handling is completed:

        eventNotifier.Wait();