1use crate::surface_requirements::{SurfaceRequirement, SurfaceRequirementSet};
7use std::cell::{Cell, RefCell};
8
9use cranpose_core::NodeId;
10use cranpose_ui_graphics::Rect;
11
12const TOP_ISOLATED_LAYER_LIMIT: usize = 8;
13
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
15pub struct LayerSurfaceReasons {
16 pub explicit_offscreen: bool,
17 pub effect: bool,
18 pub backdrop: bool,
19 pub group_opacity: bool,
20 pub blend_mode: bool,
21 pub immediate_shadow: bool,
22 pub text_local_surface: bool,
23 pub motion_stable_capture: bool,
24 pub mixed_direct_content: bool,
25 pub non_translation_transform: bool,
26 pub pixel_stable_composite: bool,
27}
28
29impl LayerSurfaceReasons {
30 pub fn has_any(self) -> bool {
35 self.explicit_offscreen
36 || self.effect
37 || self.backdrop
38 || self.group_opacity
39 || self.blend_mode
40 || self.text_local_surface
41 || self.motion_stable_capture
42 || self.non_translation_transform
43 }
44
45 pub fn labels(self) -> impl Iterator<Item = &'static str> {
46 let mut labels = [None; 11];
47 let mut len = 0usize;
48
49 if self.explicit_offscreen {
50 labels[len] = Some("explicit_offscreen");
51 len += 1;
52 }
53 if self.effect {
54 labels[len] = Some("effect");
55 len += 1;
56 }
57 if self.backdrop {
58 labels[len] = Some("backdrop");
59 len += 1;
60 }
61 if self.group_opacity {
62 labels[len] = Some("group_opacity");
63 len += 1;
64 }
65 if self.blend_mode {
66 labels[len] = Some("blend_mode");
67 len += 1;
68 }
69 if self.immediate_shadow {
70 labels[len] = Some("immediate_shadow");
71 len += 1;
72 }
73 if self.text_local_surface {
74 labels[len] = Some("text_local_surface");
75 len += 1;
76 }
77 if self.motion_stable_capture {
78 labels[len] = Some("motion_stable_capture");
79 len += 1;
80 }
81 if self.mixed_direct_content {
82 labels[len] = Some("mixed_direct_content");
83 len += 1;
84 }
85 if self.non_translation_transform {
86 labels[len] = Some("non_translation_transform");
87 len += 1;
88 }
89 if self.pixel_stable_composite {
90 labels[len] = Some("pixel_stable_composite");
91 len += 1;
92 }
93
94 labels.into_iter().flatten().take(len)
95 }
96
97 pub fn display(self) -> String {
98 let mut joined = String::new();
99 for (index, label) in self.labels().enumerate() {
100 if index > 0 {
101 joined.push('+');
102 }
103 joined.push_str(label);
104 }
105 if joined.is_empty() {
106 joined.push_str("none");
107 }
108 joined
109 }
110
111 pub fn has_renderer_forced_surface(self) -> bool {
112 self.text_local_surface || self.non_translation_transform
113 }
114}
115
116impl From<SurfaceRequirementSet> for LayerSurfaceReasons {
117 fn from(requirements: SurfaceRequirementSet) -> Self {
118 Self {
119 explicit_offscreen: requirements.contains(SurfaceRequirement::ExplicitOffscreen),
120 effect: requirements.contains(SurfaceRequirement::RenderEffect),
121 backdrop: requirements.contains(SurfaceRequirement::Backdrop),
122 group_opacity: requirements.contains(SurfaceRequirement::GroupOpacity),
123 blend_mode: requirements.contains(SurfaceRequirement::BlendMode),
124 immediate_shadow: requirements.contains(SurfaceRequirement::ImmediateShadow),
125 text_local_surface: requirements.contains(SurfaceRequirement::TextMaterialMask),
126 motion_stable_capture: requirements.contains(SurfaceRequirement::MotionStableCapture),
127 mixed_direct_content: requirements.contains(SurfaceRequirement::MixedDirectContent),
128 non_translation_transform: requirements
129 .contains(SurfaceRequirement::NonTranslationTransform),
130 pixel_stable_composite: requirements.contains(SurfaceRequirement::PixelStableComposite),
131 }
132 }
133}
134
135#[derive(Clone, Copy, Debug, PartialEq)]
136pub struct IsolatedLayerStat {
137 pub node_id: Option<NodeId>,
138 pub logical_rect: Rect,
139 pub width: u32,
140 pub height: u32,
141 pub reasons: LayerSurfaceReasons,
142}
143
144impl IsolatedLayerStat {
145 fn pixel_area(self) -> u64 {
146 (self.width as u64) * (self.height as u64)
147 }
148}
149
150impl Default for IsolatedLayerStat {
151 fn default() -> Self {
152 Self {
153 node_id: None,
154 logical_rect: Rect {
155 x: 0.0,
156 y: 0.0,
157 width: 0.0,
158 height: 0.0,
159 },
160 width: 0,
161 height: 0,
162 reasons: LayerSurfaceReasons::default(),
163 }
164 }
165}
166
167#[derive(Clone, Copy, Debug, Default, PartialEq)]
168pub struct FrameStatsSnapshot {
169 pub submits: u32,
170 pub offscreen_acquires: u32,
171 pub offscreen_news: u32,
172 pub offscreen_total_bytes: u64,
173 pub upload_bytes: u64,
174 pub isolated_layer_renders: u32,
175 pub isolated_layer_pixels: u64,
176 pub layer_cache_hits: u32,
177 pub layer_cache_misses: u32,
178 pub layer_cache_evictions: u32,
179 pub layer_cache_hit_pixels: u64,
180 pub layer_cache_miss_pixels: u64,
181 pub blur_passes: u32,
182 pub composite_passes: u32,
183 pub effect_applies: u32,
184 pub shape_passes: u32,
185 pub image_passes: u32,
186 pub text_passes: u32,
187 pub offscreen_pool_size: u32,
188 pub offscreen_pool_bytes: u64,
189 pub text_pool_size: u32,
190 pub layer_cache_size: u32,
191 pub layer_cache_bytes: u64,
192 pub image_cache_size: u32,
193 pub text_cache_size: u32,
194 pub top_isolated_layers: [Option<IsolatedLayerStat>; TOP_ISOLATED_LAYER_LIMIT],
195 pub top_isolated_layer_count: usize,
196}
197
198impl FrameStatsSnapshot {
199 pub fn top_isolated_layers(self) -> impl Iterator<Item = IsolatedLayerStat> {
200 self.top_isolated_layers
201 .into_iter()
202 .flatten()
203 .take(self.top_isolated_layer_count)
204 }
205
206 fn layer_cache_hit_rate(self) -> f64 {
207 let total = self.layer_cache_hits + self.layer_cache_misses;
208 if total > 0 {
209 (self.layer_cache_hits as f64 / total as f64) * 100.0
210 } else {
211 0.0
212 }
213 }
214
215 fn print(self, frame_count: u64) {
216 let mb = self.offscreen_total_bytes as f64 / (1024.0 * 1024.0);
217 let upload_mb = self.upload_bytes as f64 / (1024.0 * 1024.0);
218 let pool_mb = self.offscreen_pool_bytes as f64 / (1024.0 * 1024.0);
219 let layer_cache_hit_mpx = self.layer_cache_hit_pixels as f64 / 1_000_000.0;
220 let layer_cache_miss_mpx = self.layer_cache_miss_pixels as f64 / 1_000_000.0;
221 let layer_cache_mb = self.layer_cache_bytes as f64 / (1024.0 * 1024.0);
222 let isolated_layer_mpx = self.isolated_layer_pixels as f64 / 1_000_000.0;
223 eprintln!(
224 "[GPU f#{}] submits={} | offscreen: acq={} new={} {:.1}MB pool={}({:.1}MB) | \
225 uploads={:.2}MB | \
226 isolated_layers={} area={:.2}MP | \
227 layer_cache: hit={} miss={} {:.1}% evict={} hit_px={:.2}MP miss_px={:.2}MP size={}({:.1}MB) | \
228 blur={} composite={} effect={} | shape={} image={} text={} | \
229 caches: text_pool={} img={} txt={}",
230 frame_count,
231 self.submits,
232 self.offscreen_acquires,
233 self.offscreen_news,
234 mb,
235 self.offscreen_pool_size,
236 pool_mb,
237 upload_mb,
238 self.isolated_layer_renders,
239 isolated_layer_mpx,
240 self.layer_cache_hits,
241 self.layer_cache_misses,
242 self.layer_cache_hit_rate(),
243 self.layer_cache_evictions,
244 layer_cache_hit_mpx,
245 layer_cache_miss_mpx,
246 self.layer_cache_size,
247 layer_cache_mb,
248 self.blur_passes,
249 self.composite_passes,
250 self.effect_applies,
251 self.shape_passes,
252 self.image_passes,
253 self.text_passes,
254 self.text_pool_size,
255 self.image_cache_size,
256 self.text_cache_size,
257 );
258 for (index, layer) in self.top_isolated_layers().enumerate() {
259 eprintln!(
260 " [isolated #{index}] node={:?} rect=({:.1},{:.1},{:.1},{:.1}) target={}x{} reasons={}",
261 layer.node_id,
262 layer.logical_rect.x,
263 layer.logical_rect.y,
264 layer.logical_rect.width,
265 layer.logical_rect.height,
266 layer.width,
267 layer.height,
268 layer.reasons.display(),
269 );
270 }
271 }
272}
273
274#[derive(Default)]
277pub(crate) struct FrameStats {
278 pub submits: Cell<u32>,
279 pub offscreen_acquires: Cell<u32>,
280 pub offscreen_news: Cell<u32>,
281 pub offscreen_total_bytes: Cell<u64>,
282 pub upload_bytes: Cell<u64>,
283 pub isolated_layer_renders: Cell<u32>,
284 pub isolated_layer_pixels: Cell<u64>,
285 pub layer_cache_hits: Cell<u32>,
286 pub layer_cache_misses: Cell<u32>,
287 pub layer_cache_evictions: Cell<u32>,
288 pub layer_cache_hit_pixels: Cell<u64>,
289 pub layer_cache_miss_pixels: Cell<u64>,
290 pub blur_passes: Cell<u32>,
291 pub composite_passes: Cell<u32>,
292 pub effect_applies: Cell<u32>,
293 pub shape_passes: Cell<u32>,
294 pub image_passes: Cell<u32>,
295 pub text_passes: Cell<u32>,
296 pub offscreen_pool_size: Cell<u32>,
298 pub offscreen_pool_bytes: Cell<u64>,
299 pub text_pool_size: Cell<u32>,
300 pub layer_cache_size: Cell<u32>,
301 pub layer_cache_bytes: Cell<u64>,
302 pub image_cache_size: Cell<u32>,
303 pub text_cache_size: Cell<u32>,
304 top_isolated_layers: RefCell<[Option<IsolatedLayerStat>; TOP_ISOLATED_LAYER_LIMIT]>,
305 top_isolated_layer_count: Cell<usize>,
306}
307
308impl FrameStats {
309 pub fn bump_submits(&self) {
310 self.submits.set(self.submits.get() + 1);
311 }
312
313 pub fn record_offscreen_acquire(&self, width: u32, height: u32, is_new: bool) {
314 self.offscreen_acquires
315 .set(self.offscreen_acquires.get() + 1);
316 if is_new {
317 self.offscreen_news.set(self.offscreen_news.get() + 1);
318 }
319 self.offscreen_total_bytes
320 .set(self.offscreen_total_bytes.get() + (width as u64) * (height as u64) * 4);
321 }
322
323 pub fn record_upload_bytes(&self, bytes: u64) {
324 self.upload_bytes
325 .set(self.upload_bytes.get().saturating_add(bytes));
326 }
327
328 pub fn record_isolated_layer_render(
329 &self,
330 width: u32,
331 height: u32,
332 node_id: Option<NodeId>,
333 logical_rect: Rect,
334 reasons: LayerSurfaceReasons,
335 ) {
336 self.isolated_layer_renders
337 .set(self.isolated_layer_renders.get().saturating_add(1));
338 self.isolated_layer_pixels.set(
339 self.isolated_layer_pixels
340 .get()
341 .saturating_add((width as u64) * (height as u64)),
342 );
343 self.record_top_isolated_layer(IsolatedLayerStat {
344 node_id,
345 logical_rect,
346 width,
347 height,
348 reasons,
349 });
350 }
351
352 pub fn record_layer_cache_hit(&self, width: u32, height: u32) {
353 self.layer_cache_hits
354 .set(self.layer_cache_hits.get().saturating_add(1));
355 self.layer_cache_hit_pixels.set(
356 self.layer_cache_hit_pixels
357 .get()
358 .saturating_add((width as u64) * (height as u64)),
359 );
360 }
361
362 pub fn record_layer_cache_miss(&self, width: u32, height: u32) {
363 self.layer_cache_misses
364 .set(self.layer_cache_misses.get().saturating_add(1));
365 self.layer_cache_miss_pixels.set(
366 self.layer_cache_miss_pixels
367 .get()
368 .saturating_add((width as u64) * (height as u64)),
369 );
370 }
371
372 pub fn record_layer_cache_eviction(&self) {
373 self.layer_cache_evictions
374 .set(self.layer_cache_evictions.get().saturating_add(1));
375 }
376
377 pub fn bump_shapes(&self) {
378 self.shape_passes.set(self.shape_passes.get() + 1);
379 }
380
381 pub fn bump_images(&self) {
382 self.image_passes.set(self.image_passes.get() + 1);
383 }
384
385 pub fn bump_text(&self) {
386 self.text_passes.set(self.text_passes.get() + 1);
387 }
388
389 pub fn snapshot(&self) -> FrameStatsSnapshot {
390 FrameStatsSnapshot {
391 submits: self.submits.get(),
392 offscreen_acquires: self.offscreen_acquires.get(),
393 offscreen_news: self.offscreen_news.get(),
394 offscreen_total_bytes: self.offscreen_total_bytes.get(),
395 upload_bytes: self.upload_bytes.get(),
396 isolated_layer_renders: self.isolated_layer_renders.get(),
397 isolated_layer_pixels: self.isolated_layer_pixels.get(),
398 layer_cache_hits: self.layer_cache_hits.get(),
399 layer_cache_misses: self.layer_cache_misses.get(),
400 layer_cache_evictions: self.layer_cache_evictions.get(),
401 layer_cache_hit_pixels: self.layer_cache_hit_pixels.get(),
402 layer_cache_miss_pixels: self.layer_cache_miss_pixels.get(),
403 blur_passes: self.blur_passes.get(),
404 composite_passes: self.composite_passes.get(),
405 effect_applies: self.effect_applies.get(),
406 shape_passes: self.shape_passes.get(),
407 image_passes: self.image_passes.get(),
408 text_passes: self.text_passes.get(),
409 offscreen_pool_size: self.offscreen_pool_size.get(),
410 offscreen_pool_bytes: self.offscreen_pool_bytes.get(),
411 text_pool_size: self.text_pool_size.get(),
412 layer_cache_size: self.layer_cache_size.get(),
413 layer_cache_bytes: self.layer_cache_bytes.get(),
414 image_cache_size: self.image_cache_size.get(),
415 text_cache_size: self.text_cache_size.get(),
416 top_isolated_layers: *self.top_isolated_layers.borrow(),
417 top_isolated_layer_count: self.top_isolated_layer_count.get(),
418 }
419 }
420
421 pub fn reset(&self) {
422 self.submits.set(0);
423 self.offscreen_acquires.set(0);
424 self.offscreen_news.set(0);
425 self.offscreen_total_bytes.set(0);
426 self.upload_bytes.set(0);
427 self.isolated_layer_renders.set(0);
428 self.isolated_layer_pixels.set(0);
429 self.layer_cache_hits.set(0);
430 self.layer_cache_misses.set(0);
431 self.layer_cache_evictions.set(0);
432 self.layer_cache_hit_pixels.set(0);
433 self.layer_cache_miss_pixels.set(0);
434 self.blur_passes.set(0);
435 self.composite_passes.set(0);
436 self.effect_applies.set(0);
437 self.shape_passes.set(0);
438 self.image_passes.set(0);
439 self.text_passes.set(0);
440 *self.top_isolated_layers.borrow_mut() = [None; TOP_ISOLATED_LAYER_LIMIT];
441 self.top_isolated_layer_count.set(0);
442 }
443
444 pub fn maybe_print_snapshot(
445 &self,
446 snapshot: FrameStatsSnapshot,
447 frame_count: &mut u64,
448 enabled: bool,
449 ) {
450 if !enabled {
451 return;
452 }
453 *frame_count += 1;
454 if (*frame_count).is_multiple_of(60) {
455 snapshot.print(*frame_count);
456 }
457 }
458
459 fn record_top_isolated_layer(&self, layer: IsolatedLayerStat) {
460 if !layer.reasons.has_any() {
461 return;
462 }
463
464 let mut top_layers = self.top_isolated_layers.borrow_mut();
465 let len = self.top_isolated_layer_count.get();
466 let insert_at = top_layers[..len]
467 .iter()
468 .enumerate()
469 .find_map(|(index, existing)| {
470 existing
471 .filter(|existing| layer.pixel_area() > existing.pixel_area())
472 .map(|_| index)
473 })
474 .unwrap_or(len);
475
476 if insert_at >= TOP_ISOLATED_LAYER_LIMIT {
477 return;
478 }
479
480 let new_len = if len < TOP_ISOLATED_LAYER_LIMIT {
481 len + 1
482 } else {
483 TOP_ISOLATED_LAYER_LIMIT
484 };
485
486 let mut index = new_len.saturating_sub(1);
487 while index > insert_at {
488 top_layers[index] = top_layers[index - 1];
489 index -= 1;
490 }
491 top_layers[insert_at] = Some(layer);
492 self.top_isolated_layer_count.set(new_len);
493 }
494}
495
496pub(crate) fn gpu_stats_enabled() -> bool {
497 use std::sync::OnceLock;
498 static ENABLED: OnceLock<bool> = OnceLock::new();
499 *ENABLED.get_or_init(|| {
500 std::env::var("CRANPOSE_GPU_STATS")
501 .map(|v| matches!(v.as_str(), "1" | "true" | "yes"))
502 .unwrap_or(false)
503 })
504}
505
506#[cfg(test)]
507mod tests {
508 use super::*;
509
510 #[test]
511 fn layer_cache_counters_accumulate_and_reset() {
512 let stats = FrameStats::default();
513 stats.record_upload_bytes(512);
514 stats.record_layer_cache_hit(10, 20);
515 stats.record_layer_cache_hit(3, 4);
516 stats.record_layer_cache_miss(5, 6);
517 stats.record_layer_cache_eviction();
518
519 assert_eq!(stats.layer_cache_hits.get(), 2);
520 assert_eq!(stats.layer_cache_misses.get(), 1);
521 assert_eq!(stats.layer_cache_evictions.get(), 1);
522 assert_eq!(stats.layer_cache_hit_pixels.get(), 212);
523 assert_eq!(stats.layer_cache_miss_pixels.get(), 30);
524
525 stats.record_isolated_layer_render(
526 7,
527 8,
528 Some(9),
529 Rect {
530 x: 2.0,
531 y: 3.0,
532 width: 4.0,
533 height: 5.0,
534 },
535 LayerSurfaceReasons {
536 text_local_surface: true,
537 ..LayerSurfaceReasons::default()
538 },
539 );
540 let snapshot = stats.snapshot();
541
542 assert_eq!(snapshot.isolated_layer_renders, 1);
543 assert_eq!(snapshot.isolated_layer_pixels, 56);
544 assert_eq!(snapshot.upload_bytes, 512);
545 assert_eq!(snapshot.layer_cache_hits, 2);
546 assert_eq!(snapshot.layer_cache_misses, 1);
547 let top_layers = snapshot.top_isolated_layers().collect::<Vec<_>>();
548 assert_eq!(top_layers.len(), 1);
549 assert_eq!(top_layers[0].node_id, Some(9));
550 assert_eq!(stats.layer_cache_hits.get(), 2);
551 assert_eq!(stats.layer_cache_misses.get(), 1);
552
553 stats.reset();
554
555 assert_eq!(stats.layer_cache_hits.get(), 0);
556 assert_eq!(stats.layer_cache_misses.get(), 0);
557 assert_eq!(stats.layer_cache_evictions.get(), 0);
558 assert_eq!(stats.layer_cache_hit_pixels.get(), 0);
559 assert_eq!(stats.layer_cache_miss_pixels.get(), 0);
560 assert_eq!(stats.upload_bytes.get(), 0);
561 assert_eq!(stats.isolated_layer_renders.get(), 0);
562 assert_eq!(stats.isolated_layer_pixels.get(), 0);
563 assert_eq!(stats.top_isolated_layer_count.get(), 0);
564 }
565
566 #[test]
567 fn maybe_print_snapshot_only_advances_frame_counter_when_enabled() {
568 let stats = FrameStats::default();
569 let snapshot = stats.snapshot();
570 let mut frame_count = 0;
571
572 stats.maybe_print_snapshot(snapshot, &mut frame_count, false);
573 assert_eq!(frame_count, 0);
574
575 stats.maybe_print_snapshot(snapshot, &mut frame_count, true);
576 assert_eq!(frame_count, 1);
577 }
578
579 #[test]
580 fn layer_surface_reasons_report_runtime_only_bits() {
581 let reasons = LayerSurfaceReasons {
582 immediate_shadow: true,
583 text_local_surface: true,
584 mixed_direct_content: true,
585 ..LayerSurfaceReasons::default()
586 };
587
588 assert!(reasons.has_any());
589 assert!(reasons.has_renderer_forced_surface());
590 assert_eq!(
591 reasons.labels().collect::<Vec<_>>(),
592 vec![
593 "immediate_shadow",
594 "text_local_surface",
595 "mixed_direct_content"
596 ]
597 );
598 assert_eq!(
599 reasons.display(),
600 "immediate_shadow+text_local_surface+mixed_direct_content"
601 );
602 }
603
604 #[test]
605 fn immediate_shadow_only_is_diagnostic_not_isolating() {
606 let reasons = LayerSurfaceReasons {
607 immediate_shadow: true,
608 ..LayerSurfaceReasons::default()
609 };
610
611 assert!(!reasons.has_any());
612 assert!(!reasons.has_renderer_forced_surface());
613 assert_eq!(
614 reasons.labels().collect::<Vec<_>>(),
615 vec!["immediate_shadow"]
616 );
617 assert_eq!(reasons.display(), "immediate_shadow");
618 }
619
620 #[test]
621 fn mixed_direct_content_only_is_diagnostic_not_isolating() {
622 let reasons = LayerSurfaceReasons {
623 mixed_direct_content: true,
624 ..LayerSurfaceReasons::default()
625 };
626
627 assert!(!reasons.has_any());
628 assert!(!reasons.has_renderer_forced_surface());
629 assert_eq!(
630 reasons.labels().collect::<Vec<_>>(),
631 vec!["mixed_direct_content"]
632 );
633 assert_eq!(reasons.display(), "mixed_direct_content");
634 }
635
636 #[test]
637 fn has_any_matches_has_isolating_requirement_for_each_requirement() {
638 let all_requirements = [
639 SurfaceRequirement::ExplicitOffscreen,
640 SurfaceRequirement::RenderEffect,
641 SurfaceRequirement::Backdrop,
642 SurfaceRequirement::GroupOpacity,
643 SurfaceRequirement::BlendMode,
644 SurfaceRequirement::ImmediateShadow,
645 SurfaceRequirement::TextMaterialMask,
646 SurfaceRequirement::MotionStableCapture,
647 SurfaceRequirement::NonTranslationTransform,
648 SurfaceRequirement::MixedDirectContent,
649 SurfaceRequirement::PixelStableComposite,
650 ];
651 for requirement in all_requirements {
652 let set = SurfaceRequirementSet::default().with(requirement);
653 let reasons = LayerSurfaceReasons::from(set);
654 assert_eq!(
655 reasons.has_any(),
656 set.has_isolating_requirement(),
657 "has_any vs has_isolating_requirement mismatch for {requirement:?}"
658 );
659 }
660 }
661
662 #[test]
663 fn top_isolated_layers_keep_largest_runtime_surfaces() {
664 let stats = FrameStats::default();
665 for index in 0..(TOP_ISOLATED_LAYER_LIMIT + 2) {
666 stats.record_isolated_layer_render(
667 16 + index as u32,
668 8 + index as u32,
669 Some(index),
670 Rect {
671 x: index as f32,
672 y: 0.0,
673 width: 10.0,
674 height: 10.0,
675 },
676 LayerSurfaceReasons {
677 text_local_surface: true,
678 ..LayerSurfaceReasons::default()
679 },
680 );
681 }
682
683 let snapshot = stats.snapshot();
684 let top_layers = snapshot.top_isolated_layers().collect::<Vec<_>>();
685 assert_eq!(top_layers.len(), TOP_ISOLATED_LAYER_LIMIT);
686 assert_eq!(top_layers[0].node_id, Some(TOP_ISOLATED_LAYER_LIMIT + 1));
687 assert_eq!(top_layers[1].node_id, Some(TOP_ISOLATED_LAYER_LIMIT));
688 }
689}