Tag Archives: c++

C++20 coroutines and moteus_tool

I’ve had a confusing mismash of development tools for the moteus servos for a while now.  My original development tool was in python, which worked just fine.  Coroutines allowed me to express complex asynchronous logic succinctly, the program itself was rather simple, and I could easily integrate it with matplotlib for plotting.  However, when looking to run this on the raspberry pi, I needed a newer python version than came with raspbian, which turned out to be a royal pain to get installed in a repeatable manner.  Thus I rewrote a portion of the moteus_tool in C++ and just used my normal cross-compiling toolchain to generate the binaries.  What I didn’t do was port the calibration logic, as the state machine required with standard boost::asio would have bloated the logic size by 5x, and I didn’t really need to calibrate servos from the raspberry pi ever.

Still, I’ve wanted to consolidate these tools for a while now, and while working towards other telemetry and development tool goals, I decided to make another pass at removing the duplicity.  I figured it was time to try using the new C++20 coroutines to implement the asynchronous logic in C++ and see if I could get rid of the python tool.  Since I’m currently vendoring all the compilers and libraries for the C++ applications, I am already running clang-9.0 with libc++ and boost 1.72 for all the host side tools which theoretically should all support coroutines just fine.

Why coroutines?

To recap, typical callback based boost::asio code looks something like this:

void DoSomething() {
  boost::asio::async_write(
    *stream_,
    boost::asio::buffer("command to send"),
    std::bind(&Class::DoneWriting, this, pl::_1));
}

void DoneWriting(boost::system::error_code ec) {
  FailIf(ec);
  boost::asio::async_read_until(
    *stream_,
    response_,
    "\n",
    std::bind(&Class::HandleRead, this, pl::_1));
}

void HandleRead(boost::system::error_code ec) {
  FailIf(ec);
  // Do something with the result.
}

This gets even worse if you have embedded control flow.  Typically the best you can do is construct an object to hold the state, and bind it around to keep track of things:

struct Context {
  int iteration_count = 0;
};

void Start() {
  auto ctx = std::make_shared<Context>();
  Write(ctx);
}

void Write(std::shared_ptr<Context> ctx) {
  message_ = fmt::format("{}", ctx->iteration_count);
  boost::asio::async_write(
    *stream_,
    boost::asio::buffer(message_),
    std::bind(&Class::HandleWrite, this, pl::_1, ctx));
}

void HandleWrite(boost::system::error_code ec,
                 std::shared_ptr<Context> ctx) {
  FailIf(ec);
  ctx->iteration_count++;
  if (ctx->iteration_count >= kLoopCount) {
    Done();
  } else {
    Write(ctx);
  }
}

Now, when coroutines are used, you can relinquish control with a simple “co_await”, and all the logic can be written out as if it were purely procedural:

boost::asio::awaitable<void> Start() {
  for (int i = 0; i < kLoopCount; i++) {
    auto message = fmt::format("{}", i);
    co_await boost::asio::async_write(
      *stream_,
      boost::asio::buffer(message),
      boost::asio::use_awaitable);
  }
}

Not only is this significantly shorter, it more directly expresses the intent of the operation, and lifetime of the various values is easier to manage.  No longer do we have to keep around the buffer as an instance variable or a shared ptr to ensure it lives beyond the write operation.  It stays in scope until it is no longer needed like any other automatic variable.

moteus_tool

Switching moteus_tool to coroutines was straightforward, and resulted in a big reduction in the verbosity required.

DART now in bazel_deps

A previous simulator I had built for Super Mega Microbot was based on the “DART” robotics toolkit.  It is a C++ library with python bindings that includes kinematics, dynamics, and graphical rendering capabilities under a BSD license.  I wanted to use some of its dynamics capabilities for future gait work on the quad A0, and eventually re-incorporate its simulation capabilities, so integrated a subset of it into mjbots/bazel_deps.

ICFP 2019 Programming Contest

For many years now on and off I’ve played in the programming contest associated with the International Conference on Function Programming.  Each time it has been fun, exhilarating, and definitely humbling!  www.icfpcontest.org

This year, the competition runs from Friday June 21st through Monday June 24th.  Once again we’ll be competing as “Side Effects May Include…”.  Look for us on the leaderboard and cheer on!

 

log4cpp updates

While updating the mjmech code-base to interoperate with the moteus servos, I simultaneously was updating it to use C++17.  C++ rarely removes or deprecates features, but one of those which actually was removed in C++17, after being deprecated in C++11, was auto_ptr.  unique_ptr is strictly superior in all regards, and so there is no real reason not to switch.

However, mjmech depends upon a large amount of third party software.  Amazingly, only one package actually was broken by this removal of auto_ptr, log4cpp.  It actually saw some updates for C++17 compliance back in 2017, but otherwise hasn’t been updated since then.  I went ahead and forked it, and got it compiling with clang in C++17 mode at least:

After doing that, I discovered that someone had already posted a similar patch 2 years ago to the sourceforge page, but which was never applied.  Oh well, it wasn’t that much duplicated effort.

rpi_bazel updated to clang 7.0

When I initially created rpi_bazel, I set it up to use a host provided clang.  I decided to update that so that the rpi_bazel rules themselves download a binary version of an arbitrary clang.  This lets you decouple the version of clang from what is available in any given Linux distribution and improves the reproducibility of builds, since you are no longer dependent on whatever PPA you used to grab clang from.

To make that work in a cross compilation environment, the rules also cross compile libcxx and libcxxapi.  However, since bazel’s support for C++ toolchains is still in flux, for now binaries must explicitly depend upon the C++ standard library if they want it.  On the plus side, that now makes it trivial to fully support C++17 on the raspberry pi.  It should also be easy to update this to clang 8, although I haven’t gotten around to doing so yet.