rust_rocket/client.rs
1//! Main client code, including the [`RocketClient`] type.
2//!
3//! # Usage
4//!
5//! The usual workflow with the low level client API can be described in a few steps:
6//!
7//! 0. Install a rocket tracker ([original Qt editor](https://github.com/rocket/rocket)
8//! or [emoon's OpenGL-based editor](https://github.com/emoon/rocket))
9//! 1. Connect the [`RocketClient`] to the running tracker by calling [`RocketClient::new`]
10//! 2. Create tracks with [`get_track_mut`](RocketClient::get_track_mut)
11//! 3. In your main loop, poll for updates from the Rocket tracker by calling [`poll_events`](RocketClient::poll_events).
12//! 4. Keep the tracker in sync by calling [`set_row`](RocketClient::set_row) (see tips below)
13//! 5. Get values from the tracks with [`Track::get_value`]
14//!
15//! See the linked documentation items and the examples-directory for more examples.
16//!
17//! # Tips
18//!
19//! The library is agnostic to your source of time. In a typical production, some kind of music player library
20//! determines the time for everything else, including the rocket tracks.
21//! It's recommended that you treat every 8th row as a beat of music instead of real time in seconds.
22//!
23//! ```rust,no_run
24//! # use std::time::Duration;
25//! # use rust_rocket::client::{RocketClient, Event, Error};
26//! struct MusicPlayer; // Your music player, not included in this crate
27//! # impl MusicPlayer {
28//! # fn new() -> Self { Self }
29//! # fn get_time(&self) -> Duration { Duration::ZERO }
30//! # fn seek(&self, _to: Duration) {}
31//! # fn pause(&self, _state: bool) {}
32//! # }
33//!
34//! const ROWS_PER_BEAT: f32 = 8.;
35//! const BEATS_PER_MIN: f32 = 123.; // This depends on your choice of music track
36//! const SECS_PER_MIN: f32 = 60.;
37//!
38//! fn time_to_row(time: Duration) -> f32 {
39//! let secs = time.as_secs_f32();
40//! let beats = secs * BEATS_PER_MIN / SECS_PER_MIN;
41//! beats * ROWS_PER_BEAT
42//! }
43//!
44//! fn row_to_time(row: u32) -> Duration {
45//! let beats = row as f32 / ROWS_PER_BEAT;
46//! let secs = beats / (BEATS_PER_MIN / SECS_PER_MIN);
47//! Duration::from_secs_f32(secs)
48//! }
49//!
50//! fn get(rocket: &mut RocketClient, track: &str, row: f32) -> f32 {
51//! let track = rocket.get_track_mut(track).unwrap();
52//! track.get_value(row)
53//! }
54//!
55//! fn main() -> Result<(), Error> {
56//! let mut music = MusicPlayer::new(/* ... */);
57//! let mut rocket = RocketClient::new()?;
58//!
59//! // Create window, render resources etc...
60//!
61//! loop {
62//! // Get current frame's time
63//! let time = music.get_time();
64//! let row = time_to_row(time);
65//!
66//! // Keep the rocket tracker in sync
67//! // It's recommended to combine consecutive seek events to a single seek.
68//! // This ensures the smoothest scrolling in editor.
69//! let mut seek = None;
70//! while let Some(event) = rocket.poll_events()? {
71//! match event {
72//! Event::SetRow(to) => seek = Some(to),
73//! Event::Pause(state) => music.pause(state),
74//! Event::SaveTracks => {/* Call save_tracks and serialize to a file */}
75//! }
76//! }
77//! // It's recommended to call set_time only when the not seeking.
78//! match seek {
79//! Some(to_row) => {
80//! music.seek(row_to_time(to_row));
81//! continue;
82//! }
83//! None => rocket.set_row(row as u32)?,
84//! }
85//!
86//! // Render frame and read values with Track's get_value function
87//! let _ = get(&mut rocket, "track0", row);
88//! }
89//! }
90//! ```
91use crate::interpolation::*;
92use crate::track::*;
93use crate::Tracks;
94
95use byteorder::ByteOrder;
96use byteorder::{BigEndian, ReadBytesExt};
97use std::hint::unreachable_unchecked;
98use std::{
99 convert::TryFrom,
100 io::{self, Cursor, Read, Write},
101 net::{TcpStream, ToSocketAddrs},
102};
103use thiserror::Error;
104
105// Rocket protocol commands
106const CLIENT_GREETING: &[u8] = b"hello, synctracker!";
107const SERVER_GREETING: &[u8] = b"hello, demo!";
108
109const SET_KEY: u8 = 0;
110const DELETE_KEY: u8 = 1;
111const GET_TRACK: u8 = 2;
112const SET_ROW: u8 = 3;
113const PAUSE: u8 = 4;
114const SAVE_TRACKS: u8 = 5;
115
116const SET_KEY_LEN: usize = 4 + 4 + 4 + 1;
117const DELETE_KEY_LEN: usize = 4 + 4;
118const GET_TRACK_LEN: usize = 4; // Does not account for name length
119const SET_ROW_LEN: usize = 4;
120const PAUSE_LEN: usize = 1;
121
122const MAX_COMMAND_LEN: usize = SET_KEY_LEN;
123
124/// The `Error` Type. This is the main error type.
125#[derive(Debug, Error)]
126pub enum Error {
127 /// Failure to connect to a rocket tracker. This can happen if the tracker is not running, the
128 /// address isn't correct or other network-related reasons.
129 #[error("Failed to establish a TCP connection with the Rocket tracker")]
130 Connect(#[source] std::io::Error),
131 /// Failure to transmit or receive greetings with the tracker
132 #[error("Handshake with the Rocket tracker failed")]
133 Handshake(#[source] std::io::Error),
134 /// Handshake was performed but the the received greeting wasn't correct
135 #[error("The Rocket tracker greeting {0:?} wasn't correct")]
136 HandshakeGreetingMismatch([u8; SERVER_GREETING.len()]),
137 /// Error from [`TcpStream::set_nonblocking`]
138 #[error("Cannot set Rocket's TCP connection to nonblocking mode")]
139 SetNonblocking(#[source] std::io::Error),
140 /// Network IO error during operation
141 #[error("Rocket tracker disconnected")]
142 IOError(#[source] std::io::Error),
143}
144
145#[derive(Debug)]
146enum ClientState {
147 New,
148 Incomplete(usize),
149 Complete,
150}
151
152/// The `Event` Type. These are the various events from the tracker.
153#[derive(Debug, Copy, Clone)]
154pub enum Event {
155 /// The tracker changes row.
156 SetRow(u32),
157 /// The tracker pauses or unpauses.
158 Pause(bool),
159 /// The tracker asks us to save our track data.
160 /// You may want to call [`RocketClient::save_tracks`] after receiving this event.
161 SaveTracks,
162}
163
164#[derive(Debug)]
165enum ReceiveResult {
166 Some(Event),
167 None,
168 Incomplete,
169}
170
171/// The `RocketClient` type. This contains the connected socket and other fields.
172#[derive(Debug)]
173pub struct RocketClient {
174 stream: TcpStream,
175 state: ClientState,
176 cmd: Vec<u8>,
177 tracks: Vec<Track>,
178}
179
180impl RocketClient {
181 /// Construct a new RocketClient.
182 ///
183 /// This constructs a new Rocket client and connects to localhost on port 1338.
184 ///
185 /// # Errors
186 ///
187 /// [`Error::Connect`] if connection cannot be established, or [`Error::Handshake`]
188 /// if the handshake fails.
189 ///
190 /// # Examples
191 ///
192 /// ```rust,no_run
193 /// # use rust_rocket::RocketClient;
194 /// let mut rocket = RocketClient::new()?;
195 /// # Ok::<(), rust_rocket::client::Error>(())
196 /// ```
197 pub fn new() -> Result<Self, Error> {
198 Self::connect(("localhost", 1338))
199 }
200
201 /// Construct a new RocketClient.
202 ///
203 /// This constructs a new Rocket client and connects to a specified host and port.
204 ///
205 /// # Errors
206 ///
207 /// [`Error::Connect`] if connection cannot be established, or [`Error::Handshake`]
208 /// if the handshake fails.
209 ///
210 /// # Examples
211 ///
212 /// ```rust,no_run
213 /// # use rust_rocket::RocketClient;
214 /// let mut rocket = RocketClient::connect(("localhost", 1338))?;
215 /// # Ok::<(), rust_rocket::client::Error>(())
216 /// ```
217 pub fn connect(addr: impl ToSocketAddrs) -> Result<Self, Error> {
218 let stream = TcpStream::connect(addr).map_err(Error::Connect)?;
219
220 let mut rocket = Self {
221 stream,
222 state: ClientState::New,
223 cmd: Vec::new(),
224 tracks: Vec::new(),
225 };
226
227 rocket.handshake()?;
228
229 rocket
230 .stream
231 .set_nonblocking(true)
232 .map_err(Error::SetNonblocking)?;
233
234 Ok(rocket)
235 }
236
237 /// Get track by name.
238 ///
239 /// If the track does not yet exist it will be created.
240 ///
241 /// # Errors
242 ///
243 /// This method can return an [`Error::IOError`] if Rocket tracker disconnects.
244 ///
245 /// # Panics
246 ///
247 /// Will panic if `name`'s length exceeds [`u32::MAX`].
248 ///
249 /// # Examples
250 ///
251 /// ```rust,no_run
252 /// # use rust_rocket::RocketClient;
253 /// # let mut rocket = RocketClient::new()?;
254 /// let track = rocket.get_track_mut("namespace:track")?;
255 /// track.get_value(3.5);
256 /// # Ok::<(), rust_rocket::client::Error>(())
257 /// ```
258 pub fn get_track_mut(&mut self, name: &str) -> Result<&mut Track, Error> {
259 if let Some((i, _)) = self
260 .tracks
261 .iter()
262 .enumerate()
263 .find(|(_, t)| t.get_name() == name)
264 {
265 Ok(&mut self.tracks[i])
266 } else {
267 // Send GET_TRACK message
268 let mut buf = [GET_TRACK; 1 + GET_TRACK_LEN];
269 let name_len = u32::try_from(name.len()).expect("Track name too long");
270 BigEndian::write_u32(&mut buf[1..][..GET_TRACK_LEN], name_len);
271 self.stream.write_all(&buf).map_err(Error::IOError)?;
272 self.stream
273 .write_all(name.as_bytes())
274 .map_err(Error::IOError)?;
275
276 self.tracks.push(Track::new(name));
277 // SAFETY: tracks cannot be empty, because it was pushed to on the previous line
278 let track = self
279 .tracks
280 .last_mut()
281 .unwrap_or_else(|| unsafe { unreachable_unchecked() });
282 Ok(track)
283 }
284 }
285
286 /// Get track by name.
287 ///
288 /// You should use [`get_track_mut`](RocketClient::get_track_mut) to create a track.
289 pub fn get_track(&self, name: &str) -> Option<&Track> {
290 self.tracks.iter().find(|t| t.get_name() == name)
291 }
292
293 /// Get a snapshot of the tracks in the session.
294 ///
295 /// The returned [`Tracks`] can be dumped to a file in any [supported format](crate#features).
296 /// The counterpart to this function is [`RocketPlayer::new`](crate::RocketPlayer::new),
297 /// which loads tracks for playback.
298 ///
299 /// # Example
300 ///
301 /// ```rust,no_run
302 /// # use rust_rocket::RocketClient;
303 /// # use std::fs::OpenOptions;
304 /// let mut rocket = RocketClient::new()?;
305 ///
306 /// // Create tracks, call poll_events, etc...
307 ///
308 /// // Open a file for writing
309 /// let mut file = OpenOptions::new()
310 /// .write(true)
311 /// .create(true)
312 /// .truncate(true)
313 /// .open("tracks.bin")
314 /// .expect("Failed to open tracks.bin for writing");
315 ///
316 /// // Save a snapshot of the client to a file for playback in release builds
317 /// let tracks = rocket.save_tracks();
318 /// # #[cfg(feature = "bincode")]
319 /// bincode::encode_into_std_write(tracks, &mut file, bincode::config::standard())
320 /// .expect("Failed to encode tracks.bin");
321 /// # Ok::<(), rust_rocket::client::Error>(())
322 /// ```
323 pub fn save_tracks(&self) -> &Tracks {
324 &self.tracks
325 }
326
327 /// Send a SetRow message.
328 ///
329 /// This changes the current row on the tracker side.
330 ///
331 /// # Errors
332 ///
333 /// This method can return an [`Error::IOError`] if Rocket tracker disconnects.
334 pub fn set_row(&mut self, row: u32) -> Result<(), Error> {
335 // Send SET_ROW message
336 let mut buf = [SET_ROW; 1 + SET_ROW_LEN];
337 BigEndian::write_u32(&mut buf[1..][..SET_ROW_LEN], row);
338 self.stream.write_all(&buf).map_err(Error::IOError)
339 }
340
341 /// Poll for new events from the tracker.
342 ///
343 /// This polls from events from the tracker.
344 /// You should call this fairly often your main loop.
345 /// It is recommended to keep calling this as long as your receive `Some(Event)`.
346 ///
347 /// # Errors
348 ///
349 /// This method can return an [`Error::IOError`] if the rocket tracker disconnects.
350 ///
351 /// # Examples
352 ///
353 /// ```rust,no_run
354 /// # use rust_rocket::RocketClient;
355 /// # let mut rocket = RocketClient::new()?;
356 /// while let Some(event) = rocket.poll_events()? {
357 /// match event {
358 /// // Do something with the various events.
359 /// _ => (),
360 /// }
361 /// }
362 /// # Ok::<(), rust_rocket::client::Error>(())
363 /// ```
364 pub fn poll_events(&mut self) -> Result<Option<Event>, Error> {
365 loop {
366 match self.poll_event()? {
367 ReceiveResult::None => return Ok(None),
368 ReceiveResult::Incomplete => { /* Keep reading */ }
369 ReceiveResult::Some(event) => return Ok(Some(event)),
370 }
371 }
372 }
373
374 fn poll_event(&mut self) -> Result<ReceiveResult, Error> {
375 match self.state {
376 ClientState::New => self.poll_event_new(),
377 ClientState::Incomplete(bytes) => self.poll_event_incomplete(bytes),
378 ClientState::Complete => Ok(self.process_event().unwrap_or_else(|_| unreachable!())),
379 }
380 }
381
382 fn poll_event_new(&mut self) -> Result<ReceiveResult, Error> {
383 let mut buf = [0; 1];
384 match self.stream.read_exact(&mut buf) {
385 Ok(()) => {
386 self.cmd.extend_from_slice(&buf);
387 match self.cmd[0] {
388 SET_KEY => self.state = ClientState::Incomplete(SET_KEY_LEN),
389 DELETE_KEY => self.state = ClientState::Incomplete(DELETE_KEY_LEN),
390 SET_ROW => self.state = ClientState::Incomplete(SET_ROW_LEN),
391 PAUSE => self.state = ClientState::Incomplete(PAUSE_LEN),
392 SAVE_TRACKS => self.state = ClientState::Complete,
393 _ => self.state = ClientState::Complete, // Error / Unknown
394 }
395 Ok(ReceiveResult::Incomplete)
396 }
397 Err(e) => match e.kind() {
398 std::io::ErrorKind::WouldBlock => Ok(ReceiveResult::None),
399 _ => Err(Error::IOError(e)),
400 },
401 }
402 }
403
404 fn poll_event_incomplete(&mut self, bytes: usize) -> Result<ReceiveResult, Error> {
405 let mut buf = [0; MAX_COMMAND_LEN];
406 match self.stream.read(&mut buf[..bytes]) {
407 Ok(bytes_read) => {
408 self.cmd.extend_from_slice(&buf[..bytes_read]);
409 if bytes - bytes_read > 0 {
410 self.state = ClientState::Incomplete(bytes - bytes_read);
411 } else {
412 self.state = ClientState::Complete;
413 }
414 Ok(ReceiveResult::Incomplete)
415 }
416 Err(e) => match e.kind() {
417 std::io::ErrorKind::WouldBlock => Ok(ReceiveResult::None),
418 _ => Err(Error::IOError(e)),
419 },
420 }
421 }
422
423 // This function should never fail if [`poll_event_new`] and [`poll_event_incomplete`] are correct
424 fn process_event(&mut self) -> Result<ReceiveResult, io::Error> {
425 let mut result = ReceiveResult::None;
426
427 let mut cursor = Cursor::new(&self.cmd);
428 let cmd = cursor.read_u8()?;
429 match cmd {
430 SET_KEY => {
431 // usize::try_from(u32) will only be None if usize is smaller, and
432 // more than usize::MAX tracks are in use. That isn't possible because
433 // I'd imagine Vec::push and everything else will panic first.
434 // If you're running this on a microcontroller, I'd love to see it!
435 let index = usize::try_from(cursor.read_u32::<BigEndian>()?).unwrap();
436 let track = &mut self.tracks[index];
437 let row = cursor.read_u32::<BigEndian>()?;
438 let value = cursor.read_f32::<BigEndian>()?;
439 let interpolation = Interpolation::from(cursor.read_u8()?);
440 let key = Key::new(row, value, interpolation);
441
442 track.set_key(key);
443 }
444 DELETE_KEY => {
445 let index = usize::try_from(cursor.read_u32::<BigEndian>()?).unwrap();
446 let track = &mut self.tracks[index];
447 let row = cursor.read_u32::<BigEndian>()?;
448
449 track.delete_key(row);
450 }
451 SET_ROW => {
452 let row = cursor.read_u32::<BigEndian>()?;
453 result = ReceiveResult::Some(Event::SetRow(row));
454 }
455 PAUSE => {
456 let flag = cursor.read_u8()? == 1;
457 result = ReceiveResult::Some(Event::Pause(flag));
458 }
459 SAVE_TRACKS => {
460 result = ReceiveResult::Some(Event::SaveTracks);
461 }
462 _ => eprintln!("rocket: Unknown command: {:?}", cmd),
463 }
464
465 self.cmd.clear();
466 self.state = ClientState::New;
467
468 Ok(result)
469 }
470
471 fn handshake(&mut self) -> Result<(), Error> {
472 self.stream
473 .write_all(CLIENT_GREETING)
474 .map_err(Error::Handshake)?;
475
476 let mut buf = [0; SERVER_GREETING.len()];
477 self.stream.read_exact(&mut buf).map_err(Error::Handshake)?;
478
479 if buf == SERVER_GREETING {
480 Ok(())
481 } else {
482 Err(Error::HandshakeGreetingMismatch(buf))
483 }
484 }
485}