Subscribe to one vertex (L4 graph)

The smallest subscription there is: one producer, one in-process callback, and the delivery contract that follows from it. subscribe(src, callable) returns a subscription_t handle; every write to src then invokes the callback with the written rope value.

What to notice

  • subscribe is host-SDK sugar, not a wire verb. The wire data API stays read/write/await (ADR-0006); a subscription is a consumer-initiated SUBSCRIBER field-write into the producer’s :subscribers[] (ADR-0026). The callback overload enters the same single admission door as that field-write — SUBSCRIBE gate, append, durability latch (ADR-0049) — it merely skips the parse a callback cannot ride.

  • Delivery is inline, on the writing thread. The callback runs inside write(), once per write; nothing is queued and no delivery thread exists. A slow callback is a slow write.

  • The callable is bound by address, and it is the edge’s ctx. The templated overload takes an lvalue reference (a temporary would dangle) and forwards {fn, &callable} to the subscriber_fn_t form — a plain function-pointer pair, so the per-publish edge snapshot is a trivial copy rather than a std::function clone. How long that address must stay alive is the subject of unsubscribe & the release hook.

  • own_subs counts the vertex’s OWN slots. It is a sizing figure, not an “is anyone listening” test — subscribers on an ancestor are not in it (see one edge, a whole subtree), and has_subscribers is the question a producer should ask.

Source

 1/*
 2 * SPDX-License-Identifier: Apache-2.0
 3 * SPDX-FileCopyrightText: Copyright 2026 avatarsd LLC
 4 */
 5
 6/**
 7 * @file
 8 * @brief Subscribe to ONE vertex — the delivery-callback contract, and nothing else.
 9 *
10 * `subscribe(src, callable)` is host-SDK sugar over the wire subscription (a
11 * `SUBSCRIBER` field-write into `src:subscribers[]`, ADR-0049). It returns a
12 * `subscription_t` handle, and from then on every write to `src` invokes the callback
13 * **inline on the writing thread**, once per write, with the written rope value.
14 *
15 * Runs under ctest as `example_sub_callback`; it self-checks and returns non-zero on any
16 * mismatch.
17 */
18
19#include <cstddef>
20#include <cstdint>
21#include <cstdio>
22
23#include "libtracer/tracer.hpp"
24
25namespace {
26
27using tr::graph::path_t;
28using tr::graph::role_t;
29
30/** @brief A one-byte VALUE view over @p b (one heap segment). */
31tr::view::view_t value_byte(std::uint8_t b) {
32    tr::view::segment_ptr_t seg = tr::view::heap_alloc(1);
33    seg->bytes[0] = std::byte{b};
34    return tr::view::view_t::over(std::move(seg));
35}
36
37/** @brief Record a failed expectation on @p ok and report it. */
38void check(bool& ok, bool cond, const char* what) {
39    if (!cond) {
40        std::printf("  [FAIL] %s\n", what);
41        ok = false;
42    }
43}
44
45}  // namespace
46
47int main() {
48    tr::graph::graph_t g;
49    const tr::graph::vertex_handle_t temp =
50        g.register_vertex(path_t("/sensor/temp"), role_t::STORED_VALUE);
51
52    int deliveries = 0;
53    std::uint8_t last = 0;
54    // The callback is bound BY ADDRESS (lvalues only) and is the edge's `ctx`, so it must
55    // outlive the subscription — here, main's frame.
56    auto on_temp = [&](const tr::view::rope_t& v) {
57        ++deliveries;
58        last = std::to_integer<std::uint8_t>(v.only().bytes()[0]);
59        std::printf("  delivery %d: %u\n", deliveries, last);
60    };
61    const auto sub = g.subscribe(path_t("/sensor/temp"), on_temp);
62
63    std::printf("subscribe(/sensor/temp) -> %s\n", sub ? "ok" : "error");
64    for (const std::uint8_t v : {std::uint8_t{7}, std::uint8_t{8}, std::uint8_t{9}})
65        (void)g.write(temp, value_byte(v));
66
67    bool ok = true;
68    check(ok, sub.has_value(), "subscribe returns a subscription_t handle");
69    check(ok, deliveries == 3, "one delivery per write, none extra");
70    check(ok, last == 9, "the callback sees the written value");
71    check(ok, g.own_subs(temp) == 1, "the vertex carries exactly one own subscriber slot");
72    std::printf("RESULT %s\n", ok ? "ok" : "FAILED");
73    return ok ? 0 : 1;
74}

See also: graph module · communication flows · in-process pub/sub.