#pragma once // Auto-binding helpers for KPN++ Python bindings. // // Usage in your binding .cpp: // // #define KPN_BUILD_PYTHON // #include // // int produce() { return 42; } // int double_it(int x) { return x * 2; } // void print_it(int x) { std::cout << x << '\n'; } // // using MyNodes = kpn::python::NodeRegistry< // kpn::python::Entry, // kpn::python::Entry, // kpn::python::Entry // >; // // NB_MODULE(my_kpn, m) { // kpn::python::bind_network(m); // KPN_BIND_PYTHON behaviour // kpn::python::bind_debug(m); // KPN_PYTHON_DEBUG behaviour // } // // bind_network registers: // - Network class (PyNetwork) with auto-registered converters // - make_(capacity=5) factory for each entry // - Node class for each entry // // bind_debug additionally registers each raw C++ function as a free Python // callable (e.g. double_it(5) → 10) so node logic can be tested without a network. // // To support a custom type T, specialise kpn::PythonConverter before calling // bind_network: // // namespace kpn { // template<> struct PythonConverter { // static constexpr const char* type_name = "vec3"; // optional friendly name // static nb::object to_python(const MyVec3& v) { ... } // static MyVec3 from_python(nb::object o) { ... } // }; // } // namespace kpn #include "../variant_node.hpp" #include "../traits.hpp" #include "bindings.hpp" #ifdef KPN_BUILD_PYTHON #include #include #include #include #include #include #include #include // ── PythonConverter specialisations for built-in nanobind-castable types ───── // These live in kpn:: to match the primary template in variant_node.hpp. namespace kpn { template<> struct PythonConverter { static constexpr const char* type_name = "int"; static nanobind::object to_python(const int& v) { return nanobind::cast(v); } static int from_python(nanobind::object o) { return nanobind::cast(std::move(o)); } }; template<> struct PythonConverter { static constexpr const char* type_name = "float"; static nanobind::object to_python(const float& v) { return nanobind::cast(v); } static float from_python(nanobind::object o) { return nanobind::cast(std::move(o)); } }; template<> struct PythonConverter { static constexpr const char* type_name = "double"; static nanobind::object to_python(const double& v) { return nanobind::cast(v); } static double from_python(nanobind::object o) { return nanobind::cast(std::move(o)); } }; template<> struct PythonConverter { static constexpr const char* type_name = "bool"; static nanobind::object to_python(const bool& v) { return nanobind::cast(v); } static bool from_python(nanobind::object o) { return nanobind::cast(std::move(o)); } }; template<> struct PythonConverter { static constexpr const char* type_name = "str"; static nanobind::object to_python(const std::string& v) { return nanobind::cast(v); } static std::string from_python(nanobind::object o) { return nanobind::cast(std::move(o)); } }; } // namespace kpn namespace kpn::python { namespace nb = nanobind; // ── Entry ───────────────────────────────────────────────────────── // Compile-time descriptor for one bindable node function. template struct Entry { static constexpr auto func = Func; static constexpr auto name = Name; }; // ── NodeRegistry ─────────────────────────────────────────────────────── template struct NodeRegistry { using entries_tuple = std::tuple; static constexpr std::size_t size = sizeof...(Es); }; // ── Internal TMP ────────────────────────────────────────────────────────────── namespace detail { // All non-void port types for a single function (args + normalised returns). template struct entry_port_types { using args = args_t; using ret = normalised_return_t>; using type = decltype(std::tuple_cat(std::declval(), std::declval())); }; // Flat tuple of all types across all entries (may contain duplicates). template struct all_types_flat { using type = decltype(std::tuple_cat( std::declval::type>()...)); }; template struct registry_flat_types; template struct registry_flat_types> { using type = typename all_types_flat::type; }; // Unpack a tuple into unique_types_t (which takes a pack, not a tuple). // unique_types_t takes Ts... not std::tuple, so we need this bridge. template struct unpack_unique; template struct unpack_unique> { using type = kpn::detail::unique_types_t; }; // SFINAE: does PythonConverter have a 'type_name' member? template struct has_type_name : std::false_type {}; template struct has_type_name> : std::true_type {}; inline std::string make_class_name(std::string_view snake) { std::string result(snake); if (!result.empty()) result[0] = static_cast(std::toupper(result[0])); result += "Node"; return result; } } // namespace detail // ── registry_variant_t ───────────────────────────────────────────── // Deduces std::variant from all port types across the registry. template using registry_variant_t = typename kpn::detail::tuple_to_variant< typename detail::unpack_unique< typename detail::registry_flat_types::type>::type >::type; // ── Converter registration ──────────────────────────────────────────────────── template void register_one_type(PyNetwork& net) { const char* friendly = nullptr; if constexpr (detail::has_type_name>::value) friendly = PythonConverter::type_name; net.template register_full_type( [](const T& v) -> nb::object { return PythonConverter::to_python(v); }, [](nb::object o) -> T { return PythonConverter::from_python(std::move(o)); }, friendly); } template void register_types_impl(PyNetwork& net, std::tuple*) { (register_one_type(net), ...); } template void register_all_converters(PyNetwork& net) { using Flat = typename detail::registry_flat_types::type; using Unique = typename detail::unpack_unique::type; register_types_impl(net, static_cast(nullptr)); } // ── Per-entry class + factory registration ─────────────────────────────────── namespace detail { template void register_one_entry(nb::module_& m) { using Wrapper = VariantNodeWrapper; auto class_name = make_class_name(E::name.view()); auto make_name = "make_" + std::string(E::name.view()); nb::class_>(m, class_name.c_str()) .def("__init__", [](Wrapper* self, std::size_t cap) { new (self) Wrapper(cap); }, nb::arg("capacity") = 5); m.def(make_name.c_str(), [](std::size_t cap) -> std::shared_ptr> { return std::make_shared(cap); }, nb::arg("capacity") = 5); } template void register_entries_impl(nb::module_& m, std::tuple*) { (register_one_entry(m), ...); } } // namespace detail // ── bind_network ──────────────────────────────────────────────────── // Registers: // - INode — base class (opaque Python handle) // - Network — PyNetwork with auto-registered converters // - Node — VariantNodeWrapper for each entry // - make_() — factory returning shared_ptr template void bind_network(nb::module_& m) { using Variant = registry_variant_t; using Net = PyNetwork; using Entries = typename Registry::entries_tuple; nb::class_>(m, "INode"); nb::class_(m, "Network") .def("__init__", [](Net* self) { new (self) Net(); register_all_converters(*self); }) // add(name, c++_node) .def("add", [](Net& self, std::string name, std::shared_ptr> node) { self.add(std::move(name), std::move(node)); }, nb::arg("name"), nb::arg("node")) // add_node(name, callable, inputs=[...], outputs=[...], capacity=5) .def("add_node", &Net::add_node_python, nb::arg("name"), nb::arg("callable"), nb::arg("inputs") = std::vector{}, nb::arg("outputs") = std::vector{}, nb::arg("capacity") = std::size_t(5)) .def("connect", &Net::connect, nb::arg("src"), nb::arg("out_idx"), nb::arg("dst"), nb::arg("in_idx")) .def("build", &Net::build) .def("start", &Net::start) .def("stop", &Net::stop) .def("read", &Net::read, nb::arg("node"), nb::arg("out_idx") = std::size_t(0)) .def("write", &Net::write, nb::arg("node"), nb::arg("in_idx"), nb::arg("value")) ; detail::register_entries_impl(m, static_cast(nullptr)); } // ── bind_debug ───────────────────────────────────────────────────── // Exposes each node's raw C++ function as a free Python callable so node logic // can be unit-tested without constructing a network. // // Example: assert kpn.double_it(5) == 10 namespace detail { template void bind_one_debug(nb::module_& m) { auto name_str = std::string(E::name.view()); m.def(name_str.c_str(), E::func); } template void bind_debug_impl(nb::module_& m, std::tuple*) { (bind_one_debug(m), ...); } } // namespace detail template void bind_debug(nb::module_& m) { using Entries = typename Registry::entries_tuple; detail::bind_debug_impl(m, static_cast(nullptr)); } } // namespace kpn::python #endif // KPN_BUILD_PYTHON