And all that I do when I'm in Pause is I draw the internal game state, and a gui over it, but don't update the internal game state. This is great, until I come to this kind of pattern:
match self{
//.....
Paused(paused_data) => {
if match_all_unpause_conditions {
*self = *paused_data;
}
}
}
Where stuff kind of breaks, logically. I can't use std::mem::swap because I can't borrow self as mutable more than once, and cloneing or copying self would not be viable. How might I implement a Pause state in a nice and idiomatic way, such that I can swap in and out of the Pause state? Thanks!
Why do you need to store the game state inside the game state? Why not just have a flag outside that tells that the game is paused and limit access through that outer struct.
This will guarantee that you won't change the GameState while rendering, only while updating.
Another thing to consider is using generics, if OnePlayerData and TwoPlayerData have the same surface API (or mostly the same surface API), then you could define that API via a trait
trait PlayerData {
// common functionality
}
struct OnePlayerData {
// stuff in here
}
impl PlayerData for OnePlayerData {
// ...
}
impl OnePlayerData {
// functions special to OnePlayerData
}
struct TwoPlayerData {
// stuff in here
}
impl PlayerData for TwoPlayerData {
// ...
}
impl TwoPlayerData {
// functions special to TwoPlayerData
}
struct GameState<Data: PlayerData> {
player_data: Data,
is_paused: bool,
}
I will second @RustyYato's suggestion to break out the pause flag. However, if you find yourself facing this situation in the future and are adamant of finding a workaround, you can always consider using somethin like replace_with.
One more small question, if I have a Box<dyn PlayerData> and I want to run the associated trait functions, I can do that through the magic of Deref, self.player_state.draw() for example. If I do that, I never have to worry about it moving the internal dyn PlayerData around right? Because I don't want to be copying (or moving) a potentially large struct around 60 times a second.
Box is just a pointer, and you won't be moving the data stored on the heap that the Box points to around at all. It is very fast to move a pointer, (just 64/32 bits of stack/register memory).
Dynamic dispatch from trait objects can be slow due to missed optimizations (such as those from inlining functions), if you want performance, try and refactor your code to use generics instead. Also allocations are expensive, so try and limit it, especially in tight loops.
For example, do you need to handle OnePlayer and TwoPlayer in the same GameState? Could you handle them separately?
Both of them implement PlayerState which has draw, update etc., so it would be more logical to handle them both in the same place, such as the GameState, which would automatically call draw, update, etc, when necessary.
Ah, perhaps I could have an Option<T> for both, and just keep a flag, since I have so few options, and don't want to worry about enum impls that would have to worry about both
Yeah, so put all the functionality that is common between OnePlayer and TwoPlayer and put it in PlayerData. So that would be update, 'handle' and render as you already have, and new.
It's giving me the same complaint about it not being a PlayerState. I'm pretty sure that a generic type can't change its generic parameter though, because that would make it non-Sized
Already pushed commit