summaryrefslogtreecommitdiffstats
path: root/src/LightingThread.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/LightingThread.cpp')
-rw-r--r--src/LightingThread.cpp562
1 files changed, 562 insertions, 0 deletions
diff --git a/src/LightingThread.cpp b/src/LightingThread.cpp
new file mode 100644
index 000000000..d7e60e458
--- /dev/null
+++ b/src/LightingThread.cpp
@@ -0,0 +1,562 @@
+
+// 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"
+
+
+
+
+
+/// If more than this many chunks are in the queue, a warning is printed to the log
+#define WARN_ON_QUEUE_SIZE 800
+
+
+
+
+
+/// Chunk data callback that takes the chunk data and puts them into cLightingThread's m_BlockTypes[] / m_HeightMap[]:
+class cReader :
+ public cChunkDataCallback
+{
+ virtual void BlockTypes(const BLOCKTYPE * a_Type) override
+ {
+ // ROW is a block of 16 Blocks, one whole row is copied at a time (hopefully the compiler will optimize that)
+ // C++ doesn't permit copying arrays, but arrays as a part of a struct is ok :)
+ typedef struct {BLOCKTYPE m_Row[16]; } ROW;
+ ROW * InputRows = (ROW *)a_Type;
+ ROW * OutputRows = (ROW *)m_BlockTypes;
+ int InputIdx = 0;
+ int OutputIdx = m_ReadingChunkX + m_ReadingChunkZ * cChunkDef::Width * 3;
+ for (int y = 0; y < cChunkDef::Height; y++)
+ {
+ for (int z = 0; z < cChunkDef::Width; z++)
+ {
+ OutputRows[OutputIdx] = InputRows[InputIdx++];
+ OutputIdx += 3;
+ } // for z
+ // 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;
+ } // for y
+ } // BlockTypes()
+
+
+ virtual void HeightMap(const cChunkDef::HeightMap * a_Heightmap) override
+ {
+ typedef struct {HEIGHTTYPE m_Row[16]; } ROW;
+ ROW * InputRows = (ROW *)a_Heightmap;
+ ROW * OutputRows = (ROW *)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
+ }
+
+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
+ 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)
+} ;
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cLightingThread:
+
+cLightingThread::cLightingThread(void) :
+ super("cLightingThread"),
+ m_World(NULL)
+{
+}
+
+
+
+
+
+cLightingThread::~cLightingThread()
+{
+ Stop();
+}
+
+
+
+
+
+bool cLightingThread::Start(cWorld * a_World)
+{
+ ASSERT(m_World == NULL); // Not started yet
+ m_World = a_World;
+
+ return super::Start();
+}
+
+
+
+
+
+void cLightingThread::Stop(void)
+{
+ {
+ cCSLock Lock(m_CS);
+ for (sItems::iterator itr = m_Queue.begin(), end = m_Queue.end(); itr != end; ++itr)
+ {
+ delete itr->m_ChunkStay;
+ }
+ m_Queue.clear();
+ }
+ m_ShouldTerminate = true;
+ m_evtItemAdded.Set();
+
+ Wait();
+}
+
+
+
+
+
+void cLightingThread::QueueChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_CallbackAfter)
+{
+ ASSERT(m_World != NULL); // Did you call Start() properly?
+
+ cChunkStay * ChunkStay = new cChunkStay(m_World);
+ ChunkStay->Add(a_ChunkX + 1, ZERO_CHUNK_Y, a_ChunkZ + 1);
+ ChunkStay->Add(a_ChunkX + 1, ZERO_CHUNK_Y, a_ChunkZ);
+ ChunkStay->Add(a_ChunkX + 1, ZERO_CHUNK_Y, a_ChunkZ - 1);
+ ChunkStay->Add(a_ChunkX, ZERO_CHUNK_Y, a_ChunkZ + 1);
+ ChunkStay->Add(a_ChunkX, ZERO_CHUNK_Y, a_ChunkZ);
+ ChunkStay->Add(a_ChunkX, ZERO_CHUNK_Y, a_ChunkZ - 1);
+ ChunkStay->Add(a_ChunkX - 1, ZERO_CHUNK_Y, a_ChunkZ + 1);
+ ChunkStay->Add(a_ChunkX - 1, ZERO_CHUNK_Y, a_ChunkZ);
+ ChunkStay->Add(a_ChunkX - 1, ZERO_CHUNK_Y, a_ChunkZ - 1);
+ ChunkStay->Enable();
+ ChunkStay->Load();
+ cCSLock Lock(m_CS);
+ m_Queue.push_back(sItem(a_ChunkX, a_ChunkZ, ChunkStay, a_CallbackAfter));
+ if (m_Queue.size() > WARN_ON_QUEUE_SIZE)
+ {
+ LOGINFO("Lighting thread overloaded, %d items in queue", m_Queue.size());
+ }
+ m_evtItemAdded.Set();
+}
+
+
+
+
+
+void cLightingThread::WaitForQueueEmpty(void)
+{
+ cCSLock Lock(m_CS);
+ while (!m_ShouldTerminate && (!m_Queue.empty() || !m_PostponedQueue.empty()))
+ {
+ cCSUnlock Unlock(Lock);
+ m_evtQueueEmpty.Wait();
+ }
+}
+
+
+
+
+
+size_t cLightingThread::GetQueueLength(void)
+{
+ cCSLock Lock(m_CS);
+ return m_Queue.size() + m_PostponedQueue.size();
+}
+
+
+
+
+
+void cLightingThread::ChunkReady(int a_ChunkX, int a_ChunkZ)
+{
+ // Check all the items in the m_PostponedQueue, if the chunk is their neighbor, move the item to m_Queue
+
+ bool NewlyAdded = false;
+ {
+ cCSLock Lock(m_CS);
+ for (sItems::iterator itr = m_PostponedQueue.begin(); itr != m_PostponedQueue.end(); )
+ {
+ if (
+ (itr->x - a_ChunkX >= -1) && (itr->x - a_ChunkX <= 1) &&
+ (itr->x - a_ChunkX >= -1) && (itr->x - a_ChunkX <= 1)
+ )
+ {
+ // It is a neighbor
+ m_Queue.push_back(*itr);
+ itr = m_PostponedQueue.erase(itr);
+ NewlyAdded = true;
+ }
+ else
+ {
+ ++itr;
+ }
+ } // for itr - m_PostponedQueue[]
+ } // Lock(m_CS)
+
+ if (NewlyAdded)
+ {
+ m_evtItemAdded.Set(); // Notify the thread it has some work to do
+ }
+}
+
+
+
+
+
+void cLightingThread::Execute(void)
+{
+ while (true)
+ {
+ {
+ cCSLock Lock(m_CS);
+ if (m_Queue.size() == 0)
+ {
+ cCSUnlock Unlock(Lock);
+ m_evtItemAdded.Wait();
+ }
+ }
+
+ if (m_ShouldTerminate)
+ {
+ return;
+ }
+
+ // Process one items from the queue:
+ sItem Item;
+ {
+ cCSLock Lock(m_CS);
+ if (m_Queue.empty())
+ {
+ continue;
+ }
+ Item = m_Queue.front();
+ m_Queue.pop_front();
+ if (m_Queue.empty())
+ {
+ m_evtQueueEmpty.Set();
+ }
+ } // CSLock(m_CS)
+
+ LightChunk(Item);
+ }
+}
+
+
+
+
+
+
+void cLightingThread::LightChunk(cLightingThread::sItem & a_Item)
+{
+ cChunkDef::BlockNibbles BlockLight, SkyLight;
+
+ if (!ReadChunks(a_Item.x, a_Item.z))
+ {
+ // Neighbors not available. Re-queue in the postponed queue
+ cCSLock Lock(m_CS);
+ m_PostponedQueue.push_back(a_Item);
+ return;
+ }
+
+ /*
+ // DEBUG: torch somewhere:
+ m_BlockTypes[19 + 24 * cChunkDef::Width * 3 + (m_HeightMap[24 + 24 * cChunkDef::Width * 3] / 2) * BlocksPerYLayer] = E_BLOCK_TORCH;
+ // m_HeightMap[24 + 24 * cChunkDef::Width * 3]++;
+ */
+
+ 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.x, a_Item.z), 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);
+ }
+ }
+ }
+ //*/
+
+ 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.x, a_Item.z), cFile::fmWrite) &&
+ f2.Open(Printf("Chunk_%d_%d_sky.grab", a_Item.x, a_Item.z), cFile::fmWrite) &&
+ f3.Open(Printf("Chunk_%d_%d_glow.grab", a_Item.x, a_Item.z), 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);
+ }
+ }
+ }
+ //*/
+
+ CompressLight(m_BlockLight, BlockLight);
+ CompressLight(m_SkyLight, SkyLight);
+
+ m_World->ChunkLighted(a_Item.x, a_Item.z, BlockLight, SkyLight);
+
+ if (a_Item.m_Callback != NULL)
+ {
+ a_Item.m_Callback->Call(a_Item.x, a_Item.z);
+ }
+ delete a_Item.m_ChunkStay;
+}
+
+
+
+
+
+bool cLightingThread::ReadChunks(int a_ChunkX, int a_ChunkZ)
+{
+ cReader Reader;
+ Reader.m_BlockTypes = m_BlockTypes;
+ Reader.m_HeightMap = m_HeightMap;
+
+ for (int z = 0; z < 3; z++)
+ {
+ Reader.m_ReadingChunkZ = z;
+ for (int x = 0; x < 3; x++)
+ {
+ Reader.m_ReadingChunkX = x;
+ if (!m_World->GetChunkData(a_ChunkX + x - 1, a_ChunkZ + z - 1, Reader))
+ {
+ return false;
+ }
+ } // for z
+ } // for x
+
+ memset(m_BlockLight, 0, sizeof(m_BlockLight));
+ memset(m_SkyLight, 0, sizeof(m_SkyLight));
+ return true;
+}
+
+
+
+
+
+void cLightingThread::PrepareSkyLight(void)
+{
+ // Clear seeds:
+ memset(m_IsSeed1, 0, sizeof(m_IsSeed1));
+ m_NumSeeds = 0;
+
+ // 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;
+ int Current = m_HeightMap[idx] + 1;
+ 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 the top down to Current with all-light:
+ for (int y = cChunkDef::Height - 1, 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++] = 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++] = Index;
+ }
+ }
+ }
+}
+
+
+
+
+
+void cLightingThread::PrepareBlockLight(void)
+{
+ // Clear seeds:
+ memset(m_IsSeed1, 0, sizeof(m_IsSeed1));
+ memset(m_IsSeed2, 0, sizeof(m_IsSeed2));
+ m_NumSeeds = 0;
+
+ // Walk every column that has all XZ neighbors, make a seed for each light-emitting block:
+ 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;
+ for (int y = m_HeightMap[idx], Index = idx + y * BlocksPerYLayer; y >= 0; y--, Index -= BlocksPerYLayer)
+ {
+ if (g_BlockLightValue[m_BlockTypes[Index]] == 0)
+ {
+ continue;
+ }
+
+ // Add current block as a seed:
+ m_IsSeed1[Index] = true;
+ m_SeedIdx1[m_NumSeeds++] = Index;
+
+ // Light it up:
+ m_BlockLight[Index] = g_BlockLightValue[m_BlockTypes[Index]];
+ }
+ }
+ }
+}
+
+
+
+
+
+void cLightingThread::CalcLight(NIBBLETYPE * a_Light)
+{
+ int 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,
+ int a_NumSeedsIn, unsigned char * a_IsSeedIn, unsigned int * a_SeedIdxIn,
+ int & a_NumSeedsOut, unsigned char * a_IsSeedOut, unsigned int * a_SeedIdxOut
+)
+{
+ int NumSeedsOut = 0;
+ for (int i = 0; i < a_NumSeedsIn; i++)
+ {
+ int SeedIdx = a_SeedIdxIn[i];
+ int SeedX = SeedIdx % (cChunkDef::Width * 3);
+ int SeedZ = (SeedIdx / (cChunkDef::Width * 3)) % (cChunkDef::Width * 3);
+ int SeedY = SeedIdx / BlocksPerYLayer;
+
+ // 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 (SeedY < cChunkDef::Height - 1)
+ {
+ PropagateLight(a_Light, SeedIdx, SeedIdx + cChunkDef::Width * cChunkDef::Width * 3 * 3, NumSeedsOut, a_IsSeedOut, a_SeedIdxOut);
+ }
+ if (SeedY > 0)
+ {
+ PropagateLight(a_Light, SeedIdx, SeedIdx - cChunkDef::Width * cChunkDef::Width * 3 * 3, 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++] = (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;
+ }
+}
+
+
+
+