Reputation:
I'm creating a basic Minecraft server in Rust. As soon as a player logs in to my server, I need to send a KeepAlive
packet every 20 seconds to ensure the client is still connected.
I thought this would be solved by spawning a new thread that sends the packet after sleeping 20 seconds. (line 35). Unfortunately, the Server
type doesn't implement Copy
.
Error
error[E0382]: borrow of moved value: `server`
--> src/main.rs:22:9
|
20 | let mut server = Server::from_tcpstream(stream).unwrap();
| ---------- move occurs because `server` has type `ozelot::server::Server`, which does not implement the `Copy` trait
21 | 'listenloop: loop {
22 | server.update_inbuf().unwrap();
| ^^^^^^ value borrowed here after move
...
35 | thread::spawn(move || {
| ------- value moved into closure here, in previous iteration of loop
Code:
use std::thread;
use std::io::Read;
use std::fs::File;
use std::time::Duration;
use std::io::BufReader;
use ozelot::{Server, clientbound, ClientState, utils, read};
use ozelot::serverbound::ServerboundPacket;
use std::net::{TcpListener, TcpStream};
fn main() -> std::io::Result<()> {
let listener = TcpListener::bind("0.0.0.0:25565")?;
for stream in listener.incoming() {
handle_client(stream?);
}
Ok(())
}
fn handle_client(stream: TcpStream) {
let mut server = Server::from_tcpstream(stream).unwrap();
'listenloop: loop {
server.update_inbuf().unwrap();
let packets = server.read_packet().unwrap_or_default();
'packetloop: for packet in packets {
match packet {
//...
ServerboundPacket::LoginStart(ref p) => {
//...
//send keep_alive every 20 seconds
thread::spawn(move || {
thread::sleep(Duration::from_secs(20));
send_keep_alive(server)
});
fn send_keep_alive(mut server: Server) {
server.send(clientbound::KeepAlive::new(728)).unwrap();
}
//...
},
//...
_ => {},
}
}
thread::sleep(Duration::from_millis(50));
}
}
Upvotes: 2
Views: 1266
Reputation: 1653
Notice how most of the functions in ozelot::Server
take &mut self
as a parameter. This means that they are called on a mutable reference to a ozelot::Server
.
From Rust docs:
you can only have one mutable reference to a particular piece of data in a particular scope
The issue is - how can we share ozelot::Server
between the main thread and the new thread?
A lot of things in std::sync
module solve exactly these kinds of problems. In your case, you want read access from the main thread and write access from the thread calling send_keep_alive
. This calls for Mutex
- exclusive read/write access at a time, and
Wrap the server in an Arc<Mutex>
:
use std::sync::{Arc, Mutex};
// --snip--
fn handle_client(stream: TcpStream) {
let mut server = Server::from_tcpstream(stream).unwrap();
let mut server = Arc::new(Mutex::new(server));
Move a clone
of Arc
into the thread, and lock
the Mutex
to gain access:
let server = Arc::clone(server);
thread::spawn(move || {
thread::sleep(Duration::from_secs(20));
send_keep_alive(&mut server)
});
And make send_keep_alive
receive server
by mutable reference:
fn send_keep_alive(server: &mut Arc<Mutex<Server>>) {
loop {
let mut server = server.lock().unwrap();
server.send(clientbound::KeepAlive::new(728)).unwrap();
}
}
Upvotes: 1