hadronized / warmy

Hot-reloading loadable and reloadable resources
Other
215 stars 14 forks source link

Usage with Specs? #29

Open ducharmemp opened 5 years ago

ducharmemp commented 5 years ago

Hi Phaazon! Fantastic library. I've been unable to find examples where warmy is used in conjunction with specs (although my google-fu might just be weak at the moment). I was attempting to use specs.world.add_resource(Arc::new(Mutex::new(Store...))), to overcome thread safety issues but encountered issues with dyn std::any::Any + 'static with the HashCache. I'm still a beginner to rust so this is definitely over my head, but I'd love any help for situations where I'm trying to reference cached resources in a multithreaded environment. I'm attaching the full error trace from cargo run, I'd love some insight and help! If this isn't possible or I'm misunderstanding how to use resources in this type of environment I'd also love any tips that could be provided. Thanks advance and thanks for all the help!

out.txt

hadronized commented 5 years ago

Hey! I have no idea what specs is! I need to know more. :)

ducharmemp commented 5 years ago

No worries! So specs is a parallel entity-component-system that allows you to work on collections of data within a game setting. Essentially define code like below:

struct SysA;

impl<'a> System<'a> for SysA {
    // These are the resources required for execution.
    // You can also define a struct and `#[derive(SystemData)]`,
    // see the `full` example.
    type SystemData = (WriteStorage<'a, Pos>, ReadStorage<'a, Vel>);

    fn run(&mut self, (mut pos, vel): Self::SystemData) {
        // The `.join()` combines multiple components,
        // so we only access those entities which have
        // both of them.
        // You could also use `par_join()` to get a rayon `ParallelIterator`.
        for (pos, vel) in (&mut pos, &vel).join() {
            pos.0 += vel.0;
        }
    }
}

Which is pretty fantastic to work with! This basically allows me to attach a position and velocity to my game objects and they "just work" and move around, and I can trivially multithread these systems so that my updates to my movement system, camera system, physics system, etc. are all happening as fast as possible. This library also allows for the usage of "resources", which in this context are basically things that the game objects need to reference, such as the game world, the sound system, the scripting system, etc.

So! With all that being said, I tried to add the warmy store as a resource but unfortunately I ran into the above problem :( I don't necessarily see warmy as not being an option within my game, since only a few systems have need of referencing assets on disk (any they could possibly be refactored to not reference those assets), but I think it's still a useful thing to explore, even if it's not incredibly pressing :) Again thanks for the fantastic library, being able to hot reload is a great feature and one that I really can't live without!

ducharmemp commented 5 years ago

Update: I was able to sort of figure out a method. If I pass around the Arc<Mutex> myself explicitly, I'm able to avoid the compiler issues.

hadronized commented 5 years ago

Something you might be interested in: currently, warmy doesn’t support async code. I have a branch to add it multi-threading support, but it’s not that trivial to do so it’s taking a bit of a time. I’ll have a closer look at your issue when I’m home tonight. :)

icefoxen commented 5 years ago

@ducharmemp There's an example of how to put them together here: https://github.com/ggez/game-template It's rather outdated though, I'm working on updating it. It also kiiiinda moves all the actual drawing/use of assets into a single thread, which makes life a lot easier.

icefoxen commented 5 years ago

I gotta admit that now I'm working with a similar-ish problem and it would be nice to have a feature flag to replace Res's Rc<RefCell<T>> with an Arc<Mutex<T>>.

Edit: Startlingly, a fairly naive implementation of this appears to Just Work. At least, it passes unit tests. See changes here: https://github.com/icefoxen/warmy/commit/787da8b3171770b940be831b886aed84d025212a

hadronized commented 5 years ago

I have a branch about that but it’s been a while I haven’t had a look. I’ll look tomorrow! Thanks for the feedback.

hadronized commented 5 years ago

@icefoxen how do you fill those data in the Load::load method? I’ve always thought I would need either to introduce a dependency to some green-threading system, or an executor or something. I’ve also thought about having an IO loop dispatcher, but huhuhu.

icefoxen commented 5 years ago

Exactly as normal. My Load method for an image is:

/// A wrapper for a ggez Image, so we can implement warmy's `Load` trait on it.
#[derive(Debug, Clone)]
pub struct Image(pub ggez::graphics::Image);

/// And, here actually tell Warmy how to load things.
impl warmy::Load<ggez::Context, Key> for Image {
    type Error = Error;
    fn load(
        key: Key,
        _storage: &mut Storage,
        ctx: &mut ggez::Context,
    ) -> Result<Loaded<Self>, Self::Error> {
        debug!("Loading image {:?}", key);

        match key {
            Key::Path(path) => ggez::graphics::Image::new(ctx, path)
                .map(|x| warmy::Loaded::from(Image(x)))
                .map_err(|e| Error::GgezError(e)),
        }
    }
}

There's a custom Key type to handle ggez's paths, but that's it. The actual loading is single-threaded, but that's fine for ggez. The trick is than you can then make a specs Component contain a Res, which normally it can't due since Res is not Send+Sync.

#[derive(Clone, Debug, Component)]
#[storage(VecStorage)]
pub struct Sprite {
    pub img: warmy::Res<resources::Image>,
}

What I'm doing is really quite crude since ggez's drawing and resource loading both are single-threaded, I just want to be able to keep as much data as practical in specs.

hadronized commented 5 years ago

Got it. I may apply your patch upstream and feature-gate it until a proper solution exists for async code. Would you feel okay with that?

icefoxen commented 5 years ago

That's fine with me. It's a 90% solution, but that's all I want for the forseeable future.

hadronized commented 5 years ago

I’ll add that then. Thanks! :)