]> git.lizzy.rs Git - dragonblocks_alpha.git/blobdiff - src/client/client_terrain.c
Fix client terrain crashes and performance
[dragonblocks_alpha.git] / src / client / client_terrain.c
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);
 }