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