Merge branch 'master' into cli-config-library

This commit is contained in:
Jonathan Kelley 2023-12-06 21:30:14 -08:00 committed by GitHub
commit ea15c6a2eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 877 additions and 144 deletions

View file

@ -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.

View file

@ -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.

View file

@ -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

View file

@ -51,7 +51,7 @@ members = [
exclude = ["examples/mobile_demo"]
[workspace.package]
version = "0.4.2"
version = "0.4.3"
# dependencies that are shared across packages
[workspace.dependencies]
@ -78,8 +78,8 @@ 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-cli-config = { path = "packages/cli-config", version = "0.4.1" }
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" }
@ -101,7 +101,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"

View file

@ -24,6 +24,58 @@ 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"]
@ -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

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -57,8 +57,7 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result<BuildResult> {
.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")
@ -68,7 +67,7 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result<BuildResult> {
let cmd = if config.verbose {
cmd.arg("--verbose")
} else {
cmd
cmd.arg("--quiet")
};
let cmd = if config.custom_profile.is_some() {

View file

@ -123,9 +123,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

View file

@ -47,6 +47,16 @@ async fn setup_file_watcher<F: Fn() -> Result<BuildResult> + 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);

View file

@ -1,5 +1,5 @@
use crate::nodes::RenderReturn;
use crate::{Attribute, AttributeValue};
use crate::{Attribute, AttributeValue, VComponent};
use bumpalo::Bump;
use std::cell::RefCell;
use std::cell::{Cell, UnsafeCell};
@ -7,7 +7,10 @@ use std::cell::{Cell, UnsafeCell};
pub(crate) struct BumpFrame {
pub bump: UnsafeCell<Bump>,
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<Vec<*const Attribute<'static>>>,
pub(crate) props_to_drop_before_reset: RefCell<Vec<*const VComponent<'static>>>,
}
impl BumpFrame {
@ -17,6 +20,7 @@ impl BumpFrame {
bump: UnsafeCell::new(bump),
node: Cell::new(std::ptr::null()),
attributes_to_drop_before_reset: Default::default(),
props_to_drop_before_reset: Default::default(),
}
}
@ -41,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| {
@ -49,9 +57,20 @@ impl BumpFrame {
_ = l.take();
}
});
let mut props = self.props_to_drop_before_reset.borrow_mut();
props.drain(..).for_each(|prop| {
let prop = unsafe { &*prop };
_ = prop.props.borrow_mut().take();
});
unsafe {
let bump = &mut *self.bump.get();
bump.reset();
}
}
}
impl Drop for BumpFrame {
fn drop(&mut self) {
unsafe { self.reset() }
}
}

View file

@ -107,8 +107,6 @@ impl<T: std::fmt::Debug> std::fmt::Debug for Event<T> {
}
}
#[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.

View file

@ -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 {

View file

@ -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(),
})
}
@ -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)

View file

@ -367,12 +367,17 @@ impl<'src> ScopeState {
}
let mut props = self.borrowed_props.borrow_mut();
let mut drop_props = self
.previous_frame()
.props_to_drop_before_reset
.borrow_mut();
for node in element.dynamic_nodes {
if let DynamicNode::Component(comp) = node {
if !comp.static_props {
let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) };
if !comp.static_props {
props.push(unbounded);
}
drop_props.push(unbounded);
}
}

View file

@ -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());

View file

@ -24,6 +24,7 @@ fn app(cx: Scope<AppProps>) -> Element {
let mut count = use_state(cx, || 0);
let text = use_state(cx, || "...".to_string());
let eval = use_eval(cx);
cx.render(rsx! {
div {

View file

@ -369,15 +369,65 @@ fn apply_request_parts_to_response<B>(
}
}
/// SSR renderer handler for Axum
pub async fn render_handler<P: Clone + serde::Serialize + Send + Sync + 'static>(
State((cfg, ssr_state)): State<(ServeConfig<P>, 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<P>, SSRState)>,
request: Request<Body>,
) -> impl IntoResponse {
let (parts, _) = request.into_parts();
let url = parts.uri.path_and_query().unwrap().to_string();
let parts: Arc<RwLock<http::request::Parts>> = 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<P: Clone + serde::Serialize + Send + Sync + 'static>
}
}
/// SSR renderer handler for Axum
pub async fn render_handler<P: Clone + serde::Serialize + Send + Sync + 'static>(
State((cfg, ssr_state)): State<(ServeConfig<P>, SSRState)>,
request: Request<Body>,
) -> impl IntoResponse {
render_handler_with_context(State((|_: &mut _| (), cfg, ssr_state)), request).await
}
fn report_err<E: std::fmt::Display>(e: E) -> Response<BoxBody> {
Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)

View file

@ -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 =

View file

@ -1,9 +1,12 @@
[package]
name = "generational-box"
authors = ["Evan Almloff"]
version = "0.0.0"
version = "0.4.3"
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]
@ -15,3 +18,5 @@ rand = "0.8.5"
[features]
default = ["check_generation"]
check_generation = []
debug_borrows = []
debug_ownership = []

View file

@ -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();

View file

@ -2,9 +2,12 @@
#![warn(missing_docs)]
use std::{
any::Any,
cell::{Cell, Ref, RefCell, RefMut},
fmt::Debug,
error::Error,
fmt::{Debug, Display},
marker::PhantomData,
ops::{Deref, DerefMut},
rc::Rc,
};
@ -29,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);
}
@ -53,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]
@ -68,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]
@ -129,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"))]
@ -153,6 +159,8 @@ pub struct GenerationalBox<T> {
raw: MemoryLocation,
#[cfg(any(debug_assertions, feature = "check_generation"))]
generation: u32,
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
created_at: &'static std::panic::Location<'static>,
_marker: PhantomData<T>,
}
@ -161,7 +169,7 @@ impl<T: 'static> Debug for GenerationalBox<T> {
#[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 +183,7 @@ impl<T: 'static> GenerationalBox<T> {
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 +192,51 @@ impl<T: 'static> GenerationalBox<T> {
}
/// Try to read the value. Returns None if the value is no longer valid.
pub fn try_read(&self) -> Option<Ref<'static, T>> {
self.validate()
.then(|| {
Ref::filter_map(self.raw.data.borrow(), |any| {
any.as_ref()?.downcast_ref::<T>()
})
.ok()
})
.flatten()
#[track_caller]
pub fn try_read(&self) -> Result<GenerationalRef<T>, 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<'static, 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<RefMut<'static, T>> {
self.validate()
.then(|| {
RefMut::filter_map(self.raw.data.borrow_mut(), |any| {
any.as_mut()?.downcast_mut::<T>()
})
.ok()
})
.flatten()
#[track_caller]
pub fn try_write(&self) -> Result<GenerationalRefMut<T>, 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<'static, 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 +244,8 @@ impl<T: 'static> GenerationalBox<T> {
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 +263,37 @@ impl<T> Clone for GenerationalBox<T> {
}
#[derive(Clone, Copy)]
struct MemoryLocation {
data: &'static RefCell<Option<Box<dyn std::any::Any>>>,
struct MemoryLocation(&'static MemoryLocationInner);
struct MemoryLocationInner {
data: RefCell<Option<Box<dyn std::any::Any>>>,
#[cfg(any(debug_assertions, feature = "check_generation"))]
generation: &'static Cell<u32>,
generation: Cell<u32>,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrowed_at: RefCell<Vec<&'static std::panic::Location<'static>>>,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrowed_mut_at: Cell<Option<&'static std::panic::Location<'static>>>,
}
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<T: 'static>(&mut self, value: T) -> GenerationalBox<T> {
let mut inner_mut = self.data.borrow_mut();
fn replace_with_caller<T: 'static>(
&mut self,
value: T,
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
caller: &'static std::panic::Location<'static>,
) -> GenerationalBox<T> {
let mut inner_mut = self.0.data.borrow_mut();
let raw = Box::new(value);
let old = inner_mut.replace(raw);
@ -273,10 +301,315 @@ 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 = "debug_ownership"))]
created_at: caller,
_marker: PhantomData,
}
}
#[track_caller]
fn try_borrow<T: Any>(
&self,
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
created_at: &'static std::panic::Location<'static>,
) -> Result<GenerationalRef<T>, 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::<T>()) {
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_ownership"))]
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<T: Any>(
&self,
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
created_at: &'static std::panic::Location<'static>,
) -> Result<GenerationalRefMut<T>, 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::<T>()) {
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_ownership"))]
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_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_ownership"))]
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<T: 'static> {
inner: Ref<'static, T>,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrow: GenerationalRefBorrowInfo,
}
impl<T: 'static> GenerationalRef<T> {
/// Map one ref type to another.
pub fn map<U, F>(orig: GenerationalRef<T>, f: F) -> GenerationalRef<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<U, F>(orig: GenerationalRef<T>, f: F) -> Option<GenerationalRef<U>>
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<T: 'static> Deref for GenerationalRef<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<T: 'static> {
inner: RefMut<'static, T>,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrow: GenerationalRefMutBorrowInfo,
}
impl<T: 'static> GenerationalRefMut<T> {
/// Map one ref type to another.
pub fn map<U, F>(orig: GenerationalRefMut<T>, f: F) -> GenerationalRefMut<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<U, F>(orig: GenerationalRefMut<T>, f: F) -> Option<GenerationalRefMut<U>>
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<T: 'static> Deref for GenerationalRefMut<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.inner.deref()
}
}
impl<T: 'static> DerefMut for GenerationalRefMut<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 +638,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 +668,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<T: 'static>(&self, value: T) -> GenerationalBox<T> {
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<T: 'static>(
&self,
value: T,
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
caller: &'static std::panic::Location<'static>,
) -> GenerationalBox<T> {
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 +703,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 = "debug_ownership"))]
created_at: std::panic::Location::caller(),
_marker: PhantomData,
}
}

View file

@ -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::*;

View file

@ -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<T: 'static>(
cx: &ScopeState,
initial_state_fn: impl FnOnce() -> T,
) -> &UseConst<T> {
cx.use_hook(|| UseConst {
value: Rc::new(initial_state_fn()),
})
}
#[derive(Clone)]
pub struct UseConst<T> {
value: Rc<T>,
}
impl<T> PartialEq for UseConst<T> {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.value, &other.value)
}
}
impl<T: core::fmt::Display> core::fmt::Display for UseConst<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.value.fmt(f)
}
}
impl<T> UseConst<T> {
pub fn get_rc(&self) -> &Rc<T> {
&self.value
}
}
impl<T> std::ops::Deref for UseConst<T> {
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
}
}

View file

@ -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<T: 'static> {
update: Arc<dyn Fn()>,
needs_regen: Cell<bool>,
needs_regen: Rc<Cell<bool>>,
task: Rc<Cell<Option<TaskId>>>,
dependencies: Vec<Box<dyn Any>>,
state: UseState<Option<T>>,
}

View file

@ -269,6 +269,9 @@ trait_methods! {
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/azimuth>
azimuth: "azimuth", "style";
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter>
backdrop_filter: "backdrop-filter", "style";
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/backface-visibility>
backface_visibility: "backface-visibility", "style";

View file

@ -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"] }

View file

@ -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<R: Driver>(
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<R: Driver>(
} else {
let rdom = rdom.read().unwrap();
resize(
tui::layout::Rect {
ratatui::layout::Rect {
x: 0,
y: 0,
width: 1000,

View file

@ -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<CrosstermBackend<Stdout>>,
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,

View file

@ -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<RinkStyle> 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,

View file

@ -187,8 +187,8 @@ pub enum BorderStyle {
}
impl BorderStyle {
pub fn symbol_set(&self) -> Option<tui::symbols::line::Set> {
use tui::symbols::line::*;
pub fn symbol_set(&self) -> Option<ratatui::symbols::line::Set> {
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!(),

View file

@ -1,4 +1,4 @@
use tui::{
use ratatui::{
buffer::Buffer,
layout::Rect,
style::{Color, Modifier},

View file

@ -1,8 +1,14 @@
[package]
name = "dioxus-signals"
authors = ["Jonathan Kelley"]
version = "0.0.0"
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

View file

@ -100,7 +100,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() {
{
self.effect_stack.effects.write().push(*self);
}

View file

@ -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<T: 'static> $ty<Vec<T>> {
/// Read a value from the inner vector.
pub fn get(&self, index: usize) -> Option<Ref<'_, T>> {
Ref::filter_map(self.read(), |v| v.get(index)).ok()
pub fn get(&self, index: usize) -> Option<GenerationalRef<T>> {
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<'_, T>> {
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<T>> {
GenerationalRef::filter_map(self.read(), |v| v.as_ref())
}
}
};
@ -182,19 +182,19 @@ 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 +238,15 @@ impl<T: Clone + 'static> IntoIterator for CopyValue<Vec<T>> {
impl<T: 'static> CopyValue<Vec<T>> {
/// Write to an element in the inner vector.
pub fn get_mut(&self, index: usize) -> Option<RefMut<'_, T>> {
RefMut::filter_map(self.write(), |v| v.get_mut(index)).ok()
pub fn get_mut(&self, index: usize) -> Option<GenerationalRefMut<T>> {
GenerationalRefMut::filter_map(self.write(), |v| v.get_mut(index))
}
}
impl<T: 'static> CopyValue<Option<T>> {
/// Deref the inner value mutably.
pub fn as_mut(&self) -> Option<RefMut<'_, T>> {
RefMut::filter_map(self.write(), |v| v.as_mut()).ok()
pub fn as_mut(&self) -> Option<GenerationalRefMut<T>> {
GenerationalRefMut::filter_map(self.write(), |v| v.as_mut())
}
}
@ -281,14 +281,14 @@ impl<T: Clone + 'static> IntoIterator for Signal<Vec<T>> {
impl<T: 'static> Signal<Vec<T>> {
/// Returns a reference to an element or `None` if out of bounds.
pub fn get_mut(&self, index: usize) -> Option<Write<'_, T, Vec<T>>> {
pub fn get_mut(&self, index: usize) -> Option<Write<T, Vec<T>>> {
Write::filter_map(self.write(), |v| v.get_mut(index))
}
}
impl<T: 'static> Signal<Option<T>> {
/// Returns a reference to an element or `None` if out of bounds.
pub fn as_mut(&self) -> Option<Write<'_, T, Option<T>>> {
pub fn as_mut(&self) -> Option<Write<T, Option<T>>> {
Write::filter_map(self.write(), |v| v.as_mut())
}
}

View file

@ -1,5 +1,3 @@
use std::cell::{Ref, RefMut};
use std::mem::MaybeUninit;
use std::ops::Deref;
use std::rc::Rc;
@ -7,7 +5,9 @@ use std::rc::Rc;
use dioxus_core::prelude::*;
use dioxus_core::ScopeId;
use generational_box::{GenerationalBox, Owner, Store};
use generational_box::{
BorrowError, BorrowMutError, GenerationalBox, GenerationalRef, GenerationalRefMut, Owner, Store,
};
use crate::Effect;
@ -83,6 +83,7 @@ impl<T: 'static> CopyValue<T> {
/// 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();
@ -92,6 +93,22 @@ impl<T: 'static> CopyValue<T> {
}
}
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,
#[cfg(debug_assertions)]
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);
@ -117,22 +134,26 @@ impl<T: 'static> CopyValue<T> {
}
/// Try to read the value. If the value has been dropped, this will return None.
pub fn try_read(&self) -> Option<Ref<'_, T>> {
#[track_caller]
pub fn try_read(&self) -> Result<GenerationalRef<T>, BorrowError> {
self.value.try_read()
}
/// Read the value. If the value has been dropped, this will panic.
pub fn read(&self) -> Ref<'static, 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<RefMut<'static, T>> {
#[track_caller]
pub fn try_write(&self) -> Result<GenerationalRefMut<T>, BorrowMutError> {
self.value.try_write()
}
/// Write the value. If the value has been dropped, this will panic.
pub fn write(&self) -> RefMut<'static, T> {
#[track_caller]
pub fn write(&self) -> GenerationalRefMut<T> {
self.value.write()
}
@ -168,7 +189,7 @@ impl<T: 'static> PartialEq for CopyValue<T> {
}
impl<T> Deref for CopyValue<T> {
type Target = dyn Fn() -> Ref<'static, T>;
type Target = dyn Fn() -> GenerationalRef<T>;
fn deref(&self) -> &Self::Target {
// https://github.com/dtolnay/case-studies/tree/master/callable-types

View file

@ -1,5 +1,5 @@
use std::{
cell::{Ref, RefCell, RefMut},
cell::RefCell,
mem::MaybeUninit,
ops::{Deref, DerefMut},
rc::Rc,
@ -10,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::{get_effect_stack, CopyValue, Effect, EffectStack};
@ -44,9 +45,19 @@ use crate::{get_effect_stack, CopyValue, Effect, EffectStack};
/// }
/// }
/// ```
#[track_caller]
#[must_use]
pub fn use_signal<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal<T> {
*cx.use_hook(|| Signal::new(f()))
#[cfg(debug_assertions)]
let caller = std::panic::Location::caller();
*cx.use_hook(|| {
Signal::new_with_caller(
f(),
#[cfg(debug_assertions)]
caller,
)
})
}
#[derive(Clone)]
@ -138,6 +149,7 @@ impl<'de, T: serde::Deserialize<'de> + 'static> serde::Deserialize<'de> for Sign
impl<T: 'static> Signal<T> {
/// 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 {
@ -150,6 +162,26 @@ impl<T: 'static> Signal<T> {
}
}
/// 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 std::panic::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,
effect_stack: get_effect_stack(),
},
#[cfg(debug_assertions)]
caller,
),
}
}
/// Create a new signal with a custom owner scope. The signal will be dropped when the owner scope is dropped instead of the current scope.
pub fn new_in_scope(value: T, owner: ScopeId) -> Self {
Self {
@ -173,7 +205,8 @@ impl<T: 'static> Signal<T> {
/// 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<T> {
#[track_caller]
pub fn read(&self) -> GenerationalRef<T> {
let inner = self.inner.read();
if let Some(effect) = inner.effect_stack.current() {
let mut effect_subscribers = inner.effect_subscribers.borrow_mut();
@ -197,14 +230,15 @@ impl<T: 'static> Signal<T> {
}
}
}
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.
pub fn write(&self) -> Write<'_, T> {
#[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 },
@ -240,12 +274,14 @@ impl<T: 'static> Signal<T> {
}
/// 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<O>(&self, f: impl FnOnce(&T) -> O) -> O {
let write = self.read();
f(&*write)
@ -253,6 +289,7 @@ impl<T: 'static> Signal<T> {
/// 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<O>(&self, f: impl FnOnce(&mut T) -> O) -> O {
let mut write = self.write();
f(&mut *write)
@ -262,6 +299,7 @@ impl<T: 'static> Signal<T> {
impl<T: Clone + 'static> Signal<T> {
/// 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()
}
@ -281,7 +319,7 @@ impl<T: 'static> PartialEq for Signal<T> {
}
impl<T> Deref for Signal<T> {
type Target = dyn Fn() -> Ref<'static, T>;
type Target = dyn Fn() -> GenerationalRef<T>;
fn deref(&self) -> &Self::Target {
// https://github.com/dtolnay/case-studies/tree/master/callable-types
@ -324,17 +362,17 @@ impl<T: 'static> Drop for SignalSubscriberDrop<T> {
}
/// A mutable reference to a signal's value.
pub struct Write<'a, T: 'static, I: 'static = T> {
write: RefMut<'a, T>,
pub struct Write<T: 'static, I: 'static = T> {
write: GenerationalRefMut<T>,
signal: SignalSubscriberDrop<I>,
}
impl<'a, T: 'static, I: 'static> Write<'a, T, I> {
impl<T: 'static, I: 'static> Write<T, I> {
/// Map the mutable reference to the signal's value to a new type.
pub fn map<O>(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write<'a, O, I> {
pub fn map<O>(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write<O, I> {
let Self { write, signal } = myself;
Write {
write: RefMut::map(write, f),
write: GenerationalRefMut::map(write, f),
signal,
}
}
@ -343,14 +381,14 @@ impl<'a, T: 'static, I: 'static> Write<'a, T, I> {
pub fn filter_map<O>(
myself: Self,
f: impl FnOnce(&mut T) -> Option<&mut O>,
) -> Option<Write<'a, O, I>> {
) -> Option<Write<O, I>> {
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 })
}
}
impl<'a, T: 'static, I: 'static> Deref for Write<'a, T, I> {
impl<T: 'static, I: 'static> Deref for Write<T, I> {
type Target = T;
fn deref(&self) -> &Self::Target {
@ -358,7 +396,7 @@ impl<'a, T: 'static, I: 'static> Deref for Write<'a, T, I> {
}
}
impl<T, I> DerefMut for Write<'_, T, I> {
impl<T, I> DerefMut for Write<T, I> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.write
}
@ -381,11 +419,13 @@ impl<T: 'static> ReadOnlySignal<T> {
}
/// Get the current value of the signal. This will subscribe the current scope to the signal.
pub fn read(&self) -> Ref<T> {
#[track_caller]
pub fn read(&self) -> GenerationalRef<T> {
self.inner.read()
}
/// Run a closure with a reference to the signal's value.
#[track_caller]
pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
self.inner.with(f)
}
@ -405,7 +445,7 @@ impl<T: 'static> PartialEq for ReadOnlySignal<T> {
}
impl<T> Deref for ReadOnlySignal<T> {
type Target = dyn Fn() -> Ref<'static, T>;
type Target = dyn Fn() -> GenerationalRef<T>;
fn deref(&self) -> &Self::Target {
// https://github.com/dtolnay/case-studies/tree/master/callable-types

View file

@ -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 }

42
packages/ssr/src/eval.rs Normal file
View file

@ -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<dyn EvalProvider> = 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<Rc<dyn Evaluator>, EvalError> {
Ok(Rc::new(SSREvaluator) as Rc<dyn Evaluator + 'static>)
}
}
/// 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<serde_json::Value, EvalError> {
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<serde_json::Value, EvalError> {
std::future::pending::<()>().await;
unreachable!()
}
}

View file

@ -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)?;

View file

@ -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)