// LightingThread.cpp // Implements the cLightingThread class representing the thread that processes requests for lighting #include "Globals.h" #include "LightingThread.h" #include "ChunkMap.h" #include "World.h" #include "BlockInfo.h" /** Chunk data callback that takes the chunk data and puts them into cLightingThread's m_BlockTypes[] / m_HeightMap[]: */ class cReader : public cChunkDataCallback { virtual void ChunkData(const ChunkBlockData & a_BlockData, const ChunkLightData &) override { BLOCKTYPE * OutputRows = m_BlockTypes; int OutputIdx = m_ReadingChunkX + m_ReadingChunkZ * cChunkDef::Width * 3; for (size_t i = 0; i != cChunkDef::NumSections; ++i) { const auto Section = a_BlockData.GetSection(i); if (Section == nullptr) { // Skip to the next section OutputIdx += 9 * cChunkDef::SectionHeight * cChunkDef::Width; continue; } for (size_t OffsetY = 0; OffsetY != cChunkDef::SectionHeight; ++OffsetY) { for (size_t Z = 0; Z != cChunkDef::Width; ++Z) { auto InPtr = Section->data() + Z * cChunkDef::Width + OffsetY * cChunkDef::Width * cChunkDef::Width; std::copy_n(InPtr, cChunkDef::Width, OutputRows + OutputIdx * cChunkDef::Width); OutputIdx += 3; } // Skip into the next y-level in the 3x3 chunk blob; each level has cChunkDef::Width * 9 rows // We've already walked cChunkDef::Width * 3 in the "for z" cycle, that makes cChunkDef::Width * 6 rows left to skip OutputIdx += cChunkDef::Width * 6; } } } // BlockTypes() virtual void HeightMap(const cChunkDef::HeightMap & a_Heightmap) override { // Copy the entire heightmap, distribute it into the 3x3 chunk blob: typedef struct {HEIGHTTYPE m_Row[16]; } ROW; const ROW * InputRows = reinterpret_cast(a_Heightmap); ROW * OutputRows = reinterpret_cast(m_HeightMap); int InputIdx = 0; int OutputIdx = m_ReadingChunkX + m_ReadingChunkZ * cChunkDef::Width * 3; for (int z = 0; z < cChunkDef::Width; z++) { OutputRows[OutputIdx] = InputRows[InputIdx++]; OutputIdx += 3; } // for z // Find the highest block in the entire chunk, use it as a base for m_MaxHeight: HEIGHTTYPE MaxHeight = m_MaxHeight; for (size_t i = 0; i < ARRAYCOUNT(a_Heightmap); i++) { if (a_Heightmap[i] > MaxHeight) { MaxHeight = a_Heightmap[i]; } } m_MaxHeight = MaxHeight; } public: int m_ReadingChunkX; // 0, 1 or 2; x-offset of the chunk we're reading from the BlockTypes start int m_ReadingChunkZ; // 0, 1 or 2; z-offset of the chunk we're reading from the BlockTypes start HEIGHTTYPE m_MaxHeight; // Maximum value in this chunk's heightmap BLOCKTYPE * m_BlockTypes; // 3x3 chunks of block types, organized as a single XZY blob of data (instead of 3x3 XZY blobs) HEIGHTTYPE * m_HeightMap; // 3x3 chunks of height map, organized as a single XZY blob of data (instead of 3x3 XZY blobs) cReader(BLOCKTYPE * a_BlockTypes, HEIGHTTYPE * a_HeightMap) : m_ReadingChunkX(0), m_ReadingChunkZ(0), m_MaxHeight(0), m_BlockTypes(a_BlockTypes), m_HeightMap(a_HeightMap) { std::fill_n(m_BlockTypes, cChunkDef::NumBlocks * 9, E_BLOCK_AIR); } } ; //////////////////////////////////////////////////////////////////////////////// // cLightingThread: cLightingThread::cLightingThread(cWorld & a_World): Super("Lighting Executor"), m_World(a_World), m_MaxHeight(0), m_NumSeeds(0) { } cLightingThread::~cLightingThread() { Stop(); } void cLightingThread::Stop(void) { { cCSLock Lock(m_CS); for (cChunkStays::iterator itr = m_PendingQueue.begin(), end = m_PendingQueue.end(); itr != end; ++itr) { (*itr)->Disable(); delete *itr; } m_PendingQueue.clear(); for (cChunkStays::iterator itr = m_Queue.begin(), end = m_Queue.end(); itr != end; ++itr) { (*itr)->Disable(); delete *itr; } m_Queue.clear(); } m_ShouldTerminate = true; m_evtItemAdded.Set(); Super::Stop(); } void cLightingThread::QueueChunk(int a_ChunkX, int a_ChunkZ, std::unique_ptr a_CallbackAfter) { cChunkStay * ChunkStay = new cLightingChunkStay(*this, a_ChunkX, a_ChunkZ, std::move(a_CallbackAfter)); { // The ChunkStay will enqueue itself using the QueueChunkStay() once it is fully loaded // In the meantime, put it into the PendingQueue so that it can be removed when stopping the thread cCSLock Lock(m_CS); m_PendingQueue.push_back(ChunkStay); } ChunkStay->Enable(*m_World.GetChunkMap()); } void cLightingThread::WaitForQueueEmpty(void) { cCSLock Lock(m_CS); while (!m_ShouldTerminate && (!m_Queue.empty() || !m_PendingQueue.empty())) { cCSUnlock Unlock(Lock); m_evtQueueEmpty.Wait(); } } size_t cLightingThread::GetQueueLength(void) { cCSLock Lock(m_CS); return m_Queue.size() + m_PendingQueue.size(); } void cLightingThread::Execute(void) { for (;;) { { cCSLock Lock(m_CS); if (m_Queue.empty()) { cCSUnlock Unlock(Lock); m_evtItemAdded.Wait(); } } if (m_ShouldTerminate) { return; } // Process one items from the queue: cLightingChunkStay * Item; { cCSLock Lock(m_CS); if (m_Queue.empty()) { continue; } Item = static_cast(m_Queue.front()); m_Queue.pop_front(); if (m_Queue.empty()) { m_evtQueueEmpty.Set(); } } // CSLock(m_CS) LightChunk(*Item); Item->Disable(); delete Item; } } void cLightingThread::LightChunk(cLightingChunkStay & a_Item) { // If the chunk is already lit, skip it (report as success): 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}, true); } return; } cChunkDef::BlockNibbles BlockLight, SkyLight; ReadChunks(a_Item.m_ChunkX, a_Item.m_ChunkZ); PrepareBlockLight(); CalcLight(m_BlockLight); PrepareSkyLight(); /* // DEBUG: Save chunk data with highlighted seeds for visual inspection: cFile f4; if ( f4.Open(Printf("Chunk_%d_%d_seeds.grab", a_Item.m_ChunkX, a_Item.m_ChunkZ), cFile::fmWrite) ) { for (int z = 0; z < cChunkDef::Width * 3; z++) { for (int y = cChunkDef::Height / 2; y >= 0; y--) { unsigned char Seeds [cChunkDef::Width * 3]; memcpy(Seeds, m_BlockTypes + y * BlocksPerYLayer + z * cChunkDef::Width * 3, cChunkDef::Width * 3); for (int x = 0; x < cChunkDef::Width * 3; x++) { if (m_IsSeed1[y * BlocksPerYLayer + z * cChunkDef::Width * 3 + x]) { Seeds[x] = E_BLOCK_DIAMOND_BLOCK; } } f4.Write(Seeds, cChunkDef::Width * 3); } } f4.Close(); } //*/ CalcLight(m_SkyLight); /* // DEBUG: Save XY slices of the chunk data and lighting for visual inspection: cFile f1, f2, f3; if ( f1.Open(Printf("Chunk_%d_%d_data.grab", a_Item.m_ChunkX, a_Item.m_ChunkZ), cFile::fmWrite) && f2.Open(Printf("Chunk_%d_%d_sky.grab", a_Item.m_ChunkX, a_Item.m_ChunkZ), cFile::fmWrite) && f3.Open(Printf("Chunk_%d_%d_glow.grab", a_Item.m_ChunkX, a_Item.m_ChunkZ), cFile::fmWrite) ) { for (int z = 0; z < cChunkDef::Width * 3; z++) { for (int y = cChunkDef::Height / 2; y >= 0; y--) { f1.Write(m_BlockTypes + y * BlocksPerYLayer + z * cChunkDef::Width * 3, cChunkDef::Width * 3); unsigned char SkyLight [cChunkDef::Width * 3]; unsigned char BlockLight[cChunkDef::Width * 3]; for (int x = 0; x < cChunkDef::Width * 3; x++) { SkyLight[x] = m_SkyLight [y * BlocksPerYLayer + z * cChunkDef::Width * 3 + x] << 4; BlockLight[x] = m_BlockLight[y * BlocksPerYLayer + z * cChunkDef::Width * 3 + x] << 4; } f2.Write(SkyLight, cChunkDef::Width * 3); f3.Write(BlockLight, cChunkDef::Width * 3); } } f1.Close(); f2.Close(); f3.Close(); } //*/ CompressLight(m_BlockLight, BlockLight); CompressLight(m_SkyLight, SkyLight); m_World.ChunkLighted(a_Item.m_ChunkX, a_Item.m_ChunkZ, BlockLight, SkyLight); if (a_Item.m_CallbackAfter != nullptr) { a_Item.m_CallbackAfter->Call({a_Item.m_ChunkX, a_Item.m_ChunkZ}, true); } } void cLightingThread::ReadChunks(int a_ChunkX, int a_ChunkZ) { cReader Reader(m_BlockTypes, m_HeightMap); for (int z = 0; z < 3; z++) { Reader.m_ReadingChunkZ = z; for (int x = 0; x < 3; x++) { Reader.m_ReadingChunkX = x; VERIFY(m_World.GetChunkData({a_ChunkX + x - 1, a_ChunkZ + z - 1}, Reader)); } // for z } // for x memset(m_BlockLight, 0, sizeof(m_BlockLight)); memset(m_SkyLight, 0, sizeof(m_SkyLight)); m_MaxHeight = Reader.m_MaxHeight; } void cLightingThread::PrepareSkyLight(void) { // Clear seeds: memset(m_IsSeed1, 0, sizeof(m_IsSeed1)); m_NumSeeds = 0; // Fill the top of the chunk with all-light: if (m_MaxHeight < cChunkDef::Height - 1) { std::fill(m_SkyLight + (m_MaxHeight + 1) * BlocksPerYLayer, m_SkyLight + ARRAYCOUNT(m_SkyLight), 15); } // Walk every column that has all XZ neighbors for (int z = 1; z < cChunkDef::Width * 3 - 1; z++) { int BaseZ = z * cChunkDef::Width * 3; for (int x = 1; x < cChunkDef::Width * 3 - 1; x++) { int idx = BaseZ + x; // Find the lowest block in this column that receives full sunlight (go through transparent blocks): int Current = m_HeightMap[idx]; ASSERT(Current < cChunkDef::Height); while ( (Current >= 0) && cBlockInfo::IsTransparent(m_BlockTypes[idx + Current * BlocksPerYLayer]) && !cBlockInfo::IsSkylightDispersant(m_BlockTypes[idx + Current * BlocksPerYLayer]) ) { Current -= 1; // Sunlight goes down unchanged through this block } Current += 1; // Point to the last sunlit block, rather than the first non-transparent one // The other neighbors don't need transparent-block-checking. At worst we'll have a few dud seeds above the ground. int Neighbor1 = m_HeightMap[idx + 1] + 1; // X + 1 int Neighbor2 = m_HeightMap[idx - 1] + 1; // X - 1 int Neighbor3 = m_HeightMap[idx + cChunkDef::Width * 3] + 1; // Z + 1 int Neighbor4 = m_HeightMap[idx - cChunkDef::Width * 3] + 1; // Z - 1 int MaxNeighbor = std::max(std::max(Neighbor1, Neighbor2), std::max(Neighbor3, Neighbor4)); // Maximum of the four neighbors // Fill the column from m_MaxHeight to Current with all-light: for (int y = m_MaxHeight, Index = idx + y * BlocksPerYLayer; y >= Current; y--, Index -= BlocksPerYLayer) { m_SkyLight[Index] = 15; } // Add Current as a seed: if (Current < cChunkDef::Height) { int CurrentIdx = idx + Current * BlocksPerYLayer; m_IsSeed1[CurrentIdx] = true; m_SeedIdx1[m_NumSeeds++] = static_cast(CurrentIdx); } // Add seed from Current up to the highest neighbor: for (int y = Current + 1, Index = idx + y * BlocksPerYLayer; y < MaxNeighbor; y++, Index += BlocksPerYLayer) { m_IsSeed1[Index] = true; m_SeedIdx1[m_NumSeeds++] = static_cast(Index); } } } } void cLightingThread::PrepareBlockLight() { // Clear seeds: memset(m_IsSeed1, 0, sizeof(m_IsSeed1)); memset(m_IsSeed2, 0, sizeof(m_IsSeed2)); m_NumSeeds = 0; // Add each emissive block into the seeds: for (int Idx = 0; Idx < (m_MaxHeight * BlocksPerYLayer); ++Idx) { if (cBlockInfo::GetLightValue(m_BlockTypes[Idx]) == 0) { // Not a light-emissive block continue; } // Add current block as a seed: m_IsSeed1[Idx] = true; m_SeedIdx1[m_NumSeeds++] = static_cast(Idx); // Light it up: m_BlockLight[Idx] = cBlockInfo::GetLightValue(m_BlockTypes[Idx]); } } void cLightingThread::CalcLight(NIBBLETYPE * a_Light) { size_t NumSeeds2 = 0; while (m_NumSeeds > 0) { // Buffer 1 -> buffer 2 memset(m_IsSeed2, 0, sizeof(m_IsSeed2)); NumSeeds2 = 0; CalcLightStep(a_Light, m_NumSeeds, m_IsSeed1, m_SeedIdx1, NumSeeds2, m_IsSeed2, m_SeedIdx2); if (NumSeeds2 == 0) { return; } // Buffer 2 -> buffer 1 memset(m_IsSeed1, 0, sizeof(m_IsSeed1)); m_NumSeeds = 0; CalcLightStep(a_Light, NumSeeds2, m_IsSeed2, m_SeedIdx2, m_NumSeeds, m_IsSeed1, m_SeedIdx1); } } void cLightingThread::CalcLightStep( NIBBLETYPE * a_Light, size_t a_NumSeedsIn, unsigned char * a_IsSeedIn, unsigned int * a_SeedIdxIn, size_t & a_NumSeedsOut, unsigned char * a_IsSeedOut, unsigned int * a_SeedIdxOut ) { UNUSED(a_IsSeedIn); size_t NumSeedsOut = 0; for (size_t i = 0; i < a_NumSeedsIn; i++) { UInt32 SeedIdx = static_cast(a_SeedIdxIn[i]); int SeedX = SeedIdx % (cChunkDef::Width * 3); int SeedZ = (SeedIdx / (cChunkDef::Width * 3)) % (cChunkDef::Width * 3); // Propagate seed: if (SeedX < cChunkDef::Width * 3 - 1) { PropagateLight(a_Light, SeedIdx, SeedIdx + 1, NumSeedsOut, a_IsSeedOut, a_SeedIdxOut); } if (SeedX > 0) { PropagateLight(a_Light, SeedIdx, SeedIdx - 1, NumSeedsOut, a_IsSeedOut, a_SeedIdxOut); } if (SeedZ < cChunkDef::Width * 3 - 1) { PropagateLight(a_Light, SeedIdx, SeedIdx + cChunkDef::Width * 3, NumSeedsOut, a_IsSeedOut, a_SeedIdxOut); } if (SeedZ > 0) { PropagateLight(a_Light, SeedIdx, SeedIdx - cChunkDef::Width * 3, NumSeedsOut, a_IsSeedOut, a_SeedIdxOut); } if (SeedIdx < (cChunkDef::Height - 1) * BlocksPerYLayer) { PropagateLight(a_Light, SeedIdx, SeedIdx + BlocksPerYLayer, NumSeedsOut, a_IsSeedOut, a_SeedIdxOut); } if (SeedIdx >= BlocksPerYLayer) { PropagateLight(a_Light, SeedIdx, SeedIdx - BlocksPerYLayer, NumSeedsOut, a_IsSeedOut, a_SeedIdxOut); } } // for i - a_SeedIdxIn[] a_NumSeedsOut = NumSeedsOut; } void cLightingThread::CompressLight(NIBBLETYPE * a_LightArray, NIBBLETYPE * a_ChunkLight) { int InIdx = cChunkDef::Width * 49; // Index to the first nibble of the middle chunk in the a_LightArray int OutIdx = 0; for (int y = 0; y < cChunkDef::Height; y++) { for (int z = 0; z < cChunkDef::Width; z++) { for (int x = 0; x < cChunkDef::Width; x += 2) { a_ChunkLight[OutIdx++] = static_cast(a_LightArray[InIdx + 1] << 4) | a_LightArray[InIdx]; InIdx += 2; } InIdx += cChunkDef::Width * 2; } // Skip into the next y-level in the 3x3 chunk blob; each level has cChunkDef::Width * 9 rows // We've already walked cChunkDef::Width * 3 in the "for z" cycle, that makes cChunkDef::Width * 6 rows left to skip InIdx += cChunkDef::Width * cChunkDef::Width * 6; } } void cLightingThread::PropagateLight( NIBBLETYPE * a_Light, unsigned int a_SrcIdx, unsigned int a_DstIdx, size_t & a_NumSeedsOut, unsigned char * a_IsSeedOut, unsigned int * a_SeedIdxOut ) { ASSERT(a_SrcIdx < ARRAYCOUNT(m_SkyLight)); ASSERT(a_DstIdx < ARRAYCOUNT(m_BlockTypes)); if (a_Light[a_SrcIdx] <= a_Light[a_DstIdx] + cBlockInfo::GetSpreadLightFalloff(m_BlockTypes[a_DstIdx])) { // We're not offering more light than the dest block already has return; } a_Light[a_DstIdx] = a_Light[a_SrcIdx] - cBlockInfo::GetSpreadLightFalloff(m_BlockTypes[a_DstIdx]); if (!a_IsSeedOut[a_DstIdx]) { a_IsSeedOut[a_DstIdx] = true; a_SeedIdxOut[a_NumSeedsOut++] = a_DstIdx; } } void cLightingThread::QueueChunkStay(cLightingChunkStay & a_ChunkStay) { // Move the ChunkStay from the Pending queue to the lighting queue. { cCSLock Lock(m_CS); m_PendingQueue.remove(&a_ChunkStay); m_Queue.push_back(&a_ChunkStay); } m_evtItemAdded.Set(); } //////////////////////////////////////////////////////////////////////////////// // cLightingThread::cLightingChunkStay: cLightingThread::cLightingChunkStay::cLightingChunkStay(cLightingThread & a_LightingThread, int a_ChunkX, int a_ChunkZ, std::unique_ptr a_CallbackAfter) : m_LightingThread(a_LightingThread), m_ChunkX(a_ChunkX), m_ChunkZ(a_ChunkZ), m_CallbackAfter(std::move(a_CallbackAfter)) { Add(a_ChunkX + 1, a_ChunkZ + 1); Add(a_ChunkX + 1, a_ChunkZ); Add(a_ChunkX + 1, a_ChunkZ - 1); Add(a_ChunkX, a_ChunkZ + 1); Add(a_ChunkX, a_ChunkZ); Add(a_ChunkX, a_ChunkZ - 1); Add(a_ChunkX - 1, a_ChunkZ + 1); Add(a_ChunkX - 1, a_ChunkZ); Add(a_ChunkX - 1, a_ChunkZ - 1); } bool cLightingThread::cLightingChunkStay::OnAllChunksAvailable(void) { m_LightingThread.QueueChunkStay(*this); // Keep the ChunkStay alive: return false; } void cLightingThread::cLightingChunkStay::OnDisabled(void) { // Nothing needed in this callback }