1 #define _GNU_SOURCE // don't worry, GNU extensions are only used when available
3 #include <dragonstd/queue.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"
21 // this file is too long
22 Terrain *server_terrain;
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
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)
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);
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;
45 // send a chunk to a client and reset chunk request
46 static void send_chunk_to_client(ServerPlayer *player, TerrainChunk *chunk)
48 if (!within_load_distance(player, chunk->pos, server_config.load_distance))
51 pthread_rwlock_rdlock(&player->lock_peer);
53 dragonnet_peer_send_ToClientChunk(player->peer, &(ToClientChunk) {
55 .data = ((TerrainChunkMeta *) chunk->extra)->data,
57 pthread_rwlock_unlock(&player->lock_peer);
61 static void terrain_gen_step()
64 TerrainChunk *chunk = queue_deq(&terrain_gen_tasks, NULL);
69 TerrainChunkMeta *meta = chunk->extra;
72 list_ini(&changed_chunks);
73 list_apd(&changed_chunks, chunk);
75 terrain_gen_chunk(chunk, &changed_chunks);
77 pthread_mutex_lock(&meta->mtx);
78 meta->state = CHUNK_STATE_READY;
79 pthread_mutex_unlock(&meta->mtx);
81 server_terrain_lock_and_send_chunks(&changed_chunks);
83 pthread_mutex_lock(&mtx_num_gen_chunks);
85 pthread_mutex_unlock(&mtx_num_gen_chunks);
88 // there was a time when i wrote actually useful comments lol
89 static void *terrain_gen_thread()
91 #ifdef __GLIBC__ // check whether bloat is enabled
92 pthread_setname_np(pthread_self(), "terrain_gen");
95 // extremely advanced logic
103 static void generate_chunk(TerrainChunk *chunk)
108 pthread_mutex_lock(&mtx_num_gen_chunks);
110 pthread_mutex_unlock(&mtx_num_gen_chunks);
112 TerrainChunkMeta *meta = chunk->extra;
114 meta->state = CHUNK_STATE_GENERATING;
115 queue_enq(&terrain_gen_tasks, chunk);
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)
122 TerrainChunkMeta *meta = chunk->extra = malloc(sizeof *meta);
123 pthread_mutex_init(&meta->mtx, NULL);
125 if (database_load_chunk(chunk)) {
126 meta->data = terrain_serialize_chunk(server_terrain, chunk, &server_node_serialize_client);
128 meta->state = CHUNK_STATE_CREATED;
129 meta->data = (Blob) {0, NULL};
132 chunk->data[x][y][z] = server_node_create(NODE_AIR);
133 meta->tgsb.raw.nodes[x][y][z] = STAGE_VOID;
138 // callback for deleting a chunk
140 static void on_delete_chunk(TerrainChunk *chunk)
142 TerrainChunkMeta *meta = chunk->extra;
143 pthread_mutex_destroy(&meta->mtx);
145 Blob_free(&meta->data);
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)
153 if (mode == CHUNK_MODE_CREATE)
156 TerrainChunkMeta *meta = chunk->extra;
157 pthread_mutex_lock(&meta->mtx);
159 bool ret = meta->state == CHUNK_STATE_READY;
161 pthread_mutex_unlock(&meta->mtx);
165 // generate a hut for new players to spawn in
166 static void generate_spawn_hut()
169 list_ini(&changed_chunks);
172 schematic_load(&spawn_hut, RESSOURCE_PATH "schematics/spawn_hut.txt", (SchematicMapping[]) {
174 .color = {0x7d, 0x54, 0x35},
175 .type = NODE_OAK_WOOD,
179 .color = {0x50, 0x37, 0x28},
180 .type = NODE_OAK_WOOD,
185 schematic_place(&spawn_hut, (v3s32) {0, spawn_height, 0},
186 STAGE_PLAYER, &changed_chunks);
188 schematic_delete(&spawn_hut);
190 // dynamic part of spawn hut - cannot be generated by a schematic
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;
212 if (node != NODE_AIR)
218 if (node_def[node].solid)
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);
228 server_terrain_lock_and_send_chunks(&changed_chunks);
233 // called on server startup
234 void server_terrain_init()
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;
243 queue_ini(&terrain_gen_tasks);
244 terrain_gen_threads = malloc(sizeof *terrain_gen_threads * server_config.terrain_gen_threads);
246 pthread_mutex_init(&mtx_num_gen_chunks, NULL);
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);
252 // called on server shutdown
253 void server_terrain_deinit()
255 queue_fin(&terrain_gen_tasks);
257 queue_cnl(&terrain_gen_tasks);
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);
263 pthread_mutex_destroy(&mtx_num_gen_chunks);
264 queue_dst(&terrain_gen_tasks);
265 terrain_delete(server_terrain);
268 // handle chunk request from client (thread safe)
269 void server_terrain_requested_chunk(ServerPlayer *player, v3s32 pos)
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;
275 pthread_mutex_lock(&meta->mtx);
276 switch (meta->state) {
277 case CHUNK_STATE_CREATED:
278 generate_chunk(chunk);
281 case CHUNK_STATE_GENERATING:
284 case CHUNK_STATE_READY:
285 send_chunk_to_client(player, chunk);
289 pthread_mutex_unlock(&meta->mtx);
293 static void update_percentage()
295 static s32 total = 3 * 3 * 21;
296 static s32 done = -1;
297 static s32 last_percentage = -1;
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);
306 if (percentage > last_percentage) {
307 last_percentage = percentage;
308 printf("[verbose] preparing spawn... %d%%\n", percentage);
313 // prepare spawn region
314 void server_terrain_prepare_spawn()
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++) {
324 TerrainChunk *chunk = terrain_get_chunk(server_terrain, (v3s32) {x, y, z}, CHUNK_MODE_CREATE);
325 TerrainChunkMeta *meta = chunk->extra;
327 pthread_mutex_lock(&meta->mtx);
328 if (meta->state == CHUNK_STATE_CREATED)
329 generate_chunk(chunk);
330 pthread_mutex_unlock(&meta->mtx);
340 pthread_mutex_lock(&mtx_num_gen_chunks);
341 bool done = (num_gen_chunks == 0);
342 pthread_mutex_unlock(&mtx_num_gen_chunks);
350 s64 saved_spawn_height;
351 if (database_load_meta("spawn_height", &saved_spawn_height)) {
352 spawn_height = saved_spawn_height;
355 while (terrain_get_node(server_terrain, (v3s32) {0, ++spawn_height, 0}).type != NODE_AIR);
358 database_save_meta("spawn_height", spawn_height);
359 generate_spawn_hut();
363 void server_terrain_gen_node(v3s32 pos, TerrainNode node, TerrainGenStage new_tgs, List *changed_chunks)
366 TerrainChunk *chunk = terrain_get_chunk_nodep(server_terrain, pos, &offset, CHUNK_MODE_CREATE);
367 TerrainChunkMeta *meta = chunk->extra;
369 assert(pthread_rwlock_wrlock(&chunk->lock) == 0);
371 u32 *tgs = &meta->tgsb.raw.nodes[offset.x][offset.y][offset.z];
373 if (new_tgs < *tgs) {
374 pthread_rwlock_unlock(&chunk->lock);
375 server_node_delete(&node);
380 chunk->data[offset.x][offset.y][offset.z] = node;
383 list_add(changed_chunks, chunk, chunk, &cmp_ref, NULL);
385 server_terrain_send_chunk(chunk);
387 pthread_rwlock_unlock(&chunk->lock);
390 s32 server_terrain_spawn_height()
396 // send chunk to near clients
397 // meta mutex has to be locked
398 void server_terrain_send_chunk(TerrainChunk *chunk)
400 TerrainChunkMeta *meta = chunk->extra;
402 if (meta->state == CHUNK_STATE_GENERATING)
405 assert(pthread_rwlock_rdlock(&chunk->lock) == 0);
407 Blob_free(&meta->data);
408 meta->data = terrain_serialize_chunk(server_terrain, chunk, &server_node_serialize_client);
409 database_save_chunk(chunk);
411 pthread_rwlock_unlock(&chunk->lock);
413 if (meta->state == CHUNK_STATE_CREATED)
416 server_player_iterate(&send_chunk_to_client, chunk);
419 void server_terrain_lock_and_send_chunk(TerrainChunk *chunk)
421 TerrainChunkMeta *meta = chunk->extra;
423 pthread_mutex_lock(&meta->mtx);
424 server_terrain_send_chunk(chunk);
425 pthread_mutex_unlock(&meta->mtx);
428 void server_terrain_lock_and_send_chunks(List *changed_chunks)
430 list_clr(changed_chunks, &server_terrain_lock_and_send_chunk, NULL, NULL);