Skip to content

Commit 5e678bb

Browse files
committed
Add MemoryReaderWriter class
1 parent 11cd533 commit 5e678bb

File tree

1 file changed

+304
-0
lines changed

1 file changed

+304
-0
lines changed

Common.MemoryReaderWriter.h

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
// Adapter that can wrap a byte container, either fixed size or variable, and progressively append
2+
// new content or read/write various offsets.
3+
//
4+
// e.g.
5+
// std::vector<std::byte> someVector;
6+
// MemoryReaderWriter someVectorWriter(someVector);
7+
//
8+
// std::array<std::byte, 4> someArray;
9+
// MemoryReaderWriter someArrayWriter(someArray);
10+
//
11+
// MyFixedArrayThatsNonTrivial<std::byte, 4> myFixedArray;
12+
// MemoryReaderWriter myFixedArrayWriter(myFixedArray, MemoryReaderWriter::FixedResizeFunction);
13+
//
14+
// MyVector<std::byte> myVector;
15+
// auto myVectorResizer = [&](uint32_t newSizeInBytes)
16+
// {
17+
// myVector.Resize(newSizeInBytes);
18+
// return make_span<std::byte>(myVector.GetBegin(), myVector.GetSize());
19+
// }
20+
// MemoryReaderWriter writerMyVector(myVector, myArraymyVectorResizerResizer);
21+
// MemoryReaderWriter<uint32_t> writerMyVectorUint32(myVector, myArraymyVectorResizerResizer);
22+
//
23+
// uint32_t floatValueByteOffset = constantsWriter.Append(someFloatValue);
24+
// uint32_t trivialStructByteOffset = someVectorWriter.AppendAligned(someTrivialStruct);
25+
// uint32_t someFloatVectorByteOffset = someVectorWriter.AppendArray(someFloatVector);
26+
// someVectorWriter.Write(floatValueByteOffset, x);
27+
// float y = someVectorWriter.Read(floatValueByteOffset);
28+
// auto floatVectorSpan = someVectorWriter.ReadArray<float>(someFloatVectorByteOffset, 3);
29+
//
30+
// Notes:
31+
// - This class is intended to be used transiently on the stack to temporarily wrap a container.
32+
// - The caller must keep the underlying container alive during usage.
33+
// - If resizeable, the caller must not clear/reserve/resize the container during usage.
34+
// The same rules as for invalidation of iterators applies. Modifying values is fine.
35+
// - If resizeable, two MemoryReaderWriter's must not use same underlying buffer at the same time.
36+
// - Two counts exist, one for the total number of readable bytes and one for the number of bytes
37+
// written, which is initially zero until values are appended/skipped.
38+
//
39+
template <typename MinimumElementType = uint8_t> // No element should be smaller than this. All offsets are relative to this.
40+
class MemoryReaderWriter
41+
{
42+
public:
43+
MemoryReaderWriter() = default;
44+
MemoryReaderWriter(const MemoryReaderWriter& container) = delete; // Unwise to have two at the same time.
45+
MemoryReaderWriter(MemoryReaderWriter&& container) = default;
46+
47+
constexpr static uint32_t MinimumElementTypeByteSize = sizeof(MinimumElementType);
48+
49+
// The resizing function is used to grow the container and get a span for the new
50+
// container data. For most uses, the simplest constructor suffices and will choose
51+
// a suitable resizing function. If your container supports resizing but has no
52+
// resize() method, you'll need to pass your own function, or if resizing is not
53+
// intended, use the stock FixedResizeFunction.
54+
using ResizeFunctionTypeSignature = gsl::span<std::byte>(uint32_t sizeInBytes);
55+
56+
// The container to wrap can be any STL-compatible container which has data() and size()
57+
// methods and which is implicitly convertible to a span of bytes. Otherwise the caller
58+
// should use make_span. The caller is responsible for ensuring the lifetime of the
59+
// container outlives outlives the memory reader/writer.
60+
//
61+
// This constructor provides a default resizing function depending on whether the container
62+
// is trivial (e.g. a raw C array or std::array of POD's) or nontrivial, in which case it
63+
// expects an STL-compatible resize() method (e.g. std::vector, std::string). If your
64+
// container doesn't satisfy that, call the other constructor with a specific resizer.
65+
template <typename Container>
66+
explicit MemoryReaderWriter(Container& container)
67+
: m_data(gsl::make_span(container))
68+
{
69+
if constexpr (std::is_trivial_v<Container>)
70+
{
71+
// Trivial data structs and fixed sized buffers such as a std::array of POD's cannot
72+
// be resized. Attempts to do so will throw.
73+
m_resizeFunction = FixedResizeFunction;
74+
}
75+
else
76+
{
77+
// Otherwise assume it's a complex container supporting resize().
78+
m_resizeFunction = [&](uint32_t newSizeInBytes) -> gsl::span<std::byte>
79+
{
80+
container.resize(newSizeInBytes);
81+
return gsl::span(container.data(), container.size());
82+
};
83+
}
84+
}
85+
86+
template <typename Container, typename ResizeFunction>
87+
MemoryReaderWriter(Container& container, const ResizeFunction& function)
88+
: m_data(gsl::make_span(container)),
89+
m_resizeFunction(function)
90+
{
91+
}
92+
93+
explicit MemoryReaderWriter(gsl::span<std::byte> data)
94+
: m_data(data),
95+
m_resizeFunction(FixedResizeFunction)
96+
{
97+
}
98+
99+
MemoryReaderWriter(_In_reads_(valuesCount) void* data, uint32_t dataByteCount)
100+
: m_data(gsl::make_span(reinterpret_cast<std::byte*>(data), dataByteCount)),
101+
m_resizeFunction(FixedResizeFunction)
102+
{
103+
}
104+
105+
// Stock implementation of a fixed resizing function for backing stores which do not
106+
// grow, such as std::array and C arrays.
107+
static gsl::span<std::byte> FixedResizeFunction(uint32_t newSizeInBytes)
108+
{
109+
throw std::bad_alloc();
110+
}
111+
112+
inline uint32_t GetWrittenByteCount() const noexcept
113+
{
114+
return m_writtenByteCount; // Return written byte amount, not just m_data.size() which can be larger.
115+
}
116+
117+
inline uint32_t GetWrittenElementCount() const noexcept
118+
{
119+
return m_writtenByteCount / MinimumElementTypeByteSize;
120+
}
121+
122+
uint32_t GetReadableByteCount() const noexcept
123+
{
124+
return gsl::narrow_cast<uint32_t>(m_data.size_bytes());
125+
}
126+
127+
// Append a new value to the end of the writeable region, advancing the written count and returning the offset of the written value.
128+
template <typename T>
129+
uint32_t Append(const T& value)
130+
{
131+
return AppendArrayInternal(m_writtenByteCount, WrapValueAsByteSpan(value));
132+
}
133+
134+
// Align to the natural alignment of the given type first, returning the aligned offset.
135+
template <typename T>
136+
uint32_t AppendAligned(const T& value)
137+
{
138+
return AppendArrayInternal(RoundUpToMultiple(m_writtenByteCount, uint32_t(alignof(T))), WrapValueAsByteSpan(value));
139+
}
140+
141+
// Append the contents to the buffer, where Container can be anything castable to a span.
142+
template <typename Container>
143+
uint32_t AppendArray(const Container& values)
144+
{
145+
auto span = gsl::make_span(values);
146+
return AppendArrayInternal(m_writtenByteCount, gsl::as_bytes(span));
147+
}
148+
149+
// Special case for broken initializer_list which should have a data() method.
150+
template <typename T>
151+
uint32_t AppendArray(std::initializer_list<T> values)
152+
{
153+
auto span = gsl::make_span(values.begin(), values.end());
154+
return AppendArrayInternal(m_writtenByteCount, gsl::as_bytes(span));
155+
}
156+
157+
template <typename T>
158+
uint32_t AppendArray(_In_count_(valueCount) const T* values, uint32_t valueCount)
159+
{
160+
auto span = gsl::make_span(values, valueCount);
161+
return AppendArrayInternal(m_writtenByteCount, gsl::as_bytes(span));
162+
}
163+
164+
// Align first and then write the array elements.
165+
template <typename Container>
166+
uint32_t AppendArrayAligned(const Container& values)
167+
{
168+
auto span = gsl::make_span(values);
169+
using T = decltype(*span.data());
170+
return AppendArrayInternal(RoundUpToMultiple(m_writtenByteCount, uint32_t(alignof(T))), gsl::as_bytes(values));
171+
}
172+
173+
// Write to a specific offset. Unlike Append, this does not grow GetWrittenByteCount.
174+
template <typename T>
175+
void Write(uint32_t offsetInElements, const T& value)
176+
{
177+
// Types must be trivial byte-wise copyable, lacking constructors and destructors.
178+
// They can be non-standard layout though.
179+
static_assert(std::is_trivial_v<T>);
180+
Get<T>(offsetInElements) = value;
181+
}
182+
183+
template <typename T>
184+
void WriteArray(uint32_t offsetInElements, span<const T> values)
185+
{
186+
static_assert(std::is_trivial_v<T>);
187+
const uint32_t offsetInBytes = offsetInElements * MinimumElementTypeByteSize;
188+
WriteArrayInternal(offsetInBytes, gsl::as_bytes(values));
189+
}
190+
191+
// Skip by the number of writeable bytes, returning the offset before the skip.
192+
// Unlike Append, this simply reserves blank space, and no data is written.
193+
uint32_t Skip(uint32_t bytesToSkip)
194+
{
195+
assert(bytesToSkip % MinimumElementTypeByteSize == 0);
196+
return SkipInternal(m_writtenByteCount, bytesToSkip);
197+
}
198+
199+
// Skip one element of the given type. e.g. writer.Skip<int32_t>().
200+
template <typename T>
201+
uint32_t Skip()
202+
{
203+
static_assert(sizeof(T) % MinimumElementTypeByteSize == 0);
204+
return SkipInternal(m_writtenByteCount, sizeof(T));
205+
}
206+
207+
// Align to the natural alignment of the type first, then skip ahead the byte size of the type.
208+
template <typename T>
209+
uint32_t SkipAligned()
210+
{
211+
static_assert(sizeof(T) % MinimumElementTypeByteSize == 0);
212+
return SkipInternal(RoundUpToMultiple(m_writtenByteCount, uint32_t(alignof(T))), sizeof(T));
213+
}
214+
215+
// Align to the given multiple. If already aligned, nothing happens.
216+
uint32_t Align(uint32_t byteAlignment)
217+
{
218+
assert(byteAlignment % MinimumElementTypeByteSize == 0);
219+
return SkipInternal(RoundUpToMultiple(m_writtenByteCount, byteAlignment), 0);
220+
}
221+
222+
template <typename T>
223+
T& Get(uint32_t offsetInElements)
224+
{
225+
const uint32_t offsetInBytes = offsetInElements * MinimumElementTypeByteSize;
226+
assert(offsetInBytes + sizeof(T) <= size_t(m_data.size_bytes()));
227+
return *GetPointerInternal<T>(offsetInBytes);
228+
}
229+
230+
template <typename T>
231+
const T& Read(uint32_t offsetInElements) const
232+
{
233+
const uint32_t offsetInBytes = offsetInElements * MinimumElementTypeByteSize;
234+
assert(offsetInBytes + sizeof(T) <= size_t(m_data.size_bytes()));
235+
return *GetPointerInternal<T>(offsetInBytes);
236+
}
237+
238+
template <typename T>
239+
gsl::span<const T> ReadArray(uint32_t offsetInElements, uint32_t elementCount) const
240+
{
241+
const uint32_t offsetInBytes = offsetInElements * MinimumElementTypeByteSize;
242+
assert(offsetInBytes + sizeof(T) * elementCount <= size_t(m_data.size_bytes()));
243+
return gsl::make_span<const T>(GetPointerInternal<T>(offsetInBytes), elementCount);
244+
}
245+
246+
void EnsureWriteableSize(uint32_t minimumByteSize, uint32_t additionalSizeInBytes = 0)
247+
{
248+
uint32_t newSizeInBytes = minimumByteSize + additionalSizeInBytes;
249+
if (newSizeInBytes < minimumByteSize) // overflow check.
250+
std::bad_array_new_length();
251+
252+
if (newSizeInBytes > gsl::narrow_cast<uint32_t>(m_data.size_bytes()))
253+
{
254+
if (!m_resizeFunction)
255+
throw std::logic_error("MemoryReaderWriter - tried to resize a container that has no resize functionality.");
256+
257+
m_data = m_resizeFunction(newSizeInBytes);
258+
}
259+
m_writtenByteCount = newSizeInBytes;
260+
}
261+
262+
protected:
263+
template <typename T>
264+
inline T* GetPointerInternal(uint32_t offsetInBytes) const noexcept // Logically const.
265+
{
266+
return reinterpret_cast<T*>(const_cast<std::byte*>(m_data.data()) + offsetInBytes);
267+
}
268+
269+
// Append the byte array, returning the offset in elements.
270+
uint32_t AppendArrayInternal(uint32_t offsetInBytes, gsl::span<const std::byte> values)
271+
{
272+
EnsureWriteableSize(offsetInBytes, gsl::narrow_cast<uint32_t>(values.size_bytes()));
273+
WriteArrayInternal(offsetInBytes, values);
274+
return offsetInBytes / MinimumElementTypeByteSize;
275+
}
276+
277+
void WriteArrayInternal(uint32_t offsetInBytes, gsl::span<const std::byte> values)
278+
{
279+
assert(offsetInBytes + values.size_bytes() <= gsl::narrow_cast<uint32_t>(m_data.size_bytes()));
280+
memcpy(m_data.data() + offsetInBytes, values.data(), values.size_bytes());
281+
}
282+
283+
// Skip the given number of bytes, returning the offset in elements.
284+
uint32_t SkipInternal(uint32_t offsetInBytes, uint32_t additionalBytesToGrow)
285+
{
286+
EnsureWriteableSize(offsetInBytes, additionalBytesToGrow);
287+
return offsetInBytes / MinimumElementTypeByteSize;
288+
}
289+
290+
template <typename T>
291+
static gsl::span<const std::byte> WrapValueAsByteSpan(const T& value)
292+
{
293+
// Ensure we're not wrapping anything except simple structs and types,
294+
// but especially not std::vector by accident, since you really want
295+
// the bytes the vector points to and not the vector class itself.
296+
static_assert(std::is_trivial_v<T>);
297+
return gsl::span<const std::byte>(reinterpret_cast<const std::byte*>(std::addressof(value)), sizeof(T));
298+
}
299+
300+
protected:
301+
gsl::span<std::byte> m_data;
302+
std::function<ResizeFunctionTypeSignature> m_resizeFunction;
303+
uint32_t m_writtenByteCount = 0; // Last appended byte offset into m_data.
304+
};

0 commit comments

Comments
 (0)