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