Merge branch 'master' into cli-config-library
This commit is contained in:
42 files changed
+875
-142
No files matched your search
@@ -33,7 +33,7 @@ jobs:
|
|||||||
# cd fermi && mdbook build -d ../nightly/fermi && cd ..
|
# cd fermi && mdbook build -d ../nightly/fermi && cd ..
|
||||||
|
|
||||||
- name: Deploy 🚀
|
- name: Deploy 🚀
|
||||||
uses: JamesIves/github-pages-deploy-action@v4.4.3
|
uses: JamesIves/github-pages-deploy-action@v4.5.0
|
||||||
with:
|
with:
|
||||||
branch: gh-pages # The branch the action should deploy to.
|
branch: gh-pages # The branch the action should deploy to.
|
||||||
folder: docs/nightly # The folder the action should deploy.
|
folder: docs/nightly # The folder the action should deploy.
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ jobs:
|
|||||||
# cd fermi && mdbook build -d ../nightly/fermi && cd ..
|
# cd fermi && mdbook build -d ../nightly/fermi && cd ..
|
||||||
|
|
||||||
- name: Deploy 🚀
|
- name: Deploy 🚀
|
||||||
uses: JamesIves/github-pages-deploy-action@v4.4.3
|
uses: JamesIves/github-pages-deploy-action@v4.5.0
|
||||||
with:
|
with:
|
||||||
branch: gh-pages # The branch the action should deploy to.
|
branch: gh-pages # The branch the action should deploy to.
|
||||||
folder: docs/nightly # The folder the action should deploy.
|
folder: docs/nightly # The folder the action should deploy.
|
||||||
|
|||||||
@@ -86,8 +86,7 @@ jobs:
|
|||||||
|
|
||||||
# working-directory: tokio
|
# working-directory: tokio
|
||||||
env:
|
env:
|
||||||
# todo: disable memory leaks ignore
|
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields
|
||||||
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields -Zmiri-ignore-leaks
|
|
||||||
PROPTEST_CASES: 10
|
PROPTEST_CASES: 10
|
||||||
|
|
||||||
# Cache the global cargo directory, but NOT the local `target` directory which
|
# Cache the global cargo directory, but NOT the local `target` directory which
|
||||||
|
|||||||
+3
-3
@@ -51,7 +51,7 @@ members = [
|
|||||||
exclude = ["examples/mobile_demo"]
|
exclude = ["examples/mobile_demo"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.4.2"
|
version = "0.4.3"
|
||||||
|
|
||||||
# dependencies that are shared across packages
|
# dependencies that are shared across packages
|
||||||
[workspace.dependencies]
|
[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" }
|
dioxus-native-core-macro = { path = "packages/native-core-macro", version = "0.4.0" }
|
||||||
rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.4.0" }
|
rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.4.0" }
|
||||||
dioxus-signals = { path = "packages/signals" }
|
dioxus-signals = { path = "packages/signals" }
|
||||||
generational-box = { path = "packages/generational-box" }
|
|
||||||
dioxus-cli-config = { path = "packages/cli-config", version = "0.4.1" }
|
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-hot-reload = { path = "packages/hot-reload", version = "0.4.0" }
|
||||||
dioxus-fullstack = { path = "packages/fullstack", version = "0.4.1" }
|
dioxus-fullstack = { path = "packages/fullstack", version = "0.4.1" }
|
||||||
dioxus_server_macro = { path = "packages/server-macro", 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
|
# It is not meant to be published, but is used so "cargo run --example XYZ" works properly
|
||||||
[package]
|
[package]
|
||||||
name = "dioxus-examples"
|
name = "dioxus-examples"
|
||||||
version = "0.0.0"
|
version = "0.4.3"
|
||||||
authors = ["Jonathan Kelley"]
|
authors = ["Jonathan Kelley"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Top level crate for the Dioxus repository"
|
description = "Top level crate for the Dioxus repository"
|
||||||
|
|||||||
+70
-4
@@ -24,12 +24,64 @@ script = [
|
|||||||
]
|
]
|
||||||
script_runner = "@duckscript"
|
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]
|
[tasks.tests]
|
||||||
category = "Testing"
|
category = "Testing"
|
||||||
dependencies = ["tests-setup"]
|
dependencies = ["tests-setup"]
|
||||||
description = "Run all tests"
|
description = "Run all tests"
|
||||||
env = {CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*"]}
|
env = { CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*"] }
|
||||||
run_task = {name = ["test-flow", "test-with-browser"], fork = true}
|
run_task = { name = ["test-flow", "test-with-browser"], fork = true }
|
||||||
|
|
||||||
[tasks.build]
|
[tasks.build]
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
@@ -42,10 +94,24 @@ private = true
|
|||||||
[tasks.test]
|
[tasks.test]
|
||||||
dependencies = ["build"]
|
dependencies = ["build"]
|
||||||
command = "cargo"
|
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
|
private = true
|
||||||
|
|
||||||
[tasks.test-with-browser]
|
[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
|
private = true
|
||||||
workspace = true
|
workspace = true
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
name = "openid_auth_demo"
|
name = "openid_auth_demo"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
name = "query_segments_demo"
|
name = "query_segments_demo"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dioxus-cli"
|
name = "dioxus-cli"
|
||||||
version = "0.4.1"
|
version = "0.4.3"
|
||||||
authors = ["Jonathan Kelley"]
|
authors = ["Jonathan Kelley"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "CLI tool for developing, testing, and publishing Dioxus apps"
|
description = "CLI tool for developing, testing, and publishing Dioxus apps"
|
||||||
|
|||||||
@@ -57,8 +57,7 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result<BuildResult> {
|
|||||||
.arg("build")
|
.arg("build")
|
||||||
.arg("--target")
|
.arg("--target")
|
||||||
.arg("wasm32-unknown-unknown")
|
.arg("wasm32-unknown-unknown")
|
||||||
.arg("--message-format=json")
|
.arg("--message-format=json");
|
||||||
.arg("--quiet");
|
|
||||||
|
|
||||||
let cmd = if config.release {
|
let cmd = if config.release {
|
||||||
cmd.arg("--release")
|
cmd.arg("--release")
|
||||||
@@ -68,7 +67,7 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result<BuildResult> {
|
|||||||
let cmd = if config.verbose {
|
let cmd = if config.verbose {
|
||||||
cmd.arg("--verbose")
|
cmd.arg("--verbose")
|
||||||
} else {
|
} else {
|
||||||
cmd
|
cmd.arg("--quiet")
|
||||||
};
|
};
|
||||||
|
|
||||||
let cmd = if config.custom_profile.is_some() {
|
let cmd = if config.custom_profile.is_some() {
|
||||||
|
|||||||
@@ -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 file_map = hot_reload_state.file_map.clone();
|
||||||
let channels = channels.clone();
|
let channels = channels.clone();
|
||||||
let aborted = aborted.clone();
|
let aborted = aborted.clone();
|
||||||
let _ = local_socket_stream.set_nonblocking(true);
|
|
||||||
move || {
|
move || {
|
||||||
loop {
|
loop {
|
||||||
|
//accept() will block the thread when local_socket_stream is in blocking mode (default)
|
||||||
match local_socket_stream.accept() {
|
match local_socket_stream.accept() {
|
||||||
Ok(mut connection) => {
|
Ok(mut connection) => {
|
||||||
// send any templates than have changed before the socket connected
|
// send any templates than have changed before the socket connected
|
||||||
|
|||||||
@@ -47,6 +47,16 @@ async fn setup_file_watcher<F: Fn() -> Result<BuildResult> + Send + 'static>(
|
|||||||
break;
|
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) {
|
match rsx_file_map.update_rsx(path, &config.crate_dir) {
|
||||||
Ok(UpdateResult::UpdatedRsx(msgs)) => {
|
Ok(UpdateResult::UpdatedRsx(msgs)) => {
|
||||||
messages.extend(msgs);
|
messages.extend(msgs);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::nodes::RenderReturn;
|
use crate::nodes::RenderReturn;
|
||||||
use crate::{Attribute, AttributeValue};
|
use crate::{Attribute, AttributeValue, VComponent};
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::cell::{Cell, UnsafeCell};
|
use std::cell::{Cell, UnsafeCell};
|
||||||
@@ -7,7 +7,10 @@ use std::cell::{Cell, UnsafeCell};
|
|||||||
pub(crate) struct BumpFrame {
|
pub(crate) struct BumpFrame {
|
||||||
pub bump: UnsafeCell<Bump>,
|
pub bump: UnsafeCell<Bump>,
|
||||||
pub node: Cell<*const RenderReturn<'static>>,
|
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) attributes_to_drop_before_reset: RefCell<Vec<*const Attribute<'static>>>,
|
||||||
|
pub(crate) props_to_drop_before_reset: RefCell<Vec<*const VComponent<'static>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BumpFrame {
|
impl BumpFrame {
|
||||||
@@ -17,6 +20,7 @@ impl BumpFrame {
|
|||||||
bump: UnsafeCell::new(bump),
|
bump: UnsafeCell::new(bump),
|
||||||
node: Cell::new(std::ptr::null()),
|
node: Cell::new(std::ptr::null()),
|
||||||
attributes_to_drop_before_reset: Default::default(),
|
attributes_to_drop_before_reset: Default::default(),
|
||||||
|
props_to_drop_before_reset: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +45,10 @@ impl BumpFrame {
|
|||||||
.push(attribute);
|
.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) {
|
pub(crate) unsafe fn reset(&self) {
|
||||||
let mut attributes = self.attributes_to_drop_before_reset.borrow_mut();
|
let mut attributes = self.attributes_to_drop_before_reset.borrow_mut();
|
||||||
attributes.drain(..).for_each(|attribute| {
|
attributes.drain(..).for_each(|attribute| {
|
||||||
@@ -49,9 +57,20 @@ impl BumpFrame {
|
|||||||
_ = l.take();
|
_ = 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 {
|
unsafe {
|
||||||
let bump = &mut *self.bump.get();
|
let bump = &mut *self.bump.get();
|
||||||
bump.reset();
|
bump.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for BumpFrame {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { self.reset() }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.
|
/// 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.
|
/// This makes it possible to pass `move |evt| {}` style closures into components as property fields.
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ pub enum Mutation<'a> {
|
|||||||
id: ElementId,
|
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
|
/// Dioxus currently requires the use of placeholders to maintain a re-entrance point for things like list diffing
|
||||||
CreatePlaceholder {
|
CreatePlaceholder {
|
||||||
|
|||||||
@@ -707,7 +707,7 @@ impl<'a, 'b> IntoDynNode<'b> for &'a str {
|
|||||||
impl IntoDynNode<'_> for String {
|
impl IntoDynNode<'_> for String {
|
||||||
fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
|
fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
|
||||||
DynamicNode::Text(VText {
|
DynamicNode::Text(VText {
|
||||||
value: cx.bump().alloc(self),
|
value: cx.bump().alloc_str(&self),
|
||||||
id: Default::default(),
|
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 {
|
impl<'a> IntoAttributeValue<'a> for f64 {
|
||||||
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||||
AttributeValue::Float(self)
|
AttributeValue::Float(self)
|
||||||
|
|||||||
@@ -367,12 +367,17 @@ impl<'src> ScopeState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut props = self.borrowed_props.borrow_mut();
|
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 {
|
for node in element.dynamic_nodes {
|
||||||
if let DynamicNode::Component(comp) = node {
|
if let DynamicNode::Component(comp) = node {
|
||||||
|
let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) };
|
||||||
if !comp.static_props {
|
if !comp.static_props {
|
||||||
let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) };
|
|
||||||
props.push(unbounded);
|
props.push(unbounded);
|
||||||
}
|
}
|
||||||
|
drop_props.push(unbounded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
/// This function will start a multithreaded Tokio runtime as well the WebView event loop.
|
||||||
///
|
///
|
||||||
/// ```rust, ignore
|
/// ```rust, no_run
|
||||||
/// use dioxus::prelude::*;
|
/// use dioxus::prelude::*;
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// fn main() {
|
||||||
@@ -77,11 +77,12 @@ pub fn launch(root: Component) {
|
|||||||
///
|
///
|
||||||
/// You can configure the WebView window with a configuration closure
|
/// You can configure the WebView window with a configuration closure
|
||||||
///
|
///
|
||||||
/// ```rust, ignore
|
/// ```rust, no_run
|
||||||
/// use dioxus::prelude::*;
|
/// use dioxus::prelude::*;
|
||||||
|
/// use dioxus_desktop::*;
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// 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 {
|
/// 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
|
/// You can configure the WebView window with a configuration closure
|
||||||
///
|
///
|
||||||
/// ```rust, ignore
|
/// ```rust, no_run
|
||||||
/// use dioxus::prelude::*;
|
/// use dioxus::prelude::*;
|
||||||
|
/// use dioxus_desktop::Config;
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// fn main() {
|
||||||
/// dioxus_desktop::launch_with_props(app, AppProps { name: "asd" }, Config::default());
|
/// dioxus_desktop::launch_with_props(app, AppProps { name: "asd" }, Config::default());
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ fn app(cx: Scope<AppProps>) -> Element {
|
|||||||
|
|
||||||
let mut count = use_state(cx, || 0);
|
let mut count = use_state(cx, || 0);
|
||||||
let text = use_state(cx, || "...".to_string());
|
let text = use_state(cx, || "...".to_string());
|
||||||
|
let eval = use_eval(cx);
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
|
|||||||
@@ -369,15 +369,65 @@ fn apply_request_parts_to_response<B>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SSR renderer handler for Axum
|
/// SSR renderer handler for Axum with added context injection.
|
||||||
pub async fn render_handler<P: Clone + serde::Serialize + Send + Sync + 'static>(
|
///
|
||||||
State((cfg, ssr_state)): State<(ServeConfig<P>, SSRState)>,
|
/// # 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>,
|
request: Request<Body>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let (parts, _) = request.into_parts();
|
let (parts, _) = request.into_parts();
|
||||||
let url = parts.uri.path_and_query().unwrap().to_string();
|
let url = parts.uri.path_and_query().unwrap().to_string();
|
||||||
let parts: Arc<RwLock<http::request::Parts>> = Arc::new(RwLock::new(parts.into()));
|
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 {
|
match ssr_state.render(url, &cfg, &server_context).await {
|
||||||
Ok(rendered) => {
|
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> {
|
fn report_err<E: std::fmt::Display>(e: E) -> Response<BoxBody> {
|
||||||
Response::builder()
|
Response::builder()
|
||||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ impl SsrRendererPool {
|
|||||||
.expect("couldn't spawn runtime")
|
.expect("couldn't spawn runtime")
|
||||||
.block_on(async move {
|
.block_on(async move {
|
||||||
let mut vdom = VirtualDom::new_with_props(component, props);
|
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() };
|
let mut to = WriteBuffer { buffer: Vec::new() };
|
||||||
// before polling the future, we need to set the context
|
// before polling the future, we need to set the context
|
||||||
let prev_context =
|
let prev_context =
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "generational-box"
|
name = "generational-box"
|
||||||
authors = ["Evan Almloff"]
|
authors = ["Evan Almloff"]
|
||||||
version = "0.0.0"
|
version = "0.4.3"
|
||||||
edition = "2018"
|
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
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@@ -15,3 +18,5 @@ rand = "0.8.5"
|
|||||||
[features]
|
[features]
|
||||||
default = ["check_generation"]
|
default = ["check_generation"]
|
||||||
check_generation = []
|
check_generation = []
|
||||||
|
debug_borrows = []
|
||||||
|
debug_ownership = []
|
||||||
@@ -11,6 +11,8 @@ Three main types manage state in Generational Box:
|
|||||||
Example:
|
Example:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use generational_box::Store;
|
||||||
|
|
||||||
// Create a store for this thread
|
// Create a store for this thread
|
||||||
let store = Store::default();
|
let store = Store::default();
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,12 @@
|
|||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
any::Any,
|
||||||
cell::{Cell, Ref, RefCell, RefMut},
|
cell::{Cell, Ref, RefCell, RefMut},
|
||||||
fmt::Debug,
|
error::Error,
|
||||||
|
fmt::{Debug, Display},
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -29,12 +32,12 @@ fn reused() {
|
|||||||
let first_ptr;
|
let first_ptr;
|
||||||
{
|
{
|
||||||
let owner = store.owner();
|
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);
|
drop(owner);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let owner = store.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);
|
assert_eq!(first_ptr, second_ptr);
|
||||||
drop(owner);
|
drop(owner);
|
||||||
}
|
}
|
||||||
@@ -53,7 +56,10 @@ fn leaking_is_ok() {
|
|||||||
// don't drop the owner
|
// don't drop the owner
|
||||||
std::mem::forget(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]
|
#[test]
|
||||||
@@ -68,7 +74,7 @@ fn drops() {
|
|||||||
key = owner.insert(data);
|
key = owner.insert(data);
|
||||||
// drop the owner
|
// drop the owner
|
||||||
}
|
}
|
||||||
assert!(key.try_read().is_none());
|
assert!(key.try_read().is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -129,7 +135,7 @@ fn fuzz() {
|
|||||||
println!("{:?}", path);
|
println!("{:?}", path);
|
||||||
for key in valid_keys.iter() {
|
for key in valid_keys.iter() {
|
||||||
let value = key.read();
|
let value = key.read();
|
||||||
println!("{:?}", value);
|
println!("{:?}", &*value);
|
||||||
assert!(value.starts_with("hello world"));
|
assert!(value.starts_with("hello world"));
|
||||||
}
|
}
|
||||||
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
||||||
@@ -153,6 +159,8 @@ pub struct GenerationalBox<T> {
|
|||||||
raw: MemoryLocation,
|
raw: MemoryLocation,
|
||||||
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
||||||
generation: u32,
|
generation: u32,
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
|
||||||
|
created_at: &'static std::panic::Location<'static>,
|
||||||
_marker: PhantomData<T>,
|
_marker: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +169,7 @@ impl<T: 'static> Debug for GenerationalBox<T> {
|
|||||||
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
||||||
f.write_fmt(format_args!(
|
f.write_fmt(format_args!(
|
||||||
"{:?}@{:?}",
|
"{:?}@{:?}",
|
||||||
self.raw.data.as_ptr(),
|
self.raw.0.data.as_ptr(),
|
||||||
self.generation
|
self.generation
|
||||||
))?;
|
))?;
|
||||||
#[cfg(not(any(debug_assertions, feature = "check_generation")))]
|
#[cfg(not(any(debug_assertions, feature = "check_generation")))]
|
||||||
@@ -175,7 +183,7 @@ impl<T: 'static> GenerationalBox<T> {
|
|||||||
fn validate(&self) -> bool {
|
fn validate(&self) -> bool {
|
||||||
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
#[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")))]
|
#[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.
|
/// Try to read the value. Returns None if the value is no longer valid.
|
||||||
pub fn try_read(&self) -> Option<Ref<'static, T>> {
|
#[track_caller]
|
||||||
self.validate()
|
pub fn try_read(&self) -> Result<GenerationalRef<T>, BorrowError> {
|
||||||
.then(|| {
|
if !self.validate() {
|
||||||
Ref::filter_map(self.raw.data.borrow(), |any| {
|
return Err(BorrowError::Dropped(ValueDroppedError {
|
||||||
any.as_ref()?.downcast_ref::<T>()
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
})
|
created_at: self.created_at,
|
||||||
.ok()
|
}));
|
||||||
})
|
}
|
||||||
.flatten()
|
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.
|
/// 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()
|
self.try_read().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to write the value. Returns None if the value is no longer valid.
|
/// Try to write the value. Returns None if the value is no longer valid.
|
||||||
pub fn try_write(&self) -> Option<RefMut<'static, T>> {
|
#[track_caller]
|
||||||
self.validate()
|
pub fn try_write(&self) -> Result<GenerationalRefMut<T>, BorrowMutError> {
|
||||||
.then(|| {
|
if !self.validate() {
|
||||||
RefMut::filter_map(self.raw.data.borrow_mut(), |any| {
|
return Err(BorrowMutError::Dropped(ValueDroppedError {
|
||||||
any.as_mut()?.downcast_mut::<T>()
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
})
|
created_at: self.created_at,
|
||||||
.ok()
|
}));
|
||||||
})
|
}
|
||||||
.flatten()
|
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.
|
/// 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()
|
self.try_write().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the value. Panics if the value is no longer valid.
|
/// Set the value. Panics if the value is no longer valid.
|
||||||
pub fn set(&self, value: T) {
|
pub fn set(&self, value: T) {
|
||||||
self.validate().then(|| {
|
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 {
|
pub fn ptr_eq(&self, other: &Self) -> bool {
|
||||||
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
#[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")))]
|
#[cfg(not(any(debug_assertions, feature = "check_generation")))]
|
||||||
{
|
{
|
||||||
@@ -246,26 +263,37 @@ impl<T> Clone for GenerationalBox<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
struct MemoryLocation {
|
struct MemoryLocation(&'static MemoryLocationInner);
|
||||||
data: &'static RefCell<Option<Box<dyn std::any::Any>>>,
|
|
||||||
|
struct MemoryLocationInner {
|
||||||
|
data: RefCell<Option<Box<dyn std::any::Any>>>,
|
||||||
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
#[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 {
|
impl MemoryLocation {
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
fn drop(&self) {
|
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"))]
|
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
||||||
if old.is_some() {
|
if old.is_some() {
|
||||||
drop(old);
|
drop(old);
|
||||||
let new_generation = self.generation.get() + 1;
|
let new_generation = self.0.generation.get() + 1;
|
||||||
self.generation.set(new_generation);
|
self.0.generation.set(new_generation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace<T: 'static>(&mut self, value: T) -> GenerationalBox<T> {
|
fn replace_with_caller<T: 'static>(
|
||||||
let mut inner_mut = self.data.borrow_mut();
|
&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 raw = Box::new(value);
|
||||||
let old = inner_mut.replace(raw);
|
let old = inner_mut.replace(raw);
|
||||||
@@ -273,10 +301,315 @@ impl MemoryLocation {
|
|||||||
GenerationalBox {
|
GenerationalBox {
|
||||||
raw: *self,
|
raw: *self,
|
||||||
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
#[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,
|
_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.
|
/// 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() {
|
if let Some(location) = self.recycled.borrow_mut().pop() {
|
||||||
location
|
location
|
||||||
} else {
|
} else {
|
||||||
let data: &'static RefCell<_> = self.bump.alloc(RefCell::new(None));
|
let data: &'static MemoryLocationInner = self.bump.alloc(MemoryLocationInner {
|
||||||
MemoryLocation {
|
data: RefCell::new(None),
|
||||||
data,
|
|
||||||
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
#[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 {
|
impl Owner {
|
||||||
/// Insert a value into the store. The value will be dropped when the owner is dropped.
|
/// 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> {
|
pub fn insert<T: 'static>(&self, value: T) -> GenerationalBox<T> {
|
||||||
let mut location = self.store.claim();
|
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);
|
self.owned.borrow_mut().push(location);
|
||||||
key
|
key
|
||||||
}
|
}
|
||||||
@@ -344,7 +703,9 @@ impl Owner {
|
|||||||
GenerationalBox {
|
GenerationalBox {
|
||||||
raw: location,
|
raw: location,
|
||||||
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
#[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,
|
_marker: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,9 @@ pub mod computed;
|
|||||||
mod use_on_destroy;
|
mod use_on_destroy;
|
||||||
pub use use_on_destroy::*;
|
pub use use_on_destroy::*;
|
||||||
|
|
||||||
|
mod use_const;
|
||||||
|
pub use use_const::*;
|
||||||
|
|
||||||
mod use_context;
|
mod use_context;
|
||||||
pub use use_context::*;
|
pub use use_context::*;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,13 +31,14 @@ where
|
|||||||
|
|
||||||
let state = cx.use_hook(move || UseFuture {
|
let state = cx.use_hook(move || UseFuture {
|
||||||
update: cx.schedule_update(),
|
update: cx.schedule_update(),
|
||||||
needs_regen: Cell::new(true),
|
needs_regen: Rc::new(Cell::new(true)),
|
||||||
state: val.clone(),
|
state: val.clone(),
|
||||||
task: Default::default(),
|
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
|
// kill the old one, if it exists
|
||||||
if let Some(task) = state.task.take() {
|
if let Some(task) = state.task.take() {
|
||||||
cx.remove_future(task);
|
cx.remove_future(task);
|
||||||
@@ -69,11 +70,11 @@ pub enum FutureState<'a, T> {
|
|||||||
Regenerating(&'a T), // the old value
|
Regenerating(&'a T), // the old value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct UseFuture<T: 'static> {
|
pub struct UseFuture<T: 'static> {
|
||||||
update: Arc<dyn Fn()>,
|
update: Arc<dyn Fn()>,
|
||||||
needs_regen: Cell<bool>,
|
needs_regen: Rc<Cell<bool>>,
|
||||||
task: Rc<Cell<Option<TaskId>>>,
|
task: Rc<Cell<Option<TaskId>>>,
|
||||||
dependencies: Vec<Box<dyn Any>>,
|
|
||||||
state: UseState<Option<T>>,
|
state: UseState<Option<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -269,6 +269,9 @@ trait_methods! {
|
|||||||
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/azimuth>
|
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/azimuth>
|
||||||
azimuth: "azimuth", "style";
|
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>
|
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/backface-visibility>
|
||||||
backface_visibility: "backface-visibility", "style";
|
backface_visibility: "backface-visibility", "style";
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ dioxus-html = { workspace = true }
|
|||||||
dioxus-native-core = { workspace = true, features = ["layout-attributes"] }
|
dioxus-native-core = { workspace = true, features = ["layout-attributes"] }
|
||||||
dioxus-native-core-macro = { workspace = true }
|
dioxus-native-core-macro = { workspace = true }
|
||||||
|
|
||||||
tui = "0.17.0"
|
ratatui = "0.24.0"
|
||||||
crossterm = "0.26.1"
|
crossterm = "0.26.1"
|
||||||
anyhow = "1.0.42"
|
anyhow = "1.0.42"
|
||||||
tokio = { workspace = true, features = ["full"] }
|
tokio = { workspace = true, features = ["full"] }
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use futures::{channel::mpsc::UnboundedSender, pin_mut, Future, StreamExt};
|
|||||||
use futures_channel::mpsc::unbounded;
|
use futures_channel::mpsc::unbounded;
|
||||||
use layout::TaffyLayout;
|
use layout::TaffyLayout;
|
||||||
use prevent_default::PreventDefault;
|
use prevent_default::PreventDefault;
|
||||||
|
use ratatui::{backend::CrosstermBackend, Terminal};
|
||||||
use std::{io, time::Duration};
|
use std::{io, time::Duration};
|
||||||
use std::{
|
use std::{
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
@@ -26,7 +27,6 @@ use std::{rc::Rc, sync::RwLock};
|
|||||||
use style_attributes::StyleModifier;
|
use style_attributes::StyleModifier;
|
||||||
pub use taffy::{geometry::Point, prelude::*};
|
pub use taffy::{geometry::Point, prelude::*};
|
||||||
use tokio::select;
|
use tokio::select;
|
||||||
use tui::{backend::CrosstermBackend, Terminal};
|
|
||||||
use widgets::{register_widgets, RinkWidgetResponder, RinkWidgetTraitObject};
|
use widgets::{register_widgets, RinkWidgetResponder, RinkWidgetTraitObject};
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
@@ -180,7 +180,7 @@ pub fn render<R: Driver>(
|
|||||||
|
|
||||||
if !to_rerender.is_empty() || updated {
|
if !to_rerender.is_empty() || updated {
|
||||||
updated = false;
|
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 width = screen_to_layout_space(dims.width);
|
||||||
let height = screen_to_layout_space(dims.height);
|
let height = screen_to_layout_space(dims.height);
|
||||||
let root_node = rdom
|
let root_node = rdom
|
||||||
@@ -222,7 +222,7 @@ pub fn render<R: Driver>(
|
|||||||
} else {
|
} else {
|
||||||
let rdom = rdom.read().unwrap();
|
let rdom = rdom.read().unwrap();
|
||||||
resize(
|
resize(
|
||||||
tui::layout::Rect {
|
ratatui::layout::Rect {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
width: 1000,
|
width: 1000,
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
use dioxus_native_core::{prelude::*, tree::TreeRef};
|
use dioxus_native_core::{prelude::*, tree::TreeRef};
|
||||||
use std::io::Stdout;
|
use ratatui::{layout::Rect, style::Color};
|
||||||
use taffy::{
|
use taffy::{
|
||||||
geometry::Point,
|
geometry::Point,
|
||||||
prelude::{Dimension, Layout, Size},
|
prelude::{Dimension, Layout, Size},
|
||||||
Taffy,
|
Taffy,
|
||||||
};
|
};
|
||||||
use tui::{backend::CrosstermBackend, layout::Rect, style::Color};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
focus::Focused,
|
focus::Focused,
|
||||||
@@ -20,7 +19,7 @@ use crate::{
|
|||||||
const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
|
const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
|
||||||
|
|
||||||
pub(crate) fn render_vnode(
|
pub(crate) fn render_vnode(
|
||||||
frame: &mut tui::Frame<CrosstermBackend<Stdout>>,
|
frame: &mut ratatui::Frame,
|
||||||
layout: &Taffy,
|
layout: &Taffy,
|
||||||
node: NodeRef,
|
node: NodeRef,
|
||||||
cfg: Config,
|
cfg: Config,
|
||||||
@@ -96,7 +95,7 @@ pub(crate) fn render_vnode(
|
|||||||
|
|
||||||
impl RinkWidget for NodeRef<'_> {
|
impl RinkWidget for NodeRef<'_> {
|
||||||
fn render(self, area: Rect, mut buf: RinkBuffer<'_>) {
|
fn render(self, area: Rect, mut buf: RinkBuffer<'_>) {
|
||||||
use tui::symbols::line::*;
|
use ratatui::symbols::line::*;
|
||||||
|
|
||||||
enum Direction {
|
enum Direction {
|
||||||
Left,
|
Left,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::{num::ParseFloatError, str::FromStr};
|
use std::{num::ParseFloatError, str::FromStr};
|
||||||
|
|
||||||
use tui::style::{Color, Modifier, Style};
|
use ratatui::style::{Color, Modifier, Style};
|
||||||
|
|
||||||
use crate::RenderingMode;
|
use crate::RenderingMode;
|
||||||
|
|
||||||
@@ -442,6 +442,7 @@ impl RinkStyle {
|
|||||||
impl From<RinkStyle> for Style {
|
impl From<RinkStyle> for Style {
|
||||||
fn from(val: RinkStyle) -> Self {
|
fn from(val: RinkStyle) -> Self {
|
||||||
Style {
|
Style {
|
||||||
|
underline_color: None,
|
||||||
fg: val.fg.map(|c| c.color),
|
fg: val.fg.map(|c| c.color),
|
||||||
bg: val.bg.map(|c| c.color),
|
bg: val.bg.map(|c| c.color),
|
||||||
add_modifier: val.add_modifier,
|
add_modifier: val.add_modifier,
|
||||||
|
|||||||
@@ -187,8 +187,8 @@ pub enum BorderStyle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BorderStyle {
|
impl BorderStyle {
|
||||||
pub fn symbol_set(&self) -> Option<tui::symbols::line::Set> {
|
pub fn symbol_set(&self) -> Option<ratatui::symbols::line::Set> {
|
||||||
use tui::symbols::line::*;
|
use ratatui::symbols::line::*;
|
||||||
const DASHED: Set = Set {
|
const DASHED: Set = Set {
|
||||||
horizontal: "╌",
|
horizontal: "╌",
|
||||||
vertical: "╎",
|
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) {
|
fn apply_font(name: &str, value: &str, style: &mut StyleModifier) {
|
||||||
use tui::style::Modifier;
|
use ratatui::style::Modifier;
|
||||||
match name {
|
match name {
|
||||||
"font" => (),
|
"font" => (),
|
||||||
"font-family" => (),
|
"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) {
|
fn apply_text(name: &str, value: &str, style: &mut StyleModifier) {
|
||||||
use tui::style::Modifier;
|
use ratatui::style::Modifier;
|
||||||
|
|
||||||
match name {
|
match name {
|
||||||
"text-align" => todo!(),
|
"text-align" => todo!(),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use tui::{
|
use ratatui::{
|
||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
layout::Rect,
|
layout::Rect,
|
||||||
style::{Color, Modifier},
|
style::{Color, Modifier},
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dioxus-signals"
|
name = "dioxus-signals"
|
||||||
authors = ["Jonathan Kelley"]
|
authors = ["Jonathan Kelley"]
|
||||||
version = "0.0.0"
|
version = "0.4.3"
|
||||||
edition = "2018"
|
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
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
/// Run the effect callback immediately. Returns `true` if the effect was run. Returns `false` is the effect is dead.
|
||||||
pub fn try_run(&self) {
|
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);
|
self.effect_stack.effects.write().push(*self);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::rt::CopyValue;
|
use crate::rt::CopyValue;
|
||||||
use crate::signal::{ReadOnlySignal, Signal, Write};
|
use crate::signal::{ReadOnlySignal, Signal, Write};
|
||||||
|
use generational_box::GenerationalRef;
|
||||||
use std::cell::{Ref, RefMut};
|
use generational_box::GenerationalRefMut;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{Debug, Display},
|
fmt::{Debug, Display},
|
||||||
@@ -38,8 +38,8 @@ macro_rules! read_impls {
|
|||||||
|
|
||||||
impl<T: 'static> $ty<Vec<T>> {
|
impl<T: 'static> $ty<Vec<T>> {
|
||||||
/// Read a value from the inner vector.
|
/// Read a value from the inner vector.
|
||||||
pub fn get(&self, index: usize) -> Option<Ref<'_, T>> {
|
pub fn get(&self, index: usize) -> Option<GenerationalRef<T>> {
|
||||||
Ref::filter_map(self.read(), |v| v.get(index)).ok()
|
GenerationalRef::filter_map(self.read(), |v| v.get(index))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,9 +52,9 @@ macro_rules! read_impls {
|
|||||||
self.with(|v| v.clone()).unwrap()
|
self.with(|v| v.clone()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attemps to read the inner value of the Option.
|
/// Attempts to read the inner value of the Option.
|
||||||
pub fn as_ref(&self) -> Option<Ref<'_, T>> {
|
pub fn as_ref(&self) -> Option<GenerationalRef<T>> {
|
||||||
Ref::filter_map(self.read(), |v| v.as_ref()).ok()
|
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.
|
/// 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)
|
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.
|
/// 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();
|
let borrow = self.read();
|
||||||
if borrow.is_none() {
|
if borrow.is_none() {
|
||||||
drop(borrow);
|
drop(borrow);
|
||||||
self.with_mut(|v| *v = Some(default()));
|
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 {
|
} 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>> {
|
impl<T: 'static> CopyValue<Vec<T>> {
|
||||||
/// Write to an element in the inner vector.
|
/// Write to an element in the inner vector.
|
||||||
pub fn get_mut(&self, index: usize) -> Option<RefMut<'_, T>> {
|
pub fn get_mut(&self, index: usize) -> Option<GenerationalRefMut<T>> {
|
||||||
RefMut::filter_map(self.write(), |v| v.get_mut(index)).ok()
|
GenerationalRefMut::filter_map(self.write(), |v| v.get_mut(index))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static> CopyValue<Option<T>> {
|
impl<T: 'static> CopyValue<Option<T>> {
|
||||||
/// Deref the inner value mutably.
|
/// Deref the inner value mutably.
|
||||||
pub fn as_mut(&self) -> Option<RefMut<'_, T>> {
|
pub fn as_mut(&self) -> Option<GenerationalRefMut<T>> {
|
||||||
RefMut::filter_map(self.write(), |v| v.as_mut()).ok()
|
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>> {
|
impl<T: 'static> Signal<Vec<T>> {
|
||||||
/// Returns a reference to an element or `None` if out of bounds.
|
/// 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))
|
Write::filter_map(self.write(), |v| v.get_mut(index))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static> Signal<Option<T>> {
|
impl<T: 'static> Signal<Option<T>> {
|
||||||
/// Returns a reference to an element or `None` if out of bounds.
|
/// 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())
|
Write::filter_map(self.write(), |v| v.as_mut())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
use std::cell::{Ref, RefMut};
|
|
||||||
|
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
@@ -7,7 +5,9 @@ use std::rc::Rc;
|
|||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
use dioxus_core::ScopeId;
|
use dioxus_core::ScopeId;
|
||||||
|
|
||||||
use generational_box::{GenerationalBox, Owner, Store};
|
use generational_box::{
|
||||||
|
BorrowError, BorrowMutError, GenerationalBox, GenerationalRef, GenerationalRefMut, Owner, Store,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::Effect;
|
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.
|
/// 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.
|
/// Once the component this value is created in is dropped, the value will be dropped.
|
||||||
|
#[track_caller]
|
||||||
pub fn new(value: T) -> Self {
|
pub fn new(value: T) -> Self {
|
||||||
let owner = current_owner();
|
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.
|
/// 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 {
|
pub fn new_in_scope(value: T, scope: ScopeId) -> Self {
|
||||||
let owner = owner_in_scope(scope);
|
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.
|
/// 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()
|
self.value.try_read()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the value. If the value has been dropped, this will panic.
|
/// 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()
|
self.value.read()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to write the value. If the value has been dropped, this will return None.
|
/// 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()
|
self.value.try_write()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the value. If the value has been dropped, this will panic.
|
/// 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()
|
self.value.write()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +189,7 @@ impl<T: 'static> PartialEq for CopyValue<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Deref 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 {
|
fn deref(&self) -> &Self::Target {
|
||||||
// https://github.com/dtolnay/case-studies/tree/master/callable-types
|
// https://github.com/dtolnay/case-studies/tree/master/callable-types
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
cell::{Ref, RefCell, RefMut},
|
cell::RefCell,
|
||||||
mem::MaybeUninit,
|
mem::MaybeUninit,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
@@ -10,6 +10,7 @@ use dioxus_core::{
|
|||||||
prelude::{current_scope_id, has_context, provide_context, schedule_update_any},
|
prelude::{current_scope_id, has_context, provide_context, schedule_update_any},
|
||||||
ScopeId, ScopeState,
|
ScopeId, ScopeState,
|
||||||
};
|
};
|
||||||
|
use generational_box::{GenerationalRef, GenerationalRefMut};
|
||||||
|
|
||||||
use crate::{get_effect_stack, CopyValue, Effect, EffectStack};
|
use crate::{get_effect_stack, CopyValue, Effect, EffectStack};
|
||||||
|
|
||||||
@@ -44,9 +45,19 @@ use crate::{get_effect_stack, CopyValue, Effect, EffectStack};
|
|||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[track_caller]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn use_signal<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal<T> {
|
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)]
|
#[derive(Clone)]
|
||||||
@@ -138,6 +149,7 @@ impl<'de, T: serde::Deserialize<'de> + 'static> serde::Deserialize<'de> for Sign
|
|||||||
|
|
||||||
impl<T: 'static> Signal<T> {
|
impl<T: 'static> Signal<T> {
|
||||||
/// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking.
|
/// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking.
|
||||||
|
#[track_caller]
|
||||||
pub fn new(value: T) -> Self {
|
pub fn new(value: T) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: CopyValue::new(SignalData {
|
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.
|
/// 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 {
|
pub fn new_in_scope(value: T, owner: ScopeId) -> Self {
|
||||||
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.
|
/// 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.
|
/// 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();
|
let inner = self.inner.read();
|
||||||
if let Some(effect) = inner.effect_stack.current() {
|
if let Some(effect) = inner.effect_stack.current() {
|
||||||
let mut effect_subscribers = inner.effect_subscribers.borrow_mut();
|
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.
|
/// Get a mutable reference to the signal's value.
|
||||||
/// If the signal has been dropped, this will panic.
|
/// 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 inner = self.inner.write();
|
||||||
let borrow = RefMut::map(inner, |v| &mut v.value);
|
let borrow = GenerationalRefMut::map(inner, |v| &mut v.value);
|
||||||
Write {
|
Write {
|
||||||
write: borrow,
|
write: borrow,
|
||||||
signal: SignalSubscriberDrop { signal: *self },
|
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.
|
/// Set the value of the signal. This will trigger an update on all subscribers.
|
||||||
|
#[track_caller]
|
||||||
pub fn set(&self, value: T) {
|
pub fn set(&self, value: T) {
|
||||||
*self.write() = value;
|
*self.write() = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run a closure with a reference to the signal's value.
|
/// Run a closure with a reference to the signal's value.
|
||||||
/// If the signal has been dropped, this will panic.
|
/// If the signal has been dropped, this will panic.
|
||||||
|
#[track_caller]
|
||||||
pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
|
pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
|
||||||
let write = self.read();
|
let write = self.read();
|
||||||
f(&*write)
|
f(&*write)
|
||||||
@@ -253,6 +289,7 @@ impl<T: 'static> Signal<T> {
|
|||||||
|
|
||||||
/// Run a closure with a mutable reference to the signal's value.
|
/// Run a closure with a mutable reference to the signal's value.
|
||||||
/// If the signal has been dropped, this will panic.
|
/// If the signal has been dropped, this will panic.
|
||||||
|
#[track_caller]
|
||||||
pub fn with_mut<O>(&self, f: impl FnOnce(&mut T) -> O) -> O {
|
pub fn with_mut<O>(&self, f: impl FnOnce(&mut T) -> O) -> O {
|
||||||
let mut write = self.write();
|
let mut write = self.write();
|
||||||
f(&mut *write)
|
f(&mut *write)
|
||||||
@@ -262,6 +299,7 @@ impl<T: 'static> Signal<T> {
|
|||||||
impl<T: Clone + '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.
|
/// 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.
|
/// If the signal has been dropped, this will panic.
|
||||||
|
#[track_caller]
|
||||||
pub fn value(&self) -> T {
|
pub fn value(&self) -> T {
|
||||||
self.read().clone()
|
self.read().clone()
|
||||||
}
|
}
|
||||||
@@ -281,7 +319,7 @@ impl<T: 'static> PartialEq for Signal<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Deref 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 {
|
fn deref(&self) -> &Self::Target {
|
||||||
// https://github.com/dtolnay/case-studies/tree/master/callable-types
|
// 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.
|
/// A mutable reference to a signal's value.
|
||||||
pub struct Write<'a, T: 'static, I: 'static = T> {
|
pub struct Write<T: 'static, I: 'static = T> {
|
||||||
write: RefMut<'a, T>,
|
write: GenerationalRefMut<T>,
|
||||||
signal: SignalSubscriberDrop<I>,
|
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.
|
/// 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;
|
let Self { write, signal } = myself;
|
||||||
Write {
|
Write {
|
||||||
write: RefMut::map(write, f),
|
write: GenerationalRefMut::map(write, f),
|
||||||
signal,
|
signal,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -343,14 +381,14 @@ impl<'a, T: 'static, I: 'static> Write<'a, T, I> {
|
|||||||
pub fn filter_map<O>(
|
pub fn filter_map<O>(
|
||||||
myself: Self,
|
myself: Self,
|
||||||
f: impl FnOnce(&mut T) -> Option<&mut O>,
|
f: impl FnOnce(&mut T) -> Option<&mut O>,
|
||||||
) -> Option<Write<'a, O, I>> {
|
) -> Option<Write<O, I>> {
|
||||||
let Self { write, signal } = myself;
|
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 })
|
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;
|
type Target = T;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
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 {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
&mut self.write
|
&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.
|
/// 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()
|
self.inner.read()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run a closure with a reference to the signal's value.
|
/// Run a closure with a reference to the signal's value.
|
||||||
|
#[track_caller]
|
||||||
pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
|
pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
|
||||||
self.inner.with(f)
|
self.inner.with(f)
|
||||||
}
|
}
|
||||||
@@ -405,7 +445,7 @@ impl<T: 'static> PartialEq for ReadOnlySignal<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Deref 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 {
|
fn deref(&self) -> &Self::Target {
|
||||||
// https://github.com/dtolnay/case-studies/tree/master/callable-types
|
// https://github.com/dtolnay/case-studies/tree/master/callable-types
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ keywords = ["dom", "ui", "gui", "react", "ssr"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dioxus-core = { workspace = true, features = ["serialize"] }
|
dioxus-core = { workspace = true, features = ["serialize"] }
|
||||||
|
dioxus-html = { workspace = true }
|
||||||
askama_escape = "0.10.3"
|
askama_escape = "0.10.3"
|
||||||
thiserror = "1.0.23"
|
thiserror = "1.0.23"
|
||||||
rustc-hash = "1.1.0"
|
rustc-hash = "1.1.0"
|
||||||
@@ -17,6 +18,8 @@ lru = "0.10.0"
|
|||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
http = "0.2.9"
|
http = "0.2.9"
|
||||||
tokio = { version = "1.28", features = ["full"], optional = true }
|
tokio = { version = "1.28", features = ["full"], optional = true }
|
||||||
|
async-trait = "0.1.58"
|
||||||
|
serde_json = { version = "1.0" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
dioxus = { workspace = true }
|
dioxus = { workspace = true }
|
||||||
|
|||||||
@@ -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!()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -81,6 +81,7 @@ impl IncrementalRenderer {
|
|||||||
let mut html_buffer = WriteBuffer { buffer: Vec::new() };
|
let mut html_buffer = WriteBuffer { buffer: Vec::new() };
|
||||||
{
|
{
|
||||||
let mut vdom = VirtualDom::new_with_props(comp, props);
|
let mut vdom = VirtualDom::new_with_props(comp, props);
|
||||||
|
crate::eval::init_eval(vdom.base_scope());
|
||||||
rebuild_with(&mut vdom).await;
|
rebuild_with(&mut vdom).await;
|
||||||
|
|
||||||
renderer.render_before_body(&mut *html_buffer)?;
|
renderer.render_before_body(&mut *html_buffer)?;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ pub mod incremental;
|
|||||||
#[cfg(feature = "incremental")]
|
#[cfg(feature = "incremental")]
|
||||||
mod incremental_cfg;
|
mod incremental_cfg;
|
||||||
|
|
||||||
|
pub mod eval;
|
||||||
pub mod renderer;
|
pub mod renderer;
|
||||||
pub mod template;
|
pub mod template;
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@ pub fn render_lazy(f: LazyNodes<'_, '_>) -> String {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut dom = VirtualDom::new_with_props(lazy_app, props);
|
let mut dom = VirtualDom::new_with_props(lazy_app, props);
|
||||||
|
crate::eval::init_eval(dom.base_scope());
|
||||||
_ = dom.rebuild();
|
_ = dom.rebuild();
|
||||||
|
|
||||||
Renderer::new().render(&dom)
|
Renderer::new().render(&dom)
|
||||||
|
|||||||
Reference in new issue
Block a user