Skip to content

Commit fba0ada

Browse files
committed
add ref type
1 parent 0fd63e3 commit fba0ada

8 files changed

Lines changed: 1282 additions & 7 deletions

File tree

crates/luars/src/lib.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ pub use luars_derive::lua_methods;
2424
// Re-export userdata trait types at crate root for convenience
2525
pub use lua_value::LuaUserdata;
2626
pub use lua_value::userdata_trait::{
27-
LuaEnum, LuaMethodProvider, LuaRegistrable, LuaStaticMethodProvider, UdValue, UserDataTrait,
27+
LuaEnum, LuaMethodProvider, LuaRegistrable, LuaStaticMethodProvider, OpaqueUserData, UdValue,
28+
UserDataTrait,
2829
};
30+
pub use lua_value::UserDataBuilder;
2931

3032
#[cfg(test)]
3133
use crate::lua_vm::SafeOption;
@@ -37,5 +39,7 @@ pub use lua_value::{Chunk, LuaFunction, LuaTable, LuaValue};
3739
pub use lua_vm::async_thread::{AsyncCallHandle, AsyncFuture, AsyncReturnValue, AsyncThread};
3840
pub use lua_vm::lua_error::LuaFullError;
3941
pub use lua_vm::table_builder::TableBuilder;
40-
pub use lua_vm::{Instruction, LuaResult, LuaVM, OpCode};
42+
pub use lua_vm::{
43+
Instruction, LuaAnyRef, LuaFunctionRef, LuaResult, LuaStringRef, LuaTableRef, LuaVM, OpCode,
44+
};
4145
pub use stdlib::Stdlib;

crates/luars/src/lua_value/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@ pub mod chunk_serializer;
44
pub mod lua_convert;
55
mod lua_table;
66
mod lua_value;
7+
pub mod userdata_builder;
78
pub mod userdata_trait;
89

910
use std::any::Any;
1011
use std::fmt;
1112
use std::rc::Rc;
1213

1314
pub use userdata_trait::{
14-
LuaEnum, LuaMethodProvider, LuaRegistrable, LuaStaticMethodProvider, UdValue, UserDataTrait,
15-
lua_value_to_udvalue, udvalue_to_lua_value,
15+
LuaEnum, LuaMethodProvider, LuaRegistrable, LuaStaticMethodProvider, OpaqueUserData, UdValue,
16+
UserDataTrait, lua_value_to_udvalue, udvalue_to_lua_value,
1617
};
18+
pub use userdata_builder::UserDataBuilder;
1719

1820
// Re-export the optimized LuaValue and type enum for pattern matching
1921
pub use lua_table::LuaTable;
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
//! Builder-pattern wrapper for creating userdata from third-party types.
2+
//!
3+
//! [`UserDataBuilder`] lets you expose fields, methods, and metamethods for
4+
//! types you don't control (no derive macro available).
5+
//!
6+
//! # Example
7+
//!
8+
//! ```ignore
9+
//! use std::net::SocketAddr;
10+
//! use luars::UserDataBuilder;
11+
//!
12+
//! let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
13+
//! let ud = UserDataBuilder::new(addr)
14+
//! .add_field_getter("ip", |a| UdValue::Str(a.ip().to_string()))
15+
//! .add_field_getter("port", |a| UdValue::Integer(a.port() as i64))
16+
//! .set_tostring(|a| a.to_string())
17+
//! .build(&mut vm)?;
18+
//! vm.set_global("server_addr", ud)?;
19+
//! ```
20+
21+
use std::any::Any;
22+
use std::collections::HashMap;
23+
24+
use super::LuaUserdata;
25+
use super::userdata_trait::{UdValue, UserDataTrait};
26+
27+
/// Builder for creating userdata from arbitrary types.
28+
///
29+
/// Collects field getters, field setters, and metamethods, then produces a
30+
/// [`LuaValue`](crate::LuaValue) userdata when [`build`](Self::build) is called.
31+
pub struct UserDataBuilder<T: 'static> {
32+
value: T,
33+
type_name: &'static str,
34+
field_getters: HashMap<String, Box<dyn Fn(&T) -> UdValue>>,
35+
field_setters: HashMap<String, Box<dyn Fn(&mut T, UdValue) -> Result<(), String>>>,
36+
tostring_fn: Option<Box<dyn Fn(&T) -> String>>,
37+
}
38+
39+
impl<T: 'static> UserDataBuilder<T> {
40+
/// Start building userdata wrapping `value`.
41+
///
42+
/// The default type name is `std::any::type_name::<T>()`.
43+
pub fn new(value: T) -> Self {
44+
UserDataBuilder {
45+
value,
46+
type_name: std::any::type_name::<T>(),
47+
field_getters: HashMap::new(),
48+
field_setters: HashMap::new(),
49+
tostring_fn: None,
50+
}
51+
}
52+
53+
/// Override the type name shown in Lua `type()` calls and error messages.
54+
pub fn set_type_name(mut self, name: &'static str) -> Self {
55+
self.type_name = name;
56+
self
57+
}
58+
59+
/// Register a read-only field.
60+
///
61+
/// ```ignore
62+
/// .add_field_getter("ip", |addr| UdValue::Str(addr.ip().to_string()))
63+
/// ```
64+
pub fn add_field_getter<F>(mut self, name: &str, f: F) -> Self
65+
where
66+
F: Fn(&T) -> UdValue + 'static,
67+
{
68+
self.field_getters.insert(name.to_owned(), Box::new(f));
69+
self
70+
}
71+
72+
/// Register a writable field.
73+
///
74+
/// The setter receives the new `UdValue` and should return `Ok(())` on
75+
/// success or `Err(msg)` if the value is invalid.
76+
pub fn add_field_setter<F>(mut self, name: &str, f: F) -> Self
77+
where
78+
F: Fn(&mut T, UdValue) -> Result<(), String> + 'static,
79+
{
80+
self.field_setters.insert(name.to_owned(), Box::new(f));
81+
self
82+
}
83+
84+
/// Add both a getter and a setter for the same field name.
85+
pub fn add_field<G, S>(self, name: &str, getter: G, setter: S) -> Self
86+
where
87+
G: Fn(&T) -> UdValue + 'static,
88+
S: Fn(&mut T, UdValue) -> Result<(), String> + 'static,
89+
{
90+
self.add_field_getter(name, getter)
91+
.add_field_setter(name, setter)
92+
}
93+
94+
/// Set the `__tostring` metamethod.
95+
pub fn set_tostring<F>(mut self, f: F) -> Self
96+
where
97+
F: Fn(&T) -> String + 'static,
98+
{
99+
self.tostring_fn = Some(Box::new(f));
100+
self
101+
}
102+
103+
/// Consume the builder and create a `LuaValue` userdata in the VM.
104+
pub fn build(self, vm: &mut crate::lua_vm::LuaVM) -> crate::LuaResult<crate::LuaValue> {
105+
let configured = ConfiguredUserData {
106+
value: self.value,
107+
type_name: self.type_name,
108+
field_getters: self.field_getters,
109+
field_setters: self.field_setters,
110+
tostring_fn: self.tostring_fn,
111+
};
112+
let ud = LuaUserdata::new(configured);
113+
vm.create_userdata(ud)
114+
}
115+
}
116+
117+
// ---- internal trait object wrapper ------------------------------------------
118+
119+
/// Holds the value together with the user-supplied accessor closures.
120+
struct ConfiguredUserData<T: 'static> {
121+
value: T,
122+
type_name: &'static str,
123+
field_getters: HashMap<String, Box<dyn Fn(&T) -> UdValue>>,
124+
field_setters: HashMap<String, Box<dyn Fn(&mut T, UdValue) -> Result<(), String>>>,
125+
tostring_fn: Option<Box<dyn Fn(&T) -> String>>,
126+
}
127+
128+
impl<T: 'static> UserDataTrait for ConfiguredUserData<T> {
129+
fn type_name(&self) -> &'static str {
130+
self.type_name
131+
}
132+
133+
fn get_field(&self, key: &str) -> Option<UdValue> {
134+
self.field_getters.get(key).map(|f| f(&self.value))
135+
}
136+
137+
fn set_field(&mut self, key: &str, value: UdValue) -> Option<Result<(), String>> {
138+
if let Some(setter) = self.field_setters.get(key) {
139+
Some(setter(&mut self.value, value))
140+
} else {
141+
None
142+
}
143+
}
144+
145+
fn lua_tostring(&self) -> Option<String> {
146+
self.tostring_fn.as_ref().map(|f| f(&self.value))
147+
}
148+
149+
fn field_names(&self) -> &'static [&'static str] {
150+
// Dynamic fields — we can't return a static slice of the HashMap keys,
151+
// so return empty. The getters still work via get_field dispatch.
152+
&[]
153+
}
154+
155+
fn as_any(&self) -> &dyn Any {
156+
&self.value
157+
}
158+
159+
fn as_any_mut(&mut self) -> &mut dyn Any {
160+
&mut self.value
161+
}
162+
}

crates/luars/src/lua_value/userdata_trait.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,3 +478,59 @@ pub trait LuaEnum {
478478
/// Return the enum's type name.
479479
fn enum_name() -> &'static str;
480480
}
481+
482+
// ==================== OpaqueUserData ====================
483+
484+
/// Wraps any `T: 'static` as an opaque Lua userdata.
485+
///
486+
/// No fields, methods, or metamethods are exposed — the value is a "black box"
487+
/// in Lua. From Rust you can recover the original type via `downcast_ref::<T>()`.
488+
///
489+
/// Use [`LuaVM::push_any`] to create one conveniently.
490+
///
491+
/// # Example
492+
///
493+
/// ```ignore
494+
/// // Third-party type you don't control
495+
/// let client = reqwest::Client::new();
496+
/// let ud = vm.push_any(client)?;
497+
/// vm.set_global("http_client", ud)?;
498+
///
499+
/// // Later, in a Rust callback:
500+
/// let client = ud_value.downcast_ref::<reqwest::Client>().unwrap();
501+
/// ```
502+
pub struct OpaqueUserData<T: 'static> {
503+
value: T,
504+
}
505+
506+
impl<T: 'static> OpaqueUserData<T> {
507+
/// Wrap a value.
508+
pub fn new(value: T) -> Self {
509+
OpaqueUserData { value }
510+
}
511+
512+
/// Get a reference to the inner value.
513+
pub fn inner(&self) -> &T {
514+
&self.value
515+
}
516+
517+
/// Get a mutable reference to the inner value.
518+
pub fn inner_mut(&mut self) -> &mut T {
519+
&mut self.value
520+
}
521+
}
522+
523+
impl<T: 'static> UserDataTrait for OpaqueUserData<T> {
524+
fn type_name(&self) -> &'static str {
525+
std::any::type_name::<T>()
526+
}
527+
528+
fn as_any(&self) -> &dyn Any {
529+
// Downcast to T (not OpaqueUserData<T>) for ergonomic access
530+
&self.value
531+
}
532+
533+
fn as_any_mut(&mut self) -> &mut dyn Any {
534+
&mut self.value
535+
}
536+
}

0 commit comments

Comments
 (0)