-
Notifications
You must be signed in to change notification settings - Fork 116
How to store a Lua function in UserData #41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
You need |
The problem if I do that is then I get a compile error saying it could not infer an appropriate lifetime for Here's the exact code that I'm using: //! AwesomeWM drawable interface, for all things that are drawable
use std::fmt::{self, Display, Formatter};
use rustwlc::Geometry;
use cairo::ImageSurface;
use rlua::{self, Lua, UserData, AnyUserData, UserDataMethods, MetaMethod};
use ::render::Renderable;
use super::{object, class, Signal};
pub type DrawableRefreshCallback<T: UserData> = fn (&mut T);
pub struct Drawable<T: UserData> {
signals: Vec<Signal<'static>>,
surface: Option<ImageSurface>,
geometry: Geometry,
refreshed: bool,
refresh_callback: DrawableRefreshCallback<T>,
data: T
}
impl <T: UserData> Display for Drawable<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Display: {:p}", self)
}
}
impl <T: UserData> UserData for Drawable<T> {
fn add_methods(methods: &mut UserDataMethods<Self>) {
object::add_meta_methods(methods);
class::add_meta_methods(methods);
methods.add_method_mut("refresh", Drawable::refresh);
methods.add_method_mut("geometry", Drawable::geometry);
methods.add_method_mut("foo", |lua, this: &mut Drawable<T>, func: rlua::Function<'static>| {
this.signals[0].funcs.push(func);
Ok(())
});; and the compilation error:
|
I might be able to fix this if I add a lifetime parameter to the UserData trait itself, give me a bit to try that out. Just so you know though, this is almost NEVER a good idea. This bindings system is not designed to have Lua handles inside UserData, because Lua itself cannot garbage collect through a rust UserData, so you can very easily end up with unbreakable cycles. Still, I think this is possible IF the UserData trait has the lua lifetime as a parameter, which could then be set to be 'static, let me try it. |
Okay, there's now a branch called
This example now compiles, and what you want to do should now be possible, but I'm not sure actually how wise it is. Especially since this is a Function that you want to store in a UserData, you have to be 100% sure that it is not, in turn, possible to reference that UserData inside the Lua function, otherwise neither will ever be garbage collected. I haven't merged this branch with master because again, I'm not sure how useful this actually is other than to do something that's already really dangerous, but maybe it's more useful than I'm imagining? It does actually match other traits like ToLua and FromLua that way Also, how are you actually using lazy_static, since Lua is not Sync? I guess it would have to be thread local, right? |
@kyren Thanks for putting the work into make this work. I understand if you don't want this in the master branch. I'll need to think about this hard to make sure the issues you brought up don't happen. To expand on my use case: I'm trying to make Way Cooler compatibly with the libraries for the X11 window manager Awesome. Part of that is having "signals" for objects described in the "signals" section here. The data described is stored internally as EDIT: Also, if look at the documentation for Awesome closely, you'll see that the whole point of the method is to reference that userdata... so with the current setup you are correct that this will always cause an unbreakable reference cycle. Not good. The C code in the original Awesome gets around this by manually managing the reference count which you obviously shouldn't expose. I put the I do this because that's how the old Lua system in Way Cooler was designed, because Way Cooler is split into various modules that contain a lazy_static of the thing they are modifying (due to limitations when interacting with the C library that does lower level compositing). |
Would it be possible to avoid a cycle if on the I.E if I used Finally, if you're worried about the safety of this it's not actually defined for memory leaks to be unsafe in Rust terms. Of course, you still don't want memory leaks, and for the majority of cases you won't need this functionality / it won't come up. So if this comes up to be a solution for me, would it be possible to add this either as is or as a separate type with big warning documentation explaining why you should be careful to avoid cycles? |
@Timidger The problem is that |
@jonas-schievink ah, ok. Then in that case would a manual clearing of the reference to the |
That should work, yes. When the However, this opens up the possibility of memory leaks when you forget to call the function (something Lua ought to prevent in all cases IMO). As an alternative, you can try to use a weak table to store the associations you need. rlua doesn't let you create those (or at least not easily), but you can write some wrapper code in Lua. Now that I think about it, it should be possible to write a Lua wrapper for your |
Maybe there's some way to formalize this for UserData, so that instead of trying to store handles to values inside the UserData, we simply allow the methods of UserData to get / set values in some metatable storage. That way, you can store arbitrary Lua next to userdata, and it avoids all the messy lifetime issues. Let me play around with an API for this. Edit: I kind of want to solve this, because this problem tends to come up a lot. Chucklefish has absolutely run into this exact userdata reference problem before, it is not specific to rlua or rust at all (which I'm sure you guys know, but for anybody following along). |
This isn't the most ergonomic approach, but a simple solution would be to add methods to access metatable values on In fact, what I could do is just go ahead and add methods to change metatables on both What do you guys think? Edit: Also, this is basically isomorphic to not using userdata directly, and just having a Lua table with userdata and then some other data, and using functions instead of userdata methods, so something like this should already be possible to do if you don't restrict yourself to userdata methods. The features I'm suggesting are not bad though, so if this is in any way simpler, I'm happy to do it. |
I also want to point out, just in case, that Edit: I apologize if you already understand all this, it's possible you've already thought of all that and / or I misunderstand. |
@kyren Ok, so then I could store the functions directly in the metatable? Or would I still need to use a weak table as @jonas-schievink pointed out? Sounds like a good solution though.
I store the |
No, you wouldn't need a weak table because it would just be Lua functions inside of a metatable on a userdata, all stuff that Lua's gc can see normally. Weak table support in the API is unrelated, but it WOULD be possible if I had a real metatable API on Table handles, which I was going to add as part of this. |
@kyren awesome! Thanks so much for helping me through this problem. You should set up a donation link, so I can buy you a beer for this 🍺 |
Okay, I looked into this a bit more, and I forgot that currently, all UserData of a single type share the same single metatable, so I can't easily do associated UserData values like I thought, without a pretty significant performance impact. BUT, like I mentioned before, that approach is actually isomorphic to something else that's already possible. Instead of having a |
Will probably not stay like this though, see this thread: mlua-rs/rlua#41
Well, I've added userdata methods to I want to help you through making this work with way-cooler though, so if you aren't sure what I mean or how to go about using Full disclosure though, my power may go out (again) due to hurricane Irma. |
That's very kind of you haha, just buy me a beer if we meet IRL :D |
Great to hear! I'll test the changes out immediantly to make sure I have the correct idea / this fixes my problem.
Stay safe out there! |
Ok, using the latest master I whipped this up. #[derive(Clone, Debug)]
pub struct Button {
num: u32
}
impl Display for Button {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Button: {:p}", self)
}
}
impl UserData for Button {
fn add_methods(methods: &mut UserDataMethods<Self>) {
object::add_meta_methods(methods);
class::add_meta_methods(methods);
}
}
/// Makes a new button stored in a table beside its signals
pub fn new(lua: &Lua, num: u32) -> rlua::Result<Table> {
let button_table = lua.create_table();
button_table.set("data", Button { num })?;
let meta = lua.create_table();
meta.set("__index", lua.create_function(default_index))?;
meta.set("signals", lua.create_table());
meta.set("__tostring", lua.create_function(|_, button_table: Table|
Ok(format!("{}", button_table.get::<_, Button>("data").unwrap()))));
button_table.set_metatable(Some(meta));
Ok(button_table)
}
fn default_index<'lua>(lua: &'lua Lua, (button_table, index): (Table<'lua>, String))
-> rlua::Result<rlua::Value<'lua>> {
if let Ok(val) = button_table.get::<_, rlua::Value>(index.clone()) {
return Ok(val)
}
// TODO error handling
let button = button_table.raw_get::<_, Button>("data").unwrap();
match index.as_str() {
"connect_signal" => {
lua.globals().set("__temp", button_table);
Ok(rlua::Value::Function(lua.create_function(|lua, val: rlua::Value| {
let button_table = lua.globals().get::<_, Table>("__temp").unwrap();
lua.globals().set("__temp", rlua::Value::Nil);
let signals = button_table.get_metatable()
.expect("no meta")
.get::<_, Table>("signals")
.expect("signals was not a table");
signals.set(signals.len().expect("No length"), val);
Ok(())
})))
},
"emit_signal" => {
lua.globals().set("__temp", button_table);
Ok(rlua::Value::Function(lua.create_function(|lua, val: rlua::Value| {
let button_table = lua.globals().get::<_, Table>("__temp").unwrap();
lua.globals().set("__temp", rlua::Value::Nil);
let signals = button_table.get_metatable().unwrap().get::<_, Table>("signals").unwrap();
signals.get::<_,rlua::Function>(0).unwrap().call::<_,()>(button_table);
Ok(())
})))
},
"num" => Ok(rlua::Value::Number(button.num as _)),
// TODO Error here
_ => Ok(rlua::Value::Nil)
}
// TODO special "valid" property
}
pub fn init(lua: &Lua) -> rlua::Result<()> {
unimplemented!()
}
mod test {
use rlua::*;
use super::*;
#[test]
fn basic_test() {
let lua = Lua::new();
lua.globals().set("button0", new(&lua, 0).unwrap());
lua.globals().set("button1", new(&lua, 1).unwrap());
lua.eval(r#"
print(button0)
print(button0.num)
print(button1.num)
print(button0.connect_signal(function(button) button.num = 3 end))
print(button0.emit_signal())
print(button0.num)
"#,
None).unwrap()
}
} output:
This is super ugly, and I can probably find better ways to do things (like that horrible But hey, it works and that makes me happy 😁. Thanks again so much for working through this with me, this is the last nice feature rlua needed for me to justify using this over the raw C Lua bindings (which are way too low level, this is much nicer). |
I'm glad you were able to work through it, I'm sorry that the table method is a bit verbose. There's this whole API for adding methods to userdata and absolutely none for tables, so it's a tad annoying that it takes so much work to do the same for a table. I'll try and come up with some way to make this easier, and maybe have the metatable API work for both. I'm convinced though that storing lua values inside lua is the right approach as opposed to storing lua handles in a userdata, so this is at least in the right direction. |
Will probably not stay like this though, see this thread: mlua-rs/rlua#41
I finished up my implementation that relies on this (as you can see in the linked PR). Any update on when this will be released in a v0.9.2? |
Ugh, sorry for taking a while on that, I'll do it today. I still need to merge a few PRs still and also write a changelog including changes back in history before I do another release. |
No worries, take your time. I probably won't merge my branch immediately since I still want to review it and maybe add on to it. Just wanted to check the status / make sure the API wasn't going to change. |
I might actually make a change to the API in the near future, in regards to issue #38, but it won't be in the 0.9 version. We follow pre-1.0 semver (mostly). |
Awesome looks like this has been released with |
Will probably not stay like this though, see this thread: mlua-rs/rlua#41
Sorry to bump this old thread but I have a question now after reading through this again and after discussing my design with @psychon over on the Way Cooler gitter channel. Is there a reason this problem (of storing Lua data alongside Rust user data) can't be solved by exposing a way to set the user value ala lua_setuservalue? Is there a safety hole by binding a table to a user data that I'm not aware of? |
Seems like that's the canonical solution to this problem. The uservalue is a transparent reference that can be seen by the GC, which solves the problem outlined above. |
The reason I didn't implement an API using lua_setuservalue is simply because I didn't know it existed... TIL. I'll implement something using this shortly. What would you guys prefer, an API to set a single arbitrary Lua value as the associated user value, or an API that assumed a table? Considering you can implement the second in terms of the first, I would assume the first would be better, but it's a bit more work for the obvious use cases. |
Okay, the simpler version of that API is added. |
I need to store a user-provided Lua function in a struct that implements
UserData
. I tried usingUserDataMethods
to add a method that takes in a function and stores it but I keep running into lifetime errors.rlua::Lua
is expected to live for the entirety of the program (and in fact I put it in alazy_static!
so that its address is always stable), but I can't find a way to indicate in the custom user data method thatrlua::Lua
will be'static
and there's no way for me to convince it that the anonymous'lua
lifetime for the passed inrlua::Lua
reference will live longer than the reference I store in theUserData
.Here's a minimized example of the issue I'm running into:
The text was updated successfully, but these errors were encountered: