// LightingThread.cpp
// Implements the cLightingThread class representing the thread that processes requests for lighting
#include "Globals.h"
#include "LightingThread.h"
#include "ChunkMap.h"
#include "ChunkStay.h"
#include "World.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 cChunkData & a_ChunkBuffer) override
{
BLOCKTYPE * OutputRows = m_BlockTypes;
int InputIdx = 0;
int OutputIdx = m_ReadingChunkX + m_ReadingChunkZ * cChunkDef::Width * 3;
int MaxHeight = std::min(+cChunkDef::Height, m_MaxHeight + 16); // Need 16 blocks above the highest
for (int y = 0; y < MaxHeight; y++)
{
for (int z = 0; z < cChunkDef::Width; z++)
{
a_ChunkBuffer.CopyBlockTypes(OutputRows + OutputIdx * 16, static_cast<size_t>(InputIdx * 16), 16);
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
{
// Copy the entire heightmap, distribute it into the 3x3 chunk blob:
typedef struct {HEIGHTTYPE m_Row[16]; } ROW;
const ROW * InputRows = reinterpret_cast<const ROW *>(a_Heightmap);
ROW * OutputRows = reinterpret_cast<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
// 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)
{
}
} ;
////////////////////////////////////////////////////////////////////////////////
// cLightingThread:
cLightingThread::cLightingThread(void) :
super("cLightingThread"),
m_World(nullptr),
m_MaxHeight(0),
m_NumSeeds(0)
{
}
cLightingThread::~cLightingThread()
{
Stop();
}
bool cLightingThread::Start(cWorld * a_World)
{
ASSERT(m_World == nullptr); // Not started yet
m_World = a_World;
return super::Start();
}
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();
Wait();
}
void cLightingThread::QueueChunk(int a_ChunkX, int a_ChunkZ, std::unique_ptr<cChunkCoordCallback> a_CallbackAfter)
{
ASSERT(m_World != nullptr); // Did you call Start() properly?
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<cLightingChunkStay *>(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:
if (m_World->IsChunkLighted(a_Item.m_ChunkX, a_Item.m_ChunkZ))
{
if (a_Item.m_CallbackAfter != nullptr)
{
a_Item.m_CallbackAfter->Call(a_Item.m_ChunkX, a_Item.m_ChunkZ);
}
return;
}
cChunkDef::BlockNibbles BlockLight, SkyLight;
ReadChunks(a_Item.m_ChunkX, a_Item.m_ChunkZ);
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);
}
}
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;
// 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++] = static_cast<UInt32>(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<UInt32>(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 (cBlockInfo::GetLightValue(m_BlockTypes[Index]) == 0)
{
continue;
}
// Add current block as a seed:
m_IsSeed1[Index] = true;
m_SeedIdx1[m_NumSeeds++] = static_cast<UInt32>(Index);
// Light it up:
m_BlockLight[Index] = cBlockInfo::GetLightValue(m_BlockTypes[Index]);
}
}
}
}
void cLightingThread::PrepareBlockLight2(void)
{
// 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 y = 0; y < m_MaxHeight; y++)
{
int BaseY = y * BlocksPerYLayer; // Partial offset into m_BlockTypes for the Y coord
for (int z = 1; z < cChunkDef::Width * 3 - 1; z++)
{
int HBaseZ = z * cChunkDef::Width * 3; // Partial offset into m_Heightmap for the Z coord
int BaseZ = BaseY + HBaseZ; // Partial offset into m_BlockTypes for the Y and Z coords
for (int x = 1; x < cChunkDef::Width * 3 - 1; x++)
{
int idx = BaseZ + x;
if (y > m_HeightMap[HBaseZ + x])
{
// We're above the heightmap, ignore the block
continue;
}
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<UInt32>(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<UInt32>(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++] = static_cast<NIBBLETYPE>(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::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<cChunkCoordCallback> 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
}