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();
dispatcher.
Subscribe(*myEventHandler, HPS::Object::ClassID<MyCustomEvent>());
MyCustomEvent myEvent = MyCustomEvent();
dispatcher.
UnSubscribe(*myEventHandler, Object::ClassID<MyCustomEvent>());
delete myEventHandler;
MyEventHandler myEventHandler = new MyEventHandler();
MyCustomEvent myEvent =
new MyCustomEvent(
HPS.
Object.ClassID<MyCustomEvent>());
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., 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 EventDispatcher::Subscribe() function. Next, the event is instantiated. Lastly, the event dispatcher injects the event into the event queue with a call to 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 Event::Drop() to determine whether each event is extraneous. 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, Event::Drop() is invoked on the older event (this inside the Drop() function is the old Event object), and the next newer Event will be passed as a parameter to the 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 Drop() function.
If Event::Drop() returns false (meaning the event is important and should not be dropped), then the event stays in the queue and EventHandler::Handle() will eventually be called. If Event::Drop() returns true (meaning the event is not important), then the event is removed from the queue and 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:
{
public:
int myRandomNumber;
MyCustomEvent() :
HPS::Event()
{
}
MyCustomEvent(
int randomNumber) :
HPS::Event()
{
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;
if ((myRandomNumber % 3) == 0)
drop_status = true;
return drop_status;
}
};
{
public:
{
MyCustomEvent const * myEvent = (MyCustomEvent const *)in_event;
}
};
class MyCustomEvent :
HPS.Event
{
private int myRandomNumber;
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;
if ((myRandomNumber % 3) == 0)
drop_status = true;
return drop_status;
}
public int getMyRandomNumber()
{
return this.myRandomNumber;
}
};
public class MyEventHandler :
HPS.EventHandler
{
~MyEventHandler() { Shutdown(); }
public override HandleResult Handle(
HPS.
Event in_event)
{
MyCustomEvent myEvent = (MyCustomEvent)in_event;
return HandleResult.Handled;
}
};
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();
dispatcher.
Subscribe(*myEventHandler, HPS::Object::ClassID<MyCustomEvent>());
int randomNumber;
for (int i = 0; i < 100; i++)
{
randomNumber = rand();
}
dispatcher.
UnSubscribe(*myEventHandler, Object::ClassID<MyCustomEvent>());
delete myEventHandler;
MyEventHandler myEventHandler = new MyEventHandler();
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);
}
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
{
public:
int myRandomNumber;
MyCustomEvent() :
HPS::Event()
{
}
MyCustomEvent(
int randomNumber) :
HPS::Event()
{
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;
if ((myRandomNumber % 3) == 0)
drop_status = true;
return drop_status;
}
};
{
public:
{
MyCustomEvent const * myEvent = (MyCustomEvent const *)in_event;
}
};
{
MyEventHandler* myEventHandler = new MyEventHandler();
dispatcher.
Subscribe(*myEventHandler, HPS::Object::ClassID<MyCustomEvent>());
MyCustomEvent myEvent = MyCustomEvent();
dispatcher.
UnSubscribe(*myEventHandler, Object::ClassID<MyCustomEvent>());
delete myEventHandler;
}
{
MyEventHandler* myEventHandler = new MyEventHandler();
dispatcher.
Subscribe(*myEventHandler, HPS::Object::ClassID<MyCustomEvent>());
int randomNumber;
for (int i = 0; i < 100; i++)
{
randomNumber = rand();
}
dispatcher.
UnSubscribe(*myEventHandler, Object::ClassID<MyCustomEvent>());
delete myEventHandler;
}
class MyCustomEvent :
HPS.Event
{
private int myRandomNumber;
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;
if ((myRandomNumber % 3) == 0)
drop_status = true;
return drop_status;
}
public int getMyRandomNumber()
{
return this.myRandomNumber;
}
};
public class MyEventHandler :
HPS.EventHandler
{
~MyEventHandler() { Shutdown(); }
public override HandleResult Handle(
HPS.
Event in_event)
{
MyCustomEvent myEvent = (MyCustomEvent)in_event;
return HandleResult.Handled;
}
};
void injectSingleEvent(ref
HPS.
Canvas canvas)
{
MyEventHandler myEventHandler = new MyEventHandler();
MyCustomEvent myEvent =
new MyCustomEvent(
HPS.
Object.ClassID<MyCustomEvent>());
dispatcher.InjectEvent(myEvent);
dispatcher.UnSubscribe(myEventHandler,
HPS.
Object.ClassID<MyCustomEvent>());
}
{
MyEventHandler myEventHandler = new MyEventHandler();
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);
}
dispatcher.UnSubscribe(myEventHandler,
HPS.
Object.ClassID<MyCustomEvent>());
}