Added callbacks for node errors and fifo overflow
📚 Docs / deploy (push) Failing after 7s
🧪 Test / test (push) Has been cancelled

Add new doc system which should/might deploy to pages.
This commit is contained in:
2026-06-19 22:26:39 +02:00
parent 79916f1da1
commit 6f384dc4b5
30 changed files with 1192 additions and 82 deletions
+6 -6
View File
@@ -7,20 +7,20 @@
//
// [produce] --int--> [double_it] --int--> [print_it]
// [snippet: basic_node_fns]
// --8<-- [start:basic_node_fns]
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'; }
// [/snippet: basic_node_fns]
// --8<-- [end:basic_node_fns]
int main() {
using namespace kpn;
// [snippet: index_only_nodes]
// --8<-- [start:index_only_nodes]
auto src = make_node<produce>(5);
auto dbl = make_node<double_it>(5);
auto sink = make_node<print_it>(5);
// [/snippet: index_only_nodes]
// --8<-- [end:index_only_nodes]
// Wire channels
auto& dbl_in = dbl.input_channel<0>();
@@ -28,7 +28,7 @@ int main() {
src.set_output_channel<0>(&dbl_in);
dbl.set_output_channel<0>(&sink_in);
// [snippet: network_build]
// --8<-- [start:network_build]
Network net;
net.add("src", src)
.add("dbl", dbl)
@@ -40,5 +40,5 @@ int main() {
net.start();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
net.stop();
// [/snippet: network_build]
// --8<-- [end:network_build]
}
+4 -4
View File
@@ -54,7 +54,7 @@ static void report(int count, std::vector<std::string> words) {
int main() {
using namespace kpn;
// [snippet: named_port_creation]
// --8<-- [start:named_port_creation]
// tokenise: no inputs, one named output "words"
auto tok = make_node<tokenise>(out<"words">{}, 4);
@@ -63,9 +63,9 @@ int main() {
// report: two named inputs
auto snk = make_node<report>(in<"count", "words">{}, 4);
// [/snippet: named_port_creation]
// --8<-- [end:named_port_creation]
// [snippet: named_port_network]
// --8<-- [start:named_port_network]
Network net;
net.add("tok", tok)
.add("cnt", cnt)
@@ -78,5 +78,5 @@ int main() {
net.start();
std::this_thread::sleep_for(std::chrono::milliseconds(500));
net.stop();
// [/snippet: named_port_network]
// --8<-- [end:named_port_network]
}
+4 -4
View File
@@ -33,7 +33,7 @@ static std::string generate() {
return pairs[gen_index++ % 5];
}
// [snippet: multi_output_fn]
// --8<-- [start:multi_output_fn]
// 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) {
@@ -41,7 +41,7 @@ static std::tuple<std::string, std::string> parse(std::string kv) {
if (sep == std::string::npos) return {kv, ""};
return {kv.substr(0, sep), kv.substr(sep + 1)};
}
// [/snippet: multi_output_fn]
// --8<-- [end:multi_output_fn]
static void print_key(std::string key) {
std::cout << "KEY → " << key << '\n';
@@ -56,7 +56,7 @@ static void print_value(std::string value) {
int main() {
using namespace kpn;
// [snippet: fanout_network]
// --8<-- [start:fanout_network]
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);
@@ -75,5 +75,5 @@ int main() {
net.start();
std::this_thread::sleep_for(std::chrono::milliseconds(600));
net.stop();
// [/snippet: fanout_network]
// --8<-- [end:fanout_network]
}
+2 -2
View File
@@ -34,14 +34,14 @@ struct Tag {
int value = 0;
};
// [snippet: storage_policy_spec]
// --8<-- [start:storage_policy_spec]
// Override: store Tag by value despite being a struct
// (it's trivially copyable and small — this just makes the policy explicit)
template<>
struct kpn::channel_storage_policy<Tag> {
static constexpr bool by_value = true;
};
// [/snippet: storage_policy_spec]
// --8<-- [end:storage_policy_spec]
// ── Node functions ────────────────────────────────────────────────────────────
+2 -2
View File
@@ -45,7 +45,7 @@ int main() {
Network net;
// [snippet: diagnostics_handler]
// --8<-- [start:diagnostics_handler]
// Custom diagnostics handler — fires on the watchdog interval.
// Print a concise one-liner rather than the full table.
net.set_diagnostics_handler([](const std::vector<NodeSnapshot>& nodes,
@@ -58,7 +58,7 @@ int main() {
<< "overflows=" << c.overflows;
std::cout << '\n';
});
// [/snippet: diagnostics_handler]
// --8<-- [end:diagnostics_handler]
net.set_watchdog_interval(std::chrono::milliseconds(200));
+8 -8
View File
@@ -38,7 +38,7 @@ static cv::Mat make_gradient(int W, int H) {
// ── Pipeline functions ────────────────────────────────────────────────────────
// [snippet: capture_fn]
// --8<-- [start:capture_fn]
static std::tuple<cv::Mat, cv::Mat> capture() {
constexpr int W = 640, H = 480;
static cv::VideoCapture cap;
@@ -75,7 +75,7 @@ static std::tuple<cv::Mat, cv::Mat> capture() {
}
return {frame.clone(), frame.clone()};
}
// [/snippet: capture_fn]
// --8<-- [end:capture_fn]
static cv::Mat to_gray(cv::Mat bgr) {
cv::Mat gray;
@@ -120,7 +120,7 @@ static std::tuple<cv::Mat, cv::Mat> composite(cv::Mat edge_mask, cv::Mat colour)
// The constructor opens both windows on the main thread (Wayland requirement).
// operator() is called by step() whenever both channels have a frame ready.
// [snippet: display_node]
// --8<-- [start:display_node]
class DisplayNode : public kpn::MainThreadNode<DisplayNode,
kpn::in<"composite", "edges">,
cv::Mat, cv::Mat> {
@@ -150,14 +150,14 @@ private:
catch (const cv::Exception&) { return false; }
}
};
// [/snippet: display_node]
// --8<-- [end:display_node]
// ─────────────────────────────────────────────────────────────────────────────
int main() {
using namespace kpn;
// [snippet: opencv_network]
// --8<-- [start:opencv_network]
auto src = make_node<capture> (out<"colour","grey">{}, 8);
auto gray_node = make_node<to_gray> (in<"bgr">{}, out<"gray">{}, 8);
auto edge_node = make_node<edges_fn> (in<"gray">{}, out<"edges">{}, 8);
@@ -182,7 +182,7 @@ int main() {
.connect("comp", comp.template output<"result">(), "display", disp.template input<"composite">())
.connect("comp", comp.template output<"edges">(), "display", disp.template input<"edges">())
.build();
// [/snippet: opencv_network]
// --8<-- [end:opencv_network]
net.set_watchdog_interval(std::chrono::milliseconds(5000));
#ifdef KPN_WEB_DEBUG
@@ -192,7 +192,7 @@ int main() {
std::cout << "Cell-shading pipeline running. Press 'q' to stop.\n";
std::cout << "Web debug UI: http://localhost:9090\n";
// [snippet: main_thread_step]
// --8<-- [start:main_thread_step]
net.start();
// Main thread drives display — imshow/waitKey stay on the GUI thread.
@@ -201,6 +201,6 @@ int main() {
cv::waitKey(8); // yield event loop when no frame ready
net.stop();
// [/snippet: main_thread_step]
// --8<-- [end:main_thread_step]
return 0;
}
+2
View File
@@ -44,6 +44,7 @@ int main() {
auto proc = make_node<validate>();
auto snk = make_node<sink> ();
// --8<-- [start:error_handler]
// Return true → skip this invocation, keep the node running.
// Return false → stop the node (downstream drains then also stops).
proc.set_error_handler([](std::string_view name, std::exception_ptr ep) {
@@ -53,6 +54,7 @@ int main() {
}
return true;
});
// --8<-- [end:error_handler]
Network net;
net.add("source", src)
+83
View File
@@ -0,0 +1,83 @@
// Example 16 — Event Callbacks: overflow and node-stopped signals
//
// Two complementary observation mechanisms:
//
// 1. Per-node overflow callback set_overflow_callback()
// Fired (with a timestamp) when a node's output channel is full and an
// item is dropped. Useful for targeted monitoring of a specific node.
//
// 2. Network-level event handler net.set_event_handler()
// Aggregate callback covering every node: receives the node name, a
// NodeEvent (Overflow or Closed), and a timestamp. Register once and
// observe the whole network.
//
// Pipeline: [fast_source] --int--> [slow_sink]
//
// fast_source produces at ~500 items/s; slow_sink consumes at ~20 items/s.
// The channel capacity is 3, so overflows appear within milliseconds.
#include <kpn/kpn.hpp>
#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>
using namespace kpn;
using namespace std::chrono;
// ── Node functions ────────────────────────────────────────────────────────────
// --8<-- [start:node_fns]
static std::atomic<int> g_seq{0};
static int fast_source() {
std::this_thread::sleep_for(milliseconds(2)); // ~500/s
return g_seq.fetch_add(1);
}
static void slow_sink(int x) {
std::this_thread::sleep_for(milliseconds(50)); // ~20/s
std::cout << " consumed: " << x << '\n';
}
// --8<-- [end:node_fns]
// ── main ──────────────────────────────────────────────────────────────────────
int main() {
auto src = make_node<fast_source>(/*capacity=*/3);
auto snk = make_node<slow_sink> (/*capacity=*/3);
// --8<-- [start:per_node_callback]
// Per-node overflow callback — no node name needed, known at registration.
std::atomic<int> overflow_count{0};
src.set_overflow_callback([&](steady_clock::time_point ts) {
auto ms = duration_cast<milliseconds>(ts.time_since_epoch()).count();
std::cerr << "[overflow] fast_source at t=" << ms << "ms\n";
overflow_count.fetch_add(1);
});
// --8<-- [end:per_node_callback]
Network net;
// --8<-- [start:network_event_handler]
// Network-level aggregate handler — covers every node, includes node name.
net.set_event_handler([](std::string_view name, NodeEvent ev,
steady_clock::time_point ts) {
auto ms = duration_cast<milliseconds>(ts.time_since_epoch()).count();
std::string_view kind = (ev == NodeEvent::Overflow) ? "overflow" : "closed";
std::cerr << "[net:" << kind << "] node=" << name << " t=" << ms << "ms\n";
});
// --8<-- [end:network_event_handler]
net.add("source", src)
.add("sink", snk)
.connect("source", src.output<0>(), "sink", snk.input<0>())
.build()
.start();
std::this_thread::sleep_for(milliseconds(300));
net.stop();
std::cout << "\nTotal overflows observed by per-node callback: "
<< overflow_count.load() << '\n';
}
+11 -1
View File
@@ -1,8 +1,16 @@
cmake_minimum_required(VERSION 3.21)
# Build an example and register it as a CTest smoke test.
# Examples that are self-terminating (fixed sleep net.stop()) pass when
# they exit 0 within TIMEOUT seconds. OpenCV/UI examples are excluded.
function(kpn_example name)
add_executable(${name} ${name}/main.cpp)
target_link_libraries(${name} PRIVATE kpn)
add_test(NAME example_${name} COMMAND ${name})
set_tests_properties(example_${name} PROPERTIES
TIMEOUT 15
LABELS examples
)
endfunction()
kpn_example(01_hello_pipeline)
@@ -11,9 +19,11 @@ kpn_example(03_multi_output)
kpn_example(04_storage_policy)
kpn_example(05_error_handling)
kpn_example(06_watchdog)
kpn_example(15_node_error_handler)
set_tests_properties(example_06_watchdog PROPERTIES TIMEOUT 40)
kpn_example(10_static_hello_pipeline)
kpn_example(11_static_fanout)
kpn_example(15_node_error_handler)
kpn_example(16_event_callbacks)
if(KPN_WEB_DEBUG)
kpn_target_enable_web_debug(06_watchdog)