#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, a_BiomeMap);
}
}
void cChunkGeneratorThread::WaitForQueueEmpty(void)
{
cCSLock Lock(m_CS);
while (!m_ShouldTerminate && !m_Queue.empty())
{
cCSUnlock Unlock(Lock);
m_evtRemoved.Wait();
}
}
size_t cChunkGeneratorThread::GetQueueLength(void) const
{
cCSLock Lock(m_CS);
return 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(ChunkDesc);
m_PluginInterface->CallHookChunkGenerated(ChunkDesc);
#ifdef _DEBUG
// Verify that the generator has produced valid data:
ChunkDesc.VerifyHeightmap();
#endif
m_ChunkSink->OnChunkGenerated(ChunkDesc);
}