]> git.lizzy.rs Git - dragonblocks_alpha.git/blob - src/server/database.c
79d1e59e1e43cf0d19f1423f203c52c7ec8e09cd
[dragonblocks_alpha.git] / src / server / database.c
1 #include <endian.h/endian.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <sqlite3.h>
6 #include <time.h>
7 #include "day.h"
8 #include "server/database.h"
9 #include "server/server_node.h"
10 #include "server/server_terrain.h"
11 #include "perlin.h"
12
13 static sqlite3 *terrain_database;
14 static sqlite3 *meta_database;
15 static sqlite3 *players_database;
16
17 // utility functions
18
19 // prepare a SQLite3 statement
20 static inline sqlite3_stmt *prepare_statement(sqlite3 *database, const char *sql)
21 {
22         sqlite3_stmt *stmt;
23         return sqlite3_prepare_v2(database, sql, -1, &stmt, NULL) == SQLITE_OK ? stmt : NULL;
24 }
25
26 // print SQLite3 error message for failed chunk SQL statement
27 static inline void print_chunk_error(TerrainChunk *chunk, const char *action)
28 {
29         fprintf(stderr, "[warning] failed %s chunk at (%d, %d, %d): %s\n", action, chunk->pos.x, chunk->pos.y, chunk->pos.z, sqlite3_errmsg(terrain_database));
30 }
31
32 // prepare a SQLite3 chunk statement and bind the position
33 static sqlite3_stmt *prepare_chunk_statement(TerrainChunk *chunk, const char *action, const char *sql)
34 {
35         sqlite3_stmt *stmt = prepare_statement(terrain_database, sql);
36
37         if (!stmt) {
38                 print_chunk_error(chunk, action);
39                 return NULL;
40         }
41
42         Blob buffer = {0, NULL};
43         v3s32_write(&buffer, &chunk->pos);
44
45         sqlite3_bind_blob(stmt, 1, buffer.data, buffer.siz, &free);
46
47         return stmt;
48 }
49
50 // bind v3f64 to sqlite3 statement
51 static inline void bind_v3f64(sqlite3_stmt *stmt, int idx, v3f64 pos)
52 {
53         Blob buffer = {0, NULL};
54         v3f64_write(&buffer, &pos);
55
56         sqlite3_bind_blob(stmt, idx, buffer.data, buffer.siz, &free);
57 }
58
59 // bind v3f32 to sqlite3 statement
60 static inline void bind_v3f32(sqlite3_stmt *stmt, int idx, v3f32 pos)
61 {
62         Blob buffer = {0, NULL};
63         v3f32_write(&buffer, &pos);
64
65         sqlite3_bind_blob(stmt, idx, buffer.data, buffer.siz, &free);
66 }
67
68 // public functions
69
70 // open and initialize SQLite3 databases
71 bool database_init()
72 {
73         struct {
74                 sqlite3 **handle;
75                 const char *path;
76                 const char *init;
77         } databases[3] = {
78                 {&terrain_database, "terrain.sqlite", "CREATE TABLE IF NOT EXISTS terrain (pos  BLOB PRIMARY KEY, generated INTEGER, data BLOB, tgsb BLOB);"},
79                 {&meta_database,    "meta.sqlite",    "CREATE TABLE IF NOT EXISTS meta    (key  TEXT PRIMARY KEY, value INTEGER                          );"},
80                 {&players_database, "players.sqlite", "CREATE TABLE IF NOT EXISTS players (name TEXT PRIMARY KEY, pos BLOB, rot BLOB                     );"},
81         };
82
83         for (int i = 0; i < 3; i++) {
84                 if (sqlite3_open_v2(databases[i].path, databases[i].handle, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL) != SQLITE_OK) {
85                         fprintf(stderr, "[error] failed to open %s: %s\n", databases[i].path, sqlite3_errmsg(*databases[i].handle));
86                         return false;
87                 }
88
89                 char *err;
90                 if (sqlite3_exec(*databases[i].handle, databases[i].init, NULL, NULL, &err) != SQLITE_OK) {
91                         fprintf(stderr, "[error] failed initializing %s: %s\n", databases[i].path, err);
92                         sqlite3_free(err);
93                         return false;
94                 }
95         }
96
97         s64 saved_seed;
98
99         if (database_load_meta("seed", &saved_seed)) {
100                 seed = saved_seed;
101         } else {
102                 srand(time(NULL));
103                 seed = rand();
104                 database_save_meta("seed", seed);
105         }
106
107         s64 time_of_day;
108
109         if (database_load_meta("time_of_day", &time_of_day))
110                 set_time_of_day(time_of_day);
111         else
112                 set_time_of_day(12 * MINUTES_PER_HOUR);
113
114         return true;
115 }
116
117 // close databases
118 void database_deinit()
119 {
120         database_save_meta("time_of_day", (s64) get_time_of_day());
121
122         sqlite3_close(terrain_database);
123         sqlite3_close(meta_database);
124         sqlite3_close(players_database);
125 }
126
127 // load a chunk from terrain database (initializes state, tgs buffer and data), returns false on failure
128 bool database_load_chunk(TerrainChunk *chunk)
129 {
130         sqlite3_stmt *stmt = prepare_chunk_statement(chunk, "loading", "SELECT generated, data, tgsb FROM terrain WHERE pos=?");
131
132         if (!stmt)
133                 return false;
134
135         int rc = sqlite3_step(stmt);
136         bool found = rc == SQLITE_ROW;
137
138         if (found) {
139                 TerrainChunkMeta *meta = chunk->extra;
140
141                 meta->state = sqlite3_column_int(stmt, 0) ? CHUNK_STATE_READY : CHUNK_STATE_CREATED;
142                 Blob data = {sqlite3_column_bytes(stmt, 1), (void *) sqlite3_column_blob(stmt, 1)};
143                 Blob tgsb = {sqlite3_column_bytes(stmt, 2), (void *) sqlite3_column_blob(stmt, 2)};
144
145                 TerrainGenStageBuffer_read(&tgsb, &meta->tgsb);
146                 if (!terrain_deserialize_chunk(server_terrain, chunk, data, &server_node_deserialize)) {
147                         fprintf(stderr, "[error] failed deserializing chunk at (%d, %d, %d)\n", chunk->pos.x, chunk->pos.y, chunk->pos.z);
148                         abort();
149                 }
150         } else if (rc != SQLITE_DONE) {
151                 print_chunk_error(chunk, "loading");
152         }
153
154         sqlite3_finalize(stmt);
155         return found;
156 }
157
158 // save a chunk to terrain database
159 void database_save_chunk(TerrainChunk *chunk)
160 {
161         sqlite3_stmt *stmt = prepare_chunk_statement(chunk, "saving", "REPLACE INTO terrain (pos, generated, data, tgsb) VALUES(?1, ?2, ?3, ?4)");
162
163         if (!stmt)
164                 return;
165
166         TerrainChunkMeta *meta = chunk->extra;
167
168         Blob data = terrain_serialize_chunk(server_terrain, chunk, &server_node_serialize);
169
170         Blob tgsb = {0, NULL};
171         TerrainGenStageBuffer_write(&tgsb, &meta->tgsb);
172
173         sqlite3_bind_int(stmt, 2, meta->state > CHUNK_STATE_CREATED);
174         sqlite3_bind_blob(stmt, 3, data.data, data.siz, &free);
175         sqlite3_bind_blob(stmt, 4, tgsb.data, tgsb.siz, &free);
176
177         if (sqlite3_step(stmt) != SQLITE_DONE)
178                 print_chunk_error(chunk, "saving");
179
180         sqlite3_finalize(stmt);
181 }
182
183 // load a meta entry
184 bool database_load_meta(const char *key, s64 *value_ptr)
185 {
186         sqlite3_stmt *stmt = prepare_statement(meta_database, "SELECT value FROM meta WHERE key=?");
187
188         if (!stmt) {
189                 fprintf(stderr, "[warning] failed loading meta %s: %s\n", key, sqlite3_errmsg(meta_database));
190                 return false;
191         }
192
193         sqlite3_bind_text(stmt, 1, key, strlen(key), SQLITE_TRANSIENT);
194
195         int rc = sqlite3_step(stmt);
196         bool found = rc == SQLITE_ROW;
197
198         if (found)
199                 *value_ptr = sqlite3_column_int64(stmt, 0);
200         else if (rc != SQLITE_DONE)
201                 fprintf(stderr, "[warning] failed loading meta %s: %s\n", key, sqlite3_errmsg(meta_database));
202
203         sqlite3_finalize(stmt);
204         return found;
205 }
206
207 // save / update a meta entry
208 void database_save_meta(const char *key, s64 value)
209 {
210         sqlite3_stmt *stmt = prepare_statement(meta_database, "REPLACE INTO meta (key, value) VALUES(?1, ?2)");
211
212         if (!stmt) {
213                 fprintf(stderr, "[warning] failed saving meta %s: %s\n", key, sqlite3_errmsg(meta_database));
214                 return;
215         }
216
217         sqlite3_bind_text(stmt, 1, key, strlen(key), SQLITE_TRANSIENT);
218         sqlite3_bind_int64(stmt, 2, value);
219
220         if (sqlite3_step(stmt) != SQLITE_DONE)
221                 fprintf(stderr, "[warning] failed saving meta %s: %s\n", key, sqlite3_errmsg(meta_database));
222
223         sqlite3_finalize(stmt);
224 }
225
226 // load player data from database
227 bool database_load_player(char *name, v3f64 *pos, v3f32 *rot)
228 {
229         sqlite3_stmt *stmt = prepare_statement(players_database, "SELECT pos, rot FROM players WHERE name=?");
230
231         if (!stmt) {
232                 fprintf(stderr, "[warning] failed loading player %s: %s\n", name, sqlite3_errmsg(players_database));
233                 return false;
234         }
235
236         sqlite3_bind_text(stmt, 1, name, strlen(name), SQLITE_TRANSIENT);
237
238         int rc = sqlite3_step(stmt);
239         bool found = rc == SQLITE_ROW;
240
241         if (found) {
242                 v3f64_read(&(Blob) {sqlite3_column_bytes(stmt, 0), (void *) sqlite3_column_blob(stmt, 0)}, pos);
243                 v3f32_read(&(Blob) {sqlite3_column_bytes(stmt, 1), (void *) sqlite3_column_blob(stmt, 1)}, rot);
244         } else if (rc != SQLITE_DONE) {
245                 fprintf(stderr, "[warning] failed loading player %s: %s\n", name, sqlite3_errmsg(players_database));
246         }
247
248         sqlite3_finalize(stmt);
249         return found;
250 }
251
252 // insert new player into database
253 void database_create_player(char *name, v3f64 pos, v3f32 rot)
254 {
255         sqlite3_stmt *stmt = prepare_statement(players_database, "INSERT INTO players (name, pos, rot) VALUES(?1, ?2, ?3)");
256
257         if (!stmt) {
258                 fprintf(stderr, "[warning] failed creating player %s: %s\n", name, sqlite3_errmsg(players_database));
259                 return;
260         }
261
262         sqlite3_bind_text(stmt, 1, name, strlen(name), SQLITE_TRANSIENT);
263         bind_v3f64(stmt, 2, pos);
264         bind_v3f32(stmt, 3, rot);
265
266         if (sqlite3_step(stmt) != SQLITE_DONE)
267                 fprintf(stderr, "[warning] failed creating player %s: %s\n", name, sqlite3_errmsg(players_database));
268
269         sqlite3_finalize(stmt);
270 }
271
272 // update player position
273 void database_update_player_pos_rot(char *name, v3f64 pos, v3f32 rot)
274 {
275         sqlite3_stmt *stmt = prepare_statement(players_database, "UPDATE players SET pos=?1, rot=?2 WHERE name=?3");
276
277         if (!stmt) {
278                 fprintf(stderr, "[warning] failed updating player %s: %s\n", name, sqlite3_errmsg(players_database));
279                 return;
280         }
281
282         bind_v3f64(stmt, 1, pos);
283         bind_v3f32(stmt, 2, rot);
284         sqlite3_bind_text(stmt, 3, name, strlen(name), SQLITE_TRANSIENT);
285
286         if (sqlite3_step(stmt) != SQLITE_DONE)
287                 fprintf(stderr, "[warning] failed updating player %s: %s\n", name, sqlite3_errmsg(players_database));
288
289         sqlite3_finalize(stmt);
290 }