diff --git a/VC2008/MCServer.vcproj b/VC2008/MCServer.vcproj index 198ddb3d6..504ffc9ae 100644 --- a/VC2008/MCServer.vcproj +++ b/VC2008/MCServer.vcproj @@ -48,7 +48,7 @@ RuntimeLibrary="3" UsePrecompiledHeader="2" PrecompiledHeaderThrough="Globals.h" - WarningLevel="3" + WarningLevel="4" DebugInformationFormat="4" /> + + + + diff --git a/VC2010/MCServer.vcxproj b/VC2010/MCServer.vcxproj index 81de6d3d5..620e3fb54 100644 --- a/VC2010/MCServer.vcxproj +++ b/VC2010/MCServer.vcxproj @@ -476,6 +476,7 @@ NotUsing NotUsing + @@ -651,6 +652,7 @@ + diff --git a/VC2010/MCServer.vcxproj.filters b/VC2010/MCServer.vcxproj.filters index 223342bb4..5aa4a95a0 100644 --- a/VC2010/MCServer.vcxproj.filters +++ b/VC2010/MCServer.vcxproj.filters @@ -910,6 +910,7 @@ + @@ -1404,6 +1405,7 @@ + diff --git a/WebServer/Globals.h b/WebServer/Globals.h index 405de1075..389654d33 100644 --- a/WebServer/Globals.h +++ b/WebServer/Globals.h @@ -8,6 +8,29 @@ +// Compiler-dependent stuff: +#ifndef _MSC_VER + // Non-MS compilers don't know the override keyword + #define override +#else + // MSVC produces warning C4481 on the override keyword usage, so disable the warning altogether + #pragma warning(disable:4481) +#endif // _MSC_VER + + + + + +// A macro to disallow the copy constructor and operator= functions +// This should be used in the private: declarations for any class that shouldn't allow copying itself +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName &); \ + void operator=(const TypeName &) + + + + + // OS-dependent stuff: #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN diff --git a/makefile_base b/makefile_base index 650666b41..5e6889e7f 100644 --- a/makefile_base +++ b/makefile_base @@ -246,7 +246,8 @@ MCServer : \ build/cIsThread.o\ build/cSocketThreads.o\ build/WorldStorage.o\ - build/WSSCompact.o + build/WSSCompact.o\ + build/StringCompression.o $(CC) $(LNK_OPTIONS) \ build/json_reader.o\ build/json_value.o\ @@ -462,6 +463,7 @@ MCServer : \ build/cSocketThreads.o\ build/WorldStorage.o\ build/WSSCompact.o\ + build/StringCompression.o\ -o MCServer clean : @@ -1550,6 +1552,9 @@ build/WorldStorage.o : source/WorldStorage.cpp build/WSSCompact.o : source/WSSCompact.cpp $(CC) $(CC_OPTIONS) source/WSSCompact.cpp -c $(INCLUDE) -o build/WSSCompact.o +build/StringCompression.o : source/StringCompression.cpp + $(CC) $(CC_OPTIONS) source/StringCompression.cpp -c $(INCLUDE) -o build/StringCompression.o + # Template: copy and delete the "# "; insert filenames # build/.o : source/.cpp diff --git a/source/Globals.h b/source/Globals.h index 2718b8509..b2a973cfd 100644 --- a/source/Globals.h +++ b/source/Globals.h @@ -12,12 +12,28 @@ #ifndef _MSC_VER // Non-MS compilers don't know the override keyword #define override +#else + // MSVC produces warning C4481 on the override keyword usage, so disable the warning altogether + #pragma warning(disable:4481) + + // Disable some warnings that we don't care about: + #pragma warning(disable:4100) #endif // _MSC_VER +// A macro to disallow the copy constructor and operator= functions +// This should be used in the private: declarations for any class that shouldn't allow copying itself +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName &); \ + void operator=(const TypeName &) + + + + + // OS-dependent stuff: #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN @@ -114,8 +130,8 @@ -/// A generic interface used in ForEach() functions -template class cListCallback +/// A generic interface used mainly in ForEach() functions +template class cItemCallback { public: /// Called for each item in the internal list; return true to stop the loop, or false to continue enumerating diff --git a/source/StringCompression.cpp b/source/StringCompression.cpp new file mode 100644 index 000000000..d21248fac --- /dev/null +++ b/source/StringCompression.cpp @@ -0,0 +1,54 @@ + +// StringCompression.cpp + +// Implements the wrapping functions for compression and decompression using AString as their data + +#include "Globals.h" +#include "StringCompression.h" +#include "zlib.h" + + + + + +/// Compresses a_Data into a_Compressed; return Z_XXX error constants same as zlib's compress2() +int CompressString(const char * a_Data, int a_Length, AString & a_Compressed) +{ + uLongf CompressedSize = compressBound(a_Length); + + // HACK: We're assuming that AString returns its internal buffer in its data() call and we're overwriting that buffer! + // It saves us one allocation and one memcpy of the entire compressed data + // It may not work on some STL implementations! (Confirmed working on MSVC 2008 & 2010) + a_Compressed.resize(CompressedSize); + int errorcode = compress2( (Bytef*)a_Compressed.data(), &CompressedSize, (const Bytef*)a_Data, a_Length, Z_DEFAULT_COMPRESSION); + if (errorcode != Z_OK) + { + return errorcode; + } + a_Compressed.resize(CompressedSize); + return Z_OK; +} + + + + + +/// Uncompresses a_Data into a_Decompressed; returns Z_XXX error constants same as zlib's decompress() +int UncompressString(const char * a_Data, int a_Length, AString & a_Uncompressed, int a_UncompressedSize) +{ + // HACK: We're assuming that AString returns its internal buffer in its data() call and we're overwriting that buffer! + // It saves us one allocation and one memcpy of the entire compressed data + // It may not work on some STL implementations! (Confirmed working on MSVC 2008 & 2010) + a_Uncompressed.resize(a_UncompressedSize); + int errorcode = uncompress((Bytef*)a_Uncompressed.data(), (uLongf *)&a_UncompressedSize, (const Bytef*)a_Data, a_Length); + if (errorcode != Z_OK) + { + return errorcode; + } + a_Uncompressed.resize(a_UncompressedSize); + return Z_OK; +} + + + + diff --git a/source/StringCompression.h b/source/StringCompression.h new file mode 100644 index 000000000..976fd2ecd --- /dev/null +++ b/source/StringCompression.h @@ -0,0 +1,17 @@ + +// StringCompression.h + +// Interfaces to the wrapping functions for compression and decompression using AString as their data + + + + +/// Compresses a_Data into a_Compressed; return Z_XXX error constants same as zlib's compress2() +extern int CompressString(const char * a_Data, int a_Length, AString & a_Compressed); + +/// Uncompresses a_Data into a_Decompressed; returns Z_XXX error constants same as zlib's decompress() +extern int UncompressString(const char * a_Data, int a_Length, AString & a_Uncompressed, int a_UncompressedSize); + + + + diff --git a/source/WSSCompact.cpp b/source/WSSCompact.cpp index b2c2d0bb9..c023875a5 100644 --- a/source/WSSCompact.cpp +++ b/source/WSSCompact.cpp @@ -8,6 +8,11 @@ #include "cWorld.h" #include "zlib.h" #include +#include "StringCompression.h" +#include "cChestEntity.h" +#include "cSignEntity.h" +#include "cFurnaceEntity.h" +#include "BlockID.h" @@ -53,7 +58,7 @@ cWSSCompact::~cWSSCompact() -bool cWSSCompact::LoadChunk(const cChunkPtr & a_Chunk) +bool cWSSCompact::LoadChunk(const cChunkCoords & a_Chunk) { cPAKFile * f = LoadPAKFile(a_Chunk); if (f == NULL) @@ -62,14 +67,14 @@ bool cWSSCompact::LoadChunk(const cChunkPtr & a_Chunk) return false; } - return f->LoadChunk(a_Chunk); + return f->LoadChunk(a_Chunk, m_World); } -bool cWSSCompact::SaveChunk(const cChunkPtr & a_Chunk) +bool cWSSCompact::SaveChunk(const cChunkCoords & a_Chunk) { cPAKFile * f = LoadPAKFile(a_Chunk); if (f == NULL) @@ -77,18 +82,18 @@ bool cWSSCompact::SaveChunk(const cChunkPtr & a_Chunk) // For some reason we couldn't locate the file return false; } - return f->SaveChunk(a_Chunk); + return f->SaveChunk(a_Chunk, m_World); } -cWSSCompact::cPAKFile * cWSSCompact::LoadPAKFile(const cChunkPtr & a_Chunk) +cWSSCompact::cPAKFile * cWSSCompact::LoadPAKFile(const cChunkCoords & a_Chunk) { // We need to retain this weird conversion code, because some edge chunks are in the wrong PAK file - const int LayerX = (int)(floorf((float)a_Chunk->GetPosX() / 32.0f)); - const int LayerZ = (int)(floorf((float)a_Chunk->GetPosZ() / 32.0f)); + const int LayerX = (int)(floorf((float)a_Chunk.m_ChunkX / 32.0f)); + const int LayerZ = (int)(floorf((float)a_Chunk.m_ChunkZ / 32.0f)); // Is it already cached? for (cPAKFiles::iterator itr = m_PAKFiles.begin(); itr != m_PAKFiles.end(); ++itr) @@ -207,10 +212,10 @@ cWSSCompact::cPAKFile::~cPAKFile() -bool cWSSCompact::cPAKFile::LoadChunk(const cChunkPtr & a_Chunk) +bool cWSSCompact::cPAKFile::LoadChunk(const cChunkCoords & a_Chunk, cWorld * a_World) { - int ChunkX = a_Chunk->GetPosX(); - int ChunkZ = a_Chunk->GetPosZ(); + int ChunkX = a_Chunk.m_ChunkX; + int ChunkZ = a_Chunk.m_ChunkZ; sChunkHeader * Header = NULL; int Offset = 0; for (sChunkHeaders::iterator itr = m_ChunkHeaders.begin(); itr != m_ChunkHeaders.end(); ++itr) @@ -228,16 +233,16 @@ bool cWSSCompact::cPAKFile::LoadChunk(const cChunkPtr & a_Chunk) return false; } - return LoadChunk(a_Chunk, Offset, Header); + return LoadChunk(a_Chunk, Offset, Header, a_World); } -bool cWSSCompact::cPAKFile::SaveChunk(const cChunkPtr & a_Chunk) +bool cWSSCompact::cPAKFile::SaveChunk(const cChunkCoords & a_Chunk, cWorld * a_World) { - if (!SaveChunkToData(a_Chunk)) + if (!SaveChunkToData(a_Chunk, a_World)) { return false; } @@ -252,50 +257,63 @@ bool cWSSCompact::cPAKFile::SaveChunk(const cChunkPtr & a_Chunk) -bool cWSSCompact::cPAKFile::LoadChunk(const cChunkPtr & a_Chunk, int a_Offset, sChunkHeader * a_Header) +bool cWSSCompact::cPAKFile::LoadChunk(const cChunkCoords & a_Chunk, int a_Offset, sChunkHeader * a_Header, cWorld * a_World) { + // Crude data integrity check: + if (a_Header->m_UncompressedSize < cChunk::c_BlockDataSize) + { + LOGWARNING("Chunk [%d, %d] has too short decompressed data (%d out of %d needed), erasing", + a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, + a_Header->m_UncompressedSize, cChunk::c_BlockDataSize + ); + EraseChunk(a_Chunk); + m_NumDirty++; + return false; + } + // Decompress the data: - uLongf DestSize = a_Header->m_UncompressedSize; - std::auto_ptr BlockData(new char[ DestSize ]); - int errorcode = uncompress( (Bytef*)BlockData.get(), &DestSize, (Bytef*)m_DataContents.data() + a_Offset, a_Header->m_CompressedSize ); + AString UncompressedData; + int errorcode = UncompressString(m_DataContents.data() + a_Offset, a_Header->m_CompressedSize, UncompressedData, a_Header->m_UncompressedSize); if (errorcode != Z_OK) { LOGERROR("Error %d decompressing data for chunk [%d, %d] from file \"%s\"", errorcode, - a_Chunk->GetPosX(), a_Chunk->GetPosZ(), + a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, m_FileName.c_str() ); return false; } - if (a_Header->m_UncompressedSize != DestSize) + if (a_Header->m_UncompressedSize != (int)UncompressedData.size()) { LOGWARNING("Uncompressed data size differs (exp %d, got %d) for chunk [%d, %d] from file \"%s\"", - a_Header->m_UncompressedSize, DestSize, - a_Chunk->GetPosX(), a_Chunk->GetPosZ(), + a_Header->m_UncompressedSize, UncompressedData.size(), + a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, m_FileName.c_str() ); return false; } - a_Chunk->CopyBlockDataFrom(BlockData.get()); - a_Chunk->SetValid(); - - if (DestSize > cChunk::c_BlockDataSize ) // We gots some extra data :D + cEntityList Entities; + cBlockEntityList BlockEntities; + + if (a_Header->m_UncompressedSize > cChunk::c_BlockDataSize ) // We gots some extra data :D { LOGINFO("Parsing trailing JSON"); Json::Value root; // will contain the root value after parsing. Json::Reader reader; - if ( !reader.parse( BlockData.get() + cChunk::c_BlockDataSize, root, false ) ) + if ( !reader.parse( UncompressedData.data() + cChunk::c_BlockDataSize, root, false ) ) { LOGERROR("Failed to parse trailing JSON!"); } else { - a_Chunk->LoadFromJson( root ); + LoadEntitiesFromJson(root, Entities, BlockEntities, a_World); } } + a_World->ChunkDataLoaded(a_Chunk.m_ChunkX, 0, a_Chunk.m_ChunkZ, UncompressedData.data(), Entities, BlockEntities); + return true; } @@ -303,11 +321,10 @@ bool cWSSCompact::cPAKFile::LoadChunk(const cChunkPtr & a_Chunk, int a_Offset, s -void cWSSCompact::cPAKFile::EraseChunk(const cChunkPtr & a_Chunk) +void cWSSCompact::cPAKFile::EraseChunk(const cChunkCoords & a_Chunk) { - int ChunkX = a_Chunk->GetPosX(); - int ChunkZ = a_Chunk->GetPosZ(); - sChunkHeader * Header = NULL; + int ChunkX = a_Chunk.m_ChunkX; + int ChunkZ = a_Chunk.m_ChunkZ; int Offset = 0; for (sChunkHeaders::iterator itr = m_ChunkHeaders.begin(); itr != m_ChunkHeaders.end(); ++itr) { @@ -326,47 +343,52 @@ void cWSSCompact::cPAKFile::EraseChunk(const cChunkPtr & a_Chunk) -bool cWSSCompact::cPAKFile::SaveChunkToData(const cChunkPtr & a_Chunk) +bool cWSSCompact::cPAKFile::SaveChunkToData(const cChunkCoords & a_Chunk, cWorld * a_World) { - // Erase any existing data for the chunk: - EraseChunk(a_Chunk); - // Serialize the chunk: + cJsonChunkSerializer Serializer; + a_World->GetChunkData(a_Chunk.m_ChunkX, 0, a_Chunk.m_ChunkZ, &Serializer); + if (Serializer.GetBlockData().empty()) + { + // Chunk not valid + return false; + } + AString Data; - Data.assign(a_Chunk->pGetBlockData(), cChunk::c_BlockDataSize); - Json::Value root; - a_Chunk->SaveToJson( root ); - if (!root.empty()) + std::swap(Serializer.GetBlockData(), Data); + if (Serializer.HasJsonData()) { AString JsonData; Json::StyledWriter writer; - JsonData = writer.write( root ); + JsonData = writer.write(Serializer.GetRoot()); Data.append(JsonData); } // Compress the data: - uLongf CompressedSize = compressBound(Data.size()); - std::auto_ptr Compressed(new char[CompressedSize]); - int errorcode = compress2( (Bytef*)Compressed.get(), &CompressedSize, (const Bytef*)Data.data(), Data.size(), Z_DEFAULT_COMPRESSION); + AString CompressedData; + int errorcode = CompressString(Data.data(), Data.size(), CompressedData); if ( errorcode != Z_OK ) { - LOGERROR("Error %i compressing data for chunk [%d, %d]", errorcode, a_Chunk->GetPosX(), a_Chunk->GetPosZ() ); + LOGERROR("Error %i compressing data for chunk [%d, %d]", errorcode, a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ); return false; } + // Erase any existing data for the chunk: + EraseChunk(a_Chunk); + // Save the header: sChunkHeader * Header = new sChunkHeader; if (Header == NULL) { return false; } - Header->m_CompressedSize = CompressedSize; - Header->m_ChunkX = a_Chunk->GetPosX(); - Header->m_ChunkZ = a_Chunk->GetPosZ(); - Header->m_UncompressedSize = Data.size(); + Header->m_CompressedSize = (int)CompressedData.size(); + Header->m_ChunkX = a_Chunk.m_ChunkX; + Header->m_ChunkZ = a_Chunk.m_ChunkZ; + Header->m_UncompressedSize = (int)Data.size(); m_ChunkHeaders.push_back(Header); - m_DataContents.append(Compressed.get(), CompressedSize); + m_DataContents.append(CompressedData.data(), CompressedData.size()); m_NumDirty++; return true; @@ -402,7 +424,7 @@ void cWSSCompact::cPAKFile::SynchronizeFile(void) { WRITE(**itr); } - if (f.Write(m_DataContents.data(), m_DataContents.size()) != m_DataContents.size()) + if (f.Write(m_DataContents.data(), m_DataContents.size()) != (int)m_DataContents.size()) { LOGERROR("cWSSCompact: ERROR writing chunk contents to file \"%s\" (line %d); file offset %d", m_FileName.c_str(), __LINE__, f.Tell()); return; @@ -413,3 +435,70 @@ void cWSSCompact::cPAKFile::SynchronizeFile(void) + +void cWSSCompact::cPAKFile::LoadEntitiesFromJson(Json::Value & a_Value, cEntityList & a_Entities, cBlockEntityList & a_BlockEntities, cWorld * a_World) +{ + // Load chests + Json::Value AllChests = a_Value.get("Chests", Json::nullValue); + if (!AllChests.empty()) + { + for (Json::Value::iterator itr = AllChests.begin(); itr != AllChests.end(); ++itr ) + { + Json::Value & Chest = *itr; + cChestEntity * ChestEntity = new cChestEntity(0,0,0, a_World); + if (!ChestEntity->LoadFromJson( Chest ) ) + { + LOGERROR("ERROR READING CHEST FROM JSON!" ); + delete ChestEntity; + } + else + { + a_BlockEntities.push_back( ChestEntity ); + } + } // for itr - AllChests[] + } + + // Load furnaces + Json::Value AllFurnaces = a_Value.get("Furnaces", Json::nullValue); + if( !AllFurnaces.empty() ) + { + for( Json::Value::iterator itr = AllFurnaces.begin(); itr != AllFurnaces.end(); ++itr ) + { + Json::Value & Furnace = *itr; + cFurnaceEntity * FurnaceEntity = new cFurnaceEntity(0,0,0, a_World); + if( !FurnaceEntity->LoadFromJson( Furnace ) ) + { + LOGERROR("ERROR READING FURNACE FROM JSON!" ); + delete FurnaceEntity; + } + else + { + a_BlockEntities.push_back( FurnaceEntity ); + } + } // for itr - AllFurnaces[] + } + + // Load signs + Json::Value AllSigns = a_Value.get("Signs", Json::nullValue); + if( !AllSigns.empty() ) + { + for( Json::Value::iterator itr = AllSigns.begin(); itr != AllSigns.end(); ++itr ) + { + Json::Value & Sign = *itr; + cSignEntity * SignEntity = new cSignEntity( E_BLOCK_SIGN_POST, 0,0,0, a_World); + if ( !SignEntity->LoadFromJson( Sign ) ) + { + LOGERROR("ERROR READING SIGN FROM JSON!" ); + delete SignEntity; + } + else + { + a_BlockEntities.push_back( SignEntity ); + } + } // for itr - AllSigns[] + } +} + + + + diff --git a/source/WSSCompact.h b/source/WSSCompact.h index dc5ecfe9e..a2bfc4d20 100644 --- a/source/WSSCompact.h +++ b/source/WSSCompact.h @@ -37,8 +37,8 @@ protected: cPAKFile(const AString & a_FileName, int a_LayerX, int a_LayerZ); ~cPAKFile(); - bool SaveChunk(const cChunkPtr & a_Chunk); - bool LoadChunk(const cChunkPtr & a_Chunk); + bool SaveChunk(const cChunkCoords & a_Chunk, cWorld * a_World); + bool LoadChunk(const cChunkCoords & a_Chunk, cWorld * a_World); int GetLayerX(void) const {return m_LayerX; } int GetLayerZ(void) const {return m_LayerZ; } @@ -54,10 +54,11 @@ protected: int m_NumDirty; // Number of chunks that were written into m_DataContents but not into the file - bool LoadChunk(const cChunkPtr & a_Chunk, int a_Offset, sChunkHeader * a_Header); - void EraseChunk(const cChunkPtr & a_Chunk); // Erases the chunk data from m_DataContents and updates m_ChunkHeaders - bool SaveChunkToData(const cChunkPtr & a_Chunk); // Saves the chunk to m_DataContents, updates headers and m_NumDirty + bool LoadChunk(const cChunkCoords & a_Chunk, int a_Offset, sChunkHeader * a_Header, cWorld * a_World); + void EraseChunk(const cChunkCoords & a_Chunk); // Erases the chunk data from m_DataContents and updates m_ChunkHeaders + bool SaveChunkToData(const cChunkCoords & a_Chunk, cWorld * a_World); // Saves the chunk to m_DataContents, updates headers and m_NumDirty void SynchronizeFile(void); // Writes m_DataContents along with the headers to file, resets m_NumDirty + void LoadEntitiesFromJson(Json::Value & a_Value, cEntityList & a_Entities, cBlockEntityList & a_BlockEntities, cWorld * a_World); } ; typedef std::list cPAKFiles; @@ -65,11 +66,11 @@ protected: cPAKFiles m_PAKFiles; // A MRU cache of PAK files /// Loads the correct PAK file either from cache or from disk, manages the m_PAKFiles cache - cPAKFile * LoadPAKFile(const cChunkPtr & a_Chunk); + cPAKFile * LoadPAKFile(const cChunkCoords & a_Chunk); // cWSSchema overrides: - virtual bool LoadChunk(const cChunkPtr & a_Chunk) override; - virtual bool SaveChunk(const cChunkPtr & a_Chunk) override; + virtual bool LoadChunk(const cChunkCoords & a_Chunk) override; + virtual bool SaveChunk(const cChunkCoords & a_Chunk) override; virtual const AString GetName(void) const override {return "compact"; } } ; diff --git a/source/WorldStorage.cpp b/source/WorldStorage.cpp index bdc0e84a9..50c89cf7e 100644 --- a/source/WorldStorage.cpp +++ b/source/WorldStorage.cpp @@ -10,6 +10,9 @@ #include "WSSCompact.h" #include "cWorld.h" #include "cChunkGenerator.h" +#include "cEntity.h" +#include "cBlockEntity.h" +#include "BlockID.h" @@ -24,8 +27,8 @@ public: protected: // cWSSchema overrides: - virtual bool LoadChunk(const cChunkPtr & a_Chunk) override {return false; } - virtual bool SaveChunk(const cChunkPtr & a_Chunk) override {return true; } + virtual bool LoadChunk(const cChunkCoords & a_Chunk) override {return false; } + virtual bool SaveChunk(const cChunkCoords & a_Chunk) override {return true; } virtual const AString GetName(void) const override {return "forgetful"; } } ; @@ -33,6 +36,70 @@ protected: +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cJsonChunkSerializer: + +cJsonChunkSerializer::cJsonChunkSerializer(void) : + m_HasJsonData(false) +{ + m_Root["Chests"] = m_AllChests; + m_Root["Furnaces"] = m_AllFurnaces; + m_Root["Signs"] = m_AllSigns; +} + + + + + +void cJsonChunkSerializer::BlockData(const char * a_Data) +{ + m_BlockData.assign(a_Data, cChunk::c_BlockDataSize); +} + + + + + +void cJsonChunkSerializer::Entity(cEntity * a_Entity) +{ + // TODO: a_Entity->SaveToJson(m_Root); +} + + + + + +void cJsonChunkSerializer::BlockEntity(cBlockEntity * a_BlockEntity) +{ + const char * SaveInto = NULL; + switch (a_BlockEntity->GetBlockType()) + { + case E_BLOCK_CHEST: SaveInto = "Chests"; break; + case E_BLOCK_FURNACE: SaveInto = "Furnaces"; break; + case E_BLOCK_SIGN_POST: SaveInto = "Signs"; break; + case E_BLOCK_WALLSIGN: SaveInto = "Signs"; break; + + default: + { + assert(!"Unhandled blocktype in BlockEntities list while saving to JSON"); + break; + } + } // switch (BlockEntity->GetBlockType()) + if (SaveInto == NULL) + { + return; + } + + Json::Value val; + a_BlockEntity->SaveToJson(val); + m_Root[SaveInto].append(val); + m_HasJsonData = true; +} + + + + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // cWorldStorage: @@ -94,12 +161,12 @@ void cWorldStorage::WaitForFinish(void) -void cWorldStorage::QueueLoadChunk(cChunkPtr & a_Chunk) +void cWorldStorage::QueueLoadChunk(int a_ChunkX, int a_ChunkZ) { // Queues the chunk for loading; if not loaded, the chunk will be generated cCSLock Lock(m_CSLoadQueue); - m_LoadQueue.remove(a_Chunk); // Don't add twice - m_LoadQueue.push_back(a_Chunk); + m_LoadQueue.remove (cChunkCoords(a_ChunkX, a_ChunkZ)); // Don't add twice + m_LoadQueue.push_back(cChunkCoords(a_ChunkX, a_ChunkZ)); m_Event.Set(); } @@ -107,11 +174,11 @@ void cWorldStorage::QueueLoadChunk(cChunkPtr & a_Chunk) -void cWorldStorage::QueueSaveChunk(cChunkPtr & a_Chunk) +void cWorldStorage::QueueSaveChunk(int a_ChunkX, int a_ChunkZ) { cCSLock Lock(m_CSSaveQueue); - m_SaveQueue.remove(a_Chunk); // Don't add twice - m_SaveQueue.push_back(a_Chunk); + m_SaveQueue.remove (cChunkCoords(a_ChunkX, a_ChunkZ)); // Don't add twice + m_SaveQueue.push_back(cChunkCoords(a_ChunkX, a_ChunkZ)); m_Event.Set(); } @@ -119,7 +186,7 @@ void cWorldStorage::QueueSaveChunk(cChunkPtr & a_Chunk) -void cWorldStorage::UnqueueLoad(const cChunkPtr & a_Chunk) +void cWorldStorage::UnqueueLoad(const cChunkCoords & a_Chunk) { cCSLock Lock(m_CSLoadQueue); m_LoadQueue.remove(a_Chunk); @@ -129,7 +196,7 @@ void cWorldStorage::UnqueueLoad(const cChunkPtr & a_Chunk) -void cWorldStorage::UnqueueSave(const cChunkPtr & a_Chunk) +void cWorldStorage::UnqueueSave(const cChunkCoords & a_Chunk) { cCSLock Lock(m_CSSaveQueue); m_SaveQueue.remove(a_Chunk); @@ -189,38 +256,8 @@ void cWorldStorage::Execute(void) return; } - // Load 1 chunk: - cChunkPtr ToLoad; - { - cCSLock Lock(m_CSLoadQueue); - if (m_LoadQueue.size() > 0) - { - ToLoad = m_LoadQueue.front(); - m_LoadQueue.pop_front(); - } - HasMore = (m_LoadQueue.size() > 0); - } - if ((ToLoad != NULL) && !LoadChunk(ToLoad)) - { - // The chunk couldn't be loaded, generate it: - m_World->GetGenerator().GenerateChunk(ToLoad->GetPosX(), ToLoad->GetPosZ()); - } - - // Save 1 chunk: - cChunkPtr Save; - { - cCSLock Lock(m_CSSaveQueue); - if (m_SaveQueue.size() > 0) - { - Save = m_SaveQueue.front(); - m_SaveQueue.pop_front(); - } - HasMore = HasMore || (m_SaveQueue.size() > 0); - } - if ((Save != NULL) && (!m_SaveSchema->SaveChunk(Save))) - { - LOGWARNING("Cannot save chunk [%d, %d]", Save->GetPosX(), Save->GetPosZ()); - } + HasMore = LoadOneChunk(); + HasMore = HasMore | SaveOneChunk(); } while (HasMore); } } @@ -229,9 +266,62 @@ void cWorldStorage::Execute(void) -bool cWorldStorage::LoadChunk(const cChunkPtr & a_Chunk) +bool cWorldStorage::LoadOneChunk(void) { - if (a_Chunk->IsValid()) + cChunkCoords ToLoad(0, 0); + bool HasMore; + bool ShouldLoad = false; + { + cCSLock Lock(m_CSLoadQueue); + if (m_LoadQueue.size() > 0) + { + ToLoad = m_LoadQueue.front(); + m_LoadQueue.pop_front(); + ShouldLoad = true; + } + HasMore = (m_LoadQueue.size() > 0); + } + if (ShouldLoad && !LoadChunk(ToLoad)) + { + // The chunk couldn't be loaded, generate it: + m_World->GetGenerator().GenerateChunk(ToLoad.m_ChunkX, ToLoad.m_ChunkZ); + } + return HasMore; +} + + + + + +bool cWorldStorage::SaveOneChunk(void) +{ + cChunkCoords Save(0, 0); + bool HasMore; + bool ShouldSave = false; + { + cCSLock Lock(m_CSSaveQueue); + if (m_SaveQueue.size() > 0) + { + Save = m_SaveQueue.front(); + m_SaveQueue.pop_front(); + ShouldSave = true; + } + HasMore = (m_SaveQueue.size() > 0); + } + if (ShouldSave && !m_SaveSchema->SaveChunk(Save)) + { + LOGWARNING("Cannot save chunk [%d, %d]", Save.m_ChunkX, Save.m_ChunkZ); + } + return HasMore; +} + + + + + +bool cWorldStorage::LoadChunk(const cChunkCoords & a_Chunk) +{ + if (m_World->IsChunkValid(a_Chunk.m_ChunkX, 0, a_Chunk.m_ChunkZ)) { // Already loaded (can happen, since the queue is async) return true; @@ -244,7 +334,7 @@ bool cWorldStorage::LoadChunk(const cChunkPtr & a_Chunk) for (cWSSchemaList::iterator itr = m_Schemas.begin(); itr != m_Schemas.end(); ++itr) { - if ((*itr)->LoadChunk(a_Chunk)) + if (((*itr) != m_SaveSchema) && (*itr)->LoadChunk(a_Chunk)) { return true; } @@ -256,3 +346,4 @@ bool cWorldStorage::LoadChunk(const cChunkPtr & a_Chunk) + diff --git a/source/WorldStorage.h b/source/WorldStorage.h index 52573caf0..5de7f49fa 100644 --- a/source/WorldStorage.h +++ b/source/WorldStorage.h @@ -4,6 +4,7 @@ // Interfaces to the cWorldStorage class representing the chunk loading / saving thread // This class decides which storage schema to use for saving; it queries all available schemas for loading // Also declares the base class for all storage schemas, cWSSchema +// Helper serialization class cJsonChunkSerializer is declared as well @@ -15,6 +16,15 @@ #include "cChunk.h" #include "cIsThread.h" +#include + + + + + +// fwd: +class cEntity; +class cBlockEntity; @@ -27,8 +37,8 @@ public: cWSSchema(cWorld * a_World) : m_World(a_World) {} virtual ~cWSSchema() {} // Force the descendants' destructors to be virtual - virtual bool LoadChunk(const cChunkPtr & a_Chunk) = 0; - virtual bool SaveChunk(const cChunkPtr & a_Chunk) = 0; + virtual bool LoadChunk(const cChunkCoords & a_Chunk) = 0; + virtual bool SaveChunk(const cChunkCoords & a_Chunk) = 0; virtual const AString GetName(void) const = 0; protected: @@ -42,6 +52,41 @@ typedef std::list cWSSchemaList; +/// Helper class for serializing a chunk into Json +class cJsonChunkSerializer : + public cChunkDataCallback +{ +public: + + cJsonChunkSerializer(void); + + Json::Value & GetRoot (void) {return m_Root; } + AString & GetBlockData(void) {return m_BlockData; } + bool HasJsonData (void) const {return m_HasJsonData; } + +protected: + + // BlockData is serialized into string + AString m_BlockData; + + // Entities and BlockEntities are serialized to Json + Json::Value m_Root; + Json::Value m_AllChests; + Json::Value m_AllFurnaces; + Json::Value m_AllSigns; + bool m_HasJsonData; + + // cChunkDataCallback overrides: + virtual void BlockData (const char * a_Data) override; + virtual void Entity (cEntity * a_Entity) override; + virtual void BlockEntity(cBlockEntity * a_Entity) override; +} ; + + + + + +/// The actual world storage class class cWorldStorage : public cIsThread { @@ -52,11 +97,11 @@ public: cWorldStorage(void); ~cWorldStorage(); - void QueueLoadChunk(cChunkPtr & a_Chunk); // Queues the chunk for loading; if not loaded, the chunk will be generated - void QueueSaveChunk(cChunkPtr & a_Chunk); + void QueueLoadChunk(int a_ChunkX, int a_ChunkZ); // Queues the chunk for loading; if not loaded, the chunk will be generated + void QueueSaveChunk(int a_ChunkX, int a_ChunkZ); - void UnqueueLoad(const cChunkPtr & a_Chunk); - void UnqueueSave(const cChunkPtr & a_Chunk); + void UnqueueLoad(const cChunkCoords & a_Chunk); + void UnqueueSave(const cChunkCoords & a_Chunk); bool Start(cWorld * a_World, const AString & a_StorageSchemaName); // Hide the cIsThread's Start() method, we need to provide args void WaitForFinish(void); @@ -67,20 +112,31 @@ protected: AString m_StorageSchemaName; cCriticalSection m_CSLoadQueue; - cChunkPtrList m_LoadQueue; + cChunkCoordsList m_LoadQueue; cCriticalSection m_CSSaveQueue; - cChunkPtrList m_SaveQueue; + cChunkCoordsList m_SaveQueue; cEvent m_Event; // Set when there's any addition to the queues + /// All the storage schemas (all used for loading) cWSSchemaList m_Schemas; - cWSSchema * m_SaveSchema; + + /// The one storage schema used for saving + cWSSchema * m_SaveSchema; void InitSchemas(void); virtual void Execute(void) override; - bool LoadChunk(const cChunkPtr & a_Chunk); + + /// Loads one chunk from the queue (if any queued); returns true if there are more chunks in the load queue + bool LoadOneChunk(void); + + /// Saves one chunk from the queue (if any queued); returns true if there are more chunks in the save queue + bool SaveOneChunk(void); + + /// Loads the chunk specified; returns true on success, false on failure + bool LoadChunk(const cChunkCoords & a_Chunk); } ; diff --git a/source/cBlockEntity.h b/source/cBlockEntity.h index 4ac453e6f..ad1cf14ba 100644 --- a/source/cBlockEntity.h +++ b/source/cBlockEntity.h @@ -11,6 +11,11 @@ enum ENUM_BLOCK_ID; +namespace Json +{ + class Value; +}; + class cClientHandle; class cPlayer; class cWorld; @@ -41,6 +46,8 @@ public: cWorld * GetWorld(void) const {return m_World; } + virtual void SaveToJson (Json::Value & a_Value ) = 0; + virtual void UsedBy( cPlayer * a_Player ) = 0; virtual void SendTo( cClientHandle* a_Client ) { (void)a_Client; } diff --git a/source/cChestEntity.cpp b/source/cChestEntity.cpp index c79057829..47b5a9476 100644 --- a/source/cChestEntity.cpp +++ b/source/cChestEntity.cpp @@ -186,11 +186,6 @@ void cChestEntity::SendTo( cClientHandle* a_Client, cServer* a_Server ) void cChestEntity::UsedBy( cPlayer * a_Player ) { - LOG("Used a chest"); -// m_Content[0].m_ItemCount = 1; -// m_Content[0].m_ItemID = E_ITEM_STONE; -// m_Content[0].m_ItemHealth = 0; - if( !GetWindow() ) { cWindow* Window = new cWindow( this, true ); diff --git a/source/cChestEntity.h b/source/cChestEntity.h index d3d98739b..323eb0f8a 100644 --- a/source/cChestEntity.h +++ b/source/cChestEntity.h @@ -37,10 +37,10 @@ public: cItem * GetSlot( int a_Slot ); void SetSlot( int a_Slot, cItem & a_Item ); - bool LoadFromFile(cFile & a_File); // deprecated format + OBSOLETE bool LoadFromFile(cFile & a_File); // deprecated format bool LoadFromJson( const Json::Value& a_Value ); - void SaveToJson( Json::Value& a_Value ); + virtual void SaveToJson(Json::Value& a_Value ) override; void SendTo( cClientHandle* a_Client, cServer* a_Server ); diff --git a/source/cChunk.cpp b/source/cChunk.cpp index d1800c053..89d03bfb5 100644 --- a/source/cChunk.cpp +++ b/source/cChunk.cpp @@ -63,6 +63,8 @@ cChunk::cChunk(int a_X, int a_Y, int a_Z, cWorld * a_World) , m_BlockTickZ( 0 ) , m_World( a_World ) , m_IsValid(false) + , m_IsDirty(false) + , m_IsSaving(false) { // LOGINFO("### new cChunk (%i, %i) at %p, thread 0x%x ###", a_X, a_Z, this, GetCurrentThreadId()); } @@ -143,7 +145,120 @@ void cChunk::SetValid(bool a_SendToClients) bool cChunk::CanUnload(void) { cCSLock Lock(m_CSClients); - return m_LoadedByClient.empty(); + return m_LoadedByClient.empty() && !m_IsDirty; +} + + + + + +void cChunk::MarkSaving(void) +{ + m_IsSaving = true; +} + + + + + +void cChunk::MarkSaved(void) +{ + if (!m_IsSaving) + { + return; + } + m_IsDirty = false; +} + + + + + +void cChunk::MarkLoaded(void) +{ + m_IsDirty = false; + m_IsValid = true; +} + + + + + +void cChunk::GetAllData(cChunkDataCallback * a_Callback) +{ + a_Callback->BlockData(m_BlockData); + + cCSLock Lock(m_CSEntities); + for (cEntityList::iterator itr = m_Entities.begin(); itr != m_Entities.end(); ++itr) + { + a_Callback->Entity(*itr); + } + + for (cBlockEntityList::iterator itr = m_BlockEntities.begin(); itr != m_BlockEntities.end(); ++itr) + { + a_Callback->BlockEntity(*itr); + } +} + + + + + +void cChunk::SetAllData(const char * a_BlockData, cEntityList & a_Entities, cBlockEntityList & a_BlockEntities) +{ + memcpy(m_BlockData, a_BlockData, sizeof(m_BlockData)); + + // Clear the internal entities: + cCSLock Lock(m_CSEntities); + for (cEntityList::iterator itr = m_Entities.begin(); itr != m_Entities.end(); ++itr) + { + if ((*itr)->GetEntityType() == cEntity::E_PLAYER) + { + // Move players into the new entity list + a_Entities.push_back(*itr); + } + else + { + // Delete other entities (there should not be any, since we're now loading / generating the chunk) + LOGWARNING("cChunk: There is an unexpected entity #%d of type %s in chunk [%d, %d]; it will be deleted", + (*itr)->GetUniqueID(), (*itr)->GetClass(), + m_PosX, m_PosZ + ); + delete *itr; + } + } + for (cBlockEntityList::iterator itr = m_BlockEntities.begin(); itr != m_BlockEntities.end(); ++itr) + { + delete *itr; + } + + // Swap the entity lists: + std::swap(a_Entities, m_Entities); + std::swap(a_BlockEntities, m_BlockEntities); + + // Create block entities that the loader didn't load; fill them with defaults + CreateBlockEntities(); +} + + + + + +/// Returns true if there is a block entity at the coords specified +bool cChunk::HasBlockEntityAt(int a_BlockX, int a_BlockY, int a_BlockZ) +{ + for (cBlockEntityList::iterator itr = m_BlockEntities.begin(); itr != m_BlockEntities.end(); ++itr) + { + if ( + ((*itr)->GetPosX() == a_BlockX) && + ((*itr)->GetPosY() == a_BlockY) && + ((*itr)->GetPosZ() == a_BlockZ) + ) + { + return true; + } + } // for itr - m_BlockEntities[] + return false; } @@ -405,9 +520,8 @@ char cChunk::GetHeight( int a_X, int a_Z ) -void cChunk::CreateBlockEntities() +void cChunk::CreateBlockEntities(void) { - cCSLock Lock(m_CSBlockLists); for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) @@ -419,20 +533,29 @@ void cChunk::CreateBlockEntities() { case E_BLOCK_CHEST: { - m_BlockEntities.push_back( new cChestEntity( x + m_PosX * 16, y + m_PosY * 128, z + m_PosZ * 16, m_World) ); + if (!HasBlockEntityAt(x + m_PosX * 16, y + m_PosY * 128, z + m_PosZ * 16)) + { + m_BlockEntities.push_back( new cChestEntity( x + m_PosX * 16, y + m_PosY * 128, z + m_PosZ * 16, m_World) ); + } break; } case E_BLOCK_FURNACE: { - m_BlockEntities.push_back( new cFurnaceEntity( x + m_PosX * 16, y + m_PosY * 128, z + m_PosZ * 16, m_World) ); + if (!HasBlockEntityAt(x + m_PosX * 16, y + m_PosY * 128, z + m_PosZ * 16)) + { + m_BlockEntities.push_back( new cFurnaceEntity( x + m_PosX * 16, y + m_PosY * 128, z + m_PosZ * 16, m_World) ); + } break; } case E_BLOCK_SIGN_POST: case E_BLOCK_WALLSIGN: { - m_BlockEntities.push_back( new cSignEntity( BlockType, x + m_PosX * 16, y + m_PosY * 128, z + m_PosZ * 16, m_World) ); + if (!HasBlockEntityAt(x + m_PosX * 16, y + m_PosY * 128, z + m_PosZ * 16)) + { + m_BlockEntities.push_back( new cSignEntity( BlockType, x + m_PosX * 16, y + m_PosY * 128, z + m_PosZ * 16, m_World) ); + } break; } } // switch (BlockType) @@ -678,6 +801,8 @@ void cChunk::SetBlock( int a_X, int a_Y, int a_Z, char a_BlockType, char a_Block assert(IsValid()); // Is this chunk loaded / generated? + MarkDirty(); + int index = a_Y + (a_Z * 128) + (a_X * 128 * 16); char OldBlockMeta = GetLight( m_BlockMeta, index ); char OldBlockType = m_BlockType[index]; @@ -742,6 +867,8 @@ void cChunk::FastSetBlock( int a_X, int a_Y, int a_Z, char a_BlockType, char a_B assert(IsValid()); + MarkDirty(); + const int index = a_Y + (a_Z * 128) + (a_X * 128 * 16); const char OldBlock = m_BlockType[index]; if (OldBlock == a_BlockType) @@ -868,6 +995,7 @@ void cChunk::CollectPickupsByPlayer(cPlayer * a_Player) float SqrDist = DiffX * DiffX + DiffY * DiffY + DiffZ * DiffZ; if (SqrDist < 1.5f * 1.5f) // 1.5 block { + MarkDirty(); (reinterpret_cast(*itr))->CollectedBy( a_Player ); } } @@ -893,6 +1021,7 @@ void cChunk::UpdateSign(int a_PosX, int a_PosY, int a_PosZ, const AString & a_Li ) ) { + MarkDirty(); (reinterpret_cast(*itr))->SetLines(a_Line1, a_Line2, a_Line3, a_Line4); (*itr)->SendTo(NULL); } @@ -906,6 +1035,7 @@ void cChunk::UpdateSign(int a_PosX, int a_PosY, int a_PosZ, const AString & a_Li void cChunk::RemoveBlockEntity( cBlockEntity* a_BlockEntity ) { cCSLock Lock(m_CSBlockLists); + MarkDirty(); m_BlockEntities.remove( a_BlockEntity ); } @@ -924,7 +1054,7 @@ void cChunk::AddClient( cClientHandle* a_Client ) cCSLock Lock(m_CSEntities); for (cEntityList::iterator itr = m_Entities.begin(); itr != m_Entities.end(); ++itr ) { - LOG("Entity at [%i %i %i] spawning for player \"%s\"", m_PosX, m_PosY, m_PosZ, a_Client->GetUsername().c_str() ); + LOG("Entity #%d (%s) at [%i %i %i] spawning for player \"%s\"", (*itr)->GetUniqueID(), (*itr)->GetClass(), m_PosX, m_PosY, m_PosZ, a_Client->GetUsername().c_str() ); (*itr)->SpawnOn( a_Client ); } } @@ -986,6 +1116,10 @@ bool cChunk::HasAnyClient(void) void cChunk::AddEntity( cEntity * a_Entity ) { cCSLock Lock(m_CSEntities); + if (a_Entity->GetEntityType() != cEntity::E_PLAYER) + { + MarkDirty(); + } m_Entities.push_back( a_Entity ); } @@ -1002,6 +1136,10 @@ void cChunk::RemoveEntity(cEntity * a_Entity) m_Entities.remove(a_Entity); SizeAfter = m_Entities.size(); } + if ((a_Entity->GetEntityType() != cEntity::E_PLAYER) && (SizeBefore != SizeAfter)) + { + MarkDirty(); + } } @@ -1115,6 +1253,7 @@ bool cChunk::LoadFromDisk() { LOGINFO("Successfully deleted old format file \"%s\"", SourceFile.c_str()); } + m_IsDirty = false; return true; } diff --git a/source/cChunk.h b/source/cChunk.h index 0cbc11117..ebaa21e34 100644 --- a/source/cChunk.h +++ b/source/cChunk.h @@ -38,10 +38,25 @@ class cServer; class MTRand; class cPlayer; -typedef std::list cFurnaceEntityList; typedef std::list cClientHandleList; typedef std::list cBlockEntityList; +/** Interface class used for getting data out of a chunk using the GetAllData() function. +Implementation must use the pointers immediately and NOT store any of them for later use +*/ +class cChunkDataCallback +{ +public: + /// Called once to export blockdata + virtual void BlockData(const char * a_Data) = 0; + + /// Called for each entity in the chunk + virtual void Entity(cEntity * a_Entity) = 0; + + /// Called for each blockentity in the chunk + virtual void BlockEntity(cBlockEntity * a_Entity) = 0; +} ; + @@ -54,14 +69,35 @@ public: bool IsValid(void) const {return m_IsValid; } // Returns true if the chunk is valid (loaded / generated) void SetValid(bool a_SendToClients = true); // Also wakes up all clients attached to this chunk to let them finish logging in + bool IsDirty(void) const {return m_IsDirty; } // Returns true if the chunk has changed since it was last saved bool CanUnload(void); + + /* + To save a chunk, the WSSchema must: + 1. Mark the chunk as being saved (MarkSaving() ) + 2. Get the chunk's data using GetAllData() + 3. Mark the chunk as saved (MarkSaved() ) + If anywhere inside this sequence another thread mmodifies the chunk, the chunk will not get marked as saved in MarkSaved() + */ + void MarkSaving(void); // Marks the chunk as being saved. + void MarkSaved(void); // Marks the chunk as saved, if it didn't change from the last call to MarkSaving() + void MarkLoaded(void); // Marks the chunk as freshly loaded. Fails if the chunk is already valid + + /// Gets all chunk data, calls the a_Callback's methods for each data type + void GetAllData(cChunkDataCallback * a_Callback); + + /// Sets all chunk data + void SetAllData(const char * a_BlockData, cEntityList & a_Entities, cBlockEntityList & a_BlockEntities); + + /// Returns true if there is a block entity at the coords specified + bool HasBlockEntityAt(int a_BlockX, int a_BlockY, int a_BlockZ); void Tick(float a_Dt, MTRand & a_TickRandom); int GetPosX() { return m_PosX; } int GetPosY() { return m_PosY; } int GetPosZ() { return m_PosZ; } - cWorld* GetWorld() { return m_World; } + cWorld * GetWorld() { return m_World; } void Send( cClientHandle* a_Client ); void AsyncUnload( cClientHandle* a_Client ); @@ -131,6 +167,12 @@ public: } return 0; } + + inline void MarkDirty(void) + { + m_IsDirty = true; + m_IsSaving = false; + } static const int c_NumBlocks = 16*128*16; static const int c_BlockDataSize = c_NumBlocks * 2 + (c_NumBlocks/2); // 2.5 * numblocks @@ -138,15 +180,19 @@ public: private: bool m_IsValid; // True if the chunk is loaded / generated + bool m_IsDirty; // True if the chunk has changed since it was last saved + bool m_IsSaving; // True if the chunk is being saved cCriticalSection m_CSBlockLists; std::map< unsigned int, int > m_ToTickBlocks; std::vector< unsigned int > m_PendingSendBlocks; + // TODO: This CS will soon not be needed, because all chunk access is protected by its parent ChunkMap's csLayers cCriticalSection m_CSClients; cClientHandleList m_LoadedByClient; cClientHandleList m_UnloadQuery; + // TODO: This CS will soon not be needed, because all chunk access is protected by its parent ChunkMap's csLayers cCriticalSection m_CSEntities; cEntityList m_Entities; cBlockEntityList m_BlockEntities; @@ -177,7 +223,7 @@ private: void SpreadLightOfBlockY(char* a_LightBuffer, int a_X, int a_Y, int a_Z); void SpreadLightOfBlockZ(char* a_LightBuffer, int a_X, int a_Y, int a_Z); - void CreateBlockEntities(); + void CreateBlockEntities(void); }; typedef std::tr1::shared_ptr cChunkPtr; diff --git a/source/cChunk.inl.h b/source/cChunk.inl.h index c47061784..36a116e75 100644 --- a/source/cChunk.inl.h +++ b/source/cChunk.inl.h @@ -1,3 +1,4 @@ + #ifndef __C_CHUNK_INL_H__ #define __C_CHUNK_INL_H__ @@ -5,6 +6,10 @@ # define MAX(a,b) (((a)>(b))?(a):(b)) #endif + + + + __C_CHUNK_INLINE__ char cChunk::GetLight(char* a_Buffer, int a_BlockIdx) { @@ -23,6 +28,10 @@ char cChunk::GetLight(char* a_Buffer, int a_BlockIdx) return 0; } + + + + __C_CHUNK_INLINE__ char cChunk::GetLight(char* a_Buffer, int x, int y, int z) { @@ -41,6 +50,10 @@ char cChunk::GetLight(char* a_Buffer, int x, int y, int z) return 0; } + + + + __C_CHUNK_INLINE__ void cChunk::SetLight(char* a_Buffer, int a_BlockIdx, char a_Light) { @@ -57,9 +70,14 @@ void cChunk::SetLight(char* a_Buffer, int a_BlockIdx, char a_Light) a_Buffer[cindex] &= 0x0f; // Set second half to 0 a_Buffer[cindex] |= (a_Light << 4) & 0xf0; } + MarkDirty(); } } + + + + __C_CHUNK_INLINE__ void cChunk::SetLight(char* a_Buffer, int x, int y, int z, char light) { @@ -76,9 +94,14 @@ void cChunk::SetLight(char* a_Buffer, int x, int y, int z, char light) a_Buffer[cindex] &= 0x0f; // Set second half to 0 a_Buffer[cindex] |= (light << 4) & 0xf0; } + MarkDirty(); } } + + + + __C_CHUNK_INLINE__ void cChunk::SpreadLightOfBlock(char* a_LightBuffer, int a_X, int a_Y, int a_Z, char a_Falloff) { @@ -91,6 +114,10 @@ void cChunk::SpreadLightOfBlock(char* a_LightBuffer, int a_X, int a_Y, int a_Z, SetLight( a_LightBuffer, a_X, a_Y, a_Z+1, MAX(GetLight( a_LightBuffer, a_X, a_Y, a_Z+1 ), MAX(0,CurrentLight-a_Falloff) ) ); } + + + + __C_CHUNK_INLINE__ void cChunk::SpreadLightOfBlockX(char* a_LightBuffer, int a_X, int a_Y, int a_Z) { @@ -99,6 +126,10 @@ void cChunk::SpreadLightOfBlockX(char* a_LightBuffer, int a_X, int a_Y, int a_Z) SetLight( a_LightBuffer, a_X+1, a_Y, a_Z, MAX(GetLight( a_LightBuffer, a_X+1, a_Y, a_Z ), CurrentLight-1) ); } + + + + __C_CHUNK_INLINE__ void cChunk::SpreadLightOfBlockY(char* a_LightBuffer, int a_X, int a_Y, int a_Z) { @@ -107,6 +138,10 @@ void cChunk::SpreadLightOfBlockY(char* a_LightBuffer, int a_X, int a_Y, int a_Z) SetLight( a_LightBuffer, a_X, a_Y+1, a_Z, MAX(GetLight( a_LightBuffer, a_X, a_Y+1, a_Z ), CurrentLight-1) ); } + + + + __C_CHUNK_INLINE__ void cChunk::SpreadLightOfBlockZ(char* a_LightBuffer, int a_X, int a_Y, int a_Z) { @@ -115,4 +150,8 @@ void cChunk::SpreadLightOfBlockZ(char* a_LightBuffer, int a_X, int a_Y, int a_Z) SetLight( a_LightBuffer, a_X, a_Y, a_Z+1, MAX(GetLight( a_LightBuffer, a_X, a_Y, a_Z+1 ), CurrentLight-1) ); } + + + + #endif \ No newline at end of file diff --git a/source/cChunkMap.cpp b/source/cChunkMap.cpp index 6f422b3bd..7b249dcae 100644 --- a/source/cChunkMap.cpp +++ b/source/cChunkMap.cpp @@ -106,7 +106,7 @@ cChunkPtr cChunkMap::GetChunk( int a_ChunkX, int a_ChunkY, int a_ChunkZ ) cChunkPtr Chunk = Layer->GetChunk(a_ChunkX, a_ChunkZ); if (!(Chunk->IsValid())) { - m_World->GetStorage().QueueLoadChunk(Chunk); + m_World->GetStorage().QueueLoadChunk(a_ChunkX, a_ChunkZ); } return Chunk; } @@ -174,6 +174,108 @@ void cChunkMap::UseBlockEntity(cPlayer * a_Player, int a_X, int a_Y, int a_Z) +void cChunkMap::MarkChunkDirty (int a_ChunkX, int a_ChunkY, int a_ChunkZ) +{ + cCSLock Lock(m_CSLayers); + cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkY, a_ChunkZ); + if ((Chunk == NULL) || !Chunk->IsValid()) + { + return; + } + Chunk->MarkDirty(); +} + + + + + +void cChunkMap::MarkChunkSaving(int a_ChunkX, int a_ChunkY, int a_ChunkZ) +{ + cCSLock Lock(m_CSLayers); + cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkY, a_ChunkZ); + if ((Chunk == NULL) || !Chunk->IsValid()) + { + return; + } + Chunk->MarkSaving(); +} + + + + + +void cChunkMap::MarkChunkSaved (int a_ChunkX, int a_ChunkY, int a_ChunkZ) +{ + cCSLock Lock(m_CSLayers); + cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkY, a_ChunkZ); + if ((Chunk == NULL) || !Chunk->IsValid()) + { + return; + } + Chunk->MarkSaved(); +} + + + + + +void cChunkMap::ChunkDataLoaded(int a_ChunkX, int a_ChunkY, int a_ChunkZ, const char * a_BlockData, cEntityList & a_Entities, cBlockEntityList & a_BlockEntities) +{ + cCSLock Lock(m_CSLayers); + cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkY, a_ChunkZ); + if (Chunk == NULL) + { + return; + } + Chunk->SetAllData(a_BlockData, a_Entities, a_BlockEntities); + Chunk->MarkLoaded(); +} + + + + + +void cChunkMap::SetChunkData(int a_ChunkX, int a_ChunkY, int a_ChunkZ, const char * a_BlockData, cEntityList & a_Entities, cBlockEntityList & a_BlockEntities) +{ + cCSLock Lock(m_CSLayers); + cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkY, a_ChunkZ); + if (Chunk == NULL) + { + return; + } + Chunk->SetAllData(a_BlockData, a_Entities, a_BlockEntities); +} + + + + + +void cChunkMap::GetChunkData(int a_ChunkX, int a_ChunkY, int a_ChunkZ, cChunkDataCallback * a_Callback) +{ + cCSLock Lock(m_CSLayers); + cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkY, a_ChunkZ); + if ((Chunk == NULL) || !Chunk->IsValid()) + { + return; + } + Chunk->GetAllData(a_Callback); +} + + + + + +bool cChunkMap::IsChunkValid(int a_ChunkX, int a_ChunkY, int a_ChunkZ) +{ + cCSLock Lock(m_CSLayers); + cChunkPtr Chunk = GetChunkNoGen(a_ChunkX, a_ChunkY, a_ChunkZ); + return (Chunk != NULL) && Chunk->IsValid(); +} + + + + + void cChunkMap::Tick( float a_Dt, MTRand & a_TickRandom ) { cCSLock Lock(m_CSLayers); @@ -274,9 +376,9 @@ void cChunkMap::cChunkLayer::Save(void) cWorld * World = m_Parent->GetWorld(); for (int i = 0; i < ARRAYCOUNT(m_Chunks); ++i) { - if ((m_Chunks[i] != NULL) && m_Chunks[i]->IsValid()) + if ((m_Chunks[i] != NULL) && m_Chunks[i]->IsValid() && m_Chunks[i]->IsDirty()) { - World->GetStorage().QueueSaveChunk(m_Chunks[i]); + World->GetStorage().QueueSaveChunk(m_Chunks[i]->GetPosX(), m_Chunks[i]->GetPosZ()); } } // for i - m_Chunks[] } @@ -292,9 +394,6 @@ void cChunkMap::cChunkLayer::UnloadUnusedChunks(void) { if ((m_Chunks[i] != NULL) && (m_Chunks[i]->CanUnload())) { - // TODO: Save the chunk if it was changed - World->GetStorage().QueueSaveChunk(m_Chunks[i]); // _FT: FIXME: Right now it saves chunks even though it might not have changed. - // Also I'm not sure what's going on when I queue this chunks and the next line says reset the pointer.. =/ m_Chunks[i].reset(); } } // for i - m_Chunks[] diff --git a/source/cChunkMap.h b/source/cChunkMap.h index b5d6abd60..e53153c24 100644 --- a/source/cChunkMap.h +++ b/source/cChunkMap.h @@ -27,7 +27,7 @@ public: cChunkMap(cWorld* a_World ); ~cChunkMap(); - // TODO: Get rid of these in favor of the direct action methods: + // TODO: Get rid of these (put into Private section) in favor of the direct action methods: cChunkPtr GetChunk ( int a_ChunkX, int a_ChunkY, int a_ChunkZ ); // Also queues the chunk for loading / generating if not valid cChunkPtr GetChunkNoGen( int a_ChunkX, int a_ChunkY, int a_ChunkZ ); // Also queues the chunk for loading if not valid; doesn't generate @@ -36,6 +36,14 @@ public: void BroadcastToChunkOfBlock(int a_X, int a_Y, int a_Z, cPacket * a_Packet, cClientHandle * a_Exclude = NULL); void UseBlockEntity(cPlayer * a_Player, int a_X, int a_Y, int a_Z); // a_Player rclked block entity at the coords specified, handle it + void MarkChunkDirty (int a_ChunkX, int a_ChunkY, int a_ChunkZ); + void MarkChunkSaving(int a_ChunkX, int a_ChunkY, int a_ChunkZ); + void MarkChunkSaved (int a_ChunkX, int a_ChunkY, int a_ChunkZ); + void ChunkDataLoaded(int a_ChunkX, int a_ChunkY, int a_ChunkZ, const char * a_BlockData, cEntityList & a_Entities, cBlockEntityList & a_BlockEntities); + void SetChunkData (int a_ChunkX, int a_ChunkY, int a_ChunkZ, const char * a_BlockData, cEntityList & a_Entities, cBlockEntityList & a_BlockEntities); + void GetChunkData (int a_ChunkX, int a_ChunkY, int a_ChunkZ, cChunkDataCallback * a_Callback); + bool IsChunkValid (int a_ChunkX, int a_ChunkY, int a_ChunkZ); + void Tick( float a_Dt, MTRand & a_TickRand ); void UnloadUnusedChunks(); @@ -51,7 +59,7 @@ public: BlockToChunk(a_X, a_Y, a_Z, a_ChunkX, a_ChunkZ); a_X = a_X - a_ChunkX * 16; - a_Z = a_Z - a_ChunkZ*16; + a_Z = a_Z - a_ChunkZ * 16; } /// Converts absolute block coords to chunk coords: diff --git a/source/cCriticalSection.h b/source/cCriticalSection.h index 8faa04765..414c970f7 100644 --- a/source/cCriticalSection.h +++ b/source/cCriticalSection.h @@ -45,6 +45,9 @@ public: // Temporarily unlock or re-lock: void Lock(void); void Unlock(void); + +private: + DISALLOW_COPY_AND_ASSIGN(cCSLock); } ; @@ -58,6 +61,9 @@ class cCSUnlock public: cCSUnlock(cCSLock & a_Lock); ~cCSUnlock(); + +private: + DISALLOW_COPY_AND_ASSIGN(cCSUnlock); } ; diff --git a/source/cFurnaceEntity.h b/source/cFurnaceEntity.h index 647339b75..21cdb1e38 100644 --- a/source/cFurnaceEntity.h +++ b/source/cFurnaceEntity.h @@ -34,7 +34,7 @@ public: bool LoadFromFile(cFile & a_File); // deprecated format bool LoadFromJson(const Json::Value& a_Value ); - void SaveToJson (Json::Value& a_Value ); + virtual void SaveToJson(Json::Value& a_Value ) override; bool Tick( float a_Dt ); diff --git a/source/cPlayer.cpp b/source/cPlayer.cpp index 36a27ca0a..5badb671f 100644 --- a/source/cPlayer.cpp +++ b/source/cPlayer.cpp @@ -922,7 +922,7 @@ bool cPlayer::SaveToDisk() LOGERROR("ERROR WRITING PLAYER \"%s\" TO FILE \"%s\" - cannot open file", m_pState->PlayerName.c_str(), SourceFile.c_str()); return false; } - if (f.Write(JsonData.c_str(), JsonData.size()) != JsonData.size()) + if (f.Write(JsonData.c_str(), JsonData.size()) != (int)JsonData.size()) { LOGERROR("ERROR WRITING PLAYER JSON TO FILE \"%s\"", SourceFile.c_str()); return false; diff --git a/source/cSignEntity.h b/source/cSignEntity.h index 33578e1be..bc45e5ff4 100644 --- a/source/cSignEntity.h +++ b/source/cSignEntity.h @@ -23,10 +23,10 @@ public: cSignEntity(ENUM_BLOCK_ID a_BlockType, int a_X, int a_Y, int a_Z, cWorld * a_World); virtual ~cSignEntity(); - bool LoadFromFile(cFile & a_File); // deprecated format + OBSOLETE bool LoadFromFile(cFile & a_File); // deprecated format bool LoadFromJson( const Json::Value& a_Value ); - void SaveToJson( Json::Value& a_Value ); + virtual void SaveToJson(Json::Value& a_Value ) override; void SetLines( const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4 ); void SetLine( int a_Index, const AString & a_Line ); diff --git a/source/cWindow.cpp b/source/cWindow.cpp index 4710ad8a4..1914daa40 100644 --- a/source/cWindow.cpp +++ b/source/cWindow.cpp @@ -30,6 +30,10 @@ cWindow::cWindow( cWindowOwner* a_Owner, bool a_bInventoryVisible ) if( !m_bInventoryVisible ) m_DraggingItem = new cItem(); } + + + + cWindow::~cWindow() { if( !m_bInventoryVisible && m_DraggingItem ) @@ -39,6 +43,10 @@ cWindow::~cWindow() } } + + + + cItem* cWindow::GetSlot( int a_Slot ) { if(a_Slot > -1 && a_Slot < m_NumSlots) @@ -48,6 +56,10 @@ cItem* cWindow::GetSlot( int a_Slot ) return 0; } + + + + cItem* cWindow::GetDraggingItem( cPlayer * a_Player /* = 0 */ ) { if( m_bInventoryVisible && a_Player ) @@ -61,9 +73,12 @@ cItem* cWindow::GetDraggingItem( cPlayer * a_Player /* = 0 */ ) return m_DraggingItem; } + + + + void cWindow::Clicked( cPacket_WindowClick* a_ClickPacket, cPlayer & a_Player ) { - //LOG("cWindow click"); if( a_ClickPacket->m_WindowID != m_WindowID ) { LOG("WRONG WINDOW ID!"); @@ -183,6 +198,10 @@ void cWindow::Clicked( cPacket_WindowClick* a_ClickPacket, cPlayer & a_Player ) if( m_DraggingItem ) LOG("Dragging: %i", m_DraggingItem->m_ItemCount ); } + + + + void cWindow::Open( cPlayer & a_Player ) { // If player is already in OpenedBy remove player first @@ -198,6 +217,10 @@ void cWindow::Open( cPlayer & a_Player ) a_Player.GetClientHandle()->Send( WindowOpen ); } + + + + void cWindow::Close( cPlayer & a_Player ) { //Checks wheather the player is still holding an item @@ -205,11 +228,9 @@ void cWindow::Close( cPlayer & a_Player ) { LOG("Player holds item! Dropping it..."); a_Player.TossItem( true, m_DraggingItem->m_ItemCount ); - } - - cPacket_WindowClose WindowClose; + cPacket_WindowClose WindowClose; WindowClose.m_Close = (char)m_WindowID; cClientHandle* ClientHandle = a_Player.GetClientHandle(); if( ClientHandle ) ClientHandle->Send( WindowClose ); @@ -219,10 +240,12 @@ void cWindow::Close( cPlayer & a_Player ) { Destroy(); } - - } + + + + void cWindow::OwnerDestroyed() { m_Owner = 0; @@ -233,6 +256,10 @@ void cWindow::OwnerDestroyed() (*m_OpenedBy.begin() )->CloseWindow((char)GetWindowType()); } + + + + void cWindow::Destroy() { LOG("DESTROY WINDOW"); @@ -244,8 +271,16 @@ void cWindow::Destroy() delete this; } + + + + void cWindow::SendWholeWindow( cClientHandle* a_Client ) { cPacket_WholeInventory Inventory( this ); a_Client->Send( Inventory ); } + + + + diff --git a/source/cWorld.cpp b/source/cWorld.cpp index b97fcaea2..8e95b5bd5 100644 --- a/source/cWorld.cpp +++ b/source/cWorld.cpp @@ -398,7 +398,6 @@ void cWorld::InitializeSpawn() void cWorld::Tick(float a_Dt) { - int randWeather = 0; m_Time += a_Dt / 1000.f; CurrentTick++; @@ -938,6 +937,78 @@ void cWorld::Broadcast( const cPacket & a_Packet, cClientHandle* a_Exclude) +void cWorld::BroadcastToChunkOfBlock(int a_X, int a_Y, int a_Z, cPacket * a_Packet, cClientHandle * a_Exclude) +{ + m_ChunkMap->BroadcastToChunkOfBlock(a_X, a_Y, a_Z, a_Packet, a_Exclude); +} + + + + + +void cWorld::MarkChunkDirty (int a_ChunkX, int a_ChunkY, int a_ChunkZ) +{ + m_ChunkMap->MarkChunkDirty (a_ChunkX, a_ChunkY, a_ChunkZ); +} + + + + + +void cWorld::MarkChunkSaving(int a_ChunkX, int a_ChunkY, int a_ChunkZ) +{ + m_ChunkMap->MarkChunkSaving(a_ChunkX, a_ChunkY, a_ChunkZ); +} + + + + + +void cWorld::MarkChunkSaved (int a_ChunkX, int a_ChunkY, int a_ChunkZ) +{ + m_ChunkMap->MarkChunkSaved (a_ChunkX, a_ChunkY, a_ChunkZ); +} + + + + + +void cWorld::ChunkDataLoaded(int a_ChunkX, int a_ChunkY, int a_ChunkZ, const char * a_BlockData, cEntityList & a_Entities, cBlockEntityList & a_BlockEntities) +{ + m_ChunkMap->ChunkDataLoaded(a_ChunkX, a_ChunkY, a_ChunkZ, a_BlockData, a_Entities, a_BlockEntities); +} + + + + + +void cWorld::SetChunkData(int a_ChunkX, int a_ChunkY, int a_ChunkZ, const char * a_BlockData, cEntityList & a_Entities, cBlockEntityList & a_BlockEntities) +{ + m_ChunkMap->SetChunkData(a_ChunkX, a_ChunkY, a_ChunkZ, a_BlockData, a_Entities, a_BlockEntities); +} + + + + + +void cWorld::GetChunkData(int a_ChunkX, int a_ChunkY, int a_ChunkZ, cChunkDataCallback * a_Callback) +{ + m_ChunkMap->GetChunkData(a_ChunkX, a_ChunkY, a_ChunkZ, a_Callback); +} + + + + + +bool cWorld::IsChunkValid(int a_ChunkX, int a_ChunkY, int a_ChunkZ) const +{ + return m_ChunkMap->IsChunkValid(a_ChunkX, a_ChunkY, a_ChunkZ); +} + + + + + void cWorld::SetMaxPlayers(int iMax) { m_MaxPlayers = MAX_PLAYERS; diff --git a/source/cWorld.h b/source/cWorld.h index 453b30a2a..c47fa70e8 100644 --- a/source/cWorld.h +++ b/source/cWorld.h @@ -33,7 +33,7 @@ class cBlockEntity; class cWorldGenerator; // The generator that actually generates the chunks for a single world class cChunkGenerator; // The thread responsible for generating chunks typedef std::list< cPlayer * > cPlayerList; -typedef cListCallback cPlayerListCallback; +typedef cItemCallback cPlayerListCallback; @@ -67,7 +67,15 @@ public: void Broadcast( const cPacket & a_Packet, cClientHandle* a_Exclude = 0 ); - void BroadcastToChunkOfBlock(int a_X, int a_Y, int a_Z, cPacket * a_Packet, cClientHandle * a_Exclude = NULL) {return m_ChunkMap->BroadcastToChunkOfBlock(a_X, a_Y, a_Z, a_Packet, a_Exclude); } + void BroadcastToChunkOfBlock(int a_X, int a_Y, int a_Z, cPacket * a_Packet, cClientHandle * a_Exclude = NULL); + + void MarkChunkDirty (int a_ChunkX, int a_ChunkY, int a_ChunkZ); + void MarkChunkSaving(int a_ChunkX, int a_ChunkY, int a_ChunkZ); + void MarkChunkSaved (int a_ChunkX, int a_ChunkY, int a_ChunkZ); + void ChunkDataLoaded(int a_ChunkX, int a_ChunkY, int a_ChunkZ, const char * a_BlockData, cEntityList & a_Entities, cBlockEntityList & a_BlockEntities); + void SetChunkData (int a_ChunkX, int a_ChunkY, int a_ChunkZ, const char * a_BlockData, cEntityList & a_Entities, cBlockEntityList & a_BlockEntities); + void GetChunkData (int a_ChunkX, int a_ChunkY, int a_ChunkZ, cChunkDataCallback * a_Callback); + bool IsChunkValid (int a_ChunkX, int a_ChunkY, int a_ChunkZ) const; // MOTD const AString & GetDescription(void) const {return m_Description; } @@ -117,7 +125,7 @@ public: inline cWaterSimulator *GetWaterSimulator() { return m_WaterSimulator; } inline cLavaSimulator *GetLavaSimulator() { return m_LavaSimulator; } - // TODO: This interface is dangerous! + // TODO: This interface is dangerous! Export as a set of specific action functions for Lua: GetChestItem, GetFurnaceItem, SetFurnaceItem, SetSignLines etc. cBlockEntity * GetBlockEntity( int a_X, int a_Y, int a_Z ); //tolua_export /// a_Player is using block entity at [x, y, z], handle that: diff --git a/source/main.cpp b/source/main.cpp index 835d7ffc7..416c49033 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -9,9 +9,10 @@ #include "SquirrelBindings.h" #if USE_SQUIRREL + #pragma warning(push) #pragma warning(disable:4100;disable:4127;disable:4510;disable:4610;disable:4244;disable:4512) // Getting A LOT of these warnings from SqPlus #include - #pragma warning(default:4100;default:4127;default:4510;default:4610;default:4244;default:4512) + #pragma warning(pop) #endif @@ -26,7 +27,10 @@ #if defined(_MSC_VER) && defined(_DEBUG) && defined(ENABLE_LEAK_FINDER) #define XML_LEAK_FINDER + #pragma warning(push) + #pragma warning(disable:4100) #include "LeakFinder.h" + #pragma warning(pop) #endif