1
0
Fork 0

Prepare ChunkData for BlockState storage (#5105)

* Rename ChunkData Creatable test

* Add missing Y-check in RedstoneWireHandler

* Remove ChunkDef.h dependency in Scoreboard

* Prepare ChunkData for BlockState storage

+ Split chunk block, meta, block & sky light storage
+ Load the height map from disk
- Reduce duplicated code in ChunkData
- Remove saving MCSBiomes, there aren't any
- Remove the allocation pool, ref #4315, #3864

* fixed build

* fixed test

* fixed the debug compile

Co-authored-by: 12xx12 <44411062+12xx12@users.noreply.github.com>
This commit is contained in:
Tiger Wang 2021-03-05 13:03:55 +00:00 committed by GitHub
parent 5fa45182e8
commit 868cd94ee9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1106 additions and 2203 deletions

View File

@ -1,187 +0,0 @@
#pragma once
template <class T>
class cAllocationPool
{
public:
class cStarvationCallbacks
{
public:
virtual ~cStarvationCallbacks() {}
/** Is called when the reserve buffer starts to be used */
virtual void OnStartUsingReserve() = 0;
/** Is called once the reserve buffer has returned to normal size */
virtual void OnEndUsingReserve() = 0;
/** Is called when the allocation pool is unable to allocate memory. Will be repeatedly
called if it does not free sufficient memory */
virtual void OnOutOfReserve() = 0;
};
virtual ~cAllocationPool() {}
/** Allocates a pointer to T */
virtual T * Allocate() = 0;
/** Frees the pointer passed in a_ptr, invalidating it */
virtual void Free(T * a_ptr) = 0;
/** Two pools compare equal if memory allocated by one can be freed by the other */
bool IsEqual(const cAllocationPool & a_Other) const noexcept
{
return ((this == &a_Other) || DoIsEqual(a_Other) || a_Other.DoIsEqual(*this));
}
friend bool operator == (const cAllocationPool & a_Lhs, const cAllocationPool & a_Rhs)
{
return a_Lhs.IsEqual(a_Rhs);
}
friend bool operator != (const cAllocationPool & a_Lhs, const cAllocationPool & a_Rhs)
{
return !a_Lhs.IsEqual(a_Rhs);
}
private:
virtual bool DoIsEqual(const cAllocationPool & a_Other) const noexcept = 0;
};
/** Allocates memory storing unused elements in a linked list. Keeps at least NumElementsInReserve
elements in the list unless malloc fails so that the program has a reserve to handle OOM. */
template <class T>
class cListAllocationPool:
public cAllocationPool<T>
{
public:
cListAllocationPool(std::unique_ptr<typename cAllocationPool<T>::cStarvationCallbacks> a_Callbacks, size_t a_MinElementsInReserve, size_t a_MaxElementsInReserve) :
m_MinElementsInReserve(a_MinElementsInReserve),
m_MaxElementsInReserve(a_MaxElementsInReserve),
m_Callbacks(std::move(a_Callbacks))
{
for (size_t i = 0; i < m_MinElementsInReserve; i++)
{
void * space = malloc(sizeof(T));
if (space == nullptr)
{
m_Callbacks->OnStartUsingReserve();
break;
}
m_FreeList.push_front(space);
}
}
virtual ~cListAllocationPool() override
{
while (!m_FreeList.empty())
{
free(m_FreeList.front());
m_FreeList.pop_front();
}
}
virtual T * Allocate() override
{
if (m_FreeList.size() <= m_MinElementsInReserve)
{
void * space = malloc(sizeof(T));
if (space != nullptr)
{
#if defined(_MSC_VER) && !defined(NDEBUG)
// The debugging-new that is set up using macros in Globals.h doesn't support the placement-new syntax used here.
// Temporarily disable the macro
#pragma push_macro("new")
#undef new
#endif
return new(space) T;
#if defined(_MSC_VER) && !defined(NDEBUG)
// Re-enable the debugging-new macro
#pragma pop_macro("new")
#endif
}
else if (m_FreeList.size() == m_MinElementsInReserve)
{
m_Callbacks->OnStartUsingReserve();
}
else if (m_FreeList.empty())
{
m_Callbacks->OnOutOfReserve();
// Try again until the memory is avalable
return Allocate();
}
}
// placement new, used to initalize the object
#if defined(_MSC_VER) && !defined(NDEBUG)
// The debugging-new that is set up using macros in Globals.h doesn't support the placement-new syntax used here.
// Temporarily disable the macro
#pragma push_macro("new")
#undef new
#endif
T * ret = new (m_FreeList.front()) T;
#if defined(_MSC_VER) && !defined(NDEBUG)
// Re-enable the debugging-new macro
#pragma pop_macro("new")
#endif
m_FreeList.pop_front();
return ret;
}
virtual void Free(T * a_ptr) override
{
if (a_ptr == nullptr)
{
return;
}
a_ptr->~T(); // placement destruct.
if (m_FreeList.size() >= m_MaxElementsInReserve)
{
free(a_ptr);
return;
}
m_FreeList.push_front(a_ptr);
if (m_FreeList.size() == m_MinElementsInReserve)
{
m_Callbacks->OnEndUsingReserve();
}
}
private:
/** The minimum number of elements to keep in the free list before malloc fails */
size_t m_MinElementsInReserve;
/** Maximum free list size before returning memory to the OS */
size_t m_MaxElementsInReserve;
std::list<void *> m_FreeList;
std::unique_ptr<typename cAllocationPool<T>::cStarvationCallbacks> m_Callbacks;
virtual bool DoIsEqual(const cAllocationPool<T> & a_Other) const noexcept override
{
return (dynamic_cast<const cListAllocationPool<T>*>(&a_Other) != nullptr);
}
};

View File

@ -2789,7 +2789,7 @@ bool cBlockArea::cChunkReader::Coords(int a_ChunkX, int a_ChunkZ)
void cBlockArea::cChunkReader::ChunkData(const cChunkData & a_BlockBuffer)
void cBlockArea::cChunkReader::ChunkData(const ChunkBlockData & a_BlockData, const ChunkLightData & a_LightData)
{
int SizeY = m_Area.m_Size.y;
int MinY = m_Origin.y;
@ -2848,7 +2848,7 @@ void cBlockArea::cChunkReader::ChunkData(const cChunkData & a_BlockBuffer)
{
int InChunkX = BaseX + x;
int AreaX = OffX + x;
m_Area.m_BlockTypes[m_Area.MakeIndex(AreaX, AreaY, AreaZ)] = a_BlockBuffer.GetBlock({ InChunkX, InChunkY, InChunkZ });
m_Area.m_BlockTypes[m_Area.MakeIndex(AreaX, AreaY, AreaZ)] = a_BlockData.GetBlock({ InChunkX, InChunkY, InChunkZ });
} // for x
} // for z
} // for y
@ -2869,7 +2869,7 @@ void cBlockArea::cChunkReader::ChunkData(const cChunkData & a_BlockBuffer)
{
int InChunkX = BaseX + x;
int AreaX = OffX + x;
m_Area.m_BlockMetas[m_Area.MakeIndex(AreaX, AreaY, AreaZ)] = a_BlockBuffer.GetMeta({ InChunkX, InChunkY, InChunkZ });
m_Area.m_BlockMetas[m_Area.MakeIndex(AreaX, AreaY, AreaZ)] = a_BlockData.GetMeta({ InChunkX, InChunkY, InChunkZ });
} // for x
} // for z
} // for y
@ -2890,7 +2890,7 @@ void cBlockArea::cChunkReader::ChunkData(const cChunkData & a_BlockBuffer)
{
int InChunkX = BaseX + x;
int AreaX = OffX + x;
m_Area.m_BlockLight[m_Area.MakeIndex(AreaX, AreaY, AreaZ)] = a_BlockBuffer.GetBlockLight({ InChunkX, InChunkY, InChunkZ });
m_Area.m_BlockLight[m_Area.MakeIndex(AreaX, AreaY, AreaZ)] = a_LightData.GetBlockLight({ InChunkX, InChunkY, InChunkZ });
} // for x
} // for z
} // for y
@ -2911,7 +2911,7 @@ void cBlockArea::cChunkReader::ChunkData(const cChunkData & a_BlockBuffer)
{
int InChunkX = BaseX + x;
int AreaX = OffX + x;
m_Area.m_BlockSkyLight[m_Area.MakeIndex(AreaX, AreaY, AreaZ)] = a_BlockBuffer.GetSkyLight({ InChunkX, InChunkY, InChunkZ });
m_Area.m_BlockSkyLight[m_Area.MakeIndex(AreaX, AreaY, AreaZ)] = a_LightData.GetSkyLight({ InChunkX, InChunkY, InChunkZ });
} // for x
} // for z
} // for y

View File

@ -449,7 +449,7 @@ protected:
// cChunkDataCallback overrides:
virtual bool Coords(int a_ChunkX, int a_ChunkZ) override;
virtual void ChunkData(const cChunkData & a_BlockTypes) override;
virtual void ChunkData(const ChunkBlockData & a_BlockData, const ChunkLightData & a_LightData) override;
virtual void BlockEntity(cBlockEntity * a_BlockEntity) override;
} ;

View File

@ -856,6 +856,34 @@ bool cByteBuffer::WriteBuf(const void * a_Buffer, size_t a_Count)
bool cByteBuffer::WriteBuf(size_t a_Count, unsigned char a_Value)
{
CHECK_THREAD
CheckValid();
PUTBYTES(a_Count);
ASSERT(m_BufferSize >= m_ReadPos);
size_t BytesToEndOfBuffer = m_BufferSize - m_WritePos;
if (BytesToEndOfBuffer <= a_Count)
{
// Reading across the ringbuffer end, read the first part and adjust parameters:
memset(m_Buffer + m_WritePos, a_Value, BytesToEndOfBuffer);
a_Count -= BytesToEndOfBuffer;
m_WritePos = 0;
}
// Read the rest of the bytes in a single read (guaranteed to fit):
if (a_Count > 0)
{
memset(m_Buffer + m_WritePos, a_Value, a_Count);
m_WritePos += a_Count;
}
return true;
}
bool cByteBuffer::ReadSome(ContiguousByteBuffer & a_String, size_t a_Count)
{
CHECK_THREAD

View File

@ -113,6 +113,9 @@ public:
/** Writes a_Count bytes into a_Buffer; returns true if successful */
bool WriteBuf(const void * a_Buffer, size_t a_Count);
/** Writes a_Count bytes into a_Buffer; returns true if successful */
bool WriteBuf(size_t a_Count, unsigned char a_Value);
/** Reads a_Count bytes into a_String; returns true if successful */
bool ReadSome(ContiguousByteBuffer & a_String, size_t a_Count);

View File

@ -57,7 +57,6 @@ target_sources(
Root.cpp
Scoreboard.cpp
Server.cpp
SetChunkData.cpp
SpawnPrepare.cpp
Statistics.cpp
StringCompression.cpp
@ -68,7 +67,6 @@ target_sources(
World.cpp
main.cpp
AllocationPool.h
BiomeDef.h
BlockArea.h
BlockInServerPluginInterface.h

View File

@ -54,20 +54,17 @@
cChunk::cChunk(
int a_ChunkX, int a_ChunkZ,
cChunkMap * a_ChunkMap, cWorld * a_World,
cAllocationPool<cChunkData::sChunkSection> & a_Pool
cChunkMap * a_ChunkMap, cWorld * a_World
):
m_Presence(cpInvalid),
m_IsLightValid(false),
m_IsDirty(false),
m_IsSaving(false),
m_HasLoadFailed(false),
m_StayCount(0),
m_PosX(a_ChunkX),
m_PosZ(a_ChunkZ),
m_World(a_World),
m_ChunkMap(a_ChunkMap),
m_ChunkData(a_Pool),
m_WaterSimulatorData(a_World->GetWaterSimulator()->CreateChunkData()),
m_LavaSimulatorData (a_World->GetLavaSimulator ()->CreateChunkData()),
m_RedstoneSimulatorData(a_World->GetRedstoneSimulator()->CreateChunkData()),
@ -271,7 +268,7 @@ void cChunk::MarkLoadFailed(void)
MarkDirty();
// The chunk is always needed, generate it:
m_World->GetGenerator().QueueGenerateChunk({m_PosX, m_PosZ}, false);
m_World->GetGenerator().QueueGenerateChunk({ m_PosX, m_PosZ }, false);
}
@ -282,12 +279,10 @@ void cChunk::GetAllData(cChunkDataCallback & a_Callback) const
{
ASSERT(m_Presence == cpPresent);
a_Callback.HeightMap(&m_HeightMap);
a_Callback.BiomeData(&m_BiomeMap);
a_Callback.LightIsValid(m_IsLightValid);
a_Callback.ChunkData(m_ChunkData);
a_Callback.ChunkData(m_BlockData, m_LightData);
a_Callback.HeightMap(m_HeightMap);
a_Callback.BiomeMap(m_BiomeMap);
for (const auto & Entity : m_Entities)
{
@ -304,27 +299,25 @@ void cChunk::GetAllData(cChunkDataCallback & a_Callback) const
void cChunk::SetAllData(cSetChunkData & a_SetChunkData)
void cChunk::SetAllData(SetChunkData && a_SetChunkData)
{
ASSERT(a_SetChunkData.IsHeightMapValid());
ASSERT(a_SetChunkData.AreBiomesValid());
std::copy(a_SetChunkData.HeightMap, a_SetChunkData.HeightMap + std::size(a_SetChunkData.HeightMap), m_HeightMap);
std::copy(a_SetChunkData.BiomeMap, a_SetChunkData.BiomeMap + std::size(a_SetChunkData.BiomeMap), m_BiomeMap);
memcpy(m_BiomeMap, a_SetChunkData.GetBiomes(), sizeof(m_BiomeMap));
memcpy(m_HeightMap, a_SetChunkData.GetHeightMap(), sizeof(m_HeightMap));
m_ChunkData.Assign(std::move(a_SetChunkData.GetChunkData()));
m_IsLightValid = a_SetChunkData.IsLightValid();
m_BlockData = std::move(a_SetChunkData.BlockData);
m_LightData = std::move(a_SetChunkData.LightData);
m_IsLightValid = a_SetChunkData.IsLightValid;
// Entities need some extra steps to destroy, so here we're keeping the old ones.
// Move the entities already in the chunk, including player entities, so that we don't lose any:
a_SetChunkData.GetEntities().insert(
a_SetChunkData.GetEntities().end(),
a_SetChunkData.Entities.insert(
a_SetChunkData.Entities.end(),
std::make_move_iterator(m_Entities.begin()),
std::make_move_iterator(m_Entities.end())
);
// Store the augmented result:
m_Entities = std::move(a_SetChunkData.GetEntities());
m_Entities = std::move(a_SetChunkData.Entities);
// Set all the entity variables again:
for (const auto & Entity : m_Entities)
@ -335,7 +328,7 @@ void cChunk::SetAllData(cSetChunkData & a_SetChunkData)
}
// Clear the block entities present - either the loader / saver has better, or we'll create empty ones:
m_BlockEntities = std::move(a_SetChunkData.GetBlockEntities());
m_BlockEntities = std::move(a_SetChunkData.BlockEntities);
// Check that all block entities have a valid blocktype at their respective coords (DEBUG-mode only):
#ifndef NDEBUG
@ -357,15 +350,8 @@ void cChunk::SetAllData(cSetChunkData & a_SetChunkData)
// Set the chunk data as valid. This may be needed for some simulators that perform actions upon block adding (Vaporize)
SetPresence(cpPresent);
if (a_SetChunkData.ShouldMarkDirty())
{
MarkDirty();
}
// Wake up all simulators for their respective blocks:
WakeUpSimulators();
m_HasLoadFailed = false;
}
@ -380,8 +366,7 @@ void cChunk::SetLight(
// TODO: We might get cases of wrong lighting when a chunk changes in the middle of a lighting calculation.
// Postponing until we see how bad it is :)
m_ChunkData.SetBlockLight(a_BlockLight);
m_ChunkData.SetSkyLight(a_SkyLight);
m_LightData.SetAll(a_BlockLight, a_SkyLight);
MarkDirty();
m_IsLightValid = true;
@ -391,15 +376,6 @@ void cChunk::SetLight(
void cChunk::GetBlockTypes(BLOCKTYPE * a_BlockTypes)
{
m_ChunkData.CopyBlockTypes(a_BlockTypes);
}
void cChunk::WriteBlockArea(cBlockArea & a_Area, int a_MinBlockX, int a_MinBlockY, int a_MinBlockZ, int a_DataTypes)
{
if ((a_DataTypes & (cBlockArea::baTypes | cBlockArea::baMetas)) != (cBlockArea::baTypes | cBlockArea::baMetas))
@ -1261,19 +1237,18 @@ void cChunk::WakeUpSimulators(void)
auto * LavaSimulator = m_World->GetLavaSimulator();
auto * RedstoneSimulator = m_World->GetRedstoneSimulator();
for (size_t SectionIdx = 0; SectionIdx != cChunkData::NumSections; ++SectionIdx)
for (size_t SectionIdx = 0; SectionIdx != cChunkDef::NumSections; ++SectionIdx)
{
const auto * Section = m_ChunkData.GetSection(SectionIdx);
const auto * Section = m_BlockData.GetSection(SectionIdx);
if (Section == nullptr)
{
continue;
}
for (size_t BlockIdx = 0; BlockIdx != cChunkData::SectionBlockCount; ++BlockIdx)
for (size_t BlockIdx = 0; BlockIdx != ChunkBlockData::SectionBlockCount; ++BlockIdx)
{
const auto BlockType = Section->m_BlockTypes[BlockIdx];
auto Position = cChunkDef::IndexToCoordinate(BlockIdx);
Position.y += static_cast<int>(SectionIdx * cChunkData::SectionHeight);
const auto BlockType = (*Section)[BlockIdx];
const auto Position = cChunkDef::IndexToCoordinate(BlockIdx + SectionIdx * ChunkBlockData::SectionBlockCount);
RedstoneSimulator->AddBlock(*this, Position, BlockType);
WaterSimulator->AddBlock(*this, Position, BlockType);
@ -1323,7 +1298,7 @@ void cChunk::FastSetBlock(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockT
ASSERT(IsValid());
const BLOCKTYPE OldBlockType = GetBlock(a_RelX, a_RelY, a_RelZ);
const BLOCKTYPE OldBlockMeta = m_ChunkData.GetMeta({ a_RelX, a_RelY, a_RelZ });
const BLOCKTYPE OldBlockMeta = m_BlockData.GetMeta({ a_RelX, a_RelY, a_RelZ });
if ((OldBlockType == a_BlockType) && (OldBlockMeta == a_BlockMeta))
{
return;
@ -1341,7 +1316,7 @@ void cChunk::FastSetBlock(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockT
MarkDirty();
}
m_ChunkData.SetBlock({ a_RelX, a_RelY, a_RelZ }, a_BlockType);
m_BlockData.SetBlock({ a_RelX, a_RelY, a_RelZ }, a_BlockType);
// Queue block to be sent only if ...
if (
@ -1357,7 +1332,7 @@ void cChunk::FastSetBlock(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockT
m_PendingSendBlocks.push_back(sSetBlock(m_PosX, m_PosZ, a_RelX, a_RelY, a_RelZ, a_BlockType, a_BlockMeta));
}
m_ChunkData.SetMeta({ a_RelX, a_RelY, a_RelZ }, a_BlockMeta);
m_BlockData.SetMeta({ a_RelX, a_RelY, a_RelZ }, a_BlockMeta);
// ONLY recalculate lighting if it's necessary!
if (
@ -2092,9 +2067,9 @@ void cChunk::GetBlockTypeMeta(Vector3i a_RelPos, BLOCKTYPE & a_BlockType, NIBBLE
void cChunk::GetBlockInfo(Vector3i a_RelPos, BLOCKTYPE & a_BlockType, NIBBLETYPE & a_Meta, NIBBLETYPE & a_SkyLight, NIBBLETYPE & a_BlockLight) const
{
a_BlockType = GetBlock(a_RelPos);
a_Meta = m_ChunkData.GetMeta(a_RelPos);
a_SkyLight = m_ChunkData.GetSkyLight(a_RelPos);
a_BlockLight = m_ChunkData.GetBlockLight(a_RelPos);
a_Meta = m_BlockData.GetMeta(a_RelPos);
a_SkyLight = m_LightData.GetSkyLight(a_RelPos);
a_BlockLight = m_LightData.GetBlockLight(a_RelPos);
}

View File

@ -37,7 +37,8 @@ class cBlockArea;
class cFluidSimulatorData;
class cMobCensus;
class cMobSpawner;
class cSetChunkData;
struct SetChunkData;
typedef std::list<cClientHandle *> cClientHandleList;
@ -59,8 +60,7 @@ public:
cChunk(
int a_ChunkX, int a_ChunkZ, // Chunk coords
cChunkMap * a_ChunkMap, cWorld * a_World, // Parent objects
cAllocationPool<cChunkData::sChunkSection> & a_Pool
cChunkMap * a_ChunkMap, cWorld * a_World // Parent objects
);
cChunk(const cChunk & Other) = delete;
~cChunk();
@ -113,16 +113,13 @@ public:
/** Sets all chunk data as either loaded from the storage or generated.
BlockLight and BlockSkyLight are optional, if not present, chunk will be marked as unlighted.
Modifies the BlockEntity list in a_SetChunkData - moves the block entities into the chunk. */
void SetAllData(cSetChunkData & a_SetChunkData);
void SetAllData(SetChunkData && a_SetChunkData);
void SetLight(
const cChunkDef::BlockNibbles & a_BlockLight,
const cChunkDef::BlockNibbles & a_SkyLight
);
/** Copies m_BlockData into a_BlockTypes, only the block types */
void GetBlockTypes(BLOCKTYPE * a_BlockTypes);
/** Writes the specified cBlockArea at the coords specified. Note that the coords may extend beyond the chunk! */
void WriteBlockArea(cBlockArea & a_Area, int a_MinBlockX, int a_MinBlockY, int a_MinBlockZ, int a_DataTypes);
@ -160,8 +157,8 @@ public:
FastSetBlock(a_RelPos.x, a_RelPos.y, a_RelPos.z, a_BlockType, a_BlockMeta);
}
BLOCKTYPE GetBlock(int a_RelX, int a_RelY, int a_RelZ) const { return m_ChunkData.GetBlock({ a_RelX, a_RelY, a_RelZ }); }
BLOCKTYPE GetBlock(Vector3i a_RelCoords) const { return m_ChunkData.GetBlock(a_RelCoords); }
BLOCKTYPE GetBlock(int a_RelX, int a_RelY, int a_RelZ) const { return m_BlockData.GetBlock({ a_RelX, a_RelY, a_RelZ }); }
BLOCKTYPE GetBlock(Vector3i a_RelCoords) const { return m_BlockData.GetBlock(a_RelCoords); }
void GetBlockTypeMeta(Vector3i a_RelPos, BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta) const;
void GetBlockTypeMeta(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta) const
@ -370,42 +367,36 @@ public:
inline NIBBLETYPE GetMeta(int a_RelX, int a_RelY, int a_RelZ) const
{
return m_ChunkData.GetMeta({ a_RelX, a_RelY, a_RelZ });
return m_BlockData.GetMeta({ a_RelX, a_RelY, a_RelZ });
}
NIBBLETYPE GetMeta(Vector3i a_RelPos) const { return m_ChunkData.GetMeta(a_RelPos); }
NIBBLETYPE GetMeta(Vector3i a_RelPos) const { return m_BlockData.GetMeta(a_RelPos); }
void SetMeta(int a_RelX, int a_RelY, int a_RelZ, NIBBLETYPE a_Meta)
{
SetMeta({ a_RelX, a_RelY, a_RelZ }, a_Meta);
}
/** Set a meta value, with the option of not informing the client and / or not marking dirty.
Used for setting metas that are of little value for saving to disk and / or for sending to the client,
such as leaf decay flags. */
inline void SetMeta(Vector3i a_RelPos, NIBBLETYPE a_Meta)
{
bool hasChanged = m_ChunkData.SetMeta(a_RelPos, a_Meta);
if (hasChanged)
{
MarkDirty();
m_PendingSendBlocks.push_back(sSetBlock(m_PosX, m_PosZ, a_RelPos.x, a_RelPos.y, a_RelPos.z, GetBlock(a_RelPos), a_Meta));
}
m_BlockData.SetMeta(a_RelPos, a_Meta);
MarkDirty();
m_PendingSendBlocks.push_back(sSetBlock(m_PosX, m_PosZ, a_RelPos.x, a_RelPos.y, a_RelPos.z, GetBlock(a_RelPos), a_Meta));
}
/** Light alterations based on time */
NIBBLETYPE GetTimeAlteredLight(NIBBLETYPE a_Skylight) const;
/** Get the level of artificial light illuminating the block (0 - 15) */
inline NIBBLETYPE GetBlockLight(Vector3i a_RelPos) const { return m_ChunkData.GetBlockLight(a_RelPos); }
inline NIBBLETYPE GetBlockLight(int a_RelX, int a_RelY, int a_RelZ) const { return m_ChunkData.GetBlockLight({ a_RelX, a_RelY, a_RelZ }); }
inline NIBBLETYPE GetBlockLight(Vector3i a_RelPos) const { return m_LightData.GetBlockLight(a_RelPos); }
inline NIBBLETYPE GetBlockLight(int a_RelX, int a_RelY, int a_RelZ) const { return m_LightData.GetBlockLight({ a_RelX, a_RelY, a_RelZ }); }
/** Get the level of sky light illuminating the block (0 - 15) independent of daytime. */
inline NIBBLETYPE GetSkyLight(Vector3i a_RelPos) const { return m_ChunkData.GetSkyLight(a_RelPos); }
inline NIBBLETYPE GetSkyLight(int a_RelX, int a_RelY, int a_RelZ) const { return m_ChunkData.GetSkyLight({ a_RelX, a_RelY, a_RelZ }); }
inline NIBBLETYPE GetSkyLight(Vector3i a_RelPos) const { return m_LightData.GetSkyLight(a_RelPos); }
inline NIBBLETYPE GetSkyLight(int a_RelX, int a_RelY, int a_RelZ) const { return m_LightData.GetSkyLight({ a_RelX, a_RelY, a_RelZ }); }
/** Get the level of sky light illuminating the block (0 - 15), taking daytime into a account. */
inline NIBBLETYPE GetSkyLightAltered(Vector3i a_RelPos) const { return GetTimeAlteredLight(m_ChunkData.GetSkyLight(a_RelPos)); }
inline NIBBLETYPE GetSkyLightAltered(Vector3i a_RelPos) const { return GetTimeAlteredLight(m_LightData.GetSkyLight(a_RelPos)); }
inline NIBBLETYPE GetSkyLightAltered(int a_RelX, int a_RelY, int a_RelZ) const { return GetSkyLightAltered({ a_RelX, a_RelY, a_RelZ }); }
/** Same as GetBlock(), but relative coords needn't be in this chunk (uses m_Neighbor-s or m_ChunkMap in such a case)
@ -578,7 +569,6 @@ private:
bool m_IsLightValid; // True if the blocklight and skylight are calculated
bool m_IsDirty; // True if the chunk has changed since it was last saved
bool m_IsSaving; // True if the chunk is being saved
bool m_HasLoadFailed; // True if chunk failed to load and hasn't been generated yet since then
std::queue<Vector3i> m_ToTickBlocks;
sSetBlockVector m_PendingSendBlocks; ///< Blocks that have changed and need to be sent to all clients
@ -595,7 +585,8 @@ private:
cWorld * m_World;
cChunkMap * m_ChunkMap;
cChunkData m_ChunkData;
ChunkBlockData m_BlockData;
ChunkLightData m_LightData;
cChunkDef::HeightMap m_HeightMap;
cChunkDef::BiomeMap m_BiomeMap;

View File

@ -13,37 +13,37 @@
namespace
{
/** Returns true if all a_Array's elements between [0] and [a_NumElements - 1] are equal to a_Value. */
template <typename T>
bool IsAllValue(const T * a_Array, size_t a_NumElements, T a_Value)
struct SectionIndices
{
for (size_t i = 0; i < a_NumElements; i++)
{
if (a_Array[i] != a_Value)
{
return false;
}
}
return true;
}
struct sSectionIndices
{
int Section = 0; // Index into m_Sections
int Index = 0; // Index into a single sChunkSection
size_t Section = 0; // Index into m_Sections
size_t Index = 0; // Index into a single sChunkSection
};
sSectionIndices IndicesFromRelPos(Vector3i a_RelPos)
inline SectionIndices IndicesFromRelPos(const Vector3i a_RelPos)
{
ASSERT(cChunkDef::IsValidRelPos(a_RelPos));
sSectionIndices Ret;
Ret.Section = a_RelPos.y / cChunkData::SectionHeight;
Ret.Index = cChunkDef::MakeIndexNoCheck(a_RelPos.x, a_RelPos.y % cChunkData::SectionHeight, a_RelPos.z);
return Ret;
return
{
static_cast<size_t>(a_RelPos.y / cChunkDef::SectionHeight),
static_cast<size_t>(cChunkDef::MakeIndexNoCheck(a_RelPos.x, a_RelPos.y % cChunkDef::SectionHeight, a_RelPos.z))
};
}
bool IsCompressed(const size_t ElementCount)
{
return ElementCount != ChunkBlockData::SectionBlockCount;
}
template <size_t ElementCount, typename ValueType>
ValueType UnpackDefaultValue(const ValueType DefaultValue)
{
if (IsCompressed(ElementCount))
{
return DefaultValue & 0xF;
}
return DefaultValue;
}
} // namespace (anonymous)
@ -51,54 +51,16 @@ namespace
cChunkData::cChunkData(cAllocationPool<cChunkData::sChunkSection> & a_Pool):
m_Sections(),
m_Pool(a_Pool)
template<class ElementType, size_t ElementCount, ElementType DefaultValue>
void ChunkDataStore<ElementType, ElementCount, DefaultValue>::Assign(const ChunkDataStore<ElementType, ElementCount, DefaultValue> & a_Other)
{
}
cChunkData::cChunkData(cChunkData && a_Other):
m_Pool(a_Other.m_Pool)
{
for (size_t i = 0; i < NumSections; i++)
for (size_t Y = 0; Y != cChunkDef::NumSections; Y++)
{
m_Sections[i] = a_Other.m_Sections[i];
a_Other.m_Sections[i] = nullptr;
}
}
Store[Y].reset();
cChunkData::~cChunkData()
{
Clear();
}
void cChunkData::Assign(const cChunkData & a_Other)
{
// If assigning to self, no-op
if (&a_Other == this)
{
return;
}
Clear();
for (size_t i = 0; i < NumSections; ++i)
{
if (a_Other.m_Sections[i] != nullptr)
if (const auto & Other = a_Other.Store[Y]; Other != nullptr)
{
m_Sections[i] = Allocate();
*m_Sections[i] = *a_Other.m_Sections[i];
Store[Y] = std::make_unique<Type>(*Other);
}
}
}
@ -107,47 +69,65 @@ void cChunkData::Assign(const cChunkData & a_Other)
void cChunkData::Assign(cChunkData && a_Other)
template<class ElementType, size_t ElementCount, ElementType DefaultValue>
ElementType ChunkDataStore<ElementType, ElementCount, DefaultValue>::Get(const Vector3i a_Position) const
{
if (&a_Other == this)
const auto Indices = IndicesFromRelPos(a_Position);
const auto & Section = Store[Indices.Section];
if (Section != nullptr)
{
return;
if (IsCompressed(ElementCount))
{
return cChunkDef::ExpandNibble(Section->data(), Indices.Index);
}
else
{
return (*Section)[Indices.Index];
}
}
if (m_Pool != a_Other.m_Pool)
{
// Cannot transfer the memory, do a copy instead
const cChunkData & CopyOther = a_Other;
Assign(CopyOther);
return;
}
Clear();
for (size_t i = 0; i < NumSections; i++)
{
m_Sections[i] = a_Other.m_Sections[i];
a_Other.m_Sections[i] = nullptr;
}
return UnpackDefaultValue<ElementCount>(DefaultValue);
}
BLOCKTYPE cChunkData::GetBlock(Vector3i a_RelPos) const
template<class ElementType, size_t ElementCount, ElementType DefaultValue>
typename ChunkDataStore<ElementType, ElementCount, DefaultValue>::Type * ChunkDataStore<ElementType, ElementCount, DefaultValue>::GetSection(const size_t a_Y) const
{
if (!cChunkDef::IsValidRelPos(a_RelPos))
return Store[a_Y].get();
}
template<class ElementType, size_t ElementCount, ElementType DefaultValue>
void ChunkDataStore<ElementType, ElementCount, DefaultValue>::Set(const Vector3i a_Position, const ElementType a_Value)
{
const auto Indices = IndicesFromRelPos(a_Position);
auto & Section = Store[Indices.Section];
if (Section == nullptr)
{
return E_BLOCK_AIR; // Coordinates are outside outside the world, so this must be an air block
if (a_Value == UnpackDefaultValue<ElementCount>(DefaultValue))
{
return;
}
Section = cpp20::make_unique_for_overwrite<Type>();
std::fill(Section->begin(), Section->end(), DefaultValue);
}
auto Idxs = IndicesFromRelPos(a_RelPos);
if (m_Sections[Idxs.Section] != nullptr)
if (IsCompressed(ElementCount))
{
return m_Sections[Idxs.Section]->m_BlockTypes[Idxs.Index];
cChunkDef::PackNibble(Section->data(), Indices.Index, a_Value);
}
else
{
return 0;
(*Section)[Indices.Index] = a_Value;
}
}
@ -155,576 +135,100 @@ BLOCKTYPE cChunkData::GetBlock(Vector3i a_RelPos) const
void cChunkData::SetBlock(Vector3i a_RelPos, BLOCKTYPE a_Block)
template<class ElementType, size_t ElementCount, ElementType DefaultValue>
void ChunkDataStore<ElementType, ElementCount, DefaultValue>::SetSection(const ElementType (& a_Source)[ElementCount], const size_t a_Y)
{
if (!cChunkDef::IsValidRelPos(a_RelPos))
{
ASSERT(!"cChunkData::SetMeta(): index out of range!");
return;
}
auto & Section = Store[a_Y];
const auto SourceEnd = std::end(a_Source);
auto Idxs = IndicesFromRelPos(a_RelPos);
if (m_Sections[Idxs.Section] == nullptr)
if (Section != nullptr)
{
if (a_Block == 0x00)
{
return;
}
m_Sections[Idxs.Section] = Allocate();
if (m_Sections[Idxs.Section] == nullptr)
{
ASSERT(!"Failed to allocate a new section in Chunkbuffer");
return;
}
ZeroSection(m_Sections[Idxs.Section]);
std::copy(a_Source, SourceEnd, Section->begin());
}
else if (std::any_of(a_Source, SourceEnd, [](const auto Value) { return Value != DefaultValue; }))
{
Section = cpp20::make_unique_for_overwrite<Type>();
std::copy(a_Source, SourceEnd, Section->begin());
}
m_Sections[Idxs.Section]->m_BlockTypes[Idxs.Index] = a_Block;
}
NIBBLETYPE cChunkData::GetMeta(Vector3i a_RelPos) const
template<class ElementType, size_t ElementCount, ElementType DefaultValue>
void ChunkDataStore<ElementType, ElementCount, DefaultValue>::SetAll(const ElementType (& a_Source)[cChunkDef::NumSections * ElementCount])
{
if (cChunkDef::IsValidRelPos(a_RelPos))
for (size_t Y = 0; Y != cChunkDef::NumSections; Y++)
{
auto Idxs = IndicesFromRelPos(a_RelPos);
if (m_Sections[Idxs.Section] != nullptr)
{
return (m_Sections[Idxs.Section]->m_BlockMetas[Idxs.Index / 2] >> ((Idxs.Index & 1) * 4)) & 0x0f;
}
else
{
return 0;
}
SetSection(*reinterpret_cast<const ElementType (*)[ElementCount]>(a_Source + Y * ElementCount), Y);
}
// Coordinates are outside outside the world, so it must be an air block with a blank meta
return 0;
}
bool cChunkData::SetMeta(Vector3i a_RelPos, NIBBLETYPE a_Nibble)
void ChunkBlockData::Assign(const ChunkBlockData & a_Other)
{
if (!cChunkDef::IsValidRelPos(a_RelPos))
{
ASSERT(!"cChunkData::SetMeta(): index out of range!");
return false;
}
auto Idxs = IndicesFromRelPos(a_RelPos);
if (m_Sections[Idxs.Section] == nullptr)
{
if ((a_Nibble & 0xf) == 0x00)
{
return false;
}
m_Sections[Idxs.Section] = Allocate();
if (m_Sections[Idxs.Section] == nullptr)
{
ASSERT(!"Failed to allocate a new section in Chunkbuffer");
return false;
}
ZeroSection(m_Sections[Idxs.Section]);
}
NIBBLETYPE oldval = m_Sections[Idxs.Section]->m_BlockMetas[Idxs.Index / 2] >> ((Idxs.Index & 1) * 4) & 0xf;
m_Sections[Idxs.Section]->m_BlockMetas[Idxs.Index / 2] = static_cast<NIBBLETYPE>(
(m_Sections[Idxs.Section]->m_BlockMetas[Idxs.Index / 2] & (0xf0 >> ((Idxs.Index & 1) * 4))) | // The untouched nibble
((a_Nibble & 0x0f) << ((Idxs.Index & 1) * 4)) // The nibble being set
);
return oldval != a_Nibble;
m_Blocks.Assign(a_Other.m_Blocks);
m_Metas.Assign(a_Other.m_Metas);
}
NIBBLETYPE cChunkData::GetBlockLight(Vector3i a_RelPos) const
void ChunkBlockData::SetAll(const cChunkDef::BlockTypes & a_BlockSource, const cChunkDef::BlockNibbles & a_MetaSource)
{
if (cChunkDef::IsValidRelPos(a_RelPos))
{
auto Idxs = IndicesFromRelPos(a_RelPos);
if (m_Sections[Idxs.Section] != nullptr)
{
return (m_Sections[Idxs.Section]->m_BlockLight[Idxs.Index / 2] >> ((Idxs.Index & 1) * 4)) & 0x0f;
}
else
{
return 0;
}
}
ASSERT(!"cChunkData::GetMeta(): coords out of chunk range!");
return 0;
m_Blocks.SetAll(a_BlockSource);
m_Metas.SetAll(a_MetaSource);
}
NIBBLETYPE cChunkData::GetSkyLight(Vector3i a_RelPos) const
void ChunkBlockData::SetSection(const SectionType & a_BlockSource, const SectionMetaType & a_MetaSource, const size_t a_Y)
{
if (cChunkDef::IsValidRelPos(a_RelPos))
{
auto Idxs = IndicesFromRelPos(a_RelPos);
if (m_Sections[Idxs.Section] != nullptr)
{
return (m_Sections[Idxs.Section]->m_BlockSkyLight[Idxs.Index / 2] >> ((Idxs.Index & 1) * 4)) & 0x0f;
}
else
{
return 0xF;
}
}
ASSERT(!"cChunkData::GetMeta(): coords out of chunk range!");
return 0;
m_Blocks.SetSection(a_BlockSource, a_Y);
m_Metas.SetSection(a_MetaSource, a_Y);
}
const cChunkData::sChunkSection * cChunkData::GetSection(size_t a_SectionNum) const
void ChunkLightData::Assign(const ChunkLightData & a_Other)
{
if (a_SectionNum < NumSections)
{
return m_Sections[a_SectionNum];
}
ASSERT(!"cChunkData::GetSection: section index out of range");
return nullptr;
m_BlockLights.Assign(a_Other.m_BlockLights);
m_SkyLights.Assign(a_Other.m_SkyLights);
}
UInt16 cChunkData::GetSectionBitmask() const
void ChunkLightData::SetAll(const cChunkDef::BlockNibbles & a_BlockLightSource, const cChunkDef::BlockNibbles & a_SkyLightSource)
{
static_assert(NumSections <= 16U, "cChunkData::GetSectionBitmask needs a bigger data type");
UInt16 Res = 0U;
for (size_t i = 0U; i < NumSections; ++i)
{
Res |= ((m_Sections[i] != nullptr) << i);
}
return Res;
m_BlockLights.SetAll(a_BlockLightSource);
m_SkyLights.SetAll(a_SkyLightSource);
}
void cChunkData::Clear()
void ChunkLightData::SetSection(const SectionType & a_BlockLightSource, const SectionType & a_SkyLightSource, const size_t a_Y)
{
for (size_t i = 0; i < NumSections; ++i)
{
if (m_Sections[i] != nullptr)
{
Free(m_Sections[i]);
m_Sections[i] = nullptr;
}
}
m_BlockLights.SetSection(a_BlockLightSource, a_Y);
m_SkyLights.SetSection(a_SkyLightSource, a_Y);
}
void cChunkData::CopyBlockTypes(BLOCKTYPE * a_Dest, size_t a_Idx, size_t a_Length) const
{
size_t ToSkip = a_Idx;
for (size_t i = 0; i < NumSections; i++)
{
size_t StartPos = 0;
if (ToSkip > 0)
{
StartPos = std::min(ToSkip, +SectionBlockCount);
ToSkip -= StartPos;
}
if (StartPos < SectionBlockCount)
{
size_t ToCopy = std::min(+SectionBlockCount - StartPos, a_Length);
a_Length -= ToCopy;
if (m_Sections[i] != nullptr)
{
BLOCKTYPE * blockbuffer = m_Sections[i]->m_BlockTypes;
memcpy(&a_Dest[(i * SectionBlockCount) + StartPos - a_Idx], blockbuffer + StartPos, sizeof(BLOCKTYPE) * ToCopy);
}
else
{
memset(&a_Dest[(i * SectionBlockCount) + StartPos - a_Idx], 0, sizeof(BLOCKTYPE) * ToCopy);
}
}
}
}
void cChunkData::CopyMetas(NIBBLETYPE * a_Dest) const
{
for (size_t i = 0; i < NumSections; i++)
{
if (m_Sections[i] != nullptr)
{
memcpy(&a_Dest[i * SectionBlockCount / 2], &m_Sections[i]->m_BlockMetas, sizeof(m_Sections[i]->m_BlockMetas));
}
else
{
memset(&a_Dest[i * SectionBlockCount / 2], 0, sizeof(m_Sections[i]->m_BlockMetas));
}
}
}
void cChunkData::CopyBlockLight(NIBBLETYPE * a_Dest) const
{
for (size_t i = 0; i < NumSections; i++)
{
if (m_Sections[i] != nullptr)
{
memcpy(&a_Dest[i * SectionBlockCount / 2], &m_Sections[i]->m_BlockLight, sizeof(m_Sections[i]->m_BlockLight));
}
else
{
memset(&a_Dest[i * SectionBlockCount / 2], 0, sizeof(m_Sections[i]->m_BlockLight));
}
}
}
void cChunkData::CopySkyLight(NIBBLETYPE * a_Dest) const
{
for (size_t i = 0; i < NumSections; i++)
{
if (m_Sections[i] != nullptr)
{
memcpy(&a_Dest[i * SectionBlockCount / 2], &m_Sections[i]->m_BlockSkyLight, sizeof(m_Sections[i]->m_BlockSkyLight));
}
else
{
memset(&a_Dest[i * SectionBlockCount / 2], 0xff, sizeof(m_Sections[i]->m_BlockSkyLight));
}
}
}
void cChunkData::FillBlockTypes(BLOCKTYPE a_Value)
{
// If needed, allocate any missing sections
if (a_Value != 0x00)
{
for (auto & Section : m_Sections)
{
if (Section == nullptr)
{
Section = Allocate();
std::fill(std::begin(Section->m_BlockMetas), std::end(Section->m_BlockMetas), 0x00);
std::fill(std::begin(Section->m_BlockLight), std::end(Section->m_BlockLight), 0x00);
std::fill(std::begin(Section->m_BlockSkyLight), std::end(Section->m_BlockSkyLight), 0xff);
}
}
}
for (auto Section : m_Sections)
{
if (Section != nullptr)
{
std::fill(std::begin(Section->m_BlockTypes), std::end(Section->m_BlockTypes), a_Value);
}
}
}
void cChunkData::FillMetas(NIBBLETYPE a_Value)
{
// If needed, allocate any missing sections
if (a_Value != 0x00)
{
for (auto & Section : m_Sections)
{
if (Section == nullptr)
{
Section = Allocate();
std::fill(std::begin(Section->m_BlockTypes), std::end(Section->m_BlockTypes), 0x00);
std::fill(std::begin(Section->m_BlockLight), std::end(Section->m_BlockLight), 0x00);
std::fill(std::begin(Section->m_BlockSkyLight), std::end(Section->m_BlockSkyLight), 0xff);
}
}
}
NIBBLETYPE NewMeta = static_cast<NIBBLETYPE>((a_Value << 4) | a_Value);
for (auto Section : m_Sections)
{
if (Section != nullptr)
{
std::fill(std::begin(Section->m_BlockMetas), std::end(Section->m_BlockMetas), NewMeta);
}
}
}
void cChunkData::FillBlockLight(NIBBLETYPE a_Value)
{
// If needed, allocate any missing sections
if (a_Value != 0x00)
{
for (auto & Section : m_Sections)
{
if (Section == nullptr)
{
Section = Allocate();
std::fill(std::begin(Section->m_BlockTypes), std::end(Section->m_BlockTypes), 0x00);
std::fill(std::begin(Section->m_BlockMetas), std::end(Section->m_BlockMetas), 0x00);
std::fill(std::begin(Section->m_BlockSkyLight), std::end(Section->m_BlockSkyLight), 0xff);
}
}
}
NIBBLETYPE NewLight = static_cast<NIBBLETYPE>((a_Value << 4) | a_Value);
for (auto Section : m_Sections)
{
if (Section != nullptr)
{
std::fill(std::begin(Section->m_BlockLight), std::end(Section->m_BlockLight), NewLight);
}
}
}
void cChunkData::FillSkyLight(NIBBLETYPE a_Value)
{
// If needed, allocate any missing sections
if (a_Value != 0x0f)
{
for (auto & Section : m_Sections)
{
if (Section == nullptr)
{
Section = Allocate();
std::fill(std::begin(Section->m_BlockTypes), std::end(Section->m_BlockTypes), 0x00);
std::fill(std::begin(Section->m_BlockMetas), std::end(Section->m_BlockMetas), 0x00);
std::fill(std::begin(Section->m_BlockLight), std::end(Section->m_BlockLight), 0x00);
}
}
}
NIBBLETYPE NewSkyLight = static_cast<NIBBLETYPE>((a_Value << 4) | a_Value);
for (auto Section : m_Sections)
{
if (Section != nullptr)
{
std::fill(std::begin(Section->m_BlockSkyLight), std::end(Section->m_BlockSkyLight), NewSkyLight);
}
}
}
void cChunkData::SetBlockTypes(const BLOCKTYPE * a_Src)
{
ASSERT(a_Src != nullptr);
for (size_t i = 0; i < NumSections; i++)
{
// If the section is already allocated, copy the data into it:
if (m_Sections[i] != nullptr)
{
memcpy(m_Sections[i]->m_BlockTypes, &a_Src[i * SectionBlockCount], sizeof(m_Sections[i]->m_BlockTypes));
continue;
}
// The section doesn't exist, find out if it is needed:
if (IsAllValue(a_Src + i * SectionBlockCount, SectionBlockCount, static_cast<BLOCKTYPE>(0)))
{
// No need for the section, the data is all-air
continue;
}
// Allocate the section and copy the data into it:
m_Sections[i] = Allocate();
memcpy(m_Sections[i]->m_BlockTypes, &a_Src[i * SectionBlockCount], sizeof(m_Sections[i]->m_BlockTypes));
memset(m_Sections[i]->m_BlockMetas, 0x00, sizeof(m_Sections[i]->m_BlockMetas));
memset(m_Sections[i]->m_BlockLight, 0x00, sizeof(m_Sections[i]->m_BlockLight));
memset(m_Sections[i]->m_BlockSkyLight, 0xff, sizeof(m_Sections[i]->m_BlockSkyLight));
} // for i - m_Sections[]
}
void cChunkData::SetMetas(const NIBBLETYPE * a_Src)
{
ASSERT(a_Src != nullptr);
for (size_t i = 0; i < NumSections; i++)
{
// If the section is already allocated, copy the data into it:
if (m_Sections[i] != nullptr)
{
memcpy(m_Sections[i]->m_BlockMetas, &a_Src[i * SectionBlockCount / 2], sizeof(m_Sections[i]->m_BlockMetas));
continue;
}
// The section doesn't exist, find out if it is needed:
if (IsAllValue(a_Src + i * SectionBlockCount / 2, SectionBlockCount / 2, static_cast<NIBBLETYPE>(0)))
{
// No need for the section, the data is all zeroes
continue;
}
// Allocate the section and copy the data into it:
m_Sections[i] = Allocate();
memcpy(m_Sections[i]->m_BlockMetas, &a_Src[i * SectionBlockCount / 2], sizeof(m_Sections[i]->m_BlockMetas));
memset(m_Sections[i]->m_BlockTypes, 0x00, sizeof(m_Sections[i]->m_BlockTypes));
memset(m_Sections[i]->m_BlockLight, 0x00, sizeof(m_Sections[i]->m_BlockLight));
memset(m_Sections[i]->m_BlockSkyLight, 0xff, sizeof(m_Sections[i]->m_BlockSkyLight));
} // for i - m_Sections[]
}
void cChunkData::SetBlockLight(const NIBBLETYPE * a_Src)
{
if (a_Src == nullptr)
{
return;
}
for (size_t i = 0; i < NumSections; i++)
{
// If the section is already allocated, copy the data into it:
if (m_Sections[i] != nullptr)
{
memcpy(m_Sections[i]->m_BlockLight, &a_Src[i * SectionBlockCount / 2], sizeof(m_Sections[i]->m_BlockLight));
continue;
}
// The section doesn't exist, find out if it is needed:
if (IsAllValue(a_Src + i * SectionBlockCount / 2, SectionBlockCount / 2, static_cast<NIBBLETYPE>(0)))
{
// No need for the section, the data is all zeroes
continue;
}
// Allocate the section and copy the data into it:
m_Sections[i] = Allocate();
memcpy(m_Sections[i]->m_BlockLight, &a_Src[i * SectionBlockCount / 2], sizeof(m_Sections[i]->m_BlockLight));
memset(m_Sections[i]->m_BlockTypes, 0x00, sizeof(m_Sections[i]->m_BlockTypes));
memset(m_Sections[i]->m_BlockMetas, 0x00, sizeof(m_Sections[i]->m_BlockMetas));
memset(m_Sections[i]->m_BlockSkyLight, 0xff, sizeof(m_Sections[i]->m_BlockSkyLight));
} // for i - m_Sections[]
}
void cChunkData::SetSkyLight(const NIBBLETYPE * a_Src)
{
if (a_Src == nullptr)
{
return;
}
for (size_t i = 0; i < NumSections; i++)
{
// If the section is already allocated, copy the data into it:
if (m_Sections[i] != nullptr)
{
memcpy(m_Sections[i]->m_BlockSkyLight, &a_Src[i * SectionBlockCount / 2], sizeof(m_Sections[i]->m_BlockSkyLight));
continue;
}
// The section doesn't exist, find out if it is needed:
if (IsAllValue(a_Src + i * SectionBlockCount / 2, SectionBlockCount / 2, static_cast<NIBBLETYPE>(0xff)))
{
// No need for the section, the data is all zeroes
continue;
}
// Allocate the section and copy the data into it:
m_Sections[i] = Allocate();
memcpy(m_Sections[i]->m_BlockSkyLight, &a_Src[i * SectionBlockCount / 2], sizeof(m_Sections[i]->m_BlockSkyLight));
memset(m_Sections[i]->m_BlockTypes, 0x00, sizeof(m_Sections[i]->m_BlockTypes));
memset(m_Sections[i]->m_BlockMetas, 0x00, sizeof(m_Sections[i]->m_BlockMetas));
memset(m_Sections[i]->m_BlockLight, 0x00, sizeof(m_Sections[i]->m_BlockLight));
} // for i - m_Sections[]
}
UInt32 cChunkData::NumPresentSections() const
{
UInt32 Ret = 0U;
for (size_t i = 0; i < NumSections; i++)
{
if (m_Sections[i] != nullptr)
{
++Ret;
}
}
return Ret;
}
cChunkData::sChunkSection * cChunkData::Allocate(void)
{
return m_Pool.Allocate();
}
void cChunkData::Free(cChunkData::sChunkSection * a_Section)
{
m_Pool.Free(a_Section);
}
void cChunkData::ZeroSection(cChunkData::sChunkSection * a_Section) const
{
memset(a_Section->m_BlockTypes, 0x00, sizeof(a_Section->m_BlockTypes));
memset(a_Section->m_BlockMetas, 0x00, sizeof(a_Section->m_BlockMetas));
memset(a_Section->m_BlockLight, 0x00, sizeof(a_Section->m_BlockLight));
memset(a_Section->m_BlockSkyLight, 0xff, sizeof(a_Section->m_BlockSkyLight));
}
template struct ChunkDataStore<BLOCKTYPE, ChunkBlockData::SectionBlockCount, ChunkBlockData::DefaultValue>;
template struct ChunkDataStore<NIBBLETYPE, ChunkBlockData::SectionMetaCount, ChunkLightData::DefaultBlockLightValue>;
template struct ChunkDataStore<NIBBLETYPE, ChunkLightData::SectionLightCount, ChunkLightData::DefaultSkyLightValue>;

View File

@ -9,132 +9,159 @@
#pragma once
#include <cstring>
#include "AllocationPool.h"
#include "FunctionRef.h"
#include "ChunkDef.h"
class cChunkData
template <class ElementType, size_t ElementCount, ElementType DefaultValue>
struct ChunkDataStore
{
public:
using Type = std::array<ElementType, ElementCount>;
static const int SectionHeight = 16;
static const size_t NumSections = (cChunkDef::Height / SectionHeight);
static const size_t SectionBlockCount = SectionHeight * cChunkDef::Width * cChunkDef::Width;
/** Copy assign from another ChunkDataStore. */
void Assign(const ChunkDataStore<ElementType, ElementCount, DefaultValue> & a_Other);
struct sChunkSection
{
BLOCKTYPE m_BlockTypes[SectionBlockCount];
NIBBLETYPE m_BlockMetas[SectionBlockCount / 2];
NIBBLETYPE m_BlockLight[SectionBlockCount / 2];
NIBBLETYPE m_BlockSkyLight[SectionBlockCount / 2];
};
/** Gets one value at the given position.
Returns DefaultValue if the section is not allocated. */
ElementType Get(Vector3i a_Position) const;
cChunkData(cAllocationPool<sChunkSection> & a_Pool);
cChunkData(cChunkData && a_Other);
~cChunkData();
/** Returns a raw pointer to the internal representation of the specified section.
Will be nullptr if the section is not allocated. */
Type * GetSection(size_t a_Y) const;
cChunkData & operator = (cChunkData && a_Other)
{
Assign(std::move(a_Other));
return *this;
}
/** Sets one value at the given position.
Allocates a section if needed for the operation. */
void Set(Vector3i a_Position, ElementType a_Value);
/** Copy assign from another cChunkData */
void Assign(const cChunkData & a_Other);
/** Copies the data from the specified flat section array into the internal representation.
Allocates a section if needed for the operation. */
void SetSection(const ElementType (& a_Source)[ElementCount], size_t a_Y);
/** Move assign from another cChunkData */
void Assign(cChunkData && a_Other);
BLOCKTYPE GetBlock(Vector3i a_RelPos) const;
void SetBlock(Vector3i a_RelPos, BLOCKTYPE a_Block);
NIBBLETYPE GetMeta(Vector3i a_RelPos) const;
bool SetMeta(Vector3i a_RelPos, NIBBLETYPE a_Nibble);
NIBBLETYPE GetBlockLight(Vector3i a_RelPos) const;
NIBBLETYPE GetSkyLight(Vector3i a_RelPos) const;
/** Return a pointer to the chunk section or nullptr if all air */
const sChunkSection * GetSection(size_t a_SectionNum) const;
/** Returns a bitmask of chunk sections which are currently stored. */
UInt16 GetSectionBitmask() const;
/** Clears all data */
void Clear();
/** Copies the blocktype data into the specified flat array.
Optionally, only a part of the data is copied, as specified by the a_Idx and a_Length parameters. */
void CopyBlockTypes(BLOCKTYPE * a_Dest, size_t a_Idx = 0, size_t a_Length = cChunkDef::NumBlocks) const;
/** Copies the metadata into the specified flat array. */
void CopyMetas(NIBBLETYPE * a_Dest) const;
/** Copies the block light data into the specified flat array. */
void CopyBlockLight(NIBBLETYPE * a_Dest) const;
/** Copies the skylight data into the specified flat array. */
void CopySkyLight (NIBBLETYPE * a_Dest) const;
/** Fills the chunk with the specified block. */
void FillBlockTypes(BLOCKTYPE a_Value);
/** Fills the chunk with the specified meta value. */
void FillMetas (NIBBLETYPE a_Value);
/** Fills the chunk with the specified block light. */
void FillBlockLight(NIBBLETYPE a_Value);
/** Fills the chunk with the specified sky light. */
void FillSkyLight (NIBBLETYPE a_Value);
/** Copies the blocktype data from the specified flat array into the internal representation.
Allocates sections that are needed for the operation.
Requires that a_Src is a valid pointer. */
void SetBlockTypes(const BLOCKTYPE * a_Src);
/** Copies the metadata from the specified flat array into the internal representation.
Allocates sectios that are needed for the operation.
Requires that a_Src is a valid pointer. */
void SetMetas(const NIBBLETYPE * a_Src);
/** Copies the blocklight data from the specified flat array into the internal representation.
Allocates sectios that are needed for the operation.
Allows a_Src to be nullptr, in which case it doesn't do anything. */
void SetBlockLight(const NIBBLETYPE * a_Src);
/** Copies the skylight data from the specified flat array into the internal representation.
Allocates sectios that are needed for the operation.
Allows a_Src to be nullptr, in which case it doesn't do anything. */
void SetSkyLight(const NIBBLETYPE * a_Src);
/** Returns the number of sections present (i.e. non-air). */
UInt32 NumPresentSections() const;
private:
sChunkSection * m_Sections[NumSections];
cAllocationPool<sChunkSection> & m_Pool;
/** Allocates a new section. Entry-point to custom allocators. */
sChunkSection * Allocate(void);
/** Frees the specified section, previously allocated using Allocate().
Note that a_Section may be nullptr. */
void Free(sChunkSection * a_Section);
/** Sets the data in the specified section to their default values. */
void ZeroSection(sChunkSection * a_Section) const;
/** Copies the data from the specified flat array into the internal representation.
Allocates sections that are needed for the operation. */
void SetAll(const ElementType (& a_Source)[cChunkDef::NumSections * ElementCount]);
/** Contains all the sections this ChunkDataStore manages. */
std::unique_ptr<Type> Store[cChunkDef::NumSections];
};
class ChunkBlockData
{
public:
static constexpr size_t SectionBlockCount = cChunkDef::SectionHeight * cChunkDef::Width * cChunkDef::Width;
static constexpr size_t SectionMetaCount = SectionBlockCount / 2;
static constexpr BLOCKTYPE DefaultValue = 0x00;
static constexpr NIBBLETYPE DefaultMetaValue = 0x00;
using SectionType = BLOCKTYPE[SectionBlockCount];
using SectionMetaType = NIBBLETYPE[SectionMetaCount];
private:
ChunkDataStore<BLOCKTYPE, SectionBlockCount, DefaultValue> m_Blocks;
ChunkDataStore<NIBBLETYPE, SectionMetaCount, DefaultMetaValue> m_Metas;
public:
using BlockArray = decltype(m_Blocks)::Type;
using MetaArray = decltype(m_Metas)::Type;
void Assign(const ChunkBlockData & a_Other);
BLOCKTYPE GetBlock(Vector3i a_Position) const { return m_Blocks.Get(a_Position); }
NIBBLETYPE GetMeta(Vector3i a_Position) const { return m_Metas.Get(a_Position); }
BlockArray * GetSection(size_t a_Y) const { return m_Blocks.GetSection(a_Y); }
MetaArray * GetMetaSection(size_t a_Y) const { return m_Metas.GetSection(a_Y); }
void SetBlock(Vector3i a_Position, BLOCKTYPE a_Block) { m_Blocks.Set(a_Position, a_Block); }
void SetMeta(Vector3i a_Position, NIBBLETYPE a_Meta) { m_Metas.Set(a_Position, a_Meta); }
void SetAll(const cChunkDef::BlockTypes & a_BlockSource, const cChunkDef::BlockNibbles & a_MetaSource);
void SetSection(const SectionType & a_BlockSource, const SectionMetaType & a_MetaSource, size_t a_Y);
};
class ChunkLightData
{
public:
static constexpr size_t SectionLightCount = (cChunkDef::SectionHeight * cChunkDef::Width * cChunkDef::Width) / 2;
static constexpr NIBBLETYPE DefaultBlockLightValue = 0x00;
static constexpr NIBBLETYPE DefaultSkyLightValue = 0xFF;
using SectionType = NIBBLETYPE[SectionLightCount];
private:
ChunkDataStore<NIBBLETYPE, SectionLightCount, DefaultBlockLightValue> m_BlockLights;
ChunkDataStore<NIBBLETYPE, SectionLightCount, DefaultSkyLightValue> m_SkyLights;
public:
using LightArray = decltype(m_BlockLights)::Type;
void Assign(const ChunkLightData & a_Other);
NIBBLETYPE GetBlockLight(Vector3i a_Position) const { return m_BlockLights.Get(a_Position); }
NIBBLETYPE GetSkyLight(Vector3i a_Position) const { return m_SkyLights.Get(a_Position); }
LightArray * GetBlockLightSection(size_t a_Y) const { return m_BlockLights.GetSection(a_Y); }
LightArray * GetSkyLightSection(size_t a_Y) const { return m_SkyLights.GetSection(a_Y); }
void SetAll(const cChunkDef::BlockNibbles & a_BlockLightSource, const cChunkDef::BlockNibbles & a_SkyLightSource);
void SetSection(const SectionType & a_BlockLightSource, const SectionType & a_SkyLightSource, size_t a_Y);
};
namespace ChunkDef
{
using cForEachSectionCallback = cFunctionRef<void(
size_t,
ChunkBlockData::BlockArray *,
ChunkBlockData::MetaArray *,
ChunkLightData::LightArray *,
ChunkLightData::LightArray *)>;
/** Invokes the callback functor for every chunk section containing at least one present block or light section data.
This is used to collect all data for all sections. */
inline void ForEachSection(const ChunkBlockData & a_BlockData, const ChunkLightData & a_LightData, cForEachSectionCallback a_Callback)
{
for (size_t Y = 0; Y < cChunkDef::NumSections; ++Y)
{
const auto Blocks = a_BlockData.GetSection(Y);
const auto Metas = a_BlockData.GetMetaSection(Y);
const auto BlockLights = a_LightData.GetBlockLightSection(Y);
const auto SkyLights = a_LightData.GetSkyLightSection(Y);
if ((Blocks != nullptr) || (Metas != nullptr) || (BlockLights != nullptr) || (SkyLights != nullptr))
{
a_Callback(Y, Blocks, Metas, BlockLights, SkyLights);
}
}
}
}
extern template struct ChunkDataStore<BLOCKTYPE, ChunkBlockData::SectionBlockCount, ChunkBlockData::DefaultValue>;
extern template struct ChunkDataStore<NIBBLETYPE, ChunkBlockData::SectionMetaCount, ChunkLightData::DefaultBlockLightValue>;
extern template struct ChunkDataStore<NIBBLETYPE, ChunkLightData::SectionLightCount, ChunkLightData::DefaultSkyLightValue>;

View File

@ -29,17 +29,17 @@ public:
If false is returned, the chunk is skipped. */
virtual bool Coords(int a_ChunkX, int a_ChunkZ) { UNUSED(a_ChunkX); UNUSED(a_ChunkZ); return true; }
/** Called once to provide heightmap data */
virtual void HeightMap(const cChunkDef::HeightMap * a_HeightMap) { UNUSED(a_HeightMap); }
/** Called once to provide biome data */
virtual void BiomeData(const cChunkDef::BiomeMap * a_BiomeMap) { UNUSED(a_BiomeMap); }
/** Called once to let know if the chunk lighting is valid. Return value is ignored */
virtual void LightIsValid(bool a_IsLightValid) { UNUSED(a_IsLightValid); }
/** Called once to export block info */
virtual void ChunkData(const cChunkData & a_Buffer) { UNUSED(a_Buffer); }
/** Called once to export block data. */
virtual void ChunkData(const ChunkBlockData & a_BlockData, const ChunkLightData & a_LightData) { UNUSED(a_BlockData); UNUSED(a_LightData); }
/** Called once to provide heightmap data. */
virtual void HeightMap(const cChunkDef::HeightMap & a_HeightMap) { UNUSED(a_HeightMap); }
/** Called once to provide biome data. */
virtual void BiomeMap(const cChunkDef::BiomeMap & a_BiomeMap) { UNUSED(a_BiomeMap); }
/** Called for each entity in the chunk */
virtual void Entity(cEntity * a_Entity) { UNUSED(a_Entity); }
@ -51,87 +51,20 @@ public:
/** A simple implementation of the cChunkDataCallback interface that collects all block data into a single buffer */
class cChunkDataArrayCollector :
public cChunkDataCallback
{
public:
// Must be unsigned char instead of BLOCKTYPE or NIBBLETYPE, because it houses both.
unsigned char m_BlockData[cChunkDef::BlockDataSize];
protected:
virtual void ChunkData(const cChunkData & a_ChunkBuffer) override
{
a_ChunkBuffer.CopyBlockTypes(m_BlockData);
a_ChunkBuffer.CopyMetas(m_BlockData + cChunkDef::NumBlocks);
a_ChunkBuffer.CopyBlockLight(m_BlockData + 3 * cChunkDef::NumBlocks / 2);
a_ChunkBuffer.CopySkyLight(m_BlockData + 2 * cChunkDef::NumBlocks);
}
};
/** A simple implementation of the cChunkDataCallback interface that collects all block data into separate buffers */
class cChunkDataSeparateCollector :
public cChunkDataCallback
{
public:
cChunkDef::BlockTypes m_BlockTypes;
cChunkDef::BlockNibbles m_BlockMetas;
cChunkDef::BlockNibbles m_BlockLight;
cChunkDef::BlockNibbles m_BlockSkyLight;
protected:
virtual void ChunkData(const cChunkData & a_ChunkBuffer) override
{
a_ChunkBuffer.CopyBlockTypes(m_BlockTypes);
a_ChunkBuffer.CopyMetas(m_BlockMetas);
a_ChunkBuffer.CopyBlockLight(m_BlockLight);
a_ChunkBuffer.CopySkyLight(m_BlockSkyLight);
}
} ;
/** A simple implementation of the cChunkDataCallback interface that just copies the cChunkData */
class cChunkDataCopyCollector :
public cChunkDataCallback
{
public:
struct MemCallbacks:
cAllocationPool<cChunkData::sChunkSection>::cStarvationCallbacks
ChunkBlockData m_BlockData;
ChunkLightData m_LightData;
private:
virtual void ChunkData(const ChunkBlockData & a_BlockData, const ChunkLightData & a_LightData) override
{
virtual void OnStartUsingReserve() override {}
virtual void OnEndUsingReserve() override {}
virtual void OnOutOfReserve() override {}
};
cChunkDataCopyCollector():
m_Pool(std::make_unique<MemCallbacks>(), 0, cChunkData::NumSections), // Keep 1 chunk worth of reserve
m_Data(m_Pool)
{
}
cListAllocationPool<cChunkData::sChunkSection> m_Pool;
cChunkData m_Data;
protected:
virtual void ChunkData(const cChunkData & a_ChunkBuffer) override
{
m_Data.Assign(a_ChunkBuffer);
m_BlockData.Assign(a_BlockData);
m_LightData.Assign(a_LightData);
}
};

View File

@ -129,13 +129,14 @@ private:
class cChunkDef
{
public:
// Chunk dimensions:
static const int Width = 16;
static const int Height = 256;
static const int NumBlocks = Width * Height * Width;
/** If the data is collected into a single buffer, how large it needs to be: */
static const int BlockDataSize = cChunkDef::NumBlocks * 2 + (cChunkDef::NumBlocks / 2); // 2.5 * numblocks
static const int SectionHeight = 16;
static const size_t NumSections = (cChunkDef::Height / SectionHeight);
/** The type used for any heightmap operations and storage; idx = x + Width * z; Height points to the highest non-air block in the column */
typedef HEIGHTTYPE HeightMap[Width * Width];
@ -151,12 +152,6 @@ public:
/** The type used for block data in nibble format, AXIS_ORDER ordering */
typedef NIBBLETYPE BlockNibbles[NumBlocks / 2];
/** The storage wrapper used for compressed blockdata residing in RAMz */
typedef std::vector<BLOCKTYPE> COMPRESSED_BLOCKTYPE;
/** The storage wrapper used for compressed nibbledata residing in RAMz */
typedef std::vector<NIBBLETYPE> COMPRESSED_NIBBLETYPE;
/** Converts absolute block coords into relative (chunk + block) coords: */
inline static void AbsoluteToRelative(/* in-out */ int & a_X, int & a_Y, int & a_Z, /* out */ int & a_ChunkX, int & a_ChunkZ)
@ -169,9 +164,6 @@ public:
}
/** Converts the specified absolute position into a relative position within its chunk.
Use BlockToChunk to query the chunk coords. */
inline static Vector3i AbsoluteToRelative(Vector3i a_BlockPosition)
@ -181,9 +173,6 @@ public:
}
/** Converts the absolute coords into coords relative to the specified chunk. */
inline static Vector3i AbsoluteToRelative(Vector3i a_BlockPosition, cChunkCoords a_ChunkPos)
{
@ -191,8 +180,6 @@ public:
}
/** Converts relative block coordinates into absolute coordinates with a known chunk location */
inline static Vector3i RelativeToAbsolute(Vector3i a_RelBlockPosition, cChunkCoords a_ChunkCoords)
{
@ -204,21 +191,20 @@ public:
}
/** Validates a height-coordinate. Returns false if height-coordiante is out of height bounds */
inline static bool IsValidHeight(int a_Height)
{
return ((a_Height >= 0) && (a_Height < Height));
}
/** Validates a width-coordinate. Returns false if width-coordiante is out of width bounds */
inline static bool IsValidWidth(int a_Width)
{
return ((a_Width >= 0) && (a_Width < Width));
}
/** Validates a chunk relative coordinate. Returns false if the coordiante is out of bounds for a chunk. */
inline static bool IsValidRelPos(Vector3i a_RelPos)
{
@ -229,6 +215,7 @@ public:
);
}
/** Converts absolute block coords to chunk coords: */
inline static void BlockToChunk(int a_X, int a_Z, int & a_ChunkX, int & a_ChunkZ)
{
@ -239,6 +226,7 @@ public:
a_ChunkZ = ChunkCoords.m_ChunkZ;
}
/** The Y coordinate of a_Pos is ignored */
inline static cChunkCoords BlockToChunk(const Vector3i a_Position)
{
@ -273,14 +261,12 @@ public:
}
inline static int MakeIndexNoCheck(Vector3i a_RelPos)
{
return MakeIndexNoCheck(a_RelPos.x, a_RelPos.y, a_RelPos.z);
}
inline static Vector3i IndexToCoordinate(size_t index)
{
#if AXIS_ORDER == AXIS_ORDER_XZY
@ -370,115 +356,33 @@ public:
}
static NIBBLETYPE GetNibble(const COMPRESSED_NIBBLETYPE & a_Buffer, int a_BlockIdx, bool a_IsSkyLightNibble = false)
{
if ((a_BlockIdx > -1) && (a_BlockIdx < NumBlocks))
{
if (static_cast<size_t>(a_BlockIdx / 2) >= a_Buffer.size())
{
return (a_IsSkyLightNibble ? 0xff : 0);
}
return (a_Buffer[static_cast<size_t>(a_BlockIdx / 2)] >> ((a_BlockIdx & 1) * 4)) & 0x0f;
}
ASSERT(!"cChunkDef::GetNibble(): index out of chunk range!");
return 0;
}
static NIBBLETYPE GetNibble(const COMPRESSED_NIBBLETYPE & a_Buffer, int x, int y, int z, bool a_IsSkyLightNibble = false)
{
if ((x < Width) && (x > -1) && (y < Height) && (y > -1) && (z < Width) && (z > -1))
{
size_t Index = static_cast<size_t>(MakeIndexNoCheck(x, y, z));
if ((Index / 2) >= a_Buffer.size())
{
return (a_IsSkyLightNibble ? 0xff : 0);
}
return ExpandNibble(a_Buffer, Index);
}
ASSERT(!"cChunkDef::GetNibble(): coords out of chunk range!");
return 0;
}
static NIBBLETYPE GetNibble(const NIBBLETYPE * a_Buffer, Vector3i a_RelPos)
{
if (IsValidRelPos(a_RelPos))
{
auto Index = MakeIndexNoCheck(a_RelPos);
return (a_Buffer[static_cast<size_t>(Index / 2)] >> ((Index & 1) * 4)) & 0x0f;
}
ASSERT(!"Coords out of chunk range!");
return 0;
}
static NIBBLETYPE GetNibble(const NIBBLETYPE * a_Buffer, int x, int y, int z)
{
if ((x < Width) && (x > -1) && (y < Height) && (y > -1) && (z < Width) && (z > -1))
{
int Index = MakeIndexNoCheck(x, y, z);
return (a_Buffer[static_cast<size_t>(Index / 2)] >> ((Index & 1) * 4)) & 0x0f;
return ExpandNibble(a_Buffer, static_cast<size_t>(Index));
}
ASSERT(!"cChunkDef::GetNibble(): coords out of chunk range!");
return 0;
}
static void SetNibble(COMPRESSED_NIBBLETYPE & a_Buffer, int a_BlockIdx, NIBBLETYPE a_Nibble)
inline static void PackNibble(NIBBLETYPE * const a_Buffer, const size_t a_Index, const NIBBLETYPE a_Nibble)
{
if ((a_BlockIdx < 0) || (a_BlockIdx >= NumBlocks))
{
ASSERT(!"cChunkDef::SetNibble(): index out of range!");
return;
}
if (static_cast<size_t>(a_BlockIdx / 2) >= a_Buffer.size())
{
a_Buffer.resize(static_cast<size_t>((a_BlockIdx / 2) + 1));
}
a_Buffer[static_cast<size_t>(a_BlockIdx / 2)] = PackNibble(a_Buffer, static_cast<size_t>(a_BlockIdx), a_Nibble);
}
ASSERT((a_Nibble & 0xF) == a_Nibble); // Only the lower bits should be set
static void SetNibble(COMPRESSED_NIBBLETYPE & a_Buffer, int x, int y, int z, NIBBLETYPE a_Nibble)
{
if (
(x >= Width) || (x < 0) ||
(y >= Height) || (y < 0) ||
(z >= Width) || (z < 0)
)
{
ASSERT(!"cChunkDef::SetNibble(): index out of range!");
return;
}
size_t Index = static_cast<size_t>(MakeIndexNoCheck(x, y, z));
if ((Index / 2) >= a_Buffer.size())
{
a_Buffer.resize(((Index / 2) + 1));
}
a_Buffer[(Index / 2)] = PackNibble(a_Buffer, Index, a_Nibble);
}
private:
inline static NIBBLETYPE PackNibble(const COMPRESSED_NIBBLETYPE & a_Buffer, size_t a_Index, NIBBLETYPE a_Nibble)
{
return static_cast<NIBBLETYPE>(
a_Buffer[a_Index / 2] = static_cast<NIBBLETYPE>(
(a_Buffer[a_Index / 2] & (0xf0 >> ((a_Index & 1) * 4))) | // The untouched nibble
((a_Nibble & 0x0f) << ((a_Index & 1) * 4)) // The nibble being set
);
}
inline static NIBBLETYPE ExpandNibble(const COMPRESSED_NIBBLETYPE & a_Buffer, size_t a_Index)
inline static NIBBLETYPE ExpandNibble(const NIBBLETYPE * const a_Buffer, const size_t a_Index)
{
return (a_Buffer[a_Index / 2] >> ((a_Index & 1) * 4)) & 0x0f;
}
} ;

View File

@ -30,12 +30,7 @@
// cChunkMap:
cChunkMap::cChunkMap(cWorld * a_World) :
m_World(a_World),
m_Pool(
std::make_unique<cListAllocationPool<cChunkData::sChunkSection>>(
std::make_unique<cStarvationCallbacks>(), 1600u, 5000u
)
)
m_World(a_World)
{
}
@ -43,25 +38,12 @@ cChunkMap::cChunkMap(cWorld * a_World) :
cChunkMap::~cChunkMap()
{
// Explicitly destroy all chunks, so that they're guaranteed to be
// destroyed before other internals. This fixes crashes on stopping the server.
// because the chunk destructor deletes entities and those may access the chunkmap.
// Also, the cChunkData destructor accesses the chunkMap's allocator.
m_Chunks.clear();
}
cChunk & cChunkMap::ConstructChunk(int a_ChunkX, int a_ChunkZ)
{
// If not exists insert. Then, return the chunk at these coordinates:
return m_Chunks.try_emplace(
{ a_ChunkX, a_ChunkZ },
a_ChunkX, a_ChunkZ, this, m_World, *m_Pool
a_ChunkX, a_ChunkZ, this, m_World
).first->second;
}
@ -232,15 +214,15 @@ void cChunkMap::MarkChunkSaved (int a_ChunkX, int a_ChunkZ)
void cChunkMap::SetChunkData(cSetChunkData & a_SetChunkData)
void cChunkMap::SetChunkData(struct SetChunkData && a_SetChunkData)
{
int ChunkX = a_SetChunkData.GetChunkX();
int ChunkZ = a_SetChunkData.GetChunkZ();
const int ChunkX = a_SetChunkData.Chunk.m_ChunkX;
const int ChunkZ = a_SetChunkData.Chunk.m_ChunkZ;
{
cCSLock Lock(m_CSChunks);
const auto Chunk = FindChunk(ChunkX, ChunkZ);
ASSERT(Chunk != nullptr); // Chunk cannot have unloaded since it is marked as queued
Chunk->SetAllData(a_SetChunkData);
Chunk->SetAllData(std::move(a_SetChunkData));
// Notify relevant ChunkStays:
cChunkStays ToBeDisabled;
@ -310,22 +292,6 @@ bool cChunkMap::GetChunkData(cChunkCoords a_Coords, cChunkDataCallback & a_Callb
bool cChunkMap::GetChunkBlockTypes(int a_ChunkX, int a_ChunkZ, BLOCKTYPE * a_BlockTypes)
{
cCSLock Lock(m_CSChunks);
const auto Chunk = FindChunk(a_ChunkX, a_ChunkZ);
if ((Chunk == nullptr) || !Chunk->IsValid())
{
return false;
}
Chunk->GetBlockTypes(a_BlockTypes);
return true;
}
bool cChunkMap::IsChunkQueued(int a_ChunkX, int a_ChunkZ) const
{
cCSLock Lock(m_CSChunks);

View File

@ -34,10 +34,11 @@ class cFlowerPotEntity;
class cBlockArea;
class cMobCensus;
class cMobSpawner;
class cSetChunkData;
class cBoundingBox;
class cDeadlockDetect;
struct SetChunkData;
typedef std::list<cClientHandle *> cClientHandleList;
using cEntityCallback = cFunctionRef<bool(cEntity &)>;
using cBeaconCallback = cFunctionRef<bool(cBeaconEntity &)>;
@ -65,7 +66,6 @@ class cChunkMap
public:
cChunkMap(cWorld * a_World);
~cChunkMap();
/** Sends the block entity, if it is at the coords specified, to a_Client */
void SendBlockEntity(int a_BlockX, int a_BlockY, int a_BlockZ, cClientHandle & a_Client);
@ -99,7 +99,7 @@ public:
If MarkDirty is set, the chunk is set as dirty (used after generating)
Modifies the BlockEntity list in a_SetChunkData - moves the block entities into the chunk.
*/
void SetChunkData(cSetChunkData & a_SetChunkData);
void SetChunkData(SetChunkData && a_SetChunkData);
void ChunkLighted(
int a_ChunkX, int a_ChunkZ,
@ -111,9 +111,6 @@ public:
Returns true if the chunk was reported successfully, false if not (chunk not present or callback failed). */
bool GetChunkData(cChunkCoords a_Coords, cChunkDataCallback & a_Callback) const;
/** Copies the chunk's blocktypes into a_Blocks; returns true if successful */
bool GetChunkBlockTypes (int a_ChunkX, int a_ChunkZ, BLOCKTYPE * a_Blocks);
/** Returns true iff the chunk is in the loader / generator queue. */
bool IsChunkQueued(int a_ChunkX, int a_ChunkZ) const;
@ -404,24 +401,6 @@ private:
// The chunkstay can (de-)register itself using AddChunkStay() and DelChunkStay()
friend class cChunkStay;
class cStarvationCallbacks
: public cAllocationPool<cChunkData::sChunkSection>::cStarvationCallbacks
{
virtual void OnStartUsingReserve() override
{
LOG("Using backup memory buffer");
}
virtual void OnEndUsingReserve() override
{
LOG("Stoped using backup memory buffer");
}
virtual void OnOutOfReserve() override
{
LOG("Out of Memory");
}
};
typedef std::list<cChunkStay *> cChunkStays;
mutable cCriticalSection m_CSChunks;
@ -437,8 +416,6 @@ private:
/** The cChunkStay descendants that are currently enabled in this chunkmap */
cChunkStays m_ChunkStays;
std::unique_ptr<cAllocationPool<cChunkData::sChunkSection> > m_Pool;
/** Returns or creates and returns a chunk pointer corresponding to the given chunk coordinates.
Emplaces this chunk in the chunk map. */
cChunk & ConstructChunk(int a_ChunkX, int a_ChunkZ);

View File

@ -238,7 +238,7 @@ void cChunkSender::SendChunk(int a_ChunkX, int a_ChunkZ, const WeakClients & a_C
}
// Send:
m_Serializer.SendToClients(a_ChunkX, a_ChunkZ, m_Data, m_BiomeMap, Clients);
m_Serializer.SendToClients(a_ChunkX, a_ChunkZ, m_BlockData, m_LightData, m_BiomeMap, Clients);
for (const auto & Client : Clients)
{
@ -276,7 +276,7 @@ void cChunkSender::SendChunk(int a_ChunkX, int a_ChunkZ, const WeakClients & a_C
});
}
}
m_Data.Clear();
m_BlockEntities.clear();
m_EntityIDs.clear();
}
@ -303,14 +303,14 @@ void cChunkSender::Entity(cEntity * a_Entity)
void cChunkSender::BiomeData(const cChunkDef::BiomeMap * a_BiomeMap)
void cChunkSender::BiomeMap(const cChunkDef::BiomeMap & a_BiomeMap)
{
for (size_t i = 0; i < ARRAYCOUNT(m_BiomeMap); i++)
{
if ((*a_BiomeMap)[i] < 255)
if (a_BiomeMap[i] < 255)
{
// Normal MC biome, copy as-is:
m_BiomeMap[i] = static_cast<unsigned char>((*a_BiomeMap)[i]);
m_BiomeMap[i] = static_cast<unsigned char>(a_BiomeMap[i]);
}
else
{
@ -319,7 +319,3 @@ void cChunkSender::BiomeData(const cChunkDef::BiomeMap * a_BiomeMap)
}
} // for i - m_BiomeMap[]
}

View File

@ -47,7 +47,7 @@ class cChunkSender;
class cChunkSender:
class cChunkSender final :
public cIsThread,
public cChunkDataCopyCollector
{
@ -125,7 +125,7 @@ protected:
// cChunkDataCollector overrides:
// (Note that they are called while the ChunkMap's CS is locked - don't do heavy calculations here!)
virtual void BiomeData (const cChunkDef::BiomeMap * a_BiomeMap) override;
virtual void BiomeMap (const cChunkDef::BiomeMap & a_BiomeMap) override;
virtual void Entity (cEntity * a_Entity) override;
virtual void BlockEntity (cBlockEntity * a_Entity) override;

View File

@ -293,6 +293,12 @@ namespace cpp20
{
return std::unique_ptr<T>(new std::remove_extent_t<T>[a_Size]);
}
template <class T>
std::enable_if_t<!std::is_array_v<T>, std::unique_ptr<T>> make_unique_for_overwrite()
{
return std::unique_ptr<T>(new T);
}
}

View File

@ -17,25 +17,25 @@
class cReader :
public cChunkDataCallback
{
virtual void ChunkData(const cChunkData & a_ChunkBuffer) override
virtual void ChunkData(const ChunkBlockData & a_BlockData, const ChunkLightData &) override
{
BLOCKTYPE * OutputRows = m_BlockTypes;
int OutputIdx = m_ReadingChunkX + m_ReadingChunkZ * cChunkDef::Width * 3;
for (size_t i = 0; i != cChunkData::NumSections; ++i)
for (size_t i = 0; i != cChunkDef::NumSections; ++i)
{
auto * Section = a_ChunkBuffer.GetSection(i);
const auto Section = a_BlockData.GetSection(i);
if (Section == nullptr)
{
// Skip to the next section
OutputIdx += 9 * cChunkData::SectionHeight * cChunkDef::Width;
OutputIdx += 9 * cChunkDef::SectionHeight * cChunkDef::Width;
continue;
}
for (size_t OffsetY = 0; OffsetY != cChunkData::SectionHeight; ++OffsetY)
for (size_t OffsetY = 0; OffsetY != cChunkDef::SectionHeight; ++OffsetY)
{
for (size_t Z = 0; Z != cChunkDef::Width; ++Z)
{
auto InPtr = Section->m_BlockTypes + Z * cChunkDef::Width + OffsetY * cChunkDef::Width * cChunkDef::Width;
auto InPtr = Section->data() + Z * cChunkDef::Width + OffsetY * cChunkDef::Width * cChunkDef::Width;
std::copy_n(InPtr, cChunkDef::Width, OutputRows + OutputIdx * cChunkDef::Width);
OutputIdx += 3;
@ -48,7 +48,7 @@ class cReader :
} // BlockTypes()
virtual void HeightMap(const cChunkDef::HeightMap * a_Heightmap) override
virtual void HeightMap(const cChunkDef::HeightMap & a_Heightmap) override
{
// Copy the entire heightmap, distribute it into the 3x3 chunk blob:
typedef struct {HEIGHTTYPE m_Row[16]; } ROW;
@ -64,11 +64,11 @@ class cReader :
// Find the highest block in the entire chunk, use it as a base for m_MaxHeight:
HEIGHTTYPE MaxHeight = m_MaxHeight;
for (size_t i = 0; i < ARRAYCOUNT(*a_Heightmap); i++)
for (size_t i = 0; i < ARRAYCOUNT(a_Heightmap); i++)
{
if ((*a_Heightmap)[i] > MaxHeight)
if (a_Heightmap[i] > MaxHeight)
{
MaxHeight = (*a_Heightmap)[i];
MaxHeight = a_Heightmap[i];
}
}
m_MaxHeight = MaxHeight;

View File

@ -48,37 +48,73 @@ cNetherPortalScanner::cNetherPortalScanner(cEntity & a_MovingEntity, cWorld & a_
void cNetherPortalScanner::OnChunkAvailable(int a_ChunkX, int a_ChunkZ)
{
cChunkDef::BlockTypes blocks;
m_World.GetChunkBlockTypes(a_ChunkX, a_ChunkZ, blocks);
// Iterate through all of the blocks in the chunk
for (unsigned int i = 0; i < cChunkDef::NumBlocks; i++)
class PortalSearchCallback : public cChunkDataCallback
{
if (blocks[i] == E_BLOCK_NETHER_PORTAL)
{
Vector3i Coordinate = cChunkDef::IndexToCoordinate(i);
if (Coordinate.y >= m_MaxY)
{
// This is above the map, don't consider it.
continue;
}
public:
Vector3d PortalLoc = Vector3d(Coordinate.x + a_ChunkX * cChunkDef::Width, Coordinate.y, Coordinate.z + a_ChunkZ * cChunkDef::Width);
if (!m_FoundPortal)
PortalSearchCallback(const int a_ChunkX, const int a_ChunkZ, bool & a_FoundPortal, Vector3i & a_PortalLoc, const Vector3d a_Position, const int a_MaxY) :
m_ChunkX(a_ChunkX),
m_ChunkZ(a_ChunkZ),
m_FoundPortal(a_FoundPortal),
m_PortalLoc(a_PortalLoc),
m_Position(a_Position),
m_MaxY(a_MaxY)
{
}
private:
virtual void ChunkData(const ChunkBlockData & a_BlockData, const ChunkLightData &) override
{
for (size_t Y = 0; Y < cChunkDef::NumSections; ++Y)
{
m_FoundPortal = true;
m_PortalLoc = PortalLoc;
}
else
{
if ((PortalLoc - m_Position).SqrLength() < (m_PortalLoc - m_Position).SqrLength())
const auto Blocks = a_BlockData.GetSection(Y);
if (Blocks == nullptr)
{
m_FoundPortal = true;
m_PortalLoc = PortalLoc;
continue;
}
// Iterate through all of the blocks in the chunk:
for (size_t i = 0; i < ChunkBlockData::SectionBlockCount; i++)
{
if ((*Blocks)[i] != E_BLOCK_NETHER_PORTAL)
{
continue;
}
Vector3i Coordinate = cChunkDef::IndexToCoordinate(i + Y * ChunkBlockData::SectionBlockCount);
if (Coordinate.y >= m_MaxY)
{
// This is above the map, don't consider it:
continue;
}
Vector3d PortalLoc = Vector3d(Coordinate.x + m_ChunkX * cChunkDef::Width, Coordinate.y, Coordinate.z + m_ChunkZ * cChunkDef::Width);
if (!m_FoundPortal)
{
m_FoundPortal = true;
m_PortalLoc = PortalLoc;
}
else if ((PortalLoc - m_Position).SqrLength() < (m_PortalLoc - m_Position).SqrLength())
{
// Found a closer portal, use that instead:
m_PortalLoc = PortalLoc;
}
}
}
}
}
int m_ChunkX, m_ChunkZ;
bool & m_FoundPortal;
Vector3i & m_PortalLoc;
const Vector3d m_Position;
const int m_MaxY;
} Callback(a_ChunkX, a_ChunkZ, m_FoundPortal, m_PortalLoc, m_Position, m_MaxY);
[[maybe_unused]] const bool Result = m_World.GetChunkData({ a_ChunkX, a_ChunkZ }, Callback);
ASSERT(Result);
}

View File

@ -14,26 +14,22 @@
/** Calls the given function with every present chunk section. */
template <class Func>
void ForEachSection(const cChunkData & a_Data, Func a_Func)
{
for (size_t SectionIdx = 0; SectionIdx < cChunkData::NumSections; ++SectionIdx)
{
auto Section = a_Data.GetSection(SectionIdx);
if (Section != nullptr)
{
a_Func(*Section);
}
}
}
namespace
{
std::pair<UInt16, size_t> GetSectionBitmask(const ChunkBlockData & a_BlockData, const ChunkLightData & a_LightData)
{
size_t Present = 0;
UInt16 Mask = 0;
ChunkDef::ForEachSection(a_BlockData, a_LightData, [&Present, &Mask](const auto Y, auto, auto, auto, auto)
{
Present++;
Mask |= (1 << Y);
});
return { Mask, Present };
}
auto PaletteLegacy(const BLOCKTYPE a_BlockType, const NIBBLETYPE a_Meta)
{
return (a_BlockType << 4) | a_Meta;
@ -72,7 +68,7 @@ cChunkDataSerializer::cChunkDataSerializer(const eDimension a_Dimension) :
void cChunkDataSerializer::SendToClients(const int a_ChunkX, const int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData, const ClientHandles & a_SendTo)
void cChunkDataSerializer::SendToClients(const int a_ChunkX, const int a_ChunkZ, const ChunkBlockData & a_BlockData, const ChunkLightData & a_LightData, const unsigned char * a_BiomeMap, const ClientHandles & a_SendTo)
{
for (const auto & Client : a_SendTo)
{
@ -80,14 +76,14 @@ void cChunkDataSerializer::SendToClients(const int a_ChunkX, const int a_ChunkZ,
{
case cProtocol::Version::v1_8_0:
{
Serialize(Client, a_ChunkX, a_ChunkZ, a_Data, a_BiomeData, CacheVersion::v47);
Serialize(Client, a_ChunkX, a_ChunkZ, a_BlockData, a_LightData, a_BiomeMap, CacheVersion::v47);
continue;
}
case cProtocol::Version::v1_9_0:
case cProtocol::Version::v1_9_1:
case cProtocol::Version::v1_9_2:
{
Serialize(Client, a_ChunkX, a_ChunkZ, a_Data, a_BiomeData, CacheVersion::v107);
Serialize(Client, a_ChunkX, a_ChunkZ, a_BlockData, a_LightData, a_BiomeMap, CacheVersion::v107);
continue;
}
case cProtocol::Version::v1_9_4:
@ -98,23 +94,23 @@ void cChunkDataSerializer::SendToClients(const int a_ChunkX, const int a_ChunkZ,
case cProtocol::Version::v1_12_1:
case cProtocol::Version::v1_12_2:
{
Serialize(Client, a_ChunkX, a_ChunkZ, a_Data, a_BiomeData, CacheVersion::v110);
Serialize(Client, a_ChunkX, a_ChunkZ, a_BlockData, a_LightData, a_BiomeMap, CacheVersion::v110);
continue;
}
case cProtocol::Version::v1_13:
{
Serialize(Client, a_ChunkX, a_ChunkZ, a_Data, a_BiomeData, CacheVersion::v393); // This version didn't last very long xD
Serialize(Client, a_ChunkX, a_ChunkZ, a_BlockData, a_LightData, a_BiomeMap, CacheVersion::v393); // This version didn't last very long xD
continue;
}
case cProtocol::Version::v1_13_1:
case cProtocol::Version::v1_13_2:
{
Serialize(Client, a_ChunkX, a_ChunkZ, a_Data, a_BiomeData, CacheVersion::v401);
Serialize(Client, a_ChunkX, a_ChunkZ, a_BlockData, a_LightData, a_BiomeMap, CacheVersion::v401);
continue;
}
case cProtocol::Version::v1_14:
{
Serialize(Client, a_ChunkX, a_ChunkZ, a_Data, a_BiomeData, CacheVersion::v477);
Serialize(Client, a_ChunkX, a_ChunkZ, a_BlockData, a_LightData, a_BiomeMap, CacheVersion::v477);
continue;
}
}
@ -132,7 +128,7 @@ void cChunkDataSerializer::SendToClients(const int a_ChunkX, const int a_ChunkZ,
inline void cChunkDataSerializer::Serialize(const ClientHandles::value_type & a_Client, const int a_ChunkX, const int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData, const CacheVersion a_CacheVersion)
inline void cChunkDataSerializer::Serialize(const ClientHandles::value_type & a_Client, const int a_ChunkX, const int a_ChunkZ, const ChunkBlockData & a_BlockData, const ChunkLightData & a_LightData, const unsigned char * a_BiomeMap, const CacheVersion a_CacheVersion)
{
auto & Cache = m_Cache[static_cast<size_t>(a_CacheVersion)];
if (Cache.Engaged)
@ -146,32 +142,32 @@ inline void cChunkDataSerializer::Serialize(const ClientHandles::value_type & a_
{
case CacheVersion::v47:
{
Serialize47(a_ChunkX, a_ChunkZ, a_Data, a_BiomeData);
Serialize47(a_ChunkX, a_ChunkZ, a_BlockData, a_LightData, a_BiomeMap);
break;
}
case CacheVersion::v107:
{
Serialize107(a_ChunkX, a_ChunkZ, a_Data, a_BiomeData);
Serialize107(a_ChunkX, a_ChunkZ, a_BlockData, a_LightData, a_BiomeMap);
break;
}
case CacheVersion::v110:
{
Serialize110(a_ChunkX, a_ChunkZ, a_Data, a_BiomeData);
Serialize110(a_ChunkX, a_ChunkZ, a_BlockData, a_LightData, a_BiomeMap);
break;
}
case CacheVersion::v393:
{
Serialize393<&Palette393>(a_ChunkX, a_ChunkZ, a_Data, a_BiomeData);
Serialize393<&Palette393>(a_ChunkX, a_ChunkZ, a_BlockData, a_LightData, a_BiomeMap);
break;
}
case CacheVersion::v401:
{
Serialize393<&Palette401>(a_ChunkX, a_ChunkZ, a_Data, a_BiomeData);
Serialize393<&Palette401>(a_ChunkX, a_ChunkZ, a_BlockData, a_LightData, a_BiomeMap);
break;
}
case CacheVersion::v477:
{
Serialize477(a_ChunkX, a_ChunkZ, a_Data, a_BiomeData);
Serialize477(a_ChunkX, a_ChunkZ, a_BlockData, a_LightData, a_BiomeMap);
break;
}
}
@ -185,177 +181,189 @@ inline void cChunkDataSerializer::Serialize(const ClientHandles::value_type & a_
inline void cChunkDataSerializer::Serialize47(const int a_ChunkX, const int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData)
inline void cChunkDataSerializer::Serialize47(const int a_ChunkX, const int a_ChunkZ, const ChunkBlockData & a_BlockData, const ChunkLightData & a_LightData, const unsigned char * a_BiomeMap)
{
// This function returns the fully compressed packet (including packet size), not the raw packet!
const auto Bitmask = GetSectionBitmask(a_BlockData, a_LightData);
// Create the packet:
m_Packet.WriteVarInt32(0x21); // Packet id (Chunk Data packet)
m_Packet.WriteBEInt32(a_ChunkX);
m_Packet.WriteBEInt32(a_ChunkZ);
m_Packet.WriteBool(true); // "Ground-up continuous", or rather, "biome data present" flag
m_Packet.WriteBEUInt16(a_Data.GetSectionBitmask());
m_Packet.WriteBEUInt16(Bitmask.first);
// Write the chunk size:
const int BiomeDataSize = cChunkDef::Width * cChunkDef::Width;
UInt32 ChunkSize = (
a_Data.NumPresentSections() * cChunkData::SectionBlockCount * 3 + // Blocks and lighting
const size_t ChunkSize = (
Bitmask.second * (ChunkBlockData::SectionBlockCount * 2 + ChunkLightData::SectionLightCount * 2) + // Blocks and lighting
BiomeDataSize // Biome data
);
m_Packet.WriteVarInt32(ChunkSize);
m_Packet.WriteVarInt32(static_cast<UInt32>(ChunkSize));
// Chunk written as seperate arrays of (blocktype + meta), blocklight and skylight
// each array stores all present sections of the same kind packed together
// Write the block types to the packet:
ForEachSection(a_Data, [&](const cChunkData::sChunkSection & a_Section)
ChunkDef::ForEachSection(a_BlockData, a_LightData, [this](auto, const auto Blocks, const auto Metas, auto, auto)
{
const bool BlocksExist = Blocks != nullptr;
const bool MetasExist = Metas != nullptr;
for (size_t BlockIdx = 0; BlockIdx != ChunkBlockData::SectionBlockCount; ++BlockIdx)
{
for (size_t BlockIdx = 0; BlockIdx != cChunkData::SectionBlockCount; ++BlockIdx)
{
BLOCKTYPE BlockType = a_Section.m_BlockTypes[BlockIdx] & 0xFF;
NIBBLETYPE BlockMeta = a_Section.m_BlockMetas[BlockIdx / 2] >> ((BlockIdx & 1) * 4) & 0x0f;
m_Packet.WriteBEUInt8(static_cast<unsigned char>(BlockType << 4) | BlockMeta);
m_Packet.WriteBEUInt8(static_cast<unsigned char>(BlockType >> 4));
}
BLOCKTYPE BlockType = BlocksExist ? (*Blocks)[BlockIdx] : 0;
NIBBLETYPE BlockMeta = MetasExist ? cChunkDef::ExpandNibble(Metas->data(), BlockIdx) : 0;
m_Packet.WriteBEUInt8(static_cast<unsigned char>(BlockType << 4) | BlockMeta);
m_Packet.WriteBEUInt8(static_cast<unsigned char>(BlockType >> 4));
}
);
});
// Write the block lights:
ForEachSection(a_Data, [&](const cChunkData::sChunkSection & a_Section)
ChunkDef::ForEachSection(a_BlockData, a_LightData, [this](auto, auto, auto, const auto BlockLights, auto)
{
if (BlockLights == nullptr)
{
m_Packet.WriteBuf(a_Section.m_BlockLight, sizeof(a_Section.m_BlockLight));
m_Packet.WriteBuf(ChunkLightData::SectionLightCount, ChunkLightData::DefaultBlockLightValue);
}
);
else
{
m_Packet.WriteBuf(BlockLights->data(), BlockLights->size());
}
});
// Write the sky lights:
ForEachSection(a_Data, [&](const cChunkData::sChunkSection & a_Section)
ChunkDef::ForEachSection(a_BlockData, a_LightData, [this](auto, auto, auto, auto, const auto SkyLights)
{
if (SkyLights == nullptr)
{
m_Packet.WriteBuf(a_Section.m_BlockSkyLight, sizeof(a_Section.m_BlockSkyLight));
m_Packet.WriteBuf(ChunkLightData::SectionLightCount, ChunkLightData::DefaultSkyLightValue);
}
);
else
{
m_Packet.WriteBuf(SkyLights->data(), SkyLights->size());
}
});
// Write the biome data:
m_Packet.WriteBuf(a_BiomeData, BiomeDataSize);
m_Packet.WriteBuf(a_BiomeMap, BiomeDataSize);
}
inline void cChunkDataSerializer::Serialize107(const int a_ChunkX, const int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData)
inline void cChunkDataSerializer::Serialize107(const int a_ChunkX, const int a_ChunkZ, const ChunkBlockData & a_BlockData, const ChunkLightData & a_LightData, const unsigned char * a_BiomeMap)
{
// This function returns the fully compressed packet (including packet size), not the raw packet!
// Below variables tagged static because of https://developercommunity.visualstudio.com/content/problem/367326
static constexpr UInt8 BitsPerEntry = 13;
static constexpr size_t ChunkSectionDataArraySize = (ChunkBlockData::SectionBlockCount * BitsPerEntry) / 8 / 8; // Convert from bit count to long count
const auto Bitmask = GetSectionBitmask(a_BlockData, a_LightData);
// Create the packet:
m_Packet.WriteVarInt32(0x20); // Packet id (Chunk Data packet)
m_Packet.WriteBEInt32(a_ChunkX);
m_Packet.WriteBEInt32(a_ChunkZ);
m_Packet.WriteBool(true); // "Ground-up continuous", or rather, "biome data present" flag
m_Packet.WriteVarInt32(a_Data.GetSectionBitmask());
// Write the chunk size:
const UInt8 BitsPerEntry = 13;
const size_t ChunkSectionDataArraySize = (cChunkData::SectionBlockCount * BitsPerEntry) / 8 / 8; // Convert from bit count to long count
m_Packet.WriteVarInt32(Bitmask.first);
size_t ChunkSectionSize = (
1 + // Bits per block - set to 13, so the global palette is used and the palette has a length of 0
1 + // Palette length
2 + // Data array length VarInt - 2 bytes for the current value
ChunkSectionDataArraySize * 8 + // Actual block data - multiplied by 8 because first number is longs
cChunkData::SectionBlockCount / 2 // Block light
ChunkLightData::SectionLightCount // Block light
);
if (m_Dimension == dimOverworld)
{
// Sky light is only sent in the overworld.
ChunkSectionSize += cChunkData::SectionBlockCount / 2;
ChunkSectionSize += ChunkLightData::SectionLightCount;
}
const size_t BiomeDataSize = cChunkDef::Width * cChunkDef::Width;
size_t ChunkSize = (
ChunkSectionSize * a_Data.NumPresentSections() +
const size_t ChunkSize = (
ChunkSectionSize * Bitmask.second +
BiomeDataSize
);
// Write the chunk size:
m_Packet.WriteVarInt32(static_cast<UInt32>(ChunkSize));
// Write each chunk section...
ForEachSection(a_Data, [&](const cChunkData::sChunkSection & a_Section)
{
m_Packet.WriteBEUInt8(BitsPerEntry);
m_Packet.WriteVarInt32(0); // Palette length is 0
m_Packet.WriteVarInt32(static_cast<UInt32>(ChunkSectionDataArraySize));
WriteSectionDataSeamless<&PaletteLegacy>(a_Section, BitsPerEntry);
// Write lighting:
m_Packet.WriteBuf(a_Section.m_BlockLight, sizeof(a_Section.m_BlockLight));
if (m_Dimension == dimOverworld)
{
// Skylight is only sent in the overworld; the nether and end do not use it
m_Packet.WriteBuf(a_Section.m_BlockSkyLight, sizeof(a_Section.m_BlockSkyLight));
}
}
);
ChunkDef::ForEachSection(a_BlockData, a_LightData, [this](auto, const auto Blocks, const auto Metas, const auto BlockLights, const auto SkyLights)
{
m_Packet.WriteBEUInt8(BitsPerEntry);
m_Packet.WriteVarInt32(0); // Palette length is 0
m_Packet.WriteVarInt32(static_cast<UInt32>(ChunkSectionDataArraySize));
WriteBlockSectionSeamless<&PaletteLegacy>(Blocks, Metas, BitsPerEntry);
WriteLightSectionGrouped(BlockLights, SkyLights);
});
// Write the biome data
m_Packet.WriteBuf(a_BiomeData, BiomeDataSize);
m_Packet.WriteBuf(a_BiomeMap, BiomeDataSize);
}
inline void cChunkDataSerializer::Serialize110(const int a_ChunkX, const int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData)
inline void cChunkDataSerializer::Serialize110(const int a_ChunkX, const int a_ChunkZ, const ChunkBlockData & a_BlockData, const ChunkLightData & a_LightData, const unsigned char * a_BiomeMap)
{
// This function returns the fully compressed packet (including packet size), not the raw packet!
// Below variables tagged static because of https://developercommunity.visualstudio.com/content/problem/367326
static constexpr UInt8 BitsPerEntry = 13;
static constexpr size_t ChunkSectionDataArraySize = (ChunkBlockData::SectionBlockCount * BitsPerEntry) / 8 / 8; // Convert from bit count to long count
const auto Bitmask = GetSectionBitmask(a_BlockData, a_LightData);
// Create the packet:
m_Packet.WriteVarInt32(0x20); // Packet id (Chunk Data packet)
m_Packet.WriteBEInt32(a_ChunkX);
m_Packet.WriteBEInt32(a_ChunkZ);
m_Packet.WriteBool(true); // "Ground-up continuous", or rather, "biome data present" flag
m_Packet.WriteVarInt32(a_Data.GetSectionBitmask());
// Write the chunk size:
const UInt8 BitsPerEntry = 13;
const size_t ChunkSectionDataArraySize = (cChunkData::SectionBlockCount * BitsPerEntry) / 8 / 8; // Convert from bit count to long count
m_Packet.WriteVarInt32(Bitmask.first);
size_t ChunkSectionSize = (
1 + // Bits per block - set to 13, so the global palette is used and the palette has a length of 0
1 + // Palette length
2 + // Data array length VarInt - 2 bytes for the current value
ChunkSectionDataArraySize * 8 + // Actual block data - multiplied by 8 because first number is longs
cChunkData::SectionBlockCount / 2 // Block light
ChunkLightData::SectionLightCount // Block light
);
if (m_Dimension == dimOverworld)
{
// Sky light is only sent in the overworld.
ChunkSectionSize += cChunkData::SectionBlockCount / 2;
ChunkSectionSize += ChunkLightData::SectionLightCount;
}
const size_t BiomeDataSize = cChunkDef::Width * cChunkDef::Width;
size_t ChunkSize = (
ChunkSectionSize * a_Data.NumPresentSections() +
const size_t ChunkSize = (
ChunkSectionSize * Bitmask.second +
BiomeDataSize
);
// Write the chunk size:
m_Packet.WriteVarInt32(static_cast<UInt32>(ChunkSize));
// Write each chunk section...
ForEachSection(a_Data, [&](const cChunkData::sChunkSection & a_Section)
{
m_Packet.WriteBEUInt8(BitsPerEntry);
m_Packet.WriteVarInt32(0); // Palette length is 0
m_Packet.WriteVarInt32(static_cast<UInt32>(ChunkSectionDataArraySize));
WriteSectionDataSeamless<&PaletteLegacy>(a_Section, BitsPerEntry);
// Write lighting:
m_Packet.WriteBuf(a_Section.m_BlockLight, sizeof(a_Section.m_BlockLight));
if (m_Dimension == dimOverworld)
{
// Skylight is only sent in the overworld; the nether and end do not use it
m_Packet.WriteBuf(a_Section.m_BlockSkyLight, sizeof(a_Section.m_BlockSkyLight));
}
}
);
ChunkDef::ForEachSection(a_BlockData, a_LightData, [this](auto, const auto Blocks, const auto Metas, const auto BlockLights, const auto SkyLights)
{
m_Packet.WriteBEUInt8(BitsPerEntry);
m_Packet.WriteVarInt32(0); // Palette length is 0
m_Packet.WriteVarInt32(static_cast<UInt32>(ChunkSectionDataArraySize));
WriteBlockSectionSeamless<&PaletteLegacy>(Blocks, Metas, BitsPerEntry);
WriteLightSectionGrouped(BlockLights, SkyLights);
});
// Write the biome data
m_Packet.WriteBuf(a_BiomeData, BiomeDataSize);
m_Packet.WriteBuf(a_BiomeMap, BiomeDataSize);
// Identify 1.9.4's tile entity list as empty
m_Packet.WriteBEUInt8(0);
@ -366,61 +374,58 @@ inline void cChunkDataSerializer::Serialize110(const int a_ChunkX, const int a_C
template <auto Palette>
inline void cChunkDataSerializer::Serialize393(const int a_ChunkX, const int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData)
inline void cChunkDataSerializer::Serialize393(const int a_ChunkX, const int a_ChunkZ, const ChunkBlockData & a_BlockData, const ChunkLightData & a_LightData, const unsigned char * a_BiomeMap)
{
// This function returns the fully compressed packet (including packet size), not the raw packet!
// Below variables tagged static because of https://developercommunity.visualstudio.com/content/problem/367326
static constexpr UInt8 BitsPerEntry = 14;
static constexpr size_t ChunkSectionDataArraySize = (ChunkBlockData::SectionBlockCount * BitsPerEntry) / 8 / 8;
const auto Bitmask = GetSectionBitmask(a_BlockData, a_LightData);
// Create the packet:
m_Packet.WriteVarInt32(0x22); // Packet id (Chunk Data packet)
m_Packet.WriteBEInt32(a_ChunkX);
m_Packet.WriteBEInt32(a_ChunkZ);
m_Packet.WriteBool(true); // "Ground-up continuous", or rather, "biome data present" flag
m_Packet.WriteVarInt32(a_Data.GetSectionBitmask());
m_Packet.WriteVarInt32(Bitmask.first);
// Write the chunk size in bytes:
const UInt8 BitsPerEntry = 14;
const size_t ChunkSectionDataArraySize = (cChunkData::SectionBlockCount * BitsPerEntry) / 8 / 8;
size_t ChunkSectionSize = (
1 + // Bits per entry, BEUInt8, 1 byte
m_Packet.GetVarIntSize(static_cast<UInt32>(ChunkSectionDataArraySize)) + // Field containing "size of whole section", VarInt32, variable size
ChunkSectionDataArraySize * 8 + // Actual section data, lots of bytes (multiplier 1 long = 8 bytes)
cChunkData::SectionBlockCount / 2 // Size of blocklight which is always sent
ChunkLightData::SectionLightCount // Size of blocklight which is always sent
);
if (m_Dimension == dimOverworld)
{
// Sky light is only sent in the overworld.
ChunkSectionSize += cChunkData::SectionBlockCount / 2;
ChunkSectionSize += ChunkLightData::SectionLightCount;
}
const size_t BiomeDataSize = cChunkDef::Width * cChunkDef::Width;
size_t ChunkSize = (
ChunkSectionSize * a_Data.NumPresentSections() +
const size_t ChunkSize = (
ChunkSectionSize * Bitmask.second +
BiomeDataSize * 4 // Biome data now BE ints
);
// Write the chunk size in bytes:
m_Packet.WriteVarInt32(static_cast<UInt32>(ChunkSize));
// Write each chunk section...
ForEachSection(a_Data, [&](const cChunkData::sChunkSection & a_Section)
{
m_Packet.WriteBEUInt8(BitsPerEntry);
m_Packet.WriteVarInt32(static_cast<UInt32>(ChunkSectionDataArraySize));
WriteSectionDataSeamless<Palette>(a_Section, BitsPerEntry);
// Write lighting:
m_Packet.WriteBuf(a_Section.m_BlockLight, sizeof(a_Section.m_BlockLight));
if (m_Dimension == dimOverworld)
{
// Skylight is only sent in the overworld; the nether and end do not use it
m_Packet.WriteBuf(a_Section.m_BlockSkyLight, sizeof(a_Section.m_BlockSkyLight));
}
}
);
ChunkDef::ForEachSection(a_BlockData, a_LightData, [this](auto, const auto Blocks, const auto Metas, const auto BlockLights, const auto SkyLights)
{
m_Packet.WriteBEUInt8(BitsPerEntry);
m_Packet.WriteVarInt32(static_cast<UInt32>(ChunkSectionDataArraySize));
WriteBlockSectionSeamless<Palette>(Blocks, Metas, BitsPerEntry);
WriteLightSectionGrouped(BlockLights, SkyLights);
});
// Write the biome data
for (size_t i = 0; i != BiomeDataSize; i++)
{
m_Packet.WriteBEUInt32(static_cast<UInt32>(a_BiomeData[i]) & 0xff);
m_Packet.WriteBEUInt32(static_cast<UInt32>(a_BiomeMap[i]));
}
// Identify 1.9.4's tile entity list as empty
@ -431,16 +436,22 @@ inline void cChunkDataSerializer::Serialize393(const int a_ChunkX, const int a_C
inline void cChunkDataSerializer::Serialize477(const int a_ChunkX, const int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData)
inline void cChunkDataSerializer::Serialize477(const int a_ChunkX, const int a_ChunkZ, const ChunkBlockData & a_BlockData, const ChunkLightData & a_LightData, const unsigned char * a_BiomeMap)
{
// This function returns the fully compressed packet (including packet size), not the raw packet!
// Below variables tagged static because of https://developercommunity.visualstudio.com/content/problem/367326
static constexpr UInt8 BitsPerEntry = 14;
static constexpr size_t ChunkSectionDataArraySize = (ChunkBlockData::SectionBlockCount * BitsPerEntry) / 8 / 8;
const auto Bitmask = GetSectionBitmask(a_BlockData, a_LightData);
// Create the packet:
m_Packet.WriteVarInt32(0x21); // Packet id (Chunk Data packet)
m_Packet.WriteBEInt32(a_ChunkX);
m_Packet.WriteBEInt32(a_ChunkZ);
m_Packet.WriteBool(true); // "Ground-up continuous", or rather, "biome data present" flag
m_Packet.WriteVarInt32(a_Data.GetSectionBitmask());
m_Packet.WriteVarInt32(Bitmask.first);
{
cFastNBTWriter Writer;
@ -451,9 +462,6 @@ inline void cChunkDataSerializer::Serialize477(const int a_ChunkX, const int a_C
m_Packet.Write(Writer.GetResult().data(), Writer.GetResult().size());
}
// Write the chunk size in bytes:
const UInt8 BitsPerEntry = 14;
const size_t ChunkSectionDataArraySize = (cChunkData::SectionBlockCount * BitsPerEntry) / 8 / 8;
const size_t ChunkSectionSize = (
2 + // Block count, BEInt16, 2 bytes
1 + // Bits per entry, BEUInt8, 1 byte
@ -463,25 +471,26 @@ inline void cChunkDataSerializer::Serialize477(const int a_ChunkX, const int a_C
const size_t BiomeDataSize = cChunkDef::Width * cChunkDef::Width;
const size_t ChunkSize = (
ChunkSectionSize * a_Data.NumPresentSections() +
ChunkSectionSize * Bitmask.second +
BiomeDataSize * 4 // Biome data now BE ints
);
// Write the chunk size in bytes:
m_Packet.WriteVarInt32(static_cast<UInt32>(ChunkSize));
// Write each chunk section...
ForEachSection(a_Data, [&](const cChunkData::sChunkSection & a_Section)
{
m_Packet.WriteBEInt16(-1);
m_Packet.WriteBEUInt8(BitsPerEntry);
m_Packet.WriteVarInt32(static_cast<UInt32>(ChunkSectionDataArraySize));
WriteSectionDataSeamless<&Palette477>(a_Section, BitsPerEntry);
}
);
ChunkDef::ForEachSection(a_BlockData, a_LightData, [this](auto, const auto Blocks, const auto Metas, auto, auto)
{
m_Packet.WriteBEInt16(-1);
m_Packet.WriteBEUInt8(BitsPerEntry);
m_Packet.WriteVarInt32(static_cast<UInt32>(ChunkSectionDataArraySize));
WriteBlockSectionSeamless<&Palette477>(Blocks, Metas, BitsPerEntry);
});
// Write the biome data
for (size_t i = 0; i != BiomeDataSize; i++)
{
m_Packet.WriteBEUInt32(static_cast<UInt32>(a_BiomeData[i]) & 0xff);
m_Packet.WriteBEUInt32(a_BiomeMap[i]);
}
// Identify 1.9.4's tile entity list as empty
@ -493,7 +502,7 @@ inline void cChunkDataSerializer::Serialize477(const int a_ChunkX, const int a_C
template <auto Palette>
inline void cChunkDataSerializer::WriteSectionDataSeamless(const cChunkData::sChunkSection & a_Section, const UInt8 a_BitsPerEntry)
inline void cChunkDataSerializer::WriteBlockSectionSeamless(const ChunkBlockData::BlockArray * a_Blocks, const ChunkBlockData::MetaArray * a_Metas, const UInt8 a_BitsPerEntry)
{
// https://wiki.vg/Chunk_Format#Data_structure
@ -503,10 +512,13 @@ inline void cChunkDataSerializer::WriteSectionDataSeamless(const cChunkData::sCh
UInt64 Buffer = 0; // A buffer to compose multiple smaller bitsizes into one 64-bit number
unsigned char BitIndex = 0; // The bit-position in Buffer that represents where to write next
for (size_t Index = 0; Index != cChunkData::SectionBlockCount; Index++)
const bool BlocksExist = a_Blocks != nullptr;
const bool MetasExist = a_Metas != nullptr;
for (size_t Index = 0; Index != ChunkBlockData::SectionBlockCount; Index++)
{
const BLOCKTYPE BlockType = a_Section.m_BlockTypes[Index];
const NIBBLETYPE BlockMeta = (a_Section.m_BlockMetas[Index / 2] >> ((Index % 2) * 4)) & 0x0f;
const BLOCKTYPE BlockType = BlocksExist ? (*a_Blocks)[Index] : 0;
const NIBBLETYPE BlockMeta = MetasExist ? cChunkDef::ExpandNibble(a_Metas->data(), Index) : 0;
const auto Value = Palette(BlockType, BlockMeta);
// Write as much as possible of Value, starting from BitIndex, into Buffer:
@ -530,7 +542,7 @@ inline void cChunkDataSerializer::WriteSectionDataSeamless(const cChunkData::sCh
}
}
static_assert((cChunkData::SectionBlockCount % 64) == 0, "Section must fit wholly into a 64-bit long array");
static_assert((ChunkBlockData::SectionBlockCount % 64) == 0, "Section must fit wholly into a 64-bit long array");
ASSERT(BitIndex == 0);
ASSERT(Buffer == 0);
}
@ -539,6 +551,36 @@ inline void cChunkDataSerializer::WriteSectionDataSeamless(const cChunkData::sCh
inline void cChunkDataSerializer::WriteLightSectionGrouped(const ChunkLightData::LightArray * const a_BlockLights, const ChunkLightData::LightArray * const a_SkyLights)
{
// Write lighting:
if (a_BlockLights == nullptr)
{
m_Packet.WriteBuf(ChunkLightData::SectionLightCount, ChunkLightData::DefaultBlockLightValue);
}
else
{
m_Packet.WriteBuf(a_BlockLights->data(), a_BlockLights->size());
}
// Skylight is only sent in the overworld; the nether and end do not use it:
if (m_Dimension == dimOverworld)
{
if (a_SkyLights == nullptr)
{
m_Packet.WriteBuf(ChunkLightData::SectionLightCount, ChunkLightData::DefaultSkyLightValue);
}
else
{
m_Packet.WriteBuf(a_SkyLights->data(), a_SkyLights->size());
}
}
}
inline void cChunkDataSerializer::CompressPacketInto(ChunkDataCache & a_Cache)
{
m_Compressor.ReadFrom(m_Packet);

View File

@ -49,25 +49,28 @@ public:
/** For each client, serializes the chunk into their protocol version and sends it.
Parameters are the coordinates of the chunk to serialise, and the data and biome data read from the chunk. */
void SendToClients(int a_ChunkX, int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData, const ClientHandles & a_SendTo);
void SendToClients(int a_ChunkX, int a_ChunkZ, const ChunkBlockData & a_BlockData, const ChunkLightData & a_LightData, const unsigned char * a_BiomeMap, const ClientHandles & a_SendTo);
private:
/** Serialises the given chunk, storing the result into the given cache entry, and sends the data.
If the cache entry is already present, simply re-uses it. */
inline void Serialize(const ClientHandles::value_type & a_Client, int a_ChunkX, int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData, CacheVersion a_CacheVersion);
inline void Serialize(const ClientHandles::value_type & a_Client, int a_ChunkX, int a_ChunkZ, const ChunkBlockData & a_BlockData, const ChunkLightData & a_LightData, const unsigned char * a_BiomeMap, CacheVersion a_CacheVersion);
inline void Serialize47 (int a_ChunkX, int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData); // Release 1.8
inline void Serialize107(int a_ChunkX, int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData); // Release 1.9
inline void Serialize110(int a_ChunkX, int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData); // Release 1.9.4
inline void Serialize47 (int a_ChunkX, int a_ChunkZ, const ChunkBlockData & a_BlockData, const ChunkLightData & a_LightData, const unsigned char * a_BiomeMap); // Release 1.8
inline void Serialize107(int a_ChunkX, int a_ChunkZ, const ChunkBlockData & a_BlockData, const ChunkLightData & a_LightData, const unsigned char * a_BiomeMap); // Release 1.9
inline void Serialize110(int a_ChunkX, int a_ChunkZ, const ChunkBlockData & a_BlockData, const ChunkLightData & a_LightData, const unsigned char * a_BiomeMap); // Release 1.9.4
template <auto Palette>
inline void Serialize393(int a_ChunkX, int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData); // Release 1.13 - 1.13.2
inline void Serialize477(int a_ChunkX, int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData); // Release 1.14 - 1.14.4
inline void Serialize393(int a_ChunkX, int a_ChunkZ, const ChunkBlockData & a_BlockData, const ChunkLightData & a_LightData, const unsigned char * a_BiomeMap); // Release 1.13 - 1.13.2
inline void Serialize477(int a_ChunkX, int a_ChunkZ, const ChunkBlockData & a_BlockData, const ChunkLightData & a_LightData, const unsigned char * a_BiomeMap); // Release 1.14 - 1.14.4
/** Writes all blocks in a chunk section into a series of Int64.
Writes start from the bit directly subsequent to the previous write's end, possibly crossing over to the next Int64. */
template <auto Palette>
inline void WriteSectionDataSeamless(const cChunkData::sChunkSection & a_Section, const UInt8 a_BitsPerEntry);
inline void WriteBlockSectionSeamless(const ChunkBlockData::BlockArray * a_Blocks, const ChunkBlockData::MetaArray * a_Metas, UInt8 a_BitsPerEntry);
/** Copies all lights in a chunk section into the packet, block light followed immediately by sky light. */
inline void WriteLightSectionGrouped(const ChunkLightData::LightArray * a_BlockLights, const ChunkLightData::LightArray * a_SkyLights);
/** Finalises the data, compresses it if required, and stores it into cache. */
inline void CompressPacketInto(ChunkDataCache & a_Cache);

View File

@ -9,12 +9,13 @@
#pragma once
#include "FunctionRef.h"
#include "ChunkDef.h"
class cClientHandle;
class cObjective;
class cTeam;
class cWorld;

View File

@ -1,157 +0,0 @@
// SetChunkData.cpp
// Implements the cSetChunkData class used for sending loaded / generated chunk
#include "Globals.h"
#include "SetChunkData.h"
#include "BlockEntities/BlockEntity.h"
#include "Entities/Entity.h"
namespace
{
struct sMemCallbacks:
cAllocationPool<cChunkData::sChunkSection>::cStarvationCallbacks
{
virtual void OnStartUsingReserve() override {}
virtual void OnEndUsingReserve() override {}
virtual void OnOutOfReserve() override {}
};
} // namespace (anonymous)
cSetChunkData::cSetChunkData(int a_ChunkX, int a_ChunkZ, bool a_ShouldMarkDirty) :
m_ChunkX(a_ChunkX),
m_ChunkZ(a_ChunkZ),
m_Pool(std::make_unique<sMemCallbacks>(), 0u, cChunkData::NumSections),
m_ChunkData(m_Pool),
m_IsLightValid(false),
m_IsHeightMapValid(false),
m_AreBiomesValid(false),
m_ShouldMarkDirty(a_ShouldMarkDirty)
{
}
cSetChunkData::cSetChunkData(
int a_ChunkX, int a_ChunkZ,
const BLOCKTYPE * a_BlockTypes,
const NIBBLETYPE * a_BlockMetas,
const NIBBLETYPE * a_BlockLight,
const NIBBLETYPE * a_SkyLight,
const cChunkDef::HeightMap * a_HeightMap,
const cChunkDef::BiomeMap * a_Biomes,
cEntityList && a_Entities,
cBlockEntities && a_BlockEntities,
bool a_ShouldMarkDirty
) :
cSetChunkData(a_ChunkX, a_ChunkZ, a_ShouldMarkDirty)
{
// Check the params' validity:
ASSERT(a_BlockTypes != nullptr);
ASSERT(a_BlockMetas != nullptr);
// Copy block types and metas:
m_ChunkData.SetBlockTypes(a_BlockTypes);
m_ChunkData.SetMetas(a_BlockMetas);
// Copy lights, if both given:
if ((a_BlockLight != nullptr) && (a_SkyLight != nullptr))
{
m_ChunkData.SetBlockLight(a_BlockLight);
m_ChunkData.SetSkyLight(a_SkyLight);
m_IsLightValid = true;
}
// Copy the heightmap, if available:
if (a_HeightMap != nullptr)
{
memcpy(m_HeightMap, a_HeightMap, sizeof(m_HeightMap));
m_IsHeightMapValid = true;
}
// Copy biomes, if available:
if (a_Biomes != nullptr)
{
memcpy(m_Biomes, a_Biomes, sizeof(m_Biomes));
m_AreBiomesValid = true;
}
// Move entities and blockentities:
m_Entities = std::move(a_Entities);
m_BlockEntities = std::move(a_BlockEntities);
}
void cSetChunkData::CalculateHeightMap(void)
{
// Find the heighest present section in the chunk
size_t MaxSection = 0;
for (size_t i = cChunkData::NumSections - 1; i != 0; --i)
{
if (m_ChunkData.GetSection(i) != nullptr)
{
MaxSection = i;
break;
}
}
const int MaxHeight = static_cast<int>(MaxSection + 1) * cChunkData::SectionHeight - 1;
for (int x = 0; x < cChunkDef::Width; x++)
{
for (int z = 0; z < cChunkDef::Width; z++)
{
for (int y = MaxHeight; y > -1; y--)
{
if (m_ChunkData.GetBlock({x, y, z}) != E_BLOCK_AIR)
{
m_HeightMap[x + z * cChunkDef::Width] = static_cast<HEIGHTTYPE>(y);
break;
}
} // for y
} // for z
} // for x
m_IsHeightMapValid = true;
}
void cSetChunkData::RemoveInvalidBlockEntities(void)
{
for (cBlockEntities::iterator itr = m_BlockEntities.begin(); itr != m_BlockEntities.end();)
{
auto & BlockEntity = itr->second;
BLOCKTYPE EntityBlockType = BlockEntity->GetBlockType();
BLOCKTYPE WorldBlockType = m_ChunkData.GetBlock({BlockEntity->GetRelX(), BlockEntity->GetPosY(), BlockEntity->GetRelZ()});
if (EntityBlockType != WorldBlockType)
{
// Bad blocktype, remove the block entity:
FLOGD("Block entity blocktype mismatch at {0}: entity for blocktype {1}({2}) in block {3}({4}). Deleting the block entity.",
BlockEntity->GetPos(),
ItemTypeToString(EntityBlockType), EntityBlockType,
ItemTypeToString(WorldBlockType), WorldBlockType
);
itr = m_BlockEntities.erase(itr);
}
else
{
// Good blocktype, keep the block entity:
++itr;
}
} // for itr - m_BlockEntities[]
}

View File

@ -1,7 +1,7 @@
// SetChunkData.h
// Declares the cSetChunkData class used for sending loaded / generated chunk data into cWorld
// Defines the SetChunkData struct that contains the data for a loaded / generated chunk, ready to be set
#pragma once
@ -12,94 +12,25 @@
class cSetChunkData
struct SetChunkData
{
public:
/** Constructs a new instance with empty data.
Allocates new buffers for the block data.
Prefer to use this constructor, then fill the object with data and then send it to cWorld, as this will
reduce the copying required to queue the set operation. */
cSetChunkData(int a_ChunkX, int a_ChunkZ, bool a_ShouldMarkDirty);
/** Initialise the structure with chunk coordinates.
The caller is responsible for initialising the remaining members. */
SetChunkData(const cChunkCoords a_Chunk) :
Chunk(a_Chunk)
{
}
/** Constructs a new instance based on data existing elsewhere, will copy all the memory. Prefer to use the
other constructor as much as possible.
Will move the entity list and blockentities into the internal storage, and invalidate a_Entities and
a_BlockEntities.
When passing an lvalue, a_Entities and a_BlockEntities must be explicitly converted to an rvalue beforehand
with std::move().
a_BlockTypes and a_BlockMetas must always be valid.
If either of the light arrays are nullptr, the chunk data will be marked as not having any light at all and
will be scheduled for re-lighting once it is set into the chunkmap.
If a_Biomes is not valid, the internal flag is set and the world will calculate the biomes using the chunk
generator when setting the chunk data.
If a_HeightMap is not assigned, the world will calculate the heightmap based on the blocktypes when setting
the chunk data. */
cSetChunkData(
int a_ChunkX, int a_ChunkZ,
const BLOCKTYPE * a_BlockTypes,
const NIBBLETYPE * a_BlockMetas,
const NIBBLETYPE * a_BlockLight,
const NIBBLETYPE * a_SkyLight,
const cChunkDef::HeightMap * a_HeightMap,
const cChunkDef::BiomeMap * a_Biomes,
cEntityList && a_Entities,
cBlockEntities && a_BlockEntities,
bool a_ShouldMarkDirty
);
cChunkCoords Chunk;
int GetChunkX(void) const { return m_ChunkX; }
int GetChunkZ(void) const { return m_ChunkZ; }
ChunkBlockData BlockData;
ChunkLightData LightData;
/** Returns the internal storage of block types, metas and lighting. */
cChunkData & GetChunkData(void) { return m_ChunkData; }
cChunkDef::BiomeMap BiomeMap;
cChunkDef::HeightMap HeightMap;
/** Returns the internal storage for heightmap, read-only. */
const cChunkDef::HeightMap & GetHeightMap(void) const { return m_HeightMap; }
cEntityList Entities;
cBlockEntities BlockEntities;
/** Returns the internal storage for biomes, read-write. */
cChunkDef::BiomeMap & GetBiomes(void) { return m_Biomes; }
/** Returns the internal storage for entities, read-write. */
cEntityList & GetEntities(void) { return m_Entities; }
/** Returns the internal storage for block entities, read-write. */
cBlockEntities & GetBlockEntities(void) { return m_BlockEntities; }
/** Returns whether both light arrays stored in this object are valid. */
bool IsLightValid(void) const { return m_IsLightValid; }
/** Returns whether the heightmap stored in this object is valid. */
bool IsHeightMapValid(void) const { return m_IsHeightMapValid; }
/** Returns whether the biomes stored in this object are valid. */
bool AreBiomesValid(void) const { return m_AreBiomesValid; }
/** Returns whether the chunk should be marked as dirty after its data is set.
Used by the generator to save chunks after generating. */
bool ShouldMarkDirty(void) const { return m_ShouldMarkDirty; }
/** Marks the biomes stored in this object as valid. */
void MarkBiomesValid(void) { m_AreBiomesValid = true; }
/** Calculates the heightmap based on the contained blocktypes and marks it valid. */
void CalculateHeightMap(void);
/** Removes the block entities that don't have a proper blocktype at their corresponding coords. */
void RemoveInvalidBlockEntities(void);
protected:
int m_ChunkX;
int m_ChunkZ;
cListAllocationPool<cChunkData::sChunkSection> m_Pool;
cChunkData m_ChunkData;
cChunkDef::HeightMap m_HeightMap;
cChunkDef::BiomeMap m_Biomes;
cEntityList m_Entities;
cBlockEntities m_BlockEntities;
bool m_IsLightValid;
bool m_IsHeightMapValid;
bool m_AreBiomesValid;
bool m_ShouldMarkDirty;
bool IsLightValid;
};

View File

@ -160,6 +160,7 @@ namespace RedstoneWireHandler
if (
// IsYMTerracingBlocked (i.e. check block above lower terracing position, a.k.a. just the plain adjacent)
(!cBlockInfo::IsSolid(LateralBlock) || cBlockInfo::IsTransparent(LateralBlock)) &&
(Adjacent.y > 0) &&
(NeighbourChunk->GetBlock(Adjacent + OffsetYM) == E_BLOCK_REDSTONE_WIRE) // Only terrace YM with another wire
)
{

View File

@ -962,17 +962,6 @@ void cWorld::Tick(std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_La
// Call the plugins
cPluginManager::Get()->CallHookWorldTick(*this, a_Dt, a_LastTickDurationMSec);
// Set any chunk data that has been queued for setting:
cSetChunkDataPtrs SetChunkDataQueue;
{
cCSLock Lock(m_CSSetChunkDataQueue);
std::swap(SetChunkDataQueue, m_SetChunkDataQueue);
}
for (cSetChunkDataPtrs::iterator itr = SetChunkDataQueue.begin(), end = SetChunkDataQueue.end(); itr != end; ++itr)
{
SetChunkData(**itr);
} // for itr - SetChunkDataQueue[]
m_WorldAge += a_Dt;
if (m_IsDaylightCycleEnabled)
@ -997,6 +986,7 @@ void cWorld::Tick(std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_La
}
}
TickQueuedChunkDataSets();
TickQueuedBlocks();
m_ChunkMap.Tick(a_Dt);
TickMobs(a_Dt);
@ -1151,6 +1141,48 @@ void cWorld::TickMobs(std::chrono::milliseconds a_Dt)
void cWorld::TickQueuedChunkDataSets()
{
decltype(m_SetChunkDataQueue) SetChunkDataQueue;
{
cCSLock Lock(m_CSSetChunkDataQueue);
SetChunkDataQueue = std::move(m_SetChunkDataQueue);
}
// Set any chunk data that has been queued for setting:
for (auto & Item : SetChunkDataQueue)
{
// A copy of the chunk coordinates since we're moving Item.
const auto Chunk = Item.Chunk;
// Set the data:
m_ChunkMap.SetChunkData(std::move(Item));
// If a client is requesting this chunk, send it to them:
cChunkSender & ChunkSender = m_ChunkSender;
DoWithChunk(
Chunk.m_ChunkX, Chunk.m_ChunkZ,
[&ChunkSender](cChunk & a_Chunk)
{
if (a_Chunk.HasAnyClients())
{
ChunkSender.QueueSendChunkTo(
a_Chunk.GetPosX(),
a_Chunk.GetPosZ(),
cChunkSender::Priority::Medium,
a_Chunk.GetAllClients()
);
}
return true;
}
);
} // for itr - SetChunkDataQueue[]
}
void cWorld::TickQueuedEntityAdditions(void)
{
decltype(m_EntitiesToAdd) EntitiesToAdd;
@ -2236,22 +2268,8 @@ void cWorld::MarkChunkSaved (int a_ChunkX, int a_ChunkZ)
void cWorld::QueueSetChunkData(cSetChunkDataPtr a_SetChunkData)
void cWorld::QueueSetChunkData(struct SetChunkData && a_SetChunkData)
{
// Validate biomes, if needed:
if (!a_SetChunkData->AreBiomesValid())
{
// The biomes are not assigned, get them from the generator:
m_Generator.GenerateBiomes({a_SetChunkData->GetChunkX(), a_SetChunkData->GetChunkZ()}, a_SetChunkData->GetBiomes());
a_SetChunkData->MarkBiomesValid();
}
// Validate heightmap, if needed:
if (!a_SetChunkData->IsHeightMapValid())
{
a_SetChunkData->CalculateHeightMap();
}
// Store a copy of the data in the queue:
// TODO: If the queue is too large, wait for it to get processed. Not likely, though.
cCSLock Lock(m_CSSetChunkDataQueue);
@ -2262,39 +2280,6 @@ void cWorld::QueueSetChunkData(cSetChunkDataPtr a_SetChunkData)
void cWorld::SetChunkData(cSetChunkData & a_SetChunkData)
{
ASSERT(a_SetChunkData.AreBiomesValid());
ASSERT(a_SetChunkData.IsHeightMapValid());
m_ChunkMap.SetChunkData(a_SetChunkData);
// If a client is requesting this chunk, send it to them:
int ChunkX = a_SetChunkData.GetChunkX();
int ChunkZ = a_SetChunkData.GetChunkZ();
cChunkSender & ChunkSender = m_ChunkSender;
DoWithChunk(
ChunkX, ChunkZ,
[&ChunkSender] (cChunk & a_Chunk) -> bool
{
if (a_Chunk.HasAnyClients())
{
ChunkSender.QueueSendChunkTo(
a_Chunk.GetPosX(),
a_Chunk.GetPosZ(),
cChunkSender::Priority::Medium,
a_Chunk.GetAllClients()
);
}
return true;
}
);
}
void cWorld::ChunkLighted(
int a_ChunkX, int a_ChunkZ,
const cChunkDef::BlockNibbles & a_BlockLight,
@ -2317,15 +2302,6 @@ bool cWorld::GetChunkData(cChunkCoords a_Coords, cChunkDataCallback & a_Callback
bool cWorld::GetChunkBlockTypes(int a_ChunkX, int a_ChunkZ, BLOCKTYPE * a_BlockTypes)
{
return m_ChunkMap.GetChunkBlockTypes(a_ChunkX, a_ChunkZ, a_BlockTypes);
}
bool cWorld::IsChunkQueued(int a_ChunkX, int a_ChunkZ) const
{
return m_ChunkMap.IsChunkQueued(a_ChunkX, a_ChunkZ);
@ -3260,16 +3236,20 @@ void cWorld::cChunkGeneratorCallbacks::OnChunkGenerated(cChunkDesc & a_ChunkDesc
cChunkDef::BlockNibbles BlockMetas;
a_ChunkDesc.CompressBlockMetas(BlockMetas);
auto SetChunkData = std::make_unique<cSetChunkData>(
a_ChunkDesc.GetChunkX(), a_ChunkDesc.GetChunkZ(),
a_ChunkDesc.GetBlockTypes(), BlockMetas,
nullptr, nullptr, // We don't have lighting, chunk will be lighted when needed
&a_ChunkDesc.GetHeightMap(), &a_ChunkDesc.GetBiomeMap(),
std::move(a_ChunkDesc.GetEntities()), std::move(a_ChunkDesc.GetBlockEntities()),
true
);
SetChunkData->RemoveInvalidBlockEntities();
m_World->QueueSetChunkData(std::move(SetChunkData));
struct SetChunkData Data({ a_ChunkDesc.GetChunkX(), a_ChunkDesc.GetChunkZ() });
{
Data.BlockData.SetAll(a_ChunkDesc.GetBlockTypes(), BlockMetas);
std::copy(a_ChunkDesc.GetBiomeMap(), a_ChunkDesc.GetBiomeMap() + std::size(a_ChunkDesc.GetBiomeMap()), Data.BiomeMap);
std::copy(a_ChunkDesc.GetHeightMap(), a_ChunkDesc.GetHeightMap() + std::size(a_ChunkDesc.GetHeightMap()), Data.HeightMap);
Data.Entities = std::move(a_ChunkDesc.GetEntities());
Data.BlockEntities = std::move(a_ChunkDesc.GetBlockEntities());
Data.IsLightValid = false;
}
m_World->QueueSetChunkData(std::move(Data));
}

View File

@ -49,14 +49,12 @@ class cHopperEntity;
class cNoteEntity;
class cMobHeadEntity;
class cCompositeChat;
class cSetChunkData;
class cDeadlockDetect;
class cUUID;
typedef std::list< cPlayer * > cPlayerList;
struct SetChunkData;
typedef std::unique_ptr<cSetChunkData> cSetChunkDataPtr;
typedef std::vector<cSetChunkDataPtr> cSetChunkDataPtrs;
typedef std::list< cPlayer * > cPlayerList;
@ -240,8 +238,8 @@ public:
void MarkChunkSaved (int a_ChunkX, int a_ChunkZ);
/** Puts the chunk data into a queue to be set into the chunkmap in the tick thread.
If the chunk data doesn't contain valid biomes, the biomes are calculated before adding the data into the queue. */
void QueueSetChunkData(cSetChunkDataPtr a_SetChunkData);
Modifies the a_SetChunkData - moves the entities contained in it into the queue. */
void QueueSetChunkData(SetChunkData && a_SetChunkData);
void ChunkLighted(
int a_ChunkX, int a_ChunkZ,
@ -253,9 +251,6 @@ public:
Returns true if the chunk was reported successfully, false if not (chunk not present or callback failed). */
bool GetChunkData(cChunkCoords a_Coords, cChunkDataCallback & a_Callback) const;
/** Gets the chunk's blocks, only the block types */
bool GetChunkBlockTypes(int a_ChunkX, int a_ChunkZ, BLOCKTYPE * a_BlockTypes);
/** Returns true iff the chunk is in the loader / generator queue. */
bool IsChunkQueued(int a_ChunkX, int a_ChunkZ) const;
@ -1263,7 +1258,7 @@ private:
cCriticalSection m_CSSetChunkDataQueue;
/** Queue for the chunk data to be set into m_ChunkMap by the tick thread. Protected by m_CSSetChunkDataQueue */
cSetChunkDataPtrs m_SetChunkDataQueue;
std::vector<SetChunkData> m_SetChunkDataQueue;
void Tick(std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_LastTickDurationMSec);
@ -1273,6 +1268,9 @@ private:
/** Handles the mob spawning / moving / destroying each tick */
void TickMobs(std::chrono::milliseconds a_Dt);
/** Sets the chunk data queued in the m_SetChunkDataQueue queue into their chunk. */
void TickQueuedChunkDataSets();
/** Adds the entities queued in the m_EntitiesToAdd queue into their chunk.
If the entity was a player, he is also added to the m_Players list. */
void TickQueuedEntityAdditions(void);
@ -1307,10 +1305,6 @@ private:
/** Sets mob spawning values if nonexistant to their dimension specific defaults */
void InitializeAndLoadMobSpawningValues(cIniFile & a_IniFile);
/** Sets the specified chunk data into the chunkmap. Called in the tick thread.
Modifies the a_SetChunkData - moves the entities contained in it into the chunk. */
void SetChunkData(cSetChunkData & a_SetChunkData);
/** Checks if the sapling at the specified block coord is a part of a large-tree sapling (2x2).
If so, adjusts the coords so that they point to the northwest (XM ZM) corner of the sapling area and returns true.
Returns false if not a part of large-tree sapling. */

View File

@ -637,6 +637,18 @@ void cFastNBTWriter::AddByteArray(const AString & a_Name, const char * a_Value,
void cFastNBTWriter::AddByteArray(const AString & a_Name, size_t a_NumElements, unsigned char a_Value)
{
TagCommon(a_Name, TAG_ByteArray);
UInt32 len = htonl(static_cast<UInt32>(a_NumElements));
m_Result.append(reinterpret_cast<const std::byte *>(&len), 4);
m_Result.append(a_NumElements, std::byte(a_Value));
}
void cFastNBTWriter::AddIntArray(const AString & a_Name, const Int32 * a_Value, size_t a_NumElements)
{
TagCommon(a_Name, TAG_IntArray);

View File

@ -340,6 +340,7 @@ public:
void AddDouble (const AString & a_Name, double a_Value);
void AddString (const AString & a_Name, std::string_view a_Value);
void AddByteArray(const AString & a_Name, const char * a_Value, size_t a_NumElements);
void AddByteArray(const AString & a_Name, size_t a_NumElements, unsigned char a_Value);
void AddIntArray (const AString & a_Name, const Int32 * a_Value, size_t a_NumElements);
void AddByteArray(const AString & a_Name, const AString & a_Value)

View File

@ -53,16 +53,14 @@
/** Collects and stores the chunk data via the cChunkDataCallback interface */
class SerializerCollector:
class SerializerCollector final :
public cChunkDataCopyCollector
{
public:
// The data collected from the chunk:
cChunkDef::BiomeMap mBiomes;
UInt8 mVanillaBiomes[cChunkDef::Width * cChunkDef::Width];
int mVanillaHeightMap[cChunkDef::Width * cChunkDef::Width];
bool mBiomesAreValid;
UInt8 Biomes[cChunkDef::Width * cChunkDef::Width];
int Heights[cChunkDef::Width * cChunkDef::Width];
/** True if a tag has been opened in the callbacks and not yet closed. */
bool mIsTagOpen;
@ -84,7 +82,6 @@ public:
SerializerCollector(cFastNBTWriter & aWriter):
mBiomesAreValid(false),
mIsTagOpen(false),
mHasHadEntity(false),
mHasHadBlockEntity(false),
@ -106,14 +103,13 @@ public:
virtual void HeightMap(const cChunkDef::HeightMap * a_HeightMap) override
virtual void HeightMap(const cChunkDef::HeightMap & a_HeightMap) override
{
for (int RelX = 0; RelX < cChunkDef::Width; RelX++)
for (int RelZ = 0; RelZ < cChunkDef::Width; RelZ++)
{
for (int RelZ = 0; RelZ < cChunkDef::Width; RelZ++)
for (int RelX = 0; RelX < cChunkDef::Width; RelX++)
{
int Height = cChunkDef::GetHeight(*a_HeightMap, RelX, RelZ);
mVanillaHeightMap[(RelZ << 4) | RelX] = Height;
Heights[RelX + RelZ * cChunkDef::Width] = cChunkDef::GetHeight(a_HeightMap, RelX, RelZ);
}
}
}
@ -122,15 +118,14 @@ public:
virtual void BiomeData(const cChunkDef::BiomeMap * a_BiomeMap) override
virtual void BiomeMap(const cChunkDef::BiomeMap & a_BiomeMap) override
{
memcpy(mBiomes, a_BiomeMap, sizeof(mBiomes));
for (size_t i = 0; i < ARRAYCOUNT(mBiomes); i++)
for (size_t i = 0; i < ARRAYCOUNT(Biomes); i++)
{
if ((*a_BiomeMap)[i] < 255)
if (a_BiomeMap[i] < 255)
{
// Normal MC biome, copy as-is:
mVanillaBiomes[i] = static_cast<Byte>((*a_BiomeMap)[i]);
Biomes[i] = static_cast<Byte>(a_BiomeMap[i]);
}
else
{
@ -139,7 +134,6 @@ public:
return;
}
} // for i - mBiomeMap[]
mBiomesAreValid = true;
}
@ -252,13 +246,6 @@ public:
mWriter.EndList();
}
// If light not valid, reset it to defaults:
if (!mIsLightValid)
{
m_Data.FillBlockLight(0x00);
m_Data.FillSkyLight(0x0f);
}
// Check if "Entity" and "TileEntities" lists exists. MCEdit requires this.
if (!mHasHadEntity)
{
@ -1187,40 +1174,57 @@ void NBTChunkSerializer::Serialize(const cWorld & aWorld, cChunkCoords aCoords,
ASSERT(Result);
serializer.Finish(); // Close NBT tags
// Save biomes, both MCS (IntArray) and MC-vanilla (ByteArray):
if (serializer.mBiomesAreValid)
{
aWriter.AddByteArray("Biomes", reinterpret_cast<const char *>(serializer.mVanillaBiomes), ARRAYCOUNT(serializer.mVanillaBiomes));
aWriter.AddIntArray ("MCSBiomes", reinterpret_cast<const int *>(serializer.mBiomes), ARRAYCOUNT(serializer.mBiomes));
}
// Save biomes:
aWriter.AddByteArray("Biomes", reinterpret_cast<const char *>(serializer.Biomes), ARRAYCOUNT(serializer.Biomes));
// Save heightmap (Vanilla require this):
aWriter.AddIntArray("HeightMap", reinterpret_cast<const int *>(serializer.mVanillaHeightMap), ARRAYCOUNT(serializer.mVanillaHeightMap));
aWriter.AddIntArray("HeightMap", reinterpret_cast<const int *>(serializer.Heights), ARRAYCOUNT(serializer.Heights));
// Save blockdata:
aWriter.BeginList("Sections", TAG_Compound);
for (size_t Y = 0; Y != cChunkData::NumSections; ++Y)
ChunkDef::ForEachSection(serializer.m_BlockData, serializer.m_LightData, [&aWriter](const auto Y, const auto Blocks, const auto Metas, const auto BlockLights, const auto SkyLights)
{
auto section = serializer.m_Data.GetSection(Y);
if (section == nullptr)
aWriter.BeginCompound("");
if (Blocks != nullptr)
{
continue;
aWriter.AddByteArray("Blocks", reinterpret_cast<const char *>(Blocks->data()), Blocks->size());
}
else
{
aWriter.AddByteArray("Blocks", ChunkBlockData::SectionBlockCount, ChunkBlockData::DefaultValue);
}
aWriter.BeginCompound("");
aWriter.AddByteArray("Blocks", reinterpret_cast<const char *>(section->m_BlockTypes), ARRAYCOUNT(section->m_BlockTypes));
aWriter.AddByteArray("Data", reinterpret_cast<const char *>(section->m_BlockMetas), ARRAYCOUNT(section->m_BlockMetas));
if (Metas != nullptr)
{
aWriter.AddByteArray("Data", reinterpret_cast<const char *>(Metas->data()), Metas->size());
}
else
{
aWriter.AddByteArray("Data", ChunkBlockData::SectionMetaCount, ChunkBlockData::DefaultMetaValue);
}
#ifdef DEBUG_SKYLIGHT
aWriter.AddByteArray("BlockLight", reinterpret_cast<const char *>(section->m_BlockSkyLight), ARRAYCOUNT(section->m_BlockSkyLight));
#else
aWriter.AddByteArray("BlockLight", reinterpret_cast<const char *>(section->m_BlockLight), ARRAYCOUNT(section->m_BlockLight));
#endif
if (BlockLights != nullptr)
{
aWriter.AddByteArray("BlockLight", reinterpret_cast<const char *>(BlockLights->data()), BlockLights->size());
}
else
{
aWriter.AddByteArray("BlockLight", ChunkLightData::SectionLightCount, ChunkLightData::DefaultBlockLightValue);
}
if (SkyLights != nullptr)
{
aWriter.AddByteArray("SkyLight", reinterpret_cast<const char *>(SkyLights->data()), SkyLights->size());
}
else
{
aWriter.AddByteArray("SkyLight", ChunkLightData::SectionLightCount, ChunkLightData::DefaultSkyLightValue);
}
aWriter.AddByteArray("SkyLight", reinterpret_cast<const char *>(section->m_BlockSkyLight), ARRAYCOUNT(section->m_BlockSkyLight));
aWriter.AddByte("Y", static_cast<unsigned char>(Y));
aWriter.EndCompound();
}
});
aWriter.EndList(); // "Sections"
// Store the information that the lighting is valid.

View File

@ -353,16 +353,7 @@ Compression::Result cWSSAnvil::SaveChunkToData(const cChunkCoords & a_Chunk)
bool cWSSAnvil::LoadChunkFromNBT(const cChunkCoords & a_Chunk, const cParsedNBT & a_NBT, const ContiguousByteBufferView a_RawChunkData)
{
// The data arrays, in MCA-native y / z / x ordering (will be reordered for the final chunk data)
cChunkDef::BlockTypes BlockTypes;
cChunkDef::BlockNibbles MetaData;
cChunkDef::BlockNibbles BlockLight;
cChunkDef::BlockNibbles SkyLight;
memset(BlockTypes, 0, sizeof(BlockTypes));
memset(MetaData, 0, sizeof(MetaData));
memset(SkyLight, 0xff, sizeof(SkyLight)); // By default, data not present in the NBT means air, which means full skylight
memset(BlockLight, 0x00, sizeof(BlockLight));
struct SetChunkData Data(a_Chunk);
// Load the blockdata, blocklight and skylight:
int Level = a_NBT.FindChildByName(0, "Level");
@ -371,12 +362,14 @@ bool cWSSAnvil::LoadChunkFromNBT(const cChunkCoords & a_Chunk, const cParsedNBT
ChunkLoadFailed(a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, "Missing NBT tag: Level", a_RawChunkData);
return false;
}
int Sections = a_NBT.FindChildByName(Level, "Sections");
if ((Sections < 0) || (a_NBT.GetType(Sections) != TAG_List))
{
ChunkLoadFailed(a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, "Missing NBT tag: Sections", a_RawChunkData);
return false;
}
eTagType SectionsType = a_NBT.GetChildrenType(Sections);
if ((SectionsType != TAG_Compound) && (SectionsType != TAG_End))
{
@ -385,39 +378,56 @@ bool cWSSAnvil::LoadChunkFromNBT(const cChunkCoords & a_Chunk, const cParsedNBT
}
for (int Child = a_NBT.GetFirstChild(Sections); Child >= 0; Child = a_NBT.GetNextSibling(Child))
{
int y = 0;
int SectionY = a_NBT.FindChildByName(Child, "Y");
if ((SectionY < 0) || (a_NBT.GetType(SectionY) != TAG_Byte))
const int SectionYTag = a_NBT.FindChildByName(Child, "Y");
if ((SectionYTag < 0) || (a_NBT.GetType(SectionYTag) != TAG_Byte))
{
continue;
ChunkLoadFailed(a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, "NBT tag missing or has wrong: Y", a_RawChunkData);
return false;
}
y = a_NBT.GetByte(SectionY);
if ((y < 0) || (y > 15))
const int Y = a_NBT.GetByte(SectionYTag);
if ((Y < 0) || (Y > static_cast<int>(cChunkDef::NumSections - 1)))
{
continue;
ChunkLoadFailed(a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, "NBT tag exceeds chunk bounds: Y", a_RawChunkData);
return false;
}
const auto
BlockData = GetSectionData(a_NBT, Child, "Blocks", ChunkBlockData::SectionBlockCount),
MetaData = GetSectionData(a_NBT, Child, "Data", ChunkBlockData::SectionMetaCount),
BlockLightData = GetSectionData(a_NBT, Child, "BlockLight", ChunkLightData::SectionLightCount),
SkyLightData = GetSectionData(a_NBT, Child, "SkyLight", ChunkLightData::SectionLightCount);
if ((BlockData != nullptr) && (MetaData != nullptr) && (SkyLightData != nullptr) && (BlockLightData != nullptr))
{
Data.BlockData.SetSection(*reinterpret_cast<const ChunkBlockData::SectionType *>(BlockData), *reinterpret_cast<const ChunkBlockData::SectionMetaType *>(MetaData), static_cast<size_t>(Y));
Data.LightData.SetSection(*reinterpret_cast<const ChunkLightData::SectionType *>(BlockLightData), *reinterpret_cast<const ChunkLightData::SectionType *>(SkyLightData), static_cast<size_t>(Y));
}
else
{
ChunkLoadFailed(a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, "Missing chunk block/light data", a_RawChunkData);
return false;
}
CopyNBTData(a_NBT, Child, "Blocks", reinterpret_cast<char *>(&(BlockTypes[y * 4096])), 4096);
CopyNBTData(a_NBT, Child, "Data", reinterpret_cast<char *>(&(MetaData[y * 2048])), 2048);
CopyNBTData(a_NBT, Child, "SkyLight", reinterpret_cast<char *>(&(SkyLight[y * 2048])), 2048);
CopyNBTData(a_NBT, Child, "BlockLight", reinterpret_cast<char *>(&(BlockLight[y * 2048])), 2048);
} // for itr - LevelSections[]
// Load the biomes from NBT, if present and valid. First try MCS-style, then Vanilla-style:
cChunkDef::BiomeMap BiomeMap;
cChunkDef::BiomeMap * Biomes = LoadBiomeMapFromNBT(&BiomeMap, a_NBT, a_NBT.FindChildByName(Level, "MCSBiomes"));
if (Biomes == nullptr)
// Load the biomes from NBT, if present and valid:
if (!LoadBiomeMapFromNBT(Data.BiomeMap, a_NBT, a_NBT.FindChildByName(Level, "Biomes")))
{
// MCS-style biomes not available, load vanilla-style:
Biomes = LoadVanillaBiomeMapFromNBT(&BiomeMap, a_NBT, a_NBT.FindChildByName(Level, "Biomes"));
ChunkLoadFailed(a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, "Missing chunk biome data", a_RawChunkData);
return false;
}
// Height map too:
if (!LoadHeightMapFromNBT(Data.HeightMap, a_NBT, a_NBT.FindChildByName(Level, "HeightMap")))
{
ChunkLoadFailed(a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ, "Missing chunk height data", a_RawChunkData);
return false;
}
// Load the entities from NBT:
cEntityList Entities;
cBlockEntities BlockEntities;
LoadEntitiesFromNBT (Entities, a_NBT, a_NBT.FindChildByName(Level, "Entities"));
LoadBlockEntitiesFromNBT(BlockEntities, a_NBT, a_NBT.FindChildByName(Level, "TileEntities"), BlockTypes, MetaData);
LoadEntitiesFromNBT (Data.Entities, a_NBT, a_NBT.FindChildByName(Level, "Entities"));
LoadBlockEntitiesFromNBT(Data.BlockEntities, a_NBT, a_NBT.FindChildByName(Level, "TileEntities"), Data.BlockData);
bool IsLightValid = (a_NBT.FindChildByName(Level, "MCSIsLightValid") > 0);
Data.IsLightValid = (a_NBT.FindChildByName(Level, "MCSIsLightValid") > 0);
/*
// Uncomment this block for really cool stuff :)
@ -455,16 +465,7 @@ bool cWSSAnvil::LoadChunkFromNBT(const cChunkCoords & a_Chunk, const cParsedNBT
} // for y
//*/
auto SetChunkData = std::make_unique<cSetChunkData>(
a_Chunk.m_ChunkX, a_Chunk.m_ChunkZ,
BlockTypes, MetaData,
IsLightValid ? BlockLight : nullptr,
IsLightValid ? SkyLight : nullptr,
nullptr, Biomes,
std::move(Entities), std::move(BlockEntities),
false
);
m_World->QueueSetChunkData(std::move(SetChunkData));
m_World->QueueSetChunkData(std::move(Data));
return true;
}
@ -472,69 +473,66 @@ bool cWSSAnvil::LoadChunkFromNBT(const cChunkCoords & a_Chunk, const cParsedNBT
void cWSSAnvil::CopyNBTData(const cParsedNBT & a_NBT, int a_Tag, const AString & a_ChildName, char * a_Destination, size_t a_Length)
bool cWSSAnvil::LoadBiomeMapFromNBT(cChunkDef::BiomeMap & a_BiomeMap, const cParsedNBT & a_NBT, const int a_TagIdx)
{
int Child = a_NBT.FindChildByName(a_Tag, a_ChildName);
if ((Child >= 0) && (a_NBT.GetType(Child) == TAG_ByteArray) && (a_NBT.GetDataLength(Child) == a_Length))
if (
(a_TagIdx < 0) ||
(a_NBT.GetType(a_TagIdx) != TAG_ByteArray) ||
(a_NBT.GetDataLength(a_TagIdx) != std::size(a_BiomeMap))
)
{
memcpy(a_Destination, a_NBT.GetData(Child), a_Length);
return false;
}
const auto * const BiomeData = a_NBT.GetData(a_TagIdx);
for (size_t i = 0; i < ARRAYCOUNT(a_BiomeMap); i++)
{
if (BiomeData[i] > std::byte(EMCSBiome::biMaxVariantBiome))
{
// Unassigned biomes:
return false;
}
a_BiomeMap[i] = static_cast<EMCSBiome>(BiomeData[i]);
}
return true;
}
cChunkDef::BiomeMap * cWSSAnvil::LoadVanillaBiomeMapFromNBT(cChunkDef::BiomeMap * a_BiomeMap, const cParsedNBT & a_NBT, int a_TagIdx)
bool cWSSAnvil::LoadHeightMapFromNBT(cChunkDef::HeightMap & a_HeightMap, const cParsedNBT & a_NBT, const int a_TagIdx)
{
if ((a_TagIdx < 0) || (a_NBT.GetType(a_TagIdx) != TAG_ByteArray))
if (
(a_TagIdx < 0) ||
(a_NBT.GetType(a_TagIdx) != TAG_IntArray) ||
(a_NBT.GetDataLength(a_TagIdx) != (4 * std::size(a_HeightMap)))
)
{
return nullptr;
return false;
}
if (a_NBT.GetDataLength(a_TagIdx) != 16 * 16)
const auto * const HeightData = a_NBT.GetData(a_TagIdx);
for (int RelZ = 0; RelZ < cChunkDef::Width; RelZ++)
{
// The biomes stored don't match in size
return nullptr;
}
const unsigned char * VanillaBiomeData = reinterpret_cast<const unsigned char *>(a_NBT.GetData(a_TagIdx));
for (size_t i = 0; i < ARRAYCOUNT(*a_BiomeMap); i++)
{
if ((VanillaBiomeData)[i] == 0xff)
for (int RelX = 0; RelX < cChunkDef::Width; RelX++)
{
// Unassigned biomes
return nullptr;
}
(*a_BiomeMap)[i] = static_cast<EMCSBiome>(VanillaBiomeData[i]);
}
return a_BiomeMap;
}
const int Index = 4 * (RelX + RelZ * cChunkDef::Width);
const int Height = GetBEInt(HeightData + Index);
if (Height > std::numeric_limits<HEIGHTTYPE>::max())
{
// Invalid data:
return false;
}
cChunkDef::BiomeMap * cWSSAnvil::LoadBiomeMapFromNBT(cChunkDef::BiomeMap * a_BiomeMap, const cParsedNBT & a_NBT, int a_TagIdx)
{
if ((a_TagIdx < 0) || (a_NBT.GetType(a_TagIdx) != TAG_IntArray))
{
return nullptr;
}
if (a_NBT.GetDataLength(a_TagIdx) != sizeof(*a_BiomeMap))
{
// The biomes stored don't match in size
return nullptr;
}
const auto * BiomeData = a_NBT.GetData(a_TagIdx);
for (size_t i = 0; i < ARRAYCOUNT(*a_BiomeMap); i++)
{
(*a_BiomeMap)[i] = static_cast<EMCSBiome>(GetBEInt(&BiomeData[i * 4]));
if ((*a_BiomeMap)[i] == 0xff)
{
// Unassigned biomes
return nullptr;
cChunkDef::SetHeight(a_HeightMap, RelX, RelZ, static_cast<HEIGHTTYPE>(Height));
}
}
return a_BiomeMap;
return true;
}
@ -575,7 +573,7 @@ void cWSSAnvil::LoadEntitiesFromNBT(cEntityList & a_Entities, const cParsedNBT &
void cWSSAnvil::LoadBlockEntitiesFromNBT(cBlockEntities & a_BlockEntities, const cParsedNBT & a_NBT, int a_TagIdx, BLOCKTYPE * a_BlockTypes, NIBBLETYPE * a_BlockMetas)
void cWSSAnvil::LoadBlockEntitiesFromNBT(cBlockEntities & a_BlockEntities, const cParsedNBT & a_NBT, int a_TagIdx, const ChunkBlockData & a_BlockData)
{
if ((a_TagIdx < 0) || (a_NBT.GetType(a_TagIdx) != TAG_List))
{
@ -596,11 +594,11 @@ void cWSSAnvil::LoadBlockEntitiesFromNBT(cBlockEntities & a_BlockEntities, const
LOGWARNING("Bad block entity, missing the coords. Will be ignored.");
continue;
}
auto relPos = cChunkDef::AbsoluteToRelative(absPos);
const auto relPos = cChunkDef::AbsoluteToRelative(absPos);
// Load the proper BlockEntity type based on the block type:
BLOCKTYPE BlockType = cChunkDef::GetBlock(a_BlockTypes, relPos);
NIBBLETYPE BlockMeta = cChunkDef::GetNibble(a_BlockMetas, relPos);
const auto BlockType = a_BlockData.GetBlock(relPos);
const auto BlockMeta = a_BlockData.GetMeta(relPos);
OwnedBlockEntity Entity;
try
@ -3990,6 +3988,20 @@ bool cWSSAnvil::cMCAFile::GetChunkData(const cChunkCoords & a_Chunk, ContiguousB
const std::byte * cWSSAnvil::GetSectionData(const cParsedNBT & a_NBT, int a_Tag, const AString & a_ChildName, size_t a_Length)
{
int Child = a_NBT.FindChildByName(a_Tag, a_ChildName);
if ((Child >= 0) && (a_NBT.GetType(Child) == TAG_ByteArray) && (a_NBT.GetDataLength(Child) == a_Length))
{
return a_NBT.GetData(Child);
}
return nullptr;
}
bool cWSSAnvil::cMCAFile::SetChunkData(const cChunkCoords & a_Chunk, const ContiguousByteBufferView a_Data)
{
if (!OpenFile(false))

View File

@ -24,6 +24,7 @@ class cMonster;
class cProjectileEntity;
class cHangingEntity;
class cUUID;
class ChunkBlockData;
@ -106,6 +107,9 @@ protected:
/** Gets chunk data from the correct file; locks file CS as needed */
bool GetChunkData(const cChunkCoords & a_Chunk, ContiguousByteBuffer & a_Data);
/** Copies a_Length bytes of data from the specified NBT Tag's Child into the a_Destination buffer */
const std::byte * GetSectionData(const cParsedNBT & a_NBT, int a_Tag, const AString & a_ChildName, size_t a_Length);
/** Sets chunk data into the correct file; locks file CS as needed */
bool SetChunkData(const cChunkCoords & a_Chunk, ContiguousByteBufferView a_Data);
@ -119,17 +123,17 @@ protected:
a_RawChunkData is the raw (compressed) chunk data, used for offloading when chunk loading fails. */
bool LoadChunkFromNBT(const cChunkCoords & a_Chunk, const cParsedNBT & a_NBT, ContiguousByteBufferView a_RawChunkData);
/** Loads the chunk's biome map from vanilla-format; returns a_BiomeMap if biomes present and valid, nullptr otherwise */
cChunkDef::BiomeMap * LoadVanillaBiomeMapFromNBT(cChunkDef::BiomeMap * a_BiomeMap, const cParsedNBT & a_NBT, int a_TagIdx);
/** Loads the chunk's biome map into a_BiomeMap if biomes present and valid; returns false otherwise. */
bool LoadBiomeMapFromNBT(cChunkDef::BiomeMap & a_BiomeMap, const cParsedNBT & a_NBT, int a_TagIdx);
/** Loads the chunk's biome map from MCS format; returns a_BiomeMap if biomes present and valid, nullptr otherwise */
cChunkDef::BiomeMap * LoadBiomeMapFromNBT(cChunkDef::BiomeMap * a_BiomeMap, const cParsedNBT & a_NBT, int a_TagIdx);
/** Loads the chunk's height map into a_HeightMap if heights present and valid; returns false otherwise. */
bool LoadHeightMapFromNBT(cChunkDef::HeightMap & a_HeightMap, const cParsedNBT & a_NBT, int a_TagIdx);
/** 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);
/** Loads the chunk's BlockEntities from NBT data (a_Tag is the Level\\TileEntities list tag; may be -1) */
void LoadBlockEntitiesFromNBT(cBlockEntities & a_BlockEntitites, const cParsedNBT & a_NBT, int a_Tag, BLOCKTYPE * a_BlockTypes, NIBBLETYPE * a_BlockMetas);
void LoadBlockEntitiesFromNBT(cBlockEntities & a_BlockEntitites, const cParsedNBT & a_NBT, int a_Tag, const ChunkBlockData & a_BlockData);
/** Loads the data for a block entity from the specified NBT tag.
Returns the loaded block entity, or nullptr upon failure. */
@ -298,9 +302,6 @@ protected:
/** Gets the correct MCA file either from cache or from disk, manages the m_MCAFiles cache; assumes m_CS is locked */
cMCAFile * LoadMCAFile(const cChunkCoords & a_Chunk);
/** Copies a_Length bytes of data from the specified NBT Tag's Child into the a_Destination buffer */
void CopyNBTData(const cParsedNBT & a_NBT, int a_Tag, const AString & a_ChildName, char * a_Destination, size_t a_Length);
// cWSSchema overrides:
virtual bool LoadChunk(const cChunkCoords & a_Chunk) override;
virtual bool SaveChunk(const cChunkCoords & a_Chunk) override;

View File

@ -8,100 +8,99 @@
/** Performs the entire ArrayToCoords test. */
static void test()
{
class cMockAllocationPool
: public cAllocationPool<cChunkData::sChunkSection>
{
virtual cChunkData::sChunkSection * Allocate() override
{
return new cChunkData::sChunkSection();
}
virtual void Free(cChunkData::sChunkSection * a_Ptr) override
{
delete a_Ptr;
}
virtual bool DoIsEqual(const cAllocationPool<cChunkData::sChunkSection> &) const noexcept override
{
return false;
}
} Pool;
{
// Test first segment
cChunkData buffer(Pool);
// Test first segment (blocks)
ChunkBlockData buffer;
BLOCKTYPE SrcBlockBuffer[16 * 16 * 256];
memset(SrcBlockBuffer, 0x00, sizeof(SrcBlockBuffer));
SrcBlockBuffer[7 + (4 * 16) + (5 * 16 * 16)] = 0xcd;
buffer.SetBlockTypes(SrcBlockBuffer);
TEST_EQUAL(buffer.GetBlock({ 7, 5, 4 }), 0xcd);
NIBBLETYPE SrcNibbleBuffer[16 * 16 * 256 / 2];
memset(SrcNibbleBuffer, 0x00, sizeof(SrcNibbleBuffer));
SrcNibbleBuffer[(6 + (1 * 16) + (2 * 16 * 16)) / 2] = 0xe;
buffer.SetMetas(SrcNibbleBuffer);
buffer.SetAll(SrcBlockBuffer, SrcNibbleBuffer);
TEST_EQUAL(buffer.GetBlock({ 7, 5, 4 }), 0xcd);
TEST_EQUAL(buffer.GetMeta({ 6, 2, 1 }), 0xe);
}
{
// Test first segment (lights)
ChunkLightData buffer;
NIBBLETYPE SrcNibbleBuffer[16 * 16 * 256 / 2];
memset(SrcNibbleBuffer, 0x00, sizeof(SrcNibbleBuffer));
SrcNibbleBuffer[(6 + (1 * 16) + (2 * 16 * 16)) / 2] = 0xe;
buffer.SetBlockLight(SrcNibbleBuffer);
buffer.SetAll(SrcNibbleBuffer, SrcNibbleBuffer);
TEST_EQUAL(buffer.GetBlockLight({ 6, 2, 1 }), 0xe);
memset(SrcNibbleBuffer, 0x00, sizeof(SrcNibbleBuffer));
SrcNibbleBuffer[(6 + (1 * 16) + (2 * 16 * 16)) / 2] = 0xe;
buffer.SetSkyLight(SrcNibbleBuffer);
TEST_EQUAL(buffer.GetSkyLight({ 6, 2, 1 }), 0xe);
}
{
// test following segment
cChunkData buffer(Pool);
// test following segment (blocks)
ChunkBlockData buffer;
BLOCKTYPE SrcBlockBuffer[16 * 16 * 256];
memset(SrcBlockBuffer, 0x00, sizeof(SrcBlockBuffer));
SrcBlockBuffer[7 + (4 * 16) + (24 * 16 * 16)] = 0xcd;
buffer.SetBlockTypes(SrcBlockBuffer);
TEST_EQUAL(buffer.GetBlock({ 7, 24, 4 }), 0xcd);
NIBBLETYPE SrcNibbleBuffer[16 * 16 * 256 / 2];
memset(SrcNibbleBuffer, 0x00, sizeof(SrcNibbleBuffer));
SrcNibbleBuffer[(6 + (1 * 16) + (24 * 16 * 16)) / 2] = 0xe;
buffer.SetMetas(SrcNibbleBuffer);
buffer.SetAll(SrcBlockBuffer, SrcNibbleBuffer);
TEST_EQUAL(buffer.GetBlock({ 7, 24, 4 }), 0xcd);
TEST_EQUAL(buffer.GetMeta({ 6, 24, 1 }), 0xe);
}
{
// test following segment (lights)
ChunkLightData buffer;
NIBBLETYPE SrcNibbleBuffer[16 * 16 * 256 / 2];
memset(SrcNibbleBuffer, 0x00, sizeof(SrcNibbleBuffer));
SrcNibbleBuffer[(6 + 1 * 16 + 24 * 16 * 16) / 2] = 0xe;
buffer.SetBlockLight(SrcNibbleBuffer);
TEST_EQUAL(buffer.GetBlockLight({ 6, 24, 1 }), 0xe);
memset(SrcNibbleBuffer, 0xff, sizeof(SrcNibbleBuffer));
SrcNibbleBuffer[(6 + (1 * 16) + (24 * 16 * 16)) / 2] = 0xe;
buffer.SetSkyLight(SrcNibbleBuffer);
buffer.SetAll(SrcNibbleBuffer, SrcNibbleBuffer);
TEST_EQUAL(buffer.GetBlockLight({ 6, 24, 1 }), 0xe);
TEST_EQUAL(buffer.GetSkyLight({ 6, 24, 1 }), 0xe);
}
{
// test zeros
cChunkData buffer(Pool);
// test zeros (blocks)
ChunkBlockData buffer;
BLOCKTYPE SrcBlockBuffer[16 * 16 * 256];
memset(SrcBlockBuffer, 0x00, sizeof(SrcBlockBuffer));
buffer.SetBlockTypes(SrcBlockBuffer);
TEST_EQUAL(buffer.GetBlock({ 7, 24, 4 }), 0x00);
NIBBLETYPE SrcNibbleBuffer[16 * 16 * 256 / 2];
memset(SrcNibbleBuffer, 0x00, sizeof(SrcNibbleBuffer));
buffer.SetMetas(SrcNibbleBuffer);
buffer.SetAll(SrcBlockBuffer, SrcNibbleBuffer);
TEST_EQUAL(buffer.GetBlock({ 7, 24, 4 }), 0x00);
TEST_EQUAL(buffer.GetMeta({ 6, 24, 1 }), 0x0);
}
{
// test zeros (lights)
ChunkLightData buffer;
NIBBLETYPE SrcNibbleBuffer[16 * 16 * 256 / 2];
memset(SrcNibbleBuffer, 0x00, sizeof(SrcNibbleBuffer));
buffer.SetBlockLight(SrcNibbleBuffer);
TEST_EQUAL(buffer.GetBlockLight({ 6, 24, 1 }), 0x0);
memset(SrcNibbleBuffer, 0xff, sizeof(SrcNibbleBuffer));
buffer.SetSkyLight(SrcNibbleBuffer);
NIBBLETYPE SrcNibbleBuffer2[16 * 16 * 256 / 2];
memset(SrcNibbleBuffer2, 0xff, sizeof(SrcNibbleBuffer2));
buffer.SetAll(SrcNibbleBuffer, SrcNibbleBuffer2);
TEST_EQUAL(buffer.GetBlockLight({ 6, 24, 1 }), 0x0);
TEST_EQUAL(buffer.GetSkyLight({ 6, 24, 1 }), 0xf);
}
}

View File

@ -4,7 +4,7 @@ add_library(ChunkBuffer ${PROJECT_SOURCE_DIR}/src/ChunkData.cpp ${PROJECT_SOURCE
target_link_libraries(ChunkBuffer PUBLIC fmt::fmt)
add_executable(creatable-exe creatable.cpp)
add_executable(creatable-exe Creatable.cpp)
target_link_libraries(creatable-exe ChunkBuffer)
add_test(NAME creatable-test COMMAND creatable-exe)
@ -20,19 +20,11 @@ add_executable(arraystocoords-exe ArraytoCoord.cpp)
target_link_libraries(arraystocoords-exe ChunkBuffer)
add_test(NAME arraystocoords-test COMMAND arraystocoords-exe)
add_executable(copyblocks-exe CopyBlocks.cpp)
target_link_libraries(copyblocks-exe ChunkBuffer)
add_test(NAME copyblocks-test COMMAND copyblocks-exe)
# Put all test projects into a separate folder:
set_target_properties(
arraystocoords-exe
coordinates-exe
copies-exe
copyblocks-exe
creatable-exe
PROPERTIES FOLDER Tests/ChunkData
)

View File

@ -9,26 +9,8 @@
/** Performs the entire cChunkData coordinates test. */
static void test()
{
class cMockAllocationPool
: public cAllocationPool<cChunkData::sChunkSection>
{
virtual cChunkData::sChunkSection * Allocate() override
{
return new cChunkData::sChunkSection();
}
virtual void Free(cChunkData::sChunkSection * a_Ptr) override
{
delete a_Ptr;
}
virtual bool DoIsEqual(const cAllocationPool<cChunkData::sChunkSection> &) const noexcept override
{
return false;
}
} Pool;
{
cChunkData buffer(Pool);
ChunkBlockData buffer;
// Empty chunks
buffer.SetBlock({ 0, 0, 0 }, 0xAB);
@ -44,6 +26,26 @@ static void test()
TEST_EQUAL(buffer.GetBlock({ 0, 32, 0 }), 0x0);
TEST_EQUAL(buffer.GetMeta({ 0, 48, 0 }), 0x0);
// Out of range GetBlock
TEST_ASSERTS(
buffer.GetBlock({ -1, 0, 0 });
);
TEST_ASSERTS(
buffer.GetBlock({ 0, -1, 0 });
);
TEST_ASSERTS(
buffer.GetBlock({ 0, 0, -1 });
);
TEST_ASSERTS(
buffer.GetBlock({ 256, 0, 0 });
);
TEST_ASSERTS(
buffer.GetBlock({ 0, 256, 0 });
);
TEST_ASSERTS(
buffer.GetBlock({ 0, 0, 256 });
);
// Out of range SetBlock
TEST_ASSERTS(
buffer.SetBlock({ -1, 0, 0 }, 0)
@ -63,6 +65,27 @@ static void test()
TEST_ASSERTS(
buffer.SetBlock({ 0, 0, 256 }, 0)
);
// Out of range GetMeta
TEST_ASSERTS(
buffer.GetMeta({ -1, 0, 0 });
);
TEST_ASSERTS(
buffer.GetMeta({ 0, -1, 0 });
);
TEST_ASSERTS(
buffer.GetMeta({ 0, 0, -1 });
);
TEST_ASSERTS(
buffer.GetMeta({ 256, 0, 0 });
);
TEST_ASSERTS(
buffer.GetMeta({ 0, 256, 0 });
);
TEST_ASSERTS(
buffer.GetMeta({ 0, 0, 256 });
);
// Out of range SetMeta
TEST_ASSERTS(
buffer.SetMeta({ -1, 0, 0 }, 0)
@ -82,26 +105,10 @@ static void test()
TEST_ASSERTS(
buffer.SetMeta({ 0, 0, 256 }, 0)
);
// Reading out of range blocks should return air
TEST_EQUAL(buffer.GetBlock({ -1, 0, 0 }), 0);
TEST_EQUAL(buffer.GetBlock({ 0, -1, 0 }), 0);
TEST_EQUAL(buffer.GetBlock({ 0, 0, -1 }), 0);
TEST_EQUAL(buffer.GetBlock({ 256, 0, 0 }), 0);
TEST_EQUAL(buffer.GetBlock({ 0, 256, 0 }), 0);
TEST_EQUAL(buffer.GetBlock({ 0, 0, 256 }), 0);
// Reading out of range metas should return 0
TEST_EQUAL(buffer.GetMeta({ -1, 0, 0 }), 0);
TEST_EQUAL(buffer.GetMeta({ 0, -1, 0 }), 0);
TEST_EQUAL(buffer.GetMeta({ 0, 0, -1 }), 0);
TEST_EQUAL(buffer.GetMeta({ 256, 0, 0 }), 0);
TEST_EQUAL(buffer.GetMeta({ 0, 256, 0 }), 0);
TEST_EQUAL(buffer.GetMeta({ 0, 0, 256 }), 0);
}
{
cChunkData buffer(Pool);
ChunkBlockData buffer;
// Zero's
buffer.SetBlock({ 0, 0, 0 }, 0x0);
@ -118,9 +125,9 @@ static void test()
{
// Operator =
cChunkData buffer(Pool);
ChunkBlockData buffer;
buffer.SetBlock({ 0, 0, 0 }, 0x42);
cChunkData copy(Pool);
ChunkBlockData copy;
copy = std::move(buffer);
TEST_EQUAL(copy.GetBlock({ 0, 0, 0 }), 0x42);
}

View File

@ -7,41 +7,50 @@
/** Helper that copies a data store into a contiguous flat array, filling in a default value for sections that aren't present. */
template <class StoreType, typename GetType, typename DefaultType, typename OutType>
static void CopyAll(const StoreType & Data, GetType Getter, DefaultType Default, OutType & Out)
{
constexpr auto SectionCount = std::extent_v<OutType> / 16;
for (size_t Y = 0; Y != 16; Y++)
{
const auto Section = (Data.*Getter)(Y);
static_assert(SectionCount == std::tuple_size<std::remove_pointer_t<decltype(Section)>>::value, "Output array has wrong size");
if (Section == nullptr)
{
std::fill_n(Out + Y * SectionCount, SectionCount, Default);
}
else
{
std::copy(Section->begin(), Section->end(), Out + Y * SectionCount);
}
}
}
/** Performs the entire Copies test. */
static void test()
static void Test()
{
LOGD("Test started");
class cMockAllocationPool
: public cAllocationPool<cChunkData::sChunkSection>
{
virtual cChunkData::sChunkSection * Allocate() override
{
return new cChunkData::sChunkSection();
}
virtual void Free(cChunkData::sChunkSection * a_Ptr) override
{
delete a_Ptr;
}
virtual bool DoIsEqual(const cAllocationPool<cChunkData::sChunkSection>&) const noexcept override
{
return false;
}
} Pool;
{
cChunkData buffer(Pool);
ChunkBlockData buffer;
buffer.SetBlock({ 3, 1, 4 }, 0xDE);
buffer.SetMeta({ 3, 1, 4 }, 0xA);
cChunkData copy(Pool);
ChunkBlockData copy;
copy.Assign(buffer);
TEST_EQUAL(copy.GetBlock({ 3, 1, 4 }), 0xDE);
TEST_EQUAL(copy.GetMeta({ 3, 1, 4 }), 0xA);
BLOCKTYPE SrcBlockBuffer[16 * 16 * 256];
NIBBLETYPE SrcNibbleBuffer[16 * 16 * 256 / 2]{};
for (int i = 0; i < 16 * 16 * 256; i += 4)
{
SrcBlockBuffer[i + 0] = 0xde;
@ -50,20 +59,21 @@ static void test()
SrcBlockBuffer[i + 3] = 0xef;
}
buffer.SetBlockTypes(SrcBlockBuffer);
buffer.SetAll(SrcBlockBuffer, SrcNibbleBuffer);
BLOCKTYPE DstBlockBuffer[16 * 16 * 256];
buffer.CopyBlockTypes(DstBlockBuffer);
CopyAll(buffer, &ChunkBlockData::GetSection, ChunkBlockData::DefaultValue, DstBlockBuffer);
TEST_EQUAL(memcmp(SrcBlockBuffer, DstBlockBuffer, (16 * 16 * 256) - 1), 0);
memset(SrcBlockBuffer, 0x00, 16 * 16 * 256);
buffer.SetBlockTypes(SrcBlockBuffer);
buffer.CopyBlockTypes(DstBlockBuffer);
buffer.SetAll(SrcBlockBuffer, SrcNibbleBuffer);
CopyAll(buffer, &ChunkBlockData::GetSection, ChunkBlockData::DefaultValue, DstBlockBuffer);
TEST_EQUAL(memcmp(SrcBlockBuffer, DstBlockBuffer, (16 * 16 * 256) - 1), 0);
}
{
cChunkData buffer(Pool);
ChunkBlockData buffer;
BLOCKTYPE SrcBlockBuffer[16 * 16 * 256]{};
NIBBLETYPE SrcNibbleBuffer[16 * 16 * 256 / 2];
for (int i = 0; i < 16 * 16 * 256 / 2; i += 4)
{
@ -73,19 +83,19 @@ static void test()
SrcNibbleBuffer[i + 3] = 0xef;
}
buffer.SetMetas(SrcNibbleBuffer);
buffer.SetAll(SrcBlockBuffer, SrcNibbleBuffer);
NIBBLETYPE DstNibbleBuffer[16 * 16 * 256/ 2];
buffer.CopyMetas(DstNibbleBuffer);
CopyAll(buffer, &ChunkBlockData::GetMetaSection, ChunkBlockData::DefaultMetaValue, DstNibbleBuffer);
TEST_EQUAL(memcmp(SrcNibbleBuffer, DstNibbleBuffer, (16 * 16 * 256 / 2) - 1), 0);
memset(SrcNibbleBuffer, 0x00, 16 * 16 * 256 /2);
buffer.SetMetas(SrcNibbleBuffer);
buffer.CopyMetas(DstNibbleBuffer);
buffer.SetAll(SrcBlockBuffer, SrcNibbleBuffer);
CopyAll(buffer, &ChunkBlockData::GetMetaSection, ChunkBlockData::DefaultMetaValue, DstNibbleBuffer);
TEST_EQUAL(memcmp(SrcNibbleBuffer, DstNibbleBuffer, (16 * 16 * 256 / 2) - 1), 0);
}
{
cChunkData buffer(Pool);
ChunkLightData buffer;
NIBBLETYPE SrcNibbleBuffer[16 * 16 * 256 / 2];
for (int i = 0; i < 16 * 16 * 256 / 2; i += 4)
@ -96,19 +106,19 @@ static void test()
SrcNibbleBuffer[i + 3] = 0xef;
}
buffer.SetBlockLight(SrcNibbleBuffer);
buffer.SetAll(SrcNibbleBuffer, SrcNibbleBuffer);
NIBBLETYPE DstNibbleBuffer[16 * 16 * 256 / 2];
buffer.CopyBlockLight(DstNibbleBuffer);
CopyAll(buffer, &ChunkLightData::GetBlockLightSection, ChunkLightData::DefaultBlockLightValue, DstNibbleBuffer);
TEST_EQUAL(memcmp(SrcNibbleBuffer, DstNibbleBuffer, (16 * 16 * 256 /2) - 1), 0);
memset(SrcNibbleBuffer, 0x00, 16 * 16 * 256 /2);
buffer.SetBlockLight(SrcNibbleBuffer);
buffer.CopyBlockLight(DstNibbleBuffer);
buffer.SetAll(SrcNibbleBuffer, SrcNibbleBuffer);
CopyAll(buffer, &ChunkLightData::GetBlockLightSection, ChunkLightData::DefaultBlockLightValue, DstNibbleBuffer);
TEST_EQUAL(memcmp(SrcNibbleBuffer, DstNibbleBuffer, (16 * 16 * 256 /2) - 1), 0);
}
{
cChunkData buffer(Pool);
ChunkLightData buffer;
NIBBLETYPE SrcNibbleBuffer[16 * 16 * 256 / 2];
for (int i = 0; i < 16 * 16 * 256 / 2; i += 4)
@ -119,38 +129,44 @@ static void test()
SrcNibbleBuffer[i + 3] = 0xef;
}
buffer.SetSkyLight(SrcNibbleBuffer);
buffer.SetAll(SrcNibbleBuffer, SrcNibbleBuffer);
NIBBLETYPE DstNibbleBuffer[16 * 16 * 256/ 2];
buffer.CopySkyLight(DstNibbleBuffer);
CopyAll(buffer, &ChunkLightData::GetSkyLightSection, ChunkLightData::DefaultSkyLightValue, DstNibbleBuffer);
TEST_EQUAL(memcmp(SrcNibbleBuffer, DstNibbleBuffer, (16 * 16 * 256 / 2) - 1), 0);
memset(SrcNibbleBuffer, 0xFF, 16 * 16 * 256 / 2);
buffer.SetSkyLight(SrcNibbleBuffer);
buffer.CopySkyLight(DstNibbleBuffer);
buffer.SetAll(SrcNibbleBuffer, SrcNibbleBuffer);
CopyAll(buffer, &ChunkLightData::GetSkyLightSection, ChunkLightData::DefaultSkyLightValue, DstNibbleBuffer);
TEST_EQUAL(memcmp(SrcNibbleBuffer, DstNibbleBuffer, (16 * 16 * 256 / 2) - 1), 0);
}
{
cChunkData buffer(Pool);
ChunkBlockData buffer;
BLOCKTYPE SrcBlockBuffer[16 * 16 * 256];
memset(SrcBlockBuffer, 0x00, 16 * 16 * 256);
BLOCKTYPE DstBlockBuffer[16 * 16 * 256];
buffer.CopyBlockTypes(DstBlockBuffer);
CopyAll(buffer, &ChunkBlockData::GetSection, ChunkBlockData::DefaultValue, DstBlockBuffer);
TEST_EQUAL(memcmp(SrcBlockBuffer, DstBlockBuffer, (16 * 16 * 256) - 1), 0);
NIBBLETYPE SrcNibbleBuffer[16 * 16 * 256 / 2];
memset(SrcNibbleBuffer, 0x00, 16 * 16 * 256 / 2);
NIBBLETYPE DstNibbleBuffer[16 * 16 * 256 / 2];
buffer.CopyMetas(DstNibbleBuffer);
CopyAll(buffer, &ChunkBlockData::GetMetaSection, ChunkBlockData::DefaultMetaValue, DstNibbleBuffer);
TEST_EQUAL(memcmp(SrcNibbleBuffer, DstNibbleBuffer, (16 * 16 * 256 / 2) - 1), 0);
}
{
ChunkLightData buffer;
NIBBLETYPE SrcNibbleBuffer[16 * 16 * 256 / 2];
memset(SrcNibbleBuffer, 0x00, 16 * 16 * 256 / 2);
buffer.CopyBlockLight(DstNibbleBuffer);
NIBBLETYPE DstNibbleBuffer[16 * 16 * 256 / 2];
CopyAll(buffer, &ChunkLightData::GetBlockLightSection, ChunkLightData::DefaultBlockLightValue, DstNibbleBuffer);
TEST_EQUAL(memcmp(SrcNibbleBuffer, DstNibbleBuffer, (16 * 16 * 256 / 2) - 1), 0);
memset(SrcNibbleBuffer, 0xFF, 16 * 16 * 256 / 2);
buffer.CopySkyLight(DstNibbleBuffer);
CopyAll(buffer, &ChunkLightData::GetSkyLightSection, ChunkLightData::DefaultSkyLightValue, DstNibbleBuffer);
TEST_EQUAL(memcmp(SrcNibbleBuffer, DstNibbleBuffer, (16 * 16 * 256 / 2) - 1), 0);
}
}
@ -160,5 +176,5 @@ static void test()
IMPLEMENT_TEST_MAIN("ChunkData Copies",
test()
Test()
)

View File

@ -1,98 +0,0 @@
// CopyBlocks.cpp
// Implements the test for cChunkData::CopyBlockTypes() range copying
#include "Globals.h"
#include "../TestHelpers.h"
#include "ChunkData.h"
/** Performs the entire CopyBlocks test. */
static void test()
{
// Set up a cChunkData with known contents - all blocks 0x01, all metas 0x02:
class cMockAllocationPool
: public cAllocationPool<cChunkData::sChunkSection>
{
virtual cChunkData::sChunkSection * Allocate() override
{
return new cChunkData::sChunkSection();
}
virtual void Free(cChunkData::sChunkSection * a_Ptr) override
{
delete a_Ptr;
}
virtual bool DoIsEqual(const cAllocationPool<cChunkData::sChunkSection> &) const noexcept override
{
return false;
}
} Pool;
cChunkData Data(Pool);
cChunkDef::BlockTypes BlockTypes;
cChunkDef::BlockNibbles BlockMetas;
memset(BlockTypes, 0x01, sizeof(BlockTypes));
memset(BlockMetas, 0x02, sizeof(BlockMetas));
Data.SetBlockTypes(BlockTypes);
Data.SetMetas(BlockMetas);
// Try to read varying amounts of blocktypes from the cChunkData.
// Verify that the exact amount of memory is copied, by copying to a larger buffer and checking its boundaries
BLOCKTYPE TestBuffer[5 * cChunkDef::NumBlocks];
size_t WritePosIdx = 2 * cChunkDef::NumBlocks;
BLOCKTYPE * WritePosition = &TestBuffer[WritePosIdx];
memset(TestBuffer, 0x03, sizeof(TestBuffer));
size_t LastReportedStep = 1;
for (size_t idx = 0; idx < 5000; idx += 73)
{
if (idx / 500 != LastReportedStep)
{
printf("Testing index %u...\n", static_cast<unsigned>(idx));
LastReportedStep = idx / 500;
}
for (size_t len = 3; len < 700; len += 13)
{
Data.CopyBlockTypes(WritePosition, idx, len);
// Verify the data copied:
for (size_t i = 0; i < len; i++)
{
TEST_EQUAL(WritePosition[i], 0x01);
}
// Verify the space before the copied data hasn't been changed:
for (size_t i = 0; i < WritePosIdx; i++)
{
TEST_EQUAL(TestBuffer[i], 0x03);
}
// Verify the space after the copied data hasn't been changed:
for (size_t i = WritePosIdx + idx + len; i < ARRAYCOUNT(TestBuffer); i++)
{
TEST_EQUAL(TestBuffer[i], 0x03);
}
// Re-initialize the buffer for the next test:
for (size_t i = 0; i < len; i++)
{
WritePosition[i] = 0x03;
}
} // for len
} // for idx
}
IMPLEMENT_TEST_MAIN("ChunkData CopyBlocks",
test()
)

View File

@ -0,0 +1,34 @@
#include "Globals.h"
#include "../TestHelpers.h"
#include "ChunkData.h"
static void Test()
{
LOGD("Test started");
ChunkBlockData train;
ChunkLightData buffer;
/*
/-|===D
/ |===D
/---|
/ |
====/=====|===============
*/
}
IMPLEMENT_TEST_MAIN("ChunkData Creatable",
Test()
);

View File

@ -1,29 +0,0 @@
#include "Globals.h"
#include "ChunkData.h"
int main(int argc, char** argv)
{
LOGD("Test started");
class cMockAllocationPool
: public cAllocationPool<cChunkData::sChunkSection>
{
virtual cChunkData::sChunkSection * Allocate() override
{
return new cChunkData::sChunkSection();
}
virtual void Free(cChunkData::sChunkSection * a_Ptr) override
{
delete a_Ptr;
}
virtual bool DoIsEqual(const cAllocationPool<cChunkData::sChunkSection> &) const noexcept override
{
return false;
}
} Pool;
cChunkData buffer(Pool);
return 0;
}