-
-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
panic when accessing State::write
in use_future
#48
Comments
A I tried reproducing with the following, but wasn't able to. Are you referencing the state from the parent's hook or something? use iocraft::prelude::*;
use std::time::Duration;
#[component]
fn Counter(mut hooks: Hooks) -> impl Into<AnyElement<'static>> {
let mut count = hooks.use_state(|| 0);
hooks.use_future(async move {
loop {
smol::Timer::after(Duration::from_millis(100)).await;
count += 1;
}
});
element! {
Text(color: Color::Blue, content: format!("counter: {}", count))
}
}
#[component]
fn Container(mut hooks: Hooks) -> impl Into<AnyElement<'static>> {
let mut should_show = hooks.use_state(|| true);
hooks.use_terminal_events({
move |event| match event {
TerminalEvent::Key(KeyEvent { code, kind, .. }) if kind != KeyEventKind::Release => {
match code {
KeyCode::Char('x') => should_show.set(!should_show.get()),
_ => {}
}
}
_ => {}
}
});
element! {
Box {
#(if should_show.get() { Some(element!(Counter)) } else { None })
}
}
}
fn main() {
smol::block_on(element!(Container).render_loop()).unwrap();
} |
State::write
in use_future
State::write
in use_future
I've put together a PR with But before merging, I'd like to understand better what kind of use-case you have that would trigger that panic. |
@ccbrown It's this line: https://github.com/moonrepo/starbase/blob/progress-state/crates/console/src/components/progress.rs#L135 Basically, I have a progress bar component and this I have to spawn a nested task, since the outer |
Ah, yeah, that would probably do it. Because you're launching the task with I think you'd want to use hooks.use_future(async move {
loop {
sleep(Duration::from_millis(150)).await;
estimator.write().record(value.get(), Instant::now());
}
});
hooks.use_future(async move {
let mut receiver = reporter.subscribe();
while let Ok(state) = receiver.recv().await {
match state {
ProgressState::Wait(val) => {
sleep(val).await;
}
ProgressState::Exit => {
should_exit.set(true);
break;
}
ProgressState::Max(val) => {
max.set(val);
}
ProgressState::Message(val) => {
message.set(val);
}
ProgressState::Prefix(val) => {
prefix.set(val);
}
ProgressState::Suffix(val) => {
suffix.set(val);
}
ProgressState::Value(val) => {
if val >= max.get() {
value.set(max.get());
should_exit.set(true);
break;
} else {
value.set(val);
}
}
_ => {}
};
}
}); That way you're guaranteed to have all your async tasks cleaned up when the component goes away. |
I've tried that but only 1 of the futures would actually run. Let me try again, maybe my component was in a bad state. |
@ccbrown Yeah, changed it to this, but the 2nd future never runs. moonrepo/starbase@39c9222 Here's the output in the console:
If I swap the
|
@milesj can you try the same thing, but do the print using the This will make sure the output is always printed above the TUI (the change in indentation makes this diff look much bigger than it is): diff --git a/crates/console/src/components/progress.rs b/crates/console/src/components/progress.rs
index df2dccb..7a3b310 100644
--- a/crates/console/src/components/progress.rs
+++ b/crates/console/src/components/progress.rs
@@ -126,52 +126,57 @@ pub fn ProgressBar<'a>(
let reporter = props.reporter.take();
- hooks.use_future(async move {
- println!("ProgressBar REPORTER");
+ let (stdout, _) = hooks.use_output();
- let Some(reporter) = reporter else {
- return;
- };
+ hooks.use_future({
+ let stdout = stdout.clone();
+ async move {
+ stdout.println("ProgressBar REPORTER");
- let mut receiver = reporter.subscribe();
+ let Some(reporter) = reporter else {
+ return;
+ };
- while let Ok(state) = receiver.recv().await {
- match state {
- ProgressState::Wait(val) => {
- sleep(val).await;
- }
- ProgressState::Exit => {
- should_exit.set(true);
- break;
- }
- ProgressState::Max(val) => {
- max.set(val);
- }
- ProgressState::Message(val) => {
- message.set(val);
- }
- ProgressState::Prefix(val) => {
- prefix.set(val);
- }
- ProgressState::Suffix(val) => {
- suffix.set(val);
- }
- ProgressState::Value(val) => {
- if val >= max.get() {
- value.set(max.get());
+ let mut receiver = reporter.subscribe();
+
+ while let Ok(state) = receiver.recv().await {
+ match state {
+ ProgressState::Wait(val) => {
+ sleep(val).await;
+ }
+ ProgressState::Exit => {
should_exit.set(true);
break;
- } else {
- value.set(val);
}
- }
- _ => {}
- };
+ ProgressState::Max(val) => {
+ max.set(val);
+ }
+ ProgressState::Message(val) => {
+ message.set(val);
+ }
+ ProgressState::Prefix(val) => {
+ prefix.set(val);
+ }
+ ProgressState::Suffix(val) => {
+ suffix.set(val);
+ }
+ ProgressState::Value(val) => {
+ if val >= max.get() {
+ value.set(max.get());
+ should_exit.set(true);
+ break;
+ } else {
+ value.set(val);
+ }
+ }
+ _ => {}
+ };
+ }
}
});
hooks.use_future(async move {
- println!("ProgressBar LOOP");
+ stdout.println("ProgressBar LOOP");
loop {
sleep(Duration::from_millis(150)).await; And that appears to be working to me: |
Ah you're right. This has been throwing me off thinking my implementation was wrong. Let me try and tidy everything back up and see if I run into any other issues. |
@ccbrown Ok I believe I got it all working. Had to rewrite my For the progress component, I need a way to update state from outside the component, because I need to inject the current progress numbers. I'm currently achieving this using tokio's broadcast channel (I need fan out style). You can see this here:
This works perfectly when rendering a single progress bar in the terminal. However, when I want to render multiple progress bars, it seems like only the 1st bar in the stack actually updates, and the other ones do not. Here's a screenshot of what happens: When I enabled logging, the other tools are actually being processed in the background, but the UI never reflects this. At first I thought this may be an issue where iocraft was creating a new sender/receiver on each render, so I created this This didn't solve the problem. I'm debugged this quite a bit but not really sure what's going on. So my question is, have you thought about this problem at all? Basically how to update state from outside the component? For context and reference, here's the PR that's implementing the progress:
|
Ah, this looks like an iocraft bug that occurs when "key" isn't specified for items added via an iterator. To make sure state is maintained optimally, elements rendered from an iterator should have a key, similar to how it is with React. I think if you do this, it'll solve your issue: diff --git a/crates/cli/src/components/install_all_progress.rs b/crates/cli/src/components/install_all_progress.rs
index 1cb2866d..f31ad12e 100644
--- a/crates/cli/src/components/install_all_progress.rs
+++ b/crates/cli/src/components/install_all_progress.rs
@@ -48,7 +48,7 @@ pub fn InstallAllProgress<'a>(
Stack {
#(props.tools.iter().map(|(id, inner_props)| {
element! {
- Box {
+ Box(key: id.clone()) {
Box(
justify_content: JustifyContent::End,
width: props.id_width as u16, But iocraft should be able to behave better when it's not provided, or at least warn the user. I'll figure out a way to address this. |
Yesss, that worked. Thank you 🙏 It renders beautifully now. Thanks for this lib. It has made all this terminal stuff super easy to implement! |
Glad to hear it! I just published 0.5.2, which has some fixes/improvements brought up by this thread. Using |
Awesome work, thanks again. |
Looks like the recent
State::write
changes will panic when used within ause_future
and the component is unmounted.A
try_write
is probably enough to solve this problem.The text was updated successfully, but these errors were encountered: