{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"","title":"KPN++","text":"

A C++20 Kahn Process Network library. Each node wraps a plain function and runs concurrently, communicating with downstream nodes via bounded FIFO channels. Includes Python bindings via nanobind.

"},{"location":"#why-kpn","title":"Why KPN++?","text":""},{"location":"#quick-example","title":"Quick example","text":"
#include <kpn/kpn.hpp>\nusing namespace kpn;\n\nstatic int  produce()        { return 42; }\nstatic int  double_it(int x) { return x * 2; }\nstatic void print_it(int x)  { std::cout << \"result: \" << x << '\\n'; }\n\nint main() {\n    Network net;\n    net.add(\"src\",  src)\n       .add(\"dbl\",  dbl)\n       .add(\"sink\", sink)\n       .connect(\"src\",  src.output<0>(),  \"dbl\",  dbl.input<0>())\n       .connect(\"dbl\",  dbl.output<0>(),  \"sink\", sink.input<0>())\n       .build();\n\n    net.start();\n    std::this_thread::sleep_for(std::chrono::milliseconds(100));\n    net.stop();\n}\n
"},{"location":"#install-build","title":"Install & build","text":"
cmake -B build\ncmake --build build --parallel\nctest --test-dir build          # unit tests + example smoke tests\n

See Getting Started for full build options.

"},{"location":"channels/","title":"Channels","text":"

A Channel<T> is a lock-free SPSC (single-producer, single-consumer) ring buffer with atomic wait/notify.

"},{"location":"channels/#semantics","title":"Semantics","text":""},{"location":"channels/#storage-policy","title":"Storage policy","text":"

Small trivially-copyable types (\u2264 8 bytes) are stored by value. Larger types are heap-allocated and passed via shared_ptr<const T> \u2014 one allocation per push, zero-copy fan-out:

// Override: store Tag by value despite being a struct\n// (it's trivially copyable and small \u2014 this just makes the policy explicit)\ntemplate<>\nstruct kpn::channel_storage_policy<Tag> {\n    static constexpr bool by_value = true;\n};\n

Specialize kpn::ChannelDataSize<T> for accurate bandwidth reporting on heap-owning types:

template<>\nstruct kpn::ChannelDataSize<cv::Mat> {\n    static std::size_t bytes(const cv::Mat& m) { return m.total() * m.elemSize(); }\n};\n
"},{"location":"channels/#named-ports","title":"Named ports","text":"

in<\"name\"> and out<\"name\"> tag nodes for readable wiring:

    // tokenise: no inputs, one named output \"words\"\n    auto tok = make_node<tokenise>(out<\"words\">{}, 4);\n\n    // count_words: named input \"words\", named outputs \"count\" and \"words\"\n    auto cnt = make_node<count_words>(in<\"words\">{}, out<\"count\", \"words\">{}, 4);\n\n    // report: two named inputs\n    auto snk = make_node<report>(in<\"count\", \"words\">{}, 4);\n

Named ports are checked at compile time \u2014 a typo in a port name is a compile error.

"},{"location":"channels/#capacity-tuning","title":"Capacity tuning","text":"

Set capacity per node at construction:

auto node = make_node<my_func>(/*capacity=*/20);\n

Capacity is rounded up internally to the next power of two. Monitor fill levels via diagnostics to tune for your workload \u2014 a too-small capacity causes overflows; a too-large one wastes memory and hides producer/consumer speed mismatches.

"},{"location":"channels/#spin-count","title":"Spin count","text":"

Channel spins for up to ~4 \u00b5s (200 pause hints at ~20 ns each on x86) before sleeping on a futex. Set to 0 for power-constrained or predominantly-idle pipelines:

Channel<int> ch(/*capacity=*/5, /*spin_count=*/0);\n
"},{"location":"error-handling/","title":"Error Handling & Events","text":"

KPN++ provides three complementary layers for observing and reacting to failures.

"},{"location":"error-handling/#1-per-node-error-handler","title":"1. Per-node error handler","text":"

Called when a node's function throws an unhandled exception. Return true to skip the failed invocation and keep running; false to stop the node.

    // Return true  \u2192 skip this invocation, keep the node running.\n    // Return false \u2192 stop the node (downstream drains then also stops).\n    proc.set_error_handler([](std::string_view name, std::exception_ptr ep) {\n        try { std::rethrow_exception(ep); }\n        catch (const std::exception& e) {\n            std::cerr << \"[\" << name << \"] skipping item \u2014 \" << e.what() << '\\n';\n        }\n        return true;\n    });\n

When a node stops (either from false return or no handler installed), it:

  1. Disables its input channels \u2014 upstream stops pushing into dead queues.
  2. Disables its output channels \u2014 downstream nodes receive ChannelClosedError on their next pop, propagating the shutdown naturally through the graph.
"},{"location":"error-handling/#2-per-node-overflow-callback","title":"2. Per-node overflow callback","text":"

Fired with a timestamp each time an output push is dropped because the channel is full. The node name is known at registration so it is not included \u2014 keeping the callback zero-overhead when unused.

    // Per-node overflow callback \u2014 no node name needed, known at registration.\n    std::atomic<int> overflow_count{0};\n    src.set_overflow_callback([&](steady_clock::time_point ts) {\n        auto ms = duration_cast<milliseconds>(ts.time_since_epoch()).count();\n        std::cerr << \"[overflow] fast_source at t=\" << ms << \"ms\\n\";\n        overflow_count.fetch_add(1);\n    });\n

Note

The callback is purely informational \u2014 the node always continues after an overflow. To stop the node on overflow, call node.stop() from inside the callback.

A matching set_closed_callback() fires (also with just a timestamp) when the node stops due to a closed upstream channel:

node.set_closed_callback([](std::chrono::steady_clock::time_point ts) {\n    std::cerr << \"node stopped at t=\" << ts.time_since_epoch().count() << '\\n';\n});\n

Each node holds two callback slots per event type \u2014 one user-set (registered above) and one injected by the network (see below). Both fire independently.

"},{"location":"error-handling/#3-network-level-event-handler","title":"3. Network-level event handler","text":"

One callback for the whole network. Receives the node name (captured in a closure by the network at build() / start()), a NodeEvent, and a timestamp:

    // Network-level aggregate handler \u2014 covers every node, includes node name.\n    net.set_event_handler([](std::string_view name, NodeEvent ev,\n                             steady_clock::time_point ts) {\n        auto ms = duration_cast<milliseconds>(ts.time_since_epoch()).count();\n        std::string_view kind = (ev == NodeEvent::Overflow) ? \"overflow\" : \"closed\";\n        std::cerr << \"[net:\" << kind << \"] node=\" << name << \" t=\" << ms << \"ms\\n\";\n    });\n

NodeEvent values:

Value Meaning NodeEvent::Overflow An output push was dropped (channel full) NodeEvent::Closed The node stopped (crash or upstream close cascade)

The network handler and any per-node callbacks are independent \u2014 both fire when set.

"},{"location":"error-handling/#complete-example","title":"Complete example","text":"

examples/16_event_callbacks/main.cpp shows a fast producer overflowing a slow consumer, with both a per-node overflow callback and a network-level event handler active simultaneously.

Node functions:

static std::atomic<int> g_seq{0};\n\nstatic int fast_source() {\n    std::this_thread::sleep_for(milliseconds(2));   // ~500/s\n    return g_seq.fetch_add(1);\n}\n\nstatic void slow_sink(int x) {\n    std::this_thread::sleep_for(milliseconds(50));  // ~20/s\n    std::cout << \"  consumed: \" << x << '\\n';\n}\n

Per-node overflow callback:

    // Per-node overflow callback \u2014 no node name needed, known at registration.\n    std::atomic<int> overflow_count{0};\n    src.set_overflow_callback([&](steady_clock::time_point ts) {\n        auto ms = duration_cast<milliseconds>(ts.time_since_epoch()).count();\n        std::cerr << \"[overflow] fast_source at t=\" << ms << \"ms\\n\";\n        overflow_count.fetch_add(1);\n    });\n

Network-level event handler:

    // Network-level aggregate handler \u2014 covers every node, includes node name.\n    net.set_event_handler([](std::string_view name, NodeEvent ev,\n                             steady_clock::time_point ts) {\n        auto ms = duration_cast<milliseconds>(ts.time_since_epoch()).count();\n        std::string_view kind = (ev == NodeEvent::Overflow) ? \"overflow\" : \"closed\";\n        std::cerr << \"[net:\" << kind << \"] node=\" << name << \" t=\" << ms << \"ms\\n\";\n    });\n
"},{"location":"examples/","title":"Examples","text":"

All C++ examples are built by default and registered as CTest smoke tests. Run them all with:

ctest --test-dir build -L examples\n
"},{"location":"examples/#index","title":"Index","text":"Example What it shows 01_hello_pipeline Linear pipeline, index-based port wiring 02_named_ports in<>/out<> name tags, named port access 03_multi_output Tuple-returning node, per-element routing 04_storage_policy channel_storage_policy specialisation 05_error_handling Diagnostics handler, overflow channel stats 06_watchdog Watchdog interval, stall detection 10_static_hello_pipeline StaticNetwork + make_network() 11_static_fanout StaticNetwork with FanoutNode 15_node_error_handler set_error_handler() \u2014 skip or stop on exception 16_event_callbacks set_overflow_callback(), set_event_handler()"},{"location":"examples/#opencv-examples-optional","title":"OpenCV examples (optional)","text":"

Built only when OpenCV \u2265 4 is found:

Example What it shows 09_opencv_cellshade Real-time cell-shading on webcam; MainThreadNode for display 12_static_cellshade Same pipeline as a StaticNetwork 13_debug_cellshade Web debug UI overlay on the cell-shading pipeline

Run the cell-shading example:

./build/examples/09_opencv_cellshade\n# Press 'q' or close the window to stop.\n# Falls back to an animated synthetic pattern if no webcam is found.\n
"},{"location":"fanout/","title":"Fan-out & Routing","text":""},{"location":"fanout/#fanoutnode","title":"FanoutNode","text":"

Reads one item and pushes a copy to each of N output channels. All downstream nodes receive every item.

auto fan = make_fanout<Image, 2>(/*capacity=*/8);\n\nnet.connect(\"src\",  src.output<0>(),  \"fan\",   fan.input<0>())\n   .connect(\"fan\",  fan.output<0>(),  \"nodeA\", nodeA.input<0>())\n   .connect(\"fan\",  fan.output<1>(),  \"nodeB\", nodeB.input<0>());\n

If one downstream channel overflows, that output drops the item independently \u2014 the other outputs are unaffected.

See examples/11_static_fanout.

"},{"location":"fanout/#routernode","title":"RouterNode","text":"

Reads one item and pushes it to exactly one of N outputs, chosen by a selector function:

auto router = make_router<Frame, 3>(\n    [](const Frame& f) -> std::size_t { return f.stream_id % 3; });\n\nnet.connect(\"src\",    src.output<0>(),     \"router\", router.input<0>())\n   .connect(\"router\", router.output<0>(),  \"nodeA\",  nodeA.input<0>())\n   .connect(\"router\", router.output<1>(),  \"nodeB\",  nodeB.input<0>())\n   .connect(\"router\", router.output<2>(),  \"nodeC\",  nodeC.input<0>());\n

If the selector returns >= N the item is silently dropped.

"},{"location":"fanout/#filternode","title":"FilterNode","text":"

Reads one item and passes it downstream only when a predicate returns true:

auto filt = make_filter<Frame>([](const Frame& f) { return f.valid; });\n\nnet.connect(\"src\",  src.output<0>(),  \"filt\", filt.input<0>())\n   .connect(\"filt\", filt.output<0>(), \"dst\",  dst.input<0>());\n
"},{"location":"getting-started/","title":"Getting Started","text":""},{"location":"getting-started/#requirements","title":"Requirements","text":"Dependency Version Notes CMake \u2265 3.21 C++ compiler GCC \u2265 11, Clang \u2265 13 C++20 required nanobind \u2265 2.1 auto-fetched; Python \u2265 3.8 Catch2 v3 auto-fetched for tests OpenCV \u2265 4 optional; only for examples 09/12/13"},{"location":"getting-started/#build","title":"Build","text":"
cmake -B build                          # core + tests + C++ examples\ncmake --build build --parallel\nctest --test-dir build                  # run all tests including example smoke tests\n

Enable Python bindings:

cmake -B build -DKPN_BUILD_PYTHON=ON\ncmake --build build --parallel\n

Skip examples:

cmake -B build -DKPN_BUILD_EXAMPLES=OFF\n
"},{"location":"getting-started/#your-first-pipeline","title":"Your first pipeline","text":"

Three functions \u2014 source, transform, sink \u2014 wired into a Network:

static int  produce()        { return 42; }\nstatic int  double_it(int x) { return x * 2; }\nstatic void print_it(int x)  { std::cout << \"result: \" << x << '\\n'; }\n

Create nodes, connect them, build and run:

    Network net;\n    net.add(\"src\",  src)\n       .add(\"dbl\",  dbl)\n       .add(\"sink\", sink)\n       .connect(\"src\",  src.output<0>(),  \"dbl\",  dbl.input<0>())\n       .connect(\"dbl\",  dbl.output<0>(),  \"sink\", sink.input<0>())\n       .build();\n\n    net.start();\n    std::this_thread::sleep_for(std::chrono::milliseconds(100));\n    net.stop();\n

That's it. Types are inferred from function signatures. The channel between src and dbl carries int; the channel between dbl and prn also carries int. A type mismatch is a compile error.

"},{"location":"getting-started/#named-ports","title":"Named ports","text":"

For nodes with multiple inputs or outputs, name the ports for clarity:

    // tokenise: no inputs, one named output \"words\"\n    auto tok = make_node<tokenise>(out<\"words\">{}, 4);\n\n    // count_words: named input \"words\", named outputs \"count\" and \"words\"\n    auto cnt = make_node<count_words>(in<\"words\">{}, out<\"count\", \"words\">{}, 4);\n\n    // report: two named inputs\n    auto snk = make_node<report>(in<\"count\", \"words\">{}, 4);\n

Wire by name instead of index:

    Network net;\n    net.add(\"tok\", tok)\n       .add(\"cnt\", cnt)\n       .add(\"snk\", snk)\n       .connect(\"tok\", tok.template output<\"words\">(), \"cnt\", cnt.template input<\"words\">())\n       .connect(\"cnt\", cnt.template output<\"count\">(), \"snk\", snk.template input<\"count\">())\n       .connect(\"cnt\", cnt.template output<\"words\">(), \"snk\", snk.template input<\"words\">())\n       .build();\n\n    net.start();\n    std::this_thread::sleep_for(std::chrono::milliseconds(500));\n    net.stop();\n
"},{"location":"getting-started/#multi-output-nodes","title":"Multi-output nodes","text":"

Return a std::tuple to fan out to multiple downstream nodes:

// Multi-output: returns (key, value) as a tuple \u2014 KPN++ routes each element\n// to its own output port automatically.\nstatic std::tuple<std::string, std::string> parse(std::string kv) {\n    auto sep = kv.find('=');\n    if (sep == std::string::npos) return {kv, \"\"};\n    return {kv.substr(0, sep), kv.substr(sep + 1)};\n}\n

Wire each tuple element to its own downstream node:

    auto gen  = make_node<generate>(out<\"kv\">{},          4);\n    auto par  = make_node<parse>   (in<\"kv\">{},  out<\"key\", \"value\">{}, 4);\n    auto keys = make_node<print_key>  (in<\"key\">{},   4);\n    auto vals = make_node<print_value>(in<\"value\">{}, 4);\n\n    Network net;\n    net.add(\"gen\",  gen)\n       .add(\"par\",  par)\n       .add(\"keys\", keys)\n       .add(\"vals\", vals)\n       .connect(\"gen\",  gen.template output<\"kv\">(),    \"par\",  par.template input<\"kv\">())\n       .connect(\"par\",  par.template output<\"key\">(),   \"keys\", keys.template input<\"key\">())\n       .connect(\"par\",  par.template output<\"value\">(), \"vals\", vals.template input<\"value\">())\n       .build();\n\n    net.start();\n    std::this_thread::sleep_for(std::chrono::milliseconds(600));\n    net.stop();\n
"},{"location":"network/","title":"Networks","text":"

A Network wires nodes together at runtime using a builder chain.

"},{"location":"network/#building-a-network","title":"Building a network","text":"
    Network net;\n    net.add(\"src\",  src)\n       .add(\"dbl\",  dbl)\n       .add(\"sink\", sink)\n       .connect(\"src\",  src.output<0>(),  \"dbl\",  dbl.input<0>())\n       .connect(\"dbl\",  dbl.output<0>(),  \"sink\", sink.input<0>())\n       .build();\n\n    net.start();\n    std::this_thread::sleep_for(std::chrono::milliseconds(100));\n    net.stop();\n

The builder chain:

Method Purpose .add(name, node) Register a node; assigns its name .connect(src, port, dst, port) Wire one output port to one input port .build() Compute topological order; inject network callbacks .start() Start nodes in topological order .stop() Stop all nodes immediately .shutdown() Graceful drain: stop sources first, wait for channels to empty, then stop downstream"},{"location":"network/#port-access","title":"Port access","text":"

Ports are accessed by index or by name:

// By index\nnet.connect(\"src\", src.output<0>(), \"dst\", dst.input<0>());\n\n// By name (requires named ports)\n    Network net;\n    net.add(\"tok\", tok)\n       .add(\"cnt\", cnt)\n       .add(\"snk\", snk)\n       .connect(\"tok\", tok.template output<\"words\">(), \"cnt\", cnt.template input<\"words\">())\n       .connect(\"cnt\", cnt.template output<\"count\">(), \"snk\", snk.template input<\"count\">())\n       .connect(\"cnt\", cnt.template output<\"words\">(), \"snk\", snk.template input<\"words\">())\n       .build();\n\n    net.start();\n    std::this_thread::sleep_for(std::chrono::milliseconds(500));\n    net.stop();\n
"},{"location":"network/#diagnostics","title":"Diagnostics","text":"

Install a diagnostics handler to receive periodic snapshots of every node and channel:

    // Custom diagnostics handler \u2014 fires on the watchdog interval.\n    // Print a concise one-liner rather than the full table.\n    net.set_diagnostics_handler([](const std::vector<NodeSnapshot>& nodes,\n                                   const std::vector<ChannelSnapshot>& channels) {\n        std::cout << \"[diag] \";\n        for (auto& n : nodes)\n            std::cout << n.name << \"=\" << n.throughput_fps << \"fps  \";\n        for (auto& c : channels)\n            std::cout << \"channel fill=\" << static_cast<int>(c.fill_pct()) << \"% \"\n                      << \"overflows=\" << c.overflows;\n        std::cout << '\\n';\n    });\n

Or print a full report at any time:

net.print_diagnostics();   // writes to stderr by default\nnet.print_diagnostics(std::cout);\n
"},{"location":"network/#network-level-event-handler","title":"Network-level event handler","text":"

Observe overflow and node-stop events across the entire network in one place:

    // Network-level aggregate handler \u2014 covers every node, includes node name.\n    net.set_event_handler([](std::string_view name, NodeEvent ev,\n                             steady_clock::time_point ts) {\n        auto ms = duration_cast<milliseconds>(ts.time_since_epoch()).count();\n        std::string_view kind = (ev == NodeEvent::Overflow) ? \"overflow\" : \"closed\";\n        std::cerr << \"[net:\" << kind << \"] node=\" << name << \" t=\" << ms << \"ms\\n\";\n    });\n

NodeEvent is either NodeEvent::Overflow (item dropped on full channel) or NodeEvent::Closed (node stopped due to crash or closed upstream channel). See Error Handling & Events.

"},{"location":"network/#shutdown","title":"Shutdown","text":"

net.stop() halts immediately \u2014 all nodes stop in reverse topological order.

net.shutdown() drains gracefully: source nodes stop first; their output channels are polled until empty; then the next layer stops, and so on. This ensures no items are lost if downstream nodes are still consuming.

"},{"location":"network/#staticnetwork","title":"StaticNetwork","text":"

For zero-overhead compile-time topology, see Static Networks.

"},{"location":"nodes/","title":"Nodes","text":"

A node wraps any callable. Its input types are inferred from the function's parameter list; its output types from the return type.

"},{"location":"nodes/#node-types","title":"Node types","text":"Type Thread model Use case Node<Func> Dedicated thread per node Default \u2014 simplest, most isolated PoolNode<Func> Shared ThreadPool Many nodes, resource-bounded execution InterruptNode<Func> Event-driven, no thread Camera frame ready, timer tick, socket FanoutNode<T, N> Dedicated thread Broadcast one item to N outputs RouterNode<T, N> Dedicated thread Route one item to one of N outputs FilterNode<T> Dedicated thread Pass items matching a predicate"},{"location":"nodes/#creating-nodes","title":"Creating nodes","text":"

All node types are created via factory functions that infer types from the callable:

// Free function \u2014 simplest case\nauto node = make_node<my_func>();\n\n// Stateful functor (operator() is the function)\nMyProcessor proc;\nauto node = make_node(proc);\n\n// Pool node \u2014 shares a ThreadPool with other nodes\nauto pool = std::make_shared<ThreadPool>(4);\nauto node = make_pool_node<my_func>(pool);\n\n// Interrupt node \u2014 triggered externally\nauto sched = std::make_shared<ThreadPool>(2);\nauto node  = make_interrupt_node<produce_frame>(sched, out<\"frame\">{});\ncamera_sdk.on_frame_ready(node.get_trigger());\n
"},{"location":"nodes/#channel-capacity","title":"Channel capacity","text":"

Each node's input FIFO has a configurable capacity (default 5):

auto node = make_node<my_func>(/*capacity=*/20);\nauto node = make_pool_node<my_func>(pool, /*capacity=*/20);\n

When an upstream push would exceed capacity, ChannelOverflowError is thrown and the item is dropped. See Error Handling & Events to observe and react to this.

"},{"location":"nodes/#source-nodes","title":"Source nodes","text":"

A node with no inputs is a source. It self-submits immediately on start() and re-submits after each execution:

static int produce() {\n    std::this_thread::sleep_for(std::chrono::milliseconds(10));\n    return ++counter;\n}\nauto src = make_node<produce>();\n

Tip

Source nodes must sleep or yield to avoid overflowing their output channel. The channel capacity provides the only bound.

"},{"location":"nodes/#sink-nodes","title":"Sink nodes","text":"

A node with a void return is a sink \u2014 it consumes items without producing output:

static void print_it(int x) { std::cout << x << '\\n'; }\nauto snk = make_node<print_it>();\n
"},{"location":"nodes/#error-handler","title":"Error handler","text":"

When a node's function throws an unhandled exception, the default behaviour is to stop the node (disabling its channels so the shutdown cascades downstream). Install a handler to override:

    // Return true  \u2192 skip this invocation, keep the node running.\n    // Return false \u2192 stop the node (downstream drains then also stops).\n    proc.set_error_handler([](std::string_view name, std::exception_ptr ep) {\n        try { std::rethrow_exception(ep); }\n        catch (const std::exception& e) {\n            std::cerr << \"[\" << name << \"] skipping item \u2014 \" << e.what() << '\\n';\n        }\n        return true;\n    });\n

See Error Handling & Events for the full picture.

"},{"location":"shared-resource/","title":"Shared Resources","text":"

SharedResource<T> arbitrates exclusive access to a resource (ONNX session, CUDA stream, serial port) across multiple nodes using a priority-based waiter queue with starvation prevention.

"},{"location":"shared-resource/#usage","title":"Usage","text":"
#include <kpn/shared_resource.hpp>\nusing namespace kpn;\n\nSharedResource<OnnxSession> model(session_args...);\n\nstatic cv::Mat run_inference(cv::Mat frame) {\n    // Acquires the model; releases automatically on scope exit.\n    auto guard = model.acquire_balanced(in_channel, out_channel);\n    return guard->Run(frame);\n}\n
"},{"location":"shared-resource/#acquire-modes","title":"Acquire modes","text":"Method Priority acquire() Equal (fair FIFO) acquire(fn) Custom \u2014 fn() returns float in [0, 1] acquire_balanced(in_ch, out_ch) input_fill \u00d7 output_headroom \u2014 highest urgency wins

acquire_balanced favours nodes with full input queues and empty output queues \u2014 the node that has the most work to do and nowhere to stall wins the resource next.

"},{"location":"shared-resource/#starvation-prevention","title":"Starvation prevention","text":"

Each waiter's effective score grows with elapsed wait time (0.05 per second by default), ensuring a low-priority node eventually gets served regardless of how frequently higher-priority nodes compete.

"},{"location":"shared-resource/#diagnostics","title":"Diagnostics","text":"

Register with the network for snapshot reporting:

net.register_resource(\"model\", &model);\n

The diagnostics table then shows acquisition count, mean wait time, and current waiter count.

"},{"location":"static-network/","title":"Static Networks","text":"

StaticNetwork encodes the entire topology at compile time using a make_network() builder. Nodes and channel types are verified statically with zero runtime overhead.

"},{"location":"static-network/#usage","title":"Usage","text":"
#include <kpn/kpn.hpp>\nusing namespace kpn;\n\nstatic int  produce()        { return 42; }\nstatic int  double_it(int x) { return x * 2; }\nstatic void print_it(int x)  { std::cout << x << '\\n'; }\n\nint main() {\n    auto src = make_node<produce>  ();\n    auto dbl = make_node<double_it>();\n    auto prn = make_node<print_it> ();\n\n    auto net = make_network(\n        edge(src, src.output<0>(), dbl, dbl.input<0>()),\n        edge(dbl, dbl.output<0>(), prn, prn.input<0>())\n    );\n\n    net.set_event_handler([](std::string_view name, NodeEvent ev, auto ts) {\n        // same API as Network\n    });\n\n    net.start();\n    std::this_thread::sleep_for(std::chrono::milliseconds(100));\n    net.stop();\n}\n

See examples/10_static_hello_pipeline and examples/11_static_fanout.

"},{"location":"static-network/#when-to-use","title":"When to use","text":"Network StaticNetwork Topology known at Runtime Compile time Type checking Runtime (dynamic_cast) Compile time Overhead Minimal Zero Flexibility Add nodes dynamically Fixed at compile time

For most applications Network is sufficient. Use StaticNetwork when you need the absolute minimum overhead or want compile-time topology verification.

"}]}