]> git.lizzy.rs Git - dragonblocks_alpha.git/blob - src/client/client_terrain.c
e8eae363b9a34f8f4c45a3a0ce1c72ac4d41410b
[dragonblocks_alpha.git] / src / client / client_terrain.c
1 #define _GNU_SOURCE // don't worry, GNU extensions are only used when available
2 #include <assert.h>
3 #include <dragonstd/queue.h>
4 #include <sched.h>
5 #include <stdatomic.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <pthread.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"
17 #include "facedir.h"
18
19 #define MAX_REQUESTS 4
20
21 Terrain *client_terrain;
22
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)
29
30 // meshgen functions
31
32 // dequeue callback to update queue state in a thread safe manner
33 static TerrainChunk *set_dequeued(TerrainChunk *chunk)
34 {
35         TerrainChunkMeta *meta = chunk->extra;
36
37         assert(pthread_rwlock_wrlock(&meta->lock_state) == 0);
38         meta->queue = false;
39         if (meta->state < CHUNK_STATE_DIRTY)
40                 chunk = NULL;
41         else
42                 meta->state = CHUNK_STATE_CLEAN;
43         pthread_rwlock_unlock(&meta->lock_state);
44
45         return chunk;
46 }
47
48 // mesh generator step
49 static void meshgen_step()
50 {
51         TerrainChunk *chunk = queue_deq(&meshgen_tasks, &set_dequeued);
52
53         if (chunk)
54                 terrain_gfx_make_chunk_model(chunk);
55 }
56
57 // sync functions
58
59 // send chunk request command to server
60 static void request_chunk(v3s32 pos)
61 {
62         dragonnet_peer_send_ToServerRequestChunk(client, &(ToServerRequestChunk) {
63                 .pos = pos
64         });
65 }
66
67 // terrain synchronisation step
68 static void sync_step()
69 {
70         static u64 tick = 1;
71         static v3s32 *old_requests = NULL;
72         static size_t old_num_requests = 0;
73
74         ClientEntity *entity = client_player_entity_local();
75         if (!entity) {
76                 sched_yield();
77                 return;
78         }
79
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);
83
84         refcount_drp(&entity->rc);
85
86         u64 last_tick = tick++;
87
88         v3s32 *requests = malloc(MAX_REQUESTS * sizeof *requests);
89         size_t num_requests = 0;
90
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);
94
95                 if (chunk) {
96                         TerrainChunkMeta *meta = chunk->extra;
97
98                         // re-request chunks that got out of and then back into range
99                         if (meta->sync && meta->sync < last_tick)
100                                 request_chunk(pos);
101
102                         meta->sync = tick;
103                 } else if (num_requests < MAX_REQUESTS) {
104                         // avoid duplicate requests
105                         bool requested = false;
106
107                         for (size_t i = 0; i < old_num_requests; i++) {
108                                 if (v3s32_equals(old_requests[i], pos)) {
109                                         requested = true;
110                                         break;
111                                 }
112                         }
113
114                         if (!requested)
115                                 request_chunk(pos);
116
117                         requests[num_requests++] = pos;
118                 }
119         }
120
121         if (old_requests)
122                 free(old_requests);
123
124         old_requests = requests;
125         old_num_requests = num_requests;
126 }
127
128 // pthread routine for meshgen and sync thread
129
130 static struct LoopThread {
131         const char *name;
132         void (*step)();
133 } loop_threads[2] = {
134         {"meshgen", &meshgen_step},
135         {   "sync",    &sync_step},
136 };
137
138 static void *loop_routine(struct LoopThread *thread)
139 {
140 #ifdef __GLIBC__ // check whether bloat is enabled
141         pthread_setname_np(pthread_self(), thread->name);
142 #endif // __GLIBC__
143
144         // warning: extremely advanced logic
145         while (!cancel)
146                 thread->step();
147
148         return NULL;
149 }
150
151 // terrain callbacks
152 // note: all these functions require the chunk mutex to be locked, which is always the case when a terrain callback is invoked
153
154 // callback for initializing a newly created chunk
155 // allocate and initialize meta data
156 static void on_create_chunk(TerrainChunk *chunk)
157 {
158         TerrainChunkMeta *meta = chunk->extra = malloc(sizeof *meta);
159
160         meta->queue = false;
161         meta->state = CHUNK_STATE_INIT;
162         pthread_rwlock_init(&meta->lock_state, NULL);
163
164         for (int i = 0; i < 6; i++)
165                 meta->neighbors[i] = 0;
166         meta->num_neighbors = 0;
167
168         for (int i = 0; i < 6; i++)
169                 meta->depends[i] = true;
170
171         meta->has_model = false;
172         meta->model = NULL;
173         pthread_mutex_init(&meta->mtx_model, NULL);
174
175         meta->empty = false;
176
177         meta->sync = 0;
178 }
179
180 // callback for deleting a chunk
181 // free meta data
182 static void on_delete_chunk(TerrainChunk *chunk)
183 {
184         free(chunk->extra);
185 }
186
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)
190 {
191         if (mode != CHUNK_MODE_PASSIVE)
192                 return true;
193
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);
198
199         return ret;
200 }
201
202 // public functions
203
204 // called on startup
205 void client_terrain_init()
206 {
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;
212
213         cancel = false;
214         queue_ini(&meshgen_tasks);
215
216         client_terrain_set_load_distance(10); // some initial fuck idk just in case server is stupid
217
218         sync_thread = 0;
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???
222 }
223
224 // called on shutdown
225 void client_terrain_deinit()
226 {
227         queue_clr(&meshgen_tasks, NULL, NULL, NULL);
228         terrain_delete(client_terrain);
229 }
230
231 // start meshgen and sync threads
232 void client_terrain_start()
233 {
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]);
236
237         pthread_create(&sync_thread, NULL, (void *) &loop_routine, &loop_threads[1]);
238 }
239
240 // stop meshgen and sync threads
241 void client_terrain_stop()
242 {
243         cancel = true;
244         queue_cnl(&meshgen_tasks);
245
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);
250
251         if (sync_thread)
252                 pthread_join(sync_thread, NULL);
253 }
254
255 // update load distance
256 void client_terrain_set_load_distance(u32 dist)
257 {
258         load_distance = dist;
259         load_chunks = facecache_count(load_distance);
260         debug_menu_changed(ENTRY_LOAD_DISTANCE);
261 }
262
263 // return load distance
264 u32 client_terrain_get_load_distance()
265 {
266         return load_distance;
267 }
268
269 // enqueue chunk to mesh update queue
270 void client_terrain_meshgen_task(TerrainChunk *chunk, bool changed)
271 {
272         TerrainChunkMeta *meta = chunk->extra;
273
274         assert(pthread_rwlock_wrlock(&meta->lock_state) == 0);
275         bool queue = meta->queue;
276         pthread_rwlock_unlock(&meta->lock_state);
277
278         if (queue)
279                 return;
280
281         assert(pthread_rwlock_rdlock(&chunk->lock) == 0);
282         bool empty = meta->empty;
283         pthread_rwlock_unlock(&chunk->lock);
284
285         if (empty)
286                 set_dequeued(chunk);
287
288         pthread_mutex_lock(&meta->mtx_model);
289         if (empty) {
290                 meta->has_model = true;
291
292                 if (meta->model) {
293                         meta->model->flags.delete = 1;
294                         meta->model = NULL;
295                 }
296         } else {
297                 meta->queue = true;
298                 if (meta->has_model && changed)
299                         queue_ppd(&meshgen_tasks, chunk);
300                 else
301                         queue_enq(&meshgen_tasks, chunk);
302         }
303         pthread_mutex_unlock(&meta->mtx_model);
304 }
305
306 static void iterator_meshgen_task(TerrainChunk *chunk)
307 {
308         client_terrain_meshgen_task(chunk, true);
309 }
310
311 void client_terrain_receive_chunk(__attribute__((unused)) void *peer, ToClientChunk *pkt)
312 {
313         // get/create chunk
314         TerrainChunk *chunk = terrain_get_chunk(client_terrain, pkt->pos, CHUNK_MODE_CREATE);
315         TerrainChunkMeta *meta = chunk->extra;
316
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);
323
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;
328
329                 if (init) {
330                         // if this is first time, initialize references in both ways
331
332                         // get existing neighbor chunk
333                         TerrainChunk *neighbor = terrain_get_chunk(
334                                 client_terrain, v3s32_add(chunk->pos, facedir[i]), CHUNK_MODE_NOCREATE);
335                         if (!neighbor)
336                                 continue;
337                         TerrainChunkMeta *neighbor_meta = neighbor->extra;
338
339                         // initialize reference from us to neighbor
340                         meta->neighbors[i] = neighbor;
341                         meta->num_neighbors++;
342
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++;
347                 } else {
348                         // get reference
349                         TerrainChunk *neighbor = meta->neighbors[i];
350                         if (!neighbor)
351                                 continue;
352                         TerrainChunkMeta *neighbor_meta = neighbor->extra;
353
354                         // don't change state of non-dependant neighbor
355                         if (!neighbor_meta->depends[j])
356                                 continue;
357
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);
362                 }
363         }
364
365         // deserialize data
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);
370
371         // collect meshgen tasks and schedule them after chunk states have been updated
372         List meshgen_tasks;
373         list_ini(&meshgen_tasks);
374
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);
379
380         // if all neighbors are there, schedule meshgen
381         if (meta->num_neighbors == 6)
382                 list_apd(&meshgen_tasks, chunk);
383
384         // notify neighbors (and self)
385         for (int i = 0; i < 6; i++) {
386                 // select neighbor chunk
387                 TerrainChunk *neighbor = meta->neighbors[i];
388                 if (!neighbor)
389                         continue;
390                 TerrainChunkMeta *neighbor_meta = neighbor->extra;
391
392                 // don't bother with chunks that don't depend us
393                 if (!neighbor_meta->depends[i % 2 ? i - 1 : i + 1])
394                         continue;
395
396                 // don't change state if neighbors are not all present
397                 if (neighbor_meta->num_neighbors != 6)
398                         continue;
399
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);
404
405                 // remeber to schedule meshgen task later
406                 list_apd(&meshgen_tasks, neighbor);
407         }
408
409         // schedule meshgen tasks
410         list_clr(&meshgen_tasks, (void *) &iterator_meshgen_task, NULL, NULL);
411 }