Saving and Restoring
Originally written by Ishtvan on http://forums.thedarkmod.com/topic/1586
WARNING: This is a first draft, complete with spelling and grammatical errors. Also, I just got this functionality working myself. So there may well be some unknown issues with this code that have yet to pop up.
Saving and Restoring Non-Spawned Classes
NOTE: This tutorial only applies to non-spawned objects. You only have to do all of this if you are NOT deriving your class from idEntity, i.e., you do not want your class to be spawned in the map. If you're deriving from idEntity, your object will automatically be saved and restored (although you still might have to save specialized data by writing Save and Restore methods)
Let's suppose you create a new class, and have a global object for it that you want to save and restore the state of, but you do NOT want to actually spawn this object as an entity in the map (this would require the mappers to place a lot of weird invisible entities on every map, just to store specialized data).
If you take a look at src/game/game_local.cpp, you will see 2 relevant methods: idGameLocal::SaveGame and idGameLocal::InitFromSave . Within these, a bunch of calls are made to the savegame and restore game to save and restore objects. So how do you put your object in there? I will try to explain that in the following tutorial.
Step 1. Add Your Class to the Id Class Heirarchy
If your new class is not already derived from an Id class, you must add it to the Id class heirarchy by inheriting from idClass and call the proper macros to let the Id class heirarchy know about your class.
NOTE: Be aware that adding your class to the Id class heirarchy may cause some methods to be called automatically in certain situations, if they have a certain name (such as Init, Shutdown). Therefore if you don't want your method called automatically, you should name it something else. See /src/game/gamesys/class.h for a complete list of methods on idClass that may be called automatically.
Step 1A
Put the macro CLASS_PROTOTYPE( <your class name> ) within your class declaration in the header file.
Example:
In myclass.h
class myClass : public idClass { CLASS_PROTOTYPE( myClass ); public: // [...]
Step 1B
Put the macro: CLASS_DECLARATION in your implementation file. In this case we will NOT add any scripting events to the class, because, from the start, we do not intend to ever spawn this class into an entity, so it does not make sense to define scripts on it, as scripts are primarily run from entities.
The syntax is CLASS_DECLARATION( <base class>, <your derived class> )
Example:
In myclass.cpp:
CLASS_DECLARATION( idClass, myClass ) END_CLASS
Step 2. Write Your Save Method
Write the method Save( idSaveFile *save ) const for your class. This will be called automatically by Id during the save process. Save and Restore methods typically utilize idSaveFile methods to read and write relevant data. Look at src/game/gamesys/savegame.h for an explanation of idSaveFile methods.
Example:
void myClass::Save( idSaveGame *saveGame ) const { // write an integer member variable to the save file saveGame->WriteInt( m_intVar ); return; }
Step 3. Write Your Restore Method
A restore method reads relevant data from the savefile.
Key Point: (Thanks to Sparhawk for figuring this out) Since your object is not going to be spawned, it will not be copied to the entities list after being restored. When restored, your object is not automatically copied ANYWHERE, and only exists in the scope of idGameLocal::InitFromSave. When InitFromSave is finished loading everything, and the game exits from this method (which it does before starting the map), your object will go out of scope and WILL BE DESTROYED, unless you copy it somewhere else yourself.
In order to avoid destruction when idGameLocal::InitFromSave exits, your restore method must copy the instance of your class somewhere, in addition to loading the data from the savefile. It could copy it to a global variable, static member variable in the class, whatever.
Example:
Assuming that elsewhere you have declared a global, and that you also have a working copy constructor for your class, etc.:
Somewhere else with global scope:
myClass g_globalMyClass;
In myClass.cpp
void myClass::Restore( idRestoreGame *SaveGame ) { // read in your data, in this case an integer member var SaveGame->ReadInt( m_intVar ); // copy this instance somewhere else // In this case we assume that a global of type myClass has been declared // elsewhere. It could also be a static member variable of type myClass. g_globalMyClass = *this; return; }
Step 4. Add your object to the SaveGame method in idGameLocal
idSaveGame::AddObject adds any object that's been derived from idClass to a savefile, and registers the object within the savefile so that it can be restored easily later.
We want to add our object in when everything else is saved, using idSaveGame::AddObject. As far as I can tell, you can put it in anywhere in this method, as long as it's before the call to savegame.WriteObjectList(), since your object must be added before the object list is written to the savefile.
To be safe, I put it in right before savegame.WriteObjectList().
Example:
Assuming you have declared myClass myObject elsewhere, and are using it to store data.
In idGameLocal::SaveGame
// [...] // Add your object savegame.AddObject( &myObject ); // write out complete object list savegame.WriteObjectList(); // [...]
The save method for the instance of your class in g_globalMyClass will now be called automatically now when the game is saved, and the savefile will know about your object when it comes time to restore.
NOTE: AddObject takes a reference to your object. If you want to use forward-declaration of your myClass object in game_local.cpp, just call AddObject with the reference to your object.
Step 5. You're Done!
The good news is, in game_local::InitFromSave, your object will now be restored automatically, when idRestoreGame::RestoreObjects is called, which goes thru all the objects that were added to the savefile and calls the Restore method on them.
Remember to have your restore method copy your object somewhere else, since your object is not a spawnable entity and it will otherwise go out of scope and be destroyed when idGameLocal::InitFromSave exits.
Addendum: Saving Entities
Automatic Stuff
Just to emphasize again, the above tutorial is for saving something that's not necessarily an entity. If you derive from idEntity, your object will automatically be saved in idGameLocal::SaveGame. In InitFromSave, it will be automatically restored, and (I think) copied to the entities[] array.
(although I thought gameLocal.entities[] was an array of entity pointers, not actual entities, so it may be copied somewhere else... in any cases, it's copied to the same array where all the spawned entities are stored)
All you have to do is put your entity on the map, and all of this happens automatically.
Must write Save/Restore methods in your class if you want to Save/Restore data that's not in the derived class
However, if you do not specify a Save method, it will call the one on the base class/superclass of your entity. So if you have a new data member in your class that was not in the base class, and you want it saved and restored, you MUST write your own myClass::Save and myClass::Restore methods, as described above (but don't worry about the copying over for the Restore method).
I think that idSaveGame::SaveObject (the method that's called to save your object) will keep calling Save all the way up the inheritence chain, so I don't believe you need to inherit the Save method of the base class in your new class' Save method. I'm not positive about this though.
If you want an example of this, the code would be almost the same as the Save and Restore methods in the first post, assuming your new data is some member variable m_intVar. In this case, since we derived from idEntity, you only need to call saveGame->ReadInt in myClass::Restore. You do NOT need to copy your object somewhere as was done above.
Note: Spawnargs are already saved by default when an entity is saved, so if the only new data stored on your new entity is stored in spawnargs, you don't need to write Save/Restore methods.
Addendum 2
Sweet, TinMan over at D3World found another way to restore the object by adding Savefile->ReadObject in the "Init from Savegame" section of game_local.cpp. ReadObject takes a pointer, so I thought it would just turn into a bad pointer when InitFromSave exited, but apparently it doesn't. So you can use ReadObject to copy your object, instead of copying it in yourObject::Restore.
Of course this means you have to maintain your objects saving/restoring in both idGameLocal::SaveGame and idGameLocal::InitFromSave .. with my method you only need to add the object in SaveGame and then it's automatically restored in InitFromSave. Anyway, here is his method copied from D3World:
Follow the tutorial for the most part. Set up your class as a global pointer to an instance of your class, create new instance in idGameLocal::Init. Don't copy out to global in your class ::Restore Clear your object in idGameLocal::MapShutdown
In idGameLocal::SaveGame near the end
// [...] // TinMan: Save object index to this spot in file savegame.WriteObject( myObject ); // write out pending events idEvent::Save( &savegame ); savegame.Close(); }
In idGameLocal::InitFromSaveGame near the end
// [...] // TinMan: Overwrite your global pointer with the restored instance. savegame.ReadObject( reinterpret_cast<idClass *&>( overGame ) ); // Read out pending events idEvent::Restore( &savegame ); savegame.RestoreObjects(); // [...]
Important Note: For any idSaveGame::Write function you must have a corresponding idSaveGame::Read function in the same order since the savefile is being operated on in a stream.
Further Notes:
- idSaveGame::Close
- Calls ::Save on all objects in it's list (you know the one you added to via AddObject).
- idRestoreGame::RestoreObjects
- Yep you guessed it, calls ::Restore on all objects.