Author Topic: Different methods for loading/saving  (Read 22545 times)

Tim Saunders

  • Newcomer
  • Posts: 15
  • Karma: +0/-0
    • View Profile
Different methods for loading/saving
« on: March 31, 2015, 09:20:36 AM »
I'm going to be dealing with loading/saving soon and was wondering what the different options for it were. My game will have persistent levels.

I realise I could save the exact state of the game and all the actors in it, but I've noticed some games allow you to replay the game so it obviously records transitions as well somehow. How exactly does this work? I guess you could save the players actions and random seed, but then you'd have to replay the whole game up to that point when you reload if you use the recording as a game save as well.

Or is the loading/saving normally kept separate from the record/playback?

Krice

  • (Banned)
  • Rogueliker
  • ***
  • Posts: 2316
  • Karma: +0/-2
    • View Profile
    • Email
Re: Different methods for loading/saving
« Reply #1 on: March 31, 2015, 11:02:51 AM »
If you don't need playback don't even try to program it. It's a huge waste of time. You'll have plenty of things to do with just the normal save/load routines.

Omnivore

  • Rogueliker
  • ***
  • Posts: 154
  • Karma: +0/-0
    • View Profile
Re: Different methods for loading/saving
« Reply #2 on: March 31, 2015, 08:05:57 PM »
I'm going to be dealing with loading/saving soon and was wondering what the different options for it were. My game will have persistent levels.

Depends heavily upon the language you are using.  Most modern languages have relatively good serialization support which allow you to save game state in a portable form (JSON for example).  If you're not worried about exchanging save files between different machines/OS's, then you may be able to use a raw binary dump, again depends on language.  If the language you are using doesn't have good serialization support, or if you've drank too much OOP-juice, it may be a worthwhile option to use a lightweight database for storage such as SQLite.  This may let you use an ORM to untangle your objects in a class heavy implementation.

There are a couple gotchas.  Some languages have very crappy PRNG implementations.  Regardless of their performance characteristics, if you cannot easily save and restore the state of the PRNG, you're looking at a potential problem.  There are two main solutions, either use a 3rd party PRNG that has save/restore state capability or write a wrapper around the standard library PRNG that saves both the initial seed and a counter that tracks how many iterations the PRNG performs.   Save both the seed and counter along with the game state during saves, then during a load, reseed with the loaded seed and run however many iterations that the loaded counter indicates to bring the PRNG into sync with your saved state.

Another gotcha involves multi-threading.  I'll not go into detail but basically you need a 'stop the world' save event that, at the least, makes an in-memory copy of the current state, and then saves it.  There are other techniques but you need to be very careful to not end up with an inconsistent save state.

I honestly winced a bit when I read your first line above.  The easiest way to implement save/load is to build it in from the very start.  Leaving it till later is... non-optimal at best, potentially catastrophic at worst.  If you find yourself in a catastrophic situation due to too much OOP, poor language support, etc, and if you are willing to abandon the ability to backtrack, you could always just use stairs as save points.  Much easier since you only need to store player state, map level, and a seed to generate the next level.  If you are recording player moves, you *can* use this to create a persistent world which does allow backtracking - just keep track of each save point.


I realise I could save the exact state of the game and all the actors in it, but I've noticed some games allow you to replay the game so it obviously records transitions as well somehow. How exactly does this work? I guess you could save the players actions and random seed, but then you'd have to replay the whole game up to that point when you reload if you use the recording as a game save as well.

Conceptually playback is nothing more than a combination of a saved starting point and a 'undo/redo' stack.  It is easier than doing an 'undo/redo' stack in that you only need a simple list of translated player input.  It gets almost trivial to implement if you have a clean separation between your world model and your screen presentation, especially if you have already added the ability for player remapping of commands.  The only 'tricky' part is mouse input; you need to capture the translated coordinates to avoid ugly coupling with the view state.

Now for using it to restore state as part of a persistent game, playing back a list of player input from beginning of game to the latest state would be one obvious way to do it, but... a better way is to use the 'savepoint at stairs' approach I noted above and
combine it with the playback data.  This allows you to regenerate any level at any turn.

During restore (when using the playback record to restore state), you simply do not call render.  You just restore the model to the stairs savepoint, and then update it with the playback record in a simple loop.  Since you're not having to wait for the player to actually input anything and you're not calling render to show anything, it should be quite fast unless you have extremely large levels or an extremely involved AI implementation.

Or is the loading/saving normally kept separate from the record/playback?
I don't think there is a 'normally' here, depending on language and your chosen architecture, one may be much easier and simpler than the other.  For those who have drank too deeply of the well of OOP-juice, it may actually be easier to implement a savepoint + record/playback solution than to deal with all the tangled relationships.  On the other hand, if you use a data-centric approach or a hybrid, you will likely find the state save/load approach to be easier.
[EDIT]Given the relative ease of implementing save/load in terms of record/playback, I'd expect those who implement both would combine them unless there were some mitigating factors.
[/EDIT]
Hope this helps,
Brian aka Omnivore

« Last Edit: March 31, 2015, 11:14:52 PM by Omnivore »

Tim Saunders

  • Newcomer
  • Posts: 15
  • Karma: +0/-0
    • View Profile
Re: Different methods for loading/saving
« Reply #3 on: April 01, 2015, 12:34:49 PM »
Thanks for the comprehensive reply. I'm a little confused about the potential problems with the PRNG though. If you save the world state as it currently and then reload from that point, why would it matter if the PRNG would be different from that point onwards than if you'd just kept playing without saving?

I will only allow saving on the players turn so all world events should have stopped happening at that point.

Quote
Conceptually playback is nothing more than a combination of a saved starting point and a 'undo/redo' stack.  It is easier than doing an 'undo/redo' stack in that you only need a simple list of translated player input.  It gets almost trivial to implement if you have a clean separation between your world model and your screen presentation, especially if you have already added the ability for player remapping of commands.  The only 'tricky' part is mouse input; you need to capture the translated coordinates to avoid ugly coupling with the view state.

If I'm understanding this correctly then the playback is just a starting point and a series of actions (both player and other actor actions) that are played one by one changing the game state from that point onwards?

Edit: I'm beginning to see what you meant about problems with OOP implementation in that the class and the data for an object are combined meaning they have to be separated and then put back together when you load/save each time. It's something that hadn't occurred to me before.

I could save the data from each class and then recreate the class using the data  when I save/load, but perhaps that is a bit too unwieldy. Or I could rewrite the code so that the class and data objects are separate or remove classes completely and just have functions that deal with the data objects. I'm not entirely sure which direction to go from here, any advice?
« Last Edit: April 01, 2015, 02:16:09 PM by Tim Saunders »

Omnivore

  • Rogueliker
  • ***
  • Posts: 154
  • Karma: +0/-0
    • View Profile
Re: Different methods for loading/saving
« Reply #4 on: April 01, 2015, 04:42:08 PM »
Thanks for the comprehensive reply. I'm a little confused about the potential problems with the PRNG though. If you save the world state as it currently and then reload from that point, why would it matter if the PRNG would be different from that point onwards than if you'd just kept playing without saving?

There are two problems, first is that if you don't save the PRNG state, your game becomes trivially easy.  I save just before opening a chest and keep reloading, opening the chest, seeing if it has what I want, and if not, I quit (or crash the prog) and reload.  Same with boss fights (or any difficult fight). 

The second problem is related to the following:
If I'm understanding this correctly then the playback is just a starting point and a series of actions (both player and other actor actions) that are played one by one changing the game state from that point onwards?
If you save and restore the PRNG state, then you only need the player actions - the other actor actions will be the same since they are a function of player action + PRNG.   If you have a different PRNG state, then... playback isn't going to work anything like you expect.

If you're using the playback to restore game state instead of actually playing back the game for a viewer, then you can speed things up substantially by skipping anything that is unrelated to changes in the state of the model.  For example, player actions which are just showing game state to the player, inventory actions which do not change the state, even field of view calculations if not required by your mob/actor ai, all can be skipped during a 'playback for restore' since they don't change the game model state.

As for the remainder of your questions, I can't answer really without knowing what language you are using.  Some languages practically force you into OOP as the hammer for every task (C#/Java/etc) but usually those same languages have good serialization and ORM support.  The hardest thing to deal with is the relationships between various objects - a pointer or reference to an object - circular references - etc. 

Before refactoring/rewriting things, I'd stop and take a good look at your current implementation, your goals, the language you are using, the tools that language has (both standard and 3rd party) to solve the state save/restore problem.  If there is no clear answer via the serialization route, consider using temporary data transfer objects (or relational database records) to represent game state in a form more friendly to the tools you have available.

The nice thing about record/playback is that you can sidestep practically all of the problems associated with serialization (and/or Object Relational Mapping), the 'downside' (I don't consider it a downside but depending upon your chosen architecture it may be) is that it is difficult to do if there is too much coupling between your model and your user interface.  Some of the scenarios discussed in another thread about animations(and timed visual effects) would be very difficult to deal with in record/playback.

Hope this helps,
Brian aka Omnivore

PS: I'm not exactly anti-OOP; just its been way oversold and far too many people out there have the idea that it is the best tool for every job.. its not.  It's a great hammer, its a horrible screwdriver.

Tim Saunders

  • Newcomer
  • Posts: 15
  • Karma: +0/-0
    • View Profile
Re: Different methods for loading/saving
« Reply #5 on: April 01, 2015, 07:52:25 PM »
Quote
There are two problems, first is that if you don't save the PRNG state, your game becomes trivially easy.  I save just before opening a chest and keep reloading, opening the chest, seeing if it has what I want, and if not, I quit (or crash the prog) and reload.  Same with boss fights (or any difficult fight). 

Ok thanks that makes sense.

Edit: Actually I just thought, even if you did save the seed then what would stop the player saving and then walking away from the chest and back to it (thus moving the random seed onwards) before reopening it again? Wouldn't a better solution be to calculate monster drops and chest contents when the monster/level is created?

Quote
If you're using the playback to restore game state instead of actually playing back the game for a viewer, then you can speed things up substantially by skipping anything that is unrelated to changes in the state of the model.  For example, player actions which are just showing game state to the player, inventory actions which do not change the state, even field of view calculations if not required by your mob/actor ai, all can be skipped during a 'playback for restore' since they don't change the game model state.

I'm still a bit confused by the record/playback saving model. If you have thousands of turns will the code have to go through them all one by one resolving them each time? It seems like it might be quite an inefficient way of doing things with AI and everything, also I imagine any bugs at all would cause a lot of issues as the rest of the playback would be broken from that point on. I can't seem to get my head around the idea properly. I have got an animation system in place, but it would be possible to add a "playback" flag which skips to the end of any animations without playing them so I think it would still be possible.

I have managed to implement a different load/save method that seems to work ok.

Each class has a create() and a restoreFromSave(_saveObject) method.
The first one is used when creating the object for the first time and the second when restoring from a save file.

It seems to work without too many problems, but every single new variable I add to each class needs to be stored and reloaded properly which is a bit of a pain. Is that normal for a game of this type?

I am writing the game in Javascript which doesn't have a way of serialising "classes" unfortunately. I'm not really using much OOP to be honest, just have javascript style "classes" that can store data and contain functions as well which I've realised can't all be saved together.
« Last Edit: April 01, 2015, 08:00:22 PM by Tim Saunders »

Omnivore

  • Rogueliker
  • ***
  • Posts: 154
  • Karma: +0/-0
    • View Profile
Re: Different methods for loading/saving
« Reply #6 on: April 01, 2015, 08:52:06 PM »
In re the PRNG and chest scenario, yeah its possible to work around but it requires more effort on the player's part.  The boss mob scenario is likely the more important one.  Again though, the player could try to get the PRNG to a different state, there comes a point where it becomes more trouble than its worth.  For me, I consider save/restore PRNG state to be trivial as well as necessary for playback so *shrug*.

Javascript is not a language I have spent much time with.  The solution you are currently using is similar to what I've used in C/C++ before and while a pain due to having to do so much manually and also requires care in maintenance, it does work. 

As far as the record/playback goes, no you shouldn't have thousands of turns to playback.  You do a simple check point save automatically (not necessarily to disk but as part of the recording) upon exit from a level but prior to creation of a new one.  At that specific point in game state, you only need to store the player state, the location, and the PRNG state because at that point in time, that is the entire game state.

On playback you start from the last save point and then execute the saved player commands, should be at most a few hundred unless you have really big levels.  It is actually simpler to implement than a full fledged game state save restore with a non-trivial game state as long as there are no tight couplings between the model and UI.  That is the key point.  If you find yourself having to resort to a bunch of flags your architecture is probably too tightly coupled for record/playback to be viable as a save/restore replacement despite it being a superset.

In your case, since you now have a working solution to save/restore and, from your description, have a non-trivial amount of coupling between model and view, I'd just go with the working solution and possibly add in playback with view later.

Hope this helps,
Brian aka Omnivore

AquaTsar17

  • 7DRL Reviewer
  • Rogueliker
  • *
  • Posts: 57
  • Karma: +0/-0
    • View Profile
Re: Different methods for loading/saving
« Reply #7 on: April 02, 2015, 01:37:02 AM »
Regarding the chest issue, you could also just have a separate PRNG for such things. One could be for terrain and/or rewards, while another could be for combat results or spawning. This way, the player won't change the rewards of a chest by saving the seed and then moving around.

On another point though, most RLs with permadeath do not keep the save files after they are loaded. The kind of behavior you're talking about would actually be save-scumming, since otherwise the player wouldn't know what was in the chest and choose to move around in the hopes of getting something better.

Krice

  • (Banned)
  • Rogueliker
  • ***
  • Posts: 2316
  • Karma: +0/-2
    • View Profile
    • Email
Re: Different methods for loading/saving
« Reply #8 on: April 02, 2015, 08:28:56 AM »
I am writing the game in Javascript which doesn't have a way of serialising "classes" unfortunately. I'm not really using much OOP to be honest, just have javascript style "classes" that can store data and contain functions as well which I've realised can't all be saved together.

I don't know if javascript is actually a programming language, but in OOP languages like C++ saving and restoring a class is really easy. You can check out how it's done in Teemu:

http://koti.mbnet.fi/paulkp/teemu/teemu.htm

The top routine is in world.cpp, then level.cpp for saving each level, and each level contains all game objects and maps which have their save routines. Everything is saved in a pieces of streams using Tar_Ball class (tarball.cpp). It's much easier than some people say (about saving and loading in OOP languages).

Tim Saunders

  • Newcomer
  • Posts: 15
  • Karma: +0/-0
    • View Profile
Re: Different methods for loading/saving
« Reply #9 on: April 10, 2015, 12:03:06 PM »
Apologies for the delay in replying.

Quote
On playback you start from the last save point and then execute the saved player commands, should be at most a few hundred unless you have really big levels.

Ah thanks that makes sense, so you need both the ability to save the game state and step through the moves after that point.

Quote
as long as there are no tight couplings between the model and UI.

That makes sense too, although I find it hard to imagine a game with animation that doesn't have couplings between the two.

Quote
Regarding the chest issue, you could also just have a separate PRNG for such things. One could be for terrain and/or rewards, while another could be for combat results or spawning.

Excellent point, I'd not thought of that.

Quote
You can check out how it's done in Teemu

Thanks I'll have a look. By the way are you the chap from the google groups a few years back? I remember you were working on an epic roguelike back then.

Krice

  • (Banned)
  • Rogueliker
  • ***
  • Posts: 2316
  • Karma: +0/-2
    • View Profile
    • Email
Re: Different methods for loading/saving
« Reply #10 on: April 10, 2015, 01:37:19 PM »
By the way are you the chap from the google groups a few years back?

Newsgroup rec.games.roguelike.development. In fact google groups killed it for me, because I have no other way to access newsgroups and changes google made in their web reader were too much to withstand. Besides a web forum is just better in technical way than newsgroups.

mushroom patch

  • Rogueliker
  • ***
  • Posts: 554
  • Karma: +0/-0
    • View Profile
Re: Different methods for loading/saving
« Reply #11 on: April 10, 2015, 08:04:05 PM »
You should consider twitter, Krice. I think that's what all the cool rgrd nerds do now.

chooseusername

  • Rogueliker
  • ***
  • Posts: 329
  • Karma: +0/-0
    • View Profile
    • Email
Re: Different methods for loading/saving
« Reply #12 on: April 10, 2015, 08:50:35 PM »
You should consider twitter, Krice. I think that's what all the cool rgrd nerds do now.
Reddit is pretty good.  There's a reasonably popular roguelikedev subreddit.  And one of the things is a weekly sharing saturday thread, where people post progress on their roguelikes.

Krice

  • (Banned)
  • Rogueliker
  • ***
  • Posts: 2316
  • Karma: +0/-2
    • View Profile
    • Email
Re: Different methods for loading/saving
« Reply #13 on: April 11, 2015, 12:18:20 PM »
Reddit is pretty good.  There's a reasonably popular roguelikedev subreddit.

I don't get Reddit. It's like a forum, but with some kind of braindead voting system. The people in reddit are douchebags. Twitter is for casual people, you know, non-computer people, just like facebook.

mushroom patch

  • Rogueliker
  • ***
  • Posts: 554
  • Karma: +0/-0
    • View Profile
Re: Different methods for loading/saving
« Reply #14 on: April 11, 2015, 08:06:48 PM »
Yeah, I was just joking. Obviously your kind of commentary would have a difficult time in an opt-in environment like twitter or a moderated environment like reddit.