From a2ffa432b31096f2533ecb50f49ba450b29a2989 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 1 Sep 2019 09:30:00 +0200 Subject: Separated chunk generator from world / plugin interfaces. The generator now only takes care of servicing synchronous "GetChunk(X, Y)" and "GetBiomes(X, Y)" requests. --- src/Bindings/ManualBindings_World.cpp | 4 +- src/CMakeLists.txt | 2 + src/Chunk.cpp | 19 +-- src/ChunkDef.h | 18 +- src/ChunkGeneratorThread.cpp | 264 +++++++++++++++++++++++++++++ src/ChunkGeneratorThread.h | 159 ++++++++++++++++++ src/ChunkMap.cpp | 14 +- src/ChunkSender.cpp | 4 +- src/Generating/ChunkDesc.cpp | 18 +- src/Generating/ChunkDesc.h | 17 +- src/Generating/ChunkGenerator.cpp | 293 ++------------------------------- src/Generating/ChunkGenerator.h | 184 +++------------------ src/Generating/ComposableGenerator.cpp | 84 +++++----- src/Generating/ComposableGenerator.h | 8 +- src/Generating/CompositedHeiGen.h | 2 +- src/Generating/FinishGen.cpp | 1 - src/Generating/Noise3DGenerator.cpp | 6 +- src/Generating/Noise3DGenerator.h | 8 +- src/Generating/StructGen.cpp | 4 +- src/Generating/Trees.cpp | 87 ---------- src/Generating/Trees.h | 8 - src/LightingThread.cpp | 4 +- src/SpawnPrepare.cpp | 4 +- src/World.cpp | 106 ++++++++++-- src/World.h | 22 ++- src/WorldStorage/WorldStorage.cpp | 4 +- tests/Generating/CMakeLists.txt | 126 +++++++++++--- 27 files changed, 782 insertions(+), 688 deletions(-) create mode 100644 src/ChunkGeneratorThread.cpp create mode 100644 src/ChunkGeneratorThread.h diff --git a/src/Bindings/ManualBindings_World.cpp b/src/Bindings/ManualBindings_World.cpp index ee0998f87..17a66b4c2 100644 --- a/src/Bindings/ManualBindings_World.cpp +++ b/src/Bindings/ManualBindings_World.cpp @@ -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; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 072eb6c97..105f234f1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -27,6 +27,7 @@ SET (SRCS ChatColor.cpp Chunk.cpp ChunkData.cpp + ChunkGeneratorThread.cpp ChunkMap.cpp ChunkSender.cpp ChunkStay.cpp @@ -100,6 +101,7 @@ SET (HDRS ChunkData.h ChunkDataCallback.h ChunkDef.h + ChunkGeneratorThread.h ChunkMap.h ChunkSender.h ChunkStay.h diff --git a/src/Chunk.cpp b/src/Chunk.cpp index 41a59c103..7a95bceed 100644 --- a/src/Chunk.cpp +++ b/src/Chunk.cpp @@ -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: @@ -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 { diff --git a/src/ChunkDef.h b/src/ChunkDef.h index bfed62aae..95abdaf58 100644 --- a/src/ChunkDef.h +++ b/src/ChunkDef.h @@ -65,6 +65,12 @@ public: { 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); + } } ; @@ -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), @@ -525,7 +539,7 @@ public: 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; } ; diff --git a/src/ChunkGeneratorThread.cpp b/src/ChunkGeneratorThread.cpp new file mode 100644 index 000000000..a5703bd2c --- /dev/null +++ b/src/ChunkGeneratorThread.cpp @@ -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(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(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(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); +} + + + + + diff --git a/src/ChunkGeneratorThread.h b/src/ChunkGeneratorThread.h new file mode 100644 index 000000000..f7a4b3da3 --- /dev/null +++ b/src/ChunkGeneratorThread.h @@ -0,0 +1,159 @@ +#pragma once + +#include "OSSupport/IsThread.h" + + + + + +// fwd: +class cIniFile; +class cChunkDesc; +class cChunkGenerator; + + + + + +/** Takes requests for generating chunks and processes them in a separate thread one by one. +The requests are not added to the queue if there is already a request with the same coords. +Before generating, the thread checks if the chunk hasn't been already generated. +It is theoretically possible to have multiple generator threads by having multiple instances of this object, +but then it MAY happen that the chunk is generated twice. +If the generator queue is overloaded, the generator skips chunks with no clients in them. */ +class cChunkGeneratorThread : + public cIsThread +{ + using Super = cIsThread; + +public: + + /** The interface through which the plugins are called for their OnChunkGenerating / OnChunkGenerated hooks. */ + class cPluginInterface + { + public: + // Force a virtual destructor + virtual ~cPluginInterface() {} + + /** Called when the chunk is about to be generated. + The generator may be partly or fully overriden by the implementation. */ + virtual void CallHookChunkGenerating(cChunkDesc & a_ChunkDesc) = 0; + + /** Called after the chunk is generated, before it is handed to the chunk sink. + a_ChunkDesc contains the generated chunk data. Implementation may modify this data. */ + virtual void CallHookChunkGenerated(cChunkDesc & a_ChunkDesc) = 0; + } ; + + + /** The interface through which the generated chunks are handed to the cWorld or whoever created us. */ + class cChunkSink + { + public: + // Force a virtual destructor + virtual ~cChunkSink() {} + + /** Called after the chunk has been generated + The interface may store the chunk, send it over network, whatever. + The chunk is not expected to be modified, but the generator will survive if the implementation + changes the data within. All changes are ignored, though. */ + virtual void OnChunkGenerated(cChunkDesc & a_ChunkDesc) = 0; + + /** Called just before the chunk generation is started, + to verify that it hasn't been generated in the meantime. + If this callback returns true, the chunk is not generated. */ + virtual bool IsChunkValid(cChunkCoords a_Coords) = 0; + + /** Called when the generator is overloaded to skip chunks that are no longer needed. + If this callback returns false, the chunk is not generated. */ + virtual bool HasChunkAnyClients(cChunkCoords a_Coords) = 0; + + /** Called to check whether the specified chunk is in the queued state. + Currently used only in Debug-mode asserts. */ + virtual bool IsChunkQueued(cChunkCoords a_Coords) = 0; + } ; + + + cChunkGeneratorThread (void); + virtual ~cChunkGeneratorThread() override; + + /** Read settings from the ini file and initialize in preperation for being started. */ + bool Initialize(cPluginInterface & a_PluginInterface, cChunkSink & a_ChunkSink, cIniFile & a_IniFile); + + void Stop(void); + + /** 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(cChunkCoords a_Coords, bool a_ForceRegeneration, 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(cChunkCoords a_Coords, cChunkDef::BiomeMap & a_BiomeMap); + + void WaitForQueueEmpty(); + + int GetQueueLength() const; + + int GetSeed() const; + + /** 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); + + +private: + + struct QueueItem + { + /** The chunk coords */ + cChunkCoords m_Coords; + + /** Force the regeneration of an already existing chunk */ + bool m_ForceRegeneration; + + /** Callback to call after generating. */ + cChunkCoordCallback * m_Callback; + + QueueItem(cChunkCoords a_Coords, bool a_ForceRegeneration, cChunkCoordCallback * a_Callback): + m_Coords(a_Coords), + m_ForceRegeneration(a_ForceRegeneration), + m_Callback(a_Callback) + { + } + }; + + using Queue = std::list; + + + /** CS protecting access to the queue. */ + mutable cCriticalSection m_CS; + + /** Queue of the chunks to be generated. Protected against multithreaded access by m_CS. */ + Queue 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; + + /** The actual chunk generator engine used. */ + std::unique_ptr m_Generator; + + /** The plugin interface that may modify the generated chunks */ + cPluginInterface * m_PluginInterface; + + /** The destination where the generated chunks are sent */ + cChunkSink * m_ChunkSink; + + + // cIsThread override: + virtual void Execute(void) override; + + /** Generates the specified chunk and sets it into the chunksink. */ + void DoGenerate(cChunkCoords a_Coords); +}; + + + + diff --git a/src/ChunkMap.cpp b/src/ChunkMap.cpp index dd8f1add1..b5a6252ff 100644 --- a/src/ChunkMap.cpp +++ b/src/ChunkMap.cpp @@ -1798,7 +1798,7 @@ void cChunkMap::PrepareChunk(int a_ChunkX, int a_ChunkZ, std::unique_ptrCall(a_ChunkX, a_ChunkZ, true); + a_Callback->Call({a_ChunkX, a_ChunkZ}, true); } } @@ -1831,34 +1831,34 @@ bool cChunkMap::GenerateChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * } // cChunkCoordCallback override: - virtual void Call(int a_CBChunkX, int a_CBChunkZ, bool a_CBIsSuccess) override + virtual void Call(cChunkCoords a_Coords, bool a_CBIsSuccess) override { // If success is reported, the chunk is already valid, no need to do anything else: if (a_CBIsSuccess) { if (m_Callback != nullptr) { - m_Callback->Call(a_CBChunkX, a_CBChunkZ, true); + m_Callback->Call(a_Coords, true); } return; } // The chunk failed to load, generate it: cCSLock CBLock(m_ChunkMap.m_CSChunks); - cChunkPtr CBChunk = m_ChunkMap.GetChunkNoLoad(a_CBChunkX, a_CBChunkZ); + cChunkPtr CBChunk = m_ChunkMap.GetChunkNoLoad(a_Coords.m_ChunkX, a_Coords.m_ChunkZ); 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, false); + m_Callback->Call(a_Coords, false); } return; } CBChunk->SetPresence(cChunk::cpQueued); - m_World.GetGenerator().QueueGenerateChunk(a_CBChunkX, a_CBChunkZ, false, m_Callback); + m_World.GetGenerator().QueueGenerateChunk(a_Coords, false, m_Callback); } protected: @@ -1873,7 +1873,7 @@ bool cChunkMap::GenerateChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * // The chunk is valid, just call the callback: if (a_Callback != nullptr) { - a_Callback->Call(a_ChunkX, a_ChunkZ, true); + a_Callback->Call({a_ChunkX, a_ChunkZ}, true); } return true; } diff --git a/src/ChunkSender.cpp b/src/ChunkSender.cpp index c5d56f354..a11852ed6 100644 --- a/src/ChunkSender.cpp +++ b/src/ChunkSender.cpp @@ -27,11 +27,11 @@ class cNotifyChunkSender : public cChunkCoordCallback { - virtual void Call(int a_ChunkX, int a_ChunkZ, bool a_IsSuccess) override + virtual void Call(cChunkCoords a_Coords, bool a_IsSuccess) override { cChunkSender & ChunkSender = m_ChunkSender; m_World.DoWithChunk( - a_ChunkX, a_ChunkZ, + a_Coords.m_ChunkX, a_Coords.m_ChunkZ, [&ChunkSender] (cChunk & a_Chunk) -> bool { ChunkSender.QueueSendChunkTo(a_Chunk.GetPosX(), a_Chunk.GetPosZ(), cChunkSender::E_CHUNK_PRIORITY_MIDHIGH, a_Chunk.GetAllClients()); diff --git a/src/Generating/ChunkDesc.cpp b/src/Generating/ChunkDesc.cpp index e5d213fe6..18947f401 100644 --- a/src/Generating/ChunkDesc.cpp +++ b/src/Generating/ChunkDesc.cpp @@ -13,9 +13,8 @@ -cChunkDesc::cChunkDesc(int a_ChunkX, int a_ChunkZ) : - m_ChunkX(a_ChunkX), - m_ChunkZ(a_ChunkZ), +cChunkDesc::cChunkDesc(cChunkCoords a_Coords) : + m_Coords(a_Coords), m_bUseDefaultBiomes(true), m_bUseDefaultHeight(true), m_bUseDefaultComposition(true), @@ -43,10 +42,9 @@ cChunkDesc::~cChunkDesc() -void cChunkDesc::SetChunkCoords(int a_ChunkX, int a_ChunkZ) +void cChunkDesc::SetChunkCoords(cChunkCoords a_Coords) { - m_ChunkX = a_ChunkX; - m_ChunkZ = a_ChunkZ; + m_Coords = a_Coords; } @@ -369,9 +367,9 @@ void cChunkDesc::ReadBlockArea(cBlockArea & a_Dest, int a_MinRelX, int a_MaxRelX int SizeY = a_MaxRelY - a_MinRelY; int SizeZ = a_MaxRelZ - a_MinRelZ; a_Dest.Clear(); - a_Dest.m_Origin.x = m_ChunkX * cChunkDef::Width + a_MinRelX; + a_Dest.m_Origin.x = m_Coords.m_ChunkX * cChunkDef::Width + a_MinRelX; a_Dest.m_Origin.y = a_MinRelY; - a_Dest.m_Origin.z = m_ChunkZ * cChunkDef::Width + a_MinRelZ; + a_Dest.m_Origin.z = m_Coords.m_ChunkZ * cChunkDef::Width + a_MinRelZ; a_Dest.SetSize(SizeX, SizeY, SizeZ, cBlockArea::baTypes | cBlockArea::baMetas); for (int y = 0; y < SizeY; y++) @@ -593,8 +591,8 @@ cBlockEntity * cChunkDesc::GetBlockEntity(int a_RelX, int a_RelY, int a_RelZ) } } - int AbsX = a_RelX + m_ChunkX * cChunkDef::Width; - int AbsZ = a_RelZ + m_ChunkZ * cChunkDef::Width; + int AbsX = a_RelX + m_Coords.m_ChunkX * cChunkDef::Width; + int AbsZ = a_RelZ + m_Coords.m_ChunkZ * cChunkDef::Width; // The block entity is not created yet, try to create it and add to list: cBlockEntity * be = cBlockEntity::CreateByBlockType(GetBlockType(a_RelX, a_RelY, a_RelZ), GetBlockMeta(a_RelX, a_RelY, a_RelZ), AbsX, a_RelY, AbsZ); diff --git a/src/Generating/ChunkDesc.h b/src/Generating/ChunkDesc.h index 709fccb70..997b640a3 100644 --- a/src/Generating/ChunkDesc.h +++ b/src/Generating/ChunkDesc.h @@ -39,15 +39,21 @@ public: typedef NIBBLETYPE BlockNibbleBytes[cChunkDef::NumBlocks]; - cChunkDesc(int a_ChunkX, int a_ChunkZ); + cChunkDesc(cChunkCoords a_Coords); ~cChunkDesc(); - void SetChunkCoords(int a_ChunkX, int a_ChunkZ); + void SetChunkCoords(cChunkCoords a_Coords); // tolua_begin - int GetChunkX(void) const { return m_ChunkX; } - int GetChunkZ(void) const { return m_ChunkZ; } + int GetChunkX() const { return m_Coords.m_ChunkX; } // Prefer GetChunkCoords() instead + int GetChunkZ() const { return m_Coords.m_ChunkZ; } // Prefer GetChunkCoords() instead + + // tolua_end + + cChunkCoords GetChunkCoords() const { return m_Coords; } + + // tolua_begin void FillBlocks(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); void SetBlockTypeMeta(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); @@ -225,8 +231,7 @@ public: #endif // _DEBUG private: - int m_ChunkX; - int m_ChunkZ; + cChunkCoords m_Coords; cChunkDef::BiomeMap m_BiomeMap; cBlockArea m_BlockArea; diff --git a/src/Generating/ChunkGenerator.cpp b/src/Generating/ChunkGenerator.cpp index 166a05ca2..d426ca19c 100644 --- a/src/Generating/ChunkGenerator.cpp +++ b/src/Generating/ChunkGenerator.cpp @@ -12,46 +12,8 @@ -/** If the generation queue size exceeds this number, a warning will be output */ -const unsigned int QUEUE_WARNING_LIMIT = 1000; - -/** If the generation queue size exceeds this number, chunks with no clients will be skipped */ -const unsigned int QUEUE_SKIP_LIMIT = 500; - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cChunkGenerator: - -cChunkGenerator::cChunkGenerator(void) : - super("cChunkGenerator"), - m_Seed(0), // Will be overwritten by the actual generator - m_Generator(nullptr), - m_PluginInterface(nullptr), - m_ChunkSink(nullptr) -{ -} - - - - - -cChunkGenerator::~cChunkGenerator() -{ - Stop(); -} - - - - - -bool cChunkGenerator::Initialize(cPluginInterface & a_PluginInterface, cChunkSink & a_ChunkSink, cIniFile & a_IniFile) +void cChunkGenerator::Initialize(cIniFile & a_IniFile) { - m_PluginInterface = &a_PluginInterface; - m_ChunkSink = &a_ChunkSink; - // Get the seed; create a new one and log it if not found in the INI file: if (a_IniFile.HasValue("Seed", "Seed")) { @@ -63,12 +25,20 @@ bool cChunkGenerator::Initialize(cPluginInterface & a_PluginInterface, cChunkSin LOGINFO("Chosen a new random seed for world: %d", m_Seed); a_IniFile.SetValueI("Seed", "Seed", m_Seed); } +} + + + + +std::unique_ptr cChunkGenerator::CreateFromIniFile(cIniFile & a_IniFile) +{ // Get the generator engine based on the INI file settings: + std::unique_ptr res; AString GeneratorName = a_IniFile.GetValueSet("Generator", "Generator", "Composable"); if (NoCaseCompare(GeneratorName, "Noise3D") == 0) { - m_Generator = new cNoise3DGenerator(*this); + res.reset(new cNoise3DGenerator()); } else { @@ -76,90 +46,17 @@ bool cChunkGenerator::Initialize(cPluginInterface & a_PluginInterface, cChunkSin { LOGWARN("[Generator]::Generator value \"%s\" not recognized, using \"Composable\".", GeneratorName.c_str()); } - m_Generator = new cComposableGenerator(*this); + res.reset(new cComposableGenerator()); } - if (m_Generator == nullptr) + if (res == nullptr) { LOGERROR("Generator could not start, aborting the server"); - return false; - } - - m_Generator->Initialize(a_IniFile); - return true; -} - - - - - -void cChunkGenerator::Stop(void) -{ - m_ShouldTerminate = true; - m_Event.Set(); - m_evtRemoved.Set(); // Wake up anybody waiting for empty queue - super::Stop(); - - delete m_Generator; - m_Generator = nullptr; -} - - - - - -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); - - // 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! (%zu)", a_ChunkX, a_ChunkZ, m_Queue.size()); - } - m_Queue.push_back(cQueueItem{a_ChunkX, a_ChunkZ, a_ForceGenerate, a_Callback}); - } - - m_Event.Set(); -} - - - - - -void cChunkGenerator::GenerateBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) -{ - if (m_Generator != nullptr) - { - m_Generator->GenerateBiomes(a_ChunkX, a_ChunkZ, a_BiomeMap); + return nullptr; } -} - - - - -void cChunkGenerator::WaitForQueueEmpty(void) -{ - cCSLock Lock(m_CS); - while (!m_ShouldTerminate && !m_Queue.empty()) - { - cCSUnlock Unlock(Lock); - m_evtRemoved.Wait(); - } -} - - - - - -int cChunkGenerator::GetQueueLength(void) -{ - cCSLock Lock(m_CS); - return static_cast(m_Queue.size()); + res->Initialize(a_IniFile); + return res; } @@ -167,166 +64,6 @@ int cChunkGenerator::GetQueueLength(void) EMCSBiome cChunkGenerator::GetBiomeAt(int a_BlockX, int a_BlockZ) -{ - ASSERT(m_Generator != nullptr); - return m_Generator->GetBiomeAt(a_BlockX, a_BlockZ); -} - - - - - -BLOCKTYPE cChunkGenerator::GetIniBlock(cIniFile & a_IniFile, const AString & a_SectionName, const AString & a_ValueName, const AString & a_Default) -{ - AString BlockType = a_IniFile.GetValueSet(a_SectionName, a_ValueName, a_Default); - int Block = BlockStringToType(BlockType); - if (Block < 0) - { - LOGWARN("[%s].%s Could not parse block value \"%s\". Using default: \"%s\".", a_SectionName.c_str(), a_ValueName.c_str(), BlockType.c_str(), a_Default.c_str()); - return static_cast(BlockStringToType(a_Default)); - } - return static_cast(Block); -} - - - - - -void cChunkGenerator::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(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; - } - - 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 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(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_ForceGenerate && m_ChunkSink->IsChunkValid(item.m_ChunkX, item.m_ChunkZ)) - { - 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, true); - } - continue; - } - - // 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]", item.m_ChunkX, item.m_ChunkZ); - if (item.m_Callback != nullptr) - { - item.m_Callback->Call(item.m_ChunkX, item.m_ChunkZ, false); - } - continue; - } - - // 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, true); - } - NumChunksGenerated++; - } // while (!bStop) -} - - - - - -void cChunkGenerator::DoGenerate(int a_ChunkX, int a_ChunkZ) -{ - ASSERT(m_PluginInterface != nullptr); - ASSERT(m_ChunkSink != nullptr); - - cChunkDesc ChunkDesc(a_ChunkX, a_ChunkZ); - m_PluginInterface->CallHookChunkGenerating(ChunkDesc); - m_Generator->DoGenerate(a_ChunkX, a_ChunkZ, ChunkDesc); - m_PluginInterface->CallHookChunkGenerated(ChunkDesc); - - #ifdef _DEBUG - // Verify that the generator has produced valid data: - ChunkDesc.VerifyHeightmap(); - #endif - - m_ChunkSink->OnChunkGenerated(ChunkDesc); -} - - - - - -//////////////////////////////////////////////////////////////////////////////// -// cChunkGenerator::cGenerator: - -cChunkGenerator::cGenerator::cGenerator(cChunkGenerator & a_ChunkGenerator) : - m_ChunkGenerator(a_ChunkGenerator) -{ -} - - - - - -void cChunkGenerator::cGenerator::Initialize(cIniFile & a_IniFile) -{ - UNUSED(a_IniFile); -} - - - - - -EMCSBiome cChunkGenerator::cGenerator::GetBiomeAt(int a_BlockX, int a_BlockZ) { cChunkDef::BiomeMap Biomes; int Y = 0; diff --git a/src/Generating/ChunkGenerator.h b/src/Generating/ChunkGenerator.h index c4be505da..d11e846dc 100644 --- a/src/Generating/ChunkGenerator.h +++ b/src/Generating/ChunkGenerator.h @@ -1,25 +1,5 @@ - -// ChunkGenerator.h - -// Interfaces to the cChunkGenerator class representing the thread that generates chunks - -/* -The object takes requests for generating chunks and processes them in a separate thread one by one. -The requests are not added to the queue if there is already a request with the same coords -Before generating, the thread checks if the chunk hasn't been already generated. -It is theoretically possible to have multiple generator threads by having multiple instances of this object, -but then it MAY happen that the chunk is generated twice. -If the generator queue is overloaded, the generator skips chunks with no clients in them -*/ - - - - - #pragma once -#include "../OSSupport/IsThread.h" - @@ -32,158 +12,44 @@ class cChunkDesc; -class cChunkGenerator : - public cIsThread +/** The interface that all chunk generators must implement to provide the generated chunks. +Also a static factory that creates the descendants based on INI file settings. +The cChunkGeneratorThread uses this interface to generate chunks for a single world. +Ths calls to generate chunks are synchronous - they don't return until the chunk is fully generated. */ +class cChunkGenerator { - typedef cIsThread super; - public: - /** The interface that a class has to implement to become a generator */ - class cGenerator - { - public: - cGenerator(cChunkGenerator & a_ChunkGenerator); - virtual ~cGenerator() {} // Force a virtual destructor - - /** Called to initialize the generator on server startup. */ - virtual void Initialize(cIniFile & a_IniFile); - - /** Generates the biomes for the specified chunk (directly, not in a separate thread). Used by the world loader if biomes failed loading. */ - virtual void GenerateBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) = 0; - - /** Returns the biome at the specified coords. Used by ChunkMap if an invalid chunk is queried for biome. Default implementation uses GenerateBiomes(). */ - virtual EMCSBiome GetBiomeAt(int a_BlockX, int a_BlockZ); - - /** Called in a separate thread to do the actual chunk generation. Generator should generate into a_ChunkDesc. */ - virtual void DoGenerate(int a_ChunkX, int a_ChunkZ, cChunkDesc & a_ChunkDesc) = 0; - - protected: - cChunkGenerator & m_ChunkGenerator; - } ; - - - /** The interface through which the plugins are called for their OnChunkGenerating / OnChunkGenerated hooks. */ - class cPluginInterface - { - public: - // Force a virtual destructor - virtual ~cPluginInterface() {} - - /** Called when the chunk is about to be generated. - The generator may be partly or fully overriden by the implementation. */ - virtual void CallHookChunkGenerating(cChunkDesc & a_ChunkDesc) = 0; - - /** Called after the chunk is generated, before it is handed to the chunk sink. - a_ChunkDesc contains the generated chunk data. Implementation may modify this data. */ - virtual void CallHookChunkGenerated(cChunkDesc & a_ChunkDesc) = 0; - } ; - - - /** The interface through which the generated chunks are handed to the cWorld or whoever created us. */ - class cChunkSink - { - public: - // Force a virtual destructor - virtual ~cChunkSink() {} - - /** Called after the chunk has been generated - The interface may store the chunk, send it over network, whatever. - The chunk is not expected to be modified, but the generator will survive if the implementation - changes the data within. All changes are ignored, though. */ - virtual void OnChunkGenerated(cChunkDesc & a_ChunkDesc) = 0; + virtual ~cChunkGenerator() {} // Force a virtual destructor - /** Called just before the chunk generation is started, - to verify that it hasn't been generated in the meantime. - If this callback returns true, the chunk is not generated. */ - virtual bool IsChunkValid(int a_ChunkX, int a_ChunkZ) = 0; + /** Called to initialize the generator on server startup. + Descendants should call Super::Initialize() before initializing themselves. */ + virtual void Initialize(cIniFile & a_IniFile); - /** Called when the generator is overloaded to skip chunks that are no longer needed. - If this callback returns false, the chunk is not generated. */ - virtual bool HasChunkAnyClients(int a_ChunkX, int a_ChunkZ) = 0; + /** Generates the biomes for the specified chunk. + Used by the world loader if biomes failed loading. */ + virtual void GenerateBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) = 0; - /** Called to check whether the specified chunk is in the queued state. - Currently used only in Debug-mode asserts. */ - virtual bool IsChunkQueued(int a_ChunkX, int a_ChunkZ) = 0; - } ; + /** Returns the biome at the specified coords. + Used by ChunkMap if an invalid chunk is queried for biome. + The default implementation uses GenerateBiomes(). */ + virtual EMCSBiome GetBiomeAt(int a_BlockX, int a_BlockZ); + /** Does the actual chunk generation. + Descendants need to override this and generate into a_ChunkDesc. */ + virtual void Generate(int a_ChunkX, int a_ChunkZ, cChunkDesc & a_ChunkDesc) = 0; - cChunkGenerator (void); - virtual ~cChunkGenerator() override; - - /** Read settings from the ini file and initialize in preperation for being started. */ - bool Initialize(cPluginInterface & a_PluginInterface, cChunkSink & a_ChunkSink, cIniFile & a_IniFile); - - void Stop(void); - - /** 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); - - void WaitForQueueEmpty(void); - - int GetQueueLength(void); - + /** Returns the seed that was read from the INI file. */ 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 */ - 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. */ - 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; + /** Creates and initializes the entire generator based on the settings in the INI file. + Initializes the generator, so that it can be used immediately after this call returns. */ + static std::unique_ptr CreateFromIniFile(cIniFile & a_IniFile); - /** Callback to call after generating. */ - cChunkCoordCallback * m_Callback; - }; - typedef std::list cGenQueue; +protected: - - /** Seed used for the generator. */ + /** The main seed, read from the INI file, used for the entire generator. */ int m_Seed; - - /** 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; - - /** The actual generator engine used to generate chunks. */ - cGenerator * m_Generator; - - /** The plugin interface that may modify the generated chunks */ - cPluginInterface * m_PluginInterface; - - /** The destination where the generated chunks are sent */ - cChunkSink * m_ChunkSink; - - - // 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); }; diff --git a/src/Generating/ComposableGenerator.cpp b/src/Generating/ComposableGenerator.cpp index c73ecf6cc..1cb3ed7c9 100644 --- a/src/Generating/ComposableGenerator.cpp +++ b/src/Generating/ComposableGenerator.cpp @@ -6,9 +6,7 @@ #include "Globals.h" #include "ComposableGenerator.h" -#include "../World.h" #include "../IniFile.h" -#include "../Root.h" // Individual composed algorithms: #include "BioGen.h" @@ -110,8 +108,7 @@ cTerrainCompositionGenPtr cTerrainCompositionGen::CreateCompositionGen(cIniFile //////////////////////////////////////////////////////////////////////////////// // cComposableGenerator: -cComposableGenerator::cComposableGenerator(cChunkGenerator & a_ChunkGenerator) : - super(a_ChunkGenerator), +cComposableGenerator::cComposableGenerator(): m_BiomeGen(), m_ShapeGen(), m_CompositionGen() @@ -124,7 +121,7 @@ cComposableGenerator::cComposableGenerator(cChunkGenerator & a_ChunkGenerator) : void cComposableGenerator::Initialize(cIniFile & a_IniFile) { - super::Initialize(a_IniFile); + Super::Initialize(a_IniFile); InitBiomeGen(a_IniFile); InitShapeGen(a_IniFile); @@ -148,7 +145,7 @@ void cComposableGenerator::GenerateBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef: -void cComposableGenerator::DoGenerate(int a_ChunkX, int a_ChunkZ, cChunkDesc & a_ChunkDesc) +void cComposableGenerator::Generate(int a_ChunkX, int a_ChunkZ, cChunkDesc & a_ChunkDesc) { if (a_ChunkDesc.IsUsingDefaultBiomes()) { @@ -195,7 +192,7 @@ void cComposableGenerator::DoGenerate(int a_ChunkX, int a_ChunkZ, cChunkDesc & a void cComposableGenerator::InitBiomeGen(cIniFile & a_IniFile) { bool CacheOffByDefault = false; - m_BiomeGen = cBiomeGen::CreateBiomeGen(a_IniFile, m_ChunkGenerator.GetSeed(), CacheOffByDefault); + m_BiomeGen = cBiomeGen::CreateBiomeGen(a_IniFile, m_Seed, CacheOffByDefault); // Add a cache, if requested: // The default is 16 * 128 caches, which is 2 MiB of RAM. Reasonable, for the amount of work this is saving. @@ -231,7 +228,7 @@ void cComposableGenerator::InitBiomeGen(cIniFile & a_IniFile) void cComposableGenerator::InitShapeGen(cIniFile & a_IniFile) { bool CacheOffByDefault = false; - m_ShapeGen = cTerrainShapeGen::CreateShapeGen(a_IniFile, m_BiomeGen, m_ChunkGenerator.GetSeed(), CacheOffByDefault); + m_ShapeGen = cTerrainShapeGen::CreateShapeGen(a_IniFile, m_BiomeGen, m_Seed, CacheOffByDefault); /* // TODO @@ -258,7 +255,7 @@ void cComposableGenerator::InitShapeGen(cIniFile & a_IniFile) void cComposableGenerator::InitCompositionGen(cIniFile & a_IniFile) { - m_CompositionGen = cTerrainCompositionGen::CreateCompositionGen(a_IniFile, m_BiomeGen, m_ShapeGen, m_ChunkGenerator.GetSeed()); + m_CompositionGen = cTerrainCompositionGen::CreateCompositionGen(a_IniFile, m_BiomeGen, m_ShapeGen, m_Seed); // Add a cache over the composition generator: // Even a cache of size 1 is useful due to the CompositedHeiGen cache after us doing re-composition on its misses @@ -279,7 +276,6 @@ void cComposableGenerator::InitCompositionGen(cIniFile & a_IniFile) void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile) { - int Seed = m_ChunkGenerator.GetSeed(); eDimension Dimension = StringToDimension(a_IniFile.GetValue("General", "Dimension", "Overworld")); auto seaLevel = a_IniFile.GetValueI("Generator", "SeaLevel"); @@ -298,7 +294,7 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile) // Finishers, alpha-sorted: if (NoCaseCompare(finisher, "Animals") == 0) { - m_FinishGens.push_back(cFinishGenPtr(new cFinishGenPassiveMobs(Seed, a_IniFile, Dimension))); + m_FinishGens.push_back(cFinishGenPtr(new cFinishGenPassiveMobs(m_Seed, a_IniFile, Dimension))); } else if (NoCaseCompare(finisher, "BottomLava") == 0) { @@ -327,15 +323,15 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile) AllowedBlocks.push_back(E_BLOCK_HARDENED_CLAY); AllowedBlocks.push_back(E_BLOCK_STAINED_CLAY); - m_FinishGens.push_back(cFinishGenPtr(new cFinishGenSingleTopBlock(Seed, E_BLOCK_DEAD_BUSH, AllowedBiomes, 2, AllowedBlocks))); + m_FinishGens.push_back(cFinishGenPtr(new cFinishGenSingleTopBlock(m_Seed, E_BLOCK_DEAD_BUSH, AllowedBiomes, 2, AllowedBlocks))); } else if (NoCaseCompare(finisher, "DirectOverhangs") == 0) { - m_FinishGens.push_back(cFinishGenPtr(new cStructGenDirectOverhangs(Seed))); + m_FinishGens.push_back(cFinishGenPtr(new cStructGenDirectOverhangs(m_Seed))); } else if (NoCaseCompare(finisher, "DirtPockets") == 0) { - auto gen = std::make_shared(Seed + 1, cFinishGenOrePockets::DefaultNaturalPatches()); + auto gen = std::make_shared(m_Seed + 1, cFinishGenOrePockets::DefaultNaturalPatches()); if (gen->Initialize(a_IniFile, "DirtPockets")) { m_FinishGens.push_back(gen); @@ -343,12 +339,12 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile) } else if (NoCaseCompare(finisher, "DistortedMembraneOverhangs") == 0) { - m_FinishGens.push_back(cFinishGenPtr(new cStructGenDistortedMembraneOverhangs(Seed))); + m_FinishGens.push_back(cFinishGenPtr(new cStructGenDistortedMembraneOverhangs(m_Seed))); } else if (NoCaseCompare(finisher, "DualRidgeCaves") == 0) { float Threshold = static_cast(a_IniFile.GetValueSetF("Generator", "DualRidgeCavesThreshold", 0.3)); - m_FinishGens.push_back(cFinishGenPtr(new cStructGenDualRidgeCaves(Seed, Threshold))); + m_FinishGens.push_back(cFinishGenPtr(new cStructGenDualRidgeCaves(m_Seed, Threshold))); } else if (NoCaseCompare(finisher, "DungeonRooms") == 0) { @@ -356,11 +352,11 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile) int MaxSize = a_IniFile.GetValueSetI("Generator", "DungeonRoomsMaxSize", 7); int MinSize = a_IniFile.GetValueSetI("Generator", "DungeonRoomsMinSize", 5); AString HeightDistrib = a_IniFile.GetValueSet ("Generator", "DungeonRoomsHeightDistrib", "0, 0; 10, 10; 11, 500; 40, 500; 60, 40; 90, 1"); - m_FinishGens.push_back(cFinishGenPtr(new cDungeonRoomsFinisher(m_ShapeGen, Seed, GridSize, MaxSize, MinSize, HeightDistrib))); + m_FinishGens.push_back(cFinishGenPtr(new cDungeonRoomsFinisher(m_ShapeGen, m_Seed, GridSize, MaxSize, MinSize, HeightDistrib))); } else if (NoCaseCompare(finisher, "GlowStone") == 0) { - m_FinishGens.push_back(cFinishGenPtr(new cFinishGenGlowStone(Seed))); + m_FinishGens.push_back(cFinishGenPtr(new cFinishGenGlowStone(m_Seed))); } else if (NoCaseCompare(finisher, "Ice") == 0) { @@ -369,11 +365,11 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile) else if (NoCaseCompare(finisher, "LavaLakes") == 0) { int Probability = a_IniFile.GetValueSetI("Generator", "LavaLakesProbability", 10); - m_FinishGens.push_back(cFinishGenPtr(new cStructGenLakes(Seed * 5 + 16873, E_BLOCK_STATIONARY_LAVA, m_ShapeGen, Probability))); + m_FinishGens.push_back(cFinishGenPtr(new cStructGenLakes(m_Seed * 5 + 16873, E_BLOCK_STATIONARY_LAVA, m_ShapeGen, Probability))); } else if (NoCaseCompare(finisher, "LavaSprings") == 0) { - m_FinishGens.push_back(cFinishGenPtr(new cFinishGenFluidSprings(Seed, E_BLOCK_LAVA, a_IniFile, Dimension))); + m_FinishGens.push_back(cFinishGenPtr(new cFinishGenFluidSprings(m_Seed, E_BLOCK_LAVA, a_IniFile, Dimension))); } else if (NoCaseCompare(finisher, "Lilypads") == 0) { @@ -387,11 +383,11 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile) AllowedBlocks.push_back(E_BLOCK_WATER); AllowedBlocks.push_back(E_BLOCK_STATIONARY_WATER); - m_FinishGens.push_back(cFinishGenPtr(new cFinishGenSingleTopBlock(Seed, E_BLOCK_LILY_PAD, AllowedBiomes, 4, AllowedBlocks))); + m_FinishGens.push_back(cFinishGenPtr(new cFinishGenSingleTopBlock(m_Seed, E_BLOCK_LILY_PAD, AllowedBiomes, 4, AllowedBlocks))); } else if (NoCaseCompare(finisher, "MarbleCaves") == 0) { - m_FinishGens.push_back(cFinishGenPtr(new cStructGenMarbleCaves(Seed))); + m_FinishGens.push_back(cFinishGenPtr(new cStructGenMarbleCaves(m_Seed))); } else if (NoCaseCompare(finisher, "MineShafts") == 0) { @@ -402,22 +398,22 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile) int ChanceCrossing = a_IniFile.GetValueSetI("Generator", "MineShaftsChanceCrossing", 200); int ChanceStaircase = a_IniFile.GetValueSetI("Generator", "MineShaftsChanceStaircase", 200); m_FinishGens.push_back(cFinishGenPtr(new cStructGenMineShafts( - Seed, GridSize, MaxOffset, MaxSystemSize, + m_Seed, GridSize, MaxOffset, MaxSystemSize, ChanceCorridor, ChanceCrossing, ChanceStaircase ))); } else if (NoCaseCompare(finisher, "NaturalPatches") == 0) { - m_FinishGens.push_back(std::make_shared(Seed + 1, cFinishGenOreNests::DefaultNaturalPatches())); + m_FinishGens.push_back(std::make_shared(m_Seed + 1, cFinishGenOreNests::DefaultNaturalPatches())); } else if (NoCaseCompare(finisher, "NetherClumpFoliage") == 0) { - m_FinishGens.push_back(cFinishGenPtr(new cFinishGenNetherClumpFoliage(Seed))); + m_FinishGens.push_back(cFinishGenPtr(new cFinishGenNetherClumpFoliage(m_Seed))); } else if (NoCaseCompare(*itr, "NetherForts") == 0) { LOGINFO("The NetherForts finisher is obsolete, you should use \"PieceStructures: NetherFort\" instead."); - auto gen = std::make_shared(Seed); + auto gen = std::make_shared(m_Seed); if (gen->Initialize("NetherFort", seaLevel, m_BiomeGen, m_CompositedHeightCache)) { m_FinishGens.push_back(gen); @@ -425,15 +421,15 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile) } else if (NoCaseCompare(finisher, "NetherOreNests") == 0) { - m_FinishGens.push_back(std::make_shared(Seed + 2, cFinishGenOreNests::DefaultNetherOres())); + m_FinishGens.push_back(std::make_shared(m_Seed + 2, cFinishGenOreNests::DefaultNetherOres())); } else if (NoCaseCompare(finisher, "OreNests") == 0) { - m_FinishGens.push_back(std::make_shared(Seed + 3, cFinishGenOreNests::DefaultOverworldOres())); + m_FinishGens.push_back(std::make_shared(m_Seed + 3, cFinishGenOreNests::DefaultOverworldOres())); } else if (NoCaseCompare(finisher, "OrePockets") == 0) { - auto gen = std::make_shared(Seed + 2, cFinishGenOrePockets::DefaultOverworldOres()); + auto gen = std::make_shared(m_Seed + 2, cFinishGenOrePockets::DefaultOverworldOres()); if (gen->Initialize(a_IniFile, "OrePockets")) { m_FinishGens.push_back(gen); @@ -442,7 +438,7 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile) else if (NoCaseCompare(finisher, "OverworldClumpFlowers") == 0) { auto flowers = cFinishGenClumpTopBlock::ParseIniFile(a_IniFile, "OverworldClumpFlowers"); - m_FinishGens.push_back(cFinishGenPtr(new cFinishGenClumpTopBlock(Seed, flowers))); + m_FinishGens.push_back(cFinishGenPtr(new cFinishGenClumpTopBlock(m_Seed, flowers))); } else if (NoCaseCompare(finisher, "PieceStructures") == 0) { @@ -452,7 +448,7 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile) continue; } - auto gen = std::make_shared(Seed); + auto gen = std::make_shared(m_Seed); if (gen->Initialize(split[1], seaLevel, m_BiomeGen, m_CompositedHeightCache)) { m_FinishGens.push_back(gen); @@ -470,7 +466,7 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile) else if (NoCaseCompare(finisher, "RainbowRoads") == 0) { LOGINFO("The RainbowRoads finisher is obsolete, you should use \"PieceStructures: RainbowRoads\" instead."); - auto gen = std::make_shared(Seed); + auto gen = std::make_shared(m_Seed); if (gen->Initialize("RainbowRoads", seaLevel, m_BiomeGen, m_CompositedHeightCache)) { m_FinishGens.push_back(gen); @@ -478,7 +474,7 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile) } else if (NoCaseCompare(finisher, "Ravines") == 0) { - m_FinishGens.push_back(cFinishGenPtr(new cStructGenRavines(Seed, 128))); + m_FinishGens.push_back(cFinishGenPtr(new cStructGenRavines(m_Seed, 128))); } else if (NoCaseCompare(finisher, "RoughRavines") == 0) { @@ -499,7 +495,7 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile) double MaxCeilingHeightCenter = a_IniFile.GetValueSetF("Generator", "RoughRavinesMaxCeilingHeightCenter", 58); double MinCeilingHeightCenter = a_IniFile.GetValueSetF("Generator", "RoughRavinesMinCeilingHeightCenter", 36); m_FinishGens.push_back(cFinishGenPtr(new cRoughRavines( - Seed, MaxSize, MinSize, + m_Seed, MaxSize, MinSize, static_cast(MaxCenterWidth), static_cast(MinCenterWidth), static_cast(MaxRoughness), @@ -517,7 +513,7 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile) } else if (NoCaseCompare(finisher, "SoulsandRims") == 0) { - m_FinishGens.push_back(cFinishGenPtr(new cFinishGenSoulsandRims(Seed))); + m_FinishGens.push_back(cFinishGenPtr(new cFinishGenSoulsandRims(m_Seed))); } else if (NoCaseCompare(finisher, "Snow") == 0) { @@ -527,20 +523,20 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile) { int MaxCactusHeight = a_IniFile.GetValueI("Plants", "MaxCactusHeight", 3); int MaxSugarcaneHeight = a_IniFile.GetValueI("Plants", "MaxSugarcaneHeight", 3); - m_FinishGens.push_back(std::make_shared(Seed, MaxCactusHeight, MaxSugarcaneHeight)); + m_FinishGens.push_back(std::make_shared(m_Seed, MaxCactusHeight, MaxSugarcaneHeight)); } else if (NoCaseCompare(finisher, "TallGrass") == 0) { - m_FinishGens.push_back(cFinishGenPtr(new cFinishGenTallGrass(Seed))); + m_FinishGens.push_back(cFinishGenPtr(new cFinishGenTallGrass(m_Seed))); } else if (NoCaseCompare(finisher, "Trees") == 0) { - m_FinishGens.push_back(cFinishGenPtr(new cStructGenTrees(Seed, m_BiomeGen, m_ShapeGen, m_CompositionGen))); + m_FinishGens.push_back(cFinishGenPtr(new cStructGenTrees(m_Seed, m_BiomeGen, m_ShapeGen, m_CompositionGen))); } else if (NoCaseCompare(finisher, "UnderwaterBases") == 0) { LOGINFO("The UnderwaterBases finisher is obsolete, you should use \"PieceStructures: UnderwaterBases\" instead."); - auto gen = std::make_shared(Seed); + auto gen = std::make_shared(m_Seed); if (gen->Initialize("UnderwaterBases", seaLevel, m_BiomeGen, m_CompositedHeightCache)) { m_FinishGens.push_back(gen); @@ -556,28 +552,28 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile) int MaxDensity = a_IniFile.GetValueSetI("Generator", "VillageMaxDensity", 80); AString PrefabList = a_IniFile.GetValueSet("Generator", "VillagePrefabs", "PlainsVillage, SandVillage"); auto Prefabs = StringSplitAndTrim(PrefabList, ","); - m_FinishGens.push_back(std::make_shared(Seed, GridSize, MaxOffset, MaxDepth, MaxSize, MinDensity, MaxDensity, m_BiomeGen, m_CompositedHeightCache, seaLevel, Prefabs)); + m_FinishGens.push_back(std::make_shared(m_Seed, GridSize, MaxOffset, MaxDepth, MaxSize, MinDensity, MaxDensity, m_BiomeGen, m_CompositedHeightCache, seaLevel, Prefabs)); } else if (NoCaseCompare(finisher, "Vines") == 0) { int Level = a_IniFile.GetValueSetI("Generator", "VinesLevel", 40); - m_FinishGens.push_back(std::make_shared(Seed, Level)); + m_FinishGens.push_back(std::make_shared(m_Seed, Level)); } else if (NoCaseCompare(finisher, "WaterLakes") == 0) { int Probability = a_IniFile.GetValueSetI("Generator", "WaterLakesProbability", 25); - m_FinishGens.push_back(cFinishGenPtr(new cStructGenLakes(Seed * 3 + 652, E_BLOCK_STATIONARY_WATER, m_ShapeGen, Probability))); + m_FinishGens.push_back(cFinishGenPtr(new cStructGenLakes(m_Seed * 3 + 652, E_BLOCK_STATIONARY_WATER, m_ShapeGen, Probability))); } else if (NoCaseCompare(finisher, "WaterSprings") == 0) { - m_FinishGens.push_back(cFinishGenPtr(new cFinishGenFluidSprings(Seed, E_BLOCK_WATER, a_IniFile, Dimension))); + m_FinishGens.push_back(cFinishGenPtr(new cFinishGenFluidSprings(m_Seed, E_BLOCK_WATER, a_IniFile, Dimension))); } else if (NoCaseCompare(finisher, "WormNestCaves") == 0) { int Size = a_IniFile.GetValueSetI("Generator", "WormNestCavesSize", 64); int Grid = a_IniFile.GetValueSetI("Generator", "WormNestCavesGrid", 96); int MaxOffset = a_IniFile.GetValueSetI("Generator", "WormNestMaxOffset", 32); - m_FinishGens.push_back(cFinishGenPtr(new cStructGenWormNestCaves(Seed, Size, Grid, MaxOffset))); + m_FinishGens.push_back(cFinishGenPtr(new cStructGenWormNestCaves(m_Seed, Size, Grid, MaxOffset))); } else { diff --git a/src/Generating/ComposableGenerator.h b/src/Generating/ComposableGenerator.h index 0b7e795d2..2eed12b74 100644 --- a/src/Generating/ComposableGenerator.h +++ b/src/Generating/ComposableGenerator.h @@ -183,17 +183,17 @@ typedef std::list cFinishGenList; class cComposableGenerator : - public cChunkGenerator::cGenerator + public cChunkGenerator { - typedef cChunkGenerator::cGenerator super; + typedef cChunkGenerator Super; public: - cComposableGenerator(cChunkGenerator & a_ChunkGenerator); + cComposableGenerator(); // cChunkGenerator::cGenerator overrides: virtual void Initialize(cIniFile & a_IniFile) override; virtual void GenerateBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) override; - virtual void DoGenerate(int a_ChunkX, int a_ChunkZ, cChunkDesc & a_ChunkDesc) override; + virtual void Generate(int a_ChunkX, int a_ChunkZ, cChunkDesc & a_ChunkDesc) override; protected: // The generator's composition: diff --git a/src/Generating/CompositedHeiGen.h b/src/Generating/CompositedHeiGen.h index c4e6ce77d..2db781e04 100644 --- a/src/Generating/CompositedHeiGen.h +++ b/src/Generating/CompositedHeiGen.h @@ -34,7 +34,7 @@ public: { cChunkDesc::Shape shape; m_ShapeGen->GenShape(a_ChunkX, a_ChunkZ, shape); - cChunkDesc desc(a_ChunkX, a_ChunkZ); + cChunkDesc desc({a_ChunkX, a_ChunkZ}); m_BiomeGen->GenBiomes(a_ChunkX, a_ChunkZ, desc.GetBiomeMap()); // Need to initialize biomes for the composition gen desc.SetHeightFromShape(shape); m_CompositionGen->ComposeTerrain(desc, shape); diff --git a/src/Generating/FinishGen.cpp b/src/Generating/FinishGen.cpp index 3c608e026..7aece7219 100644 --- a/src/Generating/FinishGen.cpp +++ b/src/Generating/FinishGen.cpp @@ -12,7 +12,6 @@ #include "FinishGen.h" #include "../Simulator/FluidSimulator.h" // for cFluidSimulator::CanWashAway() #include "../Simulator/FireSimulator.h" -#include "../World.h" #include "../IniFile.h" #include "../MobSpawner.h" diff --git a/src/Generating/Noise3DGenerator.cpp b/src/Generating/Noise3DGenerator.cpp index 4972a0505..64f5ed05f 100644 --- a/src/Generating/Noise3DGenerator.cpp +++ b/src/Generating/Noise3DGenerator.cpp @@ -148,8 +148,8 @@ public: //////////////////////////////////////////////////////////////////////////////// // cNoise3DGenerator: -cNoise3DGenerator::cNoise3DGenerator(cChunkGenerator & a_ChunkGenerator) : - super(a_ChunkGenerator), +cNoise3DGenerator::cNoise3DGenerator(): + Super(), m_Perlin(1000), m_Cubic(1000) { @@ -207,7 +207,7 @@ void cNoise3DGenerator::GenerateBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::Bi -void cNoise3DGenerator::DoGenerate(int a_ChunkX, int a_ChunkZ, cChunkDesc & a_ChunkDesc) +void cNoise3DGenerator::Generate(int a_ChunkX, int a_ChunkZ, cChunkDesc & a_ChunkDesc) { NOISE_DATATYPE Noise[17 * 257 * 17]; GenerateNoiseArray(a_ChunkX, a_ChunkZ, Noise); diff --git a/src/Generating/Noise3DGenerator.h b/src/Generating/Noise3DGenerator.h index 91408a32b..fa760f5f3 100644 --- a/src/Generating/Noise3DGenerator.h +++ b/src/Generating/Noise3DGenerator.h @@ -21,17 +21,17 @@ class cNoise3DGenerator : - public cChunkGenerator::cGenerator + public cChunkGenerator { - typedef cChunkGenerator::cGenerator super; + typedef cChunkGenerator Super; public: - cNoise3DGenerator(cChunkGenerator & a_ChunkGenerator); + cNoise3DGenerator(); virtual ~cNoise3DGenerator() override; virtual void Initialize(cIniFile & a_IniFile) override; virtual void GenerateBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) override; - virtual void DoGenerate(int a_ChunkX, int a_ChunkZ, cChunkDesc & a_ChunkDesc) override; + virtual void Generate(int a_ChunkX, int a_ChunkZ, cChunkDesc & a_ChunkDesc) override; protected: // Linear interpolation step sizes, must be divisors of cChunkDef::Width and cChunkDef::Height, respectively: diff --git a/src/Generating/StructGen.cpp b/src/Generating/StructGen.cpp index 918467f93..266308983 100644 --- a/src/Generating/StructGen.cpp +++ b/src/Generating/StructGen.cpp @@ -19,7 +19,7 @@ void cStructGenTrees::GenFinish(cChunkDesc & a_ChunkDesc) int ChunkX = a_ChunkDesc.GetChunkX(); int ChunkZ = a_ChunkDesc.GetChunkZ(); - cChunkDesc WorkerDesc(ChunkX, ChunkZ); + cChunkDesc WorkerDesc({ChunkX, ChunkZ}); // Generate trees: for (int x = 0; x <= 2; x++) @@ -34,7 +34,7 @@ void cStructGenTrees::GenFinish(cChunkDesc & a_ChunkDesc) if ((x != 1) || (z != 1)) { Dest = &WorkerDesc; - WorkerDesc.SetChunkCoords(BaseX, BaseZ); + WorkerDesc.SetChunkCoords({BaseX, BaseZ}); // TODO: This may cause a lot of wasted calculations, instead of pulling data out of a single (cChunkDesc) cache diff --git a/src/Generating/Trees.cpp b/src/Generating/Trees.cpp index 1456c1d15..824c8cecd 100644 --- a/src/Generating/Trees.cpp +++ b/src/Generating/Trees.cpp @@ -5,7 +5,6 @@ #include "Globals.h" #include "Trees.h" -#include "../World.h" @@ -1056,89 +1055,3 @@ void GetSmallJungleTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & PushCoordBlocks(a_BlockX, hei, a_BlockZ, a_OtherBlocks, BigO1, ARRAYCOUNT(BigO1), E_BLOCK_LEAVES, E_META_LEAVES_JUNGLE); a_OtherBlocks.push_back(sSetBlock(a_BlockX, hei, a_BlockZ, E_BLOCK_LEAVES, E_META_LEAVES_JUNGLE)); } - - - - - -bool GetLargeTreeAdjustment(cWorld & a_World, int & a_X, int & a_Y, int & a_Z, NIBBLETYPE a_Meta) -{ - bool IsLarge = true; - a_Meta = a_Meta & 0x07; - - // Check to see if we are the northwest corner - for (int x = 0; x < 2; ++x) - { - for (int z = 0; z < 2; ++z) - { - NIBBLETYPE meta; - BLOCKTYPE type; - a_World.GetBlockTypeMeta(a_X + x, a_Y, a_Z + z, type, meta); - IsLarge = IsLarge && (type == E_BLOCK_SAPLING) && ((a_Meta & meta) == a_Meta); - } - } - - if (IsLarge) - { - return true; - } - - IsLarge = true; - // Check to see if we are the southwest corner - for (int x = 0; x < 2; ++x) - { - for (int z = 0; z > -2; --z) - { - NIBBLETYPE meta; - BLOCKTYPE type; - a_World.GetBlockTypeMeta(a_X + x, a_Y, a_Z + z, type, meta); - IsLarge = IsLarge && (type == E_BLOCK_SAPLING) && ((a_Meta & meta) == a_Meta); - } - } - - if (IsLarge) - { - --a_Z; - return true; - } - - IsLarge = true; - // Check to see if we are the southeast corner - for (int x = 0; x > -2; --x) - { - for (int z = 0; z > -2; --z) - { - NIBBLETYPE meta; - BLOCKTYPE type; - a_World.GetBlockTypeMeta(a_X + x, a_Y, a_Z + z, type, meta); - IsLarge = IsLarge && (type == E_BLOCK_SAPLING) && ((a_Meta & meta) == a_Meta); - } - } - - if (IsLarge) - { - --a_Z; - --a_X; - return true; - } - - IsLarge = true; - // Check to see if we are the northeast corner - for (int x = 0; x > -2; --x) - { - for (int z = 0; z < 2; ++z) - { - NIBBLETYPE meta; - BLOCKTYPE type; - a_World.GetBlockTypeMeta(a_X + x, a_Y, a_Z + z, type, meta); - IsLarge = IsLarge && (type == E_BLOCK_SAPLING) && ((a_Meta & meta) == a_Meta); - } - } - - if (IsLarge) - { - --a_X; - } - - return IsLarge; -} diff --git a/src/Generating/Trees.h b/src/Generating/Trees.h index db9940d0a..ff7ae1744 100644 --- a/src/Generating/Trees.h +++ b/src/Generating/Trees.h @@ -19,8 +19,6 @@ logs can overwrite others(leaves), but others shouldn't overwrite logs. This is #include "../Noise/Noise.h" -class cWorld; - @@ -104,9 +102,3 @@ void GetLargeJungleTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & /** Generates an image of a small jungle tree (1x1 trunk) */ void GetSmallJungleTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks); - -/** Moves the x and z coordinants to the north-west corner of a 2x2 of saplings. Returns true if a 2x2 was found, otherwise it returns false */ -bool GetLargeTreeAdjustment(cWorld & a_World, int & a_X, int & a_Y, int & a_Z, NIBBLETYPE a_Meta); - - - diff --git a/src/LightingThread.cpp b/src/LightingThread.cpp index 8c445c8e4..c10c69ccd 100644 --- a/src/LightingThread.cpp +++ b/src/LightingThread.cpp @@ -237,7 +237,7 @@ void cLightingThread::LightChunk(cLightingChunkStay & a_Item) { if (a_Item.m_CallbackAfter != nullptr) { - a_Item.m_CallbackAfter->Call(a_Item.m_ChunkX, a_Item.m_ChunkZ, true); + a_Item.m_CallbackAfter->Call({a_Item.m_ChunkX, a_Item.m_ChunkZ}, true); } return; } @@ -318,7 +318,7 @@ void cLightingThread::LightChunk(cLightingChunkStay & a_Item) if (a_Item.m_CallbackAfter != nullptr) { - a_Item.m_CallbackAfter->Call(a_Item.m_ChunkX, a_Item.m_ChunkZ, true); + a_Item.m_CallbackAfter->Call({a_Item.m_ChunkX, a_Item.m_ChunkZ}, true); } } diff --git a/src/SpawnPrepare.cpp b/src/SpawnPrepare.cpp index d861a498d..e9f9e3b09 100644 --- a/src/SpawnPrepare.cpp +++ b/src/SpawnPrepare.cpp @@ -21,9 +21,9 @@ protected: std::shared_ptr m_SpawnPrepare; - virtual void Call(int a_ChunkX, int a_ChunkZ, bool a_IsSuccess) override + virtual void Call(cChunkCoords a_Coords, bool a_IsSuccess) override { - m_SpawnPrepare->PreparedChunkCallback(a_ChunkX, a_ChunkZ); + m_SpawnPrepare->PreparedChunkCallback(a_Coords.m_ChunkX, a_Coords.m_ChunkZ); } }; diff --git a/src/World.cpp b/src/World.cpp index 2b151af8e..a619ea9c6 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -1648,13 +1648,13 @@ void cWorld::GrowTreeFromSapling(int a_X, int a_Y, int a_Z, NIBBLETYPE a_Sapling case E_META_SAPLING_ACACIA: GetAcaciaTreeImage (a_X, a_Y, a_Z, Noise, WorldAge, Logs, Other); break; case E_META_SAPLING_JUNGLE: { - bool IsLarge = GetLargeTreeAdjustment(*this, a_X, a_Y, a_Z, a_SaplingMeta); + bool IsLarge = GetLargeTreeAdjustment(a_X, a_Y, a_Z, a_SaplingMeta); GetJungleTreeImage (a_X, a_Y, a_Z, Noise, WorldAge, Logs, Other, IsLarge); break; } case E_META_SAPLING_DARK_OAK: { - if (!GetLargeTreeAdjustment(*this, a_X, a_Y, a_Z, a_SaplingMeta)) + if (!GetLargeTreeAdjustment(a_X, a_Y, a_Z, a_SaplingMeta)) { return; } @@ -1672,6 +1672,92 @@ void cWorld::GrowTreeFromSapling(int a_X, int a_Y, int a_Z, NIBBLETYPE a_Sapling +bool cWorld::GetLargeTreeAdjustment(int & a_X, int & a_Y, int & a_Z, NIBBLETYPE a_Meta) +{ + bool IsLarge = true; + a_Meta = a_Meta & 0x07; + + // Check to see if we are the northwest corner + for (int x = 0; x < 2; ++x) + { + for (int z = 0; z < 2; ++z) + { + NIBBLETYPE meta; + BLOCKTYPE type; + GetBlockTypeMeta(a_X + x, a_Y, a_Z + z, type, meta); + IsLarge = IsLarge && (type == E_BLOCK_SAPLING) && ((a_Meta & meta) == a_Meta); + } + } + + if (IsLarge) + { + return true; + } + + IsLarge = true; + // Check to see if we are the southwest corner + for (int x = 0; x < 2; ++x) + { + for (int z = 0; z > -2; --z) + { + NIBBLETYPE meta; + BLOCKTYPE type; + GetBlockTypeMeta(a_X + x, a_Y, a_Z + z, type, meta); + IsLarge = IsLarge && (type == E_BLOCK_SAPLING) && ((a_Meta & meta) == a_Meta); + } + } + + if (IsLarge) + { + --a_Z; + return true; + } + + IsLarge = true; + // Check to see if we are the southeast corner + for (int x = 0; x > -2; --x) + { + for (int z = 0; z > -2; --z) + { + NIBBLETYPE meta; + BLOCKTYPE type; + GetBlockTypeMeta(a_X + x, a_Y, a_Z + z, type, meta); + IsLarge = IsLarge && (type == E_BLOCK_SAPLING) && ((a_Meta & meta) == a_Meta); + } + } + + if (IsLarge) + { + --a_Z; + --a_X; + return true; + } + + IsLarge = true; + // Check to see if we are the northeast corner + for (int x = 0; x > -2; --x) + { + for (int z = 0; z < 2; ++z) + { + NIBBLETYPE meta; + BLOCKTYPE type; + GetBlockTypeMeta(a_X + x, a_Y, a_Z + z, type, meta); + IsLarge = IsLarge && (type == E_BLOCK_SAPLING) && ((a_Meta & meta) == a_Meta); + } + } + + if (IsLarge) + { + --a_X; + } + + return IsLarge; +} + + + + + void cWorld::GrowTreeByBiome(int a_X, int a_Y, int a_Z) { cNoise Noise(m_Generator.GetSeed()); @@ -2514,7 +2600,7 @@ void cWorld::QueueSetChunkData(cSetChunkDataPtr a_SetChunkData) 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()); + m_Generator.GenerateBiomes({a_SetChunkData->GetChunkX(), a_SetChunkData->GetChunkZ()}, a_SetChunkData->GetBiomes()); a_SetChunkData->MarkBiomesValid(); } @@ -3157,7 +3243,7 @@ void cWorld::RegenerateChunk(int a_ChunkX, int a_ChunkZ) { m_ChunkMap->MarkChunkRegenerating(a_ChunkX, a_ChunkZ); - m_Generator.QueueGenerateChunk(a_ChunkX, a_ChunkZ, true); + m_Generator.QueueGenerateChunk({a_ChunkX, a_ChunkZ}, true); } @@ -3770,27 +3856,27 @@ void cWorld::cChunkGeneratorCallbacks::OnChunkGenerated(cChunkDesc & a_ChunkDesc -bool cWorld::cChunkGeneratorCallbacks::IsChunkValid(int a_ChunkX, int a_ChunkZ) +bool cWorld::cChunkGeneratorCallbacks::IsChunkValid(cChunkCoords a_Coords) { - return m_World->IsChunkValid(a_ChunkX, a_ChunkZ); + return m_World->IsChunkValid(a_Coords.m_ChunkX, a_Coords.m_ChunkZ); } -bool cWorld::cChunkGeneratorCallbacks::IsChunkQueued(int a_ChunkX, int a_ChunkZ) +bool cWorld::cChunkGeneratorCallbacks::IsChunkQueued(cChunkCoords a_Coords) { - return m_World->IsChunkQueued(a_ChunkX, a_ChunkZ); + return m_World->IsChunkQueued(a_Coords.m_ChunkX, a_Coords.m_ChunkZ); } -bool cWorld::cChunkGeneratorCallbacks::HasChunkAnyClients(int a_ChunkX, int a_ChunkZ) +bool cWorld::cChunkGeneratorCallbacks::HasChunkAnyClients(cChunkCoords a_Coords) { - return m_World->HasChunkAnyClients(a_ChunkX, a_ChunkZ); + return m_World->HasChunkAnyClients(a_Coords.m_ChunkX, a_Coords.m_ChunkZ); } diff --git a/src/World.h b/src/World.h index 7e18c53c3..b9fa57d8a 100644 --- a/src/World.h +++ b/src/World.h @@ -8,7 +8,7 @@ #include "Simulator/SimulatorManager.h" #include "ChunkMap.h" #include "WorldStorage/WorldStorage.h" -#include "Generating/ChunkGenerator.h" +#include "ChunkGeneratorThread.h" #include "ChunkSender.h" #include "Defines.h" #include "LightingThread.h" @@ -824,7 +824,7 @@ public: // tolua_end - cChunkGenerator & GetGenerator(void) { return m_Generator; } + cChunkGeneratorThread & GetGenerator(void) { return m_Generator; } cWorldStorage & GetStorage (void) { return m_Storage; } cChunkMap * GetChunkMap (void) { return m_ChunkMap.get(); } @@ -883,16 +883,16 @@ private: /** Implementation of the callbacks that the ChunkGenerator uses to store new chunks and interface to plugins */ class cChunkGeneratorCallbacks : - public cChunkGenerator::cChunkSink, - public cChunkGenerator::cPluginInterface + public cChunkGeneratorThread::cChunkSink, + public cChunkGeneratorThread::cPluginInterface { cWorld * m_World; // cChunkSink overrides: virtual void OnChunkGenerated (cChunkDesc & a_ChunkDesc) override; - virtual bool IsChunkValid (int a_ChunkX, int a_ChunkZ) override; - virtual bool HasChunkAnyClients(int a_ChunkX, int a_ChunkZ) override; - virtual bool IsChunkQueued (int a_ChunkX, int a_ChunkZ) override; + virtual bool IsChunkValid (cChunkCoords a_Coords) override; + virtual bool HasChunkAnyClients(cChunkCoords a_Coords) override; + virtual bool IsChunkQueued (cChunkCoords a_Coords) override; // cPluginInterface overrides: virtual void CallHookChunkGenerating(cChunkDesc & a_ChunkDesc) override; @@ -1031,8 +1031,8 @@ private: Only used when this world is an Overworld. */ AString m_LinkedEndWorldName; - - cChunkGenerator m_Generator; + /** The thread responsible for generating chunks. */ + cChunkGeneratorThread m_Generator; cScoreboard m_Scoreboard; cMapManager m_MapManager; @@ -1143,4 +1143,8 @@ private: 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 X and Z 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. */ + bool GetLargeTreeAdjustment(int & a_BlockX, int & a_BlockY, int & a_BlockZ, NIBBLETYPE a_SaplingMeta); }; // tolua_export diff --git a/src/WorldStorage/WorldStorage.cpp b/src/WorldStorage/WorldStorage.cpp index a3aff0749..d1f9abf74 100644 --- a/src/WorldStorage/WorldStorage.cpp +++ b/src/WorldStorage/WorldStorage.cpp @@ -240,7 +240,7 @@ bool cWorldStorage::LoadOneChunk(void) // Call the callback, if specified: if (ToLoad.m_Callback != nullptr) { - ToLoad.m_Callback->Call(ToLoad.m_ChunkX, ToLoad.m_ChunkZ, res); + ToLoad.m_Callback->Call({ToLoad.m_ChunkX, ToLoad.m_ChunkZ}, res); } return res; } @@ -274,7 +274,7 @@ bool cWorldStorage::SaveOneChunk(void) // Call the callback, if specified: if (ToSave.m_Callback != nullptr) { - ToSave.m_Callback->Call(ToSave.m_ChunkX, ToSave.m_ChunkZ, Status); + ToSave.m_Callback->Call({ToSave.m_ChunkX, ToSave.m_ChunkZ}, Status); } return true; } diff --git a/tests/Generating/CMakeLists.txt b/tests/Generating/CMakeLists.txt index 31cfc5c24..8c25fd7c3 100644 --- a/tests/Generating/CMakeLists.txt +++ b/tests/Generating/CMakeLists.txt @@ -15,19 +15,11 @@ set (SHARED_SRCS ${CMAKE_SOURCE_DIR}/src/StringCompression.cpp ${CMAKE_SOURCE_DIR}/src/StringUtils.cpp - ${CMAKE_SOURCE_DIR}/src/Bindings/LuaState.cpp - - ${CMAKE_SOURCE_DIR}/src/Generating/ChunkDesc.cpp - ${CMAKE_SOURCE_DIR}/src/Generating/PiecePool.cpp - ${CMAKE_SOURCE_DIR}/src/Generating/Prefab.cpp - ${CMAKE_SOURCE_DIR}/src/Generating/PrefabPiecePool.cpp - ${CMAKE_SOURCE_DIR}/src/Generating/VerticalLimit.cpp - ${CMAKE_SOURCE_DIR}/src/Generating/VerticalStrategy.cpp + ${CMAKE_SOURCE_DIR}/src/Bindings/LuaState.cpp # Needed for PrefabPiecePool loading ${CMAKE_SOURCE_DIR}/src/Noise/Noise.cpp - ${CMAKE_SOURCE_DIR}/src/OSSupport/CriticalSection.cpp - ${CMAKE_SOURCE_DIR}/src/OSSupport/Event.cpp + ${CMAKE_SOURCE_DIR}/src/OSSupport/CriticalSection.cpp # Needed for LuaState ${CMAKE_SOURCE_DIR}/src/OSSupport/File.cpp ${CMAKE_SOURCE_DIR}/src/OSSupport/GZipFile.cpp ${CMAKE_SOURCE_DIR}/src/OSSupport/StackTrace.cpp @@ -37,6 +29,38 @@ set (SHARED_SRCS ${CMAKE_SOURCE_DIR}/src/WorldStorage/SchematicFileSerializer.cpp ) +set (GENERATING_SRCS + ${CMAKE_SOURCE_DIR}/src/Generating/BioGen.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/Caves.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/ChunkDesc.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/ChunkGenerator.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/CompoGen.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/CompoGenBiomal.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/ComposableGenerator.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/DistortedHeightmap.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/DungeonRoomsFinisher.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/EndGen.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/FinishGen.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/GridStructGen.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/HeiGen.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/MineShafts.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/Noise3DGenerator.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/PieceGeneratorBFSTree.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/PiecePool.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/PieceStructuresGen.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/Prefab.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/PrefabPiecePool.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/PrefabStructure.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/Ravines.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/RoughRavines.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/StructGen.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/Trees.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/TwoHeights.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/VerticalLimit.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/VerticalStrategy.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/VillageGen.cpp +) + set (SHARED_HDRS ${CMAKE_SOURCE_DIR}/src/BiomeDef.h ${CMAKE_SOURCE_DIR}/src/BlockArea.h @@ -48,13 +72,6 @@ set (SHARED_HDRS ${CMAKE_SOURCE_DIR}/src/Bindings/LuaState.h - ${CMAKE_SOURCE_DIR}/src/Generating/ChunkDesc.h - ${CMAKE_SOURCE_DIR}/src/Generating/PiecePool.h - ${CMAKE_SOURCE_DIR}/src/Generating/Prefab.h - ${CMAKE_SOURCE_DIR}/src/Generating/PrefabPiecePool.h - ${CMAKE_SOURCE_DIR}/src/Generating/VerticalLimit.h - ${CMAKE_SOURCE_DIR}/src/Generating/VerticalStrategy.h - ${CMAKE_SOURCE_DIR}/src/Noise/Noise.h ${CMAKE_SOURCE_DIR}/src/OSSupport/CriticalSection.h @@ -68,6 +85,42 @@ set (SHARED_HDRS ${CMAKE_SOURCE_DIR}/src/WorldStorage/SchematicFileSerializer.h ) +set (GENERATING_HDRS + ${CMAKE_SOURCE_DIR}/src/Generating/BioGen.h + ${CMAKE_SOURCE_DIR}/src/Generating/Caves.h + ${CMAKE_SOURCE_DIR}/src/Generating/ChunkDesc.h + ${CMAKE_SOURCE_DIR}/src/Generating/ChunkGenerator.h + ${CMAKE_SOURCE_DIR}/src/Generating/CompoGen.h + ${CMAKE_SOURCE_DIR}/src/Generating/CompoGenBiomal.h + ${CMAKE_SOURCE_DIR}/src/Generating/ComposableGenerator.h + ${CMAKE_SOURCE_DIR}/src/Generating/CompositedHeiGen.h + ${CMAKE_SOURCE_DIR}/src/Generating/DistortedHeightmap.h + ${CMAKE_SOURCE_DIR}/src/Generating/DungeonRoomsFinisher.h + ${CMAKE_SOURCE_DIR}/src/Generating/EndGen.h + ${CMAKE_SOURCE_DIR}/src/Generating/FinishGen.h + ${CMAKE_SOURCE_DIR}/src/Generating/GridStructGen.h + ${CMAKE_SOURCE_DIR}/src/Generating/HeiGen.h + ${CMAKE_SOURCE_DIR}/src/Generating/IntGen.h + ${CMAKE_SOURCE_DIR}/src/Generating/MineShafts.h + ${CMAKE_SOURCE_DIR}/src/Generating/Noise3DGenerator.h + ${CMAKE_SOURCE_DIR}/src/Generating/PieceGeneratorBFSTree.h + ${CMAKE_SOURCE_DIR}/src/Generating/PiecePool.h + ${CMAKE_SOURCE_DIR}/src/Generating/PieceStructuresGen.h + ${CMAKE_SOURCE_DIR}/src/Generating/Prefab.h + ${CMAKE_SOURCE_DIR}/src/Generating/PrefabPiecePool.h + ${CMAKE_SOURCE_DIR}/src/Generating/PrefabStructure.h + ${CMAKE_SOURCE_DIR}/src/Generating/ProtIntGen.h + ${CMAKE_SOURCE_DIR}/src/Generating/Ravines.h + ${CMAKE_SOURCE_DIR}/src/Generating/RoughRavines.h + ${CMAKE_SOURCE_DIR}/src/Generating/ShapeGen.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/StructGen.h + ${CMAKE_SOURCE_DIR}/src/Generating/Trees.h + ${CMAKE_SOURCE_DIR}/src/Generating/TwoHeights.h + ${CMAKE_SOURCE_DIR}/src/Generating/VerticalLimit.h + ${CMAKE_SOURCE_DIR}/src/Generating/VerticalStrategy.h + ${CMAKE_SOURCE_DIR}/src/Generating/VillageGen.h +) + set (STUBS Stubs.cpp LuaState_Typedefs.inc @@ -83,9 +136,16 @@ endif() -add_library(GeneratorTestingSupport STATIC ${SHARED_SRCS} ${SHARED_HDRS} ${STUBS}) +add_library(GeneratorTestingSupport STATIC + ${SHARED_SRCS} + ${SHARED_HDRS} + ${GENERATING_SRCS} + ${GENERATING_HDRS} + ${STUBS} +) target_link_libraries(GeneratorTestingSupport tolualib zlib fmt::fmt) source_group("Stubs" FILES ${STUBS}) +source_group("Generating" FILES ${GENERATING_HDRS} ${GENERATING_SRCS}) @@ -93,18 +153,32 @@ source_group("Stubs" FILES ${STUBS}) # LoadablePieces test: source_group("Data files" FILES Test.cubeset Test1.schematic) -add_executable(LoadablePieces LoadablePieces.cpp Test.cubeset Test1.schematic) +add_executable(LoadablePieces + LoadablePieces.cpp + Test.cubeset + Test1.schematic +) target_link_libraries(LoadablePieces GeneratorTestingSupport) -add_test(NAME LoadablePieces-test WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND LoadablePieces) +add_test( + NAME LoadablePieces-test + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND LoadablePieces +) # PieceRotation test: -add_executable(PieceRotation PieceRotationTest.cpp) +add_executable(PieceRotation + PieceRotationTest.cpp +) target_link_libraries(PieceRotation GeneratorTestingSupport) -add_test(NAME PieceRotation-test WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND PieceRotation) +add_test( + NAME PieceRotation-test + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND PieceRotation +) @@ -113,11 +187,13 @@ add_test(NAME PieceRotation-test WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} C # PieceGeneratorBFSTree test: add_executable(PieceGeneratorBFSTree PieceGeneratorBFSTreeTest.cpp - ${CMAKE_SOURCE_DIR}/src/Generating/PieceGeneratorBFSTree.cpp - ${CMAKE_SOURCE_DIR}/src/Generating/PieceGeneratorBFSTree.h ) target_link_libraries(PieceGeneratorBFSTree GeneratorTestingSupport) -add_test(NAME PieceGeneratorBFSTree-test WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/Server/Prefabs/PieceStructures COMMAND PieceGeneratorBFSTree) +add_test( + NAME PieceGeneratorBFSTree-test + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/Server/Prefabs/PieceStructures + COMMAND PieceGeneratorBFSTree +) -- cgit v1.2.3