From a90d073c2250d7da1c88e55f8a36aa649d8ad7f2 Mon Sep 17 00:00:00 2001 From: Cody <Mason.Cody.M@gmail.com> Date: Wed, 5 Jun 2019 11:57:48 -0400 Subject: [PATCH 01/10] Adds mouse sensitivity sliders that update settings file. --- voxygen/src/hud/mod.rs | 8 ++++ voxygen/src/hud/settings_window.rs | 75 +++++++++++++++++++++++++++--- voxygen/src/session.rs | 14 +++++- voxygen/src/settings.rs | 14 ++++-- voxygen/src/window.rs | 6 +-- 5 files changed, 103 insertions(+), 14 deletions(-) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 71e76b92ba..e349a8c78c 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -108,6 +108,8 @@ pub struct DebugInfo { pub enum Event { SendMessage(String), + AdjustMousePan(u32), + AdjustMouseZoom(u32), AdjustViewDistance(u32), AdjustVolume(f32), ChangeAudioDevice(String), @@ -567,6 +569,12 @@ impl Hud { settings_window::Event::ToggleDebug => self.show.debug = !self.show.debug, settings_window::Event::ChangeTab(tab) => self.show.open_setting_tab(tab), settings_window::Event::Close => self.show.settings(false), + settings_window::Event::AdjustMousePan(sensitivity) => { + events.push(Event::AdjustMousePan(sensitivity)); + } + settings_window::Event::AdjustMouseZoom(sensitivity) => { + events.push(Event::AdjustMouseZoom(sensitivity)); + } settings_window::Event::AdjustViewDistance(view_distance) => { events.push(Event::AdjustViewDistance(view_distance)); } diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index 9c0a02c7c3..a0533f0938 100644 --- a/voxygen/src/hud/settings_window.rs +++ b/voxygen/src/hud/settings_window.rs @@ -31,6 +31,10 @@ widget_ids! { interface, inventory_test_button, inventory_test_button_label, + mouse_pan_slider, + mouse_pan_text, + mouse_zoom_slider, + mouse_zoom_text, settings_bg, sound, test, @@ -92,6 +96,8 @@ pub enum Event { ToggleDebug, ChangeTab(SettingsTab), Close, + AdjustMousePan(u32), + AdjustMouseZoom(u32), AdjustViewDistance(u32), AdjustVolume(f32), ChangeAudioDevice(String), @@ -160,7 +166,7 @@ impl<'a> Widget for SettingsWindow<'a> { .color(TEXT_COLOR) .set(state.ids.settings_title, ui); - // Interface + // 1) Interface Tab ------------------------------- if Button::image(if let SettingsTab::Interface = self.show.settings_tab { self.imgs.settings_button_pressed } else { @@ -187,6 +193,7 @@ impl<'a> Widget for SettingsWindow<'a> { events.push(Event::ChangeTab(SettingsTab::Interface)); } + // Contents if let SettingsTab::Interface = self.show.settings_tab { // Help let show_help = @@ -255,7 +262,7 @@ impl<'a> Widget for SettingsWindow<'a> { .set(state.ids.debug_button_label, ui); } - // 2 Gameplay + // 2) Gameplay Tab -------------------------------- if Button::image(if let SettingsTab::Gameplay = self.show.settings_tab { self.imgs.settings_button_pressed } else { @@ -282,7 +289,58 @@ impl<'a> Widget for SettingsWindow<'a> { events.push(Event::ChangeTab(SettingsTab::Gameplay)); } - // 3 Controls + // Contents + if let SettingsTab::Gameplay = self.show.settings_tab { + Text::new("Pan Sensitivity") + .top_left_with_margins_on(state.ids.settings_content, 10.0, 10.0) + .font_size(14) + .font_id(self.fonts.opensans) + .color(TEXT_COLOR) + .set(state.ids.mouse_pan_text, ui); + + if let Some(new_val) = ImageSlider::discrete( + (self.global_state.settings.gameplay.pan_sensitivity * 100.0) as u32, + 1, + 200, + self.imgs.slider_indicator, + self.imgs.slider, + ) + .w_h(208.0, 22.0) + .down_from(state.ids.mouse_pan_text, 10.0) + .track_breadth(30.0) + .slider_length(10.0) + .pad_track((5.0, 5.0)) + .set(state.ids.mouse_pan_slider, ui) + { + events.push(Event::AdjustMousePan(new_val)); + } + + Text::new("Zoom Sensitivity") + .down_from(state.ids.mouse_pan_slider, 10.0) + .font_size(14) + .font_id(self.fonts.opensans) + .color(TEXT_COLOR) + .set(state.ids.mouse_zoom_text, ui); + + if let Some(new_val) = ImageSlider::discrete( + (self.global_state.settings.gameplay.zoom_sensitivity * 100.0) as u32, + 1, + 200, + self.imgs.slider_indicator, + self.imgs.slider, + ) + .w_h(208.0, 22.0) + .down_from(state.ids.mouse_zoom_text, 10.0) + .track_breadth(30.0) + .slider_length(10.0) + .pad_track((5.0, 5.0)) + .set(state.ids.mouse_zoom_slider, ui) + { + events.push(Event::AdjustMouseZoom(new_val)); + } + } + + // 3) Controls Tab -------------------------------- if Button::image(if let SettingsTab::Controls = self.show.settings_tab { self.imgs.settings_button_pressed } else { @@ -308,6 +366,8 @@ impl<'a> Widget for SettingsWindow<'a> { { events.push(Event::ChangeTab(SettingsTab::Controls)); } + + // Contents if let SettingsTab::Controls = self.show.settings_tab { Text::new( "Free Cursor\n\ @@ -376,7 +436,6 @@ impl<'a> Widget for SettingsWindow<'a> { /tp [Name] - Teleports you to another player \n\ /jump <dx> <dy> <dz> - Offset your position \n\ /goto <x> <y> <z> - Teleport to a position \n\ - /tp <name> - Teleport to another player \n\ /kill - Kill yourself \n\ /pig - Spawn pig NPC \n\ /wolf - Spawn wolf NPC \n\ @@ -462,7 +521,8 @@ impl<'a> Widget for SettingsWindow<'a> { .font_size(18) .set(state.ids.controls_controls, ui); } - // 4 Video + + // 4) Video Tab ----------------------------------- if Button::image(if let SettingsTab::Video = self.show.settings_tab { self.imgs.settings_button_pressed } else { @@ -489,6 +549,7 @@ impl<'a> Widget for SettingsWindow<'a> { { events.push(Event::ChangeTab(SettingsTab::Video)); } + // Contents if let SettingsTab::Video = self.show.settings_tab { Text::new("View Distance") @@ -515,7 +576,8 @@ impl<'a> Widget for SettingsWindow<'a> { events.push(Event::AdjustViewDistance(new_val)); } } - // 5 Sound + + // 5) Sound Tab ----------------------------------- if Button::image(if let SettingsTab::Sound = self.show.settings_tab { self.imgs.settings_button_pressed } else { @@ -542,6 +604,7 @@ impl<'a> Widget for SettingsWindow<'a> { { events.push(Event::ChangeTab(SettingsTab::Sound)); } + // Contents if let SettingsTab::Sound = self.show.settings_tab { Text::new("Volume") diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 913e421e24..28aa3d8f2c 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -149,10 +149,10 @@ impl PlayState for SessionState { return PlayStateResult::Pop; } - // Maintain global state + // Maintain global state. global_state.maintain(); - // extract HUD events ensuring the client borrow gets dropped + // Extract HUD events ensuring the client borrow gets dropped. let hud_events = self.hud.maintain( &self.client.borrow(), global_state, @@ -170,6 +170,7 @@ impl PlayState for SessionState { }, &self.scene.camera(), ); + // Maintain the UI. for event in hud_events { match event { @@ -184,6 +185,15 @@ impl PlayState for SessionState { HudEvent::Quit => { return PlayStateResult::Shutdown; } + HudEvent::AdjustMousePan(sensitivity) => { + global_state.settings.gameplay.pan_sensitivity = sensitivity as f32 / 100.0; + global_state.settings.save_to_file(); + } + HudEvent::AdjustMouseZoom(sensitivity) => { + global_state.settings.gameplay.zoom_sensitivity = + sensitivity as f32 / 100.0; + global_state.settings.save_to_file(); + } HudEvent::AdjustViewDistance(view_distance) => { self.client.borrow_mut().set_view_distance(view_distance); diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index b9d89db96e..488413ef02 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -4,11 +4,12 @@ use glutin::{MouseButton, VirtualKeyCode}; use serde_derive::{Deserialize, Serialize}; use std::{fs, io::prelude::*, path::PathBuf}; -/// `Settings` contains everything that can be configured in the Settings.toml file. +/// `Settings` contains everything that can be configured in the settings.ron file. #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] pub struct Settings { pub controls: ControlSettings, + pub gameplay: GameplaySettings, pub networking: NetworkingSettings, pub log: Log, pub graphics: GraphicsSettings, @@ -41,9 +42,14 @@ pub struct ControlSettings { pub fullscreen: KeyMouse, pub screenshot: KeyMouse, pub toggle_ingame_ui: KeyMouse, + pub attack: KeyMouse, +} + +/// `GameplaySettings` contains sensitivity and gameplay options. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct GameplaySettings { pub pan_sensitivity: f32, pub zoom_sensitivity: f32, - pub attack: KeyMouse, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -100,9 +106,11 @@ impl Default for Settings { fullscreen: KeyMouse::Key(VirtualKeyCode::F11), screenshot: KeyMouse::Key(VirtualKeyCode::F4), toggle_ingame_ui: KeyMouse::Key(VirtualKeyCode::F6), + attack: KeyMouse::Mouse(MouseButton::Left), + }, + gameplay: GameplaySettings { pan_sensitivity: 1.0, zoom_sensitivity: 1.0, - attack: KeyMouse::Mouse(MouseButton::Left), }, networking: NetworkingSettings { username: "Username".to_string(), diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index 967dc2a0af..de1f02d94f 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -54,7 +54,7 @@ pub enum Event { InputUpdate(GameInput, bool), /// Event that the ui uses. Ui(ui::Event), - // The view distance has been changed + /// The view distance has changed. ViewDistanceChanged(u32), /// Game settings have changed. SettingsChanged, @@ -140,8 +140,8 @@ impl Window { renderer: Renderer::new(device, factory, win_color_view, win_depth_view)?, window, cursor_grabbed: false, - pan_sensitivity: settings.controls.pan_sensitivity, - zoom_sensitivity: settings.controls.zoom_sensitivity, + pan_sensitivity: settings.gameplay.pan_sensitivity, + zoom_sensitivity: settings.gameplay.zoom_sensitivity, fullscreen: false, needs_refresh_resize: false, key_map, From 41f3cd58039102ca61831445c99838ed5b60a5c6 Mon Sep 17 00:00:00 2001 From: Cody <Mason.Cody.M@gmail.com> Date: Thu, 6 Jun 2019 13:42:13 -0400 Subject: [PATCH 02/10] Changes setting type to u32, enforces live in-game updates to setting. --- voxygen/src/hud/settings_window.rs | 39 ++++++++++++++++++++++-------- voxygen/src/session.rs | 7 +++--- voxygen/src/settings.rs | 8 +++--- voxygen/src/window.rs | 10 ++++---- 4 files changed, 42 insertions(+), 22 deletions(-) diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index a0533f0938..0acea547e6 100644 --- a/voxygen/src/hud/settings_window.rs +++ b/voxygen/src/hud/settings_window.rs @@ -32,9 +32,11 @@ widget_ids! { inventory_test_button, inventory_test_button_label, mouse_pan_slider, - mouse_pan_text, + mouse_pan_label, + mouse_pan_value, mouse_zoom_slider, - mouse_zoom_text, + mouse_zoom_label, + mouse_zoom_value, settings_bg, sound, test, @@ -291,22 +293,25 @@ impl<'a> Widget for SettingsWindow<'a> { // Contents if let SettingsTab::Gameplay = self.show.settings_tab { + let display_pan = self.global_state.settings.gameplay.pan_sensitivity; + let display_zoom = self.global_state.settings.gameplay.zoom_sensitivity; + Text::new("Pan Sensitivity") .top_left_with_margins_on(state.ids.settings_content, 10.0, 10.0) .font_size(14) .font_id(self.fonts.opensans) .color(TEXT_COLOR) - .set(state.ids.mouse_pan_text, ui); + .set(state.ids.mouse_pan_label, ui); if let Some(new_val) = ImageSlider::discrete( - (self.global_state.settings.gameplay.pan_sensitivity * 100.0) as u32, + display_pan, 1, 200, self.imgs.slider_indicator, self.imgs.slider, ) - .w_h(208.0, 22.0) - .down_from(state.ids.mouse_pan_text, 10.0) + .w_h(550.0, 22.0) + .down_from(state.ids.mouse_pan_label, 10.0) .track_breadth(30.0) .slider_length(10.0) .pad_track((5.0, 5.0)) @@ -315,22 +320,29 @@ impl<'a> Widget for SettingsWindow<'a> { events.push(Event::AdjustMousePan(new_val)); } + Text::new(&format!("{}", display_pan)) + .right_from(state.ids.mouse_pan_slider, 8.0) + .font_size(14) + .font_id(self.fonts.opensans) + .color(TEXT_COLOR) + .set(state.ids.mouse_pan_value, ui); + Text::new("Zoom Sensitivity") .down_from(state.ids.mouse_pan_slider, 10.0) .font_size(14) .font_id(self.fonts.opensans) .color(TEXT_COLOR) - .set(state.ids.mouse_zoom_text, ui); + .set(state.ids.mouse_zoom_label, ui); if let Some(new_val) = ImageSlider::discrete( - (self.global_state.settings.gameplay.zoom_sensitivity * 100.0) as u32, + display_zoom, 1, 200, self.imgs.slider_indicator, self.imgs.slider, ) - .w_h(208.0, 22.0) - .down_from(state.ids.mouse_zoom_text, 10.0) + .w_h(550.0, 22.0) + .down_from(state.ids.mouse_zoom_label, 10.0) .track_breadth(30.0) .slider_length(10.0) .pad_track((5.0, 5.0)) @@ -338,6 +350,13 @@ impl<'a> Widget for SettingsWindow<'a> { { events.push(Event::AdjustMouseZoom(new_val)); } + + Text::new(&format!("{}", display_zoom)) + .right_from(state.ids.mouse_zoom_slider, 8.0) + .font_size(14) + .font_id(self.fonts.opensans) + .color(TEXT_COLOR) + .set(state.ids.mouse_zoom_value, ui); } // 3) Controls Tab -------------------------------- diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 28aa3d8f2c..253737924e 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -186,12 +186,13 @@ impl PlayState for SessionState { return PlayStateResult::Shutdown; } HudEvent::AdjustMousePan(sensitivity) => { - global_state.settings.gameplay.pan_sensitivity = sensitivity as f32 / 100.0; + global_state.window.pan_sensitivity = sensitivity; + global_state.settings.gameplay.pan_sensitivity = sensitivity; global_state.settings.save_to_file(); } HudEvent::AdjustMouseZoom(sensitivity) => { - global_state.settings.gameplay.zoom_sensitivity = - sensitivity as f32 / 100.0; + global_state.window.zoom_sensitivity = sensitivity; + global_state.settings.gameplay.zoom_sensitivity = sensitivity; global_state.settings.save_to_file(); } HudEvent::AdjustViewDistance(view_distance) => { diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 488413ef02..63e1cccfa6 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -48,8 +48,8 @@ pub struct ControlSettings { /// `GameplaySettings` contains sensitivity and gameplay options. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct GameplaySettings { - pub pan_sensitivity: f32, - pub zoom_sensitivity: f32, + pub pan_sensitivity: u32, + pub zoom_sensitivity: u32, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -109,8 +109,8 @@ impl Default for Settings { attack: KeyMouse::Mouse(MouseButton::Left), }, gameplay: GameplaySettings { - pan_sensitivity: 1.0, - zoom_sensitivity: 1.0, + pan_sensitivity: 100, + zoom_sensitivity: 100, }, networking: NetworkingSettings { username: "Username".to_string(), diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index de1f02d94f..fd158a4708 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -71,8 +71,8 @@ pub struct Window { renderer: Renderer, window: glutin::GlWindow, cursor_grabbed: bool, - pub pan_sensitivity: f32, - pub zoom_sensitivity: f32, + pub pan_sensitivity: u32, + pub zoom_sensitivity: u32, fullscreen: bool, needs_refresh_resize: bool, key_map: HashMap<KeyMouse, GameInput>, @@ -230,14 +230,14 @@ impl Window { glutin::DeviceEvent::MouseMotion { delta: (dx, dy), .. } if cursor_grabbed && *focused => events.push(Event::CursorPan(Vec2::new( - dx as f32 * pan_sensitivity, - dy as f32 * pan_sensitivity, + dx as f32 * (pan_sensitivity as f32 / 100.0), + dy as f32 * (pan_sensitivity as f32 / 100.0), ))), glutin::DeviceEvent::MouseWheel { delta: glutin::MouseScrollDelta::LineDelta(_x, y), .. } if cursor_grabbed && *focused => { - events.push(Event::Zoom(y as f32 * zoom_sensitivity)) + events.push(Event::Zoom(y * (zoom_sensitivity as f32 / 100.0))) } _ => {} }, From 8a19a6f2a365fdc90b5f6fb8c3fe0a2823c4a21d Mon Sep 17 00:00:00 2001 From: Monty Marz <m.marzouq@gmx.de> Date: Thu, 6 Jun 2019 17:54:11 +0000 Subject: [PATCH 03/10] Configurable max fps --- voxygen/src/hud/mod.rs | 4 ++++ voxygen/src/hud/settings_window.rs | 29 ++++++++++++++++++++++++++++- voxygen/src/session.rs | 2 +- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index e349a8c78c..a035d17316 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -113,6 +113,7 @@ pub enum Event { AdjustViewDistance(u32), AdjustVolume(f32), ChangeAudioDevice(String), + MaximumFPS(u32), CharacterSelection, Logout, Quit, @@ -581,6 +582,9 @@ impl Hud { settings_window::Event::AdjustVolume(volume) => { events.push(Event::AdjustVolume(volume)); } + settings_window::Event::MaximumFPS(max_fps) => { + events.push(Event::MaximumFPS(max_fps)); + } settings_window::Event::ChangeAudioDevice(name) => { events.push(Event::ChangeAudioDevice(name)); } diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index 0acea547e6..d6e661e06f 100644 --- a/voxygen/src/hud/settings_window.rs +++ b/voxygen/src/hud/settings_window.rs @@ -43,6 +43,9 @@ widget_ids! { video, vd_slider, vd_slider_text, + max_fps_slider, + max_fps_text, + max_fps_value, audio_volume_slider, audio_volume_text, audio_device_list, @@ -103,6 +106,7 @@ pub enum Event { AdjustViewDistance(u32), AdjustVolume(f32), ChangeAudioDevice(String), + MaximumFPS(u32), } impl<'a> Widget for SettingsWindow<'a> { @@ -586,7 +590,7 @@ impl<'a> Widget for SettingsWindow<'a> { self.imgs.slider, ) .w_h(104.0, 22.0) - .down_from(state.ids.vd_slider_text, 10.0) + .down_from(state.ids.vd_slider_text, 8.0) .track_breadth(12.0) .slider_length(10.0) .pad_track((5.0, 5.0)) @@ -594,6 +598,29 @@ impl<'a> Widget for SettingsWindow<'a> { { events.push(Event::AdjustViewDistance(new_val)); } + Text::new("Maximum FPS") + .top_left_with_margins_on(state.ids.settings_content, 60.0, 10.0) + .font_size(14) + .font_id(self.fonts.opensans) + .color(TEXT_COLOR) + .set(state.ids.max_fps_text, ui); + + if let Some(new_val) = ImageSlider::discrete( + self.global_state.settings.graphics.view_distance, + 50, + 150, + self.imgs.slider_indicator, + self.imgs.slider, + ) + .w_h(104.0, 22.0) + .down_from(state.ids.max_fps_text, 8.0) + .track_breadth(12.0) + .slider_length(10.0) + .pad_track((5.0, 5.0)) + .set(state.ids.max_fps_slider, ui) + { + events.push(Event::MaximumFPS(new_val)); + } } // 5) Sound Tab ----------------------------------- diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 253737924e..ea1d01d3d3 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -13,7 +13,7 @@ use log::{error, warn}; use std::{cell::RefCell, rc::Rc, time::Duration}; use vek::*; -const FPS: u64 = 60; +const FPS: u64 = 1000; pub struct SessionState { scene: Scene, From 527f33a7784d93969f3a722bb0eacef8b9802b8c Mon Sep 17 00:00:00 2001 From: Cody <Mason.Cody.M@gmail.com> Date: Thu, 6 Jun 2019 15:11:39 -0400 Subject: [PATCH 04/10] Adds an FPS setting and slider. --- chat-cli/src/main.rs | 4 +-- voxygen/src/hud/mod.rs | 4 +-- voxygen/src/hud/settings_window.rs | 47 +++++++++++++++++++++----- voxygen/src/menu/char_selection/mod.rs | 6 ++-- voxygen/src/menu/main/mod.rs | 6 ++-- voxygen/src/session.rs | 20 ++++++++--- voxygen/src/settings.rs | 9 +++-- 7 files changed, 70 insertions(+), 26 deletions(-) diff --git a/chat-cli/src/main.rs b/chat-cli/src/main.rs index f68a5b47e9..bd0fb66bc2 100644 --- a/chat-cli/src/main.rs +++ b/chat-cli/src/main.rs @@ -3,7 +3,7 @@ use common::{clock::Clock, comp}; use log::{error, info}; use std::time::Duration; -const FPS: u64 = 60; +const TICK_RATE: u64 = 10; // Low value is okay, just reading messages. fn main() { // Initialize logging. @@ -48,6 +48,6 @@ fn main() { client.cleanup(); // Wait for the next tick. - clock.tick(Duration::from_millis(1000 / FPS)); + clock.tick(Duration::from_millis(1000 / TICK_RATE)); } } diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index a035d17316..ccafa92385 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -113,7 +113,7 @@ pub enum Event { AdjustViewDistance(u32), AdjustVolume(f32), ChangeAudioDevice(String), - MaximumFPS(u32), + ChangeMaxFPS(u32), CharacterSelection, Logout, Quit, @@ -583,7 +583,7 @@ impl Hud { events.push(Event::AdjustVolume(volume)); } settings_window::Event::MaximumFPS(max_fps) => { - events.push(Event::MaximumFPS(max_fps)); + events.push(Event::ChangeMaxFPS(max_fps)); } settings_window::Event::ChangeAudioDevice(name) => { events.push(Event::ChangeAudioDevice(name)); diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index d6e661e06f..c4fcf0b1b6 100644 --- a/voxygen/src/hud/settings_window.rs +++ b/voxygen/src/hud/settings_window.rs @@ -8,6 +8,9 @@ use conrod_core::{ widget::{self, Button, DropDownList, Image, Rectangle, Scrollbar, Text}, widget_ids, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, }; + +const FPS_CHOICES: [u32; 11] = [1, 30, 40, 50, 60, 90, 120, 144, 240, 300, 500]; + widget_ids! { struct Ids { settings_content, @@ -42,7 +45,8 @@ widget_ids! { test, video, vd_slider, - vd_slider_text, + vd_text, + vd_value, max_fps_slider, max_fps_text, max_fps_value, @@ -300,6 +304,7 @@ impl<'a> Widget for SettingsWindow<'a> { let display_pan = self.global_state.settings.gameplay.pan_sensitivity; let display_zoom = self.global_state.settings.gameplay.zoom_sensitivity; + // Mouse Pan Sensitivity Text::new("Pan Sensitivity") .top_left_with_margins_on(state.ids.settings_content, 10.0, 10.0) .font_size(14) @@ -331,6 +336,7 @@ impl<'a> Widget for SettingsWindow<'a> { .color(TEXT_COLOR) .set(state.ids.mouse_pan_value, ui); + // Mouse Zoom Sensitivity Text::new("Zoom Sensitivity") .down_from(state.ids.mouse_pan_slider, 10.0) .font_size(14) @@ -575,12 +581,13 @@ impl<'a> Widget for SettingsWindow<'a> { // Contents if let SettingsTab::Video = self.show.settings_tab { + // View Distance Text::new("View Distance") .top_left_with_margins_on(state.ids.settings_content, 10.0, 10.0) .font_size(14) .font_id(self.fonts.opensans) .color(TEXT_COLOR) - .set(state.ids.vd_slider_text, ui); + .set(state.ids.vd_text, ui); if let Some(new_val) = ImageSlider::discrete( self.global_state.settings.graphics.view_distance, @@ -590,7 +597,7 @@ impl<'a> Widget for SettingsWindow<'a> { self.imgs.slider, ) .w_h(104.0, 22.0) - .down_from(state.ids.vd_slider_text, 8.0) + .down_from(state.ids.vd_text, 8.0) .track_breadth(12.0) .slider_length(10.0) .pad_track((5.0, 5.0)) @@ -598,17 +605,32 @@ impl<'a> Widget for SettingsWindow<'a> { { events.push(Event::AdjustViewDistance(new_val)); } + + Text::new(&format!( + "{}", + self.global_state.settings.graphics.view_distance + )) + .right_from(state.ids.vd_slider, 8.0) + .font_size(14) + .font_id(self.fonts.opensans) + .color(TEXT_COLOR) + .set(state.ids.vd_value, ui); + + // Max FPS Text::new("Maximum FPS") - .top_left_with_margins_on(state.ids.settings_content, 60.0, 10.0) + .down_from(state.ids.vd_slider, 10.0) .font_size(14) .font_id(self.fonts.opensans) .color(TEXT_COLOR) .set(state.ids.max_fps_text, ui); - if let Some(new_val) = ImageSlider::discrete( - self.global_state.settings.graphics.view_distance, - 50, - 150, + if let Some(which) = ImageSlider::discrete( + FPS_CHOICES + .iter() + .position(|&x| x == self.global_state.settings.graphics.max_fps) + .unwrap_or(0), + 1, + 10, self.imgs.slider_indicator, self.imgs.slider, ) @@ -619,8 +641,15 @@ impl<'a> Widget for SettingsWindow<'a> { .pad_track((5.0, 5.0)) .set(state.ids.max_fps_slider, ui) { - events.push(Event::MaximumFPS(new_val)); + events.push(Event::MaximumFPS(FPS_CHOICES[which])); } + + Text::new(&format!("{}", self.global_state.settings.graphics.max_fps)) + .right_from(state.ids.max_fps_slider, 8.0) + .font_size(14) + .font_id(self.fonts.opensans) + .color(TEXT_COLOR) + .set(state.ids.max_fps_value, ui); } // 5) Sound Tab ----------------------------------- diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index 2192ce711d..e8e34a3247 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -14,8 +14,6 @@ use std::{cell::RefCell, rc::Rc, time::Duration}; use ui::CharSelectionUi; use vek::*; -const FPS: u64 = 60; - pub struct CharSelectionState { char_selection_ui: CharSelectionUi, client: Rc<RefCell<Client>>, @@ -125,7 +123,9 @@ impl PlayState for CharSelectionState { .expect("Failed to swap window buffers"); // Wait for the next tick. - clock.tick(Duration::from_millis(1000 / FPS)); + clock.tick(Duration::from_millis( + 1000 / (global_state.settings.graphics.max_fps as u64), + )); current_client_state = self.client.borrow().get_client_state(); } diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index 0fe4faf702..59c3cfb38c 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -12,8 +12,6 @@ use std::time::Duration; use ui::{Event as MainMenuEvent, MainMenuUi}; use vek::*; -const FPS: u64 = 60; - pub struct MainMenuState { main_menu_ui: MainMenuUi, } @@ -133,7 +131,9 @@ impl PlayState for MainMenuState { .expect("Failed to swap window buffers!"); // Wait for the next tick - clock.tick(Duration::from_millis(1000 / FPS)); + clock.tick(Duration::from_millis( + 1000 / (global_state.settings.graphics.max_fps as u64), + )); } } diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index ea1d01d3d3..6e20d3e725 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -13,8 +13,6 @@ use log::{error, warn}; use std::{cell::RefCell, rc::Rc, time::Duration}; use vek::*; -const FPS: u64 = 1000; - pub struct SessionState { scene: Scene, client: Rc<RefCell<Client>>, @@ -188,12 +186,16 @@ impl PlayState for SessionState { HudEvent::AdjustMousePan(sensitivity) => { global_state.window.pan_sensitivity = sensitivity; global_state.settings.gameplay.pan_sensitivity = sensitivity; - global_state.settings.save_to_file(); + if let Err(err) = global_state.settings.save_to_file() { + warn!("Failed to save settings: {:?}", err); + } } HudEvent::AdjustMouseZoom(sensitivity) => { global_state.window.zoom_sensitivity = sensitivity; global_state.settings.gameplay.zoom_sensitivity = sensitivity; - global_state.settings.save_to_file(); + if let Err(err) = global_state.settings.save_to_file() { + warn!("Failed to save settings: {:?}", err); + } } HudEvent::AdjustViewDistance(view_distance) => { self.client.borrow_mut().set_view_distance(view_distance); @@ -219,6 +221,12 @@ impl PlayState for SessionState { warn!("Failed to save settings!\n{:?}", err); } } + HudEvent::ChangeMaxFPS(fps) => { + global_state.settings.graphics.max_fps = fps; + if let Err(err) = global_state.settings.save_to_file() { + warn!("Failed to save settings!\n{:?}", err); + } + } } } @@ -236,7 +244,9 @@ impl PlayState for SessionState { .expect("Failed to swap window buffers!"); // Wait for the next tick. - clock.tick(Duration::from_millis(1000 / FPS)); + clock.tick(Duration::from_millis( + 1000 / global_state.settings.graphics.max_fps as u64, + )); // Clean things up after the tick. self.cleanup(); diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 63e1cccfa6..635cbf0d39 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -64,12 +64,14 @@ pub struct Log { pub file: PathBuf, } +/// `GraphicsSettings` contains settings related to framerate and in-game visuals. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct GraphicsSettings { pub view_distance: u32, + pub max_fps: u32, } -/// AudioSettings controls the volume of different audio subsystems and which +/// `AudioSettings` controls the volume of different audio subsystems and which /// device is used. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct AudioSettings { @@ -120,7 +122,10 @@ impl Default for Settings { log: Log { file: "voxygen.log".into(), }, - graphics: GraphicsSettings { view_distance: 5 }, + graphics: GraphicsSettings { + view_distance: 5, + max_fps: 60, + }, audio: AudioSettings { music_volume: 0.5, sfx_volume: 0.5, From 24f7323d04d1236bdec8ac4ba567be7c688a870e Mon Sep 17 00:00:00 2001 From: Cody <Mason.Cody.M@gmail.com> Date: Thu, 6 Jun 2019 15:23:51 -0400 Subject: [PATCH 05/10] Allows selection of an unstable 1 FPS option. --- voxygen/src/hud/settings_window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index c4fcf0b1b6..f725a95b15 100644 --- a/voxygen/src/hud/settings_window.rs +++ b/voxygen/src/hud/settings_window.rs @@ -629,7 +629,7 @@ impl<'a> Widget for SettingsWindow<'a> { .iter() .position(|&x| x == self.global_state.settings.graphics.max_fps) .unwrap_or(0), - 1, + 0, 10, self.imgs.slider_indicator, self.imgs.slider, From e18947eed10bd7ce421f1e114fc0f666ddfa2cde Mon Sep 17 00:00:00 2001 From: Cody <Mason.Cody.M@gmail.com> Date: Thu, 6 Jun 2019 15:26:01 -0400 Subject: [PATCH 06/10] Changes the fallback slider value to a safe one. --- voxygen/src/hud/settings_window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index f725a95b15..8413f19482 100644 --- a/voxygen/src/hud/settings_window.rs +++ b/voxygen/src/hud/settings_window.rs @@ -628,7 +628,7 @@ impl<'a> Widget for SettingsWindow<'a> { FPS_CHOICES .iter() .position(|&x| x == self.global_state.settings.graphics.max_fps) - .unwrap_or(0), + .unwrap_or(5), 0, 10, self.imgs.slider_indicator, From 026ac3297257a9ee3eaf2c1d965f575a0906a066 Mon Sep 17 00:00:00 2001 From: Cody <Mason.Cody.M@gmail.com> Date: Thu, 6 Jun 2019 15:32:47 -0400 Subject: [PATCH 07/10] Amends the suggested 1 FPS option to 15 to prevent accidental session crashes. --- voxygen/src/hud/settings_window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index 8413f19482..1f7083e04f 100644 --- a/voxygen/src/hud/settings_window.rs +++ b/voxygen/src/hud/settings_window.rs @@ -9,7 +9,7 @@ use conrod_core::{ widget_ids, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, }; -const FPS_CHOICES: [u32; 11] = [1, 30, 40, 50, 60, 90, 120, 144, 240, 300, 500]; +const FPS_CHOICES: [u32; 11] = [15, 30, 40, 50, 60, 90, 120, 144, 240, 300, 500]; widget_ids! { struct Ids { From b180f89104f18c68212bf3aebae4666cb706ba5e Mon Sep 17 00:00:00 2001 From: Cody <Mason.Cody.M@gmail.com> Date: Sat, 8 Jun 2019 19:35:23 -0400 Subject: [PATCH 08/10] Enhances deserialization so settings which are missing are added with default values. --- voxygen/src/settings.rs | 177 +++++++++++++++++++++++++--------------- 1 file changed, 110 insertions(+), 67 deletions(-) diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 635cbf0d39..0e8d73cc18 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -4,21 +4,9 @@ use glutin::{MouseButton, VirtualKeyCode}; use serde_derive::{Deserialize, Serialize}; use std::{fs, io::prelude::*, path::PathBuf}; -/// `Settings` contains everything that can be configured in the settings.ron file. -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(default)] -pub struct Settings { - pub controls: ControlSettings, - pub gameplay: GameplaySettings, - pub networking: NetworkingSettings, - pub log: Log, - pub graphics: GraphicsSettings, - pub audio: AudioSettings, - pub show_disclaimer: bool, -} - /// `ControlSettings` contains keybindings. #[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(default)] pub struct ControlSettings { pub toggle_cursor: KeyMouse, pub escape: KeyMouse, @@ -45,35 +33,108 @@ pub struct ControlSettings { pub attack: KeyMouse, } +impl Default for ControlSettings { + fn default() -> Self { + Self { + toggle_cursor: KeyMouse::Key(VirtualKeyCode::Tab), + escape: KeyMouse::Key(VirtualKeyCode::Escape), + enter: KeyMouse::Key(VirtualKeyCode::Return), + move_forward: KeyMouse::Key(VirtualKeyCode::W), + move_left: KeyMouse::Key(VirtualKeyCode::A), + move_back: KeyMouse::Key(VirtualKeyCode::S), + move_right: KeyMouse::Key(VirtualKeyCode::D), + jump: KeyMouse::Key(VirtualKeyCode::Space), + glide: KeyMouse::Key(VirtualKeyCode::LShift), + map: KeyMouse::Key(VirtualKeyCode::M), + bag: KeyMouse::Key(VirtualKeyCode::B), + quest_log: KeyMouse::Key(VirtualKeyCode::L), + character_window: KeyMouse::Key(VirtualKeyCode::C), + social: KeyMouse::Key(VirtualKeyCode::O), + spellbook: KeyMouse::Key(VirtualKeyCode::P), + settings: KeyMouse::Key(VirtualKeyCode::N), + help: KeyMouse::Key(VirtualKeyCode::F1), + toggle_interface: KeyMouse::Key(VirtualKeyCode::F2), + toggle_debug: KeyMouse::Key(VirtualKeyCode::F3), + fullscreen: KeyMouse::Key(VirtualKeyCode::F11), + screenshot: KeyMouse::Key(VirtualKeyCode::F4), + toggle_ingame_ui: KeyMouse::Key(VirtualKeyCode::F6), + attack: KeyMouse::Mouse(MouseButton::Left), + } + } +} + /// `GameplaySettings` contains sensitivity and gameplay options. #[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(default)] pub struct GameplaySettings { pub pan_sensitivity: u32, pub zoom_sensitivity: u32, } +impl Default for GameplaySettings { + fn default() -> Self { + Self { + pan_sensitivity: 100, + zoom_sensitivity: 100, + } + } +} + +/// `NetworkingSettings` stores server and networking settings. #[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(default)] pub struct NetworkingSettings { pub username: String, pub servers: Vec<String>, pub default_server: usize, } +impl Default for NetworkingSettings { + fn default() -> Self { + Self { + username: "Username".to_string(), + servers: vec!["server.veloren.net".to_string()], + default_server: 0, + } + } +} + +/// `Log` stores the name to the log file. #[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(default)] pub struct Log { pub file: PathBuf, } +impl Default for Log { + fn default() -> Self { + Self { + file: "voxygen.log".into(), + } + } +} + /// `GraphicsSettings` contains settings related to framerate and in-game visuals. #[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(default)] pub struct GraphicsSettings { pub view_distance: u32, pub max_fps: u32, } +impl Default for GraphicsSettings { + fn default() -> Self { + Self { + view_distance: 5, + max_fps: 60, + } + } +} + /// `AudioSettings` controls the volume of different audio subsystems and which /// device is used. #[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(default)] pub struct AudioSettings { pub music_volume: f32, pub sfx_volume: f32, @@ -82,55 +143,38 @@ pub struct AudioSettings { pub audio_device: Option<String>, } +impl Default for AudioSettings { + fn default() -> Self { + Self { + music_volume: 0.5, + sfx_volume: 0.5, + audio_device: None, + } + } +} + +/// `Settings` contains everything that can be configured in the settings.ron file. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(default)] +pub struct Settings { + pub controls: ControlSettings, + pub gameplay: GameplaySettings, + pub networking: NetworkingSettings, + pub log: Log, + pub graphics: GraphicsSettings, + pub audio: AudioSettings, + pub show_disclaimer: bool, +} + impl Default for Settings { fn default() -> Self { Settings { - controls: ControlSettings { - toggle_cursor: KeyMouse::Key(VirtualKeyCode::Tab), - escape: KeyMouse::Key(VirtualKeyCode::Escape), - enter: KeyMouse::Key(VirtualKeyCode::Return), - move_forward: KeyMouse::Key(VirtualKeyCode::W), - move_left: KeyMouse::Key(VirtualKeyCode::A), - move_back: KeyMouse::Key(VirtualKeyCode::S), - move_right: KeyMouse::Key(VirtualKeyCode::D), - jump: KeyMouse::Key(VirtualKeyCode::Space), - glide: KeyMouse::Key(VirtualKeyCode::LShift), - map: KeyMouse::Key(VirtualKeyCode::M), - bag: KeyMouse::Key(VirtualKeyCode::B), - quest_log: KeyMouse::Key(VirtualKeyCode::L), - character_window: KeyMouse::Key(VirtualKeyCode::C), - social: KeyMouse::Key(VirtualKeyCode::O), - spellbook: KeyMouse::Key(VirtualKeyCode::P), - settings: KeyMouse::Key(VirtualKeyCode::N), - help: KeyMouse::Key(VirtualKeyCode::F1), - toggle_interface: KeyMouse::Key(VirtualKeyCode::F2), - toggle_debug: KeyMouse::Key(VirtualKeyCode::F3), - fullscreen: KeyMouse::Key(VirtualKeyCode::F11), - screenshot: KeyMouse::Key(VirtualKeyCode::F4), - toggle_ingame_ui: KeyMouse::Key(VirtualKeyCode::F6), - attack: KeyMouse::Mouse(MouseButton::Left), - }, - gameplay: GameplaySettings { - pan_sensitivity: 100, - zoom_sensitivity: 100, - }, - networking: NetworkingSettings { - username: "Username".to_string(), - servers: vec!["server.veloren.net".to_string()], - default_server: 0, - }, - log: Log { - file: "voxygen.log".into(), - }, - graphics: GraphicsSettings { - view_distance: 5, - max_fps: 60, - }, - audio: AudioSettings { - music_volume: 0.5, - sfx_volume: 0.5, - audio_device: None, - }, + controls: ControlSettings::default(), + gameplay: GameplaySettings::default(), + networking: NetworkingSettings::default(), + log: Log::default(), + graphics: GraphicsSettings::default(), + audio: AudioSettings::default(), show_disclaimer: true, } } @@ -150,23 +194,22 @@ impl Settings { pub fn save_to_file(&self) -> std::io::Result<()> { let path = Settings::get_settings_path(); - if let Some(dir) = path.parent() { fs::create_dir_all(dir)?; } - let mut config_file = fs::File::create(path)?; + let s: &str = &ron::ser::to_string_pretty(self, ron::ser::PrettyConfig::default()).unwrap(); config_file.write_all(s.as_bytes()).unwrap(); Ok(()) } fn get_settings_path() -> PathBuf { - let proj_dirs = - ProjectDirs::from("net", "veloren", "voxygen").expect("No home directory defined!"); - let path = proj_dirs.config_dir(); - path.join("settings"); - let path = path.with_extension("ron"); - path + let proj_dirs = ProjectDirs::from("net", "veloren", "voxygen") + .expect("System's $HOME directory path not found!"); + proj_dirs + .config_dir() + .join("settings") + .with_extension("ron") } } From ea207b911a21fbcd4fe475274535bec727355993 Mon Sep 17 00:00:00 2001 From: Cody <Mason.Cody.M@gmail.com> Date: Sat, 8 Jun 2019 19:49:48 -0400 Subject: [PATCH 09/10] Amends a variable name. --- chat-cli/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chat-cli/src/main.rs b/chat-cli/src/main.rs index bd0fb66bc2..fdf8607bf9 100644 --- a/chat-cli/src/main.rs +++ b/chat-cli/src/main.rs @@ -3,7 +3,7 @@ use common::{clock::Clock, comp}; use log::{error, info}; use std::time::Duration; -const TICK_RATE: u64 = 10; // Low value is okay, just reading messages. +const TPS: u64 = 10; // Low value is okay, just reading messages. fn main() { // Initialize logging. @@ -48,6 +48,6 @@ fn main() { client.cleanup(); // Wait for the next tick. - clock.tick(Duration::from_millis(1000 / TICK_RATE)); + clock.tick(Duration::from_millis(1000 / TPS)); } } From 8dcf030bd8338265b6c7642873a62d3a25579ce6 Mon Sep 17 00:00:00 2001 From: Imbris <hank.aa7@gmail.com> Date: Sun, 9 Jun 2019 14:14:02 -0400 Subject: [PATCH 10/10] Use Instant and running compensation in clock --- common/src/clock.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/common/src/clock.rs b/common/src/clock.rs index f6aa8b268e..4fa250523b 100644 --- a/common/src/clock.rs +++ b/common/src/clock.rs @@ -1,23 +1,25 @@ use std::{ thread, - time::{Duration, SystemTime}, + time::{Duration, Instant}, }; const CLOCK_SMOOTHING: f64 = 0.9; pub struct Clock { - last_sys_time: SystemTime, + last_sys_time: Instant, last_delta: Option<Duration>, running_tps_average: f64, + compensation: f64, } impl Clock { #[allow(dead_code)] pub fn new() -> Self { Self { - last_sys_time: SystemTime::now(), + last_sys_time: Instant::now(), last_delta: None, running_tps_average: 0.0, + compensation: 1.0, } } @@ -38,27 +40,25 @@ impl Clock { #[allow(dead_code)] pub fn tick(&mut self, tgt: Duration) { - let delta = SystemTime::now() - .duration_since(self.last_sys_time) - .expect("Time went backwards!"); + let delta = Instant::now().duration_since(self.last_sys_time); // Attempt to sleep to fill the gap. if let Some(sleep_dur) = tgt.checked_sub(delta) { - let adjustment = if self.running_tps_average == 0.0 { - 1.0 - } else { - tgt.as_secs_f64() / self.running_tps_average - }; - thread::sleep(Duration::from_secs_f64( - sleep_dur.as_secs_f64() * adjustment, - )); + if self.running_tps_average != 0.0 { + self.compensation = + (self.compensation + (tgt.as_secs_f64() / self.running_tps_average) - 1.0) + .max(0.0) + } + + let sleep_secs = sleep_dur.as_secs_f64() * self.compensation; + if sleep_secs > 0.0 { + thread::sleep(Duration::from_secs_f64(sleep_secs)); + } } - let delta = SystemTime::now() - .duration_since(self.last_sys_time) - .expect("Time went backwards!"); + let delta = Instant::now().duration_since(self.last_sys_time); - self.last_sys_time = SystemTime::now(); + self.last_sys_time = Instant::now(); self.last_delta = Some(delta); self.running_tps_average = if self.running_tps_average == 0.0 { delta.as_secs_f64()