Skip to content

Commit

Permalink
Optimise chunk set (cuberite#4260)
Browse files Browse the repository at this point in the history
Closes cuberite#1244

Initially I was just going to add the cChunkData to cSetChunkData but profiling revealed 
that the copying wasn't even the biggest slowdown. Much more time was being spent in 
cChunk::CreateBlockEntities and cChunk::WakeUpSimulators than was in memcpy so I've made 
those significantly faster as well.

Optimisations performed:
 * cSetChunkData now stores blocks in a cChunkData object
 * cChunkData objects can now perform moves even if they are using different pools
 * cChunk::CreateBlockEntities now iterates in the correct order and only over present chunk sections
 * Similarly for cChunk::WakeUpSimulators
 * cSetChunkData::CalculateHeightMap now shortcuts to the highest present chunk section before checking blocks directly
  • Loading branch information
peterbell10 authored Jul 23, 2018
1 parent e27290f commit 31a11a6
Show file tree
Hide file tree
Showing 14 changed files with 194 additions and 166 deletions.
39 changes: 33 additions & 6 deletions src/AllocationPool.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,25 @@ class cAllocationPool

/** 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;
};


Expand All @@ -40,16 +59,17 @@ class cAllocationPool

/** 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, size_t NumElementsInReserve>
template <class T>
class cListAllocationPool:
public cAllocationPool<T>
{
public:

cListAllocationPool(std::unique_ptr<typename cAllocationPool<T>::cStarvationCallbacks> a_Callbacks):
cListAllocationPool(std::unique_ptr<typename cAllocationPool<T>::cStarvationCallbacks> a_Callbacks, size_t a_NumElementsInReserve):
m_NumElementsInReserve(a_NumElementsInReserve),
m_Callbacks(std::move(a_Callbacks))
{
for (size_t i = 0; i < NumElementsInReserve; i++)
for (size_t i = 0; i < m_NumElementsInReserve; i++)
{
void * space = malloc(sizeof(T));
if (space == nullptr)
Expand All @@ -74,7 +94,7 @@ class cListAllocationPool:

virtual T * Allocate() override
{
if (m_FreeList.size() <= NumElementsInReserve)
if (m_FreeList.size() <= m_NumElementsInReserve)
{
void * space = malloc(sizeof(T));
if (space != nullptr)
Expand All @@ -93,7 +113,7 @@ class cListAllocationPool:
#pragma pop_macro("new")
#endif
}
else if (m_FreeList.size() == NumElementsInReserve)
else if (m_FreeList.size() == m_NumElementsInReserve)
{
m_Callbacks->OnStartUsingReserve();
}
Expand Down Expand Up @@ -134,15 +154,22 @@ class cListAllocationPool:
// placement destruct.
a_ptr->~T();
m_FreeList.push_front(a_ptr);
if (m_FreeList.size() == NumElementsInReserve)
if (m_FreeList.size() == m_NumElementsInReserve)
{
m_Callbacks->OnEndUsingReserve();
}
}

private:
/** The minimum number of elements to keep in the free list before malloc fails */
size_t m_NumElementsInReserve;
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);
}
};


Expand Down
159 changes: 70 additions & 89 deletions src/Chunk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -322,26 +322,15 @@ void cChunk::SetAllData(cSetChunkData & a_SetChunkData)
memcpy(m_BiomeMap, a_SetChunkData.GetBiomes(), sizeof(m_BiomeMap));
memcpy(m_HeightMap, a_SetChunkData.GetHeightMap(), sizeof(m_HeightMap));

m_ChunkData.SetBlockTypes(a_SetChunkData.GetBlockTypes());
m_ChunkData.SetMetas(a_SetChunkData.GetBlockMetas());
if (a_SetChunkData.IsLightValid())
{
m_ChunkData.SetBlockLight(a_SetChunkData.GetBlockLight());
m_ChunkData.SetSkyLight(a_SetChunkData.GetSkyLight());
m_IsLightValid = true;
}
else
{
m_IsLightValid = false;
}
m_ChunkData.Assign(std::move(a_SetChunkData.GetChunkData()));
m_IsLightValid = a_SetChunkData.IsLightValid();

// Clear the block entities present - either the loader / saver has better, or we'll create empty ones:
for (auto & KeyPair : m_BlockEntities)
{
delete KeyPair.second;
}
m_BlockEntities.clear();
std::swap(a_SetChunkData.GetBlockEntities(), m_BlockEntities);
m_BlockEntities = std::move(a_SetChunkData.GetBlockEntities());

// Check that all block entities have a valid blocktype at their respective coords (DEBUG-mode only):
#ifdef _DEBUG
Expand Down Expand Up @@ -504,9 +493,9 @@ void cChunk::WriteBlockArea(cBlockArea & a_Area, int a_MinBlockX, int a_MinBlock



bool cChunk::HasBlockEntityAt(int a_BlockX, int a_BlockY, int a_BlockZ)
bool cChunk::HasBlockEntityAt(Vector3i a_BlockPos)
{
return (GetBlockEntity(a_BlockX, a_BlockY, a_BlockZ) != nullptr);
return (GetBlockEntity(a_BlockPos) != nullptr);
}


Expand Down Expand Up @@ -1433,48 +1422,32 @@ int cChunk::GetHeight(int a_X, int a_Z)

void cChunk::CreateBlockEntities(void)
{
for (int x = 0; x < Width; x++)
for (size_t SectionIdx = 0; SectionIdx != cChunkData::NumSections; ++SectionIdx)
{
for (int z = 0; z < Width; z++)
const auto * Section = m_ChunkData.GetSection(SectionIdx);
if (Section == nullptr)
{
for (int y = 0; y < Height; y++)
continue;
}

for (size_t BlockIdx = 0; BlockIdx != cChunkData::SectionBlockCount; ++BlockIdx)
{
auto BlockType = Section->m_BlockTypes[BlockIdx];
if (cBlockEntity::IsBlockEntityBlockType(BlockType))
{
BLOCKTYPE BlockType = GetBlock(x, y, z);
switch (BlockType)
auto RelPos = IndexToCoordinate(BlockIdx);
RelPos.y += SectionIdx * cChunkData::SectionHeight;
auto WorldPos = RelativeToAbsolute(RelPos, m_PosX, m_PosZ);

if (!HasBlockEntityAt(WorldPos))
{
case E_BLOCK_BEACON:
case E_BLOCK_BED:
case E_BLOCK_TRAPPED_CHEST:
case E_BLOCK_CHEST:
case E_BLOCK_COMMAND_BLOCK:
case E_BLOCK_DISPENSER:
case E_BLOCK_DROPPER:
case E_BLOCK_ENDER_CHEST:
case E_BLOCK_LIT_FURNACE:
case E_BLOCK_FURNACE:
case E_BLOCK_HOPPER:
case E_BLOCK_SIGN_POST:
case E_BLOCK_WALLSIGN:
case E_BLOCK_HEAD:
case E_BLOCK_NOTE_BLOCK:
case E_BLOCK_JUKEBOX:
case E_BLOCK_FLOWER_POT:
case E_BLOCK_MOB_SPAWNER:
case E_BLOCK_BREWING_STAND:
{
if (!HasBlockEntityAt(x + m_PosX * Width, y, z + m_PosZ * Width))
{
AddBlockEntityClean(cBlockEntity::CreateByBlockType(
BlockType, GetMeta(x, y, z),
x + m_PosX * Width, y, z + m_PosZ * Width, m_World
));
}
break;
}
} // switch (BlockType)
} // for y
} // for z
} // for x
AddBlockEntityClean(cBlockEntity::CreateByBlockType(
BlockType, GetMeta(RelPos), WorldPos.x, WorldPos.y, WorldPos.z, m_World
));
}
}
}
}
}


Expand All @@ -1483,48 +1456,56 @@ void cChunk::CreateBlockEntities(void)

void cChunk::WakeUpSimulators(void)
{
cSimulator * WaterSimulator = m_World->GetWaterSimulator();
cSimulator * LavaSimulator = m_World->GetLavaSimulator();
cSimulator * RedstoneSimulator = m_World->GetRedstoneSimulator();
int BaseX = m_PosX * cChunkDef::Width;
int BaseZ = m_PosZ * cChunkDef::Width;
for (int x = 0; x < Width; x++)
auto * WaterSimulator = m_World->GetWaterSimulator();
auto * LavaSimulator = m_World->GetLavaSimulator();
auto * RedstoneSimulator = m_World->GetRedstoneSimulator();

for (size_t SectionIdx = 0; SectionIdx != cChunkData::NumSections; ++SectionIdx)
{
int BlockX = x + BaseX;
for (int z = 0; z < Width; z++)
const auto * Section = m_ChunkData.GetSection(SectionIdx);
if (Section == nullptr)
{
continue;
}

for (size_t BlockIdx = 0; BlockIdx != cChunkData::SectionBlockCount; ++BlockIdx)
{
int BlockZ = z + BaseZ;
for (int y = GetHeight(x, z); y >= 0; y--)
auto BlockType = Section->m_BlockTypes[BlockIdx];

// Defer calculation until it's actually needed
auto WorldPos = [&]
{
BLOCKTYPE Block = GetBlock(x, y, z);
auto RelPos = IndexToCoordinate(BlockIdx);
RelPos.y += SectionIdx * cChunkData::SectionHeight;
return RelativeToAbsolute(RelPos, m_PosX, m_PosZ);
};

// The redstone sim takes multiple blocks, use the inbuilt checker
if (RedstoneSimulator->IsAllowedBlock(Block))
// The redstone sim takes multiple blocks, use the inbuilt checker
if (RedstoneSimulator->IsAllowedBlock(BlockType))
{
RedstoneSimulator->AddBlock(WorldPos(), this);
continue;
}

switch (BlockType)
{
case E_BLOCK_WATER:
{
RedstoneSimulator->AddBlock({BlockX, y, BlockZ}, this);
continue;
WaterSimulator->AddBlock(WorldPos(), this);
break;
}

switch (Block)
case E_BLOCK_LAVA:
{
case E_BLOCK_WATER:
{
WaterSimulator->AddBlock({BlockX, y, BlockZ}, this);
break;
}
case E_BLOCK_LAVA:
{
LavaSimulator->AddBlock({BlockX, y, BlockZ}, this);
break;
}
default:
{
break;
}
} // switch (BlockType)
} // for y
} // for z
} // for x
LavaSimulator->AddBlock(WorldPos(), this);
break;
}
default:
{
break;
}
} // switch (BlockType)
}
}
}


Expand Down
2 changes: 1 addition & 1 deletion src/Chunk.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ class cChunk :
void WriteBlockArea(cBlockArea & a_Area, int a_MinBlockX, int a_MinBlockY, int a_MinBlockZ, int a_DataTypes);

/** Returns true if there is a block entity at the coords specified */
bool HasBlockEntityAt(int a_BlockX, int a_BlockY, int a_BlockZ);
bool HasBlockEntityAt(Vector3i a_BlockPos);

/** Sets or resets the internal flag that prevents chunk from being unloaded.
The flag is cumulative - it can be set multiple times and then needs to be un-set that many times
Expand Down
2 changes: 1 addition & 1 deletion src/ChunkData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ void cChunkData::Assign(cChunkData && a_Other)
return;
}

if (&m_Pool != &a_Other.m_Pool)
if (m_Pool != a_Other.m_Pool)
{
// Cannot transfer the memory, do a copy instead
const cChunkData & CopyOther = a_Other;
Expand Down
4 changes: 2 additions & 2 deletions src/ChunkDataCallback.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,13 @@ class cChunkDataCopyCollector :
};

cChunkDataCopyCollector():
m_Pool(cpp14::make_unique<MemCallbacks>()),
m_Pool(cpp14::make_unique<MemCallbacks>(), cChunkData::NumSections), // Keep 1 chunk worth of reserve
m_Data(m_Pool)
{
}


cListAllocationPool<cChunkData::sChunkSection, cChunkData::NumSections> m_Pool; // Keep 1 chunk worth of reserve
cListAllocationPool<cChunkData::sChunkSection> m_Pool;
cChunkData m_Data;

protected:
Expand Down
14 changes: 7 additions & 7 deletions src/ChunkDef.h
Original file line number Diff line number Diff line change
Expand Up @@ -207,19 +207,19 @@ class cChunkDef
}


inline static Vector3i IndexToCoordinate( unsigned int index)
inline static Vector3i IndexToCoordinate(size_t index)
{
#if AXIS_ORDER == AXIS_ORDER_XZY
return Vector3i( // 1.2
index % cChunkDef::Width, // X
index / (cChunkDef::Width * cChunkDef::Width), // Y
(index / cChunkDef::Width) % cChunkDef::Width // Z
static_cast<int>(index % cChunkDef::Width), // X
static_cast<int>(index / (cChunkDef::Width * cChunkDef::Width)), // Y
static_cast<int>((index / cChunkDef::Width) % cChunkDef::Width) // Z
);
#elif AXIS_ORDER == AXIS_ORDER_YZX
return Vector3i( // 1.1
index / (cChunkDef::Height * cChunkDef::Width), // X
index % cChunkDef::Height, // Y
(index / cChunkDef::Height) % cChunkDef::Width // Z
static_cast<int>(index / (cChunkDef::Height * cChunkDef::Width)), // X
static_cast<int>(index % cChunkDef::Height), // Y
static_cast<int>((index / cChunkDef::Height) % cChunkDef::Width) // Z
);
#endif
}
Expand Down
6 changes: 2 additions & 4 deletions src/ChunkMap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,8 @@
cChunkMap::cChunkMap(cWorld * a_World) :
m_World(a_World),
m_Pool(
new cListAllocationPool<cChunkData::sChunkSection, 1600>(
std::unique_ptr<cAllocationPool<cChunkData::sChunkSection>::cStarvationCallbacks>(
new cStarvationCallbacks()
)
cpp14::make_unique<cListAllocationPool<cChunkData::sChunkSection>>(
cpp14::make_unique<cStarvationCallbacks>(), 1600u
)
)
{
Expand Down
Loading

0 comments on commit 31a11a6

Please sign in to comment.