Skip to content

Commit 4534433

Browse files
authored
feat: Channel Selection (#23)
1 parent b88a933 commit 4534433

File tree

6 files changed

+115
-45
lines changed

6 files changed

+115
-45
lines changed

Cargo.lock

Lines changed: 18 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "dioxus-radio"
3-
version = "0.4.0"
3+
version = "0.5.0"
44
edition = "2021"
55
description = "Fully-typed global state management with a topics subscription system for Dioxus 🧬"
66
readme = "./README.md"
@@ -10,10 +10,17 @@ keywords = ["dioxus"]
1010
categories = ["gui"]
1111
license = "MIT"
1212

13+
[features]
14+
tracing = ["dep:tracing", "dep:itertools"]
15+
1316
[dependencies]
1417
dioxus-lib = { version = "0.6.0", default-features = false, features = ["macro", "hooks", "signals"] }
1518
generational-box = "0.6.0"
1619
warnings = "0.2.1"
1720

21+
# Logging
22+
itertools = { version = "0.14.0", optional = true }
23+
tracing = { version = "0.1.41", optional = true }
24+
1825
[dev-dependencies]
1926
dioxus = { version = "0.6.0", features = ["desktop"] }

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
## Support
1414

1515
- **Dioxus v0.6** 🧬
16-
- All renderers ([web](https://dioxuslabs.com/learn/0.5/getting_started/wasm), [desktop](https://dioxuslabs.com/learn/0.5/getting_started/desktop), [freya](https://github.com/marc2332/freya), etc)
16+
- All renderers ([web](https://dioxuslabs.com/learn/0.6/getting_started/wasm), [desktop](https://dioxuslabs.com/learn/0.6/getting_started/desktop), [freya](https://github.com/marc2332/freya), etc)
1717
- Both WASM and native targets
1818

1919
## Questions

examples/async-demo.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ impl DataAsyncReducer for Data {
1717
async fn async_reduce(
1818
radio: &mut Radio<Data, Self::Channel>,
1919
action: Self::Action,
20-
) -> Self::Channel {
20+
) -> ChannelSelection<Self::Channel> {
2121
match action {
2222
DataAction::FetchData => {
2323
radio.write_silently().count += 1;
2424

25-
DataChannel::FetchedData
25+
ChannelSelection::Select(DataChannel::FetchedData)
2626
}
2727
}
2828
}

examples/complex-demo.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,17 @@ impl DataReducer for Data {
1515
type Action = DataAction;
1616
type Channel = DataChannel;
1717

18-
fn reduce(&mut self, message: Self::Action) -> Self::Channel {
18+
fn reduce(&mut self, message: Self::Action) -> ChannelSelection<Self::Channel> {
1919
match message {
2020
DataAction::NewList => {
2121
self.lists.push(Vec::default());
2222

23-
DataChannel::ListCreation
23+
ChannelSelection::Select(DataChannel::ListCreation)
2424
}
2525
DataAction::AddToList { list, text } => {
2626
self.lists[list].push(text);
2727

28-
DataChannel::SpecificListItemUpdate(list)
28+
ChannelSelection::Select(DataChannel::SpecificListItemUpdate(list))
2929
}
3030
}
3131
}

src/hooks/use_radio.rs

Lines changed: 83 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ mod warnings {
1010
}
1111
pub use warnings::Warning;
1212

13+
#[cfg(feature = "tracing")]
14+
pub trait RadioChannel<T>: 'static + PartialEq + Eq + Clone + Hash + std::fmt::Debug + Ord {
15+
fn derive_channel(self, _radio: &T) -> Vec<Self> {
16+
vec![self]
17+
}
18+
}
19+
20+
#[cfg(not(feature = "tracing"))]
1321
pub trait RadioChannel<T>: 'static + PartialEq + Eq + Clone {
1422
fn derive_channel(self, _radio: &T) -> Vec<Self> {
1523
vec![self]
@@ -90,6 +98,9 @@ where
9098
pub(crate) fn notify_listeners(&self, channel: &Channel) {
9199
let mut listeners = self.listeners.write_unchecked();
92100

101+
#[cfg(feature = "tracing")]
102+
tracing::info!("Notifying {channel:?}");
103+
93104
// Remove dropped listeners
94105
dioxus_lib::prelude::warnings::copy_value_hoisted::allow(|| {
95106
listeners.retain(|_, listener| listener.drop_signal.try_write().is_ok());
@@ -123,6 +134,30 @@ where
123134
pub fn peek(&self) -> ReadableRef<Signal<Value>> {
124135
self.value.peek()
125136
}
137+
138+
#[cfg(not(feature = "tracing"))]
139+
pub fn print_metrics(&self) {}
140+
141+
#[cfg(feature = "tracing")]
142+
pub fn print_metrics(&self) {
143+
use itertools::Itertools;
144+
use tracing::{info, span, Level};
145+
146+
let mut channels_subscribers = HashMap::<&Channel, usize>::new();
147+
148+
let listeners = self.listeners.peek();
149+
150+
for sub in listeners.values() {
151+
*channels_subscribers.entry(&sub.channel).or_default() += 1;
152+
}
153+
154+
let span = span!(Level::DEBUG, "Radio Station Metrics");
155+
let _enter = span.enter();
156+
157+
for (channel, count) in channels_subscribers.iter().sorted() {
158+
info!(" {count} subscribers for {channel:?}")
159+
}
160+
}
126161
}
127162

128163
pub struct RadioAntenna<Value, Channel>
@@ -181,6 +216,9 @@ where
181216
for channel in &mut self.channels {
182217
self.antenna.peek().station.notify_listeners(channel)
183218
}
219+
if !self.channels.is_empty() {
220+
self.antenna.peek().station.print_metrics();
221+
}
184222
}
185223
}
186224

@@ -353,57 +391,36 @@ where
353391
/// Example:
354392
///
355393
/// ```rs
356-
/// radio.write_with_map_channel(|value| {
394+
/// radio.write_with_channel_selection(|value| {
357395
/// // Modify `value`
358396
/// if value.cool {
359-
/// Channel::Whatever
397+
/// ChannelSelection::Select(Channel::Whatever)
360398
/// } else {
361-
/// Channel::SomethingElse
399+
/// ChannelSelection::Silence
362400
/// }
363401
/// });
364402
/// ```
365-
pub fn write_with_map_channel(&mut self, cb: impl FnOnce(&mut Value) -> Channel) {
366-
let value = self.antenna.peek().station.value.write_unchecked();
367-
let mut guard = RadioGuard {
368-
channels: Vec::default(),
369-
antenna: self.antenna,
370-
value,
371-
};
372-
let channel = cb(&mut guard.value);
373-
for channel in channel.derive_channel(&guard.value) {
374-
self.antenna.peek().station.notify_listeners(&channel)
375-
}
376-
}
377-
378-
/// Get a mutable reference to the current state value, inside a callback that returns the channel to be used or none (will use the [Radio]'s one then).
379-
///
380-
/// Example:
381-
///
382-
/// ```rs
383-
/// radio.write_with_map_optional_channel(|value| {
384-
/// // Modify `value`
385-
/// if value.cool {
386-
/// Some(Channel::Whatever)
387-
/// } else {
388-
/// None
389-
/// }
390-
/// });
391-
/// ```
392-
pub fn write_with_map_optional_channel(
403+
pub fn write_with_channel_selection(
393404
&mut self,
394-
cb: impl FnOnce(&mut Value) -> Option<Channel>,
405+
cb: impl FnOnce(&mut Value) -> ChannelSelection<Channel>,
395406
) {
396407
let value = self.antenna.peek().station.value.write_unchecked();
397408
let mut guard = RadioGuard {
398409
channels: Vec::default(),
399410
antenna: self.antenna,
400411
value,
401412
};
402-
let channel = cb(&mut guard.value);
413+
let channel_selection = cb(&mut guard.value);
414+
let channel = match channel_selection {
415+
ChannelSelection::Current => Some(self.antenna.peek().channel.clone()),
416+
ChannelSelection::Silence => None,
417+
ChannelSelection::Select(c) => Some(c),
418+
};
403419
if let Some(channel) = channel {
404420
for channel in channel.derive_channel(&guard.value) {
405421
self.antenna.peek().station.notify_listeners(&channel)
406422
}
423+
self.antenna.peek().station.print_metrics();
407424
}
408425
}
409426

@@ -421,6 +438,35 @@ where
421438
}
422439
}
423440

441+
impl<Channel> Copy for ChannelSelection<Channel> where Channel: Copy {}
442+
443+
#[derive(Clone)]
444+
pub enum ChannelSelection<Channel> {
445+
/// Notify the channel associated with the used [Radio].
446+
Current,
447+
/// Notify a given [Channel].
448+
Select(Channel),
449+
/// No subscriber will be notified.
450+
Silence,
451+
}
452+
453+
impl<Channel> ChannelSelection<Channel> {
454+
/// Change to [ChannelSelection::Current]
455+
pub fn current(&mut self) {
456+
*self = Self::Current
457+
}
458+
459+
/// Change to [ChannelSelection::Select]
460+
pub fn select(&mut self, channel: Channel) {
461+
*self = Self::Select(channel)
462+
}
463+
464+
/// Change to [ChannelSelection::Silence]
465+
pub fn silence(&mut self) {
466+
*self = Self::Silence
467+
}
468+
}
469+
424470
/// Consume the state and subscribe using the given `channel`
425471
/// Any mutation using this radio will notify other subscribers to the same `channel`,
426472
/// unless you explicitely pass a custom channel using other methods as [`Radio::write_channel()`]
@@ -462,7 +508,7 @@ pub trait DataReducer {
462508
type Channel;
463509
type Action;
464510

465-
fn reduce(&mut self, action: Self::Action) -> Self::Channel;
511+
fn reduce(&mut self, action: Self::Action) -> ChannelSelection<Self::Channel>;
466512
}
467513

468514
pub trait RadioReducer {
@@ -480,7 +526,7 @@ impl<
480526
type Action = Action;
481527

482528
fn apply(&mut self, action: Action) {
483-
self.write_with_map_channel(|data| data.reduce(action));
529+
self.write_with_channel_selection(|data| data.reduce(action));
484530
}
485531
}
486532

@@ -492,7 +538,7 @@ pub trait DataAsyncReducer {
492538
async fn async_reduce(
493539
_radio: &mut Radio<Self, Self::Channel>,
494540
_action: Self::Action,
495-
) -> Self::Channel
541+
) -> ChannelSelection<Self::Channel>
496542
where
497543
Self::Channel: RadioChannel<Self>,
498544
Self: Sized;
@@ -521,7 +567,7 @@ impl<
521567
let mut radio = *self;
522568
spawn(async move {
523569
let channel = Data::async_reduce(&mut radio, action).await;
524-
radio.write_with_map_channel(|_| channel);
570+
radio.write_with_channel_selection(|_| channel);
525571
});
526572
}
527573
}

0 commit comments

Comments
 (0)