// 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 // — 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 #include #include #include #include #include #include // ── Types ───────────────────────────────────────────────────────────────────── // Large frame type — will be stored as shared_ptr by default struct Frame { std::array 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 { 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(f.id & 0xFF)); return f; } static Frame process_frame(Frame f) { // Simulate some work for (auto& p : f.pixels) p = static_cast(255 - p); return f; } static void consume_frame(Frame f) { std::cout << "[frame] id=" << f.id << " first_px=" << static_cast(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::by_value, "Frame should use shared_ptr storage"); static_assert(channel_storage_policy::by_value, "Tag should use by_value storage (via specialisation)"); static_assert(channel_storage_policy::by_value, "int should use by_value storage"); auto prod_f = make_node (out<"frame">{}, 4); auto proc_f = make_node (in<"frame">{}, out<"frame">{}, 4); auto cons_f = make_node (in<"frame">{}, 4); auto prod_t = make_node (out<"tag">{}, 4); auto dbl_t = make_node (in<"tag">{}, out<"tag">{}, 4); auto print_t = make_node (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(); }