]> git.lizzy.rs Git - dragonblocks_alpha.git/blob - src/server/server_terrain.c
Fix client terrain crashes and performance
[dragonblocks_alpha.git] / src / server / server_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 <features.h>
5 #include <stdatomic.h>
6 #include <stdbool.h>
7 #include <stddef.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <unistd.h>
12 #include "interrupt.h"
13 #include "server/database.h"
14 #include "server/schematic.h"
15 #include "server/server_config.h"
16 #include "server/server_node.h"
17 #include "server/server_terrain.h"
18 #include "server/terrain_gen.h"
19 #include "terrain.h"
20
21 // this file is too long
22 Terrain *server_terrain;
23
24 static atomic_bool cancel;                 // remove the smooth
25 static Queue terrain_gen_tasks;            // this is terry the fat shark
26 static pthread_t *terrain_gen_threads;     // thread pool
27 static s32 spawn_height;                   // elevation to spawn players at
28 static unsigned int num_gen_chunks;        // number of enqueued / generating chunks
29 static pthread_mutex_t mtx_num_gen_chunks; // lock to protect the above
30
31 // utility functions
32
33 // return true if a player is close enough to a chunk to access it
34 static bool within_load_distance(ServerPlayer *player, v3s32 cpos, u32 dist)
35 {
36         pthread_rwlock_rdlock(&player->lock_pos);
37         v3s32 ppos = terrain_chunkp((v3s32) {player->pos.x, player->pos.y, player->pos.z});
38         pthread_rwlock_unlock(&player->lock_pos);
39
40         return abs(ppos.x - cpos.x) <= (s32) dist
41                 && abs(ppos.y - cpos.y) <= (s32) dist
42                 && abs(ppos.z - cpos.z) <= (s32) dist;
43 }
44
45 // send a chunk to a client and reset chunk request
46 static void send_chunk_to_client(ServerPlayer *player, TerrainChunk *chunk)
47 {
48         if (!within_load_distance(player, chunk->pos, server_config.load_distance))
49                 return;
50
51         pthread_rwlock_rdlock(&player->lock_peer);
52         if (player->peer)
53                 dragonnet_peer_send_ToClientChunk(player->peer, &(ToClientChunk) {
54                         .pos = chunk->pos,
55                         .data = ((TerrainChunkMeta *) chunk->extra)->data,
56                 });
57         pthread_rwlock_unlock(&player->lock_peer);
58 }
59
60 // me when the
61 static void terrain_gen_step()
62 {
63         // big chunkus
64         TerrainChunk *chunk = queue_deq(&terrain_gen_tasks, NULL);
65
66         if (!chunk)
67                 return;
68
69         TerrainChunkMeta *meta = chunk->extra;
70
71         List changed_chunks;
72         list_ini(&changed_chunks);
73         list_apd(&changed_chunks, chunk);
74
75         terrain_gen_chunk(chunk, &changed_chunks);
76
77         pthread_mutex_lock(&meta->mtx);
78         meta->state = CHUNK_STATE_READY;
79         pthread_mutex_unlock(&meta->mtx);
80
81         server_terrain_lock_and_send_chunks(&changed_chunks);
82
83         pthread_mutex_lock(&mtx_num_gen_chunks);
84         num_gen_chunks--;
85         pthread_mutex_unlock(&mtx_num_gen_chunks);
86 }
87
88 // there was a time when i wrote actually useful comments lol
89 static void *terrain_gen_thread()
90 {
91 #ifdef __GLIBC__ // check whether bloat is enabled
92         pthread_setname_np(pthread_self(), "terrain_gen");
93 #endif // __GLIBC__
94
95         // extremely advanced logic
96         while (!cancel)
97                 terrain_gen_step();
98
99         return NULL;
100         }
101
102 // enqueue chunk
103 static void generate_chunk(TerrainChunk *chunk)
104 {
105         if (cancel)
106                 return;
107
108         pthread_mutex_lock(&mtx_num_gen_chunks);
109         num_gen_chunks++;
110         pthread_mutex_unlock(&mtx_num_gen_chunks);
111
112         TerrainChunkMeta *meta = chunk->extra;
113
114         meta->state = CHUNK_STATE_GENERATING;
115         queue_enq(&terrain_gen_tasks, chunk);
116 }
117
118 // callback for initializing a newly created chunk
119 // load chunk from database or initialize state, tgstage buffer and data
120 static void on_create_chunk(TerrainChunk *chunk)
121 {
122         TerrainChunkMeta *meta = chunk->extra = malloc(sizeof *meta);
123         pthread_mutex_init(&meta->mtx, NULL);
124
125         if (database_load_chunk(chunk)) {
126                 meta->data = terrain_serialize_chunk(server_terrain, chunk, &server_node_serialize_client);
127         } else {
128                 meta->state = CHUNK_STATE_CREATED;
129                 meta->data = (Blob) {0, NULL};
130
131                 CHUNK_ITERATE {
132                         chunk->data[x][y][z] = server_node_create(NODE_AIR);
133                         meta->tgsb.raw.nodes[x][y][z] = STAGE_VOID;
134                 }
135         }
136 }
137
138 // callback for deleting a chunk
139 // free meta data
140 static void on_delete_chunk(TerrainChunk *chunk)
141 {
142         TerrainChunkMeta *meta = chunk->extra;
143         pthread_mutex_destroy(&meta->mtx);
144
145         Blob_free(&meta->data);
146         free(meta);
147 }
148
149 // callback for determining whether a chunk should be returned by terrain_get_chunk
150 // hold back chunks that are not fully generated except when the create flag is set to true
151 static bool on_get_chunk(TerrainChunk *chunk, int mode)
152 {
153         if (mode == CHUNK_MODE_CREATE)
154                 return true;
155
156         TerrainChunkMeta *meta = chunk->extra;
157         pthread_mutex_lock(&meta->mtx);
158
159         bool ret = meta->state == CHUNK_STATE_READY;
160
161         pthread_mutex_unlock(&meta->mtx);
162         return ret;
163 }
164
165 // generate a hut for new players to spawn in
166 static void generate_spawn_hut()
167 {
168         List changed_chunks;
169         list_ini(&changed_chunks);
170
171         List spawn_hut;
172         schematic_load(&spawn_hut, RESSOURCE_PATH "schematics/spawn_hut.txt", (SchematicMapping[]) {
173                 {
174                         .color = {0x7d, 0x54, 0x35},
175                         .type = NODE_OAK_WOOD,
176                         .use_color = true,
177                 },
178                 {
179                         .color = {0x50, 0x37, 0x28},
180                         .type = NODE_OAK_WOOD,
181                         .use_color = true,
182                 },
183         }, 2);
184
185         schematic_place(&spawn_hut, (v3s32) {0, spawn_height, 0},
186                 STAGE_PLAYER, &changed_chunks);
187
188         schematic_delete(&spawn_hut);
189
190         // dynamic part of spawn hut - cannot be generated by a schematic
191
192         v2s32 posts[6] = {
193                 {-4, -2},
194                 {-4, +3},
195                 {+3, -2},
196                 {+3, +3},
197                 {+4, +0},
198                 {+4, +1},
199         };
200
201         v3f32 wood_color = {
202                 (f32) 0x7d / 0xff,
203                 (f32) 0x54 / 0xff,
204                 (f32) 0x35 / 0xff};
205
206         for (int i = 0; i < 6; i++) {
207                 for (s32 y = spawn_height - 1;; y--) {
208                         v3s32 pos = {posts[i].x, y, posts[i].y};
209                         NodeType node = terrain_get_node(server_terrain, pos).type;
210
211                         if (i >= 4) {
212                                 if (node != NODE_AIR)
213                                         break;
214
215                                 pos.y++;
216                         }
217
218                         if (node_def[node].solid)
219                                 break;
220
221                         server_terrain_gen_node(pos, node == NODE_LAVA
222                                         ? server_node_create(NODE_VULCANO_STONE)
223                                         : server_node_create_color(NODE_OAK_WOOD, wood_color),
224                                 STAGE_PLAYER, &changed_chunks);
225                 }
226         }
227
228         server_terrain_lock_and_send_chunks(&changed_chunks);
229 }
230
231 // public functions
232
233 // called on server startup
234 void server_terrain_init()
235 {
236         server_terrain = terrain_create();
237         server_terrain->callbacks.create_chunk   = &on_create_chunk;
238         server_terrain->callbacks.delete_chunk   = &on_delete_chunk;
239         server_terrain->callbacks.get_chunk      = &on_get_chunk;
240         server_terrain->callbacks.delete_node    = &server_node_delete;
241
242         cancel = false;
243         queue_ini(&terrain_gen_tasks);
244         terrain_gen_threads = malloc(sizeof *terrain_gen_threads * server_config.terrain_gen_threads);
245         num_gen_chunks = 0;
246         pthread_mutex_init(&mtx_num_gen_chunks, NULL);
247
248         for (unsigned int i = 0; i < server_config.terrain_gen_threads; i++)
249                 pthread_create(&terrain_gen_threads[i], NULL, (void *) &terrain_gen_thread, NULL);
250 }
251
252 // called on server shutdown
253 void server_terrain_deinit()
254 {
255         queue_fin(&terrain_gen_tasks);
256         cancel = true;
257         queue_cnl(&terrain_gen_tasks);
258
259         for (unsigned int i = 0; i < server_config.terrain_gen_threads; i++)
260                 pthread_join(terrain_gen_threads[i], NULL);
261         free(terrain_gen_threads);
262
263         pthread_mutex_destroy(&mtx_num_gen_chunks);
264         queue_dst(&terrain_gen_tasks);
265         terrain_delete(server_terrain);
266 }
267
268 // handle chunk request from client (thread safe)
269 void server_terrain_requested_chunk(ServerPlayer *player, v3s32 pos)
270 {
271         if (within_load_distance(player, pos, server_config.load_distance)) {
272                 TerrainChunk *chunk = terrain_get_chunk(server_terrain, pos, CHUNK_MODE_CREATE);
273                 TerrainChunkMeta *meta = chunk->extra;
274
275                 pthread_mutex_lock(&meta->mtx);
276                 switch (meta->state) {
277                         case CHUNK_STATE_CREATED:
278                                 generate_chunk(chunk);
279                                 break;
280
281                         case CHUNK_STATE_GENERATING:
282                                 break;
283
284                         case CHUNK_STATE_READY:
285                                 send_chunk_to_client(player, chunk);
286                                 break;
287                 };
288
289                 pthread_mutex_unlock(&meta->mtx);
290         }
291 }
292
293 static void update_percentage()
294 {
295         static s32 total = 3 * 3 * 21;
296         static s32 done = -1;
297         static s32 last_percentage = -1;
298
299         if (done < total)
300                 done++;
301
302         pthread_mutex_lock(&mtx_num_gen_chunks);
303         s32 percentage = 100.0 * (done - num_gen_chunks) / total;
304         pthread_mutex_unlock(&mtx_num_gen_chunks);
305
306         if (percentage > last_percentage) {
307                 last_percentage = percentage;
308                 printf("[verbose] preparing spawn... %d%%\n", percentage);
309         }
310
311 }
312
313 // prepare spawn region
314 void server_terrain_prepare_spawn()
315 {
316         update_percentage();
317
318         for (s32 x = -1; x <= (s32) 1; x++) {
319                 for (s32 y = -10; y <= (s32) 10; y++) {
320                         for (s32 z = -1; z <= (s32) 1; z++) {
321                                 if (interrupt.set)
322                                         return;
323
324                                 TerrainChunk *chunk = terrain_get_chunk(server_terrain, (v3s32) {x, y, z}, CHUNK_MODE_CREATE);
325                                 TerrainChunkMeta *meta = chunk->extra;
326
327                                 pthread_mutex_lock(&meta->mtx);
328                                 if (meta->state == CHUNK_STATE_CREATED)
329                                         generate_chunk(chunk);
330                                 pthread_mutex_unlock(&meta->mtx);
331
332                                 update_percentage();
333                         }
334                 }
335         }
336
337         for (;;) {
338                 update_percentage();
339
340                 pthread_mutex_lock(&mtx_num_gen_chunks);
341                 bool done = (num_gen_chunks == 0);
342                 pthread_mutex_unlock(&mtx_num_gen_chunks);
343
344                 if (done)
345                         break;
346
347                 sched_yield();
348         }
349
350         s64 saved_spawn_height;
351         if (database_load_meta("spawn_height", &saved_spawn_height)) {
352                 spawn_height = saved_spawn_height;
353         } else {
354                 spawn_height = -1;
355                 while (terrain_get_node(server_terrain, (v3s32) {0, ++spawn_height, 0}).type != NODE_AIR);
356                 spawn_height += 5;
357
358                 database_save_meta("spawn_height", spawn_height);
359                 generate_spawn_hut();
360         }
361 }
362
363 void server_terrain_gen_node(v3s32 pos, TerrainNode node, TerrainGenStage new_tgs, List *changed_chunks)
364 {
365         v3s32 offset;
366         TerrainChunk *chunk = terrain_get_chunk_nodep(server_terrain, pos, &offset, CHUNK_MODE_CREATE);
367         TerrainChunkMeta *meta = chunk->extra;
368
369         assert(pthread_rwlock_wrlock(&chunk->lock) == 0);
370
371         u32 *tgs = &meta->tgsb.raw.nodes[offset.x][offset.y][offset.z];
372
373         if (new_tgs < *tgs) {
374                 pthread_rwlock_unlock(&chunk->lock);
375                 server_node_delete(&node);
376                 return;
377         }
378
379         *tgs = new_tgs;
380         chunk->data[offset.x][offset.y][offset.z] = node;
381
382         if (changed_chunks)
383                 list_add(changed_chunks, chunk, chunk, &cmp_ref, NULL);
384         else
385                 server_terrain_send_chunk(chunk);
386
387         pthread_rwlock_unlock(&chunk->lock);
388 }
389
390 s32 server_terrain_spawn_height()
391 {
392         // wow, so useful!
393         return spawn_height;
394 }
395
396 // send chunk to near clients
397 // meta mutex has to be locked
398 void server_terrain_send_chunk(TerrainChunk *chunk)
399 {
400         TerrainChunkMeta *meta = chunk->extra;
401
402         if (meta->state == CHUNK_STATE_GENERATING)
403                 return;
404
405         assert(pthread_rwlock_rdlock(&chunk->lock) == 0);
406
407         Blob_free(&meta->data);
408         meta->data = terrain_serialize_chunk(server_terrain, chunk, &server_node_serialize_client);
409         database_save_chunk(chunk);
410
411         pthread_rwlock_unlock(&chunk->lock);
412
413         if (meta->state == CHUNK_STATE_CREATED)
414                 return;
415
416         server_player_iterate(&send_chunk_to_client, chunk);
417 }
418
419 void server_terrain_lock_and_send_chunk(TerrainChunk *chunk)
420 {
421         TerrainChunkMeta *meta = chunk->extra;
422
423         pthread_mutex_lock(&meta->mtx);
424         server_terrain_send_chunk(chunk);
425         pthread_mutex_unlock(&meta->mtx);
426 }
427
428 void server_terrain_lock_and_send_chunks(List *changed_chunks)
429 {
430         list_clr(changed_chunks, &server_terrain_lock_and_send_chunk, NULL, NULL);
431 }