#![allow(clippy::expect_used)]
use rilua::vm::state::LuaState;
use rilua::vm::value::Userdata;
use rilua::{
FromLua, Function, Lua, LuaApi, LuaApiMut, LuaError, LuaResult, RuntimeError, StdLib, Table,
Thread, Val, runtime_error,
};
#[derive(Debug, Clone, Copy)]
struct Vec2 {
x: f64,
y: f64,
}
fn get_vec2(state: &LuaState, idx: usize) -> LuaResult<Vec2> {
let val = state.stack_get(state.base + idx);
match val {
Val::Userdata(r) => {
let ud = state
.gc
.userdata
.get(r)
.ok_or_else(|| runtime_error("userdata has been collected"))?;
ud.downcast_ref::<Vec2>()
.copied()
.ok_or_else(|| runtime_error("expected Vec2 userdata"))
}
_ => Err(runtime_error(format!(
"Vec2 expected, got {}",
val.type_name()
))),
}
}
fn push_vec2(state: &mut LuaState, v: Vec2) -> Val {
let mt = rilua::stdlib::get_registry_metatable(state, "Vec2");
let ud = match mt {
Some(mt_ref) => Userdata::with_metatable(Box::new(v), mt_ref),
None => Userdata::new(Box::new(v)),
};
let r = state.gc.alloc_userdata(ud);
Val::Userdata(r)
}
fn vec2_new(state: &mut LuaState) -> LuaResult<u32> {
let x = match state.stack_get(state.base) {
Val::Num(n) => n,
other => {
return Err(runtime_error(format!(
"vec2_new: number expected for x, got {}",
other.type_name()
)));
}
};
let y = match state.stack_get(state.base + 1) {
Val::Num(n) => n,
other => {
return Err(runtime_error(format!(
"vec2_new: number expected for y, got {}",
other.type_name()
)));
}
};
let val = push_vec2(state, Vec2 { x, y });
state.push(val);
Ok(1)
}
fn vec2_tostring(state: &mut LuaState) -> LuaResult<u32> {
let v = get_vec2(state, 0)?;
let s = format!("Vec2({}, {})", v.x, v.y);
let str_ref = state.gc.intern_string(s.as_bytes());
state.push(Val::Str(str_ref));
Ok(1)
}
fn vec2_add(state: &mut LuaState) -> LuaResult<u32> {
let a = get_vec2(state, 0)?;
let b = get_vec2(state, 1)?;
let val = push_vec2(
state,
Vec2 {
x: a.x + b.x,
y: a.y + b.y,
},
);
state.push(val);
Ok(1)
}
fn vec2_mul(state: &mut LuaState) -> LuaResult<u32> {
let a = get_vec2(state, 0)?;
let Val::Num(s) = state.stack_get(state.base + 1) else {
return Err(runtime_error(
"Vec2 __mul: number expected as second operand",
));
};
let val = push_vec2(
state,
Vec2 {
x: a.x * s,
y: a.y * s,
},
);
state.push(val);
Ok(1)
}
fn vec2_len(state: &mut LuaState) -> LuaResult<u32> {
let v = get_vec2(state, 0)?;
let mag = v.x.hypot(v.y);
state.push(Val::Num(mag));
Ok(1)
}
fn vec2_eq(state: &mut LuaState) -> LuaResult<u32> {
let a = get_vec2(state, 0)?;
let b = get_vec2(state, 1)?;
state.push(Val::Bool(a.x == b.x && a.y == b.y));
Ok(1)
}
fn rust_add_fn(state: &mut LuaState) -> LuaResult<u32> {
let a = match state.stack_get(state.base) {
Val::Num(n) => n,
other => {
return Err(runtime_error(format!(
"rust_add: number expected, got {}",
other.type_name()
)));
}
};
let b = match state.stack_get(state.base + 1) {
Val::Num(n) => n,
other => {
return Err(runtime_error(format!(
"rust_add: number expected, got {}",
other.type_name()
)));
}
};
state.push(Val::Num(a + b));
Ok(1)
}
fn setup(lua: &mut Lua) -> LuaResult<()> {
let mt = lua.create_userdata_metatable("Vec2")?;
lua.table_set_function(&mt, "__add", vec2_add)?;
lua.table_set_function(&mt, "__mul", vec2_mul)?;
lua.table_set_function(&mt, "__len", vec2_len)?;
lua.table_set_function(&mt, "__eq", vec2_eq)?;
lua.table_set_function(&mt, "__tostring", vec2_tostring)?;
lua.register_function("vec2_new", vec2_new)?;
lua.register_function("vec2_tostring", vec2_tostring)?;
lua.register_function("rust_add", rust_add_fn)?;
Ok(())
}
fn demo_call_lua_function(lua: &mut Lua) -> LuaResult<()> {
println!("\n=== Calling Lua functions from Rust ===");
let make_config: Function = lua.global("make_config")?;
let results = lua.call_function(&make_config, &[])?;
if let Some(Val::Table(_)) = results.first() {
let config: Table = Table::from_lua(results[0], lua)?;
let key_name = lua.create_string(b"name");
let key_version = lua.create_string(b"version");
let key_features = lua.create_string(b"features");
let name_val = lua.table_raw_get(&config, key_name)?;
let name = String::from_lua(name_val, lua)?;
println!("config.name = {name}");
let version_val = lua.table_raw_get(&config, key_version)?;
let version = f64::from_lua(version_val, lua)?;
println!("config.version = {version}");
let features_val = lua.table_raw_get(&config, key_features)?;
if let Val::Table(_) = features_val {
let features = Table::from_lua(features_val, lua)?;
let len = lua.table_raw_len(&features);
print!("config.features = [");
for i in 1..=len {
let v = lua.table_raw_get(&features, Val::Num(i as f64))?;
let s = String::from_lua(v, lua)?;
if i > 1 {
print!(", ");
}
print!("{s}");
}
println!("]");
}
}
Ok(())
}
fn demo_coroutine(lua: &mut Lua) -> LuaResult<()> {
println!("\n=== Coroutine interaction from Rust ===");
let make_counter: Function = lua.global("make_counter")?;
let results = lua.call_function(&make_counter, &[Val::Num(1.0), Val::Num(5.0)])?;
let thread_val = results
.first()
.copied()
.ok_or_else(|| runtime_error("make_counter returned no value"))?;
let thread = Thread::from_lua(thread_val, lua)?;
lua.set_global("_counter_co", thread)?;
println!("Resuming coroutine from Rust:");
loop {
let resume_fn: Function = lua.load("return coroutine.resume(_counter_co)")?;
let results = lua.call_function(&resume_fn, &[])?;
let ok = results.first().copied().unwrap_or(Val::Nil);
let value = results.get(1).copied().unwrap_or(Val::Nil);
match ok {
Val::Bool(true) => match value {
Val::Num(n) => println!(" yielded: {n}"),
Val::Str(_) => {
let s = String::from_lua(value, lua)?;
println!(" returned: {s}");
}
_ => println!(" returned: {value}"),
},
Val::Bool(false) => {
println!(" coroutine finished");
break;
}
_ => break,
}
}
Ok(())
}
fn demo_error_handling(lua: &mut Lua) -> LuaResult<()> {
println!("\n=== Error handling from Rust ===");
let result = lua.exec("error('boom from Lua')");
match result {
Ok(()) => println!(" (no error)"),
Err(e) => println!(" caught error: {e}"),
}
let bad_fn = lua.load("return 1 + nil")?;
let result = lua.call_function_traced(&bad_fn, &[]);
match result {
Ok(vals) => println!(" result: {vals:?}"),
Err(e) => println!(" traced error: {e}"),
}
let custom_err: LuaError = runtime_error("custom error from Rust");
println!(" custom error: {custom_err}");
let detailed_err = LuaError::Runtime(RuntimeError {
message: "detailed error".into(),
level: 1,
traceback: vec![],
});
println!(" detailed error: {detailed_err}");
Ok(())
}
fn demo_type_conversions(lua: &mut Lua) -> LuaResult<()> {
println!("\n=== Type conversions (IntoLua / FromLua) ===");
lua.set_global("my_int", 42i32)?;
lua.set_global("my_str", "hello from Rust")?;
lua.set_global("my_bool", true)?;
lua.set_global("my_float", 9.81f64)?;
lua.set_global("my_nil", Option::<i32>::None)?;
let int_val: i32 = lua.global("my_int")?;
let str_val: String = lua.global("my_str")?;
let bool_val: bool = lua.global("my_bool")?;
let float_val: f64 = lua.global("my_float")?;
let opt_val: Option<i32> = lua.global("my_nil")?;
println!(" i32: {int_val}");
println!(" String: {str_val}");
println!(" bool: {bool_val}");
println!(" f64: {float_val}");
println!(" Option: {opt_val:?}");
lua.exec("function swap(a, b) return b, a end")?;
let swap_fn: Function = lua.global("swap")?;
let results = lua.call_function(&swap_fn, &[Val::Num(1.0), Val::Num(2.0)])?;
println!(
" swap(1, 2) = ({}, {})",
results.first().copied().unwrap_or(Val::Nil),
results.get(1).copied().unwrap_or(Val::Nil),
);
Ok(())
}
fn main() {
let mut lua = Lua::new_with(StdLib::BASE | StdLib::STRING | StdLib::COROUTINE)
.expect("failed to create Lua state");
if let Err(e) = setup(&mut lua) {
eprintln!("setup error: {e}");
std::process::exit(1);
}
if let Err(e) = lua.exec_file("examples/advanced.lua") {
eprintln!("script error: {e}");
std::process::exit(1);
}
if let Err(e) = demo_type_conversions(&mut lua) {
eprintln!("type conversion error: {e}");
std::process::exit(1);
}
if let Err(e) = demo_call_lua_function(&mut lua) {
eprintln!("call lua function error: {e}");
std::process::exit(1);
}
if let Err(e) = demo_coroutine(&mut lua) {
eprintln!("coroutine error: {e}");
std::process::exit(1);
}
if let Err(e) = demo_error_handling(&mut lua) {
eprintln!("error handling error: {e}");
std::process::exit(1);
}
println!("\nAll demos completed.");
}