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
readandwrite, 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
HANDLERvertex registered with an emptyhandlers_tallocates none, and aSTORED_VALUEvertex given anon_childrendoes (reference 02 §Vertex lifecycle;value_handlers_tincore/include/libtracer/vertex.hpp).on_writeis where the device acts. It receives the written rope and the writer’swrite_ctx_t; both are borrowed for the call only, so anything retained must be copied. The handler returns aresult_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.