Skip to content

Commit 791367d

Browse files
committed
Work with C++20 coroutines
1 parent 324a603 commit 791367d

17 files changed

Lines changed: 1433 additions & 72 deletions

‎Benchmarks/CMakeLists.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
cmake_minimum_required(VERSION 3.10)
2+
cmake_policy (SET CMP0169 OLD)
23

34
include(FetchContent)
45

‎Benchmarks/requirements.txt‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
matplotlib
2+
numpy

‎CHANGES.md‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
## Master
2-
31
## Version 3.0
42

53
* Moved to C++17 as minimum supported standard C++ version.
@@ -17,6 +15,11 @@
1715
* Allow specifying a non virtual base class method when declaring class members (functions or variables) not exposed in the inherited class.
1816
* Allow using capturing lambdas in `Namespace::addFunction`, `Namespace::addProperty`, `Class<T>::addFunction`, `Class<T>::addStaticFunction`, `Class<T>::addProperty` and `Class<T>::addStaticProperty`.
1917
* Added multiple inheritance support: `deriveClass` now accepts more than one registered base class (e.g. `deriveClass<D, A, B>`).
18+
* Added C++20 coroutine integration: `CppCoroutine<R>` type that can be registered via `Namespace::addCoroutine()` to expose C++ generators to Lua using `co_yield` and `co_return`.
19+
* Added `LuaCoroutine` awaitable to resume a child Lua thread synchronously from inside a `CppCoroutine` body using `co_await`.
20+
* Added `lua_resume_x` and `lua_isyieldable_x` portable helpers in `LuaBridge/detail/LuaHelpers.h`.
21+
* Added `LUABRIDGE_HAS_CXX20_COROUTINES` feature-detection macro; opt out with `LUABRIDGE_DISABLE_CXX20_COROUTINES`.
22+
* Added `ErrorCode::CoroutineYieldFromNonCoroutine` and `ErrorCode::CoroutineAlreadyDone` error codes.
2023
* Added `Namespace::addVariable` to allow adding a modifiable value by copy into the namespace without incurring in function calls or metatables generation.
2124
* Added `luabridge::callWithHandler` free function and `LuaRef::callWithHandler` member to provide a custom Lua message handler during `lua_pcall`.
2225
* Added `luabridge::newFunction` free function and `LuaRef::newFunction` static method to wrap any C++ callable into a Lua function exposed as a `LuaRef`.

‎CMakeLists.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ project (LuaBridge)
44

55
include (CMakeDependentOption)
66

7-
set (CMAKE_CXX_STANDARD 17)
7+
set (CMAKE_CXX_STANDARD 20)
88
set (CMAKE_CXX_STANDARD_REQUIRED ON)
99
set (CMAKE_CXX_EXTENSIONS OFF)
1010

‎Images/benchmarks.png‎

-8.6 KB
Loading

‎Manual.md‎

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ Contents
4444
* [2.8 - Lua Stack](#28---lua-stack)
4545
* [2.8.1 - Enums](#281---enums)
4646
* [2.8.2 - lua_State](#282---lua_state)
47+
* [2.9 - C++20 Coroutine Integration](#29---c20-coroutine-integration)
48+
* [2.9.1 - CppCoroutine\<R\> — Generators callable from Lua](#291---cppcoroutiner----generators-callable-from-lua)
49+
* [2.9.2 - Accepting Arguments](#292---accepting-arguments)
50+
* [2.9.3 - LuaCoroutine — Awaiting a Lua Thread from C++](#293---luacoroutine----awaiting-a-lua-thread-from-c)
51+
* [2.9.4 - Limitations](#294---limitations)
4752

4853
* [3 - Passing Objects](#3---passing-objects)
4954

@@ -77,6 +82,7 @@ Contents
7782
* [6.2 - LUABRIDGE_STRICT_STACK_CONVERSIONS](#62---luabridge-strict-stack-conversions)
7883
* [6.3 - LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING](#63---luabridge-safe-c-exception-handling)
7984
* [6.4 - LUABRIDGE_RAISE_UNREGISTERED_CLASS_USAGE](#64---luabridge-raise-unregistered-class-usage)
85+
* [6.5 - LUABRIDGE_HAS_CXX20_COROUTINES / LUABRIDGE_DISABLE_CXX20_COROUTINES](#65---luabridge-has-cxx20-coroutines--luabridge-disable-cxx20-coroutines)
8086

8187
* [Appendix - API Reference](#appendix---api-reference)
8288

@@ -1245,6 +1251,113 @@ When the script calls `useStateAndArgs`, it passes only the integer and string p
12451251

12461252
The same is applicable for properties.
12471253

1254+
2.9 - C++20 Coroutine Integration
1255+
----------------------------------
1256+
1257+
LuaBridge3 provides first-class interoperability between C++20 coroutines and Lua coroutines, available when compiling with C++20 or later and Lua 5.2+ (requires `lua_yieldk`). The feature is guarded by `LUABRIDGE_HAS_CXX20_COROUTINES`, which is detected automatically and can be suppressed with `LUABRIDGE_DISABLE_CXX20_COROUTINES`.
1258+
1259+
> **Note:** C++20 coroutine integration is not supported on Lua 5.1, LuaJIT, or Luau (those targets lack a public `lua_yieldk` equivalent).
1260+
1261+
### 2.9.1 - CppCoroutine\<R\> — Generators callable from Lua
1262+
1263+
`luabridge::CppCoroutine<R>` is a coroutine return type that bridges C++20 coroutines with Lua's `coroutine.wrap` / `coroutine.resume` API. A function returning `CppCoroutine<R>` can use `co_yield` to suspend and pass a value back to Lua, and `co_return` to finish and return a final value.
1264+
1265+
Register via `Namespace::addCoroutine`:
1266+
1267+
```cpp
1268+
luabridge::getGlobalNamespace(L)
1269+
.addCoroutine("range", [](int from, int to) -> luabridge::CppCoroutine<int>
1270+
{
1271+
for (int i = from; i <= to; ++i)
1272+
co_yield i;
1273+
co_return -1; // sentinel value when the range is exhausted
1274+
});
1275+
```
1276+
1277+
From Lua, use `coroutine.wrap` to create a callable iterator:
1278+
1279+
```lua
1280+
local gen = coroutine.wrap(range)
1281+
local v = gen(1, 5) -- first call passes arguments; yields 1
1282+
while v ~= -1 do
1283+
print(v) -- 1, 2, 3, 4, 5
1284+
v = gen() -- subsequent calls resume without arguments
1285+
end
1286+
```
1287+
1288+
`CppCoroutine<void>` is also supported for coroutines that produce no values:
1289+
1290+
```cpp
1291+
.addCoroutine("doWork", []() -> luabridge::CppCoroutine<void>
1292+
{
1293+
performStep1();
1294+
co_return;
1295+
});
1296+
```
1297+
1298+
An abandoned coroutine (one that goes out of scope in Lua without being fully consumed) is automatically cleaned up by the Lua garbage collector — no manual resource management is needed.
1299+
1300+
### 2.9.2 - Accepting Arguments
1301+
1302+
The factory lambda receives the Lua call arguments on first invocation. A `lua_State*` parameter, if present, must be the **first** parameter and receives the running Lua thread:
1303+
1304+
```cpp
1305+
.addCoroutine("adder", [](int a, int b) -> luabridge::CppCoroutine<int>
1306+
{
1307+
co_yield a + b; // first resume yields the sum
1308+
co_return a * b; // second resume returns the product
1309+
});
1310+
```
1311+
1312+
```lua
1313+
local f = coroutine.wrap(adder)
1314+
print(f(3, 4)) -- 7 (yield: 3+4)
1315+
print(f()) -- 12 (return: 3*4)
1316+
```
1317+
1318+
Multiple independent instances of the same coroutine factory can run concurrently — each call to `coroutine.wrap(name)` creates a separate C++ coroutine frame:
1319+
1320+
```lua
1321+
local a = coroutine.wrap(adder)
1322+
local b = coroutine.wrap(adder)
1323+
a(1, 2) -- independent from b
1324+
b(10, 20)
1325+
```
1326+
1327+
### 2.9.3 - LuaCoroutine — Awaiting a Lua Thread from C++
1328+
1329+
`luabridge::LuaCoroutine` is an awaitable that can be used inside a `CppCoroutine` body to resume a child Lua thread synchronously. It runs the child thread to its first yield or return and gives back the status and the number of values the child left on its stack:
1330+
1331+
```cpp
1332+
.addCoroutine("driver", [](lua_State* L) -> luabridge::CppCoroutine<int>
1333+
{
1334+
// Spawn a child Lua thread and anchor it in the registry so the GC
1335+
// doesn't collect it while we hold a pointer to it.
1336+
lua_State* child = lua_newthread(L);
1337+
int ref = luaL_ref(L, LUA_REGISTRYINDEX); // pops thread from L's stack
1338+
1339+
lua_getglobal(child, "luaGenerator");
1340+
1341+
// Resume the child synchronously; suspends this C++ coroutine until done.
1342+
auto [status, nresults] = co_await luabridge::LuaCoroutine{ child, L };
1343+
1344+
int value = (nresults > 0) ? static_cast<int>(lua_tointeger(child, -nresults)) : 0;
1345+
1346+
luaL_unref(L, LUA_REGISTRYINDEX, ref);
1347+
co_return value;
1348+
});
1349+
```
1350+
1351+
`LuaCoroutine` always completes synchronously (no external event loop is required). The `status` field contains `LUA_YIELD` if the child yielded or `LUA_OK` if it returned normally.
1352+
1353+
### 2.9.4 - Limitations
1354+
1355+
* **Lua version:** Requires Lua 5.2+ (`lua_yieldk`). Not supported on Lua 5.1, LuaJIT, or Luau.
1356+
* **C++ version:** Requires C++20 (`<coroutine>`). Non-coroutine features continue to work under C++17.
1357+
* **Multi-value yield:** `co_yield` sends exactly one value per suspension. Use `std::tuple` or a struct if multiple values are needed.
1358+
* **Thread safety:** Coroutine frames must be driven from a single OS thread.
1359+
* **Instance methods:** `addCoroutine` is available on `Namespace`; registering coroutine-returning member functions on `Class<T>` is not yet supported.
1360+
12481361
3 - Passing Objects
12491362
===================
12501363

@@ -1965,6 +2078,30 @@ Override the default when you need fine-grained control:
19652078
#include <LuaBridge/LuaBridge.h>
19662079
```
19672080
2081+
6.5 - LUABRIDGE_HAS_CXX20_COROUTINES / LUABRIDGE_DISABLE_CXX20_COROUTINES
2082+
--------------------------------------------------------------------------
2083+
2084+
**`LUABRIDGE_HAS_CXX20_COROUTINES` — auto-detected, override allowed**
2085+
2086+
LuaBridge3 automatically enables C++20 coroutine support when it detects a C++20 compiler (`__cplusplus >= 202002L` or MSVC `_HAS_CXX20`). The macro is set to `1` when the feature is active and `0` otherwise.
2087+
2088+
You can force the feature **off** by defining `LUABRIDGE_DISABLE_CXX20_COROUTINES` before including any LuaBridge header:
2089+
2090+
```cpp
2091+
#define LUABRIDGE_DISABLE_CXX20_COROUTINES
2092+
#include <LuaBridge/LuaBridge.h>
2093+
```
2094+
2095+
You can also override the detection result explicitly:
2096+
2097+
```cpp
2098+
#define LUABRIDGE_HAS_CXX20_COROUTINES 0 // force off
2099+
#define LUABRIDGE_HAS_CXX20_COROUTINES 1 // force on (must actually have C++20)
2100+
#include <LuaBridge/LuaBridge.h>
2101+
```
2102+
2103+
Attempting to use coroutine integration on Lua 5.1, LuaJIT, or Luau will emit a compile-time `#error` unless `LUABRIDGE_DISABLE_COROUTINE_INTEGRATION` is also defined.
2104+
19682105
Appendix - API Reference
19692106
========================
19702107
@@ -2360,3 +2497,36 @@ TypeResult<T> get (lua_State* L, int index);
23602497
/// Checks if the Lua value at the index is convertible into the C++ value of the type T.
23612498
bool isInstance (lua_State* L, int index);
23622499
```
2500+
2501+
C++20 Coroutine Types (requires `LUABRIDGE_HAS_CXX20_COROUTINES`)
2502+
-----------------------------------------------------------------
2503+
2504+
```cpp
2505+
/// Coroutine return type for C++ generators callable from Lua.
2506+
/// R may be any LuaBridge-registered type, or void.
2507+
template <class R>
2508+
struct CppCoroutine;
2509+
2510+
/// Awaitable wrapper for a Lua thread. Use inside a CppCoroutine body with co_await.
2511+
/// Resumes the child thread synchronously and returns {status, nresults}.
2512+
class LuaCoroutine
2513+
{
2514+
public:
2515+
/// thread — the Lua thread to resume.
2516+
/// from — the calling Lua state (typically L inside the coroutine body).
2517+
LuaCoroutine(lua_State* thread, lua_State* from = nullptr) noexcept;
2518+
2519+
std::pair<int, int> await_resume() noexcept; // returns {status, nresults}
2520+
};
2521+
2522+
/// Register a CppCoroutine factory in a namespace (available on Namespace).
2523+
/// F must be a callable whose return type is CppCoroutine<R>.
2524+
template <class F>
2525+
Namespace& addCoroutine(const char* name, F factory);
2526+
2527+
/// Portable lua_resume wrapper — fills *nresults on all Lua versions (5.1–5.5).
2528+
int lua_resume_x(lua_State* L, lua_State* from, int nargs, int* nresults = nullptr);
2529+
2530+
/// Returns true if the current C function can yield via lua_yieldk.
2531+
bool lua_isyieldable_x(lua_State* L);
2532+
```

‎README.md‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,11 @@
3535
[LuaBridge3](https://github.com/kunitoki/LuaBridge3) is a lightweight and dependency-free library for mapping data,
3636
functions, and classes back and forth between C++ and [Lua](http://wwww.lua.org) (a powerful,
3737
fast, lightweight, embeddable scripting language). LuaBridge has been tested
38-
and works with Lua 5.1.5, 5.2.4, 5.3.6, 5.4.8 and 5.5.0 as well as [LuaJit](https://luajit.org/) 2.1 onwards
39-
and for the first time also with [Luau](https://luau-lang.org/) 0.713 onwards and [Ravi](https://github.com/dibyendumajumdar/ravi) 1.0-beta11.
38+
and works with:
39+
* [PUC-Lua](https://lua.org) 5.1.5, 5.2.4, 5.3.6, 5.4.8 and 5.5.0
40+
* [LuaJit](https://luajit.org/) 2.1
41+
* [Luau](https://luau-lang.org/) 0.713
42+
* [Ravi](https://github.com/dibyendumajumdar/ravi) 1.0-beta11
4043

4144
## Features
4245

@@ -87,6 +90,7 @@ LuaBridge3 offers a set of improvements compared to vanilla LuaBridge:
8790
* Consistent numeric handling and conversions (signed, unsigned and floats) across all lua versions.
8891
* NaN and Inf values pass through floating-point stack conversions without error.
8992
* Simplified registration of enum types via the `luabridge::Enum` stack wrapper.
93+
* C++20 coroutine integration via `addCoroutine()` and `CppCoroutine<R>`; await Lua threads from C++ with `LuaCoroutine`.
9094
* Opt-out handling of safe stack space checks (automatically avoids exhausting lua stack space when pushing values!).
9195
* Optional strict stack conversions via `LUABRIDGE_STRICT_STACK_CONVERSIONS` (e.g. `bool` requires an actual boolean, not any truthy value).
9296
* Error handler support in Lua calls via `LuaRef::callWithHandler` and `luabridge::callWithHandler`.

‎Source/LuaBridge/LuaBridge.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include "detail/CFunctions.h"
2020
#include "detail/ClassInfo.h"
21+
#include "detail/Coroutine.h"
2122
#include "detail/Enum.h"
2223
#include "detail/Errors.h"
2324
#include "detail/Expected.h"

‎Source/LuaBridge/detail/Config.h‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,20 @@
1212
#error LuaBridge 3 requires a compliant C++17 compiler, or C++17 has not been enabled !
1313
#endif
1414

15+
/**
16+
* @brief Enable C++20 coroutine integration with Lua coroutines.
17+
*
18+
* Requires C++20 and Lua 5.2+ (lua_yieldk). Not supported on Lua 5.1, LuaJIT, or Luau.
19+
* Define LUABRIDGE_DISABLE_CXX20_COROUTINES to force-disable even when C++20 is available.
20+
*/
21+
#if !defined(LUABRIDGE_HAS_CXX20_COROUTINES)
22+
#if !defined(LUABRIDGE_DISABLE_CXX20_COROUTINES) && (__cplusplus >= 202002L || (defined(_MSC_VER) && _HAS_CXX20))
23+
#define LUABRIDGE_HAS_CXX20_COROUTINES 1
24+
#else
25+
#define LUABRIDGE_HAS_CXX20_COROUTINES 0
26+
#endif
27+
#endif
28+
1529
#if !defined(LUABRIDGE_HAS_EXCEPTIONS)
1630
#if defined(_MSC_VER)
1731
#if _CPPUNWIND || _HAS_EXCEPTIONS

0 commit comments

Comments
 (0)