Permanently store quick saves (Was: "Feat. request: Quicksave support")
subj
To clarify, by "quick save", you mean being able to save anywhere in a level (not just at the beginning)? Would you also want to save/load with one key press?
Yeah, classic F5-F6 keys to save anywhere
Would be a cool idea. @lethal-guitar what is a good approach to do this ? Do we currently have functionality to start a game with given states of entities ?
@pratikone starting from a given state would be relatively easy, by adding another constructor to GameWorld which sets things up as needed. The tricky part is serialization: saving and loading the state to a file. Plus, not all state is in entities. Some is stored in members of GameWorld, and some is part of systems. E.g. PlayerInteractionSystem keeps track of the position of the last cloaking device picked up, so that a new one can be spawned once it runs out.
You're not supposed to have state in systems in the Entity-Component-System model, so this is something that needs to be cleaned up first. The cleanest would be if all state that's not in entities is kept in an object that's owned by the GameWorld. So there is some refactoring to be done first.
Once that's done, we can tackle the serialization problem. Unfortunately, C++ does not have any reflection features, making this harder to do in a portable way. Essentially, we need a pair of serialize/deserialize functions for each component type, similar to the code that's reading/writing the JSON for the user profile.
To avoid having to write all of that by hand, it would be good to look into some ways of how we might automatically generate this code, or to look into library-based reflection facilities. I have looked into this in the past, and could post a list of various possibilities later.
Maybe some kind of Dosbox daum save feature like memory dump and load?
Maybe some kind of Dosbox daum save feature like memory dump and load?
Unfortunately, a plain memory dump would not work for RigelEngine. It works for something like DosBox or other type of emulator, because the entire memory of the emulated system can be dumped as one block. But RigelEngine is a native application, making use of the standard C++ memory allocator. So there is no single block of memory that could be dumped, and even if we could, the dump would still contain pointers to other resources that would become invalid when trying to reload.
Still, it would be possible to restructure the code in a way that would make a memory dump feasible (by storing all state in POD structs, using indices or IDs instead of pointers, and having a single "root" object containing everything else). But even then, a dump would not be portable: A saved game created on Windows would not necessarily load on Linux, etc. (not to mention platforms with different endianess, integer sizes etc.)
For these reasons, explicit serialization is the only option I see.
Saving on one machine would be a great progress, moving saves to other platforms is unlikely scenario IMHO.)
moving saves to other platforms is unlikely scenario IMHO
Maybe so, but I wouldn't be happy with a non-portable implementation. Being cross platform was a goal from the beginning, and I don't want to compromise on that.
That being said, explicit serialization is much less work overall compared to the changes that would be necessary to enable saving based on memory dumps, with a lower risk of introducing bugs, too. So it's definitely the way to go.
This is not a trivial feature and it will take some time, but it will happen for sure :slightly_smiling_face:
This library looks really nice: https://github.com/eliasdaler/MetaStuff There's also a tool to automatically generate meta information.
#509 is laying most of the ground work I mentioned above, once it's completed, all state will be either in components or in the WorldState struct. Once we have that, we can introduce reflection using the library linked above to make components and WorldState serializable. And then everything is in place to add quick saving.
One complication is with components that have pointer or ArrayView members, as it's not possible to directly serialize those. For sprites, this is still easy because we can serialize the actor ID instead, and then reload the right sprite when deserializing.
But for things like AnimationSequence, MovementSequence etc. it's not so straightforward.
One possibility would be to centralize all possible animation sequences/movement sequences into one place, and then use an index instead of a pointer.
@necros2k7 there's now a first working version of this in #592. The quick saves are not stored on disk yet, but you can already save/load while inside a level. Still needs some thorough testing to make sure no bugs are introduced, as it is quite a big change.