sdbus++: async: server: generate signal emit fns

Populate enough of the generator to define the basic class structure
and generate bindings for emitting signals.

Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: If62c323f460fc8c73a3aca495e5b89cd84bab32a
diff --git a/example/calculator-aserver.cpp b/example/calculator-aserver.cpp
new file mode 100644
index 0000000..220cda1
--- /dev/null
+++ b/example/calculator-aserver.cpp
@@ -0,0 +1,42 @@
+#include <net/poettering/Calculator/aserver.hpp>
+#include <sdbusplus/async.hpp>
+
+class Calculator :
+    public sdbusplus::aserver::net::poettering::Calculator<Calculator>
+{
+  public:
+    explicit Calculator(sdbusplus::async::context& ctx) :
+        sdbusplus::aserver::net::poettering::Calculator<Calculator>(
+            ctx, "/net/poettering/calculator"),
+        manager(ctx, "/")
+    {
+        ctx.spawn(startup());
+    }
+
+  private:
+    auto startup() -> sdbusplus::async::task<>
+    {
+        ctx.get_bus().request_name("net.poettering.Calculator");
+
+        while (1)
+        {
+            using namespace std::literals;
+            co_await sdbusplus::async::sleep_for(ctx, 10s);
+
+            cleared(42);
+        }
+        co_return;
+    }
+
+    sdbusplus::server::manager_t manager;
+};
+
+int main()
+{
+    sdbusplus::async::context ctx;
+    [[maybe_unused]] Calculator c(ctx);
+
+    ctx.run();
+
+    return 0;
+}
diff --git a/example/meson.build b/example/meson.build
index 75b265c..c7ebf61 100644
--- a/example/meson.build
+++ b/example/meson.build
@@ -65,6 +65,15 @@
 )
 
 executable(
+    'calculator-aserver',
+    'calculator-aserver.cpp',
+    generated_sources,
+    implicit_include_directories: false,
+    include_directories: include_directories('gen'),
+    dependencies: sdbusplus_dep,
+)
+
+executable(
     'calculator-client',
     'calculator-client.cpp',
     generated_sources,
diff --git a/include/sdbusplus/async/server.hpp b/include/sdbusplus/async/server.hpp
new file mode 100644
index 0000000..b7dfbdc
--- /dev/null
+++ b/include/sdbusplus/async/server.hpp
@@ -0,0 +1,55 @@
+#pragma once
+
+#include <sdbusplus/async/context.hpp>
+#include <sdbusplus/server/manager.hpp>
+#include <sdbusplus/vtable.hpp>
+
+namespace sdbusplus::async
+{
+
+namespace server
+{
+
+namespace details
+{
+struct server_context_friend;
+}
+
+template <typename Instance, template <typename, typename> typename... Types>
+class server :
+    public sdbusplus::async::context_ref,
+    public Types<Instance, server<Instance, Types...>>...
+{
+  public:
+    using Self = server<Instance, Types...>;
+    friend details::server_context_friend;
+
+    server() = delete;
+    explicit server(sdbusplus::async::context& ctx, const char* path) :
+        context_ref(ctx), Types<Instance, Self>(path)...
+    {}
+};
+
+} // namespace server
+
+template <typename Instance, template <typename, typename> typename... Types>
+using server_t = server::server<Instance, Types...>;
+
+namespace server::details
+{
+/* Indirect so that the generated Types can access the server_t's context.
+ *
+ * If P2893 gets into C++26 we could eliminate this because we can set all
+ * the Types as friends directly.
+ */
+struct server_context_friend
+{
+    template <typename T>
+    sdbusplus::async::context& context()
+    {
+        return static_cast<T*>(this)->ctx;
+    }
+};
+} // namespace server::details
+
+} // namespace sdbusplus::async
diff --git a/tools/meson.build b/tools/meson.build
index 8570394..8e1e631 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -28,6 +28,9 @@
   'sdbusplus/templates/property.server.cpp.mako',
   'sdbusplus/templates/property.server.vtable.cpp.mako',
   'sdbusplus/templates/signal.md.mako',
+  'sdbusplus/templates/signal.aserver.emit.hpp.mako',
+  'sdbusplus/templates/signal.aserver.typeid.hpp.mako',
+  'sdbusplus/templates/signal.aserver.vtable.hpp.mako',
   'sdbusplus/templates/signal.prototype.hpp.mako',
   'sdbusplus/templates/signal.server.vtable.cpp.mako',
 )
diff --git a/tools/sdbusplus/templates/interface.aserver.hpp.mako b/tools/sdbusplus/templates/interface.aserver.hpp.mako
index e69de29..1ec817a 100644
--- a/tools/sdbusplus/templates/interface.aserver.hpp.mako
+++ b/tools/sdbusplus/templates/interface.aserver.hpp.mako
@@ -0,0 +1,95 @@
+#pragma once
+#include <sdbusplus/async/server.hpp>
+#include <sdbusplus/server/interface.hpp>
+
+#include <type_traits>
+
+% for h in interface.cpp_includes():
+#include <${h}>
+% endfor
+#include <${interface.headerFile()}>
+
+namespace sdbusplus::aserver::${interface.cppNamespace()}
+{
+
+namespace details
+{
+// forward declaration
+template <typename Instance, typename Server>
+class ${interface.classname};
+} // namespace details
+
+template <typename Instance, typename Server = void>
+struct ${interface.classname} :
+    public std::conditional_t<
+        std::is_void_v<Server>,
+        sdbusplus::async::server_t<Instance, details::${interface.classname}>,
+        details::${interface.classname}<Instance, Server>>
+{
+    template <typename... Args>
+    ${interface.classname}(Args&&... args) :
+        std::conditional_t<
+            std::is_void_v<Server>,
+            sdbusplus::async::server_t<Instance, details::${interface.classname}>,
+            details::${interface.classname}<Instance, Server>>(std::forward<Args>(args)...)
+    {}
+};
+
+namespace details
+{
+
+template <typename Instance, typename Server>
+class ${interface.classname} :
+    public sdbusplus::common::${interface.cppNamespacedClass()},
+    protected sdbusplus::async::server::details::server_context_friend
+{
+  public:
+    explicit ${interface.classname}(const char* path) :
+        _${interface.joinedName("_", "interface")}(
+            _context(), path, interface, _vtable, this)
+    {}
+
+% for s in interface.signals:
+${s.render(loader, "signal.aserver.emit.hpp.mako", signal=s, interface=interface)}
+% endfor
+
+    /** @brief Emit interface added */
+    void emit_added()
+    {
+        _${interface.joinedName("_", "interface")}.emit_added();
+    }
+
+    /** @brief Emit interface removed */
+    void emit_removed()
+    {
+        _${interface.joinedName("_", "interface")}.emit_removed();
+    }
+
+  private:
+    /** @return the async context */
+    sdbusplus::async::context& _context()
+    {
+        return sdbusplus::async::server::details::server_context_friend::
+            context<Server>();
+    }
+
+    sdbusplus::server::interface_t
+        _${interface.joinedName("_", "interface")};
+
+% for s in interface.signals:
+${s.render(loader, "signal.aserver.typeid.hpp.mako", signal=s, interface=interface)}\
+% endfor
+
+    static constexpr sdbusplus::vtable_t _vtable[] = {
+        vtable::start(),
+
+% for s in interface.signals:
+${s.render(loader, "signal.aserver.vtable.hpp.mako", signal=s, interface=interface)}\
+% endfor
+
+        vtable::end(),
+    };
+};
+
+} // namespace details
+} // namespace sdbusplus::aserver::${interface.cppNamespace()}
diff --git a/tools/sdbusplus/templates/signal.aserver.emit.hpp.mako b/tools/sdbusplus/templates/signal.aserver.emit.hpp.mako
new file mode 100644
index 0000000..797c428
--- /dev/null
+++ b/tools/sdbusplus/templates/signal.aserver.emit.hpp.mako
@@ -0,0 +1,39 @@
+<%
+    def parameters():
+        return ",\n            ".\
+            join([ parameter(p) for p in signal.properties ])
+
+    def parameter(p):
+        r = "%s %s%s" % \
+            (p.cppTypeParam(interface.name), p.camelCase, default_value(p))
+        return r
+
+    def parameters_as_list():
+        return ", ".join([ p.camelCase for p in signal.properties ])
+
+    def default_value(p):
+        if p.defaultValue != None:
+            return " = " + str(p.defaultValue)
+        else:
+            return ""
+%>\
+    /** @brief Send signal '${signal.name}'
+     *
+     *  ${ signal.description.strip() }
+% if len(signal.properties) != 0:
+     *
+    % for p in signal.properties:
+     *  @param[in] ${p.camelCase} - ${p.description.strip()}
+    % endfor
+% endif
+     *
+     */
+    void ${signal.camelCase}(${parameters()})
+    {
+        auto m = _${interface.joinedName("_", "interface")}
+                     .new_signal(\
+"${signal.name}");
+
+        m.append(${parameters_as_list()});
+        m.signal_send();
+    }
diff --git a/tools/sdbusplus/templates/signal.aserver.typeid.hpp.mako b/tools/sdbusplus/templates/signal.aserver.typeid.hpp.mako
new file mode 100644
index 0000000..4f24175
--- /dev/null
+++ b/tools/sdbusplus/templates/signal.aserver.typeid.hpp.mako
@@ -0,0 +1,3 @@
+    static constexpr auto _signal_typeid_${signal.snake_case} =
+        utility::tuple_to_array(message::types::type_id<\
+${ ", ".join( [ p.cppTypeParam(interface.name, full=True) for p in signal.properties ]) }>());
diff --git a/tools/sdbusplus/templates/signal.aserver.vtable.hpp.mako b/tools/sdbusplus/templates/signal.aserver.vtable.hpp.mako
new file mode 100644
index 0000000..f17b10b
--- /dev/null
+++ b/tools/sdbusplus/templates/signal.aserver.vtable.hpp.mako
@@ -0,0 +1,3 @@
+        vtable::signal(
+            "${signal.name}",
+            _signal_typeid_${signal.snake_case}.data()),