Added callbacks for node errors and fifo overflow
Add new doc system which should/might deploy to pages.
This commit is contained in:
@@ -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]
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
@@ -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 ────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user