This repository has been archived on 2026-10-10. You can view files and clone it, but cannot push or open issues or pull requests.
molotov/crates/molotov-bootstrap/src/fflags.rs

190 lines
5.4 KiB
Rust

// SPDX-License-Identifier: GPL-3.0-or-later
use crate::error::MolotovError;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::path::Path;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum FlagValue {
Boolean(bool),
Integer(i64),
Text(String),
}
impl From<bool> for FlagValue {
fn from(v: bool) -> Self {
FlagValue::Boolean(v)
}
}
impl From<i64> for FlagValue {
fn from(v: i64) -> Self {
FlagValue::Integer(v)
}
}
impl From<i32> for FlagValue {
fn from(v: i32) -> Self {
FlagValue::Integer(v as i64)
}
}
impl From<&str> for FlagValue {
fn from(v: &str) -> Self {
FlagValue::Text(v.to_string())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PresetProfile {
Default,
LowVram,
CompetitiveUncapped,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FastFlagProfile {
pub name: String,
pub flags: HashMap<String, FlagValue>,
}
impl FastFlagProfile {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
flags: HashMap::new(),
}
}
pub fn set(&mut self, key: &str, val: FlagValue) {
self.flags.insert(key.to_string(), val);
}
pub fn get_int(&self, key: &str) -> Option<i64> {
match self.flags.get(key) {
Some(FlagValue::Integer(v)) => Some(*v),
_ => None,
}
}
pub fn get_bool(&self, key: &str) -> Option<bool> {
match self.flags.get(key) {
Some(FlagValue::Boolean(v)) => Some(*v),
_ => None,
}
}
}
pub struct FastFlagManager {
active_profile: FastFlagProfile,
allowlist: HashSet<String>,
}
impl Default for FastFlagManager {
fn default() -> Self {
Self::new()
}
}
impl FastFlagManager {
pub fn new() -> Self {
let mut allowlist = HashSet::new();
// Seed standard safe FastFlags
allowlist.insert("FIntDebugTextureManagerSkipMips".to_string());
allowlist.insert("DFIntTextureCompositorActiveJobs".to_string());
allowlist.insert("DFIntTaskSchedulerTargetFps".to_string());
allowlist.insert("FFlagDebugGraphicsDisableDirect3D11".to_string());
allowlist.insert("DFFlagDebugPauseVoxelizer".to_string());
Self {
active_profile: FastFlagProfile::new("Default"),
allowlist,
}
}
pub fn active_profile(&self) -> &FastFlagProfile {
&self.active_profile
}
pub fn active_profile_mut(&mut self) -> &mut FastFlagProfile {
&mut self.active_profile
}
pub fn set_flag(&mut self, key: &str, val: FlagValue) {
self.active_profile.set(key, val);
}
/// Sets a FastFlag, coercing the string input to the expected type based on standard prefix rules
pub fn set_flag_coerced(&mut self, key: &str, raw_val: &str) {
let value = if key.starts_with("FFlag") || key.starts_with("DFFlag") {
let lower = raw_val.trim().to_lowercase();
if lower == "true" || lower == "1" {
FlagValue::Boolean(true)
} else if lower == "false" || lower == "0" {
FlagValue::Boolean(false)
} else {
FlagValue::Text(raw_val.to_string())
}
} else if key.starts_with("FInt") || key.starts_with("DFInt") {
if let Ok(num) = raw_val.trim().parse::<i64>() {
FlagValue::Integer(num)
} else {
FlagValue::Text(raw_val.to_string())
}
} else {
FlagValue::Text(raw_val.to_string())
};
self.set_flag(key, value);
}
pub fn import_client_app_settings(&mut self, json_str: &str) -> Result<(), MolotovError> {
let flags: HashMap<String, FlagValue> = serde_json::from_str(json_str)?;
for (k, v) in flags {
self.set_flag(&k, v);
}
Ok(())
}
pub fn get_preset(&self, preset: PresetProfile) -> FastFlagProfile {
match preset {
PresetProfile::Default => FastFlagProfile::new("Default"),
PresetProfile::LowVram => {
let mut p = FastFlagProfile::new("Low VRAM");
p.set("FIntDebugTextureManagerSkipMips", 1.into());
p.set("DFIntTextureCompositorActiveJobs", 2.into());
p
}
PresetProfile::CompetitiveUncapped => {
let mut p = FastFlagProfile::new("Competitive Uncapped");
p.set("DFIntTaskSchedulerTargetFps", 240.into());
p
}
}
}
pub fn apply_preset(&mut self, preset: PresetProfile) {
self.active_profile = self.get_preset(preset);
}
pub fn validate_against_allowlist(&self) -> Vec<String> {
let mut unverified = Vec::new();
for key in self.active_profile.flags.keys() {
if !self.allowlist.contains(key) {
unverified.push(key.clone());
}
}
unverified.sort();
unverified
}
pub fn export_client_app_settings(&self, target_path: &Path) -> Result<(), MolotovError> {
if let Some(parent) = target_path.parent() {
std::fs::create_dir_all(parent)?;
}
let json = serde_json::to_string_pretty(&self.active_profile.flags)?;
std::fs::write(target_path, json)?;
Ok(())
}
}