1 #define _GNU_SOURCE // don't worry, GNU extensions are only used when available
2 #include <dragonstd/queue.h>
8 #include "client/client.h"
9 #include "client/facedir.h"
10 #include "client/facecache.h"
11 #include "client/client_config.h"
12 #include "client/client_player.h"
13 #include "client/client_terrain.h"
14 #include "client/debug_menu.h"
15 #include "client/terrain_gfx.h"
17 #define MAX_REQUESTS 4
19 Terrain *client_terrain;
21 static atomic_bool cancel; // used to notify meshgen and sync thread about quit
22 static Queue meshgen_tasks; // TerrainCHunk * queue (thread safe)
23 static pthread_t *meshgen_threads; // consumer threads for meshgen queue
24 static pthread_t sync_thread; // this thread requests new / changed chunks from server
25 static u32 load_distance; // load distance sent by server
26 static size_t load_chunks; // cached number of facecache positions to process every sync step (matches load distance)
30 // dequeue callback to update queue state in a thread safe manner
31 static TerrainChunk *set_dequeued(TerrainChunk *chunk)
33 pthread_mutex_lock(&chunk->mtx);
34 ((TerrainChunkMeta *) chunk->extra)->queue = false;
35 pthread_mutex_unlock(&chunk->mtx);
40 // mesh generator step
41 static void meshgen_step()
43 TerrainChunk *chunk = queue_deq(&meshgen_tasks, &set_dequeued);
46 terrain_gfx_make_chunk_model(chunk);
51 // send chunk request command to server
52 static void request_chunk(v3s32 pos)
54 dragonnet_peer_send_ToServerRequestChunk(client, &(ToServerRequestChunk) {
59 // terrain synchronisation step
60 static void sync_step()
63 static v3s32 *old_requests = NULL;
64 static size_t old_num_requests = 0;
67 ClientEntity *entity = client_player_entity_local();
70 pthread_rwlock_rdlock(&entity->lock_pos_rot);
71 player_pos = entity->data.pos;
72 pthread_rwlock_unlock(&entity->lock_pos_rot);
74 refcount_drp(&entity->rc);
80 v3s32 center = terrain_node_to_chunk_pos((v3s32) {player_pos.x, player_pos.y, player_pos.z}, NULL);
82 u64 last_tick = tick++;
84 v3s32 *requests = malloc(MAX_REQUESTS * sizeof *requests);
85 size_t num_requests = 0;
87 for (size_t i = 0; i < load_chunks; i++) {
88 v3s32 pos = v3s32_add(facecache_get(i), center);
89 TerrainChunk *chunk = terrain_get_chunk(client_terrain, pos, false);
92 pthread_mutex_lock(&chunk->mtx);
94 TerrainChunkMeta *meta = chunk->extra;
95 switch (meta->state) {
97 // re-request chunks that got back into range
98 if (meta->sync < last_tick)
100 __attribute__((fallthrough));
103 meta->state = CHUNK_READY;
107 case CHUNK_RECIEVING:
111 pthread_mutex_unlock(&chunk->mtx);
112 } else if (num_requests < MAX_REQUESTS) {
113 // avoid duplicate requests
114 bool requested = false;
116 for (size_t i = 0; i < old_num_requests; i++) {
117 if (v3s32_equals(old_requests[i], pos)) {
126 requests[num_requests++] = pos;
133 old_requests = requests;
134 old_num_requests = num_requests;
137 // pthread routine for meshgen and sync thread
139 static struct LoopThread {
142 } loop_threads[2] = {
143 {"meshgen", &meshgen_step},
144 { "sync", &sync_step},
147 static void *loop_routine(struct LoopThread *thread)
149 #ifdef __GLIBC__ // check whether bloat is enabled
150 pthread_setname_np(pthread_self(), thread->name);
153 // warning: extremely advanced logic
161 // note: all these functions require the chunk mutex to be locked, which is always the case when a terrain callback is invoked
163 // callback for initializing a newly created chunk
164 // allocate and initialize meta data
165 static void on_create_chunk(TerrainChunk *chunk)
167 TerrainChunkMeta *meta = chunk->extra = malloc(sizeof *meta);
169 meta->state = CHUNK_RECIEVING;
174 meta->has_model = false;
175 for (int i = 0; i < 6; i++)
176 meta->depends[i] = false;
179 // callback for deleting a chunk
181 static void on_delete_chunk(TerrainChunk *chunk)
186 // callback for determining whether a chunk should be returned by terrain_get_chunk
187 // hold back chunks that have not been fully read from server yet when the create flag is not set
188 static bool on_get_chunk(TerrainChunk *chunk, bool create)
190 return create || ((TerrainChunkMeta *) chunk->extra)->state > CHUNK_RECIEVING;
196 void client_terrain_init()
198 client_terrain = terrain_create();
199 client_terrain->callbacks.create_chunk = &on_create_chunk;
200 client_terrain->callbacks.delete_chunk = &on_delete_chunk;
201 client_terrain->callbacks.get_chunk = &on_get_chunk;
202 client_terrain->callbacks.set_node = NULL;
203 client_terrain->callbacks.after_set_node = NULL;
206 queue_ini(&meshgen_tasks);
208 client_terrain_set_load_distance(10); // some initial fuck idk just in case server is stupid
211 meshgen_threads = malloc(sizeof *meshgen_threads * client_config.meshgen_threads);
212 for (unsigned int i = 0; i < client_config.meshgen_threads; i++)
213 meshgen_threads[i] = 0; // but why???
216 // called on shutdown
217 void client_terrain_deinit()
219 queue_clr(&meshgen_tasks, NULL, NULL, NULL);
220 terrain_delete(client_terrain);
223 // start meshgen and sync threads
224 void client_terrain_start()
226 for (unsigned int i = 0; i < client_config.meshgen_threads; i++)
227 pthread_create(&meshgen_threads[i], NULL, (void *) &loop_routine, &loop_threads[0]);
229 pthread_create(&sync_thread, NULL, (void *) &loop_routine, &loop_threads[1]);
232 // stop meshgen and sync threads
233 void client_terrain_stop()
236 queue_cnl(&meshgen_tasks);
238 for (unsigned int i = 0; i < client_config.meshgen_threads; i++)
239 if (meshgen_threads[i])
240 pthread_join(meshgen_threads[i], NULL);
241 free(meshgen_threads);
244 pthread_join(sync_thread, NULL);
247 // update load distance
248 void client_terrain_set_load_distance(u32 dist)
250 load_distance = dist;
251 load_chunks = facecache_count(load_distance);
252 debug_menu_changed(ENTRY_LOAD_DISTANCE);
255 // return load distance
256 u32 client_terrain_get_load_distance()
258 return load_distance;
261 // called when a chunk was recieved from server
262 void client_terrain_chunk_received(TerrainChunk *chunk)
264 pthread_mutex_lock(&chunk->mtx);
265 TerrainChunkMeta *meta = chunk->extra;
267 if (meta->state == CHUNK_RECIEVING)
268 meta->state = CHUNK_FRESH;
270 pthread_mutex_unlock(&chunk->mtx);
272 client_terrain_meshgen_task(chunk, true);
274 for (int i = 0; i < 6; i++) {
275 TerrainChunk *neighbor = terrain_get_chunk(client_terrain,
276 v3s32_sub(chunk->pos, facedir[i]), false);
280 pthread_mutex_lock(&neighbor->mtx);
281 TerrainChunkMeta *neighbor_meta = neighbor->extra;
282 if (neighbor_meta->depends[i])
283 client_terrain_meshgen_task(neighbor, true);
284 pthread_mutex_unlock(&neighbor->mtx);
288 // enqueue chunk to mesh update queue
289 void client_terrain_meshgen_task(TerrainChunk *chunk, bool changed)
291 TerrainChunkMeta *meta = chunk->extra;
296 meta->has_model = true;
299 meta->model->flags.delete = 1;
305 if (meta->has_model && changed)
306 queue_ppd(&meshgen_tasks, chunk);
308 queue_enq(&meshgen_tasks, chunk);