Skip to main content

euv_ui/hook/profiler/
impl.rs

1use super::*;
2
3/// Inherent implementation of [`ProfilerHandle`].
4impl ProfilerHandle {
5    /// Constructs a `ProfilerHandle` with an empty entries log.
6    ///
7    /// Lombok `New` cannot derive this for us because the
8    /// `entries` field is a `Signal<...>` rather than a plain
9    /// value — we cannot synthesise a meaningful default at
10    /// compile time, so the hook-context factory wires one up at
11    /// runtime via `Signal::create(Vec::new())`.
12    ///
13    /// # Returns
14    ///
15    /// - `ProfilerHandle` - A profiler handle with no recorded
16    ///   measurements.
17    pub fn new_with_empty_entries() -> Self {
18        Self {
19            entries: Signal::create(Vec::new()),
20        }
21    }
22
23    /// Records a fresh measurement around the given closure.
24    ///
25    /// Captures the start timestamp, runs `f`, captures the
26    /// end timestamp, and pushes a `ProfileEntry { label,
27    /// elapsed_ms, timestamp_ms }` into the entries signal.
28    /// The `elapsed_ms` is `>= 0.0` by construction (start is
29    /// always captured before `f` runs).
30    ///
31    /// # Arguments
32    ///
33    /// - `&str` - The label for this measurement.
34    /// - `F: FnOnce() -> R` - The closure to measure. Can
35    ///   return any type — the return value is forwarded to
36    ///   the caller unchanged.
37    ///
38    /// # Returns
39    ///
40    /// - `R` - Whatever `f` returned.
41    pub fn measure<F, R>(&self, label: &str, f: F) -> R
42    where
43        F: FnOnce() -> R,
44    {
45        let started_ms: f64 = now_ms();
46        let result: R = f();
47        let ended_ms: f64 = now_ms();
48        let entry: ProfileEntry =
49            ProfileEntry::new(label.to_string(), ended_ms - started_ms, ended_ms);
50        // Push the entry onto the existing entries vector.
51        // Read-modify-write via `.get()` is necessary because
52        // `Signal<T>::set` requires `T: Clone` (which
53        // `Vec<ProfileEntry>` is) but does NOT take `&mut T`
54        // — the entire new value is supplied as the argument.
55        let mut current: Vec<ProfileEntry> = self.get_entries().get();
56        current.push(entry);
57        self.get_entries().set(current);
58        result
59    }
60
61    /// Starts a measurement that will end later.
62    ///
63    /// Use this when the measured region is not a single
64    /// closure — e.g. you want to bracket an async operation
65    /// or a callback fired from event handling. The returned
66    /// `ProfilerMark` knows the start timestamp and the
67    /// entries signal; pass it to `end()` when the work
68    /// completes.
69    ///
70    /// # Arguments
71    ///
72    /// - `&str` - The label for this measurement.
73    ///
74    /// # Returns
75    ///
76    /// - `ProfilerMark` - An RAII-ish guard. Call `mark.end()`
77    ///   to push the `ProfileEntry`; drop without `end()` to
78    ///   discard the measurement.
79    pub fn begin(&self, label: &str) -> ProfilerMark {
80        ProfilerMark::new(label.to_string(), now_ms(), *self.get_entries())
81    }
82
83    /// Empties the entries vector. Useful between benchmarks
84    /// ("measure just this call, not the previous ones too")
85    /// and in tests ("start from a clean slate").
86    pub fn clear(&self) {
87        self.get_entries().set(Vec::new());
88    }
89}
90
91/// Inherent implementation of [`ProfilerMark`].
92impl ProfilerMark {
93    /// Closes the measurement started by `begin()` and pushes
94    /// the resulting `ProfileEntry` into the entries signal.
95    ///
96    /// After calling `end()`, the marker is consumed and
97    /// cannot be reused. Calling `end()` twice is a no-op on
98    /// the second call — the marker is moved into the first
99    /// call, so the borrow checker prevents a second
100    /// invocation in well-typed code.
101    pub fn end(self) {
102        let ended_ms: f64 = now_ms();
103        let entry: ProfileEntry = ProfileEntry::new(
104            self.get_label().clone(),
105            ended_ms - self.get_started_ms(),
106            ended_ms,
107        );
108        let mut current: Vec<ProfileEntry> = self.get_entries().get();
109        current.push(entry);
110        self.get_entries().set(current);
111    }
112}