]> git.lizzy.rs Git - dragonblocks_alpha.git/blob - src/terrain.c
Trees break if they have no connection to ground
[dragonblocks_alpha.git] / src / terrain.c
1 #include <math.h>
2 #include <stdbool.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <unistd.h>
7 #include "terrain.h"
8
9 typedef struct {
10         v2s32 pos;
11         Tree chunks;
12         pthread_rwlock_t lock;
13 } TerrainSector;
14
15 static TerrainChunk *allocate_chunk(v3s32 pos)
16 {
17         TerrainChunk *chunk = malloc(sizeof * chunk);
18         chunk->level = pos.y;
19         chunk->pos = pos;
20         chunk->extra = NULL;
21         pthread_mutexattr_t attr;
22         pthread_mutexattr_init(&attr);
23         pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
24         pthread_mutex_init(&chunk->mtx, &attr);
25
26         CHUNK_ITERATE
27                 chunk->data[x][y][z] = (TerrainNode) {NODE_UNKNOWN, NULL};
28
29         return chunk;
30 }
31
32 static void free_chunk(Terrain *terrain, TerrainChunk *chunk)
33 {
34         if (terrain->callbacks.delete_node) CHUNK_ITERATE
35                 terrain->callbacks.delete_node(&chunk->data[x][y][z]);
36
37         pthread_mutex_destroy(&chunk->mtx);
38         free(chunk);
39 }
40
41 static void delete_chunk(TerrainChunk *chunk, Terrain *terrain)
42 {
43         if (terrain->callbacks.delete_chunk)
44                 terrain->callbacks.delete_chunk(chunk);
45
46         free_chunk(terrain, chunk);
47 }
48
49 static void delete_sector(TerrainSector *sector, Terrain *terrain)
50 {
51         tree_clr(&sector->chunks, &delete_chunk, terrain, NULL, 0);
52         pthread_rwlock_destroy(&sector->lock);
53         free(sector);
54 }
55
56 static TerrainSector *get_sector(Terrain *terrain, v2s32 pos, bool create)
57 {
58         if (create)
59                 pthread_rwlock_wrlock(&terrain->lock);
60         else
61                 pthread_rwlock_rdlock(&terrain->lock);
62
63         TreeNode **loc = tree_nfd(&terrain->sectors, &pos, &v2s32_cmp);
64         TerrainSector *sector = NULL;
65
66         if (*loc) {
67                 sector = (*loc)->dat;
68         } else if (create) {
69                 sector = malloc(sizeof *sector);
70                 sector->pos = pos;
71                 tree_ini(&sector->chunks);
72                 pthread_rwlock_init(&sector->lock, NULL);
73
74                 tree_nmk(&terrain->sectors, loc, sector);
75         }
76
77         pthread_rwlock_unlock(&terrain->lock);
78
79         return sector;
80 }
81
82 Terrain *terrain_create()
83 {
84         Terrain *terrain = malloc(sizeof *terrain);
85         tree_ini(&terrain->sectors);
86         pthread_rwlock_init(&terrain->lock, NULL);
87         terrain->cache = NULL;
88         pthread_rwlock_init(&terrain->cache_lock, NULL);
89         return terrain;
90 }
91
92 void terrain_delete(Terrain *terrain)
93 {
94         tree_clr(&terrain->sectors, &delete_sector, terrain, NULL, 0);
95         pthread_rwlock_destroy(&terrain->lock);
96         pthread_rwlock_destroy(&terrain->cache_lock);
97         free(terrain);
98 }
99
100 TerrainChunk *terrain_get_chunk(Terrain *terrain, v3s32 pos, bool create)
101 {
102         TerrainChunk *cache = NULL;
103
104         pthread_rwlock_rdlock(&terrain->cache_lock);
105         cache = terrain->cache;
106         pthread_rwlock_unlock(&terrain->cache_lock);
107
108         if (cache && v3s32_equals(cache->pos, pos))
109                 return cache;
110
111         TerrainSector *sector = get_sector(terrain, (v2s32) {pos.x, pos.z}, create);
112         if (!sector)
113                 return NULL;
114
115         if (create)
116                 pthread_rwlock_wrlock(&sector->lock);
117         else
118                 pthread_rwlock_rdlock(&sector->lock);
119
120         TreeNode **loc = tree_nfd(&sector->chunks, &pos.y, &s32_cmp);
121         TerrainChunk *chunk = NULL;
122
123         if (*loc) {
124                 chunk = (*loc)->dat;
125
126                 if (terrain->callbacks.get_chunk && !terrain->callbacks.get_chunk(chunk, create)) {
127                         chunk = NULL;
128                 } else {
129                         pthread_rwlock_wrlock(&terrain->cache_lock);
130                         terrain->cache = chunk;
131                         pthread_rwlock_unlock(&terrain->cache_lock);
132                 }
133         } else if (create) {
134                 tree_nmk(&sector->chunks, loc, chunk = allocate_chunk(pos));
135
136                 if (terrain->callbacks.create_chunk)
137                         terrain->callbacks.create_chunk(chunk);
138         }
139
140         pthread_rwlock_unlock(&sector->lock);
141
142         return chunk;
143 }
144
145 TerrainChunk *terrain_get_chunk_nodep(Terrain *terrain, v3s32 nodep, v3s32 *offset, bool create)
146 {
147         TerrainChunk *chunk = terrain_get_chunk(terrain, terrain_chunkp(nodep), create);
148         if (!chunk)
149                 return NULL;
150         *offset = terrain_offset(nodep);
151         return chunk;
152 }
153
154 Blob terrain_serialize_chunk(__attribute__((unused)) Terrain *terrain, TerrainChunk *chunk, void (*callback)(TerrainNode *node, Blob *buffer))
155 {
156         bool empty = true;
157
158         CHUNK_ITERATE {
159                 if (chunk->data[x][y][z].type != NODE_AIR) {
160                         empty = false;
161                         break;
162                 }
163         }
164
165         if (empty)
166                 return (Blob) {0, NULL};
167
168         SerializedTerrainChunk serialized_chunk;
169
170         CHUNK_ITERATE {
171                 TerrainNode *node = &chunk->data[x][y][z];
172                 SerializedTerrainNode *serialized = &serialized_chunk.raw.nodes[x][y][z];
173
174                 serialized->type = node->type;
175                 serialized->data = (Blob) {0, NULL};
176
177                 if (callback)
178                         callback(node, &serialized->data);
179         }
180
181         Blob buffer = {0, NULL};
182         SerializedTerrainChunk_write(&buffer, &serialized_chunk);
183         SerializedTerrainChunk_free(&serialized_chunk);
184         return buffer;
185 }
186
187 bool terrain_deserialize_chunk(Terrain *terrain, TerrainChunk *chunk, Blob buffer, void (*callback)(TerrainNode *node, Blob buffer))
188 {
189         if (buffer.siz == 0) {
190                 CHUNK_ITERATE {
191                         if (terrain->callbacks.delete_node)
192                                 terrain->callbacks.delete_node(&chunk->data[x][y][z]);
193
194                         chunk->data[x][y][z] = (TerrainNode) {NODE_AIR, NULL};
195                 }
196                 return true;
197         }
198
199         // it's important to copy Blobs that have been malloc'd before reading from them
200         // because reading from a Blob modifies its data and size pointer,
201         // but does not free anything
202         SerializedTerrainChunk serialized_chunk = {0};
203         bool success = SerializedTerrainChunk_read(&buffer, &serialized_chunk);
204
205         if (success) CHUNK_ITERATE {
206                 if (terrain->callbacks.delete_node)
207                         terrain->callbacks.delete_node(&chunk->data[x][y][z]);
208
209                 TerrainNode *node = &chunk->data[x][y][z];
210                 SerializedTerrainNode *serialized = &serialized_chunk.raw.nodes[x][y][z];
211
212                 node->type = serialized->type;
213
214                 if (callback)
215                         callback(node, serialized->data);
216         }
217
218         SerializedTerrainChunk_free(&serialized_chunk);
219         return success;
220 }
221
222 void terrain_lock_chunk(TerrainChunk *chunk)
223 {
224         if (pthread_mutex_lock(&chunk->mtx) == 0)
225                 return;
226
227         fprintf(stderr, "[error] failed to lock terrain chunk mutex\n");
228         abort();
229 }
230
231 TerrainNode terrain_get_node(Terrain *terrain, v3s32 pos)
232 {
233         v3s32 offset;
234         TerrainChunk *chunk = terrain_get_chunk_nodep(terrain, pos, &offset, false);
235         if (!chunk)
236                 return (TerrainNode) {COUNT_NODE, NULL};
237
238         terrain_lock_chunk(chunk);
239         TerrainNode node = chunk->data[offset.x][offset.y][offset.z];
240         pthread_mutex_unlock(&chunk->mtx);
241
242         return node;
243 }
244
245 v3s32 terrain_chunkp(v3s32 pos)
246 {
247         return (v3s32) {
248                 floor((double) pos.x / (double) CHUNK_SIZE),
249                 floor((double) pos.y / (double) CHUNK_SIZE),
250                 floor((double) pos.z / (double) CHUNK_SIZE)};
251 }
252
253 v3s32 terrain_offset(v3s32 pos)
254 {
255         return (v3s32) {
256                 (u32) pos.x % CHUNK_SIZE,
257                 (u32) pos.y % CHUNK_SIZE,
258                 (u32) pos.z % CHUNK_SIZE};
259 }