mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 09:02:37 -05:00
Compare commits
1 Commits
server-fn-
...
lens
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73db030535 |
@@ -15,12 +15,13 @@ cfg_if! {
|
||||
}
|
||||
}
|
||||
|
||||
#[server]
|
||||
// "/api" is an optional prefix that allows you to locate server functions wherever you'd like on the server
|
||||
#[server(GetServerCount, "/api")]
|
||||
pub async fn get_server_count() -> Result<i32, ServerFnError> {
|
||||
Ok(COUNT.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
#[server]
|
||||
#[server(AdjustServerCount, "/api")]
|
||||
pub async fn adjust_server_count(
|
||||
delta: i32,
|
||||
msg: String,
|
||||
@@ -32,7 +33,7 @@ pub async fn adjust_server_count(
|
||||
Ok(new)
|
||||
}
|
||||
|
||||
#[server]
|
||||
#[server(ClearServerCount, "/api")]
|
||||
pub async fn clear_server_count() -> Result<i32, ServerFnError> {
|
||||
COUNT.store(0, Ordering::Relaxed);
|
||||
_ = COUNT_CHANNEL.send(&0).await;
|
||||
@@ -146,8 +147,6 @@ pub fn Counter() -> impl IntoView {
|
||||
// but uses HTML forms to submit the actions
|
||||
#[component]
|
||||
pub fn FormCounter() -> impl IntoView {
|
||||
// these struct names are auto-generated by #[server]
|
||||
// they are just the PascalCased versions of the function names
|
||||
let adjust = create_server_action::<AdjustServerCount>();
|
||||
let clear = create_server_action::<ClearServerCount>();
|
||||
|
||||
|
||||
@@ -107,8 +107,7 @@ pub async fn add_todo(title: String) -> Result<(), ServerFnError> {
|
||||
}
|
||||
}
|
||||
|
||||
// The struct name and path prefix arguments are optional.
|
||||
#[server]
|
||||
#[server(DeleteTodo, "/api")]
|
||||
pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
|
||||
let pool = pool()?;
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ pub struct PostMetadata {
|
||||
title: String,
|
||||
}
|
||||
|
||||
#[server]
|
||||
#[server(ListPostMetadata, "/api")]
|
||||
pub async fn list_post_metadata() -> Result<Vec<PostMetadata>, ServerFnError> {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
Ok(POSTS
|
||||
@@ -185,7 +185,7 @@ pub async fn list_post_metadata() -> Result<Vec<PostMetadata>, ServerFnError> {
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[server]
|
||||
#[server(GetPost, "/api")]
|
||||
pub async fn get_post(id: usize) -> Result<Option<Post>, ServerFnError> {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
Ok(POSTS.iter().find(|post| post.id == id).cloned())
|
||||
|
||||
@@ -173,7 +173,7 @@ pub struct PostMetadata {
|
||||
title: String,
|
||||
}
|
||||
|
||||
#[server]
|
||||
#[server(ListPostMetadata, "/api")]
|
||||
pub async fn list_post_metadata() -> Result<Vec<PostMetadata>, ServerFnError> {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
Ok(POSTS
|
||||
@@ -185,7 +185,7 @@ pub async fn list_post_metadata() -> Result<Vec<PostMetadata>, ServerFnError> {
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[server]
|
||||
#[server(GetPost, "/api")]
|
||||
pub async fn get_post(id: usize) -> Result<Option<Post>, ServerFnError> {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
Ok(POSTS.iter().find(|post| post.id == id).cloned())
|
||||
|
||||
@@ -4,14 +4,14 @@ use leptos_router::*;
|
||||
const WAIT_ONE_SECOND: u64 = 1;
|
||||
const WAIT_TWO_SECONDS: u64 = 2;
|
||||
|
||||
#[server]
|
||||
#[server(FirstWaitFn "/api")]
|
||||
async fn first_wait_fn(seconds: u64) -> Result<(), ServerFnError> {
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(seconds)).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[server]
|
||||
#[server(SecondWaitFn "/api")]
|
||||
async fn second_wait_fn(seconds: u64) -> Result<(), ServerFnError> {
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(seconds)).await;
|
||||
|
||||
|
||||
@@ -62,8 +62,7 @@ pub async fn add_todo(title: String) -> Result<(), ServerFnError> {
|
||||
}
|
||||
}
|
||||
|
||||
// The struct name and path prefix arguments are optional.
|
||||
#[server]
|
||||
#[server(DeleteTodo, "/api")]
|
||||
pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
|
||||
let mut conn = db().await?;
|
||||
|
||||
|
||||
@@ -79,8 +79,7 @@ pub async fn add_todo(title: String) -> Result<(), ServerFnError> {
|
||||
}
|
||||
}
|
||||
|
||||
// The struct name and path prefix arguments are optional.
|
||||
#[server]
|
||||
#[server(DeleteTodo, "/api")]
|
||||
pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
|
||||
let mut conn = db().await?;
|
||||
|
||||
|
||||
@@ -79,8 +79,7 @@ pub async fn add_todo(title: String) -> Result<(), ServerFnError> {
|
||||
}
|
||||
}
|
||||
|
||||
// The struct name and path prefix arguments are optional.
|
||||
#[server]
|
||||
#[server(DeleteTodo, "/api")]
|
||||
pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
|
||||
let mut conn = db().await?;
|
||||
|
||||
|
||||
@@ -59,10 +59,10 @@ pub fn add_event_listener<E>(
|
||||
if #[cfg(debug_assertions)] {
|
||||
let span = ::tracing::Span::current();
|
||||
let cb = Box::new(move |e| {
|
||||
let prev = leptos_reactive::SpecialNonReactiveZone::enter();
|
||||
leptos_reactive::SpecialNonReactiveZone::enter();
|
||||
let _guard = span.enter();
|
||||
cb(e);
|
||||
leptos_reactive::SpecialNonReactiveZone::exit(prev);
|
||||
leptos_reactive::SpecialNonReactiveZone::exit();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -88,10 +88,10 @@ pub(crate) fn add_event_listener_undelegated<E>(
|
||||
if #[cfg(debug_assertions)] {
|
||||
let span = ::tracing::Span::current();
|
||||
let cb = Box::new(move |e| {
|
||||
let prev = leptos_reactive::SpecialNonReactiveZone::enter();
|
||||
leptos_reactive::SpecialNonReactiveZone::enter();
|
||||
let _guard = span.enter();
|
||||
cb(e);
|
||||
leptos_reactive::SpecialNonReactiveZone::exit(prev);
|
||||
leptos_reactive::SpecialNonReactiveZone::exit();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,10 +218,10 @@ pub fn set_timeout_with_handle(
|
||||
if #[cfg(debug_assertions)] {
|
||||
let span = ::tracing::Span::current();
|
||||
let cb = move || {
|
||||
let prev = leptos_reactive::SpecialNonReactiveZone::enter();
|
||||
leptos_reactive::SpecialNonReactiveZone::enter();
|
||||
let _guard = span.enter();
|
||||
cb();
|
||||
leptos_reactive::SpecialNonReactiveZone::exit(prev);
|
||||
leptos_reactive::SpecialNonReactiveZone::exit();
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -273,10 +273,10 @@ pub fn debounce<T: 'static>(
|
||||
if #[cfg(debug_assertions)] {
|
||||
let span = ::tracing::Span::current();
|
||||
let cb = move |value| {
|
||||
let prev = leptos_reactive::SpecialNonReactiveZone::enter();
|
||||
leptos_reactive::SpecialNonReactiveZone::enter();
|
||||
let _guard = span.enter();
|
||||
cb(value);
|
||||
leptos_reactive::SpecialNonReactiveZone::exit(prev);
|
||||
leptos_reactive::SpecialNonReactiveZone::exit();
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -351,10 +351,10 @@ pub fn set_interval_with_handle(
|
||||
if #[cfg(debug_assertions)] {
|
||||
let span = ::tracing::Span::current();
|
||||
let cb = move || {
|
||||
let prev = leptos_reactive::SpecialNonReactiveZone::enter();
|
||||
leptos_reactive::SpecialNonReactiveZone::enter();
|
||||
let _guard = span.enter();
|
||||
cb();
|
||||
leptos_reactive::SpecialNonReactiveZone::exit(prev);
|
||||
leptos_reactive::SpecialNonReactiveZone::exit();
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -392,10 +392,10 @@ pub fn window_event_listener_untyped(
|
||||
if #[cfg(debug_assertions)] {
|
||||
let span = ::tracing::Span::current();
|
||||
let cb = move |e| {
|
||||
let prev = leptos_reactive::SpecialNonReactiveZone::enter();
|
||||
leptos_reactive::SpecialNonReactiveZone::enter();
|
||||
let _guard = span.enter();
|
||||
cb(e);
|
||||
leptos_reactive::SpecialNonReactiveZone::exit(prev);
|
||||
leptos_reactive::SpecialNonReactiveZone::exit();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use proc_macro::TokenStream;
|
||||
use proc_macro2::{Span, TokenTree};
|
||||
use quote::ToTokens;
|
||||
use rstml::{node::KeyedAttribute, parse};
|
||||
use server_fn_macro::server_macro_impl;
|
||||
use syn::parse_macro_input;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
@@ -33,7 +34,6 @@ mod params;
|
||||
mod view;
|
||||
use view::{client_template::render_template, render_view};
|
||||
mod component;
|
||||
mod server;
|
||||
mod slot;
|
||||
|
||||
/// The `view` macro uses RSX (like JSX, but Rust!) It follows most of the
|
||||
@@ -760,23 +760,17 @@ pub fn slot(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
/// If you call a server function from the client (i.e., when the `csr` or `hydrate` features
|
||||
/// are enabled), it will instead make a network request to the server.
|
||||
///
|
||||
/// You can specify one, two, three, or four arguments to the server function. All of these arguments are optional.
|
||||
/// 1. A type name that will be used to identify and register the server function
|
||||
/// (e.g., `MyServerFn`). Defaults to a PascalCased version of the function name.
|
||||
/// 2. A URL prefix at which the function will be mounted when it’s registered
|
||||
/// (e.g., `"/api"`). Defaults to `"/api"`.
|
||||
/// 3. The encoding for the server function (`"Url"`, `"Cbor"`, `"GetJson"`, or `"GetCbor`". See **Server Function Encodings** below.)
|
||||
/// 4. A specific endpoint path to be used in the URL. (By default, a unique path will be generated.)
|
||||
/// You can specify one, two, three, or four arguments to the server function:
|
||||
/// 1. **Required**: A type name that will be used to identify and register the server function
|
||||
/// (e.g., `MyServerFn`).
|
||||
/// 2. *Optional*: A URL prefix at which the function will be mounted when it’s registered
|
||||
/// (e.g., `"/api"`). Defaults to `"/"`.
|
||||
/// 3. *Optional*: The encoding for the server function (`"Url"`, `"Cbor"`, `"GetJson"`, or `"GetCbor`". See **Server Function Encodings** below.)
|
||||
/// 4. *Optional*: A specific endpoint path to be used in the URL. (By default, a unique path will be generated.)
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// // will generate a server function at `/api-prefix/hello`
|
||||
/// #[server(MyServerFnType, "/api-prefix", "Url", "hello")]
|
||||
/// pub async fn my_server_fn_type() /* ... */
|
||||
///
|
||||
/// // will generate a server function with struct `HelloWorld` and path
|
||||
/// // `/api/hello2349232342342` (hash based on location in source)
|
||||
/// #[server]
|
||||
/// pub async fn hello_world() /* ... */
|
||||
/// ```
|
||||
///
|
||||
/// The server function itself can take any number of arguments, each of which should be serializable
|
||||
@@ -865,7 +859,16 @@ pub fn slot(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
#[proc_macro_attribute]
|
||||
#[proc_macro_error]
|
||||
pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
server::server_impl(args, s)
|
||||
match server_macro_impl(
|
||||
args.into(),
|
||||
s.into(),
|
||||
syn::parse_quote!(::leptos::leptos_server::ServerFnTraitObj),
|
||||
None,
|
||||
Some(syn::parse_quote!(::leptos::server_fn)),
|
||||
) {
|
||||
Err(e) => e.to_compile_error().into(),
|
||||
Ok(s) => s.to_token_stream().into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Derives a trait that parses a map of string keys and values into a typed
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
use convert_case::{Case, Converter};
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Literal;
|
||||
use quote::{ToTokens, __private::TokenStream as TokenStream2};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
Ident, ItemFn, Token,
|
||||
};
|
||||
|
||||
pub fn server_impl(
|
||||
args: proc_macro::TokenStream,
|
||||
s: TokenStream,
|
||||
) -> TokenStream {
|
||||
let function: syn::ItemFn =
|
||||
match syn::parse(s).map_err(|e| e.to_compile_error()) {
|
||||
Ok(f) => f,
|
||||
Err(e) => return e.into(),
|
||||
};
|
||||
let ItemFn {
|
||||
attrs,
|
||||
vis,
|
||||
sig,
|
||||
block,
|
||||
} = function;
|
||||
// TODO apply middleware: https://github.com/leptos-rs/leptos/issues/1461
|
||||
let mapped_body = quote::quote! {
|
||||
#(#attrs)*
|
||||
#vis #sig {
|
||||
#block
|
||||
}
|
||||
};
|
||||
|
||||
let mut args: ServerFnArgs = match syn::parse(args) {
|
||||
Ok(args) => args,
|
||||
Err(e) => return e.to_compile_error().into(),
|
||||
};
|
||||
// default to PascalCase version of function name if no struct name given
|
||||
if args.struct_name.is_none() {
|
||||
let upper_cammel_case_name = Converter::new()
|
||||
.from_case(Case::Snake)
|
||||
.to_case(Case::UpperCamel)
|
||||
.convert(sig.ident.to_string());
|
||||
args.struct_name =
|
||||
Some(Ident::new(&upper_cammel_case_name, sig.ident.span()));
|
||||
}
|
||||
// default to "/api" if no prefix given
|
||||
if args.prefix.is_none() {
|
||||
args.prefix = Some(Literal::string("/api"));
|
||||
}
|
||||
|
||||
match server_fn_macro::server_macro_impl(
|
||||
quote::quote!(#args),
|
||||
mapped_body,
|
||||
syn::parse_quote!(::leptos::leptos_server::ServerFnTraitObj),
|
||||
None,
|
||||
Some(syn::parse_quote!(::leptos::server_fn)),
|
||||
) {
|
||||
Err(e) => e.to_compile_error().into(),
|
||||
Ok(s) => s.to_token_stream().into(),
|
||||
}
|
||||
}
|
||||
|
||||
struct ServerFnArgs {
|
||||
struct_name: Option<Ident>,
|
||||
_comma: Option<Token![,]>,
|
||||
prefix: Option<Literal>,
|
||||
_comma2: Option<Token![,]>,
|
||||
encoding: Option<Literal>,
|
||||
_comma3: Option<Token![,]>,
|
||||
fn_path: Option<Literal>,
|
||||
}
|
||||
|
||||
impl ToTokens for ServerFnArgs {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let struct_name =
|
||||
self.struct_name.as_ref().map(|s| quote::quote! { #s, });
|
||||
let prefix = self.prefix.as_ref().map(|p| quote::quote! { #p, });
|
||||
let encoding = self.encoding.as_ref().map(|e| quote::quote! { #e, });
|
||||
let fn_path = self.fn_path.as_ref().map(|f| quote::quote! { #f, });
|
||||
tokens.extend(quote::quote! {
|
||||
#struct_name
|
||||
#prefix
|
||||
#encoding
|
||||
#fn_path
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for ServerFnArgs {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let struct_name = input.parse()?;
|
||||
let _comma = input.parse()?;
|
||||
let prefix = input.parse()?;
|
||||
let _comma2 = input.parse()?;
|
||||
let encoding = input.parse()?;
|
||||
let _comma3 = input.parse()?;
|
||||
let fn_path = input.parse()?;
|
||||
|
||||
Ok(Self {
|
||||
struct_name,
|
||||
_comma,
|
||||
prefix,
|
||||
_comma2,
|
||||
encoding,
|
||||
_comma3,
|
||||
fn_path,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -43,18 +43,18 @@ impl SpecialNonReactiveZone {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn enter() -> bool {
|
||||
IS_SPECIAL_ZONE.with(|val| {
|
||||
let prev = val.get();
|
||||
val.set(true);
|
||||
prev
|
||||
})
|
||||
#[inline(always)]
|
||||
pub fn enter() {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
IS_SPECIAL_ZONE.with(|val| val.set(true))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn exit(prev: bool) {
|
||||
if !prev {
|
||||
#[inline(always)]
|
||||
pub fn exit() {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
IS_SPECIAL_ZONE.with(|val| val.set(false))
|
||||
}
|
||||
}
|
||||
|
||||
158
leptos_reactive/src/lens.rs
Normal file
158
leptos_reactive/src/lens.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
// Paths are fn pointers. They can be safely cast to usize but not back.
|
||||
|
||||
use crate::{
|
||||
create_trigger, runtime::FxIndexMap, store_value, Signal, StoredValue,
|
||||
Trigger,
|
||||
};
|
||||
use std::{any::Any, fmt::Debug};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
struct PathId(usize);
|
||||
|
||||
pub struct StoreInner<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
value: StoredValue<T>,
|
||||
lenses: FxIndexMap<PathId, Trigger>,
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for StoreInner<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("StoreInner")
|
||||
.field("value", &self.value)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> StoreInner<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
pub fn new(value: T) -> Self {
|
||||
Self {
|
||||
value: store_value(value),
|
||||
lenses: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_update<U, V>(
|
||||
&mut self,
|
||||
lens: fn(&mut T) -> &mut U,
|
||||
setter: impl FnOnce(&mut U) -> V + 'static,
|
||||
) -> Option<V> {
|
||||
// get or create trigger
|
||||
let id = PathId(lens as usize);
|
||||
let trigger = *self.lenses.entry(id).or_default();
|
||||
|
||||
// run update function
|
||||
let result = self.value.try_update_value(|value| {
|
||||
let zone = lens(value);
|
||||
setter(zone)
|
||||
})?;
|
||||
|
||||
// notify trigger
|
||||
if trigger.try_notify() {
|
||||
Some(result)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_read<U: 'static, V>(
|
||||
&mut self,
|
||||
lens: fn(&mut T) -> &mut U,
|
||||
getter: impl Fn(&U) -> V + 'static,
|
||||
) -> Signal<Option<V>> {
|
||||
// get or create trigger
|
||||
let id = PathId(lens as usize);
|
||||
let trigger = *self.lenses.entry(id).or_default();
|
||||
|
||||
let value = self.value;
|
||||
|
||||
// run update function
|
||||
Signal::derive(move || {
|
||||
trigger.track();
|
||||
value.try_update_value(|value| {
|
||||
let zone = lens(value);
|
||||
getter(&*zone)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::StoreInner;
|
||||
use crate::{
|
||||
create_effect, create_runtime, SignalGet, SignalGetUntracked,
|
||||
SignalWith, SignalWithUntracked,
|
||||
};
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
#[derive(Default)]
|
||||
struct SomeComplexType {
|
||||
a: NonCloneableUsize,
|
||||
b: NonCloneableString,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
struct NonCloneableUsize(usize);
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
struct NonCloneableString(String);
|
||||
|
||||
#[test]
|
||||
pub fn create_lens() {
|
||||
let rt = create_runtime();
|
||||
|
||||
// create the store
|
||||
let mut store = StoreInner::new(SomeComplexType::default());
|
||||
|
||||
// create two signal lenses
|
||||
fn lens_a(store: &mut SomeComplexType) -> &mut NonCloneableUsize {
|
||||
&mut store.a
|
||||
}
|
||||
fn lens_b(store: &mut SomeComplexType) -> &mut NonCloneableString {
|
||||
&mut store.b
|
||||
}
|
||||
let read_a = store.try_read(lens_a, |a| a.0);
|
||||
read_a.with_untracked(|val| assert_eq!(val, &Some(0)));
|
||||
assert_eq!(read_a.get_untracked(), Some(0));
|
||||
let read_b = store.try_read(lens_b, |b| b.0.len());
|
||||
assert_eq!(read_b.get_untracked(), Some(0));
|
||||
|
||||
// track how many times each variable notifies
|
||||
let reads_on_a = Rc::new(Cell::new(0));
|
||||
let reads_on_b = Rc::new(Cell::new(0));
|
||||
create_effect({
|
||||
let reads_on_a = Rc::clone(&reads_on_a);
|
||||
move |_| {
|
||||
read_a.track();
|
||||
reads_on_a.set(reads_on_a.get() + 1);
|
||||
}
|
||||
});
|
||||
create_effect({
|
||||
let reads_on_b = Rc::clone(&reads_on_b);
|
||||
move |_| {
|
||||
read_b.track();
|
||||
reads_on_b.set(reads_on_b.get() + 1);
|
||||
}
|
||||
});
|
||||
assert_eq!(reads_on_a.get(), 1);
|
||||
assert_eq!(reads_on_b.get(), 1);
|
||||
|
||||
// update each one once
|
||||
store.try_update(lens_a, |a| *a = NonCloneableUsize(42));
|
||||
assert_eq!(read_a.get_untracked(), Some(42));
|
||||
|
||||
store.try_update(lens_b, |b| b.0.push_str("hello, world!"));
|
||||
assert_eq!(read_b.get_untracked(), Some(13));
|
||||
|
||||
// each effect has only run once
|
||||
// none of the values has been cloned (they can't)
|
||||
assert_eq!(reads_on_a.get(), 2);
|
||||
assert_eq!(reads_on_b.get(), 2);
|
||||
|
||||
rt.dispose();
|
||||
}
|
||||
}
|
||||
@@ -83,6 +83,7 @@ mod context;
|
||||
mod diagnostics;
|
||||
mod effect;
|
||||
mod hydration;
|
||||
mod lens;
|
||||
mod memo;
|
||||
mod node;
|
||||
mod resource;
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
signal_prelude::format_signal_warning, spawn::spawn_local, use_context,
|
||||
GlobalSuspenseContext, Memo, ReadSignal, ScopeProperty, SignalDispose,
|
||||
SignalGet, SignalGetUntracked, SignalSet, SignalUpdate, SignalWith,
|
||||
SpecialNonReactiveZone, SuspenseContext, WriteSignal,
|
||||
SuspenseContext, WriteSignal,
|
||||
};
|
||||
use std::{
|
||||
any::Any,
|
||||
@@ -523,13 +523,7 @@ where
|
||||
pub fn refetch(&self) {
|
||||
_ = with_runtime(|runtime| {
|
||||
runtime.resource(self.id, |resource: &ResourceState<S, T>| {
|
||||
#[cfg(debug_assertions)]
|
||||
let prev = SpecialNonReactiveZone::enter();
|
||||
resource.refetch();
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
SpecialNonReactiveZone::exit(prev);
|
||||
}
|
||||
resource.refetch()
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::{
|
||||
use cfg_if::cfg_if;
|
||||
use core::hash::BuildHasherDefault;
|
||||
use futures::stream::FuturesUnordered;
|
||||
use indexmap::IndexSet;
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
use rustc_hash::{FxHashMap, FxHasher};
|
||||
use slotmap::{SecondaryMap, SlotMap, SparseSecondaryMap};
|
||||
use std::{
|
||||
@@ -46,7 +46,8 @@ tokio::task_local! {
|
||||
pub(crate) static TASK_RUNTIME: Option<RuntimeId>;
|
||||
}
|
||||
|
||||
type FxIndexSet<T> = IndexSet<T, BuildHasherDefault<FxHasher>>;
|
||||
pub(crate) type FxIndexSet<T> = IndexSet<T, BuildHasherDefault<FxHasher>>;
|
||||
pub(crate) type FxIndexMap<T, U> = IndexMap<T, U, BuildHasherDefault<FxHasher>>;
|
||||
|
||||
// The data structure that owns all the signals, memos, effects,
|
||||
// and other data included in the reactive system.
|
||||
@@ -776,12 +777,9 @@ impl RuntimeId {
|
||||
with_runtime(|runtime| {
|
||||
let untracked_result;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
let prev = if !diagnostics {
|
||||
SpecialNonReactiveZone::enter()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if !diagnostics {
|
||||
SpecialNonReactiveZone::enter();
|
||||
}
|
||||
|
||||
let prev_observer =
|
||||
SetObserverOnDrop(self, runtime.observer.take());
|
||||
@@ -791,9 +789,8 @@ impl RuntimeId {
|
||||
runtime.observer.set(prev_observer.1);
|
||||
std::mem::forget(prev_observer); // avoid Drop
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
if !diagnostics {
|
||||
SpecialNonReactiveZone::exit(prev);
|
||||
SpecialNonReactiveZone::exit();
|
||||
}
|
||||
|
||||
untracked_result
|
||||
@@ -1239,13 +1236,9 @@ impl Drop for SetBatchingOnDrop {
|
||||
pub fn on_cleanup(cleanup_fn: impl FnOnce() + 'static) {
|
||||
#[cfg(debug_assertions)]
|
||||
let cleanup_fn = move || {
|
||||
#[cfg(debug_assertions)]
|
||||
let prev = crate::SpecialNonReactiveZone::enter();
|
||||
crate::SpecialNonReactiveZone::enter();
|
||||
cleanup_fn();
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
crate::SpecialNonReactiveZone::exit(prev);
|
||||
}
|
||||
crate::SpecialNonReactiveZone::exit();
|
||||
};
|
||||
push_cleanup(Box::new(cleanup_fn))
|
||||
}
|
||||
|
||||
@@ -17,6 +17,12 @@ pub struct Trigger {
|
||||
pub(crate) defined_at: &'static std::panic::Location<'static>,
|
||||
}
|
||||
|
||||
impl Default for Trigger {
|
||||
fn default() -> Self {
|
||||
create_trigger()
|
||||
}
|
||||
}
|
||||
|
||||
impl Trigger {
|
||||
/// Notifies any reactive code where this trigger is tracked to rerun.
|
||||
///
|
||||
@@ -29,7 +35,7 @@ impl Trigger {
|
||||
|
||||
/// Attempts to notify any reactive code where this trigger is tracked to rerun.
|
||||
///
|
||||
/// Returns `None` if the runtime has been disposed.
|
||||
/// Returns `false` if the runtime has been disposed.
|
||||
pub fn try_notify(&self) -> bool {
|
||||
with_runtime(|runtime| {
|
||||
runtime.mark_dirty(self.id);
|
||||
|
||||
@@ -281,7 +281,7 @@ pub fn server_macro_impl(
|
||||
}
|
||||
|
||||
impl #struct_name {
|
||||
const URL: &'static str = if #fn_path.is_empty() {
|
||||
const URL: &str = if #fn_path.is_empty() {
|
||||
#server_fn_path::const_format::concatcp!(
|
||||
#fn_name_as_str,
|
||||
#server_fn_path::xxhash_rust::const_xxh64::xxh64(
|
||||
@@ -292,7 +292,7 @@ pub fn server_macro_impl(
|
||||
} else {
|
||||
#fn_path
|
||||
};
|
||||
const PREFIX: &'static str = #prefix;
|
||||
const PREFIX: &str = #prefix;
|
||||
const ENCODING: #server_fn_path::Encoding = #encoding;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user