1use std::{
2 collections::{HashMap, HashSet},
3 hash::Hash,
4 ops::{Deref, DerefMut},
5 sync::{Arc, Mutex},
6};
7
8use dioxus_lib::prelude::*;
9mod warnings {
10 pub use warnings::Warning;
11}
12pub use warnings::Warning;
13
14#[cfg(feature = "tracing")]
15pub trait RadioChannel<T>: 'static + PartialEq + Eq + Clone + Hash + std::fmt::Debug + Ord {
16 fn derive_channel(self, _radio: &T) -> Vec<Self> {
17 vec![self]
18 }
19}
20
21#[cfg(not(feature = "tracing"))]
22pub trait RadioChannel<T>: 'static + PartialEq + Eq + Clone + Hash {
23 fn derive_channel(self, _radio: &T) -> Vec<Self> {
24 vec![self]
25 }
26}
27
28pub struct RadioStation<Value, Channel>
30where
31 Channel: RadioChannel<Value>,
32 Value: 'static,
33{
34 value: Signal<Value>,
35 listeners: Signal<HashMap<Channel, Arc<Mutex<HashSet<ReactiveContext>>>>>,
36}
37
38impl<Value, Channel> Clone for RadioStation<Value, Channel>
39where
40 Channel: RadioChannel<Value>,
41{
42 fn clone(&self) -> Self {
43 *self
44 }
45}
46
47impl<Value, Channel> Copy for RadioStation<Value, Channel> where Channel: RadioChannel<Value> {}
48
49impl<Value, Channel> RadioStation<Value, Channel>
50where
51 Channel: RadioChannel<Value>,
52{
53 pub(crate) fn is_listening(
54 &self,
55 channel: &Channel,
56 reactive_context: &ReactiveContext,
57 ) -> bool {
58 let listeners = self.listeners.peek_unchecked();
59 listeners
60 .get(channel)
61 .map(|contexts| contexts.lock().unwrap().contains(reactive_context))
62 .unwrap_or_default()
63 }
64
65 pub(crate) fn listen(&self, channel: Channel, reactive_context: ReactiveContext) {
66 dioxus_lib::prelude::warnings::signal_write_in_component_body::allow(|| {
67 let mut listeners = self.listeners.write_unchecked();
68 let listeners = listeners.entry(channel).or_default();
69 reactive_context.subscribe(listeners.clone());
70 });
71 }
72
73 pub(crate) fn notify_listeners(&self, channel: &Channel) {
74 let listeners = self.listeners.write_unchecked();
75
76 #[cfg(feature = "tracing")]
77 tracing::info!("Notifying {channel:?}");
78
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 }
84 }
85 }
86 }
87
88 pub fn read(&self) -> ReadableRef<Signal<Value>> {
96 self.value.read()
97 }
98
99 pub fn peek(&self) -> ReadableRef<Signal<Value>> {
107 self.value.peek()
108 }
109
110 pub fn cleanup(&self) {
111 let mut listeners = self.listeners.write_unchecked();
112
113 listeners.retain(|_, listeners| !listeners.lock().unwrap().is_empty());
115
116 #[cfg(feature = "tracing")]
117 {
118 use itertools::Itertools;
119 use tracing::{info, span, Level};
120
121 let mut channels_subscribers = HashMap::<&Channel, usize>::new();
122
123 for (channel, listeners) in listeners.iter() {
124 *channels_subscribers.entry(&channel).or_default() =
125 listeners.lock().unwrap().len();
126 }
127
128 let span = span!(Level::DEBUG, "Radio Station Metrics");
129 let _enter = span.enter();
130
131 for (channel, count) in channels_subscribers.iter().sorted() {
132 info!(" {count} subscribers for {channel:?}")
133 }
134 }
135 }
136}
137
138pub struct RadioAntenna<Value, Channel>
139where
140 Channel: RadioChannel<Value>,
141 Value: 'static,
142{
143 pub(crate) channel: Channel,
144 station: RadioStation<Value, Channel>,
145}
146
147impl<Value, Channel> RadioAntenna<Value, Channel>
148where
149 Channel: RadioChannel<Value>,
150{
151 pub(crate) fn new(
152 channel: Channel,
153 station: RadioStation<Value, Channel>,
154 ) -> RadioAntenna<Value, Channel> {
155 RadioAntenna { channel, station }
156 }
157}
158
159pub struct RadioGuard<Value, Channel>
160where
161 Channel: RadioChannel<Value>,
162 Value: 'static,
163{
164 antenna: Signal<RadioAntenna<Value, Channel>>,
165 channels: Vec<Channel>,
166 value: WritableRef<'static, Signal<Value>>,
167}
168
169impl<Value, Channel> Drop for RadioGuard<Value, Channel>
170where
171 Channel: RadioChannel<Value>,
172{
173 fn drop(&mut self) {
174 for channel in &mut self.channels {
175 self.antenna.peek().station.notify_listeners(channel)
176 }
177 if !self.channels.is_empty() {
178 self.antenna.peek().station.cleanup();
179 }
180 }
181}
182
183impl<Value, Channel> Deref for RadioGuard<Value, Channel>
184where
185 Channel: RadioChannel<Value>,
186{
187 type Target = WritableRef<'static, Signal<Value>>;
188
189 fn deref(&self) -> &Self::Target {
190 &self.value
191 }
192}
193
194impl<Value, Channel> DerefMut for RadioGuard<Value, Channel>
195where
196 Channel: RadioChannel<Value>,
197{
198 fn deref_mut(&mut self) -> &mut WritableRef<'static, Signal<Value>> {
199 &mut self.value
200 }
201}
202
203pub struct Radio<Value, Channel>
205where
206 Channel: RadioChannel<Value>,
207 Value: 'static,
208{
209 antenna: Signal<RadioAntenna<Value, Channel>>,
210}
211
212impl<Value, Channel> Clone for Radio<Value, Channel>
213where
214 Channel: RadioChannel<Value>,
215{
216 fn clone(&self) -> Self {
217 *self
218 }
219}
220impl<Value, Channel> Copy for Radio<Value, Channel> where Channel: RadioChannel<Value> {}
221
222impl<Value, Channel> PartialEq for Radio<Value, Channel>
223where
224 Channel: RadioChannel<Value>,
225{
226 fn eq(&self, other: &Self) -> bool {
227 self.antenna == other.antenna
228 }
229}
230
231impl<Value, Channel> Radio<Value, Channel>
232where
233 Channel: RadioChannel<Value>,
234{
235 pub(crate) fn new(antenna: Signal<RadioAntenna<Value, Channel>>) -> Radio<Value, Channel> {
236 Radio { antenna }
237 }
238
239 pub(crate) fn subscribe_if_not(&self) {
240 dioxus_lib::prelude::warnings::signal_write_in_component_body::allow(|| {
241 if let Some(rc) = ReactiveContext::current() {
242 let antenna = &self.antenna.write_unchecked();
243 let channel = antenna.channel.clone();
244 let is_listening = antenna.station.is_listening(&channel, &rc);
245
246 if !is_listening {
248 antenna.station.listen(channel, rc);
249 }
250 }
251 });
252 }
253
254 pub fn read(&self) -> ReadableRef<Signal<Value>> {
262 self.subscribe_if_not();
263 self.antenna.peek().station.value.peek_unchecked()
264 }
265
266 pub fn with(&self, cb: impl FnOnce(ReadableRef<Signal<Value>>)) {
276 self.subscribe_if_not();
277 let value = self.antenna.peek().station.value;
278 let borrow = value.read();
279 cb(borrow);
280 }
281
282 pub fn write(&mut self) -> RadioGuard<Value, Channel> {
290 let value = self.antenna.peek().station.value.write_unchecked();
291 let channel = self.antenna.peek().channel.clone();
292 RadioGuard {
293 channels: channel.derive_channel(&*value),
294 antenna: self.antenna,
295 value,
296 }
297 }
298
299 pub fn write_with(&mut self, cb: impl FnOnce(RadioGuard<Value, Channel>)) {
309 let guard = self.write();
310 cb(guard);
311 }
312
313 pub fn write_channel(&mut self, channel: Channel) -> RadioGuard<Value, Channel> {
320 let value = self.antenna.peek().station.value.write_unchecked();
321 RadioGuard {
322 channels: channel.derive_channel(&*value),
323 antenna: self.antenna,
324 value,
325 }
326 }
327
328 pub fn write_channel_with(
338 &mut self,
339 channel: Channel,
340 cb: impl FnOnce(RadioGuard<Value, Channel>),
341 ) {
342 let guard = self.write_channel(channel);
343 cb(guard);
344 }
345
346 pub fn write_with_channel_selection(
361 &mut self,
362 cb: impl FnOnce(&mut Value) -> ChannelSelection<Channel>,
363 ) -> ChannelSelection<Channel> {
364 let value = self.antenna.peek().station.value.write_unchecked();
365 let mut guard = RadioGuard {
366 channels: Vec::default(),
367 antenna: self.antenna,
368 value,
369 };
370 let channel_selection = cb(&mut guard.value);
371 let channel = match channel_selection.clone() {
372 ChannelSelection::Current => Some(self.antenna.peek().channel.clone()),
373 ChannelSelection::Silence => None,
374 ChannelSelection::Select(c) => Some(c),
375 };
376 if let Some(channel) = channel {
377 for channel in channel.derive_channel(&guard.value) {
378 self.antenna.peek().station.notify_listeners(&channel)
379 }
380 self.antenna.peek().station.cleanup();
381 }
382
383 channel_selection
384 }
385
386 pub fn write_silently(&mut self) -> RadioGuard<Value, Channel> {
391 let value = self.antenna.peek().station.value.write_unchecked();
392 RadioGuard {
393 channels: Vec::default(),
394 antenna: self.antenna,
395 value,
396 }
397 }
398}
399
400impl<Channel> Copy for ChannelSelection<Channel> where Channel: Copy {}
401
402#[derive(Clone)]
403pub enum ChannelSelection<Channel> {
404 Current,
406 Select(Channel),
408 Silence,
410}
411
412impl<Channel> ChannelSelection<Channel> {
413 pub fn current(&mut self) {
415 *self = Self::Current
416 }
417
418 pub fn select(&mut self, channel: Channel) {
420 *self = Self::Select(channel)
421 }
422
423 pub fn silence(&mut self) {
425 *self = Self::Silence
426 }
427
428 pub fn is_current(&self) -> bool {
430 matches!(self, Self::Current)
431 }
432
433 pub fn is_select(&self) -> Option<&Channel> {
435 match self {
436 Self::Select(channel) => Some(channel),
437 _ => None,
438 }
439 }
440
441 pub fn is_silence(&self) -> bool {
443 matches!(self, Self::Silence)
444 }
445}
446
447pub fn use_radio<Value, Channel>(channel: Channel) -> Radio<Value, Channel>
451where
452 Channel: RadioChannel<Value>,
453 Value: 'static,
454{
455 let station = use_context::<RadioStation<Value, Channel>>();
456
457 let mut radio = use_hook(|| {
458 let antenna = RadioAntenna::new(channel.clone(), station);
459 Radio::new(Signal::new(antenna))
460 });
461
462 if radio.antenna.peek().channel != channel {
463 radio.antenna.write().channel = channel;
464 }
465
466 radio
467}
468
469pub fn use_init_radio_station<Value, Channel>(
470 init_value: impl FnOnce() -> Value,
471) -> RadioStation<Value, Channel>
472where
473 Channel: RadioChannel<Value>,
474 Value: 'static,
475{
476 use_context_provider(|| RadioStation {
477 value: Signal::new(init_value()),
478 listeners: Signal::default(),
479 })
480}
481
482pub fn use_radio_station<Value, Channel>() -> RadioStation<Value, Channel>
483where
484 Channel: RadioChannel<Value>,
485 Value: 'static,
486{
487 use_context::<RadioStation<Value, Channel>>()
488}
489
490pub trait DataReducer {
491 type Channel;
492 type Action;
493
494 fn reduce(&mut self, action: Self::Action) -> ChannelSelection<Self::Channel>;
495}
496
497pub trait RadioReducer {
498 type Action;
499 type Channel;
500
501 fn apply(&mut self, action: Self::Action) -> ChannelSelection<Self::Channel>;
502}
503
504impl<
505 Data: DataReducer<Channel = Channel, Action = Action>,
506 Channel: RadioChannel<Data>,
507 Action,
508 > RadioReducer for Radio<Data, Channel>
509{
510 type Action = Action;
511 type Channel = Channel;
512
513 fn apply(&mut self, action: Action) -> ChannelSelection<Channel> {
514 self.write_with_channel_selection(|data| data.reduce(action))
515 }
516}
517
518pub trait DataAsyncReducer {
519 type Channel;
520 type Action;
521
522 #[allow(async_fn_in_trait)]
523 async fn async_reduce(
524 _radio: &mut Radio<Self, Self::Channel>,
525 _action: Self::Action,
526 ) -> ChannelSelection<Self::Channel>
527 where
528 Self::Channel: RadioChannel<Self>,
529 Self: Sized;
530}
531
532pub trait RadioAsyncReducer {
533 type Action;
534
535 fn async_apply(&mut self, _action: Self::Action)
536 where
537 Self::Action: 'static;
538}
539
540impl<
541 Data: DataAsyncReducer<Channel = Channel, Action = Action>,
542 Channel: RadioChannel<Data>,
543 Action,
544 > RadioAsyncReducer for Radio<Data, Channel>
545{
546 type Action = Action;
547
548 fn async_apply(&mut self, action: Self::Action)
549 where
550 Self::Action: 'static,
551 {
552 let mut radio = *self;
553 spawn(async move {
554 let channel = Data::async_reduce(&mut radio, action).await;
555 radio.write_with_channel_selection(|_| channel);
556 });
557 }
558}