Browse Source

Started working on objects serialization

AlexVinS 10 years ago
parent
commit
9cc3dae5fe

+ 57 - 0
lib/mapObjects/CObjectHandler.cpp

@@ -136,6 +136,19 @@ CGObjectInstance::~CGObjectInstance()
 {
 }
 
+const std::string & CGObjectInstance::getStringId() const
+{
+	//todo: getStringId use real object type
+	if(stringId == "")
+	{
+		boost::format fmt("%s_%d");
+		fmt % "object" % id.getNum();
+		stringId = fmt.str();
+	}
+
+	return stringId;
+}
+
 void CGObjectInstance::setOwner(PlayerColor ow)
 {
 	tempOwner = ow;
@@ -311,6 +324,50 @@ bool CGObjectInstance::passableFor(PlayerColor color) const
 	return false;
 }
 
+void CGObjectInstance::writeJson(JsonNode & json, bool withState) const
+{
+	json.setType(JsonNode::DATA_STRUCT);
+
+	appearance.writeJson(json["template"]);
+	writeJsonOptions(json["options"]);
+	if(withState)
+		writeJsonState(json["state"]);
+}
+
+void CGObjectInstance::readJson(const JsonNode & json, bool withState)
+{
+	if(json.getType() != JsonNode::DATA_STRUCT)
+	{
+		logGlobal->error("Invalid object instance data");
+		return;
+	}
+	appearance.readJson(json["template"]);
+	readJsonOptions(json["options"]);
+	if(withState)
+		readJsonState(json["state"]);
+}
+
+void CGObjectInstance::writeJsonOptions(JsonNode & json) const
+{
+
+}
+
+void CGObjectInstance::readJsonOptions(const JsonNode & json)
+{
+
+}
+
+void CGObjectInstance::writeJsonState(JsonNode & json) const
+{
+
+}
+
+void CGObjectInstance::readJsonState(const JsonNode & json)
+{
+
+}
+
+
 CGObjectInstanceBySubIdFinder::CGObjectInstanceBySubIdFinder(CGObjectInstance * obj) : obj(obj)
 {
 

+ 30 - 1
lib/mapObjects/CObjectHandler.h

@@ -35,7 +35,7 @@ public:
 	virtual void newTurn() const;
 	virtual void initObj(); //synchr
 	virtual void setProperty(ui8 what, ui32 val);//synchr
-	
+
 	//Called when queries created DURING HERO VISIT are resolved
 	//First parameter is always hero that visited object and triggered the query
 	virtual void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const;
@@ -116,6 +116,8 @@ public:
 	CGObjectInstance();
 	~CGObjectInstance();
 
+	const std::string & getStringId() const;
+
 	/// "center" tile from which the sight distance is calculated
 	int3 getSightCenter() const;
 
@@ -164,17 +166,44 @@ public:
 
 	//friend class CGameHandler;
 
+	///Entry point of binary (de-)serialization
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & pos & ID & subID & id & tempOwner & blockVisit & appearance;
 		//definfo is handled by map serializer
 	}
+
+	///Entry point of Json serialization
+	void writeJson(JsonNode & json, bool withState = false) const;
+
+	///Entry point of Json de-serialization
+	void readJson(const JsonNode & json, bool withState = false);
+
 protected:
 	/// virtual method that allows synchronously update object state on server and all clients
 	virtual void setPropertyDer(ui8 what, ui32 val);
 
 	/// Gives dummy bonus from this object to hero. Can be used to track visited state
 	void giveDummyBonus(ObjectInstanceID heroID, ui8 duration = Bonus::ONE_DAY) const;
+
+	///Saves object-type specific options
+	///(!) do not forget to call inherited method first when overriding
+	virtual void writeJsonOptions(JsonNode & json) const;
+
+	///Loads object-type specific options
+	///(!) do not forget to call inherited method  first when overriding
+	virtual void readJsonOptions(const JsonNode & json);
+
+	///Saves object-type specific state
+	///(!) do not forget to call inherited method  first when overriding
+	virtual void writeJsonState(JsonNode & json) const;
+
+	///Loads object-type specific state
+	///(!) do not forget to call inherited method  first  when overriding
+	virtual void readJsonState(const JsonNode & json);
+
+private:
+	mutable std::string stringId;///<alternate id, dynamically generated, do not serialize
 };
 
 /// function object which can be used to find an object with an specific sub ID

+ 6 - 0
lib/mapObjects/ObjectTemplate.cpp

@@ -256,6 +256,12 @@ void ObjectTemplate::readJson(const JsonNode &node)
 		printPriority = 0; //default value
 }
 
+void ObjectTemplate::writeJson(JsonNode & node) const
+{
+	//todo: ObjectTemplate::writeJson
+}
+
+
 ui32 ObjectTemplate::getWidth() const
 {
 	return usedTiles.empty() ? 0 : usedTiles.front().size();

+ 1 - 0
lib/mapObjects/ObjectTemplate.h

@@ -74,6 +74,7 @@ public:
 	void readMsk();
 	void readMap(CBinaryReader & reader);
 	void readJson(const JsonNode & node);
+	void writeJson(JsonNode & node) const;
 
 	bool operator==(const ObjectTemplate& ot) const { return (id == ot.id && subid == ot.subid); }
 

+ 91 - 1
lib/mapping/MapFormatJson.cpp

@@ -16,6 +16,9 @@
 #include "CMap.h"
 #include "../CModHandler.h"
 #include "../VCMI_Lib.h"
+#include "../mapObjects/ObjectTemplate.h"
+#include "../mapObjects/CObjectHandler.h"
+#include "../mapObjects/CObjectClassesHandler.h"
 #include "../StringConstants.h"
 
 namespace TriggeredEventsDetail
@@ -117,11 +120,27 @@ namespace MapHeaderDetail
 	};
 }
 
+static std::string tailString(const std::string & input, char separator)
+{
+	std::string ret;
+	size_t splitPos = input.find(separator);
+	if (splitPos != std::string::npos)
+		ret = input.substr(splitPos + 1);
+	return ret;
+}
+
+static si32 extractNumber(const std::string & input, char separator)
+{
+	std::string tmp = tailString(input, separator);
+	return atoi(tmp.c_str());
+}
+
 ///CMapFormatJson
 const int CMapFormatJson::VERSION_MAJOR = 1;
 const int CMapFormatJson::VERSION_MINOR = 0;
 
 const std::string CMapFormatJson::HEADER_FILE_NAME = "header.json";
+const std::string CMapFormatJson::OBJECTS_FILE_NAME = "objects.json";
 
 void CMapFormatJson::readTriggeredEvents(const JsonNode & input)
 {
@@ -264,7 +283,7 @@ void CMapLoaderJson::readMap()
 	readHeader();
 	map->initTerrain();
 	readTerrain();
-	//TODO:readMap
+	readObjects();
 }
 
 void CMapLoaderJson::readHeader()
@@ -542,6 +561,65 @@ void CMapLoaderJson::readTerrain()
 
 }
 
+CMapLoaderJson::MapObjectLoader::MapObjectLoader(CMapLoaderJson * _owner, const JsonMap::value_type& json):
+	owner(_owner), instance(nullptr),handler(nullptr),id(-1), jsonKey(json.first), configuration(json.second), internalId(extractNumber(jsonKey, '_'))
+{
+
+}
+
+void CMapLoaderJson::MapObjectLoader::construct()
+{
+	//find type handler
+	std::string typeName = configuration["type"].String(), subTypeName = configuration["subType"].String();
+	if(typeName.empty())
+	{
+		logGlobal->errorStream() << "Object type missing";
+		return;
+	}
+	if(subTypeName.empty())
+	{
+		logGlobal->errorStream() << "Object subType missing";
+		return;
+	}
+
+	si32 type = owner->getIdentifier("object", typeName);
+
+//	VLC->objtypeh->getHandlerFor()
+//TODO:MapObjectLoader::construct()
+}
+
+void CMapLoaderJson::MapObjectLoader::configure()
+{
+//TODO:MapObjectLoader::configure()
+}
+
+void CMapLoaderJson::readObjects()
+{
+	LOG_TRACE(logGlobal);
+
+	std::vector<std::unique_ptr<MapObjectLoader>> loaders;//todo: optimize MapObjectLoader memory layout
+
+	const JsonNode data = readJson(OBJECTS_FILE_NAME);
+
+	//get raw data
+	for(const auto & p : data.Struct())
+		loaders.push_back(vstd::make_unique<MapObjectLoader>(this, p));
+
+	auto sortInfos = [](const std::unique_ptr<MapObjectLoader> & lhs, const std::unique_ptr<MapObjectLoader> & rhs) -> bool
+	{
+		return lhs->internalId < rhs->internalId;
+	};
+	boost::sort(loaders, sortInfos);//this makes instance ids consistent after save-load, needed for testing
+	//todo: use internalId in CMap
+
+	for(auto & ptr : loaders)
+		ptr->construct();
+
+	//configure objects after all objects are constructed so we may resolve internal IDs even to actual pointers OTF
+	for(auto & ptr : loaders)
+		ptr->configure();
+}
+
 ///CMapSaverJson
 CMapSaverJson::CMapSaverJson(CInputOutputStream * stream):
 	CMapFormatZip(stream),
@@ -576,6 +654,7 @@ void CMapSaverJson::saveMap(const std::unique_ptr<CMap>& map)
 	this->map = map.get();
 	writeHeader();
 	writeTerrain();
+	writeObjects();
 }
 
 void CMapSaverJson::writeHeader()
@@ -751,3 +830,14 @@ void CMapSaverJson::writeTerrain()
 		addToArchive(underground, "underground_terrain.json");
 	}
 }
+
+void CMapSaverJson::writeObjects()
+{
+	JsonNode data(JsonNode::DATA_STRUCT);
+
+	for(const CGObjectInstance * obj : map->objects)
+		obj->writeJson(data[obj->getStringId()], false);
+
+	addToArchive(data, OBJECTS_FILE_NAME);
+}
+

+ 102 - 48
lib/mapping/MapFormatJson.h

@@ -15,21 +15,24 @@
 
 #include "../filesystem/CZipSaver.h"
 #include "../filesystem/CZipLoader.h"
+#include "../GameConstants.h"
 
 class TriggeredEvent;
 struct TerrainTile;
 struct PlayerInfo;
+class CGObjectInstance;
+class AObjectTypeHandler;
 
 class DLL_LINKAGE CMapFormatJson
 {
-public:	
+public:
 	static const int VERSION_MAJOR;
-	static const int VERSION_MINOR;	
-	
-	static const std::string HEADER_FILE_NAME;
+	static const int VERSION_MINOR;
 
+	static const std::string HEADER_FILE_NAME;
+	static const std::string OBJECTS_FILE_NAME;
 protected:
-	
+
 	/** ptr to the map object which gets filled by data from the buffer or written to buffer */
 	CMap * map;
 
@@ -37,8 +40,8 @@ protected:
 	 * ptr to the map header object which gets filled by data from the buffer.
 	 * (when loading map and mapHeader point to the same object)
 	 */
-	std::unique_ptr<CMapHeader> mapHeader;	
-	
+	std::unique_ptr<CMapHeader> mapHeader;
+
 	/**
 	 * Reads triggered events, including victory/loss conditions
 	 */
@@ -53,11 +56,11 @@ protected:
 	 * Reads one of triggered events
 	 */
 	void readTriggeredEvent(TriggeredEvent & event, const JsonNode & source);
-	
+
 	/**
 	 * Writes one of triggered events
 	 */
-	void writeTriggeredEvent(const TriggeredEvent & event, JsonNode & dest);			
+	void writeTriggeredEvent(const TriggeredEvent & event, JsonNode & dest);
 };
 
 class DLL_LINKAGE CMapPatcher : public CMapFormatJson, public IMapPatcher
@@ -69,14 +72,14 @@ public:
 	 * @param stream. A stream containing the map data.
 	 */
 	CMapPatcher(JsonNode stream);
-		
+
 public: //IMapPatcher
 	/**
 	 * Modifies supplied map header using Json data
 	 *
 	 */
 	void patchMapHeader(std::unique_ptr<CMapHeader> & header) override;
-	
+
 private:
 	/**
 	 * Reads subset of header that can be replaced by patching.
@@ -84,7 +87,7 @@ private:
 	void readPatchData();
 
 
-	const JsonNode input;	
+	const JsonNode input;
 };
 
 class DLL_LINKAGE CMapFormatZip : public CMapFormatJson
@@ -100,7 +103,7 @@ class DLL_LINKAGE CMapLoaderJson : public CMapFormatZip, public IMapLoader
 {
 public:
 	/**
-	 * Default constructor.
+	 * Constructor.
 	 *
 	 * @param stream a stream containing the map data
 	 */
@@ -120,9 +123,28 @@ public:
 	 */
 	std::unique_ptr<CMapHeader> loadMapHeader() override;
 
-private:	
+private:
+
+	struct MapObjectLoader
+	{
+		MapObjectLoader(CMapLoaderJson * _owner, const JsonMap::value_type & json);
+		CMapLoaderJson * owner;
+		CGObjectInstance * instance;
+		std::shared_ptr<AObjectTypeHandler> handler;
+		ObjectInstanceID id;
+		std::string jsonKey;//full id defined by map creator
+		const JsonNode & configuration;
+		si32 internalId;//unique part of id defined by map creator (also = quest identifier)
+		///constructs object (without configuration)
+		void construct();
+
+		///configures object
+		void configure();
+
+	};
+
 	si32 getIdentifier(const std::string & type, const std::string & name);
-	
+
 	/**
 	 * Reads complete map.
 	 */
@@ -137,69 +159,101 @@ private:
 	 * Reads player information.
 	 */
 	void readPlayerInfo(const JsonNode & input);
-	
+
 	/**
 	 * Reads one player information.
 	 */
 	void readPlayerInfo(PlayerInfo & info, const JsonNode & input);
-	
+
+	/**
+	 * Reads team settings to header
+	 * @param input serialized header
+	 */
 	void readTeams(const JsonNode & input);
-	
+
 	void readTerrainTile(const std::string & src, TerrainTile & tile);
-	
-	void readTerrainLevel(const JsonNode & src, const int index);	
-	
+
+	void readTerrainLevel(const JsonNode & src, const int index);
+
 	void readTerrain();
-	
+
+	/**
+	 * Loads all map objects from zip archive
+	 */
+	void readObjects();
+
 	const JsonNode readJson(const std::string & archiveFilename);
-	
-	CZipLoader loader;
+
+	CZipLoader loader;///< object to handle zip archive operations
 };
 
 class DLL_LINKAGE CMapSaverJson : public CMapFormatZip, public IMapSaver
 {
 public:
 	/**
-	 * Default constructor.
+	 * Constructor.
 	 *
-	 * @param stream a stream to save the map to
+	 * @param stream a stream to save the map to, will contain zip archive
 	 */
-	CMapSaverJson(CInputOutputStream * stream);	
-	
+	CMapSaverJson(CInputOutputStream * stream);
+
 	~CMapSaverJson();
-	
+
 	/**
 	 * Actually saves the VCMI/Json map into stream.
-	 *
-	 */	
-	void saveMap(const std::unique_ptr<CMap> & map) override;	
+	 */
+	void saveMap(const std::unique_ptr<CMap> & map) override;
 private:
+
 	/**
-	 * Save @data as json file with specified @filename
-	 *
-	 */		
+	 * Saves @data as json file with specified @filename
+	 */
 	void addToArchive(const JsonNode & data, const std::string & filename);
-	
+
+	/**
+	 * Saves header to zip archive
+	 */
 	void writeHeader();
 
 	/**
-	 * Save all players info to header
-	 * @param output serialized header	 
-	 */	
+	 * Saves all players info to header
+	 * @param output serialized header
+	 */
 	void writePlayerInfo(JsonNode & output);
+
 	/**
-	 * Save one player info
-	 * @param output empty object	 
-	 */	
+	 * Saves one player info
+	 * @param output empty object
+	 */
 	void writePlayerInfo(const PlayerInfo & info, JsonNode & output);
-	
-	void writeTeams(JsonNode & output);	
 
+	/**
+	 * Saves team settings to header
+	 * @param output serialized header
+	 */
+	void writeTeams(JsonNode & output);
+
+	/**
+	 * Encodes one tile into string
+	 * @param tile tile to serialize
+	 */
 	const std::string writeTerrainTile(const TerrainTile & tile);
-	
+
+	/**
+	 * Saves map level into json
+	 * @param index z coordinate
+	 */
 	JsonNode writeTerrainLevel(const int index);
-		
+
+	/**
+	 * Saves all terrain into zip archive
+	 */
 	void writeTerrain();
-		
-	CZipSaver saver;	
+
+	/**
+	 * Saves all map objects into zip archive
+	 */
+	void writeObjects();
+
+	CZipSaver saver;///< object to handle zip archive operations
 };

+ 8 - 6
test/CMapEditManagerTest.cpp

@@ -83,8 +83,8 @@ BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_Type)
 		BOOST_CHECK(map->getTile(int3(5, 6, 1)).terType == ETerrainType::ROCK || map->getTile(int3(7, 8, 1)).terType == ETerrainType::ROCK);
 
 		//todo: add checks here and enable, also use smaller size
-		#if 0 
-		
+		#if 0
+
 		// next check
 		auto map2 = make_unique<CMap>();
 		map2->width = 128;
@@ -105,15 +105,15 @@ BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_Type)
 	}
 	catch(const std::exception & e)
 	{
-		logGlobal->errorStream() << "CMapEditManager_DrawTerrain_Type crash" << "\n" << e.what();	
+		logGlobal->errorStream() << "CMapEditManager_DrawTerrain_Type crash" << "\n" << e.what();
 		throw;
-	}	
+	}
 	logGlobal->infoStream() << "CMapEditManager_DrawTerrain_Type finish";
 }
 
 BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_View)
 {
-	logGlobal->infoStream() << "CMapEditManager_DrawTerrain_View start";	
+	logGlobal->infoStream() << "CMapEditManager_DrawTerrain_View start";
 	try
 	{
 		// Load maps and json config
@@ -151,8 +151,10 @@ BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_View)
 				const auto & posVector = posNode.Vector();
 				if(posVector.size() != 3) throw std::runtime_error("A position should consist of three values x,y,z. Continue with next position.");
 				int3 pos(posVector[0].Float(), posVector[1].Float(), posVector[2].Float());
-				logGlobal->infoStream() << boost::format("Test pattern '%s' on position x '%d', y '%d', z '%d'.") % patternStr % pos.x % pos.y % pos.z;
+#if 0
+				logGlobal->traceStream() << boost::format("Test pattern '%s' on position x '%d', y '%d', z '%d'.") % patternStr % pos.x % pos.y % pos.z;
 				CTerrainViewPatternUtils::printDebuggingInfoAboutTile(map.get(), pos);
+#endif // 0
 				const auto & originalTile = originalMap->getTile(pos);
 				editManager->getTerrainSelection().selectRange(MapRect(pos, 1, 1));
 				editManager->drawTerrain(originalTile.terType, &gen);

+ 14 - 1
test/MapComparer.cpp

@@ -148,9 +148,22 @@ void MapComparer::compareOptions()
 	BOOST_ERROR("Not implemented compareOptions()");
 }
 
+void MapComparer::compareObject(const CGObjectInstance * actual, const CGObjectInstance * expected)
+{
+	BOOST_CHECK_EQUAL(actual->getStringId(), expected->getStringId());
+	BOOST_CHECK_EQUAL(typeid(actual).name(), typeid(expected).name());//todo: remove and use just comparison
+}
+
 void MapComparer::compareObjects()
 {
-	BOOST_ERROR("Not implemented compareObjects()");
+	BOOST_CHECK_EQUAL(actual->objects.size(), expected->objects.size());
+
+	for(size_t idx = 0; idx < std::min(actual->objects.size(), expected->objects.size()); idx++)
+	{
+		BOOST_REQUIRE_EQUAL(idx, expected->objects[idx]->id.getNum());
+		BOOST_CHECK_EQUAL(idx, actual->objects[idx]->id.getNum());
+		compareObject(actual->objects[idx], expected->objects[idx]);
+	}
 }
 
 void MapComparer::compareTerrain()

+ 8 - 7
test/MapComparer.h

@@ -7,23 +7,24 @@
  * Full text of license available in license.txt file, in main folder
  *
  */
- 
+
 #pragma once
- 
+
 #include "../lib/mapping/CMap.h"
 
 struct MapComparer
 {
 	const CMap * actual;
 	const CMap * expected;
-	
+
 	void compareHeader();
 	void compareOptions();
+	void compareObject(const CGObjectInstance * actual, const CGObjectInstance * expected);
 	void compareObjects();
-	void compareTerrain();	
-	
-	void compare(); 
-	
+	void compareTerrain();
+
+	void compare();
+
 	void operator() (const std::unique_ptr<CMap>& actual, const std::unique_ptr<CMap>& expected);
 };