Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions crates/deadsync-config/src/dirs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ impl AppDirs {
self.data_dir.join("save").join("profiles")
}

/// Machine-global saved pad configs. Pad thresholds describe the physical
/// pads wired to this machine, so they live beside the other machine-level
/// save data rather than inside any player profile.
#[must_use]
pub fn pad_config_path(&self) -> PathBuf {
self.data_dir.join("save").join("padconfig.ini")
}

#[must_use]
pub fn screenshots_dir(&self) -> PathBuf {
self.data_dir.join("save").join("screenshots")
Expand Down
132 changes: 84 additions & 48 deletions crates/deadsync-profile/src/app_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,22 @@ use crate::{
struct ProfilePaths {
root: PathBuf,
defaults: PathBuf,
pad_config: PathBuf,
}
static PATHS: std::sync::OnceLock<ProfilePaths> = std::sync::OnceLock::new();

/// Install profile and default-option paths before profile loading or worker startup.
pub fn init_paths(root: PathBuf, defaults: PathBuf) -> Result<(), &'static str> {
pub fn init_paths(
root: PathBuf,
defaults: PathBuf,
pad_config: PathBuf,
) -> Result<(), &'static str> {
PATHS
.set(ProfilePaths { root, defaults })
.set(ProfilePaths {
root,
defaults,
pad_config,
})
.map_err(|_| "profile paths already initialized")
}

Expand Down Expand Up @@ -1677,79 +1686,102 @@ pub fn delete_local_profile_from_config(id: &str) -> Result<(), std::io::Error>
)
}

pub fn load_pad_configs(profile_id: &str) -> Vec<PadConfigProfile> {
pad_config::load_profile_id(&profiles_root(), profile_id, warn_duplicate_profile_guid)
/// Machine-global pad config store (`save/padconfig.ini` under the data dir).
pub fn machine_pad_config_path() -> PathBuf {
PATHS
.get()
.expect("profile paths initialized at startup")
.pad_config
.clone()
}

pub fn save_pad_configs(profile_id: &str, profiles: &[PadConfigProfile]) {
if let Err(error) = pad_config::save_profile_id_report(
&profiles_root(),
profile_id,
profiles,
warn_duplicate_profile_guid,
) {
warn!("Failed to save {}: {}", error.path.display(), error.error);
/// Merge legacy per-profile pad configs into the machine store. Called at
/// startup and again by every pad-config accessor (cheap once done), so the
/// store is ready no matter which path touches it first. The machine file's
/// existence marks the migration done; a failed write (e.g. disk full) leaves
/// the flag unset so a later call retries instead of permanently orphaning
/// the legacy configs.
pub fn migrate_pad_configs() {
use std::sync::atomic::{AtomicBool, Ordering};
static MIGRATED: AtomicBool = AtomicBool::new(false);
if MIGRATED.load(Ordering::Acquire) {
return;
}
let path = machine_pad_config_path();
match pad_config::migrate_machine_store(&path, &profiles_root()) {
Ok(migrated) => {
if let Some(count) = migrated
&& count > 0
{
info!(
"Migrated {count} pad config(s) from per-profile files into '{}'.",
path.display()
);
}
MIGRATED.store(true, Ordering::Release);
}
Err(error) => warn!(
"Failed to migrate pad configs into '{}': {error}",
path.display()
),
}
}

fn warn_pad_config_save(result: std::io::Result<bool>) {
if let Err(error) = result {
warn!(
"Failed to save {}: {error}",
machine_pad_config_path().display()
);
}
}

pub fn load_pad_configs() -> Vec<PadConfigProfile> {
migrate_pad_configs();
pad_config::load_path(&machine_pad_config_path()).unwrap_or_default()
}

#[allow(clippy::too_many_arguments)]
pub fn upsert_pad_config(
profile_id: &str,
name: &str,
backend: &str,
pad_type: Option<String>,
serial: Option<String>,
make_default: bool,
settings: Vec<(String, String)>,
) {
if let Err(error) = pad_config::upsert_profile_id_report(
&profiles_root(),
profile_id,
migrate_pad_configs();
warn_pad_config_save(pad_config::upsert_path(
&machine_pad_config_path(),
name,
backend,
pad_type,
serial,
make_default,
settings,
warn_duplicate_profile_guid,
) {
warn!("Failed to save {}: {}", error.path.display(), error.error);
}
));
}

pub fn set_default_pad_config(profile_id: &str, serial: &str, name: &str) {
if let Err(error) = pad_config::set_default_profile_id_report(
&profiles_root(),
profile_id,
pub fn set_default_pad_config(serial: &str, name: &str) {
migrate_pad_configs();
warn_pad_config_save(pad_config::set_default_path(
&machine_pad_config_path(),
serial,
name,
warn_duplicate_profile_guid,
) {
warn!("Failed to save {}: {}", error.path.display(), error.error);
}
));
}

pub fn rename_pad_config(profile_id: &str, old: &str, new: &str) {
if let Err(error) = pad_config::rename_profile_id_report(
&profiles_root(),
profile_id,
pub fn rename_pad_config(old: &str, new: &str) {
migrate_pad_configs();
warn_pad_config_save(pad_config::rename_path(
&machine_pad_config_path(),
old,
new,
warn_duplicate_profile_guid,
) {
warn!("Failed to save {}: {}", error.path.display(), error.error);
}
));
}

pub fn delete_pad_config(profile_id: &str, name: &str) {
if let Err(error) = pad_config::delete_profile_id_report(
&profiles_root(),
profile_id,
name,
warn_duplicate_profile_guid,
) {
warn!("Failed to save {}: {}", error.path.display(), error.error);
}
pub fn delete_pad_config(name: &str) {
migrate_pad_configs();
warn_pad_config_save(pad_config::delete_path(&machine_pad_config_path(), name));
}

pub fn log_profile_stats_load_error(path: &Path, error: ProfileStatsLoadError) {
Expand Down Expand Up @@ -2056,8 +2088,12 @@ mod tests {
fn wheel_score_read_does_not_deadlock_with_leaderboard_worker() {
let data =
std::env::temp_dir().join(format!("deadsync-profile-paths-{}", std::process::id()));
init_paths(data.join("profiles"), data.join("defaults.ini"))
.expect("initialize isolated profile test paths");
init_paths(
data.join("profiles"),
data.join("defaults.ini"),
data.join("padconfig.ini"),
)
.expect("initialize isolated profile test paths");
let profile_id = "test-deadlock-wheel-profile";
let chart_hash = "feedface";
let seeded = deadsync_score::CachedScore {
Expand Down
12 changes: 6 additions & 6 deletions crates/deadsync-profile/src/compat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ pub use crate::app_runtime::{
machine_leaderboard_local, machine_leaderboard_local_with_names,
machine_leaderboard_local_without_names, machine_record_local, machine_replays_local,
machine_scalar_record_local, mark_known_pack_names_for_local_profile, mark_pack_known,
mark_packs_known, personal_leaderboard_local_for_side, played_chart_counts_for_id,
played_chart_counts_for_machine, played_chart_history_for_id, played_chart_history_for_machine,
prewarm_select_music_score_caches, read_itl_file_for_id, recent_played_chart_hashes_for_id,
recent_played_chart_hashes_for_machine, rename_local_profile, rename_pad_config,
save_itl_gameplay_players, save_local_summary_score_for_side, save_pad_configs,
scan_local_profiles, score_profile_paths_for_id,
mark_packs_known, migrate_pad_configs, personal_leaderboard_local_for_side,
played_chart_counts_for_id, played_chart_counts_for_machine, played_chart_history_for_id,
played_chart_history_for_machine, prewarm_select_music_score_caches, read_itl_file_for_id,
recent_played_chart_hashes_for_id, recent_played_chart_hashes_for_machine,
rename_local_profile, rename_pad_config, save_itl_gameplay_players,
save_local_summary_score_for_side, scan_local_profiles, score_profile_paths_for_id,
scorebox_profile_snapshot_from_config as scorebox_profile_snapshot,
seed_session_gs_score_for_id, seed_session_itl_unlock_folders,
seed_session_local_itg_score_for_id, seed_session_online_itl_self_rank,
Expand Down
11 changes: 0 additions & 11 deletions crates/deadsync-profile/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2335,7 +2335,6 @@ pub struct MusicProfileSnapshot {
pub music_rate: f32,
pub avatar_texture_keys: [Option<Arc<str>>; PLAYER_SLOTS],
pub local_profile_ids: [Option<Arc<str>>; PLAYER_SLOTS],
pub pad_profile_ids: [Option<Arc<str>>; PLAYER_SLOTS],
}

/// Reuses an immutable selection profile snapshot while every source field and
Expand Down Expand Up @@ -2495,10 +2494,6 @@ fn music_profile_snapshot_from_parts(
let local_profile_ids = std::array::from_fn(|side_idx| {
active_profile_local_id(&active_profiles[side_idx]).map(Arc::<str>::from)
});
let pad_profile_ids = std::array::from_fn(|pad| {
let side = side_for_physical_pad(play_style, player_side, pad == 1);
local_profile_ids[player_side_index(side)].clone()
});
MusicProfileSnapshot {
scorebox: scorebox_runtime_view(
profiles,
Expand All @@ -2518,7 +2513,6 @@ fn music_profile_snapshot_from_parts(
.map(Arc::<str>::from)
}),
local_profile_ids,
pad_profile_ids,
}
}

Expand Down Expand Up @@ -2572,11 +2566,6 @@ fn music_profile_session_matches(
&& view.guest == active_profile_is_guest(active)
&& snapshot.local_profile_ids[side_idx].as_deref() == persistent_profile_id
})
&& (0..PLAYER_SLOTS).all(|pad| {
let side = side_for_physical_pad(play_style, player_side, pad == 1);
snapshot.pad_profile_ids[pad].as_deref()
== active_profile_local_id(&active_profiles[player_side_index(side)])
})
}

fn music_profile_fields_match(
Expand Down
Loading