Skip to content

Commit ea542bb

Browse files
committed
[update] events to only be dispatched to components subscribed to them
1 parent d00056e commit ea542bb

File tree

1 file changed

+138
-1
lines changed

1 file changed

+138
-1
lines changed

crates/lambda-rs/src/runtimes/application.rs

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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+
189254
const MAX_TARGET_FPS: u32 = 1000;
190255

191256
fn 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

Comments
 (0)