]> git.localhorst.tv Git - blank.git/blob - src/io/WorldSave.cpp
reference count entities for safer removal
[blank.git] / src / io / WorldSave.cpp
1 #include "WorldSave.hpp"
2
3 #include "filesystem.hpp"
4
5 #include <cctype>
6 #include <cstring>
7 #include <fstream>
8 #include <iostream>
9 #include <limits>
10 #include <stdexcept>
11 #include <zlib.h>
12
13 using namespace std;
14
15
16 namespace blank {
17
18 WorldSave::WorldSave(const string &path)
19 : root_path(path)
20 , conf_path(path + "world.conf")
21 , chunk_path(path + "chunks/%d/%d/%d.gz")
22 , chunk_bufsiz(chunk_path.length() + 3 * std::numeric_limits<int>::digits10)
23 , chunk_buf(new char[chunk_bufsiz]) {
24
25 }
26
27
28 bool WorldSave::Exists() const noexcept {
29         return is_dir(root_path) && is_file(conf_path);
30 }
31
32
33 void WorldSave::Read(World::Config &conf) const {
34         ifstream in(conf_path);
35         if (!in) {
36                 throw runtime_error("failed to open world config");
37         }
38
39         constexpr char spaces[] = "\n\r\t ";
40
41         string line;
42         while (getline(in, line)) {
43                 if (line.empty() || line[0] == '#') continue;
44                 auto equals_pos = line.find_first_of('=');
45
46                 auto name_begin = line.find_first_not_of(spaces, 0, sizeof(spaces));
47                 auto name_end = equals_pos - 1;
48                 while (name_end > name_begin && isspace(line[name_end])) {
49                         --name_end;
50                 }
51
52                 auto value_begin = line.find_first_not_of(spaces, equals_pos + 1, sizeof(spaces));
53                 auto value_end = line.length() - 1;
54                 while (value_end > value_begin && isspace(line[value_end])) {
55                         --value_end;
56                 }
57
58                 string name(line, name_begin, name_end - name_begin + 1);
59                 string value(line, value_begin, value_end - value_begin + 1);
60
61                 if (name == "seed") {
62                         conf.gen.seed = stoul(value);
63                 } else {
64                         throw runtime_error("unknown world option: " + name);
65                 }
66         }
67         if (in.bad()) {
68                 throw runtime_error("IO error reading world config");
69         }
70 }
71
72 void WorldSave::Write(const World::Config &conf) const {
73         if (!make_dirs(root_path)) {
74                 throw runtime_error("failed to create world save directory");
75         }
76
77         ofstream out(conf_path);
78         out << "seed = " << conf.gen.seed << endl;
79         out.close();
80
81         if (!out) {
82                 throw runtime_error("failed to write world config");
83         }
84 }
85
86
87 bool WorldSave::Exists(const Chunk::Pos &pos) const noexcept {
88         return is_file(ChunkPath(pos));
89 }
90
91 void WorldSave::Read(Chunk &chunk) const {
92         const char *path = ChunkPath(chunk.Position());
93         gzFile file = gzopen(path, "r");
94         if (!file) {
95                 throw runtime_error("failed to open chunk file");
96         }
97         if (gzread(file, chunk.BlockData(), Chunk::BlockSize()) != Chunk::BlockSize()) {
98                 throw runtime_error("failed to read chunk from file");
99         }
100         if (gzclose(file) != Z_OK) {
101                 throw runtime_error("failed to read chunk file");
102         }
103         chunk.InvalidateModel();
104         chunk.ClearSave();
105 }
106
107 void WorldSave::Write(Chunk &chunk) const {
108         const char *path = ChunkPath(chunk.Position());
109         gzFile file = gzopen(path, "w");
110         if (!file) {
111                 // check if it's because of a missing path component
112                 if (errno != ENOENT) {
113                         // nope, fatal
114                         throw runtime_error(strerror(errno));
115                 }
116                 string dir_path(path);
117                 dir_path.erase(dir_path.find_last_of("\\/"));
118                 if (!make_dirs(dir_path)) {
119                         throw runtime_error("failed to create dir for chunk file");
120                 }
121                 file = gzopen(path, "w");
122                 if (!file) {
123                         throw runtime_error("failed to open chunk file");
124                 }
125         }
126         if (gzwrite(file, chunk.BlockData(), Chunk::BlockSize()) == 0) {
127                 gzclose(file); // if this fails, it can't be helped
128                 throw runtime_error("failed to write chunk to file");
129         }
130         if (gzclose(file) != Z_OK) {
131                 throw runtime_error("failed to write chunk file");
132         }
133         chunk.ClearSave();
134 }
135
136
137 const char *WorldSave::ChunkPath(const Chunk::Pos &pos) const {
138         snprintf(chunk_buf.get(), chunk_bufsiz, chunk_path.c_str(), pos.x, pos.y, pos.z);
139         return chunk_buf.get();
140 }
141
142 }