mirror of
https://github.com/nekename/OpenDeck.git
synced 2026-10-28 09:44:01 +00:00
perf: replace debounced profile saving with a 30-second polling approach, retaining flushes for user changes (#402)
Co-authored-by: nekename <63245705+nekename@users.noreply.github.com>
This commit is contained in:
1 parent
9787e4ef3c
commit
0a0072a648
10 files changed
+76
-59
No files matched your search
@@ -1,7 +1,7 @@
|
||||
use super::Error;
|
||||
|
||||
use crate::shared::{Action, ActionContext, ActionInstance, ActionState, Context, config_dir};
|
||||
use crate::store::profiles::{LocksMut, acquire_locks, acquire_locks_mut, get_instance_mut, get_slot, get_slot_mut, save_profile};
|
||||
use crate::store::profiles::{LocksMut, acquire_locks, acquire_locks_mut, get_instance_mut, get_slot, get_slot_mut, save_profile_now};
|
||||
|
||||
use tauri::{AppHandle, Emitter, Manager, command};
|
||||
use tokio::fs::remove_dir_all;
|
||||
@@ -44,7 +44,7 @@ pub async fn create_instance(app: AppHandle, mut action: Action, context: Contex
|
||||
let _ = update_state(&app, parent.context.clone(), &mut locks).await;
|
||||
}
|
||||
|
||||
save_profile(&context.device, &mut locks).await?;
|
||||
save_profile_now(&context.device, &mut locks).await?;
|
||||
drop(locks);
|
||||
let _ = crate::events::outbound::will_appear::will_appear(&instance).await;
|
||||
|
||||
@@ -68,7 +68,7 @@ pub async fn create_instance(app: AppHandle, mut action: Action, context: Contex
|
||||
*slot = Some(instance.clone());
|
||||
let slot = slot.clone();
|
||||
|
||||
save_profile(&context.device, &mut locks).await?;
|
||||
save_profile_now(&context.device, &mut locks).await?;
|
||||
let _ = crate::events::outbound::will_appear::will_appear(&instance).await;
|
||||
|
||||
Ok(slot)
|
||||
@@ -146,7 +146,7 @@ pub async fn move_instance(source: Context, destination: Context, retain: bool)
|
||||
|
||||
let _ = crate::events::outbound::will_appear::will_appear(&new).await;
|
||||
|
||||
save_profile(&destination.device, &mut locks).await?;
|
||||
save_profile_now(&destination.device, &mut locks).await?;
|
||||
|
||||
Ok(Some(new))
|
||||
}
|
||||
@@ -190,7 +190,7 @@ pub async fn remove_instance(context: ActionContext) -> Result<(), Error> {
|
||||
}
|
||||
}
|
||||
|
||||
save_profile(&context.device, &mut locks).await?;
|
||||
save_profile_now(&context.device, &mut locks).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -219,7 +219,7 @@ pub async fn set_state(context: ActionContext, index: u16, state: ActionState) -
|
||||
let reference = get_instance_mut(&context, &mut locks).await?.unwrap();
|
||||
reference.states[index as usize] = state;
|
||||
let clone = reference.clone();
|
||||
save_profile(&context.device, &mut locks).await?;
|
||||
save_profile_now(&context.device, &mut locks).await?;
|
||||
crate::events::outbound::states::title_parameters_did_change(&clone, index).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::Error;
|
||||
|
||||
use crate::shared::DEVICES;
|
||||
use crate::store::profiles::{PROFILE_SAVE_DEBOUNCE, PROFILE_STORES, acquire_locks_mut, get_device_profiles, save_profile};
|
||||
use crate::store::profiles::{PROFILE_STORES, acquire_locks_mut, get_device_profiles, save_profile_now};
|
||||
|
||||
use tauri::{AppHandle, Emitter, Manager, command};
|
||||
|
||||
@@ -32,20 +32,8 @@ pub async fn set_selected_profile(device: String, id: String) -> Result<(), Erro
|
||||
}
|
||||
|
||||
// If a profile save is pending for this device, save it immediately to prevent losing profile data
|
||||
let entries = PROFILE_SAVE_DEBOUNCE
|
||||
.iter()
|
||||
.filter(|entry| entry.key().device == device)
|
||||
.map(|entry| entry.key().clone())
|
||||
.collect::<Vec<_>>();
|
||||
if !entries.is_empty() {
|
||||
for context in &entries {
|
||||
if let Some((_, handle)) = PROFILE_SAVE_DEBOUNCE.remove(context) {
|
||||
handle.abort();
|
||||
}
|
||||
}
|
||||
if let Err(error) = save_profile(&device, &mut locks).await {
|
||||
log::error!("Failed to save profile for device {device}: {error}");
|
||||
}
|
||||
if let Err(error) = save_profile_now(&device, &mut locks).await {
|
||||
log::error!("Failed to save profile for device {device}: {error}");
|
||||
}
|
||||
|
||||
let selected_profile = locks.device_stores.get_selected_profile(&device)?;
|
||||
|
||||
@@ -71,6 +71,11 @@ pub async fn deregister_device(uuid: &str, event: PayloadEvent<String>) -> Resul
|
||||
let _ = crate::events::outbound::will_appear::will_disappear(instance, false).await;
|
||||
}
|
||||
|
||||
// Flush any pending profile writes before removing the device.
|
||||
if let Err(error) = crate::store::profiles::save_profile_now(&event.payload, &mut locks).await {
|
||||
log::error!("Failed to flush profile for device {}: {error}", event.payload);
|
||||
}
|
||||
|
||||
if let Ok(profiles) = get_device_profiles(&event.payload) {
|
||||
for profile in profiles {
|
||||
locks.profile_stores.remove_profile(&event.payload, &profile);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::events::outbound::settings as outbound;
|
||||
use crate::shared::ActionContext;
|
||||
use crate::store::profiles::{acquire_locks, acquire_locks_mut, get_instance, get_instance_mut, save_profile};
|
||||
use crate::store::profiles::{acquire_locks, acquire_locks_mut, get_instance, get_instance_mut, mark_profile_stale};
|
||||
|
||||
use std::io::Write;
|
||||
use std::str::FromStr;
|
||||
@@ -11,7 +11,7 @@ pub async fn set_settings(event: super::ContextAndPayloadEvent<serde_json::Value
|
||||
if let Some(instance) = get_instance_mut(&event.context, &mut locks).await? {
|
||||
instance.settings = event.payload;
|
||||
outbound::did_receive_settings(instance, !from_property_inspector).await?;
|
||||
save_profile(&event.context.device, &mut locks).await?;
|
||||
mark_profile_stale(&event.context.device, &mut locks).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::ContextAndPayloadEvent;
|
||||
|
||||
use crate::events::frontend::instances::update_state;
|
||||
use crate::store::profiles::{acquire_locks_mut, debounce_profile_save, get_instance_mut, save_profile};
|
||||
use crate::store::profiles::{acquire_locks_mut, get_instance_mut, mark_profile_stale};
|
||||
|
||||
use anyhow::bail;
|
||||
use serde::Deserialize;
|
||||
@@ -59,7 +59,7 @@ pub async fn set_title(event: ContextAndPayloadEvent<SetTitlePayload>) -> Result
|
||||
}
|
||||
update_state(crate::APP_HANDLE.get().unwrap(), instance.context.clone(), &mut locks).await?;
|
||||
}
|
||||
save_profile(&event.context.device, &mut locks).await?;
|
||||
mark_profile_stale(&event.context.device, &mut locks).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -97,14 +97,7 @@ pub async fn set_image(mut event: ContextAndPayloadEvent<SetImagePayload>) -> Re
|
||||
update_state(crate::APP_HANDLE.get().unwrap(), instance.context.clone(), &mut locks).await?;
|
||||
}
|
||||
|
||||
if let Some(image) = &event.payload.image
|
||||
&& image.trim().starts_with("data:")
|
||||
{
|
||||
debounce_profile_save(event.context);
|
||||
} else {
|
||||
save_profile(&event.context.device, &mut locks).await?;
|
||||
}
|
||||
|
||||
mark_profile_stale(&event.context.device, &mut locks).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -148,7 +141,7 @@ pub async fn set_state(event: ContextAndPayloadEvent<SetStatePayload>) -> Result
|
||||
instance.current_state = event.payload.state;
|
||||
update_state(crate::APP_HANDLE.get().unwrap(), instance.context.clone(), &mut locks).await?;
|
||||
}
|
||||
save_profile(&event.context.device, &mut locks).await?;
|
||||
mark_profile_stale(&event.context.device, &mut locks).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -2,7 +2,7 @@ use super::{GenericInstancePayload, send_to_plugin};
|
||||
|
||||
use crate::events::frontend::instances::{key_moved, update_state};
|
||||
use crate::shared::{ActionContext, Context};
|
||||
use crate::store::profiles::{acquire_locks_mut, get_slot_mut, save_profile};
|
||||
use crate::store::profiles::{acquire_locks_mut, get_slot_mut, mark_profile_stale};
|
||||
|
||||
use std::sync::LazyLock;
|
||||
use std::time::Duration;
|
||||
@@ -75,7 +75,7 @@ pub async fn key_down(device: &str, key: u8) -> Result<(), anyhow::Error> {
|
||||
let _ = update_state(crate::APP_HANDLE.get().unwrap(), child, &mut locks).await;
|
||||
}
|
||||
|
||||
save_profile(device, &mut locks).await?;
|
||||
mark_profile_stale(device, &mut locks).await?;
|
||||
} else if instance.action.uuid == "opendeck.toggleaction" {
|
||||
let children = instance.children.as_ref().unwrap();
|
||||
if children.is_empty() {
|
||||
@@ -168,7 +168,7 @@ pub async fn key_up(device: &str, key: u8) -> Result<(), anyhow::Error> {
|
||||
};
|
||||
|
||||
let _ = update_state(crate::APP_HANDLE.get().unwrap(), instance.context.clone(), &mut locks).await;
|
||||
save_profile(device, &mut locks).await?;
|
||||
mark_profile_stale(device, &mut locks).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -20,6 +20,7 @@ use events::frontend;
|
||||
use shared::PRODUCT_NAME;
|
||||
|
||||
use std::sync::OnceLock;
|
||||
use std::time::Duration;
|
||||
|
||||
use tauri::{
|
||||
AppHandle, Builder, Manager, WindowEvent,
|
||||
@@ -29,6 +30,7 @@ use tauri::{
|
||||
use tauri_plugin_log::{Target, TargetKind};
|
||||
|
||||
static APP_HANDLE: OnceLock<AppHandle> = OnceLock::new();
|
||||
const SAVE_PROBE: Duration = Duration::from_secs(30);
|
||||
|
||||
fn show_window(app: &AppHandle) -> Result<(), tauri::Error> {
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -196,6 +198,16 @@ If you have already donated, thank you so much for your support!"#,
|
||||
tokio::time::sleep(std::time::Duration::from_secs(10)).await;
|
||||
}
|
||||
});
|
||||
|
||||
tokio::spawn(async {
|
||||
loop {
|
||||
tokio::time::sleep(SAVE_PROBE).await;
|
||||
if let Err(error) = store::profiles::flush_stale_profiles().await {
|
||||
log::error!("Failed to flush stale profiles: {error}");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
plugins::initialise_plugins();
|
||||
application_watcher::init_application_watcher();
|
||||
device_sleep::init_device_sleep();
|
||||
@@ -380,6 +392,13 @@ If you have already donated, thank you so much for your support!"#,
|
||||
if let tauri::RunEvent::Exit = event {
|
||||
#[cfg(windows)]
|
||||
futures::executor::block_on(plugins::deactivate_plugins());
|
||||
|
||||
let flush_future = store::profiles::flush_stale_profiles();
|
||||
match tauri::async_runtime::block_on(flush_future) {
|
||||
Ok(_) => log::info!("Successfully flushed all stale profiles on exit"),
|
||||
Err(error) => log::error!("Failed to flush stale profiles on exit: {error}"),
|
||||
}
|
||||
|
||||
tokio::spawn(elgato::reset_devices());
|
||||
use tauri_plugin_aptabase::EventTracker;
|
||||
app.flush_events_blocking();
|
||||
|
||||
@@ -439,6 +439,9 @@ pub struct Profile {
|
||||
pub sliders: Vec<Option<ActionInstance>>,
|
||||
#[serde(default)]
|
||||
pub infobars: Vec<Option<ActionInstance>>,
|
||||
|
||||
#[serde(skip)]
|
||||
pub stale: bool,
|
||||
}
|
||||
|
||||
/// A map of category names to a list of actions in that category.
|
||||
|
||||
@@ -8,10 +8,8 @@ use std::path::PathBuf;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use dashmap::DashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
pub struct ProfileStores {
|
||||
stores: HashMap<String, Store<Profile>>,
|
||||
@@ -40,6 +38,8 @@ impl ProfileStores {
|
||||
keys: Vec::new(),
|
||||
sliders: Vec::new(),
|
||||
infobars: Vec::new(),
|
||||
|
||||
stale: false,
|
||||
};
|
||||
|
||||
let mut store = Store::new(&canonical_id, &config_dir().join("profiles"), default).context(format!("Failed to create store for profile {}", canonical_id))?;
|
||||
@@ -369,27 +369,34 @@ pub async fn get_instance_mut<'a>(context: &crate::shared::ActionContext, locks:
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub async fn save_profile(device: &str, locks: &mut LocksMut<'_>) -> Result<(), anyhow::Error> {
|
||||
let selected_profile = locks.device_stores.get_selected_profile(device)?;
|
||||
let device = DEVICES.get(device).ok_or_else(|| anyhow!("device not found"))?;
|
||||
let store = locks.profile_stores.get_profile_store(&device, &selected_profile)?;
|
||||
store.save()
|
||||
pub async fn mark_profile_stale(device_id: &str, locks: &mut LocksMut<'_>) -> Result<(), anyhow::Error> {
|
||||
let selected_profile = locks.device_stores.get_selected_profile(device_id)?;
|
||||
let device = DEVICES.get(device_id).ok_or_else(|| anyhow!("device not found"))?;
|
||||
let store = locks.profile_stores.get_profile_store_mut(&device, &selected_profile).await?;
|
||||
store.value.stale = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub static PROFILE_SAVE_DEBOUNCE: LazyLock<DashMap<crate::shared::ActionContext, JoinHandle<()>>> = LazyLock::new(DashMap::new);
|
||||
pub fn debounce_profile_save(context: crate::shared::ActionContext) {
|
||||
if let Some((_, handle)) = PROFILE_SAVE_DEBOUNCE.remove(&context) {
|
||||
handle.abort();
|
||||
pub async fn save_profile_now(device_id: &str, locks: &mut LocksMut<'_>) -> Result<(), anyhow::Error> {
|
||||
let selected_profile = locks.device_stores.get_selected_profile(device_id)?;
|
||||
let device = DEVICES.get(device_id).ok_or_else(|| anyhow!("device not found"))?;
|
||||
let store = locks.profile_stores.get_profile_store_mut(&device, &selected_profile).await?;
|
||||
|
||||
if store.value.stale {
|
||||
store.save()?;
|
||||
store.value.stale = false;
|
||||
}
|
||||
PROFILE_SAVE_DEBOUNCE.insert(
|
||||
context.clone(),
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
|
||||
let mut locks = acquire_locks_mut().await;
|
||||
if let Err(error) = save_profile(&context.device, &mut locks).await {
|
||||
log::error!("Failed to save profile for device {}: {error}", context.device);
|
||||
}
|
||||
PROFILE_SAVE_DEBOUNCE.remove(&context);
|
||||
}),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn flush_stale_profiles() -> Result<(), anyhow::Error> {
|
||||
let mut locks = acquire_locks_mut().await;
|
||||
for store in locks.profile_stores.stores.values_mut() {
|
||||
if store.value.stale {
|
||||
store.save()?;
|
||||
store.value.stale = false;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -216,6 +216,8 @@ impl DiskProfile {
|
||||
keys: self.keys.into_iter().map(|x| x.map(|v| v.into_action_instance(path))).collect(),
|
||||
sliders: self.sliders.into_iter().map(|x| x.map(|v| v.into_action_instance(path))).collect(),
|
||||
infobars: self.infobars.into_iter().map(|x| x.map(|v| v.into_action_instance(path))).collect(),
|
||||
|
||||
stale: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user