// 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 { a_ChunkBuffer.CopyBlockTypes(m_BlockTypes.data()); } virtual void HeightMap(const cChunkDef::HeightMap * a_Heightmap) override { std::copy(std::begin(*a_Heightmap), std::end(*a_Heightmap), std::begin(m_HeightMap)); } public: cLightingThread::BlockDataArray & m_BlockTypes; // 3x3 chunks of block types, organized as a single XZY blob of data (instead of 3x3 XZY blobs) cLightingThread::HeightDataArray & m_HeightMap; // 3x3 chunks of height map, organized as a single XZY blob of data (instead of 3x3 XZY blobs) cReader(cLightingThread::BlockDataArray & a_BlockTypes, cLightingThread::HeightDataArray & a_HeightMap) : m_BlockTypes(a_BlockTypes), m_HeightMap(a_HeightMap) { } } ; //////////////////////////////////////////////////////////////////////////////// // cLightingThread: cLightingThread::cLightingThread(void) : super("cLightingThread"), m_World(nullptr) { } 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) { m_ShouldTerminate = true; m_evtItemDataLoaded.Set(); Wait(); } void cLightingThread::QueueChunk(int a_ChunkX, int a_ChunkZ, std::unique_ptr a_CallbackAfter) { ASSERT(m_World != nullptr); // Did you call Start() properly? // If the chunk is already lit, skip it: if (m_World->IsChunkValid(a_ChunkX, a_ChunkZ) && m_World->IsChunkLighted(a_ChunkX, a_ChunkZ)) { if (a_CallbackAfter != nullptr) { a_CallbackAfter->Call(a_ChunkX, a_ChunkZ, true); } return; } auto 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.emplace_back(ChunkStay); } ChunkStay->Enable(*m_World->GetChunkMap()); } size_t cLightingThread::GetQueueLength(void) { cCSLock Lock(m_CS); return m_PendingQueue.size(); } void cLightingThread::Execute(void) { while (!m_ShouldTerminate) { m_evtItemDataLoaded.Wait(); decltype(m_Queue) QueuedChunks; { cCSLock Lock(m_CS); std::swap(m_Queue, QueuedChunks); } for (const auto & Entry : QueuedChunks) { Entry->m_BlockTypes = cpp14::make_unique(); Entry->m_HeightMap = cpp14::make_unique(); } m_World->QueueTask( [&QueuedChunks](cWorld & a_World) { for (const auto & Entry : QueuedChunks) { cReader Reader(*Entry->m_BlockTypes, *Entry->m_HeightMap); VERIFY(a_World.GetChunkData(Entry->m_ChunkX, Entry->m_ChunkZ, Reader)); } } ).wait(); for (const auto & Entry : QueuedChunks) { LightChunk(*Entry); } } // while (!m_ShouldTerminate) } void cLightingThread::LightChunk(cLightingChunkStay & a_Item) { std::unique_ptr BlockLight{ cpp14::make_unique() }, SkyLight{ cpp14::make_unique() } ; CalcLight(a_Item, PrepareBlockLight(a_Item, *BlockLight), *BlockLight); /* // 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 * cLightingChunkStay::BlocksPerYLayer + z * cChunkDef::Width * 3, cChunkDef::Width * 3); for (int x = 0; x < cChunkDef::Width * 3; x++) { if (m_IsSeed1[y * cLightingChunkStay::BlocksPerYLayer + z * cChunkDef::Width * 3 + x]) { Seeds[x] = E_BLOCK_DIAMOND_BLOCK; } } f4.Write(Seeds, cChunkDef::Width * 3); } } f4.Close(); } //*/ CalcLight(a_Item, PrepareSkyLight(a_Item, *SkyLight), *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 * cLightingChunkStay::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 * cLightingChunkStay::BlocksPerYLayer + z * cChunkDef::Width * 3 + x] << 4; BlockLight[x] = m_BlockLight[y * cLightingChunkStay::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(); } //*/ std::shared_ptr CompressedBlockLight{ std::make_shared() }, CompressedSkyLight{ std::make_shared() } ; CompressLight(*BlockLight, *CompressedBlockLight); CompressLight(*SkyLight, *CompressedSkyLight); m_World->QueueTask( [&a_Item, CompressedBlockLight, CompressedSkyLight](cWorld & a_World) { a_World.ChunkLighted( a_Item.m_ChunkX, a_Item.m_ChunkZ, reinterpret_cast(*CompressedBlockLight->data()), reinterpret_cast(*CompressedSkyLight->data()) ); if (a_Item.m_CallbackAfter != nullptr) { a_Item.m_CallbackAfter->Call(a_Item.m_ChunkX, a_Item.m_ChunkZ, true); } a_Item.Disable(); } ); } std::vector cLightingThread::PrepareSkyLight(const cLightingChunkStay & a_Item, BlockDataArray & a_SkyLight) { std::vector IndicesToProcess; IndicesToProcess.reserve(a_Item.m_HeightMap->size() * 40); for (size_t Index = 0; Index != a_Item.m_HeightMap->size(); ++Index) { for (int Y = cChunkDef::Height - 1; Y >= (*a_Item.m_HeightMap)[Index]; --Y) { auto HeightAdjustedIndex = Index + Y * cChunkDef::Width * cChunkDef::Width; a_SkyLight[HeightAdjustedIndex] = 15; IndicesToProcess.emplace_back(HeightAdjustedIndex); } } return IndicesToProcess; } std::vector cLightingThread::PrepareBlockLight(const cLightingChunkStay & a_Item, BlockDataArray & a_BlockLight) { std::vector IndicesToProcess; IndicesToProcess.reserve(cChunkDef::Width * cChunkDef::Width); for (size_t Index = 0; Index != a_Item.m_BlockTypes->size(); ++Index) { auto Light = cBlockInfo::GetLightValue((*a_Item.m_BlockTypes)[Index]); a_BlockLight[Index] = Light; if (Light > 1) { IndicesToProcess.emplace_back(Index); } } return IndicesToProcess; } void cLightingThread::CalcLight(const cLightingChunkStay & a_Item, std::vector a_IndicesToProcess, BlockDataArray & a_Light) { while (!a_IndicesToProcess.empty()) { auto Back = a_IndicesToProcess.back(); a_IndicesToProcess.pop_back(); DiscoverLightAdjacents(a_IndicesToProcess, a_Item, a_Light, Back); } } void cLightingThread::DiscoverLightAdjacents(std::vector & a_IndicesToProcess, const cLightingChunkStay & a_Item, BlockDataArray & a_Light, size_t a_OriginatorIndex) { auto Position = cChunkDef::IndexToCoordinate(a_OriginatorIndex); if (Position.x % cChunkDef::Width != 15) { // TODO: update neighbouring chunk UpdateLightAdjacent(a_IndicesToProcess, a_Item, a_Light, a_OriginatorIndex, a_OriginatorIndex + 1); } if (Position.x % cChunkDef::Width != 0) { // TODO: update neighbouring chunk UpdateLightAdjacent(a_IndicesToProcess, a_Item, a_Light, a_OriginatorIndex, a_OriginatorIndex - 1); } if (Position.z % cChunkDef::Width != 15) { // TODO: update neighbouring chunk UpdateLightAdjacent(a_IndicesToProcess, a_Item, a_Light, a_OriginatorIndex, a_OriginatorIndex + cChunkDef::Width); } if (Position.z % cChunkDef::Width != 0) { // TODO: update neighbouring chunk UpdateLightAdjacent(a_IndicesToProcess, a_Item, a_Light, a_OriginatorIndex, a_OriginatorIndex - cChunkDef::Width); } if (Position.y != cChunkDef::Height - 1) { UpdateLightAdjacent(a_IndicesToProcess, a_Item, a_Light, a_OriginatorIndex, a_OriginatorIndex + cChunkDef::Width * cChunkDef::Width); } if (Position.y != 0) { UpdateLightAdjacent(a_IndicesToProcess, a_Item, a_Light, a_OriginatorIndex, a_OriginatorIndex - cChunkDef::Width * cChunkDef::Width); } } void cLightingThread::UpdateLightAdjacent(std::vector & a_IndicesToProcess, const cLightingChunkStay & a_Item, BlockDataArray & a_Light, size_t a_OriginatorIndex, size_t a_DestinationIndex) { // Assertions for index validity occur within PropagateLightToAdjacent if (PropagateLightToAdjacent(a_Item, a_Light, a_OriginatorIndex, a_DestinationIndex)) { a_IndicesToProcess.emplace_back(a_DestinationIndex); } } void cLightingThread::CompressLight(const BlockDataArray & a_LightArray, BlockNibbles & a_ChunkLight) { for (size_t Index = 0; Index != a_ChunkLight.size(); ++Index) { auto AdjustedIndex = Index * 2; a_ChunkLight[Index] = a_LightArray[AdjustedIndex] | static_cast(a_LightArray[AdjustedIndex + 1] << 4); } } //////////////////////////////////////////////////////////////////////////////// // cLightingThread::cLightingChunkStay: cLightingThread::cLightingChunkStay::cLightingChunkStay(cLightingThread & a_LightingThread, int a_ChunkX, int a_ChunkZ, std::unique_ptr a_CallbackAfter) : m_CallbackAfter(std::move(a_CallbackAfter)), m_ChunkX(a_ChunkX), m_ChunkZ(a_ChunkZ), m_MaxHeight(0), // Required by cReader to be initialised to zero m_LightingThread(a_LightingThread) { Add(a_ChunkX, a_ChunkZ); } bool cLightingThread::cLightingChunkStay::OnAllChunksAvailable(void) { if (m_LightingThread.m_ShouldTerminate) { return false; } ASSERT(m_LightingThread.m_World->IsChunkValid(m_ChunkX, m_ChunkZ)); // If the chunk is already lit, skip it: if (m_LightingThread.m_World->IsChunkLighted(m_ChunkX, m_ChunkZ)) { if (m_CallbackAfter != nullptr) { m_CallbackAfter->Call(m_ChunkX, m_ChunkZ, true); } { cCSLock Lock(m_LightingThread.m_CS); m_LightingThread.m_PendingQueue.erase( std::remove(m_LightingThread.m_PendingQueue.begin(), m_LightingThread.m_PendingQueue.end(), this), m_LightingThread.m_PendingQueue.end() ); } return true; } { cCSLock Lock(m_LightingThread.m_CS); m_LightingThread.m_PendingQueue.erase( std::remove(m_LightingThread.m_PendingQueue.begin(), m_LightingThread.m_PendingQueue.end(), this), m_LightingThread.m_PendingQueue.end() ); m_LightingThread.m_Queue.emplace_back(this); } m_LightingThread.m_evtItemDataLoaded.Set(); // Keep the ChunkStay alive: return false; } void cLightingThread::cLightingChunkStay::OnDisabled(void) { delete this; }