dioxus_cameras/registry.rs
1//! Shared per-stream frame state: [`Registry`] owns a map of id → [`LatestFrame`],
2//! and [`LatestFrame`] holds the most recent [`Frame`] for one stream.
3
4use std::collections::HashMap;
5use std::sync::atomic::{AtomicU32, Ordering};
6use std::sync::{Arc, Mutex};
7
8use cameras::Frame;
9
10use crate::poison::recover_lock;
11
12/// Crate-internal abstraction over anything the preview server can read
13/// frames from.
14///
15/// Kept private so [`crate::server`] depends on the trait rather than on
16/// [`Registry`] directly; lets the server switch to a different backing
17/// store (or `Arc<dyn FrameSource>`) later without public-API churn.
18pub(crate) trait FrameSource: Send + Sync + 'static {
19 fn snapshot(&self, id: u32) -> Option<(Frame, u32)>;
20}
21
22/// A shared map from stream id to [`LatestFrame`].
23///
24/// Cheap to clone, internally it holds an `Arc`, so the same registry is
25/// shared across Dioxus contexts, background threads, and HTTP handlers.
26///
27/// Obtained from [`crate::PreviewServer`]. Users don't typically construct
28/// one directly; instead they read it from context via `use_context::<Registry>()`
29/// (which [`crate::PreviewServer`] registers via
30/// [`register_with`](crate::register_with)).
31#[derive(Clone, Default)]
32pub struct Registry {
33 pub(crate) inner: Arc<Mutex<HashMap<u32, LatestFrame>>>,
34}
35
36impl FrameSource for Registry {
37 fn snapshot(&self, id: u32) -> Option<(Frame, u32)> {
38 let guard = recover_lock(&self.inner);
39 guard.get(&id)?.snapshot_with_counter()
40 }
41}
42
43/// Return the [`LatestFrame`] slot for `id`, creating one if absent.
44pub fn get_or_create_sink(registry: &Registry, id: u32) -> LatestFrame {
45 let mut guard = recover_lock(®istry.inner);
46 guard.entry(id).or_default().clone()
47}
48
49/// Drop the [`LatestFrame`] slot for `id`.
50///
51/// Other clones of that sink continue to function; the registry just stops
52/// handing it out on future [`get_or_create_sink`] calls.
53pub fn remove_sink(registry: &Registry, id: u32) {
54 let mut guard = recover_lock(®istry.inner);
55 guard.remove(&id);
56}
57
58/// A shareable slot holding the latest [`Frame`] for one stream.
59///
60/// Returned by [`get_or_create_sink`] and fed by the
61/// [pump](cameras::pump::spawn)'s sink closure. Most callers just wire a
62/// [`LatestFrame`] from the registry into a pump and never touch it directly
63///, the hook [`use_camera_stream`](crate::use_camera_stream) does exactly
64/// that. If you're running your own pump, call [`publish_frame`] on each
65/// frame.
66#[derive(Clone, Default)]
67pub struct LatestFrame {
68 pub(crate) frame: Arc<Mutex<Option<Frame>>>,
69 pub(crate) counter: Arc<AtomicU32>,
70}
71
72impl LatestFrame {
73 pub(crate) fn snapshot_with_counter(&self) -> Option<(Frame, u32)> {
74 let slot = recover_lock(&self.frame);
75 let frame = slot.as_ref()?.clone();
76 let counter = self.counter.load(Ordering::Acquire);
77 Some((frame, counter))
78 }
79}
80
81/// Publish `frame` as the latest value on `sink`, replacing any previous
82/// frame.
83///
84/// The sink's monotonic counter increments with every call so HTTP clients
85/// can skip redundant texture uploads.
86pub fn publish_frame(sink: &LatestFrame, frame: Frame) {
87 let mut slot = recover_lock(&sink.frame);
88 *slot = Some(frame);
89 sink.counter.fetch_add(1, Ordering::Release);
90}