A non-invasive, multi-format serialization library for C++17.
This library was primarily conceived as a foundation for assisting with building tooling for gamedev projects. The approach and naming conventions are borrowed from Daniel Randle's Pupping - a method for serializing data.
- Works with default constructible types.
pupene
relies on direct field access to read/write.
- Concise and simple:
pup()
- one serializer-agnostic read/write function per type.Pupper
- serializer, binary and optional JSON support included.- Serialization format can not be customized, this makes it non-ideal for dealing with third-party formats.
- Zero dependencies, unless built with JSON support.
- Not limited to performing only pure serialization.
- Example using imgui and pupene to auto-generate editor UI:s at runtime.
Add this conan remote:
conan remote add junkdog https://api.bintray.com/conan/junkdog/conan
In your project's conanfile.txt/py:
[requires]
pupene/0.2.0@junkdog/stable
- Full API documentation, including this README: https://junkdog.github.io/pupene/index.html
- Github repository: https://github.com/junkdog/pupene
- Issues: https://github.com/junkdog/pupene/issues
At the core, pupene
is built around serializers called puppers, and pup
functions - a combined read/write function per serializable type.
Include <pupene/binary.h>
and/or <pupene/json.h>
(requires -DPUPENE_BUILD_JSON=true
) to
access the appropriate puppers.
Refer to the API docs for more detailed documentation. The tests may be of some interest too.
Namespace pupene
is omitted from example code, in the interest of brevity.
Pupper
is on the receiving end of pup
; they only deal directly with values once
pup
has flattened the object into a sequence of integer, decimal and string types.
Included Puppers, and related convenience functions:
- JsonReader:
from_json()
- JsonWriter:
to_json()
- BinaryReader:
from_binary()
- BinaryWriter:
to_binary()
- DebugPupper:
to_debug()
A pup
function must exist for each serializable type.
An object is serializable when there's a matching pup
function; these describe
the structure of the type by enumerating its fields. All pup
functions must
be defined in the pupene::fns
namespace.
A limited number of fundamental types are built-in:
- Integer and decimal types
std::string
- STL containers, such as
std::vector
andstd::map
pup()
for a type FooBar
has the following signature:
template <typename P>
void pup(Pupper<P>& p, // Pupper-agnostic - works with all
FooBar& value, // must be reference
const Meta& meta); // name and Meta::Type
N.B. For function resolution to work, pup
functions must be declared in
a file which does not #include
any puppers, implicitly or explicitly.
You have some models:
struct Color {
float r, g, b, a;
};
struct FontReference {
std::string path;
Color inner;
Color outer;
};
To make the types eligible, each requires its own pup function in the namespace pupene:fns
.
#include <pupene/pup.h>
namespace pupene::fns { // required namespace
template<typename P>
void pup(Pupper<P>& p, // the serializer in use
Color& color, // always as non-const references
const Meta& meta) { // metadata for color
// idiomatic boilerplate
pup_object(p, color, meta, [&color](auto&& fpup) {
fpup(color.r, "r"); // enumerate each field
fpup(color.g, "g");
fpup(color.b, "b");
fpup(color.a, "a");
});
}
template<typename P>
void pup(Pupper<P>& p,
FontReference& ref,
const Meta& meta) {
pup_object(p, ref, meta, [&ref](auto&& fpup) {
fpup(ref.path, "path");
fpup(ref.inner, "inner"); // dispatches to pup for Color
fpup(ref.outer, "outer");
});
}
}
Once the necessary pup:s are implemented, serialization between different format becomes possible e.g.:
// load binary from input stream
std::istream& in = ...;
Color color = from_binary<Color>(in);
// save to json (assumes built with PUPENE_BUILD_JSON=true)
std::string raw_json = to_json(color);
Color
and FontReference
can now also be combined with some STL containers:
std::unordered_map<Color, FontReference> colored_fonts = {
{colorA, small}, {colorB, medium}, {colorC, large}
};
// complex keys - such as Color - are ok
std::string raw_json = to_json(colored_fonts);
// (this doesn't require any user-supplied pup:s)
std::vector<int> numbers = {0, 1, 6, -2, 4, 5, 7};
to_binary(numbers, std::cout);
Unless already added, The conan-community
remote has to be added for
the Boost
test dependency to work:
conan remote add conan-community https://api.bintray.com/conan/conan-community/conan
First-time setup:
conan install . --build missing --install-folder _builds
Build project:
conan build . --build-folder _builds
Once conan has built the project for the first time, CMake should work as expected.
Build project:
cmake --build _builds --target install -- -j 8
Tests are run with:
cmake --build _build --target test
... or run the tests with valgrind/memcheck:
cmake --build _build --target valgrind
To play nice with CLion, manually specify the compiler using CMake Options
under Settings > Build, Execution, Deployment > CMake
, e.g.:
-DCMAKE_CXX_COMPILER=/usr/bin/clang++-5.0
- Types currently require a default constructor to be eligble for
pup
. - Benchmarking and profiling remain.