A HANDLER vertex executes its write (L4 graph)

The vertex’s role decides what a write means at that address. STORED_VALUE assigns; HANDLER — roles 3–7 of reference 11 — runs the owner’s on_write instead, and its on_read supplies a value the graph never held: an MMIO register, a computation, a device command. This example registers a relay whose write toggles the device and whose read reports the device’s state.

What to notice

  • The role is invisible on the wire. A peer sees one address with read and write, exactly as for a stored value; no role code is ever transmitted (CONTEXT.md §Structural vertex, on why a role is never on the wire).

  • The seam allocation is keyed on presence, not on role. A vertex bears a value-seam block iff a handler was installed at registration, so a HANDLER vertex registered with an empty handlers_t allocates none, and a STORED_VALUE vertex given an on_children does (reference 02 §Vertex lifecycle; value_handlers_t in core/include/libtracer/vertex.hpp).

  • on_write is where the device acts. It receives the written rope and the writer’s write_ctx_t; both are borrowed for the call only, so anything retained must be copied. The handler returns a result_t<void> — a refusal is the device’s, not the graph’s.

  • This is the sink shape a subscription delivers into. Delivery is a write (CONTEXT.md §SUBSCRIBER direction), so a handler vertex is what a spec-faithful subscribe(src, target) re-dispatches to — see in-process pub/sub, which wires exactly that.

Source

 1/*
 2 * SPDX-License-Identifier: Apache-2.0
 3 * SPDX-FileCopyrightText: Copyright 2026 avatarsd LLC
 4 */
 5
 6/**
 7 * @file
 8 * @brief ONE CONCEPT — a HANDLER vertex EXECUTES its write instead of storing it.
 9 *
10 * The vertex's role decides what a write means at that address. `STORED_VALUE` assigns;
11 * `HANDLER` (roles 3–7 of `docs/reference/11-vertex-roles-and-aggregation.md`) runs the
12 * owner's `on_write`, and its `on_read` supplies a value the graph never held — an MMIO
13 * register, a computation, a device command. The role is host state and appears on no wire:
14 * a peer sees one address with `read`/`write`, exactly as for a stored value.
15 *
16 * The seam block is allocated on the PRESENCE of a handler, not on the role
17 * (`core/include/libtracer/vertex.hpp`, `value_handlers_t`), so a `HANDLER` vertex
18 * registered with an empty `handlers_t` allocates none.
19 *
20 * Runs under ctest as `example_graph_handler_vertex`; returns non-zero on any failed check.
21 */
22
23#include <cstdio>
24#include <cstring>
25#include <span>
26#include <string_view>
27
28#include "libtracer/tracer.hpp"
29
30namespace {
31
32using tr::graph::handlers_t;
33using tr::graph::path_t;
34using tr::graph::role_t;
35
36/** @brief An owned one-segment view over @p text. */
37tr::view::view_t value_of(std::string_view text) {
38    return *tr::view::over_bytes(std::as_bytes(std::span<const char>(text.data(), text.size())));
39}
40
41/** @brief Report expectation @p what and record a failure on @p ok. */
42void check(bool& ok, bool cond, const char* what) {
43    std::printf("  [%s] %s\n", cond ? "ok" : "FAIL", what);
44    ok = ok && cond;
45}
46
47}  // namespace
48
49int main() {
50    tr::graph::graph_t g;
51    bool ok = true;
52    int commands = 0;
53
54    handlers_t relay;
55    relay.on_write = [&commands](const tr::view::rope_t& in,
56                                 const tr::graph::write_ctx_t&) -> tr::graph::result_t<void> {
57        ++commands;  // the device acts here; nothing is assigned to the vertex
58        std::printf("  [relay] command #%d, %zu bytes\n", commands, in.total_length());
59        return {};
60    };
61    relay.on_read = [&commands]() -> tr::graph::result_t<tr::view::rope_t> {
62        return tr::view::rope_t{value_of(commands % 2 ? "ON" : "OFF")};  // computed, never stored
63    };
64    const auto sw = g.register_vertex(path_t("/dev/relay0"), role_t::HANDLER, std::move(relay));
65
66    const auto before = g.read(sw);
67    check(ok, before && before->get()->only().bytes().size() == 3,
68          "on_read supplies a value the graph never stored");
69
70    (void)g.write(sw, value_of("toggle"));
71    check(ok, commands == 1, "a write to a HANDLER vertex runs on_write");
72
73    const auto after = g.read(sw);
74    check(ok, after && after->get()->only().bytes().size() == 2,
75          "the next read reflects the device, not a store");
76    return ok ? 0 : 1;
77}

See also: graph module · vertex roles and aggregation · in-process pub/sub.