Using C++20 coroutines with Panda3D

Since Panda3D 1.10.0, we have supported Python coroutines in the Panda task system. These are a great way to write asynchronous code without needing to use awkward callbacks and having to jump through hoops to preserve state.

Now that coroutines have also been added to the new C++20 standard, we can support these as well in Panda. The coroutine support in C++20 is quite low-level and flexible, not prescribing a particular implementation, so we can easily integrate it with Panda’s existing task system. Only a minimal amount of glue code is needed to make them work together, linked at the bottom of this post.

Not only are they great for asynchronous programming, but they also provide a quite interesting syntactic sugar for stateful “regular” tasks without the need for awkward void casts or AsyncTask subclasses.

This is an example of two coroutines, one called by the other. They automatically become tasks when you call them. You can pass them to the task manager’s add() or you can co_await them.

PT(AsyncTask) async_load_model(Filename fn) {
  Loader *loader = Loader::get_global_ptr();

  // Load the model asynchronously
  EventParameter result = co_await loader->make_async_request(fn);

  // Extract return value
  PT(PandaNode) node = DCAST(PandaNode, result.get_ptr());
  std::cerr << "loaded model: " << *node << "\n";

  // Return anything that fits into an EventParameter or inherits TypedObject
  co_return 123;
}

PT(AsyncTask) periodic_task() {
  auto result = co_await async_load_model("models/panda");

  while (true) {
    // Put your per-frame logic here
    co_yield AsyncTask::DS_cont;
  }
}

To get it going, you just need to add it to the task manager:

  auto &task_mgr = framework.get_task_mgr();
  task_mgr.add(periodic_task());

Here is the glue code you need to make it work. I used GCC with -std=gnu++20 -fcoroutines flags. This glue code may be added to the Panda source code once coroutine support in compilers becomes more mature.

2 Likes