Porytiles
Loading...
Searching...
No Matches
tileset_repo.cpp
Go to the documentation of this file.
2
3#include <set>
4#include <string>
5
6#include "fmt/format.h"
7
17
18namespace porytiles2 {
19
20/*
21 * TODO: we need better error handling, specifically the std::unexpected returns should be more descriptive
22 */
23
25{
26 using enum TilesetArtifact::Type;
27
28 // Begin transaction for atomic writes
29 if (auto result = writer_->begin_transaction(); !result) {
30 return FormattableError{result.error()};
31 }
32
33 // Perform all write operations within the transaction
34
35 // Porytiles assets
36 // TODO: fill in the override and anim artifacts
37
38 auto bottom_png_artifact = TilesetArtifact{bottom_png};
39 auto bottom_png_key = key_provider_->key_for(tileset.name(), bottom_png_artifact);
40 if (auto result = writer_->write(bottom_png_key, bottom_png_artifact, tileset); !result.has_value()) {
41 std::ignore = writer_->rollback();
42 auto failed = FormattableError{"{}: save failed", FormatParam{bottom_png_key.key(), Style::bold}};
43 return ChainableResult<void>{failed, result};
44 }
45
46 auto middle_png_artifact = TilesetArtifact{middle_png};
47 auto middle_png_key = key_provider_->key_for(tileset.name(), middle_png_artifact);
48 if (auto result = writer_->write(middle_png_key, middle_png_artifact, tileset); !result.has_value()) {
49 std::ignore = writer_->rollback();
50 auto failed = FormattableError{"{}: save failed", FormatParam{middle_png_key.key(), Style::bold}};
51 return ChainableResult<void>{failed, result};
52 }
53
54 auto top_png_artifact = TilesetArtifact{top_png};
55 auto top_png_key = key_provider_->key_for(tileset.name(), top_png_artifact);
56 if (auto result = writer_->write(top_png_key, top_png_artifact, tileset); !result.has_value()) {
57 std::ignore = writer_->rollback();
58 auto failed = FormattableError{"{}: save failed", FormatParam{top_png_key.key(), Style::bold}};
59 return ChainableResult<void>{failed, result};
60 }
61
62 auto attr_csv_artifact = TilesetArtifact{attributes_csv};
63 auto attr_csv_key = key_provider_->key_for(tileset.name(), attr_csv_artifact);
64 if (auto result = writer_->write(attr_csv_key, attr_csv_artifact, tileset); !result.has_value()) {
65 std::ignore = writer_->rollback();
66 auto failed = FormattableError{"{}: save failed", FormatParam{attr_csv_key.key(), Style::bold}};
67 return ChainableResult<void>{failed, result};
68 }
69
70 // Porymap assets
71 // TODO: fill in the pal and anim artifacts
72
73 auto metatiles_artifact = TilesetArtifact{metatiles_bin};
74 auto metatiles_key = key_provider_->key_for(tileset.name(), metatiles_artifact);
75 if (auto result = writer_->write(metatiles_key, metatiles_artifact, tileset); !result.has_value()) {
76 std::ignore = writer_->rollback();
77 auto failed = FormattableError{"{}: save failed", FormatParam{metatiles_key.key(), Style::bold}};
78 return ChainableResult<void>{failed, result};
79 }
80
81 auto attr_artifact = TilesetArtifact{metatile_attributes_bin};
82 auto attr_key = key_provider_->key_for(tileset.name(), attr_artifact);
83 if (auto result = writer_->write(attr_key, attr_artifact, tileset); !result.has_value()) {
84 std::ignore = writer_->rollback();
85 auto failed = FormattableError{"{}: save failed", FormatParam{attr_key.key(), Style::bold}};
86 return ChainableResult<void>{failed, result};
87 }
88
89 auto tiles_png_artifact = TilesetArtifact{tiles_png};
90 auto tiles_png_key = key_provider_->key_for(tileset.name(), tiles_png_artifact);
91 if (auto result = writer_->write(tiles_png_key, tiles_png_artifact, tileset); !result.has_value()) {
92 std::ignore = writer_->rollback();
93 auto failed = FormattableError{"{}: save failed", FormatParam{tiles_png_key.key(), Style::bold}};
94 return ChainableResult<void>{failed, result};
95 }
96
97 // TODO: don't hardcode 16 here
98 for (unsigned int i = 0; i < pal::num_pals; i++) {
99 const auto pal_key = key_provider_->key_for(tileset.name(), TilesetArtifact{pal_n, i});
100 if (auto result = writer_->write(pal_key, TilesetArtifact{pal_n, i}, tileset); !result.has_value()) {
101 std::ignore = writer_->rollback();
102 auto failed = FormattableError{"{}: save failed", FormatParam{pal_key.key(), Style::bold}};
103 return ChainableResult<void>{failed, result};
104 }
105 }
106
107 // Commit all writes atomically
108 if (auto result = writer_->commit(); !result.has_value()) {
109 // Commit failed, attempt rollback (though it may not be necessary after failed commit)
110 std::ignore = writer_->rollback();
111 return ChainableResult<void>{FormattableError{"tileset commit failed"}, result};
112 }
113
114 // TODO: we should "clear" the stale contents of the tileset on disk after saving. That way, if the user e.g.
115 // removed an anim, the stale Porymap version of the anim doesn't remain on disk and clutter the filesystem. Perhaps
116 // this can be part of the tileset commit logic? We'll need some functionality in the writer implementation like
117 // "clear_stale_contents" or something. This applies both ways, e.g. if we delete the anim on the porymap side,
118 // then run an import, it should clear the anim from the porytiles side. I.e. if there is a Porymap anim on disk
119 // that does not exist in the Porytiles component, clear it. If there is a Porytiles anim on disk that does not
120 // exist in the Porymap component, clear it. Perhaps instead of auto-clearing, we can emit a diagnostic warning the
121 // user that stale assets exist on disk?
122
123 // Cache checksums after successful save
124 const auto current_checksums = checksum_provider_->compute_tileset_artifact_checksums(tileset.name());
125 const auto cache_result = checksum_provider_->cache_tileset_checksums(tileset.name(), current_checksums);
126 if (!cache_result.has_value()) {
127 return FormattableError{cache_result.error()};
128 }
129 return {};
130}
131
133{
134 using enum TilesetArtifact::Type;
135
136 // Fail as late as possible
137 bool fail_at_exit = false;
138
139 // Confirm tileset exists.
140 if (!exists(name)) {
141 return FormattableError{"tileset '{}' does not exist", FormatParam{name, Style::bold}};
142 }
143
144 auto porytiles_component = std::make_unique<PorytilesTilesetComponent>();
145 auto porymap_component = std::make_unique<PorymapTilesetComponent>();
146 auto tileset = std::make_unique<Tileset>(name, std::move(porytiles_component), std::move(porymap_component));
147
148 // Porytiles assets
149
150 const auto bottom_png_artifact = TilesetArtifact{bottom_png};
151 const auto bottom_png_key = key_provider_->key_for(tileset->name(), bottom_png_artifact);
152 if (key_provider_->artifact_exists(bottom_png_key)) {
153 const auto result = reader_->read(*tileset, bottom_png_key, bottom_png_artifact);
154 if (!result.has_value()) {
155 return ChainableResult<std::unique_ptr<Tileset>>{FormattableError{"failed to read bottom.png"}, result};
156 }
157 }
158
159 const auto middle_png_artifact = TilesetArtifact{middle_png};
160 const auto middle_png_key = key_provider_->key_for(tileset->name(), middle_png_artifact);
161 if (key_provider_->artifact_exists(middle_png_key)) {
162 const auto result = reader_->read(*tileset, middle_png_key, middle_png_artifact);
163 if (!result.has_value()) {
164 return ChainableResult<std::unique_ptr<Tileset>>{FormattableError{"failed to read middle.png"}, result};
165 }
166 }
167
168 const auto top_png_artifact = TilesetArtifact{top_png};
169 const auto top_png_key = key_provider_->key_for(tileset->name(), top_png_artifact);
170 if (key_provider_->artifact_exists(top_png_key)) {
171 const auto result = reader_->read(*tileset, top_png_key, top_png_artifact);
172 if (!result.has_value()) {
173 return ChainableResult<std::unique_ptr<Tileset>>{FormattableError{"failed to read top.png"}, result};
174 }
175 }
176
177 const auto attr_csv_artifact = TilesetArtifact{attributes_csv};
178 const auto attr_csv_key = key_provider_->key_for(tileset->name(), attr_csv_artifact);
179 if (key_provider_->artifact_exists(attr_csv_key)) {
180 const auto result = reader_->read(*tileset, attr_csv_key, attr_csv_artifact);
181 if (!result.has_value()) {
182 return ChainableResult<std::unique_ptr<Tileset>>{FormattableError{"failed to read attributes.csv"}, result};
183 }
184 }
185 else {
186 // TODO: emit warning to user about missing attr csv
187 }
188
189 for (unsigned int i = 0; i < pal::num_pals; i++) {
190 const auto override_key = key_provider_->key_for(tileset->name(), TilesetArtifact{pal_override_n, i});
191 if (key_provider_->artifact_exists(override_key)) {
192 const auto result = reader_->read(*tileset, override_key, TilesetArtifact{pal_override_n, i});
193 if (!result.has_value()) {
195 FormattableError{fmt::format("failed to read {}", override_key.key())}, result};
196 }
197 }
198 }
199
200 const std::set<std::string> porytiles_anims = key_provider_->discover_porytiles_anims(tileset->name());
201 for (const auto &anim : porytiles_anims) {
202 // Read frame 00.png
203 auto frame_00_key = key_provider_->key_for(tileset->name(), TilesetArtifact{porytiles_anim_frame, anim, 0});
204 if (!key_provider_->artifact_exists(frame_00_key)) {
205 // TODO: emit validation error: missing required 00.png
206 fail_at_exit = true;
207 continue;
208 }
209 const auto frame_00_result =
210 reader_->read(*tileset, frame_00_key, TilesetArtifact{porytiles_anim_frame, anim, 0});
211 if (!frame_00_result.has_value()) {
213 FormattableError{fmt::format("failed to read {}", frame_00_key.key())}, frame_00_result};
214 }
215
216 // Read the rest of the (optional) frames
217 std::set<int> frames = key_provider_->discover_porytiles_anim_frames(tileset->name(), anim);
218 int expected_frame = 1;
219 for (const auto frame : frames) {
220 if (frame != expected_frame) {
221 // TODO: emit validation error: frame {} did not match expected frame {}
222 fail_at_exit = true;
223 }
224 auto frame_n_key =
225 key_provider_->key_for(tileset->name(), TilesetArtifact{porytiles_anim_frame, anim, frame});
226 const auto frame_n_result =
227 reader_->read(*tileset, frame_n_key, TilesetArtifact{porytiles_anim_frame, anim, frame});
228 if (!frame_n_result.has_value()) {
230 FormattableError{fmt::format("failed to read {}", frame_n_key.key())}, frame_n_result};
231 }
232 expected_frame++;
233 }
234 }
235
236 // Porymap assets
237
238 const auto metatiles_artifact = TilesetArtifact{metatiles_bin};
239 const auto metatiles_key = key_provider_->key_for(tileset->name(), metatiles_artifact);
240 if (!key_provider_->artifact_exists(metatiles_key)) {
241 return FormattableError{"missing required porymap artifact metatiles.bin"};
242 }
243 const auto metatiles_result = reader_->read(*tileset, metatiles_key, metatiles_artifact);
244 if (!metatiles_result.has_value()) {
246 FormattableError{"failed to read metatiles.bin"}, metatiles_result};
247 }
248
249 const auto attr_artifact = TilesetArtifact{metatile_attributes_bin};
250 const auto attr_key = key_provider_->key_for(tileset->name(), attr_artifact);
251 if (!key_provider_->artifact_exists(attr_key)) {
252 return FormattableError{"missing required porymap artifact metatile_attributes.bin"};
253 }
254 const auto attr_result = reader_->read(*tileset, attr_key, attr_artifact);
255 if (!attr_result.has_value()) {
257 FormattableError{"failed to read metatile_attributes.bin"}, attr_result};
258 }
259
260 const auto tiles_png_artifact = TilesetArtifact{tiles_png};
261 const auto tiles_png_key = key_provider_->key_for(tileset->name(), tiles_png_artifact);
262 if (!key_provider_->artifact_exists(tiles_png_key)) {
263 return FormattableError{"missing required porymap artifact tiles.png"};
264 }
265 const auto tiles_png_result = reader_->read(*tileset, tiles_png_key, tiles_png_artifact);
266 if (!tiles_png_result.has_value()) {
268 FormattableError{"failed to read tiles.png"}, tiles_png_result};
269 }
270
271 for (unsigned int i = 0; i < pal::num_pals; i++) {
272 const auto pal_key = key_provider_->key_for(tileset->name(), TilesetArtifact{pal_n, i});
273 if (!key_provider_->artifact_exists(pal_key)) {
274 // TODO: emit validation error: missing required artifact {:02}.pal
275 fail_at_exit = true;
276 continue;
277 }
278 const auto pal_result = reader_->read(*tileset, pal_key, TilesetArtifact{pal_n, i});
279 if (!pal_result.has_value()) {
281 FormattableError{fmt::format("failed to read {}", pal_key.key())}, pal_result};
282 }
283 }
284
285 for (const std::set<std::string> porymap_anims = key_provider_->discover_porymap_anims(tileset->name());
286 const auto &anim : porymap_anims) {
287 // Read frame 00.png
288 auto frame_00_key = key_provider_->key_for(tileset->name(), TilesetArtifact{porymap_anim_frame, anim, 0});
289 if (!key_provider_->artifact_exists(frame_00_key)) {
290 // TODO: emit validation error: missing required 00.png
291 fail_at_exit = true;
292 continue;
293 }
294 const auto frame_00_result =
295 reader_->read(*tileset, frame_00_key, TilesetArtifact{porymap_anim_frame, anim, 0});
296 if (!frame_00_result.has_value()) {
298 FormattableError{fmt::format("failed to read {}", frame_00_key.key())}, frame_00_result};
299 }
300
301 // Read the rest of the (optional) frames
302 std::set<int> frames = key_provider_->discover_porymap_anim_frames(tileset->name(), anim);
303 int expected_frame = 1;
304 for (const auto frame : frames) {
305 if (frame != expected_frame) {
306 // TODO: emit validation error: frame {} did not match expected frame {}
307 fail_at_exit = true;
308 }
309 auto frame_n_key =
310 key_provider_->key_for(tileset->name(), TilesetArtifact{porymap_anim_frame, anim, frame});
311 const auto frame_n_result =
312 reader_->read(*tileset, frame_n_key, TilesetArtifact{porymap_anim_frame, anim, frame});
313 if (!frame_n_result.has_value()) {
315 FormattableError{fmt::format("failed to read {}", frame_n_key.key())}, frame_n_result};
316 }
317 expected_frame++;
318 }
319 }
320
321 /*
322 * TODO: now that we've loaded the attributes and metatiles, if we're not triple layer then use the loaded
323 * information to pad out tilemap_entry vector with transparent entries for the missing layers.
324 */
325
326 if (fail_at_exit) {
327 return FormattableError{"errors while loading tileset"};
328 }
329
330 return tileset;
331}
332
333bool TilesetRepo::exists(const std::string &name) const
334{
335 return key_provider_->tileset_exists(name);
336}
337
338} // namespace porytiles2
virtual std::unordered_map< ArtifactKey, std::string > compute_tileset_artifact_checksums(const std::string &tileset_name) const =0
Computes checksums for the artifacts that belong to the given Tileset.
virtual Result< void > cache_tileset_checksums(const std::string &tileset_name, const std::unordered_map< ArtifactKey, std::string > &checksums) const =0
Caches checksums for the given Tileset to persistent storage.
A result type that maintains a chainable sequence of errors for debugging and error reporting.
A text parameter with associated styling for formatted output.
General-purpose error implementation with formatted message support.
Definition error.hpp:117
virtual std::set< int > discover_porytiles_anim_frames(const std::string &tileset_name, const std::string &anim_name) const =0
Discovers the frame indices for a specific Porytiles animation.
virtual ArtifactKey key_for(const std::string &tileset_name, const TilesetArtifact &artifact) const =0
Constructs a key for a given tileset artifact.
virtual bool artifact_exists(const ArtifactKey &key) const =0
Checks whether an artifact exists in the backing store for the given key.
virtual std::set< int > discover_porymap_anim_frames(const std::string &tileset_name, const std::string &anim_name) const =0
Discovers the frame indices for a specific Porymap animation.
virtual bool tileset_exists(const std::string &tileset_name) const =0
Checks whether a tileset exists in the backing store for the given tileset name.
virtual std::set< std::string > discover_porymap_anims(const std::string &tileset_name) const =0
Discovers the names of all Porymap animations available for a tileset.
virtual std::set< std::string > discover_porytiles_anims(const std::string &tileset_name) const =0
Discovers the names of all Porytiles animations available for a tileset.
virtual ChainableResult< void > read(Tileset &dest, const ArtifactKey &src_key, const TilesetArtifact &artifact) const =0
Reads an artifact from the backing store and updates the target Tileset.
virtual Result< void > rollback()=0
Rolls back all buffered write operations in the current transaction.
virtual ChainableResult< void > write(const ArtifactKey &dest_key, const TilesetArtifact &artifact, const Tileset &src)=0
Writes an artifact from a Tileset to the backing store.
virtual ChainableResult< void > commit()=0
Commits all buffered write operations in the current transaction.
virtual Result< void > begin_transaction()=0
Begins a new transaction for atomic write operations.
Represents a Pokémon Generation III decomp tileset artifact with type and optional metadata.
Type
Enumeration of all supported tileset artifact types.
ChainableResult< void > save(const Tileset &tileset) const
Persists a given Tileset and caches new artifact checksums.
bool exists(const std::string &name) const
Checks if the given Tileset exists in the backing store.
ChainableResult< std::unique_ptr< Tileset > > load(const std::string &name) const
Loads an existing Tileset from storage.
A complete tileset containing both Porytiles and Porymap components.
Definition tileset.hpp:14
const std::string & name() const
Definition tileset.hpp:32
constexpr std::size_t num_pals
Definition palette.hpp:16
@ bold
Bold text formatting.