1use crate::{
2 console_warn, create_isomorphic_effect, diagnostics, diagnostics::*,
3 macros::debug_warn, node::NodeId, on_cleanup, runtime::with_runtime,
4 Runtime,
5};
6use futures::Stream;
7use std::{
8 any::Any,
9 cell::RefCell,
10 fmt,
11 hash::{Hash, Hasher},
12 marker::PhantomData,
13 pin::Pin,
14 rc::Rc,
15};
16use thiserror::Error;
17
18macro_rules! impl_get_fn_traits {
19 ($($ty:ident $(($method_name:ident))?),*) => {
20 $(
21 #[cfg(feature = "nightly")]
22 impl<T: Clone> FnOnce<()> for $ty<T> {
23 type Output = T;
24
25 #[inline(always)]
26 extern "rust-call" fn call_once(self, _args: ()) -> Self::Output {
27 impl_get_fn_traits!(@method_name self $($method_name)?)
28 }
29 }
30
31 #[cfg(feature = "nightly")]
32 impl<T: Clone> FnMut<()> for $ty<T> {
33 #[inline(always)]
34 extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
35 impl_get_fn_traits!(@method_name self $($method_name)?)
36 }
37 }
38
39 #[cfg(feature = "nightly")]
40 impl<T: Clone> Fn<()> for $ty<T> {
41 #[inline(always)]
42 extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
43 impl_get_fn_traits!(@method_name self $($method_name)?)
44 }
45 }
46 )*
47 };
48 (@method_name $self:ident) => {
49 $self.get()
50 };
51 (@method_name $self:ident $ident:ident) => {
52 $self.$ident()
53 };
54}
55
56macro_rules! impl_set_fn_traits {
57 ($($ty:ident $($method_name:ident)?),*) => {
58 $(
59 #[cfg(feature = "nightly")]
60 impl<T> FnOnce<(T,)> for $ty<T> {
61 type Output = ();
62
63 #[inline(always)]
64 extern "rust-call" fn call_once(self, args: (T,)) -> Self::Output {
65 impl_set_fn_traits!(@method_name self $($method_name)? args)
66 }
67 }
68
69 #[cfg(feature = "nightly")]
70 impl<T> FnMut<(T,)> for $ty<T> {
71 #[inline(always)]
72 extern "rust-call" fn call_mut(&mut self, args: (T,)) -> Self::Output {
73 impl_set_fn_traits!(@method_name self $($method_name)? args)
74 }
75 }
76
77 #[cfg(feature = "nightly")]
78 impl<T> Fn<(T,)> for $ty<T> {
79 #[inline(always)]
80 extern "rust-call" fn call(&self, args: (T,)) -> Self::Output {
81 impl_set_fn_traits!(@method_name self $($method_name)? args)
82 }
83 }
84 )*
85 };
86 (@method_name $self:ident $args:ident) => {
87 $self.set($args.0)
88 };
89 (@method_name $self:ident $ident:ident $args:ident) => {
90 $self.$ident($args.0)
91 };
92}
93
94impl_get_fn_traits![ReadSignal, RwSignal];
95impl_set_fn_traits![WriteSignal];
96
97pub mod prelude {
100 pub use super::*;
101 pub use crate::{
102 memo::*, selector::*, signal_wrappers_read::*, signal_wrappers_write::*,
103 };
104}
105
106pub trait SignalGet {
109 type Value;
111
112 #[track_caller]
118 fn get(&self) -> Self::Value;
119
120 fn try_get(&self) -> Option<Self::Value>;
123}
124
125pub trait SignalWith {
128 type Value;
130
131 #[track_caller]
137 fn with<O>(&self, f: impl FnOnce(&Self::Value) -> O) -> O;
138
139 fn try_with<O>(&self, f: impl FnOnce(&Self::Value) -> O) -> Option<O>;
143
144 fn track(&self) {
146 _ = self.try_with(|_| {});
147 }
148}
149
150pub trait SignalSet {
152 type Value;
154
155 #[track_caller]
160 fn set(&self, new_value: Self::Value);
161
162 fn try_set(&self, new_value: Self::Value) -> Option<Self::Value>;
168}
169
170pub trait SignalUpdate {
172 type Value;
174
175 #[track_caller]
181 fn update(&self, f: impl FnOnce(&mut Self::Value));
182
183 fn try_update<O>(&self, f: impl FnOnce(&mut Self::Value) -> O)
190 -> Option<O>;
191}
192
193pub trait SignalGetUntracked {
198 type Value;
200
201 #[track_caller]
207 fn get_untracked(&self) -> Self::Value;
208
209 fn try_get_untracked(&self) -> Option<Self::Value>;
213}
214
215pub trait SignalWithUntracked {
218 type Value;
220
221 #[track_caller]
227 fn with_untracked<O>(&self, f: impl FnOnce(&Self::Value) -> O) -> O;
228
229 #[track_caller]
234 fn try_with_untracked<O>(
235 &self,
236 f: impl FnOnce(&Self::Value) -> O,
237 ) -> Option<O>;
238}
239
240pub trait SignalSetUntracked<T> {
245 #[track_caller]
247 fn set_untracked(&self, new_value: T);
248
249 #[track_caller]
252 fn try_set_untracked(&self, new_value: T) -> Option<T>;
253}
254
255pub trait SignalUpdateUntracked<T> {
258 #[track_caller]
261 fn update_untracked(&self, f: impl FnOnce(&mut T));
262
263 fn try_update_untracked<O>(&self, f: impl FnOnce(&mut T) -> O)
267 -> Option<O>;
268}
269
270pub trait SignalStream<T> {
272 #[track_caller]
281 fn to_stream(&self) -> Pin<Box<dyn Stream<Item = T>>>;
282}
283
284pub trait SignalDispose {
286 #[track_caller]
291 fn dispose(self);
292}
293
294#[cfg_attr(
335 any(debug_assertions, feature="ssr"),
336 instrument(
337 level = "trace",
338 skip_all,
339 fields(
340 ty = %std::any::type_name::<T>()
341 )
342 )
343)]
344#[track_caller]
345pub fn create_signal<T>(value: T) -> (ReadSignal<T>, WriteSignal<T>) {
346 Runtime::current().create_signal(value)
347}
348
349#[cfg_attr(
357 any(debug_assertions, feature = "ssr"),
358 instrument(level = "trace", skip_all,)
359)]
360pub fn create_signal_from_stream<T>(
361 #[allow(unused_mut)] mut stream: impl Stream<Item = T> + Unpin + 'static,
363) -> ReadSignal<Option<T>> {
364 cfg_if::cfg_if! {
365 if #[cfg(feature = "ssr")] {
366 _ = stream;
367 let (read, _) = create_signal(None);
368 read
369 } else {
370 use crate::spawn_local;
371 use futures::StreamExt;
372
373 let (read, write) = create_signal(None);
374 spawn_local(async move {
375 while let Some(value) = stream.next().await {
376 write.set(Some(value));
377 }
378 });
379 read
380 }
381 }
382}
383
384pub struct ReadSignal<T>
435where
436 T: 'static,
437{
438 pub(crate) id: NodeId,
439 pub(crate) ty: PhantomData<T>,
440 #[cfg(any(debug_assertions, feature = "ssr"))]
441 pub(crate) defined_at: &'static std::panic::Location<'static>,
442}
443
444impl<T: Clone> SignalGetUntracked for ReadSignal<T> {
445 type Value = T;
446
447 #[cfg_attr(
448 any(debug_assertions, feature = "ssr"),
449 instrument(
450 level = "trace",
451 name = "ReadSignal::get_untracked()",
452 skip_all,
453 fields(
454 id = ?self.id,
455 defined_at = %self.defined_at,
456 ty = %std::any::type_name::<T>()
457 )
458 )
459 )]
460 fn get_untracked(&self) -> T {
461 match with_runtime(|runtime| {
462 self.id.try_with_no_subscription(runtime, T::clone)
463 })
464 .expect("runtime to be alive")
465 {
466 Ok(t) => t,
467 Err(_) => panic_getting_dead_signal(
468 #[cfg(any(debug_assertions, feature = "ssr"))]
469 self.defined_at,
470 ),
471 }
472 }
473
474 #[cfg_attr(
475 any(debug_assertions, feature = "ssr"),
476 instrument(
477 level = "trace",
478 name = "ReadSignal::try_get_untracked()",
479 skip_all,
480 fields(
481 id = ?self.id,
482 defined_at = %self.defined_at,
483 ty = %std::any::type_name::<T>()
484 )
485 )
486 )]
487 #[track_caller]
488 fn try_get_untracked(&self) -> Option<T> {
489 with_runtime(|runtime| {
490 self.id.try_with_no_subscription(runtime, Clone::clone).ok()
491 })
492 .ok()
493 .flatten()
494 }
495}
496
497impl<T> SignalWithUntracked for ReadSignal<T> {
498 type Value = T;
499
500 #[cfg_attr(
501 any(debug_assertions, feature = "ssr"),
502 instrument(
503 level = "trace",
504 name = "ReadSignal::with_untracked()",
505 skip_all,
506 fields(
507 id = ?self.id,
508 defined_at = %self.defined_at,
509 ty = %std::any::type_name::<T>()
510 )
511 )
512 )]
513 #[inline(always)]
514 fn with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> O {
515 self.with_no_subscription(f)
516 }
517
518 #[cfg_attr(
519 any(debug_assertions, feature = "ssr"),
520 instrument(
521 level = "trace",
522 name = "ReadSignal::try_with_untracked()",
523 skip_all,
524 fields(
525 id = ?self.id,
526 defined_at = %self.defined_at,
527 ty = %std::any::type_name::<T>()
528 )
529 )
530 )]
531 #[track_caller]
532 #[inline(always)]
533 fn try_with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
534 match with_runtime(|runtime| {
535 self.id.try_with_no_subscription(runtime, f)
536 }) {
537 Ok(Ok(o)) => Some(o),
538 _ => None,
539 }
540 }
541}
542
543impl<T> SignalWith for ReadSignal<T> {
562 type Value = T;
563
564 #[cfg_attr(
565 any(debug_assertions, feature = "ssr"),
566 instrument(
567 level = "trace",
568 name = "ReadSignal::with()",
569 skip_all,
570 fields(
571 id = ?self.id,
572 defined_at = %self.defined_at,
573 ty = %std::any::type_name::<T>()
574 )
575 )
576 )]
577 #[track_caller]
578 #[inline(always)]
579 fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
580 let diagnostics = diagnostics!(self);
581
582 match with_runtime(|runtime| self.id.try_with(runtime, f, diagnostics))
583 .expect("runtime to be alive")
584 {
585 Ok(o) => o,
586 Err(_) => panic_getting_dead_signal(
587 #[cfg(any(debug_assertions, feature = "ssr"))]
588 self.defined_at,
589 ),
590 }
591 }
592
593 #[cfg_attr(
594 any(debug_assertions, feature = "ssr"),
595 instrument(
596 level = "trace",
597 name = "ReadSignal::try_with()",
598 skip_all,
599 fields(
600 id = ?self.id,
601 defined_at = %self.defined_at,
602 ty = %std::any::type_name::<T>()
603 )
604 )
605 )]
606 #[track_caller]
607 #[inline(always)]
608 fn try_with<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
609 let diagnostics = diagnostics!(self);
610
611 with_runtime(|runtime| self.id.try_with(runtime, f, diagnostics).ok())
612 .ok()
613 .flatten()
614 }
615}
616
617impl<T: Clone> SignalGet for ReadSignal<T> {
631 type Value = T;
632
633 #[cfg_attr(
634 any(debug_assertions, feature = "ssr"),
635 instrument(
636 level = "trace",
637 name = "ReadSignal::get()",
638 skip_all,
639 fields(
640 id = ?self.id,
641 defined_at = %self.defined_at,
642 ty = %std::any::type_name::<T>()
643 )
644 )
645 )]
646 #[track_caller]
647 fn get(&self) -> T {
648 let diagnostics = diagnostics!(self);
649
650 match with_runtime(|runtime| {
651 self.id.try_with(runtime, T::clone, diagnostics)
652 })
653 .expect("runtime to be alive")
654 {
655 Ok(t) => t,
656 Err(_) => panic_getting_dead_signal(
657 #[cfg(any(debug_assertions, feature = "ssr"))]
658 self.defined_at,
659 ),
660 }
661 }
662
663 #[cfg_attr(
664 any(debug_assertions, feature = "ssr"),
665 instrument(
666 level = "trace",
667 name = "ReadSignal::try_get()",
668 skip_all,
669 fields(
670 id = ?self.id,
671 defined_at = %self.defined_at,
672 ty = %std::any::type_name::<T>()
673 )
674 )
675 )]
676 fn try_get(&self) -> Option<T> {
677 self.try_with(Clone::clone).ok()
678 }
679}
680
681impl<T: Clone> SignalStream<T> for ReadSignal<T> {
682 #[cfg_attr(
683 any(debug_assertions, feature = "ssr"),
684 instrument(
685 level = "trace",
686 name = "ReadSignal::to_stream()",
687 skip_all,
688 fields(
689 id = ?self.id,
690 defined_at = %self.defined_at,
691 ty = %std::any::type_name::<T>()
692 )
693 )
694 )]
695 fn to_stream(&self) -> Pin<Box<dyn Stream<Item = T>>> {
696 let (tx, rx) = futures::channel::mpsc::unbounded();
697
698 let close_channel = tx.clone();
699
700 on_cleanup(move || close_channel.close_channel());
701
702 let this = *self;
703
704 create_isomorphic_effect(move |_| {
705 let _ = tx.unbounded_send(this.get());
706 });
707
708 Box::pin(rx)
709 }
710}
711
712impl<T> SignalDispose for ReadSignal<T> {
713 fn dispose(self) {
714 _ = with_runtime(|runtime| runtime.dispose_node(self.id));
715 }
716}
717
718impl<T> ReadSignal<T>
719where
720 T: 'static,
721{
722 #[track_caller]
723 #[inline(always)]
724 pub(crate) fn with_no_subscription<U>(&self, f: impl FnOnce(&T) -> U) -> U {
725 #[cfg(debug_assertions)]
726 let caller = std::panic::Location::caller();
727
728 self.id
729 .try_with_no_subscription_by_id(f)
730 .unwrap_or_else(|_| {
731 #[cfg(not(debug_assertions))]
732 {
733 panic!("tried to access ReadSignal that has been disposed")
734 }
735 #[cfg(debug_assertions)]
736 {
737 panic!(
738 "at {}, tried to access ReadSignal<{}> defined at {}, \
739 but it has already been disposed",
740 caller,
741 std::any::type_name::<T>(),
742 self.defined_at
743 )
744 }
745 })
746 }
747
748 #[track_caller]
751 #[inline(always)]
752 pub(crate) fn try_with<U>(
753 &self,
754 f: impl FnOnce(&T) -> U,
755 ) -> Result<U, SignalError> {
756 let diagnostics = diagnostics!(self);
757
758 match with_runtime(|runtime| self.id.try_with(runtime, f, diagnostics))
759 {
760 Ok(Ok(v)) => Ok(v),
761 Ok(Err(e)) => Err(e),
762 Err(_) => Err(SignalError::RuntimeDisposed),
763 }
764 }
765}
766
767impl<T> Clone for ReadSignal<T> {
768 fn clone(&self) -> Self {
769 *self
770 }
771}
772
773impl<T> Copy for ReadSignal<T> {}
774
775impl<T> fmt::Debug for ReadSignal<T> {
776 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
777 let mut s = f.debug_struct("ReadSignal");
778 s.field("id", &self.id);
779 s.field("ty", &self.ty);
780 #[cfg(any(debug_assertions, feature = "ssr"))]
781 s.field("defined_at", &self.defined_at);
782 s.finish()
783 }
784}
785
786impl<T> Eq for ReadSignal<T> {}
787
788impl<T> PartialEq for ReadSignal<T> {
789 fn eq(&self, other: &Self) -> bool {
790 self.id == other.id
791 }
792}
793
794impl<T> Hash for ReadSignal<T> {
795 fn hash<H: Hasher>(&self, state: &mut H) {
796 Runtime::current().hash(state);
797 self.id.hash(state);
798 }
799}
800
801pub struct WriteSignal<T>
848where
849 T: 'static,
850{
851 pub(crate) id: NodeId,
852 pub(crate) ty: PhantomData<T>,
853 #[cfg(any(debug_assertions, feature = "ssr"))]
854 pub(crate) defined_at: &'static std::panic::Location<'static>,
855}
856
857impl<T> SignalSetUntracked<T> for WriteSignal<T>
858where
859 T: 'static,
860{
861 #[cfg_attr(
862 any(debug_assertions, feature = "ssr"),
863 instrument(
864 level = "trace",
865 name = "WriteSignal::set_untracked()",
866 skip_all,
867 fields(
868 id = ?self.id,
869 defined_at = %self.defined_at,
870 ty = %std::any::type_name::<T>()
871 )
872 )
873 )]
874 fn set_untracked(&self, new_value: T) {
875 self.id.update_with_no_effect(
876 |v| *v = new_value,
877 #[cfg(debug_assertions)]
878 Some(self.defined_at),
879 );
880 }
881
882 #[cfg_attr(
883 any(debug_assertions, feature = "ssr"),
884 instrument(
885 level = "trace",
886 name = "WriteSignal::try_set_untracked()",
887 skip_all,
888 fields(
889 id = ?self.id,
890 defined_at = %self.defined_at,
891 ty = %std::any::type_name::<T>()
892 )
893 )
894 )]
895 fn try_set_untracked(&self, new_value: T) -> Option<T> {
896 let mut new_value = Some(new_value);
897
898 self.id.update(
899 |t| *t = new_value.take().unwrap(),
900 #[cfg(debug_assertions)]
901 None,
902 );
903
904 new_value
905 }
906}
907
908impl<T> SignalUpdateUntracked<T> for WriteSignal<T> {
909 #[cfg_attr(
910 any(debug_assertions, feature = "ssr"),
911 instrument(
912 level = "trace",
913 name = "WriteSignal::updated_untracked()",
914 skip_all,
915 fields(
916 id = ?self.id,
917 defined_at = %self.defined_at,
918 ty = %std::any::type_name::<T>()
919 )
920 )
921 )]
922 #[inline(always)]
923 fn update_untracked(&self, f: impl FnOnce(&mut T)) {
924 self.id.update_with_no_effect(
925 f,
926 #[cfg(debug_assertions)]
927 Some(self.defined_at),
928 );
929 }
930
931 #[inline(always)]
932 fn try_update_untracked<O>(
933 &self,
934 f: impl FnOnce(&mut T) -> O,
935 ) -> Option<O> {
936 self.id.update_with_no_effect(
937 f,
938 #[cfg(debug_assertions)]
939 None,
940 )
941 }
942}
943
944impl<T> SignalUpdate for WriteSignal<T> {
961 type Value = T;
962
963 #[cfg_attr(
964 any(debug_assertions, feature = "ssr"),
965 instrument(
966 name = "WriteSignal::update()",
967 level = "trace",
968 skip_all,
969 fields(
970 id = ?self.id,
971 defined_at = %self.defined_at,
972 ty = %std::any::type_name::<T>()
973 )
974 )
975 )]
976 #[inline(always)]
977 fn update(&self, f: impl FnOnce(&mut T)) {
978 if self
979 .id
980 .update(
981 f,
982 #[cfg(debug_assertions)]
983 Some(self.defined_at),
984 )
985 .is_none()
986 {
987 warn_updating_dead_signal(
988 #[cfg(any(debug_assertions, feature = "ssr"))]
989 self.defined_at,
990 );
991 }
992 }
993
994 #[cfg_attr(
995 any(debug_assertions, feature = "ssr"),
996 instrument(
997 name = "WriteSignal::try_update()",
998 level = "trace",
999 skip_all,
1000 fields(
1001 id = ?self.id,
1002 defined_at = %self.defined_at,
1003 ty = %std::any::type_name::<T>()
1004 )
1005 )
1006 )]
1007 #[inline(always)]
1008 fn try_update<O>(&self, f: impl FnOnce(&mut T) -> O) -> Option<O> {
1009 self.id.update(
1010 f,
1011 #[cfg(debug_assertions)]
1012 None,
1013 )
1014 }
1015}
1016
1017impl<T> SignalSet for WriteSignal<T> {
1035 type Value = T;
1036
1037 #[cfg_attr(
1038 any(debug_assertions, feature = "ssr"),
1039 instrument(
1040 level = "trace",
1041 name = "WriteSignal::set()",
1042 skip_all,
1043 fields(
1044 id = ?self.id,
1045 defined_at = %self.defined_at,
1046 ty = %std::any::type_name::<T>()
1047 )
1048 )
1049 )]
1050 fn set(&self, new_value: T) {
1051 self.id.update(
1052 |n| *n = new_value,
1053 #[cfg(debug_assertions)]
1054 Some(self.defined_at),
1055 );
1056 }
1057
1058 #[cfg_attr(
1059 any(debug_assertions, feature = "ssr"),
1060 instrument(
1061 level = "trace",
1062 name = "WriteSignal::try_set()",
1063 skip_all,
1064 fields(
1065 id = ?self.id,
1066 defined_at = %self.defined_at,
1067 ty = %std::any::type_name::<T>()
1068 )
1069 )
1070 )]
1071 fn try_set(&self, new_value: T) -> Option<T> {
1072 let mut new_value = Some(new_value);
1073
1074 self.id.update(
1075 |t| *t = new_value.take().unwrap(),
1076 #[cfg(debug_assertions)]
1077 None,
1078 );
1079
1080 new_value
1081 }
1082}
1083
1084impl<T> SignalDispose for WriteSignal<T> {
1085 fn dispose(self) {
1086 _ = with_runtime(|runtime| runtime.dispose_node(self.id));
1087 }
1088}
1089
1090impl<T> Clone for WriteSignal<T> {
1091 fn clone(&self) -> Self {
1092 *self
1093 }
1094}
1095
1096impl<T> Copy for WriteSignal<T> {}
1097
1098impl<T> fmt::Debug for WriteSignal<T> {
1099 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1100 let mut s = f.debug_struct("WriteSignal");
1101 s.field("id", &self.id);
1102 s.field("ty", &self.ty);
1103 #[cfg(any(debug_assertions, feature = "ssr"))]
1104 s.field("defined_at", &self.defined_at);
1105 s.finish()
1106 }
1107}
1108
1109impl<T> Eq for WriteSignal<T> {}
1110
1111impl<T> PartialEq for WriteSignal<T> {
1112 fn eq(&self, other: &Self) -> bool {
1113 self.id == other.id
1114 }
1115}
1116
1117impl<T> Hash for WriteSignal<T> {
1118 fn hash<H: Hasher>(&self, state: &mut H) {
1119 Runtime::current().hash(state);
1120 self.id.hash(state);
1121 }
1122}
1123
1124#[cfg_attr(
1146 any(debug_assertions, feature="ssr"),
1147 instrument(
1148 level = "trace",
1149 skip_all,
1150 fields(
1151 ty = %std::any::type_name::<T>()
1152 )
1153 )
1154)]
1155#[track_caller]
1156pub fn create_rw_signal<T>(value: T) -> RwSignal<T> {
1157 Runtime::current().create_rw_signal(value)
1158}
1159
1160pub struct RwSignal<T>
1204where
1205 T: 'static,
1206{
1207 pub(crate) id: NodeId,
1208 pub(crate) ty: PhantomData<T>,
1209 #[cfg(any(debug_assertions, feature = "ssr"))]
1210 pub(crate) defined_at: &'static std::panic::Location<'static>,
1211}
1212
1213impl<T: Default> Default for RwSignal<T> {
1214 fn default() -> Self {
1215 Self::new(Default::default())
1216 }
1217}
1218
1219impl<T> Clone for RwSignal<T> {
1220 fn clone(&self) -> Self {
1221 *self
1222 }
1223}
1224
1225impl<T> Copy for RwSignal<T> {}
1226
1227impl<T> fmt::Debug for RwSignal<T> {
1228 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1229 let mut s = f.debug_struct("RwSignal");
1230 s.field("id", &self.id);
1231 s.field("ty", &self.ty);
1232 #[cfg(any(debug_assertions, feature = "ssr"))]
1233 s.field("defined_at", &self.defined_at);
1234 s.finish()
1235 }
1236}
1237
1238impl<T> Eq for RwSignal<T> {}
1239
1240impl<T> PartialEq for RwSignal<T> {
1241 fn eq(&self, other: &Self) -> bool {
1242 self.id == other.id
1243 }
1244}
1245
1246impl<T> Hash for RwSignal<T> {
1247 fn hash<H: Hasher>(&self, state: &mut H) {
1248 Runtime::current().hash(state);
1249 self.id.hash(state);
1250 }
1251}
1252
1253impl<T> From<T> for RwSignal<T> {
1254 fn from(value: T) -> Self {
1255 create_rw_signal(value)
1256 }
1257}
1258
1259impl<T: Clone> SignalGetUntracked for RwSignal<T> {
1260 type Value = T;
1261
1262 #[cfg_attr(
1263 any(debug_assertions, feature = "ssr"),
1264 instrument(
1265 level = "trace",
1266 name = "RwSignal::get_untracked()",
1267 skip_all,
1268 fields(
1269 id = ?self.id,
1270 defined_at = %self.defined_at,
1271 ty = %std::any::type_name::<T>()
1272 )
1273 )
1274 )]
1275 #[track_caller]
1276 fn get_untracked(&self) -> T {
1277 #[cfg(debug_assertions)]
1278 let caller = std::panic::Location::caller();
1279
1280 self.id
1281 .try_with_no_subscription_by_id(Clone::clone)
1282 .unwrap_or_else(|_| {
1283 #[cfg(not(debug_assertions))]
1284 {
1285 panic!("tried to access RwSignal that has been disposed")
1286 }
1287 #[cfg(debug_assertions)]
1288 {
1289 panic!(
1290 "at {}, tried to access RwSignal<{}> defined at {}, \
1291 but it has already been disposed",
1292 caller,
1293 std::any::type_name::<T>(),
1294 self.defined_at
1295 )
1296 }
1297 })
1298 }
1299
1300 #[cfg_attr(
1301 any(debug_assertions, feature = "ssr"),
1302 instrument(
1303 level = "trace",
1304 name = "RwSignal::try_get_untracked()",
1305 skip_all,
1306 fields(
1307 id = ?self.id,
1308 defined_at = %self.defined_at,
1309 ty = %std::any::type_name::<T>()
1310 )
1311 )
1312 )]
1313 #[track_caller]
1314 fn try_get_untracked(&self) -> Option<T> {
1315 with_runtime(|runtime| {
1316 self.id.try_with_no_subscription(runtime, Clone::clone).ok()
1317 })
1318 .ok()
1319 .flatten()
1320 }
1321}
1322
1323impl<T> SignalWithUntracked for RwSignal<T> {
1324 type Value = T;
1325
1326 #[cfg_attr(
1327 any(debug_assertions, feature = "ssr"),
1328 instrument(
1329 level = "trace",
1330 name = "RwSignal::with_untracked()",
1331 skip_all,
1332 fields(
1333 id = ?self.id,
1334 defined_at = %self.defined_at,
1335 ty = %std::any::type_name::<T>()
1336 )
1337 )
1338 )]
1339 #[inline(always)]
1340 fn with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> O {
1341 self.id
1342 .try_with_no_subscription_by_id(f)
1343 .unwrap_or_else(|_| {
1344 #[cfg(not(debug_assertions))]
1345 {
1346 panic!("tried to access RwSignal that has been disposed")
1347 }
1348 #[cfg(debug_assertions)]
1349 {
1350 panic!(
1351 "tried to access RwSignal<{}> defined at {}, but it \
1352 has already been disposed",
1353 std::any::type_name::<T>(),
1354 self.defined_at
1355 )
1356 }
1357 })
1358 }
1359
1360 #[cfg_attr(
1361 any(debug_assertions, feature = "ssr"),
1362 instrument(
1363 level = "trace",
1364 name = "RwSignal::try_with_untracked()",
1365 skip_all,
1366 fields(
1367 id = ?self.id,
1368 defined_at = %self.defined_at,
1369 ty = %std::any::type_name::<T>()
1370 )
1371 )
1372 )]
1373 #[track_caller]
1374 #[inline(always)]
1375 fn try_with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
1376 match with_runtime(|runtime| {
1377 self.id.try_with_no_subscription(runtime, f)
1378 }) {
1379 Ok(Ok(o)) => Some(o),
1380 _ => None,
1381 }
1382 }
1383}
1384
1385impl<T> SignalSetUntracked<T> for RwSignal<T> {
1386 #[cfg_attr(
1387 any(debug_assertions, feature = "ssr"),
1388 instrument(
1389 level = "trace",
1390 name = "RwSignal::set_untracked()",
1391 skip_all,
1392 fields(
1393 id = ?self.id,
1394 defined_at = %self.defined_at,
1395 ty = %std::any::type_name::<T>()
1396 )
1397 )
1398 )]
1399 fn set_untracked(&self, new_value: T) {
1400 self.id.update_with_no_effect(
1401 |v| *v = new_value,
1402 #[cfg(debug_assertions)]
1403 Some(self.defined_at),
1404 );
1405 }
1406
1407 #[cfg_attr(
1408 any(debug_assertions, feature = "ssr"),
1409 instrument(
1410 level = "trace",
1411 name = "RwSignal::try_set_untracked()",
1412 skip_all,
1413 fields(
1414 id = ?self.id,
1415 defined_at = %self.defined_at,
1416 ty = %std::any::type_name::<T>()
1417 )
1418 )
1419 )]
1420 fn try_set_untracked(&self, new_value: T) -> Option<T> {
1421 let mut new_value = Some(new_value);
1422
1423 self.id.update(
1424 |t| *t = new_value.take().unwrap(),
1425 #[cfg(debug_assertions)]
1426 None,
1427 );
1428
1429 new_value
1430 }
1431}
1432
1433impl<T> SignalUpdateUntracked<T> for RwSignal<T> {
1434 #[cfg_attr(
1435 any(debug_assertions, feature="ssr"),
1436 instrument(
1437 level = "trace",
1438 name = "RwSignal::update_untracked()",
1439 skip_all,
1440 fields(
1441 id = ?self.id,
1442 defined_at = %self.defined_at,
1443 ty = %std::any::type_name::<T>()
1444 )
1445 )
1446 )]
1447 #[inline(always)]
1448 fn update_untracked(&self, f: impl FnOnce(&mut T)) {
1449 self.id.update_with_no_effect(
1450 f,
1451 #[cfg(debug_assertions)]
1452 Some(self.defined_at),
1453 );
1454 }
1455
1456 #[cfg_attr(
1457 any(debug_assertions, feature = "ssr"),
1458 instrument(
1459 level = "trace",
1460 name = "RwSignal::try_update_untracked()",
1461 skip_all,
1462 fields(
1463 id = ?self.id,
1464 defined_at = %self.defined_at,
1465 ty = %std::any::type_name::<T>()
1466 )
1467 )
1468 )]
1469 #[inline(always)]
1470 fn try_update_untracked<O>(
1471 &self,
1472 f: impl FnOnce(&mut T) -> O,
1473 ) -> Option<O> {
1474 self.id.update_with_no_effect(
1475 f,
1476 #[cfg(debug_assertions)]
1477 None,
1478 )
1479 }
1480}
1481
1482impl<T> SignalWith for RwSignal<T> {
1502 type Value = T;
1503
1504 #[cfg_attr(
1505 any(debug_assertions, feature = "ssr"),
1506 instrument(
1507 level = "trace",
1508 name = "RwSignal::with()",
1509 skip_all,
1510 fields(
1511 id = ?self.id,
1512 defined_at = %self.defined_at,
1513 ty = %std::any::type_name::<T>()
1514 )
1515 )
1516 )]
1517 #[track_caller]
1518 #[inline(always)]
1519 fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
1520 let diagnostics = diagnostics!(self);
1521
1522 match with_runtime(|runtime| self.id.try_with(runtime, f, diagnostics))
1523 .expect("runtime to be alive")
1524 {
1525 Ok(o) => o,
1526 Err(_) => panic_getting_dead_signal(
1527 #[cfg(any(debug_assertions, feature = "ssr"))]
1528 self.defined_at,
1529 ),
1530 }
1531 }
1532
1533 #[cfg_attr(
1534 any(debug_assertions, feature = "ssr"),
1535 instrument(
1536 level = "trace",
1537 name = "RwSignal::try_with()",
1538 skip_all,
1539 fields(
1540 id = ?self.id,
1541 defined_at = %self.defined_at,
1542 ty = %std::any::type_name::<T>()
1543 )
1544 )
1545 )]
1546 #[track_caller]
1547 #[inline(always)]
1548 fn try_with<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
1549 let diagnostics = diagnostics!(self);
1550
1551 with_runtime(|runtime| self.id.try_with(runtime, f, diagnostics).ok())
1552 .ok()
1553 .flatten()
1554 }
1555}
1556
1557impl<T: Clone> SignalGet for RwSignal<T> {
1572 type Value = T;
1573
1574 #[cfg_attr(
1575 any(debug_assertions, feature = "ssr"),
1576 instrument(
1577 level = "trace",
1578 name = "RwSignal::get()",
1579 skip_all,
1580 fields(
1581 id = ?self.id,
1582 defined_at = %self.defined_at,
1583 ty = %std::any::type_name::<T>()
1584 )
1585 )
1586 )]
1587 #[track_caller]
1588 fn get(&self) -> T
1589 where
1590 T: Clone,
1591 {
1592 let diagnostics = diagnostics!(self);
1593
1594 match with_runtime(|runtime| {
1595 self.id.try_with(runtime, T::clone, diagnostics)
1596 })
1597 .expect("runtime to be alive")
1598 {
1599 Ok(t) => t,
1600 Err(_) => panic_getting_dead_signal(
1601 #[cfg(any(debug_assertions, feature = "ssr"))]
1602 self.defined_at,
1603 ),
1604 }
1605 }
1606
1607 #[cfg_attr(
1608 any(debug_assertions, feature = "ssr"),
1609 instrument(
1610 level = "trace",
1611 name = "RwSignal::try_get()",
1612 skip_all,
1613 fields(
1614 id = ?self.id,
1615 defined_at = %self.defined_at,
1616 ty = %std::any::type_name::<T>()
1617 )
1618 )
1619 )]
1620 #[track_caller]
1621 fn try_get(&self) -> Option<T> {
1622 let diagnostics = diagnostics!(self);
1623
1624 with_runtime(|runtime| {
1625 self.id.try_with(runtime, Clone::clone, diagnostics).ok()
1626 })
1627 .ok()
1628 .flatten()
1629 }
1630}
1631
1632impl<T> SignalUpdate for RwSignal<T> {
1654 type Value = T;
1655
1656 #[cfg_attr(
1657 any(debug_assertions, feature = "ssr"),
1658 instrument(
1659 level = "trace",
1660 name = "RwSignal::update()",
1661 skip_all,
1662 fields(
1663 id = ?self.id,
1664 defined_at = %self.defined_at,
1665 ty = %std::any::type_name::<T>()
1666 )
1667 )
1668 )]
1669 #[inline(always)]
1670 fn update(&self, f: impl FnOnce(&mut T)) {
1671 if self
1672 .id
1673 .update(
1674 f,
1675 #[cfg(debug_assertions)]
1676 Some(self.defined_at),
1677 )
1678 .is_none()
1679 {
1680 warn_updating_dead_signal(
1681 #[cfg(any(debug_assertions, feature = "ssr"))]
1682 self.defined_at,
1683 );
1684 }
1685 }
1686
1687 #[cfg_attr(
1688 any(debug_assertions, feature = "ssr"),
1689 instrument(
1690 level = "trace",
1691 name = "RwSignal::try_update()",
1692 skip_all,
1693 fields(
1694 id = ?self.id,
1695 defined_at = %self.defined_at,
1696 ty = %std::any::type_name::<T>()
1697 )
1698 )
1699 )]
1700 #[inline(always)]
1701 fn try_update<O>(&self, f: impl FnOnce(&mut T) -> O) -> Option<O> {
1702 self.id.update(
1703 f,
1704 #[cfg(debug_assertions)]
1705 None,
1706 )
1707 }
1708}
1709
1710impl<T> SignalSet for RwSignal<T> {
1723 type Value = T;
1724
1725 #[cfg_attr(
1726 any(debug_assertions, feature = "ssr"),
1727 instrument(
1728 level = "trace",
1729 name = "RwSignal::set()",
1730 skip_all,
1731 fields(
1732 id = ?self.id,
1733 defined_at = %self.defined_at,
1734 ty = %std::any::type_name::<T>()
1735 )
1736 )
1737 )]
1738 fn set(&self, value: T) {
1739 self.id.update(
1740 |n| *n = value,
1741 #[cfg(debug_assertions)]
1742 Some(self.defined_at),
1743 );
1744 }
1745
1746 #[cfg_attr(
1747 any(debug_assertions, feature = "ssr"),
1748 instrument(
1749 level = "trace",
1750 name = "RwSignal::try_set()",
1751 skip_all,
1752 fields(
1753 id = ?self.id,
1754 defined_at = %self.defined_at,
1755 ty = %std::any::type_name::<T>()
1756 )
1757 )
1758 )]
1759 fn try_set(&self, new_value: T) -> Option<T> {
1760 let mut new_value = Some(new_value);
1761
1762 self.id.update(
1763 |t| *t = new_value.take().unwrap(),
1764 #[cfg(debug_assertions)]
1765 None,
1766 );
1767
1768 new_value
1769 }
1770}
1771
1772impl<T: Clone> SignalStream<T> for RwSignal<T> {
1773 fn to_stream(&self) -> Pin<Box<dyn Stream<Item = T>>> {
1774 let (tx, rx) = futures::channel::mpsc::unbounded();
1775
1776 let close_channel = tx.clone();
1777
1778 on_cleanup(move || close_channel.close_channel());
1779
1780 let this = *self;
1781
1782 create_isomorphic_effect(move |_| {
1783 let _ = tx.unbounded_send(this.get());
1784 });
1785
1786 Box::pin(rx)
1787 }
1788}
1789
1790impl<T> SignalDispose for RwSignal<T> {
1791 fn dispose(self) {
1792 _ = with_runtime(|runtime| runtime.dispose_node(self.id));
1793 }
1794}
1795
1796impl<T> RwSignal<T> {
1797 #[inline(always)]
1821 #[track_caller]
1822 pub fn new(value: T) -> Self {
1823 create_rw_signal(value)
1824 }
1825
1826 #[cfg_attr(
1843 any(debug_assertions, feature = "ssr"),
1844 instrument(
1845 level = "trace",
1846 name = "RwSignal::read_only()",
1847 skip_all,
1848 fields(
1849 id = ?self.id,
1850 defined_at = %self.defined_at,
1851 ty = %std::any::type_name::<T>()
1852 )
1853 )
1854 )]
1855 #[track_caller]
1856 pub fn read_only(&self) -> ReadSignal<T> {
1857 ReadSignal {
1858 id: self.id,
1859 ty: PhantomData,
1860 #[cfg(any(debug_assertions, feature = "ssr"))]
1861 defined_at: std::panic::Location::caller(),
1862 }
1863 }
1864
1865 #[cfg_attr(
1880 any(debug_assertions, feature = "ssr"),
1881 instrument(
1882 level = "trace",
1883 name = "RwSignal::write_only()",
1884 skip_all,
1885 fields(
1886 id = ?self.id,
1887 defined_at = %self.defined_at,
1888 ty = %std::any::type_name::<T>()
1889 )
1890 )
1891 )]
1892 #[track_caller]
1893 pub fn write_only(&self) -> WriteSignal<T> {
1894 WriteSignal {
1895 id: self.id,
1896 ty: PhantomData,
1897 #[cfg(any(debug_assertions, feature = "ssr"))]
1898 defined_at: std::panic::Location::caller(),
1899 }
1900 }
1901
1902 #[cfg_attr(
1916 any(debug_assertions, feature = "ssr"),
1917 instrument(
1918 level = "trace",
1919 name = "RwSignal::split()",
1920 skip_all,
1921 fields(
1922 id = ?self.id,
1923 defined_at = %self.defined_at,
1924 ty = %std::any::type_name::<T>()
1925 )
1926 )
1927 )]
1928 #[track_caller]
1929 pub fn split(&self) -> (ReadSignal<T>, WriteSignal<T>) {
1930 (
1931 ReadSignal {
1932 id: self.id,
1933 ty: PhantomData,
1934 #[cfg(any(debug_assertions, feature = "ssr"))]
1935 defined_at: std::panic::Location::caller(),
1936 },
1937 WriteSignal {
1938 id: self.id,
1939 ty: PhantomData,
1940 #[cfg(any(debug_assertions, feature = "ssr"))]
1941 defined_at: std::panic::Location::caller(),
1942 },
1943 )
1944 }
1945}
1946
1947#[derive(Debug, Error)]
1948pub(crate) enum SignalError {
1949 #[error("tried to access a signal in a runtime that had been disposed")]
1950 RuntimeDisposed,
1951 #[error("tried to access a signal that had been disposed")]
1952 Disposed,
1953 #[error("error casting signal to type {0}")]
1954 Type(&'static str),
1955}
1956
1957impl NodeId {
1958 #[track_caller]
1959 pub(crate) fn subscribe(
1960 &self,
1961 runtime: &Runtime,
1962 #[allow(unused)] diagnostics: AccessDiagnostics,
1963 ) {
1964 if let Some(observer) = runtime.observer.get() {
1966 let mut subs = runtime.node_subscribers.borrow_mut();
1968 if let Some(subs) = subs.entry(*self) {
1969 subs.or_default().borrow_mut().insert(observer);
1970 }
1971
1972 let mut sources = runtime.node_sources.borrow_mut();
1974 if let Some(sources) = sources.entry(observer) {
1975 let sources = sources.or_default();
1976 sources.borrow_mut().insert(*self);
1977 }
1978 } else {
1979 #[cfg(all(debug_assertions, not(feature = "ssr")))]
1980 {
1981 if !SpecialNonReactiveZone::is_inside() {
1982 let AccessDiagnostics {
1983 called_at,
1984 defined_at,
1985 } = diagnostics;
1986 crate::macros::debug_warn!(
1987 "At {called_at}, you access a signal or memo (defined \
1988 at {defined_at}) outside a reactive tracking \
1989 context. This might mean your app is not responding \
1990 to changes in signal values in the way you \
1991 expect.\n\nHere’s how to fix it:\n\n1. If this is \
1992 inside a `view!` macro, make sure you are passing a \
1993 function, not a value.\n ❌ NO <p>{{x.get() * \
1994 2}}</p>\n ✅ YES <p>{{move || x.get() * \
1995 2}}</p>\n\n2. If it’s in the body of a component, \
1996 try wrapping this access in a closure: \n ❌ NO \
1997 let y = x.get() * 2\n ✅ YES let y = move || \
1998 x.get() * 2.\n\n3. If you’re *trying* to access the \
1999 value without tracking, use `.get_untracked()` or \
2000 `.with_untracked()` instead."
2001 );
2002 }
2003 }
2004 }
2005 }
2006
2007 fn try_with_no_subscription_inner(
2008 &self,
2009 runtime: &Runtime,
2010 ) -> Result<Rc<RefCell<dyn Any>>, SignalError> {
2011 runtime.update_if_necessary(*self);
2012 let nodes = runtime.nodes.borrow();
2013 let node = nodes.get(*self).ok_or(SignalError::Disposed)?;
2014 Ok(node.value())
2015 }
2016
2017 #[inline(always)]
2018 pub(crate) fn try_with_no_subscription_by_id<T, U>(
2019 &self,
2020 f: impl FnOnce(&T) -> U,
2021 ) -> Result<U, SignalError>
2022 where
2023 T: 'static,
2024 {
2025 with_runtime(|runtime| self.try_with_no_subscription(runtime, f))
2026 .expect("runtime to be alive")
2027 }
2028
2029 #[track_caller]
2030 #[inline(always)]
2031 pub(crate) fn try_with_no_subscription<T, U>(
2032 &self,
2033 runtime: &Runtime,
2034 f: impl FnOnce(&T) -> U,
2035 ) -> Result<U, SignalError>
2036 where
2037 T: 'static,
2038 {
2039 let value = self.try_with_no_subscription_inner(runtime)?;
2040 let value = value.borrow();
2041 let value = value
2042 .downcast_ref::<T>()
2043 .ok_or_else(|| SignalError::Type(std::any::type_name::<T>()))
2044 .expect("to downcast signal type");
2045 Ok(f(value))
2046 }
2047
2048 #[track_caller]
2049 #[inline(always)]
2050 pub(crate) fn try_with<T, U>(
2051 &self,
2052 runtime: &Runtime,
2053 f: impl FnOnce(&T) -> U,
2054 diagnostics: AccessDiagnostics,
2055 ) -> Result<U, SignalError>
2056 where
2057 T: 'static,
2058 {
2059 self.subscribe(runtime, diagnostics);
2060
2061 self.try_with_no_subscription(runtime, f)
2062 }
2063
2064 #[inline(always)]
2065 #[track_caller]
2066 fn update_value<T, U>(
2067 &self,
2068
2069 f: impl FnOnce(&mut T) -> U,
2070 #[cfg(debug_assertions)] defined_at: Option<
2071 &'static std::panic::Location<'static>,
2072 >,
2073 ) -> Option<U>
2074 where
2075 T: 'static,
2076 {
2077 #[cfg(debug_assertions)]
2078 let location = std::panic::Location::caller();
2079
2080 with_runtime(|runtime| {
2081 if let Some(value) = runtime.get_value(*self) {
2082 let mut value = value.borrow_mut();
2083 if let Some(value) = value.downcast_mut::<T>() {
2084 Some(f(value))
2085 } else {
2086 debug_warn!(
2087 "[Signal::update] failed when downcasting to \
2088 Signal<{}>",
2089 std::any::type_name::<T>()
2090 );
2091 None
2092 }
2093 } else {
2094 #[cfg(debug_assertions)]
2095 {
2096 if let Some(defined_at) = defined_at {
2097 debug_warn!(
2098 "[Signal::update] At {:?}, you’re trying to \
2099 update a Signal<{}> (defined at {defined_at}) \
2100 that has already been disposed of. This is \
2101 probably a logic error in a component that \
2102 creates and disposes of scopes. If it does not \
2103 cause any issues, it is safe to ignore this \
2104 warning, which occurs only in debug mode.",
2105 location,
2106 std::any::type_name::<T>()
2107 );
2108 }
2109 }
2110 None
2111 }
2112 })
2113 .unwrap_or_default()
2114 }
2115
2116 #[inline(always)]
2117 #[track_caller]
2118 pub(crate) fn update<T, U>(
2119 &self,
2120 f: impl FnOnce(&mut T) -> U,
2121 #[cfg(debug_assertions)] defined_at: Option<
2122 &'static std::panic::Location<'static>,
2123 >,
2124 ) -> Option<U>
2125 where
2126 T: 'static,
2127 {
2128 #[cfg(debug_assertions)]
2129 let location = std::panic::Location::caller();
2130
2131 with_runtime(|runtime| {
2132 let updated = if let Some(value) = runtime.get_value(*self) {
2133 let mut value = value.borrow_mut();
2134 if let Some(value) = value.downcast_mut::<T>() {
2135 Some(f(value))
2136 } else {
2137 debug_warn!(
2138 "[Signal::update] failed when downcasting to \
2139 Signal<{}>",
2140 std::any::type_name::<T>()
2141 );
2142 None
2143 }
2144 } else {
2145 #[cfg(debug_assertions)]
2146 {
2147 if let Some(defined_at) = defined_at {
2148 debug_warn!(
2149 "[Signal::update] At {:?}, you’re trying to \
2150 update a Signal<{}> (defined at {defined_at}) \
2151 that has already been disposed of. This is \
2152 probably a logic error in a component that \
2153 creates and disposes of scopes. If it does not \
2154 cause any issues, it is safe to ignore this \
2155 warning, which occurs only in debug mode.",
2156 location,
2157 std::any::type_name::<T>()
2158 );
2159 }
2160 }
2161 None
2162 };
2163
2164 if updated.is_some() {
2166 runtime.mark_dirty(*self);
2168
2169 runtime.run_effects();
2170 }
2171
2172 updated
2173 })
2174 .unwrap_or_default()
2175 }
2176
2177 #[inline(always)]
2178 pub(crate) fn update_with_no_effect<T, U>(
2179 &self,
2180
2181 f: impl FnOnce(&mut T) -> U,
2182 #[cfg(debug_assertions)] defined_at: Option<
2183 &'static std::panic::Location<'static>,
2184 >,
2185 ) -> Option<U>
2186 where
2187 T: 'static,
2188 {
2189 self.update_value(
2191 f,
2192 #[cfg(debug_assertions)]
2193 defined_at,
2194 )
2195 }
2196}
2197
2198#[cold]
2199#[inline(never)]
2200#[track_caller]
2201pub(crate) fn format_signal_warning(
2202 msg: &str,
2203 #[cfg(any(debug_assertions, feature = "ssr"))]
2204 defined_at: &'static std::panic::Location<'static>,
2205) -> String {
2206 let location = std::panic::Location::caller();
2207
2208 let defined_at_msg = {
2209 #[cfg(any(debug_assertions, feature = "ssr"))]
2210 {
2211 format!("signal created here: {defined_at}\n")
2212 }
2213
2214 #[cfg(not(any(debug_assertions, feature = "ssr")))]
2215 {
2216 String::default()
2217 }
2218 };
2219
2220 format!("{msg}\n{defined_at_msg}warning happened here: {location}",)
2221}
2222
2223#[cold]
2224#[inline(never)]
2225#[track_caller]
2226pub(crate) fn panic_getting_dead_signal(
2227 #[cfg(any(debug_assertions, feature = "ssr"))]
2228 defined_at: &'static std::panic::Location<'static>,
2229) -> ! {
2230 panic!(
2231 "{}",
2232 format_signal_warning(
2233 "Attempted to get a signal after it was disposed.",
2234 #[cfg(any(debug_assertions, feature = "ssr"))]
2235 defined_at,
2236 )
2237 )
2238}
2239
2240#[cold]
2241#[inline(never)]
2242#[track_caller]
2243pub(crate) fn warn_updating_dead_signal(
2244 #[cfg(any(debug_assertions, feature = "ssr"))]
2245 defined_at: &'static std::panic::Location<'static>,
2246) {
2247 console_warn(&format_signal_warning(
2248 "Attempted to update a signal after it was disposed.",
2249 #[cfg(any(debug_assertions, feature = "ssr"))]
2250 defined_at,
2251 ));
2252}