diff options
| author | Theresa Foley <10618364+tangent-vector@users.noreply.github.com> | 2025-05-20 21:55:39 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-05-21 04:55:39 +0000 |
| commit | 9059093bc764e901a9c4aaeb12471bf32028874f (patch) | |
| tree | 7058871ce0ec4397b6e8996506357e41ebb2517d /source/slang/slang-serialize-riff.cpp | |
| parent | 52d70f37f66d8fc34bc142386490bdcde0fc7db0 (diff) | |
Generalize serialization system used for AST (#7126)
This change takes the new approach to serialization that was used for the AST and generalizes it in a few ways:
* The new approach is no longer tangled up with the RIFF format.
The serialization system supports multiple different implementations of the underlying format.
The existing RIFF format is now supported as one back-end, but support for others will follow in subsequent changes.
* The new approach is no longer deeply specialized to AST serialization.
The old code had things like serialization for `List`s and `Dictionary`s, but it was embedded inside the `AST{Encoding|Decoding}Context`, and thus couldn't be leveraged for other serialization tasks.
This change factors out a completely AST-independent `Serializer` implementation, with an `ASTSerializer` layered on top of it to provide the additional context needed.
* There is less duplication of code between reading and writing of serialized data.
The old code had both the `ASTEncodingContext` and `ASTDecodingContext`, with serialization logic for most types being implemented in both, but with the constraint that those implementations needed to be kept in sync to avoid serialization-related runtime failures.
A key property of the revamped approach is that a single `serialize()` method for a type implements both the reading and writing directions of serialization.
Diffstat (limited to 'source/slang/slang-serialize-riff.cpp')
| -rw-r--r-- | source/slang/slang-serialize-riff.cpp | 897 |
1 files changed, 897 insertions, 0 deletions
diff --git a/source/slang/slang-serialize-riff.cpp b/source/slang/slang-serialize-riff.cpp new file mode 100644 index 000000000..01b39e825 --- /dev/null +++ b/source/slang/slang-serialize-riff.cpp @@ -0,0 +1,897 @@ +// slang-serialize-riff.cpp +#include "slang-serialize-riff.h" + +namespace Slang +{ + +// +// RIFFSerialWriter +// + +RIFFSerialWriter::RIFFSerialWriter(RIFF::ChunkBuilder* chunk, FourCC type) + : _cursor(chunk) +{ + _initialize(type); +} + +RIFFSerialWriter::RIFFSerialWriter(RIFF::Builder& riff, FourCC type) + : _cursor(riff) +{ + _initialize(type); +} + +RIFFSerialWriter::~RIFFSerialWriter() +{ + // We need to flush any pending operations to + // write objects into the object definition list chunk. + // + _flush(); +} + +SerializationMode RIFFSerialWriter::getMode() +{ + return SerializationMode::Write; +} + +void RIFFSerialWriter::handleBool(bool& value) +{ + _cursor.addDataChunk(value ? RIFFSerial::kTrueFourCC : RIFFSerial::kFalseFourCC, nullptr, 0); +} + +void RIFFSerialWriter::handleInt8(int8_t& value) +{ + _writeInt(value); +} + +void RIFFSerialWriter::handleInt16(int16_t& value) +{ + _writeInt(value); +} + +void RIFFSerialWriter::handleInt32(Int32& value) +{ + _writeInt(value); +} + +void RIFFSerialWriter::handleInt64(Int64& value) +{ + _writeInt(value); +} + +void RIFFSerialWriter::handleUInt8(uint8_t& value) +{ + _writeUInt(value); +} + +void RIFFSerialWriter::handleUInt16(uint16_t& value) +{ + _writeUInt(value); +} + +void RIFFSerialWriter::handleUInt32(UInt32& value) +{ + _writeUInt(value); +} + +void RIFFSerialWriter::handleUInt64(UInt64& value) +{ + _writeUInt(value); +} + +void RIFFSerialWriter::handleFloat32(float& value) +{ + _writeFloat(value); +} + +void RIFFSerialWriter::handleFloat64(double& value) +{ + _writeFloat(value); +} + +void RIFFSerialWriter::handleString(String& value) +{ + _cursor.addDataChunk(RIFFSerial::kStringFourCC, value.getBuffer(), value.getLength()); +} + +void RIFFSerialWriter::_writeInt(Int64 value) +{ + // We pick a 32-bit representation if it can + // faithfully represent the value, and a 64-bit + // representation otherwise. + // + if (Int32(value) == value) + { + auto v = Int32(value); + _cursor.addDataChunk(RIFFSerial::kInt32FourCC, &v, sizeof(v)); + } + else + { + _cursor.addDataChunk(RIFFSerial::kInt64FourCC, &value, sizeof(value)); + } +} + +void RIFFSerialWriter::_writeUInt(UInt64 value) +{ + // We pick a 32-bit representation if it can + // faithfully represent the value, and a 64-bit + // representation otherwise. + // + if (UInt32(value) == value) + { + auto v = UInt32(value); + _cursor.addDataChunk(RIFFSerial::kUInt32FourCC, &v, sizeof(v)); + } + else + { + _cursor.addDataChunk(RIFFSerial::kUInt64FourCC, &value, sizeof(value)); + } +} + +void RIFFSerialWriter::_writeFloat(double value) +{ + // We pick a 32-bit representation if it can + // faithfully represent the value, and a 64-bit + // representation otherwise. + // + if (float(value) == value) + { + auto v = float(value); + _cursor.addDataChunk(RIFFSerial::kFloat32FourCC, &v, sizeof(v)); + } + else + { + _cursor.addDataChunk(RIFFSerial::kFloat64FourCC, &value, sizeof(value)); + } +} + +void RIFFSerialWriter::beginArray() +{ + _cursor.beginListChunk(RIFFSerial::kArrayFourCC); +} + +void RIFFSerialWriter::endArray() +{ + _cursor.endChunk(); +} + +void RIFFSerialWriter::beginDictionary() +{ + _cursor.beginListChunk(RIFFSerial::kDictionaryFourCC); +} + +void RIFFSerialWriter::endDictionary() +{ + _cursor.endChunk(); +} + +bool RIFFSerialWriter::hasElements() +{ + return false; +} + +void RIFFSerialWriter::beginStruct() +{ + _cursor.beginListChunk(RIFFSerial::kStructFourCC); +} + +void RIFFSerialWriter::handleFieldKey(char const* name, Int index) +{ + // For now we are ignoring field keys, and treating + // structs as basically equivalent to tuples. + SLANG_UNUSED(name); + SLANG_UNUSED(index); +} + +void RIFFSerialWriter::endStruct() +{ + _cursor.endChunk(); +} + +void RIFFSerialWriter::beginTuple() +{ + _cursor.beginListChunk(RIFFSerial::kTupleFourCC); +} + +void RIFFSerialWriter::endTuple() +{ + _cursor.endChunk(); +} + +void RIFFSerialWriter::beginOptional() +{ + _cursor.beginListChunk(RIFFSerial::kOptionalFourCC); +} + +void RIFFSerialWriter::endOptional() +{ + _cursor.endChunk(); +} + +void RIFFSerialWriter::handleSharedPtr(void*& value, Callback callback, void* userData) +{ + // Because we are writing, we only care about the + // pointer that is already present in `value`. + // + void* ptr = value; + + // The first special case we check for is a null pointer, + // which we can serialize as an inline value. + // + if (ptr == nullptr) + { + _cursor.addDataChunk(RIFFSerial::kNullFourCC, nullptr, 0); + return; + } + + // Next, we check to see if we have encountered this + // pointer before, in which case we've already allocated + // an index for it in the object definition list, and + // we can simply write a reference to that index. + // + if (auto found = _mapPtrToObjectIndex.tryGetValue(ptr)) + { + auto objectIndex = *found; + _writeObjectReference(objectIndex); + return; + } + + // If we have a non-null pointer that we haven't seen + // before, then we will allocate a new entry in the + // object definition list, and the pointer itself + // will be written as a reference to that entry. + // + auto objectIndex = ObjectIndex(_objects.getCount()); + _mapPtrToObjectIndex.add(ptr, objectIndex); + _writeObjectReference(objectIndex); + + // At this point we've correctly written the *reference* + // to the object (and will be able to write further + // references later if we see an identical pointer), + // but we also need to make sure that the *definition* + // of the object gets written into the object definition + // list chunk. + // + // The `callback` that was passed in can be used to + // write out the members of the object, but if we + // simply invoked it here and now we would be at risk + // of introducing unbounded recursion in cases where + // the object graph contains very long pointer chains. + // + // (Note that we are not at risk of *infinite* recursion, + // because we have already cached the index for the + // object into `_mapPtrToObjectIndex`) + // + // We will simply add an entry to our `_objects` array + // to represent the to-be-written object, and store + // the pointer and callback there so that we can write + // everything out later, in `_flush()`. + // + ObjectInfo objectInfo; + objectInfo.ptr = ptr; + objectInfo.callback = callback; + objectInfo.userData = userData; + _objects.add(objectInfo); +} + +void RIFFSerialWriter::handleUniquePtr(void*& value, Callback callback, void* userData) +{ + // We treat all pointers as shared pointers, because there isn't really + // an optimized representation we would want to use for the unique case. + // + handleSharedPtr(value, callback, userData); +} + +void RIFFSerialWriter::handleDeferredObjectContents( + void* valuePtr, + Callback callback, + void* userData) +{ + // Because we are already deferring writing of the *entirety* of + // an object's members as part of how `handleSharedPtr()` works, + // we don't need to implement deferral at this juncture. + // + // (In practice the `handleDeferredObjectContents()` operation is + // more for the benefit of reading than writing). + // + callback(valuePtr, userData); +} + +void RIFFSerialWriter::_writeObjectReference(ObjectIndex index) +{ + _cursor.addDataChunk(RIFFSerial::kObjectReferenceFourCC, &index, sizeof(index)); +} + +void RIFFSerialWriter::_initialize(FourCC type) +{ + // The entire content that we write will be nested + // in a single list chunk, with the type that was + // passed in. + // + _cursor.beginListChunk(type); + + // The first child chunk needs to be the object + // definition list chunk, so we create it up front. + // + _objectDefinitionListChunk = _cursor.addListChunk(RIFFSerial::kObjectDefinitionListFourCC); +} + +void RIFFSerialWriter::_flush() +{ + // At this point we might have zero or more object + // waiting to be written into the object definition list + // chunk, and we need to make sure that they all + // get a chance to write their content out. + // + _cursor.setCurrentChunk(_objectDefinitionListChunk); + + // Note that we do *not* compute `_objects.getCount()` outside + // of the loop here, because writing out one object definition + // could cause other objects to be referenced, which could + // in turn add more entries to `_objects` that need to be + // written out. + // + while (_writtenObjectDefinitionCount < _objects.getCount()) + { + auto objectIndex = _writtenObjectDefinitionCount++; + auto objectInfo = _objects[objectIndex]; + + // We shouldn't ever be putting a null pointer into the + // object definition list; there is logic in `handleSharedPtr()` + // that explicitly checks for a null pointer and does an + // early-exit in that case. + // + SLANG_ASSERT(objectInfo.ptr); + + // The callback that was passed into `handleSharedPtr()` should + // be able to write out the value of the pointed-to object. + // + // Note that we are passing the *address* of `objectInfo.ptr` + // and not just its *value*, because this callback is used + // for both reading and writing, and in the reading case it + // needs to be invoked on a pointer-pointer (e.g., a `T**` when + // serializing an object pointer `T*`) so that the callee + // can set the pointed-to pointer to whatever object it + // allocates or finds. + // + objectInfo.callback(&objectInfo.ptr, objectInfo.userData); + + // TODO(tfoley): There is an important invariant here that + // the callback had better only write *one* value, but + // that is not currently being enforced. + } +} + +// +// RIFFSerialReader +// + +RIFFSerialReader::RIFFSerialReader(RIFF::Chunk const* chunk, FourCC type) + : _cursor(chunk) +{ + _initialize(type); +} + +RIFFSerialReader::~RIFFSerialReader() +{ + _flush(); +} + +SerializationMode RIFFSerialReader::getMode() +{ + return SerializationMode::Read; +} + +void RIFFSerialReader::handleBool(bool& value) +{ + switch (_peekChunkType()) + { + case RIFFSerial::kTrueFourCC: + _advanceCursor(); + value = true; + break; + + case RIFFSerial::kFalseFourCC: + _advanceCursor(); + value = false; + break; + + default: + SLANG_UNEXPECTED("invalid format in RIFF"); + break; + } +} + +void RIFFSerialReader::handleInt8(int8_t& value) +{ + value = int8_t(_readInt()); +} + +void RIFFSerialReader::handleInt16(int16_t& value) +{ + value = int16_t(_readInt()); +} + +void RIFFSerialReader::handleInt32(Int32& value) +{ + value = Int32(_readInt()); +} + +void RIFFSerialReader::handleInt64(Int64& value) +{ + value = Int64(_readInt()); +} + +void RIFFSerialReader::handleUInt8(uint8_t& value) +{ + value = uint8_t(_readUInt()); +} + +void RIFFSerialReader::handleUInt16(uint16_t& value) +{ + value = uint16_t(_readUInt()); +} + +void RIFFSerialReader::handleUInt32(UInt32& value) +{ + value = UInt32(_readUInt()); +} + +void RIFFSerialReader::handleUInt64(UInt64& value) +{ + value = UInt64(_readUInt()); +} + +void RIFFSerialReader::handleFloat32(float& value) +{ + value = float(_readFloat()); +} + +void RIFFSerialReader::handleFloat64(double& value) +{ + value = double(_readFloat()); +} + +void RIFFSerialReader::handleString(String& value) +{ + if (_peekChunkType() != RIFFSerial::kStringFourCC) + { + SLANG_UNEXPECTED("invalid format in RIFF"); + return; + } + + auto dataChunk = as<RIFF::DataChunk>(_cursor); + if (!dataChunk) + { + SLANG_UNEXPECTED("invalid format in RIFF"); + return; + } + + auto size = dataChunk->getPayloadSize(); + + value = String(); + value.appendRepeatedChar(' ', size); + dataChunk->writePayloadInto((char*)value.getBuffer(), size); + + _advanceCursor(); +} + +void RIFFSerialReader::beginArray() +{ + _beginListChunk(RIFFSerial::kArrayFourCC); +} + +void RIFFSerialReader::endArray() +{ + _endListChunk(); +} + + +void RIFFSerialReader::beginDictionary() +{ + _beginListChunk(RIFFSerial::kDictionaryFourCC); +} + +void RIFFSerialReader::endDictionary() +{ + _endListChunk(); +} + +bool RIFFSerialReader::hasElements() +{ + return _cursor.get() != nullptr; +} + +void RIFFSerialReader::beginStruct() +{ + _beginListChunk(RIFFSerial::kStructFourCC); +} + +void RIFFSerialReader::handleFieldKey(char const* name, Int index) +{ + // For now we are ignoring field keys, and treating + // structs as basically equivalent to tuples. + SLANG_UNUSED(name); + SLANG_UNUSED(index); +} + +void RIFFSerialReader::endStruct() +{ + _endListChunk(); +} + +void RIFFSerialReader::beginTuple() +{ + _beginListChunk(RIFFSerial::kTupleFourCC); +} + +void RIFFSerialReader::endTuple() +{ + _endListChunk(); +} + +void RIFFSerialReader::beginOptional() +{ + _beginListChunk(RIFFSerial::kOptionalFourCC); +} + +void RIFFSerialReader::endOptional() +{ + _endListChunk(); +} + +RIFFSerialReader::ObjectIndex RIFFSerialReader::_readObjectReference() +{ + if (_peekChunkType() != RIFFSerial::kObjectReferenceFourCC) + { + SLANG_UNEXPECTED("invalid format in RIFF"); + UNREACHABLE_RETURN(false); + } + + auto objectIndex = _readDataChunk<ObjectIndex>(); + SLANG_ASSERT(objectIndex >= 0 && objectIndex < _objects.getCount()); + return objectIndex; +} + +void RIFFSerialReader::handleSharedPtr(void*& value, Callback callback, void* userData) +{ + // The logic here largely mirrors what appears in + // `RIFFSerialWriter::handleSharedPtr`. + // + // We first check for an explicitly written null pointer. + // If we find one our work is very easy. + // + if (_peekChunkType() == RIFFSerial::kNullFourCC) + { + _advanceCursor(); + value = nullptr; + return; + } + + // Otherwise, we expect to find a reference to + // an object index. + // + // Note that `_readObjectReference()` already asserts + // that the index is in-bounds, so we don't repeat + // that test here. + // + auto objectIndex = _readObjectReference(); + + // Now we need to check if we've previously read in + // a reference to the same object. + // + auto& objectInfo = _objects[objectIndex]; + if (objectInfo.state != ObjectState::Unread) + { + // We've seen this object before, although it + // is still possible that we are in the middle + // of reading it as part of an invocation + // of `handleSharedPtr()` further up the call + // stack. + // + // If a non-nullpointer value has already been + // written into the `objectInfo`, then that means + // the callback that was run for the prior (or + // in-flight) read operation has already allocated + // or found an object and written it out. + // In that case we will trust the value. + // + if (objectInfo.ptr == nullptr) + { + // It is possible that the pointer is null because + // the callback that was invoked explicitly *chose* + // to yield a null pointer (perhaps the application + // is choosing not to deserialize some optional + // piece of state). + // + // However, if there is still a callback in-flight + // to read this object, and the pointer is null, + // then we have reached a circular reference, + // and need to signal an error. + // + if (objectInfo.state == ObjectState::ReadingInProgress) + { + SLANG_UNEXPECTED("circularity detected in RIFF deserialization"); + } + } + value = objectInfo.ptr; + return; + } + + // At this point we are reading a reference to an + // object index that has not yet been read at all. + // + SLANG_ASSERT(objectInfo.state == ObjectState::Unread); + + // We cannot return from this function until we have + // stored a pointer into `value`, to represent the + // deserialized object. + // + // Thus we will set ourselves up to start reading + // from the relevant object definition, and invoke + // the callback that was passed in. + // + // Calling into user-defined serialization logic from + // within this function creates the possibility of + // unbounded/infinite recursion, so it is vital that + // the user is properly using `deferSerializeObjectContents()` + // to delay reading data that isn't immediately + // necessary. + // + // We will still set the `objectInfo.state` to reflect + // this in-flight operation so that we can detect + // a cirularity if one occurs at runtime. + // + objectInfo.state = ObjectState::ReadingInProgress; + + // We save/restore the current cursor around + // the callback, because we need to be able + // to return to the current state to continue + // reading whatever comes after the pointer + // we were invoked to read. + // + _pushCursor(); + _cursor = objectInfo.definitionChunk; + + // Note that we are passing the address of `objectInfo.ptr`, + // and `objectInfo` is a reference to an element of the + // `_objects` array. Thus whenever the `callback` stores + // a pointer into that output parameter, the value it writes + // will automatically be visible to any subsequent calls + // to `handleSharedPtr()`, even if they occur before + // `callback` returns. + // + // Thus a "true" circularity can only occur if the callback + // recursively reads a reference to the same object again + // *before* it allocates the in-memory representation of + // that objects and stores a pointer to it into the output + // parameter. + // + callback(&objectInfo.ptr, userData); + + _popCursor(); + + objectInfo.state = ObjectState::ReadingComplete; + + value = objectInfo.ptr; +} + +void RIFFSerialReader::handleUniquePtr(void*& value, Callback callback, void* userData) +{ + // We treat all pointers as shared pointers, because there isn't really + // an optimized representation we would want to use for the unique case. + // + handleSharedPtr(value, callback, userData); +} + +void RIFFSerialReader::handleDeferredObjectContents( + void* valuePtr, + Callback callback, + void* userData) +{ + // Unlike the case in `RIFFSerialWriter::handleDeferredObjectContents()`, + // we very much *do* want to delay invoking the callback until later. + // + // There is a kind of symmetry going on, where the writer delays the + // callback passed to `handleSharedPtr()`, but *not* the callback + // passed to `handleDeferredObjectContents()`, while the reader + // does the opposite: immediately calls the callback in `handleSharedPtr()` + // but delays calling it here. + + // We make sure to save the current `_cursor` value along with + // the arguments that will be passed into the callback, so that + // we can restore the reader to this state before invoking + // the callbak in `_flush()`. + + DeferredAction deferredAction; + deferredAction.savedCursor = _cursor; + deferredAction.valuePtr = valuePtr; + deferredAction.callback = callback; + deferredAction.userData = userData; + + _deferredActions.add(deferredAction); +} + +void RIFFSerialReader::_initialize(FourCC type) +{ + // All of the content will have been serialized as a single RIFF + // list chunk (possibly a root chunk if this content comprises + // an entire file), with the given `type`. + // + _beginListChunk(type); + + // The first child chunk should be the object definition list + // chunk, and we will proactively read through all of the + // entries in that chunk to build up the `_objects` array. + // + // This operation takes linear time in the number of serialized + // objects, independent of their size, because the RIFF chunk + // headers allow us to skip over the content of each of these + // object-definition chunks. + // + _beginListChunk(RIFFSerial::kObjectDefinitionListFourCC); + while (auto objectDefinitionChunk = _cursor.get()) + { + ObjectInfo objectInfo; + objectInfo.definitionChunk = objectDefinitionChunk; + _objects.add(objectInfo); + + _advanceCursor(); + } + _endListChunk(); +} + +void RIFFSerialReader::_flush() +{ + // We need to flush any actions that were deferred + // and are still pending. + // + while (_deferredActions.getCount() != 0) + { + // TODO: For simplicity we are using the `_deferredActions` + // array as a stack (LIFO), but it would be good to + // check whether there is a menaingful difference in how + // large the array would need to grow for a FIFO vs. LIFO, + // and pick the better option. + // + auto deferredAction = _deferredActions.getLast(); + _deferredActions.removeLast(); + + _cursor = deferredAction.savedCursor; + deferredAction.callback(deferredAction.valuePtr, deferredAction.userData); + } +} + +FourCC RIFFSerialReader::_peekChunkType() +{ + auto chunk = _cursor.get(); + if (!chunk) + return 0; + return chunk->getType(); +} + +Int64 RIFFSerialReader::_readInt() +{ + switch (_peekChunkType()) + { + case RIFFSerial::kInt64FourCC: + return _readDataChunk<Int64>(); + case RIFFSerial::kInt32FourCC: + return _readDataChunk<Int32>(); + + case RIFFSerial::kUInt32FourCC: + return _readDataChunk<UInt32>(); + + case RIFFSerial::kUInt64FourCC: + { + auto uintValue = _readDataChunk<UInt64>(); + if (Int64(uintValue) < 0) + { + SLANG_UNEXPECTED("signed/unsigned mismatch in RIFF"); + } + return Int64(uintValue); + } + + default: + SLANG_UNEXPECTED("invalid format in RIFF"); + UNREACHABLE_RETURN(0); + } +} + +UInt64 RIFFSerialReader::_readUInt() +{ + switch (_peekChunkType()) + { + case RIFFSerial::kUInt64FourCC: + return _readDataChunk<UInt64>(); + case RIFFSerial::kUInt32FourCC: + return _readDataChunk<UInt32>(); + + case RIFFSerial::kInt32FourCC: + case RIFFSerial::kInt64FourCC: + { + auto intValue = _readInt(); + if (intValue < 0) + { + SLANG_UNEXPECTED("signed/unsigned mismatch in RIFF"); + } + return UInt64(intValue); + } + + default: + SLANG_UNEXPECTED("invalid format in RIFF"); + UNREACHABLE_RETURN(0); + } +} + +double RIFFSerialReader::_readFloat() +{ + switch (_peekChunkType()) + { + case RIFFSerial::kFloat32FourCC: + return _readDataChunk<float>(); + case RIFFSerial::kFloat64FourCC: + return _readDataChunk<double>(); + + default: + SLANG_UNEXPECTED("invalid format in RIFF"); + UNREACHABLE_RETURN(0); + } +} + +void RIFFSerialReader::_readDataChunk(void* outData, size_t dataSize) +{ + auto dataChunk = as<RIFF::DataChunk>(_cursor); + if (!dataChunk) + { + SLANG_UNEXPECTED("invalid format in RIFF"); + return; + } + auto size = dataChunk->getPayloadSize(); + if (size < dataSize) + { + SLANG_UNEXPECTED("invalid format in RIFF"); + return; + } + dataChunk->writePayloadInto(outData, dataSize); + _advanceCursor(); +} + + +void RIFFSerialReader::_beginListChunk(FourCC type) +{ + auto listChunk = as<RIFF::ListChunk>(_cursor); + if (!listChunk || listChunk->getType() != type) + { + SLANG_UNEXPECTED("invalid format in RIFF"); + } + + _advanceCursor(); + _pushCursor(); + + _cursor = listChunk->getFirstChild(); +} + +void RIFFSerialReader::_endListChunk() +{ + _popCursor(); +} + +void RIFFSerialReader::_advanceCursor() +{ + _cursor = _cursor.getNextSibling(); +} + +void RIFFSerialReader::_pushCursor() +{ + _stack.add(_cursor); +} + +void RIFFSerialReader::_popCursor() +{ + SLANG_ASSERT(_stack.getCount() != 0); + _cursor = _stack.getLast(); + _stack.removeLast(); +} + + +} // namespace Slang |
