Skip to content

[Feature] Bitmask-Based Component Event Filtering with Granular Handlers #91

Description

@vmarcella

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-33fnon_event(&mutself,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-385fnon_event(&mutself,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}returnOk(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 EventMask bitmask type for O(1) event category filtering
  • Add granular on_*_event methods with default no-op implementations
  • Update ApplicationRuntime to skip components based on their event mask
  • Remove the on_event method from the Component trait
  • 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)]pubstructEventMask(u8);implEventMask{pubconstNONE:Self = Self(0);pubconstWINDOW:Self = Self(1 << 0);pubconstKEYBOARD:Self = Self(1 << 1);pubconstMOUSE:Self = Self(1 << 2);pubconstRUNTIME:Self = Self(1 << 3);pubconstCOMPONENT:Self = Self(1 << 4);/// Check if this mask contains the given event category.#[inline(always)]pubconstfncontains(self,other:Self) -> bool{(self.0& other.0) != 0}/// Combine two masks.#[inline(always)]pubconstfnunion(self,other:Self) -> Self{Self(self.0 | other.0)}}implEvents{/// Return the mask for this event's category.pubconstfnmask(&self) -> EventMask{matchself{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)

pubtraitComponent<R,E>whereR:Sized + Debug,E:Sized + Debug,{/// Return which event categories this component handles./// The runtime skips dispatch for events not in this mask.fnevent_mask(&self) -> EventMask{EventMask::NONE}/// Called when a window event occurs. Override to handle.fnon_window_event(&mutself,_event:&WindowEvent){}/// Called when a keyboard event occurs. Override to handle.fnon_keyboard_event(&mutself,_event:&Key){}/// Called when a mouse event occurs. Override to handle.fnon_mouse_event(&mutself,_event:&Mouse){}/// Called when a runtime event occurs. Override to handle.fnon_runtime_event(&mutself,_event:&RuntimeEvent){}/// Called when a component event occurs. Override to handle.fnon_component_event(&mutself,_event:&ComponentEvent){}// Existing lifecycle methods remain unchangedfnon_attach(&mutself,render_context:&mutRenderContext) -> Result<R,E>;fnon_detach(&mutself,render_context:&mutRenderContext) -> Result<R,E>;fnon_update(&mutself,last_frame:&Duration) -> Result<R,E>;fnon_render(&mutself,render_context:&mutRenderContext) -> Vec<RenderCommand>;}

Example Usage

implComponent<ComponentResult,String>forPlayerController{fnevent_mask(&self) -> EventMask{EventMask::WINDOW.union(EventMask::KEYBOARD).union(EventMask::MOUSE)}fnon_window_event(&mutself,event:&WindowEvent){ifletWindowEvent::Resize{ width, height } = event {self.width = *width;self.height = *height;}}fnon_keyboard_event(&mutself,event:&Key){ifletKey::Pressed{virtual_key:Some(VirtualKey::Escape), .. } = event {self.paused = true;}}fnon_mouse_event(&mutself,event:&Mouse){ifletMouse::Moved{ x, y, .. } = event {self.cursor_position = (*x,*y);}}// on_runtime_event and on_component_event use default no-opfnon_attach(&mutself,_ctx:&mutRenderContext) -> Result<ComponentResult,String>{returnOk(ComponentResult::Success);}// ... other lifecycle methods}

Acceptance Criteria

  • EventMask type added to events.rs with NONE, WINDOW, KEYBOARD,
    MOUSE, RUNTIME, COMPONENT constants
  • Events::mask() method returns the appropriate EventMask for each variant
  • Component trait updated with event_mask() and on_*_event() methods
  • on_event method removed from Component trait
  • ApplicationRuntime updated to filter components by mask before dispatch
  • All examples updated to use new event handling API
  • Unit tests added for EventMask operations
  • Documentation updated for Component trait 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 to u16 or u32 if needed)

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions