use std::sync::OnceLock;
fn env_flag_enabled(compile_time: Option<&str>, runtime: Option<String>) -> bool {
if let Some(val) = runtime {
val.eq_ignore_ascii_case("1") || val.eq_ignore_ascii_case("true")
} else if let Some(val) = compile_time {
val.eq_ignore_ascii_case("1") || val.eq_ignore_ascii_case("true")
} else {
false
}
}
fn env_flag_default_on(compile_time: Option<&'static str>, runtime: Option<String>) -> bool {
env_str(compile_time, runtime)
.is_none_or(|val| !(val.eq_ignore_ascii_case("0") || val.eq_ignore_ascii_case("false")))
}
fn env_str(compile_time: Option<&'static str>, runtime: Option<String>) -> Option<String> {
fn non_empty(value: Option<String>) -> Option<String> {
value.filter(|v| !v.is_empty())
}
non_empty(runtime).or_else(|| non_empty(compile_time.map(str::to_string)))
}
fn parse_max_texture_size(raw: Option<String>) -> u32 {
raw.and_then(|s| s.parse().ok()).unwrap_or(0)
}
fn parse_atlas_size(raw: Option<String>) -> Option<(u32, u32)> {
raw.and_then(|s| {
let parts: Vec<&str> = s.split('x').collect();
if parts.len() == 2 {
let w = parts[0].parse::<u32>().ok()?;
let h = parts[1].parse::<u32>().ok()?;
Some((w, h))
} else {
None
}
})
}
pub fn depth_disabled() -> bool {
static DISABLED: OnceLock<bool> = OnceLock::new();
*DISABLED.get_or_init(|| {
env_flag_enabled(
option_env!("FRUST_ENGINE_NO_DEPTH"),
std::env::var("FRUST_ENGINE_NO_DEPTH").ok(),
)
})
}
pub fn uber_program_forced() -> bool {
static FORCED: OnceLock<bool> = OnceLock::new();
*FORCED.get_or_init(|| {
env_flag_enabled(
option_env!("FRUST_ENGINE_UBER_PROGRAM"),
std::env::var("FRUST_ENGINE_UBER_PROGRAM").ok(),
)
})
}
pub fn pass_merge_disabled() -> bool {
static DISABLED: OnceLock<bool> = OnceLock::new();
*DISABLED.get_or_init(|| {
env_flag_enabled(
option_env!("FRUST_ENGINE_NO_PASS_MERGE"),
std::env::var("FRUST_ENGINE_NO_PASS_MERGE").ok(),
)
})
}
pub fn image_sampler_enabled() -> bool {
static ENABLED: OnceLock<bool> = OnceLock::new();
*ENABLED.get_or_init(|| {
env_flag_default_on(
option_env!("FRUST_ENGINE_IMAGE_SAMPLER"),
std::env::var("FRUST_ENGINE_IMAGE_SAMPLER").ok(),
)
})
}
pub fn run_split_min() -> u32 {
static MIN: OnceLock<u32> = OnceLock::new();
*MIN.get_or_init(|| {
parse_run_split_min(env_str(
option_env!("FRUST_ENGINE_RUN_SPLIT_MIN"),
std::env::var("FRUST_ENGINE_RUN_SPLIT_MIN").ok(),
))
})
}
fn parse_run_split_min(raw: Option<String>) -> u32 {
raw.and_then(|s| s.trim().parse().ok())
.unwrap_or(crate::gpu::strips::RUN_SPLIT_MIN)
}
pub fn atlas_disabled() -> bool {
static DISABLED: OnceLock<bool> = OnceLock::new();
*DISABLED.get_or_init(|| {
env_flag_enabled(
option_env!("FRUST_ENGINE_NO_ATLAS"),
std::env::var("FRUST_ENGINE_NO_ATLAS").ok(),
)
})
}
pub fn glyph_quads_disabled() -> bool {
static DISABLED: OnceLock<bool> = OnceLock::new();
*DISABLED.get_or_init(|| {
env_flag_enabled(
option_env!("FRUST_ENGINE_NO_GLYPH_QUADS"),
std::env::var("FRUST_ENGINE_NO_GLYPH_QUADS").ok(),
)
})
}
pub fn parallel_walk_disabled() -> bool {
static DISABLED: OnceLock<bool> = OnceLock::new();
*DISABLED.get_or_init(|| {
env_flag_enabled(
option_env!("FRUST_ENGINE_NO_PARALLEL"),
std::env::var("FRUST_ENGINE_NO_PARALLEL").ok(),
)
})
}
pub fn parallel_workers() -> Option<usize> {
static WORKERS: OnceLock<Option<usize>> = OnceLock::new();
*WORKERS.get_or_init(|| {
parse_parallel_workers(env_str(
option_env!("FRUST_ENGINE_PARALLEL_WORKERS"),
std::env::var("FRUST_ENGINE_PARALLEL_WORKERS").ok(),
))
})
}
fn parse_parallel_workers(raw: Option<String>) -> Option<usize> {
raw.and_then(|s| s.trim().parse().ok())
.filter(|workers: &usize| *workers > 0)
}
pub fn parallel_min_cost() -> Option<u64> {
static MIN_COST: OnceLock<Option<u64>> = OnceLock::new();
*MIN_COST.get_or_init(|| {
parse_parallel_min_cost(env_str(
option_env!("FRUST_ENGINE_PARALLEL_MIN_COST"),
std::env::var("FRUST_ENGINE_PARALLEL_MIN_COST").ok(),
))
})
}
fn parse_parallel_min_cost(raw: Option<String>) -> Option<u64> {
raw.and_then(|s| s.trim().parse().ok())
}
pub fn glyph_run_cache_disabled() -> bool {
static DISABLED: OnceLock<bool> = OnceLock::new();
*DISABLED.get_or_init(|| {
env_flag_enabled(
option_env!("FRUST_ENGINE_NO_GLYPH_RUN_CACHE"),
std::env::var("FRUST_ENGINE_NO_GLYPH_RUN_CACHE").ok(),
)
})
}
pub fn shader_effects_disabled() -> bool {
static DISABLED: OnceLock<bool> = OnceLock::new();
*DISABLED.get_or_init(|| {
env_flag_enabled(
option_env!("FRUST_ENGINE_NO_SHADER_EFFECTS"),
std::env::var("FRUST_ENGINE_NO_SHADER_EFFECTS").ok(),
)
})
}
pub fn layers_disabled() -> bool {
static DISABLED: OnceLock<bool> = OnceLock::new();
*DISABLED.get_or_init(|| {
env_flag_enabled(
option_env!("FRUST_ENGINE_NO_LAYERS"),
std::env::var("FRUST_ENGINE_NO_LAYERS").ok(),
)
})
}
pub fn retained_budget_mb() -> Option<u64> {
static BUDGET: OnceLock<Option<u64>> = OnceLock::new();
*BUDGET.get_or_init(|| {
parse_retained_budget_mb(env_str(
option_env!("FRUST_ENGINE_RETAINED_BUDGET_MB"),
std::env::var("FRUST_ENGINE_RETAINED_BUDGET_MB").ok(),
))
})
}
fn parse_retained_budget_mb(raw: Option<String>) -> Option<u64> {
raw.and_then(|value| value.trim().parse().ok())
}
pub fn pool_disabled() -> bool {
static DISABLED: OnceLock<bool> = OnceLock::new();
*DISABLED.get_or_init(|| {
env_flag_enabled(
option_env!("FRUST_ENGINE_NO_POOL"),
std::env::var("FRUST_ENGINE_NO_POOL").ok(),
)
})
}
pub fn max_texture_size() -> u32 {
static MAX_SIZE: OnceLock<u32> = OnceLock::new();
*MAX_SIZE.get_or_init(|| {
parse_max_texture_size(env_str(
option_env!("FRUST_ENGINE_MAX_TEX"),
std::env::var("FRUST_ENGINE_MAX_TEX").ok(),
))
})
}
pub fn atlas_size() -> Option<(u32, u32)> {
static ATLAS_SIZE: OnceLock<Option<(u32, u32)>> = OnceLock::new();
*ATLAS_SIZE.get_or_init(|| {
parse_atlas_size(env_str(
option_env!("FRUST_ENGINE_ATLAS_SIZE"),
std::env::var("FRUST_ENGINE_ATLAS_SIZE").ok(),
))
})
}
pub fn downlevel_mode() -> bool {
static DOWNLEVEL: OnceLock<bool> = OnceLock::new();
*DOWNLEVEL.get_or_init(|| {
env_flag_enabled(
option_env!("FRUST_ENGINE_DOWNLEVEL"),
std::env::var("FRUST_ENGINE_DOWNLEVEL").ok(),
)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_env_flag_enabled() {
assert!(env_flag_enabled(Some("1"), None));
assert!(env_flag_enabled(Some("true"), None));
assert!(env_flag_enabled(Some("TRUE"), None));
assert!(!env_flag_enabled(Some("0"), None));
assert!(!env_flag_enabled(Some("false"), None));
assert!(!env_flag_enabled(None, None));
}
#[test]
fn test_runtime_overrides_compile_time() {
assert!(env_flag_enabled(Some("0"), Some("1".to_string())));
assert!(!env_flag_enabled(Some("1"), Some("0".to_string())));
}
#[test]
fn test_env_str_unset_is_none() {
assert_eq!(env_str(None, None), None);
}
#[test]
fn test_env_str_runtime_wins_over_compile_time() {
assert_eq!(
env_str(Some("1024"), Some("2048".to_string())),
Some("2048".to_string())
);
}
#[test]
fn test_env_str_empty_runtime_falls_through_to_compile_time() {
assert_eq!(
env_str(Some("1024"), Some(String::new())),
Some("1024".to_string())
);
}
#[test]
fn test_env_str_compile_time_only() {
assert_eq!(env_str(Some("1024"), None), Some("1024".to_string()));
}
#[test]
fn test_env_str_runtime_only() {
assert_eq!(
env_str(None, Some("2048".to_string())),
Some("2048".to_string())
);
}
#[test]
fn shader_effects_are_enabled_unless_the_switch_is_set() {
assert!(!shader_effects_disabled());
}
#[test]
fn parallel_workers_parses_a_positive_count_only() {
assert_eq!(parse_parallel_workers(Some("3".into())), Some(3));
assert_eq!(parse_parallel_workers(Some(" 2 ".into())), Some(2));
assert_eq!(parse_parallel_workers(Some("0".into())), None);
assert_eq!(parse_parallel_workers(Some("two".into())), None);
assert_eq!(parse_parallel_workers(None), None);
}
#[test]
fn parallel_min_cost_parses_any_count_including_zero() {
assert_eq!(parse_parallel_min_cost(Some("8000".into())), Some(8000));
assert_eq!(parse_parallel_min_cost(Some(" 12 ".into())), Some(12));
assert_eq!(parse_parallel_min_cost(Some("0".into())), Some(0));
assert_eq!(parse_parallel_min_cost(Some("-1".into())), None);
assert_eq!(parse_parallel_min_cost(Some("many".into())), None);
assert_eq!(parse_parallel_min_cost(Some(String::new())), None);
assert_eq!(parse_parallel_min_cost(None), None);
}
#[test]
fn shader_effects_switch_parses_like_every_other_engine_flag() {
assert!(env_flag_enabled(None, Some("1".to_string())));
assert!(env_flag_enabled(None, Some("TRUE".to_string())));
assert!(!env_flag_enabled(None, Some("0".to_string())));
assert!(!env_flag_enabled(None, Some(String::new())));
assert!(env_flag_enabled(Some("0"), Some("1".to_string())));
assert!(!env_flag_enabled(Some("1"), Some("0".to_string())));
}
#[test]
fn a_default_on_flag_is_off_only_when_switched_off() {
assert!(env_flag_default_on(None, None));
assert!(env_flag_default_on(None, Some("1".to_string())));
assert!(env_flag_default_on(None, Some(String::new())));
assert!(!env_flag_default_on(None, Some("0".to_string())));
assert!(!env_flag_default_on(None, Some("FALSE".to_string())));
assert!(!env_flag_default_on(Some("0"), None));
assert!(env_flag_default_on(Some("0"), Some("1".to_string())));
assert!(!env_flag_default_on(Some("1"), Some("0".to_string())));
assert!(!env_flag_default_on(Some("0"), Some(String::new())));
}
#[test]
fn the_run_split_threshold_parses_or_falls_back_to_the_measured_default() {
use crate::gpu::strips::RUN_SPLIT_MIN;
assert_eq!(parse_run_split_min(Some("64".to_string())), 64);
assert_eq!(parse_run_split_min(Some(" 256 ".to_string())), 256);
assert_eq!(parse_run_split_min(Some("0".to_string())), 0);
assert_eq!(parse_run_split_min(Some("many".to_string())), RUN_SPLIT_MIN);
assert_eq!(parse_run_split_min(None), RUN_SPLIT_MIN);
}
#[test]
fn the_retained_budget_parses_a_whole_number_of_mib_only() {
assert_eq!(parse_retained_budget_mb(None), None);
assert_eq!(parse_retained_budget_mb(Some("64".into())), Some(64));
assert_eq!(parse_retained_budget_mb(Some(" 0 ".into())), Some(0));
assert_eq!(parse_retained_budget_mb(Some("1.5".into())), None);
assert_eq!(parse_retained_budget_mb(Some("lots".into())), None);
}
#[test]
fn test_max_texture_size_parsing() {
assert_eq!(parse_max_texture_size(Some("1024".to_string())), 1024);
assert_eq!(parse_max_texture_size(Some("0".to_string())), 0);
assert_eq!(parse_max_texture_size(Some("invalid".to_string())), 0);
assert_eq!(parse_max_texture_size(None), 0);
}
#[test]
fn test_atlas_size_parsing() {
assert_eq!(
parse_atlas_size(Some("2048x2048".to_string())),
Some((2048, 2048))
);
assert_eq!(
parse_atlas_size(Some("1024x512".to_string())),
Some((1024, 512))
);
assert_eq!(parse_atlas_size(Some("2048".to_string())), None);
assert_eq!(parse_atlas_size(Some("2048x2048x2048".to_string())), None);
assert_eq!(parse_atlas_size(None), None);
}
}