Demilade Sonuga's blog
All postsEvent Handling II
In this post, we're going to finish implementing the event-handling scheme
Implementation
Currently, event_hook.rs
looks like this:
use crate::keyboard::KeyEvent;
/* COMMENTING THIS OUT, FOR NOW TO AVOID FUTURE ERRORS
// Mediator between the game code and the interrupt service routines
pub struct EventHooker {
// The functions that will be called when an event occurs
handlers: Vec<Box<dyn FnMut(EventInfo)>>
}
*/
// The info about an event that will be passed into a handler
// when it is called
pub enum EventInfo {
Timer,
Keyboard(KeyEvent)
}
The first thing to do is to uncomment EventHooker
. Then we change handlers
's type to reflect the new stuff
we've created.
use crate::keyboard::KeyEvent;
use crate::vec::Vec;
use crate::boxed_fn::BoxedFn;
// Mediator between the game code and the interrupt service routines
pub struct EventHooker {
// The functions that will be called when an event occurs
handlers: Vec<BoxedFn>>
}
// The info about an event that will be passed into a handler
// when it is called
pub enum EventInfo {
Timer,
Keyboard(KeyEvent)
}
When we started in the first event handling post, it was mentioned that EventHooker
, the main player in this
game will have 3 functions:
send_event
, to notify theEventHooker
that an event has occurredhook_event
, to tell theEventHooker
that a function should be called when an event occursunhook_event
, to tell theEventHooker
to no longer call a function when an event occurs
We'll also need a new
function, for creating new EventHooker
instances.
Okay, this is all wonderful. But before we get on with the code, we need to really think through this scheme before continuing, so we can identify the problems with it before getting on. Ideally, you should come up with the problems yourself before reading on.
Consider a scenario:
A keyboard event occurs and the send_event
function is called. All the handlers are called. Okay, nice.
But then a timer event occurs, and... Which functions in the handler
's vector were hooked for the timer event.
This is a problem. How can we determine which functions belong to which event?
Well, like a lot of things in programming, there are many ways of approaching this problem, but the way we'll go here is to create two separate vectors for each event. This will work because there are only two events. And even if we do add more events, they can't be that many.
So, our EventHooker
becomes:
pub struct EventHooker {
// DELETED: handlers: Vec<BoxedFn>>
// Functions to be called when the timer event occurs
timer_handlers: Vec<BoxedFn>,
// Functions to be called when the keyboard event occurs
keyboard_handlers: Vec<BoxedFn>
}
Now, consider this:
index: 0 1 2 3 4
timer_handlers: vec [ func0, func1, func2, func3, func4 ]
The above represents a vector of boxed functions for handling timer events. Suppose we want to remove func2.
How do we do it? Of course, we use the unhook_event
function, but how exactly will unhook_event
do it? How
will it be able to identify func2?
One way we can do this is to return the function's vector index when we add it to the vector with hook_event
.
This way, we can pass the index to unhook_event
which will then use the index to identify the correct
function to remove.
But there is another problem with this index-as-identifier scheme. In this scheme, func0, func1, func2, func3, and func4 will have identifiers of 0, 1, 2, 3, and 4 respectively. But when func2 is removed, the functions func3 and func4 will have their indexes changed to 2 and 3.
index: 0 1 2 3
timer_handlers: vec [ func0, func1, func3, func4 ]
But the code that calls hook_event
will still think 3 and 4 are the ids for func3 and func4. This will lead to
chaos later because when unhook_event
is called for func3 afterward, the number 3 will be passed as the ID because the calling code still thinks that that is the index. But that's func4's index now, so func4 gets removed
instead of func3.
To solve this, we can solve this identifier problem in another way: keep track of a single next ID and whenever
a new function is added with hook_event
, we assign that ID to it and increment it. That assigned ID is returned
from the hook_event
function as the function's identifier. The ID is also saved with the function itself
in the handlers
vector. This way, even if a function is removed, the identifier associated with a function in
addition to the handlers
vector will remain the same.
Instead of having a vector of BoxedFn
s, we create a new structure:
#[derive(Clone)]
struct Handler {
// The identifier for this handler
id: usize,
// A function called when an event occurs
func: BoxedFn
}
And handlers
will be vectors of these instead:
pub struct EventHooker {
/* DELETED:
timer_handlers: Vec<BoxedFn>,
keyboard_handlers: Vec<BoxedFn>
*/
timer_handlers: Vec<Handler>,
keyboard_handlers: Vec<Handler>,
// The identifier to be used for the next function added
next_id: usize
}
There are certainly other problems with the scheme I haven't identified yet and I won't cover them. Do that yourself and fix the problems. But what we have now should be enough to get down with the code.
Let's start with new
, to create new EventHooker
instances.
use crate::allocator::Allocator;
impl EventHooker {
// Creates a new EventHooker instance
pub fn new(allocator: *mut dyn Allocator) -> Self {
EventHooker {
timer_handlers: Vec::with_capacity(10, allocator),
keyboard_handlers: Vec::with_capacity(10, allocator),
next_id: 0
}
}
}
We initialize our handler vectors to be able to hold up to 10 handlers. And initialize next_id
to 0. I think
that makes sense because 0 is the smallest usize
and the next_id
field will be increased anytime a new handler
is added.
Next: hook_event
. With hook_event
, we need the caller to tell us the type of event, timer or keyboard, that
we're going to be setting up a handler for. We could create the function to accept EventInfo
as an argument,
but, that's not what it's for. EventInfo
is for telling a registered event handler what happened during an
event. For the caller to tell us what kind of event they're registering a function for, we can create another
enum:
pub enum EventKind {
Timer,
Keyboard
}
As you can see, the Keyboard
variant does not have any associated info. That is because this enum is solely
for telling the kind of event and not what happened during one.
The event_hook
function can then accept this and return a usize
, which is the new handler's ID.
impl EventHooker {
// Used by client code to tell the EventHooker to call function `func`
// whenever an event of the kind `event_kind` takes place.
// The returned ID is used to identify the handler when removing it
pub fn hook_event(&mut self, event_kind: EventKind, func: BoxedFn) -> usize {
}
}
To give our code more meaning, we can use a type alias instead of directly writing usize
:
type HandlerId = usize;
struct Handler {
// DELETED: id: usize,
id: HandlerId, // NEW
func: BoxedFn
}
impl EventHooker {
// DELETED: pub fn hook_event(&mut self, event_kind: EventKind, func: BoxedFn) -> usize {
pub fn hook_event(&mut self, event_kind: EventKind, func: BoxedFn) -> HandlerId { // NEW
}
}
Now, what exactly happens here?
- First, the appropriate handler vector is determined from the
event_kind
argument. - Then a new
Handler
instance is created with the given function as the handler'sfunc
field and the current value ofnext_id
as the handler id. - The newly created
Handler
is pushed into the appropriate vector. - Then the
next_id
is incremented. - Finally, the newly created
Handler
's id is returned.
In code:
impl EventHooker {
pub fn hook_event(&mut self, event_kind: EventKind, func: BoxedFn) -> HandlerId {
let handler_vec = match event_kind {
EventKind::Timer => &mut self.timer_handlers,
EventKind::Keyboard => &mut self.keyboard_handlers
};
let new_handler = Handler { func, id: self.next_id };
handler_vec.push(new_handler);
self.next_id += 1;
handler_vec[handler_vec.len() - 1].id
}
}
Now, we get on to send_event
. This is the function that is called from Interrupt Service Routines to notify
the EventHooker
that an event has occurred. The EventHooker
then executes all the functions associated
with that event. In more concrete steps:
- Determine the right handler vector from the given
EventInfo
. - Call all the functions in the handler vector, passing the given
EventInfo
as an argument.
In code:
impl EventHooker {
// Used by Interrupt Service Routines to tell the EventHooker
// that an event has occured. The `event_info` provides information
// about the event that is passed to the handler function
pub fn send_event(&self, event_info: EventInfo) {
let handler_vec = match event_info {
EventInfo::Timer => &self.timer_handlers,
EventInfo::Keyboard(_) => &self.keyboard_handlers
};
for handler in handler_vec.iter() {
(handler.func)(event_info);
}
}
}
For this to work, we need to implement Clone
and Copy
for the EventInfo
structure:
#[derive(Clone, Copy)] // NEW
pub enum EventInfo {
Timer,
Keyboard(KeyEvent)
}
And for this, too, to work we need to implement Clone
and Copy
for KeyEvent
.
In keyboard.rs
// DELETED: #[derive(Debug)]
#[derive(Debug, Clone, Copy)]
pub struct KeyEvent {
pub keycode: KeyCode,
pub direction: KeyDirection
}
And for this to work, we need to implement the same for KeyCode
and KeyDirection
.
// DELETED: #[derive(Debug)]
#[derive(Debug, Clone, Copy)] // NEW
pub enum KeyCode { /* ...Others */ }
// DELETED: #[derive(Debug)]
#[derive(Debug, Clone, Copy)] // NEW
pub enum KeyDirection { /* ...Others */ }
And finally, the unhook_event
function. This function first has to determine which handler vector to remove
the function from (from the given EventKind
), then search for a handler with the given id, then remove that
handler.
The function will also return a result, just to indicate whether or not it successfully found and removed a handler with the given id. This can help with debugging later if we ever find ourselves in a position where we have a problem with this function.
impl EventHooker {
// Used by client code to tell the EventHooker to stop calling a handler with
// with ID `id` when an event of kind `event_kind` occurs.
// A return value of `Ok(())` means that the handler was found and removed.
// A return value of `Err(())` means that the handler was not found.
pub fn unhook_event(&mut self, event_kind: EventKind, id: HandlerId) -> Result<(), ()> {
let handler_vec = match event_kind {
EventKind::Timer => &mut self.timer_handlers,
EventKind::Keyboard => &mut self.keyboard_handlers
};
for i in 0..handler_vec.len() {
if handler_vec[i].id == id {
handler_vec.remove(i);
return Ok(())
}
}
Err(())
}
}
And that's it for our event handling implementation.
Take Away
For the full code, go to the repo
In The Next Post
We'll be testing the EventHooker