From df85b25548cfff642af2b4ac4a4521b53027cb52 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 21 Aug 2023 14:23:02 -0500 Subject: [PATCH 01/27] Add debug information to borrows and ownership in signals --- packages/generational-box/Cargo.toml | 1 + packages/generational-box/src/lib.rs | 445 ++++++++++++++++++++++++--- packages/signals/src/effect.rs | 2 +- packages/signals/src/impls.rs | 33 +- packages/signals/src/rt.rs | 33 +- packages/signals/src/signal.rs | 58 +++- 6 files changed, 497 insertions(+), 75 deletions(-) diff --git a/packages/generational-box/Cargo.toml b/packages/generational-box/Cargo.toml index 6c5a5a841..cc0db9cec 100644 --- a/packages/generational-box/Cargo.toml +++ b/packages/generational-box/Cargo.toml @@ -15,3 +15,4 @@ rand = "0.8.5" [features] default = ["check_generation"] check_generation = [] +debug_borrows = [] diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 60bd41419..7d64a1ed3 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -2,9 +2,13 @@ #![warn(missing_docs)] use std::{ + any::Any, cell::{Cell, Ref, RefCell, RefMut}, - fmt::Debug, + error::Error, + fmt::{Debug, Display}, marker::PhantomData, + ops::{Deref, DerefMut}, + panic::Location, rc::Rc, }; @@ -153,6 +157,8 @@ pub struct GenerationalBox { raw: MemoryLocation, #[cfg(any(debug_assertions, feature = "check_generation"))] generation: u32, + #[cfg(any(debug_assertions, feature = "check_generation"))] + created_at: &'static std::panic::Location<'static>, _marker: PhantomData, } @@ -161,7 +167,7 @@ impl Debug for GenerationalBox { #[cfg(any(debug_assertions, feature = "check_generation"))] f.write_fmt(format_args!( "{:?}@{:?}", - self.raw.data.as_ptr(), + self.raw.0.data.as_ptr(), self.generation ))?; #[cfg(not(any(debug_assertions, feature = "check_generation")))] @@ -175,7 +181,7 @@ impl GenerationalBox { fn validate(&self) -> bool { #[cfg(any(debug_assertions, feature = "check_generation"))] { - self.raw.generation.get() == self.generation + self.raw.0.generation.get() == self.generation } #[cfg(not(any(debug_assertions, feature = "check_generation")))] { @@ -184,43 +190,51 @@ impl GenerationalBox { } /// Try to read the value. Returns None if the value is no longer valid. - pub fn try_read(&self) -> Option> { - self.validate() - .then(|| { - Ref::filter_map(self.raw.data.borrow(), |any| { - any.as_ref()?.downcast_ref::() - }) - .ok() - }) - .flatten() + #[track_caller] + pub fn try_read(&self) -> Result, BorrowError> { + if !self.validate() { + return Err(BorrowError::Dropped(ValueDroppedError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + created_at: self.created_at, + })); + } + self.raw.try_borrow( + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + self.created_at, + ) } /// Read the value. Panics if the value is no longer valid. - pub fn read(&self) -> Ref<'_, T> { + #[track_caller] + pub fn read(&self) -> GenerationalRef<'_, T> { self.try_read().unwrap() } /// Try to write the value. Returns None if the value is no longer valid. - pub fn try_write(&self) -> Option> { - self.validate() - .then(|| { - RefMut::filter_map(self.raw.data.borrow_mut(), |any| { - any.as_mut()?.downcast_mut::() - }) - .ok() - }) - .flatten() + #[track_caller] + pub fn try_write(&self) -> Result, BorrowMutError> { + if !self.validate() { + return Err(BorrowMutError::Dropped(ValueDroppedError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + created_at: self.created_at, + })); + } + self.raw.try_borrow_mut( + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + self.created_at, + ) } /// Write the value. Panics if the value is no longer valid. - pub fn write(&self) -> RefMut<'_, T> { + #[track_caller] + pub fn write(&self) -> GenerationalRefMut<'_, T> { self.try_write().unwrap() } /// Set the value. Panics if the value is no longer valid. pub fn set(&self, value: T) { self.validate().then(|| { - *self.raw.data.borrow_mut() = Some(Box::new(value)); + *self.raw.0.data.borrow_mut() = Some(Box::new(value)); }); } @@ -228,7 +242,8 @@ impl GenerationalBox { pub fn ptr_eq(&self, other: &Self) -> bool { #[cfg(any(debug_assertions, feature = "check_generation"))] { - self.raw.data.as_ptr() == other.raw.data.as_ptr() && self.generation == other.generation + self.raw.0.data.as_ptr() == other.raw.0.data.as_ptr() + && self.generation == other.generation } #[cfg(not(any(debug_assertions, feature = "check_generation")))] { @@ -246,26 +261,38 @@ impl Clone for GenerationalBox { } #[derive(Clone, Copy)] -struct MemoryLocation { - data: &'static RefCell>>, +struct MemoryLocation(&'static MemoryLocationInner); + +struct MemoryLocationInner { + data: RefCell>>, #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: &'static Cell, + generation: Cell, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_at: RefCell>>, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_mut_at: Cell>>, } impl MemoryLocation { #[allow(unused)] fn drop(&self) { - let old = self.data.borrow_mut().take(); + let old = self.0.data.borrow_mut().take(); #[cfg(any(debug_assertions, feature = "check_generation"))] if old.is_some() { drop(old); - let new_generation = self.generation.get() + 1; - self.generation.set(new_generation); + let new_generation = self.0.generation.get() + 1; + self.0.generation.set(new_generation); } } - fn replace(&mut self, value: T) -> GenerationalBox { - let mut inner_mut = self.data.borrow_mut(); + fn replace_with_caller( + &mut self, + value: T, + #[cfg(any(debug_assertions, feature = "check_generation"))] caller: &'static Location< + 'static, + >, + ) -> GenerationalBox { + let mut inner_mut = self.0.data.borrow_mut(); let raw = Box::new(value); let old = inner_mut.replace(raw); @@ -273,10 +300,318 @@ impl MemoryLocation { GenerationalBox { raw: *self, #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: self.generation.get(), + generation: self.0.generation.get(), + #[cfg(any(debug_assertions, feature = "check_generation"))] + created_at: caller, _marker: PhantomData, } } + + #[track_caller] + fn try_borrow( + &self, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + created_at: &'static std::panic::Location<'static>, + ) -> Result, BorrowError> { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + self.0 + .borrowed_at + .borrow_mut() + .push(std::panic::Location::caller()); + match self.0.data.try_borrow() { + Ok(borrow) => match Ref::filter_map(borrow, |any| any.as_ref()?.downcast_ref::()) { + Ok(reference) => Ok(GenerationalRef { + inner: reference, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: GenerationalRefBorrowInfo { + borrowed_at: std::panic::Location::caller(), + borrowed_from: self.0, + }, + }), + Err(_) => Err(BorrowError::Dropped(ValueDroppedError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + created_at, + })), + }, + Err(_) => Err(BorrowError::AlreadyBorrowedMut(AlreadyBorrowedMutError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_mut_at: self.0.borrowed_mut_at.get().unwrap(), + })), + } + } + + #[track_caller] + fn try_borrow_mut( + &self, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + created_at: &'static std::panic::Location<'static>, + ) -> Result, BorrowMutError> { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + { + self.0 + .borrowed_mut_at + .set(Some(std::panic::Location::caller())); + } + match self.0.data.try_borrow_mut() { + Ok(borrow_mut) => { + match RefMut::filter_map(borrow_mut, |any| any.as_mut()?.downcast_mut::()) { + Ok(reference) => Ok(GenerationalRefMut { + inner: reference, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: GenerationalRefMutBorrowInfo { + borrowed_from: self.0, + }, + }), + Err(_) => Err(BorrowMutError::Dropped(ValueDroppedError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + created_at, + })), + } + } + Err(_) => Err(BorrowMutError::AlreadyBorrowed(AlreadyBorrowedError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_at: self.0.borrowed_at.borrow().clone(), + })), + } + } +} + +#[derive(Debug, Clone)] +/// An error that can occur when trying to borrow a value. +pub enum BorrowError { + /// The value was dropped. + Dropped(ValueDroppedError), + /// The value was already borrowed mutably. + AlreadyBorrowedMut(AlreadyBorrowedMutError), +} + +impl Display for BorrowError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BorrowError::Dropped(error) => Display::fmt(error, f), + BorrowError::AlreadyBorrowedMut(error) => Display::fmt(error, f), + } + } +} + +impl Error for BorrowError {} + +#[derive(Debug, Clone)] +/// An error that can occur when trying to borrow a value mutably. +pub enum BorrowMutError { + /// The value was dropped. + Dropped(ValueDroppedError), + /// The value was already borrowed. + AlreadyBorrowed(AlreadyBorrowedError), + /// The value was already borrowed mutably. + AlreadyBorrowedMut(AlreadyBorrowedMutError), +} + +impl Display for BorrowMutError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BorrowMutError::Dropped(error) => Display::fmt(error, f), + BorrowMutError::AlreadyBorrowedMut(error) => Display::fmt(error, f), + BorrowMutError::AlreadyBorrowed(error) => Display::fmt(error, f), + } + } +} + +impl Error for BorrowMutError {} + +/// An error that can occur when trying to use a value that has been dropped. +#[derive(Debug, Copy, Clone)] +pub struct ValueDroppedError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + created_at: &'static std::panic::Location<'static>, +} + +impl Display for ValueDroppedError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Failed to borrow because the value was dropped.")?; + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + f.write_fmt(format_args!("created_at: {}", self.created_at))?; + Ok(()) + } +} + +impl std::error::Error for ValueDroppedError {} + +/// An error that can occur when trying to borrow a value that has already been borrowed mutably. +#[derive(Debug, Copy, Clone)] +pub struct AlreadyBorrowedMutError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_mut_at: &'static std::panic::Location<'static>, +} + +impl Display for AlreadyBorrowedMutError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Failed to borrow because the value was already borrowed mutably.")?; + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + f.write_fmt(format_args!("borrowed_mut_at: {}", self.borrowed_mut_at))?; + Ok(()) + } +} + +impl std::error::Error for AlreadyBorrowedMutError {} + +/// An error that can occur when trying to borrow a value mutably that has already been borrowed immutably. +#[derive(Debug, Clone)] +pub struct AlreadyBorrowedError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_at: Vec<&'static std::panic::Location<'static>>, +} + +impl Display for AlreadyBorrowedError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Failed to borrow mutably because the value was already borrowed immutably.")?; + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + f.write_str("borrowed_at:")?; + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + for location in self.borrowed_at.iter() { + f.write_fmt(format_args!("\t{}", location))?; + } + Ok(()) + } +} + +impl std::error::Error for AlreadyBorrowedError {} + +/// A reference to a value in a generational box. +pub struct GenerationalRef<'a, T: 'static> { + inner: Ref<'a, T>, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: GenerationalRefBorrowInfo, +} + +impl<'a, T: 'static> GenerationalRef<'a, T> { + /// Map one ref type to another. + pub fn map(orig: GenerationalRef<'a, T>, f: F) -> GenerationalRef<'a, U> + where + F: FnOnce(&T) -> &U, + { + GenerationalRef { + inner: Ref::map(orig.inner, f), + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: GenerationalRefBorrowInfo { + borrowed_at: orig.borrow.borrowed_at, + borrowed_from: orig.borrow.borrowed_from, + }, + } + } + + /// Filter one ref type to another. + pub fn filter_map(orig: GenerationalRef<'a, T>, f: F) -> Option> + where + F: FnOnce(&T) -> Option<&U>, + { + let Self { + inner, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow, + } = orig; + Ref::filter_map(inner, f).ok().map(|inner| GenerationalRef { + inner, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: GenerationalRefBorrowInfo { + borrowed_at: borrow.borrowed_at, + borrowed_from: borrow.borrowed_from, + }, + }) + } +} + +impl<'a, T: 'static> Deref for GenerationalRef<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.inner.deref() + } +} + +#[cfg(any(debug_assertions, feature = "debug_borrows"))] +struct GenerationalRefBorrowInfo { + borrowed_at: &'static std::panic::Location<'static>, + borrowed_from: &'static MemoryLocationInner, +} + +#[cfg(any(debug_assertions, feature = "debug_borrows"))] +impl Drop for GenerationalRefBorrowInfo { + fn drop(&mut self) { + self.borrowed_from + .borrowed_at + .borrow_mut() + .retain(|location| std::ptr::eq(*location, self.borrowed_at as *const _)); + } +} + +/// A mutable reference to a value in a generational box. +pub struct GenerationalRefMut<'a, T: 'static> { + inner: RefMut<'a, T>, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: GenerationalRefMutBorrowInfo, +} + +impl<'a, T: 'static> GenerationalRefMut<'a, T> { + /// Map one ref type to another. + pub fn map(orig: GenerationalRefMut<'a, T>, f: F) -> GenerationalRefMut<'a, U> + where + F: FnOnce(&mut T) -> &mut U, + { + GenerationalRefMut { + inner: RefMut::map(orig.inner, f), + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: orig.borrow, + } + } + + /// Filter one ref type to another. + pub fn filter_map( + orig: GenerationalRefMut<'a, T>, + f: F, + ) -> Option> + where + F: FnOnce(&mut T) -> Option<&mut U>, + { + let Self { + inner, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow, + } = orig; + RefMut::filter_map(inner, f) + .ok() + .map(|inner| GenerationalRefMut { + inner, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow, + }) + } +} + +impl<'a, T: 'static> Deref for GenerationalRefMut<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.inner.deref() + } +} + +impl<'a, T: 'static> DerefMut for GenerationalRefMut<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.inner.deref_mut() + } +} + +#[cfg(any(debug_assertions, feature = "debug_borrows"))] +struct GenerationalRefMutBorrowInfo { + borrowed_from: &'static MemoryLocationInner, +} + +#[cfg(any(debug_assertions, feature = "debug_borrows"))] +impl Drop for GenerationalRefMutBorrowInfo { + fn drop(&mut self) { + self.borrowed_from.borrowed_mut_at.take(); + } } /// Handles recycling generational boxes that have been dropped. Your application should have one store or one store per thread. @@ -305,12 +640,16 @@ impl Store { if let Some(location) = self.recycled.borrow_mut().pop() { location } else { - let data: &'static RefCell<_> = self.bump.alloc(RefCell::new(None)); - MemoryLocation { - data, + let data: &'static MemoryLocationInner = self.bump.alloc(MemoryLocationInner { + data: RefCell::new(None), #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: self.bump.alloc(Cell::new(0)), - } + generation: Cell::new(0), + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_at: Default::default(), + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_mut_at: Default::default(), + }); + MemoryLocation(data) } } @@ -331,9 +670,31 @@ pub struct Owner { impl Owner { /// Insert a value into the store. The value will be dropped when the owner is dropped. + #[track_caller] pub fn insert(&self, value: T) -> GenerationalBox { let mut location = self.store.claim(); - let key = location.replace(value); + let key = location.replace_with_caller( + value, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + std::panic::Location::caller(), + ); + self.owned.borrow_mut().push(location); + key + } + + /// Insert a value into the store with a specific location blamed for creating the value. The value will be dropped when the owner is dropped. + pub fn insert_with_caller( + &self, + value: T, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + caller: &'static std::panic::Location<'static>, + ) -> GenerationalBox { + let mut location = self.store.claim(); + let key = location.replace_with_caller( + value, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + caller, + ); self.owned.borrow_mut().push(location); key } @@ -344,7 +705,9 @@ impl Owner { GenerationalBox { raw: location, #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: location.generation.get(), + generation: location.0.generation.get(), + #[cfg(any(debug_assertions, feature = "check_generation"))] + created_at: std::panic::Location::caller(), _marker: PhantomData, } } diff --git a/packages/signals/src/effect.rs b/packages/signals/src/effect.rs index 0685938a0..0110ce4d1 100644 --- a/packages/signals/src/effect.rs +++ b/packages/signals/src/effect.rs @@ -83,7 +83,7 @@ impl Effect { /// Run the effect callback immediately. Returns `true` if the effect was run. Returns `false` is the effect is dead. pub fn try_run(&self) { - if let Some(mut callback) = self.callback.try_write() { + if let Ok(mut callback) = self.callback.try_write() { { get_effect_stack().effects.borrow_mut().push(*self); } diff --git a/packages/signals/src/impls.rs b/packages/signals/src/impls.rs index 6427c2e2b..8029ac312 100644 --- a/packages/signals/src/impls.rs +++ b/packages/signals/src/impls.rs @@ -1,7 +1,7 @@ use crate::rt::CopyValue; use crate::signal::{ReadOnlySignal, Signal, Write}; - -use std::cell::{Ref, RefMut}; +use generational_box::GenerationalRef; +use generational_box::GenerationalRefMut; use std::{ fmt::{Debug, Display}, @@ -38,8 +38,8 @@ macro_rules! read_impls { impl $ty> { /// Read a value from the inner vector. - pub fn get(&self, index: usize) -> Option> { - Ref::filter_map(self.read(), |v| v.get(index)).ok() + pub fn get(&self, index: usize) -> Option> { + GenerationalRef::filter_map(self.read(), |v| v.get(index)) } } @@ -52,9 +52,9 @@ macro_rules! read_impls { self.with(|v| v.clone()).unwrap() } - /// Attemps to read the inner value of the Option. - pub fn as_ref(&self) -> Option> { - Ref::filter_map(self.read(), |v| v.as_ref()).ok() + /// Attempts to read the inner value of the Option. + pub fn as_ref(&self) -> Option> { + GenerationalRef::filter_map(self.read(), |v| v.as_ref()) } } }; @@ -182,19 +182,22 @@ macro_rules! write_impls { } /// Gets the value out of the Option, or inserts the given value if the Option is empty. - pub fn get_or_insert(&self, default: T) -> Ref<'_, T> { + pub fn get_or_insert(&self, default: T) -> GenerationalRef<'_, T> { self.get_or_insert_with(|| default) } /// Gets the value out of the Option, or inserts the value returned by the given function if the Option is empty. - pub fn get_or_insert_with(&self, default: impl FnOnce() -> T) -> Ref<'_, T> { + pub fn get_or_insert_with( + &self, + default: impl FnOnce() -> T, + ) -> GenerationalRef<'_, T> { let borrow = self.read(); if borrow.is_none() { drop(borrow); self.with_mut(|v| *v = Some(default())); - Ref::map(self.read(), |v| v.as_ref().unwrap()) + GenerationalRef::map(self.read(), |v| v.as_ref().unwrap()) } else { - Ref::map(borrow, |v| v.as_ref().unwrap()) + GenerationalRef::map(borrow, |v| v.as_ref().unwrap()) } } } @@ -238,15 +241,15 @@ impl IntoIterator for CopyValue> { impl CopyValue> { /// Write to an element in the inner vector. - pub fn get_mut(&self, index: usize) -> Option> { - RefMut::filter_map(self.write(), |v| v.get_mut(index)).ok() + pub fn get_mut(&self, index: usize) -> Option> { + GenerationalRefMut::filter_map(self.write(), |v| v.get_mut(index)) } } impl CopyValue> { /// Deref the inner value mutably. - pub fn as_mut(&self) -> Option> { - RefMut::filter_map(self.write(), |v| v.as_mut()).ok() + pub fn as_mut(&self) -> Option> { + GenerationalRefMut::filter_map(self.write(), |v| v.as_mut()) } } diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index 7322d43b4..acf598217 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -1,14 +1,15 @@ -use std::cell::{Ref, RefMut}; - +use std::panic::Location; use std::rc::Rc; use dioxus_core::prelude::{ - consume_context, consume_context_from_scope, current_scope_id, provide_context, + consume_context, consume_context_from_scope, current_scope_id, has_context, provide_context, provide_context_to_scope, provide_root_context, }; use dioxus_core::ScopeId; -use generational_box::{GenerationalBox, Owner, Store}; +use generational_box::{ + BorrowError, BorrowMutError, GenerationalBox, GenerationalRef, GenerationalRefMut, Owner, Store, +}; fn current_store() -> Store { match consume_context() { @@ -21,7 +22,7 @@ fn current_store() -> Store { } fn current_owner() -> Rc { - match consume_context() { + match has_context() { Some(rt) => rt, None => { let owner = Rc::new(current_store().owner()); @@ -74,6 +75,7 @@ impl CopyValue { /// Create a new CopyValue. The value will be stored in the current component. /// /// Once the component this value is created in is dropped, the value will be dropped. + #[track_caller] pub fn new(value: T) -> Self { let owner = current_owner(); @@ -83,6 +85,15 @@ impl CopyValue { } } + pub(crate) fn new_with_caller(value: T, caller: &'static Location<'static>) -> Self { + let owner = current_owner(); + + Self { + value: owner.insert_with_caller(value, caller), + origin_scope: current_scope_id().expect("in a virtual dom"), + } + } + /// Create a new CopyValue. The value will be stored in the given scope. When the specified scope is dropped, the value will be dropped. pub fn new_in_scope(value: T, scope: ScopeId) -> Self { let owner = owner_in_scope(scope); @@ -108,22 +119,26 @@ impl CopyValue { } /// Try to read the value. If the value has been dropped, this will return None. - pub fn try_read(&self) -> Option> { + #[track_caller] + pub fn try_read(&self) -> Result, BorrowError> { self.value.try_read() } /// Read the value. If the value has been dropped, this will panic. - pub fn read(&self) -> Ref<'_, T> { + #[track_caller] + pub fn read(&self) -> GenerationalRef<'_, T> { self.value.read() } /// Try to write the value. If the value has been dropped, this will return None. - pub fn try_write(&self) -> Option> { + #[track_caller] + pub fn try_write(&self) -> Result, BorrowMutError> { self.value.try_write() } /// Write the value. If the value has been dropped, this will panic. - pub fn write(&self) -> RefMut<'_, T> { + #[track_caller] + pub fn write(&self) -> GenerationalRefMut<'_, T> { self.value.write() } diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index a5599a1ce..1656f559e 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -1,6 +1,7 @@ use std::{ - cell::{Ref, RefCell, RefMut}, + cell::RefCell, ops::{Deref, DerefMut}, + panic::Location, rc::Rc, sync::Arc, }; @@ -9,6 +10,7 @@ use dioxus_core::{ prelude::{current_scope_id, has_context, provide_context, schedule_update_any}, ScopeId, ScopeState, }; +use generational_box::{GenerationalRef, GenerationalRefMut}; use crate::{CopyValue, Effect}; @@ -43,8 +45,18 @@ use crate::{CopyValue, Effect}; /// } /// } /// ``` +#[track_caller] pub fn use_signal(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal { - *cx.use_hook(|| Signal::new(f())) + #[cfg(debug_assertions)] + let caller = Location::caller(); + + *cx.use_hook(|| { + Signal::new_with_caller( + f(), + #[cfg(debug_assertions)] + caller, + ) + }) } #[derive(Clone)] @@ -134,6 +146,7 @@ impl<'de, T: serde::Deserialize<'de> + 'static> serde::Deserialize<'de> for Sign impl Signal { /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking. + #[track_caller] pub fn new(value: T) -> Self { Self { inner: CopyValue::new(SignalData { @@ -145,6 +158,25 @@ impl Signal { } } + /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking. + fn new_with_caller( + value: T, + #[cfg(debug_assertions)] caller: &'static Location<'static>, + ) -> Self { + Self { + inner: CopyValue::new_with_caller( + SignalData { + subscribers: Default::default(), + effect_subscribers: Default::default(), + update_any: schedule_update_any().expect("in a virtual dom"), + value, + }, + #[cfg(debug_assertions)] + caller, + ), + } + } + /// Get the scope the signal was created in. pub fn origin_scope(&self) -> ScopeId { self.inner.origin_scope() @@ -152,7 +184,8 @@ impl Signal { /// Get the current value of the signal. This will subscribe the current scope to the signal. /// If the signal has been dropped, this will panic. - pub fn read(&self) -> Ref { + #[track_caller] + pub fn read(&self) -> GenerationalRef { let inner = self.inner.read(); if let Some(effect) = Effect::current() { let mut effect_subscribers = inner.effect_subscribers.borrow_mut(); @@ -176,14 +209,15 @@ impl Signal { } } } - Ref::map(inner, |v| &v.value) + GenerationalRef::map(inner, |v| &v.value) } /// Get a mutable reference to the signal's value. /// If the signal has been dropped, this will panic. + #[track_caller] pub fn write(&self) -> Write<'_, T> { let inner = self.inner.write(); - let borrow = RefMut::map(inner, |v| &mut v.value); + let borrow = GenerationalRefMut::map(inner, |v| &mut v.value); Write { write: borrow, signal: SignalSubscriberDrop { signal: *self }, @@ -219,12 +253,14 @@ impl Signal { } /// Set the value of the signal. This will trigger an update on all subscribers. + #[track_caller] pub fn set(&self, value: T) { *self.write() = value; } /// Run a closure with a reference to the signal's value. /// If the signal has been dropped, this will panic. + #[track_caller] pub fn with(&self, f: impl FnOnce(&T) -> O) -> O { let write = self.read(); f(&*write) @@ -232,6 +268,7 @@ impl Signal { /// Run a closure with a mutable reference to the signal's value. /// If the signal has been dropped, this will panic. + #[track_caller] pub fn with_mut(&self, f: impl FnOnce(&mut T) -> O) -> O { let mut write = self.write(); f(&mut *write) @@ -241,6 +278,7 @@ impl Signal { impl Signal { /// Get the current value of the signal. This will subscribe the current scope to the signal. /// If the signal has been dropped, this will panic. + #[track_caller] pub fn value(&self) -> T { self.read().clone() } @@ -264,7 +302,7 @@ impl Drop for SignalSubscriberDrop { /// A mutable reference to a signal's value. pub struct Write<'a, T: 'static, I: 'static = T> { - write: RefMut<'a, T>, + write: GenerationalRefMut<'a, T>, signal: SignalSubscriberDrop, } @@ -273,7 +311,7 @@ impl<'a, T: 'static, I: 'static> Write<'a, T, I> { pub fn map(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write<'a, O, I> { let Self { write, signal } = myself; Write { - write: RefMut::map(write, f), + write: GenerationalRefMut::map(write, f), signal, } } @@ -284,7 +322,7 @@ impl<'a, T: 'static, I: 'static> Write<'a, T, I> { f: impl FnOnce(&mut T) -> Option<&mut O>, ) -> Option> { let Self { write, signal } = myself; - let write = RefMut::filter_map(write, f).ok(); + let write = GenerationalRefMut::filter_map(write, f); write.map(|write| Write { write, signal }) } } @@ -320,11 +358,13 @@ impl ReadOnlySignal { } /// Get the current value of the signal. This will subscribe the current scope to the signal. - pub fn read(&self) -> Ref { + #[track_caller] + pub fn read(&self) -> GenerationalRef { self.inner.read() } /// Run a closure with a reference to the signal's value. + #[track_caller] pub fn with(&self, f: impl FnOnce(&T) -> O) -> O { self.inner.with(f) } From f09a2e2280f74a9ac4e50d298e6f665bad1f8ea2 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 21 Aug 2023 14:28:45 -0500 Subject: [PATCH 02/27] fix release builds --- packages/generational-box/Cargo.toml | 1 + packages/generational-box/src/lib.rs | 26 ++++++++++++-------------- packages/signals/src/rt.rs | 12 +++++++++--- packages/signals/src/signal.rs | 5 ++--- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/packages/generational-box/Cargo.toml b/packages/generational-box/Cargo.toml index cc0db9cec..7151ec103 100644 --- a/packages/generational-box/Cargo.toml +++ b/packages/generational-box/Cargo.toml @@ -16,3 +16,4 @@ rand = "0.8.5" default = ["check_generation"] check_generation = [] debug_borrows = [] +debug_ownership = [] diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 7d64a1ed3..1f8cbbe11 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -8,7 +8,6 @@ use std::{ fmt::{Debug, Display}, marker::PhantomData, ops::{Deref, DerefMut}, - panic::Location, rc::Rc, }; @@ -157,7 +156,7 @@ pub struct GenerationalBox { raw: MemoryLocation, #[cfg(any(debug_assertions, feature = "check_generation"))] generation: u32, - #[cfg(any(debug_assertions, feature = "check_generation"))] + #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at: &'static std::panic::Location<'static>, _marker: PhantomData, } @@ -288,9 +287,8 @@ impl MemoryLocation { fn replace_with_caller( &mut self, value: T, - #[cfg(any(debug_assertions, feature = "check_generation"))] caller: &'static Location< - 'static, - >, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + caller: &'static std::panic::Location<'static>, ) -> GenerationalBox { let mut inner_mut = self.0.data.borrow_mut(); @@ -301,7 +299,7 @@ impl MemoryLocation { raw: *self, #[cfg(any(debug_assertions, feature = "check_generation"))] generation: self.0.generation.get(), - #[cfg(any(debug_assertions, feature = "check_generation"))] + #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at: caller, _marker: PhantomData, } @@ -310,7 +308,7 @@ impl MemoryLocation { #[track_caller] fn try_borrow( &self, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] + #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at: &'static std::panic::Location<'static>, ) -> Result, BorrowError> { #[cfg(any(debug_assertions, feature = "debug_borrows"))] @@ -329,7 +327,7 @@ impl MemoryLocation { }, }), Err(_) => Err(BorrowError::Dropped(ValueDroppedError { - #[cfg(any(debug_assertions, feature = "debug_borrows"))] + #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at, })), }, @@ -343,7 +341,7 @@ impl MemoryLocation { #[track_caller] fn try_borrow_mut( &self, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] + #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at: &'static std::panic::Location<'static>, ) -> Result, BorrowMutError> { #[cfg(any(debug_assertions, feature = "debug_borrows"))] @@ -363,7 +361,7 @@ impl MemoryLocation { }, }), Err(_) => Err(BorrowMutError::Dropped(ValueDroppedError { - #[cfg(any(debug_assertions, feature = "debug_borrows"))] + #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at, })), } @@ -422,14 +420,14 @@ impl Error for BorrowMutError {} /// An error that can occur when trying to use a value that has been dropped. #[derive(Debug, Copy, Clone)] pub struct ValueDroppedError { - #[cfg(any(debug_assertions, feature = "debug_borrows"))] + #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at: &'static std::panic::Location<'static>, } impl Display for ValueDroppedError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("Failed to borrow because the value was dropped.")?; - #[cfg(any(debug_assertions, feature = "debug_borrows"))] + #[cfg(any(debug_assertions, feature = "debug_ownership"))] f.write_fmt(format_args!("created_at: {}", self.created_at))?; Ok(()) } @@ -686,7 +684,7 @@ impl Owner { pub fn insert_with_caller( &self, value: T, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] + #[cfg(any(debug_assertions, feature = "debug_ownership"))] caller: &'static std::panic::Location<'static>, ) -> GenerationalBox { let mut location = self.store.claim(); @@ -706,7 +704,7 @@ impl Owner { raw: location, #[cfg(any(debug_assertions, feature = "check_generation"))] generation: location.0.generation.get(), - #[cfg(any(debug_assertions, feature = "check_generation"))] + #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at: std::panic::Location::caller(), _marker: PhantomData, } diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index acf598217..aee3b0858 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -1,4 +1,3 @@ -use std::panic::Location; use std::rc::Rc; use dioxus_core::prelude::{ @@ -85,11 +84,18 @@ impl CopyValue { } } - pub(crate) fn new_with_caller(value: T, caller: &'static Location<'static>) -> Self { + pub(crate) fn new_with_caller( + value: T, + #[cfg(debug_assertions)] caller: &'static std::panic::Location<'static>, + ) -> Self { let owner = current_owner(); Self { - value: owner.insert_with_caller(value, caller), + value: owner.insert_with_caller( + value, + #[cfg(debug_assertions)] + caller, + ), origin_scope: current_scope_id().expect("in a virtual dom"), } } diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 1656f559e..70a809e85 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -1,7 +1,6 @@ use std::{ cell::RefCell, ops::{Deref, DerefMut}, - panic::Location, rc::Rc, sync::Arc, }; @@ -48,7 +47,7 @@ use crate::{CopyValue, Effect}; #[track_caller] pub fn use_signal(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal { #[cfg(debug_assertions)] - let caller = Location::caller(); + let caller = std::panic::Location::caller(); *cx.use_hook(|| { Signal::new_with_caller( @@ -161,7 +160,7 @@ impl Signal { /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking. fn new_with_caller( value: T, - #[cfg(debug_assertions)] caller: &'static Location<'static>, + #[cfg(debug_assertions)] caller: &'static std::panic::Location<'static>, ) -> Self { Self { inner: CopyValue::new_with_caller( From 18f8daf0bdc30a6eb71ab7e91863442a13d47ca6 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 21 Aug 2023 15:00:31 -0500 Subject: [PATCH 03/27] fix generational box tests --- packages/generational-box/README.md | 2 ++ packages/generational-box/src/lib.rs | 13 ++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/generational-box/README.md b/packages/generational-box/README.md index 5ab7926ba..f8d4885b8 100644 --- a/packages/generational-box/README.md +++ b/packages/generational-box/README.md @@ -11,6 +11,8 @@ Three main types manage state in Generational Box: Example: ```rust +use generational_box::Store; + // Create a store for this thread let store = Store::default(); diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 1f8cbbe11..84a81769d 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -32,12 +32,12 @@ fn reused() { let first_ptr; { let owner = store.owner(); - first_ptr = owner.insert(1).raw.data.as_ptr(); + first_ptr = owner.insert(1).raw.0.data.as_ptr(); drop(owner); } { let owner = store.owner(); - let second_ptr = owner.insert(1234).raw.data.as_ptr(); + let second_ptr = owner.insert(1234).raw.0.data.as_ptr(); assert_eq!(first_ptr, second_ptr); drop(owner); } @@ -56,7 +56,10 @@ fn leaking_is_ok() { // don't drop the owner std::mem::forget(owner); } - assert_eq!(key.try_read().as_deref(), Some(&"hello world".to_string())); + assert_eq!( + key.try_read().as_deref().unwrap(), + &"hello world".to_string() + ); } #[test] @@ -71,7 +74,7 @@ fn drops() { key = owner.insert(data); // drop the owner } - assert!(key.try_read().is_none()); + assert!(key.try_read().is_err()); } #[test] @@ -132,7 +135,7 @@ fn fuzz() { println!("{:?}", path); for key in valid_keys.iter() { let value = key.read(); - println!("{:?}", value); + println!("{:?}", &*value); assert!(value.starts_with("hello world")); } #[cfg(any(debug_assertions, feature = "check_generation"))] From b9dae3e1e0c535225a64756e58dd7570caabf9ad Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 25 Oct 2023 15:06:08 -0500 Subject: [PATCH 04/27] don't ignore leaks in miri --- .github/workflows/miri.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/miri.yml b/.github/workflows/miri.yml index 4a467b9b2..f9ad3855f 100644 --- a/.github/workflows/miri.yml +++ b/.github/workflows/miri.yml @@ -86,8 +86,7 @@ jobs: # working-directory: tokio env: - # todo: disable memory leaks ignore - MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields -Zmiri-ignore-leaks + MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields PROPTEST_CASES: 10 # Cache the global cargo directory, but NOT the local `target` directory which From eea4a02ef59c07fcd2e143e838a4080a8d59cb49 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 26 Oct 2023 13:23:12 -0500 Subject: [PATCH 05/27] add comments about the cause of the leak --- packages/core/src/bump_frame.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/core/src/bump_frame.rs b/packages/core/src/bump_frame.rs index 9ae11ab79..6927bd8bc 100644 --- a/packages/core/src/bump_frame.rs +++ b/packages/core/src/bump_frame.rs @@ -7,6 +7,8 @@ use std::cell::{Cell, UnsafeCell}; pub(crate) struct BumpFrame { pub bump: UnsafeCell, pub node: Cell<*const RenderReturn<'static>>, + + // The bump allocator will not call the destructor of the objects it allocated. Attributes and props need to have there destructor called, so we keep a list of them to drop before the bump allocator is reset. pub(crate) attributes_to_drop_before_reset: RefCell>>, pub(crate) props_to_drop_before_reset: RefCell>>, } @@ -43,6 +45,10 @@ impl BumpFrame { .push(attribute); } + /// Reset the bump allocator and drop all the attributes and props that were allocated in it. + /// + /// # Safety + /// The caller must insure that no reference to anything allocated in the bump allocator is available after this function is called. pub(crate) unsafe fn reset(&self) { let mut attributes = self.attributes_to_drop_before_reset.borrow_mut(); attributes.drain(..).for_each(|attribute| { From fdec47b90bc09945ccb5f26012b492f38587d872 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 1 Nov 2023 08:19:35 -0500 Subject: [PATCH 06/27] fix clippy --- packages/generational-box/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 628308340..f56904ede 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -485,7 +485,7 @@ pub struct GenerationalRef { borrow: GenerationalRefBorrowInfo, } -impl<'a, T: 'static> GenerationalRef { +impl GenerationalRef { /// Map one ref type to another. pub fn map(orig: GenerationalRef, f: F) -> GenerationalRef where @@ -522,7 +522,7 @@ impl<'a, T: 'static> GenerationalRef { } } -impl<'a, T: 'static> Deref for GenerationalRef { +impl Deref for GenerationalRef { type Target = T; fn deref(&self) -> &Self::Target { @@ -586,7 +586,7 @@ impl GenerationalRefMut { } } -impl<'a, T: 'static> Deref for GenerationalRefMut { +impl Deref for GenerationalRefMut { type Target = T; fn deref(&self) -> &Self::Target { @@ -594,7 +594,7 @@ impl<'a, T: 'static> Deref for GenerationalRefMut { } } -impl<'a, T: 'static> DerefMut for GenerationalRefMut { +impl DerefMut for GenerationalRefMut { fn deref_mut(&mut self) -> &mut Self::Target { self.inner.deref_mut() } From b9554fd744426eeb1fd2ae03202ec9c57f8d5d58 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 5 Nov 2023 08:32:39 -0600 Subject: [PATCH 07/27] add a noop evaluator to the ssr renderer --- .../examples/axum-hello-world/src/main.rs | 1 + packages/fullstack/src/render.rs | 2 + packages/ssr/Cargo.toml | 3 ++ packages/ssr/src/eval.rs | 42 +++++++++++++++++++ packages/ssr/src/incremental.rs | 1 + packages/ssr/src/lib.rs | 2 + 6 files changed, 51 insertions(+) create mode 100644 packages/ssr/src/eval.rs diff --git a/packages/fullstack/examples/axum-hello-world/src/main.rs b/packages/fullstack/examples/axum-hello-world/src/main.rs index 8f1d3c8c6..64e5b44a0 100644 --- a/packages/fullstack/examples/axum-hello-world/src/main.rs +++ b/packages/fullstack/examples/axum-hello-world/src/main.rs @@ -24,6 +24,7 @@ fn app(cx: Scope) -> Element { let mut count = use_state(cx, || 0); let text = use_state(cx, || "...".to_string()); + let eval = use_eval(cx); cx.render(rsx! { div { diff --git a/packages/fullstack/src/render.rs b/packages/fullstack/src/render.rs index cda41f82c..886a5bd99 100644 --- a/packages/fullstack/src/render.rs +++ b/packages/fullstack/src/render.rs @@ -45,6 +45,8 @@ impl SsrRendererPool { .expect("couldn't spawn runtime") .block_on(async move { let mut vdom = VirtualDom::new_with_props(component, props); + // Make sure the evaluator is initialized + dioxus_ssr::eval::init_eval(vdom.base_scope()); let mut to = WriteBuffer { buffer: Vec::new() }; // before polling the future, we need to set the context let prev_context = diff --git a/packages/ssr/Cargo.toml b/packages/ssr/Cargo.toml index f6376452d..71f5b26a0 100644 --- a/packages/ssr/Cargo.toml +++ b/packages/ssr/Cargo.toml @@ -10,6 +10,7 @@ keywords = ["dom", "ui", "gui", "react", "ssr"] [dependencies] dioxus-core = { workspace = true, features = ["serialize"] } +dioxus-html = { workspace = true } askama_escape = "0.10.3" thiserror = "1.0.23" rustc-hash = "1.1.0" @@ -17,6 +18,8 @@ lru = "0.10.0" tracing = { workspace = true } http = "0.2.9" tokio = { version = "1.28", features = ["full"], optional = true } +async-trait = "0.1.58" +serde_json = { version = "1.0" } [dev-dependencies] dioxus = { workspace = true } diff --git a/packages/ssr/src/eval.rs b/packages/ssr/src/eval.rs new file mode 100644 index 000000000..2ab74f4e1 --- /dev/null +++ b/packages/ssr/src/eval.rs @@ -0,0 +1,42 @@ +use async_trait::async_trait; +use dioxus_core::ScopeState; +use dioxus_html::prelude::{EvalError, EvalProvider, Evaluator}; +use std::rc::Rc; + +/// Provides the SSREvalProvider through [`cx.provide_context`]. +pub fn init_eval(cx: &ScopeState) { + let provider: Rc = Rc::new(SSREvalProvider {}); + cx.provide_context(provider); +} + +/// Reprents the ssr-target's provider of evaluators. +pub struct SSREvalProvider; +impl EvalProvider for SSREvalProvider { + fn new_evaluator(&self, _: String) -> Result, EvalError> { + Ok(Rc::new(SSREvaluator) as Rc) + } +} + +/// Represents a ssr-target's JavaScript evaluator. +pub struct SSREvaluator; + +// In ssr rendering we never run or resolve evals. +#[async_trait(?Send)] +impl Evaluator for SSREvaluator { + /// Runs the evaluated JavaScript. + async fn join(&self) -> Result { + std::future::pending::<()>().await; + unreachable!() + } + + /// Sends a message to the evaluated JavaScript. + fn send(&self, _el: serde_json::Value) -> Result<(), EvalError> { + Ok(()) + } + + /// Gets an UnboundedReceiver to receive messages from the evaluated JavaScript. + async fn recv(&self) -> Result { + std::future::pending::<()>().await; + unreachable!() + } +} diff --git a/packages/ssr/src/incremental.rs b/packages/ssr/src/incremental.rs index c3bb25314..34ffa3443 100644 --- a/packages/ssr/src/incremental.rs +++ b/packages/ssr/src/incremental.rs @@ -81,6 +81,7 @@ impl IncrementalRenderer { let mut html_buffer = WriteBuffer { buffer: Vec::new() }; { let mut vdom = VirtualDom::new_with_props(comp, props); + crate::eval::init_eval(vdom.base_scope()); rebuild_with(&mut vdom).await; renderer.render_before_body(&mut *html_buffer)?; diff --git a/packages/ssr/src/lib.rs b/packages/ssr/src/lib.rs index 6794d1f14..fdc0e9333 100644 --- a/packages/ssr/src/lib.rs +++ b/packages/ssr/src/lib.rs @@ -10,6 +10,7 @@ pub mod incremental; #[cfg(feature = "incremental")] mod incremental_cfg; +pub mod eval; pub mod renderer; pub mod template; @@ -42,6 +43,7 @@ pub fn render_lazy(f: LazyNodes<'_, '_>) -> String { }; let mut dom = VirtualDom::new_with_props(lazy_app, props); + crate::eval::init_eval(dom.base_scope()); _ = dom.rebuild(); Renderer::new().render(&dom) From 7f4e2af0c48e33915e96f0ac5e75b4c18e706632 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 13 Nov 2023 08:17:08 -0600 Subject: [PATCH 08/27] fix string memory leak --- packages/core/src/nodes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index c83b1059a..b6a685ad3 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -707,7 +707,7 @@ impl<'a, 'b> IntoDynNode<'b> for &'a str { impl IntoDynNode<'_> for String { fn into_vnode(self, cx: &ScopeState) -> DynamicNode { DynamicNode::Text(VText { - value: cx.bump().alloc(self), + value: cx.bump().alloc_str(&self), id: Default::default(), }) } From 3c115bbef76f21435d8db886a799a47503026e5f Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 19 Nov 2023 10:03:18 -0600 Subject: [PATCH 09/27] add into attribute value impl for String --- packages/core/src/nodes.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index c83b1059a..b489a7e4d 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -791,6 +791,12 @@ impl<'a> IntoAttributeValue<'a> for &'a str { } } +impl<'a> IntoAttributeValue<'a> for String { + fn into_value(self, cx: &'a Bump) -> AttributeValue<'a> { + AttributeValue::Text(cx.alloc_str(&self)) + } +} + impl<'a> IntoAttributeValue<'a> for f64 { fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { AttributeValue::Float(self) From d9220d4e42b86114096a639ba0b308062b6d763a Mon Sep 17 00:00:00 2001 From: tigerros Date: Thu, 23 Nov 2023 23:44:39 +0100 Subject: [PATCH 10/27] Add it (#1660) --- packages/html/src/global_attributes.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/html/src/global_attributes.rs b/packages/html/src/global_attributes.rs index 3792397ad..02b0b32ed 100644 --- a/packages/html/src/global_attributes.rs +++ b/packages/html/src/global_attributes.rs @@ -269,6 +269,9 @@ trait_methods! { /// azimuth: "azimuth", "style"; + /// + backdrop_filter: "backdrop-filter", "style"; + /// backface_visibility: "backface-visibility", "style"; From 18fa1e4831b372ec1cba805de8a1df8626801cd6 Mon Sep 17 00:00:00 2001 From: Exotik850 <60969639+Exotik850@users.noreply.github.com> Date: Mon, 27 Nov 2023 15:42:48 -0600 Subject: [PATCH 11/27] Make UseFuture Clone, factor out dependencies field (#1666) --- packages/hooks/src/use_future.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/hooks/src/use_future.rs b/packages/hooks/src/use_future.rs index 67bcdc74e..b9aa20c31 100644 --- a/packages/hooks/src/use_future.rs +++ b/packages/hooks/src/use_future.rs @@ -31,13 +31,14 @@ where let state = cx.use_hook(move || UseFuture { update: cx.schedule_update(), - needs_regen: Cell::new(true), + needs_regen: Rc::new(Cell::new(true)), state: val.clone(), task: Default::default(), - dependencies: Vec::new(), }); - if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen.get() { + let state_dependencies = cx.use_hook(Vec::new); + + if dependencies.clone().apply(state_dependencies) || state.needs_regen.get() { // kill the old one, if it exists if let Some(task) = state.task.take() { cx.remove_future(task); @@ -69,11 +70,11 @@ pub enum FutureState<'a, T> { Regenerating(&'a T), // the old value } +#[derive(Clone)] pub struct UseFuture { update: Arc, - needs_regen: Cell, + needs_regen: Rc>, task: Rc>>, - dependencies: Vec>, state: UseState>, } From 8e4debb22632f1673c7d943f4382b1ca7b6b9fa9 Mon Sep 17 00:00:00 2001 From: Exotik850 <60969639+Exotik850@users.noreply.github.com> Date: Mon, 27 Nov 2023 15:45:29 -0600 Subject: [PATCH 12/27] remove #[doc(hidden)] from EventHandler (#1665) * remove #[doc(hidden)] from EventHandler * Make UseFuture Clone, factor out dependencies field * Revert "Make UseFuture Clone, factor out dependencies field" This reverts commit 9ca09e595d1cda3f2047750a923a5bdc49a8fa76. * fix formatting --------- Co-authored-by: Evan Almloff --- packages/core/src/events.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 3b8edb05c..784a8c865 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -107,8 +107,6 @@ impl std::fmt::Debug for Event { } } -#[doc(hidden)] - /// The callback type generated by the `rsx!` macro when an `on` field is specified for components. /// /// This makes it possible to pass `move |evt| {}` style closures into components as property fields. From 694989e82692e17c1712ddb3e79d6198a73fb3c1 Mon Sep 17 00:00:00 2001 From: Exotik850 <60969639+Exotik850@users.noreply.github.com> Date: Tue, 28 Nov 2023 09:51:09 -0600 Subject: [PATCH 13/27] Add use_const hook (#1667) * Add use_const hook * cargo fmt * clippy allow * pub fn on get_rc --- packages/hooks/src/lib.rs | 3 ++ packages/hooks/src/use_const.rs | 76 +++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 packages/hooks/src/use_const.rs diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index a5b92a036..8178d4d3d 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -60,6 +60,9 @@ pub mod computed; mod use_on_destroy; pub use use_on_destroy::*; +mod use_const; +pub use use_const::*; + mod use_context; pub use use_context::*; diff --git a/packages/hooks/src/use_const.rs b/packages/hooks/src/use_const.rs new file mode 100644 index 000000000..47ddb1ed2 --- /dev/null +++ b/packages/hooks/src/use_const.rs @@ -0,0 +1,76 @@ +use std::rc::Rc; + +use dioxus_core::prelude::*; + +/// Store constant state between component renders. +/// +/// UseConst allows you to store state that is initialized once and then remains constant across renders. +/// You can only get an immutable reference after initalization. +/// This can be useful for values that don't need to update reactively, thus can be memoized easily +/// +/// ```rust, ignore +/// struct ComplexData(i32); +/// +/// fn Component(cx: Scope) -> Element { +/// let id = use_const(cx, || ComplexData(100)); +/// +/// cx.render(rsx! { +/// div { "{id.0}" } +/// }) +/// } +/// ``` +#[must_use] +pub fn use_const( + cx: &ScopeState, + initial_state_fn: impl FnOnce() -> T, +) -> &UseConst { + cx.use_hook(|| UseConst { + value: Rc::new(initial_state_fn()), + }) +} + +#[derive(Clone)] +pub struct UseConst { + value: Rc, +} + +impl PartialEq for UseConst { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.value, &other.value) + } +} + +impl core::fmt::Display for UseConst { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.value.fmt(f) + } +} + +impl UseConst { + pub fn get_rc(&self) -> &Rc { + &self.value + } +} + +impl std::ops::Deref for UseConst { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.value.as_ref() + } +} + +#[test] +fn use_const_makes_sense() { + #[allow(unused)] + + fn app(cx: Scope) -> Element { + let const_val = use_const(cx, || vec![0, 1, 2, 3]); + + assert!(const_val[0] == 0); + + // const_val.remove(0); // Cannot Compile, cannot get mutable reference now + + None + } +} From b4fe3829d18a8a8d6ce1a293da12e93bd94c76f5 Mon Sep 17 00:00:00 2001 From: Exotik850 <60969639+Exotik850@users.noreply.github.com> Date: Tue, 28 Nov 2023 16:41:14 -0600 Subject: [PATCH 14/27] Add cargo make tidy/miri (#1670) * Add cargo make tidy/miri * fmt * seperate tidy and tests --- Makefile.toml | 74 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 4 deletions(-) diff --git a/Makefile.toml b/Makefile.toml index 6f331b98f..1bfb02217 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -24,12 +24,64 @@ script = [ ] script_runner = "@duckscript" +[tasks.format] +command = "cargo" +args = ["fmt", "--all"] + +[tasks.check] +command = "cargo" +args = ["check", "--workspace", "--examples", "--tests"] + +[tasks.clippy] +command = "cargo" +args = [ + "clippy", + "--workspace", + "--examples", + "--tests", + "--", + "-D", + "warnings", +] + +[tasks.tidy] +category = "Formatting" +dependencies = ["format", "check", "clippy"] +description = "Format and Check workspace" + +[tasks.install-miri] +toolchain = "nightly" +install_crate = { rustup_component_name = "miri", binary = "cargo +nightly miri", test_arg = "--help" } +private = true + +[tasks.miri-native] +command = "cargo" +toolchain = "nightly" +dependencies = ["install-miri"] +args = [ + "miri", + "test", + "--package", + "dioxus-native-core", + "--test", + "miri_native", +] + +[tasks.miri-stress] +command = "cargo" +toolchain = "nightly" +dependencies = ["install-miri"] +args = ["miri", "test", "--package", "dioxus-core", "--test", "miri_stress"] + +[tasks.miri] +dependencies = ["miri-native", "miri-stress"] + [tasks.tests] category = "Testing" dependencies = ["tests-setup"] description = "Run all tests" -env = {CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*"]} -run_task = {name = ["test-flow", "test-with-browser"], fork = true} +env = { CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*"] } +run_task = { name = ["test-flow", "test-with-browser"], fork = true } [tasks.build] command = "cargo" @@ -42,10 +94,24 @@ private = true [tasks.test] dependencies = ["build"] command = "cargo" -args = ["test", "--lib", "--bins", "--tests", "--examples", "--workspace", "--exclude", "dioxus-router", "--exclude", "dioxus-desktop"] +args = [ + "test", + "--lib", + "--bins", + "--tests", + "--examples", + "--workspace", + "--exclude", + "dioxus-router", + "--exclude", + "dioxus-desktop", +] private = true [tasks.test-with-browser] -env = { CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS = ["**/packages/router", "**/packages/desktop"] } +env = { CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS = [ + "**/packages/router", + "**/packages/desktop", +] } private = true workspace = true From e0fbed7eea90592d2769e8e763b69aea58af0106 Mon Sep 17 00:00:00 2001 From: Exotik850 <60969639+Exotik850@users.noreply.github.com> Date: Tue, 28 Nov 2023 16:54:17 -0600 Subject: [PATCH 15/27] Switch outdated tui crate for ratatui (#1671) * Switch outdated tui crate for ratatui * fix query docs whoops --- packages/rink/Cargo.toml | 2 +- packages/rink/src/lib.rs | 6 +++--- packages/rink/src/render.rs | 7 +++---- packages/rink/src/style.rs | 3 ++- packages/rink/src/style_attributes.rs | 8 ++++---- packages/rink/src/widget.rs | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/rink/Cargo.toml b/packages/rink/Cargo.toml index ba3d549f8..92d1eae8c 100644 --- a/packages/rink/Cargo.toml +++ b/packages/rink/Cargo.toml @@ -14,7 +14,7 @@ dioxus-html = { workspace = true } dioxus-native-core = { workspace = true, features = ["layout-attributes"] } dioxus-native-core-macro = { workspace = true } -tui = "0.17.0" +ratatui = "0.24.0" crossterm = "0.26.1" anyhow = "1.0.42" tokio = { workspace = true, features = ["full"] } diff --git a/packages/rink/src/lib.rs b/packages/rink/src/lib.rs index 2eba1b98a..4a7261b55 100644 --- a/packages/rink/src/lib.rs +++ b/packages/rink/src/lib.rs @@ -17,6 +17,7 @@ use futures::{channel::mpsc::UnboundedSender, pin_mut, Future, StreamExt}; use futures_channel::mpsc::unbounded; use layout::TaffyLayout; use prevent_default::PreventDefault; +use ratatui::{backend::CrosstermBackend, Terminal}; use std::{io, time::Duration}; use std::{ pin::Pin, @@ -26,7 +27,6 @@ use std::{rc::Rc, sync::RwLock}; use style_attributes::StyleModifier; pub use taffy::{geometry::Point, prelude::*}; use tokio::select; -use tui::{backend::CrosstermBackend, Terminal}; use widgets::{register_widgets, RinkWidgetResponder, RinkWidgetTraitObject}; mod config; @@ -180,7 +180,7 @@ pub fn render( if !to_rerender.is_empty() || updated { updated = false; - fn resize(dims: tui::layout::Rect, taffy: &mut Taffy, rdom: &RealDom) { + fn resize(dims: ratatui::layout::Rect, taffy: &mut Taffy, rdom: &RealDom) { let width = screen_to_layout_space(dims.width); let height = screen_to_layout_space(dims.height); let root_node = rdom @@ -222,7 +222,7 @@ pub fn render( } else { let rdom = rdom.read().unwrap(); resize( - tui::layout::Rect { + ratatui::layout::Rect { x: 0, y: 0, width: 1000, diff --git a/packages/rink/src/render.rs b/packages/rink/src/render.rs index 19d642720..4185f3a62 100644 --- a/packages/rink/src/render.rs +++ b/packages/rink/src/render.rs @@ -1,11 +1,10 @@ use dioxus_native_core::{prelude::*, tree::TreeRef}; -use std::io::Stdout; +use ratatui::{layout::Rect, style::Color}; use taffy::{ geometry::Point, prelude::{Dimension, Layout, Size}, Taffy, }; -use tui::{backend::CrosstermBackend, layout::Rect, style::Color}; use crate::{ focus::Focused, @@ -20,7 +19,7 @@ use crate::{ const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; pub(crate) fn render_vnode( - frame: &mut tui::Frame>, + frame: &mut ratatui::Frame, layout: &Taffy, node: NodeRef, cfg: Config, @@ -96,7 +95,7 @@ pub(crate) fn render_vnode( impl RinkWidget for NodeRef<'_> { fn render(self, area: Rect, mut buf: RinkBuffer<'_>) { - use tui::symbols::line::*; + use ratatui::symbols::line::*; enum Direction { Left, diff --git a/packages/rink/src/style.rs b/packages/rink/src/style.rs index 857322079..905cc4b34 100644 --- a/packages/rink/src/style.rs +++ b/packages/rink/src/style.rs @@ -1,6 +1,6 @@ use std::{num::ParseFloatError, str::FromStr}; -use tui::style::{Color, Modifier, Style}; +use ratatui::style::{Color, Modifier, Style}; use crate::RenderingMode; @@ -442,6 +442,7 @@ impl RinkStyle { impl From for Style { fn from(val: RinkStyle) -> Self { Style { + underline_color: None, fg: val.fg.map(|c| c.color), bg: val.bg.map(|c| c.color), add_modifier: val.add_modifier, diff --git a/packages/rink/src/style_attributes.rs b/packages/rink/src/style_attributes.rs index 0c87e3f83..d1e8c6bee 100644 --- a/packages/rink/src/style_attributes.rs +++ b/packages/rink/src/style_attributes.rs @@ -187,8 +187,8 @@ pub enum BorderStyle { } impl BorderStyle { - pub fn symbol_set(&self) -> Option { - use tui::symbols::line::*; + pub fn symbol_set(&self) -> Option { + use ratatui::symbols::line::*; const DASHED: Set = Set { horizontal: "╌", vertical: "╎", @@ -570,7 +570,7 @@ fn apply_animation(name: &str, _value: &str, _style: &mut StyleModifier) { } fn apply_font(name: &str, value: &str, style: &mut StyleModifier) { - use tui::style::Modifier; + use ratatui::style::Modifier; match name { "font" => (), "font-family" => (), @@ -593,7 +593,7 @@ fn apply_font(name: &str, value: &str, style: &mut StyleModifier) { } fn apply_text(name: &str, value: &str, style: &mut StyleModifier) { - use tui::style::Modifier; + use ratatui::style::Modifier; match name { "text-align" => todo!(), diff --git a/packages/rink/src/widget.rs b/packages/rink/src/widget.rs index ab9044998..91af5b4f6 100644 --- a/packages/rink/src/widget.rs +++ b/packages/rink/src/widget.rs @@ -1,4 +1,4 @@ -use tui::{ +use ratatui::{ buffer::Buffer, layout::Rect, style::{Color, Modifier}, From c8a9a7b1d5cf06c59c90ee54d5ccbe12f06c6b7a Mon Sep 17 00:00:00 2001 From: zhangzhonglai Date: Fri, 1 Dec 2023 21:52:20 +0800 Subject: [PATCH 16/27] docs(desktop): fix wrong example code (#1678) * docs(desktop): fix wrong example code * stop ignoring doctests in dioxus desktop --------- Co-authored-by: ealmloff --- packages/desktop/src/lib.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index bfefaf547..63a8914f3 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -54,7 +54,7 @@ use wry::{application::window::WindowId, webview::WebContext}; /// /// This function will start a multithreaded Tokio runtime as well the WebView event loop. /// -/// ```rust, ignore +/// ```rust, no_run /// use dioxus::prelude::*; /// /// fn main() { @@ -77,11 +77,12 @@ pub fn launch(root: Component) { /// /// You can configure the WebView window with a configuration closure /// -/// ```rust, ignore +/// ```rust, no_run /// use dioxus::prelude::*; +/// use dioxus_desktop::*; /// /// fn main() { -/// dioxus_desktop::launch_cfg(app, |c| c.with_window(|w| w.with_title("My App"))); +/// dioxus_desktop::launch_cfg(app, Config::default().with_window(WindowBuilder::new().with_title("My App"))); /// } /// /// fn app(cx: Scope) -> Element { @@ -100,8 +101,9 @@ pub fn launch_cfg(root: Component, config_builder: Config) { /// /// You can configure the WebView window with a configuration closure /// -/// ```rust, ignore +/// ```rust, no_run /// use dioxus::prelude::*; +/// use dioxus_desktop::Config; /// /// fn main() { /// dioxus_desktop::launch_with_props(app, AppProps { name: "asd" }, Config::default()); From a2ca1760c9f679c8904df2d4a9bde9c43a8697a4 Mon Sep 17 00:00:00 2001 From: Felix F Xu <84662027+felixf4xu@users.noreply.github.com> Date: Sun, 3 Dec 2023 09:25:27 +0800 Subject: [PATCH 17/27] Set local_socket_stream to blocking (default) (#1682) --- packages/cli/src/server/desktop/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/server/desktop/mod.rs b/packages/cli/src/server/desktop/mod.rs index 978b97d43..98d6e9244 100644 --- a/packages/cli/src/server/desktop/mod.rs +++ b/packages/cli/src/server/desktop/mod.rs @@ -121,9 +121,9 @@ async fn start_desktop_hot_reload(hot_reload_state: HotReloadState) -> Result<() let file_map = hot_reload_state.file_map.clone(); let channels = channels.clone(); let aborted = aborted.clone(); - let _ = local_socket_stream.set_nonblocking(true); move || { loop { + //accept() will block the thread when local_socket_stream is in blocking mode (default) match local_socket_stream.accept() { Ok(mut connection) => { // send any templates than have changed before the socket connected From d404ddfccfdacc57ec6043079fa1f07f3a50b947 Mon Sep 17 00:00:00 2001 From: Felix F Xu <84662027+felixf4xu@users.noreply.github.com> Date: Mon, 4 Dec 2023 10:02:30 +0800 Subject: [PATCH 18/27] fix false notification/rebuild for dx hot-reload (#1684) * fix false notification/rebuild for dx hot-reload * Make sure we have permissions and the changed file exists before reading the metadata --------- Co-authored-by: ealmloff --- packages/cli/src/server/mod.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/cli/src/server/mod.rs b/packages/cli/src/server/mod.rs index 79a5531b6..eb4021184 100644 --- a/packages/cli/src/server/mod.rs +++ b/packages/cli/src/server/mod.rs @@ -55,6 +55,16 @@ async fn setup_file_watcher Result + Send + 'static>( break; } + // Workaround for notify and vscode-like editor: + // when edit & save a file in vscode, there will be two notifications, + // the first one is a file with empty content. + // filter the empty file notification to avoid false rebuild during hot-reload + if let Ok(metadata) = fs::metadata(path) { + if metadata.len() == 0 { + continue; + } + } + match rsx_file_map.update_rsx(path, &config.crate_dir) { Ok(UpdateResult::UpdatedRsx(msgs)) => { messages.extend(msgs); From e69a4a7cd84da0ac371d4933f7704493c5a9735a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 07:07:35 -0600 Subject: [PATCH 19/27] Bump JamesIves/github-pages-deploy-action from 4.4.3 to 4.5.0 (#1685) Bumps [JamesIves/github-pages-deploy-action](https://github.com/jamesives/github-pages-deploy-action) from 4.4.3 to 4.5.0. - [Release notes](https://github.com/jamesives/github-pages-deploy-action/releases) - [Commits](https://github.com/jamesives/github-pages-deploy-action/compare/v4.4.3...v4.5.0) --- updated-dependencies: - dependency-name: JamesIves/github-pages-deploy-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docs stable.yml | 2 +- .github/workflows/docs.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs stable.yml b/.github/workflows/docs stable.yml index 642216f0f..523a24067 100644 --- a/.github/workflows/docs stable.yml +++ b/.github/workflows/docs stable.yml @@ -33,7 +33,7 @@ jobs: # cd fermi && mdbook build -d ../nightly/fermi && cd .. - name: Deploy 🚀 - uses: JamesIves/github-pages-deploy-action@v4.4.3 + uses: JamesIves/github-pages-deploy-action@v4.5.0 with: branch: gh-pages # The branch the action should deploy to. folder: docs/nightly # The folder the action should deploy. diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 88542aea8..7701209c1 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -39,7 +39,7 @@ jobs: # cd fermi && mdbook build -d ../nightly/fermi && cd .. - name: Deploy 🚀 - uses: JamesIves/github-pages-deploy-action@v4.4.3 + uses: JamesIves/github-pages-deploy-action@v4.5.0 with: branch: gh-pages # The branch the action should deploy to. folder: docs/nightly # The folder the action should deploy. From 8149868ebc8610c4c2bc6fc41b50d9be4f4bfb40 Mon Sep 17 00:00:00 2001 From: Raul Cesar Teixeira Date: Mon, 4 Dec 2023 21:11:02 -0300 Subject: [PATCH 20/27] Fixed conflict of "--verbose" and "--quiet" flags (#1686) Co-authored-by: Raul Cesar --- packages/cli/src/builder.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/builder.rs b/packages/cli/src/builder.rs index 71be8f115..08e636237 100644 --- a/packages/cli/src/builder.rs +++ b/packages/cli/src/builder.rs @@ -54,8 +54,7 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result { .arg("build") .arg("--target") .arg("wasm32-unknown-unknown") - .arg("--message-format=json") - .arg("--quiet"); + .arg("--message-format=json"); let cmd = if config.release { cmd.arg("--release") @@ -65,7 +64,7 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result { let cmd = if config.verbose { cmd.arg("--verbose") } else { - cmd + cmd.arg("--quiet") }; let cmd = if config.custom_profile.is_some() { From 04fd2487b3edaf3c8f5cc347562de942fb10504c Mon Sep 17 00:00:00 2001 From: Ben Sully Date: Tue, 5 Dec 2023 20:10:18 +0000 Subject: [PATCH 21/27] feat(fullstack): add `render_handler_with_state` (#1687) * feat(fullstack): add `render_handler_with_state` When using server functions, the current pattern to access state such as database connections is to use `register_server_fns_with_handler` on an Axum router and 'inject' the state into the context provided to the server function. However, this only affects function calls which go via the Axum router; SSR renders bypass this, and therefore don't have access to any state. This commit adds an alternative `render_handler` which accepts some additional state. That state is injected into the context in a similar manner to `register_server_fns_with_handler`. SSR renders can then proceed to run in the same way as HTTP calls. * Change state object to 'inject_state' callback Also add a compiling doctest example. * remove the explicit for<'a> lifetime * remove unused assets_path from render_handler_with_context example --------- Co-authored-by: Evan Almloff --- .../fullstack/src/adapters/axum_adapter.rs | 66 +++++++++++++++++-- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/packages/fullstack/src/adapters/axum_adapter.rs b/packages/fullstack/src/adapters/axum_adapter.rs index 95d9ee6cd..41a7c92be 100644 --- a/packages/fullstack/src/adapters/axum_adapter.rs +++ b/packages/fullstack/src/adapters/axum_adapter.rs @@ -369,15 +369,65 @@ fn apply_request_parts_to_response( } } -/// SSR renderer handler for Axum -pub async fn render_handler( - State((cfg, ssr_state)): State<(ServeConfig

, SSRState)>, +/// SSR renderer handler for Axum with added context injection. +/// +/// # Example +/// ```rust,no_run +/// #![allow(non_snake_case)] +/// use std::sync::{Arc, Mutex}; +/// +/// use axum::routing::get; +/// use dioxus::prelude::*; +/// use dioxus_fullstack::{axum_adapter::render_handler_with_context, prelude::*}; +/// +/// fn app(cx: Scope) -> Element { +/// render! { +/// "hello!" +/// } +/// } +/// +/// #[tokio::main] +/// async fn main() { +/// let cfg = ServeConfigBuilder::new(app, ()) +/// .assets_path("dist") +/// .build(); +/// let ssr_state = SSRState::new(&cfg); +/// +/// // This could be any state you want to be accessible from your server +/// // functions using `[DioxusServerContext::get]`. +/// let state = Arc::new(Mutex::new("state".to_string())); +/// +/// let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080)); +/// axum::Server::bind(&addr) +/// .serve( +/// axum::Router::new() +/// // Register server functions, etc. +/// // Note you probably want to use `register_server_fns_with_handler` +/// // to inject the context into server functions running outside +/// // of an SSR render context. +/// .fallback(get(render_handler_with_context).with_state(( +/// move |ctx| ctx.insert(state.clone()).unwrap(), +/// cfg, +/// ssr_state, +/// ))) +/// .into_make_service(), +/// ) +/// .await +/// .unwrap(); +/// } +/// ``` +pub async fn render_handler_with_context< + P: Clone + serde::Serialize + Send + Sync + 'static, + F: FnMut(&mut DioxusServerContext), +>( + State((mut inject_context, cfg, ssr_state)): State<(F, ServeConfig

, SSRState)>, request: Request, ) -> impl IntoResponse { let (parts, _) = request.into_parts(); let url = parts.uri.path_and_query().unwrap().to_string(); let parts: Arc> = Arc::new(RwLock::new(parts.into())); - let server_context = DioxusServerContext::new(parts.clone()); + let mut server_context = DioxusServerContext::new(parts.clone()); + inject_context(&mut server_context); match ssr_state.render(url, &cfg, &server_context).await { Ok(rendered) => { @@ -395,6 +445,14 @@ pub async fn render_handler } } +/// SSR renderer handler for Axum +pub async fn render_handler( + State((cfg, ssr_state)): State<(ServeConfig

, SSRState)>, + request: Request, +) -> impl IntoResponse { + render_handler_with_context(State((|_: &mut _| (), cfg, ssr_state)), request).await +} + fn report_err(e: E) -> Response { Response::builder() .status(StatusCode::INTERNAL_SERVER_ERROR) From 0c9248688372cdefd3fedb8bc884ee3fcb3ddcca Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 5 Dec 2023 22:57:20 -0800 Subject: [PATCH 22/27] publish generational box --- Cargo.toml | 4 ++-- packages/generational-box/Cargo.toml | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d687d74d0..153866883 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,8 +76,8 @@ plasmo = { path = "packages/rink", version = "0.4.0" } dioxus-native-core = { path = "packages/native-core", version = "0.4.0" } dioxus-native-core-macro = { path = "packages/native-core-macro", version = "0.4.0" } rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.4.0" } -dioxus-signals = { path = "packages/signals" } -generational-box = { path = "packages/generational-box" } +dioxus-signals = { path = "packages/signals", version = "0.1.0" } +generational-box = { path = "packages/generational-box", version = "0.1.0" } dioxus-hot-reload = { path = "packages/hot-reload", version = "0.4.0" } dioxus-fullstack = { path = "packages/fullstack", version = "0.4.1" } dioxus_server_macro = { path = "packages/server-macro", version = "0.4.1" } diff --git a/packages/generational-box/Cargo.toml b/packages/generational-box/Cargo.toml index 6c5a5a841..661c9725b 100644 --- a/packages/generational-box/Cargo.toml +++ b/packages/generational-box/Cargo.toml @@ -1,9 +1,12 @@ [package] name = "generational-box" authors = ["Evan Almloff"] -version = "0.0.0" +version = "0.1.0" edition = "2018" - +description = "A box backed by a generational runtime" +license = "MIT OR Apache-2.0" +repository = "https://github.com/DioxusLabs/dioxus/" +keywords = ["generational", "box", "memory", "allocator"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] From 6088483fb2cdd27a0c536a6a596f0c986fe23927 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 5 Dec 2023 23:01:10 -0800 Subject: [PATCH 23/27] fix signals import --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 153866883..06e4ca1fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,7 +76,7 @@ plasmo = { path = "packages/rink", version = "0.4.0" } dioxus-native-core = { path = "packages/native-core", version = "0.4.0" } dioxus-native-core-macro = { path = "packages/native-core-macro", version = "0.4.0" } rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.4.0" } -dioxus-signals = { path = "packages/signals", version = "0.1.0" } +dioxus-signals = { path = "packages/signals" } generational-box = { path = "packages/generational-box", version = "0.1.0" } dioxus-hot-reload = { path = "packages/hot-reload", version = "0.4.0" } dioxus-fullstack = { path = "packages/fullstack", version = "0.4.1" } From 8530f53692ec4ce2df6dcec4fb2af85576d6fdac Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Wed, 6 Dec 2023 04:55:07 -0800 Subject: [PATCH 24/27] Fix typo (#1695) * Fix typo * Fix typo again --- packages/core/src/mutations.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 27976c157..f33590e2e 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -91,7 +91,7 @@ pub enum Mutation<'a> { id: ElementId, }, - /// Create an placeholder int he DOM that we will use later. + /// Create a placeholder in the DOM that we will use later. /// /// Dioxus currently requires the use of placeholders to maintain a re-entrance point for things like list diffing CreatePlaceholder { From ab1fab5f50e5e9b487ce2f7e4535d659b1d3e274 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 6 Dec 2023 16:49:05 -0800 Subject: [PATCH 25/27] No publish demos --- examples/openid_connect_demo/Cargo.toml | 1 + examples/query_segments_demo/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/examples/openid_connect_demo/Cargo.toml b/examples/openid_connect_demo/Cargo.toml index 4c5f47061..2a1abe173 100644 --- a/examples/openid_connect_demo/Cargo.toml +++ b/examples/openid_connect_demo/Cargo.toml @@ -2,6 +2,7 @@ name = "openid_auth_demo" version = "0.1.0" edition = "2021" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/examples/query_segments_demo/Cargo.toml b/examples/query_segments_demo/Cargo.toml index 4a7d784c7..ee9f49c70 100644 --- a/examples/query_segments_demo/Cargo.toml +++ b/examples/query_segments_demo/Cargo.toml @@ -2,6 +2,7 @@ name = "query_segments_demo" version = "0.1.0" edition = "2021" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From d3f6ff703c0f90cd58da8d459be056db29f3c38e Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 6 Dec 2023 16:56:35 -0800 Subject: [PATCH 26/27] Release 0.4.3 dioxus@0.4.3 dioxus-autofmt@0.4.3 dioxus-check@0.4.3 dioxus-cli@0.4.3 dioxus-core@0.4.3 dioxus-core-macro@0.4.3 dioxus-desktop@0.4.3 dioxus-fullstack@0.4.3 dioxus-hooks@0.4.3 dioxus-hot-reload@0.4.3 dioxus-html@0.4.3 dioxus-interpreter-js@0.4.3 dioxus-liveview@0.4.3 dioxus-mobile@0.4.3 dioxus-native-core@0.4.3 dioxus-native-core-macro@0.4.3 dioxus-router@0.4.3 dioxus-router-macro@0.4.3 dioxus-rsx@0.4.3 dioxus-signals@0.4.3 dioxus-ssr@0.4.3 dioxus-tui@0.4.3 dioxus-web@0.4.3 dioxus_server_macro@0.4.3 fermi@0.4.3 generational-box@0.4.3 plasmo@0.4.3 rsx-rosetta@0.4.3 Generated by cargo-workspaces --- Cargo.toml | 6 +++--- examples/tailwind/Cargo.toml | 2 +- packages/cli/Cargo.toml | 2 +- packages/generational-box/Cargo.toml | 2 +- packages/signals/Cargo.toml | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 06e4ca1fa..1772c1f47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ members = [ exclude = ["examples/mobile_demo"] [workspace.package] -version = "0.4.2" +version = "0.4.3" # dependencies that are shared across packages [workspace.dependencies] @@ -77,7 +77,7 @@ dioxus-native-core = { path = "packages/native-core", version = "0.4.0" } dioxus-native-core-macro = { path = "packages/native-core-macro", version = "0.4.0" } rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.4.0" } dioxus-signals = { path = "packages/signals" } -generational-box = { path = "packages/generational-box", version = "0.1.0" } +generational-box = { path = "packages/generational-box", version = "0.4.3" } dioxus-hot-reload = { path = "packages/hot-reload", version = "0.4.0" } dioxus-fullstack = { path = "packages/fullstack", version = "0.4.1" } dioxus_server_macro = { path = "packages/server-macro", version = "0.4.1" } @@ -99,7 +99,7 @@ prettyplease = { package = "prettier-please", version = "0.2", features = [ # It is not meant to be published, but is used so "cargo run --example XYZ" works properly [package] name = "dioxus-examples" -version = "0.0.0" +version = "0.4.3" authors = ["Jonathan Kelley"] edition = "2021" description = "Top level crate for the Dioxus repository" diff --git a/examples/tailwind/Cargo.toml b/examples/tailwind/Cargo.toml index 5c2fbe666..26ac202f1 100644 --- a/examples/tailwind/Cargo.toml +++ b/examples/tailwind/Cargo.toml @@ -18,4 +18,4 @@ dioxus = { path = "../../packages/dioxus" } dioxus-desktop = { path = "../../packages/desktop" } [target.'cfg(target_arch = "wasm32")'.dependencies] -dioxus-web = { path = "../../packages/web" } \ No newline at end of file +dioxus-web = { path = "../../packages/web" } diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index adb0239f2..128133602 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-cli" -version = "0.4.1" +version = "0.4.3" authors = ["Jonathan Kelley"] edition = "2021" description = "CLI tool for developing, testing, and publishing Dioxus apps" diff --git a/packages/generational-box/Cargo.toml b/packages/generational-box/Cargo.toml index 661c9725b..f070c1805 100644 --- a/packages/generational-box/Cargo.toml +++ b/packages/generational-box/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "generational-box" authors = ["Evan Almloff"] -version = "0.1.0" +version = "0.4.3" edition = "2018" description = "A box backed by a generational runtime" license = "MIT OR Apache-2.0" diff --git a/packages/signals/Cargo.toml b/packages/signals/Cargo.toml index 56e19bbd8..34ae671b3 100644 --- a/packages/signals/Cargo.toml +++ b/packages/signals/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "dioxus-signals" authors = ["Jonathan Kelley"] -version = "0.0.0" +version = "0.4.3" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -20,4 +20,4 @@ tokio = { version = "1", features = ["full"] } [features] default = [] -serialize = ["serde"] \ No newline at end of file +serialize = ["serde"] From 16982c9f69adcebae9d9f086328bb20b11d6882b Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 6 Dec 2023 20:54:50 -0800 Subject: [PATCH 27/27] Add metadata for signals crate --- packages/signals/Cargo.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/signals/Cargo.toml b/packages/signals/Cargo.toml index 34ae671b3..c85fb6d7f 100644 --- a/packages/signals/Cargo.toml +++ b/packages/signals/Cargo.toml @@ -3,6 +3,12 @@ name = "dioxus-signals" authors = ["Jonathan Kelley"] version = "0.4.3" edition = "2018" +description = "Signals for Dioxus" +license = "MIT OR Apache-2.0" +repository = "https://github.com/DioxusLabs/dioxus/" +homepage = "https://dioxuslabs.com" +keywords = ["dom", "ui", "gui", "react", "wasm"] +rust-version = "1.60.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html