Compare commits

..

47 Commits

Author SHA1 Message Date
Alex Orlenko 89580dd863 v0.7.0 2021-11-25 18:18:38 +00:00
Alex Orlenko a0554a56d4 Update dependencies 2021-11-25 18:12:29 +00:00
Alex Orlenko 2fee3e7891 Update docs 2021-11-25 18:01:41 +00:00
Alex Orlenko 9f073ad879 Update README & CHANGELOG 2021-11-25 13:32:13 +00:00
Alex Orlenko 1b74c89509 More performance optimizations 2021-11-25 11:16:12 +00:00
Alex Orlenko 440862de38 Update tests 2021-11-23 19:12:27 +00:00
Alex Orlenko 066d28f5e5 Update Lua::unload doc & fix clippy warnings 2021-11-23 19:01:23 +00:00
Alex Orlenko 4528f93345 Merge pull request #103 from polachok/unload
Add `Lua::unload()` to unload modules
2021-11-23 18:11:13 +00:00
Alexander Polakov d49757c7db Add unload() to remove module 2021-11-23 18:36:57 +03:00
Alex Orlenko 170818c469 Add call() function to TableExt to call tables with __call metamethod as functions 2021-11-21 23:47:45 +00:00
Alex Orlenko 55c8af1e6b Add minimum Rust requirements to README 2021-11-21 23:42:49 +00:00
Alex Orlenko e2ebe65306 Add get_named_user_value and set_named_user_value to AnyUserData 2021-11-21 23:42:47 +00:00
Alex Orlenko f0f5a8a0af Update CHANGELOG 2021-11-17 23:41:30 +00:00
Alex Orlenko 0e1be19cbb Move hook_proc under Lua::set_hook to use callback_error_ext 2021-11-16 12:27:56 +00:00
Alex Orlenko 0ef709672d Add set_warning_function/remove_warning_function/warning functions to Lua for 5.4
This utilizes Lua 5.4 warnings system (https://www.lua.org/manual/5.4/manual.html#pdf-warn)
2021-11-16 12:05:34 +00:00
Alex Orlenko 41503b4fb8 Update callback_error_ext (+ fix callback multi states handling) 2021-11-16 11:53:51 +00:00
Alex Orlenko 19bd254e1e Update comments 2021-11-14 23:27:20 +00:00
Alex Orlenko 50f20e0c2c Add thread (coroutine) cache to reset and later reuse to execute async functions.
It works on Lua 5.4 and LuaJIT (vendored) with `lua_resetthread` function.
2021-11-14 23:19:47 +00:00
Alex Orlenko 7efe807199 Include StdLib to prelude 2021-11-14 23:19:47 +00:00
Alex Orlenko 63ea8c7662 Rename wrapped_failures pool to cache 2021-11-14 23:19:47 +00:00
Alex Orlenko 863d36d5a1 Performance optimization: cache and reuse MultiValue containers 2021-11-12 15:32:53 +00:00
Alex Orlenko a8017c10b9 Update callback_error(_ext) 2021-11-12 12:41:03 +00:00
Alex Orlenko 2c7d7117d2 Optimize MultiValue allocations (recycle old container) 2021-11-12 10:55:20 +00:00
Alex Orlenko 6d689c35aa Update hook::Debug struct 2021-11-09 18:20:36 +00:00
Alex Orlenko 8af1304fd0 Add Lua::inspect_stack to get information about the interpreter runtime stack.
This functionality is provided by `lua_getstack`.
2021-11-09 14:18:14 +00:00
Alex Orlenko 153502ec73 Add set_nth_user_value and get_nth_user_value to AnyUserData
with `n` up to 65535 for all Lua versions.
2021-11-08 21:16:31 +00:00
Alex Orlenko 2ea2b1f4fb Refactor Error::CallbackError reporting and include source to
fmt::Display implementation.
This fixes #71.
2021-11-07 22:53:37 +00:00
Alex Orlenko ef8c1556e6 Add optional Send to Lua::app_data 2021-11-07 15:03:17 +00:00
Alex Orlenko b0da2fc439 Switch Table::serialize to FxHashSet 2021-11-07 14:49:19 +00:00
Alex Orlenko 204eedde3c Merge branch 'dev' 2021-11-07 13:07:22 +00:00
Alex Orlenko fbc2973aff Fix recursive tables serialization when using serde::ser::Serialize
implementation for Table.
Fixes #98.
2021-11-06 21:12:00 +00:00
Alex Orlenko d0641d812f Refactor a bit conversion int->number 2021-11-04 13:15:26 +00:00
Alex Orlenko ad70ba54a5 Publish AnyUserData::take 2021-11-04 12:32:19 +00:00
Alex Orlenko 806f0bcef4 Add luajit52 support (LuaJIT with partial compatibility with Lua 5.2) 2021-11-04 12:26:11 +00:00
Alex Orlenko 0741db7565 Make (De)SerializeOptions as const 2021-11-04 01:07:38 +00:00
Alex Orlenko d88a4282c7 Replace macro-based implementation ToLua for arrays to const generics 2021-11-04 00:59:39 +00:00
Alex Orlenko d7d987fa14 Add async meta methods for all Lua except 51 2021-11-04 00:57:49 +00:00
Alex Orlenko 4d3ac6d8c5 Add new "application data" api 2021-10-19 11:45:39 +01:00
Alex Orlenko a9ca99349c Switch to FxHash 2021-10-19 11:45:38 +01:00
Alex Orlenko f71db80a74 Change definition of lua_State to opaque struct 2021-10-19 11:45:38 +01:00
Alex Orlenko 09af3e021a v0.6.6 2021-10-12 12:22:14 +01:00
Alex Orlenko 9e3d495f91 Calculate coverage for Lua 5.4 instead of Lua 5.3 2021-10-12 12:10:04 +01:00
Alex Orlenko cab2e5a48e Fix clippy warnings 2021-10-12 12:02:42 +01:00
Alex Orlenko 25a4879cde Update examples: async_http_client/async_http_server/async_tcp_server
Make them following best practices and remove `unsafe` code.
2021-10-12 11:55:25 +01:00
Alex Orlenko ed48b11e7f Update documentation references
Using rustdoc links (see RFC https://github.com/rust-lang/rfcs/pull/1946)
2021-10-12 00:49:45 +01:00
Alex Orlenko 559f9e6c6b Add target.aarch64-apple-darwin conf to module examples 2021-10-11 18:07:34 +01:00
Alex Orlenko 458b06796c Fixed LUA_REGISTRYINDEX when generating glue.rs on cross-compiling for lua51(jit) 2021-10-08 21:15:22 +01:00
38 changed files with 1884 additions and 733 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
- name: Generate code coverage
run: |
cargo tarpaulin --verbose --features lua53,vendored,async,send,serialize,macros --out xml --exclude-files benches --exclude-files build --exclude-files mlua_derive --exclude-files src/ffi --exclude-files tests
cargo tarpaulin --verbose --features lua54,vendored,async,send,serialize,macros --out xml --exclude-files benches --exclude-files build --exclude-files mlua_derive --exclude-files src/ffi --exclude-files tests
- name: Upload to codecov.io
uses: codecov/codecov-action@v1
+24
View File
@@ -1,3 +1,27 @@
## v0.7.0
- New "application data" api to store arbitrary objects inside Lua
- New feature flag `luajit52` to build/support LuaJIT with partial compatibility with Lua 5.2
- Added async meta methods for all Lua (except 5.1)
- Added `AnyUserData::take()` to take UserData objects from Lua
- Added `set_nth_user_value`/`get_nth_user_value` to `AnyUserData` for all Lua versions
- Added `set_named_user_value`/`get_named_user_value` to `AnyUserData` for all Lua versions
- Added `Lua::inspect_stack()` to get information about the interpreter runtime stack
- Added `set_warning_function`/`remove_warning_function`/`warning` functions to `Lua` for 5.4
- Added `TableExt::call()` to call tables with `__call` metamethod as functions
- Added `Lua::unload()` to unload modules
- `ToLua` implementation for arrays changed to const generics
- Added thread (coroutine) cache for async execution (disabled by default and works for Lua 5.4/JIT)
- LuaOptions and (De)SerializeOptions marked as const
- Fixed recursive tables serialization when using `serde::Serialize` for Lua Tables
- Improved errors reporting. Now source included to `fmt::Display` implementation for `Error::CallbackError`
- Major performance improvements
## v0.6.6
- Fixed calculating `LUA_REGISTRYINDEX` when cross-compiling for lua51/jit (#82)
- Updated documentation & examples
## v0.6.5
- Fixed bug when polling async futures (#77)
+7 -5
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua"
version = "0.6.5" # remember to update html_root_url and mlua_derive
version = "0.7.0" # remember to update html_root_url and mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"]
edition = "2018"
repository = "https://github.com/khvzak/mlua"
@@ -17,7 +17,7 @@ with async/await features and support of writing native Lua modules in Rust.
"""
[package.metadata.docs.rs]
features = ["lua53", "async", "send", "serialize", "macros"]
features = ["lua54", "vendored", "async", "send", "serialize", "macros"]
rustdoc-args = ["--cfg", "docsrs"]
[workspace]
@@ -33,6 +33,7 @@ lua53 = []
lua52 = []
lua51 = []
luajit = []
luajit52 = ["luajit"]
vendored = ["lua-src", "luajit-src"]
module = ["mlua_derive"]
async = ["futures-core", "futures-task", "futures-util"]
@@ -45,6 +46,7 @@ mlua_derive = { version = "=0.6.0", optional = true, path = "mlua_derive" }
bstr = { version = "0.2", features = ["std"], default_features = false }
once_cell = { version = "1.0" }
num-traits = { version = "0.2.14" }
rustc-hash = "1.0"
futures-core = { version = "0.3.5", optional = true }
futures-task = { version = "0.3.5", optional = true }
futures-util = { version = "0.3.5", optional = true }
@@ -55,10 +57,10 @@ erased-serde = { version = "0.3", optional = true }
cc = { version = "1.0" }
pkg-config = { version = "0.3.17" }
lua-src = { version = ">= 540.0.0, < 550.0.0", optional = true }
luajit-src = { version = ">= 210.1.2, < 220.0.0", optional = true }
luajit-src = { version = ">= 210.3.1, < 220.0.0", optional = true }
[dev-dependencies]
rustyline = "8.0"
rustyline = "9.0"
criterion = { version = "0.3.4", features = ["html_reports", "async_tokio"] }
trybuild = "1.0"
futures = "0.3.5"
@@ -84,7 +86,7 @@ required-features = ["async", "serialize", "macros"]
[[example]]
name = "async_http_server"
required-features = ["async", "send"]
required-features = ["async", "macros"]
[[example]]
name = "async_tcp_server"
+13 -7
View File
@@ -1,5 +1,5 @@
# mlua
[![Build Status]][github-actions] [![Latest Version]][crates.io] [![API Documentation]][docs.rs] [![Coverage Status]][codecov.io]
[![Build Status]][github-actions] [![Latest Version]][crates.io] [![API Documentation]][docs.rs] [![Coverage Status]][codecov.io] ![MSRV]
[Build Status]: https://github.com/khvzak/mlua/workflows/CI/badge.svg
[github-actions]: https://github.com/khvzak/mlua/actions
@@ -9,17 +9,22 @@
[docs.rs]: https://docs.rs/mlua
[Coverage Status]: https://codecov.io/gh/khvzak/mlua/branch/master/graph/badge.svg?token=99339FS1CG
[codecov.io]: https://codecov.io/gh/khvzak/mlua
[MSRV]: https://img.shields.io/badge/rust-1.51+-brightgreen.svg?&logo=rust
[Guided Tour](examples/guided_tour.rs)
`mlua` is bindings to [Lua](https://www.lua.org) programming language for Rust with a goal to provide
_safe_ (as far as it's possible), high level, easy to use, practical and flexible API.
Started as [rlua](https://github.com/amethyst/rlua/tree/0.15.3) fork, `mlua` supports Lua 5.4, 5.3, 5.2 and 5.1 including LuaJIT (2.0.5 and 2.1 beta) and allows to write native Lua modules in Rust as well as use Lua in a standalone mode.
Started as [rlua] fork, `mlua` supports Lua 5.4, 5.3, 5.2 and 5.1 including LuaJIT (2.0.5 and 2.1 beta) and allows to write native Lua modules in Rust as well as use Lua in a standalone mode.
`mlua` tested on Windows/macOS/Linux including module mode in [GitHub Actions] on `x86_64` platform and cross-compilation to `aarch64` (other targets are also supported).
Check our [benchmark results](https://github.com/khvzak/script-bench-rs) for `mlua`, [rlua] and [hlua].
[GitHub Actions]: https://github.com/khvzak/mlua/actions
[rlua]: https://github.com/amethyst/rlua
[hlua]: https://github.com/tomaka/hlua
## Usage
@@ -33,6 +38,7 @@ Below is a list of the available feature flags. By default `mlua` does not enabl
* `lua52`: activate Lua [5.2] support
* `lua51`: activate Lua [5.1] support
* `luajit`: activate [LuaJIT] support
* `luajit52`: activate [LuaJIT] support with partial compatibility with Lua 5.2
* `vendored`: build static Lua(JIT) library from sources during `mlua` compilation using [lua-src] or [luajit-src] crates
* `module`: enable module mode (building loadable `cdylib` library for Lua)
* `async`: enable async/await support (any executor can be used, eg. [tokio] or [async-std])
@@ -74,7 +80,7 @@ With `serialize` feature flag enabled, `mlua` allows you to serialize/deserializ
### Compiling
You have to enable one of the features `lua54`, `lua53`, `lua52`, `lua51` or `luajit`, according to the chosen Lua version.
You have to enable one of the features `lua54`, `lua53`, `lua52`, `lua51` or `luajit(52)`, according to the chosen Lua version.
By default `mlua` uses `pkg-config` tool to find lua includes and libraries for the chosen Lua version.
In most cases it works as desired, although sometimes could be more preferable to use a custom lua library.
@@ -97,7 +103,7 @@ Add to `Cargo.toml` :
``` toml
[dependencies]
mlua = { version = "0.6", features = ["lua53", "vendored"] }
mlua = { version = "0.6", features = ["lua54", "vendored"] }
```
`main.rs`
@@ -121,7 +127,7 @@ fn main() -> LuaResult<()> {
```
### Module mode
In a module mode `mlua` allows to create a compiled Lua module that can be loaded from Lua code using [`require`](https://www.lua.org/manual/5.3/manual.html#pdf-require). In this case `mlua` uses an external Lua runtime which could lead to potential unsafety due to unpredictability of the Lua environment and usage of libraries such as [`debug`](https://www.lua.org/manual/5.3/manual.html#6.10).
In a module mode `mlua` allows to create a compiled Lua module that can be loaded from Lua code using [`require`](https://www.lua.org/manual/5.4/manual.html#pdf-require). In this case `mlua` uses an external Lua runtime which could lead to potential unsafety due to unpredictability of the Lua environment and usage of libraries such as [`debug`](https://www.lua.org/manual/5.4/manual.html#6.10).
[Example](examples/module)
@@ -132,7 +138,7 @@ Add to `Cargo.toml` :
crate-type = ["cdylib"]
[dependencies]
mlua = { version = "0.6", features = ["lua53", "vendored", "module"] }
mlua = { version = "0.6", features = ["lua54", "vendored", "module"] }
```
`lib.rs` :
@@ -158,7 +164,7 @@ And then (**macOS** example):
``` sh
$ cargo rustc -- -C link-arg=-undefined -C link-arg=dynamic_lookup
$ ln -s ./target/debug/libmy_module.dylib ./my_module.so
$ lua5.3 -e 'require("my_module").hello("world")'
$ lua5.4 -e 'require("my_module").hello("world")'
hello, world!
```
+4 -2
View File
@@ -120,7 +120,8 @@ fn call_sum_callback(c: &mut Criterion) {
}
fn call_async_sum_callback(c: &mut Criterion) {
let lua = Lua::new();
let options = LuaOptions::new().thread_cache_size(1024);
let lua = Lua::new_with(LuaStdLib::ALL_SAFE, options).unwrap();
let callback = lua
.create_async_function(|_, (a, b, c): (i64, i64, i64)| async move {
task::yield_now().await;
@@ -244,7 +245,8 @@ fn call_async_userdata_method(c: &mut Criterion) {
}
}
let lua = Lua::new();
let options = LuaOptions::new().thread_cache_size(1024);
let lua = Lua::new_with(LuaStdLib::ALL_SAFE, options).unwrap();
lua.globals().set("userdata", UserData(10)).unwrap();
c.bench_function("call async [userdata method] 10", |b| {
+7 -1
View File
@@ -10,7 +10,13 @@ pub fn probe_lua() -> PathBuf {
#[cfg(feature = "lua51")]
let artifacts = lua_src::Build::new().build(lua_src::Lua51);
#[cfg(feature = "luajit")]
let artifacts = luajit_src::Build::new().build();
let artifacts = {
let mut builder = luajit_src::Build::new();
if cfg!(feature = "luajit52") {
builder.lua52compat(true);
}
builder.build()
};
#[cfg(not(feature = "module"))]
artifacts.print_cargo_metadata();
+23 -11
View File
@@ -143,16 +143,18 @@ fn generate_glue() -> Result<()> {
(version.0 * 100) + version.1
)?;
let max_stack = if pointer_bit_width >= 32 {
1_000_000
} else {
15_000
};
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
writeln!(
glue,
"pub const LUA_REGISTRYINDEX: c_int = -{} - 1000;",
max_stack
if pointer_bit_width >= 32 {
1_000_000
} else {
15_000
}
)?;
#[cfg(any(feature = "lua51", feature = "luajit"))]
writeln!(glue, "pub const LUA_REGISTRYINDEX: c_int = -10000;")?;
// These two are only defined in lua 5.1
writeln!(glue, "pub const LUA_ENVIRONINDEX: c_int = -10001;")?;
@@ -199,7 +201,9 @@ fn main() {
feature = "lua51",
feature = "luajit"
)))]
compile_error!("You must enable one of the features: lua54, lua53, lua52, lua51, luajit");
compile_error!(
"You must enable one of the features: lua54, lua53, lua52, lua51, luajit, luajit52"
);
#[cfg(all(
feature = "lua54",
@@ -210,19 +214,27 @@ fn main() {
feature = "luajit"
)
))]
compile_error!("You can enable only one of the features: lua54, lua53, lua52, lua51, luajit");
compile_error!(
"You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52"
);
#[cfg(all(
feature = "lua53",
any(feature = "lua52", feature = "lua51", feature = "luajit")
))]
compile_error!("You can enable only one of the features: lua54, lua53, lua52, lua51, luajit");
compile_error!(
"You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52"
);
#[cfg(all(feature = "lua52", any(feature = "lua51", feature = "luajit")))]
compile_error!("You can enable only one of the features: lua54, lua53, lua52, lua51, luajit");
compile_error!(
"You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52"
);
#[cfg(all(feature = "lua51", feature = "luajit"))]
compile_error!("You can enable only one of the features: lua54, lua53, lua52, lua51, luajit");
compile_error!(
"You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52"
);
// We don't support "vendored module" mode on windows
#[cfg(all(feature = "vendored", feature = "module", target_os = "windows"))]
+7 -16
View File
@@ -1,26 +1,17 @@
use std::collections::HashMap;
use std::sync::Arc;
use hyper::body::{Body as HyperBody, HttpBody as _};
use hyper::Client as HyperClient;
use tokio::sync::Mutex;
use mlua::{chunk, ExternalResult, Lua, Result, UserData, UserDataMethods};
use mlua::{chunk, AnyUserData, ExternalResult, Lua, Result, UserData, UserDataMethods};
#[derive(Clone)]
struct BodyReader(Arc<Mutex<HyperBody>>);
impl BodyReader {
fn new(body: HyperBody) -> Self {
BodyReader(Arc::new(Mutex::new(body)))
}
}
struct BodyReader(HyperBody);
impl UserData for BodyReader {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("read", |lua, reader, ()| async move {
let mut reader = reader.0.lock().await;
if let Some(bytes) = reader.data().await {
methods.add_async_function("read", |lua, reader: AnyUserData| async move {
let mut reader = reader.borrow_mut::<Self>()?;
if let Some(bytes) = reader.0.data().await {
let bytes = bytes.to_lua_err()?;
return Some(lua.create_string(&bytes)).transpose();
}
@@ -50,7 +41,7 @@ async fn main() -> Result<()> {
}
lua_resp.set("headers", headers)?;
lua_resp.set("body", BodyReader::new(resp.into_body()))?;
lua_resp.set("body", BodyReader(resp.into_body()))?;
Ok(lua_resp)
})?;
@@ -58,7 +49,7 @@ async fn main() -> Result<()> {
let f = lua
.load(chunk! {
local res = $fetch_url(...)
print(res.status)
print("status: "..res.status)
for key, vals in pairs(res.headers) do
for _, val in ipairs(vals) do
print(key..": "..val)
+90 -53
View File
@@ -1,10 +1,16 @@
use std::future::Future;
use std::net::SocketAddr;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use hyper::server::conn::AddrStream;
use hyper::service::{make_service_fn, service_fn};
use hyper::service::Service;
use hyper::{Body, Request, Response, Server};
use mlua::{Error, Function, Lua, Result, Table, UserData, UserDataMethods};
use mlua::{
chunk, Error as LuaError, Function, Lua, String as LuaString, Table, UserData, UserDataMethods,
};
struct LuaRequest(SocketAddr, Request<Body>);
@@ -15,75 +21,106 @@ impl UserData for LuaRequest {
}
}
async fn run_server(handler: Function<'static>) -> Result<()> {
let make_svc = make_service_fn(|socket: &AddrStream| {
let remote_addr = socket.remote_addr();
let handler = handler.clone();
async move {
Ok::<_, Error>(service_fn(move |req: Request<Body>| {
let handler = handler.clone();
async move {
let lua_req = LuaRequest(remote_addr, req);
let lua_resp: Table = handler.call_async(lua_req).await?;
let body = lua_resp
.get::<_, Option<String>>("body")?
.unwrap_or_default();
pub struct Svc(Rc<Lua>, SocketAddr);
let mut resp = Response::builder()
.status(lua_resp.get::<_, Option<u16>>("status")?.unwrap_or(200));
impl Service<Request<Body>> for Svc {
type Response = Response<Body>;
type Error = LuaError;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
// If handler returns an error then generate 5xx response
let lua = self.0.clone();
let lua_req = LuaRequest(self.1, req);
Box::pin(async move {
let handler: Function = lua.named_registry_value("http_handler")?;
match handler.call_async::<_, Table>(lua_req).await {
Ok(lua_resp) => {
let status = lua_resp.get::<_, Option<u16>>("status")?.unwrap_or(200);
let mut resp = Response::builder().status(status);
// Set headers
if let Some(headers) = lua_resp.get::<_, Option<Table>>("headers")? {
for pair in headers.pairs::<String, String>() {
for pair in headers.pairs::<String, LuaString>() {
let (h, v) = pair?;
resp = resp.header(&h, v);
resp = resp.header(&h, v.as_bytes());
}
}
Ok::<_, Error>(resp.body(Body::from(body)).unwrap())
let body = lua_resp
.get::<_, Option<LuaString>>("body")?
.map(|b| Body::from(b.as_bytes().to_vec()))
.unwrap_or_else(Body::empty);
Ok(resp.body(body).unwrap())
}
}))
}
});
Err(err) => {
eprintln!("{}", err);
Ok(Response::builder()
.status(500)
.body(Body::from("Internal Server Error"))
.unwrap())
}
}
})
}
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
let lua = Rc::new(Lua::new());
// Create Lua handler function
let handler: Function = lua
.load(chunk! {
function(req)
return {
status = 200,
headers = {
["X-Req-Method"] = req:method(),
["X-Remote-Addr"] = req:remote_addr(),
},
body = "Hello from Lua!\n"
}
end
})
.eval()
.expect("cannot create Lua handler");
// Store it in the Registry
lua.set_named_registry_value("http_handler", handler)
.expect("cannot store Lua handler");
let addr = ([127, 0, 0, 1], 3000).into();
let server = Server::bind(&addr).executor(LocalExec).serve(make_svc);
let server = Server::bind(&addr).executor(LocalExec).serve(MakeSvc(lua));
println!("Listening on http://{}", addr);
tokio::task::LocalSet::new()
.run_until(server)
.await
.map_err(Error::external)
// Create `LocalSet` to spawn !Send futures
let local = tokio::task::LocalSet::new();
local.run_until(server).await.expect("cannot run server")
}
#[tokio::main]
async fn main() -> Result<()> {
let lua = Lua::new().into_static();
struct MakeSvc(Rc<Lua>);
let handler: Function = lua
.load(
r#"
function(req)
return {
status = 200,
headers = {
["X-Req-Method"] = req:method(),
["X-Remote-Addr"] = req:remote_addr(),
},
body = "Hello, World!\n"
}
end
"#,
)
.eval()?;
impl Service<&AddrStream> for MakeSvc {
type Response = Svc;
type Error = hyper::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
run_server(handler).await?;
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
// Consume the static reference and drop it.
// This is safe as long as we don't hold any other references to Lua
// or alive resources.
unsafe { Lua::from_static(lua) };
Ok(())
fn call(&mut self, stream: &AddrStream) -> Self::Future {
let lua = self.0.clone();
let remote_addr = stream.remote_addr();
Box::pin(async move { Ok(Svc(lua, remote_addr)) })
}
}
#[derive(Clone, Copy, Debug)]
+88 -89
View File
@@ -1,122 +1,121 @@
use std::sync::Arc;
use std::io;
use std::net::SocketAddr;
use std::rc::Rc;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::Mutex;
use tokio::task;
use mlua::{chunk, Function, Lua, Result, String as LuaString, UserData, UserDataMethods};
use mlua::{
chunk, AnyUserData, Function, Lua, RegistryKey, String as LuaString, UserData, UserDataMethods,
};
struct LuaTcp;
#[derive(Clone)]
struct LuaTcpListener(Arc<Mutex<TcpListener>>);
#[derive(Clone)]
struct LuaTcpStream(Arc<Mutex<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(Arc::new(Mutex::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.lock().await.accept().await?;
Ok(LuaTcpStream(Arc::new(Mutex::new(stream))))
});
}
}
struct LuaTcpStream(TcpStream);
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.lock().await.peer_addr()?.to_string())
methods.add_method("peer_addr", |_, this, ()| {
Ok(this.0.peer_addr()?.to_string())
});
methods.add_async_method("read", |lua, stream, size: usize| async move {
let mut buf = vec![0; size];
let n = stream.0.lock().await.read(&mut buf).await?;
buf.truncate(n);
lua.create_string(&buf)
});
methods.add_async_function(
"read",
|lua, (this, size): (AnyUserData, usize)| async move {
let mut this = this.borrow_mut::<Self>()?;
let mut buf = vec![0; size];
let n = this.0.read(&mut buf).await?;
buf.truncate(n);
lua.create_string(&buf)
},
);
methods.add_async_method("write", |_, stream, data: LuaString| async move {
let n = stream.0.lock().await.write(&data.as_bytes()).await?;
Ok(n)
});
methods.add_async_function(
"write",
|_, (this, data): (AnyUserData, LuaString)| async move {
let mut this = this.borrow_mut::<Self>()?;
let n = this.0.write(&data.as_bytes()).await?;
Ok(n)
},
);
methods.add_async_method("close", |_, stream, ()| async move {
stream.0.lock().await.shutdown().await?;
methods.add_async_function("close", |_, this: AnyUserData| async move {
let mut this = this.borrow_mut::<Self>()?;
this.0.shutdown().await?;
Ok(())
});
}
}
async fn run_server(lua: &'static Lua) -> Result<()> {
let spawn = lua.create_function(move |_, func: Function| {
task::spawn_local(async move { func.call_async::<_, ()>(()).await });
Ok(())
})?;
async fn run_server(lua: Lua, handler: RegistryKey) -> io::Result<()> {
let addr: SocketAddr = ([127, 0, 0, 1], 3000).into();
let listener = TcpListener::bind(addr).await.expect("cannot bind addr");
let tcp = LuaTcp;
println!("Listening on {}", addr);
let server = lua
let lua = Rc::new(lua);
let handler = Rc::new(handler);
loop {
let (stream, _) = match listener.accept().await {
Ok(res) => res,
Err(err) if is_transient_error(&err) => continue,
Err(err) => return Err(err),
};
let lua = lua.clone();
let handler = handler.clone();
task::spawn_local(async move {
let handler: Function = lua
.registry_value(&handler)
.expect("cannot get Lua handler");
let stream = LuaTcpStream(stream);
if let Err(err) = handler.call_async::<_, ()>(stream).await {
eprintln!("{}", err);
}
});
}
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
let lua = Lua::new();
// Create Lua handler function
let handler_fn = lua
.load(chunk! {
local addr = ...
local listener = $tcp.bind(addr)
print("listening on "..addr)
local accept_new = true
while true do
local stream = listener:accept()
function(stream)
local peer_addr = stream:peer_addr()
print("connected from "..peer_addr)
if not accept_new then
return
end
$spawn(function()
while true do
local data = stream:read(100)
data = data:match("^%s*(.-)%s*$") -- trim
print("["..peer_addr.."] "..data)
if data == "bye" then
stream:write("bye bye\n")
stream:close()
return
end
if data == "exit" then
stream:close()
accept_new = false
return
end
stream:write("echo: "..data.."\n")
while true do
local data = stream:read(100)
data = data:match("^%s*(.-)%s*$") // trim
print("["..peer_addr.."] "..data)
if data == "bye" then
stream:write("bye bye\n")
stream:close()
return
end
end)
stream:write("echo: "..data.."\n")
end
end
})
.into_function()?;
.eval::<Function>()
.expect("cannot create Lua handler");
// Store it in the Registry
let handler = lua
.create_registry_value(handler_fn)
.expect("cannot store Lua handler");
task::LocalSet::new()
.run_until(server.call_async::<_, ()>("0.0.0.0:1234"))
.run_until(run_server(lua, handler))
.await
.expect("cannot run server")
}
#[tokio::main]
async fn main() {
let lua = Lua::new().into_static();
run_server(lua).await.unwrap();
// Consume the static reference and drop it.
// This is safe as long as we don't hold any other references to Lua
// or alive resources.
unsafe { Lua::from_static(lua) };
fn is_transient_error(e: &io::Error) -> bool {
e.kind() == io::ErrorKind::ConnectionRefused
|| e.kind() == io::ErrorKind::ConnectionAborted
|| e.kind() == io::ErrorKind::ConnectionReset
}
+6
View File
@@ -3,3 +3,9 @@ rustflags = [
"-C", "link-arg=-undefined",
"-C", "link-arg=dynamic_lookup",
]
[target.aarch64-apple-darwin]
rustflags = [
"-C", "link-arg=-undefined",
"-C", "link-arg=dynamic_lookup",
]
+40 -41
View File
@@ -2,6 +2,7 @@
use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::convert::TryInto;
use std::ffi::{CStr, CString};
use std::hash::{BuildHasher, Hash};
use std::string::String as StdString;
@@ -348,25 +349,24 @@ macro_rules! lua_convert_int {
($x:ty) => {
impl<'lua> ToLua<'lua> for $x {
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
if let Some(i) = cast(self) {
Ok(Value::Integer(i))
} else {
// TODO: Remove conversion to Number in v0.7
cast(self)
.ok_or_else(|| Error::ToLuaConversionError {
from: stringify!($x),
to: "number",
message: Some("out of range".to_owned()),
})
.map(Value::Number)
}
cast(self)
.map(Value::Integer)
.or_else(|| cast(self).map(Value::Number))
// This is impossible error because conversion to Number never fails
.ok_or_else(|| Error::ToLuaConversionError {
from: stringify!($x),
to: "number",
message: Some("out of range".to_owned()),
})
}
}
impl<'lua> FromLua<'lua> for $x {
fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<Self> {
let ty = value.type_name();
(if let Some(i) = lua.coerce_integer(value.clone())? {
(if let Value::Integer(i) = value {
cast(i)
} else if let Some(i) = lua.coerce_integer(value.clone())? {
cast(i)
} else {
cast(lua.coerce_number(value)?.ok_or_else(|| {
@@ -451,37 +451,36 @@ where
}
}
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())?,
))
}
}
)+
impl<'lua, T, const N: usize> ToLua<'lua> for [T; N]
where
T: ToLua<'lua>,
{
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_sequence_from(self)?))
}
}
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, const N: usize> FromLua<'lua> for [T; N]
where
T: FromLua<'lua>,
{
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
if let Value::Table(table) = value {
let vec = table.sequence_values().collect::<Result<Vec<_>>>()?;
vec.try_into()
.map_err(|vec: Vec<T>| Error::FromLuaConversionError {
from: "Table",
to: "Array",
message: Some(format!("expected table of length {}, got {}", N, vec.len())),
})
} else {
Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "Array",
message: Some("expected table".to_string()),
})
}
}
}
impl<'lua, T: ToLua<'lua>> ToLua<'lua> for Box<[T]> {
+41 -15
View File
@@ -96,8 +96,8 @@ pub enum Error {
/// [`Thread::status`] can be used to check if the coroutine can be resumed without causing this
/// error.
///
/// [`Thread::resume`]: struct.Thread.html#method.resume
/// [`Thread::status`]: struct.Thread.html#method.status
/// [`Thread::resume`]: crate::Thread::resume
/// [`Thread::status`]: crate::Thread::status
CoroutineInactive,
/// An [`AnyUserData`] is not the expected type in a borrow.
///
@@ -105,15 +105,15 @@ pub enum Error {
/// metamethods for binary operators. Refer to the documentation of [`UserDataMethods`] for
/// details.
///
/// [`AnyUserData`]: struct.AnyUserData.html
/// [`UserDataMethods`]: trait.UserDataMethods.html
/// [`AnyUserData`]: crate::AnyUserData
/// [`UserDataMethods`]: crate::UserDataMethods
UserDataTypeMismatch,
/// An [`AnyUserData`] borrow failed because it has been destructed.
///
/// This error can happen either due to to being destructed in a previous __gc, or due to being
/// destructed from exiting a `Lua::scope` call.
///
/// [`AnyUserData`]: struct.AnyUserData.html
/// [`AnyUserData`]: crate::AnyUserData
UserDataDestructed,
/// An [`AnyUserData`] immutable borrow failed because it is already borrowed mutably.
///
@@ -121,8 +121,8 @@ pub enum Error {
/// tries to call a method on the same [`UserData`] type. Consider restructuring your API to
/// prevent these errors.
///
/// [`AnyUserData`]: struct.AnyUserData.html
/// [`UserData`]: trait.UserData.html
/// [`AnyUserData`]: crate::AnyUserData
/// [`UserData`]: crate::UserData
UserDataBorrowError,
/// An [`AnyUserData`] mutable borrow failed because it is already borrowed.
///
@@ -130,22 +130,24 @@ pub enum Error {
/// tries to call a method on the same [`UserData`] type. Consider restructuring your API to
/// prevent these errors.
///
/// [`AnyUserData`]: struct.AnyUserData.html
/// [`UserData`]: trait.UserData.html
/// [`AnyUserData`]: crate::AnyUserData
/// [`UserData`]: crate::UserData
UserDataBorrowMutError,
/// A [`MetaMethod`] operation is restricted (typically for `__gc` or `__metatable`).
///
/// [`MetaMethod`]: enum.MetaMethod.html
/// [`MetaMethod`]: crate::MetaMethod
MetaMethodRestricted(StdString),
/// A [`MetaMethod`] (eg. `__index` or `__newindex`) has invalid type.
///
/// [`MetaMethod`]: enum.MetaMethod.html
/// [`MetaMethod`]: crate::MetaMethod
MetaMethodTypeError {
method: StdString,
type_name: &'static str,
message: Option<StdString>,
},
/// A `RegistryKey` produced from a different Lua state was used.
/// A [`RegistryKey`] produced from a different Lua state was used.
///
/// [`RegistryKey`]: crate::RegistryKey
MismatchedRegistryKey,
/// A Rust callback returned `Err`, raising the contained `Error` as a Lua error.
CallbackError {
@@ -245,8 +247,28 @@ impl fmt::Display for Error {
Error::MismatchedRegistryKey => {
write!(fmt, "RegistryKey used from different Lua state")
}
Error::CallbackError { ref traceback, .. } => {
write!(fmt, "callback error\n{}", traceback)
Error::CallbackError { ref cause, ref traceback } => {
writeln!(fmt, "callback error")?;
// Trace errors down to the root
let (mut cause, mut full_traceback) = (cause, None);
while let Error::CallbackError { cause: ref cause2, traceback: ref traceback2 } = **cause {
cause = cause2;
full_traceback = Some(traceback2);
}
if let Some(full_traceback) = full_traceback {
let traceback = traceback.trim_start_matches("stack traceback:");
let traceback = traceback.trim_start().trim_end();
// Try to find local traceback within the full traceback
if let Some(pos) = full_traceback.find(traceback) {
write!(fmt, "{}", &full_traceback[..pos])?;
writeln!(fmt, ">{}", &full_traceback[pos..].trim_end())?;
} else {
writeln!(fmt, "{}", full_traceback.trim_end())?;
}
} else {
writeln!(fmt, "{}", traceback.trim_end())?;
}
write!(fmt, "caused by: {}", cause)
}
Error::PreviouslyResumedPanic => {
write!(fmt, "previously resumed panic returned again")
@@ -267,7 +289,11 @@ impl fmt::Display for Error {
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match *self {
Error::CallbackError { ref cause, .. } => Some(cause.as_ref()),
// An error type with a source error should either return that error via source or
// include that source's error message in its own Display output, but never both.
// https://blog.rust-lang.org/inside-rust/2021/07/01/What-the-error-handling-project-group-is-working-towards.html
// Given that we include source to fmt::Display implementation for `CallbackError`, this call returns nothing.
Error::CallbackError { .. } => None,
Error::ExternalError(ref err) => err.source(),
_ => None,
}
+9 -4
View File
@@ -23,6 +23,7 @@
//! Contains definitions from `lua.h`.
use std::marker::{PhantomData, PhantomPinned};
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
use std::os::raw::c_uchar;
use std::os::raw::{c_char, c_int, c_void};
@@ -63,7 +64,7 @@ pub use super::compat53::lua_getglobal;
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[inline(always)]
pub fn lua_upvalueindex(i: c_int) -> c_int {
pub const fn lua_upvalueindex(i: c_int) -> c_int {
LUA_REGISTRYINDEX - i
}
@@ -81,7 +82,11 @@ pub const LUA_ERRERR: c_int = 5;
pub const LUA_ERRERR: c_int = 6;
/// A raw Lua state associated with a thread.
pub type lua_State = c_void;
#[repr(C)]
pub struct lua_State {
_data: [u8; 0],
_marker: PhantomData<(*mut u8, PhantomPinned)>,
}
// basic types
pub const LUA_TNONE: c_int = -1;
@@ -569,7 +574,7 @@ pub unsafe fn lua_resume(
// warning-related functions
#[cfg(feature = "lua54")]
extern "C" {
pub fn lua_setwarnf(L: *mut lua_State, f: lua_WarnFunction, ud: *mut c_void);
pub fn lua_setwarnf(L: *mut lua_State, f: Option<lua_WarnFunction>, ud: *mut c_void);
pub fn lua_warning(L: *mut lua_State, msg: *const c_char, tocont: c_int);
}
@@ -621,7 +626,7 @@ extern "C" {
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[inline(always)]
pub unsafe fn lua_getextraspace(L: *mut lua_State) -> *mut c_void {
L.offset(-super::glue::LUA_EXTRASPACE as isize) as *mut c_void
(L as *mut c_char).offset(-super::glue::LUA_EXTRASPACE as isize) as *mut c_void
}
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
+11 -7
View File
@@ -6,7 +6,7 @@ use crate::error::{Error, Result};
use crate::ffi;
use crate::types::LuaRef;
use crate::util::{assert_stack, check_stack, error_traceback, pop_error, StackGuard};
use crate::value::{FromLuaMulti, MultiValue, ToLuaMulti};
use crate::value::{FromLuaMulti, ToLuaMulti};
#[cfg(feature = "async")]
use {futures_core::future::LocalBoxFuture, futures_util::future};
@@ -59,7 +59,7 @@ impl<'lua> Function<'lua> {
pub fn call<A: ToLuaMulti<'lua>, R: FromLuaMulti<'lua>>(&self, args: A) -> Result<R> {
let lua = self.0.lua;
let args = args.to_lua_multi(lua)?;
let mut args = args.to_lua_multi(lua)?;
let nargs = args.len() as c_int;
let results = unsafe {
@@ -69,7 +69,7 @@ impl<'lua> Function<'lua> {
ffi::lua_pushcfunction(lua.state, error_traceback);
let stack_start = ffi::lua_gettop(lua.state);
lua.push_ref(&self.0);
for arg in args {
for arg in args.drain_all() {
lua.push_value(arg)?;
}
let ret = ffi::lua_pcall(lua.state, nargs, ffi::LUA_MULTRET, stack_start);
@@ -77,7 +77,7 @@ impl<'lua> Function<'lua> {
return Err(pop_error(lua.state, ret));
}
let nresults = ffi::lua_gettop(lua.state) - stack_start;
let mut results = MultiValue::new();
let mut results = args; // Reuse MultiValue container
assert_stack(lua.state, 2);
for _ in 0..nresults {
results.push_front(lua.pop_value());
@@ -116,7 +116,7 @@ impl<'lua> Function<'lua> {
/// # }
/// ```
///
/// [`AsyncThread`]: struct.AsyncThread.html
/// [`AsyncThread`]: crate::AsyncThread
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn call_async<'fut, A, R>(&self, args: A) -> LocalBoxFuture<'fut, Result<R>>
@@ -126,8 +126,12 @@ impl<'lua> Function<'lua> {
R: FromLuaMulti<'lua> + 'fut,
{
let lua = self.0.lua;
match lua.create_thread(self.clone()) {
Ok(t) => Box::pin(t.into_async(args)),
match lua.create_recycled_thread(self.clone()) {
Ok(t) => {
let mut t = t.into_async(args);
t.set_recyclable(true);
Box::pin(t)
}
Err(e) => Box::pin(future::err(e)),
}
}
+62 -56
View File
@@ -1,29 +1,40 @@
use std::cell::UnsafeCell;
use std::ffi::CStr;
use std::marker::PhantomData;
use std::ops::{BitOr, BitOrAssign};
use std::os::raw::{c_char, c_int};
use crate::ffi::{self, lua_Debug, lua_State};
use crate::ffi::{self, lua_Debug};
use crate::lua::Lua;
use crate::util::callback_error;
/// Contains information about currently executing Lua code.
///
/// The `Debug` structure is provided as a parameter to the hook function set with
/// [`Lua::set_hook`]. You may call the methods on this structure to retrieve information about the
/// Lua code executing at the time that the hook function was called. Further information can be
/// found in the [Lua 5.3 documentation][lua_doc].
/// found in the Lua [documentation][lua_doc].
///
/// [lua_doc]: https://www.lua.org/manual/5.3/manual.html#lua_Debug
/// [`Lua::set_hook`]: struct.Lua.html#method.set_hook
#[derive(Clone)]
pub struct Debug<'a> {
ar: *mut lua_Debug,
state: *mut lua_State,
_phantom: PhantomData<&'a ()>,
/// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#lua_Debug
/// [`Lua::set_hook`]: crate::Lua::set_hook
pub struct Debug<'lua> {
lua: &'lua Lua,
ar: ActivationRecord,
}
impl<'a> Debug<'a> {
impl<'lua> Debug<'lua> {
pub(crate) fn new(lua: &'lua Lua, ar: *mut lua_Debug) -> Self {
Debug {
lua,
ar: ActivationRecord::Borrowed(ar),
}
}
pub(crate) fn new_owned(lua: &'lua Lua, ar: lua_Debug) -> Self {
Debug {
lua,
ar: ActivationRecord::Owned(UnsafeCell::new(ar)),
}
}
/// Returns the specific event that triggered the hook.
///
/// For [Lua 5.1] `DebugEvent::TailCall` is used for return events to indicate a return
@@ -32,44 +43,44 @@ impl<'a> Debug<'a> {
/// [Lua 5.1]: https://www.lua.org/manual/5.1/manual.html#pdf-LUA_HOOKTAILRET
pub fn event(&self) -> DebugEvent {
unsafe {
match (*self.ar).event {
match (*self.ar.get()).event {
ffi::LUA_HOOKCALL => DebugEvent::Call,
ffi::LUA_HOOKRET => DebugEvent::Ret,
ffi::LUA_HOOKTAILCALL => DebugEvent::TailCall,
ffi::LUA_HOOKLINE => DebugEvent::Line,
ffi::LUA_HOOKCOUNT => DebugEvent::Count,
event => mlua_panic!("Unknown Lua event code: {}", event),
event => DebugEvent::Unknown(event),
}
}
}
/// Corresponds to the `n` what mask.
pub fn names(&self) -> DebugNames<'a> {
pub fn names(&self) -> DebugNames<'lua> {
unsafe {
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("n"), self.ar) != 0,
ffi::lua_getinfo(self.lua.state, cstr!("n"), self.ar.get()) != 0,
"lua_getinfo failed with `n`"
);
DebugNames {
name: ptr_to_str((*self.ar).name),
name_what: ptr_to_str((*self.ar).namewhat),
name: ptr_to_str((*self.ar.get()).name),
name_what: ptr_to_str((*self.ar.get()).namewhat),
}
}
}
/// Corresponds to the `S` what mask.
pub fn source(&self) -> DebugSource<'a> {
pub fn source(&self) -> DebugSource<'lua> {
unsafe {
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("S"), self.ar) != 0,
ffi::lua_getinfo(self.lua.state, cstr!("S"), self.ar.get()) != 0,
"lua_getinfo failed with `S`"
);
DebugSource {
source: ptr_to_str((*self.ar).source),
short_src: ptr_to_str((*self.ar).short_src.as_ptr()),
line_defined: (*self.ar).linedefined as i32,
last_line_defined: (*self.ar).lastlinedefined as i32,
what: ptr_to_str((*self.ar).what),
source: ptr_to_str((*self.ar.get()).source),
short_src: ptr_to_str((*self.ar.get()).short_src.as_ptr()),
line_defined: (*self.ar.get()).linedefined as i32,
last_line_defined: (*self.ar.get()).lastlinedefined as i32,
what: ptr_to_str((*self.ar.get()).what),
}
}
}
@@ -78,10 +89,10 @@ impl<'a> Debug<'a> {
pub fn curr_line(&self) -> i32 {
unsafe {
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("l"), self.ar) != 0,
ffi::lua_getinfo(self.lua.state, cstr!("l"), self.ar.get()) != 0,
"lua_getinfo failed with `l`"
);
(*self.ar).currentline as i32
(*self.ar.get()).currentline as i32
}
}
@@ -90,10 +101,10 @@ impl<'a> Debug<'a> {
pub fn is_tail_call(&self) -> bool {
unsafe {
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("t"), self.ar) != 0,
ffi::lua_getinfo(self.lua.state, cstr!("t"), self.ar.get()) != 0,
"lua_getinfo failed with `t`"
);
(*self.ar).currentline != 0
(*self.ar.get()).currentline != 0
}
}
@@ -101,20 +112,35 @@ impl<'a> Debug<'a> {
pub fn stack(&self) -> DebugStack {
unsafe {
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("u"), self.ar) != 0,
ffi::lua_getinfo(self.lua.state, cstr!("u"), self.ar.get()) != 0,
"lua_getinfo failed with `u`"
);
DebugStack {
num_ups: (*self.ar).nups as i32,
num_ups: (*self.ar.get()).nups as i32,
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
num_params: (*self.ar).nparams as i32,
num_params: (*self.ar.get()).nparams as i32,
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
is_vararg: (*self.ar).isvararg != 0,
is_vararg: (*self.ar.get()).isvararg != 0,
}
}
}
}
enum ActivationRecord {
Borrowed(*mut lua_Debug),
Owned(UnsafeCell<lua_Debug>),
}
impl ActivationRecord {
#[inline]
fn get(&self) -> *mut lua_Debug {
match self {
ActivationRecord::Borrowed(x) => *x,
ActivationRecord::Owned(x) => x.get(),
}
}
}
/// Represents a specific event that triggered the hook.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DebugEvent {
@@ -123,6 +149,7 @@ pub enum DebugEvent {
TailCall,
Line,
Count,
Unknown(c_int),
}
#[derive(Clone, Debug)]
@@ -144,10 +171,10 @@ pub struct DebugSource<'a> {
pub struct DebugStack {
pub num_ups: i32,
/// Requires `feature = "lua54/lua53/lua52"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", doc))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub num_params: i32,
/// Requires `feature = "lua54/lua53/lua52"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", doc))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub is_vararg: bool,
}
@@ -255,27 +282,6 @@ impl BitOrAssign for HookTriggers {
}
}
pub(crate) unsafe extern "C" fn hook_proc(state: *mut lua_State, ar: *mut lua_Debug) {
callback_error(state, |_| {
let debug = Debug {
ar,
state,
_phantom: PhantomData,
};
let lua = mlua_expect!(Lua::make_from_ptr(state), "cannot make Lua instance");
let hook_cb = mlua_expect!(lua.hook_callback(), "no hook callback set in hook_proc");
#[allow(clippy::match_wild_err_arm)]
match hook_cb.try_borrow_mut() {
Ok(mut b) => (&mut *b)(&lua, debug),
Err(_) => mlua_panic!("Lua should not allow hooks to be called within another hook"),
}?;
Ok(())
})
}
unsafe fn ptr_to_str<'a>(input: *const c_char) -> Option<&'a [u8]> {
if input.is_null() {
None
+42 -25
View File
@@ -50,29 +50,29 @@
//! to [`Function`]s and [`UserData`].
//!
//! [Lua programming language]: https://www.lua.org/
//! [`Lua`]: struct.Lua.html
//! [executing]: struct.Chunk.html#method.exec
//! [evaluating]: struct.Chunk.html#method.eval
//! [globals]: struct.Lua.html#method.globals
//! [`ToLua`]: trait.ToLua.html
//! [`FromLua`]: trait.FromLua.html
//! [`ToLuaMulti`]: trait.ToLuaMulti.html
//! [`FromLuaMulti`]: trait.FromLuaMulti.html
//! [`Function`]: struct.Function.html
//! [`UserData`]: trait.UserData.html
//! [`UserDataFields`]: trait.UserDataFields.html
//! [`UserDataMethods`]: trait.UserDataMethods.html
//! [`LuaSerdeExt`]: serde/trait.LuaSerdeExt.html
//! [`Value`]: enum.Value.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
//! [`Lua`]: crate::Lua
//! [executing]: crate::Chunk::exec
//! [evaluating]: crate::Chunk::eval
//! [globals]: crate::Lua::globals
//! [`ToLua`]: crate::ToLua
//! [`FromLua`]: crate::FromLua
//! [`ToLuaMulti`]: crate::ToLuaMulti
//! [`FromLuaMulti`]: crate::FromLuaMulti
//! [`Function`]: crate::Function
//! [`UserData`]: crate::UserData
//! [`UserDataFields`]: crate::UserDataFields
//! [`UserDataMethods`]: crate::UserDataMethods
//! [`LuaSerdeExt`]: crate::LuaSerdeExt
//! [`Value`]: crate::Value
//! [`create_async_function`]: crate::Lua::create_async_function
//! [`call_async`]: crate::Function::call_async
//! [`AsyncThread`]: crate::AsyncThread
//! [`Future`]: std::future::Future
//! [`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html
//! [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
// mlua types in rustdoc of other crates get linked to here.
#![doc(html_root_url = "https://docs.rs/mlua/0.6.5")]
#![doc(html_root_url = "https://docs.rs/mlua/0.7.0")]
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
// warnings at all.
#![doc(test(attr(deny(warnings))))]
@@ -98,6 +98,8 @@ mod userdata;
mod util;
mod value;
pub mod prelude;
pub use crate::{ffi::lua_CFunction, ffi::lua_State};
pub use crate::error::{Error, ExternalError, ExternalResult, Result};
@@ -120,13 +122,11 @@ pub use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti
pub use crate::thread::AsyncThread;
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
#[doc(inline)]
pub use crate::serde::{
de::Options as DeserializeOptions, ser::Options as SerializeOptions, LuaSerdeExt,
};
pub mod prelude;
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub mod serde;
@@ -185,13 +185,30 @@ extern crate mlua_derive;
///
/// Everything else should work.
///
/// [`AsChunk`]: trait.AsChunk.html
/// [`UserData`]: trait.UserData.html
/// [`ToLua`]: trait.ToLua.html
/// [`AsChunk`]: crate::AsChunk
/// [`UserData`]: crate::UserData
/// [`ToLua`]: crate::ToLua
#[cfg(any(feature = "macros"))]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub use mlua_derive::chunk;
#[cfg(any(feature = "module"))]
/// Registers Lua module entrypoint.
///
/// You can register multiple entrypoints as required.
///
/// ```
/// use mlua::{Lua, Result, Table};
///
/// #[mlua::lua_module]
/// fn my_module(lua: &Lua) -> Result<Table> {
/// let exports = lua.create_table()?;
/// exports.set("hello", "world")?;
/// Ok(exports)
/// }
/// ```
///
/// Internally in the code above the compiler defines C function `luaopen_my_module`.
///
#[cfg(any(feature = "module", docsrs))]
#[cfg_attr(docsrs, doc(cfg(feature = "module")))]
pub use mlua_derive::lua_module;
+482 -105
View File
File diff suppressed because it is too large Load Diff
+20 -15
View File
@@ -12,8 +12,7 @@ use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti};
/// on success, or in the case of an error, returning `nil` and an error message.
impl<'lua, T: ToLua<'lua>, E: ToLua<'lua>> ToLuaMulti<'lua> for StdResult<T, E> {
fn to_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
let mut result = MultiValue::new();
let mut result = MultiValue::new_or_cached(lua);
match self {
Ok(v) => result.push_front(v.to_lua(lua)?),
Err(e) => {
@@ -21,14 +20,13 @@ impl<'lua, T: ToLua<'lua>, E: ToLua<'lua>> ToLuaMulti<'lua> for StdResult<T, E>
result.push_front(Nil);
}
}
Ok(result)
}
}
impl<'lua, T: ToLua<'lua>> ToLuaMulti<'lua> for T {
fn to_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
let mut v = MultiValue::new();
let mut v = MultiValue::new_or_cached(lua);
v.push_front(self.to_lua(lua)?);
Ok(v)
}
@@ -36,7 +34,9 @@ impl<'lua, T: ToLua<'lua>> ToLuaMulti<'lua> for T {
impl<'lua, T: FromLua<'lua>> FromLuaMulti<'lua> for T {
fn from_lua_multi(mut values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self> {
T::from_lua(values.pop_front().unwrap_or(Nil), lua)
let res = T::from_lua(values.pop_front().unwrap_or(Nil), lua);
lua.cache_multivalue(values);
res
}
}
@@ -76,8 +76,8 @@ impl<'lua> FromLuaMulti<'lua> for MultiValue<'lua> {
/// # }
/// ```
///
/// [`FromLua`]: trait.FromLua.html
/// [`MultiValue`]: struct.MultiValue.html
/// [`FromLua`]: crate::FromLua
/// [`MultiValue`]: crate::MultiValue
#[derive(Debug, Clone)]
pub struct Variadic<T>(Vec<T>);
@@ -125,30 +125,35 @@ impl<T> DerefMut for Variadic<T> {
impl<'lua, T: ToLua<'lua>> ToLuaMulti<'lua> for Variadic<T> {
fn to_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
self.0.into_iter().map(|e| e.to_lua(lua)).collect()
let mut values = MultiValue::new_or_cached(lua);
values.refill(self.0.into_iter().map(|e| e.to_lua(lua)))?;
Ok(values)
}
}
impl<'lua, T: FromLua<'lua>> FromLuaMulti<'lua> for Variadic<T> {
fn from_lua_multi(values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self> {
values
.into_iter()
fn from_lua_multi(mut values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self> {
let res = values
.drain_all()
.map(|e| T::from_lua(e, lua))
.collect::<Result<Vec<T>>>()
.map(Variadic)
.map(Variadic);
lua.cache_multivalue(values);
res
}
}
macro_rules! impl_tuple {
() => (
impl<'lua> ToLuaMulti<'lua> for () {
fn to_lua_multi(self, _: &'lua Lua) -> Result<MultiValue<'lua>> {
Ok(MultiValue::new())
fn to_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
Ok(MultiValue::new_or_cached(lua))
}
}
impl<'lua> FromLuaMulti<'lua> for () {
fn from_lua_multi(_: MultiValue<'lua>, _: &'lua Lua) -> Result<Self> {
fn from_lua_multi(values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self> {
lua.cache_multivalue(values);
Ok(())
}
}
+9 -6
View File
@@ -1,23 +1,26 @@
//! Re-exports most types with an extra `Lua*` prefix to prevent name clashes.
#[doc(no_inline)]
pub use crate::{
AnyUserData as LuaAnyUserData, Chunk as LuaChunk, Error as LuaError,
ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti,
Function as LuaFunction, GCMode as LuaGCMode, Integer as LuaInteger,
LightUserData as LuaLightUserData, Lua, LuaOptions, MetaMethod as LuaMetaMethod,
MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, 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,
UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable,
UserDataMethods as LuaUserDataMethods, Value as LuaValue,
Result as LuaResult, StdLib as LuaStdLib, 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, UserDataFields as LuaUserDataFields,
UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods,
Value as LuaValue,
};
#[cfg(feature = "async")]
#[doc(no_inline)]
pub use crate::AsyncThread as LuaAsyncThread;
#[cfg(feature = "serialize")]
#[doc(inline)]
#[doc(no_inline)]
pub use crate::{
DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt,
SerializeOptions as LuaSerializeOptions,
+89 -26
View File
@@ -23,6 +23,9 @@ use crate::util::{
};
use crate::value::{FromLua, FromLuaMulti, MultiValue, ToLua, ToLuaMulti, Value};
#[cfg(feature = "lua54")]
use crate::userdata::USER_VALUE_MAXSLOT;
#[cfg(feature = "async")]
use {
crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue},
@@ -35,7 +38,7 @@ use {
///
/// See [`Lua::scope`] for more details.
///
/// [`Lua::scope`]: struct.Lua.html#method.scope
/// [`Lua::scope`]: crate::Lua.html::scope
pub struct Scope<'lua, 'scope> {
lua: &'lua Lua,
destructors: RefCell<Vec<(LuaRef<'lua>, DestructorCallback<'lua>)>>,
@@ -58,8 +61,8 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
/// 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
/// [`Lua::create_function`]: crate::Lua::create_function
/// [`Lua::scope`]: crate::Lua::scope
pub fn create_function<'callback, A, R, F>(&'callback self, func: F) -> Result<Function<'lua>>
where
A: FromLuaMulti<'callback>,
@@ -87,8 +90,8 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
/// 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
/// [`Lua::create_function_mut`]: crate::Lua::create_function_mut
/// [`Lua::scope`]: crate::Lua::scope
/// [`Scope::create_function`]: #method.create_function
pub fn create_function_mut<'callback, A, R, F>(
&'callback self,
@@ -114,9 +117,9 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
///
/// Requires `feature = "async"`
///
/// [`Lua::create_async_function`]: struct.Lua.html#method.create_async_function
/// [`Lua::scope`]: struct.Lua.html#method.scope
/// [`Lua::async_scope`]: struct.Lua.html#method.async_scope
/// [`Lua::create_async_function`]: crate::Lua::create_async_function
/// [`Lua::scope`]: crate::Lua::scope
/// [`Lua::async_scope`]: crate::Lua::async_scope
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn create_async_function<'callback, A, R, F, FR>(
@@ -147,8 +150,8 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
/// 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
/// [`Lua::create_userdata`]: crate::Lua::create_userdata
/// [`Lua::scope`]: crate::Lua::scope
pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData<'lua>>
where
T: 'static + UserData,
@@ -165,8 +168,8 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
///
/// Requires `feature = "serialize"`
///
/// [`Lua::create_ser_userdata`]: struct.Lua.html#method.create_ser_userdata
/// [`Lua::scope`]: struct.Lua.html#method.scope
/// [`Lua::create_ser_userdata`]: crate::Lua::create_ser_userdata
/// [`Lua::scope`]: crate::Lua::scope
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub fn create_ser_userdata<T>(&self, data: T) -> Result<AnyUserData<'lua>>
@@ -197,12 +200,22 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
return vec![];
}
// Clear uservalue
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
ffi::lua_pushnil(state);
// Clear associated user values
#[cfg(feature = "lua54")]
for i in 1..=USER_VALUE_MAXSLOT {
ffi::lua_pushnil(state);
ffi::lua_setiuservalue(state, -2, i as c_int);
}
#[cfg(any(feature = "lua53", feature = "lua52"))]
{
ffi::lua_pushnil(state);
ffi::lua_setuservalue(state, -2);
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
ud.lua.push_ref(&newtable.0);
ffi::lua_setuservalue(state, -2);
{
ud.lua.push_ref(&newtable.0);
ffi::lua_setuservalue(state, -2);
}
vec![Box::new(take_userdata::<UserDataCell<T>>(state))]
});
@@ -234,9 +247,9 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
/// creating the userdata metatable each time a new userdata is created.
///
/// [`Scope::create_userdata`]: #method.create_userdata
/// [`Lua::create_userdata`]: struct.Lua.html#method.create_userdata
/// [`Lua::scope`]: struct.Lua.html#method.scope
/// [`UserDataMethods`]: trait.UserDataMethods.html
/// [`Lua::create_userdata`]: crate::Lua::create_userdata
/// [`Lua::scope`]:crate::Lua::scope
/// [`UserDataMethods`]: crate::UserDataMethods
pub fn create_nonstatic_userdata<T>(&self, data: T) -> Result<AnyUserData<'lua>>
where
T: 'scope + UserData,
@@ -323,8 +336,19 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 13)?;
#[allow(clippy::let_and_return)]
let data_ptr = protect_lua!(lua.state, 0, 1, |state| {
ffi::lua_newuserdata(state, mem::size_of::<UserDataCell<Rc<RefCell<T>>>>())
let ud =
ffi::lua_newuserdata(state, mem::size_of::<UserDataCell<Rc<RefCell<T>>>>());
// Set empty environment for Lua 5.1
#[cfg(any(feature = "lua51", feature = "luajit"))]
{
ffi::lua_newtable(state);
ffi::lua_setuservalue(state, -2);
}
ud
})?;
// Prepare metatable, add meta methods first and then meta fields
let meta_methods_nrec = ud_methods.meta_methods.len() + ud_fields.meta_fields.len() + 1;
@@ -416,12 +440,22 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
ffi::lua_pop(state, 1);
ud.lua.deregister_userdata_metatable(mt_ptr);
// Clear uservalue
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
ffi::lua_pushnil(state);
// Clear associated user values
#[cfg(feature = "lua54")]
for i in 1..=USER_VALUE_MAXSLOT {
ffi::lua_pushnil(state);
ffi::lua_setiuservalue(state, -2, i as c_int);
}
#[cfg(any(feature = "lua53", feature = "lua52"))]
{
ffi::lua_pushnil(state);
ffi::lua_setuservalue(state, -2);
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
ud.lua.push_ref(&newtable.0);
ffi::lua_setuservalue(state, -2);
{
ud.lua.push_ref(&newtable.0);
ffi::lua_setuservalue(state, -2);
}
// A hack to drop non-static `T`
unsafe fn seal<T>(t: T) -> Box<dyn FnOnce() + 'static> {
@@ -693,6 +727,21 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'l
));
}
#[cfg(all(feature = "async", not(feature = "lua51")))]
fn add_async_meta_method<S, A, R, M, MR>(&mut self, _meta: S, _method: M)
where
T: Clone,
S: Into<MetaMethod>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + MaybeSend + Fn(&'lua Lua, T, A) -> MR,
MR: 'lua + Future<Output = Result<R>>,
{
// The panic should never happen as async non-static code wouldn't compile
// Non-static lifetime must be bounded to 'lua lifetime
mlua_panic!("asynchronous meta methods are not supported for non-static userdata")
}
fn add_meta_function<S, A, R, F>(&mut self, meta: S, function: F)
where
S: Into<MetaMethod>,
@@ -722,6 +771,20 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'l
})),
));
}
#[cfg(all(feature = "async", not(feature = "lua51")))]
fn add_async_meta_function<S, A, R, F, FR>(&mut self, _meta: S, _function: F)
where
S: Into<MetaMethod>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + MaybeSend + Fn(&'lua Lua, A) -> FR,
FR: 'lua + Future<Output = Result<R>>,
{
// The panic should never happen as async non-static code wouldn't compile
// Non-static lifetime must be bounded to 'lua lifetime
mlua_panic!("asynchronous meta functions are not supported for non-static userdata")
}
}
struct NonStaticUserDataFields<'lua, T: UserData> {
+25 -20
View File
@@ -1,9 +1,9 @@
use std::cell::RefCell;
use std::collections::HashSet;
use std::os::raw::c_void;
use std::rc::Rc;
use std::string::String as StdString;
use rustc_hash::FxHashSet;
use serde::de::{self, IntoDeserializer};
use crate::error::{Error, Result};
@@ -16,18 +16,23 @@ use crate::value::Value;
pub struct Deserializer<'lua> {
value: Value<'lua>,
options: Options,
visited: Rc<RefCell<HashSet<*const c_void>>>,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
}
/// A struct with options to change default deserializer behavior.
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct Options {
/// If true, an attempt to serialize types such as `Thread`, `UserData`, `LightUserData`
/// and `Error` will cause an error.
/// If true, an attempt to serialize types such as [`Thread`], [`UserData`], [`LightUserData`]
/// and [`Error`] will cause an error.
/// Otherwise these types skipped when iterating or serialized as unit type.
///
/// Default: **true**
///
/// [`Thread`]: crate::Thread
/// [`UserData`]: crate::UserData
/// [`LightUserData`]: crate::LightUserData
/// [`Error`]: crate::Error
pub deny_unsupported_types: bool,
/// If true, an attempt to serialize a recursive table (table that refers to itself)
@@ -40,23 +45,23 @@ pub struct Options {
impl Default for Options {
fn default() -> Self {
Options {
deny_unsupported_types: true,
deny_recursive_tables: true,
}
Self::new()
}
}
impl Options {
/// Returns a new instance of `Options` with default parameters.
pub fn new() -> Self {
Self::default()
pub const fn new() -> Self {
Options {
deny_unsupported_types: true,
deny_recursive_tables: true,
}
}
/// Sets [`deny_unsupported_types`] option.
///
/// [`deny_unsupported_types`]: #structfield.deny_unsupported_types
pub fn deny_unsupported_types(mut self, enabled: bool) -> Self {
pub const fn deny_unsupported_types(mut self, enabled: bool) -> Self {
self.deny_unsupported_types = enabled;
self
}
@@ -81,14 +86,14 @@ impl<'lua> Deserializer<'lua> {
Deserializer {
value,
options,
visited: Rc::new(RefCell::new(HashSet::new())),
visited: Rc::new(RefCell::new(FxHashSet::default())),
}
}
fn from_parts(
value: Value<'lua>,
options: Options,
visited: Rc<RefCell<HashSet<*const c_void>>>,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
) -> Self {
Deserializer {
value,
@@ -308,7 +313,7 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
struct SeqDeserializer<'lua> {
seq: TableSequence<'lua, Value<'lua>>,
options: Options,
visited: Rc<RefCell<HashSet<*const c_void>>>,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
}
impl<'lua, 'de> de::SeqAccess<'de> for SeqDeserializer<'lua> {
@@ -346,7 +351,7 @@ struct MapDeserializer<'lua> {
pairs: TablePairs<'lua, Value<'lua>, Value<'lua>>,
value: Option<Value<'lua>>,
options: Options,
visited: Rc<RefCell<HashSet<*const c_void>>>,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
processed: usize,
}
@@ -402,7 +407,7 @@ struct EnumDeserializer<'lua> {
variant: StdString,
value: Option<Value<'lua>>,
options: Options,
visited: Rc<RefCell<HashSet<*const c_void>>>,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
}
impl<'lua, 'de> de::EnumAccess<'de> for EnumDeserializer<'lua> {
@@ -426,7 +431,7 @@ impl<'lua, 'de> de::EnumAccess<'de> for EnumDeserializer<'lua> {
struct VariantDeserializer<'lua> {
value: Option<Value<'lua>>,
options: Options,
visited: Rc<RefCell<HashSet<*const c_void>>>,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
}
impl<'lua, 'de> de::VariantAccess<'de> for VariantDeserializer<'lua> {
@@ -494,12 +499,12 @@ impl<'lua, 'de> de::VariantAccess<'de> for VariantDeserializer<'lua> {
// Used to track recursive tables but allow to traverse same tables multiple times
struct RecursionGuard {
ptr: *const c_void,
visited: Rc<RefCell<HashSet<*const c_void>>>,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
}
impl RecursionGuard {
#[inline]
fn new(table: &Table, visited: &Rc<RefCell<HashSet<*const c_void>>>) -> Self {
fn new(table: &Table, visited: &Rc<RefCell<FxHashSet<*const c_void>>>) -> Self {
let visited = Rc::clone(visited);
let lua = table.0.lua;
let ptr =
@@ -519,7 +524,7 @@ impl Drop for RecursionGuard {
fn check_value_if_skip(
value: &Value,
options: Options,
visited: &RefCell<HashSet<*const c_void>>,
visited: &RefCell<FxHashSet<*const c_void>>,
) -> Result<bool> {
match value {
Value::Table(table) => {
+8 -8
View File
@@ -69,11 +69,11 @@ pub trait LuaSerdeExt<'lua> {
/// ```
fn array_metatable(&'lua self) -> Table<'lua>;
/// Converts `T` into a `Value` instance.
/// Converts `T` into a [`Value`] instance.
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: enum.Value.html
/// [`Value`]: crate::Value
///
/// # Example
///
@@ -102,11 +102,11 @@ pub trait LuaSerdeExt<'lua> {
/// ```
fn to_value<T: Serialize + ?Sized>(&'lua self, t: &T) -> Result<Value<'lua>>;
/// Converts `T` into a `Value` instance with options.
/// Converts `T` into a [`Value`] instance with options.
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: enum.Value.html
/// [`Value`]: crate::Value
///
/// # Example
///
@@ -129,11 +129,11 @@ pub trait LuaSerdeExt<'lua> {
where
T: Serialize + ?Sized;
/// Deserializes a `Value` into any serde deserializable object.
/// Deserializes a [`Value`] into any serde deserializable object.
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: enum.Value.html
/// [`Value`]: crate::Value
///
/// # Example
///
@@ -159,11 +159,11 @@ pub trait LuaSerdeExt<'lua> {
/// ```
fn from_value<T: Deserialize<'lua>>(&'lua self, value: Value<'lua>) -> Result<T>;
/// Deserializes a `Value` into any serde deserializable object with options.
/// Deserializes a [`Value`] into any serde deserializable object with options.
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: enum.Value.html
/// [`Value`]: crate::Value
///
/// # Example
///
+16 -16
View File
@@ -28,16 +28,16 @@ pub struct Options {
///
/// Default: **true**
///
/// [`array_metatable`]: ../trait.LuaSerdeExt.html#tymethod.array_metatable
/// [`array_metatable`]: crate::LuaSerdeExt::array_metatable
pub set_array_metatable: bool,
/// If true, serialize `None` (part of `Option` type) to [`null`].
/// If true, serialize `None` (part of the `Option` type) to [`null`].
/// Otherwise it will be set to Lua [`Nil`].
///
/// Default: **true**
///
/// [`null`]: ../trait.LuaSerdeExt.html#tymethod.null
/// [`Nil`]: ../../enum.Value.html#variant.Nil
/// [`null`]: crate::LuaSerdeExt::null
/// [`Nil`]: crate::Value::Nil
pub serialize_none_to_null: bool,
/// If true, serialize `Unit` (type of `()` in Rust) and Unit structs to [`null`].
@@ -45,31 +45,31 @@ pub struct Options {
///
/// Default: **true**
///
/// [`null`]: ../trait.LuaSerdeExt.html#tymethod.null
/// [`Nil`]: ../../enum.Value.html#variant.Nil
/// [`null`]: crate::LuaSerdeExt::null
/// [`Nil`]: crate::Value::Nil
pub serialize_unit_to_null: bool,
}
impl Default for Options {
fn default() -> Self {
Self::new()
}
}
impl Options {
/// Returns a new instance of [`Options`] with default parameters.
pub const fn new() -> Self {
Options {
set_array_metatable: true,
serialize_none_to_null: true,
serialize_unit_to_null: true,
}
}
}
impl Options {
/// Returns a new instance of `Options` with default parameters.
pub fn new() -> Self {
Self::default()
}
/// Sets [`set_array_metatable`] option.
///
/// [`set_array_metatable`]: #structfield.set_array_metatable
pub fn set_array_metatable(mut self, enabled: bool) -> Self {
pub const fn set_array_metatable(mut self, enabled: bool) -> Self {
self.set_array_metatable = enabled;
self
}
@@ -77,7 +77,7 @@ impl Options {
/// Sets [`serialize_none_to_null`] option.
///
/// [`serialize_none_to_null`]: #structfield.serialize_none_to_null
pub fn serialize_none_to_null(mut self, enabled: bool) -> Self {
pub const fn serialize_none_to_null(mut self, enabled: bool) -> Self {
self.serialize_none_to_null = enabled;
self
}
@@ -85,7 +85,7 @@ impl Options {
/// Sets [`serialize_unit_to_null`] option.
///
/// [`serialize_unit_to_null`]: #structfield.serialize_unit_to_null
pub fn serialize_unit_to_null(mut self, enabled: bool) -> Self {
pub const fn serialize_unit_to_null(mut self, enabled: bool) -> Self {
self.serialize_unit_to_null = enabled;
self
}
+11 -11
View File
@@ -6,32 +6,32 @@ use std::u32;
pub struct StdLib(u32);
impl StdLib {
/// [`coroutine`](https://www.lua.org/manual/5.3/manual.html#6.2) library
/// [`coroutine`](https://www.lua.org/manual/5.4/manual.html#6.2) library
///
/// Requires `feature = "lua54/lua53/lua52"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", doc))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub const COROUTINE: StdLib = StdLib(1);
/// [`table`](https://www.lua.org/manual/5.3/manual.html#6.6) library
/// [`table`](https://www.lua.org/manual/5.4/manual.html#6.6) library
pub const TABLE: StdLib = StdLib(1 << 1);
/// [`io`](https://www.lua.org/manual/5.3/manual.html#6.8) library
/// [`io`](https://www.lua.org/manual/5.4/manual.html#6.8) library
pub const IO: StdLib = StdLib(1 << 2);
/// [`os`](https://www.lua.org/manual/5.3/manual.html#6.9) library
/// [`os`](https://www.lua.org/manual/5.4/manual.html#6.9) library
pub const OS: StdLib = StdLib(1 << 3);
/// [`string`](https://www.lua.org/manual/5.3/manual.html#6.4) library
/// [`string`](https://www.lua.org/manual/5.4/manual.html#6.4) library
pub const STRING: StdLib = StdLib(1 << 4);
/// [`utf8`](https://www.lua.org/manual/5.3/manual.html#6.5) library
/// [`utf8`](https://www.lua.org/manual/5.4/manual.html#6.5) library
///
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53", doc))]
#[cfg(any(feature = "lua54", feature = "lua53"))]
pub const UTF8: StdLib = StdLib(1 << 5);
/// [`bit`](https://www.lua.org/manual/5.2/manual.html#6.7) library
///
/// Requires `feature = "lua52/luajit"`
#[cfg(any(feature = "lua52", feature = "luajit", doc))]
pub const BIT: StdLib = StdLib(1 << 6);
/// [`math`](https://www.lua.org/manual/5.3/manual.html#6.7) library
/// [`math`](https://www.lua.org/manual/5.4/manual.html#6.7) library
pub const MATH: StdLib = StdLib(1 << 7);
/// [`package`](https://www.lua.org/manual/5.3/manual.html#6.3) library
/// [`package`](https://www.lua.org/manual/5.4/manual.html#6.3) library
pub const PACKAGE: StdLib = StdLib(1 << 8);
/// [`jit`](http://luajit.org/ext_jit.html) library
///
@@ -44,7 +44,7 @@ impl StdLib {
/// Requires `feature = "luajit"`
#[cfg(any(feature = "luajit", doc))]
pub const FFI: StdLib = StdLib(1 << 30);
/// (**unsafe**) [`debug`](https://www.lua.org/manual/5.3/manual.html#6.10) library
/// (**unsafe**) [`debug`](https://www.lua.org/manual/5.4/manual.html#6.10) library
pub const DEBUG: StdLib = StdLib(1 << 31);
/// No libraries
+81 -22
View File
@@ -2,8 +2,9 @@ use std::marker::PhantomData;
#[cfg(feature = "serialize")]
use {
serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer},
std::result::Result as StdResult,
rustc_hash::FxHashSet,
serde::ser::{self, Serialize, SerializeMap, SerializeSeq, Serializer},
std::{cell::RefCell, os::raw::c_void, result::Result as StdResult},
};
use crate::error::{Error, Result};
@@ -378,8 +379,8 @@ impl<'lua> Table<'lua> {
/// # }
/// ```
///
/// [`Result`]: type.Result.html
/// [Lua manual]: http://www.lua.org/manual/5.3/manual.html#pdf-next
/// [`Result`]: crate::Result
/// [Lua manual]: http://www.lua.org/manual/5.4/manual.html#pdf-next
pub fn pairs<K: FromLua<'lua>, V: FromLua<'lua>>(self) -> TablePairs<'lua, K, V> {
TablePairs {
table: self.0,
@@ -427,8 +428,8 @@ impl<'lua> Table<'lua> {
/// ```
///
/// [`pairs`]: #method.pairs
/// [`Result`]: type.Result.html
/// [Lua manual]: http://www.lua.org/manual/5.3/manual.html#pdf-next
/// [`Result`]: crate::Result
/// [Lua manual]: http://www.lua.org/manual/5.4/manual.html#pdf-next
pub fn sequence_values<V: FromLua<'lua>>(self) -> TableSequence<'lua, V> {
TableSequence {
table: self.0,
@@ -501,6 +502,25 @@ impl<'lua> AsRef<Table<'lua>> for Table<'lua> {
/// An extension trait for `Table`s that provides a variety of convenient functionality.
pub trait TableExt<'lua> {
/// Calls the table as function assuming it has `__call` metamethod.
///
/// The metamethod is called with the table as its first argument, followed by the passed arguments.
fn call<A, R>(&self, args: A) -> Result<R>
where
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua>;
/// Asynchronously calls the table as function assuming it has `__call` metamethod.
///
/// The metamethod is called with the table as its first argument, followed by the passed arguments.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async<'fut, A, R>(&self, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut;
/// Gets the function associated to `key` from the table and executes it,
/// passing the table itself along with `args` as function arguments.
///
@@ -563,6 +583,25 @@ pub trait TableExt<'lua> {
}
impl<'lua> TableExt<'lua> for Table<'lua> {
fn call<A, R>(&self, args: A) -> Result<R>
where
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
// Convert table to a function and call via pcall that respects the `__call` metamethod.
Function(self.0.clone()).call(args)
}
#[cfg(feature = "async")]
fn call_async<'fut, A, R>(&self, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
A: ToLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
{
Function(self.0.clone()).call_async(args)
}
fn call_method<K, A, R>(&self, key: K, args: A) -> Result<R>
where
K: ToLua<'lua>,
@@ -622,22 +661,42 @@ impl<'lua> Serialize for Table<'lua> {
where
S: Serializer,
{
let len = self.raw_len() as usize;
if len > 0 || self.is_array() {
let mut seq = serializer.serialize_seq(Some(len))?;
for v in self.clone().raw_sequence_values_by_len::<Value>(None) {
let v = v.map_err(serde::ser::Error::custom)?;
seq.serialize_element(&v)?;
}
return seq.end();
thread_local! {
static VISITED: RefCell<FxHashSet<*const c_void>> = RefCell::new(FxHashSet::default());
}
let mut map = serializer.serialize_map(None)?;
for kv in self.clone().pairs::<Value, Value>() {
let (k, v) = kv.map_err(serde::ser::Error::custom)?;
map.serialize_entry(&k, &v)?;
}
map.end()
let lua = self.0.lua;
let ptr = unsafe { lua.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, self.0.index)) };
let res = VISITED.with(|visited| {
{
let mut visited = visited.borrow_mut();
if visited.contains(&ptr) {
return Err(ser::Error::custom("recursive table detected"));
}
visited.insert(ptr);
}
let len = self.raw_len() as usize;
if len > 0 || self.is_array() {
let mut seq = serializer.serialize_seq(Some(len))?;
for v in self.clone().raw_sequence_values_by_len::<Value>(None) {
let v = v.map_err(serde::ser::Error::custom)?;
seq.serialize_element(&v)?;
}
return seq.end();
}
let mut map = serializer.serialize_map(None)?;
for kv in self.clone().pairs::<Value, Value>() {
let (k, v) = kv.map_err(serde::ser::Error::custom)?;
map.serialize_entry(&k, &v)?;
}
map.end()
});
VISITED.with(|visited| {
visited.borrow_mut().remove(&ptr);
});
res
}
}
@@ -645,7 +704,7 @@ impl<'lua> Serialize for Table<'lua> {
///
/// This struct is created by the [`Table::pairs`] method.
///
/// [`Table::pairs`]: struct.Table.html#method.pairs
/// [`Table::pairs`]: crate::Table::pairs
pub struct TablePairs<'lua, K, V> {
table: LuaRef<'lua>,
key: Option<Value<'lua>>,
@@ -704,7 +763,7 @@ where
///
/// This struct is created by the [`Table::sequence_values`] method.
///
/// [`Table::sequence_values`]: struct.Table.html#method.sequence_values
/// [`Table::sequence_values`]: crate::Table::sequence_values
pub struct TableSequence<'lua, V> {
table: LuaRef<'lua>,
index: Option<Integer>,
+36 -12
View File
@@ -5,16 +5,16 @@ use crate::error::{Error, Result};
use crate::ffi;
use crate::types::LuaRef;
use crate::util::{check_stack, error_traceback, pop_error, StackGuard};
use crate::value::{FromLuaMulti, MultiValue, ToLuaMulti};
use crate::value::{FromLuaMulti, ToLuaMulti};
#[cfg(any(feature = "lua54", all(feature = "luajit", feature = "vendored"), doc))]
#[cfg(any(feature = "lua54", all(feature = "luajit", feature = "vendored")))]
use crate::function::Function;
#[cfg(feature = "async")]
use {
crate::{
lua::{Lua, ASYNC_POLL_PENDING},
value::Value,
value::{MultiValue, Value},
},
futures_core::{future::Future, stream::Stream},
std::{
@@ -33,7 +33,7 @@ pub enum ThreadStatus {
///
/// If a thread is in this state, it can be resumed by calling [`Thread::resume`].
///
/// [`Thread::resume`]: struct.Thread.html#method.resume
/// [`Thread::resume`]: crate::Thread::resume
Resumable,
/// Either the thread has finished executing, or the thread is currently running.
Unresumable,
@@ -49,8 +49,8 @@ pub struct Thread<'lua>(pub(crate) LuaRef<'lua>);
///
/// Requires `feature = "async"`
///
/// [`Future`]: ../futures_core/future/trait.Future.html
/// [`Stream`]: ../futures_core/stream/trait.Stream.html
/// [`Future`]: futures_core::future::Future
/// [`Stream`]: futures_core::stream::Stream
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[derive(Debug)]
@@ -58,6 +58,7 @@ pub struct AsyncThread<'lua, R> {
thread: Thread<'lua>,
args0: RefCell<Option<Result<MultiValue<'lua>>>>,
ret: PhantomData<R>,
recycle: bool,
}
impl<'lua> Thread<'lua> {
@@ -108,7 +109,7 @@ impl<'lua> Thread<'lua> {
R: FromLuaMulti<'lua>,
{
let lua = self.0.lua;
let args = args.to_lua_multi(lua)?;
let mut args = args.to_lua_multi(lua)?;
let nargs = args.len() as c_int;
let results = unsafe {
let _sg = StackGuard::new(lua.state);
@@ -123,7 +124,7 @@ impl<'lua> Thread<'lua> {
}
check_stack(thread_state, nargs)?;
for arg in args {
for arg in args.drain_all() {
lua.push_value(arg)?;
}
ffi::lua_xmove(lua.state, thread_state, nargs);
@@ -136,7 +137,7 @@ impl<'lua> Thread<'lua> {
return Err(pop_error(thread_state, ret));
}
let mut results = MultiValue::new();
let mut results = args; // Reuse MultiValue container
check_stack(lua.state, nresults + 2)?; // 2 is extra for `lua.pop_value()` below
ffi::lua_xmove(thread_state, lua.state, nresults);
@@ -181,7 +182,7 @@ impl<'lua> Thread<'lua> {
///
/// [Lua 5.4]: https://www.lua.org/manual/5.4/manual.html#lua_resetthread
/// [LuaJIT]: https://github.com/openresty/luajit2#lua_resetthread
#[cfg(any(feature = "lua54", all(feature = "luajit", feature = "vendored"), doc))]
#[cfg(any(feature = "lua54", all(feature = "luajit", feature = "vendored")))]
pub fn reset(&self, func: Function<'lua>) -> Result<()> {
let lua = self.0.lua;
unsafe {
@@ -203,10 +204,10 @@ impl<'lua> Thread<'lua> {
}
}
/// Converts Thread to an AsyncThread which implements Future and Stream traits.
/// 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
/// The object calls [`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()`
@@ -215,6 +216,10 @@ impl<'lua> Thread<'lua> {
///
/// Requires `feature = "async"`
///
/// [`Future`]: futures_core::future::Future
/// [`Stream`]: futures_core::stream::Stream
/// [`resume()`]: https://www.lua.org/manual/5.4/manual.html#lua_resume
///
/// # Examples
///
/// ```
@@ -256,6 +261,7 @@ impl<'lua> Thread<'lua> {
thread: self,
args0: RefCell::new(Some(args)),
ret: PhantomData,
recycle: false,
}
}
}
@@ -266,6 +272,24 @@ impl<'lua> PartialEq for Thread<'lua> {
}
}
#[cfg(feature = "async")]
impl<'lua, R> AsyncThread<'lua, R> {
#[inline]
pub(crate) fn set_recyclable(&mut self, recyclable: bool) {
self.recycle = recyclable;
}
}
#[cfg(feature = "async")]
#[cfg(any(feature = "lua54", all(feature = "luajit", feature = "vendored")))]
impl<'lua, R> Drop for AsyncThread<'lua, R> {
fn drop(&mut self) {
if self.recycle {
self.thread.0.lua.recycle_thread(&mut self.thread);
}
}
}
#[cfg(feature = "async")]
impl<'lua, R> Stream for AsyncThread<'lua, R>
where
+23 -11
View File
@@ -1,9 +1,11 @@
use std::cell::RefCell;
use std::hash::{Hash, Hasher};
use std::os::raw::{c_int, c_void};
use std::sync::{Arc, Mutex};
use std::{fmt, mem, ptr};
#[cfg(feature = "lua54")]
use std::ffi::CStr;
#[cfg(feature = "async")]
use futures_core::future::LocalBoxFuture;
@@ -48,10 +50,16 @@ pub(crate) struct AsyncPollUpvalue<'lua> {
}
#[cfg(feature = "send")]
pub(crate) type HookCallback = Arc<RefCell<dyn FnMut(&Lua, Debug) -> Result<()> + Send>>;
pub(crate) type HookCallback = Arc<Mutex<dyn FnMut(&Lua, Debug) -> Result<()> + Send>>;
#[cfg(not(feature = "send"))]
pub(crate) type HookCallback = Arc<RefCell<dyn FnMut(&Lua, Debug) -> Result<()>>>;
pub(crate) type HookCallback = Arc<Mutex<dyn FnMut(&Lua, Debug) -> Result<()>>>;
#[cfg(all(feature = "send", feature = "lua54"))]
pub(crate) type WarnCallback = Box<dyn Fn(&Lua, &CStr, bool) -> Result<()> + Send>;
#[cfg(all(not(feature = "send"), feature = "lua54"))]
pub(crate) type WarnCallback = Box<dyn Fn(&Lua, &CStr, bool) -> Result<()>>;
#[cfg(feature = "send")]
pub trait MaybeSend: Send {}
@@ -71,15 +79,17 @@ pub(crate) struct DestructedUserdataMT;
/// 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
/// 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`].
/// Instead of placing a [`RegistryKey`] into a [`UserData`] type, prefer instead to use
/// [`AnyUserData::set_user_value`] / [`AnyUserData::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
/// [`UserData::set_user_value`]: struct.UserData.html#method.set_user_value
/// [`UserData::get_user_value`]: struct.UserData.html#method.get_user_value
/// [`UserData`]: crate::UserData
/// [`RegistryKey`]: crate::RegistryKey
/// [`Lua::remove_registry_value`]: crate::Lua::remove_registry_value
/// [`Lua::expire_registry_values`]: crate::Lua::expire_registry_values
/// [`AnyUserData::set_user_value`]: crate::AnyUserData::set_user_value
/// [`AnyUserData::get_user_value`]: crate::AnyUserData::get_user_value
pub struct RegistryKey {
pub(crate) registry_id: c_int,
pub(crate) unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
@@ -145,7 +155,9 @@ impl<'lua> Clone for LuaRef<'lua> {
impl<'lua> Drop for LuaRef<'lua> {
fn drop(&mut self) {
self.lua.drop_ref(self)
if self.index > 0 {
self.lua.drop_ref(self);
}
}
}
+291 -70
View File
@@ -3,6 +3,7 @@ use std::cell::{Ref, RefCell, RefMut};
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::{Deref, DerefMut};
use std::os::raw::{c_char, c_int};
use std::string::String as StdString;
#[cfg(feature = "async")]
@@ -23,18 +24,18 @@ use crate::types::{Callback, LuaRef, MaybeSend};
use crate::util::{check_stack, get_userdata, take_userdata, StackGuard};
use crate::value::{FromLua, FromLuaMulti, ToLua, ToLuaMulti};
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
use crate::value::Value;
#[cfg(feature = "async")]
use crate::types::AsyncCallback;
#[cfg(feature = "lua54")]
pub(crate) const USER_VALUE_MAXSLOT: usize = 8;
/// Kinds of metamethods that can be overridden.
///
/// Currently, this mechanism does not allow overriding the `__gc` metamethod, since there is
/// generally no need to do so: [`UserData`] implementors can instead just implement `Drop`.
///
/// [`UserData`]: trait.UserData.html
/// [`UserData`]: crate::UserData
#[derive(Debug, Clone)]
pub enum MetaMethod {
/// The `+` operator.
@@ -53,29 +54,29 @@ pub enum MetaMethod {
Unm,
/// The floor division (//) operator.
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53", doc))]
#[cfg(any(feature = "lua54", feature = "lua53"))]
IDiv,
/// The bitwise AND (&) operator.
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53", doc))]
#[cfg(any(feature = "lua54", feature = "lua53"))]
BAnd,
/// The bitwise OR (|) operator.
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53", doc))]
#[cfg(any(feature = "lua54", feature = "lua53"))]
BOr,
/// The bitwise XOR (binary ~) operator.
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53", doc))]
#[cfg(any(feature = "lua54", feature = "lua53"))]
BXor,
/// The bitwise NOT (unary ~) operator.
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53", doc))]
#[cfg(any(feature = "lua54", feature = "lua53"))]
BNot,
/// The bitwise left shift (<<) operator.
#[cfg(any(feature = "lua54", feature = "lua53", doc))]
#[cfg(any(feature = "lua54", feature = "lua53"))]
Shl,
/// The bitwise right shift (>>) operator.
#[cfg(any(feature = "lua54", feature = "lua53", doc))]
#[cfg(any(feature = "lua54", feature = "lua53"))]
Shr,
/// The string concatenation operator `..`.
Concat,
@@ -102,7 +103,12 @@ pub enum MetaMethod {
/// This is not an operator, but it will be called by the built-in `pairs` function.
///
/// Requires `feature = "lua54/lua53/lua52"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", doc))]
#[cfg(any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luajit52",
))]
Pairs,
/// The `__ipairs` metamethod.
///
@@ -111,7 +117,7 @@ pub enum MetaMethod {
/// Requires `feature = "lua52"`
///
/// [`ipairs`]: https://www.lua.org/manual/5.2/manual.html#pdf-ipairs
#[cfg(any(feature = "lua52", doc))]
#[cfg(any(feature = "lua52", feature = "luajit52", doc))]
IPairs,
/// The `__close` metamethod.
///
@@ -123,7 +129,7 @@ pub enum MetaMethod {
/// Requires `feature = "lua54"`
///
/// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#3.3.8
#[cfg(any(feature = "lua54", doc))]
#[cfg(any(feature = "lua54"))]
Close,
/// A custom metamethod.
///
@@ -188,9 +194,14 @@ impl MetaMethod {
MetaMethod::Call => "__call",
MetaMethod::ToString => "__tostring",
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luajit52"
))]
MetaMethod::Pairs => "__pairs",
#[cfg(feature = "lua52")]
#[cfg(any(feature = "lua52", feature = "luajit52"))]
MetaMethod::IPairs => "__ipairs",
#[cfg(feature = "lua54")]
@@ -250,9 +261,14 @@ impl From<StdString> for MetaMethod {
"__call" => MetaMethod::Call,
"__tostring" => MetaMethod::ToString,
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luajit52"
))]
"__pairs" => MetaMethod::Pairs,
#[cfg(feature = "lua52")]
#[cfg(any(feature = "lua52", feature = "luajit52"))]
"__ipairs" => MetaMethod::IPairs,
#[cfg(feature = "lua54")]
@@ -271,7 +287,7 @@ impl From<&str> for MetaMethod {
/// Method registry for [`UserData`] implementors.
///
/// [`UserData`]: trait.UserData.html
/// [`UserData`]: crate::UserData
pub trait UserDataMethods<'lua, T: UserData> {
/// Add a regular method which accepts a `&T` as the first parameter.
///
@@ -325,7 +341,7 @@ pub trait UserDataMethods<'lua, T: UserData> {
///
/// Prefer to use [`add_method`] or [`add_method_mut`] as they are easier to use.
///
/// [`AnyUserData`]: struct.AnyUserData.html
/// [`AnyUserData`]: crate::AnyUserData
/// [`add_method`]: #method.add_method
/// [`add_method_mut`]: #method.add_method_mut
fn add_function<S, A, R, F>(&mut self, name: &S, function: F)
@@ -395,6 +411,25 @@ pub trait UserDataMethods<'lua, T: UserData> {
R: ToLuaMulti<'lua>,
M: 'static + MaybeSend + FnMut(&'lua Lua, &mut T, A) -> Result<R>;
/// Add an async metamethod which accepts a `T` as the first parameter and returns Future.
/// The passed `T` is cloned from the original value.
///
/// This is an async version of [`add_meta_method`].
///
/// Requires `feature = "async"`
///
/// [`add_meta_method`]: #method.add_meta_method
#[cfg(all(feature = "async", not(feature = "lua51")))]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_meta_method<S, A, R, M, MR>(&mut self, name: S, method: M)
where
T: Clone,
S: Into<MetaMethod>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
M: 'static + MaybeSend + Fn(&'lua Lua, T, A) -> MR,
MR: 'lua + Future<Output = Result<R>>;
/// Add a metamethod which accepts generic arguments.
///
/// Metamethods for binary operators can be triggered if either the left or right argument to
@@ -419,6 +454,23 @@ pub trait UserDataMethods<'lua, T: UserData> {
R: ToLuaMulti<'lua>,
F: 'static + MaybeSend + FnMut(&'lua Lua, A) -> Result<R>;
/// Add a metamethod which accepts generic arguments and returns Future.
///
/// This is an async version of [`add_meta_function`].
///
/// Requires `feature = "async"`
///
/// [`add_meta_function`]: #method.add_meta_function
#[cfg(all(feature = "async", not(feature = "lua51")))]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_meta_function<S, A, R, F, FR>(&mut self, name: S, function: F)
where
S: Into<MetaMethod>,
A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>,
F: 'static + MaybeSend + Fn(&'lua Lua, A) -> FR,
FR: 'lua + Future<Output = Result<R>>;
//
// Below are internal methods used in generated code
//
@@ -432,11 +484,20 @@ pub trait UserDataMethods<'lua, T: UserData> {
#[doc(hidden)]
fn add_meta_callback(&mut self, _meta: MetaMethod, _callback: Callback<'lua, 'static>) {}
#[doc(hidden)]
#[cfg(feature = "async")]
fn add_async_meta_callback(
&mut self,
_meta: MetaMethod,
_callback: AsyncCallback<'lua, 'static>,
) {
}
}
/// Field registry for [`UserData`] implementors.
///
/// [`UserData`]: trait.UserData.html
/// [`UserData`]: crate::UserData
pub trait UserDataFields<'lua, T: UserData> {
/// Add a regular field getter as a method which accepts a `&T` as the parameter.
///
@@ -469,7 +530,7 @@ pub trait UserDataFields<'lua, T: UserData> {
///
/// Prefer to use [`add_field_method_get`] as it is easier to use.
///
/// [`AnyUserData`]: struct.AnyUserData.html
/// [`AnyUserData`]: crate::AnyUserData
/// [`add_field_method_get`]: #method.add_field_method_get
fn add_field_function_get<S, R, F>(&mut self, name: &S, function: F)
where
@@ -482,7 +543,7 @@ pub trait UserDataFields<'lua, T: UserData> {
///
/// Prefer to use [`add_field_method_set`] as it is easier to use.
///
/// [`AnyUserData`]: struct.AnyUserData.html
/// [`AnyUserData`]: crate::AnyUserData
/// [`add_field_method_set`]: #method.add_field_method_set
fn add_field_function_set<S, A, F>(&mut self, name: &S, function: F)
where
@@ -578,10 +639,10 @@ pub trait UserDataFields<'lua, T: UserData> {
/// # }
/// ```
///
/// [`ToLua`]: trait.ToLua.html
/// [`FromLua`]: trait.FromLua.html
/// [`UserDataFields`]: trait.UserDataFields.html
/// [`UserDataMethods`]: trait.UserDataMethods.html
/// [`ToLua`]: crate::ToLua
/// [`FromLua`]: crate::FromLua
/// [`UserDataFields`]: crate::UserDataFields
/// [`UserDataMethods`]: crate::UserDataMethods
pub trait UserData: Sized {
/// Adds custom fields specific to this userdata.
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(_fields: &mut F) {}
@@ -718,9 +779,9 @@ impl Serialize for UserDataSerializeError {
/// This API should only be used when necessary. Implementing [`UserData`] already allows defining
/// methods which check the type and acquire a borrow behind the scenes.
///
/// [`UserData`]: trait.UserData.html
/// [`is`]: #method.is
/// [`borrow`]: #method.borrow
/// [`UserData`]: crate::UserData
/// [`is`]: crate::AnyUserData::is
/// [`borrow`]: crate::AnyUserData::borrow
#[derive(Clone, Debug)]
pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>);
@@ -758,12 +819,13 @@ impl<'lua> AnyUserData<'lua> {
/// Takes out the value of `UserData` and sets the special "destructed" metatable that prevents
/// any further operations with this userdata.
#[doc(hidden)]
///
/// All associated user values will be also cleared.
pub fn take<T: 'static + UserData>(&self) -> Result<T> {
let lua = self.0.lua;
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 2)?;
check_stack(lua.state, 3)?;
let type_id = lua.push_userdata_ref(&self.0)?;
match type_id {
@@ -771,12 +833,22 @@ impl<'lua> AnyUserData<'lua> {
// Try to borrow userdata exclusively
let _ = (*get_userdata::<UserDataCell<T>>(lua.state, -1)).try_borrow_mut()?;
// Clear uservalue
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
ffi::lua_pushnil(lua.state);
// Clear associated user values
#[cfg(feature = "lua54")]
for i in 1..=USER_VALUE_MAXSLOT {
ffi::lua_pushnil(lua.state);
ffi::lua_setiuservalue(lua.state, -2, i as c_int);
}
#[cfg(any(feature = "lua53", feature = "lua52"))]
{
ffi::lua_pushnil(lua.state);
ffi::lua_setuservalue(lua.state, -2);
}
#[cfg(any(feature = "lua51", feature = "luajit"))]
protect_lua!(lua.state, 0, 1, fn(state) ffi::lua_newtable(state))?;
ffi::lua_setuservalue(lua.state, -2);
protect_lua!(lua.state, 1, 1, fn(state) {
ffi::lua_newtable(state);
ffi::lua_setuservalue(state, -2);
})?;
Ok(take_userdata::<UserDataCell<T>>(lua.state).into_inner())
}
@@ -788,54 +860,196 @@ impl<'lua> AnyUserData<'lua> {
/// Sets an associated value to this `AnyUserData`.
///
/// The value may be any Lua value whatsoever, and can be retrieved with [`get_user_value`].
/// As Lua < 5.3 allows to store only tables, the value will be stored in a table at index 1.
///
/// This is the same as calling [`set_nth_user_value`] with `n` set to 1.
///
/// [`get_user_value`]: #method.get_user_value
/// [`set_nth_user_value`]: #method.set_nth_user_value
#[inline]
pub fn set_user_value<V: ToLua<'lua>>(&self, v: V) -> Result<()> {
self.set_nth_user_value(1, v)
}
/// Returns an associated value set by [`set_user_value`].
///
/// This is the same as calling [`get_nth_user_value`] with `n` set to 1.
///
/// [`set_user_value`]: #method.set_user_value
/// [`get_nth_user_value`]: #method.get_nth_user_value
#[inline]
pub fn get_user_value<V: FromLua<'lua>>(&self) -> Result<V> {
self.get_nth_user_value(1)
}
/// Sets an associated `n`th value to this `AnyUserData`.
///
/// The value may be any Lua value whatsoever, and can be retrieved with [`get_nth_user_value`].
/// `n` starts from 1 and can be up to 65535.
///
/// This is supported for all Lua versions.
/// In Lua 5.4 first 7 elements are stored in a most efficient way.
/// For other Lua versions this functionality is provided using a wrapping table.
///
/// [`get_nth_user_value`]: #method.get_nth_user_value
pub fn set_nth_user_value<V: ToLua<'lua>>(&self, n: usize, v: V) -> Result<()> {
if n < 1 || n > u16::MAX as usize {
return Err(Error::RuntimeError(
"user value index out of bounds".to_string(),
));
}
let lua = self.0.lua;
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
let v = {
// Lua <= 5.2 allows to store only a table. Then we will wrap the value.
let t = lua.create_table_with_capacity(1, 0)?;
t.raw_set(1, v)?;
Value::Table(t)
};
#[cfg(any(feature = "lua54", feature = "lua53"))]
let v = v.to_lua(lua)?;
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 3)?;
check_stack(lua.state, 5)?;
lua.push_userdata_ref(&self.0)?;
lua.push_value(v)?;
ffi::lua_setuservalue(lua.state, -2);
lua.push_value(v.to_lua(lua)?)?;
#[cfg(feature = "lua54")]
if n < USER_VALUE_MAXSLOT {
ffi::lua_setiuservalue(lua.state, -2, n as c_int);
return Ok(());
}
// Multiple (extra) user values are emulated by storing them in a table
protect_lua!(lua.state, 2, 0, |state| {
if getuservalue_table(lua.state, -2) != ffi::LUA_TTABLE {
// Create a new table to use as uservalue
ffi::lua_pop(lua.state, 1);
ffi::lua_newtable(state);
ffi::lua_pushvalue(state, -1);
#[cfg(feature = "lua54")]
ffi::lua_setiuservalue(lua.state, -4, USER_VALUE_MAXSLOT as c_int);
#[cfg(not(feature = "lua54"))]
ffi::lua_setuservalue(lua.state, -4);
}
ffi::lua_pushvalue(state, -2);
#[cfg(feature = "lua54")]
ffi::lua_rawseti(state, -2, (n - USER_VALUE_MAXSLOT + 1) as ffi::lua_Integer);
#[cfg(not(feature = "lua54"))]
ffi::lua_rawseti(state, -2, n as ffi::lua_Integer);
})?;
Ok(())
}
}
/// Returns an associated value set by [`set_user_value`].
/// Returns an associated `n`th value set by [`set_nth_user_value`].
///
/// For Lua < 5.3 the value will be automatically extracted from the table wrapper from index 1.
/// `n` starts from 1 and can be up to 65535.
///
/// [`set_user_value`]: #method.set_user_value
pub fn get_user_value<V: FromLua<'lua>>(&self) -> Result<V> {
/// This is supported for all Lua versions.
/// In Lua 5.4 first 7 elements are stored in a most efficient way.
/// For other Lua versions this functionality is provided using a wrapping table.
///
/// [`set_nth_user_value`]: #method.set_nth_user_value
pub fn get_nth_user_value<V: FromLua<'lua>>(&self, n: usize) -> Result<V> {
if n < 1 || n > u16::MAX as usize {
return Err(Error::RuntimeError(
"user value index out of bounds".to_string(),
));
}
let lua = self.0.lua;
let res = unsafe {
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 3)?;
check_stack(lua.state, 4)?;
lua.push_userdata_ref(&self.0)?;
ffi::lua_getuservalue(lua.state, -1);
lua.pop_value()
};
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
return match <Option<Table>>::from_lua(res, lua)? {
Some(t) => t.get(1),
None => V::from_lua(Value::Nil, lua),
};
#[cfg(any(feature = "lua54", feature = "lua53"))]
V::from_lua(res, lua)
#[cfg(feature = "lua54")]
if n < USER_VALUE_MAXSLOT {
ffi::lua_getiuservalue(lua.state, -1, n as c_int);
return V::from_lua(lua.pop_value(), lua);
}
// Multiple (extra) user values are emulated by storing them in a table
protect_lua!(lua.state, 1, 1, |state| {
if getuservalue_table(lua.state, -1) != ffi::LUA_TTABLE {
ffi::lua_pushnil(lua.state);
return;
}
#[cfg(feature = "lua54")]
ffi::lua_rawgeti(state, -1, (n - USER_VALUE_MAXSLOT + 1) as ffi::lua_Integer);
#[cfg(not(feature = "lua54"))]
ffi::lua_rawgeti(state, -1, n as ffi::lua_Integer);
})?;
V::from_lua(lua.pop_value(), lua)
}
}
/// Sets an associated value to this `AnyUserData` by name.
///
/// The value can be retrieved with [`get_named_user_value`].
///
/// [`get_named_user_value`]: #method.get_named_user_value
pub fn set_named_user_value<S, V>(&self, name: &S, v: V) -> Result<()>
where
S: AsRef<[u8]> + ?Sized,
V: ToLua<'lua>,
{
let lua = self.0.lua;
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 5)?;
lua.push_userdata_ref(&self.0)?;
lua.push_value(v.to_lua(lua)?)?;
// Multiple (extra) user values are emulated by storing them in a table
let name = name.as_ref();
protect_lua!(lua.state, 2, 0, |state| {
if getuservalue_table(lua.state, -2) != ffi::LUA_TTABLE {
// Create a new table to use as uservalue
ffi::lua_pop(lua.state, 1);
ffi::lua_newtable(state);
ffi::lua_pushvalue(state, -1);
#[cfg(feature = "lua54")]
ffi::lua_setiuservalue(lua.state, -4, USER_VALUE_MAXSLOT as c_int);
#[cfg(not(feature = "lua54"))]
ffi::lua_setuservalue(lua.state, -4);
}
ffi::lua_pushlstring(state, name.as_ptr() as *const c_char, name.len());
ffi::lua_pushvalue(state, -3);
ffi::lua_rawset(state, -3);
})?;
Ok(())
}
}
/// Returns an associated value by name set by [`set_named_user_value`].
///
/// [`set_named_user_value`]: #method.set_named_user_value
pub fn get_named_user_value<S, V>(&self, name: &S) -> Result<V>
where
S: AsRef<[u8]> + ?Sized,
V: FromLua<'lua>,
{
let lua = self.0.lua;
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 4)?;
lua.push_userdata_ref(&self.0)?;
// Multiple (extra) user values are emulated by storing them in a table
let name = name.as_ref();
protect_lua!(lua.state, 1, 1, |state| {
if getuservalue_table(lua.state, -1) != ffi::LUA_TTABLE {
ffi::lua_pushnil(lua.state);
return;
}
ffi::lua_pushlstring(state, name.as_ptr() as *const c_char, name.len());
ffi::lua_rawget(state, -2);
})?;
V::from_lua(lua.pop_value(), lua)
}
}
/// Returns a metatable of this `UserData`.
@@ -845,7 +1059,7 @@ impl<'lua> AnyUserData<'lua> {
///
/// For `T: UserData + 'static` returned metatable is shared among all instances of type `T`.
///
/// [`UserDataMetatable`]: struct.UserDataMetatable.html
/// [`UserDataMetatable`]: crate::UserDataMetatable
pub fn get_metatable(&self) -> Result<UserDataMetatable<'lua>> {
self.get_raw_metatable().map(UserDataMetatable)
}
@@ -917,6 +1131,13 @@ impl<'lua> AsRef<AnyUserData<'lua>> for AnyUserData<'lua> {
}
}
unsafe fn getuservalue_table(state: *mut ffi::lua_State, idx: c_int) -> c_int {
#[cfg(feature = "lua54")]
return ffi::lua_getiuservalue(state, idx, USER_VALUE_MAXSLOT as c_int);
#[cfg(not(feature = "lua54"))]
return ffi::lua_getuservalue(state, idx);
}
/// Handle to a `UserData` metatable.
#[derive(Clone, Debug)]
pub struct UserDataMetatable<'lua>(pub(crate) Table<'lua>);
@@ -954,7 +1175,7 @@ impl<'lua> UserDataMetatable<'lua> {
///
/// The pairs are wrapped in a [`Result`], since they are lazily converted to `V` type.
///
/// [`Result`]: type.Result.html
/// [`Result`]: crate::Result
pub fn pairs<V: FromLua<'lua>>(self) -> UserDataMetatablePairs<'lua, V> {
UserDataMetatablePairs(self.0.pairs())
}
@@ -966,8 +1187,8 @@ impl<'lua> UserDataMetatable<'lua> {
///
/// This struct is created by the [`UserDataMetatable::pairs`] method.
///
/// [`UserData`]: trait.UserData.html
/// [`UserDataMetatable::pairs`]: struct.UserDataMetatable.html#method.pairs
/// [`UserData`]: crate::UserData
/// [`UserDataMetatable::pairs`]: crate::UserDataMetatable::method.pairs
pub struct UserDataMetatablePairs<'lua, V>(TablePairs<'lua, StdString, V>);
impl<'lua, V> Iterator for UserDataMetatablePairs<'lua, V>
+29 -41
View File
@@ -1,6 +1,4 @@
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::error::Error as StdError;
use std::fmt::Write;
use std::os::raw::{c_char, c_int, c_void};
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
@@ -8,12 +6,13 @@ use std::sync::Arc;
use std::{mem, ptr, slice};
use once_cell::sync::Lazy;
use rustc_hash::FxHashMap;
use crate::error::{Error, Result};
use crate::ffi;
static METATABLE_CACHE: Lazy<HashMap<TypeId, u8>> = Lazy::new(|| {
let mut map = HashMap::with_capacity(32);
static METATABLE_CACHE: Lazy<FxHashMap<TypeId, u8>> = Lazy::new(|| {
let mut map = FxHashMap::with_capacity_and_hasher(32, Default::default());
crate::lua::init_metatable_cache(&mut map);
map.insert(TypeId::of::<WrappedFailure>(), 0);
map.insert(TypeId::of::<String>(), 0);
@@ -288,6 +287,17 @@ pub unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T) -> Result<()> {
Ok(())
}
// Internally uses 3 stack spaces, does not call checkstack.
#[cfg(feature = "lua54")]
#[inline]
pub unsafe fn push_userdata_uv<T>(state: *mut ffi::lua_State, t: T, nuvalue: c_int) -> Result<()> {
let ud = protect_lua!(state, 0, 1, |state| {
ffi::lua_newuserdatauv(state, mem::size_of::<T>(), nuvalue) as *mut T
})?;
ptr::write(ud, t);
Ok(())
}
#[inline]
pub unsafe fn get_userdata<T>(state: *mut ffi::lua_State, index: c_int) -> *mut T {
let ud = ffi::lua_touserdata(state, index) as *mut T;
@@ -535,11 +545,8 @@ where
ffi::lua_settop(state, 1);
let wrapped_error = ud as *mut WrappedFailure;
ptr::write(wrapped_error, WrappedFailure::Error(err));
get_gc_metatable::<WrappedFailure>(state);
ffi::lua_setmetatable(state, -2);
// Convert to CallbackError and attach traceback
// Build `CallbackError` with traceback
let traceback = if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 {
ffi::luaL_traceback(state, state, ptr::null(), 0);
let traceback = to_string(state, -1);
@@ -548,10 +555,13 @@ where
} else {
"<not enough stack space for traceback>".to_string()
};
if let WrappedFailure::Error(ref mut err) = *wrapped_error {
let cause = Arc::new(err.clone());
*err = Error::CallbackError { traceback, cause };
}
let cause = Arc::new(err);
ptr::write(
wrapped_error,
WrappedFailure::Error(Error::CallbackError { traceback, cause }),
);
get_gc_metatable::<WrappedFailure>(state);
ffi::lua_setmetatable(state, -2);
ffi::lua_error(state)
}
@@ -736,32 +746,6 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
// be possible to make this consume arbitrary amounts of memory (for example, some
// kind of recursive error structure?)
let _ = write!(&mut (*err_buf), "{}", error);
// Find first two sources that caused the error
let mut source1 = error.source();
let mut source0 = source1.and_then(|s| s.source());
while let Some(source) = source0.and_then(|s| s.source()) {
source1 = source0;
source0 = Some(source);
}
match (source1, source0) {
(_, Some(error0))
if error0.to_string().contains("\nstack traceback:\n") =>
{
let _ = write!(&mut (*err_buf), "\ncaused by: {}", error0);
}
(Some(error1), Some(error0)) => {
let _ = write!(&mut (*err_buf), "\ncaused by: {}", error0);
let s = error1.to_string();
if let Some(traceback) = s.splitn(2, "\nstack traceback:\n").nth(1) {
let _ =
write!(&mut (*err_buf), "\nstack traceback:\n{}", traceback);
}
}
(Some(error1), None) => {
let _ = write!(&mut (*err_buf), "\ncaused by: {}", error1);
}
_ => {}
}
Ok(err_buf)
}
Some(WrappedFailure::Panic(Some(ref panic))) => {
@@ -805,7 +789,6 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
// Create destructed userdata metatable
unsafe extern "C" fn destructed_error(state: *mut ffi::lua_State) -> c_int {
// TODO: Consider changing error to UserDataDestructed in v0.7
callback_error(state, |_| Err(Error::CallbackDestructed))
}
@@ -842,9 +825,14 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
"__newindex",
"__call",
"__tostring",
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luajit52"
))]
"__pairs",
#[cfg(any(feature = "lua53", feature = "lua52"))]
#[cfg(any(feature = "lua53", feature = "lua52", feature = "luajit52"))]
"__ipairs",
#[cfg(feature = "lua54")]
"__close",
+32 -2
View File
@@ -49,10 +49,11 @@ pub enum Value<'lua> {
/// `Error` is a special builtin userdata type. When received from Lua it is implicitly cloned.
Error(Error),
}
pub use self::Value::Nil;
impl<'lua> Value<'lua> {
pub fn type_name(&self) -> &'static str {
pub const fn type_name(&self) -> &'static str {
match *self {
Value::Nil => "nil",
Value::Boolean(_) => "boolean",
@@ -161,6 +162,12 @@ impl<'lua> MultiValue<'lua> {
pub fn new() -> MultiValue<'lua> {
MultiValue(Vec::new())
}
/// Similar to `new` but can return previously used container with allocated capacity.
#[inline]
pub(crate) fn new_or_cached(lua: &'lua Lua) -> MultiValue<'lua> {
lua.new_or_cached_multivalue()
}
}
impl<'lua> Default for MultiValue<'lua> {
@@ -226,6 +233,11 @@ impl<'lua> MultiValue<'lua> {
self.0.pop()
}
#[inline]
pub fn clear(&mut self) {
self.0.clear();
}
#[inline]
pub fn len(&self) -> usize {
self.0.len()
@@ -233,13 +245,31 @@ impl<'lua> MultiValue<'lua> {
#[inline]
pub fn is_empty(&self) -> bool {
self.0.len() == 0
self.0.is_empty()
}
#[inline]
pub fn iter(&self) -> iter::Rev<slice::Iter<Value<'lua>>> {
self.0.iter().rev()
}
#[inline]
pub(crate) fn drain_all(&mut self) -> iter::Rev<vec::Drain<Value<'lua>>> {
self.0.drain(..).rev()
}
#[inline]
pub(crate) fn refill(
&mut self,
iter: impl IntoIterator<Item = Result<Value<'lua>>>,
) -> Result<()> {
self.0.clear();
for value in iter {
self.0.push(value?);
}
self.0.reverse();
Ok(())
}
}
/// Trait for types convertible to any number of Lua values.
+24 -5
View File
@@ -3,7 +3,7 @@
use std::cell::Cell;
use std::rc::Rc;
use std::sync::{
atomic::{AtomicI64, Ordering},
atomic::{AtomicI64, AtomicU64, Ordering},
Arc,
};
use std::time::Duration;
@@ -12,7 +12,8 @@ use futures_timer::Delay;
use futures_util::stream::TryStreamExt;
use mlua::{
Error, Function, Lua, Result, Table, TableExt, Thread, UserData, UserDataMethods, Value,
Error, Function, Lua, LuaOptions, MetaMethod, Result, StdLib, Table, TableExt, Thread,
UserData, UserDataMethods, Value,
};
#[tokio::test]
@@ -227,7 +228,8 @@ async fn test_async_thread() -> Result<()> {
#[tokio::test]
async fn test_async_table() -> Result<()> {
let lua = Lua::new();
let options = LuaOptions::new().thread_cache_size(4);
let lua = Lua::new_with(StdLib::ALL_SAFE, options)?;
let table = lua.create_table()?;
table.set("val", 10)?;
@@ -276,7 +278,7 @@ async fn test_async_table() -> Result<()> {
#[tokio::test]
async fn test_async_userdata() -> Result<()> {
#[derive(Clone)]
struct MyUserData(Arc<AtomicI64>);
struct MyUserData(Arc<AtomicU64>);
impl UserData for MyUserData {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
@@ -295,13 +297,20 @@ async fn test_async_userdata() -> Result<()> {
Delay::new(Duration::from_millis(n)).await;
Ok(format!("elapsed:{}ms", n))
});
#[cfg(not(feature = "lua51"))]
methods.add_async_meta_method(MetaMethod::Call, |_, data, ()| async move {
let n = data.0.load(Ordering::Relaxed);
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(Arc::new(AtomicI64::new(11))))?;
let userdata = lua.create_userdata(MyUserData(Arc::new(AtomicU64::new(11))))?;
globals.set("userdata", userdata.clone())?;
lua.load(
@@ -315,6 +324,16 @@ async fn test_async_userdata() -> Result<()> {
.exec_async()
.await?;
#[cfg(not(feature = "lua51"))]
lua.load(
r#"
userdata:set_value(15)
assert(userdata() == "elapsed:15ms")
"#,
)
.exec_async()
.await?;
Ok(())
}
+16 -1
View File
@@ -3,7 +3,7 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::ffi::{CStr, CString};
use maplit::{btreemap, btreeset, hashmap, hashset};
use mlua::{Lua, Result};
use mlua::{Error, Lua, Result};
#[test]
fn test_conv_vec() -> Result<()> {
@@ -123,3 +123,18 @@ fn test_conv_boxed_slice() -> Result<()> {
Ok(())
}
#[test]
fn test_conv_array() -> Result<()> {
let lua = Lua::new();
let v = [1, 2, 3];
lua.globals().set("v", v)?;
let v2: [i32; 3] = lua.globals().get("v")?;
assert_eq!(v, v2);
let v2 = lua.globals().get::<_, [i32; 4]>("v");
assert!(matches!(v2, Err(Error::FromLuaConversionError { .. })));
Ok(())
}
+3
View File
@@ -1,5 +1,8 @@
[target.x86_64-apple-darwin]
rustflags = ["-C", "link-args=-rdynamic"]
[target.aarch64-apple-darwin]
rustflags = ["-C", "link-args=-rdynamic"]
[target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "link-args=-rdynamic"]
+5
View File
@@ -460,6 +460,11 @@ fn test_from_value_with_options() -> Result<(), Box<dyn std::error::Error>> {
Err(err) => panic!("expected `DeserializeError` error, got {:?}", err),
};
// Check recursion when using `Serialize` impl
let t = lua.create_table()?;
t.set("t", t.clone())?;
assert!(serde_json::to_string(&t).is_err());
// Serialize Lua globals table
#[derive(Debug, Deserialize)]
struct Globals {
+15 -10
View File
@@ -1,4 +1,4 @@
use mlua::{Lua, Nil, Result, Table, TableExt, Value};
use mlua::{Error, Lua, Nil, Result, Table, TableExt, Value};
#[test]
fn test_set_get() -> Result<()> {
@@ -139,14 +139,6 @@ fn test_table_sequence_from() -> Result<()> {
vec![1, 2, 3]
);
assert_eq!(
get_table
.call::<_, Table>(&[1, 2, 3])?
.sequence_values()
.collect::<Result<Vec<i64>>>()?,
vec![1, 2, 3]
);
Ok(())
}
@@ -274,7 +266,12 @@ fn test_table_call() -> Result<()> {
lua.load(
r#"
table = {a = 1}
table = {a = 1, b = 2}
setmetatable(table, {
__call = function(t, key)
return "call_"..t[key]
end
})
function table.func(key)
return "func_"..key
@@ -289,11 +286,19 @@ fn test_table_call() -> Result<()> {
let table: Table = lua.globals().get("table")?;
assert_eq!(table.call::<_, String>("b")?, "call_2");
assert_eq!(table.call_function::<_, _, String>("func", "a")?, "func_a");
assert_eq!(
table.call_method::<_, _, String>("method", "a")?,
"method_1"
);
// Test calling non-callable table
let table2 = lua.create_table()?;
assert!(matches!(
table2.call::<_, ()>(()),
Err(Error::RuntimeError(_))
));
Ok(())
}
+146 -4
View File
@@ -847,6 +847,37 @@ fn test_mismatched_registry_key() -> Result<()> {
Ok(())
}
#[test]
fn test_application_data() -> Result<()> {
let lua = Lua::new();
lua.set_app_data("test1");
lua.set_app_data(vec!["test2"]);
let f = lua.create_function(|lua, ()| {
{
let data1 = lua.app_data_ref::<&str>().unwrap();
assert_eq!(*data1, "test1");
}
let mut data2 = lua.app_data_mut::<Vec<&str>>().unwrap();
assert_eq!(*data2, vec!["test2"]);
data2.push("test3");
Ok(())
})?;
f.call(())?;
assert_eq!(*lua.app_data_ref::<&str>().unwrap(), "test1");
assert_eq!(
*lua.app_data_ref::<Vec<&str>>().unwrap(),
vec!["test2", "test3"]
);
lua.remove_app_data::<Vec<&str>>();
assert!(matches!(lua.app_data_ref::<Vec<&str>>(), None));
Ok(())
}
#[test]
fn test_recursion() -> Result<()> {
let lua = Lua::new();
@@ -1046,17 +1077,22 @@ fn test_context_thread() -> Result<()> {
)
.into_function()?;
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luajit52"
))]
f.call::<_, ()>(lua.current_thread())?;
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[cfg(any(feature = "lua51", all(feature = "luajit", not(feature = "luajit52"))))]
f.call::<_, ()>(Nil)?;
Ok(())
}
#[test]
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[cfg(any(feature = "lua51", all(feature = "luajit", not(feature = "luajit52"))))]
fn test_context_thread_51() -> Result<()> {
let lua = Lua::new();
@@ -1105,12 +1141,118 @@ fn test_load_from_function() -> Result<()> {
assert_eq!(t.get::<_, String>("__name")?, "my_module");
assert_eq!(i.load(Ordering::Relaxed), 1);
let _: Value = lua.load_from_function("my_module", func)?;
let _: Value = lua.load_from_function("my_module", func.clone())?;
assert_eq!(i.load(Ordering::Relaxed), 1);
let func_nil = lua.create_function(move |_, _: String| Ok(Value::Nil))?;
let v: Value = lua.load_from_function("my_module2", func_nil)?;
assert_eq!(v, Value::Boolean(true));
// Test unloading and loading again
lua.unload("my_module")?;
let _: Value = lua.load_from_function("my_module", func)?;
assert_eq!(i.load(Ordering::Relaxed), 2);
// Unloading nonexistent module must not fail
lua.unload("my_module2")?;
Ok(())
}
#[test]
fn test_inspect_stack() -> Result<()> {
let lua = Lua::new();
// Not inside any function
assert!(lua.inspect_stack(0).is_none());
let logline = lua.create_function(|lua, msg: StdString| {
let debug = lua.inspect_stack(1).unwrap(); // caller
let source = debug.source().short_src.map(core::str::from_utf8);
let source = source.transpose().unwrap().unwrap_or("?");
let line = debug.curr_line();
Ok(format!("{}:{} {}", source, line, msg))
})?;
lua.globals().set("logline", logline)?;
lua.load(
r#"
local function foo()
local line = logline("hello")
return line
end
local function bar()
return foo()
end
assert(foo() == '[string "chunk"]:3 hello')
assert(bar() == '[string "chunk"]:3 hello')
assert(logline("world") == '[string "chunk"]:12 world')
"#,
)
.set_name("chunk")?
.exec()?;
Ok(())
}
#[test]
fn test_multi_states() -> Result<()> {
let lua = Lua::new();
let f = lua.create_function(|_, g: Option<Function>| {
if let Some(g) = g {
g.call(())?;
}
Ok(())
})?;
lua.globals().set("f", f)?;
lua.load("f(function() coroutine.wrap(function() f() end)() end)")
.exec()?;
Ok(())
}
#[test]
#[cfg(feature = "lua54")]
fn test_warnings() -> Result<()> {
let lua = Lua::new();
lua.set_app_data::<Vec<(StdString, bool)>>(Vec::new());
lua.set_warning_function(|lua, msg, tocont| {
let msg = msg.to_string_lossy().to_string();
lua.app_data_mut::<Vec<(StdString, bool)>>()
.unwrap()
.push((msg, tocont));
Ok(())
});
lua.warning("native warning ...", true)?;
lua.warning("finish", false)?;
lua.load(r#"warn("lua warning", "continue")"#).exec()?;
lua.remove_warning_function();
lua.warning("one more warning", false)?;
let messages = lua.app_data_ref::<Vec<(StdString, bool)>>().unwrap();
assert_eq!(
*messages,
vec![
("native warning ...".to_string(), true),
("finish".to_string(), false),
("lua warning".to_string(), true),
("continue".to_string(), false),
]
);
// Trigger error inside warning
lua.set_warning_function(|_, _, _| Err(Error::RuntimeError("warning error".to_string())));
assert!(matches!(
lua.load(r#"warn("test")"#).exec(),
Err(Error::CallbackError { cause, .. })
if matches!(*cause, Error::RuntimeError(ref err) if err == "warning error")
));
Ok(())
}
+48 -15
View File
@@ -111,7 +111,12 @@ fn test_metamethods() -> Result<()> {
Err("no such custom index".to_lua_err())
}
});
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luajit52"
))]
methods.add_meta_method(MetaMethod::Pairs, |lua, data, ()| {
use std::iter::FromIterator;
let stateless_iter = lua.create_function(|_, (data, i): (MyUserData, i64)| {
@@ -136,11 +141,16 @@ fn test_metamethods() -> Result<()> {
10
);
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
let pairs_it = {
lua.load(
#[cfg(any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luajit52"
))]
let pairs_it = lua
.load(
r#"
function pairs_it()
function()
local r = 0
for i, v in pairs(userdata1) do
r = r + v
@@ -149,17 +159,21 @@ fn test_metamethods() -> Result<()> {
end
"#,
)
.exec()?;
globals.get::<_, Function>("pairs_it")?
};
.eval::<Function>()?;
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 = "lua54", feature = "lua53", feature = "lua52"))]
assert_eq!(pairs_it.call::<_, i64>(())?, 28);
assert!(lua.load("userdata2.nonexist_field").eval::<()>().is_err());
#[cfg(any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luajit52"
))]
assert_eq!(pairs_it.call::<_, i64>(())?, 28);
let userdata2: Value = globals.get("userdata2")?;
let userdata3: Value = globals.get("userdata3")?;
@@ -285,7 +299,7 @@ fn test_userdata_take() -> Result<()> {
fn check_userdata_take(lua: &Lua, userdata: AnyUserData, rc: Arc<i64>) -> Result<()> {
lua.globals().set("userdata", userdata.clone())?;
assert_eq!(Arc::strong_count(&rc), 2);
assert_eq!(Arc::strong_count(&rc), 3);
let userdata_copy = userdata.clone();
{
let _value = userdata.borrow::<MyUserdata>()?;
@@ -299,6 +313,7 @@ fn test_userdata_take() -> Result<()> {
let value = userdata_copy.take::<MyUserdata>()?;
assert_eq!(*value.0, 18);
drop(value);
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&rc), 1);
match userdata.borrow::<MyUserdata>() {
@@ -319,6 +334,7 @@ fn test_userdata_take() -> Result<()> {
let rc = Arc::new(18);
let userdata = lua.create_userdata(MyUserdata(rc.clone()))?;
userdata.set_nth_user_value(2, MyUserdata(rc.clone()))?;
check_userdata_take(&lua, userdata, rc)?;
// Additionally check serializable userdata
@@ -326,6 +342,7 @@ fn test_userdata_take() -> Result<()> {
{
let rc = Arc::new(18);
let userdata = lua.create_ser_userdata(MyUserdata(rc.clone()))?;
userdata.set_nth_user_value(2, MyUserdata(rc.clone()))?;
check_userdata_take(&lua, userdata, rc)?;
}
@@ -355,16 +372,32 @@ fn test_destroy_userdata() -> Result<()> {
}
#[test]
fn test_user_value() -> Result<()> {
fn test_user_values() -> Result<()> {
struct MyUserData;
impl UserData for MyUserData {}
let lua = Lua::new();
let ud = lua.create_userdata(MyUserData)?;
ud.set_user_value("hello")?;
assert_eq!(ud.get_user_value::<String>()?, "hello");
assert!(ud.get_user_value::<u32>().is_err());
ud.set_nth_user_value(1, "hello")?;
ud.set_nth_user_value(2, "world")?;
ud.set_nth_user_value(65535, 321)?;
assert_eq!(ud.get_nth_user_value::<String>(1)?, "hello");
assert_eq!(ud.get_nth_user_value::<String>(2)?, "world");
assert_eq!(ud.get_nth_user_value::<Value>(3)?, Value::Nil);
assert_eq!(ud.get_nth_user_value::<i32>(65535)?, 321);
assert!(ud.get_nth_user_value::<Value>(0).is_err());
assert!(ud.get_nth_user_value::<Value>(65536).is_err());
// Named user values
ud.set_named_user_value("name", "alex")?;
ud.set_named_user_value("age", 10)?;
assert_eq!(ud.get_named_user_value::<_, String>("name")?, "alex");
assert_eq!(ud.get_named_user_value::<_, i32>("age")?, 10);
assert_eq!(ud.get_named_user_value::<_, Value>("nonexist")?, Value::Nil);
Ok(())
}