#event-handling #events #static #generic #handlers #zero-cost #system

nightly static-events

A library for creating zero-cost event systems

4 releases

Uses old Rust 2015

0.1.3 Jun 16, 2018
0.1.2 Jun 16, 2018
0.1.1 Jun 13, 2018
0.1.0 Jun 13, 2018

#23 in #zero-cost

MIT/Apache

30KB
365 lines

A generic zero-cost event handler system. Event dispatches should get compiled down to a plain function that executes all handlers involved with no dynamic dispatches.

This library is nightly-only as it relies on specialization.

See the documentation for more information.

This library is dual licenced under the MIT and Apache 2.0 licenses.


lib.rs:

A generic zero-cost event handler system. Event dispatches should get compiled down to a plain function that executes all handlers involved with no dynamic dispatches.

As this crate depends on specialization, you must enable #![feature(specialization)] in all crates that use this library:

#![feature(specialization)]
#[macro_use] extern crate static_events;

Basic model

Events can be any type that implements Event, and the type itself is used as the key that event handlers to distinguish different events.

Event handlers are primarily defined by types that implement EventRoot, which provides a default noop EventHandler implementation for all Events, which can be further overwritten with explicit EventHandler implementations.

EventSet is a further abstraction over EventRoot, that has methods that are individually generic over all Events, rather than having an impl of EventHandler for all Events like EventRoot.

Finally, EventDispatch is the highest level event handling trait, with a function that takes a Event, and returns its RetVal.

Event dispatch

Events dispatches fundamentally behave as function calls built up extensibly from individual event handlers. They take an event, and return a value (which may simply be ()).

At the beginning of an event dispatch, Event::starting_state is called once to create a temporary state value. This will be passed to all event handlers, and is used to store transient state and as an accumulator for the final return value.

The lower level event handlers, EventHandler and EventSet each contain 5 methods that are executed in the following order:
init -> check -> before_event -> on_event -> after_event

They take both the event itself and the current state as mutable borrows, and return a status value controlling the rest of the event dispatch:

  • EvOk continues the event dispatch as normal.
  • EvCancelStage prevents the execution of the currently executing method any further event handlers.
  • EvCancel immediately stops the event dispatch, proceeding to the calculation of the return value.

Finally, at the end of event dispatch, Event::to_return_value is called on the state to compute the final return value. In many cases, this will simply be a noop.

Defining events

Any module can define an event. Events are normal types that implement the Event trait, which can be declared with various macros;

  • simple_event! for events that directly return their state to the caller, or do not use state at all.
  • failable_event! for events that can fail and return Results.
  • ipc_event! for events that should only have one listener processing it.
pub struct MyEvent(u32);
simple_event!(MyEvent, u32, 0);

While Event is stable API, and can be manually implemented, this should only be done in special cases.

Defining event handlers

Individual event handlers are defined using a combination of EventRoot (a marker trait), and any number of EventHandler implementations:

struct MyEventHandler;
impl EventRoot for MyEventHandler { }
impl EventHandler<MyEvent> for MyEventHandler {
    fn on_event(&self, _: &impl EventDispatch, ev: &mut MyEvent, i: &mut u32) -> EventResult {
        *i += ev.0;
        EvOk
    }
}

assert_eq!(MyEventHandler.dispatch(MyEvent(42)), 42);

simple_event_handler! may also be used instead for handlers with no state or parameters. A event_handler! macro also exists which adds EventHandler impls to an existing struct, rather than creating a new one:

simple_event_handler!(MyEventHandler, MyEvent: {
    on_event: |_, ev, i| { *i += ev.0 }
});

Finally, multiple event handlers may be merged using the merged_eventset! macro:

simple_event_handler!(MyEventHandler, MyEvent: {
    on_event: |_, ev, i| { *i += ev.0 }
});
simple_event_handler!(MyOtherEventHandler, MyEvent: {
    on_event: |_, ev, i| { *i *= ev.0 }
});

merged_eventset! {
    #[derive(Default)]
    struct SquaringEventHandler<T: EventSet> {
        evh_a: MyEventHandler, evh_b: T,
    }
}

assert_eq!(SquaringEventHandler::<MyOtherEventHandler>::default().dispatch(MyEvent(9)), 81);

Limitations

A fundamental limitation to this approach is that event handlers cannot be dynamically added or removed at runtime, and sets of handlers can only be defined at compile-time.

As all event handlers are passed around using immutable pointers, locking or cells must be used to store state in handlers.

Dependencies