From 3d1b1f4ba8b4e186f314ad1de5a7ba55bf713cbc Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 10 Sep 2016 03:28:11 +0300 Subject: [PATCH] Add files for rebased serializer refactoring done by Ivan --- lib/serializer/BinaryDeserializer.cpp | 104 ++++ lib/serializer/BinaryDeserializer.h | 533 +++++++++++++++++++++ lib/serializer/BinarySerializer.cpp | 74 +++ lib/serializer/BinarySerializer.h | 373 ++++++++++++++ lib/serializer/CLoadIntegrityValidator.cpp | 65 +++ lib/serializer/CLoadIntegrityValidator.h | 31 ++ lib/serializer/CMemorySerializer.cpp | 40 ++ lib/serializer/CMemorySerializer.h | 43 ++ lib/serializer/CSerializer.cpp | 49 ++ lib/serializer/CSerializer.h | 201 ++++++++ lib/serializer/CTypeList.cpp | 122 +++++ lib/serializer/CTypeList.h | 230 +++++++++ lib/serializer/Connection.cpp | 280 +++++++++++ lib/serializer/Connection.h | 109 +++++ 14 files changed, 2254 insertions(+) create mode 100644 lib/serializer/BinaryDeserializer.cpp create mode 100644 lib/serializer/BinaryDeserializer.h create mode 100644 lib/serializer/BinarySerializer.cpp create mode 100644 lib/serializer/BinarySerializer.h create mode 100644 lib/serializer/CLoadIntegrityValidator.cpp create mode 100644 lib/serializer/CLoadIntegrityValidator.h create mode 100644 lib/serializer/CMemorySerializer.cpp create mode 100644 lib/serializer/CMemorySerializer.h create mode 100644 lib/serializer/CSerializer.cpp create mode 100644 lib/serializer/CSerializer.h create mode 100644 lib/serializer/CTypeList.cpp create mode 100644 lib/serializer/CTypeList.h create mode 100644 lib/serializer/Connection.cpp create mode 100644 lib/serializer/Connection.h diff --git a/lib/serializer/BinaryDeserializer.cpp b/lib/serializer/BinaryDeserializer.cpp new file mode 100644 index 000000000..538b5b536 --- /dev/null +++ b/lib/serializer/BinaryDeserializer.cpp @@ -0,0 +1,104 @@ +#include "StdInc.h" +#include "BinaryDeserializer.h" + +#include "../registerTypes/RegisterTypes.h" + +/* + * BinaryDeserializer.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +extern template void registerTypes(BinaryDeserializer & s); + +CLoadFile::CLoadFile(const boost::filesystem::path & fname, int minimalVersion /*= version*/): serializer(this) +{ + registerTypes(serializer); + openNextFile(fname, minimalVersion); +} + +CLoadFile::~CLoadFile() +{ +} + +int CLoadFile::read(void * data, unsigned size) +{ + sfile->read((char*)data,size); + return size; +} + +void CLoadFile::openNextFile(const boost::filesystem::path & fname, int minimalVersion) +{ + assert(!serializer.reverseEndianess); + assert(minimalVersion <= version); + + try + { + fName = fname.string(); + sfile = make_unique(fname, std::ios::binary); + sfile->exceptions(std::ifstream::failbit | std::ifstream::badbit); //we throw a lot anyway + + if(!(*sfile)) + THROW_FORMAT("Error: cannot open to read %s!", fName); + + //we can read + char buffer[4]; + sfile->read(buffer, 4); + if(std::memcmp(buffer,"VCMI",4)) + THROW_FORMAT("Error: not a VCMI file(%s)!", fName); + + serializer & serializer.fileVersion; + if(serializer.fileVersion < minimalVersion) + THROW_FORMAT("Error: too old file format (%s)!", fName); + + if(serializer.fileVersion > version) + { + logGlobal->warnStream() << boost::format("Warning format version mismatch: found %d when current is %d! (file %s)\n") % serializer.fileVersion % version % fName; + + auto versionptr = (char*)&serializer.fileVersion; + std::reverse(versionptr, versionptr + 4); + logGlobal->warnStream() << "Version number reversed is " << serializer.fileVersion << ", checking..."; + + if(serializer.fileVersion == version) + { + logGlobal->warnStream() << fname << " seems to have different endianness! Entering reversing mode."; + serializer.reverseEndianess = true; + } + else + THROW_FORMAT("Error: too new file format (%s)!", fName); + } + } + catch(...) + { + clear(); //if anything went wrong, we delete file and rethrow + throw; + } +} + +void CLoadFile::reportState(CLogger * out) +{ + out->debugStream() << "CLoadFile"; + if(!!sfile && *sfile) + { + out->debugStream() << "\tOpened " << fName << "\n\tPosition: " << sfile->tellg(); + } +} + +void CLoadFile::clear() +{ + sfile = nullptr; + fName.clear(); + serializer.fileVersion = 0; +} + +void CLoadFile::checkMagicBytes( const std::string &text ) +{ + std::string loaded = text; + read((void*)loaded.data(), text.length()); + if(loaded != text) + throw std::runtime_error("Magic bytes doesn't match!"); +} diff --git a/lib/serializer/BinaryDeserializer.h b/lib/serializer/BinaryDeserializer.h new file mode 100644 index 000000000..20c8a8937 --- /dev/null +++ b/lib/serializer/BinaryDeserializer.h @@ -0,0 +1,533 @@ + +/* + * BinaryDeserializer.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include + +#include "CTypeList.h" +#include "../mapObjects/CGHeroInstance.h" + +class CStackInstance; + +class DLL_LINKAGE CLoaderBase +{ +protected: + IBinaryReader * reader; +public: + CLoaderBase(IBinaryReader * r): reader(r){}; + + inline int read(void * data, unsigned size) + { + return reader->read(data, size); + }; +}; + +/// Main class for deserialization of classes from binary form +/// Effectively revesed version of BinarySerializer +class DLL_LINKAGE BinaryDeserializer : public CLoaderBase +{ + template + struct VariantLoaderHelper + { + Source & source; + std::vector> funcs; + + VariantLoaderHelper(Source & source): + source(source) + { + boost::mpl::for_each(std::ref(*this)); + } + + template + void operator()(Type) + { + funcs.push_back([&]() -> Variant + { + Type obj; + source.load(obj); + return Variant(obj); + }); + } + }; + + template + struct LoadIfStackInstance + { + static bool invoke(Ser &s, T &data) + { + return false; + } + }; + + template + struct LoadIfStackInstance + { + static bool invoke(Ser &s, CStackInstance* &data) + { + CArmedInstance *armedObj; + SlotID slot; + s.load(armedObj); + s.load(slot); + if(slot != SlotID::COMMANDER_SLOT_PLACEHOLDER) + { + assert(armedObj->hasStackAtSlot(slot)); + data = armedObj->stacks[slot]; + } + else + { + auto hero = dynamic_cast(armedObj); + assert(hero); + assert(hero->commander); + data = hero->commander; + } + return true; + } + }; + + template + struct ClassObjectCreator + { + static T *invoke() + { + static_assert(!std::is_abstract::value, "Cannot call new upon abstract classes!"); + return new T(); + } + }; + + template + struct ClassObjectCreator::value>::type> + { + static T *invoke() + { + throw std::runtime_error("Something went really wrong during deserialization. Attempted creating an object of an abstract class " + std::string(typeid(T).name())); + } + }; + +#define READ_CHECK_U32(x) \ + ui32 length; \ + load(length); \ + if(length > 500000) \ + { \ + logGlobal->warnStream() << "Warning: very big length: " << length;\ + reader->reportState(logGlobal); \ + }; + + template class CPointerLoader; + + class CBasicPointerLoader + { + public: + virtual const std::type_info * loadPtr(CLoaderBase &ar, void *data, ui32 pid) const =0; //data is pointer to the ACTUAL POINTER + virtual ~CBasicPointerLoader(){} + + template static CBasicPointerLoader *getApplier(const T * t=nullptr) + { + return new CPointerLoader(); + } + }; + + template class CPointerLoader : public CBasicPointerLoader + { + public: + const std::type_info * loadPtr(CLoaderBase &ar, void *data, ui32 pid) const override //data is pointer to the ACTUAL POINTER + { + BinaryDeserializer &s = static_cast(ar); + T *&ptr = *static_cast(data); + + //create new object under pointer + typedef typename std::remove_pointer::type npT; + ptr = ClassObjectCreator::invoke(); //does new npT or throws for abstract classes + s.ptrAllocated(ptr, pid); + //T is most derived known type, it's time to call actual serialize + ptr->serialize(s,s.fileVersion); + return &typeid(T); + } + }; + + CApplier applier; + + int write(const void * data, unsigned size); + +public: + bool reverseEndianess; //if source has different endianness than us, we reverse bytes + si32 fileVersion; + + std::map loadedPointers; + std::map loadedPointersTypes; + std::map loadedSharedPointers; + bool smartPointerSerialization; + bool saving; + + BinaryDeserializer(IBinaryReader * r): CLoaderBase(r) + { + saving = false; + fileVersion = 0; + smartPointerSerialization = true; + reverseEndianess = false; + } + + template + BinaryDeserializer & operator&(T & t) + { + this->load(t); + return * this; + } + + template < class T, typename std::enable_if < std::is_fundamental::value && !std::is_same::value, int >::type = 0 > + void load(T &data) + { + if(0) //for testing #989 + { + this->read(&data,sizeof(data)); + } + else + { + unsigned length = sizeof(data); + char* dataPtr = (char*)&data; + this->read(dataPtr,length); + if(reverseEndianess) + std::reverse(dataPtr, dataPtr + length); + } + } + + template < typename T, typename std::enable_if < is_serializeable::value, int >::type = 0 > + void load(T &data) + { + ////that const cast is evil because it allows to implicitly overwrite const objects when deserializing + typedef typename std::remove_const::type nonConstT; + nonConstT &hlp = const_cast(data); + hlp.serialize(*this,fileVersion); + } + template < typename T, typename std::enable_if < std::is_array::value, int >::type = 0 > + void load(T &data) + { + ui32 size = ARRAY_COUNT(data); + for(ui32 i = 0; i < size; i++) + load(data[i]); + } + + template < typename T, typename std::enable_if < std::is_enum::value, int >::type = 0 > + void load(T &data) + { + si32 read; + load( read ); + data = static_cast(read); + } + + template < typename T, typename std::enable_if < std::is_same::value, int >::type = 0 > + void load(T &data) + { + ui8 read; + load( read ); + data = static_cast(read); + } + + template < typename T, typename std::enable_if < std::is_same >::value, int >::type = 0 > + void load(T & data) + { + std::vector convData; + load(convData); + convData.resize(data.size()); + range::copy(convData, data.begin()); + } + + template + void load(std::vector &data, typename std::enable_if < !std::is_same::value, int >::type = 0) + { + READ_CHECK_U32(length); + data.resize(length); + for(ui32 i=0;i::value, int >::type = 0 > + void load(T &data) + { + ui8 hlp; + load( hlp ); + if(!hlp) + { + data = nullptr; + return; + } + + if(reader->smartVectorMembersSerialization) + { + typedef typename std::remove_const::type>::type TObjectType; //eg: const CGHeroInstance * => CGHeroInstance + typedef typename VectorizedTypeFor::type VType; //eg: CGHeroInstance -> CGobjectInstance + typedef typename VectorizedIDType::type IDType; + if(const auto *info = reader->getVectorizedTypeInfo()) + { + IDType id; + load(id); + if(id != IDType(-1)) + { + data = static_cast(reader->getVectorItemFromId(*info, id)); + return; + } + } + } + + if(reader->sendStackInstanceByIds) + { + bool gotLoaded = LoadIfStackInstance::invoke(* this, data); + if(gotLoaded) + return; + } + + ui32 pid = 0xffffffff; //pointer id (or maybe rather pointee id) + if(smartPointerSerialization) + { + load( pid ); //get the id + std::map::iterator i = loadedPointers.find(pid); //lookup + + if(i != loadedPointers.end()) + { + // We already got this pointer + // Cast it in case we are loading it to a non-first base pointer + assert(loadedPointersTypes.count(pid)); + data = reinterpret_cast(typeList.castRaw(i->second, loadedPointersTypes.at(pid), &typeid(typename std::remove_const::type>::type))); + return; + } + } + + //get type id + ui16 tid; + load( tid ); + + if(!tid) + { + typedef typename std::remove_pointer::type npT; + typedef typename std::remove_const::type ncpT; + data = ClassObjectCreator::invoke(); + ptrAllocated(data, pid); + load(*data); + } + else + { + auto typeInfo = applier.getApplier(tid)->loadPtr(*this,&data, pid); + data = reinterpret_cast(typeList.castRaw((void*)data, typeInfo, &typeid(typename std::remove_const::type>::type))); + } + } + + template + void ptrAllocated(const T *ptr, ui32 pid) + { + if(smartPointerSerialization && pid != 0xffffffff) + { + loadedPointersTypes[pid] = &typeid(T); + loadedPointers[pid] = (void*)ptr; //add loaded pointer to our lookup map; cast is to avoid errors with const T* pt + } + } + + template void registerType(const Base * b = nullptr, const Derived * d = nullptr) + { + applier.registerType(b, d); + } + + template + void load(std::shared_ptr &data) + { + typedef typename std::remove_const::type NonConstT; + NonConstT *internalPtr; + load(internalPtr); + + void *internalPtrDerived = typeList.castToMostDerived(internalPtr); + + if(internalPtr) + { + auto itr = loadedSharedPointers.find(internalPtrDerived); + if(itr != loadedSharedPointers.end()) + { + // This pointers is already loaded. The "data" needs to be pointed to it, + // so their shared state is actually shared. + try + { + auto actualType = typeList.getTypeInfo(internalPtr); + auto typeWeNeedToReturn = typeList.getTypeInfo(); + if(*actualType == *typeWeNeedToReturn) + { + // No casting needed, just unpack already stored shared_ptr and return it + data = boost::any_cast>(itr->second); + } + else + { + // We need to perform series of casts + auto ret = typeList.castShared(itr->second, actualType, typeWeNeedToReturn); + data = boost::any_cast>(ret); + } + } + catch(std::exception &e) + { + logGlobal->errorStream() << e.what(); + logGlobal->errorStream() << boost::format("Failed to cast stored shared ptr. Real type: %s. Needed type %s. FIXME FIXME FIXME") + % itr->second.type().name() % typeid(std::shared_ptr).name(); + //TODO scenario with inheritance -> we can have stored ptr to base and load ptr to derived (or vice versa) + assert(0); + } + } + else + { + auto hlp = std::shared_ptr(internalPtr); + data = hlp; //possibly adds const + loadedSharedPointers[internalPtrDerived] = typeList.castSharedToMostDerived(hlp); + } + } + else + data.reset(); + } + template + void load(std::unique_ptr &data) + { + T *internalPtr; + load( internalPtr ); + data.reset(internalPtr); + } + template + void load(std::array &data) + { + for(ui32 i = 0; i < N; i++) + load( data[i] ); + } + template + void load(std::set &data) + { + READ_CHECK_U32(length); + data.clear(); + T ins; + for(ui32 i=0;i + void load(std::unordered_set &data) + { + READ_CHECK_U32(length); + data.clear(); + T ins; + for(ui32 i=0;i + void load(std::list &data) + { + READ_CHECK_U32(length); + data.clear(); + T ins; + for(ui32 i=0;i + void load(std::pair &data) + { + load(data.first); + load(data.second); + } + + template + void load(std::map &data) + { + READ_CHECK_U32(length); + data.clear(); + T1 key; + T2 value; + for(ui32 i=0;i(std::move(key), std::move(value))); + } + } + template + void load(std::multimap &data) + { + READ_CHECK_U32(length); + data.clear(); + T1 key; + T2 value; + for(ui32 i = 0; i < length; i++) + { + load(key); + load(value); + data.insert(std::pair(std::move(key), std::move(value))); + } + } + void load(std::string &data) + { + READ_CHECK_U32(length); + data.resize(length); + this->read((void*)data.c_str(),length); + } + + template + void load(boost::variant &data) + { + typedef boost::variant TVariant; + + VariantLoaderHelper loader(*this); + + si32 which; + load( which ); + assert(which < loader.funcs.size()); + data = loader.funcs.at(which)(); + } + + template + void load(boost::optional & data) + { + ui8 present; + load( present ); + if(present) + { + T t; + load(t); + } + else + { + data = boost::optional(); + } + } +}; + +class DLL_LINKAGE CLoadFile : public IBinaryReader +{ +public: + BinaryDeserializer serializer; + + std::string fName; + std::unique_ptr sfile; + + CLoadFile(const boost::filesystem::path & fname, int minimalVersion = version); //throws! + ~CLoadFile(); + int read(void * data, unsigned size) override; //throws! + + void openNextFile(const boost::filesystem::path & fname, int minimalVersion); //throws! + void clear(); + void reportState(CLogger * out) override; + + void checkMagicBytes(const std::string & text); + + template + CLoadFile & operator>>(T &t) + { + serializer & t; + return * this; + } +}; diff --git a/lib/serializer/BinarySerializer.cpp b/lib/serializer/BinarySerializer.cpp new file mode 100644 index 000000000..a4ac40691 --- /dev/null +++ b/lib/serializer/BinarySerializer.cpp @@ -0,0 +1,74 @@ +#include "StdInc.h" +#include "BinarySerializer.h" + +#include "../registerTypes/RegisterTypes.h" + +/* + * BinarySerializer.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +extern template void registerTypes(BinarySerializer & s); + +CSaveFile::CSaveFile(const boost::filesystem::path &fname): serializer(this) +{ + registerTypes(serializer); + openNextFile(fname); +} + +CSaveFile::~CSaveFile() +{ +} + +int CSaveFile::write( const void * data, unsigned size ) +{ + sfile->write((char *)data,size); + return size; +} + +void CSaveFile::openNextFile(const boost::filesystem::path &fname) +{ + fName = fname; + try + { + sfile = make_unique(fname, std::ios::out | std::ios::binary); + sfile->exceptions(std::ifstream::failbit | std::ifstream::badbit); //we throw a lot anyway + + if(!(*sfile)) + THROW_FORMAT("Error: cannot open to write %s!", fname); + + sfile->write("VCMI",4); //write magic identifier + serializer & version; //write format version + } + catch(...) + { + logGlobal->errorStream() << "Failed to save to " << fname; + clear(); + throw; + } +} + +void CSaveFile::reportState(CLogger * out) +{ + out->debugStream() << "CSaveFile"; + if(sfile.get() && *sfile) + { + out->debugStream() << "\tOpened " << fName << "\n\tPosition: " << sfile->tellp(); + } +} + +void CSaveFile::clear() +{ + fName.clear(); + sfile = nullptr; +} + +void CSaveFile::putMagicBytes( const std::string &text ) +{ + write(text.c_str(), text.length()); +} diff --git a/lib/serializer/BinarySerializer.h b/lib/serializer/BinarySerializer.h new file mode 100644 index 000000000..b6f09114b --- /dev/null +++ b/lib/serializer/BinarySerializer.h @@ -0,0 +1,373 @@ + +/* + * BinarySerializer.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "CTypeList.h" +#include "../mapObjects/CArmedInstance.h" + +class DLL_LINKAGE CSaverBase +{ +protected: + IBinaryWriter * writer; +public: + CSaverBase(IBinaryWriter * w): writer(w){}; + + inline int write(const void * data, unsigned size) + { + return writer->write(data, size); + }; +}; + +/// Main class for serialization of classes into binary form +/// Behaviour for various classes is following: +/// Primitives: copy memory into underlying stream (defined in CSaverBase) +/// Containers: custom overloaded method that decouples class into primitives +/// VCMI Classes: recursively serialize them via ClassName::serialize( BinarySerializer &, int version) call +class DLL_LINKAGE BinarySerializer : public CSaverBase +{ + template + struct VariantVisitorSaver : boost::static_visitor<> + { + Handler &h; + VariantVisitorSaver(Handler &H):h(H) + { + } + + template + void operator()(const T &t) + { + h & t; + } + }; + + template + struct SaveIfStackInstance + { + static bool invoke(Ser &s, const T &data) + { + return false; + } + }; + + template + struct SaveIfStackInstance + { + static bool invoke(Ser &s, const CStackInstance* const &data) + { + assert(data->armyObj); + SlotID slot; + + if(data->getNodeType() == CBonusSystemNode::COMMANDER) + slot = SlotID::COMMANDER_SLOT_PLACEHOLDER; + else + slot = data->armyObj->findStack(data); + + assert(slot != SlotID()); + s & data->armyObj & slot; + return true; + } + }; + + template class CPointerSaver; + + class CBasicPointerSaver + { + public: + virtual void savePtr(CSaverBase &ar, const void *data) const =0; + virtual ~CBasicPointerSaver(){} + + template static CBasicPointerSaver *getApplier(const T * t=nullptr) + { + return new CPointerSaver(); + } + }; + + + template + class CPointerSaver : public CBasicPointerSaver + { + public: + void savePtr(CSaverBase &ar, const void *data) const override + { + BinarySerializer &s = static_cast(ar); + const T *ptr = static_cast(data); + + //T is most derived known type, it's time to call actual serialize + const_cast(ptr)->serialize(s,version); + } + }; + + CApplier applier; + +public: + std::map savedPointers; + + bool smartPointerSerialization; + bool saving; + + BinarySerializer(IBinaryWriter * w): CSaverBase(w) + { + saving=true; + smartPointerSerialization = true; + } + + template + void registerType(const Base * b = nullptr, const Derived * d = nullptr) + { + applier.registerType(b, d); + } + + template + BinarySerializer & operator&(const T & t) + { + this->save(t); + return * this; + } + + template < typename T, typename std::enable_if < std::is_same::value, int >::type = 0 > + void save(const T &data) + { + ui8 writ = static_cast(data); + save(writ); + } + + template < typename T, typename std::enable_if < std::is_same >::value, int >::type = 0 > + void save(const T &data) + { + std::vector convData; + std::copy(data.begin(), data.end(), std::back_inserter(convData)); + save(convData); + } + + template < class T, typename std::enable_if < std::is_fundamental::value && !std::is_same::value, int >::type = 0 > + void save(const T &data) + { + // save primitive - simply dump binary data to output + this->write(&data,sizeof(data)); + } + + template < typename T, typename std::enable_if < std::is_enum::value, int >::type = 0 > + void save(const T &data) + { + si32 writ = static_cast(data); + *this & writ; + } + + template < typename T, typename std::enable_if < std::is_array::value, int >::type = 0 > + void save(const T &data) + { + ui32 size = ARRAY_COUNT(data); + for(ui32 i=0; i < size; i++) + *this & data[i]; + } + + template < typename T, typename std::enable_if < std::is_pointer::value, int >::type = 0 > + void save(const T &data) + { + //write if pointer is not nullptr + ui8 hlp = (data!=nullptr); + save(hlp); + + //if pointer is nullptr then we don't need anything more... + if(!hlp) + return; + + if(writer->smartVectorMembersSerialization) + { + typedef typename std::remove_const::type>::type TObjectType; + typedef typename VectorizedTypeFor::type VType; + typedef typename VectorizedIDType::type IDType; + + if(const auto *info = writer->getVectorizedTypeInfo()) + { + IDType id = writer->getIdFromVectorItem(*info, data); + save(id); + if(id != IDType(-1)) //vector id is enough + return; + } + } + + if(writer->sendStackInstanceByIds) + { + const bool gotSaved = SaveIfStackInstance::invoke(*this, data); + if(gotSaved) + return; + } + + if(smartPointerSerialization) + { + // We might have an object that has multiple inheritance and store it via the non-first base pointer. + // Therefore, all pointers need to be normalized to the actual object address. + auto actualPointer = typeList.castToMostDerived(data); + std::map::iterator i = savedPointers.find(actualPointer); + if(i != savedPointers.end()) + { + //this pointer has been already serialized - write only it's id + save(i->second); + return; + } + + //give id to this pointer + ui32 pid = (ui32)savedPointers.size(); + savedPointers[actualPointer] = pid; + save(pid); + } + + //write type identifier + ui16 tid = typeList.getTypeID(data); + save(tid); + + if(!tid) + save(*data); //if type is unregistered simply write all data in a standard way + else + applier.getApplier(tid)->savePtr(*this, typeList.castToMostDerived(data)); //call serializer specific for our real type + } + + template < typename T, typename std::enable_if < is_serializeable::value, int >::type = 0 > + void save(const T &data) + { + const_cast(data).serialize(*this,version); + } + + template + void save(const std::shared_ptr &data) + { + T *internalPtr = data.get(); + save(internalPtr); + } + template + void save(const std::unique_ptr &data) + { + T *internalPtr = data.get(); + save(internalPtr); + } + template + void save(const std::vector &data) + { + ui32 length = data.size(); + *this & length; + for(ui32 i=0;i + void save(const std::array &data) + { + for(ui32 i=0; i < N; i++) + save(data[i]); + } + template + void save(const std::set &data) + { + std::set &d = const_cast &>(data); + ui32 length = d.size(); + save(length); + for(typename std::set::iterator i=d.begin();i!=d.end();i++) + save(*i); + } + template + void save(const std::unordered_set &data) + { + std::unordered_set &d = const_cast &>(data); + ui32 length = d.size(); + *this & length; + for(typename std::unordered_set::iterator i=d.begin();i!=d.end();i++) + save(*i); + } + template + void save(const std::list &data) + { + std::list &d = const_cast &>(data); + ui32 length = d.size(); + *this & length; + for(typename std::list::iterator i=d.begin();i!=d.end();i++) + save(*i); + } + void save(const std::string &data) + { + save(ui32(data.length())); + this->write(data.c_str(),data.size()); + } + template + void save(const std::pair &data) + { + save(data.first); + save(data.second); + } + template + void save(const std::map &data) + { + *this & ui32(data.size()); + for(typename std::map::const_iterator i=data.begin();i!=data.end();i++) + { + save(i->first); + save(i->second); + } + } + template + void save(const std::multimap &data) + { + *this & ui32(data.size()); + for(typename std::map::const_iterator i = data.begin(); i != data.end(); i++) + { + save(i->first); + save(i->second); + } + } + template + void save(const boost::variant &data) + { + si32 which = data.which(); + save(which); + + VariantVisitorSaver visitor(*this); + boost::apply_visitor(visitor, data); + } + template + void save(const boost::optional &data) + { + if(data) + { + save((ui8)1); + save(*data); + } + else + { + save((ui8)0); + } + } +}; + +class DLL_LINKAGE CSaveFile : public IBinaryWriter +{ +public: + BinarySerializer serializer; + + boost::filesystem::path fName; + std::unique_ptr sfile; + + CSaveFile(const boost::filesystem::path &fname); //throws! + ~CSaveFile(); + int write(const void * data, unsigned size) override; + + void openNextFile(const boost::filesystem::path &fname); //throws! + void clear(); + void reportState(CLogger * out) override; + + void putMagicBytes(const std::string &text); + + template + CSaveFile & operator<<(const T &t) + { + serializer & t; + return * this; + } +}; diff --git a/lib/serializer/CLoadIntegrityValidator.cpp b/lib/serializer/CLoadIntegrityValidator.cpp new file mode 100644 index 000000000..6eb07572c --- /dev/null +++ b/lib/serializer/CLoadIntegrityValidator.cpp @@ -0,0 +1,65 @@ +#include "StdInc.h" +#include "CLoadIntegrityValidator.h" + +#include "../registerTypes/RegisterTypes.h" + +/* + * CLoadIntegrityValidator.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +CLoadIntegrityValidator::CLoadIntegrityValidator(const boost::filesystem::path &primaryFileName, const boost::filesystem::path &controlFileName, int minimalVersion /*= version*/) + : serializer(this), foundDesync(false) +{ + registerTypes(serializer); + primaryFile = make_unique(primaryFileName, minimalVersion); + controlFile = make_unique(controlFileName, minimalVersion); + + assert(primaryFile->serializer.fileVersion == controlFile->serializer.fileVersion); + serializer.fileVersion = primaryFile->serializer.fileVersion; +} + +int CLoadIntegrityValidator::read( void * data, unsigned size ) +{ + assert(primaryFile); + assert(controlFile); + + if(!size) + return size; + + std::vector controlData(size); + auto ret = primaryFile->read(data, size); + + if(!foundDesync) + { + controlFile->read(controlData.data(), size); + if(std::memcmp(data, controlData.data(), size)) + { + logGlobal->errorStream() << "Desync found! Position: " << primaryFile->sfile->tellg(); + foundDesync = true; + //throw std::runtime_error("Savegame dsynchronized!"); + } + } + return ret; +} + +std::unique_ptr CLoadIntegrityValidator::decay() +{ + primaryFile->serializer.loadedPointers = this->serializer.loadedPointers; + primaryFile->serializer.loadedPointersTypes = this->serializer.loadedPointersTypes; + return std::move(primaryFile); +} + +void CLoadIntegrityValidator::checkMagicBytes( const std::string &text ) +{ + assert(primaryFile); + assert(controlFile); + + primaryFile->checkMagicBytes(text); + controlFile->checkMagicBytes(text); +} diff --git a/lib/serializer/CLoadIntegrityValidator.h b/lib/serializer/CLoadIntegrityValidator.h new file mode 100644 index 000000000..24ef4ddb3 --- /dev/null +++ b/lib/serializer/CLoadIntegrityValidator.h @@ -0,0 +1,31 @@ + +/* + * CLoadIntegrityValidator.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "BinaryDeserializer.h" + +/// Simple byte-to-byte saves comparator +class DLL_LINKAGE CLoadIntegrityValidator + : public IBinaryReader +{ +public: + BinaryDeserializer serializer; + std::unique_ptr primaryFile, controlFile; + bool foundDesync; + + CLoadIntegrityValidator(const boost::filesystem::path &primaryFileName, const boost::filesystem::path &controlFileName, int minimalVersion = version); //throws! + + int read( void * data, unsigned size) override; //throws! + void checkMagicBytes(const std::string &text); + + std::unique_ptr decay(); //returns primary file. CLoadIntegrityValidator stops being usable anymore +}; diff --git a/lib/serializer/CMemorySerializer.cpp b/lib/serializer/CMemorySerializer.cpp new file mode 100644 index 000000000..70e9aa424 --- /dev/null +++ b/lib/serializer/CMemorySerializer.cpp @@ -0,0 +1,40 @@ +#include "StdInc.h" +#include "CMemorySerializer.h" + +#include "../registerTypes/RegisterTypes.h" + +/* + * CMemorySerializer.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +int CMemorySerializer::read(void * data, unsigned size) +{ + if(buffer.size() < readPos + size) + throw std::runtime_error(boost::str(boost::format("Cannot read past the buffer (accessing index %d, while size is %d)!") % (readPos + size - 1) % buffer.size())); + + std::memcpy(data, buffer.data() + readPos, size); + readPos += size; + return size; +} + +int CMemorySerializer::write(const void * data, unsigned size) +{ + auto oldSize = buffer.size(); //and the pos to write from + buffer.resize(oldSize + size); + std::memcpy(buffer.data() + oldSize, data, size); + return size; +} + +CMemorySerializer::CMemorySerializer(): iser(this), oser(this) +{ + readPos = 0; + registerTypes(iser); + registerTypes(oser); +} + diff --git a/lib/serializer/CMemorySerializer.h b/lib/serializer/CMemorySerializer.h new file mode 100644 index 000000000..868da3b8f --- /dev/null +++ b/lib/serializer/CMemorySerializer.h @@ -0,0 +1,43 @@ + +/* + * CMemorySerializer.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "BinarySerializer.h" +#include "BinaryDeserializer.h" + +/// Serializer that stores objects in the dynamic buffer. Allows performing deep object copies. +class DLL_LINKAGE CMemorySerializer + : public IBinaryReader, public IBinaryWriter +{ + std::vector buffer; + + size_t readPos; //index of the next byte to be read +public: + BinaryDeserializer iser; + BinarySerializer oser; + + int read(void * data, unsigned size) override; //throws! + int write(const void * data, unsigned size) override; + + CMemorySerializer(); + + template + static std::unique_ptr deepCopy(const T &data) + { + CMemorySerializer mem; + mem.oser & &data; + + std::unique_ptr ret; + mem.iser & ret; + return ret; + } +}; diff --git a/lib/serializer/CSerializer.cpp b/lib/serializer/CSerializer.cpp new file mode 100644 index 000000000..0666633db --- /dev/null +++ b/lib/serializer/CSerializer.cpp @@ -0,0 +1,49 @@ +#include "StdInc.h" +#include "CSerializer.h" + +#include "CGameState.h" +#include "mapping/CMap.h" +#include "CHeroHandler.h" +#include "../mapObjects/CGHeroInstance.h" + +/* + * CSerializer.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +CSerializer::~CSerializer() +{ + +} + +CSerializer::CSerializer() +{ + smartVectorMembersSerialization = false; + sendStackInstanceByIds = false; +} + + +void CSerializer::addStdVecItems(CGameState *gs, LibClasses *lib) +{ + registerVectoredType(&gs->map->objects, + [](const CGObjectInstance &obj){ return obj.id; }); + registerVectoredType(&lib->heroh->heroes, + [](const CHero &h){ return h.ID; }); + registerVectoredType(&gs->map->allHeroes, + [](const CGHeroInstance &h){ return h.type->ID; }); + registerVectoredType(&lib->creh->creatures, + [](const CCreature &cre){ return cre.idNumber; }); + registerVectoredType(&lib->arth->artifacts, + [](const CArtifact &art){ return art.id; }); + registerVectoredType(&gs->map->artInstances, + [](const CArtifactInstance &artInst){ return artInst.id; }); + registerVectoredType(&gs->map->quests, + [](const CQuest &q){ return q.qid; }); + + smartVectorMembersSerialization = true; +} diff --git a/lib/serializer/CSerializer.h b/lib/serializer/CSerializer.h new file mode 100644 index 000000000..e6eefc914 --- /dev/null +++ b/lib/serializer/CSerializer.h @@ -0,0 +1,201 @@ + +/* + * CSerializer.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "../ConstTransitivePtr.h" +#include "../GameConstants.h" + +const ui32 version = 761; +const ui32 minSupportedVersion = 753; +const std::string SAVEGAME_MAGIC = "VCMISVG"; + +class CHero; +class CGHeroInstance; +class CGObjectInstance; + +class CGameState; +class LibClasses; +extern DLL_LINKAGE LibClasses * VLC; + +struct TypeComparer +{ + bool operator()(const std::type_info *a, const std::type_info *b) const + { + //#ifndef __APPLE__ + // return a->before(*b); + //#else + return strcmp(a->name(), b->name()) < 0; + //#endif + } +}; + +template +struct VectorizedObjectInfo +{ + const std::vector > *vector; //pointer to the appropriate vector + std::function idRetriever; + + VectorizedObjectInfo(const std::vector< ConstTransitivePtr > *Vector, std::function IdGetter) + :vector(Vector), idRetriever(IdGetter) + { + } +}; + +/// Base class for serializers capable of reading or writing data +class DLL_LINKAGE CSerializer +{ + template + static si32 idToNumber(const T &t, typename boost::enable_if >::type * dummy = 0) + { + return t; + } + + template + static NT idToNumber(const BaseForID &t) + { + return t.getNum(); + } + + template + void registerVectoredType(const std::vector *Vector, const std::function &idRetriever) + { + vectors[&typeid(T)] = VectorizedObjectInfo(Vector, idRetriever); + } + template + void registerVectoredType(const std::vector > *Vector, const std::function &idRetriever) + { + vectors[&typeid(T)] = VectorizedObjectInfo(Vector, idRetriever); + } + + typedef std::map TTypeVecMap; + TTypeVecMap vectors; //entry must be a pointer to vector containing pointers to the objects of key type + +public: + bool smartVectorMembersSerialization; + bool sendStackInstanceByIds; + + CSerializer(); + ~CSerializer(); + + virtual void reportState(CLogger * out){}; + + template + const VectorizedObjectInfo *getVectorizedTypeInfo() + { + const std::type_info *myType = nullptr; + + myType = &typeid(T); + + TTypeVecMap::iterator i = vectors.find(myType); + if(i == vectors.end()) + return nullptr; + else + { + assert(!i->second.empty()); + assert(i->second.type() == typeid(VectorizedObjectInfo)); + VectorizedObjectInfo *ret = &(boost::any_cast&>(i->second)); + return ret; + } + } + + template + T* getVectorItemFromId(const VectorizedObjectInfo &oInfo, U id) const + { + si32 idAsNumber = idToNumber(id); + + assert(oInfo.vector); + assert(static_cast(oInfo.vector->size()) > idAsNumber); + return const_cast((*oInfo.vector)[idAsNumber].get()); + } + + template + U getIdFromVectorItem(const VectorizedObjectInfo &oInfo, const T* obj) const + { + if(!obj) + return U(-1); + + return oInfo.idRetriever(*obj); + } + + void addStdVecItems(CGameState *gs, LibClasses *lib = VLC); +}; + +/// Helper to detect classes with user-provided serialize(S&, int version) method +template +struct is_serializeable +{ + typedef char (&Yes)[1]; + typedef char (&No)[2]; + + template + static Yes test(U * data, S* arg1 = 0, + typename std::enable_ifserialize(*arg1, int(0))) + >::value>::type * = 0); + static No test(...); + static const bool value = sizeof(Yes) == sizeof(is_serializeable::test((typename std::remove_reference::type>::type*)0)); +}; + +template //metafunction returning CGObjectInstance if T is its derivate or T elsewise +struct VectorizedTypeFor +{ + typedef typename + //if + boost::mpl::eval_if, + boost::mpl::identity, + //else if + boost::mpl::eval_if, + boost::mpl::identity, + //else + boost::mpl::identity + > >::type type; +}; +template +struct VectorizedIDType +{ + typedef typename + //if + boost::mpl::eval_if, + boost::mpl::identity, + //else if + boost::mpl::eval_if, + boost::mpl::identity, + //else if + boost::mpl::eval_if, + boost::mpl::identity, + //else if + boost::mpl::eval_if, + boost::mpl::identity, + //else if + boost::mpl::eval_if, + boost::mpl::identity, + //else if + boost::mpl::eval_if, + boost::mpl::identity, + //else + boost::mpl::identity + > > > > > >::type type; +}; + +/// Base class for deserializers +class IBinaryReader : public virtual CSerializer +{ +public: + virtual int read(void * data, unsigned size) = 0; +}; + +/// Base class for serializers +class IBinaryWriter : public virtual CSerializer +{ +public: + virtual int write(const void * data, unsigned size) = 0; +}; diff --git a/lib/serializer/CTypeList.cpp b/lib/serializer/CTypeList.cpp new file mode 100644 index 000000000..ad8f28d7f --- /dev/null +++ b/lib/serializer/CTypeList.cpp @@ -0,0 +1,122 @@ +#include "StdInc.h" +#include "CTypeList.h" + +#include "../registerTypes/RegisterTypes.h" + +/* + * CTypeList.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +extern template void registerTypes(CTypeList & s); + +CTypeList typeList; + +CTypeList::CTypeList() +{ + registerTypes(*this); +} + +CTypeList::TypeInfoPtr CTypeList::registerType(const std::type_info *type) +{ + if(auto typeDescr = getTypeDescriptor(type, false)) + return typeDescr; //type found, return ptr to structure + + //type not found - add it to the list and return given ID + auto newType = std::make_shared(); + newType->typeID = typeInfos.size() + 1; + newType->name = type->name(); + typeInfos[type] = newType; + + return newType; +} + +ui16 CTypeList::getTypeID(const std::type_info *type, bool throws) const +{ + auto descriptor = getTypeDescriptor(type, throws); + if (descriptor == nullptr) + { + return 0; + } + return descriptor->typeID; +} + +std::vector CTypeList::castSequence(TypeInfoPtr from, TypeInfoPtr to) const +{ + if(!strcmp(from->name, to->name)) + return std::vector(); + + // Perform a simple BFS in the class hierarchy. + + auto BFS = [&](bool upcast) + { + std::map previous; + std::queue q; + q.push(to); + while(q.size()) + { + auto typeNode = q.front(); + q.pop(); + for(auto &nodeBase : upcast ? typeNode->parents : typeNode->children) + { + if(!previous.count(nodeBase)) + { + previous[nodeBase] = typeNode; + q.push(nodeBase); + } + } + } + + std::vector ret; + + if(!previous.count(from)) + return ret; + + ret.push_back(from); + TypeInfoPtr ptr = from; + do + { + ptr = previous.at(ptr); + ret.push_back(ptr); + } while(ptr != to); + + return ret; + }; + + // Try looking both up and down. + auto ret = BFS(true); + if(ret.empty()) + ret = BFS(false); + + if(ret.empty()) + THROW_FORMAT("Cannot find relation between types %s and %s. Were they (and all classes between them) properly registered?", from->name % to->name); + + return ret; +} + +std::vector CTypeList::castSequence(const std::type_info *from, const std::type_info *to) const +{ + //This additional if is needed because getTypeDescriptor might fail if type is not registered + // (and if casting is not needed, then registereing should no be required) + if(!strcmp(from->name(), to->name())) + return std::vector(); + + return castSequence(getTypeDescriptor(from), getTypeDescriptor(to)); +} + +CTypeList::TypeInfoPtr CTypeList::getTypeDescriptor(const std::type_info *type, bool throws) const +{ + auto i = typeInfos.find(type); + if(i != typeInfos.end()) + return i->second; //type found, return ptr to structure + + if(!throws) + return nullptr; + + THROW_FORMAT("Cannot find type descriptor for type %s. Was it registered?", type->name()); +} diff --git a/lib/serializer/CTypeList.h b/lib/serializer/CTypeList.h new file mode 100644 index 000000000..a68a8fb94 --- /dev/null +++ b/lib/serializer/CTypeList.h @@ -0,0 +1,230 @@ + +/* + * CTypeList.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "CSerializer.h" + +struct IPointerCaster +{ + virtual boost::any castRawPtr(const boost::any &ptr) const = 0; // takes From*, returns To* + virtual boost::any castSharedPtr(const boost::any &ptr) const = 0; // takes std::shared_ptr, performs dynamic cast, returns std::shared_ptr + virtual boost::any castWeakPtr(const boost::any &ptr) const = 0; // takes std::weak_ptr, performs dynamic cast, returns std::weak_ptr. The object under poitner must live. + //virtual boost::any castUniquePtr(const boost::any &ptr) const = 0; // takes std::unique_ptr, performs dynamic cast, returns std::unique_ptr +}; + +template +struct PointerCaster : IPointerCaster +{ + virtual boost::any castRawPtr(const boost::any &ptr) const override // takes void* pointing to From object, performs dynamic cast, returns void* pointing to To object + { + From * from = (From*)boost::any_cast(ptr); + To * ret = static_cast(from); + return (void*)ret; + } + + // Helper function performing casts between smart pointers + template + boost::any castSmartPtr(const boost::any &ptr) const + { + try + { + auto from = boost::any_cast(ptr); + auto ret = std::static_pointer_cast(from); + return ret; + } + catch(std::exception &e) + { + THROW_FORMAT("Failed cast %s -> %s. Given argument was %s. Error message: %s", typeid(From).name() % typeid(To).name() % ptr.type().name() % e.what()); + } + } + + virtual boost::any castSharedPtr(const boost::any &ptr) const override + { + return castSmartPtr>(ptr); + } + virtual boost::any castWeakPtr(const boost::any &ptr) const override + { + auto from = boost::any_cast>(ptr); + return castSmartPtr>(from.lock()); + } +}; + +/// Class that implements basic reflection-like mechanisms +/// For every type registered via registerType() generates inheritance tree +/// Rarely used directly - usually used as part of CApplier +class DLL_LINKAGE CTypeList: public boost::noncopyable +{ +//public: + struct TypeDescriptor; + typedef std::shared_ptr TypeInfoPtr; + struct TypeDescriptor + { + ui16 typeID; + const char *name; + std::vector children, parents; + }; + typedef boost::shared_mutex TMutex; + typedef boost::unique_lock TUniqueLock; + typedef boost::shared_lock TSharedLock; +private: + mutable TMutex mx; + + std::map typeInfos; + std::map, std::unique_ptr> casters; //for each pair we provide a caster (each registered relations creates a single entry here) + + /// Returns sequence of types starting from "from" and ending on "to". Every next type is derived from the previous. + /// Throws if there is no link registered. + std::vector castSequence(TypeInfoPtr from, TypeInfoPtr to) const; + std::vector castSequence(const std::type_info *from, const std::type_info *to) const; + + template + boost::any castHelper(boost::any inputPtr, const std::type_info *fromArg, const std::type_info *toArg) const + { + TSharedLock lock(mx); + auto typesSequence = castSequence(fromArg, toArg); + + boost::any ptr = inputPtr; + for(int i = 0; i < static_cast(typesSequence.size()) - 1; i++) + { + auto &from = typesSequence[i]; + auto &to = typesSequence[i + 1]; + auto castingPair = std::make_pair(from, to); + if(!casters.count(castingPair)) + THROW_FORMAT("Cannot find caster for conversion %s -> %s which is needed to cast %s -> %s", from->name % to->name % fromArg->name() % toArg->name()); + + auto &caster = casters.at(castingPair); + ptr = (*caster.*CastingFunction)(ptr); //Why does unique_ptr not have operator->* ..? + } + + return ptr; + } + CTypeList &operator=(CTypeList &) + { + // As above. + assert(0); + return *this; + } + + TypeInfoPtr getTypeDescriptor(const std::type_info *type, bool throws = true) const; //if not throws, failure returns nullptr + TypeInfoPtr registerType(const std::type_info *type); + +public: + + CTypeList(); + + template + void registerType(const Base * b = nullptr, const Derived * d = nullptr) + { + TUniqueLock lock(mx); + static_assert(std::is_base_of::value, "First registerType template parameter needs to ba a base class of the second one."); + static_assert(std::has_virtual_destructor::value, "Base class needs to have a virtual destructor."); + static_assert(!std::is_same::value, "Parameters of registerTypes should be two different types."); + auto bt = getTypeInfo(b); + auto dt = getTypeInfo(d); //obtain std::type_info + auto bti = registerType(bt); + auto dti = registerType(dt); //obtain our TypeDescriptor + + // register the relation between classes + bti->children.push_back(dti); + dti->parents.push_back(bti); + casters[std::make_pair(bti, dti)] = make_unique>(); + casters[std::make_pair(dti, bti)] = make_unique>(); + } + + ui16 getTypeID(const std::type_info *type, bool throws = false) const; + + template + ui16 getTypeID(const T * t = nullptr, bool throws = false) const + { + return getTypeID(getTypeInfo(t), throws); + } + + template + void * castToMostDerived(const TInput * inputPtr) const + { + auto &baseType = typeid(typename std::remove_cv::type); + auto derivedType = getTypeInfo(inputPtr); + + if (strcmp(baseType.name(), derivedType->name()) == 0) + { + return const_cast(reinterpret_cast(inputPtr)); + } + + return boost::any_cast(castHelper<&IPointerCaster::castRawPtr>( + const_cast(reinterpret_cast(inputPtr)), &baseType, + derivedType)); + } + + template + boost::any castSharedToMostDerived(const std::shared_ptr inputPtr) const + { + auto &baseType = typeid(typename std::remove_cv::type); + auto derivedType = getTypeInfo(inputPtr.get()); + + if (!strcmp(baseType.name(), derivedType->name())) + return inputPtr; + + return castHelper<&IPointerCaster::castSharedPtr>(inputPtr, &baseType, derivedType); + } + + void * castRaw(void *inputPtr, const std::type_info *from, const std::type_info *to) const + { + return boost::any_cast(castHelper<&IPointerCaster::castRawPtr>(inputPtr, from, to)); + } + boost::any castShared(boost::any inputPtr, const std::type_info *from, const std::type_info *to) const + { + return castHelper<&IPointerCaster::castSharedPtr>(inputPtr, from, to); + } + + template const std::type_info * getTypeInfo(const T * t = nullptr) const + { + if(t) + return &typeid(*t); + else + return &typeid(T); + } +}; + +extern DLL_LINKAGE CTypeList typeList; + +/// Wrapper over CTypeList. Allows execution of templated class T for any type +/// that was resgistered for this applier +template +class CApplier +{ + std::map> apps; + + template + void addApplier(ui16 ID) + { + if(!apps.count(ID)) + { + RegisteredType * rtype = nullptr; + apps[ID].reset(T::getApplier(rtype)); + } + } + +public: + T * getApplier(ui16 ID) + { + assert(apps.count(ID)); + return apps[ID].get(); + } + + template + void registerType(const Base * b = nullptr, const Derived * d = nullptr) + { + typeList.registerType(b, d); + addApplier(typeList.getTypeID(b)); + addApplier(typeList.getTypeID(d)); + } +}; diff --git a/lib/serializer/Connection.cpp b/lib/serializer/Connection.cpp new file mode 100644 index 000000000..0a898b9cb --- /dev/null +++ b/lib/serializer/Connection.cpp @@ -0,0 +1,280 @@ +#include "StdInc.h" +#include "Connection.h" + +#include "registerTypes/RegisterTypes.h" +#include "mapping/CMap.h" +#include "CGameState.h" + +#include + +/* + * Connection.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +using namespace boost; +using namespace boost::asio::ip; + +#if defined(__hppa__) || \ + defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \ + (defined(__MIPS__) && defined(__MISPEB__)) || \ + defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \ + defined(__sparc__) +#define BIG_ENDIAN +#else +#define LIL_ENDIAN +#endif + + +void CConnection::init() +{ + boost::asio::ip::tcp::no_delay option(true); + socket->set_option(option); + + enableSmartPointerSerializatoin(); + disableStackSendingByID(); + registerTypes(iser); + registerTypes(oser); +#ifdef LIL_ENDIAN + myEndianess = true; +#else + myEndianess = false; +#endif + connected = true; + std::string pom; + //we got connection + oser & std::string("Aiya!\n") & name & myEndianess; //identify ourselves + iser & pom & pom & contactEndianess; + logNetwork->infoStream() << "Established connection with "<errorStream() << "Problem with resolving: \n" << error; + goto connerror1; + } + pom = endpoint_iterator; + if(pom != end) + logNetwork->infoStream()<<"Found endpoints:"; + else + { + logNetwork->errorStream() << "Critical problem: No endpoints found!"; + goto connerror1; + } + i=0; + while(pom != end) + { + logNetwork->infoStream() << "\t" << i << ": " << (boost::asio::ip::tcp::endpoint&)*pom; + pom++; + } + i=0; + while(endpoint_iterator != end) + { + logNetwork->infoStream() << "Trying connection to " << (boost::asio::ip::tcp::endpoint&)*endpoint_iterator << " (" << i++ << ")"; + socket->connect(*endpoint_iterator, error); + if(!error) + { + init(); + return; + } + else + { + logNetwork->errorStream() << "Problem with connecting: " << error; + } + endpoint_iterator++; + } + + //we shouldn't be here - error handling +connerror1: + logNetwork->errorStream() << "Something went wrong... checking for error info"; + if(error) + logNetwork->errorStream() << error; + else + logNetwork->errorStream() << "No error info. "; + delete io_service; + //delete socket; + throw std::runtime_error("Can't establish connection :("); +} +CConnection::CConnection(TSocket * Socket, std::string Name ) + :iser(this), oser(this), socket(Socket),io_service(&Socket->get_io_service()), name(Name)//, send(this), rec(this) +{ + init(); +} +CConnection::CConnection(TAcceptor * acceptor, boost::asio::io_service *Io_service, std::string Name) +: iser(this), oser(this), name(Name)//, send(this), rec(this) +{ + boost::system::error_code error = asio::error::host_not_found; + socket = new tcp::socket(*io_service); + acceptor->accept(*socket,error); + if (error) + { + logNetwork->errorStream() << "Error on accepting: " << error; + delete socket; + throw std::runtime_error("Can't establish connection :("); + } + init(); +} +int CConnection::write(const void * data, unsigned size) +{ + try + { + int ret; + ret = asio::write(*socket,asio::const_buffers_1(asio::const_buffer(data,size))); + return ret; + } + catch(...) + { + //connection has been lost + connected = false; + throw; + } +} +int CConnection::read(void * data, unsigned size) +{ + try + { + int ret = asio::read(*socket,asio::mutable_buffers_1(asio::mutable_buffer(data,size))); + return ret; + } + catch(...) + { + //connection has been lost + connected = false; + throw; + } +} +CConnection::~CConnection(void) +{ + if(handler) + handler->join(); + + delete handler; + + close(); + delete io_service; + delete wmx; + delete rmx; +} + +template +CConnection & CConnection::operator&(const T &t) { +// throw std::exception(); +//XXX this is temporaly ? solution to fix gcc (4.3.3, other?) compilation +// problem for more details contact t0@czlug.icis.pcz.pl or impono@gmail.com +// do not remove this exception it shoudnt be called + return *this; +} + +void CConnection::close() +{ + if(socket) + { + socket->close(); + delete socket; + socket = nullptr; + } +} + +bool CConnection::isOpen() const +{ + return socket && connected; +} + +void CConnection::reportState(CLogger * out) +{ + out->debugStream() << "CConnection"; + if(socket && socket->is_open()) + { + out->debugStream() << "\tWe have an open and valid socket"; + out->debugStream() << "\t" << socket->available() <<" bytes awaiting"; + } +} + +CPack * CConnection::retreivePack() +{ + CPack *ret = nullptr; + boost::unique_lock lock(*rmx); + logNetwork->traceStream() << "Listening... "; + iser & ret; + logNetwork->traceStream() << "\treceived server message of type " << typeid(*ret).name() << ", data: " << ret; + return ret; +} + +void CConnection::sendPackToServer(const CPack &pack, PlayerColor player, ui32 requestID) +{ + boost::unique_lock lock(*wmx); + logNetwork->traceStream() << "Sending to server a pack of type " << typeid(pack).name(); + oser & player & requestID & &pack; //packs has to be sent as polymorphic pointers! +} + +void CConnection::disableStackSendingByID() +{ + CSerializer::sendStackInstanceByIds = false; +} + +void CConnection::enableStackSendingByID() +{ + CSerializer::sendStackInstanceByIds = true; +} + +void CConnection::disableSmartPointerSerialization() +{ + iser.smartPointerSerialization = oser.smartPointerSerialization = false; +} + +void CConnection::enableSmartPointerSerializatoin() +{ + iser.smartPointerSerialization = oser.smartPointerSerialization = true; +} + +void CConnection::prepareForSendingHeroes() +{ + iser.loadedPointers.clear(); + oser.savedPointers.clear(); + disableSmartVectorMemberSerialization(); + enableSmartPointerSerializatoin(); + disableStackSendingByID(); +} + +void CConnection::enterPregameConnectionMode() +{ + iser.loadedPointers.clear(); + oser.savedPointers.clear(); + disableSmartVectorMemberSerialization(); + disableSmartPointerSerialization(); +} + +void CConnection::disableSmartVectorMemberSerialization() +{ + CSerializer::smartVectorMembersSerialization = false; +} + +void CConnection::enableSmartVectorMemberSerializatoin() +{ + CSerializer::smartVectorMembersSerialization = true; +} + +std::ostream & operator<<(std::ostream &str, const CConnection &cpc) + { + return str << "Connection with " << cpc.name << " (ID: " << cpc.connectionID << /*", " << (cpc.host ? "host" : "guest") <<*/ ")"; + } diff --git a/lib/serializer/Connection.h b/lib/serializer/Connection.h new file mode 100644 index 000000000..4e326f85d --- /dev/null +++ b/lib/serializer/Connection.h @@ -0,0 +1,109 @@ + +/* + * Connection.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "BinaryDeserializer.h" +#include "BinarySerializer.h" + +struct CPack; + +namespace boost +{ + namespace asio + { + namespace ip + { + class tcp; + } + class io_service; + + template class stream_socket_service; + template + class basic_stream_socket; + + template class socket_acceptor_service; + template + class basic_socket_acceptor; + } + class mutex; +} + +typedef boost::asio::basic_stream_socket < boost::asio::ip::tcp , boost::asio::stream_socket_service > TSocket; +typedef boost::asio::basic_socket_acceptor > TAcceptor; + +/// Main class for network communication +/// Allows establishing connection and bidirectional read-write +class DLL_LINKAGE CConnection + : public IBinaryReader, public IBinaryWriter +{ + CConnection(void); + + void init(); + void reportState(CLogger * out) override; +public: + BinaryDeserializer iser; + BinarySerializer oser; + + boost::mutex *rmx, *wmx; // read/write mutexes + TSocket * socket; + bool logging; + bool connected; + bool myEndianess, contactEndianess; //true if little endian, if endianness is different we'll have to revert received multi-byte vars + boost::asio::io_service *io_service; + std::string name; //who uses this connection + + int connectionID; + boost::thread *handler; + + bool receivedStop, sendStop; + + CConnection(std::string host, std::string port, std::string Name); + CConnection(TAcceptor * acceptor, boost::asio::io_service *Io_service, std::string Name); + CConnection(TSocket * Socket, std::string Name); //use immediately after accepting connection into socket + + int write(const void * data, unsigned size) override; + int read(void * data, unsigned size) override; + void close(); + bool isOpen() const; + template + CConnection &operator&(const T&); + virtual ~CConnection(void); + + CPack *retreivePack(); //gets from server next pack (allocates it with new) + void sendPackToServer(const CPack &pack, PlayerColor player, ui32 requestID); + + void disableStackSendingByID(); + void enableStackSendingByID(); + void disableSmartPointerSerialization(); + void enableSmartPointerSerializatoin(); + void disableSmartVectorMemberSerialization(); + void enableSmartVectorMemberSerializatoin(); + + void prepareForSendingHeroes(); //disables sending vectorized, enables smart pointer serialization, clears saved/loaded ptr cache + void enterPregameConnectionMode(); + + template + CConnection & operator>>(T &t) + { + iser & t; + return * this; + } + + template + CConnection & operator<<(const T &t) + { + oser & t; + return * this; + } +}; + +DLL_LINKAGE std::ostream &operator<<(std::ostream &str, const CConnection &cpc);