1
0

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
This commit is contained in:
madmaxoft@gmail.com 2012-05-08 11:31:54 +00:00
parent 05a1f89286
commit f2681777fb
4 changed files with 363 additions and 72 deletions

View File

@ -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);
}

View File

@ -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++;
}
}
} ;

View File

@ -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<cNBTCompound> 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<cNBTTree> 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<cNBTCompound> 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;
}

View File

@ -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);