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.
In order to reduce compile times, some files have separated declarations and definitions.
The definitions will appear in whichever compilation unit defines TMC_IMPL
.
Since each function must be defined exactly once, you must include this definition in exactly one compilation unit prior to including any TMC headers.
The simplest way to accomplish this is to put it in your main.cpp, or you can add a standalone build file to your project.
Creating and Running an Executor#
In order to run tasks, you need at least one executor. There is a globally provided instance of each executor type 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.
#define TMC_IMPL
#include "tmc/all_headers.hpp"
int main() {
return tmc::async_main([]() -> tmc::task<int> {
// Hello, world!
co_return 0;
}());
}
The cpu executor is also a high-performance thread pool that you can submit regular functions to:
#define TMC_IMPL
#include "tmc/all_headers.hpp"
int main() {
tmc::cpu_executor().init();
tmc::post(tmc::cpu_executor(), your_function);
}
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 tzcnt/tmc-asio:
#define TMC_IMPL
#include "tmc/all_headers.hpp"
#include "tmc/ex_asio.hpp"
#include "tmc/aw_asio.hpp"
tmc::task<int> accept_loop() {
asio::ip::tcp::acceptor acceptor(tmc::asio_executor(), {tcp::v4(), 55555});
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
tmc::spawn(socket_handler(std::move(socket))).detach();
}
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:
#define TMC_IMPL
#include "tmc/all_headers.hpp"
#include "tmc/ex_asio.hpp"
#include "tmc/aw_asio.hpp"
tmc::task<void> accept_loop() {
asio::ip::tcp::acceptor acceptor(tmc::asio_executor(), {tcp::v4(), 55555});
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
tmc::spawn(socket_handler(std::move(socket))).detach();
}
}
int main() {
tmc::asio_executor().init();
tmc::post_waitable(tmc::asio_executor(), accept_loop()).wait();
}