Duncan Tourolle 6f384dc4b5
Some checks failed
📚 Docs / deploy (push) Failing after 7s
🧪 Test / test (push) Has been cancelled
Added callbacks for node errors and fifo overflow
Add new doc system which should/might deploy to pages.
2026-06-19 22:26:39 +02:00

121 lines
4.4 KiB
C++

// Example 04 — Storage Policy
//
// Demonstrates how KPN++ chooses between by-value and shared_ptr storage
// inside channels depending on the type.
//
// Small trivially-copyable types (int, double, etc.) are stored by value.
// Large or non-trivially-copyable types are stored as shared_ptr<const T>
// — zero copies even across multiple downstream consumers.
//
// This example also shows how to override the policy for a specific type
// with a template specialisation.
//
// [produce_frame] --Frame--> [process_frame] --Frame--> [consume_frame]
// [produce_small] --int----> [double_it] --int----> [print_int]
#include <kpn/kpn.hpp>
#include <array>
#include <chrono>
#include <iostream>
#include <string>
#include <thread>
#include <type_traits>
// ── Types ─────────────────────────────────────────────────────────────────────
// Large frame type — will be stored as shared_ptr<const Frame> by default
struct Frame {
std::array<uint8_t, 4096> pixels{};
int id = 0;
};
// A small struct we force to be stored by value via policy specialisation
struct Tag {
int value = 0;
};
// --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;
};
// --8<-- [end:storage_policy_spec]
// ── Node functions ────────────────────────────────────────────────────────────
static int frame_id = 0;
static int tag_id = 0;
static Frame produce_frame() {
std::this_thread::sleep_for(std::chrono::milliseconds(40));
Frame f;
f.id = frame_id++;
f.pixels.fill(static_cast<uint8_t>(f.id & 0xFF));
return f;
}
static Frame process_frame(Frame f) {
// Simulate some work
for (auto& p : f.pixels) p = static_cast<uint8_t>(255 - p);
return f;
}
static void consume_frame(Frame f) {
std::cout << "[frame] id=" << f.id
<< " first_px=" << static_cast<int>(f.pixels[0])
<< " storage=shared_ptr (sizeof Frame = " << sizeof(Frame) << " B)\n";
}
static Tag produce_tag() {
std::this_thread::sleep_for(std::chrono::milliseconds(40));
return {tag_id++};
}
static Tag double_tag(Tag t) { return {t.value * 2}; }
static void print_tag(Tag t) {
std::cout << "[tag] value=" << t.value
<< " storage=by_value (sizeof Tag = " << sizeof(Tag) << " B)\n";
}
// ── main ──────────────────────────────────────────────────────────────────────
int main() {
using namespace kpn;
// Verify storage decisions at compile time
static_assert(!channel_storage_policy<Frame>::by_value,
"Frame should use shared_ptr storage");
static_assert(channel_storage_policy<Tag>::by_value,
"Tag should use by_value storage (via specialisation)");
static_assert(channel_storage_policy<int>::by_value,
"int should use by_value storage");
auto prod_f = make_node<produce_frame> (out<"frame">{}, 4);
auto proc_f = make_node<process_frame> (in<"frame">{}, out<"frame">{}, 4);
auto cons_f = make_node<consume_frame> (in<"frame">{}, 4);
auto prod_t = make_node<produce_tag> (out<"tag">{}, 4);
auto dbl_t = make_node<double_tag> (in<"tag">{}, out<"tag">{}, 4);
auto print_t = make_node<print_tag> (in<"tag">{}, 4);
Network net;
net.add("prod_f", prod_f)
.add("proc_f", proc_f)
.add("cons_f", cons_f)
.add("prod_t", prod_t)
.add("dbl_t", dbl_t)
.add("print_t", print_t)
.connect("prod_f", prod_f.template output<"frame">(), "proc_f", proc_f.template input<"frame">())
.connect("proc_f", proc_f.template output<"frame">(), "cons_f", cons_f.template input<"frame">())
.connect("prod_t", prod_t.template output<"tag">(), "dbl_t", dbl_t.template input<"tag">())
.connect("dbl_t", dbl_t.template output<"tag">(), "print_t", print_t.template input<"tag">())
.build();
net.start();
std::this_thread::sleep_for(std::chrono::milliseconds(400));
net.stop();
}