godot-rust / gdnative

Rust bindings for Godot 3
https://godot-rust.github.io
MIT License
3.62k stars 210 forks source link

Add examples for multi-threading #312

Open TheSecurityDev opened 4 years ago

TheSecurityDev commented 4 years ago

It would be great if there were examples showing how to implement multi-threading, and just running code in a new thread. I am having trouble figuring it out.

ghost commented 4 years ago

I feel like examples might be easier for contributors to make when they have a more specific use case to cover. What do you specifically want to achieve with threads? For example, procedural generation or loading resources off the main thread, maybe?

TheSecurityDev commented 4 years ago

Hello again 😉

What do you specifically want to achieve with threads?

I want to repeatedly iterate in a loop running some calculations (a simulation). It is only using variables and lists that are defined in the structs. I just need it to run in a new thread so the main thread isn't blocked. It runs forever unless an end_thread variable is set true.

I have designed it in such a way that mutiple threads could also be used with no problems. Basically it repeatedly runs through a list of items to run the calculations on, so it's not hard to divide the work up. But I don't know how much harder this will be in Rust. I first need to get it to run in one new thread. I would like examples of creating a non blocking thread to run a function that could last a long time.

The struct object will only be created once and should last for the duration of the program, but I don't know how to tell Rust that as it complains about lifetimes, thinking the thread may outlive the object. I tried your suggestions from Discord, but using crossbeam blocks the main thread and I couldn't figure out how to use mutex and arc for self, if that's what I needed to do.

One puzzling thing was I tried creating a new thread from Godot which runs the function in Rust, and although it executes in parallel (I can tell because if I print in a loop after starting the thread the values are mixed with the values being printed from the Rust function), it still blocks the main thread even after the loop.

I hope I explained it clearly enough as it's really late right now and I'm tired.

ghost commented 4 years ago

I couldn't figure out how to use mutex and arc for self, if that's what I needed to do.

One "pattern" I find useful is to have the NativeClass just be a thin wrapper around an Arc<Mutex<Something>> (or something similar):

// Just typed this in the browser. Probably won't compile!

#[derive(NativeClass)]
struct SomeScript {
    internal: Arc<Mutex<Internal>>,
}

struct Internal {
    your_shared_state: i64,
}

#[methods]
impl SomeScript {
    #[export]
    fn start_thread(&self, _owner: Reference) {
        let internal = Arc::clone(&self.internal);
        thread::spawn(move || {
            // Do things with internal: it now has 'static lifetime!
            loop {
                let answer = somewhere::else::compute_answer();
                {
                    internal.lock().your_shared_state = answer;
                }
            }
        });
    }

    #[export]
    fn read_shared_state(&self, _owner: Reference) -> i64 {
        self.internal.lock().your_shared_state
    }
}

... but I don't think this is really specific to the GDNative bindings enough to have an example here. This is just how to do shared-state concurrency in Rust in general: https://doc.rust-lang.org/book/ch16-03-shared-state.html

it still blocks the main thread even after the loop.

I'm not sure if I understand what do you mean by this. If you can print from the main thread, then the main thread shouldn't have been blocked.

TheSecurityDev commented 4 years ago

Thanks for the example. It looks promising. I am still new to Rust, so it would be helpful to have a working example of how to do threading while using Godot, as it's not something that is easy to find online. You can find other examples, but it's hard for a beginner to figure out how to change these to work with Godot-Rust. I learned a lot by looking at the current examples, as well as the Pong game made in Rust. It would just be really nice if there was a working example for threading that didn't give compiler errors and is easy to figure out how to adapt to fit your needs. The reason a lot of people use Rust is to give them a performance boost in critical areas. Usually those operations require using threads.

If you can print from the main thread, then the main thread shouldn't have been blocked.

That's why it's so puzzling. I created a new thread in Godot, printed some stuff in a loop (to ensure it would be printing while the other thread was running, and it output the entire loop. But even after it finished, the main screen was frozen and I couldn't do anything, just as if I was doing a loop in the main thread. Maybe the Rust function was communicating with Godot's main thread somehow and it caused the freeze?

I will try to adapt your example to see if I can get something working.

ghost commented 4 years ago

I'm not sure what the situation is with your observation of the behavior. If you could provide a minimal reproduction project, maybe we can check if there is a problem in the bindings.

extraymond commented 4 years ago

@toasteater

Thanks for the example! Is it possible to wrap a signal emitter inside a thread? I tried but it complains that c pointers are not safe to be send. It would be neat to notify the current node in from a background signal, just so godot can pickup the right time to collect mutated properties.

ghost commented 4 years ago

@extraymond

If the question is "is it possible", then the answer is "yes". How you can achieve it depends on how safe you want your program to be.

First, please familiarize yourself with the rules for Send and Sync. The compiler complains because references are only Send when the things they point to are Sync. Most Godot types are not Sync. Thus, it is completely correct that the wrapper types, semantically being references, are not Send.

If you are absolutely sure that you will not use the thing from two threads at the same time, you can just declare a newtype around the Godot type and unsafe impl Send on it. Notice the unsafe here: you're at your own risk.

If you want to be safer, you might want to use message passing between Rust threads, and only emit Godot signals from the main thread. This will be slightly more complicated to implement, but does not involve lying to the compiler.

It is up to you to decide how you will approach this problem, depending on your use case.

extraymond commented 4 years ago

@toasteater Thanks for your advice, I think I've got some idea now.

I was thinking about message passing too. I tried starting a async executor as a msg coordinator, embed a message sender and a lock. For now letting the gdnative node sending msg's work, just now I need a way to get it notified and the and trigger updating after locking the resource. I will try custom node. Thanks for your advice.

Another off-topic question, does changing the type of user_data helps? From what I've gathered, it seems like it's way to let native_script know whether it's required to lock the node before accessing it's methods. So it seems like a safe lock between godot <--> gdnative node, rather than gantive node <--> underlining rust code.

ghost commented 4 years ago

Another off-topic question, does changing the type of user_data helps? From what I've gathered, it seems like it's way to let native_script know whether it's required to lock the node before accessing it's methods. So it seems like a safe lock between godot <--> gdnative node, rather than gantive node <--> underlining rust code.

I'm not sure if I was able to correctly understand what you mean, sorry. The UserData type determines how the NativeClass it's associated with can be stored into and retrieved from raw C pointers. It enables convenient exporting of ordinary Rust structs and methods, since otherwise users will have to manually implement interior mutability on every type. Its choice should not interfere with Rust concurrency, although it is possible to avoid extra locks by using ArcData if you are handling your own locks. You could read the module-level documentation on user_data for more information: https://docs.rs/gdnative/0.8.0/gdnative/user_data/index.html

Please try to keep the issue on topic. If you need general help, please consider using the #gdnative_dev channel in the Godot community Discord server: https://discord.gg/zH7NUgz. Thanks for your understanding.

qbx2 commented 1 year ago

How do you send signal from the spawned thread?