Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions inc/cvc/physics/physics_codec.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
Copyright 2026 The University of Texas at Austin

This file is part of libcvc.

libcvc is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License version 2.1 as published by the Free Software Foundation.

libcvc is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
*/

#ifndef __CVC_PHYSICS_PHYSICS_CODEC_H__
#define __CVC_PHYSICS_PHYSICS_CODEC_H__

#include <cvc/core/namespace.h>

namespace cvc {

class app;
class state_codec_registry;

namespace physics {

// ----------------
// cvc::physics — pose / body_state replication codec
// ----------------
// A holon replicates a participant's avatar as a cvc::physics::body_state (or a
// bare cvc::physics::pose) carried in a state node's data(). For that payload to
// cross the cluster, the distributed-state pipeline needs the type to have both a
// registered data-type name and a (de)serialization codec in the shard's codec
// registry -- the same contract cvc::ariadne::stream::format_desc uses.
//
// The wire is little-endian, fixed-width, FIELD-BY-FIELD (never a struct memcpy,
// so trailing-bool padding and ABI differences never leak onto the wire): each
// double is its IEEE-754 binary64 bit pattern as 8 LE bytes (matching the
// built-in "cvc.f64.v1" double codec), and `active` is one byte. So the encoding
// is byte-identical across the (little-endian) fleet, which content-addressed
// blob offload and live/snapshot digest agreement both require.
//
// These helpers bridge cvc::physics types to the cvc::state codec registry; like
// the stream codec they are registered INTO a shard's (mutable) registry rather
// than baked into cvc::state, which sits below physics.

// The canonical, cross-ABI-stable data-type names, used BOTH as the app's
// registerDataType name and the codec-registry key, so dataTypeName<T>() resolves
// to this exact string rather than the compiler's mangled typeid name (an
// unregistered type would fall back to the mangled name, which differs across
// toolchains and would silently fail to replicate).
//
// Returned by an inline accessor (a plain string literal) rather than held in a
// namespace-scope constant: no static global to initialize or to export across a
// shared-library boundary (a `const char* const` data symbol is not exported by
// CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS, which covers functions only).
inline const char *body_state_type_name() { return "cvc::physics::body_state"; }
inline const char *pose_type_name() { return "cvc::physics::pose"; }

// Register body_state + pose as named data types on `ctx`, so a node holding one
// in data() reports the stable name from dataTypeName(). Idempotent.
void register_physics_data_types(app &ctx);

// Register the body_state + pose (de)serialization codecs into a shard's codec
// registry so a node's physics data() replicates cross-node. Call on every
// participating node's shard registry (alongside the built-in / volume-geometry
// codecs the shard installs itself). The registry key is body_state_type_name() /
// pose_type_name() -- identical to the registerDataType name above, so the key the
// emit path looks up (dataTypeName()) always matches a registered codec.
void register_physics_codecs(state_codec_registry &registry);

} // namespace physics
} // namespace cvc

#endif // __CVC_PHYSICS_PHYSICS_CODEC_H__
2 changes: 2 additions & 0 deletions src/cvc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ set(INCLUDE_FILES
../../inc/cvc/physics/world_geometry.h
../../inc/cvc/physics/plant_host.h
../../inc/cvc/physics/pose_tracker.h
../../inc/cvc/physics/physics_codec.h
../../inc/cvc/physics/nav_plant.h
../../inc/cvc/physics/nav_eval.h
../../inc/cvc/physics/plant_log.h
Expand Down Expand Up @@ -268,6 +269,7 @@ set(SOURCE_FILES
physics/world_geometry.cpp
physics/plant_host.cpp
physics/pose_tracker.cpp
physics/physics_codec.cpp
physics/binding_meta.cpp
physics/nav_plant.cpp
physics/nav_eval.cpp
Expand Down
164 changes: 164 additions & 0 deletions src/cvc/physics/physics_codec.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
Copyright 2026 The University of Texas at Austin

This file is part of libcvc.

libcvc is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License version 2.1 as published by the Free Software Foundation.

libcvc is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
*/

#include <boost/any.hpp>
#include <cstdint>
#include <cstring>
#include <cvc/core/app.h>
#include <cvc/physics/body.h>
#include <cvc/physics/physics_codec.h>
#include <cvc/physics/pose_tracker.h>
#include <cvc/physics/types.h>
#include <cvc/state/state_codec_registry.h>
#include <stdexcept>
#include <vector>

namespace cvc {
namespace physics {

namespace {

// Little-endian, fixed-width framing (mirrors cvc::ariadne::stream::stream_codec)
// so the content is byte-identical across the little-endian fleet.
void put_u8(std::vector<unsigned char> &o, std::uint8_t v) { o.push_back(v); }
void put_u64(std::vector<unsigned char> &o, std::uint64_t v) {
for (int i = 0; i < 8; ++i)
o.push_back(static_cast<unsigned char>((v >> (8 * i)) & 0xff));
}
// A double as its IEEE-754 binary64 bit pattern, 8 LE bytes. memcpy (not a
// reinterpret or std::bit_cast) keeps it well-defined and C++17-clean; encode and
// decode are exact inverses on any single host and byte-identical across LE hosts,
// matching the built-in "cvc.f64.v1" double codec.
void put_f64(std::vector<unsigned char> &o, double d) {
std::uint64_t bits = 0;
std::memcpy(&bits, &d, sizeof(bits));
put_u64(o, bits);
}

struct reader {
const unsigned char *p;
const unsigned char *e;
bool ok = true;
explicit reader(const std::vector<unsigned char> &b) : p(b.data()), e(b.data() + b.size()) {}
std::uint8_t u8() {
if (p + 1 > e) {
ok = false;
return 0;
}
return *p++;
}
std::uint64_t u64() {
if (p + 8 > e) {
ok = false;
return 0;
}
std::uint64_t v = 0;
for (int i = 0; i < 8; ++i)
v |= static_cast<std::uint64_t>(p[i]) << (8 * i);
p += 8;
return v;
}
double f64() {
std::uint64_t bits = u64();
double d = 0.0;
std::memcpy(&d, &bits, sizeof(d));
return d;
}
};

void put_vec3(std::vector<unsigned char> &o, const vec3 &v) {
put_f64(o, v.x);
put_f64(o, v.y);
put_f64(o, v.z);
}
void put_quat(std::vector<unsigned char> &o, const quat &q) {
put_f64(o, q.w);
put_f64(o, q.x);
put_f64(o, q.y);
put_f64(o, q.z);
}
vec3 read_vec3(reader &r) {
vec3 v;
v.x = r.f64();
v.y = r.f64();
v.z = r.f64();
return v;
}
quat read_quat(reader &r) {
quat q;
q.w = r.f64();
q.x = r.f64();
q.y = r.f64();
q.z = r.f64();
return q;
}

std::vector<unsigned char> encode_body_state(const boost::any &v) {
const body_state s = boost::any_cast<body_state>(v);
std::vector<unsigned char> o;
put_vec3(o, s.position);
put_quat(o, s.rotation);
put_vec3(o, s.linear_velocity);
put_vec3(o, s.angular_velocity);
put_u8(o, s.active ? 1u : 0u);
return o;
}

boost::any decode_body_state(const std::vector<unsigned char> &b) {
reader r(b);
body_state s;
s.position = read_vec3(r);
s.rotation = read_quat(r);
s.linear_velocity = read_vec3(r);
s.angular_velocity = read_vec3(r);
s.active = r.u8() != 0;
if (!r.ok)
throw std::runtime_error("cvc::physics::body_state decode: truncated payload");
return boost::any(s);
}

std::vector<unsigned char> encode_pose(const boost::any &v) {
const pose p = boost::any_cast<pose>(v);
std::vector<unsigned char> o;
put_vec3(o, p.position);
put_quat(o, p.rotation);
return o;
}

boost::any decode_pose(const std::vector<unsigned char> &b) {
reader r(b);
pose p;
p.position = read_vec3(r);
p.rotation = read_quat(r);
if (!r.ok)
throw std::runtime_error("cvc::physics::pose decode: truncated payload");
return boost::any(p);
}

} // namespace

void register_physics_data_types(app &ctx) {
ctx.registerDataType<body_state>(body_state_type_name());
ctx.registerDataType<pose>(pose_type_name());
}

void register_physics_codecs(state_codec_registry &registry) {
registry.register_codec(body_state_type_name(), encode_body_state, decode_body_state,
"cvc.physics.body_state.v1");
registry.register_codec(pose_type_name(), encode_pose, decode_pose, "cvc.physics.pose.v1");
}

} // namespace physics
} // namespace cvc
11 changes: 11 additions & 0 deletions src/cvc/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ add_executable(world_clock_test world_clock_test.cpp)
add_executable(clock_driver_test clock_driver_test.cpp)
add_executable(world_units_test world_units_test.cpp)
add_executable(physics_world_test physics_world_test.cpp)
add_executable(physics_codec_test physics_codec_test.cpp)
add_executable(physics_field_test physics_field_test.cpp)
add_executable(physics_vehicle_test physics_vehicle_test.cpp)
add_executable(physics_energy_test physics_energy_test.cpp)
Expand Down Expand Up @@ -303,6 +304,7 @@ set(TEST_TARGETS
clock_driver_test
world_units_test
physics_world_test
physics_codec_test
physics_field_test
physics_vehicle_test
physics_energy_test
Expand Down Expand Up @@ -1640,6 +1642,13 @@ target_link_libraries(physics_world_test
GTest::gtest_main
)

target_link_libraries(physics_codec_test
PRIVATE
cvc
GTest::gtest
GTest::gtest_main
)

target_link_libraries(physics_vehicle_test
PRIVATE
cvc
Expand Down Expand Up @@ -2038,6 +2047,7 @@ target_compile_features(world_clock_test PRIVATE cxx_std_17)
target_compile_features(clock_driver_test PRIVATE cxx_std_17)
target_compile_features(world_units_test PRIVATE cxx_std_17)
target_compile_features(physics_world_test PRIVATE cxx_std_17)
target_compile_features(physics_codec_test PRIVATE cxx_std_17)
target_compile_features(physics_field_test PRIVATE cxx_std_17)
target_compile_features(physics_vehicle_test PRIVATE cxx_std_17)
target_compile_features(physics_energy_test PRIVATE cxx_std_17)
Expand Down Expand Up @@ -2493,6 +2503,7 @@ cvc_discover_tests(world_clock_test)
cvc_discover_tests(clock_driver_test)
cvc_discover_tests(world_units_test)
cvc_discover_tests(physics_world_test)
cvc_discover_tests(physics_codec_test)
cvc_discover_tests(physics_field_test)
cvc_discover_tests(physics_vehicle_test)
cvc_discover_tests(physics_energy_test)
Expand Down
Loading
Loading