Porytiles
Loading...
Searching...
No Matches
project_tileset_metadata_provider.cpp
Go to the documentation of this file.
2
3#include <array>
4#include <filesystem>
5#include <map>
6#include <optional>
7#include <set>
8#include <string>
9#include <utility>
10#include <vector>
11
19
20namespace {
21
22using namespace porytiles;
23using namespace porytiles::detail;
24
25const std::filesystem::path headers_rel_path = std::filesystem::path{"src"} / "data" / "tilesets" / "headers.h";
26
27// Project source file paths for INCBIN parsing
28const std::filesystem::path graphics_rel_path = std::filesystem::path{"src"} / "data" / "tilesets" / "graphics.h";
29const std::filesystem::path metatiles_rel_path = std::filesystem::path{"src"} / "data" / "tilesets" / "metatiles.h";
30const std::filesystem::path src_graphics_rel_path = std::filesystem::path{"src"} / "graphics.c";
31
33[[nodiscard]] std::optional<IncbinResolutionFailure>
34classify_incbin_lookup(const std::map<std::string, IncbinDeclaration> &incbin_vars, const std::string &variable_name)
35{
36 if (!incbin_vars.contains(variable_name)) {
37 return IncbinResolutionFailure::not_found;
38 }
39 if (incbin_vars.at(variable_name).paths().empty()) {
40 return IncbinResolutionFailure::empty_paths;
41 }
42 return std::nullopt;
43}
44
50[[nodiscard]] FormattableError build_unresolved_artifact_paths_error(
51 const std::string &tileset_name, const std::vector<UnresolvedIncbinVar> &unresolved, const TextFormatter *format)
52{
53 std::vector<std::string> lines;
54
55 lines.push_back(
56 format->format("Could not resolve artifact paths for tileset '{}'.", FormatParam{tileset_name, Style::bold}));
57 lines.emplace_back("");
58 lines.emplace_back("The following INCBIN variable(s) could not be resolved:");
59
60 for (const auto &var : unresolved) {
61 if (var.reason == IncbinResolutionFailure::not_found) {
62 lines.push_back(format->format(
63 " '{}' (field '{}') was not found in any scanned file.",
64 FormatParam{var.variable_name, Style::bold},
65 FormatParam{var.field_label, Style::bold}));
66 }
67 else {
68 lines.push_back(format->format(
69 " '{}' (field '{}') was found but declared no INCBIN path.",
70 FormatParam{var.variable_name, Style::bold},
71 FormatParam{var.field_label, Style::bold}));
72 }
73 }
74
75 lines.emplace_back("");
76 lines.push_back(format->format(
77 "Scanned files: '{}', '{}', '{}'.",
78 FormatParam{graphics_rel_path.string(), Style::bold},
79 FormatParam{metatiles_rel_path.string(), Style::bold},
80 FormatParam{src_graphics_rel_path.string(), Style::bold}));
81
82 return FormattableError{std::move(lines)};
83}
84
97[[nodiscard]] ChainableResult<void> ensure_metadata_parsed(
98 const std::filesystem::path &project_root,
99 bool &metadata_parsed,
100 std::map<std::string, ProjectTilesetMetadata> &tileset_metadata,
101 const TextFormatter *format)
102{
103 if (metadata_parsed) {
104 return {};
105 }
106
107 const auto headers_path = project_root / headers_rel_path;
108 CParserFacade parser{headers_path, format};
109
111 struct_decls,
112 parser.parse_struct_initializers("gTileset_"),
113 void,
114 format->format("Failed to parse tileset headers from '{}'.", FormatParam{headers_path.string(), Style::bold}));
115
116 for (auto &struct_decl : struct_decls) {
117 const auto &tileset_name = struct_decl.variable_name();
118
119 auto is_secondary_opt = struct_decl.field_value("isSecondary");
120 bool is_secondary = is_secondary_opt.has_value() && is_secondary_opt.value() == "TRUE";
121
122 auto tiles_var = struct_decl.field_value("tiles");
123 auto palettes_var = struct_decl.field_value("palettes");
124 auto metatiles_var = struct_decl.field_value("metatiles");
125 auto metatile_attributes_var = struct_decl.field_value("metatileAttributes");
126 auto callback_var = struct_decl.field_value("callback");
127
128 if (!tiles_var.has_value() || !palettes_var.has_value() || !metatiles_var.has_value() ||
129 !metatile_attributes_var.has_value()) {
130 continue;
131 }
132
133 std::optional<std::string> callback_func = std::nullopt;
134 if (callback_var.has_value() && callback_var.value() != "NULL") {
135 callback_func = callback_var.value();
136 }
137
138 tileset_metadata.emplace(
139 tileset_name,
141 tileset_name,
142 is_secondary,
143 tiles_var.value(),
144 palettes_var.value(),
145 metatiles_var.value(),
146 metatile_attributes_var.value(),
147 callback_func});
148 }
149
150 metadata_parsed = true;
151 return {};
152}
153
169[[nodiscard]] ChainableResult<void> ensure_artifact_paths_parsed(
170 const std::filesystem::path &project_root,
171 bool &metadata_parsed,
172 std::map<std::string, ProjectTilesetMetadata> &tileset_metadata,
173 bool &artifact_paths_parsed,
174 std::map<std::string, ProjectTilesetArtifactPaths> &tileset_artifact_paths,
175 std::map<std::string, std::vector<UnresolvedIncbinVar>> &tileset_unresolved_vars,
176 const TextFormatter *format)
177{
178 if (artifact_paths_parsed) {
179 return {};
180 }
181
183 ensure_metadata_parsed(project_root, metadata_parsed, tileset_metadata, format),
184 void,
185 "Failed to resolve artifact paths.");
186
187 // Parse all INCBIN sources into a local map
188 std::map<std::string, IncbinDeclaration> incbin_vars;
189
190 const auto graphics_path = project_root / graphics_rel_path;
191 CParserFacade graphics_parser{graphics_path, format};
193 graphics_incbins,
194 graphics_parser.parse_incbin_arrays(),
195 void,
196 format->format(
197 "Failed to parse graphics INCBINs from '{}'.", FormatParam{graphics_path.string(), Style::bold}));
198 for (auto &incbin : graphics_incbins) {
199 incbin_vars.emplace(incbin.variable_name(), std::move(incbin));
200 }
201
202 const auto metatiles_path = project_root / metatiles_rel_path;
203 CParserFacade metatiles_parser{metatiles_path, format};
205 metatiles_incbins,
206 metatiles_parser.parse_incbin_arrays(),
207 void,
208 format->format(
209 "Failed to parse metatiles INCBINs from '{}'.", FormatParam{metatiles_path.string(), Style::bold}));
210 for (auto &incbin : metatiles_incbins) {
211 incbin_vars.emplace(incbin.variable_name(), std::move(incbin));
212 }
213
214 const auto src_graphics_path = project_root / src_graphics_rel_path;
215 CParserFacade src_graphics_parser{src_graphics_path, format};
217 src_graphics_incbins,
218 src_graphics_parser.parse_incbin_arrays(),
219 void,
220 format->format(
221 "Failed to parse graphics INCBINs from '{}'.", FormatParam{src_graphics_path.string(), Style::bold}));
222 for (auto &incbin : src_graphics_incbins) {
223 incbin_vars.emplace(incbin.variable_name(), std::move(incbin));
224 }
225
226 // Resolve paths per-tileset, classifying each of the four artifact fields independently so that a failure can be
227 // reported with the exact field, variable, and reason for failure
228 for (const auto &[tileset_name, metadata] : tileset_metadata) {
229 const std::array<std::pair<std::string, std::string>, 4> fields{{
230 {"tiles", metadata.tiles_var()},
231 {"palettes", metadata.palettes_var()},
232 {"metatiles", metadata.metatiles_var()},
233 {"metatileAttributes", metadata.metatile_attributes_var()},
234 }};
235
236 std::vector<UnresolvedIncbinVar> unresolved;
237 for (const auto &[field_label, variable_name] : fields) {
238 auto failure = classify_incbin_lookup(incbin_vars, variable_name);
239 if (failure.has_value()) {
240 unresolved.push_back(UnresolvedIncbinVar{field_label, variable_name, failure.value()});
241 }
242 }
243
244 if (!unresolved.empty()) {
245 tileset_unresolved_vars.emplace(tileset_name, std::move(unresolved));
246 continue;
247 }
248
249 const auto &tiles_incbin = incbin_vars.at(metadata.tiles_var());
250 const auto &palettes_incbin = incbin_vars.at(metadata.palettes_var());
251 const auto &metatiles_incbin = incbin_vars.at(metadata.metatiles_var());
252 const auto &attributes_incbin = incbin_vars.at(metadata.metatile_attributes_var());
253
254 std::vector<std::filesystem::path> palette_paths;
255 palette_paths.reserve(palettes_incbin.paths().size());
256 for (const auto &path_str : palettes_incbin.paths()) {
257 palette_paths.emplace_back(path_str);
258 }
259
260 tileset_artifact_paths.emplace(
261 tileset_name,
263 std::filesystem::path{tiles_incbin.paths().front()},
264 std::move(palette_paths),
265 std::filesystem::path{metatiles_incbin.paths().front()},
266 std::filesystem::path{attributes_incbin.paths().front()}});
267 }
268
269 artifact_paths_parsed = true;
270 return {};
271}
272
273} // namespace
274
275namespace porytiles {
276
277bool ProjectTilesetMetadataProvider::exists(const std::string &tileset_name) const
278{
279 const auto metadata_result = metadata_for(tileset_name);
280 return metadata_result.has_value();
281}
282
284{
285 PT_TRY_ASSIGN_PASS_ERR(metadata, metadata_for(tileset_name), bool);
286 return metadata.is_secondary();
287}
288
290{
291 PT_TRY_ASSIGN_PASS_ERR(metadata, metadata_for(tileset_name), bool);
292 return metadata.has_animations();
293}
294
296{
298 ensure_metadata_parsed(project_root_, metadata_parsed_, tileset_metadata_, format_),
299 std::set<std::string>,
300 "Failed to enumerate tilesets.");
301
302 std::set<std::string> names;
303 for (const auto &[tileset_name, metadata] : tileset_metadata_) {
304 names.insert(tileset_name);
305 }
306 return names;
307}
308
310ProjectTilesetMetadataProvider::metadata_for(const std::string &tileset_name) const
311{
313 ensure_metadata_parsed(project_root_, metadata_parsed_, tileset_metadata_, format_),
315 format_->format("Failed to get metadata for tileset '{}'.", FormatParam{tileset_name, Style::bold}));
316
317 if (!tileset_metadata_.contains(tileset_name)) {
318 return FormattableError{
319 format_->format("Tileset '{}' not found in headers.h file.", FormatParam{tileset_name, Style::bold})};
320 }
321
322 return tileset_metadata_.at(tileset_name);
323}
324
326ProjectTilesetMetadataProvider::artifact_paths_for(const std::string &tileset_name) const
327{
329 ensure_artifact_paths_parsed(
330 project_root_,
331 metadata_parsed_,
332 tileset_metadata_,
333 artifact_paths_parsed_,
334 tileset_artifact_paths_,
335 tileset_unresolved_vars_,
336 format_),
338 format_->format("Failed to get artifact paths for tileset '{}'.", FormatParam{tileset_name, Style::bold}));
339
340 if (!tileset_artifact_paths_.contains(tileset_name)) {
341 if (tileset_unresolved_vars_.contains(tileset_name)) {
342 return build_unresolved_artifact_paths_error(
343 tileset_name, tileset_unresolved_vars_.at(tileset_name), format_);
344 }
345 return FormattableError{format_->format(
346 "Could not resolve artifact paths for tileset '{}'.", FormatParam{tileset_name, Style::bold})};
347 }
348
349 return tileset_artifact_paths_.at(tileset_name);
350}
351
353{
354 metadata_parsed_ = false;
355 tileset_metadata_.clear();
356 artifact_paths_parsed_ = false;
357 tileset_artifact_paths_.clear();
358 tileset_unresolved_vars_.clear();
359}
360
361} // namespace porytiles
#define PT_TRY_ASSIGN_CHAIN_ERR(var, expr, return_type,...)
Unwraps a ChainableResult, chaining a new error message on failure.
#define PT_TRY_ASSIGN_PASS_ERR(var, expr, return_type)
Unwraps a ChainableResult, passing through the error chain with an empty FormattableError when types ...
#define PT_TRY_CALL_CHAIN_ERR(expr, return_type,...)
Unwraps a void ChainableResult, chaining a new error message on failure.
High-level facade for parsing C/C++ source files.
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:57
Represents resolved filesystem paths for tileset artifacts from INCBIN declarations.
void invalidate_metadata_cache() const
Invalidates the lazy-loaded metadata and artifact paths caches, forcing a re-parse on next access.
ChainableResult< ProjectTilesetArtifactPaths > artifact_paths_for(const std::string &tileset_name) const
Resolves artifact paths for a tileset by parsing INCBIN declarations.
ChainableResult< std::set< std::string > > tilesets() const override
Returns all tileset names known to this provider.
bool exists(const std::string &tileset_name) const override
Checks whether a tileset exists in the backing store for the given tileset name.
ChainableResult< ProjectTilesetMetadata > metadata_for(const std::string &tileset_name) const
Retrieves metadata for a specific tileset from the struct cache.
ChainableResult< bool > has_animations(const std::string &tileset_name) const override
Determines whether a tileset has animation support.
ChainableResult< bool > is_secondary(const std::string &tileset_name) const override
Determines whether a tileset is a secondary tileset.
Represents raw parsed metadata from a tileset struct in headers.h.
static const Style bold
Bold text formatting.
Abstract base class for applying text styling with context-aware formatting.
virtual std::string format(const std::string &format_str, const std::vector< FormatParam > &params) const
Formats a string with styled parameters using fmtlib syntax.