Skip to content

Commit

Permalink
feat: Event Loop ticker for components (#345)
Browse files Browse the repository at this point in the history
* feat: Event Loop ticker for components

* improvements and fixes

* improvements

* use_camera improvements

* improvements

* simplified

* clean up

* clean up
  • Loading branch information
marc2332 authored Oct 22, 2023
1 parent 4ac85ef commit e63ad8d
Show file tree
Hide file tree
Showing 13 changed files with 203 additions and 123 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"rust-analyzer.cargo.features": [
"devtools",
"log"
"log",
"use_camera"
]
}
2 changes: 1 addition & 1 deletion crates/hooks/src/use_accessibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ mod test {

let mut utils = launch_test_with_config(
use_focus_app,
TestingConfig::default().with_size((100.0, 100.0).into()),
*TestingConfig::default().with_size((100.0, 100.0).into()),
);

// Initial state
Expand Down
47 changes: 33 additions & 14 deletions crates/hooks/src/use_animation.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
use dioxus_core::ScopeState;
use dioxus_hooks::{use_state, UseState};
use std::time::Duration;
use tokio::time::interval;
use tokio::time::Instant;
use uuid::Uuid;

use crate::Animation;

const ANIMATION_MS: i32 = 16; // Assume 60 FPS for now
use crate::{use_platform, Animation, UsePlatform};

/// Manage the lifecyle of an [Animation].
#[derive(Clone)]
Expand All @@ -15,14 +12,16 @@ pub struct AnimationManager<'a> {
current_animation_id: &'a UseState<Option<Uuid>>,
value: &'a UseState<f64>,
cx: &'a ScopeState,
platform: UsePlatform,
}

impl<'a> AnimationManager<'a> {
/// Start the given [Animation].
pub fn start(&self, mut anim: Animation) {
let new_id = Uuid::new_v4();
let mut index = 0;

let platform = self.platform.clone();
let mut ticker = platform.new_ticker();
let value = self.value.clone();
let current_animation_id = self.current_animation_id.clone();

Expand All @@ -31,8 +30,16 @@ impl<'a> AnimationManager<'a> {

// Spawn the animation that will run at 1ms speed
self.cx.spawn(async move {
let mut ticker = interval(Duration::from_millis(ANIMATION_MS as u64));
platform.request_animation_frame();

let mut index = 0;
let mut prev_frame = Instant::now();

loop {
// Wait for the event loop to tick
ticker.tick().await;
platform.request_animation_frame();

// Stop running the animation if it was removed
if *current_animation_id.current() == Some(new_id) {
// Remove the current animation if it has finished
Expand All @@ -41,12 +48,10 @@ impl<'a> AnimationManager<'a> {
break;
}

// Advance one tick
index += prev_frame.elapsed().as_millis() as i32;
value.set(anim.move_value(index));
index += ANIMATION_MS;

// Wait 1m
ticker.tick().await;
prev_frame = Instant::now();
} else {
break;
}
Expand Down Expand Up @@ -102,12 +107,14 @@ pub fn use_animation(cx: &ScopeState, init_value: impl FnOnce() -> f64) -> Anima
let current_animation_id = use_state(cx, || None);
let init_value = *cx.use_hook(init_value);
let value = use_state(cx, || init_value);
let platform = use_platform(cx);

AnimationManager {
current_animation_id,
value,
cx,
init_value,
platform,
}
}

Expand Down Expand Up @@ -139,20 +146,24 @@ mod test {

let mut utils = launch_test(use_animation_app);

// Disable event loop ticker
utils.config().enable_ticker(false);

// Initial state
utils.wait_for_update().await;

assert_eq!(utils.root().get(0).layout().unwrap().width(), 0.0);

// State somewhere in the middle
utils.wait_for_update().await;
sleep(Duration::from_millis(32)).await;
utils.wait_for_update().await;

let width = utils.root().get(0).layout().unwrap().width();
assert!(width > 0.0);
assert!(width < 100.0);

sleep(Duration::from_millis(50)).await;
// Enable event loop ticker
utils.config().enable_ticker(true);

// State in the end
utils.wait_for_update().await;
Expand Down Expand Up @@ -189,26 +200,34 @@ mod test {

let mut utils = launch_test(use_animation_app);

// Disable event loop ticker
utils.config().enable_ticker(false);

// Initial state
utils.wait_for_update().await;

assert_eq!(utils.root().get(0).layout().unwrap().width(), 10.0);

// State somewhere in the middle
utils.wait_for_update().await;
sleep(Duration::from_millis(32)).await;
utils.wait_for_update().await;

let width = utils.root().get(0).layout().unwrap().width();
assert!(width > 10.0);

// Trigger the click event to restart the animation
utils.push_event(FreyaEvent::Mouse {
name: "click".to_string(),
cursor: (5.0, 5.0).into(),
button: Some(MouseButton::Left),
});

// Enable event loop ticker
utils.config().enable_ticker(true);

// State has been restarted
utils.wait_for_update().await;
utils.wait_for_update().await;

let width = utils.root().get(0).layout().unwrap().width();
assert_eq!(width, 10.0);
Expand Down
37 changes: 24 additions & 13 deletions crates/hooks/src/use_animation_transition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@ use dioxus_core::ScopeState;
use dioxus_hooks::{use_memo, use_state, UseFutureDep, UseState};
use freya_engine::prelude::Color;
use freya_node_state::Parse;
use std::time::Duration;
use tokio::time::interval;
use tokio::time::Instant;
use uuid::Uuid;

use crate::{Animation, TransitionAnimation};

const ANIMATION_MS: i32 = 16; // Assume 60 FPS for now
use crate::{use_platform, Animation, TransitionAnimation, UsePlatform};

/// Configure a `Transition` animation.
#[derive(Clone, Debug, Copy, PartialEq)]
Expand Down Expand Up @@ -145,6 +142,8 @@ pub struct TransitionsManager<'a> {
current_animation_id: &'a UseState<Option<Uuid>>,
/// The scope.
cx: &'a ScopeState,
/// Platform APIs
platform: UsePlatform,
}

impl<'a> TransitionsManager<'a> {
Expand All @@ -165,6 +164,8 @@ impl<'a> TransitionsManager<'a> {
fn run_with_animation(&self, mut animation: Animation) {
let animation_id = Uuid::new_v4();

let platform = self.platform.clone();
let mut ticker = platform.new_ticker();
let transitions = self.transitions.clone();
let transitions_storage = self.transitions_storage.clone();
let current_animation_id = self.current_animation_id.clone();
Expand All @@ -174,9 +175,16 @@ impl<'a> TransitionsManager<'a> {

// Spawn the animation that will run at 1ms speed
self.cx.spawn(async move {
let mut ticker = interval(Duration::from_millis(ANIMATION_MS as u64));
platform.request_animation_frame();

let mut index = 0;
let mut prev_frame = Instant::now();

loop {
// Wait for the event loop to tick
ticker.tick().await;
platform.request_animation_frame();

// Stop running the animation if it's no longer selected
if *current_animation_id.current() == Some(animation_id) {
// Remove the current animation if it has finished
Expand All @@ -185,7 +193,7 @@ impl<'a> TransitionsManager<'a> {
break;
}

// Advance one tick
index += prev_frame.elapsed().as_millis() as i32;
let value = animation.move_value(index);
transitions_storage.with_mut(|storage| {
for (i, storage) in storage.iter_mut().enumerate() {
Expand All @@ -195,10 +203,7 @@ impl<'a> TransitionsManager<'a> {
}
});

index += ANIMATION_MS;

// Wait 1ms
ticker.tick().await;
prev_frame = Instant::now();
} else {
break;
}
Expand Down Expand Up @@ -278,6 +283,7 @@ where
let current_animation_id = use_state(cx, || None);
let transitions = use_memo(cx, dependencies.clone(), &mut init);
let transitions_storage = use_state(cx, || animations_map(transitions));
let platform = use_platform(cx);

use_memo(cx, dependencies, {
let storage_setter = transitions_storage.setter();
Expand All @@ -292,6 +298,7 @@ where
transitions_storage,
cx,
transition_animation: transition,
platform,
}
}

Expand Down Expand Up @@ -332,20 +339,24 @@ mod test {

let mut utils = launch_test(use_animation_transition_app);

// Disable event loop ticker
utils.config().enable_ticker(false);

// Initial state
utils.wait_for_update().await;

assert_eq!(utils.root().get(0).layout().unwrap().width(), 0.0);

// State somewhere in the middle
utils.wait_for_update().await;
sleep(Duration::from_millis(32)).await;
utils.wait_for_update().await;

let width = utils.root().get(0).layout().unwrap().width();
assert!(width > 0.0);
assert!(width < 100.0);

sleep(Duration::from_millis(50)).await;
// Enable event loop ticker
utils.config().enable_ticker(true);

// State in the end
utils.wait_for_update().await;
Expand Down
27 changes: 7 additions & 20 deletions crates/hooks/src/use_camera.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,20 @@
use std::{
sync::{Arc, Mutex},
time::Duration,
};
use std::sync::{Arc, Mutex};

use crate::use_platform;
use dioxus_core::{AttributeValue, ScopeState};
use dioxus_hooks::{to_owned, use_effect, use_state, UseState};
use freya_common::EventMessage;
use freya_node_state::{CustomAttributeValues, ImageReference};
use nokhwa::{pixel_format::RgbFormat, utils::RequestedFormat, Camera, NokhwaError};
use tokio::time::sleep;

pub use nokhwa::utils::{CameraIndex, RequestedFormatType, Resolution};
use nokhwa::{pixel_format::RgbFormat, utils::RequestedFormat, Camera, NokhwaError};

/// Configuration for a camera
pub struct CameraSettings {
frame_rate: u32,
camera_index: CameraIndex,
resolution: Option<Resolution>,
camera_format: RequestedFormatType,
}

impl CameraSettings {
/// Specify a frame rate
pub fn with_frame_rate(mut self, frame_rate: u32) -> Self {
self.frame_rate = frame_rate;
self
}

/// Specify a camera index
pub fn with_camera_index(mut self, camera_index: CameraIndex) -> Self {
self.camera_index = camera_index;
Expand All @@ -50,7 +37,6 @@ impl CameraSettings {
impl Default for CameraSettings {
fn default() -> Self {
Self {
frame_rate: 30,
camera_index: CameraIndex::Index(0),
resolution: None,
camera_format: RequestedFormatType::AbsoluteHighestFrameRate,
Expand Down Expand Up @@ -89,11 +75,11 @@ pub fn use_camera(
.unwrap_or_else(handle_error);
}

let frame_rate = camera_settings.frame_rate;
let fps = 1000 / frame_rate;
let mut ticker = platform.new_ticker();

loop {
sleep(Duration::from_millis(fps as u64)).await;
// Wait for the event loop to tick
ticker.tick().await;

// Capture the next frame
let frame = camera.frame();
Expand All @@ -104,9 +90,10 @@ pub fn use_camera(
image_reference.lock().unwrap().replace(bts);

// Request the renderer to rerender
platform.send(EventMessage::RequestRerender).unwrap();
platform.request_animation_frame();
} else if let Err(err) = frame {
handle_error(err);
break;
}
}
} else if let Err(err) = camera {
Expand Down
2 changes: 1 addition & 1 deletion crates/hooks/src/use_focus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ mod test {

let mut utils = launch_test_with_config(
use_focus_app,
TestingConfig::default().with_size((100.0, 100.0).into()),
*TestingConfig::default().with_size((100.0, 100.0).into()),
);

// Initial state
Expand Down
4 changes: 2 additions & 2 deletions crates/hooks/src/use_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,14 @@ mod test {

let mut utils = launch_test_with_config(
use_node_app,
TestingConfig::default().with_size((500.0, 800.0).into()),
*TestingConfig::default().with_size((500.0, 800.0).into()),
);

utils.wait_for_update().await;
let root = utils.root().get(0);
assert_eq!(root.get(0).text().unwrap().parse::<f32>(), Ok(500.0 * 0.5));

utils.set_config(TestingConfig::default().with_size((300.0, 800.0).into()));
utils.config().with_size((300.0, 800.0).into());
utils.wait_for_update().await;

let root = utils.root().get(0);
Expand Down
Loading

0 comments on commit e63ad8d

Please sign in to comment.