1
0

Merge pull request #1657 from mc-server/PrepareChunk

Prepare chunk
This commit is contained in:
Mattes D 2014-12-11 17:06:18 +01:00
commit 781b3303a5
11 changed files with 294 additions and 52 deletions

View File

@ -410,7 +410,7 @@ typedef std::list<cChunkCoordsWithBool> cChunkCoordsWithBoolList;
/// Interface class used as a callback for operations that involve chunk coords
/** Interface class used as a callback for operations that involve chunk coords */
class cChunkCoordCallback
{
public:
@ -424,6 +424,27 @@ public:
/** Provides storage for a set of chunk coords together with a callback.
Used for chunk queues that notify about processed items. */
class cChunkCoordsWithCallback
{
public:
cChunkCoordsWithCallback(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_Callback):
m_ChunkX(a_ChunkX),
m_ChunkZ(a_ChunkZ),
m_Callback(a_Callback)
{
}
int m_ChunkX;
int m_ChunkZ;
cChunkCoordCallback * m_Callback;
};
/** Generic template that can store any kind of data together with a triplet of 3 coords*/
template <typename X> class cCoordWithData
{

View File

@ -2349,6 +2349,103 @@ void cChunkMap::TouchChunk(int a_ChunkX, int a_ChunkZ)
void cChunkMap::PrepareChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_Callback)
{
cCSLock Lock(m_CSLayers);
cChunkPtr Chunk = GetChunkNoLoad(a_ChunkX, a_ChunkZ);
// If the chunk is not prepared, queue it in the lighting thread, that will do all the needed processing:
if ((Chunk == nullptr) || !Chunk->IsValid() || !Chunk->IsLightValid())
{
m_World->GetLightingThread().QueueChunk(a_ChunkX, a_ChunkZ, a_Callback);
return;
}
// The chunk is present and lit, just call the callback:
if (a_Callback != nullptr)
{
a_Callback->Call(a_ChunkX, a_ChunkZ);
}
}
bool cChunkMap::GenerateChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_Callback)
{
cCSLock Lock(m_CSLayers);
cChunkPtr Chunk = GetChunkNoLoad(a_ChunkX, a_ChunkZ);
if (Chunk == nullptr)
{
// Generic error while getting the chunk - out of memory?
return false;
}
// Try loading the chunk:
if ((Chunk == nullptr) || (!Chunk->IsValid()))
{
class cPrepareLoadCallback: public cChunkCoordCallback
{
public:
cPrepareLoadCallback(cWorld & a_World, cChunkMap & a_ChunkMap, cChunkCoordCallback * a_Callback):
m_World(a_World),
m_ChunkMap(a_ChunkMap),
m_Callback(a_Callback)
{
}
// cChunkCoordCallback override:
virtual void Call(int a_CBChunkX, int a_CBChunkZ) override
{
// The chunk has been loaded or an error occurred, check if it's valid now:
cChunkPtr CBChunk = m_ChunkMap.GetChunkNoLoad(a_CBChunkX, a_CBChunkZ);
if (CBChunk == nullptr)
{
// An error occurred, but we promised to call the callback, so call it even when there's no real chunk data:
if (m_Callback != nullptr)
{
m_Callback->Call(a_CBChunkX, a_CBChunkZ);
}
return;
}
// If the chunk is not valid, queue it in the generator:
if (!CBChunk->IsValid())
{
m_World.GetGenerator().QueueGenerateChunk(a_CBChunkX, a_CBChunkZ, false, m_Callback);
return;
}
// The chunk was loaded, call the callback:
if (m_Callback != nullptr)
{
m_Callback->Call(a_CBChunkX, a_CBChunkZ);
}
}
protected:
cWorld & m_World;
cChunkMap & m_ChunkMap;
cChunkCoordCallback * m_Callback;
};
m_World->GetStorage().QueueLoadChunk(a_ChunkX, a_ChunkZ, new cPrepareLoadCallback(*m_World, *this, a_Callback));
return true;
}
// The chunk is valid, just call the callback:
if (a_Callback != nullptr)
{
a_Callback->Call(a_ChunkX, a_ChunkZ);
}
return true;
}
void cChunkMap::ChunkLoadFailed(int a_ChunkX, int a_ChunkZ)
{
cCSLock Lock(m_CSLayers);

View File

@ -280,6 +280,20 @@ public:
/** Touches the chunk, causing it to be loaded or generated */
void TouchChunk(int a_ChunkX, int a_ChunkZ);
/** Queues the chunk for preparing - making sure that it's generated and lit.
The specified chunk is queued to be loaded or generated, and lit if needed.
The specified callback is called after the chunk has been prepared. If there's no preparation to do, only the callback is called.
It is legal to call without the callback. */
void PrepareChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_CallAfter = nullptr); // Lua-accessible
/** Queues the chunk for generating.
First attempts to load the chunk from the storage. If that fails, queues the chunk for generating.
The specified callback is called after the chunk has been loaded / generated.
It is legal to call without the callback.
Returns true if successful, false if not (possibly an out-of-memory error).
If the return value is true, the callback was / will be called. */
bool GenerateChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_CallAfter = nullptr); // Lua-accessible
/** Marks the chunk as failed-to-load */
void ChunkLoadFailed(int a_ChunkX, int a_ChunkZ);

View File

@ -110,29 +110,19 @@ void cChunkGenerator::Stop(void)
void cChunkGenerator::QueueGenerateChunk(int a_ChunkX, int a_ChunkZ, bool a_ForceGenerate)
void cChunkGenerator::QueueGenerateChunk(int a_ChunkX, int a_ChunkZ, bool a_ForceGenerate, cChunkCoordCallback * a_Callback)
{
ASSERT(m_ChunkSink->IsChunkQueued(a_ChunkX, a_ChunkZ));
{
cCSLock Lock(m_CS);
// Check if it is already in the queue:
for (cChunkCoordsWithBoolList::iterator itr = m_Queue.begin(); itr != m_Queue.end(); ++itr)
{
if ((itr->m_ChunkX == a_ChunkX) && (itr->m_ChunkZ == a_ChunkZ))
{
// Already in the queue, bail out
return;
}
} // for itr - m_Queue[]
// Add to queue, issue a warning if too many:
if (m_Queue.size() >= QUEUE_WARNING_LIMIT)
{
LOGWARN("WARNING: Adding chunk [%i, %i] to generation queue; Queue is too big! (" SIZE_T_FMT ")", a_ChunkX, a_ChunkZ, m_Queue.size());
}
m_Queue.push_back(cChunkCoordsWithBool(a_ChunkX, a_ChunkZ, a_ForceGenerate));
m_Queue.push_back(cQueueItem{a_ChunkX, a_ChunkZ, a_ForceGenerate, a_Callback});
}
m_Event.Set();
@ -242,9 +232,9 @@ void cChunkGenerator::Execute(void)
continue;
}
cChunkCoordsWithBool coords = m_Queue.front(); // Get next coord from queue
cQueueItem 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 coordinate from queue
m_Queue.erase(m_Queue.begin()); // Remove the item from the queue
Lock.Unlock(); // Unlock ASAP
m_evtRemoved.Set();
@ -258,22 +248,35 @@ void cChunkGenerator::Execute(void)
LastReportTick = clock();
}
if (!coords.m_ForceGenerate && m_ChunkSink->IsChunkValid(coords.m_ChunkX, coords.m_ChunkZ))
// Skip the chunk if it's already generated and regeneration is not forced:
if (!item.m_ForceGenerate && m_ChunkSink->IsChunkValid(item.m_ChunkX, item.m_ChunkZ))
{
LOGD("Chunk [%d, %d] already generated, skipping generation", coords.m_ChunkX, coords.m_ChunkZ);
// Already generated, ignore request
LOGD("Chunk [%d, %d] already generated, skipping generation", item.m_ChunkX, item.m_ChunkZ);
if (item.m_Callback != nullptr)
{
item.m_Callback->Call(item.m_ChunkX, item.m_ChunkZ);
}
continue;
}
if (SkipEnabled && !m_ChunkSink->HasChunkAnyClients(coords.m_ChunkX, coords.m_ChunkZ))
// Skip the chunk if the generator is overloaded:
if (SkipEnabled && !m_ChunkSink->HasChunkAnyClients(item.m_ChunkX, item.m_ChunkZ))
{
LOGWARNING("Chunk generator overloaded, skipping chunk [%d, %d]", coords.m_ChunkX, coords.m_ChunkZ);
LOGWARNING("Chunk generator overloaded, skipping chunk [%d, %d]", item.m_ChunkX, item.m_ChunkZ);
if (item.m_Callback != nullptr)
{
item.m_Callback->Call(item.m_ChunkX, item.m_ChunkZ);
}
continue;
}
LOGD("Generating chunk [%d, %d]", coords.m_ChunkX, coords.m_ChunkZ);
DoGenerate(coords.m_ChunkX, coords.m_ChunkZ);
// Generate the chunk:
LOGD("Generating chunk [%d, %d]", item.m_ChunkX, item.m_ChunkZ);
DoGenerate(item.m_ChunkX, item.m_ChunkZ);
if (item.m_Callback != nullptr)
{
item.m_Callback->Call(item.m_ChunkX, item.m_ChunkZ);
}
NumChunksGenerated++;
} // while (!bStop)
}

View File

@ -119,8 +119,12 @@ public:
bool Start(cPluginInterface & a_PluginInterface, cChunkSink & a_ChunkSink, cIniFile & a_IniFile);
void Stop(void);
/// Queues the chunk for generation; removes duplicate requests
void QueueGenerateChunk(int a_ChunkX, int a_ChunkZ, bool a_ForceGenerate);
/** Queues the chunk for generation
If a-ForceGenerate is set, the chunk is regenerated even if the data is already present in the chunksink.
a_Callback is called after the chunk is generated. If the chunk was already present, the callback is still called, even if not regenerating.
It is legal to set the callback to nullptr, no callback is called then.
If the generator becomes overloaded and skips this chunk, the callback is still called. */
void QueueGenerateChunk(int a_ChunkX, int a_ChunkZ, bool a_ForceGenerate, cChunkCoordCallback * a_Callback = nullptr);
/// Generates the biomes for the specified chunk (directly, not in a separate thread). Used by the world loader if biomes failed loading.
void GenerateBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap);
@ -131,22 +135,46 @@ public:
int GetSeed(void) const { return m_Seed; }
/// Returns the biome at the specified coords. Used by ChunkMap if an invalid chunk is queried for biome
/** Returns the biome at the specified coords. Used by ChunkMap if an invalid chunk is queried for biome */
EMCSBiome GetBiomeAt(int a_BlockX, int a_BlockZ);
/// Reads a block type from the ini file; returns the blocktype on success, emits a warning and returns a_Default's representation on failure.
/** Reads a block type from the ini file; returns the blocktype on success, emits a warning and returns a_Default's representation on failure. */
static BLOCKTYPE GetIniBlock(cIniFile & a_IniFile, const AString & a_SectionName, const AString & a_ValueName, const AString & a_Default);
private:
struct cQueueItem
{
/** The chunk coords */
int m_ChunkX, m_ChunkZ;
/** Force the regeneration of an already existing chunk */
bool m_ForceGenerate;
/** Callback to call after generating.*/
cChunkCoordCallback * m_Callback;
};
typedef std::list<cQueueItem> cGenQueue;
/** Seed used for the generator. */
int m_Seed;
cCriticalSection m_CS;
cChunkCoordsWithBoolList m_Queue;
cEvent m_Event; ///< Set when an item is added to the queue or the thread should terminate
cEvent m_evtRemoved; ///< Set when an item is removed from the queue
/** CS protecting access to the queue. */
cCriticalSection m_CS;
/** Queue of the chunks to be generated. Protected against multithreaded access by m_CS. */
cGenQueue m_Queue;
/** Set when an item is added to the queue or the thread should terminate. */
cEvent m_Event;
/** Set when an item is removed from the queue. */
cEvent m_evtRemoved;
cGenerator * m_Generator; ///< The actual generator engine used to generate chunks
/** The actual generator engine used to generate chunks. */
cGenerator * m_Generator;
/** The plugin interface that may modify the generated chunks */
cPluginInterface * m_PluginInterface;
@ -158,6 +186,7 @@ private:
// cIsThread override:
virtual void Execute(void) override;
/** Generates the specified chunk and sets it into the chunksink. */
void DoGenerate(int a_ChunkX, int a_ChunkZ);
};

View File

@ -236,6 +236,16 @@ void cLightingThread::Execute(void)
void cLightingThread::LightChunk(cLightingChunkStay & a_Item)
{
// If the chunk is already lit, skip it:
if (m_World->IsChunkLighted(a_Item.m_ChunkX, a_Item.m_ChunkZ))
{
if (a_Item.m_CallbackAfter != nullptr)
{
a_Item.m_CallbackAfter->Call(a_Item.m_ChunkX, a_Item.m_ChunkZ);
}
return;
}
cChunkDef::BlockNibbles BlockLight, SkyLight;
ReadChunks(a_Item.m_ChunkX, a_Item.m_ChunkZ);

View File

@ -163,6 +163,29 @@ public:
return false;
}
/** Removes all items for which the predicate returns true. */
template <class Predicate>
void RemoveIf(Predicate a_Predicate)
{
cCSLock Lock(m_CS);
for (auto itr = m_Contents.begin(); itr != m_Contents.end();)
{
if (a_Predicate(*itr))
{
auto itr2 = itr;
++itr2;
m_Contents.erase(itr);
m_evtRemoved.Set();
itr = itr2;
}
else
{
++itr;
}
} // for itr - m_Contents[]
}
private:
/// The contents of the queue
QueueType m_Contents;

View File

@ -115,7 +115,7 @@ public:
{
int chunkX, chunkZ;
DecodeChunkCoords(i, chunkX, chunkZ);
m_World.GetLightingThread().QueueChunk(chunkX, chunkZ, this);
m_World.PrepareChunk(chunkX, chunkZ, this);
} // for i
// Wait for the lighting thread to prepare everything. Event is set in the Call() callback:
@ -2907,6 +2907,15 @@ void cWorld::TouchChunk(int a_ChunkX, int a_ChunkZ)
void cWorld::PrepareChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_CallAfter)
{
m_ChunkMap->PrepareChunk(a_ChunkX, a_ChunkZ, a_CallAfter);
}
void cWorld::ChunkLoadFailed(int a_ChunkX, int a_ChunkZ)
{
m_ChunkMap->ChunkLoadFailed(a_ChunkX, a_ChunkZ);
@ -3017,7 +3026,7 @@ void cWorld::RegenerateChunk(int a_ChunkX, int a_ChunkZ)
void cWorld::GenerateChunk(int a_ChunkX, int a_ChunkZ)
{
m_ChunkMap->TouchChunk(a_ChunkX, a_ChunkZ);
m_ChunkMap->GenerateChunk(a_ChunkX, a_ChunkZ);
}

View File

@ -375,6 +375,12 @@ public:
/** Touches the chunk, causing it to be loaded or generated */
void TouchChunk(int a_ChunkX, int a_ChunkZ);
/** Queues the chunk for preparing - making sure that it's generated and lit.
The specified chunk is queued to be loaded or generated, and lit if needed.
The specified callback is called after the chunk has been prepared. If there's no preparation to do, only the callback is called.
It is legal to call with no callback. */
void PrepareChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_CallAfter = nullptr);
/** Marks the chunk as failed-to-load: */
void ChunkLoadFailed(int a_ChunkX, int a_ChunkZ);

View File

@ -140,11 +140,11 @@ size_t cWorldStorage::GetSaveQueueLength(void)
void cWorldStorage::QueueLoadChunk(int a_ChunkX, int a_ChunkZ)
void cWorldStorage::QueueLoadChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_Callback)
{
ASSERT(m_World->IsChunkQueued(a_ChunkX, a_ChunkZ));
m_LoadQueue.EnqueueItem(cChunkCoords(a_ChunkX, a_ChunkZ));
m_LoadQueue.EnqueueItem(cChunkCoordsWithCallback(a_ChunkX, a_ChunkZ, a_Callback));
m_Event.Set();
}
@ -152,11 +152,11 @@ void cWorldStorage::QueueLoadChunk(int a_ChunkX, int a_ChunkZ)
void cWorldStorage::QueueSaveChunk(int a_ChunkX, int a_ChunkZ)
void cWorldStorage::QueueSaveChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_Callback)
{
ASSERT(m_World->IsChunkValid(a_ChunkX, a_ChunkZ));
m_SaveQueue.EnqueueItemIfNotPresent(cChunkCoords(a_ChunkX, a_ChunkZ));
m_SaveQueue.EnqueueItem(cChunkCoordsWithCallback(a_ChunkX, a_ChunkZ, a_Callback));
m_Event.Set();
}
@ -166,7 +166,11 @@ void cWorldStorage::QueueSaveChunk(int a_ChunkX, int a_ChunkZ)
void cWorldStorage::UnqueueLoad(int a_ChunkX, int a_ChunkZ)
{
m_LoadQueue.Remove(cChunkCoords(a_ChunkX, a_ChunkZ));
m_LoadQueue.RemoveIf([=](cChunkCoordsWithCallback & a_Item)
{
return (a_Item.m_ChunkX == a_ChunkX) && (a_Item.m_ChunkZ == a_ChunkZ);
}
);
}
@ -175,7 +179,11 @@ void cWorldStorage::UnqueueLoad(int a_ChunkX, int a_ChunkZ)
void cWorldStorage::UnqueueSave(const cChunkCoords & a_Chunk)
{
m_SaveQueue.Remove(a_Chunk);
m_SaveQueue.RemoveIf([=](cChunkCoordsWithCallback & a_Item)
{
return (a_Item.m_ChunkX == a_Chunk.m_ChunkX) && (a_Item.m_ChunkZ == a_Chunk.m_ChunkZ);
}
);
}
@ -244,14 +252,23 @@ void cWorldStorage::Execute(void)
bool cWorldStorage::LoadOneChunk(void)
{
cChunkCoords ToLoad(0, 0);
// Dequeue an item, bail out if there's none left:
cChunkCoordsWithCallback ToLoad(0, 0, nullptr);
bool ShouldLoad = m_LoadQueue.TryDequeueItem(ToLoad);
if (ShouldLoad)
if (!ShouldLoad)
{
return LoadChunk(ToLoad.m_ChunkX, ToLoad.m_ChunkZ);
return false;
}
return false;
// Load the chunk:
bool res = LoadChunk(ToLoad.m_ChunkX, ToLoad.m_ChunkZ);
// Call the callback, if specified:
if (ToLoad.m_Callback != nullptr)
{
ToLoad.m_Callback->Call(ToLoad.m_ChunkX, ToLoad.m_ChunkZ);
}
return res;
}
@ -260,17 +277,30 @@ bool cWorldStorage::LoadOneChunk(void)
bool cWorldStorage::SaveOneChunk(void)
{
cChunkCoords ToSave(0, 0);
// Dequeue one chunk to save:
cChunkCoordsWithCallback ToSave(0, 0, nullptr);
bool ShouldSave = m_SaveQueue.TryDequeueItem(ToSave);
if (ShouldSave && m_World->IsChunkValid(ToSave.m_ChunkX, ToSave.m_ChunkZ))
if (!ShouldSave)
{
return false;
}
// Save the chunk, if it's valid:
if (m_World->IsChunkValid(ToSave.m_ChunkX, ToSave.m_ChunkZ))
{
m_World->MarkChunkSaving(ToSave.m_ChunkX, ToSave.m_ChunkZ);
if (m_SaveSchema->SaveChunk(ToSave))
if (m_SaveSchema->SaveChunk(cChunkCoords(ToSave.m_ChunkX, ToSave.m_ChunkZ)))
{
m_World->MarkChunkSaved(ToSave.m_ChunkX, ToSave.m_ChunkZ);
}
}
return ShouldSave;
// Call the callback, if specified:
if (ToSave.m_Callback != nullptr)
{
ToSave.m_Callback->Call(ToSave.m_ChunkX, ToSave.m_ChunkZ);
}
return true;
}

View File

@ -25,7 +25,7 @@
// fwd:
class cWorld;
typedef cQueue<cChunkCoords> cChunkCoordsQueue;
typedef cQueue<cChunkCoordsWithCallback> cChunkCoordsQueue;
@ -64,8 +64,8 @@ public:
cWorldStorage(void);
~cWorldStorage();
void QueueLoadChunk(int a_ChunkX, int a_ChunkZ);
void QueueSaveChunk(int a_ChunkX, int a_ChunkZ);
void QueueLoadChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_Callback = nullptr);
void QueueSaveChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_Callback = nullptr);
void UnqueueLoad(int a_ChunkX, int a_ChunkZ);
void UnqueueSave(const cChunkCoords & a_Chunk);