1 #define _GNU_SOURCE // don't worry, GNU extensions are only used when available
3 #include <dragonstd/queue.h>
9 #include "client/client.h"
10 #include "client/facecache.h"
11 #include "client/client_config.h"
12 #include "client/client_node.h"
13 #include "client/client_player.h"
14 #include "client/client_terrain.h"
15 #include "client/debug_menu.h"
16 #include "client/terrain_gfx.h"
19 #define MAX_REQUESTS 4
21 Terrain *client_terrain;
23 static atomic_bool cancel; // used to notify meshgen and sync thread about quit
24 static Queue meshgen_tasks; // TerrainCHunk * queue (thread safe)
25 static pthread_t *meshgen_threads; // consumer threads for meshgen queue
26 static pthread_t sync_thread; // this thread requests new / changed chunks from server
27 static u32 load_distance; // load distance sent by server
28 static size_t load_chunks; // cached number of facecache positions to process every sync step (matches load distance)
32 // dequeue callback to update queue state in a thread safe manner
33 static TerrainChunk *set_dequeued(TerrainChunk *chunk)
35 TerrainChunkMeta *meta = chunk->extra;
37 assert(pthread_rwlock_wrlock(&meta->lock_state) == 0);
39 if (meta->state < CHUNK_STATE_DIRTY)
42 meta->state = CHUNK_STATE_CLEAN;
43 pthread_rwlock_unlock(&meta->lock_state);
48 // mesh generator step
49 static void meshgen_step()
51 TerrainChunk *chunk = queue_deq(&meshgen_tasks, &set_dequeued);
54 terrain_gfx_make_chunk_model(chunk);
59 // send chunk request command to server
60 static void request_chunk(v3s32 pos)
62 dragonnet_peer_send_ToServerRequestChunk(client, &(ToServerRequestChunk) {
67 // terrain synchronisation step
68 static void sync_step()
71 static v3s32 *old_requests = NULL;
72 static size_t old_num_requests = 0;
74 ClientEntity *entity = client_player_entity_local();
80 pthread_rwlock_rdlock(&entity->lock_pos_rot);
81 v3s32 center = terrain_chunkp(v3f64_to_s32(entity->data.pos));
82 pthread_rwlock_unlock(&entity->lock_pos_rot);
84 refcount_drp(&entity->rc);
86 u64 last_tick = tick++;
88 v3s32 *requests = malloc(MAX_REQUESTS * sizeof *requests);
89 size_t num_requests = 0;
91 for (size_t i = 0; i < load_chunks; i++) {
92 v3s32 pos = v3s32_add(facecache_get(i), center);
93 TerrainChunk *chunk = terrain_get_chunk(client_terrain, pos, CHUNK_MODE_NOCREATE);
96 TerrainChunkMeta *meta = chunk->extra;
98 // re-request chunks that got out of and then back into range
99 if (meta->sync && meta->sync < last_tick)
103 } else if (num_requests < MAX_REQUESTS) {
104 // avoid duplicate requests
105 bool requested = false;
107 for (size_t i = 0; i < old_num_requests; i++) {
108 if (v3s32_equals(old_requests[i], pos)) {
117 requests[num_requests++] = pos;
124 old_requests = requests;
125 old_num_requests = num_requests;
128 // pthread routine for meshgen and sync thread
130 static struct LoopThread {
133 } loop_threads[2] = {
134 {"meshgen", &meshgen_step},
135 { "sync", &sync_step},
138 static void *loop_routine(struct LoopThread *thread)
140 #ifdef __GLIBC__ // check whether bloat is enabled
141 pthread_setname_np(pthread_self(), thread->name);
144 // warning: extremely advanced logic
152 // note: all these functions require the chunk mutex to be locked, which is always the case when a terrain callback is invoked
154 // callback for initializing a newly created chunk
155 // allocate and initialize meta data
156 static void on_create_chunk(TerrainChunk *chunk)
158 TerrainChunkMeta *meta = chunk->extra = malloc(sizeof *meta);
161 meta->state = CHUNK_STATE_INIT;
162 pthread_rwlock_init(&meta->lock_state, NULL);
164 for (int i = 0; i < 6; i++)
165 meta->neighbors[i] = 0;
166 meta->num_neighbors = 0;
168 for (int i = 0; i < 6; i++)
169 meta->depends[i] = true;
171 meta->has_model = false;
173 pthread_mutex_init(&meta->mtx_model, NULL);
180 // callback for deleting a chunk
182 static void on_delete_chunk(TerrainChunk *chunk)
187 // callback for determining whether a chunk should be returned by terrain_get_chunk
188 // hold back chunks that have not been fully read from server yet when the create flag is not set
189 static bool on_get_chunk(TerrainChunk *chunk, int mode)
191 if (mode != CHUNK_MODE_PASSIVE)
194 TerrainChunkMeta *meta = chunk->extra;
195 assert(pthread_rwlock_rdlock(&meta->lock_state) == 0);
196 bool ret = meta->state > CHUNK_STATE_DEPS;
197 pthread_rwlock_unlock(&meta->lock_state);
205 void client_terrain_init()
207 client_terrain = terrain_create();
208 client_terrain->callbacks.create_chunk = &on_create_chunk;
209 client_terrain->callbacks.delete_chunk = &on_delete_chunk;
210 client_terrain->callbacks.get_chunk = &on_get_chunk;
211 client_terrain->callbacks.delete_node = &client_node_delete;
214 queue_ini(&meshgen_tasks);
216 client_terrain_set_load_distance(10); // some initial fuck idk just in case server is stupid
219 meshgen_threads = malloc(sizeof *meshgen_threads * client_config.meshgen_threads);
220 for (unsigned int i = 0; i < client_config.meshgen_threads; i++)
221 meshgen_threads[i] = 0; // but why???
224 // called on shutdown
225 void client_terrain_deinit()
227 queue_clr(&meshgen_tasks, NULL, NULL, NULL);
228 terrain_delete(client_terrain);
231 // start meshgen and sync threads
232 void client_terrain_start()
234 for (unsigned int i = 0; i < client_config.meshgen_threads; i++)
235 pthread_create(&meshgen_threads[i], NULL, (void *) &loop_routine, &loop_threads[0]);
237 pthread_create(&sync_thread, NULL, (void *) &loop_routine, &loop_threads[1]);
240 // stop meshgen and sync threads
241 void client_terrain_stop()
244 queue_cnl(&meshgen_tasks);
246 for (unsigned int i = 0; i < client_config.meshgen_threads; i++)
247 if (meshgen_threads[i])
248 pthread_join(meshgen_threads[i], NULL);
249 free(meshgen_threads);
252 pthread_join(sync_thread, NULL);
255 // update load distance
256 void client_terrain_set_load_distance(u32 dist)
258 load_distance = dist;
259 load_chunks = facecache_count(load_distance);
260 debug_menu_changed(ENTRY_LOAD_DISTANCE);
263 // return load distance
264 u32 client_terrain_get_load_distance()
266 return load_distance;
269 // enqueue chunk to mesh update queue
270 void client_terrain_meshgen_task(TerrainChunk *chunk, bool changed)
272 TerrainChunkMeta *meta = chunk->extra;
274 assert(pthread_rwlock_wrlock(&meta->lock_state) == 0);
275 bool queue = meta->queue;
276 pthread_rwlock_unlock(&meta->lock_state);
281 assert(pthread_rwlock_rdlock(&chunk->lock) == 0);
282 bool empty = meta->empty;
283 pthread_rwlock_unlock(&chunk->lock);
288 pthread_mutex_lock(&meta->mtx_model);
290 meta->has_model = true;
293 meta->model->flags.delete = 1;
298 if (meta->has_model && changed)
299 queue_ppd(&meshgen_tasks, chunk);
301 queue_enq(&meshgen_tasks, chunk);
303 pthread_mutex_unlock(&meta->mtx_model);
306 static void iterator_meshgen_task(TerrainChunk *chunk)
308 client_terrain_meshgen_task(chunk, true);
311 void client_terrain_receive_chunk(__attribute__((unused)) void *peer, ToClientChunk *pkt)
314 TerrainChunk *chunk = terrain_get_chunk(client_terrain, pkt->pos, CHUNK_MODE_CREATE);
315 TerrainChunkMeta *meta = chunk->extra;
317 assert(pthread_rwlock_wrlock(&meta->lock_state) == 0);
318 // remember whether this is the first time we're receiving the chunk
319 bool init = meta->state == CHUNK_STATE_INIT;
320 // change state to receiving
321 meta->state = CHUNK_STATE_RECV;
322 pthread_rwlock_unlock(&meta->lock_state);
324 // notify/collect neighbors
325 for (int i = 0; i < 6; i++) {
326 // this is the reverse face index
327 int j = i % 2 ? i - 1 : i + 1;
330 // if this is first time, initialize references in both ways
332 // get existing neighbor chunk
333 TerrainChunk *neighbor = terrain_get_chunk(
334 client_terrain, v3s32_add(chunk->pos, facedir[i]), CHUNK_MODE_NOCREATE);
337 TerrainChunkMeta *neighbor_meta = neighbor->extra;
339 // initialize reference from us to neighbor
340 meta->neighbors[i] = neighbor;
341 meta->num_neighbors++;
343 // initialize reference from neighbor to us
344 // they (obviously) don't have all neighbors yet, so they are already in RECV state
345 neighbor_meta->neighbors[j] = chunk;
346 neighbor_meta->num_neighbors++;
349 TerrainChunk *neighbor = meta->neighbors[i];
352 TerrainChunkMeta *neighbor_meta = neighbor->extra;
354 // don't change state of non-dependant neighbor
355 if (!neighbor_meta->depends[j])
358 // if neighbor depends on us, set them to deps resolval state
359 assert(pthread_rwlock_wrlock(&neighbor_meta->lock_state) == 0);
360 neighbor_meta->state = CHUNK_STATE_DEPS;
361 pthread_rwlock_unlock(&neighbor_meta->lock_state);
366 assert(pthread_rwlock_wrlock(&chunk->lock) == 0);
367 meta->empty = (pkt->data.siz == 0);
368 terrain_deserialize_chunk(client_terrain, chunk, pkt->data, &client_node_deserialize);
369 pthread_rwlock_unlock(&chunk->lock);
371 // collect meshgen tasks and schedule them after chunk states have been updated
373 list_ini(&meshgen_tasks);
375 // set own state to dirty (if all neighbors are there) or resolving deps else
376 assert(pthread_rwlock_wrlock(&meta->lock_state) == 0);
377 meta->state = meta->num_neighbors == 6 ? CHUNK_STATE_DIRTY : CHUNK_STATE_DEPS;
378 pthread_rwlock_unlock(&meta->lock_state);
380 // if all neighbors are there, schedule meshgen
381 if (meta->num_neighbors == 6)
382 list_apd(&meshgen_tasks, chunk);
384 // notify neighbors (and self)
385 for (int i = 0; i < 6; i++) {
386 // select neighbor chunk
387 TerrainChunk *neighbor = meta->neighbors[i];
390 TerrainChunkMeta *neighbor_meta = neighbor->extra;
392 // don't bother with chunks that don't depend us
393 if (!neighbor_meta->depends[i % 2 ? i - 1 : i + 1])
396 // don't change state if neighbors are not all present
397 if (neighbor_meta->num_neighbors != 6)
400 // set state of dependant chunk to dirty
401 assert(pthread_rwlock_wrlock(&neighbor_meta->lock_state) == 0);
402 neighbor_meta->state = CHUNK_STATE_DIRTY;
403 pthread_rwlock_unlock(&neighbor_meta->lock_state);
405 // remeber to schedule meshgen task later
406 list_apd(&meshgen_tasks, neighbor);
409 // schedule meshgen tasks
410 list_clr(&meshgen_tasks, (void *) &iterator_meshgen_task, NULL, NULL);