From fbefdbd07a0327b58ce02730abe4f9b92485adc7 Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Fri, 29 Aug 2025 15:11:18 -0700 Subject: [PATCH 1/4] hyperapp: add spawn() --- src/hyperapp.rs | 56 +++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/hyperapp.rs b/src/hyperapp.rs index 7cd0439..88b00d9 100644 --- a/src/hyperapp.rs +++ b/src/hyperapp.rs @@ -15,14 +15,10 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use uuid::Uuid; -// macro_export puts it in the root, -// so we re-export here so you can use as either -// hyperware_process_lib::run_async -// or -// hyperware_process_lib::hyperapp::run_async -pub use crate::run_async; - thread_local! { + static SPAWN_QUEUE: RefCell>>>> = RefCell::new(Vec::new()); + + pub static APP_CONTEXT: RefCell = RefCell::new(AppContext { hidden_state: None, executor: Executor::new(), @@ -140,22 +136,39 @@ pub struct Executor { tasks: Vec>>>, } +pub fn spawn(fut: impl Future + 'static) { + SPAWN_QUEUE.with(|queue| { + queue.borrow_mut().push(Box::pin(fut)); + }) +} + + impl Executor { pub fn new() -> Self { Self { tasks: Vec::new() } } - pub fn spawn(&mut self, fut: impl Future + 'static) { - self.tasks.push(Box::pin(fut)); - } - pub fn poll_all_tasks(&mut self) { - let mut ctx = Context::from_waker(noop_waker_ref()); - let mut completed = Vec::new(); + loop { + SPAWN_QUEUE.with(|queue| { + self.tasks.append(&mut queue.borrow_mut()); + }); + + let mut ctx = Context::from_waker(noop_waker_ref()); + let mut completed = Vec::new(); + + for i in 0..self.tasks.len() { + if let Poll::Ready(()) = self.tasks[i].as_mut().poll(&mut ctx) { + completed.push(i); + } + } - for i in 0..self.tasks.len() { - if let Poll::Ready(()) = self.tasks[i].as_mut().poll(&mut ctx) { - completed.push(i); + // tasks can spawn more tasks + let should_break = SPAWN_QUEUE.with(|queue| { + queue.is_empty() + }); + if should_break { + break; } } @@ -282,17 +295,6 @@ where return Err(AppSendError::SendError(e)); } -#[macro_export] -macro_rules! run_async { - ($($code:tt)*) => { - hyperware_process_lib::hyperapp::APP_CONTEXT.with(|ctx| { - ctx.borrow_mut().executor.spawn(async move { - $($code)* - }) - }) - }; -} - // Enum defining the state persistance behaviour #[derive(Clone)] pub enum SaveOptions { From b3217db1f02b2e8581b3506f45d4c11866854e84 Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Fri, 29 Aug 2025 15:36:06 -0700 Subject: [PATCH 2/4] hyperapp: fix spawn bugs --- src/hyperapp.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hyperapp.rs b/src/hyperapp.rs index 88b00d9..9d9b93e 100644 --- a/src/hyperapp.rs +++ b/src/hyperapp.rs @@ -149,13 +149,14 @@ impl Executor { } pub fn poll_all_tasks(&mut self) { + let mut completed = Vec::new(); + loop { SPAWN_QUEUE.with(|queue| { self.tasks.append(&mut queue.borrow_mut()); }); let mut ctx = Context::from_waker(noop_waker_ref()); - let mut completed = Vec::new(); for i in 0..self.tasks.len() { if let Poll::Ready(()) = self.tasks[i].as_mut().poll(&mut ctx) { @@ -165,6 +166,7 @@ impl Executor { // tasks can spawn more tasks let should_break = SPAWN_QUEUE.with(|queue| { + let queue = queue.borrow(); queue.is_empty() }); if should_break { From a16d47a2bfae7864e97d70a3914829b4e54a4033 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 29 Aug 2025 22:36:39 +0000 Subject: [PATCH 3/4] Format Rust code using rustfmt --- src/hyperapp.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hyperapp.rs b/src/hyperapp.rs index 9d9b93e..86f2ebc 100644 --- a/src/hyperapp.rs +++ b/src/hyperapp.rs @@ -142,7 +142,6 @@ pub fn spawn(fut: impl Future + 'static) { }) } - impl Executor { pub fn new() -> Self { Self { tasks: Vec::new() } From b9f1ead63356bfd4b60b337a380fef1be81d81c6 Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Fri, 29 Aug 2025 20:37:53 -0700 Subject: [PATCH 4/4] hyperapp: fix polling --- src/hyperapp.rs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/hyperapp.rs b/src/hyperapp.rs index 86f2ebc..f0b8e88 100644 --- a/src/hyperapp.rs +++ b/src/hyperapp.rs @@ -148,13 +148,14 @@ impl Executor { } pub fn poll_all_tasks(&mut self) { - let mut completed = Vec::new(); - loop { + // Drain any newly spawned tasks into our task list SPAWN_QUEUE.with(|queue| { self.tasks.append(&mut queue.borrow_mut()); }); + // Poll all tasks, collecting completed ones + let mut completed = Vec::new(); let mut ctx = Context::from_waker(noop_waker_ref()); for i in 0..self.tasks.len() { @@ -163,18 +164,18 @@ impl Executor { } } - // tasks can spawn more tasks - let should_break = SPAWN_QUEUE.with(|queue| { - let queue = queue.borrow(); - queue.is_empty() - }); - if should_break { - break; + // Remove completed tasks immediately to prevent re-polling + for idx in completed.into_iter().rev() { + let _ = self.tasks.remove(idx); } - } - for idx in completed.into_iter().rev() { - let _ = self.tasks.remove(idx); + // Check if there are new tasks spawned during polling + let has_new_tasks = SPAWN_QUEUE.with(|queue| !queue.borrow().is_empty()); + + // Continue if new tasks were spawned, otherwise we're done + if !has_new_tasks { + break; + } } } }