1 #define _GNU_SOURCE // don't worry, GNU extensions are only used when available
2 #include <dragonstd/queue.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"
19 // this file is too long
20 Terrain *server_terrain;
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
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)
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);
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;
43 // send a chunk to a client and reset chunk request
44 static void send_chunk(ServerPlayer *player, TerrainChunk *chunk)
46 if (!within_load_distance(player, chunk->pos, server_config.load_distance))
49 pthread_rwlock_rdlock(&player->lock_peer);
51 dragonnet_peer_send_ToClientChunk(player->peer, &(ToClientChunk) {
53 .data = ((TerrainChunkMeta *) chunk->extra)->data,
55 pthread_rwlock_unlock(&player->lock_peer);
58 // send chunk to near clients
59 // chunk mutex has to be locked
60 static void send_chunk_to_near(TerrainChunk *chunk)
62 TerrainChunkMeta *meta = chunk->extra;
64 if (meta->state == CHUNK_GENERATING)
67 Blob_free(&meta->data);
68 meta->data = terrain_serialize_chunk(chunk);
70 database_save_chunk(chunk);
72 if (meta->state == CHUNK_CREATED)
75 server_player_iterate(&send_chunk, chunk);
78 // Iterator for sending changed chunks to near clients
79 static void iterator_send_chunk_to_near(TerrainChunk *chunk)
81 pthread_mutex_lock(&chunk->mtx);
82 send_chunk_to_near(chunk);
83 pthread_mutex_unlock(&chunk->mtx);
87 static void terrain_gen_step()
90 TerrainChunk *chunk = queue_deq(&terrain_gen_tasks, NULL);
95 TerrainChunkMeta *meta = chunk->extra;
98 list_ini(&changed_chunks);
99 list_apd(&changed_chunks, chunk);
101 terrain_gen_chunk(chunk, &changed_chunks);
103 pthread_mutex_lock(&chunk->mtx);
104 meta->state = CHUNK_READY;
105 pthread_mutex_unlock(&chunk->mtx);
107 list_clr(&changed_chunks, &iterator_send_chunk_to_near, NULL, NULL);
109 pthread_mutex_lock(&mtx_num_gen_chunks);
111 pthread_mutex_unlock(&mtx_num_gen_chunks);
114 // there was a time when i wrote actually useful comments lol
115 static void *terrain_gen_thread()
117 #ifdef __GLIBC__ // check whether bloat is enabled
118 pthread_setname_np(pthread_self(), "terrain_gen");
121 // extremely advanced logic
129 static void generate_chunk(TerrainChunk *chunk)
134 pthread_mutex_lock(&mtx_num_gen_chunks);
136 pthread_mutex_unlock(&mtx_num_gen_chunks);
138 TerrainChunkMeta *meta = chunk->extra;
139 meta->state = CHUNK_GENERATING;
140 queue_enq(&terrain_gen_tasks, chunk);
144 // note: all these functions require the chunk mutex to be locked, which is always the case when a terrain callback is invoked
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)
150 TerrainChunkMeta *meta = chunk->extra = malloc(sizeof *meta);
152 if (!database_load_chunk(chunk)) {
153 meta->state = CHUNK_CREATED;
154 meta->data = (Blob) {0, NULL};
157 chunk->data[x][y][z] = terrain_node_create(NODE_AIR, (Blob) {0, NULL});
158 meta->tgsb.raw.nodes[x][y][z] = STAGE_VOID;
163 // callback for deleting a chunk
165 static void on_delete_chunk(TerrainChunk *chunk)
167 TerrainChunkMeta *meta = chunk->extra;
169 Blob_free(&meta->data);
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)
177 TerrainChunkMeta *meta = chunk->extra;
179 if (meta->state < CHUNK_READY && !create)
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)
189 TerrainSetNodeArg *arg = _arg;
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];
195 if (new_tgs >= *tgs) {
199 list_add(arg->changed_chunks, chunk, chunk, &cmp_ref, NULL);
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)
212 send_chunk_to_near(chunk);
215 // generate a hut for new players to spawn in
216 static void generate_spawn_hut()
219 list_ini(&changed_chunks);
222 schematic_load(&spawn_hut, RESSOURCE_PATH "schematics/spawn_hut.txt", (SchematicMapping[]) {
224 .color = {0x7d, 0x54, 0x35},
225 .type = NODE_OAK_WOOD,
229 .color = {0x50, 0x37, 0x28},
230 .type = NODE_OAK_WOOD,
235 schematic_place(&spawn_hut, (v3s32) {0, spawn_height, 0},
236 STAGE_PLAYER, &changed_chunks);
238 schematic_delete(&spawn_hut);
240 // dynamic part of spawn hut - cannot be generated by a schematic
251 Blob wood_color = {0, NULL};
252 ColorData_write(&wood_color, &(ColorData) {{(f32) 0x7d / 0xff, (f32) 0x54 / 0xff, (f32) 0x35 / 0xff}});
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;
260 if (node != NODE_AIR)
266 if (node_defs[node].solid)
269 server_terrain_gen_node(pos,
270 terrain_node_create(node == NODE_LAVA
274 STAGE_PLAYER, &changed_chunks);
278 Blob_free(&wood_color);
279 list_clr(&changed_chunks, &iterator_send_chunk_to_near, NULL, NULL);
284 // called on server startup
285 void server_terrain_init()
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;
295 queue_ini(&terrain_gen_tasks);
296 terrain_gen_threads = malloc(sizeof *terrain_gen_threads * server_config.terrain_gen_threads);
298 pthread_mutex_init(&mtx_num_gen_chunks, NULL);
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);
304 // called on server shutdown
305 void server_terrain_deinit()
307 queue_fin(&terrain_gen_tasks);
309 queue_cnl(&terrain_gen_tasks);
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);
315 pthread_mutex_destroy(&mtx_num_gen_chunks);
316 queue_dst(&terrain_gen_tasks);
317 terrain_delete(server_terrain);
320 // handle chunk request from client (thread safe)
321 void server_terrain_requested_chunk(ServerPlayer *player, v3s32 pos)
323 if (within_load_distance(player, pos, server_config.load_distance)) {
324 TerrainChunk *chunk = terrain_get_chunk(server_terrain, pos, true);
326 pthread_mutex_lock(&chunk->mtx);
328 TerrainChunkMeta *meta = chunk->extra;
329 switch (meta->state) {
331 generate_chunk(chunk);
334 case CHUNK_GENERATING:
338 send_chunk(player, chunk);
341 pthread_mutex_unlock(&chunk->mtx);
345 static void update_percentage()
347 static s32 total = 3 * 3 * 21;
348 static s32 done = -1;
349 static s32 last_percentage = -1;
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);
358 if (percentage > last_percentage) {
359 last_percentage = percentage;
360 printf("[verbose] preparing spawn... %d%%\n", percentage);
365 // prepare spawn region
366 void server_terrain_prepare_spawn()
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++) {
376 TerrainChunk *chunk = terrain_get_chunk(server_terrain, (v3s32) {x, y, z}, true);
378 pthread_mutex_lock(&chunk->mtx);
379 if (((TerrainChunkMeta *) chunk->extra)->state == CHUNK_CREATED)
380 generate_chunk(chunk);
381 pthread_mutex_unlock(&chunk->mtx);
391 pthread_mutex_lock(&mtx_num_gen_chunks);
392 bool done = (num_gen_chunks == 0);
393 pthread_mutex_unlock(&mtx_num_gen_chunks);
401 s64 saved_spawn_height;
402 if (database_load_meta("spawn_height", &saved_spawn_height)) {
403 spawn_height = saved_spawn_height;
406 while (terrain_get_node(server_terrain, (v3s32) {0, ++spawn_height, 0}).type != NODE_AIR);
409 database_save_meta("spawn_height", spawn_height);
410 generate_spawn_hut();
414 void server_terrain_gen_node(v3s32 pos, TerrainNode node, TerrainGenStage tgs, List *changed_chunks)
416 TerrainSetNodeArg arg = {
418 .changed_chunks = changed_chunks,
421 terrain_set_node(server_terrain, pos, node, true, &arg);
424 s32 server_terrain_spawn_height()