Code Monkey home page Code Monkey logo

Comments (6)

MetlHedd avatar MetlHedd commented on August 16, 2024

Hello, how are you doing?

I'm pretty sure that you can do something like this (maybe it's easier to do):

#[allow(dead_code)]
#[derive(Clone, Debug)]
pub struct RGBColor {
    r: u8,
    g: u8,
    b: u8
}

#[allow(dead_code)]
impl RGBColor {
    pub fn new(red: u8, green: u8, blue: u8) -> Self {
        Self {
            r: red,
            g: green,
            b: blue,
        }
    }

    pub fn to_u32(&self) -> u32 {
        let (r, g, b) = (self.r as u32, self.g as u32, self.b as u32);
        (r << 16) | (g << 8) | b
    }

    pub fn lua_color(lua: &Lua) -> LuaResult<LuaTable> {
        let exports = lua.create_table()?;

        exports.set("new", lua.create_function(RGBColor::lua_new)?)?;

        Ok(exports)
    }

    pub fn lua_new(_: &Lua, (r, g, b): (u32, u32, u32)) -> LuaResult<RGBColor> {
        Ok(RGBColor::new(r as u8, g as u8, b as u8))
    }
}

impl UserData for RGBColor {}

On impl UserData for RGBColor {} you can add methods, just like the docs does: https://docs.rs/mlua/0.4.1/mlua/trait.UserData.html

And then you can add the constructor with: lua_table.set("color", RGBColor::lua_color(&lua)?)?;.
The lua_table is a lua table (created with let lua_table = lua.create_table()?;) that is global: globals.set("rho", lua_table)?;.

The problem with your code is that your struct on action: Option<fn(&mut Player, &mut Player)>, requires a fn (https://doc.rust-lang.org/std/primitive.fn.html) function, but mlua creates an mlua::function::Function, you probably wants to check (and changes to) https://docs.rs/mlua/0.4.1/mlua/struct.Function.html and I think that you are going to get some lifetime problems, but it's probably fine to resolve.
You may and want to check this issue: mlua-rs/rlua#73

from mlua.

ohmree avatar ohmree commented on August 16, 2024

As I mentioned before I'd like the library to be extensible using Rust as well as (and optionally) Lua, so using mlua::function::Function in my Action struct ties me to mlua which isn't ideal.

The constructor you've shown looks fine, but it doesn't handle closures, while my library does need closures (I'm planning to use Lua as a declarative configuration language with bits of behavior mixed in, kind of like JSON with closures, a-la AwesomeWM).

Please do correct me if I'm wrong, but I don't think your suggestion can help my use case.

BTW, the rlua issue you mentioned was an interesting read, but what I understood from it was that this simply cannot be done. Am I correct?

from mlua.

MetlHedd avatar MetlHedd commented on August 16, 2024

I'm pretty sure that you can do what you want, I don't know exatcly if that way is possible.
Anyway, here is an example that may help you.

extern crate mlua;

use std::sync::{Arc, Mutex};

use mlua::prelude::*;
use mlua::{UserData, UserDataMethods};

#[derive(Clone, Copy)]
#[allow(dead_code)]
struct Action {
    value: i32,
    f_value: f32,
}

impl UserData for Action {
    fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
        methods.add_method("print", |_, this, ()| {
            println!("f_value: {}", this.f_value);

            Ok(())
        });
    }
}

#[derive(Clone)]
struct ActionsList {
    pub actions: Vec<Action>
}

#[allow(dead_code)]
impl ActionsList {
    pub fn new() -> Self {
        Self {
            actions: Vec::new()
        }
    }

    pub fn add_action(&mut self, action : Action) {
        self.actions.push(action)
    }

    pub fn get_action(&self, id : usize) -> Action {
        self.actions[id].clone()
    }
}

fn main() -> mlua::Result<()> {
    // Based on: https://doc.rust-lang.org/book/ch16-03-shared-state.html
    // Creates the action list
    let list = Arc::new(Mutex::new(ActionsList::new()));

    // Creates the lua context
    let lua = Lua::new();

    // We are going to make our functions calls inside a scope
    lua.scope(|scope| {
        let globals = lua.globals();

        // Creates the lua function: newAction(value, f_value) 
        globals.set("newAction", scope.create_function(|_, (v, f): (i32, f32)| {
            list.lock().unwrap().add_action(Action {
                value: v,
                f_value: f
            });

            Ok(list.lock().unwrap().actions.len()-1)
        })?)?;

        // Loads the script
        lua.load(r#"
        local test1 = newAction(32, 293.0)
        local test2 = newAction(399, 39.0)
        
        print("Test 2: " .. tostring(test2).."\n")
        
        function onAction(action_id, action)
            print(string.format("Receveid action_id, action: %d, %s", action_id, tostring(action)))
        
            action:print()
        
            if action_id == test1 then
                print("Action: test1\n")
            elseif action_id == test2 then
                print("Action: test2\n")
            end
        end
        "#).exec()?;

        // Run the test
        let on_action_fn : mlua::Function = globals.get("onAction")?;

        on_action_fn.call::<_, ()>((0, list.lock().unwrap().get_action(0)))?;
        on_action_fn.call::<_, ()>((1, list.lock().unwrap().get_action(1)))?;

        Ok(())
    })?;

    Ok(())
}

So I send the action_id as an information to lua and when an action needs to be executed the code on onAction will be responsible for it.

Console output:

Test 2: 1

Receveid action_id, action: 0, userdata: 0x55da910b0a18
f_value: 293
Action: test1

Receveid action_id, action: 1, userdata: 0x55da910b1078
f_value: 39
Action: test2

from mlua.

ohmree avatar ohmree commented on August 16, 2024

Thanks for taking the time to write the example.
So from what I understand you use action_ids or indexes to refer to actions?
That still doesn't address my issue of wanting to "tie down" (for lack of a better term) a LuaFunction as a Fn.
What if Action had a dyn Fn field and you wanted to be able to construct Actions from both Lua and Rust, passing a Lua anonymous function when constructing from Lua?

One possibility I can think of would be to use an enum like this:

enum FunctionType<'a, 'lua> {
    LuaFunction(LuaFunction<'lua>),
    RustFunction(&'a dyn Fn(&mut Player, &mut Player)),
}

And then having the function object member in Action be of that type.
However, that means I have to either compile my library with Lua all the time (and not as an optional feature) or spread a bunch of #[cfg(feature = "lua")] all over the place (at least once in the enum definition and 2+ times when checking if a function is a LuaFunction or a RustFunction, I think?).
I will probably resort to doing that if there's no better option but I'd like to avoid it if at all possible.

Edit: it should be noted that C++'s sol2 does allow for exactly the functionality I'm looking for.

from mlua.

khvzak avatar khvzak commented on August 16, 2024

You can try something like this:

#[derive(Builder, Clone, Default)]
#[builder(default)]
pub struct Action {
    name: String,
    description: String,
    health_added: i64,
    cost: i64,
    #[builder(setter(strip_option))]
    action: Option<Arc<Box<dyn Fn(&mut Player, &mut Player)>>>,
}

impl LuaUserData for Action {}

pub struct Player;

impl LuaUserData for &mut Player {}

Arc is needed to satisfy the Clone requirements.

and then

let lua = Lua::new().into_static();

let action_constructor = lua.create_function(|lua, arguments: LuaTable| {
    let mut ret = ActionBuilder::default();

    if let LuaValue::Function(action) = arguments.get("action")? {
        ret.action(Arc::new(Box::new(move |p1: &mut Player, p2: &mut Player| {
            lua.scope(|scope| {
                let p1 = scope.create_nonstatic_userdata(p1)?;
                let p2 = scope.create_nonstatic_userdata(p2)?;
                action.call::<_, ()>((p1, p2))
            }).expect("action failed");
        })));
    }

    Ok(ret.build())
})?;

should work.

Please note, that I explicitly leaked the Lua instance to make it 'static. And likely you would need unsafe block to consume the static reference and drop it at the end, like unsafe { drop(Lua::from_static(lua)) };

from mlua.

ohmree avatar ohmree commented on August 16, 2024

I can't believe I missed that - simply calling the Lua function from inside the Rust closure.

Thanks, I'll fiddle around with the code. Since this is a way to achieve my goal I'll close the issue.

from mlua.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.