Quick Start#

For more examples, see tzcnt/tmc-examples.

Building#

TooManyCooks is a header-only library. You can either include the specific headers that you need in each file, or #include "tmc/all_headers.hpp", which contains all of the other headers.

Creating and Running an Executor#

In order to run tasks, you need at least one executor. There are globally accessible CPU and I/O executors that you can use, or you can create your own if you prefer.

Setting up the global CPU executor is as simple as calling tmc::cpu_executor().init(); but an even easier way is to use tmc::async_main() which will take care of this for you. By default, the CPU executor will create 1 thread per physical core and automatically share work between them.

#include "tmc/all_headers.hpp"

int main() {
  return tmc::async_main([]() -> tmc::task<int> {
    // Hello, world!
    co_return 0;
  }());
}

TMC is not just for coroutines. You can also post regular functions to any TMC executor.

#include "tmc/all_headers.hpp"

int main() {
  tmc::cpu_executor().init();
  auto future = tmc::post_waitable(tmc::cpu_executor(), your_function);
  future.wait();
}

Executors provided by other TMC libraries are not initialized by async_main and must be initialized before you use them. They can be used in conjunction with the CPU executor. For example, the Asio executor provided by tmc-asio:

#include "tmc/all_headers.hpp"
#include "tmc/asio/ex_asio.hpp"
#include "tmc/asio/aw_asio.hpp"
#include <asio.hpp>

tmc::task<int> accept_loop() {
  asio::ip::tcp::acceptor acceptor(tmc::asio_executor(), {asio::ip::tcp::v4(), 8080});
  auto fg = tmc::fork_group();
  while (true) {
    auto [error, socket] = co_await acceptor.async_accept(tmc::aw_asio);
    if (error) break;

    // Kick off a handler that runs on the CPU executor
    fg.fork(socket_handler(std::move(socket)));
  }

  // Wait for all handlers to complete
  co_await std::move(fg);

  co_return 0; // return an exit code from async_main
}

int main() {
  tmc::asio_executor().init();
  return tmc::async_main(accept_loop());
}

… or they can be used completely standalone. For example, the following program does not use the CPU executor at all; it runs entirely on the Asio executor:

#include "tmc/all_headers.hpp"
#include "tmc/asio/ex_asio.hpp"
#include "tmc/asio/aw_asio.hpp"
#include <asio.hpp>

tmc::task<void> accept_loop() {
  asio::ip::tcp::acceptor acceptor(tmc::asio_executor(), {asio::ip::tcp::v4(), 8080});
  auto fg = tmc::fork_group();
  while (true) {
    auto [error, socket] = co_await acceptor.async_accept(tmc::aw_asio);
    if (error) break;

    // Kick off a handler that also runs on the Asio executor
    fg.fork(socket_handler(std::move(socket)));
  }

  // Wait for all handlers to complete
  co_await std::move(fg);
}

int main() {
  tmc::asio_executor().init();
  tmc::post_waitable(tmc::asio_executor(), accept_loop()).wait();
}

API Reference#

int tmc::async_main(tmc::task<int> &&ClientMainTask)#

A convenience function that initializes tmc::cpu_executor(), submits the ClientMainTask parameter to tmc::cpu_executor(), and then waits for it to complete. The int value returned by the submitted task will be returned from this function, so that you can use it as an exit code.