1 #define _GNU_SOURCE // don't worry, GNU extensions are only used when available
3 #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_node.h"
16 #include "server/server_terrain.h"
17 #include "server/terrain_gen.h"
20 // this file is too long
21 Terrain *server_terrain;
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
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)
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);
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;
44 // send a chunk to a client and reset chunk request
45 static void send_chunk_to_client(ServerPlayer *player, TerrainChunk *chunk)
47 if (!within_load_distance(player, chunk->pos, server_config.load_distance))
50 pthread_rwlock_rdlock(&player->lock_peer);
52 dragonnet_peer_send_ToClientChunk(player->peer, &(ToClientChunk) {
54 .data = ((TerrainChunkMeta *) chunk->extra)->data,
56 pthread_rwlock_unlock(&player->lock_peer);
60 static void terrain_gen_step()
63 TerrainChunk *chunk = queue_deq(&terrain_gen_tasks, NULL);
68 TerrainChunkMeta *meta = chunk->extra;
71 list_ini(&changed_chunks);
72 list_apd(&changed_chunks, chunk);
74 terrain_gen_chunk(chunk, &changed_chunks);
76 pthread_mutex_lock(&meta->mtx);
77 meta->state = CHUNK_STATE_READY;
78 pthread_mutex_unlock(&meta->mtx);
80 server_terrain_lock_and_send_chunks(&changed_chunks);
82 pthread_mutex_lock(&mtx_num_gen_chunks);
84 pthread_mutex_unlock(&mtx_num_gen_chunks);
87 // there was a time when i wrote actually useful comments lol
88 static void *terrain_gen_thread()
90 #ifdef __GLIBC__ // check whether bloat is enabled
91 pthread_setname_np(pthread_self(), "terrain_gen");
94 // extremely advanced logic
102 static void generate_chunk(TerrainChunk *chunk)
107 pthread_mutex_lock(&mtx_num_gen_chunks);
109 pthread_mutex_unlock(&mtx_num_gen_chunks);
111 TerrainChunkMeta *meta = chunk->extra;
113 meta->state = CHUNK_STATE_GENERATING;
114 queue_enq(&terrain_gen_tasks, chunk);
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)
121 TerrainChunkMeta *meta = chunk->extra = malloc(sizeof *meta);
122 pthread_mutex_init(&meta->mtx, NULL);
124 if (database_load_chunk(chunk)) {
125 meta->data = terrain_serialize_chunk(server_terrain, chunk, &server_node_serialize_client);
127 meta->state = CHUNK_STATE_CREATED;
128 meta->data = (Blob) {0, NULL};
131 chunk->data[x][y][z] = server_node_create(NODE_AIR);
132 meta->tgsb.raw.nodes[x][y][z] = STAGE_VOID;
137 // callback for deleting a chunk
139 static void on_delete_chunk(TerrainChunk *chunk)
141 TerrainChunkMeta *meta = chunk->extra;
142 pthread_mutex_destroy(&meta->mtx);
144 Blob_free(&meta->data);
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)
152 if (mode == CHUNK_MODE_CREATE)
155 TerrainChunkMeta *meta = chunk->extra;
156 pthread_mutex_lock(&meta->mtx);
158 bool ret = meta->state == CHUNK_STATE_READY;
160 pthread_mutex_unlock(&meta->mtx);
164 // generate a hut for new players to spawn in
165 static void generate_spawn_hut()
168 list_ini(&changed_chunks);
171 schematic_load(&spawn_hut, RESSOURCE_PATH "schematics/spawn_hut.txt", (SchematicMapping[]) {
173 .color = {0x7d, 0x54, 0x35},
174 .type = NODE_OAK_WOOD,
178 .color = {0x50, 0x37, 0x28},
179 .type = NODE_OAK_WOOD,
184 schematic_place(&spawn_hut, (v3s32) {0, spawn_height, 0},
185 STAGE_PLAYER, &changed_chunks);
187 schematic_delete(&spawn_hut);
189 // dynamic part of spawn hut - cannot be generated by a schematic
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;
211 if (node != NODE_AIR)
217 if (node_def[node].solid)
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);
227 server_terrain_lock_and_send_chunks(&changed_chunks);
232 // called on server startup
233 void server_terrain_init()
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;
242 queue_ini(&terrain_gen_tasks);
243 terrain_gen_threads = malloc(sizeof *terrain_gen_threads * server_config.terrain_gen_threads);
245 pthread_mutex_init(&mtx_num_gen_chunks, NULL);
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);
251 // called on server shutdown
252 void server_terrain_deinit()
254 queue_fin(&terrain_gen_tasks);
256 queue_cnl(&terrain_gen_tasks);
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);
262 pthread_mutex_destroy(&mtx_num_gen_chunks);
263 queue_dst(&terrain_gen_tasks);
264 terrain_delete(server_terrain);
267 // handle chunk request from client (thread safe)
268 void server_terrain_requested_chunk(ServerPlayer *player, v3s32 pos)
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;
274 pthread_mutex_lock(&meta->mtx);
275 switch (meta->state) {
276 case CHUNK_STATE_CREATED:
277 generate_chunk(chunk);
280 case CHUNK_STATE_GENERATING:
283 case CHUNK_STATE_READY:
284 send_chunk_to_client(player, chunk);
288 pthread_mutex_unlock(&meta->mtx);
292 static void update_percentage()
294 static s32 total = 3 * 3 * 21;
295 static s32 done = -1;
296 static s32 last_percentage = -1;
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);
305 if (percentage > last_percentage) {
306 last_percentage = percentage;
307 printf("[verbose] preparing spawn... %d%%\n", percentage);
312 // prepare spawn region
313 void server_terrain_prepare_spawn()
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++) {
323 TerrainChunk *chunk = terrain_get_chunk(server_terrain, (v3s32) {x, y, z}, CHUNK_MODE_CREATE);
324 TerrainChunkMeta *meta = chunk->extra;
326 pthread_mutex_lock(&meta->mtx);
327 if (meta->state == CHUNK_STATE_CREATED)
328 generate_chunk(chunk);
329 pthread_mutex_unlock(&meta->mtx);
339 pthread_mutex_lock(&mtx_num_gen_chunks);
340 bool done = (num_gen_chunks == 0);
341 pthread_mutex_unlock(&mtx_num_gen_chunks);
349 s64 saved_spawn_height;
350 if (database_load_meta("spawn_height", &saved_spawn_height)) {
351 spawn_height = saved_spawn_height;
354 while (terrain_get_node(server_terrain, (v3s32) {0, ++spawn_height, 0}).type != NODE_AIR);
357 database_save_meta("spawn_height", spawn_height);
358 generate_spawn_hut();
362 void server_terrain_gen_node(v3s32 pos, TerrainNode node, TerrainGenStage new_tgs, List *changed_chunks)
365 TerrainChunk *chunk = terrain_get_chunk_nodep(server_terrain, pos, &offset, CHUNK_MODE_CREATE);
366 TerrainChunkMeta *meta = chunk->extra;
368 assert(pthread_rwlock_wrlock(&chunk->lock) == 0);
370 u32 *tgs = &meta->tgsb.raw.nodes[offset.x][offset.y][offset.z];
372 if (new_tgs < *tgs) {
373 pthread_rwlock_unlock(&chunk->lock);
374 server_node_delete(&node);
379 chunk->data[offset.x][offset.y][offset.z] = node;
382 list_add(changed_chunks, chunk, chunk, &cmp_ref, NULL);
384 server_terrain_send_chunk(chunk);
386 pthread_rwlock_unlock(&chunk->lock);
389 s32 server_terrain_spawn_height()
395 // send chunk to near clients
396 // meta mutex has to be locked
397 void server_terrain_send_chunk(TerrainChunk *chunk)
399 TerrainChunkMeta *meta = chunk->extra;
401 if (meta->state == CHUNK_STATE_GENERATING)
404 assert(pthread_rwlock_rdlock(&chunk->lock) == 0);
406 Blob_free(&meta->data);
407 meta->data = terrain_serialize_chunk(server_terrain, chunk, &server_node_serialize_client);
408 database_save_chunk(chunk);
410 pthread_rwlock_unlock(&chunk->lock);
412 if (meta->state == CHUNK_STATE_CREATED)
415 server_player_iterate(&send_chunk_to_client, chunk);
418 void server_terrain_lock_and_send_chunk(TerrainChunk *chunk)
420 TerrainChunkMeta *meta = chunk->extra;
422 pthread_mutex_lock(&meta->mtx);
423 server_terrain_send_chunk(chunk);
424 pthread_mutex_unlock(&meta->mtx);
427 void server_terrain_lock_and_send_chunks(List *changed_chunks)
429 list_clr(changed_chunks, &server_terrain_lock_and_send_chunk, NULL, NULL);