user7852204
user7852204

Reputation:

How do I call a function every X seconds with a parameter that doesn't implement Copy?

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

Answers (1)

Oleksii Filonenko
Oleksii Filonenko

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

Related Questions