-
Notifications
You must be signed in to change notification settings - Fork 2
Open
Labels
enhancementNew feature or requestNew feature or request
Description
Overview
Refactor the Component trait's event handling system to replace the monolithic
on_event method with granular, opt-in event handlers and a bitmask-based
filtering system. This change eliminates boilerplate pattern matching in
components, improves runtime performance by skipping uninterested components,
and provides a more ergonomic API for component authors.
Current State
The current Component trait requires all components to implement on_event,
which receives every event and forces exhaustive pattern matching:
// crates/lambda-rs/src/component.rs:30-33
fn on_event(&mut self, event: Events) -> Result<R, E>;Components must match against all event variants even when they only care about
a subset:
// Example from crates/lambda-rs/examples/textured_cube.rs:370-385
fn on_event(&mut self, event: Events) -> Result<ComponentResult, String> {
match event {
Events::Window { event, .. } => match event {
WindowEvent::Resize { width, height } => {
self.width = width;
self.height = height;
}
_ => {}
},
_ => {} // Must handle all other variants
}
return Ok(ComponentResult::Success);
}This approach has several limitations:
- Boilerplate: Every component must write
_ => {}arms for unused events - Poor branch prediction: Runtime iterates all components for every event
- No filtering: Components receive events they never handle
Scope
Goals:
- Introduce
EventMaskbitmask type for O(1) event category filtering - Add granular
on_*_eventmethods with default no-op implementations - Update
ApplicationRuntimeto skip components based on their event mask - Remove the
on_eventmethod from theComponenttrait - Update all examples to use the new event handling API
Non-Goals:
- Generic
Handles<E>trait system (adds complexity without runtime benefit
when using trait objects) - Dynamic event subscription/unsubscription at runtime
- Event prioritization or ordering guarantees beyond current behavior
Proposed API
EventMask (events.rs)
/// Bitmask for O(1) event category filtering.
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
pub struct EventMask(u8);
impl EventMask {
pub const NONE: Self = Self(0);
pub const WINDOW: Self = Self(1 << 0);
pub const KEYBOARD: Self = Self(1 << 1);
pub const MOUSE: Self = Self(1 << 2);
pub const RUNTIME: Self = Self(1 << 3);
pub const COMPONENT: Self = Self(1 << 4);
/// Check if this mask contains the given event category.
#[inline(always)]
pub const fn contains(self, other: Self) -> bool {
(self.0 & other.0) != 0
}
/// Combine two masks.
#[inline(always)]
pub const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
}
impl Events {
/// Return the mask for this event's category.
pub const fn mask(&self) -> EventMask {
match self {
Events::Window { .. } => EventMask::WINDOW,
Events::Keyboard { .. } => EventMask::KEYBOARD,
Events::Mouse { .. } => EventMask::MOUSE,
Events::Runtime { .. } => EventMask::RUNTIME,
Events::Component { .. } => EventMask::COMPONENT,
}
}
}Updated Component Trait (component.rs)
pub trait Component<R, E>
where
R: Sized + Debug,
E: Sized + Debug,
{
/// Return which event categories this component handles.
/// The runtime skips dispatch for events not in this mask.
fn event_mask(&self) -> EventMask {
EventMask::NONE
}
/// Called when a window event occurs. Override to handle.
fn on_window_event(&mut self, _event: &WindowEvent) {}
/// Called when a keyboard event occurs. Override to handle.
fn on_keyboard_event(&mut self, _event: &Key) {}
/// Called when a mouse event occurs. Override to handle.
fn on_mouse_event(&mut self, _event: &Mouse) {}
/// Called when a runtime event occurs. Override to handle.
fn on_runtime_event(&mut self, _event: &RuntimeEvent) {}
/// Called when a component event occurs. Override to handle.
fn on_component_event(&mut self, _event: &ComponentEvent) {}
// Existing lifecycle methods remain unchanged
fn on_attach(&mut self, render_context: &mut RenderContext) -> Result<R, E>;
fn on_detach(&mut self, render_context: &mut RenderContext) -> Result<R, E>;
fn on_update(&mut self, last_frame: &Duration) -> Result<R, E>;
fn on_render(&mut self, render_context: &mut RenderContext) -> Vec<RenderCommand>;
}Example Usage
impl Component<ComponentResult, String> for PlayerController {
fn event_mask(&self) -> EventMask {
EventMask::WINDOW
.union(EventMask::KEYBOARD)
.union(EventMask::MOUSE)
}
fn on_window_event(&mut self, event: &WindowEvent) {
if let WindowEvent::Resize { width, height } = event {
self.width = *width;
self.height = *height;
}
}
fn on_keyboard_event(&mut self, event: &Key) {
if let Key::Pressed { virtual_key: Some(VirtualKey::Escape), .. } = event {
self.paused = true;
}
}
fn on_mouse_event(&mut self, event: &Mouse) {
if let Mouse::Moved { x, y, .. } = event {
self.cursor_position = (*x, *y);
}
}
// on_runtime_event and on_component_event use default no-op
fn on_attach(&mut self, _ctx: &mut RenderContext) -> Result<ComponentResult, String> {
return Ok(ComponentResult::Success);
}
// ... other lifecycle methods
}Acceptance Criteria
-
EventMasktype added toevents.rswithNONE,WINDOW,KEYBOARD,
MOUSE,RUNTIME,COMPONENTconstants -
Events::mask()method returns the appropriateEventMaskfor each variant -
Componenttrait updated withevent_mask()andon_*_event()methods -
on_eventmethod removed fromComponenttrait -
ApplicationRuntimeupdated to filter components by mask before dispatch - All examples updated to use new event handling API
- Unit tests added for
EventMaskoperations - Documentation updated for
Componenttrait and event handling
Affected Crates
lambda-rs
Notes
- This change is breaking and requires updates to all existing components
- The bitmask approach allows future extension (up to 8 event categories with
u8, expandable tou16oru32if needed)
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request