use std::sync::{Arc, Mutex};
use crate::blob::BlobData;
use crate::components::{
AudioCommand, ControlsCommand, IndirectLighting, InputKey, SettingCommand, SettingOp,
ShadowUpdate, Sprite, TextLabel, WindowMode,
};
use crate::config::Settings;
use crate::ecs::asset_id::AssetId;
use crate::ecs::{ComponentStorage, PipelineContext, Resources, System};
use crate::gfx::backend::{GpuProfile, GpuVendor};
use crate::gfx::display_mode::DisplayMode;
use crate::gfx::graphics_system::{RebindViz, SliderViz};
use crate::gfx::keymap::{Bindable, KeyMap};
use crate::gfx::mock_backend::{Call, MockBackend, MockState, recording_backend};
use crate::gfx::profile::FrameProfile;
use crate::gfx::quality_preset::QualityPreset;
use crate::gfx::settings;
use super::SettingsState;
use super::writer::SettingsWriter;
const VALUE_LABEL: AssetId = AssetId(1);
const HANDLE: AssetId = AssetId(2);
const QUALITY_LABEL: AssetId = AssetId(3);
const SUB_ROW_LABEL: AssetId = AssetId(4);
const REBIND_LABEL: AssetId = AssetId(5);
const VICTIM_LABEL: AssetId = AssetId(6);
const RESOLUTION_LABEL: AssetId = AssetId(7);
const TOGGLE_LABEL: AssetId = AssetId(8);
const PAD_REBIND_LABEL: AssetId = AssetId(9);
const PAD_VICTIM_LABEL: AssetId = AssetId(10);
const LIT: [f32; 3] = [1.0, 1.0, 1.0];
struct World {
components: ComponentStorage,
blob: BlobData,
profile: FrameProfile,
resources: Resources,
scratch: crate::ecs::Arena,
}
impl World {
fn ctx(&mut self) -> PipelineContext<'_> {
PipelineContext {
components: &mut self.components,
blob: &mut self.blob,
profile: &mut self.profile,
resources: &mut self.resources,
frame: crate::ecs::FrameContext::new(&self.scratch),
}
}
}
struct Fixture {
world: World,
state: SettingsState,
backend: MockBackend,
calls: Arc<Mutex<MockState>>,
saved: Arc<Mutex<Vec<Settings>>>,
}
impl Fixture {
fn new() -> Self {
Self::with_profile(GpuProfile::UNKNOWN)
}
fn with_profile(gpu_profile: GpuProfile) -> Self {
let (calls, backend) = recording_backend();
let saved: Arc<Mutex<Vec<Settings>>> = Arc::default();
let sink_log = Arc::clone(&saved);
let writer = SettingsWriter::with_sink(move |cfg| {
sink_log.lock().unwrap().push(cfg.clone());
Ok(())
});
let mut components = ComponentStorage::default();
for (id, content) in [
(VALUE_LABEL, "value"),
(QUALITY_LABEL, "quality"),
(SUB_ROW_LABEL, "sub"),
(REBIND_LABEL, "rebind"),
(VICTIM_LABEL, "victim"),
(RESOLUTION_LABEL, "resolution"),
(TOGGLE_LABEL, "toggle"),
(PAD_REBIND_LABEL, "pad_rebind"),
(PAD_VICTIM_LABEL, "pad_victim"),
] {
components.push_typed(TextLabel {
asset_id: id,
content: content.to_string(),
color: LIT,
..Default::default()
});
}
components.push_typed(Sprite {
asset_id: HANDLE,
x: 0.0,
..Default::default()
});
let cycle_value_labels = [
("graphics_quality".to_string(), QUALITY_LABEL),
("render_scale".to_string(), VALUE_LABEL),
]
.into_iter()
.collect();
let state = SettingsState {
keymap: KeyMap::default(),
rebind_rows: vec![
RebindViz {
action: Bindable::Forward,
value_id: REBIND_LABEL,
},
RebindViz {
action: Bindable::Backward,
value_id: VICTIM_LABEL,
},
],
gamepad_map: crate::components::GamepadMap::default(),
pad_rebind_rows: vec![
crate::gfx::graphics_system::PadRebindViz {
action: crate::components::GamepadAction::Jump,
value_id: PAD_REBIND_LABEL,
},
crate::gfx::graphics_system::PadRebindViz {
action: crate::components::GamepadAction::Sprint,
value_id: PAD_VICTIM_LABEL,
},
],
sliders: vec![SliderViz {
key: "exposure".to_string(),
track_x: 100.0,
track_w: 200.0,
handle_w: 20.0,
handle_id: HANDLE,
value_id: VALUE_LABEL,
}],
cycle_value_labels,
post_process: crate::gfx::render_types::PostProcessTunables::DEFAULT,
post_config: Default::default(),
authored_post_config: Default::default(),
ambient_intensity: 1.0,
quality_preset: QualityPreset::Custom,
gpu_profile,
render_scale: settings::render_scale_at(0),
upscale_backend: settings::upscale_backend_at(0),
temporal_upscaling: false,
hdr_display: false,
hdr_pq: false,
shadow_map_size: settings::shadow_resolution_at(0),
shadow_update: ShadowUpdate::EveryFrame,
shadow_distance: settings::shadow_distance_at(0),
shadow_cascades: settings::shadow_cascades_at(0),
anisotropy: settings::anisotropy_at(0),
authored_shadow_map_size: settings::shadow_resolution_at(0),
authored_shadow_update: ShadowUpdate::EveryFrame,
authored_shadow_distance: settings::shadow_distance_at(0),
authored_shadow_cascades: settings::shadow_cascades_at(0),
authored_anisotropy: settings::anisotropy_at(0),
vsync: false,
fps_cap: settings::fps_cap_at(0),
perf_stats: true,
show_fps: true,
show_vram: false,
perf_sub_row_labels: vec![(SUB_ROW_LABEL, LIT)],
window_args: Default::default(),
display_modes: Vec::new(),
resolution: None,
current_mode: None,
resolution_row_labels: vec![(RESOLUTION_LABEL, LIT)],
frames_in_flight: settings::frames_in_flight_at(0) as usize,
occlusion_two_pass: false,
texture_cap: settings::texture_quality_at(0).0,
texture_budget: settings::texture_quality_at(0).1,
persisted_graphics: Default::default(),
fog_built: true,
settings_cache: Some(Settings::default()),
settings_writer: Some(writer),
scene_cmd_cursor: Default::default(),
setting_cmd_cursor: Default::default(),
published_hud_prefs: None,
published_disabled_inputs: None,
};
Fixture {
world: World {
components,
blob: BlobData::new(vec![Some(Vec::new())]),
profile: FrameProfile::default(),
resources: Resources::new(),
scratch: crate::ecs::Arena::with_capacity(64 * 1024),
},
state,
backend,
calls,
saved,
}
}
fn apply(&mut self, cmds: Vec<SettingCommand>) {
{
let mut ctx = self.world.ctx();
let events = ctx.events_mut::<SettingCommand>();
for cmd in cmds {
events.send(cmd);
}
}
let mut ctx = self.world.ctx();
let mut ops = crate::gfx::ops::RenderOps::default();
self.state.apply_setting_commands(&mut ctx, &mut ops);
ops.replay(&mut self.backend);
}
fn next(&mut self, setting: &str) {
self.apply(vec![cycle(setting, SettingOp::Next)]);
}
fn persisted(&mut self) -> Settings {
self.state.settings_writer = None;
let saved = self.saved.lock().unwrap();
saved.last().cloned().expect("a snapshot was persisted")
}
fn saw(&self, call: &Call) -> bool {
self.calls.lock().unwrap().saw(call)
}
fn label(&mut self, id: AssetId) -> String {
self.world
.ctx()
.query::<TextLabel>()
.find(|l| l.asset_id == id)
.map(|l| l.content.clone())
.expect("label present")
}
fn label_color(&mut self, id: AssetId) -> [f32; 3] {
self.world
.ctx()
.query::<TextLabel>()
.find(|l| l.asset_id == id)
.map(|l| l.color)
.expect("label present")
}
fn sent_controls(&mut self) -> Vec<ControlsCommand> {
self.world
.ctx()
.events::<ControlsCommand>()
.map(|e| e.read(&mut Default::default()).cloned().collect())
.unwrap_or_default()
}
}
fn cycle(setting: &str, op: SettingOp) -> SettingCommand {
SettingCommand {
setting: setting.to_string(),
op,
value_label: Some(VALUE_LABEL),
persist: true,
}
}
fn drag(setting: &str, frac: f32, persist: bool) -> SettingCommand {
SettingCommand {
setting: setting.to_string(),
op: SettingOp::SetFraction(frac),
value_label: Some(VALUE_LABEL),
persist,
}
}
#[test]
fn vsync_cycles_live_and_persists() {
let mut f = Fixture::new();
f.next("vsync");
assert!(f.state.vsync, "the row cycled Off -> On");
assert!(f.saw(&Call::SetVsync(true)), "applied live");
assert_eq!(f.label(VALUE_LABEL), "On");
assert_eq!(f.persisted().graphics.vsync, Some(true));
}
#[test]
fn cycling_prev_wraps_the_option_list() {
let mut f = Fixture::new();
f.apply(vec![cycle("vsync", SettingOp::Prev)]);
assert!(f.state.vsync, "Off wraps back to the last option");
}
#[test]
fn set_index_jumps_to_the_chosen_option() {
let mut f = Fixture::new();
f.apply(vec![cycle("shadow_cascades", SettingOp::SetIndex(2))]);
assert_eq!(f.state.shadow_cascades, settings::shadow_cascades_at(2));
}
#[test]
fn unknown_setting_is_ignored() {
let mut f = Fixture::new();
f.next("not_a_setting");
assert!(f.calls.lock().unwrap().calls.is_empty(), "nothing applied");
assert!(
f.saved.lock().unwrap().is_empty(),
"an unknown key never persists"
);
}
#[test]
fn rebind_swaps_the_victim_and_relabels_both_rows() {
let mut f = Fixture::new();
let forward_key = f.state.keymap.get(Bindable::Forward);
let backward_key = f.state.keymap.get(Bindable::Backward);
f.apply(vec![SettingCommand {
setting: Bindable::Forward.setting_key().to_string(),
op: SettingOp::Rebind(backward_key),
value_label: None,
persist: true,
}]);
assert_eq!(f.state.keymap.get(Bindable::Forward), backward_key);
assert_eq!(
f.state.keymap.get(Bindable::Backward),
forward_key,
"the victim takes the rebound action's old key"
);
assert!(f.saw(&Call::SetKeymap));
assert_eq!(f.label(REBIND_LABEL), backward_key.display_name());
assert_eq!(f.label(VICTIM_LABEL), forward_key.display_name());
assert_eq!(f.persisted().controls.keymap, Some(f.state.keymap));
}
#[test]
fn rebind_to_a_free_key_has_no_victim() {
let mut f = Fixture::new();
assert!(
f.state.keymap.action_for_key(InputKey::Q).is_none(),
"Q is unbound in the default map"
);
f.apply(vec![SettingCommand {
setting: Bindable::Forward.setting_key().to_string(),
op: SettingOp::Rebind(InputKey::Q),
value_label: None,
persist: true,
}]);
assert_eq!(f.state.keymap.get(Bindable::Forward), InputKey::Q);
assert_eq!(f.label(REBIND_LABEL), InputKey::Q.display_name());
assert_eq!(f.label(VICTIM_LABEL), "victim", "no victim was relabelled");
}
#[test]
fn rebind_of_an_unknown_action_is_ignored() {
let mut f = Fixture::new();
f.apply(vec![SettingCommand {
setting: "not_an_action".to_string(),
op: SettingOp::Rebind(InputKey::Q),
value_label: None,
persist: true,
}]);
assert!(!f.saw(&Call::SetKeymap));
assert_eq!(f.state.keymap, KeyMap::default());
}
#[test]
fn slider_applies_live_and_persists_only_on_release() {
let mut f = Fixture::new();
f.apply(vec![drag("exposure", 1.0, false)]);
let mid_drag = f.state.post_process.exposure;
assert!(f.saw(&Call::UpdatePostProcess), "applied live mid-drag");
assert!(
f.saved.lock().unwrap().is_empty(),
"an in-progress drag never writes"
);
f.apply(vec![drag("exposure", 1.0, true)]);
assert_eq!(
f.state.post_process.exposure, mid_drag,
"same value applied"
);
assert!(
f.persisted().graphics.exposure_ev.is_some(),
"the release frame writes"
);
}
#[test]
fn exposure_slider_persists_ev_and_applies_the_multiplier() {
let mut f = Fixture::new();
f.apply(vec![drag("exposure", 1.0, true)]);
let ev = f.persisted().graphics.exposure_ev.expect("persisted");
assert_eq!(
f.state.post_process.exposure,
settings::slider_apply_value("exposure", ev),
"the live param is the EV mapped through the apply transform"
);
assert_eq!(
f.label(VALUE_LABEL),
settings::format_slider_value("exposure", ev)
);
}
#[test]
fn slider_moves_the_handle_along_its_track() {
let mut f = Fixture::new();
f.apply(vec![drag("exposure", 0.5, false)]);
let handle_x = f
.world
.ctx()
.query::<Sprite>()
.find(|s| s.asset_id == HANDLE)
.map(|s| s.x)
.expect("handle present");
assert_eq!(handle_x, 190.0);
}
#[test]
fn slider_clamps_an_out_of_range_fraction() {
let mut f = Fixture::new();
f.apply(vec![drag("exposure", 2.0, false)]);
let handle_x = f
.world
.ctx()
.query::<Sprite>()
.find(|s| s.asset_id == HANDLE)
.map(|s| s.x)
.expect("handle present");
assert_eq!(handle_x, 280.0, "pinned to the track's right end");
}
#[test]
fn slider_steps_by_next_and_prev() {
let mut f = Fixture::new();
let handle_x = |f: &mut Fixture| {
f.world
.ctx()
.query::<Sprite>()
.find(|s| s.asset_id == HANDLE)
.map(|s| s.x)
.expect("handle present")
};
f.apply(vec![drag("exposure", 0.5, true)]);
assert_eq!(handle_x(&mut f), 190.0);
f.apply(vec![cycle("exposure", SettingOp::Next)]);
assert!(
(handle_x(&mut f) - 199.0).abs() < 1.0e-3,
"Next steps +5% of the travel"
);
assert!(f.persisted().graphics.exposure_ev.is_some());
f.apply(vec![cycle("exposure", SettingOp::Prev)]);
assert!(
(handle_x(&mut f) - 190.0).abs() < 1.0e-3,
"Prev steps back down"
);
for _ in 0..25 {
f.apply(vec![cycle("exposure", SettingOp::Prev)]);
}
assert_eq!(handle_x(&mut f), 100.0);
}
#[test]
fn unknown_slider_is_ignored() {
let mut f = Fixture::new();
f.apply(vec![drag("not_a_slider", 0.5, true)]);
assert!(f.calls.lock().unwrap().calls.is_empty());
assert!(f.saved.lock().unwrap().is_empty());
}
#[test]
fn quality_param_slider_updates_quality_params() {
let mut f = Fixture::new();
f.apply(vec![drag("ssao_radius", 0.5, true)]);
assert!(f.saw(&Call::UpdateQualityParams));
assert!(
!f.saw(&Call::UpdatePostProcess),
"a quality param is not a post-process param"
);
assert!(f.persisted().graphics.ssao_radius.is_some());
}
#[test]
fn ambient_slider_takes_the_dedicated_setter() {
let mut f = Fixture::new();
f.apply(vec![drag("ambient_intensity", 0.25, true)]);
let applied = f.state.ambient_intensity;
assert!(f.saw(&Call::SetAmbientIntensity(applied)));
assert!(f.persisted().graphics.ambient_intensity.is_some());
}
#[test]
fn mouse_sensitivity_slider_sends_a_controls_command() {
let mut f = Fixture::new();
f.apply(vec![drag("mouse_sensitivity", 1.0, true)]);
let sent: Vec<ControlsCommand> = f
.world
.ctx()
.events::<ControlsCommand>()
.map(|e| e.read(&mut Default::default()).cloned().collect())
.unwrap_or_default();
assert_eq!(sent.len(), 1);
assert!(sent[0].mouse_sensitivity.is_some());
assert!(sent[0].fov_y_degrees.is_none());
assert!(
!f.saw(&Call::UpdatePostProcess),
"sensitivity is not a render param"
);
let stored = sent[0].mouse_sensitivity.unwrap();
assert_eq!(
f.persisted().controls.mouse_sensitivity,
Some(stored),
"the radians/pixel value is what persists"
);
}
#[test]
fn fov_slider_sends_a_controls_command() {
let mut f = Fixture::new();
f.apply(vec![drag("fov", 0.0, true)]);
let sent: Vec<ControlsCommand> = f
.world
.ctx()
.events::<ControlsCommand>()
.map(|e| e.read(&mut Default::default()).cloned().collect())
.unwrap_or_default();
assert_eq!(sent.len(), 1);
assert!(sent[0].fov_y_degrees.is_some());
assert!(sent[0].mouse_sensitivity.is_none());
assert_eq!(f.persisted().graphics.fov, sent[0].fov_y_degrees);
}
#[test]
fn pad_rebind_swaps_the_victim_and_relabels_both_rows() {
use crate::components::GamepadAction;
let mut f = Fixture::new();
let jump_button = f.state.gamepad_map.get(GamepadAction::Jump);
let sprint_button = f.state.gamepad_map.get(GamepadAction::Sprint);
f.apply(vec![SettingCommand {
setting: GamepadAction::Jump.setting_key().to_string(),
op: SettingOp::RebindButton(sprint_button),
value_label: None,
persist: true,
}]);
assert_eq!(f.state.gamepad_map.get(GamepadAction::Jump), sprint_button);
assert_eq!(
f.state.gamepad_map.get(GamepadAction::Sprint),
jump_button,
"the victim takes the rebound action's old button"
);
let sent = f.sent_controls();
assert_eq!(sent.len(), 1);
assert_eq!(
sent[0].gamepad_map,
Some(f.state.gamepad_map),
"the live map travels to InputSystem"
);
assert_eq!(f.label(PAD_REBIND_LABEL), sprint_button.display_name());
assert_eq!(f.label(PAD_VICTIM_LABEL), jump_button.display_name());
assert_eq!(
f.persisted().controls.gamepad_map,
Some(f.state.gamepad_map)
);
}
#[test]
fn pad_rebind_of_a_non_gamepad_action_is_ignored() {
let mut f = Fixture::new();
f.apply(vec![SettingCommand {
setting: Bindable::Forward.setting_key().to_string(),
op: SettingOp::RebindButton(crate::components::GamepadButton::North),
value_label: None,
persist: true,
}]);
assert_eq!(
f.state.gamepad_map,
crate::components::GamepadMap::default()
);
assert!(f.saved.lock().unwrap().is_empty(), "nothing persisted");
}
#[test]
fn gamepad_sliders_send_controls_commands_and_persist_applied_values() {
let mut f = Fixture::new();
f.apply(vec![
drag("gamepad_look_sensitivity", 1.0, true),
drag("gamepad_deadzone", 0.5, true),
]);
let sent = f.sent_controls();
assert_eq!(sent.len(), 2);
let rate = sent[0].gamepad_look_sensitivity.expect("sensitivity sent");
assert!(
(rate - 6.0).abs() < 1e-5,
"full track is the max rate: {rate}"
);
let dz = sent[1].gamepad_deadzone.expect("deadzone sent");
assert!(
(dz - 0.2).abs() < 1e-5,
"mid track of 0..40% stores 0.2: {dz}"
);
assert!(
!f.saw(&Call::UpdatePostProcess),
"the gamepad sliders are not render params"
);
let cfg = f.persisted();
assert_eq!(cfg.controls.gamepad_look_sensitivity, Some(rate));
assert_eq!(cfg.controls.gamepad_deadzone, Some(dz));
}
#[test]
fn fps_cap_publishes_the_frame_rate_cap_resource() {
let mut f = Fixture::new();
f.next("fps_cap");
let published = f
.world
.ctx()
.resource::<crate::ecs::FrameRateCap>()
.map(|c| c.0)
.expect("cap published");
assert_eq!(published, f.state.fps_cap);
assert_eq!(f.persisted().graphics.fps_cap, Some(f.state.fps_cap));
}
#[test]
fn volume_rows_send_targeted_audio_commands() {
use concinnity_core::components::AudioTarget;
let mut f = Fixture::new();
f.next("master_volume");
f.next("voice_volume");
let sent: Vec<AudioCommand> = f
.world
.ctx()
.events::<AudioCommand>()
.map(|e| e.read(&mut Default::default()).cloned().collect())
.unwrap_or_default();
assert_eq!(sent.len(), 2);
assert_eq!(sent[0].target, AudioTarget::Master);
assert_eq!(f.persisted().audio.master_volume, Some(sent[0].gain));
assert_eq!(sent[1].target, AudioTarget::Voice);
assert_eq!(f.persisted().audio.voice_volume, Some(sent[1].gain));
}
#[test]
fn perf_stats_master_grays_and_restores_the_sub_rows() {
let mut f = Fixture::new();
f.next("perf_stats");
assert!(!f.state.perf_stats, "cycled On -> Off");
assert_eq!(
f.label_color(SUB_ROW_LABEL),
super::rows::DISABLED_ROW_COLOR
);
f.next("perf_stats");
assert!(f.state.perf_stats);
assert_eq!(
f.label_color(SUB_ROW_LABEL),
LIT,
"the authored color returns"
);
}
#[test]
fn window_mode_grays_resolution_and_restores_the_windowed_size() {
let mut f = Fixture::new();
f.state.window_args.mode = WindowMode::Fullscreen;
f.state.window_args.width = 800;
f.state.window_args.height = 600;
f.apply(vec![cycle("window_mode", SettingOp::Next)]);
assert_eq!(f.state.window_args.mode, WindowMode::Windowed);
assert!(f.saw(&Call::SetWindowMode(WindowMode::Windowed)));
assert!(
f.saw(&Call::SetWindowSize(800, 600)),
"the remembered windowed size is re-applied"
);
assert_eq!(
f.label_color(RESOLUTION_LABEL),
super::rows::DISABLED_ROW_COLOR,
"Resolution is inert outside fullscreen"
);
}
#[test]
fn entering_fullscreen_restores_the_resolution_row() {
let mut f = Fixture::new();
f.apply(vec![cycle(
"window_mode",
SettingOp::SetIndex(settings::window_mode_index(WindowMode::Fullscreen)),
)]);
assert_eq!(f.state.window_args.mode, WindowMode::Fullscreen);
assert_eq!(f.label_color(RESOLUTION_LABEL), LIT);
assert!(
!f.calls
.lock()
.unwrap()
.calls
.iter()
.any(|c| matches!(c, Call::SetWindowSize(..))),
"only a return to windowed re-applies the size"
);
}
#[test]
fn resolution_cycles_the_enumerated_display_modes() {
let mut f = Fixture::new();
let modes = [
DisplayMode {
width: 1920,
height: 1080,
refresh_hz: 60,
},
DisplayMode {
width: 2560,
height: 1440,
refresh_hz: 165,
},
];
f.state.display_modes = modes.to_vec();
f.state.current_mode = Some(modes[0]);
f.next("resolution");
assert_eq!(f.state.resolution, Some(modes[1]));
assert!(f.saw(&Call::SetDisplayMode(modes[1])));
assert_eq!(f.label(VALUE_LABEL), modes[1].label());
assert_eq!(
f.persisted().graphics.resolution,
Some([2560, 1440, 165]),
"the mode persists as its three components"
);
}
#[test]
fn resolution_without_enumerated_modes_is_inert() {
let mut f = Fixture::new();
f.next("resolution");
assert!(f.state.resolution.is_none());
assert!(f.calls.lock().unwrap().calls.is_empty());
}
#[test]
fn quality_toggle_flips_the_master_preset_to_custom() {
let mut f = Fixture::with_profile(GpuProfile::UNKNOWN);
f.state.quality_preset = QualityPreset::High;
f.next("ssao");
assert!(f.saw(&Call::ApplyQualitySettings));
assert_eq!(f.state.quality_preset, QualityPreset::Custom);
assert_eq!(f.label(QUALITY_LABEL), QualityPreset::Custom.name());
let cfg = f.persisted();
assert_eq!(cfg.graphics.quality_preset, Some(QualityPreset::Custom));
assert!(cfg.graphics.ssao.is_some());
}
#[test]
fn auto_exposure_toggle_repushes_the_post_process_params() {
let mut f = Fixture::new();
f.next("auto_exposure");
assert!(f.saw(&Call::ApplyQualitySettings));
assert!(f.saw(&Call::UpdatePostProcess), "exposure reverts");
}
#[test]
fn quality_cycle_knob_rebuilds_and_flips_to_custom() {
let mut f = Fixture::new();
f.next("ssgi_rays");
assert!(f.saw(&Call::ApplyQualitySettings));
assert_eq!(f.state.quality_preset, QualityPreset::Custom);
assert!(f.persisted().graphics.ssgi_rays.is_some());
}
#[test]
fn aa_mode_cycle_refreshes_the_composite_fxaa_flag() {
let mut f = Fixture::new();
f.apply(vec![cycle(
"aa_mode",
SettingOp::SetIndex(settings::aa_mode_index(crate::components::AaMode::Fxaa)),
)]);
assert_eq!(f.state.post_config.aa_mode, crate::components::AaMode::Fxaa);
assert_eq!(f.state.post_process.fxaa, 1.0);
assert!(f.saw(&Call::UpdatePostProcess));
f.apply(vec![cycle(
"aa_mode",
SettingOp::SetIndex(settings::aa_mode_index(crate::components::AaMode::Off)),
)]);
assert_eq!(f.state.post_process.fxaa, 0.0);
}
#[test]
fn live_shadow_knobs_push_to_the_backend() {
let mut f = Fixture::new();
f.next("shadow_update");
assert!(f.saw(&Call::SetShadowUpdate));
assert_eq!(f.state.shadow_update, settings::shadow_update_at(1));
f.next("shadow_distance");
assert!(f.saw(&Call::SetShadowDistance(f.state.shadow_distance)));
f.next("shadow_cascades");
assert!(f.saw(&Call::SetShadowCascades(f.state.shadow_cascades)));
assert_eq!(f.state.quality_preset, QualityPreset::Custom);
let cfg = f.persisted();
assert!(cfg.graphics.shadow_update.is_some());
assert!(cfg.graphics.shadow_distance.is_some());
assert!(cfg.graphics.shadow_cascades.is_some());
}
#[test]
fn restart_required_rows_persist_without_a_backend_call() {
let mut f = Fixture::new();
for key in [
"render_scale",
"shadow_map_size",
"anisotropy",
"temporal_upscaling",
"hdr_display",
"hdr_pq",
"frames_in_flight",
"occlusion_two_pass",
"texture_quality",
"upscale_backend",
] {
f.next(key);
}
assert!(
f.calls.lock().unwrap().calls.is_empty(),
"no restart-required row applies live"
);
let cfg = f.persisted();
assert!(cfg.graphics.render_scale.is_some());
assert!(cfg.graphics.shadow_map_size.is_some());
assert!(cfg.graphics.anisotropy.is_some());
assert_eq!(cfg.graphics.temporal_upscaling, Some(true));
assert_eq!(cfg.graphics.hdr_display, Some(true));
assert_eq!(cfg.graphics.hdr_pq, Some(true));
assert!(cfg.graphics.frames_in_flight.is_some());
assert_eq!(cfg.graphics.occlusion_two_pass, Some(true));
assert!(cfg.graphics.texture_cap.is_some());
assert!(cfg.graphics.texture_budget.is_some());
assert!(cfg.graphics.upscale_backend.is_some());
}
#[test]
fn render_scale_flips_the_master_preset_to_custom() {
let mut f = Fixture::new();
f.state.quality_preset = QualityPreset::High;
f.next("render_scale");
assert_eq!(f.state.quality_preset, QualityPreset::Custom);
assert_eq!(f.label(QUALITY_LABEL), QualityPreset::Custom.name());
}
#[test]
fn upscale_backend_cycle_skips_unavailable_vendors() {
let mut f = Fixture::with_profile(GpuProfile::UNKNOWN);
assert_eq!(f.state.gpu_profile.vendor, GpuVendor::Other);
for _ in 0..settings::options("upscale_backend").unwrap().len() * 2 {
f.next("upscale_backend");
assert!(
settings::upscale_backend_available(f.state.upscale_backend, GpuVendor::Other),
"landed on an unavailable upscaler: {:?}",
f.state.upscale_backend
);
}
}
#[test]
fn upscale_backend_cycle_reaches_dlss_on_nvidia() {
let mut profile = GpuProfile::UNKNOWN;
profile.vendor = GpuVendor::Nvidia;
let mut f = Fixture::with_profile(profile);
let mut seen = false;
for _ in 0..settings::options("upscale_backend").unwrap().len() {
f.next("upscale_backend");
seen |= f.state.upscale_backend == crate::components::UpscalerBackend::Dlss;
}
assert!(seen, "DLSS is reachable on an NVIDIA device");
}
#[test]
fn graphics_quality_preset_clears_overrides_and_re_derives_the_rows() {
let mut f = Fixture::new();
f.state.authored_post_config.ssao = true;
f.state.authored_post_config.ssr = true;
f.state.post_config.ssao = false;
f.state
.cycle_value_labels
.insert("ssao".to_string(), TOGGLE_LABEL);
f.apply(vec![SettingCommand {
setting: "graphics_quality".to_string(),
op: SettingOp::SetIndex(crate::gfx::quality_preset::preset_index(
QualityPreset::Ultra,
)),
value_label: Some(QUALITY_LABEL),
persist: true,
}]);
assert_eq!(f.state.quality_preset, QualityPreset::Ultra);
assert!(
f.state.post_config.ssao,
"the authored feature is restored, not the cleared override"
);
assert!(f.saw(&Call::ApplyQualitySettings));
assert!(f.saw(&Call::UpdatePostProcess));
assert!(f.saw(&Call::SetShadowUpdate));
assert!(f.saw(&Call::SetShadowDistance(f.state.shadow_distance)));
assert!(f.saw(&Call::SetShadowCascades(f.state.shadow_cascades)));
assert_eq!(f.label(TOGGLE_LABEL), "On", "the dependent row relabelled");
assert_eq!(
f.label(QUALITY_LABEL),
crate::gfx::quality_preset::preset_label(QualityPreset::Ultra, &f.state.gpu_profile),
);
let cfg = f.persisted();
assert_eq!(cfg.graphics.quality_preset, Some(QualityPreset::Ultra));
assert_eq!(cfg.graphics.ssao, None);
assert_eq!(cfg.graphics.ssr, None);
assert_eq!(cfg.graphics.aa_mode, None);
assert_eq!(cfg.graphics.shadow_map_size, None);
assert_eq!(cfg.graphics.render_scale, None);
assert_eq!(cfg.graphics.anisotropy, None);
}
#[test]
fn low_preset_clamps_the_authored_features_off() {
let mut f = Fixture::new();
f.state.authored_post_config.ssao = true;
f.state.authored_post_config.indirect_lighting = IndirectLighting::Ssgi;
f.state.post_config = f.state.authored_post_config.clone();
f.apply(vec![SettingCommand {
setting: "graphics_quality".to_string(),
op: SettingOp::SetIndex(crate::gfx::quality_preset::preset_index(QualityPreset::Low)),
value_label: Some(QUALITY_LABEL),
persist: true,
}]);
let ceiling =
crate::gfx::quality_preset::resolve_ceiling(QualityPreset::Low, &f.state.gpu_profile);
assert!(!ceiling.ssgi, "the Low ceiling disallows SSGI");
assert_eq!(
f.state.post_config.indirect_lighting,
IndirectLighting::Ibl,
"the authored feature is clamped off"
);
}
#[test]
fn a_batch_persists_once_and_carries_the_cache_forward() {
let mut f = Fixture::new();
f.apply(vec![
cycle("vsync", SettingOp::Next),
cycle("occlusion_two_pass", SettingOp::Next),
]);
let cached = f.state.settings_cache.clone().expect("cache retained");
assert_eq!(cached.graphics.vsync, Some(true));
assert_eq!(cached.graphics.occlusion_two_pass, Some(true));
f.next("show_vram");
let cfg = f.persisted();
assert_eq!(
cfg.graphics.vsync,
Some(true),
"the earlier change survives"
);
assert_eq!(cfg.graphics.occlusion_two_pass, Some(true));
assert_eq!(cfg.graphics.show_vram, Some(true));
}
#[test]
fn an_empty_drain_persists_nothing() {
let mut f = Fixture::new();
f.apply(Vec::new());
assert!(f.calls.lock().unwrap().calls.is_empty());
assert!(f.saved.lock().unwrap().is_empty());
}
#[test]
fn hud_prefs_publish_under_the_master_toggle() {
let mut f = Fixture::new();
f.state.show_fps = true;
f.state.show_vram = true;
f.state.publish_hud_state(&mut f.world.ctx());
let prefs = *f.world.ctx().resource::<crate::ecs::HudPrefs>().unwrap();
assert!(prefs.show_fps);
assert!(prefs.show_vram);
f.state.perf_stats = false;
f.state.publish_hud_state(&mut f.world.ctx());
let prefs = *f.world.ctx().resource::<crate::ecs::HudPrefs>().unwrap();
assert!(!prefs.show_fps, "the master gates the sub-readout");
assert!(!prefs.show_vram);
}
#[test]
fn disabled_rows_publish_alongside_the_gray_out() {
let mut f = Fixture::new();
f.state.window_args.mode = WindowMode::Fullscreen;
f.state.publish_hud_state(&mut f.world.ctx());
let rows = f
.world
.ctx()
.resource::<crate::ecs::DisabledSettingRows>()
.map(|r| r.0.clone())
.unwrap();
assert!(rows.is_empty(), "every row is live in fullscreen");
f.state.perf_stats = false;
f.state.window_args.mode = WindowMode::Windowed;
f.state.publish_hud_state(&mut f.world.ctx());
let rows = f
.world
.ctx()
.resource::<crate::ecs::DisabledSettingRows>()
.map(|r| r.0.clone())
.unwrap();
assert!(rows.contains("show_fps"));
assert!(rows.contains("show_vram"));
assert!(rows.contains("resolution"));
}
#[test]
fn step_without_a_parked_state_is_a_noop() {
let mut f = Fixture::new();
let mut sys = super::SettingsSystem::new();
assert_eq!(
sys.step(&mut f.world.ctx()),
crate::ecs::StepResult::Continue
);
assert!(
f.world.ctx().resource::<crate::ecs::HudPrefs>().is_none(),
"nothing published without a state"
);
}
#[test]
fn step_without_a_backend_puts_the_state_back() {
let mut f = Fixture::new();
let mut sys = super::SettingsSystem::new();
f.world.resources.insert(super::SettingsSlot(Some(f.state)));
assert_eq!(
sys.step(&mut f.world.ctx()),
crate::ecs::StepResult::Continue
);
assert!(
f.world
.ctx()
.resources
.get_mut::<super::SettingsSlot>()
.is_some_and(|slot| slot.0.is_some()),
"the state is parked again for the next tick"
);
}
#[test]
fn step_drains_into_the_op_queue_and_reparks() {
let mut f = Fixture::new();
let mut sys = super::SettingsSystem::new();
{
let mut ctx = f.world.ctx();
ctx.events_mut::<SettingCommand>()
.send(cycle("vsync", SettingOp::Next));
}
f.world
.resources
.insert(crate::ecs::ActiveRenderQueues(Some(
crate::ecs::RenderQueues {
ops: Default::default(),
slots: crate::gfx::render_slots::RenderSlots::new(0, true, &[]),
},
)));
f.world.resources.insert(super::SettingsSlot(Some(f.state)));
assert_eq!(
sys.step(&mut f.world.ctx()),
crate::ecs::StepResult::Continue
);
assert!(f.world.ctx().resource::<crate::ecs::HudPrefs>().is_some());
assert!(
f.world
.ctx()
.resources
.get_mut::<super::SettingsSlot>()
.is_some_and(|slot| slot.0.is_some())
);
let mut queues = crate::ecs::ActiveRenderQueues::take(&mut f.world.resources)
.expect("the op queue is parked again");
queues.ops.replay(&mut f.backend);
assert!(
f.calls.lock().unwrap().saw(&Call::SetVsync(true)),
"the drained command reaches the backend at replay"
);
}