]> git.lizzy.rs Git - dragonblocks_alpha.git/blob - src/server/server_map.c
Add trees
[dragonblocks_alpha.git] / src / server / server_map.c
1 #define _GNU_SOURCE
2 #include <stdlib.h>
3 #include <string.h>
4 #include <unistd.h>
5 #include <stdio.h>
6 #include "map.h"
7 #include "server/database.h"
8 #include "server/mapgen.h"
9 #include "server/server_map.h"
10 #include "signal_handlers.h"
11 #include "util.h"
12
13 struct ServerMap server_map;
14 static Server *server;
15
16 // utility functions
17
18 // send a block to a client and reset block request
19 static void send_block(Client *client, MapBlock *block)
20 {
21         MapBlockExtraData *extra = block->extra;
22
23         pthread_mutex_lock(&client->mtx);
24         if (client->state == CS_ACTIVE)
25                 (void) (write_u32(client->fd, CC_BLOCK)
26                         && write_v3s32(client->fd, block->pos)
27                         && write_u64(client->fd, extra->size)
28                         && write_u64(client->fd, extra->rawsize)
29                         && write(client->fd, extra->data, extra->size) != -1);
30         pthread_mutex_unlock(&client->mtx);
31 }
32
33 // send block to near clients
34 // block mutex has to be locked
35 static void send_block_to_near(MapBlock *block)
36 {
37         MapBlockExtraData *extra = block->extra;
38
39         if (extra->state == MBS_GENERATING)
40                 return;
41
42         if (extra->data)
43                 free(extra->data);
44
45         map_serialize_block(block, &extra->data, &extra->size, &extra->rawsize);
46         database_save_block(block);
47
48         if (extra->state == MBS_CREATED)
49                 return;
50
51         pthread_rwlock_rdlock(&server->players_rwlck);
52         ITERATE_LIST(&server->players, pair) {
53                 Client *client = pair->value;
54
55                 if (within_simulation_distance(client->pos, block->pos, server->config.simulation_distance))
56                         send_block(client, block);
57         }
58         pthread_rwlock_unlock(&server->players_rwlck);
59 }
60
61 // list_clear_func callback for sending changed blocks to near clients
62 static void list_send_block(void *key, unused void *value, unused void *arg)
63 {
64         MapBlock *block = key;
65
66         pthread_mutex_lock(&block->mtx);
67         send_block_to_near(block);
68         pthread_mutex_unlock(&block->mtx);
69 }
70
71 // pthread start routine for mapgen thread
72 static void *mapgen_thread(void *arg)
73 {
74         MapBlock *block = arg;
75         MapBlockExtraData *extra = block->extra;
76
77         pthread_mutex_lock(&block->mtx);
78         extra->state = MBS_GENERATING;
79         pthread_mutex_unlock(&block->mtx);
80
81         List changed_blocks = list_create(NULL);
82         list_put(&changed_blocks, block, NULL);
83
84         mapgen_generate_block(block, &changed_blocks);
85
86         pthread_mutex_lock(&block->mtx);
87         extra->state = MBS_READY;
88         pthread_mutex_unlock(&block->mtx);
89
90         list_clear_func(&changed_blocks, &list_send_block, NULL);
91
92         pthread_mutex_lock(&server_map.joining_threads_mtx);
93         if (! server_map.joining_threads) {
94                 pthread_mutex_lock(&server_map.mapgen_threads_mtx);
95                 list_delete(&server_map.mapgen_threads, &extra->mapgen_thread);
96                 pthread_mutex_unlock(&server_map.mapgen_threads_mtx);
97         }
98         pthread_mutex_unlock(&server_map.joining_threads_mtx);
99
100         return NULL;
101 }
102
103 // launch mapgen thread for block
104 // block mutex has to be locked
105 static void launch_mapgen_thread(MapBlock *block)
106 {
107         pthread_mutex_lock(&server_map.mapgen_threads_mtx);
108         pthread_t *thread_ptr = &((MapBlockExtraData *) block->extra)->mapgen_thread;
109         pthread_create(thread_ptr, NULL, mapgen_thread, block);
110         list_put(&server_map.mapgen_threads, thread_ptr, NULL);
111         pthread_mutex_unlock(&server_map.mapgen_threads_mtx);
112 }
113
114 // list_clear_func callback used to join running generator threads on shutdown
115 static void list_join_thread(void *key, unused void *value, unused void *arg)
116 {
117         pthread_join(*(pthread_t *) key, NULL);
118 }
119
120 // map callbacks
121 // note: all these functions require the block mutex to be locked, which is always the case when a map callback is invoked
122
123 // callback for initializing a newly created block
124 // load block from database or initialize state, mgstage buffer and data
125 static void on_create_block(MapBlock *block)
126 {
127         MapBlockExtraData *extra = block->extra = malloc(sizeof(MapBlockExtraData));
128
129         if (! database_load_block(block)) {
130                 extra->state = MBS_CREATED;
131                 extra->data = NULL;
132
133                 ITERATE_MAPBLOCK {
134                         block->data[x][y][z] = map_node_create(NODE_AIR, NULL, 0);
135                         extra->mgs_buffer[x][y][z] = MGS_VOID;
136                 }
137         }
138 }
139
140 // callback for deleting a block
141 // free extra data
142 static void on_delete_block(MapBlock *block)
143 {
144         MapBlockExtraData *extra = block->extra;
145
146         if (extra->data)
147                 free(extra->data);
148
149         free(extra);
150 }
151
152 // callback for determining whether a block should be returned by map_get_block
153 // hold back blocks that are not fully generated except when the create flag is set to true
154 static bool on_get_block(MapBlock *block, bool create)
155 {
156         MapBlockExtraData *extra = block->extra;
157
158         if (extra->state < MBS_READY && ! create)
159                 return false;
160
161         return true;
162 }
163
164 // callback for deciding whether a set_node call succeeds or not
165 // reject set_node calls that try to override nodes placed by later mapgen stages, else update mgs buffer - also make sure block is inserted into changed blocks list
166 static bool on_set_node(MapBlock *block, v3u8 offset, unused MapNode *node, void *arg)
167 {
168         MapgenSetNodeArg *msn_arg = arg;
169
170         MapgenStage mgs;
171
172         if (msn_arg)
173                 mgs = msn_arg->mgs;
174         else
175                 mgs = MGS_PLAYER;
176
177         MapgenStage *old_mgs = &((MapBlockExtraData *) block->extra)->mgs_buffer[offset.x][offset.y][offset.z];
178
179         if (mgs >= *old_mgs) {
180                 *old_mgs = mgs;
181
182                 if (msn_arg)
183                         list_put(msn_arg->changed_blocks, block, NULL);
184
185                 return true;
186         }
187
188         return false;
189 }
190
191 // callback for when a block changes
192 // send block to near clients if not part of map generation
193 static void on_after_set_node(MapBlock *block, unused v3u8 offset, void *arg)
194 {
195         if (! arg)
196                 send_block_to_near(block);
197 }
198
199 // join all map generation threads
200 static void join_mapgen_threads()
201 {
202         pthread_mutex_lock(&server_map.joining_threads_mtx);
203         server_map.joining_threads = true;
204         pthread_mutex_unlock(&server_map.joining_threads_mtx);
205
206         pthread_mutex_lock(&server_map.mapgen_threads_mtx);
207         list_clear_func(&server_map.mapgen_threads, &list_join_thread, NULL);
208         pthread_mutex_unlock(&server_map.mapgen_threads_mtx);
209
210         pthread_mutex_lock(&server_map.joining_threads_mtx);
211         server_map.joining_threads = false;
212         pthread_mutex_unlock(&server_map.joining_threads_mtx);
213 }
214
215 // generate a hut for new players to spawn in
216 static void generate_spawn_hut()
217 {
218         List changed_blocks = list_create(NULL);
219
220         for (s32 x = -4; x <= +4; x++) {
221                 for (s32 y = 0; y <= 3; y++) {
222                         for (s32 z = -3; z <= +2; z++) {
223                                 mapgen_set_node((v3s32) {x, server_map.spawn_height + y, z}, map_node_create(NODE_AIR, NULL, 0), MGS_PLAYER, &changed_blocks);
224                         }
225                 }
226         }
227
228         for (s32 x = -5; x <= +5; x++) {
229                 for (s32 z = -4; z <= +3; z++) {
230                         mapgen_set_node((v3s32) {x, server_map.spawn_height - 1, z}, map_node_create(NODE_OAK_WOOD, NULL, 0), MGS_PLAYER, &changed_blocks);
231                         mapgen_set_node((v3s32) {x, server_map.spawn_height + 4, z}, map_node_create(NODE_OAK_WOOD, NULL, 0), MGS_PLAYER, &changed_blocks);
232                 }
233         }
234
235         for (s32 y = 0; y <= 3; y++) {
236                 for (s32 x = -5; x <= +5; x++) {
237                         mapgen_set_node((v3s32) {x, server_map.spawn_height + y, -4}, map_node_create(((y == 1 || y == 2) && ((x >= -3 && x <= -1) || (x >= +1 && x <= +2))) ? NODE_AIR : NODE_OAK_WOOD, NULL, 0), MGS_PLAYER, &changed_blocks);
238                         mapgen_set_node((v3s32) {x, server_map.spawn_height + y, +3}, map_node_create(((y == 1 || y == 2) && ((x >= -3 && x <= -2) || (x >= +1 && x <= +3))) ? NODE_AIR : NODE_OAK_WOOD, NULL, 0), MGS_PLAYER, &changed_blocks);
239                 }
240         }
241
242         for (s32 y = 0; y <= 3; y++) {
243                 for (s32 z = -3; z <= +2; z++) {
244                         mapgen_set_node((v3s32) {-5, server_map.spawn_height + y, z}, map_node_create(NODE_OAK_WOOD, NULL, 0), MGS_PLAYER, &changed_blocks);
245                         mapgen_set_node((v3s32) {+5, server_map.spawn_height + y, z}, map_node_create(((y != 3) && (z == -1 || z == +0)) ? NODE_AIR : NODE_OAK_WOOD, NULL, 0), MGS_PLAYER, &changed_blocks);
246                 }
247         }
248
249         v2s32 posts[6] = {
250                 {-4, -3},
251                 {-4, +2},
252                 {+4, -3},
253                 {+4, +2},
254                 {+5, -1},
255                 {+5, +0},
256         };
257
258         for (int i = 0; i < 6; i++) {
259                 for (s32 y = server_map.spawn_height - 2;; y--) {
260                         v3s32 pos = {posts[i].x, y, posts[i].y};
261                         Node node = map_get_node(server_map.map, pos).type;
262
263                         if (i >= 4) {
264                                 if (node != NODE_AIR)
265                                         break;
266
267                                 pos.y++;
268                         }
269
270                         if (node_definitions[node].solid)
271                                 break;
272
273                         mapgen_set_node(pos, map_node_create(node == NODE_LAVA ? NODE_VULCANO_STONE : NODE_OAK_WOOD, NULL, 0), MGS_PLAYER, &changed_blocks);
274                 }
275         }
276
277         list_clear_func(&changed_blocks, &list_send_block, NULL);
278 }
279
280 // public functions
281
282 // ServerMap singleton constructor
283 void server_map_init(Server *srv)
284 {
285         server = srv;
286
287         server_map.map = map_create((MapCallbacks) {
288                 .create_block = &on_create_block,
289                 .delete_block = &on_delete_block,
290                 .get_block = &on_get_block,
291                 .set_node = &on_set_node,
292                 .after_set_node = &on_after_set_node,
293         });
294         server_map.joining_threads = false;
295         server_map.mapgen_threads = list_create(NULL);
296         pthread_mutex_init(&server_map.joining_threads_mtx, NULL);
297         pthread_mutex_init(&server_map.mapgen_threads_mtx, NULL);
298 }
299
300 // ServerMap singleton destructor
301 void server_map_deinit()
302 {
303         join_mapgen_threads();
304         pthread_mutex_destroy(&server_map.joining_threads_mtx);
305         pthread_mutex_destroy(&server_map.mapgen_threads_mtx);
306         map_delete(server_map.map);
307 }
308
309 // handle block request from client (thread safe)
310 void server_map_requested_block(Client *client, v3s32 pos)
311 {
312         if (within_simulation_distance(client->pos, pos, server->config.simulation_distance)) {
313                 MapBlock *block = map_get_block(server_map.map, pos, true);
314
315                 pthread_mutex_lock(&block->mtx);
316                 MapBlockExtraData *extra = block->extra;
317
318                 switch (extra->state) {
319                         case MBS_CREATED:
320                                 launch_mapgen_thread(block);
321                                 break;
322
323                         case MBS_GENERATING:
324                                 break;
325
326                         case MBS_READY:
327                                 send_block(client, block);
328                 };
329                 pthread_mutex_unlock(&block->mtx);
330         }
331 }
332
333 // prepare spawn region
334 void server_map_prepare_spawn()
335 {
336         s32 done = 0;
337         s32 dist = server->config.simulation_distance;
338         s32 total = (dist * 2 + 1);
339         total *= total * total;
340         s32 last_percentage = -1;
341
342         for (s32 x = -dist; x <= (s32) dist; x++) {
343                 for (s32 y = -dist; y <= (s32) dist; y++) {
344                         for (s32 z = -dist; z <= (s32) dist; z++) {
345                                 if (interrupted) {
346                                         join_mapgen_threads();
347                                         return;
348                                 }
349
350                                 MapBlock *block = map_get_block(server_map.map, (v3s32) {x, y, z}, true);
351
352                                 pthread_mutex_lock(&block->mtx);
353                                 if (((MapBlockExtraData *) block->extra)->state == MBS_CREATED)
354                                         launch_mapgen_thread(block);
355                                 pthread_mutex_unlock(&block->mtx);
356
357                                 done++;
358
359                                 s32 percentage = 100.0 * done / total;
360
361                                 if (percentage > last_percentage) {
362                                         last_percentage = percentage;
363                                         printf("Preparing spawn... %d%%\n", percentage);
364                                 }
365                         }
366                 }
367         }
368
369         join_mapgen_threads();
370
371         s64 saved_spawn_height;
372         if (database_load_meta("spawn_height", &saved_spawn_height)) {
373                 server_map.spawn_height = saved_spawn_height;
374         } else {
375                 s32 spawn_height = -1;
376
377                 while (map_get_node(server_map.map, (v3s32) {0, ++spawn_height, 0}).type != NODE_AIR);
378                         ;
379
380                 server_map.spawn_height = spawn_height + 5;
381                 generate_spawn_hut();
382                 database_save_meta("spawn_height", server_map.spawn_height);
383         }
384 }