1use std::{
2 collections::{HashMap, HashSet},
3 hash::Hash,
4 ops::{Deref, DerefMut},
5 sync::{Arc, Mutex},
6};
7
8use dioxus::prelude::*;
9use dioxus_core::ReactiveContext;
10
11#[cfg(feature = "tracing")]
12pub trait RadioChannel<T>: 'static + PartialEq + Eq + Clone + Hash + std::fmt::Debug + Ord {
13 fn derive_channel(self, _radio: &T) -> Vec<Self> {
14 vec![self]
15 }
16}
17
18#[cfg(not(feature = "tracing"))]
19pub trait RadioChannel<T>: 'static + PartialEq + Eq + Clone + Hash {
20 fn derive_channel(self, _radio: &T) -> Vec<Self> {
21 vec![self]
22 }
23}
24
25pub struct RadioStation<Value, Channel>
27where
28 Channel: RadioChannel<Value>,
29 Value: 'static,
30{
31 value: Signal<Value>,
32 listeners: Signal<HashMap<Channel, Arc<Mutex<HashSet<ReactiveContext>>>>>,
33}
34
35impl<Value, Channel> Clone for RadioStation<Value, Channel>
36where
37 Channel: RadioChannel<Value>,
38{
39 fn clone(&self) -> Self {
40 *self
41 }
42}
43
44impl<Value, Channel> Copy for RadioStation<Value, Channel> where Channel: RadioChannel<Value> {}
45
46impl<Value, Channel> RadioStation<Value, Channel>
47where
48 Channel: RadioChannel<Value>,
49{
50 pub(crate) fn is_listening(
51 &self,
52 channel: &Channel,
53 reactive_context: &ReactiveContext,
54 ) -> bool {
55 let listeners = self.listeners.peek_unchecked();
56 listeners
57 .get(channel)
58 .map(|contexts| contexts.lock().unwrap().contains(reactive_context))
59 .unwrap_or_default()
60 }
61
62 pub(crate) fn listen(&self, channel: Channel, reactive_context: ReactiveContext) {
63 let mut listeners = self.listeners.write_unchecked();
64 let listeners = listeners.entry(channel).or_default();
65 reactive_context.subscribe(listeners.clone());
66 }
67
68 pub(crate) fn notify_listeners(&self, channel: &Channel) {
69 let listeners = self.listeners.write_unchecked();
70
71 #[cfg(feature = "tracing")]
72 tracing::info!("Notifying {channel:?}");
73
74 for (listener_channel, listeners) in listeners.iter() {
75 if listener_channel == channel {
76 for reactive_context in listeners.lock().unwrap().iter() {
77 reactive_context.mark_dirty();
78 }
79 }
80 }
81 }
82
83 pub fn read(&self) -> ReadableRef<'_, Signal<Value>> {
91 self.value.read()
92 }
93
94 pub fn peek(&self) -> ReadableRef<'_, Signal<Value>> {
102 self.value.peek()
103 }
104
105 pub fn cleanup(&self) {
106 let mut listeners = self.listeners.write_unchecked();
107
108 listeners.retain(|_, listeners| !listeners.lock().unwrap().is_empty());
110
111 #[cfg(feature = "tracing")]
112 {
113 use itertools::Itertools;
114 use tracing::{info, span, Level};
115
116 let mut channels_subscribers = HashMap::<&Channel, usize>::new();
117
118 for (channel, listeners) in listeners.iter() {
119 *channels_subscribers.entry(&channel).or_default() =
120 listeners.lock().unwrap().len();
121 }
122
123 let span = span!(Level::DEBUG, "Radio Station Metrics");
124 let _enter = span.enter();
125
126 for (channel, count) in channels_subscribers.iter().sorted() {
127 info!(" {count} subscribers for {channel:?}")
128 }
129 }
130 }
131}
132
133pub struct RadioAntenna<Value, Channel>
134where
135 Channel: RadioChannel<Value>,
136 Value: 'static,
137{
138 pub(crate) channel: Channel,
139 station: RadioStation<Value, Channel>,
140}
141
142impl<Value, Channel> RadioAntenna<Value, Channel>
143where
144 Channel: RadioChannel<Value>,
145{
146 pub(crate) fn new(
147 channel: Channel,
148 station: RadioStation<Value, Channel>,
149 ) -> RadioAntenna<Value, Channel> {
150 RadioAntenna { channel, station }
151 }
152}
153
154pub struct RadioGuard<Value, Channel>
155where
156 Channel: RadioChannel<Value>,
157 Value: 'static,
158{
159 antenna: Signal<RadioAntenna<Value, Channel>>,
160 channels: Vec<Channel>,
161 value: WritableRef<'static, Signal<Value>>,
162}
163
164impl<Value, Channel> Drop for RadioGuard<Value, Channel>
165where
166 Channel: RadioChannel<Value>,
167{
168 fn drop(&mut self) {
169 for channel in &mut self.channels {
170 self.antenna.peek().station.notify_listeners(channel)
171 }
172 if !self.channels.is_empty() {
173 self.antenna.peek().station.cleanup();
174 }
175 }
176}
177
178impl<Value, Channel> Deref for RadioGuard<Value, Channel>
179where
180 Channel: RadioChannel<Value>,
181{
182 type Target = WritableRef<'static, Signal<Value>>;
183
184 fn deref(&self) -> &Self::Target {
185 &self.value
186 }
187}
188
189impl<Value, Channel> DerefMut for RadioGuard<Value, Channel>
190where
191 Channel: RadioChannel<Value>,
192{
193 fn deref_mut(&mut self) -> &mut WritableRef<'static, Signal<Value>> {
194 &mut self.value
195 }
196}
197
198pub struct Radio<Value, Channel>
200where
201 Channel: RadioChannel<Value>,
202 Value: 'static,
203{
204 antenna: Signal<RadioAntenna<Value, Channel>>,
205}
206
207impl<Value, Channel> Clone for Radio<Value, Channel>
208where
209 Channel: RadioChannel<Value>,
210{
211 fn clone(&self) -> Self {
212 *self
213 }
214}
215impl<Value, Channel> Copy for Radio<Value, Channel> where Channel: RadioChannel<Value> {}
216
217impl<Value, Channel> PartialEq for Radio<Value, Channel>
218where
219 Channel: RadioChannel<Value>,
220{
221 fn eq(&self, other: &Self) -> bool {
222 self.antenna == other.antenna
223 }
224}
225
226impl<Value, Channel> Radio<Value, Channel>
227where
228 Channel: RadioChannel<Value>,
229{
230 pub(crate) fn new(antenna: Signal<RadioAntenna<Value, Channel>>) -> Radio<Value, Channel> {
231 Radio { antenna }
232 }
233
234 pub(crate) fn subscribe_if_not(&self) {
235 if let Some(rc) = ReactiveContext::current() {
236 let antenna = &self.antenna.write_unchecked();
237 let channel = antenna.channel.clone();
238 let is_listening = antenna.station.is_listening(&channel, &rc);
239
240 if !is_listening {
242 antenna.station.listen(channel, rc);
243 }
244 }
245 }
246
247 pub fn read(&self) -> ReadableRef<'_, Signal<Value>> {
255 self.subscribe_if_not();
256 self.antenna.peek().station.value.peek_unchecked()
257 }
258
259 pub fn with(&self, cb: impl FnOnce(ReadableRef<Signal<Value>>)) {
269 self.subscribe_if_not();
270 let value = self.antenna.peek().station.value;
271 let borrow = value.read();
272 cb(borrow);
273 }
274
275 pub fn write(&mut self) -> RadioGuard<Value, Channel> {
283 let value = self.antenna.peek().station.value.write_unchecked();
284 let channel = self.antenna.peek().channel.clone();
285 RadioGuard {
286 channels: channel.derive_channel(&*value),
287 antenna: self.antenna,
288 value,
289 }
290 }
291
292 pub fn write_with(&mut self, cb: impl FnOnce(RadioGuard<Value, Channel>)) {
302 let guard = self.write();
303 cb(guard);
304 }
305
306 pub fn write_channel(&mut self, channel: Channel) -> RadioGuard<Value, Channel> {
313 let value = self.antenna.peek().station.value.write_unchecked();
314 RadioGuard {
315 channels: channel.derive_channel(&*value),
316 antenna: self.antenna,
317 value,
318 }
319 }
320
321 pub fn write_channel_with(
331 &mut self,
332 channel: Channel,
333 cb: impl FnOnce(RadioGuard<Value, Channel>),
334 ) {
335 let guard = self.write_channel(channel);
336 cb(guard);
337 }
338
339 pub fn write_with_channel_selection(
354 &mut self,
355 cb: impl FnOnce(&mut Value) -> ChannelSelection<Channel>,
356 ) -> ChannelSelection<Channel> {
357 let value = self.antenna.peek().station.value.write_unchecked();
358 let mut guard = RadioGuard {
359 channels: Vec::default(),
360 antenna: self.antenna,
361 value,
362 };
363 let channel_selection = cb(&mut guard.value);
364 let channel = match channel_selection.clone() {
365 ChannelSelection::Current => Some(self.antenna.peek().channel.clone()),
366 ChannelSelection::Silence => None,
367 ChannelSelection::Select(c) => Some(c),
368 };
369 if let Some(channel) = channel {
370 for channel in channel.derive_channel(&guard.value) {
371 self.antenna.peek().station.notify_listeners(&channel)
372 }
373 self.antenna.peek().station.cleanup();
374 }
375
376 channel_selection
377 }
378
379 pub fn write_silently(&mut self) -> RadioGuard<Value, Channel> {
384 let value = self.antenna.peek().station.value.write_unchecked();
385 RadioGuard {
386 channels: Vec::default(),
387 antenna: self.antenna,
388 value,
389 }
390 }
391}
392
393impl<Channel> Copy for ChannelSelection<Channel> where Channel: Copy {}
394
395#[derive(Clone)]
396pub enum ChannelSelection<Channel> {
397 Current,
399 Select(Channel),
401 Silence,
403}
404
405impl<Channel> ChannelSelection<Channel> {
406 pub fn current(&mut self) {
408 *self = Self::Current
409 }
410
411 pub fn select(&mut self, channel: Channel) {
413 *self = Self::Select(channel)
414 }
415
416 pub fn silence(&mut self) {
418 *self = Self::Silence
419 }
420
421 pub fn is_current(&self) -> bool {
423 matches!(self, Self::Current)
424 }
425
426 pub fn is_select(&self) -> Option<&Channel> {
428 match self {
429 Self::Select(channel) => Some(channel),
430 _ => None,
431 }
432 }
433
434 pub fn is_silence(&self) -> bool {
436 matches!(self, Self::Silence)
437 }
438}
439
440pub fn use_radio<Value, Channel>(channel: Channel) -> Radio<Value, Channel>
444where
445 Channel: RadioChannel<Value>,
446 Value: 'static,
447{
448 let station = use_context::<RadioStation<Value, Channel>>();
449
450 let mut radio = use_hook(|| {
451 let antenna = RadioAntenna::new(channel.clone(), station);
452 Radio::new(Signal::new(antenna))
453 });
454
455 if radio.antenna.peek().channel != channel {
456 radio.antenna.write().channel = channel;
457 }
458
459 radio
460}
461
462pub fn use_init_radio_station<Value, Channel>(
463 init_value: impl FnOnce() -> Value,
464) -> RadioStation<Value, Channel>
465where
466 Channel: RadioChannel<Value>,
467 Value: 'static,
468{
469 use_context_provider(|| RadioStation {
470 value: Signal::new(init_value()),
471 listeners: Signal::default(),
472 })
473}
474
475pub fn use_radio_station<Value, Channel>() -> RadioStation<Value, Channel>
476where
477 Channel: RadioChannel<Value>,
478 Value: 'static,
479{
480 use_context::<RadioStation<Value, Channel>>()
481}
482
483pub trait DataReducer {
484 type Channel;
485 type Action;
486
487 fn reduce(&mut self, action: Self::Action) -> ChannelSelection<Self::Channel>;
488}
489
490pub trait RadioReducer {
491 type Action;
492 type Channel;
493
494 fn apply(&mut self, action: Self::Action) -> ChannelSelection<Self::Channel>;
495}
496
497impl<
498 Data: DataReducer<Channel = Channel, Action = Action>,
499 Channel: RadioChannel<Data>,
500 Action,
501 > RadioReducer for Radio<Data, Channel>
502{
503 type Action = Action;
504 type Channel = Channel;
505
506 fn apply(&mut self, action: Action) -> ChannelSelection<Channel> {
507 self.write_with_channel_selection(|data| data.reduce(action))
508 }
509}
510
511pub trait DataAsyncReducer {
512 type Channel;
513 type Action;
514
515 #[allow(async_fn_in_trait)]
516 async fn async_reduce(
517 _radio: &mut Radio<Self, Self::Channel>,
518 _action: Self::Action,
519 ) -> ChannelSelection<Self::Channel>
520 where
521 Self::Channel: RadioChannel<Self>,
522 Self: Sized;
523}
524
525pub trait RadioAsyncReducer {
526 type Action;
527
528 fn async_apply(&mut self, _action: Self::Action)
529 where
530 Self::Action: 'static;
531}
532
533impl<
534 Data: DataAsyncReducer<Channel = Channel, Action = Action>,
535 Channel: RadioChannel<Data>,
536 Action,
537 > RadioAsyncReducer for Radio<Data, Channel>
538{
539 type Action = Action;
540
541 fn async_apply(&mut self, action: Self::Action)
542 where
543 Self::Action: 'static,
544 {
545 let mut radio = *self;
546 spawn(async move {
547 let channel = Data::async_reduce(&mut radio, action).await;
548 radio.write_with_channel_selection(|_| channel);
549 });
550 }
551}