BitStream
Loading...
Searching...
No Matches
bit_writer.h
Go to the documentation of this file.
1#pragma once
2#include "../utility/assert.h"
3#include "../utility/crc.h"
4#include "../utility/endian.h"
5#include "../utility/meta.h"
6
7#include "byte_buffer.h"
8#include "serialize_traits.h"
9#include "stream_traits.h"
10
11#include <cstdint>
12#include <cstring>
13#include <memory>
14#include <type_traits>
15
16namespace bitstream
17{
22 template<typename Policy>
24 {
25 public:
26 static constexpr bool writing = true;
27 static constexpr bool reading = false;
28
33 template<typename... Ts,
34 typename = std::enable_if_t<std::is_constructible_v<Policy, Ts...>>>
35 bit_writer(Ts&&... args)
36 noexcept(std::is_nothrow_constructible_v<Policy, Ts...>) :
37 m_Policy(std::forward<Ts>(args) ...),
38 m_Scratch(0),
39 m_ScratchBits(0),
40 m_WordIndex(0) {}
41
42 bit_writer(const bit_writer&) = delete;
43
44 bit_writer(bit_writer&& other) noexcept :
45 m_Policy(std::move(other.m_Policy)),
46 m_Scratch(other.m_Scratch),
47 m_ScratchBits(other.m_ScratchBits),
48 m_WordIndex(other.m_WordIndex)
49 {
50 other.m_Scratch = 0;
51 other.m_ScratchBits = 0;
52 other.m_WordIndex = 0;
53 }
54
55 bit_writer& operator=(const bit_writer&) = delete;
56
58 {
59 m_Policy = std::move(rhs.m_Policy);
60 m_Scratch = rhs.m_Scratch;
61 m_ScratchBits = rhs.m_ScratchBits;
62 m_WordIndex = rhs.m_WordIndex;
63
64 rhs.m_Scratch = 0;
65 rhs.m_ScratchBits = 0;
66 rhs.m_WordIndex = 0;
67
68 return *this;
69 }
70
75 [[nodiscard]] uint8_t* get_buffer() const noexcept { return reinterpret_cast<uint8_t*>(m_Policy.get_buffer()); }
76
81 [[nodiscard]] uint32_t get_num_bits_serialized() const noexcept { return m_Policy.get_num_bits_serialized(); }
82
87 [[nodiscard]] uint32_t get_num_bytes_serialized() const noexcept { return get_num_bits_serialized() > 0U ? ((get_num_bits_serialized() - 1U) / 8U + 1U) : 0U; }
88
94 [[nodiscard]] bool can_serialize_bits(uint32_t num_bits) const noexcept { return m_Policy.can_serialize_bits(num_bits); }
95
101 [[nodiscard]] uint32_t get_remaining_bits() const noexcept { return get_total_bits() - get_num_bits_serialized(); }
102
107 [[nodiscard]] uint32_t get_total_bits() const noexcept { return m_Policy.get_total_bits(); }
108
113 uint32_t flush() noexcept
114 {
115 if (m_ScratchBits > 0U)
116 {
117 uint32_t* ptr = m_Policy.get_buffer() + m_WordIndex;
118 uint32_t ptr_value = static_cast<uint32_t>(m_Scratch >> 32U);
119 *ptr = utility::to_big_endian32(ptr_value);
120
121 m_Scratch = 0U;
122 m_ScratchBits = 0U;
123 m_WordIndex++;
124 }
125
127 }
128
134 [[nodiscard]] bool pad_to_size(uint32_t num_bytes) noexcept
135 {
136 uint32_t num_bits_written = get_num_bits_serialized();
137
138 BS_ASSERT(num_bytes * 8U >= num_bits_written);
139
140 BS_ASSERT(can_serialize_bits(num_bytes * 8U - num_bits_written));
141
142 if (num_bits_written == 0)
143 {
144 BS_ASSERT(m_Policy.extend(num_bytes * 8U - num_bits_written));
145
146 std::memset(m_Policy.get_buffer(), 0, num_bytes);
147
148 m_Scratch = 0;
149 m_ScratchBits = 0;
150 m_WordIndex = num_bytes / 4;
151
152 return true;
153 }
154
155 uint32_t remainder = (num_bytes * 8U - num_bits_written) % 32U;
156 uint32_t zero = 0;
157
158 // Align to byte
159 if (remainder != 0U)
160 BS_ASSERT(serialize_bits(zero, remainder));
161
162 uint32_t offset = get_num_bits_serialized() / 32;
163 uint32_t max = num_bytes / 4;
164
165 // Serialize words
166 for (uint32_t i = offset; i < max; i++)
167 BS_ASSERT(serialize_bits(zero, 32));
168
169 return true;
170 }
171
177 [[nodiscard]] bool pad(uint32_t num_bytes) noexcept
178 {
179 return pad_to_size(get_num_bytes_serialized() + num_bytes);
180 }
181
186 [[nodiscard]] bool align() noexcept
187 {
188 uint32_t remainder = m_ScratchBits % 8U;
189 if (remainder != 0U)
190 {
191 uint32_t zero = 0U;
192 bool status = serialize_bits(zero, 8U - remainder);
193
194 BS_ASSERT(status && get_num_bits_serialized() % 8U == 0U);
195 }
196 return true;
197 }
198
205 [[nodiscard]] bool serialize_bits(uint32_t value, uint32_t num_bits) noexcept
206 {
207 BS_ASSERT(num_bits > 0U && num_bits <= 32U);
208
209 BS_ASSERT(m_Policy.extend(num_bits));
210
211 // This is actually slower
212 // Possibly due to unlikely branching
213 /*if (num_bits == 32U && m_ScratchBits == 0U)
214 {
215 uint32_t* ptr = m_Policy.get_buffer() + m_WordIndex;
216
217 *ptr = utility::to_big_endian32(value);
218
219 m_WordIndex++;
220
221 return true;
222 }*/
223
224 uint32_t offset = 64U - num_bits - m_ScratchBits;
225 uint64_t ls_value = static_cast<uint64_t>(value) << offset;
226
227 m_Scratch |= ls_value;
228 m_ScratchBits += num_bits;
229
230 if (m_ScratchBits >= 32U)
231 {
232 uint32_t* ptr = m_Policy.get_buffer() + m_WordIndex;
233 uint32_t ptr_value = static_cast<uint32_t>(m_Scratch >> 32U);
234 *ptr = utility::to_big_endian32(ptr_value);
235 m_Scratch <<= 32ULL;
236 m_ScratchBits -= 32U;
237 m_WordIndex++;
238 }
239
240 return true;
241 }
242
249 [[nodiscard]] bool serialize_bytes(const uint8_t* bytes, uint32_t num_bits) noexcept
250 {
251 BS_ASSERT(num_bits > 0U);
252
253 BS_ASSERT(can_serialize_bits(num_bits));
254
255 // Write the byte array as words
256 const uint32_t* word_buffer = reinterpret_cast<const uint32_t*>(bytes);
257 uint32_t num_words = num_bits / 32U;
258
259 if (m_ScratchBits % 32U == 0U && num_words > 0U)
260 {
261 BS_ASSERT(m_Policy.extend(num_words * 32U));
262
263 // If the written buffer is word-aligned, just memcpy it
264 std::memcpy(m_Policy.get_buffer() + m_WordIndex, word_buffer, num_words * 4U);
265
266 m_WordIndex += num_words;
267 }
268 else
269 {
270 // If the buffer is not word-aligned, serialize a word at a time
271 for (uint32_t i = 0U; i < num_words; i++)
272 {
273 // Casting a byte-array to an int is wrong on little-endian systems
274 // We have to swap the bytes around
275 uint32_t value = utility::to_big_endian32(word_buffer[i]);
276 BS_ASSERT(serialize_bits(value, 32U));
277 }
278 }
279
280 // Early exit if the word-count matches
281 if (num_bits % 32U == 0U)
282 return true;
283
284 uint32_t remaining_bits = num_bits - num_words * 32U;
285
286 uint32_t num_bytes = (remaining_bits - 1U) / 8U + 1U;
287 for (uint32_t i = 0U; i < num_bytes; i++)
288 {
289 uint32_t value = static_cast<uint32_t>(bytes[num_words * 4U + i]);
290 BS_ASSERT(serialize_bits(value, (std::min)(remaining_bits - i * 8U, 8U)));
291 }
292
293 return true;
294 }
295
301 template<typename T>
302 [[nodiscard]] bool serialize_into(bit_writer<T>& writer) const noexcept
303 {
304 uint8_t* buffer = reinterpret_cast<uint8_t*>(m_Policy.get_buffer());
305 uint32_t num_bits = get_num_bits_serialized();
306 uint32_t remainder_bits = num_bits % 8U;
307
308 BS_ASSERT(writer.serialize_bytes(buffer, num_bits - remainder_bits));
309
310 if (remainder_bits > 0U)
311 {
312 uint32_t byte_value = buffer[num_bits / 8U] >> (8U - remainder_bits);
313 BS_ASSERT(writer.serialize_bits(byte_value, remainder_bits));
314 }
315
316 return true;
317 }
318
327 template<typename Trait, typename... Args, typename = utility::has_serialize_t<Trait, bit_writer, Args...>>
328 [[nodiscard]] bool serialize(Args&&... args) noexcept(utility::is_serialize_noexcept_v<Trait, bit_writer, Args...>)
329 {
330 return serialize_traits<Trait>::serialize(*this, std::forward<Args>(args)...);
331 }
332
343 template<typename... Args, typename Trait, typename = utility::has_deduce_serialize_t<Trait, bit_writer, Args...>>
344 [[nodiscard]] bool serialize(Trait&& arg, Args&&... args) noexcept(utility::is_deduce_serialize_noexcept_v<Trait, bit_writer, Args...>)
345 {
346 return serialize_traits<utility::deduce_trait_t<Trait, bit_writer, Args...>>::serialize(*this, std::forward<Trait>(arg), std::forward<Args>(args)...);
347 }
348
349 private:
350 Policy m_Policy;
351
352 uint64_t m_Scratch;
353 int m_ScratchBits;
354 size_t m_WordIndex;
355 };
356
358
359 template<typename T>
361}
#define BS_ASSERT(...)
Definition assert.h:15
A stream for writing objects tightly into a buffer.
Definition bit_writer.h:24
bit_writer & operator=(const bit_writer &)=delete
bit_writer & operator=(bit_writer &&rhs) noexcept
Definition bit_writer.h:57
bool serialize_into(bit_writer< T > &writer) const noexcept
Writes the contents of the buffer into the given writer. Essentially copies the entire buffer without...
Definition bit_writer.h:302
bool serialize(Trait &&arg, Args &&... args) noexcept(utility::is_deduce_serialize_noexcept_v< Trait, bit_writer, Args... >)
Writes to the buffer, by trying to deduce the trait.
Definition bit_writer.h:344
bool serialize_bits(uint32_t value, uint32_t num_bits) noexcept
Writes the first num_bits bits of value into the buffer.
Definition bit_writer.h:205
uint32_t get_total_bits() const noexcept
Returns the size of the buffer, in bits.
Definition bit_writer.h:107
uint32_t get_num_bytes_serialized() const noexcept
Returns the number of bytes which have been written to the buffer.
Definition bit_writer.h:87
uint32_t get_remaining_bits() const noexcept
Returns the number of bits which have not been written yet.
Definition bit_writer.h:101
uint32_t flush() noexcept
Flushes any remaining bits into the buffer. Use this when you no longer intend to write anything to t...
Definition bit_writer.h:113
bool serialize_bytes(const uint8_t *bytes, uint32_t num_bits) noexcept
Writes the first num_bits bits of the given byte array, 32 bits at a time.
Definition bit_writer.h:249
bit_writer(bit_writer &&other) noexcept
Definition bit_writer.h:44
bool align() noexcept
Pads the buffer with up to 8 zeros, so that the next write is byte-aligned.
Definition bit_writer.h:186
bool serialize(Args &&... args) noexcept(utility::is_serialize_noexcept_v< Trait, bit_writer, Args... >)
Writes to the buffer, using the given Trait.
Definition bit_writer.h:328
static constexpr bool reading
Definition bit_writer.h:27
bool pad_to_size(uint32_t num_bytes) noexcept
Pads the buffer up to the given number of bytes with zeros.
Definition bit_writer.h:134
static constexpr bool writing
Definition bit_writer.h:26
bit_writer(const bit_writer &)=delete
bit_writer(Ts &&... args) noexcept(std::is_nothrow_constructible_v< Policy, Ts... >)
Construct a writer with the parameters passed to the underlying policy.
Definition bit_writer.h:35
bool pad(uint32_t num_bytes) noexcept
Pads the buffer up with the given number of bytes.
Definition bit_writer.h:177
bool can_serialize_bits(uint32_t num_bits) const noexcept
Returns whether the num_bits can fit in the buffer.
Definition bit_writer.h:94
uint8_t * get_buffer() const noexcept
Returns the buffer that this writer is currently serializing into.
Definition bit_writer.h:75
uint32_t get_num_bits_serialized() const noexcept
Returns the number of bits which have been written to the buffer.
Definition bit_writer.h:81
constexpr bool is_deduce_serialize_noexcept_v
Definition meta.h:104
has_serialize_t< deduce_trait_t< Trait, Stream, Args... >, Stream, Trait, Args... > has_deduce_serialize_t
Definition meta.h:101
constexpr bool is_serialize_noexcept_v
Definition meta.h:40
BS_CONSTEXPR uint32_t to_big_endian32(uint32_t value)
Definition endian.h:104
typename deduce_trait< void, Trait, Stream, Args... >::type deduce_trait_t
Definition meta.h:96
std::void_t< decltype(serialize_traits< T >::serialize(std::declval< Stream & >(), std::declval< Args >()...))> has_serialize_t
Definition meta.h:17
Definition bounded_range.h:28
A class for specializing trait serialization functions.
Definition serialize_traits.h:11