]> git.lizzy.rs Git - dragonblocks_alpha.git/blob - src/client/client_terrain.c
9a3e52f78545a2171cfde8a61d528fd5769ffbb0
[dragonblocks_alpha.git] / src / client / client_terrain.c
1 #define _GNU_SOURCE // don't worry, GNU extensions are only used when available
2 #include <dragonstd/queue.h>
3 #include <sched.h>
4 #include <stdatomic.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <pthread.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"
16
17 #define MAX_REQUESTS 4
18
19 Terrain *client_terrain;
20
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)
27
28 // meshgen functions
29
30 // dequeue callback to update queue state in a thread safe manner
31 static TerrainChunk *set_dequeued(TerrainChunk *chunk)
32 {
33         pthread_mutex_lock(&chunk->mtx);
34         ((TerrainChunkMeta *) chunk->extra)->queue = false;
35         pthread_mutex_unlock(&chunk->mtx);
36
37         return chunk;
38 }
39
40 // mesh generator step
41 static void meshgen_step()
42 {
43         TerrainChunk *chunk = queue_deq(&meshgen_tasks, &set_dequeued);
44
45         if (chunk)
46                 terrain_gfx_make_chunk_model(chunk);
47 }
48
49 // sync functions
50
51 // send chunk request command to server
52 static void request_chunk(v3s32 pos)
53 {
54         dragonnet_peer_send_ToServerRequestChunk(client, &(ToServerRequestChunk) {
55                 .pos = pos
56         });
57 }
58
59 // terrain synchronisation step
60 static void sync_step()
61 {
62         static u64 tick = 0;
63         static v3s32 *old_requests = NULL;
64         static size_t old_num_requests = 0;
65
66         v3f64 player_pos;
67         ClientEntity *entity = client_player_entity_local();
68
69         if (entity) {
70                 pthread_rwlock_rdlock(&entity->lock_pos_rot);
71                 player_pos = entity->data.pos;
72                 pthread_rwlock_unlock(&entity->lock_pos_rot);
73
74                 refcount_drp(&entity->rc);
75         } else {
76                 sched_yield();
77                 return;
78         }
79
80         v3s32 center = terrain_node_to_chunk_pos((v3s32) {player_pos.x, player_pos.y, player_pos.z}, NULL);
81
82         u64 last_tick = tick++;
83
84         v3s32 *requests = malloc(MAX_REQUESTS * sizeof *requests);
85         size_t num_requests = 0;
86
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);
90
91                 if (chunk) {
92                         pthread_mutex_lock(&chunk->mtx);
93
94                         TerrainChunkMeta *meta = chunk->extra;
95                         switch (meta->state) {
96                                 case CHUNK_READY:
97                                         // re-request chunks that got back into range
98                                         if (meta->sync < last_tick)
99                                                 request_chunk(pos);
100                                         __attribute__((fallthrough));
101
102                                 case CHUNK_FRESH:
103                                         meta->state = CHUNK_READY;
104                                         meta->sync = tick;
105                                         break;
106
107                                 case CHUNK_RECIEVING:
108                                         break;
109                         }
110
111                         pthread_mutex_unlock(&chunk->mtx);
112                 } else if (num_requests < MAX_REQUESTS) {
113                         // avoid duplicate requests
114                         bool requested = false;
115
116                         for (size_t i = 0; i < old_num_requests; i++) {
117                                 if (v3s32_equals(old_requests[i], pos)) {
118                                         requested = true;
119                                         break;
120                                 }
121                         }
122
123                         if (!requested)
124                                 request_chunk(pos);
125
126                         requests[num_requests++] = pos;
127                 }
128         }
129
130         if (old_requests)
131                 free(old_requests);
132
133         old_requests = requests;
134         old_num_requests = num_requests;
135 }
136
137 // pthread routine for meshgen and sync thread
138
139 static struct LoopThread {
140         const char *name;
141         void (*step)();
142 } loop_threads[2] = {
143         {"meshgen", &meshgen_step},
144         {   "sync",    &sync_step},
145 };
146
147 static void *loop_routine(struct LoopThread *thread)
148 {
149 #ifdef __GLIBC__ // check whether bloat is enabled
150         pthread_setname_np(pthread_self(), thread->name);
151 #endif // __GLIBC__
152
153         // warning: extremely advanced logic
154         while (!cancel)
155                 thread->step();
156
157         return NULL;
158 }
159
160 // terrain callbacks
161 // note: all these functions require the chunk mutex to be locked, which is always the case when a terrain callback is invoked
162
163 // callback for initializing a newly created chunk
164 // allocate and initialize meta data
165 static void on_create_chunk(TerrainChunk *chunk)
166 {
167         TerrainChunkMeta *meta = chunk->extra = malloc(sizeof *meta);
168
169         meta->state = CHUNK_RECIEVING;
170         meta->queue = false;
171         meta->sync = 0;
172         meta->model = NULL;
173         meta->empty = false;
174         meta->has_model = false;
175         for (int i = 0; i < 6; i++)
176                 meta->depends[i] = false;
177 }
178
179 // callback for deleting a chunk
180 // free meta data
181 static void on_delete_chunk(TerrainChunk *chunk)
182 {
183         free(chunk->extra);
184 }
185
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)
189 {
190         return create || ((TerrainChunkMeta *) chunk->extra)->state > CHUNK_RECIEVING;
191 }
192
193 // public functions
194
195 // called on startup
196 void client_terrain_init()
197 {
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;
204
205         cancel = false;
206         queue_ini(&meshgen_tasks);
207
208         client_terrain_set_load_distance(10); // some initial fuck idk just in case server is stupid
209
210         sync_thread = 0;
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???
214 }
215
216 // called on shutdown
217 void client_terrain_deinit()
218 {
219         queue_clr(&meshgen_tasks, NULL, NULL, NULL);
220         terrain_delete(client_terrain);
221 }
222
223 // start meshgen and sync threads
224 void client_terrain_start()
225 {
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]);
228
229         pthread_create(&sync_thread, NULL, (void *) &loop_routine, &loop_threads[1]);
230 }
231
232 // stop meshgen and sync threads
233 void client_terrain_stop()
234 {
235         cancel = true;
236         queue_cnl(&meshgen_tasks);
237
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);
242
243         if (sync_thread)
244                 pthread_join(sync_thread, NULL);
245 }
246
247 // update load distance
248 void client_terrain_set_load_distance(u32 dist)
249 {
250         load_distance = dist;
251         load_chunks = facecache_count(load_distance);
252         debug_menu_changed(ENTRY_LOAD_DISTANCE);
253 }
254
255 // return load distance
256 u32 client_terrain_get_load_distance()
257 {
258         return load_distance;
259 }
260
261 // called when a chunk was recieved from server
262 void client_terrain_chunk_received(TerrainChunk *chunk)
263 {
264         pthread_mutex_lock(&chunk->mtx);
265         TerrainChunkMeta *meta = chunk->extra;
266
267         if (meta->state == CHUNK_RECIEVING)
268                 meta->state = CHUNK_FRESH;
269
270         pthread_mutex_unlock(&chunk->mtx);
271
272         client_terrain_meshgen_task(chunk, true);
273
274         for (int i = 0; i < 6; i++) {
275                 TerrainChunk *neighbor = terrain_get_chunk(client_terrain,
276                         v3s32_sub(chunk->pos, facedir[i]), false);
277                 if (!neighbor)
278                         continue;
279
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);
285         }
286 }
287
288 // enqueue chunk to mesh update queue
289 void client_terrain_meshgen_task(TerrainChunk *chunk, bool changed)
290 {
291         TerrainChunkMeta *meta = chunk->extra;
292         if (meta->queue)
293                 return;
294
295         if (meta->empty) {
296                 meta->has_model = true;
297
298                 if (meta->model) {
299                         meta->model->flags.delete = 1;
300                         meta->model = NULL;
301                 }
302         } else {
303                 meta->queue = true;
304
305                 if (meta->has_model && changed)
306                         queue_ppd(&meshgen_tasks, chunk);
307                 else
308                         queue_enq(&meshgen_tasks, chunk);
309         }
310 }