Porytiles
Loading...
Searching...
No Matches
metatile_attribute_schema.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <bit>
4#include <cstddef>
5#include <cstdint>
6#include <filesystem>
7#include <format>
8#include <optional>
9#include <ostream>
10#include <string>
11#include <string_view>
12#include <unordered_set>
13#include <utility>
14#include <vector>
15
18
19namespace porytiles {
20
28
33[[nodiscard]] inline std::string to_string(HeaderFormat format)
34{
35 switch (format) {
37 return "defines-only";
39 return "enums-only";
41 return "either";
42 }
43 panic("unhandled HeaderFormat value");
44}
45
51inline std::ostream &operator<<(std::ostream &os, const HeaderFormat format)
52{
53 return os << to_string(format);
54}
55
64enum class FieldRole { layer_type };
65
70[[nodiscard]] inline std::string to_string(FieldRole role)
71{
72 switch (role) {
74 return "layer_type";
75 }
76 panic("unhandled FieldRole value");
77}
78
88[[nodiscard]] inline std::optional<FieldRole> field_role_from_string(const std::string &text)
89{
90 if (text == "layer_type") {
92 }
93 return std::nullopt;
94}
95
101inline std::ostream &operator<<(std::ostream &os, const FieldRole role)
102{
103 return os << to_string(role);
104}
105
114inline constexpr std::string_view pin_column_prefix = "pin::";
115
124[[nodiscard]] inline std::string pin_column_name(FieldRole role)
125{
126 return std::string{pin_column_prefix} + to_string(role);
127}
128
133[[nodiscard]] inline bool is_pin_column_name(const std::string &name)
134{
135 return name.starts_with(pin_column_prefix);
136}
137
146[[nodiscard]] inline std::optional<FieldRole> role_from_pin_column_name(const std::string &name)
147{
148 if (!is_pin_column_name(name)) {
149 return std::nullopt;
150 }
151 return field_role_from_string(name.substr(pin_column_prefix.size()));
152}
153
154struct EnumDefinition;
155
163 std::filesystem::path header;
164 std::string prefix;
165 std::unordered_set<std::string> skipped{};
167
168 bool operator==(const ProviderDefinition &) const = default;
169
181 [[nodiscard]] EnumDefinition to_enum_definition(std::string field_display_name, std::uint32_t max_value) const;
182};
183
192 std::string prefix;
193 std::uint32_t max_value;
194 std::unordered_set<std::string> skipped{};
197
198 bool operator==(const EnumDefinition &) const = default;
199};
200
216class Field {
217 public:
226 std::string name,
227 std::uint32_t mask,
228 std::uint32_t default_value = 0,
229 std::optional<ProviderDefinition> provider = std::nullopt,
230 std::optional<FieldRole> role = std::nullopt)
231 : name_{std::move(name)}, mask_{mask}, default_value_{default_value}, provider_{std::move(provider)},
232 role_{role}
233 {
234 }
235
236 [[nodiscard]] const std::string &name() const
237 {
238 return name_;
239 }
240
241 [[nodiscard]] std::uint32_t mask() const
242 {
243 return mask_;
244 }
245
246 [[nodiscard]] std::uint32_t default_value() const
247 {
248 return default_value_;
249 }
250
255 [[nodiscard]] std::uint32_t offset() const
256 {
257 return static_cast<std::uint32_t>(std::countr_zero(mask_));
258 }
259
263 [[nodiscard]] std::uint32_t width() const
264 {
265 return static_cast<std::uint32_t>(std::popcount(mask_));
266 }
267
276 [[nodiscard]] std::uint32_t max_value() const
277 {
278 return mask_ >> offset();
279 }
280
281 [[nodiscard]] bool has_provider() const
282 {
283 return provider_.has_value();
284 }
285
286 [[nodiscard]] const std::optional<FieldRole> &role() const
287 {
288 return role_;
289 }
290
299 [[nodiscard]] bool packs_layer_type() const
300 {
301 return role_ == FieldRole::layer_type;
302 }
303
308 [[nodiscard]] const ProviderDefinition &provider_definition() const
309 {
310 assert_or_panic(provider_.has_value(), "Field::provider_definition() called on a field with no provider");
311 return provider_.value();
312 }
313
314 private:
315 std::string name_;
316 std::uint32_t mask_;
317 std::uint32_t default_value_;
318 std::optional<ProviderDefinition> provider_;
319 std::optional<FieldRole> role_;
320};
321
335class Schema {
336 public:
351 [[nodiscard]] static ChainableResult<Schema> create(std::vector<Field> fields, std::size_t attribute_bytes);
352
353 [[nodiscard]] const std::vector<Field> &fields() const
354 {
355 return fields_;
356 }
357
368 [[nodiscard]] const std::vector<Field> &value_fields() const
369 {
370 return value_fields_;
371 }
372
373 [[nodiscard]] std::size_t attribute_bytes() const
374 {
375 return attribute_bytes_;
376 }
377
381 [[nodiscard]] const Field *layer_type_field() const
382 {
383 return layer_type_index_.has_value() ? &fields_[layer_type_index_.value()] : nullptr;
384 }
385
394 [[nodiscard]] std::uint32_t layer_type_mask() const
395 {
396 return layer_type_mask_;
397 }
398
406 [[nodiscard]] std::uint32_t layer_type_offset() const
407 {
408 return static_cast<std::uint32_t>(std::countr_zero(layer_type_mask_));
409 }
410
411 private:
412 Schema(std::vector<Field> fields, std::size_t attribute_bytes, std::optional<std::size_t> layer_type_index)
413 : fields_{std::move(fields)}, attribute_bytes_{attribute_bytes}, layer_type_index_{layer_type_index},
414 layer_type_mask_{layer_type_index.has_value() ? fields_[layer_type_index.value()].mask() : 0}
415 {
416 for (const Field &field : fields_) {
417 if (!field.packs_layer_type()) {
418 value_fields_.push_back(field);
419 }
420 }
421 }
422
423 std::vector<Field> fields_;
424 // The non-role fields, materialized once at creation so CSV-facing consumers cannot forget to skip the
425 // layer_type-role field. Safe to duplicate: a Schema is immutable after creation.
426 std::vector<Field> value_fields_;
427 std::size_t attribute_bytes_;
428 // Index rather than pointer so the cache survives Schema copies and moves.
429 std::optional<std::size_t> layer_type_index_;
430 std::uint32_t layer_type_mask_;
431};
432
433} // namespace porytiles
434
435template <>
436struct std::formatter<porytiles::HeaderFormat> {
437 constexpr auto parse(std::format_parse_context &ctx)
438 {
439 return ctx.begin();
440 }
441
442 auto format(const porytiles::HeaderFormat &value, auto &ctx) const
443 {
444 return std::format_to(ctx.out(), "{}", porytiles::to_string(value));
445 }
446};
447
448template <>
449struct std::formatter<porytiles::FieldRole> {
450 constexpr auto parse(std::format_parse_context &ctx)
451 {
452 return ctx.begin();
453 }
454
455 auto format(const porytiles::FieldRole &value, auto &ctx) const
456 {
457 return std::format_to(ctx.out(), "{}", porytiles::to_string(value));
458 }
459};
A result type that maintains a chainable sequence of errors for debugging and error reporting.
One named bit-field within a metatile attribute layout.
const ProviderDefinition & provider_definition() const
Returns the field's provider definition.
const std::string & name() const
std::uint32_t offset() const
Returns the bit offset of the field's least-significant bit.
std::uint32_t default_value() const
bool packs_layer_type() const
Reports whether this field carries the layer_type role.
const std::optional< FieldRole > & role() const
std::uint32_t width() const
Returns the number of bits the field occupies.
Field(std::string name, std::uint32_t mask, std::uint32_t default_value=0, std::optional< ProviderDefinition > provider=std::nullopt, std::optional< FieldRole > role=std::nullopt)
Constructs a field from a name, mask, and optional default value, provider definition,...
std::uint32_t mask() const
std::uint32_t max_value() const
Returns the largest value the field can hold.
A validated metatile attribute layout: an ordered set of non-overlapping fields.
std::uint32_t layer_type_offset() const
Returns the bit offset of the layer_type's least-significant bit.
static ChainableResult< Schema > create(std::vector< Field > fields, std::size_t attribute_bytes)
Validates a set of fields against an attribute size and builds a Schema.
const std::vector< Field > & value_fields() const
Returns the fields that hold plain per-metatile values, excluding the layer_type-role field.
std::uint32_t layer_type_mask() const
Returns the mask of the layer_type bits within the packed attribute word.
const Field * layer_type_field() const
Returns the field carrying the layer_type role, or nullptr when the layer type is disabled.
const std::vector< Field > & fields() const
std::size_t attribute_bytes() const
std::string name
std::optional< ProviderDefinition > provider
std::string pin_column_name(FieldRole role)
Returns the one legal attributes.csv column name for a role's pin column.
bool is_pin_column_name(const std::string &name)
Reports whether a column name sits in the reserved pin namespace.
void panic(const StringViewSourceLoc &s)
Unconditionally terminates the program with a panic message.
Definition panic.cpp:43
void assert_or_panic(bool condition, const StringViewSourceLoc &s)
Conditionally panics if the given condition is false.
Definition panic.cpp:53
std::ostream & operator<<(std::ostream &os, const PrimaryPairingMode m)
Stream insertion operator for PrimaryPairingMode.
constexpr std::string_view pin_column_prefix
The reserved prefix marking an attributes.csv header column as a role pin column.
HeaderFormat
Describes which header declaration styles a provider header may use.
std::optional< FieldRole > field_role_from_string(const std::string &text)
Parses a FieldRole from its string form, or nullopt when the name is not a known role.
FieldRole
Semantic roles a schema field can carry beyond holding a plain per-metatile value.
std::optional< FieldRole > role_from_pin_column_name(const std::string &name)
Parses the role out of a pin column name.
std::string to_string(const PrimaryPairingMode m)
Converts a PrimaryPairingMode to its canonical string representation.
The self-contained description a header enum provider needs to scan and validate values.
bool operator==(const EnumDefinition &) const =default
std::unordered_set< std::string > skipped
Describes where and how a field's named values are declared.
std::unordered_set< std::string > skipped
EnumDefinition to_enum_definition(std::string field_display_name, std::uint32_t max_value) const
Builds the resolvable enum definition a header provider needs from this description.
bool operator==(const ProviderDefinition &) const =default
constexpr auto parse(std::format_parse_context &ctx)
auto format(const porytiles::FieldRole &value, auto &ctx) const
constexpr auto parse(std::format_parse_context &ctx)
auto format(const porytiles::HeaderFormat &value, auto &ctx) const