#pragma once #include "buffered_io.h" #include "class_factory.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _MSC_VER #pragma warning(disable : 4127) #endif enum ESaverMode { SAVER_MODE_READ = 1, SAVER_MODE_WRITE = 2, SAVER_MODE_WRITE_COMPRESSED = 3, }; namespace NBinSaverInternals { // This lets explicitly control the overload resolution priority // The higher P means higher priority in overload resolution order template struct TOverloadPriority : TOverloadPriority { }; template <> struct TOverloadPriority<0> { }; } ////////////////////////////////////////////////////////////////////////// struct IBinSaver { public: typedef unsigned char chunk_id; typedef ui32 TStoredSize; // changing this will break compatibility private: // This overload is required to avoid infinite recursion when overriding serialization in derived classes: // struct B { // virtual int operator &(IBinSaver& f) { // return 0; // } // }; // // struct D : B { // int operator &(IBinSaver& f) override { // f.Add(0, static_cast(this)); // return 0; // } // }; template ()->T::operator&(std::declval()))> void CallObjectSerialize(T* p, NBinSaverInternals::TOverloadPriority<2>) { // highest priority - will be resolved first if enabled // Note: p->operator &(*this) would lead to infinite recursion p->T::operator&(*this); } template () & std::declval())> void CallObjectSerialize(T* p, NBinSaverInternals::TOverloadPriority<1>) { // lower priority - will be resolved second if enabled (*p) & (*this); } template void CallObjectSerialize(T* p, NBinSaverInternals::TOverloadPriority<0>) { // lower priority - will be resolved last #if (!defined(_MSC_VER)) // broken in clang16 for some types // In MSVC __has_trivial_copy returns false to enums, primitive types and arrays. // static_assert(__is_trivially_copyable(T), "Class is nontrivial copyable, you must define operator&, see"); #endif DataChunk(p, sizeof(T)); } // vector template void DoVector(TVector& data) { TStoredSize nSize; if (IsReading()) { data.clear(); Add(2, &nSize); data.resize(nSize); } else { nSize = data.size(); CheckOverflow(nSize, data.size()); Add(2, &nSize); } for (TStoredSize i = 0; i < nSize; i++) Add(1, &data[i]); } template void DoArray(T (&data)[N]) { for (size_t i = 0; i < N; i++) { Add(1, &(data[i])); } } template void CheckOverflow(TStoredSize nSize, TLarge origSize) { if (nSize != origSize) { fprintf(stderr, "IBinSaver: object size is too large to be serialized (%" PRIu32 " != %" PRIu64 ")\n", nSize, (ui64)origSize); abort(); } } template void DoDataVector(TVector& data) { TStoredSize nSize = data.size(); CheckOverflow(nSize, data.size()); Add(1, &nSize); if (IsReading()) { data.clear(); data.resize(nSize); } if (nSize > 0) DataChunk(&data[0], sizeof(T) * nSize); } template void DoAnyMap(AM& data) { if (IsReading()) { data.clear(); TStoredSize nSize; Add(3, &nSize); TVector::template rebind_alloc> indices; indices.resize(nSize); for (TStoredSize i = 0; i < nSize; ++i) Add(1, &indices[i]); for (TStoredSize i = 0; i < nSize; ++i) Add(2, &data[indices[i]]); } else { TStoredSize nSize = data.size(); CheckOverflow(nSize, data.size()); Add(3, &nSize); TVector::template rebind_alloc> indices; indices.resize(nSize); TStoredSize i = 1; for (auto pos = data.begin(); pos != data.end(); ++pos, ++i) indices[nSize - i] = pos->first; for (TStoredSize j = 0; j < nSize; ++j) Add(1, &indices[j]); for (TStoredSize j = 0; j < nSize; ++j) Add(2, &data[indices[j]]); } } // hash_multimap template void DoAnyMultiMap(AMM& data) { if (IsReading()) { data.clear(); TStoredSize nSize; Add(3, &nSize); TVector::template rebind_alloc> indices; indices.resize(nSize); for (TStoredSize i = 0; i < nSize; ++i) Add(1, &indices[i]); for (TStoredSize i = 0; i < nSize; ++i) { std::pair valToInsert; valToInsert.first = indices[i]; Add(2, &valToInsert.second); data.insert(valToInsert); } } else { TStoredSize nSize = data.size(); CheckOverflow(nSize, data.size()); Add(3, &nSize); for (auto pos = data.begin(); pos != data.end(); ++pos) Add(1, (typename AMM::key_type*)(&pos->first)); for (auto pos = data.begin(); pos != data.end(); ++pos) Add(2, &pos->second); } } template void DoAnySet(T& data) { if (IsReading()) { data.clear(); TStoredSize nSize; Add(2, &nSize); for (TStoredSize i = 0; i < nSize; ++i) { typename T::value_type member; Add(1, &member); data.insert(member); } } else { TStoredSize nSize = data.size(); CheckOverflow(nSize, data.size()); Add(2, &nSize); for (const auto& elem : data) { auto member = elem; Add(1, &member); } } } // 2D array template void Do2DArray(TArray2D& a) { int nXSize = a.GetXSize(), nYSize = a.GetYSize(); Add(1, &nXSize); Add(2, &nYSize); if (IsReading()) a.SetSizes(nXSize, nYSize); for (int i = 0; i < nXSize * nYSize; i++) Add(3, &a[i / nXSize][i % nXSize]); } template void Do2DArrayData(TArray2D& a) { int nXSize = a.GetXSize(), nYSize = a.GetYSize(); Add(1, &nXSize); Add(2, &nYSize); if (IsReading()) a.SetSizes(nXSize, nYSize); if (nXSize * nYSize > 0) DataChunk(&a[0][0], sizeof(T) * nXSize * nYSize); } // strings template void DataChunkStr(TStringType& data, i64 elemSize) { if (bRead) { TStoredSize nCount = 0; File.Read(&nCount, sizeof(TStoredSize)); data.resize(nCount); if (nCount) File.Read(&*data.begin(), nCount * elemSize); } else { TStoredSize nCount = data.size(); CheckOverflow(nCount, data.size()); File.Write(&nCount, sizeof(TStoredSize)); File.Write(data.c_str(), nCount * elemSize); } } void DataChunkString(std::string& data) { DataChunkStr(data, sizeof(char)); } void DataChunkStroka(TString& data) { DataChunkStr(data, sizeof(TString::char_type)); } void DataChunkWtroka(TUtf16String& data) { DataChunkStr(data, sizeof(wchar16)); } void DataChunk(void* pData, i64 nSize) { i64 chunkSize = 1 << 30; for (i64 offset = 0; offset < nSize; offset += chunkSize) { void* ptr = (char*)pData + offset; i64 size = offset + chunkSize < nSize ? chunkSize : (nSize - offset); if (bRead) File.Read(ptr, size); else File.Write(ptr, size); } } // storing/loading pointers to objects void StoreObject(IObjectBase* pObject); IObjectBase* LoadObject(); bool bRead; TBufferedStream<> File; // maps objects addresses during save(first) to addresses during load(second) - during loading // or serves as a sign that some object has been already stored - during storing bool StableOutput; typedef THashMap PtrIdHash; TAutoPtr PtrIds; typedef THashMap> CObjectsHash; TAutoPtr Objects; TVector ObjectQueue; public: bool IsReading() { return bRead; } void AddRawData(const chunk_id, void* pData, i64 nSize) { DataChunk(pData, nSize); } // return type of Add() is used to detect specialized serializer (see HasNonTrivialSerializer below) template char Add(const chunk_id, T* p) { CallObjectSerialize(p, NBinSaverInternals::TOverloadPriority<2>()); return 0; } int Add(const chunk_id, std::string* pStr) { DataChunkString(*pStr); return 0; } int Add(const chunk_id, TString* pStr) { DataChunkStroka(*pStr); return 0; } int Add(const chunk_id, TUtf16String* pStr) { DataChunkWtroka(*pStr); return 0; } int Add(const chunk_id, TBlob* blob) { if (bRead) { ui64 size = 0; File.Read(&size, sizeof(size)); TBuffer buffer; buffer.Advance(size); if (size > 0) File.Read(buffer.Data(), buffer.Size()); (*blob) = TBlob::FromBuffer(buffer); } else { const ui64 size = blob->Size(); File.Write(&size, sizeof(size)); File.Write(blob->Data(), blob->Size()); } return 0; } template int Add(const chunk_id, TVector* pVec) { if (HasNonTrivialSerializer(0u)) DoVector(*pVec); else DoDataVector(*pVec); return 0; } template int Add(const chunk_id, T (*pVec)[N]) { if (HasNonTrivialSerializer(0u)) DoArray(*pVec); else DataChunk(pVec, sizeof(*pVec)); return 0; } template int Add(const chunk_id, TMap* pMap) { DoAnyMap(*pMap); return 0; } template int Add(const chunk_id, THashMap* pHash) { DoAnyMap(*pHash); return 0; } template int Add(const chunk_id, THashMultiMap* pHash) { DoAnyMultiMap(*pHash); return 0; } template int Add(const chunk_id, TSet* pSet) { DoAnySet(*pSet); return 0; } template int Add(const chunk_id, THashSet* pHash) { DoAnySet(*pHash); return 0; } template int Add(const chunk_id, TArray2D* pArr) { if (HasNonTrivialSerializer(0u)) Do2DArray(*pArr); else Do2DArrayData(*pArr); return 0; } template int Add(const chunk_id, TList* pList) { TList& data = *pList; if (IsReading()) { int nSize; Add(2, &nSize); data.clear(); data.insert(data.begin(), nSize, T1()); } else { int nSize = data.size(); Add(2, &nSize); } int i = 1; for (typename TList::iterator k = data.begin(); k != data.end(); ++k, ++i) Add(i + 2, &(*k)); return 0; } template int Add(const chunk_id, std::pair* pData) { Add(1, &(pData->first)); Add(2, &(pData->second)); return 0; } template int Add(const chunk_id, std::array* pData) { if (HasNonTrivialSerializer(0u)) { for (size_t i = 0; i < N; ++i) Add(1, &(*pData)[i]); } else { DataChunk((void*)pData->data(), pData->size() * sizeof(T1)); } return 0; } template int Add(const chunk_id, std::bitset* pData) { if (IsReading()) { std::string s; Add(1, &s); *pData = std::bitset(s); } else { std::string s = pData->template to_string, std::allocator>(); Add(1, &s); } return 0; } int Add(const chunk_id, TDynBitMap* pData) { if (IsReading()) { ui64 count = 0; Add(1, &count); pData->Clear(); pData->Reserve(count * sizeof(TDynBitMap::TChunk) * 8); for (ui64 i = 0; i < count; ++i) { TDynBitMap::TChunk chunk = 0; Add(i + 1, &chunk); if (i > 0) { pData->LShift(8 * sizeof(TDynBitMap::TChunk)); } pData->Or(chunk); } } else { ui64 count = pData->GetChunkCount(); Add(1, &count); for (ui64 i = 0; i < count; ++i) { // Write in reverse order TDynBitMap::TChunk chunk = pData->GetChunks()[count - i - 1]; Add(i + 1, &chunk); } } return 0; } template struct TLoadFromTypeFromListHelper { template static void Do(IBinSaver& binSaver, ui32 typeIndex, TVariantClass* pData) { if constexpr (sizeof...(TTail) == 0) { Y_ASSERT(typeIndex == 0); T0 chunk; binSaver.Add(2, &chunk); *pData = std::move(chunk); } else { if (typeIndex == 0) { Do(binSaver, 0, pData); } else { Do(binSaver, typeIndex - 1, pData); } } } }; template int Add(const chunk_id, std::variant* pData) { static_assert(std::variant_size_v> < Max()); ui32 index; if (IsReading()) { Add(1, &index); TLoadFromTypeFromListHelper>::template Do( *this, index, pData ); } else { index = pData->index(); // type cast is safe because of static_assert check above Add(1, &index); std::visit([&](auto& dst) -> void { Add(2, &dst); }, *pData); } return 0; } void AddPolymorphicBase(chunk_id, IObjectBase* pObject) { (*pObject) & (*this); } template void DoPtr(TPtrBase* pData) { if (pData && pData->Get()) { } if (IsReading()) pData->Set(CastToUserObject(LoadObject(), (T1*)nullptr)); else StoreObject(pData->GetBarePtr()); } template int Add(const chunk_id, TMaybe* pData) { TMaybe& data = *pData; if (IsReading()) { bool defined = false; Add(1, &defined); if (defined) { data = T(); Add(2, data.Get()); } } else { bool defined = data.Defined(); Add(1, &defined); if (defined) { Add(2, data.Get()); } } return 0; } template void AddMulti(TOne& one) { Add(0, &one); } template void AddMulti(THead& head, TTail&... tail) { Add(0, &head); AddMulti(tail...); } template () & std::declval())> static bool HasNonTrivialSerializer(ui32) { return true; } template static bool HasNonTrivialSerializer(...) { return sizeof(std::declval()->Add(0, std::declval())) != 1; } public: IBinSaver(IBinaryStream& stream, bool _bRead, bool stableOutput = false) : bRead(_bRead) , File(_bRead, stream) , StableOutput(stableOutput) { } virtual ~IBinSaver(); bool IsValid() const { return File.IsValid(); } }; // realisation of forward declared serialisation operator template int TPtrBase::operator&(IBinSaver& f) { f.DoPtr(this); return 0; } //////////////////////////////////////////////////////////////////////////////////////////////////// extern TClassFactory* pSaverClasses; void StartRegisterSaveload(); template struct TRegisterSaveLoadType { TRegisterSaveLoadType(int num) { StartRegisterSaveload(); pSaverClasses->RegisterType(num, TReg::NewSaveLoadNullItem, (TReg*)nullptr); } }; #define Y_BINSAVER_REGISTER(name) \ BASIC_REGISTER_CLASS(name) \ static TRegisterSaveLoadType init##name(MurmurHash(#name, sizeof(#name))); #define REGISTER_SAVELOAD_CLASS(N, name) \ BASIC_REGISTER_CLASS(name) \ static TRegisterSaveLoadType init##name##N(N); // using TObj/TRef on forward declared templ class will not work // but multiple registration with same id is allowed #define REGISTER_SAVELOAD_TEMPL1_CLASS(N, className, T) \ static TRegisterSaveLoadType> init##className##T##N(N); #define REGISTER_SAVELOAD_TEMPL2_CLASS(N, className, T1, T2) \ typedef className temp##className##T1##_##T2##temp; \ static TRegisterSaveLoadType> init##className##T1##_##T2##N(N); #define REGISTER_SAVELOAD_TEMPL3_CLASS(N, className, T1, T2, T3) \ typedef className temp##className##T1##_##T2##_##T3##temp; \ static TRegisterSaveLoadType> init##className##T1##_##T2##_##T3##N(N); #define REGISTER_SAVELOAD_NM_CLASS(N, nmspace, className) \ BASIC_REGISTER_CLASS(nmspace::className) \ static TRegisterSaveLoadType init_##nmspace##_##name##N(N); #define REGISTER_SAVELOAD_NM2_CLASS(N, nmspace1, nmspace2, className) \ BASIC_REGISTER_CLASS(nmspace1::nmspace2::className) \ static TRegisterSaveLoadType init_##nmspace1##_##nmspace2##_##name##N(N); #define REGISTER_SAVELOAD_TEMPL1_NM_CLASS(N, nmspace, className, T) \ typedef nmspace::className temp_init##nmspace##className##T##temp; \ BASIC_REGISTER_CLASS(nmspace::className) \ static TRegisterSaveLoadType> temp_init##nmspace##_##name##T##N(N); #define REGISTER_SAVELOAD_CLASS_NAME(N, cls, name) \ BASIC_REGISTER_CLASS(cls) \ static TRegisterSaveLoadType init##name##N(N); #define REGISTER_SAVELOAD_CLASS_NS_PREF(N, cls, ns, pref) \ REGISTER_SAVELOAD_CLASS_NAME(N, ns ::cls, _##pref##_##cls) #define SAVELOAD(...) \ int operator&(IBinSaver& f) { \ f.AddMulti(__VA_ARGS__); \ return 0; \ } Y_SEMICOLON_GUARD #define SAVELOAD_OVERRIDE_WITHOUT_BASE(...) \ int operator&(IBinSaver& f) override { \ f.AddMulti(__VA_ARGS__); \ return 0; \ } Y_SEMICOLON_GUARD #define SAVELOAD_OVERRIDE(base, ...) \ int operator&(IBinSaver& f) override { \ base::operator&(f); \ f.AddMulti(__VA_ARGS__); \ return 0; \ } Y_SEMICOLON_GUARD #define SAVELOAD_BASE(...) \ int operator&(IBinSaver& f) { \ TBase::operator&(f); \ f.AddMulti(__VA_ARGS__); \ return 0; \ } Y_SEMICOLON_GUARD