sdbus++: async: server: generate method-call fn

Add binding generation for method-call functions.

Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: I34ac44ae0cdb43a18b96a4ddc5e67df4971da939
diff --git a/example/calculator-aserver.cpp b/example/calculator-aserver.cpp
index 149da86..6dc8579 100644
--- a/example/calculator-aserver.cpp
+++ b/example/calculator-aserver.cpp
@@ -28,6 +28,27 @@
         return false;
     }
 
+    auto method_call(multiply_t, auto x, auto y)
+    {
+        auto r = x * y;
+        last_result(r);
+        return r;
+    }
+
+    auto method_call(divide_t, auto x, auto y)
+        -> sdbusplus::async::task<divide_t::return_type>
+    {
+        auto r = x / y;
+        last_result(r);
+        co_return r;
+    }
+
+    auto method_call(clear_t) -> sdbusplus::async::task<>
+    {
+        last_result(0);
+        co_return;
+    }
+
   private:
     auto startup() -> sdbusplus::async::task<>
     {
@@ -36,7 +57,7 @@
 
         status(State::Error);
 
-        while (1)
+        while (!ctx.stop_requested())
         {
             using namespace std::literals;
             co_await sdbusplus::async::sleep_for(ctx, 10s);
diff --git a/include/sdbusplus/async/server.hpp b/include/sdbusplus/async/server.hpp
index 9710d5f..43d023b 100644
--- a/include/sdbusplus/async/server.hpp
+++ b/include/sdbusplus/async/server.hpp
@@ -93,6 +93,24 @@
 concept has_set_property = has_set_property_nomsg<Tag, Instance, Arg> ||
                            has_set_property_msg<Tag, Instance, Arg>;
 
+/* Determine if a type has a method call. */
+template <typename Tag, typename Instance, typename... Args>
+concept has_method_nomsg = requires(Instance& i, Args&&... a) {
+                               i.method_call(Tag{}, std::forward<Args>(a)...);
+                           };
+
+/* Determine if a type has a method call that requires a msg. */
+template <typename Tag, typename Instance, typename... Args>
+concept has_method_msg =
+    requires(Instance& i, sdbusplus::message_t& m, Args&&... a) {
+        i.method_call(Tag{}, m, std::forward<Args>(a)...);
+    };
+
+/* Determine if a type has any method call. */
+template <typename Tag, typename Instance, typename... Args>
+concept has_method = has_method_nomsg<Tag, Instance, Args...> ||
+                     has_method_msg<Tag, Instance, Args...>;
+
 } // namespace server::details
 
 } // namespace sdbusplus::async
diff --git a/tools/meson.build b/tools/meson.build
index 1538289..d290b5f 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -19,8 +19,12 @@
   'sdbusplus/templates/interface.md.mako',
   'sdbusplus/templates/interface.server.cpp.mako',
   'sdbusplus/templates/interface.server.hpp.mako',
-  'sdbusplus/templates/method.md.mako',
+  'sdbusplus/templates/method.aserver.callback.hpp.mako',
+  'sdbusplus/templates/method.aserver.tag.hpp.mako',
+  'sdbusplus/templates/method.aserver.typeid.hpp.mako',
+  'sdbusplus/templates/method.aserver.vtable.hpp.mako',
   'sdbusplus/templates/method.client.hpp.mako',
+  'sdbusplus/templates/method.md.mako',
   'sdbusplus/templates/method.prototype.hpp.mako',
   'sdbusplus/templates/method.server.vtable.cpp.mako',
   'sdbusplus/templates/property.md.mako',
diff --git a/tools/sdbusplus/templates/interface.aserver.hpp.mako b/tools/sdbusplus/templates/interface.aserver.hpp.mako
index f94ba3b..0c10273 100644
--- a/tools/sdbusplus/templates/interface.aserver.hpp.mako
+++ b/tools/sdbusplus/templates/interface.aserver.hpp.mako
@@ -73,6 +73,11 @@
 ${p.render(loader, "property.aserver.tag.hpp.mako", property=p, interface=interface)}\
 % endfor
 
+    /* Method tags. */
+% for m in interface.methods:
+${m.render(loader, "method.aserver.tag.hpp.mako", method=m, interface=interface)}\
+% endfor
+
 % for p in interface.properties:
 ${p.render(loader, "property.aserver.get.hpp.mako", property=p, interface=interface)}
 % endfor
@@ -98,6 +103,9 @@
 % for p in interface.properties:
 ${p.render(loader, "property.aserver.typeid.hpp.mako", property=p, interface=interface)}\
 % endfor
+% for m in interface.methods:
+${m.render(loader, "method.aserver.typeid.hpp.mako", method=m, interface=interface)}\
+% endfor
 % for s in interface.signals:
 ${s.render(loader, "signal.aserver.typeid.hpp.mako", signal=s, interface=interface)}\
 % endfor
@@ -106,12 +114,19 @@
 ${p.render(loader, "property.aserver.callback.hpp.mako", property=p, interface=interface)}
 % endfor
 
+% for m in interface.methods:
+${m.render(loader, "method.aserver.callback.hpp.mako", method=m, interface=interface)}\
+% endfor
+
     static constexpr sdbusplus::vtable_t _vtable[] = {
         vtable::start(),
 
 % for p in interface.properties:
 ${p.render(loader, "property.aserver.vtable.hpp.mako", property=p, interface=interface)}\
 % endfor
+% for m in interface.methods:
+${m.render(loader, "method.aserver.vtable.hpp.mako", method=m, interface=interface)}\
+% endfor
 % for s in interface.signals:
 ${s.render(loader, "signal.aserver.vtable.hpp.mako", signal=s, interface=interface)}\
 % endfor
diff --git a/tools/sdbusplus/templates/method.aserver.callback.hpp.mako b/tools/sdbusplus/templates/method.aserver.callback.hpp.mako
new file mode 100644
index 0000000..d2493d0
--- /dev/null
+++ b/tools/sdbusplus/templates/method.aserver.callback.hpp.mako
@@ -0,0 +1,243 @@
+<%
+m_name = method.snake_case
+m_tag = method.snake_case + "_t"
+m_param = method.parameters_as_list()
+m_pmove = method.parameters_as_list(lambda p: f"std::move({p.camelCase})")
+m_pargs = method.parameters_as_list(lambda p: method.parameter(interface, p))
+m_ptypes = method.parameter_types_as_list(interface)
+m_return = method.cpp_return_type(interface)
+i_name = interface.classname
+m_param_count = len(method.parameters)
+m_return_count = len(method.returns)
+%>\
+    static int _callback_m_${m_name}(sd_bus_message* msg, void* context,
+                                     sd_bus_error* error [[maybe_unused]])
+        requires (server_details::has_method<
+                            ${m_tag}, Instance\
+% if m_param_count:
+, ${m_ptypes}\
+% endif
+>)
+    {
+        auto self = static_cast<${i_name}*>(context);
+        auto self_i = static_cast<Instance*>(self);
+
+        try
+        {
+            auto m = sdbusplus::message_t{msg};
+% if m_param_count:
+            auto [${m_param}] = m.unpack<${m_ptypes}>();
+% endif
+
+            constexpr auto has_method_msg =
+                server_details::has_method_msg<
+                    ${m_tag}, Instance\
+% if m_param_count:
+, ${m_ptypes}\
+% endif
+>;
+
+            if constexpr (has_method_msg)
+            {
+                constexpr auto is_async = std::is_same_v<
+                    sdbusplus::async::task<${m_return}>,
+                    decltype(self_i->method_call(${m_tag}{}, m\
+% if m_param_count:
+,
+                                ${m_pmove}\
+%endif
+))>;
+
+                if constexpr (!is_async)
+                {
+                    auto r = m.new_method_return();
+% if m_return_count == 0:
+                    \
+% elif m_return_count == 1:
+                    r.append(\
+% else:
+                    std::apply(
+                        [&](auto&&... v) { (r.append(std::move(v)), ...); },
+                        \
+% endif
+self_i->method_call(${m_tag}{}, m\
+% if m_param_count:
+,
+                            ${m_pmove}\
+%endif
+% if m_return_count != 0:
+)\
+% endif
+);
+                    r.method_return();
+                }
+                else
+                {
+                    auto fn = [](auto self, auto self_i,
+                                 sdbusplus::message_t m\
+% if m_param_count:
+,
+                                 ${m_pargs}\
+% endif
+)
+                            -> sdbusplus::async::task<>
+                    {
+                        try
+                        {
+
+                            auto r = m.new_method_return();
+% if m_return_count == 0:
+                            \
+% elif m_return_count == 1:
+                            r.append(\
+% else:
+                            std::apply(
+                                [&](auto&&... v) { (r.append(std::move(v)), ...); },
+                                \
+% endif
+co_await self_i->method_call(
+                                ${m_tag}{}, m\
+% if m_param_count:
+, ${m_pmove}\
+% endif
+% if m_return_count != 0:
+)\
+% endif
+);
+
+                            r.method_return();
+                            co_return;
+                        }
+% for e in method.errors:
+                        catch(const ${interface.errorNamespacedClass(e)}& e)
+                        {
+                            m.new_method_error(e).method_return();
+                            co_return;
+                        }
+                        % endfor
+                        catch(const std::exception&)
+                        {
+                            self->_context().get_bus().set_current_exception(
+                                std::current_exception());
+                            co_return;
+                        }
+                    };
+
+                    self->_context().spawn(
+                        std::move(fn(self, self_i, m\
+% if m_param_count:
+, ${m_pmove}\
+% endif
+)));
+                }
+            }
+            else
+            {
+                constexpr auto is_async [[maybe_unused]] = std::is_same_v<
+                    sdbusplus::async::task<${m_return}>,
+                    decltype(self_i->method_call(${m_tag}{}\
+% if m_param_count:
+,
+                                ${m_pmove}\
+% endif:
+))>;
+
+                if constexpr (!is_async)
+                {
+                    auto r = m.new_method_return();
+% if m_return_count == 0:
+                    \
+% elif m_return_count == 1:
+                    r.append(\
+% else:
+                    std::apply(
+                        [&](auto&&... v) { (r.append(std::move(v)), ...); },
+                        \
+% endif
+self_i->method_call(${m_tag}{}\
+% if m_param_count:
+,
+                            ${m_pmove}\
+%endif
+% if m_return_count != 0:
+)\
+% endif
+);
+                    r.method_return();
+                }
+                else
+                {
+                    auto fn = [](auto self, auto self_i,
+                                 sdbusplus::message_t m\
+% if m_param_count:
+,
+                                 ${m_pargs}\
+% endif
+)
+                            -> sdbusplus::async::task<>
+                    {
+                        try
+                        {
+
+                            auto r = m.new_method_return();
+% if m_return_count == 0:
+                            \
+% elif m_return_count == 1:
+                            r.append(\
+% else:
+                            std::apply(
+                                [&](auto&&... v) { (r.append(std::move(v)), ...); },
+                                \
+% endif
+co_await self_i->method_call(
+                                ${m_tag}{}\
+% if m_param_count:
+, ${m_pmove}\
+% endif
+% if m_return_count != 0:
+)\
+% endif
+);
+
+                            r.method_return();
+                            co_return;
+                        }
+% for e in method.errors:
+                        catch(const ${interface.errorNamespacedClass(e)}& e)
+                        {
+                            m.new_method_error(e).method_return();
+                            co_return;
+                        }
+                        % endfor
+                        catch(const std::exception&)
+                        {
+                            self->_context().get_bus().set_current_exception(
+                                std::current_exception());
+                            co_return;
+                        }
+                    };
+
+                    self->_context().spawn(
+                        std::move(fn(self, self_i, m\
+% if m_param_count:
+, ${m_pmove}\
+% endif
+)));
+                }
+            }
+        }
+% for e in method.errors:
+        catch(const ${interface.errorNamespacedClass(e)}& e)
+        {
+            return sd_bus_error_set(error, e.name(), e.description());
+        }
+% endfor
+        catch(const std::exception&)
+        {
+            self->_context().get_bus().set_current_exception(
+                std::current_exception());
+            return -EINVAL;
+        }
+
+        return 1;
+    }
diff --git a/tools/sdbusplus/templates/method.aserver.tag.hpp.mako b/tools/sdbusplus/templates/method.aserver.tag.hpp.mako
new file mode 100644
index 0000000..7976b28
--- /dev/null
+++ b/tools/sdbusplus/templates/method.aserver.tag.hpp.mako
@@ -0,0 +1,10 @@
+<%
+m_tag = method.snake_case + "_t"
+m_param = method.parameter_types_as_list(interface)
+m_return = method.cpp_return_type(interface)
+%>\
+    struct ${m_tag}
+    {
+        using value_types = std::tuple<${m_param}>;
+        using return_type = ${m_return};
+    };
diff --git a/tools/sdbusplus/templates/method.aserver.typeid.hpp.mako b/tools/sdbusplus/templates/method.aserver.typeid.hpp.mako
new file mode 100644
index 0000000..b9b360d
--- /dev/null
+++ b/tools/sdbusplus/templates/method.aserver.typeid.hpp.mako
@@ -0,0 +1,17 @@
+    static constexpr auto _method_typeid_p_${method.snake_case} =
+        utility::tuple_to_array(\
+% if len(method.parameters) == 0:
+std::make_tuple('\0')\
+% else:
+message::types::type_id<${method.parameter_types_as_list(interface)}>()\
+% endif
+);
+
+    static constexpr auto _method_typeid_r_${method.snake_case} =
+        utility::tuple_to_array(\
+% if len(method.returns) == 0:
+std::make_tuple('\0')\
+% else:
+message::types::type_id<${method.returns_as_list(interface)}>()\
+% endif
+);
diff --git a/tools/sdbusplus/templates/method.aserver.vtable.hpp.mako b/tools/sdbusplus/templates/method.aserver.vtable.hpp.mako
new file mode 100644
index 0000000..e511e06
--- /dev/null
+++ b/tools/sdbusplus/templates/method.aserver.vtable.hpp.mako
@@ -0,0 +1,9 @@
+        vtable::method("${method.name}",
+                       _method_typeid_p_${method.snake_case}.data(),
+                       _method_typeid_r_${method.snake_case}.data(),
+                       _callback_m_${method.snake_case}\
+% if method.cpp_flags:
+,
+                       ${method.cpp_flags}\
+% endif
+),