Porytiles
Loading...
Searching...
No Matches
project_layout_metadata_provider.cpp
Go to the documentation of this file.
2
3#include <filesystem>
4#include <fstream>
5#include <map>
6#include <set>
7#include <string>
8#include <vector>
9
10#include "nlohmann/json.hpp"
11
15
16namespace {
17
18using namespace porytiles;
19
20const std::filesystem::path layouts_json_rel_path = std::filesystem::path{"data"} / "layouts" / "layouts.json";
21
38[[nodiscard]] ChainableResult<void> ensure_layouts_parsed(
39 const std::filesystem::path &project_root,
40 bool &layouts_parsed,
41 std::string &layouts_table_label,
42 std::vector<ProjectLayoutMetadata> &layout_entries,
43 std::map<std::string, std::size_t> &layout_index,
44 const TextFormatter *format)
45{
46 if (layouts_parsed) {
47 return {};
48 }
49
50 const auto layouts_path = project_root / layouts_json_rel_path;
51
52 std::ifstream file{layouts_path};
53 if (!file.is_open()) {
54 return FormattableError{
55 format->format("Failed to open layouts file '{}'.", FormatParam{layouts_path.string(), Style::bold})};
56 }
57
58 nlohmann::json json_data;
59 try {
60 file >> json_data;
61 }
62 catch (const nlohmann::json::parse_error &e) {
63 return FormattableError{format->format(
64 "Failed to parse layouts JSON from '{}': {}.",
65 FormatParam{layouts_path.string(), Style::bold},
66 FormatParam{std::string{e.what()}})};
67 }
68
69 if (!json_data.contains("layouts_table_label")) {
70 return FormattableError{format->format(
71 "Layouts file '{}' missing required field 'layouts_table_label'.",
72 FormatParam{layouts_path.string(), Style::bold})};
73 }
74
75 if (!json_data.contains("layouts")) {
76 return FormattableError{format->format(
77 "Layouts file '{}' missing required field 'layouts'.", FormatParam{layouts_path.string(), Style::bold})};
78 }
79
80 /*
81 * Build into local temporaries so that a failure mid-loop does not leave the mutable cache in a partially populated
82 * state. Only commit to the real cache on full success.
83 */
84 auto table_label = json_data.at("layouts_table_label").get<std::string>();
85
86 const auto &layouts_array = json_data.at("layouts");
87 std::vector<ProjectLayoutMetadata> entries;
88 std::map<std::string, std::size_t> index;
89 entries.reserve(layouts_array.size());
90
91 for (const auto &entry : layouts_array) {
92 try {
93 auto id = entry.at("id").get<std::string>();
94 auto name = entry.at("name").get<std::string>();
95 auto width = entry.at("width").get<std::size_t>();
96 auto height = entry.at("height").get<std::size_t>();
97 auto primary_tileset = entry.at("primary_tileset").get<std::string>();
98 auto secondary_tileset = entry.at("secondary_tileset").get<std::string>();
99 auto border_fp = std::filesystem::path{entry.at("border_filepath").get<std::string>()};
100 auto blockdata_fp = std::filesystem::path{entry.at("blockdata_filepath").get<std::string>()};
101
102 const auto idx = entries.size();
103 entries.push_back(
105 std::move(id),
106 std::move(name),
107 width,
108 height,
109 std::move(primary_tileset),
110 std::move(secondary_tileset),
111 std::move(border_fp),
112 std::move(blockdata_fp)});
113
114 index.emplace(entries.at(idx).id(), idx);
115 index.emplace(entries.at(idx).name(), idx);
116 }
117 catch (const nlohmann::json::exception &e) {
118 return FormattableError{format->format(
119 "Malformed layout entry in '{}': {}.",
120 FormatParam{layouts_path.string(), Style::bold},
121 FormatParam{std::string{e.what()}})};
122 }
123 }
124
125 layouts_table_label = std::move(table_label);
126 layout_entries = std::move(entries);
127 layout_index = std::move(index);
128 layouts_parsed = true;
129 return {};
130}
131
148[[nodiscard]] ChainableResult<const ProjectLayoutMetadata *> lookup_layout(
149 const std::string &layout_name_or_id,
150 const std::filesystem::path &project_root,
151 bool &layouts_parsed,
152 std::string &layouts_table_label,
153 std::vector<ProjectLayoutMetadata> &layout_entries,
154 std::map<std::string, std::size_t> &layout_index,
155 const TextFormatter *format)
156{
157 if (const auto ensure_result = ensure_layouts_parsed(
158 project_root, layouts_parsed, layouts_table_label, layout_entries, layout_index, format);
159 !ensure_result.has_value()) {
162 format->format("Failed to look up layout '{}'.", FormatParam{layout_name_or_id, Style::bold})},
163 ensure_result};
164 }
165
166 if (!layout_index.contains(layout_name_or_id)) {
167 return FormattableError{
168 format->format("Layout '{}' not found in layouts.json.", FormatParam{layout_name_or_id, Style::bold})};
169 }
170
171 return &layout_entries.at(layout_index.at(layout_name_or_id));
172}
173
174} // namespace
175
176namespace porytiles {
177
178bool ProjectLayoutMetadataProvider::exists(const std::string &layout_name_or_id) const
179{
180 if (const auto result = ensure_layouts_parsed(
181 project_root_, layouts_parsed_, layouts_table_label_, layout_entries_, layout_index_, format_);
182 !result.has_value()) {
183 return false;
184 }
185 return layout_index_.contains(layout_name_or_id);
186}
187
189{
191 layout,
192 ::lookup_layout(
193 layout_name_or_id,
194 project_root_,
195 layouts_parsed_,
196 layouts_table_label_,
197 layout_entries_,
198 layout_index_,
199 format_),
200 std::size_t);
201 return layout->width();
202}
203
205{
207 layout,
208 ::lookup_layout(
209 layout_name_or_id,
210 project_root_,
211 layouts_parsed_,
212 layouts_table_label_,
213 layout_entries_,
214 layout_index_,
215 format_),
216 std::size_t);
217 return layout->height();
218}
219
221{
223 layout,
224 ::lookup_layout(
225 layout_name_or_id,
226 project_root_,
227 layouts_parsed_,
228 layouts_table_label_,
229 layout_entries_,
230 layout_index_,
231 format_),
232 std::string);
233 return layout->primary_tileset();
234}
235
237ProjectLayoutMetadataProvider::secondary_tileset(const std::string &layout_name_or_id) const
238{
240 layout,
241 ::lookup_layout(
242 layout_name_or_id,
243 project_root_,
244 layouts_parsed_,
245 layouts_table_label_,
246 layout_entries_,
247 layout_index_,
248 format_),
249 std::string);
250 return layout->secondary_tileset();
251}
252
254{
256 ensure_layouts_parsed(
257 project_root_, layouts_parsed_, layouts_table_label_, layout_entries_, layout_index_, format_),
258 std::set<std::string>,
259 "Failed to enumerate layout names.");
260
261 std::set<std::string> names;
262 for (const auto &entry : layout_entries_) {
263 names.insert(entry.name());
264 }
265 return names;
266}
267
269{
271 ensure_layouts_parsed(
272 project_root_, layouts_parsed_, layouts_table_label_, layout_entries_, layout_index_, format_),
273 std::set<std::string>,
274 "Failed to enumerate layout IDs.");
275
276 std::set<std::string> ids;
277 for (const auto &entry : layout_entries_) {
278 ids.insert(entry.id());
279 }
280 return ids;
281}
282
284{
286 ensure_layouts_parsed(
287 project_root_, layouts_parsed_, layouts_table_label_, layout_entries_, layout_index_, format_),
288 std::string,
289 "Failed to get layouts table label.");
290 return layouts_table_label_;
291}
292
294ProjectLayoutMetadataProvider::border_filepath(const std::string &layout_name_or_id) const
295{
297 layout,
298 ::lookup_layout(
299 layout_name_or_id,
300 project_root_,
301 layouts_parsed_,
302 layouts_table_label_,
303 layout_entries_,
304 layout_index_,
305 format_),
306 std::filesystem::path);
307 return project_root_ / layout->border_filepath();
308}
309
311ProjectLayoutMetadataProvider::blockdata_filepath(const std::string &layout_name_or_id) const
312{
314 layout,
315 ::lookup_layout(
316 layout_name_or_id,
317 project_root_,
318 layouts_parsed_,
319 layouts_table_label_,
320 layout_entries_,
321 layout_index_,
322 format_),
323 std::filesystem::path);
324 return project_root_ / layout->blockdata_filepath();
325}
326
328{
329 layouts_parsed_ = false;
330 layouts_table_label_.clear();
331 layout_entries_.clear();
332 layout_index_.clear();
333}
334
335} // namespace porytiles
#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.
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:63
ChainableResult< std::string > primary_tileset(const std::string &layout_name_or_id) const override
Determines the given layout's primary tileset.
ChainableResult< std::set< std::string > > layout_ids() const override
Returns all layout IDs known to this provider.
ChainableResult< std::filesystem::path > blockdata_filepath(const std::string &layout_name_or_id) const
Resolves blockdata file path for a layout by parsing layouts.json.
ChainableResult< std::size_t > width(const std::string &layout_name_or_id) const override
Determines the given layout's width in metatiles.
void invalidate_metadata_cache() const
Invalidates the lazy-loaded layout cache, forcing a re-parse on next access.
ChainableResult< std::set< std::string > > layout_names() const override
Returns all layout names known to this provider.
ChainableResult< std::string > layouts_table_label() const
ChainableResult< std::filesystem::path > border_filepath(const std::string &layout_name_or_id) const
Resolves border file path for a layout by parsing layouts.json.
bool exists(const std::string &layout_name_or_id) const override
Checks whether a layout exists in the backing store for the given layout name or ID.
ChainableResult< std::string > secondary_tileset(const std::string &layout_name_or_id) const override
Determines the given layout's secondary tileset.
ChainableResult< std::size_t > height(const std::string &layout_name_or_id) const override
Determines the given layout's height in metatiles.
Represents parsed metadata for a single layout from layouts.json.
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.
@ layout
Configuration scoped to a specific layout.