0
\$\begingroup\$

I'm working on a card game using Amethyst's Specs crate and am fairly new to Rust (therefore I'm learning through the fight with the compiler). My first System is to deal the cards to each player. I have:

  • a Card type that has Copy semantics
  • a Cards component that represents a set of Card the players or deck are holding
struct Deal {
    deck: Entity,
    players: Vec<Entity>,
}

impl<'a> System<'a> for Deal {
    type SystemData = WriteStorage<'a, Cards>;

    fn run(&mut self, mut cards: Self::SystemData) {
        // code smell - can't use `get_mut`
        let mut deck = cards.get(self.deck).unwrap().clone();
        deck.shuffle();

        for player in self.players.iter().cycle() {
            let player_cards = cards.get_mut(*player).unwrap();
            if let Some(card) = deck.take_top() {
                player_cards.add(card);
            } else {
                break;
            }
        }

        // code smell
        let mut deck = cards.get_mut(self.deck).unwrap();
        deck.clear(); 
    }
}

The compiler complains that I can't take a mutable reference to the deck's cards because the player_cards is a mutable one (or vice versa).

As noted in the code above, I clone the Cards for the deck, shuffle that, deal to the mutable players' and then empty the deck.

I would have expected that I could get a mutable reference to the deck up front, shuffle that and remove it.

Is there something simple I am missing?

Aside, I do not know if it is convention to pass entities on the system struct it self or not. Is that wrong?

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

I'm not familiar with ECS or some of the types you used in your code, and to be honest I don't think I quite understand your code as it seems incomplete, but this seems like something that could be fixed by using the Rc and RefCell types. Instead of having cards as having type Self::SystemData, I think you'd want it to be Rc<RefCell<Self::SystemData>> instead, which should allow you to create multiple mutable references to it by using the Rc::clone function.

Whilst using Rc and RefCell, you'd still need to make sure that you stick to Rust's borrowing semantics though, as this will then be runtime-checked instead of compile-time checked. This section of the book has more information about these types and how they might be used.

I don't have your full code, so I can't check if this is correct, but I think you should end up with something similar to:

use std::{cell::RefCell, rc::Rc};

struct Deal {
    deck: Entity,
    players: Vec<Entity>,
}

impl<'a> System<'a> for Deal {
    type SystemData = WriteStorage<'a, Cards>;

    fn run(&mut self, mut cards: Rc<RefCell<Self::SystemData>>) {
        let mut deck = Rc::clone(&cards).get(self.deck).unwrap().clone();
        deck.shuffle();

        for player in self.players.iter().cycle() {
            let player_cards = Rc::clone(&cards).get_mut(*player).unwrap();
            if let Some(card) = deck.take_top() {
                player_cards.add(card);
            } else {
                break;
            }
        }

        let mut deck = Rc::clone(&cards).get(self.deck).unwrap().clone();
        deck.clear(); 
    }
}

After switching to using Rc and RefCell in some cases, you might even find that you're able to do without using explicit lifetime annotations, but YMMV.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.