Skip to content

Commit

Permalink
Merge pull request #11 from KredeGC/dev
Browse files Browse the repository at this point in the history
Add more assertions
  • Loading branch information
KredeGC committed Jun 24, 2023
2 parents 5780a19 + c66926d commit f7f7e8b
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 120 deletions.
51 changes: 51 additions & 0 deletions .github/workflows/dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Test

on:
pull_request:
branches: [ master ]
paths-ignore:
- '**/*.md'
- '**/*.gitignore'
- '**/Doxyfile'

jobs:
Build:
strategy:
matrix:
machine:
- os: ubuntu-latest
action: gmake2
toolset: gcc
- os: ubuntu-latest
action: gmake2
toolset: clang
- os: windows-latest
action: vs2019
toolset: msc
runs-on: ${{ matrix.machine.os }}
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup premake
uses: abel0b/setup-premake@v2.2
with:
version: "5.0.0-beta1"
- name: Install GCC
if: matrix.machine.os == 'ubuntu-latest' && matrix.machine.toolset == 'gcc'
run: sudo apt-get update && sudo apt-get install -y gcc g++
- name: Install Clang & LLVM
if: matrix.machine.os == 'ubuntu-latest' && matrix.machine.toolset == 'clang'
run: sudo apt-get update && sudo apt-get install -y clang llvm lld
- name: Install msbuild to PATH
if: matrix.machine.os == 'windows-latest' && matrix.machine.toolset == 'msc'
uses: microsoft/setup-msbuild@v1.1
- name: Run premake
run: premake5 ${{ matrix.machine.action }} --toolset=${{ matrix.machine.toolset }} --dialect=C++17
- name: Build
run: premake5 build --config=debug --architecture=x64
- name: Run test
run: premake5 test --config=debug --architecture=x64
6 changes: 0 additions & 6 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,6 @@ name: Test
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
paths-ignore:
- '**/*.md'
- '**/*.gitignore'
- '**/Doxyfile'

jobs:
Build:
Expand Down
268 changes: 154 additions & 114 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,20 @@ Based on [Glenn Fiedler's articles](https://gafferongames.com/post/reading_and_w
* [Installation](#installation)
* [Usage](#usage)
* [Documentation](#documentation)
* [Serialization Examples](#serialization-examples)
* [Serializables - serialize_traits](#serializables---serialize_traits)
* [Booleans - bool](#booleans---bool)
* [Bounded integers - T](#bounded-integers---t)
* [Compile-time bounded integers - bounded_int\<T, T Min, T Max\>](#compile-time-bounded-integers---bounded_intt-t-min-t-max)
* [C-style strings - const char*](#c-style-strings---const-char)
* [Compile-time bounded C-style strings - bounded_string\<const char*, Max\>](#compile-time-bounded-c-style-strings---bounded_stringconst-char-max)
* [Modern strings - std::basic_string\<T\>](#modern-strings---stdbasic_stringt)
* [Compile-time bounded Modern strings - bounded_string\<std::basic_string\<T\>, Max\>](#compile-time-bounded-modern-strings---bounded_stringstdbasic_stringt-max)
* [Double-precision float - double](#double-precision-float---double)
* [Single-precision float - float](#single-precision-float---float)
* [Half-precision float - half_precision](#half-precision-float---half_precision)
* [Bounded float - bounded_range](#bounded-float---bounded_range)
* [Quaternion - smallest_three\<Q, BitsPerElement\>](#quaternion---smallest_threeq-bitsperelement)
* [Serialization Examples](#serialization-examples)
* [Extensibility](#extensibility)
* [Adding new serializables types](#adding-new-serializables-types)
* [Unified serialization](#unified-serialization)
Expand All @@ -54,7 +56,7 @@ The header files can either be downloaded from the [releases page](https://githu
The source and header files inside the `src/` directory are only tests and should not be included into your project, unless you wish to test the library as part of your pipeline.

# Usage
The library has a global header file ([`bitstream/bitstream.h`](https://github.com/KredeGC/BitStream/tree/master/include/bitstream/bitstream.h)) which includes every other header file in the library.
The library has a main header file ([`bitstream/bitstream.h`](https://github.com/KredeGC/BitStream/tree/master/include/bitstream/bitstream.h)) which includes every other header file in the library.

If you only need certain features you can instead opt to just include the files you need.
The files are stored in categories:
Expand All @@ -76,6 +78,117 @@ You can also look at the unit tests to get a better idea about what you can expe
# Documentation
Refer to [the documentation](https://kredegc.github.io/BitStream/namespaces.html) for more information about what different classes provide.

# Serialization Examples
The examples below follow the same structure: First writing to a buffer and then reading from it. Each example is littered with comments about the procedure, as well as what outcome is expected.

Writing the first 5 bits of an int to the buffer, then reading it back:
```cpp
// Create a writer, referencing the buffer and its size
alignas(uint32_t) uint8_t buffer[4]; // Buffer must be a multiple of 4 bytes / 32 bits and 4-byte-aligned
bit_writer writer(buffer, 4);

// Write the value
uint32_t value = 27; // We can choose any value below 2^5. Otherwise we need more than 5 bits
writer.serialize_bits(value, 5);

// Flush the writer's remaining state into the buffer
uint32_t num_bits = writer.flush();

// Create a reader, referencing the buffer and bits written
bit_reader reader(buffer, num_bits);

// Read the value back
uint32_t out_value; // We don't have to initialize it yet
reader.serialize_bits(out_value, 5); // out_value should now have a value of 27
```
Writing a signed int to the buffer, within a range:
```cpp
// Create a writer, referencing the buffer and its size
byte_buffer<4> buffer; // byte_bufer is just a wrapper for a 4-byte aligned buffer
bit_writer writer(buffer);
// Write the value
int32_t value = -45; // We can choose any value within the range below
writer.serialize<int32_t>(value, -90, 40); // A lower and upper bound which the value will be quantized between
// Flush the writer's remaining state into the buffer
uint32_t num_bits = writer.flush();
// Create a reader, referencing the buffer and bits written
bit_reader reader(buffer, num_bits);
// Read the value back
int32_t out_value; // We don't have to initialize it yet
reader.serialize<int32_t>(out_value, -90, 40); // out_value should now have a value of -45
```

Writing a c-style string into the buffer:
```cpp
// Create a writer, referencing the buffer and its size
byte_buffer<32> buffer;
bit_writer writer(buffer);

// Write the value
const char* value = "Hello world!";
writer.serialize<const char*>(value, 32U); // The second argument is the maximum size we expect the string to be

// Flush the writer's remaining state into the buffer
uint32_t num_bits = writer.flush();

// Create a reader, referencing the buffer and bits written
bit_reader reader(buffer, num_bits);

// Read the value back
char out_value[32]; // Set the size to the max size
reader.serialize<const char*>(out_value, 32U); // out_value should now contain "Hello world!\0"
```
Writing a std::string into the buffer:
```cpp
// Create a writer, referencing the buffer and its size
byte_buffer<32> buffer;
bit_writer writer(buffer);
// Write the value
std::string value = "Hello world!";
writer.serialize<std::string>(value, 32U); // The second argument is the maximum size we expect the string to be
// Flush the writer's remaining state into the buffer
uint32_t num_bits = writer.flush();
// Create a reader, referencing the buffer and bits written
bit_reader reader(buffer, num_bits);
// Read the value back
std::string out_value; // The string will be resized if the output doesn't fit
reader.serialize<std::string>(out_value, 32U); // out_value should now contain "Hello world!"
```

Writing a float into the buffer with a bounded range and precision:
```cpp
// Create a writer, referencing the buffer and its size
byte_buffer<4> buffer;
bit_writer writer(buffer);

// Write the value
bounded_range range(1.0f, 4.0f, 1.0f / 128.0f); // Min, Max, Precision
float value = 1.2345678f;
writer.serialize<bounded_range>(range, value);

// Flush the writer's remaining state into the buffer
uint32_t num_bits = writer.flush();

// Create a reader, referencing the buffer and bits written
bit_reader reader(buffer, num_bits);

// Read the value back
float out_value;
reader.serialize<bounded_range>(range, out_value); // out_value should now be a value close to 1.2345678f
```
These examples can also be seen in [`src/test/examples_test.cpp`](https://github.com/KredeGC/BitStream/tree/master/src/test/examples_test.cpp).
# Serializables - serialize_traits
Below is a noncomprehensive list of serializable traits.
A big feature of the library is extensibility, which is why you can add your own types as you please, or choose not to include specific types if you don't need them.
Expand Down Expand Up @@ -133,7 +246,9 @@ bool status_read = reader.serialize<bounded_int<int16_t, -512, 2098>>(out_value)

## C-style strings - const char*
A trait that only covers c-style strings.<br/>
Takes the pointer and a maximum expected string length.
Takes the pointer and a maximum expected string length.<br/>
Note: In C++20 UTF-8 strings were given their own type, which means that you either have to cast your `char8_t*` to a `char*`
or use `serialize<const char8_t*>` instead.

The call signature can be seen below:
```cpp
Expand All @@ -147,6 +262,23 @@ bool status_write = writer.serialize<const char*>(in_value, 32);
bool status_read = reader.serialize<const char*>(out_value, 32);
```
## Compile-time bounded C-style strings - bounded_string\<const char*, Max\>
A trait that only covers c-style strings.<br/>
Takes the pointer as argument and a maximum expected string length as template parameter.<br/>
This is preferable if you know the maximum length at compile time as it skips having to calculate the number of bits required.
The call signature can be seen below:
```cpp
bool serialize<bounded_string<const char*, MaxSize>>(const char* value);
```
As well as a short example of its usage:
```cpp
const char* in_value = "Hello world!";
char out_value[32]{ 0 };
bool status_write = writer.serialize<bounded_string<const char*, 32U>>(in_value);
bool status_read = reader.serialize<bounded_string<const char*, 32U>>(out_value);
```
## Modern strings - std::basic_string\<T\>
A trait that covers any combination of basic_string, including strings with different allocators.<br/>
Takes a reference to the string and a maximum expected string length.
Expand All @@ -165,6 +297,25 @@ bool status_write = writer.serialize<std::string>(in_value, 32);
bool status_read = reader.serialize<std::string>(out_value, 32);
```

## Compile-time bounded Modern strings - bounded_string\<std::basic_string\<T\>, Max\>
A trait that covers any combination of basic_string, including strings with different allocators.<br/>
Takes a reference to the string as argument and a maximum expected string length as template parameter.<br/>
This is preferable if you know the maximum length at compile time as it skips having to calculate the number of bits required.

The, somewhat bloated, call signature can be seen below:
```cpp
bool serialize<bounded_string<std::basic_string<T, Traits, Alloc>, MaxSize>>(std::basic_string<T, Traits, Alloc>& value);
// For std::string this would look like:
bool serialize<bounded_string<std::string, MaxSize>>(std::string& value);
```
As well as a short example of its usage:
```cpp
std::string in_value = "Hello world!";
std::string out_value;
bool status_write = writer.serialize<bounded_string<std::string, 32U>>(in_value);
bool status_read = reader.serialize<bounded_string<std::string, 32U>>(out_value);
```

## Double-precision float - double
A trait that covers an entire double, with no quantization.<br/>
Takes a reference to the double.
Expand Down Expand Up @@ -258,117 +409,6 @@ bool status_write = writer.serialize<smallest_three<quaternion, 12>>(in_value);
bool status_read = reader.serialize<smallest_three<quaternion, 12>>(out_value);
```
# Serialization Examples
The examples below follow the same structure: First writing to a buffer and then reading from it. Each example is littered with comments about the procedure, as well as what outcome is expected.
Writing the first 5 bits of an int to the buffer, then reading it back:
```cpp
// Create a writer, referencing the buffer and its size
alignas(uint32_t) uint8_t buffer[4]; // Buffer must be a multiple of 4 bytes / 32 bits and 4-byte-aligned
bit_writer writer(buffer, 4);
// Write the value
uint32_t value = 27; // We can choose any value below 2^5. Otherwise we need more than 5 bits
writer.serialize_bits(value, 5);
// Flush the writer's remaining state into the buffer
uint32_t num_bits = writer.flush();
// Create a reader, referencing the buffer and bits written
bit_reader reader(buffer, num_bits);
// Read the value back
uint32_t out_value; // We don't have to initialize it yet
reader.serialize_bits(out_value, 5); // out_value should now have a value of 27
```

Writing a signed int to the buffer, within a range:
```cpp
// Create a writer, referencing the buffer and its size
byte_buffer<4> buffer; // byte_bufer is just a wrapper for a 4-byte aligned buffer
bit_writer writer(buffer);

// Write the value
int32_t value = -45; // We can choose any value within the range below
writer.serialize<int32_t>(value, -90, 40); // A lower and upper bound which the value will be quantized between

// Flush the writer's remaining state into the buffer
uint32_t num_bits = writer.flush();

// Create a reader, referencing the buffer and bits written
bit_reader reader(buffer, num_bits);

// Read the value back
int32_t out_value; // We don't have to initialize it yet
reader.serialize<int32_t>(out_value, -90, 40); // out_value should now have a value of -45
```
Writing a c-style string into the buffer:
```cpp
// Create a writer, referencing the buffer and its size
byte_buffer<32> buffer;
bit_writer writer(buffer);
// Write the value
const char* value = "Hello world!";
writer.serialize<const char*>(value, 32U); // The second argument is the maximum size we expect the string to be
// Flush the writer's remaining state into the buffer
uint32_t num_bits = writer.flush();
// Create a reader, referencing the buffer and bits written
bit_reader reader(buffer, num_bits);
// Read the value back
char out_value[32]; // Set the size to the max size
reader.serialize<const char*>(out_value, 32U); // out_value should now contain "Hello world!\0"
```

Writing a std::string into the buffer:
```cpp
// Create a writer, referencing the buffer and its size
byte_buffer<32> buffer;
bit_writer writer(buffer);

// Write the value
std::string value = "Hello world!";
writer.serialize<std::string>(value, 32U); // The second argument is the maximum size we expect the string to be

// Flush the writer's remaining state into the buffer
uint32_t num_bits = writer.flush();

// Create a reader, referencing the buffer and bits written
bit_reader reader(buffer, num_bits);

// Read the value back
std::string out_value; // The string will be resized if the output doesn't fit
reader.serialize<std::string>(out_value, 32U); // out_value should now contain "Hello world!"
```
Writing a float into the buffer with a bounded range and precision:
```cpp
// Create a writer, referencing the buffer and its size
byte_buffer<4> buffer;
bit_writer writer(buffer);
// Write the value
bounded_range range(1.0f, 4.0f, 1.0f / 128.0f); // Min, Max, Precision
float value = 1.2345678f;
writer.serialize<bounded_range>(range, value);
// Flush the writer's remaining state into the buffer
uint32_t num_bits = writer.flush();
// Create a reader, referencing the buffer and bits written
bit_reader reader(buffer, num_bits);
// Read the value back
float out_value;
reader.serialize<bounded_range>(range, out_value); // out_value should now be a value close to 1.2345678f
```

These examples can also be seen in [`src/test/examples_test.cpp`](https://github.com/KredeGC/BitStream/tree/master/src/test/examples_test.cpp).

# Extensibility
The library is made with extensibility in mind.
The `bit_writer` and `bit_reader` use a template trait specialization of the given type to deduce how to serialize and deserialize the object.
Expand Down
Loading

0 comments on commit f7f7e8b

Please sign in to comment.