{"id":15717170,"url":"https://github.com/colinh/mini-coro-plus","last_synced_at":"2025-05-13T00:42:03.363Z","repository":{"id":223930923,"uuid":"761295889","full_name":"ColinH/mini-coro-plus","owner":"ColinH","description":"C++ Mini Coroutine Library","archived":false,"fork":false,"pushed_at":"2024-03-22T22:21:39.000Z","size":118,"stargazers_count":9,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-01T05:11:40.551Z","etag":null,"topics":["coroutines","cpp","cpp17","linux","macosx","posix"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ColinH.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2024-02-21T15:52:33.000Z","updated_at":"2024-12-12T01:28:04.000Z","dependencies_parsed_at":"2024-03-22T23:27:26.829Z","dependency_job_id":"85045ac0-191f-41c0-826d-10ee78c50fd5","html_url":"https://github.com/ColinH/mini-coro-plus","commit_stats":null,"previous_names":["colinh/mini-coro-plus"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ColinH%2Fmini-coro-plus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ColinH%2Fmini-coro-plus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ColinH%2Fmini-coro-plus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ColinH%2Fmini-coro-plus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ColinH","download_url":"https://codeload.github.com/ColinH/mini-coro-plus/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253850849,"owners_count":21973667,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["coroutines","cpp","cpp17","linux","macosx","posix"],"created_at":"2024-10-03T21:48:58.785Z","updated_at":"2025-05-13T00:42:03.331Z","avatar_url":"https://github.com/ColinH.png","language":"C++","readme":"# Mini Coro Plus\n\nMinimalistic asymmetric stackful [coroutines](https://en.wikipedia.org/wiki/Coroutine) for C++17.\n\nBased on the design and implementation of [minicoro](https://github.com/edubart/minicoro) by Eduardo Bart.\n\nUses assembly context switching code from [LuaCoco](https://coco.luajit.org) by Mike Pall.\n\n## Status\n\nNot quite finished yet, but the first unit tests all run successfully.\n\nCurrently only x86-64 and AArch64 are supported and only on Posix-compatible platforms.\n\n## Hello\n\nA traditional \"hello world\" example.\n\nThe `.ipp` file contains the library implementation and must be included in exactly one translation unit.\nThe `.hpp` file contains the interface and can be included as often as required.\n\n```c++\n#include \u003ciostream\u003e\n\n#include \"mini_coro_plus.hpp\"\n#include \"mini_coro_plus.ipp\"\n\nvoid function( mcp::control\u0026 ctrl )\n{\n   std::cout \u003c\u003c \"Hello\";\n   ctrl.yield();\n   std::cout \u003c\u003c \"World!\";\n}\n\nint main()\n{\n   mcp::coroutine coro( function );\n   coro.resume();\n   std::cout \u003c\u003c \", \";\n   coro.resume();\n   std::cout \u003c\u003c std::endl;\n   return 0;\n}\n```\n\nTo compile this example best use the included `Makefile` that builds all included `.cpp` files into corresponding executables -- and runs the `tests`.\nThe \"hello world\" example can then be invoked manually.\n\n```\nmini-coro-plus\u003e make\nc++ -std=c++17 -stdlib=libc++ -pedantic -MM -MQ build/dep/hello.d hello.cpp -o build/dep/hello.d\nc++ -std=c++17 -stdlib=libc++ -pedantic -Wall -Wextra -Werror -O3 hello.cpp  -o build/bin/hello\nbuild/bin/tests\nmcp: all testcases succeeded\nmini-coro-plus\u003e build/bin/hello\nHello, World!\n```\n\n## Creating\n\nA coroutine is created with a `std::function\u003c void() \u003e`, and, optionally, a stack size.\n\nAs usual this means that function pointers, function objects (aka. functors) and closures (evaluated lambda expressions) can be passed as first argument.\n\nA newly created coroutine sits in state `STARTING` and does **not** implicitly jump into its coroutine function!\n\n## Running\n\nRunning the coroutine function is achieve by calling `resume()` from outside the coroutine which puts the coroutine into state `RUNNING`.\n\nThis call to `resume()` will return when one of the following conditions is met:\n\n 1. The coroutine function returns. The coroutine enters state `COMPLETED` and may **not** be resumed again.\n 2. The coroutine function calls `ctrl.yield()` on a suitable instance of `mcp::control`. The coroutine enters state `SLEEPING` and can be resumed again later. Resuming continues execution of the coroutine function after the aforementioned  `yield()`.\n 3. The coroutine function throws an exception. The coroutine enters state `COMPLETED` and may **not** be resumed again. In this case the call to `resume()` will throw the coroutine's exception rather than returning normally.\n\nIf the `resume()` is performed by another coroutine (itself in `RUNNING` state) then this calling coroutine transitions to state `CALLING`.\n\nIt is an error to call any function other than `state()` on a coroutine in `COMPLETED` state.\n\nIt is an error to call `abort()` on a coroutine that is in `RUNNING` or `CALLING` state.\n\n## Destroying\n\nDestroying a coroutine object in states `RUNNING` or `CALLING` is an error.\n\nDestroying a coroutine object in states `STARTING` or `COMPLETED` does nothing special.\n\nDestroying a coroutine in state `SLEEPING` performs an implicit `resume()` into the coroutine in order to clean up the coroutine by destroying all local objects currently on the coroutine stack.\nThis cleanup is achieved by throwing a dedicated exception from the `yield()` call inside the coroutine, the `yield()` call that last put the coroutine into `SLEEPING` state.\n\nIn order to not interfere with the cleanup, coroutine functions must take care to **not** accidentally catch and ignore exceptions of type `mcp::internal::terminator`!\nThese exceptions must escape from the coroutine function.\n\nCalling `abort()` on a coroutine performs the cleanup for coroutines in state `SLEEPING` but not much else.\n\n## Interface\n\nThe following is an excerpt of `mini_coro_plus.hpp` with all parts that are not considered part of the public interface removed.\n\n```c++\nnamespace mcp\n{\n   enum class state : std::uint8_t\n   {\n      STARTING,  // Created without entering the coroutine function.\n      RUNNING,   // Entered the coroutine function and currently running it.\n      SLEEPING,  // Entered the coroutine which then yielded back out again.\n      CALLING,   // Entered the coroutine which then resumed a different one.\n      COMPLETED  // Finished the coroutine function to completion.\n   };\n\n   [[nodiscard]] constexpr bool can_abort( const state ) noexcept;\n   [[nodiscard]] constexpr bool can_resume( const state ) noexcept;\n   [[nodiscard]] constexpr bool can_yield( const state ) noexcept;\n\n   [[nodiscard]] constexpr std::string_view to_string( const state ) noexcept;\n\n   std::ostream\u0026 operator\u003c\u003c( std::ostream\u0026, const state );\n\n   // Control is for coroutine functions to control the coroutine they are currently running in.\n\n   class control\n   {\n   public:\n      control();\n\n      [[nodiscard]] mcp::state state() const noexcept;  // Always state::RUNNING when used correctly.\n      [[nodiscard]] std::size_t stack_size() const noexcept;\n\n      void yield();\n      void yield( std::any\u0026\u0026 any );\n      void yield( const std::any\u0026 any );\n\n      [[nodiscard]] std::any\u0026 yield_any();\n      [[nodiscard]] std::any\u0026 yield_any( std::any\u0026\u0026 any );\n      [[nodiscard]] std::any\u0026 yield_any( const std::any\u0026 any );\n\n      template\u003c typename... Ts \u003e\n      void yield( Ts\u0026\u0026... ts );\n\n      template\u003c typename... Ts \u003e\n      [[nodiscard]] std::any\u0026 yield_any( Ts\u0026\u0026... ts );\n\n      template\u003c typename T, typename... As \u003e\n      [[nodiscard]] T yield_as( As\u0026\u0026... as );\n\n      template\u003c typename T, typename... As \u003e\n      [[nodiscard]] std::optional\u003c T \u003e yield_opt( As\u0026\u0026... as );\n\n      template\u003c typename T, typename... As \u003e\n      [[nodiscard]] T* yield_ptr( As\u0026\u0026... as );\n   };\n\n   // Coroutine is for creating and controlling coroutines from the outside.\n\n   class coroutine\n   {\n   public:\n      explicit coroutine( std::function\u003c void() \u003e\u0026\u0026, const std::size_t stack_size = 0 );\n      explicit coroutine( const std::function\u003c void() \u003e\u0026, const std::size_t stack_size = 0 );\n\n      explicit coroutine( std::function\u003c void( control\u0026 ) \u003e\u0026\u0026, const std::size_t stack_size = 0 );\n      explicit coroutine( const std::function\u003c void( control\u0026 ) \u003e\u0026, const std::size_t stack_size = 0 );\n\n      [[nodiscard]] mcp::state state() const noexcept;\n      [[nodiscard]] std::size_t stack_size() const noexcept;\n      [[nodiscard]] std::size_t stack_used() const noexcept;  // Not very precise?\n\n      void abort();\n      void clear();\n      void resume();\n      void resume( std::any\u0026\u0026 any );\n      void resume( const std::any\u0026 any );\n\n      [[nodiscard]] std::any\u0026 resume_any();\n      [[nodiscard]] std::any\u0026 resume_any( std::any\u0026\u0026 any );\n      [[nodiscard]] std::any\u0026 resume_any( const std::any\u0026 any );\n\n      template\u003c typename... Ts \u003e\n      void resume( Ts\u0026\u0026... ts );\n\n      template\u003c typename... Ts \u003e\n      [[nodiscard]] std::any\u0026 resume_any( Ts\u0026\u0026... ts );\n\n      template\u003c typename T, typename... As \u003e\n      [[nodiscard]] T resume_as( As\u0026\u0026... as );\n\n      template\u003c typename T, typename... As \u003e\n      [[nodiscard]] std::optional\u003c T \u003e resume_opt( As\u0026\u0026... as );\n\n      template\u003c typename T, typename... As \u003e\n      [[nodiscard]] T* resume_ptr( As\u0026\u0026... as );\n   };\n\n}  // namespace mcp\n```\n\n## Control Flow\n\nAssume that `coro` is an `mcp::coroutine` created with a closure, function or functor `F` as first argument, and that `ctrl` is an `mcp::control` for said coroutine obtained either by using the default constructor within a running coroutine or as argument to `F`.\n\n| Action | Where | What | Where | What |\n| --- | --- | --- | --- | --- |\n| Start | Outside | Call `coro.resume()` | Inside | `F()` is called |\n| Yield | Inside | Call `ctrl.yield()` | Outside | `coro.resume()` returns |\n| Resume | Outside | Call `coro.resume()` | Inside | `ctrl.yield()` returns |\n| Finish | Inside | Return from `F()` | Outside | `coro.resume()` returns |\n| Throw | Inside | `F()` throws `E` | Outside | `coro.resume()` throws `E` |\n\nRemember that creating a coroutine does *not* yet call `F`.\nA coroutine can be resumed multiple times, until it finishes by returning from or throwing an exception within `F`.\n\n## Multithreading\n\nThis library is thread agnostic and compatible with multi-threaded applications.\n\nIn a multi-threaded application it is safe for multiple threads to create and run coroutines.\nCalling `mcp::control().yield()` applies to the coroutine running on the current thread.\nIt is an error to call from outside of a running coroutine.\n\nCoroutines can be created on one thread and resumed on a different thread.\nThe usual care needs to be taken as a coroutine object **must not** be used in multiple threads simultaneously as bad things **will** happen.\n\n## Development\n\nThis project was started to investigate whether it is possible to make [minicoro](https://github.com/edubart/minicoro) more C++ compatible.\nThe main questions were (a) how can C++ exceptions propagate from inside a coroutine to outside, and (b) can the coroutine object itself use RAII to clean up after itself?\nTurns out we can demonstrate here that the answer is \"yes\", however we can also see that the changes are quite intrusive, and therefore not well suited for additional or optional inclusion in [minicoro](https://github.com/edubart/minicoro).\n\n---\n\nCopyright (c) 2024 Dr. Colin Hirsch\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcolinh%2Fmini-coro-plus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcolinh%2Fmini-coro-plus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcolinh%2Fmini-coro-plus/lists"}