@@ -186,6 +186,71 @@ fn dispatch_event_to_component(
186186 }
187187}
188188
189+ const EVENT_CATEGORY_COUNT : usize = 5 ;
190+
191+ /// Maps a single event-category bit to the corresponding listener bucket.
192+ ///
193+ /// This helper accepts only one of the concrete event category masks produced
194+ /// by [`Events::mask`]. It returns an error for `EventMask::NONE` or any mask
195+ /// outside the supported categories so the runtime can surface the invariant
196+ /// violation without panicking.
197+ fn event_listener_bucket ( event_mask : EventMask ) -> Result < usize , String > {
198+ if event_mask. contains ( EventMask :: WINDOW ) {
199+ return Ok ( 0 ) ;
200+ }
201+ if event_mask. contains ( EventMask :: KEYBOARD ) {
202+ return Ok ( 1 ) ;
203+ }
204+ if event_mask. contains ( EventMask :: MOUSE ) {
205+ return Ok ( 2 ) ;
206+ }
207+ if event_mask. contains ( EventMask :: RUNTIME ) {
208+ return Ok ( 3 ) ;
209+ }
210+ if event_mask. contains ( EventMask :: COMPONENT ) {
211+ return Ok ( 4 ) ;
212+ }
213+
214+ return Err ( format ! (
215+ "Unsupported event mask for listener bucket: {:?}" ,
216+ event_mask
217+ ) ) ;
218+ }
219+
220+ /// Builds a per-category index of component listeners for event dispatch.
221+ ///
222+ /// Each component is inspected once during runtime startup and its position in
223+ /// the component stack is recorded in every bucket named by its [`EventMask`].
224+ /// This front-loads an `O(C)` setup cost so dispatch can visit only matching
225+ /// listeners for an event category instead of scanning all `C` components on
226+ /// every event.
227+ fn build_event_listener_index (
228+ components : & [ Box < dyn Component < ComponentResult , String > > ] ,
229+ ) -> [ Vec < usize > ; EVENT_CATEGORY_COUNT ] {
230+ let mut listeners = std:: array:: from_fn ( |_| Vec :: new ( ) ) ;
231+
232+ for ( index, component) in components. iter ( ) . enumerate ( ) {
233+ let mask = component. event_mask ( ) ;
234+ if mask. contains ( EventMask :: WINDOW ) {
235+ listeners[ 0 ] . push ( index) ;
236+ }
237+ if mask. contains ( EventMask :: KEYBOARD ) {
238+ listeners[ 1 ] . push ( index) ;
239+ }
240+ if mask. contains ( EventMask :: MOUSE ) {
241+ listeners[ 2 ] . push ( index) ;
242+ }
243+ if mask. contains ( EventMask :: RUNTIME ) {
244+ listeners[ 3 ] . push ( index) ;
245+ }
246+ if mask. contains ( EventMask :: COMPONENT ) {
247+ listeners[ 4 ] . push ( index) ;
248+ }
249+ }
250+
251+ return listeners;
252+ }
253+
189254const MAX_TARGET_FPS : u32 = 1000 ;
190255
191256fn div_ceil_u64 ( numerator : u64 , denominator : u64 ) -> u64 {
@@ -222,6 +287,7 @@ impl Runtime<(), String> for ApplicationRuntime {
222287 let mut event_loop = LoopBuilder :: new ( ) . build ( ) ;
223288 let window = self . window_builder . build ( & mut event_loop) ;
224289 let mut component_stack = self . component_stack ;
290+ let listener_index = build_event_listener_index ( & component_stack) ;
225291 let render_context = match self . render_context_builder . build ( & window) {
226292 Ok ( ctx) => ctx,
227293 Err ( err) => {
@@ -493,7 +559,34 @@ impl Runtime<(), String> for ApplicationRuntime {
493559 logging:: trace!( "Sending event: {:?} to all components" , event) ;
494560
495561 let event_mask = event. mask ( ) ;
496- for component in & mut component_stack {
562+ let bucket = match event_listener_bucket ( event_mask) {
563+ Ok ( bucket) => bucket,
564+ Err ( error) => {
565+ logging:: error!( "{}" , error) ;
566+ publisher. publish_event ( Events :: Runtime {
567+ event : RuntimeEvent :: ComponentPanic { message : error } ,
568+ issued_at : Instant :: now ( ) ,
569+ } ) ;
570+ return ;
571+ }
572+ } ;
573+ let listeners = & listener_index[ bucket] ;
574+ for component_index in listeners {
575+ let component = match component_stack. get_mut ( * component_index) {
576+ Some ( component) => component,
577+ None => {
578+ let error = format ! (
579+ "Listener index {} is out of bounds for component stack." ,
580+ component_index
581+ ) ;
582+ logging:: error!( "{}" , error) ;
583+ publisher. publish_event ( Events :: Runtime {
584+ event : RuntimeEvent :: ComponentPanic { message : error } ,
585+ issued_at : Instant :: now ( ) ,
586+ } ) ;
587+ return ;
588+ }
589+ } ;
497590 let event_result = dispatch_event_to_component (
498591 & event,
499592 event_mask,
@@ -723,4 +816,48 @@ mod tests {
723816 assert ! ( error. contains( "A component has panicked while handling an event." ) ) ;
724817 assert ! ( error. contains( "window failure" ) ) ;
725818 }
819+
820+ #[ test]
821+ fn event_listener_bucket_maps_each_category ( ) {
822+ assert_eq ! ( event_listener_bucket( EventMask :: WINDOW ) , Ok ( 0 ) ) ;
823+ assert_eq ! ( event_listener_bucket( EventMask :: KEYBOARD ) , Ok ( 1 ) ) ;
824+ assert_eq ! ( event_listener_bucket( EventMask :: MOUSE ) , Ok ( 2 ) ) ;
825+ assert_eq ! ( event_listener_bucket( EventMask :: RUNTIME ) , Ok ( 3 ) ) ;
826+ assert_eq ! ( event_listener_bucket( EventMask :: COMPONENT ) , Ok ( 4 ) ) ;
827+ }
828+
829+ #[ test]
830+ fn event_listener_bucket_rejects_empty_mask ( ) {
831+ let error = event_listener_bucket ( EventMask :: NONE ) . unwrap_err ( ) ;
832+ assert ! ( error. contains( "Unsupported event mask" ) ) ;
833+ }
834+
835+ #[ test]
836+ fn build_event_listener_index_registers_only_matching_components ( ) {
837+ let components: Vec < Box < dyn Component < ComponentResult , String > > > = vec ! [
838+ Box :: new( RecordingComponent {
839+ mask: EventMask :: WINDOW | EventMask :: KEYBOARD ,
840+ ..Default :: default ( )
841+ } ) ,
842+ Box :: new( RecordingComponent {
843+ mask: EventMask :: RUNTIME ,
844+ ..Default :: default ( )
845+ } ) ,
846+ Box :: new( RecordingComponent {
847+ mask: EventMask :: NONE ,
848+ ..Default :: default ( )
849+ } ) ,
850+ Box :: new( RecordingComponent {
851+ mask: EventMask :: MOUSE | EventMask :: COMPONENT ,
852+ ..Default :: default ( )
853+ } ) ,
854+ ] ;
855+
856+ let listeners = build_event_listener_index ( & components) ;
857+ assert_eq ! ( listeners[ 0 ] , vec![ 0 ] ) ;
858+ assert_eq ! ( listeners[ 1 ] , vec![ 0 ] ) ;
859+ assert_eq ! ( listeners[ 2 ] , vec![ 3 ] ) ;
860+ assert_eq ! ( listeners[ 3 ] , vec![ 1 ] ) ;
861+ assert_eq ! ( listeners[ 4 ] , vec![ 3 ] ) ;
862+ }
726863}
0 commit comments