vexide_core/io/
stdio.rs

1use no_std_io::io::{self, Write};
2use vex_sdk::{vexSerialReadChar, vexSerialWriteBuffer, vexSerialWriteFree};
3
4use crate::sync::{Mutex, MutexGuard};
5
6pub(crate) const STDIO_CHANNEL: u32 = 1;
7
8static STDOUT: Mutex<StdoutRaw> = Mutex::new(StdoutRaw);
9static STDIN: Mutex<StdinRaw> = Mutex::new(StdinRaw);
10
11/// A handle to a raw instance of the serial output stream of this program.
12///
13/// This handle is not synchronized or buffered in any fashion. Constructed via
14/// the `stdout_raw` function.
15struct StdoutRaw;
16
17impl io::Write for StdoutRaw {
18    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
19        let written =
20            unsafe { vexSerialWriteBuffer(STDIO_CHANNEL, buf.as_ptr(), buf.len() as u32) };
21
22        if written == -1 {
23            return Err(io::Error::new(
24                io::ErrorKind::Other,
25                "Internal write error occurred.",
26            ));
27        }
28
29        self.flush()?;
30
31        Ok(written as usize)
32    }
33
34    fn flush(&mut self) -> io::Result<()> {
35        unsafe {
36            while vexSerialWriteFree(STDIO_CHANNEL) < Stdout::INTERNAL_BUFFER_SIZE as _ {
37                // Allow VEXos to flush the buffer by yielding.
38                vex_sdk::vexTasksRun();
39            }
40        }
41
42        Ok(())
43    }
44}
45
46/// A locked serial output stream.
47/// Only one of these can exist at a time and writes occur without waiting.
48pub struct StdoutLock<'a> {
49    inner: MutexGuard<'a, StdoutRaw>,
50}
51
52impl Write for StdoutLock<'_> {
53    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
54        self.inner.write(buf)
55    }
56
57    fn flush(&mut self) -> io::Result<()> {
58        self.inner.flush()
59    }
60}
61
62/// A handle to the serial output stream of this program.
63///
64/// An instance of this can be obtained using the [`stdout`] function.
65pub struct Stdout(());
66
67/// Constructs a handle to the serial output stream
68#[must_use]
69pub const fn stdout() -> Stdout {
70    Stdout(())
71}
72
73impl Stdout {
74    /// The size of the internal VEXOs FIFO serial out buffer.
75    pub const INTERNAL_BUFFER_SIZE: usize = 2048;
76
77    /// Locks the stdout for writing.
78    /// This function will wait until the lock is acquired.
79    pub async fn lock(&self) -> StdoutLock<'static> {
80        StdoutLock {
81            inner: STDOUT.lock().await,
82        }
83    }
84    /// Attempts to lock the stdout for writing.
85    ///
86    /// This function will return `None` if the lock could not be acquired.
87    pub fn try_lock(&self) -> Option<StdoutLock<'static>> {
88        Some(StdoutLock {
89            inner: STDOUT.try_lock()?,
90        })
91    }
92}
93
94struct StdinRaw;
95
96impl io::Read for StdinRaw {
97    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
98        let mut iterator = buf.iter_mut();
99
100        let mut byte: i32;
101        let mut written: usize = 0;
102
103        // Little but cursed, but hey it gets the job done...
104        while {
105            byte = unsafe { vexSerialReadChar(STDIO_CHANNEL) };
106            byte != -1
107        } {
108            if let Some(next) = iterator.next() {
109                *next = byte as u8;
110                written += 1;
111            } else {
112                return Ok(written);
113            }
114        }
115
116        Ok(written)
117    }
118}
119
120/// A locked serial input stream.
121/// Only one of these can exist at a time and reads occur without waiting.
122pub struct StdinLock<'a> {
123    inner: MutexGuard<'a, StdinRaw>,
124}
125
126impl io::Read for StdinLock<'_> {
127    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
128        self.inner.read(buf)
129    }
130}
131
132/// A handle to the serial input stream of this program.
133///
134/// An instance of this can be obtained using the [`stdin`] function.
135pub struct Stdin(());
136
137impl Stdin {
138    /// The size of the internal VEXos serial in buffer.
139    pub const STDIN_BUFFER_SIZE: usize = 4096;
140
141    /// Locks the stdin for reading.
142    /// This function is blocking and will wait until the lock is acquired.
143    pub async fn lock(&self) -> StdinLock<'static> {
144        StdinLock {
145            inner: STDIN.lock().await,
146        }
147    }
148    /// Attempts to lock the stdin for writing.
149    ///
150    /// This function will return `None` if the lock could not be acquired.
151    pub fn try_lock(&self) -> Option<StdinLock<'static>> {
152        Some(StdinLock {
153            inner: STDIN.try_lock()?,
154        })
155    }
156}
157
158/// Constructs a handle to the serial input stream.
159#[must_use]
160pub const fn stdin() -> Stdin {
161    Stdin(())
162}
163
164//////////////////////////////
165// Printing implementations //
166//////////////////////////////
167
168#[doc(hidden)]
169#[inline]
170pub fn __print(args: core::fmt::Arguments<'_>) {
171    use alloc::format;
172
173    use crate::io::Write;
174
175    // Panic on print if stdout is not available.
176    // While this is less than ideal,
177    // the alternative is either ingoring the print, a complete deadlock, or writing unsafely without locking.
178    let mut stdout = stdout()
179        .try_lock()
180        .expect("Attempted to print while stdout was already locked.");
181
182    // Format the arguments into a byte buffer before printing them.
183    // This lets us calculate if the bytes will overflow the buffer before printing them.
184    let formatted_bytes = format!("{args}").into_bytes();
185    let remaining_bytes_in_buffer = unsafe { vexSerialWriteFree(STDIO_CHANNEL) as usize };
186
187    // Write all of our data in chunks the size of the outgoing serial buffer.
188    // This ensures that writes of length greater than [`Stdout::INTERNAL_BUFFER_SIZE`] can still be written
189    // by flushing several times.
190    for chunk in formatted_bytes.chunks(Stdout::INTERNAL_BUFFER_SIZE) {
191        // If this chunk would overflow the buffer and cause a panic during `write_all`, wait for the buffer to clear.
192        // Not only does this prevent a panic (if the panic handler prints it could cause a recursive panic and immediately exit. **Very bad**),
193        // but it also allows prints and device comms inside of tight loops that have a print.
194        //
195        //TODO: In the future we may want to actually handle prints in tight loops
196        //TODO: in a way that makes it more clear that that loop is hogging executor time.
197        //TODO: In the past a lack of serial output was a tell that the executor was being hogged.
198        //TODO: Flushing the serial buffer now removes this tell, though.
199        if remaining_bytes_in_buffer < chunk.len() {
200            // Flushing is infallible, so we can unwrap here.
201            stdout.flush().unwrap();
202        }
203
204        // Re-use the buffer to write the formatted bytes to the serial output.
205        // This technically should never error because we have already flushed the buffer if it would overflow.
206        if let Err(e) = stdout.write_all(chunk) {
207            panic!("failed printing to stdout: {e}");
208        }
209    }
210}
211
212#[macro_export]
213/// Prints a message to the standard output and appends a newline.
214macro_rules! println {
215    () => {
216        $crate::print!("\n")
217    };
218    ($($arg:tt)*) => {
219		$crate::print!("{}\n", format_args!($($arg)*))
220	};
221}
222pub use println;
223
224#[macro_export]
225/// Prints a message to the standard output.
226macro_rules! print {
227    ($($arg:tt)*) => {{
228		$crate::io::__print(format_args!($($arg)*))
229    }};
230}
231pub use print;
232
233#[macro_export]
234#[expect(
235    edition_2024_expr_fragment_specifier,
236    reason = "OK for this macro to accept `const {}` expressions"
237)]
238/// Prints and returns the value of a given expression for quick and dirty debugging.
239macro_rules! dbg {
240    () => {
241        $crate::println!("[{}:{}]", file!(), line!())
242    };
243    ($val:expr $(,)?) => {
244        match $val {
245            tmp => {
246                $crate::println!("[{}:{}] {} = {:#?}", file!(), line!(), stringify!($val), &tmp);
247                tmp
248            }
249        }
250    };
251    ($($val:expr),+ $(,)?) => {
252        ($($crate::dbg!($val)),+,)
253    };
254}
255pub use dbg;