Skip to content

Getting Started

Requirements

Dependency Version Notes
CMake ≥ 3.21
C++ compiler GCC ≥ 11, Clang ≥ 13 C++20 required
nanobind ≥ 2.1 auto-fetched; Python ≥ 3.8
Catch2 v3 auto-fetched for tests
OpenCV ≥ 4 optional; only for examples 09/12/13

Build

cmake -B build                          # core + tests + C++ examples
cmake --build build --parallel
ctest --test-dir build                  # run all tests including example smoke tests

Enable Python bindings:

cmake -B build -DKPN_BUILD_PYTHON=ON
cmake --build build --parallel

Skip examples:

cmake -B build -DKPN_BUILD_EXAMPLES=OFF

Your first pipeline

Three functions — source, transform, sink — wired into a Network:

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

Create nodes, connect them, build and run:

    Network net;
    net.add("src",  src)
       .add("dbl",  dbl)
       .add("sink", sink)
       .connect("src",  src.output<0>(),  "dbl",  dbl.input<0>())
       .connect("dbl",  dbl.output<0>(),  "sink", sink.input<0>())
       .build();

    net.start();
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    net.stop();

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.

Named ports

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

    // tokenise: no inputs, one named output "words"
    auto tok = make_node<tokenise>(out<"words">{}, 4);

    // count_words: named input "words", named outputs "count" and "words"
    auto cnt = make_node<count_words>(in<"words">{}, out<"count", "words">{}, 4);

    // report: two named inputs
    auto snk = make_node<report>(in<"count", "words">{}, 4);

Wire by name instead of index:

    Network net;
    net.add("tok", tok)
       .add("cnt", cnt)
       .add("snk", snk)
       .connect("tok", tok.template output<"words">(), "cnt", cnt.template input<"words">())
       .connect("cnt", cnt.template output<"count">(), "snk", snk.template input<"count">())
       .connect("cnt", cnt.template output<"words">(), "snk", snk.template input<"words">())
       .build();

    net.start();
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
    net.stop();

Multi-output nodes

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

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

Wire each tuple element to its own downstream node:

    auto gen  = make_node<generate>(out<"kv">{},          4);
    auto par  = make_node<parse>   (in<"kv">{},  out<"key", "value">{}, 4);
    auto keys = make_node<print_key>  (in<"key">{},   4);
    auto vals = make_node<print_value>(in<"value">{}, 4);

    Network net;
    net.add("gen",  gen)
       .add("par",  par)
       .add("keys", keys)
       .add("vals", vals)
       .connect("gen",  gen.template output<"kv">(),    "par",  par.template input<"kv">())
       .connect("par",  par.template output<"key">(),   "keys", keys.template input<"key">())
       .connect("par",  par.template output<"value">(), "vals", vals.template input<"value">())
       .build();

    net.start();
    std::this_thread::sleep_for(std::chrono::milliseconds(600));
    net.stop();