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;
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>());
// inject this new event into the dispatcher
MyCustomEvent myEvent = new MyCustomEvent(HPS.Object.ClassID<MyCustomEvent>());
// cleanup
dispatcher.UnSubscribe(myEventHandler, HPS.Object.ClassID<MyCustomEvent>());
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;
}
char const* ClassName() const override { return "MyCustomEvent"; }
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
}
};
class MyCustomEvent : HPS.Event
{
private int myRandomNumber; // this member is just for demonstration purposes
public MyCustomEvent() : base()
{
}
public MyCustomEvent(IntPtr type) : base(type)
{
}
public MyCustomEvent(IntPtr type, int randomNumber) : base(type)
{
myRandomNumber = randomNumber;
}
public override Event Clone()
{
MyCustomEvent new_event = new MyCustomEvent(GetChannel(), myRandomNumber);
return new_event;
}
public override bool Drop(Event in_that_event)
{
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;
}
public int getMyRandomNumber()
{
return this.myRandomNumber;
}
};
public class MyEventHandler : HPS.EventHandler
{
~MyEventHandler() { Shutdown(); } // IMPORTANT
public override HandleResult Handle(HPS.Event in_event)
{
MyCustomEvent myEvent = (MyCustomEvent)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;
MyEventHandler myEventHandler = new MyEventHandler();
HPS.EventDispatcher dispatcher = myCanvas.GetWindowKey().GetEventDispatcher();
dispatcher.Subscribe(myEventHandler, HPS.Object.ClassID<MyCustomEvent>());
// To activate the Drop() function, let's quickly load 100 Events into the the Event pipeline.
Random rand = new Random();
for (int i = 0; i < 100; i++)
{
int randomNumber = rand.Next();
MyCustomEvent myEvent = new MyCustomEvent(HPS.Object.ClassID<MyCustomEvent>(), randomNumber);
dispatcher.InjectEvent(myEvent); // puts this event into the event handling system
}
// cleanup
dispatcher.UnSubscribe(myEventHandler, HPS.Object.ClassID<MyCustomEvent>());
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;
}
char const* ClassName() const override { return "MyCustomEvent"; }
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;
}
class MyCustomEvent : HPS.Event
{
private int myRandomNumber; // this member is just for demonstration purposes
public MyCustomEvent() : base()
{
}
public MyCustomEvent(IntPtr type) : base(type)
{
}
public MyCustomEvent(IntPtr type, int randomNumber) : base(type)
{
myRandomNumber = randomNumber;
}
public override Event Clone()
{
MyCustomEvent new_event = new MyCustomEvent(GetChannel(), myRandomNumber);
return new_event;
}
public override bool Drop(Event in_that_event)
{
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;
}
public int getMyRandomNumber()
{
return this.myRandomNumber;
}
};
public class MyEventHandler : HPS.EventHandler
{
~MyEventHandler() { Shutdown(); } // IMPORTANT
public override HandleResult Handle(HPS.Event in_event)
{
MyCustomEvent myEvent = (MyCustomEvent)in_event;
// ... do some logic
return HandleResult.Handled; // or return NotHandled, as the case may be
}
};
void injectSingleEvent(ref 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>());
// inject this new event into the dispatcher
MyCustomEvent myEvent = new MyCustomEvent(HPS.Object.ClassID<MyCustomEvent>());
// inject this new event into the dispatcher
dispatcher.InjectEvent(myEvent);
// cleanup
dispatcher.UnSubscribe(myEventHandler, HPS.Object.ClassID<MyCustomEvent>());
}
void injectManyEvents(ref 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 activate the Drop() function, let's quickly load 100 Events into the the Event pipeline.
Random rand = new Random();
for (int i = 0; i < 100; i++)
{
int randomNumber = rand.Next();
MyCustomEvent myEvent = new MyCustomEvent(HPS.Object.ClassID<MyCustomEvent>(), randomNumber);
dispatcher.InjectEvent(myEvent); // puts this event into the event handling system
}
// cleanup
dispatcher.UnSubscribe(myEventHandler, HPS.Object.ClassID<MyCustomEvent>());
}