Porytiles
Loading...
Searching...
No Matches
metatile_attribute_schema.cpp
Go to the documentation of this file.
2
3#include <bit>
4#include <cstddef>
5#include <cstdint>
6#include <format>
7#include <optional>
8#include <string>
9#include <unordered_set>
10#include <utility>
11#include <vector>
12
16
17namespace porytiles {
18
19namespace {
20
21std::string hex_string(std::uint32_t mask)
22{
23 return std::format("0x{:X}", mask);
24}
25
33bool is_contiguous(std::uint32_t mask)
34{
35 return std::popcount(mask) == std::bit_width(mask) - std::countr_zero(mask);
36}
37
38} // namespace
39
40EnumDefinition ProviderDefinition::to_enum_definition(std::string field_display_name, std::uint32_t max_value) const
41{
42 return EnumDefinition{
43 .prefix = prefix,
44 .max_value = max_value,
45 .skipped = skipped,
46 .format = format,
47 .field_display_name = std::move(field_display_name)};
48}
49
50ChainableResult<Schema> Schema::create(std::vector<Field> fields, std::size_t attribute_bytes)
51{
54 "Schema::create requires a 1-byte, 2-byte, or 4-byte attribute size");
55
56 std::unordered_set<std::string> seen_names;
57 std::optional<std::size_t> layer_type_index;
58
59 for (std::size_t i = 0; i < fields.size(); ++i) {
60 const Field &field = fields[i];
61 const std::uint32_t mask = field.mask();
62
63 if (seen_names.contains(field.name())) {
64 return FormattableError{
65 "Field '{}' is defined more than once in the schema.", FormatParam{field.name(), Style::bold}};
66 }
67
68 // The attributes CSV header partitions its columns by name: pin_column_prefix marks a role pin column,
69 // everything else is a value column. That partition is a property of the schema, so the schema is where it is
70 // enforced. This is not the old rule that reserved the bare name "layer_type": that one blocked a name
71 // Porytiles' own inference produces, on behalf of a CSV detail the domain layer could not see. This one
72 // reserves a namespace no C macro suffix can reach, and every field name outside it stays available.
73 if (is_pin_column_name(field.name())) {
74 return FormattableError{
75 "Field '{}' starts with '{}', which is reserved for the attributes.csv role pin columns. Choose a "
76 "field name outside that namespace.",
79 }
80
81 if (mask == 0) {
82 return FormattableError{
83 "Field '{}' has a zero mask: a field must occupy at least one bit.",
84 FormatParam{field.name(), Style::bold}};
85 }
86
87 if (!is_contiguous(mask)) {
88 return FormattableError{
89 "Field '{}' has mask '{}': a field mask must be a single contiguous run of bits.",
91 FormatParam{hex_string(mask), Style::bold}};
92 }
93
94 if (static_cast<std::size_t>(std::bit_width(mask)) > attribute_bytes * 8) {
95 return FormattableError{
96 "Field '{}' has mask '{}', which extends beyond the '{}'-byte metatile attribute size.",
98 FormatParam{hex_string(mask), Style::bold},
100 }
101
102 if (field.default_value() > field.max_value()) {
103 return FormattableError{
104 "Field '{}' has default value '{}', which does not fit in its {}-bit mask '{}'.",
105 FormatParam{field.name(), Style::bold},
107 FormatParam{field.width()},
108 FormatParam{hex_string(mask), Style::bold}};
109 }
110
111 // The layer_type role marks the one field whose values Porytiles manages (compile-time inference or a CSV
112 // pin), so a provider or a default on it can never take effect and a second role field would be ambiguous.
113 if (field.packs_layer_type()) {
114 if (layer_type_index.has_value()) {
115 return FormattableError{
116 "Field '{}' carries the layer_type role, but field '{}' already carries it. Only one field may "
117 "pack the layer type.",
118 FormatParam{field.name(), Style::bold},
119 FormatParam{fields[layer_type_index.value()].name(), Style::bold}};
120 }
121 if (field.has_provider()) {
122 return FormattableError{
123 "Field '{}' carries the layer_type role, so it cannot have a value provider: its values are "
124 "managed by Porytiles.",
125 FormatParam{field.name(), Style::bold}};
126 }
127 if (field.default_value() != 0) {
128 return FormattableError{
129 "Field '{}' carries the layer_type role, so it cannot have a default value: its values are "
130 "managed by Porytiles.",
131 FormatParam{field.name(), Style::bold}};
132 }
133 layer_type_index = i;
134 }
135
136 for (std::size_t j = 0; j < i; ++j) {
137 const Field &prior = fields[j];
138 if ((prior.mask() & mask) != 0) {
139 return FormattableError{
140 "Field '{}' has mask '{}', which overlaps mask '{}' of field '{}'.",
141 FormatParam{field.name(), Style::bold},
142 FormatParam{hex_string(mask), Style::bold},
143 FormatParam{hex_string(prior.mask()), Style::bold},
144 FormatParam{prior.name(), Style::bold}};
145 }
146 }
147
148 seen_names.insert(field.name());
149 }
150
151 return Schema{std::move(fields), attribute_bytes, layer_type_index};
152}
153
154} // namespace porytiles
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 std::string & name() const
std::uint32_t default_value() const
bool packs_layer_type() const
Reports whether this field carries the layer_type role.
std::uint32_t width() const
Returns the number of bits the field occupies.
std::uint32_t mask() const
std::uint32_t max_value() const
Returns the largest value the field can hold.
A text parameter with associated styling for formatted output.
General-purpose error implementation with formatted message support.
Definition error.hpp:57
A validated metatile attribute layout: an ordered set of non-overlapping fields.
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 > & fields() const
std::size_t attribute_bytes() const
static const Style bold
Bold text formatting.
bool is_pin_column_name(const std::string &name)
Reports whether a column name sits in the reserved pin namespace.
void assert_or_panic(bool condition, const StringViewSourceLoc &s)
Conditionally panics if the given condition is false.
Definition panic.cpp:53
constexpr std::string_view pin_column_prefix
The reserved prefix marking an attributes.csv header column as a role pin column.
The self-contained description a header enum provider needs to scan and validate values.
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.