use crate::hook_context::with_hook_context;
use parking_lot::{Mutex, RwLock};
use std::{fmt, sync::Arc};
#[cfg(test)]
mod tests;
#[derive(Debug, Default)]
pub struct StateContainer<T> {
value: RwLock<T>,
version: Mutex<u64>,
}
impl<T> StateContainer<T> {
pub fn new<F>(initializer: F) -> Self
where
F: FnOnce() -> T,
{
Self {
value: RwLock::new(initializer()),
version: Mutex::new(0),
}
}
pub fn get(&self) -> T
where
T: Clone,
{
self.value.read().clone()
}
fn increment_version_and_notify(&self) {
{
let mut version = self.version.lock();
*version += 1;
}
}
pub fn set(&self, new_value: T) {
{
let mut value = self.value.write();
*value = new_value;
}
self.increment_version_and_notify();
}
pub fn update<F>(&self, updater: F)
where
F: FnOnce(&T) -> T,
T: Clone,
{
{
let mut value = self.value.write();
let new_value = updater(&*value);
*value = new_value;
}
self.increment_version_and_notify();
}
pub fn version(&self) -> u64 {
*self.version.lock()
}
}
#[derive(Debug, Default)]
pub struct StateHandle<T> {
container: Arc<StateContainer<T>>,
}
impl<T> fmt::Display for StateHandle<T>
where
T: fmt::Display + Clone,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.get())
}
}
impl<T> StateHandle<T> {
pub fn new<F>(initializer: F) -> Self
where
F: FnOnce() -> T,
{
Self {
container: Arc::new(StateContainer::new(initializer)),
}
}
pub fn from_container(container: Arc<StateContainer<T>>) -> Self {
Self { container }
}
pub fn get(&self) -> T
where
T: Clone,
{
self.container.get()
}
pub fn version(&self) -> u64 {
self.container.version()
}
pub fn container(&self) -> &Arc<StateContainer<T>> {
&self.container
}
}
impl<T> Clone for StateHandle<T> {
fn clone(&self) -> Self {
Self {
container: self.container.clone(),
}
}
}
#[derive(Debug, Default)]
pub struct StateSetter<T> {
container: Arc<StateContainer<T>>,
}
impl<T> StateSetter<T> {
pub fn new(container: Arc<StateContainer<T>>) -> Self {
Self { container }
}
pub fn set(&self, new_value: T) {
self.container.set(new_value);
}
pub fn update<F>(&self, updater: F)
where
F: FnOnce(&T) -> T,
T: Clone,
{
use crate::panic_handler::catch_panic;
let safe_updater = |current: &T| -> T {
match catch_panic(std::panic::AssertUnwindSafe(|| updater(current))) {
Ok(new_value) => new_value,
Err(panic_payload) => {
let reason = if let Some(s) = panic_payload.downcast_ref::<&str>() {
(*s).to_string()
} else if let Some(s) = panic_payload.downcast_ref::<String>() {
s.clone()
} else {
"<unknown panic>".to_string()
};
tracing::error!(
target: "hooks::state",
"State updater function panicked: {:?}",
reason
);
panic!("💥 State updater panicked: {}", reason);
}
}
};
self.container.update(safe_updater);
}
#[cfg(test)]
pub fn container(&self) -> &Arc<StateContainer<T>> {
&self.container
}
pub fn version(&self) -> u64 {
self.container.version()
}
}
impl<T> Clone for StateSetter<T> {
fn clone(&self) -> Self {
Self {
container: self.container.clone(),
}
}
}
impl<T> StateSetter<T> {
pub fn call(&self, new_value: T) {
self.set(new_value);
}
}
impl<T> StateHandle<T>
where
T: Clone,
{
pub fn field<F, R>(&self, getter: F) -> R
where
F: FnOnce(&T) -> R,
{
let value = self.get();
getter(&value)
}
pub fn map<F, R>(&self, mapper: F) -> R
where
F: FnOnce(T) -> R,
{
let value = self.get();
mapper(value)
}
}
pub fn use_state<T, F>(initializer: F) -> (StateHandle<T>, StateSetter<T>)
where
T: 'static,
F: FnOnce() -> T,
{
with_hook_context(|ctx| {
let index = ctx.next_hook_index();
let container_ref =
ctx.get_or_init_state(index, || Arc::new(StateContainer::new(initializer)));
let container = container_ref.borrow().clone();
let state_handle = StateHandle::from_container(container.clone());
let setter = StateSetter::new(container);
(state_handle, setter)
})
}