]> git.lizzy.rs Git - dragonblocks_alpha.git/commitdiff
Fix client terrain crashes and performance
authorElias Fleckenstein <eliasfleckenstein@web.de>
Sun, 24 Apr 2022 11:30:56 +0000 (13:30 +0200)
committerElias Fleckenstein <eliasfleckenstein@web.de>
Sun, 24 Apr 2022 11:30:56 +0000 (13:30 +0200)
16 files changed:
src/client/client.c
src/client/client_auth.c
src/client/client_terrain.c
src/client/client_terrain.h
src/client/gl_debug.c
src/client/interact.c
src/client/terrain_gfx.c
src/debug.sh
src/server/database.c
src/server/server_item.c
src/server/server_terrain.c
src/server/server_terrain.h
src/server/terrain_gen.c
src/server/tree_physics.c
src/terrain.c
src/terrain.h

index a4e811d3f611d8cffbee6bc3dc2c89b0a8d599a3..667f6a2b14972debd3838175120ca1b451f930b2 100644 (file)
@@ -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;
index c0fa81af2c549e7a797d89240091da1692b97d5c..0d16cea66746f64f8325ce4b76fa4ed04ed8e666 100644 (file)
@@ -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;
index 9dde1094dfa7ff9093b68459813baddb0298d4bc..e8eae363b9a34f8f4c45a3a0ce1c72ac4d41410b 100644 (file)
@@ -1,4 +1,5 @@
 #define _GNU_SOURCE // don't worry, GNU extensions are only used when available
+#include <assert.h>
 #include <dragonstd/queue.h>
 #include <sched.h>
 #include <stdatomic.h>
@@ -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);
 }
index fc1c8204e048b5c343cce689f54db5d2e4fb78a2..262a3d50a676a621beef92da37663c38206c9991 100644 (file)
@@ -1,25 +1,43 @@
 #ifndef _CLIENT_TERRAIN_H_
 #define _CLIENT_TERRAIN_H_
 
+#include <stdatomic.h>
 #include <stdbool.h>
 #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
index cc75d2440c65a1a3efa770f10f944c6909eaaa8b..7aa163f6bf195ca5ce955b58ec2eb6f0298b939d 100644 (file)
@@ -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;
        }
 }
index 8048080bff2f7b222ebce3199f5e9b97179d6adc..2aefa7e155aa1d727b91ede2082051b61ede3eab 100644 (file)
@@ -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;
index bfe8757e29151126d2fa7911b48e9b379ad8a959..ff6ec70a71f6e591f4c61046efe8cb8ba4347c20 100644 (file)
@@ -1,3 +1,4 @@
+#include <assert.h>
 #include <asprintf/asprintf.h>
 #include <linmath.h/linmath.h>
 #include <stdio.h>
 #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);
 }
index 0dcf103ccbcf6b4d6748e898055486609b3413ff..cc4cd548b57b4b0dd28a97d689002d17fb8395bc 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/bash
 
-if ! make -j$(nproc) dragonblocks_server; then
+if ! make -j$(nproc); then
        exit 1
 fi
 
index aa029c9d06722a2fdf00c797247d3064b603193d..79d1e59e1e43cf0d19f1423f203c52c7ec8e09cd 100644 (file)
@@ -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);
 
index a8065915c3850e6391db2b8437085d2415c10285..70c4c67628f76610384431320499f98d05f0a667 100644 (file)
@@ -1,3 +1,4 @@
+#include <assert.h>
 #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);
 
index 61ce30e706888798aaf672a24be884aaee4066ee..2851af0245298c5233e0cc7a174c9a6996b8210c 100644 (file)
@@ -1,4 +1,5 @@
 #define _GNU_SOURCE // don't worry, GNU extensions are only used when available
+#include <assert.h>
 #include <dragonstd/queue.h>
 #include <features.h>
 #include <stdatomic.h>
@@ -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);
index 62c0a11c183a02adcbf832b4a3097f49cad50ad0..4cd2ce36eb4b3fb55c7639645c0c3baaec8726d7 100644 (file)
@@ -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
index b5ed20911b503d755a93a77c3697a66891a9da28..b9131674ca56aefe15983e20f334344a652100aa 100644 (file)
@@ -1,3 +1,4 @@
+#include <assert.h>
 #include <math.h>
 #include <stdlib.h>
 #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)
index 03c672682f9b16c8ed9c2c2c37ee716aaedb36a1..7db6a107c701ad05307abbe165e90fe565b44980 100644 (file)
@@ -1,3 +1,4 @@
+#include <assert.h>
 #include <dragonstd/array.h>
 #include <dragonstd/list.h>
 #include <dragonstd/tree.h>
@@ -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) {
index 9238b8e038f9b1d506d3b1a2b9f83470a1b6f6a7..5f7b92864f61c1285be7b39c123fb0eda7082c77 100644 (file)
@@ -1,3 +1,4 @@
+#include <assert.h>
 #include <math.h>
 #include <stdbool.h>
 #include <stdio.h>
@@ -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(&sector->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(&sector->lock);
        else
                pthread_rwlock_rdlock(&sector->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(&sector->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;
 }
index 54869e411c202dca2c7c5b1cf0041dcb3db12cfa..c6aea4e6487ea03d9f673e5c0d7434cf99055401 100644 (file)
        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);