Skip to content

Commit

Permalink
Integrate Luau package into mlua api.
Browse files Browse the repository at this point in the history
Eg. `Lua::load_from_std_lib` with `StdLib::PACKAGE` is now supported for Luau.
  • Loading branch information
khvzak committed Nov 20, 2023
1 parent 44f5688 commit 618dac6
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 138 deletions.
19 changes: 10 additions & 9 deletions src/lua.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,6 @@ impl Lua {

let lua = unsafe { Self::inner_new(libs, options) };

#[cfg(not(feature = "luau"))]
if libs.contains(StdLib::PACKAGE) {
mlua_expect!(lua.disable_c_modules(), "Error during disabling C modules");
}
Expand Down Expand Up @@ -436,7 +435,7 @@ impl Lua {
}

#[cfg(feature = "luau")]
mlua_expect!(lua.prepare_luau_state(), "Error configuring Luau");
mlua_expect!(lua.configure_luau(), "Error configuring Luau");

lua
}
Expand Down Expand Up @@ -580,7 +579,6 @@ impl Lua {
///
/// [`StdLib`]: crate::StdLib
pub fn load_from_std_lib(&self, libs: StdLib) -> Result<()> {
#[cfg(not(feature = "luau"))]
let is_safe = unsafe { (*self.extra.get()).safe };

#[cfg(not(feature = "luau"))]
Expand All @@ -599,12 +597,9 @@ impl Lua {
let res = unsafe { load_from_std_lib(self.main_state, libs) };

// If `package` library loaded into a safe lua state then disable C modules
#[cfg(not(feature = "luau"))]
{
let curr_libs = unsafe { (*self.extra.get()).libs };
if is_safe && (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) {
mlua_expect!(self.disable_c_modules(), "Error during disabling C modules");
}
let curr_libs = unsafe { (*self.extra.get()).libs };
if is_safe && (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) {
mlua_expect!(self.disable_c_modules(), "Error during disabling C modules");
}
unsafe { (*self.extra.get()).libs |= libs };

Expand Down Expand Up @@ -3126,6 +3121,7 @@ impl Lua {
Ok(AnyUserData(self.pop_ref(), SubtypeId::None))
}

// Luau version located in `luau/mod.rs`
#[cfg(not(feature = "luau"))]
fn disable_c_modules(&self) -> Result<()> {
let package: Table = self.globals().get("package")?;
Expand Down Expand Up @@ -3525,6 +3521,11 @@ unsafe fn load_from_std_lib(state: *mut ffi::lua_State, libs: StdLib) -> Result<
requiref(state, ffi::LUA_LOADLIBNAME, ffi::luaopen_package, 1)?;
ffi::lua_pop(state, 1);
}
#[cfg(feature = "luau")]
if libs.contains(StdLib::PACKAGE) {
let lua: &Lua = mem::transmute((*extra_data(state)).inner.assume_init_ref());
crate::luau::register_package_module(lua)?;
}

#[cfg(feature = "luajit")]
{
Expand Down
88 changes: 88 additions & 0 deletions src/luau/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use std::ffi::CStr;
use std::os::raw::{c_float, c_int};

use crate::error::Result;
use crate::lua::Lua;

// Since Luau has some missing standard function, we re-implement them here

impl Lua {
pub(crate) unsafe fn configure_luau(&self) -> Result<()> {
let globals = self.globals();

globals.raw_set(
"collectgarbage",
self.create_c_function(lua_collectgarbage)?,
)?;
globals.raw_set("vector", self.create_c_function(lua_vector)?)?;

// Set `_VERSION` global to include version number
// The environment variable `LUAU_VERSION` set by the build script
if let Some(version) = ffi::luau_version() {
globals.raw_set("_VERSION", format!("Luau {version}"))?;
}

Ok(())
}

pub(crate) fn disable_c_modules(&self) -> Result<()> {
package::disable_dylibs(self);
Ok(())
}
}

unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_int {
let option = ffi::luaL_optstring(state, 1, cstr!("collect"));
let option = CStr::from_ptr(option);
let arg = ffi::luaL_optinteger(state, 2, 0);
match option.to_str() {
Ok("collect") => {
ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0);
0
}
Ok("stop") => {
ffi::lua_gc(state, ffi::LUA_GCSTOP, 0);
0
}
Ok("restart") => {
ffi::lua_gc(state, ffi::LUA_GCRESTART, 0);
0
}
Ok("count") => {
let kbytes = ffi::lua_gc(state, ffi::LUA_GCCOUNT, 0) as ffi::lua_Number;
let kbytes_rem = ffi::lua_gc(state, ffi::LUA_GCCOUNTB, 0) as ffi::lua_Number;
ffi::lua_pushnumber(state, kbytes + kbytes_rem / 1024.0);
1
}
Ok("step") => {
let res = ffi::lua_gc(state, ffi::LUA_GCSTEP, arg);
ffi::lua_pushboolean(state, res);
1
}
Ok("isrunning") => {
let res = ffi::lua_gc(state, ffi::LUA_GCISRUNNING, 0);
ffi::lua_pushboolean(state, res);
1
}
_ => ffi::luaL_error(state, cstr!("collectgarbage called with invalid option")),
}
}

// Luau vector datatype constructor
unsafe extern "C-unwind" fn lua_vector(state: *mut ffi::lua_State) -> c_int {
let x = ffi::luaL_checknumber(state, 1) as c_float;
let y = ffi::luaL_checknumber(state, 2) as c_float;
let z = ffi::luaL_checknumber(state, 3) as c_float;
#[cfg(feature = "luau-vector4")]
let w = ffi::luaL_checknumber(state, 4) as c_float;

#[cfg(not(feature = "luau-vector4"))]
ffi::lua_pushvector(state, x, y, z);
#[cfg(feature = "luau-vector4")]
ffi::lua_pushvector(state, x, y, z, w);
1
}

pub(crate) use package::register_package_module;

mod package;
184 changes: 60 additions & 124 deletions src/luau.rs → src/luau/package.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::ffi::CStr;
use std::fmt::Write;
use std::os::raw::{c_float, c_int};
use std::os::raw::c_int;
use std::path::{PathBuf, MAIN_SEPARATOR_STR};
use std::string::String as StdString;
use std::{env, fs};
Expand All @@ -15,7 +15,9 @@ use crate::value::{IntoLua, Value};
#[cfg(unix)]
use {libloading::Library, rustc_hash::FxHashMap};

// Since Luau has some missing standard function, we re-implement them here
//
// Luau package module
//

#[cfg(unix)]
const TARGET_MLUA_LUAU_ABI_VERSION: u32 = 1;
Expand Down Expand Up @@ -48,63 +50,63 @@ impl std::ops::DerefMut for LoadedDylibs {
}
}

impl Lua {
pub(crate) unsafe fn prepare_luau_state(&self) -> Result<()> {
let globals = self.globals();
pub(crate) fn register_package_module(lua: &Lua) -> Result<()> {
// Create the package table and store it in app_data for later use (bypassing globals lookup)
let package = lua.create_table()?;
lua.set_app_data(PackageKey(lua.create_registry_value(package.clone())?));

globals.raw_set(
"collectgarbage",
self.create_c_function(lua_collectgarbage)?,
)?;
globals.raw_set("require", self.create_c_function(lua_require)?)?;
globals.raw_set("package", create_package_table(self)?)?;
globals.raw_set("vector", self.create_c_function(lua_vector)?)?;
// Set `package.path`
let mut search_path = env::var("LUAU_PATH")
.or_else(|_| env::var("LUA_PATH"))
.unwrap_or_default();
if search_path.is_empty() {
search_path = "?.luau;?.lua".to_string();
}
package.raw_set("path", search_path)?;

// Set `_VERSION` global to include version number
// The environment variable `LUAU_VERSION` set by the build script
if let Some(version) = ffi::luau_version() {
globals.raw_set("_VERSION", format!("Luau {version}"))?;
// Set `package.cpath`
#[cfg(unix)]
{
let mut search_cpath = env::var("LUAU_CPATH")
.or_else(|_| env::var("LUA_CPATH"))
.unwrap_or_default();
if search_cpath.is_empty() {
if cfg!(any(target_os = "macos", target_os = "ios")) {
search_cpath = "?.dylib".to_string();
} else {
search_cpath = "?.so".to_string();
}
}
package.raw_set("cpath", search_cpath)?;
}

// Set `package.loaded` (table with a list of loaded modules)
let loaded = lua.create_table()?;
package.raw_set("loaded", loaded.clone())?;
lua.set_named_registry_value("_LOADED", loaded)?;

Ok(())
// Set `package.loaders`
let loaders = lua.create_sequence_from([lua.create_function(lua_loader)?])?;
package.raw_set("loaders", loaders.clone())?;
#[cfg(unix)]
{
loaders.push(lua.create_function(dylib_loader)?)?;
lua.set_app_data(LoadedDylibs(FxHashMap::default()));
}
lua.set_named_registry_value("_LOADERS", loaders)?;

// Register the module and `require` function in globals
let globals = lua.globals();
globals.raw_set("package", package)?;
globals.raw_set("require", unsafe { lua.create_c_function(lua_require)? })?;

Ok(())
}

unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_int {
let option = ffi::luaL_optstring(state, 1, cstr!("collect"));
let option = CStr::from_ptr(option);
let arg = ffi::luaL_optinteger(state, 2, 0);
match option.to_str() {
Ok("collect") => {
ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0);
0
}
Ok("stop") => {
ffi::lua_gc(state, ffi::LUA_GCSTOP, 0);
0
}
Ok("restart") => {
ffi::lua_gc(state, ffi::LUA_GCRESTART, 0);
0
}
Ok("count") => {
let kbytes = ffi::lua_gc(state, ffi::LUA_GCCOUNT, 0) as ffi::lua_Number;
let kbytes_rem = ffi::lua_gc(state, ffi::LUA_GCCOUNTB, 0) as ffi::lua_Number;
ffi::lua_pushnumber(state, kbytes + kbytes_rem / 1024.0);
1
}
Ok("step") => {
let res = ffi::lua_gc(state, ffi::LUA_GCSTEP, arg);
ffi::lua_pushboolean(state, res);
1
}
Ok("isrunning") => {
let res = ffi::lua_gc(state, ffi::LUA_GCISRUNNING, 0);
ffi::lua_pushboolean(state, res);
1
}
_ => ffi::luaL_error(state, cstr!("collectgarbage called with invalid option")),
}
pub(crate) fn disable_dylibs(lua: &Lua) {
// Presence of `LoadedDylibs` in app data is used as a flag
// to check whether binary modules are enabled
lua.remove_app_data::<LoadedDylibs>();
}

unsafe extern "C-unwind" fn lua_require(state: *mut ffi::lua_State) -> c_int {
Expand Down Expand Up @@ -160,74 +162,6 @@ unsafe extern "C-unwind" fn lua_require(state: *mut ffi::lua_State) -> c_int {
1
}

// Luau vector datatype constructor
unsafe extern "C-unwind" fn lua_vector(state: *mut ffi::lua_State) -> c_int {
let x = ffi::luaL_checknumber(state, 1) as c_float;
let y = ffi::luaL_checknumber(state, 2) as c_float;
let z = ffi::luaL_checknumber(state, 3) as c_float;
#[cfg(feature = "luau-vector4")]
let w = ffi::luaL_checknumber(state, 4) as c_float;

#[cfg(not(feature = "luau-vector4"))]
ffi::lua_pushvector(state, x, y, z);
#[cfg(feature = "luau-vector4")]
ffi::lua_pushvector(state, x, y, z, w);
1
}

//
// package module
//

fn create_package_table(lua: &Lua) -> Result<Table> {
// Create the package table and store it in app_data for later use (bypassing globals lookup)
let package = lua.create_table()?;
lua.set_app_data(PackageKey(lua.create_registry_value(package.clone())?));

// Set `package.path`
let mut search_path = env::var("LUAU_PATH")
.or_else(|_| env::var("LUA_PATH"))
.unwrap_or_default();
if search_path.is_empty() {
search_path = "?.luau;?.lua".to_string();
}
package.raw_set("path", search_path)?;

// Set `package.cpath`
#[cfg(unix)]
{
let mut search_cpath = env::var("LUAU_CPATH")
.or_else(|_| env::var("LUA_CPATH"))
.unwrap_or_default();
if search_cpath.is_empty() {
if cfg!(any(target_os = "macos", target_os = "ios")) {
search_cpath = "?.dylib".to_string();
} else {
search_cpath = "?.so".to_string();
}
}
package.raw_set("cpath", search_cpath)?;
}

// Set `package.loaded` (table with a list of loaded modules)
let loaded = lua.create_table()?;
package.raw_set("loaded", loaded.clone())?;
lua.set_named_registry_value("_LOADED", loaded)?;

// Set `package.loaders`
let loaders = lua.create_sequence_from([lua.create_function(lua_loader)?])?;
package.raw_set("loaders", loaders.clone())?;
#[cfg(unix)]
{
loaders.push(lua.create_function(dylib_loader)?)?;
let loaded_dylibs = LoadedDylibs(FxHashMap::default());
lua.set_app_data(loaded_dylibs);
}
lua.set_named_registry_value("_LOADERS", loaders)?;

Ok(package)
}

/// Searches for the given `name` in the given `path`.
///
/// `path` is a string containing a sequence of templates separated by semicolons.
Expand Down Expand Up @@ -306,8 +240,12 @@ fn dylib_loader(lua: &Lua, modname: StdString) -> Result<Value> {
let file_path = file_path.canonicalize()?;
// Load the library and check for symbol
unsafe {
let mut loaded_dylibs = match lua.app_data_mut::<LoadedDylibs>() {
Some(loaded_dylibs) => loaded_dylibs,
None => return "dynamic libraries are disabled in safe mode".into_lua(lua),
};
// Check if it's already loaded
if let Some(lib) = lua.app_data_ref::<LoadedDylibs>().unwrap().get(&file_path) {
if let Some(lib) = loaded_dylibs.get(&file_path) {
return find_symbol(lib);
}
if let Ok(lib) = Library::new(&file_path) {
Expand All @@ -319,9 +257,7 @@ fn dylib_loader(lua: &Lua, modname: StdString) -> Result<Value> {
return err.into_lua(lua);
}
let symbol = find_symbol(&lib);
lua.app_data_mut::<LoadedDylibs>()
.unwrap()
.insert(file_path, lib);
loaded_dylibs.insert(file_path, lib);
return symbol;
}
}
Expand Down
2 changes: 0 additions & 2 deletions src/stdlib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ impl StdLib {
pub const MATH: StdLib = StdLib(1 << 7);

/// [`package`](https://www.lua.org/manual/5.4/manual.html#6.3) library
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub const PACKAGE: StdLib = StdLib(1 << 8);

/// [`buffer`](https://luau-lang.org/library#buffer-library) library
Expand Down
Loading

0 comments on commit 618dac6

Please sign in to comment.