Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement basic server and test probe #12

Merged
merged 15 commits into from
Apr 24, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk

.vscode
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
[workspace]
members = [
"crates/dtrace-sys",
"crates/composer",
"crates/test_probe",
]

# Specify a subset of member crates that compile on all supported architectures.
goodhoko marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
11 changes: 11 additions & 0 deletions crates/composer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "composer"
goodhoko marked this conversation as resolved.
Show resolved Hide resolved
version = "0.1.0"
edition = "2021"

[dependencies]
bincode = "1.3.3"
color-eyre = "0.6.2"
eyre = "0.6.8"
rodio = { version = "0.17.1", features = ["symphonia-wav"] }
serde = { version = "1.0.160", features = ["serde_derive"] }
goodhoko marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

@strohel strohel Apr 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhat philosophical: cargo follows its own flavour of semver rules, so specifying bincode = "1.3.3" is equivalent to ^1.3.3, which pulls 1.3.x where x >= 3. So if we don't specifically need the patch version to be at least 3, we can as well say

bincode = "1.3"

That gives cargo dependency resolver a bit more leeway to deduplicate dependencies in some corner cases (think of some transitive dependency depending on bincode = "=1.3.2" or similar.

The downside is that it is more work to do this "properly" - if bincode 1.3.1 introdice a (backwards-compatible) feature that we use, we shoudl depend on 1.3.1 and not 1.3.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In tonari codebase, we typically do:

bincode = "1"
color-eyre = "0.6"
eyre = "0.6"

Only specify the major version past 1.0, and specify minor version before 1.0. This will give cargo the most flexibility assuming that our dependent crates follow the semvar compatibility requirements strictly. @strohel should we do that here as well?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@skywhale yeah that's basically what I intended to say (it convoluted way) in my above comment.

The only exception/caveat is that patch (x.y.Z+1) releases can introduce new features (if they're backwards compatible), and we can depend on the new features. So sometimes specifying all 3 components for dependencies is needed/correct.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was lazy here and just rolled with whatever cargo add ... produced.

Trying to not block this PR further, in 80d2029 I changed the versions to what @skywhale suggested.

I'll try to get to the philosophical aspect later. Now, I need to go out with the dog. .)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting back to the caveat @strohel mentions: I think that there is little reason to aim for the correct (i.e. minimal functional lower bound) version range from the get go. We'd have to essentially enumerate all the "features" we use and backtrack them to the minor version they were all included in. That'd be hard.

Also remember, that there's cargo.lock. If someone adds a dependency = "1.5" and makes our program work but in fact relies on a feature from 1.5.5 then the exact version pinned in cargo.lock must be 1.5.5+. I.e. the caveat can only arise when cargo.lock changes which isn't as often and it should be fairly easy to spot what happened in that case. At least as long as the version mismatch manifests visibly - which is something I'd expect in a language typed as strongly as rust.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@goodhoko agreed. Perhaps libraries (which do not ship Cargo.lock) that are popular should do this, but indeed that seems like an overkill for us.

29 changes: 29 additions & 0 deletions crates/composer/src/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use eyre::Result;
use serde::{Deserialize, Serialize};
use std::net::UdpSocket;

pub const DEFAULT_SERVER_ADDRESS: &str = "localhost:8888";

#[derive(Serialize, Deserialize, Debug)]
pub enum Event {
TestTick,
}

pub struct Client {
socket: UdpSocket,
}

impl Client {
pub fn new(server_address: Option<&str>) -> Result<Self> {
strohel marked this conversation as resolved.
Show resolved Hide resolved
let socket = UdpSocket::bind("localhost:0")?;
skywhale marked this conversation as resolved.
Show resolved Hide resolved
socket.connect(server_address.unwrap_or(DEFAULT_SERVER_ADDRESS))?;
strohel marked this conversation as resolved.
Show resolved Hide resolved

Ok(Self { socket })
}

pub fn send(&self, event: &Event) -> Result<()> {
let data = bincode::serialize(&event)?;
goodhoko marked this conversation as resolved.
Show resolved Hide resolved
self.socket.send(&data)?;
Ok(())
}
}
1 change: 1 addition & 0 deletions crates/composer/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod api;
strohel marked this conversation as resolved.
Show resolved Hide resolved
37 changes: 37 additions & 0 deletions crates/composer/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use composer::api::Event;
use composer::api::DEFAULT_SERVER_ADDRESS;
use eyre::Result;
use rodio::{source::Source, Decoder, OutputStream};
use std::fs::File;
use std::io::BufReader;
use std::net::UdpSocket;

fn main() -> Result<()> {
color_eyre::install()?;

let socket = UdpSocket::bind(DEFAULT_SERVER_ADDRESS)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still unsure about using Udp instead of Tcp for this.

If we were talking about sending audio packets or any other kind of short, time-limited, missable data I would definitely use Udp. However, if I understand where we are in terms of abstraction when it comes to events, an event could signify a single occurrence of something very bad. For example, a single event packet could mean reporting a catastrophic error that would result in a few seconds of screeching (I may be wrong on this though since I don't have the context of your in-person chats so correct me if I am!).

If I'm on the right ballpark about the potential importance of events though, we shouldn't be missing any, and I think the reliability of TCP would work better here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. As agreed personally, we'll continue here with UDP but should look into using reliable alternatives. I filled an issue for that #15

println!("Listening on {}", socket.local_addr()?);

let (_stream, stream_handle) = OutputStream::try_default()?;

loop {
if let Err(err) = {
// Should be more than enough.
let mut buf = [0; 1000];
goodhoko marked this conversation as resolved.
Show resolved Hide resolved
let (number_of_bytes, _) = socket.recv_from(&mut buf)?;

let event: Event = bincode::deserialize(&buf)?;
strohel marked this conversation as resolved.
Show resolved Hide resolved
println!("received a event ({number_of_bytes} bytes): {:?}", event);
goodhoko marked this conversation as resolved.
Show resolved Hide resolved

// FIXME: do the decoding and file reading outside the loop
let file = BufReader::new(File::open("src/sound_samples/click.wav")?);
let source = Decoder::new(file)?;
stream_handle.play_raw(source.convert_samples())?;

Ok::<(), eyre::Error>(())
skywhale marked this conversation as resolved.
Show resolved Hide resolved
} {
eprintln!("Could not process data-gram: {:?}", err);
goodhoko marked this conversation as resolved.
Show resolved Hide resolved
continue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nit: the continue isn't needed anymore.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed in 4b30780

}
}
}
Binary file added crates/composer/src/sound_samples/click.wav
Binary file not shown.
9 changes: 9 additions & 0 deletions crates/test_probe/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "test_probe"
version = "0.1.0"
edition = "2021"

[dependencies]
color-eyre = "0.6.2"
composer = { path = "../composer" }
eyre = "0.6.8"
33 changes: 33 additions & 0 deletions crates/test_probe/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use composer::api::{Client, Event};
use eyre::Result;
use std::{env, thread, time::Duration};

fn main() -> Result<()> {
color_eyre::install()?;

// Get server address, if given
let args: Vec<String> = env::args().collect();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: not super urgent, but I like using clap for all the argument handling so we get all the bells and whistles like usage strings, --help menu, etc :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do this in a sperate PR. I filled an issue for myself #16

let arg = args.get(1);
let server_address: Option<&str> = arg.map(|x: &String| x.as_str());
strohel marked this conversation as resolved.
Show resolved Hide resolved

let client = Client::new(server_address)?;
let event = Event::TestTick;
let delays = {
skywhale marked this conversation as resolved.
Show resolved Hide resolved
let min_delay = 5;
let max_delay = 250;
let step = 10;
// Tick fast, then slow, and then fast again
(min_delay..max_delay)
.step_by(step)
.chain((min_delay..max_delay).step_by(step).rev())
strohel marked this conversation as resolved.
Show resolved Hide resolved
};

loop {
for delay in delays.clone() {
strohel marked this conversation as resolved.
Show resolved Hide resolved
if let Err(err) = client.send(&event) {
eprintln!("Could not send event {:?}", err)
};
thread::sleep(Duration::from_millis(delay.try_into().unwrap()));
strohel marked this conversation as resolved.
Show resolved Hide resolved
}
}
}