#![cfg(feature = "derive")]
use lua_rs_runtime::{lua_methods, AnyUserData, Lua, LuaUserData, UserData, UserDataMethods};
#[derive(LuaUserData)]
#[lua(methods)]
struct Knob {
setting: i64,
}
#[lua_methods]
impl Knob {
pub fn turn(&mut self, by: i64) -> i64 {
self.setting += by;
self.setting
}
pub fn read(&self) -> i64 {
self.setting
}
}
struct Panel {
knob: Knob,
}
impl UserData for Panel {
fn add_methods<M: UserDataMethods<Self>>(m: &mut M) {
m.add_function("knob", |lua, this: AnyUserData| {
this.delegate::<Panel, Knob, _>(lua, |p| &mut p.knob)
});
}
}
#[test]
fn lua_methods_type_works_as_owned_userdata() {
let lua = Lua::new();
let knob = lua.create_userdata(Knob { setting: 10 }).unwrap();
lua.globals().set("k", &knob).unwrap();
let out: i64 = lua.load("k:turn(5); return k:read()").eval().unwrap();
assert_eq!(out, 15);
}
#[test]
fn lua_methods_type_works_as_scoped_userdata() {
let lua = Lua::new();
let mut knob = Knob { setting: 100 };
let out: i64 = lua
.scope(|s| {
let ud = s.create_userdata_ref_mut(&lua, &mut knob)?;
lua.globals().set("k", &ud)?;
lua.load("k:turn(-30); return k:read()").eval()
})
.unwrap();
assert_eq!(out, 70);
assert_eq!(knob.setting, 70);
}
#[test]
fn lua_methods_type_works_as_delegate() {
let lua = Lua::new();
let mut panel = Panel {
knob: Knob { setting: 0 },
};
let out: i64 = lua
.scope(|s| {
let ud = s.create_userdata_ref_mut(&lua, &mut panel)?;
lua.globals().set("panel", &ud)?;
lua.load(
r#"
local kn = panel:knob()
kn:turn(3)
kn:turn(4)
return kn:read()
"#,
)
.eval()
})
.unwrap();
assert_eq!(out, 7);
assert_eq!(panel.knob.setting, 7);
}
#[derive(LuaUserData)]
#[lua(methods)]
struct Rig {
#[lua(skip)]
knob: Knob,
#[lua(skip)]
bank: Vec<Knob>,
}
#[lua_methods]
impl Rig {
pub fn knob(&mut self) -> &mut Knob {
&mut self.knob
}
pub fn knob_ro(&self) -> &Knob {
&self.knob
}
pub fn nth(&mut self, i: i64) -> &mut Knob {
&mut self.bank[i as usize]
}
}
fn lua_err(lua: &Lua, body: &str) -> String {
let (ok, msg): (bool, String) = lua
.load(&format!(
"local ok, e = pcall(function() {body} end); return ok, tostring(e)"
))
.eval()
.expect("pcall wrapper evaluates");
assert!(!ok, "expected `{body}` to fail");
msg
}
#[test]
fn macro_mut_ref_method_becomes_delegate() {
let lua = Lua::new();
let mut rig = Rig {
knob: Knob { setting: 0 },
bank: vec![],
};
lua.scope(|s| {
let ud = s.create_userdata_ref_mut(&lua, &mut rig)?;
lua.globals().set("rig", &ud)?;
lua.load("local k = rig:knob(); k:turn(5); k:turn(2)").exec()
})
.unwrap();
assert_eq!(rig.knob.setting, 7);
}
#[test]
fn macro_shared_ref_method_becomes_delegate() {
let lua = Lua::new();
let mut rig = Rig {
knob: Knob { setting: 42 },
bank: vec![],
};
let out: i64 = lua
.scope(|s| {
let ud = s.create_userdata_ref_mut(&lua, &mut rig)?;
lua.globals().set("rig", &ud)?;
lua.load("return rig:knob_ro():read()").eval()
})
.unwrap();
assert_eq!(out, 42);
}
#[test]
fn macro_delegate_method_with_args() {
let lua = Lua::new();
let mut rig = Rig {
knob: Knob { setting: 0 },
bank: vec![
Knob { setting: 0 },
Knob { setting: 0 },
Knob { setting: 10 },
],
};
lua.scope(|s| {
let ud = s.create_userdata_ref_mut(&lua, &mut rig)?;
lua.globals().set("rig", &ud)?;
lua.load("rig:nth(2):turn(5)").exec()
})
.unwrap();
assert_eq!(rig.bank[2].setting, 15);
}
#[test]
fn macro_shared_delegate_rejects_mut_method() {
let lua = Lua::new();
let mut rig = Rig {
knob: Knob { setting: 1 },
bank: vec![],
};
let msg = lua
.scope(|s| {
let ud = s.create_userdata_ref_mut(&lua, &mut rig)?;
lua.globals().set("rig", &ud)?;
Ok(lua_err(&lua, "rig:knob_ro():turn(1)"))
})
.unwrap();
assert!(
msg.contains("read-only") || msg.contains("mutating"),
"expected read-only rejection, got: {msg}"
);
assert_eq!(rig.knob.setting, 1, "the value must be untouched");
}