mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-26 22:20:19 +00:00
Merge branch 'master' into cli-config-library
This commit is contained in:
commit
ea15c6a2eb
43 changed files with 877 additions and 144 deletions
2
.github/workflows/docs stable.yml
vendored
2
.github/workflows/docs stable.yml
vendored
|
@ -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.
|
||||
|
|
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
|
@ -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.
|
||||
|
|
3
.github/workflows/miri.yml
vendored
3
.github/workflows/miri.yml
vendored
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
|
76
packages/hooks/src/use_const.rs
Normal file
76
packages/hooks/src/use_const.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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>>,
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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!(),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use tui::{
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::{Color, Modifier},
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
42
packages/ssr/src/eval.rs
Normal 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!()
|
||||
}
|
||||
}
|
|
@ -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)?;
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue