Refactored task module into own crate.

This commit is contained in:
Samuel Guerra 2023-12-02 11:56:03 -03:00
parent 5de8e3097f
commit 8faceea6cf
29 changed files with 440 additions and 156 deletions

View File

@ -8,6 +8,7 @@ members = [
"zero-ui-clone_move",
"zero-ui-proc-macros",
"zero-ui-var-proc-macros",
"zero-ui-task-proc-macros",
# types:
"zero-ui-handle",
@ -17,6 +18,7 @@ members = [
"zero-ui-app_context",
"zero-ui-var",
"zero-ui-layout",
"zero-ui-task",
# view:
"zero-ui-view-api",

View File

@ -31,9 +31,11 @@ TextInput! {
# Split Core
- Tasks.
- Mostly decoupled, need app_context.
- UiTask needs WidgetId, can be decoupled.
* Color.
- Filter needs layout::Length.
- Needs impl_from_and_into_var.
- Into view_api::RenderColor.
- Can be decoupled.
* App API.
- Needs UpdateDeliveryList, that needs WidgetInfo.
@ -54,12 +56,6 @@ TextInput! {
- Does not provide App::default()?
- Could be on a feature flag.
* Color.
- Filter needs layout::Length.
- Needs impl_from_and_into_var.
- Into view_api::RenderColor.
- Can be decoupled.
* Config.
- Needs app API.
- Needs var.

View File

@ -16,7 +16,7 @@ ipc = ["zero-ui-view-api/ipc"]
# Enables http tasks.
#
# Enabled by default.
http = ["isahc", "http-cache-semantics", "http-serde"]
http = ["zero-ui-task/http"]
# Signal the build script to enable the `dyn_*`, `inspector` and `trace_widget` features for debug builds.
#
@ -97,6 +97,7 @@ zero-ui-handle = { path = "../zero-ui-handle" }
zero-ui-var = { path = "../zero-ui-var", default-features = false }
zero-ui-layout = { path = "../zero-ui-layout" }
zero-ui-state_map = { path = "../zero-ui-state_map" }
zero-ui-task = { path = "../zero-ui-task" }
# text
font-kit = "0.12"

View File

@ -24,6 +24,7 @@ use crate::{
event::{Event, EventArgs, EventHandle, EventHandles, EventUpdate, EVENTS, EVENTS_SV},
handler::{AppHandler, AppHandlerArgs, AppWeakHandle},
render::ReuseRange,
task::ui::UiTask,
text::Txt,
timer::TIMERS_SV,
var::{AnyVar, AnyVarSubscribe, Var, VarHandle, VarHandles, VarSubscribe, VarValue, VARS},
@ -2355,3 +2356,26 @@ pub(crate) fn into_harf_direction(d: LayoutDirection) -> harfbuzz_rs::Direction
LayoutDirection::RTL => harfbuzz_rs::Direction::Rtl,
}
}
/// Integrate [`UiTask`] with widget updates.
pub trait UiTaskWidget<R> {
/// Create a UI bound future executor.
///
/// The `task` is inert and must be polled using [`update`] to start, and it must be polled every
/// [`UiNode::update`] after that, in widgets the `target` can be set so that the update requests are received.
///
/// [`update`]: UiTask::update
/// [`UiNode::update`]: crate::widget_instance::UiNode::update
/// [`UiNode::info`]: crate::widget_instance::UiNode::info
fn new<F>(target: Option<WidgetId>, task: F) -> Self
where
F: std::future::Future<Output = R> + Send + 'static;
}
impl<R> UiTaskWidget<R> for UiTask<R> {
fn new<F>(target: Option<WidgetId>, task: F) -> Self
where
F: std::future::Future<Output = R> + Send + 'static,
{
UiTask::new_raw(UPDATES.waker(target), task)
}
}

View File

@ -51,23 +51,9 @@ impl<F: FnOnce()> Drop for RunOnDrop<F> {
}
}
/// Converts a [`std::panic::catch_unwind`] payload to a str.
pub fn panic_str<'s>(payload: &'s Box<dyn std::any::Any + Send + 'static>) -> &'s str {
if let Some(s) = payload.downcast_ref::<&str>() {
s
} else if let Some(s) = payload.downcast_ref::<String>() {
s
} else {
"<unknown-panic-message-type>"
}
}
/// Type alias for the *error* of [`PanicResult`].
pub type PanicPayload = Box<dyn std::any::Any + Send + 'static>;
/// The result that is returned by [`std::panic::catch_unwind`].
pub type PanicResult<R> = thread::Result<R>;
// this is the FxHasher with random const init.
#[derive(Clone)]
pub struct BuildFxHasher(usize);
@ -304,7 +290,6 @@ pub fn test_log() {
}
/// Calls [`fs4::FileExt::unlock`] and ignores "already unlocked" errors.
#[allow(unused)] // http only
pub fn unlock_ok(file: &impl fs4::FileExt) -> std::io::Result<()> {
if let Err(e) = file.unlock() {
if let Some(code) = e.raw_os_error() {

View File

@ -17,7 +17,7 @@ use std::time::{Duration, Instant};
use std::{mem, thread};
use crate::app::HeadlessApp;
use crate::context::{UPDATES, WIDGET};
use crate::context::{UiTaskWidget, UPDATES, WIDGET};
use crate::crate_util::{Handle, WeakHandle};
use crate::task;
use crate::task::ui::UiTask;

View File

@ -22,6 +22,7 @@ use crate::{
view_process::{ImageRequest, ViewImage, ViewProcessOffline, VIEW_PROCESS, VIEW_PROCESS_INITED_EVENT},
AppExtension,
},
context::UiTaskWidget,
crate_util::IdMap,
event::EventUpdate,
task::{self, fs, io::*, ui::UiTask},

View File

@ -35,6 +35,9 @@ pub use paste::paste;
#[doc(inline)]
pub use zero_ui_layout::units;
#[doc(inline)]
pub use zero_ui_task as task;
#[macro_use]
pub mod handler;
@ -57,7 +60,6 @@ pub mod l10n;
pub mod mouse;
pub mod pointer_capture;
pub mod render;
pub mod task;
pub mod text;
pub mod timer;
pub mod var;

View File

@ -18,7 +18,7 @@ use crate::app::{
use crate::app::{APP_PROCESS, EXIT_REQUESTED_EVENT};
use crate::color::COLOR_SCHEME_VAR;
use crate::context::{RenderUpdates, UpdateOp, WidgetUpdates, WindowCtx};
use crate::context::{UPDATES, WINDOW};
use crate::context::{UiTaskWidget, UPDATES, WINDOW};
use crate::crate_util::{IdMap, IdSet};
use crate::event::{AnyEventArgs, EventUpdate};
use crate::image::ImageMaskMode;

View File

@ -11,9 +11,6 @@ pub use angle::*;
mod constraints;
pub use constraints::*;
mod byte;
pub use byte::*;
mod factor;
pub use factor::*;

View File

@ -22,8 +22,6 @@ mod wgt_property_attrs;
mod widget;
mod widget_util;
mod any_all;
mod l10n;
mod lang;
@ -101,12 +99,6 @@ pub fn widget_new(input: TokenStream) -> TokenStream {
widget::expand_new(input)
}
#[doc(hidden)]
#[proc_macro]
pub fn task_any_all(input: TokenStream) -> TokenStream {
any_all::expand(input)
}
#[doc(hidden)]
#[proc_macro]
pub fn trace(input: TokenStream) -> TokenStream {

View File

@ -0,0 +1,14 @@
[package]
name = "zero-ui-task-proc-macros"
version = "0.1.0"
authors = ["Samuel Guerra <sam.rodr.g@gmail.com>", "Well <well-r@hotmail.com>"]
edition = "2021"
license = "Apache-2.0"
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1"
quote = "1"
syn = "2"

View File

@ -0,0 +1,15 @@
use proc_macro::TokenStream;
#[macro_use]
extern crate quote;
#[macro_use]
mod util;
mod any_all;
#[doc(hidden)]
#[proc_macro]
pub fn task_any_all(input: TokenStream) -> TokenStream {
any_all::expand(input)
}

View File

@ -0,0 +1,13 @@
/// `Ident` with custom span.
macro_rules! ident_spanned {
($span:expr=> $($format_name:tt)+) => {
proc_macro2::Ident::new(&format!($($format_name)+), $span)
};
}
/// `Ident` with call_site span.
macro_rules! ident {
($($tt:tt)*) => {
ident_spanned!(proc_macro2::Span::call_site()=> $($tt)*)
};
}

56
zero-ui-task/Cargo.toml Normal file
View File

@ -0,0 +1,56 @@
[package]
name = "zero-ui-task"
version = "0.1.0"
authors = ["Samuel Guerra <sam.rodr.g@gmail.com>", "Well <well-r@hotmail.com>"]
edition = "2021"
license = "Apache-2.0"
[features]
# Enables http tasks.
http = [
"serde",
"serde_json",
"isahc",
"http-cache-semantics",
"http-serde",
"once_cell",
"zero-ui-txt",
"async-recursion",
"async-trait",
"sha2",
"base64",
"fs4",
"remove_dir_all",
]
[dependencies]
zero-ui-task-proc-macros = { path = "../zero-ui-task-proc-macros" }
zero-ui-clone_move = { path = "../zero-ui-clone_move" }
zero-ui-units = { path = "../zero-ui-units" }
zero-ui-app_context = { path = "../zero-ui-app_context" }
zero-ui-var = { path = "../zero-ui-var" }
zero-ui-txt = { path = "../zero-ui-txt", optional = true }
tracing = "0.1"
pretty-type-name = "1"
flume = { version = "0.11", default-features = false, features = ["async"] }
rayon = "1"
blocking = "1"
parking_lot = "0.12"
futures-timer = "3"
isahc = { version = "1", features = ["cookies", "json"], optional = true }
futures-lite = "2"
async-fs = "2"
serde = { version = "1", optional = true }
serde_json = { version = "1", optional = true }
http-cache-semantics = { version = "1", optional = true } # isahc needs this version
http-serde = { version = "1", optional = true }
once_cell = { version = "1", optional = true }
async-recursion = { version = "1", optional = true }
async-trait = { version = "0.1", optional = true }
sha2 = { version = "0.10", optional = true }
base64 = { version = "0.21", optional = true }
fs4 = { version = "0.7", optional = true }
remove_dir_all = { version = "0.8", optional = true }

View File

@ -32,7 +32,7 @@ use std::{convert::TryFrom, fmt};
pub use flume::{RecvError, RecvTimeoutError, SendError, SendTimeoutError};
use crate::units::Deadline;
use zero_ui_units::Deadline;
/// The transmitting end of an unbounded channel.
///
@ -289,7 +289,7 @@ impl<T> Receiver<T> {
///
/// [`send`]: UnboundSender::send
/// [received]: Receiver::recv
/// [spawns]: crate::task::spawn
/// [spawns]: crate::spawn
pub fn unbounded<T>() -> (UnboundSender<T>, Receiver<T>) {
let (s, r) = flume::unbounded();
(UnboundSender(s), Receiver(r))
@ -336,7 +336,7 @@ pub fn unbounded<T>() -> (UnboundSender<T>, Receiver<T>) {
///
/// [`send`]: UnboundSender::send
/// [received]: Receiver::recv
/// [spawns]: crate::task::spawn
/// [spawns]: crate::spawn
pub fn bounded<T>(capacity: usize) -> (Sender<T>, Receiver<T>) {
let (s, r) = flume::bounded(capacity);
(Sender(s), Receiver(r))
@ -386,7 +386,7 @@ pub fn bounded<T>(capacity: usize) -> (Sender<T>, Receiver<T>) {
///
/// [`send`]: UnboundSender::send
/// [received]: Receiver::recv
/// [spawns]: crate::task::spawn
/// [spawns]: crate::spawn
pub fn rendezvous<T>() -> (Sender<T>, Receiver<T>) {
bounded::<T>(0)
}

View File

@ -0,0 +1,13 @@
/// Converts a [`std::panic::catch_unwind`] payload to a str.
pub fn panic_str<'s>(payload: &'s Box<dyn std::any::Any + Send + 'static>) -> &'s str {
if let Some(s) = payload.downcast_ref::<&str>() {
s
} else if let Some(s) = payload.downcast_ref::<String>() {
s
} else {
"<unknown-panic-message-type>"
}
}
/// The result that is returned by [`std::panic::catch_unwind`].
pub type PanicResult<R> = std::thread::Result<R>;

View File

@ -1,4 +1,6 @@
#![cfg(http)]
#![cfg(feature = "http")]
// suppress nag about very simple boxed closure signatures.
#![allow(clippy::type_complexity)]
//! HTTP client.
//!
@ -20,6 +22,7 @@
//! [`isahc`]: https://docs.rs/isahc
mod cache;
mod util;
pub use cache::*;
@ -41,8 +44,8 @@ use futures_lite::io::{AsyncReadExt, BufReader};
use isahc::{AsyncReadResponseExt, ResponseExt};
use parking_lot::{const_mutex, Mutex};
use crate::text::Txt;
use crate::units::*;
use zero_ui_txt::Txt;
use zero_ui_units::*;
/// Marker trait for types that try-to-convert to [`Uri`].
///

View File

@ -4,9 +4,9 @@ use std::{
};
use super::{Body, Error};
use crate::units::*;
use async_trait::async_trait;
use serde::*;
use zero_ui_units::*;
use http_cache_semantics as hcs;
@ -139,7 +139,7 @@ impl From<hcs::AfterResponse> for AfterResponse {
///
/// Cache implementers must store a [`CachePolicy`] and [`Body`] for a given [`CacheKey`].
///
/// [`Client`]: crate::task::http::Client
/// [`Client`]: crate::http::Client
#[async_trait]
pub trait CacheDb: Send + Sync + 'static {
/// Dynamic clone.
@ -181,9 +181,9 @@ pub trait CacheDb: Send + Sync + 'static {
///
/// See [`ClientBuilder::cache_mode`] for more information.
///
/// [`Uri`]: crate::task::http::Uri
/// [`Uri`]: crate::http::Uri
///
/// [`ClientBuilder::cache_mode`]: crate::task::http::ClientBuilder::cache_mode
/// [`ClientBuilder::cache_mode`]: crate::http::ClientBuilder::cache_mode
#[derive(Debug, Clone, Default)]
pub enum CacheMode {
/// Always requests the server, never caches the response.
@ -260,16 +260,14 @@ mod file_cache {
path::{Path, PathBuf},
};
use crate::http::util::{lock_exclusive, lock_shared, unlock_ok};
use crate::{
crate_util::{lock_exclusive, lock_shared, unlock_ok},
task::{
self,
io::{McBufErrorExt, McBufReader},
},
units::TimeUnits,
self as task,
io::{McBufErrorExt, McBufReader},
};
use async_trait::async_trait;
use fs4::FileExt;
use zero_ui_units::TimeUnits;
use super::*;
@ -289,8 +287,8 @@ mod file_cache {
/// The cache does not pull data, only data read by the returned body is written to the cache, dropping the body without reading
/// to end cancels the cache entry.
///
/// [`Client`]: crate::task::http::Client
/// [`set`]: crate::task::http::CacheDb::set
/// [`Client`]: crate::http::Client
/// [`set`]: crate::http::CacheDb::set
#[derive(Clone)]
pub struct FileSystemCache {
dir: PathBuf,
@ -653,13 +651,10 @@ mod tests {
use zero_ui_clone_move::async_clmv;
use crate::{
crate_util::{test_log, TestTempDir},
task::{
self,
http::{header::*, *},
},
units::*,
self as task,
http::{header::*, util::*, *},
};
use zero_ui_units::*;
#[test]
pub fn file_cache_miss() {

View File

@ -0,0 +1,196 @@
use std::time::Duration;
/// Calls [`fs4::FileExt::lock_exclusive`] with a timeout.
pub fn lock_exclusive(file: &impl fs4::FileExt, timeout: Duration) -> std::io::Result<()> {
lock_timeout(file, |f| f.try_lock_exclusive(), timeout)
}
/// Calls [`fs4::FileExt::lock_shared`] with a timeout.
pub fn lock_shared(file: &impl fs4::FileExt, timeout: Duration) -> std::io::Result<()> {
lock_timeout(file, |f| f.try_lock_shared(), timeout)
}
fn lock_timeout<F: fs4::FileExt>(file: &F, try_lock: impl Fn(&F) -> std::io::Result<()>, mut timeout: Duration) -> std::io::Result<()> {
let mut locked_error = None;
loop {
match try_lock(file) {
Ok(()) => return Ok(()),
Err(e) => {
if e.raw_os_error() != locked_error.get_or_insert_with(fs4::lock_contended_error).raw_os_error() {
return Err(e);
}
const INTERVAL: Duration = Duration::from_millis(10);
timeout = timeout.saturating_sub(INTERVAL);
if timeout.is_zero() {
return Err(e);
} else {
std::thread::sleep(INTERVAL.min(timeout));
}
}
}
}
}
/// Calls [`fs4::FileExt::unlock`] and ignores "already unlocked" errors.
pub fn unlock_ok(file: &impl fs4::FileExt) -> std::io::Result<()> {
if let Err(e) = file.unlock() {
if let Some(code) = e.raw_os_error() {
#[cfg(windows)]
if code == 158 {
// ERROR_NOT_LOCKED
return Ok(());
}
#[cfg(unix)]
if code == 22 {
// EINVAL
return Ok(());
}
}
Err(e)
} else {
Ok(())
}
}
/// Sets a `tracing` subscriber that writes warnings to stderr and panics on errors.
///
/// Panics if another different subscriber is already set.
#[cfg(test)]
pub fn test_log() {
use std::sync::atomic::*;
use std::fmt;
use tracing::*;
struct TestSubscriber;
impl Subscriber for TestSubscriber {
fn enabled(&self, metadata: &Metadata<'_>) -> bool {
metadata.is_event() && metadata.level() < &Level::WARN
}
fn new_span(&self, _span: &span::Attributes<'_>) -> span::Id {
unimplemented!()
}
fn record(&self, _span: &span::Id, _values: &span::Record<'_>) {
unimplemented!()
}
fn record_follows_from(&self, _span: &span::Id, _follows: &span::Id) {
unimplemented!()
}
fn event(&self, event: &Event<'_>) {
struct MsgCollector<'a>(&'a mut String);
impl<'a> field::Visit for MsgCollector<'a> {
fn record_debug(&mut self, field: &field::Field, value: &dyn fmt::Debug) {
use std::fmt::Write;
write!(self.0, "\n {} = {:?}", field.name(), value).unwrap();
}
}
let meta = event.metadata();
let file = meta.file().unwrap_or("");
let line = meta.line().unwrap_or(0);
let mut msg = format!("[{file}:{line}]");
event.record(&mut MsgCollector(&mut msg));
if meta.level() == &Level::ERROR {
panic!("[LOG-ERROR]{msg}");
} else {
eprintln!("[LOG-WARN]{msg}");
}
}
fn enter(&self, _span: &span::Id) {
unimplemented!()
}
fn exit(&self, _span: &span::Id) {
unimplemented!()
}
}
static IS_SET: AtomicBool = AtomicBool::new(false);
if !IS_SET.swap(true, Ordering::Relaxed) {
if let Err(e) = subscriber::set_global_default(TestSubscriber) {
panic!("failed to set test log subscriber, {e:?}");
}
}
}
/// A temporary directory for unit tests.
///
/// Directory is "target/tmp/unit_tests/<name>" with fallback to system temporary if the target folder is not found.
///
/// Auto cleanup on drop.
#[cfg(test)]
pub struct TestTempDir {
path: Option<std::path::PathBuf>,
}
#[cfg(test)]
impl Drop for TestTempDir {
fn drop(&mut self) {
if let Some(path) = self.path.take() {
let _ = remove_dir_all::remove_dir_all(path);
}
}
}
#[cfg(test)]
impl TestTempDir {
/// Create temporary directory for the unique teste name.
pub fn new(name: &str) -> Self {
let path = Self::try_target().unwrap_or_else(Self::fallback).join(name);
std::fs::create_dir_all(&path).unwrap_or_else(|e| panic!("failed to create temp `{}`, {e:?}", path.display()));
TestTempDir { path: Some(path) }
}
fn try_target() -> Option<std::path::PathBuf> {
let p = std::env::current_exe().ok()?;
// target/debug/deps/../../..
let target = p.parent()?.parent()?.parent()?;
if target.file_name()?.to_str()? != "target" {
return None;
}
Some(target.join("tmp/unit_tests"))
}
fn fallback() -> std::path::PathBuf {
tracing::warn!("using fallback temporary directory");
std::env::temp_dir().join("zero_ui/unit_tests")
}
/// Dereferences the temporary directory path.
pub fn path(&self) -> &std::path::Path {
self.path.as_deref().unwrap()
}
/// Drop `self` without removing the temporary files.
///
/// Returns the path to the temporary directory.
pub fn keep(mut self) -> std::path::PathBuf {
self.path.take().unwrap()
}
}
#[cfg(test)]
impl std::ops::Deref for TestTempDir {
type Target = std::path::Path;
fn deref(&self) -> &Self::Target {
self.path()
}
}
#[cfg(test)]
impl std::convert::AsRef<std::path::Path> for TestTempDir {
fn as_ref(&self) -> &std::path::Path {
self.path()
}
}
#[cfg(test)]
impl<'a> From<&'a TestTempDir> for std::path::PathBuf {
fn from(a: &'a TestTempDir) -> Self {
a.path.as_ref().unwrap().clone()
}
}

View File

@ -8,11 +8,12 @@ use std::{
time::{Duration, Instant},
};
use crate::{task::McWaker, units::*};
use crate::McWaker;
#[doc(no_inline)]
pub use futures_lite::io::*;
use parking_lot::Mutex;
use zero_ui_units::{ByteLength, ByteUnits};
/// Measure read/write of an async task.
///
@ -567,7 +568,8 @@ enum ReadState {
#[cfg(test)]
mod tests {
use super::*;
use crate::task;
use crate as task;
use zero_ui_units::TimeUnits;
#[test]
pub fn mc_buf_reader_parallel() {

View File

@ -3,21 +3,16 @@
//! Use [`run`], [`respond`] or [`spawn`] to run parallel tasks, use [`wait`], [`io`] and [`fs`] to unblock
//! IO operations, use [`http`] for async HTTP, and use [`ui`] to create async properties.
//!
//! All functions of this module propagate the [`LocalContext`].
//! All functions of this crate propagate the [`LocalContext`].
//!
//! This module also re-exports the [`rayon`] and [`parking_lot`] crates for convenience. You can use the
//! This crate also re-exports the [`rayon`] and [`parking_lot`] crates for convenience. You can use the
//! [`ParallelIteratorExt::with_ctx`] adapter in rayon iterators to propagate the [`LocalContext`]. You can
//! also use [`join`] to propagate thread context for a raw rayon join operation.
//!
//! # Examples
//!
//! ```
//! # use zero_ui_core::{*, var::*, gesture::*, task::{self, rayon::prelude::*}, widget_instance::*};
//! # #[widget($crate::Button)] pub struct Button(widget_base::WidgetBase);
//! # event_property! { pub fn click { event: CLICK_EVENT, args: ClickArgs, } }
//! # #[property(CONTEXT)]
//! # fn enabled(child: impl UiNode, enabled: impl IntoVar<bool>) -> impl UiNode { child }
//! # fn main() {
//! # macro_rules! demo { () => {
//! let enabled = var(false);
//! Button! {
//! on_click = async_hn!(enabled, |_| {
@ -34,12 +29,13 @@
//! });
//! enabled;
//! }
//! # ; }
//!
//! async fn read_numbers() -> Vec<usize> {
//! let raw = task::wait(|| std::fs::read_to_string("numbers.txt").unwrap()).await;
//! raw.par_split(',').map(|s| s.trim().parse::<usize>().unwrap()).collect()
//! }
//!
//! # }}
//! ```
//!
//! The example demonstrates three different ***tasks***, the first is a [`ui::UiTask`] in the `async_hn` handler,
@ -87,13 +83,7 @@
//! implementing operations such as loading an image from a given URL, the module is a thin wrapper around the [`isahc`] crate.
//!
//! ```
//! # use zero_ui_core::{*, var::*, handler::*, text::*, gesture::*, widget_instance::*};
//! # #[widget($crate::Button)]
//! # pub struct Button(widget_base::WidgetBase);
//! # event_property! { pub fn click { event: CLICK_EVENT, args: ClickArgs, } }
//! # #[property(CONTEXT)]
//! # fn enabled(child: impl UiNode, enabled: impl IntoVar<bool>) -> impl UiNode { child }
//! # fn main() {
//! # macro_rules! demo { () => {
//! let enabled = var(false);
//! let msg = var("loading..".to_text());
//! Button! {
@ -108,7 +98,7 @@
//! enabled.set(true);
//! });
//! }
//! # ; }
//! # }}
//! ```
//!
//! For other protocols or alternative HTTP clients you can use [external crates](#async-crates-integration).
@ -157,12 +147,12 @@ use std::{
pub use parking_lot;
use parking_lot::Mutex;
use crate::{
context::LocalContext,
crate_util::{panic_str, PanicResult},
units::Deadline,
var::{response_done_var, response_var, ResponseVar, VarValue},
};
mod crate_util;
use crate::crate_util::PanicResult;
use zero_ui_app_context::LocalContext;
use zero_ui_units::Deadline;
use zero_ui_var::{response_done_var, response_var, ResponseVar, VarValue};
#[doc(no_inline)]
pub use rayon;
@ -170,14 +160,14 @@ pub use rayon;
#[doc(no_inline)]
pub use async_fs as fs;
pub use crate::handler::async_clmv;
pub use zero_ui_clone_move::async_clmv;
pub mod channel;
pub mod io;
pub mod ui;
pub mod http;
mod rayon_ctx;
pub mod ui;
pub use rayon_ctx::*;
@ -328,7 +318,7 @@ impl RayonTask {
}
}));
if let Err(p) = r {
tracing::error!("panic in `task::spawn`: {}", panic_str(&p));
tracing::error!("panic in `task::spawn`: {}", crate_util::panic_str(&p));
}
});
}
@ -658,7 +648,7 @@ where
{
enum QuickResponse<R: VarValue> {
Quick(Option<R>),
Response(crate::var::ResponderVar<R>),
Response(zero_ui_var::ResponderVar<R>),
}
let q = Arc::new(Mutex::new(QuickResponse::Quick(None)));
@ -769,7 +759,7 @@ where
{
spawn(async move {
if let Err(p) = wait_catch(task).await {
tracing::error!("parallel `spawn_wait` task panicked: {}", panic_str(&p))
tracing::error!("parallel `spawn_wait` task panicked: {}", crate_util::panic_str(&p))
}
});
}
@ -791,7 +781,7 @@ where
/// Blocks the thread until the `task` future finishes.
///
/// This function is useful for implementing async tests, using it in an app will probably cause
/// the app to stop responding. To test UI task use [`HeadlessApp::block_on`].
/// the app to stop responding.
///
/// The crate [`futures-lite`] is used to execute the task.
///
@ -818,7 +808,6 @@ where
/// # run_ok();
/// ```
///
/// [`HeadlessApp::block_on`]: crate::app::HeadlessApp::block_on
/// [`futures-lite`]: https://docs.rs/futures-lite/
pub fn block_on<F>(task: F) -> F::Output
where
@ -857,7 +846,7 @@ pub fn doc_test<F>(spin: bool, task: F) -> F::Output
where
F: Future,
{
use crate::units::TimeUnits;
use zero_ui_units::TimeUnits;
if spin {
spin_on(with_deadline(task, 500.ms())).expect("async doc-test timeout")
@ -943,12 +932,11 @@ pub async fn yield_now() {
///
/// # UI Async
///
/// This timer works in UI async tasks too, but you should use the [`TIMERS`] instead, as they are implemented using only
/// the app loop they use the same *executor* as the app or widget tasks.
/// This timer works in UI async tasks too, but in a full app prefer `TIMERS` instead, as it is implemented using only
/// the app loop it avoids spawning the [`futures_timer`] executor.
///
/// [`Pending`]: std::task::Poll::Pending
/// [`futures_timer`]: https://docs.rs/futures-timer
/// [`TIMERS`]: crate::timer::TIMERS#async
pub async fn deadline(deadline: impl Into<Deadline>) {
let deadline = deadline.into();
if let Some(timeout) = deadline.time_left() {
@ -1103,10 +1091,8 @@ macro_rules! all {
fut7: $fut7;
}
};
($($fut:expr),+ $(,)?) => { $crate::task::__proc_any_all!{ $crate::__all; $($fut),+ } }
($($fut:expr),+ $(,)?) => { $crate::__proc_any_all!{ $crate::__all; $($fut),+ } }
}
#[doc(inline)]
pub use crate::all;
#[doc(hidden)]
#[macro_export]
@ -1114,7 +1100,7 @@ macro_rules! __all {
($($ident:ident: $fut:expr;)+) => {
{
$(let mut $ident = (Some($fut), None);)+
$crate::task::future_fn(move |cx| {
$crate::future_fn(move |cx| {
use std::task::Poll;
use std::future::Future;
@ -1240,10 +1226,8 @@ macro_rules! any {
fut7: $fut7;
}
};
($($fut:expr),+ $(,)?) => { $crate::task::__proc_any_all!{ $crate::__any; $($fut),+ } }
($($fut:expr),+ $(,)?) => { $crate::__proc_any_all!{ $crate::__any; $($fut),+ } }
}
#[doc(inline)]
pub use crate::any;
#[doc(hidden)]
#[macro_export]
@ -1251,7 +1235,7 @@ macro_rules! __any {
($($ident:ident: $fut:expr;)+) => {
{
$(let mut $ident = $fut;)+
$crate::task::future_fn(move |cx| {
$crate::future_fn(move |cx| {
use std::task::Poll;
use std::future::Future;
$(
@ -1270,7 +1254,7 @@ macro_rules! __any {
}
#[doc(hidden)]
pub use zero_ui_proc_macros::task_any_all as __proc_any_all;
pub use zero_ui_task_proc_macros::task_any_all as __proc_any_all;
/// <span data-del-macro-root></span> A future that waits for the first future that is ready with an `Ok(T)` result.
///
@ -1371,10 +1355,8 @@ macro_rules! any_ok {
fut7: $fut7;
}
};
($($fut:expr),+ $(,)?) => { $crate::task::__proc_any_all!{ $crate::__any_ok; $($fut),+ } }
($($fut:expr),+ $(,)?) => { $crate::__proc_any_all!{ $crate::__any_ok; $($fut),+ } }
}
#[doc(inline)]
pub use crate::any_ok;
#[doc(hidden)]
#[macro_export]
@ -1382,7 +1364,7 @@ macro_rules! __any_ok {
($($ident:ident: $fut: expr;)+) => {
{
$(let mut $ident = (Some($fut), None);)+
$crate::task::future_fn(move |cx| {
$crate::future_fn(move |cx| {
use std::task::Poll;
use std::future::Future;
@ -1516,10 +1498,8 @@ macro_rules! any_some {
fut7: $fut7;
}
};
($($fut:expr),+ $(,)?) => { $crate::task::__proc_any_all!{ $crate::__any_some; $($fut),+ } }
($($fut:expr),+ $(,)?) => { $crate::__proc_any_all!{ $crate::__any_some; $($fut),+ } }
}
#[doc(inline)]
pub use crate::any_some;
#[doc(hidden)]
#[macro_export]
@ -1527,7 +1507,7 @@ macro_rules! __any_some {
($($ident:ident: $fut: expr;)+) => {
{
$(let mut $ident = Some($fut);)+
$crate::task::future_fn(move |cx| {
$crate::future_fn(move |cx| {
use std::task::Poll;
use std::future::Future;
@ -1674,10 +1654,8 @@ macro_rules! all_ok {
fut7: $fut7;
}
};
($($fut:expr),+ $(,)?) => { $crate::task::__proc_any_all!{ $crate::__all_ok; $($fut),+ } }
($($fut:expr),+ $(,)?) => { $crate::__proc_any_all!{ $crate::__all_ok; $($fut),+ } }
}
#[doc(inline)]
pub use crate::all_ok;
#[doc(hidden)]
#[macro_export]
@ -1686,7 +1664,7 @@ macro_rules! __all_ok {
{
$(let mut $ident = (Some($fut), None);)+
$crate::task::future_fn(move |cx| {
$crate::future_fn(move |cx| {
use std::task::Poll;
use std::future::Future;
@ -1834,10 +1812,8 @@ macro_rules! all_some {
fut7: $fut7;
}
};
($($fut:expr),+ $(,)?) => { $crate::task::__proc_any_all!{ $crate::__all_some; $($fut),+ } }
($($fut:expr),+ $(,)?) => { $crate::__proc_any_all!{ $crate::__all_some; $($fut),+ } }
}
#[doc(inline)]
pub use crate::all_some;
#[doc(hidden)]
#[macro_export]
@ -1845,7 +1821,7 @@ macro_rules! __all_some {
($($ident:ident: $fut: expr;)+) => {
{
$(let mut $ident = (Some($fut), None);)+
$crate::task::future_fn(move |cx| {
$crate::future_fn(move |cx| {
use std::task::Poll;
use std::future::Future;
@ -2028,7 +2004,7 @@ pub mod tests {
use rayon::prelude::*;
use super::*;
use crate::units::TimeUnits;
use zero_ui_units::TimeUnits;
#[track_caller]
fn async_test<F>(test: F) -> F::Output

View File

@ -3,7 +3,7 @@ use rayon::{
prelude::{IndexedParallelIterator, ParallelIterator},
};
use crate::context::LocalContext;
use zero_ui_app_context::LocalContext;
/// Extends [`ParallelIterator`] with thread context.
pub trait ParallelIteratorExt: ParallelIterator {
@ -13,8 +13,8 @@ pub trait ParallelIteratorExt: ParallelIterator {
/// Without this adapter all closures in the iterator chain that use [`context_local!`] and
/// [`app_local!`] will probably not work correctly.
///
/// [`context_local!`]: crate::context::context_local
/// [`app_local!`]: crate::context::app_local
/// [`context_local!`]: zero_ui_app_context::context_local
/// [`app_local!`]: zero_ui_app_context::app_local
fn with_ctx(self) -> ParallelIteratorWithCtx<Self> {
ParallelIteratorWithCtx {
base: self,
@ -230,7 +230,7 @@ mod tests {
use super::*;
use rayon::prelude::*;
use crate::{app::App, context::*};
use zero_ui_app_context::*;
context_local! {
static VALUE: u32 = 0u32;
@ -238,7 +238,7 @@ mod tests {
#[test]
fn map_and_sum_with_context() {
let _app = App::minimal().run_headless(false);
let _app = LocalContext::start_app(AppId::new_unique());
let thread_id = std::thread::current().id();
let used_other_thread = Arc::new(AtomicBool::new(false));
@ -261,7 +261,7 @@ mod tests {
#[test]
fn for_each_with_context() {
let _app = App::minimal().run_headless(false);
let _app = LocalContext::start_app(AppId::new_unique());
let thread_id = std::thread::current().id();
let used_other_thread = Arc::new(AtomicBool::new(false));
@ -282,7 +282,7 @@ mod tests {
#[test]
fn chain_for_each_with_context() {
let _app = App::minimal().run_headless(false);
let _app = LocalContext::start_app(AppId::new_unique());
let thread_id = std::thread::current().id();
let used_other_thread = Arc::new(AtomicBool::new(false));
@ -307,7 +307,7 @@ mod tests {
#[test]
fn chain_for_each_with_context_inverted() {
let _app = App::minimal().run_headless(false);
let _app = LocalContext::start_app(AppId::new_unique());
let thread_id = std::thread::current().id();
let used_other_thread = Arc::new(AtomicBool::new(false));

View File

@ -7,14 +7,12 @@ use std::{
task::{Poll, Waker},
};
use crate::{context::*, widget_instance::WidgetId};
enum UiTaskState<R> {
Pending {
future: Pin<Box<dyn Future<Output = R> + Send>>,
event_loop_waker: Waker,
#[cfg(debug_assertions)]
last_update: Option<crate::var::VarUpdateId>,
last_update: Option<zero_ui_var::VarUpdateId>,
},
Ready(R),
}
@ -37,21 +35,16 @@ impl<R: fmt::Debug> fmt::Debug for UiTaskState<R> {
#[derive(Debug)]
pub struct UiTask<R>(UiTaskState<R>);
impl<R> UiTask<R> {
/// Create a UI bound future executor.
/// New task with already build event-loop waker.
///
/// The `task` is inert and must be polled using [`update`] to start, and it must be polled every
/// [`UiNode::update`] after that, in widgets the `target` can be set so that the update requests are received.
///
/// [`update`]: UiTask::update
/// [`UiNode::update`]: crate::widget_instance::UiNode::update
/// [`UiNode::info`]: crate::widget_instance::UiNode::info
pub fn new<F>(target: Option<WidgetId>, task: F) -> Self
/// App crate provides an integrated `UiTaskWidget::new` that creates the waker for widgets.
pub fn new_raw<F>(event_loop_waker: Waker, task: F) -> Self
where
F: Future<Output = R> + Send + 'static,
{
UiTask(UiTaskState::Pending {
future: Box::pin(task),
event_loop_waker: UPDATES.waker(target),
event_loop_waker,
#[cfg(debug_assertions)]
last_update: None,
})
@ -69,7 +62,7 @@ impl<R> UiTask<R> {
///
/// In debug builds this is validated and an error message is logged if incorrect updates are detected.
///
/// [`task::yield_now`]: crate::task::yield_now
/// [`task::yield_now`]: crate::yield_now
pub fn update(&mut self) -> Option<&R> {
if let UiTaskState::Pending {
future,
@ -81,7 +74,7 @@ impl<R> UiTask<R> {
{
#[cfg(debug_assertions)]
{
let update = Some(crate::var::VARS.update_id());
let update = Some(zero_ui_var::VARS.update_id());
if *last_update == update {
tracing::error!("UiTask::update called twice in the same update");
}

View File

@ -7,4 +7,4 @@ license = "Apache-2.0"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde = "1"

View File

@ -1,7 +1,5 @@
use std::{fmt, ops};
use zero_ui_var::{animation::Transitionable, impl_from_and_into_var};
use super::Factor;
/// Extension methods for initializing [`ByteLength`] values.
@ -88,12 +86,12 @@ impl ByteUnits for usize {
///
/// The value is stored in bytes, you can use associated functions to convert from other units or
/// you can use the [`ByteUnits`] extension methods to initialize from an integer literal.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, serde::Serialize, serde::Deserialize, Transitionable)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, serde::Serialize, serde::Deserialize)]
#[serde(transparent)]
pub struct ByteLength(pub usize);
impl_from_and_into_var! {
fn from(bytes: usize) -> ByteLength {
ByteLength(bytes)
impl From<usize> for ByteLength {
fn from(value: usize) -> Self {
Self(value)
}
}
impl ops::Add for ByteLength {

View File

@ -1,3 +1,4 @@
mod byte;
mod corner_radius;
mod distance_key;
mod factor;
@ -10,6 +11,7 @@ mod transform;
pub use euclid;
pub use byte::*;
pub use corner_radius::*;
pub use distance_key::*;
pub use factor::*;

View File

@ -8,7 +8,7 @@ use std::{
};
use zero_ui_txt::Txt;
use zero_ui_units::{euclid, CornerRadius2D, Deadline, Dip, Factor, FactorPercent, FactorUnits, Px};
use zero_ui_units::{euclid, ByteLength, CornerRadius2D, Deadline, Dip, Factor, FactorPercent, FactorUnits, Px};
use crate::{animation::Transitionable, easing::EasingStep, impl_from_and_into_var};
@ -184,6 +184,12 @@ where
}
}
impl Transitionable for ByteLength {
fn lerp(self, to: &Self, step: EasingStep) -> Self {
Self(self.0.lerp(&to.0, step))
}
}
impl_from_and_into_var! {
fn from(s: &'static str) -> Txt;
fn from(s: String) -> Txt;
@ -200,6 +206,8 @@ impl_from_and_into_var! {
fn from(d: Instant) -> Deadline;
fn from(d: Duration) -> Deadline;
fn from(b: usize) -> ByteLength;
}
macro_rules! impl_into_var_option {