-
-
Save evanw/06a672db1897482eadfbbf37ebf9b9ec to your computer and use it in GitHub Desktop.
[package] | |
name = "multiplayer" | |
version = "0.0.1" | |
publish = false | |
[dependencies] | |
futures = "0.1.18" | |
hyper = "0.11.17" | |
tokio-core = "0.1.12" | |
tokio-signal = "0.1.4" | |
tokio-tungstenite = "0.5.1" | |
tungstenite = "0.5.3" |
extern crate futures; | |
extern crate hyper; | |
extern crate tokio_core; | |
extern crate tokio_tungstenite; | |
extern crate tungstenite; | |
use futures::{Future, Stream}; | |
fn main() { | |
let address = "127.0.0.1:8080".parse().unwrap(); | |
let mut core = tokio_core::reactor::Core::new().unwrap(); | |
let handle = core.handle(); | |
let server = tokio_core::net::TcpListener::bind(&address, &handle).unwrap(); | |
let socket_server = server.incoming().for_each(move |(stream, address)| { | |
let handle_clone = handle.clone(); | |
tokio_tungstenite::accept_async(stream).and_then(move |_stream| { | |
start_session(handle_clone).and_then(move |_session_data| { | |
println!("Connected to {}", address); | |
Ok(()) | |
}) | |
// This particular compile error happens when this line is missing | |
// .map_err(|e| tungstenite::Error::Io(e)) | |
}) | |
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)) | |
}); | |
core.run(socket_server).unwrap(); | |
} | |
fn start_session(handle: tokio_core::reactor::Handle) | |
-> Box<Future<Item=Vec<u8>, Error=std::io::Error>> | |
{ | |
let client = hyper::Client::new(&handle); | |
let url = "https://www.example.com/api/session".parse().unwrap(); | |
Box::new(client.get(url).and_then(|res| { | |
res.body().concat2().and_then(move |body| Ok(body.to_vec())) | |
}) | |
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))) | |
} |
$ cargo build | |
Compiling multiplayer v0.0.1 | |
error[E0271]: type mismatch resolving `<futures::AndThen<std::boxed::Box<futures | |
::Future<Error=std::io::Error, Item=std::vec::Vec<u8>>>, std::result::Result<(), | |
std::io::Error>, [closure@src/main.rs:17:44: 20:8 address:_]> as futures::IntoF | |
uture>::Error == tungstenite::Error` | |
--> src/main.rs:16:45 | |
| | |
16 | tokio_tungstenite::accept_async(stream).and_then(move |_stream| { | |
| ^^^^^^^^ expected struct `std:: | |
io::Error`, found enum `tungstenite::Error` | |
| | |
= note: expected type `std::io::Error` | |
found type `tungstenite::Error` | |
error[E0599]: no method named `map_err` found for type `futures::AndThen<tokio_t | |
ungstenite::AcceptAsync<tokio_core::net::TcpStream, tungstenite::handshake::serv | |
er::NoCallback>, futures::AndThen<std::boxed::Box<futures::Future<Error=std::io: | |
:Error, Item=std::vec::Vec<u8>>>, std::result::Result<(), std::io::Error>, [clos | |
ure@src/main.rs:17:44: 20:8 address:_]>, [closure@src/main.rs:16:54: 22:6 handle | |
_clone:_, address:_]>` in the current scope | |
--> src/main.rs:23:6 | |
| | |
23 | .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)) | |
| ^^^^^^^ | |
| | |
= note: the method `map_err` exists but the following trait bounds were not s | |
atisfied: | |
`futures::AndThen<tokio_tungstenite::AcceptAsync<tokio_core::net::Tcp | |
Stream, tungstenite::handshake::server::NoCallback>, futures::AndThen<std::boxed | |
::Box<futures::Future<Error=std::io::Error, Item=std::vec::Vec<u8>>>, std::resul | |
t::Result<(), std::io::Error>, [closure@src/main.rs:17:44: 20:8 address:_]>, [cl | |
osure@src/main.rs:16:54: 22:6 handle_clone:_, address:_]> : futures::Future` | |
`&mut futures::AndThen<tokio_tungstenite::AcceptAsync<tokio_core::net | |
::TcpStream, tungstenite::handshake::server::NoCallback>, futures::AndThen<std:: | |
boxed::Box<futures::Future<Error=std::io::Error, Item=std::vec::Vec<u8>>>, std:: | |
result::Result<(), std::io::Error>, [closure@src/main.rs:17:44: 20:8 address:_]> | |
, [closure@src/main.rs:16:54: 22:6 handle_clone:_, address:_]> : futures::Stream | |
` | |
`&mut futures::AndThen<tokio_tungstenite::AcceptAsync<tokio_core::net | |
::TcpStream, tungstenite::handshake::server::NoCallback>, futures::AndThen<std:: | |
boxed::Box<futures::Future<Error=std::io::Error, Item=std::vec::Vec<u8>>>, std:: | |
result::Result<(), std::io::Error>, [closure@src/main.rs:17:44: 20:8 address:_]> | |
, [closure@src/main.rs:16:54: 22:6 handle_clone:_, address:_]> : futures::Future | |
` | |
error: aborting due to 2 previous errors | |
error: Could not compile `multiplayer`. | |
To learn more, run the command again with --verbose. |
coming here from the blog post... why was go ruled out for your use case?
Go wasn't ruled out exactly in that I didn't do a formal comparison between the two before building it. I was experimenting with Rust at the time and had a working Rust implementation, then the servers started falling over from load and we needed to use it.
That said, the multiplayer server does have some unique requirements that Rust is a good fit for. The multiplayer service is extremely memory-intensive and garbage-collected languages inherently add additional overhead because of uncollected garbage. I also wanted to have an individual process per document for robustness and Rust's smaller binary size and lack of a runtime meant this strategy was actually practical. The multiplayer server is basically a real-time graph database and generics were nice to have for working with graph data structures. The code for the multiplayer server is also pretty small so Rust's bad compile times are tolerable.
those trait bound errors 🤢 allll too familiar