Trees break if they have no connection to ground

master
Elias Fleckenstein 2022-04-22 11:24:03 +02:00
parent 6fd40203d5
commit f4c0012041
No known key found for this signature in database
GPG Key ID: 06927A5199D6C9B2
41 changed files with 923 additions and 440 deletions

2
deps/dragonnet vendored

@ -1 +1 @@
Subproject commit 431e3bd29073d9c10ab3a853116860ba83ed1c5e
Subproject commit 9ab2148e1b8d03dab816bbba6209794663b31576

2
deps/dragonstd vendored

@ -1 +1 @@
Subproject commit 8878e826fa3e7b1231e652fc13d11c7a61629d13
Subproject commit a3a50433ffe0674291d4c5e9d2247cca7f691faa

View File

@ -3,13 +3,13 @@ name neck pos 0 1.35 0
name head pos 0 0.225 0 scale 0.45 0.45 0.45 cube head
name eyes pos 0 0 +0.5
name chest pos 0 1.0125 0 scale 0.48 0.675 0.225 cube chest
name arm_left pos -0.36 1.35 0 scale -1 1 1 clockwise
name arm_left pos +0.36 1.35 0
pos 0 -0.3375 0 scale 0.24 0.675 0.225 cube arm
name hand pos 0 -0.585 0 scale 0.0625 0.0625 0.0625 rot 90 0 0
name arm_right pos +0.36 1.35 0
name arm_right pos -0.36 1.35 0 scale -1 1 1 clockwise
pos 0 -0.3375 0 scale 0.24 0.675 0.225 cube arm
name hand pos 0 -0.585 0 scale 0.0625 0.0625 0.0625 rot 90 0 0
name leg_left pos -0.12 0.675 0 scale -1 1 1 clockwise
name leg_left pos +0.12 0.675 0
pos 0 -0.3375 0 scale 0.24 0.675 0.225 cube leg
name leg_right pos +0.12 0.675 0
name leg_right pos -0.12 0.675 0 scale -1 1 1 clockwise
pos 0 -0.3375 0 scale 0.24 0.675 0.225 cube leg

View File

@ -1,4 +1,4 @@
#!/bin/bash
./dragonblocks_server "[::1]:4000" &
echo "singleplayer" | ./dragonblocks "[::1]:4000"
pkill -P $$
pkill -P $$ -9

View File

@ -78,6 +78,7 @@ set(COMMON_SOURCES
config.c
day.c
environment.c
facedir.c
interrupt.c
item.c
node.c
@ -103,7 +104,6 @@ add_executable(dragonblocks
client/cube.c
client/debug_menu.c
client/facecache.c
client/facedir.c
client/font.c
client/frustum.c
client/game.c
@ -144,10 +144,12 @@ add_executable(dragonblocks_server
server/server.c
server/server_config.c
server/server_item.c
server/server_node.c
server/server_player.c
server/server_terrain.c
server/terrain_gen.c
server/trees.c
server/tree.c
server/tree_physics.c
server/voxel_depth_search.c
server/voxel_procedural.c
)
@ -159,7 +161,7 @@ target_link_libraries(dragonblocks_server
# Version
add_custom_target(version
COMMAND ${CMAKE_COMMAND} -DBINARY_DIR=${CMAKE_CURRENT_BINARY_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/version.cmake
COMMAND ${CMAKE_COMMAND} -DBINARY_DIR=${CMAKE_CURRENT_BINARY_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/version.cmake
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
)

View File

@ -8,6 +8,7 @@
#include "client/client.h"
#include "client/client_auth.h"
#include "client/client_inventory.h"
#include "client/client_node.h"
#include "client/client_player.h"
#include "client/client_terrain.h"
#include "client/debug_menu.h"
@ -89,7 +90,7 @@ static void on_ToClientChunk(__attribute__((unused)) DragonnetPeer *peer, ToClie
{
TerrainChunk *chunk = terrain_get_chunk(client_terrain, pkt->pos, true);
terrain_deserialize_chunk(chunk, pkt->data);
terrain_deserialize_chunk(client_terrain, chunk, pkt->data, &client_node_deserialize);
((TerrainChunkMeta *) chunk->extra)->empty = (pkt->data.siz == 0);
client_terrain_chunk_received(chunk);
}

View File

@ -8,6 +8,7 @@
struct ClientAuth client_auth;
#include <string.h>
static void auth_loop()
{
while (!interrupt.set) switch (client_auth.state) {
@ -15,8 +16,11 @@ 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;

View File

@ -1,3 +1,4 @@
#include <stdlib.h>
#include "client/client.h"
#include "client/client_node.h"
#include "color.h"
@ -38,7 +39,7 @@ static void render_color(NodeArgsRender *args)
args->vertex.color = ((ColorData *) args->node->data)->color;
}
ClientNodeDef client_node_def[NODE_UNLOADED] = {
ClientNodeDef client_node_def[COUNT_NODE] = {
// unknown
{
.tiles = TILES_SIMPLE(RESSOURCE_PATH "textures/unknown.png"),
@ -215,7 +216,7 @@ ClientNodeDef client_node_def[NODE_UNLOADED] = {
void client_node_init()
{
for (NodeType node = 0; node < NODE_UNLOADED; node++) {
for (NodeType node = 0; node < COUNT_NODE; node++) {
ClientNodeDef *def = &client_node_def[node];
if (def->visibility != VISIBILITY_NONE) {
@ -235,3 +236,27 @@ void client_node_init()
}
}
}
void client_node_delete(TerrainNode *node)
{
switch (node->type) {
NODES_TREE
free(node->data);
break;
default:
break;
}
}
void client_node_deserialize(TerrainNode *node, Blob buffer)
{
switch (node->type) {
NODES_TREE
ColorData_read(&buffer, node->data = malloc(sizeof(ColorData)));
break;
default:
break;
}
}

View File

@ -36,4 +36,7 @@ typedef struct {
extern ClientNodeDef client_node_def[];
void client_node_init();
void client_node_delete(TerrainNode *node);
void client_node_deserialize(TerrainNode *node, Blob buffer);
#endif // _CLIENT_NODE_H_

View File

@ -144,7 +144,7 @@ static void local_on_add(ClientEntity *entity)
if (player_entity) {
fprintf(stderr, "[error] attempt to re-add localplayer entity\n");
exit(EXIT_FAILURE);
abort();
}
on_add(entity);

View File

@ -6,13 +6,14 @@
#include <stdlib.h>
#include <pthread.h>
#include "client/client.h"
#include "client/facedir.h"
#include "client/facecache.h"
#include "client/client_config.h"
#include "client/client_node.h"
#include "client/client_player.h"
#include "client/client_terrain.h"
#include "client/debug_menu.h"
#include "client/terrain_gfx.h"
#include "facedir.h"
#define MAX_REQUESTS 4
@ -77,7 +78,7 @@ static void sync_step()
return;
}
v3s32 center = terrain_node_to_chunk_pos((v3s32) {player_pos.x, player_pos.y, player_pos.z}, NULL);
v3s32 center = terrain_chunkp((v3s32) {player_pos.x, player_pos.y, player_pos.z});
u64 last_tick = tick++;
@ -196,11 +197,10 @@ static bool on_get_chunk(TerrainChunk *chunk, bool create)
void client_terrain_init()
{
client_terrain = terrain_create();
client_terrain->callbacks.create_chunk = &on_create_chunk;
client_terrain->callbacks.delete_chunk = &on_delete_chunk;
client_terrain->callbacks.get_chunk = &on_get_chunk;
client_terrain->callbacks.set_node = NULL;
client_terrain->callbacks.after_set_node = NULL;
client_terrain->callbacks.create_chunk = &on_create_chunk;
client_terrain->callbacks.delete_chunk = &on_delete_chunk;
client_terrain->callbacks.get_chunk = &on_get_chunk;
client_terrain->callbacks.delete_node = &client_node_delete;
cancel = false;
queue_ini(&meshgen_tasks);

View File

@ -11,7 +11,7 @@ bool raycast(v3f64 pos, v3f64 dir, f64 len, v3s32 *node_pos, NodeType *node)
*node = terrain_get_node(client_terrain,
*node_pos = (v3s32) {floor(pos.x + 0.5), floor(pos.y + 0.5), floor(pos.z + 0.5)}).type;
if (*node == NODE_UNLOADED)
if (*node == COUNT_NODE)
return false;
if (client_node_def[*node].pointable)

View File

@ -6,12 +6,12 @@
#include "client/client_node.h"
#include "client/client_terrain.h"
#include "client/cube.h"
#include "client/facedir.h"
#include "client/frustum.h"
#include "client/gl_debug.h"
#include "client/light.h"
#include "client/shader.h"
#include "client/terrain_gfx.h"
#include "facedir.h"
typedef struct {
bool visible;
@ -60,7 +60,7 @@ static inline bool show_face(NodeType self, NodeType nbr)
return nbr != self;
case VISIBILITY_SOLID:
return nbr != NODE_UNLOADED && client_node_def[nbr].visibility != VISIBILITY_SOLID;
return nbr != COUNT_NODE && client_node_def[nbr].visibility != VISIBILITY_SOLID;
default: // impossible
break;
@ -99,7 +99,7 @@ static inline void render_node(ChunkRenderData *data, v3s32 offset)
data->tried_nbrs[args.f] = true;
}
NodeType nbr_node = NODE_UNLOADED;
NodeType nbr_node = COUNT_NODE;
if (nbr_chunk)
nbr_node = nbr_chunk->data
[(nbr_offset.x + CHUNK_SIZE) % CHUNK_SIZE]

View File

@ -64,7 +64,7 @@ Texture *texture_load(char *path, bool mipmap)
&texture->width, &texture->height, &texture->channels, 0);
if (!data) {
fprintf(stderr, "[error] failed to load texture %s\n", path);
exit(EXIT_FAILURE);
abort();
}
texture_upload(texture, data, GL_RGBA, mipmap);
@ -123,7 +123,7 @@ Texture *texture_load_cubemap(char *path, bool bilinear_filter)
if (!(faces[i].data = stbi_load(filename,
&faces[i].width, &faces[i].height, &faces[i].channels, 0))) {
fprintf(stderr, "[error] failed to load texture %s\n", filename);
exit(EXIT_FAILURE);
abort();
}
size = least_common_multiple(size, faces[i].width);

View File

@ -1,6 +1,6 @@
#!/bin/bash
if ! make -j$(nproc); then
if ! make -j$(nproc) dragonblocks_server; then
exit 1
fi
@ -19,10 +19,10 @@ define hook-stop
quit
end
end
break gl_error
"
echo "$COMMON
break gl_error
run \"[::1]:4000\" < $DEBUG_DIR/name
" > $DEBUG_DIR/client_script

View File

@ -1,4 +1,4 @@
#include "client/facedir.h"
#include "facedir.h"
v3s32 facedir[6] = {
{+0, +0, -1},

View File

@ -3,147 +3,85 @@
#include "terrain.h"
#include "types.h"
NodeDef node_def[NODE_UNLOADED] = {
NodeDef node_def[COUNT_NODE] = {
// unknown
{
.solid = true,
.data_size = 0,
.dig_class = DIG_NONE,
.callbacks = {NULL},
},
// air
{
.solid = false,
.data_size = 0,
.dig_class = DIG_NONE,
.callbacks = {NULL},
},
// grass
{
.solid = true,
.data_size = 0,
.dig_class = DIG_DIRT,
.callbacks = {NULL},
},
// dirt
{
.solid = true,
.data_size = 0,
.dig_class = DIG_DIRT,
.callbacks = {NULL},
},
// stone
{
.solid = true,
.data_size = 0,
.dig_class = DIG_STONE,
.callbacks = {NULL},
},
// snow
{
.solid = true,
.data_size = 0,
.dig_class = DIG_DIRT,
.callbacks = {NULL},
},
// oak wood
{
.solid = true,
.data_size = sizeof(ColorData),
.dig_class = DIG_WOOD,
.callbacks = {
.create = NULL,
.delete = NULL,
.serialize = (void *) &ColorData_write,
.deserialize = (void *) &ColorData_read,
},
},
// oak leaves
{
.solid = true,
.data_size = sizeof(ColorData),
.dig_class = DIG_LEAVES,
.callbacks = {
.create = NULL,
.delete = NULL,
.serialize = (void *) &ColorData_write,
.deserialize = (void *) &ColorData_read,
},
},
// pine wood
{
.solid = true,
.data_size = sizeof(ColorData),
.dig_class = DIG_WOOD,
.callbacks = {
.create = NULL,
.delete = NULL,
.serialize = (void *) &ColorData_write,
.deserialize = (void *) &ColorData_read,
},
},
// pine leaves
{
.solid = true,
.data_size = sizeof(ColorData),
.dig_class = DIG_LEAVES,
.callbacks = {
.create = NULL,
.delete = NULL,
.serialize = (void *) &ColorData_write,
.deserialize = (void *) &ColorData_read,
},
},
// palm wood
{
.solid = true,
.data_size = sizeof(ColorData),
.dig_class = DIG_WOOD,
.callbacks = {
.create = NULL,
.delete = NULL,
.serialize = (void *) &ColorData_write,
.deserialize = (void *) &ColorData_read,
},
},
// palm leaves
{
.solid = true,
.data_size = sizeof(ColorData),
.dig_class = DIG_LEAVES,
.callbacks = {
.create = NULL,
.delete = NULL,
.serialize = (void *) &ColorData_write,
.deserialize = (void *) &ColorData_read,
},
},
// sand
{
.solid = true,
.data_size = 0,
.dig_class = DIG_DIRT,
.callbacks = {NULL},
},
// water
{
.solid = false,
.data_size = 0,
.dig_class = DIG_NONE,
.callbacks = {NULL},
},
// lava
{
.solid = false,
.data_size = 0,
.dig_class = DIG_NONE,
.callbacks = {NULL},
},
// vulcanostone
{
.solid = true,
.data_size = 0,
.dig_class = DIG_STONE,
.callbacks = {NULL},
},
};

View File

@ -5,6 +5,8 @@
#include <stddef.h>
#include "types.h"
#define NODES_TREE case NODE_OAK_WOOD: case NODE_OAK_LEAVES: case NODE_PINE_WOOD: case NODE_PINE_LEAVES: case NODE_PALM_WOOD: case NODE_PALM_LEAVES:
typedef enum {
NODE_UNKNOWN, // Used for unknown nodes received from server (caused by outdated clients)
NODE_AIR,
@ -22,21 +24,14 @@ typedef enum {
NODE_WATER,
NODE_LAVA,
NODE_VULCANO_STONE,
NODE_UNLOADED, // Used for nodes in unloaded chunks
COUNT_NODE, // Used for nodes in unloaded chunks
} NodeType;
struct TerrainNode;
typedef struct {
bool solid;
size_t data_size;
unsigned long dig_class;
struct {
void (*create)(struct TerrainNode *node);
void (*delete)(struct TerrainNode *node);
void (*serialize)(Blob *buffer, void *data);
void (*deserialize)(Blob *buffer, void *data);
} callbacks;
} NodeDef;
extern NodeDef node_def[];

View File

@ -20,7 +20,7 @@ static aabb3s32 round_box(aabb3f64 box)
static bool is_solid(Terrain *terrain, s32 x, s32 y, s32 z)
{
NodeType node = terrain_get_node(terrain, (v3s32) {x, y, z}).type;
return node == NODE_UNLOADED || node_def[node].solid;
return node == COUNT_NODE || node_def[node].solid;
}
bool physics_ground(Terrain *terrain, bool collide, aabb3f32 box, v3f64 *pos, v3f64 *vel)

View File

@ -232,14 +232,16 @@ static bool is_boulder(s32 diff, v3s32 pos)
smooth3d(U32(pos.x) / 16.0, U32(pos.y) / 12.0, U32(pos.z) / 16.0, 0, seed + OFFSET_BOULDER) > 0.8;
}
static DepthSearchNodeType boulder_get_node_type(v3s32 pos)
static void boulder_search_callback(DepthSearchNode *node)
{
s32 diff = pos.y - terrain_gen_get_base_height((v2s32) {pos.x, pos.z});
s32 diff = node->pos.y - terrain_gen_get_base_height((v2s32) {node->pos.x, node->pos.z});
if (diff <= 0)
return DEPTH_SEARCH_TARGET;
return is_boulder(diff, pos) ? DEPTH_SEARCH_PATH : DEPTH_SEARCH_BLOCK;
node->type = DEPTH_SEARCH_TARGET;
else if (is_boulder(diff, node->pos))
node->type = DEPTH_SEARCH_PATH;
else
node->type = DEPTH_SEARCH_BLOCK;
}
static NodeType generate_hills(BiomeArgsGenerate *args)
@ -247,7 +249,7 @@ static NodeType generate_hills(BiomeArgsGenerate *args)
HillsChunkData *chunk_data = args->chunk_data;
if (is_boulder(args->diff, args->pos) && (args->diff <= 0 || voxel_depth_search(args->pos,
&boulder_get_node_type,
(void *) &boulder_search_callback, NULL,
&chunk_data->boulder_success[args->offset.x][args->offset.y][args->offset.z],
&chunk_data->boulder_visit)))
return NODE_STONE;

View File

@ -6,6 +6,7 @@
#include <time.h>
#include "day.h"
#include "server/database.h"
#include "server/server_node.h"
#include "server/server_terrain.h"
#include "perlin.h"
@ -138,12 +139,13 @@ bool database_load_chunk(TerrainChunk *chunk)
TerrainChunkMeta *meta = chunk->extra;
meta->state = sqlite3_column_int(stmt, 0) ? CHUNK_READY : CHUNK_CREATED;
Blob_read( &(Blob) {sqlite3_column_bytes(stmt, 1), (void *) sqlite3_column_blob(stmt, 1)}, &meta->data);
TerrainGenStageBuffer_read(&(Blob) {sqlite3_column_bytes(stmt, 2), (void *) sqlite3_column_blob(stmt, 2)}, &meta->tgsb);
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)};
if (!terrain_deserialize_chunk(chunk, meta->data)) {
TerrainGenStageBuffer_read(&tgsb, &meta->tgsb);
if (!terrain_deserialize_chunk(server_terrain, chunk, data, &server_node_deserialize)) {
fprintf(stderr, "[error] failed deserializing chunk at (%d, %d, %d)\n", chunk->pos.x, chunk->pos.y, chunk->pos.z);
exit(EXIT_FAILURE);
abort();
}
} else if (rc != SQLITE_DONE) {
print_chunk_error(chunk, "loading");
@ -163,8 +165,7 @@ void database_save_chunk(TerrainChunk *chunk)
TerrainChunkMeta *meta = chunk->extra;
Blob data = {0, NULL};
Blob_write(&data, &meta->data);
Blob data = terrain_serialize_chunk(server_terrain, chunk, &server_node_serialize);
Blob tgsb = {0, NULL};
TerrainGenStageBuffer_write(&tgsb, &meta->tgsb);

View File

@ -1,6 +1,7 @@
#include <stdio.h>
#include <stdlib.h>
#include "server/schematic.h"
#include "server/server_node.h"
#include "terrain.h"
void schematic_load(List *schematic, const char *path, SchematicMapping *mappings, size_t num_mappings)
@ -26,7 +27,6 @@ void schematic_load(List *schematic, const char *path, SchematicMapping *mapping
continue;
SchematicNode *node = malloc(sizeof *node);
node->data = (Blob) {0, NULL};
v3s32 color;
if (sscanf(line, "%d %d %d %2x%2x%2x",
@ -52,14 +52,12 @@ void schematic_load(List *schematic, const char *path, SchematicMapping *mapping
continue;
}
node->type = mapping->type;
if (mapping->use_color)
ColorData_write(&node->data, &(ColorData) {{
node->node = mapping->use_color
? server_node_create_color(mapping->type, (v3f32) {
(f32) color.x / 0xFF,
(f32) color.y / 0xFF,
(f32) color.z / 0xFF,
}});
}) : server_node_create(mapping->type);
list_apd(schematic, node);
}
@ -77,14 +75,14 @@ void schematic_place(List *schematic, v3s32 pos, TerrainGenStage tgs, List *chan
server_terrain_gen_node(
v3s32_add(pos, node->pos),
terrain_node_create(node->type, node->data),
server_node_copy(node->node),
tgs, changed_chunks);
}
}
static void delete_schematic_node(SchematicNode *node)
{
Blob_free(&node->data);
server_node_delete(&node->node);
free(node);
}

View File

@ -16,8 +16,7 @@ typedef struct {
typedef struct {
v3s32 pos;
NodeType type;
Blob data;
TerrainNode node;
} SchematicNode;
void schematic_load(List *schematic, const char *path, SchematicMapping *mappings, size_t num_mappings);

View File

@ -1,23 +1,38 @@
#include "node.h"
#include "server/server_item.h"
#include "server/server_node.h"
#include "server/server_terrain.h"
#include "server/tree_physics.h"
static void use_dig(__attribute__((unused)) ServerPlayer *player, ItemStack *stack, bool pointed, v3s32 pos)
{
if (!pointed)
return;
NodeType node = terrain_get_node(server_terrain, pos).type;
if (node == NODE_UNLOADED)
v3s32 off;
TerrainChunk *chunk = terrain_get_chunk_nodep(server_terrain, pos, &off, false);
if (!chunk)
return;
TerrainChunkMeta *meta = chunk->extra;
terrain_lock_chunk(chunk);
if (!(node_def[node].dig_class & item_def[stack->type].dig_class))
TerrainNode *node = &chunk->data[off.x][off.y][off.z];
if (!(node_def[node->type].dig_class & item_def[stack->type].dig_class)) {
pthread_mutex_unlock(&chunk->mtx);
return;
}
terrain_set_node(server_terrain, pos,
terrain_node_create(NODE_AIR, (Blob) {0, NULL}),
false, NULL);
*node = server_node_create(NODE_AIR);
meta->tgsb.raw.nodes[off.x][off.y][off.z] = STAGE_PLAYER;
pthread_mutex_unlock(&chunk->mtx);
server_terrain_lock_and_send_chunk(chunk);
// destroy trees if they have no connection to ground
// todo: run in seperate thread to not block client connection
tree_physics_check(pos);
}
ServerItemDef server_item_def[COUNT_ITEM] = {

90
src/server/server_node.c Normal file
View File

@ -0,0 +1,90 @@
#include <stdlib.h>
#include "server/server_node.h"
TerrainNode server_node_create(NodeType type)
{
switch (type) {
NODES_TREE
return server_node_create_tree(type, (TreeData) {{0.5f, 0.5f, 0.5f}, 0, {0, 0, 0}});
default:
return (TerrainNode) {type, NULL};
}
}
TerrainNode server_node_create_color(NodeType type, v3f32 color)
{
switch (type) {
NODES_TREE
return server_node_create_tree(type, (TreeData) {color, 0, {0, 0, 0}});
default:
return server_node_create(type);
}
}
TerrainNode server_node_create_tree(NodeType type, TreeData data)
{
TerrainNode node = {type, malloc(sizeof data)};
*((TreeData *) node.data) = data;
return node;
}
TerrainNode server_node_copy(TerrainNode node)
{
switch (node.type) {
NODES_TREE
return server_node_create_tree(node.type, *((TreeData *) node.data));
default:
return server_node_create(node.type);
}
}
void server_node_delete(TerrainNode *node)
{
switch (node->type) {
NODES_TREE
free(node->data);
break;
default:
break;
}
}
void server_node_deserialize(TerrainNode *node, Blob buffer)
{
switch (node->type) {
NODES_TREE
TreeData_read(&buffer, node->data = malloc(sizeof(TreeData)));
break;
default:
break;
}
}
void server_node_serialize(TerrainNode *node, Blob *buffer)
{
switch (node->type) {
NODES_TREE
TreeData_write(buffer, node->data);
break;
default:
break;
}
}
void server_node_serialize_client(TerrainNode *node, Blob *buffer)
{
switch (node->type) {
NODES_TREE
ColorData_write(buffer, &(ColorData) {((TreeData *) node->data)->color});
break;
default:
break;
}
}

16
src/server/server_node.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef _SERVER_NODE_H_
#define _SERVER_NODE_H_
#include "terrain.h"
#include "types.h"
TerrainNode server_node_create(NodeType type);
TerrainNode server_node_create_color(NodeType type, v3f32 color);
TerrainNode server_node_create_tree(NodeType type, TreeData data);
TerrainNode server_node_copy(TerrainNode node);
void server_node_delete(TerrainNode *node);
void server_node_deserialize(TerrainNode *node, Blob buffer);
void server_node_serialize(TerrainNode *node, Blob *buffer);
void server_node_serialize_client(TerrainNode *node, Blob *buffer);
#endif

View File

@ -12,6 +12,7 @@
#include "server/database.h"
#include "server/schematic.h"
#include "server/server_config.h"
#include "server/server_node.h"
#include "server/server_terrain.h"
#include "server/terrain_gen.h"
#include "terrain.h"
@ -32,7 +33,7 @@ static pthread_mutex_t mtx_num_gen_chunks; // lock to protect the above
static bool within_load_distance(ServerPlayer *player, v3s32 cpos, u32 dist)
{
pthread_rwlock_rdlock(&player->lock_pos);
v3s32 ppos = terrain_node_to_chunk_pos((v3s32) {player->pos.x, player->pos.y, player->pos.z}, NULL);
v3s32 ppos = terrain_chunkp((v3s32) {player->pos.x, player->pos.y, player->pos.z});
pthread_rwlock_unlock(&player->lock_pos);
return abs(ppos.x - cpos.x) <= (s32) dist
@ -41,7 +42,7 @@ static bool within_load_distance(ServerPlayer *player, v3s32 cpos, u32 dist)
}
// send a chunk to a client and reset chunk request
static void send_chunk(ServerPlayer *player, TerrainChunk *chunk)
static void send_chunk_to_client(ServerPlayer *player, TerrainChunk *chunk)
{
if (!within_load_distance(player, chunk->pos, server_config.load_distance))
return;
@ -55,34 +56,6 @@ static void send_chunk(ServerPlayer *player, TerrainChunk *chunk)
pthread_rwlock_unlock(&player->lock_peer);
}
// send chunk to near clients
// chunk mutex has to be locked
static void send_chunk_to_near(TerrainChunk *chunk)
{
TerrainChunkMeta *meta = chunk->extra;
if (meta->state == CHUNK_GENERATING)
return;
Blob_free(&meta->data);
meta->data = terrain_serialize_chunk(chunk);
database_save_chunk(chunk);
if (meta->state == CHUNK_CREATED)
return;
server_player_iterate(&send_chunk, chunk);
}
// Iterator for sending changed chunks to near clients
static void iterator_send_chunk_to_near(TerrainChunk *chunk)
{
pthread_mutex_lock(&chunk->mtx);
send_chunk_to_near(chunk);
pthread_mutex_unlock(&chunk->mtx);
}
// me when the
static void terrain_gen_step()
{
@ -100,11 +73,11 @@ static void terrain_gen_step()
terrain_gen_chunk(chunk, &changed_chunks);
pthread_mutex_lock(&chunk->mtx);
pthread_mutex_lock(&meta->mtx);
meta->state = CHUNK_READY;
pthread_mutex_unlock(&chunk->mtx);
pthread_mutex_unlock(&meta->mtx);
list_clr(&changed_chunks, &iterator_send_chunk_to_near, NULL, NULL);
server_terrain_lock_and_send_chunks(&changed_chunks);
pthread_mutex_lock(&mtx_num_gen_chunks);
num_gen_chunks--;
@ -123,7 +96,7 @@ static void *terrain_gen_thread()
terrain_gen_step();
return NULL;
}
}
// enqueue chunk
static void generate_chunk(TerrainChunk *chunk)
@ -136,25 +109,26 @@ static void generate_chunk(TerrainChunk *chunk)
pthread_mutex_unlock(&mtx_num_gen_chunks);
TerrainChunkMeta *meta = chunk->extra;
meta->state = CHUNK_GENERATING;
queue_enq(&terrain_gen_tasks, chunk);
}
// terrain callbacks
// note: all these functions require the chunk mutex to be locked, which is always the case when a terrain callback is invoked
// callback for initializing a newly created chunk
// load chunk from database or initialize state, tgstage buffer and data
static void on_create_chunk(TerrainChunk *chunk)
{
TerrainChunkMeta *meta = chunk->extra = malloc(sizeof *meta);
pthread_mutex_init(&meta->mtx, NULL);
if (!database_load_chunk(chunk)) {
if (database_load_chunk(chunk)) {
meta->data = terrain_serialize_chunk(server_terrain, chunk, &server_node_serialize_client);
} else {
meta->state = CHUNK_CREATED;
meta->data = (Blob) {0, NULL};
CHUNK_ITERATE {
chunk->data[x][y][z] = terrain_node_create(NODE_AIR, (Blob) {0, NULL});
chunk->data[x][y][z] = server_node_create(NODE_AIR);
meta->tgsb.raw.nodes[x][y][z] = STAGE_VOID;
}
}
@ -165,6 +139,7 @@ static void on_create_chunk(TerrainChunk *chunk)
static void on_delete_chunk(TerrainChunk *chunk)
{
TerrainChunkMeta *meta = chunk->extra;
pthread_mutex_destroy(&meta->mtx);
Blob_free(&meta->data);
free(meta);
@ -174,42 +149,16 @@ static void on_delete_chunk(TerrainChunk *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)
{
TerrainChunkMeta *meta = chunk->extra;
if (meta->state < CHUNK_READY && !create)
return false;
return true;
}
// callback for deciding whether a set_node call succeeds or not
// reject set_node calls that try to override nodes placed by later terraingen stages, else update tgs buffer - also make sure chunk is inserted into changed_chunks list
static bool on_set_node(TerrainChunk *chunk, v3u8 offset, __attribute__((unused)) TerrainNode *node, void *_arg)
{
TerrainSetNodeArg *arg = _arg;
TerrainGenStage new_tgs = arg ? arg->tgs : STAGE_PLAYER;
TerrainGenStage *tgs = &((TerrainChunkMeta *) chunk->extra)->
tgsb.raw.nodes[offset.x][offset.y][offset.z];
if (new_tgs >= *tgs) {
*tgs = new_tgs;
if (arg)
list_add(arg->changed_chunks, chunk, chunk, &cmp_ref, NULL);
if (create)
return true;
}
return false;
}
TerrainChunkMeta *meta = chunk->extra;
pthread_mutex_lock(&meta->mtx);
// callback for when chunk content changes
// send chunk to near clients if not part of terrain generation
static void on_after_set_node(TerrainChunk *chunk, __attribute__((unused)) v3u8 offset, void *arg)
{
if (!arg)
send_chunk_to_near(chunk);
bool ret = meta->state == CHUNK_READY;
pthread_mutex_unlock(&meta->mtx);
return ret;
}
// generate a hut for new players to spawn in
@ -248,8 +197,10 @@ static void generate_spawn_hut()
{+4, +1},
};
Blob wood_color = {0, NULL};
ColorData_write(&wood_color, &(ColorData) {{(f32) 0x7d / 0xff, (f32) 0x54 / 0xff, (f32) 0x35 / 0xff}});
v3f32 wood_color = {
(f32) 0x7d / 0xff,
(f32) 0x54 / 0xff,
(f32) 0x35 / 0xff};
for (int i = 0; i < 6; i++) {
for (s32 y = spawn_height - 1;; y--) {
@ -266,17 +217,14 @@ static void generate_spawn_hut()
if (node_def[node].solid)
break;
server_terrain_gen_node(pos,
terrain_node_create(node == NODE_LAVA
? NODE_VULCANO_STONE
: NODE_OAK_WOOD,
wood_color),
server_terrain_gen_node(pos, node == NODE_LAVA
? server_node_create(NODE_VULCANO_STONE)
: server_node_create_color(NODE_OAK_WOOD, wood_color),
STAGE_PLAYER, &changed_chunks);
}
}
Blob_free(&wood_color);
list_clr(&changed_chunks, &iterator_send_chunk_to_near, NULL, NULL);
server_terrain_lock_and_send_chunks(&changed_chunks);
}
// public functions
@ -288,8 +236,7 @@ void server_terrain_init()
server_terrain->callbacks.create_chunk = &on_create_chunk;
server_terrain->callbacks.delete_chunk = &on_delete_chunk;
server_terrain->callbacks.get_chunk = &on_get_chunk;
server_terrain->callbacks.set_node = &on_set_node;
server_terrain->callbacks.after_set_node = &on_after_set_node;
server_terrain->callbacks.delete_node = &server_node_delete;
cancel = false;
queue_ini(&terrain_gen_tasks);
@ -322,10 +269,9 @@ 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);
pthread_mutex_lock(&chunk->mtx);
TerrainChunkMeta *meta = chunk->extra;
pthread_mutex_lock(&meta->mtx);
switch (meta->state) {
case CHUNK_CREATED:
generate_chunk(chunk);
@ -335,10 +281,11 @@ void server_terrain_requested_chunk(ServerPlayer *player, v3s32 pos)
break;
case CHUNK_READY:
send_chunk(player, chunk);
send_chunk_to_client(player, chunk);
break;
};
pthread_mutex_unlock(&chunk->mtx);
pthread_mutex_unlock(&meta->mtx);
}
}
@ -374,11 +321,12 @@ void server_terrain_prepare_spawn()
return;
TerrainChunk *chunk = terrain_get_chunk(server_terrain, (v3s32) {x, y, z}, true);
TerrainChunkMeta *meta = chunk->extra;
pthread_mutex_lock(&chunk->mtx);
if (((TerrainChunkMeta *) chunk->extra)->state == CHUNK_CREATED)
pthread_mutex_lock(&meta->mtx);
if (meta->state == CHUNK_CREATED)
generate_chunk(chunk);
pthread_mutex_unlock(&chunk->mtx);
pthread_mutex_unlock(&meta->mtx);
update_percentage();
}
@ -411,14 +359,31 @@ void server_terrain_prepare_spawn()
}
}
void server_terrain_gen_node(v3s32 pos, TerrainNode node, TerrainGenStage tgs, List *changed_chunks)
void server_terrain_gen_node(v3s32 pos, TerrainNode node, TerrainGenStage new_tgs, List *changed_chunks)
{
TerrainSetNodeArg arg = {
.tgs = tgs,
.changed_chunks = changed_chunks,
};
v3s32 offset;
TerrainChunk *chunk = terrain_get_chunk_nodep(server_terrain, pos, &offset, true);
TerrainChunkMeta *meta = chunk->extra;
terrain_set_node(server_terrain, pos, node, true, &arg);
terrain_lock_chunk(chunk);
u32 *tgs = &meta->tgsb.raw.nodes[offset.x][offset.y][offset.z];
if (new_tgs < *tgs) {
pthread_mutex_unlock(&chunk->mtx);
server_node_delete(&node);
return;
}
*tgs = new_tgs;
chunk->data[offset.x][offset.y][offset.z] = node;
if (changed_chunks)
list_add(changed_chunks, chunk, chunk, &cmp_ref, NULL);
else
server_terrain_send_chunk(chunk);
pthread_mutex_unlock(&chunk->mtx);
}
s32 server_terrain_spawn_height()
@ -426,3 +391,40 @@ s32 server_terrain_spawn_height()
// wow, so useful!
return spawn_height;
}
// send chunk to near clients
// meta mutex has to be locked
void server_terrain_send_chunk(TerrainChunk *chunk)
{
TerrainChunkMeta *meta = chunk->extra;
if (meta->state == CHUNK_GENERATING)
return;
terrain_lock_chunk(chunk);
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);
if (meta->state == CHUNK_CREATED)
return;
server_player_iterate(&send_chunk_to_client, chunk);
}
void server_terrain_lock_and_send_chunk(TerrainChunk *chunk)
{
TerrainChunkMeta *meta = chunk->extra;
pthread_mutex_lock(&meta->mtx);
server_terrain_send_chunk(chunk);
pthread_mutex_unlock(&meta->mtx);
}
void server_terrain_lock_and_send_chunks(List *changed_chunks)
{
list_clr(changed_chunks, &server_terrain_lock_and_send_chunk, NULL, NULL);
}

View File

@ -1,6 +1,7 @@
#ifndef _SERVER_TERRAIN_H_
#define _SERVER_TERRAIN_H_
#include <dragonstd/list.h>
#include <pthread.h>
#include "server/server_player.h"
#include "terrain.h"
@ -15,8 +16,7 @@ typedef enum {
typedef enum {
STAGE_VOID, // initial air, can be overridden by anything
STAGE_TERRAIN, // basic terrain, can be overridden by anything except the void
STAGE_BOULDERS, // boulders, replace terrain
STAGE_TREES, // trees replace boulders
STAGE_TREES, // trees replace terrain
STAGE_PLAYER, // player-placed nodes or things placed after terrain generation
} TerrainGenStage;
@ -26,19 +26,56 @@ typedef struct {
} TerrainSetNodeArg;
typedef struct {
pthread_mutex_t mtx; // UwU please hit me senpai
Blob data; // the big cum
TerrainChunkState state; // generation state of the chunk
pthread_t gen_thread; // thread that is generating chunk
TerrainGenStageBuffer tgsb; // buffer to make sure terraingen only overrides things it should
} TerrainChunkMeta; // OMG META VERSE WEB 3.0 VIRTUAL REALITY
extern Terrain *server_terrain; // terrain object, data is stored here
/*
Locking conventions:
- chunk mutex 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
- 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())
void server_terrain_init(); // called on server startup
void server_terrain_deinit(); // called on server shutdown
void server_terrain_requested_chunk(ServerPlayer *player, v3s32 pos); // handle chunk request from client (thread safe)
void server_terrain_prepare_spawn(); // prepare spawn region
void server_terrain_gen_node(v3s32 pos, TerrainNode node, TerrainGenStage tgs, List *changed_chunks); // set node with terraingen stage
s32 server_terrain_spawn_height(); // get the spawn height because idk
After changing the data in a chunk:
1. release chunk mtx
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
- 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
*/
// terrain object, data is stored here
extern Terrain *server_terrain;
// called on server startup
void server_terrain_init();
// called on server shutdown
void server_terrain_deinit();
// handle chunk request from client (thread safe)
void server_terrain_requested_chunk(ServerPlayer *player, v3s32 pos);
// prepare spawn region
void server_terrain_prepare_spawn();
// set node with terraingen stage
void server_terrain_gen_node(v3s32 pos, TerrainNode node, TerrainGenStage new_tgs, List *changed_chunks);
// get the spawn height because idk
s32 server_terrain_spawn_height();
// when bit chunkus changes
void server_terrain_send_chunk(TerrainChunk *chunk);
// lock and send
void server_terrain_lock_and_send_chunk(TerrainChunk *chunk);
// lock and send multiple chunks at once
void server_terrain_lock_and_send_chunks(List *list);
#endif // _SERVER_TERRAIN_H_

View File

@ -3,9 +3,10 @@
#include "environment.h"
#include "perlin.h"
#include "server/biomes.h"
#include "server/server_node.h"
#include "server/server_terrain.h"
#include "server/terrain_gen.h"
#include "server/trees.h"
#include "server/tree.h"
s32 terrain_gen_get_base_height(v2s32 pos)
{
@ -105,9 +106,9 @@ void terrain_gen_chunk(TerrainChunk *chunk, List *changed_chunks)
}
}
pthread_mutex_lock(&chunk->mtx);
terrain_lock_chunk(chunk);
if (meta->tgsb.raw.nodes[x][y][z] <= STAGE_TERRAIN) {
chunk->data[x][y][z] = terrain_node_create(node, (Blob) {0, NULL});
chunk->data[x][y][z] = server_node_create(node);
meta->tgsb.raw.nodes[x][y][z] = STAGE_TERRAIN;
}
pthread_mutex_unlock(&chunk->mtx);

View File

@ -1,9 +1,24 @@
#include <stdlib.h>
#include "server/biomes.h"
#include "server/server_node.h"
#include "server/server_terrain.h"
#include "server/trees.h"
#include "server/tree.h"
#include "server/voxel_procedural.h"
typedef struct {
NodeType type;
v3s32 root;
} ProceduralTreeArg;
static TerrainNode create_tree_node(__attribute__((unused)) v3s32 pos, v3f32 color, ProceduralTreeArg *arg)
{
return server_node_create_tree(arg->type, (TreeData) {
.color = color,
.has_root = 1,
.root = arg->root,
});
}
// oak
static bool oak_condition(TreeArgsCondition *args)
@ -11,13 +26,14 @@ static bool oak_condition(TreeArgsCondition *args)
return args->biome == BIOME_HILLS;
}
static void oak_tree_leaf(VoxelProcedural *proc)
static void oak_tree_leaf(VoxelProcedural *proc, v3s32 root)
{
if (!voxel_procedural_is_alive(proc))
return;
voxel_procedural_push(proc);
voxel_procedural_cube(proc, NODE_OAK_LEAVES, true);
voxel_procedural_cube(proc, (void *) &create_tree_node,
&(ProceduralTreeArg) {NODE_OAK_LEAVES, root});
voxel_procedural_pop(proc);
voxel_procedural_push(proc);
@ -27,11 +43,11 @@ static void oak_tree_leaf(VoxelProcedural *proc)
voxel_procedural_sz(proc, 0.8f);
voxel_procedural_ry(proc, 25.0f);
voxel_procedural_x(proc, 0.4f);
oak_tree_leaf(proc);
oak_tree_leaf(proc, root);
voxel_procedural_pop(proc);
}
static void oak_tree_top(VoxelProcedural *proc)
static void oak_tree_top(VoxelProcedural *proc, v3s32 root)
{
if (!voxel_procedural_is_alive(proc))
return;
@ -48,13 +64,13 @@ static void oak_tree_top(VoxelProcedural *proc)
voxel_procedural_sat(proc, 0.5f);
voxel_procedural_hue(proc, voxel_procedural_random(proc, 60.0f, 20.0f));
voxel_procedural_ry(proc, -45.0f);
oak_tree_leaf(proc);
oak_tree_leaf(proc, root);
voxel_procedural_pop(proc);
}
voxel_procedural_pop(proc);
}
static void oak_tree_part(VoxelProcedural *proc, f32 n)
static void oak_tree_part(VoxelProcedural *proc, v3s32 root, f32 n)
{
if (!voxel_procedural_is_alive(proc))
return;
@ -69,21 +85,22 @@ static void oak_tree_part(VoxelProcedural *proc, f32 n)
voxel_procedural_s(proc, 4.0f);
voxel_procedural_x(proc, 0.1f);
voxel_procedural_light(proc, voxel_procedural_random(proc, 0.0f, 0.1f));
voxel_procedural_cylinder(proc, NODE_OAK_WOOD, true);
voxel_procedural_cylinder(proc, (void *) &create_tree_node,
&(ProceduralTreeArg) {NODE_OAK_WOOD, root});
voxel_procedural_pop(proc);
if (i == (int) (n - 2.0f)) {
voxel_procedural_push(proc);
oak_tree_top(proc);
oak_tree_top(proc, root);
voxel_procedural_pop(proc);
}
}
voxel_procedural_pop(proc);
}
static void oak_tree(v3s32 pos, List *changed_chunks)
static void oak_tree(v3s32 root, List *changed_chunks)
{
VoxelProcedural *proc = voxel_procedural_create(changed_chunks, STAGE_TREES, pos);
VoxelProcedural *proc = voxel_procedural_create(changed_chunks, STAGE_TREES, root);
voxel_procedural_hue(proc, 40.0f);
voxel_procedural_light(proc, -0.5f);
@ -97,7 +114,7 @@ static void oak_tree(v3s32 pos, List *changed_chunks)
voxel_procedural_push(proc);
voxel_procedural_y(proc, 0.5f);
voxel_procedural_light(proc, voxel_procedural_random(proc, -0.3f, 0.05f));
oak_tree_part(proc, n);
oak_tree_part(proc, root, n);
voxel_procedural_pop(proc);
}
voxel_procedural_pop(proc);
@ -128,12 +145,10 @@ static void pine_tree(v3s32 pos, List *changed_chunks)
s32 dir = (noise3d(tree_pos.x, tree_pos.y, tree_pos.z, 0, seed + OFFSET_PINETREE_BRANCH_DIR) * 0.5 + 0.5) * 4.0;
for (v3s32 branch_pos = tree_pos; branch_length > 0; branch_length--, branch_pos = v3s32_add(branch_pos, dirs[dir]))
server_terrain_gen_node(branch_pos,
terrain_node_create(NODE_PINE_WOOD, (Blob) {0, NULL}),
server_terrain_gen_node(branch_pos, server_node_create(NODE_PINE_WOOD),
STAGE_TREES, changed_chunks);
server_terrain_gen_node(tree_pos,
terrain_node_create(NODE_PINE_WOOD, (Blob) {0, NULL}),
server_terrain_gen_node(tree_pos, server_node_create(NODE_PINE_WOOD),
STAGE_TREES, changed_chunks);
}
}
@ -147,24 +162,26 @@ static bool palm_condition(TreeArgsCondition *args)
&& ocean_get_node_at((v3s32) {args->pos.x, args->pos.y - 1, args->pos.z}, 0, args->row_data) == NODE_SAND;
}
static void palm_branch(VoxelProcedural *proc)
static void palm_branch(VoxelProcedural *proc, v3s32 root)
{
if (!voxel_procedural_is_alive(proc))
return;
voxel_procedural_cube(proc, NODE_PALM_LEAVES, true);
voxel_procedural_cube(proc, (void *) &create_tree_node,
&(ProceduralTreeArg) {NODE_PALM_LEAVES, root});
voxel_procedural_push(proc);
voxel_procedural_z(proc, 0.5f);
voxel_procedural_s(proc, 0.8f);
voxel_procedural_rx(proc, voxel_procedural_random(proc, 20.0f, 4.0f));
voxel_procedural_z(proc, 0.5f);
palm_branch(proc);
palm_branch(proc, root);
voxel_procedural_pop(proc);
}
static void palm_tree(v3s32 pos, List *changed_chunks)
static void palm_tree(v3s32 root, List *changed_chunks)
{
VoxelProcedural *proc = voxel_procedural_create(changed_chunks, STAGE_TREES, (v3s32) {pos.x, pos.y - 1, pos.z});
VoxelProcedural *proc = voxel_procedural_create(changed_chunks, STAGE_TREES, (v3s32) {root.x, root.y - 1, root.z});
f32 s = voxel_procedural_random(proc, 8.0f, 2.0f);
@ -175,7 +192,8 @@ static void palm_tree(v3s32 pos, List *changed_chunks)
voxel_procedural_s(proc, 1.0f);
voxel_procedural_light(proc, voxel_procedural_random(proc, -0.8f, 0.1f));
voxel_procedural_sat(proc, 0.5f);
voxel_procedural_cube(proc, NODE_PALM_WOOD, true);
voxel_procedural_cube(proc, (void *) &create_tree_node,
&(ProceduralTreeArg) {NODE_PALM_WOOD, root});
voxel_procedural_pop(proc);
}
voxel_procedural_pop(proc);
@ -193,7 +211,7 @@ static void palm_tree(v3s32 pos, List *changed_chunks)
voxel_procedural_light(proc, voxel_procedural_random(proc, 0.0f, 0.3f));
voxel_procedural_rx(proc, 90.0f);
voxel_procedural_s(proc, 2.0f);
palm_branch(proc);
palm_branch(proc, root);
voxel_procedural_pop(proc);
}
voxel_procedural_pop(proc);

View File

@ -1,5 +1,5 @@
#ifndef _TREES_H_
#define _TREES_H_
#ifndef _TREE_H_
#define _TREE_H_
#include <dragonstd/list.h>
#include <stdbool.h>
@ -32,4 +32,4 @@ typedef struct {
extern TreeDef tree_def[];
#endif // _TREES_H_
#endif // _TREE_H_

352
src/server/tree_physics.c Normal file
View File

@ -0,0 +1,352 @@
#include <dragonstd/array.h>
#include <dragonstd/list.h>
#include <dragonstd/tree.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "facedir.h"
#include "server/server_node.h"
#include "server/server_terrain.h"
#include "server/tree_physics.h"
#include "server/voxel_depth_search.h"
typedef struct {
v3s32 root;
bool deadlock;
Tree chunks;
} CheckTreeArg;
typedef struct {
TerrainChunk *chunk;
TerrainNode *node;
u32 *tgs;
} CheckTreeSearchNodeMeta;
static inline bool is_tree_with_root(TerrainNode *node)
{
switch (node->type) {
NODES_TREE
return ((TreeData *) node->data)->has_root;
default:
return false;
}
}
static int cmp_chunk(const TerrainChunk *chunk, const v3s32 *pos)
{
return v3s32_cmp(&chunk->pos, pos);
}
static void unlock_chunk(TerrainChunk *chunk)
{
pthread_mutex_unlock(&chunk->mtx);
}
static void init_search_node(DepthSearchNode *search_node, CheckTreeArg *arg)
{
// first, get chunk position and offset
v3s32 chunkp = terrain_chunkp(search_node->pos);
// check for chunk in cache
TerrainChunk *chunk = tree_get(&arg->chunks, &chunkp, &cmp_chunk, NULL);
// if not found in cache, get it from server_terrain and lock it
if (!chunk) {
chunk = terrain_get_chunk(server_terrain, chunkp, false);
// check if chunk is unloaded
if (!chunk) {
// if chunk is unloaded, don't remove the tree, it might have a connection to ground
search_node->type = DEPTH_SEARCH_TARGET;
// done
return;
}
// try to obtain the chunk mutex
int lock_err = pthread_mutex_lock(&chunk->mtx);
// a deadlock might occur because of the order the chunks are locked
if (lock_err == EDEADLK) {
// notify caller deadlock has occured
arg->deadlock = true;
// finish search directly
search_node->type = DEPTH_SEARCH_TARGET;
// 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();
}
// insert chunk into cache
tree_add(&arg->chunks, &chunk->pos, chunk, &cmp_chunk, NULL);
}
// get node offset
v3s32 offset = terrain_offset(search_node->pos);
// type coersion for easier access
TerrainChunkMeta *meta = chunk->extra;
// pointer to node and generation stage
TerrainNode *node = &chunk->data[offset.x][offset.y][offset.z];
u32 *tgs = &meta->tgsb.raw.nodes[offset.x][offset.y][offset.z];
// type coersion for easier access
TreeData *data = node->data;
// have we found terrain?
if (*tgs == STAGE_TERRAIN && node->type != NODE_AIR) {
// if we've reached the target, set search node type accordingly
search_node->type = DEPTH_SEARCH_TARGET;
} else if (is_tree_with_root(node) && v3s32_equals(arg->root, data->root)) {
// if node is part of our tree, continue search
search_node->type = DEPTH_SEARCH_PATH;
// allocate meta storage
CheckTreeSearchNodeMeta *search_meta = search_node->extra = malloc(sizeof *search_meta);
// store chunk, node and stage pointer for later
search_meta->chunk = chunk;
search_meta->node = node;
search_meta->tgs = tgs;
} else {
// otherwise, this is a roadblock
search_node->type = DEPTH_SEARCH_BLOCK;
}
}
static void free_search_node(DepthSearchNode *node)
{
if (node->extra)
free(node->extra);
free(node);
}
static void destroy_search_node(DepthSearchNode *node, List *changed_chunks)
{
if (node->type == DEPTH_SEARCH_PATH && !(*node->success)) {
// this is a tree/leaves node without connection to ground
CheckTreeSearchNodeMeta *meta = node->extra;
// overwrite node and generation stage
*meta->node = server_node_create(NODE_AIR);
*meta->tgs = STAGE_PLAYER;
// flag chunk as changed
list_add(changed_chunks, meta->chunk, meta->chunk, &cmp_ref, NULL);
}
free_search_node(node);
}
/*
Check whether all positions (that are part of the same tree) still are connected to the ground.
Destroy any tree parts without ground connection.
The advantage of grouping them together is that they can use the same search cache.
*/
static bool check_tree(v3s32 root, Array *positions, Array *chunks)
{
CheckTreeArg arg;
// inform depth search callbacks about root of tree (to only match nodes that belong to it)
arg.root = root;
// output parameter to prevent deadlocks
arg.deadlock = false;
// cache chunks, to accelerate lookup and prevent locking them twice
tree_ini(&arg.chunks);
// add the chunks the starting points are in to the chunk cache
for (size_t i = 0; i < chunks->siz; i++) {
TerrainChunk *chunk = ((TerrainChunk **) chunks->ptr)[i];
tree_add(&arg.chunks, &chunk->pos, chunk, &cmp_chunk, NULL);
}
// nodes that have been visited
// serves as search cache and contains all tree nodes, to remove them if no ground found
Tree visit;
tree_ini(&visit);
// success means ground has been found
// true if ground has been found for all positions
bool success_all = true;
// individual buffer for each start position (required by depth search algo)
bool success_buf[positions->siz];
// iterate over start positions
for (size_t i = 0; i < positions->siz; i++) {
success_buf[i] = false;
// call depth search algorithm to collect positions and find ground
if (!voxel_depth_search(((v3s32 *) positions->ptr)[i], (void *) &init_search_node, &arg,
&success_buf[i], &visit))
success_all = false;
// immediately stop if there was a deadlock
if (arg.deadlock)
break;
}
if (success_all || arg.deadlock) {
// ground has been found for all parts (or a deadlock was detected)
// if ground has been found for all, there is no need to pass more complex callback
tree_clr(&visit, &free_search_node, NULL, NULL, 0);
// unlock grabbed chunks
tree_clr(&arg.chunks, &unlock_chunk, NULL, NULL, 0);
// return false if there was a deadlock - caller will reinitiate search
return !arg.deadlock;
}
// keep track of changed chunks
List changed_chunks;
list_ini(&changed_chunks);
// some or all positions have no connection to ground, pass callback to destroy nodes without
tree_clr(&visit, &destroy_search_node, &changed_chunks, NULL, 0);
// now, unlock all the chunks (before sending some of them)
tree_clr(&arg.chunks, &unlock_chunk, NULL, NULL, 0);
// send changed chunks
server_terrain_lock_and_send_chunks(&changed_chunks);
// done
return true;
}
/*
To be called after a node has been removed.
For all neigbors that are part of a tree, check whether the tree now still has connection to
the ground, destroy tree (partly) otherwise.
- 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
- skip nodes that don't match the currently selected root, process them in a later iteration
*/
void tree_physics_check(v3s32 center)
{
// remember directions that have been processed
bool dirs[6] = {false};
bool skipped;
do {
skipped = false;
// the first node that has a root will initialize these variables
bool selected_root = false;
v3s32 root;
// remember selected positions and their associated locked chunks
Array positions, chunks;
array_ini(&positions, sizeof(v3s32), 5);
array_ini(&chunks, sizeof(TerrainChunk *), 5);
// remember indices of positions selected in this iteration
bool selected[6] = {false};
for (int i = 0; i < 6; i++) {
// we already processed this direction
if (dirs[i])
continue;
// this may change back to false if we skip
dirs[i] = true;
// facedir contains offsets to neighbor nodes
v3s32 pos = v3s32_add(center, facedir[i]);
// get chunk
v3s32 offset;
TerrainChunk *chunk = terrain_get_chunk_nodep(server_terrain, pos, &offset, false);
if (!chunk)
continue;
// check if chunk is already locked
bool locked_before = array_idx(&chunks, &chunk) != -1;
// lock if not locked
if (!locked_before)
terrain_lock_chunk(chunk);
// now that chunk is locked, actually get node
TerrainNode *node = &chunk->data[offset.x][offset.y][offset.z];
// check whether we're dealing with a tree node that has a root
if (is_tree_with_root(node)) {
// type coersion for easier access
TreeData *data = node->data;
// select root and initialize variables
if (!selected_root) {
selected_root = true;
root = data->root;
}
// check whether root matches
if (v3s32_equals(root, data->root)) {
// remember position
array_apd(&positions, &pos);
// remember chunk - unless it's already on the list
if (!locked_before)
array_apd(&chunks, &chunk);
// remember index was selected
selected[i] = true;
// don't run rest of loop body: don't unlock chunk mutex
continue;
} else {
// 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);
}
if (selected_root) {
// run depth search
if (!check_tree(root, &positions, &chunks)) {
// a return value of false means a deadlock occured (should be very rare)
printf("[verbose] tree_physics detected deadlock (this not an issue, but should not happen frequently)\n");
// sleep for 50ms to hopefully resolve the conflict
nanosleep(&(struct timespec) {0, 50e6}, NULL);
// invalidate faces that were selected in this iteration
for (int i = 0; i < 6; i++)
if (selected[i])
dirs[i] = false;
}
// free memory
array_clr(&positions);
array_clr(&chunks);
}
// repeat until all directions have been processed
} while (skipped);
}

View File

@ -0,0 +1,8 @@
#ifndef _TREE_PHYSICS_H_
#define _TREE_PHYSICS_H_
#include "types.h"
void tree_physics_check(v3s32 pos);
#endif // _TREE_PHYSICS_H_

View File

@ -15,7 +15,7 @@ static int cmp_depth_search_node(const DepthSearchNode *node, const v3s32 *pos)
return v3s32_cmp(&node->pos, pos);
}
bool voxel_depth_search(v3s32 pos, DepthSearchNodeType (*get_type)(v3s32 pos), bool *success, Tree *visit)
bool voxel_depth_search(v3s32 pos, void (*callback)(DepthSearchNode *node, void *arg), void *arg, bool *success, Tree *visit)
{
TreeNode **tree_node = tree_nfd(visit, &pos, &cmp_depth_search_node);
if (*tree_node)
@ -23,14 +23,15 @@ bool voxel_depth_search(v3s32 pos, DepthSearchNodeType (*get_type)(v3s32 pos), b
DepthSearchNode *node = malloc(sizeof *node);
tree_nmk(visit, tree_node, node);
node->type = get_type(pos);
node->pos = pos;
node->extra = NULL;
callback(node, arg);
if ((*(node->success = success) = (node->type == DEPTH_SEARCH_TARGET)))
return true;
if (node->type == DEPTH_SEARCH_PATH)
for (int i = 0; i < 6; i++)
if (voxel_depth_search(v3s32_add(pos, dirs[i]), get_type, success, visit))
if (voxel_depth_search(v3s32_add(pos, dirs[i]), callback, arg, success, visit))
return true;
return false;

View File

@ -8,15 +8,16 @@
typedef enum {
DEPTH_SEARCH_TARGET, // goal has been reached
DEPTH_SEARCH_PATH, // can used this as path
DEPTH_SEARCH_BLOCK // cannot use this as paths
DEPTH_SEARCH_BLOCK // cannot use this as path
} DepthSearchNodeType;
typedef struct {
v3s32 pos;
DepthSearchNodeType type;
bool *success;
void *extra;
} DepthSearchNode;
bool voxel_depth_search(v3s32 pos, DepthSearchNodeType (*get_type)(v3s32 pos), bool *success, Tree *visit);
bool voxel_depth_search(v3s32 pos, void (*callback)(DepthSearchNode *node, void *arg), void *arg, bool *success, Tree *visit);
#endif // _VOXEL_DEPTH_SEARCH_

View File

@ -171,11 +171,16 @@ bool voxel_procedural_is_alive(VoxelProcedural *proc)
VOXEL_PROCEDURAL_STATE(proc).scale[2] >= 1.0f;
}
void voxel_procedural_cube(VoxelProcedural *proc, NodeType node, bool use_color)
void voxel_procedural_cube(VoxelProcedural *proc, VoxelProceduralNode func, void *arg)
{
if (!voxel_procedural_is_alive(proc))
return;
v3f32 color = hsl_to_rgb((v3f32) {
VOXEL_PROCEDURAL_STATE(proc).h / 360.0,
VOXEL_PROCEDURAL_STATE(proc).s,
VOXEL_PROCEDURAL_STATE(proc).l});
vec4 base_corners[8] = {
{0.0f, 0.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 1.0f, 0.0f},
@ -221,30 +226,16 @@ void voxel_procedural_cube(VoxelProcedural *proc, NodeType node, bool use_color)
v[i] = floor(VOXEL_PROCEDURAL_STATE(proc).pos[i] + f + 0.5f);
}
Blob buffer = {0, NULL};
if (use_color)
ColorData_write(&buffer, &(ColorData) {hsl_to_rgb((v3f32) {
VOXEL_PROCEDURAL_STATE(proc).h / 360.0,
VOXEL_PROCEDURAL_STATE(proc).s,
VOXEL_PROCEDURAL_STATE(proc).l,
})});
server_terrain_gen_node(
v3s32_add(proc->pos, (v3s32) {v[0], v[2], v[1]}),
terrain_node_create(node, buffer),
proc->tgs,
proc->changed_chunks
);
Blob_free(&buffer);
v3s32 pos = v3s32_add(proc->pos, (v3s32) {v[0], v[2], v[1]});
server_terrain_gen_node(pos, func(pos, color, arg),
proc->tgs, proc->changed_chunks);
}
}
void voxel_procedural_cylinder(VoxelProcedural *proc, NodeType node, bool use_color)
void voxel_procedural_cylinder(VoxelProcedural *proc, VoxelProceduralNode func, void *arg)
{
voxel_procedural_cube(proc, node, use_color);
voxel_procedural_cube(proc, func, arg);
}
/*

View File

@ -24,6 +24,8 @@ typedef struct {
List state;
} VoxelProcedural;
typedef TerrainNode (*VoxelProceduralNode)(v3s32 pos, v3f32 color, void *arg);
VoxelProcedural *voxel_procedural_create(List *changed_chunks, TerrainGenStage tgs, v3s32 pos);
void voxel_procedural_delete(VoxelProcedural *proc);
void voxel_procedural_hue(VoxelProcedural *proc, f32 value);
@ -43,8 +45,8 @@ void voxel_procedural_s(VoxelProcedural *proc, f32 value);
void voxel_procedural_pop(VoxelProcedural *proc);
void voxel_procedural_push(VoxelProcedural *proc);
bool voxel_procedural_is_alive(VoxelProcedural *proc);
void voxel_procedural_cube(VoxelProcedural *proc, NodeType node, bool use_color);
void voxel_procedural_cylinder(VoxelProcedural *proc, NodeType node, bool use_color);
void voxel_procedural_cube(VoxelProcedural *proc, VoxelProceduralNode func, void *arg);
void voxel_procedural_cylinder(VoxelProcedural *proc, VoxelProceduralNode func, void *arg);
f32 voxel_procedural_random(VoxelProcedural *proc, f32 base, f32 vary);
#endif // _VOXEL_PROCEDURAL_H_

View File

@ -1,16 +1,49 @@
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "terrain.h"
typedef struct {
v2s32 pos;
Tree chunks;
pthread_rwlock_t lock;
} TerrainSector;
static TerrainChunk *allocate_chunk(v3s32 pos)
{
TerrainChunk *chunk = malloc(sizeof * chunk);
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);
CHUNK_ITERATE
chunk->data[x][y][z] = (TerrainNode) {NODE_UNKNOWN, NULL};
return chunk;
}
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);
free(chunk);
}
static void delete_chunk(TerrainChunk *chunk, Terrain *terrain)
{
if (terrain->callbacks.delete_chunk)
terrain->callbacks.delete_chunk(chunk);
terrain_free_chunk(chunk);
free_chunk(terrain, chunk);
}
static void delete_sector(TerrainSector *sector, Terrain *terrain)
@ -20,25 +53,7 @@ static void delete_sector(TerrainSector *sector, Terrain *terrain)
free(sector);
}
Terrain *terrain_create()
{
Terrain *terrain = malloc(sizeof *terrain);
tree_ini(&terrain->sectors);
pthread_rwlock_init(&terrain->lock, NULL);
terrain->cache = NULL;
pthread_rwlock_init(&terrain->cache_lock, NULL);
return terrain;
}
void terrain_delete(Terrain *terrain)
{
tree_clr(&terrain->sectors, &delete_sector, terrain, NULL, 0);
pthread_rwlock_destroy(&terrain->lock);
pthread_rwlock_destroy(&terrain->cache_lock);
free(terrain);
}
TerrainSector *terrain_get_sector(Terrain *terrain, v2s32 pos, bool create)
static TerrainSector *get_sector(Terrain *terrain, v2s32 pos, bool create)
{
if (create)
pthread_rwlock_wrlock(&terrain->lock);
@ -64,6 +79,24 @@ TerrainSector *terrain_get_sector(Terrain *terrain, v2s32 pos, bool create)
return sector;
}
Terrain *terrain_create()
{
Terrain *terrain = malloc(sizeof *terrain);
tree_ini(&terrain->sectors);
pthread_rwlock_init(&terrain->lock, NULL);
terrain->cache = NULL;
pthread_rwlock_init(&terrain->cache_lock, NULL);
return terrain;
}
void terrain_delete(Terrain *terrain)
{
tree_clr(&terrain->sectors, &delete_sector, terrain, NULL, 0);
pthread_rwlock_destroy(&terrain->lock);
pthread_rwlock_destroy(&terrain->cache_lock);
free(terrain);
}
TerrainChunk *terrain_get_chunk(Terrain *terrain, v3s32 pos, bool create)
{
TerrainChunk *cache = NULL;
@ -75,7 +108,7 @@ TerrainChunk *terrain_get_chunk(Terrain *terrain, v3s32 pos, bool create)
if (cache && v3s32_equals(cache->pos, pos))
return cache;
TerrainSector *sector = terrain_get_sector(terrain, (v2s32) {pos.x, pos.z}, create);
TerrainSector *sector = get_sector(terrain, (v2s32) {pos.x, pos.z}, create);
if (!sector)
return NULL;
@ -90,18 +123,15 @@ TerrainChunk *terrain_get_chunk(Terrain *terrain, v3s32 pos, bool create)
if (*loc) {
chunk = (*loc)->dat;
pthread_mutex_lock(&chunk->mtx);
if (terrain->callbacks.get_chunk && !terrain->callbacks.get_chunk(chunk, create)) {
pthread_mutex_unlock(&chunk->mtx);
chunk = NULL;
} else {
pthread_mutex_unlock(&chunk->mtx);
pthread_rwlock_wrlock(&terrain->cache_lock);
terrain->cache = chunk;
pthread_rwlock_unlock(&terrain->cache_lock);
}
} else if (create) {
tree_nmk(&sector->chunks, loc, chunk = terrain_allocate_chunk(pos));
tree_nmk(&sector->chunks, loc, chunk = allocate_chunk(pos));
if (terrain->callbacks.create_chunk)
terrain->callbacks.create_chunk(chunk);
@ -112,33 +142,16 @@ TerrainChunk *terrain_get_chunk(Terrain *terrain, v3s32 pos, bool create)
return chunk;
}
TerrainChunk *terrain_allocate_chunk(v3s32 pos)
TerrainChunk *terrain_get_chunk_nodep(Terrain *terrain, v3s32 nodep, v3s32 *offset, bool create)
{
TerrainChunk *chunk = malloc(sizeof * chunk);
chunk->level = pos.y;
chunk->pos = pos;
chunk->extra = NULL;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&chunk->mtx, &attr);
CHUNK_ITERATE
chunk->data[x][y][z] = terrain_node_create(NODE_UNKNOWN, (Blob) {0, NULL});
TerrainChunk *chunk = terrain_get_chunk(terrain, terrain_chunkp(nodep), create);
if (!chunk)
return NULL;
*offset = terrain_offset(nodep);
return chunk;
}
void terrain_free_chunk(TerrainChunk *chunk)
{
CHUNK_ITERATE
terrain_node_delete(chunk->data[x][y][z]);
pthread_mutex_destroy(&chunk->mtx);
free(chunk);
}
Blob terrain_serialize_chunk(TerrainChunk *chunk)
Blob terrain_serialize_chunk(__attribute__((unused)) Terrain *terrain, TerrainChunk *chunk, void (*callback)(TerrainNode *node, Blob *buffer))
{
bool empty = true;
@ -152,118 +165,95 @@ Blob terrain_serialize_chunk(TerrainChunk *chunk)
if (empty)
return (Blob) {0, NULL};
SerializedTerrainChunk chunk_data;
SerializedTerrainChunk serialized_chunk;
CHUNK_ITERATE {
TerrainNode *node = &chunk->data[x][y][z];
SerializedTerrainNode *node_data = &chunk_data.raw.nodes[x][y][z];
SerializedTerrainNode *serialized = &serialized_chunk.raw.nodes[x][y][z];
*node_data = (SerializedTerrainNode) {
.type = node->type,
.data = {
.siz = 0,
.data = NULL,
},
};
serialized->type = node->type;
serialized->data = (Blob) {0, NULL};
NodeDef *def = &node_def[node->type];
if (def->callbacks.serialize)
def->callbacks.serialize(&node_data->data, node->data);
if (callback)
callback(node, &serialized->data);
}
Blob buffer = {0, NULL};
SerializedTerrainChunk_write(&buffer, &chunk_data);
SerializedTerrainChunk_free(&chunk_data);
SerializedTerrainChunk_write(&buffer, &serialized_chunk);
SerializedTerrainChunk_free(&serialized_chunk);
return buffer;
}
bool terrain_deserialize_chunk(TerrainChunk *chunk, Blob buffer)
bool terrain_deserialize_chunk(Terrain *terrain, TerrainChunk *chunk, Blob buffer, void (*callback)(TerrainNode *node, Blob buffer))
{
if (buffer.siz == 0) {
CHUNK_ITERATE
chunk->data[x][y][z] = terrain_node_create(NODE_AIR, (Blob) {0, NULL});
CHUNK_ITERATE {
if (terrain->callbacks.delete_node)
terrain->callbacks.delete_node(&chunk->data[x][y][z]);
chunk->data[x][y][z] = (TerrainNode) {NODE_AIR, NULL};
}
return true;
}
// it's important to copy Blobs that have been malloc'd before reading from them
// because reading from a Blob modifies its data and size pointer,
// but does not free anything
SerializedTerrainChunk chunk_data = {0};
bool success = SerializedTerrainChunk_read(&buffer, &chunk_data);
SerializedTerrainChunk serialized_chunk = {0};
bool success = SerializedTerrainChunk_read(&buffer, &serialized_chunk);
if (success) CHUNK_ITERATE
chunk->data[x][y][z] = terrain_node_create(chunk_data.raw.nodes[x][y][z].type, chunk_data.raw.nodes[x][y][z].data);
if (success) CHUNK_ITERATE {
if (terrain->callbacks.delete_node)
terrain->callbacks.delete_node(&chunk->data[x][y][z]);
SerializedTerrainChunk_free(&chunk_data);
TerrainNode *node = &chunk->data[x][y][z];
SerializedTerrainNode *serialized = &serialized_chunk.raw.nodes[x][y][z];
node->type = serialized->type;
if (callback)
callback(node, serialized->data);
}
SerializedTerrainChunk_free(&serialized_chunk);
return success;
}
v3s32 terrain_node_to_chunk_pos(v3s32 pos, v3u8 *offset)
void terrain_lock_chunk(TerrainChunk *chunk)
{
if (offset)
*offset = (v3u8) {(u32) pos.x % CHUNK_SIZE, (u32) pos.y % CHUNK_SIZE, (u32) pos.z % CHUNK_SIZE};
return (v3s32) {floor((double) pos.x / (double) CHUNK_SIZE), floor((double) pos.y / (double) CHUNK_SIZE), floor((double) pos.z / (double) CHUNK_SIZE)};
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)
{
v3u8 offset;
v3s32 chunkpos = terrain_node_to_chunk_pos(pos, &offset);
TerrainChunk *chunk = terrain_get_chunk(terrain, chunkpos, false);
v3s32 offset;
TerrainChunk *chunk = terrain_get_chunk_nodep(terrain, pos, &offset, false);
if (!chunk)
return terrain_node_create(NODE_UNLOADED, (Blob) {0, NULL});
return chunk->data[offset.x][offset.y][offset.z];
}
return (TerrainNode) {COUNT_NODE, NULL};
void terrain_set_node(Terrain *terrain, v3s32 pos, TerrainNode node, bool create, void *arg)
{
v3u8 offset;
TerrainChunk *chunk = terrain_get_chunk(terrain, terrain_node_to_chunk_pos(pos, &offset), create);
if (!chunk)
return;
pthread_mutex_lock(&chunk->mtx);
if (!terrain->callbacks.set_node || terrain->callbacks.set_node(chunk, offset, &node, arg)) {
chunk->data[offset.x][offset.y][offset.z] = node;
if (terrain->callbacks.after_set_node)
terrain->callbacks.after_set_node(chunk, offset, arg);
} else {
terrain_node_delete(node);
}
terrain_lock_chunk(chunk);
TerrainNode node = chunk->data[offset.x][offset.y][offset.z];
pthread_mutex_unlock(&chunk->mtx);
}
TerrainNode terrain_node_create(NodeType type, Blob buffer)
{
if (type >= NODE_UNLOADED)
type = NODE_UNKNOWN;
NodeDef *def = &node_def[type];
TerrainNode node;
node.type = type;
node.data = def->data_size ? malloc(def->data_size) : NULL;
if (def->callbacks.create)
def->callbacks.create(&node);
if (def->callbacks.deserialize)
def->callbacks.deserialize(&buffer, node.data);
return node;
}
void terrain_node_delete(TerrainNode node)
v3s32 terrain_chunkp(v3s32 pos)
{
NodeDef *def = &node_def[node.type];
if (def->callbacks.delete)
def->callbacks.delete(&node);
if (node.data)
free(node.data);
return (v3s32) {
floor((double) pos.x / (double) CHUNK_SIZE),
floor((double) pos.y / (double) CHUNK_SIZE),
floor((double) pos.z / (double) CHUNK_SIZE)};
}
v3s32 terrain_offset(v3s32 pos)
{
return (v3s32) {
(u32) pos.x % CHUNK_SIZE,
(u32) pos.y % CHUNK_SIZE,
(u32) pos.z % CHUNK_SIZE};
}

View File

@ -18,23 +18,14 @@ typedef struct TerrainNode {
void *data;
} TerrainNode;
typedef TerrainNode TerrainChunkData[CHUNK_SIZE][CHUNK_SIZE][CHUNK_SIZE];
typedef struct {
s32 level;
v3s32 pos;
TerrainChunkData data;
TerrainNode data[CHUNK_SIZE][CHUNK_SIZE][CHUNK_SIZE];
void *extra;
pthread_mutex_t mtx;
} TerrainChunk;
typedef struct
{
v2s32 pos;
Tree chunks;
pthread_rwlock_t lock;
} TerrainSector;
typedef struct {
Tree sectors;
pthread_rwlock_t lock;
@ -44,29 +35,24 @@ typedef struct {
void (*create_chunk)(TerrainChunk *chunk);
void (*delete_chunk)(TerrainChunk *chunk);
bool (*get_chunk)(TerrainChunk *chunk, bool create);
bool (*set_node) (TerrainChunk *chunk, v3u8 offset, TerrainNode *node, void *arg);
void (*after_set_node)(TerrainChunk *chunk, v3u8 offset, void *arg);
void (*delete_node)(TerrainNode *node);
} callbacks;
} Terrain;
Terrain *terrain_create();
void terrain_delete(Terrain *terrain);
TerrainSector *terrain_get_sector(Terrain *terrain, v2s32 pos, bool create);
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_allocate_chunk(v3s32 pos);
void terrain_free_chunk(TerrainChunk *chunk);
Blob terrain_serialize_chunk(TerrainChunk *chunk);
bool terrain_deserialize_chunk(TerrainChunk *chunk, Blob buffer);
v3s32 terrain_node_to_chunk_pos(v3s32 pos, v3u8 *offset);
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_set_node(Terrain *terrain, v3s32 pos, TerrainNode node, bool create, void *arg);
TerrainNode terrain_node_create(NodeType type, Blob buffer);
void terrain_node_delete(TerrainNode node);
void terrain_lock_chunk(TerrainChunk *chunk);
v3s32 terrain_chunkp(v3s32 pos);
v3s32 terrain_offset(v3s32 pos);
#endif

View File

@ -3,6 +3,11 @@
ColorData
v3f32 color
TreeData
v3f32 color
u8 has_root
v3s32 root
SerializedTerrainNode
u32 type
Blob data