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
21 changes: 20 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,9 @@ The direct C++ parser bridge (docs only, no code change):
the host reads objects through `pj.parser_functional.v2` only.
- 1.0.0 itself changes two of those layouts: `sdk::Log` (`category`) and `sdk::DepthImage`
(`frame_id`). A parser built against 0.x that returns either type must be rebuilt for a 1.0 host.
No parser in pj-official-plugins returns them.
In pj-official-plugins no parser returns `DepthImage`, and `parser_ros` returns `Log` only from
1.4.0, which is built against the 1.0 layout. Both members are appended at the tail, so a 0.x
host reading an object from a 1.0 parser sees the members it knows at their old offsets.

Builtin decoders:

Expand Down Expand Up @@ -179,6 +181,23 @@ Builtin decoders:
the field is rejected.
- A NaN colour channel decodes as 0; it used to reach an undefined float-to-integer conversion.

Three changes that could not be made after the freeze:

- **Breaking (source, scripts):** the timestamp member of four builtin structs is renamed from
`timestamp` to `timestamp_ns`, the name the other sixteen already use: `sdk::ImageAnnotations`,
`sdk::FrameTransform`, `sdk::SceneEntity` and `sdk::SceneEntityDeletion`. Their field-table name
changes with it, so a script reads `timestamp_ns` on these types too. The wire format is
unchanged (the proto field is still `timestamp`); only code and scripts that name the member must
be updated.
- Dialog tree events no longer have to be the only top-level key of their event. `DialogPluginTyped`
dispatched a tree event only when its key was alone, and that check is compiled into each plugin,
so no later host could have added a key to the envelope. A valid tree key now claims the event and
other keys are ignored, as beside a file-picker result. Two tree keys, or a malformed tree
payload, still fail closed.
- `PJ_widget_value_t` gains `uint32_t flags`, naming the 4 bytes that were alignment padding after
`kind`. Size, alignment and every offset are unchanged. No bit is defined: a host built against
1.0 or later writes 0, and a host built earlier leaves the bytes unset.

## [0.40.0]

Host contract: extended: pj.mcp_endpoint.v1, PJ_mcp_endpoint_host_vtable_t::acquire_endpoint,
Expand Down
8 changes: 4 additions & 4 deletions docs/builtin_type.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ stream.

| Field | Type | Notes |
|-------|------|-------|
| `timestamp` | `Timestamp` | Timestamp associated with the annotation set. |
| `timestamp_ns` | `Timestamp` | Timestamp associated with the annotation set. |
| `image_topic` | `std::string` | Topic of the image these annotations overlay. |
| `points` | `std::vector<PointsAnnotation>` | Points, line lists, line strips, and line loops. |
| `circles` | `std::vector<CircleAnnotation>` | Filled or stroked circles in image-pixel space. |
Expand Down Expand Up @@ -357,7 +357,7 @@ Each `FrameTransform` contains:

| Field | Type | Notes |
|-------|------|-------|
| `timestamp` | `Timestamp` | Timestamp associated with the transform. |
| `timestamp_ns` | `Timestamp` | Timestamp associated with the transform. |
| `parent_frame_id` | `std::string` | Name of the parent reference frame. |
| `child_frame_id` | `std::string` | Name of the child reference frame. |
| `translation` | `Vector3` | Child-frame origin in parent-frame coordinates. |
Expand Down Expand Up @@ -503,10 +503,10 @@ axes glyphs, or model (mesh asset) references.

| Field on `SceneEntity` | Type | Notes |
|------------------------|------|-------|
| `timestamp` | `Timestamp` | Stamp used together with `lifetime_ns` to control expiry. |
| `timestamp_ns` | `Timestamp` | Stamp used together with `lifetime_ns` to control expiry. |
| `frame_id` | `std::string` | Frame the entity's primitives are expressed in. |
| `id` | `std::string` | Republishing with the same `(topic, id)` replaces the previous entity. |
| `lifetime_ns` | `int64_t` | `0` means persist until replaced; otherwise expire `lifetime_ns` after `timestamp`. |
| `lifetime_ns` | `int64_t` | `0` means persist until replaced; otherwise expire `lifetime_ns` after `timestamp_ns`. |
| `frame_locked` | `bool` | When true, track `frame_id` as it moves; when false, stamp into the fixed frame at publish time. |
| `arrows` / `cubes` / `spheres` / `cylinders` / `lines` / `triangles` / `texts` / `axes` / `models` | `std::vector<…Primitive>` | Heterogeneous primitive lists. `models` references a mesh asset by `url` or inline `data`. |

Expand Down
7 changes: 4 additions & 3 deletions docs/dialog-sdk-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -423,9 +423,10 @@ if that channel is malformed, dispatch returns `false` without falling through.
an object with a non-empty string `reason` and string `kind`/`topic`/`dataset` when
present; otherwise dispatch fails closed. Unknown keys inside it are tolerated.
3. In the absence of picker and scene-view-error keys, any tree event key claims the event.
Exactly one of the four tree keys must be the sole top-level key and its
payload must validate atomically. Multiple tree keys or any tree/other-key
mixture fails closed.
Exactly one of the four tree keys must be present and its payload must
validate atomically; two tree keys, or a malformed payload, fail closed.
Other top-level keys are tolerated and do not receive callbacks, stacked and
legacy keys included.
4. In the absence of picker and tree keys, either stacked key claims the event.
Both `stacked_index` and `stacked_page` are required; the index must decode
losslessly in `0..INT_MAX` and the page name must be non-empty. Invalid or
Expand Down
6 changes: 3 additions & 3 deletions docs/image_annotations_format.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ the renderable annotation fields and ignores the rest:
| `ImageAnnotations.circles` | Mapped to `ImageAnnotations::circles`. |
| `ImageAnnotations.points` | Mapped to `ImageAnnotations::points`. |
| `ImageAnnotations.texts` | Mapped to `ImageAnnotations::texts`. |
| Top-level `timestamp` | Serialized when non-zero, decoded into `ImageAnnotations::timestamp`; a timestamp of 0 is indistinguishable from an absent one. |
| Top-level `timestamp` | Serialized when non-zero, decoded into `ImageAnnotations::timestamp_ns`; a timestamp of 0 is indistinguishable from an absent one. |
| `image_topic` (field 6, string) | Serialized when non-empty, decoded into `ImageAnnotations::image_topic`. |
| Top-level `metadata` | Not serialized or decoded today. |
| Per-annotation `timestamp` | Not serialized or decoded today. |
Expand Down Expand Up @@ -104,8 +104,8 @@ values to `kPoints`, so incomplete or forward-compatible payloads still decode
to a renderable annotation.

An annotation value serializes to an empty byte buffer only when it has no circles,
points, or texts, a zero `timestamp`, and an empty `image_topic`; one carrying just a
non-zero `timestamp` and/or an `image_topic` produces a non-empty payload. Decoding an
points, or texts, a zero `timestamp_ns`, and an empty `image_topic`; one carrying just a
non-zero `timestamp_ns` and/or an `image_topic` produces a non-empty payload. Decoding an
empty buffer is treated as invalid input by the current reader.

The reader accepts protobuf wire types `VARINT`, `I64`, `LEN`, and `I32`. It
Expand Down
2 changes: 1 addition & 1 deletion pj_base/include/pj_base/builtin/frame_transforms.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ struct Pose {

/// Transform from `parent_frame_id` to `child_frame_id`.
struct FrameTransform {
Timestamp timestamp = 0;
Timestamp timestamp_ns = 0;
std::string parent_frame_id;
std::string child_frame_id;
Vector3 translation;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ struct FieldTable<Pose> {
template <>
struct FieldTable<FrameTransform> {
static constexpr std::array fields{
field<&FrameTransform::timestamp>("timestamp"),
field<&FrameTransform::timestamp_ns>("timestamp_ns"),
field<&FrameTransform::parent_frame_id>("parent_frame_id"),
field<&FrameTransform::child_frame_id>("child_frame_id"),
field<&FrameTransform::translation>("translation"),
Expand Down
2 changes: 1 addition & 1 deletion pj_base/include/pj_base/builtin/image_annotations.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ struct TextAnnotation {
/// All annotations for one image at one timestamp. References its base image
/// explicitly via `image_topic` so the renderer knows which frame to overlay.
struct ImageAnnotations {
Timestamp timestamp = 0;
Timestamp timestamp_ns = 0;
std::string image_topic;
std::vector<PointsAnnotation> points;
std::vector<CircleAnnotation> circles;
Expand Down
6 changes: 4 additions & 2 deletions pj_base/include/pj_base/builtin/image_annotations_fields.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,10 @@ struct FieldTable<TextAnnotation> {
template <>
struct FieldTable<ImageAnnotations> {
static constexpr std::array fields{
field<&ImageAnnotations::timestamp>("timestamp"), field<&ImageAnnotations::image_topic>("image_topic"),
field<&ImageAnnotations::points>("points"), field<&ImageAnnotations::circles>("circles"),
field<&ImageAnnotations::timestamp_ns>("timestamp_ns"),
field<&ImageAnnotations::image_topic>("image_topic"),
field<&ImageAnnotations::points>("points"),
field<&ImageAnnotations::circles>("circles"),
field<&ImageAnnotations::texts>("texts"),
};
static constexpr FieldTableView view{"ImageAnnotations", Span<const FieldDescriptor>(fields)};
Expand Down
8 changes: 4 additions & 4 deletions pj_base/include/pj_base/builtin/scene_entities.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,12 @@ struct KeyValuePair {
/// same id on the same topic replaces the previous entity.
///
/// Lifetime: zero means persist until replaced or deleted; otherwise the
/// entity is removed `lifetime_ns` after `timestamp`.
/// entity is removed `lifetime_ns` after `timestamp_ns`.
///
/// frame_locked: when true, the entity tracks `frame_id` as it moves; when
/// false, it is stamped into the fixed frame at publish time.
struct SceneEntity {
Timestamp timestamp = 0;
Timestamp timestamp_ns = 0;
std::string frame_id;
std::string id;
int64_t lifetime_ns = 0; ///< 0 means persist until replaced.
Expand Down Expand Up @@ -190,8 +190,8 @@ struct SceneEntityDeletion {
kAll = 1, ///< Delete every entity on this topic.
};
Type type = Type::kMatchingId;
Timestamp timestamp = 0; ///< Bounds the deletion to entities at or before this time.
std::string id; ///< Entity id to match when `type == kMatchingId`.
Timestamp timestamp_ns = 0; ///< Bounds the deletion to entities at or before this time.
std::string id; ///< Entity id to match when `type == kMatchingId`.
bool operator==(const SceneEntityDeletion&) const = default;
};

Expand Down
4 changes: 2 additions & 2 deletions pj_base/include/pj_base/builtin/scene_entities_fields.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ struct FieldTable<ModelPrimitive> {
template <>
struct FieldTable<SceneEntity> {
static constexpr std::array fields{
field<&SceneEntity::timestamp>("timestamp"),
field<&SceneEntity::timestamp_ns>("timestamp_ns"),
field<&SceneEntity::frame_id>("frame_id"),
field<&SceneEntity::id>("id"),
field<&SceneEntity::lifetime_ns>("lifetime_ns"),
Expand All @@ -192,7 +192,7 @@ template <>
struct FieldTable<SceneEntityDeletion> {
static constexpr std::array fields{
field<&SceneEntityDeletion::type>("type"),
field<&SceneEntityDeletion::timestamp>("timestamp"),
field<&SceneEntityDeletion::timestamp_ns>("timestamp_ns"),
field<&SceneEntityDeletion::id>("id"),
};
static constexpr FieldTableView view{"SceneEntityDeletion", Span<const FieldDescriptor>(fields)};
Expand Down
4 changes: 2 additions & 2 deletions pj_base/src/builtin/frame_transforms_codec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ using sdk::FrameTransform;
using sdk::FrameTransforms;

void writeFrameTransform(Writer& writer, const FrameTransform& transform) {
writer.message(1, [&](Writer& nested) { builtin_wire::writeTimestamp(nested, transform.timestamp); });
writer.message(1, [&](Writer& nested) { builtin_wire::writeTimestamp(nested, transform.timestamp_ns); });
writer.string(2, transform.parent_frame_id);
writer.string(3, transform.child_frame_id);
writer.message(4, [&](Writer& nested) { builtin_wire::writeVector3(nested, transform.translation); });
Expand All @@ -37,7 +37,7 @@ bool decodeFrameTransform(Reader& reader, FrameTransform& out) {
}
switch (tag.field) {
case 1:
return builtin_wire::readTimestampMessage(r, out.timestamp);
return builtin_wire::readTimestampMessage(r, out.timestamp_ns);
case 2:
return r.readString(out.parent_frame_id);
case 3:
Expand Down
6 changes: 3 additions & 3 deletions pj_base/src/builtin/image_annotations_codec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -343,8 +343,8 @@ std::vector<uint8_t> serializeImageAnnotations(const ImageAnnotations& annotatio
writer.message(3, [&](Writer& nested) { writeTextAnnotation(nested, text); });
}

if (annotations.timestamp != 0) {
writer.message(5, [&](Writer& nested) { builtin_wire::writeTimestamp(nested, annotations.timestamp); });
if (annotations.timestamp_ns != 0) {
writer.message(5, [&](Writer& nested) { builtin_wire::writeTimestamp(nested, annotations.timestamp_ns); });
}
if (!annotations.image_topic.empty()) {
writer.string(6, annotations.image_topic);
Expand Down Expand Up @@ -418,7 +418,7 @@ Expected<sdk::ImageAnnotations> deserializeImageAnnotations(const uint8_t* data,
break;
}
case 5: {
if (!builtin_wire::decodeTimestamp(nested, annotations.timestamp)) {
if (!builtin_wire::decodeTimestamp(nested, annotations.timestamp_ns)) {
return unexpected(std::string("ImageAnnotations wire: Timestamp decode failed"));
}
break;
Expand Down
8 changes: 4 additions & 4 deletions pj_base/src/builtin/scene_entities_codec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ bool decodeModelPrimitive(Reader& reader, ModelPrimitive& out) {

void writeSceneEntityDeletion(Writer& writer, const SceneEntityDeletion& d) {
// Duration/Timestamp share the seconds+nanos wire shape.
writer.message(1, [&](Writer& nested) { builtin_wire::writeTimestamp(nested, d.timestamp); });
writer.message(1, [&](Writer& nested) { builtin_wire::writeTimestamp(nested, d.timestamp_ns); });
writer.varint(2, static_cast<uint64_t>(d.type));
writer.string(3, d.id);
}
Expand All @@ -451,7 +451,7 @@ bool decodeSceneEntityDeletion(Reader& reader, SceneEntityDeletion& out) {
return parseFields(reader, [&](Tag tag, Reader& r) {
switch (tag.field) {
case 1:
return tag.type == WireType::kLengthDelimited && builtin_wire::readTimestampMessage(r, out.timestamp);
return tag.type == WireType::kLengthDelimited && builtin_wire::readTimestampMessage(r, out.timestamp_ns);
case 2: {
if (tag.type != WireType::kVarint) {
return false;
Expand Down Expand Up @@ -513,7 +513,7 @@ bool readPrimitiveIntoVector(Reader& reader, std::vector<Primitive>& out, Decode
// ---------- SceneEntity ----------

void writeSceneEntity(Writer& writer, const SceneEntity& e) {
writer.message(1, [&](Writer& nested) { builtin_wire::writeTimestamp(nested, e.timestamp); });
writer.message(1, [&](Writer& nested) { builtin_wire::writeTimestamp(nested, e.timestamp_ns); });
writer.string(2, e.frame_id);
writer.string(3, e.id);
// Duration shares the seconds+nanos wire shape with Timestamp.
Expand Down Expand Up @@ -556,7 +556,7 @@ bool decodeSceneEntity(Reader& reader, SceneEntity& out) {
return parseFields(reader, [&](Tag tag, Reader& r) {
switch (tag.field) {
case 1:
return tag.type == WireType::kLengthDelimited && builtin_wire::readTimestampMessage(r, out.timestamp);
return tag.type == WireType::kLengthDelimited && builtin_wire::readTimestampMessage(r, out.timestamp_ns);
case 2:
return tag.type == WireType::kLengthDelimited && r.readString(out.frame_id);
case 3:
Expand Down
18 changes: 9 additions & 9 deletions pj_base/tests/field_table_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,15 +191,15 @@ FrameTransforms makeSampleFrameTransforms() {
FrameTransforms ft;
ft.transforms.push_back(
FrameTransform{
.timestamp = 1'234'567'890'123'456,
.timestamp_ns = 1'234'567'890'123'456,
.parent_frame_id = "map",
.child_frame_id = "base_link",
.translation = {.x = 1.0, .y = 2.0, .z = 3.0},
.rotation = {.x = 0.1, .y = 0.2, .z = 0.3, .w = 0.9},
});
ft.transforms.push_back(
FrameTransform{
.timestamp = -42,
.timestamp_ns = -42,
.parent_frame_id = "odom",
.child_frame_id = "map",
.translation = {.x = -1.5, .y = 2.5, .z = -3.5},
Expand All @@ -210,7 +210,7 @@ FrameTransforms makeSampleFrameTransforms() {

ImageAnnotations makeSampleImageAnnotations() {
ImageAnnotations ia;
ia.timestamp = 9'007'199'254'740'993; // > 2^53: would lose precision as a double.
ia.timestamp_ns = 9'007'199'254'740'993; // > 2^53: would lose precision as a double.
ia.image_topic = "/camera/image";

PointsAnnotation pa;
Expand Down Expand Up @@ -244,7 +244,7 @@ ImageAnnotations makeSampleImageAnnotations() {
// metadata; the batch also carries one deletion.
SceneEntities makeSampleSceneEntities() {
SceneEntity e;
e.timestamp = 1'000'000'000'000'123; // > 2^53.
e.timestamp_ns = 1'000'000'000'000'123; // > 2^53.
e.frame_id = "map";
e.id = "entity-1";
e.lifetime_ns = 5'000'000'000;
Expand Down Expand Up @@ -348,7 +348,7 @@ SceneEntities makeSampleSceneEntities() {
entities.deletions.push_back(
SceneEntityDeletion{
.type = SceneEntityDeletion::Type::kAll, // non-default (kMatchingId == 0)
.timestamp = 999,
.timestamp_ns = 999,
.id = "",
});
return entities;
Expand Down Expand Up @@ -824,7 +824,7 @@ TEST(FieldTableTest, ImageCompressedBufferLayoutHasNoStaticRecordSize) {
TEST(FieldTableTest, Int64FieldsAreNotNumbers) {
constexpr int64_t kBig = (int64_t{1} << 55) + 12345; // > 2^53: exact only as int64.

const FieldDescriptor* ft_timestamp = findField(FieldTable<FrameTransform>::view, "timestamp");
const FieldDescriptor* ft_timestamp = findField(FieldTable<FrameTransform>::view, "timestamp_ns");
ASSERT_NE(ft_timestamp, nullptr);
EXPECT_EQ(ft_timestamp->kind, FieldKind::kInt64);
EXPECT_EQ(ft_timestamp->get_number, nullptr);
Expand All @@ -834,18 +834,18 @@ TEST(FieldTableTest, Int64FieldsAreNotNumbers) {

FrameTransform ft;
ft_timestamp->set_int64(&ft, kBig);
EXPECT_EQ(ft.timestamp, kBig);
EXPECT_EQ(ft.timestamp_ns, kBig);
EXPECT_EQ(ft_timestamp->get_int64(&ft), kBig);

const FieldDescriptor* ia_timestamp = findField(FieldTable<ImageAnnotations>::view, "timestamp");
const FieldDescriptor* ia_timestamp = findField(FieldTable<ImageAnnotations>::view, "timestamp_ns");
ASSERT_NE(ia_timestamp, nullptr);
EXPECT_EQ(ia_timestamp->kind, FieldKind::kInt64);
EXPECT_EQ(ia_timestamp->get_number, nullptr);
EXPECT_EQ(ia_timestamp->set_number, nullptr);

ImageAnnotations ia;
ia_timestamp->set_int64(&ia, kBig);
EXPECT_EQ(ia.timestamp, kBig);
EXPECT_EQ(ia.timestamp_ns, kBig);
EXPECT_EQ(ia_timestamp->get_int64(&ia), kBig);
}

Expand Down
8 changes: 4 additions & 4 deletions pj_base/tests/frame_transforms_codec_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace pb = ::PJ::test_pb;
std::vector<uint8_t> encodeFrameTransform(const FrameTransform& transform) {
std::vector<uint8_t> body;
pb::appendTag(body, 1, 2);
pb::appendLenDelim(body, pb::encodeTimestamp(transform.timestamp));
pb::appendLenDelim(body, pb::encodeTimestamp(transform.timestamp_ns));
pb::appendTag(body, 2, 2);
pb::appendString(body, transform.parent_frame_id);
pb::appendTag(body, 3, 2);
Expand All @@ -45,7 +45,7 @@ TEST(FrameTransformsCodecTest, GoldenBytesSingleTransform) {
FrameTransforms transforms;
transforms.transforms.push_back(
FrameTransform{
.timestamp = 1'234'567'890'123,
.timestamp_ns = 1'234'567'890'123,
.parent_frame_id = "map",
.child_frame_id = "base_link",
.translation = {.x = 1.0, .y = 2.0, .z = 3.0},
Expand All @@ -63,15 +63,15 @@ TEST(FrameTransformsCodecTest, RoundTripMultipleTransforms) {
FrameTransforms input;
input.transforms.push_back(
FrameTransform{
.timestamp = 42,
.timestamp_ns = 42,
.parent_frame_id = "map",
.child_frame_id = "odom",
.translation = {.x = 1.0, .y = 0.0, .z = 0.0},
.rotation = {.x = 0.0, .y = 0.0, .z = 0.0, .w = 1.0},
});
input.transforms.push_back(
FrameTransform{
.timestamp = -1,
.timestamp_ns = -1,
.parent_frame_id = "odom",
.child_frame_id = "base_link",
.translation = {.x = -1.5, .y = 2.5, .z = 3.5},
Expand Down
Loading
Loading