From b61efa4b1cd0186cc455d2b7d2dbcab5ce39b368 Mon Sep 17 00:00:00 2001 From: vE5li Date: Mon, 15 Apr 2024 21:30:30 +0200 Subject: [PATCH] Give the StateButton a proper TrackedState --- korangar/src/debug/profiling/mod.rs | 12 +- .../src/interface/windows/account/login.rs | 106 +++++------------- .../src/interface/windows/debug/packet.rs | 4 +- .../src/interface/windows/debug/profiler.rs | 94 ++++++++++++++-- .../interface/windows/settings/graphics.rs | 2 +- .../src/interface/windows/settings/render.rs | 2 +- .../src/elements/buttons/state/builder.rs | 46 ++++---- .../src/elements/buttons/state/mod.rs | 19 ++-- korangar_interface/src/state.rs | 45 ++++++-- 9 files changed, 198 insertions(+), 132 deletions(-) diff --git a/korangar/src/debug/profiling/mod.rs b/korangar/src/debug/profiling/mod.rs index 25a05365a..01dd3455a 100644 --- a/korangar/src/debug/profiling/mod.rs +++ b/korangar/src/debug/profiling/mod.rs @@ -3,7 +3,7 @@ mod ring_buffer; mod statistics; use std::mem::MaybeUninit; -use std::sync::atomic::AtomicBool; +use std::sync::atomic::{AtomicBool, AtomicUsize}; use std::sync::{LazyLock, Mutex, MutexGuard}; use std::time::Instant; @@ -22,15 +22,23 @@ static mut SHADOW_THREAD_PROFILER: LazyLock> = LazyLock::new(|| static mut DEFERRED_THREAD_PROFILER: LazyLock> = LazyLock::new(|| Mutex::new(Profiler::default())); static mut PROFILER_HALTED: AtomicBool = AtomicBool::new(false); +static mut PROFILER_HALTED_VERSION: AtomicUsize = AtomicUsize::new(0); pub fn set_profiler_halted(running: bool) { - unsafe { PROFILER_HALTED.store(running, std::sync::atomic::Ordering::Relaxed) }; + unsafe { + PROFILER_HALTED.store(running, std::sync::atomic::Ordering::Relaxed); + PROFILER_HALTED_VERSION.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + }; } pub fn is_profiler_halted() -> bool { unsafe { PROFILER_HALTED.load(std::sync::atomic::Ordering::Relaxed) } } +pub fn get_profiler_halted_version() -> usize { + unsafe { PROFILER_HALTED_VERSION.load(std::sync::atomic::Ordering::Relaxed) } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ProfilerThread { Main, diff --git a/korangar/src/interface/windows/account/login.rs b/korangar/src/interface/windows/account/login.rs index 313086cf9..8eb2b7713 100644 --- a/korangar/src/interface/windows/account/login.rs +++ b/korangar/src/interface/windows/account/login.rs @@ -1,13 +1,11 @@ -use std::cell::RefCell; use std::ops::Not; -use std::rc::Rc; use derive_new::new; use korangar_interface::elements::{ ButtonBuilder, Container, ElementWrap, FocusMode, InputFieldBuilder, PickList, StateButtonBuilder, Text, }; use korangar_interface::event::ClickAction; -use korangar_interface::state::{RawTrackedState, TrackedState, TrackedStateClone, TrackedStateExt}; +use korangar_interface::state::{RawTrackedState, TrackedState, TrackedStateBinary, TrackedStateClone, TrackedStateExt}; use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; use korangar_procedural::size_bound; @@ -62,7 +60,7 @@ impl<'a> PrototypeWindow for LoginWindow<'a> { let password = RawTrackedState::new(saved_settings.password.clone()); let selected_service = RawTrackedState::new(selected_service); - let login_settings = Rc::new(RefCell::new(login_settings)); + let login_settings = RawTrackedState::new(login_settings); let selector = { let username = username.clone(); @@ -73,19 +71,19 @@ impl<'a> PrototypeWindow for LoginWindow<'a> { let service_changed = { let mut username = username.clone(); let mut password = password.clone(); - let login_settings = login_settings.clone(); + let mut login_settings = login_settings.clone(); let selected_service = selected_service.clone(); Box::new(move || { let service_id = selected_service.cloned(); - let mut login_settings = login_settings.borrow_mut(); - let saved_settings = login_settings.service_settings.entry(service_id).or_default(); + let saved_settings = + login_settings.mutate(|login_settings| login_settings.service_settings.entry(service_id).or_default().clone()); username.mutate(|username| { - *username = saved_settings.username.clone(); + *username = saved_settings.username; }); password.mutate(|password| { - *password = saved_settings.password.clone(); + *password = saved_settings.password; }); Vec::new() @@ -95,19 +93,20 @@ impl<'a> PrototypeWindow for LoginWindow<'a> { let login_action = { let username = username.clone(); let password = password.clone(); - let login_settings = login_settings.clone(); + let mut login_settings = login_settings.clone(); let selected_service = selected_service.clone(); move || { // TODO: Deduplicate code let service_id = selected_service.cloned(); - let mut login_settings = login_settings.borrow_mut(); - login_settings.recent_service_id = Some(service_id); + login_settings.mutate(|login_settings| { + login_settings.recent_service_id = Some(service_id); - let saved_settings = login_settings.service_settings.entry(service_id).or_default(); - saved_settings.username = username.cloned(); - saved_settings.password = password.cloned(); + let saved_settings = login_settings.service_settings.entry(service_id).or_default(); + saved_settings.username = username.cloned(); + saved_settings.password = password.cloned(); + }); vec![ClickAction::Custom(UserEvent::LogIn { service_id: selected_service.cloned(), @@ -132,7 +131,7 @@ impl<'a> PrototypeWindow for LoginWindow<'a> { let password_action = { let username = username.clone(); let password = password.clone(); - let login_settings = login_settings.clone(); + let mut login_settings = login_settings.clone(); let selected_service = selected_service.clone(); Box::new(move || match password.get().is_empty() { @@ -142,12 +141,13 @@ impl<'a> PrototypeWindow for LoginWindow<'a> { // TODO: Deduplicate code let service_id = selected_service.cloned(); - let mut login_settings = login_settings.borrow_mut(); - login_settings.recent_service_id = Some(service_id); + login_settings.mutate(|login_settings| { + login_settings.recent_service_id = Some(service_id); - let saved_settings = login_settings.service_settings.entry(service_id).or_default(); - saved_settings.username = username.cloned(); - saved_settings.password = password.cloned(); + let saved_settings = login_settings.service_settings.entry(service_id).or_default(); + saved_settings.username = username.cloned(); + saved_settings.password = password.cloned(); + }); vec![ClickAction::Custom(UserEvent::LogIn { service_id: selected_service.cloned(), @@ -158,60 +158,16 @@ impl<'a> PrototypeWindow for LoginWindow<'a> { }) }; - let remember_username_selector = { - let login_settings = login_settings.clone(); - let selected_service = selected_service.clone(); - - move || { - let service_id = selected_service.cloned(); - let mut login_settings = login_settings.borrow_mut(); - let saved_settings = login_settings.service_settings.entry(service_id).or_default(); + let remember_username = { + let service_id = selected_service.clone(); - saved_settings.remember_username - } + login_settings.mapped(move |login_settings| &login_settings.service_settings.get(&service_id.get()).unwrap().remember_username) }; - let remember_password_selector = { - let login_settings = login_settings.clone(); - let selected_service = selected_service.clone(); - - move || { - let service_id = selected_service.cloned(); - let mut login_settings = login_settings.borrow_mut(); - let saved_settings = login_settings.service_settings.entry(service_id).or_default(); + let remember_password = { + let service_id = selected_service.clone(); - saved_settings.remember_password - } - }; - - let remember_username_action = { - let login_settings = login_settings.clone(); - let selected_service = selected_service.clone(); - - Box::new(move || { - let service_id = selected_service.cloned(); - let mut login_settings = login_settings.borrow_mut(); - let saved_settings = login_settings.service_settings.entry(service_id).or_default(); - - saved_settings.remember_username = !saved_settings.remember_username; - - Vec::new() - }) - }; - - let remember_password_action = { - let login_settings = login_settings.clone(); - let selected_service = selected_service.clone(); - - Box::new(move || { - let service_id = selected_service.cloned(); - let mut login_settings = login_settings.borrow_mut(); - let saved_settings = login_settings.service_settings.entry(service_id).or_default(); - - saved_settings.remember_password = !saved_settings.remember_password; - - Vec::new() - }) + login_settings.mapped(move |login_settings| &login_settings.service_settings.get(&service_id.get()).unwrap().remember_password) }; let elements = vec![ @@ -241,15 +197,15 @@ impl<'a> PrototypeWindow for LoginWindow<'a> { vec![ StateButtonBuilder::new() .with_text("Remember username") - .with_selector(remember_username_selector) - .with_event(remember_username_action) + .with_remote(remember_username.new_remote()) + .with_event(remember_username.toggle_action()) .with_transparent_background() .build() .wrap(), StateButtonBuilder::new() .with_text("Remember password") - .with_selector(remember_password_selector) - .with_event(remember_password_action) + .with_remote(remember_password.new_remote()) + .with_event(remember_password.toggle_action()) .with_transparent_background() .build() .wrap(), diff --git a/korangar/src/interface/windows/debug/packet.rs b/korangar/src/interface/windows/debug/packet.rs index 98145874f..893a497c6 100644 --- a/korangar/src/interface/windows/debug/packet.rs +++ b/korangar/src/interface/windows/debug/packet.rs @@ -61,14 +61,14 @@ impl PrototypeWindow for PacketWindow { .wrap(), StateButtonBuilder::new() .with_text("Show pings") - .with_selector(self.show_pings.selector()) + .with_remote(self.show_pings.new_remote()) .with_event(self.show_pings.toggle_action()) .with_width_bound(dimension_bound!(33.33%)) .build() .wrap(), StateButtonBuilder::new() .with_text("Update") - .with_selector(self.update.selector()) + .with_remote(self.update.new_remote()) .with_event(self.update.toggle_action()) .with_width_bound(dimension_bound!(!)) .build() diff --git a/korangar/src/interface/windows/debug/profiler.rs b/korangar/src/interface/windows/debug/profiler.rs index f0f974a98..1c765162b 100644 --- a/korangar/src/interface/windows/debug/profiler.rs +++ b/korangar/src/interface/windows/debug/profiler.rs @@ -1,14 +1,92 @@ use korangar_interface::elements::{ElementWrap, PickList, StateButtonBuilder}; -use korangar_interface::state::{RawTrackedState, TrackedState, TrackedStateBinary}; +use korangar_interface::state::{RawTrackedState, Remote, TrackedState, TrackedStateBinary, ValueState, Version}; use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; use korangar_procedural::{dimension_bound, size_bound}; -use crate::debug::{is_profiler_halted, set_profiler_halted, ProfilerThread}; +use crate::debug::{get_profiler_halted_version, is_profiler_halted, set_profiler_halted, ProfilerThread}; use crate::interface::application::Adapter; use crate::interface::elements::FrameView; use crate::interface::layout::ScreenSize; use crate::interface::windows::WindowCache; +/// Wrapper struct that exposes an implementation of [`TrackedState`] for the +/// halted state of the profiler. +#[derive(Default, Clone)] +struct TrackedProfilerHaltedState { + dummy_state: std::rc::Rc>, +} + +impl TrackedState for TrackedProfilerHaltedState { + type RemoteType = ProfilerHaltedRemote; + + fn set(&mut self, value: bool) { + set_profiler_halted(value); + } + + fn get(&self) -> std::cell::Ref<'_, bool> { + *self.dummy_state.borrow_mut() = is_profiler_halted(); + self.dummy_state.borrow() + } + + fn get_version(&self) -> korangar_interface::state::Version { + Version::from_raw(get_profiler_halted_version()) + } + + fn with_mut(&mut self, closure: Closure) -> Return + where + Closure: FnOnce(&mut bool) -> korangar_interface::state::ValueState, + { + let mut temporary_state = *self.dummy_state.borrow(); + + match closure(&mut temporary_state) { + ValueState::Mutated(return_value) => { + set_profiler_halted(temporary_state); + return_value + } + ValueState::Unchanged(return_value) => return_value, + } + } + + fn update(&mut self) { + let state = is_profiler_halted(); + set_profiler_halted(state); + } + + fn new_remote(&self) -> Self::RemoteType { + ProfilerHaltedRemote { + state: self.clone(), + version: Version::from_raw(get_profiler_halted_version()), + } + } +} + +/// Wrapper struct that exposes an implementation of [`Remote`] for the halted +/// state of the profiler. +struct ProfilerHaltedRemote { + state: TrackedProfilerHaltedState, + version: Version, +} + +impl Remote for ProfilerHaltedRemote { + type State = TrackedProfilerHaltedState; + + fn clone_state(&self) -> Self::State { + self.state.clone() + } + + fn get(&self) -> std::cell::Ref<'_, bool> { + self.state.get() + } + + fn consume_changed(&mut self) -> bool { + let version = Version::from_raw(get_profiler_halted_version()); + let changed = self.version != version; + self.version = version; + + changed + } +} + pub struct ProfilerWindow { always_update: RawTrackedState, visible_thread: RawTrackedState, @@ -31,11 +109,7 @@ impl PrototypeWindow for ProfilerWindow { } fn to_window(&self, window_cache: &WindowCache, application: &Adapter, available_space: ScreenSize) -> Window { - let toggle_halting = || { - let is_profiler_halted = is_profiler_halted(); - set_profiler_halted(!is_profiler_halted); - Vec::new() - }; + let profiler_halted_state = TrackedProfilerHaltedState::default(); let elements = vec![ PickList::default() @@ -51,15 +125,15 @@ impl PrototypeWindow for ProfilerWindow { .wrap(), StateButtonBuilder::new() .with_text("Always update") - .with_selector(self.always_update.selector()) .with_event(self.always_update.toggle_action()) + .with_remote(self.always_update.new_remote()) .with_width_bound(dimension_bound!(150)) .build() .wrap(), StateButtonBuilder::new() .with_text("Halt") - .with_selector(|| is_profiler_halted()) - .with_event(Box::new(toggle_halting)) + .with_remote(profiler_halted_state.new_remote()) + .with_event(profiler_halted_state.toggle_action()) .with_width_bound(dimension_bound!(150)) .build() .wrap(), diff --git a/korangar/src/interface/windows/settings/graphics.rs b/korangar/src/interface/windows/settings/graphics.rs index 0865da683..8ab246589 100644 --- a/korangar/src/interface/windows/settings/graphics.rs +++ b/korangar/src/interface/windows/settings/graphics.rs @@ -67,8 +67,8 @@ where 0, StateButtonBuilder::new() .with_text("Framerate limit") - .with_selector(self.framerate_limit.selector()) .with_event(self.framerate_limit.toggle_action()) + .with_remote(self.framerate_limit.new_remote()) .build() .wrap(), ); diff --git a/korangar/src/interface/windows/settings/render.rs b/korangar/src/interface/windows/settings/render.rs index be7bcc9b6..dcfecb728 100644 --- a/korangar/src/interface/windows/settings/render.rs +++ b/korangar/src/interface/windows/settings/render.rs @@ -11,8 +11,8 @@ use crate::interface::windows::WindowCache; fn render_state_button(text: &'static str, state: impl TrackedStateBinary) -> ElementCell { StateButtonBuilder::new() .with_text(text) - .with_selector(state.selector()) .with_event(state.toggle_action()) + .with_remote(state.new_remote()) .build() .wrap() } diff --git a/korangar_interface/src/elements/buttons/state/builder.rs b/korangar_interface/src/elements/buttons/state/builder.rs index 9eddd63f8..58152d38c 100644 --- a/korangar_interface/src/elements/buttons/state/builder.rs +++ b/korangar_interface/src/elements/buttons/state/builder.rs @@ -1,25 +1,26 @@ use std::marker::PhantomData; -use super::{StateButton, StateSelector}; +use super::StateButton; use crate::application::Application; use crate::builder::{Set, Unset}; use crate::layout::DimensionBound; +use crate::state::Remote; use crate::ElementEvent; /// Type state [`StateButton`] builder. This builder utilizes the type system to /// prevent calling the same method multiple times and calling /// [`build`](Self::build) before the mandatory methods have been called. #[must_use = "`build` needs to be called"] -pub struct StateButtonBuilder +pub struct StateButtonBuilder where App: Application, { text: Text, event: Event, - selector: Selector, + remote: State, transparent_background: bool, width_bound: DimensionBound, - marker: PhantomData<(App, Selector, Background, Width)>, + marker: PhantomData<(App, State, Background, Width)>, } impl StateButtonBuilder @@ -30,7 +31,7 @@ where Self { text: Unset, event: Unset, - selector: Unset, + remote: Unset, transparent_background: false, width_bound: DimensionBound::RELATIVE_ONE_HUNDRED, marker: PhantomData, @@ -38,23 +39,23 @@ where } } -impl StateButtonBuilder +impl StateButtonBuilder where App: Application, { - pub fn with_text + 'static>(self, text: Text) -> StateButtonBuilder { + pub fn with_text + 'static>(self, text: Text) -> StateButtonBuilder { StateButtonBuilder { text, ..self } } } -impl StateButtonBuilder +impl StateButtonBuilder where App: Application, { pub fn with_event + 'static>( self, event: Event, - ) -> StateButtonBuilder { + ) -> StateButtonBuilder { StateButtonBuilder { event, ..self } } } @@ -63,23 +64,23 @@ impl StateButtonBuilder bool + 'static, - ) -> StateButtonBuilder { + pub fn with_remote(self, remote: State) -> StateButtonBuilder + where + State: Remote + 'static, + { StateButtonBuilder { - selector: Box::new(selector), + remote, marker: PhantomData, ..self } } } -impl StateButtonBuilder +impl StateButtonBuilder where App: Application, { - pub fn with_transparent_background(self) -> StateButtonBuilder { + pub fn with_transparent_background(self) -> StateButtonBuilder { StateButtonBuilder { transparent_background: true, marker: PhantomData, @@ -88,11 +89,11 @@ where } } -impl StateButtonBuilder +impl StateButtonBuilder where App: Application, { - pub fn with_width_bound(self, width_bound: DimensionBound) -> StateButtonBuilder { + pub fn with_width_bound(self, width_bound: DimensionBound) -> StateButtonBuilder { StateButtonBuilder { width_bound, marker: PhantomData, @@ -101,11 +102,12 @@ where } } -impl StateButtonBuilder +impl StateButtonBuilder where App: Application, Text: AsRef + 'static, Event: ElementEvent + 'static, + State: Remote + 'static, { /// Take the builder and turn it into a [`StateButton`]. /// @@ -113,11 +115,11 @@ where /// [`with_event`](Self::with_event), and /// [`with_selector`](Self::with_selector) have been called on /// the builder. - pub fn build(self) -> StateButton { + pub fn build(self) -> StateButton { let Self { text, event, - selector, + remote, transparent_background, width_bound, .. @@ -126,7 +128,7 @@ where StateButton { text, event, - selector, + remote, transparent_background, width_bound, state: Default::default(), diff --git a/korangar_interface/src/elements/buttons/state/mod.rs b/korangar_interface/src/elements/buttons/state/mod.rs index 7b5726a23..070e8472a 100644 --- a/korangar_interface/src/elements/buttons/state/mod.rs +++ b/korangar_interface/src/elements/buttons/state/mod.rs @@ -3,33 +3,34 @@ mod builder; pub use self::builder::StateButtonBuilder; use crate::application::{Application, InterfaceRenderer, MouseInputModeTrait}; use crate::elements::{Element, ElementState}; -use crate::event::{ClickAction, HoverInformation}; +use crate::event::{ChangeEvent, ClickAction, HoverInformation}; use crate::layout::{DimensionBound, PlacementResolver}; +use crate::state::{Remote, RemoteClone}; use crate::theme::{ButtonTheme, InterfaceTheme}; use crate::ElementEvent; -type StateSelector = Box bool + 'static>; - // FIX: State button won't redraw just because the state changes -pub struct StateButton +pub struct StateButton where App: Application, Text: AsRef + 'static, Event: ElementEvent + 'static, + State: Remote, { text: Text, event: Event, - selector: StateSelector, + remote: State, width_bound: DimensionBound, transparent_background: bool, state: ElementState, } -impl Element for StateButton +impl Element for StateButton where App: Application, Text: AsRef + 'static, Event: ElementEvent + 'static, + State: Remote, { fn get_state(&self) -> &ElementState { &self.state @@ -55,6 +56,10 @@ where self.event.trigger() } + fn update(&mut self) -> Option { + self.remote.consume_changed().then_some(ChangeEvent::RENDER_WINDOW) + } + fn render( &self, render_target: &mut >::Target, @@ -92,7 +97,7 @@ where theme.button().icon_offset(), theme.button().icon_size(), foreground_color.clone(), - (self.selector)(), + self.remote.cloned(), ); renderer.render_text( diff --git a/korangar_interface/src/state.rs b/korangar_interface/src/state.rs index 725cda400..8d6c1985e 100644 --- a/korangar_interface/src/state.rs +++ b/korangar_interface/src/state.rs @@ -16,13 +16,17 @@ pub enum ValueState { pub struct Version(usize); impl Version { + pub fn from_raw(version: usize) -> Self { + Self(version) + } + fn bump(&mut self) { self.0 = self.0.wrapping_add(1); } } pub trait TrackedState: Clone { - type RemoteType; + type RemoteType: Remote + 'static; fn set(&mut self, value: Value); @@ -124,7 +128,10 @@ impl Clone for RawTrackedState { } } -impl TrackedState for RawTrackedState { +impl TrackedState for RawTrackedState +where + Value: 'static, +{ type RemoteType = RawRemote; fn set(&mut self, value: Value) { @@ -170,7 +177,9 @@ impl TrackedState for RawTrackedState { pub struct MappedTrackedState where - F: Fn(&Value) -> &As, + Value: 'static, + As: 'static, + F: Fn(&Value) -> &As + 'static, { state: RawTrackedState, mapping: F, @@ -192,7 +201,9 @@ where impl TrackedState for MappedTrackedState where - F: Clone + Fn(&Value) -> &As, + Value: 'static, + As: 'static, + F: Clone + Fn(&Value) -> &As + 'static, { type RemoteType = MappedRemote; @@ -250,7 +261,7 @@ where } } -pub trait TrackedStateClone { +pub trait TrackedStateClone: TrackedState { fn cloned(&self) -> Value; } @@ -264,7 +275,7 @@ where } } -pub trait TrackedStateTake { +pub trait TrackedStateTake: TrackedState { fn take(&mut self) -> Value; } @@ -338,7 +349,7 @@ where } } -pub trait TrackedStateBinary { +pub trait TrackedStateBinary: TrackedState { fn toggle(&mut self); fn selector(&self) -> impl Fn() -> Value + 'static; @@ -387,7 +398,7 @@ pub trait Remote { fn consume_changed(&mut self) -> bool; } -pub trait RemoteClone { +pub trait RemoteClone: Remote { fn cloned(&self) -> Value; } @@ -406,13 +417,19 @@ pub struct RawRemote { version: Version, } -impl RawRemote { +impl RawRemote +where + Value: 'static, +{ pub fn new(value: Value) -> RawRemote { RawTrackedState::new(value).new_remote() } } -impl Remote for RawRemote { +impl Remote for RawRemote +where + Value: 'static, +{ type State = RawTrackedState; fn clone_state(&self) -> RawTrackedState { @@ -443,7 +460,9 @@ impl Clone for RawRemote { pub struct MappedRemote where - F: Fn(&Value) -> &As, + Value: 'static, + As: 'static, + F: Fn(&Value) -> &As + 'static, { tracked_state: MappedTrackedState, version: Version, @@ -451,7 +470,9 @@ where impl Remote for MappedRemote where - F: Clone + Fn(&Value) -> &As, + Value: 'static, + As: 'static, + F: Clone + Fn(&Value) -> &As + 'static, { type State = MappedTrackedState;