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