From c2eab173c5554cd8a3bd7b4ee42fa3bc9087b1fa Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sun, 3 Nov 2024 11:36:23 +0000 Subject: [PATCH] Add `userdata-wrappers` feature This feature allow to opt into `impl UserData` for `Rc`/`Arc`/`Rc>`/`Arc>` where `T: UserData` Close #470 --- .github/workflows/main.yml | 24 +-- Cargo.toml | 1 + README.md | 1 + src/scope.rs | 3 +- src/userdata/registry.rs | 314 +++++++++++++++++++++++++++++-------- tests/userdata.rs | 157 +++++++++++++++++++ 6 files changed, 422 insertions(+), 78 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a4795c1b..cd12c620 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,8 +27,8 @@ jobs: - name: Build ${{ matrix.lua }} vendored run: | cargo build --features "${{ matrix.lua }},vendored" - cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow" - cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,send" + cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers" + cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers,send" shell: bash - name: Build ${{ matrix.lua }} pkg-config if: ${{ matrix.os == 'ubuntu-latest' }} @@ -51,7 +51,7 @@ jobs: toolchain: stable target: aarch64-apple-darwin - name: Cross-compile - run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serialize,macros" + run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers" build_aarch64_cross_ubuntu: name: Cross-compile to aarch64-unknown-linux-gnu @@ -72,7 +72,7 @@ jobs: sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libc6-dev-arm64-cross shell: bash - name: Cross-compile - run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serialize,macros" + run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers" shell: bash build_armv7_cross_ubuntu: @@ -94,7 +94,7 @@ jobs: sudo apt-get install -y --no-install-recommends gcc-arm-linux-gnueabihf libc-dev-armhf-cross shell: bash - name: Cross-compile - run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serialize,macros" + run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers" shell: bash test: @@ -123,8 +123,8 @@ jobs: - name: Run ${{ matrix.lua }} tests run: | cargo test --features "${{ matrix.lua }},vendored" - cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow" - cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,send" + cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers" + cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers,send" shell: bash - name: Run compile tests (macos lua54) if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua54' }} @@ -154,8 +154,8 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Run ${{ matrix.lua }} tests with address sanitizer run: | - cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions - cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,send" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions + cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions + cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers,send" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions shell: bash env: RUSTFLAGS: -Z sanitizer=address @@ -181,7 +181,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Run ${{ matrix.lua }} tests with forced memory limit run: | - cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros" + cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers" shell: bash env: RUSTFLAGS: --cfg=force_memory_limit @@ -254,7 +254,7 @@ jobs: - name: Run ${{ matrix.lua }} tests run: | cargo test --tests --features "${{ matrix.lua }},vendored" - cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros" + cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers" rustfmt: name: Rustfmt @@ -281,4 +281,4 @@ jobs: - uses: giraffate/clippy-action@v1 with: reporter: 'github-pr-review' - clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow" + clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers" diff --git a/Cargo.toml b/Cargo.toml index 988cad29..eb958cba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ error-send = [] serialize = ["dep:serde", "dep:erased-serde", "dep:serde-value"] macros = ["mlua_derive/macros"] anyhow = ["dep:anyhow", "error-send"] +userdata-wrappers = [] [dependencies] mlua_derive = { version = "=0.10.0", optional = true, path = "mlua_derive" } diff --git a/README.md b/README.md index c7035f22..ee0661f1 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ Below is a list of the available feature flags. By default `mlua` does not enabl * `serialize`: add serialization and deserialization support to `mlua` types using [serde] framework * `macros`: enable procedural macros (such as `chunk!`) * `anyhow`: enable `anyhow::Error` conversion into Lua +* `userdata-wrappers`: opt into `impl UserData` for `Rc`/`Arc`/`Rc>`/`Arc>` where `T: UserData` [5.4]: https://www.lua.org/manual/5.4/manual.html [5.3]: https://www.lua.org/manual/5.3/manual.html diff --git a/src/scope.rs b/src/scope.rs index b17d36ef..99419b6b 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,7 +1,6 @@ use std::cell::RefCell; use std::marker::PhantomData; use std::mem; -use std::os::raw::c_void; use crate::error::{Error, Result}; use crate::function::Function; @@ -183,7 +182,7 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { let ud_ptr = util::push_uninit_userdata::>(state, protect)?; // Push the metatable and register it with no TypeId - let mut registry = UserDataRegistry::new_unique(ud_ptr as *const c_void); + let mut registry = UserDataRegistry::new_unique(ud_ptr as *mut _); T::register(&mut registry); self.lua.push_userdata_metatable(registry)?; let mt_ptr = ffi::lua_topointer(state, -1); diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index 4c7eee82..d83b4487 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -21,12 +21,32 @@ use { std::future::{self, Future}, }; +#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] +use std::rc::Rc; +#[cfg(feature = "userdata-wrappers")] +use std::sync::{Arc, Mutex, RwLock}; + type StaticFieldCallback = Box Result<()> + 'static>; #[derive(Clone, Copy)] -pub(crate) enum UserDataTypeId { +enum UserDataTypeId { Shared(TypeId), Unique(usize), + + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + Rc(TypeId), + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + RcRefCell(TypeId), + #[cfg(feature = "userdata-wrappers")] + Arc(TypeId), + #[cfg(feature = "userdata-wrappers")] + ArcMutex(TypeId), + #[cfg(feature = "userdata-wrappers")] + ArcRwLock(TypeId), + #[cfg(feature = "userdata-wrappers")] + ArcParkingLotMutex(TypeId), + #[cfg(feature = "userdata-wrappers")] + ArcParkingLotRwLock(TypeId), } /// Handle to registry for userdata methods and metamethods. @@ -45,31 +65,23 @@ pub struct UserDataRegistry { #[cfg(feature = "async")] pub(crate) async_meta_methods: Vec<(String, AsyncCallback)>, - pub(crate) type_id: UserDataTypeId, + type_id: UserDataTypeId, _type: PhantomData, } impl UserDataRegistry { - #[inline] + #[inline(always)] pub(crate) fn new(type_id: TypeId) -> Self { - UserDataRegistry { - fields: Vec::new(), - field_getters: Vec::new(), - field_setters: Vec::new(), - meta_fields: Vec::new(), - methods: Vec::new(), - #[cfg(feature = "async")] - async_methods: Vec::new(), - meta_methods: Vec::new(), - #[cfg(feature = "async")] - async_meta_methods: Vec::new(), - type_id: UserDataTypeId::Shared(type_id), - _type: PhantomData, - } + Self::with_type_id(UserDataTypeId::Shared(type_id)) } - #[inline] - pub(crate) fn new_unique(ud_ptr: *const c_void) -> Self { + #[inline(always)] + pub(crate) fn new_unique(ud_ptr: *mut c_void) -> Self { + Self::with_type_id(UserDataTypeId::Unique(ud_ptr as usize)) + } + + #[inline(always)] + fn with_type_id(type_id: UserDataTypeId) -> Self { UserDataRegistry { fields: Vec::new(), field_getters: Vec::new(), @@ -81,7 +93,7 @@ impl UserDataRegistry { meta_methods: Vec::new(), #[cfg(feature = "async")] async_meta_methods: Vec::new(), - type_id: UserDataTypeId::Unique(ud_ptr as usize), + type_id, _type: PhantomData, } } @@ -91,6 +103,20 @@ impl UserDataRegistry { match self.type_id { UserDataTypeId::Shared(type_id) => Some(type_id), UserDataTypeId::Unique(_) => None, + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + UserDataTypeId::Rc(type_id) => Some(type_id), + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + UserDataTypeId::RcRefCell(type_id) => Some(type_id), + #[cfg(feature = "userdata-wrappers")] + UserDataTypeId::Arc(type_id) => Some(type_id), + #[cfg(feature = "userdata-wrappers")] + UserDataTypeId::ArcMutex(type_id) => Some(type_id), + #[cfg(feature = "userdata-wrappers")] + UserDataTypeId::ArcRwLock(type_id) => Some(type_id), + #[cfg(feature = "userdata-wrappers")] + UserDataTypeId::ArcParkingLotMutex(type_id) => Some(type_id), + #[cfg(feature = "userdata-wrappers")] + UserDataTypeId::ArcParkingLotRwLock(type_id) => Some(type_id), } } @@ -120,28 +146,102 @@ impl UserDataRegistry { let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua); match target_type_id { - // This branch is for `'static` userdata that share type metatable - UserDataTypeId::Shared(target_type_id) => { - match try_self_arg!(rawlua.get_userdata_type_id::(self_index)) { - Some(self_type_id) if self_type_id == target_type_id => { - let ud = get_userdata::>(state, self_index); - try_self_arg!((*ud).try_borrow_scoped(|ud| { - method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) - })) - } - _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), - } + #[rustfmt::skip] + UserDataTypeId::Shared(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::(self_index)) == Some(target_type_id) => + { + let ud = get_userdata::>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) + })) + } + #[rustfmt::skip] + UserDataTypeId::Unique(target_ptr) + if get_userdata::>(state, self_index) as usize == target_ptr => + { + let ud = target_ptr as *mut UserDataStorage; + try_self_arg!((*ud).try_borrow_scoped(|ud| { + method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) + })) + } + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + #[rustfmt::skip] + UserDataTypeId::Rc(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>(self_index)) == Some(target_type_id) => + { + let ud = get_userdata::>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) + })) + } + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + #[rustfmt::skip] + UserDataTypeId::RcRefCell(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) == Some(target_type_id) => + { + let ud = get_userdata::>>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + let ud = ud.try_borrow().map_err(|_| Error::UserDataBorrowError)?; + method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua) + })) + } + #[cfg(feature = "userdata-wrappers")] + #[rustfmt::skip] + UserDataTypeId::Arc(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>(self_index)) == Some(target_type_id) => + { + let ud = get_userdata::>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) + })) + } + #[cfg(feature = "userdata-wrappers")] + #[rustfmt::skip] + UserDataTypeId::ArcMutex(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) == Some(target_type_id) => + { + let ud = get_userdata::>>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + let ud = ud.try_lock().map_err(|_| Error::UserDataBorrowError)?; + method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua) + })) + } + #[cfg(feature = "userdata-wrappers")] + #[rustfmt::skip] + UserDataTypeId::ArcRwLock(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) == Some(target_type_id) => + { + let ud = get_userdata::>>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + let ud = ud.try_read().map_err(|_| Error::UserDataBorrowError)?; + method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua) + })) } - UserDataTypeId::Unique(target_ptr) => { - match get_userdata::>(state, self_index) { - ud if ud as usize == target_ptr => { - try_self_arg!((*ud).try_borrow_scoped(|ud| { - method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) - })) - } - _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), - } + #[cfg(feature = "userdata-wrappers")] + #[rustfmt::skip] + UserDataTypeId::ArcParkingLotMutex(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) + == Some(target_type_id) => + { + let ud = get_userdata::>>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + let ud = ud.try_lock().ok_or(Error::UserDataBorrowError)?; + method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua) + })) } + #[cfg(feature = "userdata-wrappers")] + #[rustfmt::skip] + UserDataTypeId::ArcParkingLotRwLock(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) + == Some(target_type_id) => + { + let ud = get_userdata::>>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + let ud = ud.try_read().ok_or(Error::UserDataBorrowError)?; + method(rawlua.lua(), &ud, args?)?.push_into_stack_multi(rawlua) + })) + } + _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), } }) } @@ -174,28 +274,96 @@ impl UserDataRegistry { let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua); match target_type_id { - // This branch is for `'static` userdata that share type metatable - UserDataTypeId::Shared(target_type_id) => { - match try_self_arg!(rawlua.get_userdata_type_id::(self_index)) { - Some(self_type_id) if self_type_id == target_type_id => { - let ud = get_userdata::>(state, self_index); - try_self_arg!((*ud).try_borrow_scoped_mut(|ud| { - method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) - })) - } - _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), - } + #[rustfmt::skip] + UserDataTypeId::Shared(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::(self_index)) == Some(target_type_id) => + { + let ud = get_userdata::>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped_mut(|ud| { + method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) + })) + } + #[rustfmt::skip] + UserDataTypeId::Unique(target_ptr) + if get_userdata::>(state, self_index) as usize == target_ptr => + { + let ud = target_ptr as *mut UserDataStorage; + try_self_arg!((*ud).try_borrow_scoped_mut(|ud| { + method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) + })) + } + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + #[rustfmt::skip] + UserDataTypeId::Rc(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>(self_index)) == Some(target_type_id) => + { + Err(Error::UserDataBorrowMutError) + }, + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + #[rustfmt::skip] + UserDataTypeId::RcRefCell(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) == Some(target_type_id) => + { + let ud = get_userdata::>>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + let mut ud = ud.try_borrow_mut().map_err(|_| Error::UserDataBorrowMutError)?; + method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua) + })) + } + #[cfg(feature = "userdata-wrappers")] + #[rustfmt::skip] + UserDataTypeId::Arc(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>(self_index)) == Some(target_type_id) => + { + Err(Error::UserDataBorrowMutError) + }, + #[cfg(feature = "userdata-wrappers")] + #[rustfmt::skip] + UserDataTypeId::ArcMutex(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) == Some(target_type_id) => + { + let ud = get_userdata::>>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + let mut ud = ud.try_lock().map_err(|_| Error::UserDataBorrowMutError)?; + method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua) + })) } - UserDataTypeId::Unique(target_ptr) => { - match get_userdata::>(state, self_index) { - ud if ud as usize == target_ptr => { - try_self_arg!((*ud).try_borrow_scoped_mut(|ud| { - method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) - })) - } - _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), - } + #[cfg(feature = "userdata-wrappers")] + #[rustfmt::skip] + UserDataTypeId::ArcRwLock(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) == Some(target_type_id) => + { + let ud = get_userdata::>>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + let mut ud = ud.try_write().map_err(|_| Error::UserDataBorrowMutError)?; + method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua) + })) } + #[cfg(feature = "userdata-wrappers")] + #[rustfmt::skip] + UserDataTypeId::ArcParkingLotMutex(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) + == Some(target_type_id) => + { + let ud = get_userdata::>>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + let mut ud = ud.try_lock().ok_or(Error::UserDataBorrowMutError)?; + method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua) + })) + } + #[cfg(feature = "userdata-wrappers")] + #[rustfmt::skip] + UserDataTypeId::ArcParkingLotRwLock(target_type_id) + if try_self_arg!(rawlua.get_userdata_type_id::>>(self_index)) + == Some(target_type_id) => + { + let ud = get_userdata::>>>(state, self_index); + try_self_arg!((*ud).try_borrow_scoped(|ud| { + let mut ud = ud.try_write().ok_or(Error::UserDataBorrowMutError)?; + method(rawlua.lua(), &mut ud, args?)?.push_into_stack_multi(rawlua) + })) + } + _ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)), } }) } @@ -607,11 +775,14 @@ impl UserDataMethods for UserDataRegistry { } macro_rules! lua_userdata_impl { - ($type:ty) => { + ($type:ty => $type_variant:tt) => { + lua_userdata_impl!($type, UserDataTypeId::$type_variant(TypeId::of::<$type>())); + }; + + ($type:ty, $type_id:expr) => { impl UserData for $type { fn register(registry: &mut UserDataRegistry) { - let type_id = TypeId::of::(); - let mut orig_registry = UserDataRegistry::new(type_id); + let mut orig_registry = UserDataRegistry::with_type_id($type_id); T::register(&mut orig_registry); // Copy all fields, methods, etc. from the original registry @@ -635,4 +806,19 @@ macro_rules! lua_userdata_impl { // A special proxy object for UserData pub(crate) struct UserDataProxy(pub(crate) PhantomData); -lua_userdata_impl!(UserDataProxy); +lua_userdata_impl!(UserDataProxy, UserDataTypeId::Shared(TypeId::of::())); + +#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] +lua_userdata_impl!(Rc => Rc); +#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] +lua_userdata_impl!(Rc> => RcRefCell); +#[cfg(feature = "userdata-wrappers")] +lua_userdata_impl!(Arc => Arc); +#[cfg(feature = "userdata-wrappers")] +lua_userdata_impl!(Arc> => ArcMutex); +#[cfg(feature = "userdata-wrappers")] +lua_userdata_impl!(Arc> => ArcRwLock); +#[cfg(feature = "userdata-wrappers")] +lua_userdata_impl!(Arc> => ArcParkingLotMutex); +#[cfg(feature = "userdata-wrappers")] +lua_userdata_impl!(Arc> => ArcParkingLotRwLock); diff --git a/tests/userdata.rs b/tests/userdata.rs index d8c06b9e..c05fb108 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -881,3 +881,160 @@ fn test_nested_userdata_gc() -> Result<()> { Ok(()) } + +#[cfg(feature = "userdata-wrappers")] +#[test] +fn test_userdata_wrappers() -> Result<()> { + struct MyUserData(i64); + + impl UserData for MyUserData { + fn add_fields>(fields: &mut F) { + fields.add_field("static", "constant"); + fields.add_field_method_get("data", |_, this| Ok(this.0)); + fields.add_field_method_set("data", |_, this, val| { + this.0 = val; + Ok(()) + }) + } + } + + let lua = Lua::new(); + let globals = lua.globals(); + + // Rc + #[cfg(not(feature = "send"))] + { + let ud = std::rc::Rc::new(MyUserData(1)); + globals.set("rc_ud", ud.clone())?; + lua.load( + r#" + assert(rc_ud.static == "constant") + local ok, err = pcall(function() rc_ud.data = 2 end) + assert( + tostring(err):sub(1, 32) == "error mutably borrowing userdata", + "expected error mutably borrowing userdata, got " .. tostring(err) + ) + assert(rc_ud.data == 1) + "#, + ) + .exec() + .unwrap(); + globals.set("rc_ud", Nil)?; + lua.gc_collect()?; + assert_eq!(std::rc::Rc::strong_count(&ud), 1); + } + + // Rc> + #[cfg(not(feature = "send"))] + { + let ud = std::rc::Rc::new(std::cell::RefCell::new(MyUserData(2))); + globals.set("rc_refcell_ud", ud.clone())?; + lua.load( + r#" + assert(rc_refcell_ud.static == "constant") + rc_refcell_ud.data = rc_refcell_ud.data + 1 + assert(rc_refcell_ud.data == 3) + "#, + ) + .exec()?; + assert_eq!(ud.borrow().0, 3); + globals.set("rc_refcell_ud", Nil)?; + lua.gc_collect()?; + assert_eq!(std::rc::Rc::strong_count(&ud), 1); + } + + // Arc + { + let ud = Arc::new(MyUserData(3)); + globals.set("arc_ud", ud.clone())?; + lua.load( + r#" + assert(arc_ud.static == "constant") + local ok, err = pcall(function() arc_ud.data = 10 end) + assert( + tostring(err):sub(1, 32) == "error mutably borrowing userdata", + "expected error mutably borrowing userdata, got " .. tostring(err) + ) + assert(arc_ud.data == 3) + "#, + ) + .exec()?; + globals.set("arc_ud", Nil)?; + lua.gc_collect()?; + assert_eq!(Arc::strong_count(&ud), 1); + } + + // Arc> + { + let ud = Arc::new(std::sync::Mutex::new(MyUserData(4))); + globals.set("arc_mutex_ud", ud.clone())?; + lua.load( + r#" + assert(arc_mutex_ud.static == "constant") + arc_mutex_ud.data = arc_mutex_ud.data + 1 + assert(arc_mutex_ud.data == 5) + "#, + ) + .exec()?; + assert_eq!(ud.lock().unwrap().0, 5); + globals.set("arc_mutex_ud", Nil)?; + lua.gc_collect()?; + assert_eq!(Arc::strong_count(&ud), 1); + } + + // Arc> + { + let ud = Arc::new(std::sync::RwLock::new(MyUserData(6))); + globals.set("arc_rwlock_ud", ud.clone())?; + lua.load( + r#" + assert(arc_rwlock_ud.static == "constant") + arc_rwlock_ud.data = arc_rwlock_ud.data + 1 + assert(arc_rwlock_ud.data == 7) + "#, + ) + .exec()?; + assert_eq!(ud.read().unwrap().0, 7); + globals.set("arc_rwlock_ud", Nil)?; + lua.gc_collect()?; + assert_eq!(Arc::strong_count(&ud), 1); + } + + // Arc> + { + let ud = Arc::new(parking_lot::Mutex::new(MyUserData(8))); + globals.set("arc_parking_lot_mutex_ud", ud.clone())?; + lua.load( + r#" + assert(arc_parking_lot_mutex_ud.static == "constant") + arc_parking_lot_mutex_ud.data = arc_parking_lot_mutex_ud.data + 1 + assert(arc_parking_lot_mutex_ud.data == 9) + "#, + ) + .exec()?; + assert_eq!(ud.lock().0, 9); + globals.set("arc_parking_lot_mutex_ud", Nil)?; + lua.gc_collect()?; + assert_eq!(Arc::strong_count(&ud), 1); + } + + // Arc> + { + let ud = Arc::new(parking_lot::RwLock::new(MyUserData(10))); + globals.set("arc_parking_lot_rwlock_ud", ud.clone())?; + lua.load( + r#" + assert(arc_parking_lot_rwlock_ud.static == "constant") + arc_parking_lot_rwlock_ud.data = arc_parking_lot_rwlock_ud.data + 1 + assert(arc_parking_lot_rwlock_ud.data == 11) + "#, + ) + .exec()?; + assert_eq!(ud.read().0, 11); + globals.set("arc_parking_lot_rwlock_ud", Nil)?; + lua.gc_collect()?; + assert_eq!(Arc::strong_count(&ud), 1); + } + + Ok(()) +}