#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>
// 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;
}
// 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++;
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;
{
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
// 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
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) {
}
} 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);
}