diff --git a/hui-derive/src/lib.rs b/hui-derive/src/lib.rs index 7b535bc..4784fa1 100644 --- a/hui-derive/src/lib.rs +++ b/hui-derive/src/lib.rs @@ -6,8 +6,16 @@ use syn::{parse_macro_input, DeriveInput}; /// Implements `Signal` trait for the given type #[proc_macro_derive(Signal)] -pub fn signal(input: TokenStream) -> TokenStream { +pub fn derive_signal(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let name = input.ident; quote!(impl ::hui::signal::Signal for #name {}).into() } + +/// Implements `State` trait for the given type +#[proc_macro_derive(State)] +pub fn derive_state(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let name = input.ident; + quote!(impl ::hui::state::State for #name {}).into() +} diff --git a/hui/Cargo.toml b/hui/Cargo.toml index 1d9ca55..44b0d6f 100644 --- a/hui/Cargo.toml +++ b/hui/Cargo.toml @@ -28,6 +28,7 @@ derive_setters = "0.1" derive_more = "0.99" tinyset = "0.4" image = { version = "0.25", default-features = false, optional = true } +rustc-hash = "1.1" [features] default = ["el_all", "image", "builtin_font", "pixel_perfect_text", "derive"] diff --git a/hui/src/instance.rs b/hui/src/instance.rs index ebfae21..a06cf95 100644 --- a/hui/src/instance.rs +++ b/hui/src/instance.rs @@ -18,9 +18,7 @@ use crate::{ /// In most cases, you should only have one instance of this struct, but multiple instances are allowed\ /// (Please note that it's possible to render multiple UI "roots" using a single instance) pub struct UiInstance { - //mouse_position: Vec2, stateful_state: StateRepo, - //event_queue: VecDeque, prev_draw_commands: UiDrawCommandList, draw_commands: UiDrawCommandList, draw_call: UiDrawCall, @@ -30,7 +28,7 @@ pub struct UiInstance { events: EventQueue, input: UiInputState, signal: SignalStore, - //True if in the middle of a laying out a frame + /// True if in the middle of a laying out a frame state: bool, } @@ -41,7 +39,7 @@ impl UiInstance { pub fn new() -> Self { UiInstance { //mouse_position: Vec2::ZERO, - stateful_state: StateRepo::default(), + stateful_state: StateRepo::new(), //event_queue: VecDeque::new(), // root_elements: Vec::new(), prev_draw_commands: UiDrawCommandList::default(), diff --git a/hui/src/state.rs b/hui/src/state.rs index 446b083..333c47f 100644 --- a/hui/src/state.rs +++ b/hui/src/state.rs @@ -2,12 +2,125 @@ use hashbrown::{HashMap, HashSet}; use nohash_hasher::BuildNoHashHasher; -use std::any::Any; +use std::{any::Any, hash::{Hash, Hasher}}; +use rustc_hash::FxHasher; //TODO impl StateRepo functions and automatic cleanup of inactive ids +#[cfg(feature = "derive")] +pub use hui_derive::State; + +/// Marker trait for state objects +pub trait State: Any {} + +/// Integer type used to identify a state object +type StateId = u64; + +fn hash_local(x: impl Hash, g: &[StateId]) -> StateId { + let mut hasher = FxHasher::default(); + 0xdeadbeefu64.hash(&mut hasher); + for x in g { + x.hash(&mut hasher); + } + x.hash(&mut hasher); + hasher.finish() +} + +fn hash_global(x: impl Hash) -> StateId { + let mut hasher = FxHasher::default(); + 0xcafebabeu64.hash(&mut hasher); + x.hash(&mut hasher); + hasher.finish() +} + #[derive(Default)] pub struct StateRepo { - state: HashMap, BuildNoHashHasher>, - active_ids: HashSet> + /// Stack of ids used to identify state objects + id_stack: Vec, + + /// Implementation detail: used to prevent needlessly reallocating the id stack if the `global`` function is used + standby: Vec, + + /// Actual state objects + state: HashMap, BuildNoHashHasher>, + + /// IDs that were accessed during the current frame, everything else is considered inactive and can be cleaned up + active_ids: HashSet> +} + +impl StateRepo { + /// Push an id to the stack + pub fn push(&mut self, id: impl Hash) { + self.id_stack.push(hash_global(id)); + } + + /// Pop the last id from the stack + /// + /// ## Panics: + /// Panics if the stack is empty + pub fn pop(&mut self) { + self.id_stack.pop().expect("stack is empty"); + } + + /// Create a new [`StateRepo`] + pub(crate) fn new() -> Self { + Self::default() + } + + /// Get a reference to a state object by its id + pub fn acquire(&mut self, id: impl Hash) -> Option<&T> { + let id = hash_local(id, &self.id_stack); + self.active_ids.insert(id); + self.state.get(&id).unwrap().downcast_ref::() + } + + /// Get a reference to a state object by its id or insert a new one + pub fn acquire_or_insert(&mut self, id: impl Hash, state: T) -> &T { + let id = hash_local(id, &self.id_stack); + self.state.entry(id) + .or_insert_with(|| Box::new(state)) + .downcast_ref::().unwrap() + } + + /// Get a reference to a state object by its id or insert a new default one + pub fn acquire_or_default(&mut self, id: impl Hash) -> &T { + let id = hash_local(id, &self.id_stack); + self.state.entry(id) + .or_insert_with(|| Box::::default()) + .downcast_ref::().unwrap() + } + + /// Get a mutable reference to a state object by its id + pub fn acquire_mut(&mut self, id: impl Hash) -> Option<&mut T> { + let id = hash_local(id, &self.id_stack); + self.active_ids.insert(id); + self.state.get_mut(&id).unwrap().downcast_mut::() + } + + /// Get a mutable reference to a state object by its id or insert a new one + pub fn acquire_mut_or_insert(&mut self, id: impl Hash, state: T) -> &mut T { + let id = hash_local(id, &self.id_stack); + self.state.entry(id) + .or_insert_with(|| Box::new(state)) + .downcast_mut::().unwrap() + } + + /// Get a mutable reference to a state object by its id or insert a new default one + pub fn acquire_mut_or_default(&mut self, id: impl Hash) -> &mut T { + let id = hash_local(id, &self.id_stack); + self.state.entry(id) + .or_insert_with(|| Box::::default()) + .downcast_mut::().unwrap() + } + + /// Temporarily forget about current id stack, and use an empty one (within the context of the closure) + /// + /// Can be useful for state management of non-hierarchical objects, e.g. popups + pub fn global(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { + self.standby.clear(); + std::mem::swap(&mut self.id_stack, &mut self.standby); + let ret = f(self); + std::mem::swap(&mut self.id_stack, &mut self.standby); + ret + } }