wowlib 0.0.0
Read & write World of Warcraft client files — a C++26 core
Loading...
Searching...
No Matches
typed.hpp
Go to the documentation of this file.
1#pragma once
2
38
39#include <algorithm>
40#include <bit>
41#include <cstddef>
42#include <cstdint>
43#include <meta>
44#include <span>
45#include <string>
46#include <string_view>
47#include <type_traits>
48#include <vector>
49
50#include <wowlib/core/error.hpp>
53#include <wowlib/db/schema.hpp>
55
56#if !defined(WOWLIB_DB_SCHEMA_EMBEDDED) || !WOWLIB_DB_SCHEMA_EMBEDDED
57#error "wowlib/db/typed.hpp needs the embedded schema blob \
58(WOWLIB_DB_SCHEMA=embedded|both): the consteval validation reads it"
59#endif
60
61namespace wowlib::db {
62 namespace detail {
66 inline constexpr unsigned char TypedSchemaBlob[] = {
67#embed <wowlib_schema.wdbs>
68 };
69
73 const char* message;
74 };
75
79 consteval blob::RangeEntry typedRangeOf(std::string_view table, ClientVersion version) {
80 const blob::View view{std::span<const unsigned char>{TypedSchemaBlob}};
81 const auto at = view.findTable(table);
82 if (!at) throw TypedMismatch{"the schema blob knows no table of this name"};
83 std::size_t era = SIZE_MAX;
84 for (std::size_t i = 0; i < view.eraCount(); ++i)
85 if (view.era(i).major == version.major) era = i;
86 if (era == SIZE_MAX)
87 throw TypedMismatch{"no targeted era shares the struct's major version"};
88 const blob::TableEntry entry = view.table(*at);
89 for (std::size_t r = 0; r < entry.rangeCount; ++r) {
90 const blob::RangeEntry range = view.range(entry.firstRange + r);
91 if (range.eraMask & (std::uint16_t{1} << era)) return range;
92 }
93 throw TypedMismatch{"the table has no schema range covering the struct's era"};
94 }
95
98 consteval std::size_t typedColumnIndex(std::string_view table, ClientVersion version, std::string_view name) {
99 const blob::View view{std::span<const unsigned char>{TypedSchemaBlob}};
100 const blob::RangeEntry range = typedRangeOf(table, version);
101 for (std::size_t c = 0; c < range.columnCount; ++c)
102 if (view.copyStringAt(view.column(range.firstColumn + c).nameOff) == name) return c;
103 throw TypedMismatch{"the era's schema has no column with this member's name"};
104 }
105
108 template <typename Record>
109 consteval bool typedValidate() {
110 const blob::View view{std::span<const unsigned char>{TypedSchemaBlob}};
111 const blob::RangeEntry range = typedRangeOf(Record::TableName, Record::Version);
112 for (const Column& member : schemaOf<Record>()) {
113 const std::size_t at = typedColumnIndex(Record::TableName, Record::Version, member.nameView());
114 const blob::ColumnEntry col = view.column(range.firstColumn + at);
115 if (col.type != member.type)
116 throw TypedMismatch{
117 "the member's value class (Int/Float/String/" "LocString) differs from the DBD column's"
118 };
119 if (member.type == ColumnType::Int && (col.bits != member.bits || col.isSigned != member.isSigned))
120 throw TypedMismatch{"the member's integer width or signedness " "differs from the DBD column's"};
121 if (col.arrayLen != member.arrayLen)
122 throw TypedMismatch{"the member's array length differs from the DBD column's"};
123 if (member.type == ColumnType::LocString && col.localeCount != member.localeCount)
124 throw TypedMismatch{"the member's LocString locale count differs " "from the DBD column's (wrong era?)"};
125 }
126 return true;
127 }
128
131 template <typename Record>
132 consteval auto typedProjection() {
133 std::vector<std::size_t> map;
134 for (const Column& member : schemaOf<Record>())
135 map.push_back(typedColumnIndex(Record::TableName, Record::Version, member.nameView()));
136 return std::define_static_array(map);
137 }
138
140 template <std::size_t N>
141 struct FixedString {
142 char text[N] = {};
143 consteval FixedString(const char (&s)[N]) { std::copy_n(s, N, text); }
144 consteval std::string_view view() const { return {text, N - 1}; }
145 };
146 }
147
154 template <TableRecord Record>
155 class TypedTable {
156 static_assert(detail::typedValidate<Record>());
157
158 public:
162 return DynTable::open(Record::TableName, Record::Version).transform([](DynTable&& table) {
163 TypedTable out{};
164 out._table = std::move(table);
165 return out;
166 });
167 }
168
171 DynTable& table() { return _table; }
173 const DynTable& table() const { return _table; }
174
176 Result<void> read(std::span<const std::byte> data) {
177 return _table.read(data);
178 }
179
182 return _table.write(policy);
183 }
184
188 std::vector<Record> load() const {
189 std::vector < Record > out(_table.rowCount());
190 ErasedRecordSink sink{out};
191 static constexpr auto Schema = schemaOf<Record>();
192 static constexpr auto Projection = detail::typedProjection<Record>();
193 for (std::size_t r = 0; r < out.size(); ++r)
194 for (std::size_t m = 0; m < Schema.size(); ++m) _copyCellIn(sink, r, m, Projection[m], Schema[m]);
195 return out;
196 }
197
201 void writeBack(const std::vector<Record>& records) requires(schemaOf<Record>().size() == detail::typedRangeOf(
202 Record::TableName, Record::Version).columnCount) {
203 _table.clearRows();
204 const ErasedRecordSource source{records};
205 static constexpr auto Schema = schemaOf<Record>();
206 static constexpr auto Projection = detail::typedProjection<Record>();
207 for (std::size_t r = 0; r < records.size(); ++r) {
208 _table.appendRow();
209 for (std::size_t m = 0; m < Schema.size(); ++m) _copyCellOut(source, r, m, Projection[m], Schema[m]);
210 }
211 }
212
218 template <detail::FixedString Name>
219 auto column() const {
220 static constexpr auto Members = detail::recordMembers<Record>();
221 constexpr std::size_t index = []() consteval {
222 for (std::size_t i = 0; i < Members.size(); ++i)
223 if (std::meta::identifier_of(Members[i]) == Name.view()) return i;
224 throw detail::TypedMismatch{"no member of this record has that name"};
225 }();
226 using F = [:std::meta::type_of(Members[index]):];
227 static_assert(std::is_arithmetic_v<F> && !std::is_same_v<F, bool>,
228 "column<\"name\">() serves scalar arithmetic columns; "
229 "load() or the generic accessors cover the rest");
230 static constexpr auto Projection = detail::typedProjection<Record>();
231 const auto view = _table.podColumn(Projection[index]);
232 // add_const_t, not `const F`: gcc-16 drops a bare const applied to a
233 // dependent splice alias (the span would come out span<F>).
234 using CF = std::add_const_t<F>;
235 CF* data = reinterpret_cast<CF*>(view->bytes.data());
236 const std::size_t count = view->bytes.size() / sizeof(F);
237 return std::span<CF>(data, count);
238 }
239
240 private:
241 TypedTable() = default;
242
246 void _copyCellIn(ErasedRecordSink& sink,
247 std::size_t r,
248 std::size_t m,
249 std::size_t col,
250 const Column& shape) const {
251 switch (shape.type) {
252 case ColumnType::Int:
253 for (std::size_t e = 0; e < shape.arrayLen; ++e) sink.setInt(r, m, e, _table.getInt(r, col, e).value());
254 break;
256 for (std::size_t e = 0; e < shape.arrayLen; ++e) sink.setFloat(r, m, e, _table.getFloat(r, col, e).value());
257 break;
259 for (std::size_t e = 0; e < shape.arrayLen; ++e) sink.
260 setString(r, m, e, _table.getString(r, col, e).value());
261 break;
263 for (std::size_t e = 0; e < shape.localeCount; ++e) sink.setString(
264 r, m, e, _table.getString(r, col, e).value());
265 sink.setInt(r, m, 0, _table.locstringFlags(r, col).value());
266 break;
267 }
268 }
269
271 void _copyCellOut(const ErasedRecordSource& source,
272 std::size_t r,
273 std::size_t m,
274 std::size_t col,
275 const Column& shape) {
276 switch (shape.type) {
277 case ColumnType::Int:
278 for (std::size_t e = 0; e < shape.arrayLen; ++e) _table.setInt(r, col, source.getInt(r, m, e), e).value();
279 break;
281 for (std::size_t e = 0; e < shape.arrayLen; ++e)
282 _table.setFloat(r, col, std::bit_cast<float>(source.getSlot(r, m, e)), e).value();
283 break;
285 for (std::size_t e = 0; e < shape.arrayLen; ++e) _table.setString(r, col, source.getString(r, m, e), e).
286 value();
287 break;
289 for (std::size_t e = 0; e < shape.localeCount; ++e) _table.setString(r, col, source.getString(r, m, e), e).
290 value();
291 _table.setLocstringFlags(r, col, static_cast<std::uint32_t>(source.getInt(r, m, 0))).value();
292 break;
293 }
294 }
295
296 DynTable _table{};
297 };
298}
The generic client-database table: schema resolved at runtime from the schema catalog,...
Result< void > setString(std::size_t row, std::size_t column, std::string_view value, std::size_t element=0)
Write a string element.
Result< std::string_view > getString(std::size_t row, std::size_t column, std::size_t element=0) const
Read a string element (String columns: the array element; LocString columns: the locale slot).
Result< void > setFloat(std::size_t row, std::size_t column, float value, std::size_t element=0)
Write a float element.
Result< std::int64_t > getInt(std::size_t row, std::size_t column, std::size_t element=0) const
Read an integer element.
Result< std::uint32_t > locstringFlags(std::size_t row, std::size_t column) const
Read a LocString column's flags word.
Result< void > setLocstringFlags(std::size_t row, std::size_t column, std::uint32_t flags)
Write a LocString column's flags word.
Result< void > setInt(std::size_t row, std::size_t column, std::int64_t value, std::size_t element=0)
Write an integer element (truncates to the column's exact width, as the typed member assignment did).
Result< float > getFloat(std::size_t row, std::size_t column, std::size_t element=0) const
Read a float element.
RecordSink over a live std::vector<Record>& — the decode target.
void setInt(std::size_t record, std::size_t column, std::size_t element, std::int64_t value) override
void setFloat(std::size_t record, std::size_t column, std::size_t element, float value) override
void setString(std::size_t record, std::size_t column, std::size_t element, std::string_view value) override
RecordSource over a const std::vector<Record>& — the encode source.
A typed, consteval-validated face over one DynTable: the table itself stays fully generic (every colu...
Definition typed.hpp:155
Result< void > read(std::span< const std::byte > data)
Decode a table file image (sugar for table().read).
Definition typed.hpp:176
std::vector< Record > load() const
Materialize every row as a Record — the projection copies only the declared columns,...
Definition typed.hpp:188
const DynTable & table() const
The generic table under this face — decode/encode/validation and the generic cell accessors all live ...
Definition typed.hpp:173
Result< FileBuffer > write(EncryptedPolicy policy=EncryptedPolicy::Preserve) const
Serialize (sugar for table().write).
Definition typed.hpp:181
auto column() const
The zero-copy typed span of scalar column Name (rows x 1; array and string columns read through load ...
Definition typed.hpp:219
DynTable & table()
The generic table under this face — decode/encode/validation and the generic cell accessors all live ...
Definition typed.hpp:171
void writeBack(const std::vector< Record > &records)
Replace the table's rows from records.
Definition typed.hpp:201
static Result< TypedTable > open()
Open the era-resolved generic table with an empty row set.
Definition typed.hpp:161
A structural view over one WDBS blob.
constexpr ColumnEntry column(std::size_t i) const
The column entry i (blob-global index).
constexpr TableEntry table(std::size_t i) const
The directory entry of table i (name-sorted order).
constexpr std::optional< std::size_t > findTable(std::string_view name) const
Binary-search the (name-sorted) table directory for name.
constexpr std::size_t eraCount() const
constexpr ClientVersion era(std::size_t i) const
The client version of era slot i.
constexpr RangeEntry range(std::size_t i) const
The range entry i (blob-global index).
constexpr std::string copyStringAt(std::uint32_t off) const
The pool string at off, copied byte by byte — the consteval-safe twin of stringAt.
DynTable — the generic, runtime-schema client-database table: the ONE table class every binding welds...
The error-handling vocabulary: ErrorCode, Error and the Result<T> alias every fallible wowlib operati...
consteval std::size_t typedColumnIndex(std::string_view table, ClientVersion version, std::string_view name)
The era-schema column index whose name is name, or a thrown TypedMismatch.
Definition typed.hpp:98
consteval auto recordMembers()
The reflected non-static data members of record Record, declaration order.
Definition schema.hpp:207
consteval bool typedValidate()
Validate Record against the blob (see the file note for the rules); also the projection builder's pre...
Definition typed.hpp:109
consteval blob::RangeEntry typedRangeOf(std::string_view table, ClientVersion version)
The blob's column list for (table, version)'s era, as blob-global column indexes [first,...
Definition typed.hpp:79
consteval auto typedProjection()
The projection map of Record: member position -> era-schema column index, as a static (never-dangling...
Definition typed.hpp:132
constexpr unsigned char TypedSchemaBlob[]
The blob, again, for CONSTANT EVALUATION in consumer TUs (the runtime catalog's copy lives inside sch...
Definition typed.hpp:66
@ 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
EncryptedPolicy
How write() treats a table that still holds keyless (undecryptable) encrypted sections.
Definition codec.hpp:33
@ Preserve
Re-emit the original image verbatim; edits are not applied.
Definition codec.hpp:34
consteval auto schemaOf()
The column schema of record Record, derived by reflection: one Column per member, declaration order,...
Definition schema.hpp:220
std::expected< T, Error > Result
Every fallible wowlib operation returns Result<T>; bindings translate the error branch into a target-...
Definition error.hpp:100
The record bridge: ErasedRecordSink / ErasedRecordSource implement the non-templated RecordSink / Rec...
Schema reflection over generated client-database record structs: the column list, record stride and f...
The WDBS schema-blob format: a constexpr-parseable view over the compact binary dbdgen emits (--schem...
std::uint16_t major
Expansion number, e.g.
One column of a record schema, derived from a record member by reflection (typed records) or from the...
Definition schema.hpp:46
std::uint8_t localeCount
LocString language slots (8 or 16); 0 otherwise.
Definition schema.hpp:62
std::uint16_t arrayLen
Element count; 1 for scalar columns.
Definition schema.hpp:60
ColumnType type
The logical value class.
Definition schema.hpp:54
One column, decoded to the raw blob facts (the catalog turns this into a wowlib::db::Column with an i...
std::uint16_t arrayLen
element count; 1 for scalars.
std::uint32_t nameOff
The column name's pool offset.
std::uint8_t localeCount
LocString language slots; 0 otherwise.
ColumnType type
The logical value class.
std::uint8_t bits
Integer element width in bits.
One schema range: a run of columns valid for the eras in the mask.
std::uint32_t firstColumn
Index of the range's first column.
std::uint16_t eraMask
Bit i set = valid for the blob's era i.
std::uint16_t columnCount
Number of consecutive columns.
One table's directory entry, decoded.
std::uint32_t rangeCount
Number of consecutive ranges.
std::uint32_t firstRange
Index of the table's first range.
consteval std::string_view view() const
Definition typed.hpp:144
consteval FixedString(const char(&s)[N])
Definition typed.hpp:143
The consteval-validation failure carrier: throwing it inside constant evaluation aborts compilation w...
Definition typed.hpp:72