summaryrefslogtreecommitdiffstats
path: root/source/slang/slang-serialize-riff.h
blob: b305113fa3eaf6e0f1fd582bb7121c7bd3102f25 (plain)
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
// slang-serialize-riff.h
#ifndef SLANG_SERIALIZE_RIFF_H
#define SLANG_SERIALIZE_RIFF_H

//
// This file provides implementations of `ISerializerImpl` that
// serialize hierarchical data in a RIFF-based format.
//
// This implementation can be seen as an adapter between the
// `Slang::Serializer` and `Slang::RIFF` subsystems, and also
// serves an an example of how to write a complete reader/writer
// pair for a new serialization format.
//

#include "../core/slang-riff.h"
#include "slang-serialize.h"

namespace Slang
{

namespace RIFFSerial
{
//
// Each value in the hierarchy will be ended as a RIFF chunk.
// The type of the chunk, and whether it is a list or data
// chunk will depend on the kind of value.
//
// All of the serialized data will be encapsulated in a single
// list chunk. This chunk can have its `FourCC` customized,
// but a default is also provided.
//

/// Default type for root chunk of a serialized object graph.
static const FourCC::RawValue kRootFourCC = SLANG_FOUR_CC('r', 'o', 'o', 't');

//
// Simple numeric values are stored as data chunks.
// Rather than go down to the granularity of 16- and
// 8-bit integers, we stick to 32- and 64-bit values
// only, since the overhead of a RIFF chunk header
// is already 64 bits (so the savings would be
// minimal).
//

static const FourCC::RawValue kInt32FourCC = SLANG_FOUR_CC('i', '3', '2', ' ');
static const FourCC::RawValue kInt64FourCC = SLANG_FOUR_CC('i', '6', '4', ' ');

static const FourCC::RawValue kUInt32FourCC = SLANG_FOUR_CC('u', '3', '2', ' ');
static const FourCC::RawValue kUInt64FourCC = SLANG_FOUR_CC('u', '6', '4', ' ');

static const FourCC::RawValue kFloat32FourCC = SLANG_FOUR_CC('f', '3', '2', ' ');
static const FourCC::RawValue kFloat64FourCC = SLANG_FOUR_CC('f', '6', '4', ' ');

//
// Boolean values are stored as empty chunks, with a unique
// type tag for each of the two possible values.
//

static const FourCC::RawValue kTrueFourCC = SLANG_FOUR_CC('t', 'r', 'u', 'e');
static const FourCC::RawValue kFalseFourCC = SLANG_FOUR_CC('f', 'a', 'l', 's');

//
// Strings are stored as a data chunk, with the payload of
// that chunk holding the bytes of the UTF-8 encoded string.
// The length of the string is stored as part of the chunk
// header.
//
// We also define a `FourCC` for raw data chunks, in anticipation
// of support for raw data being added to `ISerializerImpl` as
// an analogue of strings.
//

static const FourCC::RawValue kStringFourCC = SLANG_FOUR_CC('s', 't', 'r', ' ');
static const FourCC::RawValue kDataFourCC = SLANG_FOUR_CC('d', 'a', 't', 'a');

//
// Containers (arrays, dictionaries, optionals, tuples, and structs)
// are stored as list chunks, with their elements as child chunks.
//

static const FourCC::RawValue kArrayFourCC = SLANG_FOUR_CC('a', 'r', 'r', 'y');
static const FourCC::RawValue kDictionaryFourCC = SLANG_FOUR_CC('d', 'i', 'c', 't');
static const FourCC::RawValue kStructFourCC = SLANG_FOUR_CC('s', 't', 'r', 'c');
static const FourCC::RawValue kTupleFourCC = SLANG_FOUR_CC('t', 'p', 'l', 'e');
static const FourCC::RawValue kOptionalFourCC = SLANG_FOUR_CC('o', 'p', 't', '?');

//
// Null pointer values are simply stored as an empty data chunk with
// a distinct type.
//

static const FourCC::RawValue kNullFourCC = SLANG_FOUR_CC('n', 'u', 'l', 'l');

//
// Non-null pointers are stored as a data chunk that references a
// serialized object by its `ObjectIndex`.
//

using ObjectIndex = Int32;

static const FourCC::RawValue kObjectReferenceFourCC = SLANG_FOUR_CC('o', 'b', 'j', 'r');

//
// All of the objects transitively referenced in the serialized object
// graph are stored in a list chunk of object definitions, with one
// chunk per object. The object definitions themselves are ordinary
// values using any of the cases above.
//

static const FourCC::RawValue kObjectDefinitionListFourCC = SLANG_FOUR_CC('o', 'b', 'j', 's');

//
// The first child of the root chunk will be the object definition list
// chunk, and that will be followed by zero or more "root values" that
// have been serialized.
//

} // namespace RIFFSerial

/// Serializer implementation for writing to a tree of RIFF chunks.
struct RIFFSerialWriter
{
public:
    /// Construct a writer to append to the given RIFF `chunk`.
    ///
    /// The object graph will be serialized into a child chunk
    /// of `chunk`, as a list chunk with the given `type`.
    ///
    RIFFSerialWriter(RIFF::ChunkBuilder* chunk, FourCC type = RIFFSerial::kRootFourCC);


    /// Construct a writer to write an entire RIFF file.
    ///
    /// The object graph will be serialized as the root chunk
    /// of `riff`, with the given `type`.
    ///
    RIFFSerialWriter(RIFF::Builder& riff, FourCC type = RIFFSerial::kRootFourCC);

    /// Finalize writing.
    ///
    /// Any pending operations needed to write the entire object
    /// graph will be flushed.
    ///
    ~RIFFSerialWriter();

private:
    RIFFSerialWriter() = delete;

    /// Cursor for where in the RIFF hierarchy we are writing.
    RIFF::BuildCursor _cursor;

    /// Representation of an index into the object list.
    using ObjectIndex = RIFFSerial::ObjectIndex;

    /// Information about an object that should be
    /// added to the object definition list.
    struct ObjectInfo
    {
        /// Pointer to the in-memory C++ object.
        void* ptr;

        /// Callback that can be invoked to serialize the object's data.
        SerializerCallback callback;

        /// Context pointer for `callback`
        void* context;
    };

    /// The chunk where object definitions are listed.
    RIFF::ListChunkBuilder* _objectDefinitionListChunk = nullptr;

    /// Information on the objects that have been referenced,
    /// and which need their definitions to be serialized into
    /// the object definition list chunk.
    ///
    List<ObjectInfo> _objects;
    Index _writtenObjectDefinitionCount = 0;

    /// Maps the address of an in-memory C++ object to the
    /// corresponding entry in `_objects`, if any.
    Dictionary<void*, ObjectIndex> _mapPtrToObjectIndex;


    void _initialize(FourCC type);
    void _flush();

    void _writeInt(Int64 value);
    void _writeUInt(UInt64 value);
    void _writeFloat(double value);

    void _writeObjectReference(ObjectIndex index);

private:
    //
    // The following declarations are the requirements
    // of the `ISerializerImpl` interface:
    //

    SerializationMode getMode();

    void handleBool(bool& value);

    void handleInt8(int8_t& value);
    void handleInt16(int16_t& value);
    void handleInt32(Int32& value);
    void handleInt64(Int64& value);

    void handleUInt8(uint8_t& value);
    void handleUInt16(uint16_t& value);
    void handleUInt32(UInt32& value);
    void handleUInt64(UInt64& value);

    void handleFloat32(float& value);
    void handleFloat64(double& value);

    void handleString(String& value);

    struct Scope
    {
        // The RIFF serialization back-end is currently
        // not taking advantage of the `Scope` facility
        // in the serialization framework.
    };

    void beginArray(Scope&);
    void endArray(Scope&);

    void beginDictionary(Scope&);
    void endDictionary(Scope&);

    bool hasElements();

    void beginStruct(Scope&);
    void endStruct(Scope&);

    void beginVariant(Scope&);
    void endVariant(Scope&);

    void handleFieldKey(char const* name, Int index);

    void beginTuple(Scope&);
    void endTuple(Scope&);

    void beginOptional(Scope&);
    void endOptional(Scope&);

    void handleSharedPtr(void*& value, SerializerCallback callback, void* context);
    void handleUniquePtr(void*& value, SerializerCallback callback, void* context);

    void handleDeferredObjectContents(void* valuePtr, SerializerCallback callback, void* context);
};

/// Serializer implementation for reading from a tree of RIFF chunks.
struct RIFFSerialReader
{
public:
    /// Construct a reader to read data from the given `chunk`.
    ///
    /// Will validate that the given `chunk` is a list chunk
    /// matching the expected `type`.
    ///
    RIFFSerialReader(RIFF::Chunk const* chunk, FourCC type = RIFFSerial::kRootFourCC);

    /// Finalize the reader.
    ///
    /// This will flush any outstanding operations that
    /// might be pending.
    ///
    ~RIFFSerialReader();

    /// Read a chunk from the current cursor position.
    ///
    /// This operation can be used to skip over an entire value
    /// that might otherwise need to be read with a sequence of
    /// operations of the `ISerializerImpl` interface.
    ///
    /// The saved pointer can then be used to construct another
    /// `RIFFSerialReader` to read the contents of the chunk
    /// at some later time, or code can simply navigate the
    /// chunk in memory using their own logic.
    ///
    RIFF::Chunk const* readChunk();

private:
    /// Representation of a read cursor in the serialized RIFF data.
    using Cursor = RIFF::BoundsCheckedChunkPtr;

    /// Current cursor in the serialized RIFF data.
    Cursor _cursor;

    void _advanceCursor();

    /// A stack of saved cursors, reflecting the nesting
    /// hierarchy of container chunks being read from.
    ///
    List<Cursor> _stack;

    void _pushCursor();
    void _popCursor();

    /// Representation of an index into the object list.
    using ObjectIndex = RIFFSerial::ObjectIndex;

    /// State of a serialized object that may or may not have been read already.
    enum class ObjectState
    {
        Unread,
        ReadingInProgress,
        ReadingComplete,
    };

    /// Information about a serialized object in the object definition list.
    struct ObjectInfo
    {
        /// State of the object.
        ///
        ObjectState state = ObjectState::Unread;

        /// Pointer to an in-memory C++ object representing the serialized object.
        ///
        /// Should only be accessed with consideration of what `state` is:
        ///
        /// * If `state` is `Unread`, then this should always be null.
        ///
        /// * If `state` is `ReadingComplete`, then this should be be
        ///   a valid pointer to the in-memory representation of the serialized object
        ///   (or null, if client code chose to deserialize it into a null pointer
        ///   for some reason).
        ///
        /// * If `state` is `ReadingInProgress`, then this might be a null pointer,
        ///   indicating that the logic to deserialize the object is currently
        ///   running (but has not yet allocated a representation and set it), or
        ///   it might be non-null indicating that the in-memory representation
        ///   has been allocated.
        ///
        /// Even if `ptr` is non-null, it may not be safe to access the
        /// contents of the pointed-to object, because there may be deferred
        /// operations pending to read some or all of its members.
        ///
        void* ptr = nullptr;

        /// The chunk that holds the definition of this object.
        RIFF::Chunk const* definitionChunk = nullptr;
    };

    /// All of the objects from the object definition list chunk.
    List<ObjectInfo> _objects;

    /// A serialization action that has been deferred.
    ///
    /// Deferred actions are typically used to put off recursively
    /// reading all of the members of an object, thus avoiding
    /// the potential for unbounded or even infinite recursion.
    ///
    struct DeferredAction
    {
        /// The in-memory object that the action should apply to.
        void* valuePtr;

        /// The value of `_cursor` at the time this action was deferred.
        Cursor savedCursor;

        /// The callback to apply to read data into the `valuePtr`
        SerializerCallback callback;

        /// The context pointer for the `callback`.
        void* context;
    };

    /// Deferred actions that are still pending.
    ///
    /// As long as this array is non-empty, the contents of
    /// in-memory objects read from the serialized data should
    /// not be inspected/used.
    ///
    List<DeferredAction> _deferredActions;

    void _initialize(FourCC type);
    void _flush();

    FourCC _peekChunkType();

    Int64 _readInt();
    UInt64 _readUInt();
    double _readFloat();

    ObjectIndex _readObjectReference();

    void _readDataChunk(void* outData, size_t dataSize);

    template<typename T>
    T _readDataChunk()
    {
        T value;
        _readDataChunk(&value, sizeof(value));
        return value;
    }

    void _beginListChunk(FourCC type);
    void _endListChunk();

private:
    //
    // The following declarations are the requirements
    // of the `ISerializerImpl` interface:
    //

    SerializationMode getMode();

    void handleBool(bool& value);

    void handleInt8(int8_t& value);
    void handleInt16(int16_t& value);
    void handleInt32(Int32& value);
    void handleInt64(Int64& value);

    void handleUInt8(uint8_t& value);
    void handleUInt16(uint16_t& value);
    void handleUInt32(UInt32& value);
    void handleUInt64(UInt64& value);

    void handleFloat32(float& value);
    void handleFloat64(double& value);

    void handleString(String& value);

    struct Scope
    {
        // The RIFF serialization back-end is currently
        // not taking advantage of the `Scope` facility
        // in the serialization framework.
    };

    void beginArray(Scope&);
    void endArray(Scope&);

    void beginDictionary(Scope&);
    void endDictionary(Scope&);

    bool hasElements();

    void beginStruct(Scope&);
    void endStruct(Scope&);

    void beginVariant(Scope&);
    void endVariant(Scope&);

    void handleFieldKey(char const* name, Int index);

    void beginTuple(Scope&);
    void endTuple(Scope&);

    void beginOptional(Scope&);
    void endOptional(Scope&);

    void handleSharedPtr(void*& value, SerializerCallback callback, void* context);
    void handleUniquePtr(void*& value, SerializerCallback callback, void* context);

    void handleDeferredObjectContents(void* valuePtr, SerializerCallback callback, void* context);
};

} // namespace Slang

#endif