Skip to content

Commit 644b353

Browse files
committed
Add C++ RAII scaffolding to handle API unwinding
In order to support C++ RAII classes in the functions exposed in the API to Lua this change also introduces a new assertion `LExpect` functioning like the existing `LAssert` but instead of directly transferring control to Lua it pushes the message on the Lua stack and throws a C++ exception to unwind the C++ call stack. For this scheme the convenience macros `SG_LUA_CPP_FUN_BEGIN(Name)` and `SG_LUA_CPP_FUN_END()` wrap the function entrypoint and perform the second phase of assertion processing when needed.
1 parent 9664cd4 commit 644b353

3 files changed

Lines changed: 93 additions & 1 deletion

File tree

ui_api.cpp

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,52 @@ static ui_main_c* GetUIPtr(lua_State* L)
9595
return ui;
9696
}
9797

98+
// ===============
99+
// C++ scaffolding
100+
// ===============
101+
102+
/*
103+
* ui->LAssert transfers control immediately out of the function without destroying
104+
* any C++ objects. To support RAII this scaffolding serves as a landing pad for
105+
* ui->LExpect, to transfer control to Lua but only after the call stack has been
106+
* unwound with normal C++ exception semantics.
107+
*
108+
* Example use site:
109+
* SG_LUA_FUN_BEGIN(DoTheThing)
110+
* {
111+
* ui_main_c* ui = GetUIPtr(L);
112+
* auto foo = std::make_shared<Foo>();
113+
* ui->LExpect(L, lua_gettop(L) >= 1), "Usage: DoTheThing(x)");
114+
* ui->LExpect(L, lua_isstring(L, 1), "DoTheThing() argument 1: expected string, got %s", luaL_typename(L, 1));
115+
* return 0;
116+
* }
117+
* SG_LUA_FUN_END()
118+
*/
119+
120+
#ifdef _WIN32
121+
#define SG_NOINLINE __declspec(noinline)
122+
#else
123+
#define SG_NOINLINE [[gnu::noinline]]
124+
#endif
125+
#define SG_NORETURN [[noreturn]]
126+
127+
SG_NORETURN static void LuaErrorWrapper(lua_State* L)
128+
{
129+
lua_error(L);
130+
}
131+
132+
#define SG_LUA_CPP_FUN_BEGIN(Name) \
133+
static int l_##Name(lua_State* L) { \
134+
int (*fun)(lua_State*) = [](lua_State* L) SG_NOINLINE -> int { \
135+
try
136+
137+
#define SG_LUA_CPP_FUN_END() \
138+
catch (ui_expectationFailed_s) { return -1; } \
139+
}; \
140+
int rc = fun(L); \
141+
if (rc < 0) { LuaErrorWrapper(L); } \
142+
return rc; }
143+
98144
// =========
99145
// Callbacks
100146
// =========
@@ -272,6 +318,38 @@ static int l_imgHandleImageSize(lua_State* L)
272318
return 2;
273319
}
274320

321+
class ui_luaReader_c {
322+
public:
323+
ui_luaReader_c(ui_main_c* ui, lua_State* L, std::string funName) : ui(ui), L(L), funName(funName) {}
324+
325+
// Always zero terminated as all regular strings are terminated in Lua.
326+
std::string_view ArgToString(int k) {
327+
ui->LExpect(L, lua_isstring(L, k), "%s() argument %d: expected string, got %s",
328+
funName.c_str(), k, luaL_typename(L, k));
329+
return lua_tostring(L, k);
330+
}
331+
332+
void ArgCheckTable(int k) {
333+
ui->LExpect(L, lua_istable(L, k), "%s() argument %d: expected table, got %s",
334+
funName.c_str(), k, luaL_typename(L, k));
335+
}
336+
337+
void ArgCheckNumber(int k) {
338+
ui->LExpect(L, lua_isnumber(L, k), "%s() argument %d: expected number, got %s",
339+
funName.c_str(), k, luaL_typename(L, k));
340+
}
341+
342+
void ValCheckNumber(int k, char const* ctx) {
343+
ui->LExpect(L, lua_isnumber(L, k), "%s() %s: expected number, got %s",
344+
funName.c_str(), ctx, k, luaL_typename(L, k));
345+
}
346+
347+
private:
348+
ui_main_c* ui;
349+
lua_State* L;
350+
std::string funName;
351+
};
352+
275353
// =========
276354
// Rendering
277355
// =========

ui_main.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,17 @@ void ui_main_c::LAssert(lua_State* L, int cond, const char* fmt, ...)
9595
}
9696
}
9797

98+
void ui_main_c::LExpect(lua_State* L, int cond, const char* fmt, ...)
99+
{
100+
if (!cond) {
101+
va_list va;
102+
va_start(va, fmt);
103+
lua_pushvfstring(L, fmt, va);
104+
va_end(va);
105+
throw ui_expectationFailed_s{};
106+
}
107+
}
108+
98109
int ui_main_c::IsUserData(lua_State* L, int index, const char* metaName)
99110
{
100111
if (lua_type(L, index) != LUA_TUSERDATA || lua_getmetatable(L, index) == 0) {

ui_main.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
// Classes
99
// =======
1010

11+
struct ui_expectationFailed_s {};
12+
1113
// UI Manager
1214
class ui_main_c: public ui_IMain {
1315
public:
@@ -53,7 +55,8 @@ class ui_main_c: public ui_IMain {
5355
void ScriptInit();
5456
void ScriptShutdown();
5557

56-
void LAssert(lua_State* L, int cond, const char* fmt, ...);
58+
void LAssert(lua_State* L, int cond, const char* fmt, ...); // Non-local return to Lua code on failure
59+
void LExpect(lua_State* L, int cond, const char* fmt, ...); // Throws ui_expectationFailed_s on failure, message on Lua stack
5760
int IsUserData(lua_State* L, int index, const char* metaName);
5861
int PushCallback(const char* name);
5962
void PCall(int narg, int nret);

0 commit comments

Comments
 (0)