client/client_node.c
client/client_player.c
client/cube.c
+ client/facecache.c
client/font.c
client/game.c
client/hud.c
add_executable(DragonblocksServer
${SOURCES_COMMON}
- server/facecache.c
server/mapdb.c
server/mapgen.c
server/perlin.c
(*nodeptr)->left = (*nodeptr)->right = NULL;
}
-static void free_recursive(BintreeNode *node, BintreeFreeFunction func)
+static void free_recursive(BintreeNode *node, BintreeFreeFunction func, void *arg)
{
if (node) {
+ free_recursive(node->left, func, arg);
+ free_recursive(node->right, func, arg);
free(node->key);
- free_recursive(node->left, func);
- free_recursive(node->right, func);
+ if (func)
+ func(node->value, arg);
free(node);
}
}
-void bintree_clear(Bintree *tree, BintreeFreeFunction func)
+void bintree_clear(Bintree *tree, BintreeFreeFunction func, void *arg)
{
if (tree) {
- free_recursive(tree->root, func);
+ free_recursive(tree->root, func, arg);
tree->root = NULL;
}
}
size_t key_size;
} Bintree;
-typedef void (*BintreeFreeFunction)(void *value);
+typedef void (*BintreeFreeFunction)(void *value, void *arg);
Bintree bintree_create(size_t key_size);
BintreeNode **bintree_search(Bintree *tree, void *key);
void bintree_add_node(Bintree *tree, BintreeNode **nodeptr, void *key, void *value);
-void bintree_clear(Bintree *tree, BintreeFreeFunction func);
+void bintree_clear(Bintree *tree, BintreeFreeFunction func, void *arg);
#endif
#include "client/blockmesh.h"
+#include "client/client_map.h"
#include "client/client_node.h"
#include "client/cube.h"
{+0, +1, +0},
};
-static void make_vertices(Object *object, MapBlock *block, Map *map)
+static void make_vertices(Object *object, MapBlock *block)
{
ITERATE_MAPBLOCK {
MapNode *node = &block->data[x][y][z];
if (node_definitions[node->type].visible) {
- v3f offset = {x + 8.0f, y + 8.0f, z + 8.0f};
+ v3f32 offset = {x + (f32) MAPBLOCK_SIZE / 2.0f, y + (f32) MAPBLOCK_SIZE / 2.0f, z + (f32) MAPBLOCK_SIZE / 2.0f};
ClientNodeDefintion *client_def = &client_node_definitions[node->type];
object_set_texture(object, client_def->texture);
Node neighbor;
- if (npos.x >= 0 && npos.x < 16 && npos.y >= 0 && npos.y < 16 && npos.z >= 0 && npos.z < 16)
+ if (npos.x >= 0 && npos.x < MAPBLOCK_SIZE && npos.y >= 0 && npos.y < MAPBLOCK_SIZE && npos.z >= 0 && npos.z < MAPBLOCK_SIZE)
neighbor = block->data[npos.x][npos.y][npos.z].type;
- else
- neighbor = map_get_node(map, (v3s32) {npos.x + block->pos.x * 16, npos.y + block->pos.y * 16, npos.z + block->pos.z * 16}).type;
+ else {
+ MapNode nn = map_get_node(client_map.map, (v3s32) {npos.x + block->pos.x * MAPBLOCK_SIZE, npos.y + block->pos.y * MAPBLOCK_SIZE, npos.z + block->pos.z * MAPBLOCK_SIZE});
+ neighbor = nn.type;
+ }
if (neighbor != NODE_UNLOADED && ! node_definitions[neighbor].visible) {
for (int v = 0; v < 6; v++) {
}
}
-void blockmesh_make(MapBlock *block, Map *map)
+void blockmesh_make(MapBlock *block)
{
Object *obj = object_create();
- obj->pos = (v3f) {block->pos.x * 16.0f - 8.0f, block->pos.y * 16.0f - 8.0f, block->pos.z * 16.0f - 8.0f};
+ obj->pos = (v3f32) {block->pos.x * (f32) MAPBLOCK_SIZE - (f32) MAPBLOCK_SIZE / 2.0f, block->pos.y * (f32) MAPBLOCK_SIZE - (f32) MAPBLOCK_SIZE / 2.0f, block->pos.z * (f32) MAPBLOCK_SIZE - (f32) MAPBLOCK_SIZE / 2.0};
- make_vertices(obj, block, map);
+ make_vertices(obj, block);
if (! object_add_to_scene(obj)) {
object_delete(obj);
obj = NULL;
}
- if (block->extra)
- ((Object *) block->extra)->remove = true;
+ MapBlockExtraData *extra = block->extra;
+
+ if (extra->obj)
+ extra->obj->remove = true;
- block->extra = obj;
+ extra->obj = obj;
}
#ifndef _BLOCKMESH_H_
#define _BLOCKMESH_H_
-#include "client/scene.h"
#include "map.h"
-void blockmesh_make(MapBlock *block, Map *map);
+void blockmesh_make(MapBlock *block);
#endif
glUniformMatrix4fv(loc_view, 1, GL_FALSE, camera.view[0]);
}
-void camera_set_position(v3f pos)
+void camera_set_position(v3f32 pos)
{
camera.eye[0] = pos.x;
camera.eye[1] = pos.y;
#include <linmath.h/linmath.h>
#include "types.h"
-void camera_set_position(v3f pos);
+void camera_set_position(v3f32 pos);
void camera_set_angle(f32 yaw, f32 pitch);
void camera_on_resize(int width, int height);
void camera_enable(GLint loc_view);
#include "signal_handlers.h"
#include "util.h"
-Client client;
+static Client client;
void client_disconnect(bool send, const char *detail)
{
pthread_mutex_unlock(&client.mtx);
}
-void client_send_position(v3f pos)
+void client_send_position(v3f64 pos)
{
pthread_mutex_lock(&client.mtx);
- (void) (write_u32(client.fd, SC_POS) && write_v3f32(client.fd, pos));
+ (void) (write_u32(client.fd, SC_POS) && write_v3f64(client.fd, pos));
pthread_mutex_unlock(&client.mtx);
}
#include "network.c"
-static void *reciever_thread(__attribute__((unused)) void *unused)
+static void *reciever_thread(__attribute__((unused)) void *arg)
{
handle_packets(&client);
pthread_mutex_init(&client.mtx, NULL);
client.state = CS_CREATED;
client.name = NULL;
- client.map = map_create();
- client_player_init(client.map);
- client_map_init(client.map);
+ client_map_init(&client);
+ client_player_init();
pthread_t recv_thread;
pthread_create(&recv_thread, NULL, &reciever_thread, NULL);
if (client.name)
free(client.name);
- client_map_deinit();
+ pthread_join(recv_thread, NULL);
- map_delete(client.map);
+ client_player_deinit();
+ client_map_deinit();
pthread_mutex_destroy(&client.mtx);
}
#include "client/client_commands.h"
#include "client/scene.h"
#include "server/server_commands.h"
-#include "map.h"
#include "network.h"
#include "types.h"
pthread_mutex_t mtx;
ClientState state;
char *name;
- Map *map;
} Client;
void client_disconnect(bool send, const char *detail);
-void client_send_position(v3f pos);
+void client_send_position(v3f64 pos);
#endif
if (! read_v3s32(client->fd, &pos))
return false;
- MapBlockHeader header;
+ size_t size;
- if (! read_u32(client->fd, &header))
+ if (! read_u64(client->fd, &size))
return false;
- char data[header];
- if (! read_full(client->fd, data, header))
+ if (size > sizeof(MapBlockData)) // guard to prevent malicious or malfunctioning packets from allocating huge unnecessary amounts of memory
+ return false;
+
+ char data[size];
+ if (! read_full(client->fd, data, size))
return false;
MapBlock *block;
if (good)
- block = map_get_block(client->map, pos, true);
+ block = map_get_block(client_map.map, pos, true);
else
block = map_allocate_block(pos);
- if (block->state != MBS_CREATED)
- map_clear_meta(block);
+ map_clear_meta(block);
- bool ret = map_deserialize_block(block, data, header);
+ bool ret = map_deserialize_block(block, data, size);
if (good)
- client_map_block_changed(block);
+ client_map_block_received(block);
else
map_free_block(block);
return ret;
}
+static bool simulation_distance_handler(Client *client, bool good)
+{
+ u32 simulation_distance;
+
+ if (! read_u32(client->fd, &simulation_distance))
+ return false;
+
+ if (good)
+ client_map_set_simulation_distance(simulation_distance);
+
+ return true;
+}
+
CommandHandler command_handlers[CLIENT_COMMAND_COUNT] = {
{0},
{&disconnect_handler, "DISCONNECT", CS_CREATED | CS_AUTH | CS_ACTIVE},
{&auth_handler, "AUTH", CS_AUTH},
{&block_handler, "BLOCK", CS_ACTIVE},
+ {&simulation_distance_handler, "SIMULATION_DISTANCE", CS_ACTIVE},
};
CC_DISCONNECT,
CC_AUTH,
CC_BLOCK,
+ CC_SIMULATION_DISTANCE,
CLIENT_COMMAND_COUNT
} ClientCommand;
+#include <stdio.h>
#include <stdlib.h>
-#include <pthread.h>
#include "client/blockmesh.h"
+#include "client/facecache.h"
#include "client/client_map.h"
-#include "queue.h"
+#include "client/client_player.h"
+#include "util.h"
+#define MAX_BLOCK_REQUESTS 4
-static struct
+struct ClientMap client_map;
+Client *client;
+
+// meshgen functions
+
+// dequeue callback to thread-safely update
+static void set_dequeued(void *arg)
+{
+ MapBlock *block = arg;
+
+ pthread_mutex_lock(&block->mtx);
+ ((MapBlockExtraData *) block->extra)->queue = false;
+ pthread_mutex_unlock(&block->mtx);
+}
+
+// mesh generator step
+static void meshgen_step()
+{
+ MapBlock *block;
+
+ if ((block = queue_dequeue_callback(client_map.queue, &set_dequeued)))
+ blockmesh_make(block);
+ else
+ sched_yield();
+}
+
+// pthread start routine for meshgen thread
+static void *meshgen_thread(__attribute__((unused)) void *arg)
{
- Map *map;
- Queue *queue;
- pthread_t thread;
- bool cancel;
-} client_map;
+ while (! client_map.cancel)
+ meshgen_step();
+
+ return NULL;
+}
+
+// enqueue mesh to block update queue
+static void schedule_update_block_mesh(MapBlock *block)
+{
+ if (! block)
+ return;
-static void set_block_ready(void *block)
+ pthread_mutex_lock(&block->mtx);
+ MapBlockExtraData *extra = block->extra;
+ if (! extra->queue) {
+ extra->queue = true;
+ queue_enqueue(client_map.queue, block);
+ }
+ pthread_mutex_unlock(&block->mtx);
+}
+
+// sync functions
+
+// send block request command to server
+static void request_position(v3s32 pos)
{
- ((MapBlock *) block)->state = MBS_READY;
+ pthread_mutex_lock(&client->mtx);
+ (void) (write_u32(client->fd, SC_REQUEST_BLOCK) && write_v3s32(client->fd, pos));
+ pthread_mutex_unlock(&client->mtx);
}
-static void *meshgen_thread(__attribute__((unused)) void *unused)
+// mapblock synchronisation step
+static void sync_step()
{
- while (! client_map.cancel) {
- MapBlock *block;
- if ((block = queue_dequeue_callback(client_map.queue, &set_block_ready)))
- blockmesh_make(block, client_map.map);
- else
- sched_yield();
+ static u64 tick = 0;
+ static v3s32 *old_requested_positions = NULL;
+ static size_t old_requested_positions_count = 0;
+
+ u64 last_tick = tick++;
+
+ v3f64 player_pos = client_player_get_position();
+ v3s32 center = map_node_to_block_pos((v3s32) {player_pos.x, player_pos.y, player_pos.z}, NULL);
+
+ v3s32 *requested_positions = malloc(sizeof(v3s32) * MAX_BLOCK_REQUESTS);
+ size_t requested_positions_count = 0;
+
+ for (size_t i = 0; i < client_map.blocks_count; i++) {
+ v3s32 pos = facecache_face(i, ¢er);
+ MapBlock *block = map_get_block(client_map.map, pos, false);
+
+ if (block) {
+ pthread_mutex_lock(&block->mtx);
+ MapBlockExtraData *extra = block->extra;
+
+ switch (extra->state) {
+ case MBS_READY:
+ if (extra->last_synced < last_tick)
+ request_position(pos);
+ fallthrough;
+
+ case MBS_FRESH:
+ extra->state = MBS_READY;
+ extra->last_synced = tick;
+ break;
+
+ case MBS_RECIEVING:
+ break;
+ }
+ pthread_mutex_unlock(&block->mtx);
+ } else if (requested_positions_count < MAX_BLOCK_REQUESTS) {
+ bool should_request = true;
+
+ for (size_t i = 0; i < old_requested_positions_count; i++) {
+ if (v3s32_equals(old_requested_positions[i], pos)) {
+ should_request = false;
+ break;
+ }
+ }
+
+ if (should_request)
+ request_position(pos);
+
+ requested_positions[requested_positions_count++] = pos;
+ }
}
+ if (old_requested_positions)
+ free(old_requested_positions);
+
+ old_requested_positions = requested_positions;
+ old_requested_positions_count = requested_positions_count;
+}
+
+// pthread start routine for sync thread
+static void *sync_thread(__attribute__((unused)) void *arg)
+{
+ while (! client_map.cancel)
+ sync_step();
+
return NULL;
}
-void client_map_init(Map *map)
+// map callbacks
+// note: all these functions require the block mutex to be locked, which is always the case when a map callback is invoked
+
+// callback for initializing a newly created block
+// allocate and initialize extra data
+static void on_create_block(MapBlock *block)
{
- client_map.map = map;
- client_map.queue = queue_create();
+ MapBlockExtraData *extra = block->extra = malloc(sizeof(MapBlockExtraData));
+
+ extra->state = MBS_RECIEVING;
+ extra->queue = false;
+ extra->last_synced = 0;
+ extra->obj = NULL;
}
-void client_map_start_meshgen()
+// callback for deleting a block
+// free extra data
+static void on_delete_block(MapBlock *block)
{
- pthread_create(&client_map.thread, NULL, &meshgen_thread, NULL);
+ free(block->extra);
+}
+
+// callback for determining whether a block should be returned by map_get_block
+// hold back blocks that have not been fully read from server yet when the create flag is set to true
+static bool on_get_block(MapBlock *block, bool create)
+{
+ return create || ((MapBlockExtraData *) block->extra)->state > MBS_RECIEVING;
+}
+
+// public functions
+
+// ClientMap singleton constructor
+void client_map_init(Client *cli)
+{
+ client = cli;
+
+ client_map.map = map_create((MapCallbacks) {
+ .create_block = &on_create_block,
+ .delete_block = &on_delete_block,
+ .get_block = &on_get_block,
+ .set_node = NULL,
+ .after_set_node = NULL,
+ });
+ client_map.queue = queue_create();
+ client_map.cancel = false;
+ client_map.meshgen_thread = client_map.sync_thread = 0;
+ client_map_set_simulation_distance(16);
}
+// ClientMap singleton destructor
void client_map_deinit()
{
- client_map.cancel = true;
queue_delete(client_map.queue);
- if (client_map.thread)
- pthread_join(client_map.thread, NULL);
+ map_delete(client_map.map);
}
-static void schedule_update_block(MapBlock *block)
+// start meshgen and sync threads
+void client_map_start()
{
- if (! block)
- return;
+ pthread_create(&client_map.meshgen_thread, NULL, &meshgen_thread, NULL);
+ pthread_create(&client_map.sync_thread, NULL, &sync_thread, NULL);
+}
- pthread_mutex_lock(&block->mtx);
- if (block->state != MBS_PROCESSING) {
- block->state = MBS_PROCESSING;
- queue_enqueue(client_map.queue, block);
- }
- pthread_mutex_unlock(&block->mtx);
+// stop meshgen and sync threads
+void client_map_stop()
+{
+ client_map.cancel = true;
+
+ if (client_map.meshgen_thread)
+ pthread_join(client_map.meshgen_thread, NULL);
+
+ if (client_map.sync_thread)
+ pthread_join(client_map.sync_thread, NULL);
}
-void client_map_block_changed(MapBlock *block)
+// update simulation distance
+void client_map_set_simulation_distance(u32 simulation_distance)
{
- schedule_update_block(block);
+ client_map.simulation_distance = simulation_distance;
+ client_map.blocks_count = facecache_count(simulation_distance);
+}
+
+// called when a block was actually recieved from server
+void client_map_block_received(MapBlock *block)
+{
+ pthread_mutex_lock(&block->mtx);
+ MapBlockExtraData *extra = block->extra;
+ if (extra->state == MBS_RECIEVING)
+ extra->state = MBS_FRESH;
+ pthread_mutex_unlock(&block->mtx);
+
+ schedule_update_block_mesh(block);
- schedule_update_block(map_get_block(client_map.map, (v3s32) {block->pos.x + 1, block->pos.y + 0, block->pos.z + 0}, false));
- schedule_update_block(map_get_block(client_map.map, (v3s32) {block->pos.x + 0, block->pos.y + 1, block->pos.z + 0}, false));
- schedule_update_block(map_get_block(client_map.map, (v3s32) {block->pos.x + 0, block->pos.y + 0, block->pos.z + 1}, false));
- schedule_update_block(map_get_block(client_map.map, (v3s32) {block->pos.x - 1, block->pos.y - 0, block->pos.z - 0}, false));
- schedule_update_block(map_get_block(client_map.map, (v3s32) {block->pos.x - 0, block->pos.y - 1, block->pos.z - 0}, false));
- schedule_update_block(map_get_block(client_map.map, (v3s32) {block->pos.x - 0, block->pos.y - 0, block->pos.z - 1}, false));
+ schedule_update_block_mesh(map_get_block(client_map.map, (v3s32) {block->pos.x + 1, block->pos.y + 0, block->pos.z + 0}, false));
+ schedule_update_block_mesh(map_get_block(client_map.map, (v3s32) {block->pos.x + 0, block->pos.y + 1, block->pos.z + 0}, false));
+ schedule_update_block_mesh(map_get_block(client_map.map, (v3s32) {block->pos.x + 0, block->pos.y + 0, block->pos.z + 1}, false));
+ schedule_update_block_mesh(map_get_block(client_map.map, (v3s32) {block->pos.x - 1, block->pos.y - 0, block->pos.z - 0}, false));
+ schedule_update_block_mesh(map_get_block(client_map.map, (v3s32) {block->pos.x - 0, block->pos.y - 1, block->pos.z - 0}, false));
+ schedule_update_block_mesh(map_get_block(client_map.map, (v3s32) {block->pos.x - 0, block->pos.y - 0, block->pos.z - 1}, false));
}
#ifndef _CLIENT_MAP_H_
#define _CLIENT_MAP_H_
+#include <stdbool.h>
+#include <pthread.h>
#include "map.h"
+#include "queue.h"
+#include "client/object.h"
-void client_map_init(Map *map);
-void client_map_start_meshgen();
-void client_map_deinit();
-void client_map_block_changed(MapBlock *block);
+typedef enum
+{
+ MBS_RECIEVING, // currently deserializing
+ MBS_FRESH, // first deserialisation finished, not processed by sync thread yet
+ MBS_READY, // ready to use and processed by sync thread
+} MapBlockState;
+
+typedef struct
+{
+ MapBlockState state; // keep track of the deserialisation and sync processing state
+ bool queue; // whether the block is in meshgen queue
+ u64 last_synced; // keep track of when a block was synced the last time (used to detect when a block got out of and then back into range)
+ Object *obj; // mesh object, generated by blockmesh file
+} MapBlockExtraData;
+
+extern struct ClientMap
+{
+ Map *map; // map object
+ Queue *queue; // MapBlock * queue (thread safe)
+ bool cancel; // used to notify meshgen and sync thread about quit
+ pthread_t meshgen_thread; // consumer thread for meshgen queue
+ pthread_t sync_thread; // this thread requests new / changed blocks from server
+ u32 simulation_distance; // simulation distance sent by server
+ size_t blocks_count; // cached number of facecache positions to process every sync step (matches simulation distance)
+} client_map;
+
+void client_map_init(); // ClientMap singleton constructor
+void client_map_deinit(); // ClientMap singleton destructor
+void client_map_set_simulation_distance(u32 simulation_distance); // update simulation distance
+void client_map_start(); // start meshgen and sync threads
+void client_map_stop(); // stop meshgen and sync threads
+void client_map_block_received(MapBlock *block); // called when a block was actually recieved from server
#endif
#include <stdio.h>
#include "client/camera.h"
#include "client/client.h"
+#include "client/client_map.h"
#include "client/client_player.h"
#include "client/cube.h"
#include "client/texture.h"
struct ClientPlayer client_player;
+// to be called whenever the player position changes
+// rwlock has to be read or write locked
static void update_pos()
{
- camera_set_position((v3f) {client_player.pos.x, client_player.pos.y + client_player.eye_height, client_player.pos.z});
+ camera_set_position((v3f32) {client_player.pos.x, client_player.pos.y + client_player.eye_height, client_player.pos.z});
client_send_position(client_player.pos);
- client_player.obj->pos = client_player.pos;
+ client_player.obj->pos = (v3f32) {client_player.pos.x, client_player.pos.y, client_player.pos.z};
object_transform(client_player.obj);
char pos_text[BUFSIZ];
hud_change_text(client_player.pos_display, pos_text);
}
-void client_player_init(Map *map)
+// get absolute player bounding box
+// rwlock has to be read- or write locked
+static aabb3f64 get_box()
{
- client_player.map = map;
- client_player.pos = (v3f) {0.0f, 200.0f, 0.0f};
- client_player.velocity = (v3f) {0.0f, 0.0f, 0.0f};
- client_player.box = (aabb3f) {{-0.3f, 0.0f, -0.3f}, {0.3f, 1.75f, 0.3f}};
+ return (aabb3f64) {
+ {client_player.box.min.x + client_player.pos.x, client_player.box.min.y + client_player.pos.y, client_player.box.min.z + client_player.pos.z},
+ {client_player.box.max.x + client_player.pos.x, client_player.box.max.y + client_player.pos.y, client_player.box.max.z + client_player.pos.z},
+ };
+}
+
+// get absolute integer box that contains all nodes a float bounding box touches
+static aabb3s32 round_box(aabb3f64 box)
+{
+ return (aabb3s32) {
+ {floor(box.min.x + 0.5), floor(box.min.y + 0.5), floor(box.min.z + 0.5)},
+ {ceil(box.max.x - 0.5), ceil(box.max.y - 0.5), ceil(box.max.z - 0.5)},
+ };
+}
+
+// return true if node at x, y, z is solid (or unloaded)
+static bool is_solid(s32 x, s32 y, s32 z)
+{
+ Node node = map_get_node(client_map.map, (v3s32) {x, y, z}).type;
+ return node == NODE_UNLOADED || node_definitions[node].solid;
+}
+
+// determine if player can jump currently (must be standing on a solid block)
+// rwlock has to be read- or write locked
+static bool can_jump()
+{
+ aabb3f64 fbox = get_box();
+ fbox.min.y -= 0.5;
+
+ aabb3s32 box = round_box(fbox);
+
+ if (fbox.min.y - (f64) box.min.y > 0.01)
+ return false;
+
+ for (s32 x = box.min.x; x <= box.max.x; x++)
+ for (s32 z = box.min.z; z <= box.max.z; z++)
+ if (is_solid(x, box.min.y, z))
+ return true;
+
+ return false;
+}
+
+// ClientPlayer singleton constructor
+void client_player_init()
+{
+ client_player.pos = (v3f64) {0.0, 200.0, 0.0};
+ client_player.velocity = (v3f64) {0.0, 0.0, 0.0};
+ client_player.box = (aabb3f64) {{-0.3, 0.0, -0.3}, {0.3, 1.75, 0.3}};
client_player.yaw = client_player.pitch = 0.0f;
- client_player.eye_height = 1.5f;
+ client_player.eye_height = 1.5;
+ pthread_rwlock_init(&client_player.rwlock, NULL);
+}
+
+// ClientPlayer singleton destructor
+void client_player_deinit()
+{
+ pthread_rwlock_destroy(&client_player.rwlock);
}
+// create mesh object and hud display
void client_player_add_to_scene()
{
client_player.obj = object_create();
- client_player.obj->scale = (v3f) {0.6f, 1.75f, 0.6f};
+ client_player.obj->scale = (v3f32) {0.6, 1.75, 0.6};
client_player.obj->visible = false;
object_set_texture(client_player.obj, texture_get(RESSOURCEPATH "textures/player.png"));
},
});
+ pthread_rwlock_rdlock(&client_player.rwlock);
update_pos();
+ pthread_rwlock_unlock(&client_player.rwlock);
}
-static aabb3f get_box()
-{
- return (aabb3f) {
- {client_player.box.min.x + client_player.pos.x, client_player.box.min.y + client_player.pos.y, client_player.box.min.z + client_player.pos.z},
- {client_player.box.max.x + client_player.pos.x, client_player.box.max.y + client_player.pos.y, client_player.box.max.z + client_player.pos.z},
- };
-}
-
-static aabb3s32 round_box(aabb3f box)
+// jump if possible
+void client_player_jump()
{
- return (aabb3s32) {
- {floor(box.min.x + 0.5f), floor(box.min.y + 0.5f), floor(box.min.z + 0.5f)},
- {ceil(box.max.x - 0.5f), ceil(box.max.y - 0.5f), ceil(box.max.z - 0.5f)},
- };
+ pthread_rwlock_wrlock(&client_player.rwlock);
+ if (can_jump())
+ client_player.velocity.y += 10.0;
+ pthread_rwlock_unlock(&client_player.rwlock);
}
-static bool is_solid(s32 x, s32 y, s32 z)
+// get position (thread-safe)
+v3f64 client_player_get_position()
{
- Node node = map_get_node(client_player.map, (v3s32) {x, y, z}).type;
- return node == NODE_UNLOADED || node_definitions[node].solid;
-}
+ v3f64 pos;
-static bool can_jump()
-{
- aabb3f fbox = get_box();
- fbox.min.y -= 0.5f;
+ pthread_rwlock_rdlock(&client_player.rwlock);
+ pos = client_player.pos;
+ pthread_rwlock_unlock(&client_player.rwlock);
- aabb3s32 box = round_box(fbox);
-
- if (fbox.min.y - (f32) box.min.y > 0.01f)
- return false;
-
- for (s32 x = box.min.x; x <= box.max.x; x++)
- for (s32 z = box.min.z; z <= box.max.z; z++)
- if (is_solid(x, box.min.y, z))
- return true;
-
- return false;
-}
-
-void client_player_jump()
-{
- if (can_jump())
- client_player.velocity.y += 10.0f;
+ return pos;
}
+// to be called every frame
void client_player_tick(f64 dtime)
{
- v3f old_pos = client_player.pos;
- v3f old_velocity = client_player.velocity;
+ pthread_rwlock_wrlock(&client_player.rwlock);
- client_player.velocity.y -= 32.0f * dtime;
+ v3f64 old_pos = client_player.pos;
+ v3f64 old_velocity = client_player.velocity;
+
+ client_player.velocity.y -= 32.0 * dtime;
#define GETS(vec, comp) *(s32 *) ((char *) &vec + offsetof(v3s32, comp))
-#define GETF(vec, comp) *(f32 *) ((char *) &vec + offsetof(v3f32, comp))
+#define GETF(vec, comp) *(f64 *) ((char *) &vec + offsetof(v3f64, comp))
#define PHYSICS(a, b, c) { \
- f32 v = (GETF(client_player.velocity, a) + GETF(old_velocity, a)) / 2.0f; \
- if (v == 0.0f) \
+ f64 v = (GETF(client_player.velocity, a) + GETF(old_velocity, a)) / 2.0f; \
+ if (v == 0.0) \
goto a ## _physics_done; \
aabb3s32 box = round_box(get_box()); \
- v3f old_pos = client_player.pos; \
+ v3f64 old_pos = client_player.pos; \
GETF(client_player.pos, a) += v * dtime; \
s32 dir; \
- f32 offset; \
- if (v > 0.0f) { \
+ f64 offset; \
+ if (v > 0.0) { \
dir = +1; \
offset = GETF(client_player.box.max, a); \
- GETS(box.min, a) = ceil(GETF(old_pos, a) + offset + 0.5f); \
- GETS(box.max, a) = floor(GETF(client_player.pos, a) + offset + 0.5f); \
+ GETS(box.min, a) = ceil(GETF(old_pos, a) + offset + 0.5); \
+ GETS(box.max, a) = floor(GETF(client_player.pos, a) + offset + 0.5); \
} else { \
dir = -1; \
offset = GETF(client_player.box.min, a); \
- GETS(box.min, a) = floor(GETF(old_pos, a) + offset - 0.5f); \
- GETS(box.max, a) = ceil(GETF(client_player.pos, a) + offset - 0.5f); \
+ GETS(box.min, a) = floor(GETF(old_pos, a) + offset - 0.5); \
+ GETS(box.max, a) = ceil(GETF(client_player.pos, a) + offset - 0.5); \
} \
GETS(box.max, a) += dir; \
for (s32 a = GETS(box.min, a); a != GETS(box.max, a); a += dir) { \
for (s32 b = GETS(box.min, b); b <= GETS(box.max, b); b++) { \
for (s32 c = GETS(box.min, c); c <= GETS(box.max, c); c++) { \
if (is_solid(x, y, z)) { \
- GETF(client_player.pos, a) = (f32) a - offset - 0.5f * (f32) dir; \
- GETF(client_player.velocity, a) = 0.0f; \
+ GETF(client_player.pos, a) = (f64) a - offset - 0.5 * (f64) dir; \
+ GETF(client_player.velocity, a) = 0.0; \
goto a ## _physics_done; \
} \
} \
#undef GETF
#undef PHYSICS
- if (old_pos.x != client_player.pos.x || old_pos.y != client_player.pos.y || old_pos.z != client_player.pos.z)
+ if (! v3f64_equals(old_pos, client_player.pos))
update_pos();
+
+ pthread_rwlock_unlock(&client_player.rwlock);
}
#ifndef _CLIENT_PLAYER_H_
#define _CLIENT_PLAYER_H_
+#include <pthread.h>
#include "client/client.h"
#include "client/hud.h"
#include "client/object.h"
extern struct ClientPlayer
{
- v3f pos;
- v3f velocity;
- aabb3f box;
- f32 yaw, pitch;
- f32 eye_height;
- Object *obj;
- Map *map;
- HUDElement *pos_display;
+ v3f64 pos; // feet position
+ v3f64 velocity; // current velocity
+ aabb3f64 box; // axis-aligned bounding box (used for collision), with 0, 0, 0 being the feet position
+ f32 yaw, pitch; // look direction
+ f64 eye_height; // eye height above feet
+ pthread_rwlock_t rwlock; // used to protect the above properties
+ Object *obj; // 3D mesh object (currently always invisible), not thread safe
+ HUDElement *pos_display; // display position on HUD, not thread safe
} client_player;
-void client_player_init(Map *map);
-void client_player_add_to_scene();
-void client_player_jump();
-void client_player_tick(f64 dtime);
+void client_player_init(); // ClientPlayer singleton constructor
+void client_player_deinit(); // ClientPlayer singleton destructor
+void client_player_add_to_scene(); // create mesh object and hud display
+void client_player_jump(); // jump if possible
+v3f64 client_player_get_position(); // get position (thread-safe)
+void client_player_tick(f64 dtime); // to be called every frame
#endif
--- /dev/null
+#include <stdlib.h>
+#include "client/facecache.h"
+#include "array.h"
+
+static struct
+{
+ Array positions;
+ u32 size;
+ pthread_mutex_t mtx;
+} facecache;
+
+__attribute((constructor)) static void face_cache_init()
+{
+ facecache.size = 0;
+ facecache.positions = array_create(sizeof(v3s32));
+ v3s32 pos = {0, 0, 0};
+ array_append(&facecache.positions, &pos);
+ pthread_mutex_init(&facecache.mtx, NULL);
+}
+
+__attribute((destructor)) void face_cache_deinit()
+{
+ if (facecache.positions.ptr)
+ free(facecache.positions.ptr);
+ pthread_mutex_destroy(&facecache.mtx);
+}
+
+static void face_cache_calculate(s32 size)
+{
+#define ADDPOS(a, b, c, va, vb, vc) \
+ { \
+ v3s32 pos; \
+ *(s32 *) ((char *) &pos + offsetof(v3s32, a)) = va; \
+ *(s32 *) ((char *) &pos + offsetof(v3s32, b)) = vb; \
+ *(s32 *) ((char *) &pos + offsetof(v3s32, c)) = vc; \
+ array_append(&facecache.positions, &pos); \
+ }
+#define SQUARES(a, b, c) \
+ for (s32 va = -size + 1; va < size; va++) { \
+ for (s32 vb = -size + 1; vb < size; vb++) { \
+ ADDPOS(a, b, c, va, vb, size) \
+ ADDPOS(a, b, c, va, vb, -size) \
+ } \
+ }
+ SQUARES(x, z, y)
+ SQUARES(x, y, z)
+ SQUARES(z, y, x)
+#undef SQUARES
+#define EDGES(a, b, c) \
+ for (s32 va = -size + 1; va < size; va++) { \
+ ADDPOS(a, b, c, va, size, size) \
+ ADDPOS(a, b, c, va, size, -size) \
+ ADDPOS(a, b, c, va, -size, size) \
+ ADDPOS(a, b, c, va, -size, -size) \
+ }
+ EDGES(x, y, z)
+ EDGES(z, x, y)
+ EDGES(y, x, z)
+#undef EDGES
+ ADDPOS(x, y, z, size, size, size)
+ ADDPOS(x, y, z, size, size, -size)
+ ADDPOS(x, y, z, size, -size, size)
+ ADDPOS(x, y, z, size, -size, -size)
+ ADDPOS(x, y, z, -size, size, size)
+ ADDPOS(x, y, z, -size, size, -size)
+ ADDPOS(x, y, z, -size, -size, size)
+ ADDPOS(x, y, z, -size, -size, -size)
+#undef ADDPOS
+}
+
+v3s32 facecache_face(size_t i, v3s32 *base)
+{
+ pthread_mutex_lock(&facecache.mtx);
+ while (facecache.positions.siz <= i)
+ face_cache_calculate(++facecache.size);
+ v3s32 pos = ((v3s32 *) facecache.positions.ptr)[i];
+ pthread_mutex_unlock(&facecache.mtx);
+ if (base) {
+ pos.x += base->x;
+ pos.y += base->y;
+ pos.z += base->z;
+ }
+ return pos;
+}
+
+size_t facecache_count(u32 size)
+{
+ size_t len = 1 + size * 2;
+ return len * len * len;
+}
--- /dev/null
+#ifndef _FACECACHE_H_
+#define _FACECACHE_H_
+
+#include <pthread.h>
+#include "types.h"
+
+v3s32 facecache_face(size_t i, v3s32 *base);
+size_t facecache_count(u32 size);
+
+#endif
scene_render();
hud_render();
- // font_render();
glfwSwapBuffers(window.handle);
glfwPollEvents();
scene_on_resize(width, height);
client_node_init();
- client_map_start_meshgen();
+ client_map_start();
- camera_set_position((v3f) {0.0f, 0.0f, 0.0f});
+ camera_set_position((v3f32) {0.0f, 0.0f, 0.0f});
camera_set_angle(0.0f, 0.0f);
hud_init();
.offset = {2, 2},
.type_def = {
.text = {
- .text = "Dragonblocks Alpha",
+ .text = "Dragonblocks Alpha", // ToDo: add version
.color = {1.0f, 1.0f, 1.0f},
},
},
game_loop(client);
+ client_map_stop();
+
font_deinit();
hud_deinit();
scene_deinit();
static void element_transform(HUDElement *element)
{
- v3f pos = {
+ v3f32 pos = {
(f32) element->def.offset.x + (1.0f + element->def.pos.x) / 2.0f * (f32) hud.width,
(f32) element->def.offset.y + (1.0f + element->def.pos.y) / 2.0f * (f32) hud.height,
element->def.pos.z,
mat4x4_translate(element->transform, pos.x, pos.y, pos.z);
if (element->def.type == HUD_IMAGE) {
- v2f scale = element->def.type_def.image.scale;
+ v2f32 scale = element->def.type_def.image.scale;
switch (element->def.type_def.image.scale_type) {
case HUD_SCALE_TEXTURE:
#include <linmath.h/linmath.h>
#include "client/font.h"
#include "client/texture.h"
+#include "list.h"
#include "types.h"
typedef enum
typedef struct
{
HUDElementType type;
- v3f pos;
+ v3f32 pos;
v2s32 offset;
union
{
struct {
Texture *texture;
- v2f scale;
+ v2f32 scale;
HUDImageScaleType scale_type;
} image;
struct {
char *text;
- v3f color;
+ v3f32 color;
} text;
} type_def;
} HUDElementDefinition;
static bool move(int forward, int backward, vec3 dir)
{
- f32 sign;
- f32 speed = 4.317f;
+ f64 sign;
+ f64 speed = 4.317f;
if (glfwGetKey(window.handle, forward) == GLFW_PRESS)
sign = +1.0f;
Object *object_create()
{
Object *obj = malloc(sizeof(Object));
- obj->pos = (v3f) {0.0f, 0.0f, 0.0f};
- obj->rot = (v3f) {0.0f, 0.0f, 0.0f};
- obj->scale = (v3f) {1.0f, 1.0f, 1.0f};
+ obj->pos = (v3f32) {0.0f, 0.0f, 0.0f};
+ obj->rot = (v3f32) {0.0f, 0.0f, 0.0f};
+ obj->scale = (v3f32) {1.0f, 1.0f, 1.0f};
obj->angle = 0.0f;
obj->remove = false;
obj->meshes = NULL;
typedef struct
{
- v3f pos, rot, scale;
+ v3f32 pos, rot, scale;
f32 angle;
bool remove;
Mesh **meshes;
return true;
}
-static void list_delete_object(void *key, __attribute__((unused)) void *value, __attribute__((unused)) void *unused)
+static void list_delete_object(void *key, __attribute__((unused)) void *value, __attribute__((unused)) void *arg)
{
object_delete(key);
}
textures = list_create(&list_compare_string);
}
-static void list_delete_texture(__attribute__((unused)) void *key, void *value, __attribute__((unused)) void *unused)
+static void list_delete_texture(__attribute__((unused)) void *key, void *value, __attribute__((unused)) void *arg)
{
texture_delete(value);
}
#include <unistd.h>
#include <math.h>
#include <endian.h>
-#include <zlib.h>
+#include <string.h>
#include "map.h"
#include "util.h"
-Map *map_create()
+Map *map_create(MapCallbacks callbacks)
{
Map *map = malloc(sizeof(Map));
pthread_rwlock_init(&map->rwlck, NULL);
pthread_rwlock_init(&map->cached_rwlck, NULL);
map->sectors = bintree_create(sizeof(v2s32));
map->cached = NULL;
+ map->callbacks = callbacks;
return map;
}
-static void free_block(void *block)
+static void free_block(void *value, void *arg)
{
- map_free_block(block);
+ Map *map = arg;
+
+ if (map->callbacks.delete_block)
+ map->callbacks.delete_block(value);
+
+ map_free_block(value);
}
-static void free_sector(void *sector_void)
+static void free_sector(void *value, void *arg)
{
- MapSector *sector = sector_void;
- bintree_clear(§or->blocks, &free_block);
+ MapSector *sector = value;
+
+ bintree_clear(§or->blocks, &free_block, arg);
pthread_rwlock_destroy(§or->rwlck);
free(sector);
}
{
pthread_rwlock_destroy(&map->rwlck);
pthread_rwlock_destroy(&map->cached_rwlck);
- bintree_clear(&map->sectors, &free_sector);
+ bintree_clear(&map->sectors, &free_sector, map);
free(map);
}
cached = map->cached;
pthread_rwlock_unlock(&map->cached_rwlck);
- if (cached && cached->pos.x == pos.x && cached->pos.y == pos.y && cached->pos.z == pos.z)
+ if (cached && v3s32_equals(cached->pos, pos))
return cached;
MapSector *sector = map_get_sector(map, (v2s32) {pos.x, pos.z}, create);
if (*nodeptr) {
block = (*nodeptr)->value;
- if (block->state < MBS_READY) {
- if (! create)
- block = NULL;
+ pthread_mutex_lock(&block->mtx);
+ if (map->callbacks.get_block && ! map->callbacks.get_block(block, create)) {
+ pthread_mutex_unlock(&block->mtx);
+ block = NULL;
} else {
+ pthread_mutex_unlock(&block->mtx);
pthread_rwlock_wrlock(&map->cached_rwlck);
map->cached = block;
pthread_rwlock_unlock(&map->cached_rwlck);
}
} else if (create) {
- block = map_allocate_block(pos);
+ bintree_add_node(§or->blocks, nodeptr, &pos.y, block = map_allocate_block(pos));
- bintree_add_node(§or->blocks, nodeptr, &pos.y, block);
+ if (map->callbacks.create_block)
+ map->callbacks.create_block(block);
}
pthread_rwlock_unlock(§or->rwlck);
{
MapBlock *block = malloc(sizeof(MapBlock));
block->pos = pos;
- block->state = MBS_CREATED;
block->extra = NULL;
- block->free_extra = NULL;
- pthread_mutex_init(&block->mtx, NULL);
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+ pthread_mutex_init(&block->mtx, &attr);
return block;
}
{
map_clear_meta(block);
pthread_mutex_destroy(&block->mtx);
- if (block->free_extra)
- block->free_extra(block->extra);
free(block);
}
void map_serialize_block(MapBlock *block, char **dataptr, size_t *sizeptr)
{
- size_t uncompressed_size = sizeof(MapBlockData);
- char uncompressed_data[uncompressed_size];
-
- MapBlockData blockdata;
+ MapBlockData uncompressed;
ITERATE_MAPBLOCK {
MapNode node = block->data[x][y][z];
node_definitions[node.type].serialize(&node);
node.type = htobe32(node.type);
- blockdata[x][y][z] = node;
+ uncompressed[x][y][z] = node;
}
- memcpy(uncompressed_data, blockdata, sizeof(MapBlockData));
-
- char compressed_data[uncompressed_size];
- z_stream stream;
- stream.zalloc = Z_NULL;
- stream.zfree = Z_NULL;
- stream.opaque = Z_NULL;
-
- stream.avail_in = stream.avail_out = uncompressed_size;
- stream.next_in = (Bytef *) uncompressed_data;
- stream.next_out = (Bytef *) compressed_data;
-
- deflateInit(&stream, Z_BEST_COMPRESSION);
- deflate(&stream, Z_FINISH);
- deflateEnd(&stream);
-
- size_t compressed_size = stream.total_out;
-
- size_t size = sizeof(MapBlockHeader) + sizeof(MapBlockHeader) + compressed_size;
- char *data = malloc(size);
- *(MapBlockHeader *) data = htobe32(sizeof(MapBlockHeader) + compressed_size);
- *(MapBlockHeader *) (data + sizeof(MapBlockHeader)) = htobe32(uncompressed_size);
- memcpy(data + sizeof(MapBlockHeader) + sizeof(MapBlockHeader), compressed_data, compressed_size);
-
- *sizeptr = size;
- *dataptr = data;
+ my_compress(&uncompressed, sizeof(MapBlockData), dataptr, sizeptr);
}
bool map_deserialize_block(MapBlock *block, const char *data, size_t size)
{
- if (size < sizeof(MapBlockHeader))
- return false;
+ MapBlockData decompressed;
- MapBlockHeader uncompressed_size = be32toh(*(MapBlockHeader *) data);
-
- if (uncompressed_size < sizeof(MapBlockData))
+ if (! my_decompress(data, size, &decompressed, sizeof(MapBlockData)))
return false;
- char decompressed_data[uncompressed_size];
-
- z_stream stream;
- stream.zalloc = Z_NULL;
- stream.zfree = Z_NULL;
- stream.opaque = Z_NULL;
-
- stream.avail_in = size - sizeof(MapBlockHeader);
- stream.next_in = (Bytef *) (data + sizeof(MapBlockHeader));
- stream.avail_out = uncompressed_size;
- stream.next_out = (Bytef *) decompressed_data;
-
- inflateInit(&stream);
- inflate(&stream, Z_NO_FLUSH);
- inflateEnd(&stream);
-
- if (stream.total_out < uncompressed_size)
- return false;
-
- MapBlockData blockdata;
- memcpy(blockdata, decompressed_data, sizeof(MapBlockData));
-
ITERATE_MAPBLOCK {
- MapNode node = blockdata[x][y][z];
+ MapNode node = decompressed[x][y][z];
node.type = be32toh(node.type);
if (node.type >= NODE_UNLOADED)
node_definitions[node.type].deserialize(&node);
block->data[x][y][z] = node;
-
block->metadata[x][y][z] = list_create(&list_compare_string);
}
- block->state = MBS_MODIFIED;
-
return true;
}
v3s32 map_node_to_block_pos(v3s32 pos, v3u8 *offset)
{
if (offset)
- *offset = (v3u8) {(u32) pos.x % 16, (u32) pos.y % 16, (u32) pos.z % 16};
- return (v3s32) {floor((double) pos.x / 16.0), floor((double) pos.y / 16.0), floor((double) pos.z / 16.0)};
+ *offset = (v3u8) {(u32) pos.x % MAPBLOCK_SIZE, (u32) pos.y % MAPBLOCK_SIZE, (u32) pos.z % MAPBLOCK_SIZE};
+ return (v3s32) {floor((double) pos.x / (double) MAPBLOCK_SIZE), floor((double) pos.y / (double) MAPBLOCK_SIZE), floor((double) pos.z / (double) MAPBLOCK_SIZE)};
}
MapNode map_get_node(Map *map, v3s32 pos)
return block->data[offset.x][offset.y][offset.z];
}
-void map_set_node(Map *map, v3s32 pos, MapNode node)
+void map_set_node(Map *map, v3s32 pos, MapNode node, bool create, void *arg)
{
v3u8 offset;
- MapBlock *block = map_get_block(map, map_node_to_block_pos(pos, &offset), false);
+ MapBlock *block = map_get_block(map, map_node_to_block_pos(pos, &offset), create);
if (block) {
pthread_mutex_lock(&block->mtx);
- block->state = MBS_MODIFIED;
- list_clear(&block->metadata[offset.x][offset.y][offset.z]);
- block->data[offset.x][offset.y][offset.z] = node;
+ if (! map->callbacks.set_node || map->callbacks.set_node(block, offset, &node, arg)) {
+ if (map->callbacks.after_set_node)
+ map->callbacks.after_set_node(block, offset, arg);
+ list_clear(&block->metadata[offset.x][offset.y][offset.z]);
+ block->data[offset.x][offset.y][offset.z] = node;
+ }
pthread_mutex_unlock(&block->mtx);
}
}
#include "node.h"
#include "types.h"
-#define ITERATE_MAPBLOCK for (u8 x = 0; x < 16; x++) for (u8 y = 0; y < 16; y++) for (u8 z = 0; z < 16; z++)
+#define MAPBLOCK_SIZE 16
+#define ITERATE_MAPBLOCK for (u8 x = 0; x < MAPBLOCK_SIZE; x++) for (u8 y = 0; y < MAPBLOCK_SIZE; y++) for (u8 z = 0; z < MAPBLOCK_SIZE; z++)
typedef struct MapNode
{
NodeState state;
} MapNode;
-typedef enum
-{
- MBS_CREATED,
- MBS_READY,
- MBS_MODIFIED,
- MBS_PROCESSING,
-} MapBlockState;
-
-typedef MapNode MapBlockData[16][16][16];
-
-typedef u32 MapBlockHeader;
+typedef MapNode MapBlockData[MAPBLOCK_SIZE][MAPBLOCK_SIZE][MAPBLOCK_SIZE];
typedef struct
{
MapBlockData data;
- List metadata[16][16][16];
+ List metadata[MAPBLOCK_SIZE][MAPBLOCK_SIZE][MAPBLOCK_SIZE];
v3s32 pos;
- MapBlockState state;
pthread_mutex_t mtx;
void *extra;
- void (*free_extra)(void *ptr);
} MapBlock;
typedef struct
pthread_rwlock_t rwlck;
Bintree blocks;
v2s32 pos;
- u64 hash;
} MapSector;
+typedef struct
+{
+ void (*create_block)(MapBlock *block);
+ void (*delete_block)(MapBlock *block);
+ bool (*get_block)(MapBlock *block, bool create);
+ bool (*set_node) (MapBlock *block, v3u8 offset, MapNode *node, void *arg);
+ void (*after_set_node)(MapBlock *block, v3u8 offset, void *arg);
+} MapCallbacks;
+
typedef struct
{
pthread_rwlock_t rwlck;
Bintree sectors;
pthread_rwlock_t cached_rwlck;
MapBlock *cached;
+ MapCallbacks callbacks;
} Map;
-Map *map_create();
+Map *map_create(MapCallbacks callbacks);
void map_delete(Map *map);
MapSector *map_get_sector(Map *map, v2s32 pos, bool create);
v3s32 map_node_to_block_pos(v3s32 pos, v3u8 *offset);
MapNode map_get_node(Map *map, v3s32 pos);
-void map_set_node(Map *map, v3s32 pos, MapNode node);
+void map_set_node(Map *map, v3s32 pos, MapNode node, bool create, void *arg);
MapNode map_node_create(Node type);
void map_node_clear(MapNode *node);
static void create_state_biome(MapNode *node)
{
- node->state.biome = (v3f) {1.0f, 0.0f, 1.0f};
+ node->state.biome = (v3f32) {1.0f, 0.0f, 1.0f};
}
NodeDefintion node_definitions[NODE_UNLOADED] = {
#include <stdbool.h>
#include "types.h"
-typedef v3f NodeStateBiome;
+typedef v3f32 NodeStateBiome;
typedef union
{
+++ /dev/null
-#include <stdlib.h>
-#include "server/facecache.h"
-#include "array.h"
-
-static struct
-{
- Array positions;
- u32 size;
- pthread_mutex_t mtx;
-} facecache;
-
-__attribute((constructor)) static void face_cache_init()
-{
- facecache.size = 0;
- facecache.positions = array_create(sizeof(v3s32));
- v3s32 pos = {0, 0, 0};
- array_append(&facecache.positions, &pos);
- pthread_mutex_init(&facecache.mtx, NULL);
-}
-
-__attribute((destructor)) void face_cache_deinit()
-{
- if (facecache.positions.ptr)
- free(facecache.positions.ptr);
- pthread_mutex_destroy(&facecache.mtx);
-}
-
-static void face_cache_calculate(s32 size)
-{
-#define ADDPOS(a, b, c, va, vb, vc) \
- { \
- v3s32 pos; \
- *(s32 *) ((char *) &pos + offsetof(v3s32, a)) = va; \
- *(s32 *) ((char *) &pos + offsetof(v3s32, b)) = vb; \
- *(s32 *) ((char *) &pos + offsetof(v3s32, c)) = vc; \
- array_append(&facecache.positions, &pos); \
- }
-#define SQUARES(a, b, c) \
- for (s32 va = -size + 1; va < size; va++) { \
- for (s32 vb = -size + 1; vb < size; vb++) { \
- ADDPOS(a, b, c, va, vb, size) \
- ADDPOS(a, b, c, va, vb, -size) \
- } \
- }
- SQUARES(x, z, y)
- SQUARES(x, y, z)
- SQUARES(z, y, x)
-#undef SQUARES
-#define EDGES(a, b, c) \
- for (s32 va = -size + 1; va < size; va++) { \
- ADDPOS(a, b, c, va, size, size) \
- ADDPOS(a, b, c, va, size, -size) \
- ADDPOS(a, b, c, va, -size, size) \
- ADDPOS(a, b, c, va, -size, -size) \
- }
- EDGES(x, y, z)
- EDGES(z, x, y)
- EDGES(y, x, z)
-#undef EDGES
- ADDPOS(x, y, z, size, size, size)
- ADDPOS(x, y, z, size, size, -size)
- ADDPOS(x, y, z, size, -size, size)
- ADDPOS(x, y, z, size, -size, -size)
- ADDPOS(x, y, z, -size, size, size)
- ADDPOS(x, y, z, -size, size, -size)
- ADDPOS(x, y, z, -size, -size, size)
- ADDPOS(x, y, z, -size, -size, -size)
-#undef ADDPOS
-}
-
-v3s32 facecache_face(size_t i, v3s32 *base)
-{
- pthread_mutex_lock(&facecache.mtx);
- while (facecache.positions.siz <= i)
- face_cache_calculate(++facecache.size);
- v3s32 pos = ((v3s32 *) facecache.positions.ptr)[i];
- pthread_mutex_unlock(&facecache.mtx);
- if (base) {
- pos.x += base->x;
- pos.y += base->y;
- pos.z += base->z;
- }
- return pos;
-}
-
-size_t facecache_count(u32 size)
-{
- size_t len = 1 + size * 2;
- return len * len * len;
-}
+++ /dev/null
-#ifndef _FACECACHE_H_
-#define _FACECACHE_H_
-
-#include <pthread.h>
-#include "types.h"
-
-v3s32 facecache_face(size_t i, v3s32 *base);
-size_t facecache_count(u32 size);
-
-#endif
#include <stdio.h>
#include <endian.h>
#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
#include "server/mapdb.h"
#include "server/server_map.h"
+#include "util.h"
+// utility functions
+
+// print SQLite3 error message for failed block SQL statement
static void print_error(sqlite3 *db, MapBlock *block, const char *action)
{
- printf("Database error with %s block at %d %d %d: %s\n", action, block->pos.x, block->pos.y, block->pos.z, sqlite3_errmsg(db));
-}
-
-sqlite3 *mapdb_open(const char *path)
-{
- sqlite3 *db;
- char *err;
-
- if (sqlite3_open_v2(path, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL) != SQLITE_OK) {
- printf("Failed to open database: %s\n", sqlite3_errmsg(db));
- } else if (sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS blocks (pos BLOB PRIMARY KEY, data BLOB NOT NULL);", NULL, NULL, &err) != SQLITE_OK) {
- printf("Failed to initialize database: %s\n", err);
- sqlite3_free(err);
- }
-
- return db;
+ printf("Database error with %s block at (%d, %d, %d): %s\n", action, block->pos.x, block->pos.y, block->pos.z, sqlite3_errmsg(db));
}
+// prepare a SQLite3 block statement and bind the position
static sqlite3_stmt *prepare_statement(sqlite3 *db, MapBlock *block, const char *action, const char *sql)
{
sqlite3_stmt *stmt;
return stmt;
}
+// public functions
+
+// open and initialize SQLite3 database at path
+sqlite3 *mapdb_open(const char *path)
+{
+ sqlite3 *db;
+ char *err;
+
+ if (sqlite3_open_v2(path, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL) != SQLITE_OK) {
+ printf("Failed to open database: %s\n", sqlite3_errmsg(db));
+ } else if (sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS blocks (pos BLOB PRIMARY KEY, generated INT, size INT, data BLOB, mgsb_size INT, mgsb_data BLOB);", NULL, NULL, &err) != SQLITE_OK) {
+ printf("Failed to initialize database: %s\n", err);
+ sqlite3_free(err);
+ }
+
+ return db;
+}
+
+// load a block from map database (initializes state, mgs buffer and data), returns false on failure
bool mapdb_load_block(sqlite3 *db, MapBlock *block)
{
sqlite3_stmt *stmt;
- if (! (stmt = prepare_statement(db, block, "loading", "SELECT data FROM blocks WHERE pos=?")))
+ if (! (stmt = prepare_statement(db, block, "loading", "SELECT generated, size, data, mgsb_size, mgsb_data FROM blocks WHERE pos=?")))
return false;
int rc = sqlite3_step(stmt);
bool found = rc == SQLITE_ROW;
if (found) {
- const char *data = sqlite3_column_blob(stmt, 0);
- map_deserialize_block(block, data + sizeof(MapBlockHeader), be32toh(*(MapBlockHeader *) data));
+ MapBlockExtraData *extra = block->extra;
+
+ extra->state = sqlite3_column_int(stmt, 0) ? MBS_READY : MBS_CREATED;
+ extra->size = sqlite3_column_int64(stmt, 1);
+ extra->data = malloc(extra->size);
+ memcpy(extra->data, sqlite3_column_blob(stmt, 2), extra->size);
+
+ MapgenStageBuffer decompressed_mgsb;
+ my_decompress(sqlite3_column_blob(stmt, 4), sqlite3_column_int64(stmt, 3), &decompressed_mgsb, sizeof(MapgenStageBuffer));
+
+ ITERATE_MAPBLOCK extra->mgs_buffer[x][y][z] = be32toh(decompressed_mgsb[x][y][z]);
+
+ if (! map_deserialize_block(block, extra->data, extra->size))
+ printf("Error with deserializing block at (%d, %d, %d)\n", block->pos.x, block->pos.y, block->pos.z);
} else if (rc != SQLITE_DONE) {
print_error(db, block, "loading");
}
return found;
}
+// save a block to database
void mapdb_save_block(sqlite3 *db, MapBlock *block)
{
sqlite3_stmt *stmt;
- if (! (stmt = prepare_statement(db, block, "saving", "REPLACE INTO blocks (pos, data) VALUES(?1, ?2)")))
+ if (! (stmt = prepare_statement(db, block, "saving", "REPLACE INTO blocks (pos, generated, size, data, mgsb_size, mgsb_data) VALUES(?1, ?2, ?3, ?4, ?5, ?6)")))
return;
MapBlockExtraData *extra = block->extra;
- sqlite3_bind_blob(stmt, 2, extra->data, extra->size, SQLITE_TRANSIENT);
+ MapgenStageBuffer uncompressed_mgsb;
+ ITERATE_MAPBLOCK uncompressed_mgsb[x][y][z] = htobe32(extra->mgs_buffer[x][y][z]);
+
+ char *mgsb_data;
+ size_t mgsb_size;
+
+ my_compress(&uncompressed_mgsb, sizeof(MapgenStageBuffer), &mgsb_data, &mgsb_size);
+
+ sqlite3_bind_int(stmt, 2, extra->state > MBS_CREATED);
+ sqlite3_bind_int64(stmt, 3, extra->size);
+ sqlite3_bind_blob(stmt, 4, extra->data, extra->size, SQLITE_TRANSIENT);
+ sqlite3_bind_int64(stmt, 5, mgsb_size);
+ sqlite3_bind_blob(stmt, 6, mgsb_data, mgsb_size, &free);
if (sqlite3_step(stmt) != SQLITE_DONE)
print_error(db, block, "saving");
#include <stdbool.h>
#include "map.h"
-sqlite3 *mapdb_open(const char *path);
-bool mapdb_load_block(sqlite3 *db, MapBlock *block);
-void mapdb_save_block(sqlite3 *db, MapBlock *block);
+sqlite3 *mapdb_open(const char *path); // open and initialize SQLite3 database at path
+bool mapdb_load_block(sqlite3 *db, MapBlock *block); // load a block from map database (initializes state, mgs buffer and data), returns false on failure
+void mapdb_save_block(sqlite3 *db, MapBlock *block); // save a block to database
#endif
+#include <stdio.h>
#include <math.h>
#include "server/mapgen.h"
#include "server/perlin.h"
+#include "server/server_map.h"
-void mapgen_generate_block(MapBlock *block)
+static void set_node(v3s32 pos, MapNode node, MapgenStage mgs, List *changed_blocks)
{
- for (u8 x = 0; x < 16; x++) {
- u32 ux = x + block->pos.x * 16 + ((u32) 1 << 31);
- for (u8 z = 0; z < 16; z++) {
- u32 uz = z + block->pos.z * 16 + ((u32) 1 << 31);
+ MapgenSetNodeArg arg = {
+ .mgs = mgs,
+ .changed_blocks = changed_blocks,
+ };
+ map_set_node(server_map.map, pos, node, true, &arg);
+}
+
+// generate a block (does not manage block state or threading)
+void mapgen_generate_block(MapBlock *block, List *changed_blocks)
+{
+ printf("Generating block at (%d, %d, %d)\n", block->pos.x, block->pos.y, block->pos.z);
+
+ MapBlockExtraData *extra = block->extra;
+
+ for (u8 x = 0; x < MAPBLOCK_SIZE; x++) {
+ u32 ux = x + block->pos.x * MAPBLOCK_SIZE + ((u32) 1 << 31);
+ for (u8 z = 0; z < MAPBLOCK_SIZE; z++) {
+ u32 uz = z + block->pos.z * MAPBLOCK_SIZE + ((u32) 1 << 31);
s32 height = smooth2d(ux / 32.0, uz / 32.0, 0, 0) * 16.0 + 128.0;
bool is_mountain = false;
is_mountain = true;
}
- for (u8 y = 0; y < 16; y++) {
- s32 ay = block->pos.y * 16 + y;
+ for (u8 y = 0; y < MAPBLOCK_SIZE; y++) {
+ s32 ay = block->pos.y * MAPBLOCK_SIZE + y;
s32 diff = ay - height;
Node node = NODE_AIR;
else if (diff < 1)
node = (is_mountain && ay > 256) ? NODE_SNOW : NODE_AIR;
- block->data[x][y][z] = map_node_create(node);
- block->metadata[x][y][z] = list_create(&list_compare_string);
+ if (! is_mountain && diff == 0 && (smooth2d(x + block->pos.x * 16, z + block->pos.z * 16, 0, 3) * 0.5 + 0.5) < 0.01f) {
+ for (s8 bx = -1; bx <= 1; bx++) {
+ for (s8 by = -1; by <= 1; by++) {
+ for (s8 bz = -1; bz <= 1; bz++) {
+ v3s32 pos = {block->pos.x * MAPBLOCK_SIZE + x + bx, block->pos.y * MAPBLOCK_SIZE + y + by, block->pos.z * MAPBLOCK_SIZE + z + bz};
+ if (smooth3d(pos.x, pos.y, pos.z, 0, 4) > 0.0)
+ set_node(pos, map_node_create(NODE_STONE), MGS_BOULDERS, changed_blocks);
+ }
+ }
+ }
+ }
+
+ pthread_mutex_lock(&block->mtx);
+ if (extra->mgs_buffer[x][y][z] <= MGS_TERRAIN) {
+ block->data[x][y][z] = map_node_create(node);
+ extra->mgs_buffer[x][y][z] = MGS_TERRAIN;
- if (node == NODE_GRASS) {
- double min, max;
- min = 0.15;
- max = 0.45;
- block->data[x][y][z].state.biome.x = (smooth2d(ux / 128.0, uz / 128.0, 0, 3) * 0.5 + 0.5) * (max - min) + min;
- block->data[x][y][z].state.biome.y = 1.0;
+ if (node == NODE_GRASS) {
+ double min, max;
+ min = 0.15;
+ max = 0.45;
+ block->data[x][y][z].state.biome.x = (smooth2d(ux / 128.0, uz / 128.0, 0, 3) * 0.5 + 0.5) * (max - min) + min;
+ block->data[x][y][z].state.biome.y = 1.0;
+ }
}
+ pthread_mutex_unlock(&block->mtx);
}
}
}
#include "map.h"
-void mapgen_generate_block(MapBlock *block);
+void mapgen_generate_block(MapBlock *block, List *changed_blocks); // generate a block (does not manage block state or threading)
#endif
-#include <perlin/perlin.c>
+// include perlin submodule implementation
+#include <perlin/perlin.c>
#ifndef _PERLIN_H_
#define _PERLIN_H_
-#include <perlin/perlin.h>
+#include <perlin/perlin.h> // include perlin submodule header file
#endif
#include "signal_handlers.h"
#include "util.h"
-Server server;
+static Server server;
-void server_disconnect_client(Client *client, int flags, const char *detail)
-{
- client->state = CS_DISCONNECTED;
-
- if (! (flags & DISCO_NO_REMOVE)) {
- if (client->name) {
- pthread_rwlock_wrlock(&server.players_rwlck);
- list_delete(&server.players, client->name);
- pthread_rwlock_unlock(&server.players_rwlck);
- }
- pthread_rwlock_wrlock(&server.clients_rwlck);
- list_delete(&server.clients, client);
- pthread_rwlock_unlock(&server.clients_rwlck);
- }
-
- if (! (flags & DISCO_NO_MESSAGE))
- printf("Disconnected %s %s%s%s\n", client->name, INBRACES(detail));
-
- if (! (flags & DISCO_NO_SEND))
- send_command(client, CC_DISCONNECT);
-
- pthread_mutex_lock(&client->mtx);
- close(client->fd);
- pthread_mutex_unlock(&client->mtx);
-
- if (! (flags & DISCO_NO_JOIN))
- pthread_join(client->net_thread, NULL);
-
- if (client->name != client->address)
- free(client->name);
- free(client->address);
-
- pthread_mutex_destroy(&client->mtx);
- free(client);
-}
+// include handle_packets implementation
#include "network.c"
-static void *server_reciever_thread(void *cli)
+// utility functions
+
+// pthread start routine for reciever thread
+static void *reciever_thread(void *arg)
{
- Client *client = cli;
+ Client *client = arg;
if (! handle_packets(client))
server_disconnect_client(client, DISCO_NO_SEND | DISCO_NO_JOIN, "network error");
return NULL;
}
-static void server_accept_client()
+// accept a new connection, initialize client and start reciever thread
+static void accept_client()
{
struct sockaddr_storage client_address = {0};
socklen_t client_addrlen = sizeof(client_address);
client->address = address_string((struct sockaddr_in6 *) &client_address);
client->name = client->address;
client->server = &server;
- client->pos = (v3f) {0.0f, 0.0f, 0.0f};
- pthread_create(&client->net_thread, NULL, &server_reciever_thread, client);
- client->sent_blocks = list_create(NULL);
+ client->pos = (v3f64) {0.0f, 0.0f, 0.0f};
+ pthread_create(&client->net_thread, NULL, &reciever_thread, client);
pthread_rwlock_wrlock(&server.clients_rwlck);
list_put(&server.clients, client, NULL);
printf("Connected %s\n", client->address);
}
-static void list_disconnect_client(void *key, __attribute__((unused)) void *value, __attribute__((unused)) void *unused)
+// list_clear_func callback used on server shutdown to disconnect all clients properly
+static void list_disconnect_client(void *key, __attribute__((unused)) void *value, __attribute__((unused)) void *arg)
{
server_disconnect_client(key, DISCO_NO_REMOVE | DISCO_NO_MESSAGE, "");
}
-void server_start(int fd)
+// start up the server after socket was created, then accept connections until interrupted, then shutdown server
+static void server_run(int fd)
{
+ server.config.simulation_distance = 16;
+
server.sockfd = fd;
- server.map = map_create(NULL);
pthread_rwlock_init(&server.clients_rwlck, NULL);
server.clients = list_create(NULL);
pthread_rwlock_init(&server.players_rwlck, NULL);
server_map_init(&server);
while (! interrupted)
- server_accept_client();
+ accept_client();
printf("Shutting down\n");
- server_map_deinit();
-
pthread_rwlock_wrlock(&server.clients_rwlck);
list_clear_func(&server.clients, &list_disconnect_client, NULL);
pthread_rwlock_unlock(&server.clients_rwlck);
+
pthread_rwlock_wrlock(&server.players_rwlck);
list_clear(&server.players);
pthread_rwlock_unlock(&server.players_rwlck);
shutdown(server.sockfd, SHUT_RDWR);
close(server.sockfd);
- map_delete(server.map);
+ server_map_deinit();
exit(EXIT_SUCCESS);
}
+// public functions
+
+// disconnect a client with various options an an optional detail message (flags: DiscoFlag bitmask)
+void server_disconnect_client(Client *client, int flags, const char *detail)
+{
+ client->state = CS_DISCONNECTED;
+
+ if (! (flags & DISCO_NO_REMOVE)) {
+ if (client->name) {
+ pthread_rwlock_wrlock(&server.players_rwlck);
+ list_delete(&server.players, client->name);
+ pthread_rwlock_unlock(&server.players_rwlck);
+ }
+ pthread_rwlock_wrlock(&server.clients_rwlck);
+ list_delete(&server.clients, client);
+ pthread_rwlock_unlock(&server.clients_rwlck);
+ }
+
+ if (! (flags & DISCO_NO_MESSAGE))
+ printf("Disconnected %s %s%s%s\n", client->name, INBRACES(detail));
+
+ if (! (flags & DISCO_NO_SEND))
+ send_command(client, CC_DISCONNECT);
+
+ pthread_mutex_lock(&client->mtx);
+ close(client->fd);
+ pthread_mutex_unlock(&client->mtx);
+
+ if (! (flags & DISCO_NO_JOIN))
+ pthread_join(client->net_thread, NULL);
+
+ if (client->name != client->address)
+ free(client->name);
+ free(client->address);
+
+ pthread_mutex_destroy(&client->mtx);
+ free(client);
+}
+
+// server entry point
int main(int argc, char **argv)
{
program_name = argv[0];
freeaddrinfo(info);
signal_handlers_init();
- server_start(fd);
+ server_run(fd);
return EXIT_SUCCESS;
}
#include "client/client_commands.h"
#include "server/server_commands.h"
#include "list.h"
-#include "map.h"
#include "network.h"
+#include "types.h"
typedef struct
{
- int sockfd;
- pthread_rwlock_t clients_rwlck;
- List clients;
- pthread_rwlock_t players_rwlck;
- List players;
- Map *map;
+ int sockfd; // TCP socket to accept new connections
+ pthread_rwlock_t clients_rwlck; // lock to protect client list
+ List clients; // Client * -> NULL map with all connected clients
+ pthread_rwlock_t players_rwlck; // lock to protect player list
+ List players; // char * -> Client * map with clients that have finished auth
+ struct {
+ u32 simulation_distance; // perimeter of the cube that players can see blocks in is simulation_distance * 2 + 1
+ } config; // configuration, ToDo: read from config file
} Server;
typedef struct Client
{
- int fd;
- pthread_mutex_t mtx;
- ClientState state;
- char *address;
- char *name;
- Server *server;
- pthread_t net_thread;
- List sent_blocks;
- v3f pos;
+ int fd; // TCP socket for connection
+ pthread_mutex_t mtx; // mutex to protect socket
+ ClientState state; // state of the client (created, auth, active, disconnected)
+ char *address; // address string to use as identifier for log messages until auth is completed
+ char *name; // player name (must be unique)
+ Server *server; // pointer to server object (essentially the same for all clients)
+ pthread_t net_thread; // reciever thread ID
+ v3f64 pos; // player position
} Client;
typedef enum
{
- DISCO_NO_REMOVE = 0x01,
- DISCO_NO_SEND = 0x02,
- DISCO_NO_MESSAGE = 0x04,
- DISCO_NO_JOIN = 0x08,
+ DISCO_NO_REMOVE = 0x01, // don't remove from client and player list (to save extra work on server shutdown)
+ DISCO_NO_SEND = 0x02, // don't notfiy client about the disconnect (if client sent disconnect themselves or the TCP connection died)
+ DISCO_NO_MESSAGE = 0x04, // don't log a message about the disconnect (used on server shutdown)
+ DISCO_NO_JOIN = 0x08, // don't wait for the reciever thread to finish (if TCP connection death was reported by reciever thread, the thread is already dead)
} DiscoFlag;
-void server_disconnect_client(Client *client, int flags, const char *detail);
-void server_shutdown();
+void server_disconnect_client(Client *client, int flags, const char *detail); // disconnect a client with various options an an optional detail message (flags: DiscoFlag bitmask)
#endif
#include "server/server_map.h"
#include "util.h"
+// command callbacks
+
+// disconnect client without sending a packet (client won't recieve it)
static bool disconnect_handler(Client *client, bool good)
{
if (good)
return true;
}
+// insert client into player list and update state (if checks pass)
static bool auth_handler(Client *client, bool good)
{
char *name = read_string(client->fd, PLAYER_NAME_MAX);
pthread_mutex_lock(&client->mtx);
bool ret = write_u32(client->fd, CC_AUTH) && write_u8(client->fd, success);
+ if (ret && success)
+ ret = ret && write_u32(client->fd, CC_SIMULATION_DISTANCE) && write_u32(client->fd, client->server->config.simulation_distance);
pthread_mutex_unlock(&client->mtx);
pthread_rwlock_unlock(&client->server->players_rwlck);
return ret;
}
+// set a node on the map
static bool setnode_handler(Client *client, bool good)
{
v3s32 pos;
return false;
if (good)
- map_set_node(client->server->map, pos, node);
+ map_set_node(server_map.map, pos, node, false, NULL);
return true;
}
+// update player's position
static bool pos_handler(Client *client, bool good)
{
- v3f pos;
+ v3f64 pos;
- if (! read_v3f32(client->fd, &pos))
+ if (! read_v3f64(client->fd, &pos))
return false;
if (good)
return true;
}
+// tell server map manager client requested the block
+static bool request_block_handler(Client *client, bool good)
+{
+ v3s32 pos;
+
+ if (! read_v3s32(client->fd, &pos))
+ return false;
+
+ if (good)
+ server_map_requested_block(client, pos);
+
+ return true;
+}
+
+// declared in network.h
CommandHandler command_handlers[SERVER_COMMAND_COUNT] = {
{0},
{&disconnect_handler, "DISCONNECT", CS_CREATED | CS_ACTIVE},
{&auth_handler, "AUTH", CS_CREATED},
{&setnode_handler, "SETNODE", CS_ACTIVE},
{&pos_handler, "POS", CS_ACTIVE},
+ {&request_block_handler, "REQUEST_BLOCK", CS_ACTIVE},
};
#ifndef _SERVER_COMMANDS_H_
-#define _SCOMMANDS_H_
+#define _SERVER_COMMANDS_H_
+
+// this file must be included after client.h or server.h and before network.h
typedef enum
{
- SERVER_COMMAND_NULL,
- SC_DISCONNECT,
- SC_AUTH,
- SC_SETNODE,
- SC_POS,
- SERVER_COMMAND_COUNT,
+ SERVER_COMMAND_NULL, // invalid command
+ SC_DISCONNECT, // client notifies server about disconnecting
+ SC_AUTH, // client wants to authentify [body: name (zero terminated string)]
+ SC_SETNODE, // player placed a node [body: pos (v3s32), node (MapNode)]
+ SC_POS, // player moved [body: pos (v3f)]
+ SC_REQUEST_BLOCK, // request to send a block [body: pos (v3s32)]
+ SERVER_COMMAND_COUNT, // count of available commands
} ServerCommand;
#ifdef _CLIENT_H_
#include <string.h>
#include <unistd.h>
#include <stdio.h>
-#include "server/facecache.h"
#include "server/mapdb.h"
#include "server/mapgen.h"
#include "server/server_map.h"
#include "map.h"
+#include "util.h"
-static struct {
- Server *server;
- size_t max_blocks;
- pthread_t thread;
- sqlite3 *db;
- bool cancel;
-} server_map;
+struct ServerMap server_map;
+static Server *server;
-static void initialize_block(MapBlock *block)
-{
- if (! mapdb_load_block(server_map.db, block))
- mapgen_generate_block(block);
- block->state = MBS_MODIFIED;
-}
+// utility functions
-static void reset_client_block(void *key, __attribute__((unused)) void *value, void *block)
+// send a block to a client and reset block request
+static void send_block(Client *client, MapBlock *block)
{
- Client *client = list_get(&server_map.server->players, key);
- if (client)
- list_delete(&client->sent_blocks, block);
- free(key);
+ MapBlockExtraData *extra = block->extra;
+
+ pthread_mutex_lock(&client->mtx);
+ if (client->state == CS_ACTIVE)
+ (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);
+ pthread_mutex_unlock(&client->mtx);
}
-static void list_delete_extra_data(void *key, __attribute__((unused)) void *value, __attribute__((unused)) void *unused)
+// send block to near clients
+// block mutex has to be locked
+static void send_block_to_near(MapBlock *block)
{
- free(key);
+ MapBlockExtraData *extra = block->extra;
+
+ if (extra->state == MBS_GENERATING)
+ return;
+
+ map_serialize_block(block, &extra->data, &extra->size);
+ mapdb_save_block(server_map.db, block);
+
+ if (extra->state == MBS_CREATED)
+ return;
+
+ pthread_rwlock_rdlock(&server->players_rwlck);
+ ITERATE_LIST(&server->players, pair) {
+ Client *client = pair->value;
+
+ if (within_simulation_distance(client->pos, block->pos, server->config.simulation_distance))
+ send_block(client, block);
+ }
+ pthread_rwlock_unlock(&server->players_rwlck);
}
-static void free_extra_data(void *ext)
+// list_clear_func callback for sending changed blocks to near clients
+static void list_send_block(void *key, __attribute__((unused)) void *value, __attribute__((unused)) void *arg)
{
- MapBlockExtraData *extra = ext;
+ MapBlock *block = key;
- if (extra) {
- list_clear_func(&extra->clients, &list_delete_extra_data, NULL);
- free(extra->data);
- free(extra);
- }
+ pthread_mutex_lock(&block->mtx);
+ send_block_to_near(block);
+ pthread_mutex_unlock(&block->mtx);
}
-static void reset_block(MapBlock *block)
+// pthread start routine for mapgen thread
+static void *mapgen_thread(void *arg)
{
+ MapBlock *block = arg;
MapBlockExtraData *extra = block->extra;
- if (extra) {
- free(extra->data);
- } else {
- extra = malloc(sizeof(MapBlockExtraData));
- extra->clients = list_create(&list_compare_string);
- }
+ pthread_mutex_lock(&block->mtx);
+ extra->state = MBS_GENERATING;
+ pthread_mutex_unlock(&block->mtx);
- map_serialize_block(block, &extra->data, &extra->size);
+ List changed_blocks = list_create(NULL);
+ list_put(&changed_blocks, block, NULL);
- block->extra = extra;
- block->free_extra = &free_extra_data;
+ mapgen_generate_block(block, &changed_blocks);
- mapdb_save_block(server_map.db, block);
+ pthread_mutex_lock(&block->mtx);
+ extra->state = MBS_READY;
+ pthread_mutex_unlock(&block->mtx);
+
+ list_clear_func(&changed_blocks, &list_send_block, NULL);
- list_clear_func(&extra->clients, &reset_client_block, block);
- list_clear(&extra->clients);
+ if (! server_map.shutting_down) {
+ pthread_mutex_lock(&server_map.mapgen_threads_mtx);
+ list_delete(&server_map.mapgen_threads, &extra->mapgen_thread);
+ pthread_mutex_unlock(&server_map.mapgen_threads_mtx);
+ }
- block->state = MBS_READY;
+ return NULL;
}
-static void send_block(Client *client, MapBlock *block)
+// launch mapgen thread for block
+// block mutex has to be locked
+static void launch_mapgen_thread(MapBlock *block)
{
- MapBlockExtraData *extra = block->extra;
-
- list_put(&client->sent_blocks, block, block);
- list_put(&extra->clients, strdup(client->name), NULL);
-
- pthread_mutex_lock(&client->mtx);
- if (client->state == CS_ACTIVE)
- (void) (write_u32(client->fd, CC_BLOCK) && write_v3s32(client->fd, block->pos) && write(client->fd, extra->data, extra->size) != -1);
- pthread_mutex_unlock(&client->mtx);
+ pthread_mutex_lock(&server_map.mapgen_threads_mtx);
+ pthread_t *thread_ptr = &((MapBlockExtraData *) block->extra)->mapgen_thread;
+ pthread_create(thread_ptr, NULL, mapgen_thread, block);
+ list_put(&server_map.mapgen_threads, thread_ptr, NULL);
+ pthread_mutex_unlock(&server_map.mapgen_threads_mtx);
}
-static void reset_modified_blocks(Client *client)
+// list_clear_func callback used to join running generator threads on shutdown
+static void list_join_thread(void *key, __attribute__((unused)) void *value, __attribute__((unused)) void *arg)
{
- for (ListPair **pairptr = &client->sent_blocks.first; *pairptr != NULL; pairptr = &(*pairptr)->next) {
+ pthread_join(*(pthread_t *) key, NULL);
+}
- MapBlock *block = (*pairptr)->key;
+// map callbacks
+// note: all these functions require the block mutex to be locked, which is always the case when a map callback is invoked
- pthread_mutex_lock(&block->mtx);
- if (block->state == MBS_MODIFIED) {
- reset_block(block);
+// callback for initializing a newly created block
+// load block from database or initialize state, mgstage buffer and data
+static void on_create_block(MapBlock *block)
+{
+ MapBlockExtraData *extra = block->extra = malloc(sizeof(MapBlockExtraData));
- ListPair *next = (*pairptr)->next;
- free(*pairptr);
- *pairptr = next;
+ if (! mapdb_load_block(server_map.db, block)) {
+ extra->state = MBS_CREATED;
+ extra->data = NULL;
+
+ ITERATE_MAPBLOCK {
+ block->data[x][y][z] = map_node_create(NODE_AIR);
+ block->metadata[x][y][z] = list_create(&list_compare_string);
+ extra->mgs_buffer[x][y][z] = MGS_VOID;
}
- pthread_mutex_unlock(&block->mtx);
}
}
-static void send_blocks(Client *client)
+// callback for deleting a block
+// free extra data
+static void on_delete_block(MapBlock *block)
{
- v3s32 pos = map_node_to_block_pos((v3s32) {client->pos.x, client->pos.y, client->pos.z}, NULL);
+ MapBlockExtraData *extra = block->extra;
- for (size_t i = 0; i < server_map.max_blocks; i++) {
- MapBlock *block = map_get_block(client->server->map, facecache_face(i, &pos), true);
+ if (extra->data)
+ free(extra->data);
- pthread_mutex_lock(&block->mtx);
- switch (block->state) {
- case MBS_CREATED:
+ free(extra);
+}
- initialize_block(block);
+// callback for determining whether a block should be returned by map_get_block
+// hold back blocks that are not fully generated except when the create flag is set to true
+static bool on_get_block(MapBlock *block, bool create)
+{
+ MapBlockExtraData *extra = block->extra;
- __attribute__ ((fallthrough)); case MBS_MODIFIED:
+ if (extra->state < MBS_READY && ! create)
+ return false;
- reset_block(block);
+ return true;
+}
- send:
- send_block(client, block);
+// callback for deciding whether a set_node call succeeds or not
+// 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
+static bool on_set_node(MapBlock *block, v3u8 offset, __attribute__((unused)) MapNode *node, void *arg)
+{
+ MapgenSetNodeArg *msn_arg = arg;
- pthread_mutex_unlock(&block->mtx);
- return;
+ MapgenStage mgs;
- case MBS_READY:
+ if (msn_arg)
+ mgs = msn_arg->mgs;
+ else
+ mgs = MGS_PLAYER;
- if (! list_get(&client->sent_blocks, block))
- goto send;
- else
- break;
+ MapgenStage *old_mgs = &((MapBlockExtraData *) block->extra)->mgs_buffer[offset.x][offset.y][offset.z];
- default:
+ if (mgs >= *old_mgs) {
+ *old_mgs = mgs;
- break;
- }
- pthread_mutex_unlock(&block->mtx);
+ if (msn_arg)
+ list_put(msn_arg->changed_blocks, block, NULL);
+
+ return true;
}
+
+ return false;
}
-static void map_step()
+// callback for when a block changes
+// send block to near clients if not part of map generation
+static void on_after_set_node(MapBlock *block, __attribute__((unused)) v3u8 offset, void *arg)
{
- pthread_rwlock_rdlock(&server_map.server->players_rwlck);
- ITERATE_LIST(&server_map.server->players, pair) reset_modified_blocks(pair->value);
- ITERATE_LIST(&server_map.server->players, pair) send_blocks(pair->value);
- pthread_rwlock_unlock(&server_map.server->players_rwlck);
+ if (! arg)
+ send_block_to_near(block);
}
-static void *map_thread(__attribute__((unused)) void *unused)
+// public functions
+
+// ServerMap singleton constructor
+void server_map_init(Server *srv)
{
+ server = srv;
+
+ server_map.map = map_create((MapCallbacks) {
+ .create_block = &on_create_block,
+ .delete_block = &on_delete_block,
+ .get_block = &on_get_block,
+ .set_node = &on_set_node,
+ .after_set_node = &on_after_set_node,
+ });
+ server_map.shutting_down = false;
server_map.db = mapdb_open("map.sqlite");
-
- while (! server_map.cancel)
- map_step();
-
- return NULL;
+ server_map.mapgen_threads = list_create(NULL);
+ pthread_mutex_init(&server_map.mapgen_threads_mtx, NULL);
}
-void server_map_init(Server *server)
+// ServerMap singleton destructor
+void server_map_deinit()
{
- server_map.server = server;
- server_map.max_blocks = facecache_count(16);
+ server_map.shutting_down = true;
+
+ pthread_mutex_lock(&server_map.mapgen_threads_mtx);
+ list_clear_func(&server_map.mapgen_threads, &list_join_thread, NULL);
+ // pthread_mutex_unlock(&server_map.mapgen_threads_mtx);
+ pthread_mutex_destroy(&server_map.mapgen_threads_mtx);
- pthread_create(&server_map.thread, NULL, &map_thread, NULL);
+ sqlite3_close(server_map.db);
+ map_delete(server_map.map);
}
-void server_map_deinit()
+// handle block request from client (thread safe)
+void server_map_requested_block(Client *client, v3s32 pos)
{
- server_map.cancel = true;
- pthread_join(server_map.thread, NULL);
- sqlite3_close(server_map.db);
+ if (within_simulation_distance(client->pos, pos, server->config.simulation_distance)) {
+ MapBlock *block = map_get_block(server_map.map, pos, true);
+
+ pthread_mutex_lock(&block->mtx);
+ MapBlockExtraData *extra = block->extra;
+
+ switch (extra->state) {
+ case MBS_CREATED:
+ launch_mapgen_thread(block);
+ break;
+
+ case MBS_GENERATING:
+ break;
+
+ case MBS_READY:
+ send_block(client, block);
+ };
+ pthread_mutex_unlock(&block->mtx);
+ }
}
#ifndef _SERVER_MAP_H_
#define _SERVER_MAP_H_
+#include <stddef.h>
+#include <pthread.h>
+#include "map.h"
#include "server/server.h"
+#include "server/mapdb.h"
+
+typedef enum
+{
+ MBS_CREATED, // block exists but was not yet generated
+ MBS_GENERATING, // currently generating in a seperate thread
+ MBS_READY, // generation finished
+} MapBlockState;
+
+typedef enum
+{
+ MGS_VOID, // initial air, can be overridden by anything
+ MGS_TERRAIN, // basic terrain, can be overridden by anything except the void
+ MGS_BOULDERS, // boulders, replace terrain
+ MGS_PLAYER, // player-placed nodes or things placed after map generation
+} MapgenStage;
+
+typedef MapgenStage MapgenStageBuffer[MAPBLOCK_SIZE][MAPBLOCK_SIZE][MAPBLOCK_SIZE];
+
+typedef struct {
+ MapgenStage mgs;
+ List *changed_blocks;
+} MapgenSetNodeArg;
typedef struct
{
- List clients;
- char *data;
- size_t size;
+ char *data; // cached serialized data
+ size_t size; // size of data
+ MapBlockState state; // generation state of the block
+ pthread_t mapgen_thread; // thread that is generating block
+ MapgenStageBuffer mgs_buffer; // buffer to make sure mapgen only overrides things it should
} MapBlockExtraData;
-void server_map_init(Server *server);
-void server_map_deinit();
+extern struct ServerMap {
+ Map *map; // map object, data is stored here
+ sqlite3 *db; // SQLite3 database to save data to database
+ bool shutting_down; // is a shutdown in progress?
+ List mapgen_threads; // a list of mapgen threads (need to be joined before shutdown)
+ pthread_mutex_t mapgen_threads_mtx; // mutex to protect mapgen thread list
+} server_map; // ServerMap singleton
+
+void server_map_init(Server *server); // ServerMap singleton constructor
+void server_map_deinit(); // ServerMap singleton destructor
+void server_map_requested_block(Client *client, v3s32 pos); // handle block request from client (thread safe)
#endif
type vec[2] = {val.x, val.y}; \
WRITEVEC(type, 2) \
} \
+ bool v2 ## type ## _equals(v2 ## type a, v2 ## type b) \
+ { \
+ return a.x == b.x && a.y == b.y; \
+ } \
bool read_v3 ## type(int fd, v3 ## type *ptr) \
{ \
READVEC(type, 3) \
{ \
type vec[3] = {val.x, val.y, val.z}; \
WRITEVEC(type, 3) \
+ } \
+ bool v3 ## type ## _equals(v3 ## type a, v3 ## type b) \
+ { \
+ return a.x == b.x && a.y == b.y && a.z == b.z; \
}
#define DEFTYP(type, bits) \
typedef struct {type x, y;} v2 ## type; \
DEFRW(v2 ## type) \
DEFBOX(2 ## type) \
+ bool v2 ## type ## _equals(v2 ## type a, v2 ## type b); \
typedef struct {type x, y, z;} v3 ## type; \
DEFRW(v3 ## type) \
- DEFBOX(3 ## type)
+ DEFBOX(3 ## type) \
+ bool v3 ## type ## _equals(v3 ## type a, v3 ## type b); \
#define DEFTYP(from, to) \
typedef from to; \
DEFTYP(float, f32)
DEFTYP(double, f64)
-typedef v2f32 v2f;
-typedef v3f32 v3f;
-
-typedef aabb2f32 aabb2f;
-typedef aabb3f32 aabb3f;
-
#undef DEFRW
#undef DEFBOX
#undef DEFVEC
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
+#include <zlib.h>
+#include "map.h"
#include "util.h"
const char *program_name;
+// print system call related error message and exit
void syscall_error(const char *err)
{
perror(err);
exit(EXIT_FAILURE);
}
+// print general error message and exit
void internal_error(const char *err)
{
fprintf(stderr, "%s: %s\n", program_name, err);
exit(EXIT_FAILURE);
}
+// read from fd until \0 or EOF terminator
+// store result including terminator into allocated buffer until bufsiz+1 is hit, return NULL on read error
char *read_string(int fd, size_t bufsiz)
{
char buf[bufsiz + 1];
return strdup(buf);
}
+// convert IPv6 address to human readable, return allocated buffer
char *address_string(struct sockaddr_in6 *addr)
{
char address[INET6_ADDRSTRLEN] = {0};
return result;
}
-v3f html_to_v3f(const char *html)
+// convert #RRGGBB color to v3f32
+v3f32 html_to_v3f32(const char *html)
{
unsigned int r, g, b;
sscanf(html, "#%2x%2x%2x", &r, &g, &b);
- return (v3f) {(f32) r / 255.0f, (f32) g / 255.0f, (f32) b / 255.0f};
+ return (v3f32) {(f32) r / 255.0f, (f32) g / 255.0f, (f32) b / 255.0f};
+}
+
+// compress data using ZLib and store result(buffer allocated by malloc) in compressed
+void my_compress(const void *uncompressed, size_t uncompressed_size, char **compressed, size_t *compressed_size)
+{
+ char compressed_buffer[uncompressed_size];
+
+ z_stream stream;
+ stream.zalloc = Z_NULL;
+ stream.zfree = Z_NULL;
+ stream.opaque = Z_NULL;
+
+ stream.avail_in = stream.avail_out = uncompressed_size;
+ stream.next_in = (Bytef *) uncompressed;
+ stream.next_out = (Bytef *) compressed_buffer;
+
+ deflateInit(&stream, Z_BEST_COMPRESSION);
+ deflate(&stream, Z_FINISH);
+ deflateEnd(&stream);
+
+ *compressed_size = stream.total_out;
+ *compressed = malloc(*compressed_size);
+ memcpy(*compressed, compressed_buffer, *compressed_size);
+}
+
+// decompress data and put result into decompressed, return false if decompressed size does not match expected_decompressed_size
+bool my_decompress(const char *compressed, size_t compressed_size, void *decompressed, size_t expected_decompressed_size)
+{
+ z_stream stream;
+ stream.zalloc = Z_NULL;
+ stream.zfree = Z_NULL;
+ stream.opaque = Z_NULL;
+
+ stream.avail_in = compressed_size;
+ stream.next_in = (Bytef *) compressed;
+ stream.avail_out = expected_decompressed_size;
+ stream.next_out = (Bytef *) decompressed;
+
+ inflateInit(&stream);
+ inflate(&stream, Z_NO_FLUSH);
+ inflateEnd(&stream);
+
+ return stream.total_out == expected_decompressed_size;
+}
+
+// return true if a player is close enough to a block to access it
+bool within_simulation_distance(v3f64 player_pos, v3s32 block_pos, u32 simulation_distance)
+{
+ v3s32 player_block_pos = map_node_to_block_pos((v3s32) {player_pos.x, player_pos.y, player_pos.z}, NULL);
+ return abs(player_block_pos.x - block_pos.x) <= simulation_distance && abs(player_block_pos.y - block_pos.y) <= simulation_distance && abs(player_block_pos.z - block_pos.z) <= simulation_distance;
}
#ifndef _UTIL_H_
#define _UTIL_H_
+#include <stdbool.h>
#include <arpa/inet.h>
-#include <linmath.h/linmath.h>
#include "types.h"
-#define ever (;;)
-#define INBRACES(str) str ? "(" : "", str ? str : "", str ? ")" : ""
-#define CMPBOUNDS(x) x == 0 ? 0 : x > 0 ? 1 : -1
+#define ever (;;) // infinite for loop with style
+#define INBRACES(str) str ? "(" : "", str ? str : "", str ? ")" : "" // wrapper for printf to optionally add a message in braces if message is not NULL
+#define CMPBOUNDS(x) x == 0 ? 0 : x > 0 ? 1 : -1 // resolves 1 if x > 0, 0 if x == 0 and -1 if x < 0
+#define fallthrough __attribute__ ((fallthrough)) // prevent compiler warning about implicit fallthrough with style
-extern const char *program_name;
+extern const char *program_name; // this has to be set to program name on startup
-void syscall_error(const char *err);
-void internal_error(const char *err);
-char *read_string(int fd, size_t bufsiz);
-char *address_string(struct sockaddr_in6 *addr);
-v3f html_to_v3f(const char *html);
+void syscall_error(const char *err); // print system call related error message and exit
+void internal_error(const char *err); // print general error message and exit
+char *read_string(int fd, size_t bufsiz); // read from fd until \0 or EOF terminator
+char *address_string(struct sockaddr_in6 *addr); // convert IPv6 address to human readable, return allocated buffer
+v3f32 html_to_v3f32(const char *html); // convert #RRGGBB color to v3f32
+void my_compress(const void *uncompressed, size_t uncompressed_size, char **compressed, size_t *compressed_size); // compress data using ZLib and store result(buffer allocated by malloc) in compressed
+bool my_decompress(const char *compressed, size_t compressed_size, void *decompressed, size_t expected_decompressed_size); // decompress data and put result into decompressed, return false if decompressed size does not match expected_decompressed_size
+bool within_simulation_distance(v3f64 player_pos, v3s32 block_pos, u32 simulation_distance); // return true if a player is close enough to a block to access it
#endif