Save/Load System

Started by
20 comments, last by Black Knight 15 years, 8 months ago
I did some searching on the forum for this and the only thing people mention is to do some kind of versioning. How can I implement a save/load system so that I can load old saved maps.Currently I have a world class which has a map class member.There is no data in the world class to be saved the map class is the class that I want to save - restore. Here is the world class :

class World
{
public:
    World(DAEnginePtr engine);
    ~World();
    
        
	//
	//	Loads a map from the disk to the Map member
	//
	void				loadMap(DARendererPtr renderer,const std::string& filename);
	//
	//	Saves the map member to the disk
	//
	void				saveMap(const std::string& filename)const;

	//
	//	Frees all D3DPOOL_DEFAULT resources
	//  Calls onLostDevice on member objects
	//
	void				onLostDevice();

	//
	// After lost device create resources again
	// Calls onResetDevice on member objects
	//
	void				onResetDevice(DARenderer* renderer);

	//
	// Update world
	// Calls map update
	//
    void				update(DARenderer* renderer,float elapsedTime);

   
	
	void setMap(boost::shared_ptr<Map> map){m_Map = map;}
	boost::shared_ptr<Map> getMap(){return m_Map;}
	
private:
	
	/*
	 *	Current map
	 */
	boost::shared_ptr<Map> m_Map;
	
	
    /*
     *	Loads the shaders used by the game and the editor
     */
	void				loadShaders(DAEngine* engine);
	void				loadTextures(DAEngine* engine);
	void				loadModels(DAEngine* engine);

};



And the map class :

class Map
{
public:
    // Creates the map according to the tMapCreateParams
	Map(boost::shared_ptr<DARenderer> renderer,const tMapCreateParams* params);
	
	// Creates the map from a file on the disk
	Map(DARendererPtr renderer,const std::string& filename);
   
	//destructor
	~Map();
   	
	
	//
	// Save this map object to file
	//
	void save(const std::string& filename);

	//
	// Save all entities to file                                                                     
	//
	void saveEntities(std::ofstream& file);
    
    // Updates game objects,detect visibilities
	void update(DARenderer* renderer,float elapsedTime);

	
	// Processes collisions between game objects(camera-terrain etc.)
	void processCollisions(DARenderer* renderer,float elapsedTime);

    //Places forest on the map.Randomly selects forestCount locations 
	//and puts treeCount trees around that location
	void placeForest(boost::shared_ptr<DARenderer> renderer,
					 const STMath::Vector3& forestPosition,
					 UINT treeCount,UINT forestRadius,
					 STWin::ProgressBar *progressBar);
	/*
	 *	Changes the sealevel of the map
	 */
	void changeSeaLevel(float newSeaLevel);

	/*
	 *	Called when the d3d device is lost 
	 *	frees D3DPOOL_DEFAULT resources
	 */
	void onLostDevice();
	/*
	 *	Called when the d3d devices is reset
	 *  recreates D3DPOOL_DEFAULT resources
	 */
	void onResetDevice(DARenderer* renderer);

	//Entity		
	void				addEntity(boost::shared_ptr<Entity> addEntity);
	void				deleteEntity(const Entity* entity);
	boost::shared_ptr<Entity> getEntity(int entity)const {return m_Entities[entity];}
	boost::shared_ptr<Entity> getVisibleEntity(int entity)const {return m_VisibleEntities[entity];}
	size_t				getEntityCount()const {return m_Entities.size();}
	size_t				getVisibleEntityCount() const{return m_VisibleEntities.size();}

    //Data Access
    //set/get Name
	void		setName(const std::string& name){m_Name = name;}
	const std::string&  getName()const{return m_Name;}
	
	  
    
    boost::shared_ptr<GeoMipmapTerrain> getTerrain(){return m_Terrain;}
	boost::shared_ptr<Water> getWater(){return m_Water;}
	boost::shared_ptr<SkyPlane> getSkyPlane(){return m_SkyPlane;}

	//static data
	static const float MorningColor[4];
protected:
    //
    // name of the map appearing ingame
	//
    std::string			m_Name;	
    
	//
	// the terrain of this map
	//
	boost::shared_ptr<GeoMipmapTerrain> m_Terrain;

	//
	// The water of the map(null if map does not have water)
	// 
	boost::shared_ptr<Water> m_Water;

	//
	// The skyplane over the map
	//
	boost::shared_ptr<SkyPlane> m_SkyPlane;

	//
	// Entities on the map
	//
	std::vector<boost::shared_ptr<Entity>> m_Entities;

	//
	// visible entities.(filled inside Map::update)
	//
	std::vector<boost::shared_ptr<Entity>> m_VisibleEntities;

};



As you can see the map class holds the other objects and they will need to get saved too. Anyway first I was thinking of giving each class a save load method or make another contructor for the class which constructs the object from a file or data stream.But I don't know how to handle old save games in this approach. So I thought I could write a SaveLoadManager class which saves loads the world. This class can have different save/load method pairs for different versions of save files.For example :

class SaveLoadManager
{
public:

void load(World* world,const std::string& filename);
void save(World* world,const std::string& filename,int version);

void save_version_1(World* world,const std::string& filename);
void load_version_1(World* world,const std::string& filename);

void save_version_2(World* world,const std::string& filename);
void load_version_2(World* world,const std::string& filename);
};

//implementation
void SaveLoadManager::load(World* world,const std::string& filename)
{
 get version from file
 call the appropriate load_version_*() function
}
void SaveLoadManager::save(World* world,const std::string& filename,int version)
{
write version number to file
call the appropriate save_version_*() function
}




Each time I change map,world,terrain I would need a new save_version_* and a save_version_* to save load that new states.Also I would need to write the save file version to the file and call the appropriate load_version_* function. Another problem with this is once I saved some maps with a save/load pair I must not change them again or the maps saved before will be broken. This looks like a very clumsy way of saving loading.So what can I do fellows? Edit:Also this will create lots of code duplication in the save_version_* load_version_* methods. :|
Advertisement
What about using text-based save files? If you use something like XML, you can easily ignore information you're not interested in (E.g. if you add a field in version 3, and you're loading version 1, then there'll be information for versions 2 and 3 you're not interested in).

Alternatively, if you really want to use a binary format, you could do what Win32 does - each struct has a size value as it's first member. If the version changes, you increase that size and add new members at the end of the struct.

Older code will ignore the new struct members, and should use the size member to skip the correct number of bytes, and newer code can use the new members without problem.
Basically what Evil Steve said, but instead of using sizes (which can be unreliable), use unique ID's. If you ever need to remove data, you deprecate the old ID's without reusing them, and new data just uses new ID's.
Quote:Original post by Evil Steve
Alternatively, if you really want to use a binary format, you could do what Win32 does - each struct has a size value as it's first member. If the version changes, you increase that size and add new members at the end of the struct.

Older code will ignore the new struct members, and should use the size member to skip the correct number of bytes, and newer code can use the new members without problem.

Hmm this sound promising ill give it a try.
Quote:Original post by Zipster
Basically what Evil Steve said, but instead of using sizes (which can be unreliable), use unique ID's. If you ever need to remove data, you deprecate the old ID's without reusing them, and new data just uses new ID's.


What do you mean using IDs instead of sizes?
How would i know how much data to skip when loading an old file for example
I assume Zipster means giving each struct an ID number, and then when you load the save file you'll load a number of structs with IDs. Your code then just needs to pull out the structs with particular IDs. Old code would see unrecognised IDs but ignore them, and new code can load the older IDs and the newer ones too.
I still don't see how ids will work.
I would give my map,terrain,sky plane classes unique ids and write that ids to file with the classes?
Then later on when I add new data members to lets say terrain I will change the unique ID?
And when loading if i get a old id for the terrain ill use old loading code and if i get a new ID i will use new loading code?
Lol im confused.
Another thing is I can't do just out.write(size,terrain) so ill basically load/save each member one by one or ill create save/load structs which will contain only the data that will be saved for each class.
Then save that struct in one write call.
Quote:Original post by Black Knight
I still don't see how ids will work.
I would give my map,terrain,sky plane classes unique ids and write that ids to file with the classes?
Then later on when I add new data members to lets say terrain I will change the unique ID?
And when loading if i get a old id for the terrain ill use old loading code and if i get a new ID i will use new loading code?
Lol im confused.
Another thing is I can't do just out.write(size,terrain) so ill basically load/save each member one by one or ill create save/load structs which will contain only the data that will be saved for each class.
Then save that struct in one write call.
Something like this:
struct DataChunk{   DataChunk() {}   DataChunk(unsigned int nChunkID, unsigned int nChunkSize) :      nID(nChunkID), nSize(nChunkSize) {}   unsigned int nID;   unsigned int nSize;};struct WorldData : public DataChunk{   WorldData() : DataChunk('WRD1', sizeof(WorldData)) {}   int nWidth;   int nHeight;   // Any other data members here};

When you load your save file, you'd read in sizeof(DataChunk) bytes, which will tell you the size and ID of the chunk you're about to read. Then you read in that many bytes (minus sizeof(DataChunk)). That gives you a buffer containing the chunk information.
Once you've got that, you can store the chunk in memory and read all the other chunks. And once that's done, you can get the rest of your code to parse the list of read chunks and grab the ones it needs (Or you could have your system notify the relevant classes that you've just read a chunk).

The point is that all your data is wrapped up in these structs, and is accessed by a unique ID. When you change a struct, you also give it a new ID so that older code doesn't try to read this chunk which is not in the same format it's expecting.


However - do you really need to support multiple save versions?
Well at the moment my world editor can create modify terrains,place entities rotate scale them.Paint the terrain with different textures etc.
I want to be able to save the maps so that I can load them in the game and use them.
So when I spend some time and create a map in the editor now I don't want to lose all the placed entities the terrain modifications later when I add more data to one of the classes.This is why Im trying to be able to load old saved maps.

In the example structures you posted,I will need to convert my map,world,terrain classes to these POD structs because my classes have pointers.Thats another pain too.


Quote:In the example structures you posted,I will need to convert my map,world,terrain classes to these POD structs because my classes have pointers.Thats another pain too.


Well, yes, but you have to do that anyway.

Have you read this yet?

This topic is closed to new replies.

Advertisement