Date: Sun, 8 Nov 2026 23:20:15 +0000
Hi Ian,
Thank you for writing back. I enjoyed reading your proposal! I will admit It took me a bit of prior reading to fully understand the surrounding machinery with senders and receivers. The nearest parallel I can think of from the game development domain is with Scene Graphs which allow for pruning and pre-allocation of GPU resources before executing graphics commands on the GPU. Both this paper and the referenced P4127R0 paper were also super helpful for understanding the function signature pollution problem with allocators and why out-of-band state is preferred.
Senders do indeed have appeal over base coroutines for the reasons you mentioned on memory allocation control and serializability. That said, their further specialization into the domain of asynchronous programming over coroutines puts them one step further away from the general purpose gameplay programming domain I would hope to apply them to. This is ultimately a semantic argument as coroutine terms like task/promise/co_await/co_yield have about as much applicability to general purpose state machines as sender/receiver/scheduler/execution_agent do, so perhaps the only thing coroutines have going for them in this regard are precedent in that they are already applied as such. That point aside, if senders were to receive dedicated language support similar to what coroutines have, then I think they could become a viable alternative to coroutines for general purpose state machine authoring.
On a separate note, I have been continuing to iterate on the ideas from my previous message and have a small development to share: In the contents_visitor implementation I proposed, I suggested that the visitor function would call contents_visitor::operator()(T&) on each variable stored within the coroutine frame. However, this behaviour is incorrect and would not allow an outside observer to completely observe the state of variables stored on the coroutine frame. Specifically, any reference type variables stored on the coroutine frame would be received by the visitor as such with the underlying stored reference pointer remaining opaque. In order for this pattern to work as intended, references stored on the coroutine frame must be stored as std::reference_wrapper<T> instead with the visitor ultimately receiving a std::reference_wrapper<T>&. This unfortunately puts a new constraint on the compiler’s options for organizing the coroutine frame which causes my initially proposed assertation to fail. In the best case, this constraint would only be applied if the promise type implements a contents_visitor type, but there are likely additional knock-ons up for consideration as a result of this that I haven’t yet explored…
For further evaluation, I have amended my code sample from before wherein now the transformation from T& to std::reference_wrapper<T> happens inside the CoVar wrapper: https://godbolt.org/z/bT56nGjez
The above change also makes it possible to serialize coroutine member functions and capturing lambdas using C++23 explicit object parameter semantics: (this CoVar<T> self)
Julie
From: Ian Petersen <ispeters_at_gmail.com>
Sent: Saturday, November 7, 2026 11:23 AM
To: sg14_at_[hidden]
Cc: Julie Lim <JulieL663_at_outlook.com>
Subject: Re: [isocpp-sg14] Improving Useability of Coroutines as General Purpose State Machines
Hi Julie,
That's a clever hack! I'd be curious to see what you think of solving your problem with my proposed std::execution::function (https://wg21.link/p4223) and C++26's reflection capabilities. I think that, compared to coroutines, senders give you more control over memory allocation, and they're "just objects" (albeit complicated ones), so they may be more amenable to reflection-based approaches to serialization than coroutines. On the downside, of course, senders have no language support to make expressing the logic of your program convenient, which is a significant drawback compared to coroutines. I think this could be addressed with a language change that permits the compiler to generate a sender that represents a coroutine function.
Ian
On Fri, Nov 6, 2026 at 1:19 PM Julie Lim via SG14 <sg14_at_lists.isocpp.org> wrote:
Hello SG14,
I’m a game developer working out of Western Canada, Vancouver and I was hoping to open a discussion on some explorative work I’ve done in applying C++20 coroutines to games.
As a gameplay programmer specifically, my interest is less focused on the high-throughput, multithreaded asynchronous programming goals that coroutines were designed to support, and more focused on their application as an intuitive means to create compact, ad-hoc state machines via a single function definition. These ad-hoc state machines are especially useful for creating sequenced behaviors for characters or other game logic and have seen use in both public game engines like Unity as well as (citing from own experience) proprietary game engines with scripting solutions that support await-like syntax.
The two problems I hope to solve to make C++20 coroutines more suitable for use as general purpose state machines are as follows:
• Completely opaque internal state (preventing serialization for game state save/load/replay, network replication, and runtime CRC checking)
• Allocation at the point of invocation (as opposed to allocating once upon initialization of game or entity state and re-using as needed)
With these goals in mind, I’d like to consider the following promise type implementation: https://godbolt.org/z/ccbjWxWd3
The above code implements a seemingly compiler-agnostic coroutine save/load solution that works for all versions of MSVC, GCC, and Clang dating back to their earliest implementations of coroutines. It also provides a mechanism to measure the size of a coroutine frame before execution and a way to optionally invoke a coroutine with a pre-allocated field of memory. While it's a neat proof-of-concept, it's not really production viable due to the gross misuse of the coroutine’s destroy chain to visit each in-scope variable on the coroutine frame for identification and serialization.
However, from this implementation we can derive an insight: “Any constraints on the compiler’s ability to optimize the lifetime and layout of variables in the coroutine frame resulting from the introduction of a hypothetical coroutine variable visitor function into the C++ Standard would be superseded by the constraints that already exist via the presence of the destroy function.”
My question(s) to the study group are: does the above inferred insight hold up to scrutiny? Would the language benefit from a way for external callers to access coroutine variables? And finally, what would the introduction of a hypothetical variable visitor function into the standard look like?
For me personally, I would like to see an opt-in feature using a contents_visitor callable object type that could be defined in the promise type. If present, the compiler would generate a visitor function that takes a contents_visitor& and invokes contents_visitor::operator()(T&) with each variable, input parameter, and active awaiter stored in the frame. The visitor function pointer would be stored on the coroutine frame alongside the resume and destroy function and would be callable via a visit_contents(Promise::contents_visitor&) function on the coroutine handle (only defined if a contents_visitor is defined).
Before moving on to the next topic, I do want to note that the idea of a visitor function is not a silver bullet for enabling full general serialization of co-routines or nested coroutine chains. My own implementation above is somewhat crude in that it stores a whole memory snapshot and also limited in that it doesn’t support coroutine nesting at all. However, I think the presence of a Standard defined variable visitor function would provide a much wider range of possibilities for programmers to implement their own coroutine serialization functions constrained to the needs of their project’s requirements. The implementation of a visitor function could also lead to the emergence of other novel patterns such as bi-directional communication between the caller and the coroutine function body without the need to pass a reference into the function and risking it becoming dangling.
Turning our attention to the second problem, let’s take a look at the memory allocation features in the above code sample. The main kludge here is the use of the tls_delete variable in the promise type to optionally suppress the delete call on the coroutine memory in the event that the coroutine memory was allocated and owned by the caller. A straightforward language feature to propose from this would be an optional suppress_delete function implementable on the promise type to indicate whether delete should be called on the coroutine memory or not.
However, this approach requires that the promise type be aware of the memory context that it was allocated into. If we are to include the possibility of using memory allocated by an outside caller, then the natural approach is to pass an allocator or allocation info struct in as a parameter to the coroutine. Looking back through the group’s mailing archive, I saw that this practice has already been discussed by the group back in April with particular attention paid to the associated issue of function signature pollution. I would like to hear more about this discussion especially with the added possible use case of forwarding pre-allocated memory onto coroutines or even nested coroutines.
This got a little long winded, so I’ll pause here and thank the group members for their time in reading this far. I look forward to hearing the group’s thoughts on the ideas I have shared here and hope it can lead to some fruitful discussion.
Kind regards,
Julie Lim
LinkedIn: in/julie-lim-2a687262
_______________________________________________
SG14 mailing list
SG14_at_[hidden]
https://lists.isocpp.org/mailman/listinfo.cgi/sg14
Thank you for writing back. I enjoyed reading your proposal! I will admit It took me a bit of prior reading to fully understand the surrounding machinery with senders and receivers. The nearest parallel I can think of from the game development domain is with Scene Graphs which allow for pruning and pre-allocation of GPU resources before executing graphics commands on the GPU. Both this paper and the referenced P4127R0 paper were also super helpful for understanding the function signature pollution problem with allocators and why out-of-band state is preferred.
Senders do indeed have appeal over base coroutines for the reasons you mentioned on memory allocation control and serializability. That said, their further specialization into the domain of asynchronous programming over coroutines puts them one step further away from the general purpose gameplay programming domain I would hope to apply them to. This is ultimately a semantic argument as coroutine terms like task/promise/co_await/co_yield have about as much applicability to general purpose state machines as sender/receiver/scheduler/execution_agent do, so perhaps the only thing coroutines have going for them in this regard are precedent in that they are already applied as such. That point aside, if senders were to receive dedicated language support similar to what coroutines have, then I think they could become a viable alternative to coroutines for general purpose state machine authoring.
On a separate note, I have been continuing to iterate on the ideas from my previous message and have a small development to share: In the contents_visitor implementation I proposed, I suggested that the visitor function would call contents_visitor::operator()(T&) on each variable stored within the coroutine frame. However, this behaviour is incorrect and would not allow an outside observer to completely observe the state of variables stored on the coroutine frame. Specifically, any reference type variables stored on the coroutine frame would be received by the visitor as such with the underlying stored reference pointer remaining opaque. In order for this pattern to work as intended, references stored on the coroutine frame must be stored as std::reference_wrapper<T> instead with the visitor ultimately receiving a std::reference_wrapper<T>&. This unfortunately puts a new constraint on the compiler’s options for organizing the coroutine frame which causes my initially proposed assertation to fail. In the best case, this constraint would only be applied if the promise type implements a contents_visitor type, but there are likely additional knock-ons up for consideration as a result of this that I haven’t yet explored…
For further evaluation, I have amended my code sample from before wherein now the transformation from T& to std::reference_wrapper<T> happens inside the CoVar wrapper: https://godbolt.org/z/bT56nGjez
The above change also makes it possible to serialize coroutine member functions and capturing lambdas using C++23 explicit object parameter semantics: (this CoVar<T> self)
Julie
From: Ian Petersen <ispeters_at_gmail.com>
Sent: Saturday, November 7, 2026 11:23 AM
To: sg14_at_[hidden]
Cc: Julie Lim <JulieL663_at_outlook.com>
Subject: Re: [isocpp-sg14] Improving Useability of Coroutines as General Purpose State Machines
Hi Julie,
That's a clever hack! I'd be curious to see what you think of solving your problem with my proposed std::execution::function (https://wg21.link/p4223) and C++26's reflection capabilities. I think that, compared to coroutines, senders give you more control over memory allocation, and they're "just objects" (albeit complicated ones), so they may be more amenable to reflection-based approaches to serialization than coroutines. On the downside, of course, senders have no language support to make expressing the logic of your program convenient, which is a significant drawback compared to coroutines. I think this could be addressed with a language change that permits the compiler to generate a sender that represents a coroutine function.
Ian
On Fri, Nov 6, 2026 at 1:19 PM Julie Lim via SG14 <sg14_at_lists.isocpp.org> wrote:
Hello SG14,
I’m a game developer working out of Western Canada, Vancouver and I was hoping to open a discussion on some explorative work I’ve done in applying C++20 coroutines to games.
As a gameplay programmer specifically, my interest is less focused on the high-throughput, multithreaded asynchronous programming goals that coroutines were designed to support, and more focused on their application as an intuitive means to create compact, ad-hoc state machines via a single function definition. These ad-hoc state machines are especially useful for creating sequenced behaviors for characters or other game logic and have seen use in both public game engines like Unity as well as (citing from own experience) proprietary game engines with scripting solutions that support await-like syntax.
The two problems I hope to solve to make C++20 coroutines more suitable for use as general purpose state machines are as follows:
• Completely opaque internal state (preventing serialization for game state save/load/replay, network replication, and runtime CRC checking)
• Allocation at the point of invocation (as opposed to allocating once upon initialization of game or entity state and re-using as needed)
With these goals in mind, I’d like to consider the following promise type implementation: https://godbolt.org/z/ccbjWxWd3
The above code implements a seemingly compiler-agnostic coroutine save/load solution that works for all versions of MSVC, GCC, and Clang dating back to their earliest implementations of coroutines. It also provides a mechanism to measure the size of a coroutine frame before execution and a way to optionally invoke a coroutine with a pre-allocated field of memory. While it's a neat proof-of-concept, it's not really production viable due to the gross misuse of the coroutine’s destroy chain to visit each in-scope variable on the coroutine frame for identification and serialization.
However, from this implementation we can derive an insight: “Any constraints on the compiler’s ability to optimize the lifetime and layout of variables in the coroutine frame resulting from the introduction of a hypothetical coroutine variable visitor function into the C++ Standard would be superseded by the constraints that already exist via the presence of the destroy function.”
My question(s) to the study group are: does the above inferred insight hold up to scrutiny? Would the language benefit from a way for external callers to access coroutine variables? And finally, what would the introduction of a hypothetical variable visitor function into the standard look like?
For me personally, I would like to see an opt-in feature using a contents_visitor callable object type that could be defined in the promise type. If present, the compiler would generate a visitor function that takes a contents_visitor& and invokes contents_visitor::operator()(T&) with each variable, input parameter, and active awaiter stored in the frame. The visitor function pointer would be stored on the coroutine frame alongside the resume and destroy function and would be callable via a visit_contents(Promise::contents_visitor&) function on the coroutine handle (only defined if a contents_visitor is defined).
Before moving on to the next topic, I do want to note that the idea of a visitor function is not a silver bullet for enabling full general serialization of co-routines or nested coroutine chains. My own implementation above is somewhat crude in that it stores a whole memory snapshot and also limited in that it doesn’t support coroutine nesting at all. However, I think the presence of a Standard defined variable visitor function would provide a much wider range of possibilities for programmers to implement their own coroutine serialization functions constrained to the needs of their project’s requirements. The implementation of a visitor function could also lead to the emergence of other novel patterns such as bi-directional communication between the caller and the coroutine function body without the need to pass a reference into the function and risking it becoming dangling.
Turning our attention to the second problem, let’s take a look at the memory allocation features in the above code sample. The main kludge here is the use of the tls_delete variable in the promise type to optionally suppress the delete call on the coroutine memory in the event that the coroutine memory was allocated and owned by the caller. A straightforward language feature to propose from this would be an optional suppress_delete function implementable on the promise type to indicate whether delete should be called on the coroutine memory or not.
However, this approach requires that the promise type be aware of the memory context that it was allocated into. If we are to include the possibility of using memory allocated by an outside caller, then the natural approach is to pass an allocator or allocation info struct in as a parameter to the coroutine. Looking back through the group’s mailing archive, I saw that this practice has already been discussed by the group back in April with particular attention paid to the associated issue of function signature pollution. I would like to hear more about this discussion especially with the added possible use case of forwarding pre-allocated memory onto coroutines or even nested coroutines.
This got a little long winded, so I’ll pause here and thank the group members for their time in reading this far. I look forward to hearing the group’s thoughts on the ideas I have shared here and hope it can lead to some fruitful discussion.
Kind regards,
Julie Lim
LinkedIn: in/julie-lim-2a687262
_______________________________________________
SG14 mailing list
SG14_at_[hidden]
https://lists.isocpp.org/mailman/listinfo.cgi/sg14
Received on 2026-11-08 23:20:23
