From 342b828bf1f84163be0df1cf26a7bc41214fa9fb Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sun, 24 Apr 2022 13:30:56 +0200 Subject: [PATCH] Fix client terrain crashes and performance --- src/client/client.c | 11 +- src/client/client_auth.c | 3 - src/client/client_terrain.c | 236 ++++++++++++++++++++--------- src/client/client_terrain.h | 40 +++-- src/client/gl_debug.c | 14 +- src/client/interact.c | 2 + src/client/terrain_gfx.c | 292 ++++++++++++++++++++++-------------- src/debug.sh | 2 +- src/server/database.c | 4 +- src/server/server_item.c | 15 +- src/server/server_terrain.c | 41 ++--- src/server/server_terrain.h | 18 +-- src/server/terrain_gen.c | 5 +- src/server/tree_physics.c | 34 ++--- src/terrain.c | 44 +++--- src/terrain.h | 17 ++- 16 files changed, 473 insertions(+), 305 deletions(-) diff --git a/src/client/client.c b/src/client/client.c index a4e811d..667f6a2 100644 --- a/src/client/client.c +++ b/src/client/client.c @@ -86,15 +86,6 @@ static void on_ToClientAuth(__attribute__((unused)) DragonnetPeer *peer, ToClien flag_slp(&gfx_init); } -static void on_ToClientChunk(__attribute__((unused)) DragonnetPeer *peer, ToClientChunk *pkt) -{ - TerrainChunk *chunk = terrain_get_chunk(client_terrain, pkt->pos, true); - - terrain_deserialize_chunk(client_terrain, chunk, pkt->data, &client_node_deserialize); - ((TerrainChunkMeta *) chunk->extra)->empty = (pkt->data.siz == 0); - client_terrain_chunk_received(chunk); -} - static void on_ToClientInfo(__attribute__((unused)) DragonnetPeer *peer, ToClientInfo *pkt) { client_terrain_set_load_distance(pkt->load_distance); @@ -137,7 +128,7 @@ int main(int argc, char **argv) client->on_disconnect = &on_disconnect; client->on_recv = (void *) &on_recv; client->on_recv_type[DRAGONNET_TYPE_ToClientAuth ] = (void *) &on_ToClientAuth; - client->on_recv_type[DRAGONNET_TYPE_ToClientChunk ] = (void *) &on_ToClientChunk; + client->on_recv_type[DRAGONNET_TYPE_ToClientChunk ] = (void *) &client_terrain_receive_chunk; client->on_recv_type[DRAGONNET_TYPE_ToClientInfo ] = (void *) &on_ToClientInfo; client->on_recv_type[DRAGONNET_TYPE_ToClientTimeOfDay ] = (void *) &on_ToClientTimeOfDay; client->on_recv_type[DRAGONNET_TYPE_ToClientMovement ] = (void *) &on_ToClientMovement; diff --git a/src/client/client_auth.c b/src/client/client_auth.c index c0fa81a..0d16cea 100644 --- a/src/client/client_auth.c +++ b/src/client/client_auth.c @@ -16,11 +16,8 @@ static void auth_loop() if (client_auth.name) linenoiseFree(client_auth.name); - /* if (!(client_auth.name = linenoise("Enter name: "))) return; - */ - client_auth.name = strdup("singleplayer"); printf("[access] authenticating as %s...\n", client_auth.name); client_auth.state = AUTH_WAIT; diff --git a/src/client/client_terrain.c b/src/client/client_terrain.c index 9dde109..e8eae36 100644 --- a/src/client/client_terrain.c +++ b/src/client/client_terrain.c @@ -1,4 +1,5 @@ #define _GNU_SOURCE // don't worry, GNU extensions are only used when available +#include #include #include #include @@ -31,9 +32,15 @@ static size_t load_chunks; // cached number of facecache positions to pr // dequeue callback to update queue state in a thread safe manner static TerrainChunk *set_dequeued(TerrainChunk *chunk) { - pthread_mutex_lock(&chunk->mtx); - ((TerrainChunkMeta *) chunk->extra)->queue = false; - pthread_mutex_unlock(&chunk->mtx); + TerrainChunkMeta *meta = chunk->extra; + + assert(pthread_rwlock_wrlock(&meta->lock_state) == 0); + meta->queue = false; + if (meta->state < CHUNK_STATE_DIRTY) + chunk = NULL; + else + meta->state = CHUNK_STATE_CLEAN; + pthread_rwlock_unlock(&meta->lock_state); return chunk; } @@ -60,25 +67,21 @@ static void request_chunk(v3s32 pos) // terrain synchronisation step static void sync_step() { - static u64 tick = 0; + static u64 tick = 1; static v3s32 *old_requests = NULL; static size_t old_num_requests = 0; - v3f64 player_pos; ClientEntity *entity = client_player_entity_local(); - - if (entity) { - pthread_rwlock_rdlock(&entity->lock_pos_rot); - player_pos = entity->data.pos; - pthread_rwlock_unlock(&entity->lock_pos_rot); - - refcount_drp(&entity->rc); - } else { + if (!entity) { sched_yield(); return; } - v3s32 center = terrain_chunkp((v3s32) {player_pos.x, player_pos.y, player_pos.z}); + pthread_rwlock_rdlock(&entity->lock_pos_rot); + v3s32 center = terrain_chunkp(v3f64_to_s32(entity->data.pos)); + pthread_rwlock_unlock(&entity->lock_pos_rot); + + refcount_drp(&entity->rc); u64 last_tick = tick++; @@ -87,29 +90,16 @@ static void sync_step() for (size_t i = 0; i < load_chunks; i++) { v3s32 pos = v3s32_add(facecache_get(i), center); - TerrainChunk *chunk = terrain_get_chunk(client_terrain, pos, false); + TerrainChunk *chunk = terrain_get_chunk(client_terrain, pos, CHUNK_MODE_NOCREATE); if (chunk) { - pthread_mutex_lock(&chunk->mtx); - TerrainChunkMeta *meta = chunk->extra; - switch (meta->state) { - case CHUNK_READY: - // re-request chunks that got back into range - if (meta->sync < last_tick) - request_chunk(pos); - __attribute__((fallthrough)); - - case CHUNK_FRESH: - meta->state = CHUNK_READY; - meta->sync = tick; - break; - case CHUNK_RECIEVING: - break; - } + // re-request chunks that got out of and then back into range + if (meta->sync && meta->sync < last_tick) + request_chunk(pos); - pthread_mutex_unlock(&chunk->mtx); + meta->sync = tick; } else if (num_requests < MAX_REQUESTS) { // avoid duplicate requests bool requested = false; @@ -167,14 +157,24 @@ static void on_create_chunk(TerrainChunk *chunk) { TerrainChunkMeta *meta = chunk->extra = malloc(sizeof *meta); - meta->state = CHUNK_RECIEVING; meta->queue = false; - meta->sync = 0; + meta->state = CHUNK_STATE_INIT; + pthread_rwlock_init(&meta->lock_state, NULL); + + for (int i = 0; i < 6; i++) + meta->neighbors[i] = 0; + meta->num_neighbors = 0; + + for (int i = 0; i < 6; i++) + meta->depends[i] = true; + + meta->has_model = false; meta->model = NULL; + pthread_mutex_init(&meta->mtx_model, NULL); + meta->empty = false; - meta->has_model = false; - for (int i = 0; i < 6; i++) - meta->depends[i] = false; + + meta->sync = 0; } // callback for deleting a chunk @@ -186,9 +186,17 @@ static void on_delete_chunk(TerrainChunk *chunk) // callback for determining whether a chunk should be returned by terrain_get_chunk // hold back chunks that have not been fully read from server yet when the create flag is not set -static bool on_get_chunk(TerrainChunk *chunk, bool create) +static bool on_get_chunk(TerrainChunk *chunk, int mode) { - return create || ((TerrainChunkMeta *) chunk->extra)->state > CHUNK_RECIEVING; + if (mode != CHUNK_MODE_PASSIVE) + return true; + + TerrainChunkMeta *meta = chunk->extra; + assert(pthread_rwlock_rdlock(&meta->lock_state) == 0); + bool ret = meta->state > CHUNK_STATE_DEPS; + pthread_rwlock_unlock(&meta->lock_state); + + return ret; } // public functions @@ -258,40 +266,27 @@ u32 client_terrain_get_load_distance() return load_distance; } -// called when a chunk was recieved from server -void client_terrain_chunk_received(TerrainChunk *chunk) +// enqueue chunk to mesh update queue +void client_terrain_meshgen_task(TerrainChunk *chunk, bool changed) { - pthread_mutex_lock(&chunk->mtx); TerrainChunkMeta *meta = chunk->extra; - if (meta->state == CHUNK_RECIEVING) - meta->state = CHUNK_FRESH; - - client_terrain_meshgen_task(chunk, true); - pthread_mutex_unlock(&chunk->mtx); + assert(pthread_rwlock_wrlock(&meta->lock_state) == 0); + bool queue = meta->queue; + pthread_rwlock_unlock(&meta->lock_state); - for (int i = 0; i < 6; i++) { - TerrainChunk *neighbor = terrain_get_chunk(client_terrain, - v3s32_sub(chunk->pos, facedir[i]), false); - if (!neighbor) - continue; + if (queue) + return; - pthread_mutex_lock(&neighbor->mtx); - TerrainChunkMeta *neighbor_meta = neighbor->extra; - if (neighbor_meta->depends[i]) - client_terrain_meshgen_task(neighbor, true); - pthread_mutex_unlock(&neighbor->mtx); - } -} + assert(pthread_rwlock_rdlock(&chunk->lock) == 0); + bool empty = meta->empty; + pthread_rwlock_unlock(&chunk->lock); -// enqueue chunk to mesh update queue -void client_terrain_meshgen_task(TerrainChunk *chunk, bool changed) -{ - TerrainChunkMeta *meta = chunk->extra; - if (meta->queue) - return; + if (empty) + set_dequeued(chunk); - if (meta->empty) { + pthread_mutex_lock(&meta->mtx_model); + if (empty) { meta->has_model = true; if (meta->model) { @@ -300,10 +295,117 @@ void client_terrain_meshgen_task(TerrainChunk *chunk, bool changed) } } else { meta->queue = true; - if (meta->has_model && changed) queue_ppd(&meshgen_tasks, chunk); else queue_enq(&meshgen_tasks, chunk); } + pthread_mutex_unlock(&meta->mtx_model); +} + +static void iterator_meshgen_task(TerrainChunk *chunk) +{ + client_terrain_meshgen_task(chunk, true); +} + +void client_terrain_receive_chunk(__attribute__((unused)) void *peer, ToClientChunk *pkt) +{ + // get/create chunk + TerrainChunk *chunk = terrain_get_chunk(client_terrain, pkt->pos, CHUNK_MODE_CREATE); + TerrainChunkMeta *meta = chunk->extra; + + assert(pthread_rwlock_wrlock(&meta->lock_state) == 0); + // remember whether this is the first time we're receiving the chunk + bool init = meta->state == CHUNK_STATE_INIT; + // change state to receiving + meta->state = CHUNK_STATE_RECV; + pthread_rwlock_unlock(&meta->lock_state); + + // notify/collect neighbors + for (int i = 0; i < 6; i++) { + // this is the reverse face index + int j = i % 2 ? i - 1 : i + 1; + + if (init) { + // if this is first time, initialize references in both ways + + // get existing neighbor chunk + TerrainChunk *neighbor = terrain_get_chunk( + client_terrain, v3s32_add(chunk->pos, facedir[i]), CHUNK_MODE_NOCREATE); + if (!neighbor) + continue; + TerrainChunkMeta *neighbor_meta = neighbor->extra; + + // initialize reference from us to neighbor + meta->neighbors[i] = neighbor; + meta->num_neighbors++; + + // initialize reference from neighbor to us + // they (obviously) don't have all neighbors yet, so they are already in RECV state + neighbor_meta->neighbors[j] = chunk; + neighbor_meta->num_neighbors++; + } else { + // get reference + TerrainChunk *neighbor = meta->neighbors[i]; + if (!neighbor) + continue; + TerrainChunkMeta *neighbor_meta = neighbor->extra; + + // don't change state of non-dependant neighbor + if (!neighbor_meta->depends[j]) + continue; + + // if neighbor depends on us, set them to deps resolval state + assert(pthread_rwlock_wrlock(&neighbor_meta->lock_state) == 0); + neighbor_meta->state = CHUNK_STATE_DEPS; + pthread_rwlock_unlock(&neighbor_meta->lock_state); + } + } + + // deserialize data + assert(pthread_rwlock_wrlock(&chunk->lock) == 0); + meta->empty = (pkt->data.siz == 0); + terrain_deserialize_chunk(client_terrain, chunk, pkt->data, &client_node_deserialize); + pthread_rwlock_unlock(&chunk->lock); + + // collect meshgen tasks and schedule them after chunk states have been updated + List meshgen_tasks; + list_ini(&meshgen_tasks); + + // set own state to dirty (if all neighbors are there) or resolving deps else + assert(pthread_rwlock_wrlock(&meta->lock_state) == 0); + meta->state = meta->num_neighbors == 6 ? CHUNK_STATE_DIRTY : CHUNK_STATE_DEPS; + pthread_rwlock_unlock(&meta->lock_state); + + // if all neighbors are there, schedule meshgen + if (meta->num_neighbors == 6) + list_apd(&meshgen_tasks, chunk); + + // notify neighbors (and self) + for (int i = 0; i < 6; i++) { + // select neighbor chunk + TerrainChunk *neighbor = meta->neighbors[i]; + if (!neighbor) + continue; + TerrainChunkMeta *neighbor_meta = neighbor->extra; + + // don't bother with chunks that don't depend us + if (!neighbor_meta->depends[i % 2 ? i - 1 : i + 1]) + continue; + + // don't change state if neighbors are not all present + if (neighbor_meta->num_neighbors != 6) + continue; + + // set state of dependant chunk to dirty + assert(pthread_rwlock_wrlock(&neighbor_meta->lock_state) == 0); + neighbor_meta->state = CHUNK_STATE_DIRTY; + pthread_rwlock_unlock(&neighbor_meta->lock_state); + + // remeber to schedule meshgen task later + list_apd(&meshgen_tasks, neighbor); + } + + // schedule meshgen tasks + list_clr(&meshgen_tasks, (void *) &iterator_meshgen_task, NULL, NULL); } diff --git a/src/client/client_terrain.h b/src/client/client_terrain.h index fc1c820..262a3d5 100644 --- a/src/client/client_terrain.h +++ b/src/client/client_terrain.h @@ -1,25 +1,43 @@ #ifndef _CLIENT_TERRAIN_H_ #define _CLIENT_TERRAIN_H_ +#include #include #include "client/model.h" #include "terrain.h" #include "types.h" +#define CHUNK_MODE_NOCREATE 2 + typedef enum { - CHUNK_RECIEVING, // currently deserializing - CHUNK_FRESH, // first deserialisation finished, not processed by sync thread yet - CHUNK_READY, // ready to use and processed by sync thread + CHUNK_STATE_INIT, + CHUNK_STATE_RECV, + CHUNK_STATE_DEPS, + CHUNK_STATE_DIRTY, + CHUNK_STATE_CLEAN, } TerrainChunkState; typedef struct { - TerrainChunkState state; // keep track of the deserialisation and sync processing state - bool queue; // whether the chunk is in meshgen queue - u64 sync; // keep track of when a chunk was synced the last time (used to detect when a chunk got out of and then back into load distance) - Model *model; // generated by terrain_gfx - bool empty; // whether the chunk is all air - bool has_model; // whether the chunk got a model before - bool depends[6]; // neighbours it depends on + bool queue; + TerrainChunkState state; + pthread_rwlock_t lock_state; + + // accessed only by recv thread + TerrainChunk *neighbors[6]; + unsigned int num_neighbors; + + // write is protected by mtx_model, read is atomic + atomic_bool depends[6]; + + bool has_model; + Model *model; + pthread_mutex_t mtx_model; + + // protected by chunk data lock + bool empty; + + // accessed only by sync thread + u64 sync; } TerrainChunkMeta; extern Terrain *client_terrain; @@ -30,7 +48,7 @@ void client_terrain_set_load_distance(u32 dist); // update l u32 client_terrain_get_load_distance(); // return load distance void client_terrain_start(); // start meshgen and sync threads void client_terrain_stop(); // stop meshgen and sync threads -void client_terrain_chunk_received(TerrainChunk *chunk); // called when a chunk was recieved from server void client_terrain_meshgen_task(TerrainChunk *chunk, bool changed); // enqueue chunk to mesh update queue +void client_terrain_receive_chunk(void *peer, ToClientChunk *pkt); // callback to deserialize chunk from network #endif diff --git a/src/client/gl_debug.c b/src/client/gl_debug.c index cc75d24..7aa163f 100644 --- a/src/client/gl_debug.c +++ b/src/client/gl_debug.c @@ -7,13 +7,13 @@ static void gl_error(GLenum err, const char *file, int line) { switch (err) { - case GL_INVALID_ENUM: printf("INVALID_ENUM %s:%d\n", file, line); break; - case GL_INVALID_VALUE: printf("INVALID_VALUE %s:%d\n", file, line); break; - case GL_INVALID_OPERATION: printf("INVALID_OPERATION %s:%d\n", file, line); break; - case GL_STACK_OVERFLOW: printf("STACK_OVERFLOW %s:%d\n", file, line); break; - case GL_STACK_UNDERFLOW: printf("STACK_UNDERFLOW %s:%d\n", file, line); break; - case GL_OUT_OF_MEMORY: printf("OUT_OF_MEMORY %s:%d\n", file, line); break; - case GL_INVALID_FRAMEBUFFER_OPERATION: printf("INVALID_FRAMEBUFFER_OPERATION %s:%d\n", file, line); break; + case GL_INVALID_ENUM: fprintf(stderr, "[warning] OpenGL error: INVALID_ENUM %s:%d\n", file, line); break; + case GL_INVALID_VALUE: fprintf(stderr, "[warning] OpenGL error: INVALID_VALUE %s:%d\n", file, line); break; + case GL_INVALID_OPERATION: fprintf(stderr, "[warning] OpenGL error: INVALID_OPERATION %s:%d\n", file, line); break; + case GL_STACK_OVERFLOW: fprintf(stderr, "[warning] OpenGL error: STACK_OVERFLOW %s:%d\n", file, line); break; + case GL_STACK_UNDERFLOW: fprintf(stderr, "[warning] OpenGL error: STACK_UNDERFLOW %s:%d\n", file, line); break; + case GL_OUT_OF_MEMORY: fprintf(stderr, "[warning] OpenGL error: OUT_OF_MEMORY %s:%d\n", file, line); break; + case GL_INVALID_FRAMEBUFFER_OPERATION: fprintf(stderr, "[warning] OpenGL error: INVALID_FRAMEBUFFER_OPERATION %s:%d\n", file, line); break; default: break; } } diff --git a/src/client/interact.c b/src/client/interact.c index 8048080..2aefa7e 100644 --- a/src/client/interact.c +++ b/src/client/interact.c @@ -82,6 +82,8 @@ void interact_deinit() mesh_destroy(&selection_mesh); } +#include "client/client_terrain.h" + void interact_tick() { bool old_exists = interact_pointed.exists; diff --git a/src/client/terrain_gfx.c b/src/client/terrain_gfx.c index bfe8757..ff6ec70 100644 --- a/src/client/terrain_gfx.c +++ b/src/client/terrain_gfx.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -14,16 +15,16 @@ #include "facedir.h" typedef struct { - bool visible; - bool transparent; - ModelBatch *batch; - ModelBatch *batch_transparent; - TerrainChunk *chunk; - v3s32 chunkp; - TerrainChunk *nbrs[6]; - bool *tried_nbrs; - bool show_edges; - bool shown_edges; + TerrainChunk *chunk; // input: chunk pointer + TerrainChunkMeta *meta; // input: coersed chunk metadata pointer + v3s32 chunkp; // input: position of chunk + bool animate; // input: disable edge culling + ModelBatch *batch; // main output: vertex data + ModelBatch *batch_blend; // main output: vertex data for transparent textures + bool abort; // output: state changes have occured that invalidate generated output + bool grabbed[6]; // output: neighbors that have been grabbed + bool visible; // output: edge culled model would be visible + bool remake_needed; // output: edge culled model would be different from non-culled } ChunkRenderData; static VertexLayout terrain_vertex_layout = { @@ -50,23 +51,81 @@ static GLint loc_VP; static LightShader light_shader; static ModelShader model_shader; -static inline bool show_face(NodeType self, NodeType nbr) +static void grab_neighbor(ChunkRenderData *data, int i) { - switch (client_node_def[self].visibility) { - case VISIBILITY_CLIP: + // return if we've already subscribed/grabbed the lock + if (data->meta->depends[i]) + return; + + // we are now subscribed to state changes from the neighbor + data->meta->depends[i] = true; + + TerrainChunk *neighbor = data->meta->neighbors[i]; + TerrainChunkMeta *neighbor_meta = neighbor->extra; + + pthread_rwlock_rdlock(&neighbor_meta->lock_state); + // check neighbor in case it was already in a bad state before we subscribed + if ((data->grabbed[i] = neighbor_meta->state > CHUNK_STATE_RECV)) + // if state is good, actually grab the data lock in read mode + assert(pthread_rwlock_rdlock(&neighbor->lock) == 0); + else + // if state is bad, set flag to abort + data->abort = true; + pthread_rwlock_unlock(&neighbor_meta->lock_state); +} + +static inline bool show_face(ChunkRenderData *data, NodeArgsRender *args, v3s32 offset) +{ + NodeVisibility visibility = client_node_def[args->node->type].visibility; + + // always render clip nodes + if (visibility == VISIBILITY_CLIP) + return data->visible = true; + + // calculate offset of neighbor node from current chunk + v3s32 nbr_offset = v3s32_add(offset, facedir[args->f]); + + // if offset is outside bounds, we'll have to select a neighbor chunk + bool nbr_chunk = + nbr_offset.x < 0 || nbr_offset.x >= CHUNK_SIZE || + nbr_offset.y < 0 || nbr_offset.y >= CHUNK_SIZE || + nbr_offset.z < 0 || nbr_offset.z >= CHUNK_SIZE; + + NodeType nbr_node = NODE_UNKNOWN; + + if (nbr_chunk) { + // grab neighbor chunk data lock + grab_neighbor(data, args->f); + + // if grabbing failed, return true so caller immediately takes notice of abort + if (data->abort) return true; - case VISIBILITY_BLEND: - return nbr != self; + nbr_offset = terrain_offset(nbr_offset); - case VISIBILITY_SOLID: - return nbr != COUNT_NODE && client_node_def[nbr].visibility != VISIBILITY_SOLID; + // select node from neighbor chunk + nbr_node = data->meta->neighbors[args->f]->data + [nbr_offset.x][nbr_offset.y][nbr_offset.z].type; + } else { + // select node from current chunk + nbr_node = data->chunk->data[nbr_offset.x][nbr_offset.y][nbr_offset.z].type; + } - default: // impossible - break; + if (visibility == VISIBILITY_BLEND) { + // liquid nodes only render faces towards 'outsiders' + if (nbr_node != args->node->type) + return data->visible = true; + } else { // visibility == VISIBILITY_SOLID + // faces between two solid nodes are culled + if (client_node_def[nbr_node].visibility != VISIBILITY_SOLID) + return data->visible = true; + + // if the chunk needs to be animated, dont cull faces to nodes in other chunks + // but remember to rebuild the chunk model after the animation has finished + if (nbr_chunk && data->animate) + return data->remake_needed = true; } - // impossible return false; } @@ -77,6 +136,7 @@ static inline void render_node(ChunkRenderData *data, v3s32 offset) args.node = &data->chunk->data[offset.x][offset.y][offset.z]; ClientNodeDef *def = &client_node_def[args.node->type]; + if (def->visibility == VISIBILITY_NONE) return; @@ -85,44 +145,13 @@ static inline void render_node(ChunkRenderData *data, v3s32 offset) args.pos = v3s32_add(offset, data->chunkp); for (args.f = 0; args.f < 6; args.f++) { - v3s32 nbr_offset = v3s32_add(offset, facedir[args.f]); - - TerrainChunk *nbr_chunk; - - if (nbr_offset.z >= 0 && nbr_offset.z < CHUNK_SIZE && - nbr_offset.x >= 0 && nbr_offset.x < CHUNK_SIZE && - nbr_offset.y >= 0 && nbr_offset.y < CHUNK_SIZE) { - nbr_chunk = data->chunk; - } else if (!(nbr_chunk = data->nbrs[args.f]) && !data->tried_nbrs[args.f]) { - nbr_chunk = data->nbrs[args.f] = terrain_get_chunk(client_terrain, - v3s32_add(data->chunk->pos, facedir[args.f]), false); - data->tried_nbrs[args.f] = true; - } - - NodeType nbr_node = COUNT_NODE; - if (nbr_chunk) - nbr_node = nbr_chunk->data - [(nbr_offset.x + CHUNK_SIZE) % CHUNK_SIZE] - [(nbr_offset.y + CHUNK_SIZE) % CHUNK_SIZE] - [(nbr_offset.z + CHUNK_SIZE) % CHUNK_SIZE].type; - - bool show = show_face(args.node->type, nbr_node); - - if (show) - data->visible = true; - else if (data->show_edges && nbr_chunk != data->chunk && def->visibility == VISIBILITY_SOLID) - data->shown_edges = show = true; - - if (!show) + if (!show_face(data, &args, offset)) continue; - ModelBatch *batch = data->batch; - - if (def->visibility == VISIBILITY_BLEND) { - data->transparent = true; - batch = data->batch_transparent; - } + if (data->abort) + return; + ModelBatch *batch = def->visibility == VISIBILITY_BLEND ? data->batch_blend : data->batch; for (args.v = 0; args.v < 6; args.v++) { args.vertex.cube = cube_vertices[args.f][args.v]; args.vertex.cube.position = v3f32_add(args.vertex.cube.position, vertex_offset); @@ -151,66 +180,35 @@ static void animate_chunk_model(Model *model, f64 dtime) if (finished) { model->callbacks.step = NULL; - if (model->extra) { - TerrainChunk *chunk = model->extra; - - pthread_mutex_lock(&chunk->mtx); - client_terrain_meshgen_task(chunk, false); - pthread_mutex_unlock(&chunk->mtx); - } + if (model->extra) + client_terrain_meshgen_task(model->extra, false); } } -static Model *create_chunk_model(TerrainChunk *chunk, bool animate, bool *depends) +static Model *create_chunk_model(ChunkRenderData *data) { - ChunkRenderData data = { - .visible = false, - .transparent = false, - .batch = model_batch_create(&model_shader, &terrain_vertex_layout, offsetof(TerrainVertex, textureIndex)), - .batch_transparent = model_batch_create(&model_shader, &terrain_vertex_layout, offsetof(TerrainVertex, textureIndex)), - .chunk = chunk, - .chunkp = v3s32_scale(chunk->pos, CHUNK_SIZE), - .nbrs = {NULL}, - .tried_nbrs = depends, - .show_edges = animate, - .shown_edges = false, - }; - - CHUNK_ITERATE - render_node(&data, (v3s32) {x, y, z}); - - for (int i = 0; i < 6; i++) - if (data.nbrs[i]) - depends[i] = true; - - if (!data.visible || (!data.batch->textures.siz && !data.batch_transparent->textures.siz)) { - model_batch_free(data.batch); - model_batch_free(data.batch_transparent); + if (!data->visible || (!data->batch->textures.siz && !data->batch_blend->textures.siz)) return NULL; - } Model *model = model_create(); - if (data.shown_edges) - model->extra = chunk; + + if (data->remake_needed) + model->extra = data->chunk; + model->box = (aabb3f32) { v3f32_sub((v3f32) {-1.0f, -1.0f, -1.0f}, center_offset), v3f32_add((v3f32) {+1.0f, +1.0f, +1.0f}, center_offset)}; - model->callbacks.step = animate ? &animate_chunk_model : NULL; + + model->callbacks.step = data->animate ? &animate_chunk_model : NULL; model->callbacks.delete = &model_free_meshes; model->flags.frustum_culling = 1; - model->flags.transparent = data.transparent; + model->flags.transparent = data->batch_blend->textures.siz > 0; - model->root->visible = data.visible; - model->root->pos = v3f32_add(v3s32_to_f32(data.chunkp), center_offset); - model->root->scale = (v3f32) {0.0f, 0.0f, 0.0f}; + model->root->pos = v3f32_add(v3s32_to_f32(data->chunkp), center_offset); + model->root->scale = data->animate ? (v3f32) {0.0f, 0.0f, 0.0f} : (v3f32) {1.0f, 1.0f, 1.0f}; - if (data.visible) { - model_node_add_batch(model->root, data.batch); - model_node_add_batch(model->root, data.batch_transparent); - } else { - model_batch_free(data.batch); - model_batch_free(data.batch_transparent); - } + model_node_add_batch(model->root, data->batch); + model_node_add_batch(model->root, data->batch_blend); return model; } @@ -265,40 +263,108 @@ void terrain_gfx_update() void terrain_gfx_make_chunk_model(TerrainChunk *chunk) { + // type coersion TerrainChunkMeta *meta = chunk->extra; - pthread_mutex_lock(&chunk->mtx); - bool animate; + // lock model mutex + pthread_mutex_lock(&meta->mtx_model); + + // giving 10 arguments to a function is slow and unmaintainable, use pointer to struct instead + ChunkRenderData data = { + .chunk = chunk, + .meta = meta, + .chunkp = v3s32_scale(chunk->pos, CHUNK_SIZE), + .animate = false, + .batch = model_batch_create( + &model_shader, &terrain_vertex_layout, offsetof(TerrainVertex, textureIndex)), + .batch_blend = model_batch_create( + &model_shader, &terrain_vertex_layout, offsetof(TerrainVertex, textureIndex)), + .abort = false, + .grabbed = {false}, + .visible = false, + .remake_needed = false, + }; + + // animate if old animation hasn't finished (or this is the first model) if (meta->model) - animate = meta->model->callbacks.step ? true : false; + data.animate = meta->model->callbacks.step ? true : false; else - animate = !meta->has_model; + data.animate = !meta->has_model; + + // obtain own data lock + assert(pthread_rwlock_rdlock(&chunk->lock) == 0); - pthread_mutex_unlock(&chunk->mtx); + // clear dependencies, they are repopulated by calls to grab_neighbor + for (int i = 0; i < 6; i++) + meta->depends[i] = false; + + // render nodes + CHUNK_ITERATE { + // obtain changed state + pthread_rwlock_rdlock(&meta->lock_state); + data.abort = meta->state < CHUNK_STATE_CLEAN; + pthread_rwlock_unlock(&meta->lock_state); + + // abort if chunk has been changed + // just "break" won't work, the CHUNK_ITERATE macro is a nested loop + if (data.abort) + goto abort; + + // put vertex data into batches + render_node(&data, (v3s32) {x, y, z}); + + // abort if failed to grab a neighbor + if (data.abort) + goto abort; + } + abort: + + // release grabbed data locks + for (int i = 0; i < 6; i++) + if (data.grabbed[i]) + pthread_rwlock_unlock(&meta->neighbors[i]->lock); - bool depends[6] = {false}; - Model *model = create_chunk_model(chunk, animate, depends); + // release own data lock + pthread_rwlock_unlock(&chunk->lock); - pthread_mutex_lock(&chunk->mtx); + // only create model if we didn't abort + Model *model = data.abort ? NULL : create_chunk_model(&data); + // make sure to free batch mem if it wasn't fed into model + if (!model) { + model_batch_free(data.batch); + model_batch_free(data.batch_blend); + } + + // abort if chunk changed + if (data.abort) { + pthread_mutex_unlock(&meta->mtx_model); + return; + } + + // replace old model if (meta->model) { if (model) { + // copy animation callback model->callbacks.step = meta->model->callbacks.step; + // copy scale model->root->scale = meta->model->root->scale; model_node_transform(model->root); } + // if old model wasn't drawn in this frame yet, new model will be drawn instead meta->model->replace = model; meta->model->flags.delete = 1; } else if (model) { + // model will be drawn in next frame model_scene_add(model); + model_node_transform(model->root); } + // update pointers meta->model = model; meta->has_model = true; - for (int i = 0; i < 6; i++) - meta->depends[i] = depends[i]; - - pthread_mutex_unlock(&chunk->mtx); + // bye bye + pthread_mutex_unlock(&meta->mtx_model); } diff --git a/src/debug.sh b/src/debug.sh index 0dcf103..cc4cd54 100755 --- a/src/debug.sh +++ b/src/debug.sh @@ -1,6 +1,6 @@ #!/bin/bash -if ! make -j$(nproc) dragonblocks_server; then +if ! make -j$(nproc); then exit 1 fi diff --git a/src/server/database.c b/src/server/database.c index aa029c9..79d1e59 100644 --- a/src/server/database.c +++ b/src/server/database.c @@ -138,7 +138,7 @@ bool database_load_chunk(TerrainChunk *chunk) if (found) { TerrainChunkMeta *meta = chunk->extra; - meta->state = sqlite3_column_int(stmt, 0) ? CHUNK_READY : CHUNK_CREATED; + meta->state = sqlite3_column_int(stmt, 0) ? CHUNK_STATE_READY : CHUNK_STATE_CREATED; Blob data = {sqlite3_column_bytes(stmt, 1), (void *) sqlite3_column_blob(stmt, 1)}; Blob tgsb = {sqlite3_column_bytes(stmt, 2), (void *) sqlite3_column_blob(stmt, 2)}; @@ -170,7 +170,7 @@ void database_save_chunk(TerrainChunk *chunk) Blob tgsb = {0, NULL}; TerrainGenStageBuffer_write(&tgsb, &meta->tgsb); - sqlite3_bind_int(stmt, 2, meta->state > CHUNK_CREATED); + sqlite3_bind_int(stmt, 2, meta->state > CHUNK_STATE_CREATED); sqlite3_bind_blob(stmt, 3, data.data, data.siz, &free); sqlite3_bind_blob(stmt, 4, tgsb.data, tgsb.siz, &free); diff --git a/src/server/server_item.c b/src/server/server_item.c index a806591..70c4c67 100644 --- a/src/server/server_item.c +++ b/src/server/server_item.c @@ -1,3 +1,4 @@ +#include #include "node.h" #include "server/server_item.h" #include "server/server_node.h" @@ -9,24 +10,24 @@ static void use_dig(__attribute__((unused)) ServerPlayer *player, ItemStack *sta if (!pointed) return; - v3s32 off; - TerrainChunk *chunk = terrain_get_chunk_nodep(server_terrain, pos, &off, false); + v3s32 offset; + TerrainChunk *chunk = terrain_get_chunk_nodep(server_terrain, pos, &offset, CHUNK_MODE_PASSIVE); if (!chunk) return; TerrainChunkMeta *meta = chunk->extra; - terrain_lock_chunk(chunk); + assert(pthread_rwlock_wrlock(&chunk->lock) == 0); - TerrainNode *node = &chunk->data[off.x][off.y][off.z]; + TerrainNode *node = &chunk->data[offset.x][offset.y][offset.z]; if (!(node_def[node->type].dig_class & item_def[stack->type].dig_class)) { - pthread_mutex_unlock(&chunk->mtx); + pthread_rwlock_unlock(&chunk->lock); return; } *node = server_node_create(NODE_AIR); - meta->tgsb.raw.nodes[off.x][off.y][off.z] = STAGE_PLAYER; + meta->tgsb.raw.nodes[offset.x][offset.y][offset.z] = STAGE_PLAYER; - pthread_mutex_unlock(&chunk->mtx); + pthread_rwlock_unlock(&chunk->lock); server_terrain_lock_and_send_chunk(chunk); diff --git a/src/server/server_terrain.c b/src/server/server_terrain.c index 61ce30e..2851af0 100644 --- a/src/server/server_terrain.c +++ b/src/server/server_terrain.c @@ -1,4 +1,5 @@ #define _GNU_SOURCE // don't worry, GNU extensions are only used when available +#include #include #include #include @@ -74,7 +75,7 @@ static void terrain_gen_step() terrain_gen_chunk(chunk, &changed_chunks); pthread_mutex_lock(&meta->mtx); - meta->state = CHUNK_READY; + meta->state = CHUNK_STATE_READY; pthread_mutex_unlock(&meta->mtx); server_terrain_lock_and_send_chunks(&changed_chunks); @@ -110,7 +111,7 @@ static void generate_chunk(TerrainChunk *chunk) TerrainChunkMeta *meta = chunk->extra; - meta->state = CHUNK_GENERATING; + meta->state = CHUNK_STATE_GENERATING; queue_enq(&terrain_gen_tasks, chunk); } @@ -124,7 +125,7 @@ static void on_create_chunk(TerrainChunk *chunk) if (database_load_chunk(chunk)) { meta->data = terrain_serialize_chunk(server_terrain, chunk, &server_node_serialize_client); } else { - meta->state = CHUNK_CREATED; + meta->state = CHUNK_STATE_CREATED; meta->data = (Blob) {0, NULL}; CHUNK_ITERATE { @@ -147,15 +148,15 @@ static void on_delete_chunk(TerrainChunk *chunk) // callback for determining whether a chunk should be returned by terrain_get_chunk // hold back chunks that are not fully generated except when the create flag is set to true -static bool on_get_chunk(TerrainChunk *chunk, bool create) +static bool on_get_chunk(TerrainChunk *chunk, int mode) { - if (create) + if (mode == CHUNK_MODE_CREATE) return true; TerrainChunkMeta *meta = chunk->extra; pthread_mutex_lock(&meta->mtx); - bool ret = meta->state == CHUNK_READY; + bool ret = meta->state == CHUNK_STATE_READY; pthread_mutex_unlock(&meta->mtx); return ret; @@ -268,19 +269,19 @@ void server_terrain_deinit() void server_terrain_requested_chunk(ServerPlayer *player, v3s32 pos) { if (within_load_distance(player, pos, server_config.load_distance)) { - TerrainChunk *chunk = terrain_get_chunk(server_terrain, pos, true); + TerrainChunk *chunk = terrain_get_chunk(server_terrain, pos, CHUNK_MODE_CREATE); TerrainChunkMeta *meta = chunk->extra; pthread_mutex_lock(&meta->mtx); switch (meta->state) { - case CHUNK_CREATED: + case CHUNK_STATE_CREATED: generate_chunk(chunk); break; - case CHUNK_GENERATING: + case CHUNK_STATE_GENERATING: break; - case CHUNK_READY: + case CHUNK_STATE_READY: send_chunk_to_client(player, chunk); break; }; @@ -320,11 +321,11 @@ void server_terrain_prepare_spawn() if (interrupt.set) return; - TerrainChunk *chunk = terrain_get_chunk(server_terrain, (v3s32) {x, y, z}, true); + TerrainChunk *chunk = terrain_get_chunk(server_terrain, (v3s32) {x, y, z}, CHUNK_MODE_CREATE); TerrainChunkMeta *meta = chunk->extra; pthread_mutex_lock(&meta->mtx); - if (meta->state == CHUNK_CREATED) + if (meta->state == CHUNK_STATE_CREATED) generate_chunk(chunk); pthread_mutex_unlock(&meta->mtx); @@ -362,15 +363,15 @@ void server_terrain_prepare_spawn() void server_terrain_gen_node(v3s32 pos, TerrainNode node, TerrainGenStage new_tgs, List *changed_chunks) { v3s32 offset; - TerrainChunk *chunk = terrain_get_chunk_nodep(server_terrain, pos, &offset, true); + TerrainChunk *chunk = terrain_get_chunk_nodep(server_terrain, pos, &offset, CHUNK_MODE_CREATE); TerrainChunkMeta *meta = chunk->extra; - terrain_lock_chunk(chunk); + assert(pthread_rwlock_wrlock(&chunk->lock) == 0); u32 *tgs = &meta->tgsb.raw.nodes[offset.x][offset.y][offset.z]; if (new_tgs < *tgs) { - pthread_mutex_unlock(&chunk->mtx); + pthread_rwlock_unlock(&chunk->lock); server_node_delete(&node); return; } @@ -383,7 +384,7 @@ void server_terrain_gen_node(v3s32 pos, TerrainNode node, TerrainGenStage new_tg else server_terrain_send_chunk(chunk); - pthread_mutex_unlock(&chunk->mtx); + pthread_rwlock_unlock(&chunk->lock); } s32 server_terrain_spawn_height() @@ -398,18 +399,18 @@ void server_terrain_send_chunk(TerrainChunk *chunk) { TerrainChunkMeta *meta = chunk->extra; - if (meta->state == CHUNK_GENERATING) + if (meta->state == CHUNK_STATE_GENERATING) return; - terrain_lock_chunk(chunk); + assert(pthread_rwlock_rdlock(&chunk->lock) == 0); Blob_free(&meta->data); meta->data = terrain_serialize_chunk(server_terrain, chunk, &server_node_serialize_client); database_save_chunk(chunk); - pthread_mutex_unlock(&chunk->mtx); + pthread_rwlock_unlock(&chunk->lock); - if (meta->state == CHUNK_CREATED) + if (meta->state == CHUNK_STATE_CREATED) return; server_player_iterate(&send_chunk_to_client, chunk); diff --git a/src/server/server_terrain.h b/src/server/server_terrain.h index 62c0a11..4cd2ce3 100644 --- a/src/server/server_terrain.h +++ b/src/server/server_terrain.h @@ -8,9 +8,9 @@ #include "types.h" typedef enum { - CHUNK_CREATED, // chunk exists but was not yet generated - CHUNK_GENERATING, // currently generating in a seperate thread - CHUNK_READY, // generation finished + CHUNK_STATE_CREATED, // chunk exists but was not yet generated + CHUNK_STATE_GENERATING, // currently generating in a seperate thread + CHUNK_STATE_READY, // generation finished } TerrainChunkState; typedef enum { @@ -35,22 +35,22 @@ typedef struct { /* Locking conventions: - - chunk mutex protects chunk->data and meta->tgsb + - chunk lock protects chunk->data and meta->tgsb - meta mutex protects everything else in meta - - if both meta and chunk mutex are locked, meta must be locked first + - if both meta mutex and chunk are going to be locked, meta must be locked first - you may not lock multiple meta mutexes at once - - if multiple chunk mutexes are being locked at once, EDEADLK must be handled - - when locking a single chunk mtx, check return value and crash on failure (terrain_lock_chunk()) + - if multiple chunk locks are being obtained at once, EDEADLK must be handled + - when locking a single chunk, assert return value of zero After changing the data in a chunk: - 1. release chunk mtx + 1. release chunk lock 2. - if meta mutex is currently locked: use server_terrain_send_chunk - if meta mutex is not locked: use server_terrain_lock_and_send_chunk If an operation affects multiple nodes (potentially in multiple chunks): - create a list changed_chunks - - do job as normal, release individual chunk mutexes immediately after modifying their data + - do job as normal, release individual chunk locks immediately after modifying their data - use server_terrain_lock_and_send_chunks to clear the list Note: Unless changed_chunks is given to server_terrain_gen_node, it sends chunks automatically diff --git a/src/server/terrain_gen.c b/src/server/terrain_gen.c index b5ed209..b913167 100644 --- a/src/server/terrain_gen.c +++ b/src/server/terrain_gen.c @@ -1,3 +1,4 @@ +#include #include #include #include "environment.h" @@ -106,12 +107,12 @@ void terrain_gen_chunk(TerrainChunk *chunk, List *changed_chunks) } } - terrain_lock_chunk(chunk); + assert(pthread_rwlock_wrlock(&chunk->lock) == 0); if (meta->tgsb.raw.nodes[x][y][z] <= STAGE_TERRAIN) { chunk->data[x][y][z] = server_node_create(node); meta->tgsb.raw.nodes[x][y][z] = STAGE_TERRAIN; } - pthread_mutex_unlock(&chunk->mtx); + pthread_rwlock_unlock(&chunk->lock); } if (biome_def->after_row) diff --git a/src/server/tree_physics.c b/src/server/tree_physics.c index 03c6726..7db6a10 100644 --- a/src/server/tree_physics.c +++ b/src/server/tree_physics.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -42,7 +43,7 @@ static int cmp_chunk(const TerrainChunk *chunk, const v3s32 *pos) static void unlock_chunk(TerrainChunk *chunk) { - pthread_mutex_unlock(&chunk->mtx); + pthread_rwlock_unlock(&chunk->lock); } static void init_search_node(DepthSearchNode *search_node, CheckTreeArg *arg) @@ -55,7 +56,7 @@ static void init_search_node(DepthSearchNode *search_node, CheckTreeArg *arg) // if not found in cache, get it from server_terrain and lock it if (!chunk) { - chunk = terrain_get_chunk(server_terrain, chunkp, false); + chunk = terrain_get_chunk(server_terrain, chunkp, CHUNK_MODE_PASSIVE); // check if chunk is unloaded if (!chunk) { @@ -66,8 +67,8 @@ static void init_search_node(DepthSearchNode *search_node, CheckTreeArg *arg) return; } - // try to obtain the chunk mutex - int lock_err = pthread_mutex_lock(&chunk->mtx); + // try to obtain the chunk lock + int lock_err = pthread_rwlock_wrlock(&chunk->lock); // a deadlock might occur because of the order the chunks are locked if (lock_err == EDEADLK) { @@ -79,17 +80,11 @@ static void init_search_node(DepthSearchNode *search_node, CheckTreeArg *arg) // done return; - } else if (lock_err != 0) { - // a different error has occured while trying to obtain the lock - // this should never happen - - // print error message - fprintf(stderr, "[error] failed to lock terrain chunk mutex\n"); - - // exit program - abort(); } + // assert that no different error has occured while trying to obtain the lock + assert(lock_err == 0); + // insert chunk into cache tree_add(&arg->chunks, &chunk->pos, chunk, &cmp_chunk, NULL); } @@ -239,7 +234,7 @@ static bool check_tree(v3s32 root, Array *positions, Array *chunks) - select neighbor nodes that are leaves or wood - in every iteration, select only the nodes that belong to the same tree (have the same root) - - in every iteration, keep the mutexes of the chunks the selected nodes belong to locked + - in every iteration, keep the chunks the selected nodes belong to locked - skip nodes that don't match the currently selected root, process them in a later iteration */ void tree_physics_check(v3s32 center) @@ -276,7 +271,7 @@ void tree_physics_check(v3s32 center) // get chunk v3s32 offset; - TerrainChunk *chunk = terrain_get_chunk_nodep(server_terrain, pos, &offset, false); + TerrainChunk *chunk = terrain_get_chunk_nodep(server_terrain, pos, &offset, CHUNK_MODE_PASSIVE); if (!chunk) continue; @@ -284,8 +279,9 @@ void tree_physics_check(v3s32 center) bool locked_before = array_idx(&chunks, &chunk) != -1; // lock if not locked + // fixme: a deadlock can happen here lol if (!locked_before) - terrain_lock_chunk(chunk); + assert(pthread_rwlock_wrlock(&chunk->lock) == 0); // now that chunk is locked, actually get node TerrainNode *node = &chunk->data[offset.x][offset.y][offset.z]; @@ -313,10 +309,10 @@ void tree_physics_check(v3s32 center) // remember index was selected selected[i] = true; - // don't run rest of loop body: don't unlock chunk mutex + // don't run rest of loop body to not unlock chunk continue; } else { - // doesn't match selected root: mark as skipped + // if it doesn't match selected root, mark as skipped skipped = true; dirs[i] = false; } @@ -324,7 +320,7 @@ void tree_physics_check(v3s32 center) // only unlock if it wasn't locked before if (!locked_before) - pthread_mutex_unlock(&chunk->mtx); + pthread_rwlock_unlock(&chunk->lock); } if (selected_root) { diff --git a/src/terrain.c b/src/terrain.c index 9238b8e..5f7b928 100644 --- a/src/terrain.c +++ b/src/terrain.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -18,10 +19,7 @@ static TerrainChunk *allocate_chunk(v3s32 pos) chunk->level = pos.y; chunk->pos = pos; chunk->extra = NULL; - pthread_mutexattr_t attr; - pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); - pthread_mutex_init(&chunk->mtx, &attr); + pthread_rwlock_init(&chunk->lock, NULL); CHUNK_ITERATE chunk->data[x][y][z] = (TerrainNode) {NODE_UNKNOWN, NULL}; @@ -34,7 +32,7 @@ static void free_chunk(Terrain *terrain, TerrainChunk *chunk) if (terrain->callbacks.delete_node) CHUNK_ITERATE terrain->callbacks.delete_node(&chunk->data[x][y][z]); - pthread_mutex_destroy(&chunk->mtx); + pthread_rwlock_destroy(&chunk->lock); free(chunk); } @@ -53,9 +51,9 @@ static void delete_sector(TerrainSector *sector, Terrain *terrain) free(sector); } -static TerrainSector *get_sector(Terrain *terrain, v2s32 pos, bool create) +static TerrainSector *get_sector(Terrain *terrain, v2s32 pos, int mode) { - if (create) + if (mode == CHUNK_MODE_CREATE) pthread_rwlock_wrlock(&terrain->lock); else pthread_rwlock_rdlock(&terrain->lock); @@ -65,7 +63,7 @@ static TerrainSector *get_sector(Terrain *terrain, v2s32 pos, bool create) if (*loc) { sector = (*loc)->dat; - } else if (create) { + } else if (mode == CHUNK_MODE_CREATE) { sector = malloc(sizeof *sector); sector->pos = pos; tree_ini(§or->chunks); @@ -97,7 +95,7 @@ void terrain_delete(Terrain *terrain) free(terrain); } -TerrainChunk *terrain_get_chunk(Terrain *terrain, v3s32 pos, bool create) +TerrainChunk *terrain_get_chunk(Terrain *terrain, v3s32 pos, int mode) { TerrainChunk *cache = NULL; @@ -108,11 +106,11 @@ TerrainChunk *terrain_get_chunk(Terrain *terrain, v3s32 pos, bool create) if (cache && v3s32_equals(cache->pos, pos)) return cache; - TerrainSector *sector = get_sector(terrain, (v2s32) {pos.x, pos.z}, create); + TerrainSector *sector = get_sector(terrain, (v2s32) {pos.x, pos.z}, mode); if (!sector) return NULL; - if (create) + if (mode == CHUNK_MODE_CREATE) pthread_rwlock_wrlock(§or->lock); else pthread_rwlock_rdlock(§or->lock); @@ -123,14 +121,14 @@ TerrainChunk *terrain_get_chunk(Terrain *terrain, v3s32 pos, bool create) if (*loc) { chunk = (*loc)->dat; - if (terrain->callbacks.get_chunk && !terrain->callbacks.get_chunk(chunk, create)) { + if (terrain->callbacks.get_chunk && !terrain->callbacks.get_chunk(chunk, mode)) { chunk = NULL; } else { pthread_rwlock_wrlock(&terrain->cache_lock); terrain->cache = chunk; pthread_rwlock_unlock(&terrain->cache_lock); } - } else if (create) { + } else if (mode == CHUNK_MODE_CREATE) { tree_nmk(§or->chunks, loc, chunk = allocate_chunk(pos)); if (terrain->callbacks.create_chunk) @@ -142,9 +140,9 @@ TerrainChunk *terrain_get_chunk(Terrain *terrain, v3s32 pos, bool create) return chunk; } -TerrainChunk *terrain_get_chunk_nodep(Terrain *terrain, v3s32 nodep, v3s32 *offset, bool create) +TerrainChunk *terrain_get_chunk_nodep(Terrain *terrain, v3s32 nodep, v3s32 *offset, int mode) { - TerrainChunk *chunk = terrain_get_chunk(terrain, terrain_chunkp(nodep), create); + TerrainChunk *chunk = terrain_get_chunk(terrain, terrain_chunkp(nodep), mode); if (!chunk) return NULL; *offset = terrain_offset(nodep); @@ -193,6 +191,7 @@ bool terrain_deserialize_chunk(Terrain *terrain, TerrainChunk *chunk, Blob buffe chunk->data[x][y][z] = (TerrainNode) {NODE_AIR, NULL}; } + return true; } @@ -219,25 +218,16 @@ bool terrain_deserialize_chunk(Terrain *terrain, TerrainChunk *chunk, Blob buffe return success; } -void terrain_lock_chunk(TerrainChunk *chunk) -{ - if (pthread_mutex_lock(&chunk->mtx) == 0) - return; - - fprintf(stderr, "[error] failed to lock terrain chunk mutex\n"); - abort(); -} - TerrainNode terrain_get_node(Terrain *terrain, v3s32 pos) { v3s32 offset; - TerrainChunk *chunk = terrain_get_chunk_nodep(terrain, pos, &offset, false); + TerrainChunk *chunk = terrain_get_chunk_nodep(terrain, pos, &offset, CHUNK_MODE_PASSIVE); if (!chunk) return (TerrainNode) {COUNT_NODE, NULL}; - terrain_lock_chunk(chunk); + assert(pthread_rwlock_rdlock(&chunk->lock) == 0); TerrainNode node = chunk->data[offset.x][offset.y][offset.z]; - pthread_mutex_unlock(&chunk->mtx); + pthread_rwlock_unlock(&chunk->lock); return node; } diff --git a/src/terrain.h b/src/terrain.h index 54869e4..c6aea4e 100644 --- a/src/terrain.h +++ b/src/terrain.h @@ -13,17 +13,22 @@ for (s32 y = 0; y < CHUNK_SIZE; y++) \ for (s32 z = 0; z < CHUNK_SIZE; z++) +#define CHUNK_MODE_PASSIVE 0 +#define CHUNK_MODE_CREATE 1 + typedef struct TerrainNode { NodeType type; void *data; } TerrainNode; +typedef TerrainNode TerrainChunkData[CHUNK_SIZE][CHUNK_SIZE][CHUNK_SIZE]; + typedef struct { s32 level; v3s32 pos; - TerrainNode data[CHUNK_SIZE][CHUNK_SIZE][CHUNK_SIZE]; + TerrainChunkData data; void *extra; - pthread_mutex_t mtx; + pthread_rwlock_t lock; } TerrainChunk; typedef struct { @@ -34,7 +39,7 @@ typedef struct { struct { void (*create_chunk)(TerrainChunk *chunk); void (*delete_chunk)(TerrainChunk *chunk); - bool (*get_chunk)(TerrainChunk *chunk, bool create); + bool (*get_chunk)(TerrainChunk *chunk, int mode); void (*delete_node)(TerrainNode *node); } callbacks; } Terrain; @@ -42,16 +47,14 @@ typedef struct { Terrain *terrain_create(); void terrain_delete(Terrain *terrain); -TerrainChunk *terrain_get_chunk(Terrain *terrain, v3s32 pos, bool create); -TerrainChunk *terrain_get_chunk_nodep(Terrain *terrain, v3s32 node_pos, v3s32 *offset, bool create); +TerrainChunk *terrain_get_chunk(Terrain *terrain, v3s32 pos, int mode); +TerrainChunk *terrain_get_chunk_nodep(Terrain *terrain, v3s32 node_pos, v3s32 *offset, int mode); Blob terrain_serialize_chunk(Terrain *terrain, TerrainChunk *chunk, void (*callback)(TerrainNode *node, Blob *buffer)); bool terrain_deserialize_chunk(Terrain *terrain, TerrainChunk *chunk, Blob buffer, void (*callback)(TerrainNode *node, Blob buffer)); TerrainNode terrain_get_node(Terrain *terrain, v3s32 pos); -void terrain_lock_chunk(TerrainChunk *chunk); - v3s32 terrain_chunkp(v3s32 pos); v3s32 terrain_offset(v3s32 pos); -- 2.44.0