feat: added memory allocation presets to minecraft and cluster settings (#747)
This commit is contained in:
parent
8ca2c30993
commit
419c07b822
6 changed files with 154 additions and 34 deletions
51
packages/oneclient_app/src/components/memory_field.rs
Normal file
51
packages/oneclient_app/src/components/memory_field.rs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
use freya::prelude::*;
|
||||
|
||||
use crate::components::{Dropdown, TextInput, validate_memory};
|
||||
use crate::theme::colors;
|
||||
use crate::utils::{format_memory_gb, memory_presets_mb};
|
||||
|
||||
const UNSET_LABEL: &str = "Default";
|
||||
const CUSTOM_LABEL: &str = "Custom";
|
||||
|
||||
/// Presets picker with an input for memory allocation
|
||||
pub fn memory_field(mut memory: State<String>) -> impl IntoElement {
|
||||
let presets = memory_presets_mb();
|
||||
let selected = match memory.read().trim() {
|
||||
"" => UNSET_LABEL.to_string(),
|
||||
value => value
|
||||
.parse::<u32>()
|
||||
.ok()
|
||||
.filter(|mb| presets.contains(mb))
|
||||
.map(format_memory_gb)
|
||||
.unwrap_or_else(|| CUSTOM_LABEL.to_string()),
|
||||
};
|
||||
|
||||
let options: Vec<String> = presets.iter().copied().map(format_memory_gb).collect();
|
||||
|
||||
rect()
|
||||
.horizontal()
|
||||
.cross_align(Alignment::Center)
|
||||
.spacing(8.)
|
||||
.child(
|
||||
Dropdown::new(selected, options)
|
||||
.width(Size::px(100.))
|
||||
.height(Size::px(34.))
|
||||
.on_select(move |idx: usize| {
|
||||
if let Some(mb) = presets.get(idx).copied() {
|
||||
memory.set(mb.to_string());
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
TextInput::new(memory)
|
||||
.width(Size::px(90.))
|
||||
.placeholder("4096")
|
||||
.on_validate(validate_memory)
|
||||
.trailing(
|
||||
label()
|
||||
.text("MB")
|
||||
.font_size(12.)
|
||||
.color(colors::fg_secondary()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ mod java_prompt;
|
|||
mod link_confirm;
|
||||
mod local_image;
|
||||
mod log_viewer;
|
||||
mod memory_field;
|
||||
mod microsoft_login;
|
||||
mod navbar;
|
||||
mod notifications;
|
||||
|
|
@ -57,6 +58,7 @@ pub use java_prompt::JavaPromptOverlay;
|
|||
pub use link_confirm::ConfirmLinkOverlay;
|
||||
pub use local_image::LocalImage;
|
||||
pub use log_viewer::LogViewer;
|
||||
pub use memory_field::memory_field;
|
||||
pub use microsoft_login::use_microsoft_login;
|
||||
pub(crate) use microsoft_login::login_dialog;
|
||||
pub(crate) use navbar::window_controls;
|
||||
|
|
@ -76,7 +78,7 @@ pub use segmented_control::{Segment, SegmentedControl};
|
|||
pub use splash_curtain::SplashCurtain;
|
||||
pub use status_bar::StatusBar;
|
||||
pub use tab_bar::{TabBar, TabItem};
|
||||
pub use text_input::{TextInput, validate_number};
|
||||
pub use text_input::{TextInput, validate_memory, validate_number};
|
||||
pub use toasts::Toasts;
|
||||
pub use toggle::{toggle, toggle_controlled};
|
||||
pub use update_prompt::UpdatePromptOverlay;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,18 @@ pub fn validate_number(validator: InputValidator) {
|
|||
validator.set_valid(valid);
|
||||
}
|
||||
|
||||
/// Validates the input if user has entered more MB of memory than his machine has
|
||||
pub fn validate_memory(validator: InputValidator) {
|
||||
let text = validator.text();
|
||||
|
||||
let valid = text.is_empty()
|
||||
|| text
|
||||
.parse::<u32>()
|
||||
.is_ok_and(|mb| mb <= crate::utils::total_ram_mb());
|
||||
|
||||
validator.set_valid(valid);
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct TextInput {
|
||||
input: Input,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use chrono::{Datelike, NaiveDate};
|
|||
use oneclient_core::clusters::Cluster;
|
||||
use oneclient_common::domain::GameLoader;
|
||||
use oneclient_common::{ParsedMcVersion, VersionKey, format_mc_version, parse_mc_version};
|
||||
use sysinfo::{MemoryRefreshKind, RefreshKind, System};
|
||||
|
||||
pub type ClusterGroups = BTreeMap<ReleaseLine, Vec<Cluster>>;
|
||||
|
||||
|
|
@ -204,6 +205,56 @@ pub fn format_res((w, h): (u32, u32)) -> String {
|
|||
format!("{w}×{h}")
|
||||
}
|
||||
|
||||
/// Prevents user from choosing his max amount of ram preset (e.g Someone has 16GB of RAM,
|
||||
/// so the max preset is 16GB - 2GB = 14GB)
|
||||
const MEMORY_HEADROOM_GB: u32 = 2;
|
||||
const MEMORY_PRESETS_GB: [u32; 10] = [2, 4, 6, 8, 12, 16, 24, 32, 48, 64];
|
||||
|
||||
pub fn total_ram_mb() -> u32 {
|
||||
static TOTAL_RAM_MB: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
|
||||
|
||||
*TOTAL_RAM_MB.get_or_init(|| {
|
||||
let system = System::new_with_specifics(
|
||||
RefreshKind::nothing().with_memory(MemoryRefreshKind::nothing().with_ram()),
|
||||
);
|
||||
(system.total_memory() / 1024 / 1024).min(u32::MAX as u64) as u32
|
||||
})
|
||||
}
|
||||
|
||||
pub fn memory_presets_mb() -> Vec<u32> {
|
||||
presets_for_total_gb((total_ram_mb() as f32 / 1024.).round() as u32)
|
||||
}
|
||||
|
||||
fn presets_for_total_gb(total_gb: u32) -> Vec<u32> {
|
||||
let usable_gb = total_gb.saturating_sub(MEMORY_HEADROOM_GB);
|
||||
if usable_gb == 0 {
|
||||
return vec![1024];
|
||||
}
|
||||
|
||||
let mut presets: Vec<u32> = MEMORY_PRESETS_GB
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|gb| *gb <= usable_gb)
|
||||
.map(|gb| gb * 1024)
|
||||
.collect();
|
||||
|
||||
let usable_mb = usable_gb * 1024;
|
||||
if presets.last() != Some(&usable_mb) {
|
||||
presets.push(usable_mb);
|
||||
}
|
||||
|
||||
presets
|
||||
}
|
||||
|
||||
/// `8192` -> `8 GB` `1536` -> `1.5 GB`
|
||||
pub fn format_memory_gb(mb: u32) -> String {
|
||||
if mb.is_multiple_of(1024) {
|
||||
format!("{} GB", mb / 1024)
|
||||
} else {
|
||||
format!("{:.1} GB", mb as f32 / 1024.)
|
||||
}
|
||||
}
|
||||
|
||||
/// `7384` -> `2h 3m` `540` -> `9m` `0` -> `0m`
|
||||
pub fn format_duration_hm(secs: i64) -> String {
|
||||
if secs <= 0 {
|
||||
|
|
@ -396,6 +447,35 @@ mod tests {
|
|||
assert_eq!(line_art_key(line(26, Some(1)), &modern), Some((1, None)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn presets_stop_two_gigabytes_short_of_the_machine() {
|
||||
assert_eq!(
|
||||
presets_for_total_gb(16),
|
||||
vec![2048, 4096, 6144, 8192, 12288, 14336]
|
||||
);
|
||||
assert_eq!(
|
||||
presets_for_total_gb(32),
|
||||
vec![2048, 4096, 6144, 8192, 12288, 16384, 24576, 30720]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_ceiling_landing_on_a_round_step_is_not_repeated() {
|
||||
assert_eq!(presets_for_total_gb(8), vec![2048, 4096, 6144]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_tiny_machine_still_gets_one_preset() {
|
||||
assert_eq!(presets_for_total_gb(4), vec![2048]);
|
||||
assert_eq!(presets_for_total_gb(2), vec![1024]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memory_labels_drop_the_decimal_when_whole() {
|
||||
assert_eq!(format_memory_gb(8192), "8 GB");
|
||||
assert_eq!(format_memory_gb(1536), "1.5 GB");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_bare_legacy_version_keeps_its_line_name() {
|
||||
let clusters = [versioned(1, "1.21")];
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ use oneclient_core::settings::{
|
|||
};
|
||||
|
||||
use crate::components::{
|
||||
Button, Dropdown, Icon, IconType, ScrollArea, TextInput, toggle, toggle_controlled,
|
||||
validate_number,
|
||||
Button, Dropdown, Icon, IconType, ScrollArea, TextInput, memory_field, toggle,
|
||||
toggle_controlled, validate_number,
|
||||
};
|
||||
use crate::hooks::{
|
||||
ClusterAction, java_runtimes, loader_versions, mutation_is_running, query_error,
|
||||
|
|
@ -363,21 +363,12 @@ impl Component for MemoryRow {
|
|||
})
|
||||
.into();
|
||||
|
||||
let control = TextInput::new(memory)
|
||||
.width(Size::px(90.))
|
||||
.placeholder("4096")
|
||||
.on_validate(validate_number)
|
||||
.trailing(
|
||||
label()
|
||||
.text("MB")
|
||||
.font_size(12.)
|
||||
.color(colors::fg_secondary()),
|
||||
);
|
||||
let control = memory_field(memory);
|
||||
|
||||
settings_row(
|
||||
IconType::Database01,
|
||||
"Memory",
|
||||
"The amount of memory in megabytes allocated for the game.",
|
||||
"The amount of memory in megabytes allocated for the game. Presets leave 2 GB for the system.",
|
||||
override_cell(control, overridden, on_reset),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ use oneclient_common::Patch;
|
|||
use oneclient_core::settings::{PackageUpdateMode, ProfileUpdate, Resolution};
|
||||
|
||||
use super::settings_page;
|
||||
use crate::components::{Dropdown, Icon, IconType, TextInput, toggle, validate_number};
|
||||
use crate::components::{
|
||||
Dropdown, Icon, IconType, TextInput, memory_field, toggle, validate_number,
|
||||
};
|
||||
use crate::hooks::{use_dispatch, use_settings_snapshot};
|
||||
use crate::theme::colors;
|
||||
use crate::view::app::settings::{section_header, settings_row};
|
||||
|
|
@ -92,7 +94,7 @@ impl Component for SettingsMinecraft {
|
|||
.child(settings_row(
|
||||
IconType::Database01,
|
||||
"Memory",
|
||||
"The amount of memory in megabytes allocated for the game.",
|
||||
"The amount of memory in megabytes allocated for the game. Presets leave 2 GB for the system.",
|
||||
memory_field(memory),
|
||||
))
|
||||
.child(settings_row(
|
||||
|
|
@ -237,21 +239,3 @@ fn resolution_field(width: State<String>, height: State<String>) -> impl IntoEle
|
|||
.into_element()
|
||||
}
|
||||
|
||||
fn memory_field(memory: State<String>) -> impl IntoElement {
|
||||
rect()
|
||||
.horizontal()
|
||||
.cross_align(Alignment::Center)
|
||||
.spacing(8.)
|
||||
.child(
|
||||
TextInput::new(memory)
|
||||
.width(Size::px(90.))
|
||||
.placeholder("4096")
|
||||
.on_validate(validate_number)
|
||||
.trailing(
|
||||
label()
|
||||
.text("MB")
|
||||
.font_size(12.)
|
||||
.color(colors::fg_secondary()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue