1use std::sync::Arc;
2
3use lazy_static::lazy_static;
4use parking_lot::Mutex;
5
6use collections::{hash_map::Entry, HashMap, HashSet};
7
8use crate::{util::post_inc, ElementStateId};
9
10lazy_static! {
11 static ref LEAK_BACKTRACE: bool =
12 std::env::var("LEAK_BACKTRACE").map_or(false, |b| !b.is_empty());
13}
14
15struct ElementStateRefCount {
16 ref_count: usize,
17 frame_id: usize,
18}
19
20#[derive(Default)]
21pub struct RefCounts {
22 entity_counts: HashMap<usize, usize>,
23 element_state_counts: HashMap<ElementStateId, ElementStateRefCount>,
24 dropped_models: HashSet<usize>,
25 dropped_views: HashSet<(usize, usize)>,
26 dropped_element_states: HashSet<ElementStateId>,
27
28 #[cfg(any(test, feature = "test-support"))]
29 pub leak_detector: Arc<Mutex<LeakDetector>>,
30}
31
32impl RefCounts {
33 pub fn new(
34 #[cfg(any(test, feature = "test-support"))] leak_detector: Arc<Mutex<LeakDetector>>,
35 ) -> Self {
36 Self {
37 #[cfg(any(test, feature = "test-support"))]
38 leak_detector,
39 ..Default::default()
40 }
41 }
42
43 pub fn inc_model(&mut self, model_id: usize) {
44 match self.entity_counts.entry(model_id) {
45 Entry::Occupied(mut entry) => {
46 *entry.get_mut() += 1;
47 }
48 Entry::Vacant(entry) => {
49 entry.insert(1);
50 self.dropped_models.remove(&model_id);
51 }
52 }
53 }
54
55 pub fn inc_view(&mut self, window_id: usize, view_id: usize) {
56 match self.entity_counts.entry(view_id) {
57 Entry::Occupied(mut entry) => *entry.get_mut() += 1,
58 Entry::Vacant(entry) => {
59 entry.insert(1);
60 self.dropped_views.remove(&(window_id, view_id));
61 }
62 }
63 }
64
65 pub fn inc_element_state(&mut self, id: ElementStateId, frame_id: usize) {
66 match self.element_state_counts.entry(id) {
67 Entry::Occupied(mut entry) => {
68 let entry = entry.get_mut();
69 if entry.frame_id == frame_id || entry.ref_count >= 2 {
70 panic!("used the same element state more than once in the same frame");
71 }
72 entry.ref_count += 1;
73 entry.frame_id = frame_id;
74 }
75 Entry::Vacant(entry) => {
76 entry.insert(ElementStateRefCount {
77 ref_count: 1,
78 frame_id,
79 });
80 self.dropped_element_states.remove(&id);
81 }
82 }
83 }
84
85 pub fn dec_model(&mut self, model_id: usize) {
86 let count = self.entity_counts.get_mut(&model_id).unwrap();
87 *count -= 1;
88 if *count == 0 {
89 self.entity_counts.remove(&model_id);
90 self.dropped_models.insert(model_id);
91 }
92 }
93
94 pub fn dec_view(&mut self, window_id: usize, view_id: usize) {
95 let count = self.entity_counts.get_mut(&view_id).unwrap();
96 *count -= 1;
97 if *count == 0 {
98 self.entity_counts.remove(&view_id);
99 self.dropped_views.insert((window_id, view_id));
100 }
101 }
102
103 pub fn dec_element_state(&mut self, id: ElementStateId) {
104 let entry = self.element_state_counts.get_mut(&id).unwrap();
105 entry.ref_count -= 1;
106 if entry.ref_count == 0 {
107 self.element_state_counts.remove(&id);
108 self.dropped_element_states.insert(id);
109 }
110 }
111
112 pub fn is_entity_alive(&self, entity_id: usize) -> bool {
113 self.entity_counts.contains_key(&entity_id)
114 }
115
116 pub fn take_dropped(
117 &mut self,
118 ) -> (
119 HashSet<usize>,
120 HashSet<(usize, usize)>,
121 HashSet<ElementStateId>,
122 ) {
123 (
124 std::mem::take(&mut self.dropped_models),
125 std::mem::take(&mut self.dropped_views),
126 std::mem::take(&mut self.dropped_element_states),
127 )
128 }
129}
130
131#[cfg(any(test, feature = "test-support"))]
132#[derive(Default)]
133pub struct LeakDetector {
134 next_handle_id: usize,
135 #[allow(clippy::type_complexity)]
136 handle_backtraces: HashMap<
137 usize,
138 (
139 Option<&'static str>,
140 HashMap<usize, Option<backtrace::Backtrace>>,
141 ),
142 >,
143}
144
145#[cfg(any(test, feature = "test-support"))]
146impl LeakDetector {
147 pub fn handle_created(&mut self, type_name: Option<&'static str>, entity_id: usize) -> usize {
148 let handle_id = post_inc(&mut self.next_handle_id);
149 let entry = self.handle_backtraces.entry(entity_id).or_default();
150 let backtrace = if *LEAK_BACKTRACE {
151 Some(backtrace::Backtrace::new_unresolved())
152 } else {
153 None
154 };
155 if let Some(type_name) = type_name {
156 entry.0.get_or_insert(type_name);
157 }
158 entry.1.insert(handle_id, backtrace);
159 handle_id
160 }
161
162 pub fn handle_dropped(&mut self, entity_id: usize, handle_id: usize) {
163 if let Some((_, backtraces)) = self.handle_backtraces.get_mut(&entity_id) {
164 assert!(backtraces.remove(&handle_id).is_some());
165 if backtraces.is_empty() {
166 self.handle_backtraces.remove(&entity_id);
167 }
168 }
169 }
170
171 pub fn assert_dropped(&mut self, entity_id: usize) {
172 if let Some((type_name, backtraces)) = self.handle_backtraces.get_mut(&entity_id) {
173 for trace in backtraces.values_mut().flatten() {
174 trace.resolve();
175 eprintln!("{:?}", crate::util::CwdBacktrace(trace));
176 }
177
178 let hint = if *LEAK_BACKTRACE {
179 ""
180 } else {
181 " – set LEAK_BACKTRACE=1 for more information"
182 };
183
184 panic!(
185 "{} handles to {} {} still exist{}",
186 backtraces.len(),
187 type_name.unwrap_or("entity"),
188 entity_id,
189 hint
190 );
191 }
192 }
193
194 pub fn detect(&mut self) {
195 let mut found_leaks = false;
196 for (id, (type_name, backtraces)) in self.handle_backtraces.iter_mut() {
197 eprintln!(
198 "leaked {} handles to {} {}",
199 backtraces.len(),
200 type_name.unwrap_or("entity"),
201 id
202 );
203 for trace in backtraces.values_mut().flatten() {
204 trace.resolve();
205 eprintln!("{:?}", crate::util::CwdBacktrace(trace));
206 }
207 found_leaks = true;
208 }
209
210 let hint = if *LEAK_BACKTRACE {
211 ""
212 } else {
213 " – set LEAK_BACKTRACE=1 for more information"
214 };
215 assert!(!found_leaks, "detected leaked handles{}", hint);
216 }
217}