forked from cataclysmbnteam/Cataclysm-BN
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cata_io.h
471 lines (442 loc) · 17.7 KB
/
cata_io.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
#pragma once
#ifndef CATA_SRC_CATA_IO_H
#define CATA_SRC_CATA_IO_H
#include <functional>
#include <string>
#include <type_traits>
#include "json.h"
/**
* @name Serialization and deserialization
*
* Basic classes are the input and output archives. Each of them comes in two flavors: one stores
* data in a JSON array and the other stores them in a JSON object.
*
* The input archive is used to deserialize (ie.e read from the archive, write to a C++ variable),
* the output archive does the inverse.
*
* Input and output archives (that refer to the same JSON type, i.e. @ref JsonArrayInputArchive
* and @ref JsonArrayOutputArchive) have (nearly) the same interface (mostly the `io` function),
* they only differ in what the functions do. This is an important design decision, it allows
* the archive type to be a template parameter. The `io` functions in both archive classes should
* therefor have a compatible signature.
*
* Archive classes also have a `is_input` member type, which is either @ref std::true_type or
* if(the class is an input archive) or @ref std::false_type (it's an output archive). This can be
* used to do additional things only after loading the data, but not after storing it.
*
* Usage: implement a tempted function `io` in the class that is to be serialized and add a typedef
*
* class MySerializeableType {
* ...
* template<typename Archive>
* void item::io( Archive& archive ) {
* archive.io( "member1", member1, default_value_of_member1 );
* archive.io( "member2", member2, default_value_of_member2 );
* ...
* }
* using archive_type_tag = io::object_archive_tag;
* ...
* };
*
* The tag types are defined in io_tags.h so that this header need not be
* included to use them.
*
* The function must be declared with the archive type as template parameter, but it can be
* implemented outside of the header (e.g. in savegame_json.cpp). It will be instantiated with
* JsonArrayInputArchive and JsonArrayOutputArchive, or with JsonObjectInputArchive and
* JsonObjectOutputArchive as archive type, depending on the type of the archive_type_tag.
*
* Note that the array based archives have a much simpler interface, they only support
* reading/writing sequentially.
*
* The `io` function works for input and output archives. One can call it like this:
*
* item& tmp = ...
* JsonOutputArchive out( ... );
* tmp.io( out );
* JsonInputArchive in( ... );
* tmp.io( in );
*
* The archive classes use the Json classes (see json.h) to write and read data. This works fine
* for most built-in types (including string and certain container types). Specific classes, like
* @ref item or @ref monster, need their own logic. They used to inherit from @ref JsonDeserializer
* and @ref JsonSerializer, which would grant them functions to serialize/deserialize. The Json
* classes will automatically use those if available, but they prefer the `io` function.
*
* Classes that only use the (templated) `io` function (and which do not inherit from
* JsonDeserializer and JsonSerializer), must announce that. This allows the Archive classes to
* avoid calling Json functions, which would not work.
*
* If a class only uses the `io` function defined like this:
*
* template<typename Archive> void io( Archive& );
*
* It should also have a type tag that indicates 1) the`io` function can be used and 2) whether
* the data of the class is stored in a JSON array (@ref io::array_archive_tag) or in a
* JSON object (@ref io::object_archive_tag).
*
* using archive_type_tag = io::object_archive_tag;
*
* Note that the *archive_tag structs only need to be forward declared to work in the using
* statement. One does not need to include "io.h" to allow that statement.
*/
namespace io
{
/**
* Tag that indicates the default value for io should be the default value of the type
* itself, i.e. the result of default initialization: `T()`
* The type must support a comparison, so serializing can check whether the value is equal to the
* default value.
*
* An instance of this tag can be used as default value parameter in the io functions.
*/
struct default_tag { };
/**
* Tag that indicates the value of the io is some kind of container and its default value is to
* be empty. This does not require it to have a comparison function (e.g. suitable for @ref item).
* It requires the type to have `bool empty()` and `void clear()` functions (i.e. works for maps).
*
* An instance of this tag can be used as default value parameter in the io functions.
*/
struct empty_default_tag { };
/**
* Tag that indicates the value is required and must exists in the JSON data.
*/
struct required_tag { };
/**
* The namespace contains classes that do write/read to/from JSON via either the Json classes in
* "json.h" or via the `io` function of the object to be written/read.
* Which of those classes is actually used determined via the template parameter and use of SFINAE.
* Usage:
*
* io::detail::has_archive_tag<SomeType>::write( stream, instance_of_SomeType );
*/
namespace detail
{
template<class T, class R = void>
struct enable_if_type {
using type = R;
};
/**
* Implementation for classes that don't have an archive_type_tag defined. They use the
* normal JsonSerializer / JsonDeserializer interface, which is handled directly by the Json
* classes. Therefor the functions here simply forward to those.
*/
template<class T, class E = void>
struct has_archive_tag : std::false_type {
static void write( JsonOut &stream, const T &value ) {
stream.write( value );
}
static bool read( const JsonObject &obj, const std::string &key, T &value ) {
return obj.read( key, value );
}
static bool read( JsonArray &arr, T &value ) {
return arr.read_next( value );
}
};
/**
* Implementation for classes that use the `io` function. The function herein create Archive
* instances and call the `io` function of the object to be written/read.
*/
template<class T>
struct has_archive_tag<T, typename enable_if_type<typename T::archive_type_tag>::type> :
std::true_type {
using InArchive = typename T::archive_type_tag::InputArchive;
static void write( JsonOut &stream, const T &value ) {
typename T::archive_type_tag::OutputArchive archive( stream );
const_cast<T &>( value ).io( archive );
}
static bool read( const JsonObject &obj, const std::string &key, T &value ) {
if( !obj.has_member( key ) ) {
return false;
}
InArchive archive( obj, key );
value.io( archive );
return true;
}
static bool read( JsonArray &arr, T &value ) {
if( !arr.has_more() ) {
return false;
}
InArchive archive( arr );
value.io( archive );
return true;
}
};
} // namespace detail
/**
* Input archive reading data from a Json object.
*
* Data can be loaded from this archive using the name of the member as key.
*/
class JsonObjectInputArchive : public JsonObject
{
public:
using is_input = std::true_type;
JsonObjectInputArchive( const JsonObject &jo )
: JsonObject( jo ) {
}
/** Create archive from next object in the given Json array. */
JsonObjectInputArchive( JsonArray & );
/** Create archive from named member object in the given Json object. */
JsonObjectInputArchive( const JsonObject &, const std::string &key );
/**
* @name Deserialization
*
* The io functions read a value from the archive and store it in the reference parameter.
*
* @throw JsonError (via the Json classes) if the value in the archive is of an incompatible
* type (e.g. reading a string, but the member is a Json object).
* @return `false` if the archive did not contain the requested member, otherwise `true`.
*/
/*@{*/
/**
* If the archive does not have the requested member, the value is not changed at all.
*/
template<typename T>
bool io( const std::string &name, T &value ) {
return io::detail::has_archive_tag<T>::read( *this, name, value );
}
/**
* If the archive does not have the requested member, the given default value is assigned.
* The function still returns false if the default value had been used.
*/
template<typename T>
bool io( const std::string &name, T &value, const T &default_value ) {
if( io( name, value ) ) {
return true;
}
value = default_value;
return false;
}
/**
* Like the other io function, this uses a default constructed default value:
* Roughly equivalent to \code io<T>( name, value, T() ); \endcode
*/
template<typename T>
bool io( const std::string &name, T &value, default_tag ) {
static const T default_value = T();
return io( name, value, default_value );
}
/**
* If the archive does not have the requested member, the function `clear` is called on the
* value, this may be used for containers (e.g. std::map).
*/
template<typename T>
bool io( const std::string &name, T &value, empty_default_tag ) {
if( io( name, value ) ) {
return true;
}
value.clear();
return false;
}
/**
* Special function to load pointers. If the archive does not have the requested member,
* `nullptr` is assigned to the pointer. Otherwise the value is loaded (which must be a
* `std::string`) and the load function is called.
*
* Example:
*
* class Dummy {
* int *pointer;
* template<typename A> void io( A& ar ) {
* ar.io( "ptr", pointer,
* [this](const std::string&s) {
* pointer = new int( atoi( s.c_str() ) );
* },
* [](const int &v) {
* return std::to_string( v );
* }
* );
* }
* };
*
* @param name Name of requested member
* @param pointer
* @param load The function gets the string that was loaded from JSON and should set the pointer
* in the object accordingly. One would usually use a lambda for this function.
* @param save The inverse of the load function, it converts the pointer to a string which is
* later stored in the JSON.
* @param required If `true`, an error will be raised if the requested member does not exist
* in the JSON data.
* JsonOutputArchive, so it can be used when the archive type is a template parameter.
*/
template<typename T>
bool io( const std::string &name, T *&pointer,
const std::function<void( const std::string & )> &load,
const std::function<std::string( const T & )> &save, bool required = false ) {
// Only used by the matching function in the output archive classes.
( void ) save;
std::string ident;
if( !io( name, ident ) ) {
if( required ) {
JsonObject::throw_error( std::string( "required member is missing: " ) + name );
}
pointer = nullptr;
return false;
}
load( ident );
return true;
}
template<typename T>
bool io( const std::string &name, T *&pointer,
const std::function<void( const std::string & )> &load,
const std::function<std::string( const T & )> &save, required_tag ) {
return io<T>( name, pointer, load, save, true );
}
/*@}*/
};
/**
* Input archive reading data from a Json array.
*
* This archive is quite simple, it loads the data sequentially from the Json array. The first
* call to @ref io loads the first value, the next call loads the second and so on.
*
* Reading a value from a specific index is on purpose not supported.
*/
class JsonArrayInputArchive : public JsonArray
{
public:
using is_input = std::true_type;
JsonArrayInputArchive( const JsonArray &jo )
: JsonArray( jo ) {
}
/** Create archive from next object in the given Json array. */
JsonArrayInputArchive( JsonArray & );
/** Create archive from named member object in the given Json object. */
JsonArrayInputArchive( const JsonObject &, const std::string &key );
template<typename T>
bool io( T &value ) {
return io::detail::has_archive_tag<T>::read( *this, value );
}
};
inline JsonArrayInputArchive::JsonArrayInputArchive( JsonArray &arr )
: JsonArray( arr.next_array() ) { }
inline JsonArrayInputArchive::JsonArrayInputArchive( const JsonObject &obj, const std::string &key )
: JsonArray( obj.get_array( key ) ) { }
inline JsonObjectInputArchive::JsonObjectInputArchive( JsonArray &arr )
: JsonObject( arr.next_object() ) { }
inline JsonObjectInputArchive::JsonObjectInputArchive( const JsonObject &obj,
const std::string &key )
: JsonObject( obj.get_object( key ) ) { }
/**
* Output archive matching the input archive @ref JsonObjectInputArchive.
*
* It has the same function as the input archive, they do the exact inverse.
*
* The class creates a valid JSON value in the output stream (that means it adds the initial
* '{' and '}' to the stream).
*/
class JsonObjectOutputArchive
{
public:
using is_input = std::false_type;
JsonOut &stream;
JsonObjectOutputArchive( JsonOut &stream )
: stream( stream ) {
stream.start_object();
}
~JsonObjectOutputArchive() {
stream.end_object();
}
/**
* @name Serialization
*
* The io functions store a value (given as parameter) in the archive.
*
* @throw JsonError (via the Json classes) on any kind of error.
*
* @return All functions return `false`. Their signature should be compatible with the
* functions in @ref JsonObjectInputArchive, so they can be used when the archive type is a
* template parameter.
*/
/*@{*/
template<typename T>
bool io( const std::string &name, const T &value ) {
stream.member( name );
io::detail::has_archive_tag<T>::write( stream, value );
return false;
}
template<typename T>
bool io( const std::string &name, const T &value, const T &default_value ) {
if( value == default_value ) {
return false;
}
return io( name, value );
}
template<typename T>
bool io( const std::string &name, const T &value, default_tag ) {
static const T default_value = T();
return io<T>( name, value, default_value );
}
template<typename T>
bool io( const std::string &name, const T &value, empty_default_tag ) {
if( !value.empty() ) {
io<T>( name, value );
}
return false;
}
/**
* Special function to store pointers.
*
* If the pointer is `nullptr`, it will not be stored at all (`nullptr` is the default value).
* Otherwise the save function is called to translate the pointer into a string (which can be
* the id of the pointed to object or similar). The returned string is stored in the archive.
*
* The function signature is compatible with the similar function in the
* @ref JsonObjectInputArchive, so it can be used when the archive type is a template parameter.
*/
template<typename T>
bool io( const std::string &name, const T *pointer,
const std::function<void( const std::string & )> &,
const std::function<std::string( const T & )> &save, bool required = false ) {
if( pointer == nullptr ) {
if( required ) {
throw JsonError( "a required member is null: " + name );
}
return false;
}
return io( name, save( *pointer ) );
}
template<typename T>
bool io( const std::string &name, const T *pointer,
const std::function<void( const std::string & )> &load,
const std::function<std::string( const T & )> &save, required_tag ) {
return io<T>( name, pointer, load, save, true );
}
/*@}*/
/**
* For compatibility with the input archive. Output archives obliviously never read anything
* and always return false.
*/
template<typename T>
bool read( const std::string &, T & ) {
return false;
}
};
/**
* Output archive matching the input archive @ref JsonArrayInputArchive.
*
* The class creates a valid JSON value in the output stream (that means it adds the initial
* '[' and ']' to the stream).
*/
class JsonArrayOutputArchive
{
public:
using is_input = std::false_type;
JsonOut &stream;
JsonArrayOutputArchive( JsonOut &stream )
: stream( stream ) {
stream.start_array();
}
~JsonArrayOutputArchive() {
stream.end_array();
}
template<typename T>
bool io( const T &value ) {
io::detail::has_archive_tag<T>::write( stream, value );
return false;
}
};
} // namespace io
#endif // CATA_SRC_CATA_IO_H