Buttplug sex toy control library
1// Buttplug Rust Source Code File - See https://buttplug.io for more info.
2//
3// Copyright 2016-2024 Nonpolynomial Labs LLC. All rights reserved.
4//
5// Licensed under the BSD 3-Clause license. See LICENSE file in the project root
6// for full license information.
7
8use buttplug_core::util::{async_manager, sleep};
9use futures::Future;
10use std::{
11 sync::{
12 Arc,
13 atomic::{AtomicBool, Ordering},
14 },
15 time::Duration,
16};
17use tokio::{
18 select,
19 sync::{Notify, mpsc},
20};
21
22pub enum PingMessage {
23 Ping,
24 StartTimer,
25 StopTimer,
26 End,
27}
28
29async fn ping_timer(
30 max_ping_time: u32,
31 mut ping_msg_receiver: mpsc::Receiver<PingMessage>,
32 notifier: Arc<Notify>,
33 pinged_out_status: Arc<AtomicBool>,
34) {
35 let mut started = false;
36 let mut pinged = false;
37 loop {
38 select! {
39 _ = sleep(Duration::from_millis(max_ping_time.into())) => {
40 if started {
41 if !pinged {
42 notifier.notify_waiters();
43 pinged_out_status.store(true, Ordering::Relaxed);
44 return;
45 }
46 pinged = false;
47 }
48 }
49 msg = ping_msg_receiver.recv() => {
50 if msg.is_none() {
51 return;
52 }
53 match msg.expect("Already checked") {
54 PingMessage::StartTimer => started = true,
55 PingMessage::StopTimer => started = false,
56 PingMessage::Ping => pinged = true,
57 PingMessage::End => break,
58 }
59 }
60 };
61 }
62}
63
64pub struct PingTimer {
65 max_ping_time: u32,
66 ping_msg_sender: mpsc::Sender<PingMessage>,
67 ping_timeout_notifier: Arc<Notify>,
68 pinged_out: Arc<AtomicBool>,
69}
70
71impl Drop for PingTimer {
72 fn drop(&mut self) {
73 // This cannot block, otherwise it will throw in WASM contexts on
74 // destruction. We must use send(), not blocking_send().
75 let sender = self.ping_msg_sender.clone();
76 async_manager::spawn(async move {
77 if sender.send(PingMessage::End).await.is_err() {
78 debug!("Receiver does not exist, assuming ping timer event loop already dead.");
79 }
80 });
81 }
82}
83
84impl PingTimer {
85 pub fn new(max_ping_time: u32) -> Self {
86 let ping_timeout_notifier = Arc::new(Notify::new());
87 let (sender, receiver) = mpsc::channel(256);
88 let pinged_out = Arc::new(AtomicBool::new(false));
89 if max_ping_time > 0 {
90 let fut = ping_timer(
91 max_ping_time,
92 receiver,
93 ping_timeout_notifier.clone(),
94 pinged_out.clone(),
95 );
96 async_manager::spawn(fut);
97 }
98 Self {
99 max_ping_time,
100 ping_msg_sender: sender,
101 ping_timeout_notifier,
102 pinged_out,
103 }
104 }
105
106 pub fn ping_timeout_waiter(&self) -> impl Future<Output = ()> + use<> {
107 let notify = self.ping_timeout_notifier.clone();
108 async move {
109 notify.notified().await;
110 }
111 }
112
113 fn send_ping_msg(&self, msg: PingMessage) -> impl Future<Output = ()> + use<> {
114 let ping_msg_sender = self.ping_msg_sender.clone();
115 let max_ping_time = self.max_ping_time;
116 async move {
117 if max_ping_time == 0 {
118 return;
119 }
120 if ping_msg_sender.send(msg).await.is_err() {
121 error!("Cannot ping, no event loop available.");
122 }
123 }
124 }
125
126 pub fn start_ping_timer(&self) -> impl Future<Output = ()> + use<> {
127 // If we're starting the timer, clear our status.
128 self.pinged_out.store(false, Ordering::Relaxed);
129 self.send_ping_msg(PingMessage::StartTimer)
130 }
131
132 pub fn stop_ping_timer(&self) -> impl Future<Output = ()> + use<> {
133 self.send_ping_msg(PingMessage::StopTimer)
134 }
135
136 pub fn update_ping_time(&self) -> impl Future<Output = ()> + use<> {
137 self.send_ping_msg(PingMessage::Ping)
138 }
139
140 pub fn pinged_out(&self) -> bool {
141 self.pinged_out.load(Ordering::Relaxed)
142 }
143}