Merge pull request #3084 from iced-rs/synchronous-tasks

Synchronous `Task` Execution and `RedrawRequested` Consistency
This commit is contained in:
Héctor 2025-10-15 22:54:57 +02:00 • committed by GitHub
commit fe009142cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 184 additions and 52 deletions

View file

@ -192,6 +192,7 @@ where
let mut outdated = false; let mut outdated = false;
let mut redraw_request = window::RedrawRequest::Wait; let mut redraw_request = window::RedrawRequest::Wait;
let mut input_method = InputMethod::Disabled; let mut input_method = InputMethod::Disabled;
let mut has_layout_changed = false;
let viewport = Rectangle::with_size(self.bounds); let viewport = Rectangle::with_size(self.bounds);
let mut maybe_overlay = self let mut maybe_overlay = self
@ -259,6 +260,7 @@ where
shell.revalidate_layout(|| { shell.revalidate_layout(|| {
layout = overlay.layout(renderer, bounds); layout = overlay.layout(renderer, bounds);
has_layout_changed = true;
}); });
} }
@ -334,6 +336,8 @@ where
input_method.merge(shell.input_method()); input_method.merge(shell.input_method());
shell.revalidate_layout(|| { shell.revalidate_layout(|| {
has_layout_changed = true;
self.base = self.root.as_widget_mut().layout( self.base = self.root.as_widget_mut().layout(
&mut self.state, &mut self.state,
renderer, renderer,
@ -395,6 +399,7 @@ where
mouse_interaction, mouse_interaction,
redraw_request, redraw_request,
input_method, input_method,
has_layout_changed,
} }
}, },
event_statuses, event_statuses,
@ -624,5 +629,19 @@ pub enum State {
redraw_request: window::RedrawRequest, redraw_request: window::RedrawRequest,
/// The current [`InputMethod`] strategy of the user interface. /// The current [`InputMethod`] strategy of the user interface.
input_method: InputMethod, input_method: InputMethod,
/// Whether the layout of the [`UserInterface`] has changed.
has_layout_changed: bool,
}, },
} }
impl State {
/// Returns whether the layout of the [`UserInterface`] has changed.
pub fn has_layout_changed(&self) -> bool {
match self {
State::Outdated => true,
State::Updated {
has_layout_changed, ..
} => *has_layout_changed,
}
}
}

View file

@ -555,7 +555,6 @@ mod parser {
fn mouse_click(input: &str) -> IResult<&str, Mouse> { fn mouse_click(input: &str) -> IResult<&str, Mouse> {
let (input, _) = tag("click ")(input)?; let (input, _) = tag("click ")(input)?;
let (input, (button, target)) = mouse_button_at(input)?; let (input, (button, target)) = mouse_button_at(input)?;
Ok((input, Mouse::Click { button, target })) Ok((input, Mouse::Click { button, target }))
@ -563,7 +562,6 @@ mod parser {
fn mouse_press(input: &str) -> IResult<&str, Mouse> { fn mouse_press(input: &str) -> IResult<&str, Mouse> {
let (input, _) = tag("press ")(input)?; let (input, _) = tag("press ")(input)?;
let (input, (button, target)) = mouse_button_at(input)?; let (input, (button, target)) = mouse_button_at(input)?;
Ok((input, Mouse::Press { button, target })) Ok((input, Mouse::Press { button, target }))
@ -571,7 +569,6 @@ mod parser {
fn mouse_release(input: &str) -> IResult<&str, Mouse> { fn mouse_release(input: &str) -> IResult<&str, Mouse> {
let (input, _) = tag("release ")(input)?; let (input, _) = tag("release ")(input)?;
let (input, (button, target)) = mouse_button_at(input)?; let (input, (button, target)) = mouse_button_at(input)?;
Ok((input, Mouse::Release { button, target })) Ok((input, Mouse::Release { button, target }))

View file

@ -28,6 +28,7 @@ use crate::core::overlay;
use crate::core::renderer; use crate::core::renderer;
use crate::core::text; use crate::core::text;
use crate::core::widget::{self, Widget}; use crate::core::widget::{self, Widget};
use crate::core::window;
use crate::core::{ use crate::core::{
Clipboard, Element, Event, Length, Padding, Pixels, Point, Rectangle, Clipboard, Element, Event, Length, Padding, Pixels, Point, Rectangle,
Shell, Size, Vector, Shell, Size, Vector,
@ -201,7 +202,9 @@ where
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
viewport: &Rectangle, viewport: &Rectangle,
) { ) {
if let Event::Mouse(_) = event { if let Event::Mouse(_)
| Event::Window(window::Event::RedrawRequested(_)) = event
{
let state = tree.state.downcast_mut::<State>(); let state = tree.state.downcast_mut::<State>();
let previous_state = *state; let previous_state = *state;
let was_idle = *state == State::Idle; let was_idle = *state == State::Idle;

View file

@ -554,7 +554,7 @@ async fn run_instance<P>(
log::info!("System theme: {system_theme:?}"); log::info!("System theme: {system_theme:?}");
loop { 'next_event: loop {
// Empty the queue if possible // Empty the queue if possible
let event = if let Ok(event) = event_receiver.try_next() { let event = if let Ok(event) = event_receiver.try_next() {
event event
@ -770,11 +770,12 @@ async fn run_instance<P>(
event: event::WindowEvent::RedrawRequested, event: event::WindowEvent::RedrawRequested,
.. ..
} => { } => {
let Some(compositor) = &mut compositor else { let Some(mut current_compositor) = compositor.as_mut()
else {
continue; continue;
}; };
let Some((id, window)) = let Some((id, mut window)) =
window_manager.get_mut_alias(id) window_manager.get_mut_alias(id)
else { else {
continue; continue;
@ -803,7 +804,7 @@ async fn run_instance<P>(
); );
layout_span.finish(); layout_span.finish();
compositor.configure_surface( current_compositor.configure_surface(
&mut window.surface, &mut window.surface,
physical_size.width, physical_size.width,
physical_size.height, physical_size.height,
@ -819,20 +820,92 @@ async fn run_instance<P>(
let cursor = window.state.cursor(); let cursor = window.state.cursor();
let ui = user_interfaces let mut interface = user_interfaces
.get_mut(&id) .get_mut(&id)
.expect("Get user interface"); .expect("Get user interface");
let draw_span = debug::draw(id); let draw_span = debug::draw(id);
let (ui_state, _) = ui.update( let mut change_count = 0;
slice::from_ref(&redraw_event),
cursor,
&mut window.renderer,
&mut clipboard,
&mut messages,
);
ui.draw( let state = loop {
let (state, _) = interface.update(
slice::from_ref(&redraw_event),
cursor,
&mut window.renderer,
&mut clipboard,
&mut messages,
);
change_count += 1;
if messages.is_empty()
&& !state.has_layout_changed()
{
break state;
}
if change_count >= 10 {
log::warn!(
"More than 10 consecutive RedrawRequested events \
produced layout invalidation"
);
break state;
}
if !messages.is_empty() {
let caches: FxHashMap<_, _> =
ManuallyDrop::into_inner(user_interfaces)
.into_iter()
.map(|(id, interface)| {
(id, interface.into_cache())
})
.collect();
let actions = update(
&mut program,
&mut runtime,
&mut messages,
);
user_interfaces =
ManuallyDrop::new(build_user_interfaces(
&program,
&mut window_manager,
caches,
));
for action in actions {
run_action(
action,
&program,
&mut runtime,
&mut compositor,
&mut events,
&mut messages,
&mut clipboard,
&mut control_sender,
&mut user_interfaces,
&mut window_manager,
&mut ui_caches,
&mut is_window_opening,
&mut system_theme,
);
}
let Some(next_compositor) = compositor.as_mut()
else {
continue 'next_event;
};
current_compositor = next_compositor;
window = window_manager.get_mut(id).unwrap();
interface =
user_interfaces.get_mut(&id).unwrap();
}
};
interface.draw(
&mut window.renderer, &mut window.renderer,
window.state.theme(), window.state.theme(),
&renderer::Style { &renderer::Style {
@ -842,27 +915,28 @@ async fn run_instance<P>(
); );
draw_span.finish(); draw_span.finish();
runtime.broadcast(subscription::Event::Interaction {
window: id,
event: redraw_event,
status: core::event::Status::Ignored,
});
if let user_interface::State::Updated { if let user_interface::State::Updated {
redraw_request, redraw_request,
input_method, input_method,
mouse_interaction, mouse_interaction,
} = ui_state ..
} = state
{ {
window.request_redraw(redraw_request); window.request_redraw(redraw_request);
window.request_input_method(input_method); window.request_input_method(input_method);
window.update_mouse(mouse_interaction); window.update_mouse(mouse_interaction);
} }
runtime.broadcast(subscription::Event::Interaction {
window: id,
event: redraw_event,
status: core::event::Status::Ignored,
});
window.draw_preedit(); window.draw_preedit();
let present_span = debug::present(id); let present_span = debug::present(id);
match compositor.present( match current_compositor.present(
&mut window.renderer, &mut window.renderer,
&mut window.surface, &mut window.surface,
window.state.viewport(), window.state.viewport(),
@ -1071,31 +1145,17 @@ async fn run_instance<P>(
} }
if !messages.is_empty() || uis_stale { if !messages.is_empty() || uis_stale {
let cached_interfaces: FxHashMap< let cached_interfaces: FxHashMap<_, _> =
window::Id, ManuallyDrop::into_inner(user_interfaces)
user_interface::Cache, .into_iter()
> = ManuallyDrop::into_inner(user_interfaces) .map(|(id, ui)| (id, ui.into_cache()))
.drain() .collect();
.map(|(id, ui)| (id, ui.into_cache()))
.collect();
update(&mut program, &mut runtime, &mut messages); let actions = update(
&mut program,
for (id, window) in window_manager.iter_mut() { &mut runtime,
window.state.synchronize( &mut messages,
&program, );
id,
&window.raw,
);
window.raw.request_redraw();
}
debug::theme_changed(|| {
window_manager.first().and_then(|window| {
theme::Base::palette(window.state.theme())
})
});
user_interfaces = user_interfaces =
ManuallyDrop::new(build_user_interfaces( ManuallyDrop::new(build_user_interfaces(
@ -1103,6 +1163,24 @@ async fn run_instance<P>(
&mut window_manager, &mut window_manager,
cached_interfaces, cached_interfaces,
)); ));
for action in actions {
run_action(
action,
&program,
&mut runtime,
&mut compositor,
&mut events,
&mut messages,
&mut clipboard,
&mut control_sender,
&mut user_interfaces,
&mut window_manager,
&mut ui_caches,
&mut is_window_opening,
&mut system_theme,
);
}
} }
if let Some(redraw_at) = window_manager.redraw_at() { if let Some(redraw_at) = window_manager.redraw_at() {
@ -1152,14 +1230,36 @@ fn update<P: Program, E: Executor>(
program: &mut program::Instance<P>, program: &mut program::Instance<P>,
runtime: &mut Runtime<E, Proxy<P::Message>, Action<P::Message>>, runtime: &mut Runtime<E, Proxy<P::Message>, Action<P::Message>>,
messages: &mut Vec<P::Message>, messages: &mut Vec<P::Message>,
) where ) -> Vec<Action<P::Message>>
where
P::Theme: theme::Base, P::Theme: theme::Base,
{ {
use futures::futures;
let mut actions = Vec::new();
for message in messages.drain(..) { for message in messages.drain(..) {
let task = runtime.enter(|| program.update(message)); let task = runtime.enter(|| program.update(message));
if let Some(stream) = runtime::task::into_stream(task) { if let Some(mut stream) = runtime::task::into_stream(task) {
runtime.run(stream); let waker = futures::task::noop_waker_ref();
let mut context = futures::task::Context::from_waker(waker);
// Run immediately available actions synchronously (e.g. widget operations)
loop {
match runtime.enter(|| stream.poll_next_unpin(&mut context)) {
futures::task::Poll::Ready(Some(action)) => {
actions.push(action);
}
futures::task::Poll::Ready(None) => {
break;
}
futures::task::Poll::Pending => {
runtime.run(stream);
break;
}
}
}
} }
} }
@ -1167,6 +1267,8 @@ fn update<P: Program, E: Executor>(
let recipes = subscription::into_recipes(subscription.map(Action::Output)); let recipes = subscription::into_recipes(subscription.map(Action::Output));
runtime.track(recipes); runtime.track(recipes);
actions
} }
fn run_action<'a, P, C>( fn run_action<'a, P, C>(
@ -1606,6 +1708,17 @@ where
C: Compositor<Renderer = P::Renderer>, C: Compositor<Renderer = P::Renderer>,
P::Theme: theme::Base, P::Theme: theme::Base,
{ {
for (id, window) in window_manager.iter_mut() {
window.state.synchronize(program, id, &window.raw);
window.raw.request_redraw();
}
debug::theme_changed(|| {
window_manager
.first()
.and_then(|window| theme::Base::palette(window.state.theme()))
});
cached_user_interfaces cached_user_interfaces
.drain() .drain()
.filter_map(|(id, cache)| { .filter_map(|(id, cache)| {