Skip to content

Commit 3eebf22

Browse files
committed
Add experimental static Luau runtime support
Compile source scripts into per-state bytecode, adapt module loading and userdata lifetimes, and preserve plain-data persistence across Lua runtimes. Add standalone tests, live smoke fixtures, CI and compatibility documentation.
1 parent 58d7652 commit 3eebf22

26 files changed

Lines changed: 1073 additions & 90 deletions

‎.github/workflows/luau-runtime.yml‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Luau runtime
2+
3+
on:
4+
push:
5+
pull_request:
6+
workflow_dispatch:
7+
8+
jobs:
9+
runtime:
10+
strategy:
11+
fail-fast: false
12+
matrix:
13+
os: [ubuntu-22.04, windows-2022]
14+
runs-on: ${{ matrix.os }}
15+
steps:
16+
- uses: actions/checkout@v6
17+
- name: Configure
18+
run: cmake -S tests -B TestBuilds/Runtime-luau -DLUA_VERSION=luau -DLUA_STATIC=ON -DCMAKE_BUILD_TYPE=Debug
19+
- name: Build
20+
run: cmake --build TestBuilds/Runtime-luau --config Debug --target eluna-runtime-tests --parallel 4
21+
- name: Test
22+
run: ctest --test-dir TestBuilds/Runtime-luau -C Debug --output-on-failure

‎BindingMap.h‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,7 @@
1212
#include "ElunaUtility.h"
1313
#include <type_traits>
1414

15-
extern "C"
16-
{
17-
#include "lua.h"
18-
#include "lauxlib.h"
19-
};
15+
#include "ElunaCompat.h"
2016

2117
class BaseBindingMap
2218
{

‎CMakeLists.txt‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
set(CUSTOM_METHODS_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/methods/Custom/CustomMethods.h")
22
if(EXISTS "${CUSTOM_METHODS_HEADER}")
3-
if(NOT ${LUA_VERSION} MATCHES "luajit")
3+
if(NOT LUA_VERSION MATCHES "^(luajit|luau)$")
44
target_compile_definitions(lualib PUBLIC ELUNA_USE_CUSTOM_METHODS)
55
else()
66
target_compile_definitions(lualib INTERFACE ELUNA_USE_CUSTOM_METHODS)
@@ -17,7 +17,7 @@ elseif(WIN32)
1717
endif()
1818

1919
# Add Lua compatibility definition
20-
if(NOT ${LUA_VERSION} MATCHES "luajit")
20+
if(NOT LUA_VERSION MATCHES "^(luajit|luau)$")
2121
target_compile_definitions(lualib PUBLIC LUA_COMPAT_MODULE)
2222
endif()
2323

@@ -36,6 +36,9 @@ foreach(dir ${modules_list})
3636

3737
# Add module target and properties
3838
if(sources_module)
39+
if(LUA_VERSION STREQUAL "luau")
40+
message(FATAL_ERROR "Native Lua module ${dir} is not supported by experimental Luau; use CustomMethods.h for C++ bindings")
41+
endif()
3942
add_library(${module_name} MODULE ${sources_module})
4043
target_include_directories(${module_name} INTERFACE ${PUBLIC_INCLUDES})
4144
target_link_libraries(${module_name} PUBLIC lualib)
@@ -88,3 +91,7 @@ foreach(target ${all_targets})
8891
remove_module_sources(${target})
8992
endif()
9093
endforeach()
94+
95+
if(BUILD_TESTING)
96+
add_subdirectory(tests)
97+
endif()

‎ElunaCompat.cpp‎

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
#include "ElunaCompat.h"
88

9-
#if LUA_VERSION_NUM == 501
9+
#if !defined ELUNA_LUAU && LUA_VERSION_NUM == 501
1010
const char* luaL_tolstring(lua_State* L, int idx, size_t* len) {
1111
if (!luaL_callmeta(L, idx, "__tostring")) {
1212
int t = lua_type(L, idx), tt = 0;
@@ -41,6 +41,14 @@ const char* luaL_tolstring(lua_State* L, int idx, size_t* len) {
4141
return lua_tolstring(L, -1, len);
4242
}
4343

44+
int lua_absindex(lua_State* L, int i) {
45+
if (i < 0 && i > LUA_REGISTRYINDEX)
46+
i += lua_gettop(L) + 1;
47+
return i;
48+
}
49+
#endif
50+
51+
#if defined ELUNA_LUAU || LUA_VERSION_NUM == 501
4452
int luaL_getsubtable(lua_State* L, int i, const char* name) {
4553
int abs_i = lua_absindex(L, i);
4654
luaL_checkstack(L, 3, "not enough stack slots");
@@ -56,12 +64,6 @@ int luaL_getsubtable(lua_State* L, int i, const char* name) {
5664
return 0;
5765
}
5866

59-
int lua_absindex(lua_State* L, int i) {
60-
if (i < 0 && i > LUA_REGISTRYINDEX)
61-
i += lua_gettop(L) + 1;
62-
return i;
63-
}
64-
6567
#if !defined LUAJIT_VERSION
6668
void* luaL_testudata(lua_State* L, int index, const char* tname) {
6769
void* ud = lua_touserdata(L, index);
@@ -86,3 +88,36 @@ void luaL_setmetatable(lua_State* L, const char* tname) {
8688
}
8789
#endif
8890
#endif
91+
92+
#if defined ELUNA_LUAU
93+
int luaL_ref(lua_State* L, int table)
94+
{
95+
if (table != LUA_REGISTRYINDEX)
96+
return luaL_error(L, "Eluna's Luau references require the registry");
97+
int ref = lua_ref(L, -1);
98+
lua_pop(L, 1); // lua_ref itself does not consume the referenced value.
99+
return ref;
100+
}
101+
102+
void luaL_unref(lua_State* L, int table, int ref)
103+
{
104+
if (table != LUA_REGISTRYINDEX)
105+
luaL_error(L, "Eluna's Luau references require the registry");
106+
lua_unref(L, ref);
107+
}
108+
#endif
109+
110+
#if defined ELUNA_LUAU || (LUA_VERSION_NUM == 501 && !defined LUAJIT_VERSION)
111+
void luaL_setfuncs(lua_State* L, const luaL_Reg* functions, int upvalues)
112+
{
113+
luaL_checkstack(L, upvalues + 1, "too many upvalues");
114+
for (; functions->name; ++functions)
115+
{
116+
for (int i = 0; i < upvalues; ++i)
117+
lua_pushvalue(L, -upvalues);
118+
lua_pushcclosure(L, functions->func, upvalues);
119+
lua_setfield(L, -(upvalues + 2), functions->name);
120+
}
121+
lua_pop(L, upvalues);
122+
}
123+
#endif

‎ElunaCompat.h‎

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,53 @@
77
#ifndef ELUNACOMPAT_H
88
#define ELUNACOMPAT_H
99

10+
#if defined ELUNA_LUAU
11+
// Luau uses C++ linkage and exceptions. Do not wrap these headers in extern C.
12+
#include "lua.h"
13+
#include "lualib.h"
14+
#else
1015
extern "C"
1116
{
1217
#include "lua.h"
18+
#include "lualib.h"
1319
#include "lauxlib.h"
1420
#if __has_include(<luajit.h>)
1521
#include <luajit.h>
1622
#endif
1723
};
24+
#endif
1825

19-
/* Compatibility layer for compiling with Lua 5.1 or LuaJIT */
20-
#if LUA_VERSION_NUM == 501
26+
#if defined ELUNA_LUAU
27+
#define lua_pushglobaltable(L) lua_pushvalue((L), LUA_GLOBALSINDEX)
28+
#define lua_rawlen(L, idx) lua_objlen((L), (idx))
29+
30+
#undef lua_pushcfunction
31+
#undef lua_pushcclosure
32+
#define lua_pushcfunction(L, fn) lua_pushcclosurek((L), (fn), nullptr, 0, nullptr)
33+
#define lua_pushcclosure(L, fn, nup) lua_pushcclosurek((L), (fn), nullptr, (nup), nullptr)
34+
35+
// Preserve Lua's expression-style error API used by existing bindings.
36+
#undef luaL_error
37+
#undef luaL_argerror
38+
#define luaL_error(...) (luaL_errorL(__VA_ARGS__), 0)
39+
#define luaL_argerror(L, arg, msg) (luaL_argerrorL((L), (arg), (msg)), 0)
40+
#define lua_error(L) (lua_error(L), 0)
41+
42+
int luaL_ref(lua_State* L, int table);
43+
void luaL_unref(lua_State* L, int table, int ref);
44+
#endif
45+
46+
#if defined ELUNA_LUAU || LUA_VERSION_NUM == 501
2147
int luaL_getsubtable(lua_State* L, int i, const char* name);
48+
#if !defined LUAJIT_VERSION
49+
void luaL_setfuncs(lua_State* L, const luaL_Reg* functions, int upvalues);
50+
void* luaL_testudata(lua_State* L, int index, const char* tname);
51+
void luaL_setmetatable(lua_State* L, const char* tname);
52+
#endif
53+
#endif
54+
55+
/* Compatibility layer for compiling with Lua 5.1 or LuaJIT */
56+
#if !defined ELUNA_LUAU && LUA_VERSION_NUM == 501
2257
const char* luaL_tolstring(lua_State* L, int idx, size_t* len);
2358
int lua_absindex(lua_State* L, int i);
2459
#define lua_pushglobaltable(L) \
@@ -30,14 +65,9 @@ extern "C"
3065
#define lua_load(L, buf_read, dec_buf, str, NULL) \
3166
lua_load(L, buf_read, dec_buf, str)
3267

33-
#if !defined LUAJIT_VERSION
34-
void* luaL_testudata(lua_State* L, int index, const char* tname);
35-
void luaL_setmetatable(lua_State* L, const char* tname);
36-
#define luaL_setfuncs(L, l, n) luaL_register(L, NULL, l)
37-
#endif
3868
#endif
3969

40-
#if LUA_VERSION_NUM > 502
70+
#if !defined ELUNA_LUAU && LUA_VERSION_NUM > 502
4171
#define lua_dump(L, writer, data) \
4272
lua_dump(L, writer, data, 0)
4373
#define lua_pushunsigned(L, u) \

‎ElunaEventMgr.cpp‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,7 @@
1212
#include "Entities/Object.h"
1313
#endif
1414

15-
extern "C"
16-
{
17-
#include "lua.h"
18-
#include "lauxlib.h"
19-
};
15+
#include "ElunaCompat.h"
2016

2117
ElunaEventProcessor::~ElunaEventProcessor()
2218
{

‎ElunaLoader.cpp‎

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "ElunaConfig.h"
1010
#include "ElunaLoader.h"
1111
#include "ElunaUtility.h"
12+
#include "ElunaLuaRuntime.h"
1213
#include <fstream>
1314
#include <sstream>
1415
#include <thread>
@@ -32,11 +33,7 @@ namespace fs = std::filesystem;
3233
#include "Maps/MapManager.h"
3334
#endif
3435

35-
extern "C" {
36-
#include <lua.h>
37-
#include <lualib.h>
38-
#include <lauxlib.h>
39-
}
36+
#include "ElunaCompat.h"
4037

4138
#if defined ELUNA_TRINITY
4239
void ElunaUpdateListener::handleFileAction(efsw::WatchID /*watchid*/, std::string const& dir, std::string const& filename, efsw::Action /*action*/, std::string /*oldFilename*/)
@@ -48,7 +45,11 @@ void ElunaUpdateListener::handleFileAction(efsw::WatchID /*watchid*/, std::strin
4845
std::string ext = path.extension().string();
4946
std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c) { return std::tolower(c); });
5047

51-
if (ext != ".lua" && ext != ".ext")
48+
if (ext != ".lua" && ext != ".ext"
49+
#if defined ELUNA_LUAU
50+
&& ext != ".luau"
51+
#endif
52+
)
5253
return;
5354

5455
sElunaLoader->ReloadElunaForMap(RELOAD_ALL_STATES);
@@ -183,9 +184,14 @@ void ElunaLoader::ReadFiles(lua_State* L, std::string path)
183184
if (fs::exists(someDir) && fs::is_directory(someDir) && !fs::is_empty(someDir))
184185
{
185186
m_requirePath +=
187+
#if defined ELUNA_LUAU
188+
path + "/?.luau;" +
189+
#endif
186190
path + "/?.lua;" +
187-
path + "/?.ext;" +
188-
path + "/?.moon;";
191+
path + "/?.ext;";
192+
#if !defined ELUNA_LUAU
193+
m_requirePath += path + "/?.moon;";
194+
#endif
189195

190196
m_requirecPath +=
191197
path + "/?.dll;" +
@@ -239,6 +245,37 @@ void ElunaLoader::ReadFiles(lua_State* L, std::string path)
239245

240246
bool ElunaLoader::CompileScript(lua_State* L, LuaScript& script)
241247
{
248+
#if defined ELUNA_LUAU
249+
if (script.fileext == ".moon")
250+
{
251+
ELUNA_LOG_ERROR("[Eluna]: MoonScript is not supported by Luau: `%s`", script.filepath.c_str());
252+
return false;
253+
}
254+
std::ifstream file(script.filepath, std::ios::binary);
255+
if (!file)
256+
{
257+
ELUNA_LOG_ERROR("[Eluna]: Cannot read Luau script `%s`", script.filepath.c_str());
258+
return false;
259+
}
260+
std::string source((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
261+
if (file.bad())
262+
{
263+
ELUNA_LOG_ERROR("[Eluna]: Failed reading Luau script `%s`", script.filepath.c_str());
264+
return false;
265+
}
266+
std::string bytecode = ElunaLua::Compile(source.data(), source.size());
267+
// Luau's compiler encodes syntax errors in its output. Loading validates
268+
// that output and places a useful diagnostic on the temporary state's stack.
269+
if (luau_load(L, ("@" + script.filepath).c_str(), bytecode.data(), bytecode.size(), 0))
270+
{
271+
ELUNA_LOG_ERROR("[Eluna]: Failed compiling Luau script `%s`", script.filepath.c_str());
272+
Eluna::Report(L);
273+
return false;
274+
}
275+
lua_pop(L, 1);
276+
script.bytecode.assign(bytecode.begin(), bytecode.end());
277+
return true;
278+
#else
242279
// Attempt to load the file
243280
int err = 0;
244281
if (script.fileext == ".moon")
@@ -270,6 +307,7 @@ bool ElunaLoader::CompileScript(lua_State* L, LuaScript& script)
270307
// pop the loaded function from the stack
271308
lua_pop(L, 1);
272309
return true;
310+
#endif
273311
}
274312

275313
void ElunaLoader::ProcessScript(lua_State* L, std::string filename, const size_t& filesize, const std::string& fullpath, int32 mapId)
@@ -284,7 +322,11 @@ void ElunaLoader::ProcessScript(lua_State* L, std::string filename, const size_t
284322
filename = filename.substr(0, extDot);
285323

286324
// check extension and add path to scripts to load
287-
if (ext != ".lua" && ext != ".ext" && ext != ".moon")
325+
if (ext != ".lua" && ext != ".ext" && ext != ".moon"
326+
#if defined ELUNA_LUAU
327+
&& ext != ".luau"
328+
#endif
329+
)
288330
return;
289331
bool extension = ext == ".ext";
290332

‎ElunaLoader.h‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,7 @@
1414
#include <efsw/efsw.hpp>
1515
#endif
1616

17-
extern "C"
18-
{
19-
#include "lua.h"
20-
};
17+
#include "ElunaCompat.h"
2118

2219
enum ElunaReloadActions
2320
{

0 commit comments

Comments
 (0)