chore: Reorganize examples and interop crates (#180)
* chore: Reorganize examples and interop crates * fix interop tests
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
use futures::stream;
|
||||
use rand::rngs::ThreadRng;
|
||||
use rand::Rng;
|
||||
use route_guide::{Point, Rectangle, RouteNote};
|
||||
use std::error::Error;
|
||||
use std::time::Duration;
|
||||
use tokio::time::Instant;
|
||||
use tonic::transport::Channel;
|
||||
use tonic::Request;
|
||||
|
||||
pub mod route_guide {
|
||||
tonic::include_proto!("routeguide");
|
||||
}
|
||||
|
||||
use route_guide::routeguide_client::RouteGuideClient;
|
||||
|
||||
async fn print_features(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> {
|
||||
let rectangle = Rectangle {
|
||||
lo: Some(Point {
|
||||
latitude: 400000000,
|
||||
longitude: -750000000,
|
||||
}),
|
||||
hi: Some(Point {
|
||||
latitude: 420000000,
|
||||
longitude: -730000000,
|
||||
}),
|
||||
};
|
||||
|
||||
let mut stream = client
|
||||
.list_features(Request::new(rectangle))
|
||||
.await?
|
||||
.into_inner();
|
||||
|
||||
while let Some(feature) = stream.message().await? {
|
||||
println!("NOTE = {:?}", feature);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_record_route(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> {
|
||||
let mut rng = rand::thread_rng();
|
||||
let point_count: i32 = rng.gen_range(2, 100);
|
||||
|
||||
let mut points = vec![];
|
||||
for _ in 0..=point_count {
|
||||
points.push(random_point(&mut rng))
|
||||
}
|
||||
|
||||
println!("Traversing {} points", points.len());
|
||||
let request = Request::new(stream::iter(points));
|
||||
|
||||
match client.record_route(request).await {
|
||||
Ok(response) => println!("SUMMARY: {:?}", response.into_inner()),
|
||||
Err(e) => println!("something went wrong: {:?}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_route_chat(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> {
|
||||
let start = Instant::now();
|
||||
|
||||
let outbound = async_stream::stream! {
|
||||
let mut interval = tokio::time::interval(Duration::from_secs(1));
|
||||
|
||||
while let time = interval.tick().await {
|
||||
let elapsed = time.duration_since(start);
|
||||
let note = RouteNote {
|
||||
location: Some(Point {
|
||||
latitude: 409146138 + elapsed.as_secs() as i32,
|
||||
longitude: -746188906,
|
||||
}),
|
||||
message: format!("at {:?}", elapsed),
|
||||
};
|
||||
|
||||
yield note;
|
||||
}
|
||||
};
|
||||
|
||||
let request = Request::new(outbound);
|
||||
let response = client.route_chat(request).await?;
|
||||
let mut inbound = response.into_inner();
|
||||
|
||||
while let Some(note) = inbound.message().await? {
|
||||
println!("NOTE = {:?}", note);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut client = RouteGuideClient::connect("http://[::1]:10000").await?;
|
||||
|
||||
println!("*** SIMPLE RPC ***");
|
||||
let response = client
|
||||
.get_feature(Request::new(Point {
|
||||
latitude: 409146138,
|
||||
longitude: -746188906,
|
||||
}))
|
||||
.await?;
|
||||
println!("RESPONSE = {:?}", response);
|
||||
|
||||
println!("\n*** SERVER STREAMING ***");
|
||||
print_features(&mut client).await?;
|
||||
|
||||
println!("\n*** CLIENT STREAMING ***");
|
||||
run_record_route(&mut client).await?;
|
||||
|
||||
println!("\n*** BIDIRECTIONAL STREAMING ***");
|
||||
run_route_chat(&mut client).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn random_point(rng: &mut ThreadRng) -> Point {
|
||||
let latitude = (rng.gen_range(0, 180) - 90) * 10_000_000;
|
||||
let longitude = (rng.gen_range(0, 360) - 180) * 10_000_000;
|
||||
Point {
|
||||
latitude,
|
||||
longitude,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
use serde::Deserialize;
|
||||
use std::fs::File;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Feature {
|
||||
location: Location,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Location {
|
||||
latitude: i32,
|
||||
longitude: i32,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn load() -> Vec<crate::routeguide::Feature> {
|
||||
let file =
|
||||
File::open("tonic-examples/data/route_guide_db.json").expect("failed to open data file");
|
||||
|
||||
let decoded: Vec<Feature> =
|
||||
serde_json::from_reader(&file).expect("failed to deserialize features");
|
||||
|
||||
decoded
|
||||
.into_iter()
|
||||
.map(|feature| crate::routeguide::Feature {
|
||||
name: feature.name,
|
||||
location: Some(crate::routeguide::Point {
|
||||
longitude: feature.location.longitude,
|
||||
latitude: feature.location.latitude,
|
||||
}),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
mod data;
|
||||
|
||||
use futures::{Stream, StreamExt};
|
||||
use std::collections::HashMap;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use tokio::sync::mpsc;
|
||||
use tonic::transport::Server;
|
||||
use tonic::{Request, Response, Status};
|
||||
|
||||
pub mod routeguide {
|
||||
tonic::include_proto!("routeguide");
|
||||
}
|
||||
|
||||
use routeguide::{routeguide_server, Feature, Point, Rectangle, RouteNote, RouteSummary};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RouteGuide {
|
||||
features: Arc<Vec<Feature>>,
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl routeguide_server::RouteGuide for RouteGuide {
|
||||
async fn get_feature(&self, request: Request<Point>) -> Result<Response<Feature>, Status> {
|
||||
println!("GetFeature = {:?}", request);
|
||||
|
||||
for feature in &self.features[..] {
|
||||
if feature.location.as_ref() == Some(request.get_ref()) {
|
||||
return Ok(Response::new(feature.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
let response = Response::new(Feature {
|
||||
name: "".to_string(),
|
||||
location: None,
|
||||
});
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
type ListFeaturesStream = mpsc::Receiver<Result<Feature, Status>>;
|
||||
|
||||
async fn list_features(
|
||||
&self,
|
||||
request: Request<Rectangle>,
|
||||
) -> Result<Response<Self::ListFeaturesStream>, Status> {
|
||||
println!("ListFeatures = {:?}", request);
|
||||
|
||||
let (mut tx, rx) = mpsc::channel(4);
|
||||
let features = self.features.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
for feature in &features[..] {
|
||||
if in_range(feature.location.as_ref().unwrap(), request.get_ref()) {
|
||||
println!(" => send {:?}", feature);
|
||||
tx.send(Ok(feature.clone())).await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
println!(" /// done sending");
|
||||
});
|
||||
|
||||
Ok(Response::new(rx))
|
||||
}
|
||||
|
||||
async fn record_route(
|
||||
&self,
|
||||
request: Request<tonic::Streaming<Point>>,
|
||||
) -> Result<Response<RouteSummary>, Status> {
|
||||
println!("RecordRoute");
|
||||
|
||||
let stream = request.into_inner();
|
||||
|
||||
// Pin the inbound stream to the stack so that we can call next on it
|
||||
futures::pin_mut!(stream);
|
||||
|
||||
let mut summary = RouteSummary::default();
|
||||
let mut last_point = None;
|
||||
let now = Instant::now();
|
||||
|
||||
while let Some(point) = stream.next().await {
|
||||
let point = point?;
|
||||
|
||||
println!(" ==> Point = {:?}", point);
|
||||
|
||||
// Increment the point count
|
||||
summary.point_count += 1;
|
||||
|
||||
// Find features
|
||||
for feature in &self.features[..] {
|
||||
if feature.location.as_ref() == Some(&point) {
|
||||
summary.feature_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the distance
|
||||
if let Some(ref last_point) = last_point {
|
||||
summary.distance += calc_distance(last_point, &point);
|
||||
}
|
||||
|
||||
last_point = Some(point);
|
||||
}
|
||||
|
||||
summary.elapsed_time = now.elapsed().as_secs() as i32;
|
||||
|
||||
Ok(Response::new(summary))
|
||||
}
|
||||
|
||||
type RouteChatStream =
|
||||
Pin<Box<dyn Stream<Item = Result<RouteNote, Status>> + Send + Sync + 'static>>;
|
||||
|
||||
async fn route_chat(
|
||||
&self,
|
||||
request: Request<tonic::Streaming<RouteNote>>,
|
||||
) -> Result<Response<Self::RouteChatStream>, Status> {
|
||||
println!("RouteChat");
|
||||
|
||||
let mut notes = HashMap::new();
|
||||
let stream = request.into_inner();
|
||||
|
||||
let output = async_stream::try_stream! {
|
||||
futures::pin_mut!(stream);
|
||||
|
||||
while let Some(note) = stream.next().await {
|
||||
let note = note?;
|
||||
|
||||
let location = note.location.clone().unwrap();
|
||||
|
||||
let location_notes = notes.entry(location).or_insert(vec![]);
|
||||
location_notes.push(note);
|
||||
|
||||
for note in location_notes {
|
||||
yield note.clone();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Response::new(Box::pin(output)
|
||||
as Pin<
|
||||
Box<dyn Stream<Item = Result<RouteNote, Status>> + Send + Sync + 'static>,
|
||||
>))
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let addr = "[::1]:10000".parse().unwrap();
|
||||
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
let route_guide = RouteGuide {
|
||||
features: Arc::new(data::load()),
|
||||
};
|
||||
|
||||
let svc = routeguide_server::RouteGuideServer::new(route_guide);
|
||||
|
||||
Server::builder().add_service(svc).serve(addr).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Implement hash for Point
|
||||
impl Hash for Point {
|
||||
fn hash<H>(&self, state: &mut H)
|
||||
where
|
||||
H: Hasher,
|
||||
{
|
||||
self.latitude.hash(state);
|
||||
self.longitude.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Point {}
|
||||
|
||||
fn in_range(point: &Point, rect: &Rectangle) -> bool {
|
||||
use std::cmp;
|
||||
|
||||
let lo = rect.lo.as_ref().unwrap();
|
||||
let hi = rect.hi.as_ref().unwrap();
|
||||
|
||||
let left = cmp::min(lo.longitude, hi.longitude);
|
||||
let right = cmp::max(lo.longitude, hi.longitude);
|
||||
let top = cmp::max(lo.latitude, hi.latitude);
|
||||
let bottom = cmp::min(lo.latitude, hi.latitude);
|
||||
|
||||
point.longitude >= left
|
||||
&& point.longitude <= right
|
||||
&& point.latitude >= bottom
|
||||
&& point.latitude <= top
|
||||
}
|
||||
|
||||
/// Calculates the distance between two points using the "haversine" formula.
|
||||
/// This code was taken from http://www.movable-type.co.uk/scripts/latlong.html.
|
||||
fn calc_distance(p1: &Point, p2: &Point) -> i32 {
|
||||
const CORD_FACTOR: f64 = 1e7;
|
||||
const R: f64 = 6371000.0; // meters
|
||||
|
||||
let lat1 = p1.latitude as f64 / CORD_FACTOR;
|
||||
let lat2 = p2.latitude as f64 / CORD_FACTOR;
|
||||
let lng1 = p1.longitude as f64 / CORD_FACTOR;
|
||||
let lng2 = p2.longitude as f64 / CORD_FACTOR;
|
||||
|
||||
let lat_rad1 = lat1.to_radians();
|
||||
let lat_rad2 = lat2.to_radians();
|
||||
|
||||
let delta_lat = (lat2 - lat1).to_radians();
|
||||
let delta_lng = (lng2 - lng1).to_radians();
|
||||
|
||||
let a = (delta_lat / 2f64).sin() * (delta_lat / 2f64).sin()
|
||||
+ (lat_rad1).cos() * (lat_rad2).cos() * (delta_lng / 2f64).sin() * (delta_lng / 2f64).sin();
|
||||
|
||||
let c = 2f64 * a.sqrt().atan2((1f64 - a).sqrt());
|
||||
|
||||
(R * c) as i32
|
||||
}
|
||||
Reference in New Issue
Block a user