11#include " chunkmanager.h"
22#include < cmath>
3+ #include < algorithm>
4+
5+ void logMsg (const std::string& msg);
36
47ChunkManager::ChunkManager (unsigned int seed)
58 : seed(seed)
69 , pool(std::max(2u , std::thread::hardware_concurrency() - 1))
7- // Leave one core for the main/render thread
8- {}
10+ {
11+ logMsg (" ChunkManager: seed=" + std::to_string (seed) +
12+ " threads=" + std::to_string (std::max (2u , std::thread::hardware_concurrency () - 1 )));
13+ }
914
15+ // ─────────────────────────────────────────────────────────────────────────────
16+ // Public API
17+ // ─────────────────────────────────────────────────────────────────────────────
1018void ChunkManager::update (int playerChunkX, int playerChunkZ) {
11- // ── 1. Build the set of chunks that SHOULD exist ──────────────────────
1219 std::unordered_map<ChunkPos, bool , ChunkPosHash> desired;
1320 for (int dx = -RENDER_DIST; dx <= RENDER_DIST; dx++) {
1421 for (int dz = -RENDER_DIST; dz <= RENDER_DIST; dz++) {
15- // Optional: circular render distance (feels more natural)
1622 if (dx*dx + dz*dz > RENDER_DIST*RENDER_DIST) continue ;
17- ChunkPos p{playerChunkX + dx, playerChunkZ + dz};
18- desired[p] = true ;
23+ desired[{playerChunkX+dx, playerChunkZ+dz}] = true ;
1924 }
2025 }
2126
2227 std::unique_lock<std::mutex> lock (mapMtx);
2328
24- // ── 2. Schedule generation for new chunks ─────────────────────────────
29+ // ── Schedule terrain for new chunks ───────── ─────────────────────────────
2530 for (auto & [pos, _] : desired) {
2631 if (chunkStatus.find (pos) == chunkStatus.end ()) {
27- chunkStatus[pos] = ChunkStatus::Pending;
28- scheduleChunk (pos);
32+ chunkStatus[pos] = ChunkStatus::TerrainPending;
33+ chunkData[pos] = std::make_unique<Chunk>();
34+ lock.unlock ();
35+ scheduleTerrainGen (pos);
36+ lock.lock ();
2937 }
3038 }
3139
32- // ── 3. Mark chunks outside range for unloading ────────────────────────
33- std::vector<ChunkPos> toRemove;
40+ // ── Advance TerrainDone → DecorationPending ───────────────────────────────
41+ // Requires all 8 neighbors to be at least TerrainDone
42+ std::vector<ChunkPos> readyForDeco;
3443 for (auto & [pos, status] : chunkStatus) {
35- if (desired.find (pos) == desired.end ()) {
36- // Only unload chunks that are already uploaded (or pending/generating)
37- // For pending/generating we just let them finish and immediately unload
38- toRemove.push_back (pos);
39- }
44+ if (status == ChunkStatus::TerrainDone && allNeighborsAtLeast (pos, ChunkStatus::TerrainDone))
45+ readyForDeco.push_back (pos);
4046 }
41- lock.unlock ();
42-
43- if (!toRemove.empty ()) {
44- std::unique_lock<std::mutex> ulock (unloadMtx);
45- for (auto & p : toRemove) unloadQueue.push_back (p);
46- std::unique_lock<std::mutex> mlock (mapMtx);
47- for (auto & p : toRemove) chunkStatus.erase (p);
47+ for (auto & pos : readyForDeco) {
48+ chunkStatus[pos] = ChunkStatus::DecorationPending;
49+ lock.unlock ();
50+ scheduleDecoration (pos);
51+ lock.lock ();
4852 }
49- }
5053
51- void ChunkManager::scheduleChunk (ChunkPos pos) {
52- // Note: mapMtx is already held by the caller (update)
53- chunkStatus[pos] = ChunkStatus::Generating;
54+ // ── Advance DecorationDone → MeshPending ─────────────────────────────────
55+ // Requires all 8 neighbors to also be DecorationDone or later
56+ // This ensures no neighbor will write leaves into us after our mesh is built
57+ std::vector<ChunkPos> readyForMesh;
58+ for (auto & [pos, status] : chunkStatus) {
59+ if (status == ChunkStatus::DecorationDone && allNeighborsAtLeast (pos, ChunkStatus::DecorationDone))
60+ readyForMesh.push_back (pos);
61+ }
62+ for (auto & pos : readyForMesh) {
63+ chunkStatus[pos] = ChunkStatus::MeshPending;
64+ lock.unlock ();
65+ scheduleMesh (pos);
66+ lock.lock ();
67+ }
5468
55- pool.enqueue ([this , pos]() {
56- // ── Stage 1: generate block data ──────────────────────────────────
57- auto chunk = std::make_unique<Chunk>();
58- chunk->generate (pos.x , pos.z , seed);
69+ // ── Unload out-of-range chunks ────────────────────────────────────────────
70+ std::vector<ChunkPos> toRemove;
71+ for (auto & [pos, _] : chunkStatus)
72+ if (desired.find (pos) == desired.end ())
73+ toRemove.push_back (pos);
5974
60- // ── Stage 2: build mesh ───────────────────────────────────────────
61- std::vector<float > verts = chunk->buildMesh (pos.x , pos.z );
75+ lock.unlock ();
6276
63- // ── Check if chunk was unloaded while we were working ─────────────
77+ if (!toRemove. empty ()) {
6478 {
65- std::unique_lock<std::mutex> lock (mapMtx);
66- auto it = chunkStatus.find (pos);
67- if (it == chunkStatus.end ()) return ; // was unloaded, discard
68- it->second = ChunkStatus::MeshReady;
79+ std::unique_lock<std::mutex> ul (unloadMtx);
80+ for (auto & p : toRemove) unloadQueue.push_back (p);
6981 }
70-
71- // ── Stage 3: hand off to main thread for GPU upload ───────────────
72- {
73- std::unique_lock<std::mutex> lock (uploadMtx);
74- uploadQueue.push (UploadRequest{pos, std::move (verts)});
82+ std::unique_lock<std::mutex> ml (mapMtx);
83+ for (auto & p : toRemove) {
84+ chunkStatus.erase (p);
85+ chunkData.erase (p);
7586 }
76- });
87+ }
7788}
7889
79- void ChunkManager::drainUploadQueue (std::vector<UploadRequest>& outRequests , int maxPerFrame) {
90+ void ChunkManager::drainUploadQueue (std::vector<UploadRequest>& out , int maxPerFrame) {
8091 std::unique_lock<std::mutex> lock (uploadMtx);
8192 int count = 0 ;
8293 while (!uploadQueue.empty () && count < maxPerFrame) {
83- outRequests .push_back (std::move (uploadQueue.front ()));
94+ out .push_back (std::move (uploadQueue.front ()));
8495 uploadQueue.pop ();
8596 count++;
8697 }
@@ -104,3 +115,130 @@ void ChunkManager::getUploadedChunks(std::vector<ChunkPos>& out) const {
104115 for (auto & [pos, status] : chunkStatus)
105116 if (status == ChunkStatus::Uploaded) out.push_back (pos);
106117}
118+
119+ Chunk* ChunkManager::getChunk (int cx, int cz) {
120+ std::unique_lock<std::mutex> lock (mapMtx);
121+ auto it = chunkData.find ({cx, cz});
122+ if (it == chunkData.end ()) return nullptr ;
123+ return it->second .get ();
124+ }
125+
126+ // ─────────────────────────────────────────────────────────────────────────────
127+ // Internal helpers
128+ // ─────────────────────────────────────────────────────────────────────────────
129+ bool ChunkManager::allNeighborsAtLeast (ChunkPos pos, ChunkStatus minStatus) {
130+ // mapMtx must be held by caller
131+ for (int dx = -1 ; dx <= 1 ; dx++) {
132+ for (int dz = -1 ; dz <= 1 ; dz++) {
133+ if (dx == 0 && dz == 0 ) continue ;
134+ auto it = chunkStatus.find ({pos.x +dx, pos.z +dz});
135+ if (it == chunkStatus.end ()) return false ;
136+ if (static_cast <int >(it->second ) < static_cast <int >(minStatus)) return false ;
137+ }
138+ }
139+ return true ;
140+ }
141+
142+ // ─────────────────────────────────────────────────────────────────────────────
143+ // Stage 1: Terrain generation
144+ // ─────────────────────────────────────────────────────────────────────────────
145+ void ChunkManager::scheduleTerrainGen (ChunkPos pos) {
146+ logMsg (" scheduleTerrainGen: (" + std::to_string (pos.x ) + " ," + std::to_string (pos.z ) + " )" );
147+ pool.enqueue ([this , pos]() {
148+ Chunk* myChunk = nullptr ;
149+ {
150+ std::unique_lock<std::mutex> lock (mapMtx);
151+ auto it = chunkData.find (pos);
152+ if (it == chunkData.end ()) return ;
153+ myChunk = it->second .get ();
154+ }
155+ if (!myChunk) return ;
156+
157+ myChunk->generateTerrain (pos.x , pos.z , seed);
158+
159+ {
160+ std::unique_lock<std::mutex> lock (mapMtx);
161+ auto sit = chunkStatus.find (pos);
162+ if (sit != chunkStatus.end ()) sit->second = ChunkStatus::TerrainDone;
163+ }
164+ });
165+ }
166+
167+ // ─────────────────────────────────────────────────────────────────────────────
168+ // Stage 2: Decoration (trees) — neighbors are all TerrainDone
169+ // ─────────────────────────────────────────────────────────────────────────────
170+ void ChunkManager::scheduleDecoration (ChunkPos pos) {
171+ logMsg (" scheduleDecoration: (" + std::to_string (pos.x ) + " ," + std::to_string (pos.z ) + " )" );
172+ pool.enqueue ([this , pos]() {
173+ Chunk* myChunk = nullptr ;
174+ {
175+ std::unique_lock<std::mutex> lock (mapMtx);
176+ auto it = chunkData.find (pos);
177+ if (it == chunkData.end ()) return ;
178+ myChunk = it->second .get ();
179+ }
180+ if (!myChunk) return ;
181+
182+ // neighborFn: gets neighbor chunk pointer safely
183+ auto neighborFn = [this ](int cx, int cz) -> Chunk* {
184+ std::unique_lock<std::mutex> lock (mapMtx);
185+ auto it = chunkData.find ({cx, cz});
186+ if (it == chunkData.end ()) return nullptr ;
187+ return it->second .get ();
188+ };
189+
190+ myChunk->generateDecorations (pos.x , pos.z , seed, neighborFn);
191+
192+ {
193+ std::unique_lock<std::mutex> lock (mapMtx);
194+ auto sit = chunkStatus.find (pos);
195+ if (sit != chunkStatus.end ()) sit->second = ChunkStatus::DecorationDone;
196+ }
197+ logMsg (" decoration done: (" + std::to_string (pos.x ) + " ," + std::to_string (pos.z ) + " )" );
198+ });
199+ }
200+
201+ // ─────────────────────────────────────────────────────────────────────────────
202+ // Stage 3: Mesh building — all neighbors are DecorationDone
203+ // Passes neighbor chunks so inter-chunk face culling works correctly
204+ // ─────────────────────────────────────────────────────────────────────────────
205+ void ChunkManager::scheduleMesh (ChunkPos pos) {
206+ logMsg (" scheduleMesh: (" + std::to_string (pos.x ) + " ," + std::to_string (pos.z ) + " )" );
207+ pool.enqueue ([this , pos]() {
208+ Chunk* myChunk = nullptr ;
209+ {
210+ std::unique_lock<std::mutex> lock (mapMtx);
211+ auto it = chunkData.find (pos);
212+ if (it == chunkData.end ()) return ;
213+ myChunk = it->second .get ();
214+ }
215+ if (!myChunk) return ;
216+
217+ // Collect neighbor pointers for inter-chunk face culling
218+ // Safe to read since all neighbors are DecorationDone (no more writes)
219+ Chunk* neighbors[3 ][3 ]{};
220+ {
221+ std::unique_lock<std::mutex> lock (mapMtx);
222+ for (int dx = -1 ; dx <= 1 ; dx++) {
223+ for (int dz = -1 ; dz <= 1 ; dz++) {
224+ auto it = chunkData.find ({pos.x +dx, pos.z +dz});
225+ if (it != chunkData.end ())
226+ neighbors[dx+1 ][dz+1 ] = it->second .get ();
227+ }
228+ }
229+ }
230+
231+ std::vector<float > verts = myChunk->buildMesh (pos.x , pos.z , neighbors);
232+
233+ {
234+ std::unique_lock<std::mutex> lock (mapMtx);
235+ auto sit = chunkStatus.find (pos);
236+ if (sit != chunkStatus.end ()) sit->second = ChunkStatus::MeshReady;
237+ }
238+
239+ {
240+ std::unique_lock<std::mutex> lock (uploadMtx);
241+ uploadQueue.push (UploadRequest{pos, std::move (verts)});
242+ }
243+ });
244+ }
0 commit comments