Message bus example

#pragma once

/* SPDX-License-Identifier: GPL-3.0-or-later */

#include <avnd/concepts/painter.hpp>
#include <avnd/concepts/processor.hpp>
#include <avnd/wrappers/controls.hpp>
#include <cmath>
#include <halp/audio.hpp>
#include <halp/controls.hpp>
#include <halp/custom_widgets.hpp>
#include <halp/layout.hpp>
#include <halp/meta.hpp>

#include <cstdio>
#include <variant>

namespace examples::helpers
{
struct custom_button
{
  static constexpr double width() { return 100.; }
  static constexpr double height() { return 100.; }

  void paint(avnd::painter auto ctx)
  {
    ctx.set_stroke_color({200, 200, 200, 255});
    ctx.set_stroke_width(2.);
    ctx.set_fill_color({100, 100, 100, 255});
    ctx.begin_path();
    ctx.draw_rounded_rect(0., 0., width(), height(), 5);
    ctx.fill();
    ctx.stroke();

    ctx.set_fill_color({0, 0, 0, 255});
    ctx.begin_path();
    ctx.draw_text(20., 20., fmt::format("{}", press_count));
    ctx.fill();
  }

  bool mouse_press(double x, double y)
  {
    on_pressed();
    return true;
  }

  void mouse_move(double x, double y) { }

  void mouse_release(double x, double y) { }

  int press_count{0};

  std::function<void()> on_pressed = [] {};
};

/**
 * This example shows how communication through a thread-safe message
 * bus between the UI and the engine can be achieved.
 *
 * The data flow in the example does a complete round-trip
 * from the UI to the engine and back to the UI again:
 *
 * - When the button is clicked in the UI, it calls custom_button::on_pressed
 * - In the ui::bus::init this generates a message from this, sent through
 *   ui::bus::send_message.
 * - Behind the scene, the bindings serialize the message and ask the host to kindly
 *   pass it to the engine thread.
 * - Engine thread receives the deserialized message in MessageBusUi::process_message
 * - Engine thread sends a feedback to the ui through MessageBusUi::send_message
 * - Bindings transfer it back to the ui thread, ui::bus::process_message(ui, the_message)
 *   gets called
 *
 * To implement multiple messages one can simply use std::variant as the argument type:
 *
 *   std::variant<message1, message2, etc...>
 *
 * Note that you don't have to implement serialization manually:
 * as long as the messages are aggregates, things happen magically :-)
 *
 */
struct MessageBusUi
{
  static consteval auto name() { return "MessageBusUi example"; }
  static consteval auto c_name() { return "avnd_mbus_ui"; }
  static consteval auto uuid() { return "4ed8e7fd-a1fa-40a7-bbbe-13ee50044248"; }

  struct
  {
  } inputs;
  struct
  {
  } outputs;

  void operator()(int N) { }

  // Here are some message types. Their type names do not matter, only that they are
  // aggregates. What matters is that they are used as arguments to process_message.

  // This one will be serialized / deserialized as it is not a trivial type
  struct ui_to_processor
  {
    int foo;
    std::string bar;
    std::variant<float, double> x;
    std::vector<float> y;
    struct
    {
      int x, y;
    } baz;
  };

  // This one will be memcpy'd as it is a trivial type
  struct processor_to_ui
  {
    float bar;
    struct
    {
      int x, y;
    } baz;
  };

  // 1. Receive a message on the processing thread from the UI
  void process_message(const ui_to_processor& msg)
  {
    fprintf(stderr, "Got message in processing thread !\n");
    send_message(processor_to_ui{.bar = 1.0, .baz{3, 4}});
  }

  // 2. Send a message from the processing thread to the ui
  std::function<void(processor_to_ui)> send_message;

  // Define our UI
  struct ui
  {
    halp_meta(layout, halp::layouts::container)
    halp_meta(width, 100)
    halp_meta(height, 100)

    struct
    {
      halp_meta(layout, halp::layouts::container)
      halp::custom_actions_item<custom_button> button{
          .x = 10, .y = 10
          // We'd like to define our callback here,
          // sadly C++ scoping rules do not allow it as soon as there is nesting
      };
    } foo;

    // Define the communication between UI and processor.
    struct bus
    {
      // 3. Set up connections
      void init(ui& ui)
      {
        ui.foo.button.on_pressed = [&] {
          fprintf(stderr, "Sending message from UI thread !\n");
          this->send_message(
              ui_to_processor{.foo = 123, .bar = "hiii", .x = 0.5f, .y = {1, 3, 5}});
        };
      }

      // 4. Receive a message on the UI thread from the processing thread
      static void process_message(ui& self, processor_to_ui msg)
      {
        fprintf(stderr, "Got message in ui thread ! %d %d\n", msg.baz.x, msg.baz.y);
        self.foo.button.press_count++;
      }

      // 5. Send a message from the ui to the processing thread
      std::function<void(ui_to_processor)> send_message;
    };
  };
};
}