QuantumBadger / Speedy2D

Rust library for hardware accelerated drawing of 2D shapes, images, and text, with an easy to use API.
Apache License 2.0
381 stars 41 forks source link

Communicating between WindowHandler and a web server #89

Closed superlou closed 1 year ago

superlou commented 1 year ago

I'm trying to tie a web server and a WindowHandler together, so that the web server can send events to the WindowHandler as well as return status information. I'm using Rouille, since it seemed like a syncrhonous server would be easier to integrate. I followed the user event example, which seems to be the right way to communicate between two threads, and I can see that does seem to work:

impl WindowHandler<String> for SignWindowHandler {
    fn on_start(
        &mut self,
        helper: &mut WindowHelper<String>,
        info: speedy2d::window::WindowStartupInfo
    ) {
        let sender = helper.create_user_event_sender();

        thread::spawn(move || {
            sender.send_event("Test".to_owned()).unwrap(); 
        });
    }

However, when I try to pass the UserEventSender to Rouille, I receive an error that the sender cannot be shared between threads safely:

impl WindowHandler<String> for SignWindowHandler {
    fn on_start(
        &mut self,
        helper: &mut WindowHelper<String>,
        info: speedy2d::window::WindowStartupInfo
    ) {
        let sender = helper.create_user_event_sender();

        thread::spawn(move || {          
            rouille::start_server("0.0.0.0:3000", move |_request| {
                sender.send_event("Test".to_owned()).unwrap();
                Response::text("Hello, world!")
            });
        });
    }
error[E0277]: `Sender<speedy2d::window_internal_glutin::UserEventGlutin<String>>` cannot be shared between threads safely
   --> src/window_handler.rs:72:51
    |
72  |               rouille::start_server("0.0.0.0:3000", move |_request| {
    |               ---------------------                 ^--------------
    |               |                                     |
    |  _____________|_____________________________________within this `[closure@src/window_handler.rs:72:51: 72:66]`
    | |             |
    | |             required by a bound introduced by this call
73  | |                 sender.send_event("Test".to_owned()).unwrap();
74  | |                 Response::text("Hello, world!")
75  | |             });
    | |_____________^ `Sender<speedy2d::window_internal_glutin::UserEventGlutin<String>>` cannot be shared between threads safely
    |
    = help: within `[closure@src/window_handler.rs:72:51: 72:66]`, the trait `Sync` is not implemented for `Sender<speedy2d::window_internal_glutin::UserEventGlutin<String>>`
    = note: required because it appears within the type `EventLoopProxy<UserEventGlutin<String>>`
    = note: required because it appears within the type `EventLoopProxy<UserEventGlutin<String>>`
    = note: required because it appears within the type `EventLoopProxy<UserEventGlutin<String>>`
    = note: required because it appears within the type `UserEventSenderGlutin<String>`
    = note: required because it appears within the type `UserEventSender<String>`

Is there a better approach for communicating between threads? In both cases, I believe the closure captures the sender, and UserEventSender implements Send and Sync.

superlou commented 1 year ago

One approach seems to be putting it in a mutex:

impl WindowHandler<String> for SignWindowHandler {
    fn on_start(
        &mut self,
        helper: &mut WindowHelper<String>,
        info: speedy2d::window::WindowStartupInfo
    ) {
        let sender = helper.create_user_event_sender();
        let sender_mutex = Mutex::new(sender);

        thread::spawn(move || {          
            rouille::start_server("0.0.0.0:3000", move |_request| {
                sender_mutex.lock().unwrap().send_event("Test".to_owned()).unwrap();
                Response::text("Hello, world!")
            });
        });
    }

This agrees with the Rouille docs, but is thatf how UserEventSender is expected to behave?

QuantumBadger commented 1 year ago

Thanks for the question! The issue is that rouille::start_server may call the provided request handler (move |_request| {...) from lots of different threads, and so everything in the handler needs to be Sync.

UserEventSender is Send but not Sync, which means you can pass it between threads, but you can't have one UserEventSender shared between threads by reference (which is what's happening here).

The mutex is probably the best solution here IMO.

superlou commented 1 year ago

I get a bit nervous when I start doing lots of wrapping, but that explanation makes a lot of sense. Plus, to be able to get information from the WindowHandler to the server, I'll need to also get comfortable with wrapping things in Arc<Mutex<_>>, which seems to be the Sync equivalent of Rc<RefCell<_>>. Something like:

impl WindowHandler<String> for SignWindowHandler {
    fn on_start(&mut self, helper: &mut WindowHelper<String>, _info: WindowStartupInfo) {
        let sender = helper.create_user_event_sender();
        crate::server::start_server(&self, Mutex::new(sender));
    }

...

pub fn start_server(handler: &SignWindowHandler, sender: Mutex<UserEventSender<String>>) {
    let path = handler.root_path.clone();

    thread::spawn(move || {          
        rouille::start_server("0.0.0.0:3000", move |_request| {
            sender.lock().unwrap().send_event("Test".to_owned()).unwrap();
            let path_str = path.lock().unwrap();
            Response::text(path_str.to_str().unwrap())
        });
    });
}

I probably have a hidden type that's something like ApplicationState which is owned by the SignWindowHandler, but this seems to work for now. Thanks!