From f2681777fb21c134cd96ae1e06bf5266a59bfd5d Mon Sep 17 00:00:00 2001 From: "madmaxoft@gmail.com" Date: Tue, 8 May 2012 11:31:54 +0000 Subject: Fast NBT writer (Saving a chunk is now about twice as fast) git-svn-id: http://mc-server.googlecode.com/svn/trunk@484 0a769ca7-a7f5-676a-18bf-c427514a06d6 --- source/FastNBT.cpp | 212 +++++++++++++++++++++++++++++++++++++++++++++++++++- source/FastNBT.h | 58 +++++++++++--- source/WSSAnvil.cpp | 163 +++++++++++++++++++++++++--------------- source/WSSAnvil.h | 4 +- 4 files changed, 364 insertions(+), 73 deletions(-) diff --git a/source/FastNBT.cpp b/source/FastNBT.cpp index fa3edec52..9854fe3bc 100644 --- a/source/FastNBT.cpp +++ b/source/FastNBT.cpp @@ -17,7 +17,7 @@ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cFastNBTParser: +// cParsedNBT: #define NEEDBYTES(N) \ if (m_Length - m_Pos < N) \ @@ -318,3 +318,213 @@ int cParsedNBT::FindTagByPath(int a_Tag, const AString & a_Path) const +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cFastNBTWriter: + +cFastNBTWriter::cFastNBTWriter(void) : + m_CurrentStack(0) +{ + m_Stack[0].m_Type = TAG_Compound; + m_Result.reserve(100 * 1024); + m_Result.push_back(TAG_Compound); + WriteString("", 0); +} + + + + + +void cFastNBTWriter::BeginCompound(const AString & a_Name) +{ + if (m_CurrentStack >= MAX_STACK) + { + ASSERT(!"Stack overflow"); + return; + } + + TagCommon(a_Name, TAG_Compound); + + ++m_CurrentStack; + m_Stack[m_CurrentStack].m_Type = TAG_Compound; +} + + + + + +void cFastNBTWriter::EndCompound(void) +{ + ASSERT(m_CurrentStack > 0); + ASSERT(IsStackTopCompound()); + + m_Result.push_back(TAG_End); + --m_CurrentStack; +} + + + + + +void cFastNBTWriter::BeginList(const AString & a_Name, eTagType a_ChildrenType) +{ + if (m_CurrentStack >= MAX_STACK) + { + ASSERT(!"Stack overflow"); + return; + } + + TagCommon(a_Name, TAG_List); + + m_Result.push_back((char)a_ChildrenType); + m_Result.append(4, (char)0); + + ++m_CurrentStack; + m_Stack[m_CurrentStack].m_Type = TAG_List; + m_Stack[m_CurrentStack].m_Pos = m_Result.size() - 4; + m_Stack[m_CurrentStack].m_Count = 0; +} + + + + + +void cFastNBTWriter::EndList(void) +{ + ASSERT(m_CurrentStack > 0); + ASSERT(m_Stack[m_CurrentStack].m_Type == TAG_List); + + // Update the list count: + *((int *)(m_Result.c_str() + m_Stack[m_CurrentStack].m_Pos)) = htonl(m_Stack[m_CurrentStack].m_Count); + + --m_CurrentStack; +} + + + + + +void cFastNBTWriter::AddByte(const AString & a_Name, unsigned char a_Value) +{ + TagCommon(a_Name, TAG_Byte); + m_Result.push_back(a_Value); +} + + + + + +void cFastNBTWriter::AddShort(const AString & a_Name, Int16 a_Value) +{ + TagCommon(a_Name, TAG_Short); + Int16 Value = htons(a_Value); + m_Result.append((const char *)&Value, 2); +} + + + + + +void cFastNBTWriter::AddInt(const AString & a_Name, Int32 a_Value) +{ + TagCommon(a_Name, TAG_Int); + Int32 Value = htonl(a_Value); + m_Result.append((const char *)&Value, 4); +} + + + + + +void cFastNBTWriter::AddLong(const AString & a_Name, Int64 a_Value) +{ + TagCommon(a_Name, TAG_Long); + Int64 Value = HostToNetwork8(&a_Value); + m_Result.append((const char *)&Value, 8); +} + + + + + +void cFastNBTWriter::AddFloat(const AString & a_Name, float a_Value) +{ + TagCommon(a_Name, TAG_Float); + Int32 Value = HostToNetwork4(&a_Value); + m_Result.append((const char *)&Value, 4); +} + + + + + +void cFastNBTWriter::AddDouble(const AString & a_Name, double a_Value) +{ + TagCommon(a_Name, TAG_Double); + Int64 Value = HostToNetwork8(&a_Value); + m_Result.append((const char *)&Value, 8); +} + + + + + +void cFastNBTWriter::AddString(const AString & a_Name, const AString & a_Value) +{ + TagCommon(a_Name, TAG_String); + Int16 len = htons((short)(a_Value.size())); + m_Result.append((const char *)&len, 2); + m_Result.append(a_Value.c_str(), a_Value.size()); +} + + + + + +void cFastNBTWriter::AddByteArray(const AString & a_Name, const char * a_Value, size_t a_NumElements) +{ + TagCommon(a_Name, TAG_ByteArray); + Int32 len = htonl(a_NumElements); + m_Result.append((const char *)&len, 4); + m_Result.append(a_Value, a_NumElements); +} + + + + + +void cFastNBTWriter::AddIntArray(const AString & a_Name, const int * a_Value, size_t a_NumElements) +{ + TagCommon(a_Name, TAG_IntArray); + Int32 len = htonl(a_NumElements); + m_Result.append((const char *)&len, 2); + int * Elements = (int *)(m_Result.data() + m_Result.size()); + m_Result.append(a_NumElements * 4, (char)0); + for (size_t i = 0; i < a_NumElements; i++) + { + Elements[i] = htonl(a_Value[i]); + } +} + + + + +void cFastNBTWriter::Finish(void) +{ + ASSERT(m_CurrentStack == 0); + m_Result.push_back(TAG_End); +} + + + + + +void cFastNBTWriter::WriteString(const char * a_Data, short a_Length) +{ + Int16 Len = htons(a_Length); + m_Result.append((const char *)&Len, 2); + m_Result.append(a_Data, a_Length); +} + + + + diff --git a/source/FastNBT.h b/source/FastNBT.h index fd60fc671..a0fe7a863 100644 --- a/source/FastNBT.h +++ b/source/FastNBT.h @@ -214,19 +214,59 @@ public: void EndList(void); void AddByte (const AString & a_Name, unsigned char a_Value); - void AddShort (const AString & a_Name, unsigned char a_Value); - void AddInt (const AString & a_Name, unsigned char a_Value); - void AddLong (const AString & a_Name, unsigned char a_Value); - void AddFloat (const AString & a_Name, unsigned char a_Value); - void AddDouble (const AString & a_Name, unsigned char a_Value); - void AddString (const AString & a_Name, unsigned char a_Value); - void AddByteArray(const AString & a_Name, unsigned char a_Value); - void AddIntArray (const AString & a_Name, unsigned char a_Value); - + void AddShort (const AString & a_Name, Int16 a_Value); + void AddInt (const AString & a_Name, Int32 a_Value); + void AddLong (const AString & a_Name, Int64 a_Value); + void AddFloat (const AString & a_Name, float a_Value); + void AddDouble (const AString & a_Name, double a_Value); + void AddString (const AString & a_Name, const AString & a_Value); + void AddByteArray(const AString & a_Name, const char * a_Value, size_t a_NumElements); + void AddIntArray (const AString & a_Name, const int * a_Value, size_t a_NumElements); + + void AddByteArray(const AString & a_Name, const AString & a_Value) + { + AddByteArray(a_Name, a_Value.data(), a_Value.size()); + } + const AString & GetResult(void) const {return m_Result; } + void Finish(void); + protected: + + struct sParent + { + int m_Type; // TAG_Compound or TAG_List + int m_Pos; // for TAG_List, the position of the list count + int m_Count; // for TAG_List, the element count + } ; + + static const int MAX_STACK = 50; // Highliy doubtful that an NBT would be constructed this many levels deep + + // These two fields emulate a stack. A raw array is used due to speed issues - no reallocations are allowed. + sParent m_Stack[MAX_STACK]; + int m_CurrentStack; + AString m_Result; + + bool IsStackTopCompound(void) const { return (m_Stack[m_CurrentStack].m_Type == TAG_Compound); } + + void WriteString(const char * a_Data, short a_Length); + + inline void TagCommon(const AString & a_Name, eTagType a_Type) + { + if (IsStackTopCompound()) + { + // Compound: add the type and name: + m_Result.push_back((char)a_Type); + WriteString(a_Name.c_str(), a_Name.length()); + } + else + { + // List: add to the counter + m_Stack[m_CurrentStack].m_Count++; + } + } } ; diff --git a/source/WSSAnvil.cpp b/source/WSSAnvil.cpp index e5400cf4c..f8e3abac3 100644 --- a/source/WSSAnvil.cpp +++ b/source/WSSAnvil.cpp @@ -41,11 +41,23 @@ class cNBTChunkSerializer : public cChunkDataSeparateCollector { public: - cNBTChunkSerializer(cNBTList * a_Entities, cNBTList * a_TileEntities) : - m_Entities(a_Entities), - m_TileEntities(a_TileEntities) + cNBTChunkSerializer(cFastNBTWriter & a_Writer) : + m_Writer(a_Writer), + m_IsTagOpen(false), + m_HasHadEntity(false), + m_HasHadBlockEntity(false) { } + + + /// Close NBT tags that we've opened + void Finish(void) + { + if (m_IsTagOpen) + { + m_Writer.EndCompound(); + } + } protected: @@ -58,37 +70,38 @@ protected: // TODO: Biomes - // We need to save entities and blockentities into NBT - cNBTList * m_Entities; // Tag where entities will be saved - cNBTList * m_TileEntities; // Tag where block-entities will be saved + cFastNBTWriter & m_Writer; + + bool m_IsTagOpen; // True if a tag has been opened in the callbacks and not yet closed. + bool m_HasHadEntity; // True if any Entity has already been received and processed + bool m_HasHadBlockEntity; // True if any BlockEntity has already been received and processed - cNBTCompound * AddBasicTileEntity(cBlockEntity * a_Entity, const char * a_EntityTypeID) + void AddBasicTileEntity(cBlockEntity * a_Entity, const char * a_EntityTypeID) { - cNBTCompound * res = new cNBTCompound(m_TileEntities); - res->Add(new cNBTInt (res, "x", a_Entity->GetPosX())); - res->Add(new cNBTInt (res, "y", a_Entity->GetPosY())); - res->Add(new cNBTInt (res, "z", a_Entity->GetPosZ())); - res->Add(new cNBTString(res, "id", a_EntityTypeID)); - return res; + m_Writer.AddInt ("x", a_Entity->GetPosX()); + m_Writer.AddInt ("y", a_Entity->GetPosY()); + m_Writer.AddInt ("z", a_Entity->GetPosZ()); + m_Writer.AddString("id", a_EntityTypeID); } - void AddItem(cNBTList * a_Items, cItem * a_Item, int a_Slot) + void AddItem(cItem * a_Item, int a_Slot) { - cNBTCompound * Tag = new cNBTCompound(a_Items); - Tag->Add(new cNBTShort(Tag, "id", a_Item->m_ItemID)); - Tag->Add(new cNBTShort(Tag, "Damage", a_Item->m_ItemHealth)); - Tag->Add(new cNBTByte (Tag, "Count", a_Item->m_ItemCount)); - Tag->Add(new cNBTByte (Tag, "Slot", a_Slot)); + m_Writer.BeginCompound(""); + m_Writer.AddShort("id", a_Item->m_ItemID); + m_Writer.AddShort("Damage", a_Item->m_ItemHealth); + m_Writer.AddByte ("Count", a_Item->m_ItemCount); + m_Writer.AddByte ("Slot", a_Slot); + m_Writer.EndCompound(); } void AddChestEntity(cChestEntity * a_Entity) { - cNBTCompound * Base = AddBasicTileEntity(a_Entity, "chest"); - cNBTList * Items = new cNBTList(Base, "Items", cNBTTag::TAG_Compound); - Base->Add(Items); + m_Writer.BeginCompound(""); + AddBasicTileEntity(a_Entity, "chest"); + m_Writer.BeginList("Items", TAG_Compound); for (int i = 0; i < cChestEntity::c_ChestHeight * cChestEntity::c_ChestWidth; i++) { cItem * Item = a_Entity->GetSlot(i); @@ -96,8 +109,10 @@ protected: { continue; } - AddItem(Items, Item, i); + AddItem(Item, i); } + m_Writer.EndList(); + m_Writer.EndCompound(); } @@ -109,6 +124,20 @@ protected: virtual void BlockEntity(cBlockEntity * a_Entity) { + if (m_IsTagOpen) + { + if (!m_HasHadBlockEntity) + { + m_Writer.EndCompound(); + m_Writer.BeginCompound("TileEntities"); + } + } + else + { + m_Writer.BeginCompound("TileEntities"); + } + m_IsTagOpen = true; + // Add tile-entity into NBT: switch (a_Entity->GetBlockType()) { @@ -118,6 +147,7 @@ protected: ASSERT(!"Unhandled block entity saved into Anvil"); } } + m_HasHadBlockEntity = true; } } ; // class cNBTChunkSerializer @@ -136,19 +166,23 @@ cWSSAnvil::cWSSAnvil(cWorld * a_World) : Printf(fnam, "%s/level.dat", a_World->GetName().c_str()); if (!cFile::Exists(fnam)) { - std::auto_ptr Root(new cNBTCompound(NULL)); - cNBTCompound * Data = new cNBTCompound(Root.get()); - Root->Add(Data); - Data->Add(new cNBTInt(Data, "SpawnX", (int)(a_World->GetSpawnX()))); - Data->Add(new cNBTInt(Data, "SpawnY", (int)(a_World->GetSpawnY()))); - Data->Add(new cNBTInt(Data, "SpawnZ", (int)(a_World->GetSpawnZ()))); - AString Uncompressed; - cNBTSerializer::Serialize(Root.get(), Uncompressed); + cFastNBTWriter Writer; + Writer.BeginCompound(""); + Writer.AddInt("SpawnX", (int)(a_World->GetSpawnX())); + Writer.AddInt("SpawnY", (int)(a_World->GetSpawnY())); + Writer.AddInt("SpawnZ", (int)(a_World->GetSpawnZ())); + Writer.EndCompound(); + Writer.Finish(); + + #ifdef _DEBUG + cParsedNBT TestParse(Writer.GetResult().data(), Writer.GetResult().size()); + ASSERT(TestParse.IsValid()); + #endif // _DEBUG gzFile gz = gzopen(fnam.c_str(), "wb"); if (gz != NULL) { - gzwrite(gz, Uncompressed.data(), Uncompressed.size()); + gzwrite(gz, Writer.GetResult().data(), Writer.GetResult().size()); } gzclose(gz); } @@ -199,6 +233,14 @@ bool cWSSAnvil::SaveChunk(const cChunkCoords & a_Chunk) return false; } + // DEBUG: How fast is it? + DWORD BeginTick = GetTickCount(); + for (int i = 0; i < 1000; i++) + { + SaveChunkToData(a_Chunk, ChunkData); + } + LOGINFO("1000* SaveChunk took %d ticks.", GetTickCount() - BeginTick); + // Everything successful return true; } @@ -323,14 +365,19 @@ bool cWSSAnvil::LoadChunkFromData(const cChunkCoords & a_Chunk, const AString & bool cWSSAnvil::SaveChunkToData(const cChunkCoords & a_Chunk, AString & a_Data) { - std::auto_ptr Tree(SaveChunkToNBT(a_Chunk)); - if (Tree.get() == NULL) + cFastNBTWriter Writer; + if (!SaveChunkToNBT(a_Chunk, Writer)) { return false; } - AString Uncompressed; - cNBTSerializer::Serialize(Tree.get(), Uncompressed); - CompressString(Uncompressed.data(), Uncompressed.size(), a_Data); + Writer.Finish(); + + #ifdef _DEBUG + cParsedNBT TestParse(Writer.GetResult().data(), Writer.GetResult().size()); + ASSERT(TestParse.IsValid()); + #endif // _DEBUG + + CompressString(Writer.GetResult().data(), Writer.GetResult().size(), a_Data); return true; } @@ -474,44 +521,38 @@ void cWSSAnvil::CopyNBTData(const cParsedNBT & a_NBT, int a_Tag, const AString & -cNBTTag * cWSSAnvil::SaveChunkToNBT(const cChunkCoords & a_Chunk) +bool cWSSAnvil::SaveChunkToNBT(const cChunkCoords & a_Chunk, cFastNBTWriter & a_Writer) { - std::auto_ptr res(new cNBTCompound(NULL)); - cNBTCompound * Level = new cNBTCompound(res.get(), "Level"); - res->Add(Level); - cNBTList * Entities = new cNBTList(Level, "Entities", cNBTTag::TAG_Compound); - Level->Add(Entities); - cNBTList * TileEntities = new cNBTList(Level, "TileEntities", cNBTTag::TAG_Compound); - Level->Add(TileEntities); - cNBTChunkSerializer Serializer(Entities, TileEntities); + a_Writer.BeginCompound("Level"); + a_Writer.AddInt("xPos", a_Chunk.m_ChunkX); + a_Writer.AddInt("zPos", a_Chunk.m_ChunkZ); + cNBTChunkSerializer Serializer(a_Writer); if (!m_World->GetChunkData(a_Chunk.m_ChunkX, a_Chunk.m_ChunkY, a_Chunk.m_ChunkZ, Serializer)) { - return NULL; + return false; } - - Level->Add(new cNBTInt(Level, "xPos", a_Chunk.m_ChunkX)); - Level->Add(new cNBTInt(Level, "zPos", a_Chunk.m_ChunkZ)); + Serializer.Finish(); // Close NBT tags // TODO: Save biomes: // Level->Add(new cNBTByteArray(Level, "Biomes", AString(Serializer.m_Biomes, sizeof(Serializer.m_Biomes)); // Save blockdata: - cNBTList * Sections = new cNBTList(Level, "Sections", cNBTTag::TAG_Compound); - Level->Add(Sections); + a_Writer.BeginList("Sections", TAG_Compound); int SliceSizeBlock = cChunkDef::Width * cChunkDef::Width * 16; int SliceSizeNibble = SliceSizeBlock / 2; for (int Y = 0; Y < 16; Y++) { - cNBTCompound * Slice = new cNBTCompound(Sections); - Sections->Add(Slice); - Slice->Add(new cNBTByteArray(Slice, "Blocks", AString(Serializer.m_BlockTypes + Y * SliceSizeBlock, SliceSizeBlock))); - Slice->Add(new cNBTByteArray(Slice, "Data", AString(Serializer.m_BlockMetas + Y * SliceSizeNibble, SliceSizeNibble))); - Slice->Add(new cNBTByteArray(Slice, "SkyLight", AString(Serializer.m_BlockSkyLight + Y * SliceSizeNibble, SliceSizeNibble))); - Slice->Add(new cNBTByteArray(Slice, "BlockLight", AString(Serializer.m_BlockLight + Y * SliceSizeNibble, SliceSizeNibble))); - Slice->Add(new cNBTByte(Slice, "Y", Y)); + a_Writer.BeginCompound(""); + a_Writer.AddByteArray("Blocks", Serializer.m_BlockTypes + Y * SliceSizeBlock, SliceSizeBlock); + a_Writer.AddByteArray("Data", Serializer.m_BlockMetas + Y * SliceSizeNibble, SliceSizeNibble); + a_Writer.AddByteArray("SkyLight", Serializer.m_BlockSkyLight + Y * SliceSizeNibble, SliceSizeNibble); + a_Writer.AddByteArray("BlockLight", Serializer.m_BlockLight + Y * SliceSizeNibble, SliceSizeNibble); + a_Writer.AddByte("Y", Y); + a_Writer.EndCompound(); } - - return res.release(); + a_Writer.EndList(); // "Sections" + a_Writer.EndCompound(); // "Level" + return true; } diff --git a/source/WSSAnvil.h b/source/WSSAnvil.h index 58259f0e8..85b4fcaf1 100644 --- a/source/WSSAnvil.h +++ b/source/WSSAnvil.h @@ -102,8 +102,8 @@ protected: /// Loads the chunk from NBT data (no locking needed) bool LoadChunkFromNBT(const cChunkCoords & a_Chunk, const cParsedNBT & a_NBT); - /// Saves the chunk into NBT data; returns NULL for failure - cNBTTag * SaveChunkToNBT(const cChunkCoords & a_Chunk); + /// Saves the chunk into NBT data using a_Writer; returns true on success + bool SaveChunkToNBT(const cChunkCoords & a_Chunk, cFastNBTWriter & a_Writer); /// Loads the chunk's entities from NBT data (a_Tag is the Level\\Entities list tag; may be -1) void LoadEntitiesFromNBT(cEntityList & a_Entitites, const cParsedNBT & a_NBT, int a_Tag); -- cgit v1.2.3