mantra_miner/
lib.rs

1//! mantra-miner is a library to make your software "recite" mantras while it runs.
2//!
3//! A spoof on crypto mining, this library spawns a thread and writes the specified mantras to a
4//! buffer. The user can select the mantras as well as an optional preparation and conclusion
5//! sections that mirror the format of traditional Buddhist ritual practices.
6//!
7//! The library was born for use with [Trane](https://github.com/trane-project/trane) as a way to
8//! allow its users to contribute back to the maintainer in a symbolic and non-monetary way. In
9//! Trane, the mantra of Tara Sarasvati - the manifestation of the Buddhist deity Tara associated
10//! with wisdom, music, learning, and the arts - is recited as the users run the software to
11//! acquire and practice complex skills.
12//!  
13//! Similar examples of using mantras in mediums other than the voice exist throughout Asia. Prayer
14//! wheels contain written mantras that are said to generate the same merit as reciting the amount
15//! of mantras inside every time the wheel completes a full rotation. With the use of microfilm, a
16//! prayer wheel can contain millions or more mantras. Another example consists of carving mantras
17//! in rock, which is common in the Himalayas and Tibet.
18//!
19//! For more information, check the project's README.
20
21use anyhow::Result;
22use parking_lot::Mutex;
23use std::{
24    io::{sink, BufWriter, Write},
25    sync::{
26        mpsc::{self, Receiver, Sender, TryRecvError},
27        Arc,
28    },
29    thread,
30    time::Duration,
31};
32
33/// A mantra to be "recited" by the miner. Since a computer can't actually recite a mantra, the term
34/// refers to the process of writing the mantra syllable by syllable to an output buffer.
35#[derive(Clone, Debug, Eq, PartialEq)]
36pub struct Mantra {
37    /// The syllables of the mantra. The mantra will be recited syllable by syllable.
38    pub syllables: Vec<String>,
39
40    /// The number of times to repeat the mantra. If it's `None`, the mantra will be repeated once.
41    pub repeats: Option<usize>,
42}
43
44impl Mantra {
45    /// Writes the mantra syllable by syllable to the given buffer.
46    fn recite<T>(&self, output: &mut BufWriter<T>, rate: Duration) -> Result<()>
47    where
48        T: Write,
49    {
50        let repeats = self.repeats.unwrap_or(1);
51        for _ in 0..repeats {
52            for syllable in &self.syllables {
53                output.write_all(syllable.as_bytes())?;
54                output.write_all("\n".as_bytes())?;
55                thread::sleep(rate);
56            }
57        }
58        Ok(())
59    }
60}
61
62/// The options used to configure the mantra miner.
63#[derive(Clone, Debug, Default, Eq, PartialEq)]
64pub struct Options {
65    /// Traditional Buddhist sadhanas, or ritual practices, consists of three parts. The first part,
66    /// preparation, consists of taking refuge in the Three Jewels and arising bodhicitta, the
67    /// desire to attain enlightenment for the benefit of all sentient beings.
68    pub preparation: Option<String>,
69
70    /// The number of times to repeat the preparation. If the value is `None`, it will be recited
71    /// once.
72    pub preparation_repeats: Option<usize>,
73
74    /// The second part of the sadhana is the main body of the practice, which for the purposes of
75    /// this mantra miner consists of reciting the given mantras.
76    pub mantras: Vec<Mantra>,
77
78    /// The third part of the sadhana is the conclusion, which traditionally consists of dedicating
79    /// the merit of the practice to all sentient beings.
80    pub conclusion: Option<String>,
81
82    /// The number of times to repeat the conclusion. If the value is `None`, it will be recited
83    /// once.
84    pub conclusion_repeats: Option<usize>,
85
86    /// The number of times to repeat the entire sadhana. If it's `None`, the sadhana will be
87    /// repeated indefinitely until the miner is stopped or the program is terminated.
88    pub repeats: Option<usize>,
89
90    /// The number of nanoseconds to wait between each syllable of a mantra or character of the
91    /// preparation or conclusion.
92    pub rate_ns: u64,
93}
94
95impl Options {
96    /// Returns whether the mantra miner should perform another iteration.
97    fn should_repeat(&self, count: usize) -> bool {
98        match self.repeats {
99            Some(repeats) => count < repeats,
100            None => true,
101        }
102    }
103}
104
105/// A mantra miner that spawns a thread and "recites" mantras by writing them to an output buffer.
106pub struct MantraMiner {
107    /// The options used to configure the mantra miner.
108    options: Options,
109
110    /// The number of times the mantra miner has completed a recitation of the entire sadhana.
111    count: Arc<Mutex<usize>>,
112
113    /// The channel used to signal the thread to stop.
114    stop_channel: Option<Sender<()>>,
115}
116
117impl MantraMiner {
118    /// Returns a new instance of `MantraMiner` with the given options.
119    pub fn new(options: Options) -> MantraMiner {
120        MantraMiner {
121            options,
122            count: Arc::new(Mutex::new(0)),
123            stop_channel: None,
124        }
125    }
126
127    /// Recites the optional string. Used to recite the preparation and conclusion.
128    fn recite_string<T>(
129        input: &Option<String>,
130        output: &mut BufWriter<T>,
131        rate: Duration,
132    ) -> Result<()>
133    where
134        T: Write,
135    {
136        match input {
137            None => Ok(()),
138            Some(input) => {
139                for c in input.chars() {
140                    let mut b = [0; 4];
141                    output.write_all(c.encode_utf8(&mut b).as_bytes())?;
142                    thread::sleep(rate);
143                }
144                Ok(())
145            }
146        }
147    }
148
149    /// Runs the mantra miner.
150    fn run(options: Options, total_count: Arc<Mutex<usize>>, rx: Receiver<()>) -> Result<()> {
151        let mut run_count = 0;
152        let mut output = BufWriter::new(sink());
153        let rate = Duration::from_nanos(options.rate_ns);
154
155        while options.should_repeat(run_count) {
156            match rx.try_recv() {
157                Ok(_) | Err(TryRecvError::Disconnected) => {
158                    break;
159                }
160                Err(TryRecvError::Empty) => {}
161            }
162
163            let preparation_repeats = options.preparation_repeats.unwrap_or(1);
164            for _ in 0..preparation_repeats {
165                Self::recite_string(&options.preparation, &mut output, rate)?;
166            }
167
168            for mantra in &options.mantras {
169                mantra.recite(&mut output, rate)?;
170            }
171
172            let conclusion_repeats = options.conclusion_repeats.unwrap_or(1);
173            for _ in 0..conclusion_repeats {
174                Self::recite_string(&options.conclusion, &mut output, rate)?;
175            }
176
177            *total_count.lock() += 1;
178            run_count += 1;
179        }
180        Ok(())
181    }
182
183    /// Spawns a new thread to run the mantra miner.
184    pub fn start(&mut self) -> Result<()> {
185        // Stop any existing thread.
186        self.stop()?;
187
188        let cloned_options = self.options.clone();
189        let cloned_count = self.count.clone();
190        let (tx, rx) = mpsc::channel();
191        thread::spawn(move || {
192            let _ = MantraMiner::run(cloned_options, cloned_count, rx);
193        });
194        self.stop_channel = Some(tx);
195        Ok(())
196    }
197
198    /// Stops the thread running the mantra miner.
199    pub fn stop(&mut self) -> Result<()> {
200        if let Some(tx) = self.stop_channel.take() {
201            let _ = tx.send(());
202        }
203        Ok(())
204    }
205
206    /// Returns the options used to configure this mantra miner.
207    pub fn options(&self) -> Options {
208        self.options.clone()
209    }
210
211    /// Returns the count of the mantra miner.
212    pub fn count(&self) -> usize {
213        *self.count.lock()
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use anyhow::Result;
220    use std::{
221        io::{BufWriter, Write},
222        thread,
223        time::Duration,
224    };
225
226    use crate::{Mantra, MantraMiner, Options};
227
228    const PREPARATION: &str = "I take refuge in the Three Jewels and arise bodhicitta.";
229    const DEDICATION: &str = "I dedicate the merit of this practice to all sentient beings.";
230
231    fn simple_mantra() -> Mantra {
232        Mantra {
233            syllables: vec![
234                "om".to_string(),
235                "ma".to_string(),
236                "ni".to_string(),
237                "pad".to_string(),
238                "me".to_string(),
239                "hum".to_string(),
240            ],
241            repeats: None,
242        }
243    }
244
245    fn repeated_mantra() -> Mantra {
246        Mantra {
247            syllables: vec!["hri".to_string()],
248            repeats: Some(108),
249        }
250    }
251
252    #[test]
253    fn should_repeat() {
254        let mut options = Options::default();
255
256        options.repeats = Some(10);
257        assert_eq!(options.should_repeat(5), true);
258        assert_eq!(options.should_repeat(10), false);
259        assert_eq!(options.should_repeat(50), false);
260
261        options.repeats = None;
262        assert_eq!(options.should_repeat(5), true);
263        assert_eq!(options.should_repeat(10), true);
264        assert_eq!(options.should_repeat(50), true);
265    }
266
267    #[test]
268    fn recite_string() -> Result<()> {
269        let rate = Duration::from_nanos(10);
270        let buffer = Vec::with_capacity(100);
271        let mut output = BufWriter::new(buffer);
272        MantraMiner::recite_string(&Some(PREPARATION.to_string()), &mut output, rate)?;
273        output.flush()?;
274        assert_eq!(output.get_ref(), PREPARATION.as_bytes());
275        Ok(())
276    }
277
278    #[test]
279    fn recite_mantra() -> Result<()> {
280        let mantra = simple_mantra();
281        let rate = Duration::from_nanos(10);
282        let buffer = Vec::with_capacity(100);
283        let mut output = BufWriter::new(buffer);
284        mantra.recite(&mut output, rate)?;
285        output.flush()?;
286        assert_eq!(output.get_ref(), "om\nma\nni\npad\nme\nhum\n".as_bytes());
287        Ok(())
288    }
289
290    #[test]
291    fn set_repeats() -> Result<()> {
292        let options = Options {
293            preparation: None,
294            preparation_repeats: None,
295            mantras: vec![simple_mantra()],
296            conclusion: None,
297            conclusion_repeats: None,
298            rate_ns: 1000,
299            repeats: Some(10),
300        };
301        let mut miner = MantraMiner::new(options);
302        miner.start()?;
303        thread::sleep(Duration::from_millis(10));
304        miner.stop()?;
305        assert_eq!(miner.count(), 10);
306        Ok(())
307    }
308
309    #[test]
310    fn indefinite_repeats() -> Result<()> {
311        let options = Options {
312            preparation: None,
313            preparation_repeats: None,
314            mantras: vec![simple_mantra()],
315            conclusion: None,
316            conclusion_repeats: None,
317            rate_ns: 1000,
318            repeats: None,
319        };
320        let mut miner = MantraMiner::new(options);
321        miner.start()?;
322        thread::sleep(Duration::from_millis(10));
323        miner.stop()?;
324        assert!(miner.count() > 10);
325        Ok(())
326    }
327
328    #[test]
329    fn with_preparation_and_conclusion() -> Result<()> {
330        let options = Options {
331            preparation: Some(PREPARATION.to_string()),
332            preparation_repeats: None,
333            mantras: vec![simple_mantra()],
334            conclusion: Some(DEDICATION.to_string()),
335            conclusion_repeats: None,
336            rate_ns: 1000,
337            repeats: Some(3),
338        };
339        let mut miner = MantraMiner::new(options);
340        miner.start()?;
341        thread::sleep(Duration::from_millis(100));
342        miner.stop()?;
343        assert_eq!(miner.count(), 3);
344        Ok(())
345    }
346
347    #[test]
348    fn with_repeated_preparation_and_conclusion() -> Result<()> {
349        let options = Options {
350            preparation: Some(PREPARATION.to_string()),
351            preparation_repeats: Some(3),
352            mantras: vec![simple_mantra()],
353            conclusion: Some(DEDICATION.to_string()),
354            conclusion_repeats: Some(3),
355            rate_ns: 1000,
356            repeats: Some(3),
357        };
358        let mut miner = MantraMiner::new(options);
359        miner.start()?;
360        thread::sleep(Duration::from_millis(100));
361        miner.stop()?;
362        assert_eq!(miner.count(), 3);
363        Ok(())
364    }
365
366    #[test]
367    fn using_repeated_mantra() -> Result<()> {
368        let options = Options {
369            preparation: Some(PREPARATION.to_string()),
370            preparation_repeats: None,
371            mantras: vec![repeated_mantra()],
372            conclusion: Some(DEDICATION.to_string()),
373            conclusion_repeats: None,
374            rate_ns: 1000,
375            repeats: Some(3),
376        };
377        let mut miner = MantraMiner::new(options);
378        miner.start()?;
379        thread::sleep(Duration::from_millis(100));
380        miner.stop()?;
381        assert_eq!(miner.count(), 3);
382        Ok(())
383    }
384
385    #[test]
386    fn options() {
387        let options = Options {
388            preparation: None,
389            preparation_repeats: None,
390            mantras: vec![repeated_mantra()],
391            conclusion: None,
392            conclusion_repeats: None,
393            rate_ns: 1000,
394            repeats: Some(3),
395        };
396        let options_clone = options.clone();
397        let miner = MantraMiner::new(options);
398        assert_eq!(miner.options(), options_clone);
399    }
400}