Struggling with SDL thread safety

Hey folks,

I'm trying to create my first Rust game using Wasm and SDL. I'm also playing with Specs to compartmentalize everything in systems. Unfortunately, I'm having some issues with the lack of thread-safety of the SDL context, and am wondering what a good fix would be.

To keep everything compartmentalized, I've created a Graphics system that handles all graphical rendering:

struct Graphics(Sdl);

impl Graphics {
    fn new() -> Result<Graphics, String> {
        let context = sdl2::init()?;
        let video_subsystem = context.video()?;
        video_subsystem.window("Game", 800, 600)
            .position_centered()
            .fullscreen()
            .opengl()
            .build()
            .map_err(|v| v.to_string())?;
        Ok(Graphics(context))
    }

}

impl<'a> System<'a> for Graphics {
    type SystemData = (FetchMut<'a, InputEvents>);

    fn run(&mut self, mut data: Self::SystemData) {
        let mut event_pump = self.0.event_pump().unwrap();
        for event in event_pump.poll_iter() {
            match event {
                Event::KeyDown {keycode: Some(keycode), ..} => {
                    data.0.push(InputEvent::KeyDown(keycode));
                },
                Event::KeyUp {keycode: Some(keycode), ..} => {
                    data.0.push(InputEvent::KeyUp(keycode));
                },
                _ => {}
            }
        }
    }

}

At the moment this is intended to render a blank window, retrieve all keyboard events, then make those available as a Specs Resource so other systems can access it. Next, I initialize the system:

        let graphics = Graphics::new()?;
        ...
        let dispatcher = DispatcherBuilder::new()
            .add(Controller, "controller", &[])
            .add(audio, "audio", &[])
            .add(graphics, "graphics", &[])
            .build();

This fails:

error[E0277]: the trait bound `std::rc::Rc<sdl2::SdlDrop>: std::marker::Send` is not satisfied in `Graphics`
   --> src/main.rs:105:14
    |
105 |             .add(graphics, "graphics", &[])
    |              ^^^ `std::rc::Rc<sdl2::SdlDrop>` cannot be sent between threads safely
    |
    = help: within `Graphics`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<sdl2::SdlDrop>`
    = note: required because it appears within the type `sdl2::Sdl`
    = note: required because it appears within the type `Graphics`

Pretty sure I understand what this is doing--the SDL context isn't safe to pass across thread boundaries, and the compiler is catching that. What I don't understand is how to fix it. The only place that should access the SDL context is the new method, which creates it, and the System implementation which calls it to do rendering and poll for events. How can I compartmentalize that variable so this error will go away? I tried not creating the system myself and letting Specs do it (I.e. passing Graphics in directly rather than creating an instance) but no change.

Thanks.

1 Like

Iā€™m not familiar with SDL or specs. What does the Dispatcher do exactly? Runs those components in background thread(s)? And what are you trying to accomplish by adding graphics to it?

I don't really know anything about SDL either, but my guess is that SDL's underlying machinery isn't thread-safe in that context and so that restriction was encoded into the Rust API. Otherwise I would imagine that the underlying type would be using Arc rather than Rc for its reference counting.

Also I'm pretty sure that wasm doesn't currently have threads anyway, so even if you did do something to fix the compilation error you'd just panic at runtime instead.

I eventually sorted this out by getting rid of Specs as my ECS. Yes, Wasm is single-threaded, but Specs is multi-threaded, and while I think the multi-threading deps were compiled out of Emscripten builds, the type definitions remained and the compiler wasn't happy. Switched to rustic-ecs and now things seem good.