Skip to main content

frame_hook_plan/
frame_hook_plan.rs

1/*
2 * frame_hook_plan.rs
3 *
4 * Copyright (c) 2026 ParkSangWoo
5 *
6 * Author(s):
7 *
8 *      ParkSangWoo <dev.parksangwoo@gmail.com>
9 *
10 *
11 * This file is part of cumulus3d.
12 *
13 * Licensed under the Apache License, Version 2.0 (the "License");
14 * you may not use this file except in compliance with the License.
15 * You may obtain a copy of the License at
16 *
17 *      http://www.apache.org/licenses/LICENSE-2.0
18 *
19 * Unless required by applicable law or agreed to in writing, software
20 * distributed under the License is distributed on an "AS IS" BASIS,
21 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 * See the License for the specific language governing permissions and
23 * limitations under the License.
24 *
25 * SPDX-License-Identifier: Apache-2.0
26 */
27
28//! Runs a TOML plan (same as `cumulus3d run <plan.toml>`) with a frame hook attached, and reports
29//! what the hook saw: frame count per camera, image sizes, and the time from start to each frame.
30//!
31//! ```bash
32//! cargo run --release -p cumulus3d-cli --example frame_hook_plan -- plan.toml
33//! ```
34//! Exits with an error if the hook saw no frames or any frame buffer has the wrong size.
35
36use cumulus3d_cli::declare::{Plan, Recon};
37use cumulus3d_cli::events::Frame;
38use std::collections::BTreeMap;
39use std::sync::{Arc, Mutex};
40use std::time::Instant;
41
42#[derive(Default)]
43struct Seen {
44    per_camera: BTreeMap<String, usize>,
45    bad: usize,
46    bytes: usize,
47    sizes: BTreeMap<(u32, u32), usize>,
48    first_s: Option<f64>,
49    last_s: f64,
50    positions: usize,
51}
52
53fn main() -> Result<(), String> {
54    let path = std::env::args().nth(1).ok_or("usage: frame_hook_plan <plan.toml>")?;
55    let plan = Plan::load(&path)?;
56    let seen = Arc::new(Mutex::new(Seen::default()));
57    let refined = Arc::new(Mutex::new(Vec::new()));
58    let t0 = Instant::now();
59    let (s, r) = (seen.clone(), refined.clone());
60    let recon = Recon::from_plan(plan)
61        .on_frame(move |f: &Arc<Frame>| {
62            let mut s = s.lock().unwrap();
63            let t = t0.elapsed().as_secs_f64();
64            s.first_s.get_or_insert(t);
65            s.last_s = t;
66            *s.per_camera.entry(f.camera.clone()).or_default() += 1;
67            *s.sizes.entry((f.width, f.height)).or_default() += 1;
68            s.bytes += f.rgb.len();
69            s.positions = s.positions.max(f.position + 1);
70            if f.rgb.len() != (f.width * f.height * 3) as usize || f.width == 0 {
71                s.bad += 1;
72            }
73        })
74        .on_zone_refined(move |z, cloud| r.lock().unwrap().push((z.zone, cloud.len())))
75        .build()
76        .map_err(|e| e.to_string())?;
77    let summary = recon.run()?;
78    let s = seen.lock().unwrap();
79    let total: usize = s.per_camera.values().sum();
80    println!(
81        "frame hook: {total} frames over {} positions, per camera {:?}, sizes {:?}, {:.1} MB RGB, bad {}",
82        s.positions,
83        s.per_camera,
84        s.sizes,
85        s.bytes as f64 / 1e6,
86        s.bad
87    );
88    println!(
89        "frame hook: first frame at {:.2}s, last at {:.2}s; run total {:.1}s",
90        s.first_s.unwrap_or(0.0),
91        s.last_s,
92        t0.elapsed().as_secs_f64()
93    );
94    println!("refined zones (zone, dense points): {:?}", refined.lock().unwrap());
95    println!("events {}, hook calls {}, hook panics {}", summary.events, summary.hook_calls, summary.hook_panics);
96    if total == 0 || s.bad > 0 || summary.hook_panics > 0 {
97        return Err("frame hook check failed".into());
98    }
99    Ok(())
100}