From aeb55e0b94b34307816d10a381f026828d957c19 Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Mon, 27 Nov 2023 09:27:37 -0800 Subject: [PATCH] run_in_bg and Dynamic<&'static str> --- src/animation.rs | 26 +++++++++++++++++++++----- src/utils.rs | 28 ++++++++++++++++++++++++++++ src/value.rs | 42 +++++++++++++++++++++++++++++++----------- src/widgets/input.rs | 12 ++++++++++++ 4 files changed, 92 insertions(+), 16 deletions(-) diff --git a/src/animation.rs b/src/animation.rs index f3ee8f0..5643913 100644 --- a/src/animation.rs +++ b/src/animation.rs @@ -58,7 +58,7 @@ use kludgine::Color; use crate::animation::easings::Linear; use crate::styles::{Component, RequireInvalidation}; -use crate::utils::{IgnorePoison, UnwindsafeCondvar}; +use crate::utils::{run_in_bg, IgnorePoison, UnwindsafeCondvar}; use crate::value::Dynamic; static ANIMATIONS: Mutex = Mutex::new(Animating::new()); @@ -488,6 +488,20 @@ impl AnimationHandle { thread_state().run_unattached(id); } } + + /// Returns true if this animation is still running. + #[must_use] + pub fn is_running(&self) -> bool { + let Some(id) = self.0 else { return false }; + + thread_state().running.contains(&id) + } + + /// Returns true if this animation is complete. + #[must_use] + pub fn is_complete(&self) -> bool { + !self.is_running() + } } impl Drop for AnimationHandle { @@ -645,7 +659,7 @@ where /// will be invoked again. pub struct OnCompleteAnimation { animation: A, - callback: Box, + callback: Option>, completed: bool, } @@ -654,11 +668,11 @@ impl OnCompleteAnimation { /// `on_complete`. pub fn new(animation: A, on_complete: F) -> Self where - F: FnMut() + Send + Sync + 'static, + F: FnOnce() + Send + Sync + 'static, { Self { animation, - callback: Box::new(on_complete), + callback: Some(Box::new(on_complete)), completed: false, } } @@ -690,7 +704,9 @@ where match self.animation.animate(elapsed) { ControlFlow::Break(remaining) => { self.completed = true; - (self.callback)(); + if let Some(callback) = self.callback.take() { + run_in_bg(callback); + } ControlFlow::Break(remaining) } ControlFlow::Continue(()) => ControlFlow::Continue(()), diff --git a/src/utils.rs b/src/utils.rs index 11ffd39..646f590 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,8 @@ use std::ops::Deref; +use std::sync::mpsc::{self, SyncSender}; use std::sync::{Condvar, OnceLock, PoisonError}; +use intentional::Assert; use kludgine::app::winit::event::Modifiers; use kludgine::app::winit::keyboard::ModifiersState; @@ -173,3 +175,29 @@ impl IgnorePoison for Result> { self.map_or_else(PoisonError::into_inner, |g| g) } } + +pub trait BgFunction: FnOnce() + Send + 'static {} + +pub fn run_in_bg(f: F) +where + F: BgFunction, +{ + static BG_THREAD: Lazy>> = Lazy::new(|| { + let (sender, receiver) = mpsc::sync_channel::>(16); + std::thread::Builder::new() + .name(String::from("background")) + .spawn(move || { + while let Ok(callback) = receiver.recv() { + (callback)(); + } + }) + .assert("error spawning bg thread"); + sender + }); + + BG_THREAD + .send(Box::new(f)) + .assert("background thread not running"); +} + +impl BgFunction for T where T: FnOnce() + Send + 'static {} diff --git a/src/value.rs b/src/value.rs index 6e483be..f8d98e4 100644 --- a/src/value.rs +++ b/src/value.rs @@ -9,14 +9,13 @@ use std::sync::atomic::{self, AtomicBool}; use std::sync::{Arc, Mutex, MutexGuard, TryLockError}; use std::task::{Poll, Waker}; use std::thread::ThreadId; -use std::time::Duration; use ahash::AHashSet; -use crate::animation::{DynamicTransition, IntoAnimate, LinearInterpolate, Spawn}; +use crate::animation::{DynamicTransition, LinearInterpolate}; use crate::context::sealed::WindowHandle; use crate::context::{self, WidgetContext}; -use crate::utils::{IgnorePoison, UnwindsafeCondvar, WithClone}; +use crate::utils::{run_in_bg, IgnorePoison, UnwindsafeCondvar, WithClone}; use crate::widget::{MakeWidget, WidgetId, WidgetInstance}; use crate::widgets::{Radio, Switcher}; @@ -572,6 +571,12 @@ impl context::sealed::Trackable for Dynamic { } } +impl PartialEq for Dynamic { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.0, &other.0) + } +} + impl Default for Dynamic where T: Default, @@ -939,9 +944,7 @@ impl Drop for DynamicGuard<'_, T> { fn drop(&mut self) { if self.accessed_mut { let mut callbacks = Some(self.guard.note_changed()); - Duration::ZERO - .on_complete(move || drop(callbacks.take())) - .launch(); + run_in_bg(move || drop(callbacks.take())); } } } @@ -1420,6 +1423,12 @@ impl<'a> IntoValue for &'a str { } } +impl IntoValue for Dynamic<&'static str> { + fn into_value(self) -> Value { + self.map_each(ToString::to_string).into_value() + } +} + impl IntoValue for Dynamic { fn into_value(self) -> Value { Value::Dynamic(self) @@ -1760,6 +1769,18 @@ impl Validation { pub const fn is_error(&self) -> bool { matches!(self, Self::Invalid(_)) } + + /// Returns the result of merging both validations. + #[must_use] + pub fn and(&self, other: &Self) -> Self { + match (self, other) { + (Validation::Valid, Validation::Valid) => Validation::Valid, + (Validation::Invalid(error), _) | (_, Validation::Invalid(error)) => { + Validation::Invalid(error.clone()) + } + (Validation::None, _) | (_, Validation::None) => Validation::None, + } + } } /// A grouping of validations that can be checked simultaneously. @@ -1864,11 +1885,10 @@ impl Validations { ValidationsState::Resetting => { initial_generation = dynamic.generation(); let state = state.clone(); - Duration::ZERO - .on_complete(move || { - state.set(ValidationsState::Initial); - }) - .launch(); + + run_in_bg(move || { + state.set(ValidationsState::Initial); + }); Validation::None } ValidationsState::Initial if initial_generation == dynamic.generation() => { diff --git a/src/widgets/input.rs b/src/widgets/input.rs index e59370e..d901f65 100644 --- a/src/widgets/input.rs +++ b/src/widgets/input.rs @@ -1424,6 +1424,18 @@ macro_rules! impl_cow_string { } } + impl IntoValue<$type> for Dynamic { + fn into_value(self) -> Value<$type> { + Value::Dynamic(self.map_each_to()) + } + } + + impl IntoValue<$type> for Dynamic<&'static str> { + fn into_value(self) -> Value<$type> { + Value::Dynamic(self.map_each(|s| <$type>::from(*s))) + } + } + impl<'a> From<&'a String> for $type { fn from(s: &'a String) -> Self { Self::new(s.as_str())