Skip to content

Commit

Permalink
Separated chunk generator from world / plugin interfaces.
Browse files Browse the repository at this point in the history
The generator now only takes care of servicing synchronous "GetChunk(X, Y)" and "GetBiomes(X, Y)" requests.
  • Loading branch information
madmaxoft committed Sep 6, 2019
1 parent f021e2f commit a2ffa43
Show file tree
Hide file tree
Showing 27 changed files with 782 additions and 688 deletions.
4 changes: 2 additions & 2 deletions src/Bindings/ManualBindings_World.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -727,9 +727,9 @@ static int tolua_cWorld_PrepareChunk(lua_State * tolua_S)
{
public:
// cChunkCoordCallback override:
virtual void Call(int a_CBChunkX, int a_CBChunkZ, bool a_IsSuccess) override
virtual void Call(cChunkCoords a_Coords, bool a_IsSuccess) override
{
m_LuaCallback.Call(a_CBChunkX, a_CBChunkZ, a_IsSuccess);
m_LuaCallback.Call(a_Coords.m_ChunkX, a_Coords.m_ChunkZ, a_IsSuccess);
}

cLuaState::cOptionalCallback m_LuaCallback;
Expand Down
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ SET (SRCS
ChatColor.cpp
Chunk.cpp
ChunkData.cpp
ChunkGeneratorThread.cpp
ChunkMap.cpp
ChunkSender.cpp
ChunkStay.cpp
Expand Down Expand Up @@ -100,6 +101,7 @@ SET (HDRS
ChunkData.h
ChunkDataCallback.h
ChunkDef.h
ChunkGeneratorThread.h
ChunkMap.h
ChunkSender.h
ChunkStay.h
Expand Down
19 changes: 1 addition & 18 deletions src/Chunk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,23 +49,6 @@



////////////////////////////////////////////////////////////////////////////////
// sSetBlock:

sSetBlock::sSetBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta):
m_RelX(a_BlockX),
m_RelY(a_BlockY),
m_RelZ(a_BlockZ),
m_BlockType(a_BlockType),
m_BlockMeta(a_BlockMeta)
{
cChunkDef::AbsoluteToRelative(m_RelX, m_RelY, m_RelZ, m_ChunkX, m_ChunkZ);
}





////////////////////////////////////////////////////////////////////////////////
// cChunk:

Expand Down Expand Up @@ -276,7 +259,7 @@ void cChunk::MarkLoadFailed(void)
// If the chunk is marked as needed, generate it:
if (m_ShouldGenerateIfLoadFailed)
{
m_World->GetGenerator().QueueGenerateChunk(m_PosX, m_PosZ, false);
m_World->GetGenerator().QueueGenerateChunk({m_PosX, m_PosZ}, false);
}
else
{
Expand Down
18 changes: 16 additions & 2 deletions src/ChunkDef.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ class cChunkCoords
{
return ((m_ChunkX == a_Other.m_ChunkX) && (m_ChunkZ == a_Other.m_ChunkZ));
}

/** Returns a string that describes the chunk coords, suitable for logging. */
AString ToString() const
{
return Printf("[%d, %d]", m_ChunkX, m_ChunkZ);
}
} ;


Expand Down Expand Up @@ -445,7 +451,15 @@ struct sSetBlock
BLOCKTYPE m_BlockType;
NIBBLETYPE m_BlockMeta;

sSetBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta);
sSetBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta):
m_RelX(a_BlockX),
m_RelY(a_BlockY),
m_RelZ(a_BlockZ),
m_BlockType(a_BlockType),
m_BlockMeta(a_BlockMeta)
{
cChunkDef::AbsoluteToRelative(m_RelX, m_RelY, m_RelZ, m_ChunkX, m_ChunkZ);
}

sSetBlock(int a_ChunkX, int a_ChunkZ, int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) :
m_RelX(a_RelX), m_RelY(a_RelY), m_RelZ(a_RelZ),
Expand Down Expand Up @@ -525,7 +539,7 @@ class cChunkCoordCallback
virtual ~cChunkCoordCallback() {}

/** Called with the chunk's coords, and an optional operation status flag for operations that support it. */
virtual void Call(int a_ChunkX, int a_ChunkZ, bool a_IsSuccess) = 0;
virtual void Call(cChunkCoords a_Coords, bool a_IsSuccess) = 0;
} ;


Expand Down
264 changes: 264 additions & 0 deletions src/ChunkGeneratorThread.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
#include "Globals.h"
#include "ChunkGeneratorThread.h"
#include "Generating/ChunkGenerator.h"
#include "Generating/ChunkDesc.h"





/** If the generation queue size exceeds this number, a warning will be output */
const size_t QUEUE_WARNING_LIMIT = 1000;

/** If the generation queue size exceeds this number, chunks with no clients will be skipped */
const size_t QUEUE_SKIP_LIMIT = 500;





cChunkGeneratorThread::cChunkGeneratorThread(void) :
Super("cChunkGeneratorThread"),
m_Generator(nullptr),
m_PluginInterface(nullptr),
m_ChunkSink(nullptr)
{
}





cChunkGeneratorThread::~cChunkGeneratorThread()
{
Stop();
}





bool cChunkGeneratorThread::Initialize(cPluginInterface & a_PluginInterface, cChunkSink & a_ChunkSink, cIniFile & a_IniFile)
{
m_PluginInterface = &a_PluginInterface;
m_ChunkSink = &a_ChunkSink;

m_Generator = cChunkGenerator::CreateFromIniFile(a_IniFile);
if (m_Generator == nullptr)
{
LOGERROR("Generator could not start, aborting the server");
return false;
}
return true;
}





void cChunkGeneratorThread::Stop(void)
{
m_ShouldTerminate = true;
m_Event.Set();
m_evtRemoved.Set(); // Wake up anybody waiting for empty queue
Super::Stop();
m_Generator.reset();
}





void cChunkGeneratorThread::QueueGenerateChunk(
cChunkCoords a_Coords,
bool a_ForceRegeneration,
cChunkCoordCallback * a_Callback
)
{
ASSERT(m_ChunkSink->IsChunkQueued(a_Coords));

{
cCSLock Lock(m_CS);

// Add to queue, issue a warning if too many:
if (m_Queue.size() >= QUEUE_WARNING_LIMIT)
{
LOGWARN("WARNING: Adding chunk %s to generation queue; Queue is too big! (%zu)", a_Coords.ToString().c_str(), m_Queue.size());
}
m_Queue.push_back(QueueItem{a_Coords, a_ForceRegeneration, a_Callback});
}

m_Event.Set();
}





void cChunkGeneratorThread::GenerateBiomes(cChunkCoords a_Coords, cChunkDef::BiomeMap & a_BiomeMap)
{
if (m_Generator != nullptr)
{
m_Generator->GenerateBiomes(a_Coords.m_ChunkX, a_Coords.m_ChunkZ, a_BiomeMap);
}
}





void cChunkGeneratorThread::WaitForQueueEmpty(void)
{
cCSLock Lock(m_CS);
while (!m_ShouldTerminate && !m_Queue.empty())
{
cCSUnlock Unlock(Lock);
m_evtRemoved.Wait();
}
}





int cChunkGeneratorThread::GetQueueLength(void) const
{
cCSLock Lock(m_CS);
return static_cast<int>(m_Queue.size());
}





int cChunkGeneratorThread::GetSeed() const
{
return m_Generator->GetSeed();
}





EMCSBiome cChunkGeneratorThread::GetBiomeAt(int a_BlockX, int a_BlockZ)
{
ASSERT(m_Generator != nullptr);
return m_Generator->GetBiomeAt(a_BlockX, a_BlockZ);
}





void cChunkGeneratorThread::Execute(void)
{
// To be able to display performance information, the generator counts the chunks generated.
// When the queue gets empty, the count is reset, so that waiting for the queue is not counted into the total time.
int NumChunksGenerated = 0; // Number of chunks generated since the queue was last empty
clock_t GenerationStart = clock(); // Clock tick when the queue started to fill
clock_t LastReportTick = clock(); // Clock tick of the last report made (so that performance isn't reported too often)

while (!m_ShouldTerminate)
{
cCSLock Lock(m_CS);
while (m_Queue.empty())
{
if ((NumChunksGenerated > 16) && (clock() - LastReportTick > CLOCKS_PER_SEC))
{
/* LOG("Chunk generator performance: %.2f ch / sec (%d ch total)",
static_cast<double>(NumChunksGenerated) * CLOCKS_PER_SEC/ (clock() - GenerationStart),
NumChunksGenerated
); */
}
cCSUnlock Unlock(Lock);
m_Event.Wait();
if (m_ShouldTerminate)
{
return;
}
NumChunksGenerated = 0;
GenerationStart = clock();
LastReportTick = clock();
}

if (m_Queue.empty())
{
// Sometimes the queue remains empty
// If so, we can't do any front() operations on it!
continue;
}

auto item = m_Queue.front(); // Get next chunk from the queue
bool SkipEnabled = (m_Queue.size() > QUEUE_SKIP_LIMIT);
m_Queue.erase(m_Queue.begin()); // Remove the item from the queue
Lock.Unlock(); // Unlock ASAP
m_evtRemoved.Set();

// Display perf info once in a while:
if ((NumChunksGenerated > 512) && (clock() - LastReportTick > 2 * CLOCKS_PER_SEC))
{
LOG("Chunk generator performance: %.2f ch / sec (%d ch total)",
static_cast<double>(NumChunksGenerated) * CLOCKS_PER_SEC / (clock() - GenerationStart),
NumChunksGenerated
);
LastReportTick = clock();
}

// Skip the chunk if it's already generated and regeneration is not forced. Report as success:
if (!item.m_ForceRegeneration && m_ChunkSink->IsChunkValid(item.m_Coords))
{
LOGD("Chunk %s already generated, skipping generation", item.m_Coords.ToString().c_str());
if (item.m_Callback != nullptr)
{
item.m_Callback->Call(item.m_Coords, true);
}
continue;
}

// Skip the chunk if the generator is overloaded:
if (SkipEnabled && !m_ChunkSink->HasChunkAnyClients(item.m_Coords))
{
LOGWARNING("Chunk generator overloaded, skipping chunk %s", item.m_Coords.ToString().c_str());
if (item.m_Callback != nullptr)
{
item.m_Callback->Call(item.m_Coords, false);
}
continue;
}

// Generate the chunk:
DoGenerate(item.m_Coords);
if (item.m_Callback != nullptr)
{
item.m_Callback->Call(item.m_Coords, true);
}
NumChunksGenerated++;
} // while (!bStop)
}





void cChunkGeneratorThread::DoGenerate(cChunkCoords a_Coords)
{
ASSERT(m_PluginInterface != nullptr);
ASSERT(m_ChunkSink != nullptr);

cChunkDesc ChunkDesc(a_Coords);
m_PluginInterface->CallHookChunkGenerating(ChunkDesc);
m_Generator->Generate(a_Coords.m_ChunkX, a_Coords.m_ChunkZ, ChunkDesc);
m_PluginInterface->CallHookChunkGenerated(ChunkDesc);

#ifdef _DEBUG
// Verify that the generator has produced valid data:
ChunkDesc.VerifyHeightmap();
#endif

m_ChunkSink->OnChunkGenerated(ChunkDesc);
}





Loading

0 comments on commit a2ffa43

Please sign in to comment.