From 33406c29918dbb9940b5b052b0470264097c1663 Mon Sep 17 00:00:00 2001 From: james7132 Date: Tue, 24 May 2022 01:41:44 -0700 Subject: [PATCH 1/4] Garbage collect event memory --- crates/bevy_ecs/src/event.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/bevy_ecs/src/event.rs b/crates/bevy_ecs/src/event.rs index 22af1b3ed2dff..c8b5b7fdf094f 100644 --- a/crates/bevy_ecs/src/event.rs +++ b/crates/bevy_ecs/src/event.rs @@ -439,7 +439,10 @@ impl Events { /// called once per frame/update. pub fn update(&mut self) { std::mem::swap(&mut self.events_a, &mut self.events_b); + // Garbage collect unused event space. Shrink after clear to avoid a copy. + let new_capacity = self.events_b.len().max(self.events_b.capacity() / 2); self.events_b.clear(); + self.events_b.shrink_to(new_capacity); self.events_b.start_event_count = self.event_count; debug_assert_eq!( self.events_a.start_event_count + self.events_a.len(), From 0f9f3107e7f94aaa416875f9730637367c9f58b9 Mon Sep 17 00:00:00 2001 From: james7132 Date: Tue, 31 May 2022 14:35:53 -0700 Subject: [PATCH 2/4] Allow configuration of the garbage collection behavior --- crates/bevy_ecs/src/event.rs | 54 +++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/event.rs b/crates/bevy_ecs/src/event.rs index c8b5b7fdf094f..18aec2201832a 100644 --- a/crates/bevy_ecs/src/event.rs +++ b/crates/bevy_ecs/src/event.rs @@ -57,6 +57,41 @@ struct EventInstance { pub event: E, } +/// Settings for controlling the general behavior of [`Events`]. +pub struct EventSettings { + pub garbage_collection: EventGarbageCollection, + marker_: PhantomData T>, +} + +impl Default for EventSettings { + fn default() -> Self { + Self { + garbage_collection: Default::default(), + marker_: PhantomData, + } + } +} + +/// Settings for controlling the garbage collection behavior of [`Events`]. +pub enum EventGarbageCollection { + /// Exponentially decays the amount of allocated memory for the backing event buffer + /// with a configured minimum. This is the default. + ExponentialFalloff { + /// Each internal vec never goes below this value. + /// There are two internal vecs, so the total minimum capacity is 2 * min_capacity. + /// This defaults to 0. + min_capacity: usize, + }, + /// Disables garbage collection entirely. + None, +} + +impl Default for EventGarbageCollection { + fn default() -> Self { + Self::ExponentialFalloff { min_capacity: 0 } + } +} + /// An event collection that represents the events that occurred within the last two /// [`Events::update`] calls. /// Events can be written to using an [`EventWriter`] @@ -437,10 +472,17 @@ impl Events { /// Swaps the event buffers and clears the oldest event buffer. In general, this should be /// called once per frame/update. - pub fn update(&mut self) { + pub fn update(&mut self, settings: &EventSettings) { std::mem::swap(&mut self.events_a, &mut self.events_b); // Garbage collect unused event space. Shrink after clear to avoid a copy. - let new_capacity = self.events_b.len().max(self.events_b.capacity() / 2); + let new_capacity = match settings.garbage_collection { + EventGarbageCollection::ExponentialFalloff { min_capacity } => self + .events_b + .len() + .max(self.events_b.capacity() / 2) + .max(min_capacity), + EventGarbageCollection::None => self.events_b.capacity(), + }; self.events_b.clear(); self.events_b.shrink_to(new_capacity); self.events_b.start_event_count = self.event_count; @@ -451,8 +493,12 @@ impl Events { } /// A system that calls [`Events::update`] once per frame. - pub fn update_system(mut events: ResMut) { - events.update(); + pub fn update_system(mut events: ResMut, settings: Option>>) { + if let Some(settings) = settings { + events.update(&settings); + } else { + events.update(&Default::default()); + } } #[inline] From 5bd5b124b9677b1dd5b9eebd7a3c589cbc367bab Mon Sep 17 00:00:00 2001 From: james7132 Date: Tue, 31 May 2022 15:01:38 -0700 Subject: [PATCH 3/4] Fix tests --- crates/bevy_ecs/src/event.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/crates/bevy_ecs/src/event.rs b/crates/bevy_ecs/src/event.rs index 18aec2201832a..89bd4622dead5 100644 --- a/crates/bevy_ecs/src/event.rs +++ b/crates/bevy_ecs/src/event.rs @@ -117,7 +117,7 @@ impl Default for EventGarbageCollection { /// /// # Example /// ``` -/// use bevy_ecs::event::Events; +/// use bevy_ecs::event::*; /// /// struct MyEvent { /// value: usize @@ -126,9 +126,10 @@ impl Default for EventGarbageCollection { /// // setup /// let mut events = Events::::default(); /// let mut reader = events.get_reader(); +/// let settings = EventSettings::::default(); /// /// // run this once per update/frame -/// events.update(); +/// events.update(&settings); /// /// // somewhere else: send an event /// events.send(MyEvent { value: 1 }); @@ -647,7 +648,7 @@ mod tests { "reader_a receives next unread event" ); - events.update(); + events.update(&Default::default()); let mut reader_d = events.get_reader(); @@ -669,7 +670,7 @@ mod tests { "reader_d receives all events created before and after update" ); - events.update(); + events.update(&Default::default()); assert_eq!( get_events(&events, &mut reader_missed), @@ -703,7 +704,7 @@ mod tests { assert!(reader.iter(&events).next().is_none()); events.send(E(2)); - events.update(); + events.update(&Default::default()); events.send(E(3)); assert!(reader.iter(&events).eq([E(2), E(3)].iter())); @@ -740,12 +741,12 @@ mod tests { events.send(TestEvent { i: 0 }); assert!(!events.is_empty()); - events.update(); + events.update(&Default::default()); assert!(!events.is_empty()); // events are only empty after the second call to update // due to double buffering. - events.update(); + events.update(&Default::default()); assert!(events.is_empty()); } @@ -799,12 +800,12 @@ mod tests { events.send(TestEvent { i: 0 }); let reader = events.get_reader(); assert_eq!(reader.len(&events), 2); - events.update(); + events.update(&Default::default()); events.send(TestEvent { i: 0 }); assert_eq!(reader.len(&events), 3); - events.update(); + events.update(&Default::default()); assert_eq!(reader.len(&events), 1); - events.update(); + events.update(&Default::default()); assert!(reader.is_empty(&events)); } From adb34306649e5fb3c55e27a1bde010c112985549 Mon Sep 17 00:00:00 2001 From: James Liu Date: Tue, 31 May 2022 22:01:30 -0700 Subject: [PATCH 4/4] Better docstrings for garbage_collection Co-authored-by: Alice Cecile --- crates/bevy_ecs/src/event.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_ecs/src/event.rs b/crates/bevy_ecs/src/event.rs index 89bd4622dead5..bb85742f5b372 100644 --- a/crates/bevy_ecs/src/event.rs +++ b/crates/bevy_ecs/src/event.rs @@ -59,6 +59,7 @@ struct EventInstance { /// Settings for controlling the general behavior of [`Events`]. pub struct EventSettings { + /// Controls how and when the memory allocated for events will be freed pub garbage_collection: EventGarbageCollection, marker_: PhantomData T>, }