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}