Compare commits

..

32 Commits

Author SHA1 Message Date
Alex Orlenko e9c2b8d306 0.3.2 release 2020-04-29 16:32:16 +01:00
Alex Orlenko d672e19365 Add ToLua implementation for slices and arrays 2020-04-29 16:12:58 +01:00
Alex Orlenko bda399a5b4 Get yield function in the moment of async callback creation.
This is usefull to temporary override `coroutine.yield` prior to create_async_callback()
and then restore to original value.
2020-04-28 14:29:36 +01:00
Alex Orlenko fe5e87b0f5 Fix bugs with metatable cache:
- Don't use thread_local as Lua can be created in one thread and executed in another (in module mode);
- Make per state storage without luaL_ref;
2020-04-28 14:29:14 +01:00
Alex Orlenko 0f32e9cb43 0.3.1 release 2020-04-20 01:55:57 +01:00
Alex Orlenko 0efa0fcb6a Update documentation 2020-04-20 01:52:01 +01:00
Alex Orlenko 4e19ae6ccf Update tests (async and table) 2020-04-20 01:14:34 +01:00
Alex Orlenko c826798a6d Minor refactor 2020-04-19 16:51:35 +01:00
Alex Orlenko ee08050c1f Add TableExt trait with call_method/function methods 2020-04-19 16:15:16 +01:00
Alex Orlenko d8897d867b Update examples 2020-04-19 01:23:42 +01:00
Alex Orlenko 222f4df668 Add family of call_async function
Update documentation
Move async tests to a separate file
2020-04-18 21:26:12 +01:00
Alex Orlenko afaa0eb639 0.3.0 release 2020-04-17 23:55:58 +01:00
Alex Orlenko db5ad6bc60 Include async build/test to github workflow 2020-04-17 23:55:58 +01:00
Alex Orlenko ef06c5eec9 Update Cargo.toml 2020-04-17 23:55:57 +01:00
Alex Orlenko f7dc9da107 Update README
Cargo fmt and minor changes
2020-04-17 22:52:34 +01:00
Alex Orlenko 47e8a80c1c v0.3.0-alpha.1 with async support
Squashed commit of the async branch.
2020-04-17 22:39:50 +01:00
Alex Orlenko 1a788c48f1 Cherry-pick changes from rlua:
- Make Value::type_name() public
- Update CallbackError and ExternalError Display impl
2020-04-15 21:23:00 +01:00
Alex Orlenko d8b9c46d28 0.2.2 release 2020-02-09 22:21:34 +00:00
Alex Orlenko 9bd6d6fa92 Fix compile fail tests 2020-02-09 22:09:42 +00:00
Alex Orlenko ea6e6e00b2 Update README.md 2020-02-09 21:48:50 +00:00
Alex Orlenko 03ed7f6f99 Fix compile test 2020-01-27 21:57:12 +00:00
Alex Orlenko d5bc9c8c24 Combine lua-vendored and luajit-vendored features into "vendored" 2020-01-27 21:21:50 +00:00
Alex Orlenko 5cd0b645c4 Merge branch 'github_actions' 2020-01-25 23:07:31 +00:00
Alex Orlenko bf36eb72f8 Move from circele-ci to github actions 2020-01-25 22:35:47 +00:00
Alex Orlenko 1f7f8f62ab Merge branch 'lua_vendored' 2020-01-25 20:49:00 +00:00
Alex Orlenko e4dc773aa3 Remove __ipairs metamethod deprecated in lua 5.3 and not available by default 2020-01-25 20:47:36 +00:00
Alex Orlenko 07fc4642ae Support vendored versions of Lua and LuaJIT 2020-01-25 20:47:31 +00:00
Alex Orlenko 27121c779d Add raw_insert() and raw_remove() for tables (represented as lists) 2020-01-07 20:53:47 +00:00
Alex Orlenko 5eec0ef56b Implement PartialEq trait for Value (and subtypes)
Add equals() method to compare values optionally invoking __eq.
2020-01-07 00:03:03 +00:00
Alex Orlenko 831161bfda Fix CI badge link 2020-01-06 23:55:56 +00:00
Alex Orlenko 52be96677b 0.2.1 release 2019-12-26 23:09:11 +00:00
Alex Orlenko 42b33849e1 Add support of loading a specified set of standard libraries 2019-12-26 23:04:21 +00:00
57 changed files with 2799 additions and 1822 deletions
-49
View File
@@ -1,49 +0,0 @@
version: 2
jobs:
build:
docker:
- image: circleci/rust:latest
steps:
- checkout
- run:
name: Version information
command: rustc --version; cargo --version; rustup --version
- run:
name: Calculate dependencies
command: cargo generate-lockfile
- restore_cache:
keys:
- cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }}
- run:
name: Check Formatting
command: |
rustup component add rustfmt
rustfmt --version
cargo fmt --all -- --check --color=auto
- run:
name: Install Lua
command: |
sudo apt-get update
sudo apt-get -y --no-install-recommends install liblua5.3-dev liblua5.2-dev liblua5.1-0-dev libluajit-5.1-dev
- run:
name: Build all targets
command: cargo build --all --all-targets
- run:
name: Run all tests / Lua 5.3
command: cargo test --all --no-default-features --features lua53
- run:
name: Run all tests / Lua 5.2
command: cargo test --all --no-default-features --features lua52
- run:
name: Run all tests / Lua 5.1
command: cargo test --all --no-default-features --features lua51
- run:
name: Run all tests / LuaJIT
command: cargo test --all --no-default-features --features luajit
- save_cache:
paths:
- /usr/local/cargo/registry
- target/debug/.fingerprint
- target/debug/build
- target/debug/deps
key: cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }}
+156
View File
@@ -0,0 +1,156 @@
name: CI
on: [push, pull_request]
jobs:
build:
name: Build
runs-on: ${{ matrix.os }}
strategy:
matrix:
thing:
- stable
- macos-x86_64
- x86_64-msvc
include:
- thing: stable
target: x86_64-unknown-linux-gnu
rust: stable
os: ubuntu-latest
- thing: macos-x86_64
target: x86_64-apple-darwin
rust: stable
os: macos-latest
- thing: x86_64-msvc
target: x86_64-pc-windows-msvc
rust: stable-x86_64-msvc
os: windows-latest
steps:
- uses: actions/checkout@v1
- name: Install Rust (rustup)
if: matrix.os != 'macos-latest'
run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }}
shell: bash
- name: Install Lua (ubuntu)
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update -y
sudo apt-get install -y --no-install-recommends liblua5.3-dev liblua5.2-dev liblua5.1-0-dev libluajit-5.1-dev
- name: Install Rust (macos)
if: matrix.os == 'macos-latest'
run: |
curl https://sh.rustup.rs | sh -s -- -y
echo ::add-path::$HOME/.cargo/bin
shell: bash
- name: Install GCC (aarch64-linux)
run: |
sudo apt-get update -y
sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libc6-dev-arm64-cross
if: matrix.thing == 'aarch64-linux'
shell: bash
- run: rustup target add ${{ matrix.target }}
- name: Build (Lua 5.3/5.2/5.1 and LuaJIT vendored+async)
run: |
for FEATURE in lua53 lua52 lua51 luajit; do
echo "Building $FEATURE"
cargo build --target ${{ matrix.target }} --release --no-default-features --features "$FEATURE vendored async"
done
shell: bash
- name: Build (Lua 5.3/5.2/5.1 and LuaJIT via pkg-config)
if: matrix.os == 'ubuntu-latest'
run: |
for FEATURE in lua53 lua52 lua51 luajit; do
echo "Building $FEATURE"
cargo build --target ${{ matrix.target }} --release --no-default-features --features $FEATURE
done
shell: bash
test_linux:
name: Test on Linux
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v1
- name: Install Rust
run: rustup update stable --no-self-update && rustup default stable
shell: bash
- name: Run tests (Lua 5.3 vendored)
run: cargo test --release --no-default-features --features "lua53 vendored"
shell: bash
- name: Run tests (Lua 5.3 vendored+async)
run: cargo test --release --no-default-features --features "lua53 vendored async"
shell: bash
- name: Run tests (Lua 5.2 vendored)
run: cargo test --release --no-default-features --features "lua52 vendored"
shell: bash
- name: Run tests (Lua 5.1 vendored)
run: cargo test --release --no-default-features --features "lua51 vendored"
shell: bash
- name: Run tests (Lua 5.1 vendored+async)
run: cargo test --release --no-default-features --features "lua51 vendored async"
shell: bash
- name: Run tests (LuaJIT vendored)
run: cargo test --release --no-default-features --features "luajit vendored"
shell: bash
- name: Run tests (LuaJIT vendored+async)
run: cargo test --release --no-default-features --features "luajit vendored async"
shell: bash
- name: Run compile test (Lua 5.3)
run: |
sudo apt-get update -y
sudo apt-get install -y --no-install-recommends liblua5.3-dev
cargo test --release --no-default-features --features "lua53 vendored" -- --ignored
test_macos:
name: Test on MacOS
runs-on: macos-latest
needs: build
steps:
- uses: actions/checkout@v1
- name: Install Rust
run: |
curl https://sh.rustup.rs | sh -s -- -y
echo ::add-path::$HOME/.cargo/bin
shell: bash
- name: Run tests (Lua 5.3 vendored)
run: cargo test --release --no-default-features --features "lua53 vendored"
shell: bash
- name: Run tests (Lua 5.2 vendored)
run: cargo test --release --no-default-features --features "lua52 vendored"
shell: bash
- name: Run tests (Lua 5.1 vendored)
run: cargo test --release --no-default-features --features "lua51 vendored"
shell: bash
- name: Run tests (LuaJIT vendored)
run: cargo test --release --no-default-features --features "luajit vendored"
shell: bash
test_windows:
name: Test on Windows
runs-on: windows-latest
needs: build
steps:
- uses: actions/checkout@v1
- name: Install Rust
run: rustup update stable --no-self-update && rustup default stable
shell: bash
- name: Run tests (Lua 5.3 vendored)
run: cargo test --release --no-default-features --features "lua53 vendored"
shell: bash
- name: Run tests (Lua 5.2 vendored)
run: cargo test --release --no-default-features --features "lua52 vendored"
shell: bash
- name: Run tests (Lua 5.1 vendored)
run: cargo test --release --no-default-features --features "lua51 vendored"
shell: bash
- name: Run tests (LuaJIT vendored)
run: cargo test --release --no-default-features --features "luajit vendored"
shell: bash
rustfmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Install Rust
run: rustup update stable && rustup default stable && rustup component add rustfmt
- run: cargo fmt -- --check
+34 -9
View File
@@ -1,21 +1,26 @@
[package]
name = "mlua"
version = "0.2.0"
version = "0.3.2"
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"]
edition = "2018"
repository = "https://github.com/khvzak/mlua"
documentation = "https://docs.rs/mlua"
readme = "README.md"
keywords = ["lua", "luajit"]
categories = ["api-bindings"]
keywords = ["lua", "luajit", "async", "futures"]
categories = ["api-bindings", "asynchronous"]
license = "MIT"
links = "lua"
build = "build/main.rs"
description = """
High level bindings to Lua 5.1/5.2/5.3 (including LuaJIT)
with support of writing native lua modules in Rust.
with async/await features and support of writing native lua modules in Rust.
"""
[badges]
travis-ci = { repository = "khvzak/mlua", branch = "master" }
# github-actions = { repository = "khvzak/mlua", workflow = "CI" }
[package.metadata.docs.rs]
features = ["async"]
[workspace]
members = [
@@ -28,20 +33,40 @@ lua53 = []
lua52 = []
lua51 = []
luajit = []
vendored = ["lua-src", "luajit-src"]
async = ["futures-core", "futures-task", "futures-util"]
[dependencies]
num-traits = { version = "0.2.6" }
bstr = { version = "0.2", features = ["std"], default_features = false }
lazy_static = { version = "1.4" }
num-traits = { version = "0.2.11" }
futures-core = { version = "0.3.4", optional = true }
futures-task = { version = "0.3.4", optional = true }
futures-util = { version = "0.3.4", optional = true }
[build-dependencies]
cc = { version = "1.0" }
pkg-config = { version = "0.3.11" }
pkg-config = { version = "0.3.17" }
lua-src = { version = "535.0.1", optional = true }
luajit-src = { version = "210.0.0", optional = true }
[dev-dependencies]
rustyline = "5.0"
criterion = "0.2.0"
rustyline = "6.0"
criterion = "0.3"
trybuild = "1.0"
futures = "0.3.4"
hyper = "0.13"
tokio = { version = "0.2.18", features = ["full"] }
futures-timer = "3.0"
[[bench]]
name = "benchmark"
harness = false
[[example]]
name = "async_tcp_server"
required-features = ["async"]
[[example]]
name = "async_http_client"
required-features = ["async"]
+35 -9
View File
@@ -1,8 +1,8 @@
# mlua
[![Build Status]][circleci] [![Latest Version]][crates.io] [![API Documentation]][docs.rs]
[![Build Status]][github-actions] [![Latest Version]][crates.io] [![API Documentation]][docs.rs]
[Build Status]: https://img.shields.io/circleci/project/github/khvzak/mlua.svg
[circleci]: https://circleci.com/gh/khvzak/mlua
[Build Status]: https://github.com/khvzak/mlua/workflows/CI/badge.svg
[github-actions]: https://github.com/khvzak/mlua/actions
[Latest Version]: https://img.shields.io/crates/v/mlua.svg
[crates.io]: https://crates.io/crates/mlua
[API Documentation]: https://docs.rs/mlua/badge.svg
@@ -16,14 +16,40 @@ The `mlua` goal is to be an easy to use, practical and flexible API between Rust
*__not__* always 100% safe due to the Lua VM nature. Also, `mlua` provides a way to write native lua
modules in Rust.
### Usage
## Usage
#### standalone mode
### Async/await support
Starting from 0.3, mlua supports async/await for all Lua versions. This works using Lua [coroutines](https://www.lua.org/manual/5.3/manual.html#2.6) and require running [Thread](https://docs.rs/mlua/latest/mlua/struct.Thread.html) along with enabling `async` feature in `Cargo.toml`.
**Examples**:
- [HTTP Client](examples/async_http_client.rs)
- [TCP Server](examples/async_tcp_server.rs)
### Choosing Lua version
The following features could be used to choose Lua version: `lua53` (default), `lua52`, `lua51` and `luajit`.
By default mlua uses `pkg-config` tool to find lua includes and lib.
In most cases it works as desired, although sometimes could be more preferable to use a custom lua library.
To achieve this, mlua supports `LUA_INC`, `LUA_LIB`, `LUA_LIB_NAME` and `LUA_LINK` environment variables.
`LUA_LINK` is optional and may be `dylib` (a dynamic library) or `static` (a static library, `.a` archive).
An example how to use them:
``` sh
my_project $ LUA_INC=$HOME/tmp/lua-5.2.4/src LUA_LIB=$HOME/tmp/lua-5.2.4/src LUA_LIB_NAME=lua LUA_LINK=static cargo build
```
`mlua` also supports vendored lua/luajit using the auxilary crates [lua-src](https://crates.io/crates/lua-src) and
[luajit-src](https://crates.io/crates/luajit-src).
Just enable the `vendored` feature and cargo will automatically build and link specified lua/luajit version. This is the easiest way to get started with mlua.
### Standalone mode
Add to `Cargo.toml` :
``` toml
[dependencies]
mlua = "0.2"
mlua = "0.3"
```
`main.rs`
@@ -46,7 +72,7 @@ fn main() -> LuaResult<()> {
}
```
#### module mode
### Module mode
Add to `Cargo.toml` :
@@ -55,8 +81,8 @@ Add to `Cargo.toml` :
crate-type = ["cdylib"]
[dependencies]
mlua = "0.2"
mlua_derive = "0.2"
mlua = "0.3"
mlua_derive = "0.3"
```
`lib.rs` :
-189
View File
@@ -1,189 +0,0 @@
use std::env;
use std::fs::File;
use std::io::{BufRead, BufReader, Error, ErrorKind, Result};
use std::ops::Bound;
use std::path::{Path, PathBuf};
use std::process::Command;
trait CommandExt {
fn execute(&mut self) -> Result<()>;
}
impl CommandExt for Command {
/// Execute the command and return an error if it exited with a failure status.
fn execute(&mut self) -> Result<()> {
self.status()
.and_then(|status| {
if status.success() {
Ok(())
} else {
Err(Error::new(ErrorKind::Other, "non-zero exit code"))
}
})
.map_err(|_| {
Error::new(
ErrorKind::Other,
format!("The command {:?} did not run successfully.", self),
)
})
}
}
fn use_custom_lua<S: AsRef<str>>(include_dir: &S, lib_dir: &S, lua_lib: &S) -> Result<String> {
let mut version_found = String::new();
// Find LUA_VERSION_NUM
let mut lua_h_path = PathBuf::from(include_dir.as_ref());
lua_h_path.push("lua.h");
let f = File::open(lua_h_path)?;
let reader = BufReader::new(f);
for line in reader.lines() {
let line = line?;
let parts = line.split_whitespace().collect::<Vec<_>>();
if parts.len() == 3 && parts[1] == "LUA_VERSION_NUM" {
version_found = parts[2].to_string();
}
}
let mut static_link = "";
if env::var("LUA_LINK").unwrap_or(String::new()) == "static" {
static_link = "static=";
}
println!("cargo:rustc-link-search=native={}", lib_dir.as_ref());
println!("cargo:rustc-link-lib={}{}", static_link, lua_lib.as_ref());
Ok(version_found)
}
fn build_glue<P: AsRef<Path> + std::fmt::Debug>(include_paths: &[P]) {
let build_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
// Ensure the presence of glue.rs
// if build_dir.join("glue.rs").exists() {
// return;
// }
let mut config = cc::Build::new();
for path in include_paths {
config.include(path);
}
// Compile and run glue.c
let glue = build_dir.join("glue");
config
.get_compiler()
.to_command()
.arg("src/ffi/glue/glue.c")
.arg("-o")
.arg(&glue)
.execute()
.unwrap();
Command::new(glue)
.arg(build_dir.join("glue.rs"))
.execute()
.unwrap();
}
fn main() {
let include_dir = env::var("LUA_INC").unwrap_or(String::new());
let lib_dir = env::var("LUA_LIB").unwrap_or(String::new());
let lua_lib = env::var("LUA_LIB_NAME").unwrap_or(String::new());
println!("cargo:rerun-if-env-changed=LUA_INC");
println!("cargo:rerun-if-env-changed=LUA_LIB");
println!("cargo:rerun-if-env-changed=LUA_LIB_NAME");
println!("cargo:rerun-if-env-changed=LUA_LINK");
println!("cargo:rerun-if-changed=src/ffi/glue/glue.c");
if include_dir != "" && lib_dir != "" && lua_lib != "" {
let _version = use_custom_lua(&include_dir, &lib_dir, &lua_lib).unwrap();
build_glue(&[include_dir]);
return;
}
// Find lua via pkg-config
#[cfg(not(any(
feature = "lua53",
feature = "lua52",
feature = "lua51",
feature = "luajit"
)))]
panic!("You must enable one of the features: lua53, lua52, lua51, luajit");
#[cfg(all(
feature = "lua53",
any(feature = "lua52", feature = "lua51", feature = "luajit")
))]
panic!("You can enable only one of the features: lua53, lua52, lua51, luajit");
#[cfg(all(feature = "lua52", any(feature = "lua51", feature = "luajit")))]
panic!("You can enable only one of the features: lua53, lua52, lua51, luajit");
#[cfg(all(feature = "lua51", feature = "luajit"))]
panic!("You can enable only one of the features: lua53, lua52, lua51, luajit");
#[cfg(feature = "lua53")]
{
let mut lua = pkg_config::Config::new()
.range_version((Bound::Included("5.3"), Bound::Excluded("5.4")))
.probe("lua");
if lua.is_err() {
lua = pkg_config::Config::new().probe("lua5.3");
}
match lua {
Ok(lua) => build_glue(&lua.include_paths),
Err(err) => panic!(err),
};
}
#[cfg(feature = "lua52")]
{
let mut lua = pkg_config::Config::new()
.range_version((Bound::Included("5.2"), Bound::Excluded("5.3")))
.probe("lua");
if lua.is_err() {
lua = pkg_config::Config::new().probe("lua5.2");
}
match lua {
Ok(lua) => build_glue(&lua.include_paths),
Err(_) => panic!("Lua 5.2 not found"),
};
}
#[cfg(feature = "lua51")]
{
let mut lua = pkg_config::Config::new()
.range_version((Bound::Included("5.1"), Bound::Excluded("5.2")))
.probe("lua");
if lua.is_err() {
lua = pkg_config::Config::new().probe("lua5.1");
}
match lua {
Ok(lua) => build_glue(&lua.include_paths),
Err(err) => panic!(err),
};
}
#[cfg(feature = "luajit")]
{
let lua = pkg_config::Config::new()
.range_version((Bound::Included("2.0.5"), Bound::Unbounded))
.probe("luajit");
match lua {
Ok(lua) => build_glue(&lua.include_paths),
Err(err) => panic!(err),
};
}
}
+106
View File
@@ -0,0 +1,106 @@
use std::env;
use std::ffi::OsString;
use std::fs::File;
use std::io::{BufRead, BufReader, Result};
use std::ops::Bound;
use std::path::{Path, PathBuf};
pub fn probe_lua() -> PathBuf {
let include_dir = env::var_os("LUA_INC").unwrap_or(OsString::new());
let lib_dir = env::var_os("LUA_LIB").unwrap_or(OsString::new());
let lua_lib = env::var_os("LUA_LIB_NAME").unwrap_or(OsString::new());
println!("cargo:rerun-if-env-changed=LUA_INC");
println!("cargo:rerun-if-env-changed=LUA_LIB");
println!("cargo:rerun-if-env-changed=LUA_LIB_NAME");
println!("cargo:rerun-if-env-changed=LUA_LINK");
if include_dir != "" && lib_dir != "" && lua_lib != "" {
let _version = use_custom_lua(&include_dir, &lib_dir, &lua_lib).unwrap();
return PathBuf::from(include_dir);
}
// Find using via pkg-config
#[cfg(feature = "lua53")]
{
let mut lua = pkg_config::Config::new()
.range_version((Bound::Included("5.3"), Bound::Excluded("5.4")))
.probe("lua");
if lua.is_err() {
lua = pkg_config::Config::new().probe("lua5.3");
}
return lua.unwrap().include_paths[0].clone();
}
#[cfg(feature = "lua52")]
{
let mut lua = pkg_config::Config::new()
.range_version((Bound::Included("5.2"), Bound::Excluded("5.3")))
.probe("lua");
if lua.is_err() {
lua = pkg_config::Config::new().probe("lua5.2");
}
return lua.unwrap().include_paths[0].clone();
}
#[cfg(feature = "lua51")]
{
let mut lua = pkg_config::Config::new()
.range_version((Bound::Included("5.1"), Bound::Excluded("5.2")))
.probe("lua");
if lua.is_err() {
lua = pkg_config::Config::new().probe("lua5.1");
}
return lua.unwrap().include_paths[0].clone();
}
#[cfg(feature = "luajit")]
{
let lua = pkg_config::Config::new()
.range_version((Bound::Included("2.1.0"), Bound::Unbounded))
.probe("luajit");
return lua.unwrap().include_paths[0].clone();
}
}
fn use_custom_lua<S: AsRef<Path>>(include_dir: &S, lib_dir: &S, lua_lib: &S) -> Result<String> {
let mut version_found = String::new();
// Find LUA_VERSION_NUM
let mut lua_h_path = include_dir.as_ref().to_owned();
lua_h_path.push("lua.h");
let f = File::open(lua_h_path)?;
let reader = BufReader::new(f);
for line in reader.lines() {
let line = line?;
let parts = line.split_whitespace().collect::<Vec<_>>();
if parts.len() == 3 && parts[1] == "LUA_VERSION_NUM" {
version_found = parts[2].to_string();
}
}
let mut link_lib = String::new();
if env::var("LUA_LINK").unwrap_or(String::new()) == "static" {
link_lib = "static=".to_string();
}
println!(
"cargo:rustc-link-search=native={}",
lib_dir.as_ref().display()
);
println!(
"cargo:rustc-link-lib={}{}",
link_lib,
lua_lib.as_ref().display()
);
Ok(version_found)
}
+20
View File
@@ -0,0 +1,20 @@
use std::path::PathBuf;
#[cfg(any(feature = "lua53", feature = "lua52", feature = "lua51"))]
use lua_src;
#[cfg(feature = "luajit")]
use luajit_src;
pub fn probe_lua() -> PathBuf {
#[cfg(feature = "lua53")]
let artifacts = lua_src::Build::new().build(lua_src::Lua53);
#[cfg(feature = "lua52")]
let artifacts = lua_src::Build::new().build(lua_src::Lua52);
#[cfg(feature = "lua51")]
let artifacts = lua_src::Build::new().build(lua_src::Lua51);
#[cfg(feature = "luajit")]
let artifacts = luajit_src::Build::new().build();
artifacts.print_cargo_metadata();
artifacts.include_dir().to_owned()
}
+86
View File
@@ -0,0 +1,86 @@
#![allow(unreachable_code)]
use std::env;
use std::io::{Error, ErrorKind, Result};
use std::path::{Path, PathBuf};
use std::process::Command;
#[cfg_attr(feature = "vendored", path = "find_vendored.rs")]
#[cfg_attr(not(feature = "vendored"), path = "find_normal.rs")]
mod find;
trait CommandExt {
fn execute(&mut self) -> Result<()>;
}
impl CommandExt for Command {
/// Execute the command and return an error if it exited with a failure status.
fn execute(&mut self) -> Result<()> {
self.status()
.and_then(|status| {
if status.success() {
Ok(())
} else {
Err(Error::new(ErrorKind::Other, "non-zero exit code"))
}
})
.map_err(|_| {
Error::new(
ErrorKind::Other,
format!("The command {:?} did not run successfully.", self),
)
})
}
}
fn build_glue<P: AsRef<Path> + std::fmt::Debug>(include_path: &P) {
let build_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
let mut config = cc::Build::new();
config.include(include_path);
// Compile and run glue.c
let glue = build_dir.join("glue");
config
.get_compiler()
.to_command()
.arg("src/ffi/glue/glue.c")
.arg("-o")
.arg(&glue)
.execute()
.unwrap();
Command::new(glue)
.arg(build_dir.join("glue.rs"))
.execute()
.unwrap();
}
fn main() {
#[cfg(not(any(
feature = "lua53",
feature = "lua52",
feature = "lua51",
feature = "luajit"
)))]
panic!("You must enable one of the features: lua53, lua52, lua51, luajit");
#[cfg(all(
feature = "lua53",
any(feature = "lua52", feature = "lua51", feature = "luajit")
))]
panic!("You can enable only one of the features: lua53, lua52, lua51, luajit");
#[cfg(all(feature = "lua52", any(feature = "lua51", feature = "luajit")))]
panic!("You can enable only one of the features: lua53, lua52, lua51, luajit");
#[cfg(all(feature = "lua51", feature = "luajit"))]
panic!("You can enable only one of the features: lua53, lua52, lua51, luajit");
#[cfg(all(feature = "lua51", feature = "luajit"))]
panic!("You can enable only one of the features: lua53, lua52, lua51, luajit");
let include_dir = find::probe_lua();
build_glue(&include_dir);
}
+83
View File
@@ -0,0 +1,83 @@
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use bstr::BString;
use hyper::{body::Body as HyperBody, Client as HyperClient};
use tokio::stream::StreamExt;
use mlua::{Error, Lua, Result, UserData, UserDataMethods};
#[derive(Clone)]
struct BodyReader(Rc<RefCell<HyperBody>>);
impl BodyReader {
fn new(body: HyperBody) -> Self {
BodyReader(Rc::new(RefCell::new(body)))
}
}
impl UserData for BodyReader {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("read", |_, reader, ()| async move {
let mut reader = reader.0.borrow_mut();
let bytes = reader.try_next().await.map_err(Error::external)?;
if let Some(bytes) = bytes {
return Ok(Some(BString::from(bytes.as_ref())));
}
Ok(None)
});
}
}
#[tokio::main]
async fn main() -> Result<()> {
let lua = Lua::new();
let fetch_url = lua.create_async_function(|lua, uri: String| async move {
let client = HyperClient::new();
let uri = uri.parse().map_err(Error::external)?;
let resp = client.get(uri).await.map_err(Error::external)?;
let lua_resp = lua.create_table()?;
lua_resp.set("status", resp.status().as_u16())?;
let mut headers = HashMap::new();
for (key, value) in resp.headers().iter() {
headers
.entry(key.as_str())
.or_insert(Vec::new())
.push(value.to_str().unwrap());
}
lua_resp.set("headers", headers)?;
lua_resp.set("body", BodyReader::new(resp.into_body()))?;
Ok(lua_resp)
})?;
let globals = lua.globals();
globals.set("fetch_url", fetch_url)?;
let f = lua
.load(
r#"
local res = fetch_url(...);
print(res.status)
for key, vals in pairs(res.headers) do
for _, val in ipairs(vals) do
print(key..": "..val)
end
end
repeat
local body = res.body:read()
if body then
print(body)
end
until not body
"#,
)
.into_function()?;
f.call_async("http://httpbin.org/ip").await
}
+107
View File
@@ -0,0 +1,107 @@
use std::cell::RefCell;
use std::net::Shutdown;
use std::rc::Rc;
use bstr::BString;
use tokio::net::{TcpListener, TcpStream};
use tokio::prelude::*;
use tokio::task;
use mlua::{Function, Lua, Result, UserData, UserDataMethods};
#[derive(Clone)]
struct LuaTcp;
#[derive(Clone)]
struct LuaTcpListener(Rc<RefCell<TcpListener>>);
#[derive(Clone)]
struct LuaTcpStream(Rc<RefCell<TcpStream>>);
impl UserData for LuaTcp {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_function("bind", |_, addr: String| async move {
let listener = TcpListener::bind(addr).await?;
Ok(LuaTcpListener(Rc::new(RefCell::new(listener))))
});
}
}
impl UserData for LuaTcpListener {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("accept", |_, listener, ()| async move {
let (stream, _) = listener.0.borrow_mut().accept().await?;
Ok(LuaTcpStream(Rc::new(RefCell::new(stream))))
});
}
}
impl UserData for LuaTcpStream {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("peer_addr", |_, stream, ()| async move {
Ok(stream.0.borrow().peer_addr()?.to_string())
});
methods.add_async_method("read", |_, stream, size: usize| async move {
let mut buf = vec![0; size];
let n = stream.0.borrow_mut().read(&mut buf).await?;
buf.truncate(n);
Ok(BString::from(buf))
});
methods.add_async_method("write", |_, stream, data: BString| async move {
let n = stream.0.borrow_mut().write(&data).await?;
Ok(n)
});
methods.add_method("close", |_, stream, ()| {
stream.0.borrow().shutdown(Shutdown::Both)?;
Ok(())
});
}
}
#[tokio::main]
async fn main() -> Result<()> {
let lua = Lua::new();
let spawn = lua.create_function(move |_, func: Function| {
task::spawn_local(async move { func.call_async::<_, ()>(()).await.unwrap() });
Ok(())
})?;
let globals = lua.globals();
globals.set("tcp", LuaTcp)?;
globals.set("spawn", spawn)?;
let server = lua
.load(
r#"
local addr = ...
local listener = tcp.bind(addr)
print("listening on "..addr)
while true do
local stream = listener:accept()
local peer_addr = stream:peer_addr()
print("connected from "..peer_addr)
spawn(function()
while true do
local data = stream:read(100)
data = data:match("^%s*(.-)%s*$") -- trim
print("["..peer_addr.."] "..data)
stream:write("got: "..data.."\n")
if data == "exit" then
stream:close()
break
end
end
end)
end
"#,
)
.into_function()?;
task::LocalSet::new()
.run_until(server.call_async::<_, ()>("0.0.0.0:1234"))
.await
}
-28
View File
@@ -164,34 +164,6 @@ fn main() -> Result<()> {
< f32::EPSILON
);
// Normally, Rust types passed to `Lua` must be `Send`, because `Lua` itself is `Send`, and
// must be `'static`, because there is no way to be sure of their lifetime inside the Lua
// state. There is, however, a limited way to lift both of these requirements. You can
// call `Lua::scope` to create userdata and callbacks types that only live for as long
// as the call to scope, but do not have to be `Send` OR `'static`.
{
let mut rust_val = 0;
lua.scope(|scope| {
// We create a 'sketchy' Lua callback that holds a mutable reference to the variable
// `rust_val`. Outside of a `Lua::scope` call, this would not be allowed
// because it could be unsafe.
lua.globals().set(
"sketchy",
scope.create_function_mut(|_, ()| {
rust_val = 42;
Ok(())
})?,
)?;
lua.load("sketchy()").exec()
})?;
assert_eq!(rust_val, 42);
}
// We were able to run our 'sketchy' function inside the scope just fine. However, if we
// try to run our 'sketchy' function outside of the scope, the function we created will have
// been invalidated and we will generate an error. If our function wasn't invalidated, we
+2 -2
View File
@@ -1,7 +1,7 @@
[package]
name = "mlua_derive"
version = "0.2.0"
authors = ["Aleksandr Orlenko <zxteam@protonmail.com>"]
version = "0.3.0"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2018"
description = "Procedural macros for the mlua crate."
repository = "https://github.com/khvzak/mlua"
+47 -3
View File
@@ -122,7 +122,7 @@ impl<'lua> FromLua<'lua> for AnyUserData<'lua> {
}
}
impl<'lua, T: 'static + Send + UserData> ToLua<'lua> for T {
impl<'lua, T: 'static + UserData> ToLua<'lua> for T {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::UserData(lua.create_userdata(self)?))
}
@@ -167,7 +167,7 @@ impl<'lua> ToLua<'lua> for bool {
}
impl<'lua> FromLua<'lua> for bool {
fn from_lua(v: Value, _: &'lua Lua) -> Result<Self> {
fn from_lua(v: Value<'lua>, _: &'lua Lua) -> Result<Self> {
match v {
Value::Nil => Ok(false),
Value::Boolean(b) => Ok(b),
@@ -183,7 +183,7 @@ impl<'lua> ToLua<'lua> for LightUserData {
}
impl<'lua> FromLua<'lua> for LightUserData {
fn from_lua(value: Value, _: &'lua Lua) -> Result<Self> {
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
match value {
Value::LightUserData(ud) => Ok(ud),
_ => Err(Error::FromLuaConversionError {
@@ -373,6 +373,50 @@ macro_rules! lua_convert_float {
lua_convert_float!(f32);
lua_convert_float!(f64);
impl<'lua, T> ToLua<'lua> for &'_ [T]
where
T: Clone + ToLua<'lua>,
{
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(
lua.create_sequence_from(self.into_iter().cloned())?,
))
}
}
macro_rules! lua_convert_array {
($($N:literal)+) => {
$(
impl<'lua, T> ToLua<'lua> for [T; $N]
where
T: Clone + ToLua<'lua>,
{
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
(&self).to_lua(lua)
}
}
impl<'lua, T> ToLua<'lua> for &'_ [T; $N]
where
T: Clone + ToLua<'lua>,
{
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(
lua.create_sequence_from(self.iter().cloned())?,
))
}
}
)+
}
}
lua_convert_array! {
0 1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29
30 31 32
}
impl<'lua, T: ToLua<'lua>> ToLua<'lua> for Vec<T> {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_sequence_from(self)?))
+30 -9
View File
@@ -1,8 +1,11 @@
use std::error::Error as StdError;
use std::fmt;
use std::io::Error as IoError;
use std::net::AddrParseError;
use std::rc::Rc;
use std::result::Result as StdResult;
use std::str::Utf8Error;
use std::string::String as StdString;
use std::sync::Arc;
/// Error type returned by `mlua` methods.
#[derive(Debug, Clone)]
@@ -115,7 +118,7 @@ pub enum Error {
/// Lua call stack backtrace.
traceback: StdString,
/// Original error returned by the Rust code.
cause: Arc<Error>,
cause: Rc<Error>,
},
/// A custom error.
///
@@ -124,7 +127,7 @@ pub enum Error {
/// Returning `Err(ExternalError(...))` from a Rust callback will raise the error as a Lua
/// error. The Rust code that originally invoked the Lua code then receives a `CallbackError`,
/// from which the original error (and a stack traceback) can be recovered.
ExternalError(Arc<dyn StdError + Send + Sync>),
ExternalError(Rc<dyn StdError>),
}
/// A specialized `Result` type used by `mlua`'s API.
@@ -184,10 +187,10 @@ impl fmt::Display for Error {
Error::MismatchedRegistryKey => {
write!(fmt, "RegistryKey used from different Lua state")
}
Error::CallbackError { ref traceback, ref cause } => {
write!(fmt, "callback error: {}: {}", cause, traceback)
Error::CallbackError { ref traceback, .. } => {
write!(fmt, "callback error: {}", traceback)
}
Error::ExternalError(ref err) => write!(fmt, "external error: {}", err),
Error::ExternalError(ref err) => write!(fmt, "{}", err),
}
}
}
@@ -196,14 +199,14 @@ impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match *self {
Error::CallbackError { ref cause, .. } => Some(cause.as_ref()),
Error::ExternalError(ref err) => Some(err.as_ref()),
Error::ExternalError(ref err) => err.source(),
_ => None,
}
}
}
impl Error {
pub fn external<T: Into<Box<dyn StdError + Send + Sync>>>(err: T) -> Error {
pub fn external<T: Into<Box<dyn StdError>>>(err: T) -> Error {
Error::ExternalError(err.into().into())
}
}
@@ -214,7 +217,7 @@ pub trait ExternalError {
impl<E> ExternalError for E
where
E: Into<Box<dyn StdError + Send + Sync>>,
E: Into<Box<dyn StdError>>,
{
fn to_lua_err(self) -> Error {
Error::external(self)
@@ -233,3 +236,21 @@ where
self.map_err(|e| e.to_lua_err())
}
}
impl std::convert::From<AddrParseError> for Error {
fn from(err: AddrParseError) -> Self {
Error::external(err)
}
}
impl std::convert::From<IoError> for Error {
fn from(err: IoError) -> Self {
Error::external(err)
}
}
impl std::convert::From<Utf8Error> for Error {
fn from(err: Utf8Error) -> Self {
Error::external(err)
}
}
+8 -2
View File
@@ -263,15 +263,21 @@ int main(int argc, const char **argv) {
RS_STR("LUA_IOLIBNAME", LUA_IOLIBNAME),
RS_STR("LUA_OSLIBNAME", LUA_OSLIBNAME),
RS_STR("LUA_STRLIBNAME", LUA_STRLIBNAME),
#if LUA_VERSION_NUM == 503
#ifdef LUA_UTF8LIBNAME
RS_STR("LUA_UTF8LIBNAME", LUA_UTF8LIBNAME),
#endif
#if LUA_VERSION_NUM >= 502
#ifdef LUA_BITLIBNAME
RS_STR("LUA_BITLIBNAME", LUA_BITLIBNAME),
#endif
RS_STR("LUA_MATHLIBNAME", LUA_MATHLIBNAME),
RS_STR("LUA_DBLIBNAME", LUA_DBLIBNAME),
RS_STR("LUA_LOADLIBNAME", LUA_LOADLIBNAME),
#ifdef LUA_JITLIBNAME
RS_STR("LUA_JITLIBNAME", LUA_JITLIBNAME),
#endif
#ifdef LUA_FFILIBNAME
RS_STR("LUA_FFILIBNAME", LUA_FFILIBNAME),
#endif
};
if (!write_items(f, glue_entries)) {
+13 -4
View File
@@ -32,12 +32,15 @@ pub use super::glue::{
LUA_STRLIBNAME, LUA_TABLIBNAME,
};
#[cfg(any(feature = "lua53", feature = "lua52"))]
pub use super::glue::LUA_BITLIBNAME;
#[cfg(feature = "lua53")]
pub use super::glue::LUA_UTF8LIBNAME;
#[cfg(any(feature = "lua52", feature = "luajit"))]
pub use super::glue::LUA_BITLIBNAME;
#[cfg(feature = "luajit")]
pub use super::glue::{LUA_FFILIBNAME, LUA_JITLIBNAME};
extern "C" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
#[cfg(any(feature = "lua53", feature = "lua52"))]
@@ -48,11 +51,17 @@ extern "C" {
pub fn luaopen_string(L: *mut lua_State) -> c_int;
#[cfg(feature = "lua53")]
pub fn luaopen_utf8(L: *mut lua_State) -> c_int;
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(feature = "lua52")]
pub fn luaopen_bit32(L: *mut lua_State) -> c_int;
pub fn luaopen_math(L: *mut lua_State) -> c_int;
pub fn luaopen_debug(L: *mut lua_State) -> c_int;
pub fn luaopen_package(L: *mut lua_State) -> c_int;
#[cfg(feature = "luajit")]
pub fn luaopen_bit(L: *mut lua_State) -> c_int;
#[cfg(feature = "luajit")]
pub fn luaopen_jit(L: *mut lua_State) -> c_int;
#[cfg(feature = "luajit")]
pub fn luaopen_ffi(L: *mut lua_State) -> c_int;
pub fn luaL_openlibs(L: *mut lua_State);
}
+18 -12
View File
@@ -155,12 +155,12 @@ pub use self::lua::{
lua_yield,
};
#[cfg(any(feature = "lua53", feature = "lua52"))]
pub use self::lua::{lua_callk, lua_pcallk, lua_upvalueid, lua_upvaluejoin, lua_yieldk};
#[cfg(feature = "lua53")]
pub use self::lua::{lua_isyieldable, lua_version};
#[cfg(any(feature = "lua53", feature = "lua52"))]
pub use self::lua::{lua_callk, lua_pcallk, lua_upvalueid, lua_upvaluejoin, lua_yieldk};
// auxiliary library types
pub use self::lauxlib::luaL_Reg;
@@ -186,11 +186,14 @@ pub use self::lualib::{
luaopen_package, luaopen_string, luaopen_table,
};
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(feature = "lua53")]
pub use self::lualib::{luaopen_coroutine, luaopen_utf8};
#[cfg(feature = "lua52")]
pub use self::lualib::{luaopen_bit32, luaopen_coroutine};
#[cfg(feature = "lua53")]
pub use self::lualib::luaopen_utf8;
#[cfg(feature = "luajit")]
pub use self::lualib::{luaopen_bit, luaopen_ffi, luaopen_jit};
// constants from lua.h
pub use self::lua::{
@@ -203,14 +206,14 @@ pub use self::lua::{
LUA_TTABLE, LUA_TTHREAD, LUA_TUSERDATA, LUA_YIELD,
};
#[cfg(any(feature = "lua53", feature = "lua52"))]
pub use self::lua::{LUA_ERRGCMM, LUA_GCISRUNNING, LUA_RIDX_GLOBALS, LUA_RIDX_MAINTHREAD};
#[cfg(feature = "lua53")]
pub use self::lua::{
LUA_OPBAND, LUA_OPBNOT, LUA_OPBOR, LUA_OPBXOR, LUA_OPIDIV, LUA_OPSHL, LUA_OPSHR,
};
#[cfg(any(feature = "lua53", feature = "lua52"))]
pub use self::lua::{LUA_ERRGCMM, LUA_GCISRUNNING, LUA_RIDX_GLOBALS, LUA_RIDX_MAINTHREAD};
#[cfg(any(feature = "lua51", feature = "luajit"))]
pub use self::lua::{LUA_ENVIRONINDEX, LUA_GLOBALSINDEX};
@@ -223,12 +226,15 @@ pub use self::lualib::{
LUA_STRLIBNAME, LUA_TABLIBNAME,
};
#[cfg(any(feature = "lua53", feature = "lua52"))]
pub use self::lualib::LUA_BITLIBNAME;
#[cfg(feature = "lua53")]
pub use self::lualib::LUA_UTF8LIBNAME;
#[cfg(any(feature = "lua52", feature = "luajit"))]
pub use self::lualib::LUA_BITLIBNAME;
#[cfg(feature = "luajit")]
pub use self::lualib::{LUA_FFILIBNAME, LUA_JITLIBNAME};
// Not actually defined in lua.h / luaconf.h
pub const LUA_MAX_UPVALUES: c_int = 255;
+50
View File
@@ -9,6 +9,9 @@ use crate::util::{
};
use crate::value::{FromLuaMulti, MultiValue, ToLuaMulti};
#[cfg(feature = "async")]
use {futures_core::future::LocalBoxFuture, futures_util::future};
/// Handle to an internal Lua function.
#[derive(Clone, Debug)]
pub struct Function<'lua>(pub(crate) LuaRef<'lua>);
@@ -86,6 +89,47 @@ impl<'lua> Function<'lua> {
R::from_lua_multi(results, lua)
}
/// Returns a Feature that, when polled, calls `self`, passing `args` as function arguments,
/// and drives the execution.
///
/// Internaly it wraps the function to an [`AsyncThread`].
///
/// # Examples
///
/// ```
/// use std::time::Duration;
/// use futures_timer::Delay;
/// # use mlua::{Lua, Result};
/// # #[tokio::main]
/// # async fn main() -> Result<()> {
/// # let lua = Lua::new();
///
/// let sleep = lua.create_async_function(move |_lua, n: u64| async move {
/// Delay::new(Duration::from_millis(n)).await;
/// Ok(())
/// })?;
///
/// sleep.call_async(10).await?;
///
/// # Ok(())
/// # }
/// ```
///
/// [`AsyncThread`]: struct.AsyncThread.html
#[cfg(feature = "async")]
pub fn call_async<'fut, A, R>(&self, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
{
let lua = self.0.lua;
match lua.create_thread(self.clone()) {
Ok(t) => Box::pin(t.into_async(args)),
Err(e) => Box::pin(future::err(e)),
}
}
/// Returns a function that, when called, calls `self`, passing `args` as the first set of
/// arguments.
///
@@ -160,3 +204,9 @@ impl<'lua> Function<'lua> {
}
}
}
impl<'lua> PartialEq for Function<'lua> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
+16 -3
View File
@@ -24,6 +24,12 @@
//! The [`UserData`] trait can be implemented by user-defined types to make them available to Lua.
//! Methods and operators to be used from Lua can be added using the [`UserDataMethods`] API.
//!
//! # Async/await support
//!
//! The [`create_async_function`] allows creating non-blocking functions that returns [`Future`].
//! Lua code with async capabilities can be executed by [`call_async`] family of functions or polling
//! [`AsyncThread`] using any runtime (eg. Tokio).
//!
//! [Lua programming language]: https://www.lua.org/
//! [`Lua`]: struct.Lua.html
//! [executing]: struct.Lua.html#method.exec
@@ -35,6 +41,10 @@
//! [`FromLuaMulti`]: trait.FromLuaMulti.html
//! [`UserData`]: trait.UserData.html
//! [`UserDataMethods`]: trait.UserDataMethods.html
//! [`create_async_function`]: struct.Lua.html#method.create_async_function
//! [`call_async`]: struct.Function.html#method.call_async
//! [`AsyncThread`]: struct.AsyncThread.html
//! [`Future`]: ../futures_core/future/trait.Future.html
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
// warnings at all.
@@ -49,7 +59,7 @@ mod ffi;
mod function;
mod lua;
mod multi;
mod scope;
mod stdlib;
mod string;
mod table;
mod thread;
@@ -64,12 +74,15 @@ pub use crate::error::{Error, ExternalError, ExternalResult, Result};
pub use crate::function::Function;
pub use crate::lua::{Chunk, Lua};
pub use crate::multi::Variadic;
pub use crate::scope::Scope;
pub use crate::stdlib::StdLib;
pub use crate::string::String;
pub use crate::table::{Table, TablePairs, TableSequence};
pub use crate::table::{Table, TableExt, TablePairs, TableSequence};
pub use crate::thread::{Thread, ThreadStatus};
pub use crate::types::{Integer, LightUserData, Number, RegistryKey};
pub use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods};
pub use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Value};
#[cfg(feature = "async")]
pub use crate::thread::AsyncThread;
pub mod prelude;
+556 -161
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -146,7 +146,7 @@ macro_rules! impl_tuple {
}
impl<'lua> FromLuaMulti<'lua> for () {
fn from_lua_multi(_: MultiValue, _: &'lua Lua) -> Result<Self> {
fn from_lua_multi(_: MultiValue<'lua>, _: &'lua Lua) -> Result<Self> {
Ok(())
}
}
+5 -2
View File
@@ -5,8 +5,11 @@ pub use crate::{
ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti,
Function as LuaFunction, Integer as LuaInteger, LightUserData as LuaLightUserData, Lua,
MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber,
RegistryKey as LuaRegistryKey, Result as LuaResult, Scope as LuaScope, String as LuaString,
Table as LuaTable, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence,
RegistryKey as LuaRegistryKey, Result as LuaResult, String as LuaString, Table as LuaTable,
TableExt as LuaTableExt, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence,
Thread as LuaThread, ThreadStatus as LuaThreadStatus, ToLua, ToLuaMulti,
UserData as LuaUserData, UserDataMethods as LuaUserDataMethods, Value as LuaValue,
};
#[cfg(feature = "async")]
pub use crate::AsyncThread as LuaAsyncThread;
-484
View File
@@ -1,484 +0,0 @@
use std::any::Any;
use std::cell::Cell;
use std::cell::RefCell;
use std::marker::PhantomData;
use std::mem;
use std::os::raw::c_void;
use std::rc::Rc;
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
use crate::lua::Lua;
use crate::types::{Callback, LuaRef};
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods};
use crate::util::{
assert_stack, init_userdata_metatable, protect_lua_closure, push_string, push_userdata,
take_userdata, StackGuard,
};
use crate::value::{FromLuaMulti, MultiValue, ToLuaMulti, Value};
/// Constructed by the [`Lua::scope`] method, allows temporarily creating Lua userdata and
/// callbacks that are not required to be Send or 'static.
///
/// See [`Lua::scope`] for more details.
///
/// [`Lua::scope`]: struct.Lua.html#method.scope
pub struct Scope<'lua, 'scope> {
lua: &'lua Lua,
destructors: RefCell<Vec<(LuaRef<'lua>, fn(LuaRef<'lua>) -> Box<dyn Any>)>>,
_scope_invariant: PhantomData<Cell<&'scope ()>>,
}
impl<'lua, 'scope> Scope<'lua, 'scope> {
pub(crate) fn new(lua: &'lua Lua) -> Scope<'lua, 'scope> {
Scope {
lua,
destructors: RefCell::new(Vec::new()),
_scope_invariant: PhantomData,
}
}
/// Wraps a Rust function or closure, creating a callable Lua function handle to it.
///
/// This is a version of [`Lua::create_function`] that creates a callback which expires on
/// scope drop. See [`Lua::scope`] for more details.
///
/// [`Lua::create_function`]: struct.Lua.html#method.create_function
/// [`Lua::scope`]: struct.Lua.html#method.scope
pub fn create_function<'callback, A, R, F>(&'callback self, func: F) -> Result<Function<'lua>>
where
A: FromLuaMulti<'callback>,
R: ToLuaMulti<'callback>,
F: 'scope + Fn(&'callback Lua, A) -> Result<R>,
{
// Safe, because 'scope must outlive 'callback (due to Self containing 'scope), however the
// callback itself must be 'scope lifetime, so the function should not be able to capture
// anything of 'callback lifetime. 'scope can't be shortened due to being invariant, and
// the 'callback lifetime here can't be enlarged due to coming from a universal
// quantification in Lua::scope.
//
// I hope I got this explanation right, but in any case this is tested with compiletest_rs
// to make sure callbacks can't capture handles with lifetime outside the scope, inside the
// scope, and owned inside the callback itself.
unsafe {
self.create_callback(Box::new(move |lua, args| {
func(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
}))
}
}
/// Wraps a Rust mutable closure, creating a callable Lua function handle to it.
///
/// This is a version of [`Lua::create_function_mut`] that creates a callback which expires
/// on scope drop. See [`Lua::scope`] and [`Scope::create_function`] for more details.
///
/// [`Lua::create_function_mut`]: struct.Lua.html#method.create_function_mut
/// [`Lua::scope`]: struct.Lua.html#method.scope
/// [`Scope::create_function`]: #method.create_function
pub fn create_function_mut<'callback, A, R, F>(
&'callback self,
func: F,
) -> Result<Function<'lua>>
where
A: FromLuaMulti<'callback>,
R: ToLuaMulti<'callback>,
F: 'scope + FnMut(&'callback Lua, A) -> Result<R>,
{
let func = RefCell::new(func);
self.create_function(move |lua, args| {
(&mut *func
.try_borrow_mut()
.map_err(|_| Error::RecursiveMutCallback)?)(lua, args)
})
}
/// Create a Lua userdata object from a custom userdata type.
///
/// This is a version of [`Lua::create_userdata`] that creates a userdata which expires on
/// scope drop, and does not require that the userdata type be Send (but still requires that the
/// UserData be 'static). See [`Lua::scope`] for more details.
///
/// [`Lua::create_userdata`]: struct.Lua.html#method.create_userdata
/// [`Lua::scope`]: struct.Lua.html#method.scope
pub fn create_static_userdata<T>(&self, data: T) -> Result<AnyUserData<'lua>>
where
T: 'static + UserData,
{
// Safe even though T may not be Send, because the parent Lua cannot be sent to another
// thread while the Scope is alive (or the returned AnyUserData handle even).
unsafe {
let u = self.lua.make_userdata(data)?;
self.destructors.borrow_mut().push((u.0.clone(), |u| {
let state = u.lua.state;
assert_stack(state, 2);
u.lua.push_ref(&u);
// We know the destructor has not run yet because we hold a reference to the
// userdata.
Box::new(take_userdata::<RefCell<T>>(state))
}));
Ok(u)
}
}
/// Create a Lua userdata object from a custom userdata type.
///
/// This is a version of [`Lua::create_userdata`] that creates a userdata which expires on
/// scope drop, and does not require that the userdata type be Send or 'static. See
/// [`Lua::scope`] for more details.
///
/// Lifting the requirement that the UserData type be 'static comes with some important
/// limitations, so if you only need to eliminate the Send requirement, it is probably better to
/// use [`Scope::create_static_userdata`] instead.
///
/// The main limitation that comes from using non-'static userdata is that the produced userdata
/// will no longer have a `TypeId` associated with it, becuase `TypeId` can only work for
/// 'static types. This means that it is impossible, once the userdata is created, to get a
/// reference to it back *out* of an `AnyUserData` handle. This also implies that the
/// "function" type methods that can be added via [`UserDataMethods`] (the ones that accept
/// `AnyUserData` as a first parameter) are vastly less useful. Also, there is no way to re-use
/// a single metatable for multiple non-'static types, so there is a higher cost associated with
/// creating the userdata metatable each time a new userdata is created.
///
/// [`create_static_userdata`]: #method.create_static_userdata
/// [`Lua::create_userdata`]: struct.Lua.html#method.create_userdata
/// [`Lua::scope`]: struct.Lua.html#method.scope
/// [`UserDataMethods`]: trait.UserDataMethods.html
pub fn create_nonstatic_userdata<T>(&self, data: T) -> Result<AnyUserData<'lua>>
where
T: 'scope + UserData,
{
let data = Rc::new(RefCell::new(data));
// 'callback outliving 'scope is a lie to make the types work out, required due to the
// inability to work with the more correct callback type that is universally quantified over
// 'lua. This is safe though, because `UserData::add_methods` does not get to pick the 'lua
// lifetime, so none of the static methods UserData types can add can possibly capture
// parameters.
fn wrap_method<'scope, 'lua, 'callback: 'scope, T: 'scope>(
scope: &Scope<'lua, 'scope>,
data: Rc<RefCell<T>>,
method: NonStaticMethod<'callback, T>,
) -> Result<Function<'lua>> {
// On methods that actually receive the userdata, we fake a type check on the passed in
// userdata, where we pretend there is a unique type per call to
// `Scope::create_nonstatic_userdata`. You can grab a method from a userdata and call
// it on a mismatched userdata type, which when using normal 'static userdata will fail
// with a type mismatch, but here without this check would proceed as though you had
// called the method on the original value (since we otherwise completely ignore the
// first argument).
let check_data = data.clone();
let check_ud_type = move |lua: &'callback Lua, value| {
if let Some(value) = value {
if let Value::UserData(u) = value {
unsafe {
assert_stack(lua.state, 1);
lua.push_ref(&u.0);
ffi::lua_getuservalue(lua.state, -1);
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
{
ffi::lua_pushinteger(lua.state, 1);
ffi::lua_gettable(lua.state, -2);
ffi::lua_remove(lua.state, -2);
}
return ffi::lua_touserdata(lua.state, -1)
== check_data.as_ptr() as *mut c_void;
}
}
}
false
};
match method {
NonStaticMethod::Method(method) => {
let method_data = data.clone();
let f = Box::new(move |lua, mut args: MultiValue<'callback>| {
if !check_ud_type(lua, args.pop_front()) {
return Err(Error::UserDataTypeMismatch);
}
let data = method_data
.try_borrow()
.map_err(|_| Error::UserDataBorrowError)?;
method(lua, &*data, args)
});
unsafe { scope.create_callback(f) }
}
NonStaticMethod::MethodMut(method) => {
let method = RefCell::new(method);
let method_data = data.clone();
let f = Box::new(move |lua, mut args: MultiValue<'callback>| {
if !check_ud_type(lua, args.pop_front()) {
return Err(Error::UserDataTypeMismatch);
}
let mut method = method
.try_borrow_mut()
.map_err(|_| Error::RecursiveMutCallback)?;
let mut data = method_data
.try_borrow_mut()
.map_err(|_| Error::UserDataBorrowMutError)?;
(&mut *method)(lua, &mut *data, args)
});
unsafe { scope.create_callback(f) }
}
NonStaticMethod::Function(function) => unsafe { scope.create_callback(function) },
NonStaticMethod::FunctionMut(function) => {
let function = RefCell::new(function);
let f = Box::new(move |lua, args| {
(&mut *function
.try_borrow_mut()
.map_err(|_| Error::RecursiveMutCallback)?)(
lua, args
)
});
unsafe { scope.create_callback(f) }
}
}
}
let mut ud_methods = NonStaticUserDataMethods::default();
T::add_methods(&mut ud_methods);
unsafe {
let lua = self.lua;
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 6);
push_userdata(lua.state, ())?;
#[cfg(feature = "lua53")]
ffi::lua_pushlightuserdata(lua.state, data.as_ptr() as *mut c_void);
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
protect_lua_closure(lua.state, 0, 1, |state| {
// Lua 5.2/5.1 allows to store only table. Then we will wrap the value.
ffi::lua_createtable(state, 1, 0);
ffi::lua_pushinteger(state, 1);
ffi::lua_pushlightuserdata(state, data.as_ptr() as *mut c_void);
ffi::lua_settable(state, -3);
})?;
ffi::lua_setuservalue(lua.state, -2);
protect_lua_closure(lua.state, 0, 1, move |state| {
ffi::lua_newtable(state);
})?;
for (k, m) in ud_methods.meta_methods {
push_string(lua.state, k.name())?;
lua.push_value(Value::Function(wrap_method(self, data.clone(), m)?))?;
protect_lua_closure(lua.state, 3, 1, |state| {
ffi::lua_rawset(state, -3);
})?;
}
if ud_methods.methods.is_empty() {
init_userdata_metatable::<()>(lua.state, -1, None)?;
} else {
protect_lua_closure(lua.state, 0, 1, |state| {
ffi::lua_newtable(state);
})?;
for (k, m) in ud_methods.methods {
push_string(lua.state, &k)?;
lua.push_value(Value::Function(wrap_method(self, data.clone(), m)?))?;
protect_lua_closure(lua.state, 3, 1, |state| {
ffi::lua_rawset(state, -3);
})?;
}
init_userdata_metatable::<()>(lua.state, -2, Some(-1))?;
ffi::lua_pop(lua.state, 1);
}
ffi::lua_setmetatable(lua.state, -2);
Ok(AnyUserData(lua.pop_ref()))
}
}
// Unsafe, because the callback can improperly capture any value with 'callback scope, such as
// improperly capturing an argument. Since the 'callback lifetime is chosen by the user and the
// lifetime of the callback itself is 'scope (non-'static), the borrow checker will happily pick
// a 'callback that outlives 'scope to allow this. In order for this to be safe, the callback
// must NOT capture any parameters.
unsafe fn create_callback<'callback>(
&self,
f: Callback<'callback, 'scope>,
) -> Result<Function<'lua>> {
let f = mem::transmute::<Callback<'callback, 'scope>, Callback<'lua, 'static>>(f);
let f = self.lua.create_callback(f)?;
let mut destructors = self.destructors.borrow_mut();
destructors.push((f.0.clone(), |f| {
let state = f.lua.state;
assert_stack(state, 3);
f.lua.push_ref(&f);
ffi::lua_getupvalue(state, -1, 1);
// We know the destructor has not run yet because we hold a reference to the callback.
let ud = take_userdata::<Callback>(state);
ffi::lua_pushnil(state);
ffi::lua_setupvalue(state, -2, 1);
ffi::lua_pop(state, 1);
Box::new(ud)
}));
Ok(f)
}
}
impl<'lua, 'scope> Drop for Scope<'lua, 'scope> {
fn drop(&mut self) {
// We separate the action of invalidating the userdata in Lua and actually dropping the
// userdata type into two phases. This is so that, in the event a userdata drop panics, we
// can be sure that all of the userdata in Lua is actually invalidated.
// All destructors are non-panicking, so this is fine
let to_drop = self
.destructors
.get_mut()
.drain(..)
.map(|(r, dest)| dest(r))
.collect::<Vec<_>>();
drop(to_drop);
}
}
enum NonStaticMethod<'lua, T> {
Method(Box<dyn Fn(&'lua Lua, &T, MultiValue<'lua>) -> Result<MultiValue<'lua>>>),
MethodMut(Box<dyn FnMut(&'lua Lua, &mut T, MultiValue<'lua>) -> Result<MultiValue<'lua>>>),
Function(Box<dyn Fn(&'lua Lua, MultiValue<'lua>) -> Result<MultiValue<'lua>>>),
FunctionMut(Box<dyn FnMut(&'lua Lua, MultiValue<'lua>) -> Result<MultiValue<'lua>>>),
}
struct NonStaticUserDataMethods<'lua, T: UserData> {
methods: Vec<(Vec<u8>, NonStaticMethod<'lua, T>)>,
meta_methods: Vec<(MetaMethod, NonStaticMethod<'lua, T>)>,
}
impl<'lua, T: UserData> Default for NonStaticUserDataMethods<'lua, T> {
fn default() -> NonStaticUserDataMethods<'lua, T> {
NonStaticUserDataMethods {
methods: Vec::new(),
meta_methods: Vec::new(),
}
}
}
impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'lua, T> {
fn add_method<S, A, R, M>(&mut self, name: &S, method: M)
where
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>,
{
self.methods.push((
name.as_ref().to_vec(),
NonStaticMethod::Method(Box::new(move |lua, ud, args| {
method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
})),
));
}
fn add_method_mut<S, A, R, M>(&mut self, name: &S, mut method: M)
where
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>,
{
self.methods.push((
name.as_ref().to_vec(),
NonStaticMethod::MethodMut(Box::new(move |lua, ud, args| {
method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
})),
));
}
fn add_function<S, A, R, F>(&mut self, name: &S, function: F)
where
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>,
{
self.methods.push((
name.as_ref().to_vec(),
NonStaticMethod::Function(Box::new(move |lua, args| {
function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
})),
));
}
fn add_function_mut<S, A, R, F>(&mut self, name: &S, mut function: F)
where
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>,
{
self.methods.push((
name.as_ref().to_vec(),
NonStaticMethod::FunctionMut(Box::new(move |lua, args| {
function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
})),
));
}
fn add_meta_method<A, R, M>(&mut self, meta: MetaMethod, method: M)
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>,
{
self.meta_methods.push((
meta,
NonStaticMethod::Method(Box::new(move |lua, ud, args| {
method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
})),
));
}
fn add_meta_method_mut<A, R, M>(&mut self, meta: MetaMethod, mut method: M)
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>,
{
self.meta_methods.push((
meta,
NonStaticMethod::MethodMut(Box::new(move |lua, ud, args| {
method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
})),
));
}
fn add_meta_function<A, R, F>(&mut self, meta: MetaMethod, function: F)
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>,
{
self.meta_methods.push((
meta,
NonStaticMethod::Function(Box::new(move |lua, args| {
function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
})),
));
}
fn add_meta_function_mut<A, R, F>(&mut self, meta: MetaMethod, mut function: F)
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>,
{
self.meta_methods.push((
meta,
NonStaticMethod::FunctionMut(Box::new(move |lua, args| {
function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
})),
));
}
}
+72
View File
@@ -0,0 +1,72 @@
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign};
use std::u32;
/// Flags describing the set of lua modules to load.
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct StdLib(u32);
impl StdLib {
#[cfg(any(feature = "lua53", feature = "lua52"))]
pub const COROUTINE: StdLib = StdLib(1 << 0);
pub const TABLE: StdLib = StdLib(1 << 1);
pub const IO: StdLib = StdLib(1 << 2);
pub const OS: StdLib = StdLib(1 << 3);
pub const STRING: StdLib = StdLib(1 << 4);
#[cfg(feature = "lua53")]
pub const UTF8: StdLib = StdLib(1 << 5);
#[cfg(any(feature = "lua52", feature = "luajit"))]
pub const BIT: StdLib = StdLib(1 << 6);
pub const MATH: StdLib = StdLib(1 << 7);
pub const PACKAGE: StdLib = StdLib(1 << 8);
#[cfg(feature = "luajit")]
pub const JIT: StdLib = StdLib(1 << 9);
#[cfg(feature = "luajit")]
pub const FFI: StdLib = StdLib(1 << 10);
pub const DEBUG: StdLib = StdLib(1 << 31); // always highest bit
pub const ALL: StdLib = StdLib(u32::MAX);
pub const ALL_NO_DEBUG: StdLib = StdLib((1 << 31) - 1);
pub fn contains(self, lib: Self) -> bool {
(self & lib).0 != 0
}
}
impl BitAnd for StdLib {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
StdLib(self.0 & rhs.0)
}
}
impl BitAndAssign for StdLib {
fn bitand_assign(&mut self, rhs: Self) {
*self = StdLib(self.0 & rhs.0)
}
}
impl BitOr for StdLib {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
StdLib(self.0 | rhs.0)
}
}
impl BitOrAssign for StdLib {
fn bitor_assign(&mut self, rhs: Self) {
*self = StdLib(self.0 | rhs.0)
}
}
impl BitXor for StdLib {
type Output = Self;
fn bitxor(self, rhs: Self) -> Self::Output {
StdLib(self.0 ^ rhs.0)
}
}
impl BitXorAssign for StdLib {
fn bitxor_assign(&mut self, rhs: Self) {
*self = StdLib(self.0 ^ rhs.0)
}
}
+252 -23
View File
@@ -1,13 +1,16 @@
use std::marker::PhantomData;
use std::os::raw::c_int;
use crate::error::Result;
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
use crate::types::{Integer, LuaRef};
use crate::util::{assert_stack, protect_lua, protect_lua_closure, StackGuard};
use crate::value::{FromLua, FromLuaMulti, Nil, ToLua, ToLuaMulti, Value};
#[cfg(feature = "async")]
use {futures_core::future::LocalBoxFuture, futures_util::future};
/// Handle to an internal Lua table.
#[derive(Clone, Debug)]
pub struct Table<'lua>(pub(crate) LuaRef<'lua>);
@@ -134,26 +137,15 @@ impl<'lua> Table<'lua> {
}
/// Gets the function associated to `key` from the table and executes it,
/// passing the table itself as the first argument.
/// passing the table itself along with `args` as function arguments.
///
/// # Examples
///
/// Execute the table method with name "concat":
///
/// ```
/// # use mlua::{Lua, Result, Table};
/// # fn main() -> Result<()> {
/// # let lua = Lua::new();
/// # let object = lua.create_table()?;
/// # let concat = lua.create_function(|_, (_, a, b): (Table, String, String)| Ok(a + &b))?;
/// # object.set("concat", concat)?;
/// // simiar to: object:concat("param1", "param2")
/// object.call("concat", ("param1", "param2"))?;
/// # Ok(())
/// # }
/// ```
/// This function is deprecated since 0.3.1 in favor of [`call_method`]
/// in the `TableExt` trait.
///
/// This might invoke the `__index` metamethod.
///
/// [`call_method`]: trait.TableExt.html#tymethod.call_method
#[deprecated(since = "0.3.1", note = "Please use `call_method` instead")]
pub fn call<K, A, R>(&self, key: K, args: A) -> Result<R>
where
K: ToLua<'lua>,
@@ -166,11 +158,60 @@ impl<'lua> Table<'lua> {
self.get::<_, Function>(key)?.call(args)
}
/// Removes a key from the table, returning the value at the key
/// if the key was previously in the table.
pub fn raw_remove<K: ToLua<'lua>>(&self, key: K) -> Result<()> {
self.raw_set(key, Nil)?;
Ok(())
/// Compares two tables for equality.
///
/// Tables are compared by reference first.
/// If they are not primitively equals, then mlua will try to invoke the `__eq` metamethod.
/// mlua will check `self` first for the metamethod, then `other` if not found.
///
/// # Examples
///
/// Compare two tables using `__eq` metamethod:
///
/// ```
/// # use mlua::{Lua, Result, Table};
/// # fn main() -> Result<()> {
/// # let lua = Lua::new();
/// let table1 = lua.create_table()?;
/// table1.set(1, "value")?;
///
/// let table2 = lua.create_table()?;
/// table2.set(2, "value")?;
///
/// let always_equals_mt = lua.create_table()?;
/// always_equals_mt.set("__eq", lua.create_function(|_, (_t1, _t2): (Table, Table)| Ok(true))?)?;
/// table2.set_metatable(Some(always_equals_mt));
///
/// assert!(table1.equals(&table1.clone())?);
/// assert!(table1.equals(&table2)?);
/// # Ok(())
/// # }
/// ```
pub fn equals<T: AsRef<Self>>(&self, other: T) -> Result<bool> {
let other = other.as_ref();
if self == other {
return Ok(true);
}
// Compare using __eq metamethod if exists
// First, check the self for the metamethod.
// If self does not define it, then check the other table.
if let Some(mt) = self.get_metatable() {
if mt.contains_key("__eq")? {
return mt
.get::<_, Function>("__eq")?
.call((self.clone(), other.clone()));
}
}
if let Some(mt) = other.get_metatable() {
if mt.contains_key("__eq")? {
return mt
.get::<_, Function>("__eq")?
.call((self.clone(), other.clone()));
}
}
Ok(false)
}
/// Sets a key-value pair without invoking metamethods.
@@ -213,6 +254,70 @@ impl<'lua> Table<'lua> {
V::from_lua(value, lua)
}
/// Inserts element value at position `idx` to the table, shifting up the elements from `table[idx]`.
/// The worst case complexity is O(n), where n is the table length.
pub fn raw_insert<V: ToLua<'lua>>(&self, idx: Integer, value: V) -> Result<()> {
let lua = self.0.lua;
let size = self.raw_len();
if idx < 1 || idx > size + 1 {
return Err(Error::RuntimeError("index out of bounds".to_string()));
}
let value = value.to_lua(lua)?;
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 6);
lua.push_ref(&self.0);
lua.push_value(value)?;
protect_lua_closure(lua.state, 2, 0, |state| {
for i in (idx..size + 1).rev() {
// table[i+1] = table[i]
ffi::lua_rawgeti(state, -2, i);
ffi::lua_rawseti(state, -3, i + 1);
}
ffi::lua_rawseti(state, -2, idx);
})
}
}
/// Removes a key from the table.
///
/// If `key` is an integer, mlua shifts down the elements from `table[key+1]`,
/// and erases element `table[key]`. The complexity is O(n) in worst case,
/// where n is the table length.
///
/// For othey key types this is equivalent to setting `table[key] = nil`.
pub fn raw_remove<K: ToLua<'lua>>(&self, key: K) -> Result<()> {
let lua = self.0.lua;
let key = key.to_lua(lua)?;
match key {
Value::Integer(idx) => {
let size = self.raw_len();
if idx < 1 || idx > size {
return Err(Error::RuntimeError("index out of bounds".to_string()));
}
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 6);
lua.push_ref(&self.0);
protect_lua_closure(lua.state, 1, 0, |state| {
for i in idx..size {
ffi::lua_rawgeti(state, -1, i + 1);
ffi::lua_rawseti(state, -2, i);
}
ffi::lua_pushnil(state);
ffi::lua_rawseti(state, -2, size);
})
}
}
_ => self.raw_set(key, Nil),
}
}
/// Returns the result of the Lua `#` operator.
///
/// This might invoke the `__len` metamethod. Use the [`raw_len`] method if that is not desired.
@@ -368,6 +473,130 @@ impl<'lua> Table<'lua> {
}
}
impl<'lua> PartialEq for Table<'lua> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<'lua> AsRef<Table<'lua>> for Table<'lua> {
#[inline]
fn as_ref(&self) -> &Self {
self
}
}
/// An extension trait for `Table`s that provides a variety of convenient functionality.
pub trait TableExt<'lua> {
/// Gets the function associated to `key` from the table and executes it,
/// passing the table itself along with `args` as function arguments.
///
/// This is a shortcut for
/// `table.get::<_, Function>(key)?.call((table.clone(), arg1, ..., argN))`
///
/// This might invoke the `__index` metamethod.
fn call_method<K, A, R>(&self, key: K, args: A) -> Result<R>
where
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua>;
/// Gets the function associated to `key` from the table and executes it,
/// passing `args` as function arguments.
///
/// This is a shortcut for
/// `table.get::<_, Function>(key)?.call(args)`
///
/// This might invoke the `__index` metamethod.
fn call_function<K, A, R>(&self, key: K, args: A) -> Result<R>
where
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua>;
/// Gets the function associated to `key` from the table and asynchronously executes it,
/// passing the table itself along with `args` as function arguments and returning Future.
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
fn call_async_method<'fut, K, A, R>(&self, key: K, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut;
/// Gets the function associated to `key` from the table and asynchronously executes it,
/// passing `args` as function arguments and returning Future.
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
fn call_async_function<'fut, K, A, R>(
&self,
key: K,
args: A,
) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut;
}
impl<'lua> TableExt<'lua> for Table<'lua> {
fn call_method<K, A, R>(&self, key: K, args: A) -> Result<R>
where
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
let lua = self.0.lua;
let mut args = args.to_lua_multi(lua)?;
args.push_front(Value::Table(self.clone()));
self.get::<_, Function>(key)?.call(args)
}
fn call_function<K, A, R>(&self, key: K, args: A) -> Result<R>
where
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
self.get::<_, Function>(key)?.call(args)
}
#[cfg(feature = "async")]
fn call_async_method<'fut, K, A, R>(&self, key: K, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
{
let lua = self.0.lua;
let mut args = match args.to_lua_multi(lua) {
Ok(args) => args,
Err(e) => return Box::pin(future::err(e)),
};
args.push_front(Value::Table(self.clone()));
self.call_async_function(key, args)
}
#[cfg(feature = "async")]
fn call_async_function<'fut, K, A, R>(&self, key: K, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
{
match self.get::<_, Function>(key) {
Ok(func) => func.call_async(args),
Err(e) => Box::pin(future::err(e)),
}
}
}
/// An iterator over the pairs of a Lua table.
///
/// This struct is created by the [`Table::pairs`] method.
+214
View File
@@ -8,6 +8,24 @@ use crate::util::{
};
use crate::value::{FromLuaMulti, MultiValue, ToLuaMulti};
#[cfg(feature = "async")]
use {
crate::{
error::ExternalError,
lua::{AsyncPollPending, Lua, WAKER_REGISTRY_KEY},
util::{get_gc_userdata, push_gc_userdata},
value::Value,
},
futures_core::{future::Future, stream::Stream},
std::{
cell::RefCell,
marker::PhantomData,
os::raw::c_void,
pin::Pin,
task::{Context, Poll, Waker},
},
};
/// Status of a Lua thread (or coroutine).
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ThreadStatus {
@@ -27,6 +45,18 @@ pub enum ThreadStatus {
#[derive(Clone, Debug)]
pub struct Thread<'lua>(pub(crate) LuaRef<'lua>);
/// Thread (coroutine) representation as an async [`Future`] or [`Stream`].
///
/// [`Future`]: ../futures_core/future/trait.Future.html
/// [`Stream`]: ../futures_core/stream/trait.Stream.html
#[cfg(feature = "async")]
#[derive(Debug)]
pub struct AsyncThread<'lua, R> {
thread: Thread<'lua>,
args0: RefCell<Option<Result<MultiValue<'lua>>>>,
ret: PhantomData<R>,
}
impl<'lua> Thread<'lua> {
/// Resumes execution of this thread.
///
@@ -142,4 +172,188 @@ impl<'lua> Thread<'lua> {
}
}
}
/// Converts Thread to an AsyncThread which implements Future and Stream traits.
///
/// `args` are passed as arguments to the thread function for first call.
/// The object call `resume()` while polling and also allows to run rust futures
/// to completion using an executor.
///
/// Using AsyncThread as a Stream allows to iterate through `coroutine.yield()`
/// values whereas Future version discards that values and poll until the final
/// one (returned from the thread function).
///
/// # Examples
///
/// ```
/// # use mlua::{Lua, Result, Thread};
/// use futures::stream::TryStreamExt;
/// # #[tokio::main]
/// # async fn main() -> Result<()> {
/// # let lua = Lua::new();
/// let thread: Thread = lua.load(r#"
/// coroutine.create(function (sum)
/// for i = 1,10 do
/// sum = sum + i
/// coroutine.yield(sum)
/// end
/// return sum
/// end)
/// "#).eval()?;
///
/// let mut stream = thread.into_async::<_, i64>(1);
/// let mut sum = 0;
/// while let Some(n) = stream.try_next().await? {
/// sum += n;
/// }
///
/// assert_eq!(sum, 286);
///
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "async")]
pub fn into_async<A, R>(self, args: A) -> AsyncThread<'lua, R>
where
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
let args = args.to_lua_multi(&self.0.lua);
AsyncThread {
thread: self,
args0: RefCell::new(Some(args)),
ret: PhantomData,
}
}
}
impl<'lua> PartialEq for Thread<'lua> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
#[cfg(feature = "async")]
impl<'lua, R> Stream for AsyncThread<'lua, R>
where
R: FromLuaMulti<'lua>,
{
type Item = Result<R>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let lua = self.thread.0.lua;
match self.thread.status() {
ThreadStatus::Resumable => {}
_ => return Poll::Ready(None),
};
let _wg = WakerGuard::new(lua.state, cx.waker().clone());
let ret: MultiValue = if let Some(args) = self.args0.borrow_mut().take() {
self.thread.resume(args?)?
} else {
self.thread.resume(())?
};
if is_poll_pending(lua, &ret) {
return Poll::Pending;
}
cx.waker().wake_by_ref();
Poll::Ready(Some(R::from_lua_multi(ret, lua)))
}
}
#[cfg(feature = "async")]
impl<'lua, R> Future for AsyncThread<'lua, R>
where
R: FromLuaMulti<'lua>,
{
type Output = Result<R>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let lua = self.thread.0.lua;
match self.thread.status() {
ThreadStatus::Resumable => {}
_ => return Poll::Ready(Err("Thread already finished".to_lua_err())),
};
let _wg = WakerGuard::new(lua.state, cx.waker().clone());
let ret: MultiValue = if let Some(args) = self.args0.borrow_mut().take() {
self.thread.resume(args?)?
} else {
self.thread.resume(())?
};
if is_poll_pending(lua, &ret) {
return Poll::Pending;
}
if let ThreadStatus::Resumable = self.thread.status() {
// Ignore value returned via yield()
cx.waker().wake_by_ref();
return Poll::Pending;
}
Poll::Ready(R::from_lua_multi(ret, lua))
}
}
#[cfg(feature = "async")]
fn is_poll_pending(lua: &Lua, val: &MultiValue) -> bool {
if val.len() != 1 {
return false;
}
if let Some(Value::UserData(ud)) = val.iter().next() {
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 3);
lua.push_ref(&ud.0);
let is_pending = get_gc_userdata::<AsyncPollPending>(lua.state, -1)
.as_ref()
.is_some();
ffi::lua_pop(lua.state, 1);
return is_pending;
}
}
false
}
#[cfg(feature = "async")]
struct WakerGuard(*mut ffi::lua_State);
#[cfg(feature = "async")]
impl WakerGuard {
pub fn new(state: *mut ffi::lua_State, waker: Waker) -> Result<WakerGuard> {
unsafe {
let _sg = StackGuard::new(state);
assert_stack(state, 6);
ffi::lua_pushlightuserdata(state, &WAKER_REGISTRY_KEY as *const u8 as *mut c_void);
push_gc_userdata(state, waker)?;
ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX);
Ok(WakerGuard(state))
}
}
}
#[cfg(feature = "async")]
impl Drop for WakerGuard {
fn drop(&mut self) {
unsafe {
let state = self.0;
let _sg = StackGuard::new(state);
assert_stack(state, 2);
ffi::lua_pushlightuserdata(state, &WAKER_REGISTRY_KEY as *const u8 as *mut c_void);
ffi::lua_pushnil(state);
ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX);
}
}
}
+30 -12
View File
@@ -1,10 +1,15 @@
use std::cell::RefCell;
use std::os::raw::{c_int, c_void};
use std::sync::{Arc, Mutex};
use std::rc::Rc;
use std::{fmt, mem, ptr};
#[cfg(feature = "async")]
use futures_core::future::LocalBoxFuture;
use crate::error::Result;
use crate::ffi;
use crate::lua::Lua;
use crate::util::{assert_stack, StackGuard};
use crate::value::MultiValue;
/// Type of Lua integer numbers.
@@ -19,29 +24,28 @@ pub struct LightUserData(pub *mut c_void);
pub(crate) type Callback<'lua, 'a> =
Box<dyn Fn(&'lua Lua, MultiValue<'lua>) -> Result<MultiValue<'lua>> + 'a>;
#[cfg(feature = "async")]
pub(crate) type AsyncCallback<'lua, 'a> =
Box<dyn Fn(&'lua Lua, MultiValue<'lua>) -> LocalBoxFuture<'lua, Result<MultiValue<'lua>>> + 'a>;
/// An auto generated key into the Lua registry.
///
/// This is a handle to a value stored inside the Lua registry. It is not directly usable like the
/// `Table` or `Function` handle types, but since it doesn't hold a reference to a parent Lua and is
/// Send + Sync + 'static, it is much more flexible and can be used in many situations where it is
/// impossible to directly store a normal handle type. It is not automatically garbage collected on
/// Drop, but it can be removed with [`Lua::remove_registry_value`], and instances not manually
/// removed can be garbage collected with [`Lua::expire_registry_values`].
/// This is a handle to a value stored inside the Lua registry. It is not automatically
/// garbage collected on Drop, but it can be removed with [`Lua::remove_registry_value`],
/// and instances not manually removed can be garbage collected with [`Lua::expire_registry_values`].
///
/// Be warned, If you place this into Lua via a `UserData` type or a rust callback, it is *very
/// easy* to accidentally cause reference cycles that the Lua garbage collector cannot resolve.
/// Instead of placing a `RegistryKey` into a `UserData` type, prefer instead to use
/// [`UserData::set_user_value`] / [`UserData::get_user_value`], and instead of moving a RegistryKey
/// into a callback, prefer [`Lua::scope`].
/// [`UserData::set_user_value`] / [`UserData::get_user_value`].
///
/// [`Lua::remove_registry_value`]: struct.Lua.html#method.remove_registry_value
/// [`Lua::expire_registry_values`]: struct.Lua.html#method.expire_registry_values
/// [`Lua::scope`]: struct.Lua.html#method.scope
/// [`UserData::set_user_value`]: struct.UserData.html#method.set_user_value
/// [`UserData::get_user_value`]: struct.UserData.html#method.get_user_value
pub struct RegistryKey {
pub(crate) registry_id: c_int,
pub(crate) unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
pub(crate) unref_list: Rc<RefCell<Option<Vec<c_int>>>>,
}
impl fmt::Debug for RegistryKey {
@@ -52,7 +56,8 @@ impl fmt::Debug for RegistryKey {
impl Drop for RegistryKey {
fn drop(&mut self) {
if let Some(list) = mlua_expect!(self.unref_list.lock(), "unref_list poisoned").as_mut() {
let mut unref_list = mlua_expect!(self.unref_list.try_borrow_mut(), "unref list borrowed");
if let Some(list) = unref_list.as_mut() {
list.push(self.registry_id);
}
}
@@ -92,3 +97,16 @@ impl<'lua> Drop for LuaRef<'lua> {
self.lua.drop_ref(self)
}
}
impl<'lua> PartialEq for LuaRef<'lua> {
fn eq(&self, other: &Self) -> bool {
let lua = self.lua;
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 2);
lua.push_ref(&self);
lua.push_ref(&other);
ffi::lua_rawequal(lua.state, -1, -2) == 1
}
}
}
+95 -16
View File
@@ -1,8 +1,13 @@
use std::cell::{Ref, RefCell, RefMut};
#[cfg(feature = "async")]
use std::future::Future;
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
use crate::lua::Lua;
use crate::table::Table;
use crate::types::LuaRef;
use crate::util::{assert_stack, get_userdata, StackGuard};
use crate::value::{FromLua, FromLuaMulti, ToLua, ToLuaMulti};
@@ -75,11 +80,6 @@ pub enum MetaMethod {
///
/// This is not an operator, but it will be called by the built-in `pairs` function.
Pairs,
#[cfg(any(feature = "lua53", feature = "lua52"))]
/// The `__ipairs` metamethod.
///
/// This is not an operator, but it will be called by the built-in `ipairs` function.
IPairs,
}
impl MetaMethod {
@@ -117,8 +117,6 @@ impl MetaMethod {
MetaMethod::ToString => b"__tostring",
#[cfg(any(feature = "lua53", feature = "lua52"))]
MetaMethod::Pairs => b"__pairs",
#[cfg(any(feature = "lua53", feature = "lua52"))]
MetaMethod::IPairs => b"__ipairs",
}
}
}
@@ -139,7 +137,7 @@ pub trait UserDataMethods<'lua, T: UserData> {
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>;
M: 'static + Fn(&'lua Lua, &T, A) -> Result<R>;
/// Add a regular method which accepts a `&mut T` as the first parameter.
///
@@ -151,7 +149,23 @@ pub trait UserDataMethods<'lua, T: UserData> {
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>;
M: 'static + FnMut(&'lua Lua, &mut T, A) -> Result<R>;
/// Add an async method which accepts a `T` as the first parameter and returns Future.
/// The passed `T` is cloned from the original value.
///
/// Refer to [`add_method`] for more information about the implementation.
///
/// [`add_method`]: #method.add_method
#[cfg(feature = "async")]
fn add_async_method<S, A, R, M, MR>(&mut self, name: &S, method: M)
where
T: Clone,
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Fn(&'lua Lua, T, A) -> MR,
MR: 'static + Future<Output = Result<R>>;
/// Add a regular method as a function which accepts generic arguments, the first argument will
/// be a `UserData` of type T if the method is called with Lua method syntax:
@@ -167,7 +181,7 @@ pub trait UserDataMethods<'lua, T: UserData> {
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>;
F: 'static + Fn(&'lua Lua, A) -> Result<R>;
/// Add a regular method as a mutable function which accepts generic arguments.
///
@@ -179,7 +193,23 @@ pub trait UserDataMethods<'lua, T: UserData> {
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>;
F: 'static + FnMut(&'lua Lua, A) -> Result<R>;
/// Add a regular method as an async function which accepts generic arguments
/// and returns Future.
///
/// This is an async version of [`add_function`].
///
/// [`add_function`]: #method.add_function
#[cfg(feature = "async")]
fn add_async_function<S, A, R, F, FR>(&mut self, name: &S, function: F)
where
T: Clone,
S: ?Sized + AsRef<[u8]>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Fn(&'lua Lua, A) -> FR,
FR: 'static + Future<Output = Result<R>>;
/// Add a metamethod which accepts a `&T` as the first parameter.
///
@@ -193,7 +223,7 @@ pub trait UserDataMethods<'lua, T: UserData> {
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>;
M: 'static + Fn(&'lua Lua, &T, A) -> Result<R>;
/// Add a metamethod as a function which accepts a `&mut T` as the first parameter.
///
@@ -207,7 +237,7 @@ pub trait UserDataMethods<'lua, T: UserData> {
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>;
M: 'static + FnMut(&'lua Lua, &mut T, A) -> Result<R>;
/// Add a metamethod which accepts generic arguments.
///
@@ -218,7 +248,7 @@ pub trait UserDataMethods<'lua, T: UserData> {
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>;
F: 'static + Fn(&'lua Lua, A) -> Result<R>;
/// Add a metamethod as a mutable function which accepts generic arguments.
///
@@ -229,7 +259,7 @@ pub trait UserDataMethods<'lua, T: UserData> {
where
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>;
F: 'static + FnMut(&'lua Lua, A) -> Result<R>;
}
/// Trait for custom userdata types.
@@ -298,7 +328,7 @@ pub trait UserDataMethods<'lua, T: UserData> {
/// [`UserDataMethods`]: trait.UserDataMethods.html
pub trait UserData: Sized {
/// Adds custom methods and operators specific to this userdata.
fn add_methods<'lua, T: UserDataMethods<'lua, Self>>(_methods: &mut T) {}
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(_methods: &mut M) {}
}
/// Handle to an internal Lua userdata for any type that implements [`UserData`].
@@ -398,6 +428,42 @@ impl<'lua> AnyUserData<'lua> {
V::from_lua(res, lua)
}
fn get_metatable(&self) -> Result<Table<'lua>> {
unsafe {
let lua = self.0.lua;
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 3);
lua.push_ref(&self.0);
if ffi::lua_getmetatable(lua.state, -1) == 0 {
return Err(Error::UserDataTypeMismatch);
}
Ok(Table(lua.pop_ref()))
}
}
pub(crate) fn equals<T: AsRef<Self>>(&self, other: T) -> Result<bool> {
let other = other.as_ref();
if self == other {
return Ok(true);
}
let mt = self.get_metatable()?;
if mt != other.get_metatable()? {
return Ok(false);
}
if mt.contains_key("__eq")? {
return mt
.get::<_, Function>("__eq")?
.call((self.clone(), other.clone()));
}
Ok(false)
}
fn inspect<'a, T, R, F>(&'a self, func: F) -> Result<R>
where
T: 'static + UserData,
@@ -428,3 +494,16 @@ impl<'lua> AnyUserData<'lua> {
}
}
}
impl<'lua> PartialEq for AnyUserData<'lua> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<'lua> AsRef<AnyUserData<'lua>> for AnyUserData<'lua> {
#[inline]
fn as_ref(&self) -> &Self {
self
}
}
+114 -122
View File
@@ -1,14 +1,21 @@
use std::any::Any;
use std::any::{Any, TypeId};
use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt::Write;
use std::os::raw::{c_char, c_int, c_void};
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
use std::sync::Arc;
use std::rc::Rc;
use std::sync::Mutex;
use std::{mem, ptr, slice};
use crate::error::{Error, Result};
use crate::ffi;
lazy_static::lazy_static! {
// The capacity must(!) be greater than number of stored keys
static ref METATABLE_CACHE: Mutex<HashMap<TypeId, u8>> = Mutex::new(HashMap::with_capacity(32));
}
// Checks that Lua has enough free stack space for future stack operations. On failure, this will
// panic with an internal error message.
pub unsafe fn assert_stack(state: *mut ffi::lua_State, amount: c_int) {
@@ -175,8 +182,7 @@ pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error {
if let Some(err) = get_wrapped_error(state, -1).as_ref() {
ffi::lua_pop(state, 1);
err.clone()
} else if is_wrapped_panic(state, -1) {
let panic = get_userdata::<WrappedPanic>(state, -1);
} else if let Some(panic) = get_gc_userdata::<WrappedPanic>(state, -1).as_mut() {
if let Some(p) = (*panic).0.take() {
resume_unwind(p);
} else {
@@ -255,6 +261,41 @@ pub unsafe fn take_userdata<T>(state: *mut ffi::lua_State) -> T {
ptr::read(ud)
}
// Pushes the userdata and attaches a metatable with __gc method
// Internally uses 5 stack spaces, does not call checkstack
pub unsafe fn push_gc_userdata<T: Any>(state: *mut ffi::lua_State, t: T) -> Result<()> {
push_meta_gc_userdata::<T, T>(state, t)
}
pub unsafe fn push_meta_gc_userdata<MT: Any, T>(state: *mut ffi::lua_State, t: T) -> Result<()> {
let ud = protect_lua_closure(state, 0, 1, move |state| {
ffi::lua_newuserdata(state, mem::size_of::<T>()) as *mut T
})?;
ptr::write(ud, t);
get_gc_metatable_for::<MT>(state);
ffi::lua_setmetatable(state, -2);
Ok(())
}
// Uses 2 stack spaces, does not call checkstack
pub unsafe fn get_gc_userdata<T: Any>(state: *mut ffi::lua_State, index: c_int) -> *mut T {
get_meta_gc_userdata::<T, T>(state, index)
}
pub unsafe fn get_meta_gc_userdata<MT: Any, T>(state: *mut ffi::lua_State, index: c_int) -> *mut T {
let ud = ffi::lua_touserdata(state, index) as *mut T;
if ud.is_null() || ffi::lua_getmetatable(state, index) == 0 {
return ptr::null_mut();
}
get_gc_metatable_for::<MT>(state);
let res = ffi::lua_rawequal(state, -1, -2) != 0;
ffi::lua_pop(state, 2);
if !res {
return ptr::null_mut();
}
ud
}
// Populates the given table with the appropriate members to be a userdata metatable for the given
// type. This function takes the given table at the `metatable` index, and adds an appropriate __gc
// member to it for the given type and a __metatable entry to protect the table from script access.
@@ -380,14 +421,14 @@ where
Ok(Err(err)) => {
ffi::lua_settop(state, 1);
ptr::write(ud as *mut WrappedError, WrappedError(err));
get_error_metatable(state);
get_gc_metatable_for::<WrappedError>(state);
ffi::lua_setmetatable(state, -2);
ffi::lua_error(state)
}
Err(p) => {
ffi::lua_settop(state, 1);
ptr::write(ud as *mut WrappedPanic, WrappedPanic(Some(p)));
get_panic_metatable(state);
get_gc_metatable_for::<WrappedPanic>(state);
ffi::lua_setmetatable(state, -2);
ffi::lua_error(state)
}
@@ -428,12 +469,12 @@ pub unsafe extern "C" fn error_traceback(state: *mut ffi::lua_State) -> c_int {
ud,
WrappedError(Error::CallbackError {
traceback,
cause: Arc::new(error),
cause: Rc::new(error),
}),
);
get_error_metatable(state);
get_gc_metatable_for::<WrappedError>(state);
ffi::lua_setmetatable(state, -2);
} else if !is_wrapped_panic(state, -1) {
} else if let None = get_gc_userdata::<WrappedPanic>(state, -1).as_ref() {
if ffi::lua_checkstack(state, LUA_TRACEBACK_STACK) != 0 {
let s = ffi::luaL_tolstring(state, -1, ptr::null_mut());
ffi::luaL_traceback(state, state, s, 0);
@@ -443,68 +484,83 @@ pub unsafe extern "C" fn error_traceback(state: *mut ffi::lua_State) -> c_int {
1
}
// Does not call lua_checkstack, uses 2 stack spaces.
#[cfg(any(feature = "lua51", feature = "luajit"))]
pub unsafe fn set_main_state(state: *mut ffi::lua_State) {
ffi::lua_pushlightuserdata(state, &MAIN_THREAD_REGISTRY_KEY as *const u8 as *mut c_void);
ffi::lua_pushthread(state);
ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX);
}
// Does not call lua_checkstack, uses 1 stack space.
pub unsafe fn get_main_state(state: *mut ffi::lua_State) -> *mut ffi::lua_State {
#[cfg(any(feature = "lua53", feature = "lua52"))]
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_MAINTHREAD);
#[cfg(any(feature = "lua51", feature = "luajit"))]
{
ffi::lua_pushlightuserdata(state, &MAIN_THREAD_REGISTRY_KEY as *const u8 as *mut c_void);
ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX);
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_MAINTHREAD);
let main_state = ffi::lua_tothread(state, -1);
ffi::lua_pop(state, 1);
main_state
}
let main_state = ffi::lua_tothread(state, -1);
ffi::lua_pop(state, 1);
main_state
#[cfg(any(feature = "lua51", feature = "luajit"))]
state
}
// Pushes a WrappedError to the top of the stack. Uses two stack spaces and does not call
// lua_checkstack.
pub unsafe fn push_wrapped_error(state: *mut ffi::lua_State, err: Error) -> Result<()> {
let ud = protect_lua_closure(state, 0, 1, move |state| {
ffi::lua_newuserdata(state, mem::size_of::<WrappedError>()) as *mut WrappedError
})?;
ptr::write(ud, WrappedError(err));
get_error_metatable(state);
ffi::lua_setmetatable(state, -2);
Ok(())
push_gc_userdata::<WrappedError>(state, WrappedError(err))
}
// Checks if the value at the given index is a WrappedError, and if it is returns a pointer to it,
// otherwise returns null. Uses 2 stack spaces and does not call lua_checkstack.
pub unsafe fn get_wrapped_error(state: *mut ffi::lua_State, index: c_int) -> *const Error {
let userdata = ffi::lua_touserdata(state, index);
if userdata.is_null() {
let ud = get_gc_userdata::<WrappedError>(state, index);
if ud.is_null() {
return ptr::null();
}
&(*ud).0
}
if ffi::lua_getmetatable(state, index) == 0 {
return ptr::null();
// Initialize the internal (with __gc) metatable for a type T
pub unsafe fn init_gc_metatable_for<T: Any>(
state: *mut ffi::lua_State,
customize_fn: Option<fn(*mut ffi::lua_State)>,
) {
let type_id = TypeId::of::<T>();
let ref_addr = {
let mut mt_cache = mlua_expect!(METATABLE_CACHE.lock(), "cannot lock metatable cache");
mlua_assert!(
mt_cache.capacity() - mt_cache.len() > 0,
"out of metatable cache capacity"
);
mt_cache.insert(type_id, 0);
&mt_cache[&type_id] as *const u8
};
ffi::lua_newtable(state);
ffi::lua_pushstring(state, cstr!("__gc"));
ffi::lua_pushcfunction(state, userdata_destructor::<T>);
ffi::lua_rawset(state, -3);
ffi::lua_pushstring(state, cstr!("__metatable"));
ffi::lua_pushboolean(state, 0);
ffi::lua_rawset(state, -3);
if let Some(f) = customize_fn {
f(state)
}
get_error_metatable(state);
let res = ffi::lua_rawequal(state, -1, -2) != 0;
ffi::lua_pop(state, 2);
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, ref_addr as *mut c_void);
}
if res {
&(*get_userdata::<WrappedError>(state, -1)).0
} else {
ptr::null()
}
pub unsafe fn get_gc_metatable_for<T: Any>(state: *mut ffi::lua_State) {
let type_id = TypeId::of::<T>();
let ref_addr = {
let mt_cache = mlua_expect!(METATABLE_CACHE.lock(), "cannot lock metatable cache");
mlua_expect!(mt_cache.get(&type_id), "gc metatable does not exist") as *const u8
};
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, ref_addr as *mut c_void);
}
// Initialize the error, panic, and destructed userdata metatables.
pub unsafe fn init_error_registry(state: *mut ffi::lua_State) {
assert_stack(state, 8);
// Create error metatable
// Create error and panic metatables
unsafe extern "C" fn error_tostring(state: *mut ffi::lua_State) -> c_int {
let err_buf = callback_error(state, |_| {
@@ -524,8 +580,7 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) {
// kind of recursive error structure?)
let _ = write!(&mut (*err_buf), "{}", error);
Ok(err_buf)
} else if is_wrapped_panic(state, -1) {
let panic = get_userdata::<WrappedPanic>(state, -1);
} else if let Some(panic) = get_gc_userdata::<WrappedPanic>(state, -1).as_ref() {
if let Some(ref p) = (*panic).0 {
ffi::lua_pushlightuserdata(
state,
@@ -564,56 +619,31 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) {
1
}
ffi::lua_pushlightuserdata(
init_gc_metatable_for::<WrappedError>(
state,
&ERROR_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
Some(|state| {
ffi::lua_pushstring(state, cstr!("__tostring"));
ffi::lua_pushcfunction(state, error_tostring);
ffi::lua_rawset(state, -3);
}),
);
ffi::lua_newtable(state);
ffi::lua_pushstring(state, cstr!("__gc"));
ffi::lua_pushcfunction(state, userdata_destructor::<WrappedError>);
ffi::lua_rawset(state, -3);
ffi::lua_pushstring(state, cstr!("__tostring"));
ffi::lua_pushcfunction(state, error_tostring);
ffi::lua_rawset(state, -3);
ffi::lua_pushstring(state, cstr!("__metatable"));
ffi::lua_pushboolean(state, 0);
ffi::lua_rawset(state, -3);
ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX);
// Create panic metatable
ffi::lua_pushlightuserdata(
init_gc_metatable_for::<WrappedPanic>(
state,
&PANIC_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
Some(|state| {
ffi::lua_pushstring(state, cstr!("__tostring"));
ffi::lua_pushcfunction(state, error_tostring);
ffi::lua_rawset(state, -3);
}),
);
ffi::lua_newtable(state);
ffi::lua_pushstring(state, cstr!("__gc"));
ffi::lua_pushcfunction(state, userdata_destructor::<WrappedPanic>);
ffi::lua_rawset(state, -3);
ffi::lua_pushstring(state, cstr!("__tostring"));
ffi::lua_pushcfunction(state, error_tostring);
ffi::lua_rawset(state, -3);
ffi::lua_pushstring(state, cstr!("__metatable"));
ffi::lua_pushboolean(state, 0);
ffi::lua_rawset(state, -3);
ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX);
// Create destructed userdata metatable
unsafe extern "C" fn destructed_error(state: *mut ffi::lua_State) -> c_int {
ffi::luaL_checkstack(state, 2, ptr::null());
let ud = ffi::lua_newuserdata(state, mem::size_of::<WrappedError>()) as *mut WrappedError;
ptr::write(ud, WrappedError(Error::CallbackDestructed));
get_error_metatable(state);
get_gc_metatable_for::<WrappedError>(state);
ffi::lua_setmetatable(state, -2);
ffi::lua_error(state)
}
@@ -709,40 +739,6 @@ unsafe fn to_string<'a>(state: *mut ffi::lua_State, index: c_int) -> Cow<'a, str
}
}
// Checks if the value at the given index is a WrappedPanic. Uses 2 stack spaces and does not call
// lua_checkstack.
unsafe fn is_wrapped_panic(state: *mut ffi::lua_State, index: c_int) -> bool {
let userdata = ffi::lua_touserdata(state, index);
if userdata.is_null() {
return false;
}
if ffi::lua_getmetatable(state, index) == 0 {
return false;
}
get_panic_metatable(state);
let res = ffi::lua_rawequal(state, -1, -2) != 0;
ffi::lua_pop(state, 2);
res
}
unsafe fn get_error_metatable(state: *mut ffi::lua_State) {
ffi::lua_pushlightuserdata(
state,
&ERROR_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
);
ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX);
}
unsafe fn get_panic_metatable(state: *mut ffi::lua_State) {
ffi::lua_pushlightuserdata(
state,
&PANIC_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
);
ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX);
}
unsafe fn get_destructed_userdata_metatable(state: *mut ffi::lua_State) {
ffi::lua_pushlightuserdata(
state,
@@ -751,9 +747,5 @@ unsafe fn get_destructed_userdata_metatable(state: *mut ffi::lua_State) {
ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX);
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
static MAIN_THREAD_REGISTRY_KEY: u8 = 0;
static ERROR_METATABLE_REGISTRY_KEY: u8 = 0;
static PANIC_METATABLE_REGISTRY_KEY: u8 = 0;
static DESTRUCTED_USERDATA_METATABLE: u8 = 0;
static ERROR_PRINT_BUFFER_KEY: u8 = 0;
+50 -3
View File
@@ -2,6 +2,7 @@ use std::iter::{self, FromIterator};
use std::{slice, str, vec};
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
use crate::lua::Lua;
use crate::string::String;
@@ -47,20 +48,66 @@ pub enum Value<'lua> {
pub use self::Value::Nil;
impl<'lua> Value<'lua> {
pub(crate) fn type_name(&self) -> &'static str {
pub fn type_name(&self) -> &'static str {
match *self {
Value::Nil => "nil",
Value::Boolean(_) => "boolean",
Value::LightUserData(_) => "light userdata",
Value::LightUserData(_) => "lightuserdata",
Value::Integer(_) => "integer",
Value::Number(_) => "number",
Value::String(_) => "string",
Value::Table(_) => "table",
Value::Function(_) => "function",
Value::Thread(_) => "thread",
Value::UserData(_) | Value::Error(_) => "userdata",
Value::UserData(_) => "userdata",
Value::Error(_) => "error",
}
}
/// Compares two values for equality.
///
/// Equality comparisons do not convert strings to numbers or vice versa.
/// Tables, Functions, Threads, and Userdata are compared by reference:
/// two objects are considered equal only if they are the same object.
///
/// If Tables or Userdata have `__eq` metamethod then mlua will try to invoke it.
/// The first value is checked first. If that value does not define a metamethod
/// for `__eq`, then mlua will check the second value.
/// Then mlua calls the metamethod with the two values as arguments, if found.
pub fn equals<T: AsRef<Self>>(&self, other: T) -> Result<bool> {
match (self, other.as_ref()) {
(Value::Table(a), Value::Table(b)) => a.equals(b),
(Value::UserData(a), Value::UserData(b)) => a.equals(b),
_ => Ok(self == other.as_ref()),
}
}
}
impl<'lua> PartialEq for Value<'lua> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Value::Nil, Value::Nil) => true,
(Value::Boolean(a), Value::Boolean(b)) => a == b,
(Value::LightUserData(a), Value::LightUserData(b)) => a == b,
(Value::Integer(a), Value::Integer(b)) => *a == *b,
(Value::Integer(a), Value::Number(b)) => *a as ffi::lua_Number == *b,
(Value::Number(a), Value::Integer(b)) => *a == *b as ffi::lua_Number,
(Value::Number(a), Value::Number(b)) => *a == *b,
(Value::String(a), Value::String(b)) => a == b,
(Value::Table(a), Value::Table(b)) => a == b,
(Value::Function(a), Value::Function(b)) => a == b,
(Value::Thread(a), Value::Thread(b)) => a == b,
(Value::UserData(a), Value::UserData(b)) => a == b,
_ => false,
}
}
}
impl<'lua> AsRef<Value<'lua>> for Value<'lua> {
#[inline]
fn as_ref(&self) -> &Self {
self
}
}
/// Trait for types convertible to `Value`.
+295
View File
@@ -0,0 +1,295 @@
#![cfg(feature = "async")]
use std::cell::Cell;
use std::rc::Rc;
use std::time::Duration;
use futures_timer::Delay;
use futures_util::stream::TryStreamExt;
use mlua::{Error, Function, Lua, Result, Table, TableExt, UserData, UserDataMethods};
#[tokio::test]
async fn test_async_function() -> Result<()> {
let lua = Lua::new();
let f = lua
.create_async_function(|_lua, (a, b, c): (i64, i64, i64)| async move { Ok((a + b) * c) })?;
lua.globals().set("f", f)?;
let res: i64 = lua.load("f(1, 2, 3)").eval_async().await?;
assert_eq!(res, 9);
Ok(())
}
#[tokio::test]
async fn test_async_sleep() -> Result<()> {
let lua = Lua::new();
let sleep = lua.create_async_function(move |_lua, n: u64| async move {
Delay::new(Duration::from_millis(n)).await;
Ok(format!("elapsed:{}ms", n))
})?;
lua.globals().set("sleep", sleep)?;
let res: String = lua.load(r"return sleep(...)").call_async(100).await?;
assert_eq!(res, "elapsed:100ms");
Ok(())
}
#[tokio::test]
async fn test_async_call() -> Result<()> {
let lua = Lua::new();
let hello = lua.create_async_function(|_lua, name: String| async move {
Delay::new(Duration::from_millis(10)).await;
Ok(format!("hello, {}!", name))
})?;
match hello.call::<_, ()>("alex") {
Err(Error::RuntimeError(_)) => {}
_ => panic!(
"non-async executing async function must fail on the yield stage with RuntimeError"
),
};
assert_eq!(hello.call_async::<_, String>("alex").await?, "hello, alex!");
// Executing non-async functions using async call is allowed
let sum = lua.create_function(|_lua, (a, b): (i64, i64)| return Ok(a + b))?;
assert_eq!(sum.call_async::<_, i64>((5, 1)).await?, 6);
Ok(())
}
#[tokio::test]
async fn test_async_bind_call() -> Result<()> {
let lua = Lua::new();
let sum = lua.create_async_function(|_lua, (a, b): (i64, i64)| async move { Ok(a + b) })?;
let plus_10 = sum.bind(10)?;
lua.globals().set("plus_10", plus_10)?;
assert_eq!(lua.load("plus_10(-1)").eval_async::<i64>().await?, 9);
assert_eq!(lua.load("plus_10(1)").eval_async::<i64>().await?, 11);
Ok(())
}
#[tokio::test]
async fn test_async_handle_yield() -> Result<()> {
let lua = Lua::new();
let sum = lua.create_async_function(|_lua, (a, b): (i64, i64)| async move {
Delay::new(Duration::from_millis(10)).await;
Ok(a + b)
})?;
lua.globals().set("sleep_sum", sum)?;
let res: String = lua
.load(
r#"
sum = sleep_sum(6, 7)
assert(sum == 13)
coroutine.yield("in progress")
return "done"
"#,
)
.call_async(())
.await?;
assert_eq!(res, "done");
let min = lua
.load(
r#"
function (a, b)
coroutine.yield("ignore me")
if a < b then return a else return b end
end
"#,
)
.eval::<Function>()?;
assert_eq!(min.call_async::<_, i64>((-1, 1)).await?, -1);
Ok(())
}
#[tokio::test]
async fn test_async_return_async_closure() -> Result<()> {
let lua = Lua::new();
let f = lua.create_async_function(|lua, a: i64| async move {
Delay::new(Duration::from_millis(10)).await;
let g = lua.create_async_function(move |_, b: i64| async move {
Delay::new(Duration::from_millis(10)).await;
return Ok(a + b);
})?;
Ok(g)
})?;
lua.globals().set("f", f)?;
let res: i64 = lua
.load("local g = f(1); return g(2) + g(3)")
.call_async(())
.await?;
assert_eq!(res, 7);
Ok(())
}
#[tokio::test]
async fn test_async_thread_stream() -> Result<()> {
let lua = Lua::new();
let thread = lua.create_thread(
lua.load(
r#"
function (sum)
for i = 1,10 do
sum = sum + i
coroutine.yield(sum)
end
return sum
end
"#,
)
.eval()?,
)?;
let mut stream = thread.into_async::<_, i64>(1);
let mut sum = 0;
while let Some(n) = stream.try_next().await? {
sum += n;
}
assert_eq!(sum, 286);
Ok(())
}
#[tokio::test]
async fn test_async_thread() -> Result<()> {
let lua = Lua::new();
let cnt = Rc::new(10); // sleep 10ms
let cnt2 = cnt.clone();
let f = lua.create_async_function(move |_lua, ()| {
let cnt3 = cnt2.clone();
async move {
Delay::new(Duration::from_millis(*cnt3.as_ref())).await;
Ok("done")
}
})?;
let res: String = lua.create_thread(f)?.into_async(()).await?;
assert_eq!(res, "done");
assert_eq!(Rc::strong_count(&cnt), 2);
lua.gc_collect()?; // thread_s is non-resumable and subject to garbage collection
assert_eq!(Rc::strong_count(&cnt), 1);
Ok(())
}
#[tokio::test]
async fn test_async_table() -> Result<()> {
let lua = Lua::new();
let table = lua.create_table()?;
table.set("val", 10)?;
let get_value = lua.create_async_function(|_, table: Table| async move {
Delay::new(Duration::from_millis(10)).await;
table.get::<_, i64>("val")
})?;
table.set("get_value", get_value)?;
let set_value = lua.create_async_function(|_, (table, n): (Table, i64)| async move {
Delay::new(Duration::from_millis(10)).await;
table.set("val", n)
})?;
table.set("set_value", set_value)?;
let sleep = lua.create_async_function(|_, n| async move {
Delay::new(Duration::from_millis(n)).await;
Ok(format!("elapsed:{}ms", n))
})?;
table.set("sleep", sleep)?;
assert_eq!(
table
.call_async_method::<_, _, i64>("get_value", ())
.await?,
10
);
table.call_async_method("set_value", 15).await?;
assert_eq!(
table
.call_async_method::<_, _, i64>("get_value", ())
.await?,
15
);
assert_eq!(
table
.call_async_function::<_, _, String>("sleep", 7)
.await?,
"elapsed:7ms"
);
Ok(())
}
#[tokio::test]
async fn test_async_userdata() -> Result<()> {
#[derive(Clone)]
struct MyUserData(Rc<Cell<i64>>);
impl UserData for MyUserData {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("get_value", |_, data, ()| async move {
Delay::new(Duration::from_millis(10)).await;
Ok(data.0.get())
});
methods.add_async_method("set_value", |_, data, n| async move {
Delay::new(Duration::from_millis(10)).await;
data.0.set(n);
Ok(())
});
methods.add_async_function("sleep", |_, n| async move {
Delay::new(Duration::from_millis(n)).await;
Ok(format!("elapsed:{}ms", n))
});
}
}
let lua = Lua::new();
let globals = lua.globals();
let userdata = lua.create_userdata(MyUserData(Rc::new(Cell::new(11))))?;
globals.set("userdata", userdata.clone())?;
lua.load(
r#"
assert(userdata:get_value() == 11)
userdata:set_value(12)
assert(userdata:get_value() == 12)
assert(userdata.sleep(5) == "elapsed:5ms")
"#,
)
.exec_async()
.await?;
Ok(())
}
-11
View File
@@ -1,14 +1,3 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000"
)]
extern "system" {}
use bstr::{BStr, BString};
use mlua::{Lua, Result};
+1 -1
View File
@@ -1,5 +1,5 @@
#[cfg(any(feature = "lua53", feature = "lua51"))]
#[test]
#[ignore]
fn test_compile_fail() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/compile_fail/*.rs");
+21 -4
View File
@@ -10,6 +10,21 @@ error[E0277]: the type `std::cell::UnsafeCell<()>` may contain interior mutabili
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/lua_norefunwindsafe.rs:7:18: 7:48 lua:&mlua::lua::Lua]`
error[E0277]: the type `std::cell::UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> $DIR/lua_norefunwindsafe.rs:7:5
|
7 | catch_unwind(|| lua.create_table().unwrap());
| ^^^^^^^^^^^^ `std::cell::UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<usize>`
= note: required because it appears within the type `std::cell::Cell<usize>`
= note: required because it appears within the type `std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::marker::PhantomData<std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>>`
= note: required because it appears within the type `std::rc::Rc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `mlua::lua::Lua`
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/lua_norefunwindsafe.rs:7:18: 7:48 lua:&mlua::lua::Lua]`
error[E0277]: the type `std::cell::UnsafeCell<mlua::lua::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> $DIR/lua_norefunwindsafe.rs:7:5
|
@@ -18,8 +33,9 @@ error[E0277]: the type `std::cell::UnsafeCell<mlua::lua::ExtraData>` may contain
|
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<mlua::lua::ExtraData>`
= note: required because it appears within the type `std::cell::RefCell<mlua::lua::ExtraData>`
= note: required because it appears within the type `std::marker::PhantomData<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::sync::Arc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::marker::PhantomData<std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>>`
= note: required because it appears within the type `std::rc::Rc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `mlua::lua::Lua`
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/lua_norefunwindsafe.rs:7:18: 7:48 lua:&mlua::lua::Lua]`
@@ -33,8 +49,9 @@ error[E0277]: the type `std::cell::UnsafeCell<isize>` may contain interior mutab
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<isize>`
= note: required because it appears within the type `std::cell::Cell<isize>`
= note: required because it appears within the type `std::cell::RefCell<mlua::lua::ExtraData>`
= note: required because it appears within the type `std::marker::PhantomData<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::sync::Arc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::marker::PhantomData<std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>>`
= note: required because it appears within the type `std::rc::Rc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `mlua::lua::Lua`
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/lua_norefunwindsafe.rs:7:18: 7:48 lua:&mlua::lua::Lua]`
+23 -4
View File
@@ -12,6 +12,23 @@ error[E0277]: the type `std::cell::UnsafeCell<()>` may contain interior mutabili
= note: required because it appears within the type `mlua::table::Table<'_>`
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/ref_nounwindsafe.rs:8:18: 8:54 table:mlua::table::Table<'_>]`
error[E0277]: the type `std::cell::UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> $DIR/ref_nounwindsafe.rs:8:5
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ^^^^^^^^^^^^ `std::cell::UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<usize>`
= note: required because it appears within the type `std::cell::Cell<usize>`
= note: required because it appears within the type `std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::marker::PhantomData<std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>>`
= note: required because it appears within the type `std::rc::Rc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `mlua::lua::Lua`
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
= note: required because it appears within the type `mlua::types::LuaRef<'_>`
= note: required because it appears within the type `mlua::table::Table<'_>`
= note: required because it appears within the type `[closure@$DIR/tests/compile_fail/ref_nounwindsafe.rs:8:18: 8:54 table:mlua::table::Table<'_>]`
error[E0277]: the type `std::cell::UnsafeCell<mlua::lua::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> $DIR/ref_nounwindsafe.rs:8:5
|
@@ -20,8 +37,9 @@ error[E0277]: the type `std::cell::UnsafeCell<mlua::lua::ExtraData>` may contain
|
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<mlua::lua::ExtraData>`
= note: required because it appears within the type `std::cell::RefCell<mlua::lua::ExtraData>`
= note: required because it appears within the type `std::marker::PhantomData<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::sync::Arc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::marker::PhantomData<std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>>`
= note: required because it appears within the type `std::rc::Rc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `mlua::lua::Lua`
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
= note: required because it appears within the type `mlua::types::LuaRef<'_>`
@@ -37,8 +55,9 @@ error[E0277]: the type `std::cell::UnsafeCell<isize>` may contain interior mutab
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<isize>`
= note: required because it appears within the type `std::cell::Cell<isize>`
= note: required because it appears within the type `std::cell::RefCell<mlua::lua::ExtraData>`
= note: required because it appears within the type `std::marker::PhantomData<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::sync::Arc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `std::marker::PhantomData<std::rc::RcBox<std::cell::RefCell<mlua::lua::ExtraData>>>`
= note: required because it appears within the type `std::rc::Rc<std::cell::RefCell<mlua::lua::ExtraData>>`
= note: required because it appears within the type `mlua::lua::Lua`
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
= note: required because it appears within the type `mlua::types::LuaRef<'_>`
@@ -1,22 +0,0 @@
use mlua::{Lua, Table, Result};
struct Test {
field: i32,
}
fn main() {
let lua = Lua::new();
lua.scope(|scope| -> Result<()> {
let mut inner: Option<Table> = None;
let f = scope
.create_function_mut(move |lua, t: Table| {
if let Some(old) = inner.take() {
// Access old callback `Lua`.
}
inner = Some(t);
Ok(())
})?;
f.call::<_, ()>(lua.create_table()?)?;
Ok(())
});
}
@@ -1,45 +0,0 @@
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
--> $DIR/scope_callback_capture.rs:12:14
|
12 | .create_function_mut(move |lua, t: Table| {
| ^^^^^^^^^^^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 9:15...
--> $DIR/scope_callback_capture.rs:9:15
|
9 | lua.scope(|scope| -> Result<()> {
| _______________^
10 | | let mut inner: Option<Table> = None;
11 | | let f = scope
12 | | .create_function_mut(move |lua, t: Table| {
... |
20 | | Ok(())
21 | | });
| |_____^
note: ...so that reference does not outlive borrowed content
--> $DIR/scope_callback_capture.rs:11:17
|
11 | let f = scope
| ^^^^^
note: but, the lifetime must be valid for the method call at 9:5...
--> $DIR/scope_callback_capture.rs:9:5
|
9 | / lua.scope(|scope| -> Result<()> {
10 | | let mut inner: Option<Table> = None;
11 | | let f = scope
12 | | .create_function_mut(move |lua, t: Table| {
... |
20 | | Ok(())
21 | | });
| |______^
note: ...so that a type/lifetime parameter is in scope here
--> $DIR/scope_callback_capture.rs:9:5
|
9 | / lua.scope(|scope| -> Result<()> {
10 | | let mut inner: Option<Table> = None;
11 | | let f = scope
12 | | .create_function_mut(move |lua, t: Table| {
... |
20 | | Ok(())
21 | | });
| |______^
@@ -1,19 +0,0 @@
use mlua::{Lua, Table, Result};
struct Test {
field: i32,
}
fn main() {
let lua = Lua::new();
lua.scope(|scope| -> Result<()> {
let mut inner: Option<Table> = None;
let f = scope
.create_function_mut(|_, t: Table| {
inner = Some(t);
Ok(())
})?;
f.call::<_, ()>(lua.create_table()?)?;
Ok(())
});
}
@@ -1,45 +0,0 @@
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
--> $DIR/scope_callback_inner.rs:12:14
|
12 | .create_function_mut(|_, t: Table| {
| ^^^^^^^^^^^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 9:15...
--> $DIR/scope_callback_inner.rs:9:15
|
9 | lua.scope(|scope| -> Result<()> {
| _______________^
10 | | let mut inner: Option<Table> = None;
11 | | let f = scope
12 | | .create_function_mut(|_, t: Table| {
... |
17 | | Ok(())
18 | | });
| |_____^
note: ...so that reference does not outlive borrowed content
--> $DIR/scope_callback_inner.rs:11:17
|
11 | let f = scope
| ^^^^^
note: but, the lifetime must be valid for the method call at 9:5...
--> $DIR/scope_callback_inner.rs:9:5
|
9 | / lua.scope(|scope| -> Result<()> {
10 | | let mut inner: Option<Table> = None;
11 | | let f = scope
12 | | .create_function_mut(|_, t: Table| {
... |
17 | | Ok(())
18 | | });
| |______^
note: ...so that a type/lifetime parameter is in scope here
--> $DIR/scope_callback_inner.rs:9:5
|
9 | / lua.scope(|scope| -> Result<()> {
10 | | let mut inner: Option<Table> = None;
11 | | let f = scope
12 | | .create_function_mut(|_, t: Table| {
... |
17 | | Ok(())
18 | | });
| |______^
@@ -1,19 +0,0 @@
use mlua::{Lua, Table, Result};
struct Test {
field: i32,
}
fn main() {
let lua = Lua::new();
let mut outer: Option<Table> = None;
lua.scope(|scope| -> Result<()> {
let f = scope
.create_function_mut(|_, t: Table| {
outer = Some(t);
Ok(())
})?;
f.call::<_, ()>(lua.create_table()?)?;
Ok(())
});
}
@@ -1,11 +0,0 @@
error: borrowed data cannot be stored outside of its closure
--> $DIR/scope_callback_outer.rs:11:17
|
9 | let mut outer: Option<Table> = None;
| --------- ...so that variable is valid at time of its declaration
10 | lua.scope(|scope| -> Result<()> {
| --------------------- borrowed data cannot outlive this closure
11 | let f = scope
| ^^^^^ cannot be stored outside of its closure
12 | .create_function_mut(|_, t: Table| {
| ------------------- cannot infer an appropriate lifetime...
-23
View File
@@ -1,23 +0,0 @@
use mlua::{Lua, Result};
struct Test {
field: i32,
}
fn main() {
let lua = Lua::new();
lua.scope(|scope| -> Result<()> {
let f = {
let mut test = Test { field: 0 };
scope
.create_function_mut(|_, ()| {
test.field = 42;
//~^ error: `test` does not live long enough
Ok(())
})?
};
f.call::<_, ()>(())
});
}
@@ -1,25 +0,0 @@
error[E0373]: closure may outlive the current function, but it borrows `test`, which is owned by the current function
--> $DIR/scope_invariance.rs:14:38
|
9 | lua.scope(|scope| -> Result<()> {
| ----- has type `&mlua::scope::Scope<'_, '1>`
...
14 | .create_function_mut(|_, ()| {
| ^^^^^^^ may outlive borrowed value `test`
15 | test.field = 42;
| ---- `test` is borrowed here
|
note: function requires argument type to outlive `'1`
--> $DIR/scope_invariance.rs:13:13
|
13 | / scope
14 | | .create_function_mut(|_, ()| {
15 | | test.field = 42;
16 | | //~^ error: `test` does not live long enough
17 | | Ok(())
18 | | })?
| |__________________^
help: to force the closure to take ownership of `test` (and any other referenced variables), use the `move` keyword
|
14 | .create_function_mut(move |_, ()| {
| ^^^^^^^^^^^^
@@ -1,15 +0,0 @@
use mlua::{Lua, UserData, Result};
struct MyUserData<'a>(&'a mut i32);
impl<'a> UserData for MyUserData<'a> {}
fn main() {
let mut i = 1;
let lua = Lua::new();
lua.scope(|scope| -> Result<()> {
let _a = scope.create_nonstatic_userdata(MyUserData(&mut i))?;
let _b = scope.create_nonstatic_userdata(MyUserData(&mut i))?;
Ok(())
});
}
@@ -1,9 +0,0 @@
error[E0499]: cannot borrow `i` as mutable more than once at a time
--> $DIR/scope_mutable_aliasing.rs:12:61
|
11 | let _a = scope.create_nonstatic_userdata(MyUserData(&mut i))?;
| ------ first mutable borrow occurs here
12 | let _b = scope.create_nonstatic_userdata(MyUserData(&mut i))?;
| ------------------------- ^^^^^^ second mutable borrow occurs here
| |
| first borrow later used by call
@@ -1,20 +0,0 @@
use mlua::{Lua, UserData, Result};
struct MyUserData<'a>(&'a i32);
impl<'a> UserData for MyUserData<'a> {}
fn main() {
// Should not allow userdata borrow to outlive lifetime of AnyUserData handle
let igood = 1;
let lua = Lua::new();
lua.scope(|scope| -> Result<()> {
let _ugood = scope.create_nonstatic_userdata(MyUserData(&igood))?;
let _ubad = {
let ibad = 42;
scope.create_nonstatic_userdata(MyUserData(&ibad))?;
};
Ok(())
});
}
@@ -1,13 +0,0 @@
error[E0597]: `ibad` does not live long enough
--> $DIR/scope_userdata_borrow.rs:16:56
|
12 | lua.scope(|scope| -> Result<()> {
| ----- has type `&mlua::scope::Scope<'_, '1>`
...
16 | scope.create_nonstatic_userdata(MyUserData(&ibad))?;
| -------------------------------------------^^^^^--
| | |
| | borrowed value does not live long enough
| argument requires that `ibad` is borrowed for `'1`
17 | };
| - `ibad` dropped here while still borrowed
-11
View File
@@ -1,14 +1,3 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000"
)]
extern "system" {}
use mlua::{Function, Lua, Result, String};
#[test]
+17 -28
View File
@@ -1,21 +1,11 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000"
)]
extern "system" {}
use std::sync::Arc;
use std::rc::Rc;
use mlua::{Lua, Result, UserData};
#[test]
fn test_gc_control() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
#[cfg(any(feature = "lua53", feature = "lua52"))]
{
@@ -26,18 +16,17 @@ fn test_gc_control() -> Result<()> {
assert!(lua.gc_is_running());
}
struct MyUserdata(Arc<()>);
struct MyUserdata(Rc<()>);
impl UserData for MyUserdata {}
let rc = Arc::new(());
lua.globals()
.set("userdata", lua.create_userdata(MyUserdata(rc.clone()))?)?;
lua.globals().raw_remove("userdata")?;
let rc = Rc::new(());
globals.set("userdata", lua.create_userdata(MyUserdata(rc.clone()))?)?;
globals.raw_remove("userdata")?;
assert_eq!(Arc::strong_count(&rc), 2);
assert_eq!(Rc::strong_count(&rc), 2);
lua.gc_collect()?;
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&rc), 1);
assert_eq!(Rc::strong_count(&rc), 1);
Ok(())
}
@@ -51,15 +40,15 @@ fn test_gc_error() {
match lua
.load(
r#"
val = nil
table = {}
setmetatable(table, {
__gc = function()
error("gcwascalled")
end
})
table = nil
collectgarbage("collect")
val = nil
table = {}
setmetatable(table, {
__gc = function()
error("gcwascalled")
end
})
table = nil
collectgarbage("collect")
"#,
)
.exec()
-242
View File
@@ -1,242 +0,0 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000"
)]
extern "system" {}
use std::cell::Cell;
use std::rc::Rc;
use mlua::{Error, Function, Lua, MetaMethod, Result, String, UserData, UserDataMethods};
#[test]
fn scope_func() -> Result<()> {
let lua = Lua::new();
let rc = Rc::new(Cell::new(0));
lua.scope(|scope| {
let r = rc.clone();
let f = scope.create_function(move |_, ()| {
r.set(42);
Ok(())
})?;
lua.globals().set("bad", f.clone())?;
f.call::<_, ()>(())?;
assert_eq!(Rc::strong_count(&rc), 2);
Ok(())
})?;
assert_eq!(rc.get(), 42);
assert_eq!(Rc::strong_count(&rc), 1);
match lua.globals().get::<_, Function>("bad")?.call::<_, ()>(()) {
Err(Error::CallbackError { .. }) => {}
r => panic!("improper return for destructed function: {:?}", r),
};
Ok(())
}
#[test]
fn scope_drop() -> Result<()> {
let lua = Lua::new();
struct MyUserdata(Rc<()>);
impl UserData for MyUserdata {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("method", |_, _, ()| Ok(()));
}
}
let rc = Rc::new(());
lua.scope(|scope| {
lua.globals().set(
"test",
scope.create_static_userdata(MyUserdata(rc.clone()))?,
)?;
assert_eq!(Rc::strong_count(&rc), 2);
Ok(())
})?;
assert_eq!(Rc::strong_count(&rc), 1);
match lua.load("test:method()").exec() {
Err(Error::CallbackError { .. }) => {}
r => panic!("improper return for destructed userdata: {:?}", r),
};
Ok(())
}
#[test]
fn scope_capture() -> Result<()> {
let lua = Lua::new();
let mut i = 0;
lua.scope(|scope| {
scope
.create_function_mut(|_, ()| {
i = 42;
Ok(())
})?
.call::<_, ()>(())
})?;
assert_eq!(i, 42);
Ok(())
}
#[test]
fn outer_lua_access() -> Result<()> {
let lua = Lua::new();
let table = lua.create_table()?;
lua.scope(|scope| {
scope
.create_function_mut(|_, ()| table.set("a", "b"))?
.call::<_, ()>(())
})?;
assert_eq!(table.get::<_, String>("a")?, "b");
Ok(())
}
#[test]
fn scope_userdata_methods() -> Result<()> {
struct MyUserData<'a>(&'a Cell<i64>);
impl<'a> UserData for MyUserData<'a> {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("inc", |_, data, ()| {
data.0.set(data.0.get() + 1);
Ok(())
});
methods.add_method("dec", |_, data, ()| {
data.0.set(data.0.get() - 1);
Ok(())
});
}
}
let lua = Lua::new();
let i = Cell::new(42);
let f: Function = lua
.load(
r#"
function(u)
u:inc()
u:inc()
u:inc()
u:dec()
end
"#,
)
.eval()?;
lua.scope(|scope| f.call::<_, ()>(scope.create_nonstatic_userdata(MyUserData(&i))?))?;
assert_eq!(i.get(), 44);
Ok(())
}
#[test]
fn scope_userdata_functions() -> Result<()> {
struct MyUserData<'a>(&'a i64);
impl<'a> UserData for MyUserData<'a> {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_function(MetaMethod::Add, |lua, ()| {
let globals = lua.globals();
globals.set("i", globals.get::<_, i64>("i")? + 1)?;
Ok(())
});
methods.add_meta_function(MetaMethod::Sub, |lua, ()| {
let globals = lua.globals();
globals.set("i", globals.get::<_, i64>("i")? + 1)?;
Ok(())
});
}
}
let lua = Lua::new();
let dummy = 0;
let f = lua
.load(
r#"
i = 0
return function(u)
_ = u + u
_ = u - 1
_ = 1 + u
end
"#,
)
.eval::<Function>()?;
lua.scope(|scope| f.call::<_, ()>(scope.create_nonstatic_userdata(MyUserData(&dummy))?))?;
assert_eq!(lua.globals().get::<_, i64>("i")?, 3);
Ok(())
}
#[test]
fn scope_userdata_mismatch() -> Result<()> {
struct MyUserData<'a>(&'a Cell<i64>);
impl<'a> UserData for MyUserData<'a> {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("inc", |_, data, ()| {
data.0.set(data.0.get() + 1);
Ok(())
});
}
}
let lua = Lua::new();
lua.load(
r#"
function okay(a, b)
a.inc(a)
b.inc(b)
end
function bad(a, b)
a.inc(b)
end
"#,
)
.exec()?;
let a = Cell::new(1);
let b = Cell::new(1);
let okay: Function = lua.globals().get("okay")?;
let bad: Function = lua.globals().get("bad")?;
lua.scope(|scope| {
let au = scope.create_nonstatic_userdata(MyUserData(&a))?;
let bu = scope.create_nonstatic_userdata(MyUserData(&b))?;
assert!(okay.call::<_, ()>((au.clone(), bu.clone())).is_ok());
match bad.call::<_, ()>((au, bu)) {
Err(Error::CallbackError { ref cause, .. }) => match *cause.as_ref() {
Error::UserDataTypeMismatch => {}
ref other => panic!("wrong error type {:?}", other),
},
Err(other) => panic!("wrong error type {:?}", other),
Ok(_) => panic!("incorrectly returned Ok"),
}
Ok(())
})?;
Ok(())
}
-11
View File
@@ -1,14 +1,3 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000"
)]
extern "system" {}
use std::borrow::Cow;
use mlua::{Lua, Result, String};
+129 -13
View File
@@ -1,15 +1,4 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000"
)]
extern "system" {}
use mlua::{Lua, Nil, Result, Table, Value};
use mlua::{Lua, Nil, Result, Table, TableExt, Value};
#[test]
fn test_set_get() -> Result<()> {
@@ -91,10 +80,73 @@ fn test_table() -> Result<()> {
globals.set("table4", lua.create_sequence_from(vec![1, 2, 3, 4, 5])?)?;
let table4 = globals.get::<_, Table>("table4")?;
assert_eq!(
table4.pairs().collect::<Result<Vec<(i64, i64)>>>()?,
table4
.clone()
.pairs()
.collect::<Result<Vec<(i64, i64)>>>()?,
vec![(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
);
table4.raw_insert(4, 35)?;
table4.raw_insert(7, 7)?;
assert_eq!(
table4
.clone()
.pairs()
.collect::<Result<Vec<(i64, i64)>>>()?,
vec![(1, 1), (2, 2), (3, 3), (4, 35), (5, 4), (6, 5), (7, 7)]
);
table4.raw_remove(1)?;
assert_eq!(
table4
.clone()
.pairs()
.collect::<Result<Vec<(i64, i64)>>>()?,
vec![(1, 2), (2, 3), (3, 35), (4, 4), (5, 5), (6, 7)]
);
Ok(())
}
#[test]
fn test_table_sequence_from() -> Result<()> {
let lua = Lua::new();
let get_table = lua.create_function(|_, t: Table| Ok(t))?;
assert_eq!(
get_table
.call::<_, Table>(vec![1, 2, 3])?
.sequence_values()
.collect::<Result<Vec<i64>>>()?,
vec![1, 2, 3]
);
assert_eq!(
get_table
.call::<_, Table>([1, 2, 3].as_ref())?
.sequence_values()
.collect::<Result<Vec<i64>>>()?,
vec![1, 2, 3]
);
assert_eq!(
get_table
.call::<_, Table>([1, 2, 3])?
.sequence_values()
.collect::<Result<Vec<i64>>>()?,
vec![1, 2, 3]
);
assert_eq!(
get_table
.call::<_, Table>(&[1, 2, 3])?
.sequence_values()
.collect::<Result<Vec<i64>>>()?,
vec![1, 2, 3]
);
Ok(())
}
@@ -148,6 +200,40 @@ fn test_metatable() -> Result<()> {
Ok(())
}
#[test]
fn test_table_eq() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
lua.load(
r#"
table1 = {1}
table2 = {1}
table3 = table1
table4 = {1}
setmetatable(table4, {
__eq = function(a, b) return a[1] == b[1] end
})
"#,
)
.exec()?;
let table1 = globals.get::<_, Table>("table1")?;
let table2 = globals.get::<_, Table>("table2")?;
let table3 = globals.get::<_, Table>("table3")?;
let table4 = globals.get::<_, Table>("table4")?;
assert!(table1 != table2);
assert!(!table1.equals(&table2)?);
assert!(table1 == table3);
assert!(table1.equals(&table3)?);
assert!(table1 != table4);
assert!(table1.equals(&table4)?);
Ok(())
}
#[test]
fn test_table_error() -> Result<()> {
let lua = Lua::new();
@@ -181,3 +267,33 @@ fn test_table_error() -> Result<()> {
Ok(())
}
#[test]
fn test_table_call() -> Result<()> {
let lua = Lua::new();
lua.load(
r#"
table = {a = 1}
function table.func(key)
return "func_"..key
end
function table:method(key)
return "method_"..self[key]
end
"#,
)
.exec()?;
let table: Table = lua.globals().get("table")?;
assert_eq!(table.call_function::<_, _, String>("func", "a")?, "func_a");
assert_eq!(
table.call_method::<_, _, String>("method", "a")?,
"method_1"
);
Ok(())
}
+11 -22
View File
@@ -1,17 +1,6 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000"
)]
extern "system" {}
use std::iter::FromIterator;
use std::panic::catch_unwind;
use std::sync::Arc;
use std::rc::Rc;
use std::{error, f32, f64, fmt};
use mlua::{
@@ -92,13 +81,13 @@ fn test_lua_multi() -> Result<()> {
lua.load(
r#"
function concat(arg1, arg2)
return arg1 .. arg2
end
function concat(arg1, arg2)
return arg1 .. arg2
end
function mreturn()
return 1, 2, 3, 4, 5, 6
end
function mreturn()
return 1, 2, 3, 4, 5, 6
end
"#,
)
.exec()?;
@@ -595,22 +584,22 @@ fn test_registry_value() -> Result<()> {
#[test]
fn test_drop_registry_value() -> Result<()> {
struct MyUserdata(Arc<()>);
struct MyUserdata(Rc<()>);
impl UserData for MyUserdata {}
let lua = Lua::new();
let rc = Arc::new(());
let rc = Rc::new(());
let r = lua.create_registry_value(MyUserdata(rc.clone()))?;
assert_eq!(Arc::strong_count(&rc), 2);
assert_eq!(Rc::strong_count(&rc), 2);
drop(r);
lua.expire_registry_values();
lua.load(r#"collectgarbage("collect")"#).exec()?;
assert_eq!(Arc::strong_count(&rc), 1);
assert_eq!(Rc::strong_count(&rc), 1);
Ok(())
}
+10 -21
View File
@@ -1,14 +1,3 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000"
)]
extern "system" {}
use std::panic::catch_unwind;
use mlua::{Error, Function, Lua, Result, Thread, ThreadStatus};
@@ -20,13 +9,13 @@ fn test_thread() -> Result<()> {
let thread = lua.create_thread(
lua.load(
r#"
function (s)
local sum = s
for i = 1,4 do
sum = sum + coroutine.yield(sum)
end
return sum
function (s)
local sum = s
for i = 1,4 do
sum = sum + coroutine.yield(sum)
end
return sum
end
"#,
)
.eval()?,
@@ -47,11 +36,11 @@ fn test_thread() -> Result<()> {
let accumulate = lua.create_thread(
lua.load(
r#"
function (sum)
while true do
sum = sum + coroutine.yield(sum)
end
function (sum)
while true do
sum = sum + coroutine.yield(sum)
end
end
"#,
)
.eval::<Function>()?,
-11
View File
@@ -1,14 +1,3 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000"
)]
extern "system" {}
use std::os::raw::c_void;
use mlua::{Function, LightUserData, Lua, Result};
+35 -34
View File
@@ -1,19 +1,8 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000"
)]
extern "system" {}
use std::sync::Arc;
use std::rc::Rc;
use mlua::{
AnyUserData, ExternalError, Function, Lua, MetaMethod, Result, String, UserData,
UserDataMethods,
UserDataMethods, Value,
};
#[test]
@@ -96,6 +85,9 @@ fn test_metamethods() -> Result<()> {
MetaMethod::Sub,
|_, (lhs, rhs): (MyUserData, MyUserData)| Ok(MyUserData(lhs.0 - rhs.0)),
);
methods.add_meta_function(MetaMethod::Eq, |_, (lhs, rhs): (MyUserData, MyUserData)| {
Ok(lhs.0 == rhs.0)
});
methods.add_meta_method(MetaMethod::Index, |_, data, index: String| {
if index.to_str()? == "inner" {
Ok(data.0)
@@ -104,7 +96,7 @@ fn test_metamethods() -> Result<()> {
}
});
#[cfg(any(feature = "lua53", feature = "lua52"))]
methods.add_meta_method(MetaMethod::IPairs, |lua, data, ()| {
methods.add_meta_method(MetaMethod::Pairs, |lua, data, ()| {
use std::iter::FromIterator;
let stateless_iter = lua.create_function(|_, (data, i): (MyUserData, i64)| {
let i = i + 1;
@@ -122,18 +114,19 @@ fn test_metamethods() -> Result<()> {
let globals = lua.globals();
globals.set("userdata1", MyUserData(7))?;
globals.set("userdata2", MyUserData(3))?;
globals.set("userdata3", MyUserData(3))?;
assert_eq!(
lua.load("userdata1 + userdata2").eval::<MyUserData>()?.0,
10
);
#[cfg(any(feature = "lua53", feature = "lua52"))]
let ipairs_it = {
let pairs_it = {
lua.load(
r#"
function ipairs_it()
function pairs_it()
local r = 0
for i, v in ipairs(userdata1) do
for i, v in pairs(userdata1) do
r = r + v
end
return r
@@ -141,16 +134,23 @@ fn test_metamethods() -> Result<()> {
"#,
)
.exec()?;
globals.get::<_, Function>("ipairs_it")?
globals.get::<_, Function>("pairs_it")?
};
assert_eq!(lua.load("userdata1 - userdata2").eval::<MyUserData>()?.0, 4);
assert_eq!(lua.load("userdata1:get()").eval::<i64>()?, 7);
assert_eq!(lua.load("userdata2.inner").eval::<i64>()?, 3);
#[cfg(any(feature = "lua53", feature = "lua52"))]
assert_eq!(ipairs_it.call::<_, i64>(())?, 28);
assert_eq!(pairs_it.call::<_, i64>(())?, 28);
assert!(lua.load("userdata2.nonexist_field").eval::<()>().is_err());
let userdata2: Value = globals.get("userdata2")?;
let userdata3: Value = globals.get("userdata3")?;
assert!(lua.load("userdata2 == userdata3").eval::<bool>()?);
assert!(userdata2 != userdata3); // because references are differ
assert!(userdata2.equals(userdata3)?);
Ok(())
}
@@ -175,18 +175,18 @@ fn test_gc_userdata() -> Result<()> {
assert!(lua
.load(
r#"
local tbl = setmetatable({
userdata = userdata
}, { __gc = function(self)
-- resurrect userdata
hatch = self.userdata
end })
local tbl = setmetatable({
userdata = userdata
}, { __gc = function(self)
-- resurrect userdata
hatch = self.userdata
end })
tbl = nil
userdata = nil -- make table and userdata collectable
collectgarbage("collect")
hatch:access()
"#
tbl = nil
userdata = nil -- make table and userdata collectable
collectgarbage("collect")
hatch:access()
"#
)
.exec()
.is_err());
@@ -196,22 +196,22 @@ fn test_gc_userdata() -> Result<()> {
#[test]
fn detroys_userdata() -> Result<()> {
struct MyUserdata(Arc<()>);
struct MyUserdata(Rc<()>);
impl UserData for MyUserdata {}
let rc = Arc::new(());
let rc = Rc::new(());
let lua = Lua::new();
lua.globals().set("userdata", MyUserdata(rc.clone()))?;
assert_eq!(Arc::strong_count(&rc), 2);
assert_eq!(Rc::strong_count(&rc), 2);
// should destroy all objects
let _ = lua.globals().raw_remove("userdata")?;
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&rc), 1);
assert_eq!(Rc::strong_count(&rc), 1);
Ok(())
}
@@ -219,6 +219,7 @@ fn detroys_userdata() -> Result<()> {
#[test]
fn user_value() -> Result<()> {
struct MyUserData;
impl UserData for MyUserData {}
let lua = Lua::new();
+57
View File
@@ -0,0 +1,57 @@
use mlua::{Lua, Result, Value};
#[test]
fn test_value_eq() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
lua.load(
r#"
table1 = {1}
table2 = {1}
string1 = "hello"
string2 = "hello"
num1 = 1
num2 = 1.0
num3 = "1"
func1 = function() end
func2 = func1
func3 = function() end
thread1 = coroutine.create(function() end)
thread2 = thread1
setmetatable(table1, {
__eq = function(a, b) return a[1] == b[1] end
})
"#,
)
.exec()?;
let table1: Value = globals.get("table1")?;
let table2: Value = globals.get("table2")?;
let string1: Value = globals.get("string1")?;
let string2: Value = globals.get("string2")?;
let num1: Value = globals.get("num1")?;
let num2: Value = globals.get("num2")?;
let num3: Value = globals.get("num3")?;
let func1: Value = globals.get("func1")?;
let func2: Value = globals.get("func2")?;
let func3: Value = globals.get("func3")?;
let thread1: Value = globals.get("thread1")?;
let thread2: Value = globals.get("thread2")?;
assert!(table1 != table2);
assert!(table1.equals(table2)?);
assert!(string1 == string2);
assert!(string1.equals(string2)?);
assert!(num1 == num2);
assert!(num1.equals(num2)?);
assert!(num1 != num3);
assert!(func1 == func2);
assert!(func1 != func3);
assert!(!func1.equals(func3)?);
assert!(thread1 == thread2);
assert!(thread1.equals(thread2)?);
Ok(())
}