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);
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;
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;
#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);
}
#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;
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
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;
}
}
mesh_destroy(&selection_mesh);
}
+#include "client/client_terrain.h"
+
void interact_tick()
{
bool old_exists = interact_pointed.exists;
+#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 = {
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;
}
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;
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);
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;
}
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);
}
#!/bin/bash
-if ! make -j$(nproc) dragonblocks_server; then
+if ! make -j$(nproc); then
exit 1
fi
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)};
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);
+#include <assert.h>
#include "node.h"
#include "server/server_item.h"
#include "server/server_node.h"
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);
#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>
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);
TerrainChunkMeta *meta = chunk->extra;
- meta->state = CHUNK_GENERATING;
+ meta->state = CHUNK_STATE_GENERATING;
queue_enq(&terrain_gen_tasks, 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 {
// 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;
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;
};
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);
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;
}
else
server_terrain_send_chunk(chunk);
- pthread_mutex_unlock(&chunk->mtx);
+ pthread_rwlock_unlock(&chunk->lock);
}
s32 server_terrain_spawn_height()
{
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);
#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 {
/*
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
+#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include "environment.h"
}
}
- 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)
+#include <assert.h>
#include <dragonstd/array.h>
#include <dragonstd/list.h>
#include <dragonstd/tree.h>
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)
// 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) {
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) {
// 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);
}
- 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)
// 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;
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];
// 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;
}
// only unlock if it wasn't locked before
if (!locked_before)
- pthread_mutex_unlock(&chunk->mtx);
+ pthread_rwlock_unlock(&chunk->lock);
}
if (selected_root) {
+#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
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};
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);
}
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);
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);
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;
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);
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)
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);
chunk->data[x][y][z] = (TerrainNode) {NODE_AIR, NULL};
}
+
return true;
}
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;
}
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 {
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;
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);