wowlib 0.0.0
Read & write World of Warcraft client files — a C++26 core
Loading...
Searching...
No Matches
schema.hpp
Go to the documentation of this file.
1#pragma once
2
10
11#include <meta>
12
13#include <algorithm>
14#include <array>
15#include <ranges>
16#include <span>
17#include <concepts>
18#include <cstddef>
19#include <cstdint>
20#include <optional>
21#include <string>
22#include <string_view>
23#include <utility>
24#include <type_traits>
25#include <vector>
26
30
31namespace wowlib::db {
33 enum class [[
34 =welder::weld,
35 =welder::doc("The logical value class of a table column.")
36 ]] ColumnType : std::uint8_t {
39 String,
41 };
42
46 view of the world, not the expanded on-disk field list. */
47 struct [[
48 =welder::weld,
49 =welder::doc("One column of a table schema: its name, value class, "
50 "integer shape, array length and key roles.")
51 ]] Column {
52 [[=welder::mark::no_reassign,
53 =welder::doc("The column name: the snake_case spelling of the WoWDBDefs "
54 "column ('MapName' -> 'map_name'). This spelling is a stable "
55 "cross-release contract on every binding surface.")]]
56 const char* name = nullptr;
57 [[=welder::mark::no_reassign,
58 =welder::doc("The verbatim WoWDBDefs spelling of the column "
59 "('MapName_lang'); column_index accepts it as an alias. Empty for "
60 "columns without a DBD identity (reflection-derived schemas).")]]
61 // nullptr, not "": schemaOf's columns live in reflect_constant, where a
62 // pointer into a string literal is not a usable constant.
63 const char* dbdName = nullptr;
64 [[=welder::doc("The logical value class.")]]
66 [[=welder::doc("Integer element width in bits; 32 for float/string refs.")]]
67 std::uint8_t bits = 0;
68 [[=welder::doc("Integer signedness; false for non-Int columns.")]]
69 bool isSigned = false;
70 [[=welder::doc("Element count; 1 for scalar columns.")]]
71 std::uint16_t arrayLen = 1;
72 [[=welder::doc("LocString language slots (8 or 16); 0 otherwise.")]]
73 std::uint8_t localeCount = 0;
74 [[=welder::doc("Whether this is the table's primary key ($id$).")]]
75 bool isId = false;
76 [[=welder::doc("Whether this is a relationship key ($relation$).")]]
77 bool isRelation = false;
78 [[=welder::doc("Whether the column holds no bytes inside the record "
79 "image ($noninline$).")]]
80 bool noninline = false;
81
83 would otherwise synthesize a field-wise constructor, and its
84 `const char* name` parameter would store a pointer into the
85 marshalling temporary — Column is read-only schema metadata, never
86 constructed from a bound language. */
87 Column() = default;
88
90 @return the interned column name. */
91 constexpr std::string_view nameView() const { return name; }
92
95 constexpr std::string_view dbdNameView() const {
96 return dbdName ? std::string_view{dbdName} : std::string_view{};
97 }
98
100
102 constexpr std::size_t inlineBytes() const {
103 if (noninline) return 0;
104 switch (type) {
105 case ColumnType::Int: return static_cast<std::size_t>(bits) / 8 * arrayLen;
107 case ColumnType::String: return std::size_t{4} * arrayLen;
108 case ColumnType::LocString: return (static_cast<std::size_t>(localeCount) + 1) * 4;
109 }
110 return 0;
111 }
112
115 language slot plus the flags field, noninline columns count zero.
116 @return the field slot count. */
117 constexpr std::uint32_t fieldSlots() const {
118 if (noninline) return 0;
119 if (type == ColumnType::LocString) return static_cast<std::uint32_t>(localeCount) + 1;
120 return arrayLen;
121 }
122
126 count; 0 otherwise. */
127 constexpr std::size_t stringSlots() const {
128 if (noninline) return 0;
129 if (type == ColumnType::String) return arrayLen;
130 if (type == ColumnType::LocString) return localeCount;
131 return 0;
133 };
134
135 namespace detail {
137 template <typename T>
139 static constexpr bool Value = false;
140 };
142 template <std::size_t N>
143 struct LocstringTraits<LocString<N>> {
144 static constexpr bool Value = true;
145 static constexpr std::size_t Langs = N;
146 };
149 template <typename T>
150 struct ElementTraits {
151 using Element = T;
152 static constexpr std::size_t Extent = 1;
153 static constexpr bool IsArray = false;
154 };
155
156 template <typename T, std::size_t N>
157 struct ElementTraits<std::array<T, N>> {
158 using Element = T;
159 static constexpr std::size_t Extent = N;
160 static constexpr bool IsArray = true;
161 };
162
165 @return true when all bytes are zero. */
166 inline bool allZero(std::span<const std::byte> bytes) {
167 return std::ranges::all_of(bytes, [](std::byte b) {
168 return b == std::byte{0};
169 });
170 }
171
176 template <typename Spec, std::meta::info M>
177 consteval std::optional<Spec> annotation() {
178 auto anns = std::meta::annotations_of_with_type(M, ^^Spec);
179 if (anns.empty()) return std::nullopt;
180 return std::meta::extract<Spec>(anns[0]);
181 }
182
187 template <typename Member>
188 consteval Column classifyMember() {
189 using Elem = typename ElementTraits<Member>::Element;
190 Column col{};
191 col.arrayLen = static_cast<std::uint16_t>(ElementTraits<Member>::Extent);
192 if constexpr (LocstringTraits<Member>::Value) {
194 col.bits = 32;
195 col.localeCount = static_cast<std::uint8_t>(LocstringTraits<Member>::Langs);
196 }
197 else if constexpr (std::same_as<Elem, std::string>) {
199 col.bits = 32;
200 }
201 else if constexpr (std::same_as<Elem, float>) {
203 col.bits = 32;
204 }
205 else if constexpr (std::integral<Elem> && !std::same_as<Elem, bool>) {
206 col.type = ColumnType::Int;
207 col.bits = static_cast<std::uint8_t>(sizeof(Elem) * 8);
208 col.isSigned = std::is_signed_v<Elem>;
209 }
210 else static_assert(false, "unsupported record column type");
211 return col;
212 }
213
218 template <typename Record>
219 consteval auto recordMembers() {
220 std::vector<std::meta::info> out;
221 for (auto m : std::meta::nonstatic_data_members_of(^^Record, std::meta::access_context::unchecked())) out.
222 push_back(m);
223 return std::define_static_array(out);
224 }
225 }
226
231 template <typename Record>
232 consteval auto schemaOf() {
233 std::vector<Column> cols;
234 static constexpr auto Members = detail::recordMembers<Record>();
235 template for (constexpr auto m : Members) {
236 using M = [:std::meta::type_of(m):];
237 Column col = detail::classifyMember<M>();
238 col.name = std::define_static_string(std::meta::identifier_of(m));
240 col.isRelation = detail::annotation<detail::RelationSpec, m>().has_value();
241 col.noninline = detail::annotation<detail::NoninlineSpec, m>().has_value();
242 cols.push_back(col);
243 }
244 return std::define_static_array(cols);
245 }
246
250 template <typename Record>
251 consteval std::size_t recordStride() {
252 std::size_t bytes = 0;
253 for (const Column& col : schemaOf<Record>()) bytes += col.inlineBytes();
254 return bytes;
255 }
256
261 template <typename Record>
262 consteval std::uint32_t fieldSlotCount() {
263 std::uint32_t slots = 0;
264 for (const Column& col : schemaOf<Record>()) slots += col.fieldSlots();
265 return slots;
266 }
267
269 the engine's per-record original-offset journal.
270 @tparam Record the record type.
271 @return the per-record string slot count. */
272 template <typename Record>
273 consteval std::size_t stringSlotCount() {
274 std::size_t slots = 0;
275 for (const Column& col : schemaOf<Record>()) slots += col.stringSlots();
276 return slots;
277 }
278
281 template <typename R> concept TableRecord = std::is_class_v<R> && std::default_initializable<R> && requires {
282 { R::Version } -> std::convertible_to<ClientVersion>; { std::string_view{R::TableName} };
283 };
284}
Client version identity, the flavor axis that separates a client's CONTENT version from the engine ge...
A type the table engine can carry as its record: a flat generated (or hand-written test) struct namin...
Definition schema.hpp:269
The column annotation vocabulary generated client-database record structs declare their schema roles ...
LocString — the decoded form of a pre-Cataclysm localized string column (wowdev.wiki "langstringref")...
consteval auto recordMembers()
The reflected non-static data members of record Record, declaration order.
Definition schema.hpp:207
bool allZero(std::span< const std::byte > bytes)
Whether every byte of bytes is zero (an empty span counts as zero).
Definition schema.hpp:154
consteval std::optional< Spec > annotation()
The first annotation of type Spec on reflected member M, if any.
Definition schema.hpp:165
consteval Column classifyMember()
The physical column shape of a record member of type Member — the name and the annotation-carried rol...
Definition schema.hpp:176
consteval std::size_t recordStride()
The fixed record stride of Record inside a WDBC/WDB2 record block.
Definition schema.hpp:239
ColumnType
The logical value class of a column.
Definition schema.hpp:34
@ Int
An integer column; Column::bits / Column::isSigned give the shape.
Definition schema.hpp:35
@ Float
A 32-bit IEEE float column.
Definition schema.hpp:36
@ String
A string-block reference column (u32 offset on disk).
Definition schema.hpp:37
@ LocString
A pre-Cataclysm localized string column (see LocString).
Definition schema.hpp:38
consteval std::uint32_t fieldSlotCount()
The expanded on-disk field slot count of Record (the WDBC/WDB2 header fieldCount a fresh write derive...
Definition schema.hpp:250
consteval auto schemaOf()
The column schema of record Record, derived by reflection: one Column per member, declaration order,...
Definition schema.hpp:220
consteval std::size_t stringSlotCount()
The string-block references one record of Record stores — the width of the engine's per-record origin...
Definition schema.hpp:261
One column of a record schema, derived from a record member by reflection (typed records) or from the...
Definition schema.hpp:46
const char * dbdName
The verbatim WoWDBDefs spelling of the column ('MapName_lang'); column_index accepts it as an alias.
Definition schema.hpp:52
constexpr std::string_view nameView() const
The member spelling as a view.
Definition schema.hpp:79
constexpr std::string_view dbdNameView() const
The verbatim WoWDBDefs spelling as a view.
Definition schema.hpp:83
bool noninline
Whether the column holds no bytes inside the record image ($noninline$).
Definition schema.hpp:68
constexpr std::size_t inlineBytes() const
The bytes the column occupies inside a fixed-stride (WDBC/WDB2) record image: zero when noninline,...
Definition schema.hpp:90
std::uint8_t localeCount
LocString language slots (8 or 16); 0 otherwise.
Definition schema.hpp:62
bool isId
Whether this is the table's primary key ($id$).
Definition schema.hpp:64
const char * name
The column name: the snake_case spelling of the WoWDBDefs column ('MapName' -> 'map_name').
Definition schema.hpp:48
std::uint16_t arrayLen
Element count; 1 for scalar columns.
Definition schema.hpp:60
ColumnType type
The logical value class.
Definition schema.hpp:54
bool isRelation
Whether this is a relationship key ($relation$).
Definition schema.hpp:66
constexpr std::size_t stringSlots() const
The string-block references the column stores per record (the width of the engine's per-record offset...
Definition schema.hpp:115
constexpr std::uint32_t fieldSlots() const
The expanded on-disk field slots the column contributes to a WDBC/WDB2 header field count: arrays cou...
Definition schema.hpp:105
Column()=default
User-declared (defaulted) so Column is NOT an aggregate: bindings would otherwise synthesize a field-...
std::uint8_t bits
Integer element width in bits; 32 for float/string refs.
Definition schema.hpp:56
bool isSigned
Integer signedness; false for non-Int columns.
Definition schema.hpp:58
A decoded pre-Cataclysm localized string column: Langs language slots (8 before TBC 2....
Definition locstring.hpp:52
Trait: the element type / extent of a member — unwraps std::array.
Definition schema.hpp:138
static constexpr bool IsArray
Definition schema.hpp:141
static constexpr std::size_t Extent
Definition schema.hpp:140
Trait: is T a LocString<N>?
Definition schema.hpp:126
static constexpr bool Value
Definition schema.hpp:127