Skip to content

Commit 14376e1

Browse files
authored
fix: Proper reactive context support (#25)
* fix: Proper reactive context support * clean up
1 parent 131919e commit 14376e1

File tree

5 files changed

+40
-89
lines changed

5 files changed

+40
-89
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ struct Data {
5757
}
5858

5959
// Channels used to identify the subscribers of the State
60-
#[derive(PartialEq, Eq, Clone, Debug, Copy)]
60+
#[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)]
6161
pub enum DataChannel {
6262
ListCreation,
6363
SpecificListItemUpdate(usize),

examples/async-demo.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ impl DataAsyncReducer for Data {
2828
}
2929
}
3030

31-
#[derive(PartialEq, Eq, Clone, Debug)]
31+
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
3232
pub enum DataChannel {
3333
FetchedData,
3434
Void,

examples/complex-demo.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ impl DataReducer for Data {
3131
}
3232
}
3333

34-
#[derive(PartialEq, Eq, Clone, Debug, Copy)]
34+
#[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)]
3535
pub enum DataChannel {
3636
ListCreation,
3737
SpecificListItemUpdate(usize),

examples/simple-demo.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ struct Data {
66
pub lists: Vec<Vec<String>>,
77
}
88

9-
#[derive(PartialEq, Eq, Clone, Debug, Copy)]
9+
#[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)]
1010
pub enum DataChannel {
1111
ListCreation,
1212
SpecificListItemUpdate(usize),

src/hooks/use_radio.rs

Lines changed: 36 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{
22
collections::{HashMap, HashSet},
3+
hash::Hash,
34
ops::{Deref, DerefMut},
45
sync::{Arc, Mutex},
56
};
@@ -11,34 +12,27 @@ mod warnings {
1112
pub use warnings::Warning;
1213

1314
#[cfg(feature = "tracing")]
14-
pub trait RadioChannel<T>:
15-
'static + PartialEq + Eq + Clone + std::hash::Hash + std::fmt::Debug + Ord
16-
{
15+
pub trait RadioChannel<T>: 'static + PartialEq + Eq + Clone + Hash + std::fmt::Debug + Ord {
1716
fn derive_channel(self, _radio: &T) -> Vec<Self> {
1817
vec![self]
1918
}
2019
}
2120

2221
#[cfg(not(feature = "tracing"))]
23-
pub trait RadioChannel<T>: 'static + PartialEq + Eq + Clone {
22+
pub trait RadioChannel<T>: 'static + PartialEq + Eq + Clone + Hash {
2423
fn derive_channel(self, _radio: &T) -> Vec<Self> {
2524
vec![self]
2625
}
2726
}
2827

29-
pub struct RadioListener<Channel> {
30-
pub(crate) channel: Channel,
31-
pub(crate) drop_signal: CopyValue<()>,
32-
}
33-
3428
/// Holds a global state and all its subscribers.
3529
pub struct RadioStation<Value, Channel>
3630
where
3731
Channel: RadioChannel<Value>,
3832
Value: 'static,
3933
{
4034
value: Signal<Value>,
41-
listeners: Signal<HashMap<ReactiveContext, RadioListener<Channel>>>,
35+
listeners: Signal<HashMap<Channel, Arc<Mutex<HashSet<ReactiveContext>>>>>,
4236
}
4337

4438
impl<Value, Channel> Clone for RadioStation<Value, Channel>
@@ -63,54 +57,30 @@ where
6357
) -> bool {
6458
let listeners = self.listeners.peek_unchecked();
6559
listeners
66-
.get(reactive_context)
67-
.map(|listener| &listener.channel == channel)
60+
.get(channel)
61+
.map(|contexts| contexts.lock().unwrap().contains(reactive_context))
6862
.unwrap_or_default()
6963
}
7064

7165
pub(crate) fn listen(&self, channel: Channel, reactive_context: ReactiveContext) {
7266
dioxus_lib::prelude::warnings::signal_write_in_component_body::allow(|| {
7367
let mut listeners = self.listeners.write_unchecked();
74-
listeners.insert(
75-
reactive_context,
76-
RadioListener {
77-
channel,
78-
drop_signal: CopyValue::new_maybe_sync(()),
79-
},
80-
);
81-
});
82-
}
83-
84-
pub(crate) fn unlisten(&self, reactive_context: ReactiveContext) {
85-
dioxus_lib::prelude::warnings::signal_write_in_component_body::allow(|| {
86-
let mut listeners = match self.listeners.try_write_unchecked() {
87-
Err(generational_box::BorrowMutError::Dropped(_)) => {
88-
// It's safe to skip this error as the RadioStation's signals could have been dropped before the caller of this function.
89-
// For instance: If you closed the app, the RadioStation would be dropped along all it's signals, causing the inner components
90-
// to still have dropped signals and thus causing this error if they were to call the signals on a custom destructor.
91-
return;
92-
}
93-
Err(e) => panic!("Unexpected error: {e}"),
94-
Ok(v) => v,
95-
};
96-
listeners.remove(&reactive_context);
68+
let listeners = listeners.entry(channel).or_default();
69+
reactive_context.subscribe(listeners.clone());
9770
});
9871
}
9972

10073
pub(crate) fn notify_listeners(&self, channel: &Channel) {
101-
let mut listeners = self.listeners.write_unchecked();
74+
let listeners = self.listeners.write_unchecked();
10275

10376
#[cfg(feature = "tracing")]
10477
tracing::info!("Notifying {channel:?}");
10578

106-
// Remove dropped listeners
107-
dioxus_lib::prelude::warnings::copy_value_hoisted::allow(|| {
108-
listeners.retain(|_, listener| listener.drop_signal.try_write().is_ok());
109-
});
110-
111-
for (reactive_context, listener) in listeners.iter() {
112-
if &listener.channel == channel {
113-
reactive_context.mark_dirty();
79+
for (listener_channel, listeners) in listeners.iter() {
80+
if listener_channel == channel {
81+
for reactive_context in listeners.lock().unwrap().iter() {
82+
reactive_context.mark_dirty();
83+
}
11484
}
11585
}
11686
}
@@ -137,27 +107,30 @@ where
137107
self.value.peek()
138108
}
139109

140-
#[cfg(not(feature = "tracing"))]
141-
pub fn print_metrics(&self) {}
110+
pub fn cleanup(&self) {
111+
let mut listeners = self.listeners.write_unchecked();
142112

143-
#[cfg(feature = "tracing")]
144-
pub fn print_metrics(&self) {
145-
use itertools::Itertools;
146-
use tracing::{info, span, Level};
113+
// Clean up those channels with no reactive contexts
114+
listeners.retain(|_, listeners| !listeners.lock().unwrap().is_empty());
147115

148-
let mut channels_subscribers = HashMap::<&Channel, usize>::new();
116+
#[cfg(feature = "tracing")]
117+
{
118+
use itertools::Itertools;
119+
use tracing::{info, span, Level};
149120

150-
let listeners = self.listeners.peek();
121+
let mut channels_subscribers = HashMap::<&Channel, usize>::new();
151122

152-
for sub in listeners.values() {
153-
*channels_subscribers.entry(&sub.channel).or_default() += 1;
154-
}
123+
for (channel, listeners) in listeners.iter() {
124+
*channels_subscribers.entry(&channel).or_default() =
125+
listeners.lock().unwrap().len();
126+
}
155127

156-
let span = span!(Level::DEBUG, "Radio Station Metrics");
157-
let _enter = span.enter();
128+
let span = span!(Level::DEBUG, "Radio Station Metrics");
129+
let _enter = span.enter();
158130

159-
for (channel, count) in channels_subscribers.iter().sorted() {
160-
info!(" {count} subscribers for {channel:?}")
131+
for (channel, count) in channels_subscribers.iter().sorted() {
132+
info!(" {count} subscribers for {channel:?}")
133+
}
161134
}
162135
}
163136
}
@@ -169,8 +142,6 @@ where
169142
{
170143
pub(crate) channel: Channel,
171144
station: RadioStation<Value, Channel>,
172-
reactive_context: ReactiveContext,
173-
pub(crate) subscribers: Arc<Mutex<HashSet<ReactiveContext>>>,
174145
}
175146

176147
impl<Value, Channel> RadioAntenna<Value, Channel>
@@ -180,23 +151,8 @@ where
180151
pub(crate) fn new(
181152
channel: Channel,
182153
station: RadioStation<Value, Channel>,
183-
reactive_context: ReactiveContext,
184154
) -> RadioAntenna<Value, Channel> {
185-
RadioAntenna {
186-
channel,
187-
station,
188-
reactive_context,
189-
subscribers: Arc::default(),
190-
}
191-
}
192-
}
193-
194-
impl<Value, Channel> Drop for RadioAntenna<Value, Channel>
195-
where
196-
Channel: RadioChannel<Value>,
197-
{
198-
fn drop(&mut self) {
199-
self.station.unlisten(self.reactive_context)
155+
RadioAntenna { channel, station }
200156
}
201157
}
202158

@@ -219,7 +175,7 @@ where
219175
self.antenna.peek().station.notify_listeners(channel)
220176
}
221177
if !self.channels.is_empty() {
222-
self.antenna.peek().station.print_metrics();
178+
self.antenna.peek().station.cleanup();
223179
}
224180
}
225181
}
@@ -284,7 +240,6 @@ where
284240
dioxus_lib::prelude::warnings::signal_write_in_component_body::allow(|| {
285241
if let Some(rc) = ReactiveContext::current() {
286242
let antenna = &self.antenna.write_unchecked();
287-
rc.subscribe(antenna.subscribers.clone());
288243
let channel = antenna.channel.clone();
289244
let is_listening = antenna.station.is_listening(&channel, &rc);
290245

@@ -422,7 +377,7 @@ where
422377
for channel in channel.derive_channel(&guard.value) {
423378
self.antenna.peek().station.notify_listeners(&channel)
424379
}
425-
self.antenna.peek().station.print_metrics();
380+
self.antenna.peek().station.cleanup();
426381
}
427382
}
428383

@@ -480,11 +435,7 @@ where
480435
let station = use_context::<RadioStation<Value, Channel>>();
481436

482437
let mut radio = use_hook(|| {
483-
let antenna = RadioAntenna::new(
484-
channel.clone(),
485-
station,
486-
ReactiveContext::current().unwrap(),
487-
);
438+
let antenna = RadioAntenna::new(channel.clone(), station);
488439
Radio::new(Signal::new(antenna))
489440
});
490441

0 commit comments

Comments
 (0)