-
Notifications
You must be signed in to change notification settings - Fork 4
Extensions
Optional add-ons that bridge znet to a third-party library, or add a layer the
core deliberately does not carry. Each lives in extensions/, links znet the
way your own code would, and adds nothing to the core target: a build with all
of them off is byte-identical to one from before they existed.
They are separate because they carry dependencies the library refuses to take on. znet needs OpenSSL and zstd; it should not need a maths library because some users serialize vectors.
Nothing here is required, and nothing depends on anything except what its row says.
| Dependency | ||
|---|---|---|
| quantize | none | The compression arithmetic on plain scalars: fixed point, half floats, smallest-three quaternions, octahedral normals. Everything below that compresses a value delegates here, so they all agree bit for bit |
| bitpack | quantize |
Sub-byte fields in a Buffer. A bool costs 1 bit instead of 8; a value bounded by 200 costs 8 instead of 16. Ranged ints and floats work out their own width |
| delta | bitpack |
Send only what changed since a baseline the peer acknowledged. Snapshot history, wrap-safe sequence numbers, one flag bit per unchanged field |
A six-field player state is 50 bits sent in full and 25 on average as a delta, measured over 3000 ticks with 20% packet loss.
| Dependency | ||
|---|---|---|
| reflect | none, C++17 | Serialize a struct from its fields. A plain aggregate needs nothing declared at all; ZNET_REFLECT covers the rest |
| json | nlohmann-json | Length-prefixed json as MessagePack or text, with the depth and size bounds that make parsing network-supplied json safe, plus a ready-made PacketSerializer
|
| flatbuffers | FlatBuffers, C++17 | FlatBuffers payloads with no way to reach a root without the Verifier having run |
| entt | EnTT, C++17 |
Buffer archives for entt::snapshot and continuous_loader, so an ECS registry replicates with entity remapping already handled |
| Dependency | ||
|---|---|---|
| glm | glm, quantize
|
Read/write for vec, mat and quat, plus compressed forms: 4-byte quaternions, 4-byte normals, ranged fixed-point positions |
| box2d | Box2D 3.x | Planar body state in 10 bytes instead of 28. Orientation is one wrapped angle, not a quaternion |
| bullet | Bullet 3.x | Rigid body state in 15 bytes instead of 40 |
| jolt | Jolt 5.x, C++17 | Rigid body state in 15 bytes instead of 40 |
| Dependency | ||
|---|---|---|
| spdlog | spdlog | Routes znet's logging into spdlog with the severity intact, rather than as a pre-formatted line |
Every extension is a header-only INTERFACE target. Link it and include its
umbrella header.
target_link_libraries(my_game PRIVATE znet-reflect znet-glm)#include "znet/ext/reflect/reflect_all.h"
#include "znet/ext/glm/glm.h"Each target also has a znet:: alias, so znet::reflect works too. Everything
lives in namespace znet::ext.
A taste of what that buys, with nothing declared about the struct:
struct Move {
uint32_t entity;
float x, y, z;
};
codec->Add(kMove, znet::ext::MakeAutoSerializer<Move>(kMove));
auto packet = std::make_shared<znet::ext::AutoPacket<Move>>(kMove, move);
session->SendPacket(packet);On by default. An extension whose dependency is missing skips itself with a
message(STATUS ...) rather than failing the build, so leaving them all enabled
is safe even on a machine that has none of the libraries.
| Option | Default | |
|---|---|---|
ZNET_BUILD_EXTENSIONS |
ON |
OFF skips the whole extensions/ tree |
ZNET_EXT_ALLOW_FETCH |
ON |
OFF never downloads a missing dependency; the extension skips instead. Set this in an offline or locked-down build |
ZNET_EXT_QUANTIZE |
ON |
|
ZNET_EXT_BITPACK |
ON |
|
ZNET_EXT_DELTA |
ON |
|
ZNET_EXT_REFLECT |
ON |
|
ZNET_EXT_GLM |
ON |
|
ZNET_EXT_JSON |
ON |
|
ZNET_EXT_ENTT |
ON |
|
ZNET_EXT_FLATBUFFERS |
ON |
|
ZNET_EXT_BOX2D |
ON |
|
ZNET_EXT_BULLET |
ON |
|
ZNET_EXT_JOLT |
ON |
|
ZNET_EXT_SPDLOG |
ON |
cmake -B build -DZNET_EXT_ALLOW_FETCH=OFF -DZNET_EXT_BULLET=OFF
Configuring prints one line per extension, which is the quickest way to see what actually got built:
-- znet-quantize: enabled
-- znet-glm: enabled against glm::glm
-- znet-entt: EnTT requires C++17 and the tree is at C++14; skipping extension
-- znet-box2d: Box2D not installed, fetching it
An installed copy is always preferred: your renderer is already built against a
particular glm, and a second one on the include path helps nobody. Only when
find_package comes up empty does the build fetch, and every fetch is pinned to
a version and a SHA256 and marked SYSTEM, so the dependency's own warnings
stay out of your build.
Bullet is worth calling out: its release archive is 136 MB, and its own CMake predates several things modern CMake requires. Both are handled, but if you already have Bullet installed you will want it found rather than fetched.
Everything that ends up in one binary has to agree on the C runtime library, and
MSVC refuses a mix with LNK2038 followed by a page of duplicate symbols out of
msvcprt.lib. znet takes CMake's default, the DLL runtime (/MD).
Box2D, Bullet and Jolt each choose the static one (/MT) for themselves when
built standalone. A fetched copy is put back on this build's runtime after it is
configured, so nothing is asked of you.
An installed copy is a different matter: it was compiled before this build existed and nothing at this end can change what it was compiled against. If a vcpkg or hand-built Box2D, Bullet or Jolt used the static runtime, that extension's tests will not link. Either rebuild the dependency against the DLL runtime, or have znet fetch and build its own copy instead:
cmake -B build -DCMAKE_DISABLE_FIND_PACKAGE_Jolt=ON
None of this applies to any other compiler.
reflect, entt, flatbuffers and jolt need C++17. znet itself still builds
at C++14, and on a -DZNET_CXX_STANDARD=14 tree those four step aside and say
so rather than dragging the whole build up. The other eight work at 14.
For reflect the floor is not incidental: structured bindings are what make
deducing a struct's fields possible at all, so on C++14 it could only ever offer
the macro.
Each extension has its own suite, wired only when the parent build already has
googletest, and run by the usual ctest:
ctest -R ext- # every extension
ctest -R ext-reflect # one of them
An extension never fetches a test framework of its own, so vendoring a single extension directory into another project gets you the headers and nothing else.
The house rules, if you want to add to this list:
- header-only
INTERFACEtarget namedznet-<thing>, with aznet::<thing>alias - public headers under
include/znet/ext/<thing>/, umbrella header<thing>.hbeside them - everything in namespace
znet::ext - C++14 unless a dependency forces higher, and then gate on
ZNET_CXX_STANDARDand skip rather than fail - prefer an installed dependency;
return()early with amessage(STATUS ...)when it is absent - tests under
tests/, guarded onif(TARGET gtest_main), compiled with the core library's warning set - a README documenting the wire format, since that is the part the other end has to match
Then add add_subdirectory(<thing>) to extensions/CMakeLists.txt.