From c206c6e5a7df9aa5bc0cfb70a27af58b41d35ca9 Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Fri, 27 Jun 2025 05:05:36 -0700 Subject: [PATCH 01/16] persist OnDiff (i.e. only when state changes) --- hyperprocess_macro/src/lib.rs | 14 ++++++++- hyperware_app_common/src/lib.rs | 51 ++++++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/hyperprocess_macro/src/lib.rs b/hyperprocess_macro/src/lib.rs index 29061f7..1798762 100644 --- a/hyperprocess_macro/src/lib.rs +++ b/hyperprocess_macro/src/lib.rs @@ -1609,7 +1609,7 @@ fn generate_component_impl( // Extract values from args for use in the quote macro let name = &args.name; let endpoints = &args.endpoints; - let _save_config = &args.save_config; + let save_config = &args.save_config; let wit_world = &args.wit_world; let icon = match &args.icon { @@ -1633,6 +1633,8 @@ fn generate_component_impl( None => quote! { None }, }; + let save_config = &args.save_config; + let init_method_ident = &init_method_details.identifier; let init_method_call = &init_method_details.call; let ws_method_call = &ws_method_details.call; @@ -1683,6 +1685,11 @@ fn generate_component_impl( // Initialize our state let mut state = hyperware_app_common::initialize_state::<#self_ty>(); + // Set to persist state according to user setting + hyperware_app_common::APP_CONTEXT.with(|ctx| { + ctx.borrow_mut().hidden_state = Some(hyperware_app_common::HiddenState::new(save_config)); + }); + // Set up necessary components let app_name = #name; let app_icon = #icon; @@ -1719,6 +1726,11 @@ fn generate_component_impl( hyperware_app_common::APP_HELPERS.with(|ctx| { ctx.borrow_mut().current_message = Some(message.clone()); }); + + // Store old state if needed (for OnDiff save option) + // This only stores if old_state is None (first time or after a save) + hyperware_app_common::store_old_state(&state); + match message { hyperware_process_lib::Message::Response { body, context, .. } => { let correlation_id = context diff --git a/hyperware_app_common/src/lib.rs b/hyperware_app_common/src/lib.rs index abab84b..a9b894f 100644 --- a/hyperware_app_common/src/lib.rs +++ b/hyperware_app_common/src/lib.rs @@ -258,10 +258,13 @@ pub enum SaveOptions { EveryNMessage(u64), // Persist State Every N Seconds EveryNSeconds(u64), + // Persist State Only If Changed + OnDiff, } pub struct HiddenState { save_config: SaveOptions, message_count: u64, + old_state: Option>, // Stores the serialized state from before message processing } impl HiddenState { @@ -269,6 +272,7 @@ impl HiddenState { Self { save_config, message_count: 0, + old_state: None, } } @@ -286,12 +290,32 @@ impl HiddenState { } } SaveOptions::EveryNSeconds(_) => false, // Handled by timer instead + SaveOptions::OnDiff => false, // Will be handled separately with state comparison } } } // TODO: We need a timer macro again. +/// Store a snapshot of the current state before processing a message +/// This is used for OnDiff save option to compare state before and after +/// Only stores if old_state is None (i.e., first time or after a save) +pub fn store_old_state(state: &S) +where + S: serde::Serialize, +{ + APP_CONTEXT.with(|ctx| { + let mut ctx_mut = ctx.borrow_mut(); + if let Some(ref mut hidden_state) = ctx_mut.hidden_state { + if matches!(hidden_state.save_config, SaveOptions::OnDiff) && hidden_state.old_state.is_none() { + if let Ok(s_bytes) = rmp_serde::to_vec(state) { + hidden_state.old_state = Some(s_bytes); + } + } + } + }); +} + /// Trait that must be implemented by application state types pub trait State { /// Creates a new instance of the state. @@ -439,10 +463,35 @@ where APP_CONTEXT.with(|ctx| { let mut ctx_mut = ctx.borrow_mut(); if let Some(ref mut hidden_state) = ctx_mut.hidden_state { - if hidden_state.should_save_state() { + let should_save = if matches!(hidden_state.save_config, SaveOptions::OnDiff) { + // For OnDiff, compare current state with old state + if let Ok(current_bytes) = rmp_serde::to_vec(state) { + let state_changed = match &hidden_state.old_state { + Some(old_bytes) => old_bytes != ¤t_bytes, + None => true, // If no old state, consider it changed + }; + + if state_changed { + true + } else { + false + } + } else { + false + } + } else { + hidden_state.should_save_state() + }; + + if should_save { if let Ok(s_bytes) = rmp_serde::to_vec(state) { kiprintln!("State persisted"); let _ = set_state(&s_bytes); + + // Clear old_state after saving so it can be set again on next message + if matches!(hidden_state.save_config, SaveOptions::OnDiff) { + hidden_state.old_state = None; + } } } } From cf3069528be87e08a9e1c6f4f89e78176b064602 Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Fri, 27 Jun 2025 08:12:29 -0700 Subject: [PATCH 02/16] fix it --- hyperprocess_macro/src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/hyperprocess_macro/src/lib.rs b/hyperprocess_macro/src/lib.rs index 1798762..5a679bc 100644 --- a/hyperprocess_macro/src/lib.rs +++ b/hyperprocess_macro/src/lib.rs @@ -1633,8 +1633,6 @@ fn generate_component_impl( None => quote! { None }, }; - let save_config = &args.save_config; - let init_method_ident = &init_method_details.identifier; let init_method_call = &init_method_details.call; let ws_method_call = &ws_method_details.call; @@ -1687,7 +1685,7 @@ fn generate_component_impl( // Set to persist state according to user setting hyperware_app_common::APP_CONTEXT.with(|ctx| { - ctx.borrow_mut().hidden_state = Some(hyperware_app_common::HiddenState::new(save_config)); + ctx.borrow_mut().hidden_state = Some(hyperware_app_common::HiddenState::new(#save_config)); }); // Set up necessary components From 1cdbf5402c3613e8371f08d782a0bdedbad444fb Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Fri, 27 Jun 2025 10:09:15 -0700 Subject: [PATCH 03/16] use new process_lib --- hyperprocess_macro/Cargo.toml | 3 +-- hyperware_app_common/Cargo.toml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/hyperprocess_macro/Cargo.toml b/hyperprocess_macro/Cargo.toml index 74f4b76..ca00597 100644 --- a/hyperprocess_macro/Cargo.toml +++ b/hyperprocess_macro/Cargo.toml @@ -14,8 +14,7 @@ syn = { version = "2.0", features = ["full"] } anyhow = "1.0" futures-util = "0.3" hyperware_app_common = { path = "../hyperware_app_common" } -#hyperware_process_lib = { version = "1.0.4", features = ["logging"] } -hyperware_process_lib = { git = "https://github.com/hyperware-ai/process_lib", features = ["logging"], rev = "b7c9d27" } +hyperware_process_lib = { git = "https://github.com/hyperware-ai/process_lib", features = ["logging"], rev = "cfd6843" } once_cell = "1.20.2" paste = "1.0" process_macros = "0.1.0" diff --git a/hyperware_app_common/Cargo.toml b/hyperware_app_common/Cargo.toml index b2c2ba4..ecaaa8c 100644 --- a/hyperware_app_common/Cargo.toml +++ b/hyperware_app_common/Cargo.toml @@ -6,8 +6,7 @@ publish = false [dependencies] anyhow = "1.0" -#hyperware_process_lib = { version = "1.0.4", features = ["logging"] } -hyperware_process_lib = { git = "https://github.com/hyperware-ai/process_lib", features = ["logging"], rev = "b7c9d27" } +hyperware_process_lib = { git = "https://github.com/hyperware-ai/process_lib", features = ["logging"], rev = "cfd6843" } futures-util = "0.3" once_cell = "1.20.2" paste = "1.0" From 4a45e2ae304f6e51ce71a80ea92634f6c5502772 Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Fri, 27 Jun 2025 10:19:03 -0700 Subject: [PATCH 04/16] remove more path/method re-setting --- hyperprocess_macro/src/lib.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/hyperprocess_macro/src/lib.rs b/hyperprocess_macro/src/lib.rs index 5a679bc..d5366e5 100644 --- a/hyperprocess_macro/src/lib.rs +++ b/hyperprocess_macro/src/lib.rs @@ -1309,18 +1309,8 @@ fn generate_message_handlers( let handler_body = if handler.is_async { quote! { // Capture context values before async execution - let current_path = hyperware_app_common::get_path(); - let current_method = hyperware_app_common::get_http_method(); - let state_ptr: *mut #self_ty = state; hyperware_app_common::hyper! { - // Restore context in the async task - hyperware_app_common::APP_HELPERS.with(|ctx| { - let mut ctx_mut = ctx.borrow_mut(); - ctx_mut.current_path = current_path; - ctx_mut.current_http_method = current_method; - }); - let result = unsafe { (*state_ptr).#fn_name().await }; #response_handling } From 7a645c560ff959c634e7d7f5fa2d8c04443a2a55 Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Fri, 27 Jun 2025 10:23:58 -0700 Subject: [PATCH 05/16] remove un-setting current_path --- hyperprocess_macro/src/lib.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/hyperprocess_macro/src/lib.rs b/hyperprocess_macro/src/lib.rs index d5366e5..63ec9bc 100644 --- a/hyperprocess_macro/src/lib.rs +++ b/hyperprocess_macro/src/lib.rs @@ -1252,10 +1252,6 @@ fn generate_message_handlers( format!("Handler {} requires a request body", stringify!(#fn_name)).into_bytes() ); } - - hyperware_app_common::APP_HELPERS.with(|ctx| { - ctx.borrow_mut().current_path = None; - }); return; } } @@ -1330,9 +1326,6 @@ fn generate_message_handlers( if #path_check && #method_check { hyperware_process_lib::logging::debug!("Matched parameter-less handler {} for {} {}", stringify!(#fn_name), http_method, current_path); #handler_body - hyperware_app_common::APP_HELPERS.with(|ctx| { - ctx.borrow_mut().current_path = None; - }); return; } } @@ -1408,9 +1401,6 @@ fn generate_message_handlers( #http_request_match_arms hyperware_app_common::maybe_save_state(&mut *state); } - hyperware_app_common::APP_HELPERS.with(|ctx| { - ctx.borrow_mut().current_path = None; - }); return; }, Err(e) => { @@ -1436,9 +1426,6 @@ fn generate_message_handlers( None, error_details.into_bytes() ); - hyperware_app_common::APP_HELPERS.with(|ctx| { - ctx.borrow_mut().current_path = None; - }); return; } } @@ -1456,9 +1443,6 @@ fn generate_message_handlers( None, format!("No handler found for {} {}", http_method, current_path).into_bytes(), ); - hyperware_app_common::APP_HELPERS.with(|ctx| { - ctx.borrow_mut().current_path = None; - }); }, hyperware_process_lib::http::server::HttpServerRequest::WebSocketPush { channel_id, message_type } => { hyperware_process_lib::logging::debug!("Received WebSocket message on channel {}, type: {:?}", channel_id, message_type); From d5497f3819b2b1f2a601e225298c9b6c5896e118 Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Fri, 27 Jun 2025 10:41:51 -0700 Subject: [PATCH 06/16] set http-related context to None after done handling message --- hyperprocess_macro/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hyperprocess_macro/src/lib.rs b/hyperprocess_macro/src/lib.rs index 63ec9bc..924913c 100644 --- a/hyperprocess_macro/src/lib.rs +++ b/hyperprocess_macro/src/lib.rs @@ -1718,6 +1718,12 @@ fn generate_component_impl( hyperware_process_lib::Message::Request { .. } => { if message.is_local() && message.source().process == "http-server:distro:sys" { handle_http_server_message(&mut state, message); + hyperware_app_common::APP_HELPERS.with(|ctx| { + let ctx_mut = ctx.borrow_mut(); + ctx_mut.current_path = None; + ctx_mut.current_http_method = None; + ctx_mut.response_headers = HashMap::new(); + }); } else if message.is_local() { handle_local_message(&mut state, message); } else { From c7e9e2b20bc91b1ab122c9a30dfdbd36a7fec218 Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Fri, 27 Jun 2025 10:43:40 -0700 Subject: [PATCH 07/16] fix HashMap reference --- hyperprocess_macro/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hyperprocess_macro/src/lib.rs b/hyperprocess_macro/src/lib.rs index 924913c..862cede 100644 --- a/hyperprocess_macro/src/lib.rs +++ b/hyperprocess_macro/src/lib.rs @@ -1722,7 +1722,7 @@ fn generate_component_impl( let ctx_mut = ctx.borrow_mut(); ctx_mut.current_path = None; ctx_mut.current_http_method = None; - ctx_mut.response_headers = HashMap::new(); + ctx_mut.response_headers = std::collections::HashMap::new(); }); } else if message.is_local() { handle_local_message(&mut state, message); From 0b9ec3223b8c9f101b803c0ba6011d456bcb476e Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Fri, 27 Jun 2025 10:44:27 -0700 Subject: [PATCH 08/16] borrow as mut --- hyperprocess_macro/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hyperprocess_macro/src/lib.rs b/hyperprocess_macro/src/lib.rs index 862cede..4201629 100644 --- a/hyperprocess_macro/src/lib.rs +++ b/hyperprocess_macro/src/lib.rs @@ -1719,7 +1719,7 @@ fn generate_component_impl( if message.is_local() && message.source().process == "http-server:distro:sys" { handle_http_server_message(&mut state, message); hyperware_app_common::APP_HELPERS.with(|ctx| { - let ctx_mut = ctx.borrow_mut(); + let mut ctx_mut = ctx.borrow_mut(); ctx_mut.current_path = None; ctx_mut.current_http_method = None; ctx_mut.response_headers = std::collections::HashMap::new(); From 724182e312045c481f9a42df2a925215e94dd916 Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Mon, 30 Jun 2025 07:12:22 -0700 Subject: [PATCH 09/16] remove a comment --- hyperprocess_macro/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/hyperprocess_macro/src/lib.rs b/hyperprocess_macro/src/lib.rs index 4201629..6164c03 100644 --- a/hyperprocess_macro/src/lib.rs +++ b/hyperprocess_macro/src/lib.rs @@ -1304,7 +1304,6 @@ fn generate_message_handlers( let handler_body = if handler.is_async { quote! { - // Capture context values before async execution let state_ptr: *mut #self_ty = state; hyperware_app_common::hyper! { let result = unsafe { (*state_ptr).#fn_name().await }; From cdd0e82c1969ff7d49f4c032544a06c4ce22a1b6 Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Mon, 30 Jun 2025 08:24:18 -0700 Subject: [PATCH 10/16] add sleep() --- hyperware_app_common/src/lib.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/hyperware_app_common/src/lib.rs b/hyperware_app_common/src/lib.rs index a9b894f..c098dcd 100644 --- a/hyperware_app_common/src/lib.rs +++ b/hyperware_app_common/src/lib.rs @@ -9,7 +9,7 @@ use futures_util::task::noop_waker_ref; use hyperware_process_lib::http::server::HttpServer; use hyperware_process_lib::logging::info; use hyperware_process_lib::{ - get_state, http, kiprintln, set_state, BuildError, LazyLoadBlob, Message, Request, SendError, + get_state, http, kiprintln, set_state, timer, BuildError, LazyLoadBlob, Message, Request, SendError, }; use serde::Deserialize; use serde::Serialize; @@ -186,6 +186,14 @@ pub enum AppSendError { BuildError(BuildError), } +pub async fn sleep(sleep_ms: u64) -> Result<(), AppSendError> { + let timer_request = Request::to("our@timer:distro:sys") + .body(timer::TimerAction(sleep_ms)) + .expects_response((sleep_ms / 1_000) + 1); + + send(timer_request) +} + pub async fn send(request: Request) -> Result where R: serde::de::DeserializeOwned, From 8d314c7ba06297cf6a0c60e73b516aee46aa0a06 Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Mon, 30 Jun 2025 08:26:16 -0700 Subject: [PATCH 11/16] await the send in sleep --- hyperware_app_common/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hyperware_app_common/src/lib.rs b/hyperware_app_common/src/lib.rs index c098dcd..19ac1f7 100644 --- a/hyperware_app_common/src/lib.rs +++ b/hyperware_app_common/src/lib.rs @@ -191,7 +191,7 @@ pub async fn sleep(sleep_ms: u64) -> Result<(), AppSendError> { .body(timer::TimerAction(sleep_ms)) .expects_response((sleep_ms / 1_000) + 1); - send(timer_request) + send(timer_request).await } pub async fn send(request: Request) -> Result From 2f0c914222304a20d165c3e1685bf29ccdc42064 Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Mon, 30 Jun 2025 08:28:46 -0700 Subject: [PATCH 12/16] fix action & target --- hyperware_app_common/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hyperware_app_common/src/lib.rs b/hyperware_app_common/src/lib.rs index 19ac1f7..08a59b7 100644 --- a/hyperware_app_common/src/lib.rs +++ b/hyperware_app_common/src/lib.rs @@ -187,8 +187,8 @@ pub enum AppSendError { } pub async fn sleep(sleep_ms: u64) -> Result<(), AppSendError> { - let timer_request = Request::to("our@timer:distro:sys") - .body(timer::TimerAction(sleep_ms)) + let timer_request = Request::to(("our", "timer", "distro", "sys")) + .body(timer::TimerAction::SetTimer(sleep_ms)) .expects_response((sleep_ms / 1_000) + 1); send(timer_request).await From 67a351208fc9e08a1af020eb66ed2bb455a3dd09 Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Mon, 30 Jun 2025 08:54:36 -0700 Subject: [PATCH 13/16] sleep: fix return type --- hyperware_app_common/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hyperware_app_common/src/lib.rs b/hyperware_app_common/src/lib.rs index 08a59b7..157d94e 100644 --- a/hyperware_app_common/src/lib.rs +++ b/hyperware_app_common/src/lib.rs @@ -191,7 +191,9 @@ pub async fn sleep(sleep_ms: u64) -> Result<(), AppSendError> { .body(timer::TimerAction::SetTimer(sleep_ms)) .expects_response((sleep_ms / 1_000) + 1); - send(timer_request).await + send(timer_request).await?; + + return Ok(()); } pub async fn send(request: Request) -> Result From 2f1af9e9ddfc0dac782998b0babc09dd4c74eebe Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Mon, 30 Jun 2025 09:00:48 -0700 Subject: [PATCH 14/16] try to fix deserialization --- hyperware_app_common/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hyperware_app_common/src/lib.rs b/hyperware_app_common/src/lib.rs index 157d94e..b97953c 100644 --- a/hyperware_app_common/src/lib.rs +++ b/hyperware_app_common/src/lib.rs @@ -191,7 +191,7 @@ pub async fn sleep(sleep_ms: u64) -> Result<(), AppSendError> { .body(timer::TimerAction::SetTimer(sleep_ms)) .expects_response((sleep_ms / 1_000) + 1); - send(timer_request).await?; + let _: Vec = send(timer_request).await?; return Ok(()); } From 1edd7dcbdf14243e7685390b688b1f5133207301 Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Mon, 30 Jun 2025 09:08:28 -0700 Subject: [PATCH 15/16] do repeat yourself --- hyperware_app_common/src/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/hyperware_app_common/src/lib.rs b/hyperware_app_common/src/lib.rs index b97953c..5df1e1f 100644 --- a/hyperware_app_common/src/lib.rs +++ b/hyperware_app_common/src/lib.rs @@ -191,7 +191,12 @@ pub async fn sleep(sleep_ms: u64) -> Result<(), AppSendError> { .body(timer::TimerAction::SetTimer(sleep_ms)) .expects_response((sleep_ms / 1_000) + 1); - let _: Vec = send(timer_request).await?; + let correlation_id = Uuid::new_v4().to_string(); + if let Err(e) = request.context(correlation_id.as_bytes().to_vec()).send() { + return Err(AppSendError::BuildError(e)); + } + + let _ = ResponseFuture::new(correlation_id).await; return Ok(()); } From 7fc30e01caece399b61581c1c92f3e0e3b4b9e13 Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Mon, 30 Jun 2025 09:11:27 -0700 Subject: [PATCH 16/16] fix typo --- hyperware_app_common/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hyperware_app_common/src/lib.rs b/hyperware_app_common/src/lib.rs index 5df1e1f..4f81abf 100644 --- a/hyperware_app_common/src/lib.rs +++ b/hyperware_app_common/src/lib.rs @@ -187,7 +187,7 @@ pub enum AppSendError { } pub async fn sleep(sleep_ms: u64) -> Result<(), AppSendError> { - let timer_request = Request::to(("our", "timer", "distro", "sys")) + let request = Request::to(("our", "timer", "distro", "sys")) .body(timer::TimerAction::SetTimer(sleep_ms)) .expects_response((sleep_ms / 1_000) + 1);