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