From 0da8be079e59322f5a7a9908a85182ddbf1ed6d3 Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Thu, 9 Jan 2025 10:01:06 +0100 Subject: [PATCH] feat: Support incremental rendering in `use_camera` hook (#1034) --- crates/elements/src/definitions.rs | 3 + crates/hooks/src/use_camera.rs | 99 +++++++++++++++++++++--------- crates/hooks/src/use_node.rs | 19 ++++++ examples/camera.rs | 7 ++- 4 files changed, 97 insertions(+), 31 deletions(-) diff --git a/crates/elements/src/definitions.rs b/crates/elements/src/definitions.rs index f1ac50bc8..417a91ad7 100644 --- a/crates/elements/src/definitions.rs +++ b/crates/elements/src/definitions.rs @@ -740,6 +740,9 @@ builder_constructors! { // Image image_data: String, + + // Reference + reference: Reference, image_reference: String, // Accessibility diff --git a/crates/hooks/src/use_camera.rs b/crates/hooks/src/use_camera.rs index 685199a06..5e181aa69 100644 --- a/crates/hooks/src/use_camera.rs +++ b/crates/hooks/src/use_camera.rs @@ -3,19 +3,29 @@ use std::sync::{ Mutex, }; +use bytes::Bytes; use dioxus_core::{ prelude::spawn, use_hook, AttributeValue, }; -use dioxus_hooks::use_signal; +use dioxus_hooks::{ + to_owned, + use_effect, + use_reactive, + use_signal, +}; use dioxus_signals::{ + CopyValue, + ReadOnlySignal, + Readable, Signal, Writable, }; use freya_node_state::{ CustomAttributeValues, ImageReference, + NodeReference, }; pub use nokhwa::utils::{ CameraIndex, @@ -29,9 +39,14 @@ use nokhwa::{ NokhwaError, }; -use crate::use_platform; +use crate::{ + use_node, + use_node_with_reference, + use_platform, +}; /// Configuration for a camera +#[derive(Clone, PartialEq, Debug)] pub struct CameraSettings { camera_index: CameraIndex, resolution: Option, @@ -68,33 +83,59 @@ impl Default for CameraSettings { } } -/// Connect to a given camera and render its frames into an image element -pub fn use_camera( - camera_settings: CameraSettings, -) -> (AttributeValue, Signal>) { - let platform = use_platform(); - let mut camera_error = use_signal(|| None); - let image_reference = use_hook(|| Arc::new(Mutex::new(None))); +#[derive(Debug, Clone)] +pub struct UseCamera { + error: Signal>, + node_reference: NodeReference, + image: Arc>>, +} - let image_reference_attr = AttributeValue::any_value(CustomAttributeValues::ImageReference( - ImageReference(image_reference.clone()), - )); +impl UseCamera { + /// Get a [AttributeValue] for the `reference` attribute. + pub fn attribute(&self) -> AttributeValue { + AttributeValue::any_value(CustomAttributeValues::Reference( + self.node_reference.clone(), + )) + } - use_hook(move || { - spawn(async move { - let mut handle_error = |e: NokhwaError| { - camera_error.set(Some(e)); - }; + /// Get a [AttributeValue] for the `image_reference` attribute. + pub fn image_attribute(&self) -> AttributeValue { + AttributeValue::any_value(CustomAttributeValues::ImageReference(ImageReference( + self.image.clone(), + ))) + } + /// Get a [ReadOnlySignal] of the error. + pub fn error(&self) -> ReadOnlySignal> { + self.error.into() + } +} + +/// Connect to a given camera and render its frames into an image element +pub fn use_camera(camera_settings: CameraSettings) -> UseCamera { + let platform = use_platform(); + let mut error = use_signal(|| None); + let image = use_hook(|| Arc::new(Mutex::new(None))); + let (node_reference, size) = use_node_with_reference(); + + let camera = UseCamera { + error, + image: image.clone(), + node_reference, + }; + + use_effect(use_reactive!(|camera_settings| { + to_owned![image]; + spawn(async move { let requested = RequestedFormat::new::(camera_settings.camera_format); let camera = Camera::new(camera_settings.camera_index, requested); if let Ok(mut camera) = camera { // Set the custom resolution if specified if let Some(resolution) = camera_settings.resolution { - camera - .set_resolution(resolution) - .unwrap_or_else(&mut handle_error); + if let Err(err) = camera.set_resolution(resolution) { + error.set(Some(err)); + } } let mut ticker = platform.new_ticker(); @@ -107,22 +148,24 @@ pub fn use_camera( let frame = camera.frame(); if let Ok(frame) = frame { - let bts = frame.buffer_bytes(); - // Send the frame to the renderer via the image reference - image_reference.lock().unwrap().replace(bts); + let new_frame = frame.buffer_bytes(); + + // Replace the old frame with the new + image.lock().unwrap().replace(new_frame); - // Request the renderer to rerender + // Request a rerender + platform.invalidate_drawing_area(size.peek().area); platform.request_animation_frame(); } else if let Err(err) = frame { - handle_error(err); + error.set(Some(err)); break; } } } else if let Err(err) = camera { - handle_error(err); + error.set(Some(err)); } }); - }); + })); - (image_reference_attr, camera_error) + camera } diff --git a/crates/hooks/src/use_node.rs b/crates/hooks/src/use_node.rs index 8f44affa5..200045a9a 100644 --- a/crates/hooks/src/use_node.rs +++ b/crates/hooks/src/use_node.rs @@ -93,6 +93,25 @@ pub fn use_node_signal_with_prev() -> ( ) } +pub fn use_node_with_reference() -> (NodeReference, ReadOnlySignal) { + let (tx, signal) = use_hook(|| { + let (tx, mut rx) = channel::(NodeReferenceLayout::default()); + let mut signal = Signal::new(NodeReferenceLayout::default()); + + spawn(async move { + while rx.changed().await.is_ok() { + if *signal.peek() != *rx.borrow() { + signal.set(rx.borrow().clone()); + } + } + }); + + (Arc::new(tx), signal) + }); + + (NodeReference(tx), signal.into()) +} + #[cfg(test)] mod test { use freya::prelude::*; diff --git a/examples/camera.rs b/examples/camera.rs index 60d290552..3d5d2f247 100644 --- a/examples/camera.rs +++ b/examples/camera.rs @@ -17,14 +17,14 @@ fn main() { } #[cfg(feature = "use_camera")] fn app() -> Element { - let (image_reference, camera_error) = use_camera(CameraSettings::default()); + let image = use_camera(CameraSettings::default()); rsx!( rect { width: "100%", height: "100%", padding: "50", - if let Some(err) = &*camera_error.read() { + if let Some(err) = &*image.error().read() { label { color: "black", "{err}" @@ -33,7 +33,8 @@ fn app() -> Element { image { width: "100%", height: "100%", - image_reference + reference: image.attribute(), + image_reference: image.image_attribute() } } }