diff --git a/Cargo.lock b/Cargo.lock index 9606b9a6..efa3db2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -378,7 +378,6 @@ dependencies = [ "async-global-executor", "async-io", "async-lock", - "async-process", "crossbeam-utils", "futures-channel", "futures-core", @@ -2406,6 +2405,7 @@ dependencies = [ "iced_futures", "iced_highlighter", "iced_renderer", + "iced_runtime", "iced_wgpu", "iced_widget", "iced_winit", @@ -2434,7 +2434,6 @@ dependencies = [ name = "iced_futures" version = "0.14.0-dev" dependencies = [ - "async-std", "futures", "iced_core", "log", @@ -2777,6 +2776,7 @@ version = "0.1.0" dependencies = [ "console_error_panic_hook", "console_log", + "futures", "iced_wgpu", "iced_widget", "iced_winit", @@ -5743,12 +5743,12 @@ dependencies = [ name = "todos" version = "0.1.0" dependencies = [ - "async-std", "directories", "iced", "iced_test", "serde", "serde_json", + "tokio", "tracing-subscriber", "uuid", "wasmtimer", diff --git a/Cargo.toml b/Cargo.toml index c5e8f865..b13c26f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ all-features = true maintenance = { status = "actively-developed" } [features] -default = ["wgpu", "tiny-skia", "auto-detect-theme"] +default = ["wgpu", "tiny-skia", "auto-detect-theme", "thread-pool"] # Enables the `wgpu` GPU-accelerated renderer backend wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"] # Enables the `tiny-skia` software renderer backend @@ -43,10 +43,10 @@ markdown = ["iced_widget/markdown"] lazy = ["iced_widget/lazy"] # Enables a debug view in native platforms (press F12) debug = ["iced_winit/debug"] +# Enables the `thread-pool` futures executor as the `executor::Default` on native platforms +thread-pool = ["iced_futures/thread-pool"] # Enables `tokio` as the `executor::Default` on native platforms tokio = ["iced_futures/tokio"] -# Enables `async-std` as the `executor::Default` on native platforms -async-std = ["iced_futures/async-std"] # Enables `smol` as the `executor::Default` on native platforms smol = ["iced_futures/smol"] # Enables querying system information @@ -67,11 +67,14 @@ auto-detect-theme = ["iced_core/auto-detect-theme"] strict-assertions = ["iced_renderer/strict-assertions"] # Redraws on every runtime event, and not only when a widget requests it unconditional-rendering = ["iced_winit/unconditional-rendering"] +# Enables support for the `sipper` library +sipper = ["iced_runtime/sipper"] [dependencies] iced_core.workspace = true iced_futures.workspace = true iced_renderer.workspace = true +iced_runtime.workspace = true iced_widget.workspace = true iced_winit.features = ["program"] iced_winit.workspace = true @@ -144,13 +147,12 @@ iced_wgpu = { version = "0.14.0-dev", path = "wgpu" } iced_widget = { version = "0.14.0-dev", path = "widget" } iced_winit = { version = "0.14.0-dev", path = "winit" } -async-std = "1.0" bitflags = "2.0" bytemuck = { version = "1.0", features = ["derive"] } bytes = "1.6" cosmic-text = "0.13" dark-light = "2.0" -futures = "0.3" +futures = { version = "0.3", default-features = false } glam = "0.25" cryoglyph = { git = "https://github.com/iced-rs/cryoglyph.git", rev = "be2defe4a13fd7c97c6f4c81e8e085463eb578dc" } guillotiere = "0.6" diff --git a/examples/download_progress/Cargo.toml b/examples/download_progress/Cargo.toml index 9c52b2bd..56c538fe 100644 --- a/examples/download_progress/Cargo.toml +++ b/examples/download_progress/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["tokio"] +iced.features = ["tokio", "sipper"] [dependencies.reqwest] version = "0.12" diff --git a/examples/gallery/Cargo.toml b/examples/gallery/Cargo.toml index 5161f368..3dd5d378 100644 --- a/examples/gallery/Cargo.toml +++ b/examples/gallery/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["tokio", "image", "web-colors", "debug"] +iced.features = ["tokio", "sipper", "image", "web-colors", "debug"] reqwest.version = "0.12" reqwest.features = ["json"] diff --git a/examples/integration/Cargo.toml b/examples/integration/Cargo.toml index 3bdf9408..2b5f99e4 100644 --- a/examples/integration/Cargo.toml +++ b/examples/integration/Cargo.toml @@ -12,6 +12,9 @@ iced_wgpu.workspace = true iced_widget.workspace = true iced_widget.features = ["wgpu"] +futures.workspace = true +futures.features = ["thread-pool"] + [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tracing-subscriber = "0.3" diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml index 77b776d5..981b9e5f 100644 --- a/examples/todos/Cargo.toml +++ b/examples/todos/Cargo.toml @@ -7,14 +7,14 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["async-std", "debug"] +iced.features = ["tokio", "debug"] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" uuid = { version = "1.0", features = ["v4", "fast-rng", "serde"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -async-std.workspace = true +tokio.workspace = true directories = "6.0" tracing-subscriber = "0.3" diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 65a34c64..03e62d3c 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -482,7 +482,6 @@ enum LoadError { #[derive(Debug, Clone)] enum SaveError { - File, Write, Format, } @@ -504,15 +503,7 @@ impl SavedState { } async fn load() -> Result { - use async_std::prelude::*; - - let mut contents = String::new(); - - let mut file = async_std::fs::File::open(Self::path()) - .await - .map_err(|_| LoadError::File)?; - - file.read_to_string(&mut contents) + let contents = tokio::fs::read_to_string(Self::path()) .await .map_err(|_| LoadError::File)?; @@ -520,31 +511,25 @@ impl SavedState { } async fn save(self) -> Result<(), SaveError> { - use async_std::prelude::*; - let json = serde_json::to_string_pretty(&self) .map_err(|_| SaveError::Format)?; let path = Self::path(); if let Some(dir) = path.parent() { - async_std::fs::create_dir_all(dir) + tokio::fs::create_dir_all(dir) .await - .map_err(|_| SaveError::File)?; + .map_err(|_| SaveError::Write)?; } { - let mut file = async_std::fs::File::create(path) - .await - .map_err(|_| SaveError::File)?; - - file.write_all(json.as_bytes()) + tokio::fs::write(path, json.as_bytes()) .await .map_err(|_| SaveError::Write)?; } // This is a simple way to save at most once every couple seconds - async_std::task::sleep(std::time::Duration::from_secs(2)).await; + tokio::time::sleep(std::time::Duration::from_secs(2)).await; Ok(()) } @@ -570,7 +555,7 @@ impl SavedState { } async fn save(self) -> Result<(), SaveError> { - let storage = Self::storage().ok_or(SaveError::File)?; + let storage = Self::storage().ok_or(SaveError::Write)?; let json = serde_json::to_string_pretty(&self) .map_err(|_| SaveError::Format)?; diff --git a/examples/websocket/Cargo.toml b/examples/websocket/Cargo.toml index 88ebdae1..c47e3c93 100644 --- a/examples/websocket/Cargo.toml +++ b/examples/websocket/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["debug", "tokio"] +iced.features = ["debug", "tokio", "sipper"] warp = "0.3" diff --git a/futures/Cargo.toml b/futures/Cargo.toml index 3984ce83..e2c342ff 100644 --- a/futures/Cargo.toml +++ b/futures/Cargo.toml @@ -24,14 +24,12 @@ thread-pool = ["futures/thread-pool"] iced_core.workspace = true futures.workspace = true +futures.features = ["std"] + log.workspace = true rustc-hash.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -async-std.workspace = true -async-std.optional = true -async-std.features = ["unstable"] - smol.workspace = true smol.optional = true diff --git a/futures/src/backend/default.rs b/futures/src/backend/default.rs index 842b5927..4418834e 100644 --- a/futures/src/backend/default.rs +++ b/futures/src/backend/default.rs @@ -2,10 +2,9 @@ //! //! - On native platforms, it will use: //! - `backend::native::tokio` when the `tokio` feature is enabled. -//! - `backend::native::async-std` when the `async-std` feature is -//! enabled. //! - `backend::native::smol` when the `smol` feature is enabled. -//! - `backend::native::thread_pool` otherwise. +//! - `backend::native::thread_pool` when the `thread-pool` feature is enabled. +//! - `backend::null` otherwise. //! //! - On Wasm, it will use `backend::wasm::wasm_bindgen`. #[cfg(not(target_arch = "wasm32"))] @@ -13,24 +12,17 @@ mod platform { #[cfg(feature = "tokio")] pub use crate::backend::native::tokio::*; - #[cfg(all(feature = "async-std", not(feature = "tokio"),))] - pub use crate::backend::native::async_std::*; - - #[cfg(all( - feature = "smol", - not(any(feature = "tokio", feature = "async-std")), - ))] + #[cfg(all(feature = "smol", not(feature = "tokio"),))] pub use crate::backend::native::smol::*; #[cfg(all( feature = "thread-pool", - not(any(feature = "tokio", feature = "async-std", feature = "smol")) + not(any(feature = "tokio", feature = "smol")) ))] pub use crate::backend::native::thread_pool::*; #[cfg(not(any( feature = "tokio", - feature = "async-std", feature = "smol", feature = "thread-pool" )))] diff --git a/futures/src/backend/native.rs b/futures/src/backend/native.rs index 85af2c88..e5595bdb 100644 --- a/futures/src/backend/native.rs +++ b/futures/src/backend/native.rs @@ -2,9 +2,6 @@ #[cfg(feature = "tokio")] pub mod tokio; -#[cfg(feature = "async-std")] -pub mod async_std; - #[cfg(feature = "smol")] pub mod smol; diff --git a/futures/src/backend/native/async_std.rs b/futures/src/backend/native/async_std.rs deleted file mode 100644 index be258b26..00000000 --- a/futures/src/backend/native/async_std.rs +++ /dev/null @@ -1,56 +0,0 @@ -//! An `async-std` backend. - -/// An `async-std` executor. -#[derive(Debug)] -pub struct Executor; - -impl crate::Executor for Executor { - fn new() -> Result { - Ok(Self) - } - - #[allow(clippy::let_underscore_future)] - fn spawn(&self, future: impl Future + Send + 'static) { - let _ = async_std::task::spawn(future); - } -} - -pub mod time { - //! Listen and react to time. - use crate::subscription::{self, Hasher, Subscription}; - - /// Returns a [`Subscription`] that produces messages at a set interval. - /// - /// The first message is produced after a `duration`, and then continues to - /// produce more messages every `duration` after that. - pub fn every( - duration: std::time::Duration, - ) -> Subscription { - subscription::from_recipe(Every(duration)) - } - - #[derive(Debug)] - struct Every(std::time::Duration); - - impl subscription::Recipe for Every { - type Output = std::time::Instant; - - fn hash(&self, state: &mut Hasher) { - use std::hash::Hash; - - std::any::TypeId::of::().hash(state); - self.0.hash(state); - } - - fn stream( - self: Box, - _input: subscription::EventStream, - ) -> futures::stream::BoxStream<'static, Self::Output> { - use futures::stream::StreamExt; - - async_std::stream::interval(self.0) - .map(|_| std::time::Instant::now()) - .boxed() - } - } -} diff --git a/futures/src/backend/null.rs b/futures/src/backend/null.rs index f31415b9..59b740e3 100644 --- a/futures/src/backend/null.rs +++ b/futures/src/backend/null.rs @@ -1,4 +1,5 @@ //! A backend that does nothing! +use crate::MaybeSend; /// An executor that drops all the futures, instead of spawning them. #[derive(Debug)] @@ -9,11 +10,7 @@ impl crate::Executor for Executor { Ok(Self) } - #[cfg(not(target_arch = "wasm32"))] - fn spawn(&self, _future: impl Future + Send + 'static) {} - - #[cfg(target_arch = "wasm32")] - fn spawn(&self, _future: impl Future + 'static) {} + fn spawn(&self, _future: impl Future + MaybeSend + 'static) {} } pub mod time { diff --git a/futures/src/runtime.rs b/futures/src/runtime.rs index 0f30b469..72252458 100644 --- a/futures/src/runtime.rs +++ b/futures/src/runtime.rs @@ -1,6 +1,6 @@ //! Run commands and keep track of subscriptions. use crate::subscription; -use crate::{BoxFuture, BoxStream, Executor, MaybeSend}; +use crate::{BoxStream, Executor, MaybeSend}; use futures::{Sink, channel::mpsc}; use std::marker::PhantomData; @@ -51,20 +51,10 @@ where } /// Spawns a [`Future`] in the [`Runtime`]. - /// - /// The resulting `Message` will be forwarded to the `Sender` of the - /// [`Runtime`]. - /// - /// [`Future`]: BoxFuture - pub fn spawn(&mut self, future: BoxFuture) { - use futures::{FutureExt, SinkExt}; - - let mut sender = self.sender.clone(); - - let future = future.then(|message| async move { - let _ = sender.send(message).await; - }); - + pub fn spawn( + &mut self, + future: impl Future + MaybeSend + 'static, + ) { self.executor.spawn(future); } diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs index e9063678..f0f67607 100644 --- a/graphics/src/compositor.rs +++ b/graphics/src/compositor.rs @@ -10,7 +10,7 @@ use thiserror::Error; use std::borrow::Cow; /// A graphics compositor that can draw to windows. -pub trait Compositor: Sized { +pub trait Compositor: Sized + MaybeSend { /// The iced renderer of the backend. type Renderer; @@ -21,7 +21,7 @@ pub trait Compositor: Sized { fn new( settings: Settings, compatible_window: W, - ) -> impl Future> { + ) -> impl Future> + MaybeSend { Self::with_backend(settings, compatible_window, None) } @@ -33,7 +33,7 @@ pub trait Compositor: Sized { _settings: Settings, _compatible_window: W, _backend: Option<&str>, - ) -> impl Future>; + ) -> impl Future> + MaybeSend; /// Creates a [`Self::Renderer`] for the [`Compositor`]. fn create_renderer(&self) -> Self::Renderer; diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index fc212ef8..5fc67b97 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -21,8 +21,9 @@ multi-window = [] bytes.workspace = true iced_core.workspace = true iced_futures.workspace = true -iced_futures.features = ["thread-pool"] raw-window-handle.workspace = true -sipper.workspace = true thiserror.workspace = true + +sipper.workspace = true +sipper.optional = true diff --git a/runtime/src/task.rs b/runtime/src/task.rs index fd5970ac..624fc3a7 100644 --- a/runtime/src/task.rs +++ b/runtime/src/task.rs @@ -7,8 +7,10 @@ use crate::futures::futures::future::{self, FutureExt}; use crate::futures::futures::stream::{self, Stream, StreamExt}; use crate::futures::{BoxStream, MaybeSend, boxed_stream}; +use std::convert::Infallible; use std::sync::Arc; +#[cfg(feature = "sipper")] #[doc(no_inline)] pub use sipper::{Never, Sender, Sipper, Straw, sipper, stream}; @@ -60,6 +62,7 @@ impl Task { /// Creates a [`Task`] that runs the given [`Sipper`] to completion, mapping /// progress with the first closure and the output with the second one. + #[cfg(feature = "sipper")] pub fn sip( sipper: S, on_progress: impl FnMut(S::Progress) -> T + MaybeSend + 'static, @@ -391,7 +394,7 @@ where } /// Creates a new [`Task`] that executes the given [`Action`] and produces no output. -pub fn effect(action: impl Into>) -> Task { +pub fn effect(action: impl Into>) -> Task { let action = action.into(); Task(Some(boxed_stream(stream::once(async move { diff --git a/src/lib.rs b/src/lib.rs index 95820ed7..dd879bf6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -480,6 +480,18 @@ use iced_winit::runtime; pub use iced_futures::futures; pub use iced_futures::stream; +#[cfg(not(any( + target_arch = "wasm32", + feature = "thread-pool", + feature = "tokio", + feature = "smol" +)))] +compile_error!( + "No futures executor has been enabled! You must enable an + executor feature.\n + Available options: thread-pool, tokio, or smol." +); + #[cfg(feature = "highlighter")] pub use iced_highlighter as highlighter; @@ -519,9 +531,10 @@ pub use alignment::Vertical::{Bottom, Top}; pub mod task { //! Create runtime tasks. - pub use crate::runtime::task::{ - Handle, Never, Sipper, Straw, Task, sipper, stream, - }; + pub use crate::runtime::task::{Handle, Task}; + + #[cfg(feature = "sipper")] + pub use crate::runtime::task::{Never, Sipper, Straw, sipper, stream}; } pub mod clipboard { @@ -534,18 +547,7 @@ pub mod clipboard { pub mod executor { //! Choose your preferred executor to power your application. pub use iced_futures::Executor; - - /// A default cross-platform executor. - /// - /// - On native platforms, it will use: - /// - `iced_futures::backend::native::tokio` when the `tokio` feature is enabled. - /// - `iced_futures::backend::native::async-std` when the `async-std` feature is - /// enabled. - /// - `iced_futures::backend::native::smol` when the `smol` feature is enabled. - /// - `iced_futures::backend::native::thread_pool` otherwise. - /// - /// - On Wasm, it will use `iced_futures::backend::wasm::wasm_bindgen`. - pub type Default = iced_futures::backend::default::Executor; + pub use iced_futures::backend::default::Executor as Default; } pub mod font { diff --git a/src/time.rs b/src/time.rs index 98a800ac..f32eeb17 100644 --- a/src/time.rs +++ b/src/time.rs @@ -6,7 +6,6 @@ pub use crate::core::time::*; docsrs, doc(cfg(any( feature = "tokio", - feature = "async-std", feature = "smol", target_arch = "wasm32" ))) diff --git a/winit/src/program.rs b/winit/src/program.rs index 6904c02a..77b4f9d7 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -18,7 +18,7 @@ use crate::futures::futures::channel::oneshot; use crate::futures::futures::task; use crate::futures::futures::{Future, StreamExt}; use crate::futures::subscription::{self, Subscription}; -use crate::futures::{Executor, Runtime}; +use crate::futures::{Executor, MaybeSend, Runtime}; use crate::graphics; use crate::graphics::{Compositor, compositor}; use crate::runtime::Debug; @@ -149,7 +149,7 @@ pub fn run( ) -> Result<(), Error> where P: Program + 'static, - C: Compositor + 'static, + C: Compositor + MaybeSend + 'static, P::Theme: theme::Base, { use winit::event_loop::EventLoop; @@ -494,9 +494,7 @@ where event_loop.exit(); } }, - _ => { - break; - } + _ => break, }, task::Poll::Ready(_) => { event_loop.exit(); @@ -562,7 +560,7 @@ async fn run_instance( default_fonts: Vec>, ) where P: Program + 'static, - C: Compositor + 'static, + C: Compositor + MaybeSend + 'static, P::Theme: theme::Base, { use winit::event; @@ -579,35 +577,11 @@ async fn run_instance( let mut ui_caches = FxHashMap::default(); let mut user_interfaces = ManuallyDrop::new(FxHashMap::default()); let mut clipboard = Clipboard::unconnected(); - let mut compositor_receiver: Option> = None; debug.startup_finished(); loop { - let event = if compositor_receiver.is_some() { - let compositor_receiver = - compositor_receiver.take().expect("Waiting for compositor"); - - match compositor_receiver.await { - Ok(Ok((new_compositor, event))) => { - compositor = Some(new_compositor); - - Some(event) - } - Ok(Err(error)) => { - control_sender - .start_send(Control::Crash( - Error::GraphicsCreationFailed(error), - )) - .expect("Send control action"); - break; - } - Err(error) => { - panic!("Compositor initialization failed: {error}") - } - } - // Empty the queue if possible - } else if let Ok(event) = event_receiver.try_next() { + let event = if let Ok(event) = event_receiver.try_next() { event } else { event_receiver.next().await @@ -626,17 +600,17 @@ async fn run_instance( on_open, } => { if compositor.is_none() { - let (compositor_sender, new_compositor_receiver) = + let (compositor_sender, compositor_receiver) = oneshot::channel(); - compositor_receiver = Some(new_compositor_receiver); - let create_compositor = { + let window = window.clone(); + let mut proxy = proxy.clone(); let default_fonts = default_fonts.clone(); async move { let mut compositor = - C::new(graphics_settings, window.clone()).await; + C::new(graphics_settings, window).await; if let Ok(compositor) = &mut compositor { for font in default_fonts { @@ -645,34 +619,38 @@ async fn run_instance( } compositor_sender - .send(compositor.map(|compositor| { - ( - compositor, - Event::WindowCreated { - id, - window, - exit_on_close_request, - make_visible, - on_open, - }, - ) - })) + .send(compositor) .ok() .expect("Send compositor"); + + // HACK! Send a proxy event on completion to trigger + // a runtime re-poll + // TODO: Send compositor through proxy (?) + { + let (sender, _receiver) = oneshot::channel(); + + proxy.send_action(Action::Window( + runtime::window::Action::GetLatest(sender), + )); + } } }; - #[cfg(not(target_arch = "wasm32"))] - crate::futures::futures::executor::block_on( - create_compositor, - ); + runtime.spawn(create_compositor); - #[cfg(target_arch = "wasm32")] + match compositor_receiver + .await + .expect("Wait for compositor") { - wasm_bindgen_futures::spawn_local(create_compositor); + Ok(new_compositor) => { + compositor = Some(new_compositor); + } + Err(error) => { + let _ = control_sender + .start_send(Control::Crash(error.into())); + break; + } } - - continue; } let window = window_manager.insert(